core cleanup: sipe-csta module is purple free
[siplcs.git] / src / core / sip-csta.c
blobd4014ed5d03c3aed982e621530f48f4bc094f8ea
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 "sipe-common.h"
36 #include "sipmsg.h"
37 #include "sip-csta.h"
38 #include "sip-sec.h"
39 #include "sipe-backend-debug.h"
40 #include "sipe-dialog.h"
41 #include "sipe-utils.h"
42 #include "sipe-xml.h"
43 #include "sipe.h"
45 #define ORIGINATED_CSTA_STATUS "originated"
46 #define DELIVERED_CSTA_STATUS "delivered"
47 #define ESTABLISHED_CSTA_STATUS "established"
50 /**
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\">"\
57 "<extensions>"\
58 "<privateData>"\
59 "<private>"\
60 "<lcs:line xmlns:lcs=\"http://schemas.microsoft.com/Lcs/2005/04/RCCExtension\">%s</lcs:line>"\
61 "</private>"\
62 "</privateData>"\
63 "</extensions>"\
64 "</RequestSystemStatus>"
66 /**
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\">"\
73 "<extensions>"\
74 "<privateData>"\
75 "<private>"\
76 "<lcs:line xmlns:lcs=\"http://schemas.microsoft.com/Lcs/2005/04/RCCExtension\">%s</lcs:line>"\
77 "</private>"\
78 "</privateData>"\
79 "</extensions>"\
80 "</GetCSTAFeatures>"
82 /**
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\">"\
89 "<monitorObject>"\
90 "<deviceObject>%s</deviceObject>"\
91 "</monitorObject>"\
92 "</MonitorStart>"
94 /**
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>"\
102 "</MonitorStop>"
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>"\
115 "</MakeCall>"
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>"\
130 "</ClearConnection>"
133 static gchar *
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);
140 } else {
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;
149 *dest_p++ = *phone;
151 *dest_p = '\0';
152 return tel_uri;
156 gchar *
157 sip_to_tel_uri(const gchar *phone)
159 gchar *res = sip_to_tel_uri0(phone);
160 gchar *v;
161 /* strips everything starting with 'v:' if any */
162 if (res && (v = strstr(res, "v:"))) {
163 gchar *tmp = res;
165 res = g_strndup(res, v - res);
166 g_free(tmp);
167 return res;
169 return res;
172 gchar *
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);
179 } else {
180 return g_strdup(tel_uri);
184 static void
185 sip_csta_initialize(struct sipe_account_data *sip,
186 const gchar *line_uri,
187 const gchar *server)
189 if(!sip->csta) {
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);
193 } else {
194 SIPE_DEBUG_INFO("sip_csta_initialize: sip->csta is already instantiated, exiting.%s", "");
198 /** get CSTA feautures's callback */
199 static gboolean
200 process_csta_get_features_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
201 struct sipmsg *msg,
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 */
207 return FALSE;
209 else if (msg->response == 200) {
210 SIPE_DEBUG_INFO("process_csta_get_features_response:\n%s", msg->body ? msg->body : "");
213 return TRUE;
216 /** get CSTA feautures */
217 static void
218 sip_csta_get_features(struct sipe_account_data *sip)
220 gchar *hdr;
221 gchar *body;
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", "");
225 return;
228 hdr = g_strdup(
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,
237 "INFO",
238 sip->csta->dialog->with,
239 sip->csta->dialog->with,
240 hdr,
241 body,
242 sip->csta->dialog,
243 process_csta_get_features_response);
244 g_free(body);
245 g_free(hdr);
248 /** Monitor Start's callback */
249 static gboolean
250 process_csta_monitor_start_response(struct sipe_account_data *sip,
251 struct sipmsg *msg,
252 SIPE_UNUSED_PARAMETER struct transaction *trans)
254 SIPE_DEBUG_INFO("process_csta_monitor_start_response:\n%s", msg->body ? msg->body : "");
256 if (!sip->csta) {
257 SIPE_DEBUG_INFO("process_csta_monitor_start_response: sip->csta is not initializzed, exiting%s", "");
258 return FALSE;
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 */
264 return FALSE;
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 : "");
272 sipe_xml_free(xml);
275 return TRUE;
278 /** Monitor Start */
279 static void
280 sip_csta_monitor_start(struct sipe_account_data *sip)
282 gchar *hdr;
283 gchar *body;
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", "");
287 return;
290 hdr = g_strdup(
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,
299 "INFO",
300 sip->csta->dialog->with,
301 sip->csta->dialog->with,
302 hdr,
303 body,
304 sip->csta->dialog,
305 process_csta_monitor_start_response);
306 g_free(body);
307 g_free(hdr);
310 /** Monitor Stop */
311 static void
312 sip_csta_monitor_stop(struct sipe_account_data *sip)
314 gchar *hdr;
315 gchar *body;
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", "");
319 return;
322 if (!sip->csta->monitor_cross_ref_id) {
323 SIPE_DEBUG_INFO("sip_csta_monitor_stop: no monitor_cross_ref_id, exiting.%s", "");
324 return;
327 hdr = g_strdup(
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,
336 "INFO",
337 sip->csta->dialog->with,
338 sip->csta->dialog->with,
339 hdr,
340 body,
341 sip->csta->dialog,
342 NULL);
343 g_free(body);
344 g_free(hdr);
347 static void
348 sipe_invite_csta_gateway(struct sipe_account_data *sip);
350 /** a callback */
351 static gboolean
352 process_invite_csta_gateway_response(struct sipe_account_data *sip,
353 struct sipmsg *msg,
354 SIPE_UNUSED_PARAMETER struct transaction *trans)
356 SIPE_DEBUG_INFO("process_invite_csta_gateway_response:\n%s", msg->body ? msg->body : "");
358 if (!sip->csta) {
359 SIPE_DEBUG_INFO("process_invite_csta_gateway_response: sip->csta is not initializzed, exiting%s", "");
360 return FALSE;
363 if (!sip->csta->dialog) {
364 SIPE_DEBUG_INFO("process_invite_csta_gateway_response: GSTA dialog is NULL, exiting%s", "");
365 return FALSE;
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 */
381 return FALSE;
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);
395 } else {
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 */
400 sipe_xml_free(xml);
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,
407 NULL,
408 sip,
409 NULL);
413 return TRUE;
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 */
418 static void
419 sipe_invite_csta_gateway(struct sipe_account_data *sip)
421 gchar *hdr;
422 gchar *contact;
423 gchar *body;
425 if (!sip->csta) {
426 SIPE_DEBUG_INFO("sipe_invite_csta_gateway: sip->csta is uninitialized, exiting%s", "");
427 return;
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(
441 "Contact: %s\r\n"
442 "Supported: timer\r\n"
443 "Content-Disposition: signal;handling=required\r\n"
444 "Content-Type: application/csta+xml\r\n",
445 contact);
446 g_free(contact);
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,
453 "INVITE",
454 sip->csta->dialog->with,
455 sip->csta->dialog->with,
456 hdr,
457 body,
458 sip->csta->dialog,
459 process_invite_csta_gateway_response);
460 g_free(body);
461 g_free(hdr);
464 void
465 sip_csta_open(struct sipe_account_data *sip,
466 const gchar *line_uri,
467 const gchar *server)
469 sip_csta_initialize(sip, line_uri, server);
470 sipe_invite_csta_gateway(sip);
473 static void
474 sip_csta_free(struct sip_csta *csta)
476 if (!csta) return;
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);
490 g_free(csta);
493 void
494 sip_csta_close(struct sipe_account_data *sip)
496 if (sip->csta) {
497 sip_csta_monitor_stop(sip);
500 if (sip->csta && sip->csta->dialog) {
501 /* send BYE to CSTA */
502 send_sip_request(sip->gc,
503 "BYE",
504 sip->csta->dialog->with,
505 sip->csta->dialog->with,
506 NULL,
507 NULL,
508 sip->csta->dialog,
509 NULL);
512 sip_csta_free(sip->csta);
517 /** Make Call's callback */
518 static gboolean
519 process_csta_make_call_response(struct sipe_account_data *sip,
520 struct sipmsg *msg,
521 SIPE_UNUSED_PARAMETER struct transaction *trans)
523 SIPE_DEBUG_INFO("process_csta_make_call_response:\n%s", msg->body ? msg->body : "");
525 if (!sip->csta) {
526 SIPE_DEBUG_INFO("process_csta_make_call_response: sip->csta is not initializzed, exiting%s", "");
527 return FALSE;
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 */
533 return FALSE;
535 else if (msg->response == 200) {
536 sipe_xml *xml;
537 const sipe_xml *xn_calling_device;
538 gchar *device_id;
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 : "");
550 g_free(device_id);
551 sipe_xml_free(xml);
554 return TRUE;
557 /** Make Call */
558 void
559 sip_csta_make_call(struct sipe_account_data *sip,
560 const gchar* to_tel_uri)
562 gchar *hdr;
563 gchar *body;
565 if (!to_tel_uri) {
566 SIPE_DEBUG_INFO("sip_csta_make_call: no tel URI parameter provided, exiting.%s", "");
567 return;
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", "");
572 return;
575 g_free(sip->csta->to_tel_uri);
576 sip->csta->to_tel_uri = g_strdup(to_tel_uri);
578 hdr = g_strdup(
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,
584 sip->csta->line_uri,
585 sip->csta->to_tel_uri);
587 send_sip_request(sip->gc,
588 "INFO",
589 sip->csta->dialog->with,
590 sip->csta->dialog->with,
591 hdr,
592 body,
593 sip->csta->dialog,
594 process_csta_make_call_response);
595 g_free(body);
596 g_free(hdr);
599 static void
600 sip_csta_update_id_and_status(struct sip_csta *csta,
601 const sipe_xml *node,
602 const char *status)
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);
609 else
611 /* free old line status */
612 g_free(csta->line_status);
613 csta->line_status = NULL;
615 if (status)
617 /* save deviceID */
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 : "");
620 if (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);
628 else
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;
640 g_free(call_id);
643 void
644 process_incoming_info_csta(struct sipe_account_data *sip,
645 struct sipmsg *msg)
647 gchar *monitor_cross_ref_id;
648 sipe_xml *xml = sipe_xml_parse(msg->body, msg->bodylen);
650 if (!xml) return;
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 : "");
659 else
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"),
683 NULL);
687 g_free(monitor_cross_ref_id);
688 sipe_xml_free(xml);
694 Local Variables:
695 mode: c
696 c-file-style: "bsd"
697 indent-tabs-mode: t
698 tab-width: 8
699 End: