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