Updated to release 1.7.0
[siplcs.git] / src / sip-csta.c
blob90d7da0a8fac828f2f6c0b1eea3d6831f476189d
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 gchar *
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);
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_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);
158 } else {
159 return g_strdup(tel_uri);
163 static void
164 sip_csta_initialize(struct sipe_account_data *sip,
165 const gchar *line_uri,
166 const gchar *server)
168 if(!sip->csta) {
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);
172 } else {
173 purple_debug_info("sipe", "sip_csta_initialize: sip->csta is already instantiated, exiting.\n");
177 /** get CSTA feautures's callback */
178 static gboolean
179 process_csta_get_features_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
180 struct sipmsg *msg,
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 */
186 return FALSE;
188 else if (msg->response == 200) {
189 purple_debug_info("sipe", "process_csta_get_features_response:\n%s\n", msg->body ? msg->body : "");
192 return TRUE;
195 /** get CSTA feautures */
196 static void
197 sip_csta_get_features(struct sipe_account_data *sip)
199 gchar *hdr;
200 gchar *body;
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");
204 return;
207 hdr = g_strdup(
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,
216 "INFO",
217 sip->csta->dialog->with,
218 sip->csta->dialog->with,
219 hdr,
220 body,
221 sip->csta->dialog,
222 process_csta_get_features_response);
223 g_free(body);
224 g_free(hdr);
227 /** Monitor Start's callback */
228 static gboolean
229 process_csta_monitor_start_response(struct sipe_account_data *sip,
230 struct sipmsg *msg,
231 SIPE_UNUSED_PARAMETER struct transaction *trans)
233 purple_debug_info("sipe", "process_csta_monitor_start_response:\n%s\n", msg->body ? msg->body : "");
235 if (!sip->csta) {
236 purple_debug_info("sipe", "process_csta_monitor_start_response: sip->csta is not initializzed, exiting\n");
237 return FALSE;
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 */
243 return FALSE;
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 : "");
251 xmlnode_free(xml);
254 return TRUE;
257 /** Monitor Start */
258 static void
259 sip_csta_monitor_start(struct sipe_account_data *sip)
261 gchar *hdr;
262 gchar *body;
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");
266 return;
269 hdr = g_strdup(
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,
278 "INFO",
279 sip->csta->dialog->with,
280 sip->csta->dialog->with,
281 hdr,
282 body,
283 sip->csta->dialog,
284 process_csta_monitor_start_response);
285 g_free(body);
286 g_free(hdr);
289 /** Monitor Stop */
290 static void
291 sip_csta_monitor_stop(struct sipe_account_data *sip)
293 gchar *hdr;
294 gchar *body;
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");
298 return;
301 hdr = g_strdup(
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,
310 "INFO",
311 sip->csta->dialog->with,
312 sip->csta->dialog->with,
313 hdr,
314 body,
315 sip->csta->dialog,
316 NULL);
317 g_free(body);
318 g_free(hdr);
321 /** a callback */
322 static gboolean
323 process_invite_csta_gateway_response(struct sipe_account_data *sip,
324 struct sipmsg *msg,
325 SIPE_UNUSED_PARAMETER struct transaction *trans)
327 purple_debug_info("sipe", "process_invite_csta_gateway_response:\n%s\n", msg->body ? msg->body : "");
329 if (!sip->csta) {
330 purple_debug_info("sipe", "process_invite_csta_gateway_response: sip->csta is not initializzed, exiting\n");
331 return FALSE;
334 if (!sip->csta->dialog) {
335 purple_debug_info("sipe", "process_invite_csta_gateway_response: GSTA dialog is NULL, exiting\n");
336 return FALSE;
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 */
352 return FALSE;
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);
363 } else {
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 */
368 xmlnode_free(xml);
371 return TRUE;
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 */
376 static void
377 sipe_invite_csta_gateway(struct sipe_account_data *sip)
379 gchar *hdr;
380 gchar *contact;
381 gchar *body;
383 if (!sip->csta) {
384 purple_debug_info("sipe", "sipe_invite_csta_gateway: sip->csta is uninitialized, exiting\n");
385 return;
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(
399 "Contact: %s\r\n"
400 "Content-Disposition: signal;handling=required\r\n"
401 "Content-Type: application/csta+xml\r\n",
402 contact);
403 g_free(contact);
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,
410 "INVITE",
411 sip->csta->dialog->with,
412 sip->csta->dialog->with,
413 hdr,
414 body,
415 sip->csta->dialog,
416 process_invite_csta_gateway_response);
417 g_free(body);
418 g_free(hdr);
421 void
422 sip_csta_open(struct sipe_account_data *sip,
423 const gchar *line_uri,
424 const gchar *server)
426 sip_csta_initialize(sip, line_uri, server);
427 sipe_invite_csta_gateway(sip);
430 static void
431 sip_csta_free(struct sip_csta *csta)
433 if (!csta) return;
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);
447 g_free(csta);
450 void
451 sip_csta_close(struct sipe_account_data *sip)
453 if (sip->csta) {
454 sip_csta_monitor_stop(sip);
457 if (sip->csta && sip->csta->dialog) {
458 /* send BYE to CSTA */
459 send_sip_request(sip->gc,
460 "BYE",
461 sip->csta->dialog->with,
462 sip->csta->dialog->with,
463 NULL,
464 NULL,
465 sip->csta->dialog,
466 NULL);
469 sip_csta_free(sip->csta);
474 /** Make Call's callback */
475 static gboolean
476 process_csta_make_call_response(struct sipe_account_data *sip,
477 struct sipmsg *msg,
478 SIPE_UNUSED_PARAMETER struct transaction *trans)
480 purple_debug_info("sipe", "process_csta_make_call_response:\n%s\n", msg->body ? msg->body : "");
482 if (!sip->csta) {
483 purple_debug_info("sipe", "process_csta_make_call_response: sip->csta is not initializzed, exiting\n");
484 return FALSE;
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 */
490 return FALSE;
492 else if (msg->response == 200) {
493 xmlnode *xml;
494 xmlnode *xn_calling_device;
495 gchar *device_id;
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 : "");
507 g_free(device_id);
508 xmlnode_free(xml);
511 return TRUE;
514 /** Make Call */
515 void
516 sip_csta_make_call(struct sipe_account_data *sip,
517 const gchar* to_tel_uri)
519 gchar *hdr;
520 gchar *body;
522 if (!to_tel_uri) {
523 purple_debug_info("sipe", "sip_csta_make_call: no tel URI parameter provided, exiting.\n");
524 return;
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");
529 return;
532 g_free(sip->csta->to_tel_uri);
533 sip->csta->to_tel_uri = g_strdup(to_tel_uri);
535 hdr = g_strdup(
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,
541 sip->csta->line_uri,
542 sip->csta->to_tel_uri);
544 send_sip_request(sip->gc,
545 "INFO",
546 sip->csta->dialog->with,
547 sip->csta->dialog->with,
548 hdr,
549 body,
550 sip->csta->dialog,
551 process_csta_make_call_response);
552 g_free(body);
553 g_free(hdr);
556 static void
557 sip_csta_update_id_and_status(struct sip_csta *csta,
558 xmlnode *node,
559 const char *status)
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 : "");
566 else
568 /* free old line status */
569 g_free(csta->line_status);
570 csta->line_status = NULL;
572 if (status)
574 /* save deviceID */
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 : "");
577 if (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);
585 else
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;
597 g_free(call_id);
600 void
601 process_incoming_info_csta(struct sipe_account_data *sip,
602 struct sipmsg *msg)
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 : "");
614 else
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"),
638 NULL);
642 g_free(monitor_cross_ref_id);
643 xmlnode_free(xml);
649 Local Variables:
650 mode: c
651 c-file-style: "bsd"
652 indent-tabs-mode: t
653 tab-width: 8
654 End: