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

omacutil.c

/* $Id$ */

/*
 *
 * Copyright (C) 2004 David Mazieres (dm@uun.org)
 *
 * 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, or (at
 * your option) any later version.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 */

#include "avutil.h"
#include "getopt_long.h"

char *progname;
long opt_date;
char *opt_pwfile;
char *opt_expire;
char *opt_aux;
char *opt_sender;
char *opt_from;
char *opt_fromexp;

int opt_verbose = 0;

struct addrinfo {
  u_int32_t time;       /* Expiration time */
  unsigned nonce;       /* 24-bit nonce */
  u_char reserved : 1;
  u_char hasaux : 1;          /* last 8 bytes hash of aux string */
  u_char duration : 6;        /* ceil (log_2 (seconds valid)) */
  char aux[8];                /* must be zero or hash of aux string */
};

struct binaddr {
  char time[4];               /* message timestamp */
  char nonce[3];        /* nonce */
  char rhd;             /* reserved/hasaux/duration*/
  char aux[8];                /* zero or hash of aux string */
};

static inline void
put3b (void *_dp, u_int32_t val)
{
  unsigned char *dp = (unsigned char *) (_dp);
  dp[0] = val >> 16;
  dp[1] = val >> 8;
  dp[2] = val;
}

static inline u_int32_t
get3b (const void *_dp)
{
  const unsigned char *dp = (const unsigned char *) (_dp);
  return dp[0] << 16 | dp[1] << 8 | dp[2];
}

static void
h2nai (struct binaddr *out, const struct addrinfo *in)
{
  putint (out->time, in->time);
  put3b (out->nonce, in->nonce);
  out->rhd = in->duration;
  if (in->hasaux) {
    out->rhd |= 0x40;
    memcpy (out->aux, in->aux, sizeof (out->aux));
  }
  else
    bzero (out->aux, sizeof (out->aux));
}

static void
n2hai (struct addrinfo *out, const struct binaddr *in)
{
  out->time = getint (in->time);
  out->nonce = get3b (in->nonce);
  out->duration = in->rhd & 0x3f;
  out->hasaux = !!(in->rhd & 0x40);
  out->reserved = !!(in->rhd & 0x80);
  memcpy (out->aux, in->aux, sizeof (out->aux));
}

char *
getmacpw (FILE *pwfile)
{
  static char hashbuf[20];
  char pwbuf[300];
  char *p;
  sha1_ctx sha;

  if (!fgets (pwbuf, sizeof (pwbuf), pwfile))
    return NULL;
  p = strchr (pwbuf, '\n');
  if (!p) {
    fprintf (stderr, "password file line too long\n");
    exit (1);
  }
  *p = '\0';

  sha1_init (&sha);
  sha1_update (&sha, pwbuf, p - pwbuf);
  sha1_final (&sha, (void *) hashbuf);
  bzero (pwbuf, sizeof (pwbuf));

  return hashbuf;
}

void
sha1_str (char *out, size_t outlen, const char *in)
{
  sha1_ctx sha;
  char hashbuf[sha1_hashsize];

  sha1_init (&sha);
  sha1_update (&sha, in, strlen (in));
  sha1_final (&sha, (void *) hashbuf);

  memcpy (out, hashbuf, outlen < sizeof (hashbuf) ? outlen : sizeof (hashbuf));
}

char *
mkaddr (const char *pw, time_t exp, const char *aux)
{
  struct addrinfo ai;
  struct binaddr bi;
  struct timespec ts;
  aes_ctx aes;
  char *asc;

  ai.time = exp;
  clock_gettime (CLOCK_REALTIME, &ts);
  ai.nonce = ts.tv_nsec >> 6;
  ai.reserved = 0;
  ai.hasaux = !!aux;

  if (exp > opt_date) {
    u_long dur = exp - opt_date;
    int logdur = 0;
    while (dur > 0) {
      logdur++;
      dur = dur >> 1;
    }
    ai.duration = logdur;
  }
  else
    ai.duration = 0;

  if (aux)
    sha1_str (ai.aux, sizeof (ai.aux), aux);

  h2nai (&bi, &ai);

  aes_setkey (&aes, pw, 16);
  aes_encrypt (&aes, &bi, &bi);
  asc = armor32 (&bi, 16);
  return asc;
}

void
do_gen (const char *pw, time_t exp, const char *aux)
{
  char *asc = mkaddr (pw, exp, aux);
  printf ("%s\n", asc);
  free (asc);
}

void
do_check (FILE *pwfile, char *asc, const char *auxsrc)
{
  char dbuf[16];
  char *pw;
  aes_ctx aes;
  unsigned long now = opt_date;
  int bad;
  struct binaddr bi;
  struct addrinfo ai;
  char aux[sizeof (ai.aux)];

  if (auxsrc)
    sha1_str (aux, sizeof (aux), auxsrc);
  else
    bzero (aux, sizeof (aux));
  if (dearmor32len (asc) != sizeof (dbuf)
      || (size_t) dearmor32 (dbuf, asc) != strlen (asc)) {
    fprintf (stderr, "bad input format\n");
    exit (1);
  }

  /* dearmor32 might ignore trailing bits, so double check we would
   * have produce this output.  (This only matters for security if you
   * are imposing some sort of quote per token, in which case users
   * could generate more valid looking tokens by manipulating the last
   * character of the token.) */
  pw = armor32 (dbuf, 16);
  bad = strcmp (pw, asc);
  free (pw);
  if (bad) {
    fprintf (stderr, "bad input format\n");
    exit (1);
  }

  do {
    bad = 0;
    pw = getmacpw (pwfile);
    if (!pw) {
      fprintf (stderr, "bad mac\n");
      exit (1);
    }
    aes_setkey (&aes, pw, 16);
    bzero (pw, 20);
    aes_decrypt (&aes, &bi, dbuf);
    n2hai (&ai, &bi);

    if (!auxsrc != !ai.hasaux)
      bad++;
    if (memcmp (aux, ai.aux, sizeof (aux)))
      bad++;

    if (opt_verbose) {
      printf ("Decrypted to:\n");
      printf ("    Time: %lu\n", (unsigned long) ai.time);
      printf ("   Nonce: %u\n", ai.nonce);
      printf ("reserved: %d\n", (int) ai.reserved);
      printf ("  hasaux: %d\n", (int) ai.hasaux);
      printf ("duration: %d\n", (int) ai.duration);
      printf ("     aux: %s\n", bad ? "bad" : "ok");
    }

    if (now > ai.time || (ai.time - now) >> ai.duration)
      bad++;
  } while (bad);

  bzero (&aes, sizeof (aes));
}

char *
firstpw (void)
{
  char *ret;
  FILE *pwfile = fopen (opt_pwfile, "r");
  if (!pwfile) {
    perror (opt_pwfile);
    return NULL;
  }
  ret = getmacpw (pwfile);
  fclose (pwfile);
  return ret;
}

char *
mksender (long exp, const char *aux)
{
  char *p, *q, *s, *sender;

  s = opt_sender;
  if (!s)
    s = getenv ("MACUTIL_SENDER");
  if (!s) {
    fprintf (stderr, "%s: no MACUTIL_SENDER environment variable\n", progname);
    return NULL;
  }

  p = strchr (s, '@');
  if (!p)
    p = s + strlen (s);
  for (q = p; *q != '*'; q--)
    if (q == s) {
      fprintf (stderr, "%s: MACUTIL_SENDER must contain '*'\n", progname);
      return NULL;
    }
  p = firstpw ();
  if (!p)
    return NULL;

  sender = mkaddr (p, exp, aux);
  bzero (p, 20);
  p = sender;
  sender = xmalloc ((q - s) + strlen (q) + strlen (p));
  sprintf (sender, "%.*s%s%s", (int) (q - s), s, p, *q ? q + 1 : "");
  free (p);
  return sender;
}

int
do_sendmail (int argc, char **argv)
{
  char *sendmail = getenv ("MACUTIL_SENDMAIL");
  char *sender = NULL;
  char **av;
  long exp;

  exp =  parse_expire (opt_expire, opt_date);
  if (exp != -1)
    sender = mksender (exp, opt_aux);

  av = xmalloc ((argc + 3) * sizeof (av[0]));
  av[0] = sendmail ? sendmail : "sendmail";
  av[1] = "-f";
  av[2] = sender;
  memcpy (av + 3, argv + 1, (argc - 1) * sizeof (av[0]));
  av[argc+2] = NULL;
  if (sender)
    execvp (av[0], av);
  else {
    av[2] = av[0];
    execvp (av[0], av + 2);
  }
  perror (av[0]);
  exit (1);
}

static char *
default_macpass (void)
{
  char defsuf[] = "/.avenger/.macpass";
  char *home = getenv ("HOME");
  char *ret;

  if (!home) {
    fprintf (stderr, "no HOME environment variable set\n");
    exit (1);
  }

  ret = xmalloc (strlen (home) + sizeof (defsuf));
  sprintf (ret, "%s%s", home, defsuf);
  return ret;
}

static void usage (void) __attribute__ ((noreturn));
static void
usage (void)
{
  fprintf (stderr, "usage: %s --gen [options]\n", progname);
  fprintf (stderr, "       %s --sender [options] address-template\n",
         progname);
  fprintf (stderr, "       %s --check [options] nonce\n", progname);
  fprintf (stderr, "       %s --sendmail [sendmail options]\n", progname);
  fprintf (stderr, "options are:\n"
         "  --passfile=file  File containing password(s)\n"
         "  --expire=date    Set expiration date with --gen\n"
         "  --date=date      Act as if current time were date\n");
  exit (1);
}

int
main (int argc, char **argv)
{
  int mode = 0;
  int c;
  struct option o[] = {
    { "version", no_argument, NULL, 'v' },
    { "check", no_argument, &mode, 'c' },
    { "gen", no_argument, &mode, 'g' },
    { "passfile", required_argument, NULL, 'p' },
    { "expire", required_argument, NULL, 'e' },
    { "date", required_argument, NULL, 'd' },
    { "aux", required_argument, NULL, 'a' },
    { "from", required_argument, NULL, 'f' },
    { "sender", required_argument, NULL, 's' },
    { "fromexp", required_argument, NULL, 'E' },
    { "sendmail", no_argument, NULL, 'M' },
    { NULL, 0, NULL, 0 }
  };
  FILE *pwfile;
  opt_date = time (NULL);

  opt_expire = getenv ("MACUTIL_EXPIRE");
  if (!opt_expire)
    opt_expire = "+21D";
  opt_pwfile = getenv ("MACUTIL_PASSFILE");
  if (!opt_pwfile)
    opt_pwfile = default_macpass ();

  progname = strrchr (argv[0], '/');
  if (progname)
    progname++;
  else
    progname = argv[0];

  if (!strncmp (progname, "send", 4))
    return do_sendmail (argc, argv);

  while ((c = getopt_long (argc, argv, "+Vcf:gp:s:", o, NULL)) != -1)
    switch (c) {
    case 0:
      break;
    case 'E':
      opt_fromexp = optarg;
      break;
    case 'M':
      return do_sendmail (argc - optind + 1, argv + optind - 1);
    case 'a':
      opt_aux = optarg;
      break;
    case 'v':
      version (progname, 1);
      break;
    case 'V':
      opt_verbose = 1;
      break;
    case 'd':
      opt_date = parse_expire (optarg, time (NULL));
      break;
    case 'e':
      opt_expire = optarg;
      break;
    case 'p':
      opt_pwfile = optarg;
      break;
    case 'f':
      opt_from = optarg;
      break;
    case 's':
      opt_sender = optarg;
      break;
    case 'c':
    case 'g':
      mode = c;
      break;
    default:
      usage ();
      break;
    }

  argv += optind;
  argc -= optind;

  if (opt_sender) {
    if (mode == 'c')
      usage ();
    mode = 's';
  }
  if (!mode && opt_from && (opt_sender = getenv ("MACUTIL_SENDER")))
    mode = 's';
  if (!mode || opt_date == -1)
    usage ();

  pwfile = fopen (opt_pwfile, "r");
  if (!pwfile) {
    perror (opt_pwfile);
    exit (1);
  }

  switch (mode) {
  case 'g':
    {
      long exp = parse_expire (opt_expire, opt_date);
      char *pw = getmacpw (pwfile);
      if (argc || exp == -1)
      usage ();
      if (!pw) {
      fprintf (stderr, "%s: no passwords\n", opt_pwfile);
      exit (1);
      }
      do_gen (pw, exp, opt_aux);
      bzero (pw, 20);
      break;
    }
  case 'c':
    if (argc != 1)
      usage ();
    do_check (pwfile, argv[0], opt_aux);
    break;
  case 's':
    {
      char *from;
      long exp = parse_expire (opt_expire, opt_date);
      if (argc || exp == -1)
      usage ();
      from = mksender (exp, opt_aux);
      if (!from)
      exit (1);
      if (opt_from) {
      printf ("%s", opt_from);
      if (opt_fromexp || (opt_fromexp = getenv ("MACUTIL_FROMEXP"))) {
        time_t t = exp;
        char buf[80];
        strftime (buf, sizeof (buf), "%d %b %Y", localtime (&t));
        buf[sizeof (buf) - 1] = '\0';
        printf (" (%s%s%s)", opt_fromexp, *opt_fromexp ? " " : "", buf);
      }
      printf (" <%s>\n", from);
      }
      else
      printf ("%s\n", from);
      exit (0);
      break;
    }
  default:
    usage ();
    break;
  }

#if 0
  hmac_sha1 (buf, argv[1], argv[2], strlen (argv[2]));
  for (i = 0; i < sizeof (buf); i++)
    printf ("%02x", buf[i]);
  printf ("\n");
#endif
    
  return 0;
}

Generated by  Doxygen 1.6.0   Back to index