xref: /freebsd/usr.sbin/bsnmpd/modules/snmp_netgraph/snmp_netgraph.c (revision faf25f48d601ae39f5752602f3020e2e92605625)
1 /*
2  * Copyright (c) 2001-2003
3  *	Fraunhofer Institute for Open Communication Systems (FhG Fokus).
4  *	All rights reserved.
5  *
6  * Author: Harti Brandt <harti@freebsd.org>
7  *
8  * Redistribution of this software and documentation and use in source and
9  * binary forms, with or without modification, are permitted provided that
10  * the following conditions are met:
11  *
12  * 1. Redistributions of source code or documentation must retain the above
13  *    copyright notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS
19  * AND ITS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
20  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
21  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
22  * FRAUNHOFER FOKUS OR ITS CONTRIBUTORS  BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
25  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  *
30  * $FreeBSD$
31  *
32  * Netgraph interface for SNMPd.
33  */
34 #include <sys/types.h>
35 #include <sys/param.h>
36 #include <sys/linker.h>
37 #include <sys/socket.h>
38 #include <sys/syslog.h>
39 #include <sys/queue.h>
40 #include <sys/sysctl.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <errno.h>
44 #include <unistd.h>
45 #include <string.h>
46 #include <netgraph.h>
47 #include <bsnmp/snmpmod.h>
48 #include "snmp_netgraph.h"
49 #include "netgraph_tree.h"
50 #include "netgraph_oid.h"
51 
52 /* maximum message size */
53 #define RESBUFSIZ	20000
54 
55 /* default node name */
56 #define NODENAME	"NgSnmpd"
57 
58 /* my node Id */
59 ng_ID_t snmp_node;
60 u_char *snmp_nodename;
61 
62 /* the Object Resource registration index */
63 static u_int reg_index;
64 static const struct asn_oid oid_begemotNg = OIDX_begemotNg;
65 
66 /* configuration */
67 /* this must be smaller than int32_t because the functions in libnetgraph
68  * falsely return an int */
69 static size_t resbufsiz = RESBUFSIZ;
70 static u_int timeout = 1000;
71 static u_int debug_level;
72 
73 /* number of microseconds per clock tick */
74 static struct clockinfo clockinfo;
75 
76 /* Csock buffers. Communication on the csock is asynchronuous. This means
77  * if we wait for a specific response, we may get other messages. Put these
78  * into a queue and execute them when we are idle. */
79 struct csock_buf {
80 	STAILQ_ENTRY(csock_buf) link;
81 	struct ng_mesg *mesg;
82 	char path[NG_PATHSIZ];
83 };
84 static STAILQ_HEAD(, csock_buf) csock_bufs =
85 	STAILQ_HEAD_INITIALIZER(csock_bufs);
86 
87 /*
88  * We dispatch unsolicieted messages by node cookies and ids.
89  * So we must keep a list of hook names and dispatch functions.
90  */
91 struct msgreg {
92 	u_int32_t 	cookie;
93 	ng_ID_t		id;
94 	ng_cookie_f	*func;
95 	void		*arg;
96 	const struct lmodule *mod;
97 	SLIST_ENTRY(msgreg) link;
98 };
99 static SLIST_HEAD(, msgreg) msgreg_list =
100 	SLIST_HEAD_INITIALIZER(msgreg_list);
101 
102 /*
103  * Data messages are dispatched by hook names.
104  */
105 struct datareg {
106 	char		hook[NG_HOOKSIZ];
107 	ng_hook_f	*func;
108 	void		*arg;
109 	const struct lmodule *mod;
110 	SLIST_ENTRY(datareg) link;
111 };
112 static SLIST_HEAD(, datareg) datareg_list =
113 	SLIST_HEAD_INITIALIZER(datareg_list);
114 
115 /* the netgraph sockets */
116 static int csock, dsock;
117 static void *csock_fd, *dsock_fd;
118 
119 /* our module handle */
120 static struct lmodule *module;
121 
122 /* statistics */
123 static u_int32_t stats[LEAF_begemotNgTooLargeDatas+1];
124 
125 /* netgraph type list */
126 struct ngtype {
127 	char		name[NG_TYPESIZ];
128 	struct asn_oid	index;
129 	TAILQ_ENTRY(ngtype) link;
130 };
131 TAILQ_HEAD(ngtype_list, ngtype);
132 
133 static struct ngtype_list ngtype_list;
134 static uint64_t ngtype_tick;
135 
136 
137 /*
138  * Register a function to receive unsolicited messages
139  */
140 void *
141 ng_register_cookie(const struct lmodule *mod, u_int32_t cookie, ng_ID_t id,
142     ng_cookie_f *func, void *arg)
143 {
144 	struct msgreg *d;
145 
146 	if ((d = malloc(sizeof(*d))) == NULL)
147 		return (NULL);
148 
149 	d->cookie = cookie;
150 	d->id = id;
151 	d->func = func;
152 	d->arg = arg;
153 	d->mod = mod;
154 
155 	SLIST_INSERT_HEAD(&msgreg_list, d, link);
156 
157 	return (d);
158 }
159 
160 /*
161  * Remove a registration.
162  */
163 void
164 ng_unregister_cookie(void *dd)
165 {
166 	struct msgreg *d = dd;
167 
168 	SLIST_REMOVE(&msgreg_list, d, msgreg, link);
169 	free(d);
170 }
171 
172 /*
173  * Register a function for hook data.
174  */
175 void *
176 ng_register_hook(const struct lmodule *mod, const char *hook,
177     ng_hook_f *func, void *arg)
178 {
179 	struct datareg *d;
180 
181 	if ((d = malloc(sizeof(*d))) == NULL)
182 		return (NULL);
183 
184 	strcpy(d->hook, hook);
185 	d->func = func;
186 	d->arg = arg;
187 	d->mod = mod;
188 
189 	SLIST_INSERT_HEAD(&datareg_list, d, link);
190 
191 	return (d);
192 }
193 
194 /*
195  * Unregister a hook function
196  */
197 void
198 ng_unregister_hook(void *dd)
199 {
200 	struct datareg *d = dd;
201 
202 	SLIST_REMOVE(&datareg_list, d, datareg, link);
203 	free(d);
204 }
205 
206 /*
207  * Unregister all hooks and cookies for that module. Note: doesn't disconnect
208  * any hooks!
209  */
210 void
211 ng_unregister_module(const struct lmodule *mod)
212 {
213 	struct msgreg *m, *m1;
214 	struct datareg *d, *d1;
215 
216 	m = SLIST_FIRST(&msgreg_list);
217 	while (m != NULL) {
218 		m1 = SLIST_NEXT(m, link);
219 		if (m->mod == mod) {
220 			SLIST_REMOVE(&msgreg_list, m, msgreg, link);
221 			free(m);
222 		}
223 		m = m1;
224 	}
225 
226 	d = SLIST_FIRST(&datareg_list);
227 	while (d != NULL) {
228 		d1 = SLIST_NEXT(d, link);
229 		if (d->mod == mod) {
230 			SLIST_REMOVE(&datareg_list, d, datareg, link);
231 			free(d);
232 		}
233 		d = d1;
234 	}
235 }
236 
237 /*
238  * Dispatch a message to the correct module and delete it. More than one
239  * module can get a message.
240  */
241 static void
242 csock_handle(struct ng_mesg *mesg, const char *path)
243 {
244 	struct msgreg *d, *d1;
245 	u_int id;
246 	int len;
247 
248 	if (sscanf(path, "[%x]:%n", &id, &len) != 1 ||
249 	    (u_int)len != strlen(path)) {
250 		syslog(LOG_ERR, "cannot parse message path '%s'", path);
251 		id = 0;
252 	}
253 
254 	d = SLIST_FIRST(&msgreg_list);
255 	while (d != NULL) {
256 		d1 = SLIST_NEXT(d, link);
257 		if (d->cookie == mesg->header.typecookie &&
258 		    (d->id == 0 || d->id == id || id == 0))
259 			(*d->func)(mesg, path, id, d->arg);
260 		d = d1;
261 	}
262 	free(mesg);
263 }
264 
265 /*
266  * Input from the control socket.
267  */
268 static struct ng_mesg *
269 csock_read(char *path)
270 {
271 	struct ng_mesg *mesg;
272 	int ret, err;
273 
274 	if ((mesg = malloc(resbufsiz + 1)) == NULL) {
275 		stats[LEAF_begemotNgNoMems]++;
276 		syslog(LOG_CRIT, "out of memory");
277 		errno = ENOMEM;
278 		return (NULL);
279 	}
280 	if ((ret = NgRecvMsg(csock, mesg, resbufsiz + 1, path)) < 0) {
281 		err = errno;
282 		free(mesg);
283 		if (errno == EWOULDBLOCK) {
284 			errno = err;
285 			return (NULL);
286 		}
287 		stats[LEAF_begemotNgMsgReadErrs]++;
288 		syslog(LOG_WARNING, "read from csock: %m");
289 		errno = err;
290 		return (NULL);
291 	}
292 	if (ret == 0) {
293 		syslog(LOG_DEBUG, "node closed -- exiting");
294 		exit(0);
295 	}
296 	if ((size_t)ret > resbufsiz) {
297 		stats[LEAF_begemotNgTooLargeMsgs]++;
298 		syslog(LOG_WARNING, "ng message too large");
299 		free(mesg);
300 		errno = EFBIG;
301 		return (NULL);
302 	}
303 	return (mesg);
304 }
305 
306 static void
307 csock_input(int fd __unused, void *udata __unused)
308 {
309 	struct ng_mesg *mesg;
310 	char path[NG_PATHSIZ];
311 
312 	if ((mesg = csock_read(path)) == NULL)
313 		return;
314 
315 	csock_handle(mesg, path);
316 }
317 
318 /*
319  * Write a message to a node.
320  */
321 int
322 ng_output(const char *path, u_int cookie, u_int opcode,
323     const void *arg, size_t arglen)
324 {
325 	return (NgSendMsg(csock, path, (int)cookie, (int)opcode, arg, arglen));
326 }
327 int
328 ng_output_node(const char *node, u_int cookie, u_int opcode,
329     const void *arg, size_t arglen)
330 {
331 	char path[NG_PATHSIZ];
332 
333 	sprintf(path, "%s:", node);
334 	return (ng_output(path, cookie, opcode, arg, arglen));
335 }
336 int
337 ng_output_id(ng_ID_t node, u_int cookie, u_int opcode,
338     const void *arg, size_t arglen)
339 {
340 	char path[NG_PATHSIZ];
341 
342 	sprintf(path, "[%x]:", node);
343 	return (ng_output(path, cookie, opcode, arg, arglen));
344 }
345 
346 
347 
348 /*
349  * Execute a synchronuous dialog with the csock. All message we receive, that
350  * do not match our request, are queue until the next call to the IDLE function.
351  */
352 struct ng_mesg *
353 ng_dialog(const char *path, u_int cookie, u_int opcode,
354     const void *arg, size_t arglen)
355 {
356 	int token, err;
357 	struct ng_mesg *mesg;
358 	char rpath[NG_PATHSIZ];
359 	struct csock_buf *b;
360 	struct timeval end, tv;
361 
362 	if ((token = ng_output(path, cookie, opcode, arg, arglen)) < 0)
363 		return (NULL);
364 
365 	if (csock_fd)
366 		fd_suspend(csock_fd);
367 
368 	gettimeofday(&end, NULL);
369 	tv.tv_sec = timeout / 1000;
370 	tv.tv_usec = (timeout % 1000) * 1000;
371 	timeradd(&end, &tv, &end);
372 	for (;;) {
373 		mesg = NULL;
374 		gettimeofday(&tv, NULL);
375 		if (timercmp(&tv, &end, >=)) {
376   block:
377 			syslog(LOG_WARNING, "no response for request %u/%u",
378 			    cookie, opcode);
379 			errno = EWOULDBLOCK;
380 			break;
381 		}
382 		timersub(&end, &tv, &tv);
383 		if (tv.tv_sec == 0 && tv.tv_usec < clockinfo.tick)
384 			goto block;
385 
386 		if (setsockopt(csock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1)
387 			syslog(LOG_WARNING, "setsockopt(SO_RCVTIMEO): %m");
388 		if ((mesg = csock_read(rpath)) == NULL) {
389 			if (errno == EWOULDBLOCK)
390 				continue;
391 			break;
392 		}
393 		if (mesg->header.token == (u_int)token)
394 			break;
395 		if ((b = malloc(sizeof(*b))) == NULL) {
396 			stats[LEAF_begemotNgNoMems]++;
397 			syslog(LOG_ERR, "out of memory");
398 			free(mesg);
399 			continue;
400 		}
401 		b->mesg = mesg;
402 		strcpy(b->path, rpath);
403 		STAILQ_INSERT_TAIL(&csock_bufs, b, link);
404 	}
405 
406 	tv.tv_sec = 0;
407 	tv.tv_usec = 0;
408 	if (setsockopt(csock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1)
409 		syslog(LOG_WARNING, "setsockopt(SO_RCVTIMEO,0): %m");
410 
411 	if (csock_fd) {
412 		err = errno;
413 		fd_resume(csock_fd);
414 		errno = err;
415 	}
416 
417 	return (mesg);
418 }
419 struct ng_mesg *
420 ng_dialog_node(const char *node, u_int cookie, u_int opcode,
421     const void *arg, size_t arglen)
422 {
423 	char path[NG_PATHSIZ];
424 
425 	sprintf(path, "%s:", node);
426 	return (ng_dialog(path, cookie, opcode, arg, arglen));
427 }
428 struct ng_mesg *
429 ng_dialog_id(ng_ID_t id, u_int cookie, u_int opcode,
430     const void *arg, size_t arglen)
431 {
432 	char path[NG_PATHSIZ];
433 
434 	sprintf(path, "[%x]:", id);
435 	return (ng_dialog(path, cookie, opcode, arg, arglen));
436 }
437 
438 
439 /*
440  * Send a data message to a given hook.
441  */
442 int
443 ng_send_data(const char *hook, const void *sndbuf, size_t sndlen)
444 {
445 	return (NgSendData(dsock, hook, sndbuf, sndlen));
446 }
447 
448 /*
449  * Input from a data socket. Dispatch to the function for that hook.
450  */
451 static void
452 dsock_input(int fd __unused, void *udata __unused)
453 {
454 	u_char *resbuf, embuf[100];
455 	ssize_t len;
456 	char hook[NG_HOOKSIZ];
457 	struct datareg *d, *d1;
458 
459 	if ((resbuf = malloc(resbufsiz + 1)) == NULL) {
460 		stats[LEAF_begemotNgNoMems]++;
461 		syslog(LOG_CRIT, "out of memory");
462 		(void)NgRecvData(fd, embuf, sizeof(embuf), hook);
463 		errno = ENOMEM;
464 		return;
465 	}
466 	if ((len = NgRecvData(fd, resbuf, resbufsiz + 1, hook)) == -1) {
467 		stats[LEAF_begemotNgDataReadErrs]++;
468 		syslog(LOG_ERR, "reading message: %m");
469 		free(resbuf);
470 		return;
471 	}
472 	if (len == 0) {
473 		free(resbuf);
474 		return;
475 	}
476 	if ((size_t)len == resbufsiz + 1) {
477 		stats[LEAF_begemotNgTooLargeDatas]++;
478 		syslog(LOG_WARNING, "message too long");
479 		free(resbuf);
480 		return;
481 	}
482 
483 	/*
484 	 * Dispatch message. Maybe dispatched to more than one function.
485 	 */
486 	d = SLIST_FIRST(&datareg_list);
487 	while (d != NULL) {
488 		d1 = SLIST_NEXT(d, link);
489 		if (strcmp(hook, d->hook) == 0)
490 			(*d->func)(hook, resbuf, len, d->arg);
491 		d = d1;
492 	}
493 
494 	free(resbuf);
495 }
496 
497 /*
498  * The SNMP daemon is about to wait for an event. Look whether we have
499  * netgraph messages waiting. If yes, drain the queue.
500  */
501 static void
502 ng_idle(void)
503 {
504 	struct csock_buf *b;
505 
506 	/* execute waiting csock_bufs */
507 	while ((b = STAILQ_FIRST(&csock_bufs)) != NULL) {
508 		STAILQ_REMOVE_HEAD(&csock_bufs, link);
509 		csock_handle(b->mesg, b->path);
510 		free(b);
511 	}
512 }
513 
514 /*
515  * Called when the module is loaded. Returning a non-zero value means,
516  * rejecting the initialisation.
517  *
518  * We make the netgraph socket.
519  */
520 static int
521 ng_init(struct lmodule *mod, int argc, char *argv[])
522 {
523 	int name[2];
524 	size_t len;
525 
526 	module = mod;
527 
528 	if (argc == 0) {
529 		if ((snmp_nodename = malloc(strlen(NODENAME) + 1)) == NULL)
530 			return (ENOMEM);
531 		strcpy(snmp_nodename, NODENAME);
532 	} else {
533 		if ((snmp_nodename = malloc(NG_NODESIZ)) == NULL)
534 			return (ENOMEM);
535 		strlcpy(snmp_nodename, argv[0], NG_NODESIZ);
536 	}
537 
538 	/* fetch clockinfo (for the number of microseconds per tick) */
539 	name[0] = CTL_KERN;
540 	name[1] = KERN_CLOCKRATE;
541 	len = sizeof(clockinfo);
542 	if (sysctl(name, 2, &clockinfo, &len, NULL, 0) == -1)
543 		return (errno);
544 
545 	TAILQ_INIT(&ngtype_list);
546 
547 	return (0);
548 }
549 
550 /*
551  * Get the node Id/name/type of a node.
552  */
553 ng_ID_t
554 ng_node_id(const char *path)
555 {
556 	struct ng_mesg *resp;
557 	ng_ID_t id;
558 
559 	if ((resp = ng_dialog(path, NGM_GENERIC_COOKIE, NGM_NODEINFO,
560 	    NULL, 0)) == NULL)
561 		return (0);
562 	id = ((struct nodeinfo *)(void *)resp->data)->id;
563 	free(resp);
564 	return (id);
565 }
566 ng_ID_t
567 ng_node_id_node(const char *node)
568 {
569 	struct ng_mesg *resp;
570 	ng_ID_t id;
571 
572 	if ((resp = ng_dialog_node(node, NGM_GENERIC_COOKIE, NGM_NODEINFO,
573 	    NULL, 0)) == NULL)
574 		return (0);
575 	id = ((struct nodeinfo *)(void *)resp->data)->id;
576 	free(resp);
577 	return (id);
578 }
579 ng_ID_t
580 ng_node_name(ng_ID_t id, char *name)
581 {
582 	struct ng_mesg *resp;
583 
584 	if ((resp = ng_dialog_id(id, NGM_GENERIC_COOKIE, NGM_NODEINFO,
585 	    NULL, 0)) == NULL)
586 		return (0);
587 	strcpy(name, ((struct nodeinfo *)(void *)resp->data)->name);
588 	free(resp);
589 	return (id);
590 
591 }
592 ng_ID_t
593 ng_node_type(ng_ID_t id, char *type)
594 {
595 	struct ng_mesg *resp;
596 
597 	if ((resp = ng_dialog_id(id, NGM_GENERIC_COOKIE, NGM_NODEINFO,
598 	    NULL, 0)) == NULL)
599 		return (0);
600 	strcpy(type, ((struct nodeinfo *)(void *)resp->data)->type);
601 	free(resp);
602 	return (id);
603 }
604 
605 /*
606  * Connect our node to some other node
607  */
608 int
609 ng_connect_node(const char *node, const char *ourhook, const char *peerhook)
610 {
611 	struct ngm_connect conn;
612 
613 	snprintf(conn.path, NG_PATHSIZ, "%s:", node);
614 	strlcpy(conn.ourhook, ourhook, NG_HOOKSIZ);
615 	strlcpy(conn.peerhook, peerhook, NG_HOOKSIZ);
616 	return (NgSendMsg(csock, ".:",
617 	    NGM_GENERIC_COOKIE, NGM_CONNECT, &conn, sizeof(conn)));
618 }
619 int
620 ng_connect_id(ng_ID_t id, const char *ourhook, const char *peerhook)
621 {
622 	struct ngm_connect conn;
623 
624 	snprintf(conn.path, NG_PATHSIZ, "[%x]:", id);
625 	strlcpy(conn.ourhook, ourhook, NG_HOOKSIZ);
626 	strlcpy(conn.peerhook, peerhook, NG_HOOKSIZ);
627 	return (NgSendMsg(csock, ".:",
628 	    NGM_GENERIC_COOKIE, NGM_CONNECT, &conn, sizeof(conn)));
629 }
630 
631 int
632 ng_connect2_id(ng_ID_t id, ng_ID_t peer, const char *ourhook,
633     const char *peerhook)
634 {
635 	struct ngm_connect conn;
636 	char path[NG_PATHSIZ];
637 
638 	snprintf(path, NG_PATHSIZ, "[%x]:", id);
639 
640 	snprintf(conn.path, NG_PATHSIZ, "[%x]:", peer);
641 	strlcpy(conn.ourhook, ourhook, NG_HOOKSIZ);
642 	strlcpy(conn.peerhook, peerhook, NG_HOOKSIZ);
643 	return (NgSendMsg(csock, path,
644 	    NGM_GENERIC_COOKIE, NGM_CONNECT, &conn, sizeof(conn)));
645 }
646 
647 int
648 ng_connect2_tee_id(ng_ID_t id, ng_ID_t peer, const char *ourhook,
649     const char *peerhook)
650 {
651 	struct ngm_connect conn;
652 	char path[NG_PATHSIZ];
653 	ng_ID_t tee;
654 
655 	if ((tee = ng_mkpeer_id(id, NULL, "tee", ourhook, "left")) == 0)
656 		return (-1);
657 
658 	snprintf(path, NG_PATHSIZ, "[%x]:", tee);
659 
660 	snprintf(conn.path, NG_PATHSIZ, "[%x]:", peer);
661 	strlcpy(conn.ourhook, "right", NG_HOOKSIZ);
662 	strlcpy(conn.peerhook, peerhook, NG_HOOKSIZ);
663 	return (NgSendMsg(csock, path,
664 	    NGM_GENERIC_COOKIE, NGM_CONNECT, &conn, sizeof(conn)));
665 }
666 
667 /*
668  * Ensure that a node of type 'type' is connected to 'hook' of 'node'
669  * and return its node id. tee nodes between node and the target node
670  * are skipped. If the type is wrong, or the hook is a dead-end return 0.
671  * If type is NULL, it is not checked.
672  */
673 static ng_ID_t
674 ng_next_node_id_internal(ng_ID_t node, const char *type, const char *hook,
675     int skip_tee)
676 {
677 	struct ng_mesg *resp;
678 	struct hooklist *hooklist;
679 	u_int i;
680 
681 	if ((resp = ng_dialog_id(node, NGM_GENERIC_COOKIE, NGM_LISTHOOKS,
682 	    NULL, 0)) == NULL) {
683 		syslog(LOG_ERR, "get hook list: %m");
684 		exit(1);
685 	}
686 	hooklist = (struct hooklist *)(void *)resp->data;
687 
688 	for (i = 0; i < hooklist->nodeinfo.hooks; i++)
689 		if (strcmp(hooklist->link[i].ourhook, hook) == 0)
690 			break;
691 
692 	if (i == hooklist->nodeinfo.hooks) {
693 		free(resp);
694 		return (0);
695 	}
696 
697 	node = hooklist->link[i].nodeinfo.id;
698 
699 	if (skip_tee && strcmp(hooklist->link[i].nodeinfo.type, "tee") == 0) {
700 		if (strcmp(hooklist->link[i].peerhook, "left") == 0)
701 			node = ng_next_node_id(node, type, "right");
702 		else if (strcmp(hooklist->link[i].peerhook, "right") == 0)
703 			node = ng_next_node_id(node, type, "left");
704 		else if (type != NULL &&
705 		    strcmp(hooklist->link[i].nodeinfo.type, type) != 0)
706 			node = 0;
707 
708 	} else if (type != NULL &&
709 	    strcmp(hooklist->link[i].nodeinfo.type, type) != 0)
710 		node = 0;
711 
712 	free(resp);
713 
714 	return (node);
715 }
716 
717 /*
718  * Ensure that a node of type 'type' is connected to 'hook' of 'node'
719  * and return its node id. tee nodes between node and the target node
720  * are skipped. If the type is wrong, or the hook is a dead-end return 0.
721  * If type is NULL, it is not checked.
722  */
723 ng_ID_t
724 ng_next_node_id(ng_ID_t node, const char *type, const char *hook)
725 {
726 	return (ng_next_node_id_internal(node, type, hook, 1));
727 }
728 
729 ng_ID_t
730 ng_mkpeer_id(ng_ID_t id, const char *nodename, const char *type,
731     const char *hook, const char *peerhook)
732 {
733 	char path[NG_PATHSIZ];
734 	struct ngm_mkpeer mkpeer;
735 	struct ngm_name name;
736 
737 	strlcpy(mkpeer.type, type, NG_TYPESIZ);
738 	strlcpy(mkpeer.ourhook, hook, NG_HOOKSIZ);
739 	strlcpy(mkpeer.peerhook, peerhook, NG_HOOKSIZ);
740 
741 	sprintf(path, "[%x]:", id);
742 	if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE, NGM_MKPEER,
743 	    &mkpeer, sizeof(mkpeer)) == -1)
744 		return (0);
745 
746 	if ((id = ng_next_node_id_internal(id, NULL, hook, 0)) == 0)
747 		return (0);
748 
749 	if (nodename != NULL) {
750 		strcpy(name.name, nodename);
751 		sprintf(path, "[%x]:", id);
752 		if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE, NGM_NAME,
753 		    &name, sizeof(name)) == -1)
754 			return (0);
755 	}
756 	return (id);
757 }
758 
759 /*
760  * SHutdown node
761  */
762 int
763 ng_shutdown_id(ng_ID_t id)
764 {
765 	char path[NG_PATHSIZ];
766 
767 	snprintf(path, NG_PATHSIZ, "[%x]:", id);
768 	return (NgSendMsg(csock, path, NGM_GENERIC_COOKIE,
769 	    NGM_SHUTDOWN, NULL, 0));
770 }
771 
772 /*
773  * Disconnect one of our hooks
774  */
775 int
776 ng_rmhook(const char *ourhook)
777 {
778 	struct ngm_rmhook rmhook;
779 
780 	strlcpy(rmhook.ourhook, ourhook, NG_HOOKSIZ);
781 	return (NgSendMsg(csock, ".:",
782 	    NGM_GENERIC_COOKIE, NGM_RMHOOK, &rmhook, sizeof(rmhook)));
783 }
784 
785 /*
786  * Disconnect a hook of a node
787  */
788 int
789 ng_rmhook_id(ng_ID_t id, const char *hook)
790 {
791 	struct ngm_rmhook rmhook;
792 	char path[NG_PATHSIZ];
793 
794 	strlcpy(rmhook.ourhook, hook, NG_HOOKSIZ);
795 	snprintf(path, NG_PATHSIZ, "[%x]:", id);
796 	return (NgSendMsg(csock, path,
797 	    NGM_GENERIC_COOKIE, NGM_RMHOOK, &rmhook, sizeof(rmhook)));
798 }
799 
800 /*
801  * Disconnect a hook and shutdown all tee nodes that were connected to that
802  * hook.
803  */
804 int
805 ng_rmhook_tee_id(ng_ID_t node, const char *hook)
806 {
807 	struct ng_mesg *resp;
808 	struct hooklist *hooklist;
809 	u_int i;
810 	int first = 1;
811 	ng_ID_t next_node;
812 	const char *next_hook;
813 
814   again:
815 	/* if we have just shutdown a tee node, which had no other hooks
816 	 * connected, the node id may already be wrong here. */
817 	if ((resp = ng_dialog_id(node, NGM_GENERIC_COOKIE, NGM_LISTHOOKS,
818 	    NULL, 0)) == NULL)
819 		return (0);
820 
821 	hooklist = (struct hooklist *)(void *)resp->data;
822 
823 	for (i = 0; i < hooklist->nodeinfo.hooks; i++)
824 		if (strcmp(hooklist->link[i].ourhook, hook) == 0)
825 			break;
826 
827 	if (i == hooklist->nodeinfo.hooks) {
828 		free(resp);
829 		return (0);
830 	}
831 
832 	next_node = 0;
833 	next_hook = NULL;
834 	if (strcmp(hooklist->link[i].nodeinfo.type, "tee") == 0) {
835 		if (strcmp(hooklist->link[i].peerhook, "left") == 0) {
836 			next_node = hooklist->link[i].nodeinfo.id;
837 			next_hook = "right";
838 		} else if (strcmp(hooklist->link[i].peerhook, "right") == 0) {
839 			next_node = hooklist->link[i].nodeinfo.id;
840 			next_hook = "left";
841 		}
842 	}
843 	free(resp);
844 
845 	if (first) {
846 		ng_rmhook_id(node, hook);
847 		first = 0;
848 	} else {
849 		ng_shutdown_id(node);
850 	}
851 	if ((node = next_node) == 0)
852 		return (0);
853 	hook = next_hook;
854 
855 	goto again;
856 }
857 
858 /*
859  * Get the peer hook of a hook on a given node. Skip any tee nodes in between
860  */
861 int
862 ng_peer_hook_id(ng_ID_t node, const char *hook, char *peerhook)
863 {
864 	struct ng_mesg *resp;
865 	struct hooklist *hooklist;
866 	u_int i;
867 	int ret;
868 
869 	if ((resp = ng_dialog_id(node, NGM_GENERIC_COOKIE, NGM_LISTHOOKS,
870 	    NULL, 0)) == NULL) {
871 		syslog(LOG_ERR, "get hook list: %m");
872 		exit(1);
873 	}
874 	hooklist = (struct hooklist *)(void *)resp->data;
875 
876 	for (i = 0; i < hooklist->nodeinfo.hooks; i++)
877 		if (strcmp(hooklist->link[i].ourhook, hook) == 0)
878 			break;
879 
880 	if (i == hooklist->nodeinfo.hooks) {
881 		free(resp);
882 		return (-1);
883 	}
884 
885 	node = hooklist->link[i].nodeinfo.id;
886 
887 	ret = 0;
888 	if (strcmp(hooklist->link[i].nodeinfo.type, "tee") == 0) {
889 		if (strcmp(hooklist->link[i].peerhook, "left") == 0)
890 			ret = ng_peer_hook_id(node, "right", peerhook);
891 		else if (strcmp(hooklist->link[i].peerhook, "right") == 0)
892 			ret = ng_peer_hook_id(node, "left", peerhook);
893 		else
894 			strcpy(peerhook, hooklist->link[i].peerhook);
895 
896 	} else
897 		strcpy(peerhook, hooklist->link[i].peerhook);
898 
899 	free(resp);
900 
901 	return (ret);
902 }
903 
904 
905 /*
906  * Now the module is started. Select on the sockets, so that we can get
907  * unsolicited input.
908  */
909 static void
910 ng_start(void)
911 {
912 	if (snmp_node == 0) {
913 		if (NgMkSockNode(snmp_nodename, &csock, &dsock) < 0) {
914 			syslog(LOG_ERR, "NgMkSockNode: %m");
915 			exit(1);
916 		}
917 		snmp_node = ng_node_id(".:");
918 	}
919 
920 	if ((csock_fd = fd_select(csock, csock_input, NULL, module)) == NULL) {
921 		syslog(LOG_ERR, "fd_select failed on csock: %m");
922 		return;
923 	}
924 	if ((dsock_fd = fd_select(dsock, dsock_input, NULL, module)) == NULL) {
925 		syslog(LOG_ERR, "fd_select failed on dsock: %m");
926 		return;
927 	}
928 
929 	reg_index = or_register(&oid_begemotNg,
930 	    "The MIB for the NetGraph access module for SNMP.", module);
931 }
932 
933 /*
934  * Called, when the module is to be unloaded after it was successfully loaded
935  */
936 static int
937 ng_fini(void)
938 {
939 	struct ngtype *t;
940 
941 	while ((t = TAILQ_FIRST(&ngtype_list)) != NULL) {
942 		TAILQ_REMOVE(&ngtype_list, t, link);
943 		free(t);
944 	}
945 
946 	if (csock_fd != NULL)
947 		fd_deselect(csock_fd);
948 	(void)close(csock);
949 
950 	if (dsock_fd != NULL)
951 		fd_deselect(dsock_fd);
952 	(void)close(dsock);
953 
954 	free(snmp_nodename);
955 
956 	or_unregister(reg_index);
957 
958 	return (0);
959 }
960 
961 const struct snmp_module config = {
962 	"This module implements access to the netgraph sub-system",
963 	ng_init,
964 	ng_fini,
965 	ng_idle,
966 	NULL,
967 	NULL,
968 	ng_start,
969 	NULL,
970 	netgraph_ctree,
971 	netgraph_CTREE_SIZE,
972 	NULL
973 };
974 
975 int
976 op_ng_config(struct snmp_context *ctx, struct snmp_value *value,
977     u_int sub, u_int iidx __unused, enum snmp_op op)
978 {
979 	asn_subid_t which = value->var.subs[sub - 1];
980 	int ret;
981 
982 	switch (op) {
983 
984 	  case SNMP_OP_GETNEXT:
985 		abort();
986 
987 	  case SNMP_OP_GET:
988 		/*
989 		 * Come here for GET, GETNEXT and COMMIT
990 		 */
991 		switch (which) {
992 
993 		  case LEAF_begemotNgControlNodeName:
994 			return (string_get(value, snmp_nodename, -1));
995 
996 		  case LEAF_begemotNgResBufSiz:
997 			value->v.integer = resbufsiz;
998 			break;
999 
1000 		  case LEAF_begemotNgTimeout:
1001 			value->v.integer = timeout;
1002 			break;
1003 
1004 		  case LEAF_begemotNgDebugLevel:
1005 			value->v.uint32 = debug_level;
1006 			break;
1007 
1008 		  default:
1009 			abort();
1010 		}
1011 		return (SNMP_ERR_NOERROR);
1012 
1013 	  case SNMP_OP_SET:
1014 		switch (which) {
1015 
1016 		  case LEAF_begemotNgControlNodeName:
1017 			/* only at initialisation */
1018 			if (community != COMM_INITIALIZE)
1019 				return (SNMP_ERR_NOT_WRITEABLE);
1020 
1021 			if (snmp_node != 0)
1022 				return (SNMP_ERR_NOT_WRITEABLE);
1023 
1024 			if ((ret = string_save(value, ctx, -1, &snmp_nodename))
1025 			    != SNMP_ERR_NOERROR)
1026 				return (ret);
1027 
1028 			if (NgMkSockNode(snmp_nodename, &csock, &dsock) < 0) {
1029 				syslog(LOG_ERR, "NgMkSockNode: %m");
1030 				string_rollback(ctx, &snmp_nodename);
1031 				return (SNMP_ERR_GENERR);
1032 			}
1033 			snmp_node = ng_node_id(".:");
1034 
1035 			return (SNMP_ERR_NOERROR);
1036 
1037 		  case LEAF_begemotNgResBufSiz:
1038 			ctx->scratch->int1 = resbufsiz;
1039 			if (value->v.integer < 1024 ||
1040 			    value->v.integer > 0x10000)
1041 				return (SNMP_ERR_WRONG_VALUE);
1042 			resbufsiz = value->v.integer;
1043 			return (SNMP_ERR_NOERROR);
1044 
1045 		  case LEAF_begemotNgTimeout:
1046 			ctx->scratch->int1 = timeout;
1047 			if (value->v.integer < 10 ||
1048 			    value->v.integer > 10000)
1049 				return (SNMP_ERR_WRONG_VALUE);
1050 			timeout = value->v.integer;
1051 			return (SNMP_ERR_NOERROR);
1052 
1053 		  case LEAF_begemotNgDebugLevel:
1054 			ctx->scratch->int1 = debug_level;
1055 			debug_level = value->v.uint32;
1056 			NgSetDebug(debug_level);
1057 			return (SNMP_ERR_NOERROR);
1058 		}
1059 		abort();
1060 
1061 	  case SNMP_OP_ROLLBACK:
1062 		switch (which) {
1063 
1064 		  case LEAF_begemotNgControlNodeName:
1065 			string_rollback(ctx, &snmp_nodename);
1066 			close(csock);
1067 			close(dsock);
1068 			snmp_node = 0;
1069 			return (SNMP_ERR_NOERROR);
1070 
1071 		  case LEAF_begemotNgResBufSiz:
1072 			resbufsiz = ctx->scratch->int1;
1073 			return (SNMP_ERR_NOERROR);
1074 
1075 		  case LEAF_begemotNgTimeout:
1076 			timeout = ctx->scratch->int1;
1077 			return (SNMP_ERR_NOERROR);
1078 
1079 		  case LEAF_begemotNgDebugLevel:
1080 			debug_level = ctx->scratch->int1;
1081 			NgSetDebug(debug_level);
1082 			return (SNMP_ERR_NOERROR);
1083 		}
1084 		abort();
1085 
1086 	  case SNMP_OP_COMMIT:
1087 		switch (which) {
1088 
1089 		  case LEAF_begemotNgControlNodeName:
1090 			string_commit(ctx);
1091 			return (SNMP_ERR_NOERROR);
1092 
1093 		  case LEAF_begemotNgResBufSiz:
1094 		  case LEAF_begemotNgTimeout:
1095 		  case LEAF_begemotNgDebugLevel:
1096 			return (SNMP_ERR_NOERROR);
1097 		}
1098 		abort();
1099 	}
1100 	abort();
1101 }
1102 
1103 int
1104 op_ng_stats(struct snmp_context *ctx __unused, struct snmp_value *value,
1105     u_int sub, u_int iidx __unused, enum snmp_op op)
1106 {
1107 	switch (op) {
1108 
1109 	  case SNMP_OP_GETNEXT:
1110 		abort();
1111 
1112 	  case SNMP_OP_GET:
1113 		value->v.uint32 = stats[value->var.subs[sub - 1] - 1];
1114 		return (SNMP_ERR_NOERROR);
1115 
1116 	  case SNMP_OP_SET:
1117 		return (SNMP_ERR_NOT_WRITEABLE);
1118 
1119 	  case SNMP_OP_ROLLBACK:
1120 	  case SNMP_OP_COMMIT:
1121 		abort();
1122 	}
1123 	abort();
1124 }
1125 
1126 /*
1127  * Netgraph type table
1128  */
1129 static int
1130 fetch_types(void)
1131 {
1132 	struct ngtype *t;
1133 	struct typelist *typelist;
1134 	struct ng_mesg *resp;
1135 	u_int u, i;
1136 
1137 	if (this_tick <= ngtype_tick)
1138 		return (0);
1139 
1140 	while ((t = TAILQ_FIRST(&ngtype_list)) != NULL) {
1141 		TAILQ_REMOVE(&ngtype_list, t, link);
1142 		free(t);
1143 	}
1144 
1145 	if ((resp = ng_dialog_id(snmp_node, NGM_GENERIC_COOKIE,
1146 	    NGM_LISTTYPES, NULL, 0)) == NULL)
1147 		return (SNMP_ERR_GENERR);
1148 	typelist = (struct typelist *)(void *)resp->data;
1149 
1150 	for (u = 0; u < typelist->numtypes; u++) {
1151 		if ((t = malloc(sizeof(*t))) == NULL) {
1152 			free(resp);
1153 			return (SNMP_ERR_GENERR);
1154 		}
1155 		strcpy(t->name, typelist->typeinfo[u].type_name);
1156 		t->index.subs[0] = strlen(t->name);
1157 		t->index.len = t->index.subs[0] + 1;
1158 		for (i = 0; i < t->index.subs[0]; i++)
1159 			t->index.subs[i + 1] = t->name[i];
1160 
1161 		INSERT_OBJECT_OID(t, &ngtype_list);
1162 	}
1163 
1164 	ngtype_tick = this_tick;
1165 
1166 	free(resp);
1167 	return (0);
1168 }
1169 
1170 /*
1171  * Try to load the netgraph type with the given name. We assume, that
1172  * type 'type' is implemented in the kernel module 'ng_type'.
1173  */
1174 static int
1175 ngtype_load(const u_char *name, size_t namelen)
1176 {
1177 	char *mod;
1178 	int ret;
1179 
1180 	if ((mod = malloc(namelen + 4)) == NULL)
1181 		return (-1);
1182 	strcpy(mod, "ng_");
1183 	strncpy(mod + 3, name, namelen);
1184 	mod[namelen + 3] = '\0';
1185 
1186 	ret = kldload(mod);
1187 	free(mod);
1188 	return (ret);
1189 }
1190 
1191 /*
1192  * Unload a netgraph type.
1193  */
1194 static int
1195 ngtype_unload(const u_char *name, size_t namelen)
1196 {
1197 	char *mod;
1198 	int id;
1199 
1200 	if ((mod = malloc(namelen + 4)) == NULL)
1201 		return (-1);
1202 	strcpy(mod, "ng_");
1203 	strncpy(mod + 3, name, namelen);
1204 	mod[namelen + 3] = '\0';
1205 
1206 	if ((id = kldfind(mod)) == -1) {
1207 		free(mod);
1208 		return (-1);
1209 	}
1210 	free(mod);
1211 	return (kldunload(id));
1212 }
1213 
1214 int
1215 op_ng_type(struct snmp_context *ctx, struct snmp_value *value,
1216     u_int sub, u_int iidx, enum snmp_op op)
1217 {
1218 	asn_subid_t which = value->var.subs[sub - 1];
1219 	struct ngtype *t;
1220 	u_char *name;
1221 	size_t namelen;
1222 	int status = 1;
1223 	int ret;
1224 
1225 	switch (op) {
1226 
1227 	  case SNMP_OP_GETNEXT:
1228 		if ((ret = fetch_types()) != 0)
1229 			return (ret);
1230 		if ((t = NEXT_OBJECT_OID(&ngtype_list, &value->var, sub)) == NULL)
1231 			return (SNMP_ERR_NOSUCHNAME);
1232 		index_append(&value->var, sub, &t->index);
1233 		break;
1234 
1235 	  case SNMP_OP_GET:
1236 		if ((ret = fetch_types()) != 0)
1237 			return (ret);
1238 		if ((t = FIND_OBJECT_OID(&ngtype_list, &value->var, sub)) == NULL)
1239 			return (SNMP_ERR_NOSUCHNAME);
1240 		break;
1241 
1242 	  case SNMP_OP_SET:
1243 		if (index_decode(&value->var, sub, iidx, &name, &namelen))
1244 			return (SNMP_ERR_NO_CREATION);
1245 		if (namelen == 0 || namelen >= NG_TYPESIZ) {
1246 			free(name);
1247 			return (SNMP_ERR_NO_CREATION);
1248 		}
1249 		if ((ret = fetch_types()) != 0) {
1250 			free(name);
1251 			return (ret);
1252 		}
1253 		t = FIND_OBJECT_OID(&ngtype_list, &value->var, sub);
1254 
1255 		if (which != LEAF_begemotNgTypeStatus) {
1256 			free(name);
1257 			if (t != NULL)
1258 				return (SNMP_ERR_NOT_WRITEABLE);
1259 			return (SNMP_ERR_NO_CREATION);
1260 		}
1261 		if (!TRUTH_OK(value->v.integer)) {
1262 			free(name);
1263 			return (SNMP_ERR_WRONG_VALUE);
1264 		}
1265 		ctx->scratch->int1 = TRUTH_GET(value->v.integer);
1266 		ctx->scratch->int1 |= (t != NULL) << 1;
1267 		ctx->scratch->ptr2 = name;
1268 		ctx->scratch->int2 = namelen;
1269 
1270 		if (t == NULL) {
1271 			/* type not loaded */
1272 			if (ctx->scratch->int1 & 1) {
1273 				/* request to load */
1274 				if (ngtype_load(name, namelen) == -1) {
1275 					free(name);
1276 					if (errno == ENOENT)
1277 						return (SNMP_ERR_INCONS_NAME);
1278 					else
1279 						return (SNMP_ERR_GENERR);
1280 				}
1281 			}
1282 		} else {
1283 			/* is type loaded */
1284 			if (!(ctx->scratch->int1 & 1)) {
1285 				/* request to unload */
1286 				if (ngtype_unload(name, namelen) == -1) {
1287 					free(name);
1288 					return (SNMP_ERR_GENERR);
1289 				}
1290 			}
1291 		}
1292 		return (SNMP_ERR_NOERROR);
1293 
1294 	  case SNMP_OP_ROLLBACK:
1295 		ret = SNMP_ERR_NOERROR;
1296 		if (!(ctx->scratch->int1 & 2)) {
1297 			/* did not exist */
1298 			if (ctx->scratch->int1 & 1) {
1299 				/* request to load - unload */
1300 				if (ngtype_unload(ctx->scratch->ptr2,
1301 				    ctx->scratch->int2) == -1)
1302 					ret = SNMP_ERR_UNDO_FAILED;
1303 			}
1304 		} else {
1305 			/* did exist */
1306 			if (!(ctx->scratch->int1 & 1)) {
1307 				/* request to unload - reload */
1308 				if (ngtype_load(ctx->scratch->ptr2,
1309 				    ctx->scratch->int2) == -1)
1310 					ret = SNMP_ERR_UNDO_FAILED;
1311 			}
1312 		}
1313 		free(ctx->scratch->ptr2);
1314 		return (ret);
1315 
1316 	  case SNMP_OP_COMMIT:
1317 		free(ctx->scratch->ptr2);
1318 		return (SNMP_ERR_NOERROR);
1319 
1320 	  default:
1321 		abort();
1322 	}
1323 
1324 	/*
1325 	 * Come here for GET and COMMIT
1326 	 */
1327 	switch (which) {
1328 
1329 	  case LEAF_begemotNgTypeStatus:
1330 		value->v.integer = status;
1331 		break;
1332 
1333 	  default:
1334 		abort();
1335 	}
1336 	return (SNMP_ERR_NOERROR);
1337 }
1338 
1339 /*
1340  * Implement the node table
1341  */
1342 static int
1343 find_node(const struct asn_oid *oid, u_int sub, struct nodeinfo *info)
1344 {
1345 	ng_ID_t id = oid->subs[sub];
1346 	struct ng_mesg *resp;
1347 
1348 	if ((resp = ng_dialog_id(id, NGM_GENERIC_COOKIE, NGM_NODEINFO,
1349 	    NULL, 0)) == NULL)
1350 		return (-1);
1351 
1352 	*info = *(struct nodeinfo *)(void *)resp->data;
1353 	free(resp);
1354 	return (0);
1355 }
1356 
1357 static int
1358 ncmp(const void *p1, const void *p2)
1359 {
1360 	const struct nodeinfo *i1 = p1;
1361 	const struct nodeinfo *i2 = p2;
1362 
1363 	if (i1->id < i2->id)
1364 		return (-1);
1365 	if (i1->id > i2->id)
1366 		return (+1);
1367 	return (0);
1368 }
1369 
1370 static int
1371 find_node_next(const struct asn_oid *oid, u_int sub, struct nodeinfo *info)
1372 {
1373 	u_int idxlen = oid->len - sub;
1374 	struct ng_mesg *resp;
1375 	struct namelist *list;
1376 	ng_ID_t id;
1377 	u_int i;
1378 
1379 	if ((resp = ng_dialog_id(snmp_node, NGM_GENERIC_COOKIE, NGM_LISTNODES,
1380 	    NULL, 0)) == NULL)
1381 		return (-1);
1382 	list = (struct namelist *)(void *)resp->data;
1383 
1384 	qsort(list->nodeinfo, list->numnames, sizeof(list->nodeinfo[0]), ncmp);
1385 
1386 	if (idxlen == 0) {
1387 		if (list->numnames == 0) {
1388 			free(resp);
1389 			return (-1);
1390 		}
1391 		*info = list->nodeinfo[0];
1392 		free(resp);
1393 		return (0);
1394 	}
1395 	id = oid->subs[sub];
1396 
1397 	for (i = 0; i < list->numnames; i++)
1398 		if (list->nodeinfo[i].id > id) {
1399 			*info = list->nodeinfo[i];
1400 			free(resp);
1401 			return (0);
1402 		}
1403 
1404 	free(resp);
1405 	return (-1);
1406 }
1407 
1408 int
1409 op_ng_node(struct snmp_context *ctx __unused, struct snmp_value *value,
1410     u_int sub, u_int iidx __unused, enum snmp_op op)
1411 {
1412 	asn_subid_t which = value->var.subs[sub - 1];
1413 	u_int idxlen = value->var.len - sub;
1414 	struct nodeinfo nodeinfo;
1415 
1416 	switch (op) {
1417 
1418 	  case SNMP_OP_GETNEXT:
1419 		if (find_node_next(&value->var, sub, &nodeinfo) == -1)
1420 			return (SNMP_ERR_NOSUCHNAME);
1421 		value->var.len = sub + 1;
1422 		value->var.subs[sub] = nodeinfo.id;
1423 		break;
1424 
1425 	  case SNMP_OP_GET:
1426 		if (idxlen != 1)
1427 			return (SNMP_ERR_NOSUCHNAME);
1428 		if (find_node(&value->var, sub, &nodeinfo) == -1)
1429 			return (SNMP_ERR_NOSUCHNAME);
1430 		break;
1431 
1432 	  case SNMP_OP_SET:
1433 		if (idxlen != 1)
1434 			return (SNMP_ERR_NO_CREATION);
1435 		if (find_node(&value->var, sub, &nodeinfo) == -1)
1436 			return (SNMP_ERR_NO_CREATION);
1437 		return (SNMP_ERR_NOT_WRITEABLE);
1438 
1439 	  case SNMP_OP_ROLLBACK:
1440 	  case SNMP_OP_COMMIT:
1441 	  default:
1442 		abort();
1443 	}
1444 
1445 	/*
1446 	 * Come here for GET and COMMIT
1447 	 */
1448 	switch (which) {
1449 
1450 	  case LEAF_begemotNgNodeStatus:
1451 		value->v.integer = 1;
1452 		break;
1453 	  case LEAF_begemotNgNodeName:
1454 		return (string_get(value, nodeinfo.name, -1));
1455 	  case LEAF_begemotNgNodeType:
1456 		return (string_get(value, nodeinfo.type, -1));
1457 	  case LEAF_begemotNgNodeHooks:
1458 		value->v.uint32 = nodeinfo.hooks;
1459 		break;
1460 
1461 	  default:
1462 		abort();
1463 	}
1464 	return (SNMP_ERR_NOERROR);
1465 }
1466 
1467 /*
1468  * Implement the hook table
1469  */
1470 static int
1471 find_hook(int32_t id, const u_char *hook, size_t hooklen, struct linkinfo *info)
1472 {
1473 	struct ng_mesg *resp;
1474 	struct hooklist *list;
1475 	u_int i;
1476 
1477 	if ((resp = ng_dialog_id(id, NGM_GENERIC_COOKIE,
1478 	    NGM_LISTHOOKS, NULL, 0)) == NULL)
1479 		return (-1);
1480 
1481 	list = (struct hooklist *)(void *)resp->data;
1482 
1483 	for (i = 0; i < list->nodeinfo.hooks; i++) {
1484 		if (strlen(list->link[i].ourhook) == hooklen &&
1485 		    strncmp(list->link[i].ourhook, hook, hooklen) == 0) {
1486 			*info = list->link[i];
1487 			free(resp);
1488 			return (0);
1489 		}
1490 	}
1491 	free(resp);
1492 	return (-1);
1493 }
1494 
1495 static int
1496 hook_cmp(const void *p1, const void *p2)
1497 {
1498 	const struct linkinfo *i1 = p1;
1499 	const struct linkinfo *i2 = p2;
1500 
1501 	if (strlen(i1->ourhook) < strlen(i2->ourhook))
1502 		return (-1);
1503 	if (strlen(i1->ourhook) > strlen(i2->ourhook))
1504 		return (+1);
1505 	return (strcmp(i1->ourhook, i2->ourhook));
1506 }
1507 
1508 static int
1509 find_hook_next(const struct asn_oid *oid, u_int sub, struct nodeinfo *nodeinfo,
1510     struct linkinfo *linkinfo)
1511 {
1512 	u_int idxlen = oid->len - sub;
1513 	struct namelist *list;
1514 	struct ng_mesg *resp;
1515 	struct hooklist *hooks;
1516 	struct ng_mesg *resp1;
1517 	u_int node_index;
1518 	struct asn_oid idx;
1519 	u_int i, j;
1520 
1521 	/*
1522 	 * Get and sort Node list
1523 	 */
1524 	if ((resp = ng_dialog_id(snmp_node, NGM_GENERIC_COOKIE, NGM_LISTNODES,
1525 	    NULL, 0)) == NULL)
1526 		return (-1);
1527 	list = (struct namelist *)(void *)resp->data;
1528 
1529 	qsort(list->nodeinfo, list->numnames, sizeof(list->nodeinfo[0]), ncmp);
1530 
1531 	/*
1532 	 * If we have no index, take the first node and return the
1533 	 * first hook.
1534 	 */
1535 	if (idxlen == 0) {
1536 		node_index = 0;
1537 		goto return_first_hook;
1538 	}
1539 
1540 	/*
1541 	 * Locate node
1542 	 */
1543 	for (node_index = 0; node_index < list->numnames; node_index++)
1544 		if (list->nodeinfo[node_index].id >= oid->subs[sub])
1545 			break;
1546 
1547 	/*
1548 	 * If we have only the node part of the index take, or
1549 	 * there is no node with that Id, take the first hook of that node.
1550 	 */
1551 	if (idxlen == 1 || node_index >= list->numnames ||
1552 	    list->nodeinfo[node_index].id > oid->subs[sub])
1553 		goto return_first_hook;
1554 
1555 	/*
1556 	 * We had an exact match on the node id and have (at last part)
1557 	 * of the hook name index. Loop through the hooks of the node
1558 	 * and find the next one.
1559 	 */
1560 	if ((resp1 = ng_dialog_id(list->nodeinfo[node_index].id,
1561 	    NGM_GENERIC_COOKIE, NGM_LISTHOOKS, NULL, 0)) == NULL) {
1562 		free(resp);
1563 		return (-1);
1564 	}
1565 	hooks = (struct hooklist *)(void *)resp1->data;
1566 	if (hooks->nodeinfo.hooks > 0) {
1567 		qsort(hooks->link, hooks->nodeinfo.hooks,
1568 		    sizeof(hooks->link[0]), hook_cmp);
1569 		for (i = 0; i < hooks->nodeinfo.hooks; i++) {
1570 			idx.len = strlen(hooks->link[i].ourhook) + 1;
1571 			idx.subs[0] = idx.len - 1;
1572 			for (j = 0; j < idx.len; j++)
1573 				idx.subs[j + 1] = hooks->link[i].ourhook[j];
1574 			if (index_compare(oid, sub + 1, &idx) < 0)
1575 				break;
1576 		}
1577 		if (i < hooks->nodeinfo.hooks) {
1578 			*nodeinfo = hooks->nodeinfo;
1579 			*linkinfo = hooks->link[i];
1580 
1581 			free(resp);
1582 			free(resp1);
1583 			return (0);
1584 		}
1585 	}
1586 
1587 	/* no hook found larger than the index on the index node - take
1588 	 * first hook of next node */
1589 	free(resp1);
1590 	node_index++;
1591 
1592   return_first_hook:
1593 	while (node_index < list->numnames) {
1594 		if ((resp1 = ng_dialog_id(list->nodeinfo[node_index].id,
1595 		    NGM_GENERIC_COOKIE, NGM_LISTHOOKS, NULL, 0)) == NULL)
1596 			break;
1597 		hooks = (struct hooklist *)(void *)resp1->data;
1598 		if (hooks->nodeinfo.hooks > 0) {
1599 			qsort(hooks->link, hooks->nodeinfo.hooks,
1600 			    sizeof(hooks->link[0]), hook_cmp);
1601 
1602 			*nodeinfo = hooks->nodeinfo;
1603 			*linkinfo = hooks->link[0];
1604 
1605 			free(resp);
1606 			free(resp1);
1607 			return (0);
1608 		}
1609 
1610 		/* if we don't have hooks, try next node */
1611 		free(resp1);
1612 		node_index++;
1613 	}
1614 
1615 	free(resp);
1616 	return (-1);
1617 }
1618 
1619 int
1620 op_ng_hook(struct snmp_context *ctx __unused, struct snmp_value *value,
1621     u_int sub, u_int iidx, enum snmp_op op)
1622 {
1623 	asn_subid_t which = value->var.subs[sub - 1];
1624 	struct linkinfo linkinfo;
1625 	struct nodeinfo nodeinfo;
1626 	u_int32_t lid;
1627 	u_char *hook;
1628 	size_t hooklen;
1629 	u_int i;
1630 
1631 	switch (op) {
1632 
1633 	  case SNMP_OP_GETNEXT:
1634 		if (find_hook_next(&value->var, sub, &nodeinfo, &linkinfo) == -1)
1635 			return (SNMP_ERR_NOSUCHNAME);
1636 
1637 		value->var.len = sub + 1 + 1 + strlen(linkinfo.ourhook);
1638 		value->var.subs[sub] = nodeinfo.id;
1639 		value->var.subs[sub + 1] = strlen(linkinfo.ourhook);
1640 		for (i = 0; i < strlen(linkinfo.ourhook); i++)
1641 			value->var.subs[sub + i + 2] =
1642 			    linkinfo.ourhook[i];
1643 		break;
1644 
1645 	  case SNMP_OP_GET:
1646 		if (index_decode(&value->var, sub, iidx, &lid,
1647 		    &hook, &hooklen))
1648 			return (SNMP_ERR_NOSUCHNAME);
1649 		if (find_hook(lid, hook, hooklen, &linkinfo) == -1) {
1650 			free(hook);
1651 			return (SNMP_ERR_NOSUCHNAME);
1652 		}
1653 		free(hook);
1654 		break;
1655 
1656 	  case SNMP_OP_SET:
1657 		if (index_decode(&value->var, sub, iidx, &lid,
1658 		    &hook, &hooklen))
1659 			return (SNMP_ERR_NO_CREATION);
1660 		if (find_hook(lid, hook, hooklen, &linkinfo) == -1) {
1661 			free(hook);
1662 			return (SNMP_ERR_NO_CREATION);
1663 		}
1664 		free(hook);
1665 		return (SNMP_ERR_NOT_WRITEABLE);
1666 
1667 	  case SNMP_OP_ROLLBACK:
1668 	  case SNMP_OP_COMMIT:
1669 	  default:
1670 		abort();
1671 
1672 	}
1673 
1674 	switch (which) {
1675 
1676 	  case LEAF_begemotNgHookStatus:
1677 		value->v.integer = 1;
1678 		break;
1679 	  case LEAF_begemotNgHookPeerNodeId:
1680 		value->v.uint32 = linkinfo.nodeinfo.id;
1681 		break;
1682 	  case LEAF_begemotNgHookPeerHook:
1683 		return (string_get(value, linkinfo.peerhook, -1));
1684 	  case LEAF_begemotNgHookPeerType:
1685 		return (string_get(value, linkinfo.nodeinfo.type, -1));
1686 	  default:
1687 		abort();
1688 	}
1689 	return (SNMP_ERR_NOERROR);
1690 }
1691