Logo Search packages:      
Sourcecode: openswan version File versions  Download package

whackinit.c

/* command interface to Pluto - initiate a connection only.
 * suitable for setuid use.
 *
 * Copyright (C) 2004 Michael Richardson <mcr@xelerance.com>
 *
 * 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: whackinit.c,v 1.1 2004-12-16 01:24:45 mcr Exp $
 */

#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 <openswan.h>

#include "constants.h"
#include "oswlog.h"

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

static int setup_socket(void);

/** 
 * Print the 'ipsec whackinit --help' message
 */
static void
help(void)
{
    fprintf(stderr
      , "Usage:\n\n"
      "all forms:"
          " [--ctlbase <path>]"
          " [--label <string>]"
          "\n\n"
      "help: whack"
          " [--help]"
          " [--version]"
          "\n\n"
      "connection: whack"
          " whack"
          " (--initiate | --terminate)"
          " --name <connection_name>"
          " [--asynchronous]"
          " [--xauthname name]"
          " [--xauthpass pass]"
          "\n\n"
      "status: whack"
          " --status"
          "\n\n"
      "Openswan %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 
 *
 * @param mess The error message to print when exiting
 * @return void
 */
static void
diag(const char *mess)
{
    if (mess != NULL)
    {
      fprintf(stderr, "whackinit 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 if ugh is set.
 * Prints second arg, if non-NULL, as quoted string
 *
 * @param ugh Error message
 * @param this Optional 2nd part of error message
 * @return void
 */
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)
 */
enum option_enums {
#   define OPT_FIRST    OPT_NAME
    OPT_NAME,

    OPT_INITIATE,
    OPT_TERMINATE,
    OPT_STATUS,

    OPT_OPPO_HERE,
    OPT_OPPO_THERE,

    OPT_ASYNC,

    OPT_XAUTHNAME,
    OPT_XAUTHPASS,

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

/* Carve up space for result from getopt_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' },
    { "label", required_argument, NULL, 'l' },

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

    { "initiate", no_argument, NULL, OPT_INITIATE + OO },
    { "terminate", no_argument, NULL, OPT_TERMINATE + OO },

    { "status", no_argument, NULL, OPT_STATUS + OO },
    { "xauthname", required_argument, NULL, OPT_XAUTHNAME + OO },
    { "xauthuser", required_argument, NULL, OPT_XAUTHNAME + OO },
    { "xauthpass", required_argument, NULL, OPT_XAUTHPASS + OO },

#if 0
    /* maybe let users do this? */
    { "oppohere", required_argument, NULL, OPT_OPPO_HERE + OO },
    { "oppothere", required_argument, NULL, OPT_OPPO_THERE + OO },
#endif

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

    /* list options */

#   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 size_t
get_secret(char *buf, size_t bufsize)
{
    const char *secret;
    int len;

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

    strncpy(buf, secret, bufsize);

    len = strlen(buf) + 1;
    
    return len;
}

static int
get_value(char *buf, size_t bufsize)
{
    int len;
    int try;

    fflush(stdout);
    usleep(20000); /* give fflush time for flushing - has to go through awk */

    try = 3;
    len = 0;
    while(try > 0 && len==0)
    {
      fprintf(stderr, "Name enter:   ");
      
      memset(buf, 0, bufsize);
      
      if(fgets(buf, bufsize, stdin) != buf) {
          if(errno == 0) {
            fprintf(stderr, "Can not read password from standard in\n");
            exit(RC_WHACK_PROBLEM);
          } else {
            perror("fgets value");
            exit(RC_WHACK_PROBLEM);
          }
      }
      
      /* send the value to pluto, including \0, but fgets adds \n */
      len = strlen(buf);
      if(len == 0)
      {
          fprintf(stderr, "answer was empty, retry\n");
      }
    }
    if(len ==  0)
    {
      exit(RC_WHACK_PROBLEM);
    }

    return len;
}

static void
send_reply(int sock, char *buf, ssize_t len)
{
    /* send the secret to pluto */
    if (write(sock, buf, len) != len)
    {
      int e = errno;

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

static int setup_socket()
{
    int sock = socket(AF_UNIX, SOCK_STREAM, 0);

#if 0
    /* 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);
          fprintf(stderr, "My uid: %d my euid: %d gid: %d egid: %d\n",
                getuid(), geteuid(), getgid(), getegid());
          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);
    }
#endif


    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;
      
      switch (e)
      {
      case EACCES:
          fprintf(stderr, "whack: no right to communicate with pluto (access(\"%s\"))\n"
                , ctl_addr.sun_path);
          fprintf(stderr, "My uid: %d my euid: %d gid: %d egid: %d\n",
                getuid(), geteuid(), getgid(), getegid());
          break;
      case ENOENT:
          fprintf(stderr, "whack: Pluto is not running (no \"%s\")\n"
            , ctl_addr.sun_path);
          break;
      case ECONNREFUSED:
          fprintf(stderr, "whack: is Pluto running connect() for \"%s\" failed (%d %s)\n"
                , ctl_addr.sun_path, e, strerror(e));
          break;
      default:
          fprintf(stderr, "whack: connect() for \"%s\" failed (%d %s)\n"
                , ctl_addr.sun_path, e, strerror(e));
          break;
      }
      exit(RC_WHACK_PROBLEM);
    }
    
    /* give up all root priveledges, if we had any */
    setuid(getuid());

    /* give up any group priveledges, if we had any */
    setgid(getgid());

    return sock;
}



/* This is a hack for initiating ISAKMP exchanges. */
int
main(int argc, char **argv)
{
    struct whack_message msg;
    lset_t
        opts_seen = LEMPTY,
        cd_seen = LEMPTY;

    char xauthname[128];
    char xauthpass[128];
    int xauthnamelen, xauthpasslen;
    bool gotxauthname = FALSE, gotxauthpass = FALSE;
    int sock;

    /* get socket, and then drop root */
    sock = setup_socket();

    /* check division of numbering space */
    assert(OPT_LAST - OPT_FIRST < (sizeof cd_seen * BITS_PER_BYTE));

    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;

    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;

    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;
      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 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;
      }

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

      /* the rest of the options combine in complex ways */
      case OPT_NAME:    /* --name <connection-name> */
          name = optarg;
          msg.name = optarg;
          continue;

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

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

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

#if 0
          /* hmm */
      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;
#endif

      case OPT_ASYNC:
          msg.whack_async = TRUE;
          continue;

      case OPT_XAUTHNAME:
        gotxauthname = TRUE;
        xauthname[0]='\0';
        strncat(xauthname, optarg, sizeof(xauthname));
        xauthnamelen = strlen(xauthname)+1;
        continue;

      case OPT_XAUTHPASS:
        gotxauthpass = TRUE;
        xauthpass[0]='\0';
        strncat(xauthpass, optarg, sizeof(xauthpass));
        xauthpasslen = strlen(xauthpass)+1;
        continue;

      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.
     */

#if 0
    /* 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;
    }
#endif

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

    if (!(msg.whack_initiate || msg.whack_terminate
        || msg.whack_status))
    {
      diag("no action specified; try --help for hints");
    }

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

    if (!pack_str(&msg.name)        /* string  1 */
      || 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_STATUS)))) != LEMPTY
      || msg.whack_options
      ? WHACK_MAGIC : WHACK_BASIC_MAGIC;

    {
      int exit_status = 0;
      ssize_t len = next_str - (char *)&msg;

      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:
                      if(!gotxauthpass)
                      {
                        xauthpasslen = get_secret(xauthpass
                                            , sizeof(xauthpass));
                      }
                      send_reply(sock, xauthpass, xauthpasslen);
                      break;

                  case RC_XAUTHPROMPT:
                      if(!gotxauthname)
                      {
                        xauthnamelen = get_value(xauthname
                                           , sizeof(xauthname));
                      }
                      send_reply(sock, xauthname, xauthnamelen);
                      break;

                  /* case RC_LOG_SERIOUS: */
                  default:
                      /* pass through */
                      exit_status = s;
                      break;
                  }
                }
                ls = le;
            }
          }
      }
      return exit_status;
    }
}

/*
 * Local Variables:
 * c-basic-offset:4
 * c-style: pluto
 * End:
 */

Generated by  Doxygen 1.6.0   Back to index