1 /* dnsmasq is Copyright (c) 2000-2009 Simon Kelley
2 
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License as published by
5    the Free Software Foundation; version 2 dated June, 1991, or
6    (at your option) version 3 dated 29 June, 2007.
7 
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11    GNU General Public License for more details.
12 
13    You should have received a copy of the GNU General Public License
14    along with this program.  If not, see <http://www.gnu.org/licenses/>.
15 */
16 
17 #include "dnsmasq.h"
18 
19 #ifdef HAVE_DHCP
20 
21 static struct dhcp_lease *leases = NULL, *old_leases = NULL;
22 static int dns_dirty, file_dirty, leases_left;
23 
lease_init(time_t now)24 void lease_init(time_t now) {
25     unsigned long ei;
26     struct in_addr addr;
27     struct dhcp_lease* lease;
28     int clid_len, hw_len, hw_type;
29     FILE* leasestream;
30 
31     /* These two each hold a DHCP option max size 255
32        and get a terminating zero added */
33     daemon->dhcp_buff = safe_malloc(256);
34     daemon->dhcp_buff2 = safe_malloc(256);
35 
36     leases_left = daemon->dhcp_max;
37 
38     if (daemon->options & OPT_LEASE_RO) {
39         /* run "<lease_change_script> init" once to get the
40        initial state of the database. If leasefile-ro is
41        set without a script, we just do without any
42        lease database. */
43 #ifdef HAVE_SCRIPT
44         if (daemon->lease_change_command) {
45             strcpy(daemon->dhcp_buff, daemon->lease_change_command);
46             strcat(daemon->dhcp_buff, " init");
47             leasestream = popen(daemon->dhcp_buff, "r");
48         } else
49 #endif
50         {
51             file_dirty = dns_dirty = 0;
52             return;
53         }
54 
55     } else {
56         /* NOTE: need a+ mode to create file if it doesn't exist */
57         leasestream = daemon->lease_stream = fopen(daemon->lease_file, "a+");
58 
59         if (!leasestream)
60             die(_("cannot open or create lease file %s: %s"), daemon->lease_file, EC_FILE);
61 
62         /* a+ mode leaves pointer at end. */
63         rewind(leasestream);
64     }
65 
66     /* client-id max length is 255 which is 255*2 digits + 254 colons
67        borrow DNS packet buffer which is always larger than 1000 bytes */
68     if (leasestream)
69         while (fscanf(leasestream, "%lu %255s %16s %255s %764s", &ei, daemon->dhcp_buff2,
70                       daemon->namebuff, daemon->dhcp_buff, daemon->packet) == 5) {
71             hw_len = parse_hex(daemon->dhcp_buff2, (unsigned char*) daemon->dhcp_buff2,
72                                DHCP_CHADDR_MAX, NULL, &hw_type);
73             /* For backwards compatibility, no explict MAC address type means ether. */
74             if (hw_type == 0 && hw_len != 0) hw_type = ARPHRD_ETHER;
75 
76             addr.s_addr = inet_addr(daemon->namebuff);
77 
78             /* decode hex in place */
79             clid_len = 0;
80             if (strcmp(daemon->packet, "*") != 0)
81                 clid_len =
82                     parse_hex(daemon->packet, (unsigned char*) daemon->packet, 255, NULL, NULL);
83 
84             if (!(lease = lease_allocate(addr))) die(_("too many stored leases"), NULL, EC_MISC);
85 
86 #ifdef HAVE_BROKEN_RTC
87             if (ei != 0)
88                 lease->expires = (time_t) ei + now;
89             else
90                 lease->expires = (time_t) 0;
91             lease->length = ei;
92 #else
93             /* strictly time_t is opaque, but this hack should work on all sane systems,
94                even when sizeof(time_t) == 8 */
95             lease->expires = (time_t) ei;
96 #endif
97 
98             lease_set_hwaddr(lease, (unsigned char*) daemon->dhcp_buff2,
99                              (unsigned char*) daemon->packet, hw_len, hw_type, clid_len);
100 
101             if (strcmp(daemon->dhcp_buff, "*") != 0)
102                 lease_set_hostname(lease, daemon->dhcp_buff, 0);
103 
104             /* set these correctly: the "old" events are generated later from
105                the startup synthesised SIGHUP. */
106             lease->new = lease->changed = 0;
107         }
108 
109 #ifdef HAVE_SCRIPT
110     if (!daemon->lease_stream) {
111         int rc = 0;
112 
113         /* shell returns 127 for "command not found", 126 for bad permissions. */
114         if (!leasestream || (rc = pclose(leasestream)) == -1 || WEXITSTATUS(rc) == 127 ||
115             WEXITSTATUS(rc) == 126) {
116             if (WEXITSTATUS(rc) == 127)
117                 errno = ENOENT;
118             else if (WEXITSTATUS(rc) == 126)
119                 errno = EACCES;
120             die(_("cannot run lease-init script %s: %s"), daemon->lease_change_command, EC_FILE);
121         }
122 
123         if (WEXITSTATUS(rc) != 0) {
124             sprintf(daemon->dhcp_buff, "%d", WEXITSTATUS(rc));
125             die(_("lease-init script returned exit code %s"), daemon->dhcp_buff,
126                 WEXITSTATUS(rc) + EC_INIT_OFFSET);
127         }
128     }
129 #endif
130 
131     /* Some leases may have expired */
132     file_dirty = 0;
133     lease_prune(NULL, now);
134     dns_dirty = 1;
135 }
136 
lease_update_from_configs(void)137 void lease_update_from_configs(void) {
138     /* changes to the config may change current leases. */
139 
140     struct dhcp_lease* lease;
141     struct dhcp_config* config;
142     char* name;
143 
144     for (lease = leases; lease; lease = lease->next)
145         if ((config = find_config(daemon->dhcp_conf, NULL, lease->clid, lease->clid_len,
146                                   lease->hwaddr, lease->hwaddr_len, lease->hwaddr_type, NULL)) &&
147             (config->flags & CONFIG_NAME) &&
148             (!(config->flags & CONFIG_ADDR) || config->addr.s_addr == lease->addr.s_addr))
149             lease_set_hostname(lease, config->hostname, 1);
150         else if ((name = host_from_dns(lease->addr)))
151             lease_set_hostname(lease, name, 1); /* updates auth flag only */
152 }
153 
ourprintf(int * errp,char * format,...)154 static void ourprintf(int* errp, char* format, ...) {
155     va_list ap;
156 
157     va_start(ap, format);
158     if (!(*errp) && vfprintf(daemon->lease_stream, format, ap) < 0) *errp = errno;
159     va_end(ap);
160 }
161 
lease_update_file(time_t now)162 void lease_update_file(time_t now) {
163     struct dhcp_lease* lease;
164     time_t next_event;
165     int i, err = 0;
166 
167     if (file_dirty != 0 && daemon->lease_stream) {
168         errno = 0;
169         rewind(daemon->lease_stream);
170         if (errno != 0 || ftruncate(fileno(daemon->lease_stream), 0) != 0) err = errno;
171 
172         for (lease = leases; lease; lease = lease->next) {
173 #ifdef HAVE_BROKEN_RTC
174             ourprintf(&err, "%u ", lease->length);
175 #else
176             ourprintf(&err, "%lu ", (unsigned long) lease->expires);
177 #endif
178             if (lease->hwaddr_type != ARPHRD_ETHER || lease->hwaddr_len == 0)
179                 ourprintf(&err, "%.2x-", lease->hwaddr_type);
180             for (i = 0; i < lease->hwaddr_len; i++) {
181                 ourprintf(&err, "%.2x", lease->hwaddr[i]);
182                 if (i != lease->hwaddr_len - 1) ourprintf(&err, ":");
183             }
184 
185             ourprintf(&err, " %s ", inet_ntoa(lease->addr));
186             ourprintf(&err, "%s ", lease->hostname ? lease->hostname : "*");
187 
188             if (lease->clid && lease->clid_len != 0) {
189                 for (i = 0; i < lease->clid_len - 1; i++) ourprintf(&err, "%.2x:", lease->clid[i]);
190                 ourprintf(&err, "%.2x\n", lease->clid[i]);
191             } else
192                 ourprintf(&err, "*\n");
193         }
194 
195         if (fflush(daemon->lease_stream) != 0 || fsync(fileno(daemon->lease_stream)) < 0)
196             err = errno;
197 
198         if (!err) file_dirty = 0;
199     }
200 
201     /* Set alarm for when the first lease expires + slop. */
202     for (next_event = 0, lease = leases; lease; lease = lease->next)
203         if (lease->expires != 0 &&
204             (next_event == 0 || difftime(next_event, lease->expires + 10) > 0.0))
205             next_event = lease->expires + 10;
206 
207     if (err) {
208         if (next_event == 0 || difftime(next_event, LEASE_RETRY + now) > 0.0)
209             next_event = LEASE_RETRY + now;
210 
211         my_syslog(MS_DHCP | LOG_ERR, _("failed to write %s: %s (retry in %us)"), daemon->lease_file,
212                   strerror(err), (unsigned int) difftime(next_event, now));
213     }
214 
215     if (next_event != 0) alarm((unsigned) difftime(next_event, now));
216 }
217 
lease_update_dns(void)218 void lease_update_dns(void) {
219     struct dhcp_lease* lease;
220 
221     if (daemon->port != 0 && dns_dirty) {
222         cache_unhash_dhcp();
223 
224         for (lease = leases; lease; lease = lease->next) {
225             if (lease->fqdn) cache_add_dhcp_entry(lease->fqdn, &lease->addr, lease->expires);
226 
227             if (!(daemon->options & OPT_DHCP_FQDN) && lease->hostname)
228                 cache_add_dhcp_entry(lease->hostname, &lease->addr, lease->expires);
229         }
230 
231         dns_dirty = 0;
232     }
233 }
234 
lease_prune(struct dhcp_lease * target,time_t now)235 void lease_prune(struct dhcp_lease* target, time_t now) {
236     struct dhcp_lease *lease, *tmp, **up;
237 
238     for (lease = leases, up = &leases; lease; lease = tmp) {
239         tmp = lease->next;
240         if ((lease->expires != 0 && difftime(now, lease->expires) > 0) || lease == target) {
241             file_dirty = 1;
242             if (lease->hostname) dns_dirty = 1;
243 
244             *up = lease->next; /* unlink */
245 
246             /* Put on old_leases list 'till we
247                can run the script */
248             lease->next = old_leases;
249             old_leases = lease;
250 
251             leases_left++;
252         } else
253             up = &lease->next;
254     }
255 }
256 
lease_find_by_client(unsigned char * hwaddr,int hw_len,int hw_type,unsigned char * clid,int clid_len)257 struct dhcp_lease* lease_find_by_client(unsigned char* hwaddr, int hw_len, int hw_type,
258                                         unsigned char* clid, int clid_len) {
259     struct dhcp_lease* lease;
260 
261     if (clid)
262         for (lease = leases; lease; lease = lease->next)
263             if (lease->clid && clid_len == lease->clid_len &&
264                 memcmp(clid, lease->clid, clid_len) == 0)
265                 return lease;
266 
267     for (lease = leases; lease; lease = lease->next)
268         if ((!lease->clid || !clid) && hw_len != 0 && lease->hwaddr_len == hw_len &&
269             lease->hwaddr_type == hw_type && memcmp(hwaddr, lease->hwaddr, hw_len) == 0)
270             return lease;
271 
272     return NULL;
273 }
274 
lease_find_by_addr(struct in_addr addr)275 struct dhcp_lease* lease_find_by_addr(struct in_addr addr) {
276     struct dhcp_lease* lease;
277 
278     for (lease = leases; lease; lease = lease->next)
279         if (lease->addr.s_addr == addr.s_addr) return lease;
280 
281     return NULL;
282 }
283 
lease_allocate(struct in_addr addr)284 struct dhcp_lease* lease_allocate(struct in_addr addr) {
285     struct dhcp_lease* lease;
286     if (!leases_left || !(lease = whine_malloc(sizeof(struct dhcp_lease)))) return NULL;
287 
288     memset(lease, 0, sizeof(struct dhcp_lease));
289     lease->new = 1;
290     lease->addr = addr;
291     lease->hwaddr_len = 256; /* illegal value */
292     lease->expires = 1;
293 #ifdef HAVE_BROKEN_RTC
294     lease->length = 0xffffffff; /* illegal value */
295 #endif
296     lease->next = leases;
297     leases = lease;
298 
299     file_dirty = 1;
300     leases_left--;
301 
302     return lease;
303 }
304 
lease_set_expires(struct dhcp_lease * lease,unsigned int len,time_t now)305 void lease_set_expires(struct dhcp_lease* lease, unsigned int len, time_t now) {
306     time_t exp = now + (time_t) len;
307 
308     if (len == 0xffffffff) {
309         exp = 0;
310         len = 0;
311     }
312 
313     if (exp != lease->expires) {
314         dns_dirty = 1;
315         lease->expires = exp;
316 #ifndef HAVE_BROKEN_RTC
317         lease->aux_changed = file_dirty = 1;
318 #endif
319     }
320 
321 #ifdef HAVE_BROKEN_RTC
322     if (len != lease->length) {
323         lease->length = len;
324         lease->aux_changed = file_dirty = 1;
325     }
326 #endif
327 }
328 
lease_set_hwaddr(struct dhcp_lease * lease,unsigned char * hwaddr,unsigned char * clid,int hw_len,int hw_type,int clid_len)329 void lease_set_hwaddr(struct dhcp_lease* lease, unsigned char* hwaddr, unsigned char* clid,
330                       int hw_len, int hw_type, int clid_len) {
331     if (hw_len != lease->hwaddr_len || hw_type != lease->hwaddr_type ||
332         (hw_len != 0 && memcmp(lease->hwaddr, hwaddr, hw_len) != 0)) {
333         memcpy(lease->hwaddr, hwaddr, hw_len);
334         lease->hwaddr_len = hw_len;
335         lease->hwaddr_type = hw_type;
336         lease->changed = file_dirty = 1; /* run script on change */
337     }
338 
339     /* only update clid when one is available, stops packets
340        without a clid removing the record. Lease init uses
341        clid_len == 0 for no clid. */
342     if (clid_len != 0 && clid) {
343         if (!lease->clid) lease->clid_len = 0;
344 
345         if (lease->clid_len != clid_len) {
346             lease->aux_changed = file_dirty = 1;
347             free(lease->clid);
348             if (!(lease->clid = whine_malloc(clid_len))) return;
349         } else if (memcmp(lease->clid, clid, clid_len) != 0)
350             lease->aux_changed = file_dirty = 1;
351 
352         lease->clid_len = clid_len;
353         memcpy(lease->clid, clid, clid_len);
354     }
355 }
356 
kill_name(struct dhcp_lease * lease)357 static void kill_name(struct dhcp_lease* lease) {
358     /* run script to say we lost our old name */
359 
360     /* this shouldn't happen unless updates are very quick and the
361        script very slow, we just avoid a memory leak if it does. */
362     free(lease->old_hostname);
363 
364     /* If we know the fqdn, pass that. The helper will derive the
365        unqualified name from it, free the unqulaified name here. */
366 
367     if (lease->fqdn) {
368         lease->old_hostname = lease->fqdn;
369         free(lease->hostname);
370     } else
371         lease->old_hostname = lease->hostname;
372 
373     lease->hostname = lease->fqdn = NULL;
374 }
375 
lease_set_hostname(struct dhcp_lease * lease,char * name,int auth)376 void lease_set_hostname(struct dhcp_lease* lease, char* name, int auth) {
377     struct dhcp_lease* lease_tmp;
378     char *new_name = NULL, *new_fqdn = NULL;
379 
380     if (lease->hostname && name && hostname_isequal(lease->hostname, name)) {
381         lease->auth_name = auth;
382         return;
383     }
384 
385     if (!name && !lease->hostname) return;
386 
387     /* If a machine turns up on a new net without dropping the old lease,
388        or two machines claim the same name, then we end up with two interfaces with
389        the same name. Check for that here and remove the name from the old lease.
390        Don't allow a name from the client to override a name from dnsmasq config. */
391 
392     if (name) {
393         if ((new_name = whine_malloc(strlen(name) + 1))) {
394             char* suffix = get_domain(lease->addr);
395             strcpy(new_name, name);
396             if (suffix && (new_fqdn = whine_malloc(strlen(new_name) + strlen(suffix) + 2))) {
397                 strcpy(new_fqdn, name);
398                 strcat(new_fqdn, ".");
399                 strcat(new_fqdn, suffix);
400             }
401         }
402 
403         /* Depending on mode, we check either unqualified name or FQDN. */
404         for (lease_tmp = leases; lease_tmp; lease_tmp = lease_tmp->next) {
405             if (daemon->options & OPT_DHCP_FQDN) {
406                 if (!new_fqdn || !lease_tmp->fqdn || !hostname_isequal(lease_tmp->fqdn, new_fqdn))
407                     continue;
408             } else {
409                 if (!new_name || !lease_tmp->hostname ||
410                     !hostname_isequal(lease_tmp->hostname, new_name))
411                     continue;
412             }
413 
414             if (lease_tmp->auth_name && !auth) {
415                 free(new_name);
416                 free(new_fqdn);
417                 return;
418             }
419 
420             kill_name(lease_tmp);
421             break;
422         }
423     }
424 
425     if (lease->hostname) kill_name(lease);
426 
427     lease->hostname = new_name;
428     lease->fqdn = new_fqdn;
429     lease->auth_name = auth;
430 
431     file_dirty = 1;
432     dns_dirty = 1;
433     lease->changed = 1; /* run script on change */
434 }
435 
lease_set_interface(struct dhcp_lease * lease,int interface)436 void lease_set_interface(struct dhcp_lease* lease, int interface) {
437     if (lease->last_interface == interface) return;
438 
439     lease->last_interface = interface;
440     lease->changed = 1;
441 }
442 
rerun_scripts(void)443 void rerun_scripts(void) {
444     struct dhcp_lease* lease;
445 
446     for (lease = leases; lease; lease = lease->next) lease->changed = 1;
447 }
448 
449 /* deleted leases get transferred to the old_leases list.
450    remove them here, after calling the lease change
451    script. Also run the lease change script on new/modified leases.
452 
453    Return zero if nothing to do. */
do_script_run(time_t now)454 int do_script_run(time_t now) {
455     struct dhcp_lease* lease;
456 
457     if (old_leases) {
458         lease = old_leases;
459 
460         /* If the lease still has an old_hostname, do the "old" action on that first */
461         if (lease->old_hostname) {
462 #ifdef HAVE_SCRIPT
463             queue_script(ACTION_OLD_HOSTNAME, lease, lease->old_hostname, now);
464 #endif
465             free(lease->old_hostname);
466             lease->old_hostname = NULL;
467             return 1;
468         } else {
469             kill_name(lease);
470 #ifdef HAVE_SCRIPT
471             queue_script(ACTION_DEL, lease, lease->old_hostname, now);
472 #endif
473             old_leases = lease->next;
474 
475             free(lease->old_hostname);
476             free(lease->clid);
477             free(lease->vendorclass);
478             free(lease->userclass);
479             free(lease->supplied_hostname);
480             free(lease);
481 
482             return 1;
483         }
484     }
485 
486     /* make sure we announce the loss of a hostname before its new location. */
487     for (lease = leases; lease; lease = lease->next)
488         if (lease->old_hostname) {
489 #ifdef HAVE_SCRIPT
490             queue_script(ACTION_OLD_HOSTNAME, lease, lease->old_hostname, now);
491 #endif
492             free(lease->old_hostname);
493             lease->old_hostname = NULL;
494             return 1;
495         }
496 
497     for (lease = leases; lease; lease = lease->next)
498         if (lease->new || lease->changed ||
499             (lease->aux_changed && (daemon->options & OPT_LEASE_RO))) {
500 #ifdef HAVE_SCRIPT
501             queue_script(lease->new ? ACTION_ADD : ACTION_OLD, lease,
502                          lease->fqdn ? lease->fqdn : lease->hostname, now);
503 #endif
504             lease->new = lease->changed = lease->aux_changed = 0;
505 
506             /* these are used for the "add" call, then junked, since they're not in the database */
507             free(lease->vendorclass);
508             lease->vendorclass = NULL;
509 
510             free(lease->userclass);
511             lease->userclass = NULL;
512 
513             free(lease->supplied_hostname);
514             lease->supplied_hostname = NULL;
515 
516             return 1;
517         }
518 
519     return 0; /* nothing to do */
520 }
521 
522 #endif
523