Welcome to little lamb

Code » ssp » master » tree

[master] / src / ssp / edit.c

/* 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/hasher.h> /* ALGO_* */
#include <limb/loadopt.h>
#include <limb/output.h>
#include <limb/stralloc.h>
#include <limb/u16.h>
#include "ssp.h"

struct edit {
    stralloc sa;
    const char *secret;
    size_t cmtoff;
    size_t entoff;
    struct otp otp;
};

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 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 *ctx)
{
    const struct option options[] = {
        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 loadopt lo = LOADOPT_ZERO;

    int c, r;
    while ((c = loadopt(&ctx->sa, argc, argv, options, 0, NULL, 0, &lo))) switch (c) {
        case 'a':
            {
                if (!strcmp(LO_ARG(&lo), "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, LO_ARG(&lo), strlen(LO_ARG(&lo)));
                if (r < 0) {
                    if (first > -1)
                        list_matches(obuffer_1, OLVL_NORMAL, "did you mean ",
                                     NULL, " or ", " ?", LO_ARG(&lo), strlen(LO_ARG(&lo)),
                                     first, (const char **) algos);
                    diecmdusage(EX_USAGE, usage, &command_edit);
                }
                ctx->otp.algo = r;
            }
            break;
        case 'C':
            ctx->cmtoff = LO_OFF(&lo);
            break;
        case 'c':
            if (!LO_ARG(&lo)) {
                ctx->otp.n = 1;
            } else {
                if (validate_counter(&ctx->otp.n, LO_ARG(&lo), strlen(LO_ARG(&lo))) < 0)
                    diecmdusage(EX_USAGE, usage, &command_edit);
            }
            ctx->otp.type = TYPE_COUNTER;
            break;
        case 'd':
            r = validate_digits(LO_ARG(&lo), strlen(LO_ARG(&lo)));
            if (r < 0)
                diecmdusage(EX_USAGE, usage, &command_edit);
            ctx->otp.digits = r;
            break;
        case 'e':
            ctx->entoff = LO_OFF(&lo);
            break;
        case 's':
            ctx->secret = LO_ARG(&lo);
            break;
        case 't':
            if (!LO_ARG(&lo)) {
                ctx->otp.n = 30;
            } else {
                r = validate_precision(LO_ARG(&lo), strlen(LO_ARG(&lo)));
                if (r < 0)
                    diecmdusage(EX_USAGE, usage, &command_edit);
                ctx->otp.n = r;
            }
            ctx->otp.type = TYPE_TIME;
            break;

        case -1:
            diecmdusage(EX_USAGE, usage, &command_edit);
        default:
            die(EX_SOFTWARE, "unexpected return value ", PMINT(c), " from loadopt");
    };

    return LO_CUR(&lo);
}

int
edit_main(int argc, const char *argv[], const char *env[], const char usage[], void *ssp_)
{
    struct ssp *ssp = ssp_;
    struct edit ctx = { .sa = STRALLOC_ZERO, .otp.type = TYPE_UNKNOWN,
        .otp.algo = ALGO_UNKNOWN, .cmtoff = (size_t) -1 };

    (void) env;
    parse_cmdline(argc, argv, usage, &ctx);

    const char *entry = ctx.sa.s + ctx.entoff;

    int r = open_db(ssp, 1);
    if (r < 0)
        diefu(exitcode_from_errno(errno), "open database");

    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(ssp);

    ssize_t slen;
    if (!ctx.secret) {
        slen = cur->slen;
    } else {
        slen = base32_scan(NULL, ctx.secret, strlen(ctx.secret), 0);
        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 (ctx.cmtoff == (size_t) -1)
        clen = strlen(cur->data + cur->slen);
    else
        clen = strlen(ctx.sa.s + ctx.cmtoff);

    size_t otplen = sizeof(ctx.otp) + slen + clen + 1;

    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;
    p->n = (ctx.otp.type != TYPE_UNKNOWN) ? ctx.otp.n : cur->n;

    p->slen = slen;
    if (ctx.secret)
        base32_scan(p->data, ctx.secret, strlen(ctx.secret), 0);
    else
        memcpy(p->data, cur->data, slen);

    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");

    quiet("Editing entry ", ESC, entry, ESC, "... ");
    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, ssp);

    stralloc_free(&ctx.sa);
    return 0;
}