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