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

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

  $Id: pproxy_child.c,v 1.45 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 "pproxy.h"
#include "daemon.h"
#include "net.h"


extern struct pproxyconfig conf;


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


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

  connect_to_backend()

  Connect to backend.

*****************************************************************************/
int
connect_to_backend(void)
{
  int s;

  s = socket(AF_INET, SOCK_STREAM, 0);
  if (s < 0) {
    /* XLOG-DOC:SOS:0096:backend_socket_call_failed
     * Creating a socket for a connection to the backend failed. */
    xlog_printf(xlog_sos, 0x0096, "backend_socket_call_failed errno=%d errmsg='%s'", errno, strerror(errno));
    return -1;
  }

  if (connect(s, (struct sockaddr *) &(cs->backend.backend_addr), sizeof(struct sockaddr_in)) < 0) {
    /* XLOG-DOC:SOS:0097:connect_to_backend_failed
     * The connect to a backend failed. */
    xlog_printf(xlog_sos, 0x0097, "connect_to_backend_failed id=%s errno=%d errmsg='%s'", cs->backend.id, errno, strerror(errno));
    return -1;
  }

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

  return s;
}


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

  do_pop3_auth()

  returns:  0 - connection or protocol error
            1 - access denied
            2 - ok

*****************************************************************************/
int
do_pop3_auth(struct io_ctx *ioc, const char *user, const char *pass)
{
  char *line;
  char buf[1024];

  /***************************************************************************
    Read greeting banner and return error if it doesn't start with +OK.
  ***************************************************************************/
  line = io_readln(ioc);
  if (line == NULL) {
    /* XLOG-DOC:SOS:0098:backend_read_err
     * A read from a POP3 backend failed. */
    xlog_printf(xlog_sos, 0x0098, "backend_read_err");
    return 0;
  }
  if (strncmp(line, "+OK", 3)) {
    /* XLOG-DOC:SOS:0099:backend_not_ready
     * A POP3 backend sent a -ERR reply on connection. */
    xlog_printf(xlog_sos, 0x0099, "backend_not_ready msg='%s'", line);
    return 0;
  }


  /***************************************************************************
    Send USER command.
  ***************************************************************************/
  (void) strlcpy(buf, "USER ", sizeof(buf));
  (void) strlcat(buf, user, sizeof(buf));
  if (io_writeln(ioc, buf) < 0) {
    /* XLOG-DOC:SOS:009a:backend_read_err
     * A write to a POP3 backend failed. */
    xlog_printf(xlog_sos, 0x009a, "backend_write_err");
    return 0;
  }


  /***************************************************************************
    Read answer to USER command and return error if it doesn't start
    with +OK.
  ***************************************************************************/
  line = io_readln(ioc);
  if (line == NULL) {
    /* XLOG-DOC:SOS:009b:backend_read_err
     * A read from a POP3 backend failed. */
    xlog_printf(xlog_sos, 0x009b, "backend_read_err");
    return 0;
  }
  if (strncmp(line, "+OK", 3)) {
    /* XLOG-DOC:ADM:009c:login_failed
     * The login to a POP3 backend server failed. The answer to the USER
     * command was -ERR. */
    xlog_printf(xlog_adm, 0x009c, "login_failed msg='%s'", line);
    return 1;
  }


  /***************************************************************************
    Send PASS command.
  ***************************************************************************/
  (void) strlcpy(buf, "PASS ", sizeof(buf));
  (void) strlcat(buf, pass, sizeof(buf));
  if (io_writeln(ioc, buf) < 0) {
    /* XLOG-DOC:SOS:009d:backend_read_err
     * A write to a POP3 backend failed. */
    xlog_printf(xlog_sos, 0x009d, "backend_write_err");
    return 0;
  }


  /***************************************************************************
    Read answer to PASS command and return error if it doesn't start
    with +OK.
  ***************************************************************************/
  line = io_readln(ioc);
  if (line == NULL) {
    /* XLOG-DOC:SOS:009e:backend_read_err
     * A read from a POP3 backend failed. */
    xlog_printf(xlog_sos, 0x009e, "backend_read_err");
    return 0;
  }
  if (strncmp(line, "+OK", 3)) {
    /* XLOG-DOC:ADM:009f:login_failed
     * The login to a POP3 backend server failed. The answer to the PASS
     * command was -ERR. */
    xlog_printf(xlog_adm, 0x009f, "login_failed msg='%s'", line);
    return 1;
  }
 
 
  /***************************************************************************
    User is succesfully authenticated.
  ***************************************************************************/
  return 2;
}


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

  time_event()

  returns diff in milliseconds (1/1000th of a second)

*****************************************************************************/
#ifndef timersub
#define timersub(a, b, result)                                                \
  do {                                                                        \
    (result)->tv_sec = (a)->tv_sec - (b)->tv_sec;                             \
    (result)->tv_usec = (a)->tv_usec - (b)->tv_usec;                          \
    if ((result)->tv_usec < 0) {                                              \
      --(result)->tv_sec;                                                     \
      (result)->tv_usec += 1000000;                                           \
    }                                                                         \
  } while (0)
#endif
long
time_event(int start)
{
  static struct timeval tv_start;
  struct timeval tv_end, tv_diff;

  if (start) {
    if (gettimeofday(&tv_start, NULL) != 0) return -1;
    return 0;
  } else {
    if (gettimeofday(&tv_end, NULL) != 0) return -1;
    timersub(&tv_end, &tv_start, &tv_diff);
    return tv_diff.tv_sec * 1000 + tv_diff.tv_usec / 1000;
  }
}


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

  mailcheck()

  Sends UDP packet to backend to find out, whether the given mailbox is
  empty.

*****************************************************************************/
mailcheck_result_t
mailcheck(const char *mbfile)
{
  int s;
  char buf[MAXLEN_MAILCHECK];
  fd_set fdset;
  int try;

  DEBUG1(DG_NET, "mailcheck", "start mbfile='%s'", mbfile);

  s = net_open_udp_socket(&(cs->backend.backend_addr), conf.checkport);
  if (s < 0) return mcrError;

  /* Send mailbox file name */
  (void) strlcpy(buf, "*M", sizeof(buf));
  if (strlcat(buf, mbfile, sizeof(buf)) >= sizeof(buf)) {
    /* XLOG-DOC:SOS:0102:buffer_to_small
     * A buffer for sending a mailbox name to the pcheckd is too small. */
    xlog_printf(xlog_sos, 0x0102, "buffer_too_small");
    return mcrError;
  }

  time_event(1);	/* start timer */

  for (try=0; try < MAILCHECK_MAX_REQUESTS; try++) {
    int r;

    if (send(s, buf, strlen(buf), 0) != strlen(buf)) {
      /* XLOG-DOC:SOS:0103:send_failed
       * The send() call sending a request to the pcheckd failed. */
      xlog_printf(xlog_sos, 0x0103, "send_failed errno=%d errmsg='%s'", errno, strerror(errno));
      return mcrError;
    }

    FD_ZERO(&fdset);
    FD_SET(s, &fdset);
    r = rel_select(&fdset, conf.checktimeout, 0);
    if (r < 0) {
      /* XLOG-DOC:SOS:0104:select_failed
       * The select () call waiting for answer from pcheckd failed in an
       * unexpected way. */
      xlog_printf(xlog_sos, 0x0104, "select_failed errno=%d errmsg='%s'", errno, strerror(errno));
      return mcrError;
    }

    if (FD_ISSET(s, &fdset)) {
      goto got_answer;
    }
  }

  cs->mailcheck_time = time_event(0);		/* read timer */
  cs->mailcheck_retry = try;

  /* XLOG-DOC:ERR:0106:check_udp_timeout
   * Pproxy timed out waiting for an answer from pcheckd. */
  xlog_printf(xlog_err, 0x0106, "check_udp_timeout");

  close(s);
  return mcrTimeout;

got_answer:
  cs->mailcheck_time = time_event(0);		/* read timer */
  cs->mailcheck_retry = try;

  DEBUG2(DG_NET, "mailcheck", "mailcheck retries=%d msecs=%ld", cs->mailcheck_retry, cs->mailcheck_time);

  memset(buf, 0, sizeof(buf));
  if ((recv(s, buf, sizeof(buf)-1, 0) < 0)) {
    /* XLOG-DOC:SOS:0105:recv_failed
     * Recv() call failed when reading answer from pcheckd. */
    xlog_printf(xlog_sos, 0x0105, "recv_failed errno=%d errmsg='%s'", errno, strerror(errno));
    close(s);
    return mcrError;
  } else {
    close(s);
    if (! strncmp("+OK 3", buf, 5)) return mcrLoadTooHigh;
    if (! strncmp("+OK 4", buf, 5)) return mcrMaxSession;
    if (! strncmp("+OK 0", buf, 5)) return mcrEmpty;
    return mcrMail;
  }
}


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

  proxy_data()

  Copy data between two file descriptors with timeout.

*****************************************************************************/
void
proxy_data(struct io_ctx *ioc1, struct io_ctx *ioc2)
{
  fd_set readset;
  char buffer[1024*16];
  int n;
  int fd1 = ioc1->io_fd;
  int fd2 = ioc2->io_fd;

  DEBUG0(DG_NET, "proxy_data", "start");

  io_buf_flush(ioc2);
  io_buf_flush(ioc1);

  while (1) {
    FD_ZERO(&readset);
    if ( (!io_pending(ioc1)) && (!io_pending(ioc2)) ) {
      int r;
      FD_SET(fd1, &readset);
      FD_SET(fd2, &readset);
      r = rel_select(&readset, conf.proxytimeout, 0);
      if (r == 0) {
	/* XLOG-DOC:ERR:0107:timeout
	 * A timeout occurred while proxying data. */
        xlog_printf(xlog_err, 0x0107, "timeout");
        break;
      } else if (r < 0) {
	/* XLOG-DOC:SOS:0145:select_error
	 * An error occurred in the select () system call while proxying
	 * data. */
        xlog_printf(xlog_sos, 0x0145, "select_error errno=%d errmsg='%s'", errno, strerror(errno));
        break;
      }
    }
    if (io_pending(ioc1) || FD_ISSET(fd1, &readset)) {
      if ((n = io_read(ioc1, buffer, sizeof(buffer))) <= 0) {
	DEBUG1(DG_NET, "proxy_data", "read returned (to client): %d", n);
        break;
      }
      if (io_syswrite(ioc2, buffer, n) < 0) break;
      cs->bytesin += n;
    }
    if (io_pending(ioc2) || FD_ISSET(fd2, &readset)) {
      if ((n = io_read(ioc2, buffer, sizeof(buffer))) <= 0) {
	DEBUG1(DG_NET, "proxy_data", "read returned (to backend): %d", n);
        break;
      }
      if (io_syswrite(ioc1, buffer, n) < 0) break;
      cs->bytesout += n;
    }
  }
  /* XLOG-DOC:INF:0108:quit
   * The proxy child shuts down after its work is done. The proxied
   * bytes in both directions are reported. */
  xlog_printf(xlog_inf, 0x0108, "quit reason=shutdown time=%d bytesin=%d bytesout=%d", time(NULL) - cs->starttime, cs->bytesin, cs->bytesout);
}


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

  method_fakepop()

  Fakes POP backend with no mails

*****************************************************************************/
void
method_fakepop(struct io_ctx *ioc, session_state_t state) {
  int rc;

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

  DEBUG1(DG_NET, "method_fakepop", "faking pop server with state=%d", state);
  rc = pop3_fake(ioc, &(cs->vs));
  if (rc == 1) {
    /* XLOG-DOC:INF:0109:quit
     * The client closed the connection while faking a POP3 dialog. */
    xlog_printf(xlog_inf, 0x0109, "quit reason=client_shutdown time=%d", time(NULL) - cs->starttime);
  } else {
    /* XLOG-DOC:INF:010a:quit
     * The client send a quit while faking a POP3 dialog. */
    xlog_printf(xlog_inf, 0x010a, "quit reason=quit time=%d", time(NULL) - cs->starttime);
  }

  cs->state = sstDone;
  cs->statetime = time(NULL);
}


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

  method_proxy_xpop()

*****************************************************************************/
void
method_proxy_xpop(struct io_ctx *ioc, const char *mailbox, const char *id, int flags)
{
  int socket;
  struct io_ctx *backend_ioc;

  DEBUG0(DG_NET, "method_proxy_xpop", "start");

  /***************************************************************************
    Now connecting to backend.
  ***************************************************************************/
  cs->state = sstConnecting;
  cs->statetime = time(NULL);
  socket = connect_to_backend();
  if (socket < 0) {
    method_fakepop(ioc, sstBackendUnreachable);
    return;
  }
  DEBUG0(DG_NET, "method_proxy_xpop", "connected to backend");

  backend_ioc = io_init(iot_plain, socket, "to backend", 0, conf.idletimeout, NULL);
  if (! backend_ioc) {
    /* XLOG-DOC:SOS:010c:out_of_memory
     * There is not enought memory to allocate an IO context. */
    xlog_printf(xlog_sos, 0x010c, "out_of_memory");
    method_fakepop(ioc, sstBackendUnreachable);
    close(socket);
    return;
  }


  /***************************************************************************
    Write 3 lines to backend: mailboxfile, id for logging and flags. If this
    failes fake POP server.
  ***************************************************************************/
  if (io_writeln(backend_ioc, mailbox) < 0) goto server_err;
  if (io_writeln(backend_ioc, id) < 0) goto server_err;
  if (io_writeln(backend_ioc, flags == PP_FLAGS_M ? "M" : "") < 0) goto server_err;	
  DEBUG0(DG_NET, "method_proxy_xpop", "data sent to backend");


  /***************************************************************************
    And the rest is proxying.
  ***************************************************************************/
  cs->state = sstProxy;
  cs->statetime = time(NULL);
  proxy_data(ioc, backend_ioc);
  return;

server_err:
  /* XLOG-DOC:SOS:010d:write_to_backend_failed
   * A write call to the backend failed. */
  xlog_printf(xlog_sos, 0x010d, "write_to_backend_failed");
  method_fakepop(ioc, sstBackendUnreachable);
}


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

  method_proxy_pop()

*****************************************************************************/
void
method_proxy_pop(struct io_ctx *ioc, int fail_on_error, const char *user, const char *pass)
{
  int socket;
  struct io_ctx *backend_ioc;

  DEBUG0(DG_NET, "method_proxy_pop", "start");

  /***************************************************************************
    Now connecting to backend.
  ***************************************************************************/
  cs->state = sstConnecting;
  cs->statetime = time(NULL);
  socket = connect_to_backend();
  if (socket < 0) goto failed;
  DEBUG0(DG_NET, "method_proxy_pop", "connected to backend");

  backend_ioc = io_init(iot_plain, socket, "to backend", 0, conf.idletimeout, NULL);
  if (! backend_ioc) {
    close(socket);
    /* XLOG-DOC:SOS:010f:out_of_memory
     * There is not enought memory to allocate an IO context. */
    xlog_printf(xlog_sos, 0x010f, "out_of_memory");
    goto failed;
    return;
  }

  /***************************************************************************
    Authenticate user on backend and do the right thing.
  ***************************************************************************/
  switch (do_pop3_auth(backend_ioc, user, pass)) {
    case 0:
      DEBUG0(DG_NET, "method_proxy_pop", "backend error");
      goto failed;
    case 1:
      io_writeln(ioc, "-ERR access denied");
      break;
    case 2:
      io_writeln(ioc, "+OK");
      DEBUG0(DG_NET, "method_proxy_pop", "authenticated on backend");
      cs->state = sstProxy;
      cs->statetime = time(NULL);
      proxy_data(ioc, backend_ioc);
      break;
    default:
      /* XLOG-DOC:BUG:0110:illegal_value
       * The do_pop3_auth() function returned an illegal value. */
      xlog_printf(xlog_bug, 0x0110, "illegal_value returned by do_pop3_auth");
      exit(RCODE_ERR);
  }
  return;

failed:
  if (fail_on_error) {
    io_writeln(ioc, "-ERR temporarily unavailable");
  } else {
    method_fakepop(ioc, sstBackendUnreachable);
  }
}


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

  child_main()

  This function never returns.

*****************************************************************************/
void
child_main(struct proxy_session *this_session, int slot, struct virt_serv *vs)
{
  struct pdm_request ar;
  struct pdm_data ard;
  int fd;
  char peerbuf[16];
  pdm_result_t result;
  struct io_ctx *ioc;
  int use_fallback=0;

  signal_init_child();
  alarm(conf.authtimeout);


  /***************************************************************************
    Setup session.
  ***************************************************************************/
  cs = this_session;
  cs->starttime = time(NULL);
  cs->pid = getpid();
  memcpy(&(cs->vs), vs, sizeof(struct virt_serv));

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


  /***************************************************************************
    Create unique session ID from sidprefix, timestamp and process id.
  ***************************************************************************/
  snprintf(cs->id, sizeof(cs->id), "%s.%d.%d", conf.sidprefix, (int)cs->starttime, (int)cs->pid);
  xlog_set_id(cs->id);

  DEBUG0(DG_AUTH, "child_main", "start");


  /***************************************************************************
    Get own address and peer IP address from socket.
  ***************************************************************************/
  {
    unsigned int len = sizeof(struct sockaddr_in);
    /* own address is in vserv struct, too. maybe we should get it from
     * there. This is needed for pstatus here */
    if (getsockname(fd, (struct sockaddr *) &cs->sin_client_local, &len) < 0) {
      /* XLOG-DOC:ERR:0111:getsockname_error
       * getsockname() returned an error. This probably means that the
       * client disconnected before we could look at the socket. */
      xlog_printf(xlog_err, 0x0111, "getsockname_error errno=%d errmsg='%s'", errno, strerror(errno));
      exit(RCODE_OK);
    }
    len = sizeof(struct sockaddr_in);
    if (getpeername(fd, (struct sockaddr *) &cs->sin_client_remote, &len) < 0) {
      /* XLOG-DOC:ERR:0112:getpeername_error
       * getpeername() returned an error. This probably means that the
       * client disconnected before we could look at the socket. */
      xlog_printf(xlog_err, 0x0112, "getpeername_error errno=%d errmsg='%s'", errno, strerror(errno));
      exit(RCODE_OK);
    }
  }


  /***************************************************************************
    Log peer and virt_serv.
  ***************************************************************************/
  {
    /* XLOG-DOC:INF:0113:connect
     * Incoming connection from client. */
    xlog_printf(xlog_inf, 0x0113, "connect vserv=%s remote=%s/%d ns=%s slot=%d",
        vs->id,
	print_ip(&(cs->sin_client_remote), NULL),
	ntohs(cs->sin_client_remote.sin_port),
	vs->namespace,
	slot);
  }


  /***************************************************************************
    Setup IO context.
  ***************************************************************************/
  {
    struct protocol_info *pi = find_protocol_info(vs->prot, NULL);
    if (! pi) {
      /* XLOG-DOC:BUG:0114:unknown_protocol
       * No protocol information for this virtual server is found. This
       * should never happen. */
      xlog_printf(xlog_bug, 0x0114, "unknown_protocol");
      exit(RCODE_ERR);
    }
 
    ioc = io_init(pi->tls, fd, "to client", 1, conf.idletimeout, vs);
  }


  if (! ioc) {
    /* XLOG-DOC:SOS:0116:out_of_memory
     * There is not enought memory to allocate an IO context. */
    xlog_printf(xlog_sos, 0x0116, "out_of_memory");
    exit(RCODE_ERR);
  }
  cs->ioc_client = ioc;
  

  /***************************************************************************
    If virtual server is disabled, send error banner to user and shutdown
  ***************************************************************************/
  DEBUG0(DG_MAIN, "child_main", "Check is virtual server is disabled");
  if (vs->state == vsstDisabled) {
    /* XLOG-DOC:INF:0117:virt_serv_disabled
     * The virtual server is disabled */
    xlog_printf(xlog_inf, 0x0117, "virt_serv_disabled");
    if (pop3_banner(ioc, 0, vs->banner_err) < 0) {
      /* XLOG-DOC:ERR:0118:write_error
       * A write to the client failed. */
      xlog_printf(xlog_err, 0x0118, "write_error");
    }
    exit(RCODE_OK);
  }


  /***************************************************************************
    Output POP3 greeting banner.
  ***************************************************************************/
  DEBUG0(DG_MAIN, "child_main", "Output greeting banner");
  if (pop3_banner(ioc, 1, vs->banner_ok) < 0) {
    /* XLOG-DOC:ERR:0119:write_error
     * A write to the client failed. */
    xlog_printf(xlog_err, 0x0119, "write_error");
    exit(RCODE_OK);
  }


  /***************************************************************************
    Get username and password from client.
  ***************************************************************************/
  DEBUG0(DG_MAIN, "child_main", "Get username and password from client");
  {
    int rc = pop3_authphase(ioc, vs, cs->username, cs->password);
    if (rc == -1) {		/* error */
      exit(RCODE_OK);
    } else if (rc == 0) {	/* QUIT received */
      /* XLOG-DOC:ERR:011b:quit
       * Client sent QUIT command in authentication phase. */
      xlog_printf(xlog_inf, 0x011b, "quit reason=quit time=%d", time(NULL) - cs->starttime);
      exit(RCODE_OK);
    }
  }

  alarm(conf.sessiontimeout);

  /***************************************************************************
    If virtual server is to be faked, do this.
  ***************************************************************************/
  DEBUG0(DG_MAIN, "child_main", "If virtual server state is Fake, do it");
  if (vs->state == vsstFake) {
    /* XLOG-DOC:INF:011c:virt_serv_fake
     * The virtual server is to be faked. */
    xlog_printf(xlog_inf, 0x011c, "virt_serv_fake");
    method_fakepop(ioc, sstVirtServFake);
    exit(RCODE_OK);
  }


  /***************************************************************************
    There is one special case, if the namespace is 'USER' the real
    namespace is contained in the username (after a '=' char). We find
    the real namespace and put it in the variable 'rns' and modify the
    username by throwing away the namespace.
  ***************************************************************************/
  DEBUG0(DG_MAIN, "child_main", "Check for USER namespace");
  if (!strcmp(vs->namespace, "USER")) {
    char *p = strrchr(cs->username, '=');

    DEBUG0(DG_AUTH, "child_main", "namespace is USER");

    if (p) *p++ = 0;
    else p = conf.defaultns;

    (void) strlcpy(cs->namespace, p, sizeof(cs->namespace));
  } else {
    (void) strlcpy(cs->namespace, vs->namespace, sizeof(cs->namespace));
  }


  /***************************************************************************
    Authenticate user.
  ***************************************************************************/
  DEBUG0(DG_MAIN, "child_main", "Authenticate user");
  print_ip(&(cs->sin_client_remote), peerbuf),

  ar.user      = cs->username;
  ar.pass      = cs->password;
  ar.peer      = peerbuf;
  ar.namespace = cs->namespace;

  DEBUG3(DG_AUTH, "child_main", "calling pdm_auth_user() with peer='%s' ns='%s' user='%s'", ar.peer, ar.namespace, ar.user);

  result = pdm_auth_user(&ar, &ard);

  DEBUG1(DG_AUTH, "child_main", "pdm_auth_user() returned: %d", result);

  /* User is authenticated */
  cs->state = sstAuthPhase;
  cs->statetime = time(NULL);

  switch (result) {
    case pdmAccept:
      break;
    case pdmUnknown:
      if (conf.fallback[0]) {
	use_fallback = 1;
	break;
      }
      io_writeln(ioc, "-ERR access denied");
      /* XLOG-DOC:ERR:0177:auth_fail
       * The authorisation of the user failed because the user is unknown. */
      xlog_printf(xlog_err, 0x0177, "auth_fail reason=user_unknown user='%s'", ar.user);
      exit(RCODE_OK);
    case pdmFail:
      io_writeln(ioc, "-ERR access denied");
      switch (ard.reason) {
        case pdmFailIP:
	  /* XLOG-DOC:ERR:011d:auth_fail
	   * The authorisation of the user failed because of the client
	   * IP number. */
	  xlog_printf(xlog_err, 0x011d, "auth_fail reason=IP user='%s' IP=%s", ar.user, ar.peer);
	  exit(RCODE_OK);
        case pdmFailProtocol:
	  /* XLOG-DOC:ERR:011e:auth_fail
	   * The authorisation of the user failed because of the protocol
	   * used. */
	  xlog_printf(xlog_err, 0x011e, "auth_fail reason=protocol user='%s'", ar.user);
	  exit(RCODE_OK);
        case pdmFailPassword:
	  /* XLOG-DOC:ERR:011f:auth_fail
	   * The authorisation of the user failed because the wrong password
	   * was given. */
	  xlog_printf(xlog_err, 0x011f, "auth_fail reason=password user='%s'", ar.user);
	  exit(RCODE_OK);
        case pdmFailUnknown:
	  /* fallthrough */
	default:
	  /* XLOG-DOC:ERR:0120:auth_fail
	   * The authorisation of the user failed for unknown reasons. */
	  xlog_printf(xlog_err, 0x0120, "auth_fail reason=unspecified user='%s'", ar.user);
	  exit(RCODE_OK);
      }
    case pdmError: /* fallthrough */
    default:
      io_writeln(ioc, "-ERR internal error");
      /* XLOG-DOC:BUG:0121:illegal_auth_result
       * A PDM module returned an illegal result code. */
      xlog_printf(xlog_bug, 0x0121, "illegal_auth_result");
      exit(RCODE_OK);
  }


  /***************************************************************************
    Find backend and do the right thing...
  ***************************************************************************/
  {
    struct backend *be;
    char *use_backend = use_fallback ? conf.fallback : ard.backend;
    be = get_backend(use_backend, 0);
    if (be == NULL) {
      /* XLOG-DOC:SOS:0122:unknown_backend
       * A PDM module told us which backend to use. Unfortunately this
       * backend is not configured. */
      xlog_printf(xlog_sos, 0x0122, "unknown_backend backend=%s", use_backend);
      if (use_fallback) {
	io_writeln(ioc, "-ERR internal error");
        exit(RCODE_OK);
      } else {
        method_fakepop(ioc, sstUnknownBackend);
        exit(RCODE_OK);
      }
    }
    memcpy(&(cs->backend), be, sizeof(struct backend));
  }
  memcpy(&(cs->mailbox), ard.user, sizeof(cs->mailbox));

  memcpy(&(cs->sin_backend_remote), &(cs->backend.backend_addr), sizeof(struct sockaddr_in));

  /* XLOG-DOC:INF:0123:user_auth
   * User was authenticated. */
  xlog_printf(xlog_inf, 0x0123, "user_auth user='%s' backend=%s host=%s/%d mailbox='%s'", ar.user, cs->backend.id, print_ip(&(cs->backend.backend_addr), NULL), ntohs(cs->backend.backend_addr.sin_port), ard.user);

  switch (cs->backend.state) {
    case bstFake:
      if (! use_fallback) {
        /* XLOG-DOC:INF:0124:operation
         * The backend is configured to be faked. */
        xlog_printf(xlog_inf, 0x0124, "operation do=fake reason=backend");
        method_fakepop(ioc, sstBackendFake);
        break;
      }
      /* else fallthrough, because we can't fake for an unauthenticated user */
    case bstOffline:
      /* XLOG-DOC:INF:0125:quit
       * The backend is configured to be offline. */
      xlog_printf(xlog_inf, 0x0125, "quit reason=backend_offline time=%d", time(NULL) - cs->starttime);
      io_writeln(ioc, "-ERR temporarily unavailable");
      break;
    case bstOnline:
      switch (cs->backend.prot) {
	case ptPOP3:
	  /* XLOG-DOC:INF:0126:operation
	   * The backend uses POP3 protocol. Proxying will now be started. */
	  xlog_printf(xlog_inf, 0x0126, "operation do=proxy prot=pop3");
	  if (use_fallback) {
	    method_proxy_pop(ioc, use_fallback, ar.user, ar.pass);
	  } else {
	    method_proxy_pop(ioc, use_fallback, ard.user, ard.pass);
	  }
	  break;
	case ptCXPOP:
	  {
	    static char *mailcheck_result_name[] = {
	      "mail", "empty", "timeout", "load", "maxsession"
	    };
	    mailcheck_result_t mr = mailcheck(ard.user);
	    if (mr > 0) {
	      /* XLOG-DOC:INF:0127:operation
	       * The backend pcheckd told us that the mailbox is empty, the
	       * load on the storage server is too high, or the maximum number
	       * of sessions is reached on the storage server or a timeout
	       * occured while waiting for the answer from the pcheckd. The
	       * POP connection will be faked locally. */
	      xlog_printf(xlog_inf, 0x0127, "operation do=fake reason=%s retries=%d time=%ld host=%s", mailcheck_result_name[mr], cs->mailcheck_retry, cs->mailcheck_time, print_ip(&(cs->backend.backend_addr), NULL));
	      method_fakepop(ioc, sstMailboxEmpty);	/* XXX */
	      break;
	    }
	  }
	  /* XLOG-DOC:INF:0179:operation
	   * The backend uses XPOP protocol. Proxying will now be started. */
	  xlog_printf(xlog_inf, 0x0179, "operation do=proxy prot=xpop retries=%d time=%ld", cs->mailcheck_retry, cs->mailcheck_time);
	  method_proxy_xpop(ioc, ard.user, cs->id, ard.flags);
	  break;
	case ptXPOP:
	  /* XLOG-DOC:INF:0128:operation
	   * The backend uses XPOP protocol. Proxying will now be started. */
	  xlog_printf(xlog_inf, 0x0128, "operation do=proxy prot=xpop");
	  method_proxy_xpop(ioc, ard.user, cs->id, ard.flags);
	  break;
	default:
	  /* XLOG-DOC:BUG:0129:invalid_value
	   * Invalid value for the backend protocol. */
	  xlog_printf(xlog_bug, 0x0129, "invalid_value");
	  break;
      }
      break;
    default:
      /* XLOG-DOC:BUG:012a:invalid_value
       * Invalid value for the backend state. */
      xlog_printf(xlog_bug, 0x012a, "invalid_value");
      break;
  }

  exit(RCODE_OK);
}


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


syntax highlighted by Code2HTML, v. 0.9.1