/* 	Unix crypt(3) evaluation engine 
		
 	Author(s):	route@infonexus.com
	Version:	.1 Alpha-release
 	Date: 		March 1996					
	Kompile: 	gcc -o c3eval c3eval.c -lm

	Evaluates the strength of passwords.  Currently, only
	implemented for Unix crypt(3).

	Planned:
	
		- Option to use getpass function to obscure the entry of the
		  pasword.
		- Option to have the output of the heuristic testing to
		  inform user why his/her choice sucks...
		- Integration into passwd(1).
		- Better scoring system
		- More stuff
*/

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

#define SALT ".."		/* Default salt value */
#define INPUTSPACE 95		/* The full input space of crypt3 */

void main(argc,argv)
int argc;
char *argv[];
{

	void usage(char *);
	void entropy(char *,int);
	void statStrength(char *,int);

	char password[9];

	if(argc==1||argc>3)usage(argv[0]);
	
	strcpy(password,(char *)argv[1]); 
	password[8]='\0';			

	fprintf(stderr,"\n\nThis program is in ALPHA!  Trust this and Die!\n\n");	

	statStrength(password,strlen(password));
	entropy(password,strlen(password));

	if(argc==2)printf("Salt/Encrypted Hash: %s\n",crypt(argv[1],SALT));
	else printf("Salt/Encrypted Hash: %s\n",crypt(argv[1],argv[2]));

}						/* End main */


/*	usage()
	
	Arguments:		Character pointer
	Return value: 		None.
	Global data modified:	None.	
	
	Display usage to stdout and exit.
*/

void usage(progName){
		
	printf("\n\nUsage:\t\t %s password [salt]\n\n",progName);
	printf("Remember to escape all control characters with a '\\'\n");
	printf("Legal password/salt characters: 0-9,A-Z,a-z, !@#$%^&*()_+|~`\\=-[]{};':\",./<>?\n");
	exit(1);
}


/* 	statStrength()
	
	Arguments:		Character pointer, short.
	Return Value:		None.
	Global data modified:	None.	

	statStrength() will return the statistical strength of the password
	based on some heurisitical patterns of my own devise (Until I can
	find something better).  This is where the 'real world' evaluation 
	is done.

	Each password begins with 100 points per char of length.  The 
	Best score achievable, therefore is 800 points.  Points are
	deducted for crappy passwords...

	Follows the premise that it is easier to assume a good
	passwd and test for a bad one.
	
	notes to myself...
	[printfs at end of tests are there fur debugging]
	-all Alphabetic 				.done
	-all Numerics					.done
	-all lwer case					.done
	-all uppr case					.done
	-all same char					.done
	-too many occurences of same char		.done
	-clumped vowels/consanants (ee,nn,oo,ss)	.done
	-common char combos (ing,ea,ie,rst,oo,ee)	.done
	-phonetic tests for pronoucable words		.done~
	-Make sure we have special chars...		.done
	-'Close keybd' combos (12345,qwerty,jkl;')
	......

	
	Note that I am writing the subtests with no regard for order
	or cohesion as this point.  Just getting them down and working. 

	Planned cohesion:

	- 1 test for all same char (Horrible.  Space of 8^1)
	- 2 test for all numerics (Very bad.  Space of 8^10)
	- 3 test for all uppr/lwr case (Worse.  Space of 8^26)
	- 4 test for all alphabetic (Not much better. Space of 8^52)
	- 5 test for too many occurences (variable space)
	- 6 test for special chars 
	- 6 phonetic tests...

*/

void statStrength(password,length){

	
	char dataBuffer[9];
	char tempBuffer[9];
	int score;
	int i=0,j=0,k=0;

	strcpy(dataBuffer,(char *)password);
	score=length*100;

	printf("\nHeuristic Results:\n");
	printf("------------------------------------\n");
					/* Special case of single char pw */
	if(length==1){		
		printf("You have got to kidding me...\"%s\" is your password?\n",dataBuffer);
		score=-1;
		printf("Score: %d\n",score);
		return;
	}
	printf("Initial Score: %d\n",score);

					/* All same char */
	for(i=0,j=0;i<length;i++){	
		if(dataBuffer[i]==dataBuffer[i+1])j++;
		else break;
	}
	if(j+1==length){
		score=0;		
		printf("Score: %d\t(HomogenousCheck)\n",score);	
		return;
	}

					/* All Numerics */
	for(i=0,j=0;i<length;i++){	
		if(isdigit(dataBuffer[i]))j++;
		else break;
	}
	if(j==length){
		score=10;		
		printf("Score: %d\t(NumCheck)\n",score);	
		return;
	}
					/* All lower/uppercase alphabetics */	
	for(i=0,j=0;i<length;i++){	
		if(isupper(dataBuffer[i]))j++;
		else if(islower(dataBuffer[i]))k++;
		else break;
	}
	if(j==length||k==length){
		score/=2;		
		printf("Score: %d\t(Upper/lowercase Check)\n",score);	
	}

					/* All Alphabetic chars */
	for(;i<length;i++){	
		if(isalpha(dataBuffer[i]))j++;
		else break;
	}
	if(j==length)score/=3;		
	printf("Score: %d\t(AlphaCheck)\n",score);	


				/* Too many occurences of same char */
				/* Makes [n(n-1)]/2 comparisons */

	for(i=0,k=0;i<length;i++)for(j=0;j<length;j++)if(dataBuffer[i]==dataBuffer[j])k++;
	if((k-length)>length/3)score/=1.5;		
	printf("Score: %d\t(DupCheck)\n",score);	
	
				/* Phonetic Tests/common char combos */
				/* Wordlists are often built from simple
				   phonetic contructions and char combos */ 
		i=0;
		while(dataBuffer[i]){
			tempBuffer[i]=tolower(dataBuffer[i]);
			i++;
		}
		tempBuffer[i]='\0';	

	for(i=0;i<length;i++){
		switch(tempBuffer[i]){
			case 'a':	/* ae, ai, able */
				if(tempBuffer[i+1]=='e'||tempBuffer[i+1]=='i'||tempBuffer[i+1]=='b'&&tempBuffer[i+2]=='l'&&tempBuffer[i+3]=='e')score/=1.5;
			case 'e':	/* ee, ei, ea, ed */
				if(tempBuffer[i+1]=='e'||tempBuffer[i+1]=='i'||tempBuffer[i+1]=='a'||tempBuffer[i+1]=='d')score/=1.5;				
			case 'i':	/* ie, ive, ible, ing */
				if(tempBuffer[i+1]=='e'||tempBuffer[i+1]=='v'&&tempBuffer[i+2]=='e'||tempBuffer[i+1]=='n'&&tempBuffer[i+2]=='g'||tempBuffer[i+1]=='b'&&tempBuffer[i+2]=='l'&&tempBuffer[i+3]=='e')score/=1.5;
			case 'o':	/* ou, oo, ov */
				if(tempBuffer[i+1]=='u'||tempBuffer[i+1]=='o'||tempBuffer[i+1]=='v')score/=1.5;
			case 'u':	/* ust */
				if(tempBuffer[i+1]=='s'&&tempBuffer[i+2]=='t')score/=1.5;
			case 'r':	/* rst */	
				if(tempBuffer[i+1]=='s'&&tempBuffer[i+2]=='t')score/=1.5;
			case 's':	/* ss, st, sion */
				if(tempBuffer[i+1]=='s'||tempBuffer[i+1]=='t'||tempBuffer[i+1]=='i'&&tempBuffer[i+2]=='o'&&tempBuffer[i+3]=='n')score/=1.5;
			case 't':	/* tt, tle, tion */
				if(tempBuffer[i+1]=='t'||tempBuffer[i+1]=='l'&&tempBuffer[i+2]=='e'||tempBuffer[i+1]=='i'&&tempBuffer[i+2]=='o'&&tempBuffer[i+3]=='n')score/=1.5;
			case 'l':	/* le, ll */
				if(tempBuffer[i+1]=='e'||tempBuffer[i+1]=='l')score/=1.5;
			case 'n':	/* nn, ne */
				if(tempBuffer[i+1]=='e'||tempBuffer[i+1]=='n')score/=1.5;
			case 'b':	/* ble */
				if(tempBuffer[i+1]=='l'&&tempBuffer[i+2]=='e')score/=1.5;
			case 'q':	/* qu */
				if(tempBuffer[i+1]=='u')score/=1.5;
			case 'v':	/* ve */
				if(tempBuffer[i+1]=='e')score/=1.5;

		}
	}
	printf("Score: %d\t(PhoneticCheck)\n",score);	
	
					/* Special char test */

	for(j=0,i=0;i<length;i++)if(dataBuffer[i]>=0x20&&dataBuffer[i]<=0x2F||dataBuffer[i]>=0x3A&&dataBuffer[i]<=0x40||dataBuffer[i]>=0x5B&&dataBuffer[i]<=0x60||dataBuffer[i]>=0x7B&&dataBuffer[i]<=0x7E)j++;
	if(j<(length/3))score/=1.5;	
	printf("Score: %d\t(Special Character Check)\n",score);	


	printf("------------------------------------\n");

}	


/*	entropy()

	Arguments:		Character pointer,int.
	Return Value:		None.
	Global data modified:	None.	


	entropy() will analyze the password and return what it thinks is
	the amount of entropy the password contains.  
	FYI: Entropy is the measure of the amount of information conveyed 
	by a message.  For passwords, the greater the entropy, theorectically,
	the stronger it is.
	The entropy of a message (in bits) is log2(n), where n is the number of 
	possible encodings (meanings) of the message, assuming each is equally 
	likely. 

	Notes:
	- Assuming 94 possible input characters.. (from define above).

*/

void entropy(password,length){

	char dataBuffer[9];	
	float baseEntropy;
	float absRate;	
	float effRate;
	float redundancy;

	strcpy(dataBuffer,(char *)password);
	baseEntropy=(log(pow(INPUTSPACE,length))/log(2));
	effRate=baseEntropy/length;
	absRate=(log(INPUTSPACE)/log(2));
	redundancy=absRate-effRate;

	printf("\nInformation Theoretic Results:\n");
	printf("------------------------------------\n");
	printf("Base Entropy: %f bits/message. \nConsidering message length of %d and a total input space of %d.\nAssumes each character is equally likely.\n",baseEntropy,length,INPUTSPACE);
	printf("Absolute Rate of Character Set: %f bits/letter (con. %d pos. inputs)\n",absRate,INPUTSPACE);
	printf("Effective rate of Character Set: %f bits/letter\n",effRate);
	printf("For a redundancy of: %f bits/letter\n",redundancy);
	 		
	printf("------------------------------------\n");
}
