Welcome to little lamb

Code » ssp » commit cc4f05b

Add command import

author Olivier Brunel
2023-04-20 17:37:00 UTC
committer Olivier Brunel
2023-04-25 15:02:08 UTC
parent 7402b4b19a8645a439d6b23ccfd09f0e744d0a0d

Add command import

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);
+}