1
|
/*-
|
2
|
* Copyright (c) 2010 Ermal Lu?i <eri@pfsense.org>
|
3
|
* All rights reserved.
|
4
|
*
|
5
|
* Redistribution and use in source and binary forms, with or without
|
6
|
* modification, are permitted provided that the following conditions
|
7
|
* are met:
|
8
|
* 1. Redistributions of source code must retain the above copyright
|
9
|
* notice, this list of conditions and the following disclaimer.
|
10
|
* 2. Redistributions in binary form must reproduce the above copyright
|
11
|
* notice, this list of conditions and the following disclaimer in the
|
12
|
* documentation and/or other materials provided with the distribution.
|
13
|
*
|
14
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
15
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
16
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
17
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
18
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
19
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
20
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
21
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
22
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
23
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
24
|
* SUCH DAMAGE.
|
25
|
*/
|
26
|
|
27
|
|
28
|
/*
|
29
|
* The parsing code is taken from dnsmasq isc.c file and modified to work
|
30
|
* in this code.
|
31
|
*/
|
32
|
/* dnsmasq is Copyright (c) 2000-2007 Simon Kelley
|
33
|
|
34
|
This program is free software; you can redistribute it and/or modify
|
35
|
it under the terms of the GNU General Public License as published by
|
36
|
the Free Software Foundation; version 2 dated June, 1991, or
|
37
|
(at your option) version 3 dated 29 June, 2007.
|
38
|
|
39
|
This program is distributed in the hope that it will be useful,
|
40
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
41
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
42
|
GNU General Public License for more details.
|
43
|
|
44
|
You should have received a copy of the GNU General Public License
|
45
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
46
|
*/
|
47
|
/* Code in this file is based on contributions by John Volpe. */
|
48
|
|
49
|
#include <sys/types.h>
|
50
|
#include <sys/socket.h>
|
51
|
#include <sys/event.h>
|
52
|
#include <sys/time.h>
|
53
|
#include <sys/stat.h>
|
54
|
#include <sys/queue.h>
|
55
|
|
56
|
#include <netinet/in.h>
|
57
|
#include <arpa/nameser.h>
|
58
|
#include <arpa/inet.h>
|
59
|
|
60
|
#include <syslog.h>
|
61
|
#include <stdarg.h>
|
62
|
#include <time.h>
|
63
|
#include <signal.h>
|
64
|
|
65
|
#define _WITH_DPRINTF
|
66
|
#include <stdio.h>
|
67
|
#include <stdlib.h>
|
68
|
#include <string.h>
|
69
|
#include <fcntl.h>
|
70
|
#include <unistd.h>
|
71
|
|
72
|
#include <errno.h>
|
73
|
|
74
|
#define MAXTOK 64
|
75
|
#define PIDFILE "/var/run/dhcpleases6.pid"
|
76
|
|
77
|
struct isc_lease {
|
78
|
char *name, *fqdn;
|
79
|
time_t expires;
|
80
|
struct in_addr addr;
|
81
|
LIST_ENTRY(isc_lease) next;
|
82
|
};
|
83
|
|
84
|
LIST_HEAD(isc_leases, isc_lease) leases =
|
85
|
LIST_HEAD_INITIALIZER(leases);
|
86
|
static char *leasefile = NULL;
|
87
|
static char *HOSTS = NULL;
|
88
|
static FILE *fp = NULL;
|
89
|
static char *domain_suffix = NULL;
|
90
|
static char *command = NULL;
|
91
|
static size_t hostssize = 0;
|
92
|
|
93
|
/* Check if file exists */
|
94
|
static int
|
95
|
fexist(char * filename)
|
96
|
{
|
97
|
struct stat buf;
|
98
|
|
99
|
if (( stat (filename, &buf)) < 0)
|
100
|
return (0);
|
101
|
|
102
|
if (! S_ISREG(buf.st_mode))
|
103
|
return (0);
|
104
|
|
105
|
return(1);
|
106
|
}
|
107
|
|
108
|
static int
|
109
|
fsize(char * filename)
|
110
|
{
|
111
|
struct stat buf;
|
112
|
|
113
|
if (( stat (filename, &buf)) < 0)
|
114
|
return (-1);
|
115
|
|
116
|
if (! S_ISREG(buf.st_mode))
|
117
|
return (-1);
|
118
|
|
119
|
return(buf.st_size);
|
120
|
}
|
121
|
|
122
|
/*
|
123
|
* check for legal char a-z A-Z 0-9 -
|
124
|
* (also / , used for RFC2317 and _ used in windows queries
|
125
|
* and space, for DNS-SD stuff)
|
126
|
*/
|
127
|
static int
|
128
|
legal_char(char c) {
|
129
|
if ((c >= 'A' && c <= 'Z') ||
|
130
|
(c >= 'a' && c <= 'z') ||
|
131
|
(c >= '0' && c <= '9') ||
|
132
|
c == '-' || c == '/' || c == '_' || c == ' ')
|
133
|
return (1);
|
134
|
return (0);
|
135
|
}
|
136
|
|
137
|
/*
|
138
|
* check for legal chars and remove trailing .
|
139
|
* also fail empty string and label > 63 chars
|
140
|
*/
|
141
|
static int
|
142
|
canonicalise(char *s) {
|
143
|
size_t dotgap = 0, l = strlen(s);
|
144
|
char c;
|
145
|
int nowhite = 0;
|
146
|
|
147
|
if (l == 0 || l > MAXDNAME)
|
148
|
return (0);
|
149
|
|
150
|
if (s[l-1] == '.') {
|
151
|
if (l == 1)
|
152
|
return (0);
|
153
|
s[l-1] = 0;
|
154
|
}
|
155
|
|
156
|
while ((c = *s)) {
|
157
|
if (c == '.')
|
158
|
dotgap = 0;
|
159
|
else if (!legal_char(c) || (++dotgap > MAXLABEL))
|
160
|
return (0);
|
161
|
else if (c != ' ')
|
162
|
nowhite = 1;
|
163
|
s++;
|
164
|
}
|
165
|
|
166
|
return (nowhite);
|
167
|
}
|
168
|
|
169
|
/* don't use strcasecmp and friends here - they may be messed up by LOCALE */
|
170
|
static int
|
171
|
hostname_isequal(char *a, char *b) {
|
172
|
unsigned int c1, c2;
|
173
|
|
174
|
do {
|
175
|
c1 = (unsigned char) *a++;
|
176
|
c2 = (unsigned char) *b++;
|
177
|
|
178
|
if (c1 >= 'A' && c1 <= 'Z')
|
179
|
c1 += 'a' - 'A';
|
180
|
if (c2 >= 'A' && c2 <= 'Z')
|
181
|
c2 += 'a' - 'A';
|
182
|
|
183
|
if (c1 != c2)
|
184
|
return 0;
|
185
|
} while (c1);
|
186
|
|
187
|
return (1);
|
188
|
}
|
189
|
|
190
|
static int
|
191
|
next_token (char *token, int buffsize, FILE * fp)
|
192
|
{
|
193
|
int c, count = 0;
|
194
|
char *cp = token;
|
195
|
|
196
|
while((c = getc(fp)) != EOF) {
|
197
|
if (c == '#')
|
198
|
do {
|
199
|
c = getc(fp);
|
200
|
} while (c != '\n' && c != EOF);
|
201
|
|
202
|
if (c == ' ' || c == '\t' || c == '\n' || c == ';') {
|
203
|
if (count)
|
204
|
break;
|
205
|
} else if ((c != '"') && (count<buffsize-1)) {
|
206
|
*cp++ = c;
|
207
|
count++;
|
208
|
}
|
209
|
}
|
210
|
|
211
|
*cp = 0;
|
212
|
return count ? 1 : 0;
|
213
|
}
|
214
|
|
215
|
/*
|
216
|
* There doesn't seem to be a universally available library function
|
217
|
* which converts broken-down _GMT_ time to seconds-in-epoch.
|
218
|
* The following was borrowed from ISC dhcpd sources, where
|
219
|
* it is noted that it might not be entirely accurate for odd seconds.
|
220
|
* Since we're trying to get the same answer as dhcpd, that's just
|
221
|
* fine here.
|
222
|
*/
|
223
|
static time_t
|
224
|
convert_time(struct tm lease_time) {
|
225
|
static const int months [11] = { 31, 59, 90, 120, 151, 181,
|
226
|
212, 243, 273, 304, 334 };
|
227
|
time_t time = ((((((365 * (lease_time.tm_year - 1970) + /* Days in years since '70 */
|
228
|
(lease_time.tm_year - 1969) / 4 + /* Leap days since '70 */
|
229
|
(lease_time.tm_mon > 1 /* Days in months this year */
|
230
|
? months [lease_time.tm_mon - 2]
|
231
|
: 0) +
|
232
|
(lease_time.tm_mon > 2 && /* Leap day this year */
|
233
|
!((lease_time.tm_year - 1972) & 3)) +
|
234
|
lease_time.tm_mday - 1) * 24) + /* Day of month */
|
235
|
lease_time.tm_hour) * 60) +
|
236
|
lease_time.tm_min) * 60) + lease_time.tm_sec;
|
237
|
|
238
|
return (time);
|
239
|
}
|
240
|
|
241
|
static int
|
242
|
load_dhcp(time_t now) {
|
243
|
char namebuff[256];
|
244
|
char *hostname = namebuff;
|
245
|
char token[MAXTOK], *dot;
|
246
|
struct in_addr host_address;
|
247
|
time_t ttd, tts;
|
248
|
struct isc_lease *lease, *tmp;
|
249
|
|
250
|
rewind(fp);
|
251
|
LIST_INIT(&leases);
|
252
|
|
253
|
while ((next_token(token, MAXTOK, fp))) {
|
254
|
if (strcmp(token, "lease") == 0) {
|
255
|
hostname[0] = '\0';
|
256
|
ttd = tts = (time_t)(-1);
|
257
|
if (next_token(token, MAXTOK, fp) &&
|
258
|
(inet_pton(AF_INET, token, &host_address))) {
|
259
|
if (next_token(token, MAXTOK, fp) && *token == '{') {
|
260
|
while (next_token(token, MAXTOK, fp) && *token != '}') {
|
261
|
if ((strcmp(token, "client-hostname") == 0) ||
|
262
|
(strcmp(token, "hostname") == 0)) {
|
263
|
if (next_token(hostname, MAXDNAME, fp))
|
264
|
if (!canonicalise(hostname)) {
|
265
|
*hostname = 0;
|
266
|
syslog(LOG_ERR, "bad name in %s", leasefile);
|
267
|
}
|
268
|
} else if ((strcmp(token, "ends") == 0) ||
|
269
|
(strcmp(token, "starts") == 0)) {
|
270
|
struct tm lease_time;
|
271
|
int is_ends = (strcmp(token, "ends") == 0);
|
272
|
if (next_token(token, MAXTOK, fp) && /* skip weekday */
|
273
|
next_token(token, MAXTOK, fp) && /* Get date from lease file */
|
274
|
sscanf (token, "%d/%d/%d",
|
275
|
&lease_time.tm_year,
|
276
|
&lease_time.tm_mon,
|
277
|
&lease_time.tm_mday) == 3 &&
|
278
|
next_token(token, MAXTOK, fp) &&
|
279
|
sscanf (token, "%d:%d:%d:",
|
280
|
&lease_time.tm_hour,
|
281
|
&lease_time.tm_min,
|
282
|
&lease_time.tm_sec) == 3) {
|
283
|
if (is_ends)
|
284
|
ttd = convert_time(lease_time);
|
285
|
else
|
286
|
tts = convert_time(lease_time);
|
287
|
}
|
288
|
}
|
289
|
}
|
290
|
/* missing info? */
|
291
|
if (!*hostname)
|
292
|
continue;
|
293
|
if (ttd == (time_t)(-1))
|
294
|
continue;
|
295
|
|
296
|
/* We use 0 as infinite in ttd */
|
297
|
if ((tts != -1) && (ttd == tts - 1))
|
298
|
ttd = (time_t)0;
|
299
|
else if (difftime(now, ttd) > 0)
|
300
|
continue;
|
301
|
|
302
|
if ((dot = strchr(hostname, '.'))) {
|
303
|
if (!domain_suffix || hostname_isequal(dot+1, domain_suffix)) {
|
304
|
syslog(LOG_WARNING,
|
305
|
"Ignoring DHCP lease for %s because it has an illegal domain part",
|
306
|
hostname);
|
307
|
continue;
|
308
|
}
|
309
|
*dot = 0;
|
310
|
}
|
311
|
|
312
|
LIST_FOREACH(lease, &leases, next) {
|
313
|
if (hostname_isequal(lease->name, hostname)) {
|
314
|
lease->expires = ttd;
|
315
|
lease->addr = host_address;
|
316
|
break;
|
317
|
}
|
318
|
}
|
319
|
|
320
|
if (!lease) {
|
321
|
if ((lease = malloc(sizeof(struct isc_lease))) == NULL)
|
322
|
continue;
|
323
|
lease->expires = ttd;
|
324
|
lease->addr = host_address;
|
325
|
lease->fqdn = NULL;
|
326
|
LIST_INSERT_HEAD(&leases, lease, next);
|
327
|
} else {
|
328
|
if (lease->name != NULL)
|
329
|
free(lease->name);
|
330
|
if (lease->fqdn != NULL)
|
331
|
free(lease->fqdn);
|
332
|
}
|
333
|
|
334
|
if (!(lease->name = malloc(strlen(hostname)+1)))
|
335
|
free(lease);
|
336
|
strcpy(lease->name, hostname);
|
337
|
if ((lease->fqdn = malloc(strlen(hostname) + strlen(domain_suffix) + 2)) != NULL) {
|
338
|
strcpy(lease->fqdn, hostname);
|
339
|
strcat(lease->fqdn, ".");
|
340
|
strcat(lease->fqdn, domain_suffix);
|
341
|
} else {
|
342
|
LIST_REMOVE(lease, next);
|
343
|
free(lease->name);
|
344
|
free(lease);
|
345
|
}
|
346
|
}
|
347
|
}
|
348
|
}
|
349
|
}
|
350
|
|
351
|
|
352
|
/* prune expired leases */
|
353
|
LIST_FOREACH_SAFE(lease, &leases, next, tmp) {
|
354
|
if (lease->expires != (time_t)0 && difftime(now, lease->expires) > 0) {
|
355
|
if (lease->name)
|
356
|
free(lease->name);
|
357
|
if (lease->fqdn)
|
358
|
free(lease->fqdn);
|
359
|
LIST_REMOVE(lease, next);
|
360
|
free(lease);
|
361
|
}
|
362
|
}
|
363
|
|
364
|
return (0);
|
365
|
}
|
366
|
|
367
|
static int
|
368
|
write_status() {
|
369
|
struct isc_lease *lease;
|
370
|
struct stat tmp;
|
371
|
size_t tmpsize;
|
372
|
int fd;
|
373
|
|
374
|
fd = open(HOSTS, O_RDWR | O_CREAT | O_FSYNC);
|
375
|
if (fd < 0)
|
376
|
return 1;
|
377
|
if (fstat(fd, &tmp) < 0)
|
378
|
tmpsize = hostssize;
|
379
|
else
|
380
|
tmpsize = tmp.st_size;
|
381
|
if (tmpsize < hostssize) {
|
382
|
syslog(LOG_WARNING, "%s changed size from original!", HOSTS);
|
383
|
hostssize = tmpsize;
|
384
|
}
|
385
|
ftruncate(fd, hostssize);
|
386
|
if (lseek(fd, 0, SEEK_END) < 0) {
|
387
|
close(fd);
|
388
|
return 2;
|
389
|
}
|
390
|
/* write the tmp hosts file */
|
391
|
dprintf(fd, "\n# dhcpleases automatically entered\n"); /* put a blank line just to be on safe side */
|
392
|
LIST_FOREACH(lease, &leases, next) {
|
393
|
dprintf(fd, "%s\t%s %s\t\t# dynamic entry from dhcpd.leases\n", inet_ntoa(lease->addr),
|
394
|
lease->fqdn ? lease->fqdn : "empty", lease->name ? lease->name : "empty");
|
395
|
}
|
396
|
close(fd);
|
397
|
|
398
|
return (0);
|
399
|
}
|
400
|
|
401
|
static void
|
402
|
cleanup() {
|
403
|
struct isc_lease *lease, *tmp;
|
404
|
|
405
|
LIST_FOREACH_SAFE(lease, &leases, next, tmp) {
|
406
|
if (lease->fqdn)
|
407
|
free(lease->fqdn);
|
408
|
if (lease->name)
|
409
|
free(lease->name);
|
410
|
LIST_REMOVE(lease, next);
|
411
|
free(lease);
|
412
|
}
|
413
|
|
414
|
return;
|
415
|
}
|
416
|
|
417
|
static void
|
418
|
handle_signal(int sig) {
|
419
|
size_t size;
|
420
|
|
421
|
switch(sig) {
|
422
|
case SIGHUP:
|
423
|
size = fsize(HOSTS);
|
424
|
if (hostssize < 0)
|
425
|
break; /* XXX: exit?! */
|
426
|
else
|
427
|
hostssize = size;
|
428
|
break;
|
429
|
case SIGTERM:
|
430
|
unlink(PIDFILE);
|
431
|
cleanup();
|
432
|
exit(0);
|
433
|
break;
|
434
|
default:
|
435
|
syslog(LOG_WARNING, "unhandled signal");
|
436
|
}
|
437
|
}
|
438
|
|
439
|
int
|
440
|
main(int argc, char **argv) {
|
441
|
struct kevent evlist; /* events we want to monitor */
|
442
|
struct kevent chlist; /* events that were triggered */
|
443
|
struct sigaction sa;
|
444
|
time_t now;
|
445
|
int kq, nev, leasefd = 0, pidf, ch;
|
446
|
|
447
|
if (argc != 5) {
|
448
|
}
|
449
|
|
450
|
while ((ch = getopt(argc, argv, "c:l:")) != -1) {
|
451
|
switch (ch) {
|
452
|
case 'c':
|
453
|
command = optarg;
|
454
|
break;
|
455
|
case 'l':
|
456
|
leasefile = optarg;
|
457
|
break;
|
458
|
default:
|
459
|
perror("Wrong number of arguments given."); /* XXX: usage */
|
460
|
exit(2);
|
461
|
/* NOTREACHED */
|
462
|
}
|
463
|
}
|
464
|
argc -= optind;
|
465
|
argv += optind;
|
466
|
|
467
|
if (leasefile == NULL) {
|
468
|
syslog(LOG_ERR, "lease file is mandatory as parameter");
|
469
|
perror("lease file is mandatory as parameter");
|
470
|
exit(1);
|
471
|
}
|
472
|
if (!fexist(leasefile)) {
|
473
|
syslog(LOG_ERR, "lease file needs to exist before starting dhcpleases");
|
474
|
perror("lease file needs to exist before starting dhcpleases");
|
475
|
exit(1);
|
476
|
}
|
477
|
|
478
|
closefrom(3);
|
479
|
|
480
|
if (daemon(0, 0) < 0) {
|
481
|
syslog(LOG_ERR, "Could not daemonize");
|
482
|
perror("Could not daemonize");
|
483
|
exit(4);
|
484
|
}
|
485
|
|
486
|
reopen:
|
487
|
leasefd = open(leasefile, O_RDONLY);
|
488
|
if (leasefd < 0) {
|
489
|
syslog(LOG_ERR, "Could not get descriptor");
|
490
|
perror("Could not get descriptor");
|
491
|
exit(6);
|
492
|
}
|
493
|
|
494
|
fp = fdopen(leasefd, "r");
|
495
|
if (fp == NULL) {
|
496
|
syslog(LOG_ERR, "could not open leases file");
|
497
|
perror("could not open leases file");
|
498
|
exit(5);
|
499
|
}
|
500
|
|
501
|
pidf = open(PIDFILE, O_RDWR | O_CREAT | O_FSYNC);
|
502
|
if (pidf < 0)
|
503
|
syslog(LOG_ERR, "could not write pid file, %m");
|
504
|
else {
|
505
|
ftruncate(pidf, 0);
|
506
|
dprintf(pidf, "%u\n", getpid());
|
507
|
close(pidf);
|
508
|
}
|
509
|
|
510
|
/*
|
511
|
* Catch SIGHUP in order to reread configuration file.
|
512
|
*/
|
513
|
sa.sa_handler = handle_signal;
|
514
|
sa.sa_flags = SA_SIGINFO|SA_RESTART;
|
515
|
sigemptyset(&sa.sa_mask);
|
516
|
if (sigaction(SIGHUP, &sa, NULL) < 0) {
|
517
|
syslog(LOG_ERR, "unable to set signal handler, %m");
|
518
|
exit(9);
|
519
|
}
|
520
|
if (sigaction(SIGTERM, &sa, NULL) < 0) {
|
521
|
syslog(LOG_ERR, "unable to set signal handler, %m");
|
522
|
exit(10);
|
523
|
}
|
524
|
|
525
|
/* Create a new kernel event queue */
|
526
|
if ((kq = kqueue()) == -1)
|
527
|
exit(1);
|
528
|
|
529
|
now = time(NULL);
|
530
|
if (command == NULL) {
|
531
|
load_dhcp(now);
|
532
|
|
533
|
write_status();
|
534
|
//syslog(LOG_INFO, "written temp hosts file after modification event.");
|
535
|
|
536
|
cleanup();
|
537
|
//syslog(LOG_INFO, "Cleaned up.");
|
538
|
}
|
539
|
|
540
|
/* Initialise kevent structure */
|
541
|
EV_SET(&chlist, leasefd, EVFILT_VNODE, EV_ADD | EV_CLEAR | EV_ENABLE | EV_ONESHOT,
|
542
|
NOTE_WRITE | NOTE_ATTRIB | NOTE_DELETE | NOTE_RENAME | NOTE_LINK, 0, NULL);
|
543
|
/* Loop forever */
|
544
|
for (;;) {
|
545
|
nev = kevent(kq, &chlist, 1, &evlist, 1, NULL);
|
546
|
if (nev == -1)
|
547
|
perror("kevent()");
|
548
|
else if (nev > 0) {
|
549
|
if (evlist.flags & EV_ERROR) {
|
550
|
syslog(LOG_ERR, "EV_ERROR: %s\n", strerror(evlist.data));
|
551
|
break;
|
552
|
}
|
553
|
if ((evlist.fflags & NOTE_DELETE) || (evlist.fflags & NOTE_RENAME)) {
|
554
|
close(leasefd);
|
555
|
goto reopen;
|
556
|
}
|
557
|
now = time(NULL);
|
558
|
if (command != NULL)
|
559
|
system(command);
|
560
|
else {
|
561
|
load_dhcp(now);
|
562
|
|
563
|
write_status();
|
564
|
//syslog(LOG_INFO, "written temp hosts file after modification event.");
|
565
|
|
566
|
cleanup();
|
567
|
//syslog(LOG_INFO, "Cleaned up.");
|
568
|
}
|
569
|
}
|
570
|
}
|
571
|
|
572
|
fclose(fp);
|
573
|
unlink(PIDFILE);
|
574
|
return (0);
|
575
|
}
|