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