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.
kamailio/modules/dialog/dlg_transfer.c

471 lines
11 KiB

/*
* 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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#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: <sip:kamailio.org:5060>\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;
}