/* -*-C-*-
*******************************************************************************
*
* File:         appnmail.m
* RCS:          /usr/local/sources/CVS/mailapp-utilities/appnmail.m,v 1.11 1997/06/02 21:09:47 tom Exp
* Description:  Append stdin to Mail.app mailbox
* Author:       Carl Edman
* Created:      Fri Mar 12 18:21:23 1993
* Modified:     Sat May 31 18:07:55 1997 Tom Hageman <tom@basil.icce.rug.nl>
* Language:     Objective C
* Package:      mailapp-utilities
* Status:       First release.
*
* (C) Copyright 1993, but otherwise this file is perfect freeware.
*
*******************************************************************************
*/

#import <appkit/appkit.h>
#import <stdlib.h>
#import <stdio.h>
#import <string.h>
#import <stdarg.h>
#import <ctype.h>
#import <regex.h>
#import <sys/file.h>
#import <sys/param.h>
#import <sys/types.h>
#import <sys/stat.h>
#import <defaults/defaults.h>
#import "MailSpeaker.h"
#import "mailutil.h"
#import "mailtoc.h"
#import "optutil.h"

#define USAGE "\
Usage: %s [-nv][-r|-f|-d|][-p pri][-H|-V] mbox...\n"

#define HELP "\
 -n           skip mailbox if locked\n\
 -v           talkative mode\n\
 -r           mark message as read\n\
 -f           mark message as flagged\n\
 -d           mark message as deleted\n\
 -p pri       set message's priority level to `pri'\n\
 -V           show version\n\
 -H           this help\n\
"

#define APPNMAIL_LOCK	".appnmail.lock"
#define APPNMAIL_MBOX	"appnmail_mbox"
#define APPNMAIL_TOC	"appnmail_table_of_contents"
#define APPNMAIL_DEQUEUE_LOCK ".appnmail_dequeue.lock"

char *header=0,*content=0;
int headerlen=0,contentlen=0;
int headermaxlen=0,contentmaxlen=0;
char line[LINELEN];

/* ISO header conversion cruft. */
#import "iso2next.h"
/* Tables in there are expected to be in the "recode --header" format. */
/* XXX maybe we'd better use "recode --header --strict"? */

int iso_convert(char *line)
{
   static const struct
   {
      const char *name;
      const unsigned char *contents;
   }
   table[] = {
      /* The contents of these tables are defined in "iso2next.h".
	 If you add one there, don't forget to add it here too. */
      { "iso-8859-1", latin1_to_next },
      { "iso-8859-2", latin2_to_next },
   };
   static struct regex *isore = 0;
   const unsigned char *tt = 0;
   const char *name;
   int i, namelen;

   if (!isore) isore = re_compile("=?\\([^?]*\\)?Q?",1);
   if (re_match(line, isore) == 0) return 0;
   name = isore->braslist[0];
   namelen = (isore->braelist[0] - isore->braslist[0]);
   for (i = 0;  i < sizeof(table)/sizeof(table[0]);  i++)
   {
      if (strncasecmp(table[i].name, name, namelen) == 0 &&
	  table[i].name[namelen] == 0)
      {
	 tt = table[i].contents;
	 break;
      }
   }
   if (tt != 0)
   {
      int errors = 0;
      // hack to translate iso-stuff, in-place.
      char *s = (char *)isore->end, *t;

      for (; (t = strchr(s, '=')) != 0;  s = t)
      {
	 if (t > s && t[-1] == '?')
	 {
	    // ?= is end quote.
	    if (errors > 0)
	    {
	       fprintf(stderr, "%s: warning: MIME 8-bit header encoding `%.*s' incomplete conversion\n", progname(), namelen, name);
	       return -1;
	    }
	    strcpy(t-1, t+1);
	    strcpy((char *)isore->start, isore->end);
	    return 1;
	 }
	 else
	 {
	    //  parse "=XX" where XX is a 2-digit hex number
	    unsigned c = 0;
	    
	    sscanf(t+1, "%2x", &c);
	    if (c < 256 && tt[c] != 0)
	    {
	       *t = tt[c];
	       strcpy(t+1, t+3);
	    }
	    else
	       ++errors;
	    ++t;
	 }
      }
      fprintf(stderr, "%s: warning: Missing terminating `?=' in MIME 8-bit header `%s'\n", progname(), line);
      return 0;
   }
   fprintf(stderr, "%s: warning: MIME 8-bit header encoding `%.*s' is unsupported\n", progname(), namelen, name);
   return 0;
}

struct message_index *readmail(void)
{
   struct message_index *mi;
   int i,pri=0;
   char from[LINELEN]="",subject[LINELEN]="",reference[LINELEN]="";
   struct regex *fre=re_compile("^From:  *\\(.*\\)\n",0);
   struct regex *sre=re_compile("^Subject:  *\\(.*\\)\n",0);
   struct regex *dre=re_compile("^Date: .*\\([ 0123][0-9]\\) \\([A-Z][a-z][a-z][a-z]*\\) 1*9*\\([789][0-9]\\)",0);
   struct regex *rre=re_compile("^Next-Attachment: \\.tar\\.\\([0-9]*\\)\\.\\(.*\\)\\.attach, \\(E*,* *\\)\\([0-9]*\\), \\([0-9]*/[0-9]*\\), \\([0-9]*\\), \\([0-9]*\\)",0);
   struct regex *mre=re_compile("^Content-Type: \\([a-zA-Z]*/[a-zA-Z]*\\)",0);
   struct regex *pre=0;
   const char *pridef=0;

   if (pridef=NXGetDefaultValue("Mail","PriorityHeader"))
   {
      static char buf[MAXPATHLEN];
      sprintf(buf,"^%s: [ \t]*\\([^ \t]*\\)[ \t]*\n",pridef);
      pre=re_compile(buf,0);
      pridef=NXGetDefaultValue("Mail","PriorityValues");
   }
   
   mi=malloc(sizeof(*mi));
   
   mi->record_length=sizeof(*mi);
   mi->mes_offset=-1;
   mi->status='*';
   mi->msgtype=' ';
   mi->encrypted=' ';
   mi->sync=' ';
   mi->mes_date=message_current_date();

   headerlen=0;
   
   while(fgets(line,LINELEN,stdin))
   {
      if (*line=='\n') break;
      if (re_match(line,fre)==1)
      {
	 strpcpy(from,fre->braslist[0],fre->braelist[0]);
	 iso_convert(from);
      }
      if (re_match(line,sre)==1)
      {
	 strpcpy(subject,sre->braslist[0],sre->braelist[0]);
	 iso_convert(subject);
      }
      if (re_match(line,dre)==1)
	 mi->mes_date=message_date(atoi(dre->braslist[2])+1900,
				   dre->braslist[1],atoi(dre->braslist[0]));
      if (pre && pridef && (re_match(line,pre)==1))
      {
	 const char *beg,*end;

	 for(beg=pridef,pri=1;*beg;(beg=*end ? end+1 : end),(pri++))
	 {
	    if (!(end=index(beg,' '))) end=beg+strlen(beg);
	    if (((pre->braelist[0]-pre->braslist[0])==(end-beg))
		&&(strncmp(pre->braslist[0],beg,end-beg)==0))
	       break;
	 }
	 if (!*beg) pri=0;
      }
      if ((mi->msgtype==' ') && (re_match(line,mre)==1))
      {
	 if (!strpcaseequ("text/plain",mre->braslist[0],mre->braelist[0]))
	    mi->msgtype='m';
      }
      if (re_match(line,rre)==1)
      {
	 char *c;
	 mi->msgtype='r';
	 strpcpy(reference,rre->braslist[1],rre->braelist[1]);
	 c=reference+strlen(reference);
	 *c++='_';
	 while(c-reference<22) *c++='_';
	 *c='\0';
	 sprintf(c,"%lu",(unsigned long)time(0));
	 strcat(reference,".attach");
	 c=reference+strlen(reference);
	 strcat(reference,", ");
	 if (*(rre->braslist[2])=='E')
	 {
	    strcat(reference,"E, ");
	    mi->encrypted='e';
	 }
	 strpcat(reference,rre->braslist[4],rre->braelist[4]);
	 strcat(reference,"\n");
	 appstring(&header,&headerlen,&headermaxlen,"Next-Reference: ",
		   strlen("Next-Reference: "));
	 appstring(&header,&headerlen,&headermaxlen,reference,strlen(reference));
	 *c='\0';
      }
      else
      {
	 appstring(&header,&headerlen,&headermaxlen,line,strlen(line));
      }
   }
   appstring(&header,&headerlen,&headermaxlen,"\n",1);

   contentlen=0;
   
   while((i=fread(growstring(&content,&contentlen,&contentmaxlen,LINELEN*16),
		  1,16*LINELEN,stdin))>0)
      contentlen+=i;
   if (content[contentlen-1]!='\n')
      appstring(&content,&contentlen,&contentmaxlen,"\n",1);

   mi->mes_length=headerlen;
   if (mi->msgtype!='r') mi->mes_length+=contentlen;
   mi->record_length+=strlen(from)+1+strlen(subject)+1+strlen(reference)+1;
   mi->record_length+=sizeof(int)+sizeof(time_t)+sizeof(int);
   mi=realloc(mi,mi->record_length);
   strcpy(mi->data,from);
   strcpy(mi->data+strlen(from)+1,subject); 
   strcpy(mi->data+strlen(from)+1+strlen(subject)+1,reference);

   message_set_attachsize(mi,0);
   message_set_attachtime(mi,0);
   message_set_priority(mi,pri);
   
   free(fre); free(sre); free(rre); free(dre);
   if (pre) free(pre);
   return mi;
}

const char *get_command(const char *defaultKey, ...)
{
   /* Find command named `defaultKey' in Mail's defaults database, and check
      if it is an executable.  If not, try each of the defaults given on
      the (NULL-terminated) argument list in turn. */
   const char *command = NXGetDefaultValue("Mail", defaultKey);
   va_list ap;

   va_start(ap, defaultKey);
   do
   {
      struct stat st;
      if (command == NULL) continue;
      if (command[0] != '/') break; /* XXX should check PATH? */
      if (access(command, X_OK) < 0) continue;
      if (stat(command, &st) < 0) continue;
      if ((st.st_mode & S_IFMT) == S_IFREG) break;
   }
   while ((command = va_arg(ap, const char *)) != NULL);
   va_end(ap);
   if (command == NULL)
   {
      fprintf(stderr,"%s: cannot find executable for `%s'.\n", progname(), defaultKey);
   }
   return command;
}

#define INCOMING_MBOX	"Incoming_Mail"
#define INCOMING_TOC	"Incoming_Table_of_Contents"

int incorporate_mail(port_t mailPort, const char *mboxname, const char *mbox, const char *table_of_contents)
{
   MailSpeaker *speaker;
   int interval=1,lastinterval=0;
   int result;

   /* Signal Mail.app to incorporate mail.  Assumes Mail the mailbox open,
      and approporiate locks are in place. */
   if (mailPort==PORT_NULL) return -1;
   if (link(mbox, INCOMING_MBOX)<0)
   {
      fprintf(stderr,"%s: %s: cannot link %s to %s (%s)\n",progname(),mboxname,mbox,INCOMING_MBOX,strerror(errno));
      return -1;
   }
   if (link(table_of_contents, INCOMING_TOC)<0)
   {
      fprintf(stderr,"%s: %s: cannot link %s to %s (%s)\n",progname(),mboxname,mbox,INCOMING_MBOX,strerror(errno));
      unlink(INCOMING_MBOX);
      return -1;
   }
   speaker = [[MailSpeaker alloc] init];
   [speaker setSendPort:mailPort];
   result=[speaker incorporateNewMail];
   [speaker free];

   if (result!=0)
   {
      fprintf(stderr,"%s: %s: -incorporateNewMail failed\n",progname(),mboxname);
      unlink(INCOMING_MBOX);
      unlink(INCOMING_TOC);
      return 1;
   }

   /* Unfortunately this seems to be asynchronous, ie. Mail.app does not
      immediately process (and remove) the Incoming mbox when it receives
      a remote kick in the butt.  So we have to wait a little while... */
   while (access(INCOMING_MBOX, F_OK)==0 || access(INCOMING_TOC, F_OK)==0)
   {
      /* Nice Fibonacci series. */
      int newinterval=interval+lastinterval;

      lastinterval=interval;
      interval=newinterval;
      if (interval > 34)
      {
	 /* so we waited for 1+1+2+3+5+8+13+21+34 = 88 seconds.  Is this enough? */
	 fprintf(stderr,"%s: %s: Mail.app failed to incorporate Incoming mail???\n",progname(),mboxname);
	 unlink(INCOMING_MBOX);
	 unlink(INCOMING_TOC);
	 return 1;
      }
      sleep(interval);
   }
   unlink(mbox);
   unlink(table_of_contents);
   return 0;
}

/* Transfer messages from one {mbox, table_of_contents} combo to another. */
int transfer_mbox_contents(const char *mboxname, const char *srcmbox, const char *srctoc, const char *dstmbox, const char *dsttoc)
{
   int result=-1;
   int srcboxfd=-1, dstboxfd=-1;
   FILE *srctocf=NULL, *dsttocf=NULL;
   struct table_of_contents_header *srctoch=NULL, *dsttoch=NULL;
   struct message_index *mi;
   time_t mboxtime;
   int n;

   mboxtime = mtime(srcmbox);
   if ((srcboxfd = open(srcmbox, O_RDONLY)) < 0)
   {
      fprintf(stderr,"%s: %s: opening %s (%s)\n", progname(), mboxname, srcmbox, strerror(errno));
      goto _close;
   }
   if ((srctocf = fopen(srctoc, "r+b")) == NULL)
   {
      fprintf(stderr,"%s: %s: opening %s (%s)\n", progname(), mboxname, srctoc, strerror(errno));
      goto _close;
   }
   if ((srctoch = get_table_of_contents_header(srctocf, 0)) == NULL)
   {
      fprintf(stderr,"%s: %s: [%s] invalid header\n", progname(), mboxname, srctoc);
      goto _close;
   }
   if (srctoch->mbox_time != mboxtime)
   {
      fprintf(stderr,"%s: %s: [%s] out of sync\n", progname(), mboxname, srctoc);
      goto _close;
   }

   mboxtime = mtime(dstmbox);
   if ((dstboxfd = open(dstmbox,O_WRONLY)) < 0)
   {
      fprintf(stderr,"%s: %s: opening %s (%s)\n", progname(), mboxname, dstmbox, strerror(errno));
      goto _close;
   }
   if ((dsttocf = fopen(dsttoc,"r+b")) == NULL)
   {
      fprintf(stderr,"%s: %s: opening %s (%s)\n", progname(), mboxname, dsttoc, strerror(errno));
      goto _close;
   }
   if ((dsttoch = get_table_of_contents_header(dsttocf,1)) == NULL)
   {
      fprintf(stderr,"%s: %s: [%s] invalid header\n", progname(), mboxname, dsttoc);
      goto _close;
   }
   if (dsttoch->mbox_time != mboxtime)
   {
      fprintf(stderr,"%s: %s: [%s] out of sync\n", progname(), mboxname, dsttoc);
      goto _close;
   }

   for (n = 0;  n < srctoch->num_msgs;  free(mi), n++)
   {
      long src_offset, src_length;
      void *buf=NULL;

      errno = 0;
      if ((mi = get_message_index(srctocf)) == NULL)
      {
         fprintf(stderr,"%s: %s: [%s] inconsistency (%s)\n", progname(), mboxname, srctoc, (errno ? strerror(errno) : "invalid"));
         goto _close;
      }
      if (mi->status == 'D') continue; // Deleted in a previous pass.

      src_offset = mi->mes_offset;
      src_length = mi->mes_length;
      if (lseek(srcboxfd, src_offset, SEEK_SET) != src_offset ||
	  (buf = malloc(src_length)) == NULL ||
	  (src_length = read(srcboxfd, buf, src_length)) != mi->mes_length)
      {
	 fprintf(stderr,"%s: %s: [%s] inconsistency (%s)\n", progname(), mboxname, srcmbox, (errno ? strerror(errno) : "invalid"));
	 free(buf);
	 free(mi);
         goto _close;
      }
      mi->mes_offset = lseek(dstboxfd, 0, SEEK_END);
      if ((mi->mes_length = write(dstboxfd, buf, src_length)) != src_length)
      {
         fprintf(stderr,"%s: %s: [%s] inconsistency (%s)\n", progname(), mboxname, dstmbox, (errno ? strerror(errno) : "invalid"));
      }
      free(buf);

      /* Append message index to destination table of contents. */
      fseek(dsttocf, 0, SEEK_END);
      put_message_index(dsttocf, mi);

      /* Update header. */
      dsttoch->num_msgs++;
      dsttoch->mbox_time = mtime(dstmbox);
      fseek(dsttocf, 0, SEEK_SET);
      put_table_of_contents_header(dsttocf, dsttoch);

      /* Mark message as deleted (and restore original offset/length) in source mbox. */
      mi->status = 'D';
      mi->mes_offset = src_offset;
      mi->mes_length = src_length;
      fseek(srctocf, -mi->record_length, SEEK_CUR);
      put_message_index(srctocf, mi);
      fseek(srctocf, 0, SEEK_CUR);
   }

   /* Update header once again, just to be sure... */
   close(dstboxfd), dstboxfd = -1;
   dsttoch->mbox_time = mtime(dstmbox);
   fseek(dsttocf, 0, SEEK_SET);
   put_table_of_contents_header(dsttocf, dsttoch);

   result = 0;
 _close:
   free(dsttoch);
   if (dstboxfd >= 0) close(dstboxfd);
   if (dsttocf != NULL) fclose(dsttocf);
   free(srctoch);
   if (srcboxfd >= 0) close(srcboxfd);
   if (srctocf != NULL) fclose(srctocf);

   return result;
}

/* Dequeue mail by copying it from appnmail's private mbox to the real mbox.
   assumes proper locking has been done. */
void dequeue_mail(const char *mboxname)
{
   if (access(APPNMAIL_MBOX, F_OK) != 0) return;
   if (transfer_mbox_contents(mboxname, APPNMAIL_MBOX, APPNMAIL_TOC, "mbox", "table_of_contents") != 0) return;

   /* If successful, remove our private inbox. */
   unlink(APPNMAIL_MBOX);
   unlink(APPNMAIL_TOC);
}


void main(int ac,char *av[])
{
   struct message_index *mi;
   const char *decodeCmd=0;
   const char*uncompressCmd=0;
   const char *tarCmd=0;
   int c,ai;
   int readflg=0,nowaitflg=0,verboseflg=0,multiflg=0,flagflg=0,deleteflg=0;
   struct regex *mailutilre=0;
   int pri=-1;
   time_t mboxtime;
   int mfd=-1,tocfd=-1;
   FILE *tocf=NULL;
   struct table_of_contents_header *toch=0;
   char host[MAXHOSTNAMELEN];
   int status=EXIT_SUCCESS;

   set_progname(av[0]);

   while((c=getopt(ac,av,"rfdvnp:VH"))!=EOF) switch(c)
   {
    case 'r':
      readflg++;
      if (flagflg||deleteflg) status=EXIT_USAGE;
      break;
    case 'f':
      flagflg++;
      if (readflg||deleteflg) status=EXIT_USAGE;
      break;
    case 'd':
      deleteflg++;
      if (readflg||flagflg) status=EXIT_USAGE;
      break;
    case 'n':
      nowaitflg++;
      break;
    case 'v':
      verboseflg++;
      break;
    case 'm': // XXX currently unused.
      multiflg++;
      break;
    case 'p':
      if (pri!=-1) status=EXIT_USAGE;
      pri=atoi(optarg);
      if (pri<0) status=EXIT_USAGE;
      break;
    case 'V':
      status=EXIT_VERSION;
      break;
    case 'H':
      status=EXIT_HELP;
      break;
    case '?':
    default:
      status=EXIT_USAGE;
   }
   if (status==EXIT_SUCCESS && optind>=ac) status=EXIT_USAGE;
   handle_usage_help_version(status, USAGE, HELP);

   mi=readmail();

   if (!header || (headerlen<5) || strncmp(header,"From ",5))
   {
      fprintf(stderr,"%s: message is not in UNIX mailbox format\n", progname());
      exit(EXIT_FAILURE);
   }

   if (readflg) mi->status=' ';
   else if (flagflg) mi->status='+';
   else if (deleteflg) mi->status='d';

   if (pri>=0) message_set_priority(mi,pri);
   
   for (ai=optind; ai<ac; ai++)
   {
      char *mbox="mbox", *table_of_contents="table_of_contents";
      int ownslock=1,ownsappnmaillock=0;
      int result=EXIT_SUCCESS;

      if (verboseflg) fprintf(stderr,"%s[%d] Entering %s to deliver...\n",progname(),getpid(),av[ai]);
      if (cd_mbox(av[ai],1))
      {
	 result=EXIT_FAILURE;
	 goto next;
      }
      if (c=try_lock_mbox(host, line, NULL))
      {
	 if (c<0)
	 {
	    result=EXIT_FAILURE;
	    goto next;
	 }
	 /* If the mbox is locked (by Mail.app), we'll write the message to
	    a temporary mbox, and try to incorporate it into the mbox later.
	    (so the message is at least stored to disk in case anything
	     goes wrong).
	    XXX The way to determine whether the lock is owned by Mail.app
	    or one of our own mailapp-utilities is completely bogus
	    and depends on what is written in the user field of the lock
	    by try_lock_mbox(). */
	 if (!mailutilre) mailutilre=re_compile(LOCK_USER_RE,0);
	 if (!re_match(line,mailutilre))
	 {
	    if (verboseflg>1) fprintf(stderr,"%s[%d] Delivering into temporary mbox...\n",progname(),getpid());
	    ownslock=0;
	    mbox=APPNMAIL_MBOX;
	    table_of_contents=APPNMAIL_TOC;
	    /* Operations on our temp. mbox are protected with our own lock. */
	    /* XXX What about stale locks? */
	    if (lock_mbox_file(APPNMAIL_LOCK,0)!=0)
	    {
	       result=EXIT_FAILURE;
	       goto unlock;
	    }
	    ownsappnmaillock=1;
	 }
	 else if (lock_mbox(0))
	 {
	    result=EXIT_FAILURE;
	    goto next;
	 }
      }
      if (ownslock)
      {
	 /* try to dequeue mail first, but don't insist on it if already
	    locked by another appnmail. */
	 if (lock_mbox_file(APPNMAIL_LOCK,1)==0)
	 {
	    dequeue_mail(av[ai]);
	    unlock_mbox_file(APPNMAIL_LOCK);
	 }
      }

      mboxtime=mtime(mbox);
      
      if ((mfd=open(mbox,O_WRONLY|O_APPEND|O_CREAT,0644))==-1)
      {
	 fprintf(stderr,"%s: %s: %s\n",progname(),av[ai],strerror(errno));
	 result=EXIT_FAILURE;
	 goto unlock;
      }

      /* Even if we cannot update the table of contents,
         we'll try our damnedest to deliver the message anyway. */

      if ((tocfd=open(table_of_contents,O_CREAT|O_RDWR,0644))==-1 ||
	  (tocf=fdopen(tocfd,"r+b"))==NULL)
      {
	 fprintf(stderr,"%s: %s: %s\n",progname(),av[ai],strerror(errno));
	 result=EXIT_FAILURE;
	 //goto unlock;
      }
      else
      {
	 toch=get_table_of_contents_header(tocf,1);
	 if (!toch)
	 {
	    fprintf(stderr,"%s: %s: warning: invalid table_of_contents\n",progname(),av[ai]);
	    result=EXIT_FAILURE;
	 }
	 else if (toch->mbox_time!=mboxtime)
	 {
	    fprintf(stderr,"%s: %s: warning: table_of_contents out of sync\n",progname(),av[ai]);
	    result=EXIT_FAILURE;
	 }
      }

      /* FOR EACH */
      lseek(mfd,0,SEEK_END);
      mi->mes_offset=lseek(mfd,0,SEEK_CUR);

      if (mi->msgtype=='r')
      {
	 char path[MAXPATHLEN];
	 FILE *f;

	 if (verboseflg>1) fprintf(stderr,"%s[%d] Extracting NeXTmail attachment...\n",progname(),getpid());

	 /* Locate executables, if not already done so. */
	 if (!decodeCmd && !(decodeCmd=get_command("appnmailDecodeCommand","/NextApps/Mail.app/decode",NULL)))
	 {
	    /* ...not really a regular Mail default. */
	    result=EXIT_FAILURE;
	    goto unlock;
	 }
	 if (!uncompressCmd && !(uncompressCmd=get_command("UncompressCommand","/usr/bin/gunzip","/usr/ucb/uncompress",NULL)))
	 {
	    result=EXIT_FAILURE;
	    goto unlock;
	 }
	 if (!tarCmd && !(tarCmd=get_command("TarCommand","/NextApps/Mail.app/safetar","/usr/bin/gnutar",NULL)))
	 {
	    result=EXIT_FAILURE;
	    goto unlock;
	 }
	 strcpy(path,message_reference(mi));
	 if ((mkdir(path,0755))==-1)
	 {
	    fprintf(stderr,"%s: %s: %s: %s\n",progname(),av[ai],path,strerror(errno));
	    result=EXIT_FAILURE;
	    goto unlock;
	 }
	 sprintf(line,"cd %s && %s | %s | %s xf -",path,decodeCmd,uncompressCmd,tarCmd);
	 if (!(f=popen(line,"w")))
	 {
	    fprintf(stderr,"%s: %s: %s: %s\n",progname(),av[ai],path,strerror(errno));
	    result=EXIT_FAILURE;
	    goto unlock;
	 }
	 fwrite(content,contentlen,1,f);
	 if ((c=pclose(f))!=0)
	 {
	    fprintf(stderr,"%s: %s: %s failed (status %d)\n",progname(),av[ai],line,c);
	    result=EXIT_FAILURE;
	    goto unlock;
	 }
         message_set_attachsize(mi,dirsize(path));
         message_set_attachtime(mi,mtime(path));
	 /* Add message body *after* all failure points, to avoid TOC corruption. */
	 write(mfd,header,headerlen);
         write(mfd,"\n",1);
	 // XXX should check status.
      }
      else
      {
	 write(mfd,header,headerlen);
         write(mfd,content,contentlen);
	 // XXX should check status.
      }
      if (toch)
      {
	 toch->num_msgs++;
	 fseek(tocf,0,SEEK_END);
	 put_message_index(tocf,mi);
      }
      /* END FOR EACH */
      
      close(mfd), mfd=-1;
      if (toch)
      {
	 if (toch->mbox_time==mboxtime) toch->mbox_time=mtime(mbox);
	 fseek(tocf,0,SEEK_SET);
	 put_table_of_contents_header(tocf,toch);
      }
    unlock:
      if (tocf!=NULL) fclose(tocf), tocf=NULL;
      else if (tocfd>=0) close(tocfd), tocfd=-1;
      if (mfd>=0) close(mfd), mfd=-1;
      if (ownslock)
      {
	 unlock_mbox();
      }
      else if (ownsappnmaillock)
      {
	 unlock_mbox_file(APPNMAIL_LOCK);
      }
    next:
      if (result!=EXIT_SUCCESS) status=result;
      else if (verboseflg) fprintf(stderr,"%s[%d] ...deliver %s done\n",progname(),getpid(),av[ai]);
      if (toch) { free(toch); toch=0; }
      uncd_mbox();
   }

   if (status == EXIT_SUCCESS && !nowaitflg)
   {
      /* Dequeue pass. */
      for (ai = optind;  ai < ac;  ai++)
      {
	 if (cd_mbox(av[ai], 0) != 0) continue;
	 if (access(APPNMAIL_MBOX, F_OK) == 0)
	 {
	    /* Make sure that only a single appnmail is waiting to dequeue. */
	    if (lock_mbox_file(APPNMAIL_DEQUEUE_LOCK, 1) == 0)
	    {
	       int unlocked = 0;

	       if (verboseflg) fprintf(stderr,"%s[%d] Entering %s to dequeue...\n",progname(),getpid(),av[ai]);
	       if ((c = try_lock_mbox(host, line, NULL)) > 0)
	       {
		  if (!mailutilre) mailutilre = re_compile(LOCK_USER_RE,0);
		  if (!re_match(line, mailutilre))
		  {
		     /* Not locked by appnmail; try to contact Mail.app */
		     int incorporateDelay = 0;
		     const char *delaydef = NXGetDefaultValue("Mail", "appnmailIncorporateDelay");

		     if (delaydef && isdigit(*delaydef)) incorporateDelay = atoi(delaydef);

		     /* Wait some time (&& recheck lock) */
		     if (incorporateDelay != 0)
		     {
			if (verboseflg>2) fprintf(stderr,"%s[%d] Sleeping for appnmailIncorporateDelay = %d seconds...\n",progname(),getpid(),incorporateDelay);
			sleep(incorporateDelay);
			c = try_lock_mbox(host, line, NULL);
		     }
		     if (c > 0 && !re_match(line, mailutilre))
		     {
			if (verboseflg>2) fprintf(stderr,"%s[%d] Locking %s...\n",progname(),getpid(),APPNMAIL_LOCK);
			if ((c = lock_mbox_file(APPNMAIL_LOCK, 0)) == 0)
			{
			   /* XXX Should re-check .lock host? */
			   c = -1;
			   if (access(APPNMAIL_MBOX, F_OK) == 0)
			   {
			      port_t mailPort;

			      if (verboseflg>2) fprintf(stderr,"%s[%d] Locating Mail port on host %s...\n",progname(),getpid(), host);
			      if ((mailPort = NXPortFromName("MailSendDemo", host)) != PORT_NULL)
			      {
				 /* Unlock dequeue_lock now, to avoid race conditions.
				    XXX should analyze this more thoroughly... */
				 unlock_mbox_file(APPNMAIL_DEQUEUE_LOCK);
				 unlocked++;
				 if (verboseflg>1) fprintf(stderr,"%s[%d] Incorporating mail in open mailbox...\n",progname(),getpid());
				 if ((c = incorporate_mail(mailPort, av[ai], APPNMAIL_MBOX, APPNMAIL_TOC)) == 0)
				 {
				    c = -1;
				 }
			      }
			   }
			   unlock_mbox_file(APPNMAIL_LOCK);
			}
		     }
		  }
		  if (c > 0) c = lock_mbox(0);
	       }

	       if (c == 0)
	       {
		  if (verboseflg>2) fprintf(stderr,"%s[%d] Locking %s...\n",progname(),getpid(),APPNMAIL_LOCK);
		  if ((c=lock_mbox_file(APPNMAIL_LOCK, 0)) == 0)
		  {
		     /* Unlock dequeue_lock now, to avoid race conditions. */
		     if (!unlocked)
		     {
			unlock_mbox_file(APPNMAIL_DEQUEUE_LOCK);
			unlocked++;
		     }
		     if (access(APPNMAIL_MBOX, F_OK) == 0)
		     {
			if (verboseflg>1) fprintf(stderr,"%s[%d] Appending mail to mailbox...\n",progname(),getpid());
			dequeue_mail(av[ai]);
		     }
		     unlock_mbox_file(APPNMAIL_LOCK);
		  }
		  unlock_mbox();
	       }
	       if (!unlocked)
	       {
		  unlock_mbox_file(APPNMAIL_DEQUEUE_LOCK);
	       }
	       if (verboseflg) fprintf(stderr,"%s[%d] ...dequeue %s done\n",progname(),getpid(),av[ai]);
	    }
	 }
	 uncd_mbox();
      }
   }
   exit(status);
}
