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