/**
* Copyright 2019 dax <dax@dax.moe>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
**/
#include <stdio.h> /* fgets(3), printf(3), fprintf(3) */
#include <stdlib.h> /* atoi(3), free(3), malloc(3), realloc(3), exit(3) */
#include <string.h> /* strlen(3), memcpy(3) */
#include <sys/random.h> /* getrandom(2) */
#include <unistd.h> /* getopt(3) */
#define WORDSIZE 16
/*
* This function uses getrandom(2), a Linux-specific syscall, but
* it can be adapted to other systems
*
* getrandom(2) uses /dev/urandom, so it's fairly cryptographically secure
*
* this function also avoid modulo bias by calculating a minimum number
* for which the upper_bound will safely divide into
**/
size_t
getrandom_uniform(size_t upper_bound)
{
size_t ret = 0;
size_t min = -upper_bound % upper_bound;
do {
getrandom(&ret, sizeof(ret), 0);
} while (ret < min);
return ret % upper_bound;
}
void
free_wordlist(char **wordlist, size_t numwords) {
for (int i=0; i < numwords; i++) {
free(wordlist[i]);
}
free(wordlist);
return;
}
/*
* This can be safely used to initialize the wordlist array if
* wordlist is NULL and wordlist_size is 0
*
* It will free any allocated memory and return NULL on error
* */
char **
grow_wordlist(char **wordlist, size_t wordlist_size, size_t new_size)
{
char **new_wordlist = realloc(wordlist, sizeof(char*) * new_size);
if (!new_wordlist) {
free_wordlist(wordlist, wordlist_size);
return NULL;
}
wordlist = new_wordlist;
return wordlist;
}
int
main(int argc, char **argv)
{
size_t wordlist_size = 7776;
size_t max_words = 0;
size_t passphrase_length = 6;
size_t numwords = 0;
char **wordlist = NULL;
int opt;
char *usage = "usage: dw [-n passphrase-length] [-s max-words]";
while ((opt = getopt(argc, argv, "hs:n:")) >= 0) {
switch (opt) {
case 'h':
printf("%s\n", usage);
exit(EXIT_SUCCESS);
case 's':
max_words = atoi(optarg);
wordlist_size = max_words;
break;
case 'n':
passphrase_length = atoi(optarg);
break;
default:
fprintf(stderr, "%s\n", usage);
exit(EXIT_FAILURE);
}
}
if (optind != argc) {
fprintf(stderr, "error: too many arguments\n");
fprintf(stderr, "%s\n", usage);
exit(EXIT_FAILURE);
}
if (!passphrase_length) {
fprintf(stderr, "error: passphrase_length is 0\n");
exit(EXIT_FAILURE);
}
wordlist = grow_wordlist(wordlist, 0, wordlist_size);
if (!wordlist) {
fprintf(stderr, "error: could not allocate memory\n");
exit(EXIT_FAILURE);
}
char buf[WORDSIZE];
while (fgets(buf, WORDSIZE, stdin)) {
size_t len = strlen(buf);
if (buf[len-1] == '\n') {
buf[len-1] = '\0';
len--;
}
if (len == 0) {
continue;
}
wordlist[numwords] = malloc(len);
if (!wordlist[numwords]) {
fprintf(stderr, "error: could not allocate memory\n");
free_wordlist(wordlist, numwords);
exit(EXIT_FAILURE);
}
memcpy(wordlist[numwords], buf, len);
numwords++;
if (numwords == wordlist_size) {
if (max_words) {
break;
}
size_t new_size = wordlist_size << 1;
wordlist = grow_wordlist(wordlist, wordlist_size, new_size);
if (!wordlist) {
fprintf(stderr, "error: could not allocate memory\n");
exit(EXIT_FAILURE);
}
wordlist_size = new_size;
}
}
if (numwords == 0) {
fprintf(stderr, "error: reached EOF before any input\n");
exit(EXIT_FAILURE);
}
for (;;) {
size_t randnum = getrandom_uniform(numwords);
printf("%s", wordlist[randnum]);
passphrase_length--;
if (passphrase_length == 0) {
break;
}
printf(" ");
}
printf("\n");
free_wordlist(wordlist, numwords);
exit(EXIT_SUCCESS);
}