56ddaa94273fa100b34d854bc83461796dfabfb9
[oweals/busybox.git] / networking / udhcp / dhcpd.c
1 /* dhcpd.c
2  *
3  * udhcp Server
4  * Copyright (C) 1999 Matthew Ramsay <matthewr@moreton.com.au>
5  *                      Chris Trew <ctrew@moreton.com.au>
6  *
7  * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22  */
23
24 #include <fcntl.h>
25 #include <string.h>
26 #include <stdlib.h>
27 #include <sys/wait.h>
28 #include <sys/stat.h>
29 #include <arpa/inet.h>
30 #include <netdb.h>
31 #include <netinet/in.h>
32 #include <stdio.h>
33 #include <sys/types.h>
34 #include <sys/socket.h>
35 #include <unistd.h>
36 #include <signal.h>
37 #include <errno.h>
38 #include <sys/ioctl.h>
39 #include <time.h>
40 #include <sys/time.h>
41
42 #include "debug.h"
43 #include "dhcpd.h"
44 #include "arpping.h"
45 #include "socket.h"
46 #include "options.h"
47 #include "files.h"
48 #include "leases.h"
49 #include "packet.h"
50 #include "serverpacket.h"
51 #include "pidfile.h"
52
53
54 /* globals */
55 struct dhcpOfferedAddr *leases;
56 struct server_config_t server_config;
57 static int signal_pipe[2];
58
59 /* Exit and cleanup */
60 static void exit_server(int retval)
61 {
62         pidfile_delete(server_config.pidfile);
63         CLOSE_LOG();
64         exit(retval);
65 }
66
67
68 /* Signal handler */
69 static void signal_handler(int sig)
70 {
71         if (send(signal_pipe[1], &sig, sizeof(sig), MSG_DONTWAIT) < 0) {
72                 LOG(LOG_ERR, "Could not send signal: %s", 
73                         strerror(errno));
74         }
75 }
76
77
78 #ifdef COMBINED_BINARY  
79 int udhcpd_main(int argc, char *argv[])
80 #else
81 int main(int argc, char *argv[])
82 #endif
83 {       
84         fd_set rfds;
85         struct timeval tv;
86         int server_socket = -1;
87         int bytes, retval;
88         struct dhcpMessage packet;
89         unsigned char *state;
90         unsigned char *server_id, *requested;
91         u_int32_t server_id_align, requested_align;
92         unsigned long timeout_end;
93         struct option_set *option;
94         struct dhcpOfferedAddr *lease;
95         int pid_fd;
96         int max_sock;
97         int sig;
98         unsigned long num_ips;
99         
100         OPEN_LOG("udhcpd");
101         LOG(LOG_INFO, "udhcp server (v%s) started", VERSION);
102
103         memset(&server_config, 0, sizeof(struct server_config_t));
104         
105         if (argc < 2)
106                 read_config(DHCPD_CONF_FILE);
107         else read_config(argv[1]);
108
109         pid_fd = pidfile_acquire(server_config.pidfile);
110         pidfile_write_release(pid_fd);
111
112         if ((option = find_option(server_config.options, DHCP_LEASE_TIME))) {
113                 memcpy(&server_config.lease, option->data + 2, 4);
114                 server_config.lease = ntohl(server_config.lease);
115         }
116         else server_config.lease = LEASE_TIME;
117         
118         /* Sanity check */
119         num_ips = ntohl(server_config.end) - ntohl(server_config.start);
120         if (server_config.max_leases > num_ips) {
121                 LOG(LOG_ERR, "max_leases value (%lu) not sane, setting to %lu instead",
122                         server_config.max_leases, num_ips);
123                 server_config.max_leases = num_ips;
124         }
125
126         leases = xmalloc(sizeof(struct dhcpOfferedAddr) * server_config.max_leases);
127         memset(leases, 0, sizeof(struct dhcpOfferedAddr) * server_config.max_leases);
128         read_leases(server_config.lease_file);
129
130         if (read_interface(server_config.interface, &server_config.ifindex,
131                            &server_config.server, server_config.arp) < 0)
132                 exit_server(1);
133
134 #ifndef DEBUGGING
135         pid_fd = pidfile_acquire(server_config.pidfile); /* hold lock during fork. */
136         if (daemon(0, 0) == -1) {
137                 perror("fork");
138                 exit_server(1);
139         }
140         pidfile_write_release(pid_fd);
141 #endif
142
143
144         socketpair(AF_UNIX, SOCK_STREAM, 0, signal_pipe);
145         signal(SIGUSR1, signal_handler);
146         signal(SIGTERM, signal_handler);
147
148         timeout_end = time(0) + server_config.auto_time;
149         while(1) { /* loop until universe collapses */
150
151                 if (server_socket < 0)
152                         if ((server_socket = listen_socket(INADDR_ANY, SERVER_PORT, server_config.interface)) < 0) {
153                                 LOG(LOG_ERR, "FATAL: couldn't create server socket, %s", strerror(errno));
154                                 exit_server(0);
155                         }                       
156
157                 FD_ZERO(&rfds);
158                 FD_SET(server_socket, &rfds);
159                 FD_SET(signal_pipe[0], &rfds);
160                 if (server_config.auto_time) {
161                         tv.tv_sec = timeout_end - time(0);
162                         tv.tv_usec = 0;
163                 }
164                 if (!server_config.auto_time || tv.tv_sec > 0) {
165                         max_sock = server_socket > signal_pipe[0] ? server_socket : signal_pipe[0];
166                         retval = select(max_sock + 1, &rfds, NULL, NULL, 
167                                         server_config.auto_time ? &tv : NULL);
168                 } else retval = 0; /* If we already timed out, fall through */
169
170                 if (retval == 0) {
171                         write_leases();
172                         timeout_end = time(0) + server_config.auto_time;
173                         continue;
174                 } else if (retval < 0 && errno != EINTR) {
175                         DEBUG(LOG_INFO, "error on select");
176                         continue;
177                 }
178                 
179                 if (FD_ISSET(signal_pipe[0], &rfds)) {
180                         if (read(signal_pipe[0], &sig, sizeof(sig)) < 0)
181                                 continue; /* probably just EINTR */
182                         switch (sig) {
183                         case SIGUSR1:
184                                 LOG(LOG_INFO, "Received a SIGUSR1");
185                                 write_leases();
186                                 /* why not just reset the timeout, eh */
187                                 timeout_end = time(0) + server_config.auto_time;
188                                 continue;
189                         case SIGTERM:
190                                 LOG(LOG_INFO, "Received a SIGTERM");
191                                 exit_server(0);
192                         }
193                 }
194
195                 if ((bytes = get_packet(&packet, server_socket)) < 0) { /* this waits for a packet - idle */
196                         if (bytes == -1 && errno != EINTR) {
197                                 DEBUG(LOG_INFO, "error on read, %s, reopening socket", strerror(errno));
198                                 close(server_socket);
199                                 server_socket = -1;
200                         }
201                         continue;
202                 }
203
204                 if ((state = get_option(&packet, DHCP_MESSAGE_TYPE)) == NULL) {
205                         DEBUG(LOG_ERR, "couldn't get option from packet, ignoring");
206                         continue;
207                 }
208                 
209                 /* ADDME: look for a static lease */
210                 lease = find_lease_by_chaddr(packet.chaddr);
211                 switch (state[0]) {
212                 case DHCPDISCOVER:
213                         DEBUG(LOG_INFO,"received DISCOVER");
214                         
215                         if (sendOffer(&packet) < 0) {
216                                 LOG(LOG_ERR, "send OFFER failed");
217                         }
218                         break;                  
219                 case DHCPREQUEST:
220                         DEBUG(LOG_INFO, "received REQUEST");
221
222                         requested = get_option(&packet, DHCP_REQUESTED_IP);
223                         server_id = get_option(&packet, DHCP_SERVER_ID);
224
225                         if (requested) memcpy(&requested_align, requested, 4);
226                         if (server_id) memcpy(&server_id_align, server_id, 4);
227                 
228                         if (lease) { /*ADDME: or static lease */
229                                 if (server_id) {
230                                         /* SELECTING State */
231                                         DEBUG(LOG_INFO, "server_id = %08x", ntohl(server_id_align));
232                                         if (server_id_align == server_config.server && requested && 
233                                             requested_align == lease->yiaddr) {
234                                                 sendACK(&packet, lease->yiaddr);
235                                         }
236                                 } else {
237                                         if (requested) {
238                                                 /* INIT-REBOOT State */
239                                                 if (lease->yiaddr == requested_align)
240                                                         sendACK(&packet, lease->yiaddr);
241                                                 else sendNAK(&packet);
242                                         } else {
243                                                 /* RENEWING or REBINDING State */
244                                                 if (lease->yiaddr == packet.ciaddr)
245                                                         sendACK(&packet, lease->yiaddr);
246                                                 else {
247                                                         /* don't know what to do!!!! */
248                                                         sendNAK(&packet);
249                                                 }
250                                         }                                               
251                                 }
252                         
253                         /* what to do if we have no record of the client */
254                         } else if (server_id) {
255                                 /* SELECTING State */
256
257                         } else if (requested) {
258                                 /* INIT-REBOOT State */
259                                 if ((lease = find_lease_by_yiaddr(requested_align))) {
260                                         if (lease_expired(lease)) {
261                                                 /* probably best if we drop this lease */
262                                                 memset(lease->chaddr, 0, 16);
263                                         /* make some contention for this address */
264                                         } else sendNAK(&packet);
265                                 } else if (requested_align < server_config.start || 
266                                            requested_align > server_config.end) {
267                                         sendNAK(&packet);
268                                 } /* else remain silent */
269
270                         } else {
271                                  /* RENEWING or REBINDING State */
272                         }
273                         break;
274                 case DHCPDECLINE:
275                         DEBUG(LOG_INFO,"received DECLINE");
276                         if (lease) {
277                                 memset(lease->chaddr, 0, 16);
278                                 lease->expires = time(0) + server_config.decline_time;
279                         }                       
280                         break;
281                 case DHCPRELEASE:
282                         DEBUG(LOG_INFO,"received RELEASE");
283                         if (lease) lease->expires = time(0);
284                         break;
285                 case DHCPINFORM:
286                         DEBUG(LOG_INFO,"received INFORM");
287                         send_inform(&packet);
288                         break;  
289                 default:
290                         LOG(LOG_WARNING, "unsupported DHCP message (%02x) -- ignoring", state[0]);
291                 }
292         }
293
294         return 0;
295 }
296