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