Honor the TTL.
[icmp-dn.git] / nss-icmp.c
CommitLineData
2dec74ab
DC
1#include <stdlib.h>
2#include <stdio.h>
3#include <unistd.h>
4#include <string.h>
5#include <errno.h>
f0df8f66 6#include <time.h>
2dec74ab
DC
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
15a913c2
DC
15#define CONFIGFILE "/etc/nss-icmp.conf"
16
17struct cache {
18 struct cache *next, *prev;
19 char *addr;
20 socklen_t addrlen;
21 int af;
22 int notfound;
23 char **names;
f0df8f66 24 time_t at, ttl;
15a913c2
DC
25};
26
27static int inited = 0;
28static int timeout = -1;
29static int usecache = 1;
f0df8f66 30static time_t nfttl = 300;
15a913c2
DC
31static struct cache *cache = NULL;
32
33static 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 }
f0df8f66
DC
56 if(!strcmp(linebuf, "ttlnotfound")) {
57 if(p2 == NULL)
58 continue;
59 nfttl = atoi(p2);
60 }
15a913c2
DC
61 if(!strcmp(linebuf, "nocache")) {
62 usecache = 0;
63 }
64 }
65
66 fclose(f);
67}
68
69static 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
f0df8f66 89static void cachenotfound(const void *addr, socklen_t len, int af, time_t ttl)
15a913c2
DC
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;
f0df8f66
DC
108 cc->at = time(NULL);
109 cc->ttl = ttl;
15a913c2
DC
110
111 cc->notfound = 1;
112
113 cc->next = cache;
114 if(cache != NULL)
115 cache->prev = cc;
116 cache = cc;
117 }
118}
119
f0df8f66 120static void updatecache(const void *addr, socklen_t len, int af, char **names, time_t ttl)
15a913c2
DC
121{
122 int i;
123 struct cache *cc;
124
125 for(cc = cache; cc != NULL; cc = cc->next) {
f0df8f66 126 if((cc->af == af) && (cc->addrlen == len) && !memcmp(cc->addr, addr, len))
15a913c2
DC
127 break;
128 }
129 if(cc == NULL) {
130 if((cc = malloc(sizeof(*cc))) == NULL)
131 return;
132 memset(cc, 0, sizeof(*cc));
f0df8f66 133 if((cc->addr = malloc(len)) == NULL) {
15a913c2
DC
134 freecache(cc);
135 return;
136 }
f0df8f66
DC
137 memcpy(cc->addr, addr, len);
138 cc->addrlen = len;
139 cc->af = af;
140 cc->at = time(NULL);
141 cc->ttl = ttl;
15a913c2 142
f0df8f66 143 for(i = 0; names[i] != NULL; i++);
15a913c2
DC
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));
f0df8f66
DC
149 for(i = 0; names[i] != NULL; i++) {
150 if((cc->names[i] = malloc(strlen(names[i]) + 1)) == NULL) {
15a913c2
DC
151 freecache(cc);
152 return;
153 }
f0df8f66 154 strcpy(cc->names[i], names[i]);
15a913c2
DC
155 }
156
157 cc->next = cache;
158 if(cache != NULL)
159 cache->prev = cc;
160 cache = cc;
161 }
162}
163
f0df8f66
DC
164static 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
2dec74ab
DC
179enum 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{
15a913c2 181 int i, ret;
2dec74ab
DC
182 struct retstruct {
183 char *aliaslist[16];
184 char *addrlist[2];
185 char retaddr[16];
186 } *retbuf;
187 char addrbuf[1024];
f0df8f66 188 int an, thislen, ttl;
2dec74ab
DC
189 char *p, *p2, *p3;
190 u_int8_t *ap;
191 pid_t child;
192 int pfd[2];
193 int rl;
15a913c2
DC
194 struct cache *cc;
195
196 if(!inited) {
197 readconfig();
198 inited = 1;
199 }
2dec74ab
DC
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
f416a8cd 208 if(usecache) {
f0df8f66 209 expirecache();
f416a8cd
DC
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;
2dec74ab
DC
216 }
217
15a913c2
DC
218 if(cc == NULL) {
219 ap = (u_int8_t *)addr;
220 if(inet_ntop(af, addr, addrbuf, sizeof(addrbuf)) == NULL) {
2dec74ab
DC
221 *errnop = errno;
222 *h_errnop = NETDB_INTERNAL;
2dec74ab
DC
223 return(NSS_STATUS_UNAVAIL);
224 }
15a913c2
DC
225
226 if(pipe(pfd)) {
227 *errnop = errno;
2dec74ab 228 *h_errnop = NETDB_INTERNAL;
2dec74ab
DC
229 return(NSS_STATUS_UNAVAIL);
230 }
15a913c2
DC
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;
2dec74ab
DC
236 *h_errnop = NETDB_INTERNAL;
237 return(NSS_STATUS_UNAVAIL);
238 }
15a913c2
DC
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);
f0df8f66 254 execlp("idnlookup", "idnlookup", "-Tt", timeoutbuf, addrbuf, NULL);
15a913c2 255 } else {
f0df8f66 256 execlp("idnlookup", "idnlookup", "-T", addrbuf, NULL);
15a913c2
DC
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]);
f0df8f66
DC
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
15a913c2 292 an = 0;
15a913c2
DC
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) {
f416a8cd 315 if(usecache)
f0df8f66 316 cachenotfound(addr, len, af, nfttl);
15a913c2
DC
317 *h_errnop = TRY_AGAIN; /* XXX: Is this correct? */
318 return(NSS_STATUS_NOTFOUND);
319 }
320 retbuf->aliaslist[an] = NULL;
f0df8f66
DC
321
322 if(usecache)
323 updatecache(addr, len, af, retbuf->aliaslist, ttl);
15a913c2
DC
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 }
2dec74ab
DC
346 }
347 }
2dec74ab
DC
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}