6 * Copyright (C) 2010 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2009 pier11 <pier11@operamail.com>
9 * Implements Remote Call Control (RCC) feature for
10 * integration with legacy enterprise PBX (wired telephony) systems.
11 * Should be applicable to 2005 and 2007(R2) systems.
12 * Inderlying XML protocol CSTA is defined in ECMA-323.
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 2 of the License, or
18 * (at your option) any later version.
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, write to the Free Software
27 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
36 #include "sipe-common.h"
39 #include "sip-transport.h"
40 #include "sipe-backend.h"
41 #include "sipe-core.h"
42 #include "sipe-core-private.h"
43 #include "sipe-dialog.h"
44 #include "sipe-schedule.h"
45 #include "sipe-utils.h"
49 #define ORIGINATED_CSTA_STATUS "originated"
50 #define DELIVERED_CSTA_STATUS "delivered"
51 #define ESTABLISHED_CSTA_STATUS "established"
55 * Sends CSTA RequestSystemStatus request to SIP/CSTA Gateway.
56 * @param line_uri (%s) Ex.: tel:73124;phone-context=dialstring;partition=BE_BRS_INT
58 #define SIP_SEND_CSTA_REQUEST_SYSTEM_STATUS \
59 "<?xml version=\"1.0\"?>"\
60 "<RequestSystemStatus xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
64 "<lcs:line xmlns:lcs=\"http://schemas.microsoft.com/Lcs/2005/04/RCCExtension\">%s</lcs:line>"\
68 "</RequestSystemStatus>"
71 * Sends CSTA GetCSTAFeatures request to SIP/CSTA Gateway.
72 * @param line_uri (%s) Ex.: tel:73124;phone-context=dialstring;partition=BE_BRS_INT
74 #define SIP_SEND_CSTA_GET_CSTA_FEATURES \
75 "<?xml version=\"1.0\"?>"\
76 "<GetCSTAFeatures xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
80 "<lcs:line xmlns:lcs=\"http://schemas.microsoft.com/Lcs/2005/04/RCCExtension\">%s</lcs:line>"\
87 * Sends CSTA start monitor request to SIP/CSTA Gateway.
88 * @param line_uri (%s) Ex.: tel:73124;phone-context=dialstring;partition=BE_BRS_INT
90 #define SIP_SEND_CSTA_MONITOR_START \
91 "<?xml version=\"1.0\"?>"\
92 "<MonitorStart xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
94 "<deviceObject>%s</deviceObject>"\
99 * Sends CSTA stop monitor request to SIP/CSTA Gateway.
100 * @param monitor_cross_ref_id (%s) Ex.: 99fda87c
102 #define SIP_SEND_CSTA_MONITOR_STOP \
103 "<?xml version=\"1.0\"?>"\
104 "<MonitorStop xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
105 "<monitorCrossRefID>%s</monitorCrossRefID>"\
109 * Sends CSTA make call request to SIP/CSTA Gateway.
110 * @param line_uri (%s) Ex.: tel:73124;phone-context=dialstring;partition=BE_BRS_INT
111 * @param to_tel_uri (%s) Ex.: tel:+3222220220
113 #define SIP_SEND_CSTA_MAKE_CALL \
114 "<?xml version=\"1.0\"?>"\
115 "<MakeCall xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
116 "<callingDevice>%s</callingDevice>"\
117 "<calledDirectoryNumber>%s</calledDirectoryNumber>"\
118 "<autoOriginate>doNotPrompt</autoOriginate>"\
122 * Sends CSTA ClearConnection request to SIP/CSTA Gateway.
123 * @param call_id (%s) Ex.: 0_99f261b4
124 * @param device_id (%s) Same as in OriginatedEvent, DeliveredEvent notifications.
125 * Ex.: tel:73124;phone-context=dialstring
127 #define SIP_SEND_CSTA_CLEAR_CONNECTION \
128 "<?xml version=\"1.0\"?>"\
129 "<ClearConnection xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">"\
130 "<connectionToBeCleared>"\
131 "<callID>%s</callID>"\
132 "<deviceID>%s</deviceID>"\
133 "</connectionToBeCleared>"\
138 sip_to_tel_uri0(const gchar
*phone
)
140 if (!phone
|| strlen(phone
) == 0) return NULL
;
142 if (g_str_has_prefix(phone
, "tel:")) {
143 return g_strdup(phone
);
145 gchar
*tel_uri
= g_malloc(strlen(phone
) + 4 + 1);
146 gchar
*dest_p
= g_stpcpy(tel_uri
, "tel:");
147 for (; *phone
; phone
++) {
148 if (*phone
== ' ') continue;
149 if (*phone
== '(') continue;
150 if (*phone
== ')') continue;
151 if (*phone
== '-') continue;
152 if (*phone
== '.') continue;
161 sip_to_tel_uri(const gchar
*phone
)
163 gchar
*res
= sip_to_tel_uri0(phone
);
165 /* strips everything starting with 'v:' if any */
166 if (res
&& (v
= strstr(res
, "v:"))) {
169 res
= g_strndup(res
, v
- res
);
177 sip_tel_uri_denormalize(const gchar
*tel_uri
)
179 if (!tel_uri
) return NULL
;
181 if (g_str_has_prefix(tel_uri
, "tel:")) {
182 return g_strdup(tel_uri
+ 4);
184 return g_strdup(tel_uri
);
189 sip_csta_initialize(struct sipe_core_private
*sipe_private
,
190 const gchar
*line_uri
,
193 struct sipe_account_data
*sip
= SIPE_ACCOUNT_DATA_PRIVATE
;
195 sip
->csta
= g_new0(struct sip_csta
, 1);
196 sip
->csta
->line_uri
= g_strdup(line_uri
);
197 sip
->csta
->gateway_uri
= g_strdup(server
);
199 SIPE_DEBUG_INFO_NOFORMAT("sip_csta_initialize: sip->csta is already instantiated, exiting.");
203 /** get CSTA feautures's callback */
205 process_csta_get_features_response(SIPE_UNUSED_PARAMETER
struct sipe_core_private
*sipe_private
,
207 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
209 if (msg
->response
>= 400) {
210 SIPE_DEBUG_INFO_NOFORMAT("process_csta_get_features_response: Get CSTA features response is not 200. Failed to get features.");
211 /* @TODO notify user of failure to get CSTA features */
214 else if (msg
->response
== 200) {
215 SIPE_DEBUG_INFO("process_csta_get_features_response:\n%s", msg
->body
? msg
->body
: "");
221 /** get CSTA feautures */
223 sip_csta_get_features(struct sipe_core_private
*sipe_private
)
225 struct sipe_account_data
*sip
= SIPE_ACCOUNT_DATA_PRIVATE
;
229 if (!sip
->csta
|| !sip
->csta
->dialog
|| !sip
->csta
->dialog
->is_established
) {
230 SIPE_DEBUG_INFO_NOFORMAT("sip_csta_get_features: no dialog with CSTA, exiting.");
235 "Content-Disposition: signal;handling=required\r\n"
236 "Content-Type: application/csta+xml\r\n");
238 body
= g_strdup_printf(
239 SIP_SEND_CSTA_GET_CSTA_FEATURES
,
240 sip
->csta
->line_uri
);
242 sip_transport_info(sipe_private
,
246 process_csta_get_features_response
);
251 /** Monitor Start's callback */
253 process_csta_monitor_start_response(struct sipe_core_private
*sipe_private
,
255 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
257 struct sipe_account_data
*sip
= SIPE_ACCOUNT_DATA_PRIVATE
;
259 SIPE_DEBUG_INFO("process_csta_monitor_start_response:\n%s", msg
->body
? msg
->body
: "");
262 SIPE_DEBUG_INFO_NOFORMAT("process_csta_monitor_start_response: sip->csta is not initializzed, exiting");
266 if (msg
->response
>= 400) {
267 SIPE_DEBUG_INFO_NOFORMAT("process_csta_monitor_start_response: Monitor Start response is not 200. Failed to start monitor.");
268 /* @TODO notify user of failure to start monitor */
271 else if (msg
->response
== 200) {
272 sipe_xml
*xml
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
273 g_free(sip
->csta
->monitor_cross_ref_id
);
274 sip
->csta
->monitor_cross_ref_id
= sipe_xml_data(sipe_xml_child(xml
, "monitorCrossRefID"));
275 SIPE_DEBUG_INFO("process_csta_monitor_start_response: monitor_cross_ref_id=%s",
276 sip
->csta
->monitor_cross_ref_id
? sip
->csta
->monitor_cross_ref_id
: "");
285 sip_csta_monitor_start(struct sipe_core_private
*sipe_private
)
287 struct sipe_account_data
*sip
= SIPE_ACCOUNT_DATA_PRIVATE
;
291 if (!sip
->csta
|| !sip
->csta
->dialog
|| !sip
->csta
->dialog
->is_established
) {
292 SIPE_DEBUG_INFO_NOFORMAT("sip_csta_monitor_start: no dialog with CSTA, exiting.");
297 "Content-Disposition: signal;handling=required\r\n"
298 "Content-Type: application/csta+xml\r\n");
300 body
= g_strdup_printf(
301 SIP_SEND_CSTA_MONITOR_START
,
302 sip
->csta
->line_uri
);
304 sip_transport_info(sipe_private
,
308 process_csta_monitor_start_response
);
315 sip_csta_monitor_stop(struct sipe_core_private
*sipe_private
)
317 struct sipe_account_data
*sip
= SIPE_ACCOUNT_DATA_PRIVATE
;
321 if (!sip
->csta
|| !sip
->csta
->dialog
|| !sip
->csta
->dialog
->is_established
) {
322 SIPE_DEBUG_INFO_NOFORMAT("sip_csta_monitor_stop: no dialog with CSTA, exiting.");
326 if (!sip
->csta
->monitor_cross_ref_id
) {
327 SIPE_DEBUG_INFO_NOFORMAT("sip_csta_monitor_stop: no monitor_cross_ref_id, exiting.");
332 "Content-Disposition: signal;handling=required\r\n"
333 "Content-Type: application/csta+xml\r\n");
335 body
= g_strdup_printf(
336 SIP_SEND_CSTA_MONITOR_STOP
,
337 sip
->csta
->monitor_cross_ref_id
);
339 sip_transport_info(sipe_private
,
349 sipe_invite_csta_gateway(struct sipe_core_private
*sipe_private
,
354 process_invite_csta_gateway_response(struct sipe_core_private
*sipe_private
,
356 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
358 struct sipe_account_data
*sip
= SIPE_ACCOUNT_DATA_PRIVATE
;
360 SIPE_DEBUG_INFO("process_invite_csta_gateway_response:\n%s", msg
->body
? msg
->body
: "");
363 SIPE_DEBUG_INFO_NOFORMAT("process_invite_csta_gateway_response: sip->csta is not initializzed, exiting");
367 if (!sip
->csta
->dialog
) {
368 SIPE_DEBUG_INFO_NOFORMAT("process_invite_csta_gateway_response: GSTA dialog is NULL, exiting");
372 sipe_dialog_parse(sip
->csta
->dialog
, msg
, TRUE
);
374 if (msg
->response
>= 200) {
375 /* send ACK to CSTA */
376 sip
->csta
->dialog
->cseq
= 0;
377 sip_transport_ack(sipe_private
, sip
->csta
->dialog
);
378 sip
->csta
->dialog
->outgoing_invite
= NULL
;
379 sip
->csta
->dialog
->is_established
= TRUE
;
382 if (msg
->response
>= 400) {
383 SIPE_DEBUG_INFO_NOFORMAT("process_invite_csta_gateway_response: INVITE response is not 200. Failed to join CSTA.");
384 /* @TODO notify user of failure to join CSTA */
387 else if (msg
->response
== 200) {
388 sipe_xml
*xml
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
390 g_free(sip
->csta
->gateway_status
);
391 sip
->csta
->gateway_status
= sipe_xml_data(sipe_xml_child(xml
, "systemStatus"));
392 SIPE_DEBUG_INFO("process_invite_csta_gateway_response: gateway_status=%s",
393 sip
->csta
->gateway_status
? sip
->csta
->gateway_status
: "");
394 if (sipe_strcase_equal(sip
->csta
->gateway_status
, "normal")) {
395 if (!sip
->csta
->monitor_cross_ref_id
) {
396 sip_csta_get_features(sipe_private
);
397 sip_csta_monitor_start(sipe_private
);
400 SIPE_DEBUG_INFO("process_invite_csta_gateway_response: ERROR: CSTA status is %s, won't continue.",
401 sip
->csta
->gateway_status
);
402 /* @TODO notify user of failure to join CSTA */
406 /* schedule re-invite. RFC4028 */
407 if (sip
->csta
->dialog
->expires
) {
408 sipe_schedule_seconds(sipe_private
,
411 sip
->csta
->dialog
->expires
- 60, /* 1 minute earlier */
412 sipe_invite_csta_gateway
,
420 /** Creates long living dialog with SIP/CSTA Gateway */
421 /* should be re-entrant as require to sent re-invites every 10 min to refresh */
423 sipe_invite_csta_gateway(struct sipe_core_private
*sipe_private
,
424 SIPE_UNUSED_PARAMETER gpointer unused
)
426 struct sipe_account_data
*sip
= SIPE_ACCOUNT_DATA_PRIVATE
;
432 SIPE_DEBUG_INFO_NOFORMAT("sipe_invite_csta_gateway: sip->csta is uninitialized, exiting");
436 if(!sip
->csta
->dialog
) {
437 sip
->csta
->dialog
= g_new0(struct sip_dialog
, 1);
438 sip
->csta
->dialog
->callid
= gencallid();
439 sip
->csta
->dialog
->with
= g_strdup(sip
->csta
->gateway_uri
);
441 if (!(sip
->csta
->dialog
->ourtag
)) {
442 sip
->csta
->dialog
->ourtag
= gentag();
445 contact
= get_contact(sipe_private
);
446 hdr
= g_strdup_printf(
448 "Supported: timer\r\n"
449 "Content-Disposition: signal;handling=required\r\n"
450 "Content-Type: application/csta+xml\r\n",
454 body
= g_strdup_printf(
455 SIP_SEND_CSTA_REQUEST_SYSTEM_STATUS
,
456 sip
->csta
->line_uri
);
458 sip
->csta
->dialog
->outgoing_invite
=
459 sip_transport_invite(sipe_private
,
463 process_invite_csta_gateway_response
);
469 sip_csta_open(struct sipe_core_private
*sipe_private
,
470 const gchar
*line_uri
,
473 sip_csta_initialize(sipe_private
, line_uri
, server
);
474 sipe_invite_csta_gateway(sipe_private
, NULL
);
478 sip_csta_free(struct sip_csta
*csta
)
482 g_free(csta
->line_uri
);
483 g_free(csta
->gateway_uri
);
485 sipe_dialog_free(csta
->dialog
);
487 g_free(csta
->gateway_status
);
488 g_free(csta
->monitor_cross_ref_id
);
489 g_free(csta
->line_status
);
490 g_free(csta
->to_tel_uri
);
491 g_free(csta
->call_id
);
492 g_free(csta
->device_id
);
498 sip_csta_close(struct sipe_core_private
*sipe_private
)
500 struct sipe_account_data
*sip
= SIPE_ACCOUNT_DATA_PRIVATE
;
502 sip_csta_monitor_stop(sipe_private
);
505 if (sip
->csta
&& sip
->csta
->dialog
) {
506 /* send BYE to CSTA */
507 sip_transport_bye(sipe_private
, sip
->csta
->dialog
);
510 sip_csta_free(sip
->csta
);
515 /** Make Call's callback */
517 process_csta_make_call_response(struct sipe_core_private
*sipe_private
,
519 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
521 struct sipe_account_data
*sip
= SIPE_ACCOUNT_DATA_PRIVATE
;
523 SIPE_DEBUG_INFO("process_csta_make_call_response:\n%s", msg
->body
? msg
->body
: "");
526 SIPE_DEBUG_INFO_NOFORMAT("process_csta_make_call_response: sip->csta is not initializzed, exiting");
530 if (msg
->response
>= 400) {
531 SIPE_DEBUG_INFO_NOFORMAT("process_csta_make_call_response: Make Call response is not 200. Failed to make call.");
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_NOFORMAT("process_csta_make_call_response: SUCCESS");
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_core_private
*sipe_private
,
560 const gchar
* to_tel_uri
)
562 struct sipe_account_data
*sip
= SIPE_ACCOUNT_DATA_PRIVATE
;
567 SIPE_DEBUG_INFO_NOFORMAT("sip_csta_make_call: no tel URI parameter provided, exiting.");
571 if (!sip
->csta
|| !sip
->csta
->dialog
|| !sip
->csta
->dialog
->is_established
) {
572 SIPE_DEBUG_INFO_NOFORMAT("sip_csta_make_call: no dialog with CSTA, exiting.");
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 sip_transport_info(sipe_private
,
592 process_csta_make_call_response
);
598 sip_csta_update_id_and_status(struct sip_csta
*csta
,
599 const sipe_xml
*node
,
602 gchar
*call_id
= sipe_xml_data(sipe_xml_child(node
, "callID"));
604 if (!sipe_strequal(call_id
, csta
->call_id
)) {
605 SIPE_DEBUG_INFO("sipe_csta_update_id_and_status: callID (%s) does not match", call_id
);
609 /* free old line status */
610 g_free(csta
->line_status
);
611 csta
->line_status
= NULL
;
616 gchar
*device_id
= sipe_xml_data(sipe_xml_child(node
, "deviceID"));
617 SIPE_DEBUG_INFO("sipe_csta_update_id_and_status: device_id=(%s)", device_id
? device_id
: "");
619 g_free(csta
->device_id
);
620 csta
->device_id
= device_id
;
623 /* set new line status */
624 csta
->line_status
= g_strdup(status
);
628 /* clean up cleared connection */
629 g_free(csta
->to_tel_uri
);
630 csta
->to_tel_uri
= NULL
;
631 g_free(csta
->call_id
);
632 csta
->call_id
= NULL
;
633 g_free(csta
->device_id
);
634 csta
->device_id
= NULL
;
642 process_incoming_info_csta(struct sipe_core_private
*sipe_private
,
645 struct sipe_account_data
*sip
= SIPE_ACCOUNT_DATA_PRIVATE
;
646 gchar
*monitor_cross_ref_id
;
647 sipe_xml
*xml
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
651 monitor_cross_ref_id
= sipe_xml_data(sipe_xml_child(xml
, "monitorCrossRefID"));
653 if(!sip
->csta
|| !sipe_strequal(monitor_cross_ref_id
, sip
->csta
->monitor_cross_ref_id
))
655 SIPE_DEBUG_INFO("process_incoming_info_csta: monitorCrossRefID (%s) does not match, exiting",
656 monitor_cross_ref_id
? monitor_cross_ref_id
: "");
660 if (sipe_strequal(sipe_xml_name(xml
), "OriginatedEvent"))
662 sip_csta_update_id_and_status(sip
->csta
,
663 sipe_xml_child(xml
, "originatedConnection"),
664 ORIGINATED_CSTA_STATUS
);
666 else if (sipe_strequal(sipe_xml_name(xml
), "DeliveredEvent"))
668 sip_csta_update_id_and_status(sip
->csta
,
669 sipe_xml_child(xml
, "connection"),
670 DELIVERED_CSTA_STATUS
);
672 else if (sipe_strequal(sipe_xml_name(xml
), "EstablishedEvent"))
674 sip_csta_update_id_and_status(sip
->csta
,
675 sipe_xml_child(xml
, "establishedConnection"),
676 ESTABLISHED_CSTA_STATUS
);
678 else if (sipe_strequal(sipe_xml_name(xml
), "ConnectionClearedEvent"))
680 sip_csta_update_id_and_status(sip
->csta
,
681 sipe_xml_child(xml
, "droppedConnection"),
686 g_free(monitor_cross_ref_id
);