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