small fix for hostport.c on freebsd
[libradsec.git] / hostport.c
1 /*
2  * Copyright (C) 2006-2009 Stig Venaas <venaas@uninett.no>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  */
8
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <string.h>
12 #include <netdb.h>
13 #include <netinet/in.h>
14 #include "debug.h"
15 #include "util.h"
16 #include "list.h"
17 #include "hostport.h"
18
19 void freehostport(struct hostportres *hp) {
20     if (hp) {
21         free(hp->host);
22         free(hp->port);
23         if (hp->addrinfo)
24             freeaddrinfo(hp->addrinfo);
25         free(hp);
26     }
27 }
28
29 static int parsehostport(struct hostportres *hp, char *hostport, char *default_port) {
30     char *p, *field;
31     int ipv6 = 0;
32
33     if (!hostport) {
34         hp->port = default_port ? stringcopy(default_port, 0) : NULL;
35         return 1;
36     }
37     p = hostport;
38     /* allow literal addresses and port, e.g. [2001:db8::1]:1812 */
39     if (*p == '[') {
40         p++;
41         field = p;
42         for (; *p && *p != ']' && *p != ' ' && *p != '\t' && *p != '\n'; p++);
43         if (*p != ']') {
44             debug(DBG_ERR, "no ] matching initial [");
45             return 0;
46         }
47         ipv6 = 1;
48     } else {
49         field = p;
50         for (; *p && *p != ':' && *p != '/' && *p != ' ' && *p != '\t' && *p != '\n'; p++);
51     }
52     if (field == p) {
53         debug(DBG_ERR, "missing host/address");
54         return 0;
55     }
56
57     hp->host = stringcopy(field, p - field);
58     if (ipv6) {
59         p++;
60         if (*p && *p != ':' && *p != '/' && *p != ' ' && *p != '\t' && *p != '\n') {
61             debug(DBG_ERR, "unexpected character after ]");
62             return 0;
63         }
64     }
65     if (*p == ':') {
66             /* port number or service name is specified */;
67             field = ++p;
68             for (; *p && *p != ' ' && *p != '\t' && *p != '\n'; p++);
69             if (field == p) {
70                 debug(DBG_ERR, "syntax error, : but no following port");
71                 return 0;
72             }
73             hp->port = stringcopy(field, p - field);
74     } else
75         hp->port = default_port ? stringcopy(default_port, 0) : NULL;
76     return 1;
77 }
78     
79 struct hostportres *newhostport(char *hostport, char *default_port, uint8_t prefixok) {
80     struct hostportres *hp;
81     char *slash, *s;
82     int plen;
83     
84     hp = malloc(sizeof(struct hostportres));
85     if (!hp) {
86         debug(DBG_ERR, "resolve_newhostport: malloc failed");
87         goto errexit;
88     }
89     memset(hp, 0, sizeof(struct hostportres));
90
91     if (!parsehostport(hp, hostport, default_port))
92         goto errexit;
93
94     if (hp->host && !strcmp(hp->host, "*")) {
95         free(hp->host);
96         hp->host = NULL;
97     }
98
99     slash = hostport ? strchr(hostport, '/') : NULL;
100     if (slash) {
101         if (!prefixok) {
102             debug(DBG_WARN, "newhostport: prefix not allowed here", hp->host);
103             goto errexit;
104         }
105         s = slash + 1;
106         if (!*s) {
107             debug(DBG_WARN, "newhostport: prefix length must be specified after the / in %s", hp->host);
108             goto errexit;
109         }
110         for (; *s; s++)
111             if (*s < '0' || *s > '9') {
112                 debug(DBG_WARN, "newhostport: %s in %s is not a valid prefix length", slash + 1, hp->host);
113                 goto errexit;
114             }
115         plen = atoi(slash + 1);
116         if (plen < 0 || plen > 128) {
117             debug(DBG_WARN, "newhostport: %s in %s is not a valid prefix length", slash + 1, hp->host);
118             goto errexit;
119         }
120         hp->prefixlen = plen;
121     } else
122         hp->prefixlen = 255;
123     return hp;
124
125  errexit:
126     freehostport(hp);
127     return NULL;
128 }
129
130 int resolvehostport(struct hostportres *hp, int socktype, uint8_t passive) {
131     struct addrinfo hints, *res;
132
133     memset(&hints, 0, sizeof(hints));
134     hints.ai_socktype = socktype;
135     hints.ai_family = AF_UNSPEC;
136     if (passive)
137         hints.ai_flags = AI_PASSIVE;
138     
139     if (!hp->host && !hp->port) {
140         /* getaddrinfo() doesn't like host and port to be NULL */
141         if (getaddrinfo(hp->host, "1812" /* can be anything */, &hints, &hp->addrinfo)) {
142             debug(DBG_WARN, "resolvehostport: can't resolve (null) port (null)");
143             goto errexit;
144         }
145         for (res = hp->addrinfo; res; res = res->ai_next)
146             port_set(res->ai_addr, 0);
147     } else {
148         if (hp->prefixlen != 255)
149             hints.ai_flags |= AI_NUMERICHOST;
150         if (getaddrinfo(hp->host, hp->port, &hints, &hp->addrinfo)) {
151             debug(DBG_WARN, "resolvehostport: can't resolve %s port %s", hp->host ? hp->host : "(null)", hp->port ? hp->port : "(null)");
152             goto errexit;
153         }
154         if (hp->prefixlen != 255) {
155             switch (hp->addrinfo->ai_family) {
156             case AF_INET:
157                 if (hp->prefixlen > 32) {
158                     debug(DBG_WARN, "resolvehostport: prefix length must be <= 32 in %s", hp->host);
159                     goto errexit;
160                 }
161                 break;
162             case AF_INET6:
163                 break;
164             default:
165                 debug(DBG_WARN, "resolvehostport: prefix must be IPv4 or IPv6 in %s", hp->host);
166                 goto errexit;
167             }
168         }
169     }
170     return 1;
171
172  errexit:
173     if (hp->addrinfo)
174         freeaddrinfo(hp->addrinfo);
175     return 0;
176 }         
177
178 int addhostport(struct list **hostports, char **hostport, char *portdefault, uint8_t prefixok) {
179     struct hostportres *hp;
180     int i;
181
182     if (!*hostports) {
183         *hostports = list_create();
184         if (!*hostports) {
185             debug(DBG_ERR, "addhostport: malloc failed");
186             return 0;
187         }
188     }
189     
190     for (i = 0; hostport[i]; i++) {
191         hp = newhostport(hostport[i], portdefault, prefixok);
192         if (!hp)
193             return 0;
194         if (!list_push(*hostports, hp)) {
195             freehostport(hp);
196             debug(DBG_ERR, "addhostport: malloc failed");
197             return 0;
198         }
199     }
200     return 1;
201 }
202
203 void freehostports(struct list *hostports) {
204     struct hostportres *hp;
205
206     while ((hp = (struct hostportres *)list_shift(hostports)))
207         freehostport(hp);
208     list_destroy(hostports);
209 }
210
211 int resolvehostports(struct list *hostports, int socktype) {
212     struct list_node *entry;
213     struct hostportres *hp;
214     
215     for (entry = list_first(hostports); entry; entry = list_next(entry)) {
216         hp = (struct hostportres *)entry->data;
217         if (!hp->addrinfo && !resolvehostport(hp, socktype, 0))
218             return 0;
219     }
220     return 1;
221 }
222
223 struct addrinfo *resolvepassiveaddrinfo(char *hostport, char *default_port, int socktype) {
224     struct addrinfo *ai = NULL;
225     struct hostportres *hp = newhostport(hostport, default_port, 0);
226     if (hp && resolvehostport(hp, socktype, 1)) {
227         ai = hp->addrinfo;
228         hp->addrinfo = NULL;
229     }
230     freehostport(hp);
231     return ai;
232 }
233
234 /* returns 1 if the len first bits are equal, else 0 */
235 static int prefixmatch(void *a1, void *a2, uint8_t len) {
236     static uint8_t mask[] = { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe };
237     uint8_t r, l = len / 8;
238     if (l && memcmp(a1, a2, l))
239         return 0;
240     r = len % 8;
241     if (!r)
242         return 1;
243     return (((uint8_t *)a1)[l] & mask[r]) == (((uint8_t *)a2)[l] & mask[r]);
244 }
245
246 int addressmatches(struct list *hostports, struct sockaddr *addr) {
247     struct sockaddr_in6 *sa6 = NULL;
248     struct in_addr *a4 = NULL;
249     struct addrinfo *res;
250     struct list_node *entry;
251     struct hostportres *hp = NULL;
252     
253     if (addr->sa_family == AF_INET6) {
254         sa6 = (struct sockaddr_in6 *)addr;
255         if (IN6_IS_ADDR_V4MAPPED(&sa6->sin6_addr)) {
256             a4 = (struct in_addr *)&sa6->sin6_addr.s6_addr[12];
257             sa6 = NULL;
258         }
259     } else
260         a4 = &((struct sockaddr_in *)addr)->sin_addr;
261
262     for (entry = list_first(hostports); entry; entry = list_next(entry)) {
263         hp = (struct hostportres *)entry->data;
264         for (res = hp->addrinfo; res; res = res->ai_next)
265             if (hp->prefixlen == 255) {
266                 if ((a4 && res->ai_family == AF_INET &&
267                      !memcmp(a4, &((struct sockaddr_in *)res->ai_addr)->sin_addr, 4)) ||
268                     (sa6 && res->ai_family == AF_INET6 &&
269                      !memcmp(&sa6->sin6_addr, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, 16)))
270                     return 1;
271             } else {
272                 if ((a4 && res->ai_family == AF_INET &&
273                      prefixmatch(a4, &((struct sockaddr_in *)res->ai_addr)->sin_addr, hp->prefixlen)) ||
274                     (sa6 && res->ai_family == AF_INET6 &&
275                      prefixmatch(&sa6->sin6_addr, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, hp->prefixlen)))
276                     return 1;
277             }
278     }
279     return 0;
280 }
281
282 int connecttcphostlist(struct list *hostports,  struct addrinfo *src) {
283     int s;
284     struct list_node *entry;
285     struct hostportres *hp = NULL;
286
287     for (entry = list_first(hostports); entry; entry = list_next(entry)) {
288         hp = (struct hostportres *)entry->data;
289         debug(DBG_WARN, "connecttcphostlist: trying to open TCP connection to %s port %s", hp->host, hp->port);
290         if ((s = connecttcp(hp->addrinfo, src, list_count(hostports) > 1 ? 5 : 30)) >= 0) {
291             debug(DBG_WARN, "connecttcphostlist: TCP connection to %s port %s up", hp->host, hp->port);
292             return s;
293         }
294     }
295     debug(DBG_ERR, "connecttcphostlist: failed");
296     return -1;
297 }