author | Olivier Brunel
<jjk@jjacky.com> 2023-05-19 14:10:26 UTC |
committer | Olivier Brunel
<jjk@jjacky.com> 2023-07-10 09:33:53 UTC |
parent | aed379eab0b8e039821d7e9dd8a93c284e3d80c6 |
limb | +0 | -1 |
src/include/ssp.h | +5 | -21 |
src/ssp/add.c | +91 | -90 |
src/ssp/database.c | +95 | -120 |
src/ssp/edit.c | +89 | -93 |
src/ssp/export.c | +27 | -22 |
src/ssp/get.c | +119 | -101 |
src/ssp/import.c | +103 | -42 |
src/ssp/list.c | +50 | -42 |
src/ssp/remove.c | +45 | -11 |
src/ssp/rename.c | +60 | -18 |
src/ssp/show.c | +41 | -40 |
src/ssp/ssp.c | +23 | -60 |
diff --git a/limb b/limb deleted file mode 120000 index c00f7b8..0000000 --- a/limb +++ /dev/null @@ -1 +0,0 @@ -../limb \ No newline at end of file diff --git a/src/include/ssp.h b/src/include/ssp.h index c79f90c..51c5c24 100644 --- a/src/include/ssp.h +++ b/src/include/ssp.h @@ -13,8 +13,7 @@ enum { OPT_HELP = 1 << 0, }; -#define SSP_MAGIC 0x53535000 -#define SSP_MAGIC_INT 0x73737042 +#define SSP_MAGIC 0xa5e9f801 #define KEY_LEN 32 #define SALT_LEN 32 @@ -27,7 +26,7 @@ struct ssp { cdb cdb; cdb_data key; cdb_data val; - const char *db; + size_t cdboff; unsigned int options; u32 pos; char pwd[PWD_MAX + 1]; @@ -37,23 +36,9 @@ enum { TYPE_COUNTER = 0, TYPE_TIME, TYPE_UNKNOWN = (u8) -1, - /* note a "real" type, used by get to know when the counter value has been - * manually passed and therefore no db update should be done */ - TYPE_COUNTER_MANUAL = TYPE_UNKNOWN - 1 }; -enum { - ALGO_SHA1 = 0, - ALGO_SHA256, - ALGO_SHA512, - ALGO_SHA3_224, - ALGO_SHA3_256, - ALGO_SHA3_512, - ALGO_BLAKE3, - ALGO_UNKNOWN = (u8) -1 -}; - -extern const char *algos[]; +#define ALGO_UNKNOWN ((u8) -1) struct otp { @@ -76,8 +61,6 @@ struct otp /* ssp.c */ int exitcode_from_errno(int e); int run_command(const char *name, int argc, const char *argv[], const char *env[], void *ctx); -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, @@ -85,8 +68,9 @@ int rebuild_cdb(cdbmaker_sa *mkr, const char *oldkey, const char *newkey, 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); +void close_db(struct ssp *ctx); -#define db_file(ctx) (((ctx)->db) ? (ctx)->db : (ctx)->sa.s) +#define db_file(ctx) (ctx)->sa.s #define db_reset(ctx) (ctx)->pos = CDB_TRAVERSE_INIT() #define db_next(ctx) cdb_traverse_next(&(ctx)->cdb, &(ctx)->key, &(ctx)->val, &(ctx)->pos) #define db_find(entry,ctx) ((ctx)->key.s = entry, (ctx)->key.len = strlen(entry) + 1, \ diff --git a/src/ssp/add.c b/src/ssp/add.c index 5e3ade8..b2ee8fc 100644 --- a/src/ssp/add.c +++ b/src/ssp/add.c @@ -7,33 +7,38 @@ #include <limb/command.h> #include <limb/esc.h> #include <limb/exitcode.h> +#include <limb/hasher.h> /* ALGO_* */ +#include <limb/loadopt.h> #include <limb/output.h> -#include <limb/parseopt.h> +#include <limb/stralloc.h> #include <limb/u16.h> #include "ssp.h" struct add { + stralloc sa; + const char *secret; + size_t cmtoff; + size_t entoff; struct otp otp; - const char *comments; }; int validate_algo(int *first, const char *data, size_t dlen) { - dbg("validating algo ", LEN(data, dlen), "..."); - int algo = byte_get_match(first, data, dlen, algos); + dbg("validating algo ", ESC, PMLEN(data, dlen), ESC); + int algo = byte_get_match(first, data, dlen, (const char **) algos); if (algo < 0) - warn("invalid algorithm: ", LEN(data, dlen)); + warn("invalid algorithm: ", PMLEN(data, dlen)); return algo; } int validate_digits(const char *data, size_t dlen) { - dbg("validating digits ", LEN(data, dlen), "..."); + dbg("validating digits ", ESC, PMLEN(data, dlen), ESC); 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)); + warn("invalid number of digits, must be between 5 and 9: ", PMLEN(data, dlen)); return -1; } return digits; @@ -42,54 +47,61 @@ validate_digits(const char *data, size_t dlen) int validate_counter(u64 *c, const char *data, size_t dlen) { + dbg("validating counter", ESC, PMLEN(data, dlen), ESC); if (u64_scan(c, data) != dlen) { - warn("invalid counter value: ", LEN(data, dlen)); + warn("invalid counter value: ", PMLEN(data, dlen)); return -1; } - return 1; + return 0; } int validate_precision(const char *data, size_t dlen) { - dbg("validating precision ", LEN(data, dlen), "..."); + dbg("validating precision ", ESC, PMLEN(data, dlen), ESC); u16 u; if (u16_scan(&u, data) != dlen || u < 10 || u > 59) { - warn("invalid precision argument, must be between 10 and 59: ", LEN(data, dlen)); + warn("invalid precision argument, must be between 10 and 59: ", PMLEN(data, dlen)); return -1; } return u; } -COMMAND(add, "Add a new entry", - "<entry> [OPTION..] <secret>", +enum { + ARGID_ENTRY = OPTID_FIRST, + ARGID_SECRET +}; + +COMMAND(add, "Add a new entry", "[OPTION..] <entry> <secret>", " -a, --algo ALGO Set ALGO as hashing algorithm [sha1]\n" " Use 'list' to list available algorithms\n" " -C, --comments COMMENTS Set COMMENTS as entry's comments\n" -" -c, --counter NUM Set to counter-based (HOTP) with counter from NUM [1]\n" +" -c, --counter NUM Set to counter-based (HOTP) with counter of NUM [1]\n" " -d, --digits NUM Set to return an OTP of NUM digits [6]\n" " -t, --time[=SECS] Set to time-based (TOTP) with precision of SECS [30]\n" ); - static int -parse_cmdline(int argc, const char *argv[], const char usage[], struct add *add) +parse_cmdline(int argc, const char *argv[], const char usage[], struct add *ctx) { const struct option options[] = { - OPTION_ARG_REQ ('a', "algo", 0, OPTID_SHORTOPT), - OPTION_ARG_REQ ('C', "comments", 0, OPTID_SHORTOPT), - OPTION_ARG_REQ ('c', "counter", 0, OPTID_SHORTOPT), - OPTION_ARG_REQ ('d', "digits", 0, OPTID_SHORTOPT), - OPTION_ARG_OPT ('t', "time", 0, OPTID_SHORTOPT), - OPTION_DONE + OPTION_ARG_REQ ('a', "algo", 0, OPTID_SHORTOPT), + OPTION_ARG_REQ ('C', "comments", OPT_PATH, OPTID_SHORTOPT), + OPTION_ARG_REQ ('c', "counter", 0, OPTID_SHORTOPT), + OPTION_ARG_REQ ('d', "digits", 0, OPTID_SHORTOPT), + OPTION_ARG_OPT ('t', "time", 0, OPTID_SHORTOPT), + LOADOPT_ARGUMENTS, + ARGUMENT_REQ( "entry", OPT_PATH, ARGID_ENTRY), + ARGUMENT_REQ( "secret", 0, ARGID_SECRET), + LOADOPT_DONE }; - struct parseopt po = { 0 }; + struct loadopt lo = LOADOPT_ZERO; int c, r; - while ((c = parseopt(argc, argv, options, 0, &po))) switch (c) { + while ((c = loadopt(&ctx->sa, argc, argv, options, 0, NULL, 0, &lo))) switch (c) { case 'a': { - if (!strcmp(PO_ARG(&po), "list")) { + if (!strcmp(LO_ARG(&lo), "list")) { out("Available algorithms:"); for (r = 0; algos[r]; ++r) out("- ", algos[r]); @@ -97,114 +109,103 @@ parse_cmdline(int argc, const char *argv[], const char usage[], struct add *add) } int first = -1; - r = validate_algo(&first, PO_ARG(&po), strlen(PO_ARG(&po))); + r = validate_algo(&first, LO_ARG(&lo), strlen(LO_ARG(&lo))); if (r < 0) { if (first > -1) list_matches(obuffer_1, OLVL_NORMAL, "did you mean ", - NULL, " or ", " ?", PO_ARG(&po), strlen(PO_ARG(&po)), - first, algos); + NULL, " or ", " ?", LO_ARG(&lo), strlen(LO_ARG(&lo)), + first, (const char **) algos); diecmdusage(EX_USAGE, usage, &command_add); } - add->otp.algo = r; + ctx->otp.algo = r; } break; case 'C': - add->comments = PO_ARG(&po); + ctx->cmtoff = LO_OFF(&lo); break; case 'c': - if (validate_counter(&add->otp.c, PO_ARG(&po), strlen(PO_ARG(&po))) < 0) + if (validate_counter(&ctx->otp.c, LO_ARG(&lo), strlen(LO_ARG(&lo))) < 0) diecmdusage(EX_USAGE, usage, &command_add); - add->otp.type = TYPE_COUNTER; + ctx->otp.type = TYPE_COUNTER; break; case 'd': - r = validate_digits(PO_ARG(&po), strlen(PO_ARG(&po))); + r = validate_digits(LO_ARG(&lo), strlen(LO_ARG(&lo))); if (r < 0) diecmdusage(EX_USAGE, usage, &command_add); - add->otp.digits = r; + ctx->otp.digits = r; break; case 't': - if (!PO_ARG(&po)) { - add->otp.t.precision = 30; + if (!LO_ARG(&lo)) { + ctx->otp.t.precision = 30; } else { - r = validate_precision(PO_ARG(&po), strlen(PO_ARG(&po))); + r = validate_precision(LO_ARG(&lo), strlen(LO_ARG(&lo))); if (r < 0) diecmdusage(EX_USAGE, usage, &command_add); - add->otp.t.precision = r; + ctx->otp.t.precision = r; } - add->otp.type = TYPE_TIME; + ctx->otp.type = TYPE_TIME; break; + + case ARGID_ENTRY: + ctx->entoff = LO_OFF(&lo); + break; + case ARGID_SECRET: + ctx->secret = LO_ARG(&lo); + break; + case -1: diecmdusage(EX_USAGE, usage, &command_add); default: - die(EX_SOFTWARE, "unexpected return value ", PUTMSG_INT(c), " from parseopt"); + die(EX_SOFTWARE, "unexpected return value ", PMINT(c), " from loadopt"); }; - return PO_CUR(&po); + return LO_CUR(&lo); } int -add_main(int argc, const char *argv[], const char *env[], const char usage[], void *ctx_) +add_main(int argc, const char *argv[], const char *env[], const char usage[], void *ssp_) { - struct ssp *ctx = ctx_; - struct add add = { .otp = OTP_DEFAULT, .comments = "" }; - const char *entry; - int i; - - if (argc == 1) { - warn("argument \"entry\" missing"); - diecmdusage(EX_USAGE, usage, &command_add); - } - entry = get_entry_name(argv[1], ctx); - if (!entry) - diefusys(exitcode_from_errno(errno), "parse entry's name"); - if (*entry == '-') { - warn("argument \"entry\" cannot start with a '-'"); - diecmdusage(EX_DATA_ERR, usage, &command_add); - } + struct ssp *ssp = ssp_; + struct add ctx = { .sa = STRALLOC_ZERO, .otp = OTP_DEFAULT, + .secret = NULL, .cmtoff = (size_t) -1, .entoff = 0 }; - --argc; - ++argv; - i = parse_cmdline(argc, argv, usage, &add); - - if (argc == i) { - warn("argument \"secret\" missing"); - diecmdusage(EX_USAGE, usage, &command_add); - } else if (argc > i + 1) { - warn("too many arguments"); - diecmdusage(EX_USAGE, usage, &command_add); - } + (void) env; + parse_cmdline(argc, argv, usage, &ctx); - const char *secret = argv[i]; - ssize_t l = base32_scan(NULL, secret, strlen(secret), 0); + ssize_t l = base32_scan(NULL, ctx.secret, strlen(ctx.secret), 0); if (l < 0) dief(EX_DATA_ERR, "invalid secret: Not in base32"); if (!l) dief(EX_DATA_ERR, "secret is empty"); - size_t clen = strlen(add.comments); - char buf[sizeof(add.otp) + l + clen + 1]; - struct otp *p = (struct otp *) buf; - memcpy(p, &add.otp, sizeof(add.otp)); + size_t clen = (ctx.cmtoff != (size_t) -1) ? strlen(ctx.sa.s + ctx.cmtoff) : 0; + size_t otplen = sizeof(ctx.otp) + l + clen + 1; + + if (!stralloc_readyplus(&ctx.sa, otplen)) + diefusys(EX_TEMPFAIL, "add entry ", ESC, ctx.sa.s + ctx.entoff, ESC); + + struct otp *p = (struct otp *) (ctx.sa.s + ctx.sa.len); + ctx.sa.len += otplen; + memcpy(p, &ctx.otp, sizeof(ctx.otp)); p->slen = l; - base32_scan(p->data, secret, strlen(secret), 0); - if (clen >= 2 && add.comments[0] == '"' && add.comments[clen - 1] == '"') { - ++add.comments; - clen -= 2; - dbg("unescaping comments"); - l = unesc(p->data + l, clen, add.comments, clen); - if (l < 0) - diefusys(EX_DATA_ERR, "read comments"); - } else { - memcpy(p->data + l, add.comments, clen + 1); - } + base32_scan(p->data, ctx.secret, strlen(ctx.secret), 0); + if (clen) + memcpy(p->data + l, ctx.sa.s + ctx.cmtoff, clen + 1); + else + p->data[l] = 0; - if (open_db(ctx) < 0) + if (open_db(ssp) < 0) diefu(exitcode_from_errno(errno), "open database"); - out("Adding entry ", ESC, entry, ESC, "... "); - rebuild_db(NULL, entry, p, ctx); + const char *entry = ctx.sa.s + ctx.entoff; + out("Adding entry ", ESC, entry, ESC, "..."); + rebuild_db(NULL, entry, p, ssp); + + dbg("showing added entry"); + close_db(ssp); const char *av[] = { NULL, entry }; - run_command("show", sizeof(av) / sizeof(*av), av, env, ctx); + run_command("show", sizeof(av) / sizeof(*av), av, env, ssp); + stralloc_free(&ctx.sa); return 0; } diff --git a/src/ssp/database.c b/src/ssp/database.c index 0602571..a3ecbdd 100644 --- a/src/ssp/database.c +++ b/src/ssp/database.c @@ -2,123 +2,106 @@ * Copyright (C) 2023 Olivier Brunel jjk@jjacky.com */ /* SPDX-License-Identifier: GPL-2.0-only */ #include <errno.h> +#include <fcntl.h> /* AT_FDCWD */ #include <string.h> -#include <skalibs/random.h> -#include <limb/djbunix.h> +#include <unistd.h> #include <limb/bytestr.h> #include <limb/cdb.h> #include <limb/cdbmake.h> -#include <limb/chacha20.h> #include <limb/exitcode.h> -#include <limb/hasher_sha3_256.h> +#include <limb/hasher.h> #include <limb/output.h> -#include <limb/pbkdf2.h> +#include <limb/shldata-rw.h> +#include <limb/stralloc.h> #include <limb/term.h> #include <limb/u32.h> #include "ssp.h" -int -get_key(char key[KEY_LEN], const char salt[SALT_LEN], const char *prompt, struct ssp *ctx) +static ssize_t +get_pwd(const char *prompt, struct ssp *ctx) { ssize_t plen; if (!*ctx->pwd) { dbg("asking for password"); + if (prompt) adde("[", PUTMSG_TOGGLE_ESC, db_file(ctx), PUTMSG_TOGGLE_ESC, "] "); plen = ask_password(ctx->pwd, sizeof(ctx->pwd), prompt); - if (plen < 0) return 0; } else { + dbg("password known"); plen = strlen(ctx->pwd); } - - dbg("deriving encryption key"); - /* get encryption key */ - pbkdf2(key, KEY_LEN, sha3_256, ctx->pwd, plen, salt, SALT_LEN, ITER); - - return 1; + return plen; } int open_db(struct ssp *ctx) { - dbg("opening database..."); - int fd = open_read(db_file(ctx)); - if (fd < 0) { - /* no file is not an error at this point */ - if (errno == ENOENT) { - ctx->cdb = cdb_zero; - return 0; - } - warnusys("open ", ESC, db_file(ctx), ESC); - return -1; - } + dbg("opening database"); - size_t salen = ctx->sa.len; - if (!slurp(&ctx->sa, fd)) { - warnusys("read ", ESC, db_file(ctx), ESC); - fd_close(fd); + if (ctx->cdb.map) { + warnu("open ", ESC, db_file(ctx), ESC, ": database already opened"); + errno = EALREADY; return -1; } - fd_close(fd); - dbg("checking magic: ", HEX(ctx->sa.s + salen, sizeof(u32))); - /* check our magic */ - u32p_be((u32 *) (ctx->sa.s + salen)); - if (* (u32 *) (ctx->sa.s + salen) != SSP_MAGIC) { - warnu("read ", ESC, db_file(ctx), ESC, ": not an SSP database"); - ctx->sa.len = salen; - return (errno = ENOMSG, -1); - } - /* set off past the magic, to the salt */ - size_t off = salen + sizeof(u32); - char salt[SALT_LEN]; - memcpy(salt, ctx->sa.s + off, sizeof(salt)); - /* move off past salt, to encrypted data */ - off += sizeof(salt); + size_t off = ctx->sa.len; + ssize_t plen; - /* check the fileis big enough to contain up to encrypted data */ - if (ctx->sa.len <= off) { - warnu("read ", ESC, db_file(ctx), ESC, ": file corrupted"); - ctx->sa.len = salen; - return (errno = EBADE, -1); + plen = get_pwd("Enter database password: ", ctx); + if (plen < 0) { + warnusys("open ", ESC, db_file(ctx), ESC, ": cannot read password"); + return -1; } - char key[KEY_LEN]; - if (!get_key(key, salt, "Enter database password: ", ctx)) { - warnusys("read ", ESC, db_file(ctx), ESC, ": cannot read password"); - ctx->sa.len = salen; + u32 magic = SSP_MAGIC; + u64 ver; + if (!shldata_read(&magic, &ver, &ctx->sa, AT_FDCWD, db_file(ctx), ctx->pwd, plen)) { + if (errno == ENOENT) { + ctx->cdb = cdb_zero; + ctx->cdboff = off; + return 0; + } + if (errno == EINVAL && magic != SSP_MAGIC) + warnu("open database ", ESC, db_file(ctx), ESC, ": ", "not an SSP database"); + else if (errno == EBADMSG) + warnu("open database ", ESC, db_file(ctx), ESC, ": ", "wrong password"); + else + warnusys("open ", ESC, db_file(ctx), ESC); return -1; } - dbg("decrypting database"); - /* decrypt data in-place */ - char nonce[NONCE_LEN] = { 0 }; - chacha20(ctx->sa.s + salen, key, nonce, ctx->sa.s + off, ctx->sa.len - off); - ctx->sa.len -= off - salen; - off = salen; - - dbg("checking internal magic: ", HEX(ctx->sa.s + off, sizeof(u32))); - /* check internal magic, to "validate" the password */ - u32p_be((u32 *) (ctx->sa.s + off)); - if (* (u32 *) (ctx->sa.s + off) != SSP_MAGIC_INT) { - warnu("read ", ESC, db_file(ctx), ESC, ": wrong password"); - ctx->sa.len = salen; - return (errno = EINVAL, -1); + if (ver > 0) { + warnu("open database ", ESC, db_file(ctx), ESC, ": ", "version too recent"); + return -1; } - /* move off past internal magic, to actual cdb content */ - off += sizeof(u32); - dbg("initializing cdb"); + ctx->cdboff = off; cdb_init_frommem(&ctx->cdb, ctx->sa.s + off, ctx->sa.len - off); + dbg("initialized cdb [off=", PMUINT(off), " size=", PMUINT(ctx->cdb.size), "]"); + return 1; } +void +close_db(struct ssp *ctx) +{ + dbg("closing database"); + + if (ctx->cdb.map) { + stralloc_remove(&ctx->sa, ctx->cdboff, ctx->cdb.size); + ctx->cdboff = 0; + ctx->cdb.map = NULL; + ctx->cdb.size = 0; + } +} + int rebuild_cdb(cdbmaker_sa *mkr, const char *oldkey, const char *newkey, const struct otp *otp, struct ssp *ctx) { size_t olen = 0, nlen = 0, vlen = 0; - int r = 0; + int r, n = 0; - dbg("rebuiling cdb..."); + dbg("rebuiling cdb"); /* oldkey & newkey : renaming/editing an entry * oldkey & NULL : removing an entry @@ -132,27 +115,27 @@ rebuild_cdb(cdbmaker_sa *mkr, const char *oldkey, const char *newkey, } if (oldkey) olen = strlen(oldkey) + 1; - dbg("oldkey=", ESC, oldkey, ESC, " newkey=", ESC, newkey, ESC, - " vlen=", PUTMSG_UINT(vlen)); + dbg("oldkey=", ESC, oldkey, ESC, " newkey=", ESC, newkey, ESC, " vlen=", PMUINT(vlen)); if (!cdbmaker_sa_start(mkr)) - return 0; + return -1; /* new file? */ if (!ctx->cdb.map) { dbg("new file, adding newkey"); if (nlen && !cdbmaker_sa_add(mkr, newkey, nlen, (char *) otp, vlen)) - return 0; + return -1; + ++n; } else { int a = !nlen; db_reset(ctx); - dbg("iterating current cdb..."); + dbg("iterating current cdb"); while ((r = db_next(ctx)) == 1) { dbg("key=", ESC, db_entry(ctx), ESC); /* existing newkey isn't allowed when adding or renaming */ if (nlen == ctx->key.len && !strcmp(newkey, ctx->key.s) && (!olen || strcmp(oldkey, newkey))) { warnu("add entry ", ESC, newkey, ESC, ": Entry already exists"); - return (errno = EINVAL, 0); + return (errno = EINVAL, -1); } else { /* Note the use of strcoll() here to ensure we order entries * using locale-specific rules. */ @@ -160,7 +143,8 @@ rebuild_cdb(cdbmaker_sa *mkr, const char *oldkey, const char *newkey, dbg("inserting newkey before"); /* insert otp at the right place */ if (!cdbmaker_sa_add(mkr, newkey, nlen, (char *) otp, vlen)) - return 0; + return -1; + ++n; a = 1; } /* if current entry isn't the old one nor the new one, re-add */ @@ -169,89 +153,80 @@ rebuild_cdb(cdbmaker_sa *mkr, const char *oldkey, const char *newkey, dbg("preserving key"); if (!cdbmaker_sa_add(mkr, ctx->key.s, ctx->key.len, ctx->val.s, ctx->val.len)) - return 0; + return -1; + ++n; } } } if (r < 0) { warnu("read database: file corrupted"); - return (errno = EINVAL, 0); + return (errno = EINVAL, -1); } if (!a) { dbg("inserting newkey last"); /* insert last */ if (!cdbmaker_sa_add(mkr, newkey, nlen, (char *) otp, vlen)) - return 0; + return -1; + ++n; } } + dbg("finishing rebuild (", PMINT(n), " entries)"); if (!cdbmaker_sa_finish(mkr)) - return 0; + return -1; - return 1; + return n; } int write_db(char *data, size_t dlen, struct ssp *ctx) { - dbg("writing db..."); + dbg("writing db"); - char salt[SALT_LEN]; - char key[KEY_LEN]; - char nonce[NONCE_LEN] = { 0 }; + ssize_t plen; - /* each time we save the db, we generate a new key to encrypt it */ - random_buf(salt, sizeof(salt)); - if (!get_key(key, salt, "Enter new database password: ", ctx)) { + plen = get_pwd("Enter new database password: ", ctx); + if (plen < 0) { warnusys("read password"); return (errno = EINVAL, 0); } - u32 magic_int = SSP_MAGIC_INT; - u32p_be(&magic_int); - - dbg("encrypting database"); - chacha20_ctx chacha; - chacha20_init(key, nonce, &chacha); - chacha20_crypt(&magic_int, &magic_int, sizeof(magic_int), &chacha); - chacha20_crypt(data, data, dlen, &chacha); - chacha20_clear(&chacha); + struct iovec v; + v.iov_base = data; + v.iov_len = dlen; - u32 magic = SSP_MAGIC; - u32p_be(&magic); - - struct iovec v[4]; - v[0].iov_base = &magic; - v[0].iov_len = sizeof(magic); - v[1].iov_base = salt; - v[1].iov_len = sizeof(salt); - v[2].iov_base = &magic_int; - v[2].iov_len = sizeof(magic_int); - v[3].iov_base = data; - v[3].iov_len = dlen; - - dbg("saving database"); - if (!openwritevnclose(db_file(ctx), v, 4)) { + if (!shldata_write(AT_FDCWD, db_file(ctx), SSP_MAGIC, 0, ctx->pwd, plen, + ALGO_SHA3_256, ITER, 1, &v, 1)) { warnusys("write database to ", ESC, db_file(ctx), ESC); return 0; } + dbg("database written to ", ESC, db_file(ctx), ESC); return 1; } void rebuild_db(const char *oldkey, const char *newkey, const struct otp *otp, struct ssp *ctx) { - dbg("rebuilding database..."); + dbg("rebuilding database"); cdbmaker_sa mkr = CDBMAKER_SA_ZERO; - if (!rebuild_cdb(&mkr, oldkey, newkey, otp, ctx)) + int r = rebuild_cdb(&mkr, oldkey, newkey, otp, ctx); + if (r < 0) diefu(exitcode_from_errno(errno), "save database", (errno == ENOMEM) ? ": Out of memory" : NULL); - if (!write_db(mkr.sa.s, mkr.sa.len, ctx)) - diefusys((errno == EINVAL) ? EX_DATA_ERR : EX_CANTCREAT, "save database"); + if (!r) { + dbg("empty cdb, removing database"); + if (unlink(db_file(ctx)) < 0 && errno != ENOENT) + diefusys(EX_IOERR, "remove database"); + out("Database empty, ", ESC, db_file(ctx), ESC, " removed"); + } else { + dbg("saving new database"); + if (!write_db(mkr.sa.s, mkr.sa.len, ctx)) + diefusys((errno == EINVAL) ? EX_DATA_ERR : EX_CANTCREAT, "save database"); + out("Database saved to ", ESC, db_file(ctx), ESC); + } cdbmaker_sa_free(&mkr); - out("Database saved to ", ESC, db_file(ctx), ESC); } diff --git a/src/ssp/edit.c b/src/ssp/edit.c index dca99ee..8da75a7 100644 --- a/src/ssp/edit.c +++ b/src/ssp/edit.c @@ -7,48 +7,53 @@ #include <limb/command.h> #include <limb/esc.h> #include <limb/exitcode.h> +#include <limb/hasher.h> /* ALGO_* */ +#include <limb/loadopt.h> #include <limb/output.h> -#include <limb/parseopt.h> +#include <limb/stralloc.h> #include <limb/u16.h> #include "ssp.h" struct edit { - struct otp otp; - const char *comments; + stralloc sa; const char *secret; + size_t cmtoff; + size_t entoff; + struct otp otp; }; -COMMAND(edit, "Edit an entry", - "<entry> OPTION[..]", +COMMAND(edit, "Edit an entry", "-e ENTRY OPTION[..]", " -a, --algo ALGO Set ALGO as hashing algorithm\n" " Use 'list' to list available algorithms\n" " -C, --comments COMMENTS Set COMMENTS as entry's comments\n" -" -c, --counter[=NUM] Set to counter-based (HOTP) with counter from NUM [1]\n" +" -c, --counter[=NUM] Set to counter-based (HOTP) with counter of NUM [1]\n" " -d, --digits NUM Set to return an OTP of NUM digits [6]\n" +" -e, --entry ENTRY Edit entry ENTRY\n" " -s, --secret SECRET Set SECRET as entry's secret\n" " -t, --time[=SECS] Set to time-based (TOTP) with precision of SECS [30]\n" ); static int -parse_cmdline(int argc, const char *argv[], const char usage[], struct edit *edit) +parse_cmdline(int argc, const char *argv[], const char usage[], struct edit *ctx) { const struct option options[] = { - OPTION_ARG_REQ ('a', "algo", 0, OPTID_SHORTOPT), - OPTION_ARG_REQ ('C', "comments", 0, OPTID_SHORTOPT), - OPTION_ARG_OPT ('c', "count", 0, OPTID_SHORTOPT), - OPTION_ARG_REQ ('d', "digits", 0, OPTID_SHORTOPT), - OPTION_ARG_REQ ('s', "secret", 0, OPTID_SHORTOPT), - OPTION_ARG_OPT ('t', "time", 0, OPTID_SHORTOPT), - OPTION_DONE + OPTION_ARG_REQ ('a', "algo", 0, OPTID_SHORTOPT), + OPTION_ARG_REQ ('C', "comments", OPT_PATH, OPTID_SHORTOPT), + OPTION_ARG_OPT ('c', "count", 0, OPTID_SHORTOPT), + OPTION_ARG_REQ ('d', "digits", 0, OPTID_SHORTOPT), + OPTION_ARG_REQ ('e', "entry", OPT_PATH | OPT_REQ, OPTID_SHORTOPT), + OPTION_ARG_REQ ('s', "secret", 0, OPTID_SHORTOPT), + OPTION_ARG_OPT ('t', "time", 0, OPTID_SHORTOPT), + LOADOPT_DONE }; - struct parseopt po = { 0 }; + struct loadopt lo = LOADOPT_ZERO; int c, r; - while ((c = parseopt(argc, argv, options, 0, &po))) switch (c) { + while ((c = loadopt(&ctx->sa, argc, argv, options, 0, NULL, 0, &lo))) switch (c) { case 'a': { - if (!strcmp(PO_ARG(&po), "list")) { + if (!strcmp(LO_ARG(&lo), "list")) { out("Available algorithms:"); for (r = 0; algos[r]; ++r) out("- ", algos[r]); @@ -56,108 +61,93 @@ parse_cmdline(int argc, const char *argv[], const char usage[], struct edit *edi } int first = -1; - r = validate_algo(&first, PO_ARG(&po), strlen(PO_ARG(&po))); + r = validate_algo(&first, LO_ARG(&lo), strlen(LO_ARG(&lo))); if (r < 0) { if (first > -1) list_matches(obuffer_1, OLVL_NORMAL, "did you mean ", - NULL, " or ", " ?", PO_ARG(&po), strlen(PO_ARG(&po)), - first, algos); + NULL, " or ", " ?", LO_ARG(&lo), strlen(LO_ARG(&lo)), + first, (const char **) algos); diecmdusage(EX_USAGE, usage, &command_edit); } - edit->otp.algo = r; + ctx->otp.algo = r; } break; case 'C': - edit->comments = PO_ARG(&po); + ctx->cmtoff = LO_OFF(&lo); break; case 'c': - if (!PO_ARG(&po)) { - edit->otp.c = 1; + if (!LO_ARG(&lo)) { + ctx->otp.c = 1; } else { - if (validate_counter(&edit->otp.c, PO_ARG(&po), strlen(PO_ARG(&po))) < 0) + if (validate_counter(&ctx->otp.c, LO_ARG(&lo), strlen(LO_ARG(&lo))) < 0) diecmdusage(EX_USAGE, usage, &command_edit); } - edit->otp.type = TYPE_COUNTER; + ctx->otp.type = TYPE_COUNTER; break; case 'd': - r = validate_digits(PO_ARG(&po), strlen(PO_ARG(&po))); + r = validate_digits(LO_ARG(&lo), strlen(LO_ARG(&lo))); if (r < 0) diecmdusage(EX_USAGE, usage, &command_edit); - edit->otp.digits = r; + ctx->otp.digits = r; + break; + case 'e': + ctx->entoff = LO_OFF(&lo); break; case 's': - edit->secret = PO_ARG(&po); + ctx->secret = LO_ARG(&lo); break; case 't': - if (!PO_ARG(&po)) { - edit->otp.t.precision = 30; + if (!LO_ARG(&lo)) { + ctx->otp.t.precision = 30; } else { - r = validate_precision(PO_ARG(&po), strlen(PO_ARG(&po))); + r = validate_precision(LO_ARG(&lo), strlen(LO_ARG(&lo))); if (r < 0) diecmdusage(EX_USAGE, usage, &command_edit); - edit->otp.t.precision = r; + ctx->otp.t.precision = r; } - edit->otp.type = TYPE_TIME; + ctx->otp.type = TYPE_TIME; break; + case -1: diecmdusage(EX_USAGE, usage, &command_edit); default: - die(EX_SOFTWARE, "unexpected return value ", PUTMSG_INT(c), " from parseopt"); + die(EX_SOFTWARE, "unexpected return value ", PMINT(c), " from loadopt"); }; - return PO_CUR(&po); + return LO_CUR(&lo); } int -edit_main(int argc, const char *argv[], const char *env[], const char usage[], void *ctx_) +edit_main(int argc, const char *argv[], const char *env[], const char usage[], void *ssp_) { - struct ssp *ctx = ctx_; - struct edit edit = { .otp.type = TYPE_UNKNOWN, .otp.algo = ALGO_UNKNOWN, }; - const char *entry; - int i; - - if (argc == 1) { - warn("argument \"entry\" missing"); - diecmdusage(EX_USAGE, usage, &command_edit); - } - entry = get_entry_name(argv[1], ctx); - if (!entry) - diefusys(exitcode_from_errno(errno), "parse entry's name"); - if (*entry == '-') { - warn("argument \"entry\" cannot start with a '-'"); - diecmdusage(EX_DATA_ERR, usage, &command_edit); - } - - if (argc == 2) die(0, "nothing to do"); + struct ssp *ssp = ssp_; + struct edit ctx = { .sa = STRALLOC_ZERO, .otp.type = TYPE_UNKNOWN, + .otp.algo = ALGO_UNKNOWN, .cmtoff = (size_t) -1 }; - --argc; - ++argv; - i = parse_cmdline(argc, argv, usage, &edit); + (void) env; + parse_cmdline(argc, argv, usage, &ctx); - if (argc > i) { - warn("too many arguments"); - diecmdusage(EX_USAGE, usage, &command_edit); - } + const char *entry = ctx.sa.s + ctx.entoff; - int r = open_db(ctx); + int r = open_db(ssp); if (r < 0) diefu(exitcode_from_errno(errno), "open database"); if (!r) dief(EX_NOINPUT, "no database"); - r = db_find(entry, ctx); + r = db_find(entry, ssp); if (r < 0) diefu(EX_DATA_ERR, "find entry ", ESC, entry, ESC); if (!r) dief(EX_DATA_ERR, "no entry ", ESC, entry, ESC, " in database"); - const struct otp *cur = db_otp(ctx); + const struct otp *cur = db_otp(ssp); ssize_t slen; - if (!edit.secret) { + if (!ctx.secret) { slen = cur->slen; } else { - slen = base32_scan(NULL, edit.secret, strlen(edit.secret), 0); + slen = base32_scan(NULL, ctx.secret, strlen(ctx.secret), 0); if (slen < 0) dief(EX_DATA_ERR, "invalid secret: Not in base32"); if (!slen) @@ -165,46 +155,52 @@ edit_main(int argc, const char *argv[], const char *env[], const char usage[], v } size_t clen; - if (!edit.comments) + if (ctx.cmtoff == (size_t) -1) clen = strlen(cur->data + cur->slen); else - clen = strlen(edit.comments); + clen = strlen(ctx.sa.s + ctx.cmtoff); + + size_t otplen = sizeof(ctx.otp) + slen + clen + 1; - char buf[sizeof(edit.otp) + slen + clen + 1]; - struct otp *p = (struct otp *) buf; - p->algo = (edit.otp.algo != ALGO_UNKNOWN) ? edit.otp.algo : cur->algo; - p->type = (edit.otp.type != TYPE_UNKNOWN) ? edit.otp.type : cur->type; - p->digits = (edit.otp.digits) ? edit.otp.digits : cur->digits; + if (!stralloc_readyplus(&ctx.sa, otplen)) + diefusys(EX_TEMPFAIL, "ctx entry ", ESC, entry, ESC); + /* re-align */ + entry = ctx.sa.s + ctx.entoff; + + dbg("constructing edited entry"); + struct otp *p = (struct otp *) (ctx.sa.s + ctx.sa.len); + ctx.sa.len += otplen; + p->algo = (ctx.otp.algo != ALGO_UNKNOWN) ? ctx.otp.algo : cur->algo; + p->type = (ctx.otp.type != TYPE_UNKNOWN) ? ctx.otp.type : cur->type; + p->digits = (ctx.otp.digits) ? ctx.otp.digits : cur->digits; if (p->type == TYPE_COUNTER) - p->c = (edit.otp.type != TYPE_UNKNOWN) ? edit.otp.c : cur->c; + p->c = (ctx.otp.type != TYPE_UNKNOWN) ? ctx.otp.c : cur->c; else - p->t.precision = (edit.otp.type != TYPE_UNKNOWN) ? edit.otp.t.precision : cur->t.precision; + p->t.precision = (ctx.otp.type != TYPE_UNKNOWN) ? ctx.otp.t.precision : cur->t.precision; p->slen = slen; - if (edit.secret) - base32_scan(p->data, edit.secret, strlen(edit.secret), 0); + if (ctx.secret) + base32_scan(p->data, ctx.secret, strlen(ctx.secret), 0); else memcpy(p->data, cur->data, slen); - if (edit.comments) { - if (clen >= 2 && edit.comments[0] == '"' && edit.comments[clen - 1] == '"') { - ++edit.comments; - clen -= 2; - slen = unesc(p->data + slen, clen, edit.comments, clen); - if (slen < 0) - diefusys(EX_DATA_ERR, "read comments"); - } else { - memcpy(p->data + slen, edit.comments, clen + 1); - } - } else { - memcpy(p->data + slen, cur->data + slen, clen + 1); - } + if (ctx.cmtoff == (size_t) -1) + memcpy(p->data + slen, cur->data + cur->slen, clen + 1); + else + memcpy(p->data + slen, ctx.sa.s + ctx.cmtoff, clen + 1); + + if (cur->slen == p->slen && strlen(cur->data + cur->slen) == clen + && !memcmp(p, cur, otplen)) + dief(EX_DATA_ERR, "nothing to do"); out("Editing entry ", ESC, entry, ESC, "... "); - rebuild_db(entry, entry, p, ctx); + rebuild_db(entry, entry, p, ssp); + dbg("showing edited entry"); + close_db(ssp); const char *av[] = { NULL, entry }; - run_command("show", sizeof(av) / sizeof(*av), av, env, ctx); + run_command("show", sizeof(av) / sizeof(*av), av, env, ssp); + stralloc_free(&ctx.sa); return 0; } diff --git a/src/ssp/export.c b/src/ssp/export.c index 9554c3b..949bed5 100644 --- a/src/ssp/export.c +++ b/src/ssp/export.c @@ -4,8 +4,8 @@ #include <errno.h> #include <limb/command.h> #include <limb/exitcode.h> +#include <limb/loadopt.h> #include <limb/output.h> -#include <limb/parseopt.h> #include "ssp.h" enum { @@ -13,9 +13,14 @@ enum { }; struct export { + const char *ptrn; int options; }; +enum { + ARGID_PATTERN = OPTID_FIRST, +}; + COMMAND(export, "Export entries", "[OPTION..] [<pattern>]", " -C, --no-comments Do not export comments\n" ); @@ -24,42 +29,42 @@ static int parse_cmdline(int argc, const char *argv[], const char usage[], struct export *ctx) { const struct option options[] = { - OPTION_ARG_NONE('C', "no-comments", 0, OPTID_SHORTOPT), - OPTION_DONE + OPTION_ARG_NONE('C', "no-comments", 0, OPTID_SHORTOPT), + LOADOPT_ARGUMENTS, + ARGUMENT_OPT( "pattern", 0, ARGID_PATTERN), + LOADOPT_DONE }; - struct parseopt po = { 0 }; + struct loadopt lo = LOADOPT_ZERO; int c; - while ((c = parseopt(argc, argv, options, 0, &po))) switch (c) { + while ((c = loadopt(NULL, argc, argv, options, 0, NULL, 0, &lo))) switch (c) { case 'C': ctx->options |= OPT_NO_COMMENTS; break; + + case ARGID_PATTERN: + ctx->ptrn = LO_ARG(&lo); + break; + case -1: diecmdusage(EX_USAGE, usage, &command_export); default: - die(EX_SOFTWARE, "unexpected return value ", PUTMSG_INT(c), " from parseopt"); + die(EX_SOFTWARE, "unexpected return value ", PMINT(c), " from loadopt"); }; - return PO_CUR(&po); + return LO_CUR(&lo); } int -export_main(int argc, const char *argv[], const char *env[], const char usage[], void *ctx_) +export_main(int argc, const char *argv[], const char *env[], const char usage[], void *ssp_) { - struct export export = { 0 }; - - int i = parse_cmdline(argc, argv, usage, &export); + struct ssp *ssp = ssp_; + struct export ctx = { .ptrn = NULL, .options = 0 }; - if (argc > i + 1) { - warn("too many arguments"); - diecmdusage(EX_USAGE, usage, &command_export); - } else if (argc == i) { - argc = i = 0; - } else { - argc = 1; - } + (void) env; + parse_cmdline(argc, argv, usage, &ctx); - int C = !!(export.options & OPT_NO_COMMENTS); - const char *av[] = { NULL, "--format", (C) ? "--no-comments" : argv[i], argv[i] }; - return run_command("list", 2 + C + argc, av, env, ctx_); + int C = !!(ctx.options & OPT_NO_COMMENTS); + const char *av[] = { NULL, "--format", (C) ? "--no-comments" : ctx.ptrn, ctx.ptrn }; + return run_command("list", 2 + C + !!ctx.ptrn, av, env, ssp); } diff --git a/src/ssp/get.c b/src/ssp/get.c index 35ec2aa..50c15f2 100644 --- a/src/ssp/get.c +++ b/src/ssp/get.c @@ -15,8 +15,9 @@ #include <limb/hasher_sha3_256.h> #include <limb/hasher_sha3_512.h> #include <limb/hasher_blake3.h> +#include <limb/loadopt.h> #include <limb/output.h> -#include <limb/parseopt.h> +#include <limb/stralloc.h> #include <limb/u32.h> #include "ssp.h" #include "hotp.h" @@ -24,155 +25,172 @@ struct get { struct otp otp; const char *secret; + struct ssp *ssp; u64 ts; + u8 upd_entry; }; -COMMAND(get, "Get a One-Time Password", "<entry> [OPTION..]", +COMMAND(get, "Get a One-Time Password", "[-e ENTRY | -s SECRET] [OPTION..]", " -a, --algo ALGO Set ALGO as hashing algorithm [sha1]\n" -" -c, --counter[=NUM] Set to counter-based (HOTP) with counter from NUM [1]\n" +" -C, --counter-val NUM Same as --counter but don't update entry\n" +" -c, --counter[=NUM] Set to counter-based (HOTP) with counter of NUM [1]\n" " -d, --digits NUM Set to return an OTP of NUM digits [6]\n" +" -e, --entry ENTRY Get a password for ENTRY\n" " -s, --secret SECRET Set SECRET as entry's secret\n" " -T, --use-time TS Use TS as unix timestamp instead of current time\n" " -t, --time[=SECS] Set to time-based (TOTP) with precision of SECS [30]\n" ); static int -parse_cmdline(int argc, const char *argv[], const char usage[], struct get *get) +parse_cmdline(int argc, const char *argv[], const char usage[], struct get *ctx) { const struct option options[] = { - OPTION_ARG_REQ ('a', "algo", 0, OPTID_SHORTOPT), - OPTION_ARG_OPT ('c', "counter", 0, OPTID_SHORTOPT), - OPTION_ARG_REQ ('d', "digits", 0, OPTID_SHORTOPT), - OPTION_ARG_REQ ('s', "secret", 0, OPTID_SHORTOPT), - OPTION_ARG_REQ ('T', "use-time", 0, OPTID_SHORTOPT), - OPTION_ARG_OPT ('t', "time", 0, OPTID_SHORTOPT), - OPTION_DONE + OPTION_ARG_REQ ('a', "algo", 0, OPTID_SHORTOPT), + OPTION_ARG_REQ ('C', "counter", 0, OPTID_SHORTOPT), + OPTION_ARG_OPT ('c', "counter", 0, OPTID_SHORTOPT), + OPTION_ARG_REQ ('d', "digits", 0, OPTID_SHORTOPT), + OPTION_ARG_REQ ('e', "entry", OPT_PATH, OPTID_SHORTOPT), + OPTION_ARG_REQ ('s', "secret", 0, OPTID_SHORTOPT), + OPTION_ARG_REQ ('T', "use-time", 0, OPTID_SHORTOPT), + OPTION_ARG_OPT ('t', "time", 0, OPTID_SHORTOPT), + LOADOPT_DONE }; - struct parseopt po = { 0 }; + struct loadopt lo = LOADOPT_ZERO; int c, r; - while ((c = parseopt(argc, argv, options, 0, &po))) switch (c) { + while ((c = loadopt(&ctx->ssp->sa, argc, argv, options, 0, NULL, 0, &lo))) switch (c) { case 'a': { int first = -1; - r = validate_algo(&first, PO_ARG(&po), strlen(PO_ARG(&po))); + r = validate_algo(&first, LO_ARG(&lo), strlen(LO_ARG(&lo))); if (r < 0) { if (first > -1) list_matches(obuffer_1, OLVL_NORMAL, "did you mean ", - NULL, " or ", " ?", PO_ARG(&po), strlen(PO_ARG(&po)), - first, algos); + NULL, " or ", " ?", LO_ARG(&lo), strlen(LO_ARG(&lo)), + first, (const char **) algos); diecmdusage(EX_USAGE, usage, &command_get); } - get->otp.algo = r; + ctx->otp.algo = r; + dbg("setting algo to ", algos[r]); } break; + case 'C': + ctx->upd_entry = 0; + dbg("will not update entry's counter value"); + /* fall through */ case 'c': - if (!PO_ARG(&po)) { - get->otp.c = 1; + if (!LO_ARG(&lo)) { + ctx->otp.c = 1; } else { - if (validate_counter(&get->otp.c, PO_ARG(&po), strlen(PO_ARG(&po))) < 0) + if (validate_counter(&ctx->otp.c, LO_ARG(&lo), strlen(LO_ARG(&lo))) < 0) diecmdusage(EX_USAGE, usage, &command_get); } - get->otp.type = TYPE_COUNTER_MANUAL; + ctx->otp.type = TYPE_COUNTER; + dbg("setting to HOTP with counter=", PMUINT(ctx->otp.c)); break; case 'd': - r = validate_digits(PO_ARG(&po), strlen(PO_ARG(&po))); + r = validate_digits(LO_ARG(&lo), strlen(LO_ARG(&lo))); if (r < 0) diecmdusage(EX_USAGE, usage, &command_get); - get->otp.digits = r; + ctx->otp.digits = r; + dbg("setting to ", PMUINT(r), " digits"); + break; + case 'e': + { + const char *entry = ctx->ssp->sa.s + LO_OFF(&lo); + int r; + + dbg("looking for entry ", ESC, entry, ESC); + + if (ctx->ssp->cdb.map) { + /* re-align cdb in case of a re-alloc */ + ctx->ssp->cdb.map = ctx->ssp->sa.s + ctx->ssp->cdboff; + } else { + r = open_db(ctx->ssp); + if (r < 0) + diefu(exitcode_from_errno(errno), "open database"); + if (!r) + dief(EX_NOINPUT, "no database"); + /* re-align */ + entry = ctx->ssp->sa.s + LO_OFF(&lo); + } + + r = db_find(entry, ctx->ssp); + if (r < 0) + diefu(EX_DATA_ERR, "find entry ", ESC, entry, ESC); + if (!r) + dief(EX_DATA_ERR, "no entry ", ESC, entry, ESC, " in database"); + + ctx->otp = *db_otp(ctx->ssp); + ctx->secret = db_otp(ctx->ssp)->data; + ctx->upd_entry = 1; + dbg("using entry ", ESC, entry, ESC); + } break; case 's': - get->secret = PO_ARG(&po); - get->otp.slen = 0; + ctx->secret = LO_ARG(&lo); + ctx->otp.slen = 0; + dbg("setting secret to ", ctx->secret); break; case 'T': - if (!u640_scan(&get->ts, PO_ARG(&po))) { - warn("invalid time stamp: ", PO_ARG(&po)); + if (!u640_scan(&ctx->ts, LO_ARG(&lo))) { + warn("invalid time stamp: ", LO_ARG(&lo)); diecmdusage(EX_USAGE, usage, &command_get); } + dbg("using time value of ", PMUINT(ctx->ts)); break; case 't': - if (!PO_ARG(&po)) { - get->otp.t.precision = 30; + if (!LO_ARG(&lo)) { + ctx->otp.t.precision = 30; } else { - r = validate_precision(PO_ARG(&po), strlen(PO_ARG(&po))); + r = validate_precision(LO_ARG(&lo), strlen(LO_ARG(&lo))); if (r < 0) diecmdusage(EX_USAGE, usage, &command_get); - get->otp.t.precision = r; + ctx->otp.t.precision = r; } - get->otp.type = TYPE_TIME; + ctx->otp.type = TYPE_TIME; + dbg("setting to TOTP with precision=", PMUINT(ctx->otp.t.precision)); break; + case -1: diecmdusage(EX_USAGE, usage, &command_get); default: - die(EX_SOFTWARE, "unexpected return value ", PUTMSG_INT(c), " from parseopt"); + die(EX_SOFTWARE, "unexpected return value ", PMINT(c), " from loadopt"); }; - return PO_CUR(&po); + return LO_CUR(&lo); } int -get_main(int argc, const char *argv[], const char *env[], const char usage[], void *ctx_) +get_main(int argc, const char *argv[], const char *env[], const char usage[], void *ssp_) { - struct ssp *ctx = ctx_; - struct get get = { .otp = OTP_DEFAULT, .otp.type = TYPE_COUNTER_MANUAL, - .secret = NULL, .ts = (u64) -1 }; + struct ssp *ssp = ssp_; + struct get ctx = { .otp = OTP_DEFAULT, .secret = NULL, .ssp = ssp, + .ts = (u64) -1, .upd_entry = 0 }; - if (argc == 1) { - warn("argument \"entry\" missing"); - diecmdusage(EX_USAGE, usage, &command_get); - } + (void) env; + parse_cmdline(argc, argv, usage, &ctx); - struct otp *otp; - if (strcmp(argv[1], "-")) { - const char *entry = get_entry_name(argv[1], ctx); - if (!entry) - diefusys(exitcode_from_errno(errno), "parse entry's name"); - - int r = open_db(ctx); - if (r < 0) - diefu(exitcode_from_errno(errno), "open database"); - if (!r) - dief(EX_NOINPUT, "no database"); - - r = db_find(entry, ctx); - if (r < 0) - diefu(EX_DATA_ERR, "find entry ", ESC, entry, ESC); - if (!r) - dief(EX_DATA_ERR, "no entry ", ESC, entry, ESC, " in database"); - - get.otp = *db_otp(ctx); - get.secret = db_otp(ctx)->data; - } - - --argc; - ++argv; - int i = parse_cmdline(argc, argv, usage, &get); - - if (argc > i) { - warn("too many arguments"); - diecmdusage(EX_USAGE, usage, &command_get); - } - - if (!get.secret) + if (!ctx.secret) dief(EX_DATA_ERR, "secret missing"); - if (!get.otp.slen) { - size_t l = strlen(get.secret); - ssize_t r = base32_scan(NULL, get.secret, l, 0); + if (!ctx.otp.slen) { + dbg("decoding secret from base32"); + size_t l = strlen(ctx.secret); + ssize_t r = base32_scan(NULL, ctx.secret, l, 0); if (r < 0) dief(EX_DATA_ERR, "invalid secret: Not in base32"); - if (!stralloc_readyplus(&ctx->sa, r)) + if (!stralloc_readyplus(&ssp->sa, r)) diefusys(EX_TEMPFAIL, "process secret"); - base32_scan(ctx->sa.s + ctx->sa.len, get.secret, l , 0); - get.secret = ctx->sa.s + ctx->sa.len; - /* we don't update ctx->sa.len on purpose, so in case we need to rewrite + base32_scan(ssp->sa.s + ssp->sa.len, ctx.secret, l , 0); + ctx.secret = ssp->sa.s + ssp->sa.len; + /* we don't update ssp->sa.len on purpose, so in case we need to rewrite * the db the calculation is easier */ - get.otp.slen = r; + ctx.otp.slen = r; } hasher *hr = sha1; - switch (get.otp.algo) { + switch (ctx.otp.algo) { case ALGO_SHA256: hr = sha256; break; case ALGO_SHA512: hr = sha512; break; case ALGO_SHA3_224: hr = sha3_224; break; @@ -182,31 +200,31 @@ get_main(int argc, const char *argv[], const char *env[], const char usage[], vo } u64 c; - - if (get.otp.type == TYPE_TIME) { - if (get.ts == (u64) -1) - get.ts = time(NULL); - c = get.ts / get.otp.t.precision; + if (ctx.otp.type == TYPE_TIME) { + if (ctx.ts == (u64) -1) + ctx.ts = time(NULL); + c = ctx.ts / ctx.otp.t.precision; } else { - c = get.otp.c; + c = ctx.otp.c; } - dbg("calculating HOTP c=", PUTMSG_UINT(c), " digits=", PUTMSG_UINT(get.otp.digits)); - int r = hotp(hr, get.secret, get.otp.slen, c, get.otp.digits); - add((get.otp.type == TYPE_COUNTER) ? "H" : "T", "OTP: "); - char buf[get.otp.digits + 1]; + dbg("calculating HOTP c=", PMUINT(c), " digits=", PMUINT(ctx.otp.digits)); + int r = hotp(hr, ctx.secret, ctx.otp.slen, c, ctx.otp.digits); + add((ctx.otp.type == TYPE_COUNTER) ? "H" : "T", "OTP: "); + char buf[ctx.otp.digits + 1]; buf[u320_fmt(buf, r, sizeof(buf) - 1)] = 0; quiet(buf); /* TYPE_COUNTER implies to write the db with an updated/incremented - * counter (for next time) */ - if (get.otp.type == TYPE_COUNTER) { - struct otp *e = (struct otp *) db_otp(ctx); - ++e->c; - - size_t off = (ctx->db) ? 0 : strlen(ctx->sa.s) + 1; - off += sizeof(u32); - if (!write_db(ctx->sa.s + off, ctx->sa.len - off, ctx)) + * counter (for next time), unless asked not to */ + if (ctx.upd_entry && ctx.otp.type == TYPE_COUNTER + /* make sure the entry is also in COUNTER mode */ + && db_otp(ssp)->type == TYPE_COUNTER) { + struct otp *e = (struct otp *) db_otp(ssp); + e->c = ++ctx.otp.c; + dbg("updating entry's counter to ", PMUINT(e->c)); + + if (!write_db(ssp->sa.s + ssp->cdboff, ssp->cdb.size, ssp)) diefusys((errno == EINVAL) ? EX_DATA_ERR : EX_CANTCREAT, "save database"); } diff --git a/src/ssp/import.c b/src/ssp/import.c index 0d59128..9c31d8f 100644 --- a/src/ssp/import.c +++ b/src/ssp/import.c @@ -2,41 +2,94 @@ * 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/bytestr.h> #include <limb/cdbmake.h> #include <limb/command.h> #include <limb/copa.h> #include <limb/djbunix.h> +#include <limb/esc.h> #include <limb/exitcode.h> +#include <limb/hasher.h> /* ALGO_* */ +#include <limb/loadopt.h> #include <limb/output.h> +#include <limb/stralloc.h> #include <limb/u16.h> #include "ssp.h" +struct import { + struct ssp *ssp; + size_t fileoff; +}; + +enum { + ARGID_FILE = OPTID_FIRST, +}; COMMAND(import, "Import entries from file", "<file>", NULL); +static int +parse_cmdline(int argc, const char *argv[], const char usage[], struct import *ctx) +{ + const struct option options[] = { + LOADOPT_ARGUMENTS, + ARGUMENT_REQ( "file", OPT_PATH, ARGID_FILE), + LOADOPT_DONE + }; + struct loadopt lo = LOADOPT_ZERO; + + int c; + while ((c = loadopt(&ctx->ssp->sa, argc, argv, options, 0, NULL, 0, &lo))) switch (c) { + + case ARGID_FILE: + ctx->fileoff = LO_OFF(&lo); + break; + + case -1: + diecmdusage(EX_USAGE, usage, &command_import); + default: + die(EX_SOFTWARE, "unexpected return value ", PMINT(c), " from loadopt"); + }; + + return LO_CUR(&lo); +} + +static ssize_t +unesc(char *dst, size_t dlen, const char *sce, size_t slen) +{ + /* unescape it */ + ssize_t l = esc_scan(dst, dlen, sce, slen); + if (l < 0) return -1; + /* make sure there was no encoded NUL byte */ + if (byte_chr(dst, l, 0) < (size_t) l) + return (errno = EINVAL, -1); + dst[l++] = 0; + return l; +} + int -import_main(int argc, const char *argv[], const char *env[], const char usage[], void *ctx_) +import_main(int argc, const char *argv[], const char *env[], const char usage[], void *ssp_) { - struct ssp *ctx = ctx_; + struct ssp *ssp = ssp_; + struct import ctx = { .ssp = ssp_, .fileoff = 0 }; + stralloc sacopa = STRALLOC_ZERO; + stralloc saotp = STRALLOC_ZERO; - if (argc != 2) - diecmdusage(EX_USAGE, usage, &command_import); + (void) env; + parse_cmdline(argc, argv, usage, &ctx); - int fd = open_parsed_name(argv[1], open_readb); + int fd = open_parsed_name(ssp->sa.s + ctx.fileoff, openb_read); if (fd < 0) - diefusys(EX_NOINPUT, "open ", ESC, argv[1], ESC); + diefusys(EX_NOINPUT, "open ", ESC, ssp->sa.s + ctx.fileoff, ESC); - dbg("reading ", ESC, argv[1], ESC); - stralloc sa = STRALLOC_ZERO; - if (!slurp(&sa, fd)) - diefusys(exitcode_from_errno(errno), "read ", ESC, argv[1], ESC); + dbg("reading ", ESC, ssp->sa.s + ctx.fileoff, ESC); + if (!slurp(&sacopa, fd)) + diefusys(exitcode_from_errno(errno), "read ", ESC, ssp->sa.s + ctx.fileoff, ESC); cdbmaker_sa mkr = CDBMAKER_SA_ZERO; struct otp otp; const char *name = NULL; - size_t nlen; + size_t nlen = 0; const char *secret; ssize_t slen; size_t slen32; @@ -45,20 +98,24 @@ import_main(int argc, const char *argv[], const char *env[], const char usage[], struct copa copa; int r, n = 0; dbg("init parsing"); - copa_init(sa.s, sa.len, &copa); + comments = NULL; clen = 0; /* silence warnings */ + copa_init(sacopa.s, sacopa.len, &copa); while ((r = copa_next(&copa)) >= 0) { - dbg("r=", PUTMSG_INT(r)); - if (r) dbg("is_section=", PUTMSG_INT(copa_is_section(&copa)), - " name=", LEN(copa_name(&copa), copa_nlen(&copa))); + dbg("r=", PMINT(r)); + if (r) dbg("is_section=", PMINT(copa_is_section(&copa)), + " name=", PMLEN(copa_name(&copa), copa_nlen(&copa))); if (!r || copa_is_section(&copa)) { if (name) { if (!slen) die(EX_DATA_ERR, "missing secret in section/entry ", - ESC, LEN(name, nlen), ESC); + ESC, PMLEN(name, nlen), ESC); + + dbg("preparing entry ", ESC, PMLEN(name, nlen), ESC); + size_t otplen = sizeof(otp) + slen + clen + 1; + if (!stralloc_ready(&saotp, otplen)) + diefusys(EX_TEMPFAIL, "prepare entry ", ESC, PMLEN(name, nlen), ESC); - dbg("preparing entry ", ESC, LEN(name, nlen), ESC); - char buf[sizeof(otp) + slen + clen + 1]; - struct otp *p = (struct otp *) buf; + struct otp *p = (struct otp *) saotp.s; memcpy(p, &otp, sizeof(otp)); p->slen = slen; base32_scan(p->data, secret, slen32, 0); @@ -68,7 +125,7 @@ import_main(int argc, const char *argv[], const char *env[], const char usage[], dbg("unescaping comments"); if (unesc(p->data + slen, clen, comments, clen) < 0) diefusys(EX_DATA_ERR, "read comments in section/entry ", - ESC, LEN(name, nlen), ESC); + ESC, PMLEN(name, nlen), ESC); } else { memcpy(p->data + slen, comments, clen + 1); } @@ -79,21 +136,25 @@ import_main(int argc, const char *argv[], const char *env[], const char usage[], */ if (mkr.sa.s) { dbg("making previously generated cdb live"); - stralloc sa = ctx->sa; - ctx->sa = mkr.sa; - cdb_init_frommem(&ctx->cdb, ctx->sa.s, ctx->sa.len); + stralloc sa = ssp->sa; + ssp->sa = mkr.sa; + /* insert back db name (& import file name) */ + if (!stralloc_insertb(&ssp->sa, 0, sa.s, ssp->cdboff)) + diefusys(EX_TEMPFAIL, "prepare for importation"); + cdb_init_frommem(&ssp->cdb, ssp->sa.s + ssp->cdboff, + ssp->sa.len); mkr.sa = sa; } /* open db if we haven't already */ - if (!ctx->cdb.map && open_db(ctx) < 0) + if (!ssp->cdb.map && open_db(ssp) < 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)); + dief(EX_DATA_ERR, "invalid section/entry name: ", PMLEN(name, nlen)); out("Importing ", ESC, key, ESC, "..."); - if (!rebuild_cdb(&mkr, NULL , key, p, ctx)) + if (rebuild_cdb(&mkr, NULL , key, p, ssp) < 0) diefu(exitcode_from_errno(errno), "import data", (errno == ENOMEM) ? ": Out of memory" : NULL); ++n; @@ -116,10 +177,10 @@ import_main(int argc, const char *argv[], const char *env[], const char usage[], /* 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); + ESC, PMLEN(copa_name(&copa), copa_nlen(&copa)), ESC, + " in section/entry ", ESC, PMLEN(name, nlen), ESC); - dbg("value=", ESC, LEN(copa_value(&copa), copa_vlen(&copa)), ESC); + dbg("value=", ESC, PMLEN(copa_value(&copa), copa_vlen(&copa)), ESC); if (copa_nlen(&copa) == 4 && !strncmp(copa_name(&copa), "algo", 4)) { r = validate_algo(NULL, copa_value(&copa), copa_vlen(&copa)); @@ -139,8 +200,8 @@ import_main(int argc, const char *argv[], const char *env[], const char usage[], otp.type = TYPE_COUNTER; else dief(EX_DATA_ERR, "invalid value for \"time\" in section/entry ", - ESC, LEN(name, nlen), ESC, ": ", - LEN(copa_value(&copa), copa_vlen(&copa))); + ESC, PMLEN(name, nlen), ESC, ": ", + PMLEN(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)); @@ -150,8 +211,8 @@ import_main(int argc, const char *argv[], const char *env[], const char usage[], slen = base32_scan(NULL, copa_value(&copa), copa_vlen(&copa), 0); 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))); + ESC, PMLEN(name, nlen), ESC, ": Not in base32: ", + PMLEN(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)) { @@ -159,26 +220,26 @@ import_main(int argc, const char *argv[], const char *env[], const char usage[], 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); + ESC, PMLEN(copa_name(&copa), copa_nlen(&copa)), ESC, + " in section/entry ", ESC, PMLEN(name, nlen), ESC); } } } if (r < 0) - diefu(EX_DATA_ERR, "parse ", ESC, argv[1], ESC); + diefu(EX_DATA_ERR, "parse ", ESC, ssp->sa.s + ctx.fileoff, ESC); - if (mkr.sa.s && !write_db(mkr.sa.s, mkr.sa.len, ctx)) + if (mkr.sa.s && !write_db(mkr.sa.s, mkr.sa.len, ssp)) 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."); + out("Successfully imported ", PMUINT(n), " entries to ", ESC, db_file(ssp), ESC); - stralloc_free(&sa); + stralloc_free(&sacopa); + stralloc_free(&saotp); return 0; err: - stralloc_free(&sa); - dief(EX_DATA_ERR, "invalid section/entry ", ESC, LEN(name, nlen), ESC); + dief(EX_DATA_ERR, "invalid section/entry ", ESC, PMLEN(name, nlen), ESC); } diff --git a/src/ssp/list.c b/src/ssp/list.c index 6b5c51e..7588ab3 100644 --- a/src/ssp/list.c +++ b/src/ssp/list.c @@ -5,8 +5,8 @@ #include <fnmatch.h> #include <limb/command.h> #include <limb/exitcode.h> +#include <limb/loadopt.h> #include <limb/output.h> -#include <limb/parseopt.h> #include "ssp.h" enum { @@ -16,14 +16,18 @@ enum { OPT_FMT_INI = 1 << 3, }; +enum { + ARGID_PATTERN = OPTID_FIRST, +}; + struct list { - unsigned options; + struct ssp *ssp; const char *sep; - const char *ptrn; + size_t ptrnoff; + unsigned options; }; -COMMAND(list, "List all entries", - "[OPTION..] [<pattern>]", +COMMAND(list, "List all entries", "[OPTION..] [<pattern>]", " -C, --no-comments Do not show comments with --details/--format\n" " -d, --details Show entries' details\n" " -f, --format Output in INI-like format\n" @@ -35,17 +39,19 @@ static int parse_cmdline(int argc, const char *argv[], const char usage[], struct list *ctx) { const struct option options[] = { - OPTION_ARG_NONE('C', "no-comments", 0, OPTID_SHORTOPT), - OPTION_ARG_NONE('d', "details", 0, OPTID_SHORTOPT), - OPTION_ARG_NONE('f', "format", 0, OPTID_SHORTOPT), - OPTION_ARG_NONE('S', "secret", 0, OPTID_SHORTOPT), - OPTION_ARG_REQ ('s', "sep", 0, OPTID_SHORTOPT), - OPTION_DONE + OPTION_ARG_NONE('C', "no-comments", 0, OPTID_SHORTOPT), + OPTION_ARG_NONE('d', "details", 0, OPTID_SHORTOPT), + OPTION_ARG_NONE('f', "format", 0, OPTID_SHORTOPT), + OPTION_ARG_NONE('S', "secret", 0, OPTID_SHORTOPT), + OPTION_ARG_REQ ('s', "sep", 0, OPTID_SHORTOPT), + LOADOPT_ARGUMENTS, + ARGUMENT_OPT( "pattern", OPT_PATH, ARGID_PATTERN), + LOADOPT_DONE }; - struct parseopt po = { 0 }; + struct loadopt lo = LOADOPT_ZERO; int c; - while ((c = parseopt(argc, argv, options, 0, &po))) switch (c) { + while ((c = loadopt(&ctx->ssp->sa, argc, argv, options, 0, NULL, 0, &lo))) switch (c) { case 'C': ctx->options |= OPT_NO_COMMENTS; break; @@ -59,63 +65,65 @@ parse_cmdline(int argc, const char *argv[], const char usage[], struct list *ctx ctx->options |= OPT_DETAILS | OPT_SECRET; break; case 's': - ctx->sep = PO_ARG(&po); + ctx->sep = LO_ARG(&lo); + break; + + case ARGID_PATTERN: + ctx->ptrnoff = LO_OFF(&lo); break; + case -1: diecmdusage(EX_USAGE, usage, &command_list); default: - die(EX_SOFTWARE, "unexpected return value ", PUTMSG_INT(c), " from parseopt"); + die(EX_SOFTWARE, "unexpected return value ", PMINT(c), " from loadopt"); }; - return PO_CUR(&po); + return LO_CUR(&lo); } int -list_main(int argc, const char *argv[], const char *env[], const char usage[], void *ctx_) +list_main(int argc, const char *argv[], const char *env[], const char usage[], void *ssp_) { - struct ssp *ctx = ctx_; - struct list list = { 0, "\n", NULL }; - - int i = parse_cmdline(argc, argv, usage, &list); + struct ssp *ssp = ssp_; + struct list ctx = { .ssp = ssp, .sep = "\n", .ptrnoff = (size_t) -1 }; - if (i + 1 == argc) - list.ptrn = argv[i]; + (void) env; + parse_cmdline(argc, argv, usage, &ctx); - if (i + 1 < argc) { - warn("too many arguments"); - diecmdusage(EX_USAGE, usage, &command_list); - } - - int r = open_db(ctx); + int r = open_db(ssp); if (r < 0) diefu(exitcode_from_errno(errno), "open database"); if (!r) dief(EX_NOINPUT, "no database"); - i = 0; - db_reset(ctx); + const char *ptrn = NULL; + if (ctx.ptrnoff != (size_t) -1) + ptrn = ssp->sa.s + ctx.ptrnoff; + + int i = 0; + db_reset(ssp); dbg("iterating database..."); - while ((r = db_next(ctx)) == 1) { - dbg("entry ", ESC, db_entry(ctx), ESC); - if (list.ptrn && fnmatch(list.ptrn, db_entry(ctx), 0)) + while ((r = db_next(ssp)) == 1) { + dbg("entry ", ESC, db_entry(ssp), ESC); + if (ptrn && fnmatch(ptrn, db_entry(ssp), 0)) continue; - if (list.options & OPT_FMT_INI) { - show_entry_ini(list.options & OPT_NO_COMMENTS, ctx); - } else if (list.options & OPT_DETAILS) { - show_entry(list.options & (OPT_NO_COMMENTS | OPT_SECRET), ctx); + if (ctx.options & OPT_FMT_INI) { + show_entry_ini(ctx.options & OPT_NO_COMMENTS, ssp); + } else if (ctx.options & OPT_DETAILS) { + show_entry(ctx.options & (OPT_NO_COMMENTS | OPT_SECRET), ssp); } else { - if (i) add(list.sep); - add(ESC, db_entry(ctx), ESC); + if (i) add(ctx.sep); + add(ESC, db_entry(ssp), ESC); } i = 1; } if (r < 0) diefu(EX_DATA_ERR, "read database: file corrupted"); if (!i) - dief(EX_DATA_ERR, "no entry matching ", ESC, list.ptrn, ESC); + dief(EX_DATA_ERR, "no entry matching ", ESC, ptrn, ESC); /* add new line + flush buffers */ - if (!(list.options & (OPT_DETAILS | OPT_FMT_INI))) out(NULL); + if (!(ctx.options & (OPT_DETAILS | OPT_FMT_INI))) out(NULL); return 0; } diff --git a/src/ssp/remove.c b/src/ssp/remove.c index d96daf0..3d430df 100644 --- a/src/ssp/remove.c +++ b/src/ssp/remove.c @@ -4,38 +4,72 @@ #include <errno.h> #include <limb/command.h> #include <limb/exitcode.h> +#include <limb/loadopt.h> #include <limb/output.h> #include "ssp.h" +struct remove { + struct ssp *ssp; + size_t entoff; +}; + +enum { + ARGID_ENTRY = OPTID_FIRST, +}; COMMAND(remove, "Remove an entry", "<entry>", NULL); -int -remove_main(int argc, const char *argv[], const char *env[], const char usage[], void *ctx_) +static int +parse_cmdline(int argc, const char *argv[], const char usage[], struct remove *ctx) { - struct ssp *ctx = ctx_; + const struct option options[] = { + LOADOPT_ARGUMENTS, + ARGUMENT_REQ( "entry", OPT_PATH, ARGID_ENTRY), + LOADOPT_DONE + }; + struct loadopt lo = LOADOPT_ZERO; + + int c; + while ((c = loadopt(&ctx->ssp->sa, argc, argv, options, 0, NULL, 0, &lo))) switch (c) { + + case ARGID_ENTRY: + ctx->entoff = LO_OFF(&lo); + break; + + case -1: + diecmdusage(EX_USAGE, usage, &command_remove); + default: + die(EX_SOFTWARE, "unexpected return value ", PMINT(c), " from loadopt"); + }; - if (argc != 2) - diecmdusage(EX_USAGE, usage, &command_remove); + return LO_CUR(&lo); +} - const char *entry = get_entry_name(argv[1], ctx); - if (!entry) - diefusys(exitcode_from_errno(errno), "parse entry's name"); +int +remove_main(int argc, const char *argv[], const char *env[], const char usage[], void *ssp_) +{ + struct ssp *ssp = ssp_; + struct remove ctx = { .ssp = ssp }; - int r = open_db(ctx); + (void) env; + parse_cmdline(argc, argv, usage, &ctx); + + int r = open_db(ssp); if (r < 0) diefu(exitcode_from_errno(errno), "open database"); if (!r) dief(EX_NOINPUT, "no database"); - r = db_find(entry, ctx); + const char *entry = ssp->sa.s + ctx.entoff; + + r = db_find(entry, ssp); if (r < 0) diefu(EX_DATA_ERR, "find entry ", ESC, entry, ESC); if (!r) dief(EX_DATA_ERR, "no entry ", ESC, entry, ESC, " in database"); out("Removing ", ESC, entry, ESC, "..."); - rebuild_db(entry, NULL, NULL, ctx); + rebuild_db(entry, NULL, NULL, ssp); return 0; } diff --git a/src/ssp/rename.c b/src/ssp/rename.c index 7053bd1..19840d0 100644 --- a/src/ssp/rename.c +++ b/src/ssp/rename.c @@ -4,48 +4,90 @@ #include <errno.h> #include <limb/command.h> #include <limb/exitcode.h> +#include <limb/loadopt.h> #include <limb/output.h> +#include <limb/stralloc.h> #include "ssp.h" +struct rename { + stralloc sa; + size_t entoff; + size_t newoff; +}; + +enum { + ARGID_ENTRY = OPTID_FIRST, + ARGID_NEWNAME, +}; COMMAND(rename, "Rename an entry", "<entry> <newname>", NULL); -int -rename_main(int argc, const char *argv[], const char *env[], const char usage[], void *ctx_) +static int +parse_cmdline(int argc, const char *argv[], const char usage[], struct rename *ctx) { - struct ssp *ctx = ctx_; + const struct option options[] = { + LOADOPT_ARGUMENTS, + ARGUMENT_REQ( "entry", OPT_PATH, ARGID_ENTRY), + ARGUMENT_REQ( "newname", OPT_PATH, ARGID_NEWNAME), + LOADOPT_DONE + }; + struct loadopt lo = LOADOPT_ZERO; + + int c; + while ((c = loadopt(&ctx->sa, argc, argv, options, 0, NULL, 0, &lo))) switch (c) { + + case ARGID_ENTRY: + ctx->entoff = LO_OFF(&lo); + break; + case ARGID_NEWNAME: + ctx->newoff = LO_OFF(&lo); + break; + + case -1: + diecmdusage(EX_USAGE, usage, &command_rename); + default: + die(EX_SOFTWARE, "unexpected return value ", PMINT(c), " from loadopt"); + }; - if (argc != 3) - diecmdusage(EX_USAGE, usage, &command_rename); + return LO_CUR(&lo); +} - const char *entry = get_entry_name(argv[1], ctx); - if (!entry) - diefusys(exitcode_from_errno(errno), "parse entry's name"); +int +rename_main(int argc, const char *argv[], const char *env[], const char usage[], void *ssp_) +{ + struct ssp *ssp = ssp_; + struct rename ctx = { .sa = STRALLOC_ZERO }; - const char *new = get_entry_name(argv[2], ctx); - if (!new) - diefusys(exitcode_from_errno(errno), "parse new name"); + (void) env; + parse_cmdline(argc, argv, usage, &ctx); - if (!strcmp(entry, new)) + const char *entry = ctx.sa.s + ctx.entoff; + const char *newname = ctx.sa.s + ctx.newoff; + + if (!strcmp(entry, newname)) diefu(EX_DATA_ERR, "rename entry ", ESC, entry, ESC, ": new name is the same"); - int r = open_db(ctx); + int r = open_db(ssp); if (r < 0) diefu(exitcode_from_errno(errno), "open database"); if (!r) dief(EX_NOINPUT, "no database"); - r = db_find(entry, ctx); + + r = db_find(entry, ssp); if (r < 0) diefu(EX_DATA_ERR, "find entry ", ESC, entry, ESC); if (!r) dief(EX_DATA_ERR, "no entry ", ESC, entry, ESC, " in database"); - out("Renaming ", ESC, entry, ESC, " to ", ESC, new, ESC, "..."); - rebuild_db(entry, new, db_otp(ctx), ctx); + out("Renaming ", ESC, entry, ESC, " to ", ESC, newname, ESC, "..."); + rebuild_db(entry, newname, db_otp(ssp), ssp); - const char *av[] = { NULL, new }; - run_command("show", sizeof(av) / sizeof(*av), av, env, ctx); + dbg("showing renamed entry"); + close_db(ssp); + const char *av[] = { NULL, newname }; + run_command("show", sizeof(av) / sizeof(*av), av, env, ssp); + stralloc_free(&ctx.sa); return 0; } diff --git a/src/ssp/show.c b/src/ssp/show.c index 79aa504..ab088e4 100644 --- a/src/ssp/show.c +++ b/src/ssp/show.c @@ -6,8 +6,9 @@ #include <limb/bytestr.h> #include <limb/command.h> #include <limb/exitcode.h> +#include <limb/hasher.h> /* ALGO_* */ +#include <limb/loadopt.h> #include <limb/output.h> -#include <limb/parseopt.h> #include "ssp.h" enum { @@ -17,11 +18,16 @@ enum { }; struct show { + struct ssp *ssp; + size_t entoff; unsigned options; }; -COMMAND(show, "Show an entry", - "[OPTION..] <entry>", +enum { + ARGID_ENTRY = OPTID_FIRST, +}; + +COMMAND(show, "Show an entry", "[OPTION..] <entry>", " -C, --no-comments Do not show entry's comments\n" " -f, --format Output in INI-like format\n" " -S, --secret Show entry's secret\n" @@ -31,15 +37,17 @@ static int parse_cmdline(int argc, const char *argv[], const char usage[], struct show *ctx) { const struct option options[] = { - OPTION_ARG_NONE('C', "no-comments", 0, OPTID_SHORTOPT), - OPTION_ARG_NONE('f', "format", 0, OPTID_SHORTOPT), - OPTION_ARG_NONE('S', "secret", 0, OPTID_SHORTOPT), - OPTION_DONE + OPTION_ARG_NONE('C', "no-comments", 0, OPTID_SHORTOPT), + OPTION_ARG_NONE('f', "format", 0, OPTID_SHORTOPT), + OPTION_ARG_NONE('S', "secret", 0, OPTID_SHORTOPT), + LOADOPT_ARGUMENTS, + ARGUMENT_REQ( "entry", OPT_PATH, ARGID_ENTRY), + LOADOPT_DONE }; - struct parseopt po = { 0 }; + struct loadopt lo = LOADOPT_ZERO; int c; - while ((c = parseopt(argc, argv, options, 0, &po))) switch (c) { + while ((c = loadopt(&ctx->ssp->sa, argc, argv, options, 0, NULL, 0, &lo))) switch (c) { case 'C': ctx->options |= OPT_NO_COMMENTS; break; @@ -49,13 +57,18 @@ parse_cmdline(int argc, const char *argv[], const char usage[], struct show *ctx case 'S': ctx->options |= OPT_SECRET; break; + + case ARGID_ENTRY: + ctx->entoff = LO_OFF(&lo); + break; + case -1: diecmdusage(EX_USAGE, usage, &command_show); default: - die(EX_SOFTWARE, "unexpected return value ", PUTMSG_INT(c), " from parseopt"); + die(EX_SOFTWARE, "unexpected return value ", PMINT(c), " from loadopt"); }; - return PO_CUR(&po); + return LO_CUR(&lo); } void @@ -67,10 +80,10 @@ show_entry(unsigned options, struct ssp *ctx) out(" Algo: ", algos[otp->algo]); add(" ", types[otp->type], "-based; "); if (otp->type == TYPE_COUNTER) - out("Counter: ", PUTMSG_UINT(otp->c)); + out("Counter: ", PMUINT(otp->c)); else /* TYPE_TIME */ - out("Precision: ", PUTMSG_UINT(otp->t.precision), "s"); - out(" Digits: ", PUTMSG_UINT(otp->digits)); + out("Precision: ", PMUINT(otp->t.precision), "s"); + out(" Digits: ", PMUINT(otp->digits)); if (options & OPT_SECRET) { size_t l = base32_fmt(NULL, otp->data, otp->slen, 0); char buf[l+1]; @@ -92,12 +105,12 @@ show_entry_ini(unsigned options, struct ssp *ctx) out("[", PUTMSG_TOGGLE_ESC, db_entry(ctx), PUTMSG_TOGGLE_ESC, "]"); out("algo=", algos[otp->algo]); - out("digits=", PUTMSG_UINT(otp->digits)); + out("digits=", PMUINT(otp->digits)); if (otp->type == TYPE_TIME) { out("time=1"); - out("precision=", PUTMSG_UINT(otp->t.precision)); + out("precision=", PMUINT(otp->t.precision)); } else { - out("counter=", PUTMSG_UINT(otp->c)); + out("counter=", PMUINT(otp->c)); } size_t l = base32_fmt(NULL, otp->data, otp->slen, 1); char buf[l + 1]; @@ -109,43 +122,31 @@ show_entry_ini(unsigned options, struct ssp *ctx) } int -show_main(int argc, const char *argv[], const char *env[], const char usage[], void *ctx_) +show_main(int argc, const char *argv[], const char *env[], const char usage[], void *ssp_) { - struct ssp *ctx = ctx_; - struct show show = { 0 }; + struct ssp *ssp = ssp_; + struct show ctx = { .ssp = ssp }; - int i = parse_cmdline(argc, argv, usage, &show); + (void) env; + parse_cmdline(argc, argv, usage, &ctx); - if (i == argc) { - warn("argument \"entry\" missing"); - diecmdusage(EX_USAGE, usage, &command_show); - } - - if (i + 1 < argc) { - warn("too many arguments"); - diecmdusage(EX_USAGE, usage, &command_show); - } - - int r = open_db(ctx); + int r = open_db(ssp); if (r < 0) diefu(exitcode_from_errno(errno), "open database"); if (!r) dief(EX_NOINPUT, "no database"); - const char *entry = get_entry_name(argv[i], ctx); - if (!entry) - diefusys(exitcode_from_errno(errno), "parse entry's name"); - - r = db_find(entry, ctx); + const char *entry = ssp->sa.s + ctx.entoff; + r = db_find(entry, ssp); if (r < 0) diefu(EX_DATA_ERR, "find entry ", ESC, entry, ESC); if (!r) dief(EX_DATA_ERR, "no entry ", ESC, entry, ESC, " in database"); - if (show.options & OPT_FMT_INI) - show_entry_ini(show.options & OPT_NO_COMMENTS, ctx); + if (ctx.options & OPT_FMT_INI) + show_entry_ini(ctx.options & OPT_NO_COMMENTS, ssp); else - show_entry(show.options, ctx); + show_entry(ctx.options, ssp); return 0; } diff --git a/src/ssp/ssp.c b/src/ssp/ssp.c index 05ed0b6..c19575c 100644 --- a/src/ssp/ssp.c +++ b/src/ssp/ssp.c @@ -9,16 +9,13 @@ #include <limb/command.h> #include <limb/esc.h> #include <limb/exitcode.h> -#include <limb/parseopt.h> +#include <limb/loadopt.h> #include <limb/output.h> #include "ssp.h" #include "config.h" const char *PROG = "ssp"; -const char *algos[] = { "sha1", "sha256", "sha512", "sha3-224", "sha3-256", - "sha3-512", "blake3", 0 }; - enum { OPTID_VERSION = OPTID_FIRST, OPTID_DEBUG, @@ -41,74 +38,39 @@ exitcode_from_errno(int e) int run_command(const char *name, int argc, const char *argv[], const char *env[], void *ctx) { - dbg("running command ", name, "..."); + dbg("running command ", name); for (struct command **c = commands; *c; ++c) if (!strcmp((*c)->name, name)) return (*c)->main(argc, argv, env, "", ctx); return -1; } -ssize_t -unesc(char *dst, size_t dlen, const char *sce, size_t slen) -{ - /* unescape it */ - ssize_t l = esc_scan(dst, dlen, sce, slen); - if (l < 0) return -1; - /* make sure there was no encoded NUL byte */ - if (byte_chr(dst, l, 0) < (size_t) l) - return (errno = EINVAL, -1); - dst[l++] = 0; - return l; -} - -const char * -get_entry_name(const char *s, struct ssp *ctx) -{ - ssize_t l = strlen(s); - - if (*s != '"' || s[l - 1] != '"') - /* not quoted, return as-is */ - return s; - - size_t salen = ctx->sa.len; - l -= 2; - if (!stralloc_readyplus(&ctx->sa, l + 1)) - return NULL; - - /* unescape it */ - l = unesc(ctx->sa.s + ctx->sa.len, l, s + 1, l); - if (l < 0) return NULL; - ctx->sa.len += l; - return ctx->sa.s + salen; -} - static struct command * parse_cmdline(int *argc, const char **argv[], struct ssp *ctx) { - const char usage[] = "[-h] [-D database] command [...]"; + const char usage[] = "[-h] [OPTION..] command [...]"; const struct option options[] = { - OPTION_ARG_OPT ( 0 , "debug", 0, OPTID_DEBUG), - OPTION_ARG_REQ ('D', "database", 0, OPTID_SHORTOPT), - OPTION_ARG_NONE('h', "help", 0, OPTID_SHORTOPT), - OPTION_ARG_NONE('q', "quiet", 0, OPTID_SHORTOPT), - OPTION_ARG_NONE( 0 , "version", 0, OPTID_VERSION), - OPTION_DONE + OPTION_ARG_OPT ( 0 , "debug", 0, OPTID_DEBUG), + OPTION_ARG_REQ ('D', "database", OPT_PATH, OPTID_SHORTOPT), + OPTION_ARG_NONE('h', "help", 0, OPTID_SHORTOPT), + OPTION_ARG_NONE('q', "quiet", 0, OPTID_SHORTOPT), + OPTION_ARG_NONE( 0 , "version", 0, OPTID_VERSION), + LOADOPT_STOP }; - struct parseopt po = { 0 }; + struct loadopt lo = LOADOPT_ZERO; int c; - while ((c = parseopt(*argc, *argv, options, 0, &po))) switch (c) { + while ((c = loadopt(&ctx->sa, *argc, *argv, options, 0, NULL, 0, &lo))) switch (c) { case 'D': - ctx->db = PO_ARG(&po); break; case 'h': ctx->options |= OPT_HELP; break; case 'q': - autoopt_quiet(&options[PO_IDX(&po)], PO_ARG(&po)); + autoopt_quiet(&options[LO_IDX(&lo)], LO_ARG(&lo)); break; case OPTID_DEBUG: - if (!autoopt_debug(&options[PO_IDX(&po)], PO_ARG(&po))) + if (!autoopt_debug(&options[LO_IDX(&lo)], LO_ARG(&lo))) dieusage(EX_USAGE, usage); break; case OPTID_VERSION: @@ -116,11 +78,11 @@ parse_cmdline(int *argc, const char **argv[], struct ssp *ctx) case -1: dieusage(EX_USAGE, usage); default: - die(EX_SOFTWARE, "unexpected return value ", PUTMSG_INT(c), " from parseopt"); + die(EX_SOFTWARE, "unexpected return value ", PMINT(c), " from loadopt"); }; /* no command specified */ - if (PO_CUR(&po) == *argc) + if (LO_CUR(&lo) == *argc) dienocommand(EX_USAGE, usage, (ctx->options & OPT_HELP) ? " -D, --database FILE Use FILE as database\n" "\n" @@ -131,8 +93,8 @@ parse_cmdline(int *argc, const char **argv[], struct ssp *ctx) " --version Show version information and exit\n" : NULL); - *argc -= PO_CUR(&po); - *argv += PO_CUR(&po); + *argc -= LO_CUR(&lo); + *argv += LO_CUR(&lo); return getcommandordie(EX_USAGE, usage, **argv); } @@ -140,28 +102,29 @@ parse_cmdline(int *argc, const char **argv[], struct ssp *ctx) int main(int argc, const char *argv[], const char *env[]) { - const char usage[] = "[-h] [-D database] "; + const char usage[] = "[-h] [-D database] [-q] "; struct ssp ctx = { 0 }; setlocale(LC_ALL, ""); struct command *command = parse_cmdline(&argc, &argv, &ctx); - if (!ctx.db) { + if (!ctx.sa.len) { const char *home = env_get2(env, "HOME"); + dbg("no database set, using default. $HOME=", ESC, home, ESC); if (!home || !*home) { warn("$HOME not defined, using current directory instead"); home = "."; } - if (!stralloc_cats(&ctx.sa, home) - || !stralloc_cats(&ctx.sa, "/ssp.db") || !stralloc_0(&ctx.sa)) + if (!stralloc_cats(&ctx.sa, home) || !stralloc_cats0(&ctx.sa, "/ssp.db")) diefusys(EX_TEMPFAIL, "set database path"); } + dbg("database: ", ESC, db_file(&ctx), ESC); if (ctx.options & OPT_HELP) diecmdhelp(0, usage, command); - dbg("database: ", ESC, db_file(&ctx), ESC, "; running command ", command->name, "..."); + dbg("running command ", command->name); int r = command->main(argc, argv, env, usage, &ctx); stralloc_free(&ctx.sa); return r;