/* $OpenBSD: snmpe.c,v 1.3 2007/12/07 09:50:51 reyk Exp $ */ /* * Copyright (c) 2007 Reyk Floeter * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "snmpd.h" int snmpe_parse(struct sockaddr_storage *, struct ber_element *, struct snmp_message *); unsigned long snmpe_application(struct ber_element *); void snmpe_sig_handler(int sig, short, void *); void snmpe_shutdown(void); void snmpe_dispatch_parent(int, short, void *); int snmpe_socket_af(struct sockaddr_storage *, in_port_t); int snmpe_bind(struct address *); void snmpe_recvmsg(int fd, short, void *); struct snmpd *env = NULL; struct imsgbuf *ibuf_parent; void snmpe_sig_handler(int sig, short event, void *arg) { switch (sig) { case SIGINT: case SIGTERM: snmpe_shutdown(); default: fatalx("snmpe_sig_handler: unexpected signal"); } } pid_t snmpe(struct snmpd *x_env, int pipe_parent2snmpe[2]) { pid_t pid; struct passwd *pw; struct event ev_sigint; struct event ev_sigterm; #ifdef DEBUG struct oid *oid; #endif switch (pid = fork()) { case -1: fatal("snmpe: cannot fork"); case 0: break; default: return (pid); } env = x_env; if (control_init() == -1) fatalx("snmpe: control socket setup failed"); if ((env->sc_sock = snmpe_bind(&env->sc_address)) == -1) fatalx("snmpe: failed to bind SNMP UDP socket"); if ((pw = getpwnam(SNMPD_USER)) == NULL) fatal("snmpe: getpwnam"); #ifndef DEBUG if (chroot(pw->pw_dir) == -1) fatal("snmpe: chroot"); if (chdir("/") == -1) fatal("snmpe: chdir(\"/\")"); #else #warning disabling privilege revocation and chroot in DEBUG mode #endif setproctitle("snmp engine"); snmpd_process = PROC_SNMPE; #ifndef DEBUG if (setgroups(1, &pw->pw_gid) || setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) fatal("snmpe: cannot drop privileges"); #endif #ifdef DEBUG for (oid = NULL; (oid = mps_foreach(oid, 0)) != NULL;) { char buf[BUFSIZ]; mps_oidstring(&oid->o_id, buf, sizeof(buf)); log_debug("oid %s", buf); } #endif event_init(); signal_set(&ev_sigint, SIGINT, snmpe_sig_handler, NULL); signal_set(&ev_sigterm, SIGTERM, snmpe_sig_handler, NULL); signal_add(&ev_sigint, NULL); signal_add(&ev_sigterm, NULL); signal(SIGPIPE, SIG_IGN); signal(SIGHUP, SIG_IGN); close(pipe_parent2snmpe[0]); if ((ibuf_parent = calloc(1, sizeof(struct imsgbuf))) == NULL) fatal("snmpe"); imsg_init(ibuf_parent, pipe_parent2snmpe[1], snmpe_dispatch_parent); ibuf_parent->events = EV_READ; event_set(&ibuf_parent->ev, ibuf_parent->fd, ibuf_parent->events, ibuf_parent->handler, ibuf_parent); event_add(&ibuf_parent->ev, NULL); TAILQ_INIT(&ctl_conns); if (control_listen(env, ibuf_parent) == -1) fatalx("snmpe: control socket listen failed"); event_set(&env->sc_ev, env->sc_sock, EV_READ|EV_PERSIST, snmpe_recvmsg, env); event_add(&env->sc_ev, NULL); kr_init(); event_dispatch(); snmpe_shutdown(); kr_shutdown(); return (0); } void snmpe_shutdown(void) { log_info("snmp engine exiting"); _exit(0); } void snmpe_dispatch_parent(int fd, short event, void * ptr) { struct imsgbuf *ibuf; struct imsg imsg; ssize_t n; ibuf = ptr; switch (event) { case EV_READ: if ((n = imsg_read(ibuf)) == -1) fatal("imsg_read error"); if (n == 0) { /* this pipe is dead, so remove the event handler */ event_del(&ibuf->ev); event_loopexit(NULL); return; } break; case EV_WRITE: if (msgbuf_write(&ibuf->w) == -1) fatal("msgbuf_write"); imsg_event_add(ibuf); return; default: fatalx("snmpe_dispatch_parent: unknown event"); } for (;;) { if ((n = imsg_get(ibuf, &imsg)) == -1) fatal("snmpe_dispatch_parent: imsg_read error"); if (n == 0) break; switch (imsg.hdr.type) { default: log_debug("snmpe_dispatch_parent: unexpected imsg %d", imsg.hdr.type); break; } imsg_free(&imsg); } imsg_event_add(ibuf); } int snmpe_socket_af(struct sockaddr_storage *ss, in_port_t port) { switch (ss->ss_family) { case AF_INET: ((struct sockaddr_in *)ss)->sin_port = port; ((struct sockaddr_in *)ss)->sin_len = sizeof(struct sockaddr_in); break; case AF_INET6: ((struct sockaddr_in6 *)ss)->sin6_port = port; ((struct sockaddr_in6 *)ss)->sin6_len = sizeof(struct sockaddr_in6); break; default: return (-1); } return (0); } int snmpe_bind(struct address *addr) { char buf[512]; int s = -1; if (snmpe_socket_af(&addr->ss, htons(addr->port)) == -1) goto bad; if ((s = socket(addr->ss.ss_family, SOCK_DGRAM, IPPROTO_UDP)) == -1) goto bad; /* * Socket options */ if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) goto bad; if (bind(s, (struct sockaddr *)&addr->ss, addr->ss.ss_len) == -1) goto bad; if (log_host(&addr->ss, buf, sizeof(buf)) == NULL) goto bad; log_info("snmpe_bind: binding to address %s:%d", buf, addr->port); return (s); bad: if (s != -1) close(s); return (-1); } #ifdef DEBUG static void snmpe_debug_elements(struct ber_element *root) { static int indent = 0; long long v; int d; char *buf; size_t len; u_int i; int constructed; struct ber_oid o; char str[BUFSIZ]; /* calculate lengths */ ber_calc_len(root); switch (root->be_encoding) { case BER_TYPE_SEQUENCE: case BER_TYPE_SET: constructed = root->be_encoding; break; default: constructed = 0; break; } fprintf(stderr, "%*slen %lu ", indent, "", root->be_len); switch (root->be_class) { case BER_CLASS_UNIVERSAL: fprintf(stderr, "class: universal(%u) type: ", root->be_class); switch (root->be_type) { case BER_TYPE_EOC: fprintf(stderr, "end-of-content"); break; case BER_TYPE_BOOLEAN: fprintf(stderr, "boolean"); break; case BER_TYPE_INTEGER: fprintf(stderr, "integer"); break; case BER_TYPE_BITSTRING: fprintf(stderr, "bit-string"); break; case BER_TYPE_OCTETSTRING: fprintf(stderr, "octet-string"); break; case BER_TYPE_NULL: fprintf(stderr, "null"); break; case BER_TYPE_OBJECT: fprintf(stderr, "object"); break; case BER_TYPE_ENUMERATED: fprintf(stderr, "enumerated"); break; case BER_TYPE_SEQUENCE: fprintf(stderr, "sequence"); break; case BER_TYPE_SET: fprintf(stderr, "set"); break; } break; case BER_CLASS_APPLICATION: fprintf(stderr, "class: application(%u) type: ", root->be_class); switch (root->be_type) { case SNMP_T_IPADDR: fprintf(stderr, "ipaddr"); break; case SNMP_T_COUNTER32: fprintf(stderr, "counter32"); break; case SNMP_T_GAUGE32: fprintf(stderr, "gauge32"); break; case SNMP_T_TIMETICKS: fprintf(stderr, "timeticks"); break; case SNMP_T_OPAQUE: fprintf(stderr, "opaque"); break; case SNMP_T_COUNTER64: fprintf(stderr, "counter64"); break; } break; case BER_CLASS_CONTEXT: fprintf(stderr, "class: context(%u) type: ", root->be_class); switch (root->be_type) { case SNMP_T_GETREQ: fprintf(stderr, "getreq"); break; case SNMP_T_GETNEXTREQ: fprintf(stderr, "nextreq"); break; case SNMP_T_GETRESP: fprintf(stderr, "getresp"); break; case SNMP_T_SETREQ: fprintf(stderr, "setreq"); break; case SNMP_T_TRAP: fprintf(stderr, "trap"); break; case SNMP_T_GETBULKREQ: fprintf(stderr, "getbulkreq"); break; case SNMP_T_INFORMREQ: fprintf(stderr, "informreq"); break; case SNMP_T_TRAPV2: fprintf(stderr, "trapv2"); break; case SNMP_T_REPORT: fprintf(stderr, "report"); break; } break; case BER_CLASS_PRIVATE: fprintf(stderr, "class: private(%u) type: ", root->be_class); break; default: fprintf(stderr, "class: (%u) type: ", root->be_class); break; } fprintf(stderr, "(%lu) encoding %lu ", root->be_type, root->be_encoding); if (constructed) root->be_encoding = constructed; switch (root->be_encoding) { case BER_TYPE_BOOLEAN: if (ber_get_boolean(root, &d) == -1) { fprintf(stderr, "\n"); break; } fprintf(stderr, "%s(%d)\n", d ? "true" : "false", d); break; case BER_TYPE_INTEGER: case BER_TYPE_ENUMERATED: if (ber_get_integer(root, &v) == -1) { fprintf(stderr, "\n"); break; } fprintf(stderr, "value %lld\n", v); break; case BER_TYPE_BITSTRING: if (ber_get_bitstring(root, (void *)&buf, &len) == -1) { fprintf(stderr, "\n"); break; } fprintf(stderr, "hexdump "); for (i = 0; i < len; i++) fprintf(stderr, "%02x", buf[i]); fprintf(stderr, "\n"); break; case BER_TYPE_OBJECT: if (ber_get_oid(root, &o) == -1) { fprintf(stderr, "\n"); break; } fprintf(stderr, "oid %s", mps_oidstring(&o, str, sizeof(str))); fprintf(stderr, "\n"); break; case BER_TYPE_OCTETSTRING: if (ber_get_string(root, &buf) == -1) { fprintf(stderr, "\n"); break; } if (root->be_class == BER_CLASS_APPLICATION && root->be_type == SNMP_T_IPADDR) { fprintf(stderr, "addr %s\n", inet_ntoa(*(struct in_addr *)buf)); } else fprintf(stderr, "string \"%s\"\n", buf); break; case BER_TYPE_NULL: /* no payload */ case BER_TYPE_EOC: case BER_TYPE_SEQUENCE: case BER_TYPE_SET: default: fprintf(stderr, "\n"); break; } if (constructed && root->be_sub) { indent += 2; snmpe_debug_elements(root->be_sub); indent -= 2; } if (root->be_next) snmpe_debug_elements(root->be_next); } #endif unsigned long snmpe_application(struct ber_element *elm) { if (elm->be_class != BER_CLASS_APPLICATION) return (BER_TYPE_OCTETSTRING); switch (elm->be_type) { case SNMP_T_IPADDR: return (BER_TYPE_OCTETSTRING); case SNMP_T_COUNTER32: case SNMP_T_GAUGE32: case SNMP_T_TIMETICKS: case SNMP_T_OPAQUE: case SNMP_T_COUNTER64: return (BER_TYPE_INTEGER); default: break; } return (BER_TYPE_OCTETSTRING); } int snmpe_parse(struct sockaddr_storage *ss, struct ber_element *root, struct snmp_message *msg) { struct snmp_stats *stats = &env->sc_stats; struct ber_element *a, *b, *c, *d, *e, *f, *next, *last; const char *errstr = "invalid message"; long long ver; unsigned long type, req, errval, erridx; int class, state, i = 0, j = 0; char *comn, buf[BUFSIZ], host[MAXHOSTNAMELEN]; struct ber_oid o; size_t len; bzero(msg, sizeof(*msg)); if (ber_scanf_elements(root, "e{ieset{e", &msg->sm_header, &ver, &msg->sm_headerend, &comn, &msg->sm_pdu, &class, &type, &a) != 0) goto fail; /* SNMP version and community */ switch (ver) { case SNMP_V1: case SNMP_V2: msg->sm_version = ver; break; case SNMP_V3: default: stats->snmp_inbadversions++; log_debug("bad snmp version"); return (-1); } /* SNMP PDU context */ if (class != BER_CLASS_CONTEXT) goto fail; switch (type) { case SNMP_T_GETBULKREQ: if (msg->sm_version == SNMP_V1) { stats->snmp_inbadversions++; log_debug("invalid request for protocol version 1"); return (-1); } /* FALLTHROUGH */ case SNMP_T_GETREQ: stats->snmp_ingetrequests++; /* FALLTHROUGH */ case SNMP_T_GETNEXTREQ: if (type == SNMP_T_GETNEXTREQ) stats->snmp_ingetnexts++; if (strcmp(env->sc_rdcommunity, comn) != 0 && strcmp(env->sc_rwcommunity, comn) != 0) { stats->snmp_inbadcommunitynames++; log_debug("wrong read community"); return (-1); } msg->sm_context = type; break; case SNMP_T_SETREQ: stats->snmp_insetrequests++; if (strcmp(env->sc_rwcommunity, comn) != 0) { if (strcmp(env->sc_rdcommunity, comn) != 0) stats->snmp_inbadcommunitynames++; else stats->snmp_inbadcommunityuses++; log_debug("wrong write community"); return (-1); } msg->sm_context = type; break; case SNMP_T_GETRESP: errstr = "response without request"; stats->snmp_ingetresponses++; goto fail; case SNMP_T_TRAP: case SNMP_T_TRAPV2: if (strcmp(env->sc_trcommunity, comn) != 0) { stats->snmp_inbadcommunitynames++; log_debug("wrong trap community"); return (-1); } errstr = "received trap"; stats->snmp_intraps++; goto fail; default: errstr = "invalid context"; goto fail; } if (strlcpy(msg->sm_community, comn, sizeof(msg->sm_community)) >= sizeof(msg->sm_community)) { stats->snmp_inbadcommunitynames++; log_debug("community name too long"); return (-1); } /* SNMP PDU */ if (ber_scanf_elements(a, "iiie{et", &req, &errval, &erridx, &msg->sm_pduend, &msg->sm_varbind, &class, &type) != 0) { stats->snmp_silentdrops++; log_debug("invalid PDU"); return (-1); } if (class != BER_CLASS_UNIVERSAL && type != BER_TYPE_SEQUENCE) { stats->snmp_silentdrops++; log_debug("invalid varbind"); return (-1); } msg->sm_request = req; msg->sm_error = errval; msg->sm_errorindex = erridx; log_debug("snmpe_parse: %s: SNMPv%d '%s' context %d request %lld", log_host(ss, host, sizeof(host)), msg->sm_version + 1, msg->sm_community, msg->sm_context, msg->sm_request); errstr = "invalid varbind element"; for (i = 1, a = msg->sm_varbind, last = NULL; a != NULL; a = a->be_next, i++) { next = a->be_next; if (a->be_class != BER_CLASS_UNIVERSAL && a->be_type != BER_TYPE_SEQUENCE) goto varfail; if ((b = a->be_sub) == NULL) continue; for (state = 0; state < 2 && b != NULL; b = b->be_next) { switch (state++) { case 0: if (ber_get_oid(b, &o) != 0) goto varfail; if (o.bo_n < BER_MIN_OID_LEN || o.bo_n > BER_MAX_OID_LEN) goto varfail; log_debug("snmpe_parse: %s: oid %s", host, mps_oidstring(&o, buf, sizeof(buf))); break; case 1: c = d = NULL; switch (msg->sm_context) { case SNMP_T_GETNEXTREQ: c = ber_add_sequence(NULL); if ((d = mps_getnextreq(c, &o)) != NULL) break; ber_free_elements(c); c = NULL; msg->sm_error = SNMP_ERROR_NOSUCHNAME; msg->sm_errorindex = i; break; /* ignore error */ case SNMP_T_GETREQ: c = ber_add_sequence(NULL); if ((d = mps_getreq(c, &o)) != NULL) break; msg->sm_error = SNMP_ERROR_NOSUCHNAME; ber_free_elements(c); goto varfail; case SNMP_T_SETREQ: if (mps_setreq(b, &o) == 0) break; msg->sm_error = SNMP_ERROR_READONLY; goto varfail; case SNMP_T_GETBULKREQ: j = msg->sm_maxrepetitions; msg->sm_errorindex = 0; msg->sm_error = SNMP_ERROR_NOSUCHNAME; for (d = NULL, len = 0; j > 0; j--) { e = ber_add_sequence(NULL); if (c == NULL) c = e; f = mps_getnextreq(e, &o); if (f == NULL) { ber_free_elements(e); if (d == NULL) goto varfail; break; } len += ber_calc_len(e); if (len > SNMPD_MAXVARBINDLEN) { ber_free_elements(e); break; } if (d != NULL) ber_link_elements(d, e); d = e; } msg->sm_error = 0; break; default: goto varfail; } if (c == NULL) break; if (last == NULL) msg->sm_varbindresp = c; else ber_replace_elements(last, c); a = c; break; } } if (state < 2) { log_debug("snmpe_parse: state %d", state); goto varfail; } last = a; } return (0); varfail: log_debug("snmpe_parse: %s: %s, error index %d", host, errstr, i); if (msg->sm_error == 0) msg->sm_error = SNMP_ERROR_GENERR; msg->sm_errorindex = i; return (0); fail: stats->snmp_inasnparseerrs++; log_debug("snmpe_parse: %s: %s", host, errstr); return (-1); } void snmpe_recvmsg(int fd, short sig, void *arg) { struct snmp_stats *stats = &env->sc_stats; struct sockaddr_storage ss; u_int8_t buf[READ_BUF_SIZE], *ptr; socklen_t slen; ssize_t len; struct ber ber; struct ber_element *req = NULL, *resp = NULL; struct snmp_message msg; slen = sizeof(ss); if ((len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&ss, &slen)) < 1) return; stats->snmp_inpkts++; ber.fd = -1; ber_set_application(&ber, snmpe_application); ber_set_readbuf(&ber, buf, len); req = ber_read_elements(&ber, NULL); if (req == NULL) { stats->snmp_inasnparseerrs++; goto done; } #ifdef DEBUG snmpe_debug_elements(req); #endif if (snmpe_parse(&ss, req, &msg) == -1) goto done; if (msg.sm_varbindresp == NULL) msg.sm_varbindresp = ber_unlink_elements(msg.sm_pduend); switch (msg.sm_error) { case SNMP_ERROR_TOOBIG: stats->snmp_intoobigs++; break; case SNMP_ERROR_NOSUCHNAME: stats->snmp_innosuchnames++; break; case SNMP_ERROR_BADVALUE: stats->snmp_inbadvalues++; break; case SNMP_ERROR_READONLY: stats->snmp_inreadonlys++; break; case SNMP_ERROR_GENERR: default: stats->snmp_ingenerrs++; break; } /* Create new SNMP packet */ resp = ber_add_sequence(NULL); ber_printf_elements(resp, "ds{tiii{e}}.", msg.sm_version, msg.sm_community, BER_CLASS_CONTEXT, SNMP_T_GETRESP, msg.sm_request, msg.sm_error, msg.sm_errorindex, msg.sm_varbindresp); #ifdef DEBUG snmpe_debug_elements(resp); #endif ber_write_elements(&ber, resp); if ((len = ber_get_writebuf(&ber, (void *)&ptr)) == -1) goto done; len = sendto(fd, ptr, len, 0, (struct sockaddr *)&ss, slen); if (len != -1) stats->snmp_outpkts++; done: if (req != NULL) ber_free_elements(req); if (resp != NULL) ber_free_elements(resp); }