/* $Id$ */ /*** This file is part of nss-mdns. nss-mdns is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. nss-mdns is distributed in the hope that it will be useful, but1 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with nss-mdns; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. ***/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include "query.h" #ifdef ENABLE_AVAHI #include "avahi.h" #endif #if defined(NSS_IPV4_ONLY) && ! defined(MDNS_MINIMAL) #define _nss_mdns_gethostbyname2_r _nss_mdns4_gethostbyname2_r #define _nss_mdns_gethostbyname_r _nss_mdns4_gethostbyname_r #define _nss_mdns_gethostbyaddr_r _nss_mdns4_gethostbyaddr_r #elif defined(NSS_IPV4_ONLY) && defined(MDNS_MINIMAL) #define _nss_mdns_gethostbyname2_r _nss_mdns4_minimal_gethostbyname2_r #define _nss_mdns_gethostbyname_r _nss_mdns4_minimal_gethostbyname_r #define _nss_mdns_gethostbyaddr_r _nss_mdns4_minimal_gethostbyaddr_r #elif defined(NSS_IPV6_ONLY) && ! defined(MDNS_MINIMAL) #define _nss_mdns_gethostbyname2_r _nss_mdns6_gethostbyname2_r #define _nss_mdns_gethostbyname_r _nss_mdns6_gethostbyname_r #define _nss_mdns_gethostbyaddr_r _nss_mdns6_gethostbyaddr_r #elif defined(NSS_IPV6_ONLY) && defined(MDNS_MINIMAL) #define _nss_mdns_gethostbyname2_r _nss_mdns6_minimal_gethostbyname2_r #define _nss_mdns_gethostbyname_r _nss_mdns6_minimal_gethostbyname_r #define _nss_mdns_gethostbyaddr_r _nss_mdns6_minimal_gethostbyaddr_r #elif defined(MDNS_MINIMAL) #define _nss_mdns_gethostbyname2_r _nss_mdns_minimal_gethostbyname2_r #define _nss_mdns_gethostbyname_r _nss_mdns_minimal_gethostbyname_r #define _nss_mdns_gethostbyaddr_r _nss_mdns_minimal_gethostbyaddr_r #endif /* Maximum number of entries to return */ #define MAX_ENTRIES 16 /* The resolv.conf page states that they only support 6 domains */ #define MAX_SEARCH_DOMAINS 6 #define ALIGN(idx) do { \ if (idx % sizeof(void*)) \ idx += (sizeof(void*) - idx % sizeof(void*)); /* Align on 32 bit boundary */ \ } while(0) struct userdata { int count; int data_len; /* only valid when doing reverse lookup */ union { ipv4_address_t ipv4[MAX_ENTRIES]; ipv6_address_t ipv6[MAX_ENTRIES]; char *name[MAX_ENTRIES]; } data; }; #ifndef NSS_IPV6_ONLY static void ipv4_callback(const ipv4_address_t *ipv4, void *userdata) { struct userdata *u = userdata; assert(ipv4 && userdata); if (u->count >= MAX_ENTRIES) return; u->data.ipv4[u->count++] = *ipv4; u->data_len += sizeof(ipv4_address_t); } #endif #ifndef NSS_IPV4_ONLY static void ipv6_callback(const ipv6_address_t *ipv6, void *userdata) { struct userdata *u = userdata; assert(ipv6 && userdata); if (u->count >= MAX_ENTRIES) return; u->data.ipv6[u->count++] = *ipv6; u->data_len += sizeof(ipv6_address_t); } #endif static void name_callback(const char*name, void *userdata) { struct userdata *u = userdata; assert(name && userdata); if (u->count >= MAX_ENTRIES) return; u->data.name[u->count++] = strdup(name); u->data_len += strlen(name)+1; } static int ends_with(const char *name, const char* suffix) { size_t ln, ls; assert(name); assert(suffix); if ((ls = strlen(suffix)) > (ln = strlen(name))) return 0; return strcasecmp(name+ln-ls, suffix) == 0; } static int verify_name_allowed(const char *name) { #ifndef MDNS_MINIMAL FILE *f; #endif assert(name); #ifndef MDNS_MINIMAL if ((f = fopen(MDNS_ALLOW_FILE, "r"))) { int valid = 0; while (!feof(f)) { char ln[128], ln2[128], *t; if (!fgets(ln, sizeof(ln), f)) break; ln[strcspn(ln, "#\t\n\r ")] = 0; if (ln[0] == 0) continue; if (strcmp(ln, "*") == 0) { valid = 1; break; } if (ln[0] != '.') snprintf(t = ln2, sizeof(ln2), ".%s", ln); else t = ln; if (ends_with(name, t)) { valid = 1; break; } } fclose(f); return valid; } #endif return ends_with(name, ".local") || ends_with(name, ".local."); } #ifdef HONOUR_SEARCH_DOMAINS static char **alloc_domains(unsigned ndomains) { char **domains; if (!(domains = malloc(sizeof(char*) * ndomains))) return NULL; /* initialize them all to 0 */ memset(domains, 0, sizeof(char*) * ndomains); return domains; } static void free_domains(char **domains) { char **p; if (!domains) return; for(p = domains; *p; p++) free(*p); free(domains); } static char** parse_domains(const char *domains_in) { /* leave room for the NULL terminator */ char **domains_out; const char *start = domains_in; unsigned domain = 0; if (!(domains_out = alloc_domains(MAX_SEARCH_DOMAINS+1))) return NULL; while (domain < MAX_SEARCH_DOMAINS) { const char *end; char *tmp; size_t domain_len; end = start + strcspn(start, " \t\r\n"); domain_len = (end - start); if (!(tmp = malloc(domain_len + 1))) break; memcpy(tmp, start, domain_len); tmp[domain_len] = '\0'; domains_out[domain++] = tmp; end += strspn(end," \t\r\n"); if (!*end) break; start = end; } return domains_out; } static char** get_search_domains(void) { FILE *f = 0; char **domains = NULL; /* according to the resolv.conf man page (in Linux) the LOCALDOMAIN environment variable should override the settings in the resolv.conf file */ char *line = getenv("LOCALDOMAIN"); if (line && *line != 0) return parse_domains(line); if (!(f = fopen(RESOLV_CONF_FILE, "r"))) return NULL; while (!feof(f)) { char *start = NULL; char ln[512]; if (!fgets(ln, sizeof(ln), f)) break; start = ln + strspn(ln, " \t\r\n"); if (strncmp(start, "search", 6) && strncmp(start, "domain", 6)) continue; if (start[6] != ' ' && start[6] != '\t') continue; /* scan to the end of the keyword ('search' or 'domain' currently) */ start += strcspn(start, " \t\r\n"); /* find the begining of the first domain in the list */ start += strspn(start, " \t\r\n"); /* the resolv.conf manpage also states that 'search' and 'domain' are mutually exclusive and that the last one wins. */ free_domains(domains); domains = parse_domains(start); } fclose(f); return domains; } #endif enum nss_status _nss_mdns_gethostbyname2_r( const char *name, int af, struct hostent * result, char *buffer, size_t buflen, int *errnop, int *h_errnop) { struct userdata u; enum nss_status status = NSS_STATUS_UNAVAIL; int i; size_t address_length, l, idx, astart; void (*ipv4_func)(const ipv4_address_t *ipv4, void *userdata); void (*ipv6_func)(const ipv6_address_t *ipv6, void *userdata); int name_allowed; #ifdef ENABLE_AVAHI int avahi_works = 1; void * data[32]; #endif #ifdef ENABLE_LEGACY int fd = -1; #endif /* DEBUG_TRAP; */ if (af == AF_UNSPEC) #ifdef NSS_IPV6_ONLY af = AF_INET6; #else af = AF_INET; #endif #ifdef NSS_IPV4_ONLY if (af != AF_INET) #elif NSS_IPV6_ONLY if (af != AF_INET6) #else if (af != AF_INET && af != AF_INET6) #endif { *errnop = EINVAL; *h_errnop = NO_RECOVERY; goto finish; } address_length = af == AF_INET ? sizeof(ipv4_address_t) : sizeof(ipv6_address_t); if (buflen < sizeof(char*)+ /* alias names */ strlen(name)+1) { /* official name */ *errnop = ERANGE; *h_errnop = NO_RECOVERY; status = NSS_STATUS_TRYAGAIN; goto finish; } u.count = 0; u.data_len = 0; #ifdef NSS_IPV6_ONLY ipv4_func = NULL; #else ipv4_func = af == AF_INET ? ipv4_callback : NULL; #endif #ifdef NSS_IPV4_ONLY ipv6_func = NULL; #else ipv6_func = af == AF_INET6 ? ipv6_callback : NULL; #endif name_allowed = verify_name_allowed(name); #ifdef ENABLE_AVAHI if (avahi_works && name_allowed) { int r; if ((r = avahi_resolve_name(af, name, data)) < 0) avahi_works = 0; else if (r == 0) { if (af == AF_INET && ipv4_func) ipv4_func((ipv4_address_t*) data, &u); if (af == AF_INET6 && ipv6_func) ipv6_func((ipv6_address_t*)data, &u); } else status = NSS_STATUS_NOTFOUND; } #ifdef HONOUR_SEARCH_DOMAINS if (u.count == 0 && avahi_works && !ends_with(name, ".")) { char **domains; if ((domains = get_search_domains())) { char **p; /* Try to concatenate host names */ for (p = domains; *p; p++) { int fullnamesize; char *fullname; fullnamesize = strlen(name) + strlen(*p) + 2; if (!(fullname = malloc(fullnamesize))) break; snprintf(fullname, fullnamesize, "%s.%s", name, *p); if (verify_name_allowed(fullname)) { int r; r = avahi_resolve_name(af, fullname, data); free(fullname); if (r < 0) { /* Lookup failed */ avahi_works = 0; break; } else if (r == 0) { /* Lookup succeeded */ if (af == AF_INET && ipv4_func) ipv4_func((ipv4_address_t*) data, &u); if (af == AF_INET6 && ipv6_func) ipv6_func((ipv6_address_t*)data, &u); break; } else /* Lookup suceeded, but nothing found */ status = NSS_STATUS_NOTFOUND; } else free(fullname); } free_domains(domains); } } #endif /* HONOUR_SEARCH_DOMAINS */ #endif /* ENABLE_AVAHI */ #if defined(ENABLE_LEGACY) && defined(ENABLE_AVAHI) if (u.count == 0 && !avahi_works) #endif #if defined(ENABLE_LEGACY) { if ((fd = mdns_open_socket()) < 0) { *errnop = errno; *h_errnop = NO_RECOVERY; goto finish; } if (name_allowed) { /* Ignore return value */ mdns_query_name(fd, name, ipv4_func, ipv6_func, &u); if (!u.count) status = NSS_STATUS_NOTFOUND; } #ifdef HONOUR_SEARCH_DOMAINS if (u.count == 0 && !ends_with(name, ".")) { char **domains; /* Try the search domains if the user did not use a traling '.' */ if ((domains = get_search_domains())) { char **p; for (p = domains; *p; p++) { int fullnamesize = 0; char *fullname = NULL; fullnamesize = strlen(name) + strlen(*p) + 2; if (!(fullname = malloc(fullnamesize))) break; snprintf(fullname, fullnamesize, "%s.%s", name, *p); if (verify_name_allowed(fullname)) { /* Ignore return value */ mdns_query_name(fd, fullname, ipv4_func, ipv6_func, &u); if (u.count > 0) { /* We found something, so let's quit */ free(fullname); break; } else status = NSS_STATUS_NOTFOUND; } free(fullname); } free_domains(domains); } } #endif /* HONOUR_SEARCH_DOMAINS */ } #endif /* ENABLE_LEGACY */ if (u.count == 0) { *errnop = ETIMEDOUT; *h_errnop = HOST_NOT_FOUND; goto finish; } /* Alias names */ *((char**) buffer) = NULL; result->h_aliases = (char**) buffer; idx = sizeof(char*); /* Official name */ strcpy(buffer+idx, name); result->h_name = buffer+idx; idx += strlen(name)+1; ALIGN(idx); result->h_addrtype = af; result->h_length = address_length; /* Check if there's enough space for the addresses */ if (buflen < idx+u.data_len+sizeof(char*)*(u.count+1)) { *errnop = ERANGE; *h_errnop = NO_RECOVERY; status = NSS_STATUS_TRYAGAIN; goto finish; } /* Addresses */ astart = idx; l = u.count*address_length; memcpy(buffer+astart, &u.data, l); /* address_length is a multiple of 32bits, so idx is still aligned * correctly */ idx += l; /* Address array address_lenght is always a multiple of 32bits */ for (i = 0; i < u.count; i++) ((char**) (buffer+idx))[i] = buffer+astart+address_length*i; ((char**) (buffer+idx))[i] = NULL; result->h_addr_list = (char**) (buffer+idx); status = NSS_STATUS_SUCCESS; finish: #ifdef ENABLE_LEGACY if (fd >= 0) close(fd); #endif return status; } enum nss_status _nss_mdns_gethostbyname_r ( const char *name, struct hostent *result, char *buffer, size_t buflen, int *errnop, int *h_errnop) { return _nss_mdns_gethostbyname2_r( name, AF_UNSPEC, result, buffer, buflen, errnop, h_errnop); } enum nss_status _nss_mdns_gethostbyaddr_r( const void* addr, int len, int af, struct hostent *result, char *buffer, size_t buflen, int *errnop, int *h_errnop) { struct userdata u; enum nss_status status = NSS_STATUS_UNAVAIL; int r; size_t address_length, idx, astart; #ifdef ENABLE_AVAHI char t[256]; #endif #ifdef ENABLE_LEGACY int fd = -1; #endif *errnop = EINVAL; *h_errnop = NO_RECOVERY; u.count = 0; u.data_len = 0; /* Check for address types */ address_length = af == AF_INET ? sizeof(ipv4_address_t) : sizeof(ipv6_address_t); if (len < (int) address_length || #ifdef NSS_IPV4_ONLY af != AF_INET #elif NSS_IPV6_ONLY af != AF_INET6 #else (af != AF_INET && af != AF_INET6) #endif ) { *errnop = EINVAL; *h_errnop = NO_RECOVERY; goto finish; } /* Check for buffer space */ if (buflen < sizeof(char*)+ /* alias names */ address_length) { /* address */ *errnop = ERANGE; *h_errnop = NO_RECOVERY; status = NSS_STATUS_TRYAGAIN; goto finish; } #ifdef MDNS_MINIMAL /* Only query for 169.254.0.0/16 IPv4 in minimal mode */ if ((af == AF_INET && ((ntohl(*(const uint32_t*) addr) & 0xFFFF0000UL) != 0xA9FE0000UL)) || (af == AF_INET6 && !(((const uint8_t*) addr)[0] == 0xFE && (((const uint8_t*) addr)[1] >> 6) == 2))) { *errnop = EINVAL; *h_errnop = NO_RECOVERY; goto finish; } #endif #ifdef ENABLE_AVAHI /* Lookup using Avahi */ if ((r = avahi_resolve_address(af, addr, t, sizeof(t))) == 0) { name_callback(t, &u); } else if (r > 0) { *errnop = ETIMEDOUT; *h_errnop = HOST_NOT_FOUND; status = NSS_STATUS_NOTFOUND; goto finish; } #endif #if defined(ENABLE_AVAHI) && defined(ENABLE_LEGACY) else #endif #ifdef ENABLE_LEGACY /* Lookup using legacy mDNS queries */ { if ((fd = mdns_open_socket()) < 0) { *errnop = errno; *h_errnop = NO_RECOVERY; goto finish; } r = -1; #if ! defined(NSS_IPV6_ONLY) && ! defined(NSS_IPV4_ONLY) if (af == AF_INET) #endif #ifndef NSS_IPV6_ONLY r = mdns_query_ipv4(fd, (const ipv4_address_t*) addr, name_callback, &u); #endif #if ! defined(NSS_IPV6_ONLY) && ! defined(NSS_IPV4_ONLY) else #endif #ifndef NSS_IPV4_ONLY r = mdns_query_ipv6(fd, (const ipv6_address_t*) addr, name_callback, &u); #endif if (r < 0) { *errnop = ETIMEDOUT; *h_errnop = HOST_NOT_FOUND; status = NSS_STATUS_NOTFOUND; goto finish; } } #endif /* ENABLE_LEGACY */ if (u.count == 0) { *errnop = ETIMEDOUT; *h_errnop = NO_RECOVERY; goto finish; } /* Alias names, assuming buffer starts a nicely aligned offset */ *((char**) buffer) = NULL; result->h_aliases = (char**) buffer; idx = sizeof(char*); assert(u.count > 0); assert(u.data.name[0]); if (buflen < strlen(u.data.name[0])+1+ /* official names */ sizeof(char*)+ /* alias names */ address_length+ /* address */ sizeof(void*)*2 + /* address list */ sizeof(void*)) { /* padding to get the alignment right */ *errnop = ERANGE; *h_errnop = NO_RECOVERY; status = NSS_STATUS_TRYAGAIN; goto finish; } /* Official name */ strcpy(buffer+idx, u.data.name[0]); result->h_name = buffer+idx; idx += strlen(u.data.name[0])+1; result->h_addrtype = af; result->h_length = address_length; /* Address */ astart = idx; memcpy(buffer+astart, addr, address_length); idx += address_length; /* Address array, idx might not be at pointer alignment anymore, so we need * to ensure it is*/ ALIGN(idx); ((char**) (buffer+idx))[0] = buffer+astart; ((char**) (buffer+idx))[1] = NULL; result->h_addr_list = (char**) (buffer+idx); status = NSS_STATUS_SUCCESS; finish: #ifdef ENABLE_LEGACY if (fd >= 0) close(fd); #endif return status; }