1 /*
2 * sip-connection-helpers.c - Helper routines used by SIPConnection
3 * Copyright (C) 2005 Collabora Ltd.
4 * Copyright (C) 2006, 2007 Nokia Corporation
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public License
8 * version 2.1 as published by the Free Software Foundation.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 #include <stdlib.h>
21 #include <string.h>
22 #include <ctype.h>
23
24 #define DBUS_API_SUBJECT_TO_CHANGE 1
25 #include <dbus/dbus-glib.h>
26
27 #include <telepathy-glib/enums.h>
28 #include <telepathy-glib/errors.h>
29 #include <telepathy-glib/dbus.h>
30 #include <telepathy-glib/interfaces.h>
31 #include <telepathy-glib/svc-connection.h>
32
33 #include "sip-sofia-decls.h"
34 #include <sofia-sip/sip.h>
35 #include <sofia-sip/sip_header.h>
36
37 #include "sip-connection-private.h"
38
39 #define DEBUG_FLAG SIP_DEBUG_CONNECTION
40 #include "debug.h"
41
42 /* Default keepalive timeout in seconds,
43 * a value obtained from Sofia-SIP documentation */
44 #define SIP_CONNECTION_DEFAULT_KEEPALIVE_INTERVAL 120
45
46 /* The user is not allowed to set keepalive timeout to lower than that,
47 * to avoid wasting traffic and device power */
48 #define SIP_CONNECTION_MINIMUM_KEEPALIVE_INTERVAL 30
49
50 /* The user is not allowed to set keepalive timeout to lower than that
51 * for REGISTER keepalives, to avoid wasting traffic and device power.
52 * REGISTER is special because it may tie resources on the server side */
53 #define SIP_CONNECTION_MINIMUM_KEEPALIVE_INTERVAL_REGISTER 50
54
55 /* The value of SIP_NH_EXPIRED. This can be anything that is neither NULL
56 * nor a media channel */
57 NUA_HMAGIC_T * const _sip_nh_expired = (NUA_HMAGIC_T *)"";
58
priv_sip_to_url_make(SIPConnection * conn,su_home_t * home,TpHandle contact)59 static sip_to_t *priv_sip_to_url_make (SIPConnection *conn,
60 su_home_t *home,
61 TpHandle contact)
62 {
63 TpHandleRepoIface *contact_repo;
64 sip_to_t *to;
65 const char *address;
66
67 contact_repo = tp_base_connection_get_handles (
68 (TpBaseConnection *)conn, TP_HANDLE_TYPE_CONTACT);
69
70 address = tp_handle_inspect (contact_repo, contact);
71 if (address == NULL)
72 return NULL;
73
74 /* TODO: set display name bound to the handle using qdata? */
75
76 to = sip_to_create (home, URL_STRING_MAKE(address));
77
78 if (to &&
79 url_sanitize(to->a_url) == 0)
80 return to;
81
82 return NULL;
83 }
84
85 nua_handle_t *
sip_conn_create_register_handle(SIPConnection * conn,TpHandle contact)86 sip_conn_create_register_handle (SIPConnection *conn,
87 TpHandle contact)
88 {
89 SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (conn);
90 nua_handle_t *result = NULL;
91 su_home_t temphome[1] = { SU_HOME_INIT(temphome) };
92 sip_to_t *to;
93
94 g_assert (priv->sofia_home != NULL);
95 g_assert (priv->sofia_nua != NULL);
96
97 to = priv_sip_to_url_make (conn, temphome, contact);
98
99 if (to)
100 result = nua_handle (priv->sofia_nua, NULL, SIPTAG_TO(to), TAG_END());
101
102 su_home_deinit (temphome);
103
104 return result;
105 }
106
107 nua_handle_t *
sip_conn_create_request_handle(SIPConnection * conn,TpHandle contact)108 sip_conn_create_request_handle (SIPConnection *conn,
109 TpHandle contact)
110 {
111 SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (conn);
112 nua_handle_t *result = NULL;
113 su_home_t temphome[1] = { SU_HOME_INIT(temphome) };
114 sip_to_t *to;
115
116 g_assert (priv->sofia_home != NULL);
117 g_assert (priv->sofia_nua != NULL);
118
119 to = priv_sip_to_url_make (conn, temphome, contact);
120
121 /* TODO: Pass also SIPTAG_FROM updated from base->self_handle, to update the
122 * display name possibly set by the client */
123
124 if (to)
125 result = nua_handle (priv->sofia_nua, NULL,
126 NUTAG_URL(to->a_url),
127 SIPTAG_TO(to),
128 TAG_END());
129
130 su_home_deinit (temphome);
131
132 return result;
133 }
134
135 void
sip_conn_save_event(SIPConnection * conn,nua_saved_event_t ret_saved[1])136 sip_conn_save_event (SIPConnection *conn,
137 nua_saved_event_t ret_saved [1])
138 {
139 SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (conn);
140 nua_save_event (priv->sofia_nua, ret_saved);
141 }
142
143 void
sip_conn_update_proxy_and_transport(SIPConnection * conn)144 sip_conn_update_proxy_and_transport (SIPConnection *conn)
145 {
146 SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (conn);
147
148 if (priv->proxy_url != NULL)
149 {
150 gchar *params = NULL;
151 if (priv->proxy_url->url_type == url_sip)
152 {
153 char transport[5] = "";
154 if (url_param (priv->proxy_url->url_params, "transport",
155 transport, 5) > 0)
156 {
157 if (g_ascii_strcasecmp (transport, "tcp") == 0
158 || g_ascii_strcasecmp (transport, "udp") == 0)
159 params = g_strdup_printf ("transport=%s", transport);
160 else
161 g_message ("unrecognized transport value in the proxy URI: %s", transport);
162 }
163 }
164 nua_set_params (priv->sofia_nua,
165 NUTAG_PROXY(priv->proxy_url),
166 TAG_IF(params, NUTAG_M_PARAMS(params)),
167 TAG_NULL());
168 g_free (params);
169 }
170 }
171
172 const url_t *
sip_conn_get_local_url(SIPConnection * conn)173 sip_conn_get_local_url (SIPConnection *conn)
174 {
175 SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (conn);
176 url_t *url;
177
178 url = url_make (priv->sofia_home, "sip:*:*");
179
180 if (url == NULL)
181 return NULL;
182
183 if (priv->proxy_url != NULL)
184 {
185 url->url_type = priv->proxy_url->url_type;
186 }
187 else
188 {
189 g_assert (priv->account_url != NULL);
190 url->url_type = priv->account_url->url_type;
191 }
192
193 if (priv->local_ip_address == NULL)
194 url->url_host = "*";
195 else
196 url->url_host = priv->local_ip_address;
197
198 if (priv->local_port == 0)
199 url->url_port = "*";
200 else
201 url->url_port = su_sprintf (priv->sofia_home, "%u", priv->local_port);
202
203 if (url->url_type == url_sip && priv->proxy_url != NULL)
204 {
205 char transport[5] = "";
206 if (url_param (priv->proxy_url->url_params, "transport", transport, 5) > 0)
207 {
208 if (!g_ascii_strcasecmp(transport, "udp"))
209 url->url_params = "transport=udp";
210 else if (!g_ascii_strcasecmp(transport, "tcp"))
211 url->url_params = "transport=tcp";
212 }
213 }
214
215 /* url_sanitize (url); -- we're always sane B-] */
216
217 DEBUG("local binding expressed as <" URL_PRINT_FORMAT ">", URL_PRINT_ARGS(url));
218
219 return url;
220 }
221
222 static GHashTable*
priv_nua_get_outbound_options(nua_t * nua)223 priv_nua_get_outbound_options (nua_t* nua)
224 {
225 const char* outbound = NULL;
226 GHashTable* option_table;
227 gchar** options;
228 gchar* token;
229 gboolean value;
230 int i;
231
232 option_table = g_hash_table_new_full (g_str_hash,
233 g_str_equal,
234 g_free,
235 NULL);
236
237 nua_get_params (nua, NUTAG_OUTBOUND_REF(outbound), TAG_END());
238 if (outbound == NULL)
239 return option_table;
240
241 g_debug ("%s: got outbound options %s", G_STRFUNC, outbound);
242
243 options = g_strsplit_set (outbound, " ", 0);
244
245 for (i = 0; (token = options[i]) != NULL; i++)
246 {
247 value = TRUE;
248 /* Look for the negation prefixes */
249 if (g_ascii_strncasecmp (token, "no", 2) == 0)
250 switch (token[2])
251 {
252 case '-':
253 case '_':
254 token += 3;
255 value = FALSE;
256 break;
257 case 'n':
258 case 'N':
259 switch (token[3])
260 {
261 case '-':
262 case '_':
263 token += 4;
264 value = FALSE;
265 break;
266 }
267 break;
268 }
269
270 g_hash_table_insert (option_table,
271 g_strdup (token),
272 GINT_TO_POINTER(value));
273 }
274
275 g_strfreev (options);
276
277 return option_table;
278 }
279
280 static void
priv_nua_outbound_vectorize_walk(gpointer key,gpointer value,gpointer user_data)281 priv_nua_outbound_vectorize_walk (gpointer key,
282 gpointer value,
283 gpointer user_data)
284 {
285 gchar ***pstrv = (gchar ***)user_data;
286 const gchar *option = (const gchar *)key;
287 *(*pstrv)++ = (value)? g_strdup (option) : g_strdup_printf ("no-%s", option);
288 }
289
290 static void
priv_nua_set_outbound_options(nua_t * nua,GHashTable * option_table)291 priv_nua_set_outbound_options (nua_t* nua, GHashTable* option_table)
292 {
293 gchar* outbound;
294 gchar** options;
295 gchar** walker;
296
297 /* construct the option string array */
298 options = g_new(gchar*, g_hash_table_size (option_table) + 1);
299
300 /* fill the array with option tokens and terminate with NULL */
301 walker = options;
302 g_hash_table_foreach (option_table, priv_nua_outbound_vectorize_walk, &walker);
303 *walker = NULL;
304
305 /* concatenate all tokens into a string */
306 outbound = g_strjoinv (" ", options);
307
308 g_strfreev (options);
309
310 g_assert (outbound != NULL);
311
312 /* deliver the option string to the stack */
313 g_debug ("%s: setting outbound options %s", G_STRFUNC, outbound);
314 nua_set_params (nua, NUTAG_OUTBOUND(outbound), TAG_NULL());
315
316 g_free (outbound);
317 }
318
319 void
sip_conn_update_nua_outbound(SIPConnection * conn)320 sip_conn_update_nua_outbound (SIPConnection *conn)
321 {
322 SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (conn);
323 GHashTable *option_table;
324
325 g_return_if_fail (priv->sofia_nua != NULL);
326
327 option_table = priv_nua_get_outbound_options (priv->sofia_nua);
328
329 /* Purge existing occurrences of the affected options */
330 g_hash_table_remove (option_table, "options-keepalive");
331
332 /* Set options that affect keepalive behavior */
333 switch (priv->keepalive_mechanism)
334 {
335 case SIP_CONNECTION_KEEPALIVE_NONE:
336 case SIP_CONNECTION_KEEPALIVE_REGISTER:
337 /* For REGISTER keepalives, we use NUTAG_M_FEATURES */
338 g_hash_table_insert (option_table,
339 g_strdup ("options-keepalive"),
340 GINT_TO_POINTER(FALSE));
341 break;
342 case SIP_CONNECTION_KEEPALIVE_OPTIONS:
343 g_hash_table_insert (option_table,
344 g_strdup ("options-keepalive"),
345 GINT_TO_POINTER(TRUE));
346 break;
347 case SIP_CONNECTION_KEEPALIVE_STUN:
348 /* Not supported */
349 break;
350 case SIP_CONNECTION_KEEPALIVE_AUTO:
351 default:
352 break;
353 }
354
355 g_hash_table_insert (option_table,
356 g_strdup ("natify"),
357 GINT_TO_POINTER(priv->discover_binding));
358 g_hash_table_insert (option_table,
359 g_strdup ("use-rport"),
360 GINT_TO_POINTER(priv->discover_binding));
361
362 /* Hand options back to the NUA */
363
364 priv_nua_set_outbound_options (priv->sofia_nua, option_table);
365
366 g_hash_table_destroy (option_table);
367 }
368
369 static void
priv_sanitize_keepalive_interval(SIPConnectionPrivate * priv)370 priv_sanitize_keepalive_interval (SIPConnectionPrivate *priv)
371 {
372 gint minimum_interval;
373 if (priv->keepalive_interval > 0)
374 {
375 minimum_interval =
376 (priv->keepalive_mechanism == SIP_CONNECTION_KEEPALIVE_REGISTER)
377 ? SIP_CONNECTION_MINIMUM_KEEPALIVE_INTERVAL_REGISTER
378 : SIP_CONNECTION_MINIMUM_KEEPALIVE_INTERVAL;
379 if (priv->keepalive_interval < minimum_interval)
380 {
381 g_warning ("keepalive interval is too low, pushing to %d", minimum_interval);
382 priv->keepalive_interval = minimum_interval;
383 }
384 }
385 }
386
387 void
sip_conn_update_nua_keepalive_interval(SIPConnection * conn)388 sip_conn_update_nua_keepalive_interval (SIPConnection *conn)
389 {
390 SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (conn);
391 long keepalive_interval;
392
393 if (priv->keepalive_mechanism == SIP_CONNECTION_KEEPALIVE_NONE)
394 keepalive_interval = 0;
395 else if (priv->keepalive_interval == 0)
396 /* XXX: figure out proper default timeouts depending on transport */
397 keepalive_interval = SIP_CONNECTION_DEFAULT_KEEPALIVE_INTERVAL;
398 else
399 {
400 priv_sanitize_keepalive_interval (priv);
401 keepalive_interval = (long) priv->keepalive_interval;
402 }
403 keepalive_interval *= 1000;
404
405 DEBUG("setting keepalive interval to %ld msec", keepalive_interval);
406
407 nua_set_params (priv->sofia_nua,
408 NUTAG_KEEPALIVE(keepalive_interval),
409 TAG_NULL());
410 }
411
412 void
sip_conn_update_nua_contact_features(SIPConnection * conn)413 sip_conn_update_nua_contact_features (SIPConnection *conn)
414 {
415 SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (conn);
416 char *contact_features;
417 guint timeout;
418
419 if (priv->keepalive_mechanism != SIP_CONNECTION_KEEPALIVE_REGISTER)
420 return;
421
422 priv_sanitize_keepalive_interval (priv);
423 timeout = (priv->keepalive_interval > 0)
424 ? priv->keepalive_interval
425 : SIP_CONNECTION_DEFAULT_KEEPALIVE_INTERVAL;
426 contact_features = g_strdup_printf ("expires=%u", timeout);
427 nua_set_params(priv->sofia_nua,
428 NUTAG_M_FEATURES(contact_features),
429 TAG_NULL());
430 g_free (contact_features);
431 }
432
433 static void
sip_conn_set_stun_server_address(SIPConnection * conn,const gchar * address)434 sip_conn_set_stun_server_address (SIPConnection *conn, const gchar *address)
435 {
436 SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (conn);
437 g_return_if_fail (priv->media_factory != NULL);
438 g_object_set (priv->media_factory,
439 "stun-server", address,
440 "stun-port", priv->stun_port,
441 NULL);
442 }
443
444 static void
priv_stun_resolver_cb(sres_context_t * ctx,sres_query_t * query,sres_record_t ** answers)445 priv_stun_resolver_cb (sres_context_t *ctx, sres_query_t *query, sres_record_t **answers)
446 {
447 SIPConnection *conn = SIP_CONNECTION (ctx);
448 SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (conn);
449 sres_a_record_t *ans = NULL;
450
451 if (NULL != answers)
452 {
453 int i;
454 GPtrArray *items = g_ptr_array_sized_new (1);
455
456 for (i = 0; NULL != answers[i]; i++)
457 {
458 if ((0 == answers[i]->sr_record->r_status)
459 && (sres_type_a == answers[i]->sr_record->r_type))
460 {
461 g_ptr_array_add (items, answers[i]->sr_a);
462 }
463 }
464
465 if (items->len > 0)
466 {
467 ans = g_ptr_array_index (items, g_random_int_range (0, items->len));
468 }
469
470 g_ptr_array_free (items, TRUE);
471 }
472
473 if (NULL != ans)
474 sip_conn_set_stun_server_address (conn,
475 inet_ntoa (ans->a_addr));
476 else
477 g_debug ("Couldn't resolv STUN server address, ignoring.");
478
479 sres_free_answers (priv->sofia_resolver, answers);
480 }
481
482 void
sip_conn_resolv_stun_server(SIPConnection * conn,const gchar * stun_host)483 sip_conn_resolv_stun_server (SIPConnection *conn, const gchar *stun_host)
484 {
485 SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (conn);
486 struct in_addr test_addr;
487
488 if (stun_host == NULL)
489 {
490 sip_conn_set_stun_server_address (conn, NULL);
491 return;
492 }
493
494 if (inet_aton (stun_host, &test_addr))
495 {
496 sip_conn_set_stun_server_address (conn, stun_host);
497 return;
498 }
499
500 if (NULL == priv->sofia_resolver)
501 {
502 priv->sofia_resolver =
503 sres_resolver_create (priv->sofia->sofia_root, NULL, TAG_END());
504 }
505 g_return_if_fail (priv->sofia_resolver != NULL);
506
507 DEBUG("creating a new resolver query for STUN host name %s", stun_host);
508
509 sres_query (priv->sofia_resolver,
510 priv_stun_resolver_cb,
511 (sres_context_t *) conn,
512 sres_type_a,
513 stun_host);
514 }
515
516 static void
priv_stun_discover_cb(sres_context_t * ctx,sres_query_t * query,sres_record_t ** answers)517 priv_stun_discover_cb (sres_context_t *ctx,
518 sres_query_t *query,
519 sres_record_t **answers)
520 {
521 SIPConnection *conn = SIP_CONNECTION (ctx);
522 SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (conn);
523 sres_srv_record_t *sel = NULL;
524 int n_sel_items = 0;
525 int i;
526
527 if (answers == NULL)
528 return;
529
530 for (i = 0; NULL != answers[i]; i++)
531 {
532 if (answers[i]->sr_record->r_status != 0)
533 continue;
534 if (G_UNLIKELY (answers[i]->sr_record->r_type != sres_type_srv))
535 continue;
536
537 if (sel == NULL
538 || (answers[i]->sr_srv->srv_priority < sel->srv_priority))
539 {
540 sel = answers[i]->sr_srv;
541 n_sel_items = 1;
542 }
543 else if (answers[i]->sr_srv->srv_priority == sel->srv_priority)
544 {
545 n_sel_items++;
546 }
547 }
548
549 if (n_sel_items > 1)
550 {
551 /* Random selection procedure as recommended in RFC 2782 */
552 GArray *items = g_array_sized_new (FALSE,
553 TRUE,
554 sizeof (sres_srv_record_t *),
555 n_sel_items);
556 int sum = 0;
557 int dice;
558 sres_srv_record_t *rec;
559
560 g_assert (sel != NULL);
561
562 for (i = 0; NULL != answers[i]; i++)
563 {
564 if (answers[i]->sr_record->r_status != 0)
565 continue;
566 if (G_UNLIKELY (answers[i]->sr_record->r_type != sres_type_srv))
567 continue;
568
569 rec = answers[i]->sr_srv;
570 if (rec->srv_priority != sel->srv_priority)
571 continue;
572
573 if (rec->srv_weight == 0)
574 g_array_prepend_val (items, rec);
575 else
576 g_array_append_val (items, rec);
577 }
578
579 g_assert (n_sel_items == items->len);
580
581 for (i = 0; i < n_sel_items; i++)
582 {
583 rec = g_array_index (items, sres_srv_record_t *, i);
584 sum = (rec->srv_weight += sum);
585 }
586
587 dice = g_random_int_range (0, sum + 1);
588
589 for (i = 0; i < n_sel_items; i++)
590 {
591 rec = g_array_index (items, sres_srv_record_t *, i);
592 if (rec->srv_weight >= dice)
593 {
594 sel = rec;
595 break;
596 }
597 }
598
599 g_array_free (items, TRUE);
600 }
601
602 if (sel != NULL)
603 {
604 DEBUG ("discovery got STUN server %s:%u",
605 sel->srv_target, sel->srv_port);
606 priv->stun_port = sel->srv_port;
607 sip_conn_resolv_stun_server (conn, sel->srv_target);
608 }
609
610 sres_free_answers (priv->sofia_resolver, answers);
611 }
612
613 void
sip_conn_discover_stun_server(SIPConnection * conn)614 sip_conn_discover_stun_server (SIPConnection *conn)
615 {
616 SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (conn);
617 char *srv_domain;
618
619 if ((NULL == priv->account_url) || (NULL == priv->account_url->url_host))
620 {
621 DEBUG("unknown domain, not making STUN SRV lookup");
622 return;
623 }
624
625 if (NULL == priv->sofia_resolver)
626 {
627 priv->sofia_resolver =
628 sres_resolver_create (priv->sofia->sofia_root, NULL, TAG_END());
629 }
630 g_return_if_fail (priv->sofia_resolver != NULL);
631
632 DEBUG("creating a new STUN SRV query for domain %s", priv->account_url->url_host);
633
634 srv_domain = g_strdup_printf ("_stun._udp.%s", priv->account_url->url_host);
635
636 sres_query (priv->sofia_resolver,
637 priv_stun_discover_cb,
638 (sres_context_t *) conn,
639 sres_type_srv,
640 srv_domain);
641
642 g_free (srv_domain);
643 }
644
645 static gboolean
priv_is_user_unreserved(gchar x)646 priv_is_user_unreserved (gchar x)
647 {
648 switch (x)
649 {
650 case '-':
651 case '_':
652 case '.':
653 case '!':
654 case '~':
655 case '*':
656 case '\'':
657 case '(':
658 case ')':
659 case '&':
660 case '=':
661 case '+':
662 case '$':
663 case ',':
664 case ':':
665 case '?':
666 case ';':
667 case '/':
668 return TRUE;
669 default:
670 return g_ascii_isalnum (x);
671 }
672 }
673
674 static gboolean
priv_is_host(gchar x)675 priv_is_host (gchar x)
676 {
677 switch (x)
678 {
679 case '.':
680 case '-':
681 return TRUE;
682 default:
683 return g_ascii_isalnum (x);
684 }
685 }
686
687 static gchar *
priv_user_encode(su_home_t * home,const gchar * string)688 priv_user_encode (su_home_t *home, const gchar *string)
689 {
690 const gchar *a;
691 gchar *b;
692 gchar *res = su_zalloc (home, strlen (string) * 3 + 1);
693
694 g_return_val_if_fail (res != NULL, NULL);
695
696 a = string;
697 b = res;
698 while (*a)
699 {
700 if (priv_is_user_unreserved (*a))
701 {
702 *b++ = *a++;
703 }
704 else
705 {
706 snprintf (b, 4, "%%%02x", (guint) *a);
707 ++a;
708 b += 3;
709 }
710 }
711
712 return res;
713 }
714
715 /* unescape characters that don't need escaping */
716 static gchar *
priv_user_decode(su_home_t * home,const gchar * string)717 priv_user_decode (su_home_t *home, const gchar *string)
718 {
719 const gchar *a;
720 gchar *b;
721 gchar *res = su_zalloc (home, strlen (string) + 1);
722
723 g_return_val_if_fail (res != NULL, NULL);
724
725 a = string;
726 b = res;
727 while (*a)
728 {
729 if ((a[0] == '%') && g_ascii_isxdigit(a[1]) && g_ascii_isxdigit(a[2]))
730 {
731 gchar tmp[3] = { a[1], a[2], 0 };
732 gchar x = (gchar) (strtoul (tmp, NULL, 16));
733 if (priv_is_user_unreserved (x))
734 {
735 *b++ = x;
736 a += 3;
737 continue;
738 }
739 }
740 *b++ = *a++;
741 }
742
743 return res;
744 }
745
746 static gboolean
priv_is_tel_num(const gchar * string)747 priv_is_tel_num (const gchar *string)
748 {
749 const gchar *pc;
750 gboolean has_digits = FALSE;
751
752 g_return_val_if_fail (string != NULL, FALSE);
753
754 /* skip the initial whitespace */
755 pc = string + strspn (string, " \t");
756
757 /* the leading '+' is acceptable */
758 if (*pc == '+')
759 ++pc;
760
761 /* only digits, delimiters and inline whitespace */
762 while (*pc)
763 {
764 if (g_ascii_isdigit (*pc))
765 has_digits = TRUE;
766 else
767 switch (*pc)
768 {
769 case ' ':
770 case '\t':
771 case '-':
772 case '.':
773 case '(':
774 case ')':
775 break;
776 default:
777 return FALSE;
778 }
779 ++pc;
780 }
781
782 return has_digits;
783 }
784
785 static gchar *
priv_strip_whitespace(su_home_t * home,const gchar * string)786 priv_strip_whitespace (su_home_t *home, const gchar *string)
787 {
788 const gchar *a;
789 gchar *b;
790 gchar *res = su_zalloc (home, strlen (string) + 1);
791
792 g_return_val_if_fail (res != NULL, NULL);
793
794 b = res;
795 for (a = string; *a; a++)
796 {
797 if (!g_ascii_isspace (*a))
798 *b++ = *a;
799 }
800 *b = '\0';
801
802 return res;
803 }
804
805 gchar *
sip_conn_normalize_uri(SIPConnection * conn,const gchar * sipuri,GError ** error)806 sip_conn_normalize_uri (SIPConnection *conn,
807 const gchar *sipuri,
808 GError **error)
809 {
810 SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (conn);
811 su_home_t home[1] = { SU_HOME_INIT (home) };
812 url_t *url = NULL;;
813 gchar *retval = NULL;
814 char *c, *str;
815
816 url = url_make (home, sipuri);
817
818 /* we got username or phone number, local to our domain */
819 if ((url == NULL) ||
820 ((url->url_scheme == NULL) && (url->url_user == NULL)))
821 {
822 if ((priv->account_url == NULL) || (priv->account_url->url_host == NULL))
823 {
824 g_debug ("local uri specified and we don't know local domain yet");
825 goto error;
826 }
827
828 if (priv_is_tel_num (sipuri))
829 {
830 url = url_format (home, "sip:%s@%s;user=phone",
831 priv_strip_whitespace (home, sipuri),
832 priv->account_url->url_host);
833 }
834 else
835 {
836 url = url_format (home, "sip:%s@%s",
837 priv_user_encode (home, sipuri),
838 priv->account_url->url_host);
839 }
840 if (!url) goto error;
841 }
842 else
843 {
844 if ((url != NULL) && (url->url_user != NULL))
845 {
846 url->url_user = (char *) priv_user_decode (home, url->url_user);
847 }
848 }
849
850 if (url_sanitize (url)) goto error;
851
852 /* scheme and host should've been set by now */
853 if (!url->url_scheme || (url->url_scheme[0] == 0) ||
854 !url->url_host || (url->url_host[0] == 0))
855 goto error;
856
857 for (c = (char *) url->url_host; *c; c++)
858 {
859 /* check for illegal characters */
860 if (!priv_is_host (*c))
861 goto error;
862
863 /* convert host to lowercase */
864 *c = g_ascii_tolower (*c);
865 }
866 /* check that the hostname isn't empty */
867 if (c == url->url_host) goto error;
868
869 /* check that if we have '@', the username isn't empty */
870 if (url->url_user)
871 {
872 if (url->url_user[0] == 0) goto error;
873 }
874
875 str = url_as_string (home, url);
876 if (NULL == str) goto error;
877
878 retval = g_strdup (str);
879
880 error:
881 if (NULL == retval)
882 g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
883 "invalid SIP URI");
884
885 /* success */
886 su_home_deinit (home);
887 return retval;
888 }
889
890