--- /dev/null
+/*
+ * nss-icmp or libnss_icmp - GNU C Library NSS module to query host
+ * names by ICMP.
+ * Copyright (C) 2005 Fredrik Tolf <fredrik@dolda2000.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <nss.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+
+#define CONFIGFILE "/etc/nss-icmp.conf"
+#if 0
+#define DEBUGP(format...) fprintf(stderr, "nss-icmp: " format);
+#else
+#define DEBUGP(format...)
+#endif
+
+struct cache {
+ struct cache *next, *prev;
+ char *addr;
+ socklen_t addrlen;
+ int af;
+ int notfound;
+ char **names;
+ time_t at, ttl;
+};
+
+static int inited = 0;
+static int timeout = -1;
+static int usecache = 1;
+static time_t nfttl = 300;
+static struct cache *cache = NULL;
+
+static void readconfig(void)
+{
+ FILE *f;
+ char linebuf[1024];
+ char *p, *p2;
+
+ if((f = fopen(CONFIGFILE, "r")) == NULL)
+ return;
+
+ while(fgets(linebuf, sizeof(linebuf), f) != NULL) {
+ if(linebuf[0] == '#')
+ continue;
+ if((p = strchr(linebuf, '\n')) != NULL)
+ *p = 0;
+ if((p = strchr(linebuf, ' ')) != NULL) {
+ p2 = p + 1;
+ *p = 0;
+ }
+ if(!strcmp(linebuf, "timeout")) {
+ if(p2 == NULL)
+ continue;
+ timeout = atoi(p2);
+ }
+ if(!strcmp(linebuf, "ttlnotfound")) {
+ if(p2 == NULL)
+ continue;
+ nfttl = atoi(p2);
+ }
+ if(!strcmp(linebuf, "nocache")) {
+ usecache = 0;
+ }
+ }
+
+ fclose(f);
+}
+
+static void freecache(struct cache *cc)
+{
+ int i;
+
+ if(cc->next != NULL)
+ cc->next->prev = cc->prev;
+ if(cc->prev != NULL)
+ cc->prev->next = cc->next;
+ if(cc == cache)
+ cache = cc->next;
+ if(cc->addr != NULL)
+ free(cc->addr);
+ if(cc->names != NULL) {
+ for(i = 0; cc->names[i] != NULL; i++)
+ free(cc->names[i]);
+ free(cc->names);
+ }
+ free(cc);
+}
+
+static void cachenotfound(const void *addr, socklen_t len, int af, time_t ttl)
+{
+ struct cache *cc;
+
+ for(cc = cache; cc != NULL; cc = cc->next) {
+ if((cc->af == af) && (cc->addrlen == len) && !memcmp(cc->addr, addr, len))
+ break;
+ }
+ if(cc == NULL) {
+ if((cc = malloc(sizeof(*cc))) == NULL)
+ return;
+ memset(cc, 0, sizeof(*cc));
+ if((cc->addr = malloc(len)) == NULL) {
+ freecache(cc);
+ return;
+ }
+ memcpy(cc->addr, addr, len);
+ cc->addrlen = len;
+ cc->af = af;
+ cc->at = time(NULL);
+ cc->ttl = ttl;
+
+ cc->notfound = 1;
+
+ cc->next = cache;
+ if(cache != NULL)
+ cache->prev = cc;
+ cache = cc;
+ }
+}
+
+static void updatecache(const void *addr, socklen_t len, int af, char **names, time_t ttl)
+{
+ int i;
+ struct cache *cc;
+
+ for(cc = cache; cc != NULL; cc = cc->next) {
+ if((cc->af == af) && (cc->addrlen == len) && !memcmp(cc->addr, addr, len))
+ break;
+ }
+ if(cc == NULL) {
+ if((cc = malloc(sizeof(*cc))) == NULL)
+ return;
+ memset(cc, 0, sizeof(*cc));
+ if((cc->addr = malloc(len)) == NULL) {
+ freecache(cc);
+ return;
+ }
+ memcpy(cc->addr, addr, len);
+ cc->addrlen = len;
+ cc->af = af;
+ cc->at = time(NULL);
+ cc->ttl = ttl;
+
+ for(i = 0; names[i] != NULL; i++);
+ if((cc->names = malloc(sizeof(*(cc->names)) * (i + 1))) == NULL) {
+ freecache(cc);
+ return;
+ }
+ memset(cc->names, 0, sizeof(*(cc->names)) * (i + 1));
+ for(i = 0; names[i] != NULL; i++) {
+ if((cc->names[i] = malloc(strlen(names[i]) + 1)) == NULL) {
+ freecache(cc);
+ return;
+ }
+ strcpy(cc->names[i], names[i]);
+ }
+
+ cc->next = cache;
+ if(cache != NULL)
+ cache->prev = cc;
+ cache = cc;
+ }
+}
+
+static void expirecache(void)
+{
+ struct cache *cc, *next;
+ time_t now;
+
+ now = time(NULL);
+ for(cc = cache; cc != NULL; cc = next) {
+ next = cc->next;
+ if(now - cc->at > cc->ttl) {
+ freecache(cc);
+ continue;
+ }
+ }
+}
+
+enum nss_status _nss_icmp_gethostbyaddr_r(const void *addr, socklen_t len, int af, struct hostent *result, char *buffer, size_t buflen, int *errnop, int *h_errnop)
+{
+ int i, ret;
+ struct retstruct {
+ char *aliaslist[16];
+ char *addrlist[2];
+ char retaddr[16];
+ } *retbuf;
+ char addrbuf[1024];
+ int an, thislen, ttl;
+ char *p, *p2, *p3;
+ u_int8_t *ap;
+ pid_t child;
+ int pfd[2];
+ int rl;
+ int status;
+ struct cache *cc;
+
+ if(!inited) {
+ readconfig();
+ inited = 1;
+ }
+
+ retbuf = (struct retstruct *)buffer;
+ if((buflen < sizeof(*retbuf)) || (len > sizeof(retbuf->retaddr))) {
+ *errnop = ENOMEM;
+ *h_errnop = NETDB_INTERNAL;
+ return(NSS_STATUS_UNAVAIL);
+ }
+
+ DEBUGP("starting lookup\n");
+
+ if(usecache) {
+ expirecache();
+ for(cc = cache; cc != NULL; cc = cc->next) {
+ if((cc->af == af) && (cc->addrlen == len) && !memcmp(cc->addr, addr, len))
+ break;
+ }
+ } else {
+ cc = NULL;
+ }
+
+ if(cc == NULL) {
+ DEBUGP("address not in cache, looking up for real\n");
+ ap = (u_int8_t *)addr;
+ if(inet_ntop(af, addr, addrbuf, sizeof(addrbuf)) == NULL) {
+ *errnop = errno;
+ *h_errnop = NETDB_INTERNAL;
+ return(NSS_STATUS_UNAVAIL);
+ }
+ DEBUGP("address is %s\n", addrbuf);
+
+ if(pipe(pfd)) {
+ *errnop = errno;
+ *h_errnop = NETDB_INTERNAL;
+ return(NSS_STATUS_UNAVAIL);
+ }
+ /* I honestly don't know if it is considered OK to fork in other
+ * people's programs. We need a SUID worker, though, so there's
+ * little choice that I can see. */
+ if((child = fork()) < 0) {
+ *errnop = errno;
+ *h_errnop = NETDB_INTERNAL;
+ return(NSS_STATUS_UNAVAIL);
+ }
+
+ if(child == 0) {
+ int i, fd;
+ char timeoutbuf[128];
+
+ if((fd = open("/dev/null", O_WRONLY)) < 0)
+ exit(127);
+ close(pfd[0]);
+ dup2(pfd[1], 1);
+ dup2(fd, 2);
+ for(i = 3; i < FD_SETSIZE; i++)
+ close(i);
+
+ if(timeout != -1) {
+ snprintf(timeoutbuf, sizeof(timeoutbuf), "%i", timeout);
+ execlp("idnlookup", "idnlookup", "-Tt", timeoutbuf, addrbuf, NULL);
+ } else {
+ execlp("idnlookup", "idnlookup", "-T", addrbuf, NULL);
+ }
+ exit(127);
+ }
+
+ close(pfd[1]);
+
+ rl = 0;
+ do {
+ ret = read(pfd[0], addrbuf + rl, sizeof(addrbuf) - rl);
+ if(ret < 0) {
+ *errnop = errno;
+ *h_errnop = NETDB_INTERNAL;
+ close(pfd[0]);
+ return(NSS_STATUS_UNAVAIL);
+ }
+ rl += ret;
+ if(rl >= sizeof(addrbuf) - 1) {
+ *errnop = ENOMEM;
+ *h_errnop = NETDB_INTERNAL;
+ close(pfd[0]);
+ return(NSS_STATUS_UNAVAIL);
+ }
+ } while(ret != 0);
+ addrbuf[rl] = 0;
+ close(pfd[0]);
+
+ waitpid(child, &status, 0);
+
+ if((p = strchr(addrbuf, '\n')) == NULL) {
+ if(usecache)
+ cachenotfound(addr, len, af, nfttl);
+ *h_errnop = TRY_AGAIN; /* XXX: Is this correct? */
+ return(NSS_STATUS_NOTFOUND);
+ }
+ *(p++) = 0;
+ ttl = atoi(addrbuf);
+
+ an = 0;
+ p3 = buffer + sizeof(*retbuf);
+ while((p2 = strchr(p, '\n')) != NULL) {
+ *p2 = 0;
+ thislen = p2 - p;
+ if(thislen == 0)
+ continue;
+ if((p3 - buffer) + thislen + 1 > buflen) {
+ *errnop = ENOMEM;
+ *h_errnop = NETDB_INTERNAL;
+ return(NSS_STATUS_UNAVAIL);
+ }
+ memcpy(p3, p, thislen + 1);
+ retbuf->aliaslist[an] = p3;
+ p3 += thislen + 1;
+ p = p2 + 1;
+ if(++an == 16) {
+ *errnop = ENOMEM;
+ *h_errnop = NETDB_INTERNAL;
+ return(NSS_STATUS_UNAVAIL);
+ }
+ }
+ if(an == 0) {
+ if(usecache)
+ cachenotfound(addr, len, af, nfttl);
+ *h_errnop = TRY_AGAIN; /* XXX: Is this correct? */
+ return(NSS_STATUS_NOTFOUND);
+ }
+ retbuf->aliaslist[an] = NULL;
+
+ if(usecache)
+ updatecache(addr, len, af, retbuf->aliaslist, ttl);
+ } else {
+ DEBUGP("address found in cache\n");
+ if(cc->notfound) {
+ *h_errnop = TRY_AGAIN; /* XXX: Is this correct? */
+ return(NSS_STATUS_NOTFOUND);
+ }
+
+ p3 = buffer + sizeof(*retbuf);
+ for(i = 0; cc->names[i] != NULL; i++) {
+ thislen = strlen(cc->names[i]);
+ DEBUGP("filling in address %s, length %i\n", cc->names[i], thislen);
+ if((p3 - buffer) + thislen + 1 > buflen) {
+ *errnop = ENOMEM;
+ *h_errnop = NETDB_INTERNAL;
+ return(NSS_STATUS_UNAVAIL);
+ }
+ memcpy(p3, cc->names[i], thislen + 1);
+ retbuf->aliaslist[i] = p3;
+ p3 += thislen + 1;
+ }
+ retbuf->aliaslist[i] = NULL;
+ }
+
+ DEBUGP("returning hostent\n");
+ memcpy(retbuf->retaddr, addr, len);
+ retbuf->addrlist[0] = retbuf->retaddr;
+ retbuf->addrlist[1] = NULL;
+ result->h_name = retbuf->aliaslist[0];
+ result->h_aliases = retbuf->aliaslist;
+ result->h_addr_list = retbuf->addrlist;
+ result->h_addrtype = af;
+ result->h_length = len;
+
+ *h_errnop = NETDB_SUCCESS;
+ DEBUGP("returning\n");
+ return(NSS_STATUS_SUCCESS);
+}
+
+/*
+ * Local Variables:
+ * compile-command: "gcc -shared -Wall -g -o libnss_icmp.so.2 nss-icmp.c"
+ * End:
+ */