/*****************************************************************************

  POPular -- A POP3 server and proxy for large mail systems

  $Id: pserv_child.c,v 1.6 2002/09/15 12:27:16 sqrt Exp $

  http://www.remote.org/jochen/mail/popular/

******************************************************************************

  Copyright (C) 1999-2002  Jochen Topf <jochen@remote.org>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA

*****************************************************************************/

#if HAVE_CONFIG_H
# include <config.h>
#endif

#include "popular.h"
#include "pserv.h"
#include "daemon.h"


/* capability list */
extern struct pserv_capa capa;

extern struct pservconfig conf;

extern int debug;

/* session this child is handling */
static struct backend_session *cs;


/* number of messages in mailbox and size of alle messages */
static long box_size;

/* this is for the list of messages */
struct msg {
  struct msg *next;
  unsigned long size;
  int deleted;
  int new;
  int read;
  char name[MAXLEN_MAILFILE+1];
};

static struct msg **msglist;


/*****************************************************************************

  readmsgdir()

*****************************************************************************/
struct msg *
readmsgdir(int newflag, const char *dirname, struct msg *m)
{
  struct dirent *dirent;
  struct msg *msg, *last=m;
  struct stat st;
  char *p;
  DIR *dir = opendir(dirname);
  if (! dir) {
    /* XLOG-DOC:ADM:0006:dir_access_failed
     * The opendir() system call on a mailbox directory failed. This probably
     * means that something is wrong with permissions in the pop spool. */
    xlog_printf(xlog_adm, 0x0006, "dir_access_failed dir='%s' errno=%d errmsg='%s'", dirname, errno, strerror(errno));
    return NULL;
  }

  while ((dirent = readdir(dir))) {
    int n;
    if (dirent->d_name[0] == '.') continue;	/* no dot files */

    msg = malloc(sizeof(struct msg));
    /* XLOG-DOC:SOS:0007:out_of_memory
     * Pserv couldn't allocate memory to hold a mail message structure. */
    if (! msg) {
      xlog_printf(xlog_sos, 0x0007, "out_of_memory");
      exit(RCODE_ERR);
    }

    msg->deleted = 0;
    msg->read    = 0;
    msg->size    = 0;
    msg->new     = newflag;

    (void) strlcpy(msg->name, dirname, sizeof(msg->name));
    (void) strlcat(msg->name, "/", sizeof(msg->name));
    n = strlcat(msg->name, dirent->d_name, sizeof(msg->name));
    if (n >= sizeof(msg->name)) {
      /* XLOG-DOC:ADM:0008:name_too_long
       * The name of a mailbox directory and file is too long. */
      xlog_printf(xlog_adm, 0x0008, "name_too_long");
      free(msg);
      continue;
    }

    /* try to get message size from file name. The format must be as
       follows: The name must end in a _ (underscore) and the file size. */
    if ((p = strrchr(msg->name, '_'))) {
      int l = get_int(p+1, 0, INT_MAX, -1, -1, -1);
      if (l >= 0) msg->size = l;
    }
    /* if we don't have a message size yet, call stat() */
    if (msg->size == 0) {
      if (stat(msg->name, &st) == 0) {
        msg->size = st.st_size;
      } else {
	/* XLOG-DOC:ADM:0009:stat_failed
	 * The stat() system call failed for a mail message file. The message
	 * will not be listed. This should never happen and probably means
	 * that somebody tinkered around with a mailbox file by hand and
	 * got the permission wrong. */
	xlog_printf(xlog_adm, 0x0009, "stat_failed file='%s'", dirent->d_name);
	free(msg);
	continue;
      }
    }
    if (msg->size == 0) {
      /* XLOG-DOC:ADM:0162:msg_size_zero
       * A mail message has size zero. It will not be listed. */
      xlog_printf(xlog_adm, 0x0162, "msg_size_zero file='%s'", dirent->d_name);
      free(msg);
      continue;
    }

    cs->msgnum++;
    if (newflag) cs->msgnew++;

    msg->next = last;
    last = msg;

    /* add 12 to the size of mailbox, if mail has been read, for
       "Status: RO\r\n" header */
    if (conf.statusheader && ! newflag) msg->size += 12;
    box_size += msg->size;
  }
  closedir(dir);
  return last;
}


/*****************************************************************************

  compare_msg()

  Is called from sort() to compare two messages by name. We start comparing
  at the 5th char, because of the leading "new/" or "cur/".

*****************************************************************************/
int
compare_msg(const void *a, const void *b)
{
  return strtol(&((*(struct msg **)a)->name[4]), (char **)NULL, 10) -
         strtol(&((*(struct msg **)b)->name[4]), (char **)NULL, 10);
}


/*****************************************************************************

  readmaillist()

  Current directory must be the mailbox directory, when this function is
  called.

*****************************************************************************/
int
readmaillist()
{
  struct msg *first;
  int count;
  first = readmsgdir(1, "new", NULL);
  first = readmsgdir(0, "cur", first);
  msglist = malloc(cs->msgnum * sizeof(struct msg *));
  if (! msglist) {
    /* XLOG-DOC:SOS:0014:out_of_memory
     * Pserv can't allocate memory for a msglist structure. */
    xlog_printf(xlog_sos, 0x0014, "out_of_memory");
    return 0;
  }
  for (count = cs->msgnum; count; count--) {
    msglist[count-1] = first;
    first = first->next;
  }
  qsort(msglist, cs->msgnum, sizeof(struct msg *), compare_msg);
  return 1;
}


/*****************************************************************************

  send_dot_stuffed()

*****************************************************************************/
int
send_dot_stuffed(struct io_ctx *ioc, char *buf, size_t count)
{
  char *p=buf;
  char *q;

  if (count <= 0) return 0;

  if (buf[0] == '.') {
    if (io_syswrite(ioc, ".", 1) < 0) return -1;
  }

  while ((q = search_string(p, "\r\n.", count-(p-buf)))) {
    if (io_syswrite(ioc, p, (q-p)+2) < 0) return -1;
    p = q+1;
    *p = '.';
  }

  if (io_syswrite(ioc, p, (buf+count)-p) < 0) return -1;

  return count;
}


/*****************************************************************************

  sendmailmsg(int n, int lines)

  This function sends message <n> to the client. It will send the whole
  header, an empty line, and <lines> lines from the body. If <lines> is -1
  it will send the whole body.

  The message *must* have the proper line endings (\r\n) already, they will
  *not* be changed. If a line starts with a dot, another dot is added at
  the beginning as required by the POP3 protocol.

  If the message has already been read it will add a "Status: RO" header
  at the end of the headers.

*****************************************************************************/
char *
sendmailmsg(struct io_ctx *ioc, int n, int lines)
{
  int file;
  char *m, *p;
  int size = msglist[n]->size;

  cs->state = sstRead;
  cs->statetime = time(NULL);

  DEBUG2(DG_POP, "sendmailmsg", "started msg=%d lines=%d", n, lines);

  if (conf.statusheader && ! msglist[n]->new) size -= 12;

  if ((lines < 0) && (! msglist[n]->read)) {		/* RETR x */
    /* count this as a read and remember that this mail has been read */
    cs->msgread++;
    msglist[n]->read = 1;
  }

  file = open(msglist[n]->name, O_RDONLY);
  if (file < 0) {
    if (errno == ENOENT) {
      /* XLOG-DOC:ERR:0158:mailfile_does_not_exist
       * A mail message file does not exist. This probably means that the
       * mail has been deleted by another POP session running at the same
       * time. */
      xlog_printf(xlog_err, 0x0158, "mailfile_does_not_exist name='%s'", msglist[n]->name);
      return "-ERR no such message";
    } else {
      /* XLOG-DOC:ADM:0159:mailfile_open_failed
       * Opening a mail message file for reading failed. */
      xlog_printf(xlog_adm, 0x0159, "mailfile_open_failed name='%s' errno=%d errmsg='%s'", msglist[n]->name, errno, strerror(errno));
      return "-ERR internal error";
    }
  }

  m = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE, file, 0);
  if (m == MAP_FAILED) {
    /* XLOG-DOC:SOS:015a:mailfile_mmap_failed
     * The mmap () system call on the mail message file failed. */
    xlog_printf(xlog_sos, 0x015a, "mailfile_mmap_failed msgnum=%d errno=%d errmsg='%s'", n, errno, strerror(errno));
    return "-ERR internal error";
  }

  DEBUG0(DG_POP, "sendmailmsg", "file is mmaped");

  if (m[0] == '\0') return "-ERR internal error";

  io_buf_printf(ioc, "+OK %ld octets\r\n", msglist[n]->size);
  io_buf_flush(ioc);

  if (m[0] == '\r' && m[1] == '\n') {	/* CRLF at beginning: no header */
    p = m;
  } else {				/* find end of header */
    p = search_string(m, "\r\n\r\n", size);
    if (p) p+=2;
  }
  if (p) {				/* found body, send header */
    if (send_dot_stuffed(ioc, m, p-m) < 0) {
      /* XLOG-DOC:ERR:015d:write_error
       * Error writing mail message to proxy socket. */
      xlog_printf(xlog_err, 0x015d, "write_error errno=%d errmsg='%s'", errno, strerror(errno));
      exit(RCODE_OK);
    }
  } else {				/* no body, send header */
    if (send_dot_stuffed(ioc, m, size) < 0) {
      /* XLOG-DOC:ERR:015e:write_error
       * Error writing mail message to proxy socket. */
      xlog_printf(xlog_err, 0x015e, "write_error errno=%d errmsg='%s'", errno, strerror(errno));
      exit(RCODE_OK);
    }
  }

  if (conf.statusheader && ! msglist[n]->new) io_buf_writeln(ioc, "Status: RO");

  if (p) {
    if (lines == 0) {
      io_buf_writeln(ioc, "");
    } else if (lines == -1) {
      io_buf_flush(ioc);
      if (send_dot_stuffed(ioc, p, (m+size)-p) < 0) {
	/* XLOG-DOC:ERR:015f:write_error
	 * Error writing mail message to proxy socket. */
	xlog_printf(xlog_err, 0x015f, "write_error errno=%d errmsg='%s'", errno, strerror(errno));
	exit(RCODE_OK);
      }
    } else {
      int i;
      char *q, *r;

      DEBUG1(DG_POP, "sendmailmsg", "top lines=%d", lines);

      io_buf_flush(ioc);

      for (i=0, q=p; i<=lines; i++, q=r+2) {
	r = search_string(q, "\r\n", size-(q-m));
        DEBUG1(DG_POP, "sendmailmsg", "top r=%lx", r);
	if (!r) {
	  q = m+size;
	  break;
	}
      }
      DEBUG1(DG_POP, "sendmailmsg", "top q-p=%d", q-p);
      if (send_dot_stuffed(ioc, p, q-p) < 0) {
	/* XLOG-DOC:ERR:0161:write_error
	 * Error writing mail message to proxy socket. */
	xlog_printf(xlog_err, 0x0161, "write_error errno=%d errmsg='%s'", errno, strerror(errno));
	exit(RCODE_OK);
      }
    }
  }

  /* if the message is not terminated by CRLF, send one */
  if (m[size-2] != '\r' || m[size-1] != '\n') {
    io_buf_writeln(ioc, "");
  }

  DEBUG0(DG_POP, "sendmailmsg", "finished sending");
  if (munmap(m, size) != 0) {
    /* XLOG-DOC:SOS:015b:mailfile_munmap_failed
     * The unmap () system call on the mail message file failed. */
    xlog_printf(xlog_sos, 0x015b, "mailfile_munmap_failed msgnum=%d errno=%d errmsg='%s'", n, errno, strerror(errno));
  }
  close(file);

  return ".";
}


/*****************************************************************************

  list_or_uidl()

  LIST and UIDL command

*****************************************************************************/
void
list_or_uidl(struct io_ctx *ioc, const char *s, int uidl)
{
  if (s) {	/* argument given, list one message only */
    int num = get_int(s, 1, cs->msgnum, -1, -1, -1);
    if (num < 1) {
      io_writeln(ioc, "-ERR no such message");
    } else if (msglist[num-1]->deleted) {
      io_writeln(ioc, "-ERR message is deleted");
    } else {
      if (uidl) {
	io_buf_printf(ioc, "+OK %d %s\r\n", num, (msglist[num-1]->name)+4);
      } else {
        io_buf_printf(ioc, "+OK %d %lu\r\n", num, msglist[num-1]->size);
      }
      io_buf_flush(ioc);
    }
  } else {	/* no argument, list all messages */
    int i;
    io_buf_writeln(ioc, "+OK");
    for (i=0; i < cs->msgnum; i++) {
      if (msglist[i]->deleted) continue;
      if (uidl) {
	io_buf_printf(ioc, "%d %s\r\n", i+1, (msglist[i]->name)+4);
      } else {
	io_buf_printf(ioc, "%d %lu\r\n", i+1, msglist[i]->size);
      }
    }
    io_writeln(ioc, ".");
  }
}


/*****************************************************************************

  do_command(struct io_ctx *ioc, char *line)

  Parse <line> as POP command and do whatever the command says.

*****************************************************************************/
int
do_command(struct io_ctx *ioc, char *line)
{
  char *s;
  char buf[MAXBUF];

  /* find arguments and if there are any separate them from the command */
  s = strchr(line, ' ');
  if (s) {
    *s = 0;
    s++;
  }

  if (! strcasecmp(line, "QUIT")) {
    io_writeln(ioc, "+OK");
    return 0;
  } else if (! strcasecmp(line, "NOOP")) {
    io_writeln(ioc, "+OK");
  } else if (! strcasecmp(line, "RSET")) {
    int i;
    for (i=0; i < cs->msgnum; i++) {
      msglist[i]->deleted = 0;
      msglist[i]->read = 0;
    }
    cs->msgread = 0;
    cs->msgdel = 0;
    io_writeln(ioc, "+OK");
  } else if (! strcasecmp(line, "STAT")) {
    int i, n=0;
    unsigned long sum=0;
    for (i=0; i < cs->msgnum; i++) {
      if (msglist[i]->deleted) continue;
      n++;
      sum += msglist[i]->size;
    }
    snprintf(buf, sizeof(buf), "+OK %d %lu", n, sum);
    io_writeln(ioc, buf);
  } else if (! strcasecmp(line, "LIST")) {
    list_or_uidl(ioc, s, 0);
  } else if (! strcasecmp(line, "UIDL")) {
    list_or_uidl(ioc, s, 1);
  } else if (! strcasecmp(line, "RETR")) {
    if (s) {
      int num = get_int(s, 1, cs->msgnum, -1, -1, -1);
      if (num < 1) {
        io_writeln(ioc, "-ERR no such message");
      } else if (msglist[num-1]->deleted) {
        io_writeln(ioc, "-ERR message is deleted");
      } else {
        io_writeln(ioc, sendmailmsg(ioc, num-1, -1));
      } 
    } else {
      io_writeln(ioc, "-ERR no such message");
    }
  } else if (! strcasecmp(line, "TOP")) {
    if (s) {
      char *t = strchr(s, ' ');
      if (t) {
        int num;
        *t = '\0';
        t++;
        num = get_int(s, 1, cs->msgnum, -1, -1, -1);
        if (num < 1) {
          io_writeln(ioc, "-ERR no such message");
        } else if (msglist[num-1]->deleted) {
          io_writeln(ioc, "-ERR message is deleted");
        } else {
	  int c = get_int(t, 0, INT_MAX, -1, -1, -1);
	  if (c < 0) {
	    io_writeln(ioc, "-ERR parse error");
	  } else {
	    io_writeln(ioc, sendmailmsg(ioc, num-1, c));
	  }
	}
      } else {
        io_writeln(ioc, "-ERR parse error");
      }
    } else {
      io_writeln(ioc, "-ERR no such message");
    }
  } else if (! strcasecmp(line, "DELE")) {
    if (s) {
      int num = get_int(s, 1, cs->msgnum, -1, -1, -1);
      if (num < 1) {
        io_writeln(ioc, "-ERR no such message");
      } else if (msglist[num-1]->deleted) {
	io_writeln(ioc, "-ERR message is deleted");
      } else {
	msglist[num-1]->deleted = 1;
	cs->msgdel++;
	io_writeln(ioc, "+OK");
      } 
    } else {
      io_writeln(ioc, "-ERR no message number given");
    }
  } else if (! strcasecmp(line, "CAPA")) {                 /* RFC 2449 */
    switch (capa.capa_type) {
      case capa_error:
	io_writeln(ioc, "-ERR unknown command");
	break;
      case capa_none:
	io_writeln(ioc, "+OK capability list follows\r\n.");
	break;
      case capa_default:
	io_writeln(ioc, "+OK capability list follows\r\n" DEFAULT_CAPA_LIST ".");
	break;
      case capa_user:
	io_buf_writeln(ioc, "+OK capability list follows");
	io_buf_write(ioc, capa.text);
	io_writeln(ioc, ".");
	break;
      default:
	io_writeln(ioc, "-ERR unknown command");
	break;
    }
#if 0
  } else if (! strcasecmp(line, "LAST")) {
    /* The LAST command was defined in older POP3 RFCs. It is obsolete now
       but some clients need it, so we send a fake response. */
    io_writeln(ioc, "+OK 0");
#endif
  } else if (! strcasecmp(line, "LAST")) {
    /* check this special case, because we don't need to log this */
    /* this command was in old POP RFCs, but is not mentioned any more by
     * RFC 1939 */
    io_writeln(ioc, "-ERR unknown command");
  } else if (! strcasecmp(line, "FTRQ")) {
    /* check this special case, because we don't need to log this */
    /* this command seems to be used by the FTGate mail server, although
     * I don't know what it is supposed to do. See http://www.ftgate.com/ */
    io_writeln(ioc, "-ERR unknown command");
  } else if (! strcasecmp(line, "XSENDER")) {
    /* check this special case, because we don't need to log this */
    /* Netscape extension. */
    io_writeln(ioc, "-ERR unknown command");
  } else {
    /* XLOG-DOC:ERR:0018:unknown_command
     * The client sent an unknown POP command. */
    xlog_printf(xlog_err, 0x0018, "unknown_command cmd='%s'", line);
    io_writeln(ioc, "-ERR unknown command");
  }
  return 1;
}


/*****************************************************************************

  child_main()

*****************************************************************************/
int
child_main(int fd, struct backend_session *this_session, int slot)
{
  struct sockaddr_in sin;
  struct ip_address { unsigned char d[4]; } *ipa;
  unsigned int dummy = sizeof(sin);
  char peer[30];
  char mailbox[256], id[256], flags[256], *rl;
  int flag_master=0;
  int i, len;
  struct io_ctx *ioc;

  cs = this_session;
  cs->starttime = time(NULL);

  signal_init_child();
  alarm(conf.sessiontimeout);

  if (set_keepalive(fd) < 0) {
    DEBUG2(DG_NET, "child", "setting TCP keepalive failed errno=%d errmsg='%s'", errno, strerror(errno));
  }

  ioc = io_init(iot_plain, fd, "to proxy", 1, conf.idletimeout, NULL);
  if (! ioc) {
    /* XLOG-DOC:SOS:0022:out_of_memory
     * There is no memory to allocate an io context struct. The program is
     * immediately terminated. */
    xlog_printf(xlog_sos, 0x0022, "out_of_memory");
    exit(RCODE_ERR);
  }

  /* find out to whom we are talking */
  if (getpeername(fd, (struct sockaddr *) &sin, &dummy) < 0) {
    if (errno == ENOTSOCK) {
      (void) strlcpy(peer, "LOCAL", sizeof(peer));
    } else {
      /* XLOG-DOC:ERR:0023:getpeername_failed
       * The getpeername() call failed. This probably means that the client
       * and subsequently the proxy has closed the connection. */
      xlog_printf(xlog_err, 0x0023, "getpeername_failed errno=%d errmsg='%s'", errno, strerror(errno));
      exit(RCODE_OK);
    }
  } else {
    ipa = (struct ip_address *) &sin.sin_addr;
    snprintf(peer, sizeof(peer), "%d.%d.%d.%d", ipa->d[0], ipa->d[1], ipa->d[2], ipa->d[3]);
  }
  memcpy(&(cs->sin_proxy), &sin, sizeof(struct sockaddr_in));


  /* read mailbox name */
  rl = io_readln(ioc);
  if (! rl) exit(RCODE_ERR);
  len = strlcpy(mailbox, rl, sizeof(mailbox));
  if (len >= sizeof(mailbox)) {
    /* XLOG-DOC:ADM:0024:mailbox_too_long
     * The mailbox name that the proxy sent to the pserv is too long. */
    xlog_printf(xlog_adm, 0x0024, "mailbox_too_long from='%s' mailbox='%s'", peer, rl);
    goto internal_error;
  }
  len = strlcpy(cs->mailbox, rl, sizeof(cs->mailbox));
  if (len >= sizeof(cs->mailbox)) {
    /* XLOG-DOC:ADM:0025:mailbox_too_long
     * The mailbox name that the proxy sent to the pserv is too long. */
    xlog_printf(xlog_adm, 0x0025, "mailbox_too_long from='%s' mailbox='%s'", peer, rl);
    goto internal_error;
  }

  /* empty mailbox */
  if (mailbox[0] == '\0') {
    io_writeln(ioc, "-ERR missing mailbox");
    exit(RCODE_OK);
  }

  /* check mailbox */
  if (mailbox[0] == '/') {
    /* XLOG-DOC:ADM:0026:bad_mailbox
     * The mailbox name that the proxy sent to the pserv has a leading '/'. */
    xlog_printf(xlog_adm, 0x0026, "bad_mailbox from='%s' mailbox='%s'", peer, mailbox);
    goto internal_error;
  }
  for (i=0; mailbox[i]; i++) {
    if ( !(isalnum((int)(mailbox[i])) ||
           mailbox[i] == '.' ||
           mailbox[i] == '_' ||
           mailbox[i] == '-' ||
           mailbox[i] == '+' ||
           mailbox[i] == '/' ||
           mailbox[i] == '%' ||
           mailbox[i] == '='
          ) ||
          (mailbox[i] == '.' && mailbox[i+1] == '.')
       ) {
      /* XLOG-DOC:ADM:0027:bad_mailbox
       * The mailbox name that the proxy sent to the pserv has funny
       * chars in it. Only the following chars are allowed: a-z, A-Z, 0-9,
       * '.', '_', '-', '+', '/', '=', '%'. Two adjacent dots ("..") are not
       * allowed. */
      xlog_printf(xlog_adm, 0x0027, "bad_mailbox from='%s' mailbox='%s'", peer, mailbox);
      goto internal_error;
    }
  }

  /* read id */
  rl = io_readln(ioc);
  if (! rl) exit(RCODE_ERR);
  len = strlcpy(id, rl, sizeof(id));
  if (len >= sizeof(id)) {
    /* XLOG-DOC:ADM:0028:id_too_long
     * The id that the proxy sent to the pserv is too long. */
    xlog_printf(xlog_adm, 0x0028, "id_too_long from='%s' id='%s'", peer, rl);
    goto internal_error;
  }
  len = strlcpy(cs->id, rl, sizeof(cs->id));
  if (len >= sizeof(cs->id)) {
    /* XLOG-DOC:ADM:0029:id_too_long
     * The id that the proxy sent to the pserv is too long. */
    xlog_printf(xlog_adm, 0x0029, "id_too_long from='%s' id='%s'", peer, rl);
    goto internal_error;
  }

  /* check id */
  for (i=0; id[i]; i++) {
    if (!(isalnum((int)(id[i])) || id[i] == '.' || id[i] == '_' || id[i] == '-')) {
      /* XLOG-DOC:ADM:0030:bad_id
       * The id that the proxy sent to the pserv has funny chars in it. Only
       * the following chars are allowed: a-z, A-Z, 0-9, '.', '_', '-'. */
      xlog_printf(xlog_adm, 0x0030, "bad_id from='%s' id='%s'", peer, id);
      goto internal_error;
    }
  }

  xlog_set_id(id);

  /* read flags */
  rl = io_readln(ioc);
  if (! rl) exit(RCODE_ERR);
  len = strlcpy(flags, rl, sizeof(flags));
  if (len >= sizeof(flags)) {
    /* XLOG-DOC:ADM:0031:flags_too_long
     * The flags that the proxy sent to the pserv are too long. */
    xlog_printf(xlog_adm, 0x0031, "flags_too_long from='%s' flags='%s'", peer, rl);
    goto internal_error;
  }
  len = strlcpy(cs->flags, rl, sizeof(cs->flags));
  if (len >= sizeof(cs->flags)) {
    /* XLOG-DOC:ADM:0032:flags_too_long
     * The flags that the proxy sent to the pserv are too long. */
    xlog_printf(xlog_adm, 0x0032, "flags_too_long from='%s' flags='%s'", peer, rl);
    goto internal_error;
  }

  /* check flags */
  for (i=0; flags[i]; i++) {
    if (flags[i] == '-') continue;	/* this is for compatibility with
					   older version, that used a hyphen
					   to indicate a cleared flag */
    if (!isalpha((int)(flags[i]))) {
      /* XLOG-DOC:ADM:0033:bad_flags
       * The flags that the proxy sent to the pserv have funny chars in it.
       * Only a-z, A-Z, 0-9 are allowed. */
      xlog_printf(xlog_adm, 0x0033, "bad_flags from='%s' flags='%s'", peer, flags);
      goto internal_error;
    }
    if (flags[i] == 'M') flag_master=1;
  }

  /* XLOG-DOC:INF:0034:login
   * A new connection from the proxy is coming in and the mailbox name, the
   * logging id and the flags have been received. */
  xlog_printf(xlog_inf, 0x0034, "login slot=%d peer='%s' mailbox='%s' flags='%s'", slot, peer, mailbox, flags);

  /* prepare mailbox directory */
  {
    char mbdir[strlen(conf.popdir) + strlen(mailbox) + 2];
    snprintf(mbdir, sizeof(mbdir), "%s/%s", conf.popdir, mailbox);
    if (! mailbox_prepdir(mbdir)) goto internal_error;
  }

  /* read list of mails into memory */
  if (! readmaillist()) goto internal_error;
 
  io_writeln(ioc, "+OK");

  /* main loop, only a QUIT will get you out of here */
  do {
    cs->state = sstIdle;
    cs->statetime = time(NULL);
    rl = io_readln(ioc);
    if (!rl) exit(RCODE_OK);
  } while (do_command(ioc, rl));

  for (i=0; i < cs->msgnum; i++) {
    if (msglist[i]->deleted) {
      DEBUG1(DG_MAIN, "child", "deleting message %d", i+1);
      if (unlink(msglist[i]->name) != 0) {
	if (errno == ENOENT) {
	  /* XLOG-DOC:ERR:0148:msg_unlink_failed
	   * Deleting of a mail message failed. Probably another pserv process
	   * was faster. */
	  xlog_printf(xlog_err, 0x0148, "msg_unlink_failed mailbox='%s' msg='%s' errno=%d errmsg='%s'", cs->mailbox, msglist[i]->name, errno, strerror(errno));
	} else {
	  /* XLOG-DOC:ADM:0037:msg_unlink_failed
	   * After the QUIT command has been received, the server tried to
	   * delete a message but failed. */
	  xlog_printf(xlog_adm, 0x0037, "msg_unlink_failed mailbox='%s' msg='%s' errno=%d errmsg='%s'", cs->mailbox, msglist[i]->name, errno, strerror(errno));
	}
      }
      if (conf.logeachmsg) {
	/* XLOG-DOC:INF:017d:msg_info
	 * A message was deleted. */
        xlog_printf(xlog_inf, 0x0036, "msg_info mailbox='%s' msg='%s' size=%d op=del", cs->mailbox, msglist[i]->name, msglist[i]->size);
      }
    } else if (!flag_master && msglist[i]->read && msglist[i]->new) {
      /* only mark msg as read if master_flag is not set */
      char rbuf[256] = "cur/";
      DEBUG1(DG_MAIN, "child", "moving message %d", i+1);
      strlcat(rbuf, (msglist[i]->name)+4, sizeof(rbuf));
      if (rename(msglist[i]->name, rbuf) != 0) {
	if (errno == ENOENT) {
	  /* XLOG-DOC:ERR:0147:msg_rename_failed
	   * Renaming of a mail file failed, because it is not there. Another
	   * pserv process probably got to this file before we did. */
	  xlog_printf(xlog_err, 0x0147, "msg_rename_failed mailbox='%s' msg='%s' errno=%d errmsg='%s'", cs->mailbox, msglist[i]->name, errno, strerror(errno));
	} else {
	  /* XLOG-DOC:ADM:0038:msg_rename_failed
	   * After the QUIT command has been received, the server tried to
	   * rename a message from the 'new' into the 'cur' directory but
	   * failed. */
	  xlog_printf(xlog_adm, 0x0038, "msg_rename_failed mailbox='%s' msg='%s' errno=%d errmsg='%s'", cs->mailbox, msglist[i]->name, errno, strerror(errno));
	}
      }
      if (conf.logeachmsg) {
	/* XLOG-DOC:INF:017e:msg_info
	 * A message was moved from new to cur. */
        xlog_printf(xlog_inf, 0x0036, "msg_info mailbox='%s' msg='%s' size=%d op=mv", cs->mailbox, msglist[i]->name, msglist[i]->size);
      }
    }
  }

  cs->state = sstConnQuit;
  cs->statetime = time(NULL);
  /* XLOG-DOC:INF:0039:quit
   * This message is logged after the client send a QUIT. It contains
   * statistics about the number of mails in mailbox, the number of new
   * mails, the number of read mails, and the number of deleted mails. */
  xlog_printf(xlog_inf, 0x0039, "quit time=%d mails=%d new=%d retr=%d del=%d",
        time(NULL) - cs->starttime,
	cs->msgnum,
	cs->msgnew,
	cs->msgread,
	cs->msgdel);
  exit(RCODE_OK);

internal_error:
  io_writeln(ioc, "-ERR internal error");
  exit(RCODE_ERR);
}


/** THE END *****************************************************************/


syntax highlighted by Code2HTML, v. 0.9.1