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