Output the proper number of names, the proper number of times.
[icmp-dn.git] / icmpdnd.c
1 /*
2  *  icmpdnd - ICMP Domain Name responder daemon for Linux
3  *  Copyright (C) 2005 Fredrik Tolf <fredrik@dolda2000.com>
4  *  
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *  
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *  
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 */
19 #include <stdlib.h>
20 #include <unistd.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <syslog.h>
25 #include <time.h>
26 #include <sys/socket.h>
27 #include <sys/poll.h>
28 #include <netinet/in.h>
29 #include <arpa/inet.h>
30 #include <netinet/ip.h>
31 #include <sys/types.h>
32
33 struct icmphdr {
34     u_int8_t type;
35     u_int8_t code;
36     u_int16_t checksum;
37 };
38
39 struct reqhdr {
40     u_int8_t type;
41     u_int8_t code;
42     u_int16_t checksum;
43     u_int16_t id;
44     u_int16_t seq;
45 };
46
47 struct rephdr {
48     u_int8_t type;
49     u_int8_t code;
50     u_int16_t checksum;
51     u_int16_t id;
52     u_int16_t seq;
53     int32_t ttl;
54 };
55
56 #define ICMP_NAMEREQ 37
57 #define ICMP_NAMEREP 38
58
59 volatile int alive;
60
61 size_t filldn(char *dst)
62 {
63     char *p, *p2, *dp;
64     char namebuf[1024];
65     int hl;
66     
67     if(gethostname(namebuf, sizeof(namebuf)) < 0) {
68         perror("gethostname");
69         exit(1);
70     }
71     hl = strlen(namebuf);
72     namebuf[hl++] = '.';
73     if(getdomainname(namebuf + hl, sizeof(namebuf) - hl) < 0) {
74         perror("getdomainname");
75         exit(1);
76     }
77     if(strlen(namebuf + hl) != 0) {
78         hl = strlen(namebuf);
79         namebuf[hl++] = '.';
80     }
81     namebuf[hl] = 0;
82     
83     p = namebuf;
84     dp = dst;
85     while((p2 = strchr(p, '.')) != NULL) {
86         *p2 = 0;
87         *(dp++) = p2 - p;
88         memcpy(dp, p, p2 - p);
89         dp += p2 - p;
90         p = p2 + 1;
91     }
92     *(dp++) = 0;
93     
94     return(dp - dst);
95 }
96
97 void cksum(void *hdr, size_t len)
98 {
99     struct icmphdr *ih;
100     u_int8_t *cb;
101     int i;
102     int b1, b2;
103     
104     ih = (struct icmphdr *)hdr;
105     cb = (u_int8_t *)hdr;
106     ih->checksum = 0;
107     b1 = b2 = 0;
108     for(i = 0; i < (len & ~1); i += 2) {
109         b1 += cb[i];
110         b2 += cb[i + 1];
111     }
112     if(i & 1)
113         b1 += cb[len - 1];
114     while(1) {
115         if(b1 >= 256) {
116             b2 += b1 >> 8;
117             b1 &= 0xff;
118             continue;
119         }
120         if(b2 >= 256) {
121             b1 += b2 >> 8;
122             b2 &= 0xff;
123             continue;
124         }
125         break;
126     }
127     cb = (u_int8_t *)&ih->checksum;
128     cb[0] = ~(u_int8_t)b1;
129     cb[1] = ~(u_int8_t)b2;
130 }
131
132 int main(int argc, char **argv)
133 {
134     int i, n, ret;
135     int c, cs, s4, s6, namelen, datalen;
136     int daemonize, ttl;
137     unsigned char buf[65536];
138     struct sockaddr_storage name;
139     struct reqhdr req;
140     struct rephdr rep;
141     struct iphdr iphdr;
142     size_t hdrlen;
143     struct pollfd pfd[2];
144     time_t curtime, lasterr;
145     
146     daemonize = 1;
147     ttl = 3600;
148     while((c = getopt(argc, argv, "nht:")) != -1) {
149         switch(c) {
150         case 't':
151             ttl = atoi(optarg);
152             break;
153         case 'n':
154             daemonize = 0;
155             break;
156         case 'h':
157         case '?':
158         case ':':
159         default:
160             fprintf(stderr, "usage: icmpdnd [-n]");
161             exit((c == 'h')?0:1);
162         }
163     }
164     
165     s4 = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
166     s6 = socket(PF_INET6, SOCK_RAW, IPPROTO_ICMP);
167     if((s4 < 0) && (s6 < 0)) {
168         perror("could not open raw socket");
169         exit(1);
170     }
171     
172     if(daemonize)
173         daemon(0, 0);
174     
175     openlog("icmpdnd", LOG_PID | LOG_NDELAY, LOG_DAEMON);
176     
177     alive = 1;
178     lasterr = 0;
179     while(alive) {
180         n = 0;
181         if(s4 >= 0) {
182             pfd[n].fd = s4;
183             pfd[n].events = POLLIN;
184             n++;
185         }
186         if(s6 >= 0) {
187             pfd[n].fd = s6;
188             pfd[n].events = POLLIN;
189             n++;
190         }
191         ret = poll(pfd, n, -1);
192
193         curtime = time(NULL);
194         if(ret < 0) {
195             if(errno == EINTR)
196                 continue;
197             syslog(LOG_ERR, "error while polling sockets: %m");
198             if(lasterr == curtime) {
199                 syslog(LOG_CRIT, "exiting due to repeated errors");
200                 exit(1);
201             }
202             lasterr = curtime;
203         }
204         
205         for(i = 0; i < n; i++) {
206             if((pfd[i].revents & POLLIN) == 0)
207                 continue;
208             cs = pfd[i].fd;
209             namelen = sizeof(name);
210             memset(&name, 0, sizeof(name));
211             ret = recvfrom(cs, buf, sizeof(buf), 0, (struct sockaddr *)&name, &namelen);
212             if(ret < 0) {
213                 syslog(LOG_WARNING, "error while receiving datagram: %m");
214                 continue;
215             }
216             
217             if(cs == s4) {
218                 if(ret < sizeof(iphdr) + sizeof(req))
219                     continue;
220                 hdrlen = sizeof(iphdr);
221                 memcpy(&iphdr, buf, sizeof(iphdr));
222                 if(iphdr.protocol != IPPROTO_ICMP)
223                     continue;
224             } else if(cs == s6) {
225                 if(ret < sizeof(req))
226                     continue;
227                 ((struct sockaddr_in6 *)&name)->sin6_port = 0;
228                 hdrlen = 0;
229             } else {
230                 syslog(LOG_CRIT, "strangeness!");
231                 abort();
232             }
233             memcpy(&req, buf + hdrlen, sizeof(req));
234             if(req.type != ICMP_NAMEREQ)
235                 continue;
236             rep.type = ICMP_NAMEREP;
237             rep.code = 0;
238             rep.id = req.id;
239             rep.seq = req.seq;
240             rep.ttl = htonl(ttl);
241             memcpy(buf, &rep, sizeof(rep));
242             datalen = filldn(buf + sizeof(rep));
243         
244             cksum(buf, datalen + sizeof(rep));
245         
246             /* XXX: The correct source address needs to be filled in from
247              * the request's destination address. */
248             ret = sendto(cs, buf, datalen + sizeof(rep), 0, (struct sockaddr *)&name, namelen);
249             if(ret < 0)
250                 syslog(LOG_WARNING, "error in sending reply: %m");
251         }
252     }
253     
254     close(s4);
255     close(s6);
256     return(0);
257 }