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
38 #include "sipe-common.h"
42 #include "sipe-dialog.h"
43 #include "sipe-utils.h"
46 #define ORIGINATED_CSTA_STATUS "originated"
47 #define DELIVERED_CSTA_STATUS "delivered"
48 #define ESTABLISHED_CSTA_STATUS "established"
52 * Sends CSTA RequestSystemStatus request to SIP/CSTA Gateway.
53 * @param line_uri (%s) Ex.: tel:73124;phone-context=dialstring;partition=BE_BRS_INT
55 #define SIP_SEND_CSTA_REQUEST_SYSTEM_STATUS \
56 "<?xml version=\"1.0\"?>"\
57 "<RequestSystemStatus xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
61 "<lcs:line xmlns:lcs=\"http://schemas.microsoft.com/Lcs/2005/04/RCCExtension\">%s</lcs:line>"\
65 "</RequestSystemStatus>"
68 * Sends CSTA GetCSTAFeatures request to SIP/CSTA Gateway.
69 * @param line_uri (%s) Ex.: tel:73124;phone-context=dialstring;partition=BE_BRS_INT
71 #define SIP_SEND_CSTA_GET_CSTA_FEATURES \
72 "<?xml version=\"1.0\"?>"\
73 "<GetCSTAFeatures xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
77 "<lcs:line xmlns:lcs=\"http://schemas.microsoft.com/Lcs/2005/04/RCCExtension\">%s</lcs:line>"\
84 * Sends CSTA start monitor request to SIP/CSTA Gateway.
85 * @param line_uri (%s) Ex.: tel:73124;phone-context=dialstring;partition=BE_BRS_INT
87 #define SIP_SEND_CSTA_MONITOR_START \
88 "<?xml version=\"1.0\"?>"\
89 "<MonitorStart xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
91 "<deviceObject>%s</deviceObject>"\
96 * Sends CSTA stop monitor request to SIP/CSTA Gateway.
97 * @param monitor_cross_ref_id (%s) Ex.: 99fda87c
99 #define SIP_SEND_CSTA_MONITOR_STOP \
100 "<?xml version=\"1.0\"?>"\
101 "<MonitorStop xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
102 "<monitorCrossRefID>%s</monitorCrossRefID>"\
106 * Sends CSTA make call request to SIP/CSTA Gateway.
107 * @param line_uri (%s) Ex.: tel:73124;phone-context=dialstring;partition=BE_BRS_INT
108 * @param to_tel_uri (%s) Ex.: tel:+3222220220
110 #define SIP_SEND_CSTA_MAKE_CALL \
111 "<?xml version=\"1.0\"?>"\
112 "<MakeCall xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
113 "<callingDevice>%s</callingDevice>"\
114 "<calledDirectoryNumber>%s</calledDirectoryNumber>"\
115 "<autoOriginate>doNotPrompt</autoOriginate>"\
119 * Sends CSTA ClearConnection request to SIP/CSTA Gateway.
120 * @param call_id (%s) Ex.: 0_99f261b4
121 * @param device_id (%s) Same as in OriginatedEvent, DeliveredEvent notifications.
122 * Ex.: tel:73124;phone-context=dialstring
124 #define SIP_SEND_CSTA_CLEAR_CONNECTION \
125 "<?xml version=\"1.0\"?>"\
126 "<ClearConnection xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
127 "<connectionToBeCleared>"\
128 "<callID>%s</callID>"\
129 "<deviceID>%s</deviceID>"\
130 "</connectionToBeCleared>"\
135 sip_to_tel_uri0(const gchar
*phone
)
137 if (!phone
|| strlen(phone
) == 0) return NULL
;
139 if (g_str_has_prefix(phone
, "tel:")) {
140 return g_strdup(phone
);
142 gchar
*tel_uri
= g_malloc(strlen(phone
) + 4 + 1);
143 gchar
*dest_p
= g_stpcpy(tel_uri
, "tel:");
144 for (; *phone
; phone
++) {
145 if (*phone
== ' ') continue;
146 if (*phone
== '(') continue;
147 if (*phone
== ')') continue;
148 if (*phone
== '-') continue;
149 if (*phone
== '.') continue;
158 sip_to_tel_uri(const gchar
*phone
)
160 gchar
*res
= sip_to_tel_uri0(phone
);
162 /* strips everything starting with 'v:' if any */
163 if (res
&& (v
= strstr(res
, "v:"))) {
166 res
= g_strndup(res
, v
- res
);
174 sip_tel_uri_denormalize(const gchar
*tel_uri
)
176 if (!tel_uri
) return NULL
;
178 if (g_str_has_prefix(tel_uri
, "tel:")) {
179 return g_strdup(tel_uri
+ 4);
181 return g_strdup(tel_uri
);
186 sip_csta_initialize(struct sipe_account_data
*sip
,
187 const gchar
*line_uri
,
191 sip
->csta
= g_new0(struct sip_csta
, 1);
192 sip
->csta
->line_uri
= g_strdup(line_uri
);
193 sip
->csta
->gateway_uri
= g_strdup(server
);
195 purple_debug_info("sipe", "sip_csta_initialize: sip->csta is already instantiated, exiting.\n");
199 /** get CSTA feautures's callback */
201 process_csta_get_features_response(SIPE_UNUSED_PARAMETER
struct sipe_account_data
*sip
,
203 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
205 if (msg
->response
>= 400) {
206 purple_debug_info("sipe", "process_csta_get_features_response: Get CSTA features response is not 200. Failed to get features.\n");
207 /* @TODO notify user of failure to get CSTA features */
210 else if (msg
->response
== 200) {
211 purple_debug_info("sipe", "process_csta_get_features_response:\n%s\n", msg
->body
? msg
->body
: "");
217 /** get CSTA feautures */
219 sip_csta_get_features(struct sipe_account_data
*sip
)
224 if (!sip
->csta
|| !sip
->csta
->dialog
|| !sip
->csta
->dialog
->is_established
) {
225 purple_debug_info("sipe", "sip_csta_get_features: no dialog with CSTA, exiting.\n");
230 "Content-Disposition: signal;handling=required\r\n"
231 "Content-Type: application/csta+xml\r\n");
233 body
= g_strdup_printf(
234 SIP_SEND_CSTA_GET_CSTA_FEATURES
,
235 sip
->csta
->line_uri
);
237 send_sip_request(sip
->gc
,
239 sip
->csta
->dialog
->with
,
240 sip
->csta
->dialog
->with
,
244 process_csta_get_features_response
);
249 /** Monitor Start's callback */
251 process_csta_monitor_start_response(struct sipe_account_data
*sip
,
253 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
255 purple_debug_info("sipe", "process_csta_monitor_start_response:\n%s\n", msg
->body
? msg
->body
: "");
258 purple_debug_info("sipe", "process_csta_monitor_start_response: sip->csta is not initializzed, exiting\n");
262 if (msg
->response
>= 400) {
263 purple_debug_info("sipe", "process_csta_monitor_start_response: Monitor Start response is not 200. Failed to start monitor.\n");
264 /* @TODO notify user of failure to start monitor */
267 else if (msg
->response
== 200) {
268 xmlnode
*xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
269 g_free(sip
->csta
->monitor_cross_ref_id
);
270 sip
->csta
->monitor_cross_ref_id
= xmlnode_get_data(xmlnode_get_child(xml
, "monitorCrossRefID"));
271 purple_debug_info("sipe", "process_csta_monitor_start_response: monitor_cross_ref_id=%s\n",
272 sip
->csta
->monitor_cross_ref_id
? sip
->csta
->monitor_cross_ref_id
: "");
281 sip_csta_monitor_start(struct sipe_account_data
*sip
)
286 if (!sip
->csta
|| !sip
->csta
->dialog
|| !sip
->csta
->dialog
->is_established
) {
287 purple_debug_info("sipe", "sip_csta_monitor_start: no dialog with CSTA, exiting.\n");
292 "Content-Disposition: signal;handling=required\r\n"
293 "Content-Type: application/csta+xml\r\n");
295 body
= g_strdup_printf(
296 SIP_SEND_CSTA_MONITOR_START
,
297 sip
->csta
->line_uri
);
299 send_sip_request(sip
->gc
,
301 sip
->csta
->dialog
->with
,
302 sip
->csta
->dialog
->with
,
306 process_csta_monitor_start_response
);
313 sip_csta_monitor_stop(struct sipe_account_data
*sip
)
318 if (!sip
->csta
|| !sip
->csta
->dialog
|| !sip
->csta
->dialog
->is_established
) {
319 purple_debug_info("sipe", "sip_csta_monitor_stop: no dialog with CSTA, exiting.\n");
323 if (!sip
->csta
->monitor_cross_ref_id
) {
324 purple_debug_info("sipe", "sip_csta_monitor_stop: no monitor_cross_ref_id, exiting.\n");
329 "Content-Disposition: signal;handling=required\r\n"
330 "Content-Type: application/csta+xml\r\n");
332 body
= g_strdup_printf(
333 SIP_SEND_CSTA_MONITOR_STOP
,
334 sip
->csta
->monitor_cross_ref_id
);
336 send_sip_request(sip
->gc
,
338 sip
->csta
->dialog
->with
,
339 sip
->csta
->dialog
->with
,
349 sipe_invite_csta_gateway(struct sipe_account_data
*sip
);
353 process_invite_csta_gateway_response(struct sipe_account_data
*sip
,
355 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
357 purple_debug_info("sipe", "process_invite_csta_gateway_response:\n%s\n", msg
->body
? msg
->body
: "");
360 purple_debug_info("sipe", "process_invite_csta_gateway_response: sip->csta is not initializzed, exiting\n");
364 if (!sip
->csta
->dialog
) {
365 purple_debug_info("sipe", "process_invite_csta_gateway_response: GSTA dialog is NULL, exiting\n");
369 sipe_dialog_parse(sip
->csta
->dialog
, msg
, TRUE
);
371 if (msg
->response
>= 200) {
372 /* send ACK to CSTA */
373 sip
->csta
->dialog
->cseq
= 0;
374 send_sip_request(sip
->gc
, "ACK", sip
->csta
->dialog
->with
, sip
->csta
->dialog
->with
, NULL
, NULL
, sip
->csta
->dialog
, NULL
);
375 sip
->csta
->dialog
->outgoing_invite
= NULL
;
376 sip
->csta
->dialog
->is_established
= TRUE
;
379 if (msg
->response
>= 400) {
380 purple_debug_info("sipe", "process_invite_csta_gateway_response: INVITE response is not 200. Failed to join CSTA.\n");
381 /* @TODO notify user of failure to join CSTA */
384 else if (msg
->response
== 200) {
385 xmlnode
*xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
387 g_free(sip
->csta
->gateway_status
);
388 sip
->csta
->gateway_status
= xmlnode_get_data(xmlnode_get_child(xml
, "systemStatus"));
389 purple_debug_info("sipe", "process_invite_csta_gateway_response: gateway_status=%s\n",
390 sip
->csta
->gateway_status
? sip
->csta
->gateway_status
: "");
391 if (sipe_strcase_equal(sip
->csta
->gateway_status
, "normal")) {
392 if (!sip
->csta
->monitor_cross_ref_id
) {
393 sip_csta_get_features(sip
);
394 sip_csta_monitor_start(sip
);
397 purple_debug_info("sipe", "process_invite_csta_gateway_response: ERROR: CSTA status is %s, won't continue.\n",
398 sip
->csta
->gateway_status
);
399 /* @TODO notify user of failure to join CSTA */
403 /* schedule re-invite. RFC4028 */
404 if (sip
->csta
->dialog
->expires
) {
405 sipe_schedule_action("<+csta>",
406 sip
->csta
->dialog
->expires
- 60, /* 1 minute earlier */
407 (Action
)sipe_invite_csta_gateway
,
417 /** Creates long living dialog with SIP/CSTA Gateway */
418 /* should be re-entrant as require to sent re-invites every 10 min to refresh */
420 sipe_invite_csta_gateway(struct sipe_account_data
*sip
)
427 purple_debug_info("sipe", "sipe_invite_csta_gateway: sip->csta is uninitialized, exiting\n");
431 if(!sip
->csta
->dialog
) {
432 sip
->csta
->dialog
= g_new0(struct sip_dialog
, 1);
433 sip
->csta
->dialog
->callid
= gencallid();
434 sip
->csta
->dialog
->with
= g_strdup(sip
->csta
->gateway_uri
);
436 if (!(sip
->csta
->dialog
->ourtag
)) {
437 sip
->csta
->dialog
->ourtag
= gentag();
440 contact
= get_contact(sip
);
441 hdr
= g_strdup_printf(
443 "Supported: timer\r\n"
444 "Content-Disposition: signal;handling=required\r\n"
445 "Content-Type: application/csta+xml\r\n",
449 body
= g_strdup_printf(
450 SIP_SEND_CSTA_REQUEST_SYSTEM_STATUS
,
451 sip
->csta
->line_uri
);
453 sip
->csta
->dialog
->outgoing_invite
= send_sip_request(sip
->gc
,
455 sip
->csta
->dialog
->with
,
456 sip
->csta
->dialog
->with
,
460 process_invite_csta_gateway_response
);
466 sip_csta_open(struct sipe_account_data
*sip
,
467 const gchar
*line_uri
,
470 sip_csta_initialize(sip
, line_uri
, server
);
471 sipe_invite_csta_gateway(sip
);
475 sip_csta_free(struct sip_csta
*csta
)
479 g_free(csta
->line_uri
);
480 g_free(csta
->gateway_uri
);
482 sipe_dialog_free(csta
->dialog
);
484 g_free(csta
->gateway_status
);
485 g_free(csta
->monitor_cross_ref_id
);
486 g_free(csta
->line_status
);
487 g_free(csta
->to_tel_uri
);
488 g_free(csta
->call_id
);
489 g_free(csta
->device_id
);
495 sip_csta_close(struct sipe_account_data
*sip
)
498 sip_csta_monitor_stop(sip
);
501 if (sip
->csta
&& sip
->csta
->dialog
) {
502 /* send BYE to CSTA */
503 send_sip_request(sip
->gc
,
505 sip
->csta
->dialog
->with
,
506 sip
->csta
->dialog
->with
,
513 sip_csta_free(sip
->csta
);
518 /** Make Call's callback */
520 process_csta_make_call_response(struct sipe_account_data
*sip
,
522 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
524 purple_debug_info("sipe", "process_csta_make_call_response:\n%s\n", msg
->body
? msg
->body
: "");
527 purple_debug_info("sipe", "process_csta_make_call_response: sip->csta is not initializzed, exiting\n");
531 if (msg
->response
>= 400) {
532 purple_debug_info("sipe", "process_csta_make_call_response: Make Call response is not 200. Failed to make call.\n");
533 /* @TODO notify user of failure to make call */
536 else if (msg
->response
== 200) {
538 xmlnode
*xn_calling_device
;
541 purple_debug_info("sipe", "process_csta_make_call_response: SUCCESS\n");
543 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
544 xn_calling_device
= xmlnode_get_child(xml
, "callingDevice");
545 device_id
= xmlnode_get_data(xmlnode_get_child(xn_calling_device
, "deviceID"));
546 if (sipe_strequal(sip
->csta
->line_uri
, device_id
)) {
547 g_free(sip
->csta
->call_id
);
548 sip
->csta
->call_id
= xmlnode_get_data(xmlnode_get_child(xn_calling_device
, "callID"));
549 purple_debug_info("sipe", "process_csta_make_call_response: call_id=%s\n", sip
->csta
->call_id
? sip
->csta
->call_id
: "");
560 sip_csta_make_call(struct sipe_account_data
*sip
,
561 const gchar
* to_tel_uri
)
567 purple_debug_info("sipe", "sip_csta_make_call: no tel URI parameter provided, exiting.\n");
571 if (!sip
->csta
|| !sip
->csta
->dialog
|| !sip
->csta
->dialog
->is_established
) {
572 purple_debug_info("sipe", "sip_csta_make_call: no dialog with CSTA, exiting.\n");
576 g_free(sip
->csta
->to_tel_uri
);
577 sip
->csta
->to_tel_uri
= g_strdup(to_tel_uri
);
580 "Content-Disposition: signal;handling=required\r\n"
581 "Content-Type: application/csta+xml\r\n");
583 body
= g_strdup_printf(
584 SIP_SEND_CSTA_MAKE_CALL
,
586 sip
->csta
->to_tel_uri
);
588 send_sip_request(sip
->gc
,
590 sip
->csta
->dialog
->with
,
591 sip
->csta
->dialog
->with
,
595 process_csta_make_call_response
);
601 sip_csta_update_id_and_status(struct sip_csta
*csta
,
605 gchar
*call_id
= xmlnode_get_data(xmlnode_get_child(node
, "callID"));
607 if (!sipe_strequal(call_id
, csta
->call_id
)) {
608 purple_debug_info("sipe", "sipe_csta_update_id_and_status: callID (%s) does not match\n", call_id
);
612 /* free old line status */
613 g_free(csta
->line_status
);
614 csta
->line_status
= NULL
;
619 gchar
*device_id
= xmlnode_get_data(xmlnode_get_child(node
, "deviceID"));
620 purple_debug_info("sipe", "sipe_csta_update_id_and_status: device_id=(%s)\n", device_id
? device_id
: "");
622 g_free(csta
->device_id
);
623 csta
->device_id
= device_id
;
626 /* set new line status */
627 csta
->line_status
= g_strdup(status
);
631 /* clean up cleared connection */
632 g_free(csta
->to_tel_uri
);
633 csta
->to_tel_uri
= NULL
;
634 g_free(csta
->call_id
);
635 csta
->call_id
= NULL
;
636 g_free(csta
->device_id
);
637 csta
->device_id
= NULL
;
645 process_incoming_info_csta(struct sipe_account_data
*sip
,
648 gchar
*monitor_cross_ref_id
;
649 xmlnode
*xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
653 monitor_cross_ref_id
= xmlnode_get_data(xmlnode_get_child(xml
, "monitorCrossRefID"));
655 if(!sip
->csta
|| !sipe_strequal(monitor_cross_ref_id
, sip
->csta
->monitor_cross_ref_id
))
657 purple_debug_info("sipe", "process_incoming_info_csta: monitorCrossRefID (%s) does not match, exiting\n",
658 monitor_cross_ref_id
? monitor_cross_ref_id
: "");
662 if (sipe_strequal(xml
->name
, "OriginatedEvent"))
664 sip_csta_update_id_and_status(sip
->csta
,
665 xmlnode_get_child(xml
, "originatedConnection"),
666 ORIGINATED_CSTA_STATUS
);
668 else if (sipe_strequal(xml
->name
, "DeliveredEvent"))
670 sip_csta_update_id_and_status(sip
->csta
,
671 xmlnode_get_child(xml
, "connection"),
672 DELIVERED_CSTA_STATUS
);
674 else if (sipe_strequal(xml
->name
, "EstablishedEvent"))
676 sip_csta_update_id_and_status(sip
->csta
,
677 xmlnode_get_child(xml
, "establishedConnection"),
678 ESTABLISHED_CSTA_STATUS
);
680 else if (sipe_strequal(xml
->name
, "ConnectionClearedEvent"))
682 sip_csta_update_id_and_status(sip
->csta
,
683 xmlnode_get_child(xml
, "droppedConnection"),
688 g_free(monitor_cross_ref_id
);