xref: /dragonfly/usr.sbin/dntpd/main.c (revision 0ac6bf9d)
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.9 2005/04/27 00:42:13 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 const char *config_opt;		/* config file */
60 const char *pid_opt = "/var/run/dntpd.pid";
61 
62 int
63 main(int ac, char **av)
64 {
65     int test_opt = 0;
66     pid_t pid;
67     int rc;
68     int ch;
69     int i;
70 
71     /*
72      * Really randomize
73      */
74     srandomdev();
75     rc = 0;
76 
77     /*
78      * Process Options
79      */
80     while ((ch = getopt(ac, av, "df:l:np:qstFL:QST:")) != -1) {
81 	switch(ch) {
82 	case 'd':
83 	    debug_opt = 1;
84 	    daemon_opt = 0;
85 	    if (debug_level < 0)
86 		debug_level = 99;
87 	    if (config_opt == NULL)
88 		config_opt = "/dev/null";
89 	    break;
90 	case 'p':
91 	    pid_opt = optarg;
92 	    break;
93 	case 'f':
94 	    config_opt = optarg;
95 	    break;
96 	case 'l':
97 	    debug_level = strtol(optarg, NULL, 0);
98 	    break;
99 	case 'n':
100 	    no_update_opt = 1;
101 	    break;
102 	case 'q':
103 	    debug_level = 0;
104 	    break;
105 	case 's':
106 	    quickset_opt = 1;
107 	    break;
108 	case 'S':
109 	    quickset_opt = 0;
110 	    break;
111 	case 't':
112 	    test_opt = 1;
113 	    debug_opt = 1;
114 	    daemon_opt = 0;
115 	    if (debug_level < 0)
116 		debug_level = 99;
117 	    if (config_opt == NULL)
118 		config_opt = "/dev/null";
119 	    break;
120 	case 'F':
121 	    daemon_opt = 0;
122 	    break;
123 	case 'L':
124 	    max_sleep_opt = strtol(optarg, NULL, 0);
125 	    break;
126 	case 'T':
127 	    nom_sleep_opt = strtol(optarg, NULL, 0);
128 	    if (nom_sleep_opt < 1) {
129 		fprintf(stderr, "Warning: nominal poll interval too small, "
130 				"limiting to 1 second\n");
131 		nom_sleep_opt = 1;
132 	    }
133 	    if (nom_sleep_opt > 24 * 60 * 60) {
134 		fprintf(stderr, "Warning: nominal poll interval too large, "
135 				"limiting to 24 hours\n");
136 		nom_sleep_opt = 24 * 60 * 60;
137 	    }
138 	    if (min_sleep_opt > nom_sleep_opt)
139 		min_sleep_opt = nom_sleep_opt;
140 	    if (max_sleep_opt < nom_sleep_opt * 5)
141 		max_sleep_opt = nom_sleep_opt * 5;
142 	    break;
143 	case 'Q':
144 	    if ((pid = check_pid()) != 0) {
145 		fprintf(stderr, "%s: killing old daemon\n", av[0]);
146 		kill(pid, SIGINT);
147 		usleep(100000);
148 		if (check_pid())
149 		    sleep(1);
150 		if (check_pid())
151 		    sleep(9);
152 		if (check_pid()) {
153 		    fprintf(stderr, "%s: Unable to kill running daemon.\n", av[0]);
154 		} else {
155 		    fprintf(stderr, "%s: Running daemon has been terminated.\n", av[0]);
156 		}
157 	    } else {
158 		fprintf(stderr, "%s: There is no daemon running to kill.\n", av[0]);
159 	    }
160 	    exit(0);
161 	    break;
162 	case 'h':
163 	default:
164 	    usage(av[0]);
165 	    /* not reached */
166 	}
167     }
168     if (config_opt == NULL) {
169 	if (optind != ac)
170 	    config_opt = "/dev/null";
171 	else
172 	    config_opt = "/etc/dntpd.conf";
173     }
174 
175     if (debug_level < 0)
176 	debug_level = 1;
177 
178     process_config_file(config_opt);
179 
180     if (debug_opt == 0)
181 	openlog("dntpd", LOG_CONS|LOG_PID, LOG_DAEMON);
182 
183     if (test_opt) {
184 	if (optind != ac - 1)
185 	    usage(av[0]);
186 	dotest(av[optind]);
187 	/* not reached */
188     }
189 
190     /*
191      * Add additional hosts.
192      */
193     for (i = optind; i < ac; ++i) {
194 	add_server(av[i]);
195     }
196     if (nservers == 0) {
197 	usage(av[0]);
198 	/* not reached */
199     }
200 
201     /*
202      * Do an initial course time setting if requested using the first
203      * host successfully polled.
204      */
205     /* XXX */
206 
207     /*
208      * Daemonize, stop logging to stderr.
209      */
210     if (daemon_opt) {
211 	if ((pid = check_pid()) != 0) {
212 	    logerrstr("%s: NOTE: killing old daemon and starting a new one",
213 			av[0]);
214 	    kill(pid, SIGINT);
215 	    usleep(100000);
216 	    if (check_pid())
217 		sleep(1);
218 	    if (check_pid())
219 		sleep(9);
220 	    if (check_pid()) {
221 		logerrstr("%s: Unable to kill running daemon, exiting", av[0]);
222 		exit(1);
223 	    }
224 	}
225 	daemon(0, 0);
226     } else if (check_pid() != 0) {
227 	logerrstr("%s: A background dntpd is running, you must kill it first",
228 		av[0]);
229 	exit(1);
230     }
231     if (debug_opt == 0) {
232 	log_stderr = 0;
233 	set_pid(av[0]);
234 	signal(SIGINT, sigint_handler);
235 	logdebug(0, "dntpd version %s started\n", DNTPD_VERSION);
236     }
237 
238     /*
239      * And go.
240      */
241     sysntp_clear_alternative_corrections();
242     client_init();
243     rc = client_main(servers, nservers);
244     return(rc);
245 }
246 
247 static
248 void
249 usage(const char *av0)
250 {
251     fprintf(stderr, "%s [-dnqstFSQ] [-f config_file] [-l log_level] [-T poll_interval] [-L poll_limit] [additional_targets]\n", av0);
252     fprintf(stderr,
253 	"\t-d\tDebugging mode, implies -F, -l 99, and logs to stderr\n"
254 	"\t-f file\tSpecify the config file (/etc/dntpd.conf)\n"
255 	"\t-l int\tSet log level (0-4), default 1\n"
256 	"\t-n\tNo-update mode.  No offset or frequency corrections are made\n"
257 	"\t-q\tQuiet-mode, same as -L 0\n"
258 	"\t-s\tSet the time immediately on startup\n"
259 	"\t-t\tTest mode, implies -F, -l 99, -n, logs to stderr\n"
260 	"\t-F\tRun in foreground (log still goes to syslog)\n"
261 	"\t-L int\tMaximum polling interval\n"
262 	"\t-S\tDo not set the time immediately on startup\n"
263 	"\t-T int\tNominal polling interval\n"
264 	"\t-Q\tTerminate any running background daemon\n"
265 	"\n"
266 	"\t\tNOTE: in debug and test modes -f must be specified if\n"
267 	"\t\tyou want to use a config file.\n"
268     );
269     exit(1);
270 }
271 
272 static
273 void
274 dotest(const char *target)
275 {
276     struct server_info info;
277 
278     bzero(&info, sizeof(info));
279     info.fd = udp_socket(target, 123);
280     if (info.fd < 0) {
281 	logerrstr("unable to create UDP socket for %s", target);
282 	return;
283     }
284     info.target = strdup(target);
285     client_init();
286 
287     fprintf(stderr,
288 	    "Will run %d-second polls until interrupted.\n", nom_sleep_opt);
289 
290     for (;;) {
291 	client_poll(&info, nom_sleep_opt, 1);
292 	sleep(nom_sleep_opt);
293     }
294     /* not reached */
295 }
296 
297 static void
298 add_server(const char *target)
299 {
300     server_info_t info;
301 
302     if (nservers == maxservers) {
303 	maxservers += 16;
304 	servers = realloc(servers, maxservers * sizeof(server_info_t));
305 	assert(servers != NULL);
306     }
307     info = malloc(sizeof(struct server_info));
308     servers[nservers] = info;
309     bzero(info, sizeof(struct server_info));
310     info->fd = udp_socket(target, 123);
311     if (info->fd < 0) {
312 	logerrstr("Unable to add server %s", target);
313     } else {
314 	info->target = strdup(target);
315 	++nservers;
316     }
317 }
318 
319 static void
320 process_config_file(const char *path)
321 {
322     const char *ws = " \t\r\n";
323     char buf[1024];
324     char *keyword;
325     char *data;
326     int line;
327     FILE *fi;
328 
329     if ((fi = fopen(path, "r")) != NULL) {
330 	line = 1;
331 	while (fgets(buf, sizeof(buf), fi) != NULL) {
332 	    if (strchr(buf, '#'))
333 		*strchr(buf, '#') = 0;
334 	    if ((keyword = strtok(buf, ws)) != NULL) {
335 		data = strtok(NULL, ws);
336 		if (strcmp(keyword, "server") == 0) {
337 		    if (data == NULL) {
338 			logerr("%s:%d server missing host specification",
339 				path, line);
340 		    } else {
341 			add_server(data);
342 		    }
343 		} else {
344 		    logerr("%s:%d unknown keyword %s", path, line, keyword);
345 		}
346 	    }
347 	    ++line;
348 	}
349 	fclose(fi);
350     } else {
351 	logerr("Unable to open %s", path);
352 	exit(1);
353     }
354 }
355 
356 static
357 pid_t
358 check_pid(void)
359 {
360     char buf[32];
361     pid_t pid;
362     FILE *fi;
363 
364     pid = 0;
365     if ((fi = fopen(pid_opt, "r")) != NULL) {
366 	if (fgets(buf, sizeof(buf), fi) != NULL) {
367 	    pid = strtol(buf, NULL, 0);
368 	    if (kill(pid, 0) != 0)
369 		pid = 0;
370 	}
371 	fclose(fi);
372     }
373     return(pid);
374 }
375 
376 static
377 void
378 set_pid(const char *av0)
379 {
380     pid_t pid;
381     FILE *fo;
382 
383     pid = getpid();
384     if ((fo = fopen(pid_opt, "w")) != NULL) {
385 	fprintf(fo, "%d\n", (int)pid);
386 	fclose(fo);
387     } else {
388 	logerr("%s: Unable to create %s, continuing anyway.", av0, pid_opt);
389     }
390 }
391 
392 static
393 void
394 sigint_handler(int signo __unused)
395 {
396     remove(pid_opt);
397     /* dangerous, but we are exiting anyway so pray... */
398     logdebug(0, "dntpd version %s stopped\n", DNTPD_VERSION);
399     exit(0);
400 }
401 
402