c: Implement the CLI tool

The CLI tool should have feature parity with the Python equivalent.

Both implementations generate the same hashes so the algorithm seems to
be sound.
This commit is contained in:
2021-11-14 09:17:41 +01:00
parent f927eea9aa
commit c869c79619
2 changed files with 288 additions and 1 deletions

View File

@ -1,2 +1,2 @@
executable('passgeny', 'src/passgeny.c', dependencies: phogen_dep)
executable('passgeny', 'src/main.c', dependencies: [passgeny_dep])

287
c/cli/src/main.c Normal file
View File

@ -0,0 +1,287 @@
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <termios.h>
#include "passgeny.h"
const char passgeny_usage[] =
"\n"
"usage: Passgeny - Not a Passowrd Manager [-h] [--verbose] [--pattern PATTERN] [--stdin | --env ENV]\n"
" domain user [tokens ...]\n"
"\n"
"positional arguments:\n"
" domain Domain or unique site identifier\n"
" user Username or unique user identifier\n"
" tokens Additional tokens\n"
"\n"
"optional arguments:\n"
" -h, --help show this help message and exit\n"
" --verbose, -v Verbose\n"
" --pattern PATTERN, -p PATTERN\n"
" Set pattern\n"
" --stdin, -s Read password from stdin\n"
" --env ENV, -e ENV Read password from environment\n";
static struct option passgeny_options[] =
{
{ "verbose", no_argument, NULL, 'v' },
{ "pattern", required_argument, NULL, 'p' },
{ "stdin", no_argument, NULL, 's' },
{ "env", required_argument, NULL, 'e' },
{ NULL, 0, NULL, 0 }
};
void usage(void)
{
fprintf(stderr, "%s\n", passgeny_usage);
}
/*
* Strip `chars` from the end of string `str`. If `chars` is NULL, it defaults
* to whitespaces ("\r\n\t ").
*
* This function modifies `str` by patching the end of string with '\0'
*/
void str_rstrip(char *out, char *chars)
{
char *pend;
if (chars == NULL) chars = "\n\r\t ";
/* Remove new lines at the end of the password */
pend = out + strlen(out) - 1;
while (pend >= out)
{
if (strchr(chars, *pend) == NULL) break;
*pend = '\0';
pend--;
}
}
/*
* Safeily read a secret from the current terminal
*/
bool getpass_tty(char *out, size_t out_sz, const char *prompt)
{
struct termios tfo, tfc;
FILE *tty = NULL;
bool have_termios = false;
bool retval = false;
tty = fopen("/dev/tty", "r+");
if (tty == NULL)
{
fprintf(stderr, "Error opening /dev/tty: %s\n", strerror(errno));
goto error;
}
if (tcgetattr(fileno(tty), &tfo) < 0)
{
fprintf(stderr, "Error disabling tty echo (tcget): %s\n", strerror(errno));
goto error;
}
have_termios = true;
tfc = tfo;
tfc.c_lflag &= ~ECHO;
tfc.c_lflag |= ECHONL;
if (tcsetattr(fileno(tty), TCSANOW, &tfc) < 0)
{
fprintf(stderr, "Error disabling tty echo (tcset): %s\n", strerror(errno));
goto error;
}
fprintf(tty, "%s", prompt);
if (fgets(out, out_sz, tty) == NULL)
{
fprintf(stderr, "Error reading from /dev/tty: %s\n", strerror(errno));
fclose(tty);
goto error;
}
str_rstrip(out, NULL);
retval = true;
error:
if (have_termios)
{
tcsetattr(fileno(tty), TCSANOW, &tfo);
}
if (tty != NULL)
{
fclose(tty);
}
return retval;
}
/*
* Read the password from the environment variable `env`
*/
bool getpass_env(char *out, size_t out_sz, const char *env)
{
char *penv = getenv(env);
if (penv == NULL)
{
fprintf(stderr, "No environment variable `%s`.\n", env);
return false;
}
if (strlen(penv) >= out_sz)
{
fprintf(stderr, "Environment variable `%s` too long.\n", env);
return false;
}
/* Feeble attempt at secure erasing an environment variable */
char senv[strlen(penv) + 1];
memset(senv, 0x0, sizeof(senv));
setenv(env, senv, true);
strcpy(out, penv);
return true;
}
bool getpass_stdin(char *out, size_t out_sz)
{
if (fgets(out, out_sz, stdin) == NULL)
{
fprintf(stderr, "Error reading password from stdin: %s\n", strerror(errno));
return false;
}
str_rstrip(out, NULL);
return true;
}
int main(int argc, char *argv[])
{
char master_pass[128];
char out[1024];
char opt;
bool rc;
char password_mode = '\0'; /* Default password mode */
char *opt_e_env = NULL;
char *opt_p_pattern = NULL;
bool opt_v = false;
/*
* Parse options
*/
while ((opt = getopt_long(argc, argv, "vp:se:", passgeny_options, NULL)) != -1)
{
switch (opt)
{
case 'v': /* --verbose */
opt_v = true;
break;
case 'p': /* --pattern */
opt_p_pattern = optarg;
break;
case 's': /* --stdin */
if (password_mode != '\0')
{
fprintf(stderr, "--stdin and not allowed with --env\n");
exit(EXIT_FAILURE);
}
password_mode = 's';
break;
case 'e': /* --env */
if (password_mode != '\0')
{
fprintf(stderr, "--env and not allowed with --stdin\n");
exit(EXIT_FAILURE);
}
password_mode = 'e';
opt_e_env = optarg;
break;
default:
usage();
exit(EXIT_FAILURE);
}
}
if ((argc - optind) < 2)
{
fprintf(stderr, "Missing arguments\n");
usage();
exit(EXIT_FAILURE);
}
/*
* Read the master password
*/
switch (password_mode)
{
case 's':
rc = getpass_stdin(master_pass, sizeof(master_pass));
break;
case 'e':
rc = getpass_env(master_pass, sizeof(master_pass), opt_e_env);
break;
default:
rc = getpass_tty(master_pass, sizeof(master_pass), "Master password: ");
break;
}
if (!rc)
{
fprintf(stderr, "Error reading master password.\n");
exit(EXIT_FAILURE);
}
/*
* Generate the password
*/
passgeny_t pg;
passgeny_init(&pg, master_pass);
/* Nuke the master password from memory */
explicit_bzero(master_pass, sizeof(master_pass));
if (opt_p_pattern != NULL)
{
passgeny_set_pattern(&pg, opt_p_pattern);
}
rc = passgeny_generate(
&pg,
out,
sizeof(out),
argv[optind + 0],
argv[optind + 1],
(const char * const *)argv + optind + 2,
argc - optind - 2);
if (!rc)
{
fprintf(stderr, "Error generating password.\n");
exit(EXIT_FAILURE);
}
if (opt_v)
{
passgeny_dump_verbose(&pg);
}
printf("%s\n", out);
return 0;
}