/*
 * $Id: avpops.c,v 1.6.2.1 2005/01/20 10:01:37 ramona Exp $
 *
 * Copyright (C) 2004 Voice Sistem SRL
 *
 * This file is part of SIP Express Router.
 *
 * AVPOPS SER-module is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * AVPOPS SER-module is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * For any questions about this software and its license, please contact
 * Voice Sistem at following e-mail address:
 *         office@voice-sistem.ro
 *
 *
 * History:
 * ---------
 *  2004-10-04  first version (ramona)
 *  2004-11-15  added support for db schemes for avp_db_load (ramona)
 *  2004-11-17  aligned to new AVP core global aliases (ramona)
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h> /* for regex */
#include <regex.h>

#include "../../mem/shm_mem.h"
#include "../../mem/mem.h"
#include "../../sr_module.h"
#include "../../str.h"
#include "../../dprint.h"
#include "../../error.h"
#include "avpops_parse.h"
#include "avpops_impl.h"
#include "avpops_db.h"


MODULE_VERSION

/* modules param variables */
static char *DB_URL        = 0;  /* database url */
static char *DB_TABLE      = 0;  /* table */
static int  use_domain     = 0;  /* if domain should be use for avp matching */
static char *db_columns[6] = {"uuid","attribute","value",
                              "type","username","domain"};


static int avpops_init(void);
static int avpops_child_init(int rank);

static int register_galiases( modparam_t type, void* val);
static int fixup_db_load_avp(void** param, int param_no);
static int fixup_db_delete_avp(void** param, int param_no);
static int fixup_db_store_avp(void** param, int param_no);
static int fixup_write_avp(void** param, int param_no);
static int fixup_delete_avp(void** param, int param_no);
static int fixup_pushto_avp(void** param, int param_no);
static int fixup_check_avp(void** param, int param_no);

static int w_dbload_avps(struct sip_msg* msg, char* source, char* param);
static int w_dbstore_avps(struct sip_msg* msg, char* source, char* param);
static int w_dbdelete_avps(struct sip_msg* msg, char* source, char* param);
static int w_write_avps(struct sip_msg* msg, char* source, char* param);
static int w_delete_avps(struct sip_msg* msg, char* param, char *foo);
static int w_pushto_avps(struct sip_msg* msg, char* destination, char *param);
static int w_check_avps(struct sip_msg* msg, char* param, char *check);
static int w_print_avps(struct sip_msg* msg, char* foo, char *bar);



/*
 * Exported functions
 */
static cmd_export_t cmds[] = {
	{"avp_db_load", w_dbload_avps, 2, fixup_db_load_avp,
									REQUEST_ROUTE|FAILURE_ROUTE},
	{"avp_db_store", w_dbstore_avps, 2, fixup_db_store_avp,
									REQUEST_ROUTE|FAILURE_ROUTE},
	{"avp_db_delete", w_dbdelete_avps, 2, fixup_db_delete_avp,
									REQUEST_ROUTE|FAILURE_ROUTE},
	{"avp_write", w_write_avps, 2, fixup_write_avp,
									REQUEST_ROUTE|FAILURE_ROUTE},
	{"avp_delete", w_delete_avps, 1, fixup_delete_avp,
									REQUEST_ROUTE|FAILURE_ROUTE},
	{"avp_pushto", w_pushto_avps, 2, fixup_pushto_avp,
									REQUEST_ROUTE|FAILURE_ROUTE},
	{"avp_check", w_check_avps, 2, fixup_check_avp,
									REQUEST_ROUTE|FAILURE_ROUTE},
	{"avp_print", w_print_avps, 0, 0,
									REQUEST_ROUTE|FAILURE_ROUTE},
	{0, 0, 0, 0, 0}
};


/*
 * Exported parameters
 */
static param_export_t params[] = {
	{"avp_url",           STR_PARAM, &DB_URL         },
	{"avp_table",         STR_PARAM, &DB_TABLE       },
	{"avp_aliases",       STR_PARAM|USE_FUNC_PARAM, (void*)register_galiases },
	{"use_domain",        INT_PARAM, &use_domain     },
	{"uuid_column",       STR_PARAM, &db_columns[0]  },
	{"attribute_column",  STR_PARAM, &db_columns[1]  },
	{"value_column",      STR_PARAM, &db_columns[2]  },
	{"type_column",       STR_PARAM, &db_columns[3]  },
	{"username_column",   STR_PARAM, &db_columns[4]  },
	{"domain_column",     STR_PARAM, &db_columns[5]  },
	{"db_scheme",         STR_PARAM|USE_FUNC_PARAM, (void*)avp_add_db_scheme },
	{0, 0, 0}
};


struct module_exports exports = {
	"avpops",
	cmds,     /* Exported functions */
	params,   /* Exported parameters */
	avpops_init, /* Module initialization function */
	(response_function) 0,
	(destroy_function) 0,
	0,
	(child_init_function) avpops_child_init /* per-child init function */
};



static int register_galiases( modparam_t type, void* val)
{
	
	if (val!=0 && ((char*)val)[0]!=0)
	{
		if ( add_avp_galias_str((char*)val)!=0 )
			return -1;
	}

	return 0;
}


static int avpops_init(void)
{
	LOG(L_INFO,"AVPops - initializing\n");

	/* if DB_URL defined -> bind to a DB module */
	if (DB_URL!=0)
	{
		/* check AVP_TABLE param */
		if (DB_TABLE==0)
		{
			LOG(L_CRIT,"ERROR:avpops_init: \"AVP_DB\" present but "
				"\"AVP_TABLE\" found empty\n");
			goto error;
		}
		/* bind to the DB module */
		if (avpops_db_bind(DB_URL)<0)
			goto error;
	}

	init_store_avps( db_columns );

	return 0;
error:
	return -1;
}


static int avpops_child_init(int rank)
{
	/* init DB only if enabled */
	if (DB_URL==0)
		return 0;
	/* skip main process and TCP manager process */
	if (rank==PROC_MAIN || rank==PROC_TCP_MAIN)
		return 0;
	/* init DB connection */
	return avpops_db_init(DB_URL, DB_TABLE, db_columns);
}



static struct fis_param *get_attr_or_alias(char *s)
{
	struct fis_param *ap;
	char *p;
	int type;
	str alias;

	/* compose the param structure */
	ap = (struct fis_param*)pkg_malloc(sizeof(struct fis_param));
	if (ap==0)
	{
		LOG(L_ERR,"ERROR:avpops:get_attr_or_alias: no more pkg mem\n");
		goto error;
	}
	memset( ap, 0, sizeof(struct fis_param));

	if (*s=='$')
	{
		/* alias */
		alias .s = s+1;
		alias.len = strlen(alias.s);
		if (lookup_avp_galias( &alias, &type, &ap->val)==-1)
		{
			LOG(L_ERR,"ERROR:avpops:get_attr_or_alias: unknow alias"
				"\"%s\"\n", s+1);
			goto error;
		}
		ap->flags |= (type&AVP_NAME_STR)?AVPOPS_VAL_STR:AVPOPS_VAL_INT;
	} else {
		if ( (p=parse_avp_attr( s, ap, 0))==0 || *p!=0)
		{
			LOG(L_ERR,"ERROR:avpops:get_attr_or_alias: failed to parse "
				"attribute name <%s>\n", s);
			goto error;
		}
	}
	ap->flags |= AVPOPS_VAL_AVP;
	return ap;
error:
	return 0;
}


static int fixup_db_avp(void** param, int param_no, int allow_scheme)
{
	struct fis_param *sp;
	struct db_param  *dbp;
	int flags;
	str alias;
	char *s;
	char *p;

	flags=0;
	if (DB_URL==0)
	{
		LOG(L_ERR,"ERROR:avpops:fixup_db_avp: you have to config a db url "
			"for using avp_db_xxx functions\n");
		return E_UNSPEC;
	}

	s = (char*)*param;
	if (param_no==1)
	{
		/* prepare the fis_param structure */
		sp = (struct fis_param*)pkg_malloc(sizeof(struct fis_param));
		if (sp==0) {
			LOG(L_ERR,"ERROR:avpops:fixup_db_avp: no more pkg mem\n");
			return E_OUT_OF_MEM;
		}
		memset( sp, 0, sizeof(struct fis_param));

		if (*s!='$')
		{
			/* is a constant string -> use it as uuid*/
			sp->flags = AVPOPS_VAL_STR;
			sp->val.s = (str*)pkg_malloc(strlen(s)+1+sizeof(str));
			if (sp->val.s==0) {
				LOG(L_ERR,"ERROR:avpops:fixup_db_avp: no more pkg mem\n");
				return E_OUT_OF_MEM;
			}
			sp->val.s->s = ((char*)sp->val.s) + sizeof(str);
			sp->val.s->len = strlen(s);
			strcpy(sp->val.s->s,s);
		} else {
			/* is a variable $xxxxx */
			s++;
			if ( (p=strchr(s,'/'))!=0)
				*(p++) = 0;
			if ( (!strcasecmp( "from", s) && (flags|=AVPOPS_USE_FROM)) 
			|| (!strcasecmp( "to", s) && (flags|=AVPOPS_USE_TO)) 
			|| (!strcasecmp( "ruri", s) && (flags|=AVPOPS_USE_RURI)) )
			{
				/* check for extra flags/params */
				if (p&&(!strcasecmp("domain",p)&&!(flags|=AVPOPS_FLAG_DOMAIN)))
				{
					LOG(L_ERR,"ERROR:avpops:fixup_db_avp: unknow flag "
						"<%s>\n",p);
					return E_UNSPEC;
				}
				memset( sp, 0, sizeof(struct fis_param));
				sp->flags = flags|AVPOPS_VAL_NONE;
			} else {
				/* can be only an AVP alias */
				alias .s = s;
				alias.len = strlen(alias.s);
				if ( p || lookup_avp_galias( &alias, &flags, &sp->val)==-1 )
				{
					LOG(L_ERR,"ERROR:avpops:fixup_db_avp: source/flags \"%s\""
						" unknown!\n",s);
					return E_UNSPEC;
				}
				sp->flags = AVPOPS_VAL_AVP |
					((flags&AVP_NAME_STR)?AVPOPS_VAL_STR:AVPOPS_VAL_INT);
			}
		}
		pkg_free(*param);
		*param=(void*)sp;
	} else if (param_no==2) {
		/* compose the db_param structure */
		dbp = (struct db_param*)pkg_malloc(sizeof(struct db_param));
		if (dbp==0)
		{
			LOG(L_ERR,"ERROR:avpops:fixup_db_avp: no more pkg mem\n");
			return E_OUT_OF_MEM;
		}
		memset( dbp, 0, sizeof(struct db_param));
		if ( parse_avp_db( s, dbp, allow_scheme)!=0 )
		{
			LOG(L_ERR,"ERROR:avpops:fixup_db_avp: parse failed\n");
			return E_UNSPEC;
		}
		pkg_free(*param);
		*param=(void*)dbp;
	}

	return 0;
}


static int fixup_db_load_avp(void** param, int param_no)
{
	return fixup_db_avp( param, param_no, 1/*allow scheme*/);
}


static int fixup_db_delete_avp(void** param, int param_no)
{
	return fixup_db_avp( param, param_no, 0/*no scheme*/);
}


static int fixup_db_store_avp(void** param, int param_no)
{
	return fixup_db_avp( param, param_no, 0/*no scheme*/);
}


static int fixup_write_avp(void** param, int param_no)
{
	struct fis_param *ap;
	int  flags;
	char *s;
	char *p;

	flags=0;
	s = (char*)*param;
	ap = 0 ;
	
	if (param_no==1)
	{
		if ( *s=='$' )
		{
			/* is variable */
			if ((++s)==0)
			{
				LOG(L_ERR,"ERROR:avops:fixup_write_avp: bad param 1; "
					"expected : $[from|to|ruri] or int/str value\n");
				return E_UNSPEC;
			}
			if ( (p=strchr(s,'/'))!=0)
				*(p++) = 0;
			if ( (!strcasecmp( "from", s) && (flags|=AVPOPS_USE_FROM))
				|| (!strcasecmp( "to", s) && (flags|=AVPOPS_USE_TO))
				|| (!strcasecmp( "ruri", s) && (flags|=AVPOPS_USE_RURI))
				|| (!strcasecmp( "src_ip", s) && (flags|=AVPOPS_USE_SRC_IP)) )
			{
				ap = (struct fis_param*)pkg_malloc(sizeof(struct fis_param));
				if (ap==0)
				{
					LOG(L_ERR,"ERROR:avpops:fixup_write_avp: no more "
						"pkg mem\n");
					return E_OUT_OF_MEM;
				}
				memset( ap, 0, sizeof(struct fis_param));
				/* any falgs ? */
				if ( p && !( (flags&AVPOPS_USE_SRC_IP)==0 && (
				(!strcasecmp("username",p) && (flags|=AVPOPS_FLAG_USER)) ||
				(!strcasecmp("domain", p) && (flags|=AVPOPS_FLAG_DOMAIN)))) )
				{
					LOG(L_ERR,"ERROR:avpops:fixup_write_avp: flag \"%s\""
						" unknown!\n", p);
					return E_UNSPEC;
				}
				ap->flags = flags|AVPOPS_VAL_NONE;
			} else {
				LOG(L_ERR,"ERROR:avpops:fixup_write_avp: source \"%s\""
					" unknown!\n", s);
				return E_UNSPEC;
			}
		} else {
			/* is value */
			if ( (ap=parse_intstr_value(s,strlen(s)))==0 )
			{
				LOG(L_ERR,"ERROR:avops:fixup_write_avp: bad param 1; "
					"expected : $[from|to|ruri] or int/str value\n");
				return E_UNSPEC;
			}
		}
	} else if (param_no==2) {
		if ( (ap=get_attr_or_alias(s))==0 )
		{
			LOG(L_ERR,"ERROR:avpops:fixup_write_avp: bad attribute name"
				"/alias <%s>\n", s);
			return E_UNSPEC;
		}
		/* attr name is mandatory */
		if (ap->flags&AVPOPS_VAL_NONE)
		{
			LOG(L_ERR,"ERROR:avpops:fixup_write_avp: you must specify "
				"a name for the AVP\n");
			return E_UNSPEC;
		}
	}

	pkg_free(*param);
	*param=(void*)ap;
	return 0;
}


static int fixup_delete_avp(void** param, int param_no)
{
	struct fis_param *ap;
	char *p;

	if (param_no==1) {
		/* attribute name / alias */
		if ( (p=strchr((char*)*param,'/'))!=0 )
			*(p++)=0;
		if ( (ap=get_attr_or_alias((char*)*param))==0 )
		{
			LOG(L_ERR,"ERROR:avpops:fixup_delete_avp: bad attribute name"
				"/alias <%s>\n", (char*)*param);
			return E_UNSPEC;
		}
		/* flags */
		for( ; p&&*p ; p++ )
		{
			switch (*p)
			{
				case 'g':
				case 'G':
					ap->flags|=AVPOPS_FLAG_ALL;
					break;
				default:
					LOG(L_ERR,"ERROR:avpops:fixup_delete_avp: bad flag "
						"<%c>\n",*p);
					return E_UNSPEC;
			}
		}
		/* force some flags: if no avp name is given, force "all" flag */
		if (ap->flags&AVPOPS_VAL_NONE)
			ap->flags |= AVPOPS_FLAG_ALL;

		pkg_free(*param);
		*param=(void*)ap;
	}

	return 0;
}


static int fixup_pushto_avp(void** param, int param_no)
{
	struct fis_param *ap;
	char *s;
	char *p;

	s = (char*)*param;
	ap = 0;

	if (param_no==1)
	{
		if ( *s!='$' || (++s)==0)
		{
			LOG(L_ERR,"ERROR:avops:fixup_pushto_avp: bad param 1; expected : "
				"$[ruri|hdr_name|..]\n");
			return E_UNSPEC;
		}
		/* compose the param structure */
		ap = (struct fis_param*)pkg_malloc(sizeof(struct fis_param));
		if (ap==0)
		{
			LOG(L_ERR,"ERROR:avpops:fixup_pushto_avp: no more pkg mem\n");
			return E_OUT_OF_MEM;
		}
		memset( ap, 0, sizeof(struct fis_param));

		if ( (p=strchr((char*)*param,'/'))!=0 )
			*(p++)=0;
		if (!strcasecmp( "ruri", s))
		{
			ap->flags = AVPOPS_VAL_NONE|AVPOPS_USE_RURI;
			if ( p && !(
				(!strcasecmp("username",p) && (ap->flags|=AVPOPS_FLAG_USER)) ||
				(!strcasecmp("domain",p) && (ap->flags|=AVPOPS_FLAG_DOMAIN)) ))
			{
				LOG(L_ERR,"ERROR:avpops:fixup_pushto_avp: unknown "
					" ruri flag \"%s\"!\n",p);
				return E_UNSPEC;
			}
		} else {
			/* what's the hdr destination ? request or reply? */
			if ( p==0 )
			{
				ap->flags = AVPOPS_USE_HDRREQ;
			} else {
				if (!strcasecmp( "request", p))
					ap->flags = AVPOPS_USE_HDRREQ;
				else if (!strcasecmp( "reply", p))
					ap->flags = AVPOPS_USE_HDRRPL;
				else
				{
					LOG(L_ERR,"ERROR:avpops:fixup_pushto_avp: header "
						"destination \"%s\" unknown!\n",p);
					return E_UNSPEC;
				}
			}
			/* copy header name */
			ap->val.s = (str*)pkg_malloc( sizeof(str) + strlen(s) + 1 );
			if (ap->val.s==0)
			{
				LOG(L_ERR,"ERROR:avpops:fixup_pushto_avp: no more pkg mem\n");
				return E_OUT_OF_MEM;
			}
			ap->val.s->s = ((char*)ap->val.s) + sizeof(str);
			ap->val.s->len = strlen(s);
			strcpy( ap->val.s->s, s);
		}
	} else if (param_no==2) {
		/* attribute name / alias */
		if ( (p=strchr(s,'/'))!=0 )
			*(p++)=0;
		if ( (ap=get_attr_or_alias(s))==0 )
		{
			LOG(L_ERR,"ERROR:avpops:fixup_pushto_avp: bad attribute name"
				"/alias <%s>\n", (char*)*param);
			return E_UNSPEC;
		}
		/* attr name is mandatory */
		if (ap->flags&AVPOPS_VAL_NONE)
		{
			LOG(L_ERR,"ERROR:avpops:fixup_pushto_avp: you must specify "
				"a name for the AVP\n");
			return E_UNSPEC;
		}
		/* flags */
		for( ; p&&*p ; p++ )
		{
			switch (*p) {
				case 'g':
				case 'G':
					ap->flags|=AVPOPS_FLAG_ALL;
					break;
				default:
					LOG(L_ERR,"ERROR:avpops:fixup_pushto_avp: bad flag "
						"<%c>\n",*p);
					return E_UNSPEC;
			}
		}
	}

	pkg_free(*param);
	*param=(void*)ap;
	return 0;
}



static int fixup_check_avp(void** param, int param_no)
{
	struct fis_param *ap;
	regex_t* re;
	char *s;

	s = (char*)*param;
	ap = 0;

	if (param_no==1)
	{
		if ( (ap=get_attr_or_alias(s))==0 )
		{
			LOG(L_ERR,"ERROR:avpops:fixup_check_avp: bad attribute name"
				"/alias <%s>\n", (char*)*param);
			return E_UNSPEC;
		}
		/* attr name is mandatory */
		if (ap->flags&AVPOPS_VAL_NONE)
		{
			LOG(L_ERR,"ERROR:avpops:fixup_check_avp: you must specify "
				"a name for the AVP\n");
			return E_UNSPEC;
		}
	} else if (param_no==2) {
		if ( (ap=parse_check_value(s))==0 )
		{
			LOG(L_ERR,"ERROR:avpops:fixup_check_avp: failed to parse "
				"checked value \n");
			return E_UNSPEC;
		}
		/* if REGEXP op -> compile the expresion */
		if (ap->flags&AVPOPS_OP_RE)
		{
			if ( (ap->flags&AVPOPS_VAL_STR)==0 )
			{
				LOG(L_ERR,"ERROR:avpops:fixup_check_avp: regexp operation"
					"requires string value\n");
				return E_UNSPEC;
			}
			re = pkg_malloc(sizeof(regex_t));
			if (re==0)
			{
				LOG(L_ERR,"ERROR:avpops:fixup_check_avp: no more pkg mem\n");
				return E_OUT_OF_MEM;
			}
			DBG("DEBUG:avpops:fixup_check_avp: compiling regexp <%s>\n",
				ap->val.s->s);
			if (regcomp(re, ap->val.s->s, REG_EXTENDED|REG_ICASE|REG_NEWLINE))
			{
				pkg_free(re);
				LOG(L_ERR,"ERROR:avpops:fixip_check_avp: bad re <%s>\n",
					ap->val.s->s);
				return E_BAD_RE;
			}
			/* free the string and link the regexp */
			pkg_free(ap->val.s);
			ap->val.s = (str*)re;
		}
	}

	pkg_free(*param);
	*param=(void*)ap;
	return 0;
}


static int w_dbload_avps(struct sip_msg* msg, char* source, char* param)
{
	return ops_dbload_avps ( msg, (struct fis_param*)source,
								(struct db_param*)param, use_domain);
}


static int w_dbstore_avps(struct sip_msg* msg, char* source, char* param)
{
	return ops_dbstore_avps ( msg, (struct fis_param*)source,
								(struct db_param*)param, use_domain);
}


static int w_dbdelete_avps(struct sip_msg* msg, char* source, char* param)
{
	return ops_dbdelete_avps ( msg, (struct fis_param*)source,
								(struct db_param*)param, use_domain);
}


static int w_write_avps(struct sip_msg* msg, char* source, char* param)
{
	return ops_write_avp ( msg, (struct fis_param*)source, 
								(struct fis_param*)param);
}


static int w_delete_avps(struct sip_msg* msg, char* param, char* foo)
{
	return ops_delete_avp ( msg, (struct fis_param*)param);
}


static int w_pushto_avps(struct sip_msg* msg, char* destination, char *param)
{
	return ops_pushto_avp ( msg, (struct fis_param*)destination,
								(struct fis_param*)param);
}


static int w_check_avps(struct sip_msg* msg, char* param, char *check)
{
	return ops_check_avp ( msg, (struct fis_param*)param,
								(struct fis_param*)check);
}

static int w_print_avps(struct sip_msg* msg, char* foo, char *bar)
{
	return ops_print_avp();
}




syntax highlighted by Code2HTML, v. 0.9.1