/* This file is part of Pies. Copyright (C) 2008, 2009 Sergey Poznyakoff Pies 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. Pies 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 Pies. If not, see . */ #include "pies.h" #include #include #include "meta1lex.h" char *conffile = SYSCONFDIR "/pies.conf"; int preprocess_only; /* Preprocess config, do nothing more */ int lint_mode; /* Test configuration syntax and exit */ int log_to_stderr; /* Use stderr for logging */ int log_facility = LOG_USER; char *log_tag; struct pies_privs pies_privs; int foreground; int command; char *pidfile = LOCALSTATEDIR "/pies.pid"; char *ctlfile = LOCALSTATEDIR "/pies.ctl"; char *statfile = LOCALSTATEDIR "/pies.stat"; mode_t pies_umask = 0; unsigned long shutdown_timeout = 5; pies_acl_t pies_acl; limits_record_t pies_limits; int force_option; char *mailer_program = "/usr/sbin/sendmail"; char *mailer_command_line = "/usr/sbin/sendmail -oi -t"; int mailer_argc; char **mailer_argv; struct obstack pp_stk; struct quoting_options *pp_qopt; static void add_pp_option (const char *opt, const char *arg) { if (!DEFAULT_PREPROCESSOR) { logmsg (LOG_ERR, _("no preprocessor configured")); exit (EX_CONFIG); } obstack_1grow (&pp_stk, ' '); obstack_grow (&pp_stk, opt, strlen (opt)); if (arg) { char *qarg; size_t qlen; if (!pp_qopt) { pp_qopt = clone_quoting_options (NULL); set_quoting_style (pp_qopt, shell_quoting_style); } qarg = quotearg_alloc_mem (arg, strlen (arg), &qlen, pp_qopt); obstack_grow (&pp_stk, qarg, qlen); free (qarg); } } /* Logging */ void log_setup (int want_stderr) { if (!want_stderr) openlog (log_tag, LOG_PID, log_facility); } static int stderr_closed_p () { int fd = dup (0); if (fd < 0) return 1; close (fd); return fd <= 2; } #define GRECS_VALUE_IS_EMPTY(val) \ (!(val) || ((val)->type == GRECS_TYPE_STRING && !(val)->v.string)) int assert_grecs_value_type (grecs_locus_t * locus, const grecs_value_t * value, int type) { if (GRECS_VALUE_IS_EMPTY (value)) { grecs_error (locus, 0, _("expected %s"), grecs_data_type_string (type)); return 1; } if (value->type != type) { grecs_error (locus, 0, _("expected %s, but found %s"), grecs_data_type_string (type), grecs_data_type_string (value->type)); return 1; } return 0; } int assert_scalar_stmt (grecs_locus_t * locus, enum grecs_callback_command cmd) { if (cmd != grecs_callback_set_value) { grecs_error (locus, 0, _("Unexpected block statement")); return 1; } return 0; } static int _cb_action (enum grecs_callback_command cmd, grecs_locus_t * locus, void *varptr, grecs_value_t * value, void *cb_data) { enum return_action *pact = varptr; static struct tokendef actab[] = { {"disable", action_disable}, {"restart", action_restart}, {NULL} }; int res; if (assert_scalar_stmt (locus, cmd) || assert_grecs_value_type (locus, value, GRECS_TYPE_STRING)) return 1; if (strtotok (actab, value->v.string, &res)) { grecs_error (locus, 0, _("unknown action code: %s"), value->v.string); return 1; } *pact = res; return 0; } struct grecs_keyword return_code_keywords[] = { {"action", /* TRANSLATORS: disable and restart are keywords, do not translate them. */ N_("arg: {disable | restart}"), N_("Specifies action to take when a component finishes with this " "return code."), grecs_type_string, NULL, offsetof (struct component, act_temp.act), _cb_action, }, {"notify", N_("arg: emails"), N_("Notify this address when then component terminates."), grecs_type_string, NULL, offsetof (struct component, act_temp.addr) }, {"message", NULL, N_("Notification message text (with headers)."), grecs_type_string, NULL, offsetof (struct component, act_temp.message), NULL}, {"exec", NULL, N_("Execute this command."), grecs_type_string, NULL, offsetof (struct component, act_temp.command), NULL, }, {NULL} }; #define S(s) { #s, s } static struct tokendef ex_tokendef[] = { 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 tokendef sig_tokendef[] = { S (SIGHUP), S (SIGINT), S (SIGQUIT), S (SIGILL), S (SIGTRAP), S (SIGABRT), S (SIGIOT), S (SIGBUS), S (SIGFPE), S (SIGKILL), S (SIGUSR1), S (SIGSEGV), S (SIGUSR2), S (SIGPIPE), S (SIGALRM), S (SIGTERM), #ifdef SIGSTKFLT S (SIGSTKFLT), #endif S (SIGCHLD), S (SIGCONT), S (SIGSTOP), S (SIGTSTP), S (SIGTTIN), S (SIGTTOU), #ifdef SIGURG S (SIGURG), #endif #ifdef SIGXCPU S (SIGXCPU), #endif #ifdef SIGXFSZ S (SIGXFSZ), #endif #ifdef SIGVTALRM S (SIGVTALRM), #endif #ifdef SIGPROF S (SIGPROF), #endif #ifdef SIGWINCH S (SIGWINCH), #endif #ifdef SIGPOLL S (SIGPOLL), #endif #ifdef SIGIO S (SIGIO), #endif #ifdef SIGPWR S (SIGPWR), #endif #ifdef SIGSYS S (SIGSYS), #endif {NULL} }; #undef S static struct action * create_action (struct component *comp, grecs_locus_t * locus, grecs_value_t * val, int argc, const char *(*getarg) (grecs_value_t *, int, grecs_locus_t *)) { int i; unsigned *retv; int retc = 0; int allflag = 0; struct action *act; retv = xcalloc (argc, sizeof *retv); if (argc == 0 || (argc == 1 && strcmp (getarg (val, 0, locus), "*") == 0)) allflag = 1; else { for (i = 0; i < argc; i++) { unsigned n; const char *arg = getarg (val, i, locus); size_t len = strlen (arg); if (isdigit (arg[0])) { char *p; n = strtoul (arg, &p, 0); if (*p) { grecs_error (locus, 0, _("%s: not a number"), p); continue; } } else if (len > 3 && memcmp (arg, "SIG", 3) == 0) { if (arg[4] == '+') { char *p; n = strtoul (arg + 4, &p, 0); if (*p) { grecs_error (locus, 0, _("%s: not a number"), p); continue; } } else if (strtotok_ci (sig_tokendef, arg, &n)) { grecs_error (locus, 0, _("%s: not a signal code"), arg); continue; } n |= STATUS_SIG_BIT; } else if (strtotok_ci (ex_tokendef, arg, &n)) { grecs_error (locus, 0, _("%s: not a return code"), arg); continue; } /* Alles in ordnung */ retv[retc++] = n; } } if (retc == 0 && !allflag) { free (retv); return NULL; } act = xzalloc (sizeof *act); if (!allflag) { act->nstat = retc; act->status = retv; } if (comp->act_tail) comp->act_tail->next = act; else comp->act_head = act; comp->act_tail = act; return act; } const char * _get_string_arg (grecs_value_t * val, int num, grecs_locus_t * locus) { if (num != 0) return NULL; return val->v.string; } const char * _get_array_arg (grecs_value_t * val, int num, grecs_locus_t * locus) { if (num < val->v.arg.c) { if (assert_grecs_value_type (locus, &val->v.arg.v[num], GRECS_TYPE_STRING) == 0) return val->v.arg.v[num].v.string; } return NULL; } const char * _get_list_arg (grecs_value_t * val, int num, grecs_locus_t * locus) { grecs_value_t *elt = (grecs_value_t *) gl_list_get_at (val->v.list, num); if (!elt) { grecs_error (locus, 0, _("cannot get list item")); } else if (assert_grecs_value_type (locus, elt, GRECS_TYPE_STRING) == 0) return elt->v.string; return NULL; } static int return_code_section_parser (enum grecs_callback_command cmd, grecs_locus_t * locus, void *varptr, grecs_value_t * value, void *cb_data) { struct component *comp = varptr; size_t count; struct action *act; switch (cmd) { case grecs_callback_section_begin: if (GRECS_VALUE_IS_EMPTY (value)) { grecs_error (locus, 0, _("missing tag")); return 1; } switch (value->type) { case GRECS_TYPE_STRING: act = create_action (comp, locus, value, 1, _get_string_arg); break; case GRECS_TYPE_ARRAY: act = create_action (comp, locus, value, value->v.arg.c, _get_array_arg); break; case GRECS_TYPE_LIST: count = gl_list_size (value->v.list); act = create_action (comp, locus, value, count, _get_list_arg); } *(struct component **) cb_data = comp; if (!act) return 1; memset (&comp->act_temp, 0, sizeof (comp->act_temp)); break; case grecs_callback_section_end: act = comp->act_tail; act->act = comp->act_temp.act; act->addr = comp->act_temp.addr; act->message = comp->act_temp.message; act->command = comp->act_temp.command; break; case grecs_callback_set_value: grecs_error (locus, 0, _("invalid use of block statement")); } return 0; } static char ** config_array_to_argv (grecs_value_t *val, grecs_locus_t *locus) { int i, j; int argc; char **argv; argc = val->v.arg.c; argv = xcalloc (argc + 1, sizeof (argv[0])); for (i = j = 0; i < argc; i++) { if (assert_grecs_value_type (locus, &val->v.arg.v[i], GRECS_TYPE_STRING) == 0) argv[j++] = xstrdup (val->v.arg.v[i].v.string); } argv[j] = NULL; return argv; } static int _cb_command (enum grecs_callback_command cmd, grecs_locus_t * locus, void *varptr, grecs_value_t * value, void *cb_data) { int argc; char **argv, ***pargv = varptr; struct wordsplit ws; switch (value->type) { case GRECS_TYPE_STRING: if (wordsplit (value->v.string, &ws, WRDSF_DEFFLAGS)) { grecs_error (locus, 0, "wordsplit: %s", strerror (errno)); return 1; } argc = ws.ws_wordc; argv = ws.ws_wordv; ws.ws_wordv = NULL; break; case GRECS_TYPE_ARRAY: argv = config_array_to_argv (value, locus); break; case GRECS_TYPE_LIST: grecs_error (locus, 0, _("unexpected list")); return 1; } *pargv = argv; return 0; } static int _cb_umask (enum grecs_callback_command cmd, grecs_locus_t * locus, void *varptr, grecs_value_t * value, void *cb_data) { mode_t *pmode = varptr; char *p; unsigned long n; if (assert_scalar_stmt (locus, cmd) || assert_grecs_value_type (locus, value, GRECS_TYPE_STRING)) return 1; n = strtoul (value->v.string, &p, 8); if (*p) { grecs_error (locus, 0, _("invalid octal number")); return 1; } *pmode = n; return 0; } static int _cb_env (enum grecs_callback_command cmd, grecs_locus_t * locus, void *varptr, grecs_value_t * value, void *cb_data) { int argc; char **argv; char ***penv = varptr; struct wordsplit ws; switch (value->type) { case GRECS_TYPE_STRING: if (wordsplit (value->v.string, &ws, WRDSF_DEFFLAGS)) { grecs_error (locus, 0, "wordsplit: %s", strerror (errno)); return 1; } argc = ws.ws_wordc; argv = ws.ws_wordv; ws.ws_wordv = NULL; break; case GRECS_TYPE_ARRAY: argv = config_array_to_argv (value, locus); break; case GRECS_TYPE_LIST: grecs_error (locus, 0, _("unexpected list")); return 1; } *penv = argv; return 0; } int string_to_syslog_priority (const char *key, int *pres) { static struct tokendef tokdef_prio[] = { {"EMERG", LOG_EMERG}, {"ALERT", LOG_ALERT}, {"CRIT", LOG_CRIT}, {"ERR", LOG_ERR}, {"WARNING", LOG_WARNING}, {"NOTICE", LOG_NOTICE}, {"INFO", LOG_INFO}, {"DEBUG", LOG_DEBUG}, {NULL} }; return strtotok_ci (tokdef_prio, key, pres); } int string_to_syslog_facility (const char *key, int *pres) { static struct tokendef tokdef_fac[] = { {"auth", LOG_AUTH}, {"authpriv", LOG_AUTHPRIV}, {"cron", LOG_CRON}, {"daemon", LOG_DAEMON}, {"ftp", LOG_FTP}, {"kern", LOG_KERN}, {"lpr", LOG_LPR}, {"mail", LOG_MAIL}, {"news", LOG_NEWS}, {"syslog", LOG_SYSLOG}, {"user", LOG_USER}, {"uucp", LOG_UUCP}, {"local0", LOG_LOCAL0}, {"local1", LOG_LOCAL1}, {"local2", LOG_LOCAL2}, {"local3", LOG_LOCAL3}, {"local4", LOG_LOCAL4}, {"local5", LOG_LOCAL5}, {"local6", LOG_LOCAL6}, {"local7", LOG_LOCAL7}, {NULL} }; return strtotok_ci (tokdef_fac, key, pres); } static int cb_syslog_facility (enum grecs_callback_command cmd, grecs_locus_t * locus, void *varptr, grecs_value_t * value, void *cb_data) { const char *str; if (assert_grecs_value_type (locus, value, GRECS_TYPE_STRING)) return 1; str = value->v.string; if (c_isdigit (str[0])) { char *p; int n = strtoul (str, &p, 10); if (*p) grecs_error (locus, 0, _("expected facility number or symbolic name")); else *(int *) varptr = n; } else if (string_to_syslog_facility (str, varptr)) grecs_error (locus, 0, _("unknown syslog facility `%s'"), str); return 0; } static int _cb_redir (enum grecs_callback_command cmd, grecs_locus_t * locus, void *varptr, grecs_value_t * value, void *cb_data) { struct redirector *rp = varptr; static struct tokendef redirtab[] = { {"null", redir_null}, {"syslog", redir_syslog}, {"file", redir_file}, {NULL} }; int res; switch (value->type) { case GRECS_TYPE_STRING: if (strcmp (value->v.string, "null") == 0) { rp->type = redir_null; break; } rp->type = redir_syslog; if (string_to_syslog_priority (value->v.string, &rp->v.prio)) { grecs_error (locus, 0, _("unknown syslog priority `%s'"), value->v.string); return 0; } break; case GRECS_TYPE_ARRAY: if (assert_grecs_value_type (locus, &value->v.arg.v[0], GRECS_TYPE_STRING)) return 0; if (strtotok (redirtab, value->v.arg.v[0].v.string, &res)) grecs_error (locus, 0, _("%s: unrecognised redirector type"), value->v.arg.v[0].v.string); else { if (res != redir_null) { if (value->v.arg.c != 2) { grecs_error (locus, 0, _("wrong number of arguments")); return 0; } if (assert_grecs_value_type (locus, &value->v.arg.v[1], GRECS_TYPE_STRING)) return 0; switch (res) { case redir_null: break; case redir_syslog: if (string_to_syslog_priority (value->v.arg.v[1].v.string, &rp->v.prio)) { grecs_error (locus, 0, _("unknown syslog priority `%s'"), value->v.arg.v[1].v.string); return 0; } break; case redir_file: rp->v.file = xstrdup (value->v.arg.v[1].v.string); break; } } rp->type = res; } break; default: grecs_error (locus, 0, _("unexpected list")); } return 0; } static int _cb_url (enum grecs_callback_command cmd, grecs_locus_t * locus, void *varptr, grecs_value_t * value, void *cb_data) { struct pies_url *url; if (assert_scalar_stmt (locus, cmd) || assert_grecs_value_type (locus, value, GRECS_TYPE_STRING)) return 1; if (pies_url_create (&url, value->v.string)) { grecs_error (locus, 0, _("%s: cannot create URL: %s"), value->v.string, strerror (errno)); return 0; } *(struct pies_url **) varptr = url; return 0; } static struct tokendef 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 (enum grecs_callback_command cmd, grecs_locus_t * locus, void *varptr, grecs_value_t * value, void *cb_data) { int res; if (assert_scalar_stmt (locus, cmd) || assert_grecs_value_type (locus, value, GRECS_TYPE_STRING)) return 1; if (strtotok (modetab, value->v.string, &res)) grecs_error (locus, 0, _("%s: unrecognised mode"), value->v.string); else *(enum pies_comp_mode *) varptr = res; return 0; } static int _cb_limits (enum grecs_callback_command cmd, grecs_locus_t * locus, void *varptr, grecs_value_t * value, void *cb_data) { limits_record_t *plrec = varptr; char *p; if (assert_scalar_stmt (locus, cmd) || assert_grecs_value_type (locus, value, GRECS_TYPE_STRING)) return 1; if (parse_limits (plrec, (char *) value->v.string, &p)) grecs_error (locus, 0, _("invalid limit string (near %s)"), p); return 0; } struct grecs_keyword component_keywords[] = { {"mode", /* TRANSLATORS: The words between '{' and '}' are keywords, do not translate them. */ N_ ("mode: {exec | wait | accept | inetd | nostartaccept | pass-fd | pass}"), N_("Component execution mode."), grecs_type_string, NULL, offsetof (struct component, mode), _cb_mode, }, {"program", NULL, N_("Full name of the program."), grecs_type_string, NULL, offsetof (struct component, program), NULL, }, {"command", NULL, N_("Command line."), grecs_type_string, NULL, offsetof (struct component, argv), _cb_command, }, {"prerequisites", N_("list"), N_("List of prerequisites."), grecs_type_string | GRECS_LIST, NULL, offsetof (struct component, prereq), NULL, }, {"dependents", N_("list"), N_("List of components for which this one is a prerequisite."), grecs_type_string | GRECS_LIST, NULL, offsetof (struct component, depend), NULL, }, {"disable", NULL, N_("Disable this entry."), grecs_type_bool, NULL, offsetof (struct component, disabled), NULL, }, {"pass-fd-timeout", NULL, N_("Time to wait for pass-fd socket to become available."), grecs_type_uint, NULL, offsetof (struct component, pass_fd_timeout), NULL, }, {"precious", NULL, N_("Mark this entry as precious."), grecs_type_bool, NULL, offsetof (struct component, precious), NULL, }, {"socket", N_("url: string"), N_("Listen on the given url."), grecs_type_string, NULL, offsetof (struct component, socket_url), _cb_url, }, {"pass-fd-socket", N_("name"), N_("Pass fd through this socket."), grecs_type_string, NULL, offsetof (struct component, pass_fd_socket), NULL, }, {"acl", N_("name: string"), N_("Set ACL."), grecs_type_section, NULL, offsetof (struct component, acl), acl_section_parser, NULL, acl_keywords}, {"remove-file", N_("file"), N_("Remove file before starting the component."), grecs_type_string, NULL, offsetof (struct component, rmfile), NULL, }, {"facility", N_("arg"), N_("Override default syslog facility for this component."), grecs_type_string, NULL, offsetof (struct component, facility), cb_syslog_facility, }, {"stdout", /* TRANSLATORS: file and syslog are keywords. Do not translate them. */ N_("type: {file | syslog}> "), N_("Define what to do when the component finishes."), grecs_type_section, NULL, 0, return_code_section_parser, NULL, return_code_keywords}, {NULL} }; struct grecs_keyword * find_component_keyword (const char *ident) { struct grecs_keyword *kwp; for (kwp = component_keywords; kwp->ident; kwp++) if (strcmp (kwp->ident, ident) == 0) return kwp; return NULL; } static char * make_full_name (const char *dir, const char *file) { char *p; size_t len = strlen (dir); while (len > 0 && dir[len - 1] == '/') len--; p = xmalloc (len + 1 + strlen (file) + 1); memcpy (p, dir, len); p[len++] = '/'; strcpy (p + len, file); return p; } static int component_verify (struct component *comp, grecs_locus_t * locus) { int header = 0; int i; #define COMPERR(fmt, arg) \ do \ { \ if (!header) \ { \ grecs_error (locus, 0, _("in component %s:"), comp->tag); \ header = 1; \ } \ grecs_error (locus, 0, fmt, arg); \ } \ while (0) if (!comp->argv) COMPERR ("%s", _("missing command line")); if (comp->pass_fd_socket && comp->mode != pies_comp_pass_fd) COMPERR ("%s", _("pass-fd-socket ignored: wrong mode")); switch (comp->mode) { case pies_comp_exec: if (comp->socket_url) COMPERR ("%s", _("socket ignored: wrong mode")); break; case pies_comp_pass_fd: if (!comp->pass_fd_socket) COMPERR ("%s", _("must supply pass-fd-socket in this mode")); else if (comp->pass_fd_socket[0] != '/') { if (comp->dir) { char *p = make_full_name (comp->dir, comp->pass_fd_socket); //free (comp->pass_fd_socket); comp->pass_fd_socket = p; } else COMPERR ("%s", _("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) { COMPERR ("%s", _("socket must be specified in this mode")); /* FIXME: Memory leak */ return 1; } } if (comp->mode != pies_comp_exec && comp->redir[RETR_OUT].type != redir_null) { COMPERR ("%s", _("stdout translation invalid in this mode")); comp->redir[RETR_OUT].type = redir_null; } for (i = RETR_OUT; i <= RETR_ERR; i++) { if (comp->redir[i].type == redir_file && comp->redir[i].v.file[0] != '/') { if (comp->dir) { char *p = make_full_name (comp->dir, comp->redir[i].v.file); free (comp->redir[i].v.file); comp->redir[i].v.file = p; } else COMPERR (_("%s: must be an absolute " "file name or chdir must be specified"), comp->redir[i].v.file); } } return header; #undef COMPERR } struct component * component_create (const char *name) { struct component *comp = progman_lookup_component (name); if (!comp) { comp = xzalloc (sizeof (*comp)); comp->facility = log_facility; comp->redir[RETR_OUT].type = comp->redir[RETR_ERR].type = redir_null; comp->tag = xstrdup (name); } return comp; } void component_finish (struct component *comp, grecs_locus_t *locus) { if (component_verify (comp, locus) == 0) { /* FIXME: The prog list is traversed twice for each component statement, this is suboptimal. */ if (progman_lookup_component (comp->tag) == NULL) register_prog (comp); } } static int component_section_parser (enum grecs_callback_command cmd, grecs_locus_t * locus, void *varptr, grecs_value_t * value, void *cb_data) { struct component *comp; void **section_data = cb_data; switch (cmd) { case grecs_callback_section_begin: if (assert_grecs_value_type (locus, value, GRECS_TYPE_STRING)) return 1; comp = component_create (value->v.string); *section_data = comp; break; case grecs_callback_section_end: comp = *(struct component **) section_data; component_finish (comp, locus); break; case grecs_callback_set_value: grecs_error (locus, 0, _("expected block statement")); } return 0; } /* syslog */ static struct grecs_keyword syslog_kw[] = { {"facility", N_("name"), N_("Set syslog facility. Arg is one of the following: user, daemon, " "auth, authpriv, mail, cron, local0 through local7 (case-insensitive), " "or a facility number."), grecs_type_string, &log_facility, 0, cb_syslog_facility}, {"tag", N_("string"), N_("Tag syslog messages with this string"), grecs_type_string, &log_tag}, #if 0 /* This is reserved for future use */ { "print-priority", N_("arg"), N_("Prefix each message with its priority"), grecs_type_bool, &syslog_include_prio}, #endif {NULL}, }; struct component default_component; static int _cm_include_meta1 (enum grecs_callback_command cmd, grecs_locus_t *locus, void *varptr, grecs_value_t *value, void *cb_data) { if (assert_grecs_value_type (locus, value, GRECS_TYPE_STRING)) return 1; meta1_config_parse (value->v.string); return 0; } struct grecs_keyword pies_keywords[] = { {"component", N_(""), N_("Define a component"), grecs_type_section, NULL, 0, component_section_parser, NULL, component_keywords}, {"syslog", NULL, N_("Configure syslog logging"), grecs_type_section, NULL, 0, NULL, NULL, syslog_kw}, {"debug", NULL, N_("Set debug verbosity level."), grecs_type_uint, &debug_level, 0, NULL}, {"source-info", NULL, N_("Show source info with debugging messages."), grecs_type_bool, &source_info_option, 0, NULL}, {"pidfile", NULL, N_("Write PID to this file."), grecs_type_string, &pidfile, 0, NULL, }, {"control-file", NULL, N_("Set location of the control file."), grecs_type_string, &ctlfile, 0, NULL, }, {"stat-file", NULL, N_("Set location of the statistics output file."), grecs_type_string, &statfile, 0, NULL, }, {"user", NULL, N_("Run with this user privileges."), grecs_type_string, &pies_privs.user, 0, NULL, }, {"group", NULL, N_("Retain supplementary group."), grecs_type_string | GRECS_LIST, &pies_privs.groups, 0, NULL, }, {"allgroups", NULL, N_("Retain all supplementary groups of which user is a member."), grecs_type_bool, &pies_privs.allgroups, 0, NULL, }, {"umask", N_("arg: number"), N_("Force this umask."), grecs_type_string, &pies_umask, 0, _cb_umask, }, {"limits", NULL, N_("Set global system limits."), grecs_type_string, &pies_limits, 0, _cb_limits, }, {"shutdown-timeout", "n", N_("Wait seconds for all components to shut down."), grecs_type_uint, &shutdown_timeout, 0, NULL, }, {"return-code", N_(""), N_("Define what to do when the component finishes."), grecs_type_section, &default_component, 0, return_code_section_parser, NULL, return_code_keywords}, {"acl", N_("name: string"), N_("Set global ACL."), grecs_type_section, &pies_acl, 0, acl_section_parser, NULL, acl_keywords}, {"defacl", N_("name: string"), N_("Define an ACL."), grecs_type_section, NULL, 0, defacl_section_parser, NULL, acl_keywords}, {"include-meta1", N_("file: string"), N_("Include components from the specified MeTA1 configuration file."), grecs_type_string, NULL, 0, _cm_include_meta1, }, {"meta1-queue-dir", NULL, N_("Set the name of MeTA1 queue directory (default /var/spool/meta1)."), grecs_type_string, &meta1_queue_dir, 0, NULL, }, {"mailer-program", NULL, N_("Full path to the mailer binary."), grecs_type_string, &mailer_program, 0, NULL }, {"mailer-command-line", NULL, N_("Mailer command line (without recipient addresses)."), grecs_type_string, &mailer_command_line, 0, NULL }, {NULL} }; void config_init () { grecs_set_keywords (pies_keywords); grecs_include_path_setup (DEFAULT_VERSION_INCLUDE_DIR, DEFAULT_INCLUDE_DIR, NULL); grecs_log_to_stderr = log_to_stderr; if (DEFAULT_PREPROCESSOR) { obstack_init (&pp_stk); obstack_grow (&pp_stk, DEFAULT_PREPROCESSOR, sizeof (DEFAULT_PREPROCESSOR) - 1); } } void config_help () { static char docstring[] = N_("Configuration file structure for pies.\n" "For more information, use `info pies configuration'."); grecs_format_docstring (stdout, docstring, 0); grecs_format_statement_array (stdout, pies_keywords, 1, 0); } const char *program_version = "pies (" PACKAGE_STRING ")"; const char *argp_program_bug_address = "<" PACKAGE_BUGREPORT ">"; static char doc[] = N_("pies -- process invocation and execution supervisor"); static char args_doc[] = ""; enum { OPT_FOREGROUND = 256, OPT_SYSLOG, OPT_STDERR, OPT_DUMP_PREREQ, OPT_DUMP_DEPMAP, OPT_FORCE, OPT_CONFIG_HELP, OPT_SOURCE_INFO }; #define OPT_RESTART 'R' #define OPT_RELOAD 'r' #define OPT_STATUS 's' #define OPT_STOP 'S' static struct argp_option options[] = { #define GRP 0 {NULL, 0, NULL, 0, N_("Operation Mode"), GRP}, {"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"),}, {"force", OPT_FORCE, NULL, 0, N_("force startup even if another instance may be running"), GRP + 1}, {"lint", 't', NULL, 0, N_("parse configuration file and exit"), GRP + 1}, {NULL, 'E', NULL, 0, N_("preprocess config and exit"), GRP + 1}, {"config-file", 'c', N_("FILE"), 0, N_("use FILE instead of the default configuration"), GRP + 1}, {"config-help", OPT_CONFIG_HELP, NULL, 0, N_("show configuration file summary"), GRP + 1}, #undef GRP #define GRP 5 {NULL, 0, NULL, 0, N_("Preprocessor"), GRP}, {"define", 'D', N_("NAME[=VALUE]"), 0, N_("define a preprocessor symbol NAME as having VALUE, or empty"), GRP+1 }, {"undefine", 'U', N_("NAME"), 0, N_("undefine a preprocessor symbol NAME"), GRP+1 }, #undef GRP #define GRP 10 {NULL, 0, NULL, 0, N_("Component Management"), GRP}, {"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 #define GRP 20 {NULL, 0, NULL, 0, N_("Debugging and Additional Diagnostics"), GRP}, {"debug", 'x', N_("LEVEL"), 0, N_("set debug verbosity level"), GRP + 1}, {"source-info", OPT_SOURCE_INFO, NULL, 0, N_("show source info with debugging messages"), GRP + 1}, {"dump-prereq", OPT_DUMP_PREREQ, NULL, 0, N_("dump prerequisite charts"), GRP + 1}, {"dump-depmap", OPT_DUMP_DEPMAP, NULL, 0, N_("dump dependency map"), GRP + 1}, #undef GRP {NULL} }; static error_t parse_opt (int key, char *arg, struct argp_state *state) { switch (key) { case 'c': conffile = arg; break; case 'D': add_pp_option ("-D", arg); break; case 'U': add_pp_option ("-U", arg); break; case 'E': preprocess_only = 1; break; case 't': log_to_stderr = 1; lint_mode = 1; break; case OPT_CONFIG_HELP: config_help (); exit (0); case OPT_FOREGROUND: log_to_stderr = 1; foreground = 1; break; case OPT_RELOAD: case OPT_STATUS: case OPT_STOP: case OPT_RESTART: case OPT_DUMP_PREREQ: case OPT_DUMP_DEPMAP: 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': debug_level = strtoul (arg, NULL, 0); break; case OPT_SOURCE_INFO: source_info_option = 1; break; case OPT_FORCE: force_option = 1; break; case ARGP_KEY_INIT: break; case ARGP_KEY_FINI: break; default: return ARGP_ERR_UNKNOWN; } return 0; } static struct argp argp = { options, parse_opt, args_doc, doc, NULL, NULL, NULL }; #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; logmsg (LOG_NOTICE, "received signal %d", sig); break; case SIGHUP: logmsg (LOG_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) logmsg (LOG_ERR, _("cannot open pid file `%s': %s"), pidfile, strerror (errno)); return -1; } while ((c = fgetc (fp)) != EOF) { if (isdigit (c)) n = n * 10 + c - '0'; else if (c == '\n') break; else { logmsg (LOG_ERR, _("unexpected character %#03o in pidfile `%s'"), c, pidfile); return -1; } } fclose (fp); if (n && kill (n, 0)) { logmsg (LOG_ERR, _("cannot signal master process %lu: %s"), (unsigned long) n, strerror (errno)); if (errno == EPERM) return n; /* be on the safe side */ return -1; } return n; } enum pies_status { pies_status_ctr, /* clear to run */ pies_status_stale, pies_status_noresp, pies_status_running }; enum pies_status pies_check_status (pid_t * ppid) { pid_t pid = pidfile_read (0); int i; int rc; if (pid <= 0) return pies_status_ctr; *ppid = pid; if (kill (pid, SIGUSR2)) return pies_status_stale; for (i = 0; i < 4 && (rc = access (statfile, R_OK)); i++) sleep (1); if (rc) return pies_status_noresp; return pies_status_running; } void stop_components () { FILE *fp; size_t size = 0; char *buf = NULL; logmsg (LOG_INFO, _("stopping components")); fp = fopen (ctlfile, "r"); if (!fp) { logmsg (LOG_ERR, _("cannot open control file `%s': %s"), ctlfile, strerror (errno)); return; } if (unlink (ctlfile)) { logmsg (LOG_ERR, _("cannot unlink control file `%s': %s"), ctlfile, 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) { logmsg (LOG_ERR, _("cannot open control file `%s': %s"), ctlfile, 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) { logmsg (LOG_CRIT, _("pies is not running")); return 1; } logmsg (LOG_INFO, _("reloading pies at PID %lu"), (unsigned long) pid); return kill (pid, SIGHUP) ? EX_SOFTWARE : 0; } int pies_status () { pid_t pid; FILE *fp; if (unlink (statfile) && errno != ENOENT) logmsg (LOG_ERR, _("cannot unlink statfile `%s': %s"), statfile, strerror (errno)); switch (pies_check_status (&pid)) { case pies_status_ctr: logmsg (LOG_INFO, _("pies is not running")); break; case pies_status_stale: logmsg (LOG_INFO, _("pies is not running, but a pidfile " "is found (pid %lu)"), (unsigned long) pid); return 1; case pies_status_noresp: logmsg (LOG_INFO, _("pies seems to run with pid %lu, but is not responding"), (unsigned long) pid); if (unlink (statfile)) logmsg (LOG_ERR, _("cannot unlink statfile `%s': %s"), statfile, strerror (errno)); return 2; case pies_status_running: fp = fopen (statfile, "r"); if (!fp) logmsg (LOG_ERR, _("cannot open statfile `%s': %s"), statfile, strerror (errno)); else { char c; if (unlink (statfile)) logmsg (LOG_ERR, _("cannot unlink statfile `%s': %s"), statfile, 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) { logmsg (LOG_CRIT, _("pies is not running")); return EX_USAGE; } logmsg (LOG_INFO, _("stopping pies at PID %lu"), (unsigned long) pid); return kill (pid, SIGTERM) ? EX_SOFTWARE : 0; } /* Pidfile */ /* Check whether pidfile NAME exists and if so, whether its PID is still active. Exit if it is. */ void check_pidfile (char *name) { unsigned long pid; FILE *fp = fopen (name, "r"); if (!fp) { if (errno == ENOENT) return; logmsg (LOG_ERR, _("cannot open pidfile `%s': %s"), name, strerror (errno)); exit (EX_TEMPFAIL); } if (fscanf (fp, "%lu", &pid) != 1) { logmsg (LOG_ERR, ("cannot get pid from pidfile `%s'"), name); } else { if (kill (pid, 0) == 0) { logmsg (LOG_ERR, _ ("%s appears to run with pid %lu. If it does not, remove `%s' and retry."), program_name, pid, name); exit (EX_USAGE); } } fclose (fp); if (unlink (pidfile)) { logmsg (LOG_ERR, _("cannot unlink pidfile `%s': %s"), name, strerror (errno)); exit (EX_USAGE); } } void create_pidfile (char *name) { FILE *fp = fopen (name, "w"); if (!fp) { logmsg (LOG_ERR, _("cannot create pidfile `%s': %s"), name, strerror (errno)); exit (EX_TEMPFAIL); } fprintf (fp, "%lu", (unsigned long) getpid ()); fclose (fp); } void remove_pidfile (char *name) { if (unlink (name)) logmsg (LOG_ERR, _("cannot unlink pidfile `%s': %s"), name, strerror (errno)); } static void set_mailer_argcv () { int i; struct wordsplit ws; if (wordsplit (mailer_command_line, &ws, WRDSF_DEFFLAGS)) { logmsg (LOG_CRIT, _("cannot parse mailer command line: %s"), strerror (errno)); exit (EX_CONFIG); } mailer_argc = ws.ws_wordc; mailer_argv = xcalloc (mailer_argc + 1, sizeof (mailer_argv[0])); for (i = 0; i < mailer_argc; i++) mailer_argv[i] = xstrdup (ws.ws_wordv[i]); mailer_argv[i] = NULL; wordsplit_free (&ws); } const char version_etc_copyright[] = /* Do *not* mark this string for translation. %s is a copyright symbol suitable for this locale */ "Copyright %s 2009 Sergey Poznyakoff"; static void version (FILE *stream, struct argp_state *state) { fprintf (stream, "%s (%s) %s\n", "pies", PACKAGE, PACKAGE_VERSION); /* TRANSLATORS: Translate "(C)" to the copyright symbol (C-in-a-circle), if this symbol is available in the user's locale. Otherwise, do not translate "(C)"; leave it as-is. */ fprintf (stream, version_etc_copyright, _("(C)")); fputs (_("\ \n\ License GPLv3+: GNU GPL version 3 or later \n\ This is free software: you are free to change and redistribute it.\n\ There is NO WARRANTY, to the extent permitted by law.\n\ \n\ "), stream); /* TRANSLATORS: %s denotes an author name. */ fprintf (stream, _("Written by %s.\n"), "Sergey Poznyakoff"); } int main (int argc, char **argv) { int index; pid_t pid; extern char **environ; set_program_name (argv[0]); #ifdef ENABLE_NLS setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); bindtextdomain ("mailfromd", LOCALEDIR); textdomain (PACKAGE); #endif mf_proctitle_init (argc, argv, environ); /* Set default logging */ log_to_stderr = !stderr_closed_p (); log_setup (log_to_stderr); config_init (); argp_program_version_hook = version; if (argp_parse (&argp, argc, argv, 0, &index, NULL)) exit (EX_USAGE); if (!DEFAULT_PREPROCESSOR) grecs_preprocessor = NULL; else { grecs_preprocessor = obstack_finish (&pp_stk); free (pp_qopt); } if (preprocess_only) exit (grecs_preproc_run (conffile, grecs_preprocessor) ? EX_CONFIG : 0); if (grecs_parse (conffile)) exit (EX_CONFIG); set_mailer_argcv (); if (lint_mode) { progman_build_depmap (); exit (0); } /* Re-setup logging: it might have been reset in the config file */ log_setup (log_to_stderr); if (argc != index && command != 'R') { logmsg (LOG_ERR, "extra command line arguments"); exit (EX_CONFIG); } progman_build_depmap (); switch (command) { case OPT_RESTART: pies_priv_setup (&pies_privs); 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 ()); case OPT_DUMP_PREREQ: progman_dump_prereq (); exit (0); case OPT_DUMP_DEPMAP: progman_dump_depmap (); exit (0); default: pies_priv_setup (&pies_privs); if (pies_umask) umask (pies_umask); } switch (pies_check_status (&pid)) { case pies_status_ctr: break; case pies_status_stale: case pies_status_noresp: if (!force_option) { logmsg (LOG_ERR, _("another pies instance may be running (pid %lu), " "use --force to override"), (unsigned long) pid); exit (EX_USAGE); } break; case pies_status_running: logmsg (LOG_ERR, _("another pies instance already running (pid %lu)"), (unsigned long) pid); exit (EX_USAGE); } if (!foreground) log_setup (log_to_stderr = 0); logmsg (LOG_INFO, _("%s starting"), program_version); if (!foreground) check_pidfile (pidfile); if (!foreground && daemon (0, 0) == -1) { logmsg (LOG_ERR, _("cannot become a daemon: %s"), strerror (errno)); exit (EX_SOFTWARE); } create_pidfile (pidfile); if (argv[0][0] != '/') logmsg (LOG_NOTICE, N_("not started as an absolute pathname; " "SIGHUP will not work")); signal_setup (sig_handler); progman_create_sockets (); progman_start (); do { 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 (); } } } while (action == ACTION_CONT); progman_stop (); if (action == ACTION_RESTART && argv[0][0] == '/') { int minfd = log_to_stderr ? 2 : 0; int i; for (i = getmaxfd (); i > minfd; i--) close (i); remove_pidfile (pidfile); signal_setup (SIG_DFL); execv (argv[0], argv); } logmsg (LOG_INFO, _("%s terminated"), program_version); exit (EX_OK); } void xalloc_die () { logmsg (LOG_CRIT, _("not enough memory")); abort (); } /* EOF */