Welcome to little lamb

Code » ssp » commit e3268bf

Add command edit

author Olivier Brunel
2023-04-18 16:53:00 UTC
committer Olivier Brunel
2023-04-19 07:43:49 UTC
parent 038bde7568e5beb6ec7abfe03cdaf104128d9cb5

Add command edit

src/include/ssp.h +3 -1
src/ssp/commands.c +2 -0
src/ssp/database.c +6 -0
src/ssp/edit.c +207 -0

diff --git a/src/include/ssp.h b/src/include/ssp.h
index fc63971..ba8f30f 100644
--- a/src/include/ssp.h
+++ b/src/include/ssp.h
@@ -33,7 +33,8 @@ struct ssp {
 
 enum {
     TYPE_COUNTER = 0,
-    TYPE_TIME
+    TYPE_TIME,
+    TYPE_UNKNOWN = (1 << 2) - 1 /* max for 2 bits */
 };
 
 enum {
@@ -44,6 +45,7 @@ enum {
     ALGO_SHA3_256,
     ALGO_SHA3_512,
     ALGO_BLAKE3,
+    ALGO_UNKNOWN = (1 << 4) - 1 /* max for 4 bits */
 };
 
 extern const char *algos[];
diff --git a/src/ssp/commands.c b/src/ssp/commands.c
index 8d181b2..ab68f14 100644
--- a/src/ssp/commands.c
+++ b/src/ssp/commands.c
@@ -4,6 +4,7 @@
 #include <limb/command.h>
 
 extern struct command command_add;
+extern struct command command_edit;
 extern struct command command_list;
 extern struct command command_remove;
 extern struct command command_rename;
@@ -11,6 +12,7 @@ extern struct command command_show;
 
 struct command *commands[] = {
     &command_add,
+    &command_edit,
     &command_list,
     &command_remove,
     &command_rename,
diff --git a/src/ssp/database.c b/src/ssp/database.c
index d78e657..d923fb2 100644
--- a/src/ssp/database.c
+++ b/src/ssp/database.c
@@ -105,6 +105,12 @@ rebuild_db(const char *oldkey, const char *newkey, const struct otp *otp, struct
     cdbmaker_sa mkr;
     int r = 0;
 
+    /* oldkey & newkey  : renaming/editing an entry
+     * oldkey & NULL    : removing an entry
+     * NULL   & newkey  : adding an entry
+     * NULL   & NULL    : isn't allowed
+     */
+
     if (newkey) {
         nlen = strlen(newkey) + 1;
         vlen = sizeof(*otp) + otp->slen + strlen(otp->data + otp->slen) + 1;
diff --git a/src/ssp/edit.c b/src/ssp/edit.c
new file mode 100644
index 0000000..7f7d796
--- /dev/null
+++ b/src/ssp/edit.c
@@ -0,0 +1,207 @@
+/* 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 <limb/base32.h>
+#include <limb/bytestr.h>
+#include <limb/command.h>
+#include <limb/esc.h>
+#include <limb/exitcode.h>
+#include <limb/output.h>
+#include <limb/parseopt.h>
+#include <limb/u16.h>
+#include "ssp.h"
+
+struct edit {
+    struct otp otp;
+    const char *comments;
+    const char *secret;
+};
+
+COMMAND(edit, "Edit an entry",
+        "<entry> OPTION[..]",
+" -a, --algo ALGO                       Set ALGO as hashing algorithm [sha256]\n"
+"                                       Use 'list' to list available algorithms\n"
+" -c, --comments COMMENTS               Set COMMENTS as entry's comments\n"
+" -d, --digits[=NUM]                    Set to counter-based (HOTP) using NUM digits [6]\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)
+{
+    const struct option options[] = {
+        OPTION_ARG_REQ ('a', "algo",                    0, OPTID_SHORTOPT),
+        OPTION_ARG_REQ ('c', "comments",                0, OPTID_SHORTOPT),
+        OPTION_ARG_OPT ('d', "digits",                  0, OPTID_SHORTOPT),
+        OPTION_ARG_REQ ('s', "secret",                  0, OPTID_SHORTOPT),
+        OPTION_ARG_OPT ('t', "time",                    0, OPTID_SHORTOPT),
+        OPTION_DONE
+    };
+    struct parseopt po = { 0 };
+
+    int c;
+    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 (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;
+            }
+            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);
+                    diecmdusage(EX_USAGE, usage, &command_edit);
+                }
+            }
+            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);
+                    diecmdusage(EX_USAGE, usage, &command_edit);
+                }
+                edit->otp.precision = u;
+            }
+            break;
+        case -1:
+            diecmdusage(EX_USAGE, usage, &command_edit);
+        default:
+            die(EX_SOFTWARE, "unexpected return value ", PUTMSG_INT(c), " from parseopt");
+    };
+
+    return po.cur;
+}
+
+int
+edit_main(int argc, const char *argv[], const char *env[], const char usage[], void *ctx_)
+{
+    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");
+
+    --argc;
+    ++argv;
+    i = parse_cmdline(argc, argv, usage, &edit);
+
+    if (argc > i) {
+        warn("too many arguments");
+        diecmdusage(EX_USAGE, usage, &command_edit);
+    }
+
+    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");
+
+    const struct otp *cur = db_otp(ctx);
+
+    ssize_t slen;
+    if (!edit.secret) {
+        slen = cur->slen;
+    } else {
+        slen = base32_scan(NULL, edit.secret, strlen(edit.secret));
+        if (slen < 0)
+            dief(EX_DATA_ERR, "invalid secret: Not in base32");
+        if (!slen)
+            dief(EX_DATA_ERR, "secret is empty");
+    }
+
+    size_t clen;
+    if (!edit.comments)
+        clen = strlen(cur->data + cur->slen);
+    else
+        clen = strlen(edit.comments);
+
+    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;
+    /* we only use digits below because both digits & precision are actualy the
+     * same variable (union) */
+    if (edit.otp.type != TYPE_UNKNOWN) {
+        p->type = edit.otp.type;
+        p->digits = edit.otp.digits;
+    } else {
+        p->type = cur->type;
+        p->digits = cur->digits;
+    }
+
+    p->slen = slen;
+    if (edit.secret)
+        base32_scan(p->data, edit.secret, strlen(edit.secret));
+    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);
+    }
+
+    out("Editing entry ", ESC, entry, ESC, "... ");
+    rebuild_db(entry, entry, p, ctx);
+
+    return 0;
+}