Example code: Don't create rs_error on failing context creation.
[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 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_UNSPEC;
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     return 1;
176
177 errexit:
178     if (hp->addrinfo)
179         freeaddrinfo(hp->addrinfo);
180     return 0;
181 }
182
183 int addhostport(struct list **hostports, char **hostport, char *portdefault, uint8_t prefixok) {
184     struct hostportres *hp;
185     int i;
186
187     if (!*hostports) {
188         *hostports = list_create();
189         if (!*hostports) {
190             debug(DBG_ERR, "addhostport: malloc failed");
191             return 0;
192         }
193     }
194
195     for (i = 0; hostport[i]; i++) {
196         hp = newhostport(hostport[i], portdefault, prefixok);
197         if (!hp)
198             return 0;
199         if (!list_push(*hostports, hp)) {
200             freehostport(hp);
201             debug(DBG_ERR, "addhostport: malloc failed");
202             return 0;
203         }
204     }
205     return 1;
206 }
207
208 void freehostports(struct list *hostports) {
209     struct hostportres *hp;
210
211     while ((hp = (struct hostportres *)list_shift(hostports)))
212         freehostport(hp);
213     list_destroy(hostports);
214 }
215
216 int resolvehostports(struct list *hostports, int socktype) {
217     struct list_node *entry;
218     struct hostportres *hp;
219
220     for (entry = list_first(hostports); entry; entry = list_next(entry)) {
221         hp = (struct hostportres *)entry->data;
222         if (!hp->addrinfo && !resolvehostport(hp, socktype, 0))
223             return 0;
224     }
225     return 1;
226 }
227
228 struct addrinfo *resolvepassiveaddrinfo(char *hostport, char *default_port, int socktype) {
229     struct addrinfo *ai = NULL;
230     struct hostportres *hp = newhostport(hostport, default_port, 0);
231     if (hp && resolvehostport(hp, socktype, 1)) {
232         ai = hp->addrinfo;
233         hp->addrinfo = NULL;
234     }
235     freehostport(hp);
236     return ai;
237 }
238
239 /* returns 1 if the len first bits are equal, else 0 */
240 static int prefixmatch(void *a1, void *a2, uint8_t len) {
241     static uint8_t mask[] = { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe };
242     uint8_t r, l = len / 8;
243     if (l && memcmp(a1, a2, l))
244         return 0;
245     r = len % 8;
246     if (!r)
247         return 1;
248     return (((uint8_t *)a1)[l] & mask[r]) == (((uint8_t *)a2)[l] & mask[r]);
249 }
250
251 int addressmatches(struct list *hostports, struct sockaddr *addr, uint8_t checkport) {
252     struct sockaddr_in6 *sa6 = NULL;
253     struct in_addr *a4 = NULL;
254     struct addrinfo *res;
255     struct list_node *entry;
256     struct hostportres *hp = NULL;
257
258     if (addr->sa_family == AF_INET6) {
259         sa6 = (struct sockaddr_in6 *)addr;
260         if (IN6_IS_ADDR_V4MAPPED(&sa6->sin6_addr)) {
261             a4 = (struct in_addr *)&sa6->sin6_addr.s6_addr[12];
262             sa6 = NULL;
263         }
264     } else
265         a4 = &((struct sockaddr_in *)addr)->sin_addr;
266
267     for (entry = list_first(hostports); entry; entry = list_next(entry)) {
268         hp = (struct hostportres *)entry->data;
269         for (res = hp->addrinfo; res; res = res->ai_next)
270             if (hp->prefixlen == 255) {
271                 if ((a4 && res->ai_family == AF_INET &&
272                      !memcmp(a4, &((struct sockaddr_in *)res->ai_addr)->sin_addr, 4) &&
273                      (!checkport || ((struct sockaddr_in *)res->ai_addr)->sin_port ==
274                       ((struct sockaddr_in *)addr)->sin_port)) ||
275                     (sa6 && res->ai_family == AF_INET6 &&
276                      !memcmp(&sa6->sin6_addr,
277                              &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, 16) &&
278                      (!checkport || ((struct sockaddr_in6 *)res->ai_addr)->sin6_port ==
279                       ((struct sockaddr_in6 *)addr)->sin6_port)))
280                     return 1;
281             } else {
282                 if ((a4 && res->ai_family == AF_INET &&
283                      prefixmatch(a4, &((struct sockaddr_in *)res->ai_addr)->sin_addr, hp->prefixlen)) ||
284                     (sa6 && res->ai_family == AF_INET6 &&
285                      prefixmatch(&sa6->sin6_addr, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, hp->prefixlen)))
286                     return 1;
287             }
288     }
289     return 0;
290 }
291
292 int connecttcphostlist(struct list *hostports,  struct addrinfo *src) {
293     int s;
294     struct list_node *entry;
295     struct hostportres *hp = NULL;
296
297     for (entry = list_first(hostports); entry; entry = list_next(entry)) {
298         hp = (struct hostportres *)entry->data;
299         debug(DBG_WARN, "connecttcphostlist: trying to open TCP connection to %s port %s", hp->host, hp->port);
300         if ((s = connecttcp(hp->addrinfo, src, list_count(hostports) > 1 ? 5 : 30)) >= 0) {
301             debug(DBG_WARN, "connecttcphostlist: TCP connection to %s port %s up", hp->host, hp->port);
302             return s;
303         }
304     }
305     debug(DBG_ERR, "connecttcphostlist: failed");
306     return -1;
307 }
308
309 /* Local Variables: */
310 /* c-file-style: "stroustrup" */
311 /* End: */