/*****************************************************************************
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