author | Olivier Brunel
<jjk@jjacky.com> 2023-04-20 17:37:00 UTC |
committer | Olivier Brunel
<jjk@jjacky.com> 2023-04-25 15:02:08 UTC |
parent | 7402b4b19a8645a439d6b23ccfd09f0e744d0a0d |
src/include/ssp.h | +9 | -0 |
src/ssp/add.c | +50 | -22 |
src/ssp/commands.c | +2 | -0 |
src/ssp/edit.c | +20 | -23 |
src/ssp/import.c | +169 | -0 |
diff --git a/src/include/ssp.h b/src/include/ssp.h index d938fed..fac4f72 100644 --- a/src/include/ssp.h +++ b/src/include/ssp.h @@ -6,6 +6,7 @@ #include <skalibs/stralloc.h> #include <limb/cdb.h> +#include <limb/cdbmake.h> enum { OPT_HELP = 1 << 0, @@ -70,6 +71,9 @@ ssize_t unesc(char *dst, size_t dlen, const char *sce, size_t slen); const char *get_entry_name(const char *s, struct ssp *ctx); /* database.c */ +int rebuild_cdb(cdbmaker_sa *mkr, const char *oldkey, const char *newkey, + const struct otp *otp, struct ssp *ctx); +int write_db(char *data, size_t dlen, struct ssp *ctx); void rebuild_db(const char *oldkey, const char *newkey, const struct otp *otp, struct ssp *ctx); int open_db(struct ssp *ctx); @@ -81,6 +85,11 @@ int open_db(struct ssp *ctx); #define db_entry(ctx) (ctx)->key.s #define db_otp(ctx) ((const struct otp *) (ctx)->val.s) +/* add.c */ +int validate_algo(int *first, const char *data, size_t len); +int validate_digits(const char *data, size_t dlen); +int validate_precision(const char *data, size_t dlen); + /* show.c */ void show_entry(unsigned options, struct ssp *ctx); void show_entry_ini(unsigned options, struct ssp *ctx); diff --git a/src/ssp/add.c b/src/ssp/add.c index 8ece1a3..c69fa08 100644 --- a/src/ssp/add.c +++ b/src/ssp/add.c @@ -17,6 +17,37 @@ struct add { const char *comments; }; +int +validate_algo(int *first, const char *data, size_t dlen) +{ + int algo = byte_get_match(first, data, dlen, algos); + if (algo < 0) + warn("invalid algorithm: ", LEN(data, dlen)); + return algo; +} + +int +validate_digits(const char *data, size_t dlen) +{ + int digits = *data - '0'; + if (dlen > 1 || digits < 5 || digits > 9) { + warn("invalid number of digits, must be between 5 and 9: ", LEN(data, dlen)); + return -1; + } + return digits; +} + +int +validate_precision(const char *data, size_t dlen) +{ + u16 u; + if (u16_scan(&u, data) != dlen || u < 10 || u > 120) { + warn("invalid precision argument, must be between 10 and 120: ", LEN(data, dlen)); + return -1; + } + return u; +} + COMMAND(add, "Add a new entry", "<entry> [OPTION..] <secret>", " -a, --algo ALGO Set ALGO as hashing algorithm [sha256]\n" @@ -39,50 +70,47 @@ parse_cmdline(int argc, const char *argv[], const char usage[], struct add *add) }; struct parseopt po = { 0 }; - int c; + int c, r; while ((c = parseopt(argc, argv, options, 0, &po))) switch (c) { case 'a': { - int i, first = -1; - i = byte_get_match(&first, po.arg, strlen(po.arg), algos); - if (i < 0) { - if (!strcmp(po.arg, "list")) { - out("Available algorithms:"); - for (i = 0; algos[i]; ++i) - out("- ", algos[i]); - diecmdusage(0, usage, &command_add); - } - warn("invalid algorithm: ", po.arg); + if (!strcmp(po.arg, "list")) { + out("Available algorithms:"); + for (r = 0; algos[r]; ++r) + out("- ", algos[r]); + diecmdusage(0, usage, &command_add); + } + + int first = -1; + r = validate_algo(&first, po.arg, strlen(po.arg)); + if (r < 0) { if (first > -1) list_matches(out_putmsg, OLVL_NORMAL, "did you mean ", NULL, " or ", " ?", po.arg, strlen(po.arg), first, algos); diecmdusage(EX_USAGE, usage, &command_add); } - add->otp.algo = i; + add->otp.algo = r; } break; case 'c': add->comments = po.arg; break; case 'd': - add->otp.type = TYPE_COUNTER; - add->otp.digits = *po.arg - '0'; - if (po.arg[1] || add->otp.digits < 5 || add->otp.digits > 9) { - warn("invalid number of digits, must be between 5 and 9: ", po.arg); + r = validate_digits(po.arg, strlen(po.arg)); + if (r < 0) diecmdusage(EX_USAGE, usage, &command_add); - } + add->otp.type = TYPE_COUNTER; + add->otp.digits = r; break; case 't': if (!po.arg) { add->otp.precision = 30; } else { - u16 u; - if (!u160_scan(&u, po.arg) || u < 10 || u > 120) { - warn("invalid precision argument, must be between 10 and 120: ", po.arg); + r = validate_precision(po.arg, strlen(po.arg)); + if (r < 0) diecmdusage(EX_USAGE, usage, &command_add); - } - add->otp.precision = u; + add->otp.precision = r; } add->otp.type = TYPE_TIME; break; diff --git a/src/ssp/commands.c b/src/ssp/commands.c index ab68f14..78c3bd3 100644 --- a/src/ssp/commands.c +++ b/src/ssp/commands.c @@ -5,6 +5,7 @@ extern struct command command_add; extern struct command command_edit; +extern struct command command_import; extern struct command command_list; extern struct command command_remove; extern struct command command_rename; @@ -13,6 +14,7 @@ extern struct command command_show; struct command *commands[] = { &command_add, &command_edit, + &command_import, &command_list, &command_remove, &command_rename, diff --git a/src/ssp/edit.c b/src/ssp/edit.c index 3ba118b..d368ab0 100644 --- a/src/ssp/edit.c +++ b/src/ssp/edit.c @@ -42,59 +42,56 @@ parse_cmdline(int argc, const char *argv[], const char usage[], struct edit *edi }; struct parseopt po = { 0 }; - int c; + int c, r; while ((c = parseopt(argc, argv, options, 0, &po))) switch (c) { case 'a': { - int i, first = -1; - i = byte_get_match(&first, po.arg, strlen(po.arg), algos); - if (i < 0) { - if (!strcmp(po.arg, "list")) { - out("Available algorithms:"); - for (i = 0; algos[i]; ++i) - out("- ", algos[i]); - diecmdusage(0, usage, &command_edit); - } - warn("invalid algorithm: ", po.arg); + if (!strcmp(po.arg, "list")) { + out("Available algorithms:"); + for (r = 0; algos[r]; ++r) + out("- ", algos[r]); + diecmdusage(0, usage, &command_edit); + } + + int first = -1; + r = validate_algo(&first, po.arg, strlen(po.arg)); + if (r < 0) { if (first > -1) list_matches(out_putmsg, OLVL_NORMAL, "did you mean ", NULL, " or ", " ?", po.arg, strlen(po.arg), first, algos); diecmdusage(EX_USAGE, usage, &command_edit); } - edit->otp.algo = i; + edit->otp.algo = r; } break; case 'c': edit->comments = po.arg; break; case 'd': - edit->otp.type = TYPE_COUNTER; if (!po.arg) { edit->otp.digits = 6; } else { - edit->otp.digits = *po.arg - '0'; - if (po.arg[1] || edit->otp.digits < 5 || edit->otp.digits > 9) { - warn("invalid number of digits, must be between 5 and 9: ", po.arg); + r = validate_digits(po.arg, strlen(po.arg)); + if (r < 0) diecmdusage(EX_USAGE, usage, &command_edit); - } + edit->otp.digits = r; } + edit->otp.type = TYPE_COUNTER; break; case 's': edit->secret = po.arg; break; case 't': - edit->otp.type = TYPE_TIME; if (!po.arg) { edit->otp.precision = 30; } else { - u16 u; - if (!u160_scan(&u, po.arg) || u < 10 || u > 120) { - warn("invalid precision argument, must be between 10 and 120: ", po.arg); + r = validate_precision(po.arg, strlen(po.arg)); + if (r < 0) diecmdusage(EX_USAGE, usage, &command_edit); - } - edit->otp.precision = u; + edit->otp.precision = r; } + edit->otp.type = TYPE_TIME; break; case -1: diecmdusage(EX_USAGE, usage, &command_edit); diff --git a/src/ssp/import.c b/src/ssp/import.c new file mode 100644 index 0000000..5da09eb --- /dev/null +++ b/src/ssp/import.c @@ -0,0 +1,169 @@ +/* This file is part of ssp https://lila.oss/ssp + * Copyright (C) 2023 Olivier Brunel jjk@jjacky.com */ +/* SPDX-License-Identifier: GPL-2.0-only */ +#include <errno.h> +#include <skalibs/stralloc.h> +#include <limb/base32.h> +#include <limb/cdbmake.h> +#include <limb/command.h> +#include <limb/copa.h> +#include <limb/djbunix.h> +#include <limb/exitcode.h> +#include <limb/output.h> +#include <limb/u16.h> +#include "ssp.h" + + +COMMAND(import, "Import entries from file", "<file>", NULL); + +int +import_main(int argc, const char *argv[], const char *env[], const char usage[], void *ctx_) +{ + struct ssp *ctx = ctx_; + + if (argc != 2) + diecmdusage(EX_USAGE, usage, &command_import); + + int fd = open_parsed_name(argv[1], open_readb); + if (fd < 0) + diefusys(EX_NOINPUT, "open ", ESC, argv[1], ESC); + + stralloc sa = STRALLOC_ZERO; + if (!slurp(&sa, fd)) + diefusys(exitcode_from_errno(errno), "read ", ESC, argv[1], ESC); + + cdbmaker_sa mkr = CDBMAKER_SA_ZERO; + struct otp otp; + const char *name = NULL; + size_t nlen; + const char *secret; + ssize_t slen; + size_t slen32; + const char *comments; + size_t clen; + struct copa copa; + int r, n = 0; + copa_init(sa.s, sa.len, &copa); + while ((r = copa_next(&copa)) >= 0) { + if (!r || copa_is_section(&copa)) { + if (name) { + if (!slen) + die(EX_DATA_ERR, "missing secret in section/entry ", + ESC, LEN(name, nlen), ESC); + + char buf[sizeof(otp) + slen + clen + 1]; + struct otp *p = (struct otp *) buf; + memcpy(p, &otp, sizeof(otp)); + p->slen = slen; + base32_scan(p->data, secret, slen32); + if (clen >= 2 && comments[0] == '"' && comments[clen - 1] == '"') { + ++comments; + clen -= 2; + if (unesc(p->data + slen, clen, comments, clen) < 0) + diefusys(EX_DATA_ERR, "read comments in section/entry ", + ESC, LEN(name, nlen), ESC); + } else { + memcpy(p->data + slen, comments, clen + 1); + } + + /* if we've already imported an entry, switch cdb : + * - our newly generated one becomes the active db, + * - and we'll reuse the memory for our maker + */ + if (mkr.sa.s) { + stralloc sa = ctx->sa; + ctx->sa = mkr.sa; + cdb_init_frommem(&ctx->cdb, ctx->sa.s, ctx->sa.len); + mkr.sa = sa; + } + + /* open db if we haven't already */ + if (!ctx->cdb.map && open_db(ctx) < 0) + diefu(exitcode_from_errno(errno), "open database"); + + char key[nlen + 1]; + if (unesc(key, nlen, name, nlen) < 0) + dief(EX_DATA_ERR, "invalid section/entry name: ", LEN(name, nlen)); + out("Importing ", ESC, key, ESC, "..."); + if (!rebuild_cdb(&mkr, NULL , key, p, ctx)) + diefu(exitcode_from_errno(errno), "import data", + (errno == ENOMEM) ? ": Out of memory" : NULL); + ++n; + } + if (!r) break; + + name = copa_name(&copa); + nlen = copa_nlen(&copa); + otp.algo = ALGO_SHA256; + otp.type = TYPE_COUNTER; + otp.digits = 6; + comments = ""; + slen = clen = 0; + } else if (!name) { + dief(EX_DATA_ERR, "data before section/entry"); + } else { + int r; + + /* all options must have a value */ + if (!copa_vlen(&copa)) + dief(EX_DATA_ERR, "no value for option ", + ESC, LEN(copa_name(&copa), copa_nlen(&copa)), ESC, + " in section/entry ", ESC, LEN(name, nlen), ESC); + + if (copa_nlen(&copa) == 4 && !strncmp(copa_name(&copa), "algo", 4)) { + r = validate_algo(NULL, copa_value(&copa), copa_vlen(&copa)); + if (r < 0) goto err; + otp.algo = r; + } else if (otp.type == TYPE_COUNTER && copa_nlen(&copa) == 6 + && !strncmp(copa_name(&copa), "digits", 6)) { + r = validate_digits(copa_value(&copa), copa_vlen(&copa)); + if (r < 0) goto err; + otp.digits = r; + } else if (copa_nlen(&copa) == 4 && !strncmp(copa_name(&copa), "time", 4)) { + if (copa_vlen(&copa) == 1 && copa_value(&copa)[0] == '1') + otp.type = TYPE_TIME; + else + dief(EX_DATA_ERR, "invalid value for \"time\" in section/entry ", + ESC, LEN(name, nlen), ESC, ": ", + LEN(copa_value(&copa), copa_vlen(&copa))); + } else if (otp.type == TYPE_TIME && copa_nlen(&copa) == 9 + && !strncmp(copa_name(&copa), "precision", 9)) { + r = validate_precision(copa_value(&copa), copa_vlen(&copa)); + if (r < 0) goto err; + otp.precision = r; + } else if (copa_nlen(&copa) == 6 && !strncmp(copa_name(&copa), "secret", 6)) { + slen = base32_scan(NULL, copa_value(&copa), copa_vlen(&copa)); + if (slen < 0) + dief(EX_DATA_ERR, "invalid secret in section/entry ", + ESC, LEN(name, nlen), ESC, ": Not in base32: ", + LEN(copa_value(&copa), copa_vlen(&copa))); + secret = copa_value(&copa); + slen32 = copa_vlen(&copa); + } else if (copa_nlen(&copa) == 8 && !strncmp(copa_name(&copa), "comments", 8)) { + comments = copa_value(&copa); + clen = copa_vlen(&copa); + } else { + dief(EX_DATA_ERR, "invalid data, unknown value ", + ESC, LEN(copa_name(&copa), copa_nlen(&copa)), ESC, + " in section/entry ", ESC, LEN(name, nlen), ESC); + } + } + } + if (r < 0) + diefu(EX_DATA_ERR, "parse ", ESC, argv[1], ESC); + + if (mkr.sa.s && !write_db(mkr.sa.s, mkr.sa.len, ctx)) + diefusys((errno == EINVAL) ? EX_DATA_ERR : EX_CANTCREAT, "save database"); + cdbmaker_sa_free(&mkr); + + if (!n) + dief(EX_DATA_ERR, "nothing to import"); + out("Successfully imported ", PUTMSG_UINT(n), " entries."); + + stralloc_free(&sa); + return 0; + +err: + stralloc_free(&sa); + dief(EX_DATA_ERR, "invalid section/entry ", ESC, LEN(name, nlen), ESC); +}