Should have thought of this...
[icmp-dn.git] / nss-icmp.c
CommitLineData
732b8359
DC
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
2dec74ab
DC
22#include <stdlib.h>
23#include <stdio.h>
24#include <unistd.h>
25#include <string.h>
26#include <errno.h>
f0df8f66 27#include <time.h>
2dec74ab
DC
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>
46c36380 35#include <sys/wait.h>
2dec74ab 36
15a913c2 37#define CONFIGFILE "/etc/nss-icmp.conf"
78d913fb
DC
38#if 0
39#define DEBUGP(format...) fprintf(stderr, "nss-icmp: " format);
40#else
41#define DEBUGP(format...)
42#endif
15a913c2
DC
43
44struct cache {
45 struct cache *next, *prev;
46 char *addr;
47 socklen_t addrlen;
48 int af;
49 int notfound;
50 char **names;
f0df8f66 51 time_t at, ttl;
15a913c2
DC
52};
53
54static int inited = 0;
55static int timeout = -1;
56static int usecache = 1;
f0df8f66 57static time_t nfttl = 300;
15a913c2
DC
58static struct cache *cache = NULL;
59
60static 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 }
f0df8f66
DC
83 if(!strcmp(linebuf, "ttlnotfound")) {
84 if(p2 == NULL)
85 continue;
86 nfttl = atoi(p2);
87 }
15a913c2
DC
88 if(!strcmp(linebuf, "nocache")) {
89 usecache = 0;
90 }
91 }
92
93 fclose(f);
94}
95
96static 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
f0df8f66 116static void cachenotfound(const void *addr, socklen_t len, int af, time_t ttl)
15a913c2
DC
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;
f0df8f66
DC
135 cc->at = time(NULL);
136 cc->ttl = ttl;
15a913c2
DC
137
138 cc->notfound = 1;
139
140 cc->next = cache;
141 if(cache != NULL)
142 cache->prev = cc;
143 cache = cc;
144 }
145}
146
f0df8f66 147static void updatecache(const void *addr, socklen_t len, int af, char **names, time_t ttl)
15a913c2
DC
148{
149 int i;
150 struct cache *cc;
151
152 for(cc = cache; cc != NULL; cc = cc->next) {
f0df8f66 153 if((cc->af == af) && (cc->addrlen == len) && !memcmp(cc->addr, addr, len))
15a913c2
DC
154 break;
155 }
156 if(cc == NULL) {
157 if((cc = malloc(sizeof(*cc))) == NULL)
158 return;
159 memset(cc, 0, sizeof(*cc));
f0df8f66 160 if((cc->addr = malloc(len)) == NULL) {
15a913c2
DC
161 freecache(cc);
162 return;
163 }
f0df8f66
DC
164 memcpy(cc->addr, addr, len);
165 cc->addrlen = len;
166 cc->af = af;
167 cc->at = time(NULL);
168 cc->ttl = ttl;
15a913c2 169
f0df8f66 170 for(i = 0; names[i] != NULL; i++);
15a913c2
DC
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));
f0df8f66
DC
176 for(i = 0; names[i] != NULL; i++) {
177 if((cc->names[i] = malloc(strlen(names[i]) + 1)) == NULL) {
15a913c2
DC
178 freecache(cc);
179 return;
180 }
f0df8f66 181 strcpy(cc->names[i], names[i]);
15a913c2
DC
182 }
183
184 cc->next = cache;
185 if(cache != NULL)
186 cache->prev = cc;
187 cache = cc;
188 }
189}
190
f0df8f66
DC
191static 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
2dec74ab
DC
206enum 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{
15a913c2 208 int i, ret;
2dec74ab
DC
209 struct retstruct {
210 char *aliaslist[16];
211 char *addrlist[2];
212 char retaddr[16];
213 } *retbuf;
214 char addrbuf[1024];
f0df8f66 215 int an, thislen, ttl;
2dec74ab
DC
216 char *p, *p2, *p3;
217 u_int8_t *ap;
218 pid_t child;
219 int pfd[2];
220 int rl;
46c36380 221 int status;
15a913c2
DC
222 struct cache *cc;
223
224 if(!inited) {
225 readconfig();
226 inited = 1;
227 }
2dec74ab
DC
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
78d913fb
DC
236 DEBUGP("starting lookup\n");
237
f416a8cd 238 if(usecache) {
f0df8f66 239 expirecache();
f416a8cd
DC
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;
2dec74ab
DC
246 }
247
15a913c2 248 if(cc == NULL) {
78d913fb 249 DEBUGP("address not in cache, looking up for real\n");
15a913c2
DC
250 ap = (u_int8_t *)addr;
251 if(inet_ntop(af, addr, addrbuf, sizeof(addrbuf)) == NULL) {
2dec74ab
DC
252 *errnop = errno;
253 *h_errnop = NETDB_INTERNAL;
2dec74ab
DC
254 return(NSS_STATUS_UNAVAIL);
255 }
78d913fb 256 DEBUGP("address is %s\n", addrbuf);
15a913c2
DC
257
258 if(pipe(pfd)) {
259 *errnop = errno;
2dec74ab 260 *h_errnop = NETDB_INTERNAL;
2dec74ab
DC
261 return(NSS_STATUS_UNAVAIL);
262 }
15a913c2
DC
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;
2dec74ab
DC
268 *h_errnop = NETDB_INTERNAL;
269 return(NSS_STATUS_UNAVAIL);
270 }
15a913c2
DC
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);
f0df8f66 286 execlp("idnlookup", "idnlookup", "-Tt", timeoutbuf, addrbuf, NULL);
15a913c2 287 } else {
f0df8f66 288 execlp("idnlookup", "idnlookup", "-T", addrbuf, NULL);
15a913c2
DC
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]);
f0df8f66 314
46c36380
DC
315 waitpid(child, &status, 0);
316
f0df8f66
DC
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
15a913c2 326 an = 0;
15a913c2
DC
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) {
f416a8cd 349 if(usecache)
f0df8f66 350 cachenotfound(addr, len, af, nfttl);
15a913c2
DC
351 *h_errnop = TRY_AGAIN; /* XXX: Is this correct? */
352 return(NSS_STATUS_NOTFOUND);
353 }
354 retbuf->aliaslist[an] = NULL;
f0df8f66
DC
355
356 if(usecache)
357 updatecache(addr, len, af, retbuf->aliaslist, ttl);
15a913c2 358 } else {
78d913fb 359 DEBUGP("address found in cache\n");
15a913c2
DC
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]);
78d913fb 368 DEBUGP("filling in address %s, length %i\n", cc->names[i], thislen);
15a913c2
DC
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);
78d913fb 375 retbuf->aliaslist[i] = p3;
15a913c2 376 p3 += thislen + 1;
2dec74ab 377 }
78d913fb 378 retbuf->aliaslist[i] = NULL;
2dec74ab 379 }
2dec74ab 380
78d913fb 381 DEBUGP("returning hostent\n");
2dec74ab
DC
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;
78d913fb 392 DEBUGP("returning\n");
2dec74ab
DC
393 return(NSS_STATUS_SUCCESS);
394}
732b8359
DC
395
396/*
397 * Local Variables:
398 * compile-command: "gcc -shared -Wall -g -o libnss_icmp.so.2 nss-icmp.c"
399 * End:
400 */