1 /*
2  * Copyright 2008 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 scripts; see $SRC/cmd/svc/shell/net_include.sh
16  *
17  * Ifparse can extract selected parts of the ifconfig command line,
18  * such as failover address configuration ("ifparse -f"), or everything
19  * except failover address configuration ("ifparse -s").  By default,
20  * all parts of the command line are extracted (equivalent to ("ifparse -fs").
21  *
22  * Examples:
23  *
24  * The command:
25  *
26  * 	ifparse inet 1.2.3.4 up group two addif 1.2.3.5 up addif 1.2.3.6 up
27  *
28  * Produces the following on standard output:
29  *
30  *	set 1.2.3.4 up
31  *	group two
32  *	addif 1.2.3.5 up
33  *	addif 1.2.3.6 up
34  *
35  * The optional "set" and "destination" keywords are added to make the
36  * output easier to process by a script or another command.
37  *
38  * The command:
39  *
40  * 	ifparse -f inet 1.2.3.4 -failover up group two addif 1.2.3.5 up
41  *
42  * Produces:
43  *
44  *	addif 1.2.3.5  up
45  *
46  * Only failover address configuration has been requested.  Address
47  * 1.2.3.4 is a non-failover address, and so isn't output.
48  *
49  * The "failover" and "-failover" commands can occur several times for
50  * a given logical interface.  Only the last one counts.  For example:
51  *
52  *	ifparse -f inet 1.2.3.4 -failover failover -failover failover up
53  *
54  * Produces:
55  *
56  *	set 1.2.3.4 -failover failover -failover failover up
57  *
58  * No attempt is made to clean up such "pathological" command lines, by
59  * removing redundant "failover" and "-failover" commands.
60  */
61 
62 #include	<sys/types.h>
63 #include	<stdlib.h>
64 #include	<stdio.h>
65 #include	<string.h>
66 #include	<assert.h>
67 
68 /*
69  * Parser flags:
70  *
71  *	PARSEFIXED
72  *		Command should only appear if non-failover commands
73  *		are requested.
74  *	PARSEMOVABLE
75  *		Command should only appear if failover commands are
76  *		requested.
77  *	PARSENOW
78  *		Don't buffer the command, dump it to output immediately.
79  * 	PARSEADD
80  *		Indicates processing has moved on to additional
81  *		logical interfaces.
82  *		Dump the buffer to output and clear buffer contents.
83  *	PARSESET
84  * 		The "set" and "destination" keywords are optional.
85  * 		This flag indicates that the next address not prefixed
86  *		with a keyword will be a destination address.
87  *	PARSELOG0
88  *		Command not valid on additional logical interfaces.
89  */
90 
91 #define	PARSEFIXED	0x01
92 #define	PARSEMOVABLE	0x02
93 #define	PARSENOW	0x04
94 #define	PARSEADD	0x08
95 #define	PARSESET	0x10
96 #define	PARSELOG0	0x20
97 
98 typedef enum { AF_UNSPEC, AF_INET, AF_INET6, AF_ANY } ac_t;
99 
100 #define	NEXTARG		(-1)	/* command takes an argument */
101 #define	OPTARG		(-2)	/* command takes an optional argument */
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/OPTARG is
135  * in the second column.  Some commands can only be used with certain
136  * address 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 	{ "set",		NEXTARG,	AF_ANY, PARSESET },
211 	{ "destination",	NEXTARG,	AF_ANY, 0 },
212 	{ "zone",		NEXTARG,	AF_ANY, 0 },
213 	{ "-zone",		0,		AF_ANY, 0 },
214 	{ "all-zones",		0,		AF_ANY, 0 },
215 	{ "ether",		OPTARG,		AF_ANY, PARSENOW },
216 	{ "usesrc",		NEXTARG,	AF_ANY, PARSENOW },
217 	{ 0 /* ether addr */,	0,		AF_UNSPEC, PARSELOG0 },
218 	{ 0 /* set */,		0,		AF_ANY, PARSESET },
219 	{ 0 /* destination */,	0,		AF_ANY, 0 },
220 	{ 0,			END_OF_TABLE,	END_OF_TABLE, END_OF_TABLE},
221 };
222 
223 
224 /* Known address families */
225 struct afswtch {
226 	char *af_name;
227 	short af_af;
228 } afs[] = {
229 	{ "inet",	AF_INET },
230 	{ "ether",	AF_UNSPEC },
231 	{ "inet6",	AF_INET6 },
232 	{ 0,		0 }
233 };
234 
235 /*
236  * Append "item" to the buffer.  If there isn't enough room in the buffer,
237  * expand it.
238  */
239 static void
240 parse_append_buf(char *item)
241 {
242 	unsigned itemlen;
243 	unsigned newdumplen;
244 
245 	if (item == NULL)
246 		return;
247 
248 	itemlen = strlen(item);
249 	newdumplen = parsedumplen + itemlen;
250 
251 	/* Expand dump buffer as needed */
252 	if (parsebuflen < newdumplen)  {
253 		if ((parsebuf = realloc(parsebuf, newdumplen)) == NULL) {
254 			perror("ifparse");
255 			exit(1);
256 		}
257 		parsebuflen = newdumplen;
258 	}
259 	(void) memcpy(parsebuf + parsedumplen, item, itemlen);
260 
261 	parsedumplen = newdumplen;
262 }
263 
264 /*
265  * Dump the buffer to output.
266  */
267 static void
268 parse_dump_buf(void)
269 {
270 	/*
271 	 * When parsing, a set or addif command,  we may be some way into
272 	 * the command before we definitely know it is movable or fixed.
273 	 * If we get to the end of the command, and haven't seen a
274 	 * "failover" or "-failover" flag, the command is movable.
275 	 */
276 	if (!((parsemode == PARSEFIXED) && (parsetype & PARSEMOVABLE) != 0) &&
277 	    (parsemode & parsetype) != 0 && parsedumplen != 0) {
278 		unsigned i;
279 
280 		if (parsebuf[parsedumplen] == ' ')
281 			parsedumplen--;
282 
283 		for (i = 0; i < parsedumplen; i++)
284 			(void) putchar(parsebuf[i]);
285 
286 		(void) putchar('\n');
287 	}
288 	/* The buffer is kept in case there is more parsing to do */
289 	parsedumplen = 0;
290 	parsetype = PARSEFIXED | PARSEMOVABLE;
291 }
292 
293 /*
294  * Process a command.  The command will either be put in the buffer,
295  * or dumped directly to output.  The current contents of the buffer
296  * may be dumped to output.
297  *
298  * The buffer holds commands relating to a particular logical interface.
299  * For example, "set", "destination", "failover", "broadcast", all relate
300  * to a particular interface.  Such commands have to be buffered until
301  * all the "failover" and "-failover" commands for that interface have
302  * been seen, only then will we know whether the command is movable
303  * or not.  When the "addif" command is seen, we know we are about to
304  * start processing a new logical interface, we've seen all the
305  * "failover" and "-failover" commands for the previous interface, and
306  * can decide whether the buffer contents are movable or not.
307  *
308  */
309 static void
310 parsedump(char *cmd, int param, int flags, char *arg)
311 {
312 	char *cmdname;	/* Command name	*/
313 	char *cmdarg;	/* Argument to command, if it takes one, or NULL */
314 
315 	/*
316 	 * Is command only valid on logical interface 0?
317 	 * If processing commands on an additional logical interface, ignore
318 	 * the command.
319 	 * If processing commands on logical interface 0, don't buffer the
320 	 * command, dump it straight to output.
321 	 */
322 	if ((flags & PARSELOG0) != 0) {
323 		if (addint)
324 			return;
325 		flags |= PARSENOW;
326 	}
327 
328 	/*
329 	 * If processing the "addif" command, a destination address may
330 	 * follow without the "destination" prefix.  Add PARSESET to the
331 	 * flags so that such an anonymous address is processed correctly.
332 	 */
333 	if ((flags & PARSEADD) != 0) {
334 		flags |= PARSESET;
335 		addint = _B_TRUE;
336 	}
337 
338 	/*
339 	 * Commands that must be dumped straight to output are always fixed
340 	 * (non-movable) commands.
341 	 *
342 	 */
343 	if ((flags & PARSENOW) != 0)
344 		flags |= PARSEFIXED;
345 
346 	/*
347 	 * Source and destination addresses do not have to be prefixed
348 	 * with the keywords "set" or "destination".  Ifparse always
349 	 * inserts the optional keyword.
350 	 */
351 	if (cmd == NULL) {
352 		cmdarg = arg;
353 		if ((flags & PARSESET) != 0)
354 			cmdname = "set";
355 		else if (setaddr) {
356 			cmdname = "destination";
357 			setaddr = _B_FALSE;
358 		} else
359 			cmdname = "";
360 	} else {
361 		cmdarg = (param == 0) ? NULL : arg;
362 		cmdname = cmd;
363 	}
364 
365 	/*
366 	 * The next address without a prefix will be a destination
367 	 * address.
368 	 */
369 	if ((flags & PARSESET) != 0)
370 		setaddr = _B_TRUE;
371 
372 	/*
373 	 * Dump the command straight to output?
374 	 * Only dump the command if the parse mode specified on
375 	 * the command line matches the type of the command.
376 	 */
377 	if ((flags & PARSENOW) != 0) {
378 		if ((parsemode & flags) != 0)  {
379 			(void) fputs(cmdname, stdout);
380 			if (cmdarg != NULL) {
381 				(void) fputc(' ', stdout);
382 				(void) fputs(cmdarg, stdout);
383 			}
384 			(void) fputc('\n', stdout);
385 		}
386 		return;
387 	}
388 
389 	/*
390 	 * Only the commands relating to a particular logical interface
391 	 * are buffered.  When an "addif" command is seen, processing is
392 	 * about to start on a new logical interface, so dump the
393 	 * buffer to output.
394 	 */
395 	if ((flags & PARSEADD) != 0)
396 		parse_dump_buf();
397 
398 	/*
399 	 * If the command flags indicate the command is fixed or
400 	 * movable, update the type of the interface in the buffer
401 	 * accordingly.  For example, "-failover" has the "PARSEFIXED"
402 	 * flag, and the contents of the buffer are not movable if
403 	 * "-failover" is seen.
404 	 */
405 	if ((flags & PARSEFIXED) != 0)
406 		parsetype &= ~PARSEMOVABLE;
407 
408 	if ((flags & PARSEMOVABLE) != 0)
409 		parsetype &= ~PARSEFIXED;
410 
411 	parsetype |= flags & (PARSEFIXED | PARSEMOVABLE);
412 
413 	parse_append_buf(cmdname);
414 
415 	if (cmdarg != NULL) {
416 		parse_append_buf(" ");
417 		parse_append_buf(cmdarg);
418 	}
419 
420 	parse_append_buf(" ");
421 }
422 
423 /*
424  * Parse the part of the command line following the address family
425  * specification, if any.
426  *
427  * This function is a modified version of the function "ifconfig" in
428  * ifconfig.c.
429  */
430 static int
431 ifparse(int argc, char *argv[], struct afswtch *afp)
432 {
433 	int af = afp->af_af;
434 
435 	if (argc == 0)
436 		return (0);
437 
438 	if (strcmp(*argv, "auto-dhcp") == 0 || strcmp(*argv, "dhcp") == 0) {
439 		if ((parsemode & PARSEFIXED) != NULL) {
440 			while (argc) {
441 				(void) fputs(*argv++, stdout);
442 				if (--argc != 0)
443 					(void) fputc(' ', stdout);
444 				else
445 					(void) fputc('\n', stdout);
446 			}
447 		}
448 		return (0);
449 	}
450 
451 	while (argc > 0) {
452 		struct cmd *p;
453 		boolean_t found_cmd;
454 
455 		found_cmd = _B_FALSE;
456 		for (p = cmds; ; p++) {
457 			assert(p->c_parseflags != END_OF_TABLE);
458 			if (p->c_name) {
459 				if (strcmp(*argv, p->c_name) == 0) {
460 					/*
461 					 * indicate that the command was
462 					 * found and check to see if
463 					 * the address family is valid
464 					 */
465 					found_cmd = _B_TRUE;
466 					if (p->c_af == AF_ANY ||
467 					    af == p->c_af)
468 						break;
469 				}
470 			} else {
471 				if (p->c_af == AF_ANY ||
472 				    af == p->c_af)
473 					break;
474 			}
475 		}
476 		assert(p->c_parseflags != END_OF_TABLE);
477 		/*
478 		 * If we found the keyword, but the address family
479 		 * did not match spit out an error
480 		 */
481 		if (found_cmd && p->c_name == 0) {
482 			(void) fprintf(stderr, "ifparse: Operation %s not"
483 			    " supported for %s\n", *argv, afp->af_name);
484 			return (1);
485 		}
486 		/*
487 		 * else (no keyword found), we assume it's an address
488 		 * of some sort
489 		 */
490 		if (p->c_name == 0 && setaddr) {
491 			p++;	/* got src, do dst */
492 			assert(p->c_parseflags != END_OF_TABLE);
493 		}
494 
495 		if (p->c_parameter == NEXTARG || p->c_parameter == OPTARG) {
496 			argc--, argv++;
497 			if (argc == 0 && p->c_parameter == NEXTARG) {
498 				(void) fprintf(stderr,
499 				    "ifparse: no argument for %s\n",
500 				    p->c_name);
501 				return (1);
502 			}
503 		}
504 
505 		/*
506 		 *	Dump the command if:
507 		 *
508 		 *		there's no address family
509 		 *		restriction
510 		 *	OR
511 		 *		there is a restriction AND
512 		 *		the address families match
513 		 */
514 		if ((p->c_af == AF_ANY)	|| (af == p->c_af))
515 			parsedump(p->c_name, p->c_parameter, p->c_parseflags,
516 			    *argv);
517 		argc--, argv++;
518 	}
519 	parse_dump_buf();
520 
521 	return (0);
522 }
523 
524 /*
525  * Print command usage on standard error.
526  */
527 static void
528 usage(void)
529 {
530 	(void) fprintf(stderr,
531 	    "usage: ifparse [ -fs ] <addr_family> <commands>\n");
532 }
533 
534 int
535 main(int argc, char *argv[])
536 {
537 	int c;
538 	struct afswtch *afp;
539 
540 	while ((c = getopt(argc, argv, "fs")) != -1) {
541 		switch ((char)c) {
542 		case 'f':
543 			parsemode |= PARSEMOVABLE;
544 			break;
545 		case 's':
546 			parsemode |= PARSEFIXED;
547 			break;
548 		case '?':
549 			usage();
550 			exit(1);
551 		}
552 	}
553 
554 	if (parsemode == 0)
555 		parsemode = PARSEFIXED | PARSEMOVABLE;
556 
557 	argc -= optind;
558 	argv += optind;
559 
560 	afp = afs;
561 	if (argc > 0) {
562 		struct afswtch *aftp;
563 		for (aftp = afs; aftp->af_name; aftp++) {
564 			if (strcmp(aftp->af_name, *argv) == 0) {
565 				argc--; argv++;
566 				afp = aftp;
567 				break;
568 			}
569 		}
570 	}
571 
572 	return (ifparse(argc, argv, afp));
573 }
574