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_uri(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_tel_uri_denormalize(const gchar
*tel_uri
)
154 if (!tel_uri
) return NULL
;
156 if (g_str_has_prefix(tel_uri
, "tel:")) {
157 return g_strdup(tel_uri
+ 4);
159 return g_strdup(tel_uri
);
164 sip_csta_initialize(struct sipe_account_data
*sip
,
165 const gchar
*line_uri
,
169 sip
->csta
= g_new0(struct sip_csta
, 1);
170 sip
->csta
->line_uri
= g_strdup(line_uri
);
171 sip
->csta
->gateway_uri
= g_strdup(server
);
173 purple_debug_info("sipe", "sip_csta_initialize: sip->csta is already instantiated, exiting.\n");
177 /** get CSTA feautures's callback */
179 process_csta_get_features_response(SIPE_UNUSED_PARAMETER
struct sipe_account_data
*sip
,
181 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
183 if (msg
->response
>= 400) {
184 purple_debug_info("sipe", "process_csta_get_features_response: Get CSTA features response is not 200. Failed to get features.\n");
185 /* @TODO notify user of failure to get CSTA features */
188 else if (msg
->response
== 200) {
189 purple_debug_info("sipe", "process_csta_get_features_response:\n%s\n", msg
->body
? msg
->body
: "");
195 /** get CSTA feautures */
197 sip_csta_get_features(struct sipe_account_data
*sip
)
202 if (!sip
->csta
|| !sip
->csta
->dialog
|| !sip
->csta
->dialog
->is_established
) {
203 purple_debug_info("sipe", "sip_csta_get_features: no dialog with CSTA, exiting.\n");
208 "Content-Disposition: signal;handling=required\r\n"
209 "Content-Type: application/csta+xml\r\n");
211 body
= g_strdup_printf(
212 SIP_SEND_CSTA_GET_CSTA_FEATURES
,
213 sip
->csta
->line_uri
);
215 send_sip_request(sip
->gc
,
217 sip
->csta
->dialog
->with
,
218 sip
->csta
->dialog
->with
,
222 process_csta_get_features_response
);
227 /** Monitor Start's callback */
229 process_csta_monitor_start_response(struct sipe_account_data
*sip
,
231 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
233 purple_debug_info("sipe", "process_csta_monitor_start_response:\n%s\n", msg
->body
? msg
->body
: "");
236 purple_debug_info("sipe", "process_csta_monitor_start_response: sip->csta is not initializzed, exiting\n");
240 if (msg
->response
>= 400) {
241 purple_debug_info("sipe", "process_csta_monitor_start_response: Monitor Start response is not 200. Failed to start monitor.\n");
242 /* @TODO notify user of failure to start monitor */
245 else if (msg
->response
== 200) {
246 xmlnode
*xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
247 g_free(sip
->csta
->monitor_cross_ref_id
);
248 sip
->csta
->monitor_cross_ref_id
= xmlnode_get_data(xmlnode_get_child(xml
, "monitorCrossRefID"));
249 purple_debug_info("sipe", "process_csta_monitor_start_response: monitor_cross_ref_id=%s\n",
250 sip
->csta
->monitor_cross_ref_id
? sip
->csta
->monitor_cross_ref_id
: "");
259 sip_csta_monitor_start(struct sipe_account_data
*sip
)
264 if (!sip
->csta
|| !sip
->csta
->dialog
|| !sip
->csta
->dialog
->is_established
) {
265 purple_debug_info("sipe", "sip_csta_monitor_start: no dialog with CSTA, exiting.\n");
270 "Content-Disposition: signal;handling=required\r\n"
271 "Content-Type: application/csta+xml\r\n");
273 body
= g_strdup_printf(
274 SIP_SEND_CSTA_MONITOR_START
,
275 sip
->csta
->line_uri
);
277 send_sip_request(sip
->gc
,
279 sip
->csta
->dialog
->with
,
280 sip
->csta
->dialog
->with
,
284 process_csta_monitor_start_response
);
291 sip_csta_monitor_stop(struct sipe_account_data
*sip
)
296 if (!sip
->csta
|| !sip
->csta
->dialog
|| !sip
->csta
->dialog
->is_established
) {
297 purple_debug_info("sipe", "sip_csta_monitor_stop: no dialog with CSTA, exiting.\n");
302 "Content-Disposition: signal;handling=required\r\n"
303 "Content-Type: application/csta+xml\r\n");
305 body
= g_strdup_printf(
306 SIP_SEND_CSTA_MONITOR_STOP
,
307 sip
->csta
->monitor_cross_ref_id
);
309 send_sip_request(sip
->gc
,
311 sip
->csta
->dialog
->with
,
312 sip
->csta
->dialog
->with
,
323 process_invite_csta_gateway_response(struct sipe_account_data
*sip
,
325 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
327 purple_debug_info("sipe", "process_invite_csta_gateway_response:\n%s\n", msg
->body
? msg
->body
: "");
330 purple_debug_info("sipe", "process_invite_csta_gateway_response: sip->csta is not initializzed, exiting\n");
334 if (!sip
->csta
->dialog
) {
335 purple_debug_info("sipe", "process_invite_csta_gateway_response: GSTA dialog is NULL, exiting\n");
339 sipe_dialog_parse(sip
->csta
->dialog
, msg
, TRUE
);
341 if (msg
->response
>= 200) {
342 /* send ACK to CSTA */
343 sip
->csta
->dialog
->cseq
= 0;
344 send_sip_request(sip
->gc
, "ACK", sip
->csta
->dialog
->with
, sip
->csta
->dialog
->with
, NULL
, NULL
, sip
->csta
->dialog
, NULL
);
345 sip
->csta
->dialog
->outgoing_invite
= NULL
;
346 sip
->csta
->dialog
->is_established
= TRUE
;
349 if (msg
->response
>= 400) {
350 purple_debug_info("sipe", "process_invite_csta_gateway_response: INVITE response is not 200. Failed to join CSTA.\n");
351 /* @TODO notify user of failure to join CSTA */
354 else if (msg
->response
== 200) {
355 xmlnode
*xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
356 g_free(sip
->csta
->gateway_status
);
357 sip
->csta
->gateway_status
= xmlnode_get_data(xmlnode_get_child(xml
, "systemStatus"));
358 purple_debug_info("sipe", "process_invite_csta_gateway_response: gateway_status=%s\n",
359 sip
->csta
->gateway_status
? sip
->csta
->gateway_status
: "");
360 if (!g_ascii_strcasecmp(sip
->csta
->gateway_status
, "normal")) {
361 sip_csta_get_features(sip
);
362 sip_csta_monitor_start(sip
);
364 purple_debug_info("sipe", "process_invite_csta_gateway_response: ERRIR: CSTA status is %s, won't continue.\n",
365 sip
->csta
->gateway_status
);
366 /* @TODO notify user of failure to join CSTA */
374 /** Creates long living dialog with SIP/CSTA Gateway */
375 /* should be re-entrant as require to sent re-invites every 10 min to refresh */
377 sipe_invite_csta_gateway(struct sipe_account_data
*sip
)
384 purple_debug_info("sipe", "sipe_invite_csta_gateway: sip->csta is uninitialized, exiting\n");
388 if(!sip
->csta
->dialog
) {
389 sip
->csta
->dialog
= g_new0(struct sip_dialog
, 1);
390 sip
->csta
->dialog
->callid
= gencallid();
391 sip
->csta
->dialog
->with
= g_strdup(sip
->csta
->gateway_uri
);
393 if (!(sip
->csta
->dialog
->ourtag
)) {
394 sip
->csta
->dialog
->ourtag
= gentag();
397 contact
= get_contact(sip
);
398 hdr
= g_strdup_printf(
400 "Content-Disposition: signal;handling=required\r\n"
401 "Content-Type: application/csta+xml\r\n",
405 body
= g_strdup_printf(
406 SIP_SEND_CSTA_REQUEST_SYSTEM_STATUS
,
407 sip
->csta
->line_uri
);
409 sip
->csta
->dialog
->outgoing_invite
= send_sip_request(sip
->gc
,
411 sip
->csta
->dialog
->with
,
412 sip
->csta
->dialog
->with
,
416 process_invite_csta_gateway_response
);
422 sip_csta_open(struct sipe_account_data
*sip
,
423 const gchar
*line_uri
,
426 sip_csta_initialize(sip
, line_uri
, server
);
427 sipe_invite_csta_gateway(sip
);
431 sip_csta_free(struct sip_csta
*csta
)
435 g_free(csta
->line_uri
);
436 g_free(csta
->gateway_uri
);
438 sipe_dialog_free(csta
->dialog
);
440 g_free(csta
->gateway_status
);
441 g_free(csta
->monitor_cross_ref_id
);
442 g_free(csta
->line_status
);
443 g_free(csta
->to_tel_uri
);
444 g_free(csta
->call_id
);
445 g_free(csta
->device_id
);
451 sip_csta_close(struct sipe_account_data
*sip
)
454 sip_csta_monitor_stop(sip
);
457 if (sip
->csta
&& sip
->csta
->dialog
) {
458 /* send BYE to CSTA */
459 send_sip_request(sip
->gc
,
461 sip
->csta
->dialog
->with
,
462 sip
->csta
->dialog
->with
,
469 sip_csta_free(sip
->csta
);
474 /** Make Call's callback */
476 process_csta_make_call_response(struct sipe_account_data
*sip
,
478 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
480 purple_debug_info("sipe", "process_csta_make_call_response:\n%s\n", msg
->body
? msg
->body
: "");
483 purple_debug_info("sipe", "process_csta_make_call_response: sip->csta is not initializzed, exiting\n");
487 if (msg
->response
>= 400) {
488 purple_debug_info("sipe", "process_csta_make_call_response: Make Call response is not 200. Failed to make call.\n");
489 /* @TODO notify user of failure to make call */
492 else if (msg
->response
== 200) {
494 xmlnode
*xn_calling_device
;
497 purple_debug_info("sipe", "process_csta_make_call_response: SUCCESS\n");
499 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
500 xn_calling_device
= xmlnode_get_child(xml
, "callingDevice");
501 device_id
= xmlnode_get_data(xmlnode_get_child(xn_calling_device
, "deviceID"));
502 if (!strcmp(sip
->csta
->line_uri
, device_id
)) {
503 g_free(sip
->csta
->call_id
);
504 sip
->csta
->call_id
= xmlnode_get_data(xmlnode_get_child(xn_calling_device
, "callID"));
505 purple_debug_info("sipe", "process_csta_make_call_response: call_id=%s\n", sip
->csta
->call_id
? sip
->csta
->call_id
: "");
516 sip_csta_make_call(struct sipe_account_data
*sip
,
517 const gchar
* to_tel_uri
)
523 purple_debug_info("sipe", "sip_csta_make_call: no tel URI parameter provided, exiting.\n");
527 if (!sip
->csta
|| !sip
->csta
->dialog
|| !sip
->csta
->dialog
->is_established
) {
528 purple_debug_info("sipe", "sip_csta_make_call: no dialog with CSTA, exiting.\n");
532 g_free(sip
->csta
->to_tel_uri
);
533 sip
->csta
->to_tel_uri
= g_strdup(to_tel_uri
);
536 "Content-Disposition: signal;handling=required\r\n"
537 "Content-Type: application/csta+xml\r\n");
539 body
= g_strdup_printf(
540 SIP_SEND_CSTA_MAKE_CALL
,
542 sip
->csta
->to_tel_uri
);
544 send_sip_request(sip
->gc
,
546 sip
->csta
->dialog
->with
,
547 sip
->csta
->dialog
->with
,
551 process_csta_make_call_response
);
557 sip_csta_update_id_and_status(struct sip_csta
*csta
,
561 gchar
*call_id
= xmlnode_get_data(xmlnode_get_child(node
, "callID"));
563 if (call_id
&& csta
->call_id
&& strcmp(call_id
, csta
->call_id
)) {
564 purple_debug_info("sipe", "sipe_csta_update_id_and_status: callID (%s) does not match\n", call_id
? call_id
: "");
568 /* free old line status */
569 g_free(csta
->line_status
);
570 csta
->line_status
= NULL
;
575 gchar
*device_id
= xmlnode_get_data(xmlnode_get_child(node
, "deviceID"));
576 purple_debug_info("sipe", "sipe_csta_update_id_and_status: device_id=(%s)\n", device_id
? device_id
: "");
578 g_free(csta
->device_id
);
579 csta
->device_id
= device_id
;
582 /* set new line status */
583 csta
->line_status
= g_strdup(status
);
587 /* clean up cleared connection */
588 g_free(csta
->to_tel_uri
);
589 csta
->to_tel_uri
= NULL
;
590 g_free(csta
->call_id
);
591 csta
->call_id
= NULL
;
592 g_free(csta
->device_id
);
593 csta
->device_id
= NULL
;
601 process_incoming_info_csta(struct sipe_account_data
*sip
,
604 xmlnode
*xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
605 gchar
*monitor_cross_ref_id
= xmlnode_get_data(xmlnode_get_child(xml
, "monitorCrossRefID"));
607 if(!sip
->csta
|| (monitor_cross_ref_id
608 && sip
->csta
->monitor_cross_ref_id
609 && strcmp(monitor_cross_ref_id
, sip
->csta
->monitor_cross_ref_id
)))
611 purple_debug_info("sipe", "process_incoming_info_csta: monitorCrossRefID (%s) does not match, exiting\n",
612 monitor_cross_ref_id
? monitor_cross_ref_id
: "");
616 if (!strcmp(xml
->name
, "OriginatedEvent"))
618 sip_csta_update_id_and_status(sip
->csta
,
619 xmlnode_get_child(xml
, "originatedConnection"),
620 ORIGINATED_CSTA_STATUS
);
622 else if (!strcmp(xml
->name
, "DeliveredEvent"))
624 sip_csta_update_id_and_status(sip
->csta
,
625 xmlnode_get_child(xml
, "connection"),
626 DELIVERED_CSTA_STATUS
);
628 else if (!strcmp(xml
->name
, "EstablishedEvent"))
630 sip_csta_update_id_and_status(sip
->csta
,
631 xmlnode_get_child(xml
, "establishedConnection"),
632 ESTABLISHED_CSTA_STATUS
);
634 else if (!strcmp(xml
->name
, "ConnectionClearedEvent"))
636 sip_csta_update_id_and_status(sip
->csta
,
637 xmlnode_get_child(xml
, "droppedConnection"),
642 g_free(monitor_cross_ref_id
);