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