/* command interface to Pluto
 * Copyright (C) 1997 Angelos D. Keromytis.
 * Copyright (C) 1998-2001  D. Hugh Redelmeier.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <getopt.h>
#include <assert.h>

#include <freeswan.h>

#include <utils/optionsfrom.h>

#include "constants.h"
#include "defs.h"
#include "whack.h"

static void help(void)
{
	fprintf(stderr
		, "Usage:\n\n"
		"all forms:"
			" [--optionsfrom <filename>]"
			" [--ctlbase <path>]"
			" [--label <string>]"
			"\n\n"
		"help: whack"
			" [--help]"
			" [--version]"
			"\n\n"
		"connection: whack"
			" --name <connection_name>"
			" \\\n   "
			" [--ipv4 | --ipv6]"
			" [--tunnelipv4 | --tunnelipv6]"
			" \\\n   "
			" (--host <ip-address> | --id <identity>)"
			" \\\n   "
			" [--cert <path>]"
			" [--ca <distinguished name>]"
			" [--sendcert <policy>]"
			" \\\n   "
			" [--groups <access control groups>]"
			" \\\n   "
			" [--ikeport <port-number>]"
			" [--nexthop <ip-address>]"
			" [--srcip <ip-address>]"
			" \\\n   "
			" [--client <subnet> | --clientwithin <address range>]"
			" [--clientprotoport <protocol>/<port>]"
			" \\\n   "
			" [--dnskeyondemand]"
			" [--updown <updown>]"
			" \\\n   "
			" --to"
			" (--host <ip-address> | --id <identity>)"
			" \\\n   "
			" [--cert <path>]"
			" [--ca <distinguished name>]"
			" [--sendcert <policy>]"
			" \\\n   "
			" [--ikeport <port-number>]"
			" [--nexthop <ip-address>]"
			" [--srcip <ip-address>]"
			" \\\n   "
			" [--client <subnet> | --clientwithin <address range>]"
			" [--clientprotoport <protocol>/<port>]"
			" \\\n   "
			" [--dnskeyondemand]"
			" [--updown <updown>]"
			" [--psk]"
			" [--rsasig]"
			" \\\n   "
			" [--encrypt]"
			" [--authenticate]"
			" [--compress]"
			" [--tunnel]"
			" [--pfs]"
			" \\\n   "
			" [--ikelifetime <seconds>]"
			" [--ipseclifetime <seconds>]"
			" \\\n   "
			" [--reykeymargin <seconds>]"
			" [--reykeyfuzz <percentage>]"
			" \\\n   "
			" [--keyingtries <count>]"
			" \\\n   "
			" [--esp <esp-algos>]"
			" \\\n   "
			" [--dontrekey]"

			" [--dpdaction (none|clear|hold|restart)]"
			" \\\n   "
			" [--dpddelay <seconds> --dpdtimeout <seconds>]"
			" \\\n   "
			" [--initiateontraffic|--pass|--drop|--reject]"
			" \\\n   "
			" [--failnone|--failpass|--faildrop|--failreject]"
			"\n\n"
		"routing: whack"
			" (--route | --unroute)"
			" --name <connection_name>"
			"\n\n"
		"initiation:"
			"\n "
			" whack"
			" (--initiate | --terminate)"
			" --name <connection_name>"
			" [--asynchronous]"
			"\n\n"
		"opportunistic initiation: whack"
			" [--tunnelipv4 | --tunnelipv6]"
			" \\\n   "
			" --oppohere <ip-address>"
			" --oppothere <ip-address>"
			"\n\n"
		"delete: whack"
			" --delete"
			" (--name <connection_name> | --caname <ca name>)"
			"\n\n"
		"deletestate: whack"
			" --deletestate <state_object_number>"
			" --crash <ip-address>"
			"\n\n"
		"pubkey: whack"
			" --keyid <id>"
			" [--addkey]"
			" [--pubkeyrsa <key>]"
			"\n\n"
		"myid: whack"
			" --myid <id>"
			"\n\n"
		"ca: whack"
			" --caname <name>"
			" --cacert <path>"
			" \\\n   "
			" [--ldaphost <hostname>]"
			" [--ldapbase <base>]"
			" \\\n   "
			" [--crluri <uri>]"
			" [--crluri2 <uri>]"
			" [--ocspuri <uri>]"
			" [--strictcrlpolicy]"
			"\n\n"
#ifdef DEBUG
		"debug: whack [--name <connection_name>]"
			" \\\n   "
			" [--debug-none]"
			" [--debug-all]"
			" \\\n   "
			" [--debug-raw]"
			" [--debug-crypt]"
			" [--debug-parsing]"
			" [--debug-emitting]"
			" \\\n   "
			" [--debug-control]"
			" [--debug-lifecycle]"
			" [--debug-kernel]"
			" [--debug-dns]"
			" \\\n   "
			" [--debug-natt]"
			" [--debug-oppo]"
			" [--debug-controlmore]"
			" [--debug-private]"
			"\n\n"
#endif
		"leases: whack --leases"
			" [--name <connection_name>]"
			" [--lease-addr <ip-address> | --lease-id <identity>]"
			"\n\n"
		"listen: whack"
			" (--listen | --unlisten)"
			"\n\n"
		"list: whack [--utc]"
			" [--listalgs]"
			" [--listpubkeys]"
			" [--listcerts]"
			" [--listcacerts]"
			" \\\n   "
			" [--listacerts]"
			" [--listaacerts]"
			" [--listocspcerts]"
			" [--listgroups]"
			" \\\n   "
			" [--listcainfos]"
			" [--listcrls]"
			" [--listocsp]"
			" [--listcards]"
			" [--listplugins]"
			" [--listall]"
			"\n\n"
		"purge: whack"
			" [--purgeocsp]"
			"\n\n"
		"reread: whack"
			" [--rereadsecrets]"
			" [--rereadcacerts]"
			" [--rereadaacerts]"
			" \\\n   "
			" [--rereadocspcerts]"
			" [--rereadacerts]"
			" [--rereadcrls]"
			" [--rereadall]"
			"\n\n"
		"status: whack"
			" [--name <connection_name>] --status|--statusall"
			"\n\n"
		"scdecrypt: whack"
			" --scencrypt|scdecrypt <value>"
			" [--inbase <base>]"
			" [--outbase <base>]"
			" [--keyid <id>]"
			"\n\n"
		"shutdown: whack"
			" --shutdown"
			"\n\n"
		"strongSwan "VERSION"\n");
}

static const char *label = NULL;        /* --label operand, saved for diagnostics */

static const char *name = NULL; /* --name operand, saved for diagnostics */

/* options read by optionsfrom */
options_t *options;

/**
 * exit whack after cleaning up
 */
static void whack_exit(int status)
{
	options->destroy(options);
	exit(status);
}

/**
 * print a string as a diagnostic, then exit whack unhappily
 */
static void diag(const char *mess)
{
	if (mess != NULL)
	{
		fprintf(stderr, "whack error: ");
		if (label != NULL)
		{
			fprintf(stderr, "%s ", label);
		}
		if (name != NULL)
		{
			fprintf(stderr, "\"%s\" ", name);
		}
		fprintf(stderr, "%s\n", mess);
	}
	whack_exit(RC_WHACK_PROBLEM);
}

/* conditially calls diag; prints second arg, if non-NULL, as quoted string */
static void diagq(err_t ugh, const char *this)
{
	if (ugh != NULL)
	{
		if (this == NULL)
		{
			diag(ugh);
		}
		else
		{
			char buf[120];      /* arbitrary limit */

			snprintf(buf, sizeof(buf), "%s \"%s\"", ugh, this);
			diag(buf);
		}
	}
}

/* complex combined operands return one of these enumerated values
 * Note: these become flags in an lset_t.  Since there are more than
 * 32, we partition them into:
 * - OPT_* options (most random options)
 * - LST_* options (list various internal data)
 * - DBGOPT_* option (DEBUG options)
 * - END_* options (End description options)
 * - CD_* options (Connection Description options)
 * - CA_* options (CA description options)
 */
enum {
#   define OPT_FIRST    OPT_CTLBASE
	OPT_CTLBASE,
	OPT_NAME,

	OPT_CD,

	OPT_KEYID,
	OPT_ADDKEY,
	OPT_PUBKEYRSA,

	OPT_MYID,

	OPT_ROUTE,
	OPT_UNROUTE,

	OPT_INITIATE,
	OPT_TERMINATE,
	OPT_DELETE,
	OPT_DELETESTATE,
	OPT_LISTEN,
	OPT_UNLISTEN,

	OPT_LEASES,
	OPT_LEASEADDR,
	OPT_LEASEID,

	OPT_PURGEOCSP,

	OPT_REREADSECRETS,
	OPT_REREADCACERTS,
	OPT_REREADAACERTS,
	OPT_REREADOCSPCERTS,
	OPT_REREADACERTS,
	OPT_REREADCRLS,
	OPT_REREADALL,

	OPT_STATUS,
	OPT_STATUSALL,
	OPT_SHUTDOWN,

	OPT_OPPO_HERE,
	OPT_OPPO_THERE,

	OPT_ASYNC,
	OPT_DELETECRASH,

#   define OPT_LAST OPT_ASYNC   /* last "normal" option */

/* Smartcard options */

#   define SC_FIRST SC_ENCRYPT  /* first smartcard option */

	SC_ENCRYPT,
	SC_DECRYPT,
	SC_INBASE,
	SC_OUTBASE,

#   define SC_LAST SC_OUTBASE   /* last "smartcard" option */

/* List options */

#   define LST_FIRST LST_UTC    /* first list option */
	LST_UTC,
	LST_ALGS,
	LST_PUBKEYS,
	LST_CERTS,
	LST_CACERTS,
	LST_ACERTS,
	LST_AACERTS,
	LST_OCSPCERTS,
	LST_GROUPS,
	LST_CAINFOS,
	LST_CRLS,
	LST_OCSP,
	LST_CARDS,
	LST_PLUGINS,
	LST_ALL,

#   define LST_LAST LST_ALL     /* last list option */

/* Connection End Description options */

#   define END_FIRST END_HOST   /* first end description */
	END_HOST,
	END_ID,
	END_CERT,
	END_CA,
	END_SENDCERT,
	END_GROUPS,
	END_IKEPORT,
	END_NEXTHOP,
	END_CLIENT,
	END_CLIENTWITHIN,
	END_CLIENTPROTOPORT,
	END_DNSKEYONDEMAND,
	END_SRCIP,
	END_HOSTACCESS,
	END_UPDOWN,

#define END_LAST  END_UPDOWN    /* last end description*/

/* Connection Description options -- segregated */

#   define CD_FIRST CD_TO       /* first connection description */
	CD_TO,

#   define CD_POLICY_FIRST  CD_PSK
	CD_PSK,     /* same order as POLICY_* */
	CD_RSASIG,  /* same order as POLICY_* */
	CD_ENCRYPT, /* same order as POLICY_* */
	CD_AUTHENTICATE,    /* same order as POLICY_* */
	CD_COMPRESS,        /* same order as POLICY_* */
	CD_TUNNEL,  /* same order as POLICY_* */
	CD_PFS,     /* same order as POLICY_* */
	CD_DISABLEARRIVALCHECK,     /* same order as POLICY_* */
	CD_SHUNT0,  /* same order as POLICY_* */
	CD_SHUNT1,  /* same order as POLICY_* */
	CD_FAIL0,   /* same order as POLICY_* */
	CD_FAIL1,   /* same order as POLICY_* */
	CD_DONT_REKEY,      /* same order as POLICY_* */

	CD_TUNNELIPV4,
	CD_TUNNELIPV6,
	CD_CONNIPV4,
	CD_CONNIPV6,

	CD_IKELIFETIME,
	CD_IPSECLIFETIME,
	CD_RKMARGIN,
	CD_RKFUZZ,
	CD_KTRIES,
	CD_DPDACTION,
	CD_DPDDELAY,
	CD_DPDTIMEOUT,
	CD_IKE,
	CD_PFSGROUP,
	CD_ESP,

#   define CD_LAST CD_ESP       /* last connection description */

/* Certificate Authority (CA) description options */

#   define CA_FIRST CA_NAME     /* first ca description */

	CA_NAME,
	CA_CERT,
	CA_LDAPHOST,
	CA_LDAPBASE,
	CA_CRLURI,
	CA_CRLURI2,
	CA_OCSPURI,
	CA_STRICT

#   define CA_LAST CA_STRICT    /* last ca description */

#ifdef DEBUG    /* must be last so others are less than 32 to fit in lset_t */
#   define DBGOPT_FIRST DBGOPT_NONE
	,
	/* NOTE: these definitions must match DBG_* and IMPAIR_* in constants.h */
	DBGOPT_NONE,
	DBGOPT_ALL,

	DBGOPT_RAW,         /* same order as DBG_* */
	DBGOPT_CRYPT,       /* same order as DBG_* */
	DBGOPT_PARSING,     /* same order as DBG_* */
	DBGOPT_EMITTING,    /* same order as DBG_* */
	DBGOPT_CONTROL,     /* same order as DBG_* */
	DBGOPT_LIFECYCLE,   /* same order as DBG_* */
	DBGOPT_KERNEL,      /* same order as DBG_* */
	DBGOPT_DNS,         /* same order as DBG_* */
	DBGOPT_NATT,        /* same order as DBG_* */
	DBGOPT_OPPO,        /* same order as DBG_* */
	DBGOPT_CONTROLMORE, /* same order as DBG_* */

	DBGOPT_PRIVATE,     /* same order as DBG_* */

	DBGOPT_IMPAIR_DELAY_ADNS_KEY_ANSWER,        /* same order as IMPAIR_* */
	DBGOPT_IMPAIR_DELAY_ADNS_TXT_ANSWER,        /* same order as IMPAIR_* */
	DBGOPT_IMPAIR_BUST_MI2,     /* same order as IMPAIR_* */
	DBGOPT_IMPAIR_BUST_MR2      /* same order as IMPAIR_* */

#   define DBGOPT_LAST DBGOPT_IMPAIR_BUST_MR2
#endif

};

/* Carve up space for result from getop_long.
 * Stupidly, the only result is an int.
 * Numeric arg is bit immediately left of basic value.
 *
 */
#define OPTION_OFFSET   256     /* to get out of the way of letter options */
#define NUMERIC_ARG (1 << 9)    /* expect a numeric argument */
#define AUX_SHIFT   10  /* amount to shift for aux information */

static const struct option long_opts[] = {
#   define OO   OPTION_OFFSET
	/* name, has_arg, flag, val */

	{ "help", no_argument, NULL, 'h' },
	{ "version", no_argument, NULL, 'v' },
	{ "optionsfrom", required_argument, NULL, '+' },
	{ "label", required_argument, NULL, 'l' },

	{ "ctlbase", required_argument, NULL, OPT_CTLBASE + OO },
	{ "name", required_argument, NULL, OPT_NAME + OO },

	{ "keyid", required_argument, NULL, OPT_KEYID + OO },
	{ "addkey", no_argument, NULL, OPT_ADDKEY + OO },
	{ "pubkeyrsa", required_argument, NULL, OPT_PUBKEYRSA + OO },

	{ "myid", required_argument, NULL, OPT_MYID + OO },

	{ "route", no_argument, NULL, OPT_ROUTE + OO },
	{ "unroute", no_argument, NULL, OPT_UNROUTE + OO },

	{ "initiate", no_argument, NULL, OPT_INITIATE + OO },
	{ "terminate", no_argument, NULL, OPT_TERMINATE + OO },
	{ "delete", no_argument, NULL, OPT_DELETE + OO },
	{ "deletestate", required_argument, NULL, OPT_DELETESTATE + OO + NUMERIC_ARG },
	{ "crash", required_argument, NULL, OPT_DELETECRASH + OO },
	{ "listen", no_argument, NULL, OPT_LISTEN + OO },
	{ "unlisten", no_argument, NULL, OPT_UNLISTEN + OO },

	{ "leases", no_argument, NULL, OPT_LEASES + OO },
	{ "lease-addr", required_argument, NULL, OPT_LEASEADDR + OO },
	{ "lease-id", required_argument, NULL, OPT_LEASEID + OO },

	{ "purgeocsp", no_argument, NULL, OPT_PURGEOCSP + OO },

	{ "rereadsecrets", no_argument, NULL, OPT_REREADSECRETS + OO },
	{ "rereadcacerts", no_argument, NULL, OPT_REREADCACERTS + OO },
	{ "rereadaacerts", no_argument, NULL, OPT_REREADAACERTS + OO },
	{ "rereadocspcerts", no_argument, NULL, OPT_REREADOCSPCERTS + OO },
	{ "rereadacerts", no_argument, NULL, OPT_REREADACERTS + OO },
	{ "rereadcrls", no_argument, NULL, OPT_REREADCRLS + OO },
	{ "rereadall", no_argument, NULL, OPT_REREADALL + OO },
	{ "status", no_argument, NULL, OPT_STATUS + OO },
	{ "statusall", no_argument, NULL, OPT_STATUSALL + OO },
	{ "shutdown", no_argument, NULL, OPT_SHUTDOWN + OO },

	{ "oppohere", required_argument, NULL, OPT_OPPO_HERE + OO },
	{ "oppothere", required_argument, NULL, OPT_OPPO_THERE + OO },

	{ "asynchronous", no_argument, NULL, OPT_ASYNC + OO },

	/* smartcard options */

	{ "scencrypt", required_argument, NULL, SC_ENCRYPT + OO },
	{ "scdecrypt", required_argument, NULL, SC_DECRYPT + OO },
	{ "inbase", required_argument, NULL, SC_INBASE + OO },
	{ "outbase", required_argument, NULL, SC_OUTBASE + OO },

	/* list options */

	{ "utc", no_argument, NULL, LST_UTC + OO },
	{ "listalgs", no_argument, NULL, LST_ALGS + OO },
	{ "listpubkeys", no_argument, NULL, LST_PUBKEYS + OO },
	{ "listcerts", no_argument, NULL, LST_CERTS + OO },
	{ "listcacerts", no_argument, NULL, LST_CACERTS + OO },
	{ "listacerts", no_argument, NULL, LST_ACERTS + OO },
	{ "listaacerts", no_argument, NULL, LST_AACERTS + OO },
	{ "listocspcerts", no_argument, NULL, LST_OCSPCERTS + OO },
	{ "listgroups", no_argument, NULL, LST_GROUPS + OO },
	{ "listcainfos", no_argument, NULL, LST_CAINFOS + OO },
	{ "listcrls", no_argument, NULL, LST_CRLS + OO },
	{ "listocsp", no_argument, NULL, LST_OCSP + OO },
	{ "listcards", no_argument, NULL, LST_CARDS + OO },
	{ "listplugins", no_argument, NULL, LST_PLUGINS + OO },
	{ "listall", no_argument, NULL, LST_ALL + OO },

	/* options for an end description */

	{ "host", required_argument, NULL, END_HOST + OO },
	{ "id", required_argument, NULL, END_ID + OO },
	{ "cert", required_argument, NULL, END_CERT + OO },
	{ "ca", required_argument, NULL, END_CA + OO },
	{ "sendcert", required_argument, NULL, END_SENDCERT + OO },
	{ "groups", required_argument, NULL, END_GROUPS + OO },
	{ "ikeport", required_argument, NULL, END_IKEPORT + OO + NUMERIC_ARG },
	{ "nexthop", required_argument, NULL, END_NEXTHOP + OO },
	{ "client", required_argument, NULL, END_CLIENT + OO },
	{ "clientwithin", required_argument, NULL, END_CLIENTWITHIN + OO },
	{ "clientprotoport", required_argument, NULL, END_CLIENTPROTOPORT + OO },
	{ "dnskeyondemand", no_argument, NULL, END_DNSKEYONDEMAND + OO },
	{ "srcip",  required_argument, NULL, END_SRCIP + OO },
	{ "hostaccess", no_argument, NULL, END_HOSTACCESS + OO },
	{ "updown", required_argument, NULL, END_UPDOWN + OO },

	/* options for a connection description */

	{ "to", no_argument, NULL, CD_TO + OO },

	{ "psk", no_argument, NULL, CD_PSK + OO },
	{ "rsasig", no_argument, NULL, CD_RSASIG + OO },

	{ "encrypt", no_argument, NULL, CD_ENCRYPT + OO },
	{ "authenticate", no_argument, NULL, CD_AUTHENTICATE + OO },
	{ "compress", no_argument, NULL, CD_COMPRESS + OO },
	{ "tunnel", no_argument, NULL, CD_TUNNEL + OO },
	{ "tunnelipv4", no_argument, NULL, CD_TUNNELIPV4 + OO },
	{ "tunnelipv6", no_argument, NULL, CD_TUNNELIPV6 + OO },
	{ "pfs", no_argument, NULL, CD_PFS + OO },
	{ "disablearrivalcheck", no_argument, NULL, CD_DISABLEARRIVALCHECK + OO },
	{ "initiateontraffic", no_argument, NULL
		, CD_SHUNT0 + (POLICY_SHUNT_TRAP >> POLICY_SHUNT_SHIFT << AUX_SHIFT) + OO },
	{ "pass", no_argument, NULL
		, CD_SHUNT0 + (POLICY_SHUNT_PASS >> POLICY_SHUNT_SHIFT << AUX_SHIFT) + OO },
	{ "drop", no_argument, NULL
		, CD_SHUNT0 + (POLICY_SHUNT_DROP >> POLICY_SHUNT_SHIFT << AUX_SHIFT) + OO },
	{ "reject", no_argument, NULL
		, CD_SHUNT0 + (POLICY_SHUNT_REJECT >> POLICY_SHUNT_SHIFT << AUX_SHIFT) + OO },
	{ "failnone", no_argument, NULL
		, CD_FAIL0 + (POLICY_FAIL_NONE >> POLICY_FAIL_SHIFT << AUX_SHIFT) + OO },
	{ "failpass", no_argument, NULL
		, CD_FAIL0 + (POLICY_FAIL_PASS >> POLICY_FAIL_SHIFT << AUX_SHIFT) + OO },
	{ "faildrop", no_argument, NULL
		, CD_FAIL0 + (POLICY_FAIL_DROP >> POLICY_FAIL_SHIFT << AUX_SHIFT) + OO },
	{ "failreject", no_argument, NULL
		, CD_FAIL0 + (POLICY_FAIL_REJECT >> POLICY_FAIL_SHIFT << AUX_SHIFT) + OO },
	{ "dontrekey", no_argument, NULL, CD_DONT_REKEY + OO },
	{ "ipv4", no_argument, NULL, CD_CONNIPV4 + OO },
	{ "ipv6", no_argument, NULL, CD_CONNIPV6 + OO },

	{ "ikelifetime", required_argument, NULL, CD_IKELIFETIME + OO + NUMERIC_ARG },
	{ "ipseclifetime", required_argument, NULL, CD_IPSECLIFETIME + OO + NUMERIC_ARG },
	{ "rekeymargin", required_argument, NULL, CD_RKMARGIN + OO + NUMERIC_ARG },
	{ "rekeywindow", required_argument, NULL, CD_RKMARGIN + OO + NUMERIC_ARG }, /* OBSOLETE */
	{ "rekeyfuzz", required_argument, NULL, CD_RKFUZZ + OO + NUMERIC_ARG },
	{ "keyingtries", required_argument, NULL, CD_KTRIES + OO + NUMERIC_ARG },
	{ "dpdaction", required_argument, NULL, CD_DPDACTION + OO },
	{ "dpddelay", required_argument, NULL, CD_DPDDELAY + OO + NUMERIC_ARG },
	{ "dpdtimeout", required_argument, NULL, CD_DPDTIMEOUT + OO + NUMERIC_ARG },
	{ "ike", required_argument, NULL, CD_IKE + OO },
	{ "pfsgroup", required_argument, NULL, CD_PFSGROUP + OO },
	{ "esp", required_argument, NULL, CD_ESP + OO },

	/* options for a ca description */

	{ "caname", required_argument, NULL, CA_NAME + OO },
	{ "cacert", required_argument, NULL, CA_CERT + OO },
	{ "ldaphost", required_argument, NULL, CA_LDAPHOST + OO },
	{ "ldapbase", required_argument, NULL, CA_LDAPBASE + OO },
	{ "crluri", required_argument, NULL, CA_CRLURI + OO },
	{ "crluri2", required_argument, NULL, CA_CRLURI2 + OO },
	{ "ocspuri", required_argument, NULL, CA_OCSPURI + OO },
	{ "strictcrlpolicy", no_argument, NULL, CA_STRICT + OO },

#ifdef DEBUG
	{ "debug-none", no_argument, NULL, DBGOPT_NONE + OO },
	{ "debug-all]", no_argument, NULL, DBGOPT_ALL + OO },
	{ "debug-raw", no_argument, NULL, DBGOPT_RAW + OO },
	{ "debug-crypt", no_argument, NULL, DBGOPT_CRYPT + OO },
	{ "debug-parsing", no_argument, NULL, DBGOPT_PARSING + OO },
	{ "debug-emitting", no_argument, NULL, DBGOPT_EMITTING + OO },
	{ "debug-control", no_argument, NULL, DBGOPT_CONTROL + OO },
	{ "debug-lifecycle", no_argument, NULL, DBGOPT_LIFECYCLE + OO },
	{ "debug-klips", no_argument, NULL, DBGOPT_KERNEL + OO },
	{ "debug-kernel", no_argument, NULL, DBGOPT_KERNEL + OO },
	{ "debug-dns", no_argument, NULL, DBGOPT_DNS + OO },
	{ "debug-natt", no_argument, NULL, DBGOPT_NATT + OO },
	{ "debug-oppo", no_argument, NULL, DBGOPT_OPPO + OO },
	{ "debug-controlmore", no_argument, NULL, DBGOPT_CONTROLMORE + OO },
	{ "debug-private", no_argument, NULL, DBGOPT_PRIVATE + OO },

	{ "impair-delay-adns-key-answer", no_argument, NULL, DBGOPT_IMPAIR_DELAY_ADNS_KEY_ANSWER + OO },
	{ "impair-delay-adns-txt-answer", no_argument, NULL, DBGOPT_IMPAIR_DELAY_ADNS_TXT_ANSWER + OO },
	{ "impair-bust-mi2", no_argument, NULL, DBGOPT_IMPAIR_BUST_MI2 + OO },
	{ "impair-bust-mr2", no_argument, NULL, DBGOPT_IMPAIR_BUST_MR2 + OO },
#endif
#   undef OO
	{ 0,0,0,0 }
};

struct sockaddr_un ctl_addr = { AF_UNIX, DEFAULT_CTLBASE CTL_SUFFIX };

/* helper variables and function to encode strings from whack message */

static char	*next_str,*str_roof;

static bool pack_str(char **p)
{
	const char *s = *p == NULL? "" : *p;        /* note: NULL becomes ""! */
	size_t len = strlen(s) + 1;

	if (str_roof - next_str < (ptrdiff_t)len)
	{
		return FALSE;   /* fishy: no end found */
	}
	else
	{
		strcpy(next_str, s);
		next_str += len;
		*p = NULL;      /* don't send pointers on the wire! */
		return TRUE;
	}
}

static void check_life_time(time_t life, time_t limit, const char *which,
							const whack_message_t *msg)
{
	time_t mint = msg->sa_rekey_margin * (100 + msg->sa_rekey_fuzz) / 100;

	if (life > limit)
	{
		char buf[200];  /* arbitrary limit */

		snprintf(buf, sizeof(buf)
			, "%s [%lu seconds] must be less than %lu seconds"
			, which, (unsigned long)life, (unsigned long)limit);
		diag(buf);
	}
	if ((msg->policy & POLICY_DONT_REKEY) == LEMPTY && life <= mint)
	{
		char buf[200];  /* arbitrary limit */

		snprintf(buf, sizeof(buf)
			, "%s [%lu] must be greater than"
			" rekeymargin*(100+rekeyfuzz)/100 [%lu*(100+%lu)/100 = %lu]"
			, which
			, (unsigned long)life
			, (unsigned long)msg->sa_rekey_margin
			, (unsigned long)msg->sa_rekey_fuzz
			, (unsigned long)mint);
		diag(buf);
	}
}

static void clear_end(whack_end_t *e)
{
	zero(e);
	e->id = NULL;
	e->cert = NULL;
	e->ca = NULL;
	e->updown = NULL;
	e->host_port = IKE_UDP_PORT;
}

static void update_ports(whack_message_t *m)
{
	int port;

	if (m->left.port != 0) {
		port = htons(m->left.port);
		setportof(port, &m->left.host_addr);
		setportof(port, &m->left.client.addr);
	}
	if (m->right.port != 0) {
		port = htons(m->right.port);
		setportof(port, &m->right.host_addr);
		setportof(port, &m->right.client.addr);
	}
}

static void check_end(whack_end_t *this, whack_end_t *that,
					  bool default_nexthop, sa_family_t caf, sa_family_t taf)
{
	if (caf != addrtypeof(&this->host_addr))
		diag("address family of host inconsistent");

	if (default_nexthop)
	{
		if (isanyaddr(&that->host_addr))
			diag("our nexthop must be specified when other host is a %any or %opportunistic");
		this->host_nexthop = that->host_addr;
	}

	if (caf != addrtypeof(&this->host_nexthop))
		diag("address family of nexthop inconsistent");

	if (this->has_client)
	{
		if (taf != subnettypeof(&this->client))
			diag("address family of client subnet inconsistent");
	}
	else
	{
		/* fill in anyaddr-anyaddr as (missing) client subnet */
		ip_address cn;

		diagq(anyaddr(caf, &cn), NULL);
		diagq(rangetosubnet(&cn, &cn, &this->client), NULL);
	}

	/* fill in anyaddr if source IP is not defined */
	if (!this->has_srcip)
		diagq(anyaddr(caf, &this->host_srcip), optarg);

   /* check protocol */
	if (this->protocol != that->protocol)
		diag("the protocol for leftprotoport and rightprotoport must be the same");
}

static void get_secret(int sock)
{
	const char *buf = NULL, *secret;
	int len;

	fflush(stdout);
	usleep(20000); /* give fflush time for flushing */
#ifdef HAVE_GETPASS
	buf = getpass("Enter: ");
#endif
	secret = (buf == NULL)? "" : buf;

	/* send the secret to pluto */
	len = strlen(secret) + 1;
	if (write(sock, secret, len) != len)
	{
		int e = errno;

		fprintf(stderr, "whack: write() failed (%d %s)\n", e, strerror(e));
		exit(RC_WHACK_PROBLEM);
	}
}

/* This is a hack for initiating ISAKMP exchanges. */

int main(int argc, char **argv)
{
	whack_message_t msg;
	char esp_buf[256];  /* uses snprintf */
	lset_t
		opts_seen = LEMPTY,
		sc_seen = LEMPTY,
		lst_seen = LEMPTY,
		cd_seen = LEMPTY,
		ca_seen = LEMPTY,
		end_seen = LEMPTY,
		end_seen_before_to = LEMPTY;
	const char
		*af_used_by = NULL,
		*tunnel_af_used_by = NULL;

	/* check division of numbering space */
#ifdef DEBUG
	assert(OPTION_OFFSET + DBGOPT_LAST < NUMERIC_ARG);
#else
	assert(OPTION_OFFSET + CA_LAST < NUMERIC_ARG);
#endif
	assert(OPT_LAST - OPT_FIRST < (sizeof opts_seen * BITS_PER_BYTE));
	assert(SC_LAST  - SC_FIRST  < (sizeof sc_seen   * BITS_PER_BYTE));
	assert(LST_LAST - LST_FIRST < (sizeof lst_seen  * BITS_PER_BYTE));
	assert(END_LAST - END_FIRST < (sizeof end_seen  * BITS_PER_BYTE));
	assert(CD_LAST  - CD_FIRST  < (sizeof cd_seen   * BITS_PER_BYTE));
	assert(CA_LAST  - CA_FIRST  < (sizeof ca_seen   * BITS_PER_BYTE));
#ifdef DEBUG    /* must be last so others are less than (sizeof cd_seen * BITS_PER_BYTE) to fit in lset_t */
	assert(DBGOPT_LAST - DBGOPT_FIRST < (sizeof cd_seen * BITS_PER_BYTE));
#endif
	/* check that POLICY bit assignment matches with CD_ */
	assert(LELEM(CD_DONT_REKEY - CD_POLICY_FIRST) == POLICY_DONT_REKEY);

	zero(&msg);

	clear_end(&msg.right);      /* left set from this after --to */

	msg.name = NULL;
	msg.keyid = NULL;
	msg.keyval.ptr = NULL;
	msg.esp = NULL;
	msg.ike = NULL;
	msg.pfsgroup = NULL;

   /* if a connection is added via whack then we assume IKEv1 */
	msg.ikev1 = TRUE;

	msg.sa_ike_life_seconds = OAKLEY_ISAKMP_SA_LIFETIME_DEFAULT;
	msg.sa_ipsec_life_seconds = PLUTO_SA_LIFE_DURATION_DEFAULT;
	msg.sa_rekey_margin = SA_REPLACEMENT_MARGIN_DEFAULT;
	msg.sa_rekey_fuzz = SA_REPLACEMENT_FUZZ_DEFAULT;
	msg.sa_keying_tries = SA_REPLACEMENT_RETRIES_DEFAULT;

	msg.addr_family = AF_INET;
	msg.tunnel_addr_family = AF_INET;

	msg.cacert = NULL;
	msg.ldaphost = NULL;
	msg.ldapbase = NULL;
	msg.crluri = NULL;
	msg.crluri2 = NULL;
	msg.ocspuri = NULL;

	options = options_create();

	for (;;)
	{
		int long_index;
		unsigned long opt_whole = 0;  /* numeric argument for some flags */

		/* Note: we don't like the way short options get parsed
		 * by getopt_long, so we simply pass an empty string as
		 * the list.  It could be "hp:d:c:o:eatfs" "NARXPECK".
		 */
		int c = getopt_long(argc, argv, "", long_opts, &long_index) - OPTION_OFFSET;
		int aux = 0;

		/* decode a numeric argument, if expected */
		if (0 <= c)
		{
			if (c & NUMERIC_ARG)
			{
				char *endptr;

				c -= NUMERIC_ARG;
				opt_whole = strtoul(optarg, &endptr, 0);

				if (*endptr != '\0' || endptr == optarg)
					diagq("badly formed numeric argument", optarg);
			}
			if (c >= (1 << AUX_SHIFT))
			{
				aux = c >> AUX_SHIFT;
				c -= aux << AUX_SHIFT;
			}
		}

		/* per-class option processing */
		if (0 <= c && c <= OPT_LAST)
		{
			/* OPT_* options get added to opts_seen.
			 * Reject repeated options (unless later code intervenes).
			 */
			lset_t f = LELEM(c);

			if (opts_seen & f)
				diagq("duplicated flag", long_opts[long_index].name);
			opts_seen |= f;
		}
		else if (SC_FIRST <= c && c <= SC_LAST)
		{
			/* SC_* options get added to sc_seen.
			 * Reject repeated options (unless later code intervenes).
			 */
			lset_t f = LELEM(c - SC_FIRST);

			if (sc_seen & f)
				diagq("duplicated flag", long_opts[long_index].name);
			sc_seen |= f;
		}
		else if (LST_FIRST <= c && c <= LST_LAST)
		{
			/* LST_* options get added to lst_seen.
			 * Reject repeated options (unless later code intervenes).
			 */
			lset_t f = LELEM(c - LST_FIRST);

			if (lst_seen & f)
				diagq("duplicated flag", long_opts[long_index].name);
			lst_seen |= f;
		}
#ifdef DEBUG
		else if (DBGOPT_FIRST <= c && c <= DBGOPT_LAST)
		{
			msg.whack_options = TRUE;
		}
#endif
		else if (END_FIRST <= c && c <= END_LAST)
		{
			/* END_* options are added to end_seen.
			 * Reject repeated options (unless later code intervenes).
			 */
			lset_t f = LELEM(c - END_FIRST);

			if (end_seen & f)
				diagq("duplicated flag", long_opts[long_index].name);
			end_seen |= f;
			opts_seen |= LELEM(OPT_CD);
		}
		else if (CD_FIRST <= c && c <= CD_LAST)
		{
			/* CD_* options are added to cd_seen.
			 * Reject repeated options (unless later code intervenes).
			 */
			lset_t f = LELEM(c - CD_FIRST);

			if (cd_seen & f)
				diagq("duplicated flag", long_opts[long_index].name);
			cd_seen |= f;
			opts_seen |= LELEM(OPT_CD);
		}
		else if (CA_FIRST <= c && c <= CA_LAST)
		{
			/* CA_* options are added to ca_seen.
			 * Reject repeated options (unless later code intervenes).
			 */
			lset_t f = LELEM(c - CA_FIRST);

			if (ca_seen & f)
				diagq("duplicated flag", long_opts[long_index].name);
			ca_seen |= f;
		}

		/* Note: "break"ing from switch terminates loop.
		 * most cases should end with "continue".
		 */
		switch (c)
		{
		case EOF - OPTION_OFFSET:       /* end of flags */
			break;

		case 0 - OPTION_OFFSET: /* long option already handled */
			continue;

		case ':' - OPTION_OFFSET:       /* diagnostic already printed by getopt_long */
		case '?' - OPTION_OFFSET:       /* diagnostic already printed by getopt_long */
			diag(NULL); /* print no additional diagnostic, but exit sadly */
			break;      /* not actually reached */

		case 'h' - OPTION_OFFSET:       /* --help */
			help();
			whack_exit(0);              /* GNU coding standards say to stop here */

		case 'v' - OPTION_OFFSET:       /* --version */
			{
				const char **sp = ipsec_copyright_notice();

				printf("strongSwan "VERSION"\n");
				for (; *sp != NULL; sp++)
					puts(*sp);
			}
			whack_exit(0);   /* GNU coding standards say to stop here */

		case 'l' - OPTION_OFFSET:       /* --label <string> */
			label = optarg;             /* remember for diagnostics */
			continue;

		case '+' - OPTION_OFFSET:       /* --optionsfrom <filename> */
			if (!options->from(options, optarg, &argc, &argv, optind))
			{
				fprintf(stderr, "optionsfrom failed");
				whack_exit(RC_WHACK_PROBLEM);
			}
			continue;

		/* the rest of the options combine in complex ways */

		case OPT_CTLBASE:       /* --port <ctlbase> */
			if (snprintf(ctl_addr.sun_path, sizeof(ctl_addr.sun_path)
			, "%s%s", optarg, CTL_SUFFIX) == -1)
				diag("<ctlbase>" CTL_SUFFIX " must be fit in a sun_addr");
			continue;

		case OPT_NAME:  /* --name <connection-name> */
			name = optarg;
			msg.name = optarg;
			continue;

		case OPT_KEYID: /* --keyid <identity> */
			msg.whack_key = !msg.whack_sc_op;
			msg.keyid = optarg; /* decoded by Pluto */
			continue;

		case OPT_MYID:  /* --myid <identity> */
			msg.whack_myid = TRUE;
			msg.myid = optarg;  /* decoded by Pluto */
			continue;

		case OPT_ADDKEY:        /* --addkey */
			msg.whack_addkey = TRUE;
			continue;

		case OPT_PUBKEYRSA:     /* --pubkeyrsa <key> */
			{
				static char keyspace[RSA_MAX_ENCODING_BYTES];   /* room for 8K bit key */
				char diag_space[TTODATAV_BUF];
				const char *ugh = ttodatav(optarg, 0, 0
					, keyspace, sizeof(keyspace)
					, &msg.keyval.len, diag_space, sizeof(diag_space)
					, TTODATAV_SPACECOUNTS);

				if (ugh != NULL)
				{
					char ugh_space[80]; /* perhaps enough space */

					snprintf(ugh_space, sizeof(ugh_space)
						, "RSA public-key data malformed (%s)", ugh);
					diagq(ugh_space, optarg);
				}
				msg.pubkey_alg = PUBKEY_ALG_RSA;
				msg.keyval.ptr = keyspace;
			}
			continue;

		case OPT_ROUTE: /* --route */
			msg.whack_route = TRUE;
			continue;

		case OPT_UNROUTE:       /* --unroute */
			msg.whack_unroute = TRUE;
			continue;

		case OPT_INITIATE:      /* --initiate */
			msg.whack_initiate = TRUE;
			continue;

		case OPT_TERMINATE:     /* --terminate */
			msg.whack_terminate = TRUE;
			continue;

		case OPT_DELETE:        /* --delete */
			msg.whack_delete = TRUE;
			continue;

		case OPT_DELETESTATE:   /* --deletestate <state_object_number> */
			msg.whack_deletestate = TRUE;
			msg.whack_deletestateno = opt_whole;
			continue;

		case OPT_DELETECRASH:   /* --crash <ip-address> */
			msg.whack_crash = TRUE;
			tunnel_af_used_by = long_opts[long_index].name;
			diagq(ttoaddr(optarg, 0, msg.tunnel_addr_family, &msg.whack_crash_peer), optarg);
			if (isanyaddr(&msg.whack_crash_peer))
				diagq("0.0.0.0 or 0::0 isn't a valid client address", optarg);
			continue;

		case OPT_LEASES:        /* --leases */
			msg.whack_leases = TRUE;
			continue;

		case OPT_LEASEADDR:     /* --lease-addr <ip-address> */
			msg.whack_lease_ip = optarg;      /* decoded by Pluto */
			continue;

		case OPT_LEASEID:       /* --lease-id <identity> */
			msg.whack_lease_id = optarg;      /* decoded by Pluto */
			continue;

		case OPT_LISTEN:        /* --listen */
			msg.whack_listen = TRUE;
			continue;

		case OPT_UNLISTEN:      /* --unlisten */
			msg.whack_unlisten = TRUE;
			continue;

		case OPT_PURGEOCSP:     /* --purgeocsp */
			msg.whack_purgeocsp = TRUE;
			continue;

		case OPT_REREADSECRETS:   /* --rereadsecrets */
		case OPT_REREADCACERTS:   /* --rereadcacerts */
		case OPT_REREADAACERTS:   /* --rereadaacerts */
		case OPT_REREADOCSPCERTS: /* --rereadocspcerts */
		case OPT_REREADACERTS:    /* --rereadacerts */
		case OPT_REREADCRLS:      /* --rereadcrls */
			msg.whack_reread |= LELEM(c-OPT_REREADSECRETS);
			continue;

		case OPT_REREADALL:     /* --rereadall */
			msg.whack_reread = REREAD_ALL;
			continue;

		case OPT_STATUSALL:     /* --statusall */
			msg.whack_statusall = TRUE;
			/* fall through */

		case OPT_STATUS:        /* --status */
			msg.whack_status = TRUE;
			continue;

		case OPT_SHUTDOWN:      /* --shutdown */
			msg.whack_shutdown = TRUE;
			continue;

		case OPT_OPPO_HERE:     /* --oppohere <ip-address> */
			tunnel_af_used_by = long_opts[long_index].name;
			diagq(ttoaddr(optarg, 0, msg.tunnel_addr_family, &msg.oppo_my_client), optarg);
			if (isanyaddr(&msg.oppo_my_client))
				diagq("0.0.0.0 or 0::0 isn't a valid client address", optarg);
			continue;

		case OPT_OPPO_THERE:    /* --oppohere <ip-address> */
			tunnel_af_used_by = long_opts[long_index].name;
			diagq(ttoaddr(optarg, 0, msg.tunnel_addr_family, &msg.oppo_peer_client), optarg);
			if (isanyaddr(&msg.oppo_peer_client))
				diagq("0.0.0.0 or 0::0 isn't a valid client address", optarg);
			continue;

		case OPT_ASYNC:
			msg.whack_async = TRUE;
			continue;

		/* Smartcard options */

		case SC_ENCRYPT:        /* --scencrypt <plaintext data> */
		case SC_DECRYPT:        /* --scdecrypt <encrypted data> */
			msg.whack_sc_op = 1 + c - SC_ENCRYPT;
			msg.whack_key = FALSE;
			msg.sc_data = optarg;
			continue;

		case SC_INBASE:         /* --inform  <format> */
		case SC_OUTBASE:        /* --outform <format> */
		   {
				int base = 0;

				if (streq(optarg, "16") || strcaseeq(optarg, "hex"))
					base = 16;
				else if (streq(optarg, "64") || strcaseeq(optarg, "base64"))
					base = 64;
				else if (streq(optarg, "256") || strcaseeq(optarg, "text")
					  || strcaseeq(optarg, "ascii"))
					base = 256;
				else
					diagq("not a valid base", optarg);

				if (c == SC_INBASE)
					msg.inbase = base;
				else
					msg.outbase = base;
			}
			continue;

		/* List options */

		case LST_UTC:           /* --utc */
			msg.whack_utc = TRUE;
			continue;

		case LST_ALGS:          /* --listalgs */
		case LST_PUBKEYS:       /* --listpubkeys */
		case LST_CERTS:         /* --listcerts */
		case LST_CACERTS:       /* --listcacerts */
		case LST_ACERTS:        /* --listacerts */
		case LST_AACERTS:       /* --listaacerts */
		case LST_OCSPCERTS:     /* --listocspcerts */
		case LST_GROUPS:        /* --listgroups */
		case LST_CAINFOS:       /* --listcainfos */
		case LST_CRLS:          /* --listcrls */
		case LST_OCSP:          /* --listocsp */
		case LST_CARDS:         /* --listcards */
		case LST_PLUGINS:       /* --listplugins */
			msg.whack_list |= LELEM(c - LST_ALGS);
			continue;

		case LST_ALL:   /* --listall */
			msg.whack_list = LIST_ALL;
			continue;

		/* Connection Description options */

		case END_HOST:  /* --host <ip-address> */
		{
			lset_t new_policy = LEMPTY;

			af_used_by = long_opts[long_index].name;
			diagq(anyaddr(msg.addr_family, &msg.right.host_addr), optarg);
			if (streq(optarg, "%any"))
			{
			}
			else if (streq(optarg, "%opportunistic"))
			{
				/* always use tunnel mode; mark as opportunistic */
				new_policy |= POLICY_TUNNEL | POLICY_OPPO;
			}
			else if (streq(optarg, "%group"))
			{
				/* always use tunnel mode; mark as group */
				new_policy |= POLICY_TUNNEL | POLICY_GROUP;
			}
			else if (streq(optarg, "%opportunisticgroup"))
			{
				/* always use tunnel mode; mark as opportunistic */
				new_policy |= POLICY_TUNNEL | POLICY_OPPO | POLICY_GROUP;
			}
			else
			{
				diagq(ttoaddr(optarg, 0, msg.addr_family
					, &msg.right.host_addr), optarg);
			}

			msg.policy |= new_policy;

			if (new_policy & (POLICY_OPPO | POLICY_GROUP))
			{
				if (!LHAS(end_seen, END_CLIENT - END_FIRST))
				{
					/* set host to 0.0.0 and --client to 0.0.0.0/0
					 * or IPV6 equivalent
					 */
					ip_address any;

					tunnel_af_used_by = optarg;
					diagq(anyaddr(msg.tunnel_addr_family, &any), optarg);
					diagq(initsubnet(&any, 0, '0', &msg.right.client), optarg);
				}
				msg.right.has_client = TRUE;
			}
			if (new_policy & POLICY_GROUP)
			{
				/* client subnet must not be specified by user:
				 * it will come from the group's file.
				 */
				if (LHAS(end_seen, END_CLIENT - END_FIRST))
					diag("--host %group clashes with --client");

				end_seen |= LELEM(END_CLIENT - END_FIRST);
			}
			if (new_policy & POLICY_OPPO)
				msg.right.key_from_DNS_on_demand = TRUE;
			continue;
		}
		case END_ID:    /* --id <identity> */
			msg.right.id = optarg;      /* decoded by Pluto */
			continue;

		case END_CERT:  /* --cert <path> */
			msg.right.cert = optarg;    /* decoded by Pluto */
			continue;

		case END_CA:    /* --ca <distinguished name> */
			msg.right.ca = optarg;      /* decoded by Pluto */
			continue;

		case END_SENDCERT:
			if (streq(optarg, "yes") || streq(optarg, "always"))
			{
				msg.right.sendcert = CERT_ALWAYS_SEND;
			}
			else if (streq(optarg, "no") || streq(optarg, "never"))
			{
				msg.right.sendcert = CERT_NEVER_SEND;
			}
			else if (streq(optarg, "ifasked"))
			{
				msg.right.sendcert = CERT_SEND_IF_ASKED;
			}
			else
			{
				diagq("whack sendcert value is not legal", optarg);
			}
			continue;

		case END_GROUPS:/* --groups <access control groups> */
			msg.right.groups = optarg;  /* decoded by Pluto */
			continue;

		case END_IKEPORT:       /* --ikeport <port-number> */
			if (opt_whole<=0 || opt_whole >= 0x10000)
				diagq("<port-number> must be a number between 1 and 65535", optarg);
			msg.right.host_port = opt_whole;
			continue;

		case END_NEXTHOP:       /* --nexthop <ip-address> */
			af_used_by = long_opts[long_index].name;
			if (streq(optarg, "%direct"))
				diagq(anyaddr(msg.addr_family
					, &msg.right.host_nexthop), optarg);
			else
				diagq(ttoaddr(optarg, 0, msg.addr_family
					, &msg.right.host_nexthop), optarg);
			continue;

		case END_SRCIP:        /* --srcip <ip-address> */
			af_used_by = long_opts[long_index].name;
			if (streq(optarg, "%modeconfig") || streq(optarg, "%modecfg"))
			{
				msg.right.modecfg = TRUE;
			}
			else
			{
				diagq(ttoaddr(optarg, 0, msg.addr_family
					, &msg.right.host_srcip), optarg);
				msg.right.has_srcip = TRUE;
			}
			msg.policy |= POLICY_TUNNEL;        /* srcip => tunnel */
			continue;

		case END_CLIENT:        /* --client <subnet> */
			if (end_seen & LELEM(END_CLIENTWITHIN - END_FIRST))
				diag("--client conflicts with --clientwithin");
			tunnel_af_used_by = long_opts[long_index].name;
			if ((strlen(optarg) >= 6 && strncmp(optarg,"vhost:",6) == 0)
			||  (strlen(optarg) >= 5 && strncmp(optarg,"vnet:",5) == 0))
			{
				msg.right.virt = optarg;
			}
			else
			{
				diagq(ttosubnet(optarg, 0, msg.tunnel_addr_family, &msg.right.client), optarg);
				msg.right.has_client = TRUE;
			}
			msg.policy |= POLICY_TUNNEL;        /* client => tunnel */
			continue;

		case END_CLIENTWITHIN:  /* --clienwithin <address range> */
			if (end_seen & LELEM(END_CLIENT - END_FIRST))
				diag("--clientwithin conflicts with --client");
			tunnel_af_used_by = long_opts[long_index].name;
			diagq(ttosubnet(optarg, 0, msg.tunnel_addr_family, &msg.right.client), optarg);
			msg.right.has_client = TRUE;
			msg.policy |= POLICY_TUNNEL;        /* client => tunnel */
			msg.right.has_client_wildcard = TRUE;
			continue;

		case END_CLIENTPROTOPORT: /* --clientprotoport <protocol>/<port> */
			diagq(ttoprotoport(optarg, 0, &msg.right.protocol, &msg.right.port
				, &msg.right.has_port_wildcard), optarg);
			continue;

		case END_DNSKEYONDEMAND:        /* --dnskeyondemand */
			msg.right.key_from_DNS_on_demand = TRUE;
			continue;

		case END_HOSTACCESS:    /* --hostaccess */
			msg.right.hostaccess = TRUE;
			continue;

		case END_UPDOWN:        /* --updown <updown> */
			msg.right.updown = optarg;
			continue;

		case CD_TO:             /* --to */
			/* process right end, move it to left, reset it */
			if (!LHAS(end_seen, END_HOST - END_FIRST))
				diag("connection missing --host before --to");
			msg.left = msg.right;
			clear_end(&msg.right);
			end_seen_before_to = end_seen;
			end_seen = LEMPTY;
			continue;

		case CD_PSK:            /* --psk */
		case CD_RSASIG:         /* --rsasig */
		case CD_ENCRYPT:        /* --encrypt */
		case CD_AUTHENTICATE:   /* --authenticate */
		case CD_COMPRESS:       /* --compress */
		case CD_TUNNEL:         /* --tunnel */
		case CD_PFS:            /* --pfs */
		case CD_DISABLEARRIVALCHECK:    /* --disablearrivalcheck */
		case CD_DONT_REKEY:     /* --donotrekey */
			msg.policy |= LELEM(c - CD_POLICY_FIRST);
			continue;

		/* --initiateontraffic
		 * --pass
		 * --drop
		 * --reject
		 */
		case CD_SHUNT0:
			msg.policy = (msg.policy & ~POLICY_SHUNT_MASK)
				| ((lset_t)aux << POLICY_SHUNT_SHIFT);
			continue;

		/* --failnone
		 * --failpass
		 * --faildrop
		 * --failreject
		 */
		case CD_FAIL0:
			msg.policy = (msg.policy & ~POLICY_FAIL_MASK)
				| ((lset_t)aux << POLICY_FAIL_SHIFT);
			continue;

		case CD_IKELIFETIME:    /* --ikelifetime <seconds> */
			msg.sa_ike_life_seconds = opt_whole;
			continue;

		case CD_IPSECLIFETIME:  /* --ipseclifetime <seconds> */
			msg.sa_ipsec_life_seconds = opt_whole;
			continue;

		case CD_RKMARGIN:       /* --rekeymargin <seconds> */
			msg.sa_rekey_margin = opt_whole;
			continue;

		case CD_RKFUZZ: /* --rekeyfuzz <percentage> */
			msg.sa_rekey_fuzz = opt_whole;
			continue;

		case CD_KTRIES: /* --keyingtries <count> */
			msg.sa_keying_tries = opt_whole;
			continue;

		case CD_DPDACTION:
			if (streq(optarg, "none"))
				msg.dpd_action = DPD_ACTION_NONE;
			else if (streq(optarg, "clear"))
				msg.dpd_action = DPD_ACTION_CLEAR;
			else if (streq(optarg, "hold"))
				msg.dpd_action = DPD_ACTION_HOLD;
			else if (streq(optarg, "restart"))
				msg.dpd_action = DPD_ACTION_RESTART;
			else
				msg.dpd_action = DPD_ACTION_UNKNOWN;
			continue;

		case CD_DPDDELAY:
			msg.dpd_delay = opt_whole;
			continue;

		case CD_DPDTIMEOUT:
			msg.dpd_timeout = opt_whole;
			continue;

		case CD_IKE:    /* --ike <ike_alg1,ike_alg2,...> */
			msg.ike = optarg;
			continue;

		case CD_PFSGROUP:       /* --pfsgroup modpXXXX */
			msg.pfsgroup = optarg;
			continue;

		case CD_ESP:    /* --esp <esp_alg1,esp_alg2,...> */
			msg.esp = optarg;
			continue;

		case CD_CONNIPV4:
			if (LHAS(cd_seen, CD_CONNIPV6 - CD_FIRST))
				diag("--ipv4 conflicts with --ipv6");

			/* Since this is the default, the flag is redundant.
			 * So we don't need to set msg.addr_family
			 * and we don't need to check af_used_by
			 * and we don't have to consider defaulting tunnel_addr_family.
			 */
			continue;

		case CD_CONNIPV6:
			if (LHAS(cd_seen, CD_CONNIPV4 - CD_FIRST))
				diag("--ipv6 conflicts with --ipv4");

			if (af_used_by != NULL)
				diagq("--ipv6 must precede", af_used_by);

			af_used_by = long_opts[long_index].name;
			msg.addr_family = AF_INET6;

			/* Consider defaulting tunnel_addr_family to AF_INET6.
			 * Do so only if it hasn't yet been specified or used.
			 */
			if (LDISJOINT(cd_seen, LELEM(CD_TUNNELIPV4 - CD_FIRST) | LELEM(CD_TUNNELIPV6 - CD_FIRST))
			&& tunnel_af_used_by == NULL)
				msg.tunnel_addr_family = AF_INET6;
			continue;

		case CD_TUNNELIPV4:
			if (LHAS(cd_seen, CD_TUNNELIPV6 - CD_FIRST))
				diag("--tunnelipv4 conflicts with --tunnelipv6");

			if (tunnel_af_used_by != NULL)
				diagq("--tunnelipv4 must precede", af_used_by);

			msg.tunnel_addr_family = AF_INET;
			continue;

		case CD_TUNNELIPV6:
			if (LHAS(cd_seen, CD_TUNNELIPV4 - CD_FIRST))
				diag("--tunnelipv6 conflicts with --tunnelipv4");

			if (tunnel_af_used_by != NULL)
				diagq("--tunnelipv6 must precede", af_used_by);

			msg.tunnel_addr_family = AF_INET6;
			continue;

		case CA_NAME:           /* --caname <name> */
			msg.name = optarg;
			msg.whack_ca = TRUE;
			continue;
		case CA_CERT:           /* --cacert <path> */
			msg.cacert = optarg;
			continue;
		case CA_LDAPHOST:       /* --ldaphost <hostname> */
			msg.ldaphost = optarg;
			continue;
		case CA_LDAPBASE:       /* --ldapbase <base> */
			msg.ldapbase = optarg;
			continue;
		case CA_CRLURI:         /* --crluri <uri> */
			msg.crluri = optarg;
			continue;
		case CA_CRLURI2:        /* --crluri2 <uri> */
			msg.crluri2 = optarg;
			continue;
		case CA_OCSPURI:        /* --ocspuri <uri> */
			msg.ocspuri = optarg;
			continue;
		case CA_STRICT:         /* --strictcrlpolicy */
			msg.whack_strict = TRUE;
			continue;

#ifdef DEBUG
		case DBGOPT_NONE:       /* --debug-none */
			msg.debugging = DBG_NONE;
			continue;

		case DBGOPT_ALL:        /* --debug-all */
			msg.debugging |= DBG_ALL;   /* note: does not include PRIVATE */
			continue;

		case DBGOPT_RAW:        /* --debug-raw */
		case DBGOPT_CRYPT:      /* --debug-crypt */
		case DBGOPT_PARSING:    /* --debug-parsing */
		case DBGOPT_EMITTING:   /* --debug-emitting */
		case DBGOPT_CONTROL:    /* --debug-control */
		case DBGOPT_LIFECYCLE:  /* --debug-lifecycle */
		case DBGOPT_KERNEL:     /* --debug-kernel, --debug-klips */
		case DBGOPT_DNS:        /* --debug-dns */
		case DBGOPT_NATT:       /* --debug-natt */
		case DBGOPT_OPPO:       /* --debug-oppo */
		case DBGOPT_CONTROLMORE: /* --debug-controlmore */
		case DBGOPT_PRIVATE:    /* --debug-private */
		case DBGOPT_IMPAIR_DELAY_ADNS_KEY_ANSWER:       /* --impair-delay-adns-key-answer */
		case DBGOPT_IMPAIR_DELAY_ADNS_TXT_ANSWER:       /* --impair-delay-adns-txt-answer */
		case DBGOPT_IMPAIR_BUST_MI2:    /* --impair_bust_mi2 */
		case DBGOPT_IMPAIR_BUST_MR2:    /* --impair_bust_mr2 */
			msg.debugging |= LELEM(c-DBGOPT_RAW);
			continue;
#endif
		default:
			assert(FALSE);      /* unknown return value */
		}
		break;
	}

	if (optind != argc)
	{
		/* If you see this message unexpectedly, perhaps the
		 * case for the previous option ended with "break"
		 * instead of "continue"
		 */
		diagq("unexpected argument", argv[optind]);
	}

	/* For each possible form of the command, figure out if an argument
	 * suggests whether that form was intended, and if so, whether all
	 * required information was supplied.
	 */

	/* check opportunistic initiation simulation request */
	switch (opts_seen & (LELEM(OPT_OPPO_HERE) | LELEM(OPT_OPPO_THERE)))
	{
	case LELEM(OPT_OPPO_HERE):
	case LELEM(OPT_OPPO_THERE):
		diag("--oppohere and --oppothere must be used together");
		/*NOTREACHED*/
	case LELEM(OPT_OPPO_HERE) | LELEM(OPT_OPPO_THERE):
		msg.whack_oppo_initiate = TRUE;
		if (LIN(cd_seen, LELEM(CD_TUNNELIPV4 - CD_FIRST) | LELEM(CD_TUNNELIPV6 - CD_FIRST)))
			opts_seen &= ~LELEM(OPT_CD);
		break;
	}

	/* check leases */
	if (LHAS(opts_seen, OPT_LEASEADDR) && LHAS(opts_seen, OPT_LEASEID))
	{
		diag("--lease-addr and --lease-id cannot be used together");
	}

	/* check connection description */
	if (LHAS(opts_seen, OPT_CD))
	{
		if (!LHAS(cd_seen, CD_TO-CD_FIRST))
			diag("connection description option, but no --to");

		if (!LHAS(end_seen, END_HOST-END_FIRST))
			diag("connection missing --host after --to");

		if (isanyaddr(&msg.left.host_addr)
		&& isanyaddr(&msg.right.host_addr))
			diag("hosts cannot both be 0.0.0.0 or 0::0");

		if (msg.policy & POLICY_OPPO)
		{
			if ((msg.policy & (POLICY_PSK | POLICY_PUBKEY)) != POLICY_PUBKEY)
				diag("only PUBKEY is supported for opportunism");
			if ((msg.policy & POLICY_PFS) == 0)
				diag("PFS required for opportunism");
			if ((msg.policy & POLICY_ENCRYPT) == 0)
				diag("encryption required for opportunism");
		}

		check_end(&msg.left, &msg.right, !LHAS(end_seen_before_to, END_NEXTHOP-END_FIRST)
			, msg.addr_family, msg.tunnel_addr_family);

		check_end(&msg.right, &msg.left, !LHAS(end_seen, END_NEXTHOP-END_FIRST)
			, msg.addr_family, msg.tunnel_addr_family);

		if (subnettypeof(&msg.left.client) != subnettypeof(&msg.right.client))
			diag("endpoints clash: one is IPv4 and the other is IPv6");

		if (NEVER_NEGOTIATE(msg.policy))
		{
			/* we think this is just a shunt (because he didn't specify
			 * a host authentication method).  If he didn't specify a
			 * shunt type, he's probably gotten it wrong.
			 */
			if ((msg.policy & POLICY_SHUNT_MASK) == POLICY_SHUNT_TRAP)
				diag("non-shunt connection must have --psk or --rsasig or both");
		}
		else
		{
			/* not just a shunt: a real ipsec connection */
			if ((msg.policy & POLICY_ID_AUTH_MASK) == LEMPTY)
				diag("must specify --rsasig or --psk for a connection");

			if (!HAS_IPSEC_POLICY(msg.policy)
			&& (msg.left.has_client || msg.right.has_client))
				diag("must not specify clients for ISAKMP-only connection");
		}

		msg.whack_connection = TRUE;
	}

	/* decide whether --name is mandatory or forbidden */
	if (!LDISJOINT(opts_seen
	, LELEM(OPT_ROUTE) | LELEM(OPT_UNROUTE)
	| LELEM(OPT_INITIATE) | LELEM(OPT_TERMINATE)
	| LELEM(OPT_DELETE) | LELEM(OPT_CD)))
	{
		if (!LHAS(opts_seen, OPT_NAME) && !msg.whack_ca)
			diag("missing --name <connection_name>");
	}
	else if (!msg.whack_options && !msg.whack_status && !msg.whack_leases)
	{
		if (LHAS(opts_seen, OPT_NAME))
			diag("no reason for --name");
	}

	if (!LDISJOINT(opts_seen, LELEM(OPT_PUBKEYRSA) | LELEM(OPT_ADDKEY)))
	{
		if (!LHAS(opts_seen, OPT_KEYID))
			diag("--addkey and --pubkeyrsa require --keyid");
	}

	if (!(msg.whack_connection || msg.whack_key || msg.whack_myid
	|| msg.whack_delete || msg.whack_deletestate
	|| msg.whack_initiate || msg.whack_oppo_initiate || msg.whack_terminate
	|| msg.whack_route || msg.whack_unroute || msg.whack_listen
	|| msg.whack_unlisten || msg.whack_list || msg.whack_purgeocsp
	|| msg.whack_reread || msg.whack_ca || msg.whack_status
	|| msg.whack_options || msg.whack_shutdown || msg.whack_sc_op
	|| msg.whack_leases))
	{
		diag("no action specified; try --help for hints");
	}

	update_ports(&msg);

	/* tricky quick and dirty check for wild values */
	if (msg.sa_rekey_margin != 0
	&& msg.sa_rekey_fuzz * msg.sa_rekey_margin * 4 / msg.sa_rekey_margin / 4
	 != msg.sa_rekey_fuzz)
		diag("rekeymargin or rekeyfuzz values are so large that they cause oveflow");

	check_life_time (msg.sa_ike_life_seconds, OAKLEY_ISAKMP_SA_LIFETIME_MAXIMUM
		, "ikelifetime", &msg);

	check_life_time(msg.sa_ipsec_life_seconds, SA_LIFE_DURATION_MAXIMUM
		, "ipseclifetime", &msg);

	if (msg.dpd_action == DPD_ACTION_UNKNOWN)
		diag("dpdaction must be \"none\", \"clear\", \"hold\" or \"restart\"");

	if (msg.dpd_action != DPD_ACTION_NONE)
	{
		if (msg.dpd_delay <= 0)
			diag("dpddelay must be larger than zero");

		if (msg.dpd_timeout <= 0)
			diag("dpdtimeout must be larger than zero");

		if (msg.dpd_timeout <= msg.dpd_delay)
			diag("dpdtimeout must be larger than dpddelay");
	}

	/* pack strings for inclusion in message */
	next_str = msg.string;
	str_roof = &msg.string[sizeof(msg.string)];

	/* build esp message as esp="<esp>;<pfsgroup>" */
	if (msg.pfsgroup) {
			snprintf(esp_buf, sizeof (esp_buf), "%s;%s",
					msg.esp ? msg.esp : "",
					msg.pfsgroup ? msg.pfsgroup : "");
			msg.esp=esp_buf;
	}
	if (!pack_str(&msg.name)             /* string  1 */
	||  !pack_str(&msg.left.id)          /* string  2 */
	||  !pack_str(&msg.left.cert)        /* string  3 */
	||  !pack_str(&msg.left.ca)          /* string  4 */
	||  !pack_str(&msg.left.groups)      /* string  5 */
	||  !pack_str(&msg.left.updown)      /* string  6 */
	||  !pack_str(&msg.left.sourceip)    /* string  7 */
	||  !pack_str(&msg.left.virt)        /* string  8 */
	||  !pack_str(&msg.right.id)         /* string  9 */
	||  !pack_str(&msg.right.cert)       /* string 10 */
	||  !pack_str(&msg.right.ca)         /* string 11 */
	||  !pack_str(&msg.right.groups)     /* string 12 */
	||  !pack_str(&msg.right.updown)     /* string 13 */
	||  !pack_str(&msg.right.sourceip)   /* string 14 */
	||  !pack_str(&msg.right.virt)       /* string 15 */
	||  !pack_str(&msg.keyid)            /* string 16 */
	||  !pack_str(&msg.myid)             /* string 17 */
	||  !pack_str(&msg.cacert)           /* string 18 */
	||  !pack_str(&msg.ldaphost)         /* string 19 */
	||  !pack_str(&msg.ldapbase)         /* string 20 */
	||  !pack_str(&msg.crluri)           /* string 21 */
	||  !pack_str(&msg.crluri2)          /* string 22 */
	||  !pack_str(&msg.ocspuri)          /* string 23 */
	||  !pack_str(&msg.ike)              /* string 24 */
	||  !pack_str(&msg.esp)              /* string 25 */
	||  !pack_str(&msg.sc_data)          /* string 26 */
	||  !pack_str(&msg.whack_lease_ip)   /* string 27 */
	||  !pack_str(&msg.whack_lease_id)   /* string 28 */
	||  !pack_str(&msg.xauth_identity)   /* string 29 */
	||  str_roof - next_str < (ptrdiff_t)msg.keyval.len)
		diag("too many bytes of strings to fit in message to pluto");

	memcpy(next_str, msg.keyval.ptr, msg.keyval.len);
	msg.keyval.ptr = NULL;
	next_str += msg.keyval.len;

	msg.magic = ((opts_seen & ~LELEM(OPT_SHUTDOWN))
				| sc_seen | lst_seen | cd_seen | ca_seen) != LEMPTY
			|| msg.whack_options
		? WHACK_MAGIC : WHACK_BASIC_MAGIC;

	/* send message to Pluto */
	if (access(ctl_addr.sun_path, R_OK | W_OK) < 0)
	{
		int e = errno;

		switch (e)
		{
		case EACCES:
			fprintf(stderr, "whack: no right to communicate with pluto (access(\"%s\"))\n"
				, ctl_addr.sun_path);
			break;
		case ENOENT:
			fprintf(stderr, "whack: Pluto is not running (no \"%s\")\n"
				, ctl_addr.sun_path);
			break;
		default:
			fprintf(stderr, "whack: access(\"%s\") failed with %d %s\n"
				, ctl_addr.sun_path, errno, strerror(e));
			break;
		}
		whack_exit(RC_WHACK_PROBLEM);
	}
	else
	{
		int sock = socket(AF_UNIX, SOCK_STREAM, 0);
		int exit_status = 0;
		ssize_t len = next_str - (char *)&msg;

		if (sock == -1)
		{
			int e = errno;

			fprintf(stderr, "whack: socket() failed (%d %s)\n", e, strerror(e));
			whack_exit(RC_WHACK_PROBLEM);
		}

		if (connect(sock, (struct sockaddr *)&ctl_addr
		, offsetof(struct sockaddr_un, sun_path) + strlen(ctl_addr.sun_path)) < 0)
		{
			int e = errno;

			fprintf(stderr, "whack:%s connect() for \"%s\" failed (%d %s)\n"
				, e == ECONNREFUSED? " is Pluto running? " : ""
				, ctl_addr.sun_path, e, strerror(e));
			whack_exit(RC_WHACK_PROBLEM);
		}

		if (write(sock, &msg, len) != len)
		{
			int e = errno;

			fprintf(stderr, "whack: write() failed (%d %s)\n", e, strerror(e));
			whack_exit(RC_WHACK_PROBLEM);
		}

		/* for now, just copy reply back to stdout */

		{
			char buf[4097];     /* arbitrary limit on log line length */
			char *be = buf;

			for (;;)
			{
				char *ls = buf;
				ssize_t rl = read(sock, be, (buf + sizeof(buf)-1) - be);

				if (rl < 0)
				{
					int e = errno;

					fprintf(stderr, "whack: read() failed (%d %s)\n", e, strerror(e));
					whack_exit(RC_WHACK_PROBLEM);
				}
				if (rl == 0)
				{
					if (be != buf)
						fprintf(stderr, "whack: last line from pluto too long or unterminated\n");
					break;
				}

				be += rl;
				*be = '\0';

				for (;;)
				{
					char *le = strchr(ls, '\n');

					if (le == NULL)
					{
						/* move last, partial line to start of buffer */
						memmove(buf, ls, be-ls);
						be -= ls - buf;
						break;
					}

					le++;       /* include NL in line */
					ignore_result(write(1, ls, le - ls));

					/* figure out prefix number
					 * and how it should affect our exit status
					 */
					{
						unsigned long s = strtoul(ls, NULL, 10);

						switch (s)
						{
						case RC_COMMENT:
						case RC_LOG:
							/* ignore */
							break;
						case RC_SUCCESS:
							/* be happy */
							exit_status = 0;
							break;
						case RC_ENTERSECRET:
							get_secret(sock);
							break;
						/* case RC_LOG_SERIOUS: */
						default:
							/* pass through */
							exit_status = s;
							break;
						}
					}
					ls = le;
				}
			}
		}
		whack_exit(exit_status);
	}
	return -1; /* should never be reached */
}