/*
 * Accounting module
 *
 * $Id: acc_syslog.c,v 1.10 2006/12/08 23:51:41 janakj Exp $
 *
 * Copyright (C) 2001-2003 FhG FOKUS
 * Copyright (C) 2005 iptelorg GmbH
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include <stdio.h>
#include <string.h>
#include <time.h>

#include "../../sr_module.h"
#include "../../dprint.h"
#include "../../mem/mem.h"
#include "../tm/t_hooks.h"
#include "../tm/tm_load.h"
#include "../tm/h_table.h"
#include "../../parser/msg_parser.h"
#include "../../parser/parse_from.h"
#include "../../parser/digest/digest.h"
#include "../../usr_avp.h"
#include "../../id.h"
#include "attrs.h"
#include "../tm/tm_load.h"

/*
 * TODO:
 * - Quote attribute values properly
 * - Save request timestamp
 * - Save response timestamp
 */

/*
 * a: attr
 * c: sip_callid
 * d: to_tag
 * f: sip_from
 * g: flags
 * i: inbound_ruri
 * m: sip_method
 * n: sip_cseq
 * o: outbound_ruri
 * p: source_ip
 * r: from_tag
 * t: sip_to
 * u: digest_username
 * x: request_timestamp
 * D: to_did
 * F: from_uri
 * I: from_uid
 * M: from_did
 * R: digest_realm
 * P: source_port
 * S: sip_status
 * T: to_uri
 * U: to_uid
 * X: response_timestamp
 */

#define ALL_LOG_FMT "acdfgimnoprtuxDFIMPRSTUX"
#define ALL_LOG_FMT_LEN (sizeof(ALL_LOG_FMT) - 1)

#define A_SEPARATOR ", " /* must be shorter than ACC! */
#define A_SEPARATOR_LEN (sizeof(A_SEPARATOR) - 1)
#define A_EQ "="
#define A_EQ_LEN (sizeof(A_EQ) - 1)
#define A_EOL "\n\0"
#define A_EOL_LEN (sizeof(A_EOL) - 1)

#define ACC "ACC: "  /* Prefix of accounting messages in syslog */
#define ACC_LEN (sizeof(ACC) - 1)

#define ACC_REQUEST "request accounted: "
#define ACC_MISSED "call missed: "
#define ACC_ANSWERED "transaction answered: "
#define ACC_ACKED "request acknowledged: "

#define NA "n/a"

#define ATR(atr) atr_arr[cnt].s = A_##atr; atr_arr[cnt].len = sizeof(A_##atr) - 1;

#define A_ATTRS        "attrs"
#define A_CALLID       "callid"
#define A_TOTAG        "to_tag"
#define A_FROM         "from"
#define A_FLAGS        "flags"
#define A_IURI         "in_ruri"
#define A_METHOD       "sip_method"
#define A_CSEQ         "cseq"
#define A_OURI         "out_ruri"
#define A_FROMTAG      "from_tag"
#define A_TO           "to"
#define A_DIGUSER      "digest_username"
#define A_REQTIMESTAMP "request_timestamp"
#define A_TODID        "to_did"
#define A_FROMURI      "from_uri"
#define A_FROMUID      "from_uid"
#define A_FROMDID      "from_did"
#define A_DIGREALM     "digest_realm"
#define A_STATUS       "sip_status"
#define A_TOURI        "to_uri"
#define A_TOUID        "to_uid"
#define A_RESTIMESTAMP "response_timestamp"
#define A_SRCIP        "src_ip"
#define A_SRCPORT      "src_port"

MODULE_VERSION

struct tm_binds tmb;

static int mod_init( void );
static int fix_log_flag( modparam_t type, void* val);
static int fix_log_missed_flag( modparam_t type, void* val);

static int log_level = L_NOTICE; /* noisiness level logging facilities are used */

static int early_media = 0;         /* Enable/disable early media (183) accounting */
static int failed_transactions = 0; /* Enable/disable accounting of failed (>= 300) transactions */
static int report_cancels = 0;      /* Enable/disable CANCEL reporting */
static int report_ack = 0;          /* Enable/disable end-to-end ACK reports */
static int log_flag = 0;            /* Flag that marks transactions to be accounted */
static int log_missed_flag = 0;     /* Transaction having this flag set will be accounted in missed calls when fails */
static char* log_fmt = ALL_LOG_FMT; /* Formating string that controls what information will be collected and accounted */

/* Attribute-value pairs */
static char* attrs = "";
avp_ident_t* avps;
int avps_n;

static int acc_log_request0(struct sip_msg *rq, char *p1, char *p2);
static int acc_log_missed0(struct sip_msg *rq, char *p1, char *p2);
static int acc_log_request1(struct sip_msg *rq, char *p1, char *p2);
static int acc_log_missed1(struct sip_msg *rq, char *p1, char *p2);

static str na = STR_STATIC_INIT(NA);


static cmd_export_t cmds[] = {
	{"acc_syslog_log",    acc_log_request0, 0, 0,               REQUEST_ROUTE | FAILURE_ROUTE},
	{"acc_syslog_missed", acc_log_missed0,  0, 0,               REQUEST_ROUTE | FAILURE_ROUTE},
	{"acc_syslog_log",    acc_log_request1, 1, fixup_var_str_1, REQUEST_ROUTE | FAILURE_ROUTE},
	{"acc_syslog_missed", acc_log_missed1,  1, fixup_var_str_1, REQUEST_ROUTE | FAILURE_ROUTE},
	{0, 0, 0, 0, 0}
};


static param_export_t params[] = {
	{"early_media",		PARAM_INT, &early_media         },
	{"failed_transactions",	PARAM_INT, &failed_transactions },
	{"report_ack",		PARAM_INT, &report_ack          },
	{"report_cancels",	PARAM_INT, &report_cancels 	},
	{"log_flag",		PARAM_INT, &log_flag         	},
	{"log_flag",		PARAM_STRING|PARAM_USE_FUNC, fix_log_flag},
	{"log_missed_flag",	PARAM_INT, &log_missed_flag	},
	{"log_missed_flag",	PARAM_STRING|PARAM_USE_FUNC, fix_log_missed_flag},
	{"log_level",		PARAM_INT, &log_level           },
	{"log_fmt",		PARAM_STRING, &log_fmt          },
        {"attrs",               PARAM_STRING, &attrs            },
	{0, 0, 0}
};


struct module_exports exports= {
	"acc_syslog",
	cmds,     /* exported functions */
	0,        /* RPC methods */
	params,   /* exported params */
	mod_init, /* initialization module */
	0,	  /* response function */
	0,        /* destroy function */
	0,	  /* oncancel function */
	0         /* per-child init function */
};



/* fixes log_flag param (resolves possible named flags) */
static int fix_log_flag( modparam_t type, void* val)
{
	return fix_flag(type, val, "acc_syslog", "log_flag", &log_flag);
}



/* fixes log_missed_flag param (resolves possible named flags) */
static int fix_log_missed_flag( modparam_t type, void* val)
{
	return fix_flag(type, val, "acc_syslog", "log_missed_flag", &log_missed_flag);
}


#define TM_BUF_LEN sizeof("10000-12-31 23:59:59")

static inline int convert_time(str* buf, time_t time)
{
	struct tm* tm;

	if (!buf->s || buf->len < TM_BUF_LEN) {
		LOG(L_ERR, "ERROR:acc:convert_time: Buffer too short\n");
		return -1;
	}

	tm = gmtime(&time);
	buf->len = strftime(buf->s, buf->len, "%Y-%m-%d %H:%M:%S", tm);
	return 0;
}


static inline int skip_cancel(struct sip_msg *msg)
{
        return (msg->REQ_METHOD == METHOD_CANCEL) && report_cancels == 0;
}


/*
 * Append a constant string, uses sizeof to figure the length
 * of the string
 */
#define append(buf, ptr)                         \
    do {                                         \
        memcpy((buf).s, (ptr), sizeof(ptr) - 1); \
        (buf).s += sizeof(ptr) - 1;              \
        (buf).len -= sizeof(ptr) - 1;            \
    } while(0);


#define append_str(buf, str)                  \
    do {                                      \
        memcpy((buf).s, (str).s, (str).len);  \
        (buf).s += (str).len;                 \
        (buf).len -= (str).len;               \
    } while(0);


int verify_fmt(char *fmt) {

	if (!fmt) {
		LOG(L_ERR, "ERROR:acc:verify_fmt: formatting string zero\n");
		return -1;
	}

	if (!(*fmt)) {
		LOG(L_ERR, "ERROR:acc:verify_fmt: formatting string empty\n");
		return -1;
	}

	if (strlen(fmt) > ALL_LOG_FMT_LEN) {
		LOG(L_ERR, "ERROR:acc:verify_fmt: formatting string too long\n");
		return -1;
	}

	while(*fmt) {
		if (!strchr(ALL_LOG_FMT, *fmt)) {
			LOG(L_ERR, "ERROR:acc:verify_fmt: char in log_fmt invalid: %c\n", *fmt);
			return -1;
		}
		fmt++;
	}
	return 1;
}


/*
 * Return true if accounting is enabled and the
 * transaction is marked for accounting
 */
static inline int is_acc_on(struct sip_msg *rq)
{
	return log_flag && isflagset(rq, log_flag) == 1;
}


/*
 * Return true if missed_call accounting is enabled
 * and the transaction has the flag set
 */
static inline int is_mc_on(struct sip_msg *rq)
{
	return log_missed_flag && isflagset(rq, log_missed_flag) == 1;
}


static inline void preparse_req(struct sip_msg *rq)
{
	     /* try to parse from for From-tag for accounted transactions;
	      * don't be worried about parsing outcome -- if it failed,
	      * we will report N/A. There is no need to parse digest credentials
	      * here even if we account them, because the authentication function
	      * will do it before us and if not then we will account n/a.
	      */
	parse_headers(rq, HDR_CALLID_F | HDR_FROM_F | HDR_TO_F | HDR_CSEQ_F, 0 );
	parse_from_header(rq);
}


/* is this reply of interest for accounting ? */
static inline int should_acc_reply(struct cell* t, int code)
{
	struct sip_msg *r;

	r = t->uas.request;

	     /* validation */
	if (r == 0) {
		LOG(L_ERR, "ERROR:acc:should_acc_reply: 0 request\n");
		return 0;
	}

	     /* negative transactions reported otherwise only if explicitly
	      * demanded */
	if (!failed_transactions && code >= 300) return 0;
	if (!is_acc_on(r)) return 0;
	if (skip_cancel(r)) return 0;
	if (code < 200 && ! (early_media && code == 183)) return 0;
	return 1; /* seed is through, we will account this reply */
}


/* Extract username attribute from authorized credentials */
static inline str* cred_user(struct sip_msg* rq)
{
	struct hdr_field* h;
	auth_body_t* cred;

	get_authorized_cred(rq->proxy_auth, &h);
	if (!h) get_authorized_cred(rq->authorization, &h);
	if (!h) return 0;
	cred = (auth_body_t*)(h->parsed);
	if (!cred || !cred->digest.username.user.len)
		return 0;
	return &cred->digest.username.user;
}


/* Extract realm attribute from authorized credentials */
static inline str* cred_realm(struct sip_msg* rq)
{
	str* realm;
	struct hdr_field* h;
	auth_body_t* cred;

	get_authorized_cred(rq->proxy_auth, &h);
	if (!h) get_authorized_cred(rq->authorization, &h);
	if (!h) return 0;
	cred = (auth_body_t*)(h->parsed);
	if (!cred) return 0;
	realm = GET_REALM(&cred->digest);
	if (!realm->len || !realm->s) {
		return 0;
	}
	return realm;
}


/* Return To header field from the request in case of faked reply or
 * missing To header field in the reply
 */
static inline struct hdr_field* valid_to(struct cell* t, struct sip_msg* reply)
{
	if (reply == FAKED_REPLY || !reply || !reply->to) {
		return t->uas.request->to;
	} else {
		return reply->to;
	}
}


/* create an array of str's for accounting using a formatting string;
 * this is the heart of the accounting module -- it prints whatever
 * requested in a way, that can be used for syslog, radius,
 * sql, whatsoever
 * tm sip_msg_clones does not clone (shmmem-zed) parsed fields, other then Via1,2. Such fields clone now or use from rq_rp
 */
static int fmt2strar(char *fmt,             /* what would you like to account ? */
		     struct sip_msg *rq,    /* accounted message */
		     str* ouri,             /* Outbound Request-URI */
		     struct hdr_field *to,  /* To header field (used to extract tag) */
		     str *phrase,
		     int *total_len,        /* total length of accounted values */
		     int *attr_len,         /* total length of accounted attribute names */
		     str **val_arr,         /* that's the output -- must have MAX_ACC_COLUMNS */
		     str *atr_arr,
		     time_t req_time)       /* Timestamp of the request */
{
	static char flags_buf[INT2STR_MAX_LEN], tm_buf[TM_BUF_LEN],
		rqtm_buf[TM_BUF_LEN], srcip_buf[IP_ADDR_MAX_STR_SIZE],
		srcport_buf[INT2STR_MAX_LEN];
	int cnt, tl, al;
	struct to_body* from, *pto;
	static str mycode, flags, tm_s, rqtm_s, src_ip, src_port, from_uid, to_uid;
	str *cr, *at;
	struct cseq_body *cseq;
	char* p;

	cnt = tl = al = 0;

	     /* we don't care about parsing here; either the function
	      * was called from script, in which case the wrapping function
	      * is supposed to parse, or from reply processing in which case
	      * TM should have preparsed from REQUEST_IN callback; what's not
	      * here is replaced with NA
	      */
	while(*fmt) {
		if (cnt == ALL_LOG_FMT_LEN) {
			LOG(L_ERR, "ERROR:acc:fmt2strar: Formatting string is too long\n");
			return 0;
		}

		switch(*fmt) {
		case 'a': /* attr */
			at = print_attrs(avps, avps_n, 1);
			if (!at) {
				val_arr[cnt] = &na;
			} else {
				val_arr[cnt] = at;
			}
			ATR(ATTRS);
			break;

		case 'c': /* sip_callid */
			val_arr[cnt] = (rq->callid && rq->callid->body.len) ? &rq->callid->body : &na;
			ATR(CALLID);
			break;

		case 'd': /* to_tag */
			val_arr[cnt] = (to && (pto = (struct to_body*)(to->parsed)) && pto->tag_value.len) ? & pto->tag_value : &na;
			ATR(TOTAG);
			break;

		case 'f': /* sip_from */
			val_arr[cnt] = (rq->from && rq->from->body.len) ? &rq->from->body : &na;
			ATR(FROM);
			break;

		case 'g': /* flags */
			p = int2str(rq->flags, &flags.len);
			memcpy(flags_buf, p, flags.len);
			flags.s = flags_buf;
			val_arr[cnt] = &flags;
			ATR(FLAGS);
			break;

		case 'i': /* inbound_ruri */
			val_arr[cnt] = &rq->first_line.u.request.uri;
			ATR(IURI);
			break;

		case 'm': /* sip_method */
			val_arr[cnt] = &rq->first_line.u.request.method;
			ATR(METHOD);
			break;

		case 'n': /* sip_cseq */
			if (rq->cseq && (cseq = get_cseq(rq)) && cseq->number.len) val_arr[cnt] = &cseq->number;
			else val_arr[cnt]=&na;
			ATR(CSEQ);
			break;

		case 'o': /* outbound_ruri */
		        val_arr[cnt] = ouri;
			ATR(OURI);
			break;

		case 'p': /* source_ip */
			     /* We need to make a copy of the string here because ip_addr2a uses static
			      * buffer and subseqent calls to the function would destroy the result
			      */
			src_ip.s = srcip_buf;
			p = ip_addr2a(&rq->rcv.src_ip);
			src_ip.len = strlen(p);
			memcpy(src_ip.s, p, src_ip.len);
			val_arr[cnt] = &src_ip;
			ATR(SRCIP);
			break;

		case 'r': /* from_tag */
			if (rq->from && (from = get_from(rq)) && from->tag_value.len) {
				val_arr[cnt] = &from->tag_value;
			} else {
				val_arr[cnt] = &na;
			}
			ATR(FROMTAG);
			break;

		case 't': /* sip_to */
			val_arr[cnt] = (to && to->body.len) ? &to->body : &na;
			ATR(TO);
			break;

		case 'u': /* digest_username */
			cr = cred_user(rq);
			if (cr) val_arr[cnt] = cr;
			else val_arr[cnt] = &na;
			ATR(DIGUSER);
			break;

		case 'x': /* request_timestamp */
			rqtm_s.s = rqtm_buf;
			rqtm_s.len = TM_BUF_LEN;
			convert_time(&rqtm_s, req_time);
			val_arr[cnt] = &rqtm_s;
			ATR(REQTIMESTAMP);
			break;

		case 'D': /* to_did */
			val_arr[cnt] = &na;
			ATR(TODID);
			break;

		case 'F': /* from_uri */
			if (rq->from && (from = get_from(rq)) && from->uri.len) {
				val_arr[cnt] = &from->uri;
			} else val_arr[cnt] = &na;
			ATR(FROMURI);
			break;

		case 'I': /* from_uid */
			if (get_from_uid(&from_uid, rq) < 0) {
				val_arr[cnt] = &na;
			} else {
				val_arr[cnt] = &from_uid;
			}
			ATR(FROMUID);
			break;

		case 'M': /* from_did */
			val_arr[cnt] = &na;
			ATR(FROMDID);
			break;

		case 'P': /* source_port */
			p = int2str(rq->rcv.src_port, &src_port.len);
			memcpy(srcport_buf, p, src_port.len);
			src_port.s = srcport_buf;
			val_arr[cnt] = &src_port;
			ATR(SRCPORT);
			break;

		case 'R': /* digest_realm */
			cr = cred_realm(rq);
			if (cr) val_arr[cnt] = cr;
			else val_arr[cnt] = &na;
			ATR(DIGREALM);
			break;

		case 'S': /* sip_status */
			if (phrase->len >= 3) {
				mycode.s = phrase->s;
				mycode.len = 3;
				val_arr[cnt] = &mycode;
			} else val_arr[cnt] = &na;
			ATR(STATUS);
			break;

		case 'T': /* to_uri */
			if (rq->to && (pto = get_to(rq)) && pto->uri.len) val_arr[cnt] = &pto->uri;
			else val_arr[cnt] = &na;
			ATR(TOURI);
			break;

		case 'U': /* to_uid */
			if (get_to_uid(&to_uid, rq) < 0) {
				val_arr[cnt] = &na;
			} else {
				val_arr[cnt] = &to_uid;
			}
			ATR(TOUID);
			break;

		case 'X': /* response_timestamp */
			tm_s.s = tm_buf;
			tm_s.len = TM_BUF_LEN;
			convert_time(&tm_s, time(0));
			val_arr[cnt] = &tm_s;
			ATR(RESTIMESTAMP);
			break;

		default:
			LOG(L_CRIT, "BUG:acc:fmt2strar: unknown char: %c\n", *fmt);
			return 0;
		} /* switch (*fmt) */

		tl += val_arr[cnt]->len;
		al += atr_arr[cnt].len;
		fmt++;
		cnt++;
	} /* while (*fmt) */

	*total_len = tl;
	*attr_len = al;
	return cnt;
}



static int log_request(struct sip_msg* rq, str* ouri, struct hdr_field* to, str* txt, str* phrase, time_t req_time)
{
	static str* val_arr[ALL_LOG_FMT_LEN];
	static str atr_arr[ALL_LOG_FMT_LEN];
	int len, attr_cnt, attr_len, i;
	char *log_msg;
	str buf;

	if (skip_cancel(rq)) return 1;

	attr_cnt = fmt2strar(log_fmt, rq, ouri, to, phrase, &len, &attr_len, val_arr, atr_arr, req_time);
	if (!attr_cnt) {
		LOG(L_ERR, "ERROR:acc:log_request: fmt2strar failed\n");
		return -1;
	}

	len += attr_len + ACC_LEN + txt->len + A_EOL_LEN + attr_cnt * (A_SEPARATOR_LEN + A_EQ_LEN) - A_SEPARATOR_LEN;
	log_msg = pkg_malloc(len);
	if (!log_msg) {
		LOG(L_ERR, "ERROR:acc:log_request: No memory left for %d bytes\n", len);
		return -1;
	}

	     /* skip leading text and begin with first item's
	      * separator ", " which will be overwritten by the
	      * leading text later
	      * */
	buf.s = log_msg + ACC_LEN + txt->len - A_SEPARATOR_LEN;
	buf.len = len - ACC_LEN - txt->len + A_SEPARATOR_LEN;

	for (i = 0; i < attr_cnt; i++) {
		append(buf, A_SEPARATOR);
		append_str(buf, atr_arr[i]);
		append(buf, A_EQ);
		append_str(buf, *(val_arr[i]))
	}

	     /* terminating text */
	append(buf, A_EOL);

	     /* leading text */
	buf.s = log_msg;
	buf.len = len;
	append(buf, ACC);
	append_str(buf, *txt);

	LOG(log_level, "%s", log_msg);
	pkg_free(log_msg);
	return 1;
}


static void log_reply(struct cell* t , struct sip_msg* reply, unsigned int code, time_t req_time)
{
	str code_str, *ouri;
	static str lead = STR_STATIC_INIT(ACC_ANSWERED);
	static char code_buf[INT2STR_MAX_LEN];
	char* p;

	p = int2str(code, &code_str.len);
	memcpy(code_buf, p, code_str.len);
	code_str.s = code_buf;

	if (t->relayed_reply_branch >= 0) {
	    ouri = &t->uac[t->relayed_reply_branch].uri;
	} else {
	    ouri = GET_NEXT_HOP(t->uas.request);
	}

	log_request(t->uas.request, ouri, valid_to(t,reply), &lead, &code_str, req_time);
}


static void log_ack(struct cell* t , struct sip_msg *ack, time_t req_time)
{
	struct sip_msg *rq;
	struct hdr_field *to;
	static str lead = STR_STATIC_INIT(ACC_ACKED);
	static char code_buf[INT2STR_MAX_LEN];
	str code_str;
	char* p;

	rq = t->uas.request;
	if (ack->to) to = ack->to;
	else to = rq->to;
	p = int2str(t->uas.status, &code_str.len);
	memcpy(code_buf, p, code_str.len);
	code_str.s = code_buf;
	log_request(ack, GET_RURI(ack), to, &lead, &code_str, req_time);
}


static void log_missed(struct cell* t, struct sip_msg* reply, unsigned int code, time_t req_time)
{
        str acc_text, *ouri;
        static str leading_text = STR_STATIC_INIT(ACC_MISSED);

        get_reply_status(&acc_text, reply, code);
        if (acc_text.s == 0) {
                LOG(L_ERR, "ERROR:acc:log_missed: get_reply_status failed\n" );
                return;
        }

	if (t->relayed_reply_branch >= 0) {
	    ouri = &t->uac[t->relayed_reply_branch].uri;
	} else {
	    ouri = GET_NEXT_HOP(t->uas.request);
	}

        log_request(t->uas.request, ouri, valid_to(t, reply), &leading_text, &acc_text, req_time);
        pkg_free(acc_text.s);
}


/* these wrappers parse all what may be needed; they don't care about
 * the result -- accounting functions just display "unavailable" if there
 * is nothing meaningful
 */
static int acc_log_request1(struct sip_msg *rq, char* p1, char* p2)
{
	str phrase;
	str txt = STR_STATIC_INIT(ACC_REQUEST);

	if (get_str_fparam(&phrase, rq, (fparam_t*)p1) < 0) {
	    phrase.s = 0;
	    phrase.len = 0;
	}
	preparse_req(rq);
	return log_request(rq, GET_RURI(rq), rq->to, &txt, &phrase, time(0));
}


/* these wrappers parse all what may be needed; they don't care about
 * the result -- accounting functions just display "unavailable" if there
 * is nothing meaningful
 */
static int acc_log_missed1(struct sip_msg *rq, char* p1, char* p2)
{
	str phrase;
	str txt = STR_STATIC_INIT(ACC_MISSED);

	if (get_str_fparam(&phrase, rq, (fparam_t*)p1) < 0) {
	    phrase.s = 0;
	    phrase.len = 0;
	}
	preparse_req(rq);
	return log_request(rq, GET_RURI(rq), rq->to, &txt, &phrase, time(0));
}



/* these wrappers parse all what may be needed; they don't care about
 * the result -- accounting functions just display "unavailable" if there
 * is nothing meaningful
 */
static int acc_log_request0(struct sip_msg *rq, char* p1, char* p2)
{
	static str phrase = STR_NULL;
	str txt = STR_STATIC_INIT(ACC_REQUEST);
	return log_request(rq, GET_RURI(rq), rq->to, &txt, &phrase, time(0));
}


/* these wrappers parse all what may be needed; they don't care about
 * the result -- accounting functions just display "unavailable" if there
 * is nothing meaningful
 */
static int acc_log_missed0(struct sip_msg *rq, char* p1, char* p2)
{
	static str phrase = STR_NULL;
	str txt = STR_STATIC_INIT(ACC_MISSED);
	preparse_req(rq);
	return log_request(rq, GET_RURI(rq), rq->to, &txt, &phrase, time(0));
}



static void ack_handler(struct cell* t, int type, struct tmcb_params* ps)
{
	if (is_acc_on(t->uas.request)) {
		preparse_req(ps->req);
		log_ack(t, ps->req, (time_t)*(ps->param));
	}
}


/* initiate a report if we previously enabled MC accounting for this t */
static void failure_handler(struct cell *t, int type, struct tmcb_params* ps)
{
	/* validation */
	if (t->uas.request == 0) {
		DBG("DBG:acc:failure_handler: No uas.request, skipping local transaction\n");
		return;
	}

	if (is_invite(t) && ps->code >= 300) {
		if (is_mc_on(t->uas.request)) {
			log_missed(t, ps->rpl, ps->code, (time_t)*(ps->param));
			resetflag(t->uas.request, log_missed_flag);
		}
	}
}


/* initiate a report if we previously enabled accounting for this t */
static void replyout_handler(struct cell* t, int type, struct tmcb_params* ps)
{
	if (t->uas.request == 0) {
		DBG("DBG:acc:replyout_handler: No uas.request, local transaction, skipping\n");
		return;
	}

	     /* acc_onreply is bound to TMCB_REPLY which may be called
	      * from _reply, like when FR hits; we should not miss this
	      * event for missed calls either
	      */
	failure_handler(t, type, ps);
	if (!should_acc_reply(t, ps->code)) return;
	if (is_acc_on(t->uas.request)) log_reply(t, ps->rpl, ps->code, (time_t)*(ps->param));
}


/* parse incoming replies before cloning */
static void replyin_handler(struct cell *t, int type, struct tmcb_params* ps)
{
	     /* validation */
	if (t->uas.request == 0) {
		LOG(L_ERR, "ERROR:acc:replyin_handler:replyin_handler: 0 request\n");
		return;
	}

	     /* don't parse replies in which we are not interested */
	     /* missed calls enabled ? */
	if (((is_invite(t) && ps->code >= 300 && is_mc_on(t->uas.request))
	     || should_acc_reply(t, ps->code))
	    && (ps->rpl && ps->rpl != FAKED_REPLY)) {
		parse_headers(ps->rpl, HDR_TO_F, 0);
	}
}


/* prepare message and transaction context for later accounting */
void on_req(struct cell* t, int type, struct tmcb_params *ps)
{
	time_t req_time;
	     /* Pass the timestamp of the request as a parameter to callbacks */
	req_time = time(0);

	if (is_acc_on(ps->req) || is_mc_on(ps->req)) {
		if (tmb.register_tmcb(0, t, TMCB_RESPONSE_OUT, replyout_handler, (void*)req_time) <= 0) {
			LOG(L_ERR, "ERROR:acc:on_req: Error while registering TMCB_RESPONSE_OUT callback\n");
			return;
		}

		if (report_ack) {
			if (tmb.register_tmcb(0, t, TMCB_E2EACK_IN, ack_handler, (void*)req_time) <= 0) {
				LOG(L_ERR, "ERROR:acc:on_req: Error while registering TMCB_E2EACK_IN callback\n");
				return;
			}
		}

		if (tmb.register_tmcb(0, t, TMCB_ON_FAILURE_RO, failure_handler, (void*)req_time) <= 0) {
			LOG(L_ERR, "ERROR:acc:on_req: Error while registering TMCB_ON_FAILURE_RO callback\n");
			return;
		}

		if (tmb.register_tmcb(0, t, TMCB_RESPONSE_IN, replyin_handler, (void*)req_time) <= 0) {
			LOG(L_ERR, "ERROR:acc:on_req: Error while registering TMCB_RESPONSE_IN callback\n");
			return;
		}

		     /* do some parsing in advance */
		preparse_req(ps->req);
		     /* also, if that is INVITE, disallow silent t-drop */
		if (ps->req->REQ_METHOD == METHOD_INVITE) {
			DBG("DEBUG: noisy_timer set for accounting\n");
			t->flags |= T_NOISY_CTIMER_FLAG;
		}
	}
}


static int mod_init(void)
{
	load_tm_f load_tm;

	     /* import the TM auto-loading function */
	if ( !(load_tm=(load_tm_f)find_export("load_tm", NO_SCRIPT, 0))) {
		LOG(L_ERR, "ERROR:acc:mod_init: can't import load_tm\n");
		return -1;
	}
	     /* let the auto-loading function load all TM stuff */
	if (load_tm( &tmb )==-1) return -1;
	if (verify_fmt(log_fmt)==-1) return -1;

	     /* register callbacks*/
	     /* listen for all incoming requests  */
	if (tmb.register_tmcb( 0, 0, TMCB_REQUEST_IN, on_req, 0 ) <= 0) {
		LOG(L_ERR,"ERROR:acc:mod_init: cannot register TMCB_REQUEST_IN "
		    "callback\n");
		return -1;
	}

	if (parse_attrs(&avps, &avps_n, attrs) < 0) {
		ERR("Error while parsing 'attrs' module parameter\n");
		return -1;
	}

	return 0;
}
