Added config file.
[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     for(cc = cache; cc != NULL; cc = cc->next) {
182         if((cc->af == af) && (cc->addrlen == len) && !memcmp(cc->addr, addr, len))
183             break;
184     }
185     
186     if(cc == NULL) {
187         ap = (u_int8_t *)addr;
188         if(inet_ntop(af, addr, addrbuf, sizeof(addrbuf)) == NULL) {
189             *errnop = errno;
190             *h_errnop = NETDB_INTERNAL;
191             return(NSS_STATUS_UNAVAIL);
192         }
193     
194         if(pipe(pfd)) {
195             *errnop = errno;
196             *h_errnop = NETDB_INTERNAL;
197             return(NSS_STATUS_UNAVAIL);
198         }
199         /* I honestly don't know if it is considered OK to fork in other
200          * people's programs. We need a SUID worker, though, so there's
201          * little choice that I can see. */
202         if((child = fork()) < 0) {
203             *errnop = errno;
204             *h_errnop = NETDB_INTERNAL;
205             return(NSS_STATUS_UNAVAIL);
206         }
207     
208         if(child == 0) {
209             int i, fd;
210             char timeoutbuf[128];
211         
212             if((fd = open("/dev/null", O_WRONLY)) < 0)
213                 exit(127);
214             close(pfd[0]);
215             dup2(pfd[1], 1);
216             dup2(fd, 2);
217             for(i = 3; i < FD_SETSIZE; i++)
218                 close(i);
219         
220             if(timeout != -1) {
221                 snprintf(timeoutbuf, sizeof(timeoutbuf), "%i", timeout);
222                 execlp("idnlookup", "idnlookup", "-t", timeoutbuf, addrbuf, NULL);
223             } else {
224                 execlp("idnlookup", "idnlookup", addrbuf, NULL);
225             }
226             exit(127);
227         }
228     
229         close(pfd[1]);
230     
231         rl = 0;
232         do {
233             ret = read(pfd[0], addrbuf + rl, sizeof(addrbuf) - rl);
234             if(ret < 0) {
235                 *errnop = errno;
236                 *h_errnop = NETDB_INTERNAL;
237                 close(pfd[0]);
238                 return(NSS_STATUS_UNAVAIL);
239             }
240             rl += ret;
241             if(rl >= sizeof(addrbuf) - 1) {
242                 *errnop = ENOMEM;
243                 *h_errnop = NETDB_INTERNAL;
244                 close(pfd[0]);
245                 return(NSS_STATUS_UNAVAIL);
246             }
247         } while(ret != 0);
248         addrbuf[rl] = 0;
249         close(pfd[0]);
250     
251         an = 0;
252         p = addrbuf;
253         p3 = buffer + sizeof(*retbuf);
254         while((p2 = strchr(p, '\n')) != NULL) {
255             *p2 = 0;
256             thislen = p2 - p;
257             if(thislen == 0)
258                 continue;
259             if((p3 - buffer) + thislen + 1 > buflen) {
260                 *errnop = ENOMEM;
261                 *h_errnop = NETDB_INTERNAL;
262                 return(NSS_STATUS_UNAVAIL);
263             }
264             memcpy(p3, p, thislen + 1);
265             retbuf->aliaslist[an] = p3;
266             p3 += thislen + 1;
267             p = p2 + 1;
268             if(++an == 16) {
269                 *errnop = ENOMEM;
270                 *h_errnop = NETDB_INTERNAL;
271                 return(NSS_STATUS_UNAVAIL);
272             }
273         }
274         if(an == 0) {
275             cachenotfound(addr, len, af);
276             *h_errnop = TRY_AGAIN; /* XXX: Is this correct? */
277             return(NSS_STATUS_NOTFOUND);
278         }
279         retbuf->aliaslist[an] = NULL;
280     } else {
281         if(cc->notfound) {
282             *h_errnop = TRY_AGAIN; /* XXX: Is this correct? */
283             return(NSS_STATUS_NOTFOUND);
284         }
285         
286         p3 = buffer + sizeof(*retbuf);
287         for(i = 0; cc->names[i] != NULL; i++) {
288             thislen = strlen(cc->names[i]);
289             if((p3 - buffer) + thislen + 1 > buflen) {
290                 *errnop = ENOMEM;
291                 *h_errnop = NETDB_INTERNAL;
292                 return(NSS_STATUS_UNAVAIL);
293             }
294             memcpy(p3, cc->names[i], thislen + 1);
295             retbuf->aliaslist[an] = p3;
296             p3 += thislen + 1;
297             if(++an == 16) {
298                 *errnop = ENOMEM;
299                 *h_errnop = NETDB_INTERNAL;
300                 return(NSS_STATUS_UNAVAIL);
301             }
302         }
303     }
304     
305     memcpy(retbuf->retaddr, addr, len);
306     retbuf->addrlist[0] = retbuf->retaddr;
307     retbuf->addrlist[1] = NULL;
308     result->h_name = retbuf->aliaslist[0];
309     result->h_aliases = retbuf->aliaslist;
310     result->h_addr_list = retbuf->addrlist;
311     result->h_addrtype = af;
312     result->h_length = len;
313     
314     if(cc == NULL)
315         updatecache(result);
316     
317     *h_errnop = NETDB_SUCCESS;
318     return(NSS_STATUS_SUCCESS);
319 }