gensnmptree.c revision 122394
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 * 3. Neither the name of the Institute nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS
22 * AND ITS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
23 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
24 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
25 * FRAUNHOFER FOKUS OR ITS CONTRIBUTORS  BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
28 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
29 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
31 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 *
33 * $Begemot: bsnmp/gensnmptree/gensnmptree.c,v 1.34 2003/01/28 13:44:34 hbb Exp $
34 *
35 * Generate OID table from table description.
36 *
37 * Syntax is:
38 * ---------
39 * tree := head elements ')'
40 *
41 * entry := head ':' index STRING elements ')'
42 *
43 * leaf := head TYPE STRING ACCESS ')'
44 *
45 * column := head TYPE ACCESS ')'
46 *
47 * head := '(' INT STRING
48 *
49 * elements := EMPTY | elements element
50 *
51 * element := tree | leaf
52 *
53 * index := TYPE | index TYPE
54 *
55 */
56#include <sys/types.h>
57#include <sys/param.h>
58#include <stdio.h>
59#include <stdlib.h>
60#include <stdarg.h>
61#include <unistd.h>
62#include <string.h>
63#include <ctype.h>
64#include <err.h>
65#include <sys/queue.h>
66#include "asn1.h"
67#include "snmp.h"
68#include "snmpagent.h"
69
70/*
71 * Constant prefix for all OIDs
72 */
73static const asn_subid_t prefix[] = { 1, 3, 6 };
74#define	PREFIX_LEN	(sizeof(prefix) / sizeof(prefix[0]))
75
76u_int tree_size;
77static const char *file_prefix = "";
78static FILE *fp;
79
80/* if true generate local include paths */
81static int localincs = 0;
82
83static const char usgtxt[] = "\
84Generate SNMP tables. Copyright (c) 2001-2002 Fraunhofer Institute for\n\
85Open Communication Systems (FhG Fokus). All rights reserved.\n\
86usage: gensnmptree [-hel] [-p prefix] [name]...\n\
87options:\n\
88  -h		print this info\n\
89  -e		extrace the named oids\n\
90  -l		generate local include directives\n\
91  -p prefix	prepend prefix to file and variable names\n\
92";
93
94/*
95 * A node in the OID tree
96 */
97enum ntype {
98	NODE_LEAF = 1,
99	NODE_TREE,
100	NODE_ENTRY,
101	NODE_COLUMN
102};
103
104enum {
105	FL_GET	= 0x01,
106	FL_SET	= 0x02,
107};
108
109struct node;
110TAILQ_HEAD(node_list, node);
111
112struct node {
113	enum ntype	type;
114	asn_subid_t	id;	/* last element of OID */
115	char		*name;	/* name of node */
116	TAILQ_ENTRY(node) link;
117	u_int		lno;	/* starting line number */
118	u_int		flags;	/* allowed operations */
119
120	union {
121	  struct tree {
122	    struct node_list subs;
123	  }		tree;
124
125	  struct entry {
126	    u_int32_t	index;	/* index for table entry */
127	    char	*func;	/* function for tables */
128	    struct node_list subs;
129	  }		entry;
130
131	  struct leaf {
132	    enum snmp_syntax syntax;	/* syntax for this leaf */
133	    char	*func;		/* function name */
134	  }		leaf;
135
136	  struct column {
137	    enum snmp_syntax syntax;	/* syntax for this column */
138	  }		column;
139	}		u;
140};
141
142struct func {
143	const char	*name;
144	LIST_ENTRY(func) link;
145};
146
147static LIST_HEAD(, func) funcs = LIST_HEAD_INITIALIZER(funcs);
148
149/************************************************************
150 *
151 * Allocate memory and panic just in the case...
152 */
153static void *
154xalloc(size_t size)
155{
156	void *ptr;
157
158	if ((ptr = malloc(size)) == NULL)
159		err(1, "allocing %u bytes", size);
160
161	return (ptr);
162}
163
164/************************************************************
165 *
166 * Parsing input
167 */
168enum tok {
169	TOK_EOF = 0200,	/* end-of-file seen */
170	TOK_NUM,	/* number */
171	TOK_STR,	/* string */
172	TOK_ACCESS,	/* access operator */
173	TOK_TYPE,	/* type operator */
174};
175
176static const struct {
177	const char *str;
178	enum tok tok;
179	u_int val;
180} keywords[] = {
181	{ "GET", TOK_ACCESS, FL_GET },
182	{ "SET", TOK_ACCESS, FL_SET },
183	{ "NULL", TOK_TYPE, SNMP_SYNTAX_NULL },
184	{ "INTEGER", TOK_TYPE, SNMP_SYNTAX_INTEGER },
185	{ "INTEGER32", TOK_TYPE, SNMP_SYNTAX_INTEGER },
186	{ "UNSIGNED32", TOK_TYPE, SNMP_SYNTAX_GAUGE },
187	{ "OCTETSTRING", TOK_TYPE, SNMP_SYNTAX_OCTETSTRING },
188	{ "IPADDRESS", TOK_TYPE, SNMP_SYNTAX_IPADDRESS },
189	{ "OID", TOK_TYPE, SNMP_SYNTAX_OID },
190	{ "TIMETICKS", TOK_TYPE, SNMP_SYNTAX_TIMETICKS },
191	{ "COUNTER", TOK_TYPE, SNMP_SYNTAX_COUNTER },
192	{ "GAUGE", TOK_TYPE, SNMP_SYNTAX_GAUGE },
193	{ "COUNTER64", TOK_TYPE, SNMP_SYNTAX_COUNTER64 },
194	{ NULL, 0, 0 }
195};
196
197/* arbitrary upper limit on node names and function names */
198#define	MAXSTR	1000
199char	str[MAXSTR];
200u_long	val;		/* integer values */
201u_int 	lno = 1;	/* current line number */
202
203static void report(const char *, ...) __dead2 __printflike(1, 2);
204static void report_node(const struct node *, const char *, ...)
205    __dead2 __printflike(2, 3);
206
207/*
208 * Report an error and exit.
209 */
210static void
211report(const char *fmt, ...)
212{
213	va_list ap;
214	int c;
215
216	va_start(ap, fmt);
217	fprintf(stderr, "line %u: ", lno);
218	vfprintf(stderr, fmt, ap);
219	fprintf(stderr, "\n");
220	fprintf(stderr, "context: \"");
221	while ((c = getchar()) != EOF && c != '\n')
222		fprintf(stderr, "%c", c);
223	fprintf(stderr, "\n");
224	va_end(ap);
225	exit(1);
226}
227static void
228report_node(const struct node *np, const char *fmt, ...)
229{
230	va_list ap;
231
232	va_start(ap, fmt);
233	fprintf(stderr, "line %u, node %s: ", np->lno, np->name);
234	vfprintf(stderr, fmt, ap);
235	fprintf(stderr, "\n");
236	va_end(ap);
237	exit(1);
238}
239
240/*
241 * Return a fresh copy of the string constituting the current token.
242 */
243static char *
244savetok(void)
245{
246	return (strcpy(xalloc(strlen(str)+1), str));
247}
248
249/*
250 * Get the next token from input.
251 */
252static int
253gettoken(void)
254{
255	int c;
256
257  again:
258	/*
259	 * Skip any whitespace before the next token
260	 */
261	while ((c = getchar()) != EOF) {
262		if (c == '\n')
263			lno++;
264		if (!isspace(c))
265			break;
266	}
267	if (c == EOF)
268		return (TOK_EOF);
269	if (!isascii(c))
270		report("unexpected character %#2x", (u_int)c);
271
272	/*
273	 * Skip comments
274	 */
275	if (c == '#') {
276		while ((c = getchar()) != EOF) {
277			if (c == '\n') {
278				lno++;
279				goto again;
280			}
281		}
282		report("unexpected EOF in comment");
283	}
284
285	/*
286	 * Single character tokens
287	 */
288	if (c == ')' || c == '(' || c == ':')
289		return (c);
290
291	/*
292	 * Sort out numbers
293	 */
294	if (isdigit(c)) {
295		ungetc(c, stdin);
296		scanf("%lu", &val);
297		return (TOK_NUM);
298	}
299
300	/*
301	 * So that has to be a string.
302	 */
303	if (isalpha(c) || c == '_') {
304		size_t n = 0;
305		str[n++] = c;
306		while ((c = getchar()) != EOF) {
307			if (!isalnum(c) && c != '_') {
308				ungetc(c, stdin);
309				break;
310			}
311			if (n == sizeof(str) - 1) {
312				str[n++] = '\0';
313				report("string too long '%s...'", str);
314			}
315			str[n++] = c;
316		}
317		str[n++] = '\0';
318
319		/*
320		 * Keywords
321		 */
322		for (c = 0; keywords[c].str != NULL; c++)
323			if (strcmp(keywords[c].str, str) == 0) {
324				val = keywords[c].val;
325				return (keywords[c].tok);
326			}
327
328		return (TOK_STR);
329	}
330	if (isprint(c))
331		errx(1, "%u: unexpected character '%c'", lno, c);
332	else
333		errx(1, "%u: unexpected character 0x%02x", lno, (u_int)c);
334}
335
336/*
337 * Parse the next node (complete with all subnodes)
338 */
339static struct node *
340parse(enum tok tok)
341{
342	struct node *node;
343	struct node *sub;
344	u_int index_count;
345
346	node = xalloc(sizeof(struct node));
347	node->lno = lno;
348
349	if (tok != '(')
350		report("'(' expected at begin of node");
351	if (gettoken() != TOK_NUM)
352		report("node id expected after opening '('");
353	if (val > ASN_MAXID)
354		report("subid too large '%lu'", val);
355	node->id = (asn_subid_t)val;
356	if (gettoken() != TOK_STR)
357		report("node name expected after '(' ID");
358	node->name = savetok();
359
360	if ((tok = gettoken()) == TOK_TYPE) {
361		/* LEAF or COLUM */
362		u_int syntax = val;
363
364		if ((tok = gettoken()) == TOK_STR) {
365			/* LEAF */
366			node->type = NODE_LEAF;
367			node->u.leaf.func = savetok();
368			node->u.leaf.syntax = syntax;
369			tok = gettoken();
370		} else {
371			/* COLUMN */
372			node->type = NODE_COLUMN;
373			node->u.column.syntax = syntax;
374		}
375
376		while (tok != ')') {
377			if (tok != TOK_ACCESS)
378				report("access keyword or ')' expected");
379			node->flags |= (u_int)val;
380			tok = gettoken();
381		}
382
383	} else if (tok == ':') {
384		/* ENTRY */
385		node->type = NODE_ENTRY;
386		TAILQ_INIT(&node->u.entry.subs);
387
388		index_count = 0;
389		node->u.entry.index = 0;
390		while ((tok = gettoken()) == TOK_TYPE) {
391			if (index_count++ == SNMP_INDEXES_MAX)
392				report("too many table indexes");
393			node->u.entry.index |=
394			    val << (SNMP_INDEX_SHIFT * index_count);
395		}
396		node->u.entry.index |= index_count;
397		if (index_count == 0)
398			report("need at least one index");
399
400		if (tok != TOK_STR)
401			report("function name expected");
402
403		node->u.entry.func = savetok();
404
405		tok = gettoken();
406
407		while (tok != ')') {
408			sub = parse(tok);
409			TAILQ_INSERT_TAIL(&node->u.entry.subs, sub, link);
410			tok = gettoken();
411		}
412
413	} else {
414		/* subtree */
415		node->type = NODE_TREE;
416		TAILQ_INIT(&node->u.tree.subs);
417
418		while (tok != ')') {
419			sub = parse(tok);
420			TAILQ_INSERT_TAIL(&node->u.tree.subs, sub, link);
421			tok = gettoken();
422		}
423	}
424	return (node);
425}
426
427/*
428 * Generate the C-code table part for one node.
429 */
430static void
431gen_node(struct node *np, struct asn_oid *oid, u_int idx, const char *func)
432{
433	u_int n;
434	struct node *sub;
435	u_int syntax;
436
437	if (oid->len == ASN_MAXOIDLEN)
438		report_node(np, "OID too long");
439	oid->subs[oid->len++] = np->id;
440
441	if (np->type == NODE_TREE) {
442		TAILQ_FOREACH(sub, &np->u.tree.subs, link)
443			gen_node(sub, oid, 0, NULL);
444		oid->len--;
445		return;
446	}
447	if (np->type == NODE_ENTRY) {
448		TAILQ_FOREACH(sub, &np->u.entry.subs, link)
449			gen_node(sub, oid, np->u.entry.index, np->u.entry.func);
450		oid->len--;
451		return;
452	}
453
454	/* leaf or column */
455	if ((np->flags & (FL_GET|FL_SET)) == 0) {
456		oid->len--;
457		return;
458	}
459
460	fprintf(fp, "    {{ %u, {", oid->len);
461	for (n = 0; n < oid->len; n++)
462		fprintf(fp, " %u,", oid->subs[n]);
463	fprintf(fp, " }}, \"%s\", ", np->name);
464
465	if (np->type == NODE_COLUMN) {
466		syntax = np->u.column.syntax;
467		fprintf(fp, "SNMP_NODE_COLUMN, ");
468	} else {
469		syntax = np->u.leaf.syntax;
470		fprintf(fp, "SNMP_NODE_LEAF, ");
471	}
472
473	switch (syntax) {
474
475	  case SNMP_SYNTAX_NULL:
476		fprintf(fp, "SNMP_SYNTAX_NULL, ");
477		break;
478
479	  case SNMP_SYNTAX_INTEGER:
480		fprintf(fp, "SNMP_SYNTAX_INTEGER, ");
481		break;
482
483	  case SNMP_SYNTAX_OCTETSTRING:
484		fprintf(fp, "SNMP_SYNTAX_OCTETSTRING, ");
485		break;
486
487	  case SNMP_SYNTAX_IPADDRESS:
488		fprintf(fp, "SNMP_SYNTAX_IPADDRESS, ");
489		break;
490
491	  case SNMP_SYNTAX_OID:
492		fprintf(fp, "SNMP_SYNTAX_OID, ");
493		break;
494
495	  case SNMP_SYNTAX_TIMETICKS:
496		fprintf(fp, "SNMP_SYNTAX_TIMETICKS, ");
497		break;
498
499	  case SNMP_SYNTAX_COUNTER:
500		fprintf(fp, "SNMP_SYNTAX_COUNTER, ");
501		break;
502
503	  case SNMP_SYNTAX_GAUGE:
504		fprintf(fp, "SNMP_SYNTAX_GAUGE, ");
505		break;
506
507	  case SNMP_SYNTAX_COUNTER64:
508		fprintf(fp, "SNMP_SYNTAX_COUNTER64, ");
509		break;
510
511	  case SNMP_SYNTAX_NOSUCHOBJECT:
512	  case SNMP_SYNTAX_NOSUCHINSTANCE:
513	  case SNMP_SYNTAX_ENDOFMIBVIEW:
514		abort();
515	}
516
517	if (np->type == NODE_COLUMN)
518		fprintf(fp, "%s, ", func);
519	else
520		fprintf(fp, "%s, ", np->u.leaf.func);
521
522	fprintf(fp, "0");
523	if (np->flags & FL_SET)
524		fprintf(fp, "|SNMP_NODE_CANSET");
525	fprintf(fp, ", %#x, NULL },\n", idx);
526	oid->len--;
527	return;
528}
529
530/*
531 * Generate the header file with the function declarations.
532 */
533static void
534gen_header(struct node *np, u_int oidlen, const char *func)
535{
536	char f[MAXSTR + 4];
537	struct node *sub;
538	struct func *ptr;
539
540	oidlen++;
541	if (np->type == NODE_TREE) {
542		TAILQ_FOREACH(sub, &np->u.tree.subs, link)
543			gen_header(sub, oidlen, NULL);
544		return;
545	}
546	if (np->type == NODE_ENTRY) {
547		TAILQ_FOREACH(sub, &np->u.entry.subs, link)
548			gen_header(sub, oidlen, np->u.entry.func);
549		return;
550	}
551
552 	if((np->flags & (FL_GET|FL_SET)) == 0)
553		return;
554
555	if (np->type == NODE_COLUMN)
556		sprintf(f, "%s", func);
557	else
558		sprintf(f, "%s", np->u.leaf.func);
559
560	LIST_FOREACH(ptr, &funcs, link)
561		if (strcmp(ptr->name, f) == 0)
562			break;
563
564	if (ptr == NULL) {
565		ptr = xalloc(sizeof(*ptr));
566		ptr->name = strcpy(xalloc(strlen(f)+1), f);
567		LIST_INSERT_HEAD(&funcs, ptr, link);
568
569		fprintf(fp, "int	%s(struct snmp_context *, "
570		    "struct snmp_value *, u_int, u_int, "
571		    "enum snmp_op);\n", f);
572	}
573
574	fprintf(fp, "# define LEAF_%s %u\n", np->name, np->id);
575}
576
577/*
578 * Generate the OID table.
579 */
580static void
581gen_table(struct node *node)
582{
583	struct asn_oid oid;
584
585	fprintf(fp, "#include <sys/types.h>\n");
586	fprintf(fp, "#include <stdio.h>\n");
587	if (localincs) {
588		fprintf(fp, "#include \"asn1.h\"\n");
589		fprintf(fp, "#include \"snmp.h\"\n");
590		fprintf(fp, "#include \"snmpagent.h\"\n");
591	} else {
592		fprintf(fp, "#include <bsnmp/asn1.h>\n");
593		fprintf(fp, "#include <bsnmp/snmp.h>\n");
594		fprintf(fp, "#include <bsnmp/snmpagent.h>\n");
595	}
596	fprintf(fp, "#include \"%stree.h\"\n", file_prefix);
597	fprintf(fp, "\n");
598
599	fprintf(fp, "const struct snmp_node %sctree[] = {\n", file_prefix);
600
601	oid.len = PREFIX_LEN;
602	memcpy(oid.subs, prefix, sizeof(prefix));
603	gen_node(node, &oid, 0, NULL);
604
605	fprintf(fp, "};\n\n");
606}
607
608static int
609extract(const struct node *np, struct asn_oid *oid, const char *obj)
610{
611	struct node *sub;
612	u_long n;
613
614	if (oid->len == ASN_MAXOIDLEN)
615		report_node(np, "OID too long");
616	oid->subs[oid->len++] = np->id;
617
618	if (strcmp(obj, np->name) == 0) {
619		fprintf(fp, "#define OID_%s\t%u\n", np->name, np->id);
620		fprintf(fp, "#define OIDLEN_%s\t%u\n", np->name, oid->len);
621		fprintf(fp, "#define OIDX_%s\t{ %u, {", np->name, oid->len);
622		for (n = 0; n < oid->len; n++)
623			fprintf(fp, " %u,", oid->subs[n]);
624		fprintf(fp, " } }\n");
625		return (0);
626	}
627
628	if (np->type == NODE_TREE) {
629		TAILQ_FOREACH(sub, &np->u.tree.subs, link)
630			if (!extract(sub, oid, obj))
631				return (0);
632	} else if (np->type == NODE_ENTRY) {
633		TAILQ_FOREACH(sub, &np->u.entry.subs, link)
634			if (!extract(sub, oid, obj))
635				return (0);
636	}
637	oid->len--;
638	return (1);
639}
640
641static int
642gen_extract(const struct node *root, const char *object)
643{
644	struct asn_oid oid;
645
646	oid.len = PREFIX_LEN;
647	memcpy(oid.subs, prefix, sizeof(prefix));
648	return (extract(root, &oid, object));
649}
650
651
652static void
653check_sub_order(const struct node *np, const struct node_list *subs)
654{
655	int first;
656	const struct node *sub;
657	asn_subid_t maxid = 0;
658
659	/* ensure, that subids are ordered */
660	first = 1;
661	TAILQ_FOREACH(sub, subs, link) {
662		if (!first && sub->id <= maxid)
663			report_node(np, "subids not ordered at %s", sub->name);
664		maxid = sub->id;
665		first = 0;
666	}
667}
668
669/*
670 * Do some sanity checks on the tree definition and do some computations.
671 */
672static void
673check_tree(struct node *np)
674{
675	struct node *sub;
676
677	if (np->type == NODE_LEAF || np->type == NODE_COLUMN) {
678		if ((np->flags & (FL_GET|FL_SET)) != 0)
679			tree_size++;
680		return;
681	}
682
683	if (np->type == NODE_ENTRY) {
684		check_sub_order(np, &np->u.entry.subs);
685
686		/* ensure all subnodes are columns */
687		TAILQ_FOREACH(sub, &np->u.entry.subs, link) {
688			if (sub->type != NODE_COLUMN)
689				report_node(np, "entry subnode '%s' is not "
690				    "a column", sub->name);
691			check_tree(sub);
692		}
693	} else {
694		check_sub_order(np, &np->u.tree.subs);
695
696		TAILQ_FOREACH(sub, &np->u.tree.subs, link)
697			check_tree(sub);
698	}
699}
700
701int
702main(int argc, char *argv[])
703{
704	int do_extract = 0;
705	int opt;
706	struct node *root;
707	char fname[MAXPATHLEN + 1];
708
709	while ((opt = getopt(argc, argv, "help:")) != EOF)
710		switch (opt) {
711
712		  case 'h':
713			fprintf(stderr, "%s", usgtxt);
714			exit(0);
715
716		  case 'e':
717			do_extract = 1;
718			break;
719
720		  case 'l':
721			localincs = 1;
722			break;
723
724		  case 'p':
725			file_prefix = optarg;
726			if (strlen(file_prefix) + strlen("tree.c") >
727			    MAXPATHLEN)
728				errx(1, "prefix too long");
729			break;
730		}
731
732	if (!do_extract && argc != optind)
733		errx(1, "no arguments allowed");
734	if (do_extract && argc == optind)
735		errx(1, "no objects specified");
736
737	root = parse(gettoken());
738	if (gettoken() != TOK_EOF)
739		report("junk after closing ')'");
740
741	check_tree(root);
742
743	if (do_extract) {
744		fp = stdout;
745		while (optind < argc) {
746			if (gen_extract(root, argv[optind]))
747				errx(1, "object not found: %s", argv[optind]);
748			optind++;
749		}
750
751		return (0);
752	}
753	sprintf(fname, "%stree.h", file_prefix);
754	if ((fp = fopen(fname, "w")) == NULL)
755		err(1, "%s: ", fname);
756	gen_header(root, PREFIX_LEN, NULL);
757
758	fprintf(fp, "#define %sCTREE_SIZE %u\n", file_prefix, tree_size);
759	fprintf(fp, "extern const struct snmp_node %sctree[];\n", file_prefix);
760
761	fclose(fp);
762
763	sprintf(fname, "%stree.c", file_prefix);
764	if ((fp = fopen(fname, "w")) == NULL)
765		err(1, "%s: ", fname);
766	gen_table(root);
767	fclose(fp);
768
769	return (0);
770}
771