#include <sys/types.h>
#include <fcntl.h>
+#define CONFIGFILE "/etc/nss-icmp.conf"
+
+struct cache {
+ struct cache *next, *prev;
+ char *addr;
+ socklen_t addrlen;
+ int af;
+ int notfound;
+ char **names;
+};
+
+static int inited = 0;
+static int timeout = -1;
+static int usecache = 1;
+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, "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)
+{
+ 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->notfound = 1;
+
+ cc->next = cache;
+ if(cache != NULL)
+ cache->prev = cc;
+ cache = cc;
+ }
+}
+
+static void updatecache(struct hostent *he)
+{
+ int i;
+ struct cache *cc;
+
+ for(cc = cache; cc != NULL; cc = cc->next) {
+ if((cc->af == he->h_addrtype) && (cc->addrlen == he->h_length) && !memcmp(cc->addr, he->h_addr_list[0], he->h_length))
+ break;
+ }
+ if(cc == NULL) {
+ if((cc = malloc(sizeof(*cc))) == NULL)
+ return;
+ memset(cc, 0, sizeof(*cc));
+ if((cc->addr = malloc(he->h_length)) == NULL) {
+ freecache(cc);
+ return;
+ }
+ memcpy(cc->addr, he->h_addr_list[0], he->h_length);
+ cc->addrlen = he->h_length;
+ cc->af = he->h_addrtype;
+
+ for(i = 0; he->h_aliases[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; he->h_aliases[i] != NULL; i++) {
+ if((cc->names[i] = malloc(strlen(he->h_aliases[i]) + 1)) == NULL) {
+ freecache(cc);
+ return;
+ }
+ strcpy(cc->names[i], he->h_aliases[i]);
+ }
+
+ cc->next = cache;
+ if(cache != NULL)
+ cache->prev = cc;
+ cache = cc;
+ }
+}
+
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 ret;
+ int i, ret;
struct retstruct {
char *aliaslist[16];
char *addrlist[2];
pid_t child;
int pfd[2];
int rl;
+ struct cache *cc;
+
+ if(!inited) {
+ readconfig();
+ inited = 1;
+ }
retbuf = (struct retstruct *)buffer;
if((buflen < sizeof(*retbuf)) || (len > sizeof(retbuf->retaddr))) {
return(NSS_STATUS_UNAVAIL);
}
- ap = (u_int8_t *)addr;
- if(inet_ntop(af, addr, addrbuf, sizeof(addrbuf)) == NULL) {
- *errnop = errno;
- *h_errnop = NETDB_INTERNAL;
- return(NSS_STATUS_UNAVAIL);
- }
-
- 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;
-
- 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);
-
- execlp("idnlookup", "idnlookup", addrbuf, NULL);
- exit(127);
+ for(cc = cache; cc != NULL; cc = cc->next) {
+ if((cc->af == af) && (cc->addrlen == len) && !memcmp(cc->addr, addr, len))
+ break;
}
- close(pfd[1]);
-
- rl = 0;
- do {
- ret = read(pfd[0], addrbuf + rl, sizeof(addrbuf) - rl);
- if(ret < 0) {
+ if(cc == NULL) {
+ ap = (u_int8_t *)addr;
+ if(inet_ntop(af, addr, addrbuf, sizeof(addrbuf)) == NULL) {
*errnop = errno;
*h_errnop = NETDB_INTERNAL;
- close(pfd[0]);
return(NSS_STATUS_UNAVAIL);
}
- rl += ret;
- if(rl >= sizeof(addrbuf) - 1) {
- *errnop = ENOMEM;
+
+ if(pipe(pfd)) {
+ *errnop = errno;
*h_errnop = NETDB_INTERNAL;
- close(pfd[0]);
return(NSS_STATUS_UNAVAIL);
}
- } while(ret != 0);
- addrbuf[rl] = 0;
- close(pfd[0]);
-
- an = 0;
- p = addrbuf;
- 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;
+ /* 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);
}
- 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(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", "-t", timeoutbuf, addrbuf, NULL);
+ } else {
+ execlp("idnlookup", "idnlookup", 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]);
+
+ an = 0;
+ p = addrbuf;
+ 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) {
+ cachenotfound(addr, len, af);
+ *h_errnop = TRY_AGAIN; /* XXX: Is this correct? */
+ return(NSS_STATUS_NOTFOUND);
+ }
+ retbuf->aliaslist[an] = NULL;
+ } else {
+ 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]);
+ 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[an] = p3;
+ p3 += thislen + 1;
+ if(++an == 16) {
+ *errnop = ENOMEM;
+ *h_errnop = NETDB_INTERNAL;
+ return(NSS_STATUS_UNAVAIL);
+ }
}
}
- if(an == 0) {
- *h_errnop = TRY_AGAIN; /* XXX: Is this correct? */
- return(NSS_STATUS_NOTFOUND);
- }
- retbuf->aliaslist[an] = NULL;
memcpy(retbuf->retaddr, addr, len);
retbuf->addrlist[0] = retbuf->retaddr;
result->h_addrtype = af;
result->h_length = len;
+ if(cc == NULL)
+ updatecache(result);
+
*h_errnop = NETDB_SUCCESS;
return(NSS_STATUS_SUCCESS);
}