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