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