/*
 * ratPGPprog.c --
 *
 *	This file contains compatibility functions.
 *
 * TkRat software and its included text is Copyright 1996-2002 by
 * Martin Forssén
 *
 * The full text of the legal notice is contained in the file called
 * COPYRIGHT, included with this distribution.
 */

#include "ratFolder.h"
#include "ratPGP.h"

#include <stdio.h>

/*
 * The contents of the first bodypart of nested bodyparts
 */
#define ENCFIRST "Version: 1\r\n"

/*
 * List of keys on keyring
 */
typedef struct {
    Tcl_Obj *keyid;
    unsigned int addrCount;
    Tcl_Obj **address;
    Tcl_Obj *descr;
    Tcl_Obj *key;
} RatPGPKey;  
typedef struct {
    RatPGPKey *keys;
    unsigned int keyCount;
    unsigned int keyAlloc;
    Tcl_Obj *title;
    char *name;
    time_t mtime;
} RatPGPKeyring;
static RatPGPKeyring *keyring = NULL;

/*
 * Local functions
 */
static int RatRunPGP(Tcl_Interp *interp, int nopass, char *cmd, char *args,
		     int *toPGP, char **outFile, int *errPGP);
static Tcl_DString *RatPGPRunOld(Tcl_Interp *interp, BodyInfo *bodyInfoPtr,
				 char *text, char *start, char *end);
static int RatUpdatePGPKeys(Tcl_Interp *interp, RatPGPKeyring *k);
static void AddKey(RatPGPKeyring *k, Tcl_Obj **ids, unsigned int idNum,
	Tcl_DString *descr);
static void RatPGPFreeKeyring(RatPGPKeyring *k);
static RatPGPKeyring* RatPGPNewKeyring(const char *name);

/*
 *----------------------------------------------------------------------
 *
 * RatRunPGP --
 *
 *      Run the pgp command
 *
 * Results:
 *	Returns the pid of the pgp program on success and a negative
 *	value on failure. The toPGP and fromPGP integers will be modified.
 *
 * Side effects:
 *	forks.
 *
 *
 *----------------------------------------------------------------------
 */

static int
RatRunPGP(Tcl_Interp *interp, int nopass, char *command, char *args,
	int *toPGP, char **outFile, int *errPGP)
{
    int toPipe[2], errPipe[2], argc, pid, i, out;
    Tcl_DString cmd;
    char cmdbuf[1024];
    CONST84 char *opt_args, *pgp_path, **argv, *tmp;
    struct rlimit rlim;
    static char name[1024];

    /*
     * Setup command arrays
     */
    pgp_path = RatGetPathOption(interp, "pgp_path");
    opt_args = Tcl_GetVar2(interp, "option", "pgp_args", TCL_GLOBAL_ONLY);
    if (pgp_path && strlen(pgp_path)) {
	snprintf(cmdbuf, sizeof(cmdbuf), "%s/%s", pgp_path, command);
    } else {
	snprintf(cmdbuf, sizeof(cmdbuf), "%s", command);
    }
    Tcl_DStringInit(&cmd);
    Tcl_DStringAppend(&cmd, cmdbuf, -1);
    if (opt_args) {
	Tcl_DStringAppend(&cmd, " ", 1);
	Tcl_DStringAppend(&cmd, opt_args, -1);
    }
    Tcl_DStringAppend(&cmd, " ", 1);
    Tcl_DStringAppend(&cmd, args, -1);
    Tcl_SplitList(interp, Tcl_DStringValue(&cmd), &argc, &argv);
    /*fprintf(stderr, "Exec: %s %s\n", cmdbuf, Tcl_DStringValue(&cmd));*/
    Tcl_DStringFree(&cmd);

    /*
     * Open outfile and create the pgp subprocess.
     */
    tmp = Tcl_GetVar(interp, "rat_tmp", TCL_GLOBAL_ONLY);
    snprintf(name, sizeof(name), "%s/pgptmp.%d", tmp, getpid());
    if ( 0 > (out = open(name, O_WRONLY|O_CREAT|O_TRUNC, 0600))) {
	return 0;
    }
    pipe(toPipe);
    pipe(errPipe);
    if (0 == (pid = fork())) {
	getrlimit(RLIMIT_NOFILE, &rlim);
	for (i=0; i<rlim.rlim_cur; i++) {
	    if (i != toPipe[0] && i != out && i != errPipe[1]) {
		close(i);
	    }
	}
	dup2(toPipe[0], 0);
	dup2(out, 1);
	dup2(errPipe[1], 2);
	fcntl(0, F_SETFD, 0);
	fcntl(1, F_SETFD, 0);
	fcntl(2, F_SETFD, 0);
	if (!nopass) {
	    putenv("PGPPASSFD=0");
	}
	execvp(cmdbuf, (char**)argv);
	{
	    char buf[1024];
	    snprintf(buf, sizeof(buf), "ERROR executing '%s %s': %s\n",
		    cmdbuf, args, strerror(errno));
	    write(STDERR_FILENO, buf, strlen(buf));
	}
	exit(-1);
	/* notreached */
    }
    close(toPipe[0]);
    close(out);
    close(errPipe[1]);
    ckfree(argv);
    *toPGP = toPipe[1];
    *outFile = name;
    *errPGP = errPipe[0];
    return pid;
}

/*
 *----------------------------------------------------------------------
 *
 * RatPGPEncrypt --
 *
 *      Encrypt a bodypart. Optionally also sign it.
 *
 * Results:
 *	A multipart/encrypted body
 *
 * Side effects:
 *	Will call pgp.
 *
 *
 *----------------------------------------------------------------------
 */

BODY*
RatPGPEncrypt(Tcl_Interp *interp, ENVELOPE *env, BODY *body, int sign)
{
    int toPGP, fromPGP, errPGP, length, retry, pid, result, status, i, j;
    char *passPhrase = NULL, *hdrPtr, *from, buf[MAILTMPLEN], *command;
    CONST84 char *version;
    ADDRESS *adrPtr;
    Tcl_DString cmdDS, encDS, ds;
    BODY *multiPtr;
    PARAMETER *parmPtr;
    PART *partPtr;
    char *recipSep;

    Tcl_DStringInit(&cmdDS);
    Tcl_DStringInit(&encDS);
    Tcl_DStringInit(&ds);

    rfc822_encode_body_8bit(env, body);
    /*
     * Create command to run
     */
    version = Tcl_GetVar2(interp, "option", "pgp_version", TCL_GLOBAL_ONLY);
    if (!strcmp("gpg-1", version)) {
	command = "gpg";
	Tcl_DStringAppend(&cmdDS,
		"-eatq --no-secmem-warning --passphrase-fd 0 --batch", -1);
	if (sign) {
	    Tcl_DStringAppend(&cmdDS," -s",-1);
	}
	recipSep = " -r ";
    } else if (!strcmp("2", version)) {
	command = "pgp";
	Tcl_DStringAppend(&cmdDS, "+BATCHMODE +VERBOSE=0 -eaf", -1);
	if (sign) {
	    Tcl_DStringAppend(&cmdDS, "s", 1);
	}
	recipSep=" ";
    } else if (!strcmp("5", version)) {
	command = "pgpe";
	if (sign) {
	    Tcl_DStringAppend(&cmdDS, "-s ", -1);
	}
	Tcl_DStringAppend(&cmdDS, "-at -f +batchmode=1 -r", -1);
	recipSep=" ";
    } else if (!strcmp("6", version)) {
	command = "pgp";
	Tcl_DStringAppend(&cmdDS, "+BATCHMODE +VERBOSE=0 +force -eaf", -1);
	if (sign) {
	    Tcl_DStringAppend(&cmdDS, "s", 1);
	    if (RatAddressSize(env->from, 0) < sizeof(buf)) {
		buf[0] = '\0';
		rfc822_write_address_full(buf, env->from, NULL);
		Tcl_DStringAppend(&cmdDS, " -u {", 5);
		Tcl_DStringAppend(&cmdDS, buf, -1);
		Tcl_DStringAppend(&cmdDS, "}", 1);
	    }
	}
	recipSep=" ";
    } else {
	Tcl_SetResult(interp, "Unkown pgp version", TCL_STATIC);
	return NULL;
    }
    /* TODO, specify addresses elsewhere */
    for (adrPtr = env->to; adrPtr; adrPtr = adrPtr->next) {
	Tcl_DStringSetLength(&ds, RatAddressSize(adrPtr, 0));
	Tcl_DStringValue(&ds)[0] = '\0';
	rfc822_address(Tcl_DStringValue(&ds), adrPtr);
	Tcl_DStringSetLength(&ds, strlen(Tcl_DStringValue(&ds)));
	Tcl_DStringAppend(&cmdDS, recipSep, -1);
	Tcl_DStringAppend(&cmdDS, Tcl_DStringValue(&ds), -1);
    }
    for (adrPtr = env->cc; adrPtr; adrPtr = adrPtr->next) {
	Tcl_DStringSetLength(&ds, RatAddressSize(adrPtr, 0));
	Tcl_DStringValue(&ds)[0] = '\0';
	rfc822_write_address_full(Tcl_DStringValue(&ds), adrPtr, NULL);
	Tcl_DStringSetLength(&ds, strlen(Tcl_DStringValue(&ds)));
	Tcl_DStringAppend(&cmdDS, recipSep, -1);
	Tcl_DStringAppend(&cmdDS, Tcl_DStringValue(&ds), -1);
    }
    for (adrPtr = env->bcc; adrPtr; adrPtr = adrPtr->next) {
	Tcl_DStringSetLength(&ds, RatAddressSize(adrPtr, 0));
	Tcl_DStringValue(&ds)[0] = '\0';
	rfc822_write_address_full(Tcl_DStringValue(&ds), adrPtr, NULL);
	Tcl_DStringSetLength(&ds, strlen(Tcl_DStringValue(&ds)));
	Tcl_DStringAppend(&cmdDS, recipSep, -1);
	Tcl_DStringAppend(&cmdDS, Tcl_DStringValue(&ds), -1);
    }

    /*
     * Run command
     */
    do {
	if (sign) {
	    passPhrase = RatSenderPGPPhrase(interp);
	    if (NULL == passPhrase) {
		Tcl_DStringFree(&encDS);
		Tcl_DStringFree(&cmdDS);
		return NULL;
	    }
	}
	pid = RatRunPGP(interp, 0, command, Tcl_DStringValue(&cmdDS), &toPGP,
			&from, &errPGP);
	if (sign) {
	    write(toPGP, passPhrase, strlen(passPhrase));
	    memset(passPhrase, '\0', strlen(passPhrase));
	}
        if (strcmp("6", version) || sign) {
	    write(toPGP, "\n", 1);
	}
	hdrPtr = buf;
	buf[0] = '\0';
	rfc822_write_body_header(&hdrPtr, body);
	strlcat(buf, "\015\012", sizeof(buf));
	write(toPGP, buf, strlen(buf));
	RatInitDelayBuffer();
	rfc822_output_body(body, RatDelaySoutr, (void*)toPGP);
	close(toPGP);
	do {
	    result = waitpid(pid, &status, 0);
	} while(-1 == result && EINTR == errno);

	/*
	 * Read result
	 */
	fromPGP = open(from, O_RDONLY);
	Tcl_DStringSetLength(&encDS, 0);
	do {
	    length = read(fromPGP, buf, sizeof(buf));
	    for (i=0; i < length; i += j) {
		for (j=0; buf[i+j] != '\n' && i+j<length; j++);
		Tcl_DStringAppend(&encDS, buf+i, j);
		if ('\n' == buf[i+j]) {
		    Tcl_DStringAppend(&encDS, "\r\n", 2);
		    j++;
		}
	    }
	} while (length > 0);
	close(fromPGP);
	unlink(from);

	/*
	 * Check for errors
	 */
	if (pid != result || WEXITSTATUS(status)) {
	    Tcl_DString error;
	    char *reply;

	    Tcl_DStringInit(&error);
	    Tcl_DStringAppendElement(&error, "PGP");
	    Tcl_DStringAppendElement(&error, "error");
	    Tcl_DStringStartSublist(&error);
	    do {
		if (0 < (length = length = read(errPGP, buf, sizeof(buf)))) {
		    Tcl_DStringAppend(&error, buf, length);
		}
	    } while (length > 0);
	    Tcl_DStringEndSublist(&error);

	    reply = RatSendPGPCommand(Tcl_DStringValue(&error));
	    Tcl_DStringFree(&error);
	    if (!strncmp("ABORT", reply, 5)) {
		close(errPGP);
		Tcl_DStringFree(&encDS);
		Tcl_DStringFree(&cmdDS);
		return NULL;
	    }
	    retry = 1;
	} else {
	    retry = 0;
	}
	close(errPGP);
    } while(0 != retry);
    Tcl_DStringFree(&cmdDS);
    Tcl_DStringFree(&ds);
    mail_free_body(&body);

    /*
     * Build encrypted multipart
     */
    multiPtr = mail_newbody();
    multiPtr->type = TYPEMULTIPART;
    multiPtr->subtype = cpystr("encrypted");
    multiPtr->parameter = parmPtr = mail_newbody_parameter();
    parmPtr->attribute = cpystr("protocol");
    parmPtr->value = cpystr("application/pgp-encrypted");
    parmPtr->next = NULL;
    multiPtr->encoding = ENC7BIT;
    multiPtr->id = NULL;
    multiPtr->description = NULL;
    multiPtr->nested.part = partPtr = mail_newbody_part();
    partPtr->body.type = TYPEAPPLICATION;
    partPtr->body.subtype = cpystr("pgp-encrypted");
    partPtr->body.encoding = ENC7BIT;
    partPtr->body.contents.text.data = (unsigned char*)cpystr(ENCFIRST);
    partPtr->body.size.bytes = strlen(ENCFIRST);
    partPtr->next = mail_newbody_part();
    partPtr = partPtr->next;
    partPtr->body.type = TYPEAPPLICATION;
    partPtr->body.subtype = cpystr("octet-stream");
    partPtr->body.encoding = ENC7BIT;
    partPtr->body.contents.text.data =
	    (unsigned char*)cpystr(Tcl_DStringValue(&encDS));
    partPtr->body.size.bytes = Tcl_DStringLength(&encDS);
    Tcl_DStringFree(&encDS);
    partPtr->next = NULL;

    return multiPtr;
}


/*
 *----------------------------------------------------------------------
 *
 * RatPGPSign --
 *
 *      Sign a bodypart.
 *
 * Results:
 *	A multipart/signed body
 *
 * Side effects:
 *	Will call pgp.
 *
 *
 *----------------------------------------------------------------------
 */

BODY*
RatPGPSign(Tcl_Interp *interp, ENVELOPE *env, BODY *body, const char *keyid)
{
    int toPGP, fromPGP, errPGP, length, retry, pid, status, result, i, j;
    char *passPhrase, *hdrPtr, *from, *cmd, buf[MAILTMPLEN];
    CONST84 char *version;
    Tcl_DString sigDS;
    BODY *multiPtr;
    PARAMETER *parmPtr;
    PART *partPtr;
	
    version = Tcl_GetVar2(interp, "option", "pgp_version", TCL_GLOBAL_ONLY);

    Tcl_DStringInit(&sigDS);
    do {
	/*
	 * Run command
	 */
        passPhrase = RatSenderPGPPhrase(interp);
	if (NULL == passPhrase) {
	    return NULL;
	}
	rfc822_encode_body_7bit(NIL, body);
	if (!strcmp("gpg-1", version)) {
	    cmd = "gpg";
	    strlcpy(buf, "--detach-sign --armor --no-secmem-warning "
		    "--passphrase-fd 0 --batch", sizeof(buf));
	} else if (!strcmp("2", version)) {
	    cmd = "pgp";
	    strlcpy(buf, "+BATCHMODE +VERBOSE=0 -satbf", sizeof(buf));
	} else if (!strcmp("5", version)) {
	    cmd = "pgps";
	    strlcpy(buf, "-abf", sizeof(buf));
	} else if (!strcmp("6", version)) {
	    cmd = "pgp";
	    strlcpy(buf, "+BATCHMODE +VERBOSE=0 +force -satbf", sizeof(buf));
	} else {
	    Tcl_SetResult(interp, "Unkown pgp version", TCL_STATIC);
	    return NULL;
	}
	if (keyid && *keyid) {
	    strlcat(buf, " -u {", sizeof(buf));
	    strlcat(buf, keyid, sizeof(buf));
	    strlcat(buf, "}", sizeof(buf));
	}
	pid = RatRunPGP(interp, 0, cmd, buf, &toPGP, &from, &errPGP);
	
	write(toPGP, passPhrase, strlen(passPhrase));
	memset(passPhrase, '\0', strlen(passPhrase));
	write(toPGP, "\n", 1);
	hdrPtr = buf;
	buf[0] = '\0';
	rfc822_write_body_header(&hdrPtr, body);
	strlcat(buf, "\015\012", sizeof(buf));
	write(toPGP, buf, strlen(buf));
	RatInitDelayBuffer();
	rfc822_output_body(body, RatDelaySoutr, (void*)toPGP);
	close(toPGP);
	/*i = open("/tmp/sigdump", O_CREAT | O_TRUNC | O_WRONLY, 0644);
	  write(i, buf, strlen(buf));
	  RatInitDelayBuffer();
	  rfc822_output_body(body, RatDelaySoutr, (void*)i);
	  close(i);*/
	do {
	    result = waitpid(pid, &status, 0);
	} while(-1 == result && EINTR == errno);

	/*
	 * Read result
	 */
	fromPGP = open(from, O_RDONLY);
	Tcl_DStringSetLength(&sigDS, 0);
	do {
	    length = read(fromPGP, buf, sizeof(buf));
	    for (i=0; i < length; i += j) {
		for (j=0; buf[i+j] != '\n' && i+j<length; j++);
		Tcl_DStringAppend(&sigDS, buf+i, j);
		if ('\n' == buf[i+j]) {
		    Tcl_DStringAppend(&sigDS, "\r\n", 2);
		    j++;
		}
	    }
	} while (length > 0);
	close(fromPGP);
	unlink(from);

	/*
	 * Check for errors
	 */
	if (pid != result || WEXITSTATUS(status)) {
	    Tcl_DString error;
	    char *reply;

	    Tcl_DStringInit(&error);
	    Tcl_DStringAppendElement(&error, "PGP");
	    Tcl_DStringAppendElement(&error, "error");
	    Tcl_DStringStartSublist(&error);
	    do {
		if (0 < (length = read(errPGP, buf, sizeof(buf)))) {
		    Tcl_DStringAppend(&error, buf, length);
		}
	    } while (length > 0);
	    Tcl_DStringEndSublist(&error);

	    reply = RatSendPGPCommand(Tcl_DStringValue(&error));
	    Tcl_DStringFree(&error);
	    if (!strncmp("ABORT", reply, 5)) {
		close(errPGP);
		Tcl_DStringFree(&sigDS);
		return NULL;
	    }
	    retry = 1;
	} else {
	    retry = 0;
	}
	close(errPGP);
    } while(0 != retry);

    /*
     * Build signature multipart
     */
    multiPtr = mail_newbody();
    multiPtr->type = TYPEMULTIPART;
    multiPtr->subtype = cpystr("signed");
    multiPtr->parameter = parmPtr = mail_newbody_parameter();
    parmPtr->attribute = cpystr("micalg");
    if (!strcmp("gpg-1", version))
      parmPtr->value = cpystr("pgp-sha1");
    else
      parmPtr->value = cpystr("pgp-md5");
    parmPtr->next = mail_newbody_parameter();
    parmPtr = parmPtr->next;
    parmPtr->attribute = cpystr("protocol");
    parmPtr->value = cpystr("application/pgp-signature");
    parmPtr->next = NULL;
    multiPtr->encoding = ENC7BIT;
    multiPtr->id = NULL;
    multiPtr->description = NULL;
    multiPtr->nested.part = partPtr = mail_newbody_part();
    partPtr->body = *body;
    partPtr->next = mail_newbody_part();
    partPtr = partPtr->next;
    partPtr->body.type = TYPEAPPLICATION;
    partPtr->body.subtype = cpystr("pgp-signature");
    partPtr->body.encoding = ENC7BIT;
    partPtr->body.contents.text.data = 
	    (unsigned char*)cpystr(Tcl_DStringValue(&sigDS));
    partPtr->body.size.bytes = Tcl_DStringLength(&sigDS);
    Tcl_DStringFree(&sigDS);
    partPtr->next = NULL;

    return multiPtr;
}


/*
 *----------------------------------------------------------------------
 *
 * FindBoundary --
 *
 *      Find the boundary in a message string.
 *
 * Results:
 *	Pointer to the start of the boundary.
 *
 * Side effects:
 *	None
 *
 *
 *----------------------------------------------------------------------
 */

static char*
FindBoundary(char *text, char*boundary)
{
    char *cPtr = text;
    int l = strlen(boundary);

    if (NULL == text) return NULL;
    do {
	if ('-' == cPtr[0] && '-' == cPtr[1] && !strncmp(cPtr+2, boundary, l)){
	    return cPtr;
	}
	cPtr = strchr(cPtr, '\n');
    } while (cPtr++);
    return NULL;
}


/*
 *----------------------------------------------------------------------
 *
 * RatPGPChecksig --
 *
 *      Check the signature of a bodypart
 *
 * Results:
 *	None
 *
 * Side effects:
 *	Updates the bodyInfoPtr->sigStatus
 *
 *
 *----------------------------------------------------------------------
 */

void
RatPGPChecksig(Tcl_Interp *interp, MessageProcInfo* procInfo,
	BodyInfo *bodyInfoPtr)
{
    unsigned char *text;
    unsigned long length;
    char *command;
    CONST84 char *version;
	
    version = Tcl_GetVar2(interp, "option", "pgp_version", TCL_GLOBAL_ONLY);
	
    /*
     * Check if PGP/MIME message or old style PGP.
     * The algorithms kind of differ:-)
     */
    if (bodyInfoPtr->secPtr) {
	char *boundary, *start, *end, *from;
	char buf[2048], textfile[1024], sigfile[1024];
	int fd, toPGP, fromPGP, errPGP, pid, status, result;
	CONST84 char *tmp;
	PARAMETER *parPtr;
	Tcl_DString *resultDS = (Tcl_DString*)ckalloc(sizeof(Tcl_DString));

	/*
	 * Generate filenames
	 */
	tmp = Tcl_GetVar(interp, "rat_tmp", TCL_GLOBAL_ONLY);
	RatGenId(NULL, interp, 0, NULL);
	snprintf(textfile, sizeof(textfile), "%s/rat.%s",
		tmp, Tcl_GetStringResult(interp));
	strlcpy(sigfile, textfile, sizeof(sigfile));
	strlcat(sigfile, ".sig", sizeof(sigfile));

	/*
	 * Save text and signature in files
	 */
	boundary = NULL;
	text = (unsigned char*)(*procInfo[bodyInfoPtr->type].fetchBodyProc)
		(bodyInfoPtr->secPtr, &length);
	for (parPtr = bodyInfoPtr->secPtr->bodyPtr->parameter; parPtr;
		parPtr = parPtr->next) {
	    if (!strcasecmp(parPtr->attribute, "boundary")) {
		boundary = parPtr->value;
		break;
	    }
	}
	if (!boundary || NULL == (start = FindBoundary((char*)text,boundary))){
	    bodyInfoPtr->sigStatus = RAT_SIG_BAD;
	    return;
	}
	start += strlen(boundary) + 4;
	if (NULL == (end = FindBoundary(start, boundary))) {
	    bodyInfoPtr->sigStatus = RAT_SIG_BAD;
	    return;
	}
	end -= 2;
	fd = open(textfile, O_CREAT | O_TRUNC | O_WRONLY, 0600);
	write(fd, start, end-start);
	close(fd);
	text = (unsigned char*)(*procInfo[bodyInfoPtr->type].fetchBodyProc)
		(bodyInfoPtr->secPtr->firstbornPtr->nextPtr, &length);
	fd = open(sigfile, O_CREAT | O_TRUNC | O_WRONLY, 0600);
	if (text) {
	    write(fd, text, length);
	}
	close(fd);

	/*
	 * Run PGP command
	 */
	if (!strcmp("gpg-1", version)) {
	    command = "gpg";
	    snprintf(buf, sizeof(buf),
		    "--verify --no-secmem-warning --batch %s %s",
		    sigfile, textfile);
	} else if (!strcmp("2", version)) {
	    command = "pgp";
	    snprintf(buf, sizeof(buf), "+batchmode +verbose=0 %s %s",
		    sigfile, textfile);
	} else if (!strcmp("5", version)) {
	    command = "pgpv";
	    snprintf(buf, sizeof(buf), "+batchmode=1 %s -o %s",
		    sigfile, textfile);
	} else if (!strcmp("6", version)) {
	    command = "pgp";
	    snprintf(buf, sizeof(buf), "+batchmode +verbose=0 +force %s %s",
		    sigfile, textfile);
	} else {
	    Tcl_SetResult(interp, "Unkown pgp version", TCL_STATIC);
	    return;
	}
	pid = RatRunPGP(interp, 1, command, buf, &toPGP, &from, &errPGP);
	close(toPGP);
	do {
	    result = waitpid(pid, &status, 0);
	} while(-1 == result && EINTR == errno);
	fromPGP = open(from, O_RDONLY);
	Tcl_DStringInit(resultDS);
	while (0 < (length = read(errPGP, buf, sizeof(buf)))) {
	    Tcl_DStringAppend(resultDS, buf, length);
	}
	while (0 < (length = read(fromPGP, buf, sizeof(buf)))) {
	    Tcl_DStringAppend(resultDS, buf, length);
	}
	close(fromPGP);
	unlink(from);
	close(errPGP);
	if (pid != result || WEXITSTATUS(status)) {
	    bodyInfoPtr->sigStatus = RAT_SIG_BAD;
	} else {
	    bodyInfoPtr->sigStatus = RAT_SIG_GOOD;
	}

	/*
	 * pgp-6.5.1i does not produce usable exit codes :-(
	 */
	if (!strcmp("6", version)) {
	    bodyInfoPtr->sigStatus = RAT_UNCHECKED;
	}
	bodyInfoPtr->pgpOutput = resultDS;

	/*
	 * Clean up
	 */
	unlink(textfile);
	unlink(sigfile);
	/*fprintf(stderr, "textfile: %s\n", textfile);*/
	/*fprintf(stderr, " sigfile: %s\n", sigfile);*/
    } else {
	Tcl_DString *bodyDSPtr;
	char *start, *end;

	text = (unsigned char*)(*procInfo[bodyInfoPtr->type].fetchBodyProc)
		(bodyInfoPtr, &length);
	if (text) {
	    start = RatPGPStrFind((char*)text, length, "BEGIN PGP", 1);
	    if (NULL == start) {
		Tcl_ResetResult(interp);
		return;
	    }
	    end = RatPGPStrFind(start,length-(start-(char*)text),"END PGP ",1);
	    bodyDSPtr=RatPGPRunOld(interp,bodyInfoPtr,(char*)text,start,end+1);
	    Tcl_DStringFree(bodyDSPtr);
	    ckfree(bodyDSPtr);
	}
    }
    if (bodyInfoPtr->pgpOutput && 1<Tcl_DStringLength(bodyInfoPtr->pgpOutput)){
	Tcl_SetResult(interp, Tcl_DStringValue(bodyInfoPtr->pgpOutput),
		TCL_VOLATILE);
    } else {
	Tcl_ResetResult(interp);
    }
}


/*
 *----------------------------------------------------------------------
 *
 * RatPGPDecrypt --
 *
 *      Decryt a bodypart.
 *
 * Results:
 *	None
 *
 * Side effects:
 *	The BodyInfo structure will be modified if the decryption is ok.
 *
 *
 *----------------------------------------------------------------------
 */

void
RatPGPDecrypt(Tcl_Interp *interp, MessageProcInfo *procInfo,
	      BodyInfo **bodyInfoPtrPtr)
{
    int toPGP, fromPGP, errPGP, result, pid, retry, status;
    char *text, *passPhrase, buf[1024], *from;
    CONST84 char *version;
    BodyInfo *origPtr = *bodyInfoPtrPtr, *partInfoPtr;
    MessageInfo *msgPtr;
    unsigned long length;
    Tcl_DString bodyDS, *errDSPtr = (Tcl_DString*)ckalloc(sizeof(Tcl_DString));

    RatLog(interp, RAT_PARSE, "decrypting", RATLOG_EXPLICIT);
    version = Tcl_GetVar2(interp, "option", "pgp_version", TCL_GLOBAL_ONLY);

    /*
     * Decode the bodypart
     */
    Tcl_DStringInit(&bodyDS);
    (*procInfo[(*bodyInfoPtrPtr)->type].makeChildrenProc)
	    (interp, *bodyInfoPtrPtr);
    text = (*procInfo[(*bodyInfoPtrPtr)->type].fetchBodyProc)
	    ((*bodyInfoPtrPtr)->firstbornPtr->nextPtr, &length);
    retry = 1;
    while (retry && text) {
	passPhrase = RatPGPPhrase(interp);
	if (NULL == passPhrase) {
	    goto failed;
	}
	if (!strcmp("gpg-1", version)) {
	    pid = RatRunPGP(interp, 0, "gpg",
 			    "--decrypt -atq --no-secmem-warning "
			    "--passphrase-fd 0 --batch",
			    &toPGP, &from, &errPGP);
	} else if (!strcmp("2", version)) {
	    pid = RatRunPGP(interp, 0, "pgp", "+BATCHMODE +VERBOSE=0 -f",
		    &toPGP, &from, &errPGP);
	} else if (!strcmp("5", version)) {
	    pid = RatRunPGP(interp, 0, "pgpv", "+batchmode=1 -f",
		    &toPGP, &from, &errPGP);
	} else if (!strcmp("6", version)) {
	    pid = RatRunPGP(interp, 0,
			    "pgp", "+BATCHMODE +VERBOSE=0 +force -f",
			    &toPGP, &from, &errPGP);
	} else {
	    Tcl_SetResult(interp, "Unkown pgp version", TCL_STATIC);
	    break;
	}
	write(toPGP, passPhrase, strlen(passPhrase));
	memset(passPhrase, '\0', strlen(passPhrase));
	ckfree(passPhrase);
	write(toPGP, "\n", 1);
	write(toPGP, text, length);
	/*fprintf(stderr, "%s:%d Dumped data to '/tmp/msgdump'\n", __FILE__,
		__LINE__);
	i = open("/tmp/msgdump", O_CREAT|O_TRUNC|O_WRONLY, 0600);
	write(i, text, length);
	close(i);*/
	close(toPGP);
	do {
	    result = waitpid(pid, &status, 0);
	} while(-1 == result && EINTR == errno);

	/*
	 * Read result
	 */
	fromPGP = open(from, O_RDONLY);
	Tcl_DStringSetLength(&bodyDS, 0);
	Tcl_DStringAppend(&bodyDS, "MIME-Version: 1.0\r\n", -1);
	while (0 < (length = read(fromPGP, buf, sizeof(buf)))) {
	    Tcl_DStringAppend(&bodyDS, buf, length);
	}
	close(fromPGP);
	unlink(from);
	Tcl_DStringInit(errDSPtr);
	while (0 < (length = read(errPGP, buf, sizeof(buf)))) {
	    Tcl_DStringAppend(errDSPtr, buf, length);
	}
	close(errPGP);

	/*
	 * Check for errors?
	 */
	if (pid != result
		|| (WEXITSTATUS(status) != 0 && WEXITSTATUS(status) != 1)) {
	    Tcl_DString error;

	    ClearPGPPass(NULL);
	    Tcl_DStringInit(&error);
	    Tcl_DStringAppend(&error, "RatPGPError", -1);
	    Tcl_DStringAppendElement(&error, Tcl_DStringValue(errDSPtr));
	    if (TCL_OK != Tcl_Eval(interp, Tcl_DStringValue(&error))
		    || !strcmp("ABORT", Tcl_GetStringResult(interp))) {
		close(errPGP);
		Tcl_DStringFree(&error);
		Tcl_DStringFree(&bodyDS);
		Tcl_DStringFree(errDSPtr);
		ckfree(errDSPtr);
		RatLog(interp, RAT_PARSE, "", RATLOG_EXPLICIT);
		goto failed;
	    } else {
		retry = 1;
	    }
	} else {
	    retry = 0;
	}
    }

    /*
     * Now parse the bodypart
     */
    Tcl_DeleteCommand(interp, (*bodyInfoPtrPtr)->cmdName);
    (*bodyInfoPtrPtr)->containedEntity = RatFrMessageCreate(interp,
	    Tcl_DStringValue(&bodyDS), Tcl_DStringLength(&bodyDS),
	    &msgPtr);
    Tcl_DStringFree(&bodyDS);
    *bodyInfoPtrPtr = Fr_CreateBodyProc(interp, msgPtr);
    msgPtr->bodyInfoPtr = NULL;
    if (WEXITSTATUS(status)) {
	(*bodyInfoPtrPtr)->sigStatus = RAT_UNSIGNED;
    } else {
	(*bodyInfoPtrPtr)->sigStatus = RAT_SIG_GOOD;
    }
    (*bodyInfoPtrPtr)->pgpOutput = errDSPtr;
    (*bodyInfoPtrPtr)->altPtr = origPtr;
    RatLog(interp, RAT_PARSE, "", RATLOG_EXPLICIT);

failed:
    /*
     * Create ordinary parts for body
     */
    for (partInfoPtr = (*bodyInfoPtrPtr)->firstbornPtr; partInfoPtr;
	    partInfoPtr = partInfoPtr->nextPtr) {
	Tcl_CreateObjCommand(interp, partInfoPtr->cmdName, RatBodyCmd,
		(ClientData) partInfoPtr, NULL);
    }
    RatLog(interp, RAT_PARSE, "", RATLOG_EXPLICIT);
}

/*
 *----------------------------------------------------------------------
 *
 * RatPGPListKeys --
 *
 *      Lists the keys on a keyring
 *
 * Results:
 *	See ../doc/interface.
 *
 * Side effects:
 *	Runs the pgp command
 *
 *
 *----------------------------------------------------------------------
 */

int
RatPGPListKeys(Tcl_Interp *interp, char *keyringName)
{
    struct stat sbuf;
    Tcl_DString ck;
    RatPGPKeyring *k = NULL;
    Tcl_Obj **list, **l = NULL, *l3[3];
    int i, j, size;
    CONST84 char *value;

    if (keyringName) {
	switch (keyringName[0]) {
	case '/':
	    Tcl_DStringInit(&ck);
	    Tcl_DStringAppend(&ck, keyringName, -1);
	    break;
	case '~':
	    Tcl_TranslateFileName(interp, keyringName, &ck);
	    break;
	default:
	    Tcl_DStringInit(&ck);
	    Tcl_DStringAppend(&ck,
		    Tcl_GetVar2(interp, "env", "HOME", TCL_GLOBAL_ONLY), -1);
	    Tcl_DStringAppend(&ck, "/.pgp/", -1);
	    Tcl_DStringAppend(&ck, keyringName, -1);
	    break;
	}
    } else {
	if (NULL == (value = RatGetPathOption(interp, "pgp_keyring"))) {
	    return TCL_ERROR;
	}
	Tcl_DStringInit(&ck);
	Tcl_DStringAppend(&ck, value, -1);
    }

    /*
     * Check that we really need to do this
     */
    if ((keyring && !strcmp(keyring->name, Tcl_DStringValue(&ck)))) {
	k = keyring;
	if (0 != stat(k->name, &sbuf) || sbuf.st_mtime != k->mtime) {
	    RatPGPFreeKeyring(keyring);
	    k = NULL;
	    keyring = k = RatPGPNewKeyring(Tcl_DStringValue(&ck));
	    if (TCL_OK != RatUpdatePGPKeys(interp, k)) {
		return TCL_ERROR;
	    }
	}
    }
    if (!k) {
	k = RatPGPNewKeyring(Tcl_DStringValue(&ck));
	if (TCL_OK != RatUpdatePGPKeys(interp, k)) {
	    return TCL_ERROR;
	}
    }
    if (!keyringName) {
	keyring = k;
    }
    Tcl_DStringFree(&ck);

    if (0 < k->keyCount) {
	list = (Tcl_Obj**)ckalloc(sizeof(Tcl_Obj*)*k->keyCount);
	size = 0;
	for (i=0; i<k->keyCount; i++) {
	    if (k->keys[i].addrCount > size) {
		size = k->keys[i].addrCount + 8;
		l = (Tcl_Obj**)ckrealloc(l, sizeof(Tcl_Obj*)*size);
	    }
	    for (j=0; j < k->keys[i].addrCount; j++) {
		l[j] = k->keys[i].address[j];
	    }
	    l3[0] = k->keys[i].keyid;
	    l3[1] = Tcl_NewListObj(k->keys[i].addrCount, l);
	    l3[2] = k->keys[i].descr;
	    list[i] = Tcl_NewListObj(3, l3);
	}
	l3[0] = k->title;
	l3[1] = Tcl_NewListObj(k->keyCount, list);
	Tcl_SetObjResult(interp, Tcl_NewListObj(2, l3));
	ckfree(list);
	ckfree(l);
    } else {
	Tcl_ResetResult(interp);
    }

    if (keyring != k) {
	RatPGPFreeKeyring(k);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * RatPGPExtractKey --
 *
 *      Extracts a key from a keyring
 *
 * Results:
 *	See ../doc/interface.
 *
 * Side effects:
 *	Runs the pgp command
 *
 *
 *----------------------------------------------------------------------
 */

int
RatPGPExtractKey(Tcl_Interp *interp, char *id, char *keyringName)
{
    int toPGP, fromPGP, errPGP, pid, status, length, ret;
    Tcl_DString cmd, ck;
    char buf[1024], *cPtr, *command, *from;
    CONST84 char *value, *version;
    Tcl_Obj *rPtr;

    if (keyringName) {
	switch (keyringName[0]) {
	case '/':
	    Tcl_DStringInit(&ck);
	    Tcl_DStringAppend(&ck, keyringName, -1);
	    break;
	case '~':
	    Tcl_TranslateFileName(interp, keyringName, &ck);
	    break;
	default:
	    Tcl_DStringInit(&ck);
	    Tcl_DStringAppend(&ck,
		    Tcl_GetVar2(interp, "env", "HOME", TCL_GLOBAL_ONLY), -1);
	    Tcl_DStringAppend(&ck, "/.pgp/", -1);
	    Tcl_DStringAppend(&ck, keyringName, -1);
	    break;
	}
    } else {
	if (NULL == (value = RatGetPathOption(interp, "pgp_keyring"))) {
	    return TCL_ERROR;
	}
	Tcl_DStringInit(&ck);
	Tcl_DStringAppend(&ck, value, -1);
    }

    Tcl_DStringInit(&cmd);
    rPtr = Tcl_NewObj();
    version = Tcl_GetVar2(interp, "option", "pgp_version", TCL_GLOBAL_ONLY);
    if (!strcmp("gpg-1", version)) {
	command = "gpg";
	Tcl_DStringAppend(&cmd, "--no-secmem-warning --export -aqt --keyring ",
		-1);
    } else if (!strcmp("2", version)) {
	command = "pgp";
	Tcl_DStringAppend(&cmd, "-kxaf +BATCHMODE +VERBOSE=0 +PubRing=", -1);
    } else if (!strcmp("5", version)) {
	command = "pgpk";
	Tcl_DStringAppend(&cmd, "+batchmode=1 -x +PubRing=", -1);
    } else if (!strcmp("6", version)) {
	command = "pgp";
	Tcl_DStringAppend(&cmd,
			  "-kxaf +BATCHMODE +VERBOSE=0 +force +PubRing=", -1);
    } else {
	Tcl_SetResult(interp, "Unkown pgp version", TCL_STATIC);
	return TCL_ERROR;
    }
    Tcl_DStringAppend(&cmd, Tcl_DStringValue(&ck), Tcl_DStringLength(&ck));
    Tcl_DStringAppend(&cmd, " \"", 2);
    for (cPtr = id; *cPtr; cPtr++) {
      if ('"' == *cPtr) {
          Tcl_DStringAppend(&cmd, "\\\"", 2);
      } else {
          Tcl_DStringAppend(&cmd, cPtr, 1);
      }
    }
    Tcl_DStringAppend(&cmd, "\"", 1);
    pid = RatRunPGP(interp, 1, command, Tcl_DStringValue(&cmd),
	    &toPGP, &from, &errPGP);
    Tcl_DStringFree(&cmd);
    close(toPGP);
    do {
	ret = waitpid(pid, &status, 0);
    } while(-1 == ret && EINTR == errno);

    /*
     * Read output
     */
    fromPGP = open(from, O_RDONLY);
    do {
	if (0 < (length = read(fromPGP, buf, sizeof(buf)))) {
	    Tcl_AppendToObj(rPtr, buf, length);
	}
    } while (length > 0);
    close(fromPGP);
    unlink(from);

    /*
     * Check for errors?
     */
    if (pid != ret
	    || (WEXITSTATUS(status) != 0 && WEXITSTATUS(status) != 1)) {
	Tcl_SetStringObj(rPtr, NULL, 0);
	do {
	    if (0 < (length = read(errPGP, buf, sizeof(buf)))) {
		Tcl_AppendToObj(rPtr, buf, length);
	    }
	} while (length > 0);
	close(errPGP);
	Tcl_SetObjResult(interp, rPtr);
	return TCL_ERROR;
    }
    close(errPGP);
    Tcl_SetObjResult(interp, rPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * RatPGPAddKeys --
 *
 *      Adds keys to a keyring
 *
 * Results:
 *	See ../doc/interface.
 *
 * Side effects:
 *	Runs the pgp command
 *
 *
 *----------------------------------------------------------------------
 */

int
RatPGPAddKeys(Tcl_Interp *interp, char *keys, char *keyring)
{
    Tcl_DString cmd;
    int result;

    /*
     * Setup and execute command
     */
    Tcl_DStringInit(&cmd);
    Tcl_DStringAppendElement(&cmd, "RatPGPAddKeys");
    Tcl_DStringAppendElement(&cmd, keys);
    if (keyring) {
	Tcl_DStringAppendElement(&cmd, keyring);
    }
    result = Tcl_Eval(interp, Tcl_DStringValue(&cmd));
    Tcl_DStringFree(&cmd);
    return result;
}


/*
 *----------------------------------------------------------------------
 *
 * RatPGPRunOld --
 *
 *      Handle an bodypart generated by pgp (which is not PGP/MIME).
 *
 * Results:
 *	None
 *
 * Side effects:
 *	The BodyInfo structure will be modified.
 *
 *
 *----------------------------------------------------------------------
 */

static Tcl_DString*
RatPGPRunOld(Tcl_Interp *interp, BodyInfo *bodyInfoPtr, char *text,
		char *start, char *end)
{
    Tcl_DString *errDSPtr = (Tcl_DString*)ckalloc(sizeof(Tcl_DString)),
	*bodyDSPtr = (Tcl_DString*)ckalloc(sizeof(Tcl_DString)),
	*dsPtr = NULL;
    int needPhrase, toPGP, fromPGP, errPGP, length, preamble, pid, status,
	result, retry;
    char *ePtr, *cPtr, *passPhrase, buf[1024], *from;
    CONST84 char *version;
    FILE *fp;

    version = Tcl_GetVar2(interp, "option", "pgp_version", TCL_GLOBAL_ONLY);

    /*
     * Prepare text part
     */
    Tcl_DStringInit(bodyDSPtr);
    RatDStringApendNoCRLF(bodyDSPtr, text, start-text);
    preamble = start-text;

    /*
     * Setup and run pgp command
     */
    needPhrase = strncmp(start, "-----BEGIN PGP SIGNED", 21);
    do {
	if (needPhrase) {
	    passPhrase = RatPGPPhrase(interp);
	    if (NULL == passPhrase) {
		RatDStringApendNoCRLF(bodyDSPtr, start, end-start);
		ckfree(errDSPtr);
		bodyInfoPtr->pgpOutput = NULL;
		return bodyDSPtr;
	    }
	} else {
	    passPhrase = "";
	}
	if (!strcmp("gpg-1", version)) {
	    pid = RatRunPGP(interp, 0, "gpg", "--decrypt -atq "
		    "--passphrase-fd 0 --no-secmem-warning --batch",
		    &toPGP, &from, &errPGP);
	} else if (!strcmp("2", version)) {
	    pid = RatRunPGP(interp, 0, "pgp", "+BATCHMODE +VERBOSE=0 -f",
		    &toPGP, &from, &errPGP);
	} else if (!strcmp("5", version)) {
	    pid = RatRunPGP(interp, 0, "pgpv", "+batchmode=1 -f",
		    &toPGP, &from, &errPGP);
	} else if (!strcmp("6", version)) {
	    pid = RatRunPGP(interp, 0,
			    "pgp", "+BATCHMODE +VERBOSE=0 +force -f",
			    &toPGP, &from, &errPGP);
	} else {
	    Tcl_SetResult(interp, "Unkown pgp version", TCL_STATIC);
	    return NULL;
	}
	write(toPGP, passPhrase, strlen(passPhrase));
	memset(passPhrase, '\0', strlen(passPhrase));
	ckfree(passPhrase);
	write(toPGP, "\n", 1);
	fp = fdopen(toPGP, "w");
	/*
	 * Undo any encoding ant buggy MTA may have applied. Since we have this
	 * code here it means that the "BEGIN PGP" stuff must be visible in
	 * the raw (undecoded) text, so the only encoding we actually can
	 * handle is quoted-printable. I can live with this restriction and
	 * it is better than the performance penalty to decode all bodyparts.
	 */
	if (ENC7BIT != bodyInfoPtr->bodyPtr->encoding) {
	
	    dsPtr = RatDecode(interp, bodyInfoPtr->bodyPtr->encoding,
				  start, end-start, NULL);
	    start = Tcl_DStringValue(dsPtr);
	    ePtr = start + Tcl_DStringLength(dsPtr);
	} else {
	    if (!(ePtr = strchr(end, '\n'))) {
		ePtr = end + strlen(end);
	    }
	}
	
	/* Convert to local newline conventions */
	for (cPtr = start; cPtr < ePtr; cPtr++) {
	    if ('\r' == cPtr[0] && '\n' == cPtr[1]) cPtr++;
	    fputc(*cPtr, fp);
	}
	fclose(fp);

	if (dsPtr) {
	    Tcl_DStringFree(dsPtr);
	}
	do {
	    result = waitpid(pid, &status, 0);
	} while(-1 == result && EINTR == errno);

	/*
	 * Read result
	 */
	fromPGP = open(from, O_RDONLY);
	while (0 < (length = read(fromPGP, buf, sizeof(buf)))) {
	    Tcl_DStringAppend(bodyDSPtr, buf, length);
	}
	close(fromPGP);
	unlink(from);
	Tcl_DStringInit(errDSPtr);
	while (0 < (length = read(errPGP, buf, sizeof(buf)))) {
	    Tcl_DStringAppend(errDSPtr, buf, length);
	}
	close(errPGP);

	/*
	 * Check for errors?
	 */
	if (pid != result
		|| (WEXITSTATUS(status) != 0 && WEXITSTATUS(status) != 1)) {
	    Tcl_DString error;

	    ClearPGPPass(NULL);
	    Tcl_DStringInit(&error);
	    Tcl_DStringAppend(&error, "RatPGPError", -1);
	    Tcl_DStringAppendElement(&error, Tcl_DStringValue(errDSPtr));
	    if (TCL_OK != Tcl_Eval(interp, Tcl_DStringValue(&error))
		    || !strcmp("ABORT", Tcl_GetStringResult(interp))) {
		close(errPGP);
		Tcl_DStringFree(&error);
		Tcl_DStringFree(errDSPtr);
		ckfree(errDSPtr);
		RatLog(interp, RAT_PARSE, "", RATLOG_EXPLICIT);
		RatDStringApendNoCRLF(bodyDSPtr, start, ePtr-start);
		bodyInfoPtr->pgpOutput = NULL;
		return bodyDSPtr;
	    } else {
		retry = 1;
	    }
	} else {
	    retry = 0;
	}
    } while (0 != retry);
    if (WEXITSTATUS(status)) {
	if (needPhrase) {
	    bodyInfoPtr->sigStatus = RAT_UNSIGNED;
	} else {
	    bodyInfoPtr->sigStatus = RAT_SIG_BAD;
	}
    } else {
	bodyInfoPtr->sigStatus = RAT_UNCHECKED;
    }
    bodyInfoPtr->pgpOutput = errDSPtr;

    return bodyDSPtr;
}


/*
 *----------------------------------------------------------------------
 *
 * RatPGPHandleOld --
 *
 *      Handle an bodypart generated by pgp (which is not PGP/MIME).
 *
 * Results:
 *	None
 *
 * Side effects:
 *	The BodyInfo structure will be modified.
 *
 *
 *----------------------------------------------------------------------
 */

void
RatPGPHandleOld(Tcl_Interp *interp, BodyInfo *bodyInfoPtr, char *text,
		char *start, char *end)
{
    if (strncmp(start, "-----BEGIN PGP SIGNED", 21)) {
	char *cPtr;
	CONST84 char *t;

	bodyInfoPtr->decodedTextPtr = RatPGPRunOld(interp, bodyInfoPtr, text,
						start, end);
	if (!(cPtr = strchr(end, '\n'))) {
	    cPtr = end + strlen(end);
	}
	if (*cPtr) {
	    RatDStringApendNoCRLF(bodyInfoPtr->decodedTextPtr, cPtr, -1);
	}
	if (bodyInfoPtr->pgpOutput
		&& 1 < Tcl_DStringLength(bodyInfoPtr->pgpOutput)) {
	    Tcl_DString cmd;

	    Tcl_DStringInit(&cmd);
	    Tcl_DStringAppendElement(&cmd, "RatText");
	    t = Tcl_GetVar2(interp, "t", "pgp_output", TCL_GLOBAL_ONLY);
	    Tcl_DStringAppendElement(&cmd, t);
	    Tcl_DStringAppendElement(&cmd,
		    Tcl_DStringValue(bodyInfoPtr->pgpOutput));
	    Tcl_Eval(interp, Tcl_DStringValue(&cmd));
	    Tcl_DStringFree(&cmd);
	}

    } else {
	bodyInfoPtr->sigStatus = RAT_UNCHECKED;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * RatUpdatePGPKeys --
 *
 *      Update the internal list of pgp keys (if needed)
 *
 * Results:
 *	None
 *
 * Side effects:
 *	None.
 *
 *
 *----------------------------------------------------------------------
 */
static int
RatUpdatePGPKeys(Tcl_Interp *interp, RatPGPKeyring *k)
{
    int toPGP, errPGP, pid, status, length, ret, preamble, blankiscont;
    char buf[1024], title[1024], idbuf[1024], *command, *from;
    CONST84 char *version, *start, *end, *last;
    Tcl_DString cmd, tmp;
    struct stat sbuf;
    Tcl_RegExp exp_id, exp_addr;
    FILE *fp;
    Tcl_Obj **ids, *rPtr;
    unsigned int idNum, idAlloc;

    stat(k->name, &sbuf);
    k->mtime = sbuf.st_mtime;

    /*
     * Launch PGP command
     */
    Tcl_DStringInit(&cmd);
    version = Tcl_GetVar2(interp, "option", "pgp_version", TCL_GLOBAL_ONLY);
    if (!strcmp("gpg-1", version)) {
        command = "gpg";
	Tcl_DStringAppend(&cmd,
			  "--no-secmem-warning --list-keys --keyring ", -1);
	exp_id = Tcl_RegExpCompile(interp, "[0-9]+./([0-9A-F]{8})");
	exp_addr = Tcl_RegExpCompile(interp, "<[a-zA-Z.+@-]+>");
	blankiscont = 0;

    } else if (!strcmp("2", version)) {
	command = "pgp";
	Tcl_DStringAppend(&cmd, "-kv +BATCHMODE +VERBOSE=0 +PubRing=", -1);
	exp_id = Tcl_RegExpCompile(interp, "[0-9]/([0-9A-F]{8})");
	exp_addr = Tcl_RegExpCompile(interp, "<[a-zA-Z.+@-]+>");
	blankiscont = 1;

    } else if (!strcmp("5", version)) {
	command = "pgpk";
	Tcl_DStringAppend(&cmd, "-l +batchmode=1 +PubRing=", -1);
	exp_id = Tcl_RegExpCompile(interp, ".ub.+0x([0-9A-F]{8})");
	exp_addr = Tcl_RegExpCompile(interp, "<[a-zA-Z.+@-]+>");
	blankiscont = 0;

    } else if (!strcmp("6", version)) {
	command = "pgp";
	Tcl_DStringAppend(&cmd, "-kv +BATCHMODE +VERBOSE=0 +force +PubRing=",
			  -1);
	exp_id = Tcl_RegExpCompile(interp, "0x([0-9A-F]{8})");
	exp_addr = Tcl_RegExpCompile(interp, "<[a-zA-Z.+@-]+>");
	blankiscont = 1;

    } else {
	Tcl_SetResult(interp, "Unkown pgp version", TCL_STATIC);
	return TCL_ERROR;
    }
    Tcl_DStringAppend(&cmd, k->name, -1);
    pid = RatRunPGP(interp, 1, command, Tcl_DStringValue(&cmd),
	    &toPGP, &from, &errPGP);
    Tcl_DStringFree(&cmd);
    close(toPGP);
    do {
	ret = waitpid(pid, &status, 0);
    } while(-1 == ret && EINTR == errno);

    /*
     * Read and parse output
     */
    Tcl_DStringInit(&tmp);
    fp = fopen(from, "r");
    ids = NULL;
    idNum = idAlloc = 0;
    preamble = 1;
    buf[sizeof(buf)-1] = '\0';
    while (fgets(buf, sizeof(buf)-1, fp), !feof(fp)) {
	if (buf[0]) {
	    buf[strlen(buf)-1] = '\0';
	}
	if (blankiscont && !isspace(buf[0]) && idNum) {
	    AddKey(k, ids, idNum, &tmp);
	    Tcl_DStringSetLength(&tmp, 0);
	    idNum = 0;
	}
	if (Tcl_RegExpExec(interp, exp_id, buf, buf)
	    || Tcl_RegExpExec(interp, exp_addr, buf, buf)) {
	    if (preamble) {
		preamble = 0;
		k->title = Tcl_NewStringObj(title, -1);
		Tcl_IncrRefCount(k->title);
	    }
	    if (Tcl_DStringLength(&tmp)) {
		Tcl_DStringAppend(&tmp, "\n", 1);
	    }
	    Tcl_DStringAppend(&tmp, buf, -1);
	    last = buf;
	    do {
		if (idNum+1 >= idAlloc) {
		    idAlloc += 256;
		    ids = (Tcl_Obj**)ckrealloc(ids, sizeof(Tcl_Obj*)*idAlloc);
		}
		if (Tcl_RegExpExec(interp, exp_id, buf, buf)) {
		    Tcl_RegExpRange(exp_id, 1, &start, &end);
		    last = end;
		    strlcpy(idbuf, "0x", sizeof(idbuf));
		    strlcpy(idbuf+2, start, end-start+1);
		    ids[idNum++] = Tcl_NewStringObj(idbuf, 2+end-start);
		}
		if (Tcl_RegExpExec(interp, exp_addr, buf, buf)) {
		    Tcl_RegExpRange(exp_addr, 0, &start, &end);
		    last = end;
		    ids[idNum++] = Tcl_NewStringObj(start, end-start);
		}
	    } while (Tcl_RegExpExec(interp, exp_id, last, buf)
		     || Tcl_RegExpExec(interp, exp_addr, last, buf));
	} else {
	    if (idNum) {
		AddKey(k, ids, idNum, &tmp);
		Tcl_DStringSetLength(&tmp, 0);
		idNum = 0;
	    }
	    if (preamble && buf[0] && buf[0] != '-') {
		strlcpy(title, buf, sizeof(title));
	    }
	}
    }
    if (idNum) {
	AddKey(k, ids, idNum, &tmp);
    }
    if (idAlloc) {
	ckfree(ids);
    }
    Tcl_DStringFree(&tmp);
    fclose(fp);
    unlink(from);

    /*
     * Check for errors?
     */
    if (pid != ret
	    || (WEXITSTATUS(status) != 0 && WEXITSTATUS(status) != 1)) {
	rPtr = Tcl_NewObj();
	do {
	    if ( 0 < (length = read(errPGP, buf, sizeof(buf)))) {
		Tcl_AppendToObj(rPtr, buf, length);
	    }
	} while (length > 0);
	close(errPGP);
	Tcl_SetObjResult(interp, rPtr);
	return TCL_ERROR;
    }
    close(errPGP);
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * AddKey --
 *
 *      Add key(s) to current keyring
 *
 * Results:
 *	None
 *
 * Side effects:
 *	None
 *
 *
 *----------------------------------------------------------------------
 */
static void
AddKey(RatPGPKeyring *k, Tcl_Obj **ids, unsigned int idNum, Tcl_DString *descr)
{
    unsigned int i, j, ai, numAddr;
    RatPGPKey *key;

    if (idNum == 0
	|| !strcmp("0x00000000", Tcl_GetStringFromObj(ids[0], NULL))
	|| strncmp("0x", Tcl_GetStringFromObj(ids[0], NULL), 2)) {
	return;
    }

    for (i = numAddr = 0; i < idNum; i++) {
	if ('<' == *Tcl_GetStringFromObj(ids[i], NULL)) {
	    numAddr++;
	}
    }

    for(i = 0; i<idNum; i++) {
	if ('<' != *Tcl_GetStringFromObj(ids[i], NULL)) {
	    if (k->keyCount == k->keyAlloc) {
		k->keyAlloc += 256;
		k->keys = (RatPGPKey*)
			ckrealloc(k->keys, sizeof(RatPGPKey)*k->keyAlloc);
	    }
	    key = &k->keys[k->keyCount++];
	    key->keyid = ids[i];
	    Tcl_IncrRefCount(ids[i]);
	    key->addrCount = numAddr;
	    key->address  = (Tcl_Obj**)ckalloc(sizeof(Tcl_Obj*)*numAddr);
	    for (j=ai=0; j<idNum; j++) {
		if ('<' == *Tcl_GetStringFromObj(ids[j], NULL)) {
		    key->address[ai++] = ids[j];
		    Tcl_IncrRefCount(ids[j]);
		}
	    }
	    key->descr = Tcl_NewStringObj(
		    Tcl_DStringValue(descr), Tcl_DStringLength(descr));
	    Tcl_IncrRefCount(key->descr);
	    key->key = NULL;
	}
    }
}


/*
 *----------------------------------------------------------------------
 *
 * RatPGPFreeKeyring --
 *
 *      Deallocates a keyring
 *
 * Results:
 *	None
 *
 * Side effects:
 *	None
 *
 *
 *----------------------------------------------------------------------
 */
static void
RatPGPFreeKeyring(RatPGPKeyring *k)
{
    int i, j;

    for (i=0; i < k->keyCount; i++) {
	Tcl_DecrRefCount(k->keys[i].keyid);
	for (j=0; j < k->keys[i].addrCount; j++) {
	    Tcl_DecrRefCount(k->keys[i].address[j]);
	}
	ckfree(k->keys[i].address);
	Tcl_DecrRefCount(k->keys[i].descr);
	/*if (k->keys[i].key) {
	    Tcl_DecrRefCount(k->keys[i].key);
	}*/
    }
    ckfree(k->keys);
    if (k->title) {
	Tcl_DecrRefCount(k->title);
    }
    ckfree(k->name);
    k->keyCount = 0;
    ckfree(k);
}


/*
 *----------------------------------------------------------------------
 *
 * RatPGPNewKeyring --
 *
 *      Allocate a new keyring
 *
 * Results:
 *	None
 *
 * Side effects:
 *	None
 *
 *
 *----------------------------------------------------------------------
 */
static RatPGPKeyring*
RatPGPNewKeyring(const char *name)
{
    RatPGPKeyring *k;

    k = (RatPGPKeyring*)ckalloc(sizeof(RatPGPKeyring));
    k->keys = NULL;
    k->keyCount = 0;
    k->keyAlloc = 0;
    k->title = NULL;
    k->name = cpystr(name);
    k->mtime = 0;

    return k;
}


syntax highlighted by Code2HTML, v. 0.9.1