6 * Copyright (C) 2009 pier11 <pier11@operamail.com>
8 * Implements Remote Call Control (RCC) feature for
9 * integration with legacy enterprise PBX (wired telephony) systems.
10 * Should be applicable to 2005 and 2007(R2) systems.
11 * Inderlying XML protocol CSTA is defined in ECMA-323.
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
35 #include "sipe-dialog.h"
37 #include "sipe-utils.h"
40 #define ORIGINATED_CSTA_STATUS "originated"
41 #define DELIVERED_CSTA_STATUS "delivered"
42 #define ESTABLISHED_CSTA_STATUS "established"
46 * Sends CSTA RequestSystemStatus request to SIP/CSTA Gateway.
47 * @param line_uri (%s) Ex.: tel:73124;phone-context=dialstring;partition=BE_BRS_INT
49 #define SIP_SEND_CSTA_REQUEST_SYSTEM_STATUS \
50 "<?xml version=\"1.0\"?>"\
51 "<RequestSystemStatus xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
55 "<lcs:line xmlns:lcs=\"http://schemas.microsoft.com/Lcs/2005/04/RCCExtension\">%s</lcs:line>"\
59 "</RequestSystemStatus>"
62 * Sends CSTA GetCSTAFeatures request to SIP/CSTA Gateway.
63 * @param line_uri (%s) Ex.: tel:73124;phone-context=dialstring;partition=BE_BRS_INT
65 #define SIP_SEND_CSTA_GET_CSTA_FEATURES \
66 "<?xml version=\"1.0\"?>"\
67 "<GetCSTAFeatures xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
71 "<lcs:line xmlns:lcs=\"http://schemas.microsoft.com/Lcs/2005/04/RCCExtension\">%s</lcs:line>"\
78 * Sends CSTA start monitor request to SIP/CSTA Gateway.
79 * @param line_uri (%s) Ex.: tel:73124;phone-context=dialstring;partition=BE_BRS_INT
81 #define SIP_SEND_CSTA_MONITOR_START \
82 "<?xml version=\"1.0\"?>"\
83 "<MonitorStart xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
85 "<deviceObject>%s</deviceObject>"\
90 * Sends CSTA stop monitor request to SIP/CSTA Gateway.
91 * @param monitor_cross_ref_id (%s) Ex.: 99fda87c
93 #define SIP_SEND_CSTA_MONITOR_STOP \
94 "<?xml version=\"1.0\"?>"\
95 "<MonitorStop xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
96 "<monitorCrossRefID>%s</monitorCrossRefID>"\
100 * Sends CSTA make call request to SIP/CSTA Gateway.
101 * @param line_uri (%s) Ex.: tel:73124;phone-context=dialstring;partition=BE_BRS_INT
102 * @param to_tel_uri (%s) Ex.: tel:+3222220220
104 #define SIP_SEND_CSTA_MAKE_CALL \
105 "<?xml version=\"1.0\"?>"\
106 "<MakeCall xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
107 "<callingDevice>%s</callingDevice>"\
108 "<calledDirectoryNumber>%s</calledDirectoryNumber>"\
109 "<autoOriginate>doNotPrompt</autoOriginate>"\
113 * Sends CSTA ClearConnection request to SIP/CSTA Gateway.
114 * @param call_id (%s) Ex.: 0_99f261b4
115 * @param device_id (%s) Same as in OriginatedEvent, DeliveredEvent notifications.
116 * Ex.: tel:73124;phone-context=dialstring
118 #define SIP_SEND_CSTA_CLEAR_CONNECTION \
119 "<?xml version=\"1.0\"?>"\
120 "<ClearConnection xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
121 "<connectionToBeCleared>"\
122 "<callID>%s</callID>"\
123 "<deviceID>%s</deviceID>"\
124 "</connectionToBeCleared>"\
129 sip_to_tel_uri0(const gchar
*phone
)
131 if (!phone
|| strlen(phone
) == 0) return NULL
;
133 if (g_str_has_prefix(phone
, "tel:")) {
134 return g_strdup(phone
);
136 gchar
*tel_uri
= g_malloc(strlen(phone
) + 4 + 1);
137 gchar
*dest_p
= g_stpcpy(tel_uri
, "tel:");
138 for (; *phone
; phone
++) {
139 if (*phone
== ' ') continue;
140 if (*phone
== '(') continue;
141 if (*phone
== ')') continue;
142 if (*phone
== '-') continue;
143 if (*phone
== '.') continue;
152 sip_to_tel_uri(const gchar
*phone
)
154 gchar
*res
= sip_to_tel_uri0(phone
);
156 /* strips everything starting with 'v:' if any */
157 if (res
&& (v
= strstr(res
, "v:"))) {
160 res
= g_strndup(res
, v
- res
);
168 sip_tel_uri_denormalize(const gchar
*tel_uri
)
170 if (!tel_uri
) return NULL
;
172 if (g_str_has_prefix(tel_uri
, "tel:")) {
173 return g_strdup(tel_uri
+ 4);
175 return g_strdup(tel_uri
);
180 sip_csta_initialize(struct sipe_account_data
*sip
,
181 const gchar
*line_uri
,
185 sip
->csta
= g_new0(struct sip_csta
, 1);
186 sip
->csta
->line_uri
= g_strdup(line_uri
);
187 sip
->csta
->gateway_uri
= g_strdup(server
);
189 purple_debug_info("sipe", "sip_csta_initialize: sip->csta is already instantiated, exiting.\n");
193 /** get CSTA feautures's callback */
195 process_csta_get_features_response(SIPE_UNUSED_PARAMETER
struct sipe_account_data
*sip
,
197 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
199 if (msg
->response
>= 400) {
200 purple_debug_info("sipe", "process_csta_get_features_response: Get CSTA features response is not 200. Failed to get features.\n");
201 /* @TODO notify user of failure to get CSTA features */
204 else if (msg
->response
== 200) {
205 purple_debug_info("sipe", "process_csta_get_features_response:\n%s\n", msg
->body
? msg
->body
: "");
211 /** get CSTA feautures */
213 sip_csta_get_features(struct sipe_account_data
*sip
)
218 if (!sip
->csta
|| !sip
->csta
->dialog
|| !sip
->csta
->dialog
->is_established
) {
219 purple_debug_info("sipe", "sip_csta_get_features: no dialog with CSTA, exiting.\n");
224 "Content-Disposition: signal;handling=required\r\n"
225 "Content-Type: application/csta+xml\r\n");
227 body
= g_strdup_printf(
228 SIP_SEND_CSTA_GET_CSTA_FEATURES
,
229 sip
->csta
->line_uri
);
231 send_sip_request(sip
->gc
,
233 sip
->csta
->dialog
->with
,
234 sip
->csta
->dialog
->with
,
238 process_csta_get_features_response
);
243 /** Monitor Start's callback */
245 process_csta_monitor_start_response(struct sipe_account_data
*sip
,
247 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
249 purple_debug_info("sipe", "process_csta_monitor_start_response:\n%s\n", msg
->body
? msg
->body
: "");
252 purple_debug_info("sipe", "process_csta_monitor_start_response: sip->csta is not initializzed, exiting\n");
256 if (msg
->response
>= 400) {
257 purple_debug_info("sipe", "process_csta_monitor_start_response: Monitor Start response is not 200. Failed to start monitor.\n");
258 /* @TODO notify user of failure to start monitor */
261 else if (msg
->response
== 200) {
262 xmlnode
*xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
263 g_free(sip
->csta
->monitor_cross_ref_id
);
264 sip
->csta
->monitor_cross_ref_id
= xmlnode_get_data(xmlnode_get_child(xml
, "monitorCrossRefID"));
265 purple_debug_info("sipe", "process_csta_monitor_start_response: monitor_cross_ref_id=%s\n",
266 sip
->csta
->monitor_cross_ref_id
? sip
->csta
->monitor_cross_ref_id
: "");
275 sip_csta_monitor_start(struct sipe_account_data
*sip
)
280 if (!sip
->csta
|| !sip
->csta
->dialog
|| !sip
->csta
->dialog
->is_established
) {
281 purple_debug_info("sipe", "sip_csta_monitor_start: no dialog with CSTA, exiting.\n");
286 "Content-Disposition: signal;handling=required\r\n"
287 "Content-Type: application/csta+xml\r\n");
289 body
= g_strdup_printf(
290 SIP_SEND_CSTA_MONITOR_START
,
291 sip
->csta
->line_uri
);
293 send_sip_request(sip
->gc
,
295 sip
->csta
->dialog
->with
,
296 sip
->csta
->dialog
->with
,
300 process_csta_monitor_start_response
);
307 sip_csta_monitor_stop(struct sipe_account_data
*sip
)
312 if (!sip
->csta
|| !sip
->csta
->dialog
|| !sip
->csta
->dialog
->is_established
) {
313 purple_debug_info("sipe", "sip_csta_monitor_stop: no dialog with CSTA, exiting.\n");
318 "Content-Disposition: signal;handling=required\r\n"
319 "Content-Type: application/csta+xml\r\n");
321 body
= g_strdup_printf(
322 SIP_SEND_CSTA_MONITOR_STOP
,
323 sip
->csta
->monitor_cross_ref_id
);
325 send_sip_request(sip
->gc
,
327 sip
->csta
->dialog
->with
,
328 sip
->csta
->dialog
->with
,
338 sipe_invite_csta_gateway(struct sipe_account_data
*sip
);
342 process_invite_csta_gateway_response(struct sipe_account_data
*sip
,
344 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
346 purple_debug_info("sipe", "process_invite_csta_gateway_response:\n%s\n", msg
->body
? msg
->body
: "");
349 purple_debug_info("sipe", "process_invite_csta_gateway_response: sip->csta is not initializzed, exiting\n");
353 if (!sip
->csta
->dialog
) {
354 purple_debug_info("sipe", "process_invite_csta_gateway_response: GSTA dialog is NULL, exiting\n");
358 sipe_dialog_parse(sip
->csta
->dialog
, msg
, TRUE
);
360 if (msg
->response
>= 200) {
361 /* send ACK to CSTA */
362 sip
->csta
->dialog
->cseq
= 0;
363 send_sip_request(sip
->gc
, "ACK", sip
->csta
->dialog
->with
, sip
->csta
->dialog
->with
, NULL
, NULL
, sip
->csta
->dialog
, NULL
);
364 sip
->csta
->dialog
->outgoing_invite
= NULL
;
365 sip
->csta
->dialog
->is_established
= TRUE
;
368 if (msg
->response
>= 400) {
369 purple_debug_info("sipe", "process_invite_csta_gateway_response: INVITE response is not 200. Failed to join CSTA.\n");
370 /* @TODO notify user of failure to join CSTA */
373 else if (msg
->response
== 200) {
374 xmlnode
*xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
376 g_free(sip
->csta
->gateway_status
);
377 sip
->csta
->gateway_status
= xmlnode_get_data(xmlnode_get_child(xml
, "systemStatus"));
378 purple_debug_info("sipe", "process_invite_csta_gateway_response: gateway_status=%s\n",
379 sip
->csta
->gateway_status
? sip
->csta
->gateway_status
: "");
380 if (!g_ascii_strcasecmp(sip
->csta
->gateway_status
, "normal")) {
381 if (!sip
->csta
->monitor_cross_ref_id
) {
382 sip_csta_get_features(sip
);
383 sip_csta_monitor_start(sip
);
386 purple_debug_info("sipe", "process_invite_csta_gateway_response: ERROR: CSTA status is %s, won't continue.\n",
387 sip
->csta
->gateway_status
);
388 /* @TODO notify user of failure to join CSTA */
392 /* schedule re-invite. RFC4028 */
393 if (sip
->csta
->dialog
->expires
) {
394 sipe_schedule_action("<+csta>",
395 sip
->csta
->dialog
->expires
- 60, /* 1 minute earlier */
396 (Action
)sipe_invite_csta_gateway
,
406 /** Creates long living dialog with SIP/CSTA Gateway */
407 /* should be re-entrant as require to sent re-invites every 10 min to refresh */
409 sipe_invite_csta_gateway(struct sipe_account_data
*sip
)
416 purple_debug_info("sipe", "sipe_invite_csta_gateway: sip->csta is uninitialized, exiting\n");
420 if(!sip
->csta
->dialog
) {
421 sip
->csta
->dialog
= g_new0(struct sip_dialog
, 1);
422 sip
->csta
->dialog
->callid
= gencallid();
423 sip
->csta
->dialog
->with
= g_strdup(sip
->csta
->gateway_uri
);
425 if (!(sip
->csta
->dialog
->ourtag
)) {
426 sip
->csta
->dialog
->ourtag
= gentag();
429 contact
= get_contact(sip
);
430 hdr
= g_strdup_printf(
432 "Supported: timer\r\n"
433 "Content-Disposition: signal;handling=required\r\n"
434 "Content-Type: application/csta+xml\r\n",
438 body
= g_strdup_printf(
439 SIP_SEND_CSTA_REQUEST_SYSTEM_STATUS
,
440 sip
->csta
->line_uri
);
442 sip
->csta
->dialog
->outgoing_invite
= send_sip_request(sip
->gc
,
444 sip
->csta
->dialog
->with
,
445 sip
->csta
->dialog
->with
,
449 process_invite_csta_gateway_response
);
455 sip_csta_open(struct sipe_account_data
*sip
,
456 const gchar
*line_uri
,
459 sip_csta_initialize(sip
, line_uri
, server
);
460 sipe_invite_csta_gateway(sip
);
464 sip_csta_free(struct sip_csta
*csta
)
468 g_free(csta
->line_uri
);
469 g_free(csta
->gateway_uri
);
471 sipe_dialog_free(csta
->dialog
);
473 g_free(csta
->gateway_status
);
474 g_free(csta
->monitor_cross_ref_id
);
475 g_free(csta
->line_status
);
476 g_free(csta
->to_tel_uri
);
477 g_free(csta
->call_id
);
478 g_free(csta
->device_id
);
484 sip_csta_close(struct sipe_account_data
*sip
)
487 sip_csta_monitor_stop(sip
);
490 if (sip
->csta
&& sip
->csta
->dialog
) {
491 /* send BYE to CSTA */
492 send_sip_request(sip
->gc
,
494 sip
->csta
->dialog
->with
,
495 sip
->csta
->dialog
->with
,
502 sip_csta_free(sip
->csta
);
507 /** Make Call's callback */
509 process_csta_make_call_response(struct sipe_account_data
*sip
,
511 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
513 purple_debug_info("sipe", "process_csta_make_call_response:\n%s\n", msg
->body
? msg
->body
: "");
516 purple_debug_info("sipe", "process_csta_make_call_response: sip->csta is not initializzed, exiting\n");
520 if (msg
->response
>= 400) {
521 purple_debug_info("sipe", "process_csta_make_call_response: Make Call response is not 200. Failed to make call.\n");
522 /* @TODO notify user of failure to make call */
525 else if (msg
->response
== 200) {
527 xmlnode
*xn_calling_device
;
530 purple_debug_info("sipe", "process_csta_make_call_response: SUCCESS\n");
532 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
533 xn_calling_device
= xmlnode_get_child(xml
, "callingDevice");
534 device_id
= xmlnode_get_data(xmlnode_get_child(xn_calling_device
, "deviceID"));
535 if (!strcmp(sip
->csta
->line_uri
, device_id
)) {
536 g_free(sip
->csta
->call_id
);
537 sip
->csta
->call_id
= xmlnode_get_data(xmlnode_get_child(xn_calling_device
, "callID"));
538 purple_debug_info("sipe", "process_csta_make_call_response: call_id=%s\n", sip
->csta
->call_id
? sip
->csta
->call_id
: "");
549 sip_csta_make_call(struct sipe_account_data
*sip
,
550 const gchar
* to_tel_uri
)
556 purple_debug_info("sipe", "sip_csta_make_call: no tel URI parameter provided, exiting.\n");
560 if (!sip
->csta
|| !sip
->csta
->dialog
|| !sip
->csta
->dialog
->is_established
) {
561 purple_debug_info("sipe", "sip_csta_make_call: no dialog with CSTA, exiting.\n");
565 g_free(sip
->csta
->to_tel_uri
);
566 sip
->csta
->to_tel_uri
= g_strdup(to_tel_uri
);
569 "Content-Disposition: signal;handling=required\r\n"
570 "Content-Type: application/csta+xml\r\n");
572 body
= g_strdup_printf(
573 SIP_SEND_CSTA_MAKE_CALL
,
575 sip
->csta
->to_tel_uri
);
577 send_sip_request(sip
->gc
,
579 sip
->csta
->dialog
->with
,
580 sip
->csta
->dialog
->with
,
584 process_csta_make_call_response
);
590 sip_csta_update_id_and_status(struct sip_csta
*csta
,
594 gchar
*call_id
= xmlnode_get_data(xmlnode_get_child(node
, "callID"));
596 if (call_id
&& csta
->call_id
&& strcmp(call_id
, csta
->call_id
)) {
597 purple_debug_info("sipe", "sipe_csta_update_id_and_status: callID (%s) does not match\n", call_id
? call_id
: "");
601 /* free old line status */
602 g_free(csta
->line_status
);
603 csta
->line_status
= NULL
;
608 gchar
*device_id
= xmlnode_get_data(xmlnode_get_child(node
, "deviceID"));
609 purple_debug_info("sipe", "sipe_csta_update_id_and_status: device_id=(%s)\n", device_id
? device_id
: "");
611 g_free(csta
->device_id
);
612 csta
->device_id
= device_id
;
615 /* set new line status */
616 csta
->line_status
= g_strdup(status
);
620 /* clean up cleared connection */
621 g_free(csta
->to_tel_uri
);
622 csta
->to_tel_uri
= NULL
;
623 g_free(csta
->call_id
);
624 csta
->call_id
= NULL
;
625 g_free(csta
->device_id
);
626 csta
->device_id
= NULL
;
634 process_incoming_info_csta(struct sipe_account_data
*sip
,
637 gchar
*monitor_cross_ref_id
;
638 xmlnode
*xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
642 monitor_cross_ref_id
= xmlnode_get_data(xmlnode_get_child(xml
, "monitorCrossRefID"));
644 if(!sip
->csta
|| (monitor_cross_ref_id
645 && sip
->csta
->monitor_cross_ref_id
646 && strcmp(monitor_cross_ref_id
, sip
->csta
->monitor_cross_ref_id
)))
648 purple_debug_info("sipe", "process_incoming_info_csta: monitorCrossRefID (%s) does not match, exiting\n",
649 monitor_cross_ref_id
? monitor_cross_ref_id
: "");
653 if (!strcmp(xml
->name
, "OriginatedEvent"))
655 sip_csta_update_id_and_status(sip
->csta
,
656 xmlnode_get_child(xml
, "originatedConnection"),
657 ORIGINATED_CSTA_STATUS
);
659 else if (!strcmp(xml
->name
, "DeliveredEvent"))
661 sip_csta_update_id_and_status(sip
->csta
,
662 xmlnode_get_child(xml
, "connection"),
663 DELIVERED_CSTA_STATUS
);
665 else if (!strcmp(xml
->name
, "EstablishedEvent"))
667 sip_csta_update_id_and_status(sip
->csta
,
668 xmlnode_get_child(xml
, "establishedConnection"),
669 ESTABLISHED_CSTA_STATUS
);
671 else if (!strcmp(xml
->name
, "ConnectionClearedEvent"))
673 sip_csta_update_id_and_status(sip
->csta
,
674 xmlnode_get_child(xml
, "droppedConnection"),
679 g_free(monitor_cross_ref_id
);