summaryrefslogtreecommitdiff
path: root/hostport.c
diff options
context:
space:
mode:
Diffstat (limited to 'hostport.c')
-rw-r--r--hostport.c295
1 files changed, 295 insertions, 0 deletions
diff --git a/hostport.c b/hostport.c
new file mode 100644
index 0000000..22ff2dc
--- /dev/null
+++ b/hostport.c
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2006-2009 Stig Venaas <venaas@uninett.no>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <netdb.h>
+#include "debug.h"
+#include "util.h"
+#include "list.h"
+#include "hostport.h"
+
+void freehostport(struct hostportres *hp) {
+ if (hp) {
+ free(hp->host);
+ free(hp->port);
+ if (hp->addrinfo)
+ freeaddrinfo(hp->addrinfo);
+ free(hp);
+ }
+}
+
+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 == '[') {
+ p++;
+ field = p;
+ for (; *p && *p != ']' && *p != ' ' && *p != '\t' && *p != '\n'; p++);
+ if (*p != ']') {
+ debug(DBG_ERR, "no ] matching initial [");
+ return 0;
+ }
+ ipv6 = 1;
+ } else {
+ field = p;
+ for (; *p && *p != ':' && *p != '/' && *p != ' ' && *p != '\t' && *p != '\n'; p++);
+ }
+ if (field == p) {
+ debug(DBG_ERR, "missing host/address");
+ return 0;
+ }
+
+ hp->host = stringcopy(field, p - field);
+ if (ipv6) {
+ p++;
+ 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);
+ } else
+ hp->port = default_port ? stringcopy(default_port, 0) : NULL;
+ return 1;
+}
+
+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");
+ goto errexit;
+ }
+ memset(hp, 0, sizeof(struct hostportres));
+
+ if (!parsehostport(hp, hostport, default_port))
+ goto errexit;
+
+ if (hp->host && !strcmp(hp->host, "*")) {
+ free(hp->host);
+ hp->host = NULL;
+ }
+
+ slash = hostport ? strchr(hostport, '/') : NULL;
+ if (slash) {
+ if (!prefixok) {
+ debug(DBG_WARN, "newhostport: prefix not allowed here", hp->host);
+ goto errexit;
+ }
+ s = slash + 1;
+ if (!*s) {
+ 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, "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, "newhostport: %s in %s is not a valid prefix length", slash + 1, hp->host);
+ goto errexit;
+ }
+ hp->prefixlen = plen;
+ } else
+ hp->prefixlen = 255;
+ return hp;
+
+ errexit:
+ freehostport(hp);
+ return NULL;
+}
+
+int resolvehostport(struct hostportres *hp, int socktype, uint8_t passive) {
+ struct addrinfo hints, *res;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_socktype = socktype;
+ 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, "resolvehostport: can't resolve (null) port (null)");
+ goto errexit;
+ }
+ for (res = hp->addrinfo; res; res = res->ai_next)
+ port_set(res->ai_addr, 0);
+ } else {
+ if (hp->prefixlen != 255)
+ hints.ai_flags |= AI_NUMERICHOST;
+ if (getaddrinfo(hp->host, hp->port, &hints, &hp->addrinfo)) {
+ 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, "resolvehostport: prefix length must be <= 32 in %s", hp->host);
+ goto errexit;
+ }
+ break;
+ case AF_INET6:
+ break;
+ default:
+ debug(DBG_WARN, "resolvehostport: prefix must be IPv4 or IPv6 in %s", hp->host);
+ goto errexit;
+ }
+ }
+ }
+ return 1;
+
+ errexit:
+ if (hp->addrinfo)
+ freeaddrinfo(hp->addrinfo);
+ return 0;
+}
+
+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 && !resolvehostport(hp, socktype, 0))
+ return 0;
+ }
+ return 1;
+}
+
+struct addrinfo *resolvepassiveaddrinfo(char *hostport, char *default_port, int socktype) {
+ struct addrinfo *ai = NULL;
+ struct hostportres *hp = newhostport(hostport, default_port, 0);
+ if (hp && resolvehostport(hp, socktype, 1)) {
+ ai = hp->addrinfo;
+ hp->addrinfo = NULL;
+ }
+ freehostport(hp);
+ return ai;
+}
+
+/* returns 1 if the len first bits are equal, else 0 */
+static int prefixmatch(void *a1, void *a2, uint8_t len) {
+ static uint8_t mask[] = { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe };
+ uint8_t r, l = len / 8;
+ if (l && memcmp(a1, a2, l))
+ return 0;
+ r = len % 8;
+ if (!r)
+ return 1;
+ return (((uint8_t *)a1)[l] & mask[r]) == (((uint8_t *)a2)[l] & mask[r]);
+}
+
+int addressmatches(struct list *hostports, struct sockaddr *addr) {
+ 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)) ||
+ (sa6 && res->ai_family == AF_INET6 &&
+ !memcmp(&sa6->sin6_addr, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, 16)))
+ 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;
+}