From c869c796191c49aa401b76eab12bed57424ed79a Mon Sep 17 00:00:00 2001 From: Mitja HORVAT Date: Sun, 14 Nov 2021 09:17:41 +0100 Subject: [PATCH] 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. --- c/cli/meson.build | 2 +- c/cli/src/main.c | 287 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 288 insertions(+), 1 deletion(-) create mode 100644 c/cli/src/main.c diff --git a/c/cli/meson.build b/c/cli/meson.build index e857722..c3a599a 100644 --- a/c/cli/meson.build +++ b/c/cli/meson.build @@ -1,2 +1,2 @@ -executable('passgeny', 'src/passgeny.c', dependencies: phogen_dep) +executable('passgeny', 'src/main.c', dependencies: [passgeny_dep]) diff --git a/c/cli/src/main.c b/c/cli/src/main.c new file mode 100644 index 0000000..e11c94c --- /dev/null +++ b/c/cli/src/main.c @@ -0,0 +1,287 @@ +#include +#include +#include +#include +#include +#include +#include + +#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; +}