/* $Id: omacutil.c,v 1.2 2006/03/23 07:14:49 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_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; }