/* -*- mode: c; c-file-style: "openbsd" -*- */
/*
* Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
*
* Permission to use, copy, modify, and/or 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.
*/
/* Some of the code here (agent_priv_unix_*) has been adapted from code from
* Net-SNMP project (snmplib/snmpUnixDomain.c). Net-SNMP project is licensed
* using BSD and BSD-like licenses. I don't know the exact license of the file
* snmplib/snmpUnixDomain.c. */
#include "lldpd.h"
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#ifdef ENABLE_PRIVSEP
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include <net-snmp/agent/snmp_vars.h>
#include <net-snmp/library/snmpUnixDomain.h>
static oid netsnmp_unix[] = { TRANSPORT_DOMAIN_LOCAL };
static netsnmp_tdomain unixDomain;
static char *
agent_priv_unix_fmtaddr(netsnmp_transport *t, void *data, int len)
{
/* We don't bother to implement the full function */
return strdup("Local Unix socket with privilege separation: unknown");
}
static int
agent_priv_unix_recv(netsnmp_transport *t, void *buf, int size,
void **opaque, int *olength)
{
int rc = -1;
socklen_t tolen = sizeof(struct sockaddr_un);
struct sockaddr *to = NULL;
if (t == NULL || t->sock < 0)
goto recv_error;
to = (struct sockaddr *)calloc(1, sizeof(struct sockaddr_un));
if (to == NULL)
goto recv_error;
if (getsockname(t->sock, to, &tolen) != 0)
goto recv_error;
while (rc < 0) {
rc = recv(t->sock, buf, size, 0);
/* TODO: handle the (unlikely) case where we get EAGAIN or EWOULDBLOCK */
if (rc < 0 && errno != EINTR) {
log_warn("snmp", "unable to receive from fd %d",
t->sock);
goto recv_error;
}
}
*opaque = (void*)to;
*olength = sizeof(struct sockaddr_un);
return rc;
recv_error:
free(to);
*opaque = NULL;
*olength = 0;
return -1;
}
#define AGENT_WRITE_TIMEOUT 2000
static int
agent_priv_unix_send(netsnmp_transport *t, void *buf, int size,
void **opaque, int *olength)
{
int rc = -1;
if (t != NULL && t->sock >= 0) {
struct pollfd sagentx = {
.fd = t->sock,
.events = POLLOUT | POLLERR | POLLHUP
};
while (rc < 0) {
rc = poll(&sagentx, 1, AGENT_WRITE_TIMEOUT);
if (rc == 0) {
log_warnx("snmp",
"timeout while communicating with the master agent");
rc = -1;
break;
}
if (rc > 0) {
/* We can either write or have an error somewhere */
rc = send(t->sock, buf, size, 0);
if (rc < 0) {
if (errno == EAGAIN ||
errno == EWOULDBLOCK ||
errno == EINTR)
/* Let's retry */
continue;
log_warn("snmp",
"error while sending to master agent");
break;
}
} else {
if (errno != EINTR) {
log_warn("snmp",
"error while attempting to send to master agent");
break;
}
continue;
}
}
}
return rc;
}
static int
agent_priv_unix_close(netsnmp_transport *t)
{
int rc = 0;
if (t->sock >= 0) {
rc = close(t->sock);
t->sock = -1;
return rc;
}
return -1;
}
static int
agent_priv_unix_accept(netsnmp_transport *t)
{
log_warnx("snmp", "should not have been called");
return -1;
}
static netsnmp_transport *
agent_priv_unix_transport(const char *string, int len, int local)
{
struct sockaddr_un addr = {
.sun_family = AF_UNIX
};
netsnmp_transport *t = NULL;
if (local) {
log_warnx("snmp", "should not have been called for local transport");
return NULL;
}
if (!string)
return NULL;
if (len >= sizeof(addr.sun_path) ||
strlcpy(addr.sun_path, string, sizeof(addr.sun_path)) >= sizeof(addr.sun_path)) {
log_warnx("snmp", "path too long for Unix domain transport");
return NULL;
}
if ((t = (netsnmp_transport *)
calloc(1, sizeof(netsnmp_transport))) == NULL)
return NULL;
t->domain = netsnmp_unix;
t->domain_length =
sizeof(netsnmp_unix) / sizeof(netsnmp_unix[0]);
if ((t->sock = priv_snmp_socket(&addr)) < 0) {
netsnmp_transport_free(t);
return NULL;
}
t->flags = NETSNMP_TRANSPORT_FLAG_STREAM;
if ((t->remote = (u_char *)
calloc(1, strlen(addr.sun_path) + 1)) == NULL) {
agent_priv_unix_close(t);
netsnmp_transport_free(t);
return NULL;
}
memcpy(t->remote, addr.sun_path, strlen(addr.sun_path));
t->remote_length = strlen(addr.sun_path);
t->msgMaxSize = 0x7fffffff;
t->f_recv = agent_priv_unix_recv;
t->f_send = agent_priv_unix_send;
t->f_close = agent_priv_unix_close;
t->f_accept = agent_priv_unix_accept;
t->f_fmtaddr = agent_priv_unix_fmtaddr;
return t;
}
netsnmp_transport *
#if !HAVE_NETSNMP_TDOMAIN_F_CREATE_FROM_TSTRING_NEW
agent_priv_unix_create_tstring(const char *string, int local)
#else
agent_priv_unix_create_tstring(const char *string, int local, const char *default_target)
#endif
{
#if HAVE_NETSNMP_TDOMAIN_F_CREATE_FROM_TSTRING_NEW
if ((!string || *string == '\0') && default_target &&
*default_target != '\0') {
string = default_target;
}
#endif
if (!string)
return NULL;
return agent_priv_unix_transport(string, strlen(string), local);
}
static netsnmp_transport *
agent_priv_unix_create_ostring(const u_char * o, size_t o_len, int local)
{
return agent_priv_unix_transport((char *)o, o_len, local);
}
void
agent_priv_register_domain()
{
unixDomain.name = netsnmp_unix;
unixDomain.name_length = sizeof(netsnmp_unix) / sizeof(oid);
unixDomain.prefix = (const char**)calloc(2, sizeof(char *));
unixDomain.prefix[0] = "unix";
#if !HAVE_NETSNMP_TDOMAIN_F_CREATE_FROM_TSTRING_NEW
unixDomain.f_create_from_tstring = agent_priv_unix_create_tstring;
#else
unixDomain.f_create_from_tstring_new = agent_priv_unix_create_tstring;
#endif
unixDomain.f_create_from_ostring = agent_priv_unix_create_ostring;
netsnmp_tdomain_register(&unixDomain);
}
#endif