/* %%% copyright-cmetz-96 This software is Copyright 1996-1997 by Craig Metz, All Rights Reserved. The Inner Net License Version 2 applies to this software. You should have received a copy of the license with this software. If you didn't get a copy, you may request one from . */ /* getaddrinfo() v1.26 */ /* I'd like to thank Matti Aarnio for finding some bugs in this code and sending me patches, as well as everyone else who has contributed to this code (by reporting bugs, suggesting improvements, and commented on its behavior and proposed changes). */ /* To do what POSIX says, even when it's broken, define: */ /* #define BROKEN_LIKE_POSIX 1 */ /* Note: real apps will break if you define this, while nothing other than a conformance test suite should have a problem with it undefined. */ /* If your C runtime library provides the POSIX p1003.1g D6.6 bit types of the form u?int(16|32)_t, define: */ /* #define HAVE_POSIX1G_TYPES 1 */ /* Note: this implementation tries to guess what the correct values are for your compiler+processor combination but might not always get it right. */ #include #include #include #include #if LOCAL #include #include #include #include #endif /* LOCAL */ #include #include #if RESOLVER #include #include #endif /* RESOLVER */ #ifndef AF_LOCAL #define AF_LOCAL AF_UNIX #endif /* AF_LOCAL */ #ifndef PF_LOCAL #define PF_LOCAL PF_UNIX #endif /* PF_LOCAL */ #ifndef UNIX_PATH_MAX #define UNIX_PATH_MAX 108 #endif /* UNIX_PATH_MAX */ #if !HAVE_POSIX1G_TYPES #if (~0UL) == 0xffffffff #define int16_t short #define uint16_t unsigned short #define int32_t long #define uint32_t unsigned long #else /* (~0UL) == 0xffffffff */ #if (~0UL) == 0xffffffffffffffff #define int16_t short #define uint16_t unsigned short #define int32_t int #define uint32_t unsigned int #else /* (~0UL) == 0xffffffffffffffff */ #error Neither 32 bit nor 64 bit word size detected. #error You need to define the bit types manually. #endif /* (~0UL) == 0xffffffffffffffff */ #endif /* (~0UL) == 0xffffffff */ #endif /* !HAVE_POSIX1G_TYPES */ #if defined(INET6) && !defined(AF_INET6) #error Without a definition of AF_INET6, this system cannot support IPv6 #error addresses. #endif /* defined(INET6) && !defined(AF_INET6) */ #if INET6 #ifndef T_AAAA #define T_AAAA 28 #endif /* T_AAAA */ #endif /* INET6 */ #define GAIH_OKIFUNSPEC 0x0100 #define GAIH_EAI ~(GAIH_OKIFUNSPEC) static struct addrinfo nullreq = { 0, PF_UNSPEC, 0, 0, 0, NULL, NULL, NULL }; struct gaih_service { char *name; int num; }; struct gaih_servtuple { struct gaih_servtuple *next; int socktype; int protocol; int port; }; static struct gaih_servtuple nullserv = { NULL, 0, 0, 0 }; struct gaih_addrtuple { struct gaih_addrtuple *next; int family; char addr[16]; char *cname; }; struct gaih_typeproto { int socktype; int protocol; char *name; }; #if HOSTTABLE static int hosttable_lookup_addr(const char *name, const struct addrinfo *req, struct gaih_addrtuple **pat) { FILE *f; char buffer[1024]; char *c, *c2; int rval = 1; char *prevcname = NULL; if (!(f = fopen("/etc/hosts", "r"))) return -EAI_SYSTEM; while(fgets(buffer, sizeof(buffer), f)) { if (c = strchr(buffer, '#')) *c = 0; c = buffer; while(*c && !isspace(*c)) c++; if (!*c) continue; *(c++) = 0; while(*c && isspace(*c)) c++; if (!*c) continue; if (!(c2 = strstr(c, name))) continue; if (*(c2 - 1) && !isspace(*(c2 - 1))) continue; c2 += strlen(name); if (*c2 && !isspace(*c2)) continue; c2 = c; while(*c2 && !isspace(*c2)) c2++; if (!*c2) continue; *c2 = 0; if (!*pat) { if (!(*pat = malloc(sizeof(struct gaih_addrtuple)))) return -EAI_MEMORY; }; memset(*pat, 0, sizeof(struct gaih_addrtuple)); if (!req->ai_family || (req->ai_family == AF_INET)) if (inet_pton(AF_INET, buffer, (*pat)->addr) > 0) { (*pat)->family = AF_INET; goto build; }; #if INET6 if (!req->ai_family || (req->ai_family == AF_INET6)) if (inet_pton(AF_INET6, buffer, (*pat)->addr) > 0) { (*pat)->family = AF_INET6; goto build; }; #endif /* INET6 */ continue; build: if (req->ai_flags & AI_CANONNAME) if (prevcname && !strcmp(prevcname, c)) (*pat)->cname = prevcname; else prevcname = (*pat)->cname = strdup(c); pat = &((*pat)->next); rval = 0; }; fclose(f); return rval; }; #endif /* HOSTTABLE */ #if RESOLVER #define RRHEADER_SZ 10 int resolver_lookup_addr(const char *name, int type, const struct addrinfo *req, struct gaih_addrtuple **pat) { char answer[PACKETSZ]; int answerlen; char dn[MAXDNAME]; char *prevcname = NULL; void *p, *ep; int answers, i, j; uint16_t rtype, rclass; if ((answerlen = res_search(name, C_IN, type, answer, sizeof(answer))) < 0) { switch(h_errno) { case NETDB_INTERNAL: return -EAI_SYSTEM; case HOST_NOT_FOUND: return 1; case TRY_AGAIN: return -EAI_AGAIN; /* XXX */ case NO_RECOVERY: return -EAI_FAIL; case NO_DATA: return 1; default: return -EAI_FAIL; }; }; p = answer; ep = answer + answerlen; if (answerlen < RRHEADER_SZ) return -EAI_FAIL; { HEADER *h = (HEADER *)p; if (!h->qr || (h->opcode != QUERY) || (h->qdcount != htons(1)) || !h->ancount) return -EAI_FAIL; answers = ntohs(h->ancount); }; p += sizeof(HEADER); if ((i = dn_expand(answer, ep, p, dn, sizeof(dn))) < 0) return -EAI_FAIL; p += i; if (p + 2*sizeof(uint16_t) >= ep) return -EAI_FAIL; GETSHORT(rtype, p); GETSHORT(rclass, p); if ((rtype != type) || (rclass != C_IN)) return -EAI_FAIL; while(answers--) { if ((i = dn_expand(answer, ep, p, dn, sizeof(dn))) < 0) return -EAI_FAIL; p += i; if (p + RRHEADER_SZ >= ep) return -EAI_FAIL; GETSHORT(rtype, p); GETSHORT(rclass, p); p += sizeof(uint32_t); if (rclass != C_IN) return -EAI_FAIL; GETSHORT(rclass, p); i = rclass; if (p + i > ep) return -EAI_FAIL; if (rtype == type) { while(*pat) pat = &((*pat)->next); if (!(*pat = malloc(sizeof(struct gaih_addrtuple)))) return -EAI_MEMORY; memset(*pat, 0, sizeof(struct gaih_addrtuple)); switch(type) { case T_A: if (i != sizeof(struct in_addr)) return -EAI_FAIL; (*pat)->family = AF_INET; break; #if INET6 case T_AAAA: if (i != sizeof(struct in6_addr)) return -EAI_FAIL; (*pat)->family = AF_INET6; break; #endif /* INET6 */ default: return -EAI_FAIL; }; memcpy((*pat)->addr, p, i); if (req->ai_flags & AI_CANONNAME) if (prevcname && !strcmp(prevcname, dn)) (*pat)->cname = prevcname; else prevcname = (*pat)->cname = strdup(dn); }; p += i; }; return 0; }; #endif /* RESOLVER */ #if LOCAL static int gaih_local(const char *name, const struct gaih_service *service, const struct addrinfo *req, struct addrinfo **pai) { struct utsname utsname; if (name || (req->ai_flags & AI_CANONNAME)) if (uname(&utsname)) return -EAI_SYSTEM; if (name) { if (strcmp(name, "localhost") && strcmp(name, "local") && strcmp(name, "unix") && strcmp(name, utsname.nodename)) return (GAIH_OKIFUNSPEC | -EAI_NONAME); }; if (!(*pai = malloc(sizeof(struct addrinfo) + sizeof(struct sockaddr_un) + ((req->ai_flags & AI_CANONNAME) ? (strlen(utsname.nodename) + 1): 0)))) return -EAI_MEMORY; (*pai)->ai_next = NULL; (*pai)->ai_flags = req->ai_flags; (*pai)->ai_family = AF_LOCAL; (*pai)->ai_socktype = req->ai_socktype ? req->ai_socktype : SOCK_STREAM; (*pai)->ai_protocol = req->ai_protocol; (*pai)->ai_addrlen = sizeof(struct sockaddr_un); (*pai)->ai_addr = (void *)(*pai) + sizeof(struct addrinfo); #if SALEN ((struct sockaddr_un *)(*pai)->ai_addr)->sun_len = sizeof(struct sockaddr_un); #endif /* SALEN */ ((struct sockaddr_un *)(*pai)->ai_addr)->sun_family = AF_LOCAL; memset(((struct sockaddr_un *)(*pai)->ai_addr)->sun_path, 0, UNIX_PATH_MAX); if (service) { char *c; if (c = strchr(service->name, '/')) { if (strlen(service->name) >= sizeof(((struct sockaddr_un *)(*pai)->ai_addr)->sun_path)) return (GAIH_OKIFUNSPEC | -EAI_SERVICE); strcpy(((struct sockaddr_un *)(*pai)->ai_addr)->sun_path, service->name); } else { if (strlen(P_tmpdir "/") + 1 + strlen(service->name) >= sizeof(((struct sockaddr_un *)(*pai)->ai_addr)->sun_path)) return (GAIH_OKIFUNSPEC | -EAI_SERVICE); strcpy(((struct sockaddr_un *)(*pai)->ai_addr)->sun_path, P_tmpdir "/"); strcat(((struct sockaddr_un *)(*pai)->ai_addr)->sun_path, service->name); }; } else { if (!tmpnam(((struct sockaddr_un *)(*pai)->ai_addr)->sun_path)) return (GAIH_OKIFUNSPEC | -EAI_SYSTEM); }; if (req->ai_flags & AI_CANONNAME) strcpy((*pai)->ai_canonname = (char *)(*pai) + sizeof(struct addrinfo) + sizeof(struct sockaddr_un), utsname.nodename); else (*pai)->ai_canonname = NULL; return 0; }; #endif /* LOCAL */ static struct gaih_typeproto gaih_inet_typeproto[] = { { 0, 0, NULL }, { SOCK_STREAM, IPPROTO_TCP, "tcp" }, { SOCK_DGRAM, IPPROTO_UDP, "udp" }, { 0, 0, NULL } }; static int gaih_inet_serv(char *servicename, struct gaih_typeproto *tp, struct gaih_servtuple **st) { struct servent *s; if (!(s = getservbyname(servicename, tp->name))) return (GAIH_OKIFUNSPEC | -EAI_SERVICE); if (!(*st = malloc(sizeof(struct gaih_servtuple)))) return -EAI_MEMORY; (*st)->next = NULL; (*st)->socktype = tp->socktype; (*st)->protocol = tp->protocol; (*st)->port = s->s_port; return 0; } static int gaih_inet(const char *name, const struct gaih_service *service, const struct addrinfo *req, struct addrinfo **pai) { struct hostent *h = NULL; struct gaih_typeproto *tp = gaih_inet_typeproto; struct gaih_servtuple *st = &nullserv; struct gaih_addrtuple *at = NULL; int i; if (req->ai_protocol || req->ai_socktype) { for (tp++; tp->name && ((req->ai_socktype != tp->socktype) || !req->ai_socktype) && ((req->ai_protocol != tp->protocol) || !req->ai_protocol); tp++); if (!tp->name) if (req->ai_socktype) return (GAIH_OKIFUNSPEC | -EAI_SOCKTYPE); else return (GAIH_OKIFUNSPEC | -EAI_SERVICE); } if (service) { if (service->num < 0) { if (tp->name) { if (i = gaih_inet_serv(service->name, tp, &st)) return i; } else { struct gaih_servtuple **pst = &st; for (tp++; tp->name; tp++) { if (i = gaih_inet_serv(service->name, tp, pst)) { if (i & GAIH_OKIFUNSPEC) continue; goto ret; } pst = &((*pst)->next); } if (st == &nullserv) { i = (GAIH_OKIFUNSPEC | -EAI_SERVICE); goto ret; } } } else { if (!(st = malloc(sizeof(struct gaih_servtuple)))) return -EAI_MEMORY; st->next = NULL; st->socktype = tp->socktype; st->protocol = tp->protocol; st->port = htons(service->num); } } if (!name) { if (!(at = malloc(sizeof(struct gaih_addrtuple)))) { i = -EAI_MEMORY; goto ret; }; memset(at, 0, sizeof(struct gaih_addrtuple)); #if INET6 if (!(at->next = malloc(sizeof(struct gaih_addrtuple)))) { i = -EAI_MEMORY; goto ret; }; at->family = AF_INET6; memset(at->next, 0, sizeof(struct gaih_addrtuple)); at->next->family = AF_INET; #else /* INET6 */ at->family = AF_INET; #endif /* INET6 */ goto build; }; if (!req->ai_family || (req->ai_family == AF_INET)) { struct in_addr in_addr; if (inet_pton(AF_INET, name, &in_addr) > 0) { if (!(at = malloc(sizeof(struct gaih_addrtuple)))) return -EAI_MEMORY; memset(at, 0, sizeof(struct gaih_addrtuple)); at->family = AF_INET; memcpy(at->addr, &in_addr, sizeof(struct in_addr)); goto build; }; }; #if INET6 if (!req->ai_family || (req->ai_family == AF_INET6)) { struct in6_addr in6_addr; if (inet_pton(AF_INET6, name, &in6_addr) > 0) { if (!(at = malloc(sizeof(struct gaih_addrtuple)))) return -EAI_MEMORY; memset(at, 0, sizeof(struct gaih_addrtuple)); at->family = AF_INET6; memcpy(at->addr, &in6_addr, sizeof(struct in6_addr)); goto build; }; }; #endif /* INET6 */ if (!(req->ai_flags & AI_NONAME)) { #if HOSTTABLE if ((i = hosttable_lookup_addr(name, req, &at)) < 0) goto ret; if (!i) goto build; #endif /* HOSTTABLE */ #if RESOLVER #if INET6 if (!req->ai_family || (req->ai_family == AF_INET6)) if ((i = resolver_lookup_addr(name, T_AAAA, req, &at)) < 0) goto ret; #endif /* INET6 */ if (!req->ai_family || (req->ai_family == AF_INET)) if ((i = resolver_lookup_addr(name, T_A, req, &at)) < 0) goto ret; if (!i) goto build; #endif /* RESOLVER */ }; if (!at) return (GAIH_OKIFUNSPEC | -EAI_NONAME); build: { char *prevcname = NULL; struct gaih_servtuple *st2; struct gaih_addrtuple *at2 = at; int j; while(at2) { if (req->ai_flags & AI_CANONNAME) { if (at2->cname) j = strlen(at2->cname) + 1; else if (name) j = strlen(name) + 1; else j = 2; } else j = 0; #if INET6 if (at2->family == AF_INET6) i = sizeof(struct sockaddr_in6); else #endif /* INET6 */ i = sizeof(struct sockaddr_in); st2 = st; while(st2) { if (!(*pai = malloc(sizeof(struct addrinfo) + i + j))) { i = -EAI_MEMORY; goto ret; } memset(*pai, 0, sizeof(struct addrinfo) + i + j); (*pai)->ai_flags = req->ai_flags; (*pai)->ai_family = at2->family; (*pai)->ai_socktype = st2->socktype; (*pai)->ai_protocol = st2->protocol; (*pai)->ai_addrlen = i; (*pai)->ai_addr = (void *)(*pai) + sizeof(struct addrinfo); #if SALEN ((struct sockaddr_in *)(*pai)->ai_addr)->sin_len = i; #endif /* SALEN */ ((struct sockaddr_in *)(*pai)->ai_addr)->sin_family = at2->family; ((struct sockaddr_in *)(*pai)->ai_addr)->sin_port = st2->port; #if INET6 if (at2->family == AF_INET6) memcpy(&((struct sockaddr_in6 *)(*pai)->ai_addr)->sin6_addr, at2->addr, sizeof(struct in6_addr)); else #endif /* INET6 */ memcpy(&((struct sockaddr_in *)(*pai)->ai_addr)->sin_addr, at2->addr, sizeof(struct in_addr)); if (j) { (*pai)->ai_canonname = (void *)(*pai) + sizeof(struct addrinfo) + i; if (at2->cname) { strcpy((*pai)->ai_canonname, at2->cname); if (prevcname != at2->cname) { if (prevcname) free(prevcname); prevcname = at2->cname; }; } else strcpy((*pai)->ai_canonname, name ? name : "*"); }; pai = &((*pai)->ai_next); st2 = st2->next; }; at2 = at2->next; }; }; i = 0; ret: if (st != &nullserv) { struct gaih_servtuple *st2 = st; while(st) { st2 = st->next; free(st); st = st2; } } if (at) { struct gaih_addrtuple *at2 = at; while(at) { at2 = at->next; free(at); at = at2; } } return i; } struct gaih { int family; int (*gaih)(const char *name, const struct gaih_service *service, const struct addrinfo *req, struct addrinfo **pai); }; static struct gaih gaih[] = { #if INET6 { PF_INET6, gaih_inet }, #endif /* INET6 */ { PF_INET, gaih_inet }, #if LOCAL { PF_LOCAL, gaih_local }, #endif /* LOCAL */ { PF_UNSPEC, NULL } }; int getaddrinfo(const char *name, const char *service, const struct addrinfo *req, struct addrinfo **pai) { int i, j = 0; struct addrinfo *p = NULL, **end; struct gaih *g = gaih, *pg = NULL; struct gaih_service gaih_service, *pservice; if (name && (name[0] == '*') && !name[1]) name = NULL; if (service && (service[0] == '*') && !service[1]) service = NULL; #if BROKEN_LIKE_POSIX if (!name && !service) return EAI_NONAME; #endif /* BROKEN_LIKE_POSIX */ if (!req) req = &nullreq; if (req->ai_flags & ~3) return EAI_BADFLAGS; #if BROKEN_LIKE_POSIX if ((req->ai_flags & AI_CANONNAME) && !name) return EAI_BADFLAGS; #endif /* BROKEN_LIKE_POSIX */ if (service && *service) { char *c; gaih_service.num = strtoul(gaih_service.name = (void *)service, &c, 10); if (*c) { gaih_service.num = -1; } #if BROKEN_LIKE_POSIX else if (!req->ai_socktype) return EAI_SERVICE; #endif /* BROKEN_LIKE_POSIX */ pservice = &gaih_service; } else pservice = NULL; if (pai) end = &p; else end = NULL; while(g->gaih) { if ((req->ai_family == g->family) || !req->ai_family) { j++; if (!((pg && (pg->gaih == g->gaih)))) { pg = g; if (i = g->gaih(name, pservice, req, end)) { if (!req->ai_family && (i & GAIH_OKIFUNSPEC)) continue; goto gaih_err; } if (end) while(*end) end = &((*end)->ai_next); } } g++; } if (!j) return EAI_FAMILY; if (p) { *pai = p; return 0; } if (!pai && !i) return 0; gaih_err: if (p) freeaddrinfo(p); if (i) return -(i & GAIH_EAI); return EAI_NONAME; } void freeaddrinfo(struct addrinfo *ai) { struct addrinfo *p; while(ai) { p = ai; ai = ai->ai_next; free((void *)p); } }