/* pwchkr - find accounts with poor/crackable passwords
	Warning: this program burns a lot of cpu.
-----------------------------------
Edit history:
	Date: Tue, 29 Nov 83 18:19:32 pst
	From: leres%ucbarpa@Berkeley (Craig Leres)
	Revision 1.1  85/09/10  16:00:56  root
	Initial revision
	    Modified by Seth Alford, Roger Southwick, Steve Dum, and
	    Rick Lindsley for Tektronix
	Revision 1.2  85/11/30  22:42:07  richl
	Added code to allow for password aging.
static char *rcsid = "$Header: pwchkr.c,v 1.2 85/11/30 22:42:07 richl Exp $";
jmc 5/10/87 - cb(1), hacked and added 'x' option special chars checking
jmc 5/13/87 - added single user and LARGE dictionary checking
static char *rcsid = "$Header: pwchkr.c,v 2.0 87/03/10 16:00:56 jmc Exp $";
Rutgers edits: Don Watrous...
  Added is the -i switch to lookup the student id# in the
    /usr/adm/users/userfile file and see it that's a valid passwd.
    [Above code from ds, I believe. - daw]
  Also added - dpz's fix for brain damage on success [daw]
  Don't try to crack short encrypted strings [daw]
  Use fast encryption code (fdes5) from Robert W. Baldwin
       <BALDWIN@xx.lcs.mit.edu> [daw]
  Test for non-negative return from fscanf [daw]
  Don't eat space when reversing strings [daw]
Rutgers edits: *Hobbit* ...
  Combine Don's hack of 1.2 and jmc's 2.0 into one full-blown version.
  Make fdes a compile-time option.
  Zero out calling arguments to hide what we're doing.
  Add -digit switch to try that many uppercase characters.
-----------------------------------

By default, this program only checks for accounts with passwords the same
as the login name. The following options add more extensive checking. (The
tradeoff is cpu time -- with all options enabled it can run into the 100's
of MINUTES.) Any argument that does not begin with a "-" is assumed to be
a file name. (A single '-' means stdin.) If no file name is given,
/etc/passwd is used.

 * Options:
	-v:	verbose -- list all guesses on stdout
	-u:	output the username on the line of the password file
		currently being checked. If the program stops
		abruptly you will then know how far it got.
	-w file: use the list of words contained in "file" as likely
		passwords. Words in the file are one to a line.
	-b: 	check all guesses backwards too
	-g:	use the Full Name portion of the gecos field to
		generate more guesses
	-s:	check the single letters a-z, A-Z, 0-9 as passwords
	-c:	with each guess, check for all-lowercase and
		all-uppercase versions too.
	-x:	check guesses with "funny" characters added on.
	-[0-9]:	Check for <number> uppercase characters in an otherwise
		all-lowercase guess.
	-n:	complain about null passwords (default is to keep quiet)
	-i:	check the student id number if one exists [Rutgers]
	-p:	print the password when guessed
*/

#include <stdio.h>
#include <pwd.h>
#include <ctype.h>

int 	verbose = 0, singles = 0, backwards = 0, dictfile = 0,
	checkgecos = 0, checkcase = 0, chknulls = 0, printit = 0, 
	users = 0, chkwords = 0, checkfchars = 0, chkid = 0,
	num_uplow = 0;

char *fchars = "0123456789,<.>/?;:[{]}`~=+-_)(*&^%$#@! ";
int nums[10] = {0,0,0,0,0,0,0,0,0,0};

char *index(), *nreverse();	/* use *strchr() if need be */
long atol();
FILE *fopen();
char *fgets();

char PASSWD[] = "/etc/passwd";
char EMPTY[] = "";
static FILE 	*df = NULL, /* extended LARGE dictionary */
		*pwf = NULL, /* passwd file */
		*wlf = NULL, /* word list file */
		*ulf = NULL; /* "users" file */

char line[BUFSIZ+1];
struct passwd passwd;
char *logid = NULL;
/* "Curpw" stuff nuked; using normal filename arg; faked here. */
char *Curpw = NULL;
char *Wordlist = NULL;
char nextc;

main(argc, argv)
char **argv;
{
    register int i, c;
    register char *arg;

/* root check nuked */

    for (i = 1; i < argc; i++)
	if (arg = argv[i])	/* not null */
	  if (*arg == '-') {
	    while (*++arg) {
		if (isdigit ((int)*arg)) {
		  num_uplow++;
		  nums[*arg - '0']++;
		} else {
		  switch (*arg) {
		    case 'i':	/* check student id# */
		        chkid++;
			break;
		    case 'n':	/* complain about null passwords */
			chknulls++;
			break;
		    case 'c':	/* check cases */
			checkcase++;
			break;
		    case 'g':	/* use gecos */
			checkgecos++;
			break;
		    case 'v':	/* turn on motormouth */
			verbose++;
			break;
		    case 'b':	/* check all attempts forwards and backwards */
			backwards++;
			break;
		    case 's':	/* carry out a more intensive search,
				   checking for single letter passwords */
			singles++;
			break;
		    case 'p':	/* print out the password when found */
			printit++;
			break;
		    case 'u':	/* print out users as testing */
			users++;
			break;
		    case 'x': /* extended check of 'funny' chars */
			checkfchars++;
			break;
		    case 'w':	/* consult word list of likely passwords */
			if ((Wordlist = argv[i+1]) == NULL) {
			    fprintf(stderr,
				"%s: No file supplied with -w option\n",
				argv[0]);
			    exit (1);
			    }
			argv[i+1] = NULL;

			break;
		    default:
			  fprintf(stderr,
			    "%s: unknown option '%c'.\n",argv[0],*arg);
			  exit(1);
		  }	/* switch */
		}  /* if digit */
	    }  /* while arg */
	} else {
/* no dash, must be a filename. */
/* They did "pwf = stdin;"  originally to read stdin. */
	  if (setpwent (argv[i]))  exit (0);
	}  /* if '-' */

/* cover our trax  _H*/
    for (i = 0; i <= argc; i++) argv[i] = NULL;
    argv[0] = "*Security*";

    chkpw();
    endpwent();
    exit(0);
    }

#define ARB_CONST	60000

chkpw()
{
    register char	*cp, *cp2;
    register struct passwd *pwd;
    struct passwd	*getpwent();
    char		guess[100], stname[100], junk[100];
    char		*wordarray[ARB_CONST];
    char		*malloc(), **wordptr, **endptr;
    int			done = 0;
    register int	c;

    if (Wordlist)
    {
	if ((wlf = fopen(Wordlist,"r")) == NULL)
	{
	    perror(Wordlist);
	    exit(1);
	}

	wordptr = wordarray;
	/*
	 * note that endptr points to space OUTSIDE of wordarray
	 */
	endptr = wordarray + (sizeof(wordarray)/sizeof(char *));

	while (fscanf(wlf,"%[^\n]\n",guess) > 0)
	{
	    if (wordptr == endptr)
	    {
		fprintf(stderr,"Ran out of wordlist space. ARB_CONST %d must be too small.\n", ARB_CONST);
		exit(1);
	    }
	    if ((*wordptr = malloc(1+strlen(guess))) == NULL)
	    {
		fprintf(stderr,"malloc: no more memory for wordlist\n");
		exit (1);
	    }
	    strcpy(*wordptr,guess);
	    wordptr++;
	}
	*wordptr = NULL;
    }

#ifdef FDES
    init_des();
#endif

    while ((pwd = getpwent()) != 0 ) {

      done = 0;

	if (verbose || users) {
	    if (Curpw == NULL)
		printf("\t%s \"%s\"\n", pwd->pw_name, pwd->pw_gecos);
	    else
		printf("%s -- \t%s \"%s\"\n", Curpw, pwd->pw_name,
		    pwd->pw_gecos);
	    fflush(stdout);
	    }
	if (*pwd->pw_passwd == '\0') {
	    if (chknulls) {
		if (Curpw == NULL)
		    printf("Problem: null passwd:\t%s\tshell: %s\n",
			pwd->pw_name, pwd->pw_shell);
		else
		    printf("%s -- Problem: null passwd:\t%s\tshell: %s\n",
			Curpw, pwd->pw_name, pwd->pw_shell);
		fflush(stdout);
		}
	    continue;
	}
	if (strlen(pwd->pw_passwd) < 13) {
		if (Curpw == NULL)
		    printf("No Problem:\t%s\tshell: %s\n",
			pwd->pw_name, pwd->pw_shell);
		else
		    printf("%s -- No Problem:\t%s\tshell: %s\tlength: %d\n",
			Curpw, pwd->pw_name, pwd->pw_shell,strlen(pwd->pw_passwd));
		fflush(stdout);
	    continue;
	}
	/*
	 * Try the user's login name
	 */
	strcpy(guess, pwd->pw_name);
	if (uandltry(pwd,pwd->pw_name))
	    continue;
	strcat(guess, pwd->pw_name);
	if (uandltry(pwd, guess)) /* try login name repeated */
		continue;
#ifdef DOUBLE
/* This code from 2.0, but user?user takes entirely too much time.  _H*/
	if (checkfchars) {
	  c = strlen(pwd->pw_name); /* reusing variable 'c' */
	  strcpy(&guess[c+1], pwd->pw_name); /* leave a one char hole */
	  cp = fchars; /* reusing variable 'cp' */
	  while (*cp != NULL)
	  {
		guess[c] = *cp++;
		if (uandltry(pwd, guess)) /* name?name try */
			continue;
	  }
	}
#endif

	/*
	 * Try the student id# if one exists
	 */
	if (chkid) {
	  ulf = fopen("/usr/adm/users/userfile", "r+");
	  while (1) {
	    readatom(ulf, 100, stname);
	    if (nextc == EOF)
	      break;
	    if (strcmp(pwd->pw_name, stname) == 0) {
	      readatom(ulf, 100, junk);
	      readatom(ulf, 100, guess);
	      break;
	    }
	    newline(ulf);
	  }
	  fclose(ulf);
	  if (guess)
	    try(pwd, guess);
	}
	/*
	 * Try names from the gecos field
	 */
	if (checkgecos) {
	    strcpy(guess, pwd->pw_gecos);
	    cp = guess;
	    if (*cp == '-') cp++;		/* special gecos field */
	    if ((cp2 = index(cp, ';')) != NULL)
		*cp2 = '\0';
	    /* WHBjr hack to better divide words [from 2.0] _H*/
	    cp2 = cp;
	    while ((cp2 = index(cp2, ',')) != NULL)
		*cp2++ = ' ';
	    for (;;) {
		if ((cp2 = index(cp, ' ')) == NULL) {
		    if (uandltry(pwd,cp))
			done++;
		    break;
		    }
		*cp2 = '\0';
		if (uandltry(pwd,cp)) {
		    done++;
		    break;
		    }
		cp = ++cp2;
		}
	    }
	    
/* dictfile code from 2.0 version but since we've read a huge wordlist into
   this one, we might not need this.  _H*/
		if (!done && dictfile) /* try dictionary file */
		{
			rewind(df);
			cp = guess;
			while ((c = getc(df)) != EOF) 
			{
				if (c == '\n')
				{
					if ((cp - guess) > 8)
						guess[8] = '\0';
					else
						*cp = '\0';
					if(uandltry(pwd, guess))
					{
						done++;
						break;
					}
					cp = guess;
					continue;
				}
				*cp++ = c;
			}
		}

	if (!done && Wordlist)
	{
	    /*
	     * try the words in the wordlist
	     */
	    wordptr = wordarray;
	    while (endptr != wordptr)
	    {
		if (*wordptr == NULL)
		    break;
		if (uandltry(pwd,*wordptr++))
		{
		    done++;
		    break;
		}
	    }
	}
	if (!done && singles) {
	    /*
	     * Try all single letters
	     * (try digits too .  --Seth)
	     */
	    guess[1] = '\0';
	    for (guess[0]='a'; guess[0] <= 'z'; guess[0]++)
		if (try(pwd,guess))
		    break;
	    for (guess[0]='A'; guess[0] <= 'Z'; guess[0]++)
		if (try(pwd,guess))
		    break;
	    for (guess[0]='0'; guess[0] <= '9'; guess[0]++)
		if (try(pwd,guess))
		    break;
	    }
    }
}

/*
 * Stands for "upper and lower" try.  Calls funny char append try,
 * with the supplied version of the password, and with
 * an upper and lowercase version of the password. If the user doesn't
 * want to try upper and lower case [or N-mixed-case!] then we just return
 * after the one check.
*/

uandltry (pwd,guess)
char *guess;
struct passwd *pwd;
{
    register char *cp;
    char buf[100];
    int alllower, allupper;

    unsigned int num, nbits, len;			/* uplow vars */
    register unsigned int bits, maxbits, i, tmp;
    char buf2[100];
    register char *p2;

    alllower = allupper = 1;

    strcpy (buf, guess);
    if (ftry(pwd,buf) || (backwards && ftry(pwd,nreverse(buf)))) return (1);

    if (!checkcase) goto maybeuplow;

    cp = buf-1;
    while (*++cp) {
	if (isupper(*cp))
	    alllower = 0;
	if (islower(*cp))
	    allupper = 0;
	}

    if (!allupper) {
	for ( cp=buf; *cp != '\0'; cp++)
	    if (islower (*cp))
		*cp += 'A' - 'a';

	if (ftry(pwd,buf) || (backwards && ftry(pwd,nreverse(buf))))
	  return (1);
	}

    if (!alllower) {
	for ( cp = buf; *cp != '\0'; cp++)
	    if (isupper (*cp))
		*cp += 'a' - 'A';

	if (ftry(pwd,buf) || (backwards && ftry(pwd,nreverse(buf))))
	  return (1);
	}

maybeuplow:			/* up-low code */
    if (!num_uplow) return(0);

    for ( cp = buf; *cp != '\0'; cp++)	/* make all lower first */
      if (isupper (*cp))
	*cp += ' ';

    for (num = 1; num < 10; num++) 	/* -# control array */
      if (nums[num]) {
	len = strlen(buf);
	if (num >= len) 		/* sanity check */
	  return (0);
	for (cp = buf; *cp != '\0'; cp++)
	  if (isupper (*cp))  *cp += ' ';
	maxbits = 0;
	for (i = 0; i < len; i++)
	  maxbits = (maxbits << 1) + 1;		/* mask incr limit */
/* bits is uppercasing-mask control */
	for (bits = 0; bits < maxbits+1; bits++) {
/* figger out how many bits of "bits" are on, and does it match num? */
	  tmp = bits; nbits = 0;
	  for (i = 0; i < len; i++) {
	    if ((tmp >> i) & (int)1) nbits++;
	  }
	  if (nbits == num) {
/* okay, we have the requisite number, so do a modified strcpy.  Believe it
   or not, it's faster to do these two separate loops than to combine
   the strcpy into one big one -- byte-pushing chews cpu, I guess   _H*/
	      tmp = bits; i = 0;
	      p2 = buf2;
/* this is gonna go sorta backwards, but wtf */
	      for (cp = buf; *cp != 0; cp++) {
		*p2 = *cp;
		if ((tmp >> i++) & (int)1) *p2 -= ' ';
		p2++;
	      }
	      *p2 = 0;		/* remember the final null! */
	    if (ftry(pwd,buf2) || (backwards && ftry(pwd,nreverse(buf2))))
	      return (1);
	  }
	}
      }
    return (0);
}

/*  append funny chars if selected  -- stolen from 2.0  _H*/
ftry(pwd,guess)
register struct passwd *pwd;
char *guess;
{
	char *fptr;
	char tguess[100];
	char t2guess[100];
	short guesslen;

	if (try(pwd, guess))
		return(1);
	if (!checkfchars)
		return(0);
	fptr = fchars;
	guesslen = strlen(guess); /* first trailing char */
	strcpy(tguess, guess); /* some parameters fixed len - defensive prog */
	tguess[guesslen+1] = NULL; /* put in real end of string */
	strcpy(t2guess+1, guess); /* leading char guess */
	while (*fptr != NULL)
	{
		tguess[guesslen] = *fptr;
		if (try(pwd, tguess))
			return(1);
		t2guess[0] = *fptr++; /* now leading char */
		if (try(pwd, t2guess))
			return(1);
	}
}

try(pwd,guess)
char *guess;
register struct passwd *pwd;
{
    register char  *cp;

#ifdef FDES
    char   *fcrypt ();
#else
    char   *crypt ();
#define fcrypt crypt
#endif

    if (verbose) {
	if (Curpw == NULL)
	    printf ("Trying \"%s\" on %s\n", guess, pwd -> pw_name);
	else
	    printf ("%s -- Trying \"%s\" on %s\n", Curpw, guess,
		pwd -> pw_name);
	fflush (stdout);
	}
    if (! guess || ! *guess) return(0);
    cp = fcrypt (guess, pwd -> pw_passwd);
    if (strcmp (cp, pwd -> pw_passwd))
	return (0);
    if (Curpw == NULL)
	if (printit)
	    printf ("Problem: Guessed:\t%s\tshell: %s passwd: %s\n",
		pwd -> pw_name, pwd -> pw_shell, guess);
	else
	    printf ("Problem: Guessed:\t%s\tshell: %s\n", pwd -> pw_name,
		pwd -> pw_shell);
    else
	if (printit)
	    printf ("%s -- Problem: Guessed:\t%s\tshell: %s passwd: %s\n",
		Curpw, pwd -> pw_name, pwd -> pw_shell, guess);
	else
	    printf ("%s -- Problem: Guessed:\t%s\tshell: %s\n",
		Curpw, pwd -> pw_name, pwd -> pw_shell);
    fflush (stdout);
    return (1);
}
/* end of PW guessing program */

#define MAXUID 0x7fff	/* added by tonyb 12/29/83 */
			/* altered to a reasonable number - mae 8/20/84 */

/*
 * Add a parameter to "setpwent" so I can override the file name.
 */

setpwent(file)
char *file;
{
	if ((pwf = fopen(file,"r")) == NULL)
	    return(1);
	return(0);
}

endpwent()

{
    fclose(pwf);
    pwf = NULL;
}

char *
pwskip(p)
register char *p;
{
	while(*p && *p != ':' && *p != '\n')
		++p;
	if(*p == '\n')
		*p = '\0';
	else if(*p)
		*p++ = '\0';
	return(p);
}

struct passwd *
getpwent()
{
	register char *p;
	long	x;

	if(pwf == NULL)
	    if (setpwent(PASSWD)) {
		perror(PASSWD);
		return(NULL);
		}
	p = fgets(line, BUFSIZ, pwf);
	if(p == NULL)
		return(0);
	passwd.pw_name = p;
	p = pwskip(p);
	passwd.pw_passwd = p;
	p = pwskip(p);
	x = atol(p);	
	passwd.pw_uid = (x < 0 || x > MAXUID)? (MAXUID+1): x;
	p = pwskip(p);
	x = atol(p);
	passwd.pw_gid = (x < 0 || x > MAXUID)? (MAXUID+1): x;
	passwd.pw_comment = EMPTY;
	p = pwskip(p);
	passwd.pw_gecos = p;
	p = pwskip(p);
	passwd.pw_dir = p;
	p = pwskip(p);
	passwd.pw_shell = p;
	(void) pwskip(p);

	p = passwd.pw_passwd;
/* 	while(*p && *p != ',')
		p++;
	if(*p)
		*p++ = '\0';
	passwd.pw_age = p;
*/ 
	return(&passwd);

}


/*
 * reverse a string
 */
char *reverse(str)
char *str;

{
    register char *ptr;
    register int len;
    char	*malloc();

    if ((ptr = malloc((len = strlen(str))+1)) == NULL)
	return(NULL);
    ptr += len;
    *ptr = '\0';
    while (*str && (*--ptr = *str++))
	;
    return(ptr);
}

/*
 * reverse a string
 */
char *nreverse(str)
char *str;

{
    static char tmp[1024];
    register char *p;

    p = tmp +strlen(str);
    *p = '\0';
    while (*str && (*--p = *str++))
	;
    return(tmp);
}

readatom(fp, maxlen, buffer)
     FILE *fp;
     int maxlen;
     char *buffer;
{
  int i;

  /* skip whitespace */
  nextc = getc(fp);
  while (nextc == ' ' || nextc == '\t')
    nextc = getc(fp);
  /* now read until break char or array is filled */
  for (i = 0; nextc != ' ' && nextc != '\t' && nextc != '\n' &&
       nextc != ':' && nextc != ','  && nextc != EOF; i++) {
    if (i < maxlen)
      buffer[i] = nextc;
    nextc = getc(fp);
  }
  if (i < maxlen)
    buffer[i] = '\0';
  else
    buffer[maxlen-1] = '\0';
}

newline(fp)
     FILE *fp;
{
  while (nextc != '\n' && nextc != EOF)
    nextc = getc(fp);
}



X-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-X
 Another file downloaded from:                     The NIRVANAnet(tm) Seven

 & the Temple of the Screaming Electron   Taipan Enigma        510/935-5845
 Burn This Flag                           Zardoz               408/363-9766
 realitycheck                             Poindexter Fortran   510/527-1662
 Lies Unlimited                           Mick Freen           801/278-2699
 The New Dork Sublime                     Biffnix              415/864-DORK
 The Shrine                               Rif Raf              206/794-6674
 Planet Mirth                             Simon Jester         510/786-6560

                          "Raw Data for Raw Nerves"
X-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-X
