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