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