Download raw body.
snmpd: Add storage process and fix snmpEngine{Boots,Time}
Hello tech@,
I've send out a diff addressing the issues below before, but this diff
fully supersedes that mail.
Right now snmpd(8) has a couple of issues with snmpEngine{Boots,Time}:
- snmpEngineBoots is always 0, where SNMP-FRAMEWORK-MIB defines it as
INTEGER (1..2147483647).
- snmpEngineTime is set to gettimeofday(2). This is not in line with
SNMP-FRAMEWORK-MIB, breaks in 2038, and is susceptible to clock drift.
To fix this I would like to introduce storage process, which handles
storing information inside /var/db/snmpd (by default). From the start I
would like to store the engine information in a generic way, so that
remote engine information can be stored at a later time when needed.
The storage directory can be specified by -S, mostly to support
predictable values when I come around to expand the regression tests.
The engine boots/time is stored on disk accompanied by CLOCK_REALTIME so
that it's able to survive reboots, even though it might be similarly
susceptible to clock drift as we are now. For now this is not an issue,
since for the local engine boots/time we only care about boots. As soon
as we start storing remote engines these values should be updated at
reasonable times (my current expectation is on engine boots increment,
clock drift, and on snmpd shutdown). Internally everything is based
around CLOCK_MONOTONIC.
There should be no migration problems, since a missing file starts with
a boots value of 1, which is the upside our boots violation.
To make sure that everything is loaded correctly before accepting
connections I've introduced snmpe_run() which gets run after everything
has been send over and replaces the core logic inside snmpe_init(),
which now doesn't have the right boots information available and could
cause issues if snmpv3 requests come in early.
One minor downside is that the storage process parses the MIB
directories, while not needing this. Since this will be done in
parallel with snmpe I don't think this is a major issue and can be
addressed at a later time.
OK?
martijn@
diff refs/heads/master refs/heads/snmpd/storage
commit - 641c5f58fb1d5788175703504ce33699f848d6b7
commit + fed9f8a7dddaf57352957c54979bd93506987e58
blob - ede7cb6a399c291586383ec1541696511abb177b
blob + bb733c2cfba4af13ea69644a4b3c1536d802802b
--- etc/mtree/4.4BSD.dist
+++ etc/mtree/4.4BSD.dist
@@ -579,6 +579,8 @@ var
..
pkg
..
+ snmpd uname=_snmpd gname=_snmpd mode=0700
+ ..
yubikey uname=root gname=auth mode=0770
..
..
blob - bdcbb5bc0bf9dc6dcc5ed621f8f26fb1eb1b30ce
blob + caf1bfb635bd04e44748b36ed9cae3661e015e07
--- usr.sbin/snmpd/Makefile
+++ usr.sbin/snmpd/Makefile
@@ -5,7 +5,7 @@ MAN= snmpd.8 snmpd.conf.5
SRCS= mib.y parse.y log.c snmpe.c application.c application_blocklist.c \
application_internal.c application_agentx.c ax.c \
engine.c \
- trap.c smi.c snmpd.c \
+ trap.c smi.c snmpd.c storage.c \
proc.c usm.c traphandler.c util.c
LDADD= -levent -lutil -lcrypto
blob - ac3fcb01ced659fa6557cf3fc5b5bc7ae522e029
blob + 29813c082cb4602ae038664165db84b49de6c724
--- usr.sbin/snmpd/application_internal.c
+++ usr.sbin/snmpd/application_internal.c
@@ -530,12 +530,14 @@ struct ber_element *
appl_internal_engine(struct ber_oid *oid)
{
if (ober_oid_cmp(&OID(MIB_snmpEngineID, 0), oid) == 0)
- return ober_add_nstring(NULL, snmpd_env->sc_engineid.value,
- snmpd_env->sc_engineid.length);
+ return ober_add_nstring(NULL, snmpd_env->sc_engine.id.value,
+ snmpd_env->sc_engine.id.length);
else if (ober_oid_cmp(&OID(MIB_snmpEngineBoots, 0), oid) == 0)
- return ober_add_integer(NULL, snmpd_env->sc_engine_boots);
+ return ober_add_integer(NULL,
+ engine_boots(&snmpd_env->sc_engine));
else if (ober_oid_cmp(&OID(MIB_snmpEngineTime, 0), oid) == 0)
- return ober_add_integer(NULL, snmpd_engine_time());
+ return ober_add_integer(NULL,
+ engine_time(&snmpd_env->sc_engine));
else if (ober_oid_cmp(&OID(MIB_snmpEngineMaxMsgSize, 0), oid) == 0)
return ober_add_integer(NULL, READ_BUF_SIZE);
return NULL;
blob - 43e0f3639f670679f79ce03dbc0d15dfb9ddf031
blob + 600dd80cee78915ecdf84684b19e27d333b554da
--- usr.sbin/snmpd/engine.c
+++ usr.sbin/snmpd/engine.c
@@ -15,12 +15,155 @@
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#include <sys/time.h>
+#include <stdint.h>
#include <string.h>
+#include "snmp.h"
#include "snmpd.h"
+static int engine_in_timewindow_local(struct engine *, int32_t, int32_t);
+static int engine_in_timewindow_peer(struct engine *, int32_t, int32_t);
+static void engine_boottime_update(struct engine *);
+
int
+engine_islocal(struct engine *engine)
+{
+ return engineid_cmp(&engine->id, &snmpd_env->sc_engine.id) == 0;
+}
+
+int
+engine_in_timewindow(struct engine *engine, int32_t boots, int32_t time)
+{
+ struct engine now = *engine;
+
+ engine_boottime_update(&now);
+ if (now.boots == INT32_MAX || boots == INT32_MAX)
+ return 0;
+
+ if (engine_islocal(engine))
+ return engine_in_timewindow_local(&now, boots, time);
+ else
+ return engine_in_timewindow_peer(&now, boots, time);
+}
+
+/*
+ * Only use current boots in local calculations; Rollovers happen every
+ * ~68 years and are not supported.
+ */
+static int
+engine_in_timewindow_local(struct engine *now, int32_t boots, int32_t time)
+{
+ int32_t min, max;
+
+ if (now->boots != boots)
+ return 0;
+ min = now->time < SNMP_MAX_TIMEWINDOW ?
+ 0 : now->time - SNMP_MAX_TIMEWINDOW;
+ max = INT32_MAX - now->time < SNMP_MAX_TIMEWINDOW ?
+ INT32_MAX : now->time + SNMP_MAX_TIMEWINDOW;
+
+ return time >= min && time <= max;
+}
+
+/*
+ * We don't control remote systems, so once established allow for both rollover
+ * and at max 1 restart per second. If a system gets a greater discrepency the
+ * admin needs to fix this manually.
+ */
+static int
+engine_in_timewindow_peer(struct engine *now, int32_t boots, int32_t time)
+{
+ struct engine min, max;
+ int32_t maxboots;
+
+ if (now->time < SNMP_MAX_TIMEWINDOW) {
+ if (now->boots == 1) {
+ min.boots = 1;
+ min.time = 0;
+ } else {
+ min.boots = now->boots - 1;
+ min.time = INT32_MAX -
+ (SNMP_MAX_TIMEWINDOW - now->time);
+ }
+ } else {
+ min.boots = now->boots;
+ min.time = now->time - SNMP_MAX_TIMEWINDOW;
+ }
+
+ if (INT32_MAX - now->time < SNMP_MAX_TIMEWINDOW) {
+ if (now->boots == INT32_MAX - 1) {
+ max.boots = INT32_MAX - 1;
+ max.time = INT32_MAX;
+ } else {
+ max.boots = now->boots + 1;
+ max.time = SNMP_MAX_TIMEWINDOW -
+ (INT32_MAX - now->time);
+ }
+ } else {
+ max.boots = now->boots;
+ max.time = now->time + SNMP_MAX_TIMEWINDOW;
+ }
+
+ if (boots < min.boots || (boots == min.boots && time < min.time))
+ return 0;
+
+ maxboots = INT32_MAX - 1 - max.boots < SNMP_MAX_TIMEWINDOW ?
+ INT32_MAX - 1 : max.boots + SNMP_MAX_TIMEWINDOW;
+ if (boots > maxboots ||
+ ((boots >= max.boots && boots <= maxboots) &&
+ time > max.time - (boots - max.boots)))
+ return 0;
+
+ return 1;
+}
+
+/* Calculate the current time in a bounded manner */
+static void
+engine_boottime_update(struct engine *engine)
+{
+ struct timespec now, diff;
+ int rollovers;
+
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ timespecsub(&now, &engine->measure, &diff);
+
+ rollovers = diff.tv_sec / INT32_MAX;
+ if (INT32_MAX - (diff.tv_sec % INT32_MAX) < engine->time) {
+ rollovers++;
+ engine->time = diff.tv_sec - (INT32_MAX - engine->time);
+ } else
+ engine->time += diff.tv_sec % INT32_MAX;
+
+ if (INT32_MAX - engine->boots < rollovers) {
+ engine->boots = INT32_MAX;
+ engine->time = 0;
+ return;
+ } else
+ engine->boots += rollovers;
+ engine->measure = now;
+}
+
+int32_t
+engine_boots(struct engine *engine)
+{
+ struct engine now = *engine;
+
+ engine_boottime_update(&now);
+ return now.boots;
+}
+
+int32_t
+engine_time(struct engine *engine)
+{
+ struct engine now = *engine;
+
+ engine_boottime_update(&now);
+ return now.time;
+}
+
+int
engineid_cmp(struct engineid *e1, struct engineid *e2)
{
size_t min;
blob - 2fa55833ec48e2fe22ebfa23ef272c972dd77786
blob + 8081d1899ecf3cb6f5244565f49969013b16a575
--- usr.sbin/snmpd/parse.y
+++ usr.sbin/snmpd/parse.y
@@ -308,11 +308,11 @@ main : LISTEN ON listen_udptcp
axm_entry);
}
| engineid_local {
- if (conf->sc_engineid.length != 0) {
+ if (conf->sc_engine.id.length != 0) {
yyerror("Redefinition of engineid");
YYERROR;
}
- conf->sc_engineid = $1;
+ conf->sc_engine.id = $1;
}
| READONLY COMMUNITY STRING {
if (strlcpy(conf->sc_rdcommunity, $3,
@@ -1874,18 +1874,19 @@ parse_config(const char *filename, u_int flags)
/* Must be identical to enginefmt_local:HOSTHASH */
- if (conf->sc_engineid.length == 0) {
+ if (conf->sc_engine.id.length == 0) {
if (gethostname(hostname, sizeof(hostname)) == -1)
fatal("gethostname");
- memcpy(conf->sc_engineid.value, &npen, sizeof(npen));
- conf->sc_engineid.length += sizeof(npen);
- conf->sc_engineid.value[conf->sc_engineid.length++] =
+ memcpy(conf->sc_engine.id.value, &npen, sizeof(npen));
+ conf->sc_engine.id.length += sizeof(npen);
+ conf->sc_engine.id.value[conf->sc_engine.id.length++] =
SNMP_ENGINEID_FMT_HH;
- memcpy(conf->sc_engineid.value + conf->sc_engineid.length,
+ memcpy(conf->sc_engine.id.value + conf->sc_engine.id.length,
SHA256(hostname, strlen(hostname), sha256),
- sizeof(conf->sc_engineid.value) - conf->sc_engineid.length);
- conf->sc_engineid.length = sizeof(conf->sc_engineid.value);
- conf->sc_engineid.value[0] |= SNMP_ENGINEID_NEW;
+ sizeof(conf->sc_engine.id.value) -
+ conf->sc_engine.id.length);
+ conf->sc_engine.id.length = sizeof(conf->sc_engine.id.value);
+ conf->sc_engine.id.value[0] |= SNMP_ENGINEID_NEW;
}
/* Setup default listen addresses */
blob - 8518a72e08ce711ea82c0fed0217989006afa9f9
blob + d6b9760a78b8c9bdf38b948acef514fc30b375e3
--- usr.sbin/snmpd/snmpd.8
+++ usr.sbin/snmpd/snmpd.8
@@ -25,6 +25,7 @@
.Op Fl dNnv
.Op Fl D Ar macro Ns = Ns Ar value
.Op Fl f Ar file
+.Op Fl S Ar path
.Sh DESCRIPTION
.Nm
is a daemon which implements the SNMP protocol.
@@ -53,6 +54,12 @@ Show numeric OID values instead of their symbolic name
.It Fl n
Configtest mode.
Only check the configuration file for validity.
+.It Fl S Ar path
+Use
+.Ar path
+as the location for storing dynamic information,
+instead of the default
+.Pa /var/db/snmpd .
.It Fl v
Produce more verbose output.
.El
blob - 0bb5ff604617e1184c51452cf42daa7ec3ec3159
blob + 0b860ed1989f90bb34924f6b727e66d5246a4aa6
--- usr.sbin/snmpd/snmpd.c
+++ usr.sbin/snmpd/snmpd.c
@@ -44,6 +44,7 @@ struct snmpd *snmpd_env;
static struct privsep_proc procs[] = {
{ "snmpe", PROC_SNMPE, snmpd_dispatch_snmpe, snmpe, snmpe_shutdown },
+ { "storage", PROC_STORAGE, NULL, storage, storage_shutdown, SNMPD_STORAGE },
};
void
@@ -128,6 +129,7 @@ int
main(int argc, char *argv[])
{
int c;
+ size_t i;
struct snmpd *env;
int debug = 0, verbose = 0;
u_int flags = 0;
@@ -144,7 +146,7 @@ main(int argc, char *argv[])
/* log to stderr until daemonized */
log_init(1, LOG_DAEMON);
- while ((c = getopt(argc, argv, "dD:nNf:I:P:v")) != -1) {
+ while ((c = getopt(argc, argv, "dD:nNf:I:P:S:v")) != -1) {
switch (c) {
case 'd':
debug++;
@@ -176,6 +178,15 @@ main(int argc, char *argv[])
if (proc_id == PROC_MAX)
fatalx("invalid process name");
break;
+ case 'S':
+ if (optarg[0] != '/')
+ fatalx("%s: must be absolute path", optarg);
+ for (i = 0; i < nitems(procs); i++) {
+ if (procs[i].p_id == PROC_STORAGE) {
+ procs[i].p_chroot = optarg;
+ break;
+ }
+ }
case 'v':
verbose++;
flags |= SNMPD_F_VERBOSE;
@@ -216,8 +227,6 @@ main(int argc, char *argv[])
log_init(debug, LOG_DAEMON);
log_setverbose(verbose);
- env->sc_engine_boots = 0;
-
proc_init(ps, procs, nitems(procs), debug, argc0, argv0, proc_id);
log_procinit("parent");
@@ -317,24 +326,6 @@ snmpd_socket_af(struct sockaddr_storage *ss, int type)
return fd;
}
-u_long
-snmpd_engine_time(void)
-{
- struct timeval now;
-
- /*
- * snmpEngineBoots should be stored in a non-volatile storage.
- * snmpEngineTime is the number of seconds since snmpEngineBoots
- * was last incremented. We don't rely on non-volatile storage.
- * snmpEngineBoots is set to zero and snmpEngineTime to the system
- * clock. Hence, the tuple (snmpEngineBoots, snmpEngineTime) is
- * still unique and protects us against replay attacks. It only
- * 'expires' a little bit sooner than the RFC3414 method.
- */
- gettimeofday(&now, NULL);
- return now.tv_sec;
-}
-
void
snmpd_backend(struct snmpd *env)
{
blob - cc3fcbd3ce65a70ce11898b33a58a0243803d392
blob + 3bae9e1f385349fc2bd7889b152fcb3a4686e25d
--- usr.sbin/snmpd/snmpd.h
+++ usr.sbin/snmpd/snmpd.h
@@ -48,6 +48,7 @@
#define CONF_FILE "/etc/snmpd.conf"
#define SNMPD_BACKEND "/usr/libexec/snmpd"
+#define SNMPD_STORAGE "/var/db/snmpd/"
#define SNMPD_USER "_snmpd"
#define SNMP_PORT "161"
#define SNMPTRAP_PORT "162"
@@ -92,7 +93,9 @@ enum imsg_type {
IMSG_CTL_PROCREADY,
IMSG_TRAP_EXEC,
IMSG_AX_FD,
- IMSG_SENDFD_DONE
+ IMSG_SENDFD_DONE,
+ IMSG_STORAGE_ENGINE,
+ IMSG_STORAGE_DONE
};
struct imsgev {
@@ -114,6 +117,7 @@ struct imsgev {
enum privsep_procid {
PROC_PARENT, /* Parent process and application interface */
PROC_SNMPE, /* SNMP engine */
+ PROC_STORAGE,
PROC_MAX
};
@@ -192,6 +196,14 @@ struct engineid {
size_t length;
};
+struct engine {
+ struct engineid id;
+ int32_t boots;
+ int32_t time;
+ struct timespec measure;
+ int msg_max_size;
+};
+
#define MSG_HAS_AUTH(m) (((m)->sm_flags & SNMP_MSGFLAG_AUTH) != 0)
#define MSG_HAS_PRIV(m) (((m)->sm_flags & SNMP_MSGFLAG_PRIV) != 0)
#define MSG_SECLEVEL(m) ((m)->sm_flags & SNMP_MSGFLAG_SECMASK)
@@ -413,13 +425,12 @@ struct snmpd {
const char *sc_confpath;
struct addresslist sc_addresses;
struct axmasterlist sc_agentx_masters;
- u_int32_t sc_engine_boots;
char sc_rdcommunity[SNMPD_MAXCOMMUNITYLEN];
char sc_rwcommunity[SNMPD_MAXCOMMUNITYLEN];
char sc_trcommunity[SNMPD_MAXCOMMUNITYLEN];
- struct engineid sc_engineid;
+ struct engine sc_engine;
struct snmp_stats sc_stats;
struct snmp_system sc_system;
@@ -456,6 +467,10 @@ struct snmpd *parse_config(const char *, u_int);
int cmdline_symset(char *);
/* engine.c */
+int engine_islocal(struct engine *);
+int engine_in_timewindow(struct engine *, int32_t, int32_t);
+int32_t engine_boots(struct engine *);
+int32_t engine_time(struct engine *);
int engineid_cmp(struct engineid *, struct engineid *);
/* snmpe.c */
@@ -479,8 +494,11 @@ void smi_debug_elements(struct ber_element *);
/* snmpd.c */
int snmpd_socket_af(struct sockaddr_storage *, int);
-u_long snmpd_engine_time(void);
+/* storage.c */
+void storage(struct privsep *, struct privsep_proc *);
+void storage_shutdown(void);
+
/* usm.c */
void usm_generate_keys(void);
struct usmuser *usm_newuser(char *name, const char **);
blob - 2d1c6893eed055300ee2bb41bbb86c7791f45a93
blob + 93c715c29025bc0d66b34ac8ed20fa1ecd41fd08
--- usr.sbin/snmpd/snmpe.c
+++ usr.sbin/snmpd/snmpe.c
@@ -44,7 +44,9 @@
#include "mib.h"
void snmpe_init(struct privsep *, struct privsep_proc *, void *);
+void snmpe_run(void);
int snmpe_dispatch_parent(int, struct privsep_proc *, struct imsg *);
+int snmpe_dispatch_storage(int, struct privsep_proc *, struct imsg *);
int snmpe_parse(struct snmp_message *);
void snmpe_tryparse(int, struct snmp_message *);
int snmpe_parsevarbinds(struct snmp_message *);
@@ -62,9 +64,15 @@ static const struct timeval snmpe_tcp_timeout = { 10,
struct snmp_messages snmp_messages = RB_INITIALIZER(&snmp_messages);
static struct privsep_proc procs[] = {
- { "parent", PROC_PARENT, snmpe_dispatch_parent }
+ { "parent", PROC_PARENT, snmpe_dispatch_parent },
+ { "storage", PROC_STORAGE, snmpe_dispatch_storage }
};
+static int ready = 0;
+#define SNMPE_READY_AGENTXFD (1 << 0)
+#define SNMPE_READY_STORAGE (1 << 1)
+#define SNMPE_READY_ALL (SNMPE_READY_AGENTXFD | SNMPE_READY_STORAGE)
+
void
snmpe(struct privsep *ps, struct privsep_proc *p)
{
@@ -87,14 +95,30 @@ snmpe(struct privsep *ps, struct privsep_proc *p)
void
snmpe_init(struct privsep *ps, struct privsep_proc *p, void *arg)
{
- struct snmpd *env = ps->ps_env;
- struct address *h;
-
usm_generate_keys();
appl_init();
- /* listen for incoming SNMP UDP/TCP messages */
- TAILQ_FOREACH(h, &env->sc_addresses, entry) {
+ /* no filesystem visibility */
+ if (unveil("/", "") == -1)
+ fatal("unveil /");
+ if (pledge("stdio recvfd inet unix", NULL) == -1)
+ fatal("pledge");
+}
+
+void
+snmpe_run(void)
+{
+ struct address *h;
+
+ if (ready != SNMPE_READY_ALL)
+ return;
+ /*
+ * listen for incoming SNMP UDP/TCP messages
+ *
+ * This is delayed to ensure that the engine information is properly
+ * loaded
+ */
+ TAILQ_FOREACH(h, &snmpd_env->sc_addresses, entry) {
if (h->type == SOCK_STREAM) {
if (listen(h->fd, 5) < 0)
fatalx("snmpe: failed to listen on socket");
@@ -107,14 +131,16 @@ snmpe_init(struct privsep *ps, struct privsep_proc *p,
event_add(&h->ev, NULL);
}
- /* no filesystem visibility */
- if (unveil("/", "") == -1)
- fatal("unveil /");
- if (pledge("stdio recvfd inet unix", NULL) == -1)
- fatal("pledge");
-
- log_info("snmpe %s: ready",
- tohexstr(env->sc_engineid.value, env->sc_engineid.length));
+ snmpd_env->sc_engine.boots++;
+ snmpd_env->sc_engine.time = 0;
+ snmpd_env->sc_engine.msg_max_size = READ_BUF_SIZE;
+ clock_gettime(CLOCK_MONOTONIC, &snmpd_env->sc_engine.measure);
+ if (proc_compose(&snmpd_env->sc_ps, PROC_STORAGE, IMSG_STORAGE_ENGINE,
+ &snmpd_env->sc_engine, sizeof(snmpd_env->sc_engine)) == -1)
+ fatal("proc_compose");
+
+ log_info("snmpe %s, boots %d: ready", tohexstr(snmpd_env->sc_engine.id.value,
+ snmpd_env->sc_engine.id.length), snmpd_env->sc_engine.boots);
trap_init();
}
@@ -142,6 +168,8 @@ snmpe_dispatch_parent(int fd, struct privsep_proc *p,
case IMSG_SENDFD_DONE:
if (pledge("stdio inet unix", NULL) == -1)
fatal("pledge");
+ ready |= SNMPE_READY_AGENTXFD;
+ snmpe_run();
return 0;
default:
return -1;
@@ -149,6 +177,30 @@ snmpe_dispatch_parent(int fd, struct privsep_proc *p,
}
int
+snmpe_dispatch_storage(int fd, struct privsep_proc *p, struct imsg *imsg)
+{
+ struct engine engine;
+
+ switch (imsg_get_type(imsg)) {
+ case IMSG_STORAGE_ENGINE:
+ if (imsg_get_data(imsg, &engine, sizeof(engine)) == -1)
+ fatal("imsg_get_data");
+
+ /* Ignore other engines for now */
+ if (engineid_cmp(&engine.id, &snmpd_env->sc_engine.id) == 0)
+ snmpd_env->sc_engine = engine;
+
+ return 0;
+ case IMSG_STORAGE_DONE:
+ ready |= SNMPE_READY_STORAGE;
+ snmpe_run();
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+int
snmpe_bind(struct address *addr)
{
char buf[512];
@@ -855,7 +907,8 @@ snmpe_encode(struct snmp_message *msg)
pdu = epdu = ober_add_sequence(NULL);
if (msg->sm_version == SNMP_V3) {
if ((epdu = ober_printf_elements(epdu, "xs{",
- snmpd_env->sc_engineid.value, snmpd_env->sc_engineid.length,
+ snmpd_env->sc_engine.id.value,
+ snmpd_env->sc_engine.id.length,
msg->sm_ctxname)) == NULL) {
ober_free_elements(pdu);
return -1;
blob - dea5474c3a9dfe5ae3e69cedcfa45335a51c930f
blob + 11812daee48bcd3ef1f3d7b4e481c7c8278a6cec
--- usr.sbin/snmpd/trap.c
+++ usr.sbin/snmpd/trap.c
@@ -107,9 +107,9 @@ trap_send(struct ber_oid *oid, struct ber_element *elm
msg->sm_flags = tr->ta_seclevel != -1 ?
tr->ta_seclevel : snmpd_env->sc_min_seclevel;
msg->sm_secmodel = SNMP_SEC_USM;
- msg->sm_engine_time = snmpd_engine_time();
- msg->sm_engine_boots = snmpd_env->sc_engine_boots;
- msg->sm_ctxengineid = snmpd_env->sc_engineid;
+ msg->sm_engine_time = engine_time(&snmpd_env->sc_engine);
+ msg->sm_engine_boots = engine_boots(&snmpd_env->sc_engine);
+ msg->sm_ctxengineid = snmpd_env->sc_engine.id;
(void)strlcpy(msg->sm_username, tr->ta_usmusername,
sizeof(msg->sm_username));
msg->sm_user = tr->ta_usmuser;
blob - /dev/null
blob + 241fda76ae7e89385f4df05487e686486eb614d3 (mode 644)
--- /dev/null
+++ usr.sbin/snmpd/storage.c
@@ -0,0 +1,219 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2026 Martijn van Duren <martijn@openbsd.org>
+ *
+ * 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 <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <dirent.h>
+#include <errno.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "snmpd.h"
+
+void storage_init(struct privsep *, struct privsep_proc *, void *);
+int storage_dispatch_parent(int, struct privsep_proc *, struct imsg *);
+int storage_dispatch_snmpe(int, struct privsep_proc *, struct imsg *);
+int storage_mkdir(const char *);
+FILE *storage_open(const char *, const char *);
+void storage_store_engine(struct engine *);
+void storage_load_engines(void);
+
+static struct privsep_proc procs[] = {
+ { "parent", PROC_PARENT, storage_dispatch_parent },
+ { "snmpe", PROC_SNMPE, storage_dispatch_snmpe }
+};
+
+void
+storage(struct privsep *ps, struct privsep_proc *p)
+{
+ proc_run(ps, p, procs, nitems(procs), storage_init, NULL);
+}
+
+void
+storage_init(struct privsep *ps, struct privsep_proc *s, void *arg)
+{
+ if (unveil("/", "rwc") == -1)
+ fatal("unveil %s", SNMPD_STORAGE);
+ if (pledge("stdio rpath wpath cpath", NULL) == -1)
+ fatal("pledge");
+
+ storage_load_engines();
+ if (proc_compose(&snmpd_env->sc_ps, PROC_SNMPE,
+ IMSG_STORAGE_DONE, NULL, 0) == -1)
+ fatal("proc_compose");
+}
+
+void
+storage_shutdown(void)
+{
+}
+
+int
+storage_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
+{
+ return -1;
+}
+
+int
+storage_dispatch_snmpe(int fd, struct privsep_proc *p, struct imsg *imsg)
+{
+ struct engine engine;
+
+ switch (imsg_get_type(imsg)) {
+ case IMSG_STORAGE_ENGINE:
+ if (imsg_get_data(imsg, &engine, sizeof(engine)) == -1)
+ fatal("imsg_get_data");
+ storage_store_engine(&engine);
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+int
+storage_mkdir(const char *path)
+{
+ char copy[PATH_MAX], *parent;
+
+ if (mkdir(path, 0755) == -1) {
+ if (errno == ENOENT) {
+ strlcpy(copy, path, sizeof(copy));
+ parent = dirname(copy);
+ strlcpy(copy, parent, sizeof(copy));
+ if (storage_mkdir(copy) == -1)
+ return -1;
+ return mkdir(path, 0755);
+ }
+ return -1;
+ }
+ return 0;
+}
+
+FILE *
+storage_open(const char *path, const char *mode)
+{
+ FILE *f;
+ char copy[PATH_MAX], *parent;
+
+ if ((f = fopen(path, mode)) == NULL) {
+ if (errno == ENOENT) {
+ strlcpy(copy, path, sizeof(copy));
+ parent = dirname(copy);
+ strlcpy(copy, parent, sizeof(copy));
+ if (storage_mkdir(copy) == -1)
+ return NULL;
+ return fopen(path, mode);
+ }
+ return NULL;
+ }
+ return f;
+}
+
+void
+storage_store_engine(struct engine *engine)
+{
+ FILE *info;
+ char final[PATH_MAX], tmp[PATH_MAX];
+ struct timespec mnow, rnow, rmeasure, diff;
+
+ snprintf(tmp, sizeof(tmp), "/engines/%s/.info",
+ tohexstr(engine->id.value, engine->id.length));
+ snprintf(final, sizeof(final), "/engines/%s/info",
+ tohexstr(engine->id.value, engine->id.length));
+ if ((info = storage_open(tmp, "w")) == NULL)
+ fatal("open %s", tmp);
+
+ /*
+ * Store the REALTIME.
+ * It can drift harder than MONOTONIC, but it survives reboots.
+ */
+ clock_gettime(CLOCK_MONOTONIC, &mnow);
+ clock_gettime(CLOCK_REALTIME, &rnow);
+ timespecsub(&mnow, &engine->measure, &diff);
+ timespecsub(&rnow, &diff, &rmeasure);
+
+ fprintf(info, "%lld.%09ld\n%d\n%d\n%d\n",
+ rmeasure.tv_sec, rmeasure.tv_nsec,
+ engine->boots, engine->time, engine->msg_max_size);
+ if (fflush(info) == EOF)
+ fatal("write %s", tmp);
+ if (rename(tmp, final) == -1)
+ fatal("rename %s -> %s", tmp, final);
+}
+
+void
+storage_load_engines(void)
+{
+ DIR *engines;
+ struct dirent *engineid;
+ struct engine engine;
+ struct timespec mnow, rnow, rmeasure, diff;
+ char path[PATH_MAX];
+ FILE *info;
+
+ if ((engines = opendir("/engines")) == NULL) {
+ if (errno == ENOENT)
+ return;
+ fatal("failed to open %s", SNMPD_STORAGE);
+ }
+
+ errno = 0;
+ clock_gettime(CLOCK_MONOTONIC, &mnow);
+ clock_gettime(CLOCK_REALTIME, &rnow);
+ while ((engineid = readdir(engines)) != NULL) {
+ engine = (struct engine){};
+ engine.id.length = strlen(engineid->d_name) / 2;
+ if (engine.id.length > sizeof(engine.id.value))
+ continue;
+ if (fromhexstr(engine.id.value, engineid->d_name,
+ strlen(engineid->d_name)) == NULL)
+ continue;
+ snprintf(path, sizeof(path), "/engines/%s/info",
+ engineid->d_name);
+
+ if ((info = fopen(path, "r")) == NULL) {
+ log_warn("open %s%s", SNMPD_STORAGE, path);
+ continue;
+ }
+ if (fscanf(info, "%lld.%09ld\n%d\n%d\n%d\n",
+ &rmeasure.tv_sec, &rmeasure.tv_nsec, &engine.boots,
+ &engine.time, &engine.msg_max_size) != 5 ||
+ fgetc(info) != EOF || !feof(info)) {
+ log_warnx("invalid content %s%s", SNMPD_STORAGE, path);
+ fclose(info);
+ continue;
+ }
+ fclose(info);
+
+ timespecsub(&rnow, &rmeasure, &diff);
+ timespecsub(&mnow, &diff, &engine.measure);
+
+ if (proc_compose(&snmpd_env->sc_ps, PROC_SNMPE,
+ IMSG_STORAGE_ENGINE, &engine, sizeof(engine)) == -1)
+ fatal("proc_compose");
+ }
+ if (errno != 0)
+ fatal("failed to read %s", SNMPD_STORAGE);
+}
blob - 6201b7fc97274aa237a2adf1bf9d6a266c1aed0c
blob + 33bb0f7a06e77123b55127bb10c48ba87be596cf
--- usr.sbin/snmpd/usm.c
+++ usr.sbin/snmpd/usm.c
@@ -273,7 +273,6 @@ usm_decode(struct snmp_message *msg, struct ber_elemen
char *engineidv;
char *user;
char *digest, *salt;
- u_long now;
long long engine_boots, engine_time;
bzero(&ber, sizeof(ber));
@@ -320,7 +319,7 @@ usm_decode(struct snmp_message *msg, struct ber_elemen
}
memcpy(engineid.value, engineidv, engineid.length);
- if (engineid_cmp(&snmpd_env->sc_engineid, &engineid) != 0) {
+ if (engineid_cmp(&snmpd_env->sc_engine.id, &engineid) != 0) {
*errp = "unknown engine id";
msg->sm_usmerr = OIDVAL_usmErrEngineId;
stats->snmp_usmnosuchengine++;
@@ -374,10 +373,8 @@ usm_decode(struct snmp_message *msg, struct ber_elemen
}
if (MSG_HAS_AUTH(msg)) {
- now = snmpd_engine_time();
- if (engine_boots != snmpd_env->sc_engine_boots ||
- engine_time < (long long)(now - SNMP_MAX_TIMEWINDOW) ||
- engine_time > (long long)(now + SNMP_MAX_TIMEWINDOW)) {
+ if (!engine_in_timewindow(&snmpd_env->sc_engine,
+ engine_boots, engine_time)) {
*errp = "out of time window";
msg->sm_usmerr = OIDVAL_usmErrTimeWindow;
stats->snmp_usmtimewindow++;
@@ -434,10 +431,10 @@ usm_encode(struct snmp_message *msg, struct ber_elemen
} else
saltlen = 0;
- msg->sm_engine_boots = (u_int32_t)snmpd_env->sc_engine_boots;
- msg->sm_engine_time = (u_int32_t)snmpd_engine_time();
+ msg->sm_engine_boots = (u_int32_t)engine_boots(&snmpd_env->sc_engine);
+ msg->sm_engine_time = (u_int32_t)engine_time(&snmpd_env->sc_engine);
if ((a = ober_printf_elements(usm, "xdds",
- snmpd_env->sc_engineid.value, snmpd_env->sc_engineid.length,
+ snmpd_env->sc_engine.id.value, snmpd_env->sc_engine.id.length,
msg->sm_engine_boots, msg->sm_engine_time,
msg->sm_username)) == NULL)
goto done;
@@ -718,16 +715,16 @@ usm_passwd2key(const EVP_MD *md, char *passwd, int *ma
/* Localize the key */
#ifdef DEBUG
- assert(snmpd_env->sc_engineid_len <= SNMPD_MAXENGINEIDLEN);
+ assert(snmpd_env->sc_engine.id.length <= SNMPD_MAXENGINEIDLEN);
#endif
memcpy(pwbuf, keybuf, dlen);
- memcpy(pwbuf + dlen, snmpd_env->sc_engineid.value,
- snmpd_env->sc_engineid.length);
- memcpy(pwbuf + dlen + snmpd_env->sc_engineid.length, keybuf, dlen);
+ memcpy(pwbuf + dlen, snmpd_env->sc_engine.id.value,
+ snmpd_env->sc_engine.id.length);
+ memcpy(pwbuf + dlen + snmpd_env->sc_engine.id.length, keybuf, dlen);
if (!EVP_DigestInit_ex(ctx, md, NULL) ||
!EVP_DigestUpdate(ctx, pwbuf,
- 2 * dlen + snmpd_env->sc_engineid.length) ||
+ 2 * dlen + snmpd_env->sc_engine.id.length) ||
!EVP_DigestFinal_ex(ctx, keybuf, &dlen)) {
EVP_MD_CTX_free(ctx);
return NULL;
snmpd: Add storage process and fix snmpEngine{Boots,Time}