1 /* $OpenBSD: snmpd.c,v 1.52 2024/04/12 14:17:42 bluhm 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 226 log_procinit("parent"); 227 log_info("startup"); 228 229 event_init(); 230 231 signal_set(&ps->ps_evsigint, SIGINT, snmpd_sig_handler, ps); 232 signal_set(&ps->ps_evsigterm, SIGTERM, snmpd_sig_handler, ps); 233 signal_set(&ps->ps_evsigchld, SIGCHLD, snmpd_sig_handler, ps); 234 signal_set(&ps->ps_evsighup, SIGHUP, snmpd_sig_handler, ps); 235 signal_set(&ps->ps_evsigpipe, SIGPIPE, snmpd_sig_handler, ps); 236 signal_set(&ps->ps_evsigusr1, SIGUSR1, snmpd_sig_handler, ps); 237 238 signal_add(&ps->ps_evsigint, NULL); 239 signal_add(&ps->ps_evsigterm, NULL); 240 signal_add(&ps->ps_evsigchld, NULL); 241 signal_add(&ps->ps_evsighup, NULL); 242 signal_add(&ps->ps_evsigpipe, NULL); 243 signal_add(&ps->ps_evsigusr1, NULL); 244 245 proc_connect(ps); 246 snmpd_backend(env); 247 248 if (pledge("stdio dns sendfd proc exec id", NULL) == -1) 249 fatal("pledge"); 250 251 event_dispatch(); 252 253 log_debug("%d parent exiting", getpid()); 254 255 return (0); 256 } 257 258 void 259 snmpd_shutdown(struct snmpd *env) 260 { 261 proc_kill(&env->sc_ps); 262 263 free(env); 264 265 log_info("terminating"); 266 exit(0); 267 } 268 269 int 270 check_child(pid_t pid, const char *pname) 271 { 272 int status; 273 274 if (waitpid(pid, &status, WNOHANG) > 0) { 275 if (WIFEXITED(status)) { 276 log_warnx("check_child: lost child: %s exited", pname); 277 return (1); 278 } 279 if (WIFSIGNALED(status)) { 280 log_warnx("check_child: lost child: %s terminated; " 281 "signal %d", pname, WTERMSIG(status)); 282 return (1); 283 } 284 } 285 286 return (0); 287 } 288 289 int 290 snmpd_dispatch_snmpe(int fd, struct privsep_proc *p, struct imsg *imsg) 291 { 292 switch (imsg->hdr.type) { 293 case IMSG_TRAP_EXEC: 294 return (traphandler_priv_recvmsg(p, imsg)); 295 default: 296 break; 297 } 298 299 return (-1); 300 } 301 302 int 303 snmpd_socket_af(struct sockaddr_storage *ss, int type) 304 { 305 int fd, serrno; 306 const int enable = 1; 307 308 fd = socket(ss->ss_family, (type == SOCK_STREAM ? 309 SOCK_STREAM | SOCK_NONBLOCK : SOCK_DGRAM) | SOCK_CLOEXEC, 0); 310 if (fd == -1) 311 return -1; 312 313 if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, 314 sizeof(enable)) == -1) { 315 serrno = errno; 316 close(fd); 317 errno = serrno; 318 return -1; 319 } 320 return fd; 321 } 322 323 u_long 324 snmpd_engine_time(void) 325 { 326 struct timeval now; 327 328 /* 329 * snmpEngineBoots should be stored in a non-volatile storage. 330 * snmpEngineTime is the number of seconds since snmpEngineBoots 331 * was last incremented. We don't rely on non-volatile storage. 332 * snmpEngineBoots is set to zero and snmpEngineTime to the system 333 * clock. Hence, the tuple (snmpEngineBoots, snmpEngineTime) is 334 * still unique and protects us against replay attacks. It only 335 * 'expires' a little bit sooner than the RFC3414 method. 336 */ 337 gettimeofday(&now, NULL); 338 return now.tv_sec; 339 } 340 341 void 342 snmpd_backend(struct snmpd *env) 343 { 344 DIR *dir; 345 struct dirent *file; 346 int pair[2]; 347 char *argv[8]; 348 char execpath[PATH_MAX]; 349 size_t i = 0; 350 351 if ((dir = opendir(SNMPD_BACKEND)) == NULL) 352 fatal("opendir \"%s\"", SNMPD_BACKEND); 353 354 argv[i++] = execpath; 355 if (env->sc_rtfilter) { 356 argv[i++] = "-C"; 357 argv[i++] = "filter-routes"; 358 } 359 if (env->sc_flags & SNMPD_F_VERBOSE) 360 argv[i++] = "-vv"; 361 if (env->sc_flags & SNMPD_F_DEBUG) 362 argv[i++] = "-d"; 363 argv[i++] = "-x"; 364 argv[i++] = "3"; 365 argv[i] = NULL; 366 while ((file = readdir(dir)) != NULL) { 367 if (file->d_name[0] == '.') 368 continue; 369 if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) 370 fatal("socketpair"); 371 switch (fork()) { 372 case -1: 373 fatal("fork"); 374 case 0: 375 close(pair[1]); 376 if (dup2(pair[0], 3) == -1) 377 fatal("dup2"); 378 if (closefrom(4) == -1) 379 fatal("closefrom"); 380 (void)snprintf(execpath, sizeof(execpath), "%s/%s", 381 SNMPD_BACKEND, file->d_name); 382 execv(argv[0], argv); 383 fatal("execv"); 384 default: 385 close(pair[0]); 386 if (proc_compose_imsg(&env->sc_ps, PROC_SNMPE, -1, 387 IMSG_AX_FD, -1, pair[1], NULL, 0) == -1) 388 fatal("proc_compose_imsg"); 389 continue; 390 } 391 } 392 } 393