/* $Id$ * * Copyright (C) 2005-2008 Dan Pascu * * 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 * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../sr_module.h" #include "../../mem/mem.h" #include "../../dprint.h" #include "../../str.h" #include "../../pvar.h" #include "../../ut.h" #include "../../script_cb.h" #include "../../parser/digest/digest.h" #include "../../parser/parse_from.h" #include "../dialog/dlg_load.h" #include "../dialog/dlg_hash.h" MODULE_VERSION #define FL_USE_CALL_CONTROL (1<<28) // use call control for a dialog #if defined(__GNUC__) && !defined(__STRICT_ANSI__) # define INLINE inline #else # define INLINE #endif #define CANONICAL_URI_AVP_SPEC "$avp(s:can_uri)" #define SIGNALING_IP_AVP_SPEC "$avp(s:signaling_ip)" #define SIP_APPLICATION_AVP_SPEC "$avp(s:sip_application)" // Although `AF_LOCAL' is mandated by POSIX.1g, `AF_UNIX' is portable to // more systems. `AF_UNIX' was the traditional name stemming from BSD, so // even most POSIX systems support it. It is also the name of choice in // the Unix98 specification. So if there's no AF_LOCAL fallback to AF_UNIX #ifndef AF_LOCAL # define AF_LOCAL AF_UNIX #endif // Solaris does not have the MSG_NOSIGNAL flag for the send(2) syscall #ifndef MSG_NOSIGNAL # define MSG_NOSIGNAL 0 #endif typedef int Bool; #define True 1 #define False 0 typedef struct AVP_Param { str spec; int_str name; unsigned short type; } AVP_Param; typedef struct AVP_List { pv_spec_p pv; str name; struct AVP_List *next; } AVP_List; #define RETRY_INTERVAL 10 #define BUFFER_SIZE 8192 typedef struct CallControlSocket { char *name; // name int sock; // socket int timeout; // how many miliseconds to wait for an answer time_t last_failure; // time of the last failure char data[BUFFER_SIZE]; // buffer for the answer data } CallControlSocket; /* Function prototypes */ static int CallControl(struct sip_msg *msg, char *str1, char *str2); static int mod_init(void); static int child_init(int rank); static void destroy(void); static int postprocess_request(struct sip_msg *, unsigned int, void *); int parse_param_init(unsigned int type, void *val); int parse_param_start(unsigned int type, void *val); int parse_param_stop(unsigned int type, void *val); /* Local global variables */ static CallControlSocket callcontrol_socket = { "/var/run/callcontrol/socket", // name -1, // sock 500, // timeout in 500 miliseconds if there is no answer 0, // time of the last failure "" // data }; static int disable = False; static int diverter_avp_id = 805; /* The AVP where the canonical URI is stored (if defined) */ static AVP_Param canonical_uri_avp = {str_init(CANONICAL_URI_AVP_SPEC), {0}, 0}; /* The AVP where the caller signaling IP is stored (if defined) */ static AVP_Param signaling_ip_avp = {str_init(SIGNALING_IP_AVP_SPEC), {0}, 0}; /* The AVP where the SIP application type is stored (if defined) */ static AVP_Param sip_application_avp = {str_init(SIP_APPLICATION_AVP_SPEC), {0}, 0}; struct dlg_binds dlg_api; static int dialog_flag = -1; AVP_List *cc_init_avps = NULL, *cc_start_avps = NULL, *cc_stop_avps = NULL; pv_elem_t *model; static cmd_export_t commands[] = { {"call_control", (cmd_function)CallControl, 0, 0, 0, REQUEST_ROUTE }, {0, 0, 0, 0, 0, 0} }; static param_export_t parameters[] = { {"init", PARAM_STRING | USE_FUNC_PARAM, parse_param_init}, {"start", PARAM_STRING | USE_FUNC_PARAM, parse_param_start}, {"stop", PARAM_STRING | USE_FUNC_PARAM, parse_param_stop}, {"disable", INT_PARAM, &disable}, {"socket_name", PARAM_STRING, &(callcontrol_socket.name)}, {"socket_timeout", INT_PARAM, &(callcontrol_socket.timeout)}, {"diverter_avp_id", INT_PARAM, &diverter_avp_id}, {"canonical_uri_avp", PARAM_STR, &(canonical_uri_avp.spec)}, {"signaling_ip_avp", PARAM_STR, &(signaling_ip_avp.spec)}, {"sip_application_avp", PARAM_STR, &(sip_application_avp.spec)}, {0, 0, 0} }; struct module_exports exports = { "call_control", // module name DEFAULT_DLFLAGS, // dlopen flags commands, // exported functions parameters, // exported parameters NULL, // exported statistics NULL, // exported MI functions NULL, // exported pseudo-variables NULL, // extra processes mod_init, // module init function (before fork. kids will inherit) NULL, // reply processing function destroy, // destroy function child_init // child init function }; typedef enum CallControlAction { CAInitialize = 1, CAStart, CAStop } CallControlAction; typedef struct Contact { str username; str ip; str port; } Contact; typedef struct DialogID { unsigned int h_entry; unsigned int h_id; } DialogID; typedef struct CallInfo { CallControlAction action; DialogID dialog_id; str ruri; str diverter; str source_ip; str callid; str from; str from_tag; str sip_application; } CallInfo; #define CHECK_COND(cond) \ if ((cond) == 0) { \ LM_ERR("malformed modparam\n"); \ return -1; \ } #define CHECK_ALLOC(p) \ if (!(p)) { \ LM_ERR("no memory left\n"); \ return -1; \ } void destroy_list(AVP_List *list) { AVP_List *cur, *next; cur = list; while (cur) { next = cur->next; pkg_free(cur); cur = next; } } int parse_param(void *val, AVP_List** avps) { char *p; str s, content; AVP_List *mp = NULL; //LM_DBG("%.*s\n", content.len, content.s); content.s = (char*) val; content.len = strlen(content.s); p = (char*) pkg_malloc (content.len + 1); CHECK_ALLOC(p); p[content.len] = '\0'; memcpy(p, content.s, content.len); for (;*p != '\0';) { mp = (AVP_List*) pkg_malloc (sizeof(AVP_List)); CHECK_ALLOC(mp); mp->next = *avps; mp->pv = (pv_spec_p) pkg_malloc (sizeof(pv_spec_t)); CHECK_ALLOC(mp->pv); for (; isspace(*p); p++); CHECK_COND(*p != '\0'); mp->name.s = p; for(; isgraph(*p) && *p != '='; p++) CHECK_COND(*p != '\0'); mp->name.len = p - mp->name.s; for (; isspace(*p); p++); CHECK_COND(*p != '\0' && *p == '='); p++; //LM_DBG("%.*s\n", mp->name.len, mp->name.s); for (; isspace(*p); p++); CHECK_COND(*p != '\0' && *p == '$'); s.s = p; s.len = strlen(p); p = pv_parse_spec(&s, mp->pv); for (; isspace(*p); p++); *avps = mp; } return 0; } int parse_param_init(unsigned int type, void *val) { if (parse_param(val, &cc_init_avps) == -1) return E_CFG; return 0; } int parse_param_start(unsigned int type, void *val) { if (parse_param(val, &cc_start_avps) == -1) return E_CFG; return 0; } int parse_param_stop(unsigned int type, void *val) { if (parse_param(val, &cc_stop_avps) == -1) return E_CFG; return 0; } // Functions dealing with strings // // returns string with whitespace trimmed from left end static inline void ltrim(str *string) { while (string->len>0 && isspace((int)*(string->s))) { string->len--; string->s++; } } // returns string with whitespace trimmed from right end static inline void rtrim(str *string) { char *ptr; ptr = string->s + string->len - 1; while (string->len>0 && (*ptr==0 || isspace((int)*ptr))) { string->len--; ptr--; } } // returns string with whitespace trimmed from both ends static inline void trim(str *string) { ltrim(string); rtrim(string); } // Message checking and parsing // static Bool has_to_tag(struct sip_msg *msg) { str tag; if (!msg->to) { if (parse_headers(msg, HDR_TO_F, 0)==-1) { LOG(L_ERR, "cannot parse 'To' header\n"); return False; } if (!msg->to) { LOG(L_ERR, "missing 'To' header\n"); return False; } } tag = get_to(msg)->tag_value; if (tag.s==NULL || tag.len==0) { return False; } return True; } // Get canonical request URI static str get_canonical_request_uri(struct sip_msg* msg) { int_str value; if (!search_first_avp(canonical_uri_avp.type | AVP_VAL_STR, canonical_uri_avp.name, &value, NULL) || value.s.s==NULL || value.s.len==0) { return *GET_RURI(msg); } return value.s; } // Get caller signaling IP static str get_signaling_ip(struct sip_msg* msg) { int_str value; if (!search_first_avp(signaling_ip_avp.type | AVP_VAL_STR, signaling_ip_avp.name, &value, NULL) || !value.s.s || value.s.len==0) { value.s.s = ip_addr2a(&msg->rcv.src_ip); value.s.len = strlen(value.s.s); } return value.s; } // Get SIP application type static str get_sip_application(struct sip_msg* msg) { int_str value; if (!search_first_avp(sip_application_avp.type | AVP_VAL_STR, sip_application_avp.name, &value, NULL) || !value.s.s || value.s.len==0) { value.s.s = "audio"; value.s.len = strlen(value.s.s); } return value.s; } static str get_diverter(struct sip_msg *msg) { struct hdr_field *header; dig_cred_t *credentials; int_str avpname, avpvalue; static str diverter; diverter.s = "None"; diverter.len = 4; avpname.n = diverter_avp_id; if (search_first_avp(AVP_VAL_STR, avpname, &avpvalue, NULL)) { // have a diverted call diverter = avpvalue.s; } else { get_authorized_cred(msg->proxy_auth, &header); if (header) { credentials = &((auth_body_t*)(header->parsed))->digest; } else { if (parse_headers(msg, HDR_PROXYAUTH_F, 0) == -1) { LOG(L_ERR, "cannot parse Proxy-Authorization header\n"); return diverter; } if (!msg->proxy_auth) return diverter; if (parse_credentials(msg->proxy_auth) != 0) { LOG(L_ERR, "cannot parse credentials\n"); return diverter; } credentials = &((auth_body_t*)(msg->proxy_auth->parsed))->digest; } if (credentials->username.user.len > 0 && credentials->username.domain.len > 0 && credentials->realm.len == 0 && credentials->nonce.len == 0 && credentials->response.len == 0) { // this is a call diverted from the failure route // and sent back to proxy with append_pa_hf() diverter = credentials->username.whole; } } return diverter; } static CallInfo* get_call_info(struct sip_msg *msg, CallControlAction action) { static CallInfo call_info; hdr_flags_t headers; memset(&call_info, 0, sizeof(struct CallInfo)); switch (action) { case CAInitialize: headers = HDR_CALLID_F|HDR_FROM_F; break; case CAStart: case CAStop: headers = HDR_CALLID_F; break; default: // Invalid action. Should never get here. assert(False); return NULL; } if (parse_headers(msg, headers, 0) == -1) { LOG(L_ERR, "cannot parse required headers\n"); return NULL; } if (headers & HDR_CALLID_F) { if (msg->callid == NULL) { LOG(L_ERR, "missing Call-ID header\n"); return NULL; } call_info.callid = msg->callid->body; trim(&call_info.callid); } if (headers & HDR_FROM_F) { struct to_body *from; // yeah. suggestive structure name ;) if (msg->from == NULL) { LOG(L_ERR, "missing From header\n"); return NULL; } if (!msg->from->parsed && parse_from_header(msg)==-1) { LOG(L_ERR, "cannot parse From header\n"); return NULL; } from = get_from(msg); if (from->body.s==NULL || from->body.len==0) { LOG(L_ERR, "missing From\n"); return NULL; } if (from->tag_value.s==NULL || from->tag_value.len==0) { LOG(L_ERR, "missing From tag\n"); return NULL; } call_info.from = from->body; call_info.from_tag = from->tag_value; } if (action == CAInitialize) { call_info.ruri = get_canonical_request_uri(msg); call_info.diverter = get_diverter(msg); call_info.source_ip = get_signaling_ip(msg); call_info.sip_application = get_sip_application(msg); } call_info.action = action; return &call_info; } static char* make_custom_request(struct sip_msg *msg, CallInfo *call) { static char request[8192]; int len = 0; AVP_List *al; pv_value_t pt; switch (call->action) { case CAInitialize: al = cc_init_avps; break; case CAStart: al = cc_start_avps; break; case CAStop: al = cc_stop_avps; break; default: // should never get here, but keep gcc from complaining assert(False); return NULL; } for (; al; al = al->next) { pv_get_spec_value(msg, al->pv, &pt); if (pt.flags & PV_VAL_INT) { len += snprintf(request + len, sizeof(request), "%.*s = %d ", al->name.len, al->name.s, pt.ri); } else if (pt.flags & PV_VAL_STR) { len += snprintf(request + len, sizeof(request), "%.*s = %.*s ", al->name.len, al->name.s, pt.rs.len, pt.rs.s); } if (len >= sizeof(request)) { LM_ERR("callcontrol request is longer than %ld bytes\n", (unsigned long)sizeof(request)); return NULL; } } return request; } static char* make_default_request(CallInfo *call) { static char request[8192]; int len; switch (call->action) { case CAInitialize: len = snprintf(request, sizeof(request), "init\r\n" "ruri: %.*s\r\n" "diverter: %.*s\r\n" "sourceip: %.*s\r\n" "callid: %.*s\r\n" "from: %.*s\r\n" "fromtag: %.*s\r\n" "sip_application: %.*s\r\n" "\r\n", call->ruri.len, call->ruri.s, call->diverter.len, call->diverter.s, call->source_ip.len, call->source_ip.s, call->callid.len, call->callid.s, call->from.len, call->from.s, call->from_tag.len, call->from_tag.s, call->sip_application.len, call->sip_application.s); if (len >= sizeof(request)) { LOG(L_ERR, "callcontrol request is longer than %ld bytes\n", (unsigned long)sizeof(request)); return NULL; } break; case CAStart: len = snprintf(request, sizeof(request), "start\r\n" "callid: %.*s\r\n" "dialogid: %d:%d\r\n" "\r\n", call->callid.len, call->callid.s, call->dialog_id.h_entry, call->dialog_id.h_id); if (len >= sizeof(request)) { LOG(L_ERR, "callcontrol request is longer than %ld bytes\n", (unsigned long)sizeof(request)); return NULL; } break; case CAStop: len = snprintf(request, sizeof(request), "stop\r\n" "callid: %.*s\r\n" "\r\n", call->callid.len, call->callid.s); if (len >= sizeof(request)) { LOG(L_ERR, "callcontrol request is longer than %ld bytes\n", (unsigned long)sizeof(request)); return NULL; } break; default: // should never get here, but keep gcc from complaining assert(False); return NULL; } return request; } // Functions dealing with the external call_control helper // static Bool callcontrol_connect(void) { struct sockaddr_un addr; if (callcontrol_socket.sock >= 0) return True; if (callcontrol_socket.last_failure + RETRY_INTERVAL > time(NULL)) return False; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_LOCAL; strncpy(addr.sun_path, callcontrol_socket.name, sizeof(addr.sun_path) - 1); #ifdef HAVE_SOCKADDR_SA_LEN addr.sun_len = strlen(addr.sun_path); #endif callcontrol_socket.sock = socket(AF_LOCAL, SOCK_STREAM, 0); if (callcontrol_socket.sock < 0) { LOG(L_ERR, "can't create socket\n"); callcontrol_socket.last_failure = time(NULL); return False; } if (connect(callcontrol_socket.sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { LOG(L_ERR, "failed to connect to %s: %s\n", callcontrol_socket.name, strerror(errno)); close(callcontrol_socket.sock); callcontrol_socket.sock = -1; callcontrol_socket.last_failure = time(NULL); return False; } return True; } static void callcontrol_disconnect(void) { if (callcontrol_socket.sock < 0) return; close(callcontrol_socket.sock); callcontrol_socket.sock = -1; callcontrol_socket.last_failure = time(NULL); } static char* send_command(char *command) { int cmd_len, bytes, tries, sent, received, count; struct timeval timeout; fd_set rset; if (!callcontrol_connect()) return NULL; cmd_len = strlen(command); for (sent=0, tries=0; sentdialog_id.h_entry = dlg->h_entry; call->dialog_id.h_id = dlg->h_id; if (!cc_start_avps) message = make_default_request(call); else message = make_custom_request(msg, call); if (!message) return -5; result = send_command(message); if (result==NULL) { return -5; } else if (strcasecmp(result, "Ok\r\n")==0) { return 1; } else if (strcasecmp(result, "Not found\r\n")==0) { return -1; } else { return -5; } } // Called during a dialog ending to stop callcontrol // // Return codes: // 1 - Ok // -1 - Session not found // -5 - Internal error (message parsing, communication, ...) static int call_control_stop(struct sip_msg *msg, str callid) { CallInfo call; char *message, *result; call.action = CAStop; call.callid = callid; if (!cc_stop_avps) message = make_default_request(&call); else message = make_custom_request(msg, &call); if (!message) return -5; result = send_command(message); if (result==NULL) { return -5; } else if (strcasecmp(result, "Ok\r\n")==0) { return 1; } else if (strcasecmp(result, "Not found\r\n")==0) { return -1; } else { return -5; } } // Dialog callbacks and helpers // typedef enum { CCInactive = 0, CCActive } CallControlState; static void __dialog_replies(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params) { struct sip_msg *reply = _params->rpl; if (reply!=FAKED_REPLY && reply->REPLY_STATUS==200) { call_control_start(reply, dlg); } } static void __dialog_ended(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params) { if ((int)(long)*_params->param == CCActive) { struct sip_msg* msg = _params->rpl; if( !msg || msg == FAKED_REPLY) msg = _params->req; call_control_stop(msg, dlg->callid); *_params->param = NULL; } } static void __dialog_created(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params) { struct sip_msg *request = _params->req; if (request->REQ_METHOD != METHOD_INVITE) return; if ((request->msg_flags & FL_USE_CALL_CONTROL) == 0) return; if (dlg_api.register_dlgcb(dlg, DLGCB_RESPONSE_FWDED, __dialog_replies, NULL, NULL) != 0) LOG(L_ERR, "cannot register callback for dialog confirmation\n"); if (dlg_api.register_dlgcb(dlg, DLGCB_TERMINATED | DLGCB_FAILED | DLGCB_EXPIRED | DLGCB_DESTROY, __dialog_ended, (void*)CCActive, NULL) != 0) LOG(L_ERR, "cannot register callback for dialog termination\n"); // reset the flag to indicate that the dialog for callcontrol was created request->msg_flags &= ~FL_USE_CALL_CONTROL; } static void __dialog_loaded(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params) { if (dlg_api.register_dlgcb(dlg, DLGCB_RESPONSE_FWDED, __dialog_replies, NULL, NULL) != 0) LOG(L_ERR, "cannot register callback for dialog confirmation\n"); if (dlg_api.register_dlgcb(dlg, DLGCB_TERMINATED | DLGCB_FAILED | DLGCB_EXPIRED | DLGCB_DESTROY, __dialog_ended, (void*)CCActive, NULL) != 0) LOG(L_ERR, "cannot register callback for dialog termination\n"); } // Public API // // Return codes: // 2 - No limit // 1 - Limited // -1 - No credit // -2 - Locked // -3 - No provider // -5 - Internal error (message parsing, communication, ...) static int CallControl(struct sip_msg *msg, char *str1, char *str2) { int result; if (disable) return 2; if (msg->first_line.type!=SIP_REQUEST || msg->REQ_METHOD!=METHOD_INVITE || has_to_tag(msg)) { LOG(L_WARN, "call_control should only be called for the first INVITE\n"); return -5; } result = call_control_initialize(msg); if (result == 1) { // A call with a time limit that will be traced by callcontrol msg->msg_flags |= FL_USE_CALL_CONTROL; setflag(msg, dialog_flag); // have the dialog module trace this dialog } return result; } // Module management: initialization/destroy/function-parameter-fixing/... // static int mod_init(void) { pv_spec_t avp_spec; int *param; modparam_t type; // initialize the canonical_uri_avp structure if (canonical_uri_avp.spec.s==NULL || canonical_uri_avp.spec.len<=0) { LOG(L_ERR, "missing/empty canonical_uri_avp parameter. using default.\n"); canonical_uri_avp.spec.s = CANONICAL_URI_AVP_SPEC; } if (pv_parse_spec(&(canonical_uri_avp.spec), &avp_spec)==0 || avp_spec.type!=PVT_AVP) { LOG(L_CRIT, "invalid AVP specification for canonical_uri_avp: `%s'\n", canonical_uri_avp.spec.s); return -1; } if (pv_get_avp_name(0, &(avp_spec.pvp), &(canonical_uri_avp.name), &(canonical_uri_avp.type))!=0) { LOG(L_CRIT, "invalid AVP specification for canonical_uri_avp: `%s'\n", canonical_uri_avp.spec.s); return -1; } // initialize the signaling_ip_avp structure if (signaling_ip_avp.spec.s==NULL || signaling_ip_avp.spec.len<=0) { LOG(L_ERR, "missing/empty signaling_ip_avp parameter. using default.\n"); signaling_ip_avp.spec.s = SIGNALING_IP_AVP_SPEC; } if (pv_parse_spec(&(signaling_ip_avp.spec), &avp_spec)==0 || avp_spec.type!=PVT_AVP) { LOG(L_CRIT, "invalid AVP specification for signaling_ip_avp: `%s'\n", signaling_ip_avp.spec.s); return -1; } if (pv_get_avp_name(0, &(avp_spec.pvp), &(signaling_ip_avp.name), &(signaling_ip_avp.type))!=0) { LOG(L_CRIT, "invalid AVP specification for signaling_ip_avp: `%s'\n", signaling_ip_avp.spec.s); return -1; } // initialize the sip_application_avp structure if (sip_application_avp.spec.s==NULL || sip_application_avp.spec.len<=0) { LOG(L_ERR, "missing/empty sip_application_avp parameter. using default.\n"); sip_application_avp.spec.s = SIP_APPLICATION_AVP_SPEC; } if (pv_parse_spec(&(sip_application_avp.spec), &avp_spec)==0 || avp_spec.type!=PVT_AVP) { LOG(L_CRIT, "invalid AVP specification for sip_application_avp: `%s'\n", sip_application_avp.spec.s); return -1; } if (pv_get_avp_name(0, &(avp_spec.pvp), &(sip_application_avp.name), &(sip_application_avp.type))!=0) { LOG(L_CRIT, "invalid AVP specification for sip_application_avp: `%s'\n", sip_application_avp.spec.s); return -1; } // bind to the dialog API if (load_dlg_api(&dlg_api)!=0) { LOG(L_CRIT, "cannot load the dialog module API\n"); return -1; } // load dlg_flag and default_timeout parameters from the dialog module param = find_param_export(find_module_by_name("dialog"), "dlg_flag", INT_PARAM, &type); if (!param) { LOG(L_CRIT, "cannot find dlg_flag parameter in the dialog module\n"); return -1; } if (type != INT_PARAM) { LOG(L_CRIT, "dlg_flag parameter found but with wrong type: %d\n", type); return -1; } dialog_flag = *param; // register dialog creation callback if (dlg_api.register_dlgcb(NULL, DLGCB_CREATED, __dialog_created, NULL, NULL) != 0) { LOG(L_CRIT, "cannot register callback for dialog creation\n"); return -1; } // register dialog loading callback if (dlg_api.register_dlgcb(NULL, DLGCB_LOADED, __dialog_loaded, NULL, NULL) != 0) { LOG(L_ERR, "cannot register callback for dialogs loaded from the database\n"); } // register a pre-script callback to automatically enable dialog tracing if (register_script_cb(postprocess_request, POST_SCRIPT_CB|REQUEST_CB, 0) != 0) { LOG(L_CRIT, "ERROR:call_control:mod_init: could not register request postprocessing callback\n"); return -1; } return 0; } static int child_init(int rank) { // initialize the connection to callcontrol if needed if (!disable) callcontrol_connect(); return 0; } static void destroy(void) { if (cc_init_avps) destroy_list(cc_init_avps); if (cc_start_avps) destroy_list(cc_start_avps); if (cc_stop_avps) destroy_list(cc_stop_avps); } // Postprocess a request after the main script route is done. // // After all script processing is done, check if the dialog was actually // created to take care of call control. If the FL_USE_CALL_CONTROL flag // is still set, then the dialog creation callback was not called which // means that there was a failure relaying the message and we have to // tell the call control application to discard the call, otherwise it // would remain dangling until it expires. // static int postprocess_request(struct sip_msg *msg, unsigned int flags, void *_param) { CallInfo *call; if ((msg->msg_flags & FL_USE_CALL_CONTROL) == 0) return 1; // the FL_USE_CALL_CONTROL flag is still set => the dialog was not created LOG(L_WARN, "dialog to trace controlled call was not created. discarding callcontrol."); call = get_call_info(msg, CAStop); if (!call) { LOG(L_ERR, "can't retrieve call info\n"); return -1; } call_control_stop(msg, call->callid); return 1; }