/* This file is part of Mailfromd. Copyright (C) 2008 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 . */ #include "pies.h" int log_to_stderr; /* Use stderr for logging */ char *log_tag; /* override mu_log_tag */ mu_log_level_t debug_level; mu_debug_t pies_debug; struct pies_privs_data pies_user; int foreground; int command; char *pidfile = STATEDIR "/pies.pid"; char *ctlfile = STATEDIR "/pies.ctl"; char *statfile = STATEDIR "/pies.stat"; mode_t pies_umask = 0; unsigned long shutdown_timeout = 5; mu_acl_t pies_acl; /* Logging */ void log_setup (int want_stderr) { mu_debug_t debug; mu_diag_get_debug (&debug); if (log_tag) mu_log_tag = log_tag; if (!want_stderr) { openlog (MU_LOG_TAG (), LOG_PID, mu_log_facility); mu_debug_set_print (debug, mu_diag_syslog_printer, NULL); mu_debug_default_printer = mu_debug_syslog_printer; } else mu_debug_default_printer = mu_debug_stderr_printer; } static int stderr_closed_p() { int fd = dup (0); if (fd < 0) return 1; close (fd); return fd <= 2; } static int _cb_action (mu_debug_t debug, void *data, mu_config_value_t *arg) { enum return_action *pact = data; static struct mu_kwd actab[] = { { "disable", action_disable }, { "restart", action_restart }, { NULL } }; int res; if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug)) return 1; if (mu_kwd_xlat_name (actab, arg->v.string, &res)) { mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("unknown action code: %s"), arg); return 0; } *pact = res; return 0; } static int _cb_notify_addr (mu_debug_t debug, void *data, mu_config_value_t *arg) { mu_address_t *paddr = data; mu_address_t addr; int rc; if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug)) return 1; rc = mu_address_create (&addr, arg->v.string); if (rc) mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("%s: invalid e-mail address: %s"), arg->v.string, mu_strerror (rc)); if (*paddr) { mu_address_union (paddr, addr); mu_address_destroy (&addr); } else *paddr = addr; return 0; } struct mu_cfg_param return_code_cfg_param[] = { { "action", mu_cfg_callback, NULL, mu_offsetof (struct action, act), _cb_action, N_("Specifies action to take when a component finishes with this " "return code."), /* TRANSLATORS: disable and restart are keywords, do not translate them. */ N_("arg: {disable | restart}") }, { "notify", mu_cfg_callback, NULL, mu_offsetof (struct action, addr), _cb_notify_addr, N_("Notify this address when then component terminates."), N_("arg: email-list") }, { "message", mu_cfg_string, NULL, mu_offsetof (struct action, message), NULL, N_("Notification message text (with headers).") }, { NULL } }; static struct mu_kwd ex_kwtab[] = { #define S(s) { #s, s } S (EX_OK), S (EX_USAGE), S (EX_DATAERR), S (EX_NOINPUT), S (EX_NOUSER), S (EX_NOHOST), S (EX_UNAVAILABLE), S (EX_SOFTWARE), S (EX_OSERR), S (EX_OSFILE), S (EX_CANTCREAT), S (EX_IOERR), S (EX_TEMPFAIL), S (EX_PROTOCOL), S (EX_NOPERM), S (EX_CONFIG), { NULL } }; static struct action * create_action(struct component *comp, mu_debug_t debug, mu_config_value_t *val, int argc, const char *(*getarg) (mu_config_value_t *, int, mu_debug_t)) { int i; int *retv; int retc = 0; int allflag = 0; struct action *act; retv = xcalloc (argc, sizeof *retv); if (argc == 0 || (argc == 1 && strcmp (getarg(val, 0, debug), "*") == 0)) allflag = 1; else { for (i = 0; i < argc; i++) { int n; const char *arg = getarg(val, i, debug); if (isdigit (arg[0])) { char *p; n = strtoul (arg, &p, 0); if (*p) { mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("%s: not a number"), p); continue; } } else if (mu_kwd_xlat_name_ci (ex_kwtab, arg, &n)) { mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("%s: not a return code"), arg); continue; } if (n > MAX_RETURN_CODE) mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("%s: invalid return code"), arg); else { /* Alles in ordnung */ retv[retc++] = n; } } } if (retc == 0 && !allflag) { free (retv); return NULL; } act = xzalloc (sizeof *act); if (allflag) { for (i = 0; i <= MAX_RETURN_CODE; i++) if (comp->act[i] == NULL) comp->act[i] = act; } else for (i = 0; i < retc; i++) comp->act[retv[i]] = act; free (retv); return act; } const char * _get_string_arg (mu_config_value_t *val, int num, mu_debug_t debug) { if (num != 0) return NULL; return val->v.string; } const char * _get_array_arg (mu_config_value_t *val, int num, mu_debug_t debug) { if (num < val->v.arg.c) { if (mu_cfg_assert_value_type (&val->v.arg.v[num], MU_CFG_STRING, debug) == 0) return val->v.arg.v[num].v.string; } return NULL; } const char * _get_list_arg (mu_config_value_t *val, int num, mu_debug_t debug) { mu_config_value_t *elt; int rc = mu_list_get (val->v.list, num, (void**)&elt); if (rc) { mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("cannot get list item: %s"), mu_strerror (rc)); } else if (mu_cfg_assert_value_type (elt, MU_CFG_STRING, debug) == 0) return elt->v.string; return NULL; } static int return_code_section_parser (enum mu_cfg_section_stage stage, const mu_cfg_node_t *node, const char *section_label, void **section_data, void *call_data, mu_cfg_tree_t *tree) { struct component *comp = *section_data; size_t count; struct action *act; switch (stage) { case mu_cfg_section_start: switch (node->label->type) { case MU_CFG_STRING: act = create_action(comp, tree->debug, node->label, 1, _get_string_arg); break; case MU_CFG_ARRAY: act = create_action(comp, tree->debug, node->label, node->label->v.arg.c, _get_array_arg); break; case MU_CFG_LIST: mu_list_count (node->label->v.list, &count); act = create_action(comp, tree->debug, node->label, count, _get_list_arg); } if (!act) return 1; *section_data = act; break; case mu_cfg_section_end: break; } return 0; } void return_code_cfg_init () { struct mu_cfg_section *section; if (mu_create_canned_section ("return-code", §ion)) exit (EX_SOFTWARE); section->parser = return_code_section_parser; section->label = N_(""); mu_cfg_section_add_params (section, return_code_cfg_param); } static int _cb_group (mu_debug_t debug, void *data, mu_config_value_t *arg) { int argc, i; char **argv; mu_list_t *plist = data, list; int rc; if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug)) return 1; rc = mu_argcv_get_np (arg->v.string, strlen (arg->v.string), ",", NULL, 0, &argc, &argv, NULL); if (rc) { mu_cfg_format_error (debug, MU_DEBUG_ERROR, "mu_argcv_get: %s", mu_strerror (rc)); return 1; } if (*plist) list = *plist; else { mu_list_create (&list); *plist = list; } for (i = 0; i < argc; i++) { struct group *group = getgrnam (argv[i]); if (!group) { mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("Unknown group: %s"), argv[i]); continue; } mu_list_append (list, (void*)group->gr_gid); } mu_argcv_free (argc, argv); return 0; } static int _cb_command (mu_debug_t debug, void *data, mu_config_value_t *val) { int argc; char **argv, ***pargv = data; int rc; switch (val->type) { case MU_CFG_STRING: rc = mu_argcv_get (val->v.string, "", NULL, &argc, &argv); if (rc) { mu_cfg_format_error (debug, MU_DEBUG_ERROR, "mu_argcv_get: %s", mu_strerror (rc)); return 1; } break; case MU_CFG_ARRAY: argv = config_array_to_argv (val, debug); break; case MU_CFG_LIST: mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("unexpected list")); return 1; } *pargv = argv; return 0; } static int _cb_depend (mu_debug_t debug, void *data, mu_config_value_t *val) { char ***pargv = data; char **argv; switch (val->type) { case MU_CFG_ARRAY: argv = config_array_to_argv (val, debug); break; case MU_CFG_STRING: argv = xcalloc (2, sizeof (*argv)); argv[0] = xstrdup (val->v.string); argv[1] = NULL; break; case MU_CFG_LIST: mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("unexpected list")); return 1; } *pargv = argv; return 0; } static int _cb_umask (mu_debug_t debug, void *data, mu_config_value_t *arg) { mode_t *pmode = data; char *p; unsigned long n; if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug)) return 1; n = strtoul (arg->v.string, &p, 8); if (*p) { mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("Invalid octal number")); return 1; } *pmode = n; return 0; } static int _cb_env (mu_debug_t debug, void *data, mu_config_value_t *val) { int rc; int argc; char **argv; char ***penv = data; switch (val->type) { case MU_CFG_STRING: rc = mu_argcv_get_np (val->v.string, strlen (val->v.string), "", NULL, 0, &argc, &argv, NULL); if (rc) { mu_cfg_format_error (debug, MU_DEBUG_ERROR, "mu_argcv_get: %s", mu_strerror (rc)); return 1; } break; case MU_CFG_ARRAY: argv = config_array_to_argv (val, debug); break; case MU_CFG_LIST: mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("unexpected list")); return 1; } *penv = argv; return 0; } static int _cb_facility (mu_debug_t debug, void *data, mu_config_value_t *val) { if (mu_cfg_assert_value_type (val, MU_CFG_STRING, debug)) return 1; if (mu_string_to_syslog_facility (val->v.string, data)) { mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("Unknown syslog facility `%s'"), val->v.string); return 1; } return 0; } static int _cb_retr (mu_debug_t debug, void *data, mu_config_value_t *arg) { if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug)) return 1; if (mu_string_to_syslog_priority (arg->v.string, data)) { mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("Unknown syslog priority `%s'"), arg); return 1; } return 0; } static int _cb_url (mu_debug_t debug, void *data, mu_config_value_t *arg) { int rc; mu_url_t url; if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug)) return 1; rc = mu_url_create (&url, arg->v.string); if (rc) { mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("%s: cannot create URL: %s"), arg->v.string, mu_strerror (rc)); return 0; } rc = mu_url_parse (url); if (rc) { mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("%s: cannot parse URL: %s"), arg->v.string, mu_strerror (rc)); mu_url_destroy (&url); return 0; } *(mu_url_t*) data = url; return 0; } static struct mu_kwd modetab[] = { { "exec", pies_comp_exec }, { "wait", pies_comp_exec }, { "accept", pies_comp_accept }, { "inetd", pies_comp_inetd }, { "nostartaccept", pies_comp_inetd }, { "pass-fd", pies_comp_pass_fd }, { "pass", pies_comp_pass_fd }, { NULL } }; static int _cb_mode (mu_debug_t debug, void *data, mu_config_value_t *arg) { int res; if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug)) return 1; if (mu_kwd_xlat_name (modetab, arg->v.string, &res)) mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("%s: unrecognised mode"), arg->v.string); else *(enum pies_comp_mode *)data = res; return 0; } static int _cb_limits (mu_debug_t debug, void *data, mu_config_value_t *arg) { limits_record_t *plrec = data; char *p; if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug)) return 1; if (parse_limits (plrec, (char*) arg->v.string, &p)) mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("invalid limit string (near %s)"), p); return 0; } struct mu_cfg_param component_cfg_param[] = { { "mode", mu_cfg_callback, NULL, mu_offsetof (struct component, mode), _cb_mode, N_("Component execution mode."), /* TRANSLATORS: The words between '{' and '}' are keywords, do not translate them. */ N_("mode: {exec | wait | accept | inetd | nostartaccept | pass-fd | pass}") }, { "command", mu_cfg_callback, NULL, mu_offsetof (struct component, argv), _cb_command, N_("Command line.") }, { "depend", mu_cfg_callback, NULL, mu_offsetof (struct component, depend), _cb_depend, N_("List of dependenices (whitespace separated)"), N_("list") }, { "disable", mu_cfg_bool, NULL, mu_offsetof (struct component, disabled), NULL, N_("Disable this entry.") }, { "precious", mu_cfg_bool, NULL, mu_offsetof (struct component, precious), NULL, N_("Mark this entry as precious.") }, { "socket", mu_cfg_callback, NULL, mu_offsetof (struct component, socket_url), _cb_url, N_("Listen on the given url."), N_("url: string") }, { "pass-fd-socket", mu_cfg_string, NULL, mu_offsetof (struct component, pass_fd_socket), NULL, N_("Pass fd through this socket."), N_("name") }, { "acl", mu_cfg_section, NULL, mu_offsetof (struct component, acl), NULL, N_("Per-component access control list") }, { "remove-file", mu_cfg_string, NULL, mu_offsetof (struct component, rmfile), NULL, N_("Remove file before starting the component.") N_("file") }, { "facility", mu_cfg_callback, NULL, mu_offsetof (struct component, facility), _cb_facility, N_("Override default facility for this component."), N_("arg") }, { "stdout", mu_cfg_callback, NULL, mu_offsetof (struct component, retr[RETR_OUT]), _cb_retr, N_("Redirect program's standard output to the given syslog priority."), /* TRANSLATORS: The words between '{' and '}' are keywords, do not translate them. */ N_("prio: {emerg | alert | crit | err | warning | notice | info | debug}") }, { "stderr", mu_cfg_callback, NULL, mu_offsetof (struct component, retr[RETR_ERR]), _cb_retr, N_("Redirect program's standard error to the given syslog priority."), /* TRANSLATORS: The words between '{' and '}' are keywords, do not translate them. */ N_("prio: {emerg | alert | crit | err | warning | notice | info | debug}") }, { "user", mu_cfg_string, NULL, mu_offsetof (struct component, privs.user), NULL, N_("Run with this user privileges.") }, { "group", mu_cfg_callback, NULL, mu_offsetof (struct component, privs.groups), _cb_group, N_("Retain supplementary group.") }, { "umask", mu_cfg_callback, NULL, mu_offsetof (struct component, umask), _cb_umask, N_("Force this umask."), N_("arg: number") }, { "limits", mu_cfg_callback, NULL, mu_offsetof (struct component, limits), _cb_limits, N_("Set system limits") }, { "env", mu_cfg_callback, NULL, mu_offsetof (struct component, env), _cb_env, N_("Set program environment. Argument is a list of assignments " "separated by white space."), N_("arg: list") }, { "chdir", mu_cfg_string, NULL, mu_offsetof (struct component, dir), NULL, N_("Change to this directory before executing the component."), N_("dir") }, { "return-code", mu_cfg_section }, { NULL } }; static int component_section_parser (enum mu_cfg_section_stage stage, const mu_cfg_node_t *node, const char *section_label, void **section_data, void *call_data, mu_cfg_tree_t *tree) { struct component *comp; switch (stage) { case mu_cfg_section_start: if (node->label && mu_cfg_assert_value_type (node->label, MU_CFG_STRING, tree->debug)) return 1; comp = xzalloc (sizeof (*comp)); comp->facility = mu_log_facility; comp->retr[RETR_OUT] = comp->retr[RETR_ERR] = -1; comp->tag = node->label ? xstrdup (node->label->v.string) : NULL; *section_data = comp; break; case mu_cfg_section_end: comp = *(struct component **) section_data; if (comp->pass_fd_socket && comp->mode != pies_comp_pass_fd) mu_cfg_format_error (tree->debug, MU_DEBUG_ERROR, _("pass-fd-socket ignored: wrong mode")); switch (comp->mode) { case pies_comp_exec: if (comp->socket_url) mu_cfg_format_error (tree->debug, MU_DEBUG_ERROR, _("socket ignored: wrong mode")); break; case pies_comp_pass_fd: if (!comp->pass_fd_socket) mu_cfg_format_error (tree->debug, MU_DEBUG_ERROR, _("must supply pass-fd-socket in this mode")); else if (comp->pass_fd_socket[0] != '/') { if (comp->dir) { char *p; size_t len = strlen (comp->dir); while (len > 0 && comp->dir[len-1] == '/') len--; p = xmalloc (len + 1 + strlen (comp->pass_fd_socket) + 1); memcpy (p, comp->dir, len); p[len++] = '/'; strcpy (p + len, comp->pass_fd_socket); free (comp->pass_fd_socket); comp->pass_fd_socket = p; } else mu_cfg_format_error (tree->debug, MU_DEBUG_ERROR, _("pass-fd-socket must be an absolute " "file name or chdir must be specified")); } /* Fall through */ case pies_comp_accept: case pies_comp_inetd: if (!comp->socket_url) { mu_cfg_format_error (tree->debug, MU_DEBUG_ERROR, _("socket must be specified in this mode")); /* FIXME: Memory leak */ return 1; } } if (comp->mode != pies_comp_exec && comp->retr[RETR_OUT] != -1) { mu_cfg_format_error (tree->debug, MU_DEBUG_ERROR, _("stdout retranslation invalid in this mode")); comp->retr[RETR_OUT] = -1; } register_prog (comp); } return 0; } void component_cfg_init () { struct mu_cfg_section *section; if (mu_create_canned_section ("component", §ion)) exit (EX_SOFTWARE); section->parser = component_section_parser; section->label = N_(""); mu_cfg_section_add_params (section, component_cfg_param); } struct component default_component; static int _cb_debug (mu_debug_t debug, void *data, mu_config_value_t *arg) { int rc; if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug)) return 1; rc = mu_debug_level_from_string (arg->v.string, &debug_level, debug); if (rc) return 0; if (!pies_debug) mu_debug_create (&pies_debug, NULL); mu_debug_set_level (pies_debug, debug_level); return 0; } struct mu_cfg_param pies_cfg_param[] = { { "component", mu_cfg_section }, { "debug", mu_cfg_callback, NULL, 0, _cb_debug, N_("Set debug verbosity level.") }, { "pidfile", mu_cfg_string, &pidfile, 0, NULL, N_("Write PID to this file.") }, { "control-file", mu_cfg_string, &ctlfile, 0, NULL, N_("Set location of the control file.") }, { "stat-file", mu_cfg_string, &statfile, 0, NULL, N_("Set location of the statistics output file.") }, { "user", mu_cfg_string, &pies_user.user, 0, NULL, N_("Run with this user privileges.") }, { "group", mu_cfg_callback, &pies_user.groups, 0, _cb_group, N_("Retain supplementary group.") }, { "umask", mu_cfg_callback, &pies_umask, 0, _cb_umask, N_("Force this umask."), N_("arg: number") }, { "shutdown-timeout", mu_cfg_uint, &shutdown_timeout, 0, NULL, N_("Wait seconds for all components to shut down."), "n" }, { "return-code", mu_cfg_section, &default_component }, { "acl", mu_cfg_section, &pies_acl, 0, NULL, N_("Global access control list") }, { NULL } }; const char *program_version = "pies (" PACKAGE_STRING ")"; const char *package_bugreport = "<" PACKAGE_BUGREPORT ">"; static char doc[] = N_("pies -- process invocation and execution supervisor"); static char args_doc[] = ""; static const char *capa[] = { "common", "logging", "mailer", "debug", NULL }; enum { OPT_FOREGROUND=256, OPTION_LOG_TAG, OPT_SYSLOG, OPT_STDERR }; #define OPT_RESTART 'R' #define OPT_RELOAD 'r' #define OPT_STATUS 's' #define OPT_STOP 'S' static struct argp_option options[] = { #define GRP 0 { "foreground", OPT_FOREGROUND, 0, 0, N_("Remain in foreground."), GRP+1}, { "stderr", OPT_STDERR, NULL, 0, N_("Log to stderr"), }, { "syslog", OPT_SYSLOG, NULL, 0, N_("Log to syslog"), }, { "log-tag", OPTION_LOG_TAG, N_("STRING"), 0, N_("Set the identifier used in syslog messages to STRING"), GRP+1 }, { "debug", 'x', N_("LEVEL"), 0, N_("Set debug verbosity level."), GRP+1 }, #undef GRP #define GRP 10 { "restart-component", OPT_RESTART, NULL, 0, N_("Restart components named in the command line."), GRP+1 }, { "reload", OPT_RELOAD, NULL, 0, N_("Reload the running instance of pies."), GRP+1 }, { "hup", 0, NULL, OPTION_ALIAS }, { "status", OPT_STATUS, NULL, 0, N_("Display info about the running instance."), GRP+1 }, { "stop", OPT_STOP, NULL, 0, N_("Stop the running instance."), GRP+1 }, #undef GRP { NULL } }; static error_t parse_opt (int key, char *arg, struct argp_state *state) { static struct mu_argp_node_list lst; switch (key) { case OPT_FOREGROUND: foreground = 1; break; case OPT_RELOAD: case OPT_STATUS: case OPT_STOP: case OPT_RESTART: log_to_stderr = 1; command = key; break; case OPT_SYSLOG: log_to_stderr = 0; break; case OPT_STDERR: log_to_stderr = 1; break; case 'x': mu_argp_node_list_new (&lst, "debug", arg); break; case OPTION_LOG_TAG: log_tag = arg; break; case ARGP_KEY_INIT: mu_argp_node_list_init (&lst); break; case ARGP_KEY_FINI: mu_argp_node_list_finish (&lst, NULL, NULL); break; default: return ARGP_ERR_UNKNOWN; } return 0; } static struct argp argp = { options, parse_opt, args_doc, doc, NULL, NULL, NULL }; static void version (FILE *stream, struct argp_state *state) { mailfromd_version ("pies", stream); } void priv_setup (struct pies_privs_data *pr) { if (pr->user) { struct passwd *pw = getpwnam (pr->user); if (!pw) { mu_error (_("No such user: %s"), pr->user); exit (EX_CONFIG); } if (pw && switch_to_privs (pw->pw_uid, pw->pw_gid, pr->groups)) exit (EX_SOFTWARE); } } #define ACTION_CONT 0 #define ACTION_STOP 1 #define ACTION_RESTART 2 #define ACTION_COMPRELOAD 3 #define ACTION_DUMPSTATS 4 int action = ACTION_CONT; int children_cleanup = 0; int got_alarm = 0; RETSIGTYPE sig_handler (int sig) { switch (sig) { case SIGCHLD: children_cleanup = 1; break; case SIGTERM: case SIGINT: case SIGQUIT: action = ACTION_STOP; mu_diag_output (MU_DIAG_NOTICE, "received signal %d", sig); break; case SIGHUP: mu_diag_output (MU_DIAG_NOTICE, "received signal %d", sig); action = ACTION_RESTART; break; case SIGALRM: got_alarm = 1; break; case SIGUSR1: action = ACTION_COMPRELOAD; break; case SIGUSR2: action = ACTION_DUMPSTATS; break; } signal (sig, sig_handler); } void signal_setup (RETSIGTYPE (*sf)(int)) { signal (SIGCHLD, sf); signal (SIGTERM, sf); signal (SIGQUIT, sf); signal (SIGINT, sf); signal (SIGHUP, sf); signal (SIGALRM, sf); signal (SIGUSR1, sf); signal (SIGUSR2, sf); } pid_t pidfile_read (int must_exist) { int c; pid_t n = 0; FILE *fp = fopen (pidfile, "r"); if (!fp) { if (must_exist && errno != ENOENT) mu_diag_output (MU_DIAG_ERR, _("cannot open pid file `%s': %s"), pidfile, mu_strerror (errno)); return -1; } while ((c = fgetc (fp)) != EOF) { if (isdigit (c)) n = n * 10 + c - '0'; else if (c == '\n') break; else { mu_diag_output (MU_DIAG_ERR, _("unexpected character %#03o in pidfile `%s'"), c, pidfile); return -1; } } fclose (fp); if (kill (n, 0)) { mu_diag_output (MU_DIAG_ERR, _("cannot signal master process %lu: %s"), (unsigned long) n, mu_strerror (errno)); if (errno == EPERM) return n; /* be on the safe side */ return -1; } return n; } void stop_components () { FILE *fp; size_t size = 0; char *buf = NULL; mu_diag_output (MU_DIAG_INFO, _("stopping components")); fp = fopen (ctlfile, "r"); if (!fp) { mu_error (_("cannot open control file `%s': %s"), ctlfile, mu_strerror (errno)); return; } if (unlink (ctlfile)) { mu_error (_("cannot unlink control file `%s': %s"), ctlfile, mu_strerror (errno)); fclose (fp); return; } while (getline (&buf, &size, fp) > 0) { size_t len = strlen (buf); if (len == 0) continue; if (buf[len-1] == '\n') buf[len-1] = 0; progman_stop_component (buf); } free (buf); fclose (fp); } int request_restart_components (char **argv) { FILE *fp; pid_t pid = pidfile_read (1); if (pid == -1) return 1; fp = fopen (ctlfile, "w"); if (!fp) { mu_error (_("cannot open control file `%s': %s"), ctlfile, mu_strerror (errno)); return 1; } for (; *argv; argv++) fprintf (fp, "%s\n", *argv); fclose (fp); kill (pid, SIGUSR1); return 0; } int pies_reload () { pid_t pid = pidfile_read (1); if (pid == -1) { mu_diag_output (MU_DIAG_CRIT, _("pies is not running")); return 1; } mu_diag_output (MU_DIAG_INFO, _("reloading pies at PID %lu"), (unsigned long) pid); return kill (pid, SIGHUP) ? EX_SOFTWARE : 0; } int pies_status () { FILE *fp; pid_t pid = pidfile_read (0); int i; if (pid == -1) { mu_diag_output (MU_DIAG_INFO, _("pies is not running")); return EX_USAGE; } if (kill (pid, SIGUSR2)) { mu_diag_output (MU_DIAG_INFO, _("pies is not running, but a pidfile " "is found (pid %lu)"), (unsigned long) pid); return EX_USAGE; } mu_diag_output (MU_DIAG_INFO, _("pies is running; PID %lu"), (unsigned long) pid); for (i = 0; i < 4 && access (statfile, R_OK); i++) sleep (1); fp = fopen (statfile, "r"); if (!fp) mu_diag_output (MU_DIAG_ERR, _("cannot open statfile `%s': %s"), statfile, mu_strerror (errno)); else { char c; if (unlink (statfile)) mu_diag_output (MU_DIAG_ERR, _("cannot unlink statfile `%s': %s"), statfile, mu_strerror (errno)); while ((c = fgetc (fp)) != EOF) fputc (c, stdout); fclose (fp); } return 0; } int pies_stop () { pid_t pid = pidfile_read (1); if (pid == -1) { mu_diag_output (MU_DIAG_CRIT, _("pies is not running")); return EX_USAGE; } mu_diag_output (MU_DIAG_INFO, _("stopping pies at PID %lu"), (unsigned long) pid); return kill (pid, SIGTERM) ? EX_SOFTWARE : 0; } int main (int argc, char **argv) { int rc; int index; mf_init_nls (); if (!program_invocation_short_name) program_invocation_short_name = argv[0]; argp_program_version_hook = version; /* Set default logging */ log_setup (!stderr_closed_p ()); mu_acl_cfg_init (); return_code_cfg_init (); component_cfg_init (); mu_argp_init (program_version, package_bugreport); mu_register_all_mailer_formats (); rc = mu_app_init (&argp, capa, pies_cfg_param, argc, argv, 0, &index, NULL); if (rc) exit (EX_CONFIG); log_setup (log_to_stderr); if (argc != index && command != 'R') { mu_error ("extra command line arguments"); exit (EX_CONFIG); } switch (command) { case OPT_RESTART: priv_setup (&pies_user); if (pies_umask) umask (pies_umask); exit (request_restart_components (argv + index)); case OPT_RELOAD: exit (pies_reload ()); case OPT_STATUS: exit (pies_status ()); case OPT_STOP: exit (pies_stop ()); default: priv_setup (&pies_user); if (pies_umask) umask (pies_umask); } mu_diag_output (MU_DIAG_INFO, _("%s starting"), program_version); if (!foreground && daemon (0, 0) == -1) { mu_error (_("cannot become a daemon: %s"), mu_strerror (errno)); exit (EX_SOFTWARE); } rc = mu_daemon_create_pidfile (pidfile); if (rc) mu_error (_("Cannot create PID file `%s': %s"), pidfile, mu_strerror (rc)); if (argv[0][0] != '/') mu_diag_output (MU_DIAG_NOTICE, N_("not started as an absolute pathname; " "SIGHUP will not work")); signal_setup (sig_handler); progman_start (); while (action == ACTION_CONT) { if (!children_cleanup) pies_pause (); switch (action) { case ACTION_COMPRELOAD: stop_components (); progman_cleanup (0); progman_start (); action = ACTION_CONT; break; case ACTION_DUMPSTATS: progman_dump_stats (statfile); action = ACTION_CONT; break; } if (action == ACTION_CONT) { if (children_cleanup) { children_cleanup = 0; progman_cleanup (0); } if (got_alarm) { got_alarm = 0; progman_wake_sleeping (); } } } progman_stop (); if (action == ACTION_RESTART && argv[0][0] == '/') { int i; for (i = getmaxfd (); i > 0; i--) close (i); mu_daemon_remove_pidfile (); signal_setup (SIG_DFL); execv (argv[0], argv); } mu_diag_output (MU_DIAG_INFO, _("%s terminated"), program_version); exit (EX_OK); } void xalloc_die () { mu_error ("not enough memory"); abort (); } /* Local Variables: c-file-style: "gnu" End: */ /* EOF */