4.4BSD/usr/src/contrib/mh-6.8/uip/mhn.c

Compare this file to the similar file:
Show the results in this format:

/* mhn.c - multi-media MH */
#ifndef	lint
static char ident[] = "@(#)$Id: mhn.c,v 2.21 1992/12/15 00:20:22 jromine Exp $";
#endif	/* lint */

#include "../h/mh.h"
#include <ctype.h>
#include <errno.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef	BSD42
#include <sys/wait.h>
#endif
#ifdef LOCALE
#include	<locale.h>
#endif

/*  */

static struct swit switches[] = {
#define	AUTOSW	  0
    "auto", 0,
#define	NAUTOSW	  1
    "noauto", 0,

#define	DEBUGSW	  2
    "debug", -5,
    
#define	EBCDICSW  3
    "ebcdicsafe", 0,
#define	NEBCDICSW 4
    "noebcdicsafe", 0,

#define	FORMSW	  5
    "form formfile", 4,

#define	HEADSW	  6
    "headers", 0,
#define	NHEADSW	  7
    "noheaders", 0,

#define	LISTSW	  8
    "list", 0,
#define	NLISTSW	  9
    "nolist", 0,

#define	PARTSW	 10
    "part number", 0,

#define	SIZESW	 11
    "realsize", 0,
#define	NSIZESW	 12
    "norealsize", 0,

#define	RFC934SW 13
    "rfc934mode", 0,
#define	NRFC934SW 14
    "norfc934mode", 0,

#define	SERIALSW 15
    "serialonly", 0,
#define	NSERIALSW 16
    "noserialonly", 0,

#define	SHOWSW	 17
    "show", 0,
#define	NSHOWSW	 18
    "noshow", 0,

#define	STORESW	 19
    "store", 0,
#define	NSTORESW 20
    "nostore", 0,

#define	TYPESW	 21
    "type content", 0,

#define	VERBSW	 22
    "verbose", 0,
#define	NVERBSW	 23
    "noverbose", 0,
    
#define	HELPSW	 24
    "help", 4,

#define	PROGSW   25
    "moreproc program", -4,
#define	NPROGSW	 26
    "nomoreproc", -3,

#define	LENSW	 27
    "length lines", -4,
#define	WIDSW	 28
    "width columns", -4,

#define	FILESW	 29		/* interface from show */
    "file file", -4,

#define	VIAMSW	 30
    "viamail mailpath", -7,
#define	VIASSW	 31
    "viasubj subject", -7,
#define	VIAPSW	 32
    "viaparm arguments", -7,
#define	VIADSW	 33
    "viadesc text", -7,
#define	VIACSW	 34
    "viacmnt text", -7,
#define	VIAZSW	 35
    "viadelay seconds", -8,
#define	VIAFSW	 36
    "viafrom mailpath", -7,

    NULL, 0
};

/*  */

#define	NPARTS	50
#define	NTYPES	20

static	int	autosw = 0;
	int	debugsw = 0;
static	int	ebcdicsw = 0;
static	char   *formsw = NULLCP;
static	int	headsw = 1;
static	int	listsw = 0;
static	int	nomore = 0;
static	int	npart = 0;
static	char   *parts[NPARTS + 1];
static	char   *progsw = NULLCP;
static	int	rfc934sw = 1;
static	int	serialsw = 0;
static	int	showsw = 0;
static	int	sizesw = 1;
static	int	storesw = 0;
static	int	ntype = 0;
static	char   *types[NTYPES + 1];
	int	verbosw = 0;

static	int	endian = 0;
static  char   *mm_charset = NULL;

static	int	xpid = 0;
static	int	userrs = 0;

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


extern	int	errno;
#ifndef	BSD44
extern	int	sys_nerr;
extern	char   *sys_errlist[];
#endif

off_t	lseek ();
time_t	time ();

/*  */

#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"

static void  build_comp ();

typedef struct CTinfo {
    char   *ci_type;
    char   *ci_subtype;

#define	NPARMS	10
    char   *ci_attrs[NPARMS + 2];
    char   *ci_values[NPARMS];

    char   *ci_comment;

    char   *ci_magic;
}	CTInfo, *CI;
#define	NULLCI	((CI) 0)

static int get_ctinfo ();
static int get_comment ();


typedef struct Content {
    char   *c_partno;		/* within multipart content */

    char   *c_vrsn;		/* Body-Version: */

    char   *c_ctline;		/* Content-Type: */
    CTInfo  c_ctinfo;

    int	    c_type;		/* internal form */
#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

    int	    c_subtype;		/* filled-in by c_ctinitfnx */
    caddr_t c_ctparams;		/*   .. */
    caddr_t c_ctextern;		/*   .. */
    char   *c_showproc;		/* default, if not in profile */
    char   *c_termproc;		/* for charset madness... */
    char   *c_storeproc;	/* default, if not in profile */

    int	  (*c_ctinitfnx) ();	/* parse content */
    int	  (*c_ctlistfnx) ();	/* list content */
    int	  (*c_ctshowfnx) ();	/* show content */
    int	  (*c_ctstorefnx) ();	/* store content */
    int	  (*c_ctfreefnx) ();	/* free content-specific structures */


    char   *c_celine;		/* Content-Transfer-Encoding: */

    int	    c_encoding;		/* internal form */
#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 */

    caddr_t c_ceparams;		/* filled-in by encoding initfnx */

    int	  (*c_ceopenfnx) ();	/* get a stream to decoded contents */
    int	  (*c_ceclosefnx) ();	/* release stream */
    int	  (*c_celistfnx) ();	/* list decoding info */
    unsigned long
	  (*c_cesizefnx) ();	/* size of decoded contents */
    int	  (*c_cefreefnx) ();	/* free encoding-specific structures */


    char   *c_id;		/* Content-ID: */
    char   *c_descr;		/* Content-Description: */


    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 starts in file */
    long    c_end;		/*   ..		 ends           */

    int	    c_pid;		/* process doing display */
    char   *c_storage;		/* write contents (file) */

    int	    c_rfc934;		/* rfc934 compatibility */
}	Content, *CT;
#define	NULLCT	((CT) 0)

static CT get_content ();
static int list_content (), show_content (), store_content ();
static int user_content(), compose_content(), output_content();
static void free_content (), flush_errors ();

#if	defined(__STDC__) && defined(VSPRINTF)
static void content_error (char *, register CT, char *, ...);
#else
static void content_error ();
#endif

static int   init_encoding(), type_ok(), copy_some_headers(), set_endian();
static int   write7Bit(), writeQuoted(), writeBase64(), writeBase64aux();
static int   via_mail(), via_post(), pidcheck();

static	CT    *cts = NULL;


#define	quitser	pipeser
static	TYPESIG	pipeser ();
static	char   *fgetstr ();

/*  */

/* ARGSUSED */

main (argc, argv)
int     argc;
char  **argv;
{
    int     f6 = 0,
	    msgp = 0,
            msgnum;
    char   *cp,
	   *f1 = NULL,
	   *f2 = NULL,
	   *f3 = NULL,
	   *f4 = NULL,
	   *f5 = NULL,
	   *f7 = NULL,
	   *file = NULL,
           *folder = NULL,
           *maildir,
            buf[100],
          **ap,
          **argp,
           *arguments[MAXARGS],
           *msgs[MAXARGS];
    struct msgs *mp;
    register CT	 ct,
		*ctp;
    FILE	*fp;

#ifdef LOCALE
	setlocale(LC_ALL, "");
#endif
    invo_name = r1bindex (argv[0], '/');
    if (argv[1] && uprf (argv[1], "-via"))
	m_foil (NULLCP);
    if ((cp = m_find (invo_name)) != NULL) {
	ap = brkstring (cp = getcpy (cp), " ", "\n");
	ap = copyip (ap, arguments);
    }
    else
	ap = arguments;
    (void) copyip (argv + 1, ap);
    argp = arguments;

/*  */

    while (cp = *argp++) {
	if (*cp == '-')
	    switch (smatch (++cp, switches)) {
		case AMBIGSW: 
		    ambigsw (cp, switches);
		    done (1);
		case UNKWNSW: 
		    adios (NULLCP, "-%s unknown", cp);
		case HELPSW: 
		    (void) sprintf (buf, "%s [+folder] [msgs] [switches]",
			    invo_name);
		    help (buf, switches);
		    done (1);

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

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

		case FORMSW:
		    if (!(cp = *argp++) || *cp == '-')
			adios (NULLCP, "missing argument to %s", argp[-2]);
		    if (formsw)
			free (formsw);
		    formsw = getcpy (libpath (cp));
		    continue;

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

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

		case PARTSW:
		    if (!(cp = *argp++) || *cp == '-')
			adios (NULLCP, "missing argument to %s", argp[-2]);
		    if (npart >= NPARTS)
			adios (NULLCP,
			       "too many parts (starting with %s), %d max",
			       cp, NPARTS);
		    parts[npart++] = cp;
		    continue;

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

		case SERIALSW:
		    serialsw++;
		    continue;
		case NSERIALSW:
		    serialsw = 0;
		    continue;

		case SHOWSW:
		    showsw++;
		    continue;
		case NSHOWSW:
		    showsw = 0;
		    continue;

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

		case STORESW:
		    storesw++;
		    continue;
		case NSTORESW:
		    storesw = 0;
		    continue;

		case TYPESW:
		    if (!(cp = *argp++) || *cp == '-')
			adios (NULLCP, "missing argument to %s", argp[-2]);
		    if (ntype >= NTYPES)
			adios (NULLCP,
			       "too many types (starting with %s), %d max",
			       cp, NTYPES);
		    types[ntype++] = cp;
		    continue;

		case VERBSW: 
		    verbosw++;
		    continue;
		case NVERBSW: 
		    verbosw = 0;
		    continue;

		case PROGSW:
		    if (!(progsw = *argp++) || *progsw == '-')
			adios (NULLCP, "missing argument to %s", argp[-2]);
		    continue;
		case NPROGSW:
		    nomore++;
		    continue;

		case LENSW:
		case WIDSW:
		    if (!(cp = *argp++) || *cp == '-')
			adios (NULLCP, "missing argument to %s", argp[-2]);
		    continue;

		case FILESW:
		    if (!(cp = *argp++) || (*cp == '-' && cp[1]))
			adios (NULLCP, "missing argument to %s", argp[-2]);
		    file = *cp == '-' ? cp : path (cp, TFILE);
		    continue;

	       case VIAMSW:
		    if (!(f1 = *argp++))
			adios (NULLCP, "missing argument to %s", argp[-2]);
		    continue;
	       case VIASSW:
		    if (!(f2 = *argp++))
			adios (NULLCP, "missing argument to %s", argp[-2]);
		    continue;
	       case VIAPSW:
		    if (!(f3 = *argp++))
			adios (NULLCP, "missing argument to %s", argp[-2]);
		    continue;
	       case VIADSW:
		    if (!(f4 = *argp++))
			adios (NULLCP, "missing argument to %s", argp[-2]);
		    continue;
	       case VIACSW:
		    if (!(f5 = *argp++))
			adios (NULLCP, "missing argument to %s", argp[-2]);
		    continue;
	       case VIAZSW:
		    if (!(cp = *argp++) || *cp == '-')
			adios (NULLCP, "missing argument to %s", argp[-2]);
		    if (sscanf (cp, "%d", &f6) != 1 || f6 < 0)
			f6 = 300;
		    continue;
	       case VIAFSW:
		    if (!(f7 = *argp++))
			adios (NULLCP, "missing argument to %s", argp[-2]);
		    continue;
	    }
	if (*cp == '+' || *cp == '@') {
	    if (folder)
		adios (NULLCP, "only one folder at a time!");
	    else
		folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF);
	}
	else
	    msgs[msgp++] = cp;
    }
    parts[npart] = NULL, types[ntype] = NULL;
    if (!formsw)
	formsw = getcpy (libpath ("mhl.headers"));

    set_endian ();

/*  */

    if (f1) {
	via_mail (f1, f2, f3, f4, f5, f6, f7);
	/* NOTREACHED */
    }
    else
	if (f2 || f3 || f4 || f5 || f6)
	    adios (NULLCP, "missing -viamail \"mailpath\" switch");

    if (cp = getenv ("MHN")) {
	if (fp = fopen (cp, "r")) {
	    m_readefs ((struct node **) 0, fp, cp, 0);

	    (void) fclose (fp);
	}
	else
	    admonish ("", "unable to read $MHN profile (%s)", cp);
    }
    if (fp = fopen (cp = libpath ("mhn_defaults"), "r")) {
	m_readefs ((struct node **) 0, fp, cp, 0);

	(void) fclose (fp);
    }

    (void) sprintf (buf, "%s-cache", invo_name);
    cache = m_find (buf);

    cwdlen = strlen (cwd = getcpy (pwd ()));
    (void) sprintf (buf, "%s-storage", invo_name);
    dir = getcpy ((cp = m_find (buf)) && *cp ? cp : cwd);
    tmp = cp && *cp ? concat (cp, "/", invo_name, NULLCP)
		    : add (m_maildir (invo_name), NULLCP);

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

    if (msgp == 1
	    && !folder
	    && !npart
	    && !showsw
	    && !storesw
	    && !ntype
	    && !file
	    && (cp = getenv ("mhdraft"))
	    && strcmp (cp, msgs[0]) == 0) {
	build_comp (cp);
	/* NOTREACHED */
    }

    if (file) {
	int	stdinP;

	if (msgp)
	    adios (NULLCP, "only one file at a time!");

	if ((cts = (CT *) calloc ((unsigned) 2, sizeof *cts)) == NULL)
	    adios (NULLCP, "out of memory");

	ctp = cts;
	if (stdinP = (strcmp (file, "-") == 0)) {
	    char    buffer[BUFSIZ];

	    file = add (m_tmpfil (invo_name), NULLCP);

	    if ((fp = fopen (file, "w+")) == NULL)
		adios (file, "unable to fopen for writing and reading");
	    (void) chmod (file, 0600);
	    while (fgets (buffer, sizeof buffer, stdin))
		(void) fputs (buffer, fp);
	    (void) fflush (fp);

	    if (ferror (stdin)) {
		(void) unlink (file);
		adios ("stdin", "error reading");
	    }

	    if (ferror (fp)) {
		(void) unlink (file);
		adios (file, "error writing");
	    }

	    (void) fseek (fp, 0L, 0);
	}
	else
	    if ((fp = fopen (file, "r")) == NULL)
		adios (file, "unable to read");

	if (ct = get_content (fp, file, 1)) {
	    if (stdinP)
		ct -> c_unlink = 1;

	    ct -> c_fp = NULL;
	    if (ct -> c_end == 0L) {
		(void) fseek (fp, 0L, 2);
		ct -> c_end = ftell (fp);
	    }
	    if (ct -> c_ctinitfnx && (*ct -> c_ctinitfnx) (ct) == NOTOK)
		free_content (ct);
	    else
		*ctp++ = ct;
	}
	else
	    advise (NULLCP, "unable to decode %s", file);

	(void) fclose (fp);

	mp = NULL;
	goto go_to_it;
    }

    if (!msgp)
	msgs[msgp++] = "cur";
    if (!folder)
	folder = m_getfolder ();
    maildir = m_maildir (folder);

    if (chdir (maildir) == NOTOK)
	adios (maildir, "unable to change directory to");
    if (!(mp = m_gmsg (folder)))
	adios (NULLCP, "unable to read folder %s", folder);
    if (mp -> hghmsg == 0)
	adios (NULLCP, "no messages in %s", folder);

    for (msgnum = 0; msgnum < msgp; msgnum++)
	if (!m_convert (mp, msgs[msgnum]))
	    done (1);
    m_setseq (mp);

    if ((cts = (CT *) calloc ((unsigned) (mp -> numsel + 1), sizeof *cts))
	    == NULL)
	adios (NULLCP, "out of memory");

    ctp = cts;
    for (msgnum = mp -> lowsel; msgnum <= mp -> hghsel; msgnum++)
	if (mp -> msgstats[msgnum] & SELECTED) {
	    char   *msgnam;

	    if ((fp = fopen (msgnam = m_name (msgnum), "r")) == NULL)
		adios (msgnam, "unable to read message");

	    if (ct = get_content (fp, msgnam, 1)) {
		ct -> c_fp = NULL;
		if (ct -> c_end == 0L) {
		    (void) fseek (fp, 0L, 2);
		    ct -> c_end = ftell (fp);
		}
		if (ct -> c_ctinitfnx && (*ct -> c_ctinitfnx) (ct) == NOTOK)
		    free_content (ct);
		else
		    *ctp++ = ct;
	    }
	    else
		advise (NULLCP, "unable to decode message %s", msgnam);

	    (void) fclose (fp);
	}

go_to_it: ;
    if (!*cts)
	done (1);

    if (!listsw && !showsw && !storesw)
	showsw++;

/* listsw && showsw		-> user wants per-message listing,
				   so delay until showsw processing

	  && storesw && sizesw	-> evaluating size will cause more work,
				   so delay until after storesw processing
 */
    userrs = 1;
    (void) signal (SIGQUIT, quitser);
    (void) signal (SIGPIPE, pipeser);

    for (ctp = cts; ct = *ctp; ctp++)
	if (type_ok (ct)
	        && (ct -> c_ctlistfnx
		        || ct -> c_ctstorefnx
		        || ct -> c_ctshowfnx)) {
	    struct	stat st;

	    if (!ct -> c_umask)
		ct -> c_umask = ~(stat (ct -> c_file, &st) != NOTOK
					? (st.st_mode & 0777) : m_gmprot ());
	}

    if (listsw && !showsw && (!storesw || !sizesw)) {
	if (headsw)
	    printf (LSTFMT1, "msg", "part", "type/subtype", "size",
		    "description");

	for (ctp = cts; ct = *ctp; ctp++)
	    if (type_ok (ct) && ct -> c_ctlistfnx) {
		(void) umask (ct -> c_umask);
		(void) (*ct -> c_ctlistfnx) (ct, 1);
		if (ct -> c_fp)
		    (void) fclose (ct -> c_fp), ct -> c_fp = NULL;
	    }

	flush_errors ();
    }

    if (storesw) {
	for (ctp = cts; ct = *ctp; ctp++)
	    if (type_ok (ct) && ct -> c_ctstorefnx) {
		(void) umask (ct -> c_umask);
		(void) (*ct -> c_ctstorefnx) (ct, NULLCP);
		if (ct -> c_fp)
		    (void) fclose (ct -> c_fp), ct -> c_fp = NULL;
	    }

	flush_errors ();
    }

    if (listsw && !showsw && storesw && sizesw) {
	if (headsw)
	    printf (LSTFMT1, "msg", "part", "type/subtype", "size",
		    "description");

	for (ctp = cts; ct = *ctp; ctp++)
	    if (type_ok (ct) && ct -> c_ctlistfnx) {
		(void) umask (ct -> c_umask);
		(void) (*ct -> c_ctlistfnx) (ct, 1);
		if (ct -> c_fp)
		    (void) fclose (ct -> c_fp), ct -> c_fp = NULL;
	    }

	flush_errors ();
	listsw = 0;
    }

    if (showsw)
	for (ctp = cts; ct = *ctp; ctp++) {
	    int	    child_id,
		    i,
		    vecp;
	    char   *vec[8];
#if defined(BSD42) && !defined(WAITINT)
	    union wait status;
#else
	    int     status;
#endif
	    TYPESIG (*hstat) (), (*istat) (), (*qstat) (), (*tstat) ();

	    if (!type_ok (ct))
		continue;

	    (void) umask (ct -> c_umask);

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

		if (ct -> c_ctlistfnx)
		    (void) (*ct -> c_ctlistfnx) (ct, 1);
	    }

	    if (!ct -> c_ctshowfnx) {
		if (ct -> c_fp)
		    (void) fclose (ct -> c_fp), ct -> c_fp = NULL;
		continue;
	    }

	    vecp = 0;
	    vec[vecp++] = r1bindex (mhlproc, '/');
	    vec[vecp++] = "-form";
	    vec[vecp++] = formsw;
	    vec[vecp++] = "-nobody";
	    vec[vecp++] = ct -> c_file;
	    if (nomore)
		vec[vecp++] = "-nomoreproc";
	    else
		if (progsw) {
		    vec[vecp++] = "-moreproc";
		    vec[vecp++] = progsw;
		}
	    vec[vecp] = NULL;

	    (void) 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:
		    (void) execvp (mhlproc, vec);
		    fprintf (stderr, "unable to exec ");
		    perror (mhlproc);
		    _exit (-1);
		    /* NOTREACHED */

		default:
		    xpid = child_id;
		    break;
	    }

	    (void) (*ct -> c_ctshowfnx) (ct, 1, 0);
	    if (ct -> c_fp)
		(void) fclose (ct -> c_fp), ct -> c_fp = NULL;

	    hstat = signal (SIGHUP, SIG_IGN);
	    istat = signal (SIGINT, SIG_IGN);
	    qstat = signal (SIGQUIT, SIG_IGN);
	    tstat = signal (SIGTERM, SIG_IGN);

	    while (wait (&status) != NOTOK) {
#if defined(BSD42) && !defined(WAITINT)
		(void) pidcheck (status.w_status);
#else
		(void) pidcheck (status);
#endif
		continue;
	    }

	    (void) signal (SIGHUP, hstat);
	    (void) signal (SIGINT, istat);
	    (void) signal (SIGQUIT, qstat);
	    (void) signal (SIGTERM, tstat);

	    xpid = 0;

	    flush_errors ();
	}

    for (ctp = cts; *ctp; ctp++)
	free_content (*ctp);
    free ((char *) cts);
    cts = NULL;

    if (mp) {
	m_replace (pfolder, folder);
	if (mp -> hghsel != mp -> curmsg)
	    m_setcur (mp, mp -> hghsel);
	m_sync (mp);
	m_update ();
    }

    done (0);
    /* NOTREACHED */
}

/*  */

static TYPESIG  pipeser (i)
int	i;
{
    if (i == SIGQUIT) {
	(void) unlink ("core");

	(void) fflush (stdout);

	fprintf (stderr, "\n");
	(void) fflush (stderr);
    }

    done (1);
    /* NOTREACHED */
}

/*  */

#include "../h/mhn.h"


struct str2init {
    char   *si_key;
    int	    si_value;
    int   (*si_init) ();
};


static int InitApplication (), InitMessage (), InitMultiPart (), InitText ();
    	

static struct str2init str2cts[] = {
    "application",  CT_APPLICATION, InitApplication,
    "audio",	    CT_AUDIO,	    NULL,
    "image",	    CT_IMAGE,	    NULL,
    "message",	    CT_MESSAGE,	    InitMessage,
    "multipart",    CT_MULTIPART,   InitMultiPart,
    "text",	    CT_TEXT,	    InitText,
    "video",	    CT_VIDEO,	    NULL,

    NULL,	    CT_EXTENSION,   NULL,	/* these two must be last! */
    NULL,	    CT_UNKNOWN,	    NULL,
};


static int InitBase64 (), InitQuoted (), Init7Bit ();

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,
};

/*  */

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

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

    ct -> c_begin = ftell (ct -> c_fp = in) + 1;
    ct -> c_file = add (file, NULLCP);
    for (compnum = 1, state = FLD;;) {
	switch (state = m_getfld (state, name, buf, sizeof buf, in)) {
	    case FLD:
	    case FLDPLUS:
	    case FLDEOF:
	        compnum++;

		if (uleq (name, VRSN_FIELD)) {
		    register char  *cp;

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

		    if (ct -> c_vrsn) {
			char   *dp = trimcpy (cp);

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

		    ct -> c_vrsn = cp;
#ifdef	whocares
		    while (isspace (*cp))
			cp++;
		    for (dp = cp; istoken (*dp); dp++)
			continue;
		    c = *dp, *dp = NULL;
		    if (!uleq (cp, VRSN_VALUE)) {
			if (!isspace (c))
			    *dp = c;
			advise (NULLCP,
				"message %s has unsupported value for %s: field (%s)",
				ct -> c_file, VRSN_FIELD, cp);
			goto out;
		    }
		    *dp = c;
#endif
		    goto got_header;
		}

		if (uleq (name, TYPE_FIELD)) {
		    register char  *cp;
		    register struct str2init *s2i;
		    register CI	    ci = &ct -> c_ctinfo;

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

		    if (ct -> c_ctline) {
			char   *dp = trimcpy (cp);

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

		    if (get_ctinfo (cp, ct, 0) == NOTOK)
			goto out;
		    for (s2i = str2cts; s2i -> si_key; s2i++)
			if (uleq (ci -> ci_type, s2i -> si_key))
			    break;
		    if (!s2i -> si_key && !uprf (ci -> ci_type, "X-"))
			s2i++;
		    ct -> c_type = s2i -> si_value;
		    ct -> c_ctinitfnx = s2i -> si_init;
		    goto got_header;
		}

		if (uleq (name, ENCODING_FIELD)) {
		    register char *cp,
				  *dp;
		    char    c;
		    register struct str2init *s2i;

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

		    if (ct -> c_celine) {
			advise (NULLCP,
				"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;
		    while (isspace (*cp))
			cp++;
		    for (dp = cp; istoken (*dp); dp++)
			continue;
		    c = *dp, *dp = '\0';
		    for (s2i = str2ces; s2i -> si_key; s2i++)
			if (uleq (cp, s2i -> si_key))
			    break;
		    if (!s2i -> si_key && !uprf (cp, "X-"))
			s2i++;
		    *dp = c;
		    ct -> c_encoding = s2i -> si_value;
		    if (s2i -> si_init && (*s2i -> si_init) (ct) == NOTOK)
			goto out;
		    goto got_header;
		}

		if (uleq (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;
		}

		if (uleq (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;
		}

#ifdef	notdef
		if (uprf (name, XXX_FIELD_PRF))
		    advise (NULLCP, "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:
		break;

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

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

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

    if (!ct -> c_ctline) {
	if (toplevel < 0) {
	    if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
		goto out;
	    ct -> c_type = CT_MESSAGE;
	    ct -> c_ctinitfnx = InitMessage;
	}
	else {
	    if (get_ctinfo ("text/plain", ct, 0) == NOTOK)
		goto out;
	    ct -> c_type = CT_TEXT;
	    ct -> c_ctinitfnx = InitText;
	}
    }
    if (!ct -> c_ctlistfnx)
	ct -> c_ctlistfnx = list_content;
    if (!ct -> c_ctshowfnx)
	ct -> c_ctshowfnx = show_content;
    if (!ct -> c_ctstorefnx)
	ct -> c_ctstorefnx = store_content;

    if (!ct -> c_celine) {
	ct -> c_encoding = CE_7BIT;
	(void) Init7Bit (ct);
    }

    return ct;

out:
    free_content (ct);
    return NULLCT;
}

/*  */

static int  get_ctinfo (cp, ct, magic)
char  *cp;
register CT	ct;
int	magic;
{
    int	    i = strlen (invo_name) + 2;
    register char  *dp,
		  **ap,
		  **ep;
    char    c;
    register CI	    ci = &ct -> c_ctinfo;

    cp = ct -> c_ctline = add (cp, NULLCP);
    while (isspace (*cp))
	cp++;
    for (dp = index (cp, '\n'); dp; dp = index (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", TYPE_FIELD, cp);

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

    for (dp = cp; istoken (*dp); dp++)
	continue;
    c = *dp, *dp = '\0';
    ci -> ci_type = add (cp, NULLCP);
    *dp = c, cp = dp;

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

    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) == NOTOK)
	return NOTOK;

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

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

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

    for (dp = cp; istoken (*dp); dp++)
	continue;
    c = *dp, *dp = '\0';
    ci -> ci_subtype = add (cp, NULLCP);
    *dp = c, cp = dp;

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

    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) == NOTOK)
	return NOTOK;

    ep = (ap = ci -> ci_attrs) + NPARMS;
    while (*cp == ';') {
	char   *vp,
	       *up;

	if (ap >= ep) {
	    advise (NULLCP,
		    "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) == NOTOK)
	    return NOTOK;

	if (*cp == 0) {
	    advise (NULLCP,
		    "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 (NULLCP,
		    "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, NULLCP)) + (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 (NULLCP,
				"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 (NULLCP,
		    "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) == NOTOK)
	    return NOTOK;
    }

    if (magic && *cp == '[') {
	ct -> c_descr = ++cp;
	for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
	    if (*dp == ']')
		break;
	if (dp < cp) {
	    advise (NULLCP, "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", NULLCP);
	else
	    ct -> c_descr = NULL;
	*dp++ = c, cp = dp;

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

    if (*cp) {
	if (magic)
	    ci -> ci_magic = add (cp, NULLCP);
	else
	    advise (NULLCP,
		    "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, ap)
CT	ct;
char  **ap;
{
    register int    i;
    register char  *bp,
		   *cp;
    char    c,
	    buffer[BUFSIZ],
	   *dp;
    register CI	    ci = &ct -> c_ctinfo;

    cp = *ap;

    bp = buffer;
    cp++;
    for (i = 0;;) {
	switch (c = *cp++) {
	    case '\0':
invalid: ;
	        advise (NULLCP, "invalid comment in message %s's %s: field",
			ct -> c_file, TYPE_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 (dp = ci -> ci_comment) {
	ci -> ci_comment = concat (dp, " ", buffer, NULLCP);
	free (dp);
    }
    else
	ci -> ci_comment = add (buffer, NULLCP);

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

    *ap = cp;

    return OK;
}

/*  */

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


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

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

    size = ct -> c_cesizefnx && sizesw ? (*ct -> c_cesizefnx) (ct)
				       : 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, NULLCP));
	free (cp);
	printf (LSTFMT2d1, dp);
	free (dp);
    }

    printf ("\n");

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

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

    if (!debugsw)
	return OK;

    (void) fflush (stdout);

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

    if (ct -> c_vrsn)
	fprintf (stderr, "  %s:%s", 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, 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, ct -> c_ceparams);

    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 %d end %d\n",
	     ct -> c_fp, empty (ct -> c_file), ct -> c_begin, ct -> c_end);

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

    return OK;
}
#undef	empty

/*  */

#ifdef VSPRINTF
#ifdef __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif
#endif

#ifdef VSPRINTF
#ifdef __STDC__
static void content_error (char *what, register CT ct, char *fmt, ...)
#else
static void  content_error (va_alist)
va_dcl
#endif
#else	/* !VSPRINTF */
/* VARARGS3 */
static void  content_error (what, ct, fmt, a, b, c, d, e, f)
char   *what,
       *fmt,
       *a,
       *b,
       *c,
       *d,
       *e,
       *f;
register CT	ct;
#endif
{
#ifdef VSPRINTF
    va_list arglist;
#endif
#if defined(VSPRINTF) && !defined(__STDC__)
    char *what, *fmt;
    register CT ct;
#endif
    int	    i;
    register char *bp;
    char   buffer[BUFSIZ];
    register CI	   ci;

    bp = buffer;

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

#ifdef VSPRINTF
#ifdef __STDC__
    va_start (arglist, fmt);
#else
    va_start (arglist);
    what = va_arg(arglist, char *);
    ct   = va_arg(arglist, CT);
    fmt  = va_arg(arglist, char *);
#endif
    (void) vsprintf (bp, fmt, arglist);
    bp += strlen (bp);
#else
    (void) sprintf (bp, fmt, a, b, c, d, e, f);
    bp += strlen (bp);
#endif
    ci = &ct -> c_ctinfo;

    if (what) {
	if (*what) {
	    (void) sprintf (bp, " %s: ", what);
	    bp += strlen (bp);
	}

	if (errno > 0 && errno < sys_nerr)
	    (void) sprintf (bp, "%s", sys_errlist[errno]);
	else
	    (void) sprintf (bp, "Error %d", errno);
	bp += strlen (bp);
    }

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

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

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


static void  flush_errors ()
{
    if (errs) {
	(void) fflush (stdout);
	fprintf (stderr, "%s", errs);
	free (errs);
	errs = NULL;
    }
}

/*  */

static	jmp_buf	intrenv;


/* ARGSUSED */

static TYPESIG  intrser (i)
int	i;
{
#ifdef	BSD42
    (void) signal (SIGINT, intrser);
#endif

    (void) putchar ('\n');

    longjmp (intrenv, DONE);
}

/*  */

static int  show_content_aux ();

static int  show_content (ct, serial, alternate)
register CT	ct;
int	serial,
	alternate;
{
    register char  *cp;
    char    buffer[BUFSIZ];
    register CI ci = &ct -> c_ctinfo;

    (void) sprintf (buffer, "%s-show-%s/%s", invo_name, ci -> ci_type,
		    ci -> ci_subtype);
    if ((cp = m_find (buffer)) == NULL || *cp == 0) {
	(void) sprintf (buffer, "%s-show-%s", invo_name, ci -> ci_type);
	if (((cp = m_find (buffer)) == NULL || *cp == 0)
	        && (cp = ct -> c_showproc) == NULL) {
	    if (!alternate)
		content_error (NULLCP, ct,
			       "don't know how to display content");

	    return NOTOK;
	}
    }

    return show_content_aux (ct, serial, alternate, cp, NULLCP);
}


static int  show_content_aux (ct, serial, alternate, cp, cracked)
register CT	ct;
int	serial,
	alternate;
register char   *cp;
char   *cracked;
{
    int	    child_id,
	    fd,
	    i,
	    xlist,
	    xpause,
	    xstdin,
	    xtty;
    register char  *bp;
    char   *file,
	   *vec[4],
	    buffer[BUFSIZ],
	    exec[BUFSIZ + sizeof "exec "];
    register CI ci = &ct -> c_ctinfo;

    if (!ct -> c_ceopenfnx) {
	if (!alternate)
	    content_error (NULLCP, ct, "don't know how to decode content");

	return NOTOK;
    }

    file = NULL;
    if ((fd = (*ct -> c_ceopenfnx) (ct, &file)) == NOTOK)
	return NOTOK;
    if (ct -> c_showproc && strcmp (ct -> c_showproc, "true") == 0)
	return (alternate ? DONE : OK);
    
    xlist = xpause = xstdin = xtty = 0;
    if (cracked) {
	(void) strcpy (buffer, cp);
	goto got_command;
    }
    buffer[0] = '\0';
    for (bp = buffer; *cp; cp++)
	if (*cp == '%') {
	    switch (*++cp) {
		case 'a':	/* additional arguments */
		    {
			register char **ap,
				      **ep;
			char   *s = "";

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

		case 'e':	/* exclusive execution */
		    xtty = 1;
		    break;

		case 'F':	/* %e, %f, and stdin is terminal not content */
		    xstdin = xtty = 1;
		    /* and fall... */
	        case 'f':	/* filename */
		    (void) sprintf (bp, "%s", file);
		    break;

		case 'p':	/* pause prior to displaying content */
		    xpause = 1;
		    /* and fall... */
		case 'l':	/* display listing prior to displaying
				   content */
		    xlist = 1;
		    break;

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

		case '%':
		    goto raw;

		default:
		    *bp++ = *--cp;
		    *bp = '\0';
		    continue;
	    }
	    bp += strlen (bp);
	}
        else {
raw: ;
	    *bp++ = *cp;
	    *bp = '\0';
	}
    if (ct -> c_termproc) {
	char	term[BUFSIZ];

	(void) strcpy (term, buffer);
	(void) sprintf (buffer, ct -> c_termproc, term);
    }
got_command: ;

    if (debugsw || cracked) {
	(void) fflush (stdout);

	fprintf (stderr, "%s msg %s", cracked ? "storing" : "show",
		 ct -> c_file);
	if (ct -> c_partno)
	    fprintf (stderr, " part %s", ct -> c_partno);
	if (cracked)
	    fprintf (stderr, " using command (cd %s; %s)\n", cracked, buffer);
	else
	    fprintf (stderr, " using command %s\n", buffer);
    }

    if (xtty && xpid) {
	(void) pidcheck (pidwait (xpid, NOTOK));
	xpid = 0;
    }

    if (xlist) {
	char    prompt[BUFSIZ];

	if (ct -> c_ctlistfnx) {
	    (*ct -> c_ctlistfnx) (ct, -1);

	    if (xpause && SOprintf ("Press <return> to show content..."))
		printf ("Press <return> to show content...");
	}
	else {
	    register char *pp;

	    pp = prompt;
	    if (ct -> c_descr) {
		(void) sprintf (pp, "%s (", ct -> c_descr);
		pp += strlen (pp);
	    }

	    (void) sprintf (pp, "content %s/%s", ci -> ci_type,
			    ci -> ci_subtype);
	    pp += strlen (pp);
	    if (ct -> c_file) {
		(void) sprintf (pp, " in message %s", ct -> c_file);
		pp += strlen (pp);
		if (ct -> c_partno) {
		    (void) sprintf (pp, ", part %s", ct -> c_partno);
		    pp += strlen (pp);
		}
	    }

	    if (ct -> c_descr) {
		(void) sprintf (pp, ")");
		pp += strlen (pp);
	    }

	    if (!xpause)
		printf ("%s\n", prompt);
	    else
		if (SOprintf ("Press <return> to show %s...", prompt))
		    printf ("Press <return> to show %s...", prompt);
	}

	if (xpause) {
	    int	    intr;
	    TYPESIG (*istat) ();

	    istat = signal (SIGINT, intrser);
	    if ((intr = setjmp (intrenv)) == OK) {
		(void) fflush (stdout);
		prompt[0] = 0;
		(void) read (fileno (stdout), prompt, sizeof prompt);
	    }
	    (void) signal (SIGINT, istat);
	    if (intr != OK) {
		(void) (*ct -> c_ceclosefnx) (ct);
		return (alternate ? DONE : NOTOK);
	    }
	}
    }

    (void) sprintf (exec, "exec %s", buffer);

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

    (void) fflush (stdout);

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

	case OK:
	    if (cracked)
		(void) chdir (cracked);
	    if (!xstdin)
		(void) dup2 (fd, 0);
	    (void) close (fd);
	    (void) execvp ("/bin/sh", vec);
	    fprintf (stderr, "unable to exec ");
	    perror ("/bin/sh");
	    _exit (-1);
	    /* NOTREACHED */

	default:
	    if (!serial) {
		ct -> c_pid = child_id;
		if (xtty)
		    xpid = child_id;
	    }
	    else
		(void) pidcheck (pidXwait (child_id, NULLCP));

	    (void) (*ct -> c_ceclosefnx) (ct);
	    return (alternate ? DONE : OK);
    }
}

/*  */

static int  store_content (ct, append)
register CT	ct;
char   *append;
{
    int	    appending = append && *append;
    long    last,
	    pos;
    register char  *bp,
		   *cp;
    char   *file,
	    buffer[BUFSIZ];
    register CI ci = &ct -> c_ctinfo;
    FILE       *fp;

    if (appending) {
	(void) strcpy (buffer, append);
	goto got_filename;
    }

    if ((cp = ct -> c_storeproc) == NULL || *cp == 0) {
	(void) sprintf (buffer, "%s-store-%s/%s", invo_name, ci -> ci_type,
			ci -> ci_subtype);
	if ((cp = m_find (buffer)) == NULL || *cp == 0) {
	    (void) sprintf (buffer, "%s-store-%s", invo_name, ci -> ci_type);
	    if ((cp = m_find (buffer)) == NULL || *cp == 0)
		cp = ct -> c_type == CT_MESSAGE ? "+" : "%m%P.%s";
	}
    }

    switch (*cp) {
	case '+':
	case '@':
	    {
		char   *folder = cp[1] ? path (cp + 1, *cp == '+' ? TFOLDER 
							          : TSUBCWF)
				       : m_getfolder ();
		struct msgs *mp = NULL;
		struct stat st;

		if (stat (bp = m_mailpath (folder), &st) == NOTOK) {
		    int	    answer;
		    char   *ep;

		    if (errno != ENOENT) {
			advise (bp, "error on folder");
			goto losing_folder;
		    }

		    ep = concat ("Create folder \"", bp, "\"? ", NULLCP);
		    answer = getanswer (ep);
		    free (ep);

		    if (!answer)
			goto losing_folder;
		    if (!makedir (bp)) {
			advise (NULLCP, "unable to create folder %s", bp);
			goto losing_folder;
		    }
		}

		if (mp = m_gmsg (folder))
		    (void) sprintf (buffer, "%s/%d", mp -> foldpath,
				   mp -> hghmsg + 1);
		else
		    advise (NULLCP, "unable to read folder %s", folder);
losing_folder: ;
		if (cp[1])
		    free (folder);
		if (mp)
		    m_fmsg (mp);
		else
		    return NOTOK;
	    }
	    goto got_filename;

	case '/':
	case '|':
	case '!':
	    bp = buffer;
	    buffer[0] = '\0';
	    break;

	default:
	    bp = autosw ? cwd : dir;
	    (void) sprintf (buffer, "%s/", bp[1] ? bp : "");
	    bp = buffer + strlen (buffer);
	    break;
    }
    for (; *cp; cp++)
	if (*cp == '%') {
	    switch (*++cp) {
		case 'a':	/* additional arguments */
		    if (buffer[0] != '|' && buffer[0] != '!') {
			*bp++ = *--cp;
			*bp = '\0';
			continue;
		    }
		    else {
			register char **ap,
				      **ep;
			char   *s = "";

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

		case 'm':	/* message */
		    (void) sprintf (bp, "%s", r1bindex (ct -> c_file, '/'));
		    break;

		case 'P':	/* .part */
		    if (ct -> c_partno)
			(void) sprintf (bp, ".%s", ct -> c_partno);
		    break;

		case 'p':	/* part */
		    if (ct -> c_partno)
			(void) strcpy (bp, ct -> c_partno);
		    break;

		case 't':	/* type */
		    (void) strcpy (bp, ci -> ci_type);
		    break;

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

		case '%':
		    goto raw;

		default:
		    *bp++ = *--cp;
		    *bp = '\0';
		    continue;
	    }
	    bp += strlen (bp);
	}
	else {
raw: ;
	    *bp++ = *cp;
	    *bp = '\0';
	}
    if (buffer[0] == '|' || buffer[0] == '!')
	return show_content_aux (ct, 1, 0, buffer + 1, autosw ? cwd : dir);
got_filename: ;

    ct -> c_storage = add (buffer, NULLCP);
/*
    if (debugsw) {
 */
	(void) fflush (stdout);

	fprintf (stderr, "storing message %s", ct -> c_file);
	if (ct -> c_partno)
	    fprintf (stderr, " part %s", ct -> c_partno);
	fprintf (stderr, " as file %s\n",
		 strncmp (ct -> c_storage, cwd, cwdlen)
			|| ct -> c_storage[cwdlen] != '/'
		    ? ct -> c_storage
		    : ct -> c_storage + cwdlen + 1);
/*
    }
 */

    if (ct -> c_encoding != CE_7BIT) {
	int	cc,
		fd;

	if (!ct -> c_ceopenfnx) {
	    advise (NULLCP, "don't know how to decode part %s of message %s",
		    ct -> c_partno, ct -> c_file);
	    return NOTOK;
	}

	file = appending || !strcmp (ct -> c_storage, "-") ? NULLCP
							   : ct -> c_storage;
	if ((fd = (*ct -> c_ceopenfnx) (ct, &file)) == NOTOK)
	    return NOTOK;
	if (strcmp (file, ct -> c_storage) == 0) {
	    (void) (*ct -> c_ceclosefnx) (ct);
	    return OK;
	}

	if (!strcmp (ct -> c_storage, "-")) {
	    int	    gd;

	    if ((gd = dup (fileno (stdout))) == NOTOK) {
		advise ("stdout", "unable to dup");
losing: ;
		(void) (*ct -> c_ceclosefnx) (ct);
		return NOTOK;
	    }
	    if ((fp = fdopen (gd, appending ? "a" : "w")) == NULL) {
		advise ("stdout", "unable to fdopen (%d, \"%s\") from", gd,
			appending ? "a" : "w");
		(void) close (gd);
		goto losing;
	    }
	}
	else
	    if ((fp = fopen (ct -> c_storage, appending ? "a" : "w"))
		    == NULL) {
		advise (ct -> c_storage, "unable to fopen for %s",
			appending ? "appending" : "writing");
		goto losing;
	    }

	if (append && !*append)
	    (void) copy_some_headers (fp, ct);

	for (;;) {
	    switch (cc = read (fd, buffer, sizeof buffer)) {
		case NOTOK:
		    advise (file, "error reading content from");
		    break;

		case OK:
		    break;

		default:
		    (void) fwrite (buffer, sizeof *buffer, cc, fp);
		    continue;
	    }
	    break;
	}

	(void) (*ct -> c_ceclosefnx) (ct);

	if (cc != NOTOK && fflush (fp))
	    advise (ct -> c_storage, "error writing to");

	(void) fclose (fp);

	return (cc != NOTOK ? OK : NOTOK);
    }

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

    (void) fseek (ct -> c_fp, pos = ct -> c_begin, 0);
    last = ct -> c_end;

    if (!strcmp (ct -> c_storage, "-")) {
	int	gd;

	if ((gd = dup (fileno (stdout))) == NOTOK) {
	    advise ("stdout", "unable to dup");
	    return NOTOK;
	}
	if ((fp = fdopen (gd, appending ? "a" : "w")) == NULL) {
	    advise ("stdout", "unable to fdopen (%d, \"%s\") from", gd,
		    appending ? "a" : "w");
	    (void) close (gd);
	    return NOTOK;
	}
    }
    else
	if ((fp = fopen (ct -> c_storage, appending ? "a" : "w")) == NULL) {
	    advise (ct -> c_storage, "unable to fopen for %s",
		    appending ? "appending" : "writing");
	    return NOTOK;
	}

    if (append && !*append) {
	(void) copy_some_headers (fp, ct);
	appending = 1;
    }
    else
	appending = 0;

    while (fgets (buffer, sizeof buffer - 1, ct -> c_fp)) {
	if ((pos += strlen (buffer)) > last) {
	    int	    diff = strlen (buffer) - (pos - last);

	    if (diff >= 0)
		buffer[diff] = '\0';
	}

	if (appending)
	    switch (buffer[0]) {
		case ' ':
		case '\t':
		    if (appending < 0)
			buffer[0] = 0;
		    break;

		case '\n':
		    appending = 0;
		    break;

		default:
		    if (!uprf (buffer, XXX_FIELD_PRF)
			    && !uprf (buffer, "Message-ID:")) {
			appending = -1;
			buffer[0] = 0;
			break;
		    }
		    appending = 1;
		    break;
	    }

	(void) fputs (buffer, fp);
	if (pos >= last)
	    break;
    }

    if (fflush (fp))
	advise (ct -> c_storage, "error writing to");

    (void) fclose (fp);

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

    return OK;
}


static int  copy_some_headers (out, ct)
FILE   *out;
register CT	ct;
{
    int	    state;
    char    buf[BUFSIZ],
	    name[NAMESZ];
    FILE   *in;

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

    for (state = FLD;;) {
	switch (state = m_getfld (state, name, buf, sizeof buf, in)) {
	    case FLD:
	    case FLDPLUS:
	    case FLDEOF:
		if (uprf (name, XXX_FIELD_PRF) || uleq (name, "Message-ID")) {
		    while (state == FLDPLUS)
			state = m_getfld (state, name, buf, sizeof buf, in);
		    continue;
		}

		fprintf (out, "%s:%s", name, buf);
		while (state == FLDPLUS) {
		    state = m_getfld (state, name, buf, sizeof buf, in);
		    (void) fputs (buf, out);
		}
		if (state != FLDEOF)
		    continue;
		/* else fall... */
	   case BODY:
	   case BODYEOF:
	   case FILEEOF:
		break;

	   case LENERR:
	   case FMTERR:
	   default:
		break;
	}

	break;
    }

    (void) fclose (in);

    return OK;
}

/*  */

static void  free_ctinfo (ct)
register CT	ct;
{
    register char **ap;
    register CI	ci = &ct -> c_ctinfo;

    if (ci -> ci_type)
	free (ci -> ci_type);
    if (ci -> ci_subtype)
	free (ci -> ci_subtype);
    for (ap = ci -> ci_attrs; *ap; ap++)
	free (*ap);
    if (ci -> ci_comment)
	free (ci -> ci_comment);
    if (ci -> ci_magic)
	free (ci -> ci_magic);
}


static void  free_content (ct)
register 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)
	(void) (*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)
	(void) (*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)
	    (void) unlink (ct -> c_file);
	free (ct -> c_file);
    }
    if (ct -> c_fp)
	(void) fclose (ct -> c_fp);

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

    free ((char *) ct);
}

/*  */

static int  part_ok (ct)
register CT	ct;
{
    register char **ap;

    if (ct -> c_type == CT_MULTIPART || npart == 0)
	return 1;

    for (ap = parts; *ap; ap++)
	if (strcmp (*ap, ct -> c_partno) == 0)
	    return 1;

    return 0;
}



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

    if (ct -> c_type == CT_MULTIPART || ntype == 0)
	return 1;

    (void) sprintf (buffer, "%s/%s", ci -> ci_type, ci -> ci_subtype);
    for (ap = types; *ap; ap++)
	if (uleq (*ap, ci -> ci_type) || uleq (*ap, buffer))
	    return 1;

    return 0;
}

/*    CONTENTS */

struct k2v {
    char   *kv_key;
    int	    kv_value;
};

/*    TEXT */

#define	TEXT_UNKNOWN	0x00
#define	TEXT_PLAIN	0x01
#define	TEXT_RICHTEXT	0x02

struct text {
    int	    tx_charset;
#define	CHARSET_UNKNOWN	0x00
#define	CHARSET_USASCII	0x01
#define	CHARSET_LATIN	0x02
};

static struct k2v	Charset[] = {
    "us-ascii",	    CHARSET_USASCII,
    "iso-8859-1",   CHARSET_LATIN,

    NULL,	    CHARSET_UNKNOWN		/* this one must be last! */
};

static int  free_text (ct)
register CT	ct;
{
    register struct text *t = (struct text *) ct -> c_ctparams;

    if (!t)
	return;

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


static struct k2v	SubText[] = {
    "plain",	    TEXT_PLAIN,
    "richtext",	    TEXT_RICHTEXT,

    NULL,	    TEXT_UNKNOWN		/* this one must be last! */
};

static int  InitText (ct)
register CT	ct;
{
    char   buffer[BUFSIZ];
    register char **ap,
		  **ep;
    register struct k2v *kv;
    register CI	ci = &ct -> c_ctinfo;

    if (!*ci -> ci_subtype)	/* XXX: attmail bogosity! */
	ci -> ci_subtype = add ("plain", ci -> ci_subtype);
    for (kv = SubText; kv -> kv_key; kv++)
	if (uleq (ci -> ci_subtype, kv -> kv_key))
	    break;
    if ((ct -> c_subtype = kv -> kv_value) == TEXT_PLAIN) {
	(void) sprintf (buffer, "%%p%s '%%F'",
			progsw ? progsw
			       : moreproc && *moreproc ? moreproc : "more");
	ct -> c_showproc = add (buffer, NULLCP);
    }

    for (ap = ci -> ci_attrs, ep = ci -> ci_values; *ap; ap++, ep++)
	if (uleq (*ap, "charset")) {
	    char   *cp;
	    register struct text *t;

	    if ((t = (struct text *) calloc (1, sizeof *t)) == NULL)
		adios (NULLCP, "out of memory");
	    ct -> c_ctparams = (caddr_t) t;
	    ct -> c_ctfreefnx = free_text;

	    for (kv = Charset; kv -> kv_key; kv++)
		if (uleq (*ep, kv -> kv_key)) {
		    (void) sprintf (buffer, "%s-charset-%s", invo_name,
				    kv -> kv_key);
		    break;
		}
	    t -> tx_charset = kv -> kv_value;
	    if (!kv -> kv_key)
		(void) sprintf (buffer, "%s-charset-%s", invo_name, *ep);
	    if ((!mm_charset || !uleq (mm_charset, *ep))
		    && (cp = m_find (buffer)))
		ct -> c_termproc = getcpy (cp);
	    
	    break;
	}

    return OK;
}

/*    MULTIPART */

#define	MULTI_UNKNOWN	0x00
#define	MULTI_MIXED	0x01
#define	MULTI_ALTERNATE	0x02
#define	MULTI_DIGEST	0x03
#define	MULTI_PARALLEL	0x04


struct multipart {
    char   *mp_start;
    char   *mp_stop;

    struct part {
	CT  mp_part;

	struct part *mp_next;
    }	   *mp_parts;
};


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

    (void) list_content (ct, toplevel);

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

	if (part_ok (p) && type_ok (p) && p -> c_ctlistfnx)
	    (void) (*p -> c_ctlistfnx) (p, 0);
    }

    return OK;
}


static int  show_multi (ct, serial, alternate)
register CT	ct;
int	serial,
	alternate;
{
    int	    alternating,
	    nowalternate,
	    nowserial,
	    result;
    register struct multipart *m = (struct multipart *) ct -> c_ctparams;
    register struct part *part;
    register CT  p;
    TYPESIG (*hstat) (), (*istat) (), (*qstat) (), (*tstat) ();

    alternating = 0;
    nowalternate = alternate;
    switch (ct -> c_subtype) {
	case MULTI_PARALLEL:
	    if (!(nowserial = serialsw)) {
set_signals: ;
		hstat = signal (SIGHUP, SIG_IGN);
		istat = signal (SIGINT, SIG_IGN);
		qstat = signal (SIGQUIT, SIG_IGN);
		tstat = signal (SIGTERM, SIG_IGN);
	    }
	    break;

	case MULTI_ALTERNATE:
	    nowalternate = alternating = 1;
	    /* and fall... */
	default:
	    if (!(nowserial = serial))
		goto set_signals;
	    break;
    }

/* alternate -> we're inside an alternative
   alternating -> we are an alternative
 */

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

	if (part_ok (p)
	        && type_ok (p)
	        && p -> c_ctshowfnx) {
	    switch ((*p -> c_ctshowfnx) (p, nowserial, nowalternate)) {
		case NOTOK:
		    if (alternate && !alternating) {
			result = NOTOK;
			goto out;
		    }
		    continue;

		case OK:
		case DONE:
		    if (alternating) {
			result = DONE;
			break;
		    }
		    if (alternate)
			alternate = nowalternate = 0;
		    continue;
	    }
	    break;
	}
    }
    if (alternating && !part) {
	if (!alternate)
	    content_error (NULLCP, ct,
			   "don't know how to display any of the contents");

	result = NOTOK;
	goto out;
    }

    if (serial && !nowserial) {
	int	pid,
		kids;
#if defined(BSD42) && !defined(WAITINT)
	union wait status;
#else
	int     status;
#endif

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

	    if (p -> c_pid > OK)
		if (kill (p -> c_pid, 0) == NOTOK)
		    p -> c_pid = 0;
		else
		    kids++;
	}

	while (kids > 0 && (pid = wait (&status)) != NOTOK) {
#if defined(BSD42) && !defined(WAITINT)
	    (void) pidcheck (status.w_status);
#else
	    (void) pidcheck (status);
#endif

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

		if (xpid == pid)
		    xpid = 0;
		if (p -> c_pid == pid) {
		    p -> c_pid = 0;
		    kids--;
		    break;
		}
	    }
	}
    }

out: ;
    if (!nowserial) {
	(void) signal (SIGHUP, hstat);
	(void) signal (SIGINT, istat);
	(void) signal (SIGQUIT, qstat);
	(void) signal (SIGTERM, tstat);
    }

    return result;
}


/* ARGSUSED */

static int  store_multi (ct, unused)
register CT	ct;
char   *unused;
{
    int	    result;
    register struct multipart *m = (struct multipart *) ct -> c_ctparams;
    register struct part *part;

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

	if (part_ok (p)
	        && type_ok (p)
	        && p -> c_ctstorefnx
	        && (result = (*p -> c_ctstorefnx) (p, NULLCP)) == OK
	        && ct -> c_subtype == MULTI_ALTERNATE)
	    break;
    }

    return result;
}


static int  free_multi (ct)
register CT	ct;
{
    register struct multipart *m = (struct multipart *) ct -> c_ctparams;
    register 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;
}


static struct k2v SubMultiPart[] = {
    "mixed",	    MULTI_MIXED,
    "alternative",  MULTI_ALTERNATE,
    "digest",	    MULTI_DIGEST,
    "parallel",	    MULTI_PARALLEL,

    NULL,	    MULTI_UNKNOWN		/* this one must be last! */
};

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

    ct -> c_ctshowfnx = NULL;
    ct -> c_ctstorefnx = NULL;

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

    for (kv = SubMultiPart; kv -> kv_key; kv++)
	if (uleq (ci -> ci_subtype, kv -> kv_key))
	    break;
    ct -> c_subtype = kv -> kv_value;

    for (ap = ci -> ci_attrs, ep = ci -> ci_values; *ap; ap++, ep++)
	if (uleq (*ap, "boundary")) {
	    bp = *ep;
	    break;
	}
    if (!*ap) {
	advise (NULLCP,
		"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 (NULLCP, "out of memory");
    ct -> c_ctparams = (caddr_t) m;
    ct -> c_ctlistfnx = list_multi;
    ct -> c_ctshowfnx = show_multi;
    ct -> c_ctstorefnx = store_multi;
    ct -> c_ctfreefnx = free_multi;
    
    for (cp = bp; isspace (*cp); cp++)
	continue;
    if (!*cp) {
	advise (NULLCP, "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", NULLCP);
    m -> mp_stop = concat (bp, "--\n", NULLCP);

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

    (void) fseek (fp = ct -> c_fp, pos = ct -> c_begin, 0);
    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 (NULLCP, "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)) == NULLCT) {
		(void) fclose (ct -> c_fp), ct -> c_fp = NULL;
		return NOTOK;
	    }
	    p -> c_fp = NULL;

	    part -> mp_part = p;
	    (void) fseek (fp, pos = p -> c_begin, 0);
	    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 (NULLCP, "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) {
	register int	i;
	register struct part **base,
			     **bmp;

	i = 0;
	for (part = m -> mp_parts; part; part = part -> mp_next)
	    i++;
	if ((base = (struct part **) calloc ((unsigned) (i + 1), sizeof *base))
	        == NULL)
	    adios (NULLCP, "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;
	register char *pp;
	char    partnam[BUFSIZ];

	if (ct -> c_partno) {
	    (void) 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;

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

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

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

    return OK;
}

/*    MESSAGE */

#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 int  list_partial (ct, toplevel)
register CT	ct;
int	toplevel;
{
    register struct partial *p = (struct partial *) ct -> c_ctparams;

    (void) 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  ct_compar (a, b)
CT   *a,
     *b;
{
    register struct  partial *am = (struct partial *) ((*a) -> c_ctparams);
    register struct  partial *bm = (struct partial *) ((*b) -> c_ctparams);

    return (am -> pm_marked - bm -> pm_marked);
}


/* ARGSUSED */

static int  store_partial (ct, unused)
register CT	ct;
char   *unused;
{
    int	    cur,
	    hi,
	    i;
    register CT	    p,
		   *ctp,
		   *ctq;
    CT	   *base;
    struct partial *qm = (struct partial *) ct -> c_ctparams;

    if (qm -> pm_stored)
	return OK;

    hi = i = 0;
    for (ctp = cts; p = *ctp; ctp++)
	if (p -> c_type == CT_MESSAGE && p -> c_subtype == ct -> c_subtype) {
	    register struct partial *pm = (struct partial *) p -> c_ctparams;

	    if (!pm -> pm_stored
	            && strcmp (qm -> pm_partid, pm -> pm_partid) == 0) {
		pm -> pm_marked = pm -> pm_partno;
		if (pm -> pm_maxno)
		    hi = pm -> pm_maxno;
		pm -> pm_stored = 1;
		i++;
	    }
	    else
		pm -> pm_marked = 0;
	}
    if (hi == 0) {
	advise (NULLCP, "missing (at least) last part of multipart message");
	return NOTOK;
    }

    if ((base = (CT *) calloc ((unsigned) (i + 1), sizeof *base)) == NULL)
	adios (NULLCP, "out of memory");

    ctq = base;
    for (ctp = cts; p = *ctp; ctp++)
	if (p -> c_type == CT_MESSAGE && p -> c_subtype == ct -> c_subtype) {
	    register struct partial *pm = (struct partial *) p -> c_ctparams;

	    if (pm -> pm_marked)
		*ctq++ = p;
	}
    *ctq = NULL;

    if (i > 1)
	qsort ((char *) base, i, sizeof *base, ct_compar);

    cur = 1;
    for (ctq = base; p = *ctq; ctq++) {
	register struct partial *pm = (struct partial *) p -> c_ctparams;

	if (pm -> pm_marked != cur) {
	    if (pm -> pm_marked == cur - 1) {
		admonish (NULLCP,
			  "duplicate part %d of %d part multipart message",
			  pm -> pm_marked, hi);
		continue;
	    }

missing_part: ;
	    advise (NULLCP,
		    "missing %spart %d of %d part multipart message",
		    cur != hi ? "(at least) " : "", cur, hi);
	    goto losing;
	}
        else
	    cur++;
    }
    if (hi != --cur) {
	cur = hi;
	goto missing_part;
    }

    ctq = base;
    ct = *ctq++;
    if (store_content (ct, "") == NOTOK) {
losing: ;
	free ((char *) base);
	return NOTOK;
    }

    for (; p = *ctq; ctq++)
	if (store_content (p, ct -> c_storage) == NOTOK)
	    goto losing;

    free ((char *) base);
    return OK;
}


static int  free_partial (ct)
register CT	ct;
{
    register 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;
}


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;

    char   *eb_server;
    char   *eb_body;

    unsigned long
	    eb_size;
};


static	int	openFile ();
static	int	openFTP ();
static	int	openMail ();

/* NOTE WELL: si_key MUST NOT have value of NOTOK */

static struct str2init str2methods[] = {
    "afs",	   1,	openFile,
    "anon-ftp",	   1,	openFTP,
    "ftp",	   0,	openFTP,
    "local-file",  0,	openFile,
    "mail-server", 0,	openMail,

    NULL
};


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

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

	    for (s2i = str2methods; s2i -> si_key; s2i++)
		if (uleq (*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_value;
	    p -> c_encoding = CE_EXTERNAL;
	    if (init_encoding (p, s2i -> si_init) == NOTOK)
		return NOTOK;
	    continue;
	}
	if (uleq (*ap, "name")) {
	    e -> eb_name = *ep;
	    continue;
	}
	if (uleq (*ap, "permission")) {
	    e -> eb_permission = *ep;
	    continue;
	}
	if (uleq (*ap, "site")) {
	    e -> eb_site = *ep;
	    continue;
	}
	if (uleq (*ap, "directory")) {
	    e -> eb_dir = *ep;
	    continue;
	}
	if (uleq (*ap, "mode")) {
	    e -> eb_mode = *ep;
	    continue;
	}
	if (uleq (*ap, "size")) {
	    (void) sscanf (*ep, "%lu", &e -> eb_size);
	    continue;
	}
	if (uleq (*ap, "server")) {
	    e -> eb_server = *ep;
	    continue;
	}
	if (composing && uleq (*ap, "body")) {
	    e -> eb_body = getcpy (*ep);
	    continue;
	}
    }

    if (!e -> eb_access) {
	advise (NULLCP,
		"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_external (ct, toplevel)
register CT	ct;
int	toplevel;
{
    register struct exbody *e = (struct exbody *) ct -> c_ctparams;

    (void) 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);
	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");
    }
    (void) list_content (e -> eb_content, 0);

    return OK;
}


static int  show_external (ct, serial, alternate)
register CT	ct;
int	serial,
	alternate;
{
    register struct exbody *e = (struct exbody *) ct -> c_ctparams;
    register CT	    p = e -> eb_content;

    if (!type_ok (p))
	return OK;

    if (p -> c_ctshowfnx)
	return (*p -> c_ctshowfnx) (p, serial, alternate);

    content_error (NULLCP, p, "don't know how to display content");
    return NOTOK;
}


static int  store_external (ct)
register CT	ct;
{
    int	    result = NOTOK;
    register struct exbody *e = (struct exbody *) ct -> c_ctparams;
    register CT	    p = e -> eb_content;

    if (!type_ok (p))
	return OK;

    p -> c_partno = ct -> c_partno;
    if (p -> c_ctstorefnx)
	result = (*p -> c_ctstorefnx) (p, NULLCP);
    p -> c_partno = NULL;

    return result;
}


static int  free_external (ct)
register CT	ct;
{
    register 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;
}


static struct k2v SubMessage[] = {
    "rfc822",	     MESSAGE_RFC822,
    "partial", 	     MESSAGE_PARTIAL,
    "external-body", MESSAGE_EXTERNAL,

    NULL,	     MESSAGE_UNKNOWN		/* this one must be last! */
};

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

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

    if (!*ci -> ci_subtype)	/* XXX: attmail bogosity! */
	ci -> ci_subtype = add ("rfc822", ci -> ci_subtype);
    for (kv = SubMessage; kv -> kv_key; kv++)
	if (uleq (ci -> ci_subtype, kv -> kv_key))
	    break;

    switch (ct -> c_subtype = kv -> kv_value) {
	case MESSAGE_RFC822:
	    ct -> c_showproc = add ("%pshow -file '%F'", NULLCP);
	    break;

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

		ct -> c_ctshowfnx = NULL;
		ct -> c_ctstorefnx = NULL;

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

		for (ap = ci -> ci_attrs, ep = ci -> ci_values;
		         *ap; 
		         ap++, ep++) {
		    if (uleq (*ap, "id")) {
			p -> pm_partid = add (*ep, NULLCP);

			continue;
		    }

		    if (uleq (*ap, "number")) {
			if (sscanf (*ep, "%d", &p -> pm_partno) != 1
			        || p -> pm_partno < 1) {
invalid_param: ;
			    advise (NULLCP,
				    "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 (uleq (*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 (NULLCP,
			    "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;
		ct -> c_ctstorefnx = store_partial;
	    }
	    break;

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

		ct -> c_ctshowfnx = NULL;
		ct -> c_ctstorefnx = NULL;

		if ((e = (struct exbody *) calloc (1, sizeof *e)) == NULL)
		    adios (NULLCP, "out of memory");
		ct -> c_ctparams = (caddr_t) 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;
		}

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

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

		e -> eb_parent = ct;
		e -> eb_content = p;
		p -> c_ctextern = (caddr_t) 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) {
			content_error (NULLCP, ct,
				       "empty body for access-type=mail-server");
			goto no_body;
		    }
		    
		    if ((e -> eb_body = bp = malloc ((unsigned) size)) == NULL)
			adios (NULLCP, "out of memory");
		    (void) fseek (p -> c_fp, p -> c_begin, 0);
		    while (size > 0)
			switch (cc = fread (bp, sizeof *bp, size, p -> c_fp)) {
			    case NOTOK:
			        adios ("failed", "fread");

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

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

		(void) 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 (e -> eb_name && autosw) {
		    char   *cp = e -> eb_name;

		    if (*cp != '/'
			    && *cp != '.'
			    && *cp != '|'
			    && *cp != '!'
			    && !index (cp, '%')) {
			if (!ct -> c_storeproc)
			    ct -> c_storeproc = add (cp, NULLCP);
			if (!p -> c_storeproc)
			    p -> c_storeproc = add (cp, NULLCP);
		    }
		}

		ct -> c_ctshowfnx = show_external;
		ct -> c_ctstorefnx = store_external;
		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)
			    (void) (*p -> c_ctinitfnx) (p);
			break;
		}
	    }
	    break;

	default:
	    break;
    }

    return OK;
}


/*    APPLICATION */

#define	APPLICATION_UNKNOWN	0x00
#define	APPLICATION_OCTETS	0x01
#define	APPLICATION_ODA		0x02
#define	APPLICATION_POSTSCRIPT	0x03


static int  list_application (ct, toplevel)
register CT	ct;
int	toplevel;
{
    (void) list_content (ct, toplevel);
    if (verbosw) {
	register char **ap,
		      **ep;
	register 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;
}


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

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

    ct -> c_ctlistfnx = list_application;
    
    for (kv = SubApplication; kv -> kv_key; kv++)
	if (uleq (ci -> ci_subtype, kv -> kv_key))
	    break;

    if (autosw && !ct -> c_storeproc)
	for (ap = ci -> ci_attrs, ep = ci -> ci_values; *ap; ap++, ep++)
	    if (uleq (*ap, "name")) {
		register char   *cp;

		if (*(cp = *ep) != '/'
		        && *cp != '.'
		        && *cp != '|'
		        && *cp != '!'
		        && !index (cp, '%'))
		    ct -> c_storeproc = add (cp, NULLCP);
		break;
	    }

    if ((ct -> c_subtype = kv -> kv_value) == APPLICATION_OCTETS) {
	int	tarP,
		zP;

	tarP = zP = 0;
	for (ap = ci -> ci_attrs, ep = ci -> ci_values; *ap; ap++, ep++) {
	    if (uleq (*ap, "type")) {
		if (!uleq (*ep, "tar"))
		    break;

		tarP = 1;
		continue;
	    }

	    if (uleq (*ap, "conversions")
		    && (uleq (*ep, "compress") || uleq (*ep, "x-compress"))) {
		zP = 1;
		continue;
	    }
	}

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

    return OK;
}

/*    ENCODINGS */

struct cefile {
    char   *ce_file;
    int	    ce_unlink;

    FILE   *ce_fp;
};


static int  list_encoding (ct)
register CT	ct;
{
    register struct cefile *ce = (struct cefile *) ct -> c_ceparams;

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

    return OK;
}


static int  close_encoding (ct)
register CT	ct;
{
    register struct cefile *ce = (struct cefile *) ct -> c_ceparams;

    if (!ce)
	return;

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


static unsigned long  size_encoding (ct)
register CT	ct;
{
    int	    fd;
    unsigned long size;
    char   *file;
    register struct cefile *ce = (struct cefile *) ct -> c_ceparams;
    struct stat st;

    if (!ce) {
estimate: ;

	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)
	return stat (ce -> ce_file, &st) != NOTOK ? (long) st.st_size : 0L;

    if (ct -> c_encoding == CE_EXTERNAL)
	goto estimate;

    file = NULL;
    if ((fd = (*ct -> c_ceopenfnx) (ct, &file)) == NOTOK)
	goto estimate;

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

    (*ct -> c_ceclosefnx) (ct);

    return size;
}


static int  free_encoding (ct, toplevel)
register CT	ct;
int	toplevel;
{
    register struct cefile *ce = (struct cefile *) ct -> c_ceparams;

    if (!ce)
	return;

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

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

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


static	init_encoding (ct, openfnx)
register CT	ct;
int   (*openfnx) ();
{
    register struct cefile *ce;

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

    ct -> c_ceparams = (caddr_t) 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;
}

/*    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  openBase64 (ct, file)
register CT	ct;
char  **file;
{
    int	    bitno,
	    cc,
	    fd,
	    len,
	    skip;
    unsigned long    bits;
    register char  *cp,
		   *ep;
    unsigned char   value,
		   *b = (unsigned char *) &bits,
		   *b1 = &b[endian > 0 ? 1 : 2],
		   *b2 = &b[endian > 0 ? 2 : 1],
		   *b3 = &b[endian > 0 ? 3 : 0];
    char    buffer[BUFSIZ];
    register struct cefile *ce = (struct cefile *) ct -> c_ceparams;

    if (ce -> ce_fp)
	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;
	}

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

    ce -> ce_unlink = *file == NULL;
    if ((ce -> ce_fp = fopen (ce -> ce_file =
				    add (*file ? *file : m_scratch ("", tmp),
					 NULLCP),
			      "w+")) == NULL) {
	content_error (ce -> ce_file, ct,
		       "unable to fopen for writing and reading");
	return NOTOK;
    }

    if ((len = ct -> c_end - ct -> c_begin) < 0)
	adios (NULLCP, "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;
    }
    
    (void) lseek (fd = fileno (ct -> c_fp), (off_t)ct -> c_begin, 0);
    bitno = 18, bits = 0L, skip = 0;
    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 (NULLCP, 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,
					     (long) lseek (fd, (off_t)0, 1) - (ep - cp),
					     skip);
				content_error (NULLCP, ct,
				      "invalid BASE64 encoding -- continuing");
				continue;
			    }

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

				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 (NULLCP, ct, "invalid BASE64 encoding");
	goto clean_up;
    }
self_delimiting: ;
    (void) fseek (ct -> c_fp, 0L, 0);

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

ready_to_go: ;
    (void) fseek (ce -> ce_fp, 0L, 0);
    *file = ce -> ce_file;
    return fileno (ce -> ce_fp);

clean_up: ;
    free_encoding (ct, 0);

    return NOTOK;
}


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


static int  set_endian ()
{
    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");

    mm_charset = getenv ("MM_CHARSET");
}

/*    QUOTED */

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  openQuoted (ct, file)
register CT	ct;
char  **file;
{
    int	    cc,
	    len,
	    quoted;
    register char  *cp,
		   *ep;
    char    buffer[BUFSIZ];
    unsigned char mask;
    register struct cefile *ce = (struct cefile *) ct -> c_ceparams;

    if (ce -> ce_fp)
	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;
	}

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

    ce -> ce_unlink = *file == NULL;
    if ((ce -> ce_fp = fopen (ce -> ce_file =
				    add (*file ? *file : m_scratch ("", tmp),
					 NULLCP),
			      "w+")) == NULL) {
	content_error (ce -> ce_file, ct,
		       "unable to fopen for writing and reading");
	return NOTOK;
    }

    if ((len = ct -> c_end - ct -> c_begin) < 0)
	adios (NULLCP, "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;
    }

    (void) fseek (ct -> c_fp, ct -> c_begin, 0);
    quoted = 0;
#ifdef	lint
    mask = 0;
#endif
    while (len > 0) {
	char   *dp;

	if (fgets (buffer, sizeof buffer - 1, ct -> c_fp) == NULL) {
	    content_error (NULLCP, 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];
		    (void) putc (mask, ce -> ce_fp);
		}
		else
		    switch (*cp) {
		        case ':':
			    (void) putc (*cp, ce -> ce_fp);
			    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 (NULLCP, 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':
		    (void) putc (*cp, ce -> ce_fp);
		    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 (NULLCP, ct,
		       "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
	goto clean_up;
    }
    (void) fseek (ct -> c_fp, 0L, 0);

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

ready_to_go: ;
    (void) fseek (ce -> ce_fp, 0L, 0);
    *file = ce -> ce_file;
    return fileno (ce -> ce_fp);

clean_up: ;
    free_encoding (ct, 0);

    return NOTOK;
}


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

/*    7BIT */

static int  open7Bit (ct, file)
register CT	ct;
char  **file;
{
    int	    cc,
	    fd,
	    len;
    char    buffer[BUFSIZ];
    register struct cefile *ce = (struct cefile *) ct -> c_ceparams;

    if (ce -> ce_fp)
	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;
	}

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

    ce -> ce_unlink = *file == NULL;
    if ((ce -> ce_fp = fopen (ce -> ce_file =
				    add (*file ? *file : m_scratch ("", tmp),
					 NULLCP),
			      "w+")) == NULL) {
	content_error (ce -> ce_file, ct,
		       "unable to fopen for writing and reading");
	return NOTOK;
    }

    if ((len = ct -> c_end - ct -> c_begin) < 0)
	adios (NULLCP, "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;
    }

    (void) lseek (fd = fileno (ct -> c_fp), (off_t) ct -> c_begin, 0);
    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 (NULLCP, ct, "premature eof");
	        goto clean_up;

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

		(void) 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;
		}
	}
    (void) fseek (ct -> c_fp, 0L, 0);

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

ready_to_go: ;
    (void) fseek (ce -> ce_fp, 0L, 0);
    *file = ce -> ce_file;
    return fileno (ce -> ce_fp);

clean_up: ;
    free_encoding (ct, 0);

    return NOTOK;
}


static int  Init7Bit (ct)
register CT	ct;
{
    if (init_encoding (ct, open7Bit) == NOTOK)
	return NOTOK;
    ct -> c_cesizefnx = NULL;

    return OK;
}

/*    External */

static int  openExternal (ct, ce, file, fd)
register CT	ct;
struct cefile *ce;
char  **file;
int    *fd;
{
    char   *id;

    if (ce -> ce_fp) {
	(void) fseek (ce -> ce_fp, 0L, 0);

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

    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 (xpid) {
	(void) pidcheck (pidwait (xpid, NOTOK));
	xpid = 0;
    }

    if (cache && (id = ct -> c_id)) {
	char    buffer[BUFSIZ];

	(void) sprintf (buffer, "%s/%s", cache, id = trimcpy (id));
	free (id);

	id = getcpy (buffer);
	if (ce -> ce_fp = fopen (id, "r")) {
	    struct stat	st;

	    (void) fstat (fileno (ce -> ce_fp), &st);
	    (void) sprintf (buffer,
			    "Use cached copy %s of size %lu octets (content %s)? ",
			    id, (unsigned long) st.st_size,
			    ct -> c_partno);
	    if (getanswer (buffer)) {
		ce -> ce_unlink = 0;
		ce -> ce_file = id;
		goto ready_already;
	    }

	    (void) fclose (ce -> ce_fp), ce -> ce_fp = NULL;
	}
	free (id);
    }

    return OK;
}

/*    File */

static int  openFile (ct, file)
register CT	ct;
char  **file;
{
    int	    fd;
    char   *id;
    register struct exbody *e = (struct exbody *) ct -> c_ctextern;
    register struct cefile *ce = (struct cefile *) ct -> c_ceparams;

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

	case OK:
	    break;

	case DONE:
	    return fd;
    }

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

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

    if (cache
	    && (id = e -> eb_parent -> c_id)
	    && (!e -> eb_permission
		    || !uleq (e -> eb_permission, "read-write"))) {
	char	buffer[BUFSIZ];

	(void) sprintf (buffer, "Make cached, publically-accessible copy of %s? ",
			e -> eb_name);
	if (getanswer (buffer)) {
	    int	    mask;
	    char    cachefile[BUFSIZ];
	    FILE   *fp;

	    (void) sprintf (cachefile, "%s/%s", cache, id = trimcpy (id));
	    free (id);

	    mask = umask (0022);
	    if (fp = fopen (cachefile, "w")) {
		register FILE   *gp = ce -> ce_fp;

		(void) fseek (gp, 0L, 0);

		while (fgets (buffer, sizeof buffer - 1, gp))
		    (void) fputs (buffer, fp);
		(void) fflush (fp);

		if (ferror (gp)) {
		    admonish (ce -> ce_file, "error reading");
		    (void) unlink (cachefile);
		}

		if (ferror (fp)) {
		    admonish (cachefile, "error writing");
		    (void) unlink (cachefile);
		}
		(void) fclose (fp);
	    }
	    (void) umask (mask);
	}
    }

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

/*    FTP */

static int  openFTP (ct, file)
register CT	ct;
char  **file;
{
    int	    caching,
	    fd;
    char   *ftp,
           *id,
	   *user,
	   *pass,
	    buffer[BUFSIZ],
	    cachefile[BUFSIZ];
    register struct exbody *e = (struct exbody *) ct -> c_ctextern;
    register struct cefile *ce = (struct cefile *) ct -> c_ceparams;
    static char   *username = NULL;
    static char   *password = NULL;

    (void) sprintf (buffer, "%s-access-ftp", invo_name);
    if ((ftp = m_find (buffer)) && !*ftp)
	ftp = NULLCP;
#ifndef	FTP
    if (!ftp)
	return NOTOK;
#endif
    switch (openExternal (e -> eb_parent, ce, file, &fd)) {
	case NOTOK:
	    return NOTOK;

	case OK:
	    break;

	case DONE:
	    return fd;
    }

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

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

    if (e -> eb_flags) {
	user = "anonymous";
	(void) 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 (cache
	    && (id = e -> eb_parent -> c_id)
	    && (!e -> eb_permission
		    || !uleq (e -> eb_permission, "read-write"))) {
	(void) sprintf (buffer, "Make cached, publically-accessible copy? ");
	if (getanswer (buffer)) {
	    (void) sprintf (cachefile, "%s/%s", cache, id = trimcpy (id));
	    free (id);

	    if (*file == NULL) {
		ce -> ce_unlink = 0;
		caching = 1;
	    }
	}
    }

    if ((ce -> ce_fp = fopen (ce -> ce_file =
				    add (*file ? *file
					       : caching ? cachefile
					       : m_scratch ("", tmp),
					 NULLCP),
			      "w+")) == NULL) {
	content_error (ce -> ce_file, ct,
		       "unable to fopen for writing and reading");
	return NOTOK;
    }


#ifdef	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 && uleq (e -> eb_mode, "ascii")
	    		? "ascii" : "binary";
	vec[vecp] = NULL;

	(void) 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:
		(void) close (fileno (ce -> ce_fp));
		(void) execvp (ftp, vec);
		fprintf (stderr, "unable to exec ");
		perror (ftp);
		_exit (-1);
		/* NOTREACHED */

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

    if (cachefile[0])
	if (caching)
	    (void) chmod (cachefile, 0644);
	else {
	    int	    mask;
	    register FILE   *fp;

	    mask = umask (0022);
	    if (fp = fopen (cachefile, "w")) {
		register FILE   *gp = ce -> ce_fp;

		(void) fseek (gp, 0L, 0);

		while (fgets (buffer, sizeof buffer - 1, gp))
		    (void) fputs (buffer, fp);
		(void) fflush (fp);

		if (ferror (gp)) {
		    admonish (ce -> ce_file, "error reading");
		    (void) unlink (cachefile);
		}

		if (ferror (fp)) {
		    admonish (cachefile, "error writing");
		    (void) unlink (cachefile);
		}
		(void) fclose (fp);
	    }
	    (void) umask (mask);
	}

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

/*    Mail */

static int  openMail (ct, file)
register CT	ct;
char  **file;
{
    int	    child_id,
	    fd,
	    i,
	    result,
	    vecp;
    char   *bp,
	   *id,
	    buffer[BUFSIZ],
	   *vec[7];
    register struct exbody *e = (struct exbody *) ct -> c_ctextern;
    register struct cefile *ce = (struct cefile *) ct -> c_ceparams;

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

	case OK:
	    break;

	case DONE:
	    return fd;
    }

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

    bp = concat ("Retrieve content ", e -> eb_partno, " by asking mailbox ",
		 e -> eb_server, "\n\n", e -> eb_body, "\n? ", NULLCP);
    result = getanswer (bp);
    free (bp);

    if (!result)
	return NOTOK;

    vecp = 0;
    vec[vecp++] = r1bindex (mailproc, '/');
    vec[vecp++] = e -> eb_server;
    vec[vecp++] = "-subject";
    if (cache
	    && (id = e -> eb_parent -> c_id)
	    && (!e -> eb_permission
		    || !uleq (e -> eb_permission, "read-write"))) {
	(void) sprintf (buffer, "cache content as %s/%s", cache,
			id = trimcpy (id));
	free (id);

	vec[vecp++] = buffer;
    }
    else
	vec[vecp++] = "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:
	    (void) execvp (mailproc, vec);
	    fprintf (stderr, "unable to exec ");
	    perror (mailproc);
	    _exit (-1);
	    /* NOTREACHED */

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

    ce -> ce_unlink = *file == NULL;
    if ((ce -> ce_fp = fopen (ce -> ce_file =
				    add (*file ? *file : m_scratch ("", tmp),
					 NULLCP),
			      "w+")) == NULL) {
	content_error (ce -> ce_file, ct,
		       "unable to fopen for writing and reading");
	return NOTOK;
    }
    if (ct -> c_showproc)
	free (ct -> c_showproc);
    ct -> c_showproc = add ("true", NULLCP);

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

/*    COMPOSITION */

static	char	prefix[] = "----- =_aaaaaaaaaa";

static	char   *free_file = NULL;
static	CT	free_ct = NULL;


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

    if ((in = fopen (file, "r")) == NULL)
	adios (file, "unable to open for reading");

    (void) umask (~m_gmprot ());

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

    for (compnum = 1, state = FLD;;) {
	switch (state = m_getfld (state, name, buf, sizeof buf, in)) {
	    case FLD:
	    case FLDPLUS:
	    case FLDEOF:
		compnum++;

		if (uleq (name, VRSN_FIELD))
		    adios (NULLCP, "draft shouldn't contain %s: field",
			   VRSN_FIELD);

		if (uleq (name, TYPE_FIELD)) {
		    while (state == FLDPLUS)
			state = m_getfld (state, name, buf, sizeof buf, in);
		    goto finish_field;
		}

		if (uleq (name, ENCODING_FIELD))
		    adios (NULLCP, "draft shouldn't contain %s: field",
			   ENCODING_FIELD);

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

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

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

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

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

    if ((free_ct = ct = (CT) calloc (1, sizeof *ct)) == NULL)
	adios (NULLCP, "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, NULLCP);

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

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

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

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

    (void) fclose (in);

    if (!m -> mp_parts)
	adios (NULLCP, "no content directives found");
    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;
    }

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

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

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

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

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

    (void) 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);
	(void) rename (buf, file);
	done (1);
    }
    free_file = NULL;

    done (0);
}

/*  */

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

    for (ep = (cp = s) + n; cp < ep; ) {
	register 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;
}

/*  */

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

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

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

    if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
	int	headers,
		inlineD;
	long	pos;
	char    content[BUFSIZ];
	FILE   *out;

	ct -> c_file = add (m_tmpfil (invo_name), NULLCP);
	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] == '<') {
	    (void) strcpy (content, buf + 2);
	    inlineD = 1;
	    goto rock_and_roll;
	}
	else
	    inlineD = 0;

	(void) strcpy (content, "text/plain");
	headers = 0;
	(void) 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 (NULLCP,
			       "end-of-file after %s: field in plaintext",
			       DESCR_FIELD);
		    switch (buffer[0]) {
		        case ' ':
		        case '\t':
			    i = -1;
			    goto again_descr;

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

			default:
			    break;
		    }
	    }

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

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

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

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

	if (get_ctinfo (content, ct, inlineD) == NOTOK)
	    done (1);
	for (s2i = str2cts; s2i -> si_key; s2i++)
	    if (uleq (ci -> ci_type, s2i -> si_key))
		break;
	if (!s2i -> si_key && !uprf (ci -> ci_type, "X-"))
	    s2i++;
	switch (ct -> c_type = s2i -> si_value) {
	    case CT_MESSAGE:
	    case CT_MULTIPART:
		adios (NULLCP,
		       "it makes sense to define a in-line %s content... NOT!",
		       ct -> c_type == CT_MESSAGE ? "message" : "multipart");
		/* NOTREACHED */

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

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

    extrnal = buf[1] == '@';
    if (get_ctinfo (buf + (extrnal ? 2 : 1), ct, 1) == NOTOK)
	done (1);

    for (s2i = str2cts; s2i -> si_key; s2i++)
	if (uleq (ci -> ci_type, s2i -> si_key))
	    break;
    if (s2i -> si_key) {		/* type/subtype [file] */
	if (!ci -> ci_subtype)
	    adios (NULLCP, "missing subtype in \"#%s\"", ci -> ci_type);

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

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

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

	if (extrnal) {
	    char    msgid[BUFSIZ];
	    register struct exbody *e;
	    CT	    p;
	    static int	partno;
	    static long clock = 0L;
	    static char *msgfmt;

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

	    (void) 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 (NULLCP, "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 (NULLCP, "out of memory");
	    ct -> c_ctparams = (caddr_t) e;
	    ct -> c_ctfreefnx = free_external;

	    e -> eb_parent = ct;
	    e -> eb_content = p;
	    p -> c_ctextern = (caddr_t) e;

	    ct -> c_ctlistfnx = list_external;

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

	    if (clock == 0L) {
		(void) time (&clock);
		(void) sprintf (msgid, "<%d.%ld.%%d@%s>\n", getpid (), clock,
				LocalName ());
		partno = 0;
		msgfmt = getcpy (msgid);
	    }

	    (void) sprintf (msgid, msgfmt, ++partno);
	    ct -> c_id = getcpy (msgid);

	    return OK;
	}

	if (ci -> ci_magic) {
	    if (*ci -> ci_magic == '|' || *ci -> ci_magic == '!') {
		for (cp = ci -> ci_magic + 1; isspace (*cp); cp++)
		    continue;
		if (!*cp)
		    adios (NULLCP, "empty pipe command for #%s directive",
			   ci -> ci_type);
		cp = add (cp, NULLCP);
		free (ci -> ci_magic);
		ci -> ci_magic = cp;
	    }
	    else {
		if (access (ct -> c_file = ci -> ci_magic, 04) == 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;
	}

	(void) sprintf (buffer, "%s-compose-%s/%s", invo_name, ci -> ci_type,
			ci -> ci_subtype);
	if ((cp = m_find (buffer)) == NULL || *cp == 0) {
	    (void) sprintf (buffer, "%s-compose-%s", invo_name, ci -> ci_type);
	    if ((cp = m_find (buffer)) == NULL || *cp == 0) {
		content_error (NULLCP, ct,
			       "don't know how to compose content");
		done (1);
	    }
	}
	ci -> ci_magic = add (cp, NULLCP);
	return OK;
    }

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

    if (uleq (ci -> ci_type, "forw")) {	/* #forw [+folder] [msgs] */
	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; cp = *ap; ap++)
	    if (*cp == '+' || *cp == '@')
		if (folder)
		    adios (NULLCP, "only one folder per #forw directive");
		else
		    folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF);
	if (!folder)
	    folder = add (m_getfolder (), NULLCP);

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

	free_ctinfo (ct);
	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 (NULLCP, "out of memory");
	ct -> c_ctparams = (caddr_t) m;

	pp = &m -> mp_parts;
	for (msgnum = mp -> lowsel; msgnum <= mp -> hghsel; msgnum++)
	    if (mp -> msgstats[msgnum] & SELECTED) {
		register struct part *part;
		register CT	p;

		if ((p = (CT) calloc (1, sizeof *p)) == NULL)
		    adios (NULLCP, "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;

		(void) sprintf (buffer, "%s/%d", mp -> foldpath, msgnum);
		p -> c_file = add (buffer, NULLCP);
		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 (NULLCP, "out of memory");
		*pp = part, pp = &part -> mp_next;
		part -> mp_part = p;
	    }

	m_fmsg (mp);

	return OK;
    }

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

    if (!uleq (ci -> ci_type, "begin"))
	adios (NULLCP, "unknown directive \"#%s\"", ci -> ci_type);

					/* #begin [ alternative | parallel ] */
    vrsn = !ci -> ci_magic ? MULTI_MIXED
		: uprf (ci -> ci_magic, "alt") ? MULTI_ALTERNATE
					       : MULTI_PARALLEL;
    free_ctinfo (ct);
    (void) sprintf (buffer, "multipart/%s", SubMultiPart[vrsn - 1].kv_key);
    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 (NULLCP, "out of memory");
    ct -> c_ctparams = (caddr_t) m;

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

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

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

/*  */

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

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

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

		if (ct -> c_partno) {
		    (void) 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++) {
		    register CT  p = part -> mp_part;

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

		    if (compose_content (p) == NOTOK)
			return NOTOK;
		}

		if (rfc934sw && ct -> c_subtype == MULTI_DIGEST) {
		    int	    is934 = 1;

		    for (part = m -> mp_parts; part; part = part -> mp_next) {
			register 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) {
			register 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:
	    if (!ct -> c_file) {
		int	child_id,
			i,
			xstdout;
		register char  *bp,
			      **ap;
		char   *vec[4];
		FILE   *out;

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

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

		xstdout = 0;
		buffer[0] = '\0';
		for (bp = buffer; *cp; cp++)
		    if (*cp == '%') {
			switch (*++cp) {
		            case 'a':	/* additional arguments */
			        {
				    register char **ep;
				    char   *s = "";

				    for (ap = ci -> ci_attrs, ep = ci -> ci_values;
					     *ap;
					     ap++, ep++) {
					(void) 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':	/* filename */
				(void) sprintf (bp, "%s", ct -> c_file);
				break;

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

			    case '%':
				goto raw;

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

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

		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)
			    (void) dup2 (fileno (out), 1);
			(void) close (fileno (out));
			(void) execvp ("/bin/sh", vec);
			fprintf (stderr, "unable to exec ");
			perror ("/bin/sh");
			_exit (-1);
			/* NOTREACHED */

		   default:
			(void) fclose (out);
			if (pidXwait (child_id, NULLCP))
			    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;
	    }
	    if (ct -> c_type != CT_TEXT
		    && !(ct -> c_type == CT_APPLICATION
			     && ct -> c_subtype == APPLICATION_POSTSCRIPT))
		break;
	    /* else fall... */

	case CT_MESSAGE:
	    {
		int	charset,
			len,
			linelen,
			result;
		FILE   *in;

		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:
		        charset = ct -> c_ctparams ? 0 : -1;
			linelen = 0;
			break;

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

		    default:
			charset = linelen = 0;
			break;
		}
		while (fgets (buffer, sizeof buffer - 1, in)) {
		    if (charset == -1) {
			for (cp = buffer; *cp; cp++)
			    if (!isascii (*cp)) {
				charset = CHARSET_UNKNOWN;
				break;
			    }
			if ((linelen == -1) && (cp - buffer > CPERLIN + 1))
			    linelen = 1;
			if (result == NOTOK)
			    break;
		    }
		    else
			if ((linelen == -1) && (strlen (buffer) > CPERLIN + 1))
			    linelen = 1;
		    if (result == NOTOK)
			continue;

		    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) == 0
			        && isdigit (buffer[2 + len])) {
			    result = NOTOK;
			    if (charset != -1 && linelen != -1)
				break;
			}
		    }
		}
		if (ct -> c_type == CT_APPLICATION && !ct -> c_encoding)
		    ct -> c_encoding = linelen == -1
					        && charset != CHARSET_UNKNOWN
					    ? CE_7BIT : CE_QUOTED;
		if (ct -> c_type == CT_TEXT && !ct -> c_ctparams) {
		    register char  **ap,
				   **ep;
		    register struct text *t;

		    if (charset == CHARSET_UNKNOWN && mm_charset)
			charset = -2;
		    else
			if (charset == -1)
			    charset = CHARSET_USASCII;

		    if ((t = (struct text *) calloc (1, sizeof *t)) == NULL)
			adios (NULLCP, "out of memory");
		    ct -> c_ctparams = (caddr_t) t;
		    for (ap = ci -> ci_attrs, ep = ci -> ci_values;
			     *ap;
			     ap++, ep++)
			continue;
		    switch (t -> tx_charset = charset) {
			case CHARSET_USASCII:
			    *ap = add ("charset=us-ascii", NULLCP);
			    break;

			case CHARSET_UNKNOWN:
			default:
			    *ap = add ("charset=x-unknown", NULLCP);
			    break;

			case -2:
			    *ap = concat ("charset=", mm_charset, NULLCP);
			    break;
		    }
		    cp = index (*ap++, '=');
		    *ap = NULL;
		    *cp++ = '\0';
		    *ep = cp;
		}

		(void) fclose (in);

		return result;
	    }
    }

    return OK;
}

/*  */

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

    if (ct -> c_type == CT_MULTIPART) {
	register char  *cp;
	static	int	encl = 0;

	ap = ci -> ci_attrs, ep = ci -> ci_values;

	(void) sprintf (buffer, "boundary=%s%d", prefix, encl++);
	cp = index (*ap++ = add (buffer, NULLCP), '=');
	*ap = NULL;
	*cp++ = '\0';
	*ep = cp;
    }
    else
	if (ct -> c_type == CT_MESSAGE && ct -> c_rfc934)
	    goto rfc934_mode;

    len = 0;
    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;
    for (ap = ci -> ci_attrs, ep = ci -> ci_values; *ap; ap++, ep++) {
	if (mailbody && uleq (*ap, "body"))
	    continue;

	(void) putc (';', out);
	len++;

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

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

rfc934_mode: ;
    if (ct -> c_ctextern)
	return OK;
    switch (ct -> c_type) {
	case CT_MULTIPART:
	    {
		struct multipart *m = (struct multipart *) ct -> c_ctparams;
		register struct part *part;

		if (ct -> c_rfc934)
		    fprintf (out, "\n");

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

		    fprintf (out, "\n--%s\n", ci -> ci_values[0]);
		    if (p -> c_type == CT_MESSAGE
			    && p -> c_subtype != MESSAGE_EXTERNAL
			    && !p -> c_rfc934)
			fprintf (out, "%s: %s\n", VRSN_FIELD, VRSN_VALUE);
		    (void) output_content (p, out);
		}

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

	case CT_TEXT:
	    if (ct -> c_subtype != TEXT_PLAIN
		    || (ct -> c_ctparams
			    && ((struct text *) ct -> c_ctparams) -> tx_charset
					!= CHARSET_USASCII)) {
quoted_printable: ;
		fprintf (out, "%s: %s\n\n", ENCODING_FIELD,
			 "quoted-printable");
		(void) writeQuoted (ct, out);
		break;
	    }
	    /* else fall... */

	case CT_MESSAGE:
seven_bit: ;
	    fprintf (out, "\n");
	    if (ct -> c_type == CT_MESSAGE
		    && ct -> c_subtype == MESSAGE_EXTERNAL) {
		register struct exbody *e = (struct exbody *) ct -> c_ctparams;
		
		(void) output_content (e -> eb_content, out);
		if (e -> eb_body)
		    fprintf (out, "\n%s\n", e -> eb_body);
	    }
	    else
		(void) write7Bit (ct, out);
	    break;

	case CT_APPLICATION:
	    if (ct -> c_subtype == APPLICATION_POSTSCRIPT) {
		if (ct -> c_encoding == CE_7BIT)
		    goto seven_bit;
		goto quoted_printable;
	    }
	    /* else fall... */

	default:
	    fprintf (out, "%s: %s\n\n", ENCODING_FIELD, "base64");
	    (void) writeBase64 (ct, out);
	    break;
    }

    return OK;
}

/*  */

static int  write7Bit (ct, out)
register 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];
	(void) fputs (buffer, out);
    }
    if (c != '\n')
	(void) putc ('\n', out);

    (void) fclose (in);

    return OK;
}

/*  */

static int  writeQuoted (ct, out)
register CT	ct;
FILE   *out;
{
    register 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)) {
	register int	n;

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

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

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

		case '@':
		case '`':
		    if (ebcdicsw)
			goto three_print;
one_print: ;
		    (void) putc (*cp, out);
		    n++;
		    break;

		default:
		    if (('!' <= *cp && *cp <= '$')
			    || ('[' <= *cp && *cp <= '^')
			    || ('{' <= *cp && *cp <= '~')) {
			if (ebcdicsw)
			    goto three_print;
			goto one_print;
		    }

		    if ('%' <= *cp && *cp <= 'z')
			goto one_print;
		    /* else fall... */
		case '=':
three_print: ;
		    (void) fprintf (out, "=%02X", *cp & 0xff);
		    n += 3;
		    break;
	    }
	}

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

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

    (void) fclose (in);

    return OK;
}

/*  */

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


static int  writeBase64 (ct, out)
register 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);

    (void) fclose (in);

    return result;
}


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

    n = BPERLIN;
    while ((cc = fread (inbuf, sizeof *inbuf, sizeof inbuf, in)) > 0) {
	unsigned long bits;
	register 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] = '=';
	}

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

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

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

    return OK;
}

/*    VIAMAIL */

#include "../zotnet/tws.h"


static int  via_mail (mailsw, subjsw, parmsw, descsw, cmntsw, slowsw, fromsw)
char   *mailsw,
       *subjsw,
       *parmsw,
       *descsw,
       *cmntsw,
       *fromsw;
int	slowsw;
{
    int	    nlines,
	    nparts,
	    status;
    long    pos;
    char    tmpfil[BUFSIZ];
    struct stat st;
    FILE   *fp;

    (void) umask (~m_gmprot ());

    (void) strcpy (tmpfil, m_tmpfil (invo_name));
    if ((fp = fopen (tmpfil, "w+")) == NULL)
	adios (tmpfil, "unable to open for writing");
    (void) chmod (tmpfil, 0600);

    if (!index (mailsw, '@'))
	mailsw = concat (mailsw, "@", LocalName (), NULLCP);
    fprintf (fp, "To: %s\n", mailsw);
    nlines = 1;
    if (subjsw)
	fprintf (fp, "Subject: %s\n", subjsw), nlines++;
    if (fromsw) {
	if (!index (fromsw, '@'))
	    fromsw = concat (fromsw, "@", LocalName (), NULLCP);
	fprintf (fp, "From: %s\n", fromsw), nlines++;
    }
    fprintf (fp, "%s: %s\n", VRSN_FIELD, VRSN_VALUE), nlines++;
    fprintf (fp, "%s: application/octet-stream", TYPE_FIELD);
    if (parmsw)
	fprintf (fp, "; %s", parmsw);
    if (cmntsw)
	fprintf (fp, "\n\t(%s)", cmntsw), nlines++;
    if (descsw)
	fprintf (fp, "\n%s: %s", DESCR_FIELD, descsw), nlines++;
    fprintf (fp, "\n%s: %s\n\n", ENCODING_FIELD, "base64"), nlines += 2;
    if (fflush (fp))
	adios (tmpfil, "error writing to");

    pos = ftell (fp);
    (void) writeBase64aux (stdin, fp);
    if (fflush (fp))
	adios (tmpfil, "error writing to");

    if (fstat (fileno (fp), &st) == NOTOK)
	adios ("failed", "fstat of %s", tmpfil);
    nlines += (((long) st.st_size - pos) + CPERLIN) / (CPERLIN + 1);
    nparts = (nlines + (LPERMSG - 1)) / LPERMSG;

    if (nparts <= 1)
	status = via_post (tmpfil);
    else {
	int	partno;
	long	clock;
	char	buffer[BUFSIZ],
		msgid[BUFSIZ];
	
	if (verbosw) {
	    printf ("sending binary image as %d partial messages\n", nparts);
	    (void) fflush (stdout);
	}

	(void) time (&clock);
	(void) sprintf (msgid, "<%d.%ld@%s>", getpid (), clock, LocalName ());

	(void) fseek (fp, 0L, 0);
	if (!fgets (buffer, sizeof buffer, fp)
	        || !fgets (buffer, sizeof buffer, fp)
	        || (subjsw && !fgets (buffer, sizeof buffer, fp)))
	    adios (NULLCP, "premature eof");
	for (partno = 1; partno <= nparts; partno++) {
	    int	    lineno;
	    char    tmpdrf[BUFSIZ];
	    FILE   *out;

	    (void) strcpy (tmpdrf, m_tmpfil (invo_name));
	    if ((out = fopen (tmpdrf, "w")) == NULL)
		adios (tmpdrf, "unable to open for writing");
	    (void) chmod (tmpdrf, 0600);

	    fprintf (out, "To: %s\n", mailsw);
	    if (subjsw)
		fprintf (out, "Subject: %s\n", subjsw);
	    fprintf (out, "%s: %s\n", VRSN_FIELD, VRSN_VALUE);
	    fprintf (out,
		     "%s: message/partial; id=\"%s\";\n\tnumber=%d; total=%d\n",
		     TYPE_FIELD, msgid, partno, nparts);
	    fprintf (out, "%s: part %d of %d\n\n", DESCR_FIELD, partno,
		     nparts);

	    if (partno == 1)
		fprintf (out, "Message-ID: %s\n", msgid);

	    for (lineno = LPERMSG; lineno > 0; lineno--) {
		if (!fgets (buffer, sizeof buffer, fp)) {
		    if (partno == nparts)
			break;
		    adios (NULLCP, "premature eof");
		}

		(void) fputs (buffer, out);
	    }

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

	    (void) fclose (out);

	    if (slowsw > 0 && 1 < partno && partno < nparts) {
		if (verbosw) {
		    printf ("pausing %d seconds before sending part %d...\n",
			    slowsw, partno);
		    (void) fflush (stdout);
		}

		sleep ((unsigned) slowsw);
	    }

	    status = via_post (tmpdrf);
	    (void) unlink (tmpdrf);
	    if (status)
		break;
	}
    }

    (void) fclose (fp);
    (void) unlink (tmpfil);

    done (status ? 1 : 0);
}

/*  */

static int  via_post (file)
char   *file;
{
    int	    child_id,
	    i;

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

	case OK:
	    (void) execlp (postproc, r1bindex (postproc, '/'), file, NULLCP);
	    fprintf (stderr, "unable to exec ");
	    perror (postproc);
	    _exit (-1);
	    /* NOTREACHED */

	default:
	    return pidXwait (child_id, postproc);
    }
}

/*  */

void done (status)
int	status;
{
    register CT	   *ctp;

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

    exit (status);
}


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

    (void) unlink ("core");

    (void) fflush (stdout);

    (void) fflush (stderr);

    done (1);
    /* NOTREACHED */
}