[BACK]Return to skeyinit.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / skeyinit

File: [local] / src / usr.bin / skeyinit / skeyinit.c (download)

Revision 1.38, Fri Jun 7 21:35:26 2002 UTC (22 years ago) by millert
Branch: MAIN
Changes since 1.37: +43 -38 lines

Instead of passing seed and defaultseed to normal_mode() and
secure_mode() just pass in a single default seed.  Only secure_mode()
needs to actually change the seed and it can use its own temporary
buffer.

Fix zeroing of the secrete passphrase.  Instead of useing multiple
password buffers, crunch the key each time and compare the crunched
values.

/*	$OpenBSD: skeyinit.c,v 1.38 2002/06/07 21:35:26 millert Exp $	*/

/* OpenBSD S/Key (skeyinit.c)
 *
 * Authors:
 *          Neil M. Haller <nmh@thumper.bellcore.com>
 *          Philip R. Karn <karn@chicago.qualcomm.com>
 *          John S. Walden <jsw@thumper.bellcore.com>
 *          Scott Chasin <chasin@crimelab.com>
 *          Todd C. Miller <Todd.Miller@courtesan.com>
 *
 * S/Key initialization and seed update
 */

#include <sys/param.h>
#include <sys/file.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/time.h>

#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <pwd.h>
#include <readpassphrase.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>
#include <utmp.h>

#include <skey.h>
#include <bsd_auth.h>

#ifndef SKEY_NAMELEN
#define SKEY_NAMELEN    4
#endif

void	usage(void);
void	secure_mode(int *, char *, char *, char *, size_t);
void	normal_mode(char *, int, char *, char *);
void	timedout(int);
void	convert_db(void);
void	enable_db(int);

int
main(int argc, char **argv)
{
	int     rval, i, l, n, defaultsetup, rmkey, hexmode, enable, convert;
	char	hostname[MAXHOSTNAMELEN];
	char	seed[SKEY_MAX_SEED_LEN + 1];
	char    buf[256], key[SKEY_BINKEY_SIZE], filename[PATH_MAX], *ht;
	char    lastc, me[UT_NAMESIZE + 1], *p, *auth_type;
	struct skey skey;
	struct passwd *pp;

	n = rmkey = hexmode = enable = convert = 0;
	defaultsetup = 1;
	ht = auth_type = NULL;

	/* Build up a default seed based on the hostname and time */
	if (gethostname(hostname, sizeof(hostname)) < 0)
		err(1, "gethostname");
	for (i = 0, p = seed; hostname[i] && i < SKEY_NAMELEN; i++) {
		if (isalpha(hostname[i])) {
			if (isupper(hostname[i]))
				hostname[i] = tolower(hostname[i]);
			*p++ = hostname[i];
		} else if (isdigit(hostname[i]))
			*p++ = hostname[i];
	}
	*p = '\0';

	if ((pp = getpwuid(getuid())) == NULL)
		err(1, "no user with uid %d", getuid());
	(void)strcpy(me, pp->pw_name);

	if ((pp = getpwnam(me)) == NULL)
		err(1, "Who are you?");

	for (i = 1; i < argc && argv[i][0] == '-' && strcmp(argv[i], "--");) {
		if (argv[i][2] == '\0') {
			/* Single character switch */
			switch (argv[i][1]) {
			case 'a':
				if (argv[++i] == NULL || argv[i][0] == '\0')
					usage();
				auth_type = argv[i];
				break;
			case 's':
				defaultsetup = 0;
				auth_type = "skey";
				break;
			case 'x':
				hexmode = 1;
				break;
			case 'r':
				rmkey = 1;
				break;
			case 'n':
				if (argv[++i] == NULL || argv[i][0] == '\0')
					usage();
				if ((n = atoi(argv[i])) < 1 || n >= SKEY_MAX_SEQ)
					errx(1, "count must be > 0 and < %d",
					     SKEY_MAX_SEQ);
				break;
			case 'C':
				convert = 1;
				break;
			case 'D':
				enable = -1;
				break;
			case 'E':
				enable = 1;
				break;
			default:
				usage();
			}
		} else {
			/* Multi character switches are hash types */
			if ((ht = skey_set_algorithm(&argv[i][1])) == NULL) {
				warnx("Unknown hash algorithm %s", &argv[i][1]);
				usage();
			}
		}
		i++;
	}
	argv += i;
	argc -= i;

	if (argc > 1 || (enable && convert) || (enable && argc) ||
	    (convert && argc))
		usage();

	/* Handle -C, -D, and -E */
	if (convert || enable) {
		if (convert)
			convert_db();
		else
			enable_db(enable);
		exit(0);
	}

	/* Check for optional user string. */
	if (argc == 1) {
		if ((pp = getpwnam(argv[0])) == NULL) {
			if (getuid() == 0) {
				static struct passwd _pp;

				_pp.pw_name = argv[0];
				pp = &_pp;
				warnx("Warning, user unknown: %s", argv[0]);
			} else {
				errx(1, "User unknown: %s", argv[0]);
			}
		} else if (strcmp(pp->pw_name, me) != 0 && getuid() != 0) {
			/* Only root can change other's S/Keys. */
			errx(1, "Permission denied.");
		}
	}

	if (defaultsetup)
		fputs("Reminder - Only use this method if you are directly "
		    "connected\n           or have an encrypted channel.  If "
		    "you are using telnet,\n           hit return now and use "
		    "skeyinit -s.\n", stderr);

	if (getuid() != 0) {
		if ((pp = pw_dup(pp)) == NULL)
			err(1, NULL);
		if (!auth_userokay(pp->pw_name, auth_type, NULL, NULL))
			errx(1, "Password incorrect");
	}

	/*
	 * Lookup and lock the record we are about to modify.
	 * If this is a new entry this will prevent other users
	 * from appending new entries (and clobbering ours).
	 */
	rval = skeylookup(&skey, pp->pw_name);
	switch (rval) {
		case -1:
			if (errno == ENOENT)
				errx(1, "S/Key disabled");
			else
				err(1, "cannot open database");
			break;
		case 0:
			/* remove user if asked to do so */
			if (rmkey) {
				if (snprintf(filename, sizeof(filename),
				    "%s/%s", _PATH_SKEYDIR, pp->pw_name)
				    >= sizeof(filename)) {
					errno = ENAMETOOLONG;
					err(1, "Cannot remove S/Key entry");
				}
				if (unlink(filename) != 0)
					err(1, "Cannot remove S/Key entry");
				printf("S/Key entry for %s removed.\n",
				    pp->pw_name);
				exit(0);
			}

			(void)printf("[Updating %s with %s]\n", pp->pw_name,
			    ht ? ht : skey_get_algorithm());
			(void)printf("Old seed: [%s] %s\n",
				     skey_get_algorithm(), skey.seed);

			/*
			 * Sanity check old seed.
			 */
			l = strlen(skey.seed);
			for (p = skey.seed; *p; p++) {
				if (isalpha(*p)) {
					if (isupper(*p))
						*p = tolower(*p);
				} else if (!isdigit(*p)) {
					memmove(p, p + 1, l - (p - skey.seed));
					l--;
				}
			}

			/* If the seed ends in 0-8 just add one.  */
			if (l > 0) {
				lastc = skey.seed[l - 1];
				if (isdigit(lastc) && lastc != '9') {
					(void)strcpy(seed, skey.seed);
					seed[l - 1] = lastc + 1;
				}
				if (isdigit(lastc) && lastc == '9' && l < 16) {
					(void)strcpy(seed, skey.seed);
					seed[l - 1] = '0';
					seed[l] = '0';
					seed[l + 1] = '\0';
				}
			}
			break;
		case 1:
			if (rmkey)
				errx(1, "You have no entry to remove.");
			(void)printf("[Adding %s with %s]\n", pp->pw_name,
			    ht ? ht : skey_get_algorithm());
			if (snprintf(filename, sizeof(filename), "%s/%s",
			    _PATH_SKEYDIR, pp->pw_name) >= sizeof(filename)) {
				errno = ENAMETOOLONG;
				err(1, "Cannot create S/Key entry");
			}
			if ((l = open(filename,
			    O_RDWR | O_NONBLOCK | O_CREAT | O_TRUNC |O_NOFOLLOW,
			    S_IRUSR | S_IWUSR)) == -1 ||
			    flock(l, LOCK_EX) != 0 ||
			    (skey.keyfile = fdopen(l, "r+")) == NULL)
				err(1, "Cannot create S/Key entry");
			break;
	}
	if (fchown(fileno(skey.keyfile), pp->pw_uid, -1) != 0 ||
	    fchmod(fileno(skey.keyfile), S_IRUSR | S_IWUSR) != 0)
		err(1, "can't set owner/mode for %s", pp->pw_name);
	if (n == 0)
		n = 99;

	/* Set hash type if asked to */
	if (ht && strcmp(ht, skey_get_algorithm()) != 0)
		skey_set_algorithm(ht);

	alarm(180);
	if (!defaultsetup)
		secure_mode(&n, key, seed, buf, sizeof(buf));
	else
		normal_mode(pp->pw_name, n, key, seed);
	alarm(0);

	/* XXX - why use malloc here? */
	if ((skey.val = (char *)malloc(16 + 1)) == NULL)
		err(1, "Can't allocate memory");
	btoa8(skey.val, key);

	(void)fseek(skey.keyfile, 0L, SEEK_SET);
	(void)fprintf(skey.keyfile, "%s\n%s\n%04d\n%s\n%s\n",
	    pp->pw_name, skey_get_algorithm(), n, seed, skey.val);
	(void)fclose(skey.keyfile);

	(void)printf("\nID %s skey is otp-%s %d %s\n", pp->pw_name,
	    skey_get_algorithm(), n, seed);
	(void)printf("Next login password: %s\n\n",
	    hexmode ? put8(buf, key) : btoe(buf, key));
	exit(0);
}

void
secure_mode(int *count, char *key, char *seed, char *buf, size_t bufsiz)
{
	char *p, newseed[SKEY_MAX_SEED_LEN + 2];
	int i, n;

	(void)puts("You need the 6 words generated from the \"skey\" command.");
	for (i = 0; ; i++) {
		if (i >= 2)
			exit(1);

		(void)printf("Enter sequence count from 1 to %d: ",
		    SKEY_MAX_SEQ);
		(void)fgets(buf, bufsiz, stdin);
		clearerr(stdin);
		n = atoi(buf);
		if (n > 0 && n < SKEY_MAX_SEQ)
			break;	/* Valid range */
		(void)fprintf(stderr, "ERROR: Count must be between 1 and %d\n",
			     SKEY_MAX_SEQ);
	}

	for (i = 0; ; i++) {
		if (i >= 2)
			exit(1);

		(void)printf("Enter new seed [default %s]: ", seed);
		(void)fgets(newseed, sizeof(newseed), stdin); /* XXX */
		clearerr(stdin);
		rip(newseed);
		if (strlen(newseed) > SKEY_MAX_SEED_LEN) {
			(void)fprintf(stderr, "ERROR: Seed must be between 1 "
			    "and %d characters in length\n", SKEY_MAX_SEED_LEN);
			continue;
		}
		for (p = newseed; *p; p++) {
			if (isspace(*p)) {
				(void)fputs("ERROR: Seed must not contain "
				    "any spaces\n", stderr);
				break;
			} else if (isalpha(*p)) {
				if (isupper(*p))
					*p = tolower(*p);
			} else if (!isdigit(*p)) {
				(void)fputs("ERROR: Seed must be purely "
				    "alphanumeric\n", stderr);
				break;
			}
		}
		if (*p == '\0')
			break;  /* Valid seed */
	}
	if (newseed[0] != '\0')
		(void)strcpy(seed, newseed);

	for (i = 0; ; i++) {
		if (i >= 2)
			exit(1);

		(void)printf("otp-%s %d %s\nS/Key access password: ",
			     skey_get_algorithm(), n, seed);
		(void)fgets(buf, bufsiz, stdin);
		clearerr(stdin);
		rip(buf);
		backspace(buf);

		if (buf[0] == '?') {
			(void)puts("Enter 6 words from secure S/Key calculation.");
			continue;
		} else if (buf[0] == '\0')
			exit(1);

		if (etob(key, buf) == 1 || atob8(key, buf) == 0)
			break;	/* Valid format */
		(void)fputs("ERROR: Invalid format - try again with the 6 words.\n",
		    stderr);
	}
	*count= n;
}

void
normal_mode(char *username, int n, char *key, char *seed)
{
	int i, nn;
	char passwd[SKEY_MAX_PW_LEN+2], key2[SKEY_BINKEY_SIZE];

	/* Get user's secret passphrase */
	for (i = 0; ; i++) {
		if (i > 2)
			errx(1, "S/Key entry not updated");

		if (readpassphrase("Enter secret passphrase: ", passwd,
		    sizeof(passwd), 0) == NULL || passwd[0] == '\0')
			exit(1);

		if (strlen(passwd) < SKEY_MIN_PW_LEN) {
			(void)fprintf(stderr,
			    "ERROR: Your passphrase must be at least %d "
			    "characters long.\n", SKEY_MIN_PW_LEN);
			continue;
		} else if (strcmp(passwd, username) == 0) {
			(void)fputs("ERROR: Your passphrase may not be the "
			    "same as your user name.\n", stderr);
			continue;
		} else if (strspn(passwd, "abcdefghijklmnopqrstuvwxyz") == 
		    strlen(passwd)) {
			(void)fputs("ERROR: Your passphrase must contain more "
			    "than just lower case letters.\nWhitespace, "
			    "numbers, and punctuation are suggested.\n",
			    stderr);
			continue;
		} else if (strlen(passwd) > 63) {
			(void)fprintf(stderr, "WARNING: Your passphrase is "
			    "longer than the recommended maximum length of 63\n");
		}
		/* XXX - should check for passphrase that is really too long */

		/* Crunch seed and passphrase into starting key */
		nn = keycrunch(key, seed, passwd);
		memset(passwd, 0, sizeof(passwd));
		if (nn != 0)
			err(2, "key crunch failed");

		if (readpassphrase("Again secret passphrase: ", passwd,
		    sizeof(passwd), 0) == NULL || passwd[0] == '\0')
			exit(1);

		/* Crunch seed and passphrase into starting key */
		nn = keycrunch(key2, seed, passwd);
		memset(passwd, 0, sizeof(passwd));
		if (nn != 0)
			err(2, "key crunch failed");

		if (memcmp(key, key2, sizeof(key2)) == 0)
			break;

		(void)fputs("Passphrases do not match.\n", stderr);
	}

	nn = n;
	while (nn-- != 0)
		f(key);
}

void
enable_db(int op)
{
	if (op == 1) {
		/* enable */
		if (mkdir(_PATH_SKEYDIR, 01730) != 0 && errno != EEXIST)
			err(1, "can't mkdir %s", _PATH_SKEYDIR);
		if (chown(_PATH_SKEYDIR, geteuid(), getegid()) != 0)
			err(1, "can't chown %s", _PATH_SKEYDIR);
		if (chmod(_PATH_SKEYDIR, 01730) != 0)
			err(1, "can't chmod %s", _PATH_SKEYDIR);
	} else {
		/* disable */
		if (chmod(_PATH_SKEYDIR, 0) != 0 && errno != ENOENT)
			err(1, "can't chmod %s", _PATH_SKEYDIR);
	}
}

#define _PATH_SKEYKEYS	"/etc/skeykeys"
void
convert_db(void)
{
	struct passwd *pp;
	uid_t uid;
	FILE *keyfile;
	FILE *newfile;
	char buf[256], *logname, *hashtype, *seed, *val, *cp;
	char filename[PATH_MAX];
	int fd, n;

	if ((keyfile = fopen(_PATH_SKEYKEYS, "r")) == NULL)
		err(1, "can't open %s", _PATH_SKEYKEYS);
	if (flock(fileno(keyfile), LOCK_EX) != 0)
		err(1, "can't lock %s", _PATH_SKEYKEYS);
	enable_db(1);

	/*
	 * Loop over each entry in _PATH_SKEYKEYS, creating a file
	 * in _PATH_SKEYDIR for each one.
	 */
	while (fgets(buf, sizeof(buf), keyfile) != NULL) {
		if (buf[0] == '#')
			continue;
		if ((logname = strtok(buf, " \t")) == NULL)
			continue;
		if ((cp = strtok(NULL, " \t")) == NULL)
			continue;
		if (isalpha(*cp)) {
			hashtype = cp;
			if ((cp = strtok(NULL, " \t")) == NULL)
				continue;
		} else
			hashtype = "md4";
		n = atoi(cp);
		if ((seed = strtok(NULL, " \t")) == NULL)
			continue;
		if ((val = strtok(NULL, " \t")) == NULL)
			continue;

		if ((pp = getpwnam(logname)) != NULL)
			uid = pp->pw_uid;
		else
			uid = 0;

		/* Now write the new-style record. */
		if (snprintf(filename, sizeof(filename), "%s/%s", _PATH_SKEYDIR,
		    logname) >= sizeof(filename)) {
			errno = ENAMETOOLONG;
			warn("%s", logname);
			continue;
		}
		fd = open(filename, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
		if (fd == -1 || flock(fd, LOCK_EX) != 0 ||
		    (newfile = fdopen(fd, "r+")) == NULL) {
			warn("%s", logname);
			continue;
		}
		(void)fprintf(newfile, "%s\n%s\n%04d\n%s\n%s\n", logname,
			    hashtype, n, seed, val);
		(void)fchown(fileno(newfile), uid, -1);
		(void)fclose(newfile);
	}
	printf("%s has been populated.  NOTE: %s has *not* been removed.\n"
	    "It should be removed once you have verified that the new keys "
	    "work.\n", _PATH_SKEYDIR, _PATH_SKEYKEYS);
}

#define TIMEOUT_MSG	"Timed out waiting for input.\n"
void
timedout(int signo)
{

	write(STDERR_FILENO, TIMEOUT_MSG, sizeof(TIMEOUT_MSG) - 1);
	_exit(1);
}

void
usage(void)
{
	extern char *__progname;

	(void)fprintf(stderr, "usage: %s [-r] [-s] [-x] [-C] [-D] [-E] "
	    "[-a auth_type] [-n count]\n                "
	    "[-md4|-md5|-sha1|-rmd160] [user]\n", __progname);
	exit(1);
}