1/* dnsmasq is Copyright (c) 2000-2003 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.
6
7   This program is distributed in the hope that it will be useful,
8   but WITHOUT ANY WARRANTY; without even the implied warranty of
9   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10   GNU General Public License for more details.
11*/
12
13/* Author's email: simon@thekelleys.org.uk */
14
15#include "dnsmasq.h"
16
17static struct dhcp_lease *leases;
18static FILE *lease_file;
19static int dns_dirty, file_dirty, new_lease;
20static int leases_left;
21
22void lease_init(struct daemon *daemon, time_t now)
23{
24  unsigned int e0, e1, e2, e3, e4, e5, a0, a1, a2, a3;
25  unsigned long ei;
26  time_t expires;
27  unsigned char hwaddr[ETHER_ADDR_LEN];
28  struct in_addr addr;
29  struct dhcp_lease *lease;
30  int clid_len = 0;
31  int has_old = 0;
32  char *buff = daemon->dhcp_buff;
33  char *buff2 = daemon->dhcp_buff2;
34
35  leases = NULL;
36  leases_left = daemon->dhcp_max;
37
38  /* NOTE: need a+ mode to create file if it doesn't exist */
39  if (!(lease_file = fopen(daemon->lease_file, "a+")))
40    die("cannot open or create leases file: %s", NULL);
41
42  /* a+ mode lease pointer at end. */
43  rewind(lease_file);
44
45  while (fscanf(lease_file, "%lu %x:%x:%x:%x:%x:%x %d.%d.%d.%d %257s %257s",
46		&ei, &e0, &e1, &e2, &e3, &e4, &e5, &a0, &a1, &a2, &a3,
47		buff, buff2) == 13)
48    {
49#ifdef HAVE_BROKEN_RTC
50      if (ei)
51	expires = (time_t)ei + now;
52      else
53	expires = (time_t)0;
54#else
55      /* strictly time_t is opaque, but this hack should work on all sane systems,
56	 even when sizeof(time_t) == 8 */
57      expires = (time_t)ei;
58
59      if (ei != 0  && difftime(now, expires) > 0)
60	{
61	  has_old = 1;
62	  continue; /* expired */
63	}
64#endif
65
66      hwaddr[0] = e0;
67      hwaddr[1] = e1;
68      hwaddr[2] = e2;
69      hwaddr[3] = e3;
70      hwaddr[4] = e4;
71      hwaddr[5] = e5;
72
73      addr.s_addr = htonl((a0<<24) + (a1<<16) + (a2<<8) + a3);
74
75      /* decode hex in place */
76      if (strcmp(buff2, "*") == 0)
77	clid_len = 0;
78      else
79	{
80	  int s = (strlen(buff2)/3) + 1;
81	  for (clid_len = 0; clid_len < s; clid_len++)
82	  {
83	    buff2[(clid_len*3)+2] = 0;
84	    buff2[clid_len] = strtol(&buff2[clid_len*3], NULL, 16);
85	  }
86	}
87
88      if (!(lease = lease_allocate(buff2, clid_len, addr)))
89	die ("too many stored leases", NULL);
90
91      lease->expires = expires;
92      memcpy(lease->hwaddr, hwaddr, ETHER_ADDR_LEN);
93
94      if (strcmp(buff, "*") !=  0)
95	  lease_set_hostname(lease, buff, daemon->domain_suffix);
96    }
97
98  dns_dirty = 1;
99  file_dirty = has_old;
100  new_lease = 0;
101
102  daemon->lease_fd = fileno(lease_file);
103}
104
105void lease_update_from_configs(struct dhcp_config *dhcp_configs, char *domain)
106{
107  /* changes to the config may change current leases. */
108
109  struct dhcp_lease *lease;
110  struct dhcp_config *config;
111
112  for (lease = leases; lease; lease = lease->next)
113    if ((config = find_config(dhcp_configs, NULL, lease->clid, lease->clid_len, lease->hwaddr, NULL)) &&
114	(config->flags & CONFIG_NAME))
115      lease_set_hostname(lease, config->hostname, domain);
116}
117
118void lease_update_file(int force, time_t now)
119{
120  struct dhcp_lease *lease;
121  int i = force; /* avoid warning */
122  unsigned long expires;
123
124#ifdef HAVE_BROKEN_RTC
125  if (force || new_lease)
126    {
127      lease_prune(NULL, now);
128#else
129  if (file_dirty)
130    {
131#endif
132      rewind(lease_file);
133      ftruncate(fileno(lease_file), 0);
134
135      for (lease = leases; lease; lease = lease->next)
136	{
137#ifdef HAVE_BROKEN_RTC
138	  if (lease->expires)
139	    expires = (unsigned long) difftime(lease->expires, now);
140	  else
141	    expires = 0;
142#else
143	  expires = now; /* eliminate warning */
144	  expires = (unsigned long)lease->expires;
145#endif
146	  fprintf(lease_file, "%lu %.2x:%.2x:%.2x:%.2x:%.2x:%.2x %s %s ",
147		  expires, lease->hwaddr[0], lease->hwaddr[1],
148		  lease->hwaddr[2], lease->hwaddr[3], lease->hwaddr[4],
149		  lease->hwaddr[5], inet_ntoa(lease->addr),
150		  lease->hostname && strlen(lease->hostname) != 0 ? lease->hostname : "*");
151
152	  if (lease->clid_len)
153	    {
154	      for (i = 0; i < lease->clid_len - 1; i++)
155		fprintf(lease_file, "%.2x:", lease->clid[i]);
156	      fprintf(lease_file, "%.2x\n", lease->clid[i]);
157	    }
158	  else
159	    fprintf(lease_file, "*\n");
160
161	}
162
163      fflush(lease_file);
164      fsync(fileno(lease_file));
165      file_dirty = 0;
166      new_lease = 0;
167    }
168}
169
170void lease_update_dns(void)
171{
172  struct dhcp_lease *lease;
173
174  if (dns_dirty)
175    {
176      cache_unhash_dhcp();
177
178      for (lease = leases; lease; lease = lease->next)
179	{
180	  cache_add_dhcp_entry(lease->fqdn, &lease->addr, lease->expires);
181	  cache_add_dhcp_entry(lease->hostname, &lease->addr, lease->expires);
182	}
183
184      dns_dirty = 0;
185    }
186}
187
188void lease_prune(struct dhcp_lease *target, time_t now)
189{
190  struct dhcp_lease *lease, *tmp, **up;
191
192  for (lease = leases, up = &leases; lease; lease = tmp)
193    {
194      tmp = lease->next;
195      if ((lease->expires != 0 && difftime(now, lease->expires) > 0) || lease == target)
196	{
197	  file_dirty = 1;
198
199	  *up = lease->next; /* unlink */
200	  if (lease->hostname)
201	    {
202	      free(lease->hostname);
203	      dns_dirty = 1;
204	    }
205	  if (lease->fqdn)
206	    free(lease->fqdn);
207	  if (lease->clid)
208	    free(lease->clid);
209	  free(lease);
210	  leases_left++;
211	}
212      else
213	up = &lease->next;
214    }
215}
216
217
218struct dhcp_lease *lease_find_by_client(unsigned char *clid, int clid_len)
219{
220  struct dhcp_lease *lease;
221
222  if (clid_len)
223    {
224      for (lease = leases; lease; lease = lease->next)
225	if (lease->clid && clid_len == lease->clid_len &&
226	  memcmp(clid, lease->clid, clid_len) == 0)
227	return lease;
228    }
229  else
230    {
231      for (lease = leases; lease; lease = lease->next)
232	if (memcmp(clid, lease->hwaddr, ETHER_ADDR_LEN) == 0)
233	  return lease;
234    }
235
236  return NULL;
237}
238
239struct dhcp_lease *lease_find_by_addr(struct in_addr addr)
240{
241  struct dhcp_lease *lease;
242
243  for (lease = leases; lease; lease = lease->next)
244    if (lease->addr.s_addr == addr.s_addr)
245      return lease;
246
247  return NULL;
248}
249
250
251struct dhcp_lease *lease_allocate(unsigned char *clid, int clid_len, struct in_addr addr)
252{
253  struct dhcp_lease *lease;
254  if (!leases_left || !(lease = malloc(sizeof(struct dhcp_lease))))
255    return NULL;
256
257  lease->clid = NULL;
258  lease->clid_len = clid_len;
259
260  if (clid_len)
261    {
262      if (!(lease->clid = malloc(clid_len)))
263       {
264	 free(lease);
265	 return NULL;
266       }
267      memcpy(lease->clid, clid, clid_len);
268    }
269
270  lease->hostname = lease->fqdn = NULL;
271  lease->addr = addr;
272  memset(lease->hwaddr, 0, ETHER_ADDR_LEN);
273  lease->expires = 1;
274
275  lease->next = leases;
276  leases = lease;
277
278  file_dirty = 1;
279  new_lease = 1;
280  leases_left--;
281
282  return lease;
283}
284
285void lease_set_expires(struct dhcp_lease *lease, time_t exp)
286{
287  if (exp != lease->expires)
288    file_dirty = dns_dirty = 1;
289
290  lease->expires = exp;
291}
292
293void lease_set_hwaddr(struct dhcp_lease *lease, unsigned char *hwaddr)
294{
295  if (memcmp(lease->hwaddr, hwaddr, ETHER_ADDR_LEN) != 0)
296    {
297      file_dirty = 1;
298      memcpy(lease->hwaddr, hwaddr, ETHER_ADDR_LEN);
299    }
300}
301
302void lease_set_hostname(struct dhcp_lease *lease, char *name, char *suffix)
303{
304  struct dhcp_lease *lease_tmp;
305  char *new_name = NULL, *new_fqdn = NULL;
306
307  if (lease->hostname && name && hostname_isequal(lease->hostname, name))
308    return;
309
310  if (!name && !lease->hostname)
311    return;
312
313  /* If a machine turns up on a new net without dropping the old lease,
314     or two machines claim the same name, then we end up with two interfaces with
315     the same name. Check for that here and remove the name from the old lease. */
316
317  if (name)
318    {
319      for (lease_tmp = leases; lease_tmp; lease_tmp = lease_tmp->next)
320	if (lease_tmp->hostname && hostname_isequal(lease_tmp->hostname, name))
321	  {
322	    new_name = lease_tmp->hostname;
323	    lease_tmp->hostname = NULL;
324	    if (lease_tmp->fqdn)
325	      {
326		new_fqdn = lease_tmp->fqdn;
327		lease_tmp->fqdn = NULL;
328	      }
329	  }
330
331      if (!new_name && (new_name = malloc(strlen(name) + 1)))
332	strcpy(new_name, name);
333
334      if (suffix && !new_fqdn && (new_fqdn = malloc(strlen(name) + strlen(suffix) + 2)))
335	{
336	  strcpy(new_fqdn, name);
337	  strcat(new_fqdn, ".");
338	  strcat(new_fqdn, suffix);
339	}
340    }
341
342  if (lease->hostname)
343    free(lease->hostname);
344  if (lease->fqdn)
345    free(lease->fqdn);
346
347  lease->hostname = new_name;
348  lease->fqdn = new_fqdn;
349
350  file_dirty = dns_dirty = 1;
351}
352
353
354
355