/* 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.
 *
 * RCSID $Id$
 */

#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 "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-klips]"
	    " [--debug-dns]"
	    " \\\n   "
	    " [--debug-natt]"
	    " [--debug-oppo]"
	    " [--debug-controlmore]"
	    " [--debug-private]"
	    "\n\n"
#endif
	"listen: whack"
	    " (--listen | --unlisten)"
	    "\n\n"
	"list: whack [--utc]"
	    " [--listalgs]"
	    " [--listpubkeys]"
	    " [--listcerts]"
	    " [--listcacerts]"
	    " \\\n   "
	    " [--listacerts]"
	    " [--listaacerts]"
	    " [--listocspcerts]"
	    " [--listgroups]"
	    " \\\n   "
	    " [--listcainfos]"
	    " [--listcrls]"
	    " [--listocsp]"
	    " [--listcards]"
	    " [--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 %s\n"
	, ipsec_version_code());
}

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

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

/* 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);
    }

    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_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_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_KLIPS,	/* 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 },

    { "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 },
    { "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_KLIPS + 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, *secret;
    int len;

    fflush(stdout);
    usleep(20000); /* give fflush time for flushing */
    buf = getpass("Enter: ");
    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;

    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();
	    return 0;	/* GNU coding standards say to stop here */

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

		printf("%s\n", ipsec_version_string());
		for (; *sp != NULL; sp++)
		    puts(*sp);
	    }
	    return 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> */
	    optionsfrom(optarg, &argc, &argv, optind, stderr);
	    /* does not return on error */
	    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_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;

	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 */
	    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_KLIPS:	/* --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 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_RSASIG)) != POLICY_RSASIG)
		diag("only RSASIG 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)
    {
	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))
    {
	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.virt)	/* string  7 */
    || !pack_str(&msg.right.id)		/* string  8 */
    || !pack_str(&msg.right.cert)	/* string  9 */
    || !pack_str(&msg.right.ca)		/* string 10 */
    || !pack_str(&msg.right.groups)	/* string 11 */
    || !pack_str(&msg.right.updown)	/* string 12 */
    || !pack_str(&msg.right.virt)	/* string 13 */
    || !pack_str(&msg.keyid)		/* string 14 */
    || !pack_str(&msg.myid)		/* string 15 */
    || !pack_str(&msg.cacert)		/* string 16 */
    || !pack_str(&msg.ldaphost)		/* string 17 */
    || !pack_str(&msg.ldapbase)		/* string 18 */
    || !pack_str(&msg.crluri)		/* string 19 */
    || !pack_str(&msg.crluri2)		/* string 20 */
    || !pack_str(&msg.ocspuri)		/* string 21 */
    || !pack_str(&msg.ike)		/* string 22 */
    || !pack_str(&msg.esp)		/* string 23 */
    || !pack_str(&msg.sc_data)		/* string 24 */
    || str_roof - next_str < (ptrdiff_t)msg.keyval.len)    /* chunk (sort of string 5) */
	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;
	}
	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));
	    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));
	    exit(RC_WHACK_PROBLEM);
	}

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

	    fprintf(stderr, "whack: write() failed (%d %s)\n", e, strerror(e));
	    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));
		    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 */
		    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;
		}
	    }
	}
	return exit_status;
    }
}
