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