core cleanup: 6 more modules are purple free
[siplcs.git] / src / core / sip-csta.c
blob3b57837c8f648b50999e986e8822559759f85cef
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 <stdlib.h>
30 #include <string.h>
31 #include <time.h>
33 #include <glib.h>
35 #include "debug.h"
36 #include "xmlnode.h"
38 #include "sipe-common.h"
39 #include "sipmsg.h"
40 #include "sip-csta.h"
41 #include "sip-sec.h"
42 #include "sipe-dialog.h"
43 #include "sipe-utils.h"
44 #include "sipe.h"
46 #define ORIGINATED_CSTA_STATUS "originated"
47 #define DELIVERED_CSTA_STATUS "delivered"
48 #define ESTABLISHED_CSTA_STATUS "established"
51 /**
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\">"\
58 "<extensions>"\
59 "<privateData>"\
60 "<private>"\
61 "<lcs:line xmlns:lcs=\"http://schemas.microsoft.com/Lcs/2005/04/RCCExtension\">%s</lcs:line>"\
62 "</private>"\
63 "</privateData>"\
64 "</extensions>"\
65 "</RequestSystemStatus>"
67 /**
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\">"\
74 "<extensions>"\
75 "<privateData>"\
76 "<private>"\
77 "<lcs:line xmlns:lcs=\"http://schemas.microsoft.com/Lcs/2005/04/RCCExtension\">%s</lcs:line>"\
78 "</private>"\
79 "</privateData>"\
80 "</extensions>"\
81 "</GetCSTAFeatures>"
83 /**
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\">"\
90 "<monitorObject>"\
91 "<deviceObject>%s</deviceObject>"\
92 "</monitorObject>"\
93 "</MonitorStart>"
95 /**
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>"\
103 "</MonitorStop>"
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>"\
116 "</MakeCall>"
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>"\
131 "</ClearConnection>"
134 static gchar *
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);
141 } else {
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;
150 *dest_p++ = *phone;
152 *dest_p = '\0';
153 return tel_uri;
157 gchar *
158 sip_to_tel_uri(const gchar *phone)
160 gchar *res = sip_to_tel_uri0(phone);
161 gchar *v;
162 /* strips everything starting with 'v:' if any */
163 if (res && (v = strstr(res, "v:"))) {
164 gchar *tmp = res;
166 res = g_strndup(res, v - res);
167 g_free(tmp);
168 return res;
170 return res;
173 gchar *
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);
180 } else {
181 return g_strdup(tel_uri);
185 static void
186 sip_csta_initialize(struct sipe_account_data *sip,
187 const gchar *line_uri,
188 const gchar *server)
190 if(!sip->csta) {
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);
194 } else {
195 purple_debug_info("sipe", "sip_csta_initialize: sip->csta is already instantiated, exiting.\n");
199 /** get CSTA feautures's callback */
200 static gboolean
201 process_csta_get_features_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
202 struct sipmsg *msg,
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 */
208 return FALSE;
210 else if (msg->response == 200) {
211 purple_debug_info("sipe", "process_csta_get_features_response:\n%s\n", msg->body ? msg->body : "");
214 return TRUE;
217 /** get CSTA feautures */
218 static void
219 sip_csta_get_features(struct sipe_account_data *sip)
221 gchar *hdr;
222 gchar *body;
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");
226 return;
229 hdr = g_strdup(
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,
238 "INFO",
239 sip->csta->dialog->with,
240 sip->csta->dialog->with,
241 hdr,
242 body,
243 sip->csta->dialog,
244 process_csta_get_features_response);
245 g_free(body);
246 g_free(hdr);
249 /** Monitor Start's callback */
250 static gboolean
251 process_csta_monitor_start_response(struct sipe_account_data *sip,
252 struct sipmsg *msg,
253 SIPE_UNUSED_PARAMETER struct transaction *trans)
255 purple_debug_info("sipe", "process_csta_monitor_start_response:\n%s\n", msg->body ? msg->body : "");
257 if (!sip->csta) {
258 purple_debug_info("sipe", "process_csta_monitor_start_response: sip->csta is not initializzed, exiting\n");
259 return FALSE;
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 */
265 return FALSE;
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 : "");
273 xmlnode_free(xml);
276 return TRUE;
279 /** Monitor Start */
280 static void
281 sip_csta_monitor_start(struct sipe_account_data *sip)
283 gchar *hdr;
284 gchar *body;
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");
288 return;
291 hdr = g_strdup(
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,
300 "INFO",
301 sip->csta->dialog->with,
302 sip->csta->dialog->with,
303 hdr,
304 body,
305 sip->csta->dialog,
306 process_csta_monitor_start_response);
307 g_free(body);
308 g_free(hdr);
311 /** Monitor Stop */
312 static void
313 sip_csta_monitor_stop(struct sipe_account_data *sip)
315 gchar *hdr;
316 gchar *body;
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");
320 return;
323 if (!sip->csta->monitor_cross_ref_id) {
324 purple_debug_info("sipe", "sip_csta_monitor_stop: no monitor_cross_ref_id, exiting.\n");
325 return;
328 hdr = g_strdup(
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,
337 "INFO",
338 sip->csta->dialog->with,
339 sip->csta->dialog->with,
340 hdr,
341 body,
342 sip->csta->dialog,
343 NULL);
344 g_free(body);
345 g_free(hdr);
348 static void
349 sipe_invite_csta_gateway(struct sipe_account_data *sip);
351 /** a callback */
352 static gboolean
353 process_invite_csta_gateway_response(struct sipe_account_data *sip,
354 struct sipmsg *msg,
355 SIPE_UNUSED_PARAMETER struct transaction *trans)
357 purple_debug_info("sipe", "process_invite_csta_gateway_response:\n%s\n", msg->body ? msg->body : "");
359 if (!sip->csta) {
360 purple_debug_info("sipe", "process_invite_csta_gateway_response: sip->csta is not initializzed, exiting\n");
361 return FALSE;
364 if (!sip->csta->dialog) {
365 purple_debug_info("sipe", "process_invite_csta_gateway_response: GSTA dialog is NULL, exiting\n");
366 return FALSE;
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 */
382 return FALSE;
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);
396 } else {
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 */
401 xmlnode_free(xml);
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,
408 NULL,
409 sip,
410 NULL);
414 return TRUE;
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 */
419 static void
420 sipe_invite_csta_gateway(struct sipe_account_data *sip)
422 gchar *hdr;
423 gchar *contact;
424 gchar *body;
426 if (!sip->csta) {
427 purple_debug_info("sipe", "sipe_invite_csta_gateway: sip->csta is uninitialized, exiting\n");
428 return;
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(
442 "Contact: %s\r\n"
443 "Supported: timer\r\n"
444 "Content-Disposition: signal;handling=required\r\n"
445 "Content-Type: application/csta+xml\r\n",
446 contact);
447 g_free(contact);
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,
454 "INVITE",
455 sip->csta->dialog->with,
456 sip->csta->dialog->with,
457 hdr,
458 body,
459 sip->csta->dialog,
460 process_invite_csta_gateway_response);
461 g_free(body);
462 g_free(hdr);
465 void
466 sip_csta_open(struct sipe_account_data *sip,
467 const gchar *line_uri,
468 const gchar *server)
470 sip_csta_initialize(sip, line_uri, server);
471 sipe_invite_csta_gateway(sip);
474 static void
475 sip_csta_free(struct sip_csta *csta)
477 if (!csta) return;
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);
491 g_free(csta);
494 void
495 sip_csta_close(struct sipe_account_data *sip)
497 if (sip->csta) {
498 sip_csta_monitor_stop(sip);
501 if (sip->csta && sip->csta->dialog) {
502 /* send BYE to CSTA */
503 send_sip_request(sip->gc,
504 "BYE",
505 sip->csta->dialog->with,
506 sip->csta->dialog->with,
507 NULL,
508 NULL,
509 sip->csta->dialog,
510 NULL);
513 sip_csta_free(sip->csta);
518 /** Make Call's callback */
519 static gboolean
520 process_csta_make_call_response(struct sipe_account_data *sip,
521 struct sipmsg *msg,
522 SIPE_UNUSED_PARAMETER struct transaction *trans)
524 purple_debug_info("sipe", "process_csta_make_call_response:\n%s\n", msg->body ? msg->body : "");
526 if (!sip->csta) {
527 purple_debug_info("sipe", "process_csta_make_call_response: sip->csta is not initializzed, exiting\n");
528 return FALSE;
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 */
534 return FALSE;
536 else if (msg->response == 200) {
537 xmlnode *xml;
538 xmlnode *xn_calling_device;
539 gchar *device_id;
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 : "");
551 g_free(device_id);
552 xmlnode_free(xml);
555 return TRUE;
558 /** Make Call */
559 void
560 sip_csta_make_call(struct sipe_account_data *sip,
561 const gchar* to_tel_uri)
563 gchar *hdr;
564 gchar *body;
566 if (!to_tel_uri) {
567 purple_debug_info("sipe", "sip_csta_make_call: no tel URI parameter provided, exiting.\n");
568 return;
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");
573 return;
576 g_free(sip->csta->to_tel_uri);
577 sip->csta->to_tel_uri = g_strdup(to_tel_uri);
579 hdr = g_strdup(
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,
585 sip->csta->line_uri,
586 sip->csta->to_tel_uri);
588 send_sip_request(sip->gc,
589 "INFO",
590 sip->csta->dialog->with,
591 sip->csta->dialog->with,
592 hdr,
593 body,
594 sip->csta->dialog,
595 process_csta_make_call_response);
596 g_free(body);
597 g_free(hdr);
600 static void
601 sip_csta_update_id_and_status(struct sip_csta *csta,
602 xmlnode *node,
603 const char *status)
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);
610 else
612 /* free old line status */
613 g_free(csta->line_status);
614 csta->line_status = NULL;
616 if (status)
618 /* save deviceID */
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 : "");
621 if (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);
629 else
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;
641 g_free(call_id);
644 void
645 process_incoming_info_csta(struct sipe_account_data *sip,
646 struct sipmsg *msg)
648 gchar *monitor_cross_ref_id;
649 xmlnode *xml = xmlnode_from_str(msg->body, msg->bodylen);
651 if (!xml) return;
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 : "");
660 else
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"),
684 NULL);
688 g_free(monitor_cross_ref_id);
689 xmlnode_free(xml);
695 Local Variables:
696 mode: c
697 c-file-style: "bsd"
698 indent-tabs-mode: t
699 tab-width: 8
700 End: