aboutsummaryrefslogtreecommitdiff
path: root/options.c
diff options
context:
space:
mode:
Diffstat (limited to 'options.c')
-rw-r--r--options.c436
1 files changed, 436 insertions, 0 deletions
diff --git a/options.c b/options.c
new file mode 100644
index 0000000..3f82560
--- /dev/null
+++ b/options.c
@@ -0,0 +1,436 @@
+/*
+ * Copyright (c) 2018 Robin Krahl <robin.krahl@ireas.org>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "options.h"
+
+#include <confuse.h>
+#include <getopt.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+static cfg_opt_t cfg_opts[] = {
+ CFG_STR("algorithm", NULL, CFGF_NONE),
+ CFG_STR("model", NULL, CFGF_NONE),
+ CFG_INT("slot", 0, CFGF_NONE),
+ CFG_END()
+};
+
+static const char *optstr = "a:c:m:s:ghv";
+static struct option long_options[] = {
+ {"algorithm", required_argument, 0, 'a'},
+ {"config", required_argument, 0, 'c'},
+ {"model", required_argument, 0, 'm'},
+ {"slot", required_argument, 0, 's'},
+ {"generate", no_argument, 0, 'g'},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'v'},
+ {0}
+};
+
+static const char *DESCRIPTION =
+"nkotp -- one-time password generator for Nitrokey devices\n";
+static const char *USAGE =
+"Usage: nkotp [options] --generate | --help | --version\n";
+static const char *OPTIONS_GENERAL = "General options:\n"
+" -a algorithm, --algorithm algorithm\n"
+" set the OTP algorithm ('h' for HTOP, 't' for TOPT, default: 't')\n"
+" -c file, --config file\n"
+" set the configuration file\n"
+" -m model, --model model\n"
+" set the model to connect to ('a' for automatic, 'p' for Nitrokey\n"
+" Pro, 's' for Nitrokey Storage, default: 'a')\n"
+" -s slot, --slot slot\n"
+" set the OTP slot (default: 1)\n";
+static const char *OPTIONS_MODES = "Modes of operation:\n"
+" -g, --generate\n"
+" generate a one-time password\n"
+" -h, --help\n"
+" print this help message\n"
+" -v, --version\n"
+" print version information\n";
+
+static const char *ERRMSG_ALGORITHM = "Unsupported algorithm '%s'. Supported "
+"values are 'h' (HTOP) and 't' (TOTP).\n";
+static const char *ERRMSG_MODEL = "Unsupported model '%s'. Supported values "
+"are 'a' (automatic), 'p' (Nitrokey Pro) and 's' (Nitrokey Storage).\n";
+
+/* parse options from cli, config file or environment variables */
+static int parse_options_cli(int argc, char **argv, struct options *options);
+static int parse_options_config(struct options *options);
+static int parse_options_env(struct options *options);
+
+/* print help, usage or version messages */
+static void print_help(void);
+static void print_usage(void);
+static void print_version(void);
+
+/* set options */
+static int set_algorithm(struct options *options, const char *arg);
+static int set_config(struct options *options, const char *arg);
+static int set_mode(struct options *options, enum mode mode);
+static int set_model(struct options *options, const char *arg);
+static int set_slot(struct options *options, int arg);
+
+/* helpers */
+static char *get_home(void);
+static char *get_xdg_config_home(void);
+static char *get_default_config_file(void);
+
+int parse_options(int argc, char **argv, struct options *options)
+{
+ int err = 0;
+
+ if (!options)
+ return 1;
+
+ *options = (struct options) {0};
+
+ err = parse_options_cli(argc, argv, options);
+ if (!err)
+ err = parse_options_config(options);
+ if (!err)
+ err = parse_options_env(options);
+
+ /* set defaults */
+ if (options->alg == ALGORITHM_DEFAULT)
+ options->alg = ALGORITHM_TOTP;
+ if (options->model == MODEL_DEFAULT)
+ options->model = MODEL_AUTO;
+ if (options->slot == 0)
+ options->slot = 1;
+
+ /* check consistency */
+ if (options->slot < 0) {
+ fprintf(stderr, "The OTP slot must be positive!\n");
+ err = 1;
+ }
+ switch (options->alg) {
+ case ALGORITHM_HOTP:
+ if (options->slot > MAX_SLOT_HOTP) {
+ fprintf(stderr, "Nitrokey only supports %lu HOTP "
+ "slots.\n", MAX_SLOT_HOTP);
+ err = 1;
+ }
+ break;
+ case ALGORITHM_TOTP:
+ if (options->slot > MAX_SLOT_TOTP) {
+ fprintf(stderr, "Nitrokey only supports %lu TOTP "
+ "slots.\n", MAX_SLOT_TOTP);
+ err = 1;
+ }
+ break;
+ case ALGORITHM_DEFAULT:
+ default:
+ fprintf(stderr, "Programming error: invalid algorithm.\n");
+ err = 1;
+ }
+
+ return err;
+}
+
+void free_options(struct options *options)
+{
+ if (!options)
+ return;
+
+ if (options->cfg_file)
+ free(options->cfg_file);
+}
+
+static int parse_options_cli(int argc, char **argv, struct options *options)
+{
+ int err = 0;
+ int c;
+
+ while ((c = getopt_long(argc, argv, optstr, long_options, NULL))
+ != -1 && !err) {
+ switch (c) {
+ case 'a':
+ err = set_algorithm(options, optarg);
+ break;
+ case 'c':
+ err = set_config(options, optarg);
+ break;
+ case 'g':
+ err = set_mode(options, MODE_GENERATE);
+ break;
+ case 'h':
+ print_help();
+ return 0;
+ case 'm':
+ err = set_model(options, optarg);
+ break;
+ case 's':
+ err = set_slot(options, atoi(optarg));
+ break;
+ case 'v':
+ print_version();
+ return 0;
+ case '?':
+ print_usage();
+ return 1;
+ default:
+ fprintf(stderr, "Unexpected character code 0%o "
+ "returned by getopt.\n", c);
+ return 1;
+ }
+ }
+
+ if (!err) {
+ if (optind < argc || options->mode == MODE_NONE) {
+ print_usage();
+ err = 1;
+ }
+ }
+
+ return err;
+}
+
+static int parse_options_config(struct options *options)
+{
+ /* try to read config file from environment */
+ if (!options->cfg_file) {
+ char *env = getenv("NKOTP_CONFIG");
+ if (env)
+ options->cfg_file = strdup(env);
+ }
+
+ /* try to use the default config file */
+ if (!options->cfg_file) {
+ char *def_cfg_file = get_default_config_file();
+ if (def_cfg_file) {
+ struct stat s;
+ if (stat(def_cfg_file, &s) == 0) {
+ options->cfg_file = def_cfg_file;
+ } else {
+ free(def_cfg_file);
+ }
+ }
+ }
+
+ /* stop if no config file has been set */
+ if (!options->cfg_file)
+ return 0;
+
+ /* read config file */
+ int err = 0;
+ cfg_t *cfg = cfg_init(cfg_opts, CFGF_NONE);
+ if (cfg_parse(cfg, options->cfg_file) == CFG_PARSE_ERROR) {
+ err = 1;
+ goto cleanup;
+ }
+
+ if (options->alg == ALGORITHM_DEFAULT) {
+ char *val = cfg_getstr(cfg, "algorithm");
+ if (val)
+ err |= set_algorithm(options, val);
+ }
+
+ if (options->model == MODEL_DEFAULT) {
+ char *val = cfg_getstr(cfg, "model");
+ if (val)
+ err |= set_model(options, val);
+ }
+
+ if (options->slot == 0) {
+ int val = cfg_getint(cfg, "slot");
+ if (val)
+ err |= set_slot(options, val);
+ }
+
+cleanup:
+ cfg_free(cfg);
+ return err;
+}
+
+static int parse_options_env(struct options *options)
+{
+ int err = 0;
+ char *env;
+
+ if (options->alg == ALGORITHM_DEFAULT) {
+ env = getenv("NKOTP_ALGORITHM");
+ if (env)
+ err |= set_algorithm(options, env);
+ }
+
+ if (options->model == MODEL_DEFAULT) {
+ env = getenv("NKOTP_MODEL");
+ if (env)
+ err |= set_model(options, env);
+ }
+
+ if (options->slot == 0) {
+ env = getenv("NKOTP_SLOT");
+ if (env)
+ err |= set_slot(options, atoi(env));
+ }
+
+ return 0;
+}
+
+static void print_help(void)
+{
+ printf(DESCRIPTION);
+ printf("\n");
+ printf(USAGE);
+ printf("\n");
+ printf(OPTIONS_GENERAL);
+ printf("\n");
+ printf(OPTIONS_MODES);
+}
+
+static void print_usage(void)
+{
+ fprintf(stderr, USAGE);
+}
+
+static void print_version(void)
+{
+ printf("nkotp v" NKOTP_VERSION "\n");
+}
+
+static int set_algorithm(struct options *options, const char *arg)
+{
+ if (strlen(arg) != 1) {
+ fprintf(stderr, ERRMSG_ALGORITHM, arg);
+ return 1;
+ }
+
+ switch (arg[0]) {
+ case 'h':
+ options->alg = ALGORITHM_HOTP;
+ break;
+ case 't':
+ options->alg = ALGORITHM_TOTP;
+ break;
+ default:
+ fprintf(stderr, ERRMSG_ALGORITHM, arg);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int set_config(struct options *options, const char *arg)
+{
+ options->cfg_file = strdup(arg);
+ if (!options->cfg_file) {
+ fprintf(stderr, "Out of memory.\n");
+ return 1;
+ }
+ return 0;
+}
+
+static int set_mode(struct options *options, enum mode mode)
+{
+ if (options->mode != MODE_NONE) {
+ fprintf(stderr, "You may only set one mode of operation.\n");
+ return 1;
+ }
+ options->mode = mode;
+ return 0;
+}
+
+static int set_model(struct options *options, const char *arg)
+{
+ if (strlen(arg) != 1) {
+ fprintf(stderr, ERRMSG_MODEL, arg);
+ return 1;
+ }
+
+ switch (arg[0]) {
+ case 'a':
+ options->model = MODEL_AUTO;
+ break;
+ case 's':
+ options->model = MODEL_STORAGE;
+ break;
+ case 'p':
+ options->model = MODEL_PRO;
+ break;
+ default:
+ fprintf(stderr, ERRMSG_MODEL, arg);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int set_slot(struct options *options, int arg)
+{
+ if (arg <= 0) {
+ fprintf(stderr, "The slot must be positive.\n");
+ return 1;
+ }
+ options->slot = arg;
+ return 0;
+}
+
+static char *get_home(void)
+{
+ char *env = getenv("HOME");
+ if (env)
+ return strdup(env);
+
+ struct passwd *pw = getpwuid(getuid());
+ return strdup(pw->pw_dir);
+}
+
+static char *get_xdg_config_home(void)
+{
+ char *env = getenv("XDG_CONFIG_HOME");
+ if (env)
+ return strdup(env);
+
+ char *home = get_home();
+ char *path = NULL;
+ if (home) {
+ int len = snprintf(NULL, 0, "%s/.config", home) + 1;
+ if (len <= 0)
+ goto cleanup;
+
+ path = calloc(sizeof *path, len);
+ if (!path)
+ goto cleanup;
+ if (snprintf(path, len, "%s/.config", home) + 1 != len) {
+ free(path);
+ path = NULL;
+ }
+ }
+
+cleanup:
+ free(home);
+ return path;
+}
+
+static char *get_default_config_file(void)
+{
+ char *config_home = get_xdg_config_home();
+ if (!config_home)
+ return NULL;
+
+ char *file = NULL;
+ int len = snprintf(NULL, 0, "%s/nkotp/config", config_home) + 1;
+ if (len <= 0)
+ goto cleanup;
+
+ file = calloc(sizeof *file, len);
+ if (!file)
+ goto cleanup;
+ if (snprintf(file, len, "%s/nkotp/config", config_home) + 1 != len) {
+ free(file);
+ file = NULL;
+ }
+
+cleanup:
+ free(config_home);
+ return file;
+}