/* This file is part of Mailfromd.
Copyright (C) 2005-2020 Sergey Poznyakoff
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 3, 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, see . */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "libmf.h"
#include "srvman.h"
/* Check whether pidfile NAME exists and if so, whether its PID is still
active. Exit if it is. */
void
mf_server_check_pidfile(const char *name)
{
unsigned long pid;
FILE *fp = fopen(name, "r");
if (!fp) {
if (errno == ENOENT)
return;
mu_error(_("cannot open pidfile `%s': %s"),
name, mu_strerror(errno));
exit(EX_TEMPFAIL);
}
if (fscanf(fp, "%lu", &pid) != 1) {
mu_error(_("cannot get pid from pidfile `%s'"), name);
} else {
if (kill(pid, 0) == 0) {
mu_error(_("%s appears to run with pid %lu. If it does not, remove `%s' and retry."),
mu_program_name,
pid,
name);
exit(EX_USAGE);
}
}
fclose(fp);
if (unlink(name)) {
mu_error(_("cannot unlink pidfile `%s': %s"),
name, mu_strerror(errno));
exit(EX_USAGE);
}
}
void
mf_server_write_pidfile(const char *name)
{
FILE *fp = fopen(name, "w");
if (!fp) {
mu_error(_("cannot create pidfile `%s': %s"),
name, mu_strerror(errno));
exit(EX_TEMPFAIL);
}
fprintf(fp, "%lu\n", (unsigned long) getpid());
fclose(fp);
}
static int x_argc; /* A copy of argc and ... */
static char **x_argv; /* ... argv */
static int restart; /* Set to 1 if restart is requested */
char *mf_server_lint_option;
void
mf_server_save_cmdline(int argc, char **argv)
{
int i;
if (argc == 0) {
/* Clear saved command line */
for (i = 0; i < x_argc; i++)
free(x_argv[i]);
free(x_argv);
x_argc = 0;
x_argv = NULL;
} else if (argv[0][0] != '/') {
/* Do not save if argv[0] is not a full file name.
An appropriate error message will be issued later,
if necessary. */
return;
}
x_argc = argc;
x_argv = mu_calloc(x_argc + 1, sizeof x_argv[0]);
for (i = 0; i < x_argc; i++)
x_argv[i] = mu_strdup(argv[i]);
x_argv[i] = NULL;
}
static RETSIGTYPE
sig_stop(int sig)
{
mfd_srvman_stop();
}
static RETSIGTYPE
sig_restart(int sig)
{
restart = 1;
}
/* Umask for creating new files */
mode_t mf_server_umask = 0017;
char *mf_server_user = DEFAULT_USER; /* Switch to this user privileges after
startup */
struct mf_gid_list *mf_server_retain_groups;
/* List of group IDs to retain when
switching to user privileges. */
void
priv_setup()
{
if (getuid() == 0) {
if (!mf_server_user) {
mu_error(_("when running as root, --user option is mandatory."));
exit(EX_USAGE);
} else {
struct passwd *pw = getpwnam(mf_server_user);
if (!pw) {
mu_error(_("no such user: %s"),
mf_server_user);
exit(EX_SOFTWARE);
}
if (pw && switch_to_privs(pw->pw_uid, pw->pw_gid,
mf_server_retain_groups))
exit(EX_SOFTWARE);
}
}
}
static int
run_lint()
{
pid_t pid;
int p[2];
if (!mf_server_lint_option)
return 0;
if (pipe(p)) {
mu_error(_("pipe: %s"), mu_strerror(errno));
return -1;
}
pid = fork();
if (pid == -1) {
mu_error(_("fork: %s"), mu_strerror(errno));
close(p[0]);
close(p[1]);
return -1;
}
if (pid) {
mu_stream_t instream;
int rc, status;
close(p[1]);
rc = mu_stdio_stream_create(&instream, p[0], MU_STREAM_READ);
if (rc) {
mu_error(_("cannot open input stream: %s"),
mu_strerror (rc));
return -1;
}
rc = mu_stream_copy(mu_strerr, instream, 0, NULL);
mu_stream_close(instream);
mu_stream_destroy(&instream);
waitpid(pid, &status, 0);
return !(WIFEXITED(status) && WEXITSTATUS(status) == 0);
}
/* Child process */
/* Prepare arguments */
x_argv = mu_realloc(x_argv, (x_argc + 3) * sizeof(x_argv[0]));
x_argv[x_argc] = "--stderr";
x_argv[x_argc + 1] = mf_server_lint_option;
x_argv[x_argc + 2] = NULL;
close(2);
dup2(p[1], 2);
close(p[0]);
close_fds_above(2);
execv(x_argv[0], x_argv);
abort();
}
static int
server_idle_hook(void *data)
{
if (restart) {
if (run_lint()) {
mu_error(_("syntax check failed; restart cancelled"));
restart = 0;
} else
return 1;
}
return 0;
}
void
mf_server_start(const char *program, const char *dir, const char *pidfile,
int flags)
{
priv_setup();
if (dir && chdir(dir)) {
mu_error(_("cannot change to %s: %s"), dir,
strerror(errno));
exit(EX_OSERR);
}
if (!(flags & MF_SERVER_FOREGROUND))
mf_server_check_pidfile(pidfile);
signal(SIGTERM, sig_stop);
signal(SIGQUIT, sig_stop);
if (x_argc == 0) {
mu_diag_output(MU_DIAG_WARNING,
_("program started without full file name"));
flags |= MF_SERVER_NORESTART;
}
if (flags & MF_SERVER_NORESTART) {
mu_diag_output(MU_DIAG_WARNING,
_("restart (SIGHUP) will not work"));
signal(SIGHUP, sig_stop);
} else
signal(SIGHUP, sig_restart);
signal(SIGINT, sig_stop);
if (!(flags & MF_SERVER_FOREGROUND)) {
if (daemon(!!dir, 0) == -1) {
mu_error(_("cannot become a daemon: %s"),
mu_strerror(errno));
exit(EX_OSERR);
}
umask(mf_server_umask);
mf_server_write_pidfile(pidfile);
} else {
umask(mf_server_umask);
}
mf_server_log_setup();
mu_error(_("%s started"), program);
srvman_param.idle_hook = server_idle_hook;
if (mfd_srvman_open())
exit(EX_UNAVAILABLE);
mfd_srvman_run(NULL);
mfd_srvman_shutdown();
mfd_srvman_free();
unlink(pidfile);
if (restart) {
char *s;
mu_error(_("%s restarting"), program);
mu_daemon_remove_pidfile();
mu_onexit_run();
logger_close();
if (logger_flags(LOGF_STDERR))
close_fds_above(2);
else
close_fds_above(1);
execv(x_argv[0], x_argv);
mf_server_log_setup();
mu_error(_("cannot restart: %s"), mu_strerror(errno));
if (mu_argcv_string(x_argc, x_argv, &s) == 0)
mu_error(_("command line was: %s"), s);
} else
mu_error(_("%s terminating"), program);
exit(0);
}