home *** CD-ROM | disk | FTP | other *** search
/ vsiftp.vmssoftware.com / VSIPUBLIC@vsiftp.vmssoftware.com.tar / FREEWARE / FREEWARE40.ZIP / pine / c-client / imap2.c < prev    next >
C/C++ Source or Header  |  1994-02-03  |  66KB  |  2,143 lines

  1. /*
  2.  * Program:    Interactive Mail Access Protocol 2 (IMAP2) routines
  3.  *
  4.  * Author:    Mark Crispin
  5.  *        Networks and Distributed Computing
  6.  *        Computing & Communications
  7.  *        University of Washington
  8.  *        Administration Building, AG-44
  9.  *        Seattle, WA  98195
  10.  *        Internet: MRC@CAC.Washington.EDU
  11.  *
  12.  * Date:    15 June 1988
  13.  * Last Edited:    5 October 1993
  14.  *
  15.  * Sponsorship:    The original version of this work was developed in the
  16.  *        Symbolic Systems Resources Group of the Knowledge Systems
  17.  *        Laboratory at Stanford University in 1987-88, and was funded
  18.  *        by the Biomedical Research Technology Program of the National
  19.  *        Institutes of Health under grant number RR-00785.
  20.  *
  21.  * Original version Copyright 1988 by The Leland Stanford Junior University
  22.  * Copyright 1993 by the University of Washington
  23.  *
  24.  *  Permission to use, copy, modify, and distribute this software and its
  25.  * documentation for any purpose and without fee is hereby granted, provided
  26.  * that the above copyright notices appear in all copies and that both the
  27.  * above copyright notices and this permission notice appear in supporting
  28.  * documentation, and that the name of the University of Washington or The
  29.  * Leland Stanford Junior University not be used in advertising or publicity
  30.  * pertaining to distribution of the software without specific, written prior
  31.  * permission.  This software is made available "as is", and
  32.  * THE UNIVERSITY OF WASHINGTON AND THE LELAND STANFORD JUNIOR UNIVERSITY
  33.  * DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD TO THIS SOFTWARE,
  34.  * INCLUDING WITHOUT LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
  35.  * FITNESS FOR A PARTICULAR PURPOSE, AND IN NO EVENT SHALL THE UNIVERSITY OF
  36.  * WASHINGTON OR THE LELAND STANFORD JUNIOR UNIVERSITY BE LIABLE FOR ANY
  37.  * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
  38.  * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
  39.  * CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF
  40.  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  41.  *
  42.  */
  43.  
  44.  
  45. #include <ctype.h>
  46. #include <stdio.h>
  47. /* The following two lines were trasnposed for VMS */
  48. #include "osdep.h"
  49. #include "mail.h"
  50. #include "imap2.h"
  51. #include "misc.h"
  52.  
  53. /* Driver dispatch used by MAIL */
  54.  
  55. DRIVER imapdriver = {
  56.   "imap2",            /* driver name */
  57.   (DRIVER *) NIL,        /* next driver */
  58.   map_valid,            /* mailbox is valid for us */
  59.   map_parameters,        /* manipulate parameters */
  60.   map_find,            /* find mailboxes */
  61.   map_find_bboards,        /* find bboards */
  62.   map_find_all,            /* find all mailboxes */
  63.   map_find_all_bboards,        /* find all bboards */
  64.   map_subscribe,        /* subscribe to mailbox */
  65.   map_unsubscribe,        /* unsubscribe from mailbox */
  66.   map_subscribe_bboard,        /* subscribe to bboard */
  67.   map_unsubscribe_bboard,    /* unsubscribe from bboard */
  68.   map_create,            /* create mailbox */
  69.   map_delete,            /* delete mailbox */
  70.   map_rename,            /* rename mailbox */
  71.   map_open,            /* open mailbox */
  72.   map_close,            /* close mailbox */
  73.   map_fetchfast,        /* fetch message "fast" attributes */
  74.   map_fetchflags,        /* fetch message flags */
  75.   map_fetchstructure,        /* fetch message envelopes */
  76.   map_fetchheader,        /* fetch message header only */
  77.   map_fetchtext,        /* fetch message body only */
  78.   map_fetchbody,        /* fetch message body section */
  79.   map_setflag,            /* set message flag */
  80.   map_clearflag,        /* clear message flag */
  81.   map_search,            /* search for message based on criteria */
  82.   map_ping,            /* ping mailbox to see if still alive */
  83.   map_check,            /* check for new messages */
  84.   map_expunge,            /* expunge deleted messages */
  85.   map_copy,            /* copy messages to another mailbox */
  86.   map_move,            /* move messages to another mailbox */
  87.   map_append,            /* append string message to mailbox */
  88.   map_gc            /* garbage collect stream */
  89. };
  90.  
  91.                 /* prototype stream */
  92. MAILSTREAM imapproto = {&imapdriver};
  93.  
  94.                 /* driver parameters */
  95. long map_maxlogintrials = MAXLOGINTRIALS;
  96. long map_lookahead = MAPLOOKAHEAD;
  97. long map_port = IMAPTCPPORT;
  98. long map_prefetch = T;
  99.  
  100. /* Mail Access Protocol validate mailbox
  101.  * Accepts: mailbox name
  102.  * Returns: our driver if name is valid, NIL otherwise
  103.  */
  104.  
  105. DRIVER *map_valid (name)
  106.     char *name;
  107. {
  108.   return mail_valid_net (name,&imapdriver,NIL,NIL);
  109. }
  110.  
  111.  
  112. /* Mail Access Protocol manipulate driver parameters
  113.  * Accepts: function code
  114.  *        function-dependent value
  115.  * Returns: function-dependent return value
  116.  */
  117.  
  118. void *map_parameters (function,value)
  119.     long function;
  120.     void *value;
  121. {
  122.   switch ((int) function) {
  123.   case SET_MAXLOGINTRIALS:
  124.     map_maxlogintrials = (long) value;
  125.   case GET_MAXLOGINTRIALS:
  126.     return (void *) map_maxlogintrials;
  127.   case SET_LOOKAHEAD:
  128.     map_lookahead = (long) value;
  129.   case GET_LOOKAHEAD:
  130.     return (void *) map_lookahead;
  131.   case SET_PORT:
  132.     map_port = (long) value;
  133.   case GET_PORT:
  134.     return (void *) map_port;
  135.   case SET_PREFETCH:
  136.     map_prefetch = (long) value;
  137.   case GET_PREFETCH:
  138.     return (void *) map_prefetch;
  139.   default:
  140.     break;
  141.   }
  142.   fatal ("Invalid map_parameters function");
  143. }
  144.  
  145. /* Mail Access Protocol find list of mailboxes
  146.  * Accepts: mail stream
  147.  *        pattern to search
  148.  */
  149.  
  150. void map_find (stream,pat)
  151.     MAILSTREAM *stream;
  152.     char *pat;
  153. {
  154.   void *s = NIL;
  155.   char *t,*bbd,*patx,tmp[MAILTMPLEN];
  156.  
  157.   if (stream) {            /* have a mailbox stream open? */
  158.                 /* begin with a host specification? */
  159.     if (((*pat == '{') || ((*pat == '*') && (pat[1] == '{'))) &&
  160.     (t = strchr (pat,'}')) && *(patx = ++t)) {
  161.       if (*pat == '*') pat++;    /* yes, skip leading * (old Pine behavior) */
  162.       strcpy (tmp,pat);        /* copy host name */
  163.       tmp[patx-pat] = '\0';    /* tie off prefix */
  164.       LOCAL->prefix = cpystr (tmp);
  165.     }
  166.     else patx = pat;        /* use entire specification */
  167.     if (LOCAL && LOCAL->use_find &&
  168.     !strcmp (imap_send (stream,"FIND MAILBOXES",patx,NIL)->key,"BAD"))
  169.       LOCAL->use_find = NIL;    /* note no finding with this server */
  170.     if (LOCAL->prefix) fs_give ((void **) &LOCAL->prefix);
  171.   }
  172.   else while (t = sm_read (&s))    /* read subscription database */
  173.     if ((*t != '*') && mail_valid_net (t,&imapdriver,NIL,NIL) && pmatch(t,pat))
  174.       mm_mailbox (t);
  175. }
  176.  
  177. /* Mail Access Protocol find list of bboards
  178.  * Accepts: mail stream
  179.  *        pattern to search
  180.  */
  181.  
  182. void map_find_bboards (stream,pat)
  183.     MAILSTREAM *stream;
  184.     char *pat;
  185. {
  186.   void *s = NIL;
  187.   char *t,*bbd,*patx,tmp[MAILTMPLEN];
  188.   if (stream) {            /* have a mailbox stream open? */
  189.                 /* begin with a host specification? */
  190.     if (((*pat == '{') || ((*pat == '*') && (pat[1] == '{'))) &&
  191.     (t = strchr (pat,'}')) && *(patx = ++t)) {
  192.       if (*pat == '*') pat++;    /* yes, skip leading * (old Pine behavior) */
  193.       strcpy (tmp,pat);        /* copy host name */
  194.       tmp[patx-pat] = '\0';    /* tie off prefix */
  195.       LOCAL->prefix = cpystr (tmp);
  196.     }
  197.     else patx = pat;        /* use entire specification */
  198.                 /* this is optional, so no complaint if fail */
  199.     if (stream && LOCAL && LOCAL->use_find && LOCAL->use_bboard &&
  200.     !strcmp (imap_send (stream,"FIND BBOARDS",patx,NIL)->key,"BAD"))
  201.       LOCAL->use_bboard = NIL;
  202.     if (LOCAL->prefix) fs_give ((void **) &LOCAL->prefix);
  203.   }
  204.   else while (t = sm_read (&s))    /* read subscription database */
  205.     if ((*t == '*') && mail_valid_net (++t,&imapdriver,NIL,NIL) &&
  206.     pmatch (t,pat)) mm_bboard (t);
  207. }
  208.  
  209. /* Mail Access Protocol find list of all mailboxes
  210.  * Accepts: mail stream
  211.  *        pattern to search
  212.  */
  213.  
  214. void map_find_all (stream,pat)
  215.     MAILSTREAM *stream;
  216.     char *pat;
  217. {
  218.   char *t,*bbd,*patx,tmp[MAILTMPLEN];
  219.   if (stream) {            /* have a mailbox stream open? */
  220.                 /* begin with a host specification? */
  221.     if (((*pat == '{') || ((*pat == '*') && (pat[1] == '{'))) &&
  222.     (t = strchr (pat,'}')) && *(patx = ++t)) {
  223.       if (*pat == '*') pat++;    /* yes, skip leading * (old Pine behavior) */
  224.       strcpy (tmp,pat);        /* copy host name */
  225.       tmp[patx-pat] = '\0';    /* tie off prefix */
  226.       LOCAL->prefix = cpystr (tmp);
  227.     }
  228.     else patx = pat;        /* use entire specification */
  229.                 /* this is optional, so no complaint if fail */
  230.     if (LOCAL && LOCAL->use_find &&
  231.     !strcmp (imap_send (stream,"FIND ALL.MAILBOXES",patx,NIL)->key,"BAD")){
  232.       map_find (stream,pat);    /* perhaps older server */
  233.                 /* always include INBOX for consistency */
  234.       if (pmatch (pat,"INBOX")) mm_mailbox ("INBOX");
  235.     }
  236.     if (LOCAL->prefix) fs_give ((void **) &LOCAL->prefix);
  237.   }
  238. }
  239.  
  240.  
  241. /* Mail Access Protocol find list of all bboards
  242.  * Accepts: mail stream
  243.  *        pattern to search
  244.  */
  245.  
  246. void map_find_all_bboards (stream,pat)
  247.     MAILSTREAM *stream;
  248.     char *pat;
  249. {
  250.   char *t,*bbd,*patx,tmp[MAILTMPLEN];
  251.   if (stream) {            /* have a mailbox stream open? */
  252.                 /* begin with a host specification? */
  253.     if (((*pat == '{') || ((*pat == '*') && (pat[1] == '{'))) &&
  254.     (t = strchr (pat,'}')) && *(patx = ++t)) {
  255.       if (*pat == '*') pat++;    /* yes, skip leading * (old Pine behavior) */
  256.       strcpy (tmp,pat);        /* copy host name */
  257.       tmp[patx-pat] = '\0';    /* tie off prefix */
  258.       LOCAL->prefix = cpystr (tmp);
  259.     }
  260.     else patx = pat;        /* use entire specification */
  261.                 /* this is optional, so no complaint if fail */
  262.     if (LOCAL && LOCAL->use_find &&
  263.     !strcmp (imap_send (stream,"FIND ALL.BBOARDS",patx,NIL)->key,"BAD"))
  264.       map_find_bboards (stream,pat);
  265.     if (LOCAL->prefix) fs_give ((void **) &LOCAL->prefix);
  266.   }
  267. }
  268.  
  269. /* Mail Access Protocol subscribe to mailbox
  270.  * Accepts: mail stream
  271.  *        mailbox to add to subscription list
  272.  * Returns: T on success, NIL on failure
  273.  */
  274.  
  275. long map_subscribe (stream,mailbox)
  276.     MAILSTREAM *stream;
  277.     char *mailbox;
  278. {
  279.   return map_manage (stream,mailbox,"Subscribe Mailbox",NIL);
  280. }
  281.  
  282.  
  283. /* Mail access protocol unsubscribe to mailbox
  284.  * Accepts: mail stream
  285.  *        mailbox to delete from manage list
  286.  * Returns: T on success, NIL on failure
  287.  */
  288.  
  289. long map_unsubscribe (stream,mailbox)
  290.     MAILSTREAM *stream;
  291.     char *mailbox;
  292. {
  293.   return map_manage (stream,mailbox,"Unsubscribe Mailbox",NIL);
  294. }
  295.  
  296.  
  297. /* Mail Access Protocol subscribe to bboard
  298.  * Accepts: mail stream
  299.  *        mailbox to add to manage list
  300.  * Returns: T on success, NIL on failure
  301.  */
  302.  
  303. long map_subscribe_bboard (stream,mailbox)
  304.     MAILSTREAM *stream;
  305.     char *mailbox;
  306. {
  307.   return map_manage (stream,mailbox,"Subscribe BBoard",NIL);
  308. }
  309.  
  310.  
  311. /* Mail access protocol unsubscribe to bboard
  312.  * Accepts: mail stream
  313.  *        mailbox to delete from manage list
  314.  * Returns: T on success, NIL on failure
  315.  */
  316.  
  317. long map_unsubscribe_bboard (stream,mailbox)
  318.     MAILSTREAM *stream;
  319.     char *mailbox;
  320. {
  321.   return map_manage (stream,mailbox,"Unsubscribe BBoard",NIL);
  322. }
  323.  
  324. /* Mail Access Protocol create mailbox
  325.  * Accepts: mail stream
  326.  *        mailbox name to create
  327.  * Returns: T on success, NIL on failure
  328.  */
  329.  
  330. long map_create (stream,mailbox)
  331.     MAILSTREAM *stream;
  332.     char *mailbox;
  333. {
  334.   return map_manage (stream,mailbox,"Create",NIL);
  335. }
  336.  
  337.  
  338. /* Mail Access Protocol delete mailbox
  339.  * Accepts: mail stream
  340.  *        mailbox name to delete
  341.  * Returns: T on success, NIL on failure
  342.  */
  343.  
  344. long map_delete (stream,mailbox)
  345.     MAILSTREAM *stream;
  346.     char *mailbox;
  347. {
  348.   return map_manage (stream,mailbox,"Delete",NIL);
  349. }
  350.  
  351.  
  352. /* Mail Access Protocol rename mailbox
  353.  * Accepts: mail stream
  354.  *        old mailbox name
  355.  *        new mailbox name
  356.  * Returns: T on success, NIL on failure
  357.  */
  358.  
  359. long map_rename (stream,old,new)
  360.     MAILSTREAM *stream;
  361.     char *old;
  362.     char *new;
  363. {
  364.   return map_manage (stream,old,"Rename",new);
  365. }
  366.  
  367. /* Mail Access Protocol manage a mailbox
  368.  * Accepts: mail stream
  369.  *        mailbox to manipulate
  370.  *        command to execute
  371.  *        optional second argument
  372.  * Returns: T on success, NIL on failure
  373.  */
  374.  
  375. long map_manage (stream,mailbox,command,arg2)
  376.     MAILSTREAM *stream;
  377.     char *mailbox;
  378.     char *command;
  379.     char *arg2;
  380. {
  381.   MAILSTREAM *st = stream;
  382.   long ret;
  383.   char *s,tmp[MAILTMPLEN];
  384.   IMAPPARSEDREPLY *reply;
  385.   if (!(stream && LOCAL)) {    /* if a prototype stream requested */
  386.     if (!(stream = mail_open (NIL,mailbox,OP_HALFOPEN))) {
  387.       mm_log ("Can't access server",ERROR);
  388.       return NIL;
  389.     }
  390.   }
  391.                 /* KLUDGE: nuke host name in second argument */
  392.   if (arg2 && (*arg2 == '{') && (s = strchr (arg2,'}'))) arg2 = s + 1;
  393.                 /* get mailbox name */
  394.   mail_valid_net (mailbox,&imapdriver,NIL,tmp);
  395.                 /* send management command */
  396.   ret = imap_OK (stream,reply = imap_send (stream,command,tmp,arg2));
  397.   mm_log (reply->text, ret ? (long) NIL : ERROR);
  398.                 /* toss out temporary stream */
  399.   if (st != stream) mail_close (stream);
  400.   return ret;
  401. }
  402.  
  403. /* Mail Access Protocol open
  404.  * Accepts: stream to open
  405.  * Returns: stream to use on success, NIL on failure
  406.  */
  407.  
  408. MAILSTREAM *map_open (stream)
  409.     MAILSTREAM *stream;
  410. {
  411.   long i,j;
  412.   char username[MAILTMPLEN],pwd[MAILTMPLEN],tmp[MAILTMPLEN];
  413.   NETMBX mb;
  414.   char *s;
  415.   IMAPPARSEDREPLY *reply = NIL;
  416.                 /* return prototype for OP_PROTOTYPE call */
  417.   if (!stream) return &imapproto;
  418.   mail_valid_net_parse (stream->mailbox,&mb);
  419.   if (LOCAL) {            /* if stream opened earlier by us */
  420.     if (strcmp (ucase (strcpy (tmp,mb.host)),
  421.         ucase (strcpy (pwd,imap_host (stream))))) {
  422.                 /* if hosts are different punt it */
  423.       sprintf (tmp,"Closing connection to %s",imap_host (stream));
  424.       if (!stream->silent) mm_log (tmp,(long) NIL);
  425.       map_close (stream);
  426.     }
  427.     else {            /* else recycle if still alive */
  428.       i = stream->silent;    /* temporarily mark silent */
  429.       stream->silent = T;    /* don't give mm_exists() events */
  430.       j = map_ping (stream);    /* learn if stream still alive */
  431.       stream->silent = i;    /* restore prior state */
  432.       if (j) {            /* was stream still alive? */
  433.     sprintf (tmp,"Reusing connection to %s",mb.host);
  434.     if (!stream->silent) mm_log (tmp,(long) NIL);
  435.     map_do_gc (stream,GC_TEXTS);
  436.       }
  437.       else map_close (stream);
  438.     }
  439.     mail_free_cache (stream);
  440.   }
  441.  
  442.   if (!LOCAL) {            /* open new connection if no recycle */
  443.     stream->local = fs_get (sizeof (IMAPLOCAL));
  444.     LOCAL->reply.line = LOCAL->reply.tag = LOCAL->reply.key =
  445.       LOCAL->reply.text = LOCAL->prefix = NIL;
  446.     LOCAL->use_body = LOCAL->use_find = LOCAL->use_bboard =
  447.       LOCAL->use_purge = T;    /* assume maximal server */
  448.                 /* try authenticated open */
  449.     if (LOCAL->tcpstream = (stream->anonymous || mb.anoflag || mb.port) ? NIL :
  450.     tcp_aopen (mb.host,"/etc/rimapd")) {
  451.                 /* if success, see if reasonable banner */
  452.       if ((s = tcp_getline (LOCAL->tcpstream)) && (*s == '*') &&
  453.       (reply = imap_parse_reply (stream,s)) && !strcmp (reply->tag,"*"))
  454.     imap_parse_unsolicited (stream,reply);
  455.       else {            /* nuke the stream then */
  456.     if (s) fs_give ((void **) &s);
  457.     if (LOCAL->tcpstream) {
  458.       tcp_close (LOCAL->tcpstream);
  459.       LOCAL->tcpstream = NIL;
  460.     }
  461.       }
  462.     }
  463.     if (!LOCAL->tcpstream &&    /* try to open ordinary connection */
  464.     (LOCAL->tcpstream = tcp_open(mb.host,mb.port?(long)mb.port:map_port))&&
  465.     (!imap_OK (stream,reply = imap_reply (stream,NIL)))) {
  466.       mm_log (reply->text,ERROR);
  467.       map_close (stream);    /* failed, clean up */
  468.     }
  469.  
  470.     if (LOCAL && LOCAL->tcpstream && !strcmp (reply->key,"OK")) {
  471.                 /* only so many tries to login */
  472.       if (!lhostn) lhostn = cpystr (tcp_localhost (LOCAL->tcpstream));
  473.       for (i = 0; i < map_maxlogintrials; ++i) {
  474.     *pwd = 0;        /* get password */
  475.                 /* if caller wanted anonymous access */
  476.     if ((mb.anoflag || stream->anonymous) && !i) {
  477.       strcpy (username,"anonymous");
  478.       strcpy (pwd,*lhostn ? lhostn : "foo");
  479.     }
  480.     else mm_login (tcp_host (LOCAL->tcpstream),username,pwd,i);
  481.                 /* abort if he refuses to give a password */
  482.     if (*pwd == '\0') i = map_maxlogintrials;
  483.     else {            /* send "LOGIN username pwd" */
  484.       if (imap_OK (stream,reply = imap_send (stream,"LOGIN",username,
  485.                          pwd))) break;
  486.                 /* output failure and try again */
  487.       mm_log (reply->text,WARN);
  488.                 /* give up now if connection died */
  489.       if (!strcmp (reply->key,"BYE")) i = map_maxlogintrials;
  490.     }
  491.       }
  492.                 /* give up if too many failures */
  493.       if (i >=  map_maxlogintrials) {
  494.     mm_log (*pwd ? "Too many login failures":"Login aborted",ERROR);
  495.     map_close (stream);
  496.       }
  497.       else stream->anonymous = strcmp (username,"anonymous") ? NIL : T;
  498.     }
  499.                 /* failed utterly to open */
  500.     if (LOCAL && !LOCAL->tcpstream) map_close (stream);
  501.   }
  502.  
  503.   if (LOCAL) {            /* have a connection now??? */
  504.     stream->sequence++;        /* bump sequence number */
  505.                 /* prepare to update mailbox name */
  506.     fs_give ((void **) &stream->mailbox);
  507.     if (stream->halfopen ||    /* send "SELECT/EXAMINE/BBOARD mailbox" */
  508.     !imap_OK (stream,reply = imap_send (stream,mb.bbdflag ? "BBOARD" :
  509.                         (stream->readonly ? "EXAMINE" :
  510.                          "SELECT"),mb.mailbox,NIL))) {
  511.       sprintf (tmp,"{%s}<no_mailbox>",imap_host (stream));
  512.       stream->mailbox = cpystr (tmp);
  513.       if (!stream->halfopen) {    /* output error message if didn't ask for it */
  514.     mm_log (reply->text,ERROR);
  515.     stream->halfopen = T;
  516.       }
  517.                 /* make sure dummy message counts */
  518.       mail_exists (stream,(long) 0);
  519.       mail_recent (stream,(long) 0);
  520.     }
  521.     else {            /* update mailbox name */
  522.       sprintf (tmp,"%s{%s}%s",mb.bbdflag ? "*" : "",
  523.            imap_host (stream),mb.mailbox);
  524.       stream->mailbox = cpystr (tmp);
  525.       reply->text[11] = '\0';    /* note if server said it was readonly */
  526.       stream->readonly = !strcmp (ucase (reply->text),"[READ-ONLY]");
  527.     }
  528.     if (!(stream->nmsgs || stream->silent))
  529.       mm_log ("Mailbox is empty",(long) NIL);
  530.     if (stream->scache && LOCAL->use_purge &&
  531.     !strcmp (imap_send (stream,"PURGE ALWAYS",NIL,NIL)->key,"BAD"))
  532.       LOCAL->use_purge = NIL;
  533.   }
  534.                 /* give up if nuked during startup */
  535.   if (LOCAL && !LOCAL->tcpstream) map_close (stream);
  536.   return LOCAL ? stream : NIL;    /* if stream is alive, return to caller */
  537. }
  538.  
  539. /* Mail Access Protocol close
  540.  * Accepts: MAIL stream
  541.  */
  542.  
  543. void map_close (stream)
  544.     MAILSTREAM *stream;
  545. {
  546.   IMAPPARSEDREPLY *reply;
  547.   if (stream && LOCAL) {    /* send "LOGOUT" */
  548.     if (LOCAL->tcpstream &&
  549.     !imap_OK (stream,reply = imap_send (stream,"LOGOUT",NIL,NIL)))
  550.       mm_log (reply->text,WARN);
  551.                 /* close TCP connection if still open */
  552.     if (LOCAL->tcpstream) tcp_close (LOCAL->tcpstream);
  553.     LOCAL->tcpstream = NIL;
  554.                 /* free up memory */
  555.     if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
  556.     map_do_gc (stream,GC_TEXTS);/* nuke the cached strings */
  557.                 /* nuke the local data */
  558.     fs_give ((void **) &stream->local);
  559.   }
  560. }
  561.  
  562.  
  563. /* Mail Access Protocol fetch fast information
  564.  * Accepts: MAIL stream
  565.  *        sequence
  566.  *
  567.  * Generally, map_fetchstructure is preferred
  568.  */
  569.  
  570. void map_fetchfast (stream,sequence)
  571.     MAILSTREAM *stream;
  572.     char *sequence;
  573. {                /* send "FETCH sequence FAST" */
  574.   IMAPPARSEDREPLY *reply;
  575.   if (!imap_OK (stream,reply = imap_send (stream,"FETCH",sequence,"FAST")))
  576.     mm_log (reply->text,ERROR);
  577. }
  578.  
  579.  
  580. /* Mail Access Protocol fetch flags
  581.  * Accepts: MAIL stream
  582.  *        sequence
  583.  */
  584.  
  585. void map_fetchflags (stream,sequence)
  586.     MAILSTREAM *stream;
  587.     char *sequence;
  588. {                /* send "FETCH sequence FLAGS" */
  589.   IMAPPARSEDREPLY *reply;
  590.   if (!imap_OK (stream,reply = imap_send (stream,"FETCH",sequence,"FLAGS")))
  591.     mm_log (reply->text,ERROR);
  592. }
  593.  
  594. /* Mail Access Protocol fetch structure
  595.  * Accepts: MAIL stream
  596.  *        message # to fetch
  597.  *        pointer to return body
  598.  * Returns: envelope of this message, body returned in body value
  599.  *
  600.  * Fetches the "fast" information as well
  601.  */
  602.  
  603. ENVELOPE *map_fetchstructure (stream,msgno,body)
  604.     MAILSTREAM *stream;
  605.     long msgno;
  606.     BODY **body;
  607. {
  608.   long i = msgno;
  609.   long j = min (msgno + map_lookahead - 1,stream->nmsgs);
  610.   char seq[20];
  611.   LONGCACHE *lelt;
  612.   ENVELOPE **env;
  613.   BODY **b;
  614.   IMAPPARSEDREPLY *reply;
  615.   if (stream->scache) {        /* short cache */
  616.     if (msgno != stream->msgno){/* flush old poop if a different message */
  617.       mail_free_envelope (&stream->env);
  618.       mail_free_body (&stream->body);
  619.     }
  620.     stream->msgno = msgno;
  621.     env = &stream->env;        /* get pointers to envelope and body */
  622.     b = &stream->body;
  623.     sprintf (seq,"%ld",msgno);    /* never lookahead with a short cache */
  624.   }
  625.   else {            /* long cache */
  626.     lelt = mail_lelt (stream,msgno);
  627.     env = &lelt->env;        /* get pointers to envelope and body */
  628.     b = &lelt->body;
  629.     if (msgno != stream->nmsgs)    /* determine lookahead range */
  630.       while (i < j && !mail_lelt (stream,i+1)->env) i++;
  631.     sprintf (seq,"%ld:%ld",msgno,i);
  632.   }
  633.                 /* have the poop we need? */
  634.   if ((body && !*b && LOCAL->use_body) || !*env) {
  635.     mail_free_envelope (env);    /* flush old envelope and body */
  636.     mail_free_body (b);
  637.     if (!(body && LOCAL->use_body &&
  638.       (LOCAL->use_body =
  639.        (strcmp ((reply = imap_send(stream,"FETCH",seq,"FULL"))->key,"BAD")?
  640.         T : NIL)))) reply = imap_send (stream,"FETCH",seq,"ALL");
  641.     if (!imap_OK (stream,reply)) {
  642.       mm_log (reply->text,ERROR);
  643.       return NIL;
  644.     }
  645.   }
  646.   if (body) *body = *b;        /* return the body */
  647.   return *env;            /* return the envelope */
  648. }
  649.  
  650. /* Mail Access Protocol fetch message header
  651.  * Accepts: MAIL stream
  652.  *        message # to fetch
  653.  * Returns: message header in RFC822 format
  654.  */
  655.  
  656. char *map_fetchheader (stream,msgno)
  657.     MAILSTREAM *stream;
  658.     long msgno;
  659. {
  660.   char tmp[40];
  661.   long i = msgno - 1;
  662.   IMAPPARSEDREPLY *reply;
  663.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  664.   if (!elt->data1) {        /* not if already cached */
  665.     sprintf (tmp,"FETCH %ld",msgno);
  666.     if (!imap_OK (stream,    /* send "FETCH msgno RFC822.HEADER" */
  667.           reply = imap_send (stream,tmp,elt->data2 ?
  668.                      "RFC822.HEADER" :
  669.                      "(RFC822.HEADER RFC822.TEXT)",NIL)))
  670.       mm_log (reply->text,ERROR);
  671.   }
  672.   return elt->data1 ? (char *) elt->data1 : "";
  673. }
  674.  
  675.  
  676. /* Mail Access Protocol fetch message text (only)
  677.     body only;
  678.  * Accepts: MAIL stream
  679.  *        message # to fetch
  680.  * Returns: message text in RFC822 format
  681.  */
  682.  
  683. char *map_fetchtext (stream,msgno)
  684.     MAILSTREAM *stream;
  685.     long msgno;
  686. {
  687.   char seq[20];
  688.   long i = msgno - 1;
  689.   IMAPPARSEDREPLY *reply;
  690.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  691.   if (!elt->data2) {        /* send "FETCH msgno RFC822.TEXT" */
  692.     sprintf (seq,"%ld",msgno);
  693.     if (!imap_OK (stream,reply = imap_send(stream,"FETCH",seq,"RFC822.TEXT")))
  694.       mm_log (reply->text,ERROR);
  695.   }
  696.   return elt->data2 ? (char *) elt->data2 : "";
  697. }
  698.  
  699. /* Mail Access Protocol fetch message body as a structure
  700.  * Accepts: Mail stream
  701.  *        message # to fetch
  702.  *        section specifier
  703.  *        pointer to length
  704.  * Returns: pointer to section of message body
  705.  */
  706.  
  707. char *map_fetchbody (stream,m,sec,len)
  708.     MAILSTREAM *stream;
  709.     long m;
  710.     char *sec;
  711.     unsigned long *len;
  712. {
  713.   BODY *b;
  714.   PART *pt;
  715.   char *s = sec;
  716.   char **ss;
  717.   unsigned long i;
  718.   char seq[40];
  719.   IMAPPARSEDREPLY *reply;
  720.   *len = 0;            /* in case failure */
  721.                 /* make sure have a body */
  722.   if (!(LOCAL->use_body && map_fetchstructure (stream,m,&b) && b)) {
  723.                 /* bodies not supported, wanted section 1? */
  724.     if (strcmp (sec,"1")) return NIL;
  725.                 /* yes, return text */
  726.     *len = strlen (s = map_fetchtext (stream,m));
  727.     return s;
  728.   }
  729.   if (!(s && *s && ((i = strtol (s,&s,10)) > 0))) return NIL;
  730.   do {                /* until find desired body part */
  731.                 /* multipart content? */
  732.     if (b->type == TYPEMULTIPART) {
  733.       pt = b->contents.part;    /* yes, find desired part */
  734.       while (--i && (pt = pt->next));
  735.       if (!pt) return NIL;    /* bad specifier */
  736.                 /* note new body, check valid nesting */
  737.       if (((b = &pt->body)->type == TYPEMULTIPART) && !*s) return NIL;
  738.     }
  739.     else if (i != 1) return NIL;/* otherwise must be section 1 */
  740.                 /* need to go down further? */
  741.     if (i = *s) switch (b->type) {
  742.     case TYPEMESSAGE:        /* embedded message, calculate new base */
  743.       b = b->contents.msg.body;    /* get its body, drop into multipart case */
  744.     case TYPEMULTIPART:        /* multipart, get next section */
  745.       if ((*s++ == '.') && (i = strtol (s,&s,10)) > 0) break;
  746.     default:            /* bogus subpart specification */
  747.       return NIL;
  748.     }
  749.   } while (i);
  750.  
  751.                 /* lose if body bogus */
  752.   if ((!b) || b->type == TYPEMULTIPART) return NIL;
  753.   switch (b->type) {        /* decide where the data is based on type */
  754.   case TYPEMESSAGE:        /* encapsulated message */
  755.     ss = &b->contents.msg.text;
  756.     break;
  757.   case TYPETEXT:        /* textual data */
  758.     ss = (char **) &b->contents.text;
  759.     break;
  760.   default:            /* otherwise assume it is binary */
  761.     ss = (char **) &b->contents.binary;
  762.     break;
  763.   }
  764.   if (!*ss) {            /* fetch data if don't have it yet */
  765.     sprintf (seq,"%ld BODY[%s]",m,sec);
  766.     if (!imap_OK (stream,reply = imap_send (stream,"FETCH",seq,NIL)))
  767.       mm_log (reply->text,ERROR);
  768.   }
  769.                 /* return data size if have data */
  770.   if (s = *ss) *len = b->size.bytes;
  771.   return s;
  772. }
  773.  
  774. /* Mail Access Protocol set flag
  775.  * Accepts: MAIL stream
  776.  *        sequence
  777.  *        flag(s)
  778.  */
  779.  
  780. void map_setflag (stream,sequence,flag)
  781.     MAILSTREAM *stream;
  782.     char *sequence;
  783.     char *flag;
  784. {
  785.   char *tmp = (char *) fs_get (20 + strlen (sequence));
  786.   IMAPPARSEDREPLY *reply;
  787.                 /* "STORE sequence +Flags flag" */
  788.   sprintf (tmp,"STORE %s +Flags",sequence);
  789.   if (!imap_OK (stream,reply = imap_send (stream,tmp,flag,NIL)))
  790.     mm_log (reply->text,ERROR);
  791.   fs_give ((void **) &tmp);
  792. }
  793.  
  794.  
  795. /* Mail Access Protocol clear flag
  796.  * Accepts: MAIL stream
  797.  *        sequence
  798.  *        flag(s)
  799.  */
  800.  
  801. void map_clearflag (stream,sequence,flag)
  802.     MAILSTREAM *stream;
  803.     char *sequence;
  804.     char *flag;
  805. {
  806.   char *tmp = (char *) fs_get (20 + strlen (sequence));
  807.   IMAPPARSEDREPLY *reply;
  808.                 /* "STORE sequence -Flags flag" */
  809.   sprintf (tmp,"STORE %s -Flags",sequence);
  810.   if (!imap_OK (stream,reply = imap_send (stream,tmp,flag,NIL)))
  811.     mm_log (reply->text,ERROR);
  812.   fs_give ((void **) &tmp);
  813. }
  814.  
  815. /* Mail Access Protocol search for messages
  816.  * Accepts: MAIL stream
  817.  *        search criteria
  818.  */
  819.  
  820. void map_search (stream,criteria)
  821.     MAILSTREAM *stream;
  822.     char *criteria;
  823. {
  824.   long i,j;
  825.   char *s;
  826.   IMAPPARSEDREPLY *reply;
  827.   MESSAGECACHE *elt;
  828.                 /* do the SEARCH */
  829.   if (imap_OK (stream,reply = imap_send (stream,"SEARCH",criteria,NIL))) {
  830.                 /* can never pre-fetch with a short cache */
  831.     if (!map_prefetch || stream->scache) return;
  832.     s = LOCAL->tmp;        /* build sequence in temporary buffer */
  833.     *s = '\0';            /* initially nothing */
  834.                 /* search through mailbox */
  835.     for (i = 1; i <= stream->nmsgs; ++i)
  836.                 /* for searched messages with no envelope */
  837.       if ((elt = mail_elt (stream,i)) && elt->searched &&
  838.       !mail_lelt (stream,i)->env) {
  839.                 /* prepend with comma if not first time */
  840.     if (LOCAL->tmp[0]) *s++ = ',';
  841.     sprintf (s,"%ld",j = i);/* output message number */
  842.     s += strlen (s);    /* point at end of string */
  843.                 /* search for possible end of range */
  844.     while (i < stream->nmsgs && (elt = mail_elt (stream,i+1)) &&
  845.            elt->searched && !mail_lelt (stream,i+1)->env) i++;
  846.     if (i != j) {        /* if a range */
  847.       sprintf (s,":%ld",i);    /* output delimiter and end of range */
  848.       s += strlen (s);    /* point at end of string */
  849.     }
  850.       }
  851.     if (LOCAL->tmp[0]) {    /* anything to pre-fetch? */
  852.       s = cpystr (LOCAL->tmp);    /* yes, copy sequence */
  853.       if (!imap_OK (stream,reply = imap_send (stream,"FETCH",s,"ALL")))
  854.     mm_log (reply->text,ERROR);
  855.       fs_give ((void **) &s);    /* flush copy of sequence */
  856.     }
  857.   }
  858.   else mm_log (reply->text,ERROR);
  859. }
  860.  
  861. /* Mail Access Protocol ping mailbox
  862.  * Accepts: MAIL stream
  863.  * Returns: T if stream still alive, else NIL
  864.  */
  865.  
  866. long map_ping (stream)
  867.     MAILSTREAM *stream;
  868. {
  869.   return (LOCAL->tcpstream &&    /* send "NOOP" */
  870.       imap_OK (stream,imap_send (stream,"NOOP",NIL,NIL))) ? T : NIL;
  871. }
  872.  
  873.  
  874. /* Mail Access Protocol check mailbox
  875.  * Accepts: MAIL stream
  876.  */
  877.  
  878. void map_check (stream)
  879.     MAILSTREAM *stream;
  880. {
  881.                 /* send "CHECK" */
  882.   IMAPPARSEDREPLY *reply = imap_send (stream,"CHECK",NIL,NIL);
  883.   mm_log (reply->text,imap_OK (stream,reply) ? (long) NIL : ERROR);
  884. }
  885.  
  886.  
  887. /* Mail Access Protocol expunge mailbox
  888.  * Accepts: MAIL stream
  889.  */
  890.  
  891. void map_expunge (stream)
  892.     MAILSTREAM *stream;
  893. {
  894.                 /* send "EXPUNGE" */
  895.   IMAPPARSEDREPLY *reply = imap_send (stream,"EXPUNGE",NIL,NIL);
  896.   mm_log (reply->text,imap_OK (stream,reply) ? (long) NIL : ERROR);
  897. }
  898.  
  899. /* Mail Access Protocol copy message(s)
  900.     s;
  901.  * Accepts: MAIL stream
  902.  *        sequence
  903.  *        destination mailbox
  904.  * Returns: T if successful else NIL
  905.  */
  906.  
  907. long map_copy (stream,sequence,mailbox)
  908.     MAILSTREAM *stream;
  909.     char *sequence;
  910.     char *mailbox;
  911. {
  912.   IMAPPARSEDREPLY *reply;
  913.   if (!LOCAL->tcpstream) {    /* not valid on dead stream */
  914.     mm_log ("Copy rejected: connection to remote IMAP server closed",ERROR);
  915.     return NIL;
  916.   }
  917.                 /* send "COPY sequence mailbox" */
  918.   if (!imap_OK (stream,reply = imap_send (stream,"COPY",sequence,mailbox))) {
  919.     mm_log (reply->text,ERROR);
  920.     return NIL;
  921.   }
  922.   map_setflag (stream,sequence,"\\Seen");
  923.   return T;
  924. }
  925.  
  926.  
  927. /* Mail Access Protocol move message(s)
  928.     s;
  929.  * Accepts: MAIL stream
  930.  *        sequence
  931.  *        destination mailbox
  932.  * Returns: T if successful else NIL
  933.  */
  934.  
  935. long map_move (stream,sequence,mailbox)
  936.     MAILSTREAM *stream;
  937.     char *sequence;
  938.     char *mailbox;
  939. {
  940.   IMAPPARSEDREPLY *reply;
  941.   if (!LOCAL->tcpstream) {    /* not valid on dead stream */
  942.     mm_log ("Move rejected: connection to remote IMAP server closed",ERROR);
  943.     return NIL;
  944.   }
  945.                 /* send "COPY sequence mailbox" */
  946.   if (!imap_OK (stream,reply = imap_send (stream,"COPY",sequence,mailbox))) {
  947.     mm_log (reply->text,ERROR);
  948.     return NIL;
  949.   }
  950.   map_setflag (stream,sequence,"\\Deleted \\Seen");
  951.   return T;
  952. }
  953.  
  954. /* Mail Access Protocol append message string
  955.  * Accepts: mail stream
  956.  *        destination mailbox
  957.  *        stringstruct of message to append
  958.  * Returns: T on success, NIL on failure
  959.  */
  960.  
  961. long map_append (stream,mailbox,message)
  962.     MAILSTREAM *stream;
  963.     char *mailbox;
  964.     STRING *message;
  965. {
  966.   MAILSTREAM *st = stream;
  967.   long ret;
  968.   char tmp[MAILTMPLEN];
  969.   IMAPPARSEDREPLY *reply;
  970.                 /* in case useful stream not given */
  971.   if (!(stream && LOCAL && LOCAL->tcpstream)) {
  972.     if (!(stream = mail_open (NIL,mailbox,OP_HALFOPEN))) {
  973.       mm_log ("Can't access server for append",ERROR);
  974.       return NIL;
  975.     }
  976.   }
  977.                 /* get mailbox name */
  978.   if ((ret = mail_valid_net (mailbox,&imapdriver,NIL,tmp) ? T : NIL) &&
  979.       (!imap_OK (stream,    /* report error if it choked */
  980.          reply = imap_send_literal (stream,"APPEND",tmp,message)))) {
  981.     mm_log (reply->text,ERROR);
  982.     ret = NIL;
  983.   }
  984.                 /* toss out temporary stream */
  985.   if (st != stream) mail_close (stream);
  986.   return ret;            /* return */
  987. }
  988.  
  989. /* Mail Access Protocol garbage collect stream
  990.  * Accepts: Mail stream
  991.  *        garbage collection flags
  992.  */
  993.  
  994. void map_gc (stream,gcflags)
  995.     MAILSTREAM *stream;
  996.     long gcflags;
  997. {
  998.   char tmp[MAILTMPLEN];
  999.   if (stream->nmsgs) {        /* nothing to purge if no messages */
  1000.     sprintf (tmp,"1:%ld",stream->nmsgs);
  1001.     if (LOCAL->use_purge && (gcflags & GC_ELT) &&
  1002.     !strcmp (imap_send (stream,"PURGE STATUS",tmp,NIL)->key,"BAD"))
  1003.       LOCAL->use_purge = NIL;
  1004.     if (LOCAL->use_purge && (gcflags & GC_ENV) &&
  1005.     !strcmp (imap_send (stream,"PURGE STRUCTURE",tmp,NIL)->key,"BAD"))
  1006.       LOCAL->use_purge = NIL;
  1007.     if (LOCAL->use_purge && (gcflags & GC_TEXTS) &&
  1008.     !strcmp (imap_send (stream,"PURGE TEXTS",tmp,NIL)->key,"BAD"))
  1009.       LOCAL->use_purge = NIL;
  1010.   }
  1011.   map_do_gc (stream,gcflags);    /* now call our worker routine */
  1012. }
  1013.  
  1014.  
  1015. /* Mail Access Protocol garbage collect stream worker routine
  1016.  * Accepts: Mail stream
  1017.  *        garbage collection flags
  1018.  */
  1019.  
  1020. void map_do_gc (stream,gcflags)
  1021.     MAILSTREAM *stream;
  1022.     long gcflags;
  1023. {
  1024.   unsigned long i;
  1025.   MESSAGECACHE *elt;
  1026.   LONGCACHE *lelt;
  1027.                 /* make sure the cache is large enough */
  1028.   (*mailcache) (stream,stream->nmsgs,CH_SIZE);
  1029.   if (gcflags & GC_TEXTS) {    /* garbage collect texts? */
  1030.     for (i = 1; i <= stream->nmsgs; ++i)
  1031.       if (elt = (MESSAGECACHE *) (*mailcache) (stream,i,CH_ELT)) {
  1032.     if (elt->data1) fs_give ((void **) &elt->data1);
  1033.     if (elt->data2) fs_give ((void **) &elt->data2);
  1034.     if (!stream->scache) map_gc_body ((lelt = mail_lelt (stream,i))->body);
  1035.       }
  1036.     map_gc_body (stream->body);    /* free texts from short cache body */
  1037.   }
  1038.                 /* gc cache if requested and unlocked */
  1039.   if (gcflags & GC_ELT) for (i = 1; i <= stream->nmsgs; ++i)
  1040.     if ((elt = (MESSAGECACHE *) (*mailcache) (stream,i,CH_ELT)) &&
  1041.     (elt->lockcount == 1)) (*mailcache) (stream,i,CH_FREE);
  1042. }
  1043.  
  1044. /* Mail Access Protocol garbage collect body texts
  1045.  * Accepts: body to GC
  1046.  */
  1047.  
  1048. void map_gc_body (body)
  1049.     BODY *body;
  1050. {
  1051.   PART *part;
  1052.   if (body) switch (body->type) {
  1053.   case TYPETEXT:        /* unformatted text */
  1054.     if (body->contents.text) fs_give ((void **) &body->contents.text);
  1055.     break;
  1056.   case TYPEMULTIPART:        /* multiple part */
  1057.     if (part = body->contents.part) do map_gc_body (&part->body);
  1058.     while (part = part->next);
  1059.     break;
  1060.   case TYPEMESSAGE:        /* encapsulated message */
  1061.     map_gc_body (body->contents.msg.body);
  1062.     if (body->contents.msg.text)
  1063.       fs_give ((void **) &body->contents.msg.text);
  1064.     break;
  1065.   case TYPEAPPLICATION:        /* application data */
  1066.   case TYPEAUDIO:        /* audio */
  1067.   case TYPEIMAGE:        /* static image */
  1068.   case TYPEVIDEO:        /* video */
  1069.     if (body->contents.binary) fs_give (&body->contents.binary);
  1070.     break;
  1071.   default:
  1072.     break;
  1073.   }
  1074. }
  1075.  
  1076. /* Internal routines */
  1077.  
  1078.  
  1079. /* Mail Access Protocol return host name
  1080.  * Accepts: MAIL stream
  1081.  * Returns: host name
  1082.  */
  1083.  
  1084. char *imap_host (stream)
  1085.     MAILSTREAM *stream;
  1086. {
  1087.                 /* return host name on stream if open */
  1088.   return (LOCAL && LOCAL->tcpstream) ? tcp_host (LOCAL->tcpstream) :
  1089.     "<closed stream>";
  1090. }
  1091.  
  1092. /* Mail Access Protocol send command
  1093.  * Accepts: MAIL stream
  1094.  *        command
  1095.  *        command argument
  1096.  *        second command argument
  1097.  * Returns: parsed reply
  1098.  */
  1099.  
  1100. IMAPPARSEDREPLY *imap_send (stream,cmd,arg,arg2)
  1101.     MAILSTREAM *stream;
  1102.     char *cmd;
  1103.     char *arg;
  1104.     char *arg2;
  1105. {
  1106.   IMAPPARSEDREPLY *reply = NIL;
  1107.   if (arg2 && strpbrk (arg2,"\012\015\"%{\\")) {
  1108.     STRING s;
  1109.     INIT (&s,mail_string,(void *) arg2,(unsigned long) strlen (arg2));
  1110.     reply = imap_send_literal (stream,cmd,arg,&s);
  1111.   }
  1112.   else {
  1113.     char tag[7];
  1114.                   /* gensym a new tag */
  1115.     sprintf (tag,"A%05ld",stream->gensym++);
  1116.     if (!LOCAL->tcpstream) return imap_fake(stream,tag,"OK No-op dead stream");
  1117.                 /* begin command */
  1118.     sprintf (LOCAL->tmp,"%s %s",tag,cmd);
  1119.     mail_lock (stream);        /* lock up the stream */
  1120.     if (arg) {            /* argument present? */
  1121.       sprintf (LOCAL->tmp + strlen (LOCAL->tmp)," %s",arg);
  1122.                 /* second argument present? */
  1123.       if (arg2) sprintf (LOCAL->tmp + strlen (LOCAL->tmp),
  1124.              strchr (arg2,' ') ? " \"%s\"" : " %s",arg2);
  1125.     }
  1126.                 /* output to debugging telemetry */
  1127.     if (stream->debug) mm_dlog (LOCAL->tmp);
  1128.     strcat (LOCAL->tmp,"\015\012");
  1129.                 /* send the command */
  1130.     if (tcp_soutr (LOCAL->tcpstream,LOCAL->tmp))
  1131.       reply = imap_reply (stream,tag);
  1132.     mail_unlock (stream);    /* unlock stream */
  1133.     if (!reply) {        /* close TCP connection if it died */
  1134.       tcp_close (LOCAL->tcpstream);
  1135.       LOCAL->tcpstream = NIL;
  1136.       return imap_fake (stream,tag,"BYE IMAP connection broken in send");
  1137.     }
  1138.   }
  1139.   return reply;            /* return reply to caller */
  1140. }
  1141.  
  1142. /* Mail Access Protocol send command with literal argument
  1143.  * Accepts: MAIL stream
  1144.  *        command
  1145.  *        command argument
  1146.  *        second command argument
  1147.  * Returns: parsed reply
  1148.  */
  1149.  
  1150. IMAPPARSEDREPLY *imap_send_literal (stream,cmd,arg,arg2)
  1151.     MAILSTREAM *stream;
  1152.     char *cmd;
  1153.     char *arg;
  1154.     STRING *arg2;
  1155. {
  1156.   char tag[7];
  1157.   char *s = NIL;
  1158.   unsigned long i = SIZE (arg2);
  1159.   IMAPPARSEDREPLY *reply = NIL;
  1160.   if (!LOCAL->tcpstream) return imap_fake (stream,tag,"OK No-op dead stream");
  1161.                   /* gensym a new tag */
  1162.   sprintf (tag,"A%05ld",stream->gensym++);
  1163.                 /* begin command */
  1164.   sprintf (LOCAL->tmp,"%s %s %s {%ld}",tag,cmd,arg,i);
  1165.   mail_lock (stream);        /* lock up the stream */
  1166.                 /* output debugging telemetry */
  1167.   if (stream->debug) mm_dlog (LOCAL->tmp);
  1168.   strcat (LOCAL->tmp,"\015\012");
  1169.                 /* send the command */
  1170.   if (tcp_soutr (LOCAL->tcpstream,LOCAL->tmp) &&
  1171.       !strcmp ((reply = imap_reply (stream,tag))->tag,"+")) {
  1172.                 /* dump the message */
  1173.     while (i && tcp_sout (LOCAL->tcpstream,arg2->curpos,arg2->cursize)) {
  1174.       i -= arg2->cursize;    /* note that we wrote out this much */
  1175.       arg2->curpos += (arg2->cursize - 1);
  1176.       arg2->cursize = 1;
  1177.       SNX(arg2);        /* advance to next buffer's worth */
  1178.     }
  1179.     if ((!i) && tcp_soutr (LOCAL->tcpstream,"\015\012"))
  1180.       reply = imap_reply (stream,tag);
  1181.     else reply = NIL;
  1182.   }
  1183.   mail_unlock (stream);        /* unlock stream */
  1184.   if (!reply) {            /* close TCP connection if it died */
  1185.     tcp_close (LOCAL->tcpstream);
  1186.     LOCAL->tcpstream = NIL;
  1187.     return imap_fake (stream,tag,"BYE IMAP connection broken in send");
  1188.   }
  1189.   return reply;            /* return reply to caller */
  1190. }
  1191.  
  1192. /* Mail Access Protocol get reply
  1193.  * Accepts: MAIL stream
  1194.  *        tag to search or NIL if want a greeting
  1195.  * Returns: parsed reply, never NIL
  1196.  */
  1197.  
  1198. IMAPPARSEDREPLY *imap_reply (stream,tag)
  1199.     MAILSTREAM *stream;
  1200.     char *tag;
  1201. {
  1202.   IMAPPARSEDREPLY *reply;
  1203.   while (LOCAL->tcpstream) {    /* parse reply from server */
  1204.     if (reply = imap_parse_reply (stream,tcp_getline (LOCAL->tcpstream))) {
  1205.                 /* untagged response means unsolicited data */
  1206.       if (!strcmp (reply->tag,"*")) {
  1207.     imap_parse_unsolicited (stream,reply);
  1208.     if (tag) continue;    /* waiting for a response */
  1209.     return reply;        /* return greeting */
  1210.       }
  1211.       else {            /* not unsolicited reponse */
  1212.     if (tag && ((!strcmp (tag,reply->tag)) || (!strcmp (reply->tag,"+"))))
  1213.       return reply;        /* return if desired tag or + */
  1214.                 /* report bogon */
  1215.     sprintf (LOCAL->tmp,"Unexpected tagged response: %.80s %.80s %.80s",
  1216.          reply->tag,reply->key,reply->text);
  1217.     mm_log (LOCAL->tmp,WARN);
  1218.       }
  1219.     }
  1220.   }
  1221.   return imap_fake (stream,tag,"BYE IMAP connection broken in reply");
  1222. }
  1223.  
  1224. /* Mail Access Protocol parse reply
  1225.  * Accepts: MAIL stream
  1226.  *        text of reply
  1227.  * Returns: parsed reply, or NIL if can't parse at least a tag and key
  1228.  */
  1229.  
  1230.  
  1231. IMAPPARSEDREPLY *imap_parse_reply (stream,text)
  1232.     MAILSTREAM *stream;
  1233.     char *text;
  1234. {
  1235.   if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
  1236.   if (!(LOCAL->reply.line = text)) {
  1237.                 /* NIL text means the stream died */
  1238.     tcp_close (LOCAL->tcpstream);
  1239.     LOCAL->tcpstream = NIL;
  1240.     return NIL;
  1241.   }
  1242.   if (stream->debug) mm_dlog (LOCAL->reply.line);
  1243.   LOCAL->reply.key = NIL;    /* init fields in case error */
  1244.   LOCAL->reply.text = NIL;
  1245.                 /* parse separate tag, key, text */
  1246.   if (!((LOCAL->reply.tag = (char *) strtok (LOCAL->reply.line," ")) &&
  1247.     (LOCAL->reply.key = (char *) strtok (NIL," ")))) {
  1248.                 /* determine what is missing */
  1249.     if (!LOCAL->reply.tag) strcpy (LOCAL->tmp,"IMAP server sent a blank line");
  1250.     else sprintf (LOCAL->tmp,"Missing IMAP reply key: %.80s",LOCAL->reply.tag);
  1251.     mm_log (LOCAL->tmp,WARN);    /* pass up the barfage */
  1252.     return NIL;            /* can't parse this text */
  1253.   }
  1254.   ucase (LOCAL->reply.key);    /* make sure key is upper case */
  1255.                 /* get text as well, allow empty text */
  1256.   if (!(LOCAL->reply.text = (char *) strtok (NIL,"\n")))
  1257.     LOCAL->reply.text = LOCAL->reply.key + strlen (LOCAL->reply.key);
  1258.   return &LOCAL->reply;        /* return parsed reply */
  1259. }
  1260.  
  1261. /* Mail Access Protocol fake reply
  1262.  * Accepts: MAIL stream
  1263.  *        tag
  1264.  *        text of fake reply
  1265.  * Returns: parsed reply
  1266.  */
  1267.  
  1268. IMAPPARSEDREPLY *imap_fake (stream,tag,text)
  1269.     MAILSTREAM *stream;
  1270.     char *tag;
  1271.     char *text;
  1272. {
  1273.                 /* build fake reply string */
  1274.   sprintf (LOCAL->tmp,"%s %s",tag,text);
  1275.                 /* parse and return it */
  1276.   return imap_parse_reply (stream,cpystr (LOCAL->tmp));
  1277. }
  1278.  
  1279.  
  1280. /* Mail Access Protocol check for OK response in tagged reply
  1281.  * Accepts: MAIL stream
  1282.  *        parsed reply
  1283.  * Returns: T if OK else NIL
  1284.  */
  1285.  
  1286. long imap_OK (stream,reply)
  1287.     MAILSTREAM *stream;
  1288.     IMAPPARSEDREPLY *reply;
  1289. {
  1290.                 /* OK - operation succeeded */
  1291.   if (!strcmp (reply->key,"OK") ||
  1292.       (!strcmp (reply->tag,"*") && !strcmp (reply->key,"PREAUTH")))
  1293.     return T;
  1294.                 /* NO - operation failed */
  1295.   else if (strcmp (reply->key,"NO")) {
  1296.                 /* BAD - operation rejected */
  1297.     if (!strcmp (reply->key,"BAD"))
  1298.       sprintf (LOCAL->tmp,"IMAP error: %.80s",reply->text);
  1299.                 /* BYE - server is going away */
  1300.     else if (!strcmp (reply->key,"BYE")) strcpy (LOCAL->tmp,reply->text);
  1301.     else sprintf (LOCAL->tmp,"Unexpected IMAP response: %.80s %.80s",
  1302.           reply->key,reply->text);
  1303.     mm_log (LOCAL->tmp,WARN);    /* log the sucker */
  1304.   }
  1305.   return NIL;
  1306. }
  1307.  
  1308. /* Mail Access Protocol parse and act upon unsolicited reply
  1309.  * Accepts: MAIL stream
  1310.  *        parsed reply
  1311.  */
  1312.  
  1313. void imap_parse_unsolicited (stream,reply)
  1314.     MAILSTREAM *stream;
  1315.     IMAPPARSEDREPLY *reply;
  1316. {
  1317.   long msgno;
  1318.   char *s;
  1319.   char *keyptr,*txtptr;
  1320.                 /* see if key is a number */
  1321.   msgno = strtol (reply->key,&s,10);
  1322.   if (*s) {            /* if non-numeric */
  1323.     if (!strcmp (reply->key,"FLAGS")) imap_parse_flaglst (stream,reply);
  1324.     else if (!strcmp (reply->key,"SEARCH")) imap_searched (stream,reply->text);
  1325.     else if (!strcmp (reply->key,"MAILBOX")) {
  1326.       sprintf (LOCAL->tmp,"%s%s",LOCAL->prefix ? LOCAL->prefix:"",reply->text);
  1327.       mm_mailbox (LOCAL->tmp);
  1328.     }
  1329.     else if (!strcmp (reply->key,"BBOARD")) {
  1330.       sprintf (LOCAL->tmp,"%s%s",LOCAL->prefix ? LOCAL->prefix:"",reply->text);
  1331.       mm_bboard (LOCAL->tmp);
  1332.     }
  1333.     else if (!strcmp (reply->key,"BYE")) {
  1334.       if (!stream->silent) mm_log (reply->text,(long) NIL);
  1335.     }
  1336.     else if (!strcmp (reply->key,"OK") || !strcmp (reply->key,"PREAUTH")) {
  1337.                 /* note if server said it was readonly */
  1338.       strncpy (LOCAL->tmp,reply->text,11);
  1339.       LOCAL->tmp[11] = '\0';    /* tie off text */
  1340.       if (strcmp (ucase (LOCAL->tmp),"[READ-ONLY]")) s = reply->text;
  1341.       else {
  1342.     stream->readonly = T;    /* make readonly now */
  1343.     s = reply->text + 12;    /* skip the cookie */
  1344.       }
  1345.       mm_notify (stream,s,(long) NIL);
  1346.     }
  1347.     else if (!strcmp (reply->key,"NO")) {
  1348.       if (!stream->silent) mm_notify (stream,reply->text,WARN);
  1349.     }
  1350.     else if (!strcmp (reply->key,"BAD")) mm_notify (stream,reply->text,ERROR);
  1351.     else {
  1352.       sprintf (LOCAL->tmp,"Unexpected unsolicited message: %.80s",reply->key);
  1353.       mm_log (LOCAL->tmp,WARN);
  1354.     }
  1355.   }
  1356.  
  1357.   else {            /* if numeric, a keyword follows */
  1358.                 /* deposit null at end of keyword */
  1359.     keyptr = ucase ((char *) strtok (reply->text," "));
  1360.                 /* and locate the text after it */
  1361.     txtptr = (char *) strtok (NIL,"\n");
  1362.                 /* now take the action */
  1363.                 /* change in size of mailbox */
  1364.     if (!strcmp (keyptr,"EXISTS")) mail_exists (stream,msgno);
  1365.     else if (!strcmp (keyptr,"RECENT")) mail_recent (stream,msgno);
  1366.     else if (!strcmp (keyptr,"EXPUNGE")) imap_expunged (stream,msgno);
  1367.     else if (!strcmp (keyptr,"FETCH"))
  1368.       imap_parse_data (stream,msgno,txtptr,reply);
  1369.                 /* obsolete alias for FETCH */
  1370.     else if (!strcmp (keyptr,"STORE"))
  1371.       imap_parse_data (stream,msgno,txtptr,reply);
  1372.                 /* obsolete response to COPY */
  1373.     else if (strcmp (keyptr,"COPY")) {
  1374.       sprintf (LOCAL->tmp,"Unknown message data: %ld %.80s",msgno,keyptr);
  1375.       mm_log (LOCAL->tmp,WARN);
  1376.     }
  1377.   }
  1378. }
  1379.  
  1380. /* Mail Access Protocol parse flag list
  1381.  * Accepts: MAIL stream
  1382.  *        parsed reply
  1383.  *
  1384.  *  The reply->line is yanked out of the parsed reply and stored on
  1385.  * stream->flagstring.  This is the original fs_get'd reply string, and
  1386.  * has all the flagstrings embedded in it.
  1387.  */
  1388.  
  1389. void imap_parse_flaglst (stream,reply)
  1390.     MAILSTREAM *stream;
  1391.     IMAPPARSEDREPLY *reply;
  1392. {
  1393.   char *text = reply->text;
  1394.   char *flag;
  1395.   long i;
  1396.                 /* flush old flagstring and flags if any */
  1397.   if (stream->flagstring) fs_give ((void **) &stream->flagstring);
  1398.   for (i = 0; i < NUSERFLAGS; ++i) stream->user_flags[i] = NIL;
  1399.                 /* remember this new one */
  1400.   stream->flagstring = reply->line;
  1401.   reply->line = NIL;
  1402.   ++text;            /* skip past open parenthesis */
  1403.                 /* get first flag if any */
  1404.   if (flag = (char *) strtok (text," )")) {
  1405.     i = 0;            /* init flag index */
  1406.                 /* add all user flags */
  1407.     do if (*flag != '\\') stream->user_flags[i++] = flag;
  1408.       while (flag = (char *) strtok (NIL," )"));
  1409.   }
  1410. }
  1411.  
  1412.  
  1413. /* Mail Access Protocol messages have been searched out
  1414.  * Accepts: MAIL stream
  1415.  *        list of message numbers
  1416.  *
  1417.  * Calls external "mail_searched" function to notify main program
  1418.  */
  1419.  
  1420. void imap_searched (stream,text)
  1421.     MAILSTREAM *stream;
  1422.     char *text;
  1423. {
  1424.                 /* only do something if have text */
  1425.   if (text && (text = (char *) strtok (text," ")))
  1426.     for (; text; text = (char *) strtok (NIL," "))
  1427.       mail_searched (stream,atol (text));
  1428. }
  1429.  
  1430. /* Mail Access Protocol message has been expunged
  1431.  * Accepts: MAIL stream
  1432.  *        message number
  1433.  *
  1434.  * Calls external "mail_searched" function to notify main program
  1435.  */
  1436.  
  1437. void imap_expunged (stream,msgno)
  1438.     MAILSTREAM *stream;
  1439.     long msgno;
  1440. {
  1441.   MESSAGECACHE *elt = (MESSAGECACHE *) (*mailcache) (stream,msgno,CH_ELT);
  1442.   if (elt) {
  1443.     if (elt->data1) fs_give ((void **) &elt->data1);
  1444.     if (elt->data2) fs_give ((void **) &elt->data2);
  1445.   }
  1446.   mail_expunged (stream,msgno);    /* notify upper level */
  1447. }
  1448.  
  1449.  
  1450. /* Mail Access Protocol parse data
  1451.  * Accepts: MAIL stream
  1452.  *        message #
  1453.  *        text to parse
  1454.  *        parsed reply
  1455.  *
  1456.  *  This code should probably be made a bit more paranoid about malformed
  1457.  * S-expressions.
  1458.  */
  1459.  
  1460. void imap_parse_data (stream,msgno,text,reply)
  1461.     MAILSTREAM *stream;
  1462.     long msgno;
  1463.     char *text;
  1464.                    IMAPPARSEDREPLY *reply;
  1465. {
  1466.   char *prop;
  1467.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1468.   ++text;            /* skip past open parenthesis */
  1469.                 /* parse Lisp-form property list */
  1470.   while (prop = (char *) strtok (text," )")) {
  1471.                 /* point at value */
  1472.     text = (char *) strtok (NIL,"\n");
  1473.                 /* parse the property and its value */
  1474.     imap_parse_prop (stream,elt,ucase (prop),&text,reply);
  1475.   }
  1476. }
  1477.  
  1478. /* Mail Access Protocol parse property
  1479.  * Accepts: MAIL stream
  1480.  *        cache item
  1481.  *        property name
  1482.  *        property value text pointer
  1483.  *        parsed reply
  1484.  */
  1485.  
  1486. void imap_parse_prop (stream,elt,prop,txtptr,reply)
  1487.     MAILSTREAM *stream;
  1488.     MESSAGECACHE *elt;
  1489.     char *prop;
  1490.                    char **txtptr;
  1491.     IMAPPARSEDREPLY *reply;
  1492. {
  1493.   char *s;
  1494.   ENVELOPE **env;
  1495.   BODY **body;
  1496.   long i = elt->msgno - 1;
  1497.   if (!strcmp (prop,"ENVELOPE")) {
  1498.     if (stream->scache) {    /* short cache, flush old stuff */
  1499.       mail_free_envelope (&stream->env);
  1500.       mail_free_body (&stream->body);
  1501.       stream->msgno =elt->msgno;/* set new current message number */
  1502.       env = &stream->env;    /* get pointer to envelope */
  1503.     }
  1504.     else env = &mail_lelt (stream,elt->msgno)->env;
  1505.     imap_parse_envelope (stream,env,txtptr,reply);
  1506.   }
  1507.   else if (!strcmp (prop,"FLAGS")) imap_parse_flags (stream,elt,txtptr);
  1508.   else if (!strcmp (prop,"INTERNALDATE")) {
  1509.     if (s = imap_parse_string (stream,txtptr,reply,(long) NIL)) {
  1510.       if (!mail_parse_date (elt,s)) {
  1511.     sprintf (LOCAL->tmp,"Bogus date: %.80s",s);
  1512.     mm_log (LOCAL->tmp,WARN);
  1513.       }
  1514.       fs_give ((void **) &s);
  1515.     }
  1516.   }
  1517.   else if (!strcmp (prop,"RFC822.HEADER")) {
  1518.     if (elt->data1) fs_give ((void **) &elt->data1);
  1519.     elt->data1 = (unsigned long) imap_parse_string (stream,txtptr,reply,
  1520.                             elt->msgno);
  1521.   }
  1522.  
  1523.   else if (!strcmp (prop,"RFC822.SIZE"))
  1524.     elt->rfc822_size = imap_parse_number (stream,txtptr);
  1525.   else if (!strcmp (prop,"RFC822.TEXT")) {
  1526.     if (elt->data2) fs_give ((void **) &elt->data2);
  1527.     elt->data2 = (unsigned long) imap_parse_string (stream,txtptr,reply,
  1528.                             elt->msgno);
  1529.   }
  1530.   else if (prop[0] == 'B' && prop[1] == 'O' && prop[2] == 'D' &&
  1531.        prop[3] == 'Y') {
  1532.     s = cpystr (prop+4);    /* copy segment specifier */
  1533.     if (stream->scache) {    /* short cache, flush old stuff */
  1534.       if (elt->msgno != stream->msgno) {
  1535.                 /* losing real bad here */
  1536.     mail_free_envelope (&stream->env);
  1537.     mail_free_body (&stream->body);
  1538.     sprintf (LOCAL->tmp,"Body received for %ld when current is %ld",
  1539.          elt->msgno,stream->msgno);
  1540.     mm_log (LOCAL->tmp,WARN);
  1541.     stream->msgno = elt->msgno;
  1542.       }
  1543.       body = &stream->body;    /* get pointer to body */
  1544.     }
  1545.     else body = &mail_lelt (stream,elt->msgno)->body;
  1546.     imap_parse_body (stream,elt->msgno,body,s,txtptr,reply);
  1547.     fs_give ((void **) &s);
  1548.   }
  1549.                 /* this shouldn't happen with our client */
  1550.   else if (!strcmp (prop,"RFC822")) {
  1551.     if (elt->data2) fs_give ((void **) &elt->data2);
  1552.     elt->data2 = (unsigned long) imap_parse_string (stream,txtptr,reply,
  1553.                             elt->msgno);
  1554.   }
  1555.   else {
  1556.     sprintf (LOCAL->tmp,"Unknown message property: %.80s",prop);
  1557.     mm_log (LOCAL->tmp,WARN);
  1558.   }
  1559. }
  1560.  
  1561. /* Mail Access Protocol parse envelope
  1562.  * Accepts: MAIL stream
  1563.  *        pointer to envelope pointer
  1564.  *        current text pointer
  1565.  *        parsed reply
  1566.  *
  1567.  * Updates text pointer
  1568.  */
  1569.  
  1570. void imap_parse_envelope (stream,env,txtptr,reply)
  1571.     MAILSTREAM *stream;
  1572.     ENVELOPE **env;
  1573.     char **txtptr;
  1574.                    IMAPPARSEDREPLY *reply;
  1575. {
  1576.   char c = *((*txtptr)++);    /* grab first character */
  1577.                 /* ignore leading spaces */
  1578.   while (c == ' ') c = *((*txtptr)++);
  1579.                 /* free any old envelope */
  1580.   if (*env) mail_free_envelope (env);
  1581.   switch (c) {            /* dispatch on first character */
  1582.   case '(':            /* if envelope S-expression */
  1583.     *env = mail_newenvelope ();    /* parse the new envelope */
  1584.     (*env)->date = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1585.     (*env)->subject = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1586.     (*env)->from = imap_parse_adrlist (stream,txtptr,reply);
  1587.     (*env)->sender = imap_parse_adrlist (stream,txtptr,reply);
  1588.     (*env)->reply_to = imap_parse_adrlist (stream,txtptr,reply);
  1589.     (*env)->to = imap_parse_adrlist (stream,txtptr,reply);
  1590.     (*env)->cc = imap_parse_adrlist (stream,txtptr,reply);
  1591.     (*env)->bcc = imap_parse_adrlist (stream,txtptr,reply);
  1592.     (*env)->in_reply_to = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1593.     (*env)->message_id = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1594.     if (**txtptr != ')') {
  1595.       sprintf (LOCAL->tmp,"Junk at end of envelope: %.80s",*txtptr);
  1596.       mm_log (LOCAL->tmp,WARN);
  1597.     }
  1598.     else ++*txtptr;        /* skip past delimiter */
  1599.     break;
  1600.   case 'N':            /* if NIL */
  1601.   case 'n':
  1602.     ++*txtptr;            /* bump past "I" */
  1603.     ++*txtptr;            /* bump past "L" */
  1604.     break;
  1605.   default:
  1606.     sprintf (LOCAL->tmp,"Not an envelope: %.80s",*txtptr);
  1607.     mm_log (LOCAL->tmp,WARN);
  1608.     break;
  1609.   }
  1610. }
  1611.  
  1612. /* Mail Access Protocol parse address list
  1613.  * Accepts: MAIL stream
  1614.  *        current text pointer
  1615.  *        parsed reply
  1616.  * Returns: address list, NIL on failure
  1617.  *
  1618.  * Updates text pointer
  1619.  */
  1620.  
  1621. ADDRESS *imap_parse_adrlist (stream,txtptr,reply)
  1622.     MAILSTREAM *stream;
  1623.     char **txtptr;
  1624.                       IMAPPARSEDREPLY *reply;
  1625. {
  1626.   ADDRESS *adr = NIL;
  1627.   char c = **txtptr;        /* sniff at first character */
  1628.                 /* ignore leading spaces */
  1629.   while (c == ' ') c = *++*txtptr;
  1630.   ++*txtptr;            /* skip past open paren */
  1631.   switch (c) {
  1632.   case '(':            /* if envelope S-expression */
  1633.     adr = imap_parse_address (stream,txtptr,reply);
  1634.     if (**txtptr != ')') {
  1635.       sprintf (LOCAL->tmp,"Junk at end of address list: %.80s",*txtptr);
  1636.       mm_log (LOCAL->tmp,WARN);
  1637.     }
  1638.     else ++*txtptr;        /* skip past delimiter */
  1639.     break;
  1640.   case 'N':            /* if NIL */
  1641.   case 'n':
  1642.     ++*txtptr;            /* bump past "I" */
  1643.     ++*txtptr;            /* bump past "L" */
  1644.     break;
  1645.   default:
  1646.     sprintf (LOCAL->tmp,"Not an address: %.80s",*txtptr);
  1647.     mm_log (LOCAL->tmp,WARN);
  1648.     break;
  1649.   }
  1650.   return adr;
  1651. }
  1652.  
  1653. /* Mail Access Protocol parse address
  1654.  * Accepts: MAIL stream
  1655.  *        current text pointer
  1656.  *        parsed reply
  1657.  * Returns: address, NIL on failure
  1658.  *
  1659.  * Updates text pointer
  1660.  */
  1661.  
  1662. ADDRESS *imap_parse_address (stream,txtptr,reply)
  1663.     MAILSTREAM *stream;
  1664.     char **txtptr;
  1665.                       IMAPPARSEDREPLY *reply;
  1666. {
  1667.   ADDRESS *adr = NIL;
  1668.   ADDRESS *ret = NIL;
  1669.   ADDRESS *prev = NIL;
  1670.   char c = **txtptr;        /* sniff at first address character */
  1671.   switch (c) {
  1672.   case '(':            /* if envelope S-expression */
  1673.     while (c == '(') {        /* recursion dies on small stack machines */
  1674.       ++*txtptr;        /* skip past open paren */
  1675.       if (adr) prev = adr;    /* note previous if any */
  1676.       adr = mail_newaddr ();    /* instantiate address and parse its fields */
  1677.       adr->personal = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1678.       adr->adl = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1679.       adr->mailbox = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1680.       adr->host = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1681.       if (**txtptr != ')') {    /* handle trailing paren */
  1682.     sprintf (LOCAL->tmp,"Junk at end of address: %.80s",*txtptr);
  1683.     mm_log (LOCAL->tmp,WARN);
  1684.       }
  1685.       else ++*txtptr;        /* skip past close paren */
  1686.       c = **txtptr;        /* set up for while test */
  1687.                 /* ignore leading spaces in front of next */
  1688.       while (c == ' ') c = *++*txtptr;
  1689.       if (!ret) ret = adr;    /* if first time note first adr */
  1690.                 /* if previous link new block to it */
  1691.       if (prev) prev->next = adr;
  1692.     }
  1693.     break;
  1694.  
  1695.   case 'N':            /* if NIL */
  1696.   case 'n':
  1697.     *txtptr += 3;        /* bump past NIL */
  1698.     break;
  1699.   default:
  1700.     sprintf (LOCAL->tmp,"Not an address: %.80s",*txtptr);
  1701.     mm_log (LOCAL->tmp,WARN);
  1702.     break;
  1703.   }
  1704.   return ret;
  1705. }
  1706.  
  1707. /* Mail Access Protocol parse flags
  1708.  * Accepts: current message cache
  1709.  *        current text pointer
  1710.  *
  1711.  * Updates text pointer
  1712.  */
  1713.  
  1714. void imap_parse_flags (stream,elt,txtptr)
  1715.     MAILSTREAM *stream;
  1716.     MESSAGECACHE *elt;
  1717.     char **txtptr;
  1718. {
  1719.   char *flag;
  1720.   char c;
  1721.   elt->user_flags = NIL;    /* zap old flag values */
  1722.   elt->seen = elt->deleted = elt->flagged = elt->answered = elt->recent = NIL;
  1723.   while (T) {            /* parse list of flags */
  1724.     flag = ++*txtptr;        /* point at a flag */
  1725.                 /* scan for end of flag */
  1726.     while (**txtptr != ' ' && **txtptr != ')') ++*txtptr;
  1727.     c = **txtptr;        /* save delimiter */
  1728.     **txtptr = '\0';        /* tie off flag */
  1729.     if (*flag != '\0') {    /* if flag is non-null */
  1730.       if (*flag == '\\') {    /* if starts with \ must be sys flag */
  1731.     if (!strcmp (ucase (flag),"\\SEEN")) elt->seen = T;
  1732.     else if (!strcmp (flag,"\\DELETED")) elt->deleted = T;
  1733.     else if (!strcmp (flag,"\\FLAGGED")) elt->flagged = T;
  1734.     else if (!strcmp (flag,"\\ANSWERED")) elt->answered = T;
  1735.     else if (!strcmp (flag,"\\RECENT")) elt->recent = T;
  1736.                 /* coddle TOPS-20 server */
  1737.     else if (strcmp (flag,"\\XXXX") && strcmp (flag,"\\YYYY") &&
  1738.          strncmp (flag,"\\UNDEFINEDFLAG",14)) {
  1739.       sprintf (LOCAL->tmp,"Unknown system flag: %.80s",flag);
  1740.       mm_log (LOCAL->tmp,WARN);
  1741.     }
  1742.       }
  1743.                 /* otherwise user flag */
  1744.       else imap_parse_user_flag (stream,elt,flag);
  1745.     }
  1746.     if (c == ')') break;    /* quit if end of list */
  1747.   }
  1748.   ++*txtptr;            /* bump past delimiter */
  1749.   mm_flags (stream,elt->msgno);    /* make sure top level knows */
  1750. }
  1751.  
  1752. /* Mail Access Protocol parse user flag
  1753.  * Accepts: message cache element
  1754.  *        flag name
  1755.  */
  1756.  
  1757. void imap_parse_user_flag (stream,elt,flag)
  1758.     MAILSTREAM *stream;
  1759.     MESSAGECACHE *elt;
  1760.     char *flag;
  1761. {
  1762.   long i;
  1763.                   /* sniff through all user flags */
  1764.   for (i = 0; i < NUSERFLAGS; ++i)
  1765.                   /* match this one? */
  1766.     if (!strcmp (flag,stream->user_flags[i])) {
  1767.       elt->user_flags |= 1 << i;/* yes, set the bit for that flag */
  1768.       return;            /* and quit */
  1769.     }
  1770.   sprintf (LOCAL->tmp,"Unknown user flag: %.80s",flag);
  1771.   mm_log (LOCAL->tmp,WARN);
  1772. }
  1773.  
  1774. /* Mail Access Protocol parse string
  1775.  * Accepts: MAIL stream
  1776.  *        current text pointer
  1777.  *        parsed reply
  1778.  *        flag that it may be kept outside of free storage cache
  1779.  * Returns: string
  1780.  *
  1781.  * Updates text pointer
  1782.  */
  1783.  
  1784. char *imap_parse_string (stream,txtptr,reply,special)
  1785.     MAILSTREAM *stream;
  1786.     char **txtptr;
  1787.                   IMAPPARSEDREPLY *reply;
  1788.     long special;
  1789. {
  1790.   char *st;
  1791.   char *string = NIL;
  1792.   unsigned long i;
  1793.   char c = **txtptr;        /* sniff at first character */
  1794.                 /* ignore leading spaces */
  1795.   while (c == ' ') c = *++*txtptr;
  1796.   st = ++*txtptr;        /* remember start of string */
  1797.   switch (c) {
  1798.   case '"':            /* if quoted string */
  1799.     i = 1;            /* initial byte count */
  1800.     while (**txtptr != '"') {    /* search for end of string */
  1801.       ++i;            /* bump count */
  1802.       ++*txtptr;        /* bump pointer */
  1803.     }
  1804.     **txtptr = '\0';        /* tie off string */
  1805.     string = (char *) fs_get (i);
  1806.     strncpy (string,st,i);    /* copy the string */
  1807.     ++*txtptr;            /* bump past delimiter */
  1808.     break;
  1809.   case 'N':            /* if NIL */
  1810.   case 'n':
  1811.     ++*txtptr;            /* bump past "I" */
  1812.     ++*txtptr;            /* bump past "L" */
  1813.     break;
  1814.  
  1815.   case '{':            /* if literal string */
  1816.                 /* get size of string */
  1817.     i = imap_parse_number (stream,txtptr);
  1818.     if (special && mailgets)    /* have special routine to slurp string? */
  1819.       string = (*mailgets) (tcp_getbuffer,LOCAL->tcpstream,i);
  1820.     else {            /* must slurp into free storage */
  1821.       string = (char *) fs_get (i + 1);
  1822.       *string = '\0';        /* init in case getbuffer fails */
  1823.                 /* get the literal */
  1824.       tcp_getbuffer (LOCAL->tcpstream,i,string);
  1825.     }
  1826.     fs_give ((void **) &reply->line);
  1827.                 /* get new reply text line */
  1828.     reply->line = tcp_getline (LOCAL->tcpstream);
  1829.     if (stream->debug) mm_dlog (reply->line);
  1830.     *txtptr = reply->line;    /* set text pointer to point at it */
  1831.     break;
  1832.   default:
  1833.     sprintf (LOCAL->tmp,"Not a string: %c%.80s",c,*txtptr);
  1834.     mm_log (LOCAL->tmp,WARN);
  1835.     break;
  1836.   }
  1837.   return string;
  1838. }
  1839.  
  1840.  
  1841. /* Mail Access Protocol parse number
  1842.  * Accepts: MAIL stream
  1843.  *        current text pointer
  1844.  * Returns: number parsed
  1845.  *
  1846.  * Updates text pointer
  1847.  */
  1848.  
  1849. unsigned long imap_parse_number (stream,txtptr)
  1850.     MAILSTREAM *stream;
  1851.     char **txtptr;
  1852. {                /* parse number */
  1853.   long i = strtol (*txtptr,txtptr,10);
  1854.   if (i < 0) {            /* number valid? */
  1855.     sprintf (LOCAL->tmp,"Bad number: %ld",i);
  1856.     mm_log (LOCAL->tmp,WARN);
  1857.     i = 0;            /* make sure valid */
  1858.   }
  1859.   return (unsigned long) i;
  1860. }
  1861.  
  1862. /* Mail Access Protocol parse body structure or contents
  1863.  * Accepts: MAIL stream
  1864.  *        pointer to body pointer
  1865.  *        pointer to segment
  1866.  *        current text pointer
  1867.  *        parsed reply
  1868.  *
  1869.  * Updates text pointer, stores body
  1870.  */
  1871.  
  1872. void imap_parse_body (stream,msgno,body,seg,txtptr,reply)
  1873.     MAILSTREAM *stream;
  1874.     long msgno;
  1875.     BODY **body;
  1876.     char *seg;
  1877.                    char **txtptr;
  1878.     IMAPPARSEDREPLY *reply;
  1879. {
  1880.   char *s;
  1881.   unsigned long i;
  1882.   BODY *b;
  1883.   PART *part;
  1884.   switch (*seg++) {        /* dispatch based on type of data */
  1885.   case '\0':            /* body structure */
  1886.     mail_free_body (body);    /* flush any prior body */
  1887.                 /* instantiate and parse a new body */
  1888.     imap_parse_body_structure (stream,*body = mail_newbody (),txtptr,reply);
  1889.     break;
  1890.   case '[':            /* body section text */
  1891.     if ((!(s = strchr (seg,']'))) || s[1]) {
  1892.       sprintf (LOCAL->tmp,"Bad body index: %.80s",seg);
  1893.       mm_log (LOCAL->tmp,WARN);
  1894.       return;
  1895.     }
  1896.     *s = '\0';            /* tie off section specifier */
  1897.                 /* get the body section text */
  1898.     s = imap_parse_string (stream,txtptr,reply,msgno);
  1899.     if (!(b = *body)) {        /* must have structure first */
  1900.       mm_log ("Body contents received when body structure unknown",WARN);
  1901.       fs_give ((void **) &s);
  1902.       return;
  1903.     }
  1904.                 /* get first section number */
  1905.     if (!(seg && *seg && ((i = strtol (seg,&seg,10)) > 0))) {
  1906.       mm_log ("Bogus section number",WARN);
  1907.       fs_give ((void **) &s);
  1908.       return;
  1909.     }
  1910.  
  1911.     do {            /* multipart content? */
  1912.       if (b->type == TYPEMULTIPART) {
  1913.     part = b->contents.part;/* yes, find desired part */
  1914.     while (--i && (part = part->next));
  1915.     if (!part || (((b = &part->body)->type == TYPEMULTIPART) && !*s)) {
  1916.       mm_log ("Bad section number",WARN);
  1917.       fs_give ((void **) &s);
  1918.       return;
  1919.     }
  1920.       }
  1921.       else if (i != 1) {    /* otherwise must be section 1 */
  1922.     mm_log ("Invalid section number",WARN);
  1923.     fs_give ((void **) &s);
  1924.     return;
  1925.       }
  1926.                 /* need to go down further? */
  1927.       if (i = *seg) switch (b->type) {
  1928.       case TYPEMESSAGE:        /* embedded message, get body */
  1929.     b = b->contents.msg.body;
  1930.       case TYPEMULTIPART:    /* multipart, get next section */
  1931.     if ((*seg++ == '.') && (i = strtol (seg,&seg,10)) > 0) break;
  1932.       default:            /* bogus subpart */
  1933.     mm_log ("Invalid sub-section",WARN);
  1934.     fs_give ((void **) &s);
  1935.     return;
  1936.       }
  1937.     } while (i);
  1938.     if (b) switch (b->type) {    /* decide where the data goes based on type */
  1939.     case TYPEMULTIPART:        /* nothing to fetch with these */
  1940.       mm_log ("Textual body contents received for MULTIPART body part",WARN);
  1941.       fs_give ((void **) &s);
  1942.       return;
  1943.     case TYPEMESSAGE:        /* encapsulated message */
  1944.       fs_give ((void **) &b->contents.msg.text);
  1945.       b->contents.msg.text = s;
  1946.       break;
  1947.     case TYPETEXT:        /* textual data */
  1948.       fs_give ((void **) &b->contents.text);
  1949.       b->contents.text = (unsigned char *) s;
  1950.       break;
  1951.     default:            /* otherwise assume it is binary */
  1952.       fs_give ((void **) &b->contents.binary);
  1953.       b->contents.binary = (void *) s;
  1954.       break;
  1955.     }
  1956.     break;
  1957.   default:            /* bogon */
  1958.     sprintf (LOCAL->tmp,"Bad body fetch: %.80s",seg);
  1959.     mm_log (LOCAL->tmp,WARN);
  1960.     return;
  1961.   }
  1962. }
  1963.  
  1964. /* Mail Access Protocol parse body structure
  1965.  * Accepts: MAIL stream
  1966.  *        current text pointer
  1967.  *        parsed reply
  1968.  *        body structure to write into
  1969.  *
  1970.  * Updates text pointer
  1971.  */
  1972.  
  1973. void imap_parse_body_structure (stream,body,txtptr,reply)
  1974.     MAILSTREAM *stream;
  1975.     BODY *body;
  1976.     char **txtptr;
  1977.                      IMAPPARSEDREPLY *reply;
  1978. {
  1979.   char *s;
  1980.   PART *part = NIL;
  1981.   PARAMETER *param = NIL;
  1982.   char c = *((*txtptr)++);    /* grab first character */
  1983.                 /* ignore leading spaces */
  1984.   while (c == ' ') c = *((*txtptr)++);
  1985.   switch (c) {            /* dispatch on first character */
  1986.   case '(':            /* body structure list */
  1987.     if (**txtptr == '(') {    /* multipart body? */
  1988.       body->type= TYPEMULTIPART;/* yes, set its type */
  1989.       do {            /* instantiate new body part */
  1990.     if (part) part = part->next = mail_newbody_part ();
  1991.     else body->contents.part = part = mail_newbody_part ();
  1992.                 /* parse it */
  1993.     imap_parse_body_structure (stream,&part->body,txtptr,reply);
  1994.       } while (**txtptr == '(');/* for each body part */
  1995.       if (!(body->subtype = imap_parse_string (stream,txtptr,reply,(long)NIL)))
  1996.     mm_log ("Missing multipart subtype",WARN);
  1997.       if (**txtptr != ')') {    /* validate ending */
  1998.     sprintf (LOCAL->tmp,"Junk at end of multipart body: %.80s",*txtptr);
  1999.     mm_log (LOCAL->tmp,WARN);
  2000.       }
  2001.       else ++*txtptr;        /* skip past delimiter */
  2002.     }
  2003.  
  2004.     else {            /* not multipart, parse type name */
  2005.       if (**txtptr == ')') {    /* empty body? */
  2006.     ++*txtptr;        /* bump past it */
  2007.     break;            /* and punt */
  2008.       }
  2009.       body->type = TYPEOTHER;    /* assume unknown type */
  2010.       body->encoding = ENCOTHER;/* and unknown encoding */
  2011.                 /* parse type */
  2012.       if (s = imap_parse_string (stream,txtptr,reply,(long) NIL)) {
  2013.     ucase (s);        /* make parse easier */
  2014.     switch (*s) {        /* dispatch based on type */
  2015.     case 'A':        /* APPLICATION or AUDIO */
  2016.       if (!strcmp (s+1,"PPLICATION")) body->type = TYPEAPPLICATION;
  2017.       else if (!strcmp (s+1,"UDIO")) body->type = TYPEAUDIO;
  2018.       break;
  2019.     case 'I':        /* IMAGE */
  2020.       if (!strcmp (s+1,"MAGE")) body->type = TYPEIMAGE;
  2021.       break;
  2022.     case 'M':        /* MESSAGE */
  2023.       if (!strcmp (s+1,"ESSAGE")) body->type = TYPEMESSAGE;
  2024.       break;
  2025.     case 'T':        /* TEXT */
  2026.       if (!strcmp (s+1,"EXT")) body->type = TYPETEXT;
  2027.       break;
  2028.     case 'V':        /* VIDEO */
  2029.       if (!strcmp (s+1,"IDEO")) body->type = TYPEVIDEO;
  2030.       break;
  2031.     default:
  2032.       break;
  2033.     }
  2034.     fs_give ((void **) &s);    /* flush the string */
  2035.       }
  2036.                 /* parse subtype */
  2037.       body->subtype = imap_parse_string (stream,txtptr,reply,(long) NIL);
  2038.  
  2039.       body->parameter = NIL;    /* init parameter list */
  2040.  
  2041.       c = *(*txtptr)++;        /* sniff at first character */
  2042.                 /* ignore leading spaces */
  2043.       while (c == ' ') c = *(*txtptr)++;
  2044.                 /* parse parameter list */
  2045.       if (c == '(') while (c != ')') {
  2046.     if (body->parameter)    /* append new parameter to tail */
  2047.       param = param->next = mail_newbody_parameter ();
  2048.     else body->parameter = param = mail_newbody_parameter ();
  2049.     if (!(param->attribute = imap_parse_string (stream,txtptr,reply,
  2050.                             (long) NIL))){
  2051.       mm_log ("Missing parameter attribute",WARN);
  2052.       break;
  2053.     }
  2054.     if (!(param->value = imap_parse_string (stream,txtptr,reply,
  2055.                         (long) NIL))) {
  2056.       sprintf (LOCAL->tmp,"Missing value for parameter %.80s",
  2057.            param->attribute);
  2058.       mm_log (LOCAL->tmp,WARN);
  2059.       break;
  2060.     }
  2061.     switch (c = **txtptr) {    /* see what comes after */
  2062.     case ' ':        /* flush whitespace */
  2063.       while ((c = *++*txtptr) == ' ');
  2064.       break;
  2065.     case ')':        /* end of attribute/value pairs */
  2066.       ++*txtptr;        /* skip past closing paren */
  2067.       break;
  2068.     default:
  2069.       sprintf (LOCAL->tmp,"Junk at end of parameter: %.80s",s);
  2070.       mm_log (LOCAL->tmp,WARN);
  2071.       break;
  2072.     }
  2073.       }
  2074.       else {            /* empty parameter, must be NIL */
  2075.     if (((c == 'N') || (c == 'n')) &&
  2076.         ((*(s = *txtptr) == 'I') || (*s == 'i')) &&
  2077.         ((s[1] == 'L') || (s[1] == 'l')) && (s[2] == ' ')) *txtptr += 2;
  2078.     else {
  2079.       sprintf (LOCAL->tmp,"Bogus body parameter: %c%.80s",c,s);
  2080.       mm_log (LOCAL->tmp,WARN);
  2081.       break;
  2082.     }
  2083.       }
  2084.  
  2085.       body->id = imap_parse_string (stream,txtptr,reply,(long) NIL);
  2086.       body->description = imap_parse_string (stream,txtptr,reply,(long) NIL);
  2087.       if (s = imap_parse_string (stream,txtptr,reply,(long) NIL)) {
  2088.     ucase (s);        /* make parse easier */
  2089.     switch (*s) {        /* dispatch based on encoding */
  2090.     case '7':        /* 7BIT */
  2091.       if (!strcmp (s+1,"BIT")) body->encoding = ENC7BIT;
  2092.       break;
  2093.     case '8':        /* 8BIT */
  2094.       if (!strcmp (s+1,"BIT")) body->encoding = ENC8BIT;
  2095.       break;
  2096.     case 'B':        /* BASE64 or BINARY */
  2097.       if (!strcmp (s+1,"ASE64")) body->encoding = ENCBASE64;
  2098.       else if (!strcmp (s,"INARY")) body->encoding = ENCBINARY;
  2099.       break;
  2100.     case 'Q':        /* QUOTED-PRINTABLE */
  2101.       if (!strcmp (s+1,"UOTED-PRINTABLE"))
  2102.         body->encoding = ENCQUOTEDPRINTABLE;
  2103.       break;
  2104.     default:
  2105.       break;
  2106.     }
  2107.     fs_give ((void **) &s);    /* flush the string */
  2108.       }
  2109.                 /* parse size of contents in bytes */
  2110.       body->size.bytes = imap_parse_number (stream,txtptr);
  2111.       switch (body->type) {    /* possible extra stuff */
  2112.       case TYPEMESSAGE:        /* message envelope and body */
  2113.     if (strcmp (body->subtype,"RFC822")) break;
  2114.     imap_parse_envelope (stream,&body->contents.msg.env,txtptr,reply);
  2115.     body->contents.msg.body = mail_newbody ();
  2116.     imap_parse_body_structure(stream,body->contents.msg.body,txtptr,reply);
  2117.                 /* drop into text case */
  2118.       case TYPETEXT:        /* size in lines */
  2119.     body->size.lines = imap_parse_number (stream,txtptr);
  2120.     break;
  2121.       default:            /* otherwise nothing special */
  2122.     break;
  2123.       }
  2124.       if (**txtptr != ')') {    /* validate ending */
  2125.     sprintf (LOCAL->tmp,"Junk at end of body part: %.80s",*txtptr);
  2126.     mm_log (LOCAL->tmp,WARN);
  2127.       }
  2128.       else ++*txtptr;        /* skip past delimiter */
  2129.     }
  2130.     break;
  2131.  
  2132.   case 'N':            /* if NIL */
  2133.   case 'n':
  2134.     ++*txtptr;            /* bump past "I" */
  2135.     ++*txtptr;            /* bump past "L" */
  2136.     break;
  2137.   default:            /* otherwise quite bogus */
  2138.     sprintf (LOCAL->tmp,"Bogus body structure: %.80s",*txtptr);
  2139.     mm_log (LOCAL->tmp,WARN);
  2140.     break;
  2141.   }
  2142. }
  2143.