/* 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 */