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-common.h"
39 #include "sipe-backend-debug.h"
40 #include "sipe-dialog.h"
41 #include "sipe-utils.h"
45 #define ORIGINATED_CSTA_STATUS "originated"
46 #define DELIVERED_CSTA_STATUS "delivered"
47 #define ESTABLISHED_CSTA_STATUS "established"
51 * Sends CSTA RequestSystemStatus request to SIP/CSTA Gateway.
52 * @param line_uri (%s) Ex.: tel:73124;phone-context=dialstring;partition=BE_BRS_INT
54 #define SIP_SEND_CSTA_REQUEST_SYSTEM_STATUS \
55 "<?xml version=\"1.0\"?>"\
56 "<RequestSystemStatus xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
60 "<lcs:line xmlns:lcs=\"http://schemas.microsoft.com/Lcs/2005/04/RCCExtension\">%s</lcs:line>"\
64 "</RequestSystemStatus>"
67 * Sends CSTA GetCSTAFeatures request to SIP/CSTA Gateway.
68 * @param line_uri (%s) Ex.: tel:73124;phone-context=dialstring;partition=BE_BRS_INT
70 #define SIP_SEND_CSTA_GET_CSTA_FEATURES \
71 "<?xml version=\"1.0\"?>"\
72 "<GetCSTAFeatures xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
76 "<lcs:line xmlns:lcs=\"http://schemas.microsoft.com/Lcs/2005/04/RCCExtension\">%s</lcs:line>"\
83 * Sends CSTA start monitor request to SIP/CSTA Gateway.
84 * @param line_uri (%s) Ex.: tel:73124;phone-context=dialstring;partition=BE_BRS_INT
86 #define SIP_SEND_CSTA_MONITOR_START \
87 "<?xml version=\"1.0\"?>"\
88 "<MonitorStart xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
90 "<deviceObject>%s</deviceObject>"\
95 * Sends CSTA stop monitor request to SIP/CSTA Gateway.
96 * @param monitor_cross_ref_id (%s) Ex.: 99fda87c
98 #define SIP_SEND_CSTA_MONITOR_STOP \
99 "<?xml version=\"1.0\"?>"\
100 "<MonitorStop xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
101 "<monitorCrossRefID>%s</monitorCrossRefID>"\
105 * Sends CSTA make call request to SIP/CSTA Gateway.
106 * @param line_uri (%s) Ex.: tel:73124;phone-context=dialstring;partition=BE_BRS_INT
107 * @param to_tel_uri (%s) Ex.: tel:+3222220220
109 #define SIP_SEND_CSTA_MAKE_CALL \
110 "<?xml version=\"1.0\"?>"\
111 "<MakeCall xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
112 "<callingDevice>%s</callingDevice>"\
113 "<calledDirectoryNumber>%s</calledDirectoryNumber>"\
114 "<autoOriginate>doNotPrompt</autoOriginate>"\
118 * Sends CSTA ClearConnection request to SIP/CSTA Gateway.
119 * @param call_id (%s) Ex.: 0_99f261b4
120 * @param device_id (%s) Same as in OriginatedEvent, DeliveredEvent notifications.
121 * Ex.: tel:73124;phone-context=dialstring
123 #define SIP_SEND_CSTA_CLEAR_CONNECTION \
124 "<?xml version=\"1.0\"?>"\
125 "<ClearConnection xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
126 "<connectionToBeCleared>"\
127 "<callID>%s</callID>"\
128 "<deviceID>%s</deviceID>"\
129 "</connectionToBeCleared>"\
134 sip_to_tel_uri0(const gchar
*phone
)
136 if (!phone
|| strlen(phone
) == 0) return NULL
;
138 if (g_str_has_prefix(phone
, "tel:")) {
139 return g_strdup(phone
);
141 gchar
*tel_uri
= g_malloc(strlen(phone
) + 4 + 1);
142 gchar
*dest_p
= g_stpcpy(tel_uri
, "tel:");
143 for (; *phone
; phone
++) {
144 if (*phone
== ' ') continue;
145 if (*phone
== '(') continue;
146 if (*phone
== ')') continue;
147 if (*phone
== '-') continue;
148 if (*phone
== '.') continue;
157 sip_to_tel_uri(const gchar
*phone
)
159 gchar
*res
= sip_to_tel_uri0(phone
);
161 /* strips everything starting with 'v:' if any */
162 if (res
&& (v
= strstr(res
, "v:"))) {
165 res
= g_strndup(res
, v
- res
);
173 sip_tel_uri_denormalize(const gchar
*tel_uri
)
175 if (!tel_uri
) return NULL
;
177 if (g_str_has_prefix(tel_uri
, "tel:")) {
178 return g_strdup(tel_uri
+ 4);
180 return g_strdup(tel_uri
);
185 sip_csta_initialize(struct sipe_account_data
*sip
,
186 const gchar
*line_uri
,
190 sip
->csta
= g_new0(struct sip_csta
, 1);
191 sip
->csta
->line_uri
= g_strdup(line_uri
);
192 sip
->csta
->gateway_uri
= g_strdup(server
);
194 SIPE_DEBUG_INFO("sip_csta_initialize: sip->csta is already instantiated, exiting.%s", "");
198 /** get CSTA feautures's callback */
200 process_csta_get_features_response(SIPE_UNUSED_PARAMETER
struct sipe_account_data
*sip
,
202 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
204 if (msg
->response
>= 400) {
205 SIPE_DEBUG_INFO("process_csta_get_features_response: Get CSTA features response is not 200. Failed to get features.%s", "");
206 /* @TODO notify user of failure to get CSTA features */
209 else if (msg
->response
== 200) {
210 SIPE_DEBUG_INFO("process_csta_get_features_response:\n%s", msg
->body
? msg
->body
: "");
216 /** get CSTA feautures */
218 sip_csta_get_features(struct sipe_account_data
*sip
)
223 if (!sip
->csta
|| !sip
->csta
->dialog
|| !sip
->csta
->dialog
->is_established
) {
224 SIPE_DEBUG_INFO("sip_csta_get_features: no dialog with CSTA, exiting.%s", "");
229 "Content-Disposition: signal;handling=required\r\n"
230 "Content-Type: application/csta+xml\r\n");
232 body
= g_strdup_printf(
233 SIP_SEND_CSTA_GET_CSTA_FEATURES
,
234 sip
->csta
->line_uri
);
236 send_sip_request(sip
->gc
,
238 sip
->csta
->dialog
->with
,
239 sip
->csta
->dialog
->with
,
243 process_csta_get_features_response
);
248 /** Monitor Start's callback */
250 process_csta_monitor_start_response(struct sipe_account_data
*sip
,
252 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
254 SIPE_DEBUG_INFO("process_csta_monitor_start_response:\n%s", msg
->body
? msg
->body
: "");
257 SIPE_DEBUG_INFO("process_csta_monitor_start_response: sip->csta is not initializzed, exiting%s", "");
261 if (msg
->response
>= 400) {
262 SIPE_DEBUG_INFO("process_csta_monitor_start_response: Monitor Start response is not 200. Failed to start monitor.%s", "");
263 /* @TODO notify user of failure to start monitor */
266 else if (msg
->response
== 200) {
267 sipe_xml
*xml
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
268 g_free(sip
->csta
->monitor_cross_ref_id
);
269 sip
->csta
->monitor_cross_ref_id
= sipe_xml_data(sipe_xml_child(xml
, "monitorCrossRefID"));
270 SIPE_DEBUG_INFO("process_csta_monitor_start_response: monitor_cross_ref_id=%s",
271 sip
->csta
->monitor_cross_ref_id
? sip
->csta
->monitor_cross_ref_id
: "");
280 sip_csta_monitor_start(struct sipe_account_data
*sip
)
285 if (!sip
->csta
|| !sip
->csta
->dialog
|| !sip
->csta
->dialog
->is_established
) {
286 SIPE_DEBUG_INFO("sip_csta_monitor_start: no dialog with CSTA, exiting.%s", "");
291 "Content-Disposition: signal;handling=required\r\n"
292 "Content-Type: application/csta+xml\r\n");
294 body
= g_strdup_printf(
295 SIP_SEND_CSTA_MONITOR_START
,
296 sip
->csta
->line_uri
);
298 send_sip_request(sip
->gc
,
300 sip
->csta
->dialog
->with
,
301 sip
->csta
->dialog
->with
,
305 process_csta_monitor_start_response
);
312 sip_csta_monitor_stop(struct sipe_account_data
*sip
)
317 if (!sip
->csta
|| !sip
->csta
->dialog
|| !sip
->csta
->dialog
->is_established
) {
318 SIPE_DEBUG_INFO("sip_csta_monitor_stop: no dialog with CSTA, exiting.%s", "");
322 if (!sip
->csta
->monitor_cross_ref_id
) {
323 SIPE_DEBUG_INFO("sip_csta_monitor_stop: no monitor_cross_ref_id, exiting.%s", "");
328 "Content-Disposition: signal;handling=required\r\n"
329 "Content-Type: application/csta+xml\r\n");
331 body
= g_strdup_printf(
332 SIP_SEND_CSTA_MONITOR_STOP
,
333 sip
->csta
->monitor_cross_ref_id
);
335 send_sip_request(sip
->gc
,
337 sip
->csta
->dialog
->with
,
338 sip
->csta
->dialog
->with
,
348 sipe_invite_csta_gateway(struct sipe_account_data
*sip
);
352 process_invite_csta_gateway_response(struct sipe_account_data
*sip
,
354 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
356 SIPE_DEBUG_INFO("process_invite_csta_gateway_response:\n%s", msg
->body
? msg
->body
: "");
359 SIPE_DEBUG_INFO("process_invite_csta_gateway_response: sip->csta is not initializzed, exiting%s", "");
363 if (!sip
->csta
->dialog
) {
364 SIPE_DEBUG_INFO("process_invite_csta_gateway_response: GSTA dialog is NULL, exiting%s", "");
368 sipe_dialog_parse(sip
->csta
->dialog
, msg
, TRUE
);
370 if (msg
->response
>= 200) {
371 /* send ACK to CSTA */
372 sip
->csta
->dialog
->cseq
= 0;
373 send_sip_request(sip
->gc
, "ACK", sip
->csta
->dialog
->with
, sip
->csta
->dialog
->with
, NULL
, NULL
, sip
->csta
->dialog
, NULL
);
374 sip
->csta
->dialog
->outgoing_invite
= NULL
;
375 sip
->csta
->dialog
->is_established
= TRUE
;
378 if (msg
->response
>= 400) {
379 SIPE_DEBUG_INFO("process_invite_csta_gateway_response: INVITE response is not 200. Failed to join CSTA.%s", "");
380 /* @TODO notify user of failure to join CSTA */
383 else if (msg
->response
== 200) {
384 sipe_xml
*xml
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
386 g_free(sip
->csta
->gateway_status
);
387 sip
->csta
->gateway_status
= sipe_xml_data(sipe_xml_child(xml
, "systemStatus"));
388 SIPE_DEBUG_INFO("process_invite_csta_gateway_response: gateway_status=%s",
389 sip
->csta
->gateway_status
? sip
->csta
->gateway_status
: "");
390 if (sipe_strcase_equal(sip
->csta
->gateway_status
, "normal")) {
391 if (!sip
->csta
->monitor_cross_ref_id
) {
392 sip_csta_get_features(sip
);
393 sip_csta_monitor_start(sip
);
396 SIPE_DEBUG_INFO("process_invite_csta_gateway_response: ERROR: CSTA status is %s, won't continue.",
397 sip
->csta
->gateway_status
);
398 /* @TODO notify user of failure to join CSTA */
402 /* schedule re-invite. RFC4028 */
403 if (sip
->csta
->dialog
->expires
) {
404 sipe_schedule_action("<+csta>",
405 sip
->csta
->dialog
->expires
- 60, /* 1 minute earlier */
406 (Action
)sipe_invite_csta_gateway
,
416 /** Creates long living dialog with SIP/CSTA Gateway */
417 /* should be re-entrant as require to sent re-invites every 10 min to refresh */
419 sipe_invite_csta_gateway(struct sipe_account_data
*sip
)
426 SIPE_DEBUG_INFO("sipe_invite_csta_gateway: sip->csta is uninitialized, exiting%s", "");
430 if(!sip
->csta
->dialog
) {
431 sip
->csta
->dialog
= g_new0(struct sip_dialog
, 1);
432 sip
->csta
->dialog
->callid
= gencallid();
433 sip
->csta
->dialog
->with
= g_strdup(sip
->csta
->gateway_uri
);
435 if (!(sip
->csta
->dialog
->ourtag
)) {
436 sip
->csta
->dialog
->ourtag
= gentag();
439 contact
= get_contact(sip
);
440 hdr
= g_strdup_printf(
442 "Supported: timer\r\n"
443 "Content-Disposition: signal;handling=required\r\n"
444 "Content-Type: application/csta+xml\r\n",
448 body
= g_strdup_printf(
449 SIP_SEND_CSTA_REQUEST_SYSTEM_STATUS
,
450 sip
->csta
->line_uri
);
452 sip
->csta
->dialog
->outgoing_invite
= send_sip_request(sip
->gc
,
454 sip
->csta
->dialog
->with
,
455 sip
->csta
->dialog
->with
,
459 process_invite_csta_gateway_response
);
465 sip_csta_open(struct sipe_account_data
*sip
,
466 const gchar
*line_uri
,
469 sip_csta_initialize(sip
, line_uri
, server
);
470 sipe_invite_csta_gateway(sip
);
474 sip_csta_free(struct sip_csta
*csta
)
478 g_free(csta
->line_uri
);
479 g_free(csta
->gateway_uri
);
481 sipe_dialog_free(csta
->dialog
);
483 g_free(csta
->gateway_status
);
484 g_free(csta
->monitor_cross_ref_id
);
485 g_free(csta
->line_status
);
486 g_free(csta
->to_tel_uri
);
487 g_free(csta
->call_id
);
488 g_free(csta
->device_id
);
494 sip_csta_close(struct sipe_account_data
*sip
)
497 sip_csta_monitor_stop(sip
);
500 if (sip
->csta
&& sip
->csta
->dialog
) {
501 /* send BYE to CSTA */
502 send_sip_request(sip
->gc
,
504 sip
->csta
->dialog
->with
,
505 sip
->csta
->dialog
->with
,
512 sip_csta_free(sip
->csta
);
517 /** Make Call's callback */
519 process_csta_make_call_response(struct sipe_account_data
*sip
,
521 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
523 SIPE_DEBUG_INFO("process_csta_make_call_response:\n%s", msg
->body
? msg
->body
: "");
526 SIPE_DEBUG_INFO("process_csta_make_call_response: sip->csta is not initializzed, exiting%s", "");
530 if (msg
->response
>= 400) {
531 SIPE_DEBUG_INFO("process_csta_make_call_response: Make Call response is not 200. Failed to make call.%s", "");
532 /* @TODO notify user of failure to make call */
535 else if (msg
->response
== 200) {
537 const sipe_xml
*xn_calling_device
;
540 SIPE_DEBUG_INFO("process_csta_make_call_response: SUCCESS%s", "");
542 xml
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
543 xn_calling_device
= sipe_xml_child(xml
, "callingDevice");
544 device_id
= sipe_xml_data(sipe_xml_child(xn_calling_device
, "deviceID"));
545 if (sipe_strequal(sip
->csta
->line_uri
, device_id
)) {
546 g_free(sip
->csta
->call_id
);
547 sip
->csta
->call_id
= sipe_xml_data(sipe_xml_child(xn_calling_device
, "callID"));
548 SIPE_DEBUG_INFO("process_csta_make_call_response: call_id=%s", sip
->csta
->call_id
? sip
->csta
->call_id
: "");
559 sip_csta_make_call(struct sipe_account_data
*sip
,
560 const gchar
* to_tel_uri
)
566 SIPE_DEBUG_INFO("sip_csta_make_call: no tel URI parameter provided, exiting.%s", "");
570 if (!sip
->csta
|| !sip
->csta
->dialog
|| !sip
->csta
->dialog
->is_established
) {
571 SIPE_DEBUG_INFO("sip_csta_make_call: no dialog with CSTA, exiting.%s", "");
575 g_free(sip
->csta
->to_tel_uri
);
576 sip
->csta
->to_tel_uri
= g_strdup(to_tel_uri
);
579 "Content-Disposition: signal;handling=required\r\n"
580 "Content-Type: application/csta+xml\r\n");
582 body
= g_strdup_printf(
583 SIP_SEND_CSTA_MAKE_CALL
,
585 sip
->csta
->to_tel_uri
);
587 send_sip_request(sip
->gc
,
589 sip
->csta
->dialog
->with
,
590 sip
->csta
->dialog
->with
,
594 process_csta_make_call_response
);
600 sip_csta_update_id_and_status(struct sip_csta
*csta
,
601 const sipe_xml
*node
,
604 gchar
*call_id
= sipe_xml_data(sipe_xml_child(node
, "callID"));
606 if (!sipe_strequal(call_id
, csta
->call_id
)) {
607 SIPE_DEBUG_INFO("sipe_csta_update_id_and_status: callID (%s) does not match", call_id
);
611 /* free old line status */
612 g_free(csta
->line_status
);
613 csta
->line_status
= NULL
;
618 gchar
*device_id
= sipe_xml_data(sipe_xml_child(node
, "deviceID"));
619 SIPE_DEBUG_INFO("sipe_csta_update_id_and_status: device_id=(%s)", device_id
? device_id
: "");
621 g_free(csta
->device_id
);
622 csta
->device_id
= device_id
;
625 /* set new line status */
626 csta
->line_status
= g_strdup(status
);
630 /* clean up cleared connection */
631 g_free(csta
->to_tel_uri
);
632 csta
->to_tel_uri
= NULL
;
633 g_free(csta
->call_id
);
634 csta
->call_id
= NULL
;
635 g_free(csta
->device_id
);
636 csta
->device_id
= NULL
;
644 process_incoming_info_csta(struct sipe_account_data
*sip
,
647 gchar
*monitor_cross_ref_id
;
648 sipe_xml
*xml
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
652 monitor_cross_ref_id
= sipe_xml_data(sipe_xml_child(xml
, "monitorCrossRefID"));
654 if(!sip
->csta
|| !sipe_strequal(monitor_cross_ref_id
, sip
->csta
->monitor_cross_ref_id
))
656 SIPE_DEBUG_INFO("process_incoming_info_csta: monitorCrossRefID (%s) does not match, exiting",
657 monitor_cross_ref_id
? monitor_cross_ref_id
: "");
661 if (sipe_strequal(sipe_xml_name(xml
), "OriginatedEvent"))
663 sip_csta_update_id_and_status(sip
->csta
,
664 sipe_xml_child(xml
, "originatedConnection"),
665 ORIGINATED_CSTA_STATUS
);
667 else if (sipe_strequal(sipe_xml_name(xml
), "DeliveredEvent"))
669 sip_csta_update_id_and_status(sip
->csta
,
670 sipe_xml_child(xml
, "connection"),
671 DELIVERED_CSTA_STATUS
);
673 else if (sipe_strequal(sipe_xml_name(xml
), "EstablishedEvent"))
675 sip_csta_update_id_and_status(sip
->csta
,
676 sipe_xml_child(xml
, "establishedConnection"),
677 ESTABLISHED_CSTA_STATUS
);
679 else if (sipe_strequal(sipe_xml_name(xml
), "ConnectionClearedEvent"))
681 sip_csta_update_id_and_status(sip
->csta
,
682 sipe_xml_child(xml
, "droppedConnection"),
687 g_free(monitor_cross_ref_id
);