/*
 * Copyright (c) 2019-2021 Job Snijders <job@sobornost.net>
 * Copyright (c) 2007-2019 Alexandre Snarskii <snar@snar.spb.ru>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include <sys/types.h>
#include <sys/socket.h>

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>

#include "extern.h"
#include "sx_report.h"

extern int debug_expander;
extern int debug_aggregation;
extern int pipelining;
extern int expand_special_asn;

static int
usage(int ecode)
{
	printf("\nUsage: bgpq4 [-h host[:port]] [-S sources] [-E|G|H <num>"
	    "|f <num>|t] [-46ABbdJjKNnpwXz] [-R len] <OBJECTS> ... "
	    "[EXCEPT <OBJECTS> ...]\n");
	printf("\nVendor targets:\n");
	printf(" no option : Cisco IOS Classic (default)\n");
	printf(" -X        : Cisco IOS XR\n");
	printf(" -U        : Huawei\n");
	printf(" -u        : Huawei XPL\n");
	printf(" -j        : JSON\n");
	printf(" -J        : Juniper Junos\n");
	printf(" -K        : MikroTik RouterOSv6\n");
	printf(" -K7       : MikroTik RouterOSv7\n");
	printf(" -b        : NIC.CZ BIRD\n");
	printf(" -N        : Nokia SR OS (Classic CLI)\n");
	printf(" -n        : Nokia SR OS (MD-CLI)\n");
	printf(" -n2       : Nokia SR Linux\n");
	printf(" -B        : OpenBSD OpenBGPD\n");
	printf(" -e        : Arista EOS\n");
	printf(" -F fmt    : User defined format (example: '-F %%n/%%l')\n");

	printf("\nInput filters:\n");
	printf(" -4        : generate IPv4 prefix-lists (default)\n");
	printf(" -6        : generate IPv6 prefix-lists\n");
	printf(" -m len    : maximum prefix length (default: 32 for IPv4, "
		"128 for IPv6)\n");
	printf(" -L depth  : limit recursion depth (default: unlimited)\n"),
	printf(" -S sources: only use specified IRR sources, in the specified "
	    "order (comma separated)\n");
	printf(" -w        : 'validate' AS numbers: accept only ones with "
		"registered routes\n");

	printf("\nOutput modifiers:\n");
	printf(" -3        : assume that your device is asn32-safe (default)\n");
	printf(" -A        : try to aggregate prefix-lists/route-filters\n");
	printf(" -E        : generate extended access-list (Cisco), "
	    "route-filter (Juniper)\n"
	    "             [ip|ipv6]-prefix-list (Nokia) or prefix-set "
	    "(OpenBGPD)\n");
	printf(" -f number : generate input as-path access-list\n");
	printf(" -G number : generate output as-path access-list\n");
	printf(" -H number : generate origin as-lists (JunOS only)\n");
	printf(" -M match  : extra match conditions for JunOS route-filters\n");
	printf(" -l name   : use specified name for generated access/prefix/.."
		" list\n");
	printf(" -p        : allow special ASNs like 23456 or in the private range\n");
	printf(" -R len    : allow more specific routes up to specified masklen\n");
	printf(" -r len    : allow more specific routes from masklen specified\n");
	printf(" -s        : generate sequence numbers in prefix-lists (IOS only)\n");
	printf(" -t        : generate as-sets for OpenBGPD (OpenBGPD 6.4+), BIRD "
		"and JSON formats\n");
	printf(" -z        : generate route-filter-list (Junos only)\n");
	printf(" -W len    : specify max-entries on as-path/as-list line (use 0 for "
		"infinity)\n");

	printf("\nUtility operations:\n");
	printf(" -d        : generate some debugging output\n");
	printf(" -h host   : host running IRRD software (default: rr.ntt.net)\n"
		    "             use 'host:port' to specify alternate port\n");
	printf(" -T        : disable pipelining (not recommended)\n");
	printf(" -v        : print version and exit\n");
	printf("\n" PACKAGE_NAME " version: " PACKAGE_VERSION " "
	    "(https://github.com/bgp/bgpq4)\n");
	exit(ecode);
}

static void
version(void)
{
	printf(PACKAGE_NAME " - a versatile utility to generate BGP filters\n"
	    "version: " PACKAGE_VERSION "\n"
	    "website: https://github.com/bgp/bgpq4\n"
	    "maintainer: Job Snijders <job@sobornost.net>\n");
	exit(0);
}

static void
exclusive(void)
{
	fprintf(stderr,"-E, -F, -K , -f <asnum>, -G <asnum>, and -t are mutually"
	    " exclusive\n");
	exit(1);
}

static void
vendor_exclusive(void)
{
	fprintf(stderr, "-b (BIRD), -B (OpenBGPD), -F (formatted), -J (Junos),"
	    " -j (JSON), -K[7] (Microtik ROS), -N (Nokia SR OS Classic),"
	    " -n (Nokia SR OS MD-CLI), -U (Huawei), -u (Huawei XPL),"
	    "-e (Arista) and -X (IOS XR) options are mutually exclusive\n");
	exit(1);
}

static int
parseasnumber(struct bgpq_expander *expander, char *asnstr)
{
	char	*eon = NULL;

	expander->asnumber = strtoul(asnstr, &eon, 10);
	if (expander->asnumber < 1 || expander->asnumber > (65535ul * 65535)) {
		sx_report(SX_FATAL, "Invalid AS number: %s\n", asnstr);
		exit(1);
	}
	if (eon && *eon == '.') {
		/* -f 3.3, for example */
		uint32_t loas = strtoul(eon + 1, &eon, 10);
		if (expander->asnumber > 65535) {
			/* should prevent incorrect numbers like 65537.1 */
			sx_report(SX_FATAL,"Invalid AS number: %s\n", asnstr);
			exit(1);
		}
		if (loas < 1 || loas > 65535) {
			sx_report(SX_FATAL,"Invalid AS number: %s\n", asnstr);
			exit(1);
		}
		if (eon && *eon) {
			sx_report(SX_FATAL,"Invalid symbol in AS number: "
			    "%c (%s)\n", *eon, asnstr);
			exit(1);
		}
		expander->asnumber=(expander->asnumber << 16) + loas;
	} else if (eon && *eon) {
		sx_report(SX_FATAL,"Invalid symbol in AS number: %c (%s)\n",
			*eon, asnstr);
		exit(1);
	}
	return 0;
}

int
main(int argc, char* argv[])
{
	int c;
	struct bgpq_expander expander;
	int af = AF_INET, selectedipv4 = 0, exceptmode = 0;
	int widthSet = 0, aggregate = 0, refine = 0, refineLow = 0;
	unsigned long maxlen = 0;

#ifdef HAVE_PLEDGE
	if (pledge("stdio inet dns", NULL) == -1) {
		sx_report(SX_ERROR, "pledge() failed");
		exit(1);
	}
#endif

	bgpq_expander_init(&expander, af);

	if (getenv("IRRD_SOURCES"))
		expander.sources=getenv("IRRD_SOURCES");

	while ((c = getopt(argc, argv,
	    "23467a:AbBdDEeF:S:jJKf:l:L:m:M:NnpW:r:R:G:H:tTh:UuwXsvz")) != EOF) {
	switch (c) {
	case '2':
		if (expander.vendor != V_NOKIA_MD) {
			sx_report(SX_FATAL, "'2' can only be used after -n\n");
			exit(1);
		}
		expander.vendor = V_NOKIA_SRL;
		break;
	case '3':
		/* do nothing, 32-bit ASN support is assumed */
		break;
	case '4':
		/* do nothing, expander already configured for IPv4 */
		if (expander.family == AF_INET6) {
			sx_report(SX_FATAL, "-4 and -6 are mutually "
			    "exclusive\n");
			exit(1);
		}
		selectedipv4 = 1;
		break;
	case '6':
		if (selectedipv4) {
			sx_report(SX_FATAL, "-4 and -6 are mutually "
			    "exclusive\n");
			exit(1);
		}
		af = AF_INET6;
		expander.family = AF_INET6;
		expander.tree->family = AF_INET6;
		break;
	case '7':
		if (expander.vendor != V_MIKROTIK6) {
			sx_report(SX_FATAL, "'7' can only be used after -K\n");
			exit(1);
		}
		expander.vendor = V_MIKROTIK7;
		break;
	case 'a':
		parseasnumber(&expander, optarg);
		break;
	case 'A':
		if (aggregate)
			debug_aggregation++;
		aggregate = 1;
		break;
	case 'b':
		if (expander.vendor)
			vendor_exclusive();
		expander.vendor = V_BIRD;
		break;
	case 'B':
		if (expander.vendor)
			vendor_exclusive();
		expander.vendor = V_OPENBGPD;
		break;
	case 'd':
		debug_expander++;
		break;
	case 'E':
		if (expander.generation)
			exclusive();
		expander.generation = T_EACL;
		break;
	case 'e':
		if (expander.vendor)
			vendor_exclusive();
		expander.vendor = V_ARISTA;
		expander.sequence = 1;
		break;
	case 'F':
		if (expander.vendor)
			exclusive();
		expander.vendor = V_FORMAT;
		expander.format = optarg;
		break;
	case 'f':
		if (expander.generation)
			exclusive();
		expander.generation = T_ASPATH;
		parseasnumber(&expander, optarg);
		break;
	case 'G':
		if (expander.generation)
			exclusive();
		expander.generation = T_OASPATH;
		parseasnumber(&expander, optarg);
		break;
	case 'H':
		if (expander.generation)
			exclusive();
		expander.generation = T_ASLIST;
		parseasnumber(&expander, optarg);
		break;
	case 'h':
		{
			char *d = strchr(optarg, ':');
			expander.server = optarg;
			if (d) {
				*d = 0;
				expander.port = d + 1;
			}
		}
		break;
	case 'J':
		if (expander.vendor)
			vendor_exclusive();
		expander.vendor = V_JUNIPER;
		break;
	case 'j':
		if (expander.vendor)
			vendor_exclusive();
		expander.vendor = V_JSON;
		break;
	case 'K':
		if (expander.vendor)
			vendor_exclusive();
		expander.vendor = V_MIKROTIK6;
		break;
	case 'r':
		refineLow = strtoul(optarg, NULL, 10);
		if (!refineLow) {
			sx_report(SX_FATAL, "Invalid refineLow value:"
			    " %s\n", optarg);
			exit(1);
		}
		break;
	case 'R':
		refine = strtoul(optarg, NULL, 10);
		if (!refine) {
			sx_report(SX_FATAL,"Invalid refine length:"
			    " %s\n", optarg);
			exit(1);
		}
		break;
	case 'l':
		expander.name = optarg;
		break;
	case 'L':
		expander.maxdepth = strtol(optarg, NULL, 10);
		if (expander.maxdepth < 1) {
			sx_report(SX_FATAL, "Invalid maximum recursion"
			    " (-L): %s\n", optarg);
			exit(1);
		}
		break;
	case 'm':
		maxlen=strtoul(optarg, NULL, 10);
		if (!maxlen) {
			sx_report(SX_FATAL, "Invalid maxlen (-m): %s\n",
			    optarg);
			exit(1);
		}
		break;
	case 'M':
		{
			char	*mc, *md;
			expander.match = strdup(optarg);
			mc = md = expander.match;
			while (*mc) {
				if (*mc == '\\') {
					if (*(mc + 1) == '\n') {
						*md = '\n';
						md++;
						mc += 2;
					} else if (*(mc + 1) == 'r') {
						*md = '\r';
						md++;
						mc += 2;
					} else if (*(mc + 1) == 't') {
						*md = '\t';
						md++;
						mc += 2;
					} else if (*(mc + 1) == '\\') {
						*md = '\\';
						md++;
						mc += 2;
					} else {
						sx_report(SX_FATAL, "Unsupported"
						    " escape \%c (0x%2.2x) in "
						    "'%s'\n",
						    isprint(*mc) ? *mc : 20,
						    *mc, optarg);
						exit(1);
					}
				} else {
					if (mc != md) {
						*md = *mc;
					}
					md++;
					mc++;
				}
			}
			*md = 0;
		}
		break;
	case 'N':
		if (expander.vendor)
			vendor_exclusive();
		expander.vendor = V_NOKIA;
		break;
	case 'n':
		if (expander.vendor)
			vendor_exclusive();
		expander.vendor = V_NOKIA_MD;
		break;
	case 'p':
		expand_special_asn = 1;
		break;
	case 't':
		if (expander.generation)
			exclusive();
		expander.generation = T_ASSET;
		break;
	case 'T':
		pipelining = 0;
		break;
	case 's':
		expander.sequence = 1;
		break;
	case 'S':
		expander.sources = optarg;
		break;
	case 'U':
		if (expander.vendor)
			exclusive();
		expander.vendor = V_HUAWEI;
		break;
	case 'u':
		if (expander.vendor)
			exclusive();
		expander.vendor = V_HUAWEI_XPL;
		break;
	case 'W':
		expander.aswidth = atoi(optarg);
		if (expander.aswidth < 0) {
			sx_report(SX_FATAL,"Invalid as-width: %s\n", optarg);
			exit(1);
		}
		widthSet = 1;
		break;
	case 'w':
		expander.validate_asns = 1;
		break;
	case 'X':
		if (expander.vendor)
			vendor_exclusive();
		expander.vendor = V_CISCO_XR;
		break;
	case 'v':
		version();
		break;
	case 'z':
		if (expander.generation)
			exclusive();
		expander.generation = T_ROUTE_FILTER_LIST;
		break;
	default:
		usage(1);
	}
	}

	argc -= optind;
	argv += optind;

	if (!widthSet) {
		if (expander.generation == T_ASPATH) {
			int vendor = expander.vendor;
			switch (vendor) {
			case V_ARISTA:
			case V_CISCO:
			case V_MIKROTIK6:
			case V_MIKROTIK7:
				expander.aswidth = 4;
				break;
			case V_CISCO_XR:
				expander.aswidth = 6;
				break;
			case V_JUNIPER:
			case V_NOKIA:
			case V_NOKIA_MD:
			case V_NOKIA_SRL:
				expander.aswidth = 8;
				break;
			case V_BIRD:
				expander.aswidth = 10;
				break;
			}
		} else if (expander.generation == T_OASPATH) {
			int vendor = expander.vendor;
			switch (vendor) {
			case V_ARISTA:
			case V_CISCO:
				expander.aswidth = 5;
				break;
			case V_CISCO_XR:
				expander.aswidth = 7;
				break;
			case V_JUNIPER:
			case V_NOKIA:
			case V_NOKIA_MD:
			case V_NOKIA_SRL:
				expander.aswidth = 8;
				break;
			}
		} else if (expander.generation == T_ASLIST) {
			int vendor = expander.vendor;
			switch (vendor) {
			case V_JUNIPER:
				expander.aswidth = 8;
				break;
			}
		}
	}

	if (!expander.generation)
		expander.generation = T_PREFIXLIST;

	if (expander.vendor == V_CISCO_XR
	    && expander.generation != T_PREFIXLIST
	    && expander.generation != T_ASPATH
	    && expander.generation != T_OASPATH) {
		sx_report(SX_FATAL, "Sorry, only prefix-sets and as-paths "
		    "supported for IOS XR\n");
	}
	if (expander.vendor == V_BIRD
	    && expander.generation != T_PREFIXLIST
	    && expander.generation != T_ASPATH
	    && expander.generation != T_ASSET) {
		sx_report(SX_FATAL, "Sorry, only prefix-lists and as-paths/as-sets "
		    "supported for BIRD output\n");
	}
	if (expander.vendor == V_JSON
	    && expander.generation != T_PREFIXLIST
	    && expander.generation != T_ASPATH
	    && expander.generation != T_ASSET) {
		sx_report(SX_FATAL, "Sorry, only prefix-lists and as-paths/as-sets "
		    "supported for JSON output\n");
	}

	if (expander.vendor == V_FORMAT
	    && expander.generation != T_PREFIXLIST)
		sx_report(SX_FATAL, "Sorry, only prefix-lists supported in formatted "
		    "output\n");

	if (expander.vendor == V_HUAWEI
	    && expander.generation != T_ASPATH
	    && expander.generation != T_OASPATH
	    && expander.generation != T_PREFIXLIST)
		sx_report(SX_FATAL, "Sorry, only as-paths and prefix-lists supported "
		    "for Huawei output\n");

	if (expander.generation == T_ROUTE_FILTER_LIST
	    && expander.vendor != V_JUNIPER)
		sx_report(SX_FATAL, "Route-filter-lists (-z) supported for Juniper (-J)"
		    " output only\n");

	if (expander.generation == T_ASSET
	    && expander.vendor != V_JSON
	    && expander.vendor != V_OPENBGPD
	    && expander.vendor != V_BIRD)
		sx_report(SX_FATAL, "As-Sets (-t) supported for JSON (-j), OpenBGPD "
		    "(-B) and BIRD (-b) output only\n");

	if (aggregate
	    && expander.vendor == V_JUNIPER
	    && expander.generation == T_PREFIXLIST) {
		sx_report(SX_FATAL, "Sorry, aggregation (-A) does not work in"
		    " Juniper prefix-lists\nYou can try route-filters (-E) "
		    "or route-filter-lists (-z) instead of prefix-lists\n.");
		exit(1);
	}

	if (aggregate
	    && (expander.vendor == V_NOKIA_MD || expander.vendor == V_NOKIA || expander.vendor == V_NOKIA_SRL)
	    && expander.generation != T_PREFIXLIST) {
		sx_report(SX_FATAL, "Sorry, aggregation (-A) is not supported with "
		    "ip-prefix-lists (-E) on Nokia.\n");
		exit(1);
	}

	if (refine
	    && (expander.vendor == V_NOKIA_MD || expander.vendor == V_NOKIA || expander.vendor == V_NOKIA_SRL)
	    && expander.generation != T_PREFIXLIST) {
		sx_report(SX_FATAL, "Sorry, more-specifics (-R) is not supported with "
		    "ip-prefix-lists (-E) on Nokia.\n");
		exit(1);
	}

	if (refineLow
	     && (expander.vendor == V_NOKIA_MD || expander.vendor == V_NOKIA || expander.vendor == V_NOKIA_SRL)
	     && expander.generation != T_PREFIXLIST) {
		sx_report(SX_FATAL, "Sorry, more-specifics (-r) is not supported with "
		    "ip-prefix-lists (-E) on Nokia.\n");
		exit(1);
	}

	if (aggregate && expander.generation < T_PREFIXLIST) {
		sx_report(SX_FATAL, "Sorry, aggregation (-A) used only for prefix-"
		    "lists, extended access-lists and route-filters\n");
		exit(1);
	}

	if (expander.sequence
	    && (expander.vendor != V_CISCO && expander.vendor != V_ARISTA)) {
		sx_report(SX_FATAL, "Sorry, prefix-lists sequencing (-s) supported"
		    " only for IOS and EOS\n");
		exit(1);
	}

	if (expander.sequence && expander.generation < T_PREFIXLIST) {
		sx_report(SX_FATAL, "Sorry, prefix-lists sequencing (-s) can't be "
		    " used for non prefix-list\n");
		exit(1);
	}

	if (refineLow && !refine) {
		if (expander.family == AF_INET)
			refine = 32;
		else
			refine = 128;
	}

	if (refineLow && refineLow > refine)
		sx_report(SX_FATAL, "Incompatible values for -r %u and -R %u\n",
		    refineLow, refine);

	if (refine || refineLow) {
		if (expander.family == AF_INET6 && refine > 128) {
			sx_report(SX_FATAL, "Invalid value for refine(-R): %u (1-128 for"
			    " IPv6)\n", refine);
		} else if (expander.family == AF_INET6 && refineLow > 128) {
			sx_report(SX_FATAL, "Invalid value for refineLow(-r): %u (1-128 for"
			    " IPv6)\n", refineLow);
		} else if (expander.family == AF_INET && refine > 32) {
			sx_report(SX_FATAL, "Invalid value for refine(-R): %u (1-32 for"
			    " IPv4)\n", refine);
		} else if (expander.family == AF_INET && refineLow > 32) {
			sx_report(SX_FATAL, "Invalid value for refineLow(-r): %u (1-32 for"
			    " IPv4)\n", refineLow);
		}

		if (expander.vendor == V_JUNIPER && expander.generation == T_PREFIXLIST) {
			if (refine) {
				sx_report(SX_FATAL, "Sorry, more-specific filters (-R %u) "
				    "is not supported for Juniper prefix-lists.\n"
				    "Use router-filters (-E) or route-filter-lists (-z) "
				    "instead\n", refine);
			} else {
				sx_report(SX_FATAL, "Sorry, more-specific filters (-r %u) "
				    "is not supported for Juniper prefix-lists.\n"
				    "Use route-filters (-E) or route-filter-lists (-z) "
				    "instead\n", refineLow);
			}
		}

		if (expander.generation < T_PREFIXLIST) {
			if (refine)
				sx_report(SX_FATAL, "Sorry, more-specific filter (-R %u) "
				    "supported only with prefix-list generation\n", refine);
			else
				sx_report(SX_FATAL, "Sorry, more-specific filter (-r %u) "
				    "supported only with prefix-list generation\n", refineLow);
		}
	}

	if (maxlen) {
		if ((expander.family == AF_INET6 && maxlen > 128)
		   || (expander.family == AF_INET && maxlen > 32)) {
			sx_report(SX_FATAL, "Invalid value for max-prefixlen: %lu (1-128 "
			    "for IPv6, 1-32 for IPv4)\n", maxlen);
			exit(1);
		} else if ((expander.family == AF_INET6 && maxlen < 128)
		    || (expander.family == AF_INET  && maxlen < 32)) {
			/*
			 * inet6/128 and inet4/32 does not make sense - all
			 * routes will be accepted, so save some CPU cycles :)
			 */
			expander.maxlen = maxlen;
		}
	} else if (expander.family == AF_INET)
		expander.maxlen = 32;
	else if (expander.family == AF_INET6)
		expander.maxlen = 128;

	if (expander.generation == T_EACL && expander.vendor == V_CISCO
	    && expander.family == AF_INET6) {
		sx_report(SX_FATAL,"Sorry, ipv6 access-lists not supported "
		    "for Cisco yet.\n");
	}

	if (expander.match != NULL
	    && (expander.vendor != V_JUNIPER || expander.generation != T_EACL)) {
		sx_report(SX_FATAL, "Sorry, extra match conditions (-M) can be used "
		    "only with Juniper route-filters\n");
	}

	if ((expander.generation == T_ASPATH
	    || expander.generation == T_OASPATH
	    || expander.generation == T_ASLIST)
	    && af != AF_INET && !expander.validate_asns) {
		sx_report(SX_FATAL, "Sorry, -6 makes no sense with as-path (-f/-G) or as-list (-H) "
		    "generation\n");
	}

	if (expander.validate_asns
	    && expander.generation != T_ASPATH
	    && expander.generation != T_OASPATH
	    && expander.generation != T_ASLIST) {
		sx_report(SX_FATAL, "Sorry, -w makes sense only for as-path "
		    "(-f/-G) generation\n");
	}

	if (!argv[0])
		usage(1);

	while (argv[0]) {
		char *obj = argv[0];
		char *delim = strstr(argv[0], "::");
		if (delim) {
			expander.usesource = 1;
			obj = delim + 2;
		}
		if (!strcmp(argv[0], "EXCEPT")) {
			exceptmode = 1;
		} else if (exceptmode) {
			bgpq_expander_add_stop(&expander, argv[0]);
		} else if (!strncasecmp(obj, "AS-", 3)) {
			bgpq_expander_add_asset(&expander, argv[0]);
		} else if (!strncasecmp(obj, "RS-", 3)) {
			bgpq_expander_add_rset(&expander, argv[0]);
		} else if (!strncasecmp(obj, "AS", 2)) {
			char *ec;
			if ((ec = strchr(obj, ':'))) {
				if (!strncasecmp(ec + 1, "AS-", 3)) {
					bgpq_expander_add_asset(&expander, argv[0]);
				} else if (!strncasecmp(ec + 1, "RS-", 3)) {
					bgpq_expander_add_rset(&expander, argv[0]);
				} else {
					SX_DEBUG(debug_expander,"Unknown sub-as"
					    " object %s\n", argv[0]);
				}
			} else {
				bgpq_expander_add_as(&expander, argv[0]);
			}
		} else {
			char *ec = strchr(argv[0], '^');
			if (!ec && !bgpq_expander_add_prefix(&expander, argv[0])) {
				sx_report(SX_ERROR, "Unable to add prefix %s "
				    "(bad prefix or address-family)\n", argv[0]);
				exit(1);
			} else if (ec && !bgpq_expander_add_prefix_range(&expander,
				    argv[0])) {
				sx_report(SX_ERROR, "Unable to add prefix-range "
				    "%s (bad range or address-family)\n",
				    argv[0]);
				exit(1);
			}
		}
		argv++;
		argc--;
	}

	if (!bgpq_expand(&expander))
		exit(1);

	if (refine)
		sx_radix_tree_refine(expander.tree, refine);

	if (refineLow)
		sx_radix_tree_refineLow(expander.tree, refineLow);

	if (aggregate)
		sx_radix_tree_aggregate(expander.tree);

	switch (expander.generation) {
		case T_NONE:
			sx_report(SX_FATAL,"Unreachable point");
			exit(1);
		case T_ASPATH:
			bgpq4_print_aspath(stdout, &expander);
			break;
		case T_OASPATH:
			bgpq4_print_oaspath(stdout, &expander);
			break;
		case T_ASLIST:
			bgpq4_print_aslist(stdout, &expander);
			break;
		case T_ASSET:
			bgpq4_print_asset(stdout, &expander);
			break;
		case T_PREFIXLIST:
			bgpq4_print_prefixlist(stdout, &expander);
			break;
		case T_EACL:
			bgpq4_print_eacl(stdout, &expander);
			break;
		case T_ROUTE_FILTER_LIST:
			bgpq4_print_route_filter_list(stdout, &expander);
			break;
	}

        expander_freeall(&expander);

	return 0;
}
