snmp_netgraph.c revision 122758
1255670Sdes/*
2113908Sdes * Copyright (c) 2001-2003
3113908Sdes *	Fraunhofer Institute for Open Communication Systems (FhG Fokus).
4113908Sdes *	All rights reserved.
5113908Sdes *
6126274Sdes * Author: Harti Brandt <harti@freebsd.org>
7113908Sdes *
8113908Sdes * Redistribution of this software and documentation and use in source and
9113908Sdes * binary forms, with or without modification, are permitted provided that
10113908Sdes * the following conditions are met:
11113908Sdes *
12113908Sdes * 1. Redistributions of source code or documentation must retain the above
13113908Sdes *    copyright notice, this list of conditions and the following disclaimer.
14113908Sdes * 2. Redistributions in binary form must reproduce the above copyright
15113908Sdes *    notice, this list of conditions and the following disclaimer in the
16113908Sdes *    documentation and/or other materials provided with the distribution.
17113908Sdes *
18113908Sdes * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS
19113908Sdes * AND ITS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
20113908Sdes * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
21113908Sdes * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
22113908Sdes * FRAUNHOFER FOKUS OR ITS CONTRIBUTORS  BE LIABLE FOR ANY DIRECT, INDIRECT,
23113908Sdes * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24113908Sdes * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
25113908Sdes * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26113908Sdes * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27113908Sdes * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28113908Sdes * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29113908Sdes *
30113908Sdes * $FreeBSD: head/usr.sbin/bsnmpd/modules/snmp_netgraph/snmp_netgraph.c 122758 2003-11-15 15:26:35Z harti $
31113908Sdes *
32113908Sdes * Netgraph interface for SNMPd.
33113908Sdes */
34113908Sdes#include <sys/types.h>
35113908Sdes#include <sys/param.h>
36113908Sdes#include <sys/linker.h>
37113908Sdes#include <sys/socket.h>
38204861Sdes#include <sys/syslog.h>
39113908Sdes#include <sys/queue.h>
40113908Sdes#include <sys/sysctl.h>
41113908Sdes#include <stdio.h>
42204861Sdes#include <stdlib.h>
43113908Sdes#include <errno.h>
44113908Sdes#include <unistd.h>
45113908Sdes#include <string.h>
46204861Sdes#include <netgraph.h>
47113908Sdes#include "snmpmod.h"
48113908Sdes#include "snmp_netgraph.h"
49113908Sdes#include "netgraph_tree.h"
50204861Sdes#include "netgraph_oid.h"
51113908Sdes
52113908Sdes/* maximum message size */
53113908Sdes#define RESBUFSIZ	20000
54113908Sdes
55113908Sdes/* default node name */
56#define NODENAME	"NgSnmpd"
57
58/* my node Id */
59ng_ID_t snmp_node;
60u_char *snmp_nodename;
61
62/* the Object Resource registration index */
63static u_int reg_index;
64static 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 */
69static size_t resbufsiz = RESBUFSIZ;
70static u_int timeout = 1000;
71static u_int debug_level;
72
73/* number of microseconds per clock tick */
74static 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. */
79struct csock_buf {
80	STAILQ_ENTRY(csock_buf) link;
81	struct ng_mesg *mesg;
82	char path[NG_PATHSIZ];
83};
84static 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 */
91struct 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};
99static SLIST_HEAD(, msgreg) msgreg_list =
100	SLIST_HEAD_INITIALIZER(msgreg_list);
101
102/*
103 * Data messages are dispatched by hook names.
104 */
105struct 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};
112static SLIST_HEAD(, datareg) datareg_list =
113	SLIST_HEAD_INITIALIZER(datareg_list);
114
115/* the netgraph sockets */
116static int csock, dsock;
117static void *csock_fd, *dsock_fd;
118
119/* our module handle */
120static struct lmodule *module;
121
122/* statistics */
123static u_int32_t stats[LEAF_begemotNgTooLargeDatas+1];
124
125/* netgraph type list */
126struct ngtype {
127	char		name[NG_TYPESIZ];
128	struct asn_oid	index;
129	TAILQ_ENTRY(ngtype) link;
130};
131TAILQ_HEAD(ngtype_list, ngtype);
132
133static struct ngtype_list ngtype_list;
134static u_int32_t ngtype_tick;
135
136
137/*
138 * Register a function to receive unsolicited messages
139 */
140void *
141ng_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 */
163void
164ng_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 */
175void *
176ng_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 */
197void
198ng_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 */
210void
211ng_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 */
241static void
242csock_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 */
268static struct ng_mesg *
269csock_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
306static void
307csock_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 */
321int
322ng_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}
327int
328ng_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}
336int
337ng_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 */
352struct ng_mesg *
353ng_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}
419struct ng_mesg *
420ng_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}
428struct ng_mesg *
429ng_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 */
442int
443ng_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 */
451static void
452dsock_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 */
501static void
502ng_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 */
520static int
521ng_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 */
553ng_ID_t
554ng_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}
566ng_ID_t
567ng_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}
579ng_ID_t
580ng_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}
592ng_ID_t
593ng_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 */
608int
609ng_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}
619int
620ng_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
631int
632ng_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
647int
648ng_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 */
673static ng_ID_t
674ng_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 */
723ng_ID_t
724ng_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
729ng_ID_t
730ng_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)) == NULL)
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 */
762int
763ng_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 */
775int
776ng_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 */
788int
789ng_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 */
804int
805ng_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 */
861int
862ng_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 */
909static void
910ng_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/*
935 * Called, when the module is to be unloaded after it was successfully loaded
936 */
937static int
938ng_fini(void)
939{
940	struct ngtype *t;
941
942	while ((t = TAILQ_FIRST(&ngtype_list)) != NULL) {
943		TAILQ_REMOVE(&ngtype_list, t, link);
944		free(t);
945	}
946
947	if (csock_fd != NULL)
948		fd_deselect(csock_fd);
949	(void)close(csock);
950
951	if (dsock_fd != NULL)
952		fd_deselect(dsock_fd);
953	(void)close(dsock);
954
955	free(snmp_nodename);
956
957	or_unregister(reg_index);
958
959	return (0);
960}
961
962const struct snmp_module config = {
963	"This module implements access to the netgraph sub-system",
964	ng_init,
965	ng_fini,
966	ng_idle,
967	NULL,
968	NULL,
969	ng_start,
970	NULL,
971	netgraph_ctree,
972	netgraph_CTREE_SIZE,
973	NULL
974};
975
976int
977op_ng_config(struct snmp_context *ctx, struct snmp_value *value,
978    u_int sub, u_int iidx __unused, enum snmp_op op)
979{
980	asn_subid_t which = value->var.subs[sub - 1];
981	int ret;
982
983	switch (op) {
984
985	  case SNMP_OP_GETNEXT:
986		abort();
987
988	  case SNMP_OP_GET:
989		/*
990		 * Come here for GET, GETNEXT and COMMIT
991		 */
992		switch (which) {
993
994		  case LEAF_begemotNgControlNodeName:
995			return (string_get(value, snmp_nodename, -1));
996
997		  case LEAF_begemotNgResBufSiz:
998			value->v.integer = resbufsiz;
999			break;
1000
1001		  case LEAF_begemotNgTimeout:
1002			value->v.integer = timeout;
1003			break;
1004
1005		  case LEAF_begemotNgDebugLevel:
1006			value->v.uint32 = debug_level;
1007			break;
1008
1009		  default:
1010			abort();
1011		}
1012		return (SNMP_ERR_NOERROR);
1013
1014	  case SNMP_OP_SET:
1015		switch (which) {
1016
1017		  case LEAF_begemotNgControlNodeName:
1018			/* only at initialisation */
1019			if (community != COMM_INITIALIZE)
1020				return (SNMP_ERR_NOT_WRITEABLE);
1021
1022			if (snmp_node != 0)
1023				return (SNMP_ERR_NOT_WRITEABLE);
1024
1025			if ((ret = string_save(value, ctx, -1, &snmp_nodename))
1026			    != SNMP_ERR_NOERROR)
1027				return (ret);
1028
1029			if (NgMkSockNode(snmp_nodename, &csock, &dsock) < 0) {
1030				syslog(LOG_ERR, "NgMkSockNode: %m");
1031				string_rollback(ctx, &snmp_nodename);
1032				return (SNMP_ERR_GENERR);
1033			}
1034			snmp_node = ng_node_id(".:");
1035
1036			return (SNMP_ERR_NOERROR);
1037
1038		  case LEAF_begemotNgResBufSiz:
1039			ctx->scratch->int1 = resbufsiz;
1040			if (value->v.integer < 1024 ||
1041			    value->v.integer > 0x10000)
1042				return (SNMP_ERR_WRONG_VALUE);
1043			resbufsiz = value->v.integer;
1044			return (SNMP_ERR_NOERROR);
1045
1046		  case LEAF_begemotNgTimeout:
1047			ctx->scratch->int1 = timeout;
1048			if (value->v.integer < 10 ||
1049			    value->v.integer > 10000)
1050				return (SNMP_ERR_WRONG_VALUE);
1051			timeout = value->v.integer;
1052			return (SNMP_ERR_NOERROR);
1053
1054		  case LEAF_begemotNgDebugLevel:
1055			ctx->scratch->int1 = debug_level;
1056			debug_level = value->v.uint32;
1057			NgSetDebug(debug_level);
1058			return (SNMP_ERR_NOERROR);
1059		}
1060		abort();
1061
1062	  case SNMP_OP_ROLLBACK:
1063		switch (which) {
1064
1065		  case LEAF_begemotNgControlNodeName:
1066			string_rollback(ctx, &snmp_nodename);
1067			close(csock);
1068			close(dsock);
1069			snmp_node = 0;
1070			return (SNMP_ERR_NOERROR);
1071
1072		  case LEAF_begemotNgResBufSiz:
1073			resbufsiz = ctx->scratch->int1;
1074			return (SNMP_ERR_NOERROR);
1075
1076		  case LEAF_begemotNgTimeout:
1077			timeout = ctx->scratch->int1;
1078			return (SNMP_ERR_NOERROR);
1079
1080		  case LEAF_begemotNgDebugLevel:
1081			debug_level = ctx->scratch->int1;
1082			NgSetDebug(debug_level);
1083			return (SNMP_ERR_NOERROR);
1084		}
1085		abort();
1086
1087	  case SNMP_OP_COMMIT:
1088		switch (which) {
1089
1090		  case LEAF_begemotNgControlNodeName:
1091			string_commit(ctx);
1092			return (SNMP_ERR_NOERROR);
1093
1094		  case LEAF_begemotNgResBufSiz:
1095		  case LEAF_begemotNgTimeout:
1096		  case LEAF_begemotNgDebugLevel:
1097			return (SNMP_ERR_NOERROR);
1098		}
1099		abort();
1100	}
1101	abort();
1102}
1103
1104int
1105op_ng_stats(struct snmp_context *ctx __unused, struct snmp_value *value,
1106    u_int sub, u_int iidx __unused, enum snmp_op op)
1107{
1108	switch (op) {
1109
1110	  case SNMP_OP_GETNEXT:
1111		abort();
1112
1113	  case SNMP_OP_GET:
1114		value->v.uint32 = stats[value->var.subs[sub - 1] - 1];
1115		return (SNMP_ERR_NOERROR);
1116
1117	  case SNMP_OP_SET:
1118		return (SNMP_ERR_NOT_WRITEABLE);
1119
1120	  case SNMP_OP_ROLLBACK:
1121	  case SNMP_OP_COMMIT:
1122		abort();
1123	}
1124	abort();
1125}
1126
1127/*
1128 * Netgraph type table
1129 */
1130static int
1131fetch_types(void)
1132{
1133	struct ngtype *t;
1134	struct typelist *typelist;
1135	struct ng_mesg *resp;
1136	u_int u, i;
1137
1138	if (this_tick <= ngtype_tick)
1139		return (0);
1140
1141	while ((t = TAILQ_FIRST(&ngtype_list)) != NULL) {
1142		TAILQ_REMOVE(&ngtype_list, t, link);
1143		free(t);
1144	}
1145
1146	if ((resp = ng_dialog_id(snmp_node, NGM_GENERIC_COOKIE,
1147	    NGM_LISTTYPES, NULL, 0)) == NULL)
1148		return (SNMP_ERR_GENERR);
1149	typelist = (struct typelist *)(void *)resp->data;
1150
1151	for (u = 0; u < typelist->numtypes; u++) {
1152		if ((t = malloc(sizeof(*t))) == NULL) {
1153			free(resp);
1154			return (SNMP_ERR_GENERR);
1155		}
1156		strcpy(t->name, typelist->typeinfo[u].type_name);
1157		t->index.subs[0] = strlen(t->name);
1158		t->index.len = t->index.subs[0] + 1;
1159		for (i = 0; i < t->index.subs[0]; i++)
1160			t->index.subs[i + 1] = t->name[i];
1161
1162		INSERT_OBJECT_OID(t, &ngtype_list);
1163	}
1164
1165	ngtype_tick = this_tick;
1166
1167	free(resp);
1168	return (0);
1169}
1170
1171/*
1172 * Try to load the netgraph type with the given name. We assume, that
1173 * type 'type' is implemented in the kernel module 'ng_type'.
1174 */
1175static int
1176ngtype_load(const u_char *name, size_t namelen)
1177{
1178	char *mod;
1179	int ret;
1180
1181	if ((mod = malloc(namelen + 4)) == NULL)
1182		return (-1);
1183	strcpy(mod, "ng_");
1184	strncpy(mod + 3, name, namelen);
1185	mod[namelen + 3] = '\0';
1186
1187	ret = kldload(mod);
1188	free(mod);
1189	return (ret);
1190}
1191
1192/*
1193 * Unload a netgraph type.
1194 */
1195static int
1196ngtype_unload(const u_char *name, size_t namelen)
1197{
1198	char *mod;
1199	int id;
1200
1201	if ((mod = malloc(namelen + 4)) == NULL)
1202		return (-1);
1203	strcpy(mod, "ng_");
1204	strncpy(mod + 3, name, namelen);
1205	mod[namelen + 3] = '\0';
1206
1207	if ((id = kldfind(mod)) == -1) {
1208		free(mod);
1209		return (-1);
1210	}
1211	free(mod);
1212	return (kldunload(id));
1213}
1214
1215int
1216op_ng_type(struct snmp_context *ctx, struct snmp_value *value,
1217    u_int sub, u_int iidx, enum snmp_op op)
1218{
1219	asn_subid_t which = value->var.subs[sub - 1];
1220	struct ngtype *t;
1221	u_char *name;
1222	size_t namelen;
1223	int status = 1;
1224	int ret;
1225
1226	switch (op) {
1227
1228	  case SNMP_OP_GETNEXT:
1229		if ((ret = fetch_types()) != 0)
1230			return (ret);
1231		if ((t = NEXT_OBJECT_OID(&ngtype_list, &value->var, sub)) == NULL)
1232			return (SNMP_ERR_NOSUCHNAME);
1233		index_append(&value->var, sub, &t->index);
1234		break;
1235
1236	  case SNMP_OP_GET:
1237		if ((ret = fetch_types()) != 0)
1238			return (ret);
1239		if ((t = FIND_OBJECT_OID(&ngtype_list, &value->var, sub)) == NULL)
1240			return (SNMP_ERR_NOSUCHNAME);
1241		break;
1242
1243	  case SNMP_OP_SET:
1244		if (index_decode(&value->var, sub, iidx, &name, &namelen))
1245			return (SNMP_ERR_NO_CREATION);
1246		if (namelen == 0 || namelen >= NG_TYPESIZ) {
1247			free(name);
1248			return (SNMP_ERR_NO_CREATION);
1249		}
1250		if ((ret = fetch_types()) != 0) {
1251			free(name);
1252			return (ret);
1253		}
1254		t = FIND_OBJECT_OID(&ngtype_list, &value->var, sub);
1255
1256		if (which != LEAF_begemotNgTypeStatus) {
1257			free(name);
1258			if (t != NULL)
1259				return (SNMP_ERR_NOT_WRITEABLE);
1260			return (SNMP_ERR_NO_CREATION);
1261		}
1262		if (!TRUTH_OK(value->v.integer)) {
1263			free(name);
1264			return (SNMP_ERR_WRONG_VALUE);
1265		}
1266		ctx->scratch->int1 = TRUTH_GET(value->v.integer);
1267		ctx->scratch->int1 |= (t != NULL) << 1;
1268		ctx->scratch->ptr2 = name;
1269		ctx->scratch->int2 = namelen;
1270
1271		if (t == NULL) {
1272			/* type not loaded */
1273			if (ctx->scratch->int1 & 1) {
1274				/* request to load */
1275				if (ngtype_load(name, namelen) == -1) {
1276					free(name);
1277					if (errno == ENOENT)
1278						return (SNMP_ERR_INCONS_NAME);
1279					else
1280						return (SNMP_ERR_GENERR);
1281				}
1282			}
1283		} else {
1284			/* is type loaded */
1285			if (!(ctx->scratch->int1 & 1)) {
1286				/* request to unload */
1287				if (ngtype_unload(name, namelen) == -1) {
1288					free(name);
1289					return (SNMP_ERR_GENERR);
1290				}
1291			}
1292		}
1293		return (SNMP_ERR_NOERROR);
1294
1295	  case SNMP_OP_ROLLBACK:
1296		ret = SNMP_ERR_NOERROR;
1297		if (!(ctx->scratch->int1 & 2)) {
1298			/* did not exist */
1299			if (ctx->scratch->int1 & 1) {
1300				/* request to load - unload */
1301				if (ngtype_unload(ctx->scratch->ptr2,
1302				    ctx->scratch->int2) == -1)
1303					ret = SNMP_ERR_UNDO_FAILED;
1304			}
1305		} else {
1306			/* did exist */
1307			if (!(ctx->scratch->int1 & 1)) {
1308				/* request to unload - reload */
1309				if (ngtype_load(ctx->scratch->ptr2,
1310				    ctx->scratch->int2) == -1)
1311					ret = SNMP_ERR_UNDO_FAILED;
1312			}
1313		}
1314		free(ctx->scratch->ptr2);
1315		return (ret);
1316
1317	  case SNMP_OP_COMMIT:
1318		free(ctx->scratch->ptr2);
1319		return (SNMP_ERR_NOERROR);
1320
1321	  default:
1322		abort();
1323	}
1324
1325	/*
1326	 * Come here for GET and COMMIT
1327	 */
1328	switch (which) {
1329
1330	  case LEAF_begemotNgTypeStatus:
1331		value->v.integer = status;
1332		break;
1333
1334	  default:
1335		abort();
1336	}
1337	return (SNMP_ERR_NOERROR);
1338}
1339
1340/*
1341 * Implement the node table
1342 */
1343static int
1344find_node(const struct asn_oid *oid, u_int sub, struct nodeinfo *info)
1345{
1346	ng_ID_t id = oid->subs[sub];
1347	struct ng_mesg *resp;
1348
1349	if ((resp = ng_dialog_id(id, NGM_GENERIC_COOKIE, NGM_NODEINFO,
1350	    NULL, 0)) == NULL)
1351		return (-1);
1352
1353	*info = *(struct nodeinfo *)(void *)resp->data;
1354	free(resp);
1355	return (0);
1356}
1357
1358static int
1359ncmp(const void *p1, const void *p2)
1360{
1361	const struct nodeinfo *i1 = p1;
1362	const struct nodeinfo *i2 = p2;
1363
1364	if (i1->id < i2->id)
1365		return (-1);
1366	if (i1->id > i2->id)
1367		return (+1);
1368	return (0);
1369}
1370
1371static int
1372find_node_next(const struct asn_oid *oid, u_int sub, struct nodeinfo *info)
1373{
1374	u_int idxlen = oid->len - sub;
1375	struct ng_mesg *resp;
1376	struct namelist *list;
1377	ng_ID_t id;
1378	u_int i;
1379
1380	if ((resp = ng_dialog_id(snmp_node, NGM_GENERIC_COOKIE, NGM_LISTNODES,
1381	    NULL, 0)) == NULL)
1382		return (-1);
1383	list = (struct namelist *)(void *)resp->data;
1384
1385	qsort(list->nodeinfo, list->numnames, sizeof(list->nodeinfo[0]), ncmp);
1386
1387	if (idxlen == 0) {
1388		if (list->numnames == 0) {
1389			free(resp);
1390			return (-1);
1391		}
1392		*info = list->nodeinfo[0];
1393		free(resp);
1394		return (0);
1395	}
1396	id = oid->subs[sub];
1397
1398	for (i = 0; i < list->numnames; i++)
1399		if (list->nodeinfo[i].id > id) {
1400			*info = list->nodeinfo[i];
1401			free(resp);
1402			return (0);
1403		}
1404
1405	free(resp);
1406	return (-1);
1407}
1408
1409int
1410op_ng_node(struct snmp_context *ctx __unused, struct snmp_value *value,
1411    u_int sub, u_int iidx __unused, enum snmp_op op)
1412{
1413	asn_subid_t which = value->var.subs[sub - 1];
1414	u_int idxlen = value->var.len - sub;
1415	struct nodeinfo nodeinfo;
1416
1417	switch (op) {
1418
1419	  case SNMP_OP_GETNEXT:
1420		if (find_node_next(&value->var, sub, &nodeinfo) == -1)
1421			return (SNMP_ERR_NOSUCHNAME);
1422		value->var.len = sub + 1;
1423		value->var.subs[sub] = nodeinfo.id;
1424		break;
1425
1426	  case SNMP_OP_GET:
1427		if (idxlen != 1)
1428			return (SNMP_ERR_NOSUCHNAME);
1429		if (find_node(&value->var, sub, &nodeinfo) == -1)
1430			return (SNMP_ERR_NOSUCHNAME);
1431		break;
1432
1433	  case SNMP_OP_SET:
1434		if (idxlen != 1)
1435			return (SNMP_ERR_NO_CREATION);
1436		if (find_node(&value->var, sub, &nodeinfo) == -1)
1437			return (SNMP_ERR_NO_CREATION);
1438		return (SNMP_ERR_NOT_WRITEABLE);
1439
1440	  case SNMP_OP_ROLLBACK:
1441	  case SNMP_OP_COMMIT:
1442	  default:
1443		abort();
1444	}
1445
1446	/*
1447	 * Come here for GET and COMMIT
1448	 */
1449	switch (which) {
1450
1451	  case LEAF_begemotNgNodeStatus:
1452		value->v.integer = 1;
1453		break;
1454	  case LEAF_begemotNgNodeName:
1455		return (string_get(value, nodeinfo.name, -1));
1456	  case LEAF_begemotNgNodeType:
1457		return (string_get(value, nodeinfo.type, -1));
1458	  case LEAF_begemotNgNodeHooks:
1459		value->v.uint32 = nodeinfo.hooks;
1460		break;
1461
1462	  default:
1463		abort();
1464	}
1465	return (SNMP_ERR_NOERROR);
1466}
1467
1468/*
1469 * Implement the hook table
1470 */
1471static int
1472find_hook(int32_t id, const u_char *hook, size_t hooklen, struct linkinfo *info)
1473{
1474	struct ng_mesg *resp;
1475	struct hooklist *list;
1476	u_int i;
1477
1478	if ((resp = ng_dialog_id(id, NGM_GENERIC_COOKIE,
1479	    NGM_LISTHOOKS, NULL, 0)) == NULL)
1480		return (-1);
1481
1482	list = (struct hooklist *)(void *)resp->data;
1483
1484	for (i = 0; i < list->nodeinfo.hooks; i++) {
1485		if (strlen(list->link[i].ourhook) == hooklen &&
1486		    strncmp(list->link[i].ourhook, hook, hooklen) == 0) {
1487			*info = list->link[i];
1488			free(resp);
1489			return (0);
1490		}
1491	}
1492	free(resp);
1493	return (-1);
1494}
1495
1496static int
1497hook_cmp(const void *p1, const void *p2)
1498{
1499	const struct linkinfo *i1 = p1;
1500	const struct linkinfo *i2 = p2;
1501
1502	if (strlen(i1->ourhook) < strlen(i2->ourhook))
1503		return (-1);
1504	if (strlen(i1->ourhook) > strlen(i2->ourhook))
1505		return (+1);
1506	return (strcmp(i1->ourhook, i2->ourhook));
1507}
1508
1509static int
1510find_hook_next(const struct asn_oid *oid, u_int sub, struct nodeinfo *nodeinfo,
1511    struct linkinfo *linkinfo)
1512{
1513	u_int idxlen = oid->len - sub;
1514	struct namelist *list;
1515	struct ng_mesg *resp;
1516	struct hooklist *hooks;
1517	struct ng_mesg *resp1;
1518	u_int node_index;
1519	struct asn_oid idx;
1520	u_int i, j;
1521
1522	/*
1523	 * Get and sort Node list
1524	 */
1525	if ((resp = ng_dialog_id(snmp_node, NGM_GENERIC_COOKIE, NGM_LISTNODES,
1526	    NULL, 0)) == NULL)
1527		return (-1);
1528	list = (struct namelist *)(void *)resp->data;
1529
1530	qsort(list->nodeinfo, list->numnames, sizeof(list->nodeinfo[0]), ncmp);
1531
1532	/*
1533	 * If we have no index, take the first node and return the
1534	 * first hook.
1535	 */
1536	if (idxlen == 0) {
1537		node_index = 0;
1538		goto return_first_hook;
1539	}
1540
1541	/*
1542	 * Locate node
1543	 */
1544	for (node_index = 0; node_index < list->numnames; node_index++)
1545		if (list->nodeinfo[node_index].id >= oid->subs[sub])
1546			break;
1547
1548	/*
1549	 * If we have only the node part of the index take, or
1550	 * there is no node with that Id, take the first hook of that node.
1551	 */
1552	if (idxlen == 1 || node_index >= list->numnames ||
1553	    list->nodeinfo[node_index].id > oid->subs[sub])
1554		goto return_first_hook;
1555
1556	/*
1557	 * We had an exact match on the node id and have (at last part)
1558	 * of the hook name index. Loop through the hooks of the node
1559	 * and find the next one.
1560	 */
1561	if ((resp1 = ng_dialog_id(list->nodeinfo[node_index].id,
1562	    NGM_GENERIC_COOKIE, NGM_LISTHOOKS, NULL, 0)) == NULL) {
1563		free(resp);
1564		return (-1);
1565	}
1566	hooks = (struct hooklist *)(void *)resp1->data;
1567	if (hooks->nodeinfo.hooks > 0) {
1568		qsort(hooks->link, hooks->nodeinfo.hooks,
1569		    sizeof(hooks->link[0]), hook_cmp);
1570		for (i = 0; i < hooks->nodeinfo.hooks; i++) {
1571			idx.len = strlen(hooks->link[i].ourhook) + 1;
1572			idx.subs[0] = idx.len - 1;
1573			for (j = 0; j < idx.len; j++)
1574				idx.subs[j + 1] = hooks->link[i].ourhook[j];
1575			if (index_compare(oid, sub + 1, &idx) < 0)
1576				break;
1577		}
1578		if (i < hooks->nodeinfo.hooks) {
1579			*nodeinfo = hooks->nodeinfo;
1580			*linkinfo = hooks->link[i];
1581
1582			free(resp);
1583			free(resp1);
1584			return (0);
1585		}
1586	}
1587
1588	/* no hook found larger than the index on the index node - take
1589	 * first hook of next node */
1590	free(resp1);
1591	node_index++;
1592
1593  return_first_hook:
1594	while (node_index < list->numnames) {
1595		if ((resp1 = ng_dialog_id(list->nodeinfo[node_index].id,
1596		    NGM_GENERIC_COOKIE, NGM_LISTHOOKS, NULL, 0)) == NULL)
1597			break;
1598		hooks = (struct hooklist *)(void *)resp1->data;
1599		if (hooks->nodeinfo.hooks > 0) {
1600			qsort(hooks->link, hooks->nodeinfo.hooks,
1601			    sizeof(hooks->link[0]), hook_cmp);
1602
1603			*nodeinfo = hooks->nodeinfo;
1604			*linkinfo = hooks->link[0];
1605
1606			free(resp);
1607			free(resp1);
1608			return (0);
1609		}
1610
1611		/* if we don't have hooks, try next node */
1612		free(resp1);
1613		node_index++;
1614	}
1615
1616	free(resp);
1617	return (-1);
1618}
1619
1620int
1621op_ng_hook(struct snmp_context *ctx __unused, struct snmp_value *value,
1622    u_int sub, u_int iidx, enum snmp_op op)
1623{
1624	asn_subid_t which = value->var.subs[sub - 1];
1625	struct linkinfo linkinfo;
1626	struct nodeinfo nodeinfo;
1627	u_int32_t lid;
1628	u_char *hook;
1629	size_t hooklen;
1630	u_int i;
1631
1632	switch (op) {
1633
1634	  case SNMP_OP_GETNEXT:
1635		if (find_hook_next(&value->var, sub, &nodeinfo, &linkinfo) == -1)
1636			return (SNMP_ERR_NOSUCHNAME);
1637
1638		value->var.len = sub + 1 + 1 + strlen(linkinfo.ourhook);
1639		value->var.subs[sub] = nodeinfo.id;
1640		value->var.subs[sub + 1] = strlen(linkinfo.ourhook);
1641		for (i = 0; i < strlen(linkinfo.ourhook); i++)
1642			value->var.subs[sub + i + 2] =
1643			    linkinfo.ourhook[i];
1644		break;
1645
1646	  case SNMP_OP_GET:
1647		if (index_decode(&value->var, sub, iidx, &lid,
1648		    &hook, &hooklen))
1649			return (SNMP_ERR_NOSUCHNAME);
1650		if (find_hook(lid, hook, hooklen, &linkinfo) == -1) {
1651			free(hook);
1652			return (SNMP_ERR_NOSUCHNAME);
1653		}
1654		free(hook);
1655		break;
1656
1657	  case SNMP_OP_SET:
1658		if (index_decode(&value->var, sub, iidx, &lid,
1659		    &hook, &hooklen))
1660			return (SNMP_ERR_NO_CREATION);
1661		if (find_hook(lid, hook, hooklen, &linkinfo) == -1) {
1662			free(hook);
1663			return (SNMP_ERR_NO_CREATION);
1664		}
1665		free(hook);
1666		return (SNMP_ERR_NOT_WRITEABLE);
1667
1668	  case SNMP_OP_ROLLBACK:
1669	  case SNMP_OP_COMMIT:
1670	  default:
1671		abort();
1672
1673	}
1674
1675	switch (which) {
1676
1677	  case LEAF_begemotNgHookStatus:
1678		value->v.integer = 1;
1679		break;
1680	  case LEAF_begemotNgHookPeerNodeId:
1681		value->v.uint32 = linkinfo.nodeinfo.id;
1682		break;
1683	  case LEAF_begemotNgHookPeerHook:
1684		return (string_get(value, linkinfo.peerhook, -1));
1685	  case LEAF_begemotNgHookPeerType:
1686		return (string_get(value, linkinfo.nodeinfo.type, -1));
1687	  default:
1688		abort();
1689	}
1690	return (SNMP_ERR_NOERROR);
1691}
1692