/* pop3d.c -- POP3 server protocol parsing
*
* Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/param.h>
#include <syslog.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "prot.h"
#include <sasl/sasl.h>
#include <sasl/saslutil.h>
#include "assert.h"
#include "acl.h"
#ifdef USE_AUTOCREATE
#include "autocreate.h"
#endif
#include "util.h"
#include "auth.h"
#include "global.h"
#include "tls.h"
#include "exitcodes.h"
#include "imapd.h"
#include "mailbox.h"
#include "mboxevent.h"
#include "version.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "mboxlist.h"
#include "idle.h"
#include "telemetry.h"
#include "backend.h"
#include "proc.h"
#include "proxy.h"
#include "seen.h"
#include "userdeny.h"
/* generated headers are not necessarily in current directory */
#include "imap/imap_err.h"
#include "sync_log.h"
#include "statuscache.h"
#include "iostat.h"
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 256
#endif
#ifdef HAVE_KRB
/* kerberos des is purported to conflict with OpenSSL DES */
#define DES_DEFS
#include <krb.h>
/* MIT's kpop authentication kludge */
char klrealm[REALM_SZ];
AUTH_DAT kdata;
#endif /* HAVE_KRB */
static int kflag = 0;
extern int optind;
extern char *optarg;
extern int opterr;
#ifdef HAVE_SSL
static SSL *tls_conn;
#endif /* HAVE_SSL */
static sasl_conn_t *popd_saslconn; /* the sasl connection context */
static int popd_timeout;
static char *popd_userid = 0, *popd_subfolder = 0;
static struct mailbox *popd_mailbox = NULL;
static struct auth_state *popd_authstate = 0;
static int config_popuseacl, config_popuseimapflags;
static int popd_haveaddr = 0;
static const char *popd_clienthost = "[local]";
static struct protstream *popd_out = NULL;
static struct protstream *popd_in = NULL;
static int popd_logfd = -1;
static unsigned popd_exists = 0;
static time_t popd_login_time;
static int count_retr = 0;
static int count_top = 0;
static int count_dele = 0;
static struct msg {
uint32_t uid;
uint32_t recno;
uint32_t size;
int deleted:1;
int seen:1;
} *popd_map = NULL;
static struct io_count *io_count_start;
static struct io_count *io_count_stop;
static sasl_ssf_t extprops_ssf = 0;
static int pop3s = 0;
static int popd_starttls_done = 0;
static int popd_tls_required = 0;
static int popd_myrights;
/* the sasl proxy policy context */
static struct proxy_context popd_proxyctx = {
0, 1, &popd_authstate, NULL, NULL
};
/* signal to config.c */
const int config_need_data = CONFIG_NEED_PARTITION_DATA;
/* current namespace */
static struct namespace popd_namespace;
/* PROXY stuff */
struct backend *backend = NULL;
static struct protocol_t pop3_protocol =
{ "pop3", "pop", TYPE_STD,
{ { { 0, "+OK " },
{ "CAPA", NULL, ".", NULL,
CAPAF_ONE_PER_LINE,
{ { "SASL", CAPA_AUTH },
{ "STLS", CAPA_STARTTLS },
{ NULL, 0 } } },
{ "STLS", "+OK", "-ERR", 0 },
{ "AUTH", 255, 0, "+OK", "-ERR", "+ ", "*", NULL, 0 },
{ NULL, NULL, NULL },
{ "NOOP", NULL, "+OK" },
{ "QUIT", NULL, "+OK" } } }
};
static void bitpipe(void);
/* end PROXY stuff */
static char popd_apop_chal[45 + MAXHOSTNAMELEN + 1]; /* <rand.time@hostname> */
static void cmd_apop(char *response);
static void cmd_auth(char *arg);
static void cmd_capa(void);
static void cmd_pass(char *pass);
static void cmd_user(char *user);
static void cmd_starttls(int pop3s);
static int blat(int msg, int lines);
static int openinbox(void);
static void cmdloop(void);
static void kpop(void);
static unsigned parse_msgno(char **ptr);
static void uidl_msg(uint32_t msgno);
static int msg_exists_or_err(uint32_t msgno);
static int update_seen(void);
static void usage(void);
void shut_down(int code) __attribute__ ((noreturn));
extern int saslserver(sasl_conn_t *conn, const char *mech,
const char *init_resp, const char *resp_prefix,
const char *continuation, const char *empty_chal,
struct protstream *pin, struct protstream *pout,
int *sasl_result, char **success_data);
/* Enable the resetting of a sasl_conn_t */
static int reset_saslconn(sasl_conn_t **conn);
static struct
{
char *ipremoteport;
char *iplocalport;
sasl_ssf_t ssf;
char *authid;
} saslprops = {NULL,NULL,0,NULL};
static int popd_canon_user(sasl_conn_t *conn, void *context,
const char *user, unsigned ulen,
unsigned flags, const char *user_realm,
char *out, unsigned out_max, unsigned *out_ulen)
{
char userbuf[MAX_MAILBOX_BUFFER], *p;
size_t n;
int r;
if (!ulen) ulen = strlen(user);
if (config_getswitch(IMAPOPT_POPSUBFOLDERS)) {
/* make a working copy of the auth[z]id */
if (ulen >= MAX_MAILBOX_BUFFER) {
sasl_seterror(conn, 0, "buffer overflow while canonicalizing");
return SASL_BUFOVER;
}
memcpy(userbuf, user, ulen);
userbuf[ulen] = '\0';
user = userbuf;
/* See if we're trying to access a subfolder */
if ((p = strchr(userbuf, '+'))) {
n = config_virtdomains ? strcspn(p, "@") : strlen(p);
/* make a copy of the subfolder */
if (popd_subfolder) free(popd_subfolder);
popd_subfolder = NULL;
popd_subfolder = xstrndup(p, n);
/* strip the subfolder from the auth[z]id */
memmove(p, p+n, strlen(p+n)+1);
ulen -= n;
}
}
r = mysasl_canon_user(conn, context, user, ulen, flags, user_realm,
out, out_max, out_ulen);
if (!r && popd_subfolder && flags == SASL_CU_AUTHZID) {
/* If we're only doing the authzid, put back the subfolder
in case its used in the challenge/response calculation */
n = strlen(popd_subfolder);
if (*out_ulen + n > out_max) {
sasl_seterror(conn, 0, "buffer overflow while canonicalizing");
r = SASL_BUFOVER;
}
else {
p = (config_virtdomains && (p = strchr(out, '@'))) ?
p : out + *out_ulen;
memmove(p+n, p, strlen(p)+1);
memcpy(p, popd_subfolder, n);
*out_ulen += n;
}
}
return r;
}
static int popd_proxy_policy(sasl_conn_t *conn,
void *context,
const char *requested_user, unsigned rlen,
const char *auth_identity, unsigned alen,
const char *def_realm,
unsigned urlen,
struct propctx *propctx)
{
char userbuf[MAX_MAILBOX_BUFFER];
if (config_getswitch(IMAPOPT_POPSUBFOLDERS)) {
size_t n;
char *p;
/* make a working copy of the authzid */
if (!rlen) rlen = strlen(requested_user);
if (rlen >= MAX_MAILBOX_BUFFER) {
sasl_seterror(conn, 0, "buffer overflow while proxying");
return SASL_BUFOVER;
}
memcpy(userbuf, requested_user, rlen);
userbuf[rlen] = '\0';
requested_user = userbuf;
/* See if we're trying to access a subfolder */
if ((p = strchr(userbuf, '+'))) {
n = config_virtdomains ? strcspn(p, "@") : strlen(p);
/* strip the subfolder from the authzid */
memmove(p, p+n, strlen(p+n)+1);
rlen -= n;
}
}
return mysasl_proxy_policy(conn, context, requested_user, rlen,
auth_identity, alen, def_realm, urlen, propctx);
}
static struct sasl_callback mysasl_cb[] = {
{ SASL_CB_GETOPT, (mysasl_cb_ft *) &mysasl_config, NULL },
{ SASL_CB_PROXY_POLICY, (mysasl_cb_ft *) &popd_proxy_policy, (void*) &popd_proxyctx },
{ SASL_CB_CANON_USER, (mysasl_cb_ft *) &popd_canon_user, NULL },
{ SASL_CB_LIST_END, NULL, NULL }
};
static void popd_reset(void)
{
int bytes_in = 0;
int bytes_out = 0;
proc_cleanup();
syslog(LOG_NOTICE, "counts: retr=<%d> top=<%d> dele=<%d>",
count_retr, count_top, count_dele);
count_retr = 0;
count_top = 0;
count_dele = 0;
/* close local mailbox */
if (popd_mailbox)
mailbox_close(&popd_mailbox);
/* close backend connection */
if (backend) {
backend_disconnect(backend);
free(backend);
backend = NULL;
}
if (popd_in) {
prot_NONBLOCK(popd_in);
prot_fill(popd_in);
bytes_in = prot_bytes_in(popd_in);
prot_free(popd_in);
}
if (popd_out) {
prot_flush(popd_out);
bytes_out = prot_bytes_out(popd_out);
prot_free(popd_out);
}
if (config_auditlog)
syslog(LOG_NOTICE, "auditlog: traffic sessionid=<%s> bytes_in=<%d> bytes_out=<%d>",
session_id(), bytes_in, bytes_out);
popd_in = popd_out = NULL;
#ifdef HAVE_SSL
if (tls_conn) {
tls_reset_servertls(&tls_conn);
tls_conn = NULL;
}
#endif
cyrus_reset_stdio();
popd_clienthost = "[local]";
if (popd_logfd != -1) {
close(popd_logfd);
popd_logfd = -1;
}
if (popd_userid != NULL) {
free(popd_userid);
popd_userid = NULL;
}
if (popd_subfolder != NULL) {
free(popd_subfolder);
popd_subfolder = NULL;
}
if (popd_authstate) {
auth_freestate(popd_authstate);
popd_authstate = NULL;
}
if (popd_saslconn) {
sasl_dispose(&popd_saslconn);
popd_saslconn = NULL;
}
popd_starttls_done = 0;
if(saslprops.iplocalport) {
free(saslprops.iplocalport);
saslprops.iplocalport = NULL;
}
if(saslprops.ipremoteport) {
free(saslprops.ipremoteport);
saslprops.ipremoteport = NULL;
}
if(saslprops.authid) {
free(saslprops.authid);
saslprops.authid = NULL;
}
saslprops.ssf = 0;
popd_exists = 0;
}
/*
* run once when process is forked;
* MUST NOT exit directly; must return with non-zero error code
*/
int service_init(int argc __attribute__((unused)),
char **argv __attribute__((unused)),
char **envp __attribute__((unused)))
{
int r;
int opt;
if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
setproctitle_init(argc, argv, envp);
/* set signal handlers */
signals_set_shutdown(&shut_down);
signal(SIGPIPE, SIG_IGN);
/* load the SASL plugins */
global_sasl_init(1, 1, mysasl_cb);
/* open the mboxlist, we'll need it for real work */
mboxlist_init(0);
mboxlist_open(NULL);
/* open the quota db, we'll need it for expunge */
quotadb_init(0);
quotadb_open(NULL);
/* open the user deny db */
denydb_init(0);
denydb_open(0);
if (config_getswitch(IMAPOPT_STATUSCACHE)) {
/* open statuscache db to optimize handling an empty maildrop */
statuscache_open();
}
/* setup for sending IMAP IDLE notifications */
idle_init();
/* setup for mailbox event notifications */
mboxevent_init();
/* Set namespace */
if ((r = mboxname_init_namespace(&popd_namespace, 1)) != 0) {
syslog(LOG_ERR, "%s", error_message(r));
fatal(error_message(r), EC_CONFIG);
}
mboxevent_setnamespace(&popd_namespace);
while ((opt = getopt(argc, argv, "skp:")) != EOF) {
switch(opt) {
case 's': /* pop3s (do starttls right away) */
pop3s = 1;
if (!tls_enabled()) {
syslog(LOG_ERR, "pop3s: required OpenSSL options not present");
fatal("pop3s: required OpenSSL options not present",
EC_CONFIG);
}
break;
case 'k':
kflag++;
break;
case 'p': /* external protection */
extprops_ssf = atoi(optarg);
break;
default:
usage();
}
}
return 0;
}
/*
* run for each accepted connection
*/
int service_main(int argc __attribute__((unused)),
char **argv __attribute__((unused)),
char **envp __attribute__((unused)))
{
const char *localip, *remoteip;
sasl_security_properties_t *secprops=NULL;
struct mboxevent *mboxevent = NULL;
if (config_iolog) {
io_count_start = xmalloc (sizeof (struct io_count));
io_count_stop = xmalloc (sizeof (struct io_count));
read_io_count(io_count_start);
}
session_new_id();
signals_poll();
sync_log_init();
popd_in = prot_new(0, 0);
popd_out = prot_new(1, 1);
count_retr = 0;
count_top = 0;
count_dele = 0;
/* Find out name of client host */
popd_clienthost = get_clienthost(0, &localip, &remoteip);
/* other params should be filled in */
if (sasl_server_new("pop", config_servername, NULL, NULL, NULL,
NULL, 0, &popd_saslconn) != SASL_OK)
fatal("SASL failed initializing: sasl_server_new()",EC_TEMPFAIL);
/* will always return something valid */
secprops = mysasl_secprops(0);
if (sasl_setprop(popd_saslconn, SASL_SEC_PROPS, secprops) != SASL_OK)
fatal("Failed to set SASL property", EC_TEMPFAIL);
if (sasl_setprop(popd_saslconn, SASL_SSF_EXTERNAL, &extprops_ssf) != SASL_OK)
fatal("Failed to set SASL property", EC_TEMPFAIL);
if (localip) {
popd_haveaddr = 1;
sasl_setprop(popd_saslconn, SASL_IPLOCALPORT, localip);
saslprops.iplocalport = xstrdup(localip);
}
if (remoteip) {
sasl_setprop(popd_saslconn, SASL_IPREMOTEPORT, remoteip);
saslprops.ipremoteport = xstrdup(remoteip);
}
popd_tls_required = config_getswitch(IMAPOPT_TLS_REQUIRED);
/* Set inactivity timer */
popd_timeout = config_getint(IMAPOPT_POPTIMEOUT);
if (popd_timeout < 10) popd_timeout = 10;
popd_timeout *= 60;
prot_settimeout(popd_in, popd_timeout);
prot_setflushonread(popd_in, popd_out);
if (kflag) kpop();
/* we were connected on pop3s port so we should do
TLS negotiation immediatly */
if (pop3s == 1) cmd_starttls(1);
/* Create APOP challenge for banner */
*popd_apop_chal = 0;
if (!popd_tls_required && config_getswitch(IMAPOPT_ALLOWAPOP) &&
(sasl_checkapop(popd_saslconn, NULL, 0, NULL, 0) == SASL_OK) &&
!sasl_mkchal(popd_saslconn,
popd_apop_chal, sizeof(popd_apop_chal), 1)) {
syslog(LOG_WARNING, "APOP disabled: can't create challenge");
}
prot_printf(popd_out, "+OK");
if (config_serverinfo) prot_printf(popd_out, " %s", config_servername);
if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON) {
prot_printf(popd_out, " Cyrus POP3 %s", cyrus_version());
}
prot_printf(popd_out, " server ready %s\r\n", popd_apop_chal);
cmdloop();
/* QUIT executed */
/* send a Logout event notification */
if ((mboxevent = mboxevent_new(EVENT_LOGOUT))) {
mboxevent_set_access(mboxevent, saslprops.iplocalport,
NULL, popd_userid, NULL, 1);
mboxevent_notify(&mboxevent);
mboxevent_free(&mboxevent);
}
/* don't bother reusing KPOP connections */
if (kflag) shut_down(0);
/* cleanup */
popd_reset();
if (config_iolog) {
read_io_count(io_count_stop);
syslog(LOG_INFO,
"POP session stats : I/O read : %d bytes : I/O write : %d bytes",
io_count_stop->io_read_count - io_count_start->io_read_count,
io_count_stop->io_write_count - io_count_start->io_write_count);
free(io_count_start);
free(io_count_stop);
}
return 0;
}
/* Called by service API to shut down the service */
void service_abort(int error)
{
shut_down(error);
}
static void usage(void)
{
prot_printf(popd_out, "-ERR usage: pop3d [-C <alt_config>] [-k] [-s]\r\n");
prot_flush(popd_out);
exit(EC_USAGE);
}
/*
* Cleanly shut down and exit
*/
void shut_down(int code)
{
int bytes_in = 0;
int bytes_out = 0;
in_shutdown = 1;
proc_cleanup();
/* close local mailbox */
if (popd_mailbox)
mailbox_close(&popd_mailbox);
if (popd_map) {
free(popd_map);
}
/* close backend connection */
if (backend) {
backend_disconnect(backend);
free(backend);
}
syslog(LOG_NOTICE, "counts: retr=<%d> top=<%d> dele=<%d>",
count_retr, count_top, count_dele);
sync_log_done();
if (config_getswitch(IMAPOPT_STATUSCACHE)) {
statuscache_close();
statuscache_done();
}
mboxlist_close();
mboxlist_done();
quotadb_close();
quotadb_done();
denydb_close();
denydb_done();
idle_done();
if (popd_in) {
prot_NONBLOCK(popd_in);
prot_fill(popd_in);
bytes_in = prot_bytes_in(popd_in);
prot_free(popd_in);
}
if (popd_out) {
prot_flush(popd_out);
bytes_out = prot_bytes_out(popd_out);
prot_free(popd_out);
}
if (config_auditlog)
syslog(LOG_NOTICE, "auditlog: traffic sessionid=<%s> bytes_in=<%d> bytes_out=<%d>",
session_id(), bytes_in, bytes_out);
#ifdef HAVE_SSL
tls_shutdown_serverengine();
#endif
cyrus_done();
if (config_iolog) {
read_io_count(io_count_stop);
syslog(LOG_INFO,
"POP session stats : I/O read : %d bytes : I/O write : %d bytes",
io_count_stop->io_read_count - io_count_start->io_read_count,
io_count_stop->io_write_count - io_count_start->io_write_count);
free (io_count_start);
free (io_count_stop);
}
exit(code);
}
EXPORTED void fatal(const char* s, int code)
{
static int recurse_code = 0;
if (recurse_code) {
/* We were called recursively. Just give up */
proc_cleanup();
exit(recurse_code);
}
recurse_code = code;
if (popd_out) {
prot_printf(popd_out, "-ERR [SYS/PERM] Fatal error: %s\r\n", s);
prot_flush(popd_out);
}
syslog(LOG_ERR, "Fatal error: %s", s);
shut_down(code);
}
#ifdef HAVE_KRB
/* translate IPv4 mapped IPv6 address to IPv4 address */
#ifdef IN6_IS_ADDR_V4MAPPED
static void sockaddr_unmapped(struct sockaddr *sa, socklen_t *len)
{
struct sockaddr_in6 *sin6;
struct sockaddr_in *sin4;
uint32_t addr;
int port;
if (sa->sa_family != AF_INET6)
return;
sin6 = (struct sockaddr_in6 *)sa;
if (!IN6_IS_ADDR_V4MAPPED((&sin6->sin6_addr)))
return;
sin4 = (struct sockaddr_in *)sa;
addr = *(uint32_t *)&sin6->sin6_addr.s6_addr[12];
port = sin6->sin6_port;
memset(sin4, 0, sizeof(struct sockaddr_in));
sin4->sin_addr.s_addr = addr;
sin4->sin_port = port;
sin4->sin_family = AF_INET;
#ifdef HAVE_SOCKADDR_SA_LEN
sin4->sin_len = sizeof(struct sockaddr_in);
#endif
*len = sizeof(struct sockaddr_in);
}
#else
static void sockaddr_unmapped(struct sockaddr *sa __attribute__((unused)),
socklen_t *len __attribute__((unused)))
{
return;
}
#endif
/*
* MIT's kludge of a kpop protocol
* Client does a krb_sendauth() first thing
*/
void kpop(void)
{
Key_schedule schedule;
KTEXT_ST ticket;
char instance[INST_SZ];
char version[9];
const char *srvtab;
int r;
socklen_t len;
if (!popd_haveaddr) {
fatal("Cannot get client's IP address", EC_OSERR);
}
srvtab = config_getstring(IMAPOPT_SRVTAB);
sockaddr_unmapped((struct sockaddr *)&popd_remoteaddr, &len);
if (popd_remoteaddr.ss_family != AF_INET) {
prot_printf(popd_out,
"-ERR [AUTH] Kerberos authentication failure: %s\r\n",
"not an IPv4 connection");
shut_down(0);
}
strcpy(instance, "*");
r = krb_recvauth(0L, 0, &ticket, "pop", instance,
(struct sockaddr_in *) &popd_remoteaddr,
(struct sockaddr_in *) NULL,
&kdata, (char*) srvtab, schedule, version);
if (r) {
prot_printf(popd_out, "-ERR [AUTH] Kerberos authentication failure: %s\r\n",
krb_err_txt[r]);
syslog(LOG_NOTICE,
"badlogin: %s kpop ? %s%s%s@%s %s",
popd_clienthost, kdata.pname,
kdata.pinst[0] ? "." : "", kdata.pinst,
kdata.prealm, krb_err_txt[r]);
shut_down(0);
}
r = krb_get_lrealm(klrealm,1);
if (r) {
prot_printf(popd_out, "-ERR [AUTH] Kerberos failure: %s\r\n",
krb_err_txt[r]);
syslog(LOG_NOTICE,
"badlogin: %s kpop ? %s%s%s@%s krb_get_lrealm: %s",
popd_clienthost, kdata.pname,
kdata.pinst[0] ? "." : "", kdata.pinst,
kdata.prealm, krb_err_txt[r]);
shut_down(0);
}
}
#else
void kpop(void)
{
usage();
}
#endif
static int expunge_deleted(void)
{
struct index_record record;
uint32_t msgno;
int r = 0;
int numexpunged = 0;
struct mboxevent *mboxevent;
mboxevent = mboxevent_new(EVENT_MESSAGE_EXPUNGE);
/* loop over all known messages looking for deletes */
for (msgno = 1; msgno <= popd_exists; msgno++) {
/* not deleted? skip */
if (!popd_map[msgno-1].deleted)
continue;
/* error reading? abort */
memset(&record, 0, sizeof(struct index_record));
record.recno = popd_map[msgno-1].recno;
r = mailbox_reload_index_record(popd_mailbox, &record);
if (r) break;
/* already expunged? skip */
if (record.system_flags & FLAG_EXPUNGED)
continue;
/* mark expunged */
record.system_flags |= FLAG_DELETED | FLAG_EXPUNGED;
numexpunged++;
/* store back to the mailbox */
r = mailbox_rewrite_index_record(popd_mailbox, &record);
if (r) break;
mboxevent_extract_record(mboxevent, popd_mailbox, &record);
}
if (r) {
syslog(LOG_ERR, "IOERROR: %s failed to expunge record %u uid %u, aborting",
popd_mailbox->name, msgno, popd_map[msgno-1].uid);
}
if (!r && (numexpunged > 0)) {
syslog(LOG_NOTICE, "Expunged %d messages from %s",
numexpunged, popd_mailbox->name);
}
/* send the MessageExpunge event notification */
mboxevent_extract_mailbox(mboxevent, popd_mailbox);
mboxevent_set_numunseen(mboxevent, popd_mailbox, -1);
mboxevent_set_access(mboxevent, NULL, NULL, popd_userid, NULL, 0);
mboxevent_notify(&mboxevent);
mboxevent_free(&mboxevent);
return r;
}
/*
* Top-level command loop parsing
*/
static void cmdloop(void)
{
char inputbuf[8192];
char *p;
char *arg;
uint32_t msgno = 0;
for (;;) {
signals_poll();
/* register process */
proc_register(config_ident, popd_clienthost, popd_userid, popd_mailbox ? popd_mailbox->name : NULL, NULL);
if (backend) {
/* create a pipe from client to backend */
bitpipe();
/* pipe has been closed */
telemetry_rusage( popd_userid );
return;
}
/* check for shutdown file */
if (shutdown_file(inputbuf, sizeof(inputbuf)) ||
(popd_userid &&
userdeny(popd_userid, config_ident, inputbuf, sizeof(inputbuf)))) {
for (p = inputbuf; *p == '['; p++); /* can't have [ be first char */
prot_printf(popd_out, "-ERR [SYS/TEMP] %s\r\n", p);
telemetry_rusage( popd_userid );
shut_down(0);
}
if (!prot_fgets(inputbuf, sizeof(inputbuf), popd_in)) {
telemetry_rusage( popd_userid );
shut_down(0);
}
if (popd_mailbox &&
config_getswitch(IMAPOPT_DISCONNECT_ON_VANISHED_MAILBOX)) {
if (popd_mailbox->i.options & OPT_MAILBOX_DELETED) {
/* Mailbox has been (re)moved */
syslog(LOG_WARNING,
"Maildrop %s has been (re)moved out from under client",
popd_mailbox->name);
prot_printf(popd_out,
"-ERR [SYS/TEMP] "
"Maildrop has been (re)moved\r\n");
shut_down(0);
}
}
p = inputbuf + strlen(inputbuf);
if (p > inputbuf && p[-1] == '\n') *--p = '\0';
if (p > inputbuf && p[-1] == '\r') *--p = '\0';
/* Parse into keword and argument */
for (p = inputbuf; *p && !Uisspace(*p); p++);
if (*p) {
*p++ = '\0';
arg = p;
if (strcasecmp(inputbuf, "pass") != 0) {
while (*arg && Uisspace(*arg)) {
arg++;
}
}
if (!*arg) {
if (strcasecmp(inputbuf, "auth") == 0) {
/* HACK for MS Outlook's incorrect use of the old-style
* SASL discovery method.
* Outlook uses "AUTH \r\n" instead if "AUTH\r\n"
*/
arg = 0;
}
else {
prot_printf(popd_out, "-ERR Syntax error\r\n");
continue;
}
}
}
else {
arg = 0;
}
lcase(inputbuf);
if (config_getswitch(IMAPOPT_CHATTY))
syslog(LOG_NOTICE, "command: %s", inputbuf);
/* register process */
proc_register(config_ident, popd_clienthost, popd_userid, popd_mailbox ? popd_mailbox->name : NULL, inputbuf);
if (!strcmp(inputbuf, "quit")) {
if (!arg) {
int pollpadding =config_getint(IMAPOPT_POPPOLLPADDING);
int minpollsec = config_getint(IMAPOPT_POPMINPOLL)*60;
/* check preconditions! */
if (!popd_mailbox)
goto done;
if (mailbox_lock_index(popd_mailbox, LOCK_EXCLUSIVE))
goto done;
/* mark dirty in case everything else misses it - we're updating
* at least the last login */
mailbox_index_dirty(popd_mailbox);
if ((minpollsec > 0) && (pollpadding > 1)) {
time_t mintime = popd_login_time - (minpollsec*(pollpadding));
if (popd_mailbox->i.pop3_last_login < mintime) {
popd_mailbox->i.pop3_last_login = mintime + minpollsec;
} else {
popd_mailbox->i.pop3_last_login += minpollsec;
}
} else {
popd_mailbox->i.pop3_last_login = popd_login_time;
}
/* look for deleted messages */
expunge_deleted();
/* update seen data */
update_seen();
/* unlock will commit changes */
mailbox_unlock_index(popd_mailbox, NULL);
done:
prot_printf(popd_out, "+OK\r\n");
telemetry_rusage( popd_userid );
return;
}
else
prot_printf(popd_out, "-ERR Unexpected extra argument\r\n");
}
else if (!strcmp(inputbuf, "capa")) {
if (arg) {
prot_printf(popd_out, "-ERR Unexpected extra argument\r\n");
} else {
cmd_capa();
}
}
else if (!popd_authstate) {
if (!strcmp(inputbuf, "user")) {
if (!arg) {
prot_printf(popd_out, "-ERR Missing argument\r\n");
}
else {
cmd_user(arg);
}
}
else if (!strcmp(inputbuf, "pass")) {
if (!arg) prot_printf(popd_out, "-ERR Missing argument\r\n");
else cmd_pass(arg);
}
else if (!strcmp(inputbuf, "apop") && *popd_apop_chal) {
if (!arg) prot_printf(popd_out, "-ERR Missing argument\r\n");
else cmd_apop(arg);
}
else if (!strcmp(inputbuf, "auth")) {
cmd_auth(arg);
}
else if (!strcmp(inputbuf, "stls") && tls_enabled()) {
if (arg) {
prot_printf(popd_out, "-ERR Unexpected extra argument\r\n");
} else {
/* XXX discard any input pipelined after STLS */
prot_flush(popd_in);
cmd_starttls(0);
}
}
else {
prot_printf(popd_out, "-ERR Unrecognized command\r\n");
}
}
else if (!strcmp(inputbuf, "stat")) {
unsigned nmsgs = 0, totsize = 0;
if (arg) {
prot_printf(popd_out, "-ERR Unexpected extra argument\r\n");
}
else {
for (msgno = 1; msgno <= popd_exists; msgno++) {
if (!popd_map[msgno-1].deleted) {
nmsgs++;
totsize += popd_map[msgno-1].size;
}
}
prot_printf(popd_out, "+OK %u %u\r\n", nmsgs, totsize);
}
}
else if (!strcmp(inputbuf, "list")) {
if (arg) {
msgno = parse_msgno(&arg);
if (msgno) {
prot_printf(popd_out, "+OK %u %u\r\n",
msgno, popd_map[msgno-1].size);
}
}
else {
prot_printf(popd_out, "+OK scan listing follows\r\n");
for (msgno = 1; msgno <= popd_exists; msgno++) {
if (!popd_map[msgno-1].deleted)
prot_printf(popd_out, "%u %u\r\n",
msgno, popd_map[msgno-1].size);
}
prot_printf(popd_out, ".\r\n");
}
}
else if (!strcmp(inputbuf, "retr")) {
if (!arg) prot_printf(popd_out, "-ERR Missing argument\r\n");
else {
msgno = parse_msgno(&arg);
if (msgno) {
blat(msgno, -1);
popd_map[msgno-1].seen = 1;
count_retr++;
}
}
}
else if (!strcmp(inputbuf, "dele")) {
if (!arg) prot_printf(popd_out, "-ERR Missing argument\r\n");
else if (config_popuseacl && !(popd_myrights & ACL_DELETEMSG)) {
prot_printf(popd_out, "-ERR [SYS/PERM] %s\r\n",
error_message(IMAP_PERMISSION_DENIED));
}
else {
msgno = parse_msgno(&arg);
if (msgno) {
popd_map[msgno-1].deleted = 1;
prot_printf(popd_out, "+OK message deleted\r\n");
count_dele++;
}
}
}
else if (!strcmp(inputbuf, "noop")) {
if (arg)
prot_printf(popd_out, "-ERR Unexpected extra argument\r\n");
else
prot_printf(popd_out, "+OK\r\n");
}
else if (!strcmp(inputbuf, "rset")) {
if (arg)
prot_printf(popd_out, "-ERR Unexpected extra argument\r\n");
else {
for (msgno = 1; msgno <= popd_exists; msgno++) {
popd_map[msgno-1].deleted = 0;
popd_map[msgno-1].seen = 0;
}
prot_printf(popd_out, "+OK\r\n");
}
}
else if (!strcmp(inputbuf, "top")) {
const char *p = arg;
uint32_t num;
uint32_t lines;
int r;
if (!p)
p = "";
while (*p && Uisspace(*p)) {
p++;
}
/* special case, can't just parse_msgno */
r = parseuint32(p, &p, &num);
if (r || !*p) {
prot_printf(popd_out, "-ERR Missing argument\r\n");
}
else {
msgno = num;
/* skip over whitespace */
while (*p && Uisspace(*p)) {
p++;
}
if (parseuint32(p, &p, &lines)) {
prot_printf(popd_out, "-ERR Invalid number of lines\r\n");
}
else if (*p) {
prot_printf(popd_out, "-ERR Unexpected extra argument\r\n");
}
else if (msg_exists_or_err(msgno)) {
blat(msgno, lines);
count_top++;
}
}
}
else if (!strcmp(inputbuf, "uidl")) {
if (arg) {
msgno = parse_msgno(&arg);
if (msgno) {
prot_printf(popd_out, "+OK ");
uidl_msg(msgno);
}
}
else {
prot_printf(popd_out, "+OK unique-id listing follows\r\n");
for (msgno = 1; msgno <= popd_exists; msgno++) {
if (!popd_map[msgno-1].deleted)
uidl_msg(msgno);
}
prot_printf(popd_out, ".\r\n");
}
}
else {
prot_printf(popd_out, "-ERR Unrecognized command\r\n");
}
}
}
unsigned parse_msgno(char **ptr)
{
const char *p;
uint32_t msgno;
int r;
p = *ptr;
/* skip leading whitespace */
while (*p && Uisspace(*p)) {
p++;
}
r = parseuint32(p, &p, &msgno);
if (r) {
prot_printf(popd_out, "-ERR Not a number\r\n");
*ptr = (char *)p;
}
else if (*p) {
prot_printf(popd_out, "-ERR Unexpected extra argument\r\n");
*ptr = (char *)p;
}
else if (msg_exists_or_err(msgno))
return msgno;
return 0;
}
int msg_exists_or_err(uint32_t msgno)
{
if (msgno < 1 || msgno > popd_exists ||
popd_map[msgno-1].deleted) {
prot_printf(popd_out, "-ERR No such message\r\n");
return 0;
}
return 1;
}
void uidl_msg(uint32_t msgno)
{
if (popd_mailbox->i.options & OPT_POP3_NEW_UIDL) {
switch (config_getenum(IMAPOPT_UIDL_FORMAT)) {
case IMAP_ENUM_UIDL_FORMAT_UIDONLY:
prot_printf(popd_out, "%u %u\r\n", msgno,
popd_map[msgno-1].uid);
break;
case IMAP_ENUM_UIDL_FORMAT_CYRUS:
prot_printf(popd_out, "%u %u.%u\r\n", msgno,
popd_mailbox->i.uidvalidity,
popd_map[msgno-1].uid);
break;
case IMAP_ENUM_UIDL_FORMAT_DOVECOT: {
char uidl[100];
snprintf(uidl, 100, "%08x%08x",
popd_map[msgno-1].uid,
popd_mailbox->i.uidvalidity);
prot_printf(popd_out, "%u %s\r\n", msgno, uidl);
}
break;
case IMAP_ENUM_UIDL_FORMAT_COURIER:
prot_printf(popd_out, "%u %u-%u\r\n", msgno,
popd_mailbox->i.uidvalidity,
popd_map[msgno-1].uid);
break;
default:
abort();
}
}
else {
prot_printf(popd_out, "%u %u\r\n", msgno,
popd_map[msgno-1].uid);
}
}
#ifdef HAVE_SSL
static void cmd_starttls(int pop3s)
{
int result;
int *layerp;
sasl_ssf_t ssf;
char *auth_id;
/* SASL and openssl have different ideas about whether ssf is signed */
layerp = (int *) &ssf;
if (popd_starttls_done == 1)
{
prot_printf(popd_out, "-ERR %s\r\n",
"Already successfully executed STLS");
return;
}
result=tls_init_serverengine("pop3",
5, /* depth to verify */
!pop3s, /* can client auth? */
NULL);
if (result == -1) {
syslog(LOG_ERR, "[pop3d] error initializing TLS");
if (pop3s == 0)
prot_printf(popd_out, "-ERR [SYS/PERM] %s\r\n", "Error initializing TLS");
else
shut_down(0);
return;
}
if (pop3s == 0)
{
prot_printf(popd_out, "+OK %s\r\n", "Begin TLS negotiation now");
/* must flush our buffers before starting tls */
prot_flush(popd_out);
}
result=tls_start_servertls(0, /* read */
1, /* write */
pop3s ? 180 : popd_timeout,
layerp,
&auth_id,
&tls_conn);
/* if error */
if (result==-1) {
if (pop3s == 0) {
prot_printf(popd_out, "-ERR [SYS/PERM] Starttls failed\r\n");
syslog(LOG_NOTICE, "[pop3d] STARTTLS failed: %s", popd_clienthost);
} else {
syslog(LOG_NOTICE, "pop3s failed: %s", popd_clienthost);
shut_down(0);
}
return;
}
/* tell SASL about the negotiated layer */
result = sasl_setprop(popd_saslconn, SASL_SSF_EXTERNAL, &ssf);
if (result == SASL_OK) {
saslprops.ssf = ssf;
result = sasl_setprop(popd_saslconn, SASL_AUTH_EXTERNAL, auth_id);
}
if (result != SASL_OK) {
syslog(LOG_NOTICE, "sasl_setprop() failed: cmd_starttls()");
if (pop3s == 0) {
fatal("sasl_setprop() failed: cmd_starttls()", EC_TEMPFAIL);
} else {
shut_down(0);
}
}
if(saslprops.authid) {
free(saslprops.authid);
saslprops.authid = NULL;
}
if(auth_id)
saslprops.authid = xstrdup(auth_id);
/* tell the prot layer about our new layers */
prot_settls(popd_in, tls_conn);
prot_settls(popd_out, tls_conn);
popd_starttls_done = 1;
popd_tls_required = 0;
}
#else
static void cmd_starttls(int pop3s __attribute__((unused)))
{
fatal("cmd_starttls() called, but no OpenSSL", EC_SOFTWARE);
}
#endif /* HAVE_SSL */
static void cmd_apop(char *response)
{
int sasl_result;
const void *canon_user;
int failedloginpause;
/* possibly disallow APOP */
if (popd_tls_required) {
prot_printf(popd_out,
"-ERR [AUTH] APOP command only available under a layer\r\n");
return;
}
assert(response != NULL);
if (popd_userid) {
prot_printf(popd_out, "-ERR [AUTH] Must give PASS command\r\n");
return;
}
sasl_result = sasl_checkapop(popd_saslconn,
popd_apop_chal,
strlen(popd_apop_chal),
response,
strlen(response));
/* failed authentication */
if (sasl_result != SASL_OK)
{
syslog(LOG_NOTICE, "badlogin: %s APOP (%s) %s",
popd_clienthost, popd_apop_chal,
sasl_errdetail(popd_saslconn));
failedloginpause = config_getint(IMAPOPT_FAILEDLOGINPAUSE);
if (failedloginpause != 0) {
sleep(failedloginpause);
}
/* Don't allow user probing */
if (sasl_result == SASL_NOUSER) sasl_result = SASL_BADAUTH;
prot_printf(popd_out, "-ERR [AUTH] authenticating: %s\r\n",
sasl_errstring(sasl_result, NULL, NULL));
free(popd_subfolder);
popd_subfolder = NULL;
return;
}
/* successful authentication */
/*
* get the userid from SASL --- already canonicalized from
* mysasl_proxy_policy()
*/
sasl_result = sasl_getprop(popd_saslconn, SASL_USERNAME, &canon_user);
if (sasl_result != SASL_OK) {
prot_printf(popd_out,
"-ERR [AUTH] weird SASL error %d getting SASL_USERNAME\r\n",
sasl_result);
free(popd_subfolder);
popd_subfolder = NULL;
return;
}
popd_userid = xstrdup((const char *) canon_user);
syslog(LOG_NOTICE, "login: %s %s%s APOP%s %s SESSIONID=<%s>", popd_clienthost,
popd_userid, popd_subfolder ? popd_subfolder : "",
popd_starttls_done ? "+TLS" : "", "User logged in", session_id());
popd_authstate = auth_newstate(popd_userid);
openinbox();
}
static void cmd_user(char *user)
{
char userbuf[MAX_MAILBOX_BUFFER], *dot, *domain;
unsigned userlen;
/* possibly disallow USER */
if (popd_tls_required ||
!(kflag || popd_starttls_done || (extprops_ssf > 1) ||
config_getswitch(IMAPOPT_ALLOWPLAINTEXT))) {
prot_printf(popd_out,
"-ERR [AUTH] USER command only available under a layer\r\n");
return;
}
if (popd_userid) {
prot_printf(popd_out, "-ERR [AUTH] Must give PASS command\r\n");
return;
}
if (popd_canon_user(popd_saslconn, NULL, user, 0,
SASL_CU_AUTHID | SASL_CU_AUTHZID,
NULL, userbuf, sizeof(userbuf), &userlen) ||
/* '.' isn't allowed if '.' is the hierarchy separator */
(popd_namespace.hier_sep == '.' && (dot = strchr(userbuf, '.')) &&
!(config_virtdomains && /* allow '.' in dom.ain */
(domain = strchr(userbuf, '@')) && (dot > domain))) ||
strlen(userbuf) + 6 >= MAX_MAILBOX_BUFFER) {
prot_printf(popd_out, "-ERR [AUTH] Invalid user\r\n");
syslog(LOG_NOTICE,
"badlogin: %s plaintext %s invalid user",
popd_clienthost, beautify_string(user));
}
else {
popd_userid = xstrdup(userbuf);
prot_printf(popd_out, "+OK Name is a valid mailbox\r\n");
}
}
static void cmd_pass(char *pass)
{
int failedloginpause;
if (!popd_userid) {
prot_printf(popd_out, "-ERR [AUTH] Must give USER command\r\n");
return;
}
#ifdef HAVE_KRB
if (kflag) {
if (strcmp(popd_userid, kdata.pname) != 0 ||
kdata.pinst[0] ||
strcmp(klrealm, kdata.prealm) != 0) {
prot_printf(popd_out, "-ERR [AUTH] Invalid login\r\n");
syslog(LOG_NOTICE,
"badlogin: %s kpop %s %s%s%s@%s access denied",
popd_clienthost, popd_userid,
kdata.pname, kdata.pinst[0] ? "." : "",
kdata.pinst, kdata.prealm);
return;
}
syslog(LOG_NOTICE, "login: %s %s%s KPOP%s %s SESSIONID=<%s>", popd_clienthost,
popd_userid, popd_subfolder ? popd_subfolder : "",
popd_starttls_done ? "+TLS" : "", "User logged in", session_id());
openinbox();
return;
}
#endif
if (!strcmp(popd_userid, "anonymous")) {
if (config_getswitch(IMAPOPT_ALLOWANONYMOUSLOGIN)) {
pass = beautify_string(pass);
if (strlen(pass) > 500) pass[500] = '\0';
syslog(LOG_NOTICE, "login: %s anonymous %s",
popd_clienthost, pass);
}
else {
syslog(LOG_NOTICE, "badlogin: %s anonymous login refused",
popd_clienthost);
prot_printf(popd_out, "-ERR [AUTH] Invalid login\r\n");
return;
}
}
else if (sasl_checkpass(popd_saslconn,
popd_userid,
strlen(popd_userid),
pass,
strlen(pass))!=SASL_OK) {
syslog(LOG_NOTICE, "badlogin: %s plaintext %s %s",
popd_clienthost, popd_userid, sasl_errdetail(popd_saslconn));
failedloginpause = config_getint(IMAPOPT_FAILEDLOGINPAUSE);
if (failedloginpause != 0) {
sleep(failedloginpause);
}
prot_printf(popd_out, "-ERR [AUTH] Invalid login\r\n");
free(popd_userid);
popd_userid = NULL;
free(popd_subfolder);
popd_subfolder = NULL;
return;
}
else {
/* successful authentication */
int sasl_result, plaintextloginpause;
const void *val;
free(popd_userid);
popd_userid = 0;
/* get the userid from SASL --- already canonicalized from
* mysasl_proxy_policy()
*/
sasl_result = sasl_getprop(popd_saslconn, SASL_USERNAME, &val);
if (sasl_result != SASL_OK) {
prot_printf(popd_out,
"-ERR [AUTH] weird SASL error %d getting SASL_USERNAME\r\n",
sasl_result);
if (popd_subfolder) {
free(popd_subfolder);
popd_subfolder = 0;
}
return;
}
popd_userid = xstrdup((const char *) val);
syslog(LOG_NOTICE, "login: %s %s%s plaintext%s %s SESSIONID=<%s>", popd_clienthost,
popd_userid, popd_subfolder ? popd_subfolder : "",
popd_starttls_done ? "+TLS" : "", "User logged in", session_id());
if ((!popd_starttls_done) &&
(plaintextloginpause = config_getint(IMAPOPT_PLAINTEXTLOGINPAUSE))
!= 0) {
sleep(plaintextloginpause);
}
}
/* popd_authstate may have been set as a side effect
* of sasl_checkpass() calling mysasl_proxy_policy */
if (popd_authstate)
auth_freestate(popd_authstate);
popd_authstate = auth_newstate(popd_userid);
openinbox();
}
/* Handle the POP3 Extension extension.
*/
static void cmd_capa(void)
{
int minpoll = config_getint(IMAPOPT_POPMINPOLL) * 60;
int expire = config_getint(IMAPOPT_POPEXPIRETIME);
int mechcount;
const char *mechlist;
prot_printf(popd_out, "+OK List of capabilities follows\r\n");
/* SASL special case: print SASL, then a list of supported capabilities */
if (!popd_tls_required && (!popd_authstate || saslprops.ssf) &&
sasl_listmech(popd_saslconn,
NULL, /* should be id string */
"SASL ", " ", "\r\n",
&mechlist,
NULL, &mechcount) == SASL_OK && mechcount > 0) {
prot_write(popd_out, mechlist, strlen(mechlist));
}
if (tls_enabled() && !popd_starttls_done && !popd_authstate) {
prot_printf(popd_out, "STLS\r\n");
}
if (expire < 0) {
prot_printf(popd_out, "EXPIRE NEVER\r\n");
} else {
prot_printf(popd_out, "EXPIRE %d\r\n", expire);
}
prot_printf(popd_out, "LOGIN-DELAY %d\r\n", minpoll);
prot_printf(popd_out, "TOP\r\n");
prot_printf(popd_out, "UIDL\r\n");
prot_printf(popd_out, "PIPELINING\r\n");
prot_printf(popd_out, "RESP-CODES\r\n");
prot_printf(popd_out, "AUTH-RESP-CODE\r\n");
if (!popd_tls_required && !popd_authstate &&
(kflag || popd_starttls_done || (extprops_ssf > 1)
|| config_getswitch(IMAPOPT_ALLOWPLAINTEXT))) {
prot_printf(popd_out, "USER\r\n");
}
if (popd_authstate || (config_serverinfo == IMAP_ENUM_SERVERINFO_ON)) {
prot_printf(popd_out,
"IMPLEMENTATION Cyrus POP3 %s\r\n",
cyrus_version());
}
prot_printf(popd_out, ".\r\n");
prot_flush(popd_out);
}
static void cmd_auth(char *arg)
{
int r, sasl_result;
char *authtype;
const void *val;
const char *canon_user;
int failedloginpause;
/* possibly disallow AUTH */
if (popd_tls_required) {
prot_printf(popd_out,
"-ERR [AUTH] AUTH command only available under a layer\r\n");
return;
}
/* if client didn't specify an argument we give them the list
*
* XXX This method of mechanism discovery is an undocumented feature
* that appeared in draft-myers-sasl-pop3 and is still used by
* some clients.
*/
if (!arg) {
const char *sasllist;
int mechnum;
prot_printf(popd_out, "+OK List of supported mechanisms follows\r\n");
/* CRLF separated, dot terminated */
if (sasl_listmech(popd_saslconn, NULL,
"", "\r\n", "\r\n",
&sasllist,
NULL, &mechnum) == SASL_OK) {
if (mechnum>0) {
prot_printf(popd_out,"%s",sasllist);
}
}
prot_printf(popd_out, ".\r\n");
return;
}
authtype = arg;
/* according to RFC 2449, since we advertise the "SASL" capability, we
* must accept an optional second argument as an initial client
* response (base64 encoded!).
*/
while (*arg && !Uisspace(*arg)) {
arg++;
}
if (Uisspace(*arg)) {
/* null terminate authtype, get argument */
*arg++ = '\0';
} else {
/* no optional client response */
arg = NULL;
}
r = saslserver(popd_saslconn, authtype, arg, "", "+ ", "",
popd_in, popd_out, &sasl_result, NULL);
if (r) {
const char *errorstring = NULL;
switch (r) {
case IMAP_SASL_CANCEL:
prot_printf(popd_out,
"-ERR [AUTH] Client canceled authentication\r\n");
break;
case IMAP_SASL_PROTERR:
errorstring = prot_error(popd_in);
prot_printf(popd_out,
"-ERR [AUTH] Error reading client response: %s\r\n",
errorstring ? errorstring : "");
break;
default:
/* failed authentication */
if (authtype) {
syslog(LOG_NOTICE, "badlogin: %s %s %s",
popd_clienthost, authtype,
sasl_errstring(sasl_result, NULL, NULL));
} else {
syslog(LOG_NOTICE, "badlogin: %s %s",
popd_clienthost, authtype);
}
failedloginpause = config_getint(IMAPOPT_FAILEDLOGINPAUSE);
if (failedloginpause != 0) {
sleep(failedloginpause);
}
/* Don't allow user probing */
if (sasl_result == SASL_NOUSER) sasl_result = SASL_BADAUTH;
prot_printf(popd_out, "-ERR [AUTH] authenticating: %s\r\n",
sasl_errstring(sasl_result, NULL, NULL));
}
free(popd_subfolder);
popd_subfolder = NULL;
reset_saslconn(&popd_saslconn);
return;
}
/* successful authentication */
/* get the userid from SASL --- already canonicalized from
* mysasl_proxy_policy()
*/
sasl_result = sasl_getprop(popd_saslconn, SASL_USERNAME, &val);
if (sasl_result != SASL_OK) {
prot_printf(popd_out,
"-ERR [AUTH] weird SASL error %d getting SASL_USERNAME\r\n",
sasl_result);
return;
}
canon_user = (const char *) val;
/* If we're proxying, the authzid may contain a subfolder,
so re-canonify it */
if (config_getswitch(IMAPOPT_POPSUBFOLDERS) && strchr(canon_user, '+')) {
char userbuf[MAX_MAILBOX_BUFFER];
unsigned userlen;
sasl_result = popd_canon_user(popd_saslconn, NULL, canon_user, 0,
SASL_CU_AUTHID | SASL_CU_AUTHZID,
NULL, userbuf, sizeof(userbuf), &userlen);
if (sasl_result != SASL_OK) {
prot_printf(popd_out,
"-ERR [AUTH] SASL canonification error %d\r\n",
sasl_result);
return;
}
popd_userid = xstrdup(userbuf);
} else {
popd_userid = xstrdup(canon_user);
}
syslog(LOG_NOTICE, "login: %s %s%s %s%s %s SESSIONID=<%s>", popd_clienthost,
popd_userid, popd_subfolder ? popd_subfolder : "",
authtype, popd_starttls_done ? "+TLS" : "", "User logged in", session_id());
sasl_getprop(popd_saslconn, SASL_SSF, &val);
saslprops.ssf = *((sasl_ssf_t *) val);
if (!openinbox()) {
prot_setsasl(popd_in, popd_saslconn);
prot_setsasl(popd_out, popd_saslconn);
}
else {
reset_saslconn(&popd_saslconn);
}
}
/*
* Complete the login process by opening and locking the user's inbox
*/
int openinbox(void)
{
int myrights = 0;
int r, log_level = LOG_ERR;
const char *statusline = NULL;
mbentry_t *mbentry = NULL;
struct statusdata sdata = STATUSDATA_INIT;
struct proc_limits limits;
struct mboxevent *mboxevent;
/* send a Login event notification */
if ((mboxevent = mboxevent_new(EVENT_LOGIN))) {
mboxevent_set_access(mboxevent, saslprops.iplocalport,
saslprops.ipremoteport, popd_userid, NULL, 0);
mboxevent_notify(&mboxevent);
mboxevent_free(&mboxevent);
}
const char *subfolder = popd_subfolder ? popd_subfolder + 1 : NULL;
mbname_t *mbname = mbname_from_extsub(subfolder, &popd_namespace, popd_userid);
r = mboxlist_lookup(mbname_intname(mbname), &mbentry, NULL);
#ifdef USE_AUTOCREATE
/* Try once again after autocreate_inbox */
if (r == IMAP_MAILBOX_NONEXISTENT) {
/* NOTE - if we have a subfolder, autocreateinbox should still create
* it if it's an autocreate folder - otherwise tough luck */
r = autocreate_user(&popd_namespace, popd_userid);
if (!r) r = mboxlist_lookup(mbname_intname(mbname), &mbentry, NULL);
}
#endif
if (!r && (config_popuseacl = config_getswitch(IMAPOPT_POPUSEACL)) &&
(!mbentry->acl ||
!((myrights = cyrus_acl_myrights(popd_authstate, mbentry->acl)) & ACL_READ))) {
r = (myrights & ACL_LOOKUP) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
}
else if (!r && (mbentry->mbtype & MBTYPE_DELETED)) {
r = IMAP_MAILBOX_NONEXISTENT;
}
else if (!r && (mbentry->mbtype & MBTYPE_RESERVE)) {
r = IMAP_MAILBOX_RESERVED;
}
else if (!r && (mbentry->mbtype & MBTYPE_MOVING)) {
r = IMAP_MAILBOX_MOVED;
}
if (r) {
sleep(3);
log_level = LOG_INFO;
syslog(log_level, "Unable to open maildrop %s: %s",
mbname_intname(mbname), error_message(r));
prot_printf(popd_out,
"-ERR [SYS/TEMP] Unable to open maildrop: %s\r\n",
error_message(r));
goto fail;
}
if (mbentry->mbtype & MBTYPE_REMOTE) {
/* remote mailbox */
char userid[MAX_MAILBOX_NAME];
/* Make a working copy of userid in case we need to alter it */
strlcpy(userid, popd_userid, sizeof(userid));
if (popd_subfolder) {
/* Add the subfolder back to the userid for proxying */
size_t n = strlen(popd_subfolder);
char *p = (config_virtdomains && (p = strchr(userid, '@'))) ?
p : userid + strlen(userid);
memmove(p+n, p, strlen(p)+1);
memcpy(p, popd_subfolder, n);
}
backend = backend_connect(NULL, mbentry->server, &pop3_protocol,
userid, NULL, &statusline, -1);
if (!backend) {
syslog(LOG_ERR, "couldn't authenticate to backend server");
prot_printf(popd_out, "-ERR%s",
statusline ? statusline :
" Authentication to backend server failed\r\n");
prot_flush(popd_out);
goto fail;
}
}
else if (config_getswitch(IMAPOPT_STATUSCACHE) &&
!(r = statuscache_lookup(mbname_intname(mbname), popd_userid, STATUS_MESSAGES, &sdata)) &&
!sdata.messages) {
/* local mailbox (empty) -- don't bother opening the mailbox */
syslog(LOG_INFO, "optimized mode for empty maildrop: %s", popd_userid);
}
else {
/* local mailbox */
uint32_t exists;
int minpoll;
popd_login_time = time(0);
r = mailbox_open_iwl(mbname_intname(mbname), &popd_mailbox);
if (r) {
sleep(3);
syslog(log_level, "Unable to open maildrop %s: %s",
mbname_intname(mbname), error_message(r));
prot_printf(popd_out,
"-ERR [SYS/PERM] Unable to open maildrop: %s\r\n",
error_message(r));
goto fail;
}
popd_myrights = cyrus_acl_myrights(popd_authstate, popd_mailbox->acl);
if (config_popuseacl && !(popd_myrights & ACL_READ)) {
r = (popd_myrights & ACL_LOOKUP) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
log_level = LOG_INFO;
}
if (r) {
mailbox_close(&popd_mailbox);
syslog(LOG_ERR, "Unable to lock maildrop %s: %s",
mbname_intname(mbname), error_message(r));
prot_printf(popd_out,
"-ERR [IN-USE] Unable to lock maildrop: %s\r\n",
error_message(r));
goto fail;
}
if ((minpoll = config_getint(IMAPOPT_POPMINPOLL)) &&
popd_mailbox->i.pop3_last_login + 60*minpoll > popd_login_time) {
prot_printf(popd_out,
"-ERR [LOGIN-DELAY] Logins must be at least %d minute%s apart\r\n",
minpoll, minpoll > 1 ? "s" : "");
mailbox_close(&popd_mailbox);
goto fail;
}
free(popd_map);
popd_map = (struct msg *)xmalloc(popd_mailbox->i.exists *
sizeof(struct msg));
config_popuseimapflags = config_getswitch(IMAPOPT_POPUSEIMAPFLAGS);
exists = 0;
unsigned iterflags = ITER_SKIP_EXPUNGED;
if (config_popuseimapflags) iterflags |= ITER_SKIP_DELETED;
struct mailbox_iter *iter = mailbox_iter_init(popd_mailbox, 0, iterflags);
const message_t *msg;
while ((msg = mailbox_iter_step(iter))) {
const struct index_record *record = msg_record(msg);
if (popd_mailbox->i.pop3_show_after &&
record->internaldate <= popd_mailbox->i.pop3_show_after) {
/* Ignore messages older than the "show after" date */
continue;
}
popd_map[exists].recno = record->recno;
popd_map[exists].uid = record->uid;
popd_map[exists].size = record->size;
popd_map[exists].deleted = 0;
popd_map[exists].seen = 0;
exists++;
if (exists >= popd_mailbox->i.exists)
break; /* we're full! */
}
mailbox_iter_done(&iter);
popd_exists = exists;
/* finished our initial read */
mailbox_unlock_index(popd_mailbox, NULL);
}
limits.procname = "pop3d";
limits.clienthost = popd_clienthost;
limits.userid = popd_userid;
if (proc_checklimits(&limits)) {
const char *sep = "";
prot_printf(popd_out,
"-ERR Too many open connections (");
if (limits.maxhost) {
prot_printf(popd_out, "%s%d of %d from %s", sep,
limits.host, limits.maxhost, popd_clienthost);
sep = ", ";
}
if (limits.maxuser) {
prot_printf(popd_out, "%s%d of %d for %s", sep,
limits.user, limits.maxuser, popd_userid);
}
prot_printf(popd_out, ")\r\n");
mailbox_close(&popd_mailbox);
goto fail;
}
/* Create telemetry log */
popd_logfd = telemetry_log(popd_userid, popd_in, popd_out, 0);
mboxlist_entry_free(&mbentry);
mbname_free(&mbname);
if (statusline)
prot_printf(popd_out, "+OK%s", statusline);
else
prot_printf(popd_out, "+OK Mailbox locked and ready SESSIONID=<%s>\r\n",
session_id());
prot_flush(popd_out);
return 0;
fail:
mboxlist_entry_free(&mbentry);
mbname_free(&mbname);
free(popd_userid);
popd_userid = 0;
if (popd_subfolder) {
free(popd_subfolder);
popd_subfolder = 0;
}
auth_freestate(popd_authstate);
popd_authstate = NULL;
return 1;
}
static int blat(int msgno, int lines)
{
FILE *msgfile;
char buf[4096];
const char *fname;
int thisline = -2;
struct index_record record;
/* XXX - map file */
memset(&record, 0, sizeof(struct index_record));
record.recno = popd_map[msgno-1].recno;
if (mailbox_reload_index_record(popd_mailbox, &record)) {
prot_printf(popd_out, "-ERR [SYS/PERM] Could not read index record\r\n");
return IMAP_IOERROR;
}
fname = mailbox_record_fname(popd_mailbox, &record);
msgfile = fopen(fname, "r");
if (!msgfile) {
prot_printf(popd_out, "-ERR [SYS/PERM] Could not read message file\r\n");
return IMAP_IOERROR;
}
prot_printf(popd_out, "+OK Message follows\r\n");
while (lines != thisline) {
if (!fgets(buf, sizeof(buf), msgfile)) break;
if (thisline < 0) {
if (buf[0] == '\r' && buf[1] == '\n') thisline = 0;
}
else thisline++;
if (buf[0] == '.')
(void)prot_putc('.', popd_out);
do {
prot_printf(popd_out, "%s", buf);
}
while (buf[strlen(buf)-1] != '\n' && fgets(buf, sizeof(buf), msgfile));
}
fclose(msgfile);
/* Protect against messages not ending in CRLF */
if (buf[strlen(buf)-1] != '\n') prot_printf(popd_out, "\r\n");
prot_printf(popd_out, ".\r\n");
/* Reset inactivity timer in case we spend a long time
pushing data to the client over a slow link. */
prot_resettimeout(popd_in);
return 0;
}
/* Reset the given sasl_conn_t to a sane state */
static int reset_saslconn(sasl_conn_t **conn)
{
int ret;
sasl_security_properties_t *secprops = NULL;
sasl_dispose(conn);
/* do initialization typical of service_main */
ret = sasl_server_new("pop", config_servername,
NULL, NULL, NULL,
NULL, 0, conn);
if(ret != SASL_OK) return ret;
if(saslprops.ipremoteport)
ret = sasl_setprop(*conn, SASL_IPREMOTEPORT,
saslprops.ipremoteport);
if(ret != SASL_OK) return ret;
if(saslprops.iplocalport)
ret = sasl_setprop(*conn, SASL_IPLOCALPORT,
saslprops.iplocalport);
if(ret != SASL_OK) return ret;
secprops = mysasl_secprops(0);
ret = sasl_setprop(*conn, SASL_SEC_PROPS, secprops);
if(ret != SASL_OK) return ret;
/* end of service_main initialization excepting SSF */
/* If we have TLS/SSL info, set it */
if(saslprops.ssf) {
ret = sasl_setprop(*conn, SASL_SSF_EXTERNAL, &saslprops.ssf);
} else {
ret = sasl_setprop(*conn, SASL_SSF_EXTERNAL, &extprops_ssf);
}
if(ret != SASL_OK) return ret;
if(saslprops.authid) {
ret = sasl_setprop(*conn, SASL_AUTH_EXTERNAL, saslprops.authid);
if(ret != SASL_OK) return ret;
}
/* End TLS/SSL Info */
return SASL_OK;
}
/* we've authenticated the client, we've connected to the backend.
now it's all up to them */
static void bitpipe(void)
{
struct protgroup *protin = protgroup_new(2);
int shutdown = 0;
char buf[4096];
protgroup_insert(protin, popd_in);
protgroup_insert(protin, backend->in);
do {
/* Flush any buffered output */
prot_flush(popd_out);
prot_flush(backend->out);
/* check for shutdown file */
if (shutdown_file(buf, sizeof(buf)) ||
userdeny(popd_userid, config_ident, buf, sizeof(buf))) {
shutdown = 1;
goto done;
}
} while (!proxy_check_input(protin, popd_in, popd_out,
backend->in, backend->out, 0));
done:
/* ok, we're done. */
protgroup_free(protin);
if (shutdown) {
char *p;
for (p = buf; *p == '['; p++); /* can't have [ be first char */
prot_printf(popd_out, "-ERR [SYS/TEMP] %s\r\n", p);
shut_down(0);
}
return;
}
/* Merge our read messages with the existing \Seen database */
static int update_seen(void)
{
unsigned msgno;
struct index_record record;
int r = 0;
if (!config_popuseimapflags)
return 0;
if (config_popuseacl && !(popd_myrights & ACL_SETSEEN))
return 0;
/* we know this mailbox must be owned by the user, because
* all POP mailboxes are */
for (msgno = 1; msgno <= popd_exists; msgno++) {
if (!popd_map[msgno-1].seen)
continue; /* don't even need to check */
memset(&record, 0, sizeof(struct index_record));
record.recno = popd_map[msgno-1].recno;
if (mailbox_reload_index_record(popd_mailbox, &record))
continue;
if (record.system_flags & FLAG_EXPUNGED)
continue; /* already expunged */
if (record.system_flags & FLAG_SEEN)
continue; /* already seen */
record.system_flags |= FLAG_SEEN;
r = mailbox_rewrite_index_record(popd_mailbox, &record);
if (r) break;
}
return r;
}