xref: /openbsd/usr.sbin/snmpd/snmpd.c (revision 4bdff4be)
1 /*	$OpenBSD: snmpd.c,v 1.50 2023/12/22 13:04:30 martijn Exp $	*/
2 
3 /*
4  * Copyright (c) 2007, 2008, 2012 Reyk Floeter <reyk@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/wait.h>
21 
22 #include <dirent.h>
23 #include <err.h>
24 #include <errno.h>
25 #include <pwd.h>
26 #include <signal.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <syslog.h>
30 #include <unistd.h>
31 
32 #include "log.h"
33 #include "snmpd.h"
34 
35 __dead void	 usage(void);
36 
37 void	 snmpd_shutdown(struct snmpd *);
38 void	 snmpd_sig_handler(int, short, void *);
39 int	 snmpd_dispatch_snmpe(int, struct privsep_proc *, struct imsg *);
40 int	 check_child(pid_t, const char *);
41 void	 snmpd_backend(struct snmpd *);
42 
43 struct snmpd	*snmpd_env;
44 
45 static struct privsep_proc procs[] = {
46 	{ "snmpe", PROC_SNMPE, snmpd_dispatch_snmpe, snmpe, snmpe_shutdown },
47 };
48 
49 enum privsep_procid privsep_process;
50 
51 void
52 snmpd_sig_handler(int sig, short event, void *arg)
53 {
54 	struct privsep	*ps = arg;
55 	struct snmpd	*env = ps->ps_env;
56 	int		 die = 0, status, fail, id;
57 	pid_t		pid;
58 	char		*cause;
59 
60 	switch (sig) {
61 	case SIGTERM:
62 	case SIGINT:
63 		die = 1;
64 		/* FALLTHROUGH */
65 	case SIGCHLD:
66 		do {
67 			int len;
68 
69 			pid = waitpid(WAIT_ANY, &status, WNOHANG);
70 			if (pid <= 0)
71 				continue;
72 
73 			fail = 0;
74 			if (WIFSIGNALED(status)) {
75 				fail = 1;
76 				len = asprintf(&cause, "terminated; signal %d",
77 				    WTERMSIG(status));
78 			} else if (WIFEXITED(status)) {
79 				if (WEXITSTATUS(status) != 0) {
80 					fail = 1;
81 					len = asprintf(&cause,
82 					    "exited abnormally");
83 				} else
84 					len = asprintf(&cause, "exited okay");
85 			} else
86 				fatalx("unexpected cause of SIGCHLD");
87 
88 			if (len == -1)
89 				fatal("asprintf");
90 
91 			for (id = 0; id < PROC_MAX; id++) {
92 				if (pid == ps->ps_pid[id] &&
93 				    check_child(ps->ps_pid[id],
94 				    ps->ps_title[id])) {
95 					die  = 1;
96 					if (fail)
97 						log_warnx("lost child: %s %s",
98 						    ps->ps_title[id], cause);
99 					break;
100 				}
101 			}
102 			free(cause);
103 		} while (pid > 0 || (pid == -1 && errno == EINTR));
104 
105 		if (die)
106 			snmpd_shutdown(env);
107 		break;
108 	case SIGHUP:
109 		/* reconfigure */
110 		break;
111 	case SIGUSR1:
112 		/* ignore */
113 		break;
114 	default:
115 		fatalx("unexpected signal");
116 	}
117 }
118 
119 __dead void
120 usage(void)
121 {
122 	extern char	*__progname;
123 
124 	fprintf(stderr, "usage: %s [-dNnv] [-D macro=value] "
125 	    "[-f file]\n", __progname);
126 	exit(1);
127 }
128 
129 int
130 main(int argc, char *argv[])
131 {
132 	int		 c;
133 	struct snmpd	*env;
134 	int		 debug = 0, verbose = 0;
135 	u_int		 flags = 0;
136 	int		 noaction = 0;
137 	const char	*conffile = CONF_FILE;
138 	struct privsep	*ps;
139 	int		 proc_id = PROC_PARENT, proc_instance = 0;
140 	int		 argc0 = argc;
141 	char		**argv0 = argv;
142 	const char	*errp, *title = NULL;
143 
144 	smi_init();
145 
146 	/* log to stderr until daemonized */
147 	log_init(1, LOG_DAEMON);
148 
149 	while ((c = getopt(argc, argv, "dD:nNf:I:P:v")) != -1) {
150 		switch (c) {
151 		case 'd':
152 			debug++;
153 			flags |= SNMPD_F_DEBUG;
154 			break;
155 		case 'D':
156 			if (cmdline_symset(optarg) < 0)
157 				log_warnx("could not parse macro definition %s",
158 				    optarg);
159 			break;
160 		case 'n':
161 			noaction = 1;
162 			break;
163 		case 'N':
164 			flags |= SNMPD_F_NONAMES;
165 			break;
166 		case 'f':
167 			conffile = optarg;
168 			break;
169 		case 'I':
170 			proc_instance = strtonum(optarg, 0,
171 			    PROC_MAX_INSTANCES, &errp);
172 			if (errp)
173 				fatalx("invalid process instance");
174 			break;
175 		case 'P':
176 			title = optarg;
177 			proc_id = proc_getid(procs, nitems(procs), title);
178 			if (proc_id == PROC_MAX)
179 				fatalx("invalid process name");
180 			break;
181 		case 'v':
182 			verbose++;
183 			flags |= SNMPD_F_VERBOSE;
184 			break;
185 		default:
186 			usage();
187 		}
188 	}
189 
190 	argc -= optind;
191 	argv += optind;
192 	if (argc > 0)
193 		usage();
194 
195 	log_setverbose(verbose);
196 
197 	if ((env = parse_config(conffile, flags)) == NULL)
198 		exit(1);
199 
200 	ps = &env->sc_ps;
201 	ps->ps_env = env;
202 	snmpd_env = env;
203 	ps->ps_instance = proc_instance;
204 	if (title)
205 		ps->ps_title[proc_id] = title;
206 
207 	if (noaction) {
208 		fprintf(stderr, "configuration ok\n");
209 		exit(0);
210 	}
211 
212 	if (geteuid())
213 		errx(1, "need root privileges");
214 
215 	if ((ps->ps_pw = getpwnam(SNMPD_USER)) == NULL)
216 		errx(1, "unknown user %s", SNMPD_USER);
217 
218 	log_init(debug, LOG_DAEMON);
219 	log_setverbose(verbose);
220 
221 	gettimeofday(&env->sc_starttime, NULL);
222 	env->sc_engine_boots = 0;
223 
224 	proc_init(ps, procs, nitems(procs), debug, argc0, argv0, proc_id);
225 	if (!debug && daemon(0, 0) == -1)
226 		err(1, "failed to daemonize");
227 
228 	log_procinit("parent");
229 	log_info("startup");
230 
231 	event_init();
232 
233 	signal_set(&ps->ps_evsigint, SIGINT, snmpd_sig_handler, ps);
234 	signal_set(&ps->ps_evsigterm, SIGTERM, snmpd_sig_handler, ps);
235 	signal_set(&ps->ps_evsigchld, SIGCHLD, snmpd_sig_handler, ps);
236 	signal_set(&ps->ps_evsighup, SIGHUP, snmpd_sig_handler, ps);
237 	signal_set(&ps->ps_evsigpipe, SIGPIPE, snmpd_sig_handler, ps);
238 	signal_set(&ps->ps_evsigusr1, SIGUSR1, snmpd_sig_handler, ps);
239 
240 	signal_add(&ps->ps_evsigint, NULL);
241 	signal_add(&ps->ps_evsigterm, NULL);
242 	signal_add(&ps->ps_evsigchld, NULL);
243 	signal_add(&ps->ps_evsighup, NULL);
244 	signal_add(&ps->ps_evsigpipe, NULL);
245 	signal_add(&ps->ps_evsigusr1, NULL);
246 
247 	proc_connect(ps);
248 	snmpd_backend(env);
249 
250 	if (pledge("stdio dns sendfd proc exec id", NULL) == -1)
251 		fatal("pledge");
252 
253 	event_dispatch();
254 
255 	log_debug("%d parent exiting", getpid());
256 
257 	return (0);
258 }
259 
260 void
261 snmpd_shutdown(struct snmpd *env)
262 {
263 	proc_kill(&env->sc_ps);
264 
265 	free(env);
266 
267 	log_info("terminating");
268 	exit(0);
269 }
270 
271 int
272 check_child(pid_t pid, const char *pname)
273 {
274 	int	status;
275 
276 	if (waitpid(pid, &status, WNOHANG) > 0) {
277 		if (WIFEXITED(status)) {
278 			log_warnx("check_child: lost child: %s exited", pname);
279 			return (1);
280 		}
281 		if (WIFSIGNALED(status)) {
282 			log_warnx("check_child: lost child: %s terminated; "
283 			    "signal %d", pname, WTERMSIG(status));
284 			return (1);
285 		}
286 	}
287 
288 	return (0);
289 }
290 
291 int
292 snmpd_dispatch_snmpe(int fd, struct privsep_proc *p, struct imsg *imsg)
293 {
294 	switch (imsg->hdr.type) {
295 	case IMSG_TRAP_EXEC:
296 		return (traphandler_priv_recvmsg(p, imsg));
297 	default:
298 		break;
299 	}
300 
301 	return (-1);
302 }
303 
304 int
305 snmpd_socket_af(struct sockaddr_storage *ss, int type)
306 {
307 	int fd, serrno;
308 	const int enable = 1;
309 
310 	fd = socket(ss->ss_family, (type == SOCK_STREAM ?
311 	    SOCK_STREAM | SOCK_NONBLOCK : SOCK_DGRAM) | SOCK_CLOEXEC, 0);
312 	if (fd == -1)
313 		return -1;
314 
315 	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable,
316 	    sizeof(enable)) == -1) {
317 		serrno = errno;
318 		close(fd);
319 		errno = serrno;
320 		return -1;
321 	}
322 	return fd;
323 }
324 
325 u_long
326 snmpd_engine_time(void)
327 {
328 	struct timeval	 now;
329 
330 	/*
331 	 * snmpEngineBoots should be stored in a non-volatile storage.
332 	 * snmpEngineTime is the number of seconds since snmpEngineBoots
333 	 * was last incremented. We don't rely on non-volatile storage.
334 	 * snmpEngineBoots is set to zero and snmpEngineTime to the system
335 	 * clock. Hence, the tuple (snmpEngineBoots, snmpEngineTime) is
336 	 * still unique and protects us against replay attacks. It only
337 	 * 'expires' a little bit sooner than the RFC3414 method.
338 	 */
339 	gettimeofday(&now, NULL);
340 	return now.tv_sec;
341 }
342 
343 void
344 snmpd_backend(struct snmpd *env)
345 {
346 	DIR *dir;
347 	struct dirent *file;
348 	int pair[2];
349 	char *argv[8];
350 	char execpath[PATH_MAX];
351 	size_t i = 0;
352 
353 	if ((dir = opendir(SNMPD_BACKEND)) == NULL)
354 		fatal("opendir \"%s\"", SNMPD_BACKEND);
355 
356 	argv[i++] = execpath;
357 	if (env->sc_rtfilter) {
358 		argv[i++] = "-C";
359 		argv[i++] = "filter-routes";
360 	}
361 	if (env->sc_flags & SNMPD_F_VERBOSE)
362 		argv[i++] = "-vv";
363 	if (env->sc_flags & SNMPD_F_DEBUG) {
364 		argv[i++] = "-d";
365 		argv[i++] = "-x";
366 		argv[i++] = "3";
367 	} else {
368 		argv[i++] = "-x";
369 		argv[i++] = "0";
370 	}
371 	argv[i] = NULL;
372 	while ((file = readdir(dir)) != NULL) {
373 		if (file->d_name[0] == '.')
374 			continue;
375 		if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1)
376 			fatal("socketpair");
377 		switch (fork()) {
378 		case -1:
379 			fatal("fork");
380 		case 0:
381 			close(pair[1]);
382 			if (dup2(pair[0],
383 			    env->sc_flags & SNMPD_F_DEBUG ? 3 : 0) == -1)
384 				fatal("dup2");
385 			if (closefrom(env->sc_flags & SNMPD_F_DEBUG ? 4 : 1) == -1)
386 				fatal("closefrom");
387 			(void)snprintf(execpath, sizeof(execpath), "%s/%s",
388 			    SNMPD_BACKEND, file->d_name);
389 			execv(argv[0], argv);
390 			fatal("execv");
391 		default:
392 			close(pair[0]);
393 			if (proc_compose_imsg(&env->sc_ps, PROC_SNMPE, -1,
394 			    IMSG_AX_FD, -1, pair[1], NULL, 0) == -1)
395 				fatal("proc_compose_imsg");
396 			continue;
397 		}
398 	}
399 }
400