/* $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;
}
syntax highlighted by Code2HTML, v. 0.9.1