/* 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); }