Added SILC Thread Queue API
[silc.git] / lib / silcutil / silcnet.c
1 /*
2
3   silcnet.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 1997 - 2007 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; version 2 of the License.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18 */
19 /* $Id$ */
20
21 #include "silc.h"
22
23 /* Returns bound port from listener */
24
25 SilcUInt16 *silc_net_listener_get_port(SilcNetListener listener,
26                                        SilcUInt32 *port_count)
27 {
28   SilcUInt16 *ports;
29   int i;
30
31   ports = silc_calloc(listener->socks_count, sizeof(*ports));
32   if (!ports)
33     return NULL;
34
35   for (i = 0; i < listener->socks_count; i++)
36     ports[i] = silc_net_get_local_port(listener->socks[i]);
37
38   if (port_count)
39     *port_count = listener->socks_count;
40
41   return ports;
42 }
43
44 /* Return bound IP from listener */
45
46 char **silc_net_listener_get_ip(SilcNetListener listener,
47                                 SilcUInt32 *ip_count)
48 {
49   char **ips = NULL, *ip;
50   int i, k;
51
52   ips = silc_calloc(listener->socks_count, sizeof(*ips));
53   if (!ips)
54     return NULL;
55
56   for (i = 0, k = 0; i < listener->socks_count; i++) {
57     if (silc_net_check_local_by_sock(listener->socks[i], NULL, &ip))
58       ips[k++] = ip;
59   }
60
61   if (ip_count)
62     *ip_count = k;
63
64   return ips;
65 }
66
67 /* Return bound hostname from listener */
68
69 char **silc_net_listener_get_hostname(SilcNetListener listener,
70                                       SilcUInt32 *hostname_count)
71 {
72   char **hs = NULL, *h;
73   int i, k;
74
75   hs = silc_calloc(listener->socks_count, sizeof(*hs));
76   if (!hs)
77     return NULL;
78
79   for (i = 0, k = 0; i < listener->socks_count; i++) {
80     if (silc_net_check_local_by_sock(listener->socks[i], &h, NULL))
81       hs[k++] = h;
82   }
83
84   if (hostname_count)
85     *hostname_count = k;
86
87   return hs;
88 }
89
90 /* Accepts a connection from a particular socket */
91
92 int silc_net_accept_connection(int sock)
93 {
94   int ret = accept(sock, 0, 0);
95   if (ret < 0)
96     silc_set_errno_posix(errno);
97   return ret;
98 }
99
100 /* Sets a option for a socket. */
101
102 int silc_net_set_socket_opt(int sock, int level, int option, int on)
103 {
104   int ret = setsockopt(sock, level, option, (void *)&on, sizeof(on));
105   if (ret < 0)
106     silc_set_errno_posix(errno);
107   return ret;
108 }
109
110 /* Get socket options */
111
112 int silc_net_get_socket_opt(int sock, int level, int option,
113                             void *optval, int *opt_len)
114 {
115   int ret = getsockopt(sock, level, option, optval, opt_len);
116   if (ret < 0)
117     silc_set_errno_posix(errno);
118   return ret;
119 }
120
121 /* Checks whether IP address sent as argument is valid IPv4 address. */
122
123 SilcBool silc_net_is_ip4(const char *addr)
124 {
125   int count = 0;
126
127   while (*addr) {
128     if (*addr != '.' && !isdigit((int)*addr))
129       return FALSE;
130     if (*addr == '.')
131       count++;
132     addr++;
133   }
134
135   if (count != 3)
136     return FALSE;
137
138   return TRUE;
139 }
140
141 /* Checks whether IP address sent as argument is valid IPv6 address. */
142
143 SilcBool silc_net_is_ip6(const char *addr)
144 {
145   /* XXX does this work with all kinds of IPv6 addresses? */
146   while (*addr) {
147     if (*addr != ':' && !isxdigit((int)*addr))
148       return FALSE;
149     addr++;
150   }
151
152   return TRUE;
153 }
154
155 /* Checks whether IP address sent as argument is valid IP address. */
156
157 SilcBool silc_net_is_ip(const char *addr)
158 {
159   if (silc_net_is_ip4(addr))
160     return TRUE;
161   return silc_net_is_ip6(addr);
162 }
163
164 /* Internal context for async resolving */
165 typedef struct {
166   SilcNetResolveCallback completion;
167   void *context;
168   SilcBool prefer_ipv6;
169   SilcSchedule schedule;
170   char *input;
171   char *result;
172 } *SilcNetResolveContext;
173
174 SILC_TASK_CALLBACK(silc_net_resolve_completion)
175 {
176   SilcNetResolveContext r = (SilcNetResolveContext)context;
177
178   /* Call the completion callback */
179   if (r->completion)
180     (*r->completion)(r->result, r->context);
181
182   silc_free(r->input);
183   silc_free(r->result);
184   silc_free(r);
185 }
186
187 /* Thread function to resolve the address for hostname. */
188
189 static void *silc_net_gethostbyname_thread(void *context)
190 {
191   SilcNetResolveContext r = (SilcNetResolveContext)context;
192   SilcSchedule schedule = r->schedule;
193   char tmp[64];
194
195   if (silc_net_gethostbyname(r->input, r->prefer_ipv6, tmp, sizeof(tmp)))
196     r->result = silc_strdup(tmp);
197
198   silc_schedule_task_add(schedule, 0, silc_net_resolve_completion, r, 0, 1,
199                          SILC_TASK_TIMEOUT);
200   silc_schedule_wakeup(schedule);
201   return NULL;
202 }
203
204 /* Thread function to resolve the hostname for address. */
205
206 static void *silc_net_gethostbyaddr_thread(void *context)
207 {
208   SilcNetResolveContext r = (SilcNetResolveContext)context;
209   SilcSchedule schedule = r->schedule;
210   char tmp[256];
211
212   if (silc_net_gethostbyaddr(r->input, tmp, sizeof(tmp)))
213     r->result = silc_strdup(tmp);
214
215   silc_schedule_task_add(schedule, 0, silc_net_resolve_completion, r, 0, 1,
216                          SILC_TASK_TIMEOUT);
217   silc_schedule_wakeup(schedule);
218   return NULL;
219 }
220
221 /* Resolves IP address for hostname. */
222
223 SilcBool silc_net_gethostbyname(const char *name,
224                                 SilcBool prefer_ipv6, char *address,
225                                 SilcUInt32 address_len)
226 {
227 #ifdef HAVE_IPV6
228   struct addrinfo hints, *ai, *tmp, *ip4 = NULL, *ip6 = NULL;
229
230   memset(&hints, 0, sizeof(hints));
231   hints.ai_socktype = SOCK_STREAM;
232   if (getaddrinfo(name, NULL, &hints, &ai))
233     return FALSE;
234
235   for (tmp = ai; tmp; tmp = tmp->ai_next) {
236     if (tmp->ai_family == AF_INET6) {
237       ip6 = tmp;
238       if (ip4)
239         break;
240       continue;
241     }
242     if (tmp->ai_family == AF_INET) {
243       ip4 = tmp;
244       if (ip6)
245         break;
246       continue;
247     }
248   }
249
250   tmp = (prefer_ipv6 ? (ip6 ? ip6 : ip4) : (ip4 ? ip4 : ip6));
251   if (!tmp) {
252     freeaddrinfo(ai);
253     return FALSE;
254   }
255
256   if (getnameinfo(tmp->ai_addr, tmp->ai_addrlen, address,
257                   address_len, NULL, 0, NI_NUMERICHOST)) {
258     freeaddrinfo(ai);
259     return FALSE;
260   }
261
262   freeaddrinfo(ai);
263 #else
264   struct hostent *hp;
265   struct in_addr ip;
266   char *tmp;
267
268   if (silc_net_is_ip4(name)) {
269     memset(address, 0, address_len);
270     if (address_len < strlen(name))
271       return FALSE;
272     strncpy(address, name, strlen(name));
273     return TRUE;
274   }
275
276   hp = gethostbyname(name);
277   if (!hp)
278     return FALSE;
279
280   memcpy(&ip.s_addr, hp->h_addr_list[0], 4);
281   tmp = inet_ntoa(ip);
282   if (!tmp)
283     return FALSE;
284   if (address_len < strlen(tmp))
285     return FALSE;
286   memset(address, 0, address_len);
287   strncpy(address, tmp, strlen(tmp));
288 #endif
289
290   return TRUE;
291 }
292
293 /* Resolves IP address for hostname async. */
294
295 void silc_net_gethostbyname_async(const char *name,
296                                   SilcBool prefer_ipv6,
297                                   SilcSchedule schedule,
298                                   SilcNetResolveCallback completion,
299                                   void *context)
300 {
301   SilcNetResolveContext r = silc_calloc(1, sizeof(*r));
302
303   if (!schedule) {
304     schedule = silc_schedule_get_global();
305     SILC_VERIFY(schedule);
306   }
307
308   r->completion = completion;
309   r->context = context;
310   r->prefer_ipv6 = prefer_ipv6;
311   r->schedule = schedule;
312   r->input = silc_strdup(name);
313
314   silc_thread_create(silc_net_gethostbyname_thread, r, FALSE);
315 }
316
317 /* Resolves hostname by IP address. */
318
319 SilcBool silc_net_gethostbyaddr(const char *addr, char *name,
320                                 SilcUInt32 name_len)
321 {
322 #ifdef HAVE_IPV6
323   struct addrinfo req, *ai;
324
325   memset(&req, 0, sizeof(req));
326   req.ai_socktype = SOCK_STREAM;
327   req.ai_flags = AI_CANONNAME;
328
329   if (getaddrinfo(addr, NULL, &req, &ai))
330     return FALSE;
331   if (getnameinfo(ai->ai_addr, ai->ai_addrlen, name, name_len, NULL, 0, 0)) {
332     freeaddrinfo(ai);
333     return FALSE;
334   }
335   freeaddrinfo(ai);
336 #else
337   struct hostent *hp;
338   unsigned char a[4];
339
340   if (!silc_net_addr2bin(addr, a, sizeof(a)))
341     return FALSE;
342
343   hp = gethostbyaddr(a, 4, AF_INET);
344   if (!hp)
345     return FALSE;
346   if (name_len < strlen(hp->h_name))
347     return FALSE;
348   memset(name, 0, name_len);
349   strncpy(name, hp->h_name, strlen(hp->h_name));
350 #endif
351
352   return TRUE;
353 }
354
355 /* Resolves hostname by IP address async. */
356
357 void silc_net_gethostbyaddr_async(const char *addr,
358                                   SilcSchedule schedule,
359                                   SilcNetResolveCallback completion,
360                                   void *context)
361 {
362   SilcNetResolveContext r = silc_calloc(1, sizeof(*r));
363
364   if (!schedule) {
365     schedule = silc_schedule_get_global();
366     SILC_VERIFY(schedule);
367   }
368
369   r->completion = completion;
370   r->context = context;
371   r->schedule = schedule;
372   r->input = silc_strdup(addr);
373
374   silc_thread_create(silc_net_gethostbyaddr_thread, r, FALSE);
375 }
376
377 #ifndef SILC_SYMBIAN
378
379 /* Performs lookups for remote name and IP address. This peforms reverse
380    lookup as well to verify that the IP has FQDN. */
381
382 SilcBool silc_net_check_host_by_sock(SilcSocket sock, char **hostname,
383                                      char **ip)
384 {
385   char host[1024];
386   int rval, len;
387
388 #ifdef HAVE_IPV6
389   struct sockaddr_storage remote;
390   char s[NI_MAXHOST];
391
392   if (hostname)
393     *hostname = NULL;
394   *ip = NULL;
395
396   SILC_LOG_DEBUG(("Resolving remote hostname and IP address"));
397
398   memset(&remote, 0, sizeof(remote));
399   memset(&s, 0, sizeof(s));
400   len = sizeof(remote);
401   rval = getpeername(sock, (struct sockaddr *)&remote, &len);
402   if (rval < 0)
403     return FALSE;
404
405   if (getnameinfo((struct sockaddr *)&remote, len, s, sizeof(s), NULL, 0,
406                   NI_NUMERICHOST))
407     return FALSE;
408
409   *ip = silc_memdup(s, strlen(s));
410   if (*ip == NULL)
411     return FALSE;
412 #else
413   struct sockaddr_in remote;
414   char *host_ip;
415
416   if (hostname)
417     *hostname = NULL;
418   *ip = NULL;
419
420   SILC_LOG_DEBUG(("Resolving remote hostname and IP address"));
421
422   memset(&remote, 0, sizeof(remote));
423   len = sizeof(remote);
424   rval = getpeername(sock, (struct sockaddr *)&remote, &len);
425   if (rval < 0)
426     return FALSE;
427
428   host_ip = inet_ntoa(remote.sin_addr);
429   if (!host_ip)
430     return FALSE;
431
432   *ip = silc_memdup(host_ip, strlen(host_ip));
433   if (*ip == NULL)
434     return FALSE;
435 #endif
436
437   /* Do reverse lookup if we want hostname too. */
438   if (hostname) {
439     /* Get host by address */
440     if (!silc_net_gethostbyaddr(*ip, host, sizeof(host)))
441       return FALSE;
442
443     *hostname = silc_memdup(host, strlen(host));
444     SILC_LOG_DEBUG(("Resolved hostname `%s'", *hostname));
445
446     /* Reverse */
447     if (!silc_net_gethostbyname(*hostname, TRUE, host, sizeof(host)))
448       return FALSE;
449
450     if (strcmp(*ip, host))
451       return FALSE;
452   }
453
454   SILC_LOG_DEBUG(("Resolved IP address `%s'", *ip));
455   return TRUE;
456 }
457
458 /* Performs lookups for local name and IP address. This peforms reverse
459    lookup as well to verify that the IP has FQDN. */
460
461 SilcBool silc_net_check_local_by_sock(SilcSocket sock, char **hostname,
462                                       char **ip)
463 {
464   char host[1024];
465   int rval, len;
466
467 #ifdef HAVE_IPV6
468   struct sockaddr_storage local;
469   char s[NI_MAXHOST];
470
471   if (hostname)
472     *hostname = NULL;
473   *ip = NULL;
474
475   SILC_LOG_DEBUG(("Resolving local hostname and IP address"));
476
477   memset(&local, 0, sizeof(local));
478   memset(&s, 0, sizeof(s));
479   len = sizeof(local);
480   rval = getsockname(sock, (struct sockaddr *)&local, &len);
481   if (rval < 0)
482     return FALSE;
483
484   if (getnameinfo((struct sockaddr *)&local, len, s, sizeof(s), NULL, 0,
485                   NI_NUMERICHOST))
486     return FALSE;
487
488   *ip = silc_memdup(s, strlen(s));
489   if (*ip == NULL)
490     return FALSE;
491 #else
492   struct sockaddr_in local;
493   char *host_ip;
494
495   if (hostname)
496     *hostname = NULL;
497   *ip = NULL;
498
499   SILC_LOG_DEBUG(("Resolving local hostname and IP address"));
500
501   memset(&local, 0, sizeof(local));
502   len = sizeof(local);
503   rval = getsockname(sock, (struct sockaddr *)&local, &len);
504   if (rval < 0)
505     return FALSE;
506
507   host_ip = inet_ntoa(local.sin_addr);
508   if (!host_ip)
509     return FALSE;
510
511   *ip = silc_memdup(host_ip, strlen(host_ip));
512   if (*ip == NULL)
513     return FALSE;
514 #endif
515
516   /* Do reverse lookup if we want hostname too. */
517   if (hostname) {
518     /* Get host by address */
519     if (!silc_net_gethostbyaddr(*ip, host, sizeof(host)))
520       return FALSE;
521
522     *hostname = silc_memdup(host, strlen(host));
523     SILC_LOG_DEBUG(("Resolved hostname `%s'", *hostname));
524
525     /* Reverse */
526     if (!silc_net_gethostbyname(*hostname, TRUE, host, sizeof(host)))
527       return FALSE;
528
529     if (strcmp(*ip, host))
530       return FALSE;
531   }
532
533   SILC_LOG_DEBUG(("Resolved IP address `%s'", *ip));
534   return TRUE;
535 }
536
537 /* Return remote port by socket. */
538
539 SilcUInt16 silc_net_get_remote_port(SilcSocket sock)
540 {
541 #ifdef HAVE_IPV6
542   struct sockaddr_storage remote;
543   int len;
544   char s[NI_MAXSERV];
545
546   memset(&remote, 0, sizeof(remote));
547   len = sizeof(remote);
548   if (getpeername(sock, (struct sockaddr *)&remote, &len) < 0)
549     return 0;
550
551   if (getnameinfo((struct sockaddr *)&remote, len, NULL, 0, s, sizeof(s),
552                   NI_NUMERICSERV))
553     return 0;
554
555   return atoi(s);
556 #else
557   struct sockaddr_in remote;
558   int len;
559
560   memset(&remote, 0, sizeof(remote));
561   len = sizeof(remote);
562   if (getpeername(sock, (struct sockaddr *)&remote, &len) < 0)
563     return 0;
564
565   return ntohs(remote.sin_port);
566 #endif
567 }
568
569 /* Return local port by socket. */
570
571 SilcUInt16 silc_net_get_local_port(SilcSocket sock)
572 {
573 #ifdef HAVE_IPV6
574   struct sockaddr_storage local;
575   int len;
576   char s[NI_MAXSERV];
577
578   memset(&local, 0, sizeof(local));
579   len = sizeof(local);
580   if (getsockname(sock, (struct sockaddr *)&local, &len) < 0)
581     return 0;
582
583   if (getnameinfo((struct sockaddr *)&local, len, NULL, 0, s, sizeof(s),
584                   NI_NUMERICSERV))
585     return 0;
586
587   return atoi(s);
588 #else
589   struct sockaddr_in local;
590   int len;
591
592   memset(&local, 0, sizeof(local));
593   len = sizeof(local);
594   if (getsockname(sock, (struct sockaddr *)&local, &len) < 0)
595     return 0;
596
597   return ntohs(local.sin_port);
598 #endif
599 }
600 #endif /* !SILC_SYMBIAN */
601
602 /* Return name of localhost. */
603
604 char *silc_net_localhost(void)
605 {
606   char hostname[256], ip_addr[64];
607
608   if (gethostname(hostname, sizeof(hostname)))
609     return NULL;
610
611   if (!silc_net_gethostbyname(hostname, TRUE, ip_addr, sizeof(ip_addr)))
612     return silc_strdup(hostname);
613
614   silc_net_gethostbyaddr(ip_addr, hostname, sizeof(hostname));
615   return silc_strdup(hostname);
616 }
617
618 /* Returns local IP address */
619
620 char *silc_net_localip(void)
621 {
622   char hostname[256], ip_addr[64];
623
624   if (gethostname(hostname, sizeof(hostname)))
625     return NULL;
626
627   if (!silc_net_gethostbyname(hostname, TRUE, ip_addr, sizeof(ip_addr)))
628     return NULL;
629
630   return silc_strdup(ip_addr);
631 }