allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc) (04/24/89)
Posting-number: Volume 6, Issue 91 Submitted-by: ut-emx!clyde@cs.utexas.edu (Head UNIX Hacquer) Archive-name: chkpwd Enclosed is some code that I worked up to do some password checking. I hope it can be useful to others. # This is a shell archive. # Remove everything above and including the cut line. # Then run the rest of the file through sh. -----cut here-----cut here-----cut here-----cut here----- #!/bin/sh # shar: Shell Archiver # Run the following text with /bin/sh to create: # README # Makefile # checkpasswd.h # checkpasswd.c # makedict.c # viewdict.c # wormwords # This archive created: Mon Jan 9 13:27:05 1989 # By: Head UNIX Hacquer (Moose & Squirrel Software) echo shar: extracting README '(1040 characters)' sed 's/^XX//' << \SHAR_EOF > README XX XXThis is a password checking program that I wrote after the infamous Internet XXworm. I used the password cracking algorithim the worm used in order XXto check the obviousness of a password. XX XXThe routine checkpasswd.c does this. Read the source for the routine XXfor details of how it works. In this package are the following: XX XXREADME This file XXMakefile Q.E.D. XXcheckpasswd.c Password check routine XXcheckpasswd.h Include for checkpasswd calling XXdriver.c Test program XXmakedict.c Program to build dbm dictionary XXwormwords The infamous worm password list XXviewdict.c Program to view dbm dictionary XX XXI hope this can be a kernel of intelligent password checking for 4BSD systems. XX XXI am working on a replacement for the BSD passwd program, which should be XXavailable soon. XX XXContents copyright 1989 Clyde W. Hoover (Moose & Squirrel Software, NotInc.) XX XXDistribution unlimited provided copyright notice is retained. XXPlease spread this around as much as useful. XX XX Clyde Hoover XX Computation Center XX The University of Texas at Austin XX clyde@emx.utexas.edu SHAR_EOF if test 1040 -ne "`wc -c README`" then echo shar: error transmitting README '(should have been 1040 characters)' fi echo shar: extracting Makefile '(670 characters)' sed 's/^XX//' << \SHAR_EOF > Makefile XX# XX# Make password checking routines and test program XX XX# XX# The checkpasswd routine depends upon the existence of the ndbm XX# routines in the 4.3BSD libc. Changes will have to be made to XX# checkpasswd.c if these are not available. XX# XXCFLAGS = -g XX XXpwtest: driver.o checkpasswd.o XX cc $(CFLAGS) -o pwtest driver.o checkpasswd.o XX XXcheckpasswd.o: checkpasswd.c checkpasswd.h XX cc $(CFLAGS) -c checkpasswd.c XX XXmakedict: makedict.c XX cc $(CFLAGS) -o makedict makedict.c XX XXviewdict: viewdict.c XX cc $(CFLAGS) -o viewdict viewdict.c XX XXpwdict: wormlist makedict XX -rm -f pwdict XX cat /usr/dict/words wormlist | makedict pwdict XX @echo install pwdict to where PWDICT points to in checkpasswd.c SHAR_EOF if test 670 -ne "`wc -c Makefile`" then echo shar: error transmitting Makefile '(should have been 670 characters)' fi echo shar: extracting checkpasswd.h '(1399 characters)' sed 's/^XX//' << \SHAR_EOF > checkpasswd.h XX/* XX * passwdcheck.h XX * XX * Copyright 1989 Clyde W. Hoover (Moose & Squirrel Software, NotInc.) XX * XX */ XX XX#define EXPORT /**/ XX#define IMPORT extern XX XX#define PWCK_FAIL -1 XX#define PWCK_OK 0 XX#define PWCK_NULL 1 XX#define PWCK_OBVIOUS 2 XX#define PWCK_FINGER 3 XX#define PWCK_INDICT 4 XX#define PWCK_ILLCHAR 5 XX#define PWCK_SHORT 6 XX XX/* XX * Return codes for checkpassword() are: XX * XX * PWCK_OK if <password> is ok to use XX * PWCK_FAIL if something failed during the check process XX * PWCK_NULL if <password> is the null string XX * PWCK_OBVIOUS if <password> is too "obvious" XX * (equals login name, host name, 'zzzz' ) XX * PWCK_FINGER if <password> is in the users' passwd/finger info XX * PWCK_INDICT if <password> is in the dictionaries checked XX * PWCK_ILLCHAR if <password> is lexically illegal XX * PWCK_SHORT if <password> is too short XX */ XX XX/* XX * Password checking peference block XX */ XXstruct pwck_preferences { XX char OneCaseOk, /* Are single-case pwds acceptable */ XX CtrlOk, /* Are control characters acceptable */ XX CharRunLen, /* How long can run of characters be */ XX MinPwLen; /* Minimum password length */ XX char *EgrepPath; /* Path to the 'egrep' program */ XX}; XX XX/* XX * Preference list XX */ XXIMPORT struct pwck_preferences pwck_preferences; XX XX/* XX * List of control characters not allowed in passwords XX */ XXIMPORT char pwck_illegalcc[]; XX XX/* XX * List of dictionaries to check XX */ XXIMPORT char *pwck_dicitonaries[]; XX SHAR_EOF if test 1399 -ne "`wc -c checkpasswd.h`" then echo shar: error transmitting checkpasswd.h '(should have been 1399 characters)' fi echo shar: extracting checkpasswd.c '(13550 characters)' sed 's/^XX//' << \SHAR_EOF > checkpasswd.c XX/* XX * checkpasswd.c - Login password check routines. XX * XX * Perform various sanity and security checks on a password candidate. XX * XX * Written December, 1988 XX * XX * Copyright 1989 Clyde W. Hoover (Moose & Squirrel Software, NotInc.) XX * XX * Clyde Hoover XX * Computation Center XX * The University of Texas at Austin XX * Austin, Texas XX * clyde@emx.utexas.edu XX */ XX XX#ifndef lint XXstatic char sccsid[] = "%W% %G% (cc.utexas.edu) %P%"; XX#endif XX XX#include <sys/types.h> XX#include <strings.h> XX#include <ctype.h> XX XX#define LOCAL static XX XX#include "checkpasswd.h" XX XX/* XX * Special string compare defines. XX */ XX#define try(P,C,V) { if (cistrcmp(P,C) == 0) return(V); } XX#define mtry(P,C,V) { int i; if ((i = StrAllCmp(P,C,V)) != PWCK_OK) return(i); } XX XX/* XX * Table of operational parameter preferences. XX * May be modified by the caller. XX */ XXEXPORT XXstruct pwck_preferences pwck_preferences = { XX 1, /* single-case pwds ok */ XX 1, /* control chars ok */ XX 3, /* dup length = 3 */ XX 4, /* minimum length */ XX "PATH=/bin:/usr/bin:/usr/ucb; egrep" /* How to find egrep */ XX}; XX XX/* XX * Table of control chars best avoided - XX * mostly commonly-used terminal special chars. XX * May be modified by the caller. XX */ XX#define ctrl(d) ('d' & 037) XX XXEXPORT XXchar pwck_illegalcc[128] = { XX ctrl(c), ctrl(d), ctrl(h), /* ctrl(i),*/ ctrl(j), ctrl(m), XX ctrl(o), ctrl(r), ctrl(s), ctrl(q), ctrl(z), ctrl(\\), XX ctrl([), /* escape */ XX '\0177', /* rubout */ XX 0 XX}; XX XX#define PWDICT "/usr/dict/pwwords" XX/* XX * List of forbidden password dictionaries to look in. XX * May be modified by the caller. XX */ XXstatic char *pwck_dicitonaries[16] = { XX PWDICT, /* illegal passwords list */ XX 0 XX}; XX XX/* XX * The 'pwck_*' routines all use the PWCK_* return XX * codes, which are then propigated up to the caller of checkpassword(). XX * XX * All pwck_* routines in the table below are called thusly: XX * pwck_*(password, userid) XX * password = plaintext password string to test. XX * userid = the user id which wants to use <password>. XX * XX * Table of check functions to be called by checkpassword() XX */ XXint pwck_lexical(), pwck_local(), pwck_passwd(), pwck_dictionary(); XX XXtypedef int (*function)(); XX XXLOCAL XXfunction pwck_vector[] = { XX pwck_lexical, XX pwck_local, XX pwck_passwd, XX pwck_dictionary, XX 0 XX}; XX XX/* ------------------------------------------------------------------- */ XX XX/* XX * checkpassword - Password candidate sanity checker. XX * XX * Arguments; XX * password = plain text password string to check. XX * userid = the uid whom the password is for, -1 to disable. XX * XX * Returns: XX * PWCK_OK if <password> is ok to use. XX * PWCK_FAIL if something failed during the check process. XX * PWCK_NULL if <password> is the null string XX * PWCK_OBVIOUS if <password> is too "obvious". XX * (equals login name, host name, 'zzzz' ). XX * PWCK_FINGER if <password> is in the users' passwd/finger info. XX * PWCK_INDICT if <password> is in the dictionaries checked. XX * PWCK_ILLCHAR if <password> is lexically illegal. XX * PWCK_SHORT if <password> is too short. XX * XX */ XXcheckpassword(password, userid) XXchar *password; /* Plaintext of password to check */ XXint userid; /* The user this is for */ XX{ XX int rcode; /* General purpose scratch */ XX function *checkfunc; /* Check function pointer */ XX XX if (password == 0 || *password == 0) XX return(PWCK_NULL); /* Null password */ XX XX for (checkfunc = pwck_vector; *checkfunc; checkfunc++) { XX if ((rcode = (*checkfunc)(password, userid)) != PWCK_OK) XX return(rcode); XX } XX return(PWCK_OK); XX} XX XX XX/* ------------------------------------------------------------------- */ XX XX#define P_U 0x1 /* Upper case in password */ XX#define P_L 0x2 /* Lower case in password */ XX#define P_C 0x4 /* Control chars in password */ XX#define P_D 0x8 /* Digits in password */ XX#define P_P 0x10 /* Punctutation chars in password */ XX XX#define hasone(P) (what |= (P)) XX#define hasany(P) ((what & (P)) == (P)) XX XX#define ccok pwck_preferences.CtrlOk XX#define mcok pwck_preferences.OneCaseOk XX#define runl pwck_preferences.CharRunLen XX#define minl pwck_preferences.MinPwLen XX XX/* XX * pwck_lexical - Perform lexical analysis of password candidate. XX * XX * Things which are ok: XX * Mixed case XX * Digits XX * Punctutation XX * Control characters (except for those in the forbidden table) XX * (controlled by the preferences) XX * XX * Things which are NOT ok: XX * Passwords less that 'minl' length XX * Runs of more than <runl> of the same character (e.g. 'zzz') XX * Single-case strings XX * (controlled by the preferences) XX * XX * Things not checked for: XX * Cycles of character groups (e.g. 'aabbcc' or 'ababab') XX */ XXstatic int XXpwck_lexical(password, userid) XXchar *password; XXint userid; /* NOTUSED */ XX{ XX int rc; /* Duplicate character run count */ XX char *p = password; /* Scratch */ XX char what = 0, /* Lexical analysis result flags */ XX last = 0; /* Last character seen (for run checks) */ XX XX if (minl && strlen(password) < minl) XX return(PWCK_SHORT); XX XX for (p = password; *p; p++) { XX if (*p != last) { XX last = *p; XX rc = 0; XX } XX else { /* Run of same characters */ XX if (runl && ++rc >= runl) XX return(PWCK_OBVIOUS); XX } XX if (*p < ' ') { /* Control character */ XX if (!ccok) XX return(PWCK_ILLCHAR); XX if (index (pwck_illegalcc, *p)) XX return(PWCK_ILLCHAR); XX hasone(P_C); XX } XX else if (isupper(*p)) hasone(P_U); XX else if (islower(*p)) hasone(P_L); XX else if (ispunct(*p)) hasone(P_P); XX else if (isdigit(*p)) hasone(P_D); XX } XX if (hasany(P_U | P_L)) return(PWCK_OK); /* UC+lc */ XX if (hasany(P_D)) return(PWCK_OK); /* Numbers */ XX if (hasany(P_P)) return(PWCK_OK); /* Punctutation chars */ XX if (hasany(P_C)) return(PWCK_OK); /* Control chars */ XX /* XX * Check for mono-case passwords XX */ XX if (!hasany(P_U) && mcok) /* All lower case alpha */ XX return(PWCK_OK); XX if (!hasany(P_L) && mcok) /* All upper case alpha */ XX return(PWCK_OK); XX XX return(PWCK_ILLCHAR); XX} XX#undef hasone XX#undef hasany XX XX/* XX * pwck_local - Perform 'local' password checks. XX * XX * Returns: XX * PWCK_OBVIOUS if <password> == hostname XX * PWCK_OK if otherwise XX */ XXLOCAL int XXpwck_local(password, userid) XXchar *password; XXint userid; /* NOTUSED */ XX{ XX char myname[32]; /* Scratch */ XX XX (void) gethostname(myname, sizeof(myname)); XX try(password, myname, PWCK_OBVIOUS); XX /* XX * Want to try full canoncalized hostname here in case gethostname XX * didn't get that for us. XX * XX * Then look in users' .rhosts and try those strings (maybe) XX */ XX return(PWCK_OK); XX} XX XX/* XX * pwck_dictionary - Look in the forbidden password dictionaries. XX * XX * Returns: XX * PWCK_INDICT if <password> was in any dictionary XX * PWCK_OK if not XX */ XXLOCAL int XXpwck_dictionary(password, userid) XXchar *password; XXint userid; /* NOTUSED */ XX{ XX int i, /* Counter */ XX rcode; /* Return code temp */ XX XX for (i = 0; pwck_dicitonaries[i]; i++) { XX if ((rcode = XX IsInDictionary(pwck_dicitonaries[i], password)) != PWCK_OK) XX return(rcode); XX } XX return(PWCK_OK); XX} XX XX/* XX * IsInDictionary - look for <password> in <dictionary> XX * XX * Use a DBM version of the dictionary if present, XX * use egrep to search the flat file if not. XX * XX * Returns: XX * PWCK_INDICT if <password> was found in <dictionary> XX * PWCK_OK if not XX */ XX#define returnwith(code) { dbm_close(dbp); return(code); } XX#define EGREP pwck_preferences.EgrepPath XX XX#include <ndbm.h> XX XXLOCAL int XXIsInDictionary(dictionary, password) XXchar *dictionary, /* Pathname of dictionary */ XX *password; /* Plaintext of password */ XX{ XX DBM *dbp; /* DBM database pointer */ XX datum k, /* DBM lookup key */ XX d; /* DBM lookup datum */ XX int uc = isupper(password[0]); /* Is first char UC? */ XX char pwtemp[128]; /* Scratch buffer */ XX XX if ((dbp = dbm_open(dictionary, 0, 0)) == (DBM *)0) { XX char command[128]; /* Command build buffer */ XX int rc; /* System() return code */ XX XX if (access(dictionary, 0) < 0) XX return(PWCK_OK); XX /* XX * If the first letter is capitalized, look for XX * "[wW]ord" else look for "word" XX */ XX if (uc) XX (void) sprintf(command, XX "%s -s '^[%c%c]%s$' %s > /dev/null", XX EGREP, password[0], password[0] & ~040, XX &password[1], dictionary); XX else XX (void) sprintf(command, "%s -s '^%s$' %s > /dev/null", XX EGREP, password, dictionary); XX rc = system(command); XX if (rc == 0) XX return(PWCK_INDICT); XX else XX return(PWCK_OK); XX } XX /* XX * Look in the DBM version of the dictionary. XX * Look for <password>, then if the first letter XX * is capitalized, force to lower and look again. I don't care XX * if <password> is in the dictionary but has mixed case letters, XX * but if the first letter has been capitalized, I care because XX * that's not a sufficent permutation to be secure. XX */ XX (void) strcpy(pwtemp, password); XX k.dptr = pwtemp; XX k.dsize = strlen(pwtemp); XX d = dbm_fetch(dbp, k); XX if (d.dptr) XX returnwith(PWCK_INDICT); XX if (uc) { XX pwtemp[0] |= 040; XX d = dbm_fetch(dbp, k); XX if (d.dptr) XX returnwith(PWCK_INDICT); XX } XX returnwith(PWCK_OK); XX} XX#undef returnwith XX XX XX/* XX * pwck_password - Check password candidate against the users' password XX * file information, or any other information that is publicly XX * available about this user that a bandit could use as XX * password guesses. XX * XX * Here is the place to search your 'finger' database. XX */ XX#include <pwd.h> XX XXstatic int XXpwck_passwd(password, userid) XXchar *password; XXint userid; XX{ XX char temp[128]; /* Scratch */ XX struct passwd *pwp; /* Pointer to user information */ XX XX if (userid < 0) /* Can't do user checks */ XX return(PWCK_OK); XX XX pwp = getpwuid(userid); XX if (pwp == 0) XX return(PWCK_FAIL); XX XX try(password, pwp->pw_name, PWCK_OBVIOUS); /* Checks 'name' and 'Name' */ XX XX (void) strcpy(temp, pwp->pw_name); XX (void) strcat(temp, pwp->pw_name); XX try(password, temp, PWCK_OBVIOUS); /* Check 'namename' */ XX XX (void) strcpy(temp, pwp->pw_name); XX MirrorString(temp); XX try(password, temp, PWCK_OBVIOUS); /* 'eman' */ XX XX /* XX * Try every word in user's GECOS entry XX */ XX mtry(password, pwp->pw_gecos, PWCK_FINGER); XX return(PWCK_OK); XX} XX/* ------------------------------------------------------------------- */ XX/* XX * StrAllCmp - Compare all sub-strings (delineated by white space) XX * XX * Returns: XX * PWCK_OK if no match found XX * rc if match found XX */ XXLOCAL XXStrAllCmp(s1, s2, rc) XXchar *s1, /* String to look for */ XX *s2; /* String to look for <s1> in */ XXint rc; /* What to return on match */ XX{ XX int l; /* Temp */ XX XX for (l = strlen(s1); *s2; s2++) XX if (cistrncmp(s1, s2, l) == 0) XX return (rc); XX return(PWCK_OK); XX} XX XX/* XX * MirrorString - reverse a string in place XX */ XXLOCAL XXMirrorString(s) XXchar *s; /* String to reverse */ XX{ XX char *p; /* Scratch */ XX char t[128]; /* Scratch */ XX XX (void) strcpy(t,s); XX p = t; XX while (*p) p++; /* Find end of string */ XX --p; XX for (; *s; ) XX *s++ = *p--; XX} XX XX/* XX * Case indepedant string comparasion routines swiped from XX * the source to MIT Hesiod. XX */ XX/* XX * Copyright (c) 1986 Regents of the University of California. XX * All rights reserved. The Berkeley software License Agreement XX * specifies the terms and conditions for redistribution. XX */ XX XX/* XX * This array is designed for mapping upper and lower case letter XX * together for a case independent comparison. The mappings are XX * based upon ascii character sequences. XX */ XX XXLOCAL char charmap[] = { XX '\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007', XX '\010', '\011', '\012', '\013', '\014', '\015', '\016', '\017', XX '\020', '\021', '\022', '\023', '\024', '\025', '\026', '\027', XX '\030', '\031', '\032', '\033', '\034', '\035', '\036', '\037', XX '\040', '\041', '\042', '\043', '\044', '\045', '\046', '\047', XX '\050', '\051', '\052', '\053', '\054', '\055', '\056', '\057', XX '\060', '\061', '\062', '\063', '\064', '\065', '\066', '\067', XX '\070', '\071', '\072', '\073', '\074', '\075', '\076', '\077', XX '\100', '\141', '\142', '\143', '\144', '\145', '\146', '\147', XX '\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157', XX '\160', '\161', '\162', '\163', '\164', '\165', '\166', '\167', XX '\170', '\171', '\172', '\133', '\134', '\135', '\136', '\137', XX '\140', '\141', '\142', '\143', '\144', '\145', '\146', '\147', XX '\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157', XX '\160', '\161', '\162', '\163', '\164', '\165', '\166', '\167', XX '\170', '\171', '\172', '\173', '\174', '\175', '\176', '\177', XX '\200', '\201', '\202', '\203', '\204', '\205', '\206', '\207', XX '\210', '\211', '\212', '\213', '\214', '\215', '\216', '\217', XX '\220', '\221', '\222', '\223', '\224', '\225', '\226', '\227', XX '\230', '\231', '\232', '\233', '\234', '\235', '\236', '\237', XX '\240', '\241', '\242', '\243', '\244', '\245', '\246', '\247', XX '\250', '\251', '\252', '\253', '\254', '\255', '\256', '\257', XX '\260', '\261', '\262', '\263', '\264', '\265', '\266', '\267', XX '\270', '\271', '\272', '\273', '\274', '\275', '\276', '\277', XX '\300', '\341', '\342', '\343', '\344', '\345', '\346', '\347', XX '\350', '\351', '\352', '\353', '\354', '\355', '\356', '\357', XX '\360', '\361', '\362', '\363', '\364', '\365', '\366', '\367', XX '\370', '\371', '\372', '\333', '\334', '\335', '\336', '\337', XX '\340', '\341', '\342', '\343', '\344', '\345', '\346', '\347', XX '\350', '\351', '\352', '\353', '\354', '\355', '\356', '\357', XX '\360', '\361', '\362', '\363', '\364', '\365', '\366', '\367', XX '\370', '\371', '\372', '\373', '\374', '\375', '\376', '\377', XX}; XX XXLOCAL XXcistrcmp(s1, s2) XXregister char *s1, *s2; XX{ XX register char *cm = charmap; XX XX while (cm[*s1] == cm[*s2++]) XX if (*s1++=='\0') XX return(0); XX return(cm[*s1] - cm[*--s2]); XX} XX XXLOCAL XXcistrncmp(s1, s2, n) XXregister char *s1, *s2; XXregister n; XX{ XX register char *cm = charmap; XX XX while (--n >= 0 && cm[*s1] == cm[*s2++]) XX if (*s1++ == '\0') XX return(0); XX return(n<0 ? 0 : cm[*s1] - cm[*--s2]); XX} XX XX/* END */ SHAR_EOF if test 13550 -ne "`wc -c checkpasswd.c`" then echo shar: error transmitting checkpasswd.c '(should have been 13550 characters)' fi echo shar: extracting makedict.c '(653 characters)' sed 's/^XX//' << \SHAR_EOF > makedict.c XX/* XX * makedict - Make DBM version of password dictionary XX */ XX XX#include <sys/file.h> XX#include <stdio.h> XX#include <ndbm.h> XX XXextern char *index(); XX XXchar line[80]; XX XXmain(argc, argv) XXchar *argv[]; XX{ XX DBM *dp; XX int recs = 0; XX datum d, XX k; XX XX dp = dbm_open(argv[1], O_RDWR, 0); XX if (dp == 0) { XX if ((dp = dbm_open(argv[1], O_RDWR|O_CREAT, 0644)) == 0) { XX perror("open dbm"); XX exit(1); XX } XX } XX while (!feof(stdin)) { XX char *p; XX XX fgets(line, 80, stdin); XX p = index(line, '\n'); XX if (p) *p = 0; XX d.dptr = line; XX d.dsize = strlen(line); XX dbm_store(dp, d, d, DBM_INSERT); XX recs++; XX } XX dbm_close(dp); XX printf("%s built, %d records\n", argv[1], recs); XX} SHAR_EOF if test 653 -ne "`wc -c makedict.c`" then echo shar: error transmitting makedict.c '(should have been 653 characters)' fi echo shar: extracting viewdict.c '(406 characters)' sed 's/^XX//' << \SHAR_EOF > viewdict.c XX/* XX * viewdict - View DBM version of a password dictionary XX */ XX XX#include <sys/file.h> XX#include <ndbm.h> XX XXmain(argc, argv) XXchar *argv[]; XX{ XX DBM *dp; XX datum k; XX char t[128]; XX XX if ((dp = dbm_open(argv[1], O_RDONLY, 0)) == 0) { XX perror(argv[1]); XX exit(1); XX } XX for (k = dbm_firstkey(dp); k.dptr != 0; k = dbm_nextkey(dp)) { XX strncpy(t, k.dptr, k.dsize); XX t[k.dsize] = 0; XX printf("%s\n", t); XX } XX exit(0); XX} SHAR_EOF if test 406 -ne "`wc -c viewdict.c`" then echo shar: error transmitting viewdict.c '(should have been 406 characters)' fi echo shar: extracting wormwords '(3278 characters)' sed 's/^XX//' << \SHAR_EOF > wormwords XXaaa XXacademia XXaerobics XXairplane XXalbany XXalbatross XXalbert XXalex XXalexander XXalgebra XXaliases XXalphabet XXama XXamorphous XXanalog XXanchor XXandromache XXanimals XXanswer XXanthropogenic XXanvils XXanything XXaria XXariadne XXarrow XXarthur XXathena XXatmosphere XXaztecs XXazure XXbacchus XXbailey XXbanana XXbananas XXbandit XXbanks XXbarber XXbaritone XXbass XXbassoon XXbatman XXbeater XXbeauty XXbeethoven XXbeloved XXbenz XXbeowulf XXberkeley XXberliner XXberyl XXbeverly XXbicameral XXbob XXbrenda XXbrian XXbridget XXbroadway XXbumbling XXburgess XXcampanile XXcantor XXcardinal XXcarmen XXcarolina XXcaroline XXcascades XXcastle XXcat XXcayuga XXceltics XXcerulean XXchange XXcharles XXcharming XXcharon XXchester XXcigar XXclassic XXclusters XXcoffee XXcoke XXcollins XXcommrades XXcomputer XXcondo XXcookie XXcooper XXcornelius XXcouscous XXcreation XXcreosote XXcretin XXdaemon XXdancer XXdaniel XXdanny XXdave XXdecember XXdefoe XXdeluge XXdesperate XXdevelop XXdieter XXdigital XXdiscovery XXdisney XXdog XXdrought XXduncan XXeager XXeasier XXedges XXedinburgh XXedwin XXedwina XXegghead XXeiderdown XXeileen XXeinstein XXelephant XXelizabeth XXellen XXemerald XXengine XXengineer XXenterprise XXenzyme XXersatz XXestablish XXestate XXeuclid XXevelyn XXextension XXfairway XXfelicia XXfender XXfermat XXfidelity XXfinite XXfishers XXflakes XXfloat XXflower XXflowers XXfoolproof XXfootball XXforesight XXformat XXforsythe XXfourier XXfred XXfriend XXfrighten XXfun XXfungible XXgabriel XXgardner XXgarfield XXgauss XXgeorge XXgertrude XXginger XXglacier XXgnu XXgolfer XXgorgeous XXgorges XXgosling XXgouge XXgraham XXgryphon XXguest XXguitar XXgumption XXguntis XXhacker XXhamlet XXhandily XXhappening XXharmony XXharold XXharvey XXhebrides XXheinlein XXhello XXhelp XXherbert XXhiawatha XXhibernia XXhoney XXhorse XXhorus XXhutchins XXimbroglio XXimperial XXinclude XXingres XXinna XXinnocuous XXirishman XXisis XXjapan XXjessica XXjester XXjixian XXjohnny XXjoseph XXjoshua XXjudith XXjuggle XXjulia XXkathleen XXkermit XXkernel XXkirkland XXknight XXladle XXlambda XXlamination XXlarkin XXlarry XXlazarus XXlebesgue XXlee XXleland XXleroy XXlewis XXlight XXlisa XXlouis XXlynne XXmacintosh XXmack XXmaggot XXmagic XXmalcolm XXmark XXmarkus XXmarty XXmarvin XXmaster XXmaurice XXmellon XXmerlin XXmets XXmichael XXmichelle XXmike XXminimum XXminsky XXmoguls XXmoose XXmorley XXmozart XXnancy XXnapoleon XXnepenthe XXness XXnetwork XXnewton XXnext XXnoxious XXnutrition XXnyquist XXoceanography XXocelot XXolivetti XXolivia XXoracle XXorca XXorwell XXosiris XXoutlaw XXoxford XXpacific XXpainless XXpakistan XXpam XXpapers XXpassword XXpatricia XXpenguin XXpeoria XXpercolate XXpersimmon XXpersona XXpete XXpeter XXphilip XXphoenix XXpierre XXpizza XXplover XXplymouth XXpolynomial XXpondering XXpork XXposter XXpraise XXprecious XXprelude XXprince XXprinceton XXprotect XXprotozoa XXpumpkin XXpuneet XXpuppet XXrabbit XXrachmaninoff XXrainbow XXraindrop XXraleigh XXrandom XXrascal XXreally XXrebecca XXremote XXrick XXripple XXrobotics XXrochester XXrolex XXromano XXronald XXrosebud XXrosemary XXroses XXruben XXrules XXruth XXsal XXsaxon XXscamper XXscheme XXscott XXscotty XXsecret XXsensor XXserenity XXsharks XXsharon XXsheffield XXsheldon XXshiva XXshivers XXshuttle XXsignature XXsimon XXsimple XXsinger XXsingle XXsmile XXsmiles XXsmooch XXsmother XXsnatch XXsnoopy XXsoap XXsocrates XXsossina XXsparrows XXspit XXspring XXspringer XXsquires XXstrangle XXstratford XXstuttgart XXsubway XXsuccess XXsummer XXsuper XXsuperstage XXsupport XXsupported XXsurfer XXsuzanne XXswearer XXsymmetry XXtangerine XXtape XXtarget XXtarragon XXtaylor XXtelephone XXtemptation XXthailand XXtiger XXtoggle XXtomato XXtopography XXtortoise XXtoyota XXtrails XXtrivial XXtrombone XXtubas XXtuttle XXumesh XXunhappy XXunicorn XXunknown XXurchin XXutility XXvasant XXvertigo XXvicky XXvillage XXvirginia XXwarren XXwater XXweenie XXwhatnot XXwhiting XXwhitney XXwill XXwilliam XXwilliamsburg XXwillie XXwinston XXwisconsin XXwizard XXwombat XXwoodwind XXwormwood XXyacov XXyang XXyellowstone XXyosemite XXzap XXzimmerman SHAR_EOF if test 3278 -ne "`wc -c wormwords`" then echo shar: error transmitting wormwords '(should have been 3278 characters)' fi # End of shell archive exit 0 -- Shouter-To-Dead-Parrots @ Univ. of Texas Computation Center; Austin, Texas clyde@emx.utexas.edu; ...!cs.utexas.edu!ut-emx!clyde "You really have to take a broad perspective when giving pat answers to other people's problems." - Eyebeam