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");
317 if (!sip
->csta
->monitor_cross_ref_id
) {
318 purple_debug_info("sipe", "sip_csta_monitor_stop: no monitor_cross_ref_id, exiting.\n");
323 "Content-Disposition: signal;handling=required\r\n"
324 "Content-Type: application/csta+xml\r\n");
326 body
= g_strdup_printf(
327 SIP_SEND_CSTA_MONITOR_STOP
,
328 sip
->csta
->monitor_cross_ref_id
);
330 send_sip_request(sip
->gc
,
332 sip
->csta
->dialog
->with
,
333 sip
->csta
->dialog
->with
,
343 sipe_invite_csta_gateway(struct sipe_account_data
*sip
);
347 process_invite_csta_gateway_response(struct sipe_account_data
*sip
,
349 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
351 purple_debug_info("sipe", "process_invite_csta_gateway_response:\n%s\n", msg
->body
? msg
->body
: "");
354 purple_debug_info("sipe", "process_invite_csta_gateway_response: sip->csta is not initializzed, exiting\n");
358 if (!sip
->csta
->dialog
) {
359 purple_debug_info("sipe", "process_invite_csta_gateway_response: GSTA dialog is NULL, exiting\n");
363 sipe_dialog_parse(sip
->csta
->dialog
, msg
, TRUE
);
365 if (msg
->response
>= 200) {
366 /* send ACK to CSTA */
367 sip
->csta
->dialog
->cseq
= 0;
368 send_sip_request(sip
->gc
, "ACK", sip
->csta
->dialog
->with
, sip
->csta
->dialog
->with
, NULL
, NULL
, sip
->csta
->dialog
, NULL
);
369 sip
->csta
->dialog
->outgoing_invite
= NULL
;
370 sip
->csta
->dialog
->is_established
= TRUE
;
373 if (msg
->response
>= 400) {
374 purple_debug_info("sipe", "process_invite_csta_gateway_response: INVITE response is not 200. Failed to join CSTA.\n");
375 /* @TODO notify user of failure to join CSTA */
378 else if (msg
->response
== 200) {
379 xmlnode
*xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
381 g_free(sip
->csta
->gateway_status
);
382 sip
->csta
->gateway_status
= xmlnode_get_data(xmlnode_get_child(xml
, "systemStatus"));
383 purple_debug_info("sipe", "process_invite_csta_gateway_response: gateway_status=%s\n",
384 sip
->csta
->gateway_status
? sip
->csta
->gateway_status
: "");
385 if (!g_ascii_strcasecmp(sip
->csta
->gateway_status
, "normal")) {
386 if (!sip
->csta
->monitor_cross_ref_id
) {
387 sip_csta_get_features(sip
);
388 sip_csta_monitor_start(sip
);
391 purple_debug_info("sipe", "process_invite_csta_gateway_response: ERROR: CSTA status is %s, won't continue.\n",
392 sip
->csta
->gateway_status
);
393 /* @TODO notify user of failure to join CSTA */
397 /* schedule re-invite. RFC4028 */
398 if (sip
->csta
->dialog
->expires
) {
399 sipe_schedule_action("<+csta>",
400 sip
->csta
->dialog
->expires
- 60, /* 1 minute earlier */
401 (Action
)sipe_invite_csta_gateway
,
411 /** Creates long living dialog with SIP/CSTA Gateway */
412 /* should be re-entrant as require to sent re-invites every 10 min to refresh */
414 sipe_invite_csta_gateway(struct sipe_account_data
*sip
)
421 purple_debug_info("sipe", "sipe_invite_csta_gateway: sip->csta is uninitialized, exiting\n");
425 if(!sip
->csta
->dialog
) {
426 sip
->csta
->dialog
= g_new0(struct sip_dialog
, 1);
427 sip
->csta
->dialog
->callid
= gencallid();
428 sip
->csta
->dialog
->with
= g_strdup(sip
->csta
->gateway_uri
);
430 if (!(sip
->csta
->dialog
->ourtag
)) {
431 sip
->csta
->dialog
->ourtag
= gentag();
434 contact
= get_contact(sip
);
435 hdr
= g_strdup_printf(
437 "Supported: timer\r\n"
438 "Content-Disposition: signal;handling=required\r\n"
439 "Content-Type: application/csta+xml\r\n",
443 body
= g_strdup_printf(
444 SIP_SEND_CSTA_REQUEST_SYSTEM_STATUS
,
445 sip
->csta
->line_uri
);
447 sip
->csta
->dialog
->outgoing_invite
= send_sip_request(sip
->gc
,
449 sip
->csta
->dialog
->with
,
450 sip
->csta
->dialog
->with
,
454 process_invite_csta_gateway_response
);
460 sip_csta_open(struct sipe_account_data
*sip
,
461 const gchar
*line_uri
,
464 sip_csta_initialize(sip
, line_uri
, server
);
465 sipe_invite_csta_gateway(sip
);
469 sip_csta_free(struct sip_csta
*csta
)
473 g_free(csta
->line_uri
);
474 g_free(csta
->gateway_uri
);
476 sipe_dialog_free(csta
->dialog
);
478 g_free(csta
->gateway_status
);
479 g_free(csta
->monitor_cross_ref_id
);
480 g_free(csta
->line_status
);
481 g_free(csta
->to_tel_uri
);
482 g_free(csta
->call_id
);
483 g_free(csta
->device_id
);
489 sip_csta_close(struct sipe_account_data
*sip
)
492 sip_csta_monitor_stop(sip
);
495 if (sip
->csta
&& sip
->csta
->dialog
) {
496 /* send BYE to CSTA */
497 send_sip_request(sip
->gc
,
499 sip
->csta
->dialog
->with
,
500 sip
->csta
->dialog
->with
,
507 sip_csta_free(sip
->csta
);
512 /** Make Call's callback */
514 process_csta_make_call_response(struct sipe_account_data
*sip
,
516 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
518 purple_debug_info("sipe", "process_csta_make_call_response:\n%s\n", msg
->body
? msg
->body
: "");
521 purple_debug_info("sipe", "process_csta_make_call_response: sip->csta is not initializzed, exiting\n");
525 if (msg
->response
>= 400) {
526 purple_debug_info("sipe", "process_csta_make_call_response: Make Call response is not 200. Failed to make call.\n");
527 /* @TODO notify user of failure to make call */
530 else if (msg
->response
== 200) {
532 xmlnode
*xn_calling_device
;
535 purple_debug_info("sipe", "process_csta_make_call_response: SUCCESS\n");
537 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
538 xn_calling_device
= xmlnode_get_child(xml
, "callingDevice");
539 device_id
= xmlnode_get_data(xmlnode_get_child(xn_calling_device
, "deviceID"));
540 if (sipe_strequal(sip
->csta
->line_uri
, device_id
)) {
541 g_free(sip
->csta
->call_id
);
542 sip
->csta
->call_id
= xmlnode_get_data(xmlnode_get_child(xn_calling_device
, "callID"));
543 purple_debug_info("sipe", "process_csta_make_call_response: call_id=%s\n", sip
->csta
->call_id
? sip
->csta
->call_id
: "");
554 sip_csta_make_call(struct sipe_account_data
*sip
,
555 const gchar
* to_tel_uri
)
561 purple_debug_info("sipe", "sip_csta_make_call: no tel URI parameter provided, exiting.\n");
565 if (!sip
->csta
|| !sip
->csta
->dialog
|| !sip
->csta
->dialog
->is_established
) {
566 purple_debug_info("sipe", "sip_csta_make_call: no dialog with CSTA, exiting.\n");
570 g_free(sip
->csta
->to_tel_uri
);
571 sip
->csta
->to_tel_uri
= g_strdup(to_tel_uri
);
574 "Content-Disposition: signal;handling=required\r\n"
575 "Content-Type: application/csta+xml\r\n");
577 body
= g_strdup_printf(
578 SIP_SEND_CSTA_MAKE_CALL
,
580 sip
->csta
->to_tel_uri
);
582 send_sip_request(sip
->gc
,
584 sip
->csta
->dialog
->with
,
585 sip
->csta
->dialog
->with
,
589 process_csta_make_call_response
);
595 sip_csta_update_id_and_status(struct sip_csta
*csta
,
599 gchar
*call_id
= xmlnode_get_data(xmlnode_get_child(node
, "callID"));
601 if (!sipe_strequal(call_id
, csta
->call_id
)) {
602 purple_debug_info("sipe", "sipe_csta_update_id_and_status: callID (%s) does not match\n", call_id
);
606 /* free old line status */
607 g_free(csta
->line_status
);
608 csta
->line_status
= NULL
;
613 gchar
*device_id
= xmlnode_get_data(xmlnode_get_child(node
, "deviceID"));
614 purple_debug_info("sipe", "sipe_csta_update_id_and_status: device_id=(%s)\n", device_id
? device_id
: "");
616 g_free(csta
->device_id
);
617 csta
->device_id
= device_id
;
620 /* set new line status */
621 csta
->line_status
= g_strdup(status
);
625 /* clean up cleared connection */
626 g_free(csta
->to_tel_uri
);
627 csta
->to_tel_uri
= NULL
;
628 g_free(csta
->call_id
);
629 csta
->call_id
= NULL
;
630 g_free(csta
->device_id
);
631 csta
->device_id
= NULL
;
639 process_incoming_info_csta(struct sipe_account_data
*sip
,
642 gchar
*monitor_cross_ref_id
;
643 xmlnode
*xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
647 monitor_cross_ref_id
= xmlnode_get_data(xmlnode_get_child(xml
, "monitorCrossRefID"));
649 if(!sip
->csta
|| !sipe_strequal(monitor_cross_ref_id
, sip
->csta
->monitor_cross_ref_id
))
651 purple_debug_info("sipe", "process_incoming_info_csta: monitorCrossRefID (%s) does not match, exiting\n",
652 monitor_cross_ref_id
? monitor_cross_ref_id
: "");
656 if (sipe_strequal(xml
->name
, "OriginatedEvent"))
658 sip_csta_update_id_and_status(sip
->csta
,
659 xmlnode_get_child(xml
, "originatedConnection"),
660 ORIGINATED_CSTA_STATUS
);
662 else if (sipe_strequal(xml
->name
, "DeliveredEvent"))
664 sip_csta_update_id_and_status(sip
->csta
,
665 xmlnode_get_child(xml
, "connection"),
666 DELIVERED_CSTA_STATUS
);
668 else if (sipe_strequal(xml
->name
, "EstablishedEvent"))
670 sip_csta_update_id_and_status(sip
->csta
,
671 xmlnode_get_child(xml
, "establishedConnection"),
672 ESTABLISHED_CSTA_STATUS
);
674 else if (sipe_strequal(xml
->name
, "ConnectionClearedEvent"))
676 sip_csta_update_id_and_status(sip
->csta
,
677 xmlnode_get_child(xml
, "droppedConnection"),
682 g_free(monitor_cross_ref_id
);