mirror of https://github.com/sipwise/kamailio.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1094 lines
29 KiB
1094 lines
29 KiB
/*
|
|
* Accounting module
|
|
*
|
|
* $Id$
|
|
*
|
|
* 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 <radiusclient-ng.h>
|
|
#include "../../rad_dict.h"
|
|
|
|
#include "../../sr_module.h"
|
|
#include "../../dprint.h"
|
|
#include "../../mem/mem.h"
|
|
#include "../../modules/tm/t_hooks.h"
|
|
#include "../../modules/tm/tm_load.h"
|
|
#include "../../modules/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 "../../modules/tm/tm_load.h"
|
|
|
|
#include "../../parser/parse_rr.h"
|
|
#include "../../trim.h"
|
|
#include "../acc_syslog/attrs.h"
|
|
|
|
/*
|
|
* FIXME:
|
|
* - Quote attribute values properly
|
|
* - Explicitly called accounting function will generate Acct-Status-Type
|
|
* set to failed (rad_status does not work properly in this case), it
|
|
* should generate Interim-Update
|
|
* - INVITEs with to tag should generate Interim-Update
|
|
* - Configurable option which allows to switch From/To based on the
|
|
* value of ftag route parameter
|
|
*/
|
|
|
|
/*
|
|
* 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
|
|
* s: server_id
|
|
* 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 "acdfgimnoprstuxDFIMPRSTUX"
|
|
#define ALL_LOG_FMT_LEN (sizeof(ALL_LOG_FMT) - 1)
|
|
|
|
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 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_param = "";
|
|
avp_ident_t* avps;
|
|
int avps_n;
|
|
|
|
static char *radius_config = "/usr/local/etc/radiusclient-ng/radiusclient.conf";
|
|
static int service_type = -1;
|
|
static int swap_dir = 0;
|
|
|
|
static void *rh;
|
|
static struct attr attrs[A_MAX];
|
|
static struct val vals[V_MAX];
|
|
|
|
static int acc_rad_request0(struct sip_msg *rq, char *p1, char *p2);
|
|
static int acc_rad_missed0(struct sip_msg *rq, char *p1, char *p2);
|
|
static int acc_rad_request1(struct sip_msg *rq, char *p1, char *p2);
|
|
static int acc_rad_missed1(struct sip_msg *rq, char *p1, char *p2);
|
|
|
|
static cmd_export_t cmds[] = {
|
|
{"acc_rad_log", acc_rad_request0, 0, 0, REQUEST_ROUTE | FAILURE_ROUTE},
|
|
{"acc_rad_missed", acc_rad_missed0, 0, 0, REQUEST_ROUTE | FAILURE_ROUTE},
|
|
{"acc_rad_log", acc_rad_request1, 1, fixup_var_int_1, REQUEST_ROUTE | FAILURE_ROUTE},
|
|
{"acc_rad_missed", acc_rad_missed1, 1, fixup_var_int_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_fmt", PARAM_STRING, &log_fmt },
|
|
{"attrs", PARAM_STRING, &attrs_param },
|
|
{"radius_config", PARAM_STRING, &radius_config },
|
|
{"service_type", PARAM_INT, &service_type },
|
|
{"swap_direction", PARAM_INT, &swap_dir },
|
|
{0, 0, 0}
|
|
};
|
|
|
|
|
|
struct module_exports exports= {
|
|
"acc_radius",
|
|
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_radius", "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_radius", "log_missed_flag", &log_missed_flag);
|
|
}
|
|
|
|
|
|
|
|
static inline int skip_cancel(struct sip_msg *msg)
|
|
{
|
|
return (msg->REQ_METHOD == METHOD_CANCEL) && report_cancels == 0;
|
|
}
|
|
|
|
static int check_ftag(struct sip_msg* msg, str* uri)
|
|
{
|
|
param_hooks_t hooks;
|
|
param_t* params;
|
|
char* semi;
|
|
struct to_body* from;
|
|
str t;
|
|
|
|
t = *uri;
|
|
params = 0;
|
|
semi = q_memchr(t.s, ';', t.len);
|
|
if (!semi) {
|
|
DBG("No ftag parameter found\n");
|
|
return -1;
|
|
}
|
|
|
|
t.len -= semi - uri->s + 1;
|
|
t.s = semi + 1;
|
|
trim_leading(&t);
|
|
|
|
if (parse_params(&t, CLASS_URI, &hooks, ¶ms) < 0) {
|
|
ERR("Error while parsing parameters\n");
|
|
return -1;
|
|
}
|
|
|
|
if (!hooks.uri.ftag) {
|
|
DBG("No ftag parameter found\n");
|
|
goto err;
|
|
}
|
|
|
|
from = get_from(msg);
|
|
|
|
if (!from || !from->tag_value.len || !from->tag_value.s) {
|
|
DBG("No from tag parameter found\n");
|
|
goto err;
|
|
}
|
|
|
|
if (from->tag_value.len == hooks.uri.ftag->body.len &&
|
|
!strncmp(from->tag_value.s, hooks.uri.ftag->body.s, hooks.uri.ftag->body.len)) {
|
|
DBG("Route ftag and From tag are same\n");
|
|
free_params(params);
|
|
return 0;
|
|
} else {
|
|
DBG("Route ftag and From tag are NOT same\n");
|
|
free_params(params);
|
|
return 1;
|
|
}
|
|
|
|
err:
|
|
if (params) free_params(params);
|
|
return -1;
|
|
}
|
|
|
|
static int get_direction(struct sip_msg* msg)
|
|
{
|
|
int ret;
|
|
if (parse_orig_ruri(msg) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (!msg->parsed_orig_ruri_ok) {
|
|
ERR("Error while parsing original Request-URI\n");
|
|
return -1;
|
|
}
|
|
|
|
ret = check_self(&msg->parsed_orig_ruri.host,
|
|
msg->parsed_orig_ruri.port_no ? msg->parsed_orig_ruri.port_no : SIP_PORT, 0);/* match all protos*/
|
|
if (ret < 0) return -1;
|
|
if (ret > 0) {
|
|
/* Route is in ruri */
|
|
return check_ftag(msg, &msg->first_line.u.request.uri);
|
|
} else {
|
|
if (msg->route) {
|
|
if (parse_rr(msg->route) < 0) {
|
|
ERR("Error while parsing Route HF\n");
|
|
return -1;
|
|
}
|
|
ret = check_ftag(msg, &((rr_t*)msg->route->parsed)->nameaddr.uri);
|
|
if (msg->route->parsed) free_rr((rr_t**)(void*)&msg->route->parsed);
|
|
return ret;
|
|
} else {
|
|
DBG("No Route headers found\n");
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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 | HDR_ROUTE_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 fmt2rad(char *fmt,
|
|
struct sip_msg *rq,
|
|
str* ouri,
|
|
struct hdr_field *to,
|
|
unsigned int code,
|
|
VALUE_PAIR** send,
|
|
time_t req_time) /* Timestamp of the request */
|
|
{
|
|
static unsigned int cseq_num, src_port, src_ip;
|
|
static time_t rq_time, rs_time;
|
|
int cnt;
|
|
struct to_body* from, *pto;
|
|
str val, *cr, *at;
|
|
struct cseq_body *cseq;
|
|
struct attr* attr;
|
|
int dir;
|
|
|
|
cnt = 0;
|
|
dir = -2;
|
|
|
|
/* 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.
|
|
*/
|
|
while(*fmt) {
|
|
if (cnt == ALL_LOG_FMT_LEN) {
|
|
LOG(L_ERR, "ERROR:acc:fmt2rad: Formatting string is too long\n");
|
|
return 0;
|
|
}
|
|
|
|
attr = 0;
|
|
switch(*fmt) {
|
|
case 'a': /* attr */
|
|
at = print_attrs(avps, avps_n, 0);
|
|
if (at) {
|
|
attr = &attrs[A_SER_ATTR];
|
|
val = *at;
|
|
}
|
|
break;
|
|
|
|
case 'c': /* sip_callid */
|
|
if (rq->callid && rq->callid->body.len) {
|
|
attr = &attrs[A_ACCT_SESSION_ID];
|
|
val = rq->callid->body;
|
|
}
|
|
break;
|
|
|
|
case 'd': /* to_tag */
|
|
if (swap_dir && dir == -2) dir = get_direction(rq);
|
|
if (dir <= 0) {
|
|
if (to && (pto = (struct to_body*)(to->parsed)) && pto->tag_value.len) {
|
|
attr = &attrs[A_SIP_TO_TAG];
|
|
val = pto->tag_value;
|
|
}
|
|
} else {
|
|
if (rq->from && (from = get_from(rq)) && from->tag_value.len) {
|
|
attr = &attrs[A_SIP_TO_TAG];
|
|
val = from->tag_value;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'f': /* sip_from */
|
|
if (rq->from && rq->from->body.len) {
|
|
attr = &attrs[A_SER_FROM];
|
|
val = rq->from->body;
|
|
}
|
|
break;
|
|
|
|
case 'g': /* flags */
|
|
attr = &attrs[A_SER_FLAGS];
|
|
val.s = (char*)&rq->flags;
|
|
val.len = sizeof(unsigned int);
|
|
break;
|
|
|
|
case 'i': /* inbound_ruri */
|
|
attr = &attrs[A_SER_ORIGINAL_REQUEST_ID];
|
|
val = rq->first_line.u.request.uri;
|
|
break;
|
|
|
|
case 'm': /* sip_method */
|
|
attr = &attrs[A_SIP_METHOD];
|
|
val = rq->first_line.u.request.method;
|
|
break;
|
|
|
|
case 'n': /* sip_cseq */
|
|
if (rq->cseq && (cseq = get_cseq(rq)) && cseq->number.len) {
|
|
attr = &attrs[A_SIP_CSEQ];
|
|
str2int(&cseq->number, &cseq_num);
|
|
val.s = (char*)&cseq_num;
|
|
val.len = sizeof(unsigned int);
|
|
}
|
|
break;
|
|
|
|
case 'o': /* outbound_ruri */
|
|
attr = &attrs[A_SIP_TRANSLATED_REQUEST_ID];
|
|
val = *ouri;
|
|
break;
|
|
|
|
case 'p': /* Source IP address */
|
|
attr = &attrs[A_SIP_SOURCE_IP_ADDRESS];
|
|
src_ip = ntohl(rq->rcv.src_ip.u.addr32[0]);
|
|
val.s = (char*)&src_ip;
|
|
val.len = sizeof(src_ip);
|
|
break;
|
|
|
|
case 'r': /* from_tag */
|
|
if (swap_dir && dir == -2) dir = get_direction(rq);
|
|
if (dir <= 0) {
|
|
if (rq->from && (from = get_from(rq)) && from->tag_value.len) {
|
|
attr = &attrs[A_SIP_FROM_TAG];
|
|
val = from->tag_value;
|
|
}
|
|
} else {
|
|
if (to && (pto = (struct to_body*)(to->parsed)) && pto->tag_value.len) {
|
|
attr = &attrs[A_SIP_FROM_TAG];
|
|
val = pto->tag_value;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 's': /* server_id */
|
|
attr = &attrs[A_SER_SERVER_ID];
|
|
val.s = (char*)&server_id;
|
|
val.len = sizeof(int);
|
|
break;
|
|
|
|
case 't': /* sip_to */
|
|
if (to && to->body.len) {
|
|
attr = &attrs[A_SER_TO];
|
|
val = to->body;
|
|
}
|
|
break;
|
|
|
|
case 'u': /* digest_username */
|
|
cr = cred_user(rq);
|
|
if (cr) {
|
|
attr = &attrs[A_SER_DIGEST_USERNAME];
|
|
val = *cr;
|
|
}
|
|
break;
|
|
|
|
case 'x': /* request_timestamp */
|
|
attr = &attrs[A_SER_REQUEST_TIMESTAMP];
|
|
rq_time = req_time;
|
|
val.s = (char*)&rq_time;
|
|
val.len = sizeof(time_t);
|
|
break;
|
|
|
|
case 'D': /* to_did */
|
|
break;
|
|
|
|
case 'F': /* from_uri */
|
|
if (swap_dir && dir == -2) dir = get_direction(rq);
|
|
if (dir <= 0) {
|
|
if (rq->from && (from = get_from(rq)) && from->uri.len) {
|
|
attr = &attrs[A_CALLING_STATION_ID];
|
|
val = from->uri;
|
|
}
|
|
} else {
|
|
if (rq->to && (pto = get_to(rq)) && pto->uri.len) {
|
|
attr = &attrs[A_CALLING_STATION_ID];
|
|
val = pto->uri;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'I': /* from_uid */
|
|
if (get_from_uid(&val, rq) < 0) {
|
|
attr = &attrs[A_SER_FROM_UID];
|
|
}
|
|
break;
|
|
|
|
case 'M': /* from_did */
|
|
break;
|
|
|
|
case 'P': /* Source port */
|
|
attr = &attrs[A_SIP_SOURCE_PORT];
|
|
src_port = rq->rcv.src_port;
|
|
val.s = (char*)&src_port;
|
|
val.len = sizeof(unsigned int);
|
|
break;
|
|
|
|
case 'R': /* digest_realm */
|
|
cr = cred_realm(rq);
|
|
if (cr) {
|
|
attr = &attrs[A_SER_DIGEST_REALM];
|
|
val = *cr;
|
|
}
|
|
break;
|
|
|
|
case 'S': /* sip_status */
|
|
attr = &attrs[A_SIP_RESPONSE_CODE];
|
|
val.s = (char*)&code;
|
|
val.len = sizeof(unsigned int);
|
|
break;
|
|
|
|
case 'T': /* to_uri */
|
|
if (swap_dir && dir == -2) dir = get_direction(rq);
|
|
if (dir <= 0) {
|
|
if (rq->to && (pto = get_to(rq)) && pto->uri.len) {
|
|
attr = &attrs[A_CALLED_STATION_ID];
|
|
val = pto->uri;
|
|
}
|
|
} else {
|
|
if (rq->from && (from = get_from(rq)) && from->uri.len) {
|
|
attr = &attrs[A_CALLED_STATION_ID];
|
|
val = from->uri;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'U': /* to_uid */
|
|
if (get_from_uid(&val, rq) < 0) {
|
|
attr = &attrs[A_SER_TO_UID];
|
|
}
|
|
break;
|
|
|
|
case 'X': /* response_timestamp */
|
|
attr = &attrs[A_SER_RESPONSE_TIMESTAMP];
|
|
rs_time = time(0);
|
|
val.s = (char*)&rs_time;
|
|
val.len = sizeof(time_t);
|
|
break;
|
|
|
|
default:
|
|
LOG(L_CRIT, "BUG:acc:fmt2rad: unknown char: %c\n", *fmt);
|
|
return -1;
|
|
} /* switch (*fmt) */
|
|
|
|
if (attr) {
|
|
if (!rc_avpair_add(rh, send, ATTRID(attr->v), val.s, val.len, VENDOR(attr->v))) {
|
|
LOG(L_ERR, "ERROR:acc:fmt2rad: Failed to add attribute %s\n",
|
|
attr->n);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
fmt++;
|
|
cnt++;
|
|
} /* while (*fmt) */
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Return the value of Acc-Status-Type attribute based on SIP method
|
|
* and response code
|
|
*/
|
|
static inline UINT4 rad_status(struct sip_msg *rq, unsigned int code)
|
|
{
|
|
/* Faked reply */
|
|
if (code == 0) {
|
|
return vals[V_FAILED].v;
|
|
}
|
|
|
|
/* Successful call start */
|
|
if ((rq->REQ_METHOD == METHOD_INVITE || rq->REQ_METHOD == METHOD_ACK)
|
|
&& code >= 200 && code < 300) {
|
|
return vals[V_START].v;
|
|
}
|
|
|
|
/* Successful call termination */
|
|
if ((rq->REQ_METHOD == METHOD_BYE || rq->REQ_METHOD == METHOD_CANCEL)) {
|
|
return vals[V_STOP].v;
|
|
}
|
|
|
|
/* Successful transaction */
|
|
if (code >= 200 && code < 300) {
|
|
return vals[V_INTERIM_UPDATE].v;
|
|
}
|
|
|
|
/* Otherwise it failed */
|
|
return vals[V_FAILED].v;
|
|
}
|
|
|
|
|
|
/*
|
|
* Add User-Name attribute
|
|
*/
|
|
static inline int add_user_name(struct sip_msg* rq, void* rh, VALUE_PAIR** send)
|
|
{
|
|
struct sip_uri puri;
|
|
str* user, *realm;
|
|
str user_name;
|
|
struct to_body* from;
|
|
|
|
user = cred_user(rq); /* try to take it from credentials */
|
|
realm = cred_realm(rq);
|
|
|
|
if (!user || !realm) {
|
|
if (rq->from && (from = get_from(rq)) && from->uri.len) {
|
|
if (parse_uri(from->uri.s, from->uri.len, &puri) < 0 ) {
|
|
LOG(L_ERR, "ERROR:acc:add_user_name: Bad From URI\n");
|
|
return -1;
|
|
}
|
|
|
|
user = &puri.user;
|
|
realm = &puri.host;
|
|
} else {
|
|
DBG("acc:add_user_name: Neither digest nor From found, mandatory attribute User-Name not added\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
user_name.len = user->len + 1 + realm->len;
|
|
user_name.s = pkg_malloc(user_name.len);
|
|
if (!user_name.s) {
|
|
LOG(L_ERR, "ERROR:acc:add_user_name: no memory\n");
|
|
return -1;
|
|
}
|
|
memcpy(user_name.s, user->s, user->len);
|
|
user_name.s[user->len] = '@';
|
|
memcpy(user_name.s + user->len + 1, realm->s, realm->len);
|
|
|
|
if (!rc_avpair_add(rh, send, ATTRID(attrs[A_USER_NAME].v),
|
|
user_name.s, user_name.len, VENDOR(attrs[A_USER_NAME].v))) {
|
|
LOG(L_ERR, "ERROR:acc:add_user_name: Failed to add User-Name attribute\n");
|
|
pkg_free(user_name.s);
|
|
return -1;
|
|
}
|
|
pkg_free(user_name.s);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* skip leading text and begin with first item's
|
|
* separator ", " which will be overwritten by the
|
|
* leading text later
|
|
*
|
|
*/
|
|
static int log_request(struct sip_msg* rq, str* ouri, struct hdr_field* to, unsigned int code, time_t req_time)
|
|
{
|
|
VALUE_PAIR *send;
|
|
UINT4 av_type;
|
|
|
|
send = NULL;
|
|
if (skip_cancel(rq)) return 1;
|
|
|
|
if (fmt2rad(log_fmt, rq, ouri, to, code, &send, req_time) < 0) goto error;
|
|
|
|
/* Add Acct-Status-Type attribute */
|
|
av_type = rad_status(rq, code);
|
|
if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_ACCT_STATUS_TYPE].v), &av_type, -1,
|
|
VENDOR(attrs[A_ACCT_STATUS_TYPE].v))) {
|
|
ERR("Add Status-Type\n");
|
|
goto error;
|
|
}
|
|
|
|
/* Add Service-Type attribute */
|
|
av_type = (service_type != -1) ? service_type : vals[V_SIP_SESSION].v;
|
|
if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_SERVICE_TYPE].v), &av_type, -1,
|
|
VENDOR(attrs[A_SERVICE_TYPE].v))) {
|
|
ERR("add STATUS_TYPE\n");
|
|
goto error;
|
|
}
|
|
|
|
/* Add User-Name attribute */
|
|
if (add_user_name(rq, rh, &send) < 0) goto error;
|
|
|
|
/* Send the request out */
|
|
if (rc_acct(rh, SIP_PORT, send) != OK_RC) {
|
|
ERR("RADIUS accounting request failed\n");
|
|
goto error;
|
|
}
|
|
|
|
rc_avpair_free(send);
|
|
return 1;
|
|
|
|
error:
|
|
rc_avpair_free(send);
|
|
return -1;
|
|
}
|
|
|
|
|
|
static void log_reply(struct cell* t , struct sip_msg* reply, unsigned int code, time_t req_time)
|
|
{
|
|
str* ouri;
|
|
|
|
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), code, 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;
|
|
|
|
rq = t->uas.request;
|
|
if (ack->to) to = ack->to;
|
|
else to = rq->to;
|
|
log_request(ack, GET_RURI(ack), to, t->uas.status, req_time);
|
|
}
|
|
|
|
|
|
static void log_missed(struct cell* t, struct sip_msg* reply, unsigned int code, time_t req_time)
|
|
{
|
|
str* ouri;
|
|
|
|
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), code, req_time);
|
|
}
|
|
|
|
|
|
/* 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_rad_request0(struct sip_msg *rq, char* p1, char* p2)
|
|
{
|
|
preparse_req(rq);
|
|
return log_request(rq, GET_RURI(rq), rq->to, 0, 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_rad_missed0(struct sip_msg *rq, char* p1, char* p2)
|
|
{
|
|
preparse_req(rq);
|
|
return log_request(rq, GET_RURI(rq), rq->to, 0, 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_rad_request1(struct sip_msg *rq, char* p1, char* p2)
|
|
{
|
|
int code;
|
|
preparse_req(rq);
|
|
if (get_int_fparam(&code, rq, (fparam_t*)p1) < 0) {
|
|
code = 0;
|
|
}
|
|
return log_request(rq, GET_RURI(rq), rq->to, code, 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_rad_missed1(struct sip_msg *rq, char* p1, char* p2)
|
|
{
|
|
int code;
|
|
preparse_req(rq);
|
|
if (get_int_fparam(&code, rq, (fparam_t*)p1) < 0) {
|
|
code = 0;
|
|
}
|
|
return log_request(rq, GET_RURI(rq), rq->to, code, 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) <= 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) <= 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) <= 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) <= 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)
|
|
{
|
|
DICT_VENDOR *vend;
|
|
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) <= 0) {
|
|
LOG(L_ERR,"ERROR:acc:mod_init: cannot register TMCB_REQUEST_IN "
|
|
"callback\n");
|
|
return -1;
|
|
}
|
|
|
|
memset(attrs, 0, sizeof(attrs));
|
|
memset(vals, 0, sizeof(vals));
|
|
|
|
attrs[A_USER_NAME].n = "User-Name";
|
|
attrs[A_SERVICE_TYPE].n = "Service-Type";
|
|
attrs[A_CALLED_STATION_ID].n = "Called-Station-Id";
|
|
attrs[A_CALLING_STATION_ID].n = "Calling-Station-Id";
|
|
attrs[A_ACCT_STATUS_TYPE].n = "Acct-Status-Type";
|
|
attrs[A_ACCT_SESSION_ID].n = "Acct-Session-Id";
|
|
|
|
attrs[A_SIP_METHOD].n = "Sip-Method";
|
|
attrs[A_SIP_RESPONSE_CODE].n = "Sip-Response-Code";
|
|
attrs[A_SIP_CSEQ].n = "Sip-CSeq";
|
|
attrs[A_SIP_TO_TAG].n = "Sip-To-Tag";
|
|
attrs[A_SIP_FROM_TAG].n = "Sip-From-Tag";
|
|
attrs[A_SIP_TRANSLATED_REQUEST_ID].n = "Sip-Translated-Request-Id";
|
|
attrs[A_SIP_SOURCE_IP_ADDRESS].n = "Sip-Source-IP-Address";
|
|
attrs[A_SIP_SOURCE_PORT].n = "Sip-Source-Port";
|
|
|
|
attrs[A_SER_ATTR].n = "SER-Attr";
|
|
attrs[A_SER_FROM].n = "SER-From";
|
|
attrs[A_SER_FLAGS].n = "SER-Flags";
|
|
attrs[A_SER_ORIGINAL_REQUEST_ID].n = "SER-Original-Request-Id";
|
|
attrs[A_SER_TO].n = "SER-To";
|
|
attrs[A_SER_DIGEST_USERNAME].n = "SER-Digest-Username";
|
|
attrs[A_SER_DIGEST_REALM].n = "SER-Digest-Realm";
|
|
attrs[A_SER_REQUEST_TIMESTAMP].n = "SER-Request-Timestamp";
|
|
attrs[A_SER_TO_DID].n = "SER-To-DID";
|
|
attrs[A_SER_FROM_UID].n = "SER-From-UID";
|
|
attrs[A_SER_FROM_DID].n = "SER-From-DID";
|
|
attrs[A_SER_TO_UID].n = "SER-To-UID";
|
|
attrs[A_SER_RESPONSE_TIMESTAMP].n = "SER-Response-Timestamp";
|
|
attrs[A_SER_SERVER_ID].n = "SER-Server-ID";
|
|
|
|
vals[V_START].n = "Start";
|
|
vals[V_STOP].n = "Stop";
|
|
vals[V_INTERIM_UPDATE].n = "Interim-Update";
|
|
vals[V_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:mod_init: 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:mod_init: Error reading radius dictionary\n");
|
|
return -1;
|
|
}
|
|
|
|
vend = rc_dict_findvend(rh, "iptelorg");
|
|
if (vend == NULL) {
|
|
ERR("RADIUS dictionary is missing required vendor 'iptelorg'\n");
|
|
return -1;
|
|
}
|
|
|
|
INIT_AV(rh, attrs, vals, "acc", -1, -1);
|
|
|
|
if (service_type != -1) {
|
|
vals[V_SIP_SESSION].v = service_type;
|
|
}
|
|
|
|
if (parse_attrs(&avps, &avps_n, attrs_param) < 0) {
|
|
ERR("Error while parsing 'attrs' module parameter\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|