Add an error code.
[radsecproxy.git] / hostport.c
index 6360cbd..54c1d74 100644 (file)
@@ -6,14 +6,22 @@
  * copyright notice and this permission notice appear in all copies.
  */
 
+/* Code contributions from:
+ *
+ * Simon Leinen <simon.leinen@switch.ch>
+ */
+
 #include <stdlib.h>
+#include <unistd.h>
 #include <string.h>
 #include <netdb.h>
+#include <netinet/in.h>
 #include "debug.h"
 #include "util.h"
-#include "resolve.h"
+#include "list.h"
+#include "hostport.h"
 
-void resolve_freehostport(struct hostportres *hp) {
+void freehostport(struct hostportres *hp) {
     if (hp) {
        free(hp->host);
        free(hp->port);
@@ -23,10 +31,14 @@ void resolve_freehostport(struct hostportres *hp) {
     }
 }
 
-static int resolve_parsehostport(struct hostportres *hp, char *hostport, char *default_port) {
+static int parsehostport(struct hostportres *hp, char *hostport, char *default_port) {
     char *p, *field;
     int ipv6 = 0;
 
+    if (!hostport) {
+       hp->port = default_port ? stringcopy(default_port, 0) : NULL;
+       return 1;
+    }
     p = hostport;
     /* allow literal addresses and port, e.g. [2001:db8::1]:1812 */
     if (*p == '[') {
@@ -40,7 +52,7 @@ static int resolve_parsehostport(struct hostportres *hp, char *hostport, char *d
        ipv6 = 1;
     } else {
        field = p;
-       for (; *p && *p != ':' && *p != ' ' && *p != '\t' && *p != '\n'; p++);
+       for (; *p && *p != ':' && *p != '/' && *p != ' ' && *p != '\t' && *p != '\n'; p++);
     }
     if (field == p) {
        debug(DBG_ERR, "missing host/address");
@@ -50,30 +62,30 @@ static int resolve_parsehostport(struct hostportres *hp, char *hostport, char *d
     hp->host = stringcopy(field, p - field);
     if (ipv6) {
        p++;
-       if (*p && *p != ':' && *p != ' ' && *p != '\t' && *p != '\n') {
+       if (*p && *p != ':' && *p != '/' && *p != ' ' && *p != '\t' && *p != '\n') {
            debug(DBG_ERR, "unexpected character after ]");
            return 0;
        }
     }
     if (*p == ':') {
-           /* port number or service name is specified */;
-           field = ++p;
-           for (; *p && *p != ' ' && *p != '\t' && *p != '\n'; p++);
-           if (field == p) {
-               debug(DBG_ERR, "syntax error, : but no following port");
-               return 0;
-           }
-           hp->port = stringcopy(field, p - field);
+       /* port number or service name is specified */;
+       field = ++p;
+       for (; *p && *p != ' ' && *p != '\t' && *p != '\n'; p++);
+       if (field == p) {
+           debug(DBG_ERR, "syntax error, : but no following port");
+           return 0;
+       }
+       hp->port = stringcopy(field, p - field);
     } else
        hp->port = default_port ? stringcopy(default_port, 0) : NULL;
     return 1;
 }
-    
-struct hostportres *resolve_newhostport(char *hostport, char *default_port, uint8_t prefixok) {
+
+struct hostportres *newhostport(char *hostport, char *default_port, uint8_t prefixok) {
     struct hostportres *hp;
     char *slash, *s;
     int plen;
-    
+
     hp = malloc(sizeof(struct hostportres));
     if (!hp) {
        debug(DBG_ERR, "resolve_newhostport: malloc failed");
@@ -81,47 +93,46 @@ struct hostportres *resolve_newhostport(char *hostport, char *default_port, uint
     }
     memset(hp, 0, sizeof(struct hostportres));
 
-    if (!resolve_parsehostport(hp, hostport, default_port))
+    if (!parsehostport(hp, hostport, default_port))
        goto errexit;
 
-    if (!strcmp(hp->host, "*")) {
+    if (hp->host && !strcmp(hp->host, "*")) {
        free(hp->host);
        hp->host = NULL;
     }
 
-    slash = hp->host ? strchr(hp->host, '/') : NULL;
+    slash = hostport ? strchr(hostport, '/') : NULL;
     if (slash) {
        if (!prefixok) {
-           debug(DBG_WARN, "resolve_newhostport: prefix not allowed here", hp->host);
+           debug(DBG_WARN, "newhostport: prefix not allowed here", hp->host);
            goto errexit;
        }
        s = slash + 1;
        if (!*s) {
-           debug(DBG_WARN, "resolve_newhostport: prefix length must be specified after the / in %s", hp->host);
+           debug(DBG_WARN, "newhostport: prefix length must be specified after the / in %s", hp->host);
            goto errexit;
        }
        for (; *s; s++)
            if (*s < '0' || *s > '9') {
-               debug(DBG_WARN, "resolve_newhostport: %s in %s is not a valid prefix length", slash + 1, hp->host);
+               debug(DBG_WARN, "newhostport: %s in %s is not a valid prefix length", slash + 1, hp->host);
                goto errexit;
            }
        plen = atoi(slash + 1);
        if (plen < 0 || plen > 128) {
-           debug(DBG_WARN, "resolve_newhostport: %s in %s is not a valid prefix length", slash + 1, hp->host);
+           debug(DBG_WARN, "newhostport: %s in %s is not a valid prefix length", slash + 1, hp->host);
            goto errexit;
        }
        hp->prefixlen = plen;
-       *slash = '\0';
     } else
        hp->prefixlen = 255;
     return hp;
 
- errexit:
-    resolve_freehostport(hp);
+errexit:
+    freehostport(hp);
     return NULL;
 }
 
-static int resolve_resolve(struct hostportres *hp, int socktype, uint8_t passive) {
+int resolvehostport(struct hostportres *hp, int socktype, uint8_t passive) {
     struct addrinfo hints, *res;
 
     memset(&hints, 0, sizeof(hints));
@@ -129,11 +140,11 @@ static int resolve_resolve(struct hostportres *hp, int socktype, uint8_t passive
     hints.ai_family = AF_UNSPEC;
     if (passive)
        hints.ai_flags = AI_PASSIVE;
-    
+
     if (!hp->host && !hp->port) {
        /* getaddrinfo() doesn't like host and port to be NULL */
        if (getaddrinfo(hp->host, "1812" /* can be anything */, &hints, &hp->addrinfo)) {
-           debug(DBG_WARN, "resolve_resolve: can't resolve (null) port (null)");
+           debug(DBG_WARN, "resolvehostport: can't resolve (null) port (null)");
            goto errexit;
        }
        for (res = hp->addrinfo; res; res = res->ai_next)
@@ -142,53 +153,86 @@ static int resolve_resolve(struct hostportres *hp, int socktype, uint8_t passive
        if (hp->prefixlen != 255)
            hints.ai_flags |= AI_NUMERICHOST;
        if (getaddrinfo(hp->host, hp->port, &hints, &hp->addrinfo)) {
-           debug(DBG_WARN, "resolve_resolve: can't resolve %s port %s", hp->host ? hp->host : "(null)", hp->port ? hp->port : "(null)");
+           debug(DBG_WARN, "resolvehostport: can't resolve %s port %s", hp->host ? hp->host : "(null)", hp->port ? hp->port : "(null)");
            goto errexit;
        }
        if (hp->prefixlen != 255) {
            switch (hp->addrinfo->ai_family) {
            case AF_INET:
                if (hp->prefixlen > 32) {
-                   debug(DBG_WARN, "resolve_resolve: prefix length must be <= 32 in %s", hp->host);
+                   debug(DBG_WARN, "resolvehostport: prefix length must be <= 32 in %s", hp->host);
                    goto errexit;
                }
                break;
            case AF_INET6:
                break;
            default:
-               debug(DBG_WARN, "resolve_resolve: prefix must be IPv4 or IPv6 in %s", hp->host);
+               debug(DBG_WARN, "resolvehostport: prefix must be IPv4 or IPv6 in %s", hp->host);
                goto errexit;
            }
        }
     }
     return 1;
 
- errexit:
+errexit:
     if (hp->addrinfo)
        freeaddrinfo(hp->addrinfo);
     return 0;
-}        
+}
 
-int resolve_hostports(struct list *hostports, int socktype) {
+int addhostport(struct list **hostports, char **hostport, char *portdefault, uint8_t prefixok) {
+    struct hostportres *hp;
+    int i;
+
+    if (!*hostports) {
+       *hostports = list_create();
+        if (!*hostports) {
+           debug(DBG_ERR, "addhostport: malloc failed");
+           return 0;
+       }
+    }
+
+    for (i = 0; hostport[i]; i++) {
+       hp = newhostport(hostport[i], portdefault, prefixok);
+       if (!hp)
+           return 0;
+       if (!list_push(*hostports, hp)) {
+           freehostport(hp);
+           debug(DBG_ERR, "addhostport: malloc failed");
+           return 0;
+       }
+    }
+    return 1;
+}
+
+void freehostports(struct list *hostports) {
+    struct hostportres *hp;
+
+    while ((hp = (struct hostportres *)list_shift(hostports)))
+       freehostport(hp);
+    list_destroy(hostports);
+}
+
+int resolvehostports(struct list *hostports, int socktype) {
     struct list_node *entry;
     struct hostportres *hp;
-    
+
     for (entry = list_first(hostports); entry; entry = list_next(entry)) {
        hp = (struct hostportres *)entry->data;
-       if (!hp->addrinfo && !resolve_resolve(hp, socktype, 0))
+       if (!hp->addrinfo && !resolvehostport(hp, socktype, 0))
            return 0;
     }
     return 1;
 }
 
-struct addrinfo *resolve_passiveaddrinfo(char *hostport, char *default_port, int socktype) {
+struct addrinfo *resolvepassiveaddrinfo(char *hostport, char *default_port, int socktype) {
     struct addrinfo *ai = NULL;
-    struct hostportres *hp = resolve_newhostport(hostport, default_port, 0);
-    if (hp && resolve_resolve(hp, socktype, 1)) {
+    struct hostportres *hp = newhostport(hostport, default_port, 0);
+    if (hp && resolvehostport(hp, socktype, 1)) {
        ai = hp->addrinfo;
        hp->addrinfo = NULL;
     }
-    resolve_freehostport(hp);
+    freehostport(hp);
     return ai;
 }
 
@@ -203,3 +247,65 @@ static int prefixmatch(void *a1, void *a2, uint8_t len) {
        return 1;
     return (((uint8_t *)a1)[l] & mask[r]) == (((uint8_t *)a2)[l] & mask[r]);
 }
+
+int addressmatches(struct list *hostports, struct sockaddr *addr, uint8_t checkport) {
+    struct sockaddr_in6 *sa6 = NULL;
+    struct in_addr *a4 = NULL;
+    struct addrinfo *res;
+    struct list_node *entry;
+    struct hostportres *hp = NULL;
+
+    if (addr->sa_family == AF_INET6) {
+        sa6 = (struct sockaddr_in6 *)addr;
+        if (IN6_IS_ADDR_V4MAPPED(&sa6->sin6_addr)) {
+            a4 = (struct in_addr *)&sa6->sin6_addr.s6_addr[12];
+           sa6 = NULL;
+       }
+    } else
+       a4 = &((struct sockaddr_in *)addr)->sin_addr;
+
+    for (entry = list_first(hostports); entry; entry = list_next(entry)) {
+       hp = (struct hostportres *)entry->data;
+       for (res = hp->addrinfo; res; res = res->ai_next)
+           if (hp->prefixlen == 255) {
+               if ((a4 && res->ai_family == AF_INET &&
+                    !memcmp(a4, &((struct sockaddr_in *)res->ai_addr)->sin_addr, 4) &&
+                    (!checkport || ((struct sockaddr_in *)res->ai_addr)->sin_port ==
+                     ((struct sockaddr_in *)addr)->sin_port)) ||
+                   (sa6 && res->ai_family == AF_INET6 &&
+                    !memcmp(&sa6->sin6_addr,
+                            &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, 16) &&
+                    (!checkport || ((struct sockaddr_in6 *)res->ai_addr)->sin6_port ==
+                     ((struct sockaddr_in6 *)addr)->sin6_port)))
+                   return 1;
+           } else {
+               if ((a4 && res->ai_family == AF_INET &&
+                    prefixmatch(a4, &((struct sockaddr_in *)res->ai_addr)->sin_addr, hp->prefixlen)) ||
+                   (sa6 && res->ai_family == AF_INET6 &&
+                    prefixmatch(&sa6->sin6_addr, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, hp->prefixlen)))
+                   return 1;
+           }
+    }
+    return 0;
+}
+
+int connecttcphostlist(struct list *hostports,  struct addrinfo *src) {
+    int s;
+    struct list_node *entry;
+    struct hostportres *hp = NULL;
+
+    for (entry = list_first(hostports); entry; entry = list_next(entry)) {
+       hp = (struct hostportres *)entry->data;
+       debug(DBG_WARN, "connecttcphostlist: trying to open TCP connection to %s port %s", hp->host, hp->port);
+       if ((s = connecttcp(hp->addrinfo, src, list_count(hostports) > 1 ? 5 : 30)) >= 0) {
+           debug(DBG_WARN, "connecttcphostlist: TCP connection to %s port %s up", hp->host, hp->port);
+           return s;
+       }
+    }
+    debug(DBG_ERR, "connecttcphostlist: failed");
+    return -1;
+}
+
+/* Local Variables: */
+/* c-file-style: "stroustrup" */
+/* End: */