Updated to release 1.7.1
[siplcs.git] / src / sip-csta.c
blob6dec854fdd22e8257422ec46712dd107dd881be0
1 /**
2 * @file sip-csta.c
4 * pidgin-sipe
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
29 #include <glib.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include "debug.h"
34 #include "sipe.h"
35 #include "sipe-dialog.h"
36 #include "sip-csta.h"
37 #include "sipe-utils.h"
40 #define ORIGINATED_CSTA_STATUS "originated"
41 #define DELIVERED_CSTA_STATUS "delivered"
42 #define ESTABLISHED_CSTA_STATUS "established"
45 /**
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\">"\
52 "<extensions>"\
53 "<privateData>"\
54 "<private>"\
55 "<lcs:line xmlns:lcs=\"http://schemas.microsoft.com/Lcs/2005/04/RCCExtension\">%s</lcs:line>"\
56 "</private>"\
57 "</privateData>"\
58 "</extensions>"\
59 "</RequestSystemStatus>"
61 /**
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\">"\
68 "<extensions>"\
69 "<privateData>"\
70 "<private>"\
71 "<lcs:line xmlns:lcs=\"http://schemas.microsoft.com/Lcs/2005/04/RCCExtension\">%s</lcs:line>"\
72 "</private>"\
73 "</privateData>"\
74 "</extensions>"\
75 "</GetCSTAFeatures>"
77 /**
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\">"\
84 "<monitorObject>"\
85 "<deviceObject>%s</deviceObject>"\
86 "</monitorObject>"\
87 "</MonitorStart>"
89 /**
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>"\
97 "</MonitorStop>"
99 /**
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>"\
110 "</MakeCall>"
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>"\
125 "</ClearConnection>"
128 static gchar *
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);
135 } else {
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;
144 *dest_p++ = *phone;
146 *dest_p = '\0';
147 return tel_uri;
151 gchar *
152 sip_to_tel_uri(const gchar *phone)
154 gchar *res = sip_to_tel_uri0(phone);
155 gchar *v;
156 /* strips everything starting with 'v:' if any */
157 if (res && (v = strstr(res, "v:"))) {
158 gchar *tmp = res;
160 res = g_strndup(res, v - res);
161 g_free(tmp);
162 return res;
164 return res;
167 gchar *
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);
174 } else {
175 return g_strdup(tel_uri);
179 static void
180 sip_csta_initialize(struct sipe_account_data *sip,
181 const gchar *line_uri,
182 const gchar *server)
184 if(!sip->csta) {
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);
188 } else {
189 purple_debug_info("sipe", "sip_csta_initialize: sip->csta is already instantiated, exiting.\n");
193 /** get CSTA feautures's callback */
194 static gboolean
195 process_csta_get_features_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
196 struct sipmsg *msg,
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 */
202 return FALSE;
204 else if (msg->response == 200) {
205 purple_debug_info("sipe", "process_csta_get_features_response:\n%s\n", msg->body ? msg->body : "");
208 return TRUE;
211 /** get CSTA feautures */
212 static void
213 sip_csta_get_features(struct sipe_account_data *sip)
215 gchar *hdr;
216 gchar *body;
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");
220 return;
223 hdr = g_strdup(
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,
232 "INFO",
233 sip->csta->dialog->with,
234 sip->csta->dialog->with,
235 hdr,
236 body,
237 sip->csta->dialog,
238 process_csta_get_features_response);
239 g_free(body);
240 g_free(hdr);
243 /** Monitor Start's callback */
244 static gboolean
245 process_csta_monitor_start_response(struct sipe_account_data *sip,
246 struct sipmsg *msg,
247 SIPE_UNUSED_PARAMETER struct transaction *trans)
249 purple_debug_info("sipe", "process_csta_monitor_start_response:\n%s\n", msg->body ? msg->body : "");
251 if (!sip->csta) {
252 purple_debug_info("sipe", "process_csta_monitor_start_response: sip->csta is not initializzed, exiting\n");
253 return FALSE;
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 */
259 return FALSE;
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 : "");
267 xmlnode_free(xml);
270 return TRUE;
273 /** Monitor Start */
274 static void
275 sip_csta_monitor_start(struct sipe_account_data *sip)
277 gchar *hdr;
278 gchar *body;
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");
282 return;
285 hdr = g_strdup(
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,
294 "INFO",
295 sip->csta->dialog->with,
296 sip->csta->dialog->with,
297 hdr,
298 body,
299 sip->csta->dialog,
300 process_csta_monitor_start_response);
301 g_free(body);
302 g_free(hdr);
305 /** Monitor Stop */
306 static void
307 sip_csta_monitor_stop(struct sipe_account_data *sip)
309 gchar *hdr;
310 gchar *body;
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");
314 return;
317 hdr = g_strdup(
318 "Content-Disposition: signal;handling=required\r\n"
319 "Content-Type: application/csta+xml\r\n");
321 body = g_strdup_printf(
322 SIP_SEND_CSTA_MONITOR_STOP,
323 sip->csta->monitor_cross_ref_id);
325 send_sip_request(sip->gc,
326 "INFO",
327 sip->csta->dialog->with,
328 sip->csta->dialog->with,
329 hdr,
330 body,
331 sip->csta->dialog,
332 NULL);
333 g_free(body);
334 g_free(hdr);
337 static void
338 sipe_invite_csta_gateway(struct sipe_account_data *sip);
340 /** a callback */
341 static gboolean
342 process_invite_csta_gateway_response(struct sipe_account_data *sip,
343 struct sipmsg *msg,
344 SIPE_UNUSED_PARAMETER struct transaction *trans)
346 purple_debug_info("sipe", "process_invite_csta_gateway_response:\n%s\n", msg->body ? msg->body : "");
348 if (!sip->csta) {
349 purple_debug_info("sipe", "process_invite_csta_gateway_response: sip->csta is not initializzed, exiting\n");
350 return FALSE;
353 if (!sip->csta->dialog) {
354 purple_debug_info("sipe", "process_invite_csta_gateway_response: GSTA dialog is NULL, exiting\n");
355 return FALSE;
358 sipe_dialog_parse(sip->csta->dialog, msg, TRUE);
360 if (msg->response >= 200) {
361 /* send ACK to CSTA */
362 sip->csta->dialog->cseq = 0;
363 send_sip_request(sip->gc, "ACK", sip->csta->dialog->with, sip->csta->dialog->with, NULL, NULL, sip->csta->dialog, NULL);
364 sip->csta->dialog->outgoing_invite = NULL;
365 sip->csta->dialog->is_established = TRUE;
368 if (msg->response >= 400) {
369 purple_debug_info("sipe", "process_invite_csta_gateway_response: INVITE response is not 200. Failed to join CSTA.\n");
370 /* @TODO notify user of failure to join CSTA */
371 return FALSE;
373 else if (msg->response == 200) {
374 xmlnode *xml = xmlnode_from_str(msg->body, msg->bodylen);
376 g_free(sip->csta->gateway_status);
377 sip->csta->gateway_status = xmlnode_get_data(xmlnode_get_child(xml, "systemStatus"));
378 purple_debug_info("sipe", "process_invite_csta_gateway_response: gateway_status=%s\n",
379 sip->csta->gateway_status ? sip->csta->gateway_status : "");
380 if (!g_ascii_strcasecmp(sip->csta->gateway_status, "normal")) {
381 if (!sip->csta->monitor_cross_ref_id) {
382 sip_csta_get_features(sip);
383 sip_csta_monitor_start(sip);
385 } else {
386 purple_debug_info("sipe", "process_invite_csta_gateway_response: ERROR: CSTA status is %s, won't continue.\n",
387 sip->csta->gateway_status);
388 /* @TODO notify user of failure to join CSTA */
390 xmlnode_free(xml);
392 /* schedule re-invite. RFC4028 */
393 if (sip->csta->dialog->expires) {
394 sipe_schedule_action("<+csta>",
395 sip->csta->dialog->expires - 60, /* 1 minute earlier */
396 (Action)sipe_invite_csta_gateway,
397 NULL,
398 sip,
399 NULL);
403 return TRUE;
406 /** Creates long living dialog with SIP/CSTA Gateway */
407 /* should be re-entrant as require to sent re-invites every 10 min to refresh */
408 static void
409 sipe_invite_csta_gateway(struct sipe_account_data *sip)
411 gchar *hdr;
412 gchar *contact;
413 gchar *body;
415 if (!sip->csta) {
416 purple_debug_info("sipe", "sipe_invite_csta_gateway: sip->csta is uninitialized, exiting\n");
417 return;
420 if(!sip->csta->dialog) {
421 sip->csta->dialog = g_new0(struct sip_dialog, 1);
422 sip->csta->dialog->callid = gencallid();
423 sip->csta->dialog->with = g_strdup(sip->csta->gateway_uri);
425 if (!(sip->csta->dialog->ourtag)) {
426 sip->csta->dialog->ourtag = gentag();
429 contact = get_contact(sip);
430 hdr = g_strdup_printf(
431 "Contact: %s\r\n"
432 "Supported: timer\r\n"
433 "Content-Disposition: signal;handling=required\r\n"
434 "Content-Type: application/csta+xml\r\n",
435 contact);
436 g_free(contact);
438 body = g_strdup_printf(
439 SIP_SEND_CSTA_REQUEST_SYSTEM_STATUS,
440 sip->csta->line_uri);
442 sip->csta->dialog->outgoing_invite = send_sip_request(sip->gc,
443 "INVITE",
444 sip->csta->dialog->with,
445 sip->csta->dialog->with,
446 hdr,
447 body,
448 sip->csta->dialog,
449 process_invite_csta_gateway_response);
450 g_free(body);
451 g_free(hdr);
454 void
455 sip_csta_open(struct sipe_account_data *sip,
456 const gchar *line_uri,
457 const gchar *server)
459 sip_csta_initialize(sip, line_uri, server);
460 sipe_invite_csta_gateway(sip);
463 static void
464 sip_csta_free(struct sip_csta *csta)
466 if (!csta) return;
468 g_free(csta->line_uri);
469 g_free(csta->gateway_uri);
471 sipe_dialog_free(csta->dialog);
473 g_free(csta->gateway_status);
474 g_free(csta->monitor_cross_ref_id);
475 g_free(csta->line_status);
476 g_free(csta->to_tel_uri);
477 g_free(csta->call_id);
478 g_free(csta->device_id);
480 g_free(csta);
483 void
484 sip_csta_close(struct sipe_account_data *sip)
486 if (sip->csta) {
487 sip_csta_monitor_stop(sip);
490 if (sip->csta && sip->csta->dialog) {
491 /* send BYE to CSTA */
492 send_sip_request(sip->gc,
493 "BYE",
494 sip->csta->dialog->with,
495 sip->csta->dialog->with,
496 NULL,
497 NULL,
498 sip->csta->dialog,
499 NULL);
502 sip_csta_free(sip->csta);
507 /** Make Call's callback */
508 static gboolean
509 process_csta_make_call_response(struct sipe_account_data *sip,
510 struct sipmsg *msg,
511 SIPE_UNUSED_PARAMETER struct transaction *trans)
513 purple_debug_info("sipe", "process_csta_make_call_response:\n%s\n", msg->body ? msg->body : "");
515 if (!sip->csta) {
516 purple_debug_info("sipe", "process_csta_make_call_response: sip->csta is not initializzed, exiting\n");
517 return FALSE;
520 if (msg->response >= 400) {
521 purple_debug_info("sipe", "process_csta_make_call_response: Make Call response is not 200. Failed to make call.\n");
522 /* @TODO notify user of failure to make call */
523 return FALSE;
525 else if (msg->response == 200) {
526 xmlnode *xml;
527 xmlnode *xn_calling_device;
528 gchar *device_id;
530 purple_debug_info("sipe", "process_csta_make_call_response: SUCCESS\n");
532 xml = xmlnode_from_str(msg->body, msg->bodylen);
533 xn_calling_device = xmlnode_get_child(xml, "callingDevice");
534 device_id = xmlnode_get_data(xmlnode_get_child(xn_calling_device, "deviceID"));
535 if (!strcmp(sip->csta->line_uri, device_id)) {
536 g_free(sip->csta->call_id);
537 sip->csta->call_id = xmlnode_get_data(xmlnode_get_child(xn_calling_device, "callID"));
538 purple_debug_info("sipe", "process_csta_make_call_response: call_id=%s\n", sip->csta->call_id ? sip->csta->call_id : "");
540 g_free(device_id);
541 xmlnode_free(xml);
544 return TRUE;
547 /** Make Call */
548 void
549 sip_csta_make_call(struct sipe_account_data *sip,
550 const gchar* to_tel_uri)
552 gchar *hdr;
553 gchar *body;
555 if (!to_tel_uri) {
556 purple_debug_info("sipe", "sip_csta_make_call: no tel URI parameter provided, exiting.\n");
557 return;
560 if (!sip->csta || !sip->csta->dialog || !sip->csta->dialog->is_established) {
561 purple_debug_info("sipe", "sip_csta_make_call: no dialog with CSTA, exiting.\n");
562 return;
565 g_free(sip->csta->to_tel_uri);
566 sip->csta->to_tel_uri = g_strdup(to_tel_uri);
568 hdr = g_strdup(
569 "Content-Disposition: signal;handling=required\r\n"
570 "Content-Type: application/csta+xml\r\n");
572 body = g_strdup_printf(
573 SIP_SEND_CSTA_MAKE_CALL,
574 sip->csta->line_uri,
575 sip->csta->to_tel_uri);
577 send_sip_request(sip->gc,
578 "INFO",
579 sip->csta->dialog->with,
580 sip->csta->dialog->with,
581 hdr,
582 body,
583 sip->csta->dialog,
584 process_csta_make_call_response);
585 g_free(body);
586 g_free(hdr);
589 static void
590 sip_csta_update_id_and_status(struct sip_csta *csta,
591 xmlnode *node,
592 const char *status)
594 gchar *call_id = xmlnode_get_data(xmlnode_get_child(node, "callID"));
596 if (call_id && csta->call_id && strcmp(call_id, csta->call_id)) {
597 purple_debug_info("sipe", "sipe_csta_update_id_and_status: callID (%s) does not match\n", call_id ? call_id : "");
599 else
601 /* free old line status */
602 g_free(csta->line_status);
603 csta->line_status = NULL;
605 if (status)
607 /* save deviceID */
608 gchar *device_id = xmlnode_get_data(xmlnode_get_child(node, "deviceID"));
609 purple_debug_info("sipe", "sipe_csta_update_id_and_status: device_id=(%s)\n", device_id ? device_id : "");
610 if (device_id) {
611 g_free(csta->device_id);
612 csta->device_id = device_id;
615 /* set new line status */
616 csta->line_status = g_strdup(status);
618 else
620 /* clean up cleared connection */
621 g_free(csta->to_tel_uri);
622 csta->to_tel_uri = NULL;
623 g_free(csta->call_id);
624 csta->call_id = NULL;
625 g_free(csta->device_id);
626 csta->device_id = NULL;
630 g_free(call_id);
633 void
634 process_incoming_info_csta(struct sipe_account_data *sip,
635 struct sipmsg *msg)
637 gchar *monitor_cross_ref_id;
638 xmlnode *xml = xmlnode_from_str(msg->body, msg->bodylen);
640 if (!xml) return;
642 monitor_cross_ref_id = xmlnode_get_data(xmlnode_get_child(xml, "monitorCrossRefID"));
644 if(!sip->csta || (monitor_cross_ref_id
645 && sip->csta->monitor_cross_ref_id
646 && strcmp(monitor_cross_ref_id, sip->csta->monitor_cross_ref_id)))
648 purple_debug_info("sipe", "process_incoming_info_csta: monitorCrossRefID (%s) does not match, exiting\n",
649 monitor_cross_ref_id ? monitor_cross_ref_id : "");
651 else
653 if (!strcmp(xml->name, "OriginatedEvent"))
655 sip_csta_update_id_and_status(sip->csta,
656 xmlnode_get_child(xml, "originatedConnection"),
657 ORIGINATED_CSTA_STATUS);
659 else if (!strcmp(xml->name, "DeliveredEvent"))
661 sip_csta_update_id_and_status(sip->csta,
662 xmlnode_get_child(xml, "connection"),
663 DELIVERED_CSTA_STATUS);
665 else if (!strcmp(xml->name, "EstablishedEvent"))
667 sip_csta_update_id_and_status(sip->csta,
668 xmlnode_get_child(xml, "establishedConnection"),
669 ESTABLISHED_CSTA_STATUS);
671 else if (!strcmp(xml->name, "ConnectionClearedEvent"))
673 sip_csta_update_id_and_status(sip->csta,
674 xmlnode_get_child(xml, "droppedConnection"),
675 NULL);
679 g_free(monitor_cross_ref_id);
680 xmlnode_free(xml);
686 Local Variables:
687 mode: c
688 c-file-style: "bsd"
689 indent-tabs-mode: t
690 tab-width: 8
691 End: