Output the proper number of names, the proper number of times.
[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 #if 0
17 #define DEBUGP(format...) fprintf(stderr, "nss-icmp: " format);
18 #else
19 #define DEBUGP(format...)
20 #endif
21
22 struct cache {
23     struct cache *next, *prev;
24     char *addr;
25     socklen_t addrlen;
26     int af;
27     int notfound;
28     char **names;
29     time_t at, ttl;
30 };
31
32 static int inited = 0;
33 static int timeout = -1;
34 static int usecache = 1;
35 static time_t nfttl = 300;
36 static struct cache *cache = NULL;
37
38 static 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         }
61         if(!strcmp(linebuf, "ttlnotfound")) {
62             if(p2 == NULL)
63                 continue;
64             nfttl = atoi(p2);
65         }
66         if(!strcmp(linebuf, "nocache")) {
67             usecache = 0;
68         }
69     }
70     
71     fclose(f);
72 }
73
74 static 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
94 static void cachenotfound(const void *addr, socklen_t len, int af, time_t ttl)
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;
113         cc->at = time(NULL);
114         cc->ttl = ttl;
115         
116         cc->notfound = 1;
117         
118         cc->next = cache;
119         if(cache != NULL)
120             cache->prev = cc;
121         cache = cc;
122     }
123 }
124
125 static void updatecache(const void *addr, socklen_t len, int af, char **names, time_t ttl)
126 {
127     int i;
128     struct cache *cc;
129     
130     for(cc = cache; cc != NULL; cc = cc->next) {
131         if((cc->af == af) && (cc->addrlen == len) && !memcmp(cc->addr, addr, len))
132             break;
133     }
134     if(cc == NULL) {
135         if((cc = malloc(sizeof(*cc))) == NULL)
136             return;
137         memset(cc, 0, sizeof(*cc));
138         if((cc->addr = malloc(len)) == NULL) {
139             freecache(cc);
140             return;
141         }
142         memcpy(cc->addr, addr, len);
143         cc->addrlen = len;
144         cc->af = af;
145         cc->at = time(NULL);
146         cc->ttl = ttl;
147         
148         for(i = 0; names[i] != NULL; i++);
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));
154         for(i = 0; names[i] != NULL; i++) {
155             if((cc->names[i] = malloc(strlen(names[i]) + 1)) == NULL) {
156                 freecache(cc);
157                 return;
158             }
159             strcpy(cc->names[i], names[i]);
160         }
161         
162         cc->next = cache;
163         if(cache != NULL)
164             cache->prev = cc;
165         cache = cc;
166     }
167 }
168
169 static 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
184 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)
185 {
186     int i, ret;
187     struct retstruct {
188         char *aliaslist[16];
189         char *addrlist[2];
190         char retaddr[16];
191     } *retbuf;
192     char addrbuf[1024];
193     int an, thislen, ttl;
194     char *p, *p2, *p3;
195     u_int8_t *ap;
196     pid_t child;
197     int pfd[2];
198     int rl;
199     struct cache *cc;
200     
201     if(!inited) {
202         readconfig();
203         inited = 1;
204     }
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     
213     DEBUGP("starting lookup\n");
214     
215     if(usecache) {
216         expirecache();
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;
223     }
224     
225     if(cc == NULL) {
226         DEBUGP("address not in cache, looking up for real\n");
227         ap = (u_int8_t *)addr;
228         if(inet_ntop(af, addr, addrbuf, sizeof(addrbuf)) == NULL) {
229             *errnop = errno;
230             *h_errnop = NETDB_INTERNAL;
231             return(NSS_STATUS_UNAVAIL);
232         }
233         DEBUGP("address is %s\n", addrbuf);
234     
235         if(pipe(pfd)) {
236             *errnop = errno;
237             *h_errnop = NETDB_INTERNAL;
238             return(NSS_STATUS_UNAVAIL);
239         }
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;
245             *h_errnop = NETDB_INTERNAL;
246             return(NSS_STATUS_UNAVAIL);
247         }
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);
263                 execlp("idnlookup", "idnlookup", "-Tt", timeoutbuf, addrbuf, NULL);
264             } else {
265                 execlp("idnlookup", "idnlookup", "-T", addrbuf, NULL);
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]);
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         
301         an = 0;
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) {
324             if(usecache)
325                 cachenotfound(addr, len, af, nfttl);
326             *h_errnop = TRY_AGAIN; /* XXX: Is this correct? */
327             return(NSS_STATUS_NOTFOUND);
328         }
329         retbuf->aliaslist[an] = NULL;
330         
331         if(usecache)
332             updatecache(addr, len, af, retbuf->aliaslist, ttl);
333     } else {
334         DEBUGP("address found in cache\n");
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]);
343             DEBUGP("filling in address %s, length %i\n", cc->names[i], thislen);
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);
350             retbuf->aliaslist[i] = p3;
351             p3 += thislen + 1;
352         }
353         retbuf->aliaslist[i] = NULL;
354     }
355     
356     DEBUGP("returning hostent\n");
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;
367     DEBUGP("returning\n");
368     return(NSS_STATUS_SUCCESS);
369 }