xref: /openbsd-src/usr.sbin/snmpd/application_agentx.c (revision 1ad61ae0a79a724d2d3ec69e69c8e1d1ff6b53a0)
1 /*	$OpenBSD: application_agentx.c,v 1.14 2023/11/06 11:04:41 martijn Exp $ */
2 /*
3  * Copyright (c) 2022 Martijn van Duren <martijn@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/queue.h>
19 #include <sys/socket.h>
20 #include <sys/stat.h>
21 #include <sys/time.h>
22 #include <sys/types.h>
23 #include <sys/un.h>
24 
25 #include <errno.h>
26 #include <event.h>
27 #include <inttypes.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <unistd.h>
32 
33 #include "application.h"
34 #include "ax.h"
35 #include "log.h"
36 #include "smi.h"
37 #include "snmp.h"
38 #include "snmpd.h"
39 
40 #define AGENTX_DEFAULTTIMEOUT 5
41 
42 struct appl_agentx_connection {
43 	uint32_t conn_id;
44 	/*
45 	 * A backend has several overruling properties:
46 	 * - If it exits, snmpd crashes
47 	 * - All registrations are priority 1
48 	 * - All registrations own the subtree.
49 	 */
50 	int conn_backend;
51 	struct ax *conn_ax;
52 	struct event conn_rev;
53 	struct event conn_wev;
54 
55 	TAILQ_HEAD(, appl_agentx_session) conn_sessions;
56 	RB_ENTRY(appl_agentx_connection) conn_entry;
57 };
58 
59 struct appl_agentx_session {
60 	uint32_t sess_id;
61 	struct appl_agentx_connection *sess_conn;
62 	/*
63 	 * RFC 2741 section 7.1.1:
64 	 * All subsequent AgentX protocol operations initiated by the master
65 	 * agent for this session must use this byte ordering and set this bit
66 	 * accordingly.
67 	 */
68 	enum ax_byte_order sess_byteorder;
69 	uint8_t sess_timeout;
70 	struct ax_oid sess_oid;
71 	struct ax_ostring sess_descr;
72 	struct appl_backend sess_backend;
73 
74 	RB_ENTRY(appl_agentx_session) sess_entry;
75 	TAILQ_ENTRY(appl_agentx_session) sess_conn_entry;
76 };
77 
78 void appl_agentx_listen(struct agentx_master *);
79 void appl_agentx_accept(int, short, void *);
80 void appl_agentx_free(struct appl_agentx_connection *, enum appl_close_reason);
81 void appl_agentx_recv(int, short, void *);
82 void appl_agentx_open(struct appl_agentx_connection *, struct ax_pdu *);
83 void appl_agentx_close(struct appl_agentx_session *, struct ax_pdu *);
84 void appl_agentx_forceclose(struct appl_backend *, enum appl_close_reason);
85 void appl_agentx_session_free(struct appl_agentx_session *);
86 void appl_agentx_register(struct appl_agentx_session *, struct ax_pdu *);
87 void appl_agentx_unregister(struct appl_agentx_session *, struct ax_pdu *);
88 void appl_agentx_get(struct appl_backend *, int32_t, int32_t, const char *,
89     struct appl_varbind *);
90 void appl_agentx_getnext(struct appl_backend *, int32_t, int32_t, const char *,
91     struct appl_varbind *);
92 void appl_agentx_addagentcaps(struct appl_agentx_session *, struct ax_pdu *);
93 void appl_agentx_removeagentcaps(struct appl_agentx_session *, struct ax_pdu *);
94 void appl_agentx_response(struct appl_agentx_session *, struct ax_pdu *);
95 void appl_agentx_send(int, short, void *);
96 struct ber_oid *appl_agentx_oid2ber_oid(struct ax_oid *, struct ber_oid *);
97 struct ber_element *appl_agentx_value2ber_element(struct ax_varbind *);
98 struct ax_ostring *appl_agentx_string2ostring(const char *,
99     struct ax_ostring *);
100 int appl_agentx_cmp(struct appl_agentx_connection *,
101     struct appl_agentx_connection *);
102 int appl_agentx_session_cmp(struct appl_agentx_session *,
103     struct appl_agentx_session *);
104 
105 struct appl_backend_functions appl_agentx_functions = {
106 	.ab_close = appl_agentx_forceclose,
107 	.ab_get = appl_agentx_get,
108 	.ab_getnext = appl_agentx_getnext,
109 	.ab_getbulk = NULL, /* not properly supported in application.c and libagentx */
110 };
111 
112 RB_HEAD(appl_agentx_conns, appl_agentx_connection) appl_agentx_conns =
113     RB_INITIALIZER(&appl_agentx_conns);
114 RB_HEAD(appl_agentx_sessions, appl_agentx_session) appl_agentx_sessions =
115     RB_INITIALIZER(&appl_agentx_sessions);
116 
117 RB_PROTOTYPE_STATIC(appl_agentx_conns, appl_agentx_connection, conn_entry,
118     appl_agentx_cmp);
119 RB_PROTOTYPE_STATIC(appl_agentx_sessions, appl_agentx_session, sess_entry,
120     appl_agentx_session_cmp);
121 
122 void
123 appl_agentx(void)
124 {
125 	struct agentx_master *master;
126 
127 	TAILQ_FOREACH(master, &(snmpd_env->sc_agentx_masters), axm_entry)
128 		appl_agentx_listen(master);
129 }
130 
131 void
132 appl_agentx_init(void)
133 {
134 	struct agentx_master *master;
135 
136 	TAILQ_FOREACH(master, &(snmpd_env->sc_agentx_masters), axm_entry) {
137 		if (master->axm_fd == -1)
138 			continue;
139 		event_set(&(master->axm_ev), master->axm_fd,
140 		    EV_READ | EV_PERSIST, appl_agentx_accept, master);
141 		event_add(&(master->axm_ev), NULL);
142 		log_info("AgentX: listening on %s", master->axm_sun.sun_path);
143 	}
144 }
145 void
146 appl_agentx_listen(struct agentx_master *master)
147 {
148 	mode_t mask;
149 
150 	unlink(master->axm_sun.sun_path);
151 
152 	mask = umask(0777);
153 	if ((master->axm_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 ||
154 	    bind(master->axm_fd, (struct sockaddr *)&(master->axm_sun),
155 	    sizeof(master->axm_sun)) == -1 ||
156 	    listen(master->axm_fd, 5)) {
157 		log_warn("AgentX: listen %s", master->axm_sun.sun_path);
158 		umask(mask);
159 		return;
160 	}
161 	umask(mask);
162 	if (chown(master->axm_sun.sun_path, master->axm_owner,
163 	    master->axm_group) == -1) {
164 		log_warn("AgentX: chown %s", master->axm_sun.sun_path);
165 		goto fail;
166 	}
167 	if (chmod(master->axm_sun.sun_path, master->axm_mode) == -1) {
168 		log_warn("AgentX: chmod %s", master->axm_sun.sun_path);
169 		goto fail;
170 	}
171 	return;
172  fail:
173 	close(master->axm_fd);
174 	master->axm_fd = -1;
175 }
176 
177 void
178 appl_agentx_shutdown(void)
179 {
180 	struct appl_agentx_connection *conn, *tconn;
181 
182 	RB_FOREACH_SAFE(conn, appl_agentx_conns, &appl_agentx_conns, tconn)
183 		appl_agentx_free(conn, APPL_CLOSE_REASONSHUTDOWN);
184 }
185 
186 void
187 appl_agentx_accept(int masterfd, short event, void *cookie)
188 {
189 	int fd;
190 	struct agentx_master *master = cookie;
191 	struct sockaddr_un sun;
192 	socklen_t sunlen = sizeof(sun);
193 	struct appl_agentx_connection *conn = NULL;
194 
195 	if ((fd = accept(masterfd, (struct sockaddr *)&sun, &sunlen)) == -1) {
196 		log_warn("AgentX: accept %s", master->axm_sun.sun_path);
197 		return;
198 	}
199 
200 	if ((conn = malloc(sizeof(*conn))) == NULL) {
201 		log_warn(NULL);
202 		goto fail;
203 	}
204 
205 	conn->conn_backend = 0;
206 	TAILQ_INIT(&(conn->conn_sessions));
207 	if ((conn->conn_ax = ax_new(fd)) == NULL) {
208 		log_warn(NULL);
209 		goto fail;
210 	}
211 
212 	do {
213 		conn->conn_id = arc4random();
214 	} while (RB_INSERT(appl_agentx_conns,
215 	    &appl_agentx_conns, conn) != NULL);
216 
217 	event_set(&(conn->conn_rev), fd, EV_READ | EV_PERSIST,
218 	    appl_agentx_recv, conn);
219 	event_add(&(conn->conn_rev), NULL);
220 	event_set(&(conn->conn_wev), fd, EV_WRITE, appl_agentx_send, conn);
221 	log_info("AgentX(%"PRIu32"): new connection", conn->conn_id);
222 
223 	return;
224  fail:
225 	close(fd);
226 	free(conn);
227 }
228 
229 void
230 appl_agentx_backend(int fd)
231 {
232 	struct appl_agentx_connection *conn;
233 
234 	if ((conn = malloc(sizeof(*conn))) == NULL)
235 		fatal(NULL);
236 
237 	conn->conn_backend = 1;
238 	TAILQ_INIT(&(conn->conn_sessions));
239 	if ((conn->conn_ax = ax_new(fd)) == NULL)
240 		fatal("ax_new");
241 
242 	do {
243 		conn->conn_id = arc4random();
244 	} while (RB_INSERT(appl_agentx_conns,
245 	    &appl_agentx_conns, conn) != NULL);
246 
247 	event_set(&(conn->conn_rev), fd, EV_READ | EV_PERSIST,
248 	    appl_agentx_recv, conn);
249 	event_add(&(conn->conn_rev), NULL);
250 	event_set(&(conn->conn_wev), fd, EV_WRITE, appl_agentx_send, conn);
251 }
252 
253 void
254 appl_agentx_free(struct appl_agentx_connection *conn,
255     enum appl_close_reason reason)
256 {
257 	struct appl_agentx_session *session;
258 
259 	while ((session = TAILQ_FIRST(&(conn->conn_sessions))) != NULL) {
260 		if (conn->conn_ax == NULL)
261 			appl_agentx_session_free(session);
262 		else
263 			appl_agentx_forceclose(&(session->sess_backend),
264 			    reason);
265 	}
266 
267 	event_del(&(conn->conn_rev));
268 	event_del(&(conn->conn_wev));
269 
270 	RB_REMOVE(appl_agentx_conns, &appl_agentx_conns, conn);
271 	if (conn->conn_ax != NULL)
272 		(void)ax_send(conn->conn_ax);
273 	ax_free(conn->conn_ax);
274 	if (conn->conn_backend)
275 		fatalx("AgentX(%"PRIu32"): disappeared unexpected",
276 		    conn->conn_id);
277 	free(conn);
278 }
279 
280 void
281 appl_agentx_recv(int fd, short event, void *cookie)
282 {
283 	struct appl_agentx_connection *conn = cookie;
284 	struct appl_agentx_session *session = NULL;
285 	struct ax_pdu *pdu;
286 	enum appl_error error;
287 	char name[100];
288 
289 	snprintf(name, sizeof(name), "AgentX(%"PRIu32")", conn->conn_id);
290 	if ((pdu = ax_recv(conn->conn_ax)) == NULL) {
291 		if (errno == EAGAIN)
292 			return;
293 		log_warn("%s", name);
294 		/*
295 		 * Either the connection is dead, or we had garbage on the line.
296 		 * Both make sure we can't continue on this stream.
297 		 */
298 		if (errno == ECONNRESET) {
299 			ax_free(conn->conn_ax);
300 			conn->conn_ax = NULL;
301 		}
302 		appl_agentx_free(conn, errno == EPROTO ?
303 		    APPL_CLOSE_REASONPROTOCOLERROR : APPL_CLOSE_REASONOTHER);
304 		return;
305 	}
306 
307 	conn->conn_ax->ax_byteorder = pdu->ap_header.aph_flags &
308 	    AX_PDU_FLAG_NETWORK_BYTE_ORDER ?
309 	    AX_BYTE_ORDER_BE : AX_BYTE_ORDER_LE;
310 	if (pdu->ap_header.aph_type != AX_PDU_TYPE_OPEN) {
311 		/* Make sure we only look for connection-local sessions */
312 		TAILQ_FOREACH(session, &(conn->conn_sessions),
313 		    sess_conn_entry) {
314 			if (session->sess_id == pdu->ap_header.aph_sessionid)
315 				break;
316 		}
317 		if (session == NULL) {
318 			log_warnx("%s: Session %"PRIu32" not found for request",
319 			    name, pdu->ap_header.aph_sessionid);
320 			error = APPL_ERROR_NOTOPEN;
321 			goto fail;
322 		}
323 		strlcpy(name, session->sess_backend.ab_name, sizeof(name));
324 		/*
325 		 * RFC2741 section 7.1.1 bullet 4 is unclear on what byte order
326 		 * the response should be. My best guess is that it makes more
327 		 * sense that replies are in the same byte-order as what was
328 		 * requested.
329 		 * In practice we always have the same byte order as when we
330 		 * opened the session, so it's likely a non-issue, however, we
331 		 * can change to session byte order here.
332 		 */
333 	}
334 
335 	if (pdu->ap_header.aph_flags & AX_PDU_FLAG_INSTANCE_REGISTRATION) {
336 		if (pdu->ap_header.aph_type != AX_PDU_TYPE_REGISTER) {
337 			log_warnx("%s: %s: Invalid INSTANCE_REGISTRATION flag",
338 			    name, ax_pdutype2string(pdu->ap_header.aph_flags));
339 			error = APPL_ERROR_PARSEERROR;
340 			goto fail;
341 		}
342 	}
343 	if (pdu->ap_header.aph_flags & AX_PDU_FLAG_NEW_INDEX) {
344 		if (pdu->ap_header.aph_type != AX_PDU_TYPE_INDEXALLOCATE &&
345 		    pdu->ap_header.aph_type != AX_PDU_TYPE_INDEXDEALLOCATE) {
346 			log_warnx("%s: %s: Invalid NEW_INDEX flag", name,
347 			    ax_pdutype2string(pdu->ap_header.aph_flags));
348 			error = APPL_ERROR_PARSEERROR;
349 			goto fail;
350 		}
351 	}
352 	if (pdu->ap_header.aph_flags & AX_PDU_FLAG_ANY_INDEX) {
353 		if (pdu->ap_header.aph_type != AX_PDU_TYPE_INDEXALLOCATE &&
354 		    pdu->ap_header.aph_type != AX_PDU_TYPE_INDEXDEALLOCATE) {
355 			log_warnx("%s: %s: Invalid ANY_INDEX flag", name,
356 			    ax_pdutype2string(pdu->ap_header.aph_flags));
357 			error = APPL_ERROR_PARSEERROR;
358 			goto fail;
359 		}
360 	}
361 	if (pdu->ap_header.aph_flags & AX_PDU_FLAG_NON_DEFAULT_CONTEXT) {
362 		if (pdu->ap_header.aph_type != AX_PDU_TYPE_REGISTER &&
363 		    pdu->ap_header.aph_type != AX_PDU_TYPE_UNREGISTER &&
364 		    pdu->ap_header.aph_type != AX_PDU_TYPE_ADDAGENTCAPS &&
365 		    pdu->ap_header.aph_type != AX_PDU_TYPE_REMOVEAGENTCAPS &&
366 		    pdu->ap_header.aph_type != AX_PDU_TYPE_GET &&
367 		    pdu->ap_header.aph_type != AX_PDU_TYPE_GETNEXT &&
368 		    pdu->ap_header.aph_type != AX_PDU_TYPE_GETBULK &&
369 		    pdu->ap_header.aph_type != AX_PDU_TYPE_INDEXALLOCATE &&
370 		    pdu->ap_header.aph_type != AX_PDU_TYPE_INDEXDEALLOCATE &&
371 		    pdu->ap_header.aph_type != AX_PDU_TYPE_NOTIFY &&
372 		    pdu->ap_header.aph_type != AX_PDU_TYPE_TESTSET &&
373 		    pdu->ap_header.aph_type != AX_PDU_TYPE_PING) {
374 			log_warnx("%s: %s: Invalid NON_DEFAULT_CONTEXT flag",
375 			    name, ax_pdutype2string(pdu->ap_header.aph_flags));
376 			error = APPL_ERROR_PARSEERROR;
377 			goto fail;
378 		}
379 		if (appl_context(pdu->ap_context.aos_string, 0) == NULL) {
380 			log_warnx("%s: %s: Unsupported context",
381 			    name, ax_pdutype2string(pdu->ap_header.aph_flags));
382 			error = APPL_ERROR_UNSUPPORTEDCONTEXT;
383 			goto fail;
384 		}
385 	}
386 	switch (pdu->ap_header.aph_type) {
387 	case AX_PDU_TYPE_OPEN:
388 		appl_agentx_open(conn, pdu);
389 		break;
390 	case AX_PDU_TYPE_CLOSE:
391 		appl_agentx_close(session, pdu);
392 		break;
393 	case AX_PDU_TYPE_REGISTER:
394 		appl_agentx_register(session, pdu);
395 		break;
396 	case AX_PDU_TYPE_UNREGISTER:
397 		appl_agentx_unregister(session, pdu);
398 		break;
399 	case AX_PDU_TYPE_GET:
400 	case AX_PDU_TYPE_GETNEXT:
401 	case AX_PDU_TYPE_GETBULK:
402 	case AX_PDU_TYPE_TESTSET:
403 	case AX_PDU_TYPE_COMMITSET:
404 	case AX_PDU_TYPE_UNDOSET:
405 	case AX_PDU_TYPE_CLEANUPSET:
406 		log_warnx("%s: %s: Not an adminsitrative message", name,
407 		    ax_pdutype2string(pdu->ap_header.aph_type));
408 		error = APPL_ERROR_PARSEERROR;
409 		goto fail;
410 	case AX_PDU_TYPE_NOTIFY:
411 		log_warnx("%s: %s: not supported", name,
412 		    ax_pdutype2string(pdu->ap_header.aph_type));
413 		/*
414 		 * RFC 2741 section 7.1.10:
415 		 * Note that the master agent's successful response indicates
416 		 * the agentx-Notify-PDU was received and validated.  It does
417 		 * not indicate that any particular notifications were actually
418 		 * generated or received by notification targets
419 		 */
420 		/* XXX Not yet - FALLTHROUGH */
421 	case AX_PDU_TYPE_PING:
422 		ax_response(conn->conn_ax, pdu->ap_header.aph_sessionid,
423 		    pdu->ap_header.aph_transactionid,
424 		    pdu->ap_header.aph_packetid, smi_getticks(),
425 		    APPL_ERROR_NOERROR, 0, NULL, 0);
426 		event_add(&(conn->conn_wev), NULL);
427 		break;
428 	case AX_PDU_TYPE_INDEXALLOCATE:
429 	case AX_PDU_TYPE_INDEXDEALLOCATE:
430 		log_warnx("%s: %s: not supported", name,
431 		    ax_pdutype2string(pdu->ap_header.aph_type));
432 		ax_response(conn->conn_ax, pdu->ap_header.aph_sessionid,
433 		    pdu->ap_header.aph_transactionid,
434 		    pdu->ap_header.aph_packetid, smi_getticks(),
435 		    APPL_ERROR_PROCESSINGERROR, 1,
436 		    pdu->ap_payload.ap_vbl.ap_varbind,
437 		    pdu->ap_payload.ap_vbl.ap_nvarbind);
438 		event_add(&(conn->conn_wev), NULL);
439 		break;
440 	case AX_PDU_TYPE_ADDAGENTCAPS:
441 		appl_agentx_addagentcaps(session, pdu);
442 		break;
443 	case AX_PDU_TYPE_REMOVEAGENTCAPS:
444 		appl_agentx_removeagentcaps(session, pdu);
445 		break;
446 	case AX_PDU_TYPE_RESPONSE:
447 		appl_agentx_response(session, pdu);
448 		break;
449 	}
450 
451 	ax_pdu_free(pdu);
452 	return;
453  fail:
454 	ax_response(conn->conn_ax, pdu->ap_header.aph_sessionid,
455 	    pdu->ap_header.aph_transactionid,
456 	    pdu->ap_header.aph_packetid, smi_getticks(),
457 	    error, 0, NULL, 0);
458 	event_add(&(conn->conn_wev), NULL);
459 	ax_pdu_free(pdu);
460 
461 	if (session == NULL || error != APPL_ERROR_PARSEERROR)
462 		return;
463 
464 	appl_agentx_forceclose(&(session->sess_backend),
465 	    APPL_CLOSE_REASONPARSEERROR);
466 	if (TAILQ_EMPTY(&(conn->conn_sessions)))
467 		appl_agentx_free(conn, APPL_CLOSE_REASONOTHER);
468 }
469 
470 void
471 appl_agentx_open(struct appl_agentx_connection *conn, struct ax_pdu *pdu)
472 {
473 	struct appl_agentx_session *session;
474 	struct ber_oid oid;
475 	char oidbuf[1024];
476 	enum appl_error error = APPL_ERROR_NOERROR;
477 
478 	if ((session = malloc(sizeof(*session))) == NULL) {
479 		log_warn(NULL);
480 		error = APPL_ERROR_OPENFAILED;
481 		goto fail;
482 	}
483 	session->sess_descr.aos_string = NULL;
484 
485 	session->sess_conn = conn;
486 	if (pdu->ap_header.aph_flags & AX_PDU_FLAG_NETWORK_BYTE_ORDER)
487 		session->sess_byteorder = AX_BYTE_ORDER_BE;
488 	else
489 		session->sess_byteorder = AX_BYTE_ORDER_LE;
490 
491 	/* RFC 2742 agentxSessionObjectID */
492 	if (pdu->ap_payload.ap_open.ap_oid.aoi_idlen == 0) {
493 		pdu->ap_payload.ap_open.ap_oid.aoi_id[0] = 0;
494 		pdu->ap_payload.ap_open.ap_oid.aoi_id[1] = 0;
495 		pdu->ap_payload.ap_open.ap_oid.aoi_idlen = 2;
496 	} else if (pdu->ap_payload.ap_open.ap_oid.aoi_idlen == 1) {
497 		log_warnx("AgentX(%"PRIu32"): Invalid oid: Open Failed",
498 		    conn->conn_id);
499 		error = APPL_ERROR_PARSEERROR;
500 		goto fail;
501 	}
502 	/* RFC 2742 agentxSessionDescr */
503 	if (pdu->ap_payload.ap_open.ap_descr.aos_slen > 255) {
504 		log_warnx("AgentX(%"PRIu32"): Invalid descr (too long): Open "
505 		    "Failed", conn->conn_id);
506 		error = APPL_ERROR_PARSEERROR;
507 		goto fail;
508 	}
509 	/*
510 	 * ax_ostring is always NUL-terminated, but doesn't scan for internal
511 	 * NUL-bytes. However, mbstowcs stops at NUL, which might be in the
512 	 * middle of the string.
513 	 */
514 	if (strlen(pdu->ap_payload.ap_open.ap_descr.aos_string) !=
515 	    pdu->ap_payload.ap_open.ap_descr.aos_slen ||
516 	    mbstowcs(NULL,
517 	    pdu->ap_payload.ap_open.ap_descr.aos_string, 0) == (size_t)-1) {
518 		log_warnx("AgentX(%"PRIu32"): Invalid descr (not UTF-8): "
519 		    "Open Failed", conn->conn_id);
520 		error = APPL_ERROR_PARSEERROR;
521 		goto fail;
522 	}
523 
524 	session->sess_timeout = pdu->ap_payload.ap_open.ap_timeout;
525 	session->sess_oid = pdu->ap_payload.ap_open.ap_oid;
526 	session->sess_descr.aos_slen = pdu->ap_payload.ap_open.ap_descr.aos_slen;
527 	if (pdu->ap_payload.ap_open.ap_descr.aos_string != NULL) {
528 		session->sess_descr.aos_string =
529 		    strdup(pdu->ap_payload.ap_open.ap_descr.aos_string);
530 		if (session->sess_descr.aos_string == NULL) {
531 			log_warn("AgentX(%"PRIu32"): strdup: Open Failed",
532 			    conn->conn_id);
533 			error = APPL_ERROR_OPENFAILED;
534 			goto fail;
535 		}
536 	}
537 
538 	/* RFC 2742 agentxSessionIndex: chances of reuse, slim to none */
539 	do {
540 		session->sess_id = arc4random();
541 	} while (RB_INSERT(appl_agentx_sessions,
542 	    &appl_agentx_sessions, session) != NULL);
543 
544 	if (asprintf(&(session->sess_backend.ab_name),
545 	    "AgentX(%"PRIu32"/%"PRIu32")",
546 	    conn->conn_id, session->sess_id) == -1) {
547 		log_warn("AgentX(%"PRIu32"): asprintf: Open Failed",
548 		    conn->conn_id);
549 		error = APPL_ERROR_OPENFAILED;
550 		goto fail;
551 	}
552 	session->sess_backend.ab_cookie = session;
553 	session->sess_backend.ab_retries = 0;
554 	session->sess_backend.ab_fn = &appl_agentx_functions;
555 	session->sess_backend.ab_range = 1;
556 	RB_INIT(&(session->sess_backend.ab_requests));
557 	TAILQ_INSERT_TAIL(&(conn->conn_sessions), session, sess_conn_entry);
558 
559 	appl_agentx_oid2ber_oid(&(session->sess_oid), &oid);
560 	smi_oid2string(&oid, oidbuf, sizeof(oidbuf), 0);
561 	log_info("%s: %s %s: Open", session->sess_backend.ab_name, oidbuf,
562 	    session->sess_descr.aos_string);
563 
564 	ax_response(conn->conn_ax, session->sess_id,
565 	    pdu->ap_header.aph_transactionid, pdu->ap_header.aph_packetid,
566 	    smi_getticks(), APPL_ERROR_NOERROR, 0, NULL, 0);
567 	event_add(&(conn->conn_wev), NULL);
568 
569 	return;
570  fail:
571 	ax_response(conn->conn_ax, 0, pdu->ap_header.aph_transactionid,
572 	    pdu->ap_header.aph_packetid, 0, error, 0, NULL, 0);
573 	event_add(&(conn->conn_wev), NULL);
574 	if (session != NULL)
575 		free(session->sess_descr.aos_string);
576 	free(session);
577 }
578 
579 void
580 appl_agentx_close(struct appl_agentx_session *session, struct ax_pdu *pdu)
581 {
582 	struct appl_agentx_connection *conn = session->sess_conn;
583 	char name[100];
584 	enum appl_error error = APPL_ERROR_NOERROR;
585 
586 	strlcpy(name, session->sess_backend.ab_name, sizeof(name));
587 	if (pdu->ap_payload.ap_close.ap_reason == AX_CLOSE_BYMANAGER) {
588 		log_warnx("%s: Invalid close reason", name);
589 		error = APPL_ERROR_PARSEERROR;
590 	} else {
591 		appl_agentx_session_free(session);
592 		log_info("%s: Closed by subagent (%s)", name,
593 		    ax_closereason2string(pdu->ap_payload.ap_close.ap_reason));
594 	}
595 
596 	ax_response(conn->conn_ax, pdu->ap_header.aph_sessionid,
597 	    pdu->ap_header.aph_transactionid, pdu->ap_header.aph_packetid,
598 	    smi_getticks(), error, 0, NULL, 0);
599 	event_add(&(conn->conn_wev), NULL);
600 	if (error == APPL_ERROR_NOERROR)
601 		return;
602 
603 	appl_agentx_forceclose(&(session->sess_backend),
604 	    APPL_CLOSE_REASONPARSEERROR);
605 	if (TAILQ_EMPTY(&(conn->conn_sessions)))
606 		appl_agentx_free(conn, APPL_CLOSE_REASONOTHER);
607 }
608 
609 void
610 appl_agentx_forceclose(struct appl_backend *backend,
611     enum appl_close_reason reason)
612 {
613 	struct appl_agentx_session *session = backend->ab_cookie;
614 	char name[100];
615 
616 	session->sess_conn->conn_ax->ax_byteorder = session->sess_byteorder;
617 	ax_close(session->sess_conn->conn_ax, session->sess_id,
618 	    (enum ax_close_reason) reason);
619 	event_add(&(session->sess_conn->conn_wev), NULL);
620 
621 	strlcpy(name, session->sess_backend.ab_name, sizeof(name));
622 	appl_agentx_session_free(session);
623 	log_info("%s: Closed by snmpd (%s)", name,
624 	    ax_closereason2string((enum ax_close_reason)reason));
625 }
626 
627 void
628 appl_agentx_session_free(struct appl_agentx_session *session)
629 {
630 	struct appl_agentx_connection *conn = session->sess_conn;
631 
632 	appl_close(&(session->sess_backend));
633 
634 	RB_REMOVE(appl_agentx_sessions, &appl_agentx_sessions, session);
635 	TAILQ_REMOVE(&(conn->conn_sessions), session, sess_conn_entry);
636 
637 	free(session->sess_backend.ab_name);
638 	free(session->sess_descr.aos_string);
639 	free(session);
640 }
641 
642 void
643 appl_agentx_register(struct appl_agentx_session *session, struct ax_pdu *pdu)
644 {
645 	uint32_t timeout;
646 	struct ber_oid oid;
647 	enum appl_error error;
648 	int subtree = 0;
649 
650 	timeout = pdu->ap_payload.ap_register.ap_timeout;
651 	timeout = timeout != 0 ? timeout : session->sess_timeout != 0 ?
652 	    session->sess_timeout : AGENTX_DEFAULTTIMEOUT;
653 	timeout *= 100;
654 
655 	if (session->sess_conn->conn_backend) {
656 		pdu->ap_payload.ap_register.ap_priority = 1;
657 		subtree = 1;
658 	}
659 	if (appl_agentx_oid2ber_oid(
660 	    &(pdu->ap_payload.ap_register.ap_subtree), &oid) == NULL) {
661 		log_warnx("%s: Failed to register: oid too small",
662 		    session->sess_backend.ab_name);
663 		error = APPL_ERROR_PROCESSINGERROR;
664 		goto fail;
665 	}
666 
667 	error = appl_register(pdu->ap_context.aos_string, timeout,
668 	    pdu->ap_payload.ap_register.ap_priority, &oid,
669 	    pdu->ap_header.aph_flags & AX_PDU_FLAG_INSTANCE_REGISTRATION,
670 	    subtree, pdu->ap_payload.ap_register.ap_range_subid,
671 	    pdu->ap_payload.ap_register.ap_upper_bound,
672 	    &(session->sess_backend));
673 
674  fail:
675 	ax_response(session->sess_conn->conn_ax, session->sess_id,
676 	    pdu->ap_header.aph_transactionid, pdu->ap_header.aph_packetid,
677 	    smi_getticks(), error, 0, NULL, 0);
678 	event_add(&(session->sess_conn->conn_wev), NULL);
679 }
680 
681 void
682 appl_agentx_unregister(struct appl_agentx_session *session, struct ax_pdu *pdu)
683 {
684 	struct ber_oid oid;
685 	enum appl_error error;
686 
687 	if (appl_agentx_oid2ber_oid(
688 	    &(pdu->ap_payload.ap_unregister.ap_subtree), &oid) == NULL) {
689 		log_warnx("%s: Failed to unregister: oid too small",
690 		    session->sess_backend.ab_name);
691 		error = APPL_ERROR_PROCESSINGERROR;
692 		goto fail;
693 	}
694 
695 	error = appl_unregister(pdu->ap_context.aos_string,
696 	    pdu->ap_payload.ap_unregister.ap_priority, &oid,
697 	    pdu->ap_payload.ap_unregister.ap_range_subid,
698 	    pdu->ap_payload.ap_unregister.ap_upper_bound,
699 	    &(session->sess_backend));
700 
701  fail:
702 	ax_response(session->sess_conn->conn_ax, session->sess_id,
703 	    pdu->ap_header.aph_transactionid, pdu->ap_header.aph_packetid,
704 	    smi_getticks(), error, 0, NULL, 0);
705 	event_add(&(session->sess_conn->conn_wev), NULL);
706 }
707 
708 #define AX_PDU_FLAG_INDEX (AX_PDU_FLAG_NEW_INDEX | AX_PDU_FLAG_ANY_INDEX)
709 
710 void
711 appl_agentx_get(struct appl_backend *backend, int32_t transactionid,
712     int32_t requestid, const char *ctx, struct appl_varbind *vblist)
713 {
714 	struct appl_agentx_session *session = backend->ab_cookie;
715 	struct ax_ostring *context, string;
716 	struct appl_varbind *vb;
717 	struct ax_searchrange *srl;
718 	size_t i, j, nsr;
719 
720 	if (session->sess_conn->conn_ax == NULL)
721 		return;
722 
723 	for (nsr = 0, vb = vblist; vb != NULL; vb = vb->av_next)
724 		nsr++;
725 
726 	if ((srl = calloc(nsr, sizeof(*srl))) == NULL) {
727 		log_warn(NULL);
728 		appl_response(backend, requestid, APPL_ERROR_GENERR, 1, vblist);
729 		return;
730 	}
731 
732 	for (i = 0, vb = vblist; i < nsr; i++, vb = vb->av_next) {
733 		srl[i].asr_start.aoi_include = vb->av_include;
734 		srl[i].asr_start.aoi_idlen = vb->av_oid.bo_n;
735 		for (j = 0; j < vb->av_oid.bo_n; j++)
736 			srl[i].asr_start.aoi_id[j] = vb->av_oid.bo_id[j];
737 		srl[i].asr_stop.aoi_include = 0;
738 		srl[i].asr_stop.aoi_idlen = 0;
739 	}
740 	if ((context = appl_agentx_string2ostring(ctx, &string)) == NULL) {
741 		if (errno != 0) {
742 			log_warn("Failed to convert context");
743 			appl_response(backend, requestid,
744 			    APPL_ERROR_GENERR, 1, vblist);
745 			free(srl);
746 			return;
747 		}
748 	}
749 
750 	session->sess_conn->conn_ax->ax_byteorder = session->sess_byteorder;
751 	if (ax_get(session->sess_conn->conn_ax, session->sess_id, transactionid,
752 	    requestid, context, srl, nsr) == -1)
753 		appl_response(backend, requestid, APPL_ERROR_GENERR, 1, vblist);
754 	else
755 		event_add(&(session->sess_conn->conn_wev), NULL);
756 	free(srl);
757 	if (context != NULL)
758 		free(context->aos_string);
759 }
760 
761 void
762 appl_agentx_getnext(struct appl_backend *backend, int32_t transactionid,
763     int32_t requestid, const char *ctx, struct appl_varbind *vblist)
764 {
765 	struct appl_agentx_session *session = backend->ab_cookie;
766 	struct ax_ostring *context, string;
767 	struct appl_varbind *vb;
768 	struct ax_searchrange *srl;
769 	size_t i, j, nsr;
770 
771 	if (session->sess_conn->conn_ax == NULL)
772 		return;
773 
774 	for (nsr = 0, vb = vblist; vb != NULL; vb = vb->av_next)
775 		nsr++;
776 
777 	if ((srl = calloc(nsr, sizeof(*srl))) == NULL) {
778 		log_warn(NULL);
779 		appl_response(backend, requestid, APPL_ERROR_GENERR, 1, vblist);
780 		return;
781 	}
782 
783 	for (i = 0, vb = vblist; i < nsr; i++, vb = vb->av_next) {
784 		srl[i].asr_start.aoi_include = vb->av_include;
785 		srl[i].asr_start.aoi_idlen = vb->av_oid.bo_n;
786 		for (j = 0; j < vb->av_oid.bo_n; j++)
787 			srl[i].asr_start.aoi_id[j] = vb->av_oid.bo_id[j];
788 		srl[i].asr_stop.aoi_include = 0;
789 		srl[i].asr_stop.aoi_idlen = vb->av_oid_end.bo_n;
790 		for (j = 0; j < vb->av_oid_end.bo_n; j++)
791 			srl[i].asr_stop.aoi_id[j] = vb->av_oid_end.bo_id[j];
792 	}
793 	if ((context = appl_agentx_string2ostring(ctx, &string)) == NULL) {
794 		if (errno != 0) {
795 			log_warn("Failed to convert context");
796 			appl_response(backend, requestid,
797 			    APPL_ERROR_GENERR, 1, vblist);
798 			free(srl);
799 			return;
800 		}
801 	}
802 
803 	session->sess_conn->conn_ax->ax_byteorder = session->sess_byteorder;
804 	if (ax_getnext(session->sess_conn->conn_ax, session->sess_id, transactionid,
805 	    requestid, context, srl, nsr) == -1)
806 		appl_response(backend, requestid, APPL_ERROR_GENERR, 1, vblist);
807 	else
808 		event_add(&(session->sess_conn->conn_wev), NULL);
809 	free(srl);
810 	if (context != NULL)
811 		free(context->aos_string);
812 }
813 
814 void
815 appl_agentx_addagentcaps(struct appl_agentx_session *session,
816     struct ax_pdu *pdu)
817 {
818 	struct ber_oid oid;
819 	enum appl_error error;
820 
821 	if (appl_agentx_oid2ber_oid(&(pdu->ap_payload.ap_addagentcaps.ap_oid),
822 	    &oid) == NULL) {
823 		log_warnx("%s: Failed to add agent capabilities: oid too small",
824 		    session->sess_backend.ab_name);
825 		error = APPL_ERROR_PARSEERROR;
826 		goto fail;
827 	}
828 
829 	error = appl_addagentcaps(pdu->ap_context.aos_string, &oid,
830 	    pdu->ap_payload.ap_addagentcaps.ap_descr.aos_string,
831 	    &(session->sess_backend));
832 
833  fail:
834 	ax_response(session->sess_conn->conn_ax, session->sess_id,
835 	    pdu->ap_header.aph_transactionid, pdu->ap_header.aph_packetid,
836 	    smi_getticks(), error, 0, NULL, 0);
837 	event_add(&(session->sess_conn->conn_wev), NULL);
838 }
839 
840 void
841 appl_agentx_removeagentcaps(struct appl_agentx_session *session,
842     struct ax_pdu *pdu)
843 {
844 	struct ber_oid oid;
845 	enum appl_error error;
846 
847 	if (appl_agentx_oid2ber_oid(&(pdu->ap_payload.ap_addagentcaps.ap_oid),
848 	    &oid) == NULL) {
849 		log_warnx("%s: Failed to remove agent capabilities: "
850 		    "oid too small", session->sess_backend.ab_name);
851 		error = APPL_ERROR_PARSEERROR;
852 		goto fail;
853 	}
854 
855 	error = appl_removeagentcaps(pdu->ap_context.aos_string, &oid,
856 	    &(session->sess_backend));
857 
858  fail:
859 	ax_response(session->sess_conn->conn_ax, session->sess_id,
860 	    pdu->ap_header.aph_transactionid, pdu->ap_header.aph_packetid,
861 	    smi_getticks(), error, 0, NULL, 0);
862 	event_add(&(session->sess_conn->conn_wev), NULL);
863 }
864 
865 void
866 appl_agentx_response(struct appl_agentx_session *session, struct ax_pdu *pdu)
867 {
868 	struct appl_varbind *response = NULL;
869 	struct ax_varbind *vb;
870 	enum appl_error error;
871 	uint16_t index;
872 	size_t i, nvarbind;
873 
874 	nvarbind = pdu->ap_payload.ap_response.ap_nvarbind;
875 	if ((response = calloc(nvarbind, sizeof(*response))) == NULL) {
876 		log_warn(NULL);
877 		appl_response(&(session->sess_backend),
878 		    pdu->ap_header.aph_packetid,
879 		    APPL_ERROR_GENERR, 1, NULL);
880 		return;
881 	}
882 
883 	error = (enum appl_error)pdu->ap_payload.ap_response.ap_error;
884 	index = pdu->ap_payload.ap_response.ap_index;
885 	for (i = 0; i < nvarbind; i++) {
886 		response[i].av_next = i + 1 == nvarbind ?
887 		    NULL : &(response[i + 1]);
888 		vb = &(pdu->ap_payload.ap_response.ap_varbindlist[i]);
889 
890 		if (appl_agentx_oid2ber_oid(&(vb->avb_oid),
891 		    &(response[i].av_oid)) == NULL) {
892 			log_warnx("%s: invalid oid",
893 			    session->sess_backend.ab_name);
894 			if (error != APPL_ERROR_NOERROR) {
895 				error = APPL_ERROR_GENERR;
896 				index = i + 1;
897 			}
898 			continue;
899 		}
900 		response[i].av_value = appl_agentx_value2ber_element(vb);
901 		if (response[i].av_value == NULL) {
902 			log_warn("%s: Failed to parse response value",
903 			    session->sess_backend.ab_name);
904 			if (error != APPL_ERROR_NOERROR) {
905 				error = APPL_ERROR_GENERR;
906 				index = i + 1;
907 			}
908 		}
909 	}
910 	appl_response(&(session->sess_backend), pdu->ap_header.aph_packetid,
911 	    error, index, response);
912 	free(response);
913 }
914 
915 void
916 appl_agentx_send(int fd, short event, void *cookie)
917 {
918 	struct appl_agentx_connection *conn = cookie;
919 
920 	switch (ax_send(conn->conn_ax)) {
921 	case -1:
922 		if (errno == EAGAIN)
923 			break;
924 		log_warn("AgentX(%"PRIu32")", conn->conn_id);
925 		ax_free(conn->conn_ax);
926 		conn->conn_ax = NULL;
927 		appl_agentx_free(conn, APPL_CLOSE_REASONOTHER);
928 		return;
929 	case 0:
930 		return;
931 	default:
932 		break;
933 	}
934 	event_add(&(conn->conn_wev), NULL);
935 }
936 
937 struct ber_oid *
938 appl_agentx_oid2ber_oid(struct ax_oid *aoid, struct ber_oid *boid)
939 {
940 	size_t i;
941 
942 	if (aoid->aoi_idlen < BER_MIN_OID_LEN ||
943 	    aoid->aoi_idlen > BER_MAX_OID_LEN) {
944 		errno = EINVAL;
945 		return NULL;
946 	}
947 
948 
949 	boid->bo_n = aoid->aoi_idlen;
950 	for (i = 0; i < boid->bo_n; i++)
951 		boid->bo_id[i] = aoid->aoi_id[i];
952 	return boid;
953 }
954 
955 struct ber_element *
956 appl_agentx_value2ber_element(struct ax_varbind *vb)
957 {
958 	struct ber_oid oid;
959 	struct ber_element *elm;
960 
961 	switch (vb->avb_type) {
962 	case AX_DATA_TYPE_INTEGER:
963 		return ober_add_integer(NULL, vb->avb_data.avb_int32);
964 	case AX_DATA_TYPE_OCTETSTRING:
965 		return ober_add_nstring(NULL,
966 		    vb->avb_data.avb_ostring.aos_string,
967 		    vb->avb_data.avb_ostring.aos_slen);
968 	case AX_DATA_TYPE_NULL:
969 		return ober_add_null(NULL);
970 	case AX_DATA_TYPE_OID:
971 		if (appl_agentx_oid2ber_oid(
972 		    &(vb->avb_data.avb_oid), &oid) == NULL)
973 			return NULL;
974 		return ober_add_oid(NULL, &oid);
975 	case AX_DATA_TYPE_IPADDRESS:
976 		if ((elm = ober_add_nstring(NULL,
977 		    vb->avb_data.avb_ostring.aos_string,
978 		    vb->avb_data.avb_ostring.aos_slen)) == NULL)
979 			return NULL;
980 		ober_set_header(elm, BER_CLASS_APPLICATION, SNMP_T_IPADDR);
981 		return elm;
982 	case AX_DATA_TYPE_COUNTER32:
983 		elm = ober_add_integer(NULL, vb->avb_data.avb_uint32);
984 		if (elm == NULL)
985 			return NULL;
986 		ober_set_header(elm, BER_CLASS_APPLICATION, SNMP_T_COUNTER32);
987 		return elm;
988 	case AX_DATA_TYPE_GAUGE32:
989 		elm = ober_add_integer(NULL, vb->avb_data.avb_uint32);
990 		if (elm == NULL)
991 			return NULL;
992 		ober_set_header(elm, BER_CLASS_APPLICATION, SNMP_T_GAUGE32);
993 		return elm;
994 	case AX_DATA_TYPE_TIMETICKS:
995 		elm = ober_add_integer(NULL, vb->avb_data.avb_uint32);
996 		if (elm == NULL)
997 			return NULL;
998 		ober_set_header(elm, BER_CLASS_APPLICATION, SNMP_T_TIMETICKS);
999 		return elm;
1000 	case AX_DATA_TYPE_OPAQUE:
1001 		if ((elm = ober_add_nstring(NULL,
1002 		    vb->avb_data.avb_ostring.aos_string,
1003 		    vb->avb_data.avb_ostring.aos_slen)) == NULL)
1004 			return NULL;
1005 		ober_set_header(elm, BER_CLASS_APPLICATION, SNMP_T_OPAQUE);
1006 		return elm;
1007 	case AX_DATA_TYPE_COUNTER64:
1008 		elm = ober_add_integer(NULL, vb->avb_data.avb_uint64);
1009 		if (elm == NULL)
1010 			return NULL;
1011 		ober_set_header(elm, BER_CLASS_APPLICATION, SNMP_T_COUNTER64);
1012 		return elm;
1013 	case AX_DATA_TYPE_NOSUCHOBJECT:
1014 		return appl_exception(APPL_EXC_NOSUCHOBJECT);
1015 	case AX_DATA_TYPE_NOSUCHINSTANCE:
1016 		return appl_exception(APPL_EXC_NOSUCHINSTANCE);
1017 	case AX_DATA_TYPE_ENDOFMIBVIEW:
1018 		return appl_exception(APPL_EXC_ENDOFMIBVIEW);
1019 	default:
1020 		errno = EINVAL;
1021 		return NULL;
1022 	}
1023 }
1024 
1025 struct ax_ostring *
1026 appl_agentx_string2ostring(const char *str, struct ax_ostring *ostring)
1027 {
1028 	if (str == NULL) {
1029 		errno = 0;
1030 		return NULL;
1031 	}
1032 
1033 	ostring->aos_slen = strlen(str);
1034 	if ((ostring->aos_string = strdup(str)) == NULL)
1035 		return NULL;
1036 	return ostring;
1037 }
1038 
1039 int
1040 appl_agentx_cmp(struct appl_agentx_connection *conn1,
1041     struct appl_agentx_connection *conn2)
1042 {
1043 	return conn1->conn_id < conn2->conn_id ? -1 :
1044 	    conn1->conn_id > conn2->conn_id;
1045 }
1046 
1047 int
1048 appl_agentx_session_cmp(struct appl_agentx_session *sess1,
1049     struct appl_agentx_session *sess2)
1050 {
1051 	return sess1->sess_id < sess2->sess_id ? -1 : sess1->sess_id > sess2->sess_id;
1052 }
1053 
1054 RB_GENERATE_STATIC(appl_agentx_conns, appl_agentx_connection, conn_entry,
1055     appl_agentx_cmp);
1056 RB_GENERATE_STATIC(appl_agentx_sessions, appl_agentx_session, sess_entry,
1057     appl_agentx_session_cmp);
1058