/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999-2021 Free Software Foundation, Inc.
GNU Mailutils 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 3, or (at your option)
any later version.
GNU Mailutils 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 GNU Mailutils. If not, see . */
#include "pop3d.h"
#include "mailutils/pam.h"
#include "mailutils/cli.h"
#include "mailutils/pop3.h"
#include "mailutils/kwd.h"
#include "tcpwrap.h"
mu_mailbox_t mbox;
int state;
char *username;
char *md5shared;
mu_m_server_t server;
unsigned int idle_timeout;
int pop3d_transcript;
int debug_mode;
int pop3d_xlines;
char *apop_database_name = APOP_PASSFILE;
int apop_database_safety = MU_FILE_SAFETY_ALL;
uid_t apop_database_owner;
int apop_database_owner_set;
int initial_state = AUTHORIZATION;
/* Should all the messages be undeleted on startup */
int undelete_on_startup;
#ifdef ENABLE_LOGIN_DELAY
/* Minimum allowed delay between two successive logins */
time_t login_delay = 0;
char *login_stat_file = LOGIN_STAT_FILE;
#endif
unsigned expire = EXPIRE_NEVER; /* Expire messages after this number of days */
int expire_on_exit = 0; /* Delete expired messages on exit */
const char *program_version = "pop3d (" PACKAGE_STRING ")";
static void
set_foreground (struct mu_parseopt *po, struct mu_option *opt,
char const *arg)
{
mu_m_server_set_foreground (server, 1);
}
static void
set_inetd_mode (struct mu_parseopt *po, struct mu_option *opt,
char const *arg)
{
mu_m_server_set_mode (server, MODE_INTERACTIVE);
}
static void
set_daemon_mode (struct mu_parseopt *po, struct mu_option *opt,
char const *arg)
{
mu_m_server_set_mode (server, MODE_DAEMON);
if (arg)
{
size_t max_children;
char *errmsg;
int rc = mu_str_to_c (arg, mu_c_size, &max_children, &errmsg);
if (rc)
{
mu_parseopt_error (po, _("%s: bad argument"), arg);
exit (po->po_exit_error);
}
mu_m_server_set_max_children (server, max_children);
}
}
static struct mu_option pop3d_options[] = {
{ "foreground", 0, NULL, MU_OPTION_DEFAULT,
N_("remain in foreground"),
mu_c_bool, NULL, set_foreground },
{ "inetd", 'i', NULL, MU_OPTION_DEFAULT,
N_("run in inetd mode"),
mu_c_bool, NULL, set_inetd_mode },
{ "daemon", 'd', N_("NUMBER"), MU_OPTION_ARG_OPTIONAL,
N_("runs in daemon mode with a maximum of NUMBER children"),
mu_c_string, NULL, set_daemon_mode },
MU_OPTION_END
}, *options[] = { pop3d_options, NULL };
static int
cb_bulletin_source (void *data, mu_config_value_t *val)
{
if (mu_cfg_assert_value_type (val, MU_CFG_STRING))
return 1;
set_bulletin_source (val->v.string); /* FIXME: Error reporting? */
return 0;
}
static int
cb2_file_safety_checks (const char *name, void *data)
{
if (mu_file_safety_compose (data, name, MU_FILE_SAFETY_ALL))
mu_error (_("unknown keyword: %s"), name);
return 0;
}
static int
cb_apop_safety_checks (void *data, mu_config_value_t *arg)
{
return mu_cfg_string_value_cb (arg, cb2_file_safety_checks,
&apop_database_safety);
}
static int
cb_apop_database_owner (void *data, mu_config_value_t *val)
{
struct passwd *pw;
if (mu_cfg_assert_value_type (val, MU_CFG_STRING))
return 1;
pw = getpwnam (val->v.string);
if (!pw)
{
char *p;
unsigned long n;
n = strtoul (val->v.string, &p, 10);
if (*p)
{
mu_error (_("no such user: %s"), val->v.string);
return 1;
}
apop_database_owner = n;
}
else
apop_database_owner = pw->pw_uid;
apop_database_owner_set = 1;
return 0;
}
#ifdef ENABLE_DBM
static int
cb_bulletin_db (void *data, mu_config_value_t *val)
{
if (mu_cfg_assert_value_type (val, MU_CFG_STRING))
return 1;
set_bulletin_db (val->v.string); /* FIXME: Error reporting? */
return 0;
}
#endif
static int
cb_tls (void *data, mu_config_value_t *val)
{
int *res = data;
static struct mu_kwd tls_kwd[] = {
{ "no", tls_no },
{ "false", tls_no },
{ "off", tls_no },
{ "0", tls_no },
{ "ondemand", tls_ondemand },
{ "stls", tls_ondemand },
{ "required", tls_required },
{ "connection", tls_connection },
/* For compatibility with prior versions: */
{ "yes", tls_connection },
{ "true", tls_connection },
{ "on", tls_connection },
{ "1", tls_connection },
{ NULL }
};
if (mu_cfg_assert_value_type (val, MU_CFG_STRING))
return 1;
if (mu_kwd_xlat_name (tls_kwd, val->v.string, res))
mu_error (_("not a valid tls keyword: %s"), val->v.string);
return 0;
}
static struct mu_cfg_param pop3d_srv_param[] = {
{ "tls-mode", mu_cfg_callback,
NULL, mu_offsetof (struct pop3d_srv_config, tls_mode), cb_tls,
N_("Kind of TLS encryption to use for this server"),
/* TRANSLATORS: words to the right of : are keywords - do not translate */
N_("arg: false|true|ondemand|stls|required|connection") },
{ "tls", mu_cfg_section,
NULL, mu_offsetof (struct pop3d_srv_config, tls_conf) },
{ NULL }
};
static struct mu_cfg_param pop3d_cfg_param[] = {
{ "undelete", mu_c_bool, &undelete_on_startup, 0, NULL,
N_("On startup, clear deletion marks from all the messages.") },
{ "expire", mu_c_uint, &expire, 0, NULL,
N_("Automatically expire read messages after the given number of days."),
N_("days") },
{ "delete-expired", mu_c_bool, &expire_on_exit, 0, NULL,
N_("Delete expired messages upon closing the mailbox.") },
{ "scan-lines", mu_c_bool, &pop3d_xlines, 0, NULL,
N_("Output the number of lines in the message in its scan listing.") },
{ "apop-database-file", mu_c_string, &apop_database_name, 0, NULL,
N_("set APOP database file name or URL") },
{ "apop-database-owner", mu_cfg_callback, NULL, 0, cb_apop_database_owner,
N_("Name or UID of the APOP database owner"),
N_("arg: string") },
{ "apop-database-safety", mu_cfg_callback, NULL, 0, cb_apop_safety_checks,
N_("Configure safety checks for APOP database files. Argument is a list or "
"sequence of check names optionally prefixed with '+' to enable or "
"'-' to disable the corresponding check. Valid check names are:\n"
"\n"
" none disable all checks\n"
" all enable all checks\n"
" gwrfil forbid group writable files\n"
" awrfil forbid world writable files\n"
" grdfil forbid group readable files\n"
" ardfil forbid world writable files\n"
" linkwrdir forbid symbolic links in group or world writable directories\n"
" gwrdir forbid files in group writable directories\n"
" awrdir forbid files in world writable directories\n"),
N_("arg: list") },
{ "tls", mu_cfg_section, &global_tls_conf },
{ "tls-mode", mu_cfg_callback,
&global_tls_mode, 0, cb_tls,
N_("Kind of TLS encryption to use for the inetd server"
" and all server blocks that lack the tls-mode statement."),
/* TRANSLATORS: words to the right of : are keywords - do not translate */
N_("arg: false|true|ondemand|stls|required|connection") },
#ifdef ENABLE_LOGIN_DELAY
{ "login-delay", mu_c_time, &login_delay, 0, NULL,
N_("Set the minimal allowed delay between two successive logins.") },
{ "stat-file", mu_c_string, &login_stat_file, 0, NULL,
N_("Set the name of login statistics file (for login-delay).") },
#endif
{ "bulletin-source", mu_cfg_callback, NULL, 0, cb_bulletin_source,
N_("Get bulletins from the specified mailbox."),
N_("url: string") },
#ifdef ENABLE_DBM
{ "bulletin-db", mu_cfg_callback, NULL, 0, cb_bulletin_db,
N_("Set the bulletin database file name."),
N_("file: string") },
#endif
{ "output-buffer-size", mu_c_size, &pop3d_output_bufsize, 0, NULL,
N_("Size of the output buffer.") },
{ "mandatory-locking", mu_cfg_section },
{ ".server", mu_cfg_section, NULL, 0, NULL,
N_("Server configuration.") },
{ "transcript", mu_c_bool, &pop3d_transcript, 0, NULL,
N_("Set global transcript mode.") },
TCP_WRAPPERS_CONFIG
{ NULL }
};
static char *capa[] = {
"auth",
"debug",
"mailbox",
"locking",
"logging",
NULL
};
struct mu_cli_setup cli = {
.optv = options,
.cfg = pop3d_cfg_param,
.prog_doc = N_("GNU pop3d -- the POP3 daemon."),
.server = 1
};
int
pop3d_get_client_address (int fd, struct sockaddr_in *pcs)
{
mu_diag_output (MU_DIAG_INFO, _("incoming connection opened"));
/* log information on the connecting client. */
if (debug_mode)
{
mu_diag_output (MU_DIAG_INFO, _("started in debugging mode"));
return 1;
}
else
{
socklen_t len = sizeof *pcs;
if (getpeername (fd, (struct sockaddr*) pcs, &len) < 0)
{
mu_diag_output (MU_DIAG_ERROR,
_("cannot obtain IP address of client: %s"),
strerror (errno));
return 1;
}
}
return 0;
}
/* The main part of the daemon. This function reads input from the client and
executes the proper functions. Also handles the bulk of error reporting.
Arguments:
ifd -- input descriptor
ofd -- output descriptor
tls -- initiate encrypted connection */
int
pop3d_mainloop (int ifd, int ofd, struct pop3d_srv_config *cfg)
{
int status = OK;
char buffer[512];
static int sigtab[] = { SIGILL, SIGBUS, SIGFPE, SIGSEGV, SIGSTOP, SIGPIPE,
SIGABRT, SIGINT, SIGQUIT, SIGTERM, SIGHUP, SIGALRM };
struct pop3d_session session;
mu_set_signals (pop3d_child_signal, sigtab, MU_ARRAY_SIZE (sigtab));
pop3d_setio (ifd, ofd,
cfg->tls_mode == tls_connection ? &cfg->tls_conf : NULL);
if (cfg->tls_mode == tls_required)
initial_state = INITIAL;
state = cfg->tls_mode == tls_connection ? AUTHORIZATION : initial_state;
pop3d_session_init (&session);
session.tls_mode = cfg->tls_mode;
session.tls_conf = &cfg->tls_conf;
/* FIXME: state should also be in the session? */
/* Prepare the shared secret for APOP. */
{
char *local_hostname;
status = mu_get_host_name (&local_hostname);
if (status)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_get_host_name", NULL, status);
exit (EXIT_FAILURE);
}
md5shared = mu_alloc (strlen (local_hostname) + 51);
snprintf (md5shared, strlen (local_hostname) + 50, "<%u.%u@%s>", getpid (),
(unsigned)time (NULL), local_hostname);
free (local_hostname);
}
/* Lets boogie. */
pop3d_outf ("+OK POP3 Ready %s\n", md5shared);
while (state != UPDATE && state != ABORT)
{
char *buf;
char *arg, *cmd;
pop3d_command_handler_t handler;
pop3d_flush_output ();
status = OK;
buf = pop3d_readline (buffer, sizeof (buffer));
pop3d_parse_command (buf, &cmd, &arg);
if (state == TRANSACTION && !mu_mailbox_is_updated (mbox))
{
static mu_off_t mailbox_size;
mu_off_t newsize = 0;
mu_mailbox_get_size (mbox, &newsize);
/* Did we shrink? First time save the size. */
if (!mailbox_size)
mailbox_size = newsize;
else if (newsize < mailbox_size) /* FIXME: Should it be a != ? */
pop3d_abquit (ERR_MBOX_SYNC); /* Out of sync, Bail out. */
}
/* Refresh the Lock. */
manlock_touchlock (mbox);
if ((handler = pop3d_find_command (cmd)) != NULL)
status = handler (arg, &session);
else
status = ERR_BAD_CMD;
if (status != OK)
pop3d_outf ("-ERR %s\n", pop3d_error_string (status));
}
pop3d_session_free (&session);
pop3d_bye ();
return status;
}
int
pop3d_connection (int fd, struct sockaddr *sa, int salen,
struct mu_srv_config *pconf,
void *data)
{
struct pop3d_srv_config *cfg = (struct pop3d_srv_config *) pconf;
idle_timeout = cfg->m_cfg.timeout;
pop3d_transcript = cfg->m_cfg.transcript;
pop3d_mainloop (fd, fd, cfg);
return 0;
}
static void
pop3d_alloc_die ()
{
pop3d_abquit (ERR_NO_MEM);
}
#ifdef ENABLE_DBM
static void
set_dbm_safety ()
{
mu_url_t hints = mu_dbm_get_hint ();
const char *param[] = { "+all" };
mu_url_add_param (hints, 1, param);
}
#endif
int
main (int argc, char **argv)
{
struct group *gr;
int status = OK;
static int sigtab[] = { SIGILL, SIGBUS, SIGFPE, SIGSEGV, SIGSTOP, SIGPIPE };
/* Native Language Support */
MU_APP_INIT_NLS ();
MU_AUTH_REGISTER_ALL_MODULES();
/* Register the desired formats. */
mu_register_local_mbox_formats ();
mu_tcpwrapper_cfg_init ();
manlock_cfg_init ();
mu_acl_cfg_init ();
mu_tls_cfg_init ();
mu_m_server_create (&server, program_version);
mu_m_server_set_config_size (server, sizeof (struct pop3d_srv_config));
mu_m_server_set_conn (server, pop3d_connection);
mu_m_server_set_prefork (server, mu_tcp_wrapper_prefork);
mu_m_server_set_mode (server, MODE_INTERACTIVE);
mu_m_server_set_max_children (server, 20);
/* FIXME mu_m_server_set_pidfile (); */
mu_m_server_set_default_port (server, 110);
mu_m_server_set_timeout (server, 600);
mu_m_server_set_strexit (server, mu_strexit);
mu_m_server_cfg_init (server, pop3d_srv_param);
mu_alloc_die_hook = pop3d_alloc_die;
mu_log_syslog = 1;
manlock_mandatory_locking = 1;
#ifdef ENABLE_DBM
set_dbm_safety ();
#endif
mu_cli (argc, argv, &cli, capa, server, &argc, &argv);
if (argc)
{
mu_error (_("too many arguments"));
exit (EX_USAGE);
}
if (expire == 0)
expire_on_exit = 1;
#ifdef USE_LIBPAM
if (!mu_pam_service)
mu_pam_service = "gnu-pop3d";
#endif
if (mu_m_server_mode (server) == MODE_INTERACTIVE && isatty (0))
{
/* If input is a tty, switch to debug mode */
debug_mode = 1;
}
else
{
errno = 0;
gr = getgrnam ("mail");
if (gr == NULL)
{
if (errno == 0 || errno == ENOENT)
{
mu_error (_("%s: no such group"), "mail");
exit (EX_CONFIG);
}
else
{
mu_diag_funcall (MU_DIAG_ERROR, "getgrnam", "mail", errno);
exit (EX_OSERR);
}
}
if (setgid (gr->gr_gid) == -1)
{
mu_error (_("error setting mail group: %s"), mu_strerror (errno));
exit (EX_OSERR);
}
}
/* Set the signal handlers. */
mu_set_signals (pop3d_master_signal, sigtab, MU_ARRAY_SIZE (sigtab));
mu_stdstream_strerr_setup (mu_log_syslog ?
MU_STRERR_SYSLOG : MU_STRERR_STDERR);
umask (S_IROTH | S_IWOTH | S_IXOTH); /* 007 */
/* Check TLS environment, i.e. cert and key files */
mu_m_server_set_preflight (server, stls_preflight);
/* Actually run the daemon. */
if (mu_m_server_mode (server) == MODE_DAEMON)
{
mu_m_server_begin (server);
status = mu_m_server_run (server);
mu_m_server_end (server);
mu_m_server_destroy (&server);
}
else
{
struct pop3d_srv_config cfg;
memset (&cfg, 0, sizeof cfg);
idle_timeout = mu_m_server_timeout (server);
switch (stls_server_check (&cfg, ""))
{
case MU_TLS_CONFIG_OK:
if (mu_init_tls_libs ())
status = EX_OK;
else
{
mu_error (_("TLS is not configured, but requested in the "
"configuration"));
exit (EX_CONFIG);
}
break;
case MU_TLS_CONFIG_NULL:
break;
case MU_TLS_CONFIG_UNSAFE:
exit (EX_CONFIG);
default:
exit (EX_UNAVAILABLE);
}
/* Make sure we are in the root directory. */
chdir ("/");
status = pop3d_mainloop (MU_STDIN_FD, MU_STDOUT_FD, &cfg);
}
if (status)
mu_error (_("main loop status: %s"), mu_strerror (status));
/* Close the syslog connection and exit. */
closelog ();
return status ? EX_SOFTWARE : EX_OK;
}