xref: /dragonfly/usr.sbin/dntpd/main.c (revision 8e1c6f81)
1 /*
2  * Copyright (c) 2005 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  *
34  * $DragonFly: src/usr.sbin/dntpd/main.c,v 1.11 2007/06/26 02:40:20 dillon Exp $
35  */
36 
37 #include "defs.h"
38 
39 static void usage(const char *av0);
40 static void dotest(const char *target);
41 static void add_server(const char *target);
42 static void process_config_file(const char *path);
43 static pid_t check_pid(void);
44 static void set_pid(const char *av0);
45 static void sigint_handler(int signo __unused);
46 
47 static struct server_info **servers;
48 static int nservers;
49 static int maxservers;
50 
51 int daemon_opt = 1;
52 int debug_opt = 0;
53 int debug_level = -1;		/* (set to default later) */
54 int quickset_opt = 0;		/* immediately set time of day on startup */
55 int no_update_opt = 0;		/* do not make any actual updates */
56 int min_sleep_opt = 5;		/* 5 seconds minimum poll interval */
57 int nom_sleep_opt = 300;	/* 5 minutes nominal poll interval */
58 int max_sleep_opt = 1800;	/* 30 minutes maximum poll interval */
59 double insane_deviation = 0.5;	/* 0.5 seconds of deviation == insane */
60 const char *config_opt;		/* config file */
61 const char *pid_opt = "/var/run/dntpd.pid";
62 
63 int
64 main(int ac, char **av)
65 {
66     int test_opt = 0;
67     pid_t pid;
68     int rc;
69     int ch;
70     int i;
71 
72     /*
73      * Really randomize
74      */
75     srandomdev();
76     rc = 0;
77 
78     /*
79      * Process Options
80      */
81     while ((ch = getopt(ac, av, "df:i:l:np:qstFL:QST:")) != -1) {
82 	switch(ch) {
83 	case 'd':
84 	    debug_opt = 1;
85 	    daemon_opt = 0;
86 	    if (debug_level < 0)
87 		debug_level = 99;
88 	    if (config_opt == NULL)
89 		config_opt = "/dev/null";
90 	    break;
91 	case 'p':
92 	    pid_opt = optarg;
93 	    break;
94 	case 'f':
95 	    config_opt = optarg;
96 	    break;
97 	case 'i':
98 	    insane_deviation = strtod(optarg, NULL);
99 	    break;
100 	case 'l':
101 	    debug_level = strtol(optarg, NULL, 0);
102 	    break;
103 	case 'n':
104 	    no_update_opt = 1;
105 	    break;
106 	case 'q':
107 	    debug_level = 0;
108 	    break;
109 	case 's':
110 	    quickset_opt = 1;
111 	    break;
112 	case 'S':
113 	    quickset_opt = 0;
114 	    break;
115 	case 't':
116 	    test_opt = 1;
117 	    debug_opt = 1;
118 	    daemon_opt = 0;
119 	    if (debug_level < 0)
120 		debug_level = 99;
121 	    if (config_opt == NULL)
122 		config_opt = "/dev/null";
123 	    break;
124 	case 'F':
125 	    daemon_opt = 0;
126 	    break;
127 	case 'L':
128 	    max_sleep_opt = strtol(optarg, NULL, 0);
129 	    break;
130 	case 'T':
131 	    nom_sleep_opt = strtol(optarg, NULL, 0);
132 	    if (nom_sleep_opt < 1) {
133 		fprintf(stderr, "Warning: nominal poll interval too small, "
134 				"limiting to 1 second\n");
135 		nom_sleep_opt = 1;
136 	    }
137 	    if (nom_sleep_opt > 24 * 60 * 60) {
138 		fprintf(stderr, "Warning: nominal poll interval too large, "
139 				"limiting to 24 hours\n");
140 		nom_sleep_opt = 24 * 60 * 60;
141 	    }
142 	    if (min_sleep_opt > nom_sleep_opt)
143 		min_sleep_opt = nom_sleep_opt;
144 	    if (max_sleep_opt < nom_sleep_opt * 5)
145 		max_sleep_opt = nom_sleep_opt * 5;
146 	    break;
147 	case 'Q':
148 	    if ((pid = check_pid()) != 0) {
149 		fprintf(stderr, "%s: killing old daemon\n", av[0]);
150 		kill(pid, SIGINT);
151 		usleep(100000);
152 		if (check_pid())
153 		    sleep(1);
154 		if (check_pid())
155 		    sleep(9);
156 		if (check_pid()) {
157 		    fprintf(stderr, "%s: Unable to kill running daemon.\n", av[0]);
158 		} else {
159 		    fprintf(stderr, "%s: Running daemon has been terminated.\n", av[0]);
160 		}
161 	    } else {
162 		fprintf(stderr, "%s: There is no daemon running to kill.\n", av[0]);
163 	    }
164 	    exit(0);
165 	    break;
166 	case 'h':
167 	default:
168 	    usage(av[0]);
169 	    /* not reached */
170 	}
171     }
172 
173     /*
174      * Make sure min and nom intervals are less then or equal to the maximum
175      * interval.
176      */
177     if (min_sleep_opt > max_sleep_opt)
178 	min_sleep_opt = max_sleep_opt;
179     if (nom_sleep_opt > max_sleep_opt)
180 	nom_sleep_opt = max_sleep_opt;
181 
182     /*
183      * Set default config file
184      */
185     if (config_opt == NULL) {
186 	if (optind != ac)
187 	    config_opt = "/dev/null";
188 	else
189 	    config_opt = "/etc/dntpd.conf";
190     }
191 
192     if (debug_level < 0)
193 	debug_level = 1;
194 
195     process_config_file(config_opt);
196 
197     if (debug_opt == 0)
198 	openlog("dntpd", LOG_CONS|LOG_PID, LOG_DAEMON);
199 
200     if (test_opt) {
201 	if (optind != ac - 1)
202 	    usage(av[0]);
203 	dotest(av[optind]);
204 	/* not reached */
205     }
206 
207     /*
208      * Add additional hosts.
209      */
210     for (i = optind; i < ac; ++i) {
211 	add_server(av[i]);
212     }
213     if (nservers == 0) {
214 	usage(av[0]);
215 	/* not reached */
216     }
217 
218     /*
219      * Do an initial course time setting if requested using the first
220      * host successfully polled.
221      */
222     /* XXX */
223 
224     /*
225      * Daemonize, stop logging to stderr.
226      */
227     if (daemon_opt) {
228 	if ((pid = check_pid()) != 0) {
229 	    logerrstr("%s: NOTE: killing old daemon and starting a new one",
230 			av[0]);
231 	    kill(pid, SIGINT);
232 	    usleep(100000);
233 	    if (check_pid())
234 		sleep(1);
235 	    if (check_pid())
236 		sleep(9);
237 	    if (check_pid()) {
238 		logerrstr("%s: Unable to kill running daemon, exiting", av[0]);
239 		exit(1);
240 	    }
241 	}
242 	daemon(0, 0);
243     } else if (check_pid() != 0) {
244 	logerrstr("%s: A background dntpd is running, you must kill it first",
245 		av[0]);
246 	exit(1);
247     }
248     if (debug_opt == 0) {
249 	log_stderr = 0;
250 	set_pid(av[0]);
251 	signal(SIGINT, sigint_handler);
252 	logdebug(0, "dntpd version %s started\n", DNTPD_VERSION);
253     }
254 
255     /*
256      * And go.
257      */
258     sysntp_clear_alternative_corrections();
259     client_init();
260     client_check_duplicate_ips(servers, nservers);
261     rc = client_main(servers, nservers);
262     return(rc);
263 }
264 
265 static
266 void
267 usage(const char *av0)
268 {
269     fprintf(stderr, "%s [-dnqstFSQ] [-f config_file] [-l log_level] [-T poll_interval] [-L poll_limit] [additional_targets]\n", av0);
270     fprintf(stderr,
271 	"\t-d\tDebugging mode, implies -F, -l 99, and logs to stderr\n"
272 	"\t-f file\tSpecify the config file (/etc/dntpd.conf)\n"
273 	"\t-l int\tSet log level (0-4), default 1\n"
274 	"\t-n\tNo-update mode.  No offset or frequency corrections are made\n"
275 	"\t-q\tQuiet-mode, same as -L 0\n"
276 	"\t-s\tSet the time immediately on startup\n"
277 	"\t-t\tTest mode, implies -F, -l 99, -n, logs to stderr\n"
278 	"\t-F\tRun in foreground (log still goes to syslog)\n"
279 	"\t-L int\tMaximum polling interval\n"
280 	"\t-S\tDo not set the time immediately on startup\n"
281 	"\t-T int\tNominal polling interval\n"
282 	"\t-Q\tTerminate any running background daemon\n"
283 	"\n"
284 	"\t\tNOTE: in debug and test modes -f must be specified if\n"
285 	"\t\tyou want to use a config file.\n"
286     );
287     exit(1);
288 }
289 
290 static
291 void
292 dotest(const char *target)
293 {
294     struct server_info info;
295 
296     bzero(&info, sizeof(info));
297     info.fd = udp_socket(target, 123, &info.sam);
298     if (info.fd < 0) {
299 	logerrstr("unable to create UDP socket for %s", target);
300 	return;
301     }
302     info.target = strdup(target);
303     client_init();
304 
305     fprintf(stderr,
306 	    "Will run %d-second polls until interrupted.\n", nom_sleep_opt);
307 
308     for (;;) {
309 	client_poll(&info, nom_sleep_opt, 1);
310 	sleep(nom_sleep_opt);
311     }
312     /* not reached */
313 }
314 
315 static void
316 add_server(const char *target)
317 {
318     server_info_t info;
319     const char *ipstr;
320 
321     if (nservers == maxservers) {
322 	maxservers += 16;
323 	servers = realloc(servers, maxservers * sizeof(server_info_t));
324 	assert(servers != NULL);
325     }
326     info = malloc(sizeof(struct server_info));
327     servers[nservers] = info;
328     bzero(info, sizeof(struct server_info));
329     info->fd = udp_socket(target, 123, &info->sam);
330     info->target = strdup(target);
331     if (info->fd >= 0) {
332 	ipstr = addr2ascii(AF_INET, &info->sam.sin_addr,
333 			   sizeof(info->sam.sin_addr), NULL);
334 	info->ipstr = strdup(ipstr);
335     } else {
336 	client_setserverstate(info, -1, "DNS or IP lookup failure");
337     }
338     ++nservers;
339 }
340 
341 void
342 disconnect_server(server_info_t info)
343 {
344     if (info->fd >= 0)
345 	close(info->fd);
346     info->fd = -1;
347     if (info->ipstr) {
348 	free(info->ipstr);
349 	info->ipstr = NULL;
350     }
351 }
352 
353 void
354 reconnect_server(server_info_t info)
355 {
356     const char *ipstr;
357 
358     if (info->fd >= 0)
359 	close(info->fd);
360     if (info->ipstr) {
361 	free(info->ipstr);
362 	info->ipstr = NULL;
363     }
364     info->fd = udp_socket(info->target, 123, &info->sam);
365     if (info->fd >= 0) {
366 	ipstr = addr2ascii(AF_INET, &info->sam.sin_addr,
367 			   sizeof(info->sam.sin_addr), NULL);
368 	info->ipstr = strdup(ipstr);
369     }
370 }
371 
372 static void
373 process_config_file(const char *path)
374 {
375     const char *ws = " \t\r\n";
376     char buf[1024];
377     char *keyword;
378     char *data;
379     int line;
380     FILE *fi;
381 
382     if ((fi = fopen(path, "r")) != NULL) {
383 	line = 1;
384 	while (fgets(buf, sizeof(buf), fi) != NULL) {
385 	    if (strchr(buf, '#'))
386 		*strchr(buf, '#') = 0;
387 	    if ((keyword = strtok(buf, ws)) != NULL) {
388 		data = strtok(NULL, ws);
389 		if (strcmp(keyword, "server") == 0) {
390 		    if (data == NULL) {
391 			logerr("%s:%d server missing host specification",
392 				path, line);
393 		    } else {
394 			add_server(data);
395 		    }
396 		} else {
397 		    logerr("%s:%d unknown keyword %s", path, line, keyword);
398 		}
399 	    }
400 	    ++line;
401 	}
402 	fclose(fi);
403     } else {
404 	logerr("Unable to open %s", path);
405 	exit(1);
406     }
407 }
408 
409 static
410 pid_t
411 check_pid(void)
412 {
413     char buf[32];
414     pid_t pid;
415     FILE *fi;
416 
417     pid = 0;
418     if ((fi = fopen(pid_opt, "r")) != NULL) {
419 	if (fgets(buf, sizeof(buf), fi) != NULL) {
420 	    pid = strtol(buf, NULL, 0);
421 	    if (kill(pid, 0) != 0)
422 		pid = 0;
423 	}
424 	fclose(fi);
425     }
426     return(pid);
427 }
428 
429 static
430 void
431 set_pid(const char *av0)
432 {
433     pid_t pid;
434     FILE *fo;
435 
436     pid = getpid();
437     if ((fo = fopen(pid_opt, "w")) != NULL) {
438 	fprintf(fo, "%d\n", (int)pid);
439 	fclose(fo);
440     } else {
441 	logerr("%s: Unable to create %s, continuing anyway.", av0, pid_opt);
442     }
443 }
444 
445 static
446 void
447 sigint_handler(int signo __unused)
448 {
449     remove(pid_opt);
450     /* dangerous, but we are exiting anyway so pray... */
451     logdebug(0, "dntpd version %s stopped\n", DNTPD_VERSION);
452     exit(0);
453 }
454 
455