/* * Copyright (C) 2009 Daniel-Constantin Mierla (asipto.com) * * 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 * \brief Call transfer * \ingroup dialog * Module: \ref dialog */ #include #include #include #include "../../dprint.h" #include "../../ut.h" #include "../../trim.h" #include "../../mem/mem.h" #include "../../mem/shm_mem.h" #include "../../parser/parse_from.h" #include "../../parser/msg_parser.h" #include "../../modules/tm/tm_load.h" #include "dlg_req_within.h" #include "dlg_handlers.h" #include "dlg_transfer.h" #define DLG_HOLD_SDP "v=0\r\no=kamailio-bridge 0 0 IN IP4 0.0.0.0\r\ns=kamailio\r\nc=IN IP4 0.0.0.0\r\nt=0 0\r\nm=audio 9 RTP/AVP 8 0\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:0 PCMU/8000\r\n" #define DLG_HOLD_SDP_LEN (sizeof(DLG_HOLD_SDP)-1) /* #define DLG_HOLD_CT_HDR "Contact: \r\nContent-Type: application/sdp\r\n" #define DLG_HOLD_CT_HDR_LEN (sizeof(DLG_HOLD_CT_HDR)-1) */ extern str dlg_bridge_controller; extern str dlg_bridge_contact; static char *dlg_bridge_hdrs_buf = NULL; static str dlg_bridge_inv_hdrs = {0}; static str dlg_bridge_ref_hdrs = {0}; int dlg_bridge_init_hdrs(void) { if(dlg_bridge_hdrs_buf!=NULL) return 0; dlg_bridge_hdrs_buf = (char*)pkg_malloc((dlg_bridge_contact.len + 46) * sizeof(char)); if(dlg_bridge_hdrs_buf==NULL) { LM_ERR("no more pkg memory\n"); return -1; } strncpy(dlg_bridge_hdrs_buf, "Contact: <", 10); strncpy(dlg_bridge_hdrs_buf + 10, dlg_bridge_contact.s, dlg_bridge_contact.len); strncpy(dlg_bridge_hdrs_buf + 10 + dlg_bridge_contact.len, ">\r\nContent-Type: application/sdp\r\n", 34); dlg_bridge_hdrs_buf[dlg_bridge_contact.len+44] = '\0'; dlg_bridge_inv_hdrs.s = dlg_bridge_hdrs_buf; dlg_bridge_inv_hdrs.len = dlg_bridge_contact.len + 44; dlg_bridge_ref_hdrs.s = dlg_bridge_hdrs_buf; dlg_bridge_ref_hdrs.len = dlg_bridge_contact.len + 13; return 0; } void dlg_bridge_destroy_hdrs(void) { if(dlg_bridge_hdrs_buf!=NULL) pkg_free(dlg_bridge_hdrs_buf); } void dlg_transfer_ctx_free(dlg_transfer_ctx_t *dtc) { struct dlg_cell *dlg; if(dtc==NULL) return; if(dtc->from.s!=NULL) shm_free(dtc->from.s); if(dtc->to.s!=NULL) shm_free(dtc->to.s); dlg = dtc->dlg; if(dlg!=NULL) { if (dlg->tag[DLG_CALLER_LEG].s) shm_free(dlg->tag[DLG_CALLER_LEG].s); if (dlg->tag[DLG_CALLEE_LEG].s) shm_free(dlg->tag[DLG_CALLEE_LEG].s); if (dlg->cseq[DLG_CALLER_LEG].s) shm_free(dlg->cseq[DLG_CALLER_LEG].s); if (dlg->cseq[DLG_CALLEE_LEG].s) shm_free(dlg->cseq[DLG_CALLEE_LEG].s); shm_free(dlg); } shm_free(dtc); } void dlg_refer_tm_callback(struct cell *t, int type, struct tmcb_params *ps) { dlg_transfer_ctx_t *dtc = NULL; dlg_t* dialog_info = NULL; str met = {"BYE", 3}; int result; struct dlg_cell *dlg; uac_req_t uac_r; if(ps->param==NULL || *ps->param==0) { LM_DBG("message id not received\n"); return; } dtc = *((dlg_transfer_ctx_t**)ps->param); if(dtc==NULL) return; LM_DBG("REFER completed with status %d\n", ps->code); /* we send the BYE anyhow */ dlg = dtc->dlg; if ((dialog_info = build_dlg_t(dlg, DLG_CALLEE_LEG)) == 0){ LM_ERR("failed to create dlg_t\n"); goto error; } /* after REFER, the CSeq must be increased */ dialog_info->loc_seq.value++; set_uac_req(&uac_r, &met, NULL, NULL, dialog_info, 0, NULL, NULL); result = d_tmb.t_request_within(&uac_r); if(result < 0) { LM_ERR("failed to send the REFER request\n"); /* todo: clean-up dtc */ goto error; } free_tm_dlg(dialog_info); dlg_transfer_ctx_free(dtc); LM_DBG("BYE sent\n"); return; error: dlg_transfer_ctx_free(dtc); if(dialog_info) free_tm_dlg(dialog_info); return; } static int dlg_refer_callee(dlg_transfer_ctx_t *dtc) { /*verify direction*/ dlg_t* dialog_info = NULL; str met = {"REFER", 5}; int result; str hdrs; struct dlg_cell *dlg; uac_req_t uac_r; dlg = dtc->dlg; if ((dialog_info = build_dlg_t(dlg, DLG_CALLEE_LEG)) == 0){ LM_ERR("failed to create dlg_t\n"); goto error; } hdrs.len = 23 + 2*CRLF_LEN + dlg_bridge_controller.len + dtc->to.len + dlg_bridge_ref_hdrs.len; LM_DBG("sending REFER [%d] <%.*s>\n", hdrs.len, dtc->to.len, dtc->to.s); hdrs.s = (char*)pkg_malloc(hdrs.len*sizeof(char)); if(hdrs.s == NULL) goto error; memcpy(hdrs.s, "Referred-By: ", 13); memcpy(hdrs.s+13, dlg_bridge_controller.s, dlg_bridge_controller.len); memcpy(hdrs.s+13+dlg_bridge_controller.len, CRLF, CRLF_LEN); memcpy(hdrs.s+13+dlg_bridge_controller.len+CRLF_LEN, "Refer-To: ", 10); memcpy(hdrs.s+23+dlg_bridge_controller.len+CRLF_LEN, dtc->to.s, dtc->to.len); memcpy(hdrs.s+23+dlg_bridge_controller.len+CRLF_LEN+dtc->to.len, CRLF, CRLF_LEN); memcpy(hdrs.s+23+dlg_bridge_controller.len+CRLF_LEN+dtc->to.len+CRLF_LEN, dlg_bridge_ref_hdrs.s, dlg_bridge_ref_hdrs.len); set_uac_req(&uac_r, &met, &hdrs, NULL, dialog_info, TMCB_LOCAL_COMPLETED, dlg_refer_tm_callback, (void*)dtc); result = d_tmb.t_request_within(&uac_r); pkg_free(hdrs.s); if(result < 0) { LM_ERR("failed to send the REFER request\n"); /* todo: clean-up dtc */ goto error; } free_tm_dlg(dialog_info); LM_DBG("REFER sent\n"); return 0; error: if(dialog_info) free_tm_dlg(dialog_info); return -1; } void dlg_bridge_tm_callback(struct cell *t, int type, struct tmcb_params *ps) { struct sip_msg *msg = NULL; dlg_transfer_ctx_t *dtc = NULL; struct dlg_cell *dlg = NULL; str s; str cseq; str empty = {"", 0}; if(ps->param==NULL || *ps->param==0) { LM_DBG("message id not received\n"); return; } dtc = *((dlg_transfer_ctx_t**)ps->param); if(dtc==NULL) return; LM_DBG("completed with status %d\n", ps->code); if(ps->code>=300) goto error; /* 2xx - build dialog/send refer */ msg = ps->rpl; if((msg->cseq==NULL || parse_headers(msg,HDR_CSEQ_F,0)<0) || msg->cseq==NULL || msg->cseq->parsed==NULL) { LM_ERR("bad sip message or missing CSeq hdr :-/\n"); goto error; } cseq = (get_cseq(msg))->number; if((msg->to==NULL && parse_headers(msg, HDR_TO_F,0)<0) || msg->to==NULL) { LM_ERR("bad request or missing TO hdr\n"); goto error; } if(parse_from_header(msg)) { LM_ERR("bad request or missing FROM hdr\n"); goto error; } if((msg->callid==NULL && parse_headers(msg,HDR_CALLID_F,0)<0) || msg->callid==NULL){ LM_ERR("bad request or missing CALLID hdr\n"); goto error; } s = msg->callid->body; trim(&s); /* some sanity checks */ if (s.len==0 || get_from(msg)->tag_value.len==0) { LM_ERR("invalid request -> callid (%d) or from TAG (%d) empty\n", s.len, get_from(msg)->tag_value.len); goto error; } dlg = build_new_dlg(&s /*callid*/, &(get_from(msg)->uri) /*from uri*/, &(get_to(msg)->uri) /*to uri*/, &(get_from(msg)->tag_value)/*from_tag*/, &(get_to(msg)->uri) /*use to as r-uri*/ ); if (dlg==0) { LM_ERR("failed to create new dialog\n"); goto error; } dtc->dlg = dlg; if (dlg_set_leg_info(dlg, &(get_from(msg)->tag_value), &empty, &dlg_bridge_controller, &cseq, DLG_CALLER_LEG)!=0) { LM_ERR("dlg_set_leg_info failed\n"); goto error; } if (populate_leg_info(dlg, msg, t, DLG_CALLEE_LEG, &(get_to(msg)->tag_value)) !=0) { LM_ERR("could not add further info to the dialog\n"); shm_free(dlg); goto error; } if(dlg_refer_callee(dtc)!=0) goto error; return; error: dlg_transfer_ctx_free(dtc); return; } int dlg_bridge(str *from, str *to, str *op, str *bd) { dlg_transfer_ctx_t *dtc; int ret; str s_method = {"INVITE", 6}; str s_body; uac_req_t uac_r; dtc = (dlg_transfer_ctx_t*)shm_malloc(sizeof(dlg_transfer_ctx_t)); if(dtc==NULL) { LM_ERR("no shm\n"); return -1; } memset(dtc, 0, sizeof(dlg_transfer_ctx_t)); dtc->from.s = (char*)shm_malloc((from->len+1)*sizeof(char)); if(dtc->from.s==NULL) { LM_ERR("no shm\n"); shm_free(dtc); return -1; } dtc->to.s = (char*)shm_malloc((to->len+1)*sizeof(char)); if(dtc->to.s==NULL) { LM_ERR("no shm\n"); shm_free(dtc->from.s); shm_free(dtc); return -1; } memcpy(dtc->from.s, from->s, from->len); dtc->from.len = from->len; dtc->from.s[dtc->from.len] = '\0'; memcpy(dtc->to.s, to->s, to->len); dtc->to.len = to->len; dtc->to.s[dtc->to.len] = '\0'; LM_DBG("bridge <%.*s> to <%.*s>\n", dtc->from.len, dtc->from.s, dtc->to.len, dtc->to.s); if(bd!=NULL && bd->s!=NULL && bd->len>0) { s_body.s = bd->s; s_body.len = bd->len; } else { s_body.s = DLG_HOLD_SDP; s_body.len = DLG_HOLD_SDP_LEN; } memset(&uac_r, '\0', sizeof(uac_req_t)); uac_r.method = &s_method; uac_r.headers = &dlg_bridge_inv_hdrs; uac_r.body = &s_body; uac_r.cb_flags = TMCB_LOCAL_COMPLETED; uac_r.cb = dlg_bridge_tm_callback; uac_r.cbp = (void*)(long)dtc; ret = d_tmb.t_request(&uac_r, /* UAC Req */ &dtc->from, /* Request-URI (To) */ &dtc->from, /* To */ &dlg_bridge_controller, /* From */ (op != NULL && op->len>0)?op:NULL /* Outbound-URI */ ); if(ret<0) { dlg_transfer_ctx_free(dtc); return -1; } return 0; } int dlg_transfer(struct dlg_cell *dlg, str *to, int side) { dlg_transfer_ctx_t *dtc = NULL; struct dlg_cell *ndlg = NULL; str from; str empty = {"", 0}; dtc = (dlg_transfer_ctx_t*)shm_malloc(sizeof(dlg_transfer_ctx_t)); if(dtc==NULL) { LM_ERR("no shm\n"); return -1; } if(side==DLG_CALLEE_LEG) { from = dlg->from_uri; } else { from = dlg->to_uri; } memset(dtc, 0, sizeof(dlg_transfer_ctx_t)); dtc->from.s = (char*)shm_malloc((from.len+1)*sizeof(char)); if(dtc->from.s==NULL) { LM_ERR("no shm\n"); shm_free(dtc); return -1; } dtc->to.s = (char*)shm_malloc((to->len+1)*sizeof(char)); if(dtc->to.s==NULL) { LM_ERR("no shm\n"); shm_free(dtc->from.s); shm_free(dtc); return -1; } memcpy(dtc->from.s, from.s, from.len); dtc->from.len = from.len; dtc->from.s[dtc->from.len] = '\0'; memcpy(dtc->to.s, to->s, to->len); dtc->to.len = to->len; dtc->to.s[dtc->to.len] = '\0'; if(side==DLG_CALLER_LEG) ndlg = build_new_dlg(&dlg->callid /*callid*/, &dlg->to_uri /*from uri*/, &dlg->from_uri /*to uri*/, &dlg->tag[side]/*from_tag*/, &dlg->req_uri /*req uri */ ); else ndlg = build_new_dlg(&dlg->callid /*callid*/, &dlg->from_uri /*from uri*/, &dlg->to_uri /*to uri*/, &dlg->tag[side]/*from_tag*/, &dlg->req_uri /*req uri */ ); if (ndlg==0) { LM_ERR("failed to create new dialog\n"); goto error; } dtc->dlg = ndlg; if (dlg_set_leg_info(ndlg, &dlg->tag[side], &empty, &dlg->contact[side], &dlg->cseq[side], DLG_CALLER_LEG)!=0) { LM_ERR("dlg_set_leg_info failed for caller\n"); goto error; } if(side==DLG_CALLEE_LEG) side = DLG_CALLER_LEG; else side = DLG_CALLEE_LEG; if (dlg_set_leg_info(ndlg, &dlg->tag[side], &dlg->route_set[side], &dlg->contact[side], &dlg->cseq[side], DLG_CALLEE_LEG)!=0) { LM_ERR("dlg_set_leg_info failed for caller\n"); goto error; } if(dlg_refer_callee(dtc)!=0) goto error; return 0; error: dlg_transfer_ctx_free(dtc); return -1; }