diff options
Diffstat (limited to 'options.c')
-rw-r--r-- | options.c | 436 |
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; +} |