/*
 * Generate (hopefully) pronounceable random passwords.  These can often be
 * remembered more easily than completely random passwords, and are immune to
 * dictionary searches, etc.
 *
 * The original version of this program was written in BASIC on an OSI
 * Superboard II SBC.  That version is long gone (the SB2's cassette drive
 * was never trustworthy, it eventually scrambled the tape), but the basic
 * (pardon the pun) idea lives on here, with a few modification like basing
 * the selection on "graphs" (actually, they are a restricted set of phonemes)
 * and having randomly-selected spellings for those graphs.
 */

#include <stdio.h>
#include <math.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdlib.h>

#ifdef ALLBYOPTS
#include <getopt.h>
#endif

#ifdef RAND48
extern double drand48();
#define RANDOM(c)       ((int) ((drand48() * (c))))
#else
#define RANDOM(c)	((int) (rand(c) / 32767.0 * (c)))
#endif

char *spelling[] = {
/*a*/	"a",				(char *) 0,	/* 2*/
/*A*/	"a",	"ae",	"ai",		(char *) 0,	/* 6*/
/*b*/	"b",				(char *) 0,	/* 8*/
/*ch*/	"ch",				(char *) 0,	/*10*/
/*d*/	"d",				(char *) 0,	/*12*/
/*e*/	"e",				(char *) 0,	/*14*/
/*E*/	"e",	"ee",	"ie",		(char *) 0,	/*18*/
/*f*/	"f",	"ph",	"gh",		(char *) 0,	/*22*/
/*g*/	"g",				(char *) 0,	/*24*/
/*h*/	"h",				(char *) 0,	/*26*/
/*i*/	"i",	"e",			(char *) 0,	/*29*/
/*I*/	"i",	"ai",			(char *) 0,	/*32*/
/*i'*/	"i",	"ei",			(char *) 0,	/*35*/
/*j*/	"j",	"g",			(char *) 0,	/*38*/
/*k*/	"k",	"c",			(char *) 0,	/*41*/
/*l*/	"l",				(char *) 0,	/*43*/
/*m*/	"m",				(char *) 0,	/*45*/
/*n*/	"n",				(char *) 0,	/*47*/
/*ng*/	"ng",				(char *) 0,	/*49*/
/*o*/	"o",	"a",	"ah",		(char *) 0,	/*53*/
/*O*/	"o",	"oh",			(char *) 0,	/*56*/
#ifndef ALLBYOPT
/*place */"oo",	"u",			(char *) 0,	/*59*/
/*holder*/"oo",	"w",			(char *) 0,	/*62*/
#else
#ifdef ALTPHON
/*oo*/	"u",	"u",			(char *) 0,	/*59*/
/*OO*/	"oo",	"w",			(char *) 0,	/*62*/
#else
/*oo*/	"oo",	"u",			(char *) 0,	/*59*/
/*OO*/	"oo",	"w",			(char *) 0,	/*62*/
#endif
#endif
/*p*/	"p",				(char *) 0,	/*64*/
/*qu*/	"qu",				(char *) 0,	/*66*/
/*r*/	"r",				(char *) 0,	/*68*/
/*s*/	"s",	"c",			(char *) 0,	/*71*/
/*sh*/	"sh",	"s",			(char *) 0,	/*74*/
/*t*/	"t",				(char *) 0,	/*76*/
/*th*/	"th",				(char *) 0,	/*78*/
/*TH*/	"th",				(char *) 0,	/*80*/
/*u*/	"u",				(char *) 0,	/*82*/
/*U*/	"u",	"oo",			(char *) 0,	/*85*/
/*v*/	"v",				(char *) 0,	/*87*/
/*x*/	"x",				(char *) 0,	/*89*/
/*y*/	"y",				(char *) 0,	/*91*/
/*z*/	"z",	"s",			(char *) 0,	/*94*/
};

#ifdef ALLBYOPTS

char *alt_phonics[] =
{
                                                        /* 0*/ /*56*/
/*oo*/	"u",	"u",			(char *) 0,	/* 3*/ /*59*/
/*OO*/	"oo",	"w",			(char *) 0	/* 6*/ /*62*/
};

char *reg_phonics[] =
{
                                                        /* 0*/ /*56*/
/*oo*/	"oo",	"u",			(char *) 0,	/* 3*/ /*59*/
/*OO*/	"oo",	"w",			(char *) 0	/* 6*/ /*62*/
};

void alter_spelling_table(int wants_alternate_phonics)
{
  char **p;
  char **q = &spelling[56];
  int i;

  if(wants_alternate_phonics)
    p = alt_phonics;
  else
    p = reg_phonics;

  for(i = 0; i < 6; ++i)
    *q++ = *p++;
}

#endif

struct graph {
	char *graph;
	char type;
#define CONSONANT	0
#define VOWEL_LONG	1
#define VOWEL_SHORT	2
#define VOWEL_OTHER	3
#define VOWEL_MASK	3

#ifdef ALLBYOPTS

#define NUMERAL         8

#else

#ifdef NUMERALS
#define NUMERAL         8
#endif

#endif

#define iscons(c)	(((c)->type & VOWEL_MASK) == 0)
#define isvowel(c)	(((c)->type & VOWEL_MASK) != 0)
/*	char frequency;			*/	/* unused for now */
	char **spellings;
/*	struct graph **following;	*/	/* maybe later */
} graph[] = {
	"a",	VOWEL_SHORT,	&spelling[0],
	"A",	VOWEL_LONG,	&spelling[2],
	"b",	CONSONANT,	&spelling[6],
	"ch",	CONSONANT,	&spelling[8],
	"d",	CONSONANT,	&spelling[10],
	"e",	VOWEL_SHORT,	&spelling[12],
	"E",	VOWEL_LONG,	&spelling[14],
	"f",	CONSONANT,	&spelling[18],
	"g",	CONSONANT,	&spelling[22],
	"h",	CONSONANT,	&spelling[24],
	"i",	VOWEL_SHORT,	&spelling[26],
	"I",	VOWEL_LONG,	&spelling[29],
	"i'",	VOWEL_OTHER,	&spelling[32],
	"j",	CONSONANT,	&spelling[35],
	"k",	CONSONANT,	&spelling[38],
	"l",	CONSONANT,	&spelling[41],
	"m",	CONSONANT,	&spelling[43],
	"n",	CONSONANT,	&spelling[45],
	"ng",	CONSONANT,	&spelling[47],
	"o",	VOWEL_SHORT,	&spelling[49],
	"O",	VOWEL_LONG,	&spelling[53],
	"oo",	VOWEL_SHORT,	&spelling[56],
	"OO",	VOWEL_LONG,	&spelling[59],
	"p",	CONSONANT,	&spelling[62],
	"qu",	CONSONANT,	&spelling[64],
	"r",	CONSONANT,	&spelling[66],
	"s",	CONSONANT,	&spelling[68],
	"sh",	CONSONANT,	&spelling[71],
	"t",	CONSONANT,	&spelling[74],
	"th",	CONSONANT,	&spelling[76],
	"TH",	CONSONANT,	&spelling[78],
	"u",	VOWEL_SHORT,	&spelling[80],
	"U",	VOWEL_LONG,	&spelling[82],
	"v",	CONSONANT,	&spelling[85],
	"x",	CONSONANT,	&spelling[87],
	"y",	CONSONANT,	&spelling[89],
	"z",	CONSONANT,	&spelling[91],
	0,	0,		&spelling[94],
};

struct graph *vowel[] = {
	&graph[0],	&graph[1],	&graph[5],	&graph[6],
	&graph[10],	&graph[11],	&graph[12],	&graph[19],
	&graph[20],	&graph[21],	&graph[22],	&graph[30],
	&graph[31],
	(struct graph *) 0,
};

struct graph *consonant[] = {
	&graph[2],	&graph[3],	&graph[4],	&graph[7],
	&graph[8],	&graph[9],	&graph[13],	&graph[14],
	&graph[15],	&graph[16],	&graph[17],	&graph[18],
	&graph[23],	&graph[24],	&graph[25],	&graph[26],
	&graph[27],	&graph[28],	&graph[29],	&graph[32],
	&graph[33],	&graph[34],	&graph[35],
	(struct graph *) 0,
};

/*
 * Randomly select a graph from the specifield array.  Eventually, this should
 * account for graph frequencies as well.
 */

struct graph *selgraph(graphs)
	struct graph **graphs;
{
	register int cnt;

	for (cnt = 0; graphs[cnt] != (struct graph *) 0; cnt++)
		;
	return graphs[RANDOM(cnt)];
}

/*
 * Randomly select a spelling for the specified graph.  This is not linear:
 * earlier spellings are preferred over later ones, but the latter do
 * sometimes sneak in.
 */

char *selspell(graph)
	struct graph *graph;
{
	register int cnt, sel;

	for (cnt = 0; graph->spellings[cnt] != (char *) 0; cnt++)
		;
	if (cnt == 0) {
		fprintf(stderr, "PANIC: selspell(%s) got count(spellings) == 0\n", graph->graph);
		exit(2);
	}
	if (cnt == 1)
		return *graph->spellings;
/*
 * This may not be the best way to do it... maybe Weemba'd care to lend a
 * hand here?  After all, my specialty is programming, NOT math.
 */

	if 
	  (
	    (sel = cnt - (int) sqrt((double) RANDOM(cnt * cnt) + 1) - 1) < 0 
	    || 
	    sel >= cnt
	  ) 
	  {
#ifdef BUGCATCH
	    fprintf
	      (
	        stderr, 
		"PANIC: selspell(%s) got nlrand(%d) == %d\n", 
		graph->graph, 
		cnt, 
		sel
	      );

		exit(2);
#else
		sel = 0;
#endif
	}

	return graph->spellings[sel];
}

/*
 * Choose the next source for a graph.  The rules are:  a consonant MUST be
 * followed by a vowel; a vowel may be followed by a vowel of a different
 * type or by a consonant, but never more than two consecutive vowel graphs.
 */

choosenext(cur, prev)
{
	if (cur == CONSONANT)
		return VOWEL_MASK;
	else if (prev == -1 || (prev & VOWEL_MASK) != 0)
		return CONSONANT;
	else if (RANDOM(10) == 5)
		return VOWEL_MASK;
	else
		return CONSONANT;
}

/*
 * We are passed an array of (struct graph *); choose an entry randomly and
 * assemble a string fitting the size constraint.  We use the original (OSI)
 * paradigm:  alternate consonants and vowels, with the option of two vowels
 * in a row occasionally.  The only difference is that they must be different
 * *types* of vowels, a distinction that the OSI version didn't consider.
 */

#ifdef ALLBYOPTS
pwgen
(
  struct graph **initial,
  char *pw,
  int maxlen,
  int wants_capitalize,
  int wants_numerals
)
#else
pwgen
(
  struct graph **initial,
  char *pw,
  int maxlen
)
#endif

{
	int pwlen, state, prev, tmp;
	struct graph *graph;
	char *spelling;
	int capc;

	capc=0;
	pwlen = 0;
	state = initial[0]->type;
	prev = -1;
  while (pwlen < maxlen - 1) 
    {
      do 
	{
			graph = selgraph(initial);
		} while (state != CONSONANT && graph->type == prev);
      
      if ((spelling = selspell(graph)) == (char *) 0) 
	{
	  fprintf
	    (
	      stderr, 
	      "PANIC: got NULL in selspell(%s)\n", 
	      graph->graph
	    );
		
			exit(2);
		}

      strcpy(pw, spelling);

      if ( (state==CONSONANT) && ( (capc++)==1) ) 
	{

#ifdef ALLBYOPTS
	  if(wants_numerals)
	    {
	      *pw++ = (RANDOM(10) + '0');
	      pwlen++;
		strcpy(pw, spelling);
	    }
#else
#ifdef NUMERALS
		  *pw++ = (RANDOM(10) + '0');
		  pwlen++;
		  strcpy(pw, spelling);
#endif
#endif

#ifdef ALLBYOPTS
	  if(wants_capitalize)
	    {
	      *pw = toupper(*pw);
	    }
#else
#ifdef CAPITALIZE
		  *pw = toupper(*pw);
#endif
#endif

		}

		while (*pw != '\0')
			pwlen++, pw++;

		tmp = prev;
		prev = graph->type;

		if ((state = choosenext(prev, tmp)) == CONSONANT)
			initial = consonant;
		else
			initial = vowel;
	}
}

void sec_pass(char *buf, int len) {
	static const int        low = '!';
	static const int        high = '~';
        const float             range = high - low;
        struct timeval          t;
        int                     i;

        gettimeofday(&t, 0);

        srand48(t.tv_sec + t.tv_usec * 102457 + getpid() + getppid());

        for ( i = 0 ; i < len ; i++ ) {
                int c = low + (int)(drand48() * range);
                switch ( c ) {
                case '\\':
                case '\'':
                case '\"':
                        c = '%';
                }
                buf[i]=c;
        }
	buf[i]='\0';
}

#ifdef ALLBYOPTS
#define ALTPHON_OPT    'a'
#define CAPITALIZE_OPT 'c'
#define NUMERALS_OPT   'n'
#define SECURE_OPT     's'
#define HELP_OPT       'h'
#define END_SHORT_OPT  '\0'
#define END_LONG_OPT   {(char *) 0, 0, (int *) 0, '\0'}

const char short_blank = END_SHORT_OPT;
const struct option long_blank = END_LONG_OPT;

int optionlen(struct option *option_array)
{
  int result = 0;

  while(option_array[result].name != NULL)
    result++;

  return result;
}

void remove_short_option(char option, char *option_array)
{
  int i;
  int found_index = -1; /* index of found index !or! -1 if none */
  int size = strlen(option_array);

  for(i = 0; i < size; i++)
    {
      if(option_array[i] == option)
	{
	  found_index = i;
	  break;
	}
    }

  if(found_index != -1)
    {
      if(found_index != size - 1) /* not last item? */
	{
	  /* replace found item with last item */
	  
	  option_array[found_index] = option_array[size - 1];
	}

      /* blank out last item */
      option_array[size - 1] = short_blank;
    }
}

void remove_long_option(char option, struct option *option_array)
{
  int i;
  int found_index = -1; /* index of found index !or! -1 if none */
  int size = optionlen(option_array);

  for(i = 0; i < size; i++)
    {
      if(option_array[i].val == option)
	{
	  found_index = i;
	  break;
	}
    }

  if(found_index != -1)
    {
      if(found_index != size - 1) /* not last item? */
	{
	  /* replace found item with last item */
	  
	  option_array[found_index] = option_array[size - 1];
	}

      /* blank out last item */
      option_array[size - 1] = long_blank;
    }
}

void print_option_array(struct option *option_array)
{
  int i = 0;

  while(option_array[i].val != short_blank)
    {
      if(option_array[i].flag != NULL)
	printf
	  (
	    "{%s, %d, %d, '%c'=%x}\n",
	    option_array[i].name,
	    option_array[i].has_arg,
	    *option_array[i].flag,
	    (char) option_array[i].val,
	    option_array[i].val
	  );
      else
	printf
	  (
	    "{%s, %d, NULL, '%c'=%x}\n",
	    option_array[i].name,
	    option_array[i].has_arg,
	    (char) option_array[i].val,
	    option_array[i].val
	  );

      i++;
    }
}

struct option longopts[] =
{
  {"alt-phonics",    no_argument,  (int *) 0,  ALTPHON_OPT    },
  {"capitalize",     no_argument,  (int *) 0,  CAPITALIZE_OPT },
  {"numerals",       no_argument,  (int *) 0,  NUMERALS_OPT   },
  {"secure",         no_argument,  (int *) 0,  SECURE_OPT     },
  {"help",           no_argument,  (int *) 0,  HELP_OPT       },
  END_LONG_OPT
};

char opt_string[] =
{
  ALTPHON_OPT,
  CAPITALIZE_OPT,
  NUMERALS_OPT,
  SECURE_OPT,
  HELP_OPT,
  END_SHORT_OPT
};

void disable_option(char *opt_chars)
{
  while(*opt_chars)
    {
      remove_short_option(*opt_chars, opt_string);
      remove_long_option(*opt_chars, longopts);

      opt_chars++;
    }
}
#endif

void usage(FILE *out, char *name)
{
  fprintf(out, "Usage: %s ", name);

#ifdef ALLBYOPTS
  fprintf(out, "[ -acns --alt-phonics --capitalize ");
  fprintf(out, "--numerals --secure ] ");
  fprintf(out, "maxlength [count]\n");
  fprintf(out, "Use the command \"pwgen -s maxlen [count]\" ");
#else
  fprintf(out, "maxlength [count]\n");
  fprintf(out, "Use the command \"spwgen maxlen [count]\" ");
#endif
  
  fprintf(out, "to generate more secure passwords.\n");
}

int main(int argc, char **argv)
{
  int first_nonopt = 1; /* index of the first non-option in argv */
  int is_bad_opt = 0; /* true if unrecognized option */
  int cnt, len, wants_secure=0;
  char buf[20];
#ifdef ALLBYOPTS
  int wants_alternate_phonics = 0; /* boolean from options */
  int wants_capitalize = 0; /* boolean from options */
  int wants_numerals = 0; /* boolean from options */
  int wants_help = 0; /* boolean from options */
  int getopt_result;
  int longindex = 0;

  getopt_result = 
    getopt_long
    (
      argc, 
      argv, 
      opt_string,
      longopts, 
      &longindex
    );

  while(getopt_result != EOF)
    {
      /* process opts here */
      
      switch(getopt_result)
	{
	case ALTPHON_OPT:
	  wants_alternate_phonics = 1;

	  disable_option("ash");

	  break;

	case CAPITALIZE_OPT:
	  wants_capitalize = 1;

	  disable_option("csh");

	  break;
	  
	case NUMERALS_OPT:
	  wants_numerals = 1;

	  disable_option("nsh");

	  break;

	case SECURE_OPT:
	  wants_secure = 1;
	  
	  disable_option("acnsh");
	  
	  break;
	  
	case HELP_OPT:
	  wants_help = 1;
	  
	  disable_option("acnsh");
	  
	  break;
	  
	case '?':
	default:
	  is_bad_opt = 1;
	  break;
	}
      
      if(is_bad_opt) 
	break;
      else
	getopt_result = 
	  getopt_long
	  (
	    argc, 
	    argv, 
	    opt_string, 
	    longopts, 
	    &longindex
	  );
    }

  if(! is_bad_opt)
    {
      alter_spelling_table(wants_alternate_phonics);
    }                                                     

  first_nonopt = optind;

#endif

  if(wants_help)
    {
      usage(stdout, argv[0]);

      printf("You can use -a, -c or -n in any combination.\n");

      printf("You can't use these with -s or vise versa.\n");

      printf("\n");
      printf("\n");
      printf("OPTIONS\n");
      printf("\n");
      printf("  -a or --alt-phonics\n");
      printf("       use a slightly altered phonics table to generate\n");
      printf("       syllables.\n");
      printf("\n");
      printf("  -c or --capitalize\n");
      printf("       use (not so) random capitalizations in generated\n");
      printf("       passwords\n");
      printf("\n");
      printf("  -n or --numerals\n");
      printf("       use (not so) random placement of numerals in\n");
      printf("       generated passwords\n");
      printf("\n");
      printf("  -s or --secure\n");
      printf("       generate totally random, unmemorable, unpronouncable,\n");
      printf("       but secure and unguessable passwords\n");
      printf("\n");
      printf("  -h or --help\n");
      printf("       print this message and exit successfully\n");
      printf("\n");
      printf("\n");
      printf("ARGUMENTS\n");
      printf("\n");
      printf("  maxlength (required with -a, -c, -n or -s)\n");
      printf("       a number spec ifying the masimum length\n");
      printf("       of each generated password\n");
      printf("\n");
      printf("  count\n");
      printf("       the number of passwords generated. each will\n");
      printf("       be on its own line.\n");
      printf("\n");
      printf("SEE ALSO: man pwgen for details.\n");
      printf("\n");

      exit(0);
    }
  else if 
         ( 
	   (argc < first_nonopt + 1) 
	   || 
	   (argc > first_nonopt + 3) 
	   || (is_bad_opt) 
	 )
    {
      usage(stderr, argv[0]);
      
      return 1;
    }

  if ((len = atoi(argv[first_nonopt + 0])) < 4 || len > 16) 
    {
      fprintf
	(
	  stderr, 
	  "%s: invalid maxlength %s\n", 
	  argv[0], 
	  argv[first_nonopt + 0]
	);

      return 1;
    }

  if (argc == first_nonopt + 1)
    cnt = 1;                                      
  else if ((cnt = atoi(argv[first_nonopt + 1])) < 1) 
    {
      fprintf
	(
	  stderr, 
	  "%s: invalid count %s\n",  
	  argv[0], 
	  argv[first_nonopt + 1]
	);

      return 1;                                      
        }          

/*
 * Changed '|' into '^' in the srand() formula to get a better RNG
 * Vincent Renardias <vincent@waw.com>
 * Jan, 11th 1997.
 */

#ifdef RAND48
  srand48((time(0)<<9) ^ (getpgrp()<<15) ^ (getpid()) ^ (time(0)>>11));
#else
  srand(time(0) ^ (getpgrp() << 8) + getpid());
#endif

  while (cnt-- != 0) 
    {
      if (wants_secure == 1) 
	{
	  sec_pass(buf,len);
	} 
      else 
	{
#ifdef ALLBYOPTS
	  pwgen
	    (
	      (RANDOM(10) < 4? vowel: consonant), 
	      buf, 
	      len,
	      wants_capitalize,
	      wants_numerals
	    );
#else
	  pwgen((RANDOM(10) < 4? vowel: consonant), buf, len);
#endif
	}
      
      printf("%s\n", buf);
    }
  
  return 0;
}



syntax highlighted by Code2HTML, v. 0.9.1