/* command interface to Pluto * Copyright (C) 1997 Angelos D. Keromytis. * Copyright (C) 1998, 1999 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 . * * 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: whack.c,v 1.61 2000/06/18 21:20:35 dhr Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "constants.h" #include "defs.h" #include "version.c" #include "whack.h" static void help(void) { fprintf(stderr, "Usage:\n\n" "all forms:" " [--optionsfrom ]" " [--ctlbase ]" " [--label ]" "\n\n" "help: whack" " [--help]" " [--version]" "\n\n" "connection: whack" " --name " " \\\n " " (--host | --id )" " [--ikeport ]" " \\\n " " " " [--nexthop ]" " [--client ]" " [--updown ]" " \\\n " " --to" " (--host | --id )" " [--ikeport ]" " \\\n " " " " [--nexthop ]" " [--client ]" " [--updown ]" " \\\n " " [--psk]" " [--rsasig]" " [--encrypt]" " [--authenticate]" " [--tunnel]" " [--pfs]" " \\\n " " [--ikelifetime ]" " [--ipseclifetime ]" " \\\n " " [--reykeymargin ]" " [--reykeyfuzz ]" " [--keyingtries ]" "\n\n" "routing: whack" " (--route | --unroute)" " --name " "\n\n" "initiation: whack" " (--initiate | --terminate)" " --name " " [--asynchronous]" "\n\n" "delete: whack" " --delete" " --name " "\n\n" "pubkey: whack" " --keyid " " [--pubkeyrsa ]" "\n\n" #ifdef DEBUG "debug: whack" " \\\n " " [--debug-none]" " [--debug-all]" " \\\n " " [--debug-raw]" " [--debug-crypt]" " [--debug-parsing]" " \\\n " " [--debug-emitting]" " [--debug-control]" " [--debug-klips]" " [--debug-private]" "\n\n" #endif "listen: whack" " (--listen | --unlisten)" "\n\n" "status: whack" " --status" "\n\n" "shutdown: whack" " --shutdown" "\n\n" "FreeS/WAN %s\n", freeswan_version); } 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 as quoted string */ static void diagq(complaint_t ugh, const char *this) { if (ugh != NULL) { char buf[120]; snprintf(buf, sizeof(buf), "%s \"%s\"", ugh, this); diag(buf); } } /* complex combined operands return one of these enumerated values * Note: we are close to the limit of 32 flags in an unsigned long! */ enum { OPT_CTLBASE, OPT_NAME, # define OPT_CD_FIRST OPT_TO /* first connection description */ OPT_TO, OPT_HOST, /* first per-end */ OPT_ID, OPT_IKEPORT, OPT_NEXTHOP, OPT_CLIENT, OPT_UPDOWN, /* last per-end */ # define OPT_POLICY_FIRST OPT_PSK OPT_PSK, /* same order as POLICY_* */ OPT_RSASIG, /* same order as POLICY_* */ OPT_ENCRYPT, /* same order as POLICY_* */ OPT_AUTHENTICATE, /* same order as POLICY_* */ OPT_TUNNEL, /* same order as POLICY_* */ OPT_PFS, /* same order as POLICY_* */ OPT_IKELIFETIME, OPT_IPSECLIFETIME, OPT_RKMARGIN, OPT_RKFUZZ, OPT_KTRIES, # define OPT_CD_LAST OPT_KTRIES /* last connection description */ OPT_KEYID, OPT_PUBKEYRSA, OPT_ROUTE, OPT_UNROUTE, OPT_INITIATE, OPT_TERMINATE, OPT_DELETE, OPT_LISTEN, OPT_UNLISTEN, OPT_STATUS, OPT_SHUTDOWN, OPT_ASYNC #ifdef DEBUG /* must be last so others are less than 32 to fit in lset_t */ , OPT_DEBUG_NONE, OPT_DEBUG_ALL, OPT_DEBUG_RAW, /* same order as DBG_* */ OPT_DEBUG_CRYPT, /* same order as DBG_* */ OPT_DEBUG_PARSING, /* same order as DBG_* */ OPT_DEBUG_EMITTING, /* same order as DBG_* */ OPT_DEBUG_CONTROL, /* same order as DBG_* */ OPT_DEBUG_KLIPS, /* same order as DBG_* */ OPT_DEBUG_PRIVATE /* same order as DBG_* */ #endif }; #define OPTION_OFFSET 256 /* to get out of the way of letter options */ #define NUMERIC_ARG (1 << 6) /* expect a numeric argument */ 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 }, { "to", no_argument, NULL, OPT_TO + OO }, { "host", required_argument, NULL, OPT_HOST + OO }, { "id", required_argument, NULL, OPT_ID + OO }, { "ikeport", required_argument, NULL, OPT_IKEPORT + OO + NUMERIC_ARG }, { "nexthop", required_argument, NULL, OPT_NEXTHOP + OO }, { "client", required_argument, NULL, OPT_CLIENT + OO }, { "updown", required_argument, NULL, OPT_UPDOWN + OO }, { "psk", no_argument, NULL, OPT_PSK + OO }, { "rsasig", no_argument, NULL, OPT_RSASIG + OO }, { "encrypt", no_argument, NULL, OPT_ENCRYPT + OO }, { "authenticate", no_argument, NULL, OPT_AUTHENTICATE + OO }, { "tunnel", no_argument, NULL, OPT_TUNNEL + OO }, { "pfs", no_argument, NULL, OPT_PFS + OO }, { "ikelifetime", required_argument, NULL, OPT_IKELIFETIME + OO + NUMERIC_ARG }, { "ipseclifetime", required_argument, NULL, OPT_IPSECLIFETIME + OO + NUMERIC_ARG }, { "rekeymargin", required_argument, NULL, OPT_RKMARGIN + OO + NUMERIC_ARG }, { "rekeywindow", required_argument, NULL, OPT_RKMARGIN + OO + NUMERIC_ARG }, /* OBSOLETE */ { "rekeyfuzz", required_argument, NULL, OPT_RKFUZZ + OO + NUMERIC_ARG }, { "keyingtries", required_argument, NULL, OPT_KTRIES + OO + NUMERIC_ARG }, { "keyid", required_argument, NULL, OPT_KEYID + OO }, { "pubkeyrsa", required_argument, NULL, OPT_PUBKEYRSA + 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 }, { "listen", no_argument, NULL, OPT_LISTEN + OO }, { "unlisten", no_argument, NULL, OPT_UNLISTEN + OO }, { "status", no_argument, NULL, OPT_STATUS + OO }, { "shutdown", no_argument, NULL, OPT_SHUTDOWN + OO }, { "asynchronous", no_argument, NULL, OPT_ASYNC + OO }, #ifdef DEBUG { "debug-none", no_argument, NULL, OPT_DEBUG_NONE + OO }, { "debug-all]", no_argument, NULL, OPT_DEBUG_ALL + OO }, { "debug-raw", no_argument, NULL, OPT_DEBUG_RAW + OO }, { "debug-crypt", no_argument, NULL, OPT_DEBUG_CRYPT + OO }, { "debug-parsing", no_argument, NULL, OPT_DEBUG_PARSING + OO }, { "debug-emitting", no_argument, NULL, OPT_DEBUG_EMITTING + OO }, { "debug-control", no_argument, NULL, OPT_DEBUG_CONTROL + OO }, { "debug-klips", no_argument, NULL, OPT_DEBUG_KLIPS + OO }, { "debug-private", no_argument, NULL, OPT_DEBUG_PRIVATE + 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 *name , const struct whack_message *msg) { time_t mint = msg->sa_rekey_margin * (100 + msg->sa_rekey_fuzz) / 100; if (life > limit) { char buf[200]; snprintf(buf, sizeof(buf) , "%s [%lu seconds] must be less than %lu seconds" , name, (unsigned long)life, (unsigned long)limit); diag(buf); } if (life <= mint) { char buf[200]; snprintf(buf, sizeof(buf) , "%s [%lu] must be greater than" " rekeymargin*(100+rekeyfuzz)/100 [%lu*(100+%lu)/100]" , name , (unsigned long)life , (unsigned long)msg->sa_rekey_margin , (unsigned long)msg->sa_rekey_fuzz); diag(buf); } } /* This is a hack for initiating ISAKMP exchanges. */ int main(int argc, char **argv) { struct whack_message msg; lset_t opts_seen = LEMPTY, opts_seen_before_to; memset(&msg, '\0', sizeof(msg)); msg.magic = WHACK_MAGIC; msg.right.host_port = IKE_UDP_PORT; msg.name = NULL; msg.left.id = NULL; msg.left.updown = NULL; msg.right.id = NULL; msg.right.updown = NULL; msg.keyid = NULL; msg.keyval.ptr = NULL; msg.sa_ike_life_seconds = OAKLEY_ISAKMP_SA_LIFETIME_DEFAULT; msg.sa_ipsec_life_seconds = 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; for (;;) { int long_index; unsigned long opt_whole; /* 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; /* decode a numeric argument, if expected */ if (0 <= c && (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); } /* all of our non-letter flags except OPT_DEBUG_* get * added to opts_seen. The OP_DEBUG_* exception is because * we have too many to fit in a 32-bit string! * Unless other code intervenes, we reject repeated options. */ if (0 <= c #ifdef DEBUG && c < OPT_DEBUG_NONE #endif ) { lset_t f = LELEM(c); if (opts_seen & f) diagq("duplicated flag", long_opts[long_index].name); opts_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 */ fprintf(stderr, "FreeS/WAN %s\n", freeswan_version); return 0; /* GNU coding standards say to stop here */ case 'l' - OPTION_OFFSET: /* --label */ label = optarg; /* remember for diagnostics */ continue; case '+' - OPTION_OFFSET: /* --optionsfrom */ 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 */ if (snprintf(ctl_addr.sun_path, sizeof(ctl_addr.sun_path) , "%s%s", optarg, CTL_SUFFIX) == -1) diag("" CTL_SUFFIX " must be fit in a sun_addr"); continue; case OPT_NAME: /* --name */ name = optarg; msg.name = optarg; continue; case OPT_HOST: /* --host */ diagq(atoaddr(optarg, 0, &msg.right.host_addr), optarg); continue; case OPT_ID: /* --id */ msg.right.id = optarg; /* decoded by Pluto */ continue; case OPT_IKEPORT: /* --ikeport */ if (opt_whole<=0 || opt_whole >= 0x10000) diagq(" must be a number between 1 and 65535", optarg); msg.right.host_port = opt_whole; continue; case OPT_NEXTHOP: /* --nexthop */ diagq(atoaddr(optarg, 0, &msg.right.host_nexthop), optarg); continue; case OPT_CLIENT: /* --client */ diagq(atosubnet(optarg, 0, &msg.right.client_net, &msg.right.client_mask), optarg); msg.right.has_client = TRUE; msg.policy |= POLICY_TUNNEL; /* client => tunnel */ continue; case OPT_UPDOWN: /* --updown */ msg.right.updown = optarg; continue; case OPT_TO: /* --to */ /* process right end, move it to left, reset it */ if ((opts_seen & (LELEM(OPT_HOST) | LELEM(OPT_ID))) == 0) diag("connection missing both --host and --id (before --to)"); msg.left = msg.right; memset(&msg.right, '\0', sizeof(msg.right)); msg.right.id = NULL; msg.right.updown = NULL; msg.right.host_port = IKE_UDP_PORT; opts_seen_before_to = opts_seen; opts_seen = (opts_seen & ~LRANGE(OPT_HOST,OPT_UPDOWN)); continue; case OPT_PSK: /* --psk */ case OPT_RSASIG: /* --rsasig */ case OPT_ENCRYPT: /* --encrypt */ case OPT_AUTHENTICATE: /* --authenticate */ case OPT_TUNNEL: /* --tunnel */ case OPT_PFS: /* -- pfs */ msg.policy |= LELEM(c - OPT_POLICY_FIRST); continue; case OPT_IKELIFETIME: /* --ikelifetime */ msg.sa_ike_life_seconds = opt_whole; continue; case OPT_IPSECLIFETIME: /* --ipseclifetime */ msg.sa_ipsec_life_seconds = opt_whole; continue; case OPT_RKMARGIN: /* --rekeymargin */ msg.sa_rekey_margin = opt_whole; continue; case OPT_RKFUZZ: /* --rekeyfuzz */ msg.sa_rekey_fuzz = opt_whole; continue; case OPT_KTRIES: /* --keyingtries */ msg.sa_keying_tries = opt_whole; continue; case OPT_KEYID: /* --keyid */ msg.whack_key = TRUE; msg.keyid = optarg; /* decoded by Pluto */ continue; case OPT_PUBKEYRSA: { const char *ugh; static char keyspace[1024]; msg.pubkey_alg = PUBKEY_ALG_RSA; ugh = atobytes(optarg, 0, keyspace, sizeof(keyspace), &msg.keyval.len); if (ugh != NULL) { char ugh_space[60]; /* perhaps enough space */ snprintf(ugh_space, sizeof(ugh_space) , "PSK data malformed (%s)", ugh); diagq(ugh_space, optarg); } 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_LISTEN: /* --listen */ msg.whack_listen = TRUE; continue; case OPT_UNLISTEN: /* --unlisten */ msg.whack_unlisten = TRUE; continue; case OPT_STATUS: /* --status */ msg.whack_status = TRUE; continue; case OPT_SHUTDOWN: /* --shutdown */ msg.whack_shutdown = TRUE; continue; case OPT_ASYNC: msg.whack_async = TRUE; continue; #ifdef DEBUG case OPT_DEBUG_NONE: /* --debug-none */ msg.whack_options = TRUE; msg.debugging = DBG_NONE; continue; case OPT_DEBUG_ALL: /* --debug-all */ msg.whack_options = TRUE; msg.debugging |= DBG_ALL; /* note: does not include PRIVATE */ continue; case OPT_DEBUG_RAW: /* --debug-raw */ case OPT_DEBUG_CRYPT: /* --debug-crypt */ case OPT_DEBUG_PARSING: /* --debug-parsing */ case OPT_DEBUG_EMITTING: /* --debug-emitting */ case OPT_DEBUG_CONTROL: /* --debug-control */ case OPT_DEBUG_KLIPS: /* --debug-klips */ case OPT_DEBUG_PRIVATE: /* --debug-private */ msg.whack_options = TRUE; msg.debugging |= LELEM(c-OPT_DEBUG_RAW); continue; #endif default: assert(FALSE); /* unknown return value */ } break; } if (optind != argc) 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. */ if (opts_seen & LRANGE(OPT_CD_FIRST, OPT_CD_LAST)) { if (!LALLIN(opts_seen, LELEM(OPT_TO))) diag("connection description option, but no --to"); if ((opts_seen & (LELEM(OPT_HOST) | LELEM(OPT_ID))) == 0) diag("connection missing both --host and --id (after --to)"); if (is_NO_IP(msg.left.host_addr) && is_NO_IP(msg.right.host_addr)) diag("hosts cannot both be 0.0.0.0"); if ((opts_seen_before_to & LELEM(OPT_NEXTHOP)) == 0) { if (is_NO_IP(msg.right.host_addr)) diag("left nexthop must be specified when right is Road Warrior"); msg.left.host_nexthop = msg.right.host_addr; } if ((opts_seen & LELEM(OPT_NEXTHOP)) == 0) { if (is_NO_IP(msg.left.host_addr)) diag("right nexthop must be specified when left is Road Warrior"); msg.right.host_nexthop = msg.left.host_addr; } msg.whack_connection = TRUE; } /* decide whether --name is mandatory or forbidden */ if (opts_seen & (LELEM(OPT_ROUTE) | LELEM(OPT_UNROUTE) | LELEM(OPT_INITIATE) | LELEM(OPT_TERMINATE) | LELEM(OPT_DELETE) | LELEM(OPT_TO))) { if ((opts_seen & LELEM(OPT_NAME)) == 0) diag("missing --name "); } else if (!msg.whack_options) { if ((opts_seen & LELEM(OPT_NAME)) != 0) diag("no reason for --name"); } if (opts_seen & LELEM(OPT_PUBKEYRSA)) { if ((opts_seen & LELEM(OPT_KEYID)) == 0) diag("--pubkeyrsa requires --keyid"); } if (!(msg.whack_connection || msg.whack_key || msg.whack_delete || msg.whack_initiate || msg.whack_terminate || msg.whack_route || msg.whack_unroute || msg.whack_listen || msg.whack_unlisten || msg.whack_status || msg.whack_options || msg.whack_shutdown)) { diag("no action specified; try --help for hints"); } /* 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); /* pack strings for inclusion in message */ next_str = msg.string; str_roof = &msg.string[sizeof(msg.string)]; if (!pack_str(&msg.name) /* string 1 */ || !pack_str(&msg.left.id) /* string 2 */ || !pack_str(&msg.left.updown) /* string 3 */ || !pack_str(&msg.right.id) /* string 4 */ || !pack_str(&msg.right.updown) /* string 5 */ || !pack_str(&msg.keyid) /* string 6 */ || 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; /* send message to Pluto */ { 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: connect() for %s failed (%d %s)\n" , 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]; char *be = buf; for (;;) { char *ls = buf; ssize_t len = read(sock, be, (buf + sizeof(buf)-1) - be); if (len < 0) { int e = errno; fprintf(stderr, "whack: read() failed (%d %s)\n", e, strerror(e)); exit(RC_WHACK_PROBLEM); } if (len == 0) { if (be != buf) fprintf(stderr, "whack: last line from pluto too long or unterminated\n"); break; } be += len; *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 */ /* 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_LOG_SERIOUS: */ default: /* pass through */ exit_status = s; break; } } write(1, ls, le - ls); ls = le; } } } return exit_status; } }