imap4r1.c
上传用户:ycwykj01
上传日期:2007-01-04
资源大小:1819k
文件大小:129k
源码类别:

网络编程

开发平台:

Unix_Linux

  1. /*
  2.  * Program: Interactive Message Access Protocol 4rev1 (IMAP4R1) 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: 28 October 1999
  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 1999 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. #include <ctype.h>
  44. #include <stdio.h>
  45. #include <time.h>
  46. #include "mail.h"
  47. #include "osdep.h"
  48. #include "imap4r1.h"
  49. #include "rfc822.h"
  50. #include "misc.h"
  51. static char *hdrheader = "BODY.PEEK[HEADER.FIELDS (Path Message-ID";
  52. static char *hdrtrailer ="Newsgroups Followup-To References)]";
  53. static char *allheader = "(UID ENVELOPE";
  54. static char *fasttrailer = "INTERNALDATE RFC822.SIZE FLAGS)";
  55. /* Driver dispatch used by MAIL */
  56. DRIVER imapdriver = {
  57.   "imap", /* driver name */
  58. /* driver flags */
  59.   DR_MAIL|DR_NEWS|DR_NAMESPACE|DR_CRLF|DR_RECYCLE,
  60.   (DRIVER *) NIL, /* next driver */
  61.   imap_valid, /* mailbox is valid for us */
  62.   imap_parameters, /* manipulate parameters */
  63.   imap_scan, /* scan mailboxes */
  64.   imap_list, /* find mailboxes */
  65.   imap_lsub, /* find subscribed mailboxes */
  66.   imap_subscribe, /* subscribe to mailbox */
  67.   imap_unsubscribe, /* unsubscribe from mailbox */
  68.   imap_create, /* create mailbox */
  69.   imap_delete, /* delete mailbox */
  70.   imap_rename, /* rename mailbox */
  71.   imap_status, /* status of mailbox */
  72.   imap_open, /* open mailbox */
  73.   imap_close, /* close mailbox */
  74.   imap_fast, /* fetch message "fast" attributes */
  75.   imap_flags, /* fetch message flags */
  76.   imap_overview, /* fetch overview */
  77.   imap_structure, /* fetch message envelopes */
  78.   NIL, /* fetch message header */
  79.   NIL, /* fetch message body */
  80.   imap_msgdata, /* fetch partial message */
  81.   imap_uid, /* unique identifier */
  82.   imap_msgno, /* message number */
  83.   imap_flag, /* modify flags */
  84.   NIL, /* per-message modify flags */
  85.   imap_search, /* search for message based on criteria */
  86.   imap_sort, /* sort messages */
  87.   imap_thread, /* thread messages */
  88.   imap_ping, /* ping mailbox to see if still alive */
  89.   imap_check, /* check for new messages */
  90.   imap_expunge, /* expunge deleted messages */
  91.   imap_copy, /* copy messages to another mailbox */
  92.   imap_append, /* append string message to mailbox */
  93.   imap_gc /* garbage collect stream */
  94. };
  95. /* prototype stream */
  96. MAILSTREAM imapproto = {&imapdriver};
  97. /* driver parameters */
  98. static unsigned long imap_maxlogintrials = MAXLOGINTRIALS;
  99. static long imap_lookahead = IMAPLOOKAHEAD;
  100. static long imap_uidlookahead = IMAPUIDLOOKAHEAD;
  101. static long imap_defaultport = 0;
  102. static long imap_altport = 0;
  103. static char *imap_altname = NIL;
  104. static long imap_prefetch = IMAPLOOKAHEAD;
  105. static long imap_closeonerror = NIL;
  106. static imapenvelope_t imap_envelope = NIL;
  107. static imapreferral_t imap_referral = NIL;
  108. static char *imap_extrahdrs = NIL;
  109. /* IMAP validate mailbox
  110.  * Accepts: mailbox name
  111.  * Returns: our driver if name is valid, NIL otherwise
  112.  */
  113. DRIVER *imap_valid (char *name)
  114. {
  115.   return mail_valid_net (name,&imapdriver,NIL,NIL);
  116. }
  117. /* IMAP manipulate driver parameters
  118.  * Accepts: function code
  119.  *     function-dependent value
  120.  * Returns: function-dependent return value
  121.  */
  122. void *imap_parameters (long function,void *value)
  123. {
  124.   switch ((int) function) {
  125.   case SET_NAMESPACE:
  126.     fatal ("SET_NAMESPACE not permitted");
  127.   case GET_NAMESPACE:
  128.     if (((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->use_namespace &&
  129. !((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->namespace)
  130.       imap_send (((MAILSTREAM *) value),"NAMESPACE",NIL);
  131.     value = (void *) &((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->namespace;
  132.     break;
  133.   case GET_THREADERS:
  134.     value = (void *) ((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->threader;
  135.     break;
  136.   case SET_MAXLOGINTRIALS:
  137.     imap_maxlogintrials = (long) value;
  138.     break;
  139.   case GET_MAXLOGINTRIALS:
  140.     value = (void *) imap_maxlogintrials;
  141.     break;
  142.   case SET_LOOKAHEAD:
  143.     imap_lookahead = (long) value;
  144.     break;
  145.   case GET_LOOKAHEAD:
  146.     value = (void *) imap_lookahead;
  147.     break;
  148.   case SET_UIDLOOKAHEAD:
  149.     imap_uidlookahead = (long) value;
  150.     break;
  151.   case GET_UIDLOOKAHEAD:
  152.     value = (void *) imap_uidlookahead;
  153.     break;
  154.   case SET_IMAPPORT:
  155.     imap_defaultport = (long) value;
  156.     break;
  157.   case GET_IMAPPORT:
  158.     value = (void *) imap_defaultport;
  159.     break;
  160.   case SET_ALTIMAPPORT:
  161.     imap_altport = (long) value;
  162.     break;
  163.   case GET_ALTIMAPPORT:
  164.     value = (void *) imap_altport;
  165.     break;
  166.   case SET_ALTIMAPNAME:
  167.     imap_altname = (char *) value;
  168.     break;
  169.   case GET_ALTIMAPNAME:
  170.     value = (void *) imap_altname;
  171.     break;
  172.   case SET_PREFETCH:
  173.     imap_prefetch = (long) value;
  174.     break;
  175.   case GET_PREFETCH:
  176.     value = (void *) imap_prefetch;
  177.     break;
  178.   case SET_CLOSEONERROR:
  179.     imap_closeonerror = (long) value;
  180.     break;
  181.   case GET_CLOSEONERROR:
  182.     value = (void *) imap_closeonerror;
  183.     break;
  184.   case SET_IMAPENVELOPE:
  185.     imap_envelope = (imapenvelope_t) value;
  186.     break;
  187.   case GET_IMAPENVELOPE:
  188.     value = (void *) imap_envelope;
  189.     break;
  190.   case SET_IMAPREFERRAL:
  191.     imap_referral = (imapreferral_t) value;
  192.     break;
  193.   case GET_IMAPREFERRAL:
  194.     value = (void *) imap_referral;
  195.     break;
  196.   case SET_IMAPEXTRAHEADERS:
  197.     imap_extrahdrs = (char *) value;
  198.     break;
  199.   case GET_IMAPEXTRAHEADERS:
  200.     value = (void *) imap_extrahdrs;
  201.     break;
  202.   default:
  203.     value = NIL; /* error case */
  204.     break;
  205.   }
  206.   return value;
  207. }
  208. /* IMAP scan mailboxes
  209.  * Accepts: mail stream
  210.  *     reference
  211.  *     pattern to search
  212.  *     string to scan
  213.  */
  214. void imap_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
  215. {
  216.   imap_list_work (stream,"SCAN",ref,pat,contents);
  217. }
  218. /* IMAP list mailboxes
  219.  * Accepts: mail stream
  220.  *     reference
  221.  *     pattern to search
  222.  */
  223. void imap_list (MAILSTREAM *stream,char *ref,char *pat)
  224. {
  225.   imap_list_work (stream,"LIST",ref,pat,NIL);
  226. }
  227. /* IMAP list subscribed mailboxes
  228.  * Accepts: mail stream
  229.  *     reference
  230.  *     pattern to search
  231.  */
  232. void imap_lsub (MAILSTREAM *stream,char *ref,char *pat)
  233. {
  234.   void *sdb = NIL;
  235.   char *s,mbx[MAILTMPLEN];
  236. /* do it on the server */
  237.   imap_list_work (stream,"LSUB",ref,pat,NIL);
  238.   if (*pat == '{') { /* if remote pattern, must be IMAP */
  239.     if (!imap_valid (pat)) return;
  240.     ref = NIL; /* good IMAP pattern, punt reference */
  241.   }
  242. /* if remote reference, must be valid IMAP */
  243.   if (ref && (*ref == '{') && !imap_valid (ref)) return;
  244. /* kludgy application of reference */
  245.   if (ref && *ref) sprintf (mbx,"%s%s",ref,pat);
  246.   else strcpy (mbx,pat);
  247.   if (s = sm_read (&sdb)) do if (imap_valid (s) && pmatch (s,mbx))
  248.     mm_lsub (stream,NIL,s,NIL);
  249.   while (s = sm_read (&sdb)); /* until no more subscriptions */
  250. }
  251. /* IMAP find list of mailboxes
  252.  * Accepts: mail stream
  253.  *     list command
  254.  *     reference
  255.  *     pattern to search
  256.  *     string to scan
  257.  */
  258. void imap_list_work (MAILSTREAM *stream,char *cmd,char *ref,char *pat,
  259.      char *contents)
  260. {
  261.   MAILSTREAM *st = stream;
  262.   int pl;
  263.   char *s,prefix[MAILTMPLEN],mbx[MAILTMPLEN];
  264.   IMAPARG *args[4],aref,apat,acont;
  265.   if (ref && *ref) { /* have a reference? */
  266.     if (!(imap_valid (ref) && /* make sure valid IMAP name and open stream */
  267.   ((stream && LOCAL && LOCAL->netstream) ||
  268.    (stream = mail_open (NIL,ref,OP_HALFOPEN|OP_SILENT))))) return;
  269. /* calculate prefix length */
  270.     pl = strchr (ref,'}') + 1 - ref;
  271.     strncpy (prefix,ref,pl); /* build prefix */
  272.     prefix[pl] = ''; /* tie off prefix */
  273.     ref += pl; /* update reference */
  274.   }
  275.   else {
  276.     if (!(imap_valid (pat) && /* make sure valid IMAP name and open stream */
  277.   ((stream && LOCAL && LOCAL->netstream) ||
  278.    (stream = mail_open (NIL,pat,OP_HALFOPEN|OP_SILENT))))) return;
  279. /* calculate prefix length */
  280.     pl = strchr (pat,'}') + 1 - pat;
  281.     strncpy (prefix,pat,pl); /* build prefix */
  282.     prefix[pl] = ''; /* tie off prefix */
  283.     pat += pl; /* update reference */
  284.   }
  285.   LOCAL->prefix = prefix; /* note prefix */
  286.   if (contents) { /* want to do a scan? */
  287.     if (LOCAL->use_scan) {
  288.       args[0] = &aref; args[1] = &apat; args[2] = &acont; args[3] = NIL;
  289.       aref.type = ASTRING; aref.text = (void *) (ref ? ref : "");
  290.       apat.type = LISTMAILBOX; apat.text = (void *) pat;
  291.       acont.type = ASTRING; acont.text = (void *) contents;
  292.       imap_send (stream,cmd,args);
  293.     }
  294.     else mm_log ("Scan not valid on this IMAP server",ERROR);
  295.   }
  296.   else if (LEVELIMAP4 (stream)){/* easy if IMAP4 */
  297.     args[0] = &aref; args[1] = &apat; args[2] = NIL;
  298.     aref.type = ASTRING; aref.text = (void *) (ref ? ref : "");
  299.     apat.type = LISTMAILBOX; apat.text = (void *) pat;
  300. /* referrals armed? */
  301.     if (LOCAL->use_mbx_ref && mail_parameters (stream,GET_IMAPREFERRAL,NIL) &&
  302.       ((cmd[0] == 'L') || (cmd[0] == 'l')) && !cmd[4]) {
  303. /* yes, convert LIST -> RLIST */
  304.       if (((cmd[1] == 'I') || (cmd[1] == 'i')) &&
  305.   ((cmd[2] == 'S') || (cmd[1] == 's')) &&
  306.   ((cmd[3] == 'T') || (cmd[3] == 't'))) cmd = "RLIST";
  307. /* and convert LSUB -> RLSUB */
  308.       else if (((cmd[1] == 'S') || (cmd[1] == 's')) &&
  309.        ((cmd[2] == 'U') || (cmd[1] == 'u')) &&
  310.        ((cmd[3] == 'B') || (cmd[3] == 'b'))) cmd = "RLSUB";
  311.     }
  312.     imap_send (stream,cmd,args);
  313.   }
  314.   else if (LEVEL1176 (stream)) {/* convert to IMAP2 format wildcard */
  315. /* kludgy application of reference */
  316.     if (ref && *ref) sprintf (mbx,"%s%s",ref,pat);
  317.     else strcpy (mbx,pat);
  318.     for (s = mbx; *s; s++) if (*s == '%') *s = '*';
  319.     args[0] = &apat; args[1] = NIL;
  320.     apat.type = LISTMAILBOX; apat.text = (void *) mbx;
  321.     if (!(strstr (cmd,"LIST") &&/* if list, try IMAP2bis, then RFC-1176 */
  322.   strcmp (imap_send (stream,"FIND ALL.MAILBOXES",args)->key,"BAD")) &&
  323. !strcmp (imap_send (stream,"FIND MAILBOXES",args)->key,"BAD"))
  324.       LOCAL->rfc1176 = NIL; /* must be RFC-1064 */
  325.   }
  326.   LOCAL->prefix = NIL; /* no more prefix */
  327. /* close temporary stream if we made one */
  328.   if (stream != st) mail_close (stream);
  329. }
  330. /* IMAP subscribe to mailbox
  331.  * Accepts: mail stream
  332.  *     mailbox to add to subscription list
  333.  * Returns: T on success, NIL on failure
  334.  */
  335. long imap_subscribe (MAILSTREAM *stream,char *mailbox)
  336. {
  337.   MAILSTREAM *st = stream;
  338.   long ret = ((stream && LOCAL && LOCAL->netstream) ||
  339.       (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT))) ?
  340. imap_manage (stream,mailbox,LEVELIMAP4 (stream) ?
  341.      "Subscribe" : "Subscribe Mailbox",NIL) : NIL;
  342. /* toss out temporary stream */
  343.   if (st != stream) mail_close (stream);
  344.   return ret;
  345. }
  346. /* IMAP unsubscribe to mailbox
  347.  * Accepts: mail stream
  348.  *     mailbox to delete from manage list
  349.  * Returns: T on success, NIL on failure
  350.  */
  351. long imap_unsubscribe (MAILSTREAM *stream,char *mailbox)
  352. {
  353.   MAILSTREAM *st = stream;
  354.   long ret = ((stream && LOCAL && LOCAL->netstream) ||
  355.       (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT))) ?
  356. imap_manage (stream,mailbox,LEVELIMAP4 (stream) ?
  357.      "Unsubscribe" : "Unsubscribe Mailbox",NIL) : NIL;
  358. /* toss out temporary stream */
  359.   if (st != stream) mail_close (stream);
  360.   return ret;
  361. }
  362. /* IMAP create mailbox
  363.  * Accepts: mail stream
  364.  *     mailbox name to create
  365.  * Returns: T on success, NIL on failure
  366.  */
  367. long imap_create (MAILSTREAM *stream,char *mailbox)
  368. {
  369.   return imap_manage (stream,mailbox,"Create",NIL);
  370. }
  371. /* IMAP delete mailbox
  372.  * Accepts: mail stream
  373.  *     mailbox name to delete
  374.  * Returns: T on success, NIL on failure
  375.  */
  376. long imap_delete (MAILSTREAM *stream,char *mailbox)
  377. {
  378.   return imap_manage (stream,mailbox,"Delete",NIL);
  379. }
  380. /* IMAP rename mailbox
  381.  * Accepts: mail stream
  382.  *     old mailbox name
  383.  *     new mailbox name
  384.  * Returns: T on success, NIL on failure
  385.  */
  386. long imap_rename (MAILSTREAM *stream,char *old,char *newname)
  387. {
  388.   return imap_manage (stream,old,"Rename",newname);
  389. }
  390. /* IMAP manage a mailbox
  391.  * Accepts: mail stream
  392.  *     mailbox to manipulate
  393.  *     command to execute
  394.  *     optional second argument
  395.  * Returns: T on success, NIL on failure
  396.  */
  397. long imap_manage (MAILSTREAM *stream,char *mailbox,char *command,char *arg2)
  398. {
  399.   MAILSTREAM *st = stream;
  400.   IMAPPARSEDREPLY *reply;
  401.   long ret = NIL;
  402.   char mbx[MAILTMPLEN],mbx2[MAILTMPLEN];
  403.   IMAPARG *args[3],ambx,amb2;
  404.   imapreferral_t ir =
  405.     (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
  406.   ambx.type = amb2.type = ASTRING; ambx.text = (void *) mbx;
  407.   amb2.text = (void *) mbx2;
  408.   args[0] = &ambx; args[1] = args[2] = NIL;
  409. /* require valid names and open stream */
  410.   if (mail_valid_net (mailbox,&imapdriver,NIL,mbx) &&
  411.       (arg2 ? mail_valid_net (arg2,&imapdriver,NIL,mbx2) : &imapdriver) &&
  412.       ((stream && LOCAL && LOCAL->netstream) ||
  413.        (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT)))) {
  414.     if (arg2) args[1] = &amb2; /* second arg present? */
  415.     if (!(ret = (imap_OK (stream,reply = imap_send (stream,command,args)))) &&
  416. ir && LOCAL->referral) {
  417.       long code = -1;
  418.       switch (*command) { /* which command was it? */
  419.       case 'S': code = REFSUBSCRIBE; break;
  420.       case 'U': code = REFUNSUBSCRIBE; break;
  421.       case 'C': code = REFCREATE; break;
  422.       case 'D': code = REFDELETE; break;
  423.       case 'R': code = REFRENAME; break;
  424.       default:
  425. fatal ("impossible referral command");
  426.       }
  427.       if ((code >= 0) && (mailbox = (*ir) (stream,LOCAL->referral,code)))
  428. ret = imap_manage (NIL,mailbox,command,(*command == 'R') ?
  429.    (mailbox + strlen (mailbox) + 1) : NIL);
  430.     }
  431.     mm_log (reply->text,ret ? NIL : ERROR);
  432. /* toss out temporary stream */
  433.     if (st != stream) mail_close (stream);
  434.   }
  435.   return ret;
  436. }
  437. /* IMAP status
  438.  * Accepts: mail stream
  439.  *     mailbox name
  440.  *     status flags
  441.  * Returns: T on success, NIL on failure
  442.  */
  443. long imap_status (MAILSTREAM *stream,char *mbx,long flags)
  444. {
  445.   IMAPARG *args[3],ambx,aflg;
  446.   char tmp[MAILTMPLEN];
  447.   NETMBX mb;
  448.   unsigned long i;
  449.   long ret = NIL;
  450.   MAILSTREAM *tstream = stream;
  451.   imapreferral_t ir =
  452.     (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
  453.   mail_valid_net_parse (mbx,&mb);
  454. /* can't use stream if not IMAP4rev1, STATUS,
  455.    or halfopen and right host */
  456.   if (stream && (!(LEVELSTATUS (stream) || stream->halfopen)
  457.  || strcmp (ucase (strcpy (tmp,imap_host (stream))),
  458.     ucase (mb.host))))
  459.     return imap_status (NIL,mbx,flags);
  460. /* make stream if don't have one */
  461.   if (!(stream || (stream = mail_open (NIL,mbx,OP_HALFOPEN|OP_SILENT))))
  462.     return NIL;
  463.   args[0] = &ambx;args[1] = NIL;/* set up first argument as mailbox */
  464.   ambx.type = ASTRING; ambx.text = (void *) mb.mailbox;
  465.   if (LEVELSTATUS (stream)) { /* have STATUS command? */
  466.     aflg.type = FLAGS; aflg.text = (void *) tmp;
  467.     args[1] = &aflg; args[2] = NIL;
  468.     tmp[0] = tmp[1] = ''; /* build flag list */
  469.     if (flags & SA_MESSAGES) strcat (tmp," MESSAGES");
  470.     if (flags & SA_RECENT) strcat (tmp," RECENT");
  471.     if (flags & SA_UNSEEN) strcat (tmp," UNSEEN");
  472.     if (flags & SA_UIDNEXT) strcat (tmp,LEVELIMAP4rev1 (stream) ?
  473.     " UIDNEXT" : " UID-NEXT");
  474.     if (flags & SA_UIDVALIDITY) strcat (tmp,LEVELIMAP4rev1 (stream) ?
  475. " UIDVALIDITY" : " UID-VALIDITY");
  476.     tmp[0] = '(';
  477.     strcat (tmp,")");
  478. /* send "STATUS mailbox flag" */
  479.     if (imap_OK (stream,imap_send (stream,"STATUS",args))) ret = T;
  480.     else if (ir && LOCAL->referral &&
  481.      (mbx = (*ir) (stream,LOCAL->referral,REFSTATUS)))
  482.       ret = imap_status (NIL,mbx,flags);
  483.   }
  484. /* IMAP2 way */
  485.   else if (imap_OK (stream,imap_send (stream,"EXAMINE",args))) {
  486.     MAILSTATUS status;
  487.     status.flags = flags & ~ (SA_UIDNEXT | SA_UIDVALIDITY);
  488.     status.messages = stream->nmsgs;
  489.     status.recent = stream->recent;
  490.     status.unseen = 0;
  491.     if (flags & SA_UNSEEN) { /* must search to get unseen messages */
  492. /* clear search vector */
  493.       for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = NIL;
  494.       if (imap_OK (stream,imap_send (stream,"SEARCH UNSEEN",NIL)))
  495. for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++)
  496.   if (mail_elt (stream,i)->searched) status.unseen++;
  497.     }
  498.     strcpy (strchr (strcpy (tmp,stream->mailbox),'}') + 1,mb.mailbox);
  499. /* pass status to main program */
  500.     mm_status (stream,tmp,&status);
  501.     ret = T; /* note success */
  502.   }
  503.   if (stream != tstream) mail_close (stream);
  504.   return ret; /* success */
  505. }
  506. /* IMAP open
  507.  * Accepts: stream to open
  508.  * Returns: stream to use on success, NIL on failure
  509.  */
  510. MAILSTREAM *imap_open (MAILSTREAM *stream)
  511. {
  512.   unsigned long i,j;
  513.   char *s,tmp[MAILTMPLEN],usr[MAILTMPLEN];
  514.   NETMBX mb;
  515.   IMAPPARSEDREPLY *reply = NIL;
  516.   imapreferral_t ir =
  517.     (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
  518. /* return prototype for OP_PROTOTYPE call */
  519.   if (!stream) return &imapproto;
  520.   mail_valid_net_parse (stream->mailbox,&mb);
  521.   usr[0] = ''; /* initially no user name */
  522.   if (LOCAL) { /* if stream opened earlier by us */
  523.     if (LOCAL->netstream) { /* recycle if still alive */
  524.       i = stream->silent; /* temporarily mark silent */
  525.       stream->silent = T; /* don't give mm_exists() events */
  526.       j = imap_ping (stream); /* learn if stream still alive */
  527.       stream->silent = i; /* restore prior state */
  528.       if (j) { /* was stream still alive? */
  529. sprintf (tmp,"Reusing connection to %s",imap_host (stream));
  530. if (LOCAL->user) sprintf (tmp + strlen (tmp),"/user="%s"",
  531.   LOCAL->user);
  532. if (!stream->silent) mm_log (tmp,(long) NIL);
  533.       }
  534.       else imap_close (stream,NIL);
  535.     }
  536.     else imap_close (stream,NIL);
  537.   }
  538. /* copy flags from name */
  539.   if (mb.dbgflag) stream->debug = T;
  540.   if (mb.anoflag) stream->anonymous = T;
  541.   if (mb.secflag) stream->secure = T;
  542.   mb.tryaltflag = stream->tryalt;
  543.   if (!LOCAL) { /* open new connection if no recycle */
  544.     NETDRIVER *altd = (NETDRIVER *) mail_parameters (NIL,GET_ALTDRIVER,NIL);
  545.     char * alts = (char *) mail_parameters(NIL,GET_ALTIMAPNAME,NIL);
  546.     unsigned long altp =
  547.       (unsigned long) mail_parameters (NIL,GET_ALTIMAPPORT,NIL);
  548.     unsigned long defprt = imap_defaultport ? imap_defaultport : IMAPTCPPORT;
  549.     stream->local = /* instantiate localdata */
  550.       (void *) memset (fs_get (sizeof (IMAPLOCAL)),0,sizeof (IMAPLOCAL));
  551. /* assume IMAP2bis server */
  552.     LOCAL->imap2bis = LOCAL->rfc1176 = T;
  553. /* just do this if rimap not allowed */
  554.     if (stream->anonymous || mb.port || mb.altflag)
  555.       reply = imap_tcp (stream,&mb,NIL,defprt,altd,alts,altp);
  556. /* try secure rimap */
  557.     else if (!(reply = imap_rimap (stream,"*imap",&mb,usr,tmp))) {
  558. /* no secure rimap, need to do tryalt? */
  559.       if ((mb.tryaltflag || mail_parameters (NIL,GET_TRYALTFIRST,NIL)) &&
  560.   altd && (reply = imap_tcp (stream,&mb,NIL,0,altd,alts,altp)))
  561. mb.altflag = T; /* tryalt succeeded, light altflag */
  562. /* no alt, try rimap first, then TCP */
  563.       else if (!(reply = imap_rimap (stream,"imap",&mb,usr,tmp)))
  564. reply = imap_tcp (stream,&mb,NIL,defprt,NIL,NIL,NIL);
  565.     }
  566. /* if have a connection */
  567.     if (LOCAL->netstream && reply && imap_OK (stream,reply)) {
  568. /* if not preauthenticated */
  569.       if (strcmp (reply->key,"PREAUTH")) {
  570. /* get server capabilities */
  571. imap_send (stream,"CAPABILITY",NIL);
  572. /* remote name for authentication */
  573. strncpy (mb.host,net_remotehost (LOCAL->netstream),NETMAXHOST-1);
  574. mb.host[NETMAXHOST-1] = '';
  575. if (!(stream->anonymous ? imap_anon (stream,tmp) :
  576.       (LOCAL->use_auth ? imap_auth (stream,&mb,tmp,usr) :
  577.        imap_login (stream,&mb,tmp,usr)))) {
  578. /* failed, is there a referral? */
  579.   if (ir && LOCAL->referral &&
  580.       (s = (*ir) (stream,LOCAL->referral,REFAUTHFAILED))) {
  581.     imap_close (stream,NIL);
  582.     fs_give ((void **) &stream->mailbox);
  583.     stream->mailbox = s;/* set as new mailbox name to open */
  584.     return imap_open (stream);
  585.   }
  586.   return NIL; /* authentication failed */
  587. }
  588.       }
  589.     }
  590.     else { /* log error if there was one */
  591.       if (reply) mm_log (reply->text,ERROR);
  592.       return NIL; /* lost during greeting */
  593.     }
  594. /* get server capabilities again */
  595.     imap_send (stream,"CAPABILITY",NIL);
  596.   }
  597.   if (LOCAL->netstream) { /* still have a connection? */
  598.     if (ir && LOCAL->referral &&
  599. (s = (*ir) (stream,LOCAL->referral,REFAUTH))) {
  600.       imap_close (stream,NIL);
  601.       fs_give ((void **) &stream->mailbox);
  602.       stream->mailbox = s; /* set as new mailbox name to open */
  603.       return imap_open (stream);/* recurse to log in on real site */
  604.     }
  605.     stream->perm_seen = stream->perm_deleted = stream->perm_answered =
  606.       stream->perm_draft = LEVELIMAP4 (stream) ? NIL : T;
  607.     stream->perm_user_flags = LEVELIMAP4 (stream) ? NIL : 0xffffffff;
  608.     stream->sequence++; /* bump sequence number */
  609.     sprintf (tmp,"{%s",net_host (LOCAL->netstream));
  610.     if (!((i = net_port (LOCAL->netstream)) & 0xffff0000))
  611.       sprintf (tmp + strlen (tmp),":%lu",i);
  612.     strcat (tmp,"/imap");
  613.     if (mb.altflag) sprintf (tmp + strlen (tmp),"/%s",(char *)
  614.      mail_parameters (NIL,GET_ALTDRIVERNAME,NIL));
  615.     if (mb.secflag) strcat (tmp,"/secure");
  616.     if (stream->anonymous) strcat (tmp,"/anonymous}");
  617.     else { /* record user name */
  618.       if (!LOCAL->user && usr[0]) LOCAL->user = cpystr (usr);
  619.       if (LOCAL->user) sprintf (tmp + strlen (tmp),"/user="%s"}",
  620. LOCAL->user);
  621.     }
  622.     if (!stream->halfopen) { /* wants to open a mailbox? */
  623.       IMAPARG *args[2];
  624.       IMAPARG ambx;
  625.       ambx.type = ASTRING;
  626.       ambx.text = (void *) mb.mailbox;
  627.       args[0] = &ambx; args[1] = NIL;
  628.       if (imap_OK (stream,reply = imap_send (stream,stream->rdonly ?
  629.      "EXAMINE": "SELECT",args))) {
  630. strcat (tmp,mb.mailbox);/* mailbox name */
  631. if (!stream->nmsgs && !stream->silent)
  632.   mm_log ("Mailbox is empty",(long) NIL);
  633. /* note if an INBOX or not */
  634. stream->inbox = !strcmp (ucase (mb.mailbox),"INBOX");
  635.       }
  636.       else if (ir && LOCAL->referral &&
  637.        (s = (*ir) (stream,LOCAL->referral,REFSELECT))) {
  638. imap_close (stream,NIL);
  639. fs_give ((void **) &stream->mailbox);
  640. stream->mailbox = s; /* set as new mailbox name to open */
  641. return imap_open (stream);
  642.       }
  643.       else {
  644. mm_log (reply->text,ERROR);
  645. if (imap_closeonerror) return NIL;
  646. stream->halfopen = T; /* let him keep it half-open */
  647.       }
  648.     }
  649.     if (stream->halfopen) { /* half-open connection? */
  650.       strcat (tmp,"<no_mailbox>");
  651. /* make sure dummy message counts */
  652.       mail_exists (stream,(long) 0);
  653.       mail_recent (stream,(long) 0);
  654.     }
  655.     fs_give ((void **) &stream->mailbox);
  656.     stream->mailbox = cpystr (tmp);
  657.   }
  658. /* success if stream open */
  659.   return LOCAL->netstream ? stream : NIL;
  660. }
  661. /* IMAP TCP connect
  662.  * Accepts: MAIL stream
  663.  *     NETMBX specification
  664.  *     default network driver
  665.  *     default port (zero means do tryalt behavior)
  666.  *     alternate network driver
  667.  *     alternate driver service name
  668.  *     alternate driver port
  669.  * Returns: parsed reply if success, else NIL
  670.  */
  671. IMAPPARSEDREPLY *imap_tcp (MAILSTREAM *stream,NETMBX *mb,NETDRIVER *dv,
  672.    unsigned long port,
  673.    NETDRIVER *altd,char *alts,unsigned long altp)
  674. {
  675.   return (LOCAL->netstream =
  676.   (port ? net_open (mb,dv,port,altd,alts,altp) :
  677.    net_open_work (altd,mb->host,alts,altp,mb->port,T))) ?
  678.      imap_reply (stream,NIL) : NIL;
  679. }
  680. /* IMAP rimap connect
  681.  * Accepts: MAIL stream
  682.  *     NETMBX specification
  683.  *     service to use
  684.  *     user name
  685.  *     scratch buffer
  686.  * Returns: parsed reply if success, else NIL
  687.  */
  688. IMAPPARSEDREPLY *imap_rimap (MAILSTREAM *stream,char *service,NETMBX *mb,
  689.      char *usr,char *tmp)
  690. {
  691.   unsigned long i;
  692.   char c[2];
  693.   NETSTREAM *tstream;
  694.   IMAPPARSEDREPLY *reply = NIL;
  695. /* try rimap open */
  696.   if (tstream = net_aopen (NIL,mb,service,usr)) {
  697. /* if success, see if reasonable banner */
  698.     if (net_getbuffer (tstream,(long) 1,c) && (*c == '*')) {
  699.       i = 0; /* copy to buffer */
  700.       do tmp[i++] = *c;
  701.       while (net_getbuffer (tstream,(long) 1,c) && (*c != '15') &&
  702.      (*c != '12') && (i < (MAILTMPLEN-1)));
  703.       tmp[i] = ''; /* tie off */
  704. /* snarfed a valid greeting? */
  705.       if ((*c == '15') && net_getbuffer (tstream,(long) 1,c) &&
  706.   (*c == '12') &&
  707.   !strcmp ((reply = imap_parse_reply (stream,cpystr (tmp)))->tag,"*")){
  708. /* parse line as IMAP */
  709. imap_parse_unsolicited (stream,reply);
  710. /* make sure it looked like good stuff */
  711. if (imap_OK (stream,reply)) {
  712.   LOCAL->netstream = tstream;
  713.   return reply; /* return success */
  714. }
  715.       }
  716.     }
  717.     net_close (tstream); /* failed, punt the temporary netstream */
  718.   }
  719.   return NIL;
  720. }
  721. /* IMAP log in as anonymous
  722.  * Accepts: stream to authenticate
  723.  *     scratch buffer
  724.  * Returns: T on success, NIL on failure
  725.  */
  726. long imap_anon (MAILSTREAM *stream,char *tmp)
  727. {
  728.   IMAPPARSEDREPLY *reply;
  729.   char *s = net_localhost (LOCAL->netstream);
  730.   if (LOCAL->use_authanon) {
  731.     char tag[16];
  732.     unsigned long i;
  733.     char *broken = "IMAP connection broken (anonymous auth)";
  734.     sprintf (tag,"%08lx",stream->gensym++);
  735. /* build command */
  736.     sprintf (tmp,"%s AUTHENTICATE ANONYMOUS",tag);
  737.     if (!imap_soutr (stream,tmp)) {
  738.       mm_log (broken,ERROR);
  739.       return NIL;
  740.     }
  741.     if (imap_challenge (stream,&i)) imap_response (stream,s,strlen (s));
  742. /* get response */
  743.     if (!(reply = &LOCAL->reply)->tag)
  744.       reply = imap_fake (stream,tag,broken);
  745. /* what we wanted? */
  746.     if (strcmp (reply->tag,tag)) {
  747. /* abort if don't have tagged response */
  748.       while (strcmp ((reply = imap_reply (stream,tag))->tag,tag))
  749. imap_soutr (stream,"*");
  750.     }
  751.   }
  752.   else {
  753.     IMAPARG *args[2];
  754.     IMAPARG ausr;
  755.     ausr.type = ASTRING;
  756.     ausr.text = (void *) s;
  757.     args[0] = &ausr; args[1] = NIL;
  758. /* send "LOGIN anonymous <host>" */
  759.     reply = imap_send (stream,"LOGIN ANONYMOUS",args);
  760.   }
  761. /* success if reply OK */
  762.   if (imap_OK (stream,reply)) return T;
  763.   mm_log (reply->text,ERROR);
  764.   return NIL;
  765. }
  766. /* IMAP authenticate
  767.  * Accepts: stream to authenticate
  768.  *     parsed network mailbox structure
  769.  *     scratch buffer
  770.  *     place to return user name
  771.  * Returns: T on success, NIL on failure
  772.  */
  773. long imap_auth (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr)
  774. {
  775.   unsigned long trial,ua;
  776.   int ok;
  777.   char tag[16];
  778.   char *lsterr = NIL;
  779.   AUTHENTICATOR *at;
  780.   IMAPPARSEDREPLY *reply;
  781.   for (ua = LOCAL->use_auth; LOCAL->netstream && ua &&
  782.        (at = mail_lookup_auth (find_rightmost_bit (&ua) + 1));) {
  783.     if (lsterr) { /* previous authenticator failed? */
  784.       sprintf(tmp,"Retrying using %s authentication after %s",at->name,lsterr);
  785.       mm_log (tmp,NIL);
  786.       fs_give ((void **) &lsterr);
  787.     }
  788.     trial = 0; /* initial trial count */
  789.     tmp[0] = ''; /* no error */
  790.     do { /* gensym a new tag */
  791.       if (tmp[0]) mm_log (tmp,WARN);
  792.       sprintf (tag,"%08lx",stream->gensym++);
  793. /* build command */
  794.       sprintf (tmp,"%s AUTHENTICATE %s",tag,at->name);
  795.       if (imap_soutr (stream,tmp)) {
  796. ok = (*at->client) (imap_challenge,imap_response,mb,stream,&trial,usr);
  797. /* get response */
  798. if (!(reply = &LOCAL->reply)->tag)
  799.   reply=imap_fake (stream,tag,"IMAP connection broken (authenticate)");
  800. /* what we wanted? */
  801. if (strcmp (reply->tag,tag)) {
  802. /* abort if don't have tagged response */
  803.   while (strcmp ((reply = imap_reply (stream,tag))->tag,tag))
  804.     imap_soutr (stream,"*");
  805. }
  806. /* cancel any last error */
  807. if (lsterr) fs_give ((void **) &lsterr);
  808. /* done if got success response */
  809. if (ok && imap_OK (stream,reply)) return T;
  810. lsterr = cpystr (reply->text);
  811. sprintf (tmp,"Retrying %s authentication after %s",at->name,lsterr);
  812.       }
  813.     }
  814.     while (LOCAL->netstream && !LOCAL->byeseen && trial &&
  815.    (trial < imap_maxlogintrials));
  816.   }
  817.   if (lsterr) { /* previous authenticator failed? */
  818.     sprintf (tmp,"Can not authenticate to IMAP server: %s",lsterr);
  819.     mm_log (tmp,ERROR);
  820.     fs_give ((void **) &lsterr);
  821.   }
  822.   return NIL; /* ran out of authenticators */
  823. }
  824. /* IMAP login
  825.  * Accepts: stream to login
  826.  *     parsed network mailbox structure
  827.  *     scratch buffer
  828.  *     place to return user name
  829.  * Returns: T on success, NIL on failure
  830.  */
  831. long imap_login (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr)
  832. {
  833.   unsigned long trial = 0;
  834.   IMAPPARSEDREPLY *reply;
  835.   IMAPARG *args[3];
  836.   IMAPARG ausr,apwd;
  837.   if (stream->secure) { /* never do LOGIN if want security */
  838.     mm_log ("Can't do secure authentication with this server",ERROR);
  839.     return NIL;
  840.   }
  841.   ausr.type = apwd.type = ASTRING;
  842.   ausr.text = (void *) usr;
  843.   apwd.text = (void *) tmp;
  844.   args[0] = &ausr; args[1] = &apwd; args[2] = NIL;
  845.   while (LOCAL->netstream && !LOCAL->byeseen && !LOCAL->referral &&
  846.  (trial < imap_maxlogintrials)) {
  847.     tmp[0] = 0; /* prompt user for password */
  848.     mm_login (mb,usr,tmp,trial++);
  849.     if (!tmp[0]) { /* user refused to give a password */
  850.       mm_log ("Login aborted",ERROR);
  851.       return NIL;
  852.     }
  853. /* send "LOGIN usr tmp" */
  854.     if (imap_OK (stream,reply = imap_send (stream,"LOGIN",args))) return T;
  855.     mm_log (reply->text,WARN);
  856.   }
  857.   if (!LOCAL->referral) mm_log ("Too many login failures",ERROR);
  858.   return NIL;
  859. }
  860. /* Get challenge to authenticator in binary
  861.  * Accepts: stream
  862.  *     pointer to returned size
  863.  * Returns: challenge or NIL if not challenge
  864.  */
  865. void *imap_challenge (void *s,unsigned long *len)
  866. {
  867.   MAILSTREAM *stream = (MAILSTREAM *) s;
  868.   IMAPPARSEDREPLY *reply;
  869.   while (LOCAL->netstream) { /* parse reply from server */
  870.     if (reply = imap_parse_reply (stream,net_getline (LOCAL->netstream))) {
  871. /* received challenge? */
  872.       if (!strcmp (reply->tag,"+"))
  873. return rfc822_base64 ((unsigned char *) reply->text,
  874.       strlen (reply->text),len);
  875. /* untagged data? */
  876.       else if (!strcmp (reply->tag,"*")) imap_parse_unsolicited (stream,reply);
  877.       else break; /* tagged response */
  878.     }
  879.   }
  880.   return NIL; /* tagged response, bogon, or lost stream */
  881. }
  882. /* Send authenticator response in BASE64
  883.  * Accepts: MAIL stream
  884.  *     string to send
  885.  *     length of string
  886.  * Returns: T if successful, else NIL
  887.  */
  888. long imap_response (void *s,char *response,unsigned long size)
  889. {
  890.   MAILSTREAM *stream = (MAILSTREAM *) s;
  891.   unsigned long i,j,ret;
  892.   char *t,*u;
  893.   if (response) { /* make CRLFless BASE64 string */
  894.     if (size) {
  895.       for (t = (char *) rfc822_binary ((void *) response,size,&i),u = t,j = 0;
  896.    j < i; j++) if (t[j] > ' ') *u++ = t[j];
  897.       *u = ''; /* tie off string for mm_dlog() */
  898.       if (stream->debug) mm_dlog (t);
  899. /* append CRLF */
  900.       *u++ = '15'; *u++ = '12';
  901.       ret = net_sout (LOCAL->netstream,t,u - t);
  902.       fs_give ((void **) &t);
  903.     }
  904.     else ret = imap_soutr (stream,"");
  905.   }
  906. /* abort requested */
  907.   else ret = imap_soutr (stream,"*");
  908.   return ret;
  909. }
  910. /* IMAP close
  911.  * Accepts: MAIL stream
  912.  *     option flags
  913.  */
  914. void imap_close (MAILSTREAM *stream,long options)
  915. {
  916.   THREADER *thr,*t;
  917.   IMAPPARSEDREPLY *reply;
  918.   if (stream && LOCAL) { /* send "LOGOUT" */
  919.     if (!LOCAL->byeseen) { /* don't even think of doing it if saw a BYE */
  920. /* expunge silently if requested */
  921.       if (options & CL_EXPUNGE) imap_send (stream,"EXPUNGE",NIL);
  922.       if (LOCAL->netstream &&
  923.   !imap_OK (stream,reply = imap_send (stream,"LOGOUT",NIL)))
  924. mm_log (reply->text,WARN);
  925.     }
  926. /* close NET connection if still open */
  927.     if (LOCAL->netstream) net_close (LOCAL->netstream);
  928.     LOCAL->netstream = NIL;
  929. /* free up memory */
  930.     if (LOCAL->sortdata) fs_give ((void **) &LOCAL->sortdata);
  931.     if (LOCAL->namespace) {
  932.       mail_free_namespace (&LOCAL->namespace[0]);
  933.       mail_free_namespace (&LOCAL->namespace[1]);
  934.       mail_free_namespace (&LOCAL->namespace[2]);
  935.       fs_give ((void **) &LOCAL->namespace);
  936.     }
  937.     if (LOCAL->threaddata) mail_free_threadnode (&LOCAL->threaddata);
  938.     if (thr = LOCAL->threader) {/* flush threaders */
  939.       while (t = thr) {
  940. fs_give ((void **) &t->name);
  941. thr = t->next;
  942. fs_give ((void **) &t);
  943.       }
  944.     }
  945.     if (LOCAL->referral) fs_give ((void **) &LOCAL->referral);
  946.     if (LOCAL->user) fs_give ((void **) &LOCAL->user);
  947.     if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
  948. /* nuke the local data */
  949.     fs_give ((void **) &stream->local);
  950.   }
  951. }
  952. /* IMAP fetch fast information
  953.  * Accepts: MAIL stream
  954.  *     sequence
  955.  *     option flags
  956.  *
  957.  * Generally, imap_fetchstructure is preferred
  958.  */
  959. void imap_fast (MAILSTREAM *stream,char *sequence,long flags)
  960. { /* send "FETCH sequence FAST" */
  961.   char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH";
  962.   IMAPPARSEDREPLY *reply;
  963.   IMAPARG *args[4],aseq,aatt[2];
  964.   aseq.type = SEQUENCE; aseq.text = (void *) sequence;
  965.   args[0] = &aseq; args[1] = &aatt[0];
  966.   if (LEVELIMAP4 (stream)) { /* send the hairier form if IMAP4 */
  967.     aatt[0].type = aatt[1].type = ATOM;
  968.     aatt[0].text = (void *) "(UID";
  969.     aatt[1].text = (void *) fasttrailer;
  970.     args[2] = &aatt[1];
  971.     args[3] = NIL;
  972.   }
  973.   else { /* just do FETCH FAST */
  974.     aatt[0].type = ATOM;
  975.     aatt[0].text = (void *) "FAST";
  976.     args[2] = NIL;
  977.   }
  978.   if (!imap_OK (stream,reply = imap_send (stream,cmd,args)))
  979.     mm_log (reply->text,ERROR);
  980. }
  981. /* IMAP fetch flags
  982.  * Accepts: MAIL stream
  983.  *     sequence
  984.  *     option flags
  985.  */
  986. void imap_flags (MAILSTREAM *stream,char *sequence,long flags)
  987. { /* send "FETCH sequence FLAGS" */
  988.   char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH";
  989.   IMAPPARSEDREPLY *reply;
  990.   IMAPARG *args[3],aseq,aatt;
  991.   aseq.type = SEQUENCE; aseq.text = (void *) sequence;
  992.   aatt.type = ATOM; aatt.text = (void *) "FLAGS";
  993.   args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
  994.   if (!imap_OK (stream,reply = imap_send (stream,cmd,args)))
  995.     mm_log (reply->text,ERROR);
  996. }
  997. /* IMAP fetch message overview
  998.  * Accepts: mail stream
  999.  *     UID sequence to fetch
  1000.  *     pointer to overview return function
  1001.  * Returns: T if successful, NIL otherwise
  1002.  */
  1003. long imap_overview (MAILSTREAM *stream,char *sequence,overview_t ofn)
  1004. {
  1005.   MESSAGECACHE *elt;
  1006.   ENVELOPE *env;
  1007.   OVERVIEW ov;
  1008.   char *s,*t;
  1009.   unsigned long i,j,start,last,len;
  1010.   if (!mail_uid_sequence (stream,sequence) || !LOCAL->netstream) return NIL;
  1011. /* build overview sequence */
  1012.   for (i = 1,len = start = last = 0,s = t = NIL; i <= stream->nmsgs; ++i)
  1013.     if ((elt = mail_elt (stream,i))->sequence) {
  1014.       if (!elt->private.msg.env) {
  1015. if (s) { /* continuing a sequence */
  1016.   if (i == last + 1) last = i;
  1017.   else { /* end of range */
  1018.     if (last != start) sprintf (t,":%lu,%lu",last,i);
  1019.     else sprintf (t,",%lu",i);
  1020.     start = last = i; /* begin a new range */
  1021.     if ((j = ((t += strlen (t)) - s)) > (MAILTMPLEN - 20)) {
  1022.       fs_resize ((void **) s,len += MAILTMPLEN);
  1023.       t = s + j; /* relocate current pointer */
  1024.     }
  1025.   }
  1026. }
  1027. else { /* first time, start new buffer */
  1028.   s = (char *) fs_get (len = MAILTMPLEN);
  1029.   sprintf (s,"%lu",start = last = i);
  1030.   t = s + strlen (s); /* end of buffer */
  1031. }
  1032.       }
  1033.     }
  1034. /* last sequence */
  1035.   if (last != start) sprintf (t,":%lu",last);
  1036.   if (s) { /* prefetch as needed */
  1037.     IMAPARG *args[7],aseq,aatt[5];
  1038.     args[0] = &aseq; args[1] = &aatt[0];
  1039.     aseq.type = SEQUENCE; aseq.text = (void *) s;
  1040. /* send the hairier form if IMAP4rev1 */
  1041.     if (LEVELIMAP4rev1 (stream)) {
  1042.       aatt[0].type = aatt[1].type = aatt[2].type = aatt[3].type = aatt[4].type 
  1043. = ATOM;
  1044.       aatt[0].text = (void *) allheader;
  1045.       aatt[1].text = (void *) hdrheader;
  1046.       aatt[3].text = (void *) hdrtrailer;
  1047.       aatt[4].text = (void *) fasttrailer;
  1048.       args[i = 2] = &aatt[1];
  1049.       if (aatt[2].text = (void *) imap_extrahdrs) args[++i] = &aatt[2];
  1050.       args[++i] = &aatt[3];
  1051.       args[++i] = &aatt[4];
  1052.       args[++i] = NIL;
  1053.     }
  1054.     else { /* just do FETCH ALL */
  1055.       aatt[0].type = ATOM;
  1056.       aatt[0].text = (void *) "ALL";
  1057.       args[2] = NIL;
  1058.     }
  1059.     imap_send (stream,"FETCH",args);
  1060.     fs_give ((void **) &s);
  1061.   }
  1062.   ov.optional.lines = 0; /* now overview each message */
  1063.   ov.optional.xref = NIL;
  1064.   if (ofn) for (i = 1; i <= stream->nmsgs; i++)
  1065.     if (((elt = mail_elt (stream,i))->sequence) &&
  1066. (env = mail_fetch_structure (stream,i,NIL,NIL)) && ofn) {
  1067.       ov.subject = env->subject;
  1068.       ov.from = env->from;
  1069.       ov.date = env->date;
  1070.       ov.message_id = env->message_id;
  1071.       ov.references = env->references;
  1072.       ov.optional.octets = elt->rfc822_size;
  1073.       (*ofn) (stream,mail_uid (stream,i),&ov);
  1074.     }
  1075.   return LONGT;
  1076. }
  1077. /* IMAP fetch structure
  1078.  * Accepts: MAIL stream
  1079.  *     message # to fetch
  1080.  *     pointer to return body
  1081.  *     option flags
  1082.  * Returns: envelope of this message, body returned in body value
  1083.  *
  1084.  * Fetches the "fast" information as well
  1085.  */
  1086. ENVELOPE *imap_structure (MAILSTREAM *stream,unsigned long msgno,BODY **body,
  1087.   long flags)
  1088. {
  1089.   unsigned long i,j,k;
  1090.   char *s,seq[128],tmp[MAILTMPLEN];
  1091.   MESSAGECACHE *elt;
  1092.   ENVELOPE **env;
  1093.   BODY **b;
  1094.   IMAPPARSEDREPLY *reply = NIL;
  1095.   IMAPARG *args[3],aseq,aatt;
  1096.   args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
  1097.   aseq.type = SEQUENCE; aseq.text = (void *) seq;
  1098.   aatt.type = ATOM; aatt.text = NIL;
  1099.   if (flags & FT_UID) /* see if can find msgno from UID */
  1100.     for (i = 1; i <= stream->nmsgs; i++)
  1101.       if ((elt = mail_elt (stream,i))->private.uid == msgno) {
  1102. msgno = i; /* found msgno, use it from now on */
  1103. flags &= ~FT_UID; /* no longer a UID fetch */
  1104.       }
  1105.   sprintf (seq,"%lu",msgno); /* initial sequence */
  1106. /* IMAP UID fetching is a special case */
  1107.   if (LEVELIMAP4 (stream) && (flags & FT_UID)) {
  1108.     strcpy (tmp,allheader);
  1109.     if (LEVELIMAP4rev1(stream)){/* get extra headers if IMAP4rev1 */
  1110.       if (imap_extrahdrs) sprintf (tmp + strlen (tmp)," %s %s %s",
  1111.    hdrheader,imap_extrahdrs,hdrtrailer);
  1112.       else sprintf (tmp + strlen (tmp)," %s %s",hdrheader,hdrtrailer);
  1113.     }
  1114.     if (body) strcat (tmp," BODYSTRUCTURE");
  1115.     sprintf (tmp + strlen (tmp)," %s",fasttrailer);
  1116.     aatt.text = (void *) tmp; /* do the built command */
  1117.     if (!imap_OK (stream,reply = imap_send (stream,"UID FETCH",args))) {
  1118.       mm_log (reply->text,ERROR);
  1119.     }
  1120. /* now hunt for this UID */
  1121.     for (i = 1; i <= stream->nmsgs; i++)
  1122.       if ((elt = mail_elt (stream,i))->private.uid == msgno) {
  1123. if (body) *body = elt->private.msg.body;
  1124. return elt->private.msg.env;
  1125.       }
  1126.     if (body) *body = NIL; /* can't find the UID */
  1127.     return NIL;
  1128.   }
  1129.   elt = mail_elt (stream,msgno);/* get cache pointer */
  1130.   if (stream->scache) { /* short caching? */
  1131.     env = &stream->env; /* use temporaries on the stream */
  1132.     b = &stream->body;
  1133.     if (msgno != stream->msgno){/* flush old poop if a different message */
  1134.       mail_free_envelope (env);
  1135.       mail_free_body (b);
  1136.       stream->msgno = msgno; /* this is now the current short cache msg */
  1137.     }
  1138.   }
  1139.   else { /* normal cache */
  1140.     env = &elt->private.msg.env;/* get envelope and body pointers */
  1141.     b = &elt->private.msg.body;
  1142. /* prefetch if don't have envelope */
  1143.     if ((k = imap_lookahead) && (!*env || (*env)->incomplete))
  1144. /* build message number list */
  1145.       for (i = msgno + 1, s = seq; k && (i <= stream->nmsgs); i++)
  1146. if (!mail_elt (stream,i)->private.msg.env) {
  1147.   s += strlen (s); /* find string end, see if nearing end */
  1148.   if ((s - seq) > (MAILTMPLEN - 20)) break;
  1149.   sprintf (s,",%lu",i); /* append message */
  1150.     for (j = i + 1, k--; /* hunt for last message without an envelope */
  1151.        k && (j <= stream->nmsgs) &&
  1152.        !mail_elt (stream,j)->private.msg.env; j++, k--);
  1153. /* if different, make a range */
  1154.   if (i != --j) sprintf (s + strlen (s),":%lu",i = j);
  1155. }
  1156.   }
  1157.   if (LEVELIMAP4 (stream)) { /* has extensible body structure and UIDs */
  1158.     tmp[0] = ''; /* initialize command */
  1159. /* need envelope? */
  1160.     if (!*env || (*env)->incomplete) {
  1161.       strcat (tmp," ENVELOPE"); /* yes, get it and possible extra poop */
  1162.       if (LEVELIMAP4rev1 (stream)) {
  1163. if (imap_extrahdrs) sprintf (tmp + strlen (tmp)," %s %s %s",
  1164.      hdrheader,imap_extrahdrs,hdrtrailer);
  1165. else sprintf (tmp + strlen (tmp)," %s %s",hdrheader,hdrtrailer);
  1166.       }
  1167.     }
  1168. /* need anything else? */
  1169.     if (body && !*b) strcat (tmp," BODYSTRUCTURE");
  1170.     if (!elt->private.uid) strcat (tmp," UID");
  1171.     if (!elt->day) strcat (tmp," INTERNALDATE");
  1172.     if (!elt->rfc822_size) strcat (tmp," RFC822.SIZE");
  1173.     if (tmp[0]) { /* anything to do? */
  1174.       strcat (tmp," FLAGS)"); /* always get current flags */
  1175.       tmp[0] = '('; /* make into a list */
  1176.       aatt.text = (void *) tmp; /* do the built command */
  1177.     }
  1178.   }
  1179. /* has non-extensive body */
  1180.   else if (LEVELIMAP2bis (stream)) {
  1181.     if (!*env || (*env)->incomplete) 
  1182.       aatt.text = (body && !*b) ? (void *) "FULL" : (void *) "ALL";
  1183.     else if (body && !*b) aatt.text = (void *) "BODY";
  1184.     else if (!(elt->rfc822_size && elt->day)) aatt.text = (void *) "FAST";
  1185.   }
  1186.   else if (!*env || (*env)->incomplete) aatt.text = (void *) "ALL";
  1187.   else if (!(elt->rfc822_size && elt->day)) aatt.text = (void *) "FAST";
  1188.   if (aatt.text) { /* need to fetch anything? */
  1189.     if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args))) {
  1190. /* failed, probably RFC-1176 server */
  1191.       if (!LEVELIMAP4 (stream) && LEVELIMAP2bis (stream) && body && !*b){
  1192. aatt.text = (void *) "ALL";
  1193. if (imap_OK (stream,reply = imap_send (stream,"FETCH",args)))
  1194.   LOCAL->imap2bis = NIL;/* doesn't have body capabilities */
  1195. else mm_log (reply->text,ERROR);
  1196.       }
  1197.       else mm_log (reply->text,ERROR);
  1198.     }
  1199.   }
  1200.   if (body) *body = *b; /* return the body */
  1201.   return *env; /* return the envelope */
  1202. }
  1203. /* IMAP fetch message data
  1204.  * Accepts: MAIL stream
  1205.  *     message number
  1206.  *     section specifier
  1207.  *     offset of first designated byte or 0 to start at beginning
  1208.  *     maximum number of bytes or 0 for all bytes
  1209.  *     lines to fetch if header
  1210.  *     flags
  1211.  * Returns: T on success, NIL on failure
  1212.  */
  1213. long imap_msgdata (MAILSTREAM *stream,unsigned long msgno,char *section,
  1214.    unsigned long first,unsigned long last,STRINGLIST *lines,
  1215.    long flags)
  1216. {
  1217.   char *t,tmp[MAILTMPLEN],part[40];
  1218.   char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH";
  1219.   IMAPPARSEDREPLY *reply;
  1220.   IMAPARG *args[5],aseq,aatt,alns,acls;
  1221.   aseq.type = NUMBER; aseq.text = (void *) msgno;
  1222.   aatt.type = ATOM; /* assume atomic attribute */
  1223.   alns.type = LIST; alns.text = (void *) lines;
  1224.   acls.type = BODYCLOSE; acls.text = (void *) part;
  1225.   args[0] = &aseq; args[1] = &aatt; args[2] = args[3] = args[4] = NIL;
  1226.   part[0] = ''; /* initially no partial specifier */
  1227.   if (!(flags & FT_PREFETCHTEXT) &&
  1228.       LEVELIMAP4rev1 (stream)) {/* easy if IMAP4rev1 server and no prefetch */
  1229.     aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
  1230.     if (lines) { /* want specific header lines? */
  1231.       sprintf (tmp,"%s.FIELDS%s",section,(flags & FT_NOT) ? ".NOT" : "");
  1232.       aatt.text = (void *) tmp;
  1233.       args[2] = &alns; args[3] = &acls;
  1234.     }
  1235.     else {
  1236.       aatt.text = (void *) section;
  1237.       args[2] = &acls;
  1238.     }
  1239.     if (first || last) sprintf (part,"<%lu.%lu>",first,last ? last:-1);
  1240.   }
  1241. #if 0
  1242.   /* I don't think that this is a good idea.  If partial fetch isn't available,
  1243.    * the application is going to have to do a full fetch anyway if it wants to
  1244.    * get the data.  The mailgets call will indicate what happened.
  1245.    */
  1246.   else if (first || last) { /* partial fetching is only on IMAP4rev1 */
  1247.     mm_notify (stream,"[NOTIMAP4REV1] Can't do partial fetch",WARN);
  1248.     return NIL;
  1249.   }
  1250. #endif
  1251. /* BODY.PEEK[HEADER] becomes RFC822.HEADER */
  1252.   else if (!strcmp (section,"HEADER")) {
  1253.     if (flags & FT_PEEK) aatt.text = (void *)
  1254.       ((flags & FT_PREFETCHTEXT) ? "(RFC822.HEADER RFC822.TEXT)" :
  1255.        "RFC822.HEADER");
  1256.     else {
  1257.       mm_notify (stream,"[NOTIMAP4] Can't do non-peeking header fetch",WARN);
  1258.       return NIL;
  1259.     }
  1260.   }
  1261. /* other peeking was introduced in RFC-1730 */
  1262.   else if ((flags & FT_PEEK) && !LEVEL1730 (stream)) {
  1263.     mm_notify (stream,"[NOTIMAP4] Can't do peeking fetch",WARN);
  1264.     return NIL;
  1265.   }
  1266. /* BODY[TEXT] becomes RFC822.TEXT */
  1267.   else if (!strcmp (section,"TEXT")) aatt.text = (void *)
  1268.     ((flags & FT_PEEK) ? "RFC822.TEXT.PEEK" : "RFC822.TEXT");
  1269. /* BODY[] becomes RFC822 */
  1270.   else if (!section[0]) aatt.text = (void *)
  1271.     ((flags & FT_PEEK) ? "RFC822.PEEK" : "RFC822");
  1272. /* nested header */
  1273.   else if (t = strstr (section,".HEADER")) {
  1274.     if (!LEVEL1730 (stream)) { /* this was introduced in RFC-1730 */
  1275.       mm_notify (stream,"[NOTIMAP4] Can't do nested header fetch",WARN);
  1276.       return NIL;
  1277.     }
  1278.     aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
  1279.     args[2] = &acls; /* will need to close section */
  1280.     aatt.text = (void *) tmp; /* convert .HEADER to .0 for RFC-1730 server */
  1281.     strncpy (tmp,section,t-section);
  1282.     strcpy (tmp+(t-section),".0");
  1283.   }
  1284. /* extended nested text */
  1285.   else if (strstr (section,".MIME") || strstr (section,".TEXT")) {
  1286.     mm_notify (stream,"[NOTIMAP4REV1] Can't do extended body part fetch",WARN);
  1287.     return NIL;
  1288.   }
  1289. /* nested message */
  1290.   else if (LEVELIMAP2bis (stream)) {
  1291.     aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
  1292.     args[2] = &acls; /* will need to close section */
  1293.     aatt.text = (void *) section;
  1294.   }
  1295.   else { /* ancient server */
  1296.     mm_notify (stream,"[NOTIMAP2BIS] Can't do body part fetch",WARN);
  1297.     return NIL;
  1298.   }
  1299. /* send the fetch command */
  1300.   if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) {
  1301.     mm_log (reply->text,ERROR);
  1302.     return NIL; /* failure */
  1303.   }
  1304.   return T;
  1305. }
  1306. /* IMAP fetch UID
  1307.  * Accepts: MAIL stream
  1308.  *     message number
  1309.  * Returns: UID
  1310.  */
  1311. unsigned long imap_uid (MAILSTREAM *stream,unsigned long msgno)
  1312. {
  1313.   MESSAGECACHE *elt;
  1314.   IMAPPARSEDREPLY *reply;
  1315.   IMAPARG *args[3],aseq,aatt;
  1316.   char *s,seq[MAILTMPLEN];
  1317.   unsigned long i,j,k;
  1318. /* IMAP2 didn't have UIDs */
  1319.   if (!LEVELIMAP4 (stream)) return msgno;
  1320. /* do we know its UID yet? */
  1321.   if (!(elt = mail_elt (stream,msgno))->private.uid) {
  1322.     aseq.type = SEQUENCE; aseq.text = (void *) seq;
  1323.     aatt.type = ATOM; aatt.text = (void *) "UID";
  1324.     args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
  1325.     sprintf (seq,"%lu",msgno);
  1326.     if (k = imap_uidlookahead) {/* build UID list */
  1327.       for (i = msgno + 1, s = seq; k && (i <= stream->nmsgs); i++)
  1328. if (!mail_elt (stream,i)->private.uid) {
  1329.   s += strlen (s); /* find string end, see if nearing end */
  1330.   if ((s - seq) > (MAILTMPLEN - 20)) break;
  1331.   sprintf (s,",%lu",i); /* append message */
  1332.   for (j = i + 1, k--; /* hunt for last message without a UID */
  1333.        k && (j <= stream->nmsgs) && !mail_elt (stream,j)->private.uid;
  1334.        j++, k--);
  1335. /* if different, make a range */
  1336.   if (i != --j) sprintf (s + strlen (s),":%lu",i = j);
  1337. }
  1338.     }
  1339. /* send "FETCH msgno UID" */
  1340.     if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args)))
  1341.       mm_log (reply->text,ERROR);
  1342.   }
  1343.   return elt->private.uid; /* return our UID now */
  1344. }
  1345. /* IMAP fetch message number from UID
  1346.  * Accepts: MAIL stream
  1347.  *     UID
  1348.  * Returns: message number
  1349.  */
  1350. unsigned long imap_msgno (MAILSTREAM *stream,unsigned long uid)
  1351. {
  1352.   IMAPPARSEDREPLY *reply;
  1353.   IMAPARG *args[3],aseq,aatt;
  1354.   char seq[MAILTMPLEN];
  1355.   unsigned long msgno;
  1356. /* IMAP2 didn't have UIDs */
  1357.   if (!LEVELIMAP4 (stream)) return uid;
  1358. /* have server hunt for UID */
  1359.   aseq.type = SEQUENCE; aseq.text = (void *) seq;
  1360.   aatt.type = ATOM; aatt.text = (void *) "UID";
  1361.   args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
  1362.   sprintf (seq,"%lu",uid);
  1363. /* send "UID FETCH uid UID" */
  1364.   if (!imap_OK (stream,reply = imap_send (stream,"UID FETCH",args)))
  1365.     mm_log (reply->text,ERROR);
  1366.   for (msgno = 1; msgno <= stream->nmsgs; msgno++)
  1367.     if (mail_elt (stream,msgno)->private.uid == uid) return msgno;
  1368.   return 0; /* didn't find the UID anywhere */
  1369. }
  1370. /* IMAP modify flags
  1371.  * Accepts: MAIL stream
  1372.  *     sequence
  1373.  *     flag(s)
  1374.  *     option flags
  1375.  */
  1376. void imap_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
  1377. {
  1378.   char *cmd = (LEVELIMAP4 (stream) && (flags & ST_UID)) ? "UID STORE":"STORE";
  1379.   IMAPPARSEDREPLY *reply;
  1380.   IMAPARG *args[4],aseq,ascm,aflg;
  1381.   aseq.type = SEQUENCE; aseq.text = (void *) sequence;
  1382.   ascm.type = ATOM; ascm.text = (void *)
  1383.     ((flags & ST_SET) ?
  1384.      ((LEVELIMAP4 (stream) && (flags & ST_SILENT)) ?
  1385.       "+Flags.silent" : "+Flags") :
  1386.      ((LEVELIMAP4 (stream) && (flags & ST_SILENT)) ?
  1387.       "-Flags.silent" : "-Flags"));
  1388.   aflg.type = FLAGS; aflg.text = (void *) flag;
  1389.   args[0] = &aseq; args[1] = &ascm; args[2] = &aflg; args[3] = NIL;
  1390. /* send "STORE sequence +Flags flag" */
  1391.   if (!imap_OK (stream,reply = imap_send (stream,cmd,args)))
  1392.     mm_log (reply->text,ERROR);
  1393. }
  1394. /* IMAP search for messages
  1395.  * Accepts: MAIL stream
  1396.  *     character set
  1397.  *     search program
  1398.  *     option flags
  1399.  */
  1400. void imap_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags)
  1401. {
  1402.   char *cmd = (LEVELIMAP4 (stream) && (flags & SE_UID)) ?"UID SEARCH":"SEARCH";
  1403.   unsigned long i,j,k;
  1404.   char *s,tmp[MAILTMPLEN];
  1405.   IMAPPARSEDREPLY *reply;
  1406.   MESSAGECACHE *elt;
  1407.   IMAPARG *args[4],apgm,aseq,aatt,achs;
  1408.   args[1] = args[2] = args[3] = NIL;
  1409.   apgm.type = SEARCHPROGRAM; apgm.text = (void *) pgm;
  1410.   aseq.type = SEQUENCE;
  1411.   aatt.type = ATOM;
  1412.   achs.type = ASTRING;
  1413.   if (charset) { /* optional charset argument requested */
  1414.     args[0] = &aatt; args[1] = &achs; args[2] = &apgm;
  1415.     aatt.text = (void *) "CHARSET";
  1416.     achs.text = (void *) charset;
  1417.   }
  1418.   else args[0] = &apgm;
  1419. /* be sure that receiver understands */
  1420.   LOCAL->uidsearch = (flags & SE_UID) ? T : NIL;
  1421.   if (!LEVELIMAP4 (stream) && /* if old server but new functions... */
  1422.       (charset || LOCAL->uidsearch || pgm->msgno || pgm->uid || pgm->or ||
  1423.        pgm->not || pgm->header || pgm->larger || pgm->smaller ||
  1424.        pgm->sentbefore || pgm->senton || pgm->sentsince || pgm->draft ||
  1425.        pgm->undraft || pgm->return_path || pgm->sender || pgm->reply_to ||
  1426.        pgm->message_id || pgm->in_reply_to || pgm->newsgroups ||
  1427.        pgm->followup_to || pgm->references))
  1428.     mail_search_default (stream,charset,pgm,flags);
  1429. /* do the SEARCH */
  1430.   else if (!imap_OK (stream,reply = imap_send (stream,cmd,args)))
  1431.     mm_log (reply->text,ERROR);
  1432. /* can never pre-fetch with a short cache */
  1433.   else if ((k = imap_prefetch) && !(flags & (SE_NOPREFETCH|SE_UID)) &&
  1434.    !stream->scache) { /* only if prefetching permitted */
  1435.     s = LOCAL->tmp; /* build sequence in temporary buffer */
  1436.     *s = ''; /* initially nothing */
  1437. /* search through mailbox */
  1438.     for (i = 1; k && (i <= stream->nmsgs); ++i) 
  1439. /* for searched messages with no envelope */
  1440.       if ((elt = mail_elt (stream,i)) && elt->searched &&
  1441.   !mail_elt (stream,i)->private.msg.env) {
  1442. /* prepend with comma if not first time */
  1443. if (LOCAL->tmp[0]) *s++ = ',';
  1444. sprintf (s,"%lu",j = i);/* output message number */
  1445. s += strlen (s); /* point at end of string */
  1446. k--; /* count one up */
  1447. /* search for possible end of range */
  1448. while (k && (i < stream->nmsgs) &&
  1449.        (elt = mail_elt (stream,i+1))->searched &&
  1450.        !elt->private.msg.env) i++,k--;
  1451. if (i != j) { /* if a range */
  1452.   sprintf (s,":%lu",i); /* output delimiter and end of range */
  1453.   s += strlen (s); /* point at end of string */
  1454. }
  1455.       }
  1456.     if (LOCAL->tmp[0]) { /* anything to pre-fetch? */
  1457.       args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
  1458.       aseq.text = (void *) cpystr (LOCAL->tmp);
  1459.       if (LEVELIMAP4 (stream)) {/* IMAP4 fetching does more */
  1460. strcpy (tmp,allheader);
  1461. if (LEVELIMAP4rev1 (stream)) {
  1462.   if (imap_extrahdrs) sprintf (tmp + strlen (tmp)," %s %s %s",
  1463.        hdrheader,imap_extrahdrs,hdrtrailer);
  1464.   else sprintf (tmp + strlen (tmp)," %s %s",hdrheader,hdrtrailer);
  1465. }
  1466. sprintf (tmp + strlen (tmp)," %s",fasttrailer);
  1467. aatt.text = (void *) tmp;
  1468.       }
  1469.       else aatt.text = (void *) "ALL";
  1470.       if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args)))
  1471. mm_log (reply->text,ERROR);
  1472. /* flush copy of sequence */
  1473.       fs_give ((void **) &aseq.text);
  1474.     }
  1475.   }
  1476. }
  1477. /* IMAP sort messages
  1478.  * Accepts: mail stream
  1479.  *     character set
  1480.  *     search program
  1481.  *     sort program
  1482.  *     option flags
  1483.  * Returns: vector of sorted message sequences or NIL if error
  1484.  */
  1485. unsigned long *imap_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
  1486.   SORTPGM *pgm,long flags)
  1487. {
  1488.   unsigned long i,j,start,last;
  1489.   unsigned long *ret = NIL;
  1490.   pgm->nmsgs = 0; /* start off with no messages */
  1491. /* can use server-based sort? */
  1492.   if (LOCAL->use_sort && !(flags & SO_NOSERVER)) {
  1493.     char *cmd = (flags & SE_UID) ? "UID SORT" : "SORT";
  1494.     IMAPARG *args[4],apgm,achs,aspg;
  1495.     IMAPPARSEDREPLY *reply;
  1496.     SEARCHSET *ss = NIL;
  1497.     apgm.type = SORTPROGRAM; apgm.text = (void *) pgm;
  1498.     achs.type = ASTRING; achs.text = (void *) (charset ? charset : "US-ASCII");
  1499.     aspg.type = SEARCHPROGRAM;
  1500. /* did he provide a searchpgm? */
  1501.     if (!(aspg.text = (void *) spg)) {
  1502.       for (i = 1,start = last = 0; i <= stream->nmsgs; ++i)
  1503. if (mail_elt (stream,i)->searched) {
  1504.   if (ss) { /* continuing a sequence */
  1505.     if (i == last + 1) last = i;
  1506.     else { /* end of range */
  1507.       if (last != start) ss->last = last;
  1508.       (ss = ss->next = mail_newsearchset ())->first = i;
  1509.       start = last = i; /* begin a new range */
  1510.     }
  1511.   }
  1512.   else { /* first time, start new searchpgm */
  1513.     (spg = mail_newsearchpgm ())->msgno = ss = mail_newsearchset ();
  1514.     ss->first = start = last = i;
  1515.   }
  1516. }
  1517. /* nothing to sort if no messages */
  1518.       if (!(aspg.text = (void *) spg)) return NIL;
  1519. /* else install last sequence */
  1520.       if (last != start) ss->last = last;
  1521.     }
  1522.     args[0] = &apgm; args[1] = &achs; args[2] = &aspg; args[3] = NIL;
  1523.     if (imap_OK (stream,reply = imap_send (stream,cmd,args))) {
  1524.       pgm->nmsgs = LOCAL->sortsize;
  1525.       ret = LOCAL->sortdata;
  1526.       LOCAL->sortdata = NIL; /* mail program is responsible for flushing */
  1527.     }
  1528.     else mm_log (reply->text,ERROR);
  1529. /* free any temporary searchpgm */
  1530.     if (ss) mail_free_searchpgm (&spg);
  1531.   }
  1532. /* not much can do if short caching */
  1533.   else if (stream->scache) ret = mail_sort_msgs (stream,charset,spg,pgm,flags);
  1534.   else { /* try to be a bit more clever */
  1535.     char *s,*t;
  1536.     unsigned long len;
  1537.     MESSAGECACHE *elt;
  1538.     SORTCACHE **sc;
  1539.     SORTPGM *sp;
  1540.     int needenvs = 0;
  1541. /* see if need envelopes */
  1542.     for (sp = pgm; sp && !needenvs; sp = sp->next) switch (sp->function) {
  1543.     case SORTDATE: case SORTFROM: case SORTSUBJECT: case SORTTO: case SORTCC:
  1544.       needenvs = T;
  1545.     }
  1546.     if (spg) { /* only if a search needs to be done */
  1547.       int silent = stream->silent;
  1548.       stream->silent = T; /* don't pass up mm_searched() events */
  1549. /* search for messages */
  1550.       mail_search_full (stream,charset,spg,NIL);
  1551.       stream->silent = silent; /* restore silence state */
  1552.     }
  1553. /* initialize progress counters */
  1554.     pgm->nmsgs = pgm->progress.cached = 0;
  1555. /* pass 1: count messages to sort */
  1556.     for (i = 1,len = start = last = 0,s = t = NIL; i <= stream->nmsgs; ++i)
  1557.       if ((elt = mail_elt (stream,i))->searched) {
  1558. pgm->nmsgs++;
  1559. if (needenvs ? !elt->private.msg.env : !elt->day) {
  1560.   if (s) { /* continuing a sequence */
  1561.     if (i == last + 1) last = i;
  1562.     else { /* end of range */
  1563.       if (last != start) sprintf (t,":%lu,%lu",last,i);
  1564.       else sprintf (t,",%lu",i);
  1565.       start = last = i; /* begin a new range */
  1566.       if ((j = ((t += strlen (t)) - s)) > (MAILTMPLEN - 20)) {
  1567. fs_resize ((void **) s,len += MAILTMPLEN);
  1568. t = s + j; /* relocate current pointer */
  1569.       }
  1570.     }
  1571.   }
  1572.   else { /* first time, start new buffer */
  1573.     s = (char *) fs_get (len = MAILTMPLEN);
  1574.     sprintf (s,"%lu",start = last = i);
  1575.     t = s + strlen (s); /* end of buffer */
  1576.   }
  1577. }
  1578.       }
  1579. /* last sequence */
  1580.     if (last != start) sprintf (t,":%lu",last);
  1581.     if (s) { /* prefetch needed data */
  1582.       IMAPARG *args[7],aseq,aatt[5];
  1583.       args[0] = &aseq; args[1] = &aatt[0];
  1584.       aseq.type = SEQUENCE; aseq.text = (void *) s;
  1585. /* send the hairier form if IMAP4rev1 */
  1586.       if (needenvs && LEVELIMAP4rev1 (stream)) {
  1587. aatt[0].type = aatt[1].type = aatt[2].type = aatt[3].type =
  1588.   aatt[4].type = ATOM;
  1589. aatt[0].text = (void *) allheader;
  1590. aatt[1].text = (void *) hdrheader;
  1591. aatt[3].text = (void *) hdrtrailer;
  1592. aatt[4].text = (void *) fasttrailer;
  1593. args[i = 2] = &aatt[1];
  1594. if (aatt[2].text = (void *) imap_extrahdrs) args[++i] = &aatt[2];
  1595. args[++i] = &aatt[3];
  1596. args[++i] = &aatt[4];
  1597. args[++i] = NIL;
  1598.       }
  1599.       else { /* just do FETCH ALL */
  1600. aatt[0].type = ATOM;
  1601. aatt[0].text = (void *) (needenvs ? "ALL" : "FAST");
  1602. args[2] = NIL;
  1603.       }
  1604.       imap_send (stream,"FETCH",args);
  1605.       fs_give ((void **) &s);
  1606.     }
  1607.     if (pgm->nmsgs) { /* pass 2: sort cache */
  1608.       sortresults_t sr = (sortresults_t)
  1609. mail_parameters (NIL,GET_SORTRESULTS,NIL);
  1610.       sc = mail_sort_loadcache (stream,pgm);
  1611. /* pass 3: sort messages */
  1612.       if (!pgm->abort) ret = mail_sort_cache (stream,pgm,sc,flags);
  1613.       fs_give ((void **) &sc); /* don't need sort vector any more */
  1614. /* also return via callback if requested */
  1615.       if (sr) (*sr) (stream,ret,pgm->nmsgs);
  1616.     }
  1617.   }
  1618.   return ret;
  1619. }
  1620. /* IMAP thread messages
  1621.  * Accepts: mail stream
  1622.  *     thread type
  1623.  *     character set
  1624.  *     search program
  1625.  *     option flags
  1626.  * Returns: thread node tree
  1627.  */
  1628. THREADNODE *imap_thread (MAILSTREAM *stream,char *type,char *charset,
  1629.  SEARCHPGM *spg,long flags)
  1630. {
  1631.   THREADNODE *ret = NIL;
  1632.   if (LOCAL->threader) {
  1633.     unsigned long i,start,last;
  1634.     char *cmd = (flags & SE_UID) ? "UID THREAD" : "THREAD";
  1635.     IMAPARG *args[4],apgm,achs,aspg;
  1636.     IMAPPARSEDREPLY *reply;
  1637.     SEARCHSET *ss = NIL;
  1638.     apgm.type = ATOM; apgm.text = (void *) type;
  1639.     achs.type = ASTRING; achs.text = (void *) (charset ? charset : "US-ASCII");
  1640.     aspg.type = SEARCHPROGRAM;
  1641. /* did he provide a searchpgm? */
  1642.     if (!(aspg.text = (void *) spg)) {
  1643.       for (i = 1,start = last = 0; i <= stream->nmsgs; ++i)
  1644. if (mail_elt (stream,i)->searched) {
  1645.   if (ss) { /* continuing a sequence */
  1646.     if (i == last + 1) last = i;
  1647.     else { /* end of range */
  1648.       if (last != start) ss->last = last;
  1649.       (ss = ss->next = mail_newsearchset ())->first = i;
  1650.       start = last = i; /* begin a new range */
  1651.     }
  1652.   }
  1653.   else { /* first time, start new searchpgm */
  1654.     (spg = mail_newsearchpgm ())->msgno = ss = mail_newsearchset ();
  1655.     ss->first = start = last = i;
  1656.   }
  1657. }
  1658. /* nothing to sort if no messages */
  1659.       if (!(aspg.text = (void *) spg)) return NIL;
  1660. /* else install last sequence */
  1661.       if (last != start) ss->last = last;
  1662.     }
  1663.     args[0] = &apgm; args[1] = &achs; args[2] = &aspg; args[3] = NIL;
  1664.     if (imap_OK (stream,reply = imap_send (stream,cmd,args))) {
  1665.       ret = LOCAL->threaddata;
  1666.       LOCAL->threaddata = NIL; /* mail program is responsible for flushing */
  1667.     }
  1668.     else mm_log (reply->text,ERROR);
  1669. /* free any temporary searchpgm */
  1670.     if (ss) mail_free_searchpgm (&spg);
  1671.   }
  1672.   else ret = mail_thread_msgs (stream,type,charset,spg,flags | SO_NOSERVER,
  1673.        imap_sort);
  1674.   return ret;
  1675. }
  1676. /* IMAP ping mailbox
  1677.  * Accepts: MAIL stream
  1678.  * Returns: T if stream still alive, else NIL
  1679.  */
  1680. long imap_ping (MAILSTREAM *stream)
  1681. {
  1682.   return (LOCAL->netstream && /* send "NOOP" */
  1683.   imap_OK (stream,imap_send (stream,"NOOP",NIL))) ? T : NIL;
  1684. }
  1685. /* IMAP check mailbox
  1686.  * Accepts: MAIL stream
  1687.  */
  1688. void imap_check (MAILSTREAM *stream)
  1689. {
  1690. /* send "CHECK" */
  1691.   IMAPPARSEDREPLY *reply = imap_send (stream,"CHECK",NIL);
  1692.   mm_log (reply->text,imap_OK (stream,reply) ? (long) NIL : ERROR);
  1693. }
  1694. /* IMAP expunge mailbox
  1695.  * Accepts: MAIL stream
  1696.  */
  1697. void imap_expunge (MAILSTREAM *stream)
  1698. {
  1699. /* send "EXPUNGE" */
  1700.   IMAPPARSEDREPLY *reply = imap_send (stream,"EXPUNGE",NIL);
  1701.   mm_log (reply->text,imap_OK (stream,reply) ? (long) NIL : ERROR);
  1702. }
  1703. /* IMAP copy message(s)
  1704.  * Accepts: MAIL stream
  1705.  *     sequence
  1706.  *     destination mailbox
  1707.  *     option flags
  1708.  * Returns: T if successful else NIL
  1709.  */
  1710. long imap_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long flags)
  1711. {
  1712.   char *cmd = (LEVELIMAP4 (stream) && (flags & CP_UID)) ? "UID COPY" : "COPY";
  1713.   char *s;
  1714.   IMAPPARSEDREPLY *reply;
  1715.   IMAPARG *args[3],aseq,ambx;
  1716.   imapreferral_t ir =
  1717.     (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
  1718.   mailproxycopy_t pc =
  1719.     (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
  1720.   aseq.type = SEQUENCE; aseq.text = (void *) sequence;
  1721.   ambx.type = ASTRING; ambx.text = (void *) mailbox;
  1722.   args[0] = &aseq; args[1] = &ambx; args[2] = NIL;
  1723. /* send "COPY sequence mailbox" */
  1724.   if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) {
  1725.     if (ir && pc && LOCAL->referral && mail_sequence (stream,sequence) &&
  1726. (s = (*ir) (stream,LOCAL->referral,REFCOPY)))
  1727.       return (*pc) (stream,sequence,s,flags);
  1728.     mm_log (reply->text,ERROR);
  1729.     return NIL;
  1730.   }
  1731. /* delete the messages if the user said to */
  1732.   if (flags & CP_MOVE) imap_flag (stream,sequence,"\Deleted",
  1733.   ST_SET + ((flags & CP_UID) ? ST_UID : NIL));
  1734.   return T;
  1735. }
  1736. /* IMAP append message string
  1737.  * Accepts: mail stream
  1738.  *     destination mailbox
  1739.  *     stringstruct of message to append
  1740.  * Returns: T on success, NIL on failure
  1741.  */
  1742. long imap_append (MAILSTREAM *stream,char *mailbox,char *flags,char *date,
  1743.  STRING *msg)
  1744. {
  1745.   char tmp[MAILTMPLEN];
  1746.   long ret = NIL;
  1747.   if (mail_valid_net (mailbox,&imapdriver,NIL,tmp)) {
  1748.     MAILSTREAM *st = stream;
  1749. /* default stream */
  1750.     if (!(stream && LOCAL && LOCAL->netstream))
  1751.       stream =  mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT);
  1752.     if (stream) { /* do the operation if valid stream */
  1753.       IMAPPARSEDREPLY *reply = NIL;
  1754.       IMAPARG *args[5],ambx,aflg,adat,amsg;
  1755.       imapreferral_t ir =
  1756. (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
  1757.       int i = 0;
  1758.       ambx.type = ASTRING; ambx.text = (void *) tmp;
  1759.       aflg.type = FLAGS; aflg.text = (void *) flags;
  1760.       adat.type = ASTRING; adat.text = (void *) date;
  1761.       amsg.type = LITERAL; amsg.text = (void *) msg;
  1762.       args[i++] = &ambx;
  1763.       if (flags) args[i++] = &aflg;
  1764.       if (date) args[i++] = &adat;
  1765.       args[i++] = &amsg;
  1766.       args[i++] = NIL;
  1767.       if (!strcmp ((reply = imap_send (stream,"APPEND",args))->key,"BAD") &&
  1768.   (flags || date)) { /* full form and got a BAD? */
  1769. /* yes, retry with old IMAP2bis form */
  1770. args[1] = &amsg; args[2] = NIL;
  1771. reply = imap_send (stream,"APPEND",args);
  1772.       }
  1773.       if (imap_OK (stream,reply)) ret = T;
  1774.       else if (ir && LOCAL->referral &&
  1775.        (mailbox = (*ir) (stream,LOCAL->referral,REFAPPEND)))
  1776. ret = imap_append (NIL,mailbox,flags,date,msg);
  1777. /* no referral, babble the error */
  1778.       else mm_log (reply->text,ERROR);
  1779. /* toss out temporary stream */
  1780.       if (st != stream) mail_close (stream);
  1781.     }
  1782.     else mm_log ("Can't access server for append",ERROR);
  1783.   }
  1784.   return ret; /* return */
  1785. }
  1786. /* IMAP garbage collect stream
  1787.  * Accepts: Mail stream
  1788.  *     garbage collection flags
  1789.  */
  1790. void imap_gc (MAILSTREAM *stream,long gcflags)
  1791. {
  1792.   unsigned long i;
  1793.   MESSAGECACHE *elt;
  1794.   mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
  1795. /* make sure the cache is large enough */
  1796.   (*mc) (stream,stream->nmsgs,CH_SIZE);
  1797.   if (gcflags & GC_TEXTS) { /* garbage collect texts? */
  1798.     if (!stream->scache) for (i = 1; i <= stream->nmsgs; ++i)
  1799.       if (elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT))
  1800. imap_gc_body (elt->private.msg.body);
  1801.     imap_gc_body (stream->body);
  1802.   }
  1803. /* gc cache if requested and unlocked */
  1804.   if (gcflags & GC_ELT) for (i = 1; i <= stream->nmsgs; ++i)
  1805.     if ((elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT)) &&
  1806. (elt->lockcount == 1)) (*mc) (stream,i,CH_FREE);
  1807. }
  1808. /* IMAP garbage collect body texts
  1809.  * Accepts: body to GC
  1810.  */
  1811. void imap_gc_body (BODY *body)
  1812. {
  1813.   PART *part;
  1814.   if (body) { /* have a body? */
  1815.     if (body->mime.text.data) /* flush MIME data */
  1816.       fs_give ((void **) &body->mime.text.data);
  1817. /* flush text contents */
  1818.     if (body->contents.text.data)
  1819.       fs_give ((void **) &body->contents.text.data);
  1820.     body->mime.text.size = body->contents.text.size = 0;
  1821. /* multipart? */
  1822.     if (body->type == TYPEMULTIPART)
  1823.       for (part = body->nested.part; part; part = part->next)
  1824. imap_gc_body (&part->body);
  1825. /* MESSAGE/RFC822? */
  1826.     else if ((body->type == TYPEMESSAGE) && !strcmp (body->subtype,"RFC822")) {
  1827.       imap_gc_body (body->nested.msg->body);
  1828.       if (body->nested.msg->full.text.data)
  1829. fs_give ((void **) &body->nested.msg->full.text.data);
  1830.       if (body->nested.msg->header.text.data)
  1831. fs_give ((void **) &body->nested.msg->header.text.data);
  1832.       if (body->nested.msg->text.text.data)
  1833. fs_give ((void **) &body->nested.msg->text.text.data);
  1834.       body->nested.msg->full.text.size = body->nested.msg->header.text.size =
  1835. body->nested.msg->text.text.size = 0;
  1836.     }
  1837.   }
  1838. }
  1839. /* Internal routines */
  1840. /* IMAP send command
  1841.  * Accepts: MAIL stream
  1842.  *     command
  1843.  *     argument list