home *** CD-ROM | disk | FTP | other *** search
/ Big Green CD 8 / BGCD_8_Dev.iso / NEXTSTEP / UNIX / Mail / mailapp-utilities-2.1-MIHS / Source / appnmail.m < prev    next >
Encoding:
Text File  |  1998-02-14  |  25.8 KB  |  862 lines

  1. /* -*-ObjC-*-
  2. *******************************************************************************
  3. *
  4. * File:         appnmail.m
  5. * RCS:          /usr/local/sources/CVS/mailapp-utilities/appnmail.m,v 1.18 1998/02/14 17:34:41 tom Exp
  6. * Description:  Append stdin to Mail.app mailbox
  7. * Author:       Carl Edman
  8. * Created:      Fri Mar 12 18:21:23 1993
  9. * Modified:     Fri Jan  2 14:57:58 1998 Tom Hageman <tom@basil.icce.rug.nl>
  10. * Language:     Objective C
  11. * Package:      mailapp-utilities
  12. * Status:       Exp.
  13. *
  14. * (C) Copyright 1993, but otherwise this file is perfect freeware.
  15. *
  16. *******************************************************************************
  17. */
  18.  
  19. #import <libc.h>
  20. #import <errno.h>
  21. //#import <stdlib.h>
  22. //#import <stdio.h>
  23. //#import <string.h>
  24. //#import <stdarg.h>
  25. #import <ctype.h>
  26. #import <regex.h>
  27. //#import <sys/file.h>
  28. //#import <sys/param.h>
  29. //#import <sys/types.h>
  30. //#import <sys/stat.h>
  31. #import "compat.h"
  32. #import "MailProxy.h"
  33. #import "mailutil.h"
  34. #define _MAILTOC_DEFINES 1
  35. #import "mailtoc.h"
  36. #import "optutil.h"
  37. #import "iso_convert.h"
  38.  
  39. #define USAGE "\
  40. Usage: %s [-nvi][-r|-f|-d|][-p pri][-H|-V] mbox...\n"
  41.  
  42. #define HELP "\
  43. -n           skip mailbox if locked\n\
  44. -v           talkative mode\n\
  45. -i           force incorporation of pending messages into mailbox\n\
  46. -r           mark message as read\n\
  47. -f           mark message as flagged\n\
  48. -d           mark message as deleted\n\
  49. -p pri       set message's priority level to `pri'\n\
  50. -V           show version\n\
  51. -H           this help\n\
  52. "
  53.  
  54. #define APPNMAIL_LOCK    ".appnmail.lock"
  55. #define APPNMAIL_MBOX    "appnmail_mbox"
  56. #define APPNMAIL_TOC    "appnmail_table_of_contents"
  57. #define APPNMAIL_DEQUEUE_LOCK ".appnmail_dequeue.lock"
  58.  
  59. #define PGP_SIGNED "-----BEGIN PGP SIGNED MESSAGE-----"
  60. #define PGP_ENCRYPTED "-----BEGIN PGP MESSAGE-----"
  61.  
  62. char *header=0,*content=0;
  63. int headerlen=0,contentlen=0;
  64. int headermaxlen=0,contentmaxlen=0;
  65. char line[LINELEN];
  66.  
  67. int verboseflg=0;
  68.  
  69. struct regex *mailutilre=0;
  70. const char *decodeCmd=0;
  71. const char*uncompressCmd=0;
  72. const char *tarCmd=0;
  73.  
  74.  
  75. struct message_index *readmail(void)
  76. {
  77.    struct message_index *mi;
  78.    int i,pri=0;
  79.    char from[LINELEN]="",subject[LINELEN]="",reference[LINELEN]="";
  80.    struct regex *fre=re_compile("^From:  *\\(.*\\)\n",0);
  81.    struct regex *sre=re_compile("^Subject:  *\\(.*\\)\n",0);
  82.    // Y2000 fix applied here.
  83.    struct regex *dre=re_compile("^Date:.*\\([ 0123][0-9]\\) \\([A-Z][a-z][a-z]\\)[a-z]* \\([12]*[0-9]*[0-9][0-9]\\) ",0);
  84.    struct regex *rre=re_compile("^Next-Attachment: \\.tar\\.\\([0-9]*\\)\\.\\(.*\\)\\.attach, \\(E*,* *\\)\\([0-9]*\\), \\([0-9]*/[0-9]*\\), \\([0-9]*\\), \\([0-9]*\\)",0);
  85.    struct regex *mre=re_compile("^Content-Type: \\([-_a-zA-Z0-9]*/[-_a-zA-Z0-9]*\\)",0);
  86.    struct regex *pre=0;
  87.    const char *pridef=0;
  88.  
  89.    if ((pridef=NXGetDefaultValue(MAIL_APP,"PriorityHeader")) != NULL)
  90.    {
  91.       static char buf[MAXPATHLEN];
  92.       sprintf(buf,"^%s: [ \t]*\\([^ \t]*\\)[ \t]*\n",pridef);
  93.       pre=re_compile(buf,0);
  94.       pridef=NXGetDefaultValue(MAIL_APP,"PriorityValues");
  95.    }
  96.    
  97.    mi = malloc(sizeof(*mi));
  98.    
  99.    mi->record_length = sizeof(*mi);
  100.    mi->mes_offset = -1;
  101.    mi->status = MT_STATUS_NEW;
  102.    mi->msgtype = MT_UNSET;
  103.    mi->encrypted = MT_UNSET;
  104.    mi->sync = MT_UNSET;
  105.    mi->mes_date = message_current_date();
  106.  
  107.    headerlen=0;
  108.    
  109.    while(fgets(line,LINELEN,stdin))
  110.    {
  111.       if (*line=='\n') break;
  112.  
  113.       /* XXX Should handle line-continuations. */
  114.       if (re_match(line,fre)==1)
  115.       {
  116.      strpcpy(from,fre->braslist[0],fre->braelist[0]);
  117.      iso_convert(from);
  118.       }
  119.       if (re_match(line,sre)==1)
  120.       {
  121.      strpcpy(subject,sre->braslist[0],sre->braelist[0]);
  122.      iso_convert(subject);
  123.       }
  124.       if (re_match(line,dre)==1)
  125.       {
  126.      // Y2000 fix applied here.
  127.      unsigned year = atoi(dre->braslist[2]);
  128.      if (year < 70) year += 2000;
  129.      else if (year < 200) year += 1900;
  130.      // else assume year includes century...
  131.      mi->mes_date=message_date(year,dre->braslist[1],atoi(dre->braslist[0]));
  132.       }
  133.       if (pre && pridef && (re_match(line,pre)==1))
  134.       {
  135.      const char *beg,*end;
  136.  
  137.      for(beg=pridef,pri=1;*beg;(beg=*end ? end+1 : end),(pri++))
  138.      {
  139.         if (!(end=index(beg,' '))) end=beg+strlen(beg);
  140.         if (((pre->braelist[0]-pre->braslist[0])==(end-beg))
  141.         &&(strncmp(pre->braslist[0],beg,end-beg)==0))
  142.            break;
  143.      }
  144.      if (!*beg) pri=0;
  145.       }
  146.       if (re_match(line, mre) == 1)
  147.       {
  148.      if ((mi->msgtype == MT_UNSET) &&
  149.          !strpcaseequ("text/plain",mre->braslist[0],mre->braelist[0]))
  150.         mi->msgtype = MT_TYPE_MIME;
  151.      // Experimental RFC2015 encrypt/signature detection.
  152.      if (strpcaseequ("multipart/encrypted",mre->braslist[0],mre->braelist[0]))
  153.         mi->encrypted = MT_ENCRYPTED;
  154.      else if (strpcaseequ("multipart/signed",mre->braslist[0],mre->braelist[0]))
  155.         mi->encrypted = 's'; // Naughty, naughty...
  156.       }
  157.       if (re_match(line,rre)==1)
  158.       {
  159.      char *c;
  160.      mi->msgtype = MT_TYPE_NEXT;
  161.      strpcpy(reference,rre->braslist[1],rre->braelist[1]);
  162.      c=reference+strlen(reference);
  163.      *c++='_';
  164.      while(c-reference<22) *c++='_';
  165.      *c='\0';
  166.      sprintf(c,"%lu",(unsigned long)time(0));
  167.      strcat(reference,".attach");
  168.      c=reference+strlen(reference);
  169.      strcat(reference,", ");
  170.      if (*(rre->braslist[2])=='E')
  171.      {
  172.         strcat(reference,"E, ");
  173.         mi->encrypted = MT_ENCRYPTED;
  174.      }
  175.      strpcat(reference,rre->braslist[4],rre->braelist[4]);
  176.      strcat(reference,"\n");
  177.      appstring(&header,&headerlen,&headermaxlen,"Next-Reference: ",
  178.            strlen("Next-Reference: "));
  179.      appstring(&header,&headerlen,&headermaxlen,reference,strlen(reference));
  180.      *c='\0';
  181.       }
  182.       else
  183.       {
  184.      appstring(&header,&headerlen,&headermaxlen,line,strlen(line));
  185.       }
  186.    }
  187.    appstring(&header,&headerlen,&headermaxlen,"\n",1);
  188.  
  189.    contentlen=0;
  190.    
  191.    while((i=fread(growstring(&content,&contentlen,&contentmaxlen,LINELEN*16),
  192.           1,16*LINELEN,stdin))>0)
  193.       contentlen+=i;
  194.    if (content[contentlen-1]!='\n')
  195.       appstring(&content,&contentlen,&contentmaxlen,"\n",1);
  196.  
  197.    mi->mes_length=headerlen;
  198.    if (mi->msgtype != MT_TYPE_NEXT) mi->mes_length+=contentlen;
  199.    mi->record_length+=strlen(from)+1+strlen(subject)+1+strlen(reference)+1;
  200.    mi->record_length+=sizeof(int)+sizeof(time_t)+sizeof(int);
  201.    mi=realloc(mi,mi->record_length);
  202.    strcpy(mi->data,from);
  203.    strcpy(mi->data+strlen(from)+1,subject); 
  204.    strcpy(mi->data+strlen(from)+1+strlen(subject)+1,reference);
  205.  
  206.    message_set_attachsize(mi,0);
  207.    message_set_attachtime(mi,0);
  208.    message_set_priority(mi,pri);
  209.  
  210.    // Experimental PGP encrypt/signature detection.
  211.    if (mi->encrypted == MT_UNSET)
  212.    {
  213.       if (contentlen >= sizeof(PGP_ENCRYPTED)-1 &&
  214.       strncmp(content, PGP_ENCRYPTED, sizeof(PGP_ENCRYPTED)-1)==0)
  215.      mi->encrypted = MT_ENCRYPTED;
  216.       else if (contentlen >= sizeof(PGP_SIGNED)-1 &&
  217.            strncmp(content, PGP_SIGNED, sizeof(PGP_SIGNED)-1)==0)
  218.      mi->encrypted = 's'; // XXX Naughty, naughty...
  219.    }
  220.  
  221.    free(fre); free(sre); free(rre); free(dre);
  222.    if (pre) free(pre);
  223.    return mi;
  224. }
  225.  
  226. const char *get_command(const char *defaultKey, ...)
  227. {
  228.    /* Find command named `defaultKey' in Mail's defaults database, and check
  229.       if it is an executable.  If not, try each of the defaults given on
  230.       the (NULL-terminated) argument list in turn. */
  231.    const char *command = NXGetDefaultValue(MAIL_APP, defaultKey);
  232.    va_list ap;
  233.  
  234.    va_start(ap, defaultKey);
  235.    do
  236.    {
  237.       struct stat st;
  238.       if (command == NULL) continue;
  239.       if (command[0] != '/') break; /* XXX should check PATH? */
  240.       if (access(command, X_OK) < 0) continue;
  241.       if (stat(command, &st) < 0) continue;
  242.       if ((st.st_mode & S_IFMT) == S_IFREG) break;
  243.    }
  244.    while ((command = va_arg(ap, const char *)) != NULL);
  245.    va_end(ap);
  246.    if (command == NULL)
  247.    {
  248.       fprintf(stderr,"%s: cannot find executable for `%s'.\n", progname(), defaultKey);
  249.    }
  250.    return command;
  251. }
  252.  
  253. #define INCOMING_MBOX    "Incoming_Mail"
  254. #define INCOMING_TOC    "Incoming_Table_of_Contents"
  255.  
  256. int incorporate_mail(MailProxy *mailProxy, const char *mboxname, const char *mbox, const char *table_of_contents)
  257. {
  258.    int interval=1,lastinterval=0;
  259.  
  260.    /* Signal Mail.app to incorporate mail.  Assumes Mail the mailbox open,
  261.       and approporiate locks are in place. */
  262.    if (mailProxy == nil) return -1;
  263.    if (link(mbox, INCOMING_MBOX)<0)
  264.    {
  265.       fprintf(stderr,"%s: %s: cannot link %s to %s (%s)\n",progname(),mboxname,mbox,INCOMING_MBOX,strerror(errno));
  266.       return -1;
  267.    }
  268.    if (link(table_of_contents, INCOMING_TOC)<0)
  269.    {
  270.       fprintf(stderr,"%s: %s: cannot link %s to %s (%s)\n",progname(),mboxname,mbox,INCOMING_MBOX,strerror(errno));
  271.       unlink(INCOMING_MBOX);
  272.       return -1;
  273.    }
  274.    if (![mailProxy incorporateNewMail])
  275.    {
  276.       fprintf(stderr,"%s: %s: -incorporateNewMail failed\n",progname(),mboxname);
  277.       unlink(INCOMING_MBOX);
  278.       unlink(INCOMING_TOC);
  279.       return 1;
  280.    }
  281.  
  282.    /* Unfortunately this seems to be asynchronous, ie. Mail.app does not
  283.       immediately process (and remove) the Incoming mbox when it receives
  284.       a remote kick in the butt.  So we have to wait a little while... */
  285.    while (access(INCOMING_MBOX, F_OK)==0 || access(INCOMING_TOC, F_OK)==0)
  286.    {
  287.       /* Nice Fibonacci series. */
  288.       int newinterval=interval+lastinterval;
  289.  
  290.       lastinterval=interval;
  291.       interval=newinterval;
  292.       if (interval > 34)
  293.       {
  294.      /* so we waited for 1+1+2+3+5+8+13+21+34 = 88 seconds.  Is this enough? */
  295.      fprintf(stderr,"%s: %s: Mail.app failed to incorporate Incoming mail???\n",progname(),mboxname);
  296.      unlink(INCOMING_MBOX);
  297.      unlink(INCOMING_TOC);
  298.      return 1;
  299.       }
  300.       sleep(interval);
  301.    }
  302.    unlink(mbox);
  303.    unlink(table_of_contents);
  304.    return 0;
  305. }
  306.  
  307. /* Transfer messages from one {mbox, table_of_contents} combo to another. */
  308. int transfer_mbox_contents(const char *mboxname, const char *srcmbox, const char *srctoc, const char *dstmbox, const char *dsttoc)
  309. {
  310.    int result=-1;
  311.    int srcboxfd=-1, dstboxfd=-1;
  312.    FILE *srctocf=NULL, *dsttocf=NULL;
  313.    struct table_of_contents_header *srctoch=NULL, *dsttoch=NULL;
  314.    struct message_index *mi;
  315.    time_t mboxtime;
  316.    int n;
  317.  
  318.    mboxtime = mtime(srcmbox);
  319.    if ((srcboxfd = open(srcmbox, O_RDONLY)) < 0)
  320.    {
  321.       fprintf(stderr,"%s: %s: opening %s (%s)\n", progname(), mboxname, srcmbox, strerror(errno));
  322.       goto _close;
  323.    }
  324.    if ((srctocf = fopen(srctoc, "r+b")) == NULL)
  325.    {
  326.       fprintf(stderr,"%s: %s: opening %s (%s)\n", progname(), mboxname, srctoc, strerror(errno));
  327.       goto _close;
  328.    }
  329.    if ((srctoch = get_table_of_contents_header(srctocf, 0)) == NULL)
  330.    {
  331.       fprintf(stderr,"%s: %s: [%s] invalid header\n", progname(), mboxname, srctoc);
  332.       goto _close;
  333.    }
  334.    if (srctoch->mbox_time != mboxtime)
  335.    {
  336.       fprintf(stderr,"%s: %s: [%s] out of sync\n", progname(), mboxname, srctoc);
  337.       goto _close;
  338.    }
  339.  
  340.    mboxtime = mtime(dstmbox);
  341.    if ((dstboxfd = open(dstmbox,O_WRONLY)) < 0)
  342.    {
  343.       fprintf(stderr,"%s: %s: opening %s (%s)\n", progname(), mboxname, dstmbox, strerror(errno));
  344.       goto _close;
  345.    }
  346.    if ((dsttocf = fopen(dsttoc,"r+b")) == NULL)
  347.    {
  348.       fprintf(stderr,"%s: %s: opening %s (%s)\n", progname(), mboxname, dsttoc, strerror(errno));
  349.       goto _close;
  350.    }
  351.    if ((dsttoch = get_table_of_contents_header(dsttocf,1)) == NULL)
  352.    {
  353.       fprintf(stderr,"%s: %s: [%s] invalid header\n", progname(), mboxname, dsttoc);
  354.       goto _close;
  355.    }
  356.    if (dsttoch->mbox_time != mboxtime)
  357.    {
  358.       fprintf(stderr,"%s: %s: [%s] out of sync\n", progname(), mboxname, dsttoc);
  359.       goto _close;
  360.    }
  361.  
  362.    for (n = 0;  n < srctoch->num_msgs;  free(mi), n++)
  363.    {
  364.       long src_offset, src_length;
  365.       void *buf=NULL;
  366.  
  367.       errno = 0;
  368.       if ((mi = get_message_index(srctocf)) == NULL)
  369.       {
  370.          fprintf(stderr,"%s: %s: [%s] inconsistency (%s)\n", progname(), mboxname, srctoc, (errno ? strerror(errno) : "invalid"));
  371.          goto _close;
  372.       }
  373.       if (mi->status == 'D') continue; // Deleted in a previous pass.
  374.  
  375.       src_offset = mi->mes_offset;
  376.       src_length = mi->mes_length;
  377.       if (lseek(srcboxfd, src_offset, SEEK_SET) != src_offset ||
  378.       (buf = malloc(src_length)) == NULL ||
  379.       (src_length = read(srcboxfd, buf, src_length)) != mi->mes_length)
  380.       {
  381.      fprintf(stderr,"%s: %s: [%s] inconsistency (%s)\n", progname(), mboxname, srcmbox, (errno ? strerror(errno) : "invalid"));
  382.      free(buf);
  383.      free(mi);
  384.          goto _close;
  385.       }
  386.       mi->mes_offset = lseek(dstboxfd, 0, SEEK_END);
  387.       if ((mi->mes_length = write(dstboxfd, buf, src_length)) != src_length)
  388.       {
  389.          fprintf(stderr,"%s: %s: [%s] inconsistency (%s)\n", progname(), mboxname, dstmbox, (errno ? strerror(errno) : "invalid"));
  390.       }
  391.       free(buf);
  392.  
  393.       /* Append message index to destination table of contents. */
  394.       fseek(dsttocf, 0, SEEK_END);
  395.       put_message_index(dsttocf, mi);
  396.  
  397.       /* Update header. */
  398.       dsttoch->num_msgs++;
  399.       dsttoch->mbox_time = mtime(dstmbox);
  400.       fseek(dsttocf, 0, SEEK_SET);
  401.       put_table_of_contents_header(dsttocf, dsttoch);
  402.  
  403.       /* Mark message as deleted (and restore original offset/length) in source mbox. */
  404.       mi->status = 'D';
  405.       mi->mes_offset = src_offset;
  406.       mi->mes_length = src_length;
  407.       fseek(srctocf, -mi->record_length, SEEK_CUR);
  408.       put_message_index(srctocf, mi);
  409.       fseek(srctocf, 0, SEEK_CUR);
  410.    }
  411.  
  412.    /* Update header once again, just to be sure... */
  413.    close(dstboxfd), dstboxfd = -1;
  414.    dsttoch->mbox_time = mtime(dstmbox);
  415.    fseek(dsttocf, 0, SEEK_SET);
  416.    put_table_of_contents_header(dsttocf, dsttoch);
  417.  
  418.    result = 0;
  419.  _close:
  420.    free(dsttoch);
  421.    if (dstboxfd >= 0) close(dstboxfd);
  422.    if (dsttocf != NULL) fclose(dsttocf);
  423.    free(srctoch);
  424.    if (srcboxfd >= 0) close(srcboxfd);
  425.    if (srctocf != NULL) fclose(srctocf);
  426.  
  427.    return result;
  428. }
  429.  
  430. /* Dequeue mail by copying it from appnmail's private mbox to the real mbox.
  431.    assumes proper locking has been done. */
  432. void dequeue_mail(const char *mboxname)
  433. {
  434.    if (access(APPNMAIL_MBOX, F_OK) != 0) return;
  435.    if (transfer_mbox_contents(mboxname, APPNMAIL_MBOX, APPNMAIL_TOC, "mbox", "table_of_contents") != 0) return;
  436.  
  437.    /* If successful, remove our private inbox. */
  438.    unlink(APPNMAIL_MBOX);
  439.    unlink(APPNMAIL_TOC);
  440. }
  441.  
  442. /* Extract NeXTmail attachment from message into `path'. */
  443. int extract_attachment(const char *mboxname, const char *path)
  444. {
  445.    FILE *f;
  446.    int st;
  447.  
  448.    if (verboseflg>1) fprintf(stderr,"%s[%d] Extracting NeXTmail attachment...\n",progname(),getpid());
  449.  
  450.    /* Locate executables, if not already done so. */
  451.    if (!decodeCmd && !(decodeCmd=get_command("appnmailDecodeCommand","/NextApps/Mail.app/decode",NULL)))
  452.    {
  453.       /* ...not really a regular Mail default. */
  454.       return EXIT_FAILURE;
  455.    }
  456.    if (!uncompressCmd && !(uncompressCmd=get_command("UncompressCommand","/usr/bin/gunzip","/usr/ucb/uncompress",NULL)))
  457.    {
  458.       return EXIT_FAILURE;
  459.    }
  460.    if (!tarCmd && !(tarCmd=get_command("TarCommand","/NextApps/Mail.app/safetar","/usr/bin/gnutar",NULL)))
  461.    {
  462.       return EXIT_FAILURE;
  463.    }
  464.    if ((mkdir(path,0755))==-1)
  465.    {
  466.       fprintf(stderr,"%s: %s: %s: %s\n",progname(),mboxname,path,strerror(errno));
  467.       return EXIT_FAILURE;
  468.    }
  469.    sprintf(line,"cd %s && %s | %s | %s xf -",path,decodeCmd,uncompressCmd,tarCmd);
  470.    if (!(f=popen(line,"w")))
  471.    {
  472.       fprintf(stderr,"%s: %s: %s: %s\n",progname(),mboxname,path,strerror(errno));
  473.       return EXIT_FAILURE;
  474.    }
  475.    fwrite(content,contentlen,1,f);
  476.    if ((st=pclose(f))!=0)
  477.    {
  478.       fprintf(stderr,"%s: %s: %s failed (status %d)\n",progname(),mboxname,line,st);
  479.       return EXIT_FAILURE;
  480.    }
  481.    return EXIT_SUCCESS;
  482. }
  483.  
  484. /* Save message in the mailbox, or, if it is locked, in a (temporary) private
  485.    mailbox inside the .mbox folder. */
  486. int save_message(const char *mboxname, struct message_index *mi)
  487. {
  488.    time_t mboxtime;
  489.    int mfd=-1,tocfd=-1;
  490.    FILE *tocf=NULL;
  491.    struct table_of_contents_header *toch=0;
  492.    char host[MAXHOSTNAMELEN];
  493.    char *mbox="mbox", *table_of_contents="table_of_contents";
  494.    const char *attachment;
  495.    int ownslock=1,ownsappnmaillock=0;
  496.    int st;
  497.    int result=EXIT_SUCCESS;
  498.  
  499.    if (verboseflg) fprintf(stderr,"%s[%d] Entering %s to deliver...\n",progname(),getpid(),mboxname);
  500.  
  501.    if (cd_mbox(mboxname,1) != 0) return EXIT_FAILURE;
  502.  
  503.    if ((st = try_lock_mbox(host, line, NULL)) != 0)
  504.    {
  505.       if (st < 0) return EXIT_FAILURE;
  506.  
  507.       /* If the mbox is locked (by Mail.app), we'll write the message to
  508.      a temporary mbox, and try to incorporate it into the mbox later.
  509.      (so the message is at least stored to disk in case anything
  510.       goes wrong).
  511.      XXX The way to determine whether the lock is owned by Mail.app
  512.      or one of our own mailapp-utilities is completely bogus
  513.      and depends on what is written in the user field of the lock
  514.      by try_lock_mbox(). */
  515.       if (!mailutilre) mailutilre=re_compile(LOCK_USER_RE,0);
  516.       if (*line && !re_match(line,mailutilre))
  517.       {
  518.      if (verboseflg>1) fprintf(stderr,"%s[%d] Delivering into temporary mbox...\n",progname(),getpid());
  519.      ownslock=0;
  520.      mbox=APPNMAIL_MBOX;
  521.      table_of_contents=APPNMAIL_TOC;
  522.      /* Operations on our temp. mbox are protected with our own lock. */
  523.      /* XXX What about stale locks? */
  524.      if (lock_mbox_file(APPNMAIL_LOCK,0)!=0)
  525.      {
  526.         result=EXIT_FAILURE;
  527.         goto unlock;
  528.      }
  529.      ownsappnmaillock=1;
  530.       }
  531.       else if (lock_mbox(0)!=0)
  532.       {
  533.      result=EXIT_FAILURE;
  534.      goto cleanup;
  535.       }
  536.    }
  537.    if (ownslock)
  538.    {
  539.       /* try to dequeue mail first, but don't insist on it if already
  540.      locked by another appnmail. */
  541.       if (lock_mbox_file(APPNMAIL_LOCK,1)==0)
  542.       {
  543.      dequeue_mail(mboxname);
  544.      unlock_mbox_file(APPNMAIL_LOCK);
  545.       }
  546.    }
  547.  
  548.    mboxtime=mtime(mbox);
  549.    
  550.    if ((mfd=open(mbox,O_WRONLY|O_APPEND|O_CREAT,0644))==-1)
  551.    {
  552.       fprintf(stderr,"%s: %s: %s\n",progname(),mboxname,strerror(errno));
  553.       result=EXIT_FAILURE;
  554.       goto unlock;
  555.    }
  556.  
  557.    /* Even if we cannot update the table of contents,
  558.       we'll try our damnedest to deliver the message anyway. */
  559.  
  560.    if ((tocfd=open(table_of_contents,O_CREAT|O_RDWR,0644))==-1 ||
  561.        (tocf=fdopen(tocfd,"r+b"))==NULL)
  562.    {
  563.       fprintf(stderr,"%s: %s: %s\n",progname(),mboxname,strerror(errno));
  564.       result=EXIT_FAILURE;
  565.    }
  566.    else
  567.    {
  568.       toch=get_table_of_contents_header(tocf,1);
  569.       if (!toch)
  570.       {
  571.      fprintf(stderr,"%s: %s: warning: invalid table_of_contents\n",progname(),mboxname);
  572.      result=EXIT_FAILURE;
  573.       }
  574.       else if (toch->mbox_time!=mboxtime)
  575.       {
  576.      fprintf(stderr,"%s: %s: warning: table_of_contents out of sync\n",progname(),mboxname);
  577.      result=EXIT_FAILURE;
  578.       }
  579.    }
  580.  
  581.    /* FOR EACH */
  582.    lseek(mfd,0,SEEK_END);
  583.    mi->mes_offset=lseek(mfd,0,SEEK_CUR);
  584.  
  585.    if ((attachment = message_reference(mi)) && *attachment)
  586.    {
  587.       char path[MAXPATHLEN+1];
  588.  
  589.       if (extract_attachment(mboxname, attachment) != EXIT_SUCCESS)
  590.       {
  591.      result=EXIT_FAILURE;
  592.      goto unlock;
  593.       }
  594.       message_set_attachsize(mi, dirsize(strcpy(path, attachment)));
  595.       message_set_attachtime(mi, mtime(attachment));
  596.       /* Add message body *after* all failure points, to avoid TOC corruption. */
  597.       if (write(mfd,header,headerlen) != headerlen ||
  598.       write(mfd,"\n",1) != 1)
  599.       {
  600.      fprintf(stderr, "%s: %s: truncated message\n",progname(),mboxname);
  601.      result=EXIT_FAILURE;
  602.      goto unlock;
  603.       }
  604.    }
  605.    else
  606.    {
  607.       if (write(mfd,header,headerlen) != headerlen ||
  608.       write(mfd,content,contentlen) != contentlen)
  609.       {
  610.      fprintf(stderr, "%s: %s: truncated message\n",progname(),mboxname);
  611.      result=EXIT_FAILURE;
  612.      goto unlock;
  613.       }
  614.    }
  615.    if (toch)
  616.    {
  617.       toch->num_msgs++;
  618.       fseek(tocf,0,SEEK_END);
  619.       if (put_message_index(tocf,mi) != 0)
  620.       {
  621.      fprintf(stderr, "%s: %s: warning: truncated message index\n",progname(),mboxname);
  622.      result=EXIT_FAILURE;
  623.      goto unlock;
  624.       }
  625.    }
  626.    /* END FOR EACH */
  627.    
  628.    close(mfd), mfd=-1;
  629.    if (toch)
  630.    {
  631.       if (toch->mbox_time==mboxtime) toch->mbox_time=mtime(mbox);
  632.       fseek(tocf,0,SEEK_SET);
  633.       put_table_of_contents_header(tocf,toch);
  634.    }
  635.  unlock:
  636.    if (tocf!=NULL) fclose(tocf), tocf=NULL;
  637.    else if (tocfd>=0) close(tocfd), tocfd=-1;
  638.    if (mfd>=0) close(mfd), mfd=-1;
  639.    if (ownslock)
  640.    {
  641.       unlock_mbox();
  642.    }
  643.    else if (ownsappnmaillock)
  644.    {
  645.       unlock_mbox_file(APPNMAIL_LOCK);
  646.    }
  647.  cleanup:
  648.    if (result==EXIT_SUCCESS && verboseflg) fprintf(stderr,"%s[%d] ...deliver %s done\n",progname(),getpid(),mboxname);
  649.    if (toch) free(toch);
  650.    uncd_mbox();
  651.    return result;
  652. }
  653.  
  654. /* Incorporate mail from appnmail's private mbox into the real mbox.
  655.    Handles synchronization with other instances of appnmail, proper locking,
  656.    etc. */
  657. void incorporate(const char *mboxname, int force)
  658. {
  659.    int st;
  660.    char host[MAXHOSTNAMELEN];
  661.  
  662.    if (cd_mbox(mboxname, 0) != 0) return;
  663.    if (access(APPNMAIL_MBOX, F_OK) == 0)
  664.    {
  665.       /* Make sure that only a single appnmail is waiting to dequeue. */
  666.       if (lock_mbox_file(APPNMAIL_DEQUEUE_LOCK, 1) == 0)
  667.       {
  668.      int unlocked = 0;
  669.  
  670.      if (verboseflg) fprintf(stderr,"%s[%d] Entering %s to dequeue...\n",progname(),getpid(),mboxname);
  671.      if ((st = try_lock_mbox(host, line, NULL)) > 0)
  672.      {
  673.         if (!mailutilre) mailutilre = re_compile(LOCK_USER_RE,0);
  674.         if (*line && !re_match(line, mailutilre))
  675.         {
  676.            /* XXX This does not work in Rhapsody, or if compiled in OPENSTEP */
  677.            /* Not locked by appnmail; try to contact Mail.app */
  678.            int incorporateDelay = 0;
  679.            const char *delaydef = NXGetDefaultValue(MAIL_APP, "appnmailIncorporateDelay");
  680.  
  681.            if (delaydef && isdigit(*delaydef)) incorporateDelay = atoi(delaydef);
  682.  
  683.            /* Wait some time (&& recheck lock) */
  684.            if (incorporateDelay != 0)
  685.            {
  686.           if (verboseflg>2) fprintf(stderr,"%s[%d] Sleeping for appnmailIncorporateDelay = %d seconds...\n",progname(),getpid(),incorporateDelay);
  687.           sleep(incorporateDelay);
  688.           st = try_lock_mbox(host, line, NULL);
  689.            }
  690.  
  691.            if (st > 0 && *line && !re_match(line, mailutilre) &&
  692.            [MailProxy canIncorporateNewMail])
  693.            {
  694.           if (verboseflg>2) fprintf(stderr,"%s[%d] Locking %s...\n",progname(),getpid(),APPNMAIL_LOCK);
  695.           if ((st = lock_mbox_file(APPNMAIL_LOCK, 0)) == 0)
  696.           {
  697.              /* XXX Should re-check .lock host? */
  698.              st = -1;
  699.              if (access(APPNMAIL_MBOX, F_OK) == 0)
  700.              {
  701.             MailProxy *mailProxy;
  702.  
  703.             if (verboseflg>2) fprintf(stderr,"%s[%d] Locating Mail port on host %s...\n",progname(),getpid(), host);
  704.             mailProxy = [[MailProxy alloc] initWithMailer:NULL host:host forceLaunch:NO];
  705.             if ([mailProxy isConnected])
  706.             {
  707.                /* Unlock dequeue_lock now, to avoid race conditions.
  708.                   XXX should analyze this more thoroughly... */
  709.                unlock_mbox_file(APPNMAIL_DEQUEUE_LOCK);
  710.                unlocked++;
  711.                if (verboseflg>1) fprintf(stderr,"%s[%d] Incorporating mail in open mailbox...\n",progname(),getpid());
  712.                if (incorporate_mail(mailProxy, mboxname, APPNMAIL_MBOX, APPNMAIL_TOC) != 0)
  713.                {
  714.                   /* Retry using conventional mbox lock, but only
  715.                      if no other appnmails are dequeueing. */
  716.                   if (lock_mbox_file(APPNMAIL_DEQUEUE_LOCK, 1) == 0)
  717.                   {
  718.                  unlocked = 0;
  719.                  st = 1;
  720.                   }
  721.                   else
  722.                   {
  723.                  st = -1;
  724.                   }
  725.                }
  726.                [mailProxy release];
  727.             }
  728.              }
  729.              unlock_mbox_file(APPNMAIL_LOCK);
  730.           }
  731.            }
  732.         }
  733.         if (st > 0) st = lock_mbox(0);
  734.      }
  735.  
  736.      if (st == 0)
  737.      {
  738.         if (verboseflg>2) fprintf(stderr,"%s[%d] Locking %s...\n",progname(),getpid(),APPNMAIL_LOCK);
  739.         if ((st = lock_mbox_file(APPNMAIL_LOCK, 0)) == 0)
  740.         {
  741.            /* Unlock dequeue_lock now, to avoid race conditions. */
  742.            unlock_mbox_file(APPNMAIL_DEQUEUE_LOCK);
  743.            unlocked++;
  744.  
  745.            if (access(APPNMAIL_MBOX, F_OK) == 0)
  746.            {
  747.           if (verboseflg>1) fprintf(stderr,"%s[%d] Appending mail to mailbox...\n",progname(),getpid());
  748.           dequeue_mail(mboxname);
  749.            }
  750.            unlock_mbox_file(APPNMAIL_LOCK);
  751.         }
  752.         unlock_mbox();
  753.      }
  754.      if (!unlocked)
  755.      {
  756.         unlock_mbox_file(APPNMAIL_DEQUEUE_LOCK);
  757.      }
  758.      if (verboseflg) fprintf(stderr,"%s[%d] ...dequeue %s done\n",progname(),getpid(),mboxname);
  759.       }
  760.    }
  761.    uncd_mbox();
  762. }
  763.  
  764.  
  765. int main(int ac,char *av[])
  766. {
  767.    struct message_index *mi;
  768.    int c,ai;
  769.    int readflg=0,nowaitflg=0,multiflg=0,flagflg=0,deleteflg=0,incorporateflg=0;
  770.    int pri=-1;
  771.    int status=EXIT_SUCCESS;
  772.    POOL_INIT
  773.  
  774.    set_progname(av[0]);
  775.  
  776.    while((c=getopt(ac,av,"rfdvnp:iVH"))!=EOF) switch(c)
  777.    {
  778.     case 'r':
  779.       readflg++;
  780.       if (flagflg||deleteflg) status=EXIT_USAGE;
  781.       break;
  782.     case 'f':
  783.       flagflg++;
  784.       if (readflg||deleteflg) status=EXIT_USAGE;
  785.       break;
  786.     case 'd':
  787.       deleteflg++;
  788.       if (readflg||flagflg) status=EXIT_USAGE;
  789.       break;
  790.     case 'n':
  791.       nowaitflg++;
  792.       break;
  793.     case 'v':
  794.       verboseflg++;
  795.       break;
  796.     case 'm': // XXX currently unused.
  797.       multiflg++;
  798.       break;
  799.     case 'p':
  800.       if (pri!=-1) status=EXIT_USAGE;
  801.       pri=atoi(optarg);
  802.       if (pri<0) status=EXIT_USAGE;
  803.       break;
  804.    case 'i':
  805.       incorporateflg++;
  806.       break;
  807.     case 'V':
  808.       status=EXIT_VERSION;
  809.       break;
  810.     case 'H':
  811.       status=EXIT_HELP;
  812.       break;
  813.     case '?':
  814.     default:
  815.       status=EXIT_USAGE;
  816.    }
  817.    if (status==EXIT_SUCCESS && optind>=ac) status=EXIT_USAGE;
  818.    handle_usage_help_version(status, USAGE, HELP);
  819.  
  820.    /* This is to avoid problems with effective vs. real user ids. */
  821.    setuid(geteuid());
  822.  
  823.    /* Mail message is optional if -i flag is given, so avoid reading stdin
  824.       in interactive use, and allow empty message if redirected. */
  825.    if (!incorporateflg || !isatty(fileno(stdin)))
  826.    {
  827.       mi=readmail();
  828.  
  829.       if (!(incorporateflg && headerlen == 0 && contentlen == 0))
  830.       {
  831.      if (!header || (headerlen<5) || strncmp(header,"From ",5))
  832.      {
  833.         fprintf(stderr,"%s: message is not in UNIX mailbox format\n", progname());
  834.         exit(EXIT_FAILURE);
  835.      }
  836.  
  837.      if (readflg) mi->status = MT_STATUS_READ;
  838.      else if (flagflg) mi->status = MT_STATUS_FLAGGED;
  839.      else if (deleteflg) mi->status = MT_STATUS_DELETED;
  840.  
  841.      if (pri>=0) message_set_priority(mi,pri);
  842.      
  843.      for (ai=optind; ai<ac; ai++)
  844.      {
  845.         c = save_message(av[ai], mi);
  846.         if (c != EXIT_SUCCESS) status = c;
  847.      }
  848.       }
  849.    }
  850.  
  851.    if (incorporateflg || (status == EXIT_SUCCESS && !nowaitflg))
  852.    {
  853.       /* Dequeue pass. */
  854.       for (ai = optind;  ai < ac;  ai++)
  855.       {
  856.      incorporate(av[ai], incorporateflg);
  857.       }
  858.    }
  859.    POOL_RELEASE
  860.    return status;
  861. }
  862.