Projet

Général

Profil

Télécharger (14 ko) Statistiques
| Branche: | Révision:

univnautes-tools / pfPorts / dhcpleases6 / files / dhcpleases6.c @ d2628919

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
}
    (1-1/1)