diff --git a/contrib/bsnmp/snmpd/trans_inet.c b/contrib/bsnmp/snmpd/trans_inet.c index dccfb6234222..eb41f9b678e9 100644 --- a/contrib/bsnmp/snmpd/trans_inet.c +++ b/contrib/bsnmp/snmpd/trans_inet.c @@ -1,1344 +1,1343 @@ /* * Copyright (c) 2018 * Hartmut Brandt. * All rights reserved. * * Author: Harti Brandt * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Begemot: bsnmp/snmpd/trans_udp.c,v 1.5 2005/10/04 08:46:56 brandt_h Exp $ * * Internet transport */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "asn1.h" #include "snmp.h" #include "snmpmod.h" #include "snmpd.h" #define SNMPTREE_TYPES #define SNMPENUM_FUNCS #include "tree.h" #include "oid.h" extern const struct transport_def inet_trans; struct inet_port; struct inet_port_params; struct port_sock; typedef int create_func(struct inet_port *, struct inet_port_params *); typedef void input_func(int, void *); typedef int activate_func(struct inet_port *); typedef void deactivate_func(struct inet_port *); typedef void parse_ctrl_func(struct port_sock *, const struct msghdr *); typedef void setsrc_func(struct port_sock *, struct msghdr *, char *); static create_func ipv4_create; static input_func ipv4_input; static activate_func ipv4_activate; static deactivate_func ipv4_deactivate; static parse_ctrl_func ipv4_parse_ctrl; static setsrc_func ipv4_setsrc; static create_func ipv6_create; static input_func ipv6_input; static activate_func ipv6_activate; static deactivate_func ipv6_deactivate; static parse_ctrl_func ipv6_parse_ctrl; static setsrc_func ipv6_setsrc; static create_func ipv6z_create; static create_func dns_create; static activate_func dns_activate; static deactivate_func dns_deactivate; struct port_sock { /* common input stuff; must be first */ struct port_input input; /** link field */ TAILQ_ENTRY(port_sock) link; /** pointer to parent */ struct inet_port *port; /** bind address */ struct sockaddr_storage bind_addr; /** reply destination */ struct sockaddr_storage ret_dest; /** need to set source address in reply; set for INADDR_ANY */ bool set_ret_source; /** address of the receive interface */ union { /** IPv4 case */ struct in_addr a4; /** IPv6 case */ struct in6_pktinfo a6; } ret_source; /** parse control message */ parse_ctrl_func *parse_ctrl; /** set source address for a send() */ setsrc_func *setsrc; }; static_assert(offsetof(struct port_sock, input) == 0, "input not first in port_sock"); /** * Table row for the inet ports. * * When actived each row can have one or several open sockets. For ipv6, * ipv4 and ipv6z addresses it is always one, for dns addresses more than * one socket can be open. */ struct inet_port { /** common i/o port stuff (must be first) */ struct tport tport; /** transport protocol */ enum BegemotSnmpdTransportProto proto; /** row status */ enum RowStatus row_status; /** socket list */ TAILQ_HEAD(, port_sock) socks; /** value for InetAddressType::dns */ char *dns_addr; /** port number in dns case; network byte order */ uint16_t dns_port; /** create a port */ create_func *create; /** activate a port */ activate_func *activate; /** deactivate port */ deactivate_func *deactivate; }; static_assert(offsetof(struct inet_port, tport) == 0, "tport not first in inet_port"); /** to be used in bind_addr field */ #define AF_DNS AF_VENDOR00 /** registered transport */ static struct transport *my_trans; /** set operation */ enum { SET_CREATED, SET_ACTIVATED, SET_DEACTIVATE, SET_DESTROY, }; /** length of the control data buffer */ static const size_t RECV_CBUF_SIZE = MAX(CMSG_SPACE(SOCKCREDSIZE(CMGROUP_MAX)) + CMSG_SPACE(sizeof(struct in_addr)), CMSG_SPACE(SOCKCREDSIZE(CMGROUP_MAX)) + CMSG_SPACE(sizeof(struct in6_pktinfo))); /** length of the control data buffer */ static const size_t XMIT_CBUF_SIZE = MAX(CMSG_SPACE(sizeof(struct in_addr)), CMSG_SPACE(sizeof(struct in6_pktinfo))); /** * Start the transport. This registers the transport with the * transport table. * * \return SNMP error code */ static int inet_start(void) { return (trans_register(&inet_trans, &my_trans)); } /** * Stop the transport. This tries to unregister the transport which * in turn fails if the list of ports is not empty. * * \return SNMP error code */ static int inet_stop(int force __unused) { if (my_trans != NULL) if (trans_unregister(my_trans) != 0) return (SNMP_ERR_GENERR); return (SNMP_ERR_NOERROR); } /** * Deactivate SNMP port. * * \param tp port to close */ static void deactivate_port(struct inet_port *p) { p->deactivate(p); } /* * This function activates a port. For ports opened via the config files * this is called just before entering the event loop. For ports create * during runtime this is called when the RowStatus is set to Active or * as second step for CreateAndGo. * * \param tp transport port * * \return SNMP error code */ static int inet_activate(struct tport *tp) { struct inet_port *port = (struct inet_port *)tp; return (port->activate(port)); } /* * Close the SNMP port if it is open and destroy it. * * \param tp port to close */ static void inet_destroy_port(struct tport *tp) { struct inet_port *port = (struct inet_port *)tp; deactivate_port(port); trans_remove_port(tp); free(port->dns_addr); free(port); } /** * If the input struct has no buffer allocated yet, do it now. If allocation * fails, read the data into a local buffer and drop it. * * \param pi input struct * * \return -1 if allocation fails, 0 otherwise */ static int inet_alloc_buf(struct port_input *pi) { char drop_buf[2000]; if (pi->buf == NULL) { if ((pi->buf = buf_alloc(0)) == NULL) { (void)recvfrom(pi->fd, drop_buf, sizeof(drop_buf), 0, NULL, NULL); return (-1); } pi->buflen = buf_size(0); } return (0); } /** * Read message into input buffer. Get also the source address and any * control data that is available. If the message is truncated, increment * corresponding statistics. * * \param pi input object * \param msg message object to fill * \param cbuf control data buffer * * \return -1 when something goes wrong, 0 othersise */ static int inet_read_msg(struct port_input *pi, struct msghdr *msg, char *cbuf) { struct iovec iov[1]; iov[0].iov_base = pi->buf; iov[0].iov_len = pi->buflen; msg->msg_name = pi->peer; msg->msg_namelen = pi->peerlen; msg->msg_iov = iov; msg->msg_iovlen = 1; msg->msg_control = cbuf; msg->msg_controllen = RECV_CBUF_SIZE; msg->msg_flags = 0; memset(cbuf, 0, RECV_CBUF_SIZE); const ssize_t len = recvmsg(pi->fd, msg, 0); if (len == -1 || len == 0) /* receive error */ return (-1); if (msg->msg_flags & MSG_TRUNC) { /* truncated - drop */ snmpd_stats.silentDrops++; snmpd_stats.inTooLong++; return (-1); } pi->length = (size_t)len; return (0); } /* * Input available on socket. * * \param tp transport port * \param pi input struct * * \return number of bytes received */ static ssize_t inet_recv(struct tport *tp, struct port_input *pi) { struct inet_port *port = __containerof(tp, struct inet_port, tport); struct port_sock *sock = __containerof(pi, struct port_sock, input); assert(port->proto == BegemotSnmpdTransportProto_udp); if (inet_alloc_buf(pi) == -1) return (-1); char cbuf[RECV_CBUF_SIZE]; struct msghdr msg; if (inet_read_msg(pi, &msg, cbuf) == -1) return (-1); sock->parse_ctrl(sock, &msg); return (0); } /* * Send message. * * \param tp port * \param buf data to send * \param len number of bytes to send * \param addr destination address * \param addlen destination address length * * \return number of bytes sent */ static ssize_t inet_send2(struct tport *tp, const u_char *buf, size_t len, struct port_input *pi) { struct inet_port *p = __containerof(tp, struct inet_port, tport); struct port_sock *s = (pi == NULL) ? TAILQ_FIRST(&p->socks) : __containerof(pi, struct port_sock, input); struct iovec iov; iov.iov_base = __DECONST(void*, buf); iov.iov_len = len; struct msghdr msg; msg.msg_flags = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_name = (void *)pi->peer; msg.msg_namelen = pi->peerlen; char cbuf[XMIT_CBUF_SIZE]; if (s->set_ret_source) { s->setsrc(s, &msg, cbuf); } else { msg.msg_control = NULL; msg.msg_controllen = 0; } return (sendmsg(s->input.fd, &msg, 0)); } /** exported to daemon */ const struct transport_def inet_trans = { - "inet", - OIDX_begemotSnmpdTransInet, - inet_start, - inet_stop, - inet_destroy_port, - inet_activate, - NULL, - inet_recv, - inet_send2, + .name = "inet", + .id = OIDX_begemotSnmpdTransInet, + .start = inet_start, + .stop = inet_stop, + .close_port = inet_destroy_port, + .init_port = inet_activate, + .recv = inet_recv, + .send2 = inet_send2, }; struct inet_port_params { /** index oid */ struct asn_oid index; /** internet address type */ enum InetAddressType type; /** internet address */ u_char *addr; /** length of address */ size_t addr_len; /** port number */ uint32_t port; /** protocol */ enum BegemotSnmpdTransportProto proto; }; /** * IPv4 creation stuff. Parse the index, fill socket address and creates * a port_sock. * * \param port the port to create * \param params parameters from the SNMP SET * * \return SNMP error */ static int ipv4_create(struct inet_port *port, struct inet_port_params *params) { if (params->addr_len != 4) return (SNMP_ERR_INCONS_VALUE); struct port_sock *sock = calloc(1, sizeof(struct port_sock)); if (sock == NULL) return (SNMP_ERR_GENERR); snmpd_input_init(&sock->input); TAILQ_INSERT_HEAD(&port->socks, sock, link); struct sockaddr_in *sin = (struct sockaddr_in *)&sock->bind_addr; sin->sin_len = sizeof(struct sockaddr_in); sin->sin_family = AF_INET; sin->sin_port = htons(params->port); memcpy(&sin->sin_addr, params->addr, 4); /* network byte order */ sock->port = port; return (SNMP_ERR_NOERROR); } /* * An IPv4 inet port is ready. Delegate to the generic code to read the data * and react. * * \param fd file descriptor that is ready * \param udata inet_port pointer */ static void ipv4_input(int fd __unused, void *udata) { struct port_sock *sock = udata; sock->input.peerlen = sizeof(struct sockaddr_in); snmpd_input(&sock->input, &sock->port->tport); } /** * Activate an IPv4 socket. * * \param sock thhe socket to activate * * \return error code */ static int ipv4_activate_sock(struct port_sock *sock) { if ((sock->input.fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1) { syslog(LOG_ERR, "creating UDP4 socket: %m"); return (SNMP_ERR_RES_UNAVAIL); } const struct sockaddr_in *sin = (const struct sockaddr_in *)&sock->bind_addr; if (sin->sin_addr.s_addr == INADDR_ANY) { /* need to know from which address to return */ static const int on = 1; if (setsockopt(sock->input.fd, IPPROTO_IP, IP_RECVDSTADDR, &on, sizeof(on)) == -1) { syslog(LOG_ERR, "setsockopt(IP_RECVDSTADDR): %m"); (void)close(sock->input.fd); sock->input.fd = -1; return (SNMP_ERR_GENERR); } sock->set_ret_source = true; } if (bind(sock->input.fd, (const struct sockaddr *)sin, sizeof(*sin))) { if (errno == EADDRNOTAVAIL) { (void)close(sock->input.fd); sock->input.fd = -1; return (SNMP_ERR_INCONS_NAME); } syslog(LOG_ERR, "bind: %s:%u %m", inet_ntoa(sin->sin_addr), ntohs(sin->sin_port)); (void)close(sock->input.fd); sock->input.fd = -1; return (SNMP_ERR_GENERR); } if ((sock->input.id = fd_select(sock->input.fd, ipv4_input, sock, NULL)) == NULL) { (void)close(sock->input.fd); sock->input.fd = -1; return (SNMP_ERR_GENERR); } sock->input.peer = (struct sockaddr *)&sock->ret_dest; sock->parse_ctrl = ipv4_parse_ctrl; sock->setsrc = ipv4_setsrc; return (SNMP_ERR_NOERROR); } /** * Open an IPv4 socket. Make the socket, bind it and put it on the select * list. The socket struct has already been created during creation. * * \param p inet port * * \return SNMP error code */ static int ipv4_activate(struct inet_port *p) { struct port_sock *sock = TAILQ_FIRST(&p->socks); assert(sock); assert(!TAILQ_NEXT(sock, link)); const int ret = ipv4_activate_sock(sock); if (ret == SNMP_ERR_NOERROR) p->row_status = RowStatus_active; return (ret); } /** * Close an IPv4 socket. Keep the sock object. * * \param p inet port */ static void ipv4_deactivate(struct inet_port *p) { struct port_sock *sock = TAILQ_FIRST(&p->socks); assert(sock); assert(!TAILQ_NEXT(sock, link)); snmpd_input_close(&sock->input); p->row_status = RowStatus_notInService; } /** * Parse the control data received with a UDPv4 packet. This may contain * credentials (for a local connection) and the address of the interface * the message was received on. If there are credentials set the priv flag * if the effective UID is zero. * * \param sock the sock object * \param msg the received message */ static void ipv4_parse_ctrl(struct port_sock *sock, const struct msghdr *msg) { struct sockcred *cred = NULL; for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL; cmsg = CMSG_NXTHDR(msg, cmsg)) { if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_RECVDSTADDR) { memcpy(&sock->ret_source.a4, CMSG_DATA(cmsg), sizeof(struct in_addr)); } else if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDS) { cred = (struct sockcred *)(void *)CMSG_DATA(cmsg); } } sock->input.priv = 0; if (sock->input.cred && cred) /* remote end has sent credentials */ sock->input.priv = (cred->sc_euid == 0); } /** * Set the source address option for IPv4 sockets. * * \param sock socket object * \param msg message */ static void ipv4_setsrc(struct port_sock *sock, struct msghdr *msg, char *cbuf) { struct cmsghdr *cmsg; msg->msg_control = cbuf; msg->msg_controllen = CMSG_SPACE(sizeof(struct in_addr)); /* select outgoing interface by setting source address */ cmsg = CMSG_FIRSTHDR(msg); cmsg->cmsg_level = IPPROTO_IP; cmsg->cmsg_type = IP_SENDSRCADDR; cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr)); memcpy(CMSG_DATA(cmsg), &sock->ret_source.a4, sizeof(struct in_addr)); } /** * Common part of IPv6 creation. This is used by both ipv6_create() and * ipv6z_create(). * * \param port the table row * \param params creation parameters * \param scope_id scope_id (0 or from index) * * \return SNMP_ERR_NOERROR if port has been created, error code otherwise */ static int ipv6_create_common(struct inet_port *port, struct inet_port_params *params, u_int scope_id) { struct port_sock *sock = calloc(1, sizeof(struct port_sock)); if (sock == NULL) return (SNMP_ERR_GENERR); struct sockaddr_in6 *sin = (struct sockaddr_in6 *)&sock->bind_addr; sin->sin6_len = sizeof(struct sockaddr_in6); sin->sin6_family = AF_INET6; sin->sin6_port = htons(params->port); sin->sin6_flowinfo = 0; sin->sin6_scope_id = scope_id; memcpy(sin->sin6_addr.s6_addr, params->addr, 16); if (IN6_IS_ADDR_LINKLOCAL(&sin->sin6_addr) && scope_id == 0) { char buf[INET6_ADDRSTRLEN]; syslog(LOG_INFO, "%s: link local address used without scope " "index: %s", __func__, inet_ntop(AF_INET6, &sin->sin6_addr, buf, sizeof(buf))); free(sock); return (SNMP_ERR_NO_CREATION); } sock->port = port; snmpd_input_init(&sock->input); TAILQ_INSERT_HEAD(&port->socks, sock, link); return (SNMP_ERR_NOERROR); } /** * IPv6 creation stuff. Parse the index, fill socket address and creates * a port_sock. * * \param port the port to create * \param params parameters from the SNMP SET * * \return SNMP error */ static int ipv6_create(struct inet_port *port, struct inet_port_params *params) { if (params->addr_len != 16) return (SNMP_ERR_INCONS_VALUE); const int ret = ipv6_create_common(port, params, 0); if (ret != SNMP_ERR_NOERROR) return (ret); return (SNMP_ERR_NOERROR); } /* * An IPv6 inet port is ready. Delegate to the generic code to read the data * and react. * * \param fd file descriptor that is ready * \param udata inet_port pointer */ static void ipv6_input(int fd __unused, void *udata) { struct port_sock *sock = udata; sock->input.peerlen = sizeof(struct sockaddr_in6); snmpd_input(&sock->input, &sock->port->tport); } /** * Activate an IPv6 socket. * * \param sock thhe socket to activate * * \return error code */ static int ipv6_activate_sock(struct port_sock *sock) { if ((sock->input.fd = socket(PF_INET6, SOCK_DGRAM, 0)) == -1) { syslog(LOG_ERR, "creating UDP6 socket: %m"); return (SNMP_ERR_RES_UNAVAIL); } const struct sockaddr_in6 *sin = (const struct sockaddr_in6 *)&sock->bind_addr; if (memcmp(&sin->sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0) { /* need to know from which address to return */ static const int on = 1; if (setsockopt(sock->input.fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)) == -1) { syslog(LOG_ERR, "setsockopt(IP6_PKTINFO): %m"); (void)close(sock->input.fd); sock->input.fd = -1; return (SNMP_ERR_GENERR); } sock->set_ret_source = true; } if (bind(sock->input.fd, (const struct sockaddr *)sin, sizeof(*sin))) { if (community != COMM_INITIALIZE && errno == EADDRNOTAVAIL) { (void)close(sock->input.fd); sock->input.fd = -1; return (SNMP_ERR_INCONS_NAME); } char buf[INET6_ADDRSTRLEN]; syslog(LOG_ERR, "bind: %s:%u:%u %m", inet_ntop(AF_INET6, &sin->sin6_addr, buf, sizeof(buf)), sin->sin6_scope_id, ntohs(sin->sin6_port)); (void)close(sock->input.fd); sock->input.fd = -1; return (SNMP_ERR_GENERR); } if ((sock->input.id = fd_select(sock->input.fd, ipv6_input, sock, NULL)) == NULL) { (void)close(sock->input.fd); sock->input.fd = -1; return (SNMP_ERR_GENERR); } sock->input.peer = (struct sockaddr *)&sock->ret_dest; sock->parse_ctrl = ipv6_parse_ctrl; sock->setsrc = ipv6_setsrc; return (SNMP_ERR_NOERROR); } /** * Open an IPv6 socket. * * \param port inet port * * \return SNMP error code */ static int ipv6_activate(struct inet_port *p) { struct port_sock *sock = TAILQ_FIRST(&p->socks); assert(sock); const int ret = ipv6_activate_sock(sock); if (ret == SNMP_ERR_NOERROR) p->row_status = RowStatus_active; return (ret); } /** * Close an IPv6 socket. Keep the sock object. * * \param p inet port */ static void ipv6_deactivate(struct inet_port *p) { struct port_sock *sock = TAILQ_FIRST(&p->socks); assert(sock); assert(!TAILQ_NEXT(sock, link)); snmpd_input_close(&sock->input); p->row_status = RowStatus_notInService; } /** * IPv6 specific part of message processing. The control data may contain * credentials and packet info that contains the destination address of * the packet. * * \param sock the sock object * \param msg the received message */ static void ipv6_parse_ctrl(struct port_sock *sock, const struct msghdr *msg) { struct sockcred *cred = NULL; for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL; cmsg = CMSG_NXTHDR(msg, cmsg)) { if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) { const struct in6_pktinfo *info = (const struct in6_pktinfo *)(const void *) CMSG_DATA(cmsg); sock->ret_source.a6.ipi6_addr = info->ipi6_addr; sock->ret_source.a6.ipi6_ifindex = !IN6_IS_ADDR_LINKLOCAL(&info->ipi6_addr) ? 0: info->ipi6_ifindex; } else if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDS) { cred = (struct sockcred *)(void *)CMSG_DATA(cmsg); } } sock->input.priv = 0; if (sock->input.cred && cred) /* remote end has sent credentials */ sock->input.priv = (cred->sc_euid == 0); } /** * Set the source address option for IPv4 sockets. * * \param sock socket object * \param msg message */ static void ipv6_setsrc(struct port_sock *sock, struct msghdr *msg, char *cbuf) { struct cmsghdr *cmsg; msg->msg_control = cbuf; msg->msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)); /* select outgoing interface by setting source address */ cmsg = CMSG_FIRSTHDR(msg); cmsg->cmsg_level = IPPROTO_IPV6; cmsg->cmsg_type = IPV6_PKTINFO; cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); memcpy(CMSG_DATA(cmsg), &sock->ret_source.a6, sizeof(struct in6_pktinfo)); } /** * IPv6z creation stuff. Parse the index, fill socket address and creates * a port_sock. * * \param port the port to create * \param params parameters from the SNMP SET * * \return SNMP error */ static int ipv6z_create(struct inet_port *port, struct inet_port_params *params) { if (params->addr_len != 20) return (SNMP_ERR_INCONS_VALUE); u_int scope_id = 0; for (u_int i = 16; i < 20; i++) { scope_id <<= 8; scope_id |= params->addr[i]; } const int ret = ipv6_create_common(port, params, scope_id); if (ret != SNMP_ERR_NOERROR) return (ret); return (SNMP_ERR_NOERROR); } /** * DNS name creation stuff. Parse the index and save the string. * This does not create a socket struct. * * \param port the port to create * \param params parameters from the SNMP SET * * \return SNMP error */ static int dns_create(struct inet_port *port, struct inet_port_params *params) { if (params->addr_len > 64) return (SNMP_ERR_INCONS_VALUE); if (strnlen(params->addr, params->addr_len) != params->addr_len) return (SNMP_ERR_INCONS_VALUE); if ((port->dns_addr = realloc(params->addr, params->addr_len + 1)) == NULL) return (SNMP_ERR_GENERR); port->dns_addr[params->addr_len] = '\0'; params->addr = NULL; port->dns_port = htons(params->port); return (SNMP_ERR_NOERROR); } /** * Open sockets. This loops through all the addresses returned by getaddrinfo * and opens a socket for each of them. * * \param port inet port * * \return SNMP error code */ static int dns_activate(struct inet_port *port) { struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; // XXX udp-only hints.ai_flags = AI_ADDRCONFIG | AI_PASSIVE | AI_NUMERICSERV; char portbuf[8]; sprintf(portbuf, "%hu", ntohs(port->dns_port)); struct addrinfo *res0; int error = getaddrinfo(port->dns_addr[0] == '\0' ? NULL : port->dns_addr, portbuf, &hints, &res0); if (error) { syslog(LOG_ERR, "cannot resolve address '%s': %s", port->dns_addr, gai_strerror(error)); return (SNMP_ERR_GENERR); } for (struct addrinfo *res = res0; res != NULL; res = res->ai_next) { if (res->ai_family != AF_INET && res->ai_family != AF_INET6) continue; struct port_sock *sock = calloc(1, sizeof(struct port_sock)); if (sock == NULL) return (SNMP_ERR_GENERR); snmpd_input_init(&sock->input); sock->port = port; int ret = SNMP_ERR_NOERROR; if (res->ai_family == AF_INET) { *(struct sockaddr_in *)&sock->bind_addr = *(struct sockaddr_in *)(void *)res->ai_addr; ret = ipv4_activate_sock(sock); } else { *(struct sockaddr_in6 *)&sock->bind_addr = *(struct sockaddr_in6 *)(void *)res->ai_addr; ret = ipv6_activate_sock(sock); } if (ret != SNMP_ERR_NOERROR) free(sock); else TAILQ_INSERT_HEAD(&port->socks, sock, link); } if (!TAILQ_EMPTY(&port->socks)) port->row_status = RowStatus_active; freeaddrinfo(res0); return (SNMP_ERR_GENERR); } /** * Deactive the socket. Close all open sockets and delete all sock objects. * * \param port inet port */ static void dns_deactivate(struct inet_port *port) { while (!TAILQ_EMPTY(&port->socks)) { struct port_sock *sock = TAILQ_FIRST(&port->socks); TAILQ_REMOVE(&port->socks, sock, link); snmpd_input_close(&sock->input); free(sock); } port->row_status = RowStatus_notInService; } static int inet_create(struct inet_port_params *params, struct inet_port **pp) { int err = SNMP_ERR_NOERROR; struct inet_port *port = NULL; if (params->port > 0xffff) { err = SNMP_ERR_NO_CREATION; goto fail; } if ((port = malloc(sizeof(*port))) == NULL) { err = SNMP_ERR_GENERR; goto fail; } memset(port, 0, sizeof(*port)); TAILQ_INIT(&port->socks); port->proto = params->proto; port->tport.index = params->index; switch (params->type) { case InetAddressType_ipv4: port->create = ipv4_create; port->activate = ipv4_activate; port->deactivate = ipv4_deactivate; break; case InetAddressType_ipv6: port->create = ipv6_create; port->activate = ipv6_activate; port->deactivate = ipv6_deactivate; break; case InetAddressType_ipv6z: port->create = ipv6z_create; port->activate = ipv6_activate; port->deactivate = ipv6_deactivate; break; case InetAddressType_dns: port->create = dns_create; port->activate = dns_activate; port->deactivate = dns_deactivate; break; default: err = SNMP_ERR_NO_CREATION; goto fail; } if ((err = port->create(port, params)) != SNMP_ERR_NOERROR) goto fail; *pp = port; trans_insert_port(my_trans, &port->tport); return (err); fail: free(port->dns_addr); free(port); return (err); } static int create_and_go(struct snmp_context *ctx, struct inet_port_params *params) { int err; struct inet_port *port; if ((err = inet_create(params, &port)) != SNMP_ERR_NOERROR) return (err); port->row_status = RowStatus_createAndGo; ctx->scratch->ptr1 = port; if (community == COMM_INITIALIZE) return (err); return (inet_activate(&port->tport)); } static int create_and_wait(struct snmp_context *ctx, struct inet_port_params *params) { int err; struct inet_port *port; if ((err = inet_create(params, &port)) != SNMP_ERR_NOERROR) return (err); port->row_status = RowStatus_createAndWait; ctx->scratch->ptr1 = port; return (err); } /** * This is called to set a RowStatus value in the port table during * SET processing. * * When this is called during initialization time and the RowStatus is set * to CreateAndGo, the port is actually not activated. This is done when * the main code calls the init() for all ports later. */ static int inet_port_set(struct snmp_context *ctx, struct inet_port *port, struct inet_port_params *params, enum RowStatus nstatus) { switch (nstatus) { case RowStatus_createAndGo: if (port != NULL) return (SNMP_ERR_INCONS_VALUE); ctx->scratch->int1 = SET_CREATED; return (create_and_go(ctx, params)); case RowStatus_createAndWait: if (port != NULL) return (SNMP_ERR_INCONS_VALUE); ctx->scratch->int1 = SET_CREATED; return (create_and_wait(ctx, params)); case RowStatus_active: if (port == NULL) return (SNMP_ERR_INCONS_VALUE); switch (port->row_status) { case RowStatus_notReady: /* this can not happend */ abort(); case RowStatus_notInService: ctx->scratch->int1 = SET_ACTIVATED; return (inet_activate(&port->tport)); case RowStatus_active: return (SNMP_ERR_NOERROR); case RowStatus_createAndGo: case RowStatus_createAndWait: case RowStatus_destroy: abort(); } break; case RowStatus_notInService: if (port == NULL) return (SNMP_ERR_INCONS_VALUE); switch (port->row_status) { case RowStatus_notReady: /* this can not happend */ abort(); case RowStatus_notInService: return (SNMP_ERR_NOERROR); case RowStatus_active: /* this is done during commit */ ctx->scratch->int1 = SET_DEACTIVATE; return (SNMP_ERR_NOERROR); case RowStatus_createAndGo: case RowStatus_createAndWait: case RowStatus_destroy: abort(); } break; case RowStatus_destroy: /* this is done during commit */ ctx->scratch->int1 = SET_DESTROY; return (SNMP_ERR_NOERROR); case RowStatus_notReady: return (SNMP_ERR_WRONG_VALUE); } abort(); } /* * Port table */ int op_snmp_trans_inet(struct snmp_context *ctx, struct snmp_value *value, u_int sub, u_int iidx __unused, enum snmp_op op) { asn_subid_t which = value->var.subs[sub - 1]; struct inet_port *port; struct inet_port_params params; int ret; switch (op) { case SNMP_OP_GETNEXT: if ((port = (struct inet_port *)trans_next_port(my_trans, &value->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); index_append(&value->var, sub, &port->tport.index); goto fetch; case SNMP_OP_GET: if ((port = (struct inet_port *)trans_find_port(my_trans, &value->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); goto fetch; case SNMP_OP_SET: port = (struct inet_port *)trans_find_port(my_trans, &value->var, sub); if (which != LEAF_begemotSnmpdTransInetStatus) abort(); if (!isok_RowStatus(value->v.integer)) return (SNMP_ERR_WRONG_VALUE); if (index_decode(&value->var, sub, iidx, ¶ms.type, ¶ms.addr, ¶ms.addr_len, ¶ms.port, ¶ms.proto)) return (SNMP_ERR_NO_CREATION); asn_slice_oid(¶ms.index, &value->var, sub, value->var.len); ret = inet_port_set(ctx, port, ¶ms, (enum RowStatus)value->v.integer); free(params.addr); return (ret); case SNMP_OP_ROLLBACK: if ((port = (struct inet_port *)trans_find_port(my_trans, &value->var, sub)) == NULL) /* cannot happen */ abort(); switch (ctx->scratch->int1) { case SET_CREATED: /* newly created */ assert(port != NULL); inet_destroy_port(&port->tport); return (SNMP_ERR_NOERROR); case SET_DESTROY: /* do it now */ assert(port != NULL); return (SNMP_ERR_NOERROR); case SET_ACTIVATED: deactivate_port(port); return (SNMP_ERR_NOERROR); case SET_DEACTIVATE: return (SNMP_ERR_NOERROR); } abort(); case SNMP_OP_COMMIT: if ((port = (struct inet_port *)trans_find_port(my_trans, &value->var, sub)) == NULL) /* cannot happen */ abort(); switch (ctx->scratch->int1) { case SET_CREATED: /* newly created */ assert(port != NULL); return (SNMP_ERR_NOERROR); case SET_DESTROY: /* do it now */ assert(port != NULL); inet_destroy_port(&port->tport); return (SNMP_ERR_NOERROR); case SET_ACTIVATED: return (SNMP_ERR_NOERROR); case SET_DEACTIVATE: deactivate_port(port); return (SNMP_ERR_NOERROR); } abort(); } abort(); fetch: switch (which) { case LEAF_begemotSnmpdTransInetStatus: value->v.integer = port->row_status; break; default: abort(); } return (SNMP_ERR_NOERROR); } diff --git a/contrib/bsnmp/snmpd/trans_lsock.c b/contrib/bsnmp/snmpd/trans_lsock.c index ca2311be7cc3..68aea2c77187 100644 --- a/contrib/bsnmp/snmpd/trans_lsock.c +++ b/contrib/bsnmp/snmpd/trans_lsock.c @@ -1,670 +1,669 @@ /* * Copyright (c) 2003 * Fraunhofer Institute for Open Communication Systems (FhG Fokus). * All rights reserved. * * Author: Harti Brandt * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Begemot: bsnmp/snmpd/trans_lsock.c,v 1.6 2005/02/25 11:50:25 brandt_h Exp $ * * Local domain socket transport */ #include #include #include #include #include #include #include #include #include #include #include #include #include "snmpmod.h" #include "snmpd.h" #include "trans_lsock.h" #include "tree.h" #include "oid.h" static const struct asn_oid oid_begemotSnmpdLocalPortTable = OIDX_begemotSnmpdLocalPortTable; static int lsock_start(void); static int lsock_stop(int); static void lsock_close_port(struct tport *); static int lsock_init_port(struct tport *); static ssize_t lsock_send(struct tport *, const u_char *, size_t, const struct sockaddr *, size_t); static ssize_t lsock_recv(struct tport *, struct port_input *); /* exported */ const struct transport_def lsock_trans = { - "lsock", - OIDX_begemotSnmpdTransLsock, - lsock_start, - lsock_stop, - lsock_close_port, - lsock_init_port, - lsock_send, - lsock_recv, - NULL + .name = "lsock", + .id = OIDX_begemotSnmpdTransLsock, + .start = lsock_start, + .stop = lsock_stop, + .close_port = lsock_close_port, + .init_port = lsock_init_port, + .send = lsock_send, + .recv = lsock_recv, }; static struct transport *my_trans; static int lsock_remove(struct tport *tp, intptr_t arg __unused) { struct lsock_port *port = (struct lsock_port *)tp; (void)remove(port->name); return (-1); } static int lsock_stop(int force) { if (my_trans != NULL) { if (!force && trans_first_port(my_trans) != NULL) return (SNMP_ERR_GENERR); trans_iter_port(my_trans, lsock_remove, 0); return (trans_unregister(my_trans)); } return (SNMP_ERR_NOERROR); } static int lsock_start(void) { return (trans_register(&lsock_trans, &my_trans)); } /* * Open a local port. If this is a datagram socket create also the * one and only peer. */ static int lsock_open_port(u_char *name, size_t namelen, struct lsock_port **pp, int type) { struct lsock_port *port; struct lsock_peer *peer = NULL; int is_stream, need_cred; size_t u; int err; struct sockaddr_un sa; if (namelen == 0 || namelen + 1 > sizeof(sa.sun_path)) return (SNMP_ERR_BADVALUE); switch (type) { case LOCP_DGRAM_UNPRIV: is_stream = 0; need_cred = 0; break; case LOCP_DGRAM_PRIV: is_stream = 0; need_cred = 1; break; case LOCP_STREAM_UNPRIV: is_stream = 1; need_cred = 0; break; case LOCP_STREAM_PRIV: is_stream = 1; need_cred = 1; break; default: return (SNMP_ERR_BADVALUE); } if ((port = calloc(1, sizeof(*port))) == NULL) return (SNMP_ERR_GENERR); if (!is_stream) { if ((peer = calloc(1, sizeof(*peer))) == NULL) { free(port); return (SNMP_ERR_GENERR); } } if ((port->name = malloc(namelen + 1)) == NULL) { free(port); if (!is_stream) free(peer); return (SNMP_ERR_GENERR); } strncpy(port->name, name, namelen); port->name[namelen] = '\0'; port->type = type; port->str_sock = -1; LIST_INIT(&port->peers); port->tport.index.len = namelen + 1; port->tport.index.subs[0] = namelen; for (u = 0; u < namelen; u++) port->tport.index.subs[u + 1] = name[u]; if (peer != NULL) { LIST_INSERT_HEAD(&port->peers, peer, link); peer->port = port; peer->input.fd = -1; peer->input.id = NULL; peer->input.stream = is_stream; peer->input.cred = need_cred; peer->input.peer = (struct sockaddr *)&peer->peer; } trans_insert_port(my_trans, &port->tport); if (community != COMM_INITIALIZE && (err = lsock_init_port(&port->tport)) != SNMP_ERR_NOERROR) { lsock_close_port(&port->tport); return (err); } *pp = port; return (SNMP_ERR_NOERROR); } /* * Close a local domain peer */ static void lsock_peer_close(struct lsock_peer *peer) { LIST_REMOVE(peer, link); snmpd_input_close(&peer->input); free(peer); } /* * Close a local port */ static void lsock_close_port(struct tport *tp) { struct lsock_port *port = (struct lsock_port *)tp; struct lsock_peer *peer; if (port->str_id != NULL) fd_deselect(port->str_id); if (port->str_sock >= 0) (void)close(port->str_sock); (void)remove(port->name); trans_remove_port(tp); while ((peer = LIST_FIRST(&port->peers)) != NULL) lsock_peer_close(peer); free(port->name); free(port); } /* * Input on a local socket (either datagram or stream) */ static void lsock_input(int fd __unused, void *udata) { struct lsock_peer *peer = udata; struct lsock_port *p = peer->port; peer->input.peerlen = sizeof(peer->peer); if (snmpd_input(&peer->input, &p->tport) == -1 && peer->input.stream) /* framing or other input error */ lsock_peer_close(peer); } /* * A UNIX domain listening socket is ready. This means we have a peer * that we need to accept */ static void lsock_listen_input(int fd, void *udata) { struct lsock_port *p = udata; struct lsock_peer *peer; if ((peer = calloc(1, sizeof(*peer))) == NULL) { syslog(LOG_WARNING, "%s: peer malloc failed", p->name); (void)close(accept(fd, NULL, NULL)); return; } peer->port = p; peer->input.stream = 1; peer->input.cred = (p->type == LOCP_DGRAM_PRIV || p->type == LOCP_STREAM_PRIV); peer->input.peerlen = sizeof(peer->peer); peer->input.peer = (struct sockaddr *)&peer->peer; peer->input.fd = accept(fd, peer->input.peer, &peer->input.peerlen); if (peer->input.fd == -1) { syslog(LOG_WARNING, "%s: accept failed: %m", p->name); free(peer); return; } if ((peer->input.id = fd_select(peer->input.fd, lsock_input, peer, NULL)) == NULL) { close(peer->input.fd); free(peer); return; } LIST_INSERT_HEAD(&p->peers, peer, link); } /* * Create a local socket */ static int lsock_init_port(struct tport *tp) { struct lsock_port *p = (struct lsock_port *)tp; struct sockaddr_un sa; if (p->type == LOCP_STREAM_PRIV || p->type == LOCP_STREAM_UNPRIV) { if ((p->str_sock = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) { syslog(LOG_ERR, "creating local socket: %m"); return (SNMP_ERR_RES_UNAVAIL); } strlcpy(sa.sun_path, p->name, sizeof(sa.sun_path)); sa.sun_family = AF_LOCAL; sa.sun_len = SUN_LEN(&sa); (void)remove(p->name); if (bind(p->str_sock, (struct sockaddr *)&sa, sizeof(sa))) { if (errno == EADDRNOTAVAIL) { close(p->str_sock); p->str_sock = -1; return (SNMP_ERR_INCONS_NAME); } syslog(LOG_ERR, "bind: %s %m", p->name); close(p->str_sock); p->str_sock = -1; return (SNMP_ERR_GENERR); } if (chmod(p->name, 0666) == -1) syslog(LOG_WARNING, "chmod(%s,0666): %m", p->name); if (listen(p->str_sock, 10) == -1) { syslog(LOG_ERR, "listen: %s %m", p->name); (void)remove(p->name); close(p->str_sock); p->str_sock = -1; return (SNMP_ERR_GENERR); } p->str_id = fd_select(p->str_sock, lsock_listen_input, p, NULL); if (p->str_id == NULL) { (void)remove(p->name); close(p->str_sock); p->str_sock = -1; return (SNMP_ERR_GENERR); } } else { struct lsock_peer *peer; const int on = 1; peer = LIST_FIRST(&p->peers); if ((peer->input.fd = socket(PF_LOCAL, SOCK_DGRAM, 0)) < 0) { syslog(LOG_ERR, "creating local socket: %m"); return (SNMP_ERR_RES_UNAVAIL); } if (setsockopt(peer->input.fd, 0, LOCAL_CREDS, &on, sizeof(on)) == -1) { syslog(LOG_ERR, "setsockopt(LOCAL_CREDS): %m"); close(peer->input.fd); peer->input.fd = -1; return (SNMP_ERR_GENERR); } strlcpy(sa.sun_path, p->name, sizeof(sa.sun_path)); sa.sun_family = AF_LOCAL; sa.sun_len = SUN_LEN(&sa); (void)remove(p->name); if (bind(peer->input.fd, (struct sockaddr *)&sa, sizeof(sa))) { if (errno == EADDRNOTAVAIL) { close(peer->input.fd); peer->input.fd = -1; return (SNMP_ERR_INCONS_NAME); } syslog(LOG_ERR, "bind: %s %m", p->name); close(peer->input.fd); peer->input.fd = -1; return (SNMP_ERR_GENERR); } if (chmod(p->name, 0666) == -1) syslog(LOG_WARNING, "chmod(%s,0666): %m", p->name); peer->input.id = fd_select(peer->input.fd, lsock_input, peer, NULL); if (peer->input.id == NULL) { (void)remove(p->name); close(peer->input.fd); peer->input.fd = -1; return (SNMP_ERR_GENERR); } } return (SNMP_ERR_NOERROR); } /* * Send something */ static ssize_t lsock_send(struct tport *tp, const u_char *buf, size_t len, const struct sockaddr *addr, size_t addrlen) { struct lsock_port *p = (struct lsock_port *)tp; struct lsock_peer *peer; if (p->type == LOCP_DGRAM_PRIV || p->type == LOCP_DGRAM_UNPRIV) { peer = LIST_FIRST(&p->peers); } else { /* search for the peer */ LIST_FOREACH(peer, &p->peers, link) if (peer->input.peerlen == addrlen && memcmp(peer->input.peer, addr, addrlen) == 0) break; if (peer == NULL) { errno = ENOTCONN; return (-1); } } return (sendto(peer->input.fd, buf, len, MSG_NOSIGNAL, addr, addrlen)); } static void check_priv_stream(struct port_input *pi) { struct xucred ucred; socklen_t ucredlen; /* obtain the accept time credentials */ ucredlen = sizeof(ucred); if (getsockopt(pi->fd, 0, LOCAL_PEERCRED, &ucred, &ucredlen) == 0 && ucredlen >= sizeof(ucred) && ucred.cr_version == XUCRED_VERSION) pi->priv = (ucred.cr_uid == 0); else pi->priv = 0; } /* * Receive something */ static ssize_t lsock_recv(struct tport *tp __unused, struct port_input *pi) { struct msghdr msg; struct iovec iov[1]; ssize_t len; msg.msg_control = NULL; msg.msg_controllen = 0; if (pi->buf == NULL) { /* no buffer yet - allocate one */ if ((pi->buf = buf_alloc(0)) == NULL) { /* ups - could not get buffer. Return an error * the caller must close the transport. */ return (-1); } pi->buflen = buf_size(0); pi->consumed = 0; pi->length = 0; } /* try to get a message */ msg.msg_name = pi->peer; msg.msg_namelen = pi->peerlen; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_control = NULL; msg.msg_controllen = 0; msg.msg_flags = 0; iov[0].iov_base = pi->buf + pi->length; iov[0].iov_len = pi->buflen - pi->length; len = recvmsg(pi->fd, &msg, 0); if (len == -1 || len == 0) /* receive error */ return (-1); pi->length += len; if (pi->cred) check_priv_stream(pi); return (0); } /* * Dependency to create a lsock port */ struct lsock_dep { struct snmp_dependency dep; /* index (path name) */ u_char *path; size_t pathlen; /* the port */ struct lsock_port *port; /* which of the fields are set */ u_int set; /* type of the port */ int type; /* status */ int status; }; #define LD_TYPE 0x01 #define LD_STATUS 0x02 #define LD_CREATE 0x04 /* rollback create */ #define LD_DELETE 0x08 /* rollback delete */ /* * dependency handler for lsock ports */ static int lsock_func(struct snmp_context *ctx, struct snmp_dependency *dep, enum snmp_depop op) { struct lsock_dep *ld = (struct lsock_dep *)(void *)dep; int err = SNMP_ERR_NOERROR; switch (op) { case SNMP_DEPOP_COMMIT: if (!(ld->set & LD_STATUS)) err = SNMP_ERR_BADVALUE; else if (ld->port == NULL) { if (!ld->status) err = SNMP_ERR_BADVALUE; else { /* create */ err = lsock_open_port(ld->path, ld->pathlen, &ld->port, ld->type); if (err == SNMP_ERR_NOERROR) ld->set |= LD_CREATE; } } else if (!ld->status) { /* delete - hard to roll back so defer to finalizer */ ld->set |= LD_DELETE; } else /* modify - read-only */ err = SNMP_ERR_READONLY; return (err); case SNMP_DEPOP_ROLLBACK: if (ld->set & LD_CREATE) { /* was create */ lsock_close_port(&ld->port->tport); } return (SNMP_ERR_NOERROR); case SNMP_DEPOP_FINISH: if ((ld->set & LD_DELETE) && ctx->code == SNMP_RET_OK) lsock_close_port(&ld->port->tport); free(ld->path); return (SNMP_ERR_NOERROR); } abort(); } /* * Local port table */ int op_lsock_port(struct snmp_context *ctx, struct snmp_value *value, u_int sub, u_int iidx, enum snmp_op op) { asn_subid_t which = value->var.subs[sub-1]; struct lsock_port *p; u_char *name; size_t namelen; struct lsock_dep *ld; struct asn_oid didx; switch (op) { case SNMP_OP_GETNEXT: if ((p = (struct lsock_port *)trans_next_port(my_trans, &value->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); index_append(&value->var, sub, &p->tport.index); break; case SNMP_OP_GET: if ((p = (struct lsock_port *)trans_find_port(my_trans, &value->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); break; case SNMP_OP_SET: p = (struct lsock_port *)trans_find_port(my_trans, &value->var, sub); if (index_decode(&value->var, sub, iidx, &name, &namelen)) return (SNMP_ERR_NO_CREATION); asn_slice_oid(&didx, &value->var, sub, value->var.len); if ((ld = (struct lsock_dep *)(void *)snmp_dep_lookup(ctx, &oid_begemotSnmpdLocalPortTable, &didx, sizeof(*ld), lsock_func)) == NULL) { free(name); return (SNMP_ERR_GENERR); } if (ld->path == NULL) { ld->path = name; ld->pathlen = namelen; } else { free(name); } ld->port = p; switch (which) { case LEAF_begemotSnmpdLocalPortStatus: if (ld->set & LD_STATUS) return (SNMP_ERR_INCONS_VALUE); if (!TRUTH_OK(value->v.integer)) return (SNMP_ERR_WRONG_VALUE); ld->status = TRUTH_GET(value->v.integer); ld->set |= LD_STATUS; break; case LEAF_begemotSnmpdLocalPortType: if (ld->set & LD_TYPE) return (SNMP_ERR_INCONS_VALUE); if (value->v.integer < 1 || value->v.integer > 4) return (SNMP_ERR_WRONG_VALUE); ld->type = value->v.integer; ld->set |= LD_TYPE; break; } return (SNMP_ERR_NOERROR); case SNMP_OP_ROLLBACK: case SNMP_OP_COMMIT: return (SNMP_ERR_NOERROR); default: abort(); } /* * Come here to fetch the value */ switch (which) { case LEAF_begemotSnmpdLocalPortStatus: value->v.integer = 1; break; case LEAF_begemotSnmpdLocalPortType: value->v.integer = p->type; break; default: abort(); } return (SNMP_ERR_NOERROR); } diff --git a/contrib/bsnmp/snmpd/trans_udp.c b/contrib/bsnmp/snmpd/trans_udp.c index 8e9d1510d1d7..55824b82f6c8 100644 --- a/contrib/bsnmp/snmpd/trans_udp.c +++ b/contrib/bsnmp/snmpd/trans_udp.c @@ -1,439 +1,438 @@ /* * Copyright (c) 2003 * Fraunhofer Institute for Open Communication Systems (FhG Fokus). * All rights reserved. * * Author: Harti Brandt * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Begemot: bsnmp/snmpd/trans_udp.c,v 1.5 2005/10/04 08:46:56 brandt_h Exp $ * * UDP transport */ #include #include #include #include #include #include #include #include #include #include #include #include "snmpmod.h" #include "snmpd.h" #include "trans_udp.h" #include "tree.h" #include "oid.h" static int udp_start(void); static int udp_stop(int); static void udp_close_port(struct tport *); static int udp_init_port(struct tport *); static ssize_t udp_send(struct tport *, const u_char *, size_t, const struct sockaddr *, size_t); static ssize_t udp_recv(struct tport *, struct port_input *); /* exported */ const struct transport_def udp_trans = { - "udp", - OIDX_begemotSnmpdTransUdp, - udp_start, - udp_stop, - udp_close_port, - udp_init_port, - udp_send, - udp_recv, - NULL + .name = "udp", + .id = OIDX_begemotSnmpdTransUdp, + .start = udp_start, + .stop = udp_stop, + .close_port = udp_close_port, + .init_port = udp_init_port, + .send = udp_send, + .recv = udp_recv, }; static struct transport *my_trans; static int udp_start(void) { return (trans_register(&udp_trans, &my_trans)); } static int udp_stop(int force __unused) { if (my_trans != NULL) if (trans_unregister(my_trans) != 0) return (SNMP_ERR_GENERR); return (SNMP_ERR_NOERROR); } /* * A UDP port is ready */ static void udp_input(int fd __unused, void *udata) { struct udp_port *p = udata; p->input.peerlen = sizeof(p->ret); snmpd_input(&p->input, &p->tport); } /* * Create a UDP socket and bind it to the given port */ static int udp_init_port(struct tport *tp) { struct udp_port *p = (struct udp_port *)tp; struct sockaddr_in addr; u_int32_t ip; const int on = 1; if ((p->input.fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { syslog(LOG_ERR, "creating UDP socket: %m"); return (SNMP_ERR_RES_UNAVAIL); } ip = (p->addr[0] << 24) | (p->addr[1] << 16) | (p->addr[2] << 8) | p->addr[3]; memset(&addr, 0, sizeof(addr)); addr.sin_addr.s_addr = htonl(ip); addr.sin_port = htons(p->port); addr.sin_family = AF_INET; addr.sin_len = sizeof(addr); if (addr.sin_addr.s_addr == INADDR_ANY) { if (setsockopt(p->input.fd, IPPROTO_IP, IP_RECVDSTADDR, &on, sizeof(on)) == -1) { syslog(LOG_ERR, "setsockopt(IP_RECVDSTADDR): %m"); close(p->input.fd); p->input.fd = -1; return (SNMP_ERR_GENERR); } p->recvdstaddr = true; } if (bind(p->input.fd, (struct sockaddr *)&addr, sizeof(addr))) { if (errno == EADDRNOTAVAIL) { close(p->input.fd); p->input.fd = -1; return (SNMP_ERR_INCONS_NAME); } syslog(LOG_ERR, "bind: %s:%u %m", inet_ntoa(addr.sin_addr), p->port); close(p->input.fd); p->input.fd = -1; return (SNMP_ERR_GENERR); } if ((p->input.id = fd_select(p->input.fd, udp_input, p, NULL)) == NULL) { close(p->input.fd); p->input.fd = -1; return (SNMP_ERR_GENERR); } return (SNMP_ERR_NOERROR); } /* * Create a new SNMP Port object and start it, if we are not * in initialization mode. The arguments are in host byte order. */ static int udp_open_port(u_int8_t *addr, u_int32_t udp_port, struct udp_port **pp) { struct udp_port *port; int err; if (udp_port > 0xffff) return (SNMP_ERR_NO_CREATION); if ((port = malloc(sizeof(*port))) == NULL) return (SNMP_ERR_GENERR); memset(port, 0, sizeof(*port)); /* initialize common part */ port->tport.index.len = 5; port->tport.index.subs[0] = addr[0]; port->tport.index.subs[1] = addr[1]; port->tport.index.subs[2] = addr[2]; port->tport.index.subs[3] = addr[3]; port->tport.index.subs[4] = udp_port; port->addr[0] = addr[0]; port->addr[1] = addr[1]; port->addr[2] = addr[2]; port->addr[3] = addr[3]; port->port = udp_port; port->input.fd = -1; port->input.id = NULL; port->input.stream = 0; port->input.cred = 0; port->input.peer = (struct sockaddr *)&port->ret; port->input.peerlen = sizeof(port->ret); trans_insert_port(my_trans, &port->tport); if (community != COMM_INITIALIZE && (err = udp_init_port(&port->tport)) != SNMP_ERR_NOERROR) { udp_close_port(&port->tport); return (err); } *pp = port; return (SNMP_ERR_NOERROR); } /* * Close an SNMP port */ static void udp_close_port(struct tport *tp) { struct udp_port *port = (struct udp_port *)tp; snmpd_input_close(&port->input); trans_remove_port(tp); free(port); } /* * Send something */ static ssize_t udp_send(struct tport *tp, const u_char *buf, size_t len, const struct sockaddr *addr, size_t addrlen) { struct udp_port *p = (struct udp_port *)tp; struct cmsghdr *cmsg; struct msghdr msg; char cbuf[CMSG_SPACE(sizeof(struct in_addr))]; struct iovec iov; iov.iov_base = __DECONST(void*, buf); iov.iov_len = len; msg.msg_flags = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_name = __DECONST(void *, addr); msg.msg_namelen = addrlen; if (p->recvdstaddr) { msg.msg_control = cbuf; msg.msg_controllen = sizeof(cbuf); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = IPPROTO_IP; cmsg->cmsg_type = IP_SENDSRCADDR; cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr)); memcpy(CMSG_DATA(cmsg), &p->dstaddr, sizeof(struct in_addr)); } else { msg.msg_control = NULL; msg.msg_controllen = 0; } return (sendmsg(p->input.fd, &msg, 0)); } static void check_priv_dgram(struct port_input *pi, struct sockcred *cred) { /* process explicitly sends credentials */ if (cred) pi->priv = (cred->sc_euid == 0); else pi->priv = 0; } /* * Input from a datagram socket. * Each receive should return one datagram. */ static ssize_t udp_recv(struct tport *tp, struct port_input *pi) { u_char embuf[1000]; char cbuf[CMSG_SPACE(SOCKCREDSIZE(CMGROUP_MAX)) + CMSG_SPACE(sizeof(struct in_addr))]; struct udp_port *p = (struct udp_port *)tp; struct msghdr msg; struct iovec iov[1]; ssize_t len; struct cmsghdr *cmsg; struct sockcred *cred = NULL; if (pi->buf == NULL) { /* no buffer yet - allocate one */ if ((pi->buf = buf_alloc(0)) == NULL) { /* ups - could not get buffer. Read away input * and drop it */ (void)recvfrom(pi->fd, embuf, sizeof(embuf), 0, NULL, NULL); /* return error */ return (-1); } pi->buflen = buf_size(0); } /* try to get a message */ msg.msg_name = pi->peer; msg.msg_namelen = pi->peerlen; msg.msg_iov = iov; msg.msg_iovlen = 1; memset(cbuf, 0, sizeof(cbuf)); msg.msg_control = cbuf; msg.msg_controllen = sizeof(cbuf); msg.msg_flags = 0; iov[0].iov_base = pi->buf; iov[0].iov_len = pi->buflen; len = recvmsg(pi->fd, &msg, 0); if (len == -1 || len == 0) /* receive error */ return (-1); if (msg.msg_flags & MSG_TRUNC) { /* truncated - drop */ snmpd_stats.silentDrops++; snmpd_stats.inTooLong++; return (-1); } pi->length = (size_t)len; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_RECVDSTADDR) memcpy(&p->dstaddr, CMSG_DATA(cmsg), sizeof(struct in_addr)); if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDS) cred = (struct sockcred *)(void *)CMSG_DATA(cmsg); } if (pi->cred) check_priv_dgram(pi, cred); return (0); } /* * Port table */ int op_snmp_port(struct snmp_context *ctx, struct snmp_value *value, u_int sub, u_int iidx, enum snmp_op op) { asn_subid_t which = value->var.subs[sub-1]; struct udp_port *p; u_int8_t addr[4]; u_int32_t port; switch (op) { case SNMP_OP_GETNEXT: if ((p = (struct udp_port *)trans_next_port(my_trans, &value->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); index_append(&value->var, sub, &p->tport.index); break; case SNMP_OP_GET: if ((p = (struct udp_port *)trans_find_port(my_trans, &value->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); break; case SNMP_OP_SET: p = (struct udp_port *)trans_find_port(my_trans, &value->var, sub); ctx->scratch->int1 = (p != NULL); if (which != LEAF_begemotSnmpdPortStatus) abort(); if (!TRUTH_OK(value->v.integer)) return (SNMP_ERR_WRONG_VALUE); ctx->scratch->int2 = TRUTH_GET(value->v.integer); if (ctx->scratch->int2) { /* open an SNMP port */ if (p != NULL) /* already open - do nothing */ return (SNMP_ERR_NOERROR); if (index_decode(&value->var, sub, iidx, addr, &port)) return (SNMP_ERR_NO_CREATION); return (udp_open_port(addr, port, &p)); } else { /* close SNMP port - do in commit */ } return (SNMP_ERR_NOERROR); case SNMP_OP_ROLLBACK: p = (struct udp_port *)trans_find_port(my_trans, &value->var, sub); if (ctx->scratch->int1 == 0) { /* did not exist */ if (ctx->scratch->int2 == 1) { /* created */ if (p != NULL) udp_close_port(&p->tport); } } return (SNMP_ERR_NOERROR); case SNMP_OP_COMMIT: p = (struct udp_port *)trans_find_port(my_trans, &value->var, sub); if (ctx->scratch->int1 == 1) { /* did exist */ if (ctx->scratch->int2 == 0) { /* delete */ if (p != NULL) udp_close_port(&p->tport); } } return (SNMP_ERR_NOERROR); default: abort(); } /* * Come here to fetch the value */ switch (which) { case LEAF_begemotSnmpdPortStatus: value->v.integer = 1; break; default: abort(); } return (SNMP_ERR_NOERROR); }