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