1 /*
2  * Copyright 2000-2003 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 /*
6  * Copyright (c) 1983 Regents of the University of California.
7  * All rights reserved.  The Berkeley software License Agreement
8  * specifies the terms and conditions for redistribution.
9  */
10 
11 #pragma ident	"%Z%%M%	%I%	%E% SMI"
12 
13 /*
14  * Ifparse splits up an ifconfig command line, and was written for use
15  * with the networking boot script /etc/init.d/network (which is in the
16  * source tree as usr/src/cmd/initpkg/init.d/network).
17  *
18  * Ifparse can extract selected parts of the ifconfig command line,
19  * such as failover address configuration ("ifparse -f"), or everything
20  * except failover address configuration ("ifparse -s").  By default,
21  * all parts of the command line are extracted (equivalent to ("ifparse -fs").
22  *
23  * Examples:
24  *
25  * The command:
26  *
27  * 	ifparse inet 1.2.3.4 up group two addif 1.2.3.5 up addif 1.2.3.6 up
28  *
29  * Produces the following on standard output:
30  *
31  *	set 1.2.3.4 up
32  *	group two
33  *	addif 1.2.3.5 up
34  *	addif 1.2.3.6 up
35  *
36  * The optional "set" and "destination" keywords are added to make the
37  * output easier to process by a script or another command.
38  *
39  * The command:
40  *
41  * 	ifparse -f inet 1.2.3.4 -failover up group two addif 1.2.3.5 up
42  *
43  * Produces:
44  *
45  *	addif 1.2.3.5  up
46  *
47  * Only failover address configuration has been requested.  Address
48  * 1.2.3.4 is a non-failover address, and so isn't output.
49  *
50  * The "failover" and "-failover" commands can occur several times for
51  * a given logical interface.  Only the last one counts.  For example:
52  *
53  *	ifparse -f inet 1.2.3.4 -failover failover -failover failover up
54  *
55  * Produces:
56  *
57  *	set 1.2.3.4 -failover failover -failover failover up
58  *
59  * No attempt is made to clean up such "pathological" command lines, by
60  * removing redundant "failover" and "-failover" commands.
61  */
62 
63 #include	<sys/types.h>
64 #include	<stdlib.h>
65 #include	<stdio.h>
66 #include	<string.h>
67 #include	<assert.h>
68 
69 /*
70  * Parser flags:
71  *
72  *	PARSEFIXED
73  *		Command should only appear if non-failover commands
74  *		are requested.
75  *	PARSEMOVABLE
76  *		Command should only appear if failover commands are
77  *		requested.
78  *	PARSENOW
79  *		Don't buffer the command, dump it to output immediately.
80  * 	PARSEADD
81  *		Indicates processing has moved on to additional
82  *		logical interfaces.
83  *		Dump the buffer to output and clear buffer contents.
84  *	PARSESET
85  * 		The "set" and "destination" keywords are optional.
86  * 		This flag indicates that the next address not prefixed
87  *		with a keyword will be a destination address.
88  *	PARSELOG0
89  *		Command not valid on additional logical interfaces.
90  */
91 
92 #define	PARSEFIXED	0x01
93 #define	PARSEMOVABLE	0x02
94 #define	PARSENOW	0x04
95 #define	PARSEADD	0x08
96 #define	PARSESET	0x10
97 #define	PARSELOG0	0x20
98 
99 typedef enum { AF_UNSPEC, AF_INET, AF_INET6, AF_ANY } ac_t;
100 
101 #define	NEXTARG		(-1)
102 
103 #define	END_OF_TABLE	(-1)
104 
105 /* Parsemode, the type of commands requested by the user. */
106 int	parsemode = 0;
107 
108 /* Parsetype, the type of the command currently in the buffer. */
109 int	parsetype = PARSEFIXED | PARSEMOVABLE;
110 
111 /* Parsebuf, pointer to the buffer. */
112 char	*parsebuf = NULL;
113 
114 /* Parsebuflen, the size of the buffer area. */
115 unsigned parsebuflen = 0;
116 
117 /* Parsedumplen, the amount of the buffer currently in use. */
118 unsigned parsedumplen = 0;
119 
120 /*
121  * Setaddr, used to decide whether an address without a keyword
122  * prefix is a source or destination address.
123  */
124 boolean_t setaddr = _B_FALSE;
125 
126 /*
127  * Some ifconfig commands are only valid on the first logical interface.
128  * As soon as an "addif" command is seen, "addint" is set.
129  */
130 boolean_t addint = _B_FALSE;
131 
132 /*
133  * The parser table is based on that in ifconfig.  A command may or
134  * may not have an argument, as indicated by whether NEXTARG is in the
135  * second column.  Some commands can only be used with certain address
136  * families, as indicated in the third column.  The fourth column
137  * contains flags that control parser action.
138  *
139  * Ifparse buffers logical interface configuration commands such as "set",
140  * "netmask" and "broadcast".  This buffering continues until an "addif"
141  * command is seen, at which point the buffer is emptied, and the process
142  * starts again.
143  *
144  * Some commands do not relate to logical interface configuration and are
145  * dumped to output as soon as they are seen, such as "group" and "standby".
146  *
147  */
148 
149 struct	cmd {
150 	char	*c_name;
151 	int	c_parameter;		/* NEXTARG means next argv */
152 	int	c_af;			/* address family restrictions */
153 	int	c_parseflags;		/* parsing flags */
154 } cmds[] = {
155 	{ "up",			0,		AF_ANY, 0 },
156 	{ "down",		0,		AF_ANY, 0 },
157 	{ "trailers",		0, 		AF_ANY, PARSENOW },
158 	{ "-trailers",		0,		AF_ANY, PARSENOW },
159 	{ "arp",		0,		AF_INET, PARSENOW },
160 	{ "-arp",		0,		AF_INET, PARSENOW },
161 	{ "private",		0,		AF_ANY, 0 },
162 	{ "-private",		0,		AF_ANY, 0 },
163 	{ "router",		0,		AF_ANY, PARSELOG0 },
164 	{ "-router",		0,		AF_ANY, PARSELOG0 },
165 	{ "xmit",		0,		AF_ANY, 0 },
166 	{ "-xmit",		0,		AF_ANY, 0 },
167 	{ "-nud",		0,		AF_INET6, PARSENOW },
168 	{ "nud",		0,		AF_INET6, PARSENOW },
169 	{ "anycast",		0,		AF_ANY, 0 },
170 	{ "-anycast",		0,		AF_ANY, 0 },
171 	{ "local",		0,		AF_ANY, 0 },
172 	{ "-local",		0,		AF_ANY, 0 },
173 	{ "deprecated",		0,		AF_ANY, 0 },
174 	{ "-deprecated", 	0, 		AF_ANY, 0 },
175 	{ "preferred",		0,		AF_INET6, 0 },
176 	{ "-preferred",		0,		AF_INET6, 0 },
177 	{ "debug",		0,		AF_ANY, PARSENOW },
178 	{ "verbose",		0,		AF_ANY, PARSENOW },
179 	{ "netmask",		NEXTARG,	AF_INET, 0 },
180 	{ "metric",		NEXTARG,	AF_ANY, 0 },
181 	{ "mtu",		NEXTARG,	AF_ANY, 0 },
182 	{ "index",		NEXTARG,	AF_ANY, PARSELOG0 },
183 	{ "broadcast",		NEXTARG,	AF_INET, 0 },
184 	{ "auto-revarp", 	0,		AF_INET, PARSEFIXED},
185 	{ "plumb",		0,		AF_ANY, PARSENOW },
186 	{ "unplumb",		0,		AF_ANY, PARSENOW },
187 	{ "subnet",		NEXTARG,	AF_ANY, 0 },
188 	{ "token",		NEXTARG,	AF_INET6, PARSELOG0 },
189 	{ "tsrc",		NEXTARG,	AF_ANY, PARSELOG0 },
190 	{ "tdst",		NEXTARG,	AF_ANY, PARSELOG0 },
191 	{ "encr_auth_algs", 	NEXTARG,	AF_ANY, PARSELOG0 },
192 	{ "encr_algs",		NEXTARG,	AF_ANY, PARSELOG0 },
193 	{ "auth_algs",		NEXTARG,	AF_ANY, PARSELOG0 },
194 	{ "addif",		NEXTARG,	AF_ANY, PARSEADD },
195 	{ "removeif",		NEXTARG,	AF_ANY, PARSELOG0 },
196 	{ "modlist",		0,		AF_ANY, PARSENOW },
197 	{ "modinsert",		NEXTARG,	AF_ANY, PARSENOW },
198 	{ "modremove",		NEXTARG,	AF_ANY, PARSENOW },
199 	{ "failover",		0,		AF_ANY, PARSEMOVABLE },
200 	{ "-failover",		0, 		AF_ANY, PARSEFIXED },
201 	{ "standby",		0,		AF_ANY, PARSENOW },
202 	{ "-standby",		0,		AF_ANY, PARSENOW },
203 	{ "failed",		0,		AF_ANY, PARSENOW },
204 	{ "-failed",		0,		AF_ANY, PARSENOW },
205 	{ "group",		NEXTARG,	AF_ANY, PARSELOG0 },
206 	{ "configinfo",		0,		AF_ANY, PARSENOW },
207 	{ "encaplimit",		NEXTARG,	AF_ANY,	PARSELOG0 },
208 	{ "-encaplimit",	0,		AF_ANY,	PARSELOG0 },
209 	{ "thoplimit",		NEXTARG,	AF_ANY, PARSELOG0 },
210 #ifdef DEBUG
211 	{ "getnd",		NEXTARG,	AF_INET6, PARSELOG0 },
212 	{ "setnd",		NEXTARG,	AF_INET6, PARSELOG0 },
213 	{ "delnd",		NEXTARG,	AF_INET6, PARSELOG0 },
214 #endif
215 /* XXX for testing SIOCT* ioctls. Remove */
216 	{ "set",		NEXTARG,	AF_ANY, PARSESET },
217 	{ "destination",	NEXTARG,	AF_ANY, 0 },
218 	{ 0 /* ether addr */,	0,		AF_UNSPEC, PARSELOG0 },
219 	{ 0 /* set */,		0,		AF_ANY, PARSESET },
220 	{ 0 /* destination */,	0,		AF_ANY, 0 },
221 	{ 0,			END_OF_TABLE,	END_OF_TABLE, END_OF_TABLE},
222 };
223 
224 
225 /* Known address families */
226 struct afswtch {
227 	char *af_name;
228 	short af_af;
229 } afs[] = {
230 	{ "inet",	AF_INET },
231 	{ "ether",	AF_UNSPEC },
232 	{ "inet6",	AF_INET6 },
233 	{ 0,		0 }
234 };
235 
236 /*
237  * Append "item" to the buffer.  If there isn't enough room in the buffer,
238  * expand it.
239  */
240 static void
241 parse_append_buf(char *item)
242 {
243 	unsigned itemlen;
244 	unsigned newdumplen;
245 
246 	if (item == NULL)
247 		return;
248 
249 	itemlen = strlen(item);
250 	newdumplen = parsedumplen + itemlen;
251 
252 	/* Expand dump buffer as needed */
253 	if (parsebuflen < newdumplen)  {
254 		if ((parsebuf = realloc(parsebuf, newdumplen)) == NULL) {
255 			perror("ifparse");
256 			exit(1);
257 		}
258 		parsebuflen = newdumplen;
259 	}
260 	(void) memcpy(parsebuf + parsedumplen, item, itemlen);
261 
262 	parsedumplen = newdumplen;
263 }
264 
265 /*
266  * Dump the buffer to output.
267  */
268 static void
269 parse_dump_buf(void)
270 {
271 	/*
272 	 * When parsing, a set or addif command,  we may be some way into
273 	 * the command before we definitely know it is movable or fixed.
274 	 * If we get to the end of the command, and haven't seen a
275 	 * "failover" or "-failover" flag, the command is movable.
276 	 */
277 	if (!((parsemode  == PARSEFIXED) &&
278 		(parsetype & PARSEMOVABLE) != 0) &&
279 		(parsemode & parsetype) != 0 &&
280 		parsedumplen != 0) {
281 		unsigned i;
282 
283 		if (parsebuf[parsedumplen] == ' ')
284 			parsedumplen--;
285 
286 		for (i = 0; i < parsedumplen; i++)
287 			(void) putchar(parsebuf[i]);
288 
289 		(void) putchar('\n');
290 	}
291 	/* The buffer is kept in case there is more parsing to do */
292 	parsedumplen = 0;
293 	parsetype = PARSEFIXED | PARSEMOVABLE;
294 }
295 
296 /*
297  * Process a command.  The command will either be put in the buffer,
298  * or dumped directly to output.  The current contents of the buffer
299  * may be dumped to output.
300  *
301  * The buffer holds commands relating to a particular logical interface.
302  * For example, "set", "destination", "failover", "broadcast", all relate
303  * to a particular interface.  Such commands have to be buffered until
304  * all the "failover" and "-failover" commands for that interface have
305  * been seen, only then will we know whether the command is movable
306  * or not.  When the "addif" command is seen, we know we are about to
307  * start processing a new logical interface, we've seen all the
308  * "failover" and "-failover" commands for the previous interface, and
309  * can decide whether the buffer contents are movable or not.
310  *
311  */
312 static void
313 parsedump(char *cmd, int param, int flags, char *arg)
314 {
315 	char *cmdname;	/* Command name	*/
316 	char *cmdarg;	/* Argument to command, if it takes one, or NULL */
317 
318 	/*
319 	 * Is command only valid on logical interface 0?
320 	 * If processing commands on an additional logical interface, ignore
321 	 * the command.
322 	 * If processing commands on logical interface 0, don't buffer the
323 	 * command, dump it straight to output.
324 	 */
325 	if ((flags & PARSELOG0) != 0) {
326 		if (addint)
327 			return;
328 		flags |= PARSENOW;
329 	}
330 
331 	/*
332 	 * If processing the "addif" command, a destination address may
333 	 * follow without the "destination" prefix.  Add PARSESET to the
334 	 * flags so that such an anonymous address is processed correctly.
335 	 */
336 	if ((flags & PARSEADD) != 0) {
337 		flags |= PARSESET;
338 		addint = _B_TRUE;
339 	}
340 
341 	/*
342 	 * Commands that must be dumped straight to output are always fixed
343 	 * (non-movable) commands.
344 	 *
345 	 */
346 	if ((flags & PARSENOW) != 0)
347 		flags |= PARSEFIXED;
348 
349 	/*
350 	 * Source and destination addresses do not have to be prefixed
351 	 * with the keywords "set" or "destination".  Ifparse always
352 	 * inserts the optional keyword.
353 	 */
354 	if (cmd == NULL) {
355 		cmdarg = arg;
356 		if ((flags & PARSESET) != 0)
357 			cmdname = "set";
358 		else if (setaddr) {
359 			cmdname = "destination";
360 			setaddr = _B_FALSE;
361 		} else
362 			cmdname = "";
363 	} else {
364 		cmdarg = (param == NEXTARG) ? arg : NULL;
365 		cmdname = cmd;
366 	}
367 
368 	/*
369 	 * The next address without a prefix will be a destination
370 	 * address.
371 	 */
372 	if ((flags & PARSESET) != 0)
373 		setaddr = _B_TRUE;
374 
375 	/*
376 	 * Dump the command straight to output?
377 	 * Only dump the command if the parse mode specified on
378 	 * the command line matches the type of the command.
379 	 */
380 	if ((flags & PARSENOW) != 0) {
381 		if ((parsemode & flags) != 0)  {
382 			(void) fputs(cmdname, stdout);
383 			if (cmdarg != NULL) {
384 				(void) fputc(' ', stdout);
385 				(void) fputs(cmdarg, stdout);
386 			}
387 			(void) fputc('\n', stdout);
388 		}
389 		return;
390 	}
391 
392 	/*
393 	 * Only the commands relating to a particular logical interface
394 	 * are buffered.  When an "addif" command is seen, processing is
395 	 * about to start on a new logical interface, so dump the
396 	 * buffer to output.
397 	 */
398 	if ((flags & PARSEADD) != 0)
399 		parse_dump_buf();
400 
401 	/*
402 	 * If the command flags indicate the command is fixed or
403 	 * movable, update the type of the interface in the buffer
404 	 * accordingly.  For example, "-failover" has the "PARSEFIXED"
405 	 * flag, and the contents of the buffer are not movable if
406 	 * "-failover" is seen.
407 	 */
408 	if ((flags & PARSEFIXED) != 0)
409 		parsetype &= ~PARSEMOVABLE;
410 
411 	if ((flags & PARSEMOVABLE) != 0)
412 		parsetype &= ~PARSEFIXED;
413 
414 	parsetype |= flags & (PARSEFIXED | PARSEMOVABLE);
415 
416 	parse_append_buf(cmdname);
417 
418 	if (cmdarg != NULL) {
419 		parse_append_buf(" ");
420 		parse_append_buf(cmdarg);
421 	}
422 
423 	parse_append_buf(" ");
424 }
425 
426 /*
427  * Parse the part of the command line following the address family
428  * specification, if any.
429  *
430  * This function is a modified version of the function "ifconfig" in
431  * ifconfig.c.
432  */
433 static int
434 ifparse(int argc, char *argv[], struct afswtch *afp)
435 {
436 	int af;
437 
438 	if (argc == 0) {
439 		return (0);
440 	}
441 
442 	af = afp->af_af;
443 
444 	if (strcmp(*argv, "auto-dhcp") == 0 || strcmp(*argv, "dhcp") == 0) {
445 		if (af == AF_INET) {
446 			if ((parsemode & PARSEFIXED) != NULL) {
447 				while (argc) {
448 					(void) fputs(*argv++, stdout);
449 					if (--argc != 0)
450 						(void) fputc(' ', stdout);
451 					else
452 						(void) fputc('\n', stdout);
453 				}
454 			}
455 			return (0);
456 		} else {
457 			(void) fprintf(stderr, "ifparse: dhcp not supported "
458 			    "for inet6\n");
459 
460 			return (1);
461 		}
462 	}
463 
464 	while (argc > 0) {
465 		struct cmd *p;
466 		boolean_t found_cmd;
467 
468 		found_cmd = _B_FALSE;
469 		for (p = cmds; ; p++) {
470 			assert(p->c_parseflags != END_OF_TABLE);
471 			if (p->c_name) {
472 				if (strcmp(*argv, p->c_name) == 0) {
473 					/*
474 					 * indicate that the command was
475 					 * found and check to see if
476 					 * the address family is valid
477 					 */
478 					found_cmd = _B_TRUE;
479 					if (p->c_af == AF_ANY ||
480 					    af == p->c_af)
481 						break;
482 				}
483 			} else {
484 				if (p->c_af == AF_ANY ||
485 				    af == p->c_af)
486 					break;
487 			}
488 		}
489 		assert(p->c_parseflags != END_OF_TABLE);
490 		/*
491 		 * If we found the keyword, but the address family
492 		 * did not match spit out an error
493 		 */
494 		if (found_cmd && p->c_name == 0) {
495 			(void) fprintf(stderr, "ifparse: Operation %s not"
496 			    " supported for %s\n", *argv, afp->af_name);
497 			return (1);
498 		}
499 		/*
500 		 * else (no keyword found), we assume it's an address
501 		 * of some sort
502 		 */
503 		if (p->c_name == 0 && setaddr) {
504 			p++;	/* got src, do dst */
505 			assert(p->c_parseflags != END_OF_TABLE);
506 		}
507 		if (p->c_parameter == NEXTARG) {
508 			argc--, argv++;
509 			if (argc == 0) {
510 				(void) fprintf(stderr,
511 				    "ifparse: no argument for %s\n",
512 				    p->c_name);
513 				return (1);
514 			}
515 		}
516 		/*
517 		 *	Dump the command if:
518 		 *
519 		 *		there's no address family
520 		 *		restriction
521 		 *	OR
522 		 *		there is a restriction AND
523 		 *		the address families match
524 		 */
525 		if ((p->c_af == AF_ANY)	|| (af == p->c_af))
526 			parsedump(p->c_name, p->c_parameter,
527 				p->c_parseflags, *argv);
528 		argc--, argv++;
529 	}
530 	parse_dump_buf();
531 
532 	return (0);
533 }
534 
535 /*
536  * Print command usage on standard error.
537  */
538 static void
539 usage(void)
540 {
541 	(void) fprintf(stderr,
542 		"usage: ifparse [ -fs ] <addr_family> <commands>\n");
543 }
544 
545 int
546 main(int argc, char *argv[])
547 {
548 	int c;
549 	struct afswtch *afp;
550 
551 	while ((c = getopt(argc, argv, "fs")) != -1) {
552 		switch ((char)c) {
553 		case 'f':
554 			parsemode |= PARSEMOVABLE;
555 			break;
556 		case 's':
557 			parsemode |= PARSEFIXED;
558 			break;
559 		case '?':
560 			usage();
561 			exit(1);
562 		}
563 	}
564 
565 	if (parsemode == 0)
566 		parsemode = PARSEFIXED | PARSEMOVABLE;
567 
568 	argc -= optind;
569 	argv += optind;
570 
571 	afp = afs;
572 	if (argc > 0) {
573 		struct afswtch *aftp;
574 		for (aftp = afs; aftp->af_name; aftp++) {
575 			if (strcmp(aftp->af_name, *argv) == 0) {
576 				argc--; argv++;
577 				afp = aftp;
578 				break;
579 			}
580 		}
581 	}
582 
583 	return (ifparse(argc, argv, afp));
584 }
585