/* This file is part of GNU Pies. Copyright (C) 2008, 2009, 2010 Sergey Poznyakoff GNU 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. GNU 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 GNU Pies. If not, see . */ #include "pies.h" #include #include #include "meta1lex.h" int preprocess_only; /* Preprocess config, do nothing more */ int lint_mode; /* Test configuration syntax and exit */ int log_to_stderr_only; /* Use only stderr for logging */ int log_facility = LOG_USER; char *log_tag; struct pies_privs pies_privs; int foreground; int command; char *statedir = DEFAULT_STATE_DIR; char *instance; char *pidfile; char *ctlfile; char *statfile; char *qotdfile; int inetd_mode; mode_t pies_umask = 0; unsigned long shutdown_timeout = 5; size_t default_max_rate; 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; enum config_syntax { CONF_PIES, CONF_META1, CONF_INETD }; struct config_file { struct config_file *next; enum config_syntax syntax; char *name; }; static struct config_file *conf_head, *conf_tail; struct config_syntax_descr { const char *name; enum config_syntax type; }; static struct config_syntax_descr config_syntax_tab[] = { { "pies" , CONF_PIES }, { "meta1", CONF_META1 }, { "inetd", CONF_INETD }, { NULL } }; int str_to_config_syntax (const char *str, enum config_syntax *psynt) { struct config_syntax_descr *p; for (p = config_syntax_tab; p->name; p++) if (strcmp (p->name, str) == 0) { *psynt = p->type; return 0; } return 1; } void add_config (enum config_syntax syntax, const char *name) { struct config_file *file = xmalloc (sizeof (file[0])); file->next = NULL; file->syntax = syntax; file->name = xstrdup (name); if (conf_tail) conf_tail->next = file; else conf_head = file; conf_tail = file; } 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 */ 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 a 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, size_t *pargc) { 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; if (pargc) *pargc = argc; return argv; } static int _cb_command (enum grecs_callback_command cmd, grecs_locus_t *locus, void *varptr, grecs_value_t *value, void *cb_data) { struct component *comp = 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; } comp->argc = ws.ws_wordc; comp->argv = ws.ws_wordv; break; case GRECS_TYPE_ARRAY: comp->argv = config_array_to_argv (value, locus, &comp->argc); break; case GRECS_TYPE_LIST: grecs_error (locus, 0, _("unexpected list")); return 1; } 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, NULL); 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 socktype_xtab[] = { { "stream", SOCK_STREAM }, { "dgram", SOCK_DGRAM }, { "seqpacket", SOCK_SEQPACKET }, { "raw", SOCK_RAW }, { "rdm", SOCK_RDM }, #ifdef SOCK_PACKET { "packet", SOCK_PACKET }, #endif { NULL } }; int str_to_socket_type (const char *str, int *pres) { return strtotok (socktype_xtab, str, pres); } int socket_type_to_str (int socket_type, const char **pres) { return toktostr (socktype_xtab, socket_type, pres); } static int _cb_socket_type (enum grecs_callback_command cmd, grecs_locus_t *locus, void *varptr, grecs_value_t *value, void *cb_data) { if (assert_scalar_stmt (locus, cmd) || assert_grecs_value_type (locus, value, GRECS_TYPE_STRING)) return 1; if (str_to_socket_type (value->v.string, varptr)) grecs_error (locus, 0, _("bad socket type")); 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; } int str_to_cf (const char *string, int *flags) { size_t len = strlen (string); int neg = 0; int mask; static struct tokendef cf_tab[] = { { "disable", CF_DISABLED }, { "precious", CF_PRECIOUS }, { "wait", CF_WAIT }, { "tcpmux", CF_TCPMUX }, { "tcpmuxplus", CF_TCPMUXPLUS }, { "internal", CF_INTERNAL }, { "sockenv", CF_SOCKENV }, { "resolve", CF_RESOLVE }, { "siggroup", CF_SIGGROUP }, { NULL } }; if (len > 2 && memcmp (string, "no", 2) == 0) { neg++; string += 2; } if (strtotok (cf_tab, string, &mask)) return 1; if (neg) *flags &= ~mask; else *flags |= mask; return 0; } static int _cb_flags (enum grecs_callback_command cmd, grecs_locus_t *locus, void *varptr, grecs_value_t *value, void *cb_data) { int *flags = varptr; switch (value->type) { case GRECS_TYPE_STRING: if (str_to_cf (value->v.string, flags)) { grecs_error (locus, 0, _("%s: unrecognised flag"), value->v.string); return 1; } break; case GRECS_TYPE_LIST: { const void *p; gl_list_iterator_t itr = gl_list_iterator (value->v.list); while (gl_list_iterator_next (&itr, &p, NULL)) { const grecs_value_t *vp = p; if (assert_grecs_value_type (locus, vp, GRECS_TYPE_STRING)) return 1; if (str_to_cf (vp->v.string, flags)) { grecs_error (locus, 0, _("%s: unrecognised flag"), vp->v.string); return 1; } } } break; case GRECS_TYPE_ARRAY: grecs_error (locus, 0, _("too many arguments")); return 1; } 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, 0, _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, }, {"flags", N_("list"), N_("List of flags."), grecs_type_string | GRECS_LIST, NULL, offsetof (struct component, flags), _cb_flags }, {"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, }, {"max-instances", NULL, N_("Maximum number of running instances."), grecs_type_size, NULL, offsetof (struct component, max_instances), NULL }, {"max-instances-message", NULL, N_("Text to send back if max-instances is reached (inetd-components only)."), grecs_type_string, NULL, offsetof (struct component, max_instances_message), NULL }, {"max-ip-connections", NULL, N_("Maximum number of simultaneous connections per IP address (inetd only)."), grecs_type_size, NULL, offsetof (struct component, max_ip_connections), NULL }, {"max-ip-connections-message", NULL, N_("Text to send back if max-ip-connections-message is reached (inetd only)."), grecs_type_string, NULL, offsetof (struct component, max_ip_connections_message), NULL }, {"max-rate", NULL, N_("Maximum number of times an inetd component can be invoked in one minute."), grecs_type_size, NULL, offsetof (struct component, max_rate), NULL }, {"socket", N_("url: string"), N_("Listen on the given url."), grecs_type_string, NULL, offsetof (struct component, socket_url), _cb_url, }, {"socket-type", /* TRANSLATORS: words after `type:' are keywords. */ N_("type: {stream | dgram | raw | rdm | seqpacket}"), N_("Set socket type."), grecs_type_int, NULL, offsetof (struct component, socket_type), _cb_socket_type }, {"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}, {"access-denied-message", NULL, N_("Text to send back if access is denied (inetd-components only)."), grecs_type_string, NULL, offsetof (struct component, access_denied_message), NULL }, {"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}> 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(func, fmt, arg) \ do \ { \ if (!header) \ { \ grecs_warning (locus, 0, _("in component %s:"), comp->tag); \ header = 1; \ } \ func (locus, 0, fmt, arg); \ } \ while (0) if (comp->flags & CF_INTERNAL) { comp->mode = pies_comp_inetd; if (!comp->service) /* TRANSLATORS: do not translate quoted words, they are keywords. */ COMPERR (grecs_error, "%s", _("`internal' used without `service'")); else { comp->builtin = inetd_builtin_lookup (comp->service, comp->socket_type); if (!comp->builtin) COMPERR (grecs_error, "%s", _("unknown internal service")); if (comp->argv) /* TRANSLATORS: do not translate quoted words, they are keywords. */ COMPERR (grecs_error, "%s", _("`internal' used with `command'")); } } else if (!comp->argv) COMPERR (grecs_error, "%s", _("missing command line")); if (ISCF_TCPMUX (comp->flags)) { comp->mode = pies_comp_inetd; if ((comp->flags & (CF_TCPMUX | CF_TCPMUXPLUS)) == (CF_TCPMUX | CF_TCPMUXPLUS)) COMPERR (grecs_error, "%s", _("both `tcpmux' and `tcpmuxplus' used")); else if (!comp->service) /* TRANSLATORS: do not translate quoted words, they are keywords. */ COMPERR (grecs_error, "%s", _("`internal' used without `service'")); } if (comp->pass_fd_socket && comp->mode != pies_comp_pass_fd) COMPERR (grecs_error, "%s", _("pass-fd-socket ignored: wrong mode")); switch (comp->mode) { case pies_comp_exec: if (comp->socket_url) COMPERR (grecs_error, "%s", _("socket ignored: wrong mode")); break; case pies_comp_pass_fd: if (!comp->pass_fd_socket) COMPERR (grecs_error, "%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 (grecs_error, "%s", _("pass-fd-socket must be an absolute " "file name or chdir must be specified")); } /* Fall through */ case pies_comp_accept: if (!comp->socket_url) { COMPERR (grecs_error, "%s", _("socket must be specified in this mode")); /* FIXME: Memory leak */ return 1; } break; case pies_comp_inetd: if (ISCF_TCPMUX (comp->flags)) { pies_url_destroy (&comp->socket_url); if (!comp->tcpmux) { COMPERR (grecs_warning, "%s", _("TCPMUX master not specified, assuming \"tcpmux\"")); comp->tcpmux = xstrdup ("tcpmux"); } } else if (comp->tcpmux) { comp->flags |= CF_TCPMUX; pies_url_destroy (&comp->socket_url); } else if (!comp->socket_url) { COMPERR (grecs_error, "%s", _("socket must be specified in this mode")); /* FIXME: Memory leak */ return 1; } } if (comp->mode == pies_comp_inetd) { if ((comp->flags & CF_WAIT) && comp->socket_type == SOCK_STREAM) { if (comp->max_instances) COMPERR (grecs_error, "%s", _("max-instances ignored")); else comp->max_instances = 1; } } else if (comp->flags & CF_WAIT) { /* TRANSLATORS: `wait' is a keywords, do not translate. */ COMPERR (grecs_error, "%s", _("wait is useless in this mode")); comp->flags &= ~CF_WAIT; } if (comp->mode != pies_comp_exec && comp->redir[RETR_OUT].type != redir_null) { COMPERR (grecs_error, "%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 (grecs_error, _("%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); comp->socket_type = SOCK_STREAM; } 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 _cb_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; } static int _cb_include_inetd (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; return inetd_parse_conf (value->v.string); } struct grecs_keyword pies_keywords[] = { {"component", N_("tag: string"), 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}, {"state-directory", NULL, N_("Full file name of the program state directory."), grecs_type_string, &statedir, 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, }, {"qotd-file", NULL, N_("Set location of the QOTD file."), grecs_type_string, &qotdfile, 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_("tag: exit-code-list"), 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-inetd", N_("file-or-dir: string"), N_("Include inetd configuration file or directory"), grecs_type_string, NULL, 0, _cb_include_inetd }, {"include-meta1", N_("file: string"), N_("Include components from the specified MeTA1 configuration file."), grecs_type_string, NULL, 0, _cb_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_only; if (DEFAULT_PREPROCESSOR) { obstack_init (&pp_stk); obstack_grow (&pp_stk, DEFAULT_PREPROCESSOR, sizeof (DEFAULT_PREPROCESSOR) - 1); } } void config_help () { static char docstring[] = /* TRANSLATORS: do not translate words between ` and ' */ 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_SYNTAX, OPT_SYSLOG, OPT_STDERR, OPT_DUMP_PREREQ, OPT_DUMP_DEPMAP, OPT_FORCE, OPT_CONFIG_HELP, OPT_SOURCE_INFO, OPT_RATE, OPT_INSTANCE }; #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}, {"inetd", 'i', NULL, 0, N_("run in inetd mode"), 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}, {"syntax", OPT_SYNTAX, "{pies|inetd|meta1}", 0, N_("expect configuration files in the given syntax"), GRP+1 }, {"rate", OPT_RATE, N_("NUMBER"), 0, N_("set default maximum rate for inetd-style components"), GRP + 1}, {"instance", OPT_INSTANCE, N_("NAME"), 0, N_("set instance name"), 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 enum config_syntax current_syntax = CONF_PIES; static error_t parse_opt (int key, char *arg, struct argp_state *state) { char *p; switch (key) { case 'c': add_config (current_syntax, 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 'i': if (!instance) instance = "inetd"; current_syntax = CONF_INETD; inetd_mode = 1; break; case 't': log_to_stderr_only = 1; lint_mode = 1; break; case OPT_CONFIG_HELP: config_help (); exit (0); case OPT_FOREGROUND: log_to_stderr_only = 1; foreground = 1; break; case OPT_INSTANCE: instance = arg; break; case OPT_SYNTAX: if (str_to_config_syntax (arg, ¤t_syntax)) { logmsg (LOG_ERR, _("unknown syntax type: %s"), arg); exit (EX_USAGE); } break; case OPT_RELOAD: case OPT_STATUS: case OPT_STOP: case OPT_RESTART: case OPT_DUMP_PREREQ: case OPT_DUMP_DEPMAP: log_to_stderr_only = 1; command = key; break; case OPT_SYSLOG: log_to_stderr_only = 0; break; case OPT_STDERR: log_to_stderr_only = 1; break; case 'x': debug_level = strtoul (arg, NULL, 0); break; case OPT_RATE: default_max_rate = strtoul (arg, &p, 10); if (*p) { logmsg (LOG_ERR, _("not a number: %s"), arg); exit (EX_USAGE); } 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)) { if (errno != ESRCH) 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", PACKAGE, PACKAGE_NAME, 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"); } static char * mkfilename (const char *dir, const char *name, const char *suf) { size_t dirlen = strlen (dir); char *s; while (dirlen > 0 && dir[dirlen-1] == '/') dirlen--; s = xmalloc (dirlen + 1 + strlen (name) + strlen (suf) + 1); strcpy (s, dir); strcat (s, "/"); strcat (s, name); strcat (s, suf); return s; } static void set_conf_file_names (const char *base) { if (!conf_head) { char *name = mkfilename (SYSCONFDIR, base, ".conf"); add_config (current_syntax, name); free (name); } } static void set_state_file_names (const char *base) { if (!pidfile) pidfile = mkfilename (statedir, base, ".pid"); if (!ctlfile) ctlfile = mkfilename (statedir, base, ".ctl"); if (!statfile) statfile = mkfilename (statedir, base, ".stat"); if (!qotdfile) qotdfile = mkfilename (statedir, base, ".qotd"); } int main (int argc, char **argv) { int index; pid_t pid; extern char **environ; struct config_file *file; 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_quoting_style (NULL, shell_quoting_style); /* Set default logging */ diag_setup (DIAG_TO_SYSLOG | (stderr_closed_p () ? 0 : DIAG_TO_STDERR)); config_init (); argp_program_version_hook = version; if (argp_parse (&argp, argc, argv, 0, &index, NULL)) exit (EX_USAGE); if (!instance) { instance = strrchr (program_name, '/'); if (!instance) instance = (char*) program_name; else instance++; } log_tag = instance; set_conf_file_names (instance); if (!DEFAULT_PREPROCESSOR) grecs_preprocessor = NULL; else { grecs_preprocessor = obstack_finish (&pp_stk); free (pp_qopt); } if (preprocess_only) { for (file = conf_head; file; file = file->next) { if (file->syntax == CONF_PIES && grecs_preproc_run (file->name, grecs_preprocessor)) exit (EX_CONFIG); } exit (0); } else for (file = conf_head; file; file = file->next) { switch (file->syntax) { case CONF_PIES: if (grecs_parse (file->name)) exit (EX_CONFIG); break; case CONF_INETD: if (inetd_parse_conf (file->name)) exit (EX_CONFIG); break; case CONF_META1: if (meta1_config_parse (file->name)) exit (EX_CONFIG); break; } } set_state_file_names (instance); set_mailer_argcv (); if (lint_mode) { progman_build_depmap (); exit (0); } /* Re-setup logging: it might have been reset in the config file */ diag_setup (log_to_stderr_only ? DIAG_TO_STDERR : 0); 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); } logmsg (LOG_INFO, _("%s starting"), program_version); if (!foreground) { check_pidfile (pidfile); if (daemon (0, 0) == -1) { logmsg (LOG_ERR, _("cannot become a daemon: %s"), strerror (errno)); exit (EX_SOFTWARE); } diag_setup (DIAG_TO_SYSLOG); } create_pidfile (pidfile); if (argv[0][0] != '/') logmsg (LOG_NOTICE, _("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) { progman_wake_sleeping (1); got_alarm = 0; } } } while (action == ACTION_CONT); progman_stop (); remove_pidfile (pidfile); if (action == ACTION_RESTART && argv[0][0] == '/') { int minfd = DIAG_OUTPUT (DIAG_TO_STDERR) ? 2 : 0; int i; for (i = getmaxfd (); i > minfd; i--) close (i); 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 */