Logo Search packages:      
Sourcecode: heirloom-mailx version File versions  Download package

imap.c

/*
 * Heirloom mailx - a mail user agent derived from Berkeley Mail.
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 */
/*
 * Copyright (c) 2004
 *    Gunnar Ritter.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    This product includes software developed by Gunnar Ritter
 *    and his contributors.
 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef lint
#ifdef      DOSCCS
static char sccsid[] = "@(#)imap.c  1.219 (gritter) 1/6/08";
#endif
#endif /* not lint */

#include "config.h"

/*
 * Mail -- a mail program
 *
 * IMAP v4r1 client following RFC 2060.
 */

#include "rcv.h"
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>

#ifdef      HAVE_SOCKETS

#include "md5.h"

#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#ifdef  HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif  /* HAVE_ARPA_INET_H */

#include "extern.h"

static int  verbose;

#define     IMAP_ANSWER()     { \
                        if (mp->mb_type != MB_CACHE) { \
                              enum okay ok = OKAY; \
                              while (mp->mb_active & MB_COMD) \
                                    ok = imap_answer(mp, 1); \
                              if (ok == STOP) \
                                    return STOP; \
                        } \
                  }
#define     IMAP_OUT(x, y, action)  \
                  { \
                        if (mp->mb_type != MB_CACHE) { \
                              if (imap_finish(mp) == STOP) \
                                    return STOP; \
                              if (verbose) \
                                    fprintf(stderr, ">>> %s", x); \
                              mp->mb_active |= (y); \
                              if (swrite(&mp->mb_sock, x) == STOP) \
                                    action; \
                        } else { \
                              if (queuefp != NULL) \
                                    fputs(x, queuefp); \
                        } \
                  }

static struct     record {
      struct record     *rec_next;
      unsigned long     rec_count;
      enum rec_type {
            REC_EXISTS,
            REC_EXPUNGE
      } rec_type;
} *record, *recend;

static enum {
      RESPONSE_TAGGED,
      RESPONSE_DATA,
      RESPONSE_FATAL,
      RESPONSE_CONT,
      RESPONSE_ILLEGAL
} response_type;

static enum {
      RESPONSE_OK,
      RESPONSE_NO,
      RESPONSE_BAD,
      RESPONSE_PREAUTH,
      RESPONSE_BYE,
      RESPONSE_OTHER,
      RESPONSE_UNKNOWN
} response_status;

static char *responded_tag;
static char *responded_text;
static char *responded_other_text;
static long responded_other_number;

static enum {
      MAILBOX_DATA_FLAGS,
      MAILBOX_DATA_LIST,
      MAILBOX_DATA_LSUB,
      MAILBOX_DATA_MAILBOX,
      MAILBOX_DATA_SEARCH,
      MAILBOX_DATA_STATUS,
      MAILBOX_DATA_EXISTS,
      MAILBOX_DATA_RECENT,
      MESSAGE_DATA_EXPUNGE,
      MESSAGE_DATA_FETCH,
      CAPABILITY_DATA,
      RESPONSE_OTHER_UNKNOWN
} response_other;

static enum list_attributes {
      LIST_NONE         = 000,
      LIST_NOINFERIORS  = 001,
      LIST_NOSELECT           = 002,
      LIST_MARKED       = 004,
      LIST_UNMARKED           = 010
} list_attributes;

static int  list_hierarchy_delimiter;
static char *list_name;

struct list_item {
      struct list_item  *l_next;
      char  *l_name;
      char  *l_base;
      enum list_attributes    l_attr;
      int   l_delim;
      int   l_level;
      int   l_has_children;
};

static char *imapbuf;
static size_t     imapbufsize;
static sigjmp_buf imapjmp;
static sighandler_type savealrm;
static int  reset_tio;
static struct termios   otio;
static int  imapkeepalive;
static long had_exists = -1;
static long had_expunge = -1;
static long expunged_messages;
static volatile int     imaplock;

static int  same_imap_account;

static void imap_other_get(char *pp);
static void imap_response_get(const char **cp);
static void imap_response_parse(void);
static enum okay imap_answer(struct mailbox *mp, int errprnt);
static enum okay imap_parse_list(void);
static enum okay imap_finish(struct mailbox *mp);
static void imap_timer_off(void);
static void imapcatch(int s);
static void maincatch(int s);
static enum okay imap_noop1(struct mailbox *mp);
static void rec_queue(enum rec_type type, unsigned long count);
static enum okay rec_dequeue(void);
static void rec_rmqueue(void);
static void imapalarm(int s);
static int imap_use_starttls(const char *uhp);
static enum okay imap_preauth(struct mailbox *mp, const char *xserver,
            const char *uhp);
static enum okay imap_capability(struct mailbox *mp);
static enum okay imap_auth(struct mailbox *mp, const char *uhp,
            char *xuser, const char *pass);
static enum okay imap_cram_md5(struct mailbox *mp,
            char *xuser, const char *xpass);
static enum okay imap_login(struct mailbox *mp, char *xuser, const char *xpass);
#ifdef      USE_GSSAPI
static enum okay imap_gss(struct mailbox *mp, char *user);
#endif      /* USE_GSSAPI */
static enum okay imap_flags(struct mailbox *mp, unsigned X, unsigned Y);
static void imap_init(struct mailbox *mp, int n);
static void imap_setptr(struct mailbox *mp, int newmail, int transparent,
            int *prevcount);
static char *imap_have_password(const char *server);
static void imap_split(char **server, const char **sp, int *use_ssl,
            const char **cp, char **uhp, char **mbx,
            const char **pass, char **user);
static int imap_setfile1(const char *xserver, int newmail, int isedit,
            int transparent);
static int imap_fetchdata(struct mailbox *mp, struct message *m,
            size_t expected, int need,
            const char *head, size_t headsize, long headlines);
static void imap_putstr(struct mailbox *mp, struct message *m,
            const char *str,
            const char *head, size_t headsize, long headlines);
static enum okay imap_get(struct mailbox *mp, struct message *m,
            enum needspec need);
static void commitmsg(struct mailbox *mp, struct message *to,
            struct message from, enum havespec have);
static enum okay imap_fetchheaders(struct mailbox *mp, struct message *m,
            int bot, int top);
static enum okay imap_exit(struct mailbox *mp);
static enum okay imap_delete(struct mailbox *mp, int n, struct message *m, int
            needstat);
static enum okay imap_close(struct mailbox *mp);
static enum okay imap_update(struct mailbox *mp);
static enum okay imap_store(struct mailbox *mp, struct message *m,
            int n, int c, const char *sp, int needstat);
static enum okay imap_unstore(struct message *m, int n, const char *flag);
static const char *tag(int new);
static char *imap_putflags(int f);
static void imap_getflags(const char *cp, char **xp, enum mflag *f);
static enum okay imap_append1(struct mailbox *mp, const char *name, FILE *fp,
            off_t off1, long xsize, enum mflag flag, time_t t);
static enum okay imap_append0(struct mailbox *mp, const char *name, FILE *fp);
static enum okay imap_list1(struct mailbox *mp, const char *base,
            struct list_item **list, struct list_item **lend, int level);
static enum okay imap_list(struct mailbox *mp, const char *base,
            int strip, FILE *fp);
static void dopr(FILE *fp);
static enum okay imap_copy1(struct mailbox *mp, struct message *m, int n,
            const char *name);
static enum okay imap_copyuid_parse(const char *cp, unsigned long *uidvalidity,
            unsigned long *olduid, unsigned long *newuid);
static enum okay imap_appenduid_parse(const char *cp,
            unsigned long *uidvalidity, unsigned long *uid);
static enum okay imap_copyuid(struct mailbox *mp, struct message *m,
            const char *name);
static enum okay imap_appenduid(struct mailbox *mp, FILE *fp, time_t t,
            long off1, long xsize, long size, long lines,
            int flag, const char *name);
static enum okay imap_appenduid_cached(struct mailbox *mp, FILE *fp);
static enum okay imap_search2(struct mailbox *mp, struct message *m,
            int count, const char *spec, int f);
static enum okay imap_remove1(struct mailbox *mp, const char *name);
static enum okay imap_rename1(struct mailbox *mp, const char *old,
            const char *new);
static char *imap_strex(const char *cp, char **xp);
static enum okay check_expunged(void);

static void 
imap_other_get(char *pp)
{
      char  *xp;

      if (ascncasecmp(pp, "FLAGS ", 6) == 0) {
            pp += 6;
            response_other = MAILBOX_DATA_FLAGS;
      } else if (ascncasecmp(pp, "LIST ", 5) == 0) {
            pp += 5;
            response_other = MAILBOX_DATA_LIST;
      } else if (ascncasecmp(pp, "LSUB ", 5) == 0) {
            pp += 5;
            response_other = MAILBOX_DATA_LSUB;
      } else if (ascncasecmp(pp, "MAILBOX ", 8) == 0) {
            pp += 8;
            response_other = MAILBOX_DATA_MAILBOX;
      } else if (ascncasecmp(pp, "SEARCH ", 7) == 0) {
            pp += 7;
            response_other = MAILBOX_DATA_SEARCH;
      } else if (ascncasecmp(pp, "STATUS ", 7) == 0) {
            pp += 7;
            response_other = MAILBOX_DATA_STATUS;
      } else if (ascncasecmp(pp, "CAPABILITY ", 11) == 0) {
            pp += 11;
            response_other = CAPABILITY_DATA;
      } else {
            responded_other_number = strtol(pp, &xp, 10);
            while (*xp == ' ')
                  xp++;
            if (ascncasecmp(xp, "EXISTS\r\n", 8) == 0) {
                  response_other = MAILBOX_DATA_EXISTS;
            } else if (ascncasecmp(xp, "RECENT\r\n", 8) == 0) {
                  response_other = MAILBOX_DATA_RECENT;
            } else if (ascncasecmp(xp, "EXPUNGE\r\n", 9) == 0) {
                  response_other = MESSAGE_DATA_EXPUNGE;
            } else if (ascncasecmp(xp, "FETCH ", 6) == 0) {
                  pp = &xp[6];
                  response_other = MESSAGE_DATA_FETCH;
            } else
                  response_other = RESPONSE_OTHER_UNKNOWN;
      }
      responded_other_text = pp;
}

static void 
imap_response_get(const char **cp)
{
      if (ascncasecmp(*cp, "OK ", 3) == 0) {
            *cp += 3;
            response_status = RESPONSE_OK;
      } else if (ascncasecmp(*cp, "NO ", 3) == 0) {
            *cp += 3;
            response_status = RESPONSE_NO;
      } else if (ascncasecmp(*cp, "BAD ", 4) == 0) {
            *cp += 4;
            response_status = RESPONSE_BAD;
      } else if (ascncasecmp(*cp, "PREAUTH ", 8) == 0) {
            *cp += 8;
            response_status = RESPONSE_PREAUTH;
      } else if (ascncasecmp(*cp, "BYE ", 4) == 0) {
            *cp += 4;
            response_status = RESPONSE_BYE;
      } else
            response_status = RESPONSE_OTHER;
}

static void 
imap_response_parse(void)
{
      static char *parsebuf;
      static size_t     parsebufsize;
      const char  *ip = imapbuf;
      char  *pp;

      if (parsebufsize < imapbufsize) {
            free(parsebuf);
            parsebuf = smalloc(parsebufsize = imapbufsize);
      }
      strcpy(parsebuf, imapbuf);
      pp = parsebuf;
      switch (*ip) {
      case '+':
            response_type = RESPONSE_CONT;
            ip++;
            pp++;
            while (*ip == ' ') {
                  ip++;
                  pp++;
            }
            break;
      case '*':
            ip++;
            pp++;
            while (*ip == ' ') {
                  ip++;
                  pp++;
            }
            imap_response_get(&ip);
            pp = &parsebuf[ip - imapbuf];
            switch (response_status) {
            case RESPONSE_BYE:
                  response_type = RESPONSE_FATAL;
                  break;
            default:
                  response_type = RESPONSE_DATA;
            }
            break;
      default:
            responded_tag = parsebuf;
            while (*pp && *pp != ' ')
                  pp++;
            if (*pp == '\0') {
                  response_type = RESPONSE_ILLEGAL;
                  break;
            }
            *pp++ = '\0';
            while (*pp && *pp == ' ')
                  pp++;
            if (*pp == '\0') {
                  response_type = RESPONSE_ILLEGAL;
                  break;
            }
            ip = &imapbuf[pp - parsebuf];
            response_type = RESPONSE_TAGGED;
            imap_response_get(&ip);
            pp = &parsebuf[ip - imapbuf];
      }
      responded_text = pp;
      if (response_type != RESPONSE_CONT &&
                  response_type != RESPONSE_ILLEGAL &&
                  response_status == RESPONSE_OTHER)
            imap_other_get(pp);
}

static enum okay 
imap_answer(struct mailbox *mp, int errprnt)
{
      int   i, complete;
      enum okay ok = STOP;

      if (mp->mb_type == MB_CACHE)
            return OKAY;
again:      if (sgetline(&imapbuf, &imapbufsize, NULL, &mp->mb_sock) > 0) {
            if (verbose)
                  fputs(imapbuf, stderr);
            imap_response_parse();
            if (response_type == RESPONSE_ILLEGAL)
                  goto again;
            if (response_type == RESPONSE_CONT)
                  return OKAY;
            if (response_status == RESPONSE_OTHER) {
                  if (response_other == MAILBOX_DATA_EXISTS) {
                        had_exists = responded_other_number;
                        rec_queue(REC_EXISTS, responded_other_number);
                        if (had_expunge > 0)
                              had_expunge = 0;
                  } else if (response_other == MESSAGE_DATA_EXPUNGE) {
                        rec_queue(REC_EXPUNGE, responded_other_number);
                        if (had_expunge < 0)
                              had_expunge = 0;
                        had_expunge++;
                        expunged_messages++;
                  }
            }
            complete = 0;
            if (response_type == RESPONSE_TAGGED) {
                  if (asccasecmp(responded_tag, tag(0)) == 0)
                        complete |= 1;
                  else
                        goto again;
            }
            switch (response_status) {
            case RESPONSE_PREAUTH:
                  mp->mb_active &= ~MB_PREAUTH;
                  /*FALLTHRU*/
            case RESPONSE_OK:
            okay: ok = OKAY;
                  complete |= 2;
                  break;
            case RESPONSE_NO:
            case RESPONSE_BAD:
            stop: ok = STOP;
                  complete |= 2;
                  if (errprnt)
                        fprintf(stderr, catgets(catd, CATSET, 218,
                              "IMAP error: %s"), responded_text);
                  break;
            case RESPONSE_UNKNOWN:  /* does not happen */
            case RESPONSE_BYE:
                  i = mp->mb_active;
                  mp->mb_active = MB_NONE;
                  if (i & MB_BYE)
                        goto okay;
                  else
                        goto stop;
            case RESPONSE_OTHER:
                  ok = OKAY;
            }
            if (response_status != RESPONSE_OTHER &&
                        ascncasecmp(responded_text, "[ALERT] ", 8) == 0)
                  fprintf(stderr, "IMAP alert: %s", &responded_text[8]);
            if (complete == 3)
                  mp->mb_active &= ~MB_COMD;
      } else {
            ok = STOP;
            mp->mb_active = MB_NONE;
      }
      return ok;
}

static enum okay 
imap_parse_list(void)
{
      char  *cp;

      cp = responded_other_text;
      list_attributes = LIST_NONE;
      if (*cp == '(') {
            while (*cp && *cp != ')') {
                  if (*cp == '\\') {
                        if (ascncasecmp(&cp[1], "Noinferiors ", 12)
                                    == 0) {
                              list_attributes |= LIST_NOINFERIORS;
                              cp += 12;
                        } else if (ascncasecmp(&cp[1], "Noselect ", 9)
                                    == 0) {
                              list_attributes |= LIST_NOSELECT;
                              cp += 9;
                        } else if (ascncasecmp(&cp[1], "Marked ", 7)
                                    == 0) {
                              list_attributes |= LIST_MARKED;
                              cp += 7;
                        } else if (ascncasecmp(&cp[1], "Unmarked ", 9)
                                    == 0) {
                              list_attributes |= LIST_UNMARKED;
                              cp += 9;
                        }
                  }
                  cp++;
            }
            if (*++cp != ' ')
                  return STOP;
            while (*cp == ' ')
                  cp++;
      }
      list_hierarchy_delimiter = EOF;
      if (*cp == '"') {
            if (*++cp == '\\')
                  cp++;
            list_hierarchy_delimiter = *cp++ & 0377;
            if (cp[0] != '"' || cp[1] != ' ')
                  return STOP;
            cp++;
      } else if (cp[0] == 'N' && cp[1] == 'I' && cp[2] == 'L' &&
                  cp[3] == ' ') {
            list_hierarchy_delimiter = EOF;
            cp += 3;
      }
      while (*cp == ' ')
            cp++;
      list_name = cp;
      while (*cp && *cp != '\r')
            cp++;
      *cp = '\0';
      return OKAY;
}

static enum okay 
imap_finish(struct mailbox *mp)
{
      while (mp->mb_sock.s_fd > 0 && mp->mb_active & MB_COMD)
            imap_answer(mp, 1);
      return OKAY;
}

static void 
imap_timer_off(void)
{
      if (imapkeepalive > 0) {
            alarm(0);
            safe_signal(SIGALRM, savealrm);
      }
}

static void 
imapcatch(int s)
{
      if (reset_tio)
            tcsetattr(0, TCSADRAIN, &otio);
      switch (s) {
      case SIGINT:
            fprintf(stderr, catgets(catd, CATSET, 102, "Interrupt\n"));
            siglongjmp(imapjmp, 1);
            /*NOTREACHED*/
      case SIGPIPE:
            fprintf(stderr, "Received SIGPIPE during IMAP operation\n");
            break;
      }
}

static void
maincatch(int s)
{
      if (interrupts++ == 0) {
            fprintf(stderr, catgets(catd, CATSET, 102, "Interrupt\n"));
            return;
      }
      onintr(0);
}

static enum okay 
imap_noop1(struct mailbox *mp)
{
      char  o[LINESIZE];
      FILE  *queuefp = NULL;

      snprintf(o, sizeof o, "%s NOOP\r\n", tag(1));
      IMAP_OUT(o, MB_COMD, return STOP)
      IMAP_ANSWER()
      return OKAY;
}

enum okay 
imap_noop(void)
{
      sighandler_type   saveint, savepipe;
      enum okay   ok = STOP;

      (void)&saveint;
      (void)&savepipe;
      (void)&ok;
      if (mb.mb_type != MB_IMAP)
            return STOP;
      verbose = value("verbose") != NULL;
      imaplock = 1;
      if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
            safe_signal(SIGINT, maincatch);
      savepipe = safe_signal(SIGPIPE, SIG_IGN);
      if (sigsetjmp(imapjmp, 1) == 0) {
            if (savepipe != SIG_IGN)
                  safe_signal(SIGPIPE, imapcatch);
            ok = imap_noop1(&mb);
      }
      safe_signal(SIGINT, saveint);
      safe_signal(SIGPIPE, savepipe);
      imaplock = 0;
      if (interrupts)
            onintr(0);
      return ok;
}

static void 
rec_queue(enum rec_type type, unsigned long count)
{
      struct record     *rp;

      rp = scalloc(1, sizeof *rp);
      rp->rec_type = type;
      rp->rec_count = count;
      if (record && recend) {
            recend->rec_next = rp;
            recend = rp;
      } else
            record = recend = rp;
}

static enum okay 
rec_dequeue(void)
{
      struct message    *omessage;
      enum okay   ok = OKAY;
      struct record     *rp = record, *rq = NULL;
      unsigned long     exists = 0;
      long  i;

      if (record == NULL)
            return STOP;
      omessage = message;
      message = smalloc((msgCount+1) * sizeof *message);
      if (msgCount)
            memcpy(message, omessage, msgCount * sizeof *message);
      memset(&message[msgCount], 0, sizeof *message);
      while (rp) {
            switch (rp->rec_type) {
            case REC_EXISTS:
                  exists = rp->rec_count;
                  break;
            case REC_EXPUNGE:
                  if (rp->rec_count == 0) {
                        ok = STOP;
                        break;
                  }
                  if (rp->rec_count > msgCount) {
                        if (exists == 0 || rp->rec_count > exists--)
                              ok = STOP;
                        break;
                  }
                  if (exists > 0)
                        exists--;
                  delcache(&mb, &message[rp->rec_count-1]);
                  memmove(&message[rp->rec_count-1],
                        &message[rp->rec_count],
                        (msgCount - rp->rec_count + 1) *
                              sizeof *message);
                  msgCount--;
                  /*
                   * If the message was part of a collapsed thread,
                   * the m_collapsed field of one of its ancestors
                   * should be incremented. It seems hardly possible
                   * to do this with the current message structure,
                   * though. The result is that a '+' may be shown
                   * in the header summary even if no collapsed
                   * children exists.
                   */
                  break;
            }
            free(rq);
            rq = rp;
            rp = rp->rec_next;
      }
      free(rq);
      record = recend = NULL;
      if (ok == OKAY && exists > msgCount) {
            message = srealloc(message,
                        (exists + 1) * sizeof *message);
            memset(&message[msgCount], 0,
                        (exists - msgCount + 1) * sizeof *message);
            for (i = msgCount; i < exists; i++)
                  imap_init(&mb, i);
            imap_flags(&mb, msgCount+1, exists);
            msgCount = exists;
      }
      if (ok == STOP) {
            free(message);
            message = omessage;
      }
      return ok;
}

static void 
rec_rmqueue(void)
{
      struct record     *rp, *rq = NULL;

      for (rp = record; rp; rp = rp->rec_next) {
            free(rq);
            rq = rp;
      }
      free(rq);
      record = recend = NULL;
}

/*ARGSUSED*/
static void 
imapalarm(int s)
{
      sighandler_type   saveint;
      sighandler_type savepipe;

      if (imaplock++ == 0) {
            if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
                  safe_signal(SIGINT, maincatch);
            savepipe = safe_signal(SIGPIPE, SIG_IGN);
            if (sigsetjmp(imapjmp, 1)) {
                  safe_signal(SIGINT, saveint);
                  safe_signal(SIGPIPE, savepipe);
                  goto brk;
            }
            if (savepipe != SIG_IGN)
                  safe_signal(SIGPIPE, imapcatch);
            if (imap_noop1(&mb) != OKAY) {
                  safe_signal(SIGINT, saveint);
                  safe_signal(SIGPIPE, savepipe);
                  goto out;
            }
            safe_signal(SIGINT, saveint);
            safe_signal(SIGPIPE, savepipe);
      }
brk:  alarm(imapkeepalive);
out:  imaplock--;
}

static int 
imap_use_starttls(const char *uhp)
{
      char  *var;

      if (value("imap-use-starttls"))
            return 1;
      var = savecat("imap-use-starttls-", uhp);
      return value(var) != NULL;
}

static enum okay 
imap_preauth(struct mailbox *mp, const char *xserver, const char *uhp)
{
      char  *server, *cp;

      mp->mb_active |= MB_PREAUTH;
      imap_answer(mp, 1);
      if ((cp = strchr(xserver, ':')) != NULL) {
            server = salloc(cp - xserver + 1);
            memcpy(server, xserver, cp - xserver);
            server[cp - xserver] = '\0';
      } else
            server = (char *)xserver;
#ifdef      USE_SSL
      if (mp->mb_sock.s_use_ssl == 0 && imap_use_starttls(uhp)) {
            FILE  *queuefp = NULL;
            char  o[LINESIZE];

            snprintf(o, sizeof o, "%s STARTTLS\r\n", tag(1));
            IMAP_OUT(o, MB_COMD, return STOP);
            IMAP_ANSWER()
            if (ssl_open(server, &mp->mb_sock, uhp) != OKAY)
                  return STOP;
      }
#else /* !USE_SSL */
      if (imap_use_starttls(uhp)) {
            fprintf(stderr, "No SSL support compiled in.\n");
            return STOP;
      }
#endif      /* !USE_SSL */
      imap_capability(mp);
      return OKAY;
}

static enum okay 
imap_capability(struct mailbox *mp)
{
      char  o[LINESIZE];
      FILE  *queuefp = NULL;
      enum okay   ok = STOP;
      const char  *cp;

      snprintf(o, sizeof o, "%s CAPABILITY\r\n", tag(1));
      IMAP_OUT(o, MB_COMD, return STOP)
      while (mp->mb_active & MB_COMD) {
            ok = imap_answer(mp, 0);
            if (response_status == RESPONSE_OTHER &&
                        response_other == CAPABILITY_DATA) {
                  cp = responded_other_text;
                  while (*cp) {
                        while (spacechar(*cp&0377))
                              cp++;
                        if (strncmp(cp, "UIDPLUS", 7) == 0 &&
                                    spacechar(cp[7]&0377))
                              /* RFC 2359 */
                              mp->mb_flags |= MB_UIDPLUS;
                        while (*cp && !spacechar(*cp&0377))
                              cp++;
                  }
            }
      }
      return ok;
}

static enum okay 
imap_auth(struct mailbox *mp, const char *uhp, char *xuser, const char *pass)
{
      char  *var;
      char  *auth;

      if (!(mp->mb_active & MB_PREAUTH))
            return OKAY;
      if ((auth = value("imap-auth")) == NULL) {
            var = ac_alloc(strlen(uhp) + 11);
            strcpy(var, "imap-auth-");
            strcpy(&var[10], uhp);
            auth = value(var);
            ac_free(var);
      }
      if (auth == NULL || strcmp(auth, "login") == 0)
            return imap_login(mp, xuser, pass);
      if (strcmp(auth, "cram-md5") == 0)
            return imap_cram_md5(mp, xuser, pass);
      if (strcmp(auth, "gssapi") == 0) {
#ifdef      USE_GSSAPI
            return imap_gss(mp, xuser);
#else /* !USE_GSSAPI */
            fprintf(stderr, "No GSSAPI support compiled in.\n");
            return STOP;
#endif      /* !USE_GSSAPI */
      }
      fprintf(stderr, "Unknown IMAP authentication method: \"%s\"\n", auth);
      return STOP;
}

/*
 * Implementation of RFC 2194.
 */
static enum okay 
imap_cram_md5(struct mailbox *mp, char *xuser, const char *xpass)
{
      char o[LINESIZE];
      const char  *user, *pass;
      char  *cp;
      FILE  *queuefp = NULL;
      enum okay   ok = STOP;

retry:      if (xuser == NULL) {
            if ((user = getuser()) == NULL)
                  return STOP;
      } else
            user = xuser;
      if (xpass == NULL) {
            if ((pass = getpassword(&otio, &reset_tio, NULL)) == NULL)
                  return STOP;
      } else
            pass = xpass;
      snprintf(o, sizeof o, "%s AUTHENTICATE CRAM-MD5\r\n", tag(1));
      IMAP_OUT(o, 0, return STOP)
      imap_answer(mp, 1);
      if (response_type != RESPONSE_CONT)
            return STOP;
      cp = cram_md5_string(user, pass, responded_text);
      IMAP_OUT(cp, MB_COMD, return STOP)
      while (mp->mb_active & MB_COMD)
            ok = imap_answer(mp, 1);
      if (ok == STOP) {
            xpass = NULL;
            goto retry;
      }
      return ok;
}

static enum okay 
imap_login(struct mailbox *mp, char *xuser, const char *xpass)
{
      char o[LINESIZE];
      const char  *user, *pass;
      FILE  *queuefp = NULL;
      enum okay   ok = STOP;

retry:      if (xuser == NULL) {
            if ((user = getuser()) == NULL)
                  return STOP;
      } else
            user = xuser;
      if (xpass == NULL) {
            if ((pass = getpassword(&otio, &reset_tio, NULL)) == NULL)
                  return STOP;
      } else
            pass = xpass;
      snprintf(o, sizeof o, "%s LOGIN %s %s\r\n",
                  tag(1), imap_quotestr(user), imap_quotestr(pass));
      IMAP_OUT(o, MB_COMD, return STOP)
      while (mp->mb_active & MB_COMD)
            ok = imap_answer(mp, 1);
      if (ok == STOP) {
            xpass = NULL;
            goto retry;
      }
      return OKAY;
}

#ifdef      USE_GSSAPI
#include "imap_gssapi.c"
#endif      /* USE_GSSAPI */

enum okay
imap_select(struct mailbox *mp, off_t *size, int *count, const char *mbx)
{
      enum okay ok = OKAY;
      char  *cp;
      char o[LINESIZE];
      FILE  *queuefp = NULL;

      mp->mb_uidvalidity = 0;
      snprintf(o, sizeof o, "%s SELECT %s\r\n", tag(1), imap_quotestr(mbx));
      IMAP_OUT(o, MB_COMD, return STOP)
      while (mp->mb_active & MB_COMD) {
            ok = imap_answer(mp, 1);
            if (response_status != RESPONSE_OTHER &&
                        (cp = asccasestr(responded_text,
                                     "[UIDVALIDITY ")) != NULL)
                  mp->mb_uidvalidity = atol(&cp[13]);
      }
      *count = had_exists > 0 ? had_exists : 0;
      if (response_status != RESPONSE_OTHER &&
                  ascncasecmp(responded_text, "[READ-ONLY] ", 12)
                  == 0)
            mp->mb_perm = 0;
      return ok;
}

static enum okay 
imap_flags(struct mailbox *mp, unsigned X, unsigned Y)
{
      char o[LINESIZE];
      FILE  *queuefp = NULL;
      char  *cp;
      struct message *m;
      unsigned    x = X, y = Y;
      int n;

      snprintf(o, sizeof o, "%s FETCH %u:%u (FLAGS UID)\r\n", tag(1), x, y);
      IMAP_OUT(o, MB_COMD, return STOP)
      while (mp->mb_active & MB_COMD) {
            imap_answer(mp, 1);
            if (response_status == RESPONSE_OTHER &&
                        response_other == MESSAGE_DATA_FETCH) {
                  n = responded_other_number;
                  if (n < x || n > y)
                        continue;
                  m = &message[n-1];
                  m->m_xsize = 0;
            } else
                  continue;
            if ((cp = asccasestr(responded_other_text, "FLAGS ")) != NULL) {
                  cp += 5;
                  while (*cp == ' ')
                        cp++;
                  if (*cp == '(')
                        imap_getflags(cp, &cp, &m->m_flag);
            }
            if ((cp = asccasestr(responded_other_text, "UID ")) != NULL)
                  m->m_uid = strtoul(&cp[4], NULL, 10);
            getcache1(mp, m, NEED_UNSPEC, 1);
            m->m_flag &= ~MHIDDEN;
      }
      while (x <= y && message[x-1].m_xsize && message[x-1].m_time)
            x++;
      while (y > x && message[y-1].m_xsize && message[y-1].m_time)
            y--;
      if (x <= y) {
            snprintf(o, sizeof o,
                  "%s FETCH %u:%u (RFC822.SIZE INTERNALDATE)\r\n",
                  tag(1), x, y);
            IMAP_OUT(o, MB_COMD, return STOP)
            while (mp->mb_active & MB_COMD) {
                  imap_answer(mp, 1);
                  if (response_status == RESPONSE_OTHER &&
                              response_other == MESSAGE_DATA_FETCH) {
                        n = responded_other_number;
                        if (n < x || n > y)
                              continue;
                        m = &message[n-1];
                  } else
                        continue;
                  if ((cp = asccasestr(responded_other_text,
                                    "RFC822.SIZE ")) != NULL)
                        m->m_xsize = strtol(&cp[12], NULL, 10);
                  if ((cp = asccasestr(responded_other_text,
                                    "INTERNALDATE ")) != NULL)
                        m->m_time = imap_read_date_time(&cp[13]);
            }
      }
      for (n = X; n <= Y; n++)
            putcache(mp, &message[n-1]);
      return OKAY;
}

static void 
imap_init(struct mailbox *mp, int n)
{
      struct message *m = &message[n];

      m->m_flag = MUSED|MNOFROM;
      m->m_block = 0;
      m->m_offset = 0;
}

static void 
imap_setptr(struct mailbox *mp, int newmail, int transparent, int *prevcount)
{
      struct message    *omessage = 0;
      int i, omsgCount = 0;
      enum okay   dequeued = STOP;

      if (newmail || transparent) {
            omessage = message;
            omsgCount = msgCount;
      }
      if (newmail)
            dequeued = rec_dequeue();
      if (had_exists >= 0) {
            if (dequeued != OKAY)
                  msgCount = had_exists;
            had_exists = -1;
      }
      if (had_expunge >= 0) {
            if (dequeued != OKAY)
                  msgCount -= had_expunge;
            had_expunge = -1;
      }
      if (newmail && expunged_messages)
            printf("Expunged %ld message%s.\n",
                        expunged_messages,
                        expunged_messages != 1 ? "s" : "");
      *prevcount = omsgCount - expunged_messages;
      expunged_messages = 0;
      if (msgCount < 0) {
            fputs("IMAP error: Negative message count\n", stderr);
            msgCount = 0;
      }
      if (dequeued != OKAY) {
            message = scalloc(msgCount + 1, sizeof *message);
            for (i = 0; i < msgCount; i++)
                  imap_init(mp, i);
            if (!newmail && mp->mb_type == MB_IMAP)
                  initcache(mp);
            if (msgCount > 0)
                  imap_flags(mp, 1, msgCount);
            message[msgCount].m_size = 0;
            message[msgCount].m_lines = 0;
            rec_rmqueue();
      }
      if (newmail || transparent)
            transflags(omessage, omsgCount, transparent);
      else
            setdot(message);
}

static char *
imap_have_password(const char *server)
{
      char *var, *cp;

      var = ac_alloc(strlen(server) + 10);
      strcpy(var, "password-");
      strcpy(&var[9], server);
      if ((cp = value(var)) != NULL)
            cp = savestr(cp);
      ac_free(var);
      return cp;
}

static void
imap_split(char **server, const char **sp, int *use_ssl, const char **cp,
            char **uhp, char **mbx, const char **pass, char **user)
{
      *sp = *server;
      if (strncmp(*sp, "imap://", 7) == 0) {
            *sp = &(*sp)[7];
            *use_ssl = 0;
#ifdef      USE_SSL
      } else if (strncmp(*sp, "imaps://", 8) == 0) {
            *sp = &(*sp)[8];
            *use_ssl = 1;
#endif      /* USE_SSL */
      }
      if ((*cp = strchr(*sp, '/')) != NULL && (*cp)[1] != '\0') {
            *uhp = savestr((char *)(*sp));
            (*uhp)[*cp - *sp] = '\0';
            *mbx = (char *)&(*cp)[1];
      } else {
            if (*cp)
                  (*server)[*cp - *server] = '\0';
            *uhp = (char *)(*sp);
            *mbx = "INBOX";
      }
      *pass = imap_have_password(*uhp);
      if ((*cp = last_at_before_slash(*uhp)) != NULL) {
            *user = salloc(*cp - *uhp + 1);
            memcpy(*user, *uhp, *cp - *uhp);
            (*user)[*cp - *uhp] = '\0';
            *sp = &(*cp)[1];
            *user = strdec(*user);
      } else {
            *user = NULL;
            *sp = *uhp;
      }
}

int 
imap_setfile(const char *xserver, int newmail, int isedit)
{
      return imap_setfile1(xserver, newmail, isedit, 0);
}

static int 
imap_setfile1(const char *xserver, int newmail, int isedit, int transparent)
{
      struct sock so;
      sighandler_type   saveint;
      sighandler_type savepipe;
      char *server, *user, *account;
      const char *cp, *sp, *pass;
      char  *uhp, *mbx;
      int use_ssl = 0;
      enum  mbflags     same_flags;
      int   prevcount = 0;

      (void)&sp;
      (void)&use_ssl;
      (void)&saveint;
      (void)&savepipe;
      server = savestr((char *)xserver);
      verbose = value("verbose") != NULL;
      if (newmail) {
            saveint = safe_signal(SIGINT, SIG_IGN);
            savepipe = safe_signal(SIGPIPE, SIG_IGN);
            if (saveint != SIG_IGN)
                  safe_signal(SIGINT, imapcatch);
            if (savepipe != SIG_IGN)
                  safe_signal(SIGPIPE, imapcatch);
            imaplock = 1;
            goto newmail;
      }
      same_flags = mb.mb_flags;
      same_imap_account = 0;
      sp = protbase(server);
      if (mb.mb_imap_account) {
            if (mb.mb_sock.s_fd > 0 &&
                        strcmp(mb.mb_imap_account, sp) == 0 &&
                        disconnected(mb.mb_imap_account) == 0)
                  same_imap_account = 1;
      }
      account = sstrdup(sp);
      imap_split(&server, &sp, &use_ssl, &cp, &uhp, &mbx, &pass, &user);
      so.s_fd = -1;
      if (!same_imap_account) {
            if (!disconnected(account) &&
                        sopen(sp, &so, use_ssl, uhp,
                        use_ssl ? "imaps" : "imap", verbose) != OKAY)
            return -1;
      } else
            so = mb.mb_sock;
      if (!transparent)
            quit();
      edit = isedit;
      free(mb.mb_imap_account);
      mb.mb_imap_account = account;
      if (!same_imap_account) {
            if (mb.mb_sock.s_fd >= 0)
                  sclose(&mb.mb_sock);
      }
      same_imap_account = 0;
      if (!transparent) {
            if (mb.mb_itf) {
                  fclose(mb.mb_itf);
                  mb.mb_itf = NULL;
            }
            if (mb.mb_otf) {
                  fclose(mb.mb_otf);
                  mb.mb_otf = NULL;
            }
            free(mb.mb_imap_mailbox);
            mb.mb_imap_mailbox = sstrdup(mbx);
            initbox(server);
      }
      mb.mb_type = MB_VOID;
      mb.mb_active = MB_NONE;;
      imaplock = 1;
      saveint = safe_signal(SIGINT, SIG_IGN);
      savepipe = safe_signal(SIGPIPE, SIG_IGN);
      if (sigsetjmp(imapjmp, 1)) {
            sclose(&so);
            safe_signal(SIGINT, saveint);
            safe_signal(SIGPIPE, savepipe);
            imaplock = 0;
            return -1;
      }
      if (saveint != SIG_IGN)
            safe_signal(SIGINT, imapcatch);
      if (savepipe != SIG_IGN)
            safe_signal(SIGPIPE, imapcatch);
      if (mb.mb_sock.s_fd < 0) {
            if (disconnected(mb.mb_imap_account)) {
                  if (cache_setptr(transparent) == STOP)
                        fprintf(stderr,
                              "Mailbox \"%s\" is not cached.\n",
                              server);
                  goto done;
            }
            if ((cp = value("imap-keepalive")) != NULL) {
                  if ((imapkeepalive = strtol(cp, NULL, 10)) > 0) {
                        savealrm = safe_signal(SIGALRM, imapalarm);
                        alarm(imapkeepalive);
                  }
            }
            mb.mb_sock = so;
            mb.mb_sock.s_desc = "IMAP";
            mb.mb_sock.s_onclose = imap_timer_off;
            if (imap_preauth(&mb, sp, uhp) != OKAY ||
                        imap_auth(&mb, uhp, user, pass) != OKAY) {
                  sclose(&mb.mb_sock);
                  imap_timer_off();
                  safe_signal(SIGINT, saveint);
                  safe_signal(SIGPIPE, savepipe);
                  imaplock = 0;
                  return -1;
            }
      } else      /* same account */
            mb.mb_flags |= same_flags;
      mb.mb_perm = Rflag ? 0 : MB_DELE;
      mb.mb_type = MB_IMAP;
      cache_dequeue(&mb);
      if (imap_select(&mb, &mailsize, &msgCount, mbx) != OKAY) {
            /*sclose(&mb.mb_sock);
            imap_timer_off();*/
            safe_signal(SIGINT, saveint);
            safe_signal(SIGPIPE, savepipe);
            imaplock = 0;
            mb.mb_type = MB_VOID;
            return -1;
      }
newmail:
      imap_setptr(&mb, newmail, transparent, &prevcount);
done: setmsize(msgCount);
      if (!newmail && !transparent)
            sawcom = 0;
      safe_signal(SIGINT, saveint);
      safe_signal(SIGPIPE, savepipe);
      imaplock = 0;
      if (!newmail && mb.mb_type == MB_IMAP)
            purgecache(&mb, message, msgCount);
      if ((newmail || transparent) && mb.mb_sorted) {
            mb.mb_threaded = 0;
            sort((void *)-1);
      }
      if (!newmail && !edit && msgCount == 0) {
            if ((mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE) &&
                        value("emptystart") == NULL)
                  fprintf(stderr, catgets(catd, CATSET, 258,
                        "No mail at %s\n"), server);
            return 1;
      }
      if (newmail)
            newmailinfo(prevcount);
      return 0;
}

static int
imap_fetchdata(struct mailbox *mp, struct message *m, size_t expected,
            int need,
            const char *head, size_t headsize, long headlines)
{
      char  *line = NULL, *lp;
      size_t      linesize = 0, linelen, size = 0;
      int   emptyline = 0, lines = 0, excess = 0;
      off_t offset;

      fseek(mp->mb_otf, 0L, SEEK_END);
      offset = ftell(mp->mb_otf);
      if (head)
            fwrite(head, 1, headsize, mp->mb_otf);
      while (sgetline(&line, &linesize, &linelen, &mp->mb_sock) > 0) {
            lp = line;
            if (linelen > expected) {
                  excess = linelen - expected;
                  linelen = expected;
            }
            /*
             * Need to mask 'From ' lines. This cannot be done properly
             * since some servers pass them as 'From ' and others as
             * '>From '. Although one could identify the first kind of
             * server in principle, it is not possible to identify the
             * second as '>From ' may also come from a server of the
             * first type as actual data. So do what is absolutely
             * necessary only - mask 'From '.
             *
             * If the line is the first line of the message header, it
             * is likely a real 'From ' line. In this case, it is just
             * ignored since it violates all standards.
             */
            if (lp[0] == 'F' && lp[1] == 'r' && lp[2] == 'o' &&
                        lp[3] == 'm' && lp[4] == ' ') {
                  if (lines + headlines != 0) {
                        fputc('>', mp->mb_otf);
                        size++;
                  } else
                        goto skip;
            }
            if (lp[linelen-1] == '\n' && (linelen == 1 ||
                              lp[linelen-2] == '\r')) {
                  emptyline = linelen <= 2;
                  if (linelen > 2) {
                        fwrite(lp, 1, linelen - 2, mp->mb_otf);
                        size += linelen - 1;
                  } else
                        size++;
                  fputc('\n', mp->mb_otf);
            } else {
                  emptyline = 0;
                  fwrite(lp, 1, linelen, mp->mb_otf);
                  size += linelen;
            }
            lines++;
      skip: if ((expected -= linelen) <= 0)
                  break;
      }
      if (!emptyline) {
            /*
             * This is very ugly; but some IMAP daemons don't end a
             * message with \r\n\r\n, and we need \n\n for mbox format.
             */
            fputc('\n', mp->mb_otf);
            lines++;
            size++;
      }
      fflush(mp->mb_otf);
      if (m != NULL) {
            m->m_size = size + headsize;
            m->m_lines = lines + headlines;
            m->m_block = mailx_blockof(offset);
            m->m_offset = mailx_offsetof(offset);
            switch (need) {
            case NEED_HEADER:
                  m->m_have |= HAVE_HEADER;
                  break;
            case NEED_BODY:
                  m->m_have |= HAVE_HEADER|HAVE_BODY;
                  m->m_xlines = m->m_lines;
                  m->m_xsize = m->m_size;
                  break;
            }
      }
      free(line);
      return excess;
}

static void
imap_putstr(struct mailbox *mp, struct message *m, const char *str,
            const char *head, size_t headsize, long headlines)
{
      off_t offset;
      size_t      len;

      len = strlen(str);
      fseek(mp->mb_otf, 0L, SEEK_END);
      offset = ftell(mp->mb_otf);
      if (head)
            fwrite(head, 1, headsize, mp->mb_otf);
      if (len > 0) {
            fwrite(str, 1, len, mp->mb_otf);
            fputc('\n', mp->mb_otf);
            len++;
      }
      fflush(mp->mb_otf);
      if (m != NULL) {
            m->m_size = headsize + len;
            m->m_lines = headlines + 1;
            m->m_block = mailx_blockof(offset);
            m->m_offset = mailx_offsetof(offset);
            m->m_have |= HAVE_HEADER|HAVE_BODY;
            m->m_xlines = m->m_lines;
            m->m_xsize = m->m_size;
      }
}

static enum okay 
imap_get(struct mailbox *mp, struct message *m, enum needspec need)
{
      sighandler_type   saveint = SIG_IGN;
      sighandler_type savepipe = SIG_IGN;
      char o[LINESIZE], *cp = NULL, *item = NULL, *resp = NULL, *loc = NULL;
      size_t expected, headsize = 0;
      int number = m - message + 1;
      enum okay ok = STOP;
      FILE  *queuefp = NULL;
      char  *head = NULL;
      long  headlines = 0;
      struct message    mt;
      long  n = -1;
      unsigned long     u = 0;

      (void)&saveint;
      (void)&savepipe;
      (void)&number;
      (void)&need;
      (void)&cp;
      (void)&loc;
      (void)&ok;
      (void)&headsize;
      (void)&head;
      (void)&headlines;
      (void)&item;
      (void)&resp;
      (void)&u;
      verbose = value("verbose") != NULL;
      if (getcache(mp, m, need) == OKAY)
            return OKAY;
      if (mp->mb_type == MB_CACHE) {
            fprintf(stderr, "Message %u not available.\n", number);
            return STOP;
      }
      if (mp->mb_sock.s_fd < 0) {
            fprintf(stderr, "IMAP connection closed.\n");
            return STOP;
      }
      switch (need) {
      case NEED_HEADER:
            resp = item = "RFC822.HEADER";
            break;
      case NEED_BODY:
            item = "BODY.PEEK[]";
            resp = "BODY[]";
            if (m->m_flag & HAVE_HEADER && m->m_size) {
                  char  *hdr = smalloc(m->m_size);
                  fflush(mp->mb_otf);
                  if (fseek(mp->mb_itf, (long)mailx_positionof(m->m_block,
                                    m->m_offset), SEEK_SET) < 0 ||
                              fread(hdr, 1, m->m_size, mp->mb_itf)
                                    != m->m_size) {
                        free(hdr);
                        break;
                  }
                  head = hdr;
                  headsize = m->m_size;
                  headlines = m->m_lines;
                  item = "BODY.PEEK[TEXT]";
                  resp = "BODY[TEXT]";
            }
            break;
      case NEED_UNSPEC:
            return STOP;
      }
      imaplock = 1;
      savepipe = safe_signal(SIGPIPE, SIG_IGN);
      if (sigsetjmp(imapjmp, 1)) {
            safe_signal(SIGINT, saveint);
            safe_signal(SIGPIPE, savepipe);
            imaplock = 0;
            return STOP;
      }
      if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
            safe_signal(SIGINT, maincatch);
      if (savepipe != SIG_IGN)
            safe_signal(SIGPIPE, imapcatch);
      if (m->m_uid)
            snprintf(o, sizeof o,
                        "%s UID FETCH %lu (%s)\r\n",
                        tag(1), m->m_uid, item);
      else {
            if (check_expunged() == STOP)
                  goto out;
            snprintf(o, sizeof o,
                        "%s FETCH %u (%s)\r\n",
                        tag(1), number, item);
      }
      IMAP_OUT(o, MB_COMD, goto out)
      for (;;) {
            ok = imap_answer(mp, 1);
            if (ok == STOP)
                  break;
            if (response_status != RESPONSE_OTHER ||
                        response_other != MESSAGE_DATA_FETCH)
                  continue;
            if ((loc = asccasestr(responded_other_text, resp)) == NULL)
                  continue;
            if (m->m_uid) {
                  if ((cp = asccasestr(responded_other_text, "UID "))) {
                        u = atol(&cp[4]);
                        n = 0;
                  } else {
                        n = -1;
                        u = 0;
                  }
            } else
                  n = responded_other_number;
            if ((cp = strrchr(responded_other_text, '{')) == NULL) {
                  if (m->m_uid ? m->m_uid != u : n != number)
                        continue;
                  if ((cp = strchr(loc, '"')) != NULL) {
                        cp = imap_unquotestr(cp);
                        imap_putstr(mp, m, cp,
                                    head, headsize, headlines);
                  } else {
                        m->m_have |= HAVE_HEADER|HAVE_BODY;
                        m->m_xlines = m->m_lines;
                        m->m_xsize = m->m_size;
                  }
                  goto out;
            }
            expected = atol(&cp[1]);
            if (m->m_uid ? n == 0 && m->m_uid != u : n != number) {
                  imap_fetchdata(mp, NULL, expected, need, NULL, 0, 0);
                  continue;
            }
            mt = *m;
            imap_fetchdata(mp, &mt, expected, need,
                        head, headsize, headlines);
            if (n >= 0) {
                  commitmsg(mp, m, mt, mt.m_have);
                  break;
            }
            if (n == -1 && sgetline(&imapbuf, &imapbufsize, NULL,
                                    &mp->mb_sock) > 0) {
                  if (verbose)
                        fputs(imapbuf, stderr);
                  if ((cp = asccasestr(imapbuf, "UID ")) != NULL) {
                        u = atol(&cp[4]);
                        if (u == m->m_uid) {
                              commitmsg(mp, m, mt, mt.m_have);
                              break;
                        }
                  }
            }
      }
out:  while (mp->mb_active & MB_COMD)
            ok = imap_answer(mp, 1);
      if (saveint != SIG_IGN)
            safe_signal(SIGINT, saveint);
      if (savepipe != SIG_IGN)
            safe_signal(SIGPIPE, savepipe);
      imaplock--;
      if (ok == OKAY)
            putcache(mp, m);
      free(head);
      if (interrupts)
            onintr(0);
      return ok;
}

enum okay 
imap_header(struct message *m)
{
      return imap_get(&mb, m, NEED_HEADER);
}


enum okay 
imap_body(struct message *m)
{
      return imap_get(&mb, m, NEED_BODY);
}

static void 
commitmsg(struct mailbox *mp, struct message *to,
            struct message from, enum havespec have)
{
      to->m_size = from.m_size;
      to->m_lines = from.m_lines;
      to->m_block = from.m_block;
      to->m_offset = from.m_offset;
      to->m_have = have;
      if (have & HAVE_BODY) {
            to->m_xlines = from.m_lines;
            to->m_xsize = from.m_size;
      }
      putcache(mp, to);
}

static enum okay 
imap_fetchheaders (
    struct mailbox *mp,
    struct message *m,
    int bot,
    int top /* bot > top */
)
{
      char  o[LINESIZE], *cp;
      struct message    mt;
      size_t      expected;
      enum okay   ok;
      int   n = 0, u;
      FILE  *queuefp = NULL;

      if (m[bot].m_uid)
            snprintf(o, sizeof o,
                  "%s UID FETCH %lu:%lu (RFC822.HEADER)\r\n",
                  tag(1), m[bot-1].m_uid, m[top-1].m_uid);
      else {
            if (check_expunged() == STOP)
                  return STOP;
            snprintf(o, sizeof o,
                  "%s FETCH %u:%u (RFC822.HEADER)\r\n",
                  tag(1), bot, top);
      }
      IMAP_OUT(o, MB_COMD, return STOP)
      for (;;) {
            ok = imap_answer(mp, 1);
            if (response_status != RESPONSE_OTHER)
                  break;
            if (response_other != MESSAGE_DATA_FETCH)
                  continue;
            if (ok == STOP || (cp=strrchr(responded_other_text, '{')) == 0)
                  return STOP;
            if (asccasestr(responded_other_text, "RFC822.HEADER") == NULL)
                  continue;
            expected = atol(&cp[1]);
            if (m[bot-1].m_uid) {
                  if ((cp=asccasestr(responded_other_text, "UID "))) {
                        u = atol(&cp[4]);
                        for (n = bot; n <= top; n++)
                              if (m[n-1].m_uid == u)
                                    break;
                        if (n > top) {
                              imap_fetchdata(mp, NULL, expected,
                                          NEED_HEADER,
                                          NULL, 0, 0);
                              continue;
                        }
                  } else
                        n = -1;
            } else {
                  n = responded_other_number;
                  if (n <= 0 || n > msgCount) {
                        imap_fetchdata(mp, NULL, expected, NEED_HEADER,
                                    NULL, 0, 0);
                        continue;
                  }
            }
            imap_fetchdata(mp, &mt, expected, NEED_HEADER, NULL, 0, 0);
            if (n >= 0 && !(m[n-1].m_have & HAVE_HEADER))
                  commitmsg(mp, &m[n-1], mt, HAVE_HEADER);
            if (n == -1 && sgetline(&imapbuf, &imapbufsize, NULL,
                              &mp->mb_sock) > 0) {
                  if (verbose)
                        fputs(imapbuf, stderr);
                  if ((cp = asccasestr(imapbuf, "UID ")) != NULL) {
                        u = atol(&cp[4]);
                        for (n = bot; n <= top; n++)
                              if (m[n-1].m_uid == u)
                                    break;
                        if (n <= top && !(m[n-1].m_have & HAVE_HEADER))
                              commitmsg(mp, &m[n-1], mt, HAVE_HEADER);
                        n = 0;
                  }
            }
      }
      while (mp->mb_active & MB_COMD)
            ok = imap_answer(mp, 1);
      return ok;
}

void 
imap_getheaders(int bot, int top)
{
      sighandler_type   saveint, savepipe;
      enum okay   ok = STOP;
      int   i, chunk = 256;

      (void)&saveint;
      (void)&savepipe;
      (void)&ok;
      (void)&bot;
      (void)&top;
      verbose = value("verbose") != NULL;
      if (mb.mb_type == MB_CACHE)
            return;
      if (bot < 1)
            bot = 1;
      if (top > msgCount)
            top = msgCount;
      for (i = bot; i < top; i++) {
            if (message[i-1].m_have & HAVE_HEADER ||
                        getcache(&mb, &message[i-1], NEED_HEADER)
                        == OKAY)
                  bot = i+1;
            else
                  break;
      }
      for (i = top; i > bot; i--) {
            if (message[i-1].m_have & HAVE_HEADER ||
                        getcache(&mb, &message[i-1], NEED_HEADER)
                        == OKAY)
                  top = i-1;
            else
                  break;
      }
      if (bot >= top)
            return;
      imaplock = 1;
      if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
            safe_signal(SIGINT, maincatch);
      savepipe = safe_signal(SIGPIPE, SIG_IGN);
      if (sigsetjmp(imapjmp, 1) == 0) {
            if (savepipe != SIG_IGN)
                  safe_signal(SIGPIPE, imapcatch);
            for (i = bot; i <= top; i += chunk) {
                  ok = imap_fetchheaders(&mb, message, i,
                              i+chunk-1 < top ? i+chunk-1 : top);
                  if (interrupts)
                        onintr(0);
            }
      }
      safe_signal(SIGINT, saveint);
      safe_signal(SIGPIPE, savepipe);
      imaplock = 0;
}

static enum okay 
imap_exit(struct mailbox *mp)
{
      char  o[LINESIZE];
      FILE  *queuefp = NULL;

      verbose = value("verbose") != NULL;
      mp->mb_active |= MB_BYE;
      snprintf(o, sizeof o, "%s LOGOUT\r\n", tag(1));
      IMAP_OUT(o, MB_COMD, return STOP)
      IMAP_ANSWER()
      return OKAY;
}

static enum okay 
imap_delete(struct mailbox *mp, int n, struct message *m, int needstat)
{
      imap_store(mp, m, n, '+', "\\Deleted", needstat);
      if (mp->mb_type == MB_IMAP)
            delcache(mp, m);
      return OKAY;
}

static enum okay 
imap_close(struct mailbox *mp)
{
      char  o[LINESIZE];
      FILE  *queuefp = NULL;

      snprintf(o, sizeof o, "%s CLOSE\r\n", tag(1));
      IMAP_OUT(o, MB_COMD, return STOP)
      IMAP_ANSWER()
      return OKAY;
}

static enum okay 
imap_update(struct mailbox *mp)
{
      FILE *readstat = NULL;
      struct message *m;
      int dodel, c, gotcha = 0, held = 0, modflags = 0, needstat, stored = 0;

      verbose = value("verbose") != NULL;
      if (Tflag != NULL) {
            if ((readstat = Zopen(Tflag, "w", NULL)) == NULL)
                  Tflag = NULL;
      }
      if (!edit && mp->mb_perm != 0) {
            holdbits();
            for (m = &message[0], c = 0; m < &message[msgCount]; m++) {
                  if (m->m_flag & MBOX)
                        c++;
            }
            if (c > 0)
                  if (makembox() == STOP)
                        goto bypass;
      }
      for (m = &message[0], gotcha=0, held=0; m < &message[msgCount]; m++) {
            if (readstat != NULL && (m->m_flag & (MREAD|MDELETED)) != 0) {
                  char *id;

                  if ((id = hfield("message-id", m)) != NULL ||
                              (id = hfield("article-id", m)) != NULL)
                        fprintf(readstat, "%s\n", id);
            }
            if (mp->mb_perm == 0) {
                  dodel = 0;
            } else if (edit) {
                  dodel = m->m_flag & MDELETED;
            } else {
                  dodel = !((m->m_flag&MPRESERVE) ||
                              (m->m_flag&MTOUCH) == 0);
            }
            /*
             * Fetch the result after around each 800 STORE commands
             * sent (approx. 32k data sent). Otherwise, servers will
             * try to flush the return queue at some point, leading
             * to a deadlock if we are still writing commands but not
             * reading their results.
             */
            needstat = stored > 0 && stored % 800 == 0;
            if (dodel) {
                  imap_delete(mp, m-message+1, m, needstat);
                  stored++;
                  gotcha++;
            } else {
                  if ((m->m_flag&(MREAD|MSTATUS)) == (MREAD|MSTATUS)) {
                        imap_store(mp, m, m-message+1,
                                    '+', "\\Seen", needstat);
                        stored++;
                  }
                  if (m->m_flag & MFLAG) {
                        imap_store(mp, m, m-message+1,
                                    '+', "\\Flagged", needstat);
                        stored++;
                  }
                  if (m->m_flag & MUNFLAG) {
                        imap_store(mp, m, m-message+1,
                                    '-', "\\Flagged", needstat);
                        stored++;
                  }
                  if (m->m_flag & MANSWER) {
                        imap_store(mp, m, m-message+1,
                                    '+', "\\Answered", needstat);
                        stored++;
                  }
                  if (m->m_flag & MUNANSWER) {
                        imap_store(mp, m, m-message+1,
                                    '-', "\\Answered", needstat);
                        stored++;
                  }
                  if (m->m_flag & MDRAFT) {
                        imap_store(mp, m, m-message+1,
                                    '+', "\\Draft", needstat);
                        stored++;
                  }
                  if (m->m_flag & MUNDRAFT) {
                        imap_store(mp, m, m-message+1,
                                    '-', "\\Draft", needstat);
                        stored++;
                  }
                  if (mp->mb_type != MB_CACHE ||
                        !edit && (!(m->m_flag&(MBOXED|MSAVED|MDELETED))
                              || (m->m_flag &
                                    (MBOXED|MPRESERVE|MTOUCH)) ==
                                    (MPRESERVE|MTOUCH)) ||
                              edit && !(m->m_flag & MDELETED))
                        held++;
                  if (m->m_flag & MNEW) {
                        m->m_flag &= ~MNEW;
                        m->m_flag |= MSTATUS;
                  }
            }
      }
bypass:     if (readstat != NULL)
            Fclose(readstat);
      if (gotcha)
            imap_close(mp);
      for (m = &message[0]; m < &message[msgCount]; m++)
            if (!(m->m_flag&MUNLINKED) &&
                        m->m_flag&(MBOXED|MDELETED|MSAVED|MSTATUS|
                              MFLAG|MUNFLAG|MANSWER|MUNANSWER|
                              MDRAFT|MUNDRAFT)) {
                  putcache(mp, m);
                  modflags++;
            }
      if ((gotcha || modflags) && edit) {
            printf(catgets(catd, CATSET, 168, "\"%s\" "), mailname);
            printf(value("bsdcompat") || value("bsdmsgs") ?
                        catgets(catd, CATSET, 170, "complete\n") :
                        catgets(catd, CATSET, 212, "updated.\n"));
      } else if (held && !edit && mp->mb_perm != 0) {
            if (held == 1)
                  printf(catgets(catd, CATSET, 155,
                        "Held 1 message in %s\n"), mailname);
            else if (held > 1)
                  printf(catgets(catd, CATSET, 156,
                        "Held %d messages in %s\n"), held, mailname);
      }
      fflush(stdout);
      return OKAY;
}

void 
imap_quit(void)
{
      sighandler_type   saveint;
      sighandler_type savepipe;

      verbose = value("verbose") != NULL;
      if (mb.mb_type == MB_CACHE) {
            imap_update(&mb);
            return;
      }
      if (mb.mb_sock.s_fd < 0) {
            fprintf(stderr, "IMAP connection closed.\n");
            return;
      }
      imaplock = 1;
      saveint = safe_signal(SIGINT, SIG_IGN);
      savepipe = safe_signal(SIGPIPE, SIG_IGN);
      if (sigsetjmp(imapjmp, 1)) {
            safe_signal(SIGINT, saveint);
            safe_signal(SIGPIPE, saveint);
            imaplock = 0;
            return;
      }
      if (saveint != SIG_IGN)
            safe_signal(SIGINT, imapcatch);
      if (savepipe != SIG_IGN)
            safe_signal(SIGPIPE, imapcatch);
      imap_update(&mb);
      if (!same_imap_account) {
            imap_exit(&mb);
            sclose(&mb.mb_sock);
      }
      safe_signal(SIGINT, saveint);
      safe_signal(SIGPIPE, savepipe);
      imaplock = 0;
}

static enum okay 
imap_store(struct mailbox *mp, struct message *m, int n,
            int c, const char *sp, int needstat)
{
      char  o[LINESIZE];
      FILE  *queuefp = NULL;

      if (mp->mb_type == MB_CACHE && (queuefp = cache_queue(mp)) == NULL)
            return STOP;
      if (m->m_uid)
            snprintf(o, sizeof o,
                        "%s UID STORE %lu %cFLAGS (%s)\r\n",
                        tag(1), m->m_uid, c, sp);
      else {
            if (check_expunged() == STOP)
                  return STOP;
            snprintf(o, sizeof o,
                        "%s STORE %u %cFLAGS (%s)\r\n",
                        tag(1), n, c, sp);
      }
      IMAP_OUT(o, MB_COMD, return STOP)
      if (needstat)
            IMAP_ANSWER()
      else
            mb.mb_active &= ~MB_COMD;
      if (queuefp != NULL)
            Fclose(queuefp);
      return OKAY;
}

enum okay 
imap_undelete(struct message *m, int n)
{
      return imap_unstore(m, n, "\\Deleted");
}

enum okay 
imap_unread(struct message *m, int n)
{
      return imap_unstore(m, n, "\\Seen");
}

static enum okay 
imap_unstore(struct message *m, int n, const char *flag)
{
      sighandler_type   saveint, savepipe;
      enum okay   ok = STOP;

      (void)&saveint;
      (void)&savepipe;
      (void)&ok;
      verbose = value("verbose") != NULL;
      imaplock = 1;
      if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
            safe_signal(SIGINT, maincatch);
      savepipe = safe_signal(SIGPIPE, SIG_IGN);
      if (sigsetjmp(imapjmp, 1) == 0) {
            if (savepipe != SIG_IGN)
                  safe_signal(SIGPIPE, imapcatch);
            ok = imap_store(&mb, m, n, '-', flag, 1);
      }
      safe_signal(SIGINT, saveint);
      safe_signal(SIGPIPE, savepipe);
      imaplock = 0;
      if (interrupts)
            onintr(0);
      return ok;
}

static const char *
tag(int new)
{
      static char ts[20];
      static long n;

      if (new)
            n++;
      snprintf(ts, sizeof ts, "T%lu", n);
      return ts;
}

int 
imap_imap(void *vp)
{
      sighandler_type   saveint, savepipe;
      char  o[LINESIZE];
      enum okay   ok = STOP;
      struct mailbox    *mp = &mb;
      FILE  *queuefp = NULL;

      (void)&saveint;
      (void)&savepipe;
      (void)&ok;
      verbose = value("verbose") != NULL;
      if (mp->mb_type != MB_IMAP) {
            printf("Not operating on an IMAP mailbox.\n");
            return 1;
      }
      imaplock = 1;
      if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
            safe_signal(SIGINT, maincatch);
      savepipe = safe_signal(SIGPIPE, SIG_IGN);
      if (sigsetjmp(imapjmp, 1) == 0) {
            if (savepipe != SIG_IGN)
                  safe_signal(SIGPIPE, imapcatch);
            snprintf(o, sizeof o, "%s %s\r\n", tag(1), (char *)vp);
            IMAP_OUT(o, MB_COMD, goto out)
            while (mp->mb_active & MB_COMD) {
                  ok = imap_answer(mp, 0);
                  fputs(responded_text, stdout);
            }
      }
out:  safe_signal(SIGINT, saveint);
      safe_signal(SIGPIPE, savepipe);
      imaplock = 0;
      if (interrupts)
            onintr(0);
      return ok != OKAY;
}

int 
imap_newmail(int autoinc)
{
      if (autoinc && had_exists < 0 && had_expunge < 0) {
            verbose = value("verbose") != NULL;
            imaplock = 1;
            imap_noop();
            imaplock = 0;
      }
      if (had_exists == msgCount && had_expunge < 0)
            /*
             * Some servers always respond with EXISTS to NOOP. If
             * the mailbox has been changed but the number of messages
             * has not, an EXPUNGE must also had been sent; otherwise,
             * nothing has changed.
             */
            had_exists = -1;
      return had_expunge >= 0 ? 2 : had_exists >= 0 ? 1 : 0;
}

static char *
imap_putflags(int f)
{
      const char  *cp;
      char  *buf, *bp;

      bp = buf = salloc(100);
      if (f & (MREAD|MFLAGGED|MANSWERED|MDRAFTED)) {
            *bp++ = '(';
            if (f & MREAD) {
                  if (bp[-1] != '(')
                        *bp++ = ' ';
                  for (cp = "\\Seen"; *cp; cp++)
                        *bp++ = *cp;
            }
            if (f & MFLAGGED) {
                  if (bp[-1] != '(')
                        *bp++ = ' ';
                  for (cp = "\\Flagged"; *cp; cp++)
                        *bp++ = *cp;
            }
            if (f & MANSWERED) {
                  if (bp[-1] != '(')
                        *bp++ = ' ';
                  for (cp = "\\Answered"; *cp; cp++)
                        *bp++ = *cp;
            }
            if (f & MDRAFT) {
                  if (bp[-1] != '(')
                        *bp++ = ' ';
                  for (cp = "\\Draft"; *cp; cp++)
                        *bp++ = *cp;
            }
            *bp++ = ')';
            *bp++ = ' ';
      }
      *bp = '\0';
      return buf;
}

static void 
imap_getflags(const char *cp, char **xp, enum mflag *f)
{
      while (*cp != ')') {
            if (*cp == '\\') {
                  if (ascncasecmp(cp, "\\Seen", 5) == 0)
                        *f |= MREAD;
                  else if (ascncasecmp(cp, "\\Recent", 7) == 0)
                        *f |= MNEW;
                  else if (ascncasecmp(cp, "\\Deleted", 8) == 0)
                        *f |= MDELETED;
                  else if (ascncasecmp(cp, "\\Flagged", 8) == 0)
                        *f |= MFLAGGED;
                  else if (ascncasecmp(cp, "\\Answered", 9) == 0)
                        *f |= MANSWERED;
                  else if (ascncasecmp(cp, "\\Draft", 6) == 0)
                        *f |= MDRAFTED;
            }
            cp++;
      }
      if (xp)
            *xp = (char *)cp;
}

static enum okay
imap_append1(struct mailbox *mp, const char *name, FILE *fp,
            off_t off1, long xsize, enum mflag flag, time_t t)
{
      char  o[LINESIZE];
      char  *buf;
      size_t      bufsize, buflen, count;
      enum okay   ok = STOP;
      long  size, lines, ysize;
      int   twice = 0;
      FILE  *queuefp = NULL;

      if (mp->mb_type == MB_CACHE) {
            queuefp = cache_queue(mp);
            if (queuefp == NULL)
                  return STOP;
            ok = OKAY;
      }
      buf = smalloc(bufsize = LINESIZE);
      buflen = 0;
again:      size = xsize;
      count = fsize(fp);
      fseek(fp, off1, SEEK_SET);
      snprintf(o, sizeof o, "%s APPEND %s %s%s {%ld}\r\n",
                  tag(1), imap_quotestr(name),
                  imap_putflags(flag),
                  imap_make_date_time(t),
                  size);
      IMAP_OUT(o, MB_COMD, goto out)
      while (mp->mb_active & MB_COMD) {
            ok = imap_answer(mp, twice);
            if (response_type == RESPONSE_CONT)
                  break;
      }
      if (mp->mb_type != MB_CACHE && ok == STOP) {
            if (twice == 0)
                  goto trycreate;
            else
                  goto out;
      }
      lines = ysize = 0;
      while (size > 0) {
            fgetline(&buf, &bufsize, &count, &buflen, fp, 1);
            lines++;
            ysize += buflen;
            buf[buflen-1] = '\r';
            buf[buflen] = '\n';
            if (mp->mb_type != MB_CACHE)
                  swrite1(&mp->mb_sock, buf, buflen+1, 1);
            else if (queuefp)
                  fwrite(buf, 1, buflen+1, queuefp);
            size -= buflen+1;
      }
      if (mp->mb_type != MB_CACHE)
            swrite(&mp->mb_sock, "\r\n");
      else if (queuefp)
            fputs("\r\n", queuefp);
      while (mp->mb_active & MB_COMD) {
            ok = imap_answer(mp, 0);
            if (response_status == RESPONSE_NO /*&&
                        ascncasecmp(responded_text,
                              "[TRYCREATE] ", 12) == 0*/) {
      trycreate:  if (twice++) {
                        ok = STOP;
                        goto out;
                  }
                  snprintf(o, sizeof o, "%s CREATE %s\r\n",
                              tag(1),
                              imap_quotestr(name));
                  IMAP_OUT(o, MB_COMD, goto out);
                  while (mp->mb_active & MB_COMD)
                        ok = imap_answer(mp, 1);
                  if (ok == STOP)
                        goto out;
                  imap_created_mailbox++;
                  goto again;
            } else if (ok != OKAY)
                  fprintf(stderr, "IMAP error: %s", responded_text);
            else if (response_status == RESPONSE_OK &&
                        mp->mb_flags & MB_UIDPLUS)
                  imap_appenduid(mp, fp, t, off1, xsize, ysize, lines,
                              flag, name);
      }
out:  if (queuefp != NULL)
            Fclose(queuefp);
      free(buf);
      return ok;
}

static enum okay
imap_append0(struct mailbox *mp, const char *name, FILE *fp)
{
      char  *buf, *bp, *lp;
      size_t      bufsize, buflen, count;
      off_t off1 = -1, offs;
      int   inhead = 1;
      int   flag = MNEW|MNEWEST;
      long  size = 0;
      time_t      tim;
      enum okay   ok;

      buf = smalloc(bufsize = LINESIZE);
      buflen = 0;
      count = fsize(fp);
      offs = ftell(fp);
      time(&tim);
      do {
            bp = fgetline(&buf, &bufsize, &count, &buflen, fp, 1);
            if (bp == NULL || strncmp(buf, "From ", 5) == 0) {
                  if (off1 != (off_t)-1) {
                        ok=imap_append1(mp, name, fp, off1,
                                    size, flag, tim);
                        if (ok == STOP)
                              return STOP;
                        fseek(fp, offs+buflen, SEEK_SET);
                  }
                  off1 = offs + buflen;
                  size = 0;
                  inhead = 1;
                  flag = MNEW;
                  if (bp != NULL)
                        tim = unixtime(buf);
            } else
                  size += buflen+1;
            offs += buflen;
            if (bp && buf[0] == '\n')
                  inhead = 0;
            else if (bp && inhead && ascncasecmp(buf, "status", 6) == 0) {
                  lp = &buf[6];
                  while (whitechar(*lp&0377))
                        lp++;
                  if (*lp == ':')
                        while (*++lp != '\0')
                              switch (*lp) {
                              case 'R':
                                    flag |= MREAD;
                                    break;
                              case 'O':
                                    flag &= ~MNEW;
                                    break;
                              }
            } else if (bp && inhead &&
                        ascncasecmp(buf, "x-status", 8) == 0) {
                  lp = &buf[8];
                  while (whitechar(*lp&0377))
                        lp++;
                  if (*lp == ':')
                        while (*++lp != '\0')
                              switch (*lp) {
                              case 'F':
                                    flag |= MFLAGGED;
                                    break;
                              case 'A':
                                    flag |= MANSWERED;
                                    break;
                              case 'T':
                                    flag |= MDRAFTED;
                                    break;
                              }
            }
      } while (bp != NULL);
      free(buf);
      return OKAY;
}

enum okay
imap_append(const char *xserver, FILE *fp)
{
      sighandler_type   saveint, savepipe;
      char  *server, *uhp, *mbx, *user;
      const char  *sp, *cp, *pass;
      int   use_ssl;
      enum okay   ok = STOP;

      (void)&saveint;
      (void)&savepipe;
      (void)&ok;
      verbose = value("verbose") != NULL;
      server = savestr((char *)xserver);
      imap_split(&server, &sp, &use_ssl, &cp, &uhp, &mbx, &pass, &user);
      imaplock = 1;
      if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
            safe_signal(SIGINT, maincatch);
      savepipe = safe_signal(SIGPIPE, SIG_IGN);
      if (sigsetjmp(imapjmp, 1))
            goto out;
      if (savepipe != SIG_IGN)
            safe_signal(SIGPIPE, imapcatch);
      if ((mb.mb_type == MB_CACHE || mb.mb_sock.s_fd > 0) &&
                  mb.mb_imap_account &&
                  strcmp(protbase(server), mb.mb_imap_account) == 0) {
            ok = imap_append0(&mb, mbx, fp);
      }
      else {
            struct mailbox    mx;

            memset(&mx, 0, sizeof mx);
            if (disconnected(server) == 0) {
                  if (sopen(sp, &mx.mb_sock, use_ssl, uhp,
                              use_ssl ? "imaps" : "imap",
                              verbose) != OKAY)
                        goto fail;
                  mx.mb_sock.s_desc = "IMAP";
                  mx.mb_type = MB_IMAP;
                  mx.mb_imap_account = (char *)protbase(server);
                  mx.mb_imap_mailbox = mbx;
                  if (imap_preauth(&mx, sp, uhp) != OKAY ||
                              imap_auth(&mx, uhp, user, pass)!=OKAY) {
                        sclose(&mx.mb_sock);
                        goto fail;
                  }
                  ok = imap_append0(&mx, mbx, fp);
                  imap_exit(&mx);
                  sclose(&mx.mb_sock);
            } else {
                  mx.mb_imap_account = (char *)protbase(server);
                  mx.mb_imap_mailbox = mbx;
                  mx.mb_type = MB_CACHE;
                  ok = imap_append0(&mx, mbx, fp);
            }
      fail:;
      }
out:  safe_signal(SIGINT, saveint);
      safe_signal(SIGPIPE, savepipe);
      imaplock = 0;
      if (interrupts)
            onintr(0);
      return ok;
}

static enum okay 
imap_list1(struct mailbox *mp, const char *base, struct list_item **list,
            struct list_item **lend, int level)
{
      char  o[LINESIZE];
      enum okay   ok = STOP;
      char  *cp;
      const char  *bp;
      FILE  *queuefp = NULL;
      struct list_item  *lp;

      *list = *lend = NULL;
      snprintf(o, sizeof o, "%s LIST %s %%\r\n",
                  tag(1), imap_quotestr(base));
      IMAP_OUT(o, MB_COMD, return STOP);
      while (mp->mb_active & MB_COMD) {
            ok = imap_answer(mp, 1);
            if (response_status == RESPONSE_OTHER &&
                        response_other == MAILBOX_DATA_LIST &&
                        imap_parse_list() == OKAY) {
                  cp = imap_unquotestr(list_name);
                  lp = csalloc(1, sizeof *lp);
                  lp->l_name = cp;
                  for (bp = base; *bp && *bp == *cp; bp++)
                        cp++;
                  lp->l_base = *cp ? cp : savestr(base);
                  lp->l_attr = list_attributes;
                  lp->l_level = level+1;
                  lp->l_delim = list_hierarchy_delimiter;
                  if (*list && *lend) {
                        (*lend)->l_next = lp;
                        *lend = lp;
                  } else
                        *list = *lend = lp;
            }
      }
      return ok;
}

static enum okay
imap_list(struct mailbox *mp, const char *base, int strip, FILE *fp)
{
      struct list_item  *list, *lend, *lp, *lx, *ly;
      int   n;
      const char  *bp;
      char  *cp;
      int   depth;

      verbose = value("verbose") != NULL;
      depth = (cp = value("imap-list-depth")) != NULL ? atoi(cp) : 2;
      if (imap_list1(mp, base, &list, &lend, 0) == STOP)
            return STOP;
      if (list == NULL || lend == NULL)
            return OKAY;
      for (lp = list; lp; lp = lp->l_next)
            if (lp->l_delim != '/' && lp->l_delim != EOF &&
                        lp->l_level < depth &&
                        (lp->l_attr&LIST_NOINFERIORS) == 0) {
                  cp = salloc((n = strlen(lp->l_name)) + 2);
                  strcpy(cp, lp->l_name);
                  cp[n] = lp->l_delim;
                  cp[n+1] = '\0';
                  if (imap_list1(mp, cp, &lx, &ly, lp->l_level) == OKAY &&
                              lx && ly) {
                        lp->l_has_children = 1;
                        if (strcmp(cp, lx->l_name) == 0)
                              lx = lx->l_next;
                        if (lx) {
                              lend->l_next = lx;
                              lend = ly;
                        }
                  }
            }
      for (lp = list; lp; lp = lp->l_next) {
            if (strip) {
                  cp = lp->l_name;
                  for (bp = base; *bp && *bp == *cp; bp++)
                        cp++;
            } else
                  cp = lp->l_name;
            if ((lp->l_attr&LIST_NOSELECT) == 0)
                  fprintf(fp, "%s\n", *cp ? cp : base);
            else if (lp->l_has_children == 0)
                  fprintf(fp, "%s%c\n", *cp ? cp : base,
                        lp->l_delim != EOF ? lp->l_delim : '\n');
      }
      return OKAY;
}

void 
imap_folders(const char *name, int strip)
{
      sighandler_type   saveint, savepipe;
      const char  *fold, *cp, *sp;
      char  *tempfn;
      FILE  *fp;
      int   columnize = is_a_tty[1];

      (void)&saveint;
      (void)&savepipe;
      (void)&fp;
      (void)&fold;
      cp = protbase(name);
      sp = mb.mb_imap_account;
      if (strcmp(cp, sp)) {
            fprintf(stderr, "Cannot list folders on other than the "
                        "current IMAP account,\n\"%s\". "
                        "Try \"folders @\".\n", sp);
            return;
      }
      fold = protfile(name);
      if (columnize) {
            if ((fp = Ftemp(&tempfn, "Ri", "w+", 0600, 1)) == NULL) {
                  perror("tmpfile");
                  return;
            }
            rm(tempfn);
            Ftfree(&tempfn);
      } else
            fp = stdout;
      imaplock = 1;
      if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
            safe_signal(SIGINT, maincatch);
      savepipe = safe_signal(SIGPIPE, SIG_IGN);
      if (sigsetjmp(imapjmp, 1))
            goto out;
      if (savepipe != SIG_IGN)
            safe_signal(SIGPIPE, imapcatch);
      if (mb.mb_type == MB_CACHE)
            cache_list(&mb, fold, strip, fp);
      else
            imap_list(&mb, fold, strip, fp);
      imaplock = 0;
      if (interrupts) {
            if (columnize)
                  Fclose(fp);
            onintr(0);
      }
      fflush(fp);
      if (columnize) {
            rewind(fp);
            if (fsize(fp) > 0)
                  dopr(fp);
            else
                  fprintf(stderr, "Folder not found.\n");
      }
out:
      safe_signal(SIGINT, saveint);
      safe_signal(SIGPIPE, savepipe);
      if (columnize)
            Fclose(fp);
}

static void
dopr(FILE *fp)
{
      char  o[LINESIZE], *tempfn;
      int   c;
      long  n = 0, mx = 0, columns, width;
      FILE  *out;

      if ((out = Ftemp(&tempfn, "Ro", "w+", 0600, 1)) == NULL) {
            perror("tmpfile");
            return;
      }
      rm(tempfn);
      Ftfree(&tempfn);
      while ((c = getc(fp)) != EOF) {
            if (c == '\n') {
                  if (n > mx)
                        mx = n;
                  n = 0;
            } else
                  n++;
      }
      rewind(fp);
      width = scrnwidth;
      if (mx < width / 2) {
            columns = width / (mx+2);
            snprintf(o, sizeof o,
                        "sort | pr -%lu -w%lu -t",
                        columns, width);
      } else
            strncpy(o, "sort", sizeof o)[sizeof o - 1] = '\0';
      run_command(SHELL, 0, fileno(fp), fileno(out), "-c", o, NULL);
      try_pager(out);
      Fclose(out);
}

static enum okay 
imap_copy1(struct mailbox *mp, struct message *m, int n, const char *name)
{
      char  o[LINESIZE];
      const char  *qname;
      enum okay   ok = STOP;
      int   twice = 0;
      FILE  *queuefp = NULL;

      if (mp->mb_type == MB_CACHE) {
            if ((queuefp = cache_queue(mp)) == NULL)
                  return STOP;
            ok = OKAY;
      }
      qname = imap_quotestr(name = protfile(name));
      /*
       * Since it is not possible to set flags on the copy, recently
       * set flags must be set on the original to include it in the copy.
       */
      if ((m->m_flag&(MREAD|MSTATUS)) == (MREAD|MSTATUS))
            imap_store(mp, m, n, '+', "\\Seen", 0);
      if (m->m_flag&MFLAG)
            imap_store(mp, m, n, '+', "\\Flagged", 0);
      if (m->m_flag&MUNFLAG)
            imap_store(mp, m, n, '-', "\\Flagged", 0);
      if (m->m_flag&MANSWER)
            imap_store(mp, m, n, '+', "\\Answered", 0);
      if (m->m_flag&MUNANSWER)
            imap_store(mp, m, n, '-', "\\Flagged", 0);
      if (m->m_flag&MDRAFT)
            imap_store(mp, m, n, '+', "\\Draft", 0);
      if (m->m_flag&MUNDRAFT)
            imap_store(mp, m, n, '-', "\\Draft", 0);
again:      if (m->m_uid)
            snprintf(o, sizeof o, "%s UID COPY %lu %s\r\n",
                        tag(1), m->m_uid, qname);
      else {
            if (check_expunged() == STOP)
                  goto out;
            snprintf(o, sizeof o, "%s COPY %u %s\r\n",
                        tag(1), n, qname);
      }
      IMAP_OUT(o, MB_COMD, goto out)
      while (mp->mb_active & MB_COMD)
            ok = imap_answer(mp, twice);
      if (mp->mb_type == MB_IMAP &&
                  mp->mb_flags & MB_UIDPLUS &&
                  response_status == RESPONSE_OK)
            imap_copyuid(mp, m, name);
      if (response_status == RESPONSE_NO && twice++ == 0) {
            snprintf(o, sizeof o, "%s CREATE %s\r\n", tag(1), qname);
            IMAP_OUT(o, MB_COMD, goto out)
            while (mp->mb_active & MB_COMD)
                  ok = imap_answer(mp, 1);
            if (ok == OKAY) {
                  imap_created_mailbox++;
                  goto again;
            }
      }
      if (queuefp != NULL)
            Fclose(queuefp);
      /*
       * ... and reset the flag to its initial value so that
       * the 'exit' command still leaves the message unread.
       */
out:  if ((m->m_flag&(MREAD|MSTATUS)) == (MREAD|MSTATUS))
            imap_store(mp, m, n, '-', "\\Seen", 0);
      if (m->m_flag&MFLAG)
            imap_store(mp, m, n, '-', "\\Flagged", 0);
      if (m->m_flag&MUNFLAG)
            imap_store(mp, m, n, '+', "\\Flagged", 0);
      if (m->m_flag&MANSWER)
            imap_store(mp, m, n, '-', "\\Answered", 0);
      if (m->m_flag&MUNANSWER)
            imap_store(mp, m, n, '+', "\\Answered", 0);
      if (m->m_flag&MDRAFT)
            imap_store(mp, m, n, '-', "\\Draft", 0);
      if (m->m_flag&MUNDRAFT)
            imap_store(mp, m, n, '+', "\\Draft", 0);
      return ok;
}

enum okay 
imap_copy(struct message *m, int n, const char *name)
{
      sighandler_type   saveint, savepipe;
      enum okay   ok = STOP;

      (void)&saveint;
      (void)&savepipe;
      (void)&ok;
      verbose = value("verbose") != NULL;
      imaplock = 1;
      if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
            safe_signal(SIGINT, maincatch);
      savepipe = safe_signal(SIGPIPE, SIG_IGN);
      if (sigsetjmp(imapjmp, 1) == 0) {
            if (savepipe != SIG_IGN)
                  safe_signal(SIGPIPE, imapcatch);
            ok = imap_copy1(&mb, m, n, name);
      }
      safe_signal(SIGINT, saveint);
      safe_signal(SIGPIPE, savepipe);
      imaplock = 0;
      if (interrupts)
            onintr(0);
      return ok;
}

static enum okay 
imap_copyuid_parse(const char *cp, unsigned long *uidvalidity,
            unsigned long *olduid, unsigned long *newuid)
{
      char  *xp, *yp, *zp;

      *uidvalidity = strtoul(cp, &xp, 10);
      *olduid = strtoul(xp, &yp, 10);
      *newuid = strtoul(yp, &zp, 10);
      return *uidvalidity && *olduid && *newuid && xp > cp && *xp == ' ' &&
            yp > xp && *yp == ' ' && zp > yp && *zp == ']';
}

static enum okay 
imap_appenduid_parse(const char *cp, unsigned long *uidvalidity,
            unsigned long *uid)
{
      char  *xp, *yp;

      *uidvalidity = strtoul(cp, &xp, 10);
      *uid = strtoul(xp, &yp, 10);
      return *uidvalidity && *uid && xp > cp && *xp == ' ' &&
            yp > xp && *yp == ']';
}

static enum okay 
imap_copyuid(struct mailbox *mp, struct message *m, const char *name)
{
      const char  *cp;
      unsigned long     uidvalidity, olduid, newuid;
      struct mailbox    xmb;
      struct message    xm;

      if ((cp = asccasestr(responded_text, "[COPYUID ")) == NULL ||
                  imap_copyuid_parse(&cp[9], &uidvalidity,
                        &olduid, &newuid) == STOP)
            return STOP;
      xmb = *mp;
      xmb.mb_cache_directory = NULL;
      xmb.mb_imap_mailbox = savestr((char *)name);
      xmb.mb_uidvalidity = uidvalidity;
      initcache(&xmb);
      if (m == NULL) {
            memset(&xm, 0, sizeof xm);
            xm.m_uid = olduid;
            if (getcache1(mp, &xm, NEED_UNSPEC, 3) != OKAY)
                  return STOP;
            getcache(mp, &xm, NEED_HEADER);
            getcache(mp, &xm, NEED_BODY);
      } else {
            if ((m->m_flag & HAVE_HEADER) == 0)
                  getcache(mp, m, NEED_HEADER);
            if ((m->m_flag & HAVE_BODY) == 0)
                  getcache(mp, m, NEED_BODY);
            xm = *m;
      }
      xm.m_uid = newuid;
      xm.m_flag &= ~MFULLYCACHED;
      putcache(&xmb, &xm);
      return OKAY;
}

static enum okay
imap_appenduid(struct mailbox *mp, FILE *fp, time_t t, long off1,
            long xsize, long size, long lines, int flag, const char *name)
{
      const char  *cp;
      unsigned long     uidvalidity, uid;
      struct mailbox    xmb;
      struct message    xm;

      if ((cp = asccasestr(responded_text, "[APPENDUID ")) == NULL ||
                  imap_appenduid_parse(&cp[11], &uidvalidity,
                        &uid) == STOP)
            return STOP;
      xmb = *mp;
      xmb.mb_cache_directory = NULL;
      xmb.mb_imap_mailbox = savestr((char *)name);
      xmb.mb_uidvalidity = uidvalidity;
      xmb.mb_otf = xmb.mb_itf = fp;
      initcache(&xmb);
      memset(&xm, 0, sizeof xm);
      xm.m_flag = flag&MREAD | MNEW;
      xm.m_time = t;
      xm.m_block = mailx_blockof(off1);
      xm.m_offset = mailx_offsetof(off1);
      xm.m_size = size;
      xm.m_xsize = xsize;
      xm.m_lines = xm.m_xlines = lines;
      xm.m_uid = uid;
      xm.m_have = HAVE_HEADER|HAVE_BODY;
      putcache(&xmb, &xm);
      return OKAY;
}

static enum okay
imap_appenduid_cached(struct mailbox *mp, FILE *fp)
{
      FILE  *tp = NULL;
      time_t      t;
      long  size, xsize, ysize, lines;
      enum mflag  flag = MNEW;
      char  *name, *buf, *bp, *cp, *tempCopy;
      size_t      bufsize, buflen, count;
      enum okay   ok = STOP;

      buf = smalloc(bufsize = LINESIZE);
      buflen = 0;
      count = fsize(fp);
      if (fgetline(&buf, &bufsize, &count, &buflen, fp, 0) == NULL)
            goto stop;
      for (bp = buf; *bp != ' '; bp++);   /* strip old tag */
      while (*bp == ' ')
            bp++;
      if ((cp = strrchr(bp, '{')) == NULL)
            goto stop;
      xsize = atol(&cp[1]) + 2;
      if ((name = imap_strex(&bp[7], &cp)) == NULL)
            goto stop;
      while (*cp == ' ')
            cp++;
      if (*cp == '(') {
            imap_getflags(cp, &cp, &flag);
            while (*++cp == ' ');
      }
      t = imap_read_date_time(cp);
      if ((tp = Ftemp(&tempCopy, "Rc", "w+", 0600, 1)) == NULL)
            goto stop;
      rm(tempCopy);
      Ftfree(&tempCopy);
      size = xsize;
      ysize = lines = 0;
      while (size > 0) {
            if (fgetline(&buf, &bufsize, &count, &buflen, fp, 0) == NULL)
                  goto stop;
            size -= buflen;
            buf[--buflen] = '\0';
            buf[buflen-1] = '\n';
            fwrite(buf, 1, buflen, tp);
            ysize += buflen;
            lines++;
      }
      fflush(tp);
      rewind(tp);
      imap_appenduid(mp, tp, t, 0, xsize-2, ysize-1, lines-1, flag,
                  imap_unquotestr(name));
      ok = OKAY;
stop: free(buf);
      if (tp)
            Fclose(tp);
      return ok;
}

static enum okay 
imap_search2(struct mailbox *mp, struct message *m, int count,
            const char *spec, int f)
{
      char  *o;
      size_t      osize;
      FILE  *queuefp = NULL;
      enum okay   ok = STOP;
      int   i;
      unsigned long     n;
      const char  *cp;
      char  *xp, *cs, c;

      c = 0;
      for (cp = spec; *cp; cp++)
            c |= *cp;
      if (c & 0200) {
            cp = gettcharset();
#ifdef      HAVE_ICONV
            if (asccasecmp(cp, "utf-8")) {
                  iconv_t     it;
                  char  *sp, *nsp, *nspec;
                  size_t      sz, nsz;
                  if ((it = iconv_open_ft("utf-8", cp)) != (iconv_t)-1) {
                        sz = strlen(spec) + 1;
                        nsp = nspec = salloc(nsz = 6*strlen(spec) + 1);
                        sp = (char *)spec;
                        if (iconv(it, &sp, &sz, &nsp, &nsz)
                                    != (size_t)-1 && sz == 0) {
                              spec = nspec;
                              cp = "utf-8";
                        }
                        iconv_close(it);
                  }
            }
#endif      /* HAVE_ICONV */
            cp = imap_quotestr(cp);
            cs = salloc(n = strlen(cp) + 10);
            snprintf(cs, n, "CHARSET %s ", cp);
      } else
            cs = "";
      o = ac_alloc(osize = strlen(spec) + 60);
      snprintf(o, osize, "%s UID SEARCH %s%s\r\n", tag(1), cs, spec);
      IMAP_OUT(o, MB_COMD, goto out)
      while (mp->mb_active & MB_COMD) {
            ok = imap_answer(mp, 0);
            if (response_status == RESPONSE_OTHER &&
                        response_other == MAILBOX_DATA_SEARCH) {
                  xp = responded_other_text;
                  while (*xp && *xp != '\r') {
                        n = strtoul(xp, &xp, 10);
                        for (i = 0; i < count; i++)
                              if (m[i].m_uid == n &&
                                          (m[i].m_flag&MHIDDEN) 
                                          == 0 &&
                                          (f == MDELETED ||
                                           (m[i].m_flag&MDELETED)
                                           == 0))
                                    mark(i+1, f);
                  }
            }
      }
out:  ac_free(o);
      return ok;
}

enum okay 
imap_search1(const char *spec, int f)
{
      sighandler_type   saveint, savepipe;
      enum okay   ok = STOP;

      (void)&saveint;
      (void)&savepipe;
      (void)&ok;
      if (mb.mb_type != MB_IMAP)
            return STOP;
      verbose = value("verbose") != NULL;
      imaplock = 1;
      if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
            safe_signal(SIGINT, maincatch);
      savepipe = safe_signal(SIGPIPE, SIG_IGN);
      if (sigsetjmp(imapjmp, 1) == 0) {
            if (savepipe != SIG_IGN)
                  safe_signal(SIGPIPE, imapcatch);
            ok = imap_search2(&mb, message, msgCount, spec, f);
      }
      safe_signal(SIGINT, saveint);
      safe_signal(SIGPIPE, savepipe);
      imaplock = 0;
      if (interrupts)
            onintr(0);
      return ok;
}

int 
imap_thisaccount(const char *cp)
{
      if (mb.mb_type != MB_CACHE && mb.mb_type != MB_IMAP)
            return 0;
      if ((mb.mb_type != MB_CACHE && mb.mb_sock.s_fd < 0) ||
                  mb.mb_imap_account == NULL)
            return 0;
      return strcmp(protbase(cp), mb.mb_imap_account) == 0;
}

enum okay 
imap_remove(const char *name)
{
      sighandler_type saveint, savepipe;
      enum okay   ok = STOP;

      (void)&saveint;
      (void)&savepipe;
      (void)&ok;
      verbose = value("verbose") != NULL;
      if (mb.mb_type != MB_IMAP) {
            fprintf(stderr, "Refusing to remove \"%s\" "
                        "in disconnected mode.\n", name);
            return STOP;
      }
      if (!imap_thisaccount(name)) {
            fprintf(stderr, "Can only remove mailboxes on current IMAP "
                        "server: \"%s\" not removed.\n", name);
            return STOP;
      }
      imaplock = 1;
      if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
            safe_signal(SIGINT, maincatch);
      savepipe = safe_signal(SIGPIPE, SIG_IGN);
      if (sigsetjmp(imapjmp, 1) == 0) {
            if (savepipe != SIG_IGN)
                  safe_signal(SIGPIPE, imapcatch);
            ok = imap_remove1(&mb, protfile(name));
      }
      safe_signal(SIGINT, saveint);
      safe_signal(SIGPIPE, savepipe);
      imaplock = 0;
      if (ok == OKAY)
            ok = cache_remove(name);
      if (interrupts)
            onintr(0);
      return ok;
}

static enum okay 
imap_remove1(struct mailbox *mp, const char *name)
{
      FILE  *queuefp = NULL;
      char  *o;
      int   os;
      enum okay   ok = STOP;

      o = ac_alloc(os = 2*strlen(name) + 100);
      snprintf(o, os, "%s DELETE %s\r\n", tag(1), imap_quotestr(name));
      IMAP_OUT(o, MB_COMD, goto out)
      while (mp->mb_active & MB_COMD)
            ok = imap_answer(mp, 1);
out:  ac_free(o);
      return ok;
}

enum okay 
imap_rename(const char *old, const char *new)
{
      sighandler_type saveint, savepipe;
      enum okay   ok = STOP;

      (void)&saveint;
      (void)&savepipe;
      (void)&ok;
      verbose = value("verbose") != NULL;
      if (mb.mb_type != MB_IMAP) {
            fprintf(stderr, "Refusing to rename mailboxes "
                        "in disconnected mode.\n");
            return STOP;
      }
      if (!imap_thisaccount(old) || !imap_thisaccount(new)) {
            fprintf(stderr, "Can only rename mailboxes on current IMAP "
                        "server: \"%s\" not renamed to \"%s\".\n",
                        old, new);
            return STOP;
      }
      imaplock = 1;
      if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
            safe_signal(SIGINT, maincatch);
      savepipe = safe_signal(SIGPIPE, SIG_IGN);
      if (sigsetjmp(imapjmp, 1) == 0) {
            if (savepipe != SIG_IGN)
                  safe_signal(SIGPIPE, imapcatch);
            ok = imap_rename1(&mb, protfile(old), protfile(new));
      }
      safe_signal(SIGINT, saveint);
      safe_signal(SIGPIPE, savepipe);
      imaplock = 0;
      if (ok == OKAY)
            ok = cache_rename(old, new);
      if (interrupts)
            onintr(0);
      return ok;
}

static enum okay 
imap_rename1(struct mailbox *mp, const char *old, const char *new)
{
      FILE  *queuefp = NULL;
      char  *o;
      int   os;
      enum okay   ok = STOP;

      o = ac_alloc(os = 2*strlen(old) + 2*strlen(new) + 100);
      snprintf(o, os, "%s RENAME %s %s\r\n", tag(1),
                  imap_quotestr(old), imap_quotestr(new));
      IMAP_OUT(o, MB_COMD, goto out)
      while (mp->mb_active & MB_COMD)
            ok = imap_answer(mp, 1);
out:  ac_free(o);
      return ok;
}

enum okay
imap_dequeue(struct mailbox *mp, FILE *fp)
{
      FILE  *queuefp = NULL;
      char  o[LINESIZE], *newname;
      char  *buf, *bp, *cp, iob[4096];
      size_t      bufsize, buflen, count;
      enum okay   ok = OKAY, rok = OKAY;
      long  offs, offs1, offs2, octets;
      int   n, twice, gotcha = 0;

      buf = smalloc(bufsize = LINESIZE);
      buflen = 0;
      count = fsize(fp);
      while (offs1 = ftell(fp),
                  fgetline(&buf, &bufsize, &count, &buflen, fp, 0)
                  != NULL) {
            for (bp = buf; *bp != ' '; bp++);   /* strip old tag */
            while (*bp == ' ')
                  bp++;
            twice = 0;
            offs = ftell(fp);
      again:      snprintf(o, sizeof o, "%s %s", tag(1), bp);
            if (ascncasecmp(bp, "UID COPY ", 9) == 0) {
                  cp = &bp[9];
                  while (digitchar(*cp&0377))
                        cp++;
                  if (*cp != ' ')
                        goto fail;
                  while (*cp == ' ')
                        cp++;
                  if ((newname = imap_strex(cp, NULL)) == NULL)
                        goto fail;
                  IMAP_OUT(o, MB_COMD, continue)
                  while (mp->mb_active & MB_COMD)
                        ok = imap_answer(mp, twice);
                  if (response_status == RESPONSE_NO && twice++ == 0)
                        goto trycreate;
                  if (response_status == RESPONSE_OK &&
                              mp->mb_flags & MB_UIDPLUS) {
                        imap_copyuid(mp, NULL,
                                    imap_unquotestr(newname));
                  }
            } else if (ascncasecmp(bp, "UID STORE ", 10) == 0) {
                  IMAP_OUT(o, MB_COMD, continue)
                  while (mp->mb_active & MB_COMD)
                        ok = imap_answer(mp, 1);
                  if (ok == OKAY)
                        gotcha++;
            } else if (ascncasecmp(bp, "APPEND ", 7) == 0) {
                  if ((cp = strrchr(bp, '{')) == NULL)
                        goto fail;
                  octets = atol(&cp[1]) + 2;
                  if ((newname = imap_strex(&bp[7], NULL)) == NULL)
                        goto fail;
                  IMAP_OUT(o, MB_COMD, continue)
                  while (mp->mb_active & MB_COMD) {
                        ok = imap_answer(mp, twice);
                        if (response_type == RESPONSE_CONT)
                              break;
                  }
                  if (ok == STOP) {
                        if (twice++ == 0) {
                              fseek(fp, offs, SEEK_SET);
                              goto trycreate;
                        }
                        goto fail;
                  }
                  while (octets > 0) {
                        n = octets > sizeof iob ? sizeof iob : octets;
                        octets -= n;
                        if (fread(iob, 1, n, fp) != n)
                              goto fail;
                        swrite1(&mp->mb_sock, iob, n, 1);
                  }
                  swrite(&mp->mb_sock, "");
                  while (mp->mb_active & MB_COMD) {
                        ok = imap_answer(mp, 0);
                        if (response_status == RESPONSE_NO &&
                                    twice++ == 0) {
                              fseek(fp, offs, SEEK_SET);
                              goto trycreate;
                        }
                  }
                  if (response_status == RESPONSE_OK &&
                              mp->mb_flags & MB_UIDPLUS) {
                        offs2 = ftell(fp);
                        fseek(fp, offs1, SEEK_SET);
                        if (imap_appenduid_cached(mp, fp) == STOP) {
                              fseek(fp, offs2, SEEK_SET);
                              goto fail;
                        }
                  }
            } else {
            fail: fprintf(stderr,
                        "Invalid command in IMAP cache queue: \"%s\"\n",
                        bp);
                  rok = STOP;
            }
            continue;
      trycreate:
            snprintf(o, sizeof o, "%s CREATE %s\r\n",
                        tag(1), newname);
            IMAP_OUT(o, MB_COMD, continue)
            while (mp->mb_active & MB_COMD)
                  ok = imap_answer(mp, 1);
            if (ok == OKAY)
                  goto again;
      }
      fflush(fp);
      rewind(fp);
      ftruncate(fileno(fp), 0);
      if (gotcha)
            imap_close(mp);
      return rok;
}

static char *
imap_strex(const char *cp, char **xp)
{
      const char  *cq;
      char  *n;

      if (*cp != '"')
            return NULL;
      for (cq = &cp[1]; *cq; cq++) {
            if (*cq == '\\')
                  cq++;
            else if (*cq == '"')
                  break;
      }
      if (*cq != '"')
            return NULL;
      n = salloc(cq - cp + 2);
      memcpy(n, cp, cq - cp + 1);
      n[cq - cp + 1] = '\0';
      if (xp)
            *xp = (char *)&cq[1];
      return n;
}

static enum okay 
check_expunged(void)
{
      if (expunged_messages > 0) {
            fprintf(stderr,
                  "Command not executed - messages have been expunged\n");
            return STOP;
      }
      return OKAY;
}

/*ARGSUSED*/
int 
cconnect(void *vp)
{
      char  *cp, *cq;
      int   omsgCount = msgCount;

      if (mb.mb_type == MB_IMAP && mb.mb_sock.s_fd > 0) {
            fprintf(stderr, "Already connected.\n");
            return 1;
      }
      unset_allow_undefined = 1;
      unset_internal("disconnected");
      cp = protbase(mailname);
      if (strncmp(cp, "imap://", 7) == 0)
            cp += 7;
      else if (strncmp(cp, "imaps://", 8) == 0)
            cp += 8;
      if ((cq = strchr(cp, ':')) != NULL)
            *cq = '\0';
      unset_internal(savecat("disconnected-", cp));
      unset_allow_undefined = 0;
      if (mb.mb_type == MB_CACHE) {
            imap_setfile1(mailname, 0, edit, 1);
            if (msgCount > omsgCount)
                  newmailinfo(omsgCount);
      }
      return 0;
}

int 
cdisconnect(void *vp)
{
      int   *msgvec = vp;

      if (mb.mb_type == MB_CACHE) {
            fprintf(stderr, "Not connected.\n");
            return 1;
      } else if (mb.mb_type == MB_IMAP) {
            if (cached_uidvalidity(&mb) == 0) {
                  fprintf(stderr, "The current mailbox is not cached.\n");
                  return 1;
            }
      }
      if (*msgvec)
            ccache(vp);
      assign("disconnected", "");
      if (mb.mb_type == MB_IMAP) {
            sclose(&mb.mb_sock);
            imap_setfile1(mailname, 0, edit, 1);
      }
      return 0;
}
int 
ccache(void *vp)
{
      int   *msgvec = vp, *ip;
      struct message    *mp;

      if (mb.mb_type != MB_IMAP) {
            fprintf(stderr, "Not connected to an IMAP server.\n");
            return 1;
      }
      if (cached_uidvalidity(&mb) == 0) {
            fprintf(stderr, "The current mailbox is not cached.\n");
            return 1;
      }
      for (ip = msgvec; *ip; ip++) {
            mp = &message[*ip-1];
            if (!(mp->m_have & HAVE_BODY))
                  get_body(mp);
      }
      return 0;
}
#else /* !HAVE_SOCKETS */

#include "extern.h"

static void 
noimap(void)
{
      fprintf(stderr, catgets(catd, CATSET, 216,
                        "No IMAP support compiled in.\n"));
}

int 
imap_setfile(const char *server, int newmail, int isedit)
{
      noimap();
      return -1;
}

enum okay 
imap_header(struct message *mp)
{
      noimap();
      return STOP;
}

enum okay 
imap_body(struct message *mp)
{
      noimap();
      return STOP;
}

void 
imap_getheaders(int bot, int top)
{
}

void 
imap_quit(void)
{
      noimap();
}

/*ARGSUSED*/
int 
imap_imap(void *vp)
{
      noimap();
      return 1;
}

/*ARGSUSED*/
int 
imap_newmail(int dummy)
{
      return 0;
}

/*ARGSUSED*/
enum okay 
imap_undelete(struct message *m, int n)
{
      return STOP;
}

/*ARGSUSED*/
enum okay 
imap_unread(struct message *m, int n)
{
      return STOP;
}

/*ARGSUSED*/
enum okay
imap_append(const char *server, FILE *fp)
{
      noimap();
      return STOP;
}

/*ARGSUSED*/
void 
imap_folders(const char *name, int strip)
{
      noimap();
}

/*ARGSUSED*/
enum okay 
imap_remove(const char *name)
{
      noimap();
      return STOP;
}

/*ARGSUSED*/
enum okay 
imap_rename(const char *old, const char *new)
{
      noimap();
      return STOP;
}

enum okay 
imap_copy(struct message *m, int n, const char *name)
{
      noimap();
      return STOP;
}

/*ARGSUSED*/
enum okay 
imap_search1(const char *spec, int f)
{
      return STOP;
}

int 
imap_thisaccount(const char *cp)
{
      return 0;
}

enum okay 
imap_noop(void)
{
      noimap();
      return STOP;
}

/*ARGSUSED*/
int 
cconnect(void *vp)
{
      noimap();
      return 1;
}

/*ARGSUSED*/
int 
cdisconnect(void *vp)
{
      noimap();
      return 1;
}

/*ARGSUSED*/
int 
ccache(void *vp)
{
      noimap();
      return 1;
}
#endif      /* HAVE_SOCKETS */

time_t
imap_read_date_time(const char *cp)
{
      time_t      t;
      int   i, year, month, day, hour, minute, second;
      int   sign = -1;
      char  buf[3];

      /*
       * "25-Jul-2004 15:33:44 +0200"
       * |    |    |    |    |    |  
       * 0    5   10   15   20   25  
       */
      if (cp[0] != '"' || strlen(cp) < 28 || cp[27] != '"')
            goto invalid;
      day = strtol(&cp[1], NULL, 10);
      for (i = 0; month_names[i]; i++)
            if (ascncasecmp(&cp[4], month_names[i], 3) == 0)
                  break;
      if (month_names[i] == NULL)
            goto invalid;
      month = i + 1;
      year = strtol(&cp[8], NULL, 10);
      hour = strtol(&cp[13], NULL, 10);
      minute = strtol(&cp[16], NULL, 10);
      second = strtol(&cp[19], NULL, 10);
      if ((t = combinetime(year, month, day, hour, minute, second)) ==
                  (time_t)-1)
            goto invalid;
      switch (cp[22]) {
      case '-':
            sign = 1;
            break;
      case '+':
            break;
      default:
            goto invalid;
      }
      buf[2] = '\0';
      buf[0] = cp[23];
      buf[1] = cp[24];
      t += strtol(buf, NULL, 10) * sign * 3600;
      buf[0] = cp[25];
      buf[1] = cp[26];
      t += strtol(buf, NULL, 10) * sign * 60;
      return t;
invalid:
      time(&t);
      return t;
}

time_t
imap_read_date(const char *cp)
{
      time_t      t;
      int   year, month, day, i, tzdiff;
      struct tm   *tmptr;
      char  *xp, *yp;

      if (*cp == '"')
            cp++;
      day = strtol(cp, &xp, 10);
      if (day <= 0 || day > 31 || *xp++ != '-')
            return -1;
      for (i = 0; month_names[i]; i++)
            if (ascncasecmp(xp, month_names[i], 3) == 0)
                  break;
      if (month_names[i] == NULL)
            return -1;
      month = i+1;
      if (xp[3] != '-')
            return -1;
      year = strtol(&xp[4], &yp, 10);
      if (year < 1970 || year > 2037 || yp != &xp[8])
            return -1;
      if (yp[0] != '\0' && (yp[1] != '"' || yp[2] != '\0'))
            return -1;
      if ((t = combinetime(year, month, day, 0, 0, 0)) == (time_t)-1)
            return -1;
      tzdiff = t - mktime(gmtime(&t));
      tmptr = localtime(&t);
      if (tmptr->tm_isdst > 0)
            tzdiff += 3600;
      t -= tzdiff;
      return t;
}

const char *
imap_make_date_time(time_t t)
{
      static char s[30];
      struct tm   *tmptr;
      int   tzdiff, tzdiff_hour, tzdiff_min;

      tzdiff = t - mktime(gmtime(&t));
      tzdiff_hour = (int)(tzdiff / 60);
      tzdiff_min = tzdiff_hour % 60;
      tzdiff_hour /= 60;
      tmptr = localtime(&t);
      if (tmptr->tm_isdst > 0)
            tzdiff_hour++;
      snprintf(s, sizeof s, "\"%02d-%s-%04d %02d:%02d:%02d %+03d%02d\"",
                  tmptr->tm_mday,
                  month_names[tmptr->tm_mon],
                  tmptr->tm_year + 1900,
                  tmptr->tm_hour,
                  tmptr->tm_min,
                  tmptr->tm_sec,
                  tzdiff_hour,
                  tzdiff_min);
      return s;
}

char *
imap_quotestr(const char *s)
{
      char  *n, *np;

      np = n = salloc(2 * strlen(s) + 3);
      *np++ = '"';
      while (*s) {
            if (*s == '"' || *s == '\\')
                  *np++ = '\\';
            *np++ = *s++;
      }
      *np++ = '"';
      *np = '\0';
      return n;
}

char *
imap_unquotestr(const char *s)
{
      char  *n, *np;

      if (*s != '"')
            return savestr(s);
      np = n = salloc(strlen(s) + 1);
      while (*++s) {
            if (*s == '\\')
                  s++;
            else if (*s == '"')
                  break;
            *np++ = *s;
      }
      *np = '\0';
      return n;
}

Generated by  Doxygen 1.6.0   Back to index