/* signature.c - RPM signature functions */

/* NOTES
 *
 * Things have been cleaned up wrt PGP.  We can now handle
 * signatures of any length (which means you can use any
 * size key you like).  We also honor PGPPATH finally.
 */

#include "config.h"
#include "miscfn.h"

#if HAVE_ASM_BYTEORDER_H
#include <asm/byteorder.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>

#include "intl.h"
#include "md5.h"
#include "misc.h"
#include "rpmlib.h"
#include "rpmlead.h"
#include "signature.h"
#include "tread.h"
#include "messages.h"

typedef int (*md5func)(char * fn, unsigned char * digest);

static int makePGPSignature(char *file, void **sig, int_32 *size,
			    char *passPhrase);
static int checkSize(int fd, int size, int sigsize);
static int verifySizeSignature(char *datafile, int_32 size, char *result);
static int verifyMD5Signature(char *datafile, unsigned char *sig,
			      char *result, md5func fn);
static int verifyPGPSignature(char *datafile, void *sig,
			      int count, char *result);
static int checkPassPhrase(char *passPhrase);

int rpmLookupSignatureType(void)
{
    char *name;

    if (! (name = rpmGetVar(RPMVAR_SIGTYPE))) {
	return 0;
    }

    if (!strcasecmp(name, "none")) {
	return 0;
    } else if (!strcasecmp(name, "pgp")) {
	return RPMSIGTAG_PGP;
    } else {
	return -1;
    }
}

/* rpmReadSignature() emulates the new style signatures if it finds an */
/* old-style one.  It also immediately verifies the header+archive  */
/* size and returns an error if it doesn't match.                   */

int rpmReadSignature(int fd, Header *header, short sig_type)
{
    unsigned char buf[2048];
    int sigSize, pad;
    int_32 type, count;
    int_32 *archSize;
    Header h;

    if (header) {
	*header = NULL;
    }
    
    switch (sig_type) {
      case RPMSIG_NONE:
	rpmMessage(RPMMESS_DEBUG, "No signature\n");
	break;
      case RPMSIG_PGP262_1024:
	rpmMessage(RPMMESS_DEBUG, "Old PGP signature\n");
	/* These are always 256 bytes */
	if (timedRead(fd, buf, 256) != 256) {
	    return 1;
	}
	if (header) {
	    *header = headerNew();
	    headerAddEntry(*header, RPMSIGTAG_PGP, RPM_BIN_TYPE, buf, 152);
	}
	break;
      case RPMSIG_MD5:
      case RPMSIG_MD5_PGP:
	rpmError(RPMERR_BADSIGTYPE,
	      _("Old (internal-only) signature!  How did you get that!?"));
	return 1;
	break;
      case RPMSIG_HEADERSIG:
	rpmMessage(RPMMESS_DEBUG, "New Header signature\n");
	/* This is a new style signature */
	h = headerRead(fd, HEADER_MAGIC_YES);
	if (! h) {
	    return 1;
	}
	sigSize = headerSizeof(h, HEADER_MAGIC_YES);
	pad = (8 - (sigSize % 8)) % 8; /* 8-byte pad */
	rpmMessage(RPMMESS_DEBUG, "Signature size: %d\n", sigSize);
	rpmMessage(RPMMESS_DEBUG, "Signature pad : %d\n", pad);
	if (! headerGetEntry(h, RPMSIGTAG_SIZE, &type, (void **)&archSize, &count)) {
	    headerFree(h);
	    return 1;
	}
	if (checkSize(fd, *archSize, sigSize + pad)) {
	    headerFree(h);
	    return 1;
	}
	if (pad) {
	    if (timedRead(fd, buf, pad) != pad) {
		headerFree(h);
		return 1;
	    }
	}
	if (header) {
	    *header = h;
	} else {
	    headerFree(h);
	}
	break;
      default:
	return 1;
    }

    return 0;
}

int rpmWriteSignature(int fd, Header header)
{
    int sigSize, pad;
    unsigned char buf[8];
    
    headerWrite(fd, header, HEADER_MAGIC_YES);
    sigSize = headerSizeof(header, HEADER_MAGIC_YES);
    pad = (8 - (sigSize % 8)) % 8;
    if (pad) {
	rpmMessage(RPMMESS_DEBUG, "Signature size: %d\n", sigSize);
	rpmMessage(RPMMESS_DEBUG, "Signature pad : %d\n", pad);
	memset(buf, 0, pad);
	write(fd, buf, pad);
    }
    return 0;
}

Header rpmNewSignature(void)
{
    return headerNew();
}

void rpmFreeSignature(Header h)
{
    headerFree(h);
}

int rpmAddSignature(Header header, char *file, int_32 sigTag, char *passPhrase)
{
    struct stat statbuf;
    int_32 size;
    unsigned char buf[16];
    void *sig;
    
    switch (sigTag) {
      case RPMSIGTAG_SIZE:
	stat(file, &statbuf);
	size = statbuf.st_size;
	headerAddEntry(header, RPMSIGTAG_SIZE, RPM_INT32_TYPE, &size, 1);
	break;
      case RPMSIGTAG_MD5:
	mdbinfile(file, buf);
	headerAddEntry(header, sigTag, RPM_BIN_TYPE, buf, 16);
	break;
      case RPMSIGTAG_PGP:
	makePGPSignature(file, &sig, &size, passPhrase);
	headerAddEntry(header, sigTag, RPM_BIN_TYPE, sig, size);
	break;
    }

    return 0;
}

static int makePGPSignature(char *file, void **sig, int_32 *size,
			    char *passPhrase)
{
    char name[1024];
    char sigfile[1024];
    int pid, status;
    int fd, inpipe[2];
    FILE *fpipe;
    struct stat statbuf;

    sprintf(name, "+myname=\"%s\"", rpmGetVar(RPMVAR_PGP_NAME));

    sprintf(sigfile, "%s.sig", file);

    pipe(inpipe);
    
    if (!(pid = fork())) {
	close(0);
	dup2(inpipe[0], 3);
	close(inpipe[1]);
	dosetenv("PGPPASSFD", "3", 1);
	if (rpmGetVar(RPMVAR_PGP_PATH)) {
	    dosetenv("PGPPATH", rpmGetVar(RPMVAR_PGP_PATH), 1);
	}
	/* dosetenv("PGPPASS", passPhrase, 1); */
	execlp("pgp", "pgp",
	       "+batchmode=on", "+verbose=0", "+armor=off",
	       name, "-sb", file, sigfile,
	       NULL);
	rpmError(RPMERR_EXEC, _("Couldn't exec pgp"));
	_exit(RPMERR_EXEC);
    }

    fpipe = fdopen(inpipe[1], "w");
    close(inpipe[0]);
    fprintf(fpipe, "%s\n", passPhrase);
    fclose(fpipe);

    waitpid(pid, &status, 0);
    if (!WIFEXITED(status) || WEXITSTATUS(status)) {
	rpmError(RPMERR_SIGGEN, _("pgp failed"));
	return 1;
    }

    if (stat(sigfile, &statbuf)) {
	/* PGP failed to write signature */
	unlink(sigfile);  /* Just in case */
	rpmError(RPMERR_SIGGEN, _("pgp failed to write signature"));
	return 1;
    }

    *size = statbuf.st_size;
    rpmMessage(RPMMESS_DEBUG, "PGP sig size: %d\n", *size);
    *sig = malloc(*size);
    
    fd = open(sigfile, O_RDONLY);
    if (timedRead(fd, *sig, *size) != *size) {
	unlink(sigfile);
	close(fd);
	free(*sig);
	rpmError(RPMERR_SIGGEN, _("unable to read the signature"));
	return 1;
    }
    close(fd);
    unlink(sigfile);

    rpmMessage(RPMMESS_DEBUG, "Got %d bytes of PGP sig\n", *size);
    
    return 0;
}

static int checkSize(int fd, int size, int sigsize)
{
    int headerArchiveSize;
    struct stat statbuf;

    fstat(fd, &statbuf);

    if (S_ISREG(statbuf.st_mode)) {
	headerArchiveSize = statbuf.st_size - sizeof(struct rpmlead) - sigsize;

	rpmMessage(RPMMESS_DEBUG, "sigsize         : %d\n", sigsize);
	rpmMessage(RPMMESS_DEBUG, "Header + Archive: %d\n", headerArchiveSize);
	rpmMessage(RPMMESS_DEBUG, "expected size   : %d\n", size);

	return size - headerArchiveSize;
    } else {
	rpmMessage(RPMMESS_DEBUG, "file is not regular -- skipping size check\n");
	return 0;
    }
}

int rpmVerifySignature(char *file, int_32 sigTag, void *sig, int count,
		    char *result)
{
    switch (sigTag) {
      case RPMSIGTAG_SIZE:
	if (verifySizeSignature(file, *(int_32 *)sig, result)) {
	    return RPMSIG_BAD;
	}
	break;
      case RPMSIGTAG_MD5:
	if (verifyMD5Signature(file, sig, result, mdbinfile)) {
	    return 1;
	}
	break;
      case RPMSIGTAG_LEMD5_1:
      case RPMSIGTAG_LEMD5_2:
	if (verifyMD5Signature(file, sig, result, mdbinfileBroken)) {
	    return 1;
	}
	break;
      case RPMSIGTAG_PGP:
	return verifyPGPSignature(file, sig, count, result);
	break;
      default:
	sprintf(result, "Do not know how to verify sig type %d\n", sigTag);
	return RPMSIG_UNKNOWN;
    }

    return RPMSIG_OK;
}

static int verifySizeSignature(char *datafile, int_32 size, char *result)
{
    struct stat statbuf;

    stat(datafile, &statbuf);
    if (size != statbuf.st_size) {
	sprintf(result, "Header+Archive size mismatch.\n"
		"Expected %d, saw %d.\n",
		size, (int)statbuf.st_size);
	return 1;
    }

    sprintf(result, "Header+Archive size OK: %d bytes\n", size);
    return 0;
}

static int verifyMD5Signature(char *datafile, unsigned char *sig, 
			      char *result, md5func fn)
{
    unsigned char md5sum[16];

    fn(datafile, md5sum);
    if (memcmp(md5sum, sig, 16)) {
	sprintf(result, "MD5 sum mismatch\n"
		"Expected: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
		"%02x%02x%02x%02x%02x\n"
		"Saw     : %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
		"%02x%02x%02x%02x%02x\n",
		sig[0],  sig[1],  sig[2],  sig[3],
		sig[4],  sig[5],  sig[6],  sig[7],
		sig[8],  sig[9],  sig[10], sig[11],
		sig[12], sig[13], sig[14], sig[15],
		md5sum[0],  md5sum[1],  md5sum[2],  md5sum[3],
		md5sum[4],  md5sum[5],  md5sum[6],  md5sum[7],
		md5sum[8],  md5sum[9],  md5sum[10], md5sum[11],
		md5sum[12], md5sum[13], md5sum[14], md5sum[15]);
	return 1;
    }

    sprintf(result, "MD5 sum OK: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
                    "%02x%02x%02x%02x%02x\n",
	    md5sum[0],  md5sum[1],  md5sum[2],  md5sum[3],
	    md5sum[4],  md5sum[5],  md5sum[6],  md5sum[7],
	    md5sum[8],  md5sum[9],  md5sum[10], md5sum[11],
	    md5sum[12], md5sum[13], md5sum[14], md5sum[15]);

    return 0;
}

static int verifyPGPSignature(char *datafile, void *sig,
			      int count, char *result)
{
    int pid, status, outpipe[2], sfd;
    char *sigfile;
    unsigned char buf[8192];
    FILE *file;
    int res = RPMSIG_OK;

    /* Write out the signature */
    sigfile = tempnam(rpmGetVar(RPMVAR_TMPPATH), "rpmsig");
    sfd = open(sigfile, O_WRONLY|O_CREAT|O_TRUNC, 0644);
    write(sfd, sig, count);
    close(sfd);

    /* Now run PGP */
    pipe(outpipe);

    if (!(pid = fork())) {
	close(1);
	close(outpipe[0]);
	dup2(outpipe[1], 1);
	if (rpmGetVar(RPMVAR_PGP_PATH)) {
	    dosetenv("PGPPATH", rpmGetVar(RPMVAR_PGP_PATH), 1);
	}
	execlp("pgp", "pgp",
	       "+batchmode=on", "+verbose=0",
	       sigfile, datafile,
	       NULL);
	printf("exec failed!\n");
	rpmError(RPMERR_EXEC, 
		 _("Could not run pgp.  Use --nopgp to skip PGP checks."));
	_exit(RPMERR_EXEC);
    }

    close(outpipe[1]);
    file = fdopen(outpipe[0], "r");
    result[0] = '\0';
    while (fgets(buf, 1024, file)) {
	if (strncmp("File '", buf, 6) &&
	    strncmp("Text is assu", buf, 12) &&
	    buf[0] != '\n') {
	    strcat(result, buf);
	}
	if (!strncmp("WARNING: Can't find the right public key", buf, 40)) {
	    res = RPMSIG_NOKEY;
	}
    }
    fclose(file);

    waitpid(pid, &status, 0);
    unlink(sigfile);
    if (!res && (!WIFEXITED(status) || WEXITSTATUS(status))) {
	res = RPMSIG_BAD;
    }
    
    return res;
}

char *rpmGetPassPhrase(char *prompt)
{
    char *pass;

    if (! rpmGetVar(RPMVAR_PGP_NAME)) {
	rpmError(RPMERR_SIGGEN,
	         _("You must set \"pgp_name:\" in your rpmrc file"));
	return NULL;
    }

    if (prompt) {
        pass = getpass(prompt);
    } else {
        pass = getpass("");
    }

    if (checkPassPhrase(pass)) {
	return NULL;
    }

    return pass;
}

static int checkPassPhrase(char *passPhrase)
{
    char name[1024];
    int passPhrasePipe[2];
    FILE *fpipe;
    int pid, status;
    int fd;

    sprintf(name, "+myname=\"%s\"", rpmGetVar(RPMVAR_PGP_NAME));

    pipe(passPhrasePipe);
    if (!(pid = fork())) {
	close(0);
	close(1);
	if (! rpmIsVerbose()) {
	    close(2);
	}
	if ((fd = open("/dev/null", O_RDONLY)) != 0) {
	    dup2(fd, 0);
	}
	if ((fd = open("/dev/null", O_WRONLY)) != 1) {
	    dup2(fd, 1);
	}
	dup2(passPhrasePipe[0], 3);
	dosetenv("PGPPASSFD", "3", 1);
	if (rpmGetVar(RPMVAR_PGP_PATH)) {
	    dosetenv("PGPPATH", rpmGetVar(RPMVAR_PGP_PATH), 1);
	}
	execlp("pgp", "pgp",
	       "+batchmode=on", "+verbose=0",
	       name, "-sf",
	       NULL);
	rpmError(RPMERR_EXEC, _("Couldn't exec pgp"));
	_exit(RPMERR_EXEC);
    }

    fpipe = fdopen(passPhrasePipe[1], "w");
    close(passPhrasePipe[0]);
    fprintf(fpipe, "%s\n", passPhrase);
    fclose(fpipe);

    waitpid(pid, &status, 0);
    if (!WIFEXITED(status) || WEXITSTATUS(status)) {
	return 1;
    }

    /* passPhrase is good */
    return 0;
}
