/*
 * Program:	Mailbox Access routines
 *
 * Author:	Mark Crispin
 *		Networks and Distributed Computing
 *		Computing & Communications
 *		University of Washington
 *		Administration Building, AG-44
 *		Seattle, WA  98195
 *		Internet: MRC@CAC.Washington.EDU
 *
 * Date:	22 November 1989
 * Last Edited:	6 May 1992
 *
 * Copyright 1992 by the University of Washington
 *
 *  Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appears in all copies and that both the
 * above copyright notice and this permission notice appear in supporting
 * documentation, and that the name of the University of Washington not be
 * used in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.  This software is made
 * available "as is", and
 * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
 * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
 * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
 * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */


#include <ctype.h>
#include <stdio.h>
#if unix
#include <sys/types.h>
#endif
#include "osdep.h"
#include "mail.h"
#include "misc.h"

DRIVER *maildrivers = NIL;	/* list of mail drivers */
mailgets_t mailgets = NIL;	/* pointer to alternate gets function */

/* Default limited get string
 * Accepts: readin function pointer
 *	    stream to use
 *	    number of bytes
 * Returns: string read in, truncated if necessary
 *
 * This is a sample mailgets routine.  It simply truncates any data larger
 * than MAXMESSAGESIZE.  On most systems, you generally don't use a mailgets
 * routine at all, but on some systems (e.g. DOS) it's required to prevent the
 * application from crashing.  This one is filled in by the os driver for any
 * OS that requires a mailgets routine and the main program has not already
 * supplied one, generally in tcp_open().
 */

char *mm_gets (f,stream,size)
	readfn_t f;
	void *stream;
	unsigned long size;
{
  char *s;
  char tmp[MAILTMPLEN+1];
  unsigned long i,j = 0;
				/* truncate? */
  if (i = (size > MAXMESSAGESIZE)) {
    sprintf (tmp,"%ld character literal truncated to %ld characters",
	     size,MAXMESSAGESIZE);
    mm_log (tmp,WARN);		/* warn user */
    i = size - MAXMESSAGESIZE;	/* number of bytes of slop */
    size = MAXMESSAGESIZE;	/* maximum length string we can read */
  }
  s = (char *) fs_get (size + 1);
  *s = '\0';			/* init in case getbuffer fails */
  (*f) (stream,size,s);		/* get the literal */
				/* toss out everything after that */
  while (i -= j) (*f) (stream,j = min ((long) MAILTMPLEN,i),tmp);
  return s;
}

/* Mail routines
 *
 *  mail_xxx routines are the interface between this module and the outside
 * world.  Only these routines should be referenced by external callers.
 *
 *  Note that there is an important difference between a "sequence" and a
 * "message #" (msgno).  A sequence is a string representing a sequence in
 * {"n", "n:m", or combination separated by commas} format, whereas a msgno
 * is a single integer.
 *
 */

/* Mail link driver
 * Accepts: driver to add to list
 */

void mail_link (driver)
	DRIVER *driver;
{
  DRIVER **d = &maildrivers;
  while (*d) d = &(*d)->next;	/* find end of list of drivers */
  *d = driver;			/* put driver at the end */
  driver->next = NIL;		/* this driver is the end of the list */
}


/* Mail find list of mailboxes
 * Accepts: mail stream
 *	    pattern to search
 */

void mail_find (stream,pat)
	MAILSTREAM *stream;
	char *pat;
{
  DRIVER **d = &maildrivers;
				/* if have a stream, do it for that stream */
  if (stream && stream->dtb) (*stream->dtb->find) (stream,pat);
				/* otherwise do for all DTB's */
  else do ((*d)->find) (NIL,pat);
  while (*(d = &(*d)->next));	/* until at the end */
}


/* Mail find list of bboards
 * Accepts: mail stream
 *	    pattern to search
 */

void mail_find_bboards (stream,pat)
	MAILSTREAM *stream;
	char *pat;
{
  DRIVER **d = &maildrivers;
  if (stream && stream->dtb) (*stream->dtb->find_bboard) (stream,pat);
  else do ((*d)->find_bboard) (NIL,pat);
  while (*(d = &(*d)->next));	/* until at the end */
}

/* Mail open
 * Accepts: candidate stream for recycling
 *	    mailbox name
 *	    initial debugging flag
 * Returns: stream to use on success, NIL on failure
 */

MAILSTREAM *mail_open (stream,name,debug)
	MAILSTREAM *stream;
	char *name;
	long debug;
{
  long i;
  char tmp[MAILTMPLEN];
  DRIVER *factory = maildrivers ? (*maildrivers->valid) (name) : NIL;
  if (factory) {		/* must have a factory */
    if (!stream) {		/* instantiate stream if none to recycle */
      stream = (MAILSTREAM *) fs_get (sizeof (MAILSTREAM));
      stream->dtb = factory;	/* initialize stream fields */
      stream->local = NIL;
      stream->mailbox = cpystr (name);
      stream->lock = stream->debug = stream->silent = stream->readonly =
	stream->anonymous = NIL;
      stream->use = stream->sequence = stream->gensym = stream->nmsgs =
	stream->recent = 0;
      stream->flagstring = NIL;
      for (i = 0; i < NUSERFLAGS; ++i) stream->user_flags[i] = NIL;
      stream->cache = NIL;	/* no cache */
      stream->cachesize = 0;
    }
    else {			/* close driver if different from factory */
      if (stream->dtb != factory) {
	if (stream->dtb) (*stream->dtb->close) (stream);
	stream->dtb = factory;	/* establish factory as our driver */
	stream->local = NIL;	/* flush old driver's local data */
      }
				/* clear old silent and readonly state */
      stream->silent = stream->readonly = NIL;
				/* clean up old stream for recycling */
      if (stream->mailbox) fs_give ((void **) &stream->mailbox);
      stream->mailbox = cpystr (name);
      mail_free_cache (&stream->cache,&stream->cachesize);
    }
    if (debug == 36) {		/* special hack from Tenex? */
      stream->debug = NIL;	/* yes, no special debugging */
      stream->silent = T;	/* request silence */
    }
				/* a second grotesque hack */
    else if (debug == 69) stream->anonymous = T;
    else stream->debug = debug;	/* set to desired debugging state */
				/* have driver open, flush if failed */
    if (!(*factory->open) (stream)) stream = mail_close (stream);
  }
  else if (debug != 36) {	/* not from Tenex internal please */
    sprintf (tmp,"Unknown mailbox format: %.80s",name);
    mm_log (tmp,ERROR);		/* give an error */
  }
  return stream;		/* return the stream */
}

/* Mail close
 * Accepts: mail stream
 * Returns: NIL
 */

MAILSTREAM *mail_close (stream)
	MAILSTREAM *stream;
{
  long i;
  if (stream) {			/* make sure argument given */
				/* do the driver's close action */
    if (stream->dtb) (*stream->dtb->close) (stream);
    stream->dtb = NIL;		/* disassociate from driver */
    stream->local = NIL;	/* and driver's local data */
    if (stream->mailbox) fs_give ((void **) &stream->mailbox);
    stream->mailbox = NIL;
    stream->sequence++;		/* invalidate sequence */
    if (stream->flagstring) fs_give ((void **) &stream->flagstring);
    stream->flagstring = NIL;
    for (i = 0; i < NUSERFLAGS; ++i) stream->user_flags[i] = NIL;
    mail_free_cache (&stream->cache,&stream->cachesize);
				/* finally free the stream's storage */
    if (!stream->use) fs_give ((void **) &stream);
  }
  return NIL;
}

/* Mail make handle
 * Accepts: Mail stream
 * Returns: handle
 *
 *  Handles provide a way to have multiple pointers to a stream yet allow the
 * stream's owner to nuke it or recycle it.
 */

MAILHANDLE *mail_makehandle (stream)
	MAILSTREAM *stream;
{
  MAILHANDLE *handle = (MAILHANDLE *) fs_get (sizeof (MAILHANDLE));
  handle->stream = stream;	/* copy stream */
				/* and its sequence */
  handle->sequence = stream->sequence;
  stream->use++;		/* let stream know another handle exists */
  return handle;
}


/* Mail release handle
 * Accepts: Mail handle
 */

void mail_free_handle (handle)
	MAILHANDLE **handle;
{
  MAILSTREAM *s;
  if (*handle) {		/* only free if exists */
				/* resign stream, flush unreferenced zombies */
    if ((!--(s = (*handle)->stream)->use) && !s->dtb) fs_give ((void **) &s);
    fs_give ((void **) handle);	/* now flush the handle */
  }
}


/* Mail get stream handle
 * Accepts: Mail handle
 * Returns: Mail stream or NIL if stream gone
 */

MAILSTREAM *mail_stream (handle)
	MAILHANDLE *handle;
{
  MAILSTREAM *s = handle->stream;
  return (s->dtb && (handle->sequence == s->sequence)) ? s : NIL;
}

/* Mail fetch cache element
 * Accepts: Mail stream
 *	    message # to fetch
 * Returns: cache element of this message
 * Can also be used to create cache elements for new messages.
 */

MESSAGECACHE *mail_elt (stream,msgno)
	MAILSTREAM *stream;
	long msgno;
{
  long i = msgno - 1;
  if (msgno < 1) fatal ("mail_elt called for non-positive message number");
  mail_cache (stream,msgno);	/* make sure cache is large enough */
  if (!stream->cache[i]) {	/* if no cache entry, create it */
    stream->cache[i] = (MESSAGECACHE *) fs_get (sizeof (MESSAGECACHE));
				/* initialize newly-created cache entry */
    stream->cache[i]->lockcount = 1;
    stream->cache[i]->msgno = msgno;
    stream->cache[i]->internal_date = NIL;
    stream->cache[i]->seen = stream->cache[i]->deleted =
      stream->cache[i]->flagged = stream->cache[i]->answered =
	stream->cache[i]->searched = stream->cache[i]->recent = NIL;
    stream->cache[i]->user_flags = NIL;
    stream->cache[i]->rfc822_size = 0;
    stream->cache[i]->env = NIL;
    stream->cache[i]->body = NIL;
  }
  return stream->cache[i];	/* return the cache element */
}


/* Mail make cache large enough for given number of messages
 * Accepts: Mail stream
 *	    number of messages in cache
 */

void mail_cache (stream,size)
	MAILSTREAM *stream;
	long size;
{
  size_t new;
  unsigned long i = stream->cachesize;
				/* do nothing if size adequate */
  if (size <= stream->cachesize) return;
  new = (stream->cachesize = size + CACHEINCREMENT) * sizeof (MESSAGECACHE *);
  if (stream->cache) fs_resize ((void **) &stream->cache,new);
  else stream->cache = (MESSAGECACHE **) fs_get (new);
				/* init cache */
  while (i < stream->cachesize) stream->cache[i++] = NIL;
}

/* Mail fetch fast information
 * Accepts: Mail stream
 *	    sequence
 *
 * Generally, mail_fetchenvelope is preferred
 */

void mail_fetchfast (stream,sequence)
	MAILSTREAM *stream;
	char *sequence;
{
  				/* do the driver's action */
  if (stream->dtb) (*stream->dtb->fetchfast) (stream,sequence);
}


/* Mail fetch flags
 * Accepts: Mail stream
 *	    sequence
 */

void mail_fetchflags (stream,sequence)
	MAILSTREAM *stream;
	char *sequence;
{
  				/* do the driver's action */
  if (stream->dtb) (*stream->dtb->fetchflags) (stream,sequence);
}


/* Mail fetch envelope
 * Accepts: Mail stream
 *	    message # to fetch
 * Returns: envelope of this message
 *
 * Fetches the "fast" information as well
 */

ENVELOPE *mail_fetchenvelope (stream,msgno)
	MAILSTREAM *stream;
	long msgno;
{
  if (msgno > stream->nmsgs)
    fatal ("mail_fetchenvelope called for non-existent message number");
  				/* do the driver's action */
  return stream->dtb ? (*stream->dtb->fetchenvelope) (stream,msgno) : NIL;
}

/* Mail fetch message header
 * Accepts: Mail stream
 *	    message # to fetch
 * Returns: message header in RFC822 format
 */

char *mail_fetchheader (stream,msgno)
	MAILSTREAM *stream;
	long msgno;
{
  if (msgno > stream->nmsgs)
    fatal ("mail_fetchheader called for non-existent message number");
  				/* do the driver's action */
  return stream->dtb ? (*stream->dtb->fetchheader) (stream,msgno) : "";
}


/* Mail fetch message text (only)
	body only;
 * Accepts: Mail stream
 *	    message # to fetch
 * Returns: message text in RFC822 format
 */

char *mail_fetchtext (stream,msgno)
	MAILSTREAM *stream;
	long msgno;
{
  if (msgno > stream->nmsgs)
    fatal ("mail_fetchtext called for non-existent message number");
  				/* do the driver's action */
  return stream->dtb ? (*stream->dtb->fetchtext) (stream,msgno) : "";
}


/* Mail fetch message body as a structure
 * Accepts: Mail stream
 *	    message # to fetch
 *	    section specifier (#.#.#...#)
 *	    pointer to returned length
 * Returns: pointer to section of message body
 */

char *mail_fetchbody (stream,m,sec,len)
	MAILSTREAM *stream;
	long m;
	char *sec;
	unsigned long *len;
{
  if (m > stream->nmsgs)
    fatal ("mail_fetchbody called for non-existent message number");
  				/* do the driver's action */
  return stream->dtb ? (*stream->dtb->fetchbody) (stream,m,sec,len) : "";
}

/* Mail fetch From string for menu
 * Accepts: destination string
 *	    Mail stream
 *	    message # to fetch
 *	    desired string length
 * Returns: string of requested length
 */

void mail_fetchfrom (s,stream,msgno,length)
	char *s;
	MAILSTREAM *stream;
	long msgno;
	long length;
{
  char *t;
  char tmp[MAILTMPLEN];
  ENVELOPE *env = mail_fetchenvelope (stream,msgno);
  memset (s,' ',length);	/* fill it with spaces */
  s[length] = '\0';		/* tie off with null */
				/* get first from address from envelope */
  if (env && env->from) {	/* if a personal name exists use it */
    if (!(t = env->from->personal))
      sprintf (t = tmp,"%s@%s",env->from->mailbox,env->from->host);
    memcpy (s,t,min (length,(long) strlen (t)));
  }
}


/* Mail fetch Subject string for menu
 * Accepts: destination string
 *	    Mail stream
 *	    message # to fetch
 *	    desired string length
 * Returns: string of no more than requested length
 */

void mail_fetchsubject (s,stream,msgno,length)
	char *s;
	MAILSTREAM *stream;
	long msgno;
	long length;
{
  ENVELOPE *env = mail_fetchenvelope (stream,msgno);
  memset (s,'\0',length+1);
				/* copy subject from envelope */
  if (env && env->subject) strncpy (s,env->subject,length);
  else *s = ' ';		/* if no subject then just a space */
}

/* Mail set flag
 * Accepts: Mail stream
 *	    sequence
 *	    flag(s)
 */

void mail_setflag (stream,sequence,flag)
	MAILSTREAM *stream;
	char *sequence;
	char *flag;
{
  				/* do the driver's action */
  if (stream->dtb) (*stream->dtb->setflag) (stream,sequence,flag);
}


/* Mail clear flag
 * Accepts: Mail stream
 *	    sequence
 *	    flag(s)
 */

void mail_clearflag (stream,sequence,flag)
	MAILSTREAM *stream;
	char *sequence;
	char *flag;
{
  				/* do the driver's action */
  if (stream->dtb) (*stream->dtb->clearflag) (stream,sequence,flag);
}


/* Mail search for messages
 * Accepts: Mail stream
 *	    search criteria
 */

void mail_search (stream,criteria)
	MAILSTREAM *stream;
	char *criteria;
{
  long i = 1;
  while (i <= stream->nmsgs) mail_elt (stream,i++)->searched = NIL;
  				/* do the driver's action */
  if (stream->dtb) (*stream->dtb->search) (stream,criteria);
}


/* Mail ping mailbox
 * Accepts: Mail stream
 * Returns: stream if still open else NIL
 */

long mail_ping (stream)
	MAILSTREAM *stream;
{
  				/* do the driver's action */
  return stream->dtb ? (*stream->dtb->ping) (stream) : NIL;
}

/* Mail check mailbox
 * Accepts: Mail stream
 */

void mail_check (stream)
	MAILSTREAM *stream;
{
  				/* do the driver's action */
  if (stream->dtb) (*stream->dtb->check) (stream);
}


/* Mail expunge mailbox
 * Accepts: Mail stream
 */

void mail_expunge (stream)
	MAILSTREAM *stream;
{
  				/* do the driver's action */
  if (stream->dtb) (*stream->dtb->expunge) (stream);
}


/* Mail copy message(s)
	s;
 * Accepts: Mail stream
 *	    sequence
 *	    destination mailbox
 */

long mail_copy (stream,sequence,mailbox)
	MAILSTREAM *stream;
	char *sequence;
	char *mailbox;
{
  				/* do the driver's action */
  return stream->dtb ? (*stream->dtb->copy) (stream,sequence,mailbox) : NIL;
}


/* Mail move message(s)
	s;
 * Accepts: Mail stream
 *	    sequence
 *	    destination mailbox
 */

long mail_move (stream,sequence,mailbox)
	MAILSTREAM *stream;
	char *sequence;
	char *mailbox;
{
  				/* do the driver's action */
  return stream->dtb ? (*stream->dtb->move) (stream,sequence,mailbox) : NIL;
}

/* Mail garbage collect stream
 * Accepts: Mail stream
 *	    garbage collection flags
 */

void mail_gc (stream,gcflags)
	MAILSTREAM *stream;
	long gcflags;
{
  unsigned long i = 0;
  MESSAGECACHE *elt;
  if (gcflags & GC_ENV)		/* garbage collect envelopes? */
    while (i < stream->nmsgs)
      if ((elt = stream->cache[i++]) && elt->env) {
	mail_free_envelope (&elt->env);
	mail_free_body (&elt->body);
      }
  				/* do the driver's action */
  if (stream->dtb) (*stream->dtb->gc) (stream,gcflags);
}

/* Mail messages have been searched out
 * Accepts: MAIL stream
 *	    message number
 *
 * Calls external "mm_searched" function to notify main program
 */

void mail_searched (stream,msgno)
	MAILSTREAM *stream;
	long msgno;
{
				/* mark as searched */
  mail_elt (stream,msgno)->searched = T;
  mm_searched (stream,msgno);	/* notify main program */
}


/* Mail n messages exist
 * Accepts: MAIL stream
 *	    number of messages
 *
 * Calls external "mm_exists" function that notifies main program prior
 * to updating the stream
 */

void mail_exists (stream,nmsgs)
	MAILSTREAM *stream;
	long nmsgs;
{
  mail_cache (stream,nmsgs);	/* make sure cache is large enough */
				/* notify main program of change */
  if (!stream->silent) mm_exists (stream,nmsgs);
  stream->nmsgs = nmsgs;	/* update stream status */
}

/* Mail n messages are recent
 * Accepts: MAIL stream
 *	    number of recent messages
 */

void mail_recent (stream,recent)
	MAILSTREAM *stream;
	long recent;
{
  stream->recent = recent;	/* update stream status */
}


/* Mail message n is expunged
 * Accepts: MAIL stream
 *	    message #
 *
 * Calls external "mm_expunged" function that notifies main program prior
 * to updating the stream
 */

void mail_expunged (stream,msgno)
	MAILSTREAM *stream;
	long msgno;
{
  long i = msgno - 1;
  if (stream->cache[i]) {	/* if an element is there */
    stream->cache[i]->msgno = 0;/* invalidate its message number */
				/* maybe free this message element */
    mail_free_elt (&stream->cache[i]);
  }
				/* slide down remainder of cache */
  for (i = msgno; i < stream->nmsgs; ++i)
    if (stream->cache[i-1] = stream->cache[i]) stream->cache[i-1]->msgno = i;
  stream->cache[stream->nmsgs-1] = NIL;
  --stream->nmsgs;		/* update stream status */
				/* notify main program of change */
  if (!stream->silent) mm_expunged (stream,msgno);
}

/* Mail stream status routines */


/* Mail lock stream
 * Accepts: Mail stream
 */

void mail_lock (stream)
	MAILSTREAM *stream;
{
  if (stream->lock) fatal ("Lock when already locked");
  else stream->lock = T;	/* lock stream */
}


/* Mail unlock stream
 * Accepts: Mail stream
 */

void mail_unlock (stream)
	MAILSTREAM *stream;
{
  if (!stream->lock) fatal ("Unlock when not locked");
  else stream->lock = NIL;	/* unlock stream */
}


/* Mail turn on debugging telemetry
 * Accepts: Mail stream
 */

void mail_debug (stream)
	MAILSTREAM *stream;
{
  stream->debug = T;		/* turn on debugging telemetry */
}


/* Mail turn off debugging telemetry
 * Accepts: Mail stream
 */

void mail_nodebug (stream)
	MAILSTREAM *stream;
{
  stream->debug = NIL;		/* turn off debugging telemetry */
}

/* Mail parse sequence
 * Accepts: MAIL stream
 *	    sequence to parse
 * Returns: T if parse successful, else NIL
 */

long mail_sequence (stream,sequence)
	MAILSTREAM *stream;
	char *sequence;
{
  long i,j,x;
  for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->sequence = NIL;
  while (*sequence) {		/* while there is something to parse */
				/* parse and validate message number */
    if (((i = (int) strtol ((const char *) sequence,&sequence,10)) < 1) ||
	(i > stream->nmsgs)) {
      mm_log ("Sequence invalid",ERROR);
      return NIL;
    }
    switch (*sequence) {	/* see what the delimiter is */
    case ':':			/* sequence range */
				/* parse end of range */
      if (((j = (int) strtol ((const char *) ++sequence,&sequence,10)) < 1) ||
	  (j > stream->nmsgs) || (*sequence && *sequence++ != ',')) {
	mm_log ("Sequence range invalid",ERROR);
	return NIL;
      }
      if (i > j) {		/* swap the range if backwards */
	x = i; i = j; j = x;
      }
				/* mark each item in the sequence */
      while (i <= j) mail_elt (stream,j--)->sequence = T;
      break;
    case ',':			/* single message */
      ++sequence;		/* skip the delimiter, fall into end case */
    case '\0':			/* end of sequence, mark this message */
      mail_elt (stream,i)->sequence = T;
      break;
    default:			/* anything else is a syntax error! */
      mm_log ("Syntax error in sequence",ERROR);
      return NIL;
    }
  }
  return T;			/* successfully parsed sequence */
}

/* Mail data structure instantiation routines */


/* Mail instantiate envelope
 * Returns: new envelope
 */

ENVELOPE *mail_newenvelope ()
{
  ENVELOPE *env = (ENVELOPE *) fs_get (sizeof (ENVELOPE));
  env->remail = NIL;		/* initialize all fields */
  env->return_path = NIL;
  env->date = NIL;
  env->subject = NIL;
  env->from = env->sender = env->reply_to = env->to = env->cc = env->bcc = NIL;
  env->in_reply_to = env->message_id = env->newsgroups = NIL;
  return env;
}


/* Mail instantiate address
 * Returns: new address
 */

ADDRESS *mail_newaddr ()
{
  ADDRESS *adr = (ADDRESS *) fs_get (sizeof (ADDRESS));
				/* initialize all fields */
  adr->personal = adr->adl = adr->mailbox = adr->host = adr->error = NIL;
  adr->next = NIL;
  return adr;
}


/* Mail instantiate body
 * Returns: new body
 */

BODY *mail_newbody ()
{
  return mail_initbody ((BODY *) fs_get (sizeof (BODY)));
}

/* Mail initialize body
 * Accepts: body
 * Returns: body
 */

BODY *mail_initbody (body)
	BODY *body;
{
  body->type = TYPETEXT;	/* content type */
  body->encoding = ENC7BIT;	/* content encoding */
  body->subtype = body->id = body->description = NIL;
  body->parameter = NIL;
  body->contents.text = NIL;	/* no contents yet */
  body->contents.binary = NIL;
  body->contents.part = NIL;
  body->contents.msg.env = NIL;
  body->contents.msg.body = NIL;
  body->contents.msg.text = NIL;
  body->size.lines = body->size.bytes = body->size.ibytes = 0;
  return body;
}


/* Mail instantiate body parameter
 * Returns: new body part
 */

PARAMETER *mail_newbody_parameter ()
{
  PARAMETER *parameter = (PARAMETER *) fs_get (sizeof (PARAMETER));
  parameter->attribute = parameter->value = NIL;
  parameter->next = NIL;	/* no next yet */
  return parameter;
}


/* Mail instantiate body part
 * Returns: new body part
 */

PART *mail_newbody_part ()
{
  PART *part = (PART *) fs_get (sizeof (PART));
  mail_initbody (&part->body);	/* initialize the body */
  part->offset = 0;		/* no offset yet */
  part->next = NIL;		/* no next yet */
  return part;
}

/* Mail garbage collection routines */


/* Mail garbage collect body
 * Accepts: pointer to body pointer
 */

void mail_free_body (body)
	BODY **body;
{
  if (*body) {			/* only free if exists */
    mail_free_body_data (*body);/* free its data */
    fs_give ((void **) body);	/* return body to free storage */
  }
}


/* Mail garbage collect body data
 * Accepts: body pointer
 */

void mail_free_body_data (body)
	BODY *body;
{
  if (body->subtype) fs_give ((void **) &body->subtype);
  mail_free_body_parameter (&body->parameter);
  if (body->id) fs_give ((void **) &body->id);
  if (body->description) fs_give ((void **) &body->description);
  switch (body->type) {		/* free contents */
  case TYPETEXT:		/* unformatted text */
    if (body->contents.text) fs_give ((void **) &body->contents.text);
    break;
  case TYPEMULTIPART:		/* multiple part */
    mail_free_body_part (&body->contents.part);
    break;
  case TYPEMESSAGE:		/* encapsulated message */
    mail_free_envelope (&body->contents.msg.env);
    mail_free_body (&body->contents.msg.body);
    if (body->contents.msg.text)
      fs_give ((void **) &body->contents.msg.text);
    break;
  case TYPEAPPLICATION:		/* application data */
  case TYPEAUDIO:		/* audio */
  case TYPEIMAGE:		/* static image */
  case TYPEVIDEO:		/* video */
    if (body->contents.binary) fs_give (&body->contents.binary);
    break;
  default:
    break;
  }
}

/* Mail garbage collect body parameter
 * Accepts: pointer to body parameter pointer
 */

void mail_free_body_parameter (parameter)
	PARAMETER **parameter;
{
  if (*parameter) {		/* only free if exists */
    if ((*parameter)->attribute) fs_give ((void **) &(*parameter)->attribute);
    if ((*parameter)->value) fs_give ((void **) &(*parameter)->value);
				/* run down the list as necessary */
    mail_free_body_parameter (&(*parameter)->next);
				/* return body part to free storage */
    fs_give ((void **) parameter);
  }
}


/* Mail garbage collect body part
 * Accepts: pointer to body part pointer
 */

void mail_free_body_part (part)
	PART **part;
{
  if (*part) {			/* only free if exists */
    mail_free_body_data (&(*part)->body);
				/* run down the list as necessary */
    mail_free_body_part (&(*part)->next);
    fs_give ((void **) part);	/* return body part to free storage */
  }
}

/* Mail garbage collect message cache
 * Accepts: pointer to message cache pointer
 *
 * The message cache is set to NIL when this function finishes.
 */

void mail_free_cache (cache,size)
	MESSAGECACHE ***cache;
	unsigned long *size;
{
  unsigned long i;
				/* free each array element */
  for (i = 0; i < *size; ++i) mail_free_elt (&(*cache)[i]);
  fs_give ((void **) cache);	/* return cache to free storage */
  *size = 0;			/* note that cache is empty */
}


/* Mail garbage collect cache element
 * Accepts: pointer to cache element pointer
 */

void mail_free_elt (elt)
	MESSAGECACHE **elt;
{
				/* only free if exists and no sharers */
  if (*elt && !--(*elt)->lockcount) {
    if ((*elt)->internal_date) fs_give ((void **) &(*elt)->internal_date);
    mail_free_envelope (&(*elt)->env);
    mail_free_body (&(*elt)->body);
    fs_give ((void **) elt);	/* return cache element to free storage */
  }
  else *elt = NIL;		/* else simply drop pointer */
}

/* Mail garbage collect envelope
 * Accepts: pointer to envelope pointer
 */

void mail_free_envelope (env)
	ENVELOPE **env;
{
  if (*env) {			/* only free if exists */
    if ((*env)->remail) fs_give ((void **) &(*env)->remail);
    mail_free_address (&(*env)->return_path);
    if ((*env)->date) fs_give ((void **) &(*env)->date);
    mail_free_address (&(*env)->from);
    mail_free_address (&(*env)->sender);
    mail_free_address (&(*env)->reply_to);
    if ((*env)->subject) fs_give ((void **) &(*env)->subject);
    mail_free_address (&(*env)->to);
    mail_free_address (&(*env)->cc);
    mail_free_address (&(*env)->bcc);
    if ((*env)->in_reply_to) fs_give ((void **) &(*env)->in_reply_to);
    if ((*env)->message_id) fs_give ((void **) &(*env)->message_id);
    if ((*env)->newsgroups) fs_give ((void **) &(*env)->newsgroups);
    fs_give ((void **) env);	/* return envelope to free storage */
  }
}


/* Mail garbage collect address
 * Accepts: pointer to address pointer
 */

void mail_free_address (address)
	ADDRESS **address;
{
  if (*address) {		/* only free if exists */
    if ((*address)->personal) fs_give ((void **) &(*address)->personal);
    if ((*address)->adl) fs_give ((void **) &(*address)->adl);
    if ((*address)->mailbox) fs_give ((void **) &(*address)->mailbox);
    if ((*address)->host) fs_give ((void **) &(*address)->host);
    if ((*address)->error) fs_give ((void **) &(*address)->error);
    mail_free_address (&(*address)->next);
    fs_give ((void **) address);/* return address to free storage */
  }
}
