/* $Id: macutil.c,v 1.19 2005/09/20 00:20:50 dm Exp $ */ /* * * 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_quiet; int opt_verbose = 0; struct addrinfo { u_int32_t time; /* Expiration time */ unsigned nonce; /* 24-bit nonce */ u_char reserved : 2; /* Must be zero */ u_char duration : 6; /* ceil (log_2 (seconds valid)) */ char mbz[8]; /* must be zero */ }; struct binaddr { char time[4]; /* message timestamp */ char nonce[3]; /* nonce */ char rd; /* reserved/duration */ char mbz[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->rd = in->duration & 0x3f; bzero (out->mbz, sizeof (out->mbz)); } static void n2hai (struct addrinfo *out, const struct binaddr *in) { out->time = getint (in->time); out->nonce = get3b (in->nonce); out->duration = in->rd & 0x3f; out->reserved = (unsigned) in->rd >> 6; memcpy (out->mbz, in->mbz, sizeof (out->mbz)); } char * getmacpw (FILE *pwfile, const char *aux) { static char pwbuf[300]; char *p; static char hashbuf[sha1_hashsize]; again: if (!fgets (pwbuf, sizeof (pwbuf), pwfile)) return NULL; p = strchr (pwbuf, '\n'); if (!p) { fprintf (stderr, "fatal: line too long in %s\n", opt_pwfile); exit (1); } if (p - pwbuf > 64) { fprintf (stderr, "%s: skipping line longer than 64 characters\n", opt_pwfile); goto again; } *p = '\0'; if (aux) hmac_sha1 (hashbuf, pwbuf, aux, strlen (aux) + 1); else hmac_sha1 (hashbuf, pwbuf, NULL, 0); bzero (pwbuf, sizeof (pwbuf)); return hashbuf; } char * mkaddr (const char *pw, time_t exp) { 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; if (exp > opt_date) { u_long dur = exp - opt_date + 1; int logdur = 0; while (dur > 0) { logdur++; dur = dur >> 1; } ai.duration = logdur; } else ai.duration = 0; 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) { char *asc = mkaddr (pw, exp); 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; unsigned i; if (dearmor32len (asc) != sizeof (dbuf) || (size_t) dearmor32 (dbuf, asc) != strlen (asc)) { if (!opt_quiet) fprintf (stderr, "bad input format\n"); exit (1); } /* dearmor32 might ignore trailing bits, so double check we would * have produced this output. (This only matters for security if * you are imposing some sort of quota 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) { if (!opt_quiet) fprintf (stderr, "bad input format\n"); exit (1); } do { bad = 0; pw = getmacpw (pwfile, auxsrc); if (!pw) { if (!opt_quiet) fprintf (stderr, "verification failed\n"); exit (1); } aes_setkey (&aes, pw, 16); aes_decrypt (&aes, &bi, dbuf); n2hai (&ai, &bi); for (i = 0; i < sizeof (ai.mbz); i++) if (ai.mbz[i]) bad++; if (opt_verbose) { time_t t = ai.time; printf ("Decrypted to:\n"); printf (" Time: %lu = %s", (unsigned long) t, ctime (&t)); printf (" Nonce: 0x%x\n", ai.nonce); printf ("reserved: %d\n", (int) ai.reserved); printf ("duration: %d\n", (int) ai.duration); printf (" mbz: %s\n", bad ? "bad" : "ok"); } if (ai.reserved) bad++; if ((ai.time - now) >> ai.duration) bad++; if (now > ai.time) 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, opt_aux); 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); 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" " --aux=string Non-secret string to perturb algorithm\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:gNOqp: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 'q': opt_quiet = 1; 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, opt_aux); if (argc || exp == -1) usage (); if (!pw) { fprintf (stderr, "%s: no passwords\n", opt_pwfile); exit (1); } do_gen (pw, exp); 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; }