Include config.h only when HAVE_CONFIG_H is defined.
[icmp-dn.git] / src / nss-icmp.c
1 /*
2  *  nss-icmp or libnss_icmp - GNU C Library NSS module to query host
3  *  names by ICMP.
4  *  Copyright (C) 2005 Fredrik Tolf <fredrik@dolda2000.com>
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU Library General Public
8  *  License as published by the Free Software Foundation; either
9  *  version 2 of the License, or (at your option) any later version.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Library General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Library General Public
17  *  License along with this library; if not, write to the Free
18  *  Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
19  *  MA 02111-1307, USA
20  */
21
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <unistd.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <time.h>
28 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #include <netdb.h>
31 #include <arpa/inet.h>
32 #include <nss.h>
33 #include <sys/types.h>
34 #include <fcntl.h>
35 #include <sys/wait.h>
36 #ifdef HAVE_CONFIG_H
37 #include "config.h"
38 #endif
39
40 #ifndef CONFIGFILE
41 #define CONFIGFILE "/etc/nss-icmp.conf"
42 #endif
43
44 #if 0
45 #define DEBUGP(format...) fprintf(stderr, "nss-icmp: " format);
46 #else
47 #define DEBUGP(format...)
48 #endif
49
50 struct cache {
51     struct cache *next, *prev;
52     char *addr;
53     socklen_t addrlen;
54     int af;
55     int notfound;
56     char **names;
57     time_t at, ttl;
58 };
59
60 static int inited = 0;
61 static int timeout = -1;
62 static int usecache = 1;
63 static time_t nfttl = 300;
64 static struct cache *cache = NULL;
65
66 static void readconfig(void)
67 {
68     FILE *f;
69     char linebuf[1024];
70     char *p, *p2;
71     
72     if((f = fopen(CONFIGFILE, "r")) == NULL)
73         return;
74     
75     while(fgets(linebuf, sizeof(linebuf), f) != NULL) {
76         if(linebuf[0] == '#')
77             continue;
78         if((p = strchr(linebuf, '\n')) != NULL)
79             *p = 0;
80         if((p = strchr(linebuf, ' ')) != NULL) {
81             p2 = p + 1;
82             *p = 0;
83         }
84         if(!strcmp(linebuf, "timeout")) {
85             if(p2 == NULL)
86                 continue;
87             timeout = atoi(p2);
88         }
89         if(!strcmp(linebuf, "ttlnotfound")) {
90             if(p2 == NULL)
91                 continue;
92             nfttl = atoi(p2);
93         }
94         if(!strcmp(linebuf, "nocache")) {
95             usecache = 0;
96         }
97     }
98     
99     fclose(f);
100 }
101
102 static void freecache(struct cache *cc)
103 {
104     int i;
105     
106     if(cc->next != NULL)
107         cc->next->prev = cc->prev;
108     if(cc->prev != NULL)
109         cc->prev->next = cc->next;
110     if(cc == cache)
111         cache = cc->next;
112     if(cc->addr != NULL)
113         free(cc->addr);
114     if(cc->names != NULL) {
115         for(i = 0; cc->names[i] != NULL; i++)
116             free(cc->names[i]);
117         free(cc->names);
118     }
119     free(cc);
120 }
121
122 static void cachenotfound(const void *addr, socklen_t len, int af, time_t ttl)
123 {
124     struct cache *cc;
125     
126     for(cc = cache; cc != NULL; cc = cc->next) {
127         if((cc->af == af) && (cc->addrlen == len) && !memcmp(cc->addr, addr, len))
128             break;
129     }
130     if(cc == NULL) {
131         if((cc = malloc(sizeof(*cc))) == NULL)
132             return;
133         memset(cc, 0, sizeof(*cc));
134         if((cc->addr = malloc(len)) == NULL) {
135             freecache(cc);
136             return;
137         }
138         memcpy(cc->addr, addr, len);
139         cc->addrlen = len;
140         cc->af = af;
141         cc->at = time(NULL);
142         cc->ttl = ttl;
143         
144         cc->notfound = 1;
145         
146         cc->next = cache;
147         if(cache != NULL)
148             cache->prev = cc;
149         cache = cc;
150     }
151 }
152
153 static void updatecache(const void *addr, socklen_t len, int af, char **names, time_t ttl)
154 {
155     int i;
156     struct cache *cc;
157     
158     for(cc = cache; cc != NULL; cc = cc->next) {
159         if((cc->af == af) && (cc->addrlen == len) && !memcmp(cc->addr, addr, len))
160             break;
161     }
162     if(cc == NULL) {
163         if((cc = malloc(sizeof(*cc))) == NULL)
164             return;
165         memset(cc, 0, sizeof(*cc));
166         if((cc->addr = malloc(len)) == NULL) {
167             freecache(cc);
168             return;
169         }
170         memcpy(cc->addr, addr, len);
171         cc->addrlen = len;
172         cc->af = af;
173         cc->at = time(NULL);
174         cc->ttl = ttl;
175         
176         for(i = 0; names[i] != NULL; i++);
177         if((cc->names = malloc(sizeof(*(cc->names)) * (i + 1))) == NULL) {
178             freecache(cc);
179             return;
180         }
181         memset(cc->names, 0, sizeof(*(cc->names)) * (i + 1));
182         for(i = 0; names[i] != NULL; i++) {
183             if((cc->names[i] = malloc(strlen(names[i]) + 1)) == NULL) {
184                 freecache(cc);
185                 return;
186             }
187             strcpy(cc->names[i], names[i]);
188         }
189         
190         cc->next = cache;
191         if(cache != NULL)
192             cache->prev = cc;
193         cache = cc;
194     }
195 }
196
197 static void expirecache(void)
198 {
199     struct cache *cc, *next;
200     time_t now;
201     
202     now = time(NULL);
203     for(cc = cache; cc != NULL; cc = next) {
204         next = cc->next;
205         if(now - cc->at > cc->ttl) {
206             freecache(cc);
207             continue;
208         }
209     }
210 }
211
212 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)
213 {
214     int i, ret;
215     struct retstruct {
216         char *aliaslist[16];
217         char *addrlist[2];
218         char retaddr[16];
219     } *retbuf;
220     char addrbuf[1024];
221     int an, thislen, ttl;
222     char *p, *p2, *p3;
223     u_int8_t *ap;
224     pid_t child;
225     int pfd[2];
226     int rl;
227     int status;
228     struct cache *cc;
229     
230     if(!inited) {
231         readconfig();
232         inited = 1;
233     }
234     
235     retbuf = (struct retstruct *)buffer;
236     if((buflen < sizeof(*retbuf)) || (len > sizeof(retbuf->retaddr))) {
237         *errnop = ENOMEM;
238         *h_errnop = NETDB_INTERNAL;
239         return(NSS_STATUS_UNAVAIL);
240     }
241     
242     DEBUGP("starting lookup\n");
243     
244     if(usecache) {
245         expirecache();
246         for(cc = cache; cc != NULL; cc = cc->next) {
247             if((cc->af == af) && (cc->addrlen == len) && !memcmp(cc->addr, addr, len))
248                 break;
249         }
250     } else {
251         cc = NULL;
252     }
253     
254     if(cc == NULL) {
255         DEBUGP("address not in cache, looking up for real\n");
256         ap = (u_int8_t *)addr;
257         if(inet_ntop(af, addr, addrbuf, sizeof(addrbuf)) == NULL) {
258             *errnop = errno;
259             *h_errnop = NETDB_INTERNAL;
260             return(NSS_STATUS_UNAVAIL);
261         }
262         DEBUGP("address is %s\n", addrbuf);
263     
264         if(pipe(pfd)) {
265             *errnop = errno;
266             *h_errnop = NETDB_INTERNAL;
267             return(NSS_STATUS_UNAVAIL);
268         }
269         /* I honestly don't know if it is considered OK to fork in other
270          * people's programs. We need a SUID worker, though, so there's
271          * little choice that I can see. */
272         if((child = fork()) < 0) {
273             *errnop = errno;
274             *h_errnop = NETDB_INTERNAL;
275             return(NSS_STATUS_UNAVAIL);
276         }
277     
278         if(child == 0) {
279             int i, fd;
280             char timeoutbuf[128];
281         
282             if((fd = open("/dev/null", O_WRONLY)) < 0)
283                 exit(127);
284             close(pfd[0]);
285             dup2(pfd[1], 1);
286             dup2(fd, 2);
287             for(i = 3; i < FD_SETSIZE; i++)
288                 close(i);
289         
290             if(timeout != -1) {
291                 snprintf(timeoutbuf, sizeof(timeoutbuf), "%i", timeout);
292                 execlp("idnlookup", "idnlookup", "-Tt", timeoutbuf, addrbuf, NULL);
293             } else {
294                 execlp("idnlookup", "idnlookup", "-T", addrbuf, NULL);
295             }
296             exit(127);
297         }
298     
299         close(pfd[1]);
300     
301         rl = 0;
302         do {
303             ret = read(pfd[0], addrbuf + rl, sizeof(addrbuf) - rl);
304             if(ret < 0) {
305                 *errnop = errno;
306                 *h_errnop = NETDB_INTERNAL;
307                 close(pfd[0]);
308                 return(NSS_STATUS_UNAVAIL);
309             }
310             rl += ret;
311             if(rl >= sizeof(addrbuf) - 1) {
312                 *errnop = ENOMEM;
313                 *h_errnop = NETDB_INTERNAL;
314                 close(pfd[0]);
315                 return(NSS_STATUS_UNAVAIL);
316             }
317         } while(ret != 0);
318         addrbuf[rl] = 0;
319         close(pfd[0]);
320         
321         waitpid(child, &status, 0);
322         
323         if((p = strchr(addrbuf, '\n')) == NULL) {
324             if(usecache)
325                 cachenotfound(addr, len, af, nfttl);
326             *h_errnop = TRY_AGAIN; /* XXX: Is this correct? */
327             return(NSS_STATUS_NOTFOUND);
328         }
329         *(p++) = 0;
330         ttl = atoi(addrbuf);
331         
332         an = 0;
333         p3 = buffer + sizeof(*retbuf);
334         while((p2 = strchr(p, '\n')) != NULL) {
335             *p2 = 0;
336             thislen = p2 - p;
337             if(thislen == 0)
338                 continue;
339             if((p3 - buffer) + thislen + 1 > buflen) {
340                 *errnop = ENOMEM;
341                 *h_errnop = NETDB_INTERNAL;
342                 return(NSS_STATUS_UNAVAIL);
343             }
344             memcpy(p3, p, thislen + 1);
345             retbuf->aliaslist[an] = p3;
346             p3 += thislen + 1;
347             p = p2 + 1;
348             if(++an == 16) {
349                 *errnop = ENOMEM;
350                 *h_errnop = NETDB_INTERNAL;
351                 return(NSS_STATUS_UNAVAIL);
352             }
353         }
354         if(an == 0) {
355             if(usecache)
356                 cachenotfound(addr, len, af, nfttl);
357             *h_errnop = TRY_AGAIN; /* XXX: Is this correct? */
358             return(NSS_STATUS_NOTFOUND);
359         }
360         retbuf->aliaslist[an] = NULL;
361         
362         if(usecache)
363             updatecache(addr, len, af, retbuf->aliaslist, ttl);
364     } else {
365         DEBUGP("address found in cache\n");
366         if(cc->notfound) {
367             *h_errnop = TRY_AGAIN; /* XXX: Is this correct? */
368             return(NSS_STATUS_NOTFOUND);
369         }
370         
371         p3 = buffer + sizeof(*retbuf);
372         for(i = 0; cc->names[i] != NULL; i++) {
373             thislen = strlen(cc->names[i]);
374             DEBUGP("filling in address %s, length %i\n", cc->names[i], thislen);
375             if((p3 - buffer) + thislen + 1 > buflen) {
376                 *errnop = ENOMEM;
377                 *h_errnop = NETDB_INTERNAL;
378                 return(NSS_STATUS_UNAVAIL);
379             }
380             memcpy(p3, cc->names[i], thislen + 1);
381             retbuf->aliaslist[i] = p3;
382             p3 += thislen + 1;
383         }
384         retbuf->aliaslist[i] = NULL;
385     }
386     
387     DEBUGP("returning hostent\n");
388     memcpy(retbuf->retaddr, addr, len);
389     retbuf->addrlist[0] = retbuf->retaddr;
390     retbuf->addrlist[1] = NULL;
391     result->h_name = retbuf->aliaslist[0];
392     result->h_aliases = retbuf->aliaslist;
393     result->h_addr_list = retbuf->addrlist;
394     result->h_addrtype = af;
395     result->h_length = len;
396     
397     *h_errnop = NETDB_SUCCESS;
398     DEBUGP("returning\n");
399     return(NSS_STATUS_SUCCESS);
400 }
401
402 /*
403  * Local Variables:
404  * compile-command: "gcc -shared -Wall -g -o libnss_icmp.so.2 nss-icmp.c"
405  * End:
406  */