/*
daemon.c
This file is part of GNU Anubis.
Copyright (C) 2001-2024 The Anubis Team.
GNU Anubis 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 of the License, or (at your
option) any later version.
GNU Anubis 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 Anubis. If not, see .
*/
#include "headers.h"
#include "extern.h"
/* TCP wrappers */
#ifdef USE_LIBWRAP
# include
int deny_severity = LOG_INFO;
int allow_severity = LOG_INFO;
#endif /* USE_LIBWRAP */
char *log_tag = "anubis";
int log_facility = LOG_MAIL;
/************
DAEMONIZE
*************/
void
daemonize (void)
{
signal (SIGHUP, SIG_IGN);
#ifdef HAVE_DAEMON
if (daemon (0, 0) == -1)
anubis_error (EXIT_FAILURE, errno, _("daemon() failed"));
#else
chdir ("/");
umask (0);
switch (fork ())
{
case -1: /* fork() failed */
anubis_error (EXIT_FAILURE, errno, _("Cannot fork."));
break;
case 0: /* child process */
break;
default: /* parent process */
quit (0);
}
if (setsid () == -1)
anubis_error (EXIT_FAILURE, errno, _("setsid() failed"));
close (0);
close (1);
close (2);
#endif /* HAVE_DAEMON */
topt &= ~T_FOREGROUND;
topt |= T_DAEMON;
openlog (log_tag, LOG_PID, log_facility);
info (NORMAL, _("%s daemon startup succeeded."), version);
write_pid_file ();
return;
}
char *
format_exit_status (char *buffer, size_t buflen, int status)
{
if (WIFEXITED(status))
{
if (WEXITSTATUS(status) == 0)
snprintf(buffer, buflen, _("Exited successfully"));
else
snprintf(buffer, buflen, _("Failed with status %d"),
WEXITSTATUS(status));
}
else if (WIFSIGNALED(status))
snprintf(buffer, buflen,
_("Terminated on signal %d"), WTERMSIG(status));
else if (WIFSTOPPED(status))
snprintf(buffer, buflen,
_("Stopped on signal %d"), WSTOPSIG(status));
#ifdef WCOREDUMP
else if (WCOREDUMP(status))
snprintf(buffer, buflen, _("Dumped core"));
#endif
else
snprintf(buffer, buflen, _("Terminated"));
return buffer;
}
static void
report_process_status (size_t count, pid_t pid, int status)
{
char buffer[LINEBUFFER];
count--;
info (VERBOSE,
ngettext
("Child [%lu] finished. %s. %d client left.",
"Child [%lu] finished. %s. %d clients left.",
count),
(unsigned long) pid,
format_exit_status (buffer, sizeof buffer, status), count);
}
static void
subprocess_report_status (size_t count, pid_t pid, int status)
{
char buffer[LINEBUFFER];
info (VERBOSE, _("Local program [%lu] finished. %s"),
(unsigned long) pid,
format_exit_status (buffer, sizeof buffer, status));
}
/************************************
If a service is not available,
then close a transmission channel.
*************************************/
void
service_unavailable (NET_STREAM * sd_client)
{
char buf[LINEBUFFER + 1];
snprintf (buf, LINEBUFFER,
"421 %s Service not available, closing transmission channel."
CRLF, (topt & T_LOCAL_MTA) ? "localhost" : session.mta);
swrite (SERVER, *sd_client, buf);
stream_close (*sd_client);
stream_destroy (sd_client);
return;
}
/*************************
Set an unprivileged user
(if possible).
**************************/
void
set_unprivileged_user (void)
{
if (topt & T_USER_NOTPRIVIL)
{
if (check_username (session.notprivileged)) {
anubis_changeowner (session.notprivileged);
assign_string (&session.clientname, session.notprivileged);
}
else
anubis_error (EXIT_FAILURE, 0,
_("WARNING: An unprivileged user cannot be resolved. Verify your settings!"));
}
else
{
if (check_username (DEFAULT_UNPRIVILEGED_USER)) {
anubis_changeowner (DEFAULT_UNPRIVILEGED_USER);
assign_string (&session.clientname, DEFAULT_UNPRIVILEGED_USER);
}
else
info (NORMAL,
_("WARNING: An unprivileged user has not been specified!"));
}
return;
}
int
anubis_child_main (struct sockaddr_in *addr)
{
int rc;
proclist_init ();
switch (anubis_mode)
{
case anubis_transparent:
rc = anubis_transparent_mode (addr);
break;
#ifdef WITH_GSASL
case anubis_authenticate:
rc = anubis_authenticate_mode (addr);
break;
#endif /* WITH_GSASL */
case anubis_proxy:
rc = anubis_proxy_mode (addr);
break;
default:
abort();
}
proclist_cleanup (subprocess_report_status);
net_close_stream (&remote_client);
return rc;
}
/**************
DAEMON loop
***************/
void
loop (int sd_bind)
{
struct sockaddr_in addr;
pid_t childpid = 0;
socklen_t addrlen;
#ifdef USE_LIBWRAP
struct request_info req;
#endif /* USE_LIBWRAP */
addrlen = sizeof (addr);
proclist_init ();
info (VERBOSE, _("GNU Anubis is running..."));
for (;;)
{
int fd;
size_t count;
fd = accept (sd_bind, (struct sockaddr *) &addr, &addrlen);
count = proclist_cleanup (report_process_status);
if (fd < 0)
{
if (errno == EINTR)
continue;
else
{
anubis_error (0, errno, _("accept() failed"));
continue;
}
}
/* Create the TCP stream */
net_create_stream (&remote_client, fd);
remote_server = NULL;
/*
Check the TCP wrappers settings.
*/
#ifdef USE_LIBWRAP
request_init (&req, RQ_DAEMON, "anubis", RQ_FILE, fd, 0);
fromhost (&req);
if (hosts_access (&req) == 0)
{
info (NORMAL,
_("TCP wrappers: connection from %s:%u rejected."),
inet_ntoa (addr.sin_addr), ntohs (addr.sin_port));
service_unavailable (&remote_client);
continue;
}
#endif /* USE_LIBWRAP */
/*
Read the system configuration file (SUPERVISOR).
*/
if (!(topt & T_NORC))
{
open_rcfile (CF_SUPERVISOR);
process_rcfile (CF_SUPERVISOR);
}
if (count >= MAXCLIENTS)
{
info (NORMAL,
_("Too many clients. Connection from %s:%u rejected."),
inet_ntoa (addr.sin_addr), ntohs (addr.sin_port));
service_unavailable (&remote_client);
}
else
{
info (NORMAL, _("Connection from %s:%u"),
inet_ntoa (addr.sin_addr), ntohs (addr.sin_port));
childpid = fork ();
if (childpid == -1)
anubis_error (0, errno, _("daemon: cannot fork"));
else if (childpid == 0)
{ /* a child process */
/* FIXME */
signal (SIGCHLD, SIG_IGN);
quit (anubis_child_main (&addr));
}
else /* master process */
proclist_register (childpid);
net_close_stream (&remote_client);
}
}
return;
}
/********************************************
Run an outgoing mail processor on standard
input and output as described in RFC 821.
*********************************************/
static int
_stdio_write (void *sd, const char *data, size_t size, size_t * nbytes)
{
int rc;
int fd = (int) (ptrdiff_t) sd;
if (fd == 0)
fd = 1;
rc = write (fd, data, size);
if (rc > 0)
{
*nbytes = rc;
return 0;
}
return errno;
}
static int
_stdio_read (void *sd, char *data, size_t size, size_t * nbytes)
{
int n;
int fd = (int) (ptrdiff_t) sd;
fd_set rds;
errno = 0;
FD_ZERO (&rds);
FD_SET (fd, &rds);
do
n = select (fd + 1, &rds, NULL, NULL, NULL);
while (n < 0 && errno == EINTR);
if (n > 0)
{
n = read (fd, data, size);
if (n >= 0)
*nbytes = n;
else
return errno;
}
return 0;
}
static const char *
_stdio_strerror (void *ignored_data, int rc)
{
return strerror (rc);
}
void
create_stdio_stream (NET_STREAM *s)
{
net_create_stream (s, 0);
stream_set_read (*s, _stdio_read);
stream_set_write (*s, _stdio_write);
stream_set_strerror (*s, _stdio_strerror);
}
void
stdinout (void)
{
topt &= ~T_SSL;
topt |= T_FOREGROUND;
topt |= T_SMTP_ERROR_CODES;
proclist_init ();
anubis_getlogin (&session.clientname);
auth_tunnel (); /* session.clientname = session.supervisor */
ASSERT_MTA_CONFIG ();
create_stdio_stream (&remote_client);
alarm (300);
if (topt & T_LOCAL_MTA)
remote_server = make_local_connection (session.execpath, session.execargs);
else
remote_server = make_remote_connection (session.mta, session.mta_port);
alarm (0);
if (remote_server == NULL)
{
service_unavailable (&remote_client);
free_mem ();
return;
}
stream_set_read (remote_server, _stdio_read);
stream_set_write (remote_server, _stdio_write);
stream_set_strerror (remote_server, _stdio_strerror);
smtp_session_transparent ();
proclist_cleanup (subprocess_report_status);
net_close_stream (&remote_server);
free_mem ();
return;
}
/* EOF */