/*
 * Program:	MBOX mail 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:	10 March 1992
 * Last Edited:	6 January 1999
 *
 * Copyright 1999 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 <stdio.h>
#include <ctype.h>
#include <errno.h>
extern int errno;		/* just in case */
#include "mail.h"
#include "osdep.h"
#include <sys/stat.h>
#include <sys/time.h>
#include "mbox.h"
#include "unix.h"
#include "misc.h"
#include "dummy.h"

/* MBOX mail routines */


/* Driver dispatch used by MAIL */

DRIVER mboxdriver = {
  "mbox",			/* driver name */
  DR_LOCAL|DR_MAIL,		/* driver flags */
  (DRIVER *) NIL,		/* next driver */
  mbox_valid,			/* mailbox is valid for us */
  unix_parameters,		/* manipulate parameters */
  unix_scan,			/* scan mailboxes */
  unix_list,			/* find mailboxes */
  unix_lsub,			/* find subscribed mailboxes */
  NIL,				/* subscribe to mailbox */
  NIL,				/* unsubscribe from mailbox */
  unix_create,			/* create mailbox */
  mbox_delete,			/* delete mailbox */
  mbox_rename,			/* rename mailbox */
  mbox_status,			/* status of mailbox */
  mbox_open,			/* open mailbox */
  unix_close,			/* close mailbox */
  NIL,				/* fetch message "fast" attributes */
  NIL,				/* fetch message flags */
  NIL,				/* fetch overview */
  NIL,				/* fetch message structure */
  unix_header,			/* fetch message header */
  unix_text,			/* fetch message body */
  NIL,				/* fetch partial message text */
  NIL,				/* unique identifier */
  NIL,				/* message number */
  NIL,				/* modify flags */
  unix_flagmsg,			/* per-message modify flags */
  NIL,				/* search for message based on criteria */
  NIL,				/* sort messages */
  NIL,				/* thread messages */
  mbox_ping,			/* ping mailbox to see if still alive */
  mbox_check,			/* check for new messages */
  mbox_expunge,			/* expunge deleted messages */
  unix_copy,			/* copy messages to another mailbox */
  unix_append,			/* append string message to mailbox */
  NIL				/* garbage collect stream */
};

				/* prototype stream */
MAILSTREAM mboxproto = {&mboxdriver};

/* MBOX mail validate mailbox
 * Accepts: mailbox name
 * Returns: our driver if name is valid, NIL otherwise
 */

DRIVER *mbox_valid (char *name)
{
  char tmp[MAILTMPLEN];
  char *s = mailboxfile (tmp,name);
				/* only INBOX, mbox must exist */
  if ((s && !*s) && (unix_isvalid ("mbox",tmp) || !errno) &&
      (unix_isvalid (sysinbox(),tmp) || !errno || (errno == ENOENT)))
    return &mboxdriver;
  return NIL;			/* can't win (yet, anyway) */
}

/* MBOX mail delete mailbox
 * Accepts: MAIL stream
 *	    mailbox name to delete
 * Returns: T on success, NIL on failure
 */

long mbox_delete (MAILSTREAM *stream,char *mailbox)
{
  return mbox_rename (stream,mailbox,NIL);
}


/* MBOX mail rename mailbox
 * Accepts: MAIL stream
 *	    old mailbox name
 *	    new mailbox name (or NIL for delete)
 * Returns: T on success, NIL on failure
 */

long mbox_rename (MAILSTREAM *stream,char *old,char *newname)
{
  char tmp[MAILTMPLEN];
  long ret = unix_rename (stream,"~/mbox",newname);
				/* recreate file if renamed INBOX */
  if (ret) unix_create (NIL,"mbox");
  else mm_log (tmp,ERROR);	/* log error */
  return ret;			/* return success */
}

/* MBOX Mail status
 * Accepts: mail stream
 *	    mailbox name
 *	    status flags
 * Returns: T on success, NIL on failure
 */

long mbox_status (MAILSTREAM *stream,char *mbx,long flags)
{
  MAILSTATUS status;
  unsigned long i;
  MAILSTREAM *tstream = NIL;
  MAILSTREAM *systream = NIL;
				/* make temporary stream (unless this mbx) */
  if (!stream && !(stream = tstream =
		   mail_open (NIL,mbx,OP_READONLY|OP_SILENT))) return NIL;
  status.flags = flags;		/* return status values */
  status.messages = stream->nmsgs;
  status.recent = stream->recent;
  if (flags & SA_UNSEEN)	/* must search to get unseen messages */
    for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++)
      if (!mail_elt (stream,i)->seen) status.unseen++;
  status.uidnext = stream->uid_last + 1;
  status.uidvalidity = stream->uid_validity;
  if (!status.recent &&		/* calculate post-snarf results */
      (systream = mail_open (NIL,sysinbox (),OP_READONLY|OP_SILENT))) {
    status.messages += systream->nmsgs;
    status.recent += systream->recent;
    if (flags & SA_UNSEEN)	/* must search to get unseen messages */
      for (i = 1; i <= systream->nmsgs; i++)
	if (!mail_elt (systream,i)->seen) status.unseen++;
				/* kludge but probably good enough */
    status.uidnext += systream->nmsgs;
  }
				/* pass status to main program */
  mm_status (stream,mbx,&status);
  if (tstream) mail_close (tstream);
  if (systream) mail_close (systream);
  return T;			/* success */
}

/* MBOX mail open
 * Accepts: stream to open
 * Returns: stream on success, NIL on failure
 */

MAILSTREAM *mbox_open (MAILSTREAM *stream)
{
  unsigned long i = 1;
  unsigned long recent = 0;
  char tmp[MAILTMPLEN];
				/* return prototype for OP_PROTOTYPE call */
  if (!stream) return &mboxproto;
				/* change mailbox file name */
  sprintf (tmp,"%s/mbox",myhomedir ());
  fs_give ((void **) &stream->mailbox);
  stream->mailbox = cpystr (tmp);
				/* open mailbox, snarf new mail */
  if (!(unix_open (stream) && mbox_ping (stream))) return NIL;
				/* notify upper level of mailbox sizes */
  mail_exists (stream,stream->nmsgs);
  while (i <= stream->nmsgs) if (mail_elt (stream,i++)->recent) ++recent;
  mail_recent (stream,recent);	/* including recent messages */
  return stream;
}

/* MBOX mail ping mailbox
 * Accepts: MAIL stream
 * Returns: T if stream alive, else NIL
 * No-op for readonly files, since read/writer can expunge it from under us!
 */

static int snarfed = 0;		/* number of snarfs */

long mbox_ping (MAILSTREAM *stream)
{
  int sfd;
  unsigned long size;
  struct stat sbuf;
  char *s,lock[MAILTMPLEN],lockx[MAILTMPLEN];
				/* time to try snarf and sysinbox non-empty? */
  if (LOCAL && !stream->rdonly && !stream->lock &&
      (time (0) > (LOCAL->lastsnarf + 30)) &&
      !stat (sysinbox (),&sbuf) && sbuf.st_size) {
				/* yes, open and lock sysinbox */
    if ((sfd = unix_lock (sysinbox (),O_RDWR,NIL,lockx,LOCK_EX)) >= 0) {
				/* locked sysinbox in good format? */
      if (fstat (sfd,&sbuf) || !(size = sbuf.st_size) ||
	  !unix_isvalid_fd (sfd,lock)) {
	sprintf (LOCAL->buf,"Mail drop %s is not in standard Unix format",
		 sysinbox ());
	mm_log (LOCAL->buf,ERROR);
      }
				/* sysinbox good, parse and excl-lock mbox */
      else if (unix_parse (stream,lock,LOCK_EX)) {
	lseek (sfd,0,L_SET);	/* read entire sysinbox into memory */
	read (sfd,s = (char *) fs_get (size + 1),size);
	s[size] = '\0';		/* tie it off */
				/* append to end of mbox */
	lseek (LOCAL->fd,LOCAL->filesize,L_SET);

				/* copy to mbox */
	if ((write (LOCAL->fd,s,size) < 0) || fsync (LOCAL->fd)) {
	  sprintf (LOCAL->buf,"New mail move failed: %s",strerror (errno));
	  mm_log (LOCAL->buf,ERROR);
				/* revert mbox to previous size */
	  ftruncate (LOCAL->fd,LOCAL->filesize);
	}
				/* sysinbox better not have changed */
	else if (fstat (sfd,&sbuf) || (size != sbuf.st_size)) {
	  sprintf (LOCAL->buf,"Mail drop %s lock failure, old=%lu now=%lu",
		   sysinbox (),size,sbuf.st_size);
	  mm_log (LOCAL->buf,ERROR);
				/* revert mbox to previous size */
	  ftruncate (LOCAL->fd,LOCAL->filesize);
	  /* Believe it or not, a Singaporean government system actually had
	   * symlinks from /var/mail/user to ~user/mbox.  To compound this
	   * error, they used an SVR4 system; BSD and OSF locks would have
	   * prevented it but not SVR4 locks.
	   */
	  if (!fstat (sfd,&sbuf) && (size == sbuf.st_size))
	    syslog (LOG_ALERT,"File %s and %s are the same file!",
		    sysinbox,stream->mailbox);
	}
	else {			/* data copied OK */
	  ftruncate (sfd,0);	/* truncate sysinbox to zero bytes */
	  if (!snarfed++) {	/* have we snarfed before? */
				/* syslog if server, else mm_log() */
	    sprintf (LOCAL->buf,"Moved %lu bytes of new mail to %s from %s",
		     size,stream->mailbox,sysinbox ());
	    if (strcmp ((char *) mail_parameters (NIL,GET_SERVICENAME,NIL),
			"unknown"))
	      syslog (LOG_INFO,"%s host= %s",LOCAL->buf,tcp_clienthost ());
	    else mm_log (LOCAL->buf,WARN);
	  }
	}
				/* done with sysinbox text */
	fs_give ((void **) &s);
				/* all done with mbox */
	unix_unlock (LOCAL->fd,stream,lock);
	mail_unlock (stream);	/* unlock the stream */
	mm_nocritical (stream);	/* done with critical */
      }
				/* all done with sysinbox */
      unix_unlock (sfd,NIL,lockx);
    }
    LOCAL->lastsnarf = time (0);/* note time of last snarf */
  }
  return unix_ping (stream);	/* do the unix routine now */
}

/* MBOX mail check mailbox
 * Accepts: MAIL stream
 */

void mbox_check (MAILSTREAM *stream)
{
				/* do local ping, then do unix routine */
  if (mbox_ping (stream)) unix_check (stream);
}


/* MBOX mail expunge mailbox
 * Accepts: MAIL stream
 */

void mbox_expunge (MAILSTREAM *stream)
{
  unix_expunge (stream);	/* do expunge */
  mbox_ping (stream);		/* do local ping */
}
