/*

$Id: pop,v 1.40 2001/03/31 14:34:03 joseph Exp $

*/

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include "memleak.h"
#include <string.h>
#include <time.h>
#include <ctype.h>

#include "swis.h"

#include "sys/errno.h"


#include "defines.h"

#include "CRstring.h"
#include "bufsock.h"
#include "config.h"
#include "err.h"
#include "errlist.h"
#include "ftrunc.h"
#include "log.h"
#include "msgs.h"
#include "pop.h"
#include "stopquit.h"
#include "treport.h"
#include "user.h"
#include "xconnect.h"
#include "xsock.h"
#include "dkm.h"
#include "oversize.h"
#include "leakcheck.h"
#include "popstar.h"

#include "md5.h"

typedef struct {
  bufsock bs;
  const user_info *u;
  bool negotiated;
  int stat_count;
  int stat_size;
  FILE *fp;
  long last_offset;
  int msg;
  int msg_cur;
  status_handle status;
  stopquit_t stopped;
  int *list;
  char **ids, **saved_ids;
  int first_msg;
  int session_count; /* total number of messages downloaded for this users */
  int requested; /* number of messages requested for this user, so far (ie. session_count + outstanding RETR's) */
  int requestedbytes; /* number of bytes requested for this user, so far */
  bool unfinished; /* pop download did not complete - restart and get remaining messages */
} pop_info;

static bool pop_rmailnum = true;
static xsock pop_socket = -1;
static bool pop_exit_installed = false;
static char last_error[128] = "[None]"; /* last error returned from server */
static pop_info *saved_pop_info = NULL;

static const char *pop_queueable[] =
{
  "APOP",
  "USER",
  "PASS",
//  "STAT", /* Must wait for a response to this before we know how many messages we need to fetch */
//  "LIST", /* Have to think before queueing this */
//  "LAST", /* Must wait for response before we know what message to start at ;-) */
//  "QUIT", /* Must catch up and things, but QUIT is handled differently anyway */
  "RETR",
  "DELE",
  NULL
};

typedef bool (*pop_comhandler)(xsock, pop_info *, const char *);

#define POP_MAXPIPELINE 15

static struct
{
  char            com[256];
  pop_comhandler handler;
}
pop_pipeline[ POP_MAXPIPELINE ];

static int pop_pipesize = 0;
static int pop_maxpipesize = 0;

static void pop_close()
{
  if (pop_socket != -1)
  {
    xsock_close(pop_socket);
    pop_socket = -1;
  }
}

static int pop_canqueue( const char *command )
{
  const char **x = pop_queueable;
  while ( *x )
  {
    if ( !memcmp( *x, command, strlen( *x ) ) )
    {
//      xsyslogf( log_NAME, log_DebugInfo, "%s is queuable.(pop_maxpipesize = %d)", command, pop_maxpipesize );
      return 1;
    }
    x++;
  }
//  xsyslogf( log_NAME, log_DebugInfo, "%s is NOT queuable.", command );

  return 0;
}

static bool pop_unpipe( pop_info *info )
{
  int x;
  bool ret;
  char com[ sizeof(pop_pipeline[0].com) ];
  pop_comhandler handler;

  if ( pop_pipesize <= 0 ) return true;

  /* must record command we're going to execute and then shuffle the pipe, otherwise we get in a terrible
   * mess if the handler adds a command to the pipeline
   * (because this would cause a pipefull, then pop_unpipe would get called, then that would try and
   * execute the same handler again...)
   */
  strcpy( com, pop_pipeline[0].com );
  handler = pop_pipeline[0].handler;

  pop_pipesize--;
  for ( x = 0; x < pop_pipesize; x++ )
  {
    strcpy( pop_pipeline[x].com, pop_pipeline[x+1].com );
    pop_pipeline[x].handler = pop_pipeline[x+1].handler;
  }

//  xsyslogf( log_NAME, log_DebugInfo, "Pipefull: %s - waiting for result. pipesize = %d",
//            pop_pipeline[0].com, pop_pipesize );

  ret = (*handler)( pop_socket, info, com );
  return ret;
}

static bool pop_exe_command( pop_info *info, const char *command, pop_comhandler handler )
{
  bool ret = true;
  leak_check();
  leak_check();

  if ( pop_canqueue( command ) && pop_maxpipesize > 0 )
  {
   /* Pipeline is full - process entries to make space */
    while ( pop_pipesize >= pop_maxpipesize && ret )
    {
//      xsyslogf( log_NAME, log_DebugInfo, "Pipefull, pipesize = %d", pop_pipesize );
      ret = pop_unpipe( info );
    }

    /* must be space for one command now... */
    if ( pop_pipesize < pop_maxpipesize )
    {
      /* Must do write until we're ready to put the command on the queue - otherwise a handler
       * might send a command, and the handler for that command would be put on the pipe before
       * the handler for our command
       */
      if (!xsock_xwrite(pop_socket, command))
      {
        leak_check();
        strcpy( last_error, "Sending to server failed" );
        return false;
      }
      *pop_pipeline[pop_pipesize].com = 0;
      strncat( pop_pipeline[pop_pipesize].com, command, sizeof(pop_pipeline[0].com) - 1 );
      pop_pipeline[pop_pipesize].handler = handler;
      pop_pipesize++;
//      xsyslogf( log_NAME, log_DebugInfo, "%s queued. pipesize = %d",pop_pipeline[pop_pipesize-1].com,pop_pipesize );
    }
    else
    {
      xsyslogf( log_NAME, log_DebugInfo, "Failed to queue %s. pipesize = %d",
                pop_pipeline[pop_pipesize-1].com,pop_pipesize );
    }
    return ret; /* NB. This returns the status of a _previous_ command */
  }

  while ( pop_pipesize > 0 && ret )
    ret = pop_unpipe( info ); /* can't xsock_xwrite command until all these have done */

  if ( !ret ) return ret; /* return anything that failed. pipeline may still contain commands. */

  if (!xsock_xwrite(pop_socket, command))
  {
    leak_check();
    strcpy( last_error, "Sending to server failed" );
    return false;
  }
  return (*handler)(pop_socket, info, command);
}


static void pop_forget_id (char **ids, int idno)
{
  if (ids)
  {
    free (ids[idno]);
    ids[idno] = 0;
  }
}


static bool pop_handle_user(xsock sock, pop_info *info, const char *com)
{
  const char *pop_buf;
  bool wasline;

  sock=sock;

  xsyslog_logmessage(log_NAME, msgs_lookup1("Awaiting", com), log_DebugInfo);
  if ((pop_buf = bufsock_read_line(info->bs, &wasline, NULL)) == 0)
  {
    char str[32];
    sprintf(str, "%d", errno);
    xsyslog_logmessage(log_NAME,
      msgs_lookup3("NoRep", "USER", errlist_str(errno), str),
      log_SocketError);
    strcpy( last_error, "No response from server" );
    return false;
  }
  if (strncmp(pop_buf, "+OK", 3))
  {
    xsyslog_logmessage(log_NAME, msgs_lookup1("BadRep", "USER"),
    	log_ServerError);
    xsyslog_logmessage(log_NAME, pop_buf, log_ServerError);
    strcpy( last_error, "Reply to USER command: ");
    strncat( last_error, pop_buf, sizeof(last_error) - strlen(last_error) - 1 );
    return false;
  }
  xsyslog_logmessage(log_NAME, pop_buf, log_ServerResponse);
  /*while (!wasline && bufsock_read_line(info->bs, &wasline));*/
  return true;
}

static bool pop_handle_pass(xsock sock, pop_info *info, const char *com)
{
  const char *pop_buf;
  char cmd[5];
  bool wasline;

  sock=sock;

  strncpy (cmd, com, 4);
  cmd[4] = 0;

  xsyslog_logmessage(log_NAME, msgs_lookup1("Awaiting", com), log_DebugInfo);
  if ((pop_buf = bufsock_read_line(info->bs, &wasline, NULL)) == 0)
  {
    char str[32];
    sprintf(str, "%d", errno);
    xsyslog_logmessage(log_NAME,
      msgs_lookup3("NoRep", cmd, errlist_str(errno), str),
      log_SocketError);
    strcpy( last_error, "No response from server" );
    return false;
  }
  if (strncmp(pop_buf, "+OK", 3))
  {
    xsyslog_logmessage(log_NAME, msgs_lookup1("BadRep", cmd),
    	log_ServerError);
    xsyslog_logmessage(log_NAME, pop_buf, log_ServerError);
    sprintf( last_error, "Reply to %s command: ", cmd);
    strncat( last_error, pop_buf, sizeof(last_error) - strlen(last_error) - 1 );
    return false;
  }
  xsyslog_logmessage(log_NAME, pop_buf, log_ServerResponse);
  /*while (!wasline && bufsock_read_line(info->bs, &wasline));*/
  return true;
}


static int pop_get_config_size( const char *name )
{
  const char *start = config_lookup( name ); /* defaults to amount given in name if not in config file */
  int maxsize = atoi( start );

  while ( isdigit( *start ) ) start++;

  if ( toupper(*start) == 'K' )
    maxsize *= 1024;
  else if ( toupper(*start) == 'M' )
    maxsize *= 1024 * 1024;

  return maxsize;
}


static bool pop_handle_stat(xsock sock, pop_info *info, const char *com)
{
  const char *pop_buf;
  bool wasline;
  int maxsize = info->u->maxsize;
  if ( maxsize == -1 )
    maxsize = pop_get_config_size( "MaxSize:0" );

  sock=sock;

  xsyslog_logmessage(log_NAME, msgs_lookup1("Awaiting", com), log_DebugInfo);
  if ((pop_buf = bufsock_read_line(info->bs, &wasline, NULL)) == 0)
  {
    char str[32];
    sprintf(str, "%d", errno);
    xsyslog_logmessage(log_NAME,
      msgs_lookup3("NoRep", "STAT", errlist_str(errno), str),
      log_SocketError);
    strcpy( last_error, "No response from server" );
    return false;
  }
  if (strncmp(pop_buf, "+OK", 3))
  {
    xsyslog_logmessage(log_NAME, msgs_lookup1("BadRep", "STAT"),
    	log_ServerError);
    xsyslog_logmessage(log_NAME, pop_buf, log_ServerError);
    strcpy( last_error, "Reply to STAT command: ");
    strncat( last_error, pop_buf, sizeof(last_error) - strlen(last_error) - 1 );
    return false;
  }
  xsyslog_logmessage(log_NAME, pop_buf, log_ServerResponse);
  sscanf(pop_buf + 4, "%d %d", &info->stat_count, &info->stat_size);
  if ( !info->u->last )
  {
    status_count_total(info->status, info->stat_count);
    status_total_total(info->status, info->stat_size);
  }
  xsyslogf(log_NAME, log_DebugInfo, "About to alloc 'list' block, maxsize = %d", maxsize );
  if ( info->stat_count && ( config_lookup_bool("List:Y") || maxsize != 0 ) )
  {
    int i;
    /* Get indiviual message sizes, providing:
      - There are messages to be download
      and - the user has requested it, or has a maxsize limit set
     */

    info->list = malloc(info->stat_count * sizeof(int));
    if (info->list == NULL)
    {
      xsyslogf(log_NAME, log_OSError,
               "Failed to allocate %d bytes for stat block",
               info->stat_count * sizeof(int) );
      strcpy(last_error, "Out of memory for stat buffer");
      return false;
    }
    for (i = 0; i < info->stat_count; ++i)
      info->list[i] = -1;
  }
  /*while (!wasline && bufsock_read_line(info->bs, &wasline));*/
  return info->negotiated = true;
}


static bool pop_handle_last(xsock sock, pop_info *info, const char *com)
{
  const char *pop_buf;
  bool wasline;

  sock=sock;

  xsyslog_logmessage(log_NAME, msgs_lookup1("Awaiting", com), log_DebugInfo);
  if ((pop_buf = bufsock_read_line(info->bs, &wasline, NULL)) == 0)
  {
    char str[32];
    sprintf(str, "%d", errno);
    xsyslog_logmessage(log_NAME,
      msgs_lookup3("NoRep", "LAST", errlist_str(errno), str),
      log_SocketError);
    return false;
  }
  if (strncmp(pop_buf, "+OK", 3))
  {
    xsyslog_logmessage(log_NAME, msgs_lookup1("BadRep", "LAST"),
    	log_ServerError);
    xsyslog_logmessage(log_NAME, pop_buf, log_ServerError);
    return false;
  }
  xsyslog_logmessage(log_NAME, pop_buf, log_ServerResponse);
  if ( sscanf( pop_buf + 4, "%d", &info->first_msg ) != 1 )
    return false;
  info->first_msg++; /* server returns number of last message we accessed */
  return true;
}

static const char *pop_negotiate(pop_info *info)
{
  const char *pop_buf;
  bool wasline;
  char *cmd;
  int maxlen, x;
  char *apop, *apop_end = 0; /* for APOP checking */

  info->negotiated = false;

  status_show_misc(info->status, msgs_lookup1("LogIn", info->u->name));
  if ((pop_buf = bufsock_read_line(info->bs, &wasline, NULL)) == 0)
  {
    char str[32];
    sprintf(str, "%d", errno);
    xsyslog_logmessage(log_NAME,
      msgs_lookup2("NoLoginRep", errlist_str(errno), str),
      log_SocketError);
    return "No response from server";
  }
  xsyslog_logmessage(log_NAME, pop_buf, log_ServerResponse);
  if (strncmp(pop_buf, "+OK", 3))
  {
    xsyslog_logmessage(log_NAME, pop_buf, log_ServerError);
    strcpy( last_error, "Connection message - ");
    strncat( last_error, pop_buf, sizeof(last_error) - strlen(last_error) - 1 );
    return last_error;
  }
  /*while (!wasline && bufsock_read_line(info->bs, &wasline));*/

  /* Check for APOP support */
  if (info->u->apop)
  {
    apop = strrchr (pop_buf, '<');
    if (apop)
    {
      apop_end = strchr (apop, '@');
      if (apop_end)
        apop_end = strchr (apop_end, '>');
    }
  }
  if (apop_end && apop_end == pop_buf + strlen (pop_buf) - 1)
  {
    /* We have APOP */
    char resbuf[17] = {0};

    maxlen = 7 + strlen (info->u->name) + 32; /* "APOP " name " " md5sum */
    x = strlen (apop) + strlen (info->u->password) + 1; /* stamp password */
    if (x > maxlen)
      maxlen = x;
    cmd = malloc (maxlen);
    if (!cmd)
      return "Out of memory";

    sprintf (cmd, "%s%s", apop, info->u->password);
    md5_buffer (cmd, strlen (cmd), resbuf );

    sprintf (cmd, "APOP %s ................................", info->u->name);
    apop = cmd + 6 + strlen (info->u->name);
    for (x = 0; x < 16; x++)
    {
      char c = (resbuf[x] >> 4) & 0xF;
      apop[x * 2] = c + ((c < 10) ? '0' : 'a' - 10);
      c = resbuf[x] & 0xF;
      apop[x * 2 + 1] = c + ((c < 10) ? '0' : 'a' - 10);
    }
    /* Now send the command (after 'else' block) */
  }
  else
  {
    /* No APOP; use USER and PASS */
    maxlen = strlen( info->u->name );
    x = strlen( info->u->password );
    if ( x > maxlen ) maxlen = x;
    cmd = malloc( maxlen + 6 ); /* "USER" + " " + info + NULL */
    if ( !cmd )
    {
      return "Out of memory";
    }

    sprintf(cmd, "USER %s", info->u->name);
    if (!pop_exe_command(info, cmd, pop_handle_user))
    {
      free( cmd );
      return last_error;
    }
    sprintf(cmd, "PASS %s", info->u->password);
  }
  /* Send PASS or APOP */
  if (!pop_exe_command(info, cmd, pop_handle_pass))
  {
    free( cmd );
    return last_error;
  }
  free( cmd );

  /* The previous command(s) might get pipelined, but this one won't */
  if (!pop_exe_command(info, "STAT", pop_handle_stat))
    return last_error;

  return NULL;
}

static bool pop_handle_uidl(xsock sock, pop_info *info, const char *com)
{
  const char *pop_buf;
  bool wasline;
  int msg, id;

  sock=sock;

  info->ids = malloc(info->stat_count * sizeof(char *));
  if (info->ids == NULL)
  {
    xsyslogf(log_NAME, log_OSError,
      "Failed to allocate %d bytes for id block",
      info->stat_count * sizeof(char *) );
    strcpy(last_error, "Out of memory for id buffer");
    return false;
  }
  for (id = 0; id < info->stat_count; ++id)
    info->ids[id] = 0;

  xsyslog_logmessage(log_NAME, msgs_lookup1("Awaiting", com), log_DebugInfo);
  if ((pop_buf = bufsock_read_line(info->bs, &wasline, NULL)) == 0)
  {
    char str[32];
    sprintf(str, "%d", errno);
    xsyslog_logmessage(log_NAME,
      msgs_lookup3("NoRep", "UIDL", errlist_str(errno), str),
      log_SocketError);
    return false;
  }

  if (strncmp(pop_buf, "+OK", 3))
  {
    xsyslog_logmessage(log_NAME, msgs_lookup1("BadRep", "UIDL"),
    	log_ServerError);
    xsyslog_logmessage(log_NAME, pop_buf, log_ServerError);
    return false;
  }
  xsyslog_logmessage(log_NAME, pop_buf, log_ServerResponse);

  while ((pop_buf = bufsock_read_line(info->bs, &wasline, NULL)) != 0 &&
  	pop_buf[0] && pop_buf[0] != '.')
  {
    sscanf(pop_buf, "%d %n", &msg, &id);
    if (msg > info->stat_count)
    {
      treport(msgs_lookup("BadList"), log_ServerError);
      xsyslog_logmessage(log_NAME, pop_buf, log_ServerError);
    }
    else
      info->ids[msg - 1] = CRstrdup (pop_buf + id);
  }
  return true;
}

static bool pop_handle_list(xsock sock, pop_info *info, const char *com)
{
  const char *pop_buf;
  bool wasline;
  int msg, size;

  sock=sock;

  xsyslog_logmessage(log_NAME, msgs_lookup1("Awaiting", com), log_DebugInfo);
  if ((pop_buf = bufsock_read_line(info->bs, &wasline, NULL)) == 0)
  {
    char str[32];
    sprintf(str, "%d", errno);
    xsyslog_logmessage(log_NAME,
      msgs_lookup3("NoRep", "LIST", errlist_str(errno), str),
      log_SocketError);
    return false;
  }
  if (strncmp(pop_buf, "+OK", 3))
  {
    xsyslog_logmessage(log_NAME, msgs_lookup1("BadRep", "LIST"),
    	log_ServerError);
    xsyslog_logmessage(log_NAME, pop_buf, log_ServerError);
    return false;
  }
  xsyslog_logmessage(log_NAME, pop_buf, log_ServerResponse);
  /*while (!wasline && bufsock_read_line(info->bs, &wasline));*/
  while ((pop_buf = bufsock_read_line(info->bs, &wasline, NULL)) != 0 &&
  	pop_buf[0] && pop_buf[0] != '.')
  {
    sscanf(pop_buf, "%d %d", &msg, &size);
    if (msg > info->stat_count)
    {
      treport(msgs_lookup("BadList"), log_ServerError);
      xsyslog_logmessage(log_NAME, pop_buf, log_ServerError);
    }
    else
      info->list[msg - 1] = size;
  }
  return true;
}

static bool pop_handle_dele(xsock sock, pop_info *info, const char *com)
{
  const char *pop_buf;
  bool wasline;

  sock=sock;

  xsyslog_logmessage(log_NAME, msgs_lookup1("Awaiting", com), log_DebugInfo);
  if ((pop_buf = bufsock_read_line(info->bs, &wasline, NULL)) == 0)
  {
    char str[32];
    sprintf(str, "%d", errno);
    xsyslog_logmessage(log_NAME,
      msgs_lookup3("NoRep", "DELE", errlist_str(errno), str),
      log_SocketError);
    return false;
  }
  if (strncmp(pop_buf, "+OK", 3))
  {
    xsyslog_logmessage(log_NAME, msgs_lookup1("BadRep", "DELE"),
    	log_ServerError);
    xsyslog_logmessage(log_NAME, pop_buf, log_ServerError);
    return false;
  }
  xsyslog_logmessage(log_NAME, pop_buf, log_ServerResponse);
  /*while (!wasline && bufsock_read_line(info->bs, &wasline));*/
  return true;
}

static void pop_show_status(const pop_info *info, bool force)
{
  static int lt;
  int tn;

  _swix(OS_ReadMonotonicTime, _OUT(0), &tn);
  if (!info->msg_cur)
  {
    status_count(info->status, info->msg);
    if (info->list && info->list[info->msg - 1] != -1)
      status_bytes_total(info->status, info->list[info->msg - 1]);
    lt = tn;
  }
  else if (tn - lt > 25 || force)
  {
    status_bytes(info->status, info->msg_cur);
    /*
    printf("\x0bMessage %4d : %10d", info->msg, info->msg_cur);
    if (info->msg_total != -1)
      printf("/%-10d\n", info->msg_total);
    else
      printf("\n");
    */
    lt = tn;
  }
}


#ifdef BLOCK_OPEN_RELAYS
static int check_open_relay (const char *ptr)
{
  const char *name, *name_end;
  char dq_name[256];
  unsigned int dq[4] = { 256, 256, 256, 256 };

  while (isspace (*ptr)) ptr++;
  while (!isspace (*ptr)) ptr++; /* skip 'from' */
  while (isspace (*ptr)) ptr++;
  name = ptr;
  while (!isspace (*ptr)) ptr++; /* skip name */
  name_end = ptr;
  while (isspace (*ptr)) ptr++;
  if (*ptr == '(')
  {
    name = ++ptr;
    while (*ptr != ')' && !isspace (*ptr)) ptr++; /* skip name */
    name_end = ptr;
  }
  /* OK, we have a machine name or IP address */
  if (*name == '[') /* assume dotted quad */
  {
    dns_t *dns;
    struct hostent *host;
    sscanf (name, "[%i.%i.%i.%i]", &dq[3], &dq[2], &dq[1], &dq[0]);
    if (dq[0] < 256 && dq[1] < 256 && dq[2] < 256 && dq[3] < 256)
    {
      sprintf (dq_name, "%i.%i.%i.%i.relays.mail-abuse.org",
               dq[0], dq[1], dq[2], dq[3]);
      name_end = (name = dq_name) + strlen (name);
      xsyslogf (log_NAME, log_DebugInfo, "Checking for known open relay: %s", dq_name);
      if ((host = xsock_gethostbyname(dq_name, &dns)) == 0)
        return false; /* not a known open relay */
      /* Is a known open relay if first returned IP is 127.0.0.2 */
      if (host->h_addr_list[0][0] == 127 &&
          host->h_addr_list[0][1] == 0 &&
          host->h_addr_list[0][2] == 0 &&
          host->h_addr_list[0][3] == 2)
      {
        dns_dispose(dns);
        goto open_relay;
      }
      /* We're not checking alias IPs */
      return false;
    }
    /* Something's wrong with the IP address? Treat as if not known */
    return false;
  }
  else
  {
    dns_t *dns;
    struct hostent *host;
    dns_t *dns_check;
    struct hostent *host_check;
    int i;
    if (name_end - name > 255)
      name_end = name + 255;
    dq_name[name_end - name] = 0;
    strncpy (dq_name, name, name_end - name);
    if ((host_check = xsock_gethostbyname(dq_name, &dns_check)) == 0)
      return 0; /* assume OK (could be temporarily unresolvable) */
    for (i = 0; i < host_check->h_length; i += 4)
    {
      sprintf (dq_name, "%i.%i.%i.%i.relays.mail-abuse.org",
               host_check->h_addr_list[i][3],
               host_check->h_addr_list[i][2],
               host_check->h_addr_list[i][1],
               host_check->h_addr_list[i][0]);
      name_end = (name = dq_name) + strlen (name);
      xsyslogf (log_NAME, log_DebugInfo, "Checking for known open relay: %s", dq_name);
      if ((host = xsock_gethostbyname(dq_name, &dns)) == 0)
        continue; /* not a known open relay */
      /* Is a known open relay if first returned IP is 127.0.0.2 */
      if (host->h_addr_list[0][0] == 127 &&
          host->h_addr_list[0][1] == 0 &&
          host->h_addr_list[0][2] == 0 &&
          host->h_addr_list[0][3] == 2)
      {
        dns_dispose(dns_check);
        dns_dispose(dns);
        goto open_relay;
      }
    }
    dns_dispose(dns_check);
    dns_dispose(dns);
    return false;
  }
  /* Resolve the name */


open_relay:
  xsyslogf (log_NAME, log_ClientInfo, "Mail received from an open relay: %s A 127.0.0.2", dq_name);
  return true;
}
#endif

static int pop_lineterm_len;

static bool pop_handle_retr(xsock sock, pop_info *info, const char *com)
{
  const char *pop_buf;
  bool wasline, isline;
  const char *seperator;
#ifdef BLOCK_OPEN_RELAYS
  int inheader = 1; /* true until we encounter the message body or some header other than Return-Path and Received */
  char relay[256] = "";
#endif

  seperator = pop_rmailnum ? HASHBANG_RMAIL_N : HASHBANG_RMAIL;

  sock=sock;

  xsyslog_logmessage(log_NAME, msgs_lookup1("Awaiting", com), log_DebugInfo);
  if ((pop_buf = bufsock_read_line(info->bs, &wasline, NULL)) == 0)
  {
    char str[32];
    sprintf(str, "%d", errno);
    xsyslog_logmessage(log_NAME,
      msgs_lookup3("NoRep", "RETR", errlist_str(errno), str),
      log_SocketError);
    return false;
  }
  if (strncmp(pop_buf, "+OK", 3))
  {
    xsyslog_logmessage(log_NAME, msgs_lookup1("BadRep", "RETR"),
    	log_ServerError);
    xsyslog_logmessage(log_NAME, pop_buf, log_ServerError);
    return false;
  }
  xsyslog_logmessage(log_NAME, pop_buf, log_ServerResponse);
  /*while (!wasline && bufsock_read_line(info->bs, &wasline));*/
  /* Write separator */
  if ( fprintf( info->fp, seperator, 0 ) < 0 )
  {
    treport(msgs_lookup1("InFile", info->u->lname), log_OSError);
    return false;
  }

  ++info->msg;
  info->msg_cur = 0;
  pop_show_status(info, true);

  for (; !info->stopped; )
  {
    bool escdot;
    int len;

    if ((pop_buf = bufsock_read_line(info->bs, &isline, &len)) == 0)
    {
      char str[32];
      sprintf(str, "%d", errno);
      xsyslog_logmessage(log_NAME,
        msgs_lookup3("NoRep", "RETR", errlist_str(errno), str),
        log_SocketError);
      return false;
    }

    if ( frontend_debug )
      xsyslogf( log_NAME, log_DebugInfo, "<< '%s'", pop_buf );

    if (wasline && pop_buf[0] == '.')
      escdot = true;
    else
      escdot = false;
    if (escdot)
    {
      if (pop_buf[1] == 0 && isline)
      {
        pop_show_status(info, true);
        break;
      }
    }
    info->msg_cur += (len + pop_lineterm_len - escdot);
    pop_show_status(info, false);
    if (fwrite(escdot ? pop_buf + 1 : pop_buf, 1, escdot ? len - 1 : len, info->fp) == EOF ||
        ( isline && putc('\n', info->fp) == EOF) )
    {
      treport(msgs_lookup1("InFile", info->u->lname), log_OSError);
      return false;
    }
#ifdef BLOCK_OPEN_RELAYS
    if (wasline && inheader)
    {
      const char *ptr = escdot ? pop_buf + 1 : pop_buf;
      if (!memcmp (ptr, "Return-Path:", 9))
        ;
      else if (!memcmp (ptr, "Return-Path:", 9))
        ;
      else if (!memcmp (ptr, "Received:", 9))
      {
        if (inheader == 1)
          strncpy (relay, ptr + 9, sizeof (relay) - 1);
      }
      else if (!isspace (*ptr))
        inheader = 0;
    }
#endif
    wasline = isline;
  }

  if (info->stopped)
    return false; /* main routine will truncate mailfile back to end of last message */

#ifdef BLOCK_OPEN_RELAYS
  if (*relay && check_open_relay (relay))
  {
//    char cmd[32];
//    sprintf(cmd, "DELE %d", atoi(com + 5));
//    pop_exe_command(info, cmd, pop_handle_dele);
    return false;
  }
#endif

  if (pop_rmailnum)
  {
    long int len = ftell( info->fp );
    fseek( info->fp, info->last_offset, SEEK_SET );
    if ( fprintf( info->fp, HASHBANG_RMAIL_N, len - info->last_offset - LENGTH_HASHBANG_RMAIL_N) < 0 )
      return false;
    info->last_offset = len;
    fseek( info->fp, 0, SEEK_END );
  }
  info->session_count++;


  fflush(info->fp);

  if (info->u->delete)
  {
    char cmd[32];
    sprintf(cmd, "DELE %d", atoi(com + 5));
    if (!pop_exe_command(info, cmd, pop_handle_dele))
      return false;
  }
  return true;
}

static bool pop_eraselastmsg( FILE **f, char *lname, long offset )
{
  char pathname[MAX_PATH_LEN];

  snprintf( pathname, sizeof (pathname), TASK_MAIL_TEXT".%s", lname );
  fclose( *f );
  file_truncate( pathname, offset ); /* truncate to end of last message received */
  *f = fopen(pathname, "r+"); /* r+ so we can update the # rmail markers */
  if ( !*f || fseek( *f, 0, SEEK_END) )
  {
    treport( msgs_lookup1("InFile", lname ), log_OSError );
    return false;
  }
  return true;
}

static bool pop_addoversizemsg( pop_info *info )
{
  char size[64];
  char mbox[64];
  int len;

  dkm_to_str( size, info->list[info->msg-1] );
  *mbox = 0;
  len = strlen( info->u->name );
  if ( len >= sizeof(mbox)-1 ) len = sizeof(mbox) - 2; /* null + '@' */
  memcpy( mbox, info->u->name, len );
  mbox[ len ] = '@';
  mbox[ len + 1 ] = 0;
  strncat( mbox, info->u->server, sizeof(mbox) - (len+2) );

  if ( fprintf( info->fp,
  /*0        1         2         3         4         5         6         7         8 */
  /*12345678901234567890123456789012345678901234567890123456789012345678901234567890 */
   "\n"
   "************************************************************************\n"
   "* %-68s *\n"
   "* %-68s *\n"
   "* %-68s *\n"
   "* %-68s *\n"
   "* Mailbox      : %-53s *\n"
   "* Message size : %-53s *\n"
   "* %-68s *\n"
   "* %-68s *\n"
   "************************************************************************\n"
   "\n",
  /* End of format string */
  /*0        1         2         3         4         5         6         7 */
  /*1234567890123456789012345678901234567890123456789012345678901234567890 */
     "Message from "TASK_NAME" "TASK_VERSION,
     "The email shown above / below exceeded the configured size limit and",
     "was not fetched.",
     "",
     mbox,
     size,
     "To fetch this message, repeat the fetch and press 'Download'",
     "when the 'Oversize query' dialogue appears." ) < 0 )
  {
    treport(msgs_lookup1("InFile", info->u->lname), log_OSError);
    return false;
  }

  return true;
}

static bool pop_handle_top(xsock sock, pop_info *info, const char *com)
{
  const char *pop_buf;
  bool wasline = true, isline;
  char from[64], subject[64]; /* to hold headers we'll read from the message */
  int inheader = 1; /* true whilst we're in the header of the message - ie. before first blank line */
  long fpos;
  const char *seperator;
#ifdef BLOCK_OPEN_RELAYS
  char relay[256] = "";
#endif

  seperator = pop_rmailnum ? HASHBANG_RMAIL_N : HASHBANG_RMAIL;

  sock=sock;
  strcpy( from, "(unknown)" );
  strcpy( subject, "(unknown)" );

  xsyslog_logmessage(log_NAME, msgs_lookup1("Awaiting", com), log_DebugInfo);
  if ((pop_buf = bufsock_read_line(info->bs, &wasline, NULL)) == 0)
  {
    char str[32];
    sprintf(str, "%d", errno);
    xsyslog_logmessage(log_NAME,
      msgs_lookup3("NoRep", "TOP", errlist_str(errno), str),
      log_SocketError);
    return false;
  }

  /* Write separator */
  if ( fprintf( info->fp, seperator, 0 ) < 0 )
  {
    treport(msgs_lookup1("InFile", info->u->lname), log_OSError);
    return false;
  }

  ++info->msg;
  info->msg_cur = 0;
  pop_show_status(info, true);

  if (strncmp(pop_buf, "+OK", 3))
  {
    xsyslog_logmessage(log_NAME, msgs_lookup1("BadRep", "TOP"),
    	log_ServerError);
    xsyslog_logmessage(log_NAME, pop_buf, log_ServerError);
    /* Oh dear. TOP command failed, server probably doesn't support it.
     * Write out a fake header, then leave the user to make the choice.
     * From / Subject will be left showing '(unknown)'
     * Delete / Download / Leave will work as expected, except it's impossible
     * to download a message header.
     */
    if ( fprintf( info->fp,
      "From: "TASK_NAME"\n"
      "Subject: Oversize email\n"
      "X-Info: Server reply to TOP was '%s'\n"
      "\n", pop_buf ) < 0 )
    {
      treport(msgs_lookup1("InFile", info->u->lname), log_OSError);
      return false;
    }
  }
  else
  {
    xsyslog_logmessage(log_NAME, pop_buf, log_ServerResponse);

    for (; !info->stopped; )
    {
      bool escdot;

      if ((pop_buf = bufsock_read_line(info->bs, &isline, NULL)) == 0)
      {
        char str[32];
        sprintf(str, "%d", errno);
        xsyslog_logmessage(log_NAME,
          msgs_lookup3("NoRep", "TOP", errlist_str(errno), str),
          log_SocketError);
        return false;
      }
      /* We only check for a '.' at the start of the line if the last block we got was the end
       * of a line.
       */
      if (wasline && pop_buf[0] == '.')
        escdot = true;
      else
        escdot = false;
      if (escdot)
      {
        if (pop_buf[1] == 0 && isline)
        {
          pop_show_status(info, true);
          break;
        }
      }
      info->msg_cur += (strlen(pop_buf) + pop_lineterm_len - escdot);
      pop_show_status(info, false);
      if (fputs(escdot ? pop_buf + 1 : pop_buf, info->fp) == EOF ||
      	putc('\n', info->fp) == EOF)
      {
        treport(msgs_lookup1("InFile", info->u->lname), log_OSError);
        return false;
      }
      if ( wasline && inheader )
      {
        const char *ptr = escdot ? pop_buf + 1 : pop_buf;
        if ( !memcmp( ptr, "From:", 5 ) )
        {
          inheader = 2;
          ptr += 5;
          while ( isspace( *ptr ) ) ptr++;
          *from = 0;
          strncat( from, ptr, sizeof(from) - 1 );
        }
        else if ( !memcmp( ptr, "Subject:", 8 ) )
        {
          inheader = 2;
          ptr += 8;
          while ( isspace( *ptr ) ) ptr++;
          *subject = 0;
          strncat( subject, ptr, sizeof(subject) - 1 );
        }
#ifdef BLOCK_OPEN_RELAYS
        else if (!memcmp (ptr, "Return-Path:", 9))
          ;
        else if (!memcmp (ptr, "Received:", 9))
        {
          if (inheader == 1)
            strncpy (relay, ptr + 9, sizeof (relay) - 1);
        }
#endif
        else if ( *ptr == 0 )
        {
          inheader = 0;
          /* Write out a bit saying why the message was downloaded header only */
          if ( !pop_addoversizemsg( info ) )
            return false;
        }
#ifdef BLOCK_OPEN_RELAYS
        else if (!isspace (*ptr))
          inheader = 2;
#endif
      }
      wasline = isline;
    }

    if ( info->stopped )
      return false; /* main routine will truncate mailfile back to end of last message */
  }
  if ( inheader )
  {
    /* er, haven't seen end of header. Odd. Write out a bit saying why the message was downloaded header only */
    if ( !pop_addoversizemsg( info ) )
      return false;
  }

#ifdef BLOCK_OPEN_RELAYS
  if (*relay && check_open_relay (relay))
  {
//    char cmd[32];
//    sprintf(cmd, "DELE %d", atoi(com + 5));
//    pop_exe_command(info, cmd, pop_handle_dele);
    return false;
  }
#endif

switch ( oversize_query( info->u->name, info->u->server, from, subject,  info->list[info->msg-1] ) )
  {
    case osaction_abort:
      return false;

    case osaction_download:
    {
      char cmd[32];
      info->msg--; /* pop_handle_retr will increment this again */
      /* truncate file back to get rid of that header we fetched */
      pop_eraselastmsg( &info->fp, info->u->lname, info->last_offset );
      sprintf(cmd, "RETR %d", atoi( com + 4 ) );
      info->requested++;
      return pop_exe_command(info, cmd, pop_handle_retr);
    }

    case osaction_delete:
    {
      char cmd[32];
      /* truncate file back to get rid of that header we fetched */
      pop_eraselastmsg( &info->fp, info->u->lname, info->last_offset );
      /* update status window as though we'd fetched the message */
      status_bytes( info->status, info->list[info->msg-1] );
      sprintf( cmd, "DELE %d", atoi( com + 4 ) );
      /* remove messages bytes, as we didn't download it - this stops
       * the 'max amount of data per connection' being triggered too
       * early
       */
      info->requestedbytes -= info->list[info->msg-1];
      return pop_exe_command(info, cmd, pop_handle_dele);
    }

    case osaction_skip:
      /* truncate file back to get rid of that header we fetched */
      pop_eraselastmsg( &info->fp, info->u->lname, info->last_offset );
      /* remove messages bytes, as we didn't download it - this stops
       * the 'max amount of data per connection' being triggered too
       * early
       */
      info->requestedbytes -= info->list[info->msg-1];
      pop_forget_id (info->ids, info->msg - 1);
      return true; /* nothing to actually do */
      
    case osaction_leave:
      /* handled after switch */
      /* also forget this message's ID, if we fetched it */
      pop_forget_id (info->ids, info->msg - 1);
      break;
    
    default:
      assert(0);
  }
  /* otherwise, must be osaction_leave */
  info->session_count++;

  if (pop_rmailnum)
  {
    fpos = ftell( info->fp );
    fseek( info->fp, info->last_offset, SEEK_SET );
    if ( fprintf( info->fp, HASHBANG_RMAIL_N, fpos - info->last_offset - LENGTH_HASHBANG_RMAIL_N) < 0 )
      return false;
    info->last_offset = fpos;
    fseek( info->fp, 0, SEEK_END );
  }

  fflush(info->fp);

  return true;
}

static bool pop_handle_quit(xsock sock, pop_info *info, const char *com)
{
  const char *pop_buf;
  bool wasline;
  sock=sock;

  while ( pop_pipesize > 0 )
    pop_unpipe( info ); /* ignore return code. What can we do if something aborts? */

  if ( !xsock_xwrite(pop_socket, "QUIT") ) /* must be done with an empty pipeline */
    return false;

  xsyslog_logmessage(log_NAME, msgs_lookup1("Awaiting", com), log_DebugInfo);
  if ((pop_buf = bufsock_read_line(info->bs, &wasline, NULL)) == 0)
  {
    char str[32];
    sprintf(str, "%d", errno);
    xsyslog_logmessage(log_NAME,
      msgs_lookup3("NoRep", "QUIT", errlist_str(errno), str),
      log_SocketError);
    return false;
  }
  if (strncmp(pop_buf, "+OK", 3))
  {
    xsyslog_logmessage(log_NAME, msgs_lookup1("BadRep", "QUIT"),
    	log_ServerError);
    xsyslog_logmessage(log_NAME, pop_buf, log_ServerError);
    return false;
  }
  xsyslog_logmessage(log_NAME, pop_buf, log_ServerResponse);
  /*while (!wasline && bufsock_read_line(info->bs, &wasline));*/
  return true;
}


static bool pop_id_in_saved_ids (const pop_info *info, int msgno)
{
  int i = -1;
  const char *id;

  if (!info->ids || !info->saved_ids)
    return false;

  id = info->ids[msgno];
  while (info->saved_ids[++i])
    if (id[0] == info->saved_ids[i][0] && !strcmp (id, info->saved_ids[i]))
      return true;

  return false;
}


static bool pop_download( pop_info *info )
{
  int i;
  bool result = false;
  char pathname[MAX_PATH_LEN];
  char cmd[32];
  int config_maxsize = pop_get_config_size( "MaxSize:0" ); /* get size from config file */
  int config_maxmsgsperconn= config_lookup_num( "MaxMessagesPerConnection:0" );
  int config_maxsizeperconn = pop_get_config_size( "MaxSizePerConnection:0" );
  leak_check();

  status_show_user(info->status, info->u->name, info->u->server);
  snprintf(pathname, sizeof (pathname), TASK_MAIL_TEXT".%s", info->u->lname);
  info->fp = fopen(pathname, "r+"); /* r+ so we can update the # rmail markers */
  if (!info->fp)
  {
    /* file doesn't exist? Try creating it... */
    info->fp = fopen(pathname, "w");
    if ( info->fp )
      fclose( info->fp );
    info->fp = fopen(pathname, "r+");
  }
  if ( !info->fp || fseek(info->fp, 0, SEEK_END) )
  {
    treport(msgs_lookup1("InFile", info->u->lname), log_OSError);
    leak_check();
    xsock_xwrite(pop_socket, "QUIT");
    leak_check();
    return false;
  }
  info->last_offset = ftell(info->fp);

  info->msg = 0;

  if (info->list)
  {
    if (!pop_exe_command(info, "LIST", pop_handle_list))
    {
      free(info->list);
      info->list = 0;
    }
  }
  info->first_msg = 1;
  if ( info->u->uidl )
  {
    if (!pop_exe_command(info, "UIDL", pop_handle_uidl))
    {
      info->first_msg = 1;
      status_total_total(info->status, info->stat_size);
      status_count_total(info->status, info->stat_count);
    }
    else
    {
      /* FIXME: combined list + last could give proper done / todo sizes.
       *        last on own should sho ? for todo sizes */
      status_total_total(info->status, info->stat_size);
      if ( info->first_msg <= info->stat_count )
      {
        status_count_total(info->status, info->stat_count); /* to avoid opening status window unneccessarily */
        info->msg = info->first_msg - 1;
      }

    }
  }
  else if ( info->u->last )
  {
    if (!pop_exe_command(info, "LAST", pop_handle_last))
    {
      info->first_msg = 1;
      status_total_total(info->status, info->stat_size);
      status_count_total(info->status, info->stat_count);
    }
    else
    {
      /* FIXME: combined list + last could give proper done / todo sizes.
       *        last on own should sho ? for todo sizes */
      status_total_total(info->status, info->stat_size);
      if ( info->first_msg <= info->stat_count )
      {
        status_count_total(info->status, info->stat_count); /* to avoid opening status window unneccessarily */
        info->msg = info->first_msg - 1;
      }

    }
  }

  for (i = info->first_msg; i <= info->stat_count; ++i)
  {
    int maxsize = info->u->maxsize;
    if ( maxsize == -1 ) maxsize = config_maxsize;

    if (pop_id_in_saved_ids (info, i - 1))
    {
      xsyslogf(log_NAME, log_DebugInfo, "already seen message %d, UID %s", i, info->ids[i-1]);
      continue;
    }

    if ( info->list )
      xsyslogf(log_NAME, log_DebugInfo, "maxsize = %d, i = %d, info->list[i-1] = %d", maxsize, i, info->list[i-1] );
    else
      xsyslogf(log_NAME, log_DebugInfo, "info->list is NULL" );

    xsyslogf(log_NAME, log_DebugInfo, "requested = %d/%d, max = %d/%d", info->requested, info->requestedbytes,
                                                            config_maxmsgsperconn, config_maxsizeperconn );

    /* check the max amount data/messages per connection criteria */
    if ( info->u->delete || info->u->last || info->u->uidl)
    {
      /* only do it if delete, last or uidl is enabled, otherwise we'll loop
       * infinitely because we'll download the same set of messages on the
       * next connection!
       */

      if ( config_maxmsgsperconn > 0 && info->requested >= config_maxmsgsperconn )
      {
        /* reached number of messages to be retrieved before dropping the connection */
        info->unfinished = 1;
        goto pop_skipmsgs;
      }

      /* Check the maximum amount of data per connection criteria */
      if ( info->list )
      {
        /* We have message sizes - add message we're about to fetch */
        info->requestedbytes += info->list[i-1];
        if ( config_maxsizeperconn > 0 && info->requestedbytes >= config_maxsizeperconn && info->requested > 0 )
        {
          /* This isn't the first message, and we have exceeded the size limit */
          info->unfinished = 1;
          goto pop_skipmsgs;
        }
      }
    }

    if ( !info->list || maxsize == 0 || info->list[i-1] <= maxsize )
    {
      /* if there's no 'LIST' of message sizes, or there's no limit, or the limit is greater than the message size */
      /* NB. we look as info->last[info->msg] rather than info->msg-1, because info->msg has not been incremented yet */
      sprintf(cmd, "RETR %d", i);
      info->requested++;
      if (!pop_exe_command(info, cmd, pop_handle_retr))
        goto pop_download_exit;
    }
    else
    {
      /* request first 10 lines of messages. NB. 'TOP' is an optional POP3 command (RFC1939) */
      xsyslogf( log_NAME, log_MiscInfo, "Message %d in mailbox %s is too big (%d bytes)",
                i, info->u->lname, info->list[i-1] );
      sprintf( cmd, "TOP %d %d", i, config_lookup_num("OverSizeMsgLines:10") );
      if (!pop_exe_command(info, cmd, pop_handle_top))
        goto pop_download_exit;
    }
  }

pop_skipmsgs:
  /* unqueue pipelined commands before we decided if we've been successful */
  while ( pop_pipesize > 0 )
    pop_unpipe( info ); /* ignore return code. What can we do if something aborts? */

  if (!info->stopped)
    result = true;

pop_download_exit:
  /* forget about any unprocessed previously-unseen messages */
  /* Potentially a problem: if a RETR succeeded but a DELE of the same
   * message failed, that message's ID will be dropped. Possible refetch.
   */
  if (info->ids)
  {
    --i;
    while (++i < info->stat_count)
      if (!pop_id_in_saved_ids (info, i - 1))
        pop_forget_id (info->ids, i - 1);
  }
  leak_check();
  if (result)
  {
    pop_handle_quit(pop_socket, info, "QUIT");
    leak_check();
    fclose(info->fp); /* must be done after handle quit, so we can write any queued RETR's. */
  }
  else
  {
    if ( config_lookup_bool("SendQuitOnError:Y") )
      xsock_xwrite(pop_socket, "QUIT");
    fclose(info->fp);
    file_truncate(pathname, info->last_offset); /* truncate to end of last message received */
  }
  if ( info->session_count == 0 && file_exists( pathname ) && file_size( pathname ) == 0 )
    remove( pathname ); /* remove spool file if empty. (happens for mailboxs with LAST set to Y) */
  leak_check();
  return result;
}

static void pop_stop_handler( stopquit_t action, void *han )
{
  pop_info *info = (pop_info *) han;
  info->stopped = action;
  xsyslog_logmessage(log_NAME, msgs_lookup("Stopped"), log_MiscInfo);
}


static const char IdsFile[] = TASK_CHOICESDIR".UidlCache";
static const char IdsFileR[] = TASK_CHOICESWRITEDIR".UidlCache";
static const char IdsFileW[] = TASK_CHOICESWRITEDIR".UidlCacheW";


static char **pop_read_saved_ids (const user_info *u)
{
  char line[512], **ids = 0;
  int num = 0, alloc = 0;
  const int namelen = strlen (u->name);
  FILE *fp = fopen(IdsFile, "r");

  if (!fp)
    return 0; /* missing? not a problem */

  while (fgets (line, sizeof (line), fp))
  {
    int ll = strlen (line);
    if (ll && line[ll - 1] == '\n')
      line[--ll] = 0; /* remove trailing \n */

    if (line[0] > ' ' &&
        !strncmp (line, u->name, namelen) &&
        line[namelen] == ' ' &&
        !strcmp (line + namelen + 1, u->server))
    {
      /* Found our user... */
      while (fgets (line, sizeof (line), fp))
      {
        if (line[0] != '\t')
          break;
        if (num >= alloc - 1)
        {
          ids = realloc (ids, (alloc += 16) * sizeof (char *));
          if (!ids)
          {
            xsyslogf(log_NAME, log_OSError,
              "Failed to allocate %d bytes for saved id block",
              alloc * sizeof(char *) );
            strcpy(last_error, "Out of memory for saved id buffer");
            fclose (fp);
            return 0;
          }
        }
        ids[num] = CRstrdup (line + 1);
        if (ids[num])
          ids[++num] = 0;
      }
      fclose (fp);
      return ids;
    }
  }

  fclose (fp);
  return 0;
}


static void pop_save_ids (const pop_info *info)
{
  char line[512];
  bool write = true;
  const int namelen = strlen (info->u->name);
  FILE *fp = fopen(IdsFile, "r");
  FILE *wp = fopen(fp ? IdsFileW : IdsFile, "w");

  if (!wp)
  {
    xsyslogf(log_NAME, log_OSError, "Unable to open IDs list file (%s)",
             fp ? IdsFileW : IdsFile);
    strcpy(last_error, "Unable to open IDs list file");
    if (fp)
      fclose (fp);
    return;
  }

  if (fp)
  {
    while (fgets (line, sizeof (line), fp))
    {
      int ll = strlen (line);

      if (ll && line[ll - 1] == '\n')
        line[--ll] = 0; /* remove trailing \n */

      if (line[0] > ' ')
        /* Found a section which may be for this user.
         * If it is, stop copying until EOF or next section.
         */
        write = !(!strncmp (line, info->u->name, namelen) &&
                  line[namelen] == ' ' &&
                  !strcmp (line + namelen + 1, info->u->server));

      if (write)
        if (fputs (line, wp) == EOF || fputc ('\n', wp) == EOF)
          goto write_error;
    }
    fclose (fp);
  }

  if (info->ids)
  {
    if (fputs (info->u->name, wp) == EOF ||
        fputc (' ', wp) == EOF ||
        fputs (info->u->server, wp) == EOF ||
        fputc ('\n', wp) == EOF)
      goto write_error;
    if (info->ids)
    {
      int i;
      for (i = 0; i < info->stat_count; ++i)
        if (info->ids[i])
          if (fputc ('\t', wp) == EOF ||
              fputs (info->ids[i], wp) == EOF ||
              fputc ('\n', wp) == EOF)
            goto write_error;
    }
  }

  if (fclose (wp) == EOF)
    goto close_error;

  if (fp)
  {
    /* Need to replace the original file */
    err_check (_swix (OS_File, _INR (0, 1), 6, IdsFile));
    err_check (_swix (OS_FSControl, _INR (0, 2), 25, IdsFileW, IdsFileR));
  }

  return;

write_error:
  xsyslogf(log_NAME, log_OSError, "Unable to write IDs list file (%s)", IdsFileW);
  strcpy(last_error, "Unable to write IDs list file");
  if (fp)
    fclose (fp);
  if (fclose (wp))
  {
close_error:
    xsyslogf(log_NAME, log_OSError, "Unable to close IDs list file (%s)", IdsFileW);
    strcpy(last_error, "Unable to close IDs list file");
  }
}


bool pop_fetch(int user, int *count)
{
  stopquit_t stopped = stopquit_Null;
  bool result = false;
  pop_info *info = NULL;
  status_handle status = status_create();
  const user_info *u = user_get(user);
  const char *error;
  int reportconnectfail;
  int retry = config_lookup_num("MaxRetries:0");

  pop_rmailnum = config_lookup_bool("RmailNum:Y");

  if (count) *count = 0;

  pop_pipesize = 0;
  pop_maxpipesize = config_lookup_num("MaxPipeSize:8"); /* default to no pipelining */
  if ( pop_maxpipesize > POP_MAXPIPELINE ) pop_maxpipesize = POP_MAXPIPELINE;

  pop_lineterm_len = config_lookup_num("LineTermLen:2");
  reportconnectfail = config_lookup_bool("ReportConnectFailure:Y");

  status_set_label(status, msgs_lookup("User"));

/*printf("Attempting connection for %s@%s\n", u->name, u->server);*/
  /* Naughty naughty, assume we can't be given an invalid index */

  if ( !saved_pop_info )
  {
    saved_pop_info = malloc(sizeof(pop_info));
    if ( !saved_pop_info )
    {
      treport(msgs_nomem()->errmess, log_OSError);
      status_free(status);
      return false;
    }
  }
  info = saved_pop_info;
  saved_pop_info = NULL;

  info->session_count = 0;

  do
  {
    info->status         = status;
    info->bs             = 0;
    info->u              = u;
    info->stopped        = stopquit_Null;
    info->list           = 0;
    info->ids            = 0;
    info->saved_ids      = 0;
    info->requested      = 0;
    info->requestedbytes = 0;
    info->unfinished     = 0;

    stopquit_registerhandler( pop_stop_handler, info );

    status_settitle( status, u->server );

    if (u->uidl)
      info->saved_ids = pop_read_saved_ids (u);

    if ((pop_socket = xconnect_pop(u->server, status)) == -1)
    {
      if ( !info->stopped && reportconnectfail)
      {
        if (retry > 0)
          retry--;
        else
          treport(msgs_lookup2("POPConnect", u->name, u->server), log_SocketError);
      }
      else if (info->stopped)
        retry = 0;
      goto pop_fetch_fail;
    }

    if (!pop_exit_installed)
    {
      atexit(pop_close);
      pop_exit_installed = true;
    }

    if ((info->bs = bufsock_create(pop_socket)) == 0)
    {
      retry = 0;
      goto pop_fetch_fail;
    }

    error = pop_negotiate( info );
    if ( error )
    {
      if (!info->stopped)
      {
        if (retry > 0)
          retry--;
        else
          treport(msgs_lookup3("NegFail", u->name, u->server, error), log_ServerError);
      }
      else
      {
        retry = 0;
      }
      strcpy( last_error, "[None]" );
      xsock_xwrite(pop_socket, "QUIT");
      goto pop_fetch_fail;
    }
    
    {
      char msgs[32];
      char bytes[32];
      
      sprintf(msgs, "%d", info->stat_count);
      sprintf(bytes, "%d", info->stat_size);

      xsyslog_logmessage(log_NAME,
        msgs_lookup4("Stat", u->name, u->server, msgs, bytes),
        log_DebugMiscInfo);
    }

    if (!info->stat_count)
    {
      pop_handle_quit(pop_socket, info, "QUIT");
      result = true;
    }
    else if ( pop_download(info) )
      result = true;

    retry = 0;

pop_fetch_fail:
    stopquit_deregister();
    status_settitle( status, TASK_NAME" status" );
    if (info)
    {
      int i;
      if (u->uidl)
        pop_save_ids (info);

      if (info->bs)
        bufsock_free(info->bs);
      stopped = info->stopped;
      if (info->list)
        free(info->list);
      if (info->ids)
      {
        for (i = 0; i < info->stat_count; ++i)
          free (info->ids[i]);
        free(info->ids);
      }
      if (info->saved_ids)
      {
        i = -1;
        while (info->saved_ids[++i])
          free (info->saved_ids[i]);
        free(info->saved_ids);
      }
    }
    leak_check();
    pop_close();
    if ( info && info->unfinished )
      xsyslogf(log_NAME, log_MiscInfo, "Fetched %d messages for %s@%s, synchronising with server",
                                       info->session_count, u->name, u->server);
  }
  while ( (info && info->unfinished) || (retry > 0 && !info->stopped) );


  if (count) *count = info->session_count;
  if (info->session_count > 0)
  {
    xsyslogf(log_NAME, log_MiscInfo, "Fetched %d messages for %s@%s", info->session_count, u->name, u->server);
  }
  else
  {
    xsyslogf(log_NAME, log_DebugMiscInfo, "Fetched 0 messages for %s@%s", u->name, u->server);
  }
  if ( info )
  {
    if ( !saved_pop_info )
    {
      saved_pop_info = info;
    }
    else
    {
      free(info);
    }
  }
  status_free(status);
  leak_check();
  if (stopped)
    stopquit_act(stopped);
  leak_check();
  return result;
}
