/*
 * Copyright (C) 2004 FhG Fokus
 *
 * This file is part of ser, a free SIP server.
 *
 * ser 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
 *
 * For a license to use the ser software under conditions
 * other than those described here, or to purchase support for this
 * software, please contact iptel.org by e-mail at the following addresses:
 *    info@iptel.org
 *
 * ser 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <string.h>
#include <stdlib.h>
#ifdef EXTRA_DEBUG
#include <assert.h>
#endif
#include "../../core/sr_module.h"
#include "../../core/error.h"
#include "../../core/lump_struct.h"
#include "../../core/data_lump.h"
#include "../../core/data_lump_rpl.h"
#include "../../core/usr_avp.h"
#include "../../core/mem/mem.h"
#include "../../core/parser/parse_uri.h"
#include "../../core/parser/msg_parser.h"
#include "../../core/parser/parse_nameaddr.h"
#include "../../core/ut.h"
#include "../../core/dset.h"
#include "../../core/trim.h"
#include "../../core/str.h"
#include "../../core/dprint.h"
#include "../../core/re.h"
#include "../../core/action.h"

#include "../../core/parser/parse_hname2.h"
#include "../xprint/xp_lib.h"
#define NO_SCRIPT -1

MODULE_VERSION

/* name of attributed used to store flags with command flags2attr */
#define HDR_ID 0
#define HDR_STR 1

#define PARAM_DELIM '/'
#define VAL_TYPE_INT (1 << 0)
#define VAL_TYPE_STR (1 << 1)

typedef struct hdr_name
{
	enum
	{
		hdrId,
		hdrStr
	} kind;
	union
	{
		int n;
		str s;
	} name;
	char field_delimiter;
	char array_delimiter;
	int val_types;
} hdr_name_t;

static int xlbuf_size = 256;
static char *xlbuf = NULL;
static str *xl_nul = NULL;
static xl_print_log_f *xl_print = NULL;
static xl_parse_format_f *xl_parse = NULL;
static xl_elog_free_all_f *xl_free = NULL;
static xl_get_nulstr_f *xl_getnul = NULL;

static int mod_init();
static int set_iattr(struct sip_msg *msg, char *p1, char *p2);
static int set_sattr(struct sip_msg *msg, char *p1, char *p2);
static int print_attr(struct sip_msg *msg, char *p1, char *p2);
static int del_attr(struct sip_msg *msg, char *p1, char *p2);
static int subst_attr(struct sip_msg *msg, char *p1, char *p2);
static int flags2attr(struct sip_msg *msg, char *p1, char *p2);
static int attr2uri(struct sip_msg *msg, char *p1, char *p2);
static int dump_attrs(struct sip_msg *msg, char *p1, char *p2);
static int attr_equals(struct sip_msg *msg, char *p1, char *p2);
static int attr_exists(struct sip_msg *msg, char *p1, char *p2);
static int attr_equals_xl(struct sip_msg *msg, char *p1, char *p2);
static int xlset_attr(struct sip_msg *msg, char *p1, char *p2);
static int xlfix_attr(struct sip_msg *msg, char *p1, char *p2);
static int insert_req(struct sip_msg *msg, char *p1, char *p2);
static int append_req(struct sip_msg *msg, char *p1, char *p2);
static int replace_req(struct sip_msg *msg, char *p1, char *p2);
static int append_reply(struct sip_msg *msg, char *p1, char *p2);
static int attr_destination(struct sip_msg *msg, char *p1, char *p2);
static int xlset_destination(struct sip_msg *msg, char *p1, char *p2);
static int attr_hdr_body2attrs(struct sip_msg *msg, char *p1, char *p2);
static int attr_hdr_body2attrs2(struct sip_msg *msg, char *p1, char *p2);
static int del_attrs(struct sip_msg *msg, char *p1, char *p2);

static int set_iattr_fixup(void **, int);
static int avpid_fixup(void **, int);
static int subst_attr_fixup(void **, int);
static int fixup_part(void **, int);
static int fixup_xl_1(void **, int);
static int fixup_attr_1_xl_2(void **, int);
static int fixup_str_1_attr_2(void **, int);
static int xlfix_attr_fixup(void **param, int param_no);
static int attr_hdr_body2attrs_fixup(void **, int);
static int attr_hdr_body2attrs2_fixup(void **, int);
static int avpgroup_fixup(void **, int);

/*
 * Exported functions
 */
static cmd_export_t cmds[] = {
		{"set_iattr", set_iattr, 2, set_iattr_fixup, 0,
				REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
		{"set_sattr", set_sattr, 2, fixup_var_str_12, 0,
				REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
		{"set_attr", set_sattr, 2, fixup_var_str_12, 0,
				REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
		{"print_attr", print_attr, 1, avpid_fixup, 0,
				REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
		{"del_attr", del_attr, 1, avpid_fixup, 0,
				REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
		{"del_attrs", del_attrs, 1, avpgroup_fixup, 0,
				REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
		{"subst_attr", subst_attr, 2, subst_attr_fixup, 0,
				REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
		{"flags2attr", flags2attr, 1, avpid_fixup, 0,
				REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
		{"attr2uri", attr2uri, 1, fixup_part, 0, REQUEST_ROUTE | FAILURE_ROUTE},
		{"attr2uri", attr2uri, 2, fixup_part, 0, REQUEST_ROUTE | FAILURE_ROUTE},
		{"dump_attrs", dump_attrs, 0, 0, 0,
				REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
		{"dump_attrs", dump_attrs, 1, avpgroup_fixup, 0,
				REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
		{"attr_equals", attr_equals, 2, fixup_var_str_12, 0,
				REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
		{"attr_exists", attr_exists, 1, fixup_var_str_1, 0,
				REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
		{"attr_equals_xl", attr_equals_xl, 2, fixup_attr_1_xl_2, 0,
				REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
		{"xlset_attr", xlset_attr, 2, fixup_attr_1_xl_2, 0,
				REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
		{"xlfix_attr", xlfix_attr, 1, xlfix_attr_fixup, 0,
				REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
		{"insert_attr_hf", insert_req, 2, fixup_str_1_attr_2, 0,
				REQUEST_ROUTE | FAILURE_ROUTE},
		{"insert_attr_hf", insert_req, 1, fixup_str_1_attr_2, 0,
				REQUEST_ROUTE | FAILURE_ROUTE},
		{"append_attr_hf", append_req, 2, fixup_str_1_attr_2, 0,
				REQUEST_ROUTE | FAILURE_ROUTE},
		{"append_attr_hf", append_req, 1, fixup_str_1_attr_2, 0,
				REQUEST_ROUTE | FAILURE_ROUTE},
		{"replace_attr_hf", replace_req, 2, fixup_str_1_attr_2, 0,
				REQUEST_ROUTE | FAILURE_ROUTE},
		{"replace_attr_hf", replace_req, 1, fixup_str_1_attr_2, 0,
				REQUEST_ROUTE | FAILURE_ROUTE},
		{"attr_to_reply", append_reply, 2, fixup_str_1_attr_2, 0,
				REQUEST_ROUTE | FAILURE_ROUTE},
		{"attr_to_reply", append_reply, 1, fixup_str_1_attr_2, 0,
				REQUEST_ROUTE | FAILURE_ROUTE},
		{"attr_destination", attr_destination, 1, avpid_fixup, 0,
				REQUEST_ROUTE | FAILURE_ROUTE},
		{"xlset_destination", xlset_destination, 1, fixup_xl_1, 0,
				REQUEST_ROUTE},
		{"hdr_body2attrs", attr_hdr_body2attrs, 2, attr_hdr_body2attrs_fixup, 0,
				REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
		{"hdr_body2attrs2", attr_hdr_body2attrs2, 2, attr_hdr_body2attrs2_fixup,
				0, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
		{0, 0, 0, 0, 0, 0}};


/*
 * Exported parameters
 */
static param_export_t params[] = {
		{"xlbuf_size", PARAM_INT, &xlbuf_size}, {0, 0, 0}};


struct module_exports exports = {
		"avp", DEFAULT_DLFLAGS, /* dlopen flags */
		cmds,					/* Exported commands */
		params,					/* Exported parameters */
		0,						/* RPC */
		0,						/* pseudo-variables exports */
		0,						/* response function*/
		mod_init,				/* module initialization function */
		0,						/* per-child init function */
		0						/* destroy function */
};


static int set_iattr_fixup(void **param, int param_no)
{
	if(param_no == 1) {
		return fixup_var_str_12(param, param_no);
	} else {
		return fixup_var_int_12(param, param_no);
	}
}


static int get_avp_id(avp_ident_t *id, fparam_t *p, struct sip_msg *msg)
{
	str str_id;
	avp_t *avp;
	avp_value_t val;
	int ret;

	switch(p->type) {
		case FPARAM_AVP:
			avp = search_avp(p->v.avp, &val, 0);
			if(!avp) {
				DBG("get_avp_id: AVP %s does not exist\n", p->orig);
				return -1;
			}
			if((avp->flags & AVP_VAL_STR) == 0) {
				DBG("get_avp_id: Not a string AVP\n");
				return -1;
			}
			str_id = val.s;
			break;

		case FPARAM_SELECT:
			ret = run_select(&str_id, p->v.select, msg);
			if(ret < 0 || ret > 0)
				return -1;
			break;

		case FPARAM_STR:
			str_id = p->v.str;
			break;

		default:
			ERR("Invalid parameter type in get_avp_id\n");
			return -1;
	}

	return parse_avp_ident(&str_id, id);
}


static int set_iattr(struct sip_msg *msg, char *p1, char *p2)
{
	avp_ident_t avpid;
	int_str value;
	int i;

	if(get_avp_id(&avpid, (fparam_t *)p1, msg) < 0) {
		return -1;
	}

	if(get_int_fparam(&i, msg, (fparam_t *)p2) < 0) {
		ERR("Error while obtaining attribute value from '%s'\n",
				((fparam_t *)p1)->orig);
		return -1;
	}
	value.n = i;

	if(add_avp(avpid.flags | AVP_NAME_STR, avpid.name, value) != 0) {
		ERR("add_avp failed\n");
		return -1;
	}
	return 1;
}


static int set_sattr(struct sip_msg *msg, char *p1, char *p2)
{
	avp_ident_t avpid;
	int_str value;

	if(get_avp_id(&avpid, (fparam_t *)p1, msg) < 0) {
		return -1;
	}

	if(get_str_fparam(&value.s, msg, (fparam_t *)p2) < 0) {
		ERR("Error while obtaining attribute value from '%s'\n",
				((fparam_t *)p2)->orig);
		return -1;
	}

	if(add_avp(avpid.flags | AVP_NAME_STR | AVP_VAL_STR, avpid.name, value)
			!= 0) {
		ERR("add_avp failed\n");
		return -1;
	}

	return 1;
}


static int avpid_fixup(void **param, int param_no)
{
	if(param_no == 1) {
		if(fix_param(FPARAM_AVP, param) == 0)
			return 0;
		ERR("Invalid AVP identifier: '%s'\n", (char *)*param);
		return -1;
	}
	return 0;
}


static int print_attr(struct sip_msg *msg, char *p1, char *p2)
{
	fparam_t *fp;
	int_str value;
	avp_t *avp;

	fp = (fparam_t *)p1;

	avp = search_avp(fp->v.avp, &value, NULL);
	if(avp == 0) {
		LOG(L_INFO, "AVP '%s' not found\n", fp->orig);
		return -1;
	}

	if(avp->flags & AVP_VAL_STR) {
		LOG(L_INFO, "AVP: '%s'='%.*s'\n", fp->orig, value.s.len,
				ZSW(value.s.s));
	} else {
		LOG(L_INFO, "AVP: '%s'=%ld\n", fp->orig, value.n);
	}
	return 1;
}


static int del_attr(struct sip_msg *msg, char *p1, char *p2)
{
	fparam_t *fp;
	avp_t *avp;
	struct search_state st;

	fp = (fparam_t *)p1;

	avp = search_avp(fp->v.avp, 0, &st);
	while(avp) {
		destroy_avp(avp);
		avp = search_next_avp(&st, 0);
	}
	return 1;
}


static int del_attrs(struct sip_msg *msg, char *p1, char *p2)
{
	return (reset_avp_list((unsigned long)p1) == 0) ? 1 : -1;
}


static int subst_attr_fixup(void **param, int param_no)
{
	if(param_no == 1) {
		return avpid_fixup(param, 1);
	}
	if(param_no == 2) {
		if(fix_param(FPARAM_SUBST, param) != 0)
			return -1;
	}
	return 0;
}


static int subst_attr(struct sip_msg *msg, char *p1, char *p2)
{
	avp_t *avp;
	avp_value_t val;
	str *res = NULL;
	int count;
	avp_ident_t *name = &((fparam_t *)p1)->v.avp;

	if((avp = search_avp(*name, &val, NULL))) {
		if(avp->flags & AVP_VAL_STR) {
			res = subst_str(val.s.s, msg, ((fparam_t *)p2)->v.subst, &count);
			if(res == NULL) {
				ERR("avp_subst: error while running subst\n");
				goto error;
			}

			DBG("avp_subst: %d, result %.*s\n", count, res->len, ZSW(res->s));
			val.s = *res;

			if(add_avp_before(
					   avp, name->flags | AVP_VAL_STR, name->name, val)) {
				ERR("avp_subst: error while adding new AVP\n");
				goto error;
			}

			destroy_avp(avp);
			return 1;
		} else {
			ERR("avp_subst: AVP has numeric value\n");
			goto error;
		}
	} else {
		ERR("avp_subst: AVP[%.*s] index %d, flags %x not found\n",
				name->name.s.len, name->name.s.s, name->index, name->flags);
		goto error;
	}

error:
	if(res)
		pkg_free(res);
	return -1;
}


static int flags2attr(struct sip_msg *msg, char *p1, char *p2)
{
	avp_ident_t *id;
	int_str value;

	value.n = msg->flags;

	id = &((fparam_t *)p1)->v.avp;

	if(add_avp(id->flags, id->name, value) != 0) {
		ERR("add_avp failed\n");
		return -1;
	}

	return 1;
}


static int fixup_part(void **param, int param_no)
{
	int i;
	fparam_t *fp;

	static struct
	{
		char *s;
		int i;
	} fixup_parse[] = {{"", SET_URI_T}, {"prefix", PREFIX_T},
			{"uri", SET_URI_T}, {"username", SET_USER_T}, {"user", SET_USER_T},
			{"usernamepassword", SET_USERPASS_T}, {"userpass", SET_USERPASS_T},
			{"domain", SET_HOST_T}, {"host", SET_HOST_T},
			{"domainport", SET_HOSTPORT_T}, {"hostport", SET_HOSTPORT_T},
			{"port", SET_PORT_T}, {"strip", STRIP_T},
			{"strip_tail", STRIP_TAIL_T}, {0, 0}};

	if(param_no == 1) {
		return avpid_fixup(param, 1);
	} else if(param_no == 2) {
		/* Create fparam structure */
		if(fix_param(FPARAM_STRING, param) != 0)
			return -1;

		/* We will parse the string now and store the value
		 * as int
		 */
		fp = (fparam_t *)*param;
		fp->type = FPARAM_INT;

		for(i = 0; fixup_parse[i].s; i++) {
			if(!strcasecmp(fp->orig, fixup_parse[i].s)) {
				fp->v.i = fixup_parse[i].i;
				return 1;
			}
		}

		ERR("Invalid parameter value: '%s'\n", fp->orig);
		return -1;
	}
	return 0;
}


static int attr2uri(struct sip_msg *msg, char *p1, char *p2)
{
	int_str value;
	avp_t *avp_entry;
	struct action act;
	struct run_act_ctx ra_ctx;
	int pnr;
	unsigned int u;

	if(p2) {
		pnr = ((fparam_t *)p2)->v.i;
	} else {
		pnr = SET_URI_T;
	}

	avp_entry = search_avp(((fparam_t *)p1)->v.avp, &value, NULL);
	if(avp_entry == 0) {
		ERR("attr2uri: AVP '%s' not found\n", ((fparam_t *)p1)->orig);
		return -1;
	}

	memset(&act, 0, sizeof(act));

	if((pnr == STRIP_T) || (pnr == STRIP_TAIL_T)) {
		/* we need integer value for these actions */
		if(avp_entry->flags & AVP_VAL_STR) {
			if(str2int(&value.s, &u)) {
				ERR("not an integer value: %.*s\n", value.s.len, value.s.s);
				return -1;
			}
			act.val[0].u.number = u;
		} else {
			act.val[0].u.number = value.n;
		}
		act.val[0].type = NUMBER_ST;
	} else {
		/* we need string value */
		if((avp_entry->flags & AVP_VAL_STR) == 0) {
			act.val[0].u.string = int2str(value.n, NULL);
		} else {
			act.val[0].u.string = value.s.s;
		}
		act.val[0].type = STRING_ST;
	}
	act.type = pnr;
	init_run_actions_ctx(&ra_ctx);
	if(do_action(&ra_ctx, &act, msg) < 0) {
		ERR("failed to change ruri part.\n");
		return -1;
	}
	return 1;
}


/*
 * sends avp list to log in readable form
 *
 */
static void dump_avp_reverse(avp_t *avp)
{
	str *name;
	int_str val;

	if(avp) {
		/* AVPs are added to front of the list, reverse by recursion */
		dump_avp_reverse(avp->next);

		name = get_avp_name(avp);
		get_avp_val(avp, &val);
		switch(avp->flags & (AVP_NAME_STR | AVP_VAL_STR)) {
			case 0:
				/* avp type ID, int value */
				LOG(L_INFO, "AVP[%d]=%ld\n", avp->id, val.n);
				break;

			case AVP_NAME_STR:
				/* avp type str, int value */
				name = get_avp_name(avp);
				LOG(L_INFO, "AVP[\"%.*s\"]=%ld\n", name->len, name->s, val.n);
				break;

			case AVP_VAL_STR:
				/* avp type ID, str value */
				LOG(L_INFO, "AVP[%d]=\"%.*s\"\n", avp->id, val.s.len, val.s.s);
				break;

			case AVP_NAME_STR | AVP_VAL_STR:
				/* avp type str, str value */
				name = get_avp_name(avp);
				LOG(L_INFO, "AVP[\"%.*s\"]=\"%.*s\"\n", name->len, name->s,
						val.s.len, val.s.s);
				break;
		}
	}
}


static int dump_attrs(struct sip_msg *m, char *x, char *y)
{
	avp_list_t avp_list;
	unsigned long flags;

	if(x) {
		flags = (unsigned long)x;
	} else {
		flags = AVP_CLASS_ALL | AVP_TRACK_ALL;
	}


	if(flags & AVP_CLASS_GLOBAL) {
		avp_list = get_avp_list(AVP_CLASS_GLOBAL);
		INFO("class=GLOBAL\n");
		if(!avp_list) {
			LOG(L_INFO, "INFO: No AVP present\n");
		} else {
			dump_avp_reverse(avp_list);
		}
	}

	if(flags & AVP_CLASS_DOMAIN && flags & AVP_TRACK_FROM) {
		avp_list = get_avp_list(AVP_CLASS_DOMAIN | AVP_TRACK_FROM);
		INFO("track=FROM class=DOMAIN\n");
		if(!avp_list) {
			LOG(L_INFO, "INFO: No AVP present\n");
		} else {
			dump_avp_reverse(avp_list);
		}
	}

	if(flags & AVP_CLASS_DOMAIN && flags & AVP_TRACK_TO) {
		avp_list = get_avp_list(AVP_CLASS_DOMAIN | AVP_TRACK_TO);
		INFO("track=TO class=DOMAIN\n");
		if(!avp_list) {
			LOG(L_INFO, "INFO: No AVP present\n");
		} else {
			dump_avp_reverse(avp_list);
		}
	}

	if(flags & AVP_CLASS_USER && flags & AVP_TRACK_FROM) {
		avp_list = get_avp_list(AVP_CLASS_USER | AVP_TRACK_FROM);
		INFO("track=FROM class=USER\n");
		if(!avp_list) {
			LOG(L_INFO, "INFO: No AVP present\n");
		} else {
			dump_avp_reverse(avp_list);
		}
	}

	if(flags & AVP_CLASS_USER && flags & AVP_TRACK_TO) {
		avp_list = get_avp_list(AVP_CLASS_USER | AVP_TRACK_TO);
		INFO("track=TO class=USER\n");
		if(!avp_list) {
			LOG(L_INFO, "INFO: No AVP present\n");
		} else {
			dump_avp_reverse(avp_list);
		}
	}

	if(flags & AVP_CLASS_URI && flags & AVP_TRACK_FROM) {
		avp_list = get_avp_list(AVP_CLASS_URI | AVP_TRACK_FROM);
		INFO("track=FROM class=URI\n");
		if(!avp_list) {
			LOG(L_INFO, "INFO: No AVP present\n");
		} else {
			dump_avp_reverse(avp_list);
		}
	}

	if(flags & AVP_CLASS_URI && flags & AVP_TRACK_TO) {
		avp_list = get_avp_list(AVP_CLASS_URI | AVP_TRACK_TO);
		INFO("track=TO class=URI\n");
		if(!avp_list) {
			LOG(L_INFO, "INFO: No AVP present\n");
		} else {
			dump_avp_reverse(avp_list);
		}
	}
	return 1;
}


/*
 *  returns 1 if msg contains an AVP with the given name and value,
 *  returns -1 otherwise
 */
static int attr_equals(struct sip_msg *msg, char *p1, char *p2)
{
	avp_ident_t avpid;
	int_str value, avp_value;
	avp_t *avp;
	struct search_state st;

	if(get_avp_id(&avpid, (fparam_t *)p1, msg) < 0) {
		return -1;
	}

	if(p2 && get_str_fparam(&value.s, msg, (fparam_t *)p2) < 0) {
		ERR("Error while obtaining attribute value from '%s'\n",
				((fparam_t *)p2)->orig);
		return -1;
	}

	avp = search_avp(avpid, &avp_value, &st);
	if(avp == 0)
		return -1;

	if(!p2)
		return 1;

	while(avp != 0) {
		if(avp->flags & AVP_VAL_STR) {
			if((avp_value.s.len == value.s.len)
					&& !memcmp(avp_value.s.s, value.s.s, avp_value.s.len)) {
				return 1;
			}
		} else {
			if(avp_value.n == str2s(value.s.s, value.s.len, 0)) {
				return 1;
			}
		}
		avp = search_next_avp(&st, &avp_value);
	}

	return -1;
}


static int attr_exists(struct sip_msg *msg, char *p1, char *p2)
{
	return attr_equals(msg, p1, NULL);
}


static int xl_printstr(
		struct sip_msg *msg, xl_elog_t *format, char **res, int *res_len)
{
	int len;

	if(!format || !res) {
		LOG(L_ERR, "xl_printstr: Called with null format or res\n");
		return -1;
	}

	if(!xlbuf) {
		xlbuf = pkg_malloc((xlbuf_size + 1) * sizeof(char));
		if(!xlbuf) {
			LOG(L_CRIT, "xl_printstr: No memory left for format buffer\n");
			return -1;
		}
	}

	len = xlbuf_size;
	if(xl_print(msg, format, xlbuf, &len) < 0) {
		LOG(L_ERR, "xl_printstr: Error while formatting result\n");
		return -1;
	}

	if((xl_nul) && (xl_nul->len == len) && !strncmp(xl_nul->s, xlbuf, len)) {
		return 0;
	}

	*res = xlbuf;
	if(res_len) {
		*res_len = len;
	}
	return len;
}


static int attr_equals_xl(struct sip_msg *msg, char *p1, char *format)
{
	avp_ident_t *avpid;
	avp_value_t avp_val;
	struct search_state st;
	str xl_val;
	avp_t *avp;

	avpid = &((fparam_t *)p1)->v.avp;

	if(xl_printstr(msg, (xl_elog_t *)format, &xl_val.s, &xl_val.len) > 0) {
		for(avp = search_avp(*avpid, &avp_val, &st); avp;
				avp = search_next_avp(&st, &avp_val)) {
			if(avp->flags & AVP_VAL_STR) {
				if((avp_val.s.len == xl_val.len)
						&& !memcmp(avp_val.s.s, xl_val.s, avp_val.s.len))
					return 1;
			} else {
				if(avp_val.n == str2s(xl_val.s, xl_val.len, 0))
					return 1;
			}
		}
		return -1;
	}

	ERR("avp_equals_xl:Error while expanding xl_format\n");
	return -1;
}

/* get the pointer to the xl lib functions */
static int get_xl_functions(void)
{
	if(!xl_print) {
		xl_print = (xl_print_log_f *)find_export("xprint", NO_SCRIPT, 0);

		if(!xl_print) {
			LOG(L_CRIT, "ERROR: cannot find \"xprint\", is module xprint "
						"loaded?\n");
			return -1;
		}
	}

	if(!xl_parse) {
		xl_parse = (xl_parse_format_f *)find_export("xparse", NO_SCRIPT, 0);

		if(!xl_parse) {
			LOG(L_CRIT, "ERROR: cannot find \"xparse\", is module xprint "
						"loaded?\n");
			return -1;
		}
	}

	if(!xl_free) {
		xl_free = (xl_elog_free_all_f *)find_export("xfree", NO_SCRIPT, 0);

		if(!xl_free) {
			LOG(L_CRIT,
					"ERROR: cannot find \"xfree\", is module xprint loaded?\n");
			return -1;
		}
	}

	if(!xl_nul) {
		xl_getnul = (xl_get_nulstr_f *)find_export("xnulstr", NO_SCRIPT, 0);
		if(xl_getnul) {
			xl_nul = xl_getnul();
		}

		if(!xl_nul) {
			LOG(L_CRIT, "ERROR: cannot find \"xnulstr\", is module xprint "
						"loaded?\n");
			return -1;
		} else {
			LOG(L_INFO, "INFO: xprint null is \"%.*s\"\n", xl_nul->len,
					xl_nul->s);
		}
	}

	return 0;
}

/*
 * Convert xl format string to xl format description
 */
static int fixup_xl_1(void **param, int param_no)
{
	xl_elog_t *model;

	if(get_xl_functions())
		return -1;

	if(param_no == 1) {
		if(*param) {
			if(xl_parse((char *)(*param), &model) < 0) {
				LOG(L_ERR, "ERROR: xl_fixup: wrong format[%s]\n",
						(char *)(*param));
				return E_UNSPEC;
			}

			*param = (void *)model;
			return 0;
		} else {
			LOG(L_ERR, "ERROR: xl_fixup: null format\n");
			return E_UNSPEC;
		}
	}

	return 0;
}

static int fixup_attr_1_xl_2(void **param, int param_no)
{
	if(param_no == 1) {
		return avpid_fixup(param, 1);
	} else if(param_no == 2) {
		return fixup_xl_1(param, 1);
	}
	return 0;
}


static int xlset_attr(struct sip_msg *msg, char *p1, char *format)
{
	avp_ident_t *avpid;
	avp_value_t val;

	avpid = &((fparam_t *)p1)->v.avp;

	if(xl_printstr(msg, (xl_elog_t *)format, &val.s.s, &val.s.len) > 0) {
		if(add_avp(avpid->flags | AVP_VAL_STR, avpid->name, val)) {
			ERR("xlset_attr:Error adding new AVP\n");
			return -1;
		}
		return 1;
	}

	ERR("xlset_attr:Error while expanding xl_format\n");
	return -1;
}

/*
 * get the xl function pointers and fix up the AVP parameter
 */
static int xlfix_attr_fixup(void **param, int param_no)
{
	if(get_xl_functions())
		return -1;

	if(param_no == 1)
		return avpid_fixup(param, 1);

	return 0;
}

/* fixes an attribute containing xl formatted string to pure string runtime */
static int xlfix_attr(struct sip_msg *msg, char *p1, char *p2)
{
	avp_t *avp;
	avp_ident_t *avpid;
	avp_value_t val;
	xl_elog_t *format = NULL;
	int ret = -1;

	avpid = &((fparam_t *)p1)->v.avp;

	/* search the AVP */
	avp = search_avp(*avpid, &val, 0);
	if(!avp) {
		DBG("xlfix_attr: AVP does not exist\n");
		goto error;
	}
	if((avp->flags & AVP_VAL_STR) == 0) {
		DBG("xlfix_attr: Not a string AVP\n");
		goto error;
	}

	/* parse the xl syntax -- AVP values are always zero-terminated */
	if(xl_parse(val.s.s, &format) < 0) {
		LOG(L_ERR, "ERROR: xlfix_attr: wrong format[%s]\n", val.s.s);
		goto error;
	}

	if(xl_printstr(msg, format, &val.s.s, &val.s.len) > 0) {
		/* we must delete and re-add the AVP again */
		destroy_avp(avp);
		if(add_avp(avpid->flags | AVP_VAL_STR, avpid->name, val)) {
			ERR("xlfix_attr:Error adding new AVP\n");
			goto error;
		}
		/* everything went OK */
		ret = 1;
	}

error:
	/* free the parsed xl expression */
	if(format)
		xl_free(format);

	return ret;
}


static int request_hf_helper(struct sip_msg *msg, str *hf, avp_ident_t *ident,
		struct lump *anchor, struct search_state *st, int front, int reverse,
		int reply)
{
	struct lump *new_anchor;
	static struct search_state state;
	avp_t *avp;
	char *s;
	str fin_val;
	int len, ret;
	int_str val;
	struct hdr_field *pos, *found = NULL;

	if(!anchor && !reply) {

		if(parse_headers(msg, HDR_EOH_F, 0) == -1) {
			LOG(L_ERR,
					"ERROR: request_hf_helper: Error while parsing message\n");
			return -1;
		}

		pos = msg->headers;
		while(pos && (pos->type != HDR_EOH_T)) {
			if((hf->len == pos->name.len)
					&& (!strncasecmp(hf->s, pos->name.s, pos->name.len))) {
				found = pos;
				if(front) {
					break;
				}
			}
			pos = pos->next;
		}

		if(found) {
			if(front) {
				len = found->name.s - msg->buf;
			} else {
				len = found->name.s + found->len - msg->buf;
			}
		} else {
			len = msg->unparsed - msg->buf;
		}

		new_anchor = anchor_lump(msg, len, 0, 0);
		if(new_anchor == 0) {
			LOG(L_ERR, "ERROR: request_hf_helper: Can't get anchor\n");
			return -1;
		}
	} else {
		new_anchor = anchor;
	}

	if(!st) {
		st = &state;
		avp = search_avp(*ident, NULL, st);
		ret = -1;
	} else {
		avp = search_next_avp(st, NULL);
		ret = 1;
	}

	if(avp) {
		if(reverse
				&& (request_hf_helper(msg, hf, ident, new_anchor, st, front,
							reverse, reply)
						== -1)) {
			return -1;
		}

		get_avp_val(avp, &val);
		if(avp->flags & AVP_VAL_STR) {
			fin_val = val.s;
		} else {
			fin_val.s = int2str(val.n, &fin_val.len);
		}

		len = hf->len + 2 + fin_val.len + 2;
		s = (char *)pkg_malloc(len);
		if(!s) {
			LOG(L_ERR,
					"ERROR: request_hf_helper: No memory left for data lump\n");
			return -1;
		}

		memcpy(s, hf->s, hf->len);
		memcpy(s + hf->len, ": ", 2);
		memcpy(s + hf->len + 2, fin_val.s, fin_val.len);
		memcpy(s + hf->len + 2 + fin_val.len, CRLF, CRLF_LEN);

		if(reply) {
			if(add_lump_rpl(msg, s, len, LUMP_RPL_HDR | LUMP_RPL_NODUP) == 0) {
				LOG(L_ERR, "ERROR: request_hf_helper: Can't insert RPL lump\n");
				pkg_free(s);
				return -1;
			}
		} else {
			if((front && (insert_new_lump_before(new_anchor, s, len, 0) == 0))
					|| (!front
							&& (insert_new_lump_after(new_anchor, s, len, 0)
									== 0))) {
				LOG(L_ERR, "ERROR: request_hf_helper: Can't insert lump\n");
				pkg_free(s);
				return -1;
			}
		}
		if(!reverse
				&& (request_hf_helper(msg, hf, ident, new_anchor, st, front,
							reverse, reply)
						== -1)) {
			return -1;
		}
		return 1;
	};

	/* in case of topmost call (st==NULL) return error */
	/* otherwise it's OK, no more AVPs found */
	return ret;
}


static int fixup_str_1_attr_2(void **param, int param_no)
{
	if(param_no == 1) {
		return fixup_var_str_12(param, 1);
	} else if(param_no == 2) {
		return avpid_fixup(param, 1);
	}
	return 0;
}


static int insert_req(struct sip_msg *msg, char *p1, char *p2)
{
	avp_ident_t ident, *avp;
	str hf;

	if(get_str_fparam(&hf, msg, (fparam_t *)p1) < 0) {
		ERR("Error while obtaining attribute value from '%s'\n",
				((fparam_t *)p1)->orig);
		return -1;
	}

	if(p2) {
		avp = &((fparam_t *)p2)->v.avp;
	} else {
		ident.name.s = hf;
		ident.flags = AVP_NAME_STR;
		ident.index = 0;
		avp = &ident;
	}
	return (request_hf_helper(msg, &hf, avp, NULL, NULL, 1, 0, 0));
}


static int append_req(struct sip_msg *msg, char *p1, char *p2)
{
	avp_ident_t ident, *avp;
	str hf;

	if(get_str_fparam(&hf, msg, (fparam_t *)p1) < 0) {
		ERR("Error while obtaining attribute value from '%s'\n",
				((fparam_t *)p1)->orig);
		return -1;
	}

	if(p2) {
		avp = &((fparam_t *)p2)->v.avp;
	} else {
		ident.name.s = hf;
		ident.flags = AVP_NAME_STR;
		ident.index = 0;
		avp = &ident;
	}
	return (request_hf_helper(msg, &hf, avp, NULL, NULL, 0, 1, 0));
}


static int replace_req(struct sip_msg *msg, char *p1, char *p2)
{
	struct hdr_field *pos;
	str hf;

	if(get_str_fparam(&hf, msg, (fparam_t *)p1) < 0) {
		ERR("Error while obtaining attribute value from '%s'\n",
				((fparam_t *)p1)->orig);
		return -1;
	}

	if(parse_headers(msg, HDR_EOH_F, 0) == -1) {
		LOG(L_ERR, "ERROR: replace_req: Error while parsing message\n");
		return -1;
	}

	pos = msg->headers;
	while(pos && (pos->type != HDR_EOH_T)) {
		if(hf.len == pos->name.len
				&& !strncasecmp(hf.s, pos->name.s, pos->name.len)) {
			if(del_lump(msg, pos->name.s - msg->buf, pos->len, 0) == 0) {
				LOG(L_ERR, "ERROR: Can't insert del lump\n");
				return -1;
			}
		}
		pos = pos->next;
	}
	return append_req(msg, p1, p2);
}


static int append_reply(struct sip_msg *msg, char *p1, char *p2)
{
	avp_ident_t ident, *avp;
	str hf;

	if(get_str_fparam(&hf, msg, (fparam_t *)p1) < 0) {
		ERR("Error while obtaining attribute value from '%s'\n",
				((fparam_t *)p1)->orig);
		return -1;
	}

	if(p2) {
		avp = &((fparam_t *)p2)->v.avp;
	} else {
		ident.name.s = hf;
		ident.flags = AVP_NAME_STR;
		ident.index = 0;
		avp = &ident;
	}
	return (request_hf_helper(msg, &hf, avp, NULL, NULL, 0, 1, 1));
}


static int set_destination(struct sip_msg *msg, str *dest)
{
	name_addr_t nameaddr;

	if(!parse_nameaddr(dest, &nameaddr)) {
		return set_dst_uri(msg, &nameaddr.uri);
	} else {
		/* it is just URI, pass it through */
		return set_dst_uri(msg, dest);
	}
}


static int attr_destination(struct sip_msg *msg, char *p1, char *p2)
{
	avp_t *avp;
	avp_value_t val;

	if((avp = search_avp(((fparam_t *)p1)->v.avp, &val, NULL))) {
		if(avp->flags & AVP_VAL_STR) {
			if(set_destination(msg, &val.s)) {
				LOG(L_ERR, "ERROR: avp_destination: Can't set dst uri\n");
				return -1;
			};
			/* dst_uri changed, so it makes sense to re-use the current uri
			 * for forking */
			ruri_mark_new(); /* re-use uri for serial forking */
			return 1;
		} else {
			ERR("avp_destination:AVP has numeric value\n");
			return -1;
		}
	}
	return -1;
}


static int xlset_destination(struct sip_msg *msg, char *format, char *p2)
{
	str val;

	if(xl_printstr(msg, (xl_elog_t *)format, &val.s, &val.len) > 0) {
		DBG("Setting dest to: '%.*s'\n", val.len, val.s);
		if(set_destination(msg, &val) == 0) {
			return 1;
		}
	}

	return -1;
}


static int attr_hdr_body2attrs(struct sip_msg *m, char *header_, char *prefix_)
{
	char name_buf[50];
	str *prefix = (str *)prefix_;
	struct hdr_name *header = (void *)header_;
	struct hdr_field *hf;
	str s, name, val;
	int_str name2, val2;
	int val_type, arr;
	if(header->kind == HDR_STR) {
		if(parse_headers(m, HDR_EOH_F, 0) == -1) {
			LOG(L_ERR, "ERROR: attr_hdr_body2attrs: Error while parsing "
					   "message\n");
			return -1;
		}

		for(hf = m->headers; hf; hf = hf->next) {
			if((header->name.s.len == hf->name.len)
					&& (!strncasecmp(
							header->name.s.s, hf->name.s, hf->name.len))) {
				break;
			}
		}
	} else {
		if(parse_headers(m, header->name.n, 0) == -1) {
			LOG(L_ERR, "ERROR: attr_hdr_body2attrs: Error while parsing "
					   "message\n");
			return -1;
		}
		switch(header->name.n) {
			//	HDR_xxx:
			default:
				hf = NULL;
				break;
		}
	}
	if(!hf || !hf->body.len)
		return 1;

	// parse body of hf
	s = hf->body;
	name_buf[0] = '\0';
	while(s.len) {
		trim_leading(&s);
		name.s = s.s;
		while(s.len
				&& ((s.s[0] >= 'a' && s.s[0] <= 'z')
						|| (s.s[0] >= 'A' && s.s[0] <= 'Z')
						|| (s.s[0] >= '0' && s.s[0] <= '9') || s.s[0] == '_'
						|| s.s[0] == '-')) {
			s.s++;
			s.len--;
		}
		if(s.s == name.s)
			break;
		name.len = s.s - name.s;
		trim_leading(&s);
		if(!s.len)
			break;
		if(s.s[0] == '=') {
			s.s++;
			s.len--;
			arr = -1;

			while(s.len) {
				trim_leading(&s);
				val_type = 0;
				if(!s.len)
					break;
				if(s.s[0] == '"') {
					s.s++;
					s.len--;
					val.s = s.s;

					s.s = q_memchr(s.s, '\"', s.len);
					if(!s.s)
						break;
					val.len = s.s - val.s;
					val_type = AVP_VAL_STR;
					s.s++;
					s.len -= s.s - val.s;
				} else {
					int r;
					val.s = s.s;
					if(s.s[0] == '+' || s.s[0] == '-') {
						s.s++;
						s.len--;
					}
					val2.n = 0;
					r = 0;
					while(s.len) {
						if(s.s[0] == header->field_delimiter
								|| (header->array_delimiter
										&& header->array_delimiter == s.s[0]))
							goto token_end;
						switch(s.s[0]) {
							case ' ':
							case '\t':
							case '\n':
							case '\r':
								goto token_end;
						}
						if(!val_type && s.s[0] >= '0' && s.s[0] <= '9') {
							r++;
							val2.n *= 10;
							val2.n += s.s[0] - '0';
							// overflow detection ???
						} else {
							val_type = AVP_VAL_STR;
						}
						s.s++;
						s.len--;
					}
				token_end:
					if(r == 0)
						val_type = AVP_VAL_STR;
					if(!val_type && val.s[0] == '-') {
						val2.n = -val2.n;
					}
					val.len = s.s - val.s;
				}
				trim_leading(&s);
				if(arr >= 0
						|| (s.len && header->array_delimiter
								&& header->array_delimiter == s.s[0])) {
					arr++;
					if(arr == 100)
						LOG(L_ERR, "ERROR: avp index out of limit\n");
				}
				if(val.len && arr < 100) {
					if(prefix != NULL || arr >= 0) {
						if((prefix ? prefix->len : 0) + name.len + 1
										+ ((arr >= 0) ? 3 /*#99*/ : 0)
								> sizeof(name_buf)) {
							if(arr <= 0)
								LOG(L_ERR, "ERROR: avp name too long\n");
							goto cont;
						}
						name2.s.len = 0;
						name2.s.s = name_buf;
						if(prefix != NULL) {
							if(name_buf[0] == '\0') {
								memcpy(&name_buf[0], prefix->s, prefix->len);
							}
							name2.s.len += prefix->len;
						}
						if(arr <= 0) {
							memcpy(&name_buf[name2.s.len], name.s, name.len);
						}
						name2.s.len += name.len;
						if(arr >= 0) {
							name_buf[name2.s.len] = '#';
							name2.s.len++;
							if(arr >= 10) {
								name_buf[name2.s.len] = '0' + (arr / 10);
								name2.s.len++;
							}
							name_buf[name2.s.len] = '0' + (arr % 10);
							name2.s.len++;
						}
					} else {
						name2.s.s = name.s;
						name2.s.len = name.len;
					}
					if(((val_type & AVP_VAL_STR)
							   && (header->val_types & VAL_TYPE_STR))
							|| ((val_type & AVP_VAL_STR) == 0
									&& (header->val_types & VAL_TYPE_INT))) {
						if(val_type) {
							val2.s.s = val.s;
							val2.s.len = val.len;
							DBG("DEBUG: attr_hdr_body2attrs: adding avp "
								"'%.*s', sval: '%.*s'\n",
									name2.s.len, (char *)name2.s.s, val.len,
									val.s);
						} else {
							DBG("DEBUG: attr_hdr_body2attrs: adding avp "
								"'%.*s', ival: '%ld'\n",
									name2.s.len, (char *)name2.s.s, val2.n);
						}
						if(add_avp(AVP_NAME_STR | val_type, name2, val2) != 0) {
							LOG(L_ERR, "ERROR: attr_hdr_body2attrs: add_avp "
									   "failed\n");
							return 1;
						}
					}
				}
			cont:
				if(s.len && header->array_delimiter
						&& header->array_delimiter == s.s[0]) {
					s.s++;
					s.len--;
				} else {
					break;
				}
			};
		}
		if(s.len && s.s && s.s[0] == header->field_delimiter) {
			s.s++;
			s.len--;
		} else {
			break;
		}
	}
	return 1;
}


static int attr_hdr_body2attrs2(
		struct sip_msg *msg, char *header_, char *prefix_)
{
	return attr_hdr_body2attrs(msg, header_, prefix_);
}


static int attr_hdr_body2attrs_fixup(void **param, int param_no)
{
	char *c, *sparams;
	hdr_name_t *h = NULL;
	int n;
	str *s;
	if(param_no == 1) {
		c = *param;
		if(*c == '#') {
			LOG(L_ERR,
					"attr_hdr_body2attrs_fixup: header name is not "
					"valid '%s'\n",
					c);
			return E_CFG;
		} else {
			sparams = strchr(c, PARAM_DELIM);
			if(sparams)
				n = sparams - c;
			else
				n = strlen(c);
			if(n == 0) {
				LOG(L_ERR, "attr_hdr_body2attrs_fixup: header name is empty\n");
				return E_CFG;
			}
			h = pkg_malloc(sizeof(hdr_name_t) + n + 1);
			if(!h) {
				LOG(L_ERR, "attr_hdr_body2attrs_fixup: out of memory\n");
				return E_OUT_OF_MEM;
			}
			h->kind = HDR_STR;
			h->name.s.len = n;
			h->name.s.s = (char *)h + sizeof(hdr_name_t);
			memcpy(h->name.s.s, c, n + 1);
		}
		if(sparams) {
			h->val_types = 0;
			while(*sparams) {
				switch(*sparams) {
					case 'i':
					case 'I':
						h->val_types = VAL_TYPE_INT;
						break;
					case 's':
					case 'S':
						h->val_types = VAL_TYPE_STR;
						break;
					case PARAM_DELIM:
						break;
					default:
						LOG(L_ERR,
								"attr_hdr_body2attrs_fixup: bad field param "
								"modifier near '%s'\n",
								sparams);
						pkg_free(h);
						return E_CFG;
				}
				sparams++;
			}
			if(!h->val_types) {
				LOG(L_ERR, "attr_hdr_body2attrs_fixup: no field param modifier "
						   "specified\n");
				pkg_free(h);
				return E_CFG;
			}
		} else {
			h->val_types = VAL_TYPE_INT | VAL_TYPE_STR;
		}
		pkg_free(*param);
		h->field_delimiter = ',';
		h->array_delimiter = '\0';

		*param = h;
	} else if(param_no == 2) {
		n = strlen(*param);
		if(n == 0) {
			s = NULL;
		} else {
			s = pkg_malloc(sizeof(str) + n + 1);
			if(!s) {
				LOG(L_ERR, "attr_hdr_body2attrs_fixup: out of memory\n");
				return E_OUT_OF_MEM;
			}
			s->len = n;
			s->s = (char *)s + sizeof(str);
			memcpy(s->s, *param, n + 1);
		}
		pkg_free(*param);
		*param = s;
	}
	return 0;
}

static int attr_hdr_body2attrs2_fixup(void **param, int param_no)
{
	struct hdr_name *h;
	int res = attr_hdr_body2attrs_fixup(param, param_no);
	if(res == 0 && param_no == 1) {
		h = *param;
		h->field_delimiter = ';';
		h->array_delimiter = ',';
	}
	return res;
}


static int avpgroup_fixup(void **param, int param_no)
{
	unsigned long flags;
	char *s;

	if(param_no == 1) {
		/* Determine the track and class of attributes to be loaded */
		s = (char *)*param;
		flags = 0;
		if(*s != '$' || (strlen(s) != 3 && strlen(s) != 2)) {
			ERR("Invalid parameter value, $xy expected\n");
			return -1;
		}
		switch((s[1] << 8) + s[2]) {
			case 0x4655: /* $fu */
			case 0x6675:
			case 0x4675:
			case 0x6655:
				flags = AVP_TRACK_FROM | AVP_CLASS_USER;
				break;

			case 0x4652: /* $fr */
			case 0x6672:
			case 0x4672:
			case 0x6652:
				flags = AVP_TRACK_FROM | AVP_CLASS_URI;
				break;

			case 0x5455: /* $tu */
			case 0x7475:
			case 0x5475:
			case 0x7455:
				flags = AVP_TRACK_TO | AVP_CLASS_USER;
				break;

			case 0x5452: /* $tr */
			case 0x7472:
			case 0x5472:
			case 0x7452:
				flags = AVP_TRACK_TO | AVP_CLASS_URI;
				break;

			case 0x4644: /* $fd */
			case 0x6664:
			case 0x4664:
			case 0x6644:
				flags = AVP_TRACK_FROM | AVP_CLASS_DOMAIN;
				break;

			case 0x5444: /* $td */
			case 0x7464:
			case 0x5464:
			case 0x7444:
				flags = AVP_TRACK_TO | AVP_CLASS_DOMAIN;
				break;

			case 0x6700: /* $td */
			case 0x4700:
				flags = AVP_CLASS_GLOBAL;
				break;

			default:
				ERR("Invalid parameter value: '%s'\n", s);
				return -1;
		}

		pkg_free(*param);
		*param = (void *)flags;
		return 1;
	}
	return 0;
}


static int select_attr_fixup(str *res, select_t *s, struct sip_msg *msg)
{
	avp_ident_t *avp_ident;

#define SEL_PARAM_IDX 1

	if(!msg) { /* fixup call */
		str attr_name;

		if(s->params[SEL_PARAM_IDX].type != SEL_PARAM_STR) {
			ERR("attribute name expected.\n");
			return -1;
		}

		attr_name = s->params[SEL_PARAM_IDX].v.s;
		DEBUG("fix up for attribute '%.*s'\n", STR_FMT(&attr_name));

		if(!(avp_ident = pkg_malloc(sizeof(avp_ident_t)))) {
			ERR("out of mem; requested: %d.\n", (int)sizeof(avp_ident_t));
			return -1;
		}
		memset(avp_ident, 0, sizeof(avp_ident_t));

		/* skip leading `$' */
		if((1 < attr_name.len) && (attr_name.s[0] == '$')) {
			attr_name.len--;
			attr_name.s++;
		}
		if(parse_avp_ident(&attr_name, avp_ident) < 0) {
			ERR("failed to parse attribute name: `%.*s'.\n",
					STR_FMT(&attr_name));
			pkg_free(avp_ident);
			return -1;
		}
		s->params[SEL_PARAM_IDX].v.p = avp_ident;
		s->params[SEL_PARAM_IDX].type = SEL_PARAM_PTR;
	} else { /* run time call */
		avp_t *ret;
		avp_value_t val;

#ifdef EXTRA_DEBUG
		assert(s->params[SEL_PARAM_IDX].type == SEL_PARAM_PTR);
#endif
		avp_ident = s->params[SEL_PARAM_IDX].v.p;
		ret = search_first_avp(avp_ident->flags, avp_ident->name, &val, NULL);
		if(ret && ret->flags & AVP_VAL_STR)
			*res = val.s;
	}

	return 0;

#undef SEL_PARAM_IDX
}

SELECT_F(select_any_nameaddr)
ABSTRACT_F(select_attr);

select_row_t sel_declaration[] = {{NULL, SEL_PARAM_STR, STR_STATIC_INIT("avp"),
										  select_attr, SEL_PARAM_EXPECTED},
		{NULL, SEL_PARAM_STR, STR_STATIC_INIT("attr"), select_attr,
				SEL_PARAM_EXPECTED},
		{NULL, SEL_PARAM_STR, STR_STATIC_INIT("attribute"), select_attr,
				SEL_PARAM_EXPECTED},
		{select_attr, SEL_PARAM_STR, STR_NULL, select_attr_fixup,
				FIXUP_CALL | CONSUME_NEXT_STR},

		{select_attr_fixup, SEL_PARAM_STR, STR_STATIC_INIT("nameaddr"),
				select_any_nameaddr, NESTED},

		{NULL, SEL_PARAM_INT, STR_NULL, NULL, 0}};

static int mod_init()
{
	DBG("initializing\n");
	return register_select_table(sel_declaration);
}
