/* * Copyright (C) 2001-2003 FhG Fokus * Copyright (C) 2006 Voice Sistem SRL * Copyright (C) 2008 Juha Heinanen * * This file is part of Kamailio, a free SIP server. * * Kamailio 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 * * Kamailio 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 * */ /*! \file * \ingroup acc * \brief Acc:: Core accounting * * Module: \ref acc */ #include <stdio.h> #include <time.h> #include "../../core/dprint.h" #include "../../core/error.h" #include "../../core/mem/mem.h" #include "../../core/usr_avp.h" #include "../../core/async_task.h" #include "../../lib/srdb1/db.h" #include "../../core/parser/hf.h" #include "../../core/parser/msg_parser.h" #include "../../core/parser/parse_from.h" #include "../../core/parser/digest/digest.h" #include "../../modules/tm/t_funcs.h" #include "acc_mod.h" #include "acc.h" #include "acc_extra.h" #include "acc_logic.h" #include "acc_api.h" extern struct acc_extra *log_extra; extern struct acc_extra *leg_info; extern struct acc_enviroment acc_env; extern char *acc_time_format; extern int acc_extra_nullable; static db_func_t acc_dbf; static db1_con_t* db_handle=0; extern struct acc_extra *db_extra; /* arrays used to collect the values before being * pushed to the storage backend (whatever used) * (3 = datetime + max 2 from time_mode) */ static str *val_arr = NULL; static int *int_arr = NULL; static char *type_arr = NULL; #define ACC_TIME_FORMAT_SIZE 128 static char acc_time_format_buf[ACC_TIME_FORMAT_SIZE]; /******************************************** * acc CORE function ********************************************/ #define get_ft_body( _ft_hdr) \ ((struct to_body*)_ft_hdr->parsed) #define SET_EMPTY_VAL(_i) \ do { \ c_vals[_i].s = 0; \ c_vals[_i].len = 0; \ } while(0) /* returns: * method name * from TAG * to TAG * callid * sip_code * sip_status * */ int core2strar(struct sip_msg *req, str *c_vals, int *i_vals, char *t_vals) { struct to_body *ft_body; struct hdr_field *from; struct hdr_field *to; /* method : request/reply - cseq parsed in acc_preparse_req() */ c_vals[0] = get_cseq(req)->method; t_vals[0] = TYPE_STR; /* from/to URI and TAG */ if (req->msg_flags&FL_REQ_UPSTREAM) { LM_DBG("the flag UPSTREAM is set -> swap F/T\n"); \ from = acc_env.to; to = req->from; } else { from = req->from; to = acc_env.to; } if (from && (ft_body=get_ft_body(from)) && ft_body->tag_value.len) { c_vals[1] = ft_body->tag_value; t_vals[1] = TYPE_STR; } else { SET_EMPTY_VAL(1); t_vals[1] = TYPE_NULL; } if (to && (ft_body=get_ft_body(to)) && ft_body->tag_value.len) { c_vals[2] = ft_body->tag_value; t_vals[2] = TYPE_STR; } else { SET_EMPTY_VAL(2); t_vals[2] = TYPE_NULL; } LM_DBG("default - totag[%.*s]\n", c_vals[2].len, c_vals[2].s); if (c_vals[2].len == 0 && acc_env.to_tag.s && acc_env.to_tag.len > 0) { LM_DBG("extra [%p] totag[%.*s]\n", acc_env.to_tag.s, acc_env.to_tag.len, acc_env.to_tag.s); c_vals[2].len = acc_env.to_tag.len; c_vals[2].s = acc_env.to_tag.s; } /* Callid */ if (req->callid && req->callid->body.len) { c_vals[3] = req->callid->body; t_vals[3] = TYPE_STR; } else { SET_EMPTY_VAL(3); t_vals[3] = TYPE_NULL; } /* SIP code */ c_vals[4] = acc_env.code_s; i_vals[4] = acc_env.code; t_vals[4] = TYPE_INT; /* SIP status */ c_vals[5] = acc_env.reason; t_vals[5] = TYPE_STR; gettimeofday(&acc_env.tv, NULL); acc_env.ts = acc_env.tv.tv_sec; return ACC_CORE_LEN; } /******************************************** * LOG ACCOUNTING ********************************************/ static str *log_attrs = NULL; #define SET_LOG_ATTR(_n,_atr) \ do { \ log_attrs[_n].s=A_##_atr; \ log_attrs[_n].len=A_##_atr##_LEN; \ n++; \ } while(0) void acc_log_init(void) { struct acc_extra *extra; int n; n = 0; /* fixed core attributes */ SET_LOG_ATTR(n,METHOD); SET_LOG_ATTR(n,FROMTAG); SET_LOG_ATTR(n,TOTAG); SET_LOG_ATTR(n,CALLID); SET_LOG_ATTR(n,CODE); SET_LOG_ATTR(n,STATUS); /* init the extra db keys */ for(extra=log_extra; extra ; extra=extra->next) log_attrs[n++] = extra->name; /* multi leg call columns */ for( extra=leg_info ; extra ; extra=extra->next) log_attrs[n++] = extra->name; } int acc_log_request( struct sip_msg *rq) { static char log_msg[MAX_SYSLOG_SIZE]; static char *log_msg_end=log_msg+MAX_SYSLOG_SIZE-2; char *p; int n; int m; int o; int i; struct tm t; double dtime; /* get default values */ m = core2strar( rq, val_arr, int_arr, type_arr); /* get extra values */ o = extra2strar( log_extra, rq, val_arr+m, int_arr+m, type_arr+m); m += o; for ( i=0,p=log_msg ; i<m ; i++ ) { if (p+1+log_attrs[i].len+1+val_arr[i].len >= log_msg_end) { LM_WARN("acc message too long, truncating..\n"); p = log_msg_end; break; } *(p++) = A_SEPARATOR_CHR; memcpy(p, log_attrs[i].s, log_attrs[i].len); p += log_attrs[i].len; *(p++) = A_EQ_CHR; if (val_arr[i].s != NULL) { memcpy(p, val_arr[i].s, val_arr[i].len); p += val_arr[i].len; } } /* get per leg attributes */ if ( leg_info ) { n = legs2strar(leg_info,rq,val_arr+m,int_arr+m,type_arr+m, 1); do { for (i=m; i<m+n; i++) { if (p+1+log_attrs[i].len+1+val_arr[i].len >= log_msg_end) { LM_WARN("acc message too long, truncating..\n"); p = log_msg_end; break; } *(p++) = A_SEPARATOR_CHR; memcpy(p, log_attrs[i].s, log_attrs[i].len); p += log_attrs[i].len; *(p++) = A_EQ_CHR; if (val_arr[i].s != NULL) { memcpy(p, val_arr[i].s, val_arr[i].len); p += val_arr[i].len; } } } while (p!=log_msg_end && (n=legs2strar(leg_info,rq,val_arr+m, int_arr+m,type_arr+m, 0))!=0); } /* terminating line */ *(p++) = '\n'; *(p++) = 0; if(acc_time_mode==1) { LM_GEN2(log_facility, log_level, "%.*stimestamp=%lu;%s=%u%s", acc_env.text.len, acc_env.text.s,(unsigned long)acc_env.ts, acc_time_exten.s, (unsigned int)acc_env.tv.tv_usec, log_msg); } else if(acc_time_mode==2) { dtime = (double)acc_env.tv.tv_usec; dtime = (dtime / 1000000) + (double)acc_env.tv.tv_sec; LM_GEN2(log_facility, log_level, "%.*stimestamp=%lu;%s=%.3f%s", acc_env.text.len, acc_env.text.s,(unsigned long)acc_env.ts, acc_time_attr.s, dtime, log_msg); } else if(acc_time_mode==3 || acc_time_mode==4) { if(acc_time_mode==3) { localtime_r(&acc_env.ts, &t); } else { gmtime_r(&acc_env.ts, &t); } if(strftime(acc_time_format_buf, ACC_TIME_FORMAT_SIZE, acc_time_format, &t)<=0) { acc_time_format_buf[0] = '\0'; } LM_GEN2(log_facility, log_level, "%.*stimestamp=%lu;%s=%s%s", acc_env.text.len, acc_env.text.s,(unsigned long)acc_env.ts, acc_time_attr.s, acc_time_format_buf, log_msg); } else { LM_GEN2(log_facility, log_level, "%.*stimestamp=%lu%s", acc_env.text.len, acc_env.text.s,(unsigned long)acc_env.ts, log_msg); } /* free memory allocated by extra2strar */ free_strar_mem( &(type_arr[m-o]), &(val_arr[m-o]), o, m); return 1; } /******************************************** * SQL ACCOUNTING ********************************************/ int acc_is_db_ready(void) { if(db_handle!=0) return 1; return 0; } /* caution: keys need to be aligned to core format * (3 = datetime + max 2 from time_mode) */ static db_key_t *db_keys = NULL; static db_val_t *db_vals = NULL; int acc_get_db_handlers(void **vf, void **vh) { if(db_handle==0) return -1; *vf = (void*)&acc_dbf; *vh = (void*)db_handle; return 0; } static void acc_db_init_keys(void) { struct acc_extra *extra; int time_idx; int i; int n; /* init the static db keys */ n = 0; /* caution: keys need to be aligned to core format */ db_keys[n++] = &acc_method_col; db_keys[n++] = &acc_fromtag_col; db_keys[n++] = &acc_totag_col; db_keys[n++] = &acc_callid_col; db_keys[n++] = &acc_sipcode_col; db_keys[n++] = &acc_sipreason_col; db_keys[n++] = &acc_time_col; time_idx = n-1; if(acc_time_mode==1 || acc_time_mode==2 || acc_time_mode==3 || acc_time_mode==4) { db_keys[n++] = &acc_time_attr; if(acc_time_mode==1) { db_keys[n++] = &acc_time_exten; } } /* init the extra db keys */ for(extra=db_extra; extra ; extra=extra->next) db_keys[n++] = &extra->name; /* multi leg call columns */ for( extra=leg_info ; extra ; extra=extra->next) db_keys[n++] = &extra->name; /* init the values */ for(i=0; i<n; i++) { VAL_TYPE(db_vals+i)=DB1_STR; VAL_NULL(db_vals+i)=0; } VAL_TYPE(db_vals+time_idx)=DB1_DATETIME; if(acc_time_mode==1) { VAL_TYPE(db_vals+time_idx+1)=DB1_INT; VAL_TYPE(db_vals+time_idx+2)=DB1_INT; } else if(acc_time_mode==2) { VAL_TYPE(db_vals+time_idx+1)=DB1_DOUBLE; } else if(acc_time_mode==3 || acc_time_mode==4) { VAL_TYPE(db_vals+time_idx+1)=DB1_STRING; } } /* binds to the corresponding database module * returns 0 on success, -1 on error */ int acc_db_init(const str* db_url) { if (db_bind_mod(db_url, &acc_dbf)<0){ LM_ERR("bind_db failed\n"); return -1; } /* Check database capabilities */ if (!DB_CAPABILITY(acc_dbf, DB_CAP_INSERT)) { LM_ERR("database module does not implement insert function\n"); return -1; } acc_db_init_keys(); return 0; } /* initialize the database connection * returns 0 on success, -1 on error */ int acc_db_init_child(const str *db_url) { db_handle=acc_dbf.init(db_url); if (db_handle==0){ LM_ERR("unable to connect to the database\n"); return -1; } return 0; } /* close a db connection */ void acc_db_close(void) { if (db_handle && acc_dbf.close) acc_dbf.close(db_handle); } int acc_db_request( struct sip_msg *rq) { int m; int n; int i; int o; struct tm t; double dtime; /* formated database columns */ m = core2strar( rq, val_arr, int_arr, type_arr ); for(i=0; i<m; i++) VAL_STR(db_vals+i) = val_arr[i]; /* time value */ VAL_TIME(db_vals+m) = acc_env.ts; m++; /* extra time value */ if(acc_time_mode==1) { VAL_INT(db_vals+m) = (int)acc_env.tv.tv_sec; m++; VAL_INT(db_vals+m) = (int)acc_env.tv.tv_usec; m++; } else if(acc_time_mode==2) { dtime = (double)acc_env.tv.tv_usec; dtime = (dtime / 1000000) + (double)acc_env.tv.tv_sec; VAL_DOUBLE(db_vals+m) = dtime; m++; } else if(acc_time_mode==3 || acc_time_mode==4) { if(acc_time_mode==3) { localtime_r(&acc_env.ts, &t); } else { gmtime_r(&acc_env.ts, &t); } if(strftime(acc_time_format_buf, ACC_TIME_FORMAT_SIZE, acc_time_format, &t)<=0) { acc_time_format_buf[0] = '\0'; } VAL_STRING(db_vals+m) = acc_time_format_buf; m++; } i = m; /* extra columns */ o = extra2strar( db_extra, rq, val_arr+m, int_arr+m, type_arr+m); m += o; for( ; i<m; i++) { if (acc_extra_nullable == 1 && type_arr[i] == TYPE_NULL) { LM_DBG("attr[%d] is NULL\n", i); VAL_NULL(db_vals + i) = 1; } else { LM_DBG("attr[%d] is STR\n", i); VAL_STR(db_vals+i) = val_arr[i]; } } if (acc_dbf.use_table(db_handle, &acc_env.text/*table*/) < 0) { LM_ERR("error in use_table\n"); goto error; } /* multi-leg columns */ if ( !leg_info ) { if(acc_db_insert_mode==1 && acc_dbf.insert_delayed!=NULL) { if (acc_dbf.insert_delayed(db_handle, db_keys, db_vals, m) < 0) { LM_ERR("failed to insert delayed into database\n"); goto error; } } else if(acc_db_insert_mode==2 && acc_dbf.insert_async!=NULL && async_task_workers_active()) { if (acc_dbf.insert_async(db_handle, db_keys, db_vals, m) < 0) { LM_ERR("failed to insert async into database\n"); goto error; } } else { if (acc_dbf.insert(db_handle, db_keys, db_vals, m) < 0) { LM_ERR("failed to insert into database\n"); goto error; } } } else { n = legs2strar(leg_info,rq,val_arr+m,int_arr+m,type_arr+m,1); do { for (i=m; i<m+n; i++) { if (acc_extra_nullable == 1 && type_arr[i] == TYPE_NULL) { VAL_NULL(db_vals + i) = 1; } else { VAL_STR(db_vals+i)=val_arr[i]; } } if(acc_db_insert_mode==1 && acc_dbf.insert_delayed!=NULL) { if(acc_dbf.insert_delayed(db_handle,db_keys,db_vals,m+n)<0) { LM_ERR("failed to insert delayed into database\n"); goto error; } } else if(acc_db_insert_mode==2 && acc_dbf.insert_async!=NULL) { if(acc_dbf.insert_async(db_handle,db_keys,db_vals,m+n)<0) { LM_ERR("failed to insert async into database\n"); goto error; } } else { if (acc_dbf.insert(db_handle, db_keys, db_vals, m+n) < 0) { LM_ERR("failed to insert into database\n"); goto error; } } }while ( (n=legs2strar(leg_info,rq,val_arr+m,int_arr+m, type_arr+m,0))!=0 ); } /* free memory allocated by extra2strar */ free_strar_mem( &(type_arr[m-o]), &(val_arr[m-o]), o, m); return 1; error: /* free memory allocated by extra2strar */ free_strar_mem( &(type_arr[m-o]), &(val_arr[m-o]), o, m); return -1; } /** * @brief test if acc flag from enternal engines is set */ int is_eng_acc_on(sip_msg_t *msg) { acc_engine_t *e; e = acc_api_get_engines(); if(e==NULL) { return 0; } while(e) { if(e->flags & 1) { if(isflagset(msg, e->acc_flag) == 1) { return 1; } } e = e->next; } return 0; } /** * @brief test if acc flag from enternal engines is set */ int is_eng_mc_on(sip_msg_t *msg) { acc_engine_t *e; e = acc_api_get_engines(); if(e==NULL) { return 0; } while(e) { if(e->flags & 1) { if(isflagset(msg, e->missed_flag) == 1) { return 1; } } e = e->next; } return 0; } /** * @brief execute all acc engines for a SIP request event */ int acc_run_engines(struct sip_msg *msg, int type, int *reset) { acc_info_t inf; acc_engine_t *e; e = acc_api_get_engines(); if(e==NULL) return 0; memset(&inf, 0, sizeof(acc_info_t)); inf.env = &acc_env; inf.varr = val_arr; inf.iarr = int_arr; inf.tarr = type_arr; inf.leg_info = leg_info; while(e) { if(e->flags & 1) { if((type==0) && isflagset(msg, e->acc_flag) == 1) { LM_DBG("acc event for engine: %s\n", e->name); e->acc_req(msg, &inf); if(reset) *reset |= 1 << e->acc_flag; } if((type==1) && isflagset(msg, e->missed_flag) == 1) { LM_DBG("missed event for engine: %s\n", e->name); e->acc_req(msg, &inf); if(reset) *reset |= 1 << e->missed_flag; } } e = e->next; } return 0; } /** * @brief set hooks to acc_info_t attributes */ void acc_api_set_arrays(acc_info_t *inf) { inf->varr = val_arr; inf->iarr = int_arr; inf->tarr = type_arr; inf->leg_info = leg_info; } int acc_arrays_alloc(void) { if ((val_arr = pkg_malloc((ACC_CORE_LEN + acc_extra_size + MAX_ACC_LEG + 3) * sizeof(str))) == NULL) { LM_ERR("failed to alloc val_arr\n"); return -1; } if ((int_arr = pkg_malloc((ACC_CORE_LEN + acc_extra_size + MAX_ACC_LEG + 3) * sizeof(int))) == NULL) { LM_ERR("failed to alloc int_arr\n"); return -1; } if ((type_arr = pkg_malloc((ACC_CORE_LEN + acc_extra_size + MAX_ACC_LEG + 3) * sizeof(char))) == NULL) { LM_ERR("failed to alloc type_arr\n"); return -1; } if ((log_attrs = pkg_malloc((ACC_CORE_LEN + acc_extra_size + MAX_ACC_LEG + 3) * sizeof(str))) == NULL) { LM_ERR("failed to alloc log_attrs\n"); return -1; } if ((db_keys = pkg_malloc((ACC_CORE_LEN + acc_extra_size + MAX_ACC_LEG + 3) * sizeof(db_key_t))) == NULL) { LM_ERR("failed to alloc db_keys\n"); return -1; } if ((db_vals = pkg_malloc((ACC_CORE_LEN + acc_extra_size + MAX_ACC_LEG + 3) * sizeof(db_val_t))) == NULL) { LM_ERR("failed to alloc db_vals\n"); return -1; } return 1; } void acc_arrays_free(void) { if (val_arr) { pkg_free(val_arr); } if (int_arr) { pkg_free(int_arr); } if (type_arr) { pkg_free(type_arr); } if (log_attrs) { pkg_free(log_attrs); } if (db_keys) { pkg_free(db_keys); } if (db_vals) { pkg_free(db_vals); } }