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