#include "system.h"
#include <errno.h>
#include <sys/wait.h>
#include <termios.h>

#include <popt.h>
#include <rpm/rpmcli.h>
#include <rpm/rpmlog.h>
#include <rpm/rpmsign.h>
#include "cliutils.hh"
#include "debug.h"

enum modes {
    MODE_NONE    = 0,
    MODE_ADDSIGN = (1 << 0),
    MODE_RESIGN  = (1 << 1),
    MODE_DELSIGN = (1 << 2),
    MODE_DELFILESIGN = (1 << 3),
};

static int mode = MODE_NONE;

#if defined(WITH_IMAEVM) || defined(WITH_FSVERITY)
static int fskpass = 0;
static char * fileSigningKey = NULL;
#endif
#ifdef WITH_FSVERITY
static char * fileSigningCert = NULL;
static char * verityAlgorithm = NULL;
#endif

static struct rpmSignArgs sargs;

static struct poptOption signOptsTable[] = {
    { "addsign", '\0', (POPT_ARG_VAL|POPT_ARGFLAG_OR), &mode, MODE_ADDSIGN,
	N_("sign package(s), adding a new signature"), NULL },
    { "resign", '\0', (POPT_ARG_VAL|POPT_ARGFLAG_OR), &mode, MODE_RESIGN,
	N_("resign package(s), deleting any previous signatures"), NULL },
    { "delsign", '\0', (POPT_ARG_VAL|POPT_ARGFLAG_OR), &mode, MODE_DELSIGN,
	N_("delete package signatures"), NULL },
#if defined(WITH_IMAEVM) || defined(WITH_FSVERITY)
    { "delfilesign", '\0', (POPT_ARG_VAL|POPT_ARGFLAG_OR), &mode,
      MODE_DELFILESIGN,	N_("delete IMA and fsverity file signatures"), NULL },
#endif
    { "rpmv3", '\0', (POPT_ARG_VAL|POPT_ARGFLAG_OR),
	&sargs.signflags, RPMSIGN_FLAG_RPMV3,
	N_("request legacy rpm v3 header+payload signatures on v4 packages") },
    { "rpmv4", '\0', (POPT_ARG_VAL|POPT_ARGFLAG_OR),
	&sargs.signflags, RPMSIGN_FLAG_RPMV4,
	N_("request legacy rpm v4 header signatures on v6 packages") },
    { "rpmv6", '\0', (POPT_ARG_VAL|POPT_ARGFLAG_OR),
	&sargs.signflags, RPMSIGN_FLAG_RPMV6,
	N_("request rpm v6 header signature on v4 packages") },
#ifdef WITH_IMAEVM
    { "signfiles", '\0', (POPT_ARG_VAL|POPT_ARGFLAG_OR),
	&sargs.signflags, RPMSIGN_FLAG_IMA,
	N_("sign package(s) files"), NULL},
#endif
#ifdef WITH_FSVERITY
    { "signverity", '\0', (POPT_ARG_VAL|POPT_ARGFLAG_OR),
	&sargs.signflags, RPMSIGN_FLAG_FSVERITY,
	N_("generate fsverity signatures for package(s) files"), NULL},
    { "verityalgo", '\0', POPT_ARG_STRING, &verityAlgorithm, 0,
	N_("algorithm to use for verity signatures, default sha256"),
	N_("<algorithm>") },
    { "certpath", '\0', POPT_ARG_STRING, &fileSigningCert, 0,
	N_("use file signing cert <cert>"),
	N_("<cert>") },
#endif
#if defined(WITH_IMAEVM) || defined(WITH_FSVERITY)
    { "fskpath", '\0', POPT_ARG_STRING, &fileSigningKey, 0,
	N_("use file signing key <key>"),
	N_("<key>") },
    { "fskpass", '\0', POPT_ARG_NONE, &fskpass, 0,
	N_("prompt for file signing key password"), NULL},
#endif
    POPT_TABLEEND
};

static struct poptOption optionsTable[] = {
    { NULL, '\0', POPT_ARG_INCLUDE_TABLE, signOptsTable, 0,
	N_("Signature options:"), NULL },
    { NULL, '\0', POPT_ARG_INCLUDE_TABLE, rpmcliAllPoptTable, 0,
	N_("Common options for all rpm modes and executables:"), NULL },

    POPT_AUTOALIAS
    POPT_AUTOHELP
    POPT_TABLEEND
};

#if defined(WITH_IMAEVM) || defined(WITH_FSVERITY)
static int flags_sign_files(int flags)
{
	return (flags & (RPMSIGN_FLAG_IMA | RPMSIGN_FLAG_FSVERITY) ? 1 : 0);
}

static char *get_fskpass(void)
{
    struct termios flags, tmp_flags;
    int passlen = 64;
    char *password = (char *)xmalloc(passlen);
    char *pwd = NULL;

    tcgetattr(fileno(stdin), &flags);
    tmp_flags = flags;
    tmp_flags.c_lflag &= ~ECHO;
    tmp_flags.c_lflag |= ECHONL;

    if (tcsetattr(fileno(stdin), TCSANOW, &tmp_flags) != 0) {
	perror("tcsetattr");
	goto exit;
    }

    printf("PEM password: ");
    pwd = fgets(password, passlen, stdin);

    if (tcsetattr(fileno(stdin), TCSANOW, &flags) != 0) {
	perror("tcsetattr");
	pwd = NULL;
	goto exit;
    }

exit:
    if (pwd)
	pwd[strlen(pwd) - 1] = '\0';  /* remove newline */
    else
	free(password);
    return pwd;
}
#endif

/* TODO: permit overriding macro setup on the command line */
static int doSign(poptContext optCon, struct rpmSignArgs *sargs)
{
    int rc = EXIT_FAILURE;
    char * name = rpmExpand("%{?_openpgp_sign_id}", NULL);

    if (rstreq(name, "")) {
	fprintf(stderr, _("You must set \"%%_openpgp_sign_id\" in your macro file\n"));
	goto exit;
    }

#if defined(WITH_IMAEVM) || defined(WITH_FSVERITY)
    if (fileSigningKey) {
	rpmPushMacro(NULL, "_file_signing_key", NULL, fileSigningKey, RMIL_GLOBAL);
    }

#ifdef WITH_FSVERITY
    if (fileSigningCert) {
	rpmPushMacro(NULL, "_file_signing_cert", NULL, fileSigningCert, RMIL_GLOBAL);
    }
    if (verityAlgorithm) {
	rpmPushMacro(NULL, "_verity_algorithm", NULL, verityAlgorithm, RMIL_GLOBAL);
    }
#endif

    if (flags_sign_files(sargs->signflags)) {
	char *fileSigningKeyPassword = NULL;
	char *key = rpmExpand("%{?_file_signing_key}", NULL);
	if (rstreq(key, "")) {
	    fprintf(stderr, _("You must set \"%%_file_signing_key\" in your macro file or on the command line with --fskpath\n"));
	    free(key);
	    goto exit;
	}

	if (fskpass) {
	    fileSigningKeyPassword = get_fskpass();
	}

	if (fileSigningKeyPassword) {
	    rpmPushMacro(NULL, "_file_signing_key_password", NULL,
			fileSigningKeyPassword, RMIL_CMDLINE);
	    memset(fileSigningKeyPassword, 0, strlen(fileSigningKeyPassword));
	    free(fileSigningKeyPassword);
	}

	free(key);
    }
#endif

    const char *arg;
    rc = 0;
    while ((arg = poptGetArg(optCon)) != NULL) {
	if (rpmPkgSign(arg, sargs) < 0)
	    rc++;
	else
	    rpmlog(RPMLOG_INFO, "%s\n", arg);
    }

exit:
    free(name);
    return rc;
}

int main(int argc, char *argv[])
{
    int ec = EXIT_FAILURE;
    poptContext optCon = NULL;
    const char *arg;
    
    optCon = rpmcliInit(argc, argv, optionsTable);

    if (argc <= 1) {
	printUsage(optCon, stderr, 0);
	goto exit;
    }

    if (poptPeekArg(optCon) == NULL) {
	argerror(_("no arguments given"));
    }

#if defined(WITH_IMAEVM) || defined(WITH_FSVERITY)
    if (fileSigningKey && !(flags_sign_files(sargs.signflags))) {
	argerror(_("--fskpath may only be specified when signing files"));
    }
#endif

    switch (mode) {
    case MODE_RESIGN:
	sargs.signflags |= RPMSIGN_FLAG_RESIGN;
	/* fallthrough */
    case MODE_ADDSIGN:
	ec = doSign(optCon, &sargs);
	break;
    case MODE_DELSIGN:
	ec = 0;
	while ((arg = poptGetArg(optCon)) != NULL) {
	    if (rpmPkgDelSign(arg, &sargs) < 0)
		ec++;
	    else
		rpmlog(RPMLOG_INFO, "%s\n", arg);
	}
	break;
    case MODE_DELFILESIGN:
	ec = 0;
	while ((arg = poptGetArg(optCon)) != NULL) {
	    if (rpmPkgDelFileSign(arg, &sargs) < 0)
		ec++;
	    else
		rpmlog(RPMLOG_INFO, "%s\n", arg);
	}
	break;
    case MODE_NONE:
	printUsage(optCon, stderr, 0);
	break;
    default:
	argerror(_("only one major mode may be specified"));
	break;
    }

exit:
    rpmcliFini(optCon);
    return RETVAL(ec);
}
