/* * Accounting module * * $Id: acc_mod.c,v 1.39.2.3 2005/09/20 16:03:14 janakj Exp $ * * Copyright (C) 2001-2003 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * History: * ------- * 2003-03-06: aligned to change in callback names (jiri) * 2003-03-06: fixed improper sql connection, now from * child_init (jiri) * 2003-03-11: New module interface (janakj) * 2003-03-16: flags export parameter added (janakj) * 2003-04-04 grand acc cleanup (jiri) * 2003-04-06: Opens database connection in child_init only (janakj) * 2003-04-24 parameter validation (0 t->uas.request) added (jiri) * 2003-11-04 multidomain support for mysql introduced (jiri) * 2003-12-04 global TM callbacks switched to per transaction callbacks * (bogdan) * 2004-06-06 db cleanup: static db_url, calls to acc_db_{bind,init,close) * (andrei) */ #include #include #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 "acc_mod.h" #include "acc.h" #include "../tm/tm_load.h" #ifdef RAD_ACC # ifdef RADIUSCLIENT_NG_4 # include # else # include # endif #include "dict.h" #endif #ifdef DIAM_ACC #include "diam_dict.h" #include "dict.h" #include "diam_tcp.h" #define M_NAME "acc" #endif MODULE_VERSION struct tm_binds tmb; static int mod_init( void ); static void destroy(void); static int child_init(int rank); /* buffer used to read from TCP connection*/ #ifdef DIAM_ACC rd_buf_t *rb; #endif /* ----- Parameter variables ----------- */ /* what would you like to report on */ /* should early media replies (183) be logged ? default==no */ int early_media = 0; /* should failed replies (>=3xx) be logged ? default==no */ int failed_transactions = 0; /* would you like us to report CANCELs from upstream too? */ int report_cancels = 0; /* report e2e ACKs too */ int report_ack = 1; /* syslog flags, that need to be set for a transaction to * be reported; 0=any, 1..MAX_FLAG otherwise */ int log_flag = 0; int log_missed_flag = 0; /* noisiness level logging facilities are used */ int log_level=L_NOTICE; char *log_fmt=DEFAULT_LOG_FMT; #ifdef RAD_ACC static char *radius_config = (CFG_DIR "radiusclient.conf"); int radius_flag = 0; int radius_missed_flag = 0; static int service_type = -1; void *rh; struct attr attrs[A_MAX]; struct val vals[V_MAX]; #endif /* DIAMETER */ #ifdef DIAM_ACC int diameter_flag = 1; int diameter_missed_flag = 2; char* diameter_client_host="localhost"; int diameter_client_port=3000; #endif #ifdef SQL_ACC static char *db_url=DEFAULT_DB_URL; /* Database url */ /* sql flags, that need to be set for a transaction to * be reported; 0=any, 1..MAX_FLAG otherwise; by default * set to the same values as syslog -> reporting for both * takes place */ int db_flag = 0; int db_missed_flag = 0; int db_localtime = 0; char *db_table_acc="acc"; /* name of database table> */ /* names of columns in tables acc/missed calls*/ char* acc_sip_from_col = "sip_from"; char* acc_sip_to_col = "sip_to"; char* acc_sip_status_col = "sip_status"; char* acc_sip_method_col = "sip_method"; char* acc_i_uri_col = "i_uri"; char* acc_o_uri_col = "o_uri"; char* acc_totag_col = "totag"; char* acc_fromtag_col = "fromtag"; char* acc_domain_col = "domain"; char* acc_from_uri = "from_uri"; char* acc_to_uri = "to_uri"; char* acc_sip_callid_col = "sip_callid"; char* acc_user_col = "username"; char* acc_time_col = "time"; /* name of missed calls table, default=="missed_calls" */ char *db_table_mc="missed_calls"; #endif static int w_acc_log_request(struct sip_msg *rq, char *comment, char *foo); #ifdef SQL_ACC static int w_acc_db_request(struct sip_msg *rq, char *comment, char *foo); #endif #ifdef RAD_ACC static int w_acc_rad_request(struct sip_msg *rq, char *comment, char *foo); #endif /* DIAMETER */ #ifdef DIAM_ACC static int w_acc_diam_request(struct sip_msg *rq, char *comment, char *foo); #endif static cmd_export_t cmds[] = { {"acc_log_request", w_acc_log_request, 1, 0, REQUEST_ROUTE|FAILURE_ROUTE}, #ifdef SQL_ACC {"acc_db_request", w_acc_db_request, 2, 0, REQUEST_ROUTE|FAILURE_ROUTE}, #endif #ifdef RAD_ACC {"acc_rad_request", w_acc_rad_request, 1, 0, REQUEST_ROUTE|FAILURE_ROUTE}, #endif /* DIAMETER */ #ifdef DIAM_ACC {"acc_diam_request", w_acc_diam_request, 1, 0,REQUEST_ROUTE|FAILURE_ROUTE}, #endif {0, 0, 0, 0, 0} }; static param_export_t params[] = { {"early_media", INT_PARAM, &early_media }, {"failed_transactions", INT_PARAM, &failed_transactions }, {"report_ack", INT_PARAM, &report_ack }, {"report_cancels", INT_PARAM, &report_cancels }, /* syslog specific */ {"log_flag", INT_PARAM, &log_flag }, {"log_missed_flag", INT_PARAM, &log_missed_flag }, {"log_level", INT_PARAM, &log_level }, {"log_fmt", STR_PARAM, &log_fmt }, #ifdef RAD_ACC {"radius_config", STR_PARAM, &radius_config }, {"radius_flag", INT_PARAM, &radius_flag }, {"radius_missed_flag", INT_PARAM, &radius_missed_flag }, {"service_type", INT_PARAM, &service_type }, #endif /* DIAMETER */ #ifdef DIAM_ACC {"diameter_flag", INT_PARAM, &diameter_flag }, {"diameter_missed_flag",INT_PARAM, &diameter_missed_flag}, {"diameter_client_host",STR_PARAM, &diameter_client_host}, {"diameter_client_port",INT_PARAM, &diameter_client_port}, #endif /* db-specific */ #ifdef SQL_ACC {"db_flag", INT_PARAM, &db_flag }, {"db_missed_flag", INT_PARAM, &db_missed_flag }, {"db_table_acc", STR_PARAM, &db_table_acc }, {"db_table_missed_calls", STR_PARAM, &db_table_mc }, {"db_url", STR_PARAM, &db_url }, {"db_localtime", INT_PARAM, &db_localtime}, {"acc_sip_from_column", STR_PARAM, &acc_sip_from_col }, {"acc_sip_to_column", STR_PARAM, &acc_sip_to_col}, {"acc_sip_status_column", STR_PARAM, &acc_sip_status_col}, {"acc_sip_method_column", STR_PARAM, &acc_sip_method_col}, {"acc_i_uri_column", STR_PARAM, &acc_i_uri_col }, {"acc_o_uri_column", STR_PARAM, &acc_o_uri_col }, {"acc_sip_callid_column", STR_PARAM, &acc_sip_callid_col}, {"acc_user_column", STR_PARAM, &acc_user_col }, {"acc_time_column", STR_PARAM, &acc_time_col }, {"acc_from_uri_column", STR_PARAM, &acc_from_uri }, {"acc_to_uri_column", STR_PARAM, &acc_to_uri }, {"acc_totag_column", STR_PARAM, &acc_totag_col }, {"acc_fromtag_column", STR_PARAM, &acc_fromtag_col }, {"acc_domain_column", STR_PARAM, &acc_domain_col }, #endif {0,0,0} }; struct module_exports exports= { "acc", cmds, /* exported functions */ params, /* exported params */ mod_init, /* initialization module */ 0, /* response function */ destroy, /* destroy function */ 0, /* oncancel function */ child_init /* per-child init function */ }; /* ------------- Callback handlers --------------- */ static void acc_onreq( struct cell* t, int type, struct tmcb_params *ps ); static void tmcb_func( struct cell* t, int type, struct tmcb_params *ps ); /* --------------- function definitions -------------*/ static int verify_fmt(char *fmt) { if (!fmt) { LOG(L_ERR, "ERROR: verify_fmt: formatting string zero\n"); return -1; } if (!(*fmt)) { LOG(L_ERR, "ERROR: verify_fmt: formatting string empty\n"); return -1; } if (strlen(fmt)>ALL_LOG_FMT_LEN) { LOG(L_ERR, "ERROR: verify_fmt: formatting string too long\n"); return -1; } while(*fmt) { if (!strchr(ALL_LOG_FMT,*fmt)) { LOG(L_ERR, "ERROR: verify_fmt: char in log_fmt invalid: %c\n", *fmt); return -1; } fmt++; } return 1; } static int mod_init( void ) { load_tm_f load_tm; fprintf( stderr, "acc - initializing\n"); /* 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, acc_onreq, 0 ) <=0 ) { LOG(L_ERR,"ERROR:acc:mod_init: cannot register TMCB_REQUEST_IN " "callback\n"); return -1; } #ifdef SQL_ACC if (acc_db_bind(db_url)<0){ LOG(L_ERR, "ERROR:acc_db_init: failed..." "did you load a database module?\n"); return -1; } #endif #ifdef RAD_ACC memset(attrs, 0, sizeof(attrs)); memset(attrs, 0, sizeof(vals)); attrs[A_CALLING_STATION_ID].n = "Calling-Station-Id"; attrs[A_CALLED_STATION_ID].n = "Called-Station-Id"; attrs[A_SIP_TRANSLATED_REQUEST_URI].n = "Sip-Translated-Request-URI"; attrs[A_ACCT_SESSION_ID].n = "Acct-Session-Id"; attrs[A_SIP_TO_TAG].n = "Sip-To-Tag"; attrs[A_SIP_FROM_TAG].n = "Sip-From-Tag"; attrs[A_SIP_CSEQ].n = "Sip-CSeq"; attrs[A_ACCT_STATUS_TYPE].n = "Acct-Status-Type"; attrs[A_SERVICE_TYPE].n = "Service-Type"; attrs[A_SIP_RESPONSE_CODE].n = "Sip-Response-Code"; attrs[A_SIP_METHOD].n = "Sip-Method"; attrs[A_USER_NAME].n = "User-Name"; vals[V_STATUS_START].n = "Start"; vals[V_STATUS_STOP].n = "Stop"; vals[V_STATUS_FAILED].n = "Failed"; vals[V_SIP_SESSION].n = "Sip-Session"; /* open log */ rc_openlog("ser"); /* read config */ if ((rh = rc_read_config(radius_config)) == NULL) { LOG(L_ERR, "ERROR: acc: error opening radius config file: %s\n", radius_config ); return -1; } /* read dictionary */ if (rc_read_dictionary(rh, rc_conf_str(rh, "dictionary"))!=0) { LOG(L_ERR, "ERROR: acc: error reading radius dictionary\n"); return -1; } INIT_AV(rh, attrs, vals, "acc", -1, -1); if (service_type != -1) vals[V_SIP_SESSION].v = service_type; #endif return 0; } static int child_init(int rank) { #ifdef SQL_ACC if (acc_db_init()<0) return -1; #endif /* DIAMETER */ #ifdef DIAM_ACC /* open TCP connection */ DBG(M_NAME": Initializing TCP connection\n"); sockfd = init_mytcp(diameter_client_host, diameter_client_port); if(sockfd==-1) { DBG(M_NAME": TCP connection not established\n"); return -1; } DBG(M_NAME": TCP connection established on sockfd=%d\n", sockfd); /* every child with its buffer */ rb = (rd_buf_t*)pkg_malloc(sizeof(rd_buf_t)); if(!rb) { DBG("acc: mod_child_init: no more free memory\n"); return -1; } rb->buf = 0; #endif return 0; } static void destroy(void) { #ifdef SQL_ACC acc_db_close(); #endif #ifdef DIAM_ACC close_tcp_connection(sockfd); #endif } static inline void acc_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 */ parse_headers(rq, HDR_CALLID| HDR_FROM| HDR_TO, 0 ); parse_from_header(rq); if (strchr(log_fmt, 'p') || strchr(log_fmt, 'D')) { parse_orig_ruri(rq); } } /* prepare message and transaction context for later accounting */ static void acc_onreq( struct cell* t, int type, struct tmcb_params *ps ) { int tmcb_types; if (is_acc_on(ps->req) || is_mc_on(ps->req)) { /* install addaitional handlers */ tmcb_types = /* report on completed transactions */ TMCB_RESPONSE_OUT | /* account e2e acks if configured to do so */ TMCB_E2EACK_IN | /* report on missed calls */ TMCB_ON_FAILURE_RO | /* get incoming replies ready for processing */ TMCB_RESPONSE_IN; if (tmb.register_tmcb( 0, t, tmcb_types, tmcb_func, 0 )<=0) { LOG(L_ERR,"ERROR:acc:acc_onreq: cannot register additional " "callbacks\n"); return; } /* do some parsing in advance */ acc_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; } } } /* 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 */ } /* parse incoming replies before cloning */ static inline void acc_onreply_in(struct cell *t, struct sip_msg *reply, int code, void *param) { /* validation */ if (t->uas.request==0) { LOG(L_ERR, "ERROR: acc: should_acc_reply: 0 request\n"); return; } /* don't parse replies in which we are not interested */ /* missed calls enabled ? */ if (((is_invite(t) && code>=300 && is_mc_on(t->uas.request)) || should_acc_reply(t,code)) && (reply && reply!=FAKED_REPLY)) { parse_headers(reply, HDR_TO, 0 ); } } /* initiate a report if we previously enabled MC accounting for this t */ static inline void on_missed(struct cell *t, struct sip_msg *reply, int code, void *param ) { int reset_lmf; #ifdef SQL_ACC int reset_dmf; #endif #ifdef RAD_ACC int reset_rmf; #endif /* DIAMETER */ #ifdef DIAM_ACC int reset_dimf; #endif /* validation */ if (t->uas.request==0) { DBG("DBG: acc: on_missed: no uas.request, local t; skipping\n"); return; } if (is_invite(t) && code>=300) { if (is_log_mc_on(t->uas.request)) { acc_log_missed( t, reply, code); reset_lmf=1; } else reset_lmf=0; #ifdef SQL_ACC if (is_db_mc_on(t->uas.request)) { acc_db_missed( t, reply, code); reset_dmf=1; } else reset_dmf=0; #endif #ifdef RAD_ACC if (is_rad_mc_on(t->uas.request)) { acc_rad_missed(t, reply, code ); reset_rmf=1; } else reset_rmf=0; #endif /* DIAMETER */ #ifdef DIAM_ACC if (is_diam_mc_on(t->uas.request)) { acc_diam_missed(t, reply, code ); reset_dimf=1; } else reset_dimf=0; #endif /* we report on missed calls when the first * forwarding attempt fails; we do not wish to * report on every attempt; so we clear the flags; * we do it after all reporting is over to be sure * that all reporting functions got a fair chance */ if (reset_lmf) resetflag(t->uas.request, log_missed_flag); #ifdef SQL_ACC if (reset_dmf) resetflag(t->uas.request, db_missed_flag); #endif #ifdef RAD_ACC if (reset_rmf) resetflag(t->uas.request, radius_missed_flag); #endif /* DIAMETER */ #ifdef DIAM_ACC if (reset_dimf) resetflag(t->uas.request, diameter_missed_flag); #endif } } /* initiate a report if we previously enabled accounting for this t */ static inline void acc_onreply( struct cell* t, struct sip_msg *reply, int code, void *param ) { /* validation */ if (t->uas.request==0) { DBG("DBG: acc: onreply: no uas.request, local t; 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 */ on_missed(t, reply, code, param ); if (!should_acc_reply(t, code)) return; if (is_log_acc_on(t->uas.request)) acc_log_reply(t, reply, code); #ifdef SQL_ACC if (is_db_acc_on(t->uas.request)) acc_db_reply(t, reply, code); #endif #ifdef RAD_ACC if (is_rad_acc_on(t->uas.request)) acc_rad_reply(t, reply, code); #endif /* DIAMETER */ #ifdef DIAM_ACC if (is_diam_acc_on(t->uas.request)) acc_diam_reply(t, reply, code); #endif } static inline void acc_onack( struct cell* t , struct sip_msg *ack, int code, void *param ) { /* only for those guys who insist on seeing ACKs as well */ if (!report_ack) return; /* if acc enabled for flagged transaction, check if flag matches */ if (is_log_acc_on(t->uas.request)) { acc_preparse_req(ack); acc_log_ack(t, ack); } #ifdef SQL_ACC if (is_db_acc_on(t->uas.request)) { acc_preparse_req(ack); acc_db_ack(t, ack); } #endif #ifdef RAD_ACC if (is_rad_acc_on(t->uas.request)) { acc_preparse_req(ack); acc_rad_ack(t,ack); } #endif /* DIAMETER */ #ifdef DIAM_ACC if (is_diam_acc_on(t->uas.request)) { acc_preparse_req(ack); acc_diam_ack(t,ack); } #endif } static void tmcb_func( struct cell* t, int type, struct tmcb_params *ps ) { if (type&TMCB_RESPONSE_OUT) { acc_onreply( t, ps->rpl, ps->code, ps->param ); } else if (type&TMCB_E2EACK_IN) { acc_onack( t, ps->req, ps->code, ps->param ); } else if (type&TMCB_ON_FAILURE_RO) { on_missed( t, ps->rpl, ps->code, ps->param ); } else if (type&TMCB_RESPONSE_IN) { acc_onreply_in( t, ps->rpl, ps->code, ps->param); } } /* 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 w_acc_log_request(struct sip_msg *rq, char *comment, char *foo) { str txt; str phrase; txt.s=ACC_REQUEST; txt.len=ACC_REQUEST_LEN; phrase.s=comment; phrase.len=strlen(comment); /* fix_param would be faster! */ acc_preparse_req(rq); return acc_log_request(rq, rq->to, &txt, &phrase); } #ifdef SQL_ACC static int w_acc_db_request(struct sip_msg *rq, char *comment, char *table) { str phrase; phrase.s=comment; phrase.len=strlen(comment); /* fix_param would be faster! */ acc_preparse_req(rq); return acc_db_request(rq, rq->to,&phrase,table, SQL_MC_FMT ); } #endif #ifdef RAD_ACC static int w_acc_rad_request(struct sip_msg *rq, char *comment, char *foo) { str phrase; phrase.s=comment; phrase.len=strlen(comment); /* fix_param would be faster! */ acc_preparse_req(rq); return acc_rad_request(rq, rq->to,&phrase); } #endif /* DIAMETER */ #ifdef DIAM_ACC static int w_acc_diam_request(struct sip_msg *rq, char *comment, char *foo) { str phrase; phrase.s=comment; phrase.len=strlen(comment); /* fix_param would be faster! */ acc_preparse_req(rq); return acc_diam_request(rq, rq->to,&phrase); } #endif