
/*
 * mhbuild.c -- expand/translate MIME composition files
 *
 * $Id$
 */

#include <h/mh.h>
#include <fcntl.h>
#include <h/signals.h>
#include <h/md5.h>
#include <errno.h>
#include <signal.h>
#include <zotnet/mts/mts.h>
#include <zotnet/tws/tws.h>
#include <h/mhn.h>

#ifdef HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif

static struct swit switches[] = {
#define	AUTOSW                  0
    { "auto", 0 },
#define	NAUTOSW                 1
    { "noauto", 0 },
#define	CHECKSW                 2
    { "check", 0 },
#define	NCHECKSW                3
    { "nocheck", 0 },
#define	EBCDICSW                4
    { "ebcdicsafe", 0 },
#define	NEBCDICSW               5
    { "noebcdicsafe", 0 },
#define	HEADSW                  6
    { "headers", 0 },
#define	NHEADSW                 7
    { "noheaders", 0 },
#define	LISTSW                  8
    { "list", 0 },
#define	NLISTSW                 9
    { "nolist", 0 },
#define	SIZESW                 10
    { "realsize", 0 },
#define	NSIZESW                11
    { "norealsize", 0 },
#define	RFC934SW               12
    { "rfc934mode", 0 },
#define	NRFC934SW              13
    { "norfc934mode", 0 },
#define	VERBSW                 14
    { "verbose", 0 },
#define	NVERBSW                15
    { "noverbose", 0 },
#define	RCACHESW               16
    { "rcache policy", 0 },
#define	WCACHESW               17
    { "wcache policy", 0 },
#define VERSIONSW              18
    { "version", 0 },
#define	HELPSW                 19
    { "help", 4 },
#define	DEBUGSW                20
    { "debug", -5 },
    { NULL, 0 }
};


static struct swit caches[] = {
#define	CACHE_NEVER    0
    { "never", 0 },
#define	CACHE_PRIVATE  1
    { "private", 0 },
#define	CACHE_PUBLIC   2
    { "public", 0 },
#define	CACHE_ASK      3
    { "ask", 0 },
    { NULL, 0 }
};

extern int errno;

int debugsw  = 0;
int verbosw  = 0;

static int autosw   = 0;
static int checksw  = 0;
static int ebcdicsw = 0;
static int headsw   = 1;
static int listsw   = 0;
static int rfc934sw = 0;
static int sizesw   = 1;

static int rcachesw = CACHE_ASK;
static int wcachesw = CACHE_ASK;

static int endian   = 0;
static int userrs   = 0;
static pid_t xpid   = 0;

static char *cache_public;
static char *cache_private;
static int cwdlen;
static char *cwd;
static char *errs = NULL;
static char *dir;
static char *tmp;

/* various formats for -list option */
#define	LSTFMT1		"%4s %-5s %-24s %5s %-36s\n"
#define	LSTFMT2a	"%4d "
#define	LSTFMT2b	"%-5s %-24.24s "
#define	LSTFMT2c1	"%5lu"
#define	LSTFMT2c2	"%4lu%c"
#define	LSTFMT2c3	"huge "
#define	LSTFMT2c4	"     "
#define	LSTFMT2d1	" %-36.36s"
#define	LSTFMT2d2	"\t     %-65.65s\n"

#define	NPARMS	10

/*
 * ABSTRACT TYPES FOR MHN
 */
typedef struct cefile  *CE;
typedef struct CTinfo  *CI;
typedef struct Content *CT;

/*
 * Structure for storing parsed elements
 * of the Content-Type component.
 */
struct CTinfo {
    char *ci_type;
    char *ci_subtype;
    char *ci_attrs[NPARMS + 2];
    char *ci_values[NPARMS];
    char *ci_comment;
    char *ci_magic;
};

/*
 * Structure for storing information related
 * to Content-Transfer-Encoding handling.
 */
struct cefile {
    char *ce_file;
    FILE *ce_fp;
    int	  ce_unlink;	/* remove file when done? */
};

/*
 * flags for Content->c_type
 */
#define	CT_UNKNOWN	0x00
#define	CT_APPLICATION	0x01
#define	CT_AUDIO	0x02
#define	CT_IMAGE	0x03
#define	CT_MESSAGE	0x04
#define	CT_MULTIPART	0x05
#define	CT_TEXT		0x06
#define	CT_VIDEO	0x07
#define	CT_EXTENSION	0x08

/*
 * flags for Content->c_encoding
 */
#define	CE_UNKNOWN	0x00
#define	CE_BASE64	0x01
#define	CE_QUOTED	0x02
#define	CE_8BIT		0x03
#define	CE_7BIT		0x04
#define	CE_BINARY	0x05
#define	CE_EXTENSION	0x06
#define	CE_EXTERNAL	0x07	/* for external-body */

/*
 * type for Init function (both type and encoding)
 */
typedef int (*InitFunc) (CT);

/*
 * types for Content-Type access functions
 */
typedef int (*ListFunc) (CT, int);
typedef int (*ShowFunc) (CT, int, int);
typedef int (*StoreFunc) (CT, char *);
typedef void (*FreeFunc) (CT);

/*
 * types for various encoding access functions
 */
typedef int (*OpenCEFunc) (CT, char **);
typedef void (*CloseCEFunc) (CT);
typedef int (*ListCEFunc) (CT);
typedef unsigned long (*SizeCEFunc) (CT);
typedef void (*FreeCEFunc) (CT, int);

/*
 * Primary structure for handling Content (Entity)
 */
struct Content {
    char *c_partno;		/* within multipart content          */
    char *c_vrsn;		/* Body-Version:                     */

    char *c_ctline;		/* Content-Type:                     */
    int	c_type;			/* internal form/flag for type       */
    int	c_subtype;		/* filled-in by c_ctinitfnx          */
    struct CTinfo c_ctinfo;	/* parsed elements of Content-Type   */

    void *c_ctparams;		/* content type specific data        */
    struct exbody *c_ctexbody;	/*   ..                              */

    char *c_showproc;		/* default, if not in profile        */
    char *c_termproc;		/* for charset madness...            */
    char *c_storeproc;		/* overrides profile entry, if any   */

    InitFunc  c_ctinitfnx;	/* parse content                     */
    ListFunc  c_ctlistfnx;	/* list content                      */
    ShowFunc  c_ctshowfnx;	/* show content                      */
    StoreFunc c_ctstorefnx;	/* store content                     */
    FreeFunc  c_ctfreefnx;	/* free content-specific structures  */

    char *c_celine;		/* Content-Transfer-Encoding:        */
    int	c_encoding;		/* internal form                     */
    CE c_cefile;		/* filled-in by encoding initfnx     */

    OpenCEFunc  c_ceopenfnx;	/* get a stream to decoded contents  */
    CloseCEFunc c_ceclosefnx;	/* release stream                    */
    ListCEFunc  c_celistfnx;	/* list decoding info                */
    SizeCEFunc  c_cesizefnx;	/* size of decoded contents          */
    FreeCEFunc  c_cefreefnx;	/* free encoding-specific structures */

    char *c_id;			/* Content-ID:                       */
    char *c_descr;		/* Content-Description:              */
    int	c_digested;		/* Content-MD5:                      */
    unsigned char c_digest[16];	/*   ..                              */
    FILE *c_fp;			/* read contents (stream)            */
    char *c_file;		/* read contents (file)              */
    int	c_unlink;		/* remove file when done?            */
    int	c_umask;		/* associated umask                  */
    long c_begin;		/* where content body starts in file */
    long c_end;			/* where content body ends in file   */
    pid_t c_pid;		/* process doing display             */
    char *c_storage;		/* write contents (file)             */
    int	c_rfc934;		/* rfc934 compatibility flag         */
};

/*
 * The main list of contents to display
 */
static CT *cts = NULL;


static char prefix[] = "----- =_aaaaaaaaaa";
static char *free_file = NULL;
static CT free_ct = NULL;

/*
 * Structure for mapping types to their internal flags
 */
struct k2v {
    char *kv_key;
    int	  kv_value;
};


/*
 * Structures for TEXT messages
 */
#define	TEXT_UNKNOWN	0x00
#define	TEXT_PLAIN	0x01
#define	TEXT_RICHTEXT	0x02
#define TEXT_ENRICHED	0x03

#define	CHARSET_UNKNOWN	    0x00
#define CHARSET_UNSPECIFIED 0x01  /* only needed when building drafts */
#define	CHARSET_USASCII	    0x01
#define	CHARSET_LATIN	    0x02

struct text {
    int	tx_charset;
};

static struct k2v SubText[] = {
    { "plain",    TEXT_PLAIN },
    { "richtext", TEXT_RICHTEXT },  /* defined in RFC-1341    */
    { "enriched", TEXT_ENRICHED },  /* defined in RFC-1896    */
    { NULL,       TEXT_UNKNOWN }    /* this one must be last! */
};

static struct k2v Charset[] = {
    { "us-ascii",   CHARSET_USASCII },
    { "iso-8859-1", CHARSET_LATIN },
    { NULL,         CHARSET_UNKNOWN }  /* this one must be last! */
};


/*
 * Structures for MULTIPART messages
 */
#define	MULTI_UNKNOWN	0x00
#define	MULTI_MIXED	0x01
#define	MULTI_ALTERNATE	0x02
#define	MULTI_DIGEST	0x03
#define	MULTI_PARALLEL	0x04

struct part {
    CT mp_part;
    struct part *mp_next;
};

struct multipart {
    char *mp_start;
    char *mp_stop;
    struct part *mp_parts;
};

static struct k2v SubMultiPart[] = {
    { "mixed",       MULTI_MIXED },
    { "alternative", MULTI_ALTERNATE },
    { "digest",      MULTI_DIGEST },
    { "parallel",    MULTI_PARALLEL },
    { NULL,          MULTI_UNKNOWN }    /* this one must be last! */
};


/*
 * Structures for MESSAGE messages
 */
#define	MESSAGE_UNKNOWN	 0x00
#define	MESSAGE_RFC822	 0x01
#define	MESSAGE_PARTIAL	 0x02
#define	MESSAGE_EXTERNAL 0x03

struct partial {
    char *pm_partid;
    int	pm_partno;
    int	pm_maxno;
    int	pm_marked;
    int	pm_stored;
};

static struct k2v SubMessage[] = {
    { "rfc822",        MESSAGE_RFC822 },
    { "partial",       MESSAGE_PARTIAL },
    { "external-body", MESSAGE_EXTERNAL },
    { NULL,            MESSAGE_UNKNOWN }		/* this one must be last! */
};

/*
 * Structure for message/external bodies
 */
struct exbody {
    CT eb_parent;
    CT eb_content;
    char *eb_partno;
    char *eb_access;
    int	eb_flags;
    char *eb_name;
    char *eb_permission;
    char *eb_site;
    char *eb_dir;
    char *eb_mode;
    unsigned long eb_size;
    char *eb_server;
    char *eb_subject;
    char *eb_body;
};


/*
 * Structure for APPLICATION messages
 */
#define	APPLICATION_UNKNOWN	0x00
#define	APPLICATION_OCTETS	0x01
#define	APPLICATION_POSTSCRIPT	0x02

static struct k2v SubApplication[] = {
    { "octet-stream", APPLICATION_OCTETS },
    { "postscript",   APPLICATION_POSTSCRIPT },
    { NULL,           APPLICATION_UNKNOWN }	/* this one must be last! */
};


/*
 * static prototypes
 */
static CT get_content (FILE *, char *, int);
static int get_ctinfo (char *, CT, int);
static int get_comment (CT, char **, int);
static int list_content (CT, int);
static void content_error (char *, CT, char *, ...);
static int make_intermediates (char *);
static void free_ctinfo (CT);
static void free_content (CT);
static int InitGeneric (CT);
static int InitText (CT);
static void free_text (CT);
static int InitMultiPart (CT);
static int list_multi (CT, int);
static void free_multi (CT);
static int InitMessage (CT);
static int list_partial (CT, int);
static void free_partial (CT);
static int params_external (CT, int);
static int list_external (CT, int);
static void free_external (CT);
static int InitApplication (CT);
static int list_application (CT, int);
static int init_encoding (CT, OpenCEFunc);
static int list_encoding (CT);
static void close_encoding (CT);
static unsigned long size_encoding (CT);
static void free_encoding (CT, int);
static int InitBase64 (CT);
static int openBase64 (CT, char **);
static void set_endian (void);
static int InitQuoted (CT);
static int openQuoted (CT, char **);
static int Init7Bit (CT);
static int open7Bit (CT, char **);
static int openExternal (CT, CT, CE, char **, int *);
static int InitFile (CT);
static int openFile (CT, char **);
static int InitFTP (CT);
static int openFTP (CT, char **);
static int InitMail (CT);
static int openMail (CT, char **);
static int find_cache (CT, int, int *, char *, char *);
static int find_cache_aux (int, char *, char *, char *);
static int find_cache_aux2 (char *, char *, char *);
static void build_comp (char *);
static char *fgetstr (char *, int, FILE *);
static int user_content (FILE *, char *, char *, CT *);
static void set_id (CT, int);
static int compose_content (CT);
static int output_content (CT, FILE *);
static int write7Bit (CT, FILE *);
static int writeQuoted (CT, FILE *);
static int writeBase64 (CT, FILE *);
static int writeBase64aux (FILE *, FILE *);
static void writeDigest (CT, FILE *, int);
static int readDigest (CT, char *);
static int pidcheck (int);


/*
 * Structures for mapping (content) types to
 * the functions to handle them.
 */

struct str2init {
    char *si_key;
    int	  si_val;
    InitFunc si_init;
};

static struct str2init str2cts[] = {
    { "application", CT_APPLICATION, InitApplication },
    { "audio",	     CT_AUDIO,	     InitGeneric },
    { "image",	     CT_IMAGE,	     InitGeneric },
    { "message",     CT_MESSAGE,     InitMessage },
    { "multipart",   CT_MULTIPART,   InitMultiPart },
    { "text",	     CT_TEXT,	     InitText },
    { "video",	     CT_VIDEO,	     InitGeneric },
    { NULL,	     CT_EXTENSION,   NULL },  /* these two must be last! */
    { NULL,	     CT_UNKNOWN,     NULL },
};

static struct str2init str2ces[] = {
    { "base64",		  CE_BASE64,	InitBase64 },
    { "quoted-printable", CE_QUOTED,	InitQuoted },
    { "8bit",		  CE_8BIT,	Init7Bit },
    { "7bit",		  CE_7BIT,	Init7Bit },
    { "binary",		  CE_BINARY,	NULL },
    { NULL,		  CE_EXTENSION,	NULL },	 /* these two must be last! */
    { NULL,		  CE_UNKNOWN,	NULL },
};

/*
 * NOTE WELL: si_key MUST NOT have value of NOTOK
 *
 * si_key is 1 if access method is anonymous.
 */
static struct str2init str2methods[] = {
    { "afs",         1,	InitFile },
    { "anon-ftp",    1,	InitFTP },
    { "ftp",         0,	InitFTP },
    { "local-file",  0,	InitFile },
    { "mail-server", 0,	InitMail },
    { NULL,          0, NULL }
};


int
main (int argc, char **argv)
{
    int *icachesw;
    char *cp, buf[100], **ap, **argp;
    char *arguments[MAXARGS], *compfile = NULL;
    FILE *fp;

#ifdef LOCALE
    setlocale(LC_ALL, "");
#endif
    invo_name = r1bindex (argv[0], '/');

    if ((cp = context_find (invo_name))) {
	ap = brkstring (cp = getcpy (cp), " ", "\n");
	ap = copyip (ap, arguments);
    } else {
	ap = arguments;
    }
    copyip (argv + 1, ap);
    argp = arguments;

    while ((cp = *argp++)) {
	if (cp[0] == '-' && cp[1] == '\0') {
	    if (compfile)
		adios (NULL, "cannot specify both standard input and a file");
	    else
		compfile = cp;
	    listsw = 0;		/* turn off -list if using standard in/out */
	    verbosw = 0;	/* turn off -verbose listings */
	    break;
	}
	if (*cp == '-') {
	    switch (smatch (++cp, switches)) {
	    case AMBIGSW: 
		ambigsw (cp, switches);
		done (1);
	    case UNKWNSW: 
		adios (NULL, "-%s unknown", cp);

	    case HELPSW: 
		sprintf (buf, "%s [switches] file", invo_name);
		print_help (buf, switches, 1);
		done (1);
	    case VERSIONSW:
		print_version(invo_name);
		done (1);

	    case AUTOSW:
		autosw++;
		continue;
	    case NAUTOSW:
		autosw = 0;
		continue;

	    case RCACHESW:
		icachesw = &rcachesw;
		goto do_cache;
	    case WCACHESW:
		icachesw = &wcachesw;
	    do_cache: ;
		if (!(cp = *argp++) || *cp == '-')
		    adios (NULL, "missing argument to %s", argp[-2]);
		switch (*icachesw = smatch (cp, caches)) {
		case AMBIGSW:
		    ambigsw (cp, caches);
		    done (1);
		case UNKWNSW:
		    adios (NULL, "%s unknown", cp);
		default:
		    break;
		}
		continue;

	    case CHECKSW:
		checksw++;
		continue;
	    case NCHECKSW:
		checksw = 0;
		continue;

	    case DEBUGSW:
		debugsw++;
		continue;
    
	    case EBCDICSW:
		ebcdicsw++;
		continue;
	    case NEBCDICSW:
		ebcdicsw = 0;
		continue;

	    case HEADSW:
		headsw++;
		continue;
	    case NHEADSW:
		headsw = 0;
		continue;

	    case LISTSW:
		listsw++;
		continue;
	    case NLISTSW:
		listsw = 0;
		continue;

	    case RFC934SW:
		rfc934sw++;
		continue;
	    case NRFC934SW:
		rfc934sw = 0;
		continue;

	    case SIZESW:
		sizesw++;
		continue;
	    case NSIZESW:
		sizesw = 0;
		continue;

	    case VERBSW: 
		verbosw++;
		continue;
	    case NVERBSW: 
		verbosw = 0;
		continue;
	    }
	}
	if (compfile)
	    adios (NULL, "only one composition file allowed");
	else
	    compfile = cp;
    }

    set_endian ();

    /*
     * Check if we've specified an additional profile
     */
    if ((cp = getenv ("MHBUILD"))) {
	if ((fp = fopen (cp, "r"))) {
	    m_readefs ((struct node **) 0, fp, cp, 0);
	    fclose (fp);
	} else {
	    admonish ("", "unable to read $MHBUILD profile (%s)", cp);
	}
    }

    /*
     * Read the standard profile setup
     */
    if ((fp = fopen (cp = etcpath ("mhbuild.defaults"), "r"))) {
	m_readefs ((struct node **) 0, fp, cp, 0);
	fclose (fp);
    }

    /* Check for public cache location */
    sprintf (buf, "%s-cache", invo_name);
    if ((cache_public = context_find (buf)) && *cache_public != '/')
	cache_public = NULL;

    /* Check for private cache location */
    sprintf (buf, "%s-private-cache", invo_name);
    if (!(cache_private = context_find (buf)))
	cache_private = ".cache";
    cache_private = getcpy (m_maildir (cache_private));

    cwd = getcpy (pwd());
    cwdlen = strlen (cwd);

    /*
     * Check for storage directory.  If no profile
     * entry, then use current directory.
     */
    sprintf (buf, "%s-storage", invo_name);
    dir = getcpy ((cp = context_find (buf)) && *cp ? cp : cwd);
    tmp = cp && *cp ? concat (cp, "/", invo_name, NULL)
	: add (m_maildir (invo_name), NULL);

    if (!context_find ("path"))
	free (path ("./", TFOLDER));

    /* Check if we have a file to process */
    if (!compfile)
	adios (NULL, "need to specify a %s composition file", invo_name);

    /*
     * Process the composition file.
     */
    build_comp (compfile);
    /* NOT REACHED */
}


/*
 * Main routine for reading/parsing the headers
 * of a message content.
 *
 * toplevel =  1   # we are at the top level of the message
 * toplevel =  0   # we are inside message type or multipart type
 *                 # other than multipart/digest
 * toplevel = -1   # we are inside multipart/digest
 */

static CT
get_content (FILE *in, char *file, int toplevel)
{
    int compnum, state;
    char buf[BUFSIZ], name[NAMESZ];
    CT ct;

    if (!(ct = (CT) calloc (1, sizeof(*ct))))
	adios (NULL, "out of memory");

    ct->c_fp = in;
    ct->c_begin = ftell (ct->c_fp) + 1;
    ct->c_file = add (file, NULL);

    /*
     * Read the content headers
     */
    for (compnum = 1, state = FLD;;) {
	switch (state = m_getfld (state, name, buf, sizeof(buf), in)) {
	case FLD:
	case FLDPLUS:
	case FLDEOF:
	    compnum++;

	    /* Get MIME-Version field */
	    if (!strcasecmp (name, VRSN_FIELD)) {
		int ucmp;
		char c, *cp, *dp;

		cp = add (buf, NULL);
		while (state == FLDPLUS) {
		    state = m_getfld (state, name, buf, sizeof(buf), in);
		    cp = add (buf, cp);
		}

		if (ct->c_vrsn) {
		    advise (NULL, "message %s has multiple %s: fields (%s)",
			    ct->c_file, VRSN_FIELD, dp = trimcpy (cp));
		    free (dp);
		    free (cp);
		    goto out;
		}

		ct->c_vrsn = cp;
		while (isspace (*cp))
		    cp++;
		for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
		    *dp++ = ' ';
		for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
		    if (!isspace (*dp))
			break;
		*++dp = '\0';
		if (debugsw)
		    fprintf (stderr, "%s: %s\n", VRSN_FIELD, cp);

		if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK)
		    goto out;

		for (dp = cp; istoken (*dp); dp++)
		    continue;
		c = *dp, *dp = '\0';
		ucmp = !strcasecmp (cp, VRSN_VALUE);
		*dp = c;
		if (!ucmp)
		    admonish (NULL,
			      "message %s has unknown value for %s: field (%s)",
			      ct->c_file, VRSN_FIELD, cp);
		goto got_header;
	    }

	    /* Get Content-Type field */
	    if (!strcasecmp (name, TYPE_FIELD)) {
		char *cp;
		struct str2init *s2i;
		CI ci = &ct->c_ctinfo;

		cp = add (buf, NULL);
		while (state == FLDPLUS) {
		    state = m_getfld (state, name, buf, sizeof(buf), in);
		    cp = add (buf, cp);
		}

		/* Check if we've already seen a Content-Type header */
		if (ct->c_ctline) {
		    char *dp = trimcpy (cp);

		    advise (NULL, "message %s has multiple %s: fields (%s)",
			    ct->c_file, TYPE_FIELD, dp);
		    free (dp);
		    free (cp);
		    goto out;
		}

		/* Parse the Content-Type field */
		if (get_ctinfo (cp, ct, 0) == NOTOK)
		    goto out;

		/*
		 * Set the Init function and the internal
		 * flag for this content type.
		 */
		for (s2i = str2cts; s2i->si_key; s2i++)
		    if (!strcasecmp (ci->ci_type, s2i->si_key))
			break;
		if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
		    s2i++;
		ct->c_type = s2i->si_val;
		ct->c_ctinitfnx = s2i->si_init;
		goto got_header;
	    }

	    /* Get Content-Transfer-Encoding field */
	    if (!strcasecmp (name, ENCODING_FIELD)) {
		char *cp, *dp;
		char c;
		struct str2init *s2i;

		cp = add (buf, NULL);
		while (state == FLDPLUS) {
		    state = m_getfld (state, name, buf, sizeof(buf), in);
		    cp = add (buf, cp);
		}

		/*
		 * Check if we've already seen the
		 * Content-Transfer-Encoding field
		 */
		if (ct->c_celine) {
		    advise (NULL, "message %s has multiple %s: fields (%s)",
			    ct->c_file, ENCODING_FIELD, dp = trimcpy (cp));
		    free (dp);
		    free (cp);
		    goto out;
		}

		ct->c_celine = cp;	/* Save copy of this field */
		while (isspace (*cp))
		    cp++;
		for (dp = cp; istoken (*dp); dp++)
		    continue;
		c = *dp;
		*dp = '\0';

		/*
		 * Find the internal flag and Init function
		 * for this transfer encoding.
		 */
		for (s2i = str2ces; s2i->si_key; s2i++)
		    if (!strcasecmp (cp, s2i->si_key))
			break;
		if (!s2i->si_key && !uprf (cp, "X-"))
		    s2i++;
		*dp = c;
		ct->c_encoding = s2i->si_val;

		/* Call the Init function for this encoding */
		if (s2i->si_init && (*s2i->si_init) (ct) == NOTOK)
		    goto out;
		goto got_header;
	    }

	    /* Get Content-ID field */
	    if (!strcasecmp (name, ID_FIELD)) {
		ct->c_id = add (buf, ct->c_id);
		while (state == FLDPLUS) {
		    state = m_getfld (state, name, buf, sizeof(buf), in);
		    ct->c_id = add (buf, ct->c_id);
		}
		goto got_header;
	    }

	    /* Get Content-Description field */
	    if (!strcasecmp (name, DESCR_FIELD)) {
		ct->c_descr = add (buf, ct->c_descr);
		while (state == FLDPLUS) {
		    state = m_getfld (state, name, buf, sizeof(buf), in);
		    ct->c_descr = add (buf, ct->c_descr);
		}
		goto got_header;
	    }

	    /* Get Content-MD5 field */
	    if (!strcasecmp (name, MD5_FIELD)) {
		char *cp, *dp, *ep;

		cp = add (buf, NULL);
		while (state == FLDPLUS) {
		    state = m_getfld (state, name, buf, sizeof(buf), in);
		    cp = add (buf, cp);
		}

		if (!checksw) {
		    free (cp);
		    goto got_header;
		}

		if (ct->c_digested) {
		    advise (NULL, "message %s has multiple %s: fields (%s)",
			    ct->c_file, MD5_FIELD, dp = trimcpy (cp));
		    free (dp);
		    free (cp);
		    goto out;
		}

		ep = cp;
		while (isspace (*cp))
		    cp++;
		for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
		    *dp++ = ' ';
		for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
		    if (!isspace (*dp))
			break;
		*++dp = '\0';
		if (debugsw)
		    fprintf (stderr, "%s: %s\n", MD5_FIELD, cp);

		if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK) {
		    free (ep);
		    goto out;
		}

		for (dp = cp; *dp && !isspace (*dp); dp++)
		    continue;
		*dp = '\0';

		readDigest (ct, cp);
		free (ep);
		ct->c_digested++;
		goto got_header;
	    }

#if 0
	    if (uprf (name, XXX_FIELD_PRF))
		advise (NULL, "unknown field (%s) in message %s",
			name, ct->c_file);
	    /* and fall... */
#endif

	    while (state == FLDPLUS)
		state = m_getfld (state, name, buf, sizeof(buf), in);

got_header:
	    if (state != FLDEOF) {
		ct->c_begin = ftell (in) + 1;
		continue;
	    }
	    /* else fall... */

	case BODY:
	case BODYEOF:
	    ct->c_begin = ftell (in) - strlen (buf);
	    break;

	case FILEEOF:
	    ct->c_begin = ftell (in);
	    break;

	case LENERR:
	case FMTERR:
	    adios (NULL, "message format error in component #%d", compnum);

	default:
	    adios (NULL, "getfld() returned %d", state);
	}
	break;
    }

    /*
     * Check if we saw a Content-Type field.
     * If not, then assign a default value for
     * it, and the Init function.
     */
    if (!ct->c_ctline) {
	/*
	 * If we are inside a multipart/digest message,
	 * so default type is message/rfc822
	 */
	if (toplevel < 0) {
	    if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
		goto out;
	    ct->c_type = CT_MESSAGE;
	    ct->c_ctinitfnx = InitMessage;
	} else {
	    /*
	     * Else default type is text/plain
	     */
	    if (get_ctinfo ("text/plain", ct, 0) == NOTOK)
		goto out;
	    ct->c_type = CT_TEXT;
	    ct->c_ctinitfnx = InitText;
	}
    }

    /*
     * Set up default handlers.  Some of these
     * may change when the Init function is called.
     */
    if (!ct->c_ctlistfnx)
	ct->c_ctlistfnx = list_content;

    /* Use default Transfer-Encoding, if necessary */
    if (!ct->c_celine) {
	ct->c_encoding = CE_7BIT;
	Init7Bit (ct);
    }

    return ct;

 out:
    free_content (ct);
    return NULL;
}


/*
 * Used to parse both:
 *   1) Content-Type line
 *   2) composition directives
 *
 * and fills in the information of the CTinfo structure.
 */

static int
get_ctinfo (char *cp, CT ct, int magic)
{
    int	i;
    char *dp, **ap, **ep;
    char c;
    CI ci;

    ci = &ct->c_ctinfo;
    i = strlen (invo_name) + 2;

    /* store copy of Content-Type line */
    cp = ct->c_ctline = add (cp, NULL);

    while (isspace (*cp))	/* trim leading spaces */
	cp++;

    /* change newlines to spaces */
    for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
	*dp++ = ' ';

    /* trim trailing spaces */
    for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
	if (!isspace (*dp))
	    break;
    *++dp = '\0';

    if (debugsw)
	fprintf (stderr, "%s: %s\n", TYPE_FIELD, cp);

    if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
	return NOTOK;

    for (dp = cp; istoken (*dp); dp++)
	continue;
    c = *dp, *dp = '\0';
    ci->ci_type = add (cp, NULL);	/* store content type */
    *dp = c, cp = dp;

    if (!*ci->ci_type) {
	advise (NULL, "invalid %s: field in message %s (empty type)", 
		TYPE_FIELD, ct->c_file);
	return NOTOK;
    }

    /* down case the content type string */
    for (dp = ci->ci_type; *dp; dp++)
	if (isalpha(*dp) && isupper (*dp))
	    *dp = tolower (*dp);

    while (isspace (*cp))
	cp++;

    if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
	return NOTOK;

    if (*cp != '/') {
	if (!magic)
	    ci->ci_subtype = add ("", NULL);
	goto magic_skip;
    }

    cp++;
    while (isspace (*cp))
	cp++;

    if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
	return NOTOK;

    for (dp = cp; istoken (*dp); dp++)
	continue;
    c = *dp, *dp = '\0';
    ci->ci_subtype = add (cp, NULL);	/* store the content subtype */
    *dp = c, cp = dp;

    if (!*ci->ci_subtype) {
	advise (NULL,
		"invalid %s: field in message %s (empty subtype for \"%s\")",
		TYPE_FIELD, ct->c_file, ci->ci_type);
	return NOTOK;
    }

    /* down case the content subtype string */
    for (dp = ci->ci_subtype; *dp; dp++)
	if (isalpha(*dp) && isupper (*dp))
	    *dp = tolower (*dp);

magic_skip:
    while (isspace (*cp))
	cp++;

    if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
	return NOTOK;

    /*
     * Parse attribute/value pairs given with Content-Type
     */
    ep = (ap = ci->ci_attrs) + NPARMS;
    while (*cp == ';') {
	char *vp, *up;

	if (ap >= ep) {
	    advise (NULL,
		    "too many parameters in message %s's %s: field (%d max)",
		    ct->c_file, TYPE_FIELD, NPARMS);
	    return NOTOK;
	}

	cp++;
	while (isspace (*cp))
	    cp++;

	if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
	    return NOTOK;

	if (*cp == 0) {
	    advise (NULL,
		    "extraneous trailing ';' in message %s's %s: parameter list",
		    ct->c_file, TYPE_FIELD);
	    return OK;
	}

	for (dp = cp; istoken (*dp); dp++)
	    if (isalpha(*dp) && isupper (*dp))
		*dp = tolower (*dp);
	for (up = dp; isspace (*dp); )
	    dp++;
	if (dp == cp || *dp != '=') {
	    advise (NULL,
		    "invalid parameter in message %s's %s: field\n%*.*sparameter %s (error detected at offset %d)",
		    ct->c_file, TYPE_FIELD, i, i, "", cp, dp - cp);
	    return NOTOK;
	}

	vp = (*ap = add (cp, NULL)) + (up - cp);
	*vp = '\0';
	for (dp++; isspace (*dp); )
	    dp++;
	ci->ci_values[ap - ci->ci_attrs] = vp = *ap + (dp - cp);
	if (*dp == '"') {
	    for (cp = ++dp, dp = vp;;) {
		switch (c = *cp++) {
		    case '\0':
bad_quote:
		        advise (NULL,
				"invalid quoted-string in message %s's %s: field\n%*.*s(parameter %s)",
				ct->c_file, TYPE_FIELD, i, i, "", *ap);
			return NOTOK;

		    case '\\':
			*dp++ = c;
			if ((c = *cp++) == '\0')
			    goto bad_quote;
			/* else fall... */

		    default:
    			*dp++ = c;
    			continue;

		    case '"':
			*dp = '\0';
			break;
		}
		break;
	    }
	} else {
	    for (cp = dp, dp = vp; istoken (*cp); cp++, dp++)
		continue;
	    *dp = '\0';
	}
	if (!*vp) {
	    advise (NULL,
		    "invalid parameter in message %s's %s: field\n%*.*s(parameter %s)",
		    ct->c_file, TYPE_FIELD, i, i, "", *ap);
	    return NOTOK;
	}
	ap++;

	while (isspace (*cp))
	    cp++;

	if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
	    return NOTOK;
    }

    /*
     * Get any <Content-Id> given in buffer
     */
    if (magic && *cp == '<') {
	if (ct->c_id) {
	    free (ct->c_id);
	    ct->c_id = NULL;
	}
	if (!(dp = strchr(ct->c_id = ++cp, '>'))) {
	    advise (NULL, "invalid ID in message %s", ct->c_file);
	    return NOTOK;
	}
	c = *dp;
	*dp = '\0';
	if (*ct->c_id)
	    ct->c_id = concat ("<", ct->c_id, ">\n", NULL);
	else
	    ct->c_id = NULL;
	*dp++ = c;
	cp = dp;

	while (isspace (*cp))
	    cp++;
    }

    /*
     * Get any [Content-Description] given in buffer.
     */
    if (magic && *cp == '[') {
	ct->c_descr = ++cp;
	for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
	    if (*dp == ']')
		break;
	if (dp < cp) {
	    advise (NULL, "invalid description in message %s", ct->c_file);
	    ct->c_descr = NULL;
	    return NOTOK;
	}
	
	c = *dp;
	*dp = '\0';
	if (*ct->c_descr)
	    ct->c_descr = concat (ct->c_descr, "\n", NULL);
	else
	    ct->c_descr = NULL;
	*dp++ = c;
	cp = dp;

	while (isspace (*cp))
	    cp++;
    }

    /*
     * Check if anything is left over
     */
    if (*cp) {
	if (magic)
	    ci->ci_magic = add (cp, NULL);
	else
	    advise (NULL,
		    "extraneous information in message %s's %s: field\n%*.*s(%s)",
		ct->c_file, TYPE_FIELD, i, i, "", cp);
    }

    return OK;
}


static int
get_comment (CT ct, char **ap, int istype)
{
    int i;
    char *bp, *cp;
    char c, buffer[BUFSIZ], *dp;
    CI ci;

    ci = &ct->c_ctinfo;
    cp = *ap;
    bp = buffer;
    cp++;

    for (i = 0;;) {
	switch (c = *cp++) {
	case '\0':
invalid:
	advise (NULL, "invalid comment in message %s's %s: field",
		ct->c_file, istype ? TYPE_FIELD : VRSN_FIELD);
	return NOTOK;

	case '\\':
	    *bp++ = c;
	    if ((c = *cp++) == '\0')
		goto invalid;
	    *bp++ = c;
	    continue;

	case '(':
	    i++;
	    /* and fall... */
	default:
	    *bp++ = c;
	    continue;

	case ')':
	    if (--i < 0)
		break;
	    *bp++ = c;
	    continue;
	}
	break;
    }
    *bp = '\0';

    if (istype) {
	if ((dp = ci->ci_comment)) {
	    ci->ci_comment = concat (dp, " ", buffer, NULL);
	    free (dp);
	} else {
	    ci->ci_comment = add (buffer, NULL);
	}
    }

    while (isspace (*cp))
	cp++;

    *ap = cp;
    return OK;
}


#define empty(s) ((s) ? (s) : "")

/*
 * Generic method for listing content
 */

static int
list_content (CT ct, int toplevel)
{
    unsigned long size;
    char **ap, **ep;
    char *cp, buffer[BUFSIZ];
    CI ci = &ct->c_ctinfo;

    printf (toplevel > 0 ? LSTFMT2a : toplevel < 0 ? "part " : "     ",
	    atoi (r1bindex (empty (ct->c_file), '/')));
    sprintf (buffer, "%s/%s", empty (ci->ci_type), empty (ci->ci_subtype));
    printf (LSTFMT2b, empty (ct->c_partno), buffer);

    if (ct->c_cesizefnx && sizesw)
	size = (*ct->c_cesizefnx) (ct);
    else
	size = ct->c_end - ct->c_begin;

    for (cp = " KMGT"; size > 9999; size >>= 10)
	if (!*++cp)
	    break;

    switch (*cp) {
        case ' ':
	    if (size > 0 || ct->c_encoding != CE_EXTERNAL)
		printf (LSTFMT2c1, size);
	    else
		printf (LSTFMT2c4);
	    break;

	default:
	    printf (LSTFMT2c2, size, *cp);
	    break;

	case '\0':
	    printf (LSTFMT2c3);
    }

    if (ct->c_descr) {
	char *dp;

	dp = trimcpy (cp = add (ct->c_descr, NULL));
	free (cp);
	printf (LSTFMT2d1, dp);
	free (dp);
    }

    printf ("\n");

    if (verbosw && ci->ci_comment) {
	char *dp;

	dp = trimcpy (cp = add (ci->ci_comment, NULL));
	free (cp);
	sprintf (buffer, "(%s)", dp);
	free (dp);
	printf (LSTFMT2d2, buffer);
    }

    if (!debugsw)
	return OK;

    fflush (stdout);

    fprintf (stderr, "  partno \"%s\"\n", empty (ct->c_partno));

    if (ct->c_vrsn)
	fprintf (stderr, "  %s:%s\n", VRSN_FIELD, ct->c_vrsn);

    if (ct->c_ctline)
	fprintf (stderr, "  %s:%s", TYPE_FIELD, ct->c_ctline);
    fprintf (stderr,
	     "    type \"%s\"  subtype \"%s\"  comment \"%s\"  magic \"%s\"\n",
	     empty (ci->ci_type), empty (ci->ci_subtype),
	     empty (ci->ci_comment), empty (ci->ci_magic));
    for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
	fprintf (stderr, "      parameter %s=\"%s\"\n", *ap, *ep);
    fprintf (stderr, "    type 0x%x subtype 0x%x params 0x%x\n",
	     ct->c_type, ct->c_subtype, (unsigned int) ct->c_ctparams);

    fprintf (stderr, "     showproc \"%s\"\n", empty (ct->c_showproc));
    fprintf (stderr, "     termproc \"%s\"\n", empty (ct->c_termproc));
    fprintf (stderr, "     storeproc \"%s\"\n", empty (ct->c_storeproc));

    if (ct->c_celine)
	fprintf (stderr, "  %s:%s", ENCODING_FIELD, ct->c_celine);
    fprintf (stderr, "    encoding 0x%x params 0x%x\n",
	     ct->c_encoding, (unsigned int) ct->c_cefile);

    if (ct->c_id)
	fprintf (stderr, "  %s:%s", ID_FIELD, ct->c_id);
    if (ct->c_descr)
	fprintf (stderr, "  %s:%s", DESCR_FIELD, ct->c_descr);

    fprintf (stderr, "  fp 0x%x file \"%s\" begin %ld end %ld\n",
	     (unsigned int) ct->c_fp, empty (ct->c_file),
	     ct->c_begin, ct->c_end);

    if (ct->c_celistfnx)
	(*ct->c_celistfnx) (ct);

    return OK;
}


#undef empty

static void
content_error (char *what, CT ct, char *fmt, ...)
{
    va_list arglist;
    int	i;
    char *bp;
    char buffer[BUFSIZ];
    CI ci;

    bp = buffer;

    if (userrs && invo_name && *invo_name) {
	sprintf (bp, "%s: ", invo_name);
	bp += strlen (bp);
    }

    va_start (arglist, fmt);
    vsprintf (bp, fmt, arglist);
    bp += strlen (bp);
    ci = &ct->c_ctinfo;

    if (what) {
	char *s;

	if (*what) {
	    sprintf (bp, " %s: ", what);
	    bp += strlen (bp);
	}
	if ((s = strerror (errno)))
	    sprintf (bp, "%s", s);
	else
	    sprintf (bp, "Error %d", errno);
	bp += strlen (bp);
    }

    i = strlen (invo_name) + 2;
    sprintf (bp, "\n%*.*s(content %s/%s", i, i, "", ci->ci_type, ci->ci_subtype);
    bp += strlen (bp);
    if (ct->c_file) {
	sprintf (bp, " in message %s", ct->c_file);
	bp += strlen (bp);
	if (ct->c_partno) {
	    sprintf (bp, ", part %s", ct->c_partno);
	    bp += strlen (bp);
	}
    }
    sprintf (bp, ")");
    bp += strlen (bp);

    if (userrs) {
	*bp++ = '\n';
	*bp = '\0';

	errs = add (buffer, errs);
    }
    else
	advise (NULL, "%s", buffer);
}


static int
make_intermediates (char *file)
{
    char *cp;

    for (cp = file + 1; cp = strchr(cp, '/'); cp++) {
	struct stat st;

	*cp = '\0';
	if (stat (file, &st) == NOTOK) {
	    int	answer;
	    char *ep;

	    if (errno != ENOENT) {
		advise (file, "error on directory");
losing_directory:
		*cp = '/';
		return NOTOK;
	    }

	    ep = concat ("Create directory \"", file, "\"? ", NULL);
	    answer = getanswer (ep);
	    free (ep);

	    if (!answer)
		goto losing_directory;
	    if (!makedir (file)) {
		advise (NULL, "unable to create directory %s", file);
		goto losing_directory;
	    }
	}

	*cp = '/';
    }

    return OK;
}


static void
free_ctinfo (CT ct)
{
    char **ap;
    CI ci;

    ci = &ct->c_ctinfo;
    if (ci->ci_type) {
	free (ci->ci_type);
	ci->ci_type = NULL;
    }
    if (ci->ci_subtype) {
	free (ci->ci_subtype);
	ci->ci_subtype = NULL;
    }
    for (ap = ci->ci_attrs; *ap; ap++) {
	free (*ap);
	*ap = NULL;
    }
    if (ci->ci_comment) {
	free (ci->ci_comment);
	ci->ci_comment = NULL;
    }
    if (ci->ci_magic) {
	free (ci->ci_magic);
	ci->ci_magic = NULL;
    }
}


static void
free_content (CT ct)
{
    if (!ct)
	return;

    if (ct->c_partno)
	free (ct->c_partno);

    if (ct->c_vrsn)
	free (ct->c_vrsn);

    if (ct->c_ctline)
	free (ct->c_ctline);

    free_ctinfo (ct);

    if (ct->c_ctfreefnx)
	(*ct->c_ctfreefnx) (ct);

    if (ct->c_showproc)
	free (ct->c_showproc);
    if (ct->c_termproc)
	free (ct->c_termproc);
    if (ct->c_storeproc)
	free (ct->c_storeproc);

    if (ct->c_celine)
	free (ct->c_celine);
    if (ct->c_cefreefnx)
	(*ct->c_cefreefnx) (ct, 1);

    if (ct->c_id)
	free (ct->c_id);
    if (ct->c_descr)
	free (ct->c_descr);

    if (ct->c_file) {
	if (ct->c_unlink)
	    unlink (ct->c_file);
	free (ct->c_file);
    }
    if (ct->c_fp)
	fclose (ct->c_fp);

    if (ct->c_storage)
	free (ct->c_storage);

    free (ct);
}


/*
 * CONTENTS
 *
 * Handles content types audio, image, and video
 */

static int
InitGeneric (CT ct)
{
    char **ap, **ep, *cp;
    CI ci = &ct->c_ctinfo;

    /* check if content specifies a filename */
    if (autosw) {
	for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
	    if (!ct->c_storeproc
		&& !strcasecmp (*ap, "name")
		&& *(cp = *ep) != '/'
		&& *cp != '.'
		&& *cp != '|'
		&& *cp != '!'
		&& !strchr(cp, '%'))
		ct->c_storeproc = add (cp, NULL);
	}
    }

    return OK;
}


/*
 * TEXT
 */

static int
InitText (CT ct)
{
    char **ap, **ep;
    struct k2v *kv;
    struct text *t;
    CI ci = &ct->c_ctinfo;

    /* check for missing subtype */
    if (!*ci->ci_subtype)
	ci->ci_subtype = add ("plain", ci->ci_subtype);

    /* match subtype */
    for (kv = SubText; kv->kv_key; kv++)
	if (!strcasecmp (ci->ci_subtype, kv->kv_key))
	    break;
    ct->c_subtype = kv->kv_value;

    /* setup methods for this type */
    ct->c_ctfreefnx = free_text;

    /* allocate text character set structure */
    if ((t = (struct text *) calloc (1, sizeof(*t))) == NULL)
	adios (NULL, "out of memory");
    ct->c_ctparams = (void *) t;

    /* initially mark character set as unspecified */
    t->tx_charset = CHARSET_UNSPECIFIED;

    /* scan for charset parameter */
    for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
	if (!strcasecmp (*ap, "charset"))
	    break;

    /* check if content specified a character set */
    if (*ap) {
	/* match character set or set to CHARSET_UNKNOWN */
	for (kv = Charset; kv->kv_key; kv++)
	    if (!strcasecmp (*ep, kv->kv_key))
		break;
	t->tx_charset = kv->kv_value;
    }

    /* check if content specifies a filename */
    if (autosw) {
	char *cp;

	for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
	    if (!ct->c_storeproc
		&& !strcasecmp (*ap, "name")
		&& *(cp = *ep) != '/'
		&& *cp != '.'
		&& *cp != '|'
		&& *cp != '!'
		&& !strchr(cp, '%'))
		ct->c_storeproc = add (cp, NULL);
	}
    }

    return OK;
}


static void
free_text (CT ct)
{
    struct text *t;

    if (!(t = (struct text *) ct->c_ctparams))
	return;

    free ((char *) t);
    ct->c_ctparams = NULL;
}


/*
 * MULTIPART
 */

static int
InitMultiPart (CT ct)
{
    int	inout;
    long last, pos;
    char *cp, *dp, **ap, **ep;
    char *bp, buffer[BUFSIZ];
    struct multipart *m;
    struct k2v *kv;
    struct part *part, **next;
    CI ci = &ct->c_ctinfo;
    CT p;
    FILE *fp;

    /*
     * The encoding for multipart messages must be either
     * 7bit, 8bit, or binary (per RFC2045).
     */
    if (ct->c_encoding != CE_7BIT && ct->c_encoding != CE_8BIT
	&& ct->c_encoding != CE_BINARY) {
	admonish (NULL,
		  "\"%s/%s\" type in message %s must be encoded in 7bit, 8bit, or binary",
		  ci->ci_type, ci->ci_subtype, ct->c_file);
	return NOTOK;
    }

    /* match subtype */
    for (kv = SubMultiPart; kv->kv_key; kv++)
	if (!strcasecmp (ci->ci_subtype, kv->kv_key))
	    break;
    ct->c_subtype = kv->kv_value;

    /* setup methods for this type */
    ct->c_ctlistfnx = list_multi;
    ct->c_ctfreefnx = free_multi;

    /*
     * Check for "boundary" parameter, which is
     * required for multipart messages.
     */
    for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
	if (!strcasecmp (*ap, "boundary")) {
	    bp = *ep;
	    break;
	}
    }
    if (!*ap) {
	advise (NULL,
		"a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
		ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
	return NOTOK;
    }
    
    if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
	adios (NULL, "out of memory");
    ct->c_ctparams = (void *) m;

    for (cp = bp; isspace (*cp); cp++)
	continue;
    if (!*cp) {
	advise (NULL, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
		ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
	return NOTOK;
    }
    for (cp = bp, dp = cp + strlen (cp) - 1; dp > cp; dp--)
	if (!isspace (*dp))
	    break;
    *++dp = '\0';
    m->mp_start = concat (bp, "\n", NULL);
    m->mp_stop = concat (bp, "--\n", NULL);

    if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
	advise (ct->c_file, "unable to open for reading");
	return NOTOK;
    }

    fseek (fp = ct->c_fp, pos = ct->c_begin, SEEK_SET);
    last = ct->c_end;
    next = &m->mp_parts;
    part = NULL;
    inout = 1;

    while (fgets (buffer, sizeof(buffer) - 1, fp)) {
	if (pos > last)
	    break;

	pos += strlen (buffer);
	if (buffer[0] != '-' || buffer[1] != '-')
	    continue;
	if (inout) {
	    if (strcmp (buffer + 2, m->mp_start))
		continue;
next_part:
	    if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
		adios (NULL, "out of memory");
	    *next = part;
	    next = &part->mp_next;

	    if (!(p = get_content (fp, ct->c_file,
		rfc934sw && ct->c_subtype == MULTI_DIGEST ? -1 : 0))) {
		fclose (ct->c_fp);
		ct->c_fp = NULL;
		return NOTOK;
	    }
	    p->c_fp = NULL;
	    part->mp_part = p;
	    pos = p->c_begin;
	    fseek (fp, pos, SEEK_SET);
	    inout = 0;
	} else {
	    if (strcmp (buffer + 2, m->mp_start) == 0) {
		inout = 1;
end_part:
		p = part->mp_part;
		p->c_end = ftell(fp) - (strlen(buffer) + 1);
		if (p->c_end < p->c_begin)
		    p->c_begin = p->c_end;
		if (inout)
		    goto next_part;
		goto last_part;
	    } else {
		if (strcmp (buffer + 2, m->mp_stop) == 0)
		    goto end_part;
	    }
	}
    }

    advise (NULL, "bogus multipart content in message %s", ct->c_file);
    if (!inout && part) {
	p = part->mp_part;
	p->c_end = ct->c_end;

	if (p->c_begin >= p->c_end) {
	    for (next = &m->mp_parts;
		     *next != part;
		     next = &((*next)->mp_next))
		continue;
	    *next = NULL;
	    free_content (p);
	    free ((char *) part);
	}
    }

last_part:
    if (ct->c_subtype == MULTI_ALTERNATE && m->mp_parts->mp_next) {
	int i;
	struct part **base, **bmp;

	i = 0;
	for (part = m->mp_parts; part; part = part->mp_next)
	    i++;
	if ((base = (struct part **) calloc ((size_t) (i + 1), sizeof(*base)))
	        == NULL)
	    adios (NULL, "out of memory");
	bmp = base;
	for (part = m->mp_parts; part; part = part->mp_next)
	    *bmp++ = part;
	*bmp = NULL;

	next = &m->mp_parts;
	for (bmp--; bmp >= base; bmp--) {
	    part = *bmp;
	    *next = part, next = &part->mp_next;
	}
	*next = NULL;

	free ((char *) base);
    }

    {
	int partnum;
	char *pp;
	char partnam[BUFSIZ];

	if (ct->c_partno) {
	    sprintf (partnam, "%s.", ct->c_partno);
	    pp = partnam + strlen (partnam);
	}
	else
	    pp = partnam;

	for (part = m->mp_parts, partnum = 1; part;
	         part = part->mp_next, partnum++) {
	    p = part->mp_part;

	    sprintf (pp, "%d", partnum);
	    p->c_partno = add (partnam, NULL);

	    if (p->c_ctinitfnx && (*p->c_ctinitfnx) (p) == NOTOK) {
		fclose (ct->c_fp);
		ct->c_fp = NULL;
		return NOTOK;
	    }
	}
    }

    fclose (ct->c_fp);
    ct->c_fp = NULL;
    return OK;
}


/*
 * list content types in multipart message
 */

static int
list_multi (CT ct, int toplevel)
{
    struct multipart *m = (struct multipart *) ct->c_ctparams;
    struct part *part;

    list_content (ct, toplevel);

    for (part = m->mp_parts; part; part = part->mp_next) {
	CT p = part->mp_part;

	if (p->c_ctlistfnx)
	    (*p->c_ctlistfnx) (p, 0);
    }

    return OK;
}


static void
free_multi (CT ct)
{
    struct multipart *m = (struct multipart *) ct->c_ctparams;
    struct part *part, *next;

    if (!m)
	return;

    if (m->mp_start)
	free (m->mp_start);
    if (m->mp_stop)
	free (m->mp_stop);
	
    for (part = m->mp_parts; part; part = next) {
	next = part->mp_next;

	free_content (part->mp_part);

	free ((char *) part);
    }
    m->mp_parts = NULL;

    free ((char *) m);
    ct->c_ctparams = NULL;
}


/*
 * MESSAGE
 */

static int
InitMessage (CT ct)
{
    struct k2v *kv;
    CI ci = &ct->c_ctinfo;

    if (ct->c_encoding != CE_7BIT) {
	admonish (NULL,
		  "\"%s/%s\" type in message %s should be encoded in 7bit",
		  ci->ci_type, ci->ci_subtype, ct->c_file);
	return NOTOK;
    }

    /* check for missing subtype */
    if (!*ci->ci_subtype)
	ci->ci_subtype = add ("rfc822", ci->ci_subtype);

    /* match subtype */
    for (kv = SubMessage; kv->kv_key; kv++)
	if (!strcasecmp (ci->ci_subtype, kv->kv_key))
	    break;
    ct->c_subtype = kv->kv_value;

    switch (ct->c_subtype) {
	case MESSAGE_RFC822:
	    break;

	case MESSAGE_PARTIAL:
	    {
		char **ap, **ep;
		struct partial *p;

		if ((p = (struct partial *) calloc (1, sizeof(*p))) == NULL)
		    adios (NULL, "out of memory");
		ct->c_ctparams = (void *) p;
		ct->c_ctfreefnx = free_partial;

		for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
		    if (!strcasecmp (*ap, "id")) {
			p->pm_partid = add (*ep, NULL);
			continue;
		    }
		    if (!strcasecmp (*ap, "number")) {
			if (sscanf (*ep, "%d", &p->pm_partno) != 1
			        || p->pm_partno < 1) {
invalid_param:
			    advise (NULL,
				    "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
				    *ap, ci->ci_type, ci->ci_subtype,
				    ct->c_file, TYPE_FIELD);
			    return NOTOK;
			}
			continue;
		    }
		    if (!strcasecmp (*ap, "total")) {
			if (sscanf (*ep, "%d", &p->pm_maxno) != 1
			        || p->pm_maxno < 1)
			    goto invalid_param;
			continue;
		    }
		}

		if (!p->pm_partid
		        || !p->pm_partno
		        || (p->pm_maxno && p->pm_partno > p->pm_maxno)) {
		    advise (NULL,
			    "invalid parameters for \"%s/%s\" type in message %s's %s field",
			    ci->ci_type, ci->ci_subtype,
			    ct->c_file, TYPE_FIELD);
		    return NOTOK;
		}

		ct->c_ctlistfnx = list_partial;
	    }
	    break;

	case MESSAGE_EXTERNAL:
	    {
		int exresult;
		struct exbody *e;
		CT p;
		FILE *fp;

		if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
		    adios (NULL, "out of memory");
		ct->c_ctparams = (void *) e;
		ct->c_ctfreefnx = free_external;

		if (!ct->c_fp
		        && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
		    advise (ct->c_file, "unable to open for reading");
		    return NOTOK;
		}

		fseek (fp = ct->c_fp, ct->c_begin, SEEK_SET);

		if (!(p = get_content (fp, ct->c_file, 0))) {
		    fclose (ct->c_fp);
		    ct->c_fp = NULL;
		    return NOTOK;
		}

		e->eb_parent = ct;
		e->eb_content = p;
		p->c_ctexbody = e;
		if ((exresult = params_external (ct, 0)) != NOTOK
		        && p->c_ceopenfnx == openMail) {
		    int	cc, size;
		    char *bp;
		    
		    if ((size = ct->c_end - p->c_begin) <= 0) {
			if (!e->eb_subject)
			    content_error (NULL, ct,
					   "empty body for access-type=mail-server");
			goto no_body;
		    }
		    
		    if ((e->eb_body = bp = malloc ((unsigned) size)) == NULL)
			adios (NULL, "out of memory");
		    fseek (p->c_fp, p->c_begin, SEEK_SET);
		    while (size > 0)
			switch (cc = fread (bp, sizeof(*bp), size, p->c_fp)) {
			    case NOTOK:
			        adios ("failed", "fread");

			    case OK:
				adios (NULL, "unexpected EOF from fread");

			    default:
				bp += cc, size -= cc;
				break;
			}
		    *bp = 0;
		}
no_body:
		p->c_fp = NULL;
		p->c_end = p->c_begin;

		fclose (ct->c_fp);
		ct->c_fp = NULL;

		ct->c_ctlistfnx = list_external;

		if (exresult == NOTOK)
		    return NOTOK;
		if (e->eb_flags == NOTOK)
		    return OK;

		if (autosw) {
		    char *cp;

		    if ((cp = e->eb_name)
			&& *cp != '/'
			&& *cp != '.'
			&& *cp != '|'
			&& *cp != '!'
			&& !strchr(cp, '%')) {
			if (!ct->c_storeproc)
			    ct->c_storeproc = add (cp, NULL);
			if (!p->c_storeproc)
			    p->c_storeproc = add (cp, NULL);
		    }
		}

		switch (p->c_type) {
		    case CT_MULTIPART:
		        break;

		    case CT_MESSAGE:
			if (p->c_subtype != MESSAGE_RFC822)
			    break;
			/* else fall... */
		    default:
			e->eb_partno = ct->c_partno;
			if (p->c_ctinitfnx)
			    (*p->c_ctinitfnx) (p);
			break;
		}
	    }
	    break;

	default:
	    break;
    }

    return OK;
}


static int
params_external (CT ct, int composing)
{
    char **ap, **ep;
    struct exbody *e = (struct exbody *) ct->c_ctparams;
    CI ci = &ct->c_ctinfo;

    for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
	if (!strcasecmp (*ap, "access-type")) {
	    struct str2init *s2i;
	    CT p = e->eb_content;

	    for (s2i = str2methods; s2i->si_key; s2i++)
		if (!strcasecmp (*ep, s2i->si_key))
		    break;

	    if (!s2i->si_key) {
		e->eb_access = *ep;
		e->eb_flags = NOTOK;
		p->c_encoding = CE_EXTERNAL;
		continue;
	    }
	    e->eb_access = s2i->si_key;
	    e->eb_flags = s2i->si_val;
	    p->c_encoding = CE_EXTERNAL;

	    /* Call the Init function for this external type */
	    if ((*s2i->si_init)(p) == NOTOK)
		return NOTOK;
	    continue;
	}
	if (!strcasecmp (*ap, "name")) {
	    e->eb_name = *ep;
	    continue;
	}
	if (!strcasecmp (*ap, "permission")) {
	    e->eb_permission = *ep;
	    continue;
	}
	if (!strcasecmp (*ap, "site")) {
	    e->eb_site = *ep;
	    continue;
	}
	if (!strcasecmp (*ap, "directory")) {
	    e->eb_dir = *ep;
	    continue;
	}
	if (!strcasecmp (*ap, "mode")) {
	    e->eb_mode = *ep;
	    continue;
	}
	if (!strcasecmp (*ap, "size")) {
	    sscanf (*ep, "%lu", &e->eb_size);
	    continue;
	}
	if (!strcasecmp (*ap, "server")) {
	    e->eb_server = *ep;
	    continue;
	}
	if (!strcasecmp (*ap, "subject")) {
	    e->eb_subject = *ep;
	    continue;
	}
	if (composing && !strcasecmp (*ap, "body")) {
	    e->eb_body = getcpy (*ep);
	    continue;
	}
    }

    if (!e->eb_access) {
	advise (NULL,
		"invalid parameters for \"%s/%s\" type in message %s's %s field",
		ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
	return NOTOK;
    }

    return OK;
}


static int
list_partial (CT ct, int toplevel)
{
    struct partial *p = (struct partial *) ct->c_ctparams;

    list_content (ct, toplevel);
    if (verbosw) {
	printf ("\t     [message %s, part %d", p->pm_partid, p->pm_partno);
	if (p->pm_maxno)
	    printf (" of %d", p->pm_maxno);
	printf ("]\n");
    }

    return OK;
}


static int
list_external (CT ct, int toplevel)
{
    struct exbody *e = (struct exbody *) ct->c_ctparams;

    list_content (ct, toplevel);
    if (verbosw) {
	if (e->eb_name)
	    printf ("\t     retrieve %s\n", e->eb_name);
	if (e->eb_dir)
	    printf ("\t in directory %s\n", e->eb_dir);
	if (e->eb_site)
	    printf ("\t         from %s\n", e->eb_site);
	if (e->eb_server)
	    printf ("\t from mailbox %s\n", e->eb_server);
	if (e->eb_subject)
	    printf ("\t with subject %s\n", e->eb_subject);
	printf     ("\t        using %s", e->eb_access);
	if (e->eb_mode)
	    printf (" (in %s mode)", e->eb_mode);
	if (e->eb_permission)
	    printf (" (permission %s)", e->eb_permission);
	if (e->eb_flags == NOTOK)
	    printf (" [service unavailable]");
	printf ("\n");
    }
    list_content (e->eb_content, 0);

    return OK;
}


static void
free_partial (CT ct)
{
    struct partial *p = (struct partial *) ct->c_ctparams;

    if (!p)
	return;

    if (p->pm_partid)
	free (p->pm_partid);

    free ((char *) p);
    ct->c_ctparams = NULL;
}


static void
free_external (CT ct)
{
    struct exbody *e = (struct exbody *) ct->c_ctparams;

    if (!e)
	return;

    free_content (e->eb_content);
    if (e->eb_body)
	free (e->eb_body);

    free ((char *) e);
    ct->c_ctparams = NULL;
}


/*
 * APPLICATION
 */

static int
InitApplication (CT ct)
{
    char **ap, **ep, *cp;
    struct k2v *kv;
    CI ci = &ct->c_ctinfo;

    /* match subtype */
    for (kv = SubApplication; kv->kv_key; kv++)
	if (!strcasecmp (ci->ci_subtype, kv->kv_key))
	    break;
    ct->c_subtype = kv->kv_value;

    /* setup methods for this type */
    ct->c_ctlistfnx = list_application;

    /* check if content specifies a filename */
    if (autosw) {
	for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
	    if (!ct->c_storeproc
		&& !strcasecmp (*ap, "name") 
		&& *(cp = *ep) != '/'
		&& *cp != '.'
		&& *cp != '|'
		&& *cp != '!'
		&& !strchr(cp, '%'))
		ct->c_storeproc = add (cp, NULL);
	}
    }

    if (ct->c_subtype == APPLICATION_OCTETS) {
	int tarP = 0;
	int zP = 0;

	for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
	    /* check for "type=tar" attribute */
	    if (!strcasecmp (*ap, "type")) {
		if (strcasecmp (*ep, "tar"))
		    break;

		tarP = 1;
		continue;
	    }

	    /* check for "conversions=compress" attribute */
	    if ((!strcasecmp (*ap, "conversions") || !strcasecmp (*ap, "x-conversions"))
		&& (!strcasecmp (*ep, "compress") || !strcasecmp (*ep, "x-compress"))) {
		zP = 1;
		continue;
	    }
	}

	if (tarP) {
	    if (!ct->c_storeproc)
		if (autosw) {
		    ct->c_storeproc = add (zP ? "| uncompress | tar xvpf -"
					   : "| tar xvpf -", NULL);
		    ct->c_umask = 0022;
		} else {
		    ct->c_storeproc = add (zP ? "%m%P.tar.Z" : "%m%P.tar",
					   NULL);
		}
	}
    }

    return OK;
}


static int
list_application (CT ct, int toplevel)
{
    list_content (ct, toplevel);
    if (verbosw) {
	char **ap, **ep;
	CI ci = &ct->c_ctinfo;

	for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
	    printf ("\t     %s=\"%s\"\n", *ap, *ep);
    }

    return OK;
}


/*
 * TRANSFER ENCODINGS
 */

static int
init_encoding (CT ct, OpenCEFunc openfnx)
{
    CE ce;

    if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL)
	adios (NULL, "out of memory");

    ct->c_cefile     = ce;
    ct->c_ceopenfnx  = openfnx;
    ct->c_ceclosefnx = close_encoding;
    ct->c_cesizefnx  = size_encoding;
    ct->c_celistfnx  = list_encoding;
    ct->c_cefreefnx  = free_encoding;

    return OK;
}


static int
list_encoding (CT ct)
{
    CE ce;

    if ((ce = ct->c_cefile))
	fprintf (stderr, "  decoded fp 0x%x file \"%s\"\n",
		 (unsigned int) ce->ce_fp, ce->ce_file ? ce->ce_file : "");

    return OK;
}


static void
close_encoding (CT ct)
{
    CE ce;

    if (!(ce = ct->c_cefile))
	return;

    if (ce->ce_fp) {
	fclose (ce->ce_fp);
	ce->ce_fp = NULL;
    }
}


static unsigned long
size_encoding (CT ct)
{
    int	fd;
    unsigned long size;
    char *file;
    CE ce;
    struct stat st;

    if (!(ce = ct->c_cefile))
	return (ct->c_end - ct->c_begin);

    if (ce->ce_fp && fstat (fileno (ce->ce_fp), &st) != NOTOK)
	return (long) st.st_size;

    if (ce->ce_file) {
	if (stat (ce->ce_file, &st) != NOTOK)
	    return (long) st.st_size;
	else
	    return 0L;
    }

    if (ct->c_encoding == CE_EXTERNAL)
	return (ct->c_end - ct->c_begin);	

    file = NULL;
    if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
	return (ct->c_end - ct->c_begin);

    if (fstat (fd, &st) != NOTOK)
	size = (long) st.st_size;
    else
	size = 0L;

    (*ct->c_ceclosefnx) (ct);
    return size;
}


static void
free_encoding (CT ct, int toplevel)
{
    CE ce;

    if (!(ce = ct->c_cefile))
	return;

    if (ce->ce_fp) {
	fclose (ce->ce_fp);
	ce->ce_fp = NULL;
    }

    if (ce->ce_file) {
	if (ce->ce_unlink)
	    unlink (ce->ce_file);
	free (ce->ce_file);
    }

    if (toplevel) {
	free ((char *) ce);
	ct->c_cefile = NULL;
    } else {
	ct->c_ceopenfnx = NULL;
    }
}


/*
 * BASE64
 */

static unsigned char b642nib[0x80] = {
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
    0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 
    0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
    0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
    0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 
    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
    0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
    0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
};


static int
InitBase64 (CT ct)
{
    return init_encoding (ct, openBase64);
}


static int
openBase64 (CT ct, char **file)
{
    int	bitno, cc, digested;
    int fd, len, skip;
    unsigned long bits;
    unsigned char value, *b, *b1, *b2, *b3;
    char *cp, *ep, buffer[BUFSIZ];
    CE ce;
    MD5_CTX mdContext;

    b  = (unsigned char *) &bits;
    b1 = &b[endian > 0 ? 1 : 2];
    b2 = &b[endian > 0 ? 2 : 1];
    b3 = &b[endian > 0 ? 3 : 0];

    ce = ct->c_cefile;
    if (ce->ce_fp) {
	fseek (ce->ce_fp, 0L, SEEK_SET);
	goto ready_to_go;
    }

    if (ce->ce_file) {
	if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
	    content_error (ce->ce_file, ct, "unable to fopen for reading");
	    return NOTOK;
	}
	goto ready_to_go;
    }

    if (*file == NULL) {
	ce->ce_file = add (m_scratch ("", tmp), NULL);
	ce->ce_unlink = 1;
    } else {
	ce->ce_file = add (*file, NULL);
	ce->ce_unlink = 0;
    }

    if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
	content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
	return NOTOK;
    }

    if ((len = ct->c_end - ct->c_begin) < 0)
	adios (NULL, "internal error(1)");

    if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
	content_error (ct->c_file, ct, "unable to open for reading");
	return NOTOK;
    }
    
    if ((digested = ct->c_digested))
	MD5Init (&mdContext);

    bitno = 18;
    bits = 0L;
    skip = 0;

    lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
    while (len > 0) {
	switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
	case NOTOK:
	    content_error (ct->c_file, ct, "error reading from");
	    goto clean_up;

	case OK:
	    content_error (NULL, ct, "premature eof");
	    goto clean_up;

	default:
	    if (cc > len)
		cc = len;
	    len -= cc;

	    for (ep = (cp = buffer) + cc; cp < ep; cp++) {
		switch (*cp) {
		default:
		    if (isspace (*cp))
			break;
		    if (skip
			|| (*cp & 0x80)
			|| (value = b642nib[*cp & 0x7f]) > 0x3f) {
			if (debugsw)
			    fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n", *cp,
				     lseek (fd, (off_t)0, SEEK_CUR) - (ep - cp), skip);
			content_error (NULL, ct,
				       "invalid BASE64 encoding -- continuing");
			continue;
		    }

		    bits |= value << bitno;
test_end:
		    if ((bitno -= 6) < 0) {
			putc ((char) *b1, ce->ce_fp);
			if (digested)
			    MD5Update (&mdContext, b1, 1);
			if (skip < 2) {
			    putc ((char) *b2, ce->ce_fp);
			    if (digested)
				MD5Update (&mdContext, b2, 1);
			    if (skip < 1) {
				putc ((char) *b3, ce->ce_fp);
				if (digested)
				    MD5Update (&mdContext, b3, 1);
			    }
			}

			if (ferror (ce->ce_fp)) {
			    content_error (ce->ce_file, ct,
					   "error writing to");
			    goto clean_up;
			}
			bitno = 18, bits = 0L, skip = 0;
		    }
		    break;

		case '=':
		    if (++skip > 3)
			goto self_delimiting;
		    goto test_end;
		}
	    }
	}
    }

    if (bitno != 18) {
	if (debugsw)
	    fprintf (stderr, "premature ending (bitno %d)\n", bitno);

	content_error (NULL, ct, "invalid BASE64 encoding");
	goto clean_up;
    }

self_delimiting:
    fseek (ct->c_fp, 0L, SEEK_SET);

    if (fflush (ce->ce_fp)) {
	content_error (ce->ce_file, ct, "error writing to");
	goto clean_up;
    }

    if (digested) {
	unsigned char digest[16];

	MD5Final (digest, &mdContext);
	if (memcmp((char *) digest, (char *) ct->c_digest,
		   sizeof(digest) / sizeof(digest[0])))
	    content_error (NULL, ct,
			   "content integrity suspect (digest mismatch) -- continuing");
	else
	    if (debugsw)
		fprintf (stderr, "content integrity confirmed\n");
    }

    fseek (ce->ce_fp, 0L, SEEK_SET);

ready_to_go:
    *file = ce->ce_file;
    return fileno (ce->ce_fp);

clean_up:
    free_encoding (ct, 0);
    return NOTOK;
}


static void
set_endian (void)
{
    char *cp;
    union {
	long l;
	char c[sizeof(long)];
    } un;

    un.l = 1;
    endian = un.c[0] ? -1 : 1;
    if (debugsw)
	fprintf (stderr, "%s endian architecture\n",
		 endian > 0 ? "big" : "little");

    if ((cp = getenv ("MM_NOASK")) && !strcmp (cp, "1"))
	listsw  = 0;
}


/*
 * QUOTED PRINTABLE
 */

static char hex2nib[0x80] = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};


static int 
InitQuoted (CT ct)
{
    return init_encoding (ct, openQuoted);
}


static int
openQuoted (CT ct, char **file)
{
    int	cc, digested, len, quoted;
    char *cp, *ep;
    char buffer[BUFSIZ];
    unsigned char mask;
    CE ce;
    MD5_CTX mdContext;

    ce = ct->c_cefile;
    if (ce->ce_fp) {
	fseek (ce->ce_fp, 0L, SEEK_SET);
	goto ready_to_go;
    }

    if (ce->ce_file) {
	if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
	    content_error (ce->ce_file, ct, "unable to fopen for reading");
	    return NOTOK;
	}
	goto ready_to_go;
    }

    if (*file == NULL) {
	ce->ce_file = add (m_scratch ("", tmp), NULL);
	ce->ce_unlink = 1;
    } else {
	ce->ce_file = add (*file, NULL);
	ce->ce_unlink = 0;
    }

    if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
	content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
	return NOTOK;
    }

    if ((len = ct->c_end - ct->c_begin) < 0)
	adios (NULL, "internal error(2)");

    if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
	content_error (ct->c_file, ct, "unable to open for reading");
	return NOTOK;
    }

    if ((digested = ct->c_digested))
	MD5Init (&mdContext);

    quoted = 0;
#ifdef lint
    mask = 0;
#endif

    fseek (ct->c_fp, ct->c_begin, SEEK_SET);
    while (len > 0) {
	char *dp;

	if (fgets (buffer, sizeof(buffer) - 1, ct->c_fp) == NULL) {
	    content_error (NULL, ct, "premature eof");
	    goto clean_up;
	}

	if ((cc = strlen (buffer)) > len)
	    cc = len;
	len -= cc;

	for (ep = (cp = buffer) + cc - 1; cp <= ep; ep--)
	    if (!isspace (*ep))
		break;
	*++ep = '\n', ep++;

	for (; cp < ep; cp++) {
	    if (quoted) {
		if (quoted > 1) {
		    if (!isxdigit (*cp)) {
invalid_hex:
			dp = "expecting hexidecimal-digit";
			goto invalid_encoding;
		    }
		    mask <<= 4;
		    mask |= hex2nib[*cp & 0x7f];
		    putc (mask, ce->ce_fp);
		    if (digested)
			MD5Update (&mdContext, &mask, 1);
		} else {
		    switch (*cp) {
		    case ':':
			putc (*cp, ce->ce_fp);
			if (digested)
			    MD5Update (&mdContext, (unsigned char *) ":", 1);
			break;

		    default:
			if (!isxdigit (*cp))
			    goto invalid_hex;
			mask = hex2nib[*cp & 0x7f];
			quoted = 2;
			continue;
		    }
		}

		if (ferror (ce->ce_fp)) {
		    content_error (ce->ce_file, ct, "error writing to");
		    goto clean_up;
		}
		quoted = 0;
		continue;
	    }

	    switch (*cp) {
	    default:
		if (*cp < '!' || *cp > '~') {
		    int	i;
		    dp = "expecting character in range [!..~]";

invalid_encoding:
		    i = strlen (invo_name) + 2;
		    content_error (NULL, ct,
				   "invalid QUOTED-PRINTABLE encoding -- %s,\n%*.*sbut got char 0x%x",
				   dp, i, i, "", *cp);
		    goto clean_up;
		}
		/* and fall...*/
	    case ' ':
	    case '\t':
	    case '\n':
		putc (*cp, ce->ce_fp);
		if (digested) {
		    if (*cp == '\n')
			MD5Update (&mdContext, (unsigned char *) "\r\n",2);
		    else
			MD5Update (&mdContext, (unsigned char *) cp, 1);
		}
		if (ferror (ce->ce_fp)) {
		    content_error (ce->ce_file, ct, "error writing to");
		    goto clean_up;
		}
		break;

	    case '=':
		if (*++cp != '\n') {
		    quoted = 1;
		    cp--;
		}
		break;
	    }
	}
    }
    if (quoted) {
	content_error (NULL, ct,
		       "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
	goto clean_up;
    }

    fseek (ct->c_fp, 0L, SEEK_SET);

    if (fflush (ce->ce_fp)) {
	content_error (ce->ce_file, ct, "error writing to");
	goto clean_up;
    }

    if (digested) {
	unsigned char digest[16];

	MD5Final (digest, &mdContext);
	if (memcmp((char *) digest, (char *) ct->c_digest,
		   sizeof(digest) / sizeof(digest[0])))
	    content_error (NULL, ct,
			   "content integrity suspect (digest mismatch) -- continuing");
	else
	    if (debugsw)
		fprintf (stderr, "content integrity confirmed\n");
    }

    fseek (ce->ce_fp, 0L, SEEK_SET);

ready_to_go:
    *file = ce->ce_file;
    return fileno (ce->ce_fp);

clean_up:
    free_encoding (ct, 0);
    return NOTOK;
}


/*
 * 7BIT
 */

static int
Init7Bit (CT ct)
{
    if (init_encoding (ct, open7Bit) == NOTOK)
	return NOTOK;

    ct->c_cesizefnx = NULL;	/* no need to decode for real size */
    return OK;
}


static int
open7Bit (CT ct, char **file)
{
    int	cc, fd, len;
    char buffer[BUFSIZ];
    CE ce;

    ce = ct->c_cefile;
    if (ce->ce_fp) {
	fseek (ce->ce_fp, 0L, SEEK_SET);
	goto ready_to_go;
    }

    if (ce->ce_file) {
	if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
	    content_error (ce->ce_file, ct, "unable to fopen for reading");
	    return NOTOK;
	}
	goto ready_to_go;
    }

    if (*file == NULL) {
	ce->ce_file = add (m_scratch ("", tmp), NULL);
	ce->ce_unlink = 1;
    } else {
	ce->ce_file = add (*file, NULL);
	ce->ce_unlink = 0;
    }

    if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
	content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
	return NOTOK;
    }

    if (ct->c_type == CT_MULTIPART) {
	char **ap, **ep;
	CI ci = &ct->c_ctinfo;

	len = 0;
	fprintf (ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype);
	len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type)
	    + 1 + strlen (ci->ci_subtype);
	for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
	    putc (';', ce->ce_fp);
	    len++;

	    sprintf (buffer, "%s=\"%s\"", *ap, *ep);

	    if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
		fputs ("\n\t", ce->ce_fp);
		len = 8;
	    } else {
		putc (' ', ce->ce_fp);
		len++;
	    }
	    fprintf (ce->ce_fp, "%s", buffer);
	    len += cc;
	}

	if (ci->ci_comment) {
	    if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
		fputs ("\n\t", ce->ce_fp);
		len = 8;
	    }
	    else {
		putc (' ', ce->ce_fp);
		len++;
	    }
	    fprintf (ce->ce_fp, "(%s)", ci->ci_comment);
	    len += cc;
	}
	fprintf (ce->ce_fp, "\n");
	if (ct->c_id)
	    fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id);
	if (ct->c_descr)
	    fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr);
	fprintf (ce->ce_fp, "\n");
    }

    if ((len = ct->c_end - ct->c_begin) < 0)
	adios (NULL, "internal error(3)");

    if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
	content_error (ct->c_file, ct, "unable to open for reading");
	return NOTOK;
    }

    lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
    while (len > 0)
	switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
	case NOTOK:
	    content_error (ct->c_file, ct, "error reading from");
	    goto clean_up;

	case OK:
	    content_error (NULL, ct, "premature eof");
	    goto clean_up;

	default:
	    if (cc > len)
		cc = len;
	    len -= cc;

	    fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp);
	    if (ferror (ce->ce_fp)) {
		content_error (ce->ce_file, ct, "error writing to");
		goto clean_up;
	    }
	}

    fseek (ct->c_fp, 0L, SEEK_SET);

    if (fflush (ce->ce_fp)) {
	content_error (ce->ce_file, ct, "error writing to");
	goto clean_up;
    }

    fseek (ce->ce_fp, 0L, SEEK_SET);

ready_to_go:
    *file = ce->ce_file;
    return fileno (ce->ce_fp);

clean_up:
    free_encoding (ct, 0);
    return NOTOK;
}


/*
 * External
 */

static int
openExternal (CT ct, CT cb, CE ce, char **file, int *fd)
{
    char cachefile[BUFSIZ];

    if (ce->ce_fp) {
	fseek (ce->ce_fp, 0L, SEEK_SET);
	goto ready_already;
    }

    if (ce->ce_file) {
	if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
	    content_error (ce->ce_file, ct, "unable to fopen for reading");
	    return NOTOK;
	}
	goto ready_already;
    }

    if (find_cache (ct, rcachesw, (int *) 0, cb->c_id, cachefile) != NOTOK) {
	if ((ce->ce_fp = fopen (cachefile, "r"))) {
	    ce->ce_file = getcpy (cachefile);
	    ce->ce_unlink = 0;
	    goto ready_already;
	} else {
	    admonish (cachefile, "unable to fopen for reading");
	}
    }

    return OK;

ready_already:
    *file = ce->ce_file;
    *fd = fileno (ce->ce_fp);
    return DONE;
}

/*
 * File
 */

static int
InitFile (CT ct)
{
    return init_encoding (ct, openFile);
}


static int
openFile (CT ct, char **file)
{
    int	fd, cachetype;
    char cachefile[BUFSIZ];
    struct exbody *e = ct->c_ctexbody;
    CE ce = ct->c_cefile;

    switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
	case NOTOK:
	    return NOTOK;

	case OK:
	    break;

	case DONE:
	    return fd;
    }

    if (!e->eb_name) {
	content_error (NULL, ct, "missing name parameter");
	return NOTOK;
    }

    ce->ce_file = getcpy (e->eb_name);
    ce->ce_unlink = 0;

    if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
	content_error (ce->ce_file, ct, "unable to fopen for reading");
	return NOTOK;
    }

    if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write"))
	    && find_cache (NULL, wcachesw, &cachetype,
			   e->eb_content->c_id, cachefile) != NOTOK) {
	int mask;
	FILE *fp;

	mask = umask (cachetype ? ~m_gmprot () : 0222);
	if ((fp = fopen (cachefile, "w"))) {
	    int	cc;
	    char buffer[BUFSIZ];
	    FILE *gp = ce->ce_fp;

	    fseek (gp, 0L, SEEK_SET);

	    while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
		       > 0)
		fwrite (buffer, sizeof(*buffer), cc, fp);
	    fflush (fp);

	    if (ferror (gp)) {
		admonish (ce->ce_file, "error reading");
		unlink (cachefile);
	    }
	    else
		if (ferror (fp)) {
		    admonish (cachefile, "error writing");
		    unlink (cachefile);
		}
	    fclose (fp);
	}
	umask (mask);
    }

    fseek (ce->ce_fp, 0L, SEEK_SET);
    *file = ce->ce_file;
    return fileno (ce->ce_fp);
}

/*
 * FTP
 */

static int
InitFTP (CT ct)
{
    return init_encoding (ct, openFTP);
}


static int
openFTP (CT ct, char **file)
{
    int	cachetype, caching, fd;
    char *bp, *ftp, *user, *pass;
    char buffer[BUFSIZ], cachefile[BUFSIZ];
    struct exbody *e;
    CE ce;
    static char *username = NULL;
    static char *password = NULL;

    e  = ct->c_ctexbody;
    ce = ct->c_cefile;

    sprintf (buffer, "%s-access-ftp", invo_name);
    if ((ftp = context_find (buffer)) && !*ftp)
	ftp = NULL;

#ifndef BUILTIN_FTP
    if (!ftp)
	return NOTOK;
#endif

    switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
	case NOTOK:
	    return NOTOK;

	case OK:
	    break;

	case DONE:
	    return fd;
    }

    if (!e->eb_name || !e->eb_site) {
	content_error (NULL, ct, "missing %s parameter",
		       e->eb_name ? "site": "name");
	return NOTOK;
    }

    if (xpid) {
	if (xpid < 0)
	    xpid = -xpid;
	pidcheck (pidwait (xpid, NOTOK));
	xpid = 0;
    }

    bp = buffer;
    sprintf (bp, "Retrieve %s", e->eb_name);
    bp += strlen (bp);
    if (e->eb_partno) {
	sprintf (bp, " (content %s)", e->eb_partno);
	bp += strlen (bp);
    }
    sprintf (bp, "\n    using %sFTP from site %s",
		    e->eb_flags ? "anonymous " : "", e->eb_site);
    bp += strlen (bp);
    if (e->eb_size > 0) {
	sprintf (bp, " (%lu octets)", e->eb_size);
	bp += strlen (bp);
    }
    sprintf (bp, "? ");
    if (!getanswer (buffer))
	return NOTOK;

    if (e->eb_flags) {
	user = "anonymous";
	sprintf (pass = buffer, "%s@%s", getusr (), LocalName ());
    } else {
	ruserpass (e->eb_site, &username, &password);
	user = username;
	pass = password;
    }

    ce->ce_unlink = (*file == NULL);
    caching = 0;
    cachefile[0] = '\0';
    if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write"))
	    && find_cache (NULL, wcachesw, &cachetype,
			   e->eb_content->c_id, cachefile) != NOTOK) {
	if (*file == NULL) {
	    ce->ce_unlink = 0;
	    caching = 1;
	}
    }

    if (*file == NULL)
	ce->ce_file = add (*file, NULL);
    else if (caching)
	ce->ce_file = add (cachefile, NULL);
    else
	ce->ce_file = add (m_scratch ("", tmp), NULL);

    if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
	content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
	return NOTOK;
    }

#ifdef BUILTIN_FTP
    if (ftp)
#endif
    {
	int child_id, i, vecp;
	char *vec[9];

	vecp = 0;
	vec[vecp++] = r1bindex (ftp, '/');
	vec[vecp++] = e->eb_site;
	vec[vecp++] = user;
	vec[vecp++] = pass;
	vec[vecp++] = e->eb_dir;
	vec[vecp++] = e->eb_name;
	vec[vecp++] = ce->ce_file,
	vec[vecp++] = e->eb_mode && !strcasecmp (e->eb_mode, "ascii")
	    		? "ascii" : "binary";
	vec[vecp] = NULL;

	fflush (stdout);

	for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
	    sleep (5);
	switch (child_id) {
	    case NOTOK:
	        adios ("fork", "unable to");
		/* NOTREACHED */

	    case OK:
		close (fileno (ce->ce_fp));
		execvp (ftp, vec);
		fprintf (stderr, "unable to exec ");
		perror (ftp);
		_exit (-1);
		/* NOTREACHED */

	    default:
		if (pidXwait (child_id, NULL)) {
#ifdef BUILTIN_FTP
losing_ftp:
#endif
		    username = password = NULL;
		    ce->ce_unlink = 1;
		    return NOTOK;
		}
		break;
	}
    }
#ifdef BUILTIN_FTP
    else
	if (ftp_get (e->eb_site, user, pass, e->eb_dir, e->eb_name,
		     ce->ce_file,
		     e->eb_mode && !strcasecmp (e->eb_mode, "ascii"), 0)
	        == NOTOK)
	    goto losing_ftp;
#endif

    if (cachefile[0])
	if (caching)
	    chmod (cachefile, cachetype ? m_gmprot () : 0444);
	else {
	    int	mask;
	    FILE *fp;

	    mask = umask (cachetype ? ~m_gmprot () : 0222);
	    if ((fp = fopen (cachefile, "w"))) {
		int cc;
		FILE *gp = ce->ce_fp;

		fseek (gp, 0L, SEEK_SET);

		while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
		           > 0)
		    fwrite (buffer, sizeof(*buffer), cc, fp);
		fflush (fp);

		if (ferror (gp)) {
		    admonish (ce->ce_file, "error reading");
		    unlink (cachefile);
		}
		else
		    if (ferror (fp)) {
			admonish (cachefile, "error writing");
			unlink (cachefile);
		    }
		fclose (fp);
	    }
	    umask (mask);
	}

    fseek (ce->ce_fp, 0L, SEEK_SET);
    *file = ce->ce_file;
    return fileno (ce->ce_fp);
}


/*
 * Mail
 */

static int
InitMail (CT ct)
{
    return init_encoding (ct, openMail);
}


static int
openMail (CT ct, char **file)
{
    int	child_id, fd, i, vecp;
    char *bp, buffer[BUFSIZ], *vec[7];
    struct exbody *e = ct->c_ctexbody;
    CE ce = ct->c_cefile;

    switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
	case NOTOK:
	    return NOTOK;

	case OK:
	    break;

	case DONE:
	    return fd;
    }

    if (!e->eb_server) {
	content_error (NULL, ct, "missing server parameter");
	return NOTOK;
    }

    if (xpid) {
	if (xpid < 0)
	    xpid = -xpid;
	pidcheck (pidwait (xpid, NOTOK));
	xpid = 0;
    }

    bp = buffer;
    sprintf (bp, "Retrieve content");
    bp += strlen (bp);
    if (e->eb_partno) {
	sprintf (bp, " %s", e->eb_partno);
	bp += strlen (bp);
    }
    sprintf (bp, " by asking %s\n\n%s\n? ",
		    e->eb_server,
		    e->eb_subject ? e->eb_subject : e->eb_body);
    if (!getanswer (buffer))
	return NOTOK;

    vecp = 0;
    vec[vecp++] = r1bindex (mailproc, '/');
    vec[vecp++] = e->eb_server;
    vec[vecp++] = "-subject";
    vec[vecp++] = e->eb_subject ? e->eb_subject : "mail-server request";
    vec[vecp++] = "-body";
    vec[vecp++] = e->eb_body;
    vec[vecp] = NULL;

    for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
	sleep (5);
    switch (child_id) {
	case NOTOK:
	    advise ("fork", "unable to");
	    return NOTOK;

	case OK:
	    execvp (mailproc, vec);
	    fprintf (stderr, "unable to exec ");
	    perror (mailproc);
	    _exit (-1);
	    /* NOTREACHED */

	default:
	    if (pidXwait (child_id, NULL) == OK)
		advise (NULL, "request sent");
	    break;
    }

    if (*file == NULL) {
	ce->ce_file = add (m_scratch ("", tmp), NULL);
	ce->ce_unlink = 1;
    } else {
	ce->ce_file = add (*file, NULL);
	ce->ce_unlink = 0;
    }

    if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
	content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
	return NOTOK;
    }

    fseek (ce->ce_fp, 0L, SEEK_SET);
    *file = ce->ce_file;
    return fileno (ce->ce_fp);
}

/*
 * CACHE
 */

static int
find_cache (CT ct, int policy, int *writing, char *id, char *buffer)
{
    int	status = NOTOK;

    if (id == NULL)
	return NOTOK;
    id = trimcpy (id);

    if (debugsw)
	fprintf (stderr, "find_cache %s(%d) %s %s\n",
		 caches[policy].sw, policy, writing ? "writing" : "reading",
		 id);

    switch (policy) {
	case CACHE_NEVER:
	default:
	    break;

	case CACHE_ASK:
	case CACHE_PUBLIC:
	    if (cache_private
		    && !writing
		    && find_cache_aux (writing ? 2 : 0, cache_private, id,
				       buffer) == OK) {
		if (access (buffer, R_OK) != NOTOK) {
got_private:
		    if (writing)
			*writing = 1;
got_it:
		    status = OK;
		    break;
		}
	    }
	    if (cache_public
		    && find_cache_aux (writing ? 1 : 0, cache_public, id,
				       buffer) == OK) {
		if (writing || access (buffer, R_OK) != NOTOK) {
		    if (writing)
			*writing = 0;
		    goto got_it;
		}
	    }
	    break;

	case CACHE_PRIVATE:
	    if (cache_private
		    && find_cache_aux (writing ? 2 : 0, cache_private, id,
				       buffer) == OK) {
		if (writing || access (buffer, R_OK) != NOTOK)
		    goto got_private;
	    }
	    break;

    }

    if (status == OK && policy == CACHE_ASK) {
	char   *bp,
		query[BUFSIZ];

	if (xpid) {
	    if (xpid < 0)
		xpid = -xpid;
	    pidcheck (pidwait (xpid, NOTOK));
	    xpid = 0;
	}

	bp = query;
	if (writing)
	    sprintf (bp, "Make cached, publically-accessible copy");
	else {
	    struct stat st;

	    sprintf (bp, "Use cached copy");
	    bp += strlen (bp);
	    if (ct->c_partno) {
		sprintf (bp, " of content %s", ct->c_partno);
		bp += strlen (bp);
	    }
	    stat (buffer, &st);
	    sprintf (bp, " (size %lu octets)",
			    (unsigned long) st.st_size);
	}
	bp += strlen (bp);
	sprintf (bp, "\n    in file %s? ", buffer);
	if (!getanswer (query))
	    status = NOTOK;
    }
    if (status == OK && writing) {
	if (*writing && strchr(buffer, '/'))
	    make_intermediates (buffer);
	unlink (buffer);
    }

    free (id);
    return status;
}


static int
find_cache_aux (int writing, char *directory, char *id, char *buffer)
{
    int	mask, usemap;
    char mapfile[BUFSIZ], mapname[BUFSIZ];
    FILE *fp;
    static int partno, pid;
    static time_t clock = 0;

#ifdef BSD42
    usemap = strchr(id, '/') ? 1 : 0;
#else
    usemap = 1;
#endif

    if (debugsw)
	fprintf (stderr, "find_cache_aux %s usemap=%d\n", directory, usemap);

    sprintf (mapfile, "%s/cache.map", directory);
    if (find_cache_aux2 (mapfile, id, mapname) == OK)
	goto done_map;

    if (!writing) {
	if (usemap)
	    return NOTOK;

use_raw:
	sprintf (buffer, "%s/%s", directory, id);
	return OK;
    }

    if (!usemap && access (mapfile, W_OK) == NOTOK)
	goto use_raw;

    if (clock != 0) {
	time_t now;
	
	time (&now);
	if (now > clock)
	    clock = 0;
    }
    else
	pid = getpid ();
    if (clock == 0) {
	time (&clock);
	partno = 0;
    }
    else
	if (partno > 0xff) {
	    clock++;
	    partno = 0;
	}

    sprintf (mapname, "%08x%04x%02x",
		(unsigned int) (clock & 0xffffffff),
		(unsigned int) (pid & 0xffff),
		(unsigned int) (partno++ & 0xff));

    if (debugsw)
	fprintf (stderr, "creating mapping %s->%s\n", mapname, id);

    make_intermediates (mapfile);
    mask = umask (writing == 2 ? 0077 : 0);
    if (!(fp = lkfopen (mapfile, "a")) && errno == ENOENT) {
	int fd;

	if ((fd = creat (mapfile, 0666)) != NOTOK) {
	    close (fd);
	    fp = lkfopen (mapfile, "a");
	}
    }
    umask (mask);
    if (!fp)
	return NOTOK;
    fprintf (fp, "%s: %s\n", mapname, id);
    lkfclose (fp, mapfile);

done_map:
    if (*mapname == '/')
	strcpy (buffer, mapname);
    else
	sprintf (buffer, "%s/%s", directory, mapname);
    if (debugsw)
	fprintf (stderr, "use %s\n", buffer);

    return OK;
}


static int
find_cache_aux2 (char *mapfile, char *id, char *mapname)
{
    int	state;
    char buf[BUFSIZ], name[NAMESZ];
    FILE *fp;

    if (!(fp = lkfopen (mapfile, "r")))
	return NOTOK;

    for (state = FLD;;) {
	int result;
	char *cp, *dp;

	switch (state = m_getfld (state, name, buf, sizeof(buf), fp)) {
	    case FLD:
	    case FLDPLUS:
	    case FLDEOF:
	        strcpy (mapname, name);
		if (state != FLDPLUS)
		    cp = buf;
		else {
		    cp = add (buf, NULL);
		    while (state == FLDPLUS) {
			state = m_getfld (state, name, buf, sizeof(buf), fp);
			cp = add (buf, cp);
		    }
		}
		dp = trimcpy (cp);
		if (cp != buf)
		    free (cp);
		if (debugsw)
		    fprintf (stderr, "compare %s to %s <- %s\n", id, dp,
			     mapname);
		result = strcmp (id, dp);
		free (dp);
		if (result == 0) {
		    lkfclose (fp, mapfile);
		    return OK;
		}
		if (state != FLDEOF)
		    continue;
		/* else fall... */

	    case BODY:
	    case BODYEOF:
	    case FILEEOF:
	    default:
		break;
	}
	break;
    }

    lkfclose (fp, mapfile);
    return NOTOK;
}


/*
 * Main routine for translating composition file
 * into valid MIME message.
 */

static void
build_comp (char *file)
{
    int	compnum, state;
    char *cp, buf[BUFSIZ], name[NAMESZ], tmpfil[BUFSIZ];
    struct multipart *m;
    struct part **pp;
    CT ct;
    FILE *in, *out;

    /* open the composition draft */
    if (file[0] == '-' && file[1] == '\0')
	in = stdin;
    else if ((in = fopen (file, "r")) == NULL)
	adios (file, "unable to open for reading");

    umask (~m_gmprot ());

    strcpy (tmpfil, m_scratch (file, invo_name));
    if ((out = fopen (tmpfil, "w")) == NULL)
	adios (tmpfil, "unable to open for writing");
    free_file = tmpfil;

    /*
     * Scan through the header to the beginning of the body.
     */
    for (compnum = 1, state = FLD;;) {
	switch (state = m_getfld (state, name, buf, sizeof(buf), in)) {
	case FLD:
	case FLDPLUS:
	case FLDEOF:
	    compnum++;

	    /* abort if draft has Mime-Version header field */
	    if (!strcasecmp (name, VRSN_FIELD))
		adios (NULL, "draft shouldn't contain %s: field", VRSN_FIELD);

	    /* abort if draft has Content-Transfer-Encoding header field */
	    if (!strcasecmp (name, ENCODING_FIELD))
		adios (NULL, "draft shouldn't contain %s: field", ENCODING_FIELD);

	    /* ignore any Content-Type fields in the header */
	    if (!strcasecmp (name, TYPE_FIELD)) {
		while (state == FLDPLUS)
		    state = m_getfld (state, name, buf, sizeof(buf), in);
		goto finish_field;
	    }

	    /* output the header */
	    fprintf (out, "%s:%s", name, buf);
	    while (state == FLDPLUS) {
		state = m_getfld (state, name, buf, sizeof(buf), in);
		fputs (buf, out);
	    }
finish_field:
	    if (state != FLDEOF)
		continue;
	    /* else fall... */

	case FILEEOF:
	    adios (NULL, "draft has empty body -- no directives!");
	    /* NOTREACHED */

	case BODY:
	case BODYEOF:
	    fseek (in, (long) (-strlen (buf)), SEEK_CUR);
	    break;

	case LENERR:
	case FMTERR:
	    adios (NULL, "message format error in component #%d", compnum);

	default:
	    adios (NULL, "getfld() returned %d", state);
	}
	break;
    }

    /*
     * We initally assume we will find multiple contents in the
     * draft.  So create a multipart/mixed content to hold everything.
     * We can remove this later, if it is not needed.
     */
    if ((free_ct = ct = (CT) calloc (1, sizeof(*ct))) == NULL)
	adios (NULL, "out of memory");
    if (get_ctinfo ("multipart/mixed", ct, 0) == NOTOK)
	done (1);
    ct->c_type = CT_MULTIPART;
    ct->c_subtype = MULTI_MIXED;
    ct->c_ctlistfnx = list_multi;
    ct->c_ctfreefnx = free_multi;
    ct->c_file = add (file, NULL);

    if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
	adios (NULL, "out of memory");
    ct->c_ctparams = (void *) m;

    pp = &m->mp_parts;
    while (fgetstr (buf, sizeof(buf) - 1, in)) {
	struct part *part;
	CT p;

	if (user_content (in, file, buf, &p) == DONE) {
	    admonish (NULL, "ignoring spurious #end");
	    continue;
	}
	if (!p)
	    continue;

	if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
	    adios (NULL, "out of memory");
	*pp = part;
	pp = &part->mp_next;
	part->mp_part = p;
    }

    fclose (in);

    /* check if any contents were found */
    if (!m->mp_parts)
	adios (NULL, "no content directives found");

    /*
     * If only one content was found, then remove and
     * free the outer multipart content.
     */
    if (!m->mp_parts->mp_next) {
	CT p = m->mp_parts->mp_part;

	m->mp_parts->mp_part = NULL;
	free_content (ct);
	free_ct = ct = p;
    } else {
	set_id (ct, 1);
    }

    if ((cp = strchr(prefix, 'a')) == NULL)
	adios (NULL, "internal error(4)");

    while (compose_content (ct) == NOTOK) {
	if (*cp < 'z')
	    (*cp)++;
        else
	    if (*++cp == 0)
		adios (NULL, "giving up trying to find a unique delimiter string");
    	    else
		(*cp)++;
    }

    fprintf (out, "%s: %s\n", VRSN_FIELD, VRSN_VALUE);
    output_content (ct, out);

    if (fflush (out))
	adios (tmpfil, "error writing to");

    fclose (out);

    if (listsw && ct->c_ctlistfnx) {
	char *savfile;

	if (headsw)
	    printf (LSTFMT1, "msg", "part", "type/subtype", "size", "description");

				/* to get msgno */
	savfile = ct->c_file;
	ct->c_file = file;
	(*ct->c_ctlistfnx) (ct, 1);
	ct->c_file = savfile;
    }

    free_content (ct);
    free_ct = NULL;


    if (file[0] == '-' && file[1] == '\0') {
	/* Write tmpfile to stdout */
	if ((out = fopen(tmpfil, "r")) == NULL)
	    adios (NULL, "unable to open temp file %s", tmpfil);
	while (fgets(buf, BUFSIZ, out) != 0)
	    fputs(buf, stdout);
	unlink(tmpfil);
    } else {
	/* Rename composition draft and temp file */
	sprintf (buf, "%s.orig", m_backup (file));
	if (rename (file, buf) == NOTOK)
	    adios (buf, "unable to rename %s to", file);
	if (rename (tmpfil, file) == NOTOK) {
	    advise (file, "unable to rename %s to", tmpfil);
	    rename (buf, file);
	    done (1);
	}
    }

    free_file = NULL;
    done (0);
}


static char *
fgetstr (char *s, int n, FILE *stream)
{
    char *cp, *ep;

    for (ep = (cp = s) + n; cp < ep; ) {
	int i;

	if (!fgets (cp, n, stream))
	    return (cp != s ? s : NULL);
	if (cp == s && *cp != '#')
	    return s;

	cp += (i = strlen (cp)) - 1;
	if (i <= 1 || *cp-- != '\n' || *cp != '\\')
	    break;
	*cp = '\0';
	n -= (i - 2);
    }

    return s;
}


/*
 * Scan the contents and directives given
 * in the composition draft.
 */

static int
user_content (FILE *in, char *file, char *buf, CT *ctp)
{
    int	extrnal, vrsn;
    char *cp, **ap;
    char buffer[BUFSIZ];
    struct multipart *m;
    struct part **pp;
    struct stat st;
    struct str2init *s2i;
    CI ci;
    CT ct;

    if (buf[0] == '\n' || strcmp (buf, "#\n") == 0) {
	*ctp = NULL;
	return OK;
    }

    if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
	adios (NULL, "out of memory");
    *ctp = ct;
    ci = &ct->c_ctinfo;
    ct->c_ctlistfnx = list_content;
    set_id (ct, 0);

    /*
     * Handle plain text.  Check if line
     *
     * 1) doesn't begin with '#'   (or)
     * 2) begins with "##"         (or)
     * 3) begins with "#<"
     */
    if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
	int headers;
	int inlineD;
	long pos;
	char content[BUFSIZ];
	FILE *out;

	/* use a temp file to collect the plain text lines */
	ct->c_file = add (m_tmpfil (invo_name), NULL);
	ct->c_unlink = 1;

	if ((out = fopen (ct->c_file, "w")) == NULL)
	    adios (ct->c_file, "unable to open for writing");

	if (buf[0] == '#' && buf[1] == '<') {
	    strcpy (content, buf + 2);
	    inlineD = 1;
	    goto rock_and_roll;
	} else {
	    inlineD = 0;
	}

	strcpy (content, "text/plain");
	headers = 0;
	strcpy (buffer, buf[0] != '#' ? buf : buf + 1);
	for (;;) {
	    int	i;

	    if (headers >= 0 && uprf (buffer, DESCR_FIELD)
		&& buffer[i = strlen (DESCR_FIELD)] == ':') {
		headers = 1;

again_descr:
		ct->c_descr = add (buffer + i + 1, ct->c_descr);
		if (!fgetstr (buffer, sizeof(buffer) - 1, in))
		    adios (NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
		switch (buffer[0]) {
		case ' ':
		case '\t':
		    i = -1;
		    goto again_descr;

		case '#':
		    adios (NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
		    /* NOTREACHED */

		default:
		    break;
		}
	    }

	    if (headers != 1 || buffer[0] != '\n')
		fputs (buffer, out);

rock_and_roll:
	    headers = -1;
	    pos = ftell (in);
	    if ((cp = fgetstr (buffer, sizeof(buffer) - 1, in)) == NULL)
		break;
	    if (buffer[0] == '#') {
		char *bp;

		if (buffer[1] != '#')
		    break;
		for (cp = (bp = buffer) + 1; *cp; cp++)
		    *bp++ = *cp;
		*bp = '\0';
	    }
	}

	if (listsw)
	    ct->c_end = ftell (out);
	fclose (out);

	if (get_ctinfo (content, ct, inlineD) == NOTOK)
	    done (1);
	for (s2i = str2cts; s2i->si_key; s2i++)
	    if (!strcasecmp (ci->ci_type, s2i->si_key))
		break;
	if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
	    s2i++;

	switch (ct->c_type = s2i->si_val) {
	case CT_MESSAGE:
	    if (!strcasecmp (ci->ci_subtype, "rfc822")) {
		ct->c_encoding = CE_7BIT;
		goto call_init;
	    }
	    /* else fall... */
	case CT_MULTIPART:
	    adios (NULL, "it doesn't make sense to define an in-line %s content",
		   ct->c_type == CT_MESSAGE ? "message" : "multipart");
	    /* NOTREACHED */

	default:
call_init:
	    if ((ct->c_ctinitfnx = s2i->si_init))
		(*ct->c_ctinitfnx) (ct);
	    break;
	}

	if (cp)
	    fseek (in, pos, SEEK_SET);
	return OK;
    }

    /*
     * If we've reached this point, the next
     * line must be some type of directive.
     */

    /* check if directive is external-type */
    extrnal = (buf[1] == '@');

    /* parse directive */
    if (get_ctinfo (buf + (extrnal ? 2 : 1), ct, 1) == NOTOK)
	done (1);

    /* check directive against the list of MIME types */
    for (s2i = str2cts; s2i->si_key; s2i++)
	if (!strcasecmp (ci->ci_type, s2i->si_key))
	    break;

    /*
     * check if directive was of the form:
     *    #type/subtype  (or)
     *    #@type/subtype
     */
    if (s2i->si_key) {
	if (!ci->ci_subtype)
	    adios (NULL, "missing subtype in \"#%s\"", ci->ci_type);

	switch (ct->c_type = s2i->si_val) {
	case CT_MULTIPART:
	    adios (NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"",
		   ci->ci_type, ci->ci_subtype);
	    /* NOTREACHED */

	case CT_MESSAGE:
	    if (!strcasecmp (ci->ci_subtype, "partial"))
		adios (NULL, "sorry, \"#%s/%s\" isn't supported",
		       ci->ci_type, ci->ci_subtype);
	    if (!strcasecmp (ci->ci_subtype, "external-body"))
		adios (NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
		       ci->ci_type, ci->ci_subtype);
use_forw:
	    adios (NULL,
		   "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
		   ci->ci_type, ci->ci_subtype);
	    /* NOTREACHED */

	default:
	    if ((ct->c_ctinitfnx = s2i->si_init))
		(*ct->c_ctinitfnx) (ct);
	    break;
	}

	/*
	 * #@type/subtype (external types directive)
	 */
	if (extrnal) {
	    struct exbody *e;
	    CT p;

	    if (!ci->ci_magic)
		adios (NULL, "need external information for \"#@%s/%s\"",
		       ci->ci_type, ci->ci_subtype);
	    p = ct;

	    sprintf (buffer, "message/external-body; %s", ci->ci_magic);
	    free (ci->ci_magic);
	    ci->ci_magic = NULL;

	    if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
		adios (NULL, "out of memory");
	    *ctp = ct;
	    ci = &ct->c_ctinfo;
	    ct->c_ctlistfnx = list_content;
	    if (get_ctinfo (buffer, ct, 0) == NOTOK)
		done (1);
	    ct->c_type = CT_MESSAGE;
	    ct->c_subtype = MESSAGE_EXTERNAL;

	    if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
		adios (NULL, "out of memory");
	    ct->c_ctparams = (void *) e;
	    ct->c_ctfreefnx = free_external;

	    e->eb_parent = ct;
	    e->eb_content = p;
	    p->c_ctexbody = e;

	    ct->c_ctlistfnx = list_external;

	    if (params_external (ct, 1) == NOTOK)
		done (1);

	    return OK;
	}

	/* Handle [file] argument */
	if (ci->ci_magic) {
	    if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
		for (cp = ci->ci_magic + 1; isspace (*cp); cp++)
		    continue;
		if (!*cp)
		    adios (NULL, "empty pipe command for #%s directive", ci->ci_type);
		cp = add (cp, NULL);
		free (ci->ci_magic);
		ci->ci_magic = cp;
	    } else {
		if (access (ct->c_file = ci->ci_magic, R_OK) == NOTOK)
		    adios ("reading", "unable to access %s for", ct->c_file);
		if (listsw && stat (ct->c_file, &st) != NOTOK)
		    ct->c_end = (long) st.st_size;
		ci->ci_magic = NULL;
	    }
	    return OK;
	}

	/*
	 * No [file] argument, so check profile for
	 * method to compose content.
	 */
	sprintf (buffer, "%s-compose-%s/%s", invo_name, ci->ci_type, ci->ci_subtype);
	if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
	    sprintf (buffer, "%s-compose-%s", invo_name, ci->ci_type);
	    if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
		content_error (NULL, ct, "don't know how to compose content");
		done (1);
	    }
	}
	ci->ci_magic = add (cp, NULL);
	return OK;
    }

    if (extrnal)
	adios (NULL, "externally definition not allowed for \"#%s\"", ci->ci_type);

    /*
     * Message directive
     * #forw [+folder] [msgs]
     */
    if (!strcasecmp (ci->ci_type, "forw")) {
	int msgnum;
	char *folder, *arguments[MAXARGS];
	struct msgs *mp;

	if (ci->ci_magic) {
	    ap = brkstring (ci->ci_magic, " ", "\n");
	    ap = copyip (ap, arguments);
	} else {
	    arguments[0] = "cur";
	    arguments[1] = NULL;
	}

	folder = NULL;
	for (ap = arguments; *ap; ap++) {
	    cp = *ap;
	    if (*cp == '+' || *cp == '@')
		if (folder)
		    adios (NULL, "only one folder per #forw directive");
		else
		    folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF);
	}
	if (!folder)
	    folder = add (getfolder (1), NULL);

	if (!(mp = folder_read (folder)))
	    adios (NULL, "unable to read folder %s", folder);
	for (ap = arguments; *ap; ap++) {
	    cp = *ap;
	    if (*cp != '+' && *cp != '@')
		if (!m_convert (mp, cp))
		    done (1);
	}
	free (folder);
	free_ctinfo (ct);

	/*
	 * If there is more than one message to include, make this
	 * a content of type "multipart/digest" and insert each message
	 * as a subpart.  If there is only one message, then make this
	 * a content of type "message/rfc822".
	 */
	if (mp->numsel > 1) {
	    if (get_ctinfo ("multipart/digest", ct, 0) == NOTOK)
		done (1);
	    ct->c_type = CT_MULTIPART;
	    ct->c_subtype = MULTI_DIGEST;
	    ct->c_ctlistfnx = list_multi;
	    ct->c_ctfreefnx = free_multi;

	    if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
		adios (NULL, "out of memory");
	    ct->c_ctparams = (void *) m;
	    pp = &m->mp_parts;

	    for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
		if (is_selected(mp, msgnum)) {
		    struct part *part;
		    CT p;

		    if ((p = (CT) calloc (1, sizeof(*p))) == NULL)
			adios (NULL, "out of memory");
		    if (get_ctinfo ("message/rfc822", p, 0) == NOTOK)
			done (1);
		    p->c_type = CT_MESSAGE;
		    p->c_subtype = MESSAGE_RFC822;
		    p->c_ctlistfnx = list_content;

		    sprintf (buffer, "%s/%d", mp->foldpath, msgnum);
		    p->c_file = add (buffer, NULL);
		    if (listsw && stat (p->c_file, &st) != NOTOK)
			p->c_end = (long) st.st_size;

		    if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
			adios (NULL, "out of memory");
		    *pp = part;
		    pp = &part->mp_next;
		    part->mp_part = p;
		}
	    }
	} else {
	    if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
		done (1);
	    ct->c_type = CT_MESSAGE;
	    ct->c_subtype = MESSAGE_RFC822;
	    ct->c_ctlistfnx = list_content;

	    msgnum = mp->lowsel;
	    sprintf (buffer, "%s/%d", mp->foldpath, msgnum);
	    ct->c_file = add (buffer, NULL);
	    if (listsw && stat (ct->c_file, &st) != NOTOK)
		ct->c_end = (long) st.st_size;
	}

	folder_free (mp);	/* free folder/message structure */
	return OK;
    }

    /*
     * #end
     */
    if (!strcasecmp (ci->ci_type, "end")) {
	free_content (ct);
	*ctp = NULL;
	return DONE;
    }

    /*
     * #begin [ alternative | parallel ]
     */
    if (!strcasecmp (ci->ci_type, "begin")) {
	if (!ci->ci_magic) {
	    vrsn = MULTI_MIXED;
	    cp = SubMultiPart[vrsn - 1].kv_key;
	} else if (!strcasecmp (ci->ci_magic, "alternative")) {
	    vrsn = MULTI_ALTERNATE;
	    cp = SubMultiPart[vrsn - 1].kv_key;
	} else if (!strcasecmp (ci->ci_magic, "parallel")) {
	    vrsn = MULTI_PARALLEL;
	    cp = SubMultiPart[vrsn - 1].kv_key;
	} else if (uprf (ci->ci_magic, "digest")) {
	    goto use_forw;
	} else {
	    vrsn = MULTI_UNKNOWN;
	    cp = ci->ci_magic;
	}

	free_ctinfo (ct);
	sprintf (buffer, "multipart/%s", cp);
	if (get_ctinfo (buffer, ct, 0) == NOTOK)
	    done (1);
	ct->c_type = CT_MULTIPART;
	ct->c_subtype = vrsn;
	ct->c_ctlistfnx = list_multi;
	ct->c_ctfreefnx = free_multi;

	if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
	    adios (NULL, "out of memory");
	ct->c_ctparams = (void *) m;

	pp = &m->mp_parts;
	while (fgetstr (buffer, sizeof(buffer) - 1, in)) {
	    struct part *part;
	    CT p;

	    if (user_content (in, file, buffer, &p) == DONE) {
		if (!m->mp_parts)
		    adios (NULL, "empty \"#begin ... #end\" sequence");
		return OK;
	    }
	    if (!p)
		continue;

	    if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
		adios (NULL, "out of memory");
	    *pp = part;
	    pp = &part->mp_next;
	    part->mp_part = p;
	}
	admonish (NULL, "premature end-of-file, missing #end");
	return OK;
    }

    /*
     * Unknown directive
     */
    adios (NULL, "unknown directive \"#%s\"", ci->ci_type);
}


static void
set_id (CT ct, int top)
{
    char msgid[BUFSIZ];
    static int partno;
    static time_t clock = 0;
    static char *msgfmt;

    if (clock == 0) {
	time (&clock);
	sprintf (msgid, "<%d.%ld.%%d@%s>\n",
		(int) getpid(), (long) clock, LocalName());
	partno = 0;
	msgfmt = getcpy(msgid);
    }
    sprintf (msgid, msgfmt, top ? 0 : ++partno);
    ct->c_id = getcpy (msgid);
}


static char ebcdicsafe[0x100] = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
    0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};


/*
 * Fill out, or expand the various contents in the composition
 * draft.  Read-in any necessary files.  Parse and execute any
 * commands specified the profile composition strings.
 */

static int
compose_content (CT ct)
{
    char *cp;
    char buffer[BUFSIZ];
    CI ci = &ct->c_ctinfo;

    switch (ct->c_type) {
    case CT_MULTIPART:
    {
	int partnum;
	char *pp;
	char partnam[BUFSIZ];
	struct multipart *m = (struct multipart *) ct->c_ctparams;
	struct part *part;

	if (ct->c_partno) {
	    sprintf (partnam, "%s.", ct->c_partno);
	    pp = partnam + strlen (partnam);
	} else {
	    pp = partnam;
	}

	for (part = m->mp_parts, partnum = 1; part; part = part->mp_next, partnum++) {
	    CT p = part->mp_part;

	    sprintf (pp, "%d", partnum);
	    p->c_partno = add (partnam, NULL);
	    if (compose_content (p) == NOTOK)
		return NOTOK;
	}

	/*
	 * If the -rfc934mode switch is given, then check all
	 * the subparts of a multipart/digest.  If they are all
	 * message/rfc822, then mark this content and all
	 * subparts with the rfc934 compatibility mode flag.
	 */
	if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
	    int	is934 = 1;

	    for (part = m->mp_parts; part; part = part->mp_next) {
		CT p = part->mp_part;

		if (p->c_subtype != MESSAGE_RFC822) {
		    is934 = 0;
		    break;
		}
	    }
	    ct->c_rfc934 = is934;
	    for (part = m->mp_parts; part; part = part->mp_next) {
		CT p = part->mp_part;

		if ((p->c_rfc934 = is934))
		    p->c_end++;
	    }
	}

	if (listsw) {
	    ct->c_end = (partnum = strlen (prefix) + 2) + 2;
	    if (ct->c_rfc934)
		ct->c_end += 1;

	    for (part = m->mp_parts; part; part = part->mp_next)
		ct->c_end += part->mp_part->c_end + partnum;
	}
    }
    break;

    default:
	/*
	 * discrete types (text/application/audio/image/video)
	 */
	if (!ct->c_file) {
	    pid_t child_id;
	    int i, xstdout;
	    char *bp, **ap;
	    char *vec[4];
	    FILE *out;

	    if (!(cp = ci->ci_magic))
		adios (NULL, "internal error(5)");

	    ct->c_file = add (m_tmpfil (invo_name), NULL);
	    ct->c_unlink = 1;

	    xstdout = 0;
	    buffer[0] = '\0';

	    /*
	     * Parse composition string
	     */
	    for (bp = buffer; *cp; cp++)
		if (*cp == '%') {
		    switch (*++cp) {
		    case 'a':
		    {
			/* insert parameters from directive */
			char **ep;
			char *s = "";

			for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
			    sprintf (bp, "%s%s=\"%s\"", s, *ap, *ep);
			    bp += strlen (bp);
			    s = " ";
			}
		    }
		    break;

		    case 'F':
			/* %f, and stdout is not-redirected */
			xstdout = 1;
			/* and fall... */

		    case 'f':
			/*
			 * insert temporary filename where
			 * content should be written
			 */
			sprintf (bp, "%s", ct->c_file);
			break;

		    case 's':
			/* insert content subtype */
			strcpy (bp, ci->ci_subtype);
			break;

		    case '%':
			/* insert character % */
			goto raw;

		    default:
			*bp++ = *--cp;
			*bp = '\0';
			continue;
		    }
		    bp += strlen (bp);
		}
		else {
raw:
		*bp++ = *cp;
		*bp = '\0';
		}

	    if (verbosw)
		printf ("composing content %s/%s from command\n\t%s\n",
			ci->ci_type, ci->ci_subtype, buffer);

	    fflush (stdout);	/* not sure if need for -noverbose */

	    vec[0] = "/bin/sh";
	    vec[1] = "-c";
	    vec[2] = buffer;
	    vec[3] = NULL;

	    if ((out = fopen (ct->c_file, "w")) == NULL)
		adios (ct->c_file, "unable to open for writing");

	    for (i = 0; (child_id = vfork()) == NOTOK && i > 5; i++)
		sleep (5);
	    switch (child_id) {
	    case NOTOK:
		adios ("fork", "unable to fork");
		/* NOTREACHED */

	    case OK:
		if (!xstdout)
		    dup2 (fileno (out), 1);
		close (fileno (out));
		execvp ("/bin/sh", vec);
		fprintf (stderr, "unable to exec ");
		perror ("/bin/sh");
		_exit (-1);
		/* NOTREACHED */

	    default:
		fclose (out);
		if (pidXwait(child_id, NULL))
		    done (1);
		break;
	    }
	}
	if (listsw && ct->c_end == 0L) {
	    struct stat st;

	    if (stat (ct->c_file, &st) != NOTOK)
		ct->c_end = (long) st.st_size;
	}

	/* For type audio, image, or video */
	if (ct->c_type != CT_TEXT && ct->c_type != CT_APPLICATION) {
	    ct->c_encoding = CE_BASE64;
	    break;
	}
	/* else fall... */

    case CT_MESSAGE:
    {
	int check8bit, contains8bit = 0;
	int len, linelen, result;
	struct text *t;
	FILE *in;

	if (ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_EXTERNAL)
	    return OK;

	if ((in = fopen (ct->c_file, "r")) == NULL)
	    adios (ct->c_file, "unable to open for reading");

	len = strlen (prefix);
	result = OK;

	switch (ct->c_type) {
	case CT_TEXT:
	    t = (struct text *) ct->c_ctparams;
	    check8bit = (t->tx_charset == CHARSET_UNSPECIFIED) ? 1 : 0;
	    linelen = (ct->c_subtype == TEXT_PLAIN) ? 0 : -1;
	    break;

	case CT_APPLICATION:
	    check8bit = ct->c_encoding ? 0 : 1;
	    linelen = ct->c_encoding ? 0 : -1;
	    break;

	default:
	    check8bit = 0;
	    linelen = 0;
	    break;
	}

	/* Scan the content */
	while (fgets (buffer, sizeof(buffer) - 1, in)) {
	    if (check8bit) {
		for (cp = buffer; *cp; cp++) {
		    /* check for 8bit characters in content */
		    if (!isascii (*cp)) {
			contains8bit = 1;
			check8bit = 0;		/* no need to keep checking */
			break;
		    }
		    if (linelen == -1 && ebcdicsw && !ebcdicsafe[*cp & 0xff])
			linelen = 1;
		}
		/* check line length */
		if ((linelen == -1) && (cp - buffer > CPERLIN + 1))
		    linelen = 1;
		if (result == NOTOK)
		    break;
	    } else {
		/* check line length */
		if ((linelen == -1) && (strlen (buffer) > CPERLIN + 1))
		    linelen = 1;
	    }
	    if (result == NOTOK)
		continue;

	    /* Check if line ends with a space */
	    if (linelen == -1
		&& (cp = buffer + strlen (buffer) - 2) > buffer && isspace (*cp))
		linelen = 1;

	    /*
	     * Check if content contains a line that clashes
	     * with our standard boundary for multipart messages.
	     */
	    if (buffer[0] == '-' && buffer[1] == '-') {
		for (cp = buffer + strlen (buffer) - 1; cp >= buffer; cp--)
		    if (!isspace (*cp))
			break;
		*++cp = '\0';
		if (!strncmp(buffer + 2, prefix, len) && isdigit(buffer[2 + len])) {
		    result = NOTOK;
		    if (check8bit != 1 && linelen != -1)
			break;
		}
	    }
	}

	if (ct->c_type == CT_TEXT) {
	    /*
	     * If the text content didn't specify a character
	     * set, we need to figure out which one was used.
	     */
	    if (t->tx_charset == CHARSET_UNSPECIFIED) {
		char **ap, **ep;

		for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
		    continue;

		if (contains8bit) {
		    t->tx_charset = CHARSET_UNKNOWN;
		    *ap = concat ("charset=", write_charset_8bit(), NULL);
		} else {
		    t->tx_charset = CHARSET_USASCII;
		    *ap = add ("charset=us-ascii", NULL);
		}

		cp = strchr(*ap++, '=');
		*ap = NULL;
		*cp++ = '\0';
		*ep = cp;
	    }

	    if (ct->c_subtype == TEXT_PLAIN)
		ct->c_encoding = (t->tx_charset != CHARSET_USASCII || checksw)
			? CE_QUOTED : CE_7BIT;
	    else
		ct->c_encoding = (t->tx_charset != CHARSET_USASCII || checksw
			|| linelen != -1) ? CE_QUOTED : CE_7BIT;
	}

	if (ct->c_type == CT_APPLICATION && !ct->c_encoding) {
	    if (contains8bit || linelen != -1 || checksw)
		ct->c_encoding = (ct->c_subtype == APPLICATION_POSTSCRIPT)
			? CE_QUOTED : CE_BASE64;
	    else
		ct->c_encoding = CE_7BIT;
	}

	fclose (in);
	return result;
    }
    }

    return OK;
}


/*
 * Main routine to output the new MIME message.
 */

static int
output_content (CT ct, FILE *out)
{
    int	cc, mailbody, len;
    char **ap, **ep;
    char buffer[BUFSIZ];
    CI ci = &ct->c_ctinfo;

    /*
     * If message is type multipart, then add the multipart
     * boundary to the list of attribute/value pairs.
     */
    if (ct->c_type == CT_MULTIPART) {
	char *cp;
	static int level = 0;	/* store nesting level */

	ap = ci->ci_attrs;
	ep = ci->ci_values;
	sprintf (buffer, "boundary=%s%d", prefix, level++);
	cp = strchr(*ap++ = add (buffer, NULL), '=');
	*ap = NULL;
	*cp++ = '\0';
	*ep = cp;
    }

    /*
     * Skip the printing of content type, parameters, content
     * description, and content id if the content is of type
     * "message" and the rfc934 compatibility flag is set
     * (which means we are inside multipart/digest and the
     * switch -rfc934mode was given).
     */
    if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
	goto rfc934_mode;

    len = 0;

    /*
     * output the content type and subtype
     */
    fprintf (out, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype);
    len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type)
	+ 1 + strlen (ci->ci_subtype);
    mailbody = ct->c_type == CT_MESSAGE
	&& ct->c_subtype == MESSAGE_EXTERNAL
	&& ((struct exbody *) ct->c_ctparams)->eb_body;

    /*
     * output the attribute/value pairs
     */
    for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
	if (mailbody && !strcasecmp (*ap, "body"))
	    continue;

	putc (';', out);
	len++;
	sprintf (buffer, "%s=\"%s\"", *ap, *ep);

	if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
	    fputs ("\n\t", out);
	    len = 8;
	} else {
	    putc (' ', out);
	    len++;
	}
	fputs (buffer, out);
	len += cc;
    }

    if (ci->ci_comment) {
	if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
	    fputs ("\n\t", out);
	    len = 8;
	} else {
	    putc (' ', out);
	    len++;
	}
	fprintf (out, "(%s)", ci->ci_comment);
	len += cc;
    }
    putc ('\n', out);

    /*
     * output the Content-ID
     */
    if (ct->c_id)
	fprintf (out, "%s: %s", ID_FIELD, ct->c_id);

    /*
     * output the Content-Description
     */
    if (ct->c_descr)
	fprintf (out, "%s: %s", DESCR_FIELD, ct->c_descr);

rfc934_mode:
    if (ct->c_ctexbody)
	return OK;

    switch (ct->c_type) {
    case CT_MULTIPART:
    {
	struct multipart *m = (struct multipart *) ct->c_ctparams;
	struct part *part;

	if (ct->c_rfc934)
	    putc ('\n', out);

	for (part = m->mp_parts; part; part = part->mp_next) {
	    CT p = part->mp_part;

	    fprintf (out, "\n--%s\n", ci->ci_values[0]);
	    output_content (p, out);
	}
	fprintf (out, "\n--%s--\n", ci->ci_values[0]);
    }
    break;

    case CT_MESSAGE:
	putc ('\n', out);
	if (ct->c_subtype != MESSAGE_EXTERNAL) {
	    write7Bit (ct, out);
	} else {
	    struct exbody *e = (struct exbody *) ct->c_ctparams;
		
	    output_content (e->eb_content, out);
	    if (e->eb_body) {
		char *cp;

		putc ('\n', out);
		for (cp = e->eb_body; *cp; cp++) {
		    CT ct2 = e->eb_content;
		    CI ci2 = &ct2->c_ctinfo;

		    if (*cp == '\\')
			switch (*++cp) {
			case 'I':
			    if (ct2->c_id) {
				char *dp = trimcpy (ct2->c_id);
					
				fputs (dp, out);
				free (dp);
			    }
			    continue;

			case 'N':
			    for (ap = ci2->ci_attrs, ep = ci2->ci_values; *ap; ap++, ep++)
				if (!strcasecmp (*ap, "name")) {
				    fprintf (out, "%s", *ep);
				    break;
				}
			    continue;
					    
			case 'T':
			    fprintf (out, "%s/%s", ci2->ci_type, ci2->ci_subtype);
			    for (ap = ci2->ci_attrs, ep = ci2->ci_values; *ap; ap++, ep++)
				fprintf (out, "; %s=\"%s\"", *ap, *ep);
			    continue;

			case 'n':
			    putc ('\n', out);
			    continue;

			case 't':
			    putc ('\t', out);
			    continue;

			case '\0':
			    cp--;
			    break;

			case '\\':
			case '"':
			    break;

			default:
			    putc ('\\', out);
			    break;
			}

		    putc (*cp, out);
		}
		putc ('\n', out);
	    }
	}
	break;

    case CT_TEXT:
    case CT_APPLICATION:
    default:
	switch (ct->c_encoding) {
	case CE_7BIT:
	    putc ('\n', out);
	    write7Bit (ct, out);
	    break;
	case CE_QUOTED:
	    if (checksw)
		writeDigest (ct, out, 1);
	    fprintf (out, "%s: %s\n\n", ENCODING_FIELD, "quoted-printable");
	    writeQuoted (ct, out);
	    break;
	default:
	    if (checksw)
		writeDigest (ct, out, 0);
	    fprintf (out, "%s: %s\n\n", ENCODING_FIELD, "base64");
	    writeBase64 (ct, out);
	    break;
	}
	break;
    }

    return OK;
}


static int
write7Bit (CT ct, FILE *out)
{
    char c, buffer[BUFSIZ];
    FILE *in;

    if ((in = fopen (ct->c_file, "r")) == NULL)
	adios (ct->c_file, "unable to open for reading");

    c = '\n';
    while (fgets (buffer, sizeof(buffer) - 1, in)) {
	c = buffer[strlen (buffer) - 1];
	fputs (buffer, out);
    }
    if (c != '\n')
	putc ('\n', out);

    fclose (in);
    return OK;
}


static int
writeQuoted (CT ct, FILE *out)
{
    char *cp;
    char c, buffer[BUFSIZ];
    FILE *in;

    if ((in = fopen (ct->c_file, "r")) == NULL)
	adios (ct->c_file, "unable to open for reading");

    while (fgets (buffer, sizeof(buffer) - 1, in)) {
	int n;

	cp = buffer + strlen (buffer) - 1;
	if ((c = *cp) == '\n')
	    *cp = '\0';

	if (strncmp (cp = buffer, "From ", sizeof("From ") - 1) == 0) {
	    fprintf (out, "=%02X", *cp++ & 0xff);
	    n = 3;
	} else {
	    n = 0;
	}
	for (; *cp; cp++) {
	    if (n > CPERLIN - 3) {
		fputs ("=\n", out);
		n = 0;
	    }

	    switch (*cp) {
		case ' ':
		case '\t':
		    putc (*cp, out);
		    n++;
		    break;

		default:
		    if (*cp < '!' || *cp > '~'
			    || (ebcdicsw && !ebcdicsafe[*cp & 0xff]))
			goto three_print;
		    putc (*cp, out);
		    n++;
		    break;

		case '=':
three_print:
		    fprintf (out, "=%02X", *cp & 0xff);
		    n += 3;
		    break;
	    }
	}

	if (c == '\n') {
	    if (cp > buffer && (*--cp == ' ' || *cp == '\t'))
		fputs ("=\n", out);

	    putc ('\n', out);
	} else {
	    fputs ("=\n", out);
	}
    }

    fclose (in);

    return OK;
}


static char nib2b64[0x40+1] =
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";


static int
writeBase64 (CT ct, FILE *out)
{
    int	result;
    FILE *in;

    if ((in = fopen (ct->c_file, "r")) == NULL)
	adios (ct->c_file, "unable to open for reading");

    result = writeBase64aux (in, out);
    fclose (in);
    return result;
}


static int
writeBase64aux (FILE *in, FILE *out)
{
    int	cc, n;
    char inbuf[3];

    n = BPERLIN;
    while ((cc = fread (inbuf, sizeof(*inbuf), sizeof(inbuf), in)) > 0) {
	unsigned long bits;
	char *bp;
	char outbuf[4];

	if (cc < sizeof(inbuf)) {
	    inbuf[2] = 0;
	    if (cc < sizeof(inbuf) - 1)
		inbuf[1] = 0;
	}
	bits = (inbuf[0] & 0xff) << 16;
	bits |= (inbuf[1] & 0xff) << 8;
	bits |= inbuf[2] & 0xff;

	for (bp = outbuf + sizeof(outbuf); bp > outbuf; bits >>= 6)
	    *--bp = nib2b64[bits & 0x3f];
	if (cc < sizeof(inbuf)) {
	    outbuf[3] = '=';
	    if (cc < sizeof inbuf - 1)
		outbuf[2] = '=';
	}

	fwrite (outbuf, sizeof(*outbuf), sizeof(outbuf), out);

	if (cc < sizeof(inbuf)) {
	    putc ('\n', out);
	    return OK;
	}

	if (--n <= 0) {
	    n = BPERLIN;
	    putc ('\n', out);
	}
    }
    if (n != BPERLIN)
	putc ('\n', out);

    return OK;
}


static void
writeDigest (CT ct, FILE *out, int asciiP)
{
    int	cc;
    char buffer[BUFSIZ];
    unsigned char *dp;
    unsigned char digest[16];
    FILE *in;
    MD5_CTX mdContext;

    if ((in = fopen (ct->c_file, "r")) == NULL)
	adios (ct->c_file, "unable to open for reading");

    MD5Init (&mdContext);
    if (asciiP) {
	while (fgets (buffer, sizeof(buffer) - 1, in)) {
	    char c, *cp;

	    cp = buffer + strlen (buffer) - 1;
	    if ((c = *cp) == '\n')
		*cp = '\0';

	    MD5Update (&mdContext, (unsigned char *) buffer,
		       (unsigned int) strlen (buffer));

	    if (c == '\n')
		MD5Update (&mdContext, (unsigned char *) "\r\n", 2);
	}
    }
    else
	while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), in)) > 0)
	    MD5Update (&mdContext, (unsigned char *) buffer,
		       (unsigned int) cc);
    MD5Final (digest, &mdContext);
    if (debugsw) {
	unsigned char *ep;

	fprintf (stderr, "MD5 digest=");
	for (ep = (dp = digest) + sizeof digest / sizeof digest[0];
	         dp < ep;
	         dp++)
	    fprintf (stderr, "%02x", *dp & 0xff);
	fprintf (stderr, "\n");
    }

    fclose (in);

    fprintf (out, "%s: ", MD5_FIELD);
    for (dp = digest, cc = sizeof digest / sizeof digest[0]; cc > 0; cc -= 3) {
	unsigned long bits;
	char *bp;
	char outbuf[4];

	bits = (*dp++ & 0xff) << 16;
	if (cc > 1) {
	    bits |= (*dp++ & 0xff) << 8;
	    if (cc > 2)
		bits |= *dp++ & 0xff;
	}

	for (bp = outbuf + sizeof(outbuf); bp > outbuf; bits >>= 6)
	    *--bp = nib2b64[bits & 0x3f];
	if (cc < 3) {
	    outbuf[3] = '=';
	    if (cc < 2)
		outbuf[2] = '=';
	}

        fwrite (outbuf, sizeof(*outbuf), sizeof(outbuf), out);
    }
    fprintf (out, "\n");
}


static int
readDigest (CT ct, char *cp)
{
    int	bitno, skip;
    unsigned long bits;
    char *bp = cp;
    unsigned char *dp, value, *ep;
    unsigned char *b, *b1, *b2, *b3;

    b  = (unsigned char *) &bits,
    b1 = &b[endian > 0 ? 1 : 2],
    b2 = &b[endian > 0 ? 2 : 1],
    b3 = &b[endian > 0 ? 3 : 0];
    bitno = 18;
    bits = 0L;
    skip = 0;

    for (ep = (dp = ct->c_digest)
	         + sizeof(ct->c_digest) / sizeof(ct->c_digest[0]); *cp; cp++)
	switch (*cp) {
	    default:
	        if (skip
		        || (*cp & 0x80)
		        || (value = b642nib[*cp & 0x7f]) > 0x3f) {
		    if (debugsw)
			fprintf (stderr, "invalid BASE64 encoding\n");
		    return NOTOK;
		}

		bits |= value << bitno;
test_end:
		if ((bitno -= 6) < 0) {
		    if (dp + (3 - skip) > ep)
			goto invalid_digest;
		    *dp++ = *b1;
		    if (skip < 2) {
			*dp++ = *b2;
			if (skip < 1)
			    *dp++ = *b3;
		    }
		    bitno = 18;
		    bits = 0L;
		    skip = 0;
		}
		break;

	    case '=':
		if (++skip > 3)
		    goto self_delimiting;
		goto test_end;
	}
    if (bitno != 18) {
	if (debugsw)
	    fprintf (stderr, "premature ending (bitno %d)\n", bitno);

	return NOTOK;
    }
self_delimiting:
    if (dp != ep) {
invalid_digest:
	if (debugsw) {
	    while (*cp)
		cp++;
	    fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
		     cp - bp);
	}

	return NOTOK;
    }

    if (debugsw) {
	fprintf (stderr, "MD5 digest=");
	for (dp = ct->c_digest; dp < ep; dp++)
	    fprintf (stderr, "%02x", *dp & 0xff);
	fprintf (stderr, "\n");
    }

    return OK;
}


void done (int status)
{
    CT *ctp;

    if ((ctp = cts))
	for (; *ctp; ctp++)
	    free_content (*ctp);
    if (free_ct)
	free_content (free_ct);
    if (free_file)
	unlink (free_file);

    exit (status);
}


static int
pidcheck (int status)
{
    if ((status & 0xff00) == 0xff00 || (status & 0x007f) != SIGQUIT)
	return status;

/*  unlink ("core"); */

    fflush (stdout);
    fflush (stderr);
    done (1);
    /* NOTREACHED */
}
