From: Guido van Rooij Date: Thu, 19 May 1994 18:13:18 +0000 (+0000) Subject: 1) Added s/key support . X-Git-Tag: FreeBSD-release/1.1.5~567 X-Git-Url: https://git.subgeniuskitty.com/unix-history/.git/commitdiff_plain/05a0983de6eef621093c8768ed48cded629eaaaa?hp=51b3ba26b5e4535fa2ab29790b5f85ee35be5c5d 1) Added s/key support . 2 Added optional excessive login logging. 3) Added login acces control on a per host/tty base. 4) See skey(1) for skey descriptions and src/usr.bin/login/README for the logging and access control features. -Guido --- diff --git a/etc/Makefile b/etc/Makefile index e5a81669a9..2c4cdb96a9 100644 --- a/etc/Makefile +++ b/etc/Makefile @@ -1,5 +1,5 @@ # from: @(#)Makefile 5.11 (Berkeley) 5/21/91 -# $Id: Makefile,v 1.58 1994/05/17 13:03:34 jkh Exp $ +# $Id: Makefile,v 1.59 1994/05/19 12:29:28 rgrimes Exp $ NOOBJ= noobj @@ -9,8 +9,9 @@ BINOWN= root BINGRP= wheel BIN1= aliases csh.cshrc csh.login csh.logout dm.conf \ ftpusers gettytab group hosts host.conf hosts.equiv hosts.lpd \ - inetd.conf manpath.config motd myname netstart networks phones \ - printcap protocols rc rc.local remote security services shells \ + inetd.conf login.access manpath.config motd myname netstart \ + networks phones printcap protocols rc rc.local remote \ + security services shells skey.access \ syslog.conf ttys etc.${MACHINE}/disktab rpc # -rw-rw-rw- diff --git a/etc/login.access b/etc/login.access new file mode 100644 index 0000000000..5cf5454146 --- /dev/null +++ b/etc/login.access @@ -0,0 +1,44 @@ +# Login access control table. +# +# When someone logs in, the table is scanned for the first entry that +# matches the (user, host) combination, or, in case of non-networked +# logins, the first entry that matches the (user, tty) combination. The +# permissions field of that table entry determines whether the login will +# be accepted or refused. +# +# Format of the login access control table is three fields separated by a +# ":" character: +# +# permission : users : origins +# +# The first field should be a "+" (access granted) or "-" (access denied) +# character. The second field should be a list of one or more login names, +# group names, or ALL (always matches). The third field should be a list +# of one or more tty names (for non-networked logins), host names, domain +# names (begin with "."), host addresses, internet network numbers (end +# with "."), ALL (always matches) or LOCAL (matches any string that does +# not contain a "." character). If you run NIS you can use @netgroupname +# in host or user patterns. +# +# The EXCEPT operator makes it possible to write very compact rules. +# +# The group file is searched only when a name does not match that of the +# logged-in user. Only groups are matched in which users are explicitly +# listed: the program does not look at a user's primary group id value. +# +############################################################################## +# +# Disallow console logins to all but a few accounts. +# +#-:ALL EXCEPT wheel shutdown sync:console +# +# Disallow non-local logins to privileged accounts (group wheel). +# +#-:wheel:ALL EXCEPT LOCAL .win.tue.nl +# +# Some accounts are not allowed to login from anywhere: +# +#-:wsbscaro wsbsecr wsbspac wsbsym wscosor wstaiwde:ALL +# +# All other accounts are allowed to login from anywhere. +# diff --git a/etc/skey.access b/etc/skey.access new file mode 100644 index 0000000000..22cdb69fe1 --- /dev/null +++ b/etc/skey.access @@ -0,0 +1,8 @@ +# First word says if UNIX passwords are to be permitted or denied. +# remainder of the rule is a networknumber and mask. A rule matches a +# host if any of its addresses satisfies: +# +# network = (address & mask) +# +#what network mask +permit 0.0.0.0 0.0.0.0 diff --git a/lib/Makefile b/lib/Makefile index 89f276c755..6b7ed85637 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -1,7 +1,7 @@ # @(#)Makefile 5.25.1.1 (Berkeley) 5/7/91 SUBDIR= csu.${MACHINE} libc libcurses libm libmalloc libpthread \ - libresolv librpcsvc libtelnet libterm libutil liby + libresolv librpcsvc libskey libtelnet libterm libutil liby .if exists(libcrypt) .if !defined(NOCRYPT) diff --git a/lib/libskey/Makefile b/lib/libskey/Makefile new file mode 100644 index 0000000000..ecf483333c --- /dev/null +++ b/lib/libskey/Makefile @@ -0,0 +1,7 @@ +# @(#)Makefile 5.4 (Berkeley) 5/7/91 + +LIB= skey +SRCS= authfile.c md4.c put.c skey_crypt.c skeylogin.c skeysubr.c +CFLAGS+=-DMPU8086 +.include + diff --git a/lib/libskey/authfile.c b/lib/libskey/authfile.c new file mode 100644 index 0000000000..32b920a727 --- /dev/null +++ b/lib/libskey/authfile.c @@ -0,0 +1,170 @@ + /* Portions taken from the skey distribution on Oct 21 1993 */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if (MAXHOSTNAMELEN < 64) /* AIX weirdness */ +#undef MAXHOSTNAMELEN +#endif + +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 255 +#endif + +#include "skey.h" + +static int isaddr(); +static int rdnets(); + +#define MAXADDR 16 /* how many addresses can a machine + * have? */ + + /* + * Turn host into an IP address and then look it up in the authorization + * database to determine if ordinary password logins are OK + */ +int authfile(host) +char *host; +{ + char *addr[MAXADDR]; + char **ap; + long n; + struct hostent *hp; + char **lp; + struct hostent *xp; + int addr_length; + + if (strlen(host) == 0) { + /* Local login, okay */ + return 1; + } + if (isaddr(host)) { + return rdnets(inet_addr(host)); + } else { + + /* + * Stash away a copy of the host address list because it will be + * clobbered by other gethostbyXXX() calls. + */ + + hp = gethostbyname(host); + if (hp == NULL) { + syslog(LOG_ERR, "unknown host: %s", host); + return 0; + } + if (hp->h_addrtype != AF_INET) { + syslog(LOG_ERR, "unknown network family: %d", hp->h_addrtype); + return 0; + } + for (lp = hp->h_addr_list, ap = addr; ap < addr + MAXADDR; lp++, ap++) { + if (*lp == NULL) { + *ap = 0; + break; + } else { + if ((*ap = malloc(hp->h_length)) == 0) { + syslog(LOG_ERR, "out of memory"); + return 0; + } + memcpy(*ap, *lp, hp->h_length); + } + } + addr_length = hp->h_length; + + /* + * See if any of the addresses matches a pattern in the control file. + * Report and skip the address if it does not belong to the remote + * host. Assume localhost == localhost.domain. + */ + +#define NEQ(x,y) (strcasecmp((x),(y)) != 0) + + while (ap-- > addr) { + memcpy((char *) &n, *ap, addr_length); + if (rdnets(n)) { + if ((hp = gethostbyaddr(*ap, addr_length, AF_INET)) == 0 + || (NEQ(host, hp->h_name) && NEQ(host, "localhost"))) { + syslog(LOG_ERR, "IP address %s not registered for host %s", + inet_ntoa(*(struct in_addr *) * ap), host); + continue; + } + return 1; + } + } + return 0; + } +} +static int rdnets(host) +unsigned long host; +{ + FILE *fp; + char buf[128], + *cp; + long pattern, + mask; + char *strtok(); + int permit_it = 0; + + fp = fopen("/etc/skey.access", "r"); + if (fp == NULL) + return 1; /* XXX */ + while (fgets(buf, sizeof(buf), fp), !feof(fp)) { + if (buf[0] == '#') + continue; /* Comment */ + cp = strtok(buf, " \t"); + if (cp == NULL) + continue; + /* two choices permit or deny */ + if (strncasecmp(cp, "permit", 4) == 0) { + permit_it = 1; + } else { + if (strncasecmp(cp, "deny", 4) == 0) { + permit_it = 0; + } else { + continue; /* ignore this it is not + * permit/deny */ + } + } + cp = strtok(NULL, " \t"); + if (cp == NULL) + continue; /* Invalid line */ + pattern = inet_addr(cp); + cp = strtok(NULL, " \t"); + if (cp == NULL) + continue; /* Invalid line */ + mask = inet_addr(cp); + if ((host & mask) == pattern) { + fclose(fp); + return permit_it; + } + } + fclose(fp); + return 0; +} + + /* + * Return TRUE if string appears to be an IP address in dotted decimal; + * return FALSE otherwise (i.e., if string is a domain name) + */ +static int isaddr(s) +register char *s; +{ + char c; + + if (s == NULL) + return 1; /* Can't happen */ + + while ((c = *s++) != '\0') { + if (c != '[' && c != ']' && !isdigit(c) && c != '.') + return 0; + } + return 1; +} diff --git a/lib/libskey/md4.c b/lib/libskey/md4.c new file mode 100644 index 0000000000..96bc7d0bf9 --- /dev/null +++ b/lib/libskey/md4.c @@ -0,0 +1,316 @@ +/* + * md4.c -- Implementation of MD4 Message Digest Algorithm + * Updated: 2/16/90 by Ronald L. Rivest + * (C) 1990 RSA Data Security, Inc. + * + * Portability nits fixed and reformatted - 2/12/91 Phil Karn + */ + +/* + * To use MD4: + * -- Include md4.h in your program + * -- Declare an MDstruct MD to hold the state of the digest computation. + * -- Initialize MD using MDbegin(&MD) + * -- For each full block (64 bytes) X you wish to process, call + * MDupdate(&MD,X,512) + * (512 is the number of bits in a full block.) + * -- For the last block (less than 64 bytes) you wish to process, + * MDupdate(&MD,X,n) + * where n is the number of bits in the partial block. A partial + * block terminates the computation, so every MD computation should + * terminate by processing a partial block, even if it has n = 0. + * -- The message digest is available in MD.buffer[0] ... MD.buffer[3]. + * (Least-significant byte of each word should be output first.) + * -- You can print out the digest using MDprint(&MD) + */ + +/* Implementation notes: + * This implementation assumes that longs are 32-bit quantities. + * If the machine stores the least-significant byte of an long in the + * least-addressed byte (eg., VAX and 8086), then LOWBYTEFIRST should be + * set to TRUE. Otherwise (eg., SUNS), LOWBYTEFIRST should be set to + * FALSE. Note that on machines with LOWBYTEFIRST FALSE the routine + * MDupdate modifies has a side-effect on its input array (the order of bytes + * in each word are reversed). If this is undesired a call to MDreverse(X) can + * reverse the bytes of X back into order after each call to MDupdate. + */ +#define TRUE 1 +#define FALSE 0 + +#if (defined(__MSDOS__) || defined(MPU8086) || defined(MPU8080) \ + || defined(vax) || defined (MIPSEL)) +#define LOWBYTEFIRST TRUE /* Low order bytes are first in memory */ +#else /* Almost all other machines are big-endian */ +#define LOWBYTEFIRST FALSE +#endif + + +/* Compile-time includes */ +#include +#include "md4.h" + +/* Compile-time declarations of MD4 ``magic constants'' */ +#define I0 0x67452301 /* Initial values for MD buffer */ +#define I1 0xefcdab89 +#define I2 0x98badcfe +#define I3 0x10325476 +#define C2 013240474631 /* round 2 constant = sqrt(2) in octal */ +#define C3 015666365641 /* round 3 constant = sqrt(3) in octal */ +/* C2 and C3 are from Knuth, The Art of Programming, Volume 2 + * (Seminumerical Algorithms), Second Edition (1981), Addison-Wesley. + * Table 2, page 660. + */ +#define fs1 3 /* round 1 shift amounts */ +#define fs2 7 +#define fs3 11 +#define fs4 19 +#define gs1 3 /* round 2 shift amounts */ +#define gs2 5 +#define gs3 9 +#define gs4 13 +#define hs1 3 /* round 3 shift amounts */ +#define hs2 9 +#define hs3 11 +#define hs4 15 + + +/* Compile-time macro declarations for MD4. + * Note: The ``rot'' operator uses the variable ``tmp''. + * It assumes tmp is declared as unsigned long, so that the >> + * operator will shift in zeros rather than extending the sign bit. + */ +#define f(X,Y,Z) ((X&Y) | ((~X)&Z)) +#define g(X,Y,Z) ((X&Y) | (X&Z) | (Y&Z)) +#define h(X,Y,Z) (X^Y^Z) +#define rot(X,S) (tmp=X,(tmp<>(32-S))) +#define ff(A,B,C,D,i,s) A = rot((A + f(B,C,D) + X[i]),s) +#define gg(A,B,C,D,i,s) A = rot((A + g(B,C,D) + X[i] + C2),s) +#define hh(A,B,C,D,i,s) A = rot((A + h(B,C,D) + X[i] + C3),s) + +void MDreverse __ARGS((unsigned long *X)); + +/* MDprint(MDp) + * Print message digest buffer MDp as 32 hexadecimal digits. + * Order is from low-order byte of buffer[0] to high-order byte of buffer[3]. + * Each byte is printed with high-order hexadecimal digit first. + * This is a user-callable routine. + */ +void +MDprint(MDp) +MDptr MDp; +{ + int i,j; + + for(i=0;i<4;i++) + for(j=0;j<32;j=j+8) + printf("%02lx",(MDp->buffer[i]>>j) & 0xFF); +} + +/* MDbegin(MDp) + * Initialize message digest buffer MDp. + * This is a user-callable routine. + */ +void +MDbegin(MDp) +MDptr MDp; +{ + int i; + + MDp->buffer[0] = I0; + MDp->buffer[1] = I1; + MDp->buffer[2] = I2; + MDp->buffer[3] = I3; + for(i=0;i<8;i++) + MDp->count[i] = 0; + MDp->done = 0; +} + +/* MDreverse(X) + * Reverse the byte-ordering of every long in X. + * Assumes X is an array of 16 longs. + * The macro revx reverses the byte-ordering of the next word of X. + */ +#define revx { t = (*X << 16) | (*X >> 16); \ + *X++ = ((t & 0xFF00FF00) >> 8) | ((t & 0x00FF00FF) << 8); } +void +MDreverse(X) +unsigned long *X; +{ + register unsigned long t; + + revx; + revx; + revx; + revx; + revx; + revx; + revx; + revx; + revx; + revx; + revx; + revx; + revx; + revx; + revx; + revx; +} + +/* MDblock(MDp,X) + * Update message digest buffer MDp->buffer using 16-word data block X. + * Assumes all 16 words of X are full of data. + * Does not update MDp->count. + * This routine is not user-callable. + */ +static void +MDblock(MDp,X) +MDptr MDp; +unsigned long *X; +{ + register unsigned long tmp, A, B, C, D; + +#if LOWBYTEFIRST == FALSE + MDreverse(X); +#endif + A = MDp->buffer[0]; + B = MDp->buffer[1]; + C = MDp->buffer[2]; + D = MDp->buffer[3]; + /* Update the message digest buffer */ + ff(A,B,C,D,0,fs1); /* Round 1 */ + ff(D,A,B,C,1,fs2); + ff(C,D,A,B,2,fs3); + ff(B,C,D,A,3,fs4); + ff(A,B,C,D,4,fs1); + ff(D,A,B,C,5,fs2); + ff(C,D,A,B,6,fs3); + ff(B,C,D,A,7,fs4); + ff(A,B,C,D,8,fs1); + ff(D,A,B,C,9,fs2); + ff(C,D,A,B,10,fs3); + ff(B,C,D,A,11,fs4); + ff(A,B,C,D,12,fs1); + ff(D,A,B,C,13,fs2); + ff(C,D,A,B,14,fs3); + ff(B,C,D,A,15,fs4); + gg(A,B,C,D,0,gs1); /* Round 2 */ + gg(D,A,B,C,4,gs2); + gg(C,D,A,B,8,gs3); + gg(B,C,D,A,12,gs4); + gg(A,B,C,D,1,gs1); + gg(D,A,B,C,5,gs2); + gg(C,D,A,B,9,gs3); + gg(B,C,D,A,13,gs4); + gg(A,B,C,D,2,gs1); + gg(D,A,B,C,6,gs2); + gg(C,D,A,B,10,gs3); + gg(B,C,D,A,14,gs4); + gg(A,B,C,D,3,gs1); + gg(D,A,B,C,7,gs2); + gg(C,D,A,B,11,gs3); + gg(B,C,D,A,15,gs4); + hh(A,B,C,D,0,hs1); /* Round 3 */ + hh(D,A,B,C,8,hs2); + hh(C,D,A,B,4,hs3); + hh(B,C,D,A,12,hs4); + hh(A,B,C,D,2,hs1); + hh(D,A,B,C,10,hs2); + hh(C,D,A,B,6,hs3); + hh(B,C,D,A,14,hs4); + hh(A,B,C,D,1,hs1); + hh(D,A,B,C,9,hs2); + hh(C,D,A,B,5,hs3); + hh(B,C,D,A,13,hs4); + hh(A,B,C,D,3,hs1); + hh(D,A,B,C,11,hs2); + hh(C,D,A,B,7,hs3); + hh(B,C,D,A,15,hs4); + MDp->buffer[0] += A; + MDp->buffer[1] += B; + MDp->buffer[2] += C; + MDp->buffer[3] += D; +} + +/* MDupdate(MDp,X,count) + * Input: MDp -- an MDptr + * X -- a pointer to an array of unsigned characters. + * count -- the number of bits of X to use. + * (if not a multiple of 8, uses high bits of last byte.) + * Update MDp using the number of bits of X given by count. + * This is the basic input routine for an MD4 user. + * The routine completes the MD computation when count < 512, so + * every MD computation should end with one call to MDupdate with a + * count less than 512. A call with count 0 will be ignored if the + * MD has already been terminated (done != 0), so an extra call with count + * 0 can be given as a ``courtesy close'' to force termination if desired. + */ +void +MDupdate(MDp,X,count) +MDptr MDp; +unsigned char *X; +unsigned int count; +{ + int i,bit,byte,mask; + unsigned long tmp; + unsigned char XX[64]; + unsigned char *p; + + /* return with no error if this is a courtesy close with count + * zero and MDp->done is true. + */ + if(count == 0 && MDp->done) + return; + /* check to see if MD is already done and report error */ + if(MDp->done){ + printf("\nError: MDupdate MD already done."); + return; + } + /* Add count to MDp->count */ + tmp = count; + p = MDp->count; + while(tmp){ + tmp += *p; + *p++ = tmp; + tmp = tmp >> 8; + } + /* Process data */ + if(count == 512){ + /* Full block of data to handle */ + MDblock(MDp,(unsigned long *)X); + } else if(count > 512){ + /* Check for count too large */ + printf("\nError: MDupdate called with illegal count value %ld.",count); + return; + } else { + /* partial block -- must be last block so finish up + * Find out how many bytes and residual bits there are + */ + byte = count >> 3; + bit = count & 7; + /* Copy X into XX since we need to modify it */ + for(i=0;i<=byte;i++) + XX[i] = X[i]; + for(i=byte+1;i<64;i++) + XX[i] = 0; + /* Add padding '1' bit and low-order zeros in last byte */ + mask = 1 << (7 - bit); + XX[byte] = (XX[byte] | mask) & ~( mask - 1); + /* If room for bit count, finish up with this block */ + if(byte <= 55){ + for(i=0;i<8;i++) + XX[56+i] = MDp->count[i]; + MDblock(MDp,(unsigned long *)XX); + } else { + /* need to do two blocks to finish up */ + MDblock(MDp,(unsigned long *)XX); + for(i=0;i<56;i++) + XX[i] = 0; + for(i=0;i<8;i++) + XX[56+i] = MDp->count[i]; + MDblock(MDp,(unsigned long *)XX); + } + /* Set flag saying we're done with MD computation */ + MDp->done = 1; + } +} +/* End of md4.c */ diff --git a/lib/libskey/md4.h b/lib/libskey/md4.h new file mode 100644 index 0000000000..5f2e031eb5 --- /dev/null +++ b/lib/libskey/md4.h @@ -0,0 +1,50 @@ +#ifdef __STDC__ +#define __ARGS(X) X /* For ANSI C */ +#else +#define __ARGS(X) () +#endif + +/* + * + * md4.h -- Header file for implementation of MD4 Message Digest Algorithm + * Updated: 2/13/90 by Ronald L. Rivest + * (C) 1990 RSA Data Security, Inc. + * Reformatted and de-linted - 2/12/91 Phil Karn + */ + +/* MDstruct is the data structure for a message digest computation. */ +typedef struct { + unsigned long buffer[4];/* Holds 4-word result of MD computation */ + unsigned char count[8]; /* Number of bits processed so far */ + unsigned int done; /* Nonzero means MD computation finished */ +} MDstruct, *MDptr; + +/* MDbegin(MD) + * Input: MD -- an MDptr + * Initialize the MDstruct prepatory to doing a message digest computation. + */ +extern void MDbegin __ARGS((MDptr MDp)); + +/* MDupdate(MD,X,count) + * Input: MD -- an MDptr + * X -- a pointer to an array of unsigned characters. + * count -- the number of bits of X to use (an unsigned int). + * Updates MD using the first ``count'' bits of X. + * The array pointed to by X is not modified. + * If count is not a multiple of 8, MDupdate uses high bits of last byte. + * This is the basic input routine for a user. + * The routine terminates the MD computation when count < 512, so + * every MD computation should end with one call to MDupdate with a + * count less than 512. Zero is OK for a count. + */ +extern void MDupdate __ARGS((MDptr MDp,unsigned char *X,unsigned int count)); + +/* MDprint(MD) + * Input: MD -- an MDptr + * Prints message digest buffer MD as 32 hexadecimal digits. + * Order is from low-order byte of buffer[0] to high-order byte of buffer[3]. + * Each byte is printed with high-order hexadecimal digit first. + */ +extern void MDprint __ARGS((MDptr MDp)); + +/* End of md4.h */ diff --git a/lib/libskey/put.c b/lib/libskey/put.c new file mode 100644 index 0000000000..1ef1ec0a86 --- /dev/null +++ b/lib/libskey/put.c @@ -0,0 +1,2289 @@ +#include +#include +#include +#include +#include "skey.h" + +static unsigned long extract __ARGS((char *s,int start,int length)); +static void standard __ARGS((char *word)); +static void insert __ARGS((char *s, int x, int start, int length)); +static int wsrch __ARGS((char *w,int low,int high)); + +/* Dictionary for integer-word translations */ +char Wp[2048][4] = { +"A", +"ABE", +"ACE", +"ACT", +"AD", +"ADA", +"ADD", +"AGO", +"AID", +"AIM", +"AIR", +"ALL", +"ALP", +"AM", +"AMY", +"AN", +"ANA", +"AND", +"ANN", +"ANT", +"ANY", +"APE", +"APS", +"APT", +"ARC", +"ARE", +"ARK", +"ARM", +"ART", +"AS", +"ASH", +"ASK", +"AT", +"ATE", +"AUG", +"AUK", +"AVE", +"AWE", +"AWK", +"AWL", +"AWN", +"AX", +"AYE", +"BAD", +"BAG", +"BAH", +"BAM", +"BAN", +"BAR", +"BAT", +"BAY", +"BE", +"BED", +"BEE", +"BEG", +"BEN", +"BET", +"BEY", +"BIB", +"BID", +"BIG", +"BIN", +"BIT", +"BOB", +"BOG", +"BON", +"BOO", +"BOP", +"BOW", +"BOY", +"BUB", +"BUD", +"BUG", +"BUM", +"BUN", +"BUS", +"BUT", +"BUY", +"BY", +"BYE", +"CAB", +"CAL", +"CAM", +"CAN", +"CAP", +"CAR", +"CAT", +"CAW", +"COD", +"COG", +"COL", +"CON", +"COO", +"COP", +"COT", +"COW", +"COY", +"CRY", +"CUB", +"CUE", +"CUP", +"CUR", +"CUT", +"DAB", +"DAD", +"DAM", +"DAN", +"DAR", +"DAY", +"DEE", +"DEL", +"DEN", +"DES", +"DEW", +"DID", +"DIE", +"DIG", +"DIN", +"DIP", +"DO", +"DOE", +"DOG", +"DON", +"DOT", +"DOW", +"DRY", +"DUB", +"DUD", +"DUE", +"DUG", +"DUN", +"EAR", +"EAT", +"ED", +"EEL", +"EGG", +"EGO", +"ELI", +"ELK", +"ELM", +"ELY", +"EM", +"END", +"EST", +"ETC", +"EVA", +"EVE", +"EWE", +"EYE", +"FAD", +"FAN", +"FAR", +"FAT", +"FAY", +"FED", +"FEE", +"FEW", +"FIB", +"FIG", +"FIN", +"FIR", +"FIT", +"FLO", +"FLY", +"FOE", +"FOG", +"FOR", +"FRY", +"FUM", +"FUN", +"FUR", +"GAB", +"GAD", +"GAG", +"GAL", +"GAM", +"GAP", +"GAS", +"GAY", +"GEE", +"GEL", +"GEM", +"GET", +"GIG", +"GIL", +"GIN", +"GO", +"GOT", +"GUM", +"GUN", +"GUS", +"GUT", +"GUY", +"GYM", +"GYP", +"HA", +"HAD", +"HAL", +"HAM", +"HAN", +"HAP", +"HAS", +"HAT", +"HAW", +"HAY", +"HE", +"HEM", +"HEN", +"HER", +"HEW", +"HEY", +"HI", +"HID", +"HIM", +"HIP", +"HIS", +"HIT", +"HO", +"HOB", +"HOC", +"HOE", +"HOG", +"HOP", +"HOT", +"HOW", +"HUB", +"HUE", +"HUG", +"HUH", +"HUM", +"HUT", +"I", +"ICY", +"IDA", +"IF", +"IKE", +"ILL", +"INK", +"INN", +"IO", +"ION", +"IQ", +"IRA", +"IRE", +"IRK", +"IS", +"IT", +"ITS", +"IVY", +"JAB", +"JAG", +"JAM", +"JAN", +"JAR", +"JAW", +"JAY", +"JET", +"JIG", +"JIM", +"JO", +"JOB", +"JOE", +"JOG", +"JOT", +"JOY", +"JUG", +"JUT", +"KAY", +"KEG", +"KEN", +"KEY", +"KID", +"KIM", +"KIN", +"KIT", +"LA", +"LAB", +"LAC", +"LAD", +"LAG", +"LAM", +"LAP", +"LAW", +"LAY", +"LEA", +"LED", +"LEE", +"LEG", +"LEN", +"LEO", +"LET", +"LEW", +"LID", +"LIE", +"LIN", +"LIP", +"LIT", +"LO", +"LOB", +"LOG", +"LOP", +"LOS", +"LOT", +"LOU", +"LOW", +"LOY", +"LUG", +"LYE", +"MA", +"MAC", +"MAD", +"MAE", +"MAN", +"MAO", +"MAP", +"MAT", +"MAW", +"MAY", +"ME", +"MEG", +"MEL", +"MEN", +"MET", +"MEW", +"MID", +"MIN", +"MIT", +"MOB", +"MOD", +"MOE", +"MOO", +"MOP", +"MOS", +"MOT", +"MOW", +"MUD", +"MUG", +"MUM", +"MY", +"NAB", +"NAG", +"NAN", +"NAP", +"NAT", +"NAY", +"NE", +"NED", +"NEE", +"NET", +"NEW", +"NIB", +"NIL", +"NIP", +"NIT", +"NO", +"NOB", +"NOD", +"NON", +"NOR", +"NOT", +"NOV", +"NOW", +"NU", +"NUN", +"NUT", +"O", +"OAF", +"OAK", +"OAR", +"OAT", +"ODD", +"ODE", +"OF", +"OFF", +"OFT", +"OH", +"OIL", +"OK", +"OLD", +"ON", +"ONE", +"OR", +"ORB", +"ORE", +"ORR", +"OS", +"OTT", +"OUR", +"OUT", +"OVA", +"OW", +"OWE", +"OWL", +"OWN", +"OX", +"PA", +"PAD", +"PAL", +"PAM", +"PAN", +"PAP", +"PAR", +"PAT", +"PAW", +"PAY", +"PEA", +"PEG", +"PEN", +"PEP", +"PER", +"PET", +"PEW", +"PHI", +"PI", +"PIE", +"PIN", +"PIT", +"PLY", +"PO", +"POD", +"POE", +"POP", +"POT", +"POW", +"PRO", +"PRY", +"PUB", +"PUG", +"PUN", +"PUP", +"PUT", +"QUO", +"RAG", +"RAM", +"RAN", +"RAP", +"RAT", +"RAW", +"RAY", +"REB", +"RED", +"REP", +"RET", +"RIB", +"RID", +"RIG", +"RIM", +"RIO", +"RIP", +"ROB", +"ROD", +"ROE", +"RON", +"ROT", +"ROW", +"ROY", +"RUB", +"RUE", +"RUG", +"RUM", +"RUN", +"RYE", +"SAC", +"SAD", +"SAG", +"SAL", +"SAM", +"SAN", +"SAP", +"SAT", +"SAW", +"SAY", +"SEA", +"SEC", +"SEE", +"SEN", +"SET", +"SEW", +"SHE", +"SHY", +"SIN", +"SIP", +"SIR", +"SIS", +"SIT", +"SKI", +"SKY", +"SLY", +"SO", +"SOB", +"SOD", +"SON", +"SOP", +"SOW", +"SOY", +"SPA", +"SPY", +"SUB", +"SUD", +"SUE", +"SUM", +"SUN", +"SUP", +"TAB", +"TAD", +"TAG", +"TAN", +"TAP", +"TAR", +"TEA", +"TED", +"TEE", +"TEN", +"THE", +"THY", +"TIC", +"TIE", +"TIM", +"TIN", +"TIP", +"TO", +"TOE", +"TOG", +"TOM", +"TON", +"TOO", +"TOP", +"TOW", +"TOY", +"TRY", +"TUB", +"TUG", +"TUM", +"TUN", +"TWO", +"UN", +"UP", +"US", +"USE", +"VAN", +"VAT", +"VET", +"VIE", +"WAD", +"WAG", +"WAR", +"WAS", +"WAY", +"WE", +"WEB", +"WED", +"WEE", +"WET", +"WHO", +"WHY", +"WIN", +"WIT", +"WOK", +"WON", +"WOO", +"WOW", +"WRY", +"WU", +"YAM", +"YAP", +"YAW", +"YE", +"YEA", +"YES", +"YET", +"YOU", +"ABED", +"ABEL", +"ABET", +"ABLE", +"ABUT", +"ACHE", +"ACID", +"ACME", +"ACRE", +"ACTA", +"ACTS", +"ADAM", +"ADDS", +"ADEN", +"AFAR", +"AFRO", +"AGEE", +"AHEM", +"AHOY", +"AIDA", +"AIDE", +"AIDS", +"AIRY", +"AJAR", +"AKIN", +"ALAN", +"ALEC", +"ALGA", +"ALIA", +"ALLY", +"ALMA", +"ALOE", +"ALSO", +"ALTO", +"ALUM", +"ALVA", +"AMEN", +"AMES", +"AMID", +"AMMO", +"AMOK", +"AMOS", +"AMRA", +"ANDY", +"ANEW", +"ANNA", +"ANNE", +"ANTE", +"ANTI", +"AQUA", +"ARAB", +"ARCH", +"AREA", +"ARGO", +"ARID", +"ARMY", +"ARTS", +"ARTY", +"ASIA", +"ASKS", +"ATOM", +"AUNT", +"AURA", +"AUTO", +"AVER", +"AVID", +"AVIS", +"AVON", +"AVOW", +"AWAY", +"AWRY", +"BABE", +"BABY", +"BACH", +"BACK", +"BADE", +"BAIL", +"BAIT", +"BAKE", +"BALD", +"BALE", +"BALI", +"BALK", +"BALL", +"BALM", +"BAND", +"BANE", +"BANG", +"BANK", +"BARB", +"BARD", +"BARE", +"BARK", +"BARN", +"BARR", +"BASE", +"BASH", +"BASK", +"BASS", +"BATE", +"BATH", +"BAWD", +"BAWL", +"BEAD", +"BEAK", +"BEAM", +"BEAN", +"BEAR", +"BEAT", +"BEAU", +"BECK", +"BEEF", +"BEEN", +"BEER", +"BEET", +"BELA", +"BELL", +"BELT", +"BEND", +"BENT", +"BERG", +"BERN", +"BERT", +"BESS", +"BEST", +"BETA", +"BETH", +"BHOY", +"BIAS", +"BIDE", +"BIEN", +"BILE", +"BILK", +"BILL", +"BIND", +"BING", +"BIRD", +"BITE", +"BITS", +"BLAB", +"BLAT", +"BLED", +"BLEW", +"BLOB", +"BLOC", +"BLOT", +"BLOW", +"BLUE", +"BLUM", +"BLUR", +"BOAR", +"BOAT", +"BOCA", +"BOCK", +"BODE", +"BODY", +"BOGY", +"BOHR", +"BOIL", +"BOLD", +"BOLO", +"BOLT", +"BOMB", +"BONA", +"BOND", +"BONE", +"BONG", +"BONN", +"BONY", +"BOOK", +"BOOM", +"BOON", +"BOOT", +"BORE", +"BORG", +"BORN", +"BOSE", +"BOSS", +"BOTH", +"BOUT", +"BOWL", +"BOYD", +"BRAD", +"BRAE", +"BRAG", +"BRAN", +"BRAY", +"BRED", +"BREW", +"BRIG", +"BRIM", +"BROW", +"BUCK", +"BUDD", +"BUFF", +"BULB", +"BULK", +"BULL", +"BUNK", +"BUNT", +"BUOY", +"BURG", +"BURL", +"BURN", +"BURR", +"BURT", +"BURY", +"BUSH", +"BUSS", +"BUST", +"BUSY", +"BYTE", +"CADY", +"CAFE", +"CAGE", +"CAIN", +"CAKE", +"CALF", +"CALL", +"CALM", +"CAME", +"CANE", +"CANT", +"CARD", +"CARE", +"CARL", +"CARR", +"CART", +"CASE", +"CASH", +"CASK", +"CAST", +"CAVE", +"CEIL", +"CELL", +"CENT", +"CERN", +"CHAD", +"CHAR", +"CHAT", +"CHAW", +"CHEF", +"CHEN", +"CHEW", +"CHIC", +"CHIN", +"CHOU", +"CHOW", +"CHUB", +"CHUG", +"CHUM", +"CITE", +"CITY", +"CLAD", +"CLAM", +"CLAN", +"CLAW", +"CLAY", +"CLOD", +"CLOG", +"CLOT", +"CLUB", +"CLUE", +"COAL", +"COAT", +"COCA", +"COCK", +"COCO", +"CODA", +"CODE", +"CODY", +"COED", +"COIL", +"COIN", +"COKE", +"COLA", +"COLD", +"COLT", +"COMA", +"COMB", +"COME", +"COOK", +"COOL", +"COON", +"COOT", +"CORD", +"CORE", +"CORK", +"CORN", +"COST", +"COVE", +"COWL", +"CRAB", +"CRAG", +"CRAM", +"CRAY", +"CREW", +"CRIB", +"CROW", +"CRUD", +"CUBA", +"CUBE", +"CUFF", +"CULL", +"CULT", +"CUNY", +"CURB", +"CURD", +"CURE", +"CURL", +"CURT", +"CUTS", +"DADE", +"DALE", +"DAME", +"DANA", +"DANE", +"DANG", +"DANK", +"DARE", +"DARK", +"DARN", +"DART", +"DASH", +"DATA", +"DATE", +"DAVE", +"DAVY", +"DAWN", +"DAYS", +"DEAD", +"DEAF", +"DEAL", +"DEAN", +"DEAR", +"DEBT", +"DECK", +"DEED", +"DEEM", +"DEER", +"DEFT", +"DEFY", +"DELL", +"DENT", +"DENY", +"DESK", +"DIAL", +"DICE", +"DIED", +"DIET", +"DIME", +"DINE", +"DING", +"DINT", +"DIRE", +"DIRT", +"DISC", +"DISH", +"DISK", +"DIVE", +"DOCK", +"DOES", +"DOLE", +"DOLL", +"DOLT", +"DOME", +"DONE", +"DOOM", +"DOOR", +"DORA", +"DOSE", +"DOTE", +"DOUG", +"DOUR", +"DOVE", +"DOWN", +"DRAB", +"DRAG", +"DRAM", +"DRAW", +"DREW", +"DRUB", +"DRUG", +"DRUM", +"DUAL", +"DUCK", +"DUCT", +"DUEL", +"DUET", +"DUKE", +"DULL", +"DUMB", +"DUNE", +"DUNK", +"DUSK", +"DUST", +"DUTY", +"EACH", +"EARL", +"EARN", +"EASE", +"EAST", +"EASY", +"EBEN", +"ECHO", +"EDDY", +"EDEN", +"EDGE", +"EDGY", +"EDIT", +"EDNA", +"EGAN", +"ELAN", +"ELBA", +"ELLA", +"ELSE", +"EMIL", +"EMIT", +"EMMA", +"ENDS", +"ERIC", +"EROS", +"EVEN", +"EVER", +"EVIL", +"EYED", +"FACE", +"FACT", +"FADE", +"FAIL", +"FAIN", +"FAIR", +"FAKE", +"FALL", +"FAME", +"FANG", +"FARM", +"FAST", +"FATE", +"FAWN", +"FEAR", +"FEAT", +"FEED", +"FEEL", +"FEET", +"FELL", +"FELT", +"FEND", +"FERN", +"FEST", +"FEUD", +"FIEF", +"FIGS", +"FILE", +"FILL", +"FILM", +"FIND", +"FINE", +"FINK", +"FIRE", +"FIRM", +"FISH", +"FISK", +"FIST", +"FITS", +"FIVE", +"FLAG", +"FLAK", +"FLAM", +"FLAT", +"FLAW", +"FLEA", +"FLED", +"FLEW", +"FLIT", +"FLOC", +"FLOG", +"FLOW", +"FLUB", +"FLUE", +"FOAL", +"FOAM", +"FOGY", +"FOIL", +"FOLD", +"FOLK", +"FOND", +"FONT", +"FOOD", +"FOOL", +"FOOT", +"FORD", +"FORE", +"FORK", +"FORM", +"FORT", +"FOSS", +"FOUL", +"FOUR", +"FOWL", +"FRAU", +"FRAY", +"FRED", +"FREE", +"FRET", +"FREY", +"FROG", +"FROM", +"FUEL", +"FULL", +"FUME", +"FUND", +"FUNK", +"FURY", +"FUSE", +"FUSS", +"GAFF", +"GAGE", +"GAIL", +"GAIN", +"GAIT", +"GALA", +"GALE", +"GALL", +"GALT", +"GAME", +"GANG", +"GARB", +"GARY", +"GASH", +"GATE", +"GAUL", +"GAUR", +"GAVE", +"GAWK", +"GEAR", +"GELD", +"GENE", +"GENT", +"GERM", +"GETS", +"GIBE", +"GIFT", +"GILD", +"GILL", +"GILT", +"GINA", +"GIRD", +"GIRL", +"GIST", +"GIVE", +"GLAD", +"GLEE", +"GLEN", +"GLIB", +"GLOB", +"GLOM", +"GLOW", +"GLUE", +"GLUM", +"GLUT", +"GOAD", +"GOAL", +"GOAT", +"GOER", +"GOES", +"GOLD", +"GOLF", +"GONE", +"GONG", +"GOOD", +"GOOF", +"GORE", +"GORY", +"GOSH", +"GOUT", +"GOWN", +"GRAB", +"GRAD", +"GRAY", +"GREG", +"GREW", +"GREY", +"GRID", +"GRIM", +"GRIN", +"GRIT", +"GROW", +"GRUB", +"GULF", +"GULL", +"GUNK", +"GURU", +"GUSH", +"GUST", +"GWEN", +"GWYN", +"HAAG", +"HAAS", +"HACK", +"HAIL", +"HAIR", +"HALE", +"HALF", +"HALL", +"HALO", +"HALT", +"HAND", +"HANG", +"HANK", +"HANS", +"HARD", +"HARK", +"HARM", +"HART", +"HASH", +"HAST", +"HATE", +"HATH", +"HAUL", +"HAVE", +"HAWK", +"HAYS", +"HEAD", +"HEAL", +"HEAR", +"HEAT", +"HEBE", +"HECK", +"HEED", +"HEEL", +"HEFT", +"HELD", +"HELL", +"HELM", +"HERB", +"HERD", +"HERE", +"HERO", +"HERS", +"HESS", +"HEWN", +"HICK", +"HIDE", +"HIGH", +"HIKE", +"HILL", +"HILT", +"HIND", +"HINT", +"HIRE", +"HISS", +"HIVE", +"HOBO", +"HOCK", +"HOFF", +"HOLD", +"HOLE", +"HOLM", +"HOLT", +"HOME", +"HONE", +"HONK", +"HOOD", +"HOOF", +"HOOK", +"HOOT", +"HORN", +"HOSE", +"HOST", +"HOUR", +"HOVE", +"HOWE", +"HOWL", +"HOYT", +"HUCK", +"HUED", +"HUFF", +"HUGE", +"HUGH", +"HUGO", +"HULK", +"HULL", +"HUNK", +"HUNT", +"HURD", +"HURL", +"HURT", +"HUSH", +"HYDE", +"HYMN", +"IBIS", +"ICON", +"IDEA", +"IDLE", +"IFFY", +"INCA", +"INCH", +"INTO", +"IONS", +"IOTA", +"IOWA", +"IRIS", +"IRMA", +"IRON", +"ISLE", +"ITCH", +"ITEM", +"IVAN", +"JACK", +"JADE", +"JAIL", +"JAKE", +"JANE", +"JAVA", +"JEAN", +"JEFF", +"JERK", +"JESS", +"JEST", +"JIBE", +"JILL", +"JILT", +"JIVE", +"JOAN", +"JOBS", +"JOCK", +"JOEL", +"JOEY", +"JOHN", +"JOIN", +"JOKE", +"JOLT", +"JOVE", +"JUDD", +"JUDE", +"JUDO", +"JUDY", +"JUJU", +"JUKE", +"JULY", +"JUNE", +"JUNK", +"JUNO", +"JURY", +"JUST", +"JUTE", +"KAHN", +"KALE", +"KANE", +"KANT", +"KARL", +"KATE", +"KEEL", +"KEEN", +"KENO", +"KENT", +"KERN", +"KERR", +"KEYS", +"KICK", +"KILL", +"KIND", +"KING", +"KIRK", +"KISS", +"KITE", +"KLAN", +"KNEE", +"KNEW", +"KNIT", +"KNOB", +"KNOT", +"KNOW", +"KOCH", +"KONG", +"KUDO", +"KURD", +"KURT", +"KYLE", +"LACE", +"LACK", +"LACY", +"LADY", +"LAID", +"LAIN", +"LAIR", +"LAKE", +"LAMB", +"LAME", +"LAND", +"LANE", +"LANG", +"LARD", +"LARK", +"LASS", +"LAST", +"LATE", +"LAUD", +"LAVA", +"LAWN", +"LAWS", +"LAYS", +"LEAD", +"LEAF", +"LEAK", +"LEAN", +"LEAR", +"LEEK", +"LEER", +"LEFT", +"LEND", +"LENS", +"LENT", +"LEON", +"LESK", +"LESS", +"LEST", +"LETS", +"LIAR", +"LICE", +"LICK", +"LIED", +"LIEN", +"LIES", +"LIEU", +"LIFE", +"LIFT", +"LIKE", +"LILA", +"LILT", +"LILY", +"LIMA", +"LIMB", +"LIME", +"LIND", +"LINE", +"LINK", +"LINT", +"LION", +"LISA", +"LIST", +"LIVE", +"LOAD", +"LOAF", +"LOAM", +"LOAN", +"LOCK", +"LOFT", +"LOGE", +"LOIS", +"LOLA", +"LONE", +"LONG", +"LOOK", +"LOON", +"LOOT", +"LORD", +"LORE", +"LOSE", +"LOSS", +"LOST", +"LOUD", +"LOVE", +"LOWE", +"LUCK", +"LUCY", +"LUGE", +"LUKE", +"LULU", +"LUND", +"LUNG", +"LURA", +"LURE", +"LURK", +"LUSH", +"LUST", +"LYLE", +"LYNN", +"LYON", +"LYRA", +"MACE", +"MADE", +"MAGI", +"MAID", +"MAIL", +"MAIN", +"MAKE", +"MALE", +"MALI", +"MALL", +"MALT", +"MANA", +"MANN", +"MANY", +"MARC", +"MARE", +"MARK", +"MARS", +"MART", +"MARY", +"MASH", +"MASK", +"MASS", +"MAST", +"MATE", +"MATH", +"MAUL", +"MAYO", +"MEAD", +"MEAL", +"MEAN", +"MEAT", +"MEEK", +"MEET", +"MELD", +"MELT", +"MEMO", +"MEND", +"MENU", +"MERT", +"MESH", +"MESS", +"MICE", +"MIKE", +"MILD", +"MILE", +"MILK", +"MILL", +"MILT", +"MIMI", +"MIND", +"MINE", +"MINI", +"MINK", +"MINT", +"MIRE", +"MISS", +"MIST", +"MITE", +"MITT", +"MOAN", +"MOAT", +"MOCK", +"MODE", +"MOLD", +"MOLE", +"MOLL", +"MOLT", +"MONA", +"MONK", +"MONT", +"MOOD", +"MOON", +"MOOR", +"MOOT", +"MORE", +"MORN", +"MORT", +"MOSS", +"MOST", +"MOTH", +"MOVE", +"MUCH", +"MUCK", +"MUDD", +"MUFF", +"MULE", +"MULL", +"MURK", +"MUSH", +"MUST", +"MUTE", +"MUTT", +"MYRA", +"MYTH", +"NAGY", +"NAIL", +"NAIR", +"NAME", +"NARY", +"NASH", +"NAVE", +"NAVY", +"NEAL", +"NEAR", +"NEAT", +"NECK", +"NEED", +"NEIL", +"NELL", +"NEON", +"NERO", +"NESS", +"NEST", +"NEWS", +"NEWT", +"NIBS", +"NICE", +"NICK", +"NILE", +"NINA", +"NINE", +"NOAH", +"NODE", +"NOEL", +"NOLL", +"NONE", +"NOOK", +"NOON", +"NORM", +"NOSE", +"NOTE", +"NOUN", +"NOVA", +"NUDE", +"NULL", +"NUMB", +"OATH", +"OBEY", +"OBOE", +"ODIN", +"OHIO", +"OILY", +"OINT", +"OKAY", +"OLAF", +"OLDY", +"OLGA", +"OLIN", +"OMAN", +"OMEN", +"OMIT", +"ONCE", +"ONES", +"ONLY", +"ONTO", +"ONUS", +"ORAL", +"ORGY", +"OSLO", +"OTIS", +"OTTO", +"OUCH", +"OUST", +"OUTS", +"OVAL", +"OVEN", +"OVER", +"OWLY", +"OWNS", +"QUAD", +"QUIT", +"QUOD", +"RACE", +"RACK", +"RACY", +"RAFT", +"RAGE", +"RAID", +"RAIL", +"RAIN", +"RAKE", +"RANK", +"RANT", +"RARE", +"RASH", +"RATE", +"RAVE", +"RAYS", +"READ", +"REAL", +"REAM", +"REAR", +"RECK", +"REED", +"REEF", +"REEK", +"REEL", +"REID", +"REIN", +"RENA", +"REND", +"RENT", +"REST", +"RICE", +"RICH", +"RICK", +"RIDE", +"RIFT", +"RILL", +"RIME", +"RING", +"RINK", +"RISE", +"RISK", +"RITE", +"ROAD", +"ROAM", +"ROAR", +"ROBE", +"ROCK", +"RODE", +"ROIL", +"ROLL", +"ROME", +"ROOD", +"ROOF", +"ROOK", +"ROOM", +"ROOT", +"ROSA", +"ROSE", +"ROSS", +"ROSY", +"ROTH", +"ROUT", +"ROVE", +"ROWE", +"ROWS", +"RUBE", +"RUBY", +"RUDE", +"RUDY", +"RUIN", +"RULE", +"RUNG", +"RUNS", +"RUNT", +"RUSE", +"RUSH", +"RUSK", +"RUSS", +"RUST", +"RUTH", +"SACK", +"SAFE", +"SAGE", +"SAID", +"SAIL", +"SALE", +"SALK", +"SALT", +"SAME", +"SAND", +"SANE", +"SANG", +"SANK", +"SARA", +"SAUL", +"SAVE", +"SAYS", +"SCAN", +"SCAR", +"SCAT", +"SCOT", +"SEAL", +"SEAM", +"SEAR", +"SEAT", +"SEED", +"SEEK", +"SEEM", +"SEEN", +"SEES", +"SELF", +"SELL", +"SEND", +"SENT", +"SETS", +"SEWN", +"SHAG", +"SHAM", +"SHAW", +"SHAY", +"SHED", +"SHIM", +"SHIN", +"SHOD", +"SHOE", +"SHOT", +"SHOW", +"SHUN", +"SHUT", +"SICK", +"SIDE", +"SIFT", +"SIGH", +"SIGN", +"SILK", +"SILL", +"SILO", +"SILT", +"SINE", +"SING", +"SINK", +"SIRE", +"SITE", +"SITS", +"SITU", +"SKAT", +"SKEW", +"SKID", +"SKIM", +"SKIN", +"SKIT", +"SLAB", +"SLAM", +"SLAT", +"SLAY", +"SLED", +"SLEW", +"SLID", +"SLIM", +"SLIT", +"SLOB", +"SLOG", +"SLOT", +"SLOW", +"SLUG", +"SLUM", +"SLUR", +"SMOG", +"SMUG", +"SNAG", +"SNOB", +"SNOW", +"SNUB", +"SNUG", +"SOAK", +"SOAR", +"SOCK", +"SODA", +"SOFA", +"SOFT", +"SOIL", +"SOLD", +"SOME", +"SONG", +"SOON", +"SOOT", +"SORE", +"SORT", +"SOUL", +"SOUR", +"SOWN", +"STAB", +"STAG", +"STAN", +"STAR", +"STAY", +"STEM", +"STEW", +"STIR", +"STOW", +"STUB", +"STUN", +"SUCH", +"SUDS", +"SUIT", +"SULK", +"SUMS", +"SUNG", +"SUNK", +"SURE", +"SURF", +"SWAB", +"SWAG", +"SWAM", +"SWAN", +"SWAT", +"SWAY", +"SWIM", +"SWUM", +"TACK", +"TACT", +"TAIL", +"TAKE", +"TALE", +"TALK", +"TALL", +"TANK", +"TASK", +"TATE", +"TAUT", +"TEAL", +"TEAM", +"TEAR", +"TECH", +"TEEM", +"TEEN", +"TEET", +"TELL", +"TEND", +"TENT", +"TERM", +"TERN", +"TESS", +"TEST", +"THAN", +"THAT", +"THEE", +"THEM", +"THEN", +"THEY", +"THIN", +"THIS", +"THUD", +"THUG", +"TICK", +"TIDE", +"TIDY", +"TIED", +"TIER", +"TILE", +"TILL", +"TILT", +"TIME", +"TINA", +"TINE", +"TINT", +"TINY", +"TIRE", +"TOAD", +"TOGO", +"TOIL", +"TOLD", +"TOLL", +"TONE", +"TONG", +"TONY", +"TOOK", +"TOOL", +"TOOT", +"TORE", +"TORN", +"TOTE", +"TOUR", +"TOUT", +"TOWN", +"TRAG", +"TRAM", +"TRAY", +"TREE", +"TREK", +"TRIG", +"TRIM", +"TRIO", +"TROD", +"TROT", +"TROY", +"TRUE", +"TUBA", +"TUBE", +"TUCK", +"TUFT", +"TUNA", +"TUNE", +"TUNG", +"TURF", +"TURN", +"TUSK", +"TWIG", +"TWIN", +"TWIT", +"ULAN", +"UNIT", +"URGE", +"USED", +"USER", +"USES", +"UTAH", +"VAIL", +"VAIN", +"VALE", +"VARY", +"VASE", +"VAST", +"VEAL", +"VEDA", +"VEIL", +"VEIN", +"VEND", +"VENT", +"VERB", +"VERY", +"VETO", +"VICE", +"VIEW", +"VINE", +"VISE", +"VOID", +"VOLT", +"VOTE", +"WACK", +"WADE", +"WAGE", +"WAIL", +"WAIT", +"WAKE", +"WALE", +"WALK", +"WALL", +"WALT", +"WAND", +"WANE", +"WANG", +"WANT", +"WARD", +"WARM", +"WARN", +"WART", +"WASH", +"WAST", +"WATS", +"WATT", +"WAVE", +"WAVY", +"WAYS", +"WEAK", +"WEAL", +"WEAN", +"WEAR", +"WEED", +"WEEK", +"WEIR", +"WELD", +"WELL", +"WELT", +"WENT", +"WERE", +"WERT", +"WEST", +"WHAM", +"WHAT", +"WHEE", +"WHEN", +"WHET", +"WHOA", +"WHOM", +"WICK", +"WIFE", +"WILD", +"WILL", +"WIND", +"WINE", +"WING", +"WINK", +"WINO", +"WIRE", +"WISE", +"WISH", +"WITH", +"WOLF", +"WONT", +"WOOD", +"WOOL", +"WORD", +"WORE", +"WORK", +"WORM", +"WORN", +"WOVE", +"WRIT", +"WYNN", +"YALE", +"YANG", +"YANK", +"YARD", +"YARN", +"YAWL", +"YAWN", +"YEAH", +"YEAR", +"YELL", +"YOGA", +"YOKE" +}; + +/* Encode 8 bytes in 'c' as a string of English words. + * Returns a pointer to a static buffer + */ +char * +btoe(engout,c) +char *c, *engout; +{ + char cp[9]; /* add in room for the parity 2 bits*/ + int p,i ; + + engout[0] = '\0'; + memcpy(cp, c,8); + /* compute parity */ + for(p = 0,i = 0; i < 64;i += 2) + p += extract(cp,i,2); + + cp[8] = (char)p << 6; + strncat(engout,&Wp[extract(cp, 0,11)][0],4); + strcat(engout," "); + strncat(engout,&Wp[extract(cp,11,11)][0],4); + strcat(engout," "); + strncat(engout,&Wp[extract(cp,22,11)][0],4); + strcat(engout," "); + strncat(engout,&Wp[extract(cp,33,11)][0],4); + strcat(engout," "); + strncat(engout,&Wp[extract(cp,44,11)][0],4); + strcat(engout," "); + strncat(engout,&Wp[extract(cp,55,11)][0],4); +#ifdef notdef + printf("engout is %s\n\r",engout); +#endif + return(engout); +} + +/* convert English to binary + * returns 1 OK - all good words and parity is OK + * 0 word not in data base + * -1 badly formed in put ie > 4 char word + * -2 words OK but parity is wrong + */ +int +etob(out, e) +char *out; +char *e; +{ + char *word; + int i, p, v,l, low,high; + char b[9]; + char input[36]; + + if(e == NULL) + return -1; + + strncpy(input,e,sizeof(input)); + memset(b, 0, sizeof(b)); + memset(out, 0, 8); + for(i=0,p=0;i<6;i++,p+=11){ + if((word = strtok(i == 0 ? input : NULL," ")) == NULL) + return -1; + l = strlen(word); + if(l > 4 || l < 1){ + return -1; + } else if(l < 4){ + low = 0; + high = 570; + } else { + low = 571; + high = 2047; + } + standard(word); + if( (v = wsrch(word,low,high)) < 0 ) + return 0; + insert(b,v,p,11); + } + + /* now check the parity of what we got */ + for(p = 0, i = 0; i < 64; i +=2) + p += extract(b, i, 2); + + if( (p & 3) != extract(b, 64,2) ) + return -2; + + memcpy(out,b,8); + + return 1; +} +/* Display 8 bytes as a series of 16-bit hex digits */ +char * +put8(out,s) +char *out; +char *s; +{ + sprintf(out,"%02X%02X %02X%02X %02X%02X %02X%02X", + s[0] & 0xff,s[1] & 0xff,s[2] & 0xff, + s[3] & 0xff,s[4] & 0xff,s[5] & 0xff, + s[6] & 0xff,s[7] & 0xff); + return out; +} +#ifdef notdef +/* Encode 8 bytes in 'cp' as stream of ascii letters. + * Provided as a possible alternative to btoe() + */ +char * +btoc(cp) +char *cp; +{ + int i; + static char out[31]; + + /* code out put by characters 6 bits each added to 0x21 (!)*/ + for(i=0;i <= 10;i++){ + /* last one is only 4 bits not 6*/ + out[i] = '!'+ extract(cp,6*i,i >= 10 ? 4:6); + } + out[i] = '\0'; + return(out); +} +#endif + +/* Internal subroutines for word encoding/decoding */ + +/* Dictionary binary search */ +static int +wsrch(w,low,high) +char *w; +int low, high; +{ + int i,j; + + for(;;){ + i = (low + high)/2; + if((j = strncmp(w,Wp[i],4)) == 0) + return i; /* Found it */ + if(high == low+1){ + /* Avoid effects of integer truncation in /2 */ + if(strncmp(w,Wp[high],4) == 0) + return high; + else + return -1; + } + if(low >= high) + return -1; /* I don't *think* this can happen...*/ + if(j < 0) + high = i; /* Search lower half */ + else + low = i; /* Search upper half */ + } +} +static void +insert(s, x, start, length) +char *s; +int x; +int start, length; +{ + unsigned char cl; + unsigned char cc; + unsigned char cr; + unsigned long y; + int shift; + + assert(length <= 11); + assert(start >= 0); + assert(length >= 0); + assert(start +length <= 66); + + shift = ((8 -(( start + length) % 8))%8); + y = (long) x << shift; + cl = (y >> 16) & 0xff; + cc = (y >> 8) & 0xff; + cr = y & 0xff; + if(shift + length > 16){ + s[start /8] |= cl; + s[start/8 +1] |= cc; + s[start/8 +2] |= cr; + } else if(shift +length > 8){ + s[start/8] |= cc; + s[start/8 + 1] |= cr; + } else { + s[start/8] |= cr; + } +} + +static void +standard(word) +register char *word; +{ + while(*word){ + if(!isascii(*word)) + break; + if(islower(*word)) + *word = toupper(*word); + if(*word == '1') + *word = 'L'; + if(*word == '0') + *word = 'O'; + if(*word == '5') + *word = 'S'; + word++; + } +} + +/* Extract 'length' bits from the char array 's' starting with bit 'start' */ +static unsigned long +extract(s, start, length) +char *s; +int start, length; +{ + unsigned char cl; + unsigned char cc; + unsigned char cr; + unsigned long x; + + assert(length <= 11); + assert(start >= 0); + assert(length >= 0); + assert(start +length <= 66); + + cl = s[start/8]; + cc = s[start/8 +1]; + cr = s[start/8 +2]; + x = ((long)(cl<<8 | cc) <<8 | cr) ; + x = x >> (24 - (length + (start %8))); + x =( x & (0xffff >> (16-length) ) ); + return(x); +} + diff --git a/lib/libskey/skey.h b/lib/libskey/skey.h new file mode 100644 index 0000000000..15447131f0 --- /dev/null +++ b/lib/libskey/skey.h @@ -0,0 +1,42 @@ +#if defined(__TURBOC__) || defined(__STDC__) || defined(LATTICE) +#define ANSIPROTO 1 +#endif + +#ifndef __ARGS +#ifdef ANSIPROTO +#define __ARGS(x) x +#else +#define __ARGS(x) () +#endif +#endif + +/* Server-side data structure for reading keys file during login */ +struct skey { + FILE *keyfile; + char buf[256]; + char *logname; + int n; + char *seed; + char *val; + long recstart; /*needed so reread of buffer is efficient*/ + + +}; + +/* Client-side structure for scanning data stream for challenge */ +struct mc { + char buf[256]; + int skip; + int cnt; +}; + +void f __ARGS((char *x)); +int keycrunch __ARGS((char *result,char *seed,char *passwd)); +char *btoe __ARGS((char *engout,char *c)); +char *put8 __ARGS((char *out,char *s)); +int etob __ARGS((char *out,char *e)); +void rip __ARGS((char *buf)); +int skeychallenge __ARGS((struct skey *mp,char *name, char *challenge)); +int skeylookup __ARGS((struct skey *mp,char *name)); +int skeyverify __ARGS((struct skey *mp,char *response)); +int skeyverify __ARGS((struct skey *mp,char *response)); diff --git a/lib/libskey/skey_crypt.c b/lib/libskey/skey_crypt.c new file mode 100644 index 0000000000..ca1024f774 --- /dev/null +++ b/lib/libskey/skey_crypt.c @@ -0,0 +1,38 @@ +/* Author: Wietse Venema, Eindhoven University of Technology. */ + +#include +#include +#include + +#include "skey.h" + +/* skey_crypt - return encrypted UNIX passwd if s/key or regular password ok */ + +char *skey_crypt(pp, salt, pwd, pwok) +char *pp; +char *salt; +struct passwd *pwd; +int pwok; +{ + struct skey skey; + char *p; + char *crypt(); + + /* Try s/key authentication even when the UNIX password is permitted. */ + + if (pwd != 0 && skeylookup(&skey, pwd->pw_name) == 0 + && skeyverify(&skey, pp) == 0) { + /* s/key authentication succeeded */ + return (pwd->pw_passwd); + } + + /* When s/key authentication does not work, always invoke crypt(). */ + + p = crypt(pp, salt); + if (pwok && pwd != 0 && strcmp(p, pwd->pw_passwd) == 0) + return (pwd->pw_passwd); + + /* The user does not exist or entered bad input. */ + + return (":"); +} diff --git a/lib/libskey/skeylogin.c b/lib/libskey/skeylogin.c new file mode 100644 index 0000000000..bc49cb7152 --- /dev/null +++ b/lib/libskey/skeylogin.c @@ -0,0 +1,328 @@ +/* Login code for S/KEY Authentication. S/KEY is a trademark + * of Bellcore. + * + * Mink is the former name of the S/KEY authentication system. + * Many references for mink may still be found in this program. */ + +#include +#ifdef QUOTA +#include +#endif +#include +#include +#include + + +#include +#include +#include +#include +#include +#include +#include "skey.h" + +#define KEYFILE "/etc/skeykeys" + +char *skipspace(); +int skeylookup __ARGS((struct skey *mp,char *name)); + +#define setpriority(x,y,z) /* nothing */ + +/* Issue a skey challenge for user 'name'. If successful, + * fill in the caller's skey structure and return 0. If unsuccessful + * (e.g., if name is unknown) return -1. + * + * The file read/write pointer is left at the start of the + * record. + */ +int +getskeyprompt(mp,name,prompt) +struct skey *mp; +char *name; +char *prompt; +{ + int rval; + + sevenbit(name); + rval = skeylookup(mp,name); + strcpy(prompt,"s/key 55 latour1\n"); + switch(rval){ + case -1: /* File error */ + return -1; + case 0: /* Lookup succeeded, return challenge */ + sprintf(prompt,"s/key %d %s\n",mp->n - 1,mp->seed); + return 0; + case 1: /* User not found */ + fclose(mp->keyfile); + return -1; + } + return -1; /* Can't happen */ +} +/* Return a skey challenge string for user 'name'. If successful, + * fill in the caller's skey structure and return 0. If unsuccessful + * (e.g., if name is unknown) return -1. + * + * The file read/write pointer is left at the start of the + * record. + */ +int +skeychallenge(mp,name, ss) +struct skey *mp; +char *name; +char *ss; +{ + int rval; + + rval = skeylookup(mp,name); + switch(rval){ + case -1: /* File error */ + return -1; + case 0: /* Lookup succeeded, issue challenge */ + sprintf(ss, "s/key %d %s",mp->n - 1,mp->seed); + return 0; + case 1: /* User not found */ + fclose(mp->keyfile); + return -1; + } + return -1; /* Can't happen */ +} + +/* Find an entry in the One-time Password database. + * Return codes: + * -1: error in opening database + * 0: entry found, file R/W pointer positioned at beginning of record + * 1: entry not found, file R/W pointer positioned at EOF + */ +int +skeylookup(mp,name) +struct skey *mp; +char *name; +{ + int found; + int len; + long recstart; + char *cp; + struct stat statbuf; + + /* See if the KEYFILE exists, and create it if not */ + if(stat(KEYFILE,&statbuf) == -1 && errno == ENOENT){ + mp->keyfile = fopen(KEYFILE,"w+"); + (void) chmod(KEYFILE, 0644); + } else { + /* Otherwise open normally for update */ + mp->keyfile = fopen(KEYFILE,"r+"); + } + if(mp->keyfile == NULL) + return -1; + + /* Look up user name in database */ + len = strlen(name); + if( len > 8 ) len = 8; /* Added 8/2/91 - nmh */ + found = 0; + while(!feof(mp->keyfile)){ + recstart = ftell(mp->keyfile); + mp->recstart = recstart; + if(fgets(mp->buf,sizeof(mp->buf),mp->keyfile) != mp->buf){ + break; + } + rip(mp->buf); + if(mp->buf[0] == '#') + continue; /* Comment */ + if((mp->logname = strtok(mp->buf," \t")) == NULL) + continue; + if((cp = strtok(NULL," \t")) == NULL) + continue; + mp->n = atoi(cp); + if((mp->seed = strtok(NULL," \t")) == NULL) + continue; + if((mp->val = strtok(NULL," \t")) == NULL) + continue; + if(strlen(mp->logname) == len + && strncmp(mp->logname,name,len) == 0){ + found = 1; + break; + } + } + if(found){ + fseek(mp->keyfile,recstart,0); + return 0; + } else + return 1; +} +/* Verify response to a s/key challenge. + * + * Return codes: + * -1: Error of some sort; database unchanged + * 0: Verify successful, database updated + * 1: Verify failed, database unchanged + * + * The database file is always closed by this call. + */ +int +skeyverify(mp,response) +struct skey *mp; +char *response; +{ + struct timeval startval; + struct timeval endval; +long microsec; + char key[8]; + char fkey[8]; + char filekey[8]; + time_t now; + struct tm *tm; + char tbuf[27],buf[60]; + char me[80]; + int rval; + char *cp; + + time(&now); + tm = localtime(&now); + strftime(tbuf, sizeof(tbuf), " %b %d,%Y %T", tm); + + if(response == NULL){ + fclose(mp->keyfile); + return -1; + } + rip(response); + + /* Convert response to binary */ + if(etob(key,response) != 1 && atob8(key,response) != 0){ + /* Neither english words or ascii hex */ + fclose(mp->keyfile); + return -1; + } + + /* Compute fkey = f(key) */ + memcpy(fkey,key,sizeof(key)); + f(fkey); + /* in order to make the window of update as short as possible + we must do the comparison here and if OK write it back + other wise the same password can be used twice to get in + to the system + */ + + setpriority(PRIO_PROCESS, 0, -4); +/* + gettimeofday(&startval, (char *)0 ); +*/ + + /* reread the file record NOW*/ + + fseek(mp->keyfile,mp->recstart,0); + if(fgets(mp->buf,sizeof(mp->buf),mp->keyfile) != mp->buf){ + setpriority(PRIO_PROCESS, 0, 0); + fclose(mp->keyfile); + return -1; + } + rip(mp->buf); + mp->logname = strtok(mp->buf," \t"); + cp = strtok(NULL," \t") ; + mp->seed = strtok(NULL," \t"); + mp->val = strtok(NULL," \t"); + /* And convert file value to hex for comparison */ + atob8(filekey,mp->val); + + /* Do actual comparison */ + if(memcmp(filekey,fkey,8) != 0){ + /* Wrong response */ + setpriority(PRIO_PROCESS, 0, 0); + fclose(mp->keyfile); + return 1; + } + + /* Update key in database by overwriting entire record. Note + * that we must write exactly the same number of bytes as in + * the original record (note fixed width field for N) + */ + btoa8(mp->val,key); + mp->n--; + fseek(mp->keyfile,mp->recstart,0); + fprintf(mp->keyfile,"%s %04d %-16s %s %-21s\n",mp->logname,mp->n,mp->seed, + mp->val, tbuf); +/* +gettimeofday(&endval, (char *)0 ); + microsec = (endval.tv_sec - startval.tv_sec) * 1000000 + (endval.tv_usec - startval.tv_usec); +fprintf(stderr, "window= %d micro seconds \n" , microsec); +*/ + + + fclose(mp->keyfile); + + setpriority(PRIO_PROCESS, 0, 0); + return 0; +} + + +/* Convert 8-byte hex-ascii string to binary array + * Returns 0 on success, -1 on error + */ +atob8(out,in) +register char *out,*in; +{ + register int i; + register int val; + + if(in == NULL || out == NULL) + return -1; + + for(i=0;i<8;i++){ + if((in = skipspace(in)) == NULL) + return -1; + if((val = htoi(*in++)) == -1) + return -1; + *out = val << 4; + + if((in = skipspace(in)) == NULL) + return -1; + if((val = htoi(*in++)) == -1) + return -1; + *out++ |= val; + } + return 0; +} + +char * +skipspace(cp) +register char *cp; +{ + while(*cp == ' ' || *cp == '\t') + cp++; + + if(*cp == '\0') + return NULL; + else + return cp; +} + +/* Convert 8-byte binary array to hex-ascii string */ +int +btoa8(out,in) +register char *out,*in; +{ + register int i; + + if(in == NULL || out == NULL) + return -1; + + for(i=0;i<8;i++){ + sprintf(out,"%02x",*in++ & 0xff); + out += 2; + } + return 0; +} + + +/* Convert hex digit to binary integer */ +int +htoi(c) +register char c; +{ + if('0' <= c && c <= '9') + return c - '0'; + if('a' <= c && c <= 'f') + return 10 + c - 'a'; + if('A' <= c && c <= 'F') + return 10 + c - 'A'; + return -1; +} diff --git a/lib/libskey/skeysubr.c b/lib/libskey/skeysubr.c new file mode 100644 index 0000000000..9762f1a6c9 --- /dev/null +++ b/lib/libskey/skeysubr.c @@ -0,0 +1,225 @@ +#include +#include +#include +#ifdef __MSDOS__ +#include +#endif +#ifdef unix /* Assume POSIX */ +#include +#include +#endif +#include "md4.h" +#include "skey.h" + +#if (defined(__MSDOS__) || defined(MPU8086) || defined(MPU8080) \ + || defined(vax) || defined (MIPSEL)) +#define LITTLE_ENDIAN /* Low order bytes are first in memory */ +#endif /* Almost all other machines are big-endian */ + +/* Crunch a key: + * concatenate the seed and the password, run through MD4 and + * collapse to 64 bits. This is defined as the user's starting key. + */ +int +keycrunch(result,seed,passwd) +char *result; /* 8-byte result */ +char *seed; /* Seed, any length */ +char *passwd; /* Password, any length */ +{ + char *buf; + MDstruct md; + unsigned int buflen; +#ifndef LITTLE_ENDIAN + int i; + register long tmp; +#endif + + buflen = strlen(seed) + strlen(passwd); + if((buf = malloc(buflen+1)) == NULL) + return -1; + strcpy(buf,seed); + strcat(buf,passwd); + + /* Crunch the key through MD4 */ + sevenbit(buf); + MDbegin(&md); + MDupdate(&md,(unsigned char *)buf,8*buflen); + + free(buf); + + /* Fold result from 128 to 64 bits */ + md.buffer[0] ^= md.buffer[2]; + md.buffer[1] ^= md.buffer[3]; + +#ifdef LITTLE_ENDIAN + /* Only works on byte-addressed little-endian machines!! */ + memcpy(result,(char *)md.buffer,8); +#else + /* Default (but slow) code that will convert to + * little-endian byte ordering on any machine + */ + for(i=0;i<2;i++){ + tmp = md.buffer[i]; + *result++ = tmp; + tmp >>= 8; + *result++ = tmp; + tmp >>= 8; + *result++ = tmp; + tmp >>= 8; + *result++ = tmp; + } +#endif + + return 0; +} + +/* The one-way function f(). Takes 8 bytes and returns 8 bytes in place */ +void +f(x) +char *x; +{ + MDstruct md; +#ifndef LITTLE_ENDIAN + register long tmp; +#endif + + MDbegin(&md); + MDupdate(&md,(unsigned char *)x,64); + + /* Fold 128 to 64 bits */ + md.buffer[0] ^= md.buffer[2]; + md.buffer[1] ^= md.buffer[3]; + +#ifdef LITTLE_ENDIAN + /* Only works on byte-addressed little-endian machines!! */ + memcpy(x,(char *)md.buffer,8); + +#else + /* Default (but slow) code that will convert to + * little-endian byte ordering on any machine + */ + tmp = md.buffer[0]; + *x++ = tmp; + tmp >>= 8; + *x++ = tmp; + tmp >>= 8; + *x++ = tmp; + tmp >>= 8; + *x++ = tmp; + + tmp = md.buffer[1]; + *x++ = tmp; + tmp >>= 8; + *x++ = tmp; + tmp >>= 8; + *x++ = tmp; + tmp >>= 8; + *x = tmp; +#endif +} + +/* Strip trailing cr/lf from a line of text */ +void +rip(buf) +char *buf; +{ + char *cp; + + if((cp = strchr(buf,'\r')) != NULL) + *cp = '\0'; + + if((cp = strchr(buf,'\n')) != NULL) + *cp = '\0'; +} +/************************/ +#ifdef __MSDOS__ +char * +readpass(buf,n) +char *buf; +int n; +{ + int i; + char *cp; + + for(cp=buf,i = 0; i < n ; i++) + if ((*cp++ = bdos(7,0,0)) == '\r') + break; + *cp = '\0'; + printf("\n"); + rip(buf); + return buf; +} +#else +char * +readpass(buf,n) +char *buf; +int n; +{ + struct termios saved_ttymode; + struct termios noecho_ttymode; + + /* Save normal line editing modes */ + tcgetattr(0, &saved_ttymode); + + /* Turn off echoing */ + tcgetattr(0, &noecho_ttymode); + noecho_ttymode.c_lflag &= ~ECHO; + tcsetattr(0, TCSANOW, &noecho_ttymode); + fgets(buf,n,stdin); + rip(buf); + + /* Restore previous tty modes */ + tcsetattr(0, TCSANOW, &saved_ttymode); + + /* + after the secret key is taken from the keyboard, the line feed is + written to standard error instead of standard output. That means that + anyone using the program from a terminal won't notice, but capturing + standard output will get the key words without a newline in front of + them. + */ + fprintf(stderr, "\n"); + fflush(stderr); + sevenbit(buf); + + return buf; +} + +#endif + +/* removebackspaced over charaters from the string*/ +backspace(buf) +char *buf; +{ + char bs = 0x8; + char *cp = buf; + char *out = buf; + + while(*cp){ + if( *cp == bs ) { + if(out == buf){ + cp++; + continue; + } + else { + cp++; + out--; + } + } + else { + *out++ = *cp++; + } + + } + *out = '\0'; + +} +sevenbit(s) +char *s; +{ + /* make sure there are only 7 bit code in the line*/ + while(*s){ + *s = 0x7f & ( *s); + s++; + } +} diff --git a/libexec/ftpd/Makefile b/libexec/ftpd/Makefile index fe7ffd1a2c..91b1a731f5 100644 --- a/libexec/ftpd/Makefile +++ b/libexec/ftpd/Makefile @@ -2,16 +2,19 @@ PROG= ftpd -CFLAGS+=-I${.CURDIR}/../../usr.bin/ftp -DSETPROCTITLE -SRCS= ftpd.c ftpcmd.c glob.c logwtmp.c popen.c vers.c +CFLAGS+=-I${.CURDIR}/../../usr.bin/ftp -I${.CURDIR}/../../lib \ + -DSETPROCTITLE -DSKEY +SRCS= ftpd.c ftpcmd.c glob.c logwtmp.c popen.c vers.c skey-stuff.c MAN8= ftpd.8 CLEANFILES+=ftpcmd.c y.tab.h .PATH: ${.CURDIR}/../../usr.bin/ftp +DPADD+= /usr/lib/libskey.a +LDADD+= -lskey + .if exists(/usr/lib/libcrypt.a) DPADD+= ${LIBCRYPT} LDADD+= -lcrypt .endif - .include diff --git a/libexec/ftpd/ftpd.c b/libexec/ftpd/ftpd.c index 2638e8dd8b..701d2a29a8 100644 --- a/libexec/ftpd/ftpd.c +++ b/libexec/ftpd/ftpd.c @@ -144,6 +144,11 @@ char *LastArgv = NULL; /* end of argv */ char proctitle[BUFSIZ]; /* initial part of title */ #endif /* SETPROCTITLE */ +#ifdef SKEY +int pwok = 0; +char *skey_challenge(); +char *skey_crypt(); +#endif main(argc, argv, envp) int argc; char *argv[]; @@ -151,6 +156,9 @@ main(argc, argv, envp) { int addrlen, on = 1, tos; char *cp; +#ifdef SKEY + char addr_string[20]; /* XXX */ +#endif /* * LOG_NDELAY sets up the logging connection immediately, @@ -162,6 +170,10 @@ main(argc, argv, envp) syslog(LOG_ERR, "getpeername (%s): %m",argv[0]); exit(1); } +#ifdef SKEY + strcpy(addr_string, inet_ntoa(his_addr.sin_addr)); + pwok = authfile(addr_string); +#endif addrlen = sizeof (ctrl_addr); if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) { syslog(LOG_ERR, "getsockname (%s): %m",argv[0]); @@ -384,7 +396,11 @@ user(name) return; } } +#ifdef SKEY + reply(331, "%s", skey_challenge(name, pw, pwok)); +#else reply(331, "Password required for %s.", name); +#endif askpasswd = 1; /* * Delay before reading passwd after first failed @@ -448,7 +464,11 @@ pass(passwd) salt = "xx"; else salt = pw->pw_passwd; +#ifdef SKEY + xpasswd = skey_crypt(passwd, salt, pw, pwok); +#else xpasswd = crypt(passwd, salt); +#endif /* The strcmp does not catch null passwords! */ if (pw == NULL || *pw->pw_passwd == '\0' || strcmp(xpasswd, pw->pw_passwd)) { diff --git a/libexec/ftpd/skey-stuff.c b/libexec/ftpd/skey-stuff.c new file mode 100644 index 0000000000..c4e491266a --- /dev/null +++ b/libexec/ftpd/skey-stuff.c @@ -0,0 +1,23 @@ +/* Author: Wietse Venema, Eindhoven University of Technology. */ + +#include +#include + +#include "libskey/skey.h" + +/* skey_challenge - additional password prompt stuff */ + +char *skey_challenge(name, pwd, pwok) +char *name; +struct passwd *pwd; +int pwok; +{ + static char buf[128]; + struct skey skey; + + /* Display s/key challenge where appropriate. */ + + if (pwd == 0 || skeychallenge(&skey, pwd->pw_name, buf) != 0) + sprintf(buf, "Password required for %s.", name); + return (buf); +} diff --git a/libexec/rexecd/Makefile b/libexec/rexecd/Makefile index 093e9a6444..5916212ae5 100644 --- a/libexec/rexecd/Makefile +++ b/libexec/rexecd/Makefile @@ -2,6 +2,12 @@ PROG= rexecd MAN8= rexecd.8 +CFLAGS+= -DSKEY + +.PATH: ${.CURDIR}/../../usr.bin/key + +DPADD+= /usr/lib/libskey.a +LDADD+= -lskey .if exists(/usr/lib/libcrypt.a) DPADD+= ${LIBCRYPT} diff --git a/libexec/rexecd/rexecd.c b/libexec/rexecd/rexecd.c index a88f92b048..ff2704bafe 100644 --- a/libexec/rexecd/rexecd.c +++ b/libexec/rexecd/rexecd.c @@ -98,7 +98,13 @@ doit(f, fromp) struct sockaddr_in *fromp; { char cmdbuf[NCARGS+1], *cp, *namep; +#ifdef SKEY + char *skey_crypt(); + int permit_passwd = authfile(inet_ntoa(fromp->sin_addr)); + char user[16], pass[100]; +#else /* SKEY */ char user[16], pass[16]; +#endif /* SKEY */ struct passwd *pwd; int s; u_short port; @@ -154,7 +160,11 @@ doit(f, fromp) } endpwent(); if (*pwd->pw_passwd != '\0') { +#ifdef SKEY + namep = skey_crypt(pass, pwd->pw_passwd, pwd, permit_passwd); +#else /* SKEY */ namep = crypt(pass, pwd->pw_passwd); +#endif /* SKEY */ if (strcmp(namep, pwd->pw_passwd)) { error("Password incorrect.\n"); exit(1); diff --git a/share/man/man5/Makefile b/share/man/man5/Makefile index 4fe2321078..9f05dfcf8f 100644 --- a/share/man/man5/Makefile +++ b/share/man/man5/Makefile @@ -2,10 +2,10 @@ # Clean up and added pcfs, humm should pcfs be a subdir i386? MAN5= a.out.5 acct.5 core.5 dir.5 disktab.5 \ - fs.5 fstab.5 group.5 hosts.5 networks.5 \ - passwd.5 pcfs.5 phones.5 printcap.5 \ + fs.5 fstab.5 group.5 hosts.5 login.access.5 \ + networks.5 passwd.5 pcfs.5 phones.5 printcap.5 \ protocols.5 remote.5 resolver.5 services.5 \ - shells.5 stab.5 types.5 utmp.5 + shells.5 skey.access.5 stab.5 types.5 utmp.5 MLINKS= fs.5 inode.5 utmp.5 wtmp.5 utmp.5 lastlog.5 diff --git a/share/man/man5/login.access.5 b/share/man/man5/login.access.5 new file mode 100644 index 0000000000..effa71e153 --- /dev/null +++ b/share/man/man5/login.access.5 @@ -0,0 +1,45 @@ +.\" this is comment +.Dd April 30, 1994 +.Dt SKEY.ACCESS 5 +.Os FreeBSD 1.2 +.Sh NAME +.Nm login.access +.Nd Login access control table +.Sh DESCRIPTION +The +.Nm login.access +file specifies (user, host) combinations and/or (user, tty) +combinations for which a login will be either accepted or refused. +.Pp +When someone logs in, the +.Nm login.access +is scanned for the first entry that +matches the (user, host) combination, or, in case of non-networked +logins, the first entry that matches the (user, tty) combination. The +permissions field of that table entry determines whether the login will +be accepted or refused. +.Pp +Each line of the login access control table has three fields separated by a +":" character: permission : users : origins + +The first field should be a "+" (access granted) or "-" (access denied) +character. The second field should be a list of one or more login names +or wildcards. The third field should be a list of one or more tty +names (for non-networked logins), host names, domain names (begin with +"."), host addresses, internet network numbers (end with ".") or +wildcards. +.Pp +The following wildcards are supported: ALL matches any string; LOCAL +matches any string that does not contain a "." character. +.Sh FILES +.Bl -tag -width /etc/login.access -compact +.It Pa /etc/login.access +The +.Nm login.access +file resides in +.Pa /etc . +.El +.Sh SEE ALSO +.Xr login 1 +.Sh AUTHOR +Guido van Rooij diff --git a/share/man/man5/skey.access.5 b/share/man/man5/skey.access.5 new file mode 100644 index 0000000000..08924376da --- /dev/null +++ b/share/man/man5/skey.access.5 @@ -0,0 +1,34 @@ +.\" this is comment +.Dd April 30, 1994 +.Dt SKEY.ACCESS 5 +.Os FreeBSD 1.2 +.Sh NAME +.Nm skey.access +.Nd List of S/Key obligated host adresses +.Sh DESCRIPTION +The +.Nm skey.access +file contains a number of lines specifying host IP adresses +for which the use of S/Key passwords is obligated. +.Pp +The first word of each line says if UNIX passwords are +to be permitted or denied. When denied, only S/Key passwords +are allowed. +The remainder of the rule is a networknumber and mask. A rule matches a +host if any of its addresses satisfies: + network = (address & mask) +.Sh FILES +.Bl -tag -width /etc/skey.access -compact +.It Pa /etc/skey.access +The +.Nm skey.access +file resides in +.Pa /etc . +.El +.Sh SEE ALSO +.Xr skey 1 , +.Xr keyinit 1 , +.Xr key 1 , +.Xr keyinfo 1 +.Sh AUTHOR +Guido van Rooij diff --git a/usr.bin/Makefile b/usr.bin/Makefile index 4264e4caad..cde98226c4 100644 --- a/usr.bin/Makefile +++ b/usr.bin/Makefile @@ -5,16 +5,16 @@ SUBDIR= ar at basename bdes biff cal calendar cap_mkdb checknr chat chpass \ cksum cmp col colcrt colrm column comm compress crontab ctags cut \ dirname du elvis elvisrecover env error expand f2c false file find \ finger fmt fold fpr from fsplit fstat ftp getopt gprof groups head \ - hexdump id indent ipcrm ipcs join ktrace last lastcomm leave lex \ - locate lock logger login logname look lorder m4 machine mail make \ - mesg mkdep mkfifo mkstr more msgs mt netstat nfsstat nice nm nohup \ - pagesize passwd paste printenv printf quota ranlib rdist ref renice \ - rev rlogin rpcgen rpcinfo rsh rup ruptime rusers rwall rwho script \ - sed shar showmount size soelim split strings strip su symorder \ - syscons tail talk tcopy tee telnet tftp time tip tn3270 touch tput \ - tr true tset tsort tty ul uname unexpand unifdef uniq unvis users \ - uudecode uuencode vacation vgrind vi vis vmstat w wall wc what \ - whereis which who whoami whois window write xargs xinstall xstr \ - yacc yes + hexdump id indent ipcrm ipcs join key keyinfo keyinit ktrace last \ + lastcomm leave lex locate lock logger login logname look lorder \ + m4 machine mail make mesg mkdep mkfifo mkstr more msgs mt netstat \ + nfsstat nice nm nohup pagesize passwd paste printenv printf quota \ + ranlib rdist ref renice rev rlogin rpcgen rpcinfo rsh rup ruptime \ + rusers rwall rwho script sed shar showmount size soelim split \ + strings strip su symorder syscons tail talk tcopy tee telnet \ + tftp time tip tn3270 touch tput tr true tset tsort tty ul \ + uname unexpand unifdef uniq unvis users uudecode uuencode \ + vacation vgrind vi vis vmstat w wall wc what whereis which \ + who whoami whois window write xargs xinstall xstr yacc yes .include diff --git a/usr.bin/key/Makefile b/usr.bin/key/Makefile new file mode 100644 index 0000000000..b8553abe38 --- /dev/null +++ b/usr.bin/key/Makefile @@ -0,0 +1,21 @@ + +# @(#)Makefile 5.6 (Berkeley) 3/5/91 +# + +PROG= key +MAN1= key.1 skey.1 +CFLAGS+=-I${.CURDIR}/../../lib + + +DPADD= /usr/bin/libskey.a +LDADD= -lskey + +.if exists(/usr/lib/libcrypt.a) +DPADD+= ${LIBCRYPT} +LDADD+= -lcrypt +.endif + +SRCS= skey.c + +.include + diff --git a/usr.bin/key/README.WZV b/usr.bin/key/README.WZV new file mode 100644 index 0000000000..a13f3b5824 --- /dev/null +++ b/usr.bin/key/README.WZV @@ -0,0 +1,100 @@ +One of the nice things of S/Key is that it still leaves you the option +to use regular UNIX passwords. In fact, the presence of S/Key support +is completely invisible for a user until she has set up a password with +the keyinit command. You can permit regular UNIX passwords for local +logins, while at the same time insisting on S/Key passwords for logins +from outside. + +ORIGIN + +These files are modified versions of the s/key files found on +thumper.bellcore.com at 21 oct 1993. They have been fixed to +run on top of SunOS 4.1.3 and Solaris 2.3. + +Installation is described at the end of this file. + +USAGE + +Use the keyinit command to set up a new series of s/key passwords. + + wzv_6% keyinit + Updating wietse: + Old key: wz173500 + Reminder - Only use this method if you are direct connected. + If you are using telnet or dial-in exit with no password and use keyinit -s. + Enter secret password: + Again secret password: + + ID wietse s/key is 99 wz173501 + BLAH BLA BLAH BLAH BLAH BLA + +Be sure to make your secret password sufficiently long. Try using a +full sentence instead of just one single word. + +You will have to do a "keyinit" on every system that you want to login +on using one-time passwords. + +Whenever you log into an s/key protected system you will see +something like: + + login: wietse + s/key 98 wz173501 + Password: + +In this case you can either enter your regular UNIX password or +your one-time s/key password. For example, I open a local window +to compute the password: + + local% key 98 wz173501 + Reminder - Do not use key while logged in via telnet or rlogin. + Enter secret password: + BLAH BLA BLAH BLAH BLAH BLA + +The "BLAH BLA BLAH BLAH BLAH BLA" is the one-time s/key password. + +If you have to type the one-time password in by hand, it is convenient +to have echo turned on so that you can correct typing errors. Just type +a newline at the "Password:" prompt: + + login: wietse + s/key 98 wz173501 + Password: (turning echo on) + Password:BLAH BLA BLAH BLAH BLAH BLA + +The 98 in the challenge will be 97 the next time, and so on. You'll get +a warning when you are about to run out of s/key passwords, so that you +will have to run the keyinit command again. + +Sometimes it is more practical to carry a piece of paper with a small +series of one-time passwords. You can generate the list with: + + % key -n 10 98 wz173501 + 98: BLAH BLA BLAH BLAH BLAH BLA + 97: ... + 96: ... + +Be careful when printing material like this! + +INSTALLATION + +To install, do: make sunos4 (or whatever), then: make install. + +The UNIX password is always permitted with non-network logins. By +default, UNIX passwords are always permitted (the Bellcore code by +default disallows UNIX passwords but I think that is too painful). In +order to permit UNIX passwords only with logins from specific networks, +create a file /etc/skey.access. For example, + + # First word says if UNIX passwords are to be permitted or denied. + # remainder of the rule is a networknumber and mask. A rule matches a + # host if any of its addresses satisfies: + # + # network = (address & mask) + # + #what network mask + permit 131.155.210.0 255.255.255.0 + deny 0.0.0.0 0.0.0.0 + +This particular example will permit UNIX passwords with logins from any +host on network 131.155.210, but will insist on one-time passwords in +all other cases. diff --git a/usr.bin/key/key.1 b/usr.bin/key/key.1 new file mode 100644 index 0000000000..d9da463712 --- /dev/null +++ b/usr.bin/key/key.1 @@ -0,0 +1,49 @@ +.ll 6i +.pl 10.5i +.\" @(#)key.1 1.0 (Bellcore) 12/2/91 +.\" +.lt 6.0i +.TH KEY 1 "2 December 1991" +.AT 3 +.SH NAME +key \- Stand\-alone program for computing responses to S/Key challenges. +.SH SYNOPSIS +.B key [\-n ] +.SH DESCRIPTION +.I key +Takes the optional count of the number of one time access +passwords to print +along with a (maximum) sequence number and key as command line args, +it prompts for the user's secret password, and produces both word +and hex format responses. +.SH EXAMPLE +.sh + Usage example: +.sp 0 + >key \-n 5 99 th91334 +.sp 0 + Enter password: +.sp 0 + OMEN US HORN OMIT BACK AHOY +.sp 0 + .... 4 more passwords. +.sp 0 + > +.LP +.SH OPTIONS +.LP +.B \-n +the number of one time access passwords to print. +The default is one. +.SH DIAGNOSTICS +.SH BUGS +.LP +.SH SEE ALSO +.BR skey(1), +.BR keyinit(1), +.BR keysu(1), +.BR keyinfo(1) +.SH AUTHOR +Command by Phil Karn, Neil M. Haller, John S. Walden +.SH CONTACT +staff@thumper.bellcore.com diff --git a/usr.bin/key/skey.1 b/usr.bin/key/skey.1 new file mode 100644 index 0000000000..0a8b1b668a --- /dev/null +++ b/usr.bin/key/skey.1 @@ -0,0 +1,59 @@ +.ll 6i +.pl 10.5i +.\" @(#)skey.1 1.1 10/28/93 +.\" +.lt 6.0i +.TH KEY 1 "28 October 1993" +.AT 3 +.SH NAME +S/key \- A proceedure to use one time passwords for accessing computer systems. +.SH DESCRIPTION +.I S/key +is a proceedure for using one time password to authenticate access to +compter systems. It uses 64 bits of information transformed by the +MD4 algorithm. The user supplies the 64 bits in the form of 6 English +words that are generated by a secure computer. +Example use of the S/key program +.I key +.sp + Usage example: +.sp 0 + >key 99 th91334 +.sp 0 + Enter password: +.sp 0 + OMEN US HORN OMIT BACK AHOY +.sp 0 + > +.sp +The programs that are part of the S/Key system are keyinit, key, and +keyinfo. Keyinit is used to get your ID set up, key is +used to get the one time password each time, +keyinfo is used to extract information from the S/Key database. +.sp +When you run "keyinit" you inform the system of your +secret password. Running "key" then generates the +one-time passwords, and also requires your secret +password. If however, you misspell your password +while running "key", you will get a list of passwords +that will not work, and no indication about the problem. +.sp +Password sequence numbers count backward from 99. If you +don't know this, the syntax for "key" will be confusing. +.sp +You can enter the passwords using small letters, even +though the "key" program gives them in caps. +.sp +Macintosh and a general purpose PC use +are available. +.sp +Under FreeBSD, you can control, with /etc/skey.access, from which +hosts and/or networks the use of S/Key passwords is obligated. +.LP +.SH SEE ALSO +.BR keyinit(1), +.BR key(1), +.BR keyinfo(1) +.BR skey.access(5) +.SH AUTHOR +Phil Karn, Neil M. Haller, John S. Walden, Scott Chasin diff --git a/usr.bin/key/skey.c b/usr.bin/key/skey.c new file mode 100644 index 0000000000..e025312025 --- /dev/null +++ b/usr.bin/key/skey.c @@ -0,0 +1,128 @@ +/* Stand-alone program for computing responses to S/Key challenges. + * Takes the iteration count and seed as command line args, prompts + * for the user's key, and produces both word and hex format responses. + * + * Usage example: + * >skey 88 ka9q2 + * Enter password: + * OMEN US HORN OMIT BACK AHOY + * C848 666B 6435 0A93 + * > + */ +#include +#include +#include +#ifdef __MSDOS__ +#include +#else /* Assume BSD unix */ +#include +#endif +#include "libskey/md4.h" +#include "libskey/skey.h" + +char *readpass(); +void usage(); +int getopt(); +extern int optind; +extern char *optarg; + +int +main(argc,argv) +int argc; +char *argv[]; +{ + int n,cnt,i; + char passwd[256],passwd2[256]; + char key[8]; + char *seed; + char buf[33]; + char *slash; + + cnt = 1; + while((i = getopt(argc,argv,"n:")) != EOF){ + switch(i){ + case 'n': + cnt = atoi(optarg); + break; + } + } + /* could be in the form / */ + if(argc <= optind + 1){ + /*look for / in it */ + if(argc <= optind){ + usage(argv[0]); + return 1; + } + + slash = strchr(argv[optind], '/'); + if(slash == NULL){ + usage(argv[0]); + return 1; + } + *slash++ = '\0'; + seed = slash; + + if((n = atoi(argv[optind])) < 0){ + fprintf(stderr,"%s not positive\n",argv[optind]); + usage(argv[0]); + return 1; + } + } + else { + + if((n = atoi(argv[optind])) < 0){ + fprintf(stderr,"%s not positive\n",argv[optind]); + usage(argv[0]); + return 1; + } + seed = argv[++optind]; + } + fprintf(stderr,"Reminder - Do not use this program while logged in via telnet or rlogin.\n"); + + /* Get user's secret password */ + for(;;){ + fprintf(stderr,"Enter secret password: "); + readpass(passwd,sizeof(passwd)); + break; + /************ + fprintf(stderr,"Again secret password: "); + readpass(passwd2,sizeof(passwd)); + if(strcmp(passwd,passwd2) == 0) break; + fprintf(stderr, "Sorry no match\n"); + **************/ + + } + + /* Crunch seed and password into starting key */ + if(keycrunch(key,seed,passwd) != 0){ + fprintf(stderr,"%s: key crunch failed\n",argv[0]); + return 1; + } + if(cnt == 1){ + while(n-- != 0) + f(key); + printf("%s\n",btoe(buf,key)); +#ifdef HEXIN + printf("%s\n",put8(buf,key)); +#endif + } else { + for(i=0;i<=n-cnt;i++) + f(key); + for(;i<=n;i++){ +#ifdef HEXIN + printf("%d: %-29s %s\n",i,btoe(buf,key),put8(buf,key)); +#else + printf("%d: %-29s\n",i,btoe(buf,key)); +#endif + f(key); + } + } + return 0; +} +void +usage(s) +char *s; +{ + fprintf(stderr,"Usage: %s [-n count] [/] \n",s); +} + diff --git a/usr.bin/keyinfo/Makefile b/usr.bin/keyinfo/Makefile new file mode 100644 index 0000000000..41baee6e1d --- /dev/null +++ b/usr.bin/keyinfo/Makefile @@ -0,0 +1,9 @@ +# @(#)Makefile 5.5 (Berkeley) 7/1/90 + +MAN1= keyinfo.1 + +beforeinstall: + install -c -o ${BINOWN} -g ${BINGRP} -m ${BINMODE} \ + ${.CURDIR}/keyinfo.sh ${DESTDIR}${BINDIR}/keyinfo + +.include diff --git a/usr.bin/keyinfo/keyinfo.1 b/usr.bin/keyinfo/keyinfo.1 new file mode 100644 index 0000000000..b12aa962b9 --- /dev/null +++ b/usr.bin/keyinfo/keyinfo.1 @@ -0,0 +1,40 @@ +.ll 6i +.pl 10.5i +.\" @(#)keyinfo.1 1.1 (Bellcore) 7/20/93 +.\" +.lt 6.0i +.TH KEYINFO 1 "20 July 1993" +.AT 3 +.SH NAME +keyinfo \- display current S/Key sequence number and seed +.SH SYNOPSIS +.B keyinfo [username] +.SH DESCRIPTION +.I keyinfo +takes an optional user name and displays the user\'s current sequence +number and seed found in the S/Key database /etc/skeykeys. +.sp 1 +The command can be useful when generating a list of passwords for use +on a field trip, by combining with the command +.I key +in the form: +.sp + >key \-n `keyinfo`|lpr +.SH EXAMPLE +.sh +Usage example: +.sp 0 + >keyinfo +.sp 0 + 0098 ws91340 +.LP +.SH ARGUMENTS +.TP +.B username +The S/key user to display the information for. The default is +to display S/Key information on the user who invokes the command. +.SH SEE ALSO +.BR keyinit(1), +.BR key(1) +.SH AUTHOR +Command by Phil Karn, Neil M. Haller, John S. Walden diff --git a/usr.bin/keyinfo/keyinfo.sh b/usr.bin/keyinfo/keyinfo.sh new file mode 100644 index 0000000000..5879442db1 --- /dev/null +++ b/usr.bin/keyinfo/keyinfo.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# search /etc/skeykeys for the skey string for this user OR user specified +# in 1st parameter + +PATH=/bin:/usr/bin + +test -f /etc/skeykeys && { + WHO=${1-`id | sed 's/^[^(]*(\([^)]*\).*/\1/'`} + awk '/^'${WHO}'[ ]/ { print $2-1, $3 }' /etc/skeykeys +} diff --git a/usr.bin/keyinit/Makefile b/usr.bin/keyinit/Makefile new file mode 100644 index 0000000000..4c44d30f21 --- /dev/null +++ b/usr.bin/keyinit/Makefile @@ -0,0 +1,21 @@ + +# @(#)Makefile 5.6 (Berkeley) 3/5/91 +# + +PROG= keyinit +MAN1= keyinit.1 +CFLAGS+=-I${.CURDIR}/../../lib +DPADD= /usr/bin/libskey.a +LDADD= -lskey + +.if exists(/usr/lib/libcrypt.a) +DPADD+= ${LIBCRYPT} +LDADD+= -lcrypt +.endif + +SRCS= skeyinit.c + +BINOWN= root +BINMODE=4555 + +.include diff --git a/usr.bin/keyinit/keyinit.1 b/usr.bin/keyinit/keyinit.1 new file mode 100644 index 0000000000..2fe2d03944 --- /dev/null +++ b/usr.bin/keyinit/keyinit.1 @@ -0,0 +1,64 @@ +.ll 6i +.pl 10.5i +.\" @(#)keyinit.1 1.0 (Bellcore) 7/20/93 +.\" +.lt 6.0i +.TH KEYINIT 1 "20 July 1993" +.AT 3 +.SH NAME +keyinit \- Change password or add user to S/Key authentication system. +.SH SYNOPSIS +.B keyinit [\-s] [] +.SH DESCRIPTION +.I keyinit +initializes the system so you can use S/Key one-time passwords to +login. The program will ask you to enter a secret pass phrase; enter a +phrase of several words in response. After the S/Key database has been +updated you can login using either your regular UNIX password or using +S/Key one-time passwords. +.PP +When logging in from another machine you can avoid typing a real +password over the network, by typing your S/Key pass phrase to the +\fIkey\fR command on the local machine: the program will respond with +the one-time password that you should use to log into the remote +machine. This is most conveniently done with cut-and-paste operations +using a mouse. Alternatively, you can pre-compute one-time passwords +using the \fIkey\fR command and carry them with you on a piece of paper. +.PP +\fIkeyinit\fR requires you to type your secret password, so it should +be used only on a secure terminal. For example, on the console of a +workstation. If you are using \fIkeyinit\fR while logged in over an +untrusted network, follow the instructions given below with the \-s +option. +.SH OPTIONS +.IP \-s +Set secure mode where the user is expected to have used a secure +machine to generate the first one time password. Without the \-s the +system will assume you are direct connected over secure communications +and prompt you for your secret password. +The \-s option also allows one to set the seed and count for complete +control of the parameters. You can use keyinit -s in compination with +the +.I key +command to set the seed and count if you do not like the defaults. +To do this run keyinit in one window and put in your count and seed +then run key in another window to generate the correct 6 english words +for that count and seed. You can then +"cut" and "paste" them or copy them into the keyinit window. +.sp +.LP +.B +the ID for the user to be changed/added +.SH DIAGNOSTICS +.SH FILES +.TP +/etc/skeykeys data base of information for S/Key system. +.SH BUGS +.LP +.SH SEE ALSO +.BR skey(1), +.BR key(1), +.BR keysu(1), +.BR keyinfo(1) +.SH AUTHOR +Command by Phil Karn, Neil M. Haller, John S. Walden diff --git a/usr.bin/keyinit/skeyinit.c b/usr.bin/keyinit/skeyinit.c new file mode 100644 index 0000000000..d13bd6b0ba --- /dev/null +++ b/usr.bin/keyinit/skeyinit.c @@ -0,0 +1,195 @@ +/* change password or add user to S/KEY authentication system. + * S/KEY is a tradmark of Bellcore */ + +#include +#include +#include +#include "libskey/skey.h" +#include +#include + +extern int optind; +extern char *optarg; + +char * readpass(); + +int skeylookup __ARGS((struct skey *mp,char *name)); + +#define NAMELEN 2 +int +main(argc,argv) +int argc; +char *argv[]; +{ + struct skey skey; + int rval,n,nn,i,defaultsetup; + char seed[18],tmp[80],key[8]; + struct passwd *ppuser,*pp; + char defaultseed[17], passwd[256],passwd2[256] ; + + + time_t now; + struct tm *tm; + char tbuf[27],buf[60]; + char lastc, me[80]; + int l; + + time(&now); +#if 0 /* Choose a more random seed */ + tm = localtime(&now); + strftime(tbuf, sizeof(tbuf), "%M%j", tm); +#else + sprintf(tbuf, "%05ld", (long) (now % 100000)); +#endif + gethostname(defaultseed,NAMELEN); + strcpy(&defaultseed[NAMELEN],tbuf); + + pp = ppuser = getpwuid(getuid()); + strcpy(me,pp->pw_name); + defaultsetup = 1; + if( argc > 1){ + if(strcmp("-s", argv[1]) == 0) + defaultsetup = 0; + else + pp = getpwnam(argv[1]); + if(argc > 2) + pp = getpwnam(argv[2]); + + } + if(pp == NULL){ + printf("User unknown\n"); + return 1; + } + if(strcmp( pp->pw_name,me) != 0){ + if(getuid() != 0){ + /* Only root can change other's passwds */ + printf("Permission denied.\n"); + return(1); + } + } + + + + rval = skeylookup(&skey,pp->pw_name); + switch(rval){ + case -1: + perror("error in opening database"); + return 1; + case 0: + printf("Updating %s:\n",pp->pw_name); + printf("Old key: %s\n",skey.seed); + /* lets be nice if they have a skey.seed that ends in 0-8 just add one*/ + l = strlen(skey.seed); + if( l > 0){ + lastc = skey.seed[l-1]; + if( isdigit(lastc) && lastc != '9' ){ + strcpy(defaultseed, skey.seed); + defaultseed[l-1] = lastc + 1; + } + if( isdigit(lastc) && lastc == '9' && l < 16){ + strcpy(defaultseed, skey.seed); + defaultseed[l-1] = '0'; + defaultseed[l] = '0'; + defaultseed[l+1] = '\0'; + } + } + break; + case 1: + skey.val = 0; /* XXX */ + printf("Adding %s:\n",pp->pw_name); + break; + } + n = 99; + if( ! defaultsetup){ + printf("Reminder you need the 6 english words from the skey command.\n"); + for(i=0;;i++){ + if(i >= 2) exit(1); + printf("Enter sequence count from 1 to 10000: "); + fgets(tmp,sizeof(tmp),stdin); + n = atoi(tmp); + if(n > 0 && n < 10000) + break; /* Valid range */ + printf("Count must be > 0 and < 10000\n"); + } + } + if( !defaultsetup){ + printf("Enter new key [default %s]: ", defaultseed); + fflush(stdout); + fgets(seed,sizeof(seed),stdin); + rip(seed); + if(strlen(seed) > 16){ + printf("Seed truncated to 16 chars\n"); + seed[16] = '\0'; + } + if( seed[0] == '\0') strcpy(seed,defaultseed); + for(i=0;;i++){ + if(i >= 2) exit(1); + printf("s/key %d %s\ns/key access password: ",n,seed); + fgets(tmp,sizeof(tmp),stdin); + rip(tmp); + backspace(tmp); + if(tmp[0] == '?'){ + printf("Enter 6 English words from secure S/Key calculation.\n"); + continue; + } + if(tmp[0] == '\0'){ + exit(1); + } + if(etob(key,tmp) == 1 || atob8(key,tmp) == 0) + break; /* Valid format */ + printf("Invalid format, try again with 6 English words.\n"); + } + } else { + /* Get user's secret password */ + fprintf(stderr,"Reminder - Only use this method if you are directly connected.\n"); + fprintf(stderr,"If you are using telnet or rlogin exit with no password and use keyinit -s.\n"); + for(i=0;;i++){ + if(i >= 2) exit(1); + fprintf(stderr,"Enter secret password: "); + readpass(passwd,sizeof(passwd)); + if(passwd[0] == '\0'){ + exit(1); + } + fprintf(stderr,"Again secret password: "); + readpass(passwd2,sizeof(passwd)); + if(passwd2[0] == '\0'){ + exit(1); + } + if(strlen(passwd) < 4 && strlen(passwd2) < 4) { + fprintf(stderr, "Sorry your password must be longer\n\r"); + exit(1); + } + if(strcmp(passwd,passwd2) == 0) break; + fprintf(stderr, "Sorry no match\n"); + + + } + strcpy(seed,defaultseed); + + /* Crunch seed and password into starting key */ + if(keycrunch(key,seed,passwd) != 0){ + fprintf(stderr,"%s: key crunch failed\n",argv[0]); + return 1; + } + nn = n; + while(nn-- != 0) + f(key); + } + time(&now); + tm = localtime(&now); + strftime(tbuf, sizeof(tbuf), " %b %d,%Y %T", tm); + if (skey.val == NULL) + skey.val = (char *) malloc(16+1); + + + btoa8(skey.val,key); + fprintf(skey.keyfile,"%s %04d %-16s %s %-21s\n",pp->pw_name,n, + seed,skey.val, tbuf); + fclose(skey.keyfile); + printf("\nID %s s/key is %d %s\n",pp->pw_name,n,seed); + printf("%s\n",btoe(buf,key)); +#ifdef HEXIN + printf("%s\n",put8(buf,key)); +#endif + return 0; +} diff --git a/usr.bin/login/Makefile b/usr.bin/login/Makefile index 20ae828244..5ee16726fb 100644 --- a/usr.bin/login/Makefile +++ b/usr.bin/login/Makefile @@ -1,11 +1,14 @@ # @(#)Makefile 5.6 (Berkeley) 6/24/90 PROG= login -SRCS= klogin.c login.c -DPADD= ${LIBUTIL} -LDADD= -lutil +MAN5= login.access.5 +SRCS= klogin.c login.c login_access.c login_skey.c +DPADD= ${LIBUTIL} /usr/lib/libskey.a +LDADD= -lutil -lskey BINOWN= root BINMODE=4555 +#CFLAGS+=-DLOGIN_ACCESS -DSKEY -DLOGALL -I${.CURDIR}/../../lib +CFLAGS+=-DLOGIN_ACCESS -DSKEY -I${.CURDIR}/../../lib .if exists(${DESTDIR}/usr/lib/libcrypt.a) DPADD+= ${LIBCRYPT} diff --git a/usr.bin/login/README b/usr.bin/login/README new file mode 100644 index 0000000000..6ad7a10d47 --- /dev/null +++ b/usr.bin/login/README @@ -0,0 +1,10 @@ +This login has additional functionalities. They are all based on (part of) +Wietse Venema's logdaemon package. + + +The following defines can be used: +1) LOGIN_ACCESS to allow access control on a per tty/user combination +2) SKEY to allow the use of s/key one time passwords +3) LOGALL to log all logins + +-Guido diff --git a/usr.bin/login/login.access.5 b/usr.bin/login/login.access.5 new file mode 100644 index 0000000000..45719b9a6f --- /dev/null +++ b/usr.bin/login/login.access.5 @@ -0,0 +1,50 @@ +.\" this is comment +.Dd April 30, 1994 +.Dt SKEY.ACCESS 5 +.Os FreeBSD 1.2 +.Sh NAME +.Nm login.access +.Nd Login access control table +.Sh DESCRIPTION +The +.Nm login.access +file specifies (user, host) combinations and/or (user, tty) +combinations for which a login will be either accepted or refused. +.Pp +When someone logs in, the +.Nm login.access +is scanned for the first entry that +matches the (user, host) combination, or, in case of non-networked +logins, the first entry that matches the (user, tty) combination. The +permissions field of that table entry determines whether the login will +be accepted or refused. +.Pp +Each line of the login access control table has three fields separated by a +":" character: permission : users : origins + +The first field should be a "+" (access granted) or "-" (access denied) +character. The second field should be a list of one or more login names, +group names, or ALL (always matches). The third field should be a list +of one or more tty names (for non-networked logins), host names, domain +names (begin with "."), host addresses, internet network numbers (end +with "."), ALL (always matches) or LOCAL (matches any string that does +not contain a "." character). If you run NIS you can use @netgroupname +in host or user patterns. + +The EXCEPT operator makes it possible to write very compact rules. + +The group file is searched only when a name does not match that of the +logged-in user. Only groups are matched in which users are explicitly +listed: the program does not look at a user's primary group id value. + +.Bl -tag -width /etc/login.access -compact +.It Pa /etc/login.access +The +.Nm login.access +file resides in +.Pa /etc . +.El +.Sh SEE ALSO +.Xr login 1 +.Sh AUTHOR +Guido van Rooij diff --git a/usr.bin/login/login.c b/usr.bin/login/login.c index ad1d4d60ad..ab0adf8280 100644 --- a/usr.bin/login/login.c +++ b/usr.bin/login/login.c @@ -117,6 +117,10 @@ main(argc, argv) char *domain, *salt, *ttyn; char tbuf[MAXPATHLEN + 2], tname[sizeof(_PATH_TTY) + 10]; char localhost[MAXHOSTNAMELEN]; +#ifdef SKEY + int permit_passwd = 0; + char *skey_getpass(), *skey_crypt(); +#endif /* SKEY */ (void)signal(SIGALRM, timedout); (void)alarm((u_int)timeout); @@ -152,9 +156,11 @@ main(argc, argv) exit(1); } hflag = 1; +#ifndef SKEY if (domain && (p = index(optarg, '.')) && strcasecmp(p, domain) == 0) *p = 0; +#endif /* SKEY */ hostname = optarg; break; case 'p': @@ -190,6 +196,10 @@ main(argc, argv) else tty = ttyn; +#ifdef SKEY + permit_passwd = (hostname == 0 || authfile(hostname) != 0); +#endif + for (cnt = 0;; ask = 1) { if (ask) { fflag = 0; @@ -248,7 +258,11 @@ main(argc, argv) (void)setpriority(PRIO_PROCESS, 0, -4); +#ifdef SKEY + p = skey_getpass("Password:", pwd, permit_passwd); +#else p = getpass("Password:"); +#endif /* SKEY */ if (pwd) { #ifdef KERBEROS @@ -256,9 +270,19 @@ main(argc, argv) if (rval == 0) authok = 1; else if (rval == 1) +#ifdef SKEY + rval = strcmp(skey_crypt(p, salt, pwd, permit_passwd), + pwd->pw_passwd); +#else rval = strcmp(crypt(p, salt), pwd->pw_passwd); +#endif /* SKEY */ +#else +#ifdef SKEY + rval = strcmp(skey_crypt(p, salt, pwd, permit_passwd), + pwd->pw_passwd); #else rval = strcmp(crypt(p, salt), pwd->pw_passwd); +#endif /* SKEY */ #endif } bzero(p, strlen(p)); @@ -393,6 +417,18 @@ main(argc, argv) (void)printf("Warning: no Kerberos tickets issued.\n"); #endif +#ifdef LOGALL + /* + * Syslog each successful login, so we don't have to watch hundreds + * of wtmp or lastlogin files. + */ + if (hostname) { + syslog(LOG_INFO, "login from %s as %s", hostname, pwd->pw_name); + } else { + syslog(LOG_INFO, "login on %s as %s", tty, pwd->pw_name); + } +#endif + if (!quietlog) { (void)printf( "Copyright (c) 1980,1983,1986,1988,1990,1991 The Regents of the University\n%s", @@ -405,6 +441,19 @@ main(argc, argv) (st.st_mtime > st.st_atime) ? "new " : ""); } +#ifdef LOGIN_ACCESS + if (login_access(pwd->pw_name, hostname ? hostname : tty) == 0) { + printf("Permission denied\n"); + if (hostname) + syslog(LOG_NOTICE, "%s LOGIN REFUSED FROM %s", + pwd->pw_name, hostname); + else + syslog(LOG_NOTICE, "%s LOGIN REFUSED ON %s", + pwd->pw_name, tty); + sleepexit(1); + } +#endif + (void)signal(SIGALRM, SIG_DFL); (void)signal(SIGQUIT, SIG_DFL); (void)signal(SIGINT, SIG_DFL); diff --git a/usr.bin/login/login_access.c b/usr.bin/login/login_access.c new file mode 100644 index 0000000000..16a6ee6463 --- /dev/null +++ b/usr.bin/login/login_access.c @@ -0,0 +1,240 @@ + /* + * This module implements a simple but effective form of login access + * control based on login names and on host (or domain) names, internet + * addresses (or network numbers), or on terminal line names in case of + * non-networked logins. Diagnostics are reported through syslog(3). + * + * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. + */ + +#ifdef LOGIN_ACCESS +#ifndef lint +static char sccsid[] = "%Z% %M% %I% %E% %U%"; +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +extern struct group *getgrnam(); +extern int errno; + + /* Path name of the access control file. */ + +#ifndef TABLE +#define TABLE "/etc/login.access" +#endif + + /* Delimiters for fields and for lists of users, ttys or hosts. */ + +static char fs[] = ":"; /* field separator */ +static char sep[] = ", \t"; /* list-element separator */ + + /* Constants to be used in assignments only, not in comparisons... */ + +#define YES 1 +#define NO 0 + +static int list_match(); +static int user_match(); +static int from_match(); +static int string_match(); + +/* login_access - match username/group and host/tty with access control file */ + +login_access(user, from) +char *user; +char *from; +{ + FILE *fp; + char line[BUFSIZ]; + char *perm; /* becomes permission field */ + char *users; /* becomes list of login names */ + char *froms; /* becomes list of terminals or hosts */ + int match = NO; + int end; + int lineno = 0; /* for diagnostics */ + + /* + * Process the table one line at a time and stop at the first match. + * Blank lines and lines that begin with a '#' character are ignored. + * Non-comment lines are broken at the ':' character. All fields are + * mandatory. The first field should be a "+" or "-" character. A + * non-existing table means no access control. + */ + + if (fp = fopen(TABLE, "r")) { + while (!match && fgets(line, sizeof(line), fp)) { + lineno++; + if (line[end = strlen(line) - 1] != '\n') { + syslog(LOG_ERR, "%s: line %d: missing newline or line too long", + TABLE, lineno); + continue; + } + if (line[0] == '#') + continue; /* comment line */ + while (end > 0 && isspace(line[end - 1])) + end--; + line[end] = 0; /* strip trailing whitespace */ + if (line[0] == 0) /* skip blank lines */ + continue; + if (!(perm = strtok(line, fs)) + || !(users = strtok((char *) 0, fs)) + || !(froms = strtok((char *) 0, fs)) + || strtok((char *) 0, fs)) { + syslog(LOG_ERR, "%s: line %d: bad field count", TABLE, lineno); + continue; + } + if (perm[0] != '+' && perm[0] != '-') { + syslog(LOG_ERR, "%s: line %d: bad first field", TABLE, lineno); + continue; + } + match = (list_match(froms, from, from_match) + && list_match(users, user, user_match)); + } + (void) fclose(fp); + } else if (errno != ENOENT) { + syslog(LOG_ERR, "cannot open %s: %m", TABLE); + } + return (match == 0 || (line[0] == '+')); +} + +/* list_match - match an item against a list of tokens with exceptions */ + +static int list_match(list, item, match_fn) +char *list; +char *item; +int (*match_fn) (); +{ + char *tok; + int match = NO; + + /* + * Process tokens one at a time. We have exhausted all possible matches + * when we reach an "EXCEPT" token or the end of the list. If we do find + * a match, look for an "EXCEPT" list and recurse to determine whether + * the match is affected by any exceptions. + */ + + for (tok = strtok(list, sep); tok != 0; tok = strtok((char *) 0, sep)) { + if (strcasecmp(tok, "EXCEPT") == 0) /* EXCEPT: give up */ + break; + if (match = (*match_fn) (tok, item)) /* YES */ + break; + } + /* Process exceptions to matches. */ + + if (match != NO) { + while ((tok = strtok((char *) 0, sep)) && strcasecmp(tok, "EXCEPT")) + /* VOID */ ; + if (tok == 0 || list_match((char *) 0, item, match_fn) == NO) + return (match); + } + return (NO); +} + +/* netgroup_match - match group against machine or user */ + +static int netgroup_match(group, machine, user) +char *machine; +char *user; +{ +#ifdef NIS + static char *mydomain = 0; + + if (mydomain == 0) + yp_get_default_domain(&mydomain); + return (innetgr(group, machine, user, mydomain)); +#else + syslog(LOG_ERR, "NIS netgroup support not configured"); +#endif +} + +/* user_match - match a username against one token */ + +static int user_match(tok, string) +char *tok; +char *string; +{ + struct group *group; + int i; + + /* + * If a token has the magic value "ALL" the match always succeeds. + * Otherwise, return YES if the token fully matches the username, or if + * the token is a group that contains the username. + */ + + if (tok[0] == '@') { /* netgroup */ + return (netgroup_match(tok + 1, (char *) 0, string)); + } else if (string_match(tok, string)) { /* ALL or exact match */ + return (YES); + } else if (group = getgrnam(tok)) { /* try group membership */ + for (i = 0; group->gr_mem[i]; i++) + if (strcasecmp(string, group->gr_mem[i]) == 0) + return (YES); + } + return (NO); +} + +/* from_match - match a host or tty against a list of tokens */ + +static int from_match(tok, string) +char *tok; +char *string; +{ + int tok_len; + int str_len; + + /* + * If a token has the magic value "ALL" the match always succeeds. Return + * YES if the token fully matches the string. If the token is a domain + * name, return YES if it matches the last fields of the string. If the + * token has the magic value "LOCAL", return YES if the string does not + * contain a "." character. If the token is a network number, return YES + * if it matches the head of the string. + */ + + if (tok[0] == '@') { /* netgroup */ + return (netgroup_match(tok + 1, string, (char *) 0)); + } else if (string_match(tok, string)) { /* ALL or exact match */ + return (YES); + } else if (tok[0] == '.') { /* domain: match last fields */ + if ((str_len = strlen(string)) > (tok_len = strlen(tok)) + && strcasecmp(tok, string + str_len - tok_len) == 0) + return (YES); + } else if (strcasecmp(tok, "LOCAL") == 0) { /* local: no dots */ + if (strchr(string, '.') == 0) + return (YES); + } else if (tok[(tok_len = strlen(tok)) - 1] == '.' /* network */ + && strncmp(tok, string, tok_len) == 0) { + return (YES); + } + return (NO); +} + +/* string_match - match a string against one token */ + +static int string_match(tok, string) +char *tok; +char *string; +{ + + /* + * If the token has the magic value "ALL" the match always succeeds. + * Otherwise, return YES if the token fully matches the string. + */ + + if (strcasecmp(tok, "ALL") == 0) { /* all: always matches */ + return (YES); + } else if (strcasecmp(tok, string) == 0) { /* try exact match */ + return (YES); + } + return (NO); +} +#endif /* LOGIN_ACCES */ diff --git a/usr.bin/login/login_skey.c b/usr.bin/login/login_skey.c new file mode 100644 index 0000000000..066e00d5ab --- /dev/null +++ b/usr.bin/login/login_skey.c @@ -0,0 +1,105 @@ + /* Portions taken from the skey distribution on Oct 21 1993 */ +#ifdef SKEY +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libskey/skey.h" + +/* skey_getpass - read regular or s/key password */ + +char *skey_getpass(prompt, pwd, pwok) +char *prompt; +struct passwd *pwd; +int pwok; +{ + static char buf[128]; + struct skey skey; + char *cp; + void rip(); + struct termios saved_ttymode; + struct termios noecho_ttymode; + char *username = pwd ? pwd->pw_name : "nope"; + int sflag; + + /* Attempt an s/key challenge. */ + + if ((sflag = skeychallenge(&skey, username, buf)) == 0) { + printf("%s\n", buf); + } + if (!pwok) { + printf("(s/key required)\n"); + } + fputs(prompt, stdout); + fflush(stdout); + + /* Save current input modes and turn echo off. */ + + tcgetattr(0, &saved_ttymode); + tcgetattr(0, &noecho_ttymode); + noecho_ttymode.c_lflag &= ~ECHO; + tcsetattr(0, TCSANOW, &noecho_ttymode); + + /* Read password. */ + + buf[0] = 0; + fgets(buf, sizeof(buf), stdin); + rip(buf); + + /* Restore previous input modes. */ + + tcsetattr(0, TCSANOW, &saved_ttymode); + + /* Give S/Key users a chance to do it with echo on. */ + + if (sflag == 0 && feof(stdin) == 0 && buf[0] == 0) { + fputs(" (turning echo on)\n", stdout); + fputs(prompt, stdout); + fflush(stdout); + fgets(buf, sizeof(buf), stdin); + rip(buf); + } else { + putchar('\n'); + } + return (buf); +} + +/* skey_crypt - return encrypted UNIX passwd if s/key or regular password ok */ + +char *skey_crypt(pp, salt, pwd, pwok) +char *pp; +char *salt; +struct passwd *pwd; +int pwok; +{ + struct skey skey; + char *p; + char *crypt(); + + /* Try s/key authentication even when the UNIX password is permitted. */ + + if (pwd != 0 && skeylookup(&skey, pwd->pw_name) == 0 + && skeyverify(&skey, pp) == 0) { + /* s/key authentication succeeded */ + if (skey.n < 5) + printf("Warning! Change s/key password soon\n"); + return (pwd->pw_passwd); + } + + /* When s/key authentication does not work, always invoke crypt(). */ + + p = crypt(pp, salt); + if (pwok && pwd != 0 && strcmp(p, pwd->pw_passwd) == 0) + return (pwd->pw_passwd); + + /* The user does not exist or entered bad input. */ + + return (":"); +} +#endif SKEY