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