/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
/*
 * Copyright (c) 1983 Regents of the University of California.
 * All rights reserved.  The Berkeley software License Agreement
 * specifies the terms and conditions for redistribution.
 */

/*
 * Ifparse splits up an ifconfig command line, and was written for use
 * with the networking boot scripts; see $SRC/cmd/svc/shell/net_include.sh
 *
 * Ifparse can extract selected parts of the ifconfig command line,
 * such as failover address configuration ("ifparse -f"), or everything
 * except failover address configuration ("ifparse -s").  By default,
 * all parts of the command line are extracted (equivalent to ("ifparse -fs").
 *
 * Examples:
 *
 * The command:
 *
 * 	ifparse inet 1.2.3.4 up group two addif 1.2.3.5 up addif 1.2.3.6 up
 *
 * Produces the following on standard output:
 *
 *	set 1.2.3.4 up
 *	group two
 *	addif 1.2.3.5 up
 *	addif 1.2.3.6 up
 *
 * The optional "set" and "destination" keywords are added to make the
 * output easier to process by a script or another command.
 *
 * The command:
 *
 * 	ifparse -f inet 1.2.3.4 -failover up group two addif 1.2.3.5 up
 *
 * Produces:
 *
 *	addif 1.2.3.5  up
 *
 * Only failover address configuration has been requested.  Address
 * 1.2.3.4 is a non-failover address, and so isn't output.
 *
 * The "failover" and "-failover" commands can occur several times for
 * a given logical interface.  Only the last one counts.  For example:
 *
 *	ifparse -f inet 1.2.3.4 -failover failover -failover failover up
 *
 * Produces:
 *
 *	set 1.2.3.4 -failover failover -failover failover up
 *
 * No attempt is made to clean up such "pathological" command lines, by
 * removing redundant "failover" and "-failover" commands.
 */

#include	<sys/types.h>
#include	<stdlib.h>
#include	<stdio.h>
#include	<string.h>
#include	<assert.h>

/*
 * Parser flags:
 *
 *	PARSEFIXED
 *		Command should only appear if non-failover commands
 *		are requested.
 *	PARSEMOVABLE
 *		Command should only appear if failover commands are
 *		requested.
 *	PARSENOW
 *		Don't buffer the command, dump it to output immediately.
 * 	PARSEADD
 *		Indicates processing has moved on to additional
 *		logical interfaces.
 *		Dump the buffer to output and clear buffer contents.
 *	PARSESET
 * 		The "set" and "destination" keywords are optional.
 * 		This flag indicates that the next address not prefixed
 *		with a keyword will be a destination address.
 *	PARSELOG0
 *		Command not valid on additional logical interfaces.
 */

#define	PARSEFIXED	0x01
#define	PARSEMOVABLE	0x02
#define	PARSENOW	0x04
#define	PARSEADD	0x08
#define	PARSESET	0x10
#define	PARSELOG0	0x20

typedef enum { AF_UNSPEC, AF_INET, AF_INET6, AF_ANY } ac_t;

#define	NEXTARG		(-1)	/* command takes an argument */
#define	OPTARG		(-2)	/* command takes an optional argument */

#define	END_OF_TABLE	(-1)

/* Parsemode, the type of commands requested by the user. */
int	parsemode = 0;

/* Parsetype, the type of the command currently in the buffer. */
int	parsetype = PARSEFIXED | PARSEMOVABLE;

/* Parsebuf, pointer to the buffer. */
char	*parsebuf = NULL;

/* Parsebuflen, the size of the buffer area. */
unsigned parsebuflen = 0;

/* Parsedumplen, the amount of the buffer currently in use. */
unsigned parsedumplen = 0;

/*
 * Setaddr, used to decide whether an address without a keyword
 * prefix is a source or destination address.
 */
boolean_t setaddr = _B_FALSE;

/*
 * Some ifconfig commands are only valid on the first logical interface.
 * As soon as an "addif" command is seen, "addint" is set.
 */
boolean_t addint = _B_FALSE;

/*
 * The parser table is based on that in ifconfig.  A command may or
 * may not have an argument, as indicated by whether NEXTARG/OPTARG is
 * in the second column.  Some commands can only be used with certain
 * address families, as indicated in the third column.  The fourth column
 * contains flags that control parser action.
 *
 * Ifparse buffers logical interface configuration commands such as "set",
 * "netmask" and "broadcast".  This buffering continues until an "addif"
 * command is seen, at which point the buffer is emptied, and the process
 * starts again.
 *
 * Some commands do not relate to logical interface configuration and are
 * dumped to output as soon as they are seen, such as "group" and "standby".
 *
 */

struct	cmd {
	char	*c_name;
	int	c_parameter;		/* NEXTARG means next argv */
	int	c_af;			/* address family restrictions */
	int	c_parseflags;		/* parsing flags */
} cmds[] = {
	{ "up",			0,		AF_ANY, 0 },
	{ "down",		0,		AF_ANY, 0 },
	{ "trailers",		0, 		AF_ANY, PARSENOW },
	{ "-trailers",		0,		AF_ANY, PARSENOW },
	{ "arp",		0,		AF_INET, PARSENOW },
	{ "-arp",		0,		AF_INET, PARSENOW },
	{ "private",		0,		AF_ANY, 0 },
	{ "-private",		0,		AF_ANY, 0 },
	{ "router",		0,		AF_ANY, PARSELOG0 },
	{ "-router",		0,		AF_ANY, PARSELOG0 },
	{ "xmit",		0,		AF_ANY, 0 },
	{ "-xmit",		0,		AF_ANY, 0 },
	{ "-nud",		0,		AF_INET6, PARSENOW },
	{ "nud",		0,		AF_INET6, PARSENOW },
	{ "anycast",		0,		AF_ANY, 0 },
	{ "-anycast",		0,		AF_ANY, 0 },
	{ "local",		0,		AF_ANY, 0 },
	{ "-local",		0,		AF_ANY, 0 },
	{ "deprecated",		0,		AF_ANY, 0 },
	{ "-deprecated", 	0, 		AF_ANY, 0 },
	{ "preferred",		0,		AF_INET6, 0 },
	{ "-preferred",		0,		AF_INET6, 0 },
	{ "debug",		0,		AF_ANY, PARSENOW },
	{ "verbose",		0,		AF_ANY, PARSENOW },
	{ "netmask",		NEXTARG,	AF_INET, 0 },
	{ "metric",		NEXTARG,	AF_ANY, 0 },
	{ "mtu",		NEXTARG,	AF_ANY, 0 },
	{ "index",		NEXTARG,	AF_ANY, PARSELOG0 },
	{ "broadcast",		NEXTARG,	AF_INET, 0 },
	{ "auto-revarp", 	0,		AF_INET, PARSEFIXED},
	{ "plumb",		0,		AF_ANY, PARSENOW },
	{ "unplumb",		0,		AF_ANY, PARSENOW },
	{ "ipmp",		0,		AF_ANY, PARSELOG0 },
	{ "subnet",		NEXTARG,	AF_ANY, 0 },
	{ "token",		NEXTARG,	AF_INET6, PARSELOG0 },
	{ "tsrc",		NEXTARG,	AF_ANY, PARSELOG0 },
	{ "tdst",		NEXTARG,	AF_ANY, PARSELOG0 },
	{ "encr_auth_algs", 	NEXTARG,	AF_ANY, PARSELOG0 },
	{ "encr_algs",		NEXTARG,	AF_ANY, PARSELOG0 },
	{ "auth_algs",		NEXTARG,	AF_ANY, PARSELOG0 },
	{ "addif",		NEXTARG,	AF_ANY, PARSEADD },
	{ "removeif",		NEXTARG,	AF_ANY, PARSELOG0 },
	{ "modlist",		0,		AF_ANY, PARSENOW },
	{ "modinsert",		NEXTARG,	AF_ANY, PARSENOW },
	{ "modremove",		NEXTARG,	AF_ANY, PARSENOW },
	{ "failover",		0,		AF_ANY, PARSEMOVABLE },
	{ "-failover",		0, 		AF_ANY, PARSEFIXED },
	{ "standby",		0,		AF_ANY, PARSENOW },
	{ "-standby",		0,		AF_ANY, PARSENOW },
	{ "failed",		0,		AF_ANY, PARSENOW },
	{ "-failed",		0,		AF_ANY, PARSENOW },
	{ "group",		NEXTARG,	AF_ANY, PARSELOG0 },
	{ "configinfo",		0,		AF_ANY, PARSENOW },
	{ "encaplimit",		NEXTARG,	AF_ANY,	PARSELOG0 },
	{ "-encaplimit",	0,		AF_ANY,	PARSELOG0 },
	{ "thoplimit",		NEXTARG,	AF_ANY, PARSELOG0 },
	{ "set",		NEXTARG,	AF_ANY, PARSESET },
	{ "destination",	NEXTARG,	AF_ANY, 0 },
	{ "zone",		NEXTARG,	AF_ANY, 0 },
	{ "-zone",		0,		AF_ANY, 0 },
	{ "all-zones",		0,		AF_ANY, 0 },
	{ "ether",		OPTARG,		AF_ANY, PARSENOW },
	{ "usesrc",		NEXTARG,	AF_ANY, PARSENOW },
	{ 0 /* ether addr */,	0,		AF_UNSPEC, PARSELOG0 },
	{ 0 /* set */,		0,		AF_ANY, PARSESET },
	{ 0 /* destination */,	0,		AF_ANY, 0 },
	{ 0,			END_OF_TABLE,	END_OF_TABLE, END_OF_TABLE},
};


/* Known address families */
struct afswtch {
	char *af_name;
	short af_af;
} afs[] = {
	{ "inet",	AF_INET },
	{ "ether",	AF_UNSPEC },
	{ "inet6",	AF_INET6 },
	{ 0,		0 }
};

/*
 * Append "item" to the buffer.  If there isn't enough room in the buffer,
 * expand it.
 */
static void
parse_append_buf(char *item)
{
	unsigned itemlen;
	unsigned newdumplen;

	if (item == NULL)
		return;

	itemlen = strlen(item);
	newdumplen = parsedumplen + itemlen;

	/* Expand dump buffer as needed */
	if (parsebuflen < newdumplen)  {
		if ((parsebuf = realloc(parsebuf, newdumplen)) == NULL) {
			perror("ifparse");
			exit(1);
		}
		parsebuflen = newdumplen;
	}
	(void) memcpy(parsebuf + parsedumplen, item, itemlen);

	parsedumplen = newdumplen;
}

/*
 * Dump the buffer to output.
 */
static void
parse_dump_buf(void)
{
	/*
	 * When parsing, a set or addif command,  we may be some way into
	 * the command before we definitely know it is movable or fixed.
	 * If we get to the end of the command, and haven't seen a
	 * "failover" or "-failover" flag, the command is movable.
	 */
	if (!((parsemode == PARSEFIXED) && (parsetype & PARSEMOVABLE) != 0) &&
	    (parsemode & parsetype) != 0 && parsedumplen != 0) {
		unsigned i;

		if (parsebuf[parsedumplen] == ' ')
			parsedumplen--;

		for (i = 0; i < parsedumplen; i++)
			(void) putchar(parsebuf[i]);

		(void) putchar('\n');
	}
	/* The buffer is kept in case there is more parsing to do */
	parsedumplen = 0;
	parsetype = PARSEFIXED | PARSEMOVABLE;
}

/*
 * Process a command.  The command will either be put in the buffer,
 * or dumped directly to output.  The current contents of the buffer
 * may be dumped to output.
 *
 * The buffer holds commands relating to a particular logical interface.
 * For example, "set", "destination", "failover", "broadcast", all relate
 * to a particular interface.  Such commands have to be buffered until
 * all the "failover" and "-failover" commands for that interface have
 * been seen, only then will we know whether the command is movable
 * or not.  When the "addif" command is seen, we know we are about to
 * start processing a new logical interface, we've seen all the
 * "failover" and "-failover" commands for the previous interface, and
 * can decide whether the buffer contents are movable or not.
 *
 */
static void
parsedump(char *cmd, int param, int flags, char *arg)
{
	char *cmdname;	/* Command name	*/
	char *cmdarg;	/* Argument to command, if it takes one, or NULL */

	/*
	 * Is command only valid on logical interface 0?
	 * If processing commands on an additional logical interface, ignore
	 * the command.
	 * If processing commands on logical interface 0, don't buffer the
	 * command, dump it straight to output.
	 */
	if ((flags & PARSELOG0) != 0) {
		if (addint)
			return;
		flags |= PARSENOW;
	}

	/*
	 * If processing the "addif" command, a destination address may
	 * follow without the "destination" prefix.  Add PARSESET to the
	 * flags so that such an anonymous address is processed correctly.
	 */
	if ((flags & PARSEADD) != 0) {
		flags |= PARSESET;
		addint = _B_TRUE;
	}

	/*
	 * Commands that must be dumped straight to output are always fixed
	 * (non-movable) commands.
	 *
	 */
	if ((flags & PARSENOW) != 0)
		flags |= PARSEFIXED;

	/*
	 * Source and destination addresses do not have to be prefixed
	 * with the keywords "set" or "destination".  Ifparse always
	 * inserts the optional keyword.
	 */
	if (cmd == NULL) {
		cmdarg = arg;
		if ((flags & PARSESET) != 0)
			cmdname = "set";
		else if (setaddr) {
			cmdname = "destination";
			setaddr = _B_FALSE;
		} else
			cmdname = "";
	} else {
		cmdarg = (param == 0) ? NULL : arg;
		cmdname = cmd;
	}

	/*
	 * The next address without a prefix will be a destination
	 * address.
	 */
	if ((flags & PARSESET) != 0)
		setaddr = _B_TRUE;

	/*
	 * Dump the command straight to output?
	 * Only dump the command if the parse mode specified on
	 * the command line matches the type of the command.
	 */
	if ((flags & PARSENOW) != 0) {
		if ((parsemode & flags) != 0)  {
			(void) fputs(cmdname, stdout);
			if (cmdarg != NULL) {
				(void) fputc(' ', stdout);
				(void) fputs(cmdarg, stdout);
			}
			(void) fputc('\n', stdout);
		}
		return;
	}

	/*
	 * Only the commands relating to a particular logical interface
	 * are buffered.  When an "addif" command is seen, processing is
	 * about to start on a new logical interface, so dump the
	 * buffer to output.
	 */
	if ((flags & PARSEADD) != 0)
		parse_dump_buf();

	/*
	 * If the command flags indicate the command is fixed or
	 * movable, update the type of the interface in the buffer
	 * accordingly.  For example, "-failover" has the "PARSEFIXED"
	 * flag, and the contents of the buffer are not movable if
	 * "-failover" is seen.
	 */
	if ((flags & PARSEFIXED) != 0)
		parsetype &= ~PARSEMOVABLE;

	if ((flags & PARSEMOVABLE) != 0)
		parsetype &= ~PARSEFIXED;

	parsetype |= flags & (PARSEFIXED | PARSEMOVABLE);

	parse_append_buf(cmdname);

	if (cmdarg != NULL) {
		parse_append_buf(" ");
		parse_append_buf(cmdarg);
	}

	parse_append_buf(" ");
}

/*
 * Parse the part of the command line following the address family
 * specification, if any.
 *
 * This function is a modified version of the function "ifconfig" in
 * ifconfig.c.
 */
static int
ifparse(int argc, char *argv[], struct afswtch *afp)
{
	int af = afp->af_af;

	if (argc == 0)
		return (0);

	if (strcmp(*argv, "auto-dhcp") == 0 || strcmp(*argv, "dhcp") == 0) {
		if ((parsemode & PARSEFIXED) != NULL) {
			while (argc) {
				(void) fputs(*argv++, stdout);
				if (--argc != 0)
					(void) fputc(' ', stdout);
				else
					(void) fputc('\n', stdout);
			}
		}
		return (0);
	}

	while (argc > 0) {
		struct cmd *p;
		boolean_t found_cmd;

		found_cmd = _B_FALSE;
		for (p = cmds; ; p++) {
			assert(p->c_parseflags != END_OF_TABLE);
			if (p->c_name) {
				if (strcmp(*argv, p->c_name) == 0) {
					/*
					 * indicate that the command was
					 * found and check to see if
					 * the address family is valid
					 */
					found_cmd = _B_TRUE;
					if (p->c_af == AF_ANY ||
					    af == p->c_af)
						break;
				}
			} else {
				if (p->c_af == AF_ANY ||
				    af == p->c_af)
					break;
			}
		}
		assert(p->c_parseflags != END_OF_TABLE);
		/*
		 * If we found the keyword, but the address family
		 * did not match spit out an error
		 */
		if (found_cmd && p->c_name == 0) {
			(void) fprintf(stderr, "ifparse: Operation %s not"
			    " supported for %s\n", *argv, afp->af_name);
			return (1);
		}
		/*
		 * else (no keyword found), we assume it's an address
		 * of some sort
		 */
		if (p->c_name == 0 && setaddr) {
			p++;	/* got src, do dst */
			assert(p->c_parseflags != END_OF_TABLE);
		}

		if (p->c_parameter == NEXTARG || p->c_parameter == OPTARG) {
			argc--, argv++;
			if (argc == 0 && p->c_parameter == NEXTARG) {
				(void) fprintf(stderr,
				    "ifparse: no argument for %s\n",
				    p->c_name);
				return (1);
			}
		}

		/*
		 *	Dump the command if:
		 *
		 *		there's no address family
		 *		restriction
		 *	OR
		 *		there is a restriction AND
		 *		the address families match
		 */
		if ((p->c_af == AF_ANY)	|| (af == p->c_af))
			parsedump(p->c_name, p->c_parameter, p->c_parseflags,
			    *argv);
		argc--, argv++;
	}
	parse_dump_buf();

	return (0);
}

/*
 * Print command usage on standard error.
 */
static void
usage(void)
{
	(void) fprintf(stderr,
	    "usage: ifparse [ -fs ] <addr_family> <commands>\n");
}

int
main(int argc, char *argv[])
{
	int c;
	struct afswtch *afp;

	while ((c = getopt(argc, argv, "fs")) != -1) {
		switch ((char)c) {
		case 'f':
			parsemode |= PARSEMOVABLE;
			break;
		case 's':
			parsemode |= PARSEFIXED;
			break;
		case '?':
			usage();
			exit(1);
		}
	}

	if (parsemode == 0)
		parsemode = PARSEFIXED | PARSEMOVABLE;

	argc -= optind;
	argv += optind;

	afp = afs;
	if (argc > 0) {
		struct afswtch *aftp;
		for (aftp = afs; aftp->af_name; aftp++) {
			if (strcmp(aftp->af_name, *argv) == 0) {
				argc--; argv++;
				afp = aftp;
				break;
			}
		}
	}

	return (ifparse(argc, argv, afp));
}