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

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

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

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

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

  Copyright (C) 1999-2001  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"


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

  pop3_banner()

  Send POP3 greeting to client

*****************************************************************************/
int
pop3_banner(struct io_ctx *ioc, int ok, const char *greeting)
{
  char buf[strlen(greeting)+5+1];

  (void) strlcpy(buf, ok ? "+OK " : "-ERR ", sizeof(buf));
  (void) strlcat(buf, greeting, sizeof(buf));

  return io_writeln(ioc, buf);
}


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

  send_stls_capa()

*****************************************************************************/
void
send_stls_capa(struct io_ctx *ioc, struct virt_serv *vserv)
{
  if (ioc->io_type == iot_tls) return; /* already in TLS mode */
  if (vserv->starttls_type == starttls_off) return; /* no STARTTLS allowed */
  io_writeln(ioc, "STLS");
}


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

  send_capa()

*****************************************************************************/
int
send_capa(struct io_ctx *ioc, struct virt_serv *vserv)
{
  switch (vserv->capa_type) {
    case capa_error:
      return io_writeln(ioc, "-ERR unknown command");
    case capa_none:
      return io_writeln(ioc, "+OK capability list follows\r\n.");
    case capa_default:
      io_write(ioc, "+OK capability list follows\r\n" DEFAULT_CAPA_LIST);
      send_stls_capa(ioc, vserv);
      return io_writeln(ioc, ".");
    case capa_user:
      if (io_buf_writeln(ioc, "+OK capability list follows") < 0) return -1;
      if (io_buf_write(ioc, vserv->capa_ptr->text) < 0) return -1;
      send_stls_capa(ioc, vserv);
      return io_writeln(ioc, ".");
    default:
      return io_writeln(ioc, "-ERR unknown command");
  }
}


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

  pop3_authphase()

  username and password must point to a place that it at least 
  MAXLEN_USERNAME or MAXLEN_PASSWORD chars long.

  Returns -1 on error, 0 on QUIT or 1 on success.

*****************************************************************************/
int
pop3_authphase(struct io_ctx *ioc, struct virt_serv *vserv, char *username, char *password)
{
  char *buffer;
  int len;
  int bad=0;

  /* read command until a USER, STLS or QUIT command comes around */
#if USE_TLS
loop:
#endif
  while (1) {
    buffer = io_readln(ioc);
    if (!buffer) return -1;
    DEBUG1(DG_POP, "pop3_authphase", "phase 1: got command: '%s'", buffer);

    /* QUIT command */
    if (! strncasecmp("QUIT", buffer, 4)) {
      (void) io_writeln(ioc, "+OK");
      close(ioc->io_fd);
      io_destroy(ioc);
      return 0;
    }

#if USE_TLS
    /* STLS command (only if this virtual server allows it) */
    if (vserv->starttls_type != starttls_off &&
	ioc->io_type == iot_plain &&
	! strncasecmp("STLS", buffer, 4)) {
      /* XLOG-DOC:INF:0201:switch_to_tls
       * Connection is switched to TLS after STLS command. */
      xlog_printf(xlog_inf, 0x0201, "switch_to_tls");
      if (io_writeln(ioc, "+OK") < 0) goto write_error;
      (void) io_buf_flush(ioc);
      if (! io_tls_init(ioc)) {
	/* XLOG-DOC:ERR:019f:tls_init_after_stls_failed
	 * Initialization of new TLS connection after STLS failed. The
	 * connection will be dropped because we can't do anything else
	 * at this point. The +OK was already sent because we had to send
	 * it before initializing TLS on this socket.  */
	xlog_printf(xlog_err, 0x019f, "tls_init_after_stls_failed");
	close(ioc->io_fd);
	io_destroy(ioc);
	return 0;
      }
      /* XXX flush input buffer ?? */
      continue;
    }
#endif

    /* AUTH, APOP, STLS command */
    if ((! strncasecmp("AUTH", buffer, 4)) ||
        (! strncasecmp("APOP", buffer, 4)) ||
        (! strncasecmp("STLS", buffer, 4))) {
      if (io_writeln(ioc, "-ERR not supported") < 0) goto write_error;
      continue;
    }

    /* CAPA command */
    if (! strncasecmp("CAPA", buffer, 4)) {
      if (send_capa(ioc, vserv) < 0) goto write_error;
      continue;
    }

    /* USER command */
    if (!strncasecmp("USER ", buffer, 5)) {
#if USE_TLS
      if (vserv->starttls_type == starttls_force && ioc->io_type == iot_plain) {
	if (io_writeln(ioc, "-ERR must send STLS first") < 0) goto write_error;
	continue;
      } else {
#endif
	break;
#if USE_TLS
      }
#endif
    }
  
    if (! strcasecmp("USER", buffer)) {
      if (io_writeln(ioc, "-ERR missing user name") < 0) goto write_error;
      continue;
    }
 
    if (strcasecmp(buffer, "LIST") && strcasecmp(buffer, "NOOP")) {
      /* XLOG-DOC:ERR:0047:unknown_command
       * An unknown POP3 command was received from the client. The client
       * got an error message. No action needs to be taken. */
      xlog_printf(xlog_err, 0x0047, "unknown_command cmd='%s'", buffer);
    }
    if (++bad >= MAX_BAD_COMMANDS) {
      if (io_writeln(ioc, "-ERR unknown command (too many bad commands, closing connection)") < 0) goto write_error;
      /* XLOG-DOC:ERR:017f:too_many_bad_commands
       * Too many bad commands were received while in auth phase. The server
       * will close the connection. The number is configured in the
       * MAX_BAD_COMMANDS define in pconfig.h */
      xlog_printf(xlog_err, 0x017f, "too_many_bad_commands num=%d", MAX_BAD_COMMANDS);
      return -1;
    }
    if (io_writeln(ioc, "-ERR unknown command") < 0) goto write_error;
  }

  len = strlcpy(username, buffer+5, MAXLEN_USERNAME-1);
  if (len >= MAXLEN_USERNAME-1) {
    /* XLOG-DOC:ERR:0048:user_name_too_long
     * The user name that the client sent is too long for some internal
     * buffer. The buffer should be big enough for any sensible name. */
    xlog_printf(xlog_err, 0x0048, "user_name_too_long name='%s'", buffer+5);
    (void) io_writeln(ioc, "-ERR user name too long");
    return -1;
  }

  if (io_writeln(ioc, "+OK") < 0) goto write_error;

  /* read command until a PASS, STLS or QUIT command comes around */
  while (1) {
    buffer = io_readln(ioc);
    if (!buffer) return -1;
    DEBUG1(DG_POP, "pop3_authphase", "phase 2: got command: '%s'", buffer);

    /* QUIT command */
    if (! strncasecmp("QUIT", buffer, 4)) {
      (void) io_writeln(ioc, "+OK");
      close(ioc->io_fd);
      io_destroy(ioc);
      return 0;
    }

#if USE_TLS
    /* STLS command (only if this virtual server allows it) */
    if (vserv->starttls_type != starttls_off &&
	ioc->io_type == iot_plain &&
	! strncasecmp("STLS", buffer, 4)) {
      /* XLOG-DOC:INF:0200:switch_to_tls
       * Connection is switched to TLS after STLS command. */
      xlog_printf(xlog_inf, 0x0200, "switch_to_tls");
      if (io_writeln(ioc, "+OK") < 0) goto write_error;
      (void) io_buf_flush(ioc);
      if (! io_tls_init(ioc)) {
	/* XLOG-DOC:ERR:0202:tls_init_after_stls_failed
	 * Initialization of new TLS connection after STLS failed. The
	 * connection will be dropped because we can't do anything else
	 * at this point. The +OK was already sent because we had to send
	 * it before initializing TLS on this socket.  */
	xlog_printf(xlog_err, 0x0202, "tls_init_after_stls_failed");
	close(ioc->io_fd);
	io_destroy(ioc);
	return 0;
      }
      /* XXX flush input buffer ?? */

      /* Delete already entered username and go back to waiting for a USER
       * command so that a man-in-the-middle could not send wrong user. */
      username[0] = '\0';
      goto loop;
    }
#endif

    /* AUTH, APOP, STLS command */
    if ((! strncasecmp("AUTH", buffer, 4)) ||
        (! strncasecmp("APOP", buffer, 4)) ||
        (! strncasecmp("STLS", buffer, 4))) {
      if (io_writeln(ioc, "-ERR not supported") < 0) goto write_error;
      continue;
    }

    /* CAPA command */
    if (! strncasecmp("CAPA", buffer, 4)) {
      if (send_capa(ioc, vserv) < 0) goto write_error;
      continue;
    }

    /* PASS command */
    if (!strncasecmp("PASS ", buffer, 5)) break;
 
    if (! strcasecmp("PASS", buffer)) {
      if (io_writeln(ioc, "-ERR missing password") < 0) goto write_error;
      continue;
    }
 
    if (strcasecmp(buffer, "LIST") && strcasecmp(buffer, "NOOP")) {
      /* XLOG-DOC:ERR:004a:unknown_command
       * An unknown POP3 command was received from the client. The client
       * got an error message. No action needs to be taken. */
      xlog_printf(xlog_err, 0x004a, "unknown_command cmd='%s'", buffer);
    }
    if (++bad >= MAX_BAD_COMMANDS) {
      if (io_writeln(ioc, "-ERR unknown command (too many bad commands, closing connection)") < 0) goto write_error;
      /* XLOG-DOC:ERR:0180:too_many_bad_commands
       * Too many bad commands were received while in auth phase. The server
       * will close the connection. The number is configured in the
       * MAX_BAD_COMMANDS define in pconfig.h */
      xlog_printf(xlog_err, 0x0180, "too_many_bad_commands num=%d", MAX_BAD_COMMANDS);
      return -1;
    }
    if (io_writeln(ioc, "-ERR unknown command") < 0) goto write_error;
  }

  len = strlcpy(password, buffer+5, MAXLEN_PASSWORD-1);
  if (len >= MAXLEN_PASSWORD-1) {
    /* XLOG-DOC:ERR:004b:password_too_long
     * The password that the client sent is too long for some internal
     * buffer. The buffer should be big enough for any sensible password. */
    xlog_printf(xlog_err, 0x004b, "password_too_long pw='%s'", buffer+5);
    (void) io_writeln(ioc, "-ERR password too long");
    return -1;
  }

  return 1;

write_error:
  /* XLOG-DOC:ERR:0049:write_error
   * An error occurred while writing to the client. This probably means
   * that the client closed the connection. */
  xlog_printf(xlog_err, 0x0049, "write_error");
  return -1;
}


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

  pop3_fake()

  Fakes POP server with no mails.

  Returns 0 if QUIT command was received, 1 otherwise

*****************************************************************************/
int
pop3_fake(struct io_ctx *ioc, struct virt_serv *vserv)
{
  int n;
  char *buffer;

  static struct {
    char *cmd;
    int len;
    char *answer;
  } cmd_list[] = {
    { "NOOP",  4, "+OK" },
    { "RSET",  4, "+OK" },
    { "LIST ", 5, "-ERR no such message" },
    { "UIDL ", 5, "-ERR no such message" },
    { "RETR",  4, "-ERR no such message" },
    { "TOP",   3, "-ERR no such message" },
    { "DELE",  4, "-ERR no such message" },
    { "LIST",  4, "+OK\r\n." },
    { "UIDL",  4, "+OK\r\n." },
    { "STAT",  4, "+OK 0 0" },
    { "",      0, "-ERR unknown command" },
    { NULL,    0, NULL }
  };

  io_writeln(ioc, "+OK");

  while ((buffer = io_readln(ioc))) {
    if (! strncasecmp("QUIT", buffer, 4)) {
      (void) io_writeln(ioc, "+OK");
      return 0;
    } else if (! strncasecmp("CAPA", buffer, 4)) {
      if (send_capa(ioc, vserv) < 0) return -1;
      continue;
    }

    for (n=0; cmd_list[n].cmd; n++) {
      if (! strncasecmp(cmd_list[n].cmd, buffer, cmd_list[n].len)) {
        if (io_writeln(ioc, cmd_list[n].answer) < 0) return -1;
	break;
      }
    } 
  }

  return 1;
}


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


syntax highlighted by Code2HTML, v. 0.9.1