aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.gitignore6
-rw-r--r--src/Makefile.am57
-rw-r--r--src/depmap.c170
-rw-r--r--src/limits.c299
-rw-r--r--src/meta1gram.y439
-rw-r--r--src/meta1lex.h31
-rw-r--r--src/meta1lex.l231
-rw-r--r--src/pies.c1581
-rw-r--r--src/pies.h205
-rw-r--r--src/pies.rcin31
-rw-r--r--src/progman.c1741
-rw-r--r--src/socket.c446
12 files changed, 5237 insertions, 0 deletions
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000..8a4684a
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,6 @@
+meta1gram.c
+meta1gram.h
+meta1gram.output
+meta1lex.c
+pies
+pies.rc
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..52648c8
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,57 @@
+# This file is part of mailfrom filter.
+# Copyright (C) 2008, 2009 Sergey Poznyakoff
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+sbin_PROGRAMS = pies
+
+pies_SOURCES = \
+ depmap.c\
+ limits.c\
+ meta1gram.y\
+ meta1lex.l\
+ pies.c\
+ progman.c\
+ socket.c
+
+noinst_HEADERS = \
+ meta1gram.h\
+ meta1lex.h\
+ pies.h
+
+EXTRA_DIST = pies.rcin
+noinst_DATA = pies.rc
+DISTCLEANFILES = pies.rc
+.rcin.rc:
+ $(AM_V_GEN)sed 's|SBINDIR|$(sbindir)|g' $< > $@
+
+meta1lex.c: meta1gram.h
+
+INCLUDES = \
+ $(MAILUTILS_INCLUDES)\
+ $(MU_COMMON_INCLUDES)\
+ -I$(top_srcdir)/lib\
+ -I$(top_srcdir)/gnu
+
+LDADD = \
+ ../lib/libmf.a\
+ ../gnu/libgnu.a\
+ $(MAILUTILS_LIBS)\
+ $(MF_PROCTITLE_LIBS)
+
+AM_CPPFLAGS=-DSYSCONFDIR=\"$(sysconfdir)\"\
+ -DSTATEDIR=\"$(localstatedir)\"
+
+AM_YFLAGS=-dvt -pmeta1
+AM_LFLAGS=-dvp -Pmeta1 -olex.yy.c
diff --git a/src/depmap.c b/src/depmap.c
new file mode 100644
index 0000000..76b492f
--- /dev/null
+++ b/src/depmap.c
@@ -0,0 +1,170 @@
+/* This file is part of Mailfromd.
+ Copyright (C) 2008 Sergey Poznyakoff
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 3 of the License, or (at your
+ option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "pies.h"
+
+#ifndef CHAR_BIT
+# define CHAR_BIT 8
+#endif
+#define BITS_PER_WORD (sizeof(unsigned)*CHAR_BIT)
+#define MAXTABLE 32767
+
+#define WORDSIZE(n) (((n) + BITS_PER_WORD - 1) / BITS_PER_WORD)
+#define SETBIT(x, i) ((x)[(i)/BITS_PER_WORD] |= (1<<((i) % BITS_PER_WORD)))
+#define RESETBIT(x, i) ((x)[(i)/BITS_PER_WORD] &= ~(1<<((i) % BITS_PER_WORD)))
+#define BITISSET(x, i) (((x)[(i)/BITS_PER_WORD] & (1<<((i) % BITS_PER_WORD))) != 0)
+
+void
+TC (unsigned *R, int n)
+{
+ register int rowsize;
+ register unsigned mask;
+ register unsigned *rowj;
+ register unsigned *rp;
+ register unsigned *rend;
+ register unsigned *ccol;
+
+ unsigned *relend;
+ unsigned *cword;
+ unsigned *rowi;
+
+ rowsize = WORDSIZE (n) * sizeof (unsigned);
+ relend = (unsigned *) ((char *) R + (n * rowsize));
+
+ cword = R;
+ mask = 1;
+ rowi = R;
+ while (rowi < relend)
+ {
+ ccol = cword;
+ rowj = R;
+
+ while (rowj < relend)
+ {
+ if (*ccol & mask)
+ {
+ rp = rowi;
+ rend = (unsigned *) ((char *) rowj + rowsize);
+
+ while (rowj < rend)
+ *rowj++ |= *rp++;
+ }
+ else
+ {
+ rowj = (unsigned *) ((char *) rowj + rowsize);
+ }
+
+ ccol = (unsigned *) ((char *) ccol + rowsize);
+ }
+
+ mask <<= 1;
+ if (mask == 0)
+ {
+ mask = 1;
+ cword++;
+ }
+ rowi = (unsigned *) ((char *) rowi + rowsize);
+ }
+}
+
+struct pies_depmap
+{
+ unsigned nrows;
+ unsigned rowlen;
+ unsigned r[1];
+};
+
+pies_depmap_t
+depmap_alloc (unsigned count)
+{
+ size_t size = (count + BITS_PER_WORD - 1) / BITS_PER_WORD;
+ pies_depmap_t dmap = xzalloc (sizeof (*dmap) - 1
+ + count * size * sizeof (unsigned));
+ dmap->nrows = count;
+ dmap->rowlen = size;
+ return dmap;
+}
+
+pies_depmap_t
+depmap_copy (pies_depmap_t dpm)
+{
+ pies_depmap_t copy = depmap_alloc (dpm->nrows);
+ memcpy (copy->r, dpm->r, dpm->nrows * dpm->rowlen * sizeof (unsigned));
+ return copy;
+}
+
+static unsigned *
+depmap_rowptr (pies_depmap_t dmap, unsigned row)
+{
+ return dmap->r + dmap->rowlen * row;
+}
+
+void
+depmap_set (pies_depmap_t dmap, unsigned row, unsigned col)
+{
+ unsigned *rptr = depmap_rowptr (dmap, row);
+ SETBIT (rptr, col);
+}
+
+int
+depmap_isset (pies_depmap_t dmap, unsigned row, unsigned col)
+{
+ unsigned *rptr = depmap_rowptr (dmap, row);
+ return BITISSET (rptr, col);
+}
+
+void
+depmap_tc (pies_depmap_t dmap)
+{
+ TC (dmap->r, dmap->nrows);
+}
+
+struct pies_depmap_pos
+{
+ enum pies_depmap_direction dir;
+ unsigned coord[2];
+};
+
+unsigned
+depmap_next (pies_depmap_t dmap, pies_depmap_pos_t pos)
+{
+ for (pos->coord[pos->dir]++; pos->coord[pos->dir] < dmap->nrows;
+ pos->coord[pos->dir]++)
+ if (depmap_isset (dmap, pos->coord[0], pos->coord[1]))
+ break;
+
+ return pos->coord[pos->dir] == dmap->nrows ?
+ (unsigned) -1 : pos->coord[pos->dir];
+}
+
+unsigned
+depmap_first (pies_depmap_t dmap, enum pies_depmap_direction dir,
+ unsigned coord, pies_depmap_pos_t *ppos)
+{
+ pies_depmap_pos_t pos = xmalloc (sizeof *pos);
+ *ppos = pos;
+ pos->dir = dir;
+ pos->coord[!pos->dir] = coord;
+ pos->coord[pos->dir] = -1;
+ return depmap_next (dmap, pos);
+}
+
+/*
+ Local Variables:
+ c-file-style: "gnu"
+ End:
+*/
+/* EOF */
diff --git a/src/limits.c b/src/limits.c
new file mode 100644
index 0000000..90d6459
--- /dev/null
+++ b/src/limits.c
@@ -0,0 +1,299 @@
+/* This file is part of Mailfromd.
+ Copyright (C) 2008 Sergey Poznyakoff
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <pies.h>
+
+#define SET_LIMIT_AS 0x0001
+#define SET_LIMIT_CPU 0x0002
+#define SET_LIMIT_DATA 0x0004
+#define SET_LIMIT_FSIZE 0x0008
+#define SET_LIMIT_NPROC 0x0010
+#define SET_LIMIT_CORE 0x0020
+#define SET_LIMIT_MEMLOCK 0x0040
+#define SET_LIMIT_NOFILE 0x0080
+#define SET_LIMIT_RSS 0x0100
+#define SET_LIMIT_STACK 0x0200
+#define SET_LIMIT_LOGINS 0x0400
+#define SET_LIMIT_PRIO 0x0800
+
+struct limits_rec {
+ unsigned set;
+ rlim_t limit_as;
+ rlim_t limit_cpu;
+ rlim_t limit_data;
+ rlim_t limit_fsize;
+ rlim_t limit_nproc;
+ rlim_t limit_core;
+ rlim_t limit_memlock;
+ rlim_t limit_nofile;
+ rlim_t limit_rss;
+ rlim_t limit_stack;
+ int limit_logins;
+ int limit_prio;
+};
+
+int
+do_set_limit (int rlimit, rlim_t limit)
+{
+ struct rlimit rlim;
+
+ MU_DEBUG2 (pies_debug, MU_DEBUG_TRACE1,
+ "Setting limit %d to %lu\n", rlimit, (unsigned long) limit);
+ rlim.rlim_cur = limit;
+ rlim.rlim_max = limit;
+
+ if (setrlimit(rlimit, &rlim))
+ {
+ mu_diag_output (MU_DIAG_NOTICE, _("error setting limit: %s"),
+ mu_strerror (errno));
+ return 1;
+ }
+ return 0;
+}
+
+static int
+set_prio (int prio)
+{
+ MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE2, "Setting priority to %d\n", prio);
+ if (setpriority (PRIO_PROCESS, 0, prio))
+ {
+ mu_diag_output (MU_DIAG_NOTICE, _("error setting priority: %s"),
+ mu_strerror (errno));
+ return 1;
+ }
+ return 0;
+}
+
+/* Counts the number of user logins and check against the limit */
+static int
+check_logins (const char *name, int limit)
+{
+ mu_diag_output (MU_DIAG_NOTICE, _("L limit is not implemented"));
+ return 0;
+}
+
+int
+set_limits (const char *name, struct limits_rec *lrec)
+{
+ int rc = 0;
+
+ if (!lrec)
+ return 0;
+
+ MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE2, "Setting limits for %s\n", name);
+
+#if defined(RLIMIT_AS)
+ if (lrec->set & SET_LIMIT_AS)
+ rc |= do_set_limit(RLIMIT_AS, lrec->limit_as);
+#endif
+#if defined(RLIMIT_CPU)
+ if (lrec->set & SET_LIMIT_CPU)
+ rc |= do_set_limit(RLIMIT_CPU, lrec->limit_cpu);
+#endif
+#if defined(RLIMIT_DATA)
+ if (lrec->set & SET_LIMIT_DATA)
+ rc |= do_set_limit(RLIMIT_DATA, lrec->limit_data);
+#endif
+#if defined(RLIMIT_FSIZE)
+ if (lrec->set & SET_LIMIT_FSIZE)
+ rc |= do_set_limit(RLIMIT_FSIZE, lrec->limit_fsize);
+#endif
+#if defined(RLIMIT_NPROC)
+ if (lrec->set & SET_LIMIT_NPROC)
+ rc |= do_set_limit(RLIMIT_NPROC, lrec->limit_nproc);
+#endif
+#if defined(RLIMIT_CORE)
+ if (lrec->set & SET_LIMIT_CORE)
+ rc |= do_set_limit(RLIMIT_CORE, lrec->limit_core);
+#endif
+#if defined(RLIMIT_MEMLOCK)
+ if (lrec->set & SET_LIMIT_MEMLOCK)
+ rc |= do_set_limit(RLIMIT_MEMLOCK, lrec->limit_memlock);
+#endif
+#if defined(RLIMIT_NOFILE)
+ if (lrec->set & SET_LIMIT_NOFILE)
+ rc |= do_set_limit(RLIMIT_NOFILE, lrec->limit_nofile);
+#endif
+#if defined(RLIMIT_RSS)
+ if (lrec->set & SET_LIMIT_RSS)
+ rc |= do_set_limit(RLIMIT_RSS, lrec->limit_rss);
+#endif
+#if defined(RLIMIT_STACK)
+ if (lrec->set & SET_LIMIT_STACK)
+ rc |= do_set_limit(RLIMIT_STACK, lrec->limit_stack);
+#endif
+ if (lrec->set & SET_LIMIT_LOGINS)
+ rc |= check_logins(name, lrec->limit_logins);
+ if (lrec->set & SET_LIMIT_PRIO)
+ rc |= set_prio(lrec->limit_logins);
+ return rc;
+}
+
+int
+getlimit (char **ptr, rlim_t *rlim, int mul)
+{
+ if (**ptr == '-')
+ {
+ *rlim = RLIM_INFINITY;
+ ++*ptr;
+ }
+ else
+ {
+ unsigned long val;
+
+ val = strtoul (*ptr, ptr, 10);
+ if (val == 0)
+ return 1;
+ *rlim = val * mul;
+ }
+ return 0;
+}
+
+/* Parse limits string and fill appropriate fields in lrec.
+
+ The string consists of _commands_, optionally separated by any amount
+ of whitespace. A command has the following form:
+
+ [AaCcDdFfMmNnRrSsTtUuLlPp](-|[0-9]+)
+
+ i.e. a letter followed by number or a dash. The latters stands for
+ 'unlimited'. Commands are interpreted as follows:
+
+ Command ulimit setrlimit() The limit it sets
+ option arg
+ -------------------------------------------------------------
+ [Aa] a RLIMIT_AS max address space (KB)
+ [Cc] c RLIMIT_CORE max core file size (KB)
+ [Dd] d RLIMIT_DATA max data size (KB)
+ [Ff] f RLIMIT_FSIZE Maximum filesize (KB)
+ [Mm] m RLIMIT_MEMLOCK max locked-in-memory address
+ space (KB)
+ [Nn] n RLIMIT_NOFILE max number of open files
+ [Rr] r RLIMIT_RSS max resident set size (KB)
+ [Ss] s RLIMIT_STACK max stack size (KB)
+ [Tt] t RLIMIT_CPU max CPU time (MIN)
+ [Uu] u RLIMIT_NPROC max number of processes
+ [Ll] l (none) max number of logins (N/A)
+ [Pp] p (none) process priority -20..20
+ (negative = high priority)
+ */
+int
+parse_limits (limits_record_t *plrec, char *str, char **endp)
+{
+ int c;
+ struct limits_rec *lrec = xmalloc (sizeof (*lrec));
+ *plrec = lrec;
+ lrec->set = 0;
+ while ((c = *str++))
+ {
+ if (c == ' ' || c == '\t')
+ continue;
+ switch (c)
+ {
+ case 'a':
+ case 'A':
+ /* RLIMIT_AS - max address space (KB) */
+ if (!getlimit (&str, &lrec->limit_as, 1024))
+ lrec->set |= SET_LIMIT_AS;
+ break;
+
+ case 't':
+ case 'T':
+ /* RLIMIT_CPU - max CPU time (MIN) */
+ if (!getlimit (&str, &lrec->limit_cpu, 60))
+ lrec->set |= SET_LIMIT_CPU;
+ break;
+
+ case 'd':
+ case 'D':
+ /* RLIMIT_DATA - max data size (KB) */
+ if (!getlimit (&str, &lrec->limit_data, 1024))
+ lrec->set |= SET_LIMIT_DATA;
+ break;
+
+ case 'f':
+ case 'F':
+ /* RLIMIT_FSIZE - Maximum filesize (KB) */
+ if (!getlimit (&str, &lrec->limit_fsize, 1024))
+ lrec->set |= SET_LIMIT_FSIZE;
+ break;
+
+ case 'u':
+ case 'U':
+ /* RLIMIT_NPROC - max number of processes */
+ if (!getlimit (&str, &lrec->limit_nproc, 1))
+ lrec->set |= SET_LIMIT_NPROC;
+ break;
+
+ case 'c':
+ case 'C':
+ /* RLIMIT_CORE - max core file size (KB) */
+ if (!getlimit (&str, &lrec->limit_core, 1024))
+ lrec->set |= SET_LIMIT_CORE;
+ break;
+
+ case 'm':
+ case 'M':
+ /* RLIMIT_MEMLOCK - max locked-in-memory
+ * address space (KB)
+ */
+ if (!getlimit (&str, &lrec->limit_memlock, 1024))
+ lrec->set |= SET_LIMIT_MEMLOCK;
+ break;
+
+ case 'n':
+ case 'N':
+ /* RLIMIT_NOFILE - max number of open files */
+ if (!getlimit (&str, &lrec->limit_nofile, 1))
+ lrec->set |= SET_LIMIT_NOFILE;
+ break;
+
+ case 'r':
+ case 'R':
+ /* RLIMIT_RSS - max resident set size (KB) */
+ if (!getlimit (&str, &lrec->limit_rss, 1024))
+ lrec->set |= SET_LIMIT_RSS;
+ break;
+
+ case 's':
+ case 'S':
+ /* RLIMIT_STACK - max stack size (KB) */
+ if (!getlimit (&str, &lrec->limit_stack, 1024))
+ lrec->set |= SET_LIMIT_STACK;
+ break;
+
+ case 'l':
+ case 'L':
+ lrec->limit_logins = strtol (str, &str, 10);
+ if (lrec->limit_logins >= 0)
+ lrec->set |= SET_LIMIT_LOGINS;
+ break;
+
+ case 'p':
+ case 'P':
+ lrec->limit_prio = strtol (str, &str, 10);
+ if (lrec->limit_prio > 0)
+ lrec->set |= SET_LIMIT_PRIO;
+ break;
+
+ default:
+ *endp = str-1;
+ return 1;
+ }
+ }
+ return 0;
+}
+
diff --git a/src/meta1gram.y b/src/meta1gram.y
new file mode 100644
index 0000000..4966634
--- /dev/null
+++ b/src/meta1gram.y
@@ -0,0 +1,439 @@
+%{
+/* MeTA1 configuration parser for Mailfromd.
+ Copyright (C) 2008 Sergey Poznyakoff
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* This file implements a grammar for parsing MeTA1 main configuration file,
+ and a set of functions for converting its statements into Pies
+ configuration statements. */
+
+#include "pies.h"
+#include "meta1lex.h"
+
+/* FIXME: Use mu_opool_alloc? */
+static mu_config_value_t *
+create_value (int type)
+{
+ mu_config_value_t *val = mu_alloc (sizeof (*val));
+ val->type = type;
+ return val;
+}
+
+static mu_config_value_t *
+config_value_dup (mu_config_value_t *src)
+{
+ if (!src)
+ return NULL;
+ else
+ {
+ mu_config_value_t *val = mu_alloc (sizeof (*val));
+ *val = *src;
+ return val;
+ }
+}
+
+static mu_cfg_node_t *
+alloc_node (enum mu_cfg_node_type type, mu_cfg_locus_t *loc,
+ const char *tag, mu_config_value_t *label,
+ mu_cfg_node_t *node)
+{
+ char *p;
+ mu_cfg_node_t *np;
+ size_t size = sizeof *np + strlen (tag) + 1;
+ np = mu_alloc (size);
+ np->type = type;
+ np->locus = *loc;
+ p = (char*) (np + 1);
+ np->tag = p;
+ strcpy (p, tag);
+ np->label = label;
+ np->node = node;
+ np->next = NULL;
+ return np;
+}
+
+static mu_cfg_node_t *translate_component (mu_cfg_node_t *);
+
+mu_cfg_node_t *meta1_parse_head;
+
+%}
+
+%union {
+ mu_cfg_node_t node;
+ mu_cfg_node_t *pnode;
+ mu_config_value_t *value;
+ struct { mu_cfg_node_t *head, *tail; } nodelist;
+ char *string;
+ unsigned long number;
+ mu_list_t list;
+}
+
+%token <string> META1_IDENT META1_STRING META1_NUMBER
+
+%type <nodelist> stmtlist
+%type <pnode> stmt simple block
+%type <list> slist list values
+%type <string> string ident
+%type <value> value
+
+%%
+
+input : stmtlist
+ {
+ mu_cfg_node_t *node;
+ meta1_parse_head = NULL;
+ for (node = $1.head; node; node = node->next)
+ {
+ mu_cfg_node_t *new_node = translate_component (node);
+ if (new_node)
+ {
+ new_node->next = meta1_parse_head;
+ meta1_parse_head = new_node;
+ }
+ }
+ }
+ ;
+
+stmtlist: stmt
+ {
+ $$.head = $$.tail = $1;
+ }
+ | stmtlist stmt
+ {
+ $$ = $1;
+ $$.tail->next = $2;
+ $$.tail = $2;
+ }
+ ;
+
+stmt : simple
+ | block
+ ;
+
+simple : ident '=' value opt_sc
+ {
+ $$ = alloc_node (mu_cfg_node_param, &meta1_locus,
+ $1, $3,
+ NULL);
+ }
+ ;
+
+block : ident tag '{' stmtlist '}' opt_sc
+ {
+ /* FIXME: tag is currently ignored */
+ $$ = alloc_node (mu_cfg_node_tag, &meta1_locus,
+ $1, NULL,
+ $4.head);
+ }
+ ;
+
+tag : /* empty */
+ | META1_IDENT
+ ;
+
+ident : META1_IDENT
+ ;
+
+value : string
+ {
+ $$ = create_value (MU_CFG_STRING);
+ $$->v.string = $1;
+ }
+ | list
+ {
+ $$ = create_value (MU_CFG_LIST);
+ $$->v.list = $1;
+ }
+ | META1_NUMBER
+ {
+ $$ = create_value (MU_CFG_STRING);
+ $$->v.string = $1;
+ }
+ ;
+
+string : META1_IDENT
+ | slist
+ {
+ mu_iterator_t itr;
+ mu_list_get_iterator ($1, &itr);
+
+ meta1_line_begin ();
+ for (mu_iterator_first (itr);
+ !mu_iterator_is_done (itr); mu_iterator_next (itr))
+ {
+ char *p;
+ mu_iterator_current (itr, (void**)&p);
+ meta1_line_add (p, strlen (p));
+ }
+ $$ = meta1_line_finish ();
+ mu_iterator_destroy (&itr);
+ mu_list_destroy (&$1);
+ }
+ ;
+
+slist : META1_STRING
+ {
+ mu_list_create (&$$);
+ mu_list_append ($$, $1);
+ }
+ | slist META1_STRING
+ {
+ mu_list_append ($1, $2);
+ $$ = $1;
+ }
+ ;
+
+list : '{' values '}'
+ {
+ $$ = $2;
+ }
+ | '{' values ',' '}'
+ {
+ $$ = $2;
+ }
+ ;
+
+values : value
+ {
+ mu_list_create (&$$);
+ mu_list_append ($$, $1);
+ }
+ | values ',' value
+ {
+ mu_list_append ($1, $3);
+ $$ = $1;
+ }
+ ;
+
+opt_sc : /* empty */
+ | ';'
+ ;
+
+%%
+int
+yyerror (char *s)
+{
+ meta1_parse_error ("%s", s);
+ return 0;
+}
+
+void
+meta1_parser_set_debug ()
+{
+ mu_log_level_t lev = mu_global_debug_level ("meta1");
+ if (lev & MU_DEBUG_LEVEL_MASK (MU_DEBUG_TRACE6))
+ yydebug = 1;
+}
+
+struct node_trans
+{
+ char *name;
+ char *new_name;
+ mu_cfg_node_t *(*fun) (const char *, mu_cfg_node_t *);
+};
+
+
+static struct node_trans *
+find_node_trans (struct node_trans *tab, const char *name)
+{
+ for (; tab->name; tab++)
+ if (strcmp (tab->name, name) == 0)
+ return tab;
+ return NULL;
+}
+
+static mu_cfg_node_t *
+find_node (mu_cfg_node_t *list, const char *name)
+{
+ for (; list; list = list->next)
+ {
+ if (strcmp (list->tag, name) == 0)
+ return list;
+ }
+ return NULL;
+}
+
+static mu_cfg_node_t *
+xlat_listen_socket (const char *name, mu_cfg_node_t *src)
+{
+ mu_cfg_node_t *p;
+ mu_config_value_t *val;
+
+ meta1_line_begin ();
+ p = find_node (src->node, "type");
+ if (!p || !p->label || p->label->type != MU_CFG_STRING)
+ return NULL;
+ meta1_line_add (p->label->v.string, strlen (p->label->v.string));
+ meta1_line_add ("://", 3);
+ if (strcmp (p->label->v.string, "inet") == 0)
+ {
+ const char *addr;
+ p = find_node (src->node, "address");
+ if (p)
+ {
+ if (p->label->type != MU_CFG_STRING)
+ return NULL;
+ addr = p->label->v.string;
+ }
+ else
+ addr = "0.0.0.0";
+ meta1_line_add (addr, strlen (addr));
+ meta1_line_add (":", 1);
+ p = find_node (src->node, "port");
+ if (p->label->type != MU_CFG_STRING)
+ return NULL;
+ meta1_line_add (p->label->v.string, strlen (p->label->v.string));
+ }
+ else if (strcmp (p->label->v.string, "unix") == 0)
+ {
+ p = find_node (src->node, "path");
+ if (!p || p->label->type != MU_CFG_STRING)
+ return NULL;
+ meta1_line_add (p->label->v.string, strlen (p->label->v.string));
+ /* FIXME: Other substatements:
+ listen_socket {
+ type=unix;
+ path = /tmp/socket;
+ umask = 077;
+ user = user;
+ group = group;
+ }
+ */
+ }
+ val = create_value (MU_CFG_STRING);
+ val->v.string = meta1_line_finish ();
+ return alloc_node (mu_cfg_node_param, &src->locus, "socket", val, NULL);
+}
+
+static struct node_trans root_node_trans[] = {
+ { "listen_socket", "socket", xlat_listen_socket },
+ { "start_action", "mode" },
+ { "pass_fd_socket", "pass-fd-socket" },
+ { "user", "user" },
+ { "path", "program" },
+ { "arguments", "command" },
+ { "restart_dependencies", "dependents", },
+ { NULL }
+};
+
+static mu_cfg_node_t *
+create_string_node (const char *tag, const char *str, mu_cfg_locus_t *locus)
+{
+ mu_config_value_t *val = create_value (MU_CFG_STRING);
+
+ val->v.string = meta1_string (str, strlen (str));
+ return alloc_node (mu_cfg_node_param, locus, tag, val, NULL);
+}
+
+static mu_cfg_node_t *
+create_redir_node (const char *tag, const char *dir,
+ const char *name, mu_cfg_locus_t *locus)
+{
+ mu_config_value_t *val = create_value (MU_CFG_ARRAY);
+ val->v.arg.c = 2;
+ val->v.arg.v = mu_alloc (2 * sizeof (val->v.arg.v[0]));
+
+ val->v.arg.v[1].type = MU_CFG_STRING;
+ val->v.arg.v[0].v.string = meta1_string ("file", 4);
+
+ val->v.arg.v[1].type = MU_CFG_STRING;
+ meta1_line_begin ();
+ meta1_line_add (dir, strlen (dir));
+ meta1_line_add ("/", 1);
+ meta1_line_add (name, strlen (name));
+ meta1_line_add (".log", 4);
+ val->v.arg.v[1].v.string = meta1_line_finish ();
+
+ return alloc_node (mu_cfg_node_param, locus, tag, val, NULL);
+}
+
+static mu_cfg_node_t *
+translate_node (mu_cfg_node_t *src)
+{
+ mu_cfg_node_t *dst = NULL;
+ struct node_trans *nt = find_node_trans (root_node_trans, src->tag);
+
+ if (nt)
+ {
+ if (nt->fun)
+ dst = nt->fun (nt->new_name, src);
+ else
+ dst = alloc_node (mu_cfg_node_param, &src->locus,
+ nt->new_name, config_value_dup (src->label),
+ NULL);
+ }
+ return dst;
+}
+
+#define META1_QUEUE_DIR() \
+ (meta1_queue_dir ? meta1_queue_dir : "/var/spool/meta1")
+
+static mu_cfg_node_t *
+translate_node_list (mu_cfg_node_t *src, const char *name)
+{
+ mu_cfg_node_t *head = NULL, *tail = NULL;
+ mu_cfg_locus_t *locus = &src->locus;
+
+ for (; src; src = src->next)
+ {
+ mu_cfg_node_t *dst = translate_node (src);
+ if (dst)
+ {
+ if (tail)
+ tail->next = dst;
+ else
+ head = dst;
+ tail = dst;
+ }
+ }
+ if (head)
+ {
+ /* Add common statements: */
+ mu_cfg_node_t *node;
+
+ node = create_string_node ("allgroups", "yes", locus);
+ tail->next = node;
+ tail = node;
+
+ node = create_string_node ("chdir", META1_QUEUE_DIR (), locus);
+ tail->next = node;
+ tail = node;
+
+ node = create_redir_node ("stderr", META1_QUEUE_DIR (), name, locus);
+ tail->next = node;
+ tail = node;
+
+ node = create_string_node ("settle-timeout", "1", locus);
+ tail->next = node;
+ tail = node;
+ }
+ return head;
+}
+
+static mu_cfg_node_t *
+translate_component (mu_cfg_node_t *src)
+{
+ mu_cfg_node_t *dst = NULL;
+ if (src->type == mu_cfg_node_tag)
+ {
+ mu_config_value_t *val = create_value (MU_CFG_STRING);
+ val->v.string = meta1_string (src->tag, strlen (src->tag));
+ dst = alloc_node (mu_cfg_node_tag, &src->locus,
+ "component", val,
+ translate_node_list (src->node, val->v.string));
+ }
+ return dst;
+}
+
diff --git a/src/meta1lex.h b/src/meta1lex.h
new file mode 100644
index 0000000..8db32f9
--- /dev/null
+++ b/src/meta1lex.h
@@ -0,0 +1,31 @@
+/* This file is part of Mailfromd.
+ Copyright (C) 2008 Sergey Poznyakoff
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+extern mu_cfg_locus_t meta1_locus;
+extern size_t meta1_error_count;
+extern mu_cfg_node_t *meta1_parse_head;
+extern mu_cfg_tree_t *meta1_parse_tree;
+extern char *meta1_queue_dir;
+
+char *meta1_string (const char *str, size_t len);
+void meta1_line_add (const char *text, size_t len);
+void meta1_line_add_unescape_last (const char *text, size_t len);
+void meta1_line_add_unescape_hex (const char *text, size_t len);
+void meta1_line_begin (void);
+char *meta1_line_finish ();
+void meta1_parse_error (const char *fmt, ...);
+int meta1_config_parse (const char *name);
+
diff --git a/src/meta1lex.l b/src/meta1lex.l
new file mode 100644
index 0000000..312a1fd
--- /dev/null
+++ b/src/meta1lex.l
@@ -0,0 +1,231 @@
+%{
+/* MeTA1 configuration lexer for Mailfromd.
+ Copyright (C) 2008 Sergey Poznyakoff
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* This file implements a lexical analyzer for MeTA1 main configuration file.
+ */
+
+#include "pies.h"
+#include "meta1gram.h"
+#include "meta1lex.h"
+
+mu_cfg_locus_t meta1_locus;
+size_t meta1_error_count;
+mu_opool_t meta1_pool;
+char *meta1_queue_dir;
+
+#define yylval meta1lval
+%}
+
+%x COMMENT STR
+X [0-9a-fA-F]
+%%
+ /* C-style comments */
+"/*" BEGIN (COMMENT);
+<COMMENT>[^*\n]* /* eat anything that's not a '*' */
+<COMMENT>"*"+[^*/\n]* /* eat up '*'s not followed by '/'s */
+<COMMENT>\n ++meta1_locus.line;
+<COMMENT>"*"+"/" BEGIN (INITIAL);
+ /* End-of-line comments */
+#.*\n { meta1_locus.line++; }
+#.* /* end-of-file comment */;
+ /* Number */
+0[xX]{X}+ |
+0[0-7]+ |
+[1-9][0-9]+ { meta1_line_begin ();
+ meta1_line_add (yytext, yyleng);
+ yylval.string = meta1_line_finish ();
+ return META1_NUMBER; }
+ /* Identifiers (unquoted strings) */
+[a-zA-Z0-9_\./:\*-]+ { meta1_line_begin ();
+ meta1_line_add (yytext, yyleng);
+ yylval.string = meta1_line_finish ();
+ return META1_IDENT; }
+ /* Quoted strings */
+\"[^\\"\n]*\" { meta1_line_begin ();
+ meta1_line_add (yytext + 1, yyleng - 2);
+ yylval.string = meta1_line_finish ();
+ return META1_STRING; }
+\"[^\\"\n]*\\x{X}{1,2} { BEGIN(STR);
+ meta1_line_begin ();
+ meta1_line_add_unescape_hex (yytext + 1, yyleng - 1);
+ }
+\"[^\\"\n]*\\. { BEGIN(STR);
+ meta1_line_begin ();
+ meta1_line_add_unescape_last (yytext + 1, yyleng - 1); }
+<STR>[^\\"\n]*\\x{X}{1,2} { meta1_line_add_unescape_hex (yytext, yyleng); }
+<STR>[^\\"\n]*\\. { meta1_line_add_unescape_last (yytext, yyleng); }
+<STR>[^\\"\n]*\" { BEGIN (INITIAL);
+ if (yyleng > 1)
+ meta1_line_add (yytext, yyleng - 1);
+ yylval.string = meta1_line_finish ();
+ return META1_STRING; }
+<STR>[^\\"\n]*\n { BEGIN (INITIAL);
+ meta1_parse_error (_("newline in a string"));
+ meta1_line_add (yytext, yyleng - 1);
+ yylval.string = meta1_line_finish ();
+ return META1_STRING; }
+ /* Other tokens */
+[ \t\f][ \t\f]* ;
+\n { meta1_locus.line++; }
+[,;{}=] return yytext[0];
+. { if (isascii (yytext[0]) && isprint (yytext[0]))
+ meta1_parse_error (_("stray character %c"), yytext[0]);
+ else
+ meta1_parse_error (_("stray character \\%03o"),
+ (unsigned char) yytext[0]); }
+%%
+
+int
+yywrap ()
+{
+ return 1;
+}
+
+void
+meta1_line_add (const char *text, size_t len)
+{
+ mu_opool_append (meta1_pool, text, len);
+}
+
+static void
+unescape_to_line (int c)
+{
+ char t;
+
+ if (c == 'v')
+ t = '\v';
+ else
+ {
+ t = mu_argcv_unquote_char (c);
+ if (t == c && t != '\\' && t != '\"')
+ meta1_parse_error (_("unknown escape sequence '\\%c'"), c);
+ }
+ mu_opool_append_char (meta1_pool, t);
+}
+
+void
+meta1_line_add_unescape_last (const char *text, size_t len)
+{
+ mu_opool_append (meta1_pool, text, len - 2);
+ unescape_to_line (text[len - 1]);
+}
+
+void
+meta1_line_add_unescape_hex (const char *text, size_t len)
+{
+ for (; text[len-1] != 'x' && len > 0; len--)
+ ;
+ mu_opool_append (meta1_pool, text, len - 2);
+ mu_opool_append_char (meta1_pool, (char) strtoul (text + len, NULL, 16));
+}
+
+void
+meta1_line_begin ()
+{
+ if (!meta1_pool)
+ mu_opool_create (&meta1_pool, 1);
+ else
+ mu_opool_clear (meta1_pool);
+}
+
+char *
+meta1_line_finish ()
+{
+ mu_opool_append_char (meta1_pool, 0);
+ return mu_opool_finish (meta1_pool, NULL);
+}
+
+char *
+meta1_string (const char *str, size_t len)
+{
+ meta1_line_begin ();
+ meta1_line_add (str, len);
+ return meta1_line_finish ();
+}
+
+void
+meta1_parse_error (const char *fmt, ...)
+{
+ va_list ap;
+ mu_debug_t debug;
+
+ mu_diag_get_debug (&debug);
+ mu_debug_printf (debug, 0, "%s:%lu: ", meta1_locus.file,
+ (unsigned long) meta1_locus.line);
+ va_start (ap, fmt);
+ mu_debug_vprintf (debug, 0, fmt, ap);
+ mu_debug_printf (debug, 0, "\n");
+ va_end (ap);
+ meta1_error_count++;
+}
+
+void
+meta1_lexer_set_debug ()
+{
+ mu_log_level_t lev = mu_global_debug_level ("meta1");
+ yy_flex_debug = (lev & MU_DEBUG_LEVEL_MASK (MU_DEBUG_TRACE7));
+}
+
+static int
+_cfg_default_printer (void *unused, mu_log_level_t level, const char *str)
+{
+ fprintf (stderr, "%s", str);
+ return 0;
+}
+
+mu_cfg_tree_t *meta1_parse_tree;
+
+/* Parse MeTA1 configuration file `name'. Populate `meta1_parse_tree' with
+ the parse tree. */
+int
+meta1_config_parse (const char *name)
+{
+ int rc;
+ FILE *fp;
+
+ fp = fopen (name, "r");
+ if (!fp)
+ {
+ mu_error (_("%s: cannot open file: %s"), name, mu_strerror (errno));
+ return 1;
+ }
+ meta1_locus.file = meta1_string (name, strlen (name));
+ meta1_locus.line = 1;
+ meta1_lexer_set_debug ();
+ meta1_parser_set_debug ();
+
+ yyrestart (fp);
+ rc = meta1parse ();
+ fclose (fp);
+ if (meta1_error_count)
+ rc = 1;
+ if (rc == 0)
+ {
+ meta1_parse_tree = mu_alloc (sizeof (*meta1_parse_tree));
+ mu_debug_create (&meta1_parse_tree->debug, NULL);
+ mu_debug_set_print (meta1_parse_tree->debug, _cfg_default_printer, NULL);
+ mu_debug_set_level (meta1_parse_tree->debug,
+ mu_global_debug_level ("meta1"));
+ meta1_parse_tree->node = meta1_parse_head;
+ meta1_parse_tree->pool = meta1_pool;
+ }
+ return rc;
+}
+
+
+
+
diff --git a/src/pies.c b/src/pies.c
new file mode 100644
index 0000000..10862c2
--- /dev/null
+++ b/src/pies.c
@@ -0,0 +1,1581 @@
+/* This file is part of Mailfromd.
+ Copyright (C) 2008, 2009 Sergey Poznyakoff
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "pies.h"
+#include "meta1lex.h"
+
+int log_to_stderr; /* Use stderr for logging */
+char *log_tag; /* override mu_log_tag */
+mu_log_level_t debug_level;
+mu_debug_t pies_debug;
+struct mf_privs pies_privs;
+int foreground;
+int command;
+char *pidfile = STATEDIR "/pies.pid";
+char *ctlfile = STATEDIR "/pies.ctl";
+char *statfile = STATEDIR "/pies.stat";
+mode_t pies_umask = 0;
+unsigned long shutdown_timeout = 5;
+mu_acl_t pies_acl;
+limits_record_t pies_limits;
+int force_option;
+
+
+/* Logging */
+void
+log_setup (int want_stderr)
+{
+ mu_debug_t debug;
+
+ mu_diag_get_debug (&debug);
+
+ if (log_tag)
+ mu_log_tag = log_tag;
+
+ if (!want_stderr)
+ {
+ openlog (MU_LOG_TAG (), LOG_PID, mu_log_facility);
+ mu_debug_set_print (debug, mu_diag_syslog_printer, NULL);
+ mu_debug_default_printer = mu_debug_syslog_printer;
+ }
+ else
+ mu_debug_default_printer = mu_debug_stderr_printer;
+}
+
+static int
+stderr_closed_p()
+{
+ int fd = dup (0);
+ if (fd < 0)
+ return 1;
+ close (fd);
+ return fd <= 2;
+}
+
+
+static int
+_cb_action (mu_debug_t debug, void *data, mu_config_value_t *arg)
+{
+ enum return_action *pact = data;
+ static struct mu_kwd actab[] = {
+ { "disable", action_disable },
+ { "restart", action_restart },
+ { NULL }
+ };
+ int res;
+
+ if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug))
+ return 1;
+ if (mu_kwd_xlat_name (actab, arg->v.string, &res))
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("unknown action code: %s"), arg);
+ return 0;
+ }
+ *pact = res;
+ return 0;
+}
+
+static int
+_cb_notify_addr (mu_debug_t debug, void *data, mu_config_value_t *arg)
+{
+ mu_address_t *paddr = data;
+ mu_address_t addr;
+ int rc;
+
+ if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug))
+ return 1;
+ rc = mu_address_create (&addr, arg->v.string);
+ if (rc)
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("%s: invalid e-mail address: %s"),
+ arg->v.string, mu_strerror (rc));
+ if (*paddr)
+ {
+ mu_address_union (paddr, addr);
+ mu_address_destroy (&addr);
+ }
+ else
+ *paddr = addr;
+ return 0;
+}
+
+struct mu_cfg_param return_code_cfg_param[] = {
+ { "action", mu_cfg_callback, NULL,
+ mu_offsetof (struct component, act_temp.act), _cb_action,
+ N_("Specifies action to take when a component finishes with this "
+ "return code."),
+ /* TRANSLATORS: disable and restart are keywords, do not translate them. */
+ N_("arg: {disable | restart}") },
+ { "notify", mu_cfg_callback, NULL,
+ mu_offsetof (struct component, act_temp.addr), _cb_notify_addr,
+ N_("Notify this address when then component terminates."),
+ N_("arg: email-list") },
+ { "message", mu_cfg_string, NULL,
+ mu_offsetof (struct component, act_temp.message), NULL,
+ N_("Notification message text (with headers).") },
+ { "exec", mu_cfg_string, NULL,
+ mu_offsetof (struct component, act_temp.command), NULL,
+ N_("Execute this command.") },
+ { NULL }
+};
+
+#define S(s) { #s, s }
+static struct mu_kwd ex_kwtab[] = {
+ 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 mu_kwd sig_kwtab[] = {
+ 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,
+ mu_debug_t debug, mu_config_value_t *val, int argc,
+ const char *(*getarg) (mu_config_value_t *, int, mu_debug_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, debug), "*") == 0))
+ allflag = 1;
+ else
+ {
+ for (i = 0; i < argc; i++)
+ {
+ unsigned n;
+ const char *arg = getarg(val, i, debug);
+ size_t len = strlen (arg);
+
+ if (isdigit (arg[0]))
+ {
+ char *p;
+ n = strtoul (arg, &p, 0);
+ if (*p)
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("%s: not a number"), p);
+ continue;
+ }
+ }
+ else if (len > 3 && memcmp (arg, "SIG", 3) == 0)
+ {
+ if (arg[4] == '+')
+ {
+ char *p;
+ n = strtoul (arg + 4, &p, 0);
+ if (*p)
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("%s: not a number"), p);
+ continue;
+ }
+ }
+ else if (mu_kwd_xlat_name_ci (sig_kwtab, arg, &n))
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("%s: not a signal code"), arg);
+ continue;
+ }
+ n |= STATUS_SIG_BIT;
+ }
+ else if (mu_kwd_xlat_name_ci (ex_kwtab, arg, &n))
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("%s: not a return code"), arg);
+ continue;
+ }
+
+ /* 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 (mu_config_value_t *val, int num, mu_debug_t debug)
+{
+ if (num != 0)
+ return NULL;
+ return val->v.string;
+}
+
+const char *
+_get_array_arg (mu_config_value_t *val, int num, mu_debug_t debug)
+{
+ if (num < val->v.arg.c)
+ {
+ if (mu_cfg_assert_value_type (&val->v.arg.v[num], MU_CFG_STRING,
+ debug) == 0)
+ return val->v.arg.v[num].v.string;
+ }
+ return NULL;
+}
+
+const char *
+_get_list_arg (mu_config_value_t *val, int num, mu_debug_t debug)
+{
+ mu_config_value_t *elt;
+ int rc = mu_list_get (val->v.list, num, (void**)&elt);
+ if (rc)
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("cannot get list item: %s"),
+ mu_strerror (rc));
+ }
+ else if (mu_cfg_assert_value_type (elt, MU_CFG_STRING, debug) == 0)
+ return elt->v.string;
+ return NULL;
+}
+
+static int
+return_code_section_parser (enum mu_cfg_section_stage stage,
+ const mu_cfg_node_t *node,
+ const char *section_label, void **section_data,
+ void *call_data,
+ mu_cfg_tree_t *tree)
+{
+ struct component *comp = *section_data;
+ size_t count;
+ struct action *act;
+
+ if (!node->label)
+ {
+ mu_cfg_format_error (tree->debug, MU_DEBUG_ERROR, _("missing tag"));
+ return 1;
+ }
+
+ switch (stage)
+ {
+ case mu_cfg_section_start:
+ switch (node->label->type)
+ {
+ case MU_CFG_STRING:
+ act = create_action (comp, tree->debug, node->label,
+ 1,
+ _get_string_arg);
+ break;
+
+ case MU_CFG_ARRAY:
+ act = create_action (comp, tree->debug, node->label,
+ node->label->v.arg.c,
+ _get_array_arg);
+ break;
+
+ case MU_CFG_LIST:
+ mu_list_count (node->label->v.list, &count);
+ act = create_action (comp, tree->debug, node->label,
+ count,
+ _get_list_arg);
+ }
+
+ if (!act)
+ return 1;
+ memset (&comp->act_temp, 0, sizeof (comp->act_temp));
+ break;
+
+ case mu_cfg_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;
+ }
+ return 0;
+}
+
+void
+return_code_cfg_init ()
+{
+ struct mu_cfg_section *section;
+
+ if (mu_create_canned_section ("return-code", &section))
+ exit (EX_SOFTWARE);
+ section->parser = return_code_section_parser;
+ section->label = N_("<tag: exit-code-list>");
+ mu_cfg_section_add_params (section, return_code_cfg_param);
+}
+
+static int
+_cb_command (mu_debug_t debug, void *data, mu_config_value_t *val)
+{
+ int argc;
+ char **argv, ***pargv = data;
+ int rc;
+
+ switch (val->type)
+ {
+ case MU_CFG_STRING:
+ rc = mu_argcv_get (val->v.string, "", NULL, &argc, &argv);
+ if (rc)
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ "mu_argcv_get: %s", mu_strerror (rc));
+ return 1;
+ }
+ break;
+
+ case MU_CFG_ARRAY:
+ argv = config_array_to_argv (val, debug);
+ break;
+
+ case MU_CFG_LIST:
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("unexpected list"));
+ return 1;
+ }
+ *pargv = argv;
+ return 0;
+}
+
+static int
+_cb_umask (mu_debug_t debug, void *data, mu_config_value_t *arg)
+{
+ mode_t *pmode = data;
+ char *p;
+ unsigned long n;
+
+ if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug))
+ return 1;
+ n = strtoul (arg->v.string, &p, 8);
+ if (*p)
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("invalid octal number"));
+ return 1;
+ }
+ *pmode = n;
+ return 0;
+}
+
+static int
+_cb_env (mu_debug_t debug, void *data, mu_config_value_t *val)
+{
+ int rc;
+ int argc;
+ char **argv;
+ char ***penv = data;
+
+ switch (val->type)
+ {
+ case MU_CFG_STRING:
+ rc = mu_argcv_get_np (val->v.string, strlen (val->v.string), "",
+ NULL, 0, &argc, &argv, NULL);
+ if (rc)
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ "mu_argcv_get: %s", mu_strerror (rc));
+ return 1;
+ }
+ break;
+
+ case MU_CFG_ARRAY:
+ argv = config_array_to_argv (val, debug);
+ break;
+
+ case MU_CFG_LIST:
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("unexpected list"));
+ return 1;
+ }
+
+ *penv = argv;
+ return 0;
+}
+
+static int
+_cb_facility (mu_debug_t debug, void *data, mu_config_value_t *val)
+{
+ if (mu_cfg_assert_value_type (val, MU_CFG_STRING, debug))
+ return 1;
+
+ if (mu_string_to_syslog_facility (val->v.string, data))
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("unknown syslog facility `%s'"),
+ val->v.string);
+ return 1;
+ }
+ return 0;
+}
+
+static int
+_cb_redir (mu_debug_t debug, void *data, mu_config_value_t *arg)
+{
+ struct redirector *rp = data;
+ static struct mu_kwd redirtab[] = {
+ { "null", redir_null },
+ { "syslog", redir_syslog },
+ { "file", redir_file },
+ { NULL }
+ };
+ int res;
+
+ switch (arg->type)
+ {
+ case MU_CFG_STRING:
+ if (strcmp (arg->v.string, "null") == 0)
+ {
+ rp->type = redir_null;
+ break;
+ }
+ rp->type = redir_syslog;
+ if (mu_string_to_syslog_priority (arg->v.string, &rp->v.prio))
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("unknown syslog priority `%s'"),
+ arg);
+ return 0;
+ }
+ break;
+
+ case MU_CFG_ARRAY:
+ if (mu_cfg_assert_value_type (&arg->v.arg.v[0], MU_CFG_STRING, debug))
+ return 0;
+ if (mu_kwd_xlat_name (redirtab, arg->v.arg.v[0].v.string, &res))
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("%s: unrecognised redirector type"),
+ arg->v.arg.v[0].v.string);
+ else
+ {
+ if (res != redir_null)
+ {
+ if (arg->v.arg.c != 2)
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("wrong number of arguments"));
+ return 0;
+ }
+ if (mu_cfg_assert_value_type (&arg->v.arg.v[1], MU_CFG_STRING,
+ debug))
+ return 0;
+
+ switch (res)
+ {
+ case redir_null:
+ break;
+
+ case redir_syslog:
+ if (mu_string_to_syslog_priority (arg->v.arg.v[1].v.string,
+ &rp->v.prio))
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("unknown syslog priority `%s'"),
+ arg->v.arg.v[1].v.string);
+ return 0;
+ }
+ break;
+
+ case redir_file:
+ rp->v.file = xstrdup (arg->v.arg.v[1].v.string);
+ break;
+ }
+ }
+ rp->type = res;
+ }
+ break;
+
+ default:
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("unexpected list"));
+ }
+
+ return 0;
+}
+
+static int
+_cb_url (mu_debug_t debug, void *data, mu_config_value_t *arg)
+{
+ int rc;
+ mu_url_t url;
+
+ if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug))
+ return 1;
+ rc = mu_url_create (&url, arg->v.string);
+ if (rc)
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("%s: cannot create URL: %s"),
+ arg->v.string,
+ mu_strerror (rc));
+ return 0;
+ }
+ rc = mu_url_parse (url);
+ if (rc)
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("%s: cannot parse URL: %s"),
+ arg->v.string,
+ mu_strerror (rc));
+ mu_url_destroy (&url);
+ return 0;
+ }
+ *(mu_url_t*) data = url;
+ return 0;
+}
+
+static struct mu_kwd modetab[] = {
+ { "exec", pies_comp_exec },
+ { "wait", pies_comp_exec },
+ { "accept", pies_comp_accept },
+ { "inetd", pies_comp_inetd },
+ { "nostartaccept", pies_comp_inetd },
+ { "pass-fd", pies_comp_pass_fd },
+ { "pass", pies_comp_pass_fd },
+ { NULL }
+};
+
+static int
+_cb_mode (mu_debug_t debug, void *data, mu_config_value_t *arg)
+{
+ int res;
+
+ if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug))
+ return 1;
+ if (mu_kwd_xlat_name (modetab, arg->v.string, &res))
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("%s: unrecognised mode"),
+ arg->v.string);
+ else
+ *(enum pies_comp_mode *)data = res;
+ return 0;
+}
+
+static int
+_cb_limits (mu_debug_t debug, void *data, mu_config_value_t *arg)
+{
+ limits_record_t *plrec = data;
+ char *p;
+
+ if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug))
+ return 1;
+ if (parse_limits (plrec, (char*) arg->v.string, &p))
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("invalid limit string (near %s)"),
+ p);
+ return 0;
+}
+
+struct mu_cfg_param component_cfg_param[] = {
+ { "mode", mu_cfg_callback, NULL,
+ mu_offsetof (struct component, mode), _cb_mode,
+ N_("Component execution mode."),
+ /* TRANSLATORS: The words between '{' and '}' are keywords, do not
+ translate them. */
+ N_("mode: {exec | wait | accept | inetd | nostartaccept | pass-fd | pass}")
+ },
+ { "program", mu_cfg_string, NULL,
+ mu_offsetof (struct component, program), NULL,
+ N_("Full name of the program.") },
+ { "command", mu_cfg_callback, NULL,
+ mu_offsetof (struct component, argv), _cb_command,
+ N_("Command line.") },
+ { "prerequisites", MU_CFG_LIST_OF(mu_cfg_string), NULL,
+ mu_offsetof (struct component, prereq), NULL,
+ N_("List of prerequisites."),
+ N_("list") },
+ { "dependents", MU_CFG_LIST_OF(mu_cfg_string), NULL,
+ mu_offsetof (struct component, depend), NULL,
+ N_("List of components for which this one is a prerequisite."),
+ N_("list") },
+ { "disable", mu_cfg_bool, NULL,
+ mu_offsetof (struct component, disabled), NULL,
+ N_("Disable this entry.") },
+ { "settle-timeout", mu_cfg_uint, NULL,
+ mu_offsetof (struct component, settle_timeout), NULL,
+ N_("Time to wait before starting this component.") },
+ { "precious", mu_cfg_bool, NULL,
+ mu_offsetof (struct component, precious), NULL,
+ N_("Mark this entry as precious.") },
+ { "socket", mu_cfg_callback, NULL,
+ mu_offsetof (struct component, socket_url), _cb_url,
+ N_("Listen on the given url."),
+ N_("url: string") },
+ { "pass-fd-socket", mu_cfg_string, NULL,
+ mu_offsetof (struct component, pass_fd_socket), NULL,
+ N_("Pass fd through this socket."),
+ N_("name") },
+ { "acl", mu_cfg_section, NULL, mu_offsetof (struct component, acl), NULL,
+ N_("Per-component access control list") },
+ { "remove-file", mu_cfg_string, NULL,
+ mu_offsetof (struct component, rmfile), NULL,
+ N_("Remove file before starting the component."),
+ N_("file") },
+ { "facility", mu_cfg_callback, NULL,
+ mu_offsetof (struct component, facility), _cb_facility,
+ N_("Override default syslog facility for this component."),
+ N_("arg") },
+ { "stdout", mu_cfg_callback, NULL,
+ mu_offsetof (struct component, redir[RETR_OUT]), _cb_redir,
+ N_("Redirect program's standard output to the given file or "
+ "syslog priority."),
+ /* TRANSLATORS: file and syslog are keywords. Do not translate them. */
+ N_("type: {file | syslog}> <channel: string")
+ },
+ { "stderr", mu_cfg_callback, NULL,
+ mu_offsetof (struct component, redir[RETR_ERR]), _cb_redir,
+ N_("Redirect program's standard error to the given file or "
+ "syslog priority."),
+ /* TRANSLATORS: file and syslog are keywords. Do not translate them. */
+ N_("type: {file | syslog}> <channel: string")
+ },
+ { "user", mu_cfg_string, NULL,
+ mu_offsetof (struct component, privs.user), NULL,
+ N_("Run with this user privileges.") },
+ { "group", MU_CFG_LIST_OF(mu_cfg_string), NULL,
+ mu_offsetof (struct component, privs.groups), NULL,
+ N_("Retain supplementary group.") },
+ { "allgroups", mu_cfg_bool, NULL,
+ mu_offsetof (struct component, privs.allgroups), NULL,
+ N_("Retain all supplementary groups of which user is a member.") },
+ { "umask", mu_cfg_callback, NULL,
+ mu_offsetof (struct component, umask), _cb_umask,
+ N_("Force this umask."),
+ N_("arg: number") },
+ { "limits", mu_cfg_callback, NULL,
+ mu_offsetof (struct component, limits), _cb_limits,
+ N_("Set system limits") },
+ { "env", mu_cfg_callback, NULL,
+ mu_offsetof (struct component, env), _cb_env,
+ N_("Set program environment. Argument is a list of assignments "
+ "separated by white space."),
+ N_("arg: list") },
+ { "chdir", mu_cfg_string, NULL,
+ mu_offsetof (struct component, dir), NULL,
+ N_("Change to this directory before executing the component."),
+ N_("dir") },
+ { "return-code", mu_cfg_section },
+ { NULL }
+};
+
+static 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, mu_debug_t debug)
+{
+ int header = 0;
+ int i;
+#define COMPERR(fmt, arg) \
+ do \
+ { \
+ if (!header) \
+ { \
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR, \
+ _("in component %s:"), comp->tag); \
+ header = 1; \
+ } \
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR, fmt, arg); \
+ } \
+ while (0)
+
+ if (!comp->argv)
+ COMPERR ("%s", _("missing command line"));
+ if (comp->pass_fd_socket && comp->mode != pies_comp_pass_fd)
+ COMPERR ("%s", _("pass-fd-socket ignored: wrong mode"));
+ switch (comp->mode)
+ {
+ case pies_comp_exec:
+ if (comp->socket_url)
+ COMPERR ("%s", _("socket ignored: wrong mode"));
+ break;
+
+ case pies_comp_pass_fd:
+ if (!comp->pass_fd_socket)
+ COMPERR ("%s", _("must supply pass-fd-socket in this mode"));
+ else if (comp->pass_fd_socket[0] != '/')
+ {
+ if (comp->dir)
+ {
+ char *p = make_full_name (comp->dir, comp->pass_fd_socket);
+ free (comp->pass_fd_socket);
+ comp->pass_fd_socket = p;
+ }
+ else
+ COMPERR ("%s", _("pass-fd-socket must be an absolute "
+ "file name or chdir must be specified"));
+ }
+ /* Fall through */
+
+ case pies_comp_accept:
+ case pies_comp_inetd:
+ if (!comp->socket_url)
+ {
+ COMPERR ("%s", _("socket must be specified in this mode"));
+ /* FIXME: Memory leak */
+ return 1;
+ }
+ }
+
+ if (comp->mode != pies_comp_exec
+ && comp->redir[RETR_OUT].type != redir_null)
+ {
+ COMPERR ("%s", _("stdout translation invalid in this mode"));
+ comp->redir[RETR_OUT].type = redir_null;
+ }
+
+ for (i = RETR_OUT; i <= RETR_ERR; i++)
+ {
+ if (comp->redir[i].type == redir_file && comp->redir[i].v.file[0] != '/')
+ {
+ if (comp->dir)
+ {
+ char *p = make_full_name (comp->dir, comp->redir[i].v.file);
+ free (comp->redir[i].v.file);
+ comp->redir[i].v.file = p;
+ }
+ else
+ COMPERR (_("%s: must be an absolute "
+ "file name or chdir must be specified"),
+ comp->redir[i].v.file);
+ }
+ }
+
+ return header;
+#undef COMPERR
+}
+
+static int
+component_section_parser (enum mu_cfg_section_stage stage,
+ const mu_cfg_node_t *node,
+ const char *section_label, void **section_data,
+ void *call_data,
+ mu_cfg_tree_t *tree)
+{
+ struct component *comp;
+
+ switch (stage)
+ {
+ case mu_cfg_section_start:
+ if (node->label
+ && mu_cfg_assert_value_type (node->label, MU_CFG_STRING,
+ tree->debug))
+ return 1;
+ comp = progman_lookup_component (node->label->v.string);
+ if (!comp)
+ {
+ comp = xzalloc (sizeof (*comp));
+ comp->facility = mu_log_facility;
+ comp->redir[RETR_OUT].type = comp->redir[RETR_ERR].type = redir_null;
+ comp->tag = node->label ? xstrdup (node->label->v.string) : NULL;
+ }
+ *section_data = comp;
+ break;
+
+ case mu_cfg_section_end:
+ comp = *(struct component **) section_data;
+ if (component_verify (comp, tree->debug) == 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);
+ }
+ }
+ return 0;
+}
+
+void
+component_cfg_init ()
+{
+ struct mu_cfg_section *section;
+
+ if (mu_create_canned_section ("component", &section))
+ exit (EX_SOFTWARE);
+ section->parser = component_section_parser;
+ section->label = N_("<tag: string>");
+ mu_cfg_section_add_params (section, component_cfg_param);
+}
+
+
+
+struct component default_component;
+
+static int
+_cb_debug (mu_debug_t debug, void *data, mu_config_value_t *arg)
+{
+ int rc;
+
+ if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug))
+ return 1;
+ rc = mu_debug_level_from_string (arg->v.string, &debug_level, debug);
+ if (rc)
+ return 0;
+ if (!pies_debug)
+ mu_debug_create (&pies_debug, NULL);
+ mu_debug_set_level (pies_debug, debug_level);
+ return 0;
+}
+
+static int _cm_include_meta1 (mu_debug_t, void *, mu_config_value_t *);
+
+struct mu_cfg_param pies_cfg_param[] = {
+ { "component", mu_cfg_section },
+ { "debug", mu_cfg_callback, NULL, 0, _cb_debug,
+ N_("Set debug verbosity level.") },
+ { "pidfile", mu_cfg_string, &pidfile, 0, NULL,
+ N_("Write PID to this file.") },
+ { "control-file", mu_cfg_string, &ctlfile, 0, NULL,
+ N_("Set location of the control file.") },
+ { "stat-file", mu_cfg_string, &statfile, 0, NULL,
+ N_("Set location of the statistics output file.") },
+ { "user", mu_cfg_string, &pies_privs.user, 0, NULL,
+ N_("Run with this user privileges.") },
+ { "group", MU_CFG_LIST_OF(mu_cfg_string), &pies_privs.groups, 0, NULL,
+ N_("Retain supplementary group.") },
+ { "allgroups", mu_cfg_bool, &pies_privs.allgroups, 0, NULL,
+ N_("Retain all supplementary groups of which user is a member.") },
+ { "umask", mu_cfg_callback, &pies_umask, 0, _cb_umask,
+ N_("Force this umask."),
+ N_("arg: number") },
+ { "limits", mu_cfg_callback, &pies_limits, 0, _cb_limits,
+ N_("Set global system limits.") },
+ { "shutdown-timeout", mu_cfg_uint, &shutdown_timeout, 0, NULL,
+ N_("Wait <n> seconds for all components to shut down."),
+ "n" },
+ { "return-code", mu_cfg_section, &default_component },
+ { "acl", mu_cfg_section, &pies_acl, 0, NULL,
+ N_("Global access control list") },
+ { "include-meta1", mu_cfg_callback, NULL, 0, _cm_include_meta1,
+ N_("Include components from the specified MeTA1 configuration file."),
+ N_("file: string") },
+ { "meta1-queue-dir", mu_cfg_string, &meta1_queue_dir, 0, NULL,
+ N_("Set name of MeTA1 queue directory (default /var/spool/meta1).") },
+ { NULL }
+};
+
+static int
+_cm_include_meta1 (mu_debug_t debug, void *data, mu_config_value_t *arg)
+{
+ int flags = 0;
+
+ if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug))
+ return 1;
+ if (meta1_config_parse (arg->v.string) == 0)
+ {
+ if (mu_cfg_parser_verbose)
+ flags |= MU_PARSE_CONFIG_VERBOSE;
+ if (mu_cfg_parser_verbose > 1)
+ flags |= MU_PARSE_CONFIG_DUMP;
+ mu_cfg_tree_reduce (meta1_parse_tree, mu_program_name, pies_cfg_param,
+ flags, NULL);
+ }
+ return 0;
+}
+
+
+
+const char *program_version = "pies (" PACKAGE_STRING ")";
+const char *package_bugreport = "<" PACKAGE_BUGREPORT ">";
+static char doc[] = N_("pies -- process invocation and execution supervisor");
+static char args_doc[] = "";
+
+static const char *capa[] = {
+ "common",
+ "logging",
+ "mailer",
+ "debug",
+ NULL
+};
+
+enum {
+ OPT_FOREGROUND=256,
+ OPT_LOG_TAG,
+ OPT_SYSLOG,
+ OPT_STDERR,
+ OPT_DUMP_PREREQ,
+ OPT_DUMP_DEPMAP,
+ OPT_FORCE
+};
+
+#define OPT_RESTART 'R'
+#define OPT_RELOAD 'r'
+#define OPT_STATUS 's'
+#define OPT_STOP 'S'
+
+static struct argp_option options[] = {
+#define GRP 0
+ { "foreground", OPT_FOREGROUND, 0, 0, N_("remain in foreground"), GRP+1},
+ { "stderr", OPT_STDERR, NULL, 0, N_("log to stderr"), },
+ { "syslog", OPT_SYSLOG, NULL, 0, N_("log to syslog"), },
+ { "log-tag", OPT_LOG_TAG, N_("STRING"), 0,
+ N_("set the identifier used in syslog messages to STRING"), GRP+1 },
+ { "debug", 'x', N_("LEVEL"), 0,
+ N_("set debug verbosity level"), GRP+1 },
+ { "force", OPT_FORCE, NULL, 0,
+ N_("force startup even if another instance may be running"), GRP+1 },
+#undef GRP
+
+#define GRP 10
+ { "restart-component", OPT_RESTART, NULL, 0,
+ N_("restart components named in the command line"), GRP+1 },
+ { "reload", OPT_RELOAD, NULL, 0,
+ N_("reload the running instance of pies "), GRP+1 },
+ { "hup", 0, NULL, OPTION_ALIAS },
+ { "status", OPT_STATUS, NULL, 0,
+ N_("display info about the running instance "), GRP+1 },
+ { "stop", OPT_STOP, NULL, 0,
+ N_("stop the running instance "), GRP+1 },
+#undef GRP
+
+#define GRP 20
+ { "dump-prereq", OPT_DUMP_PREREQ, NULL, 0,
+ N_("dump prerequisite charts"), GRP+1 },
+ { "dump-depmap", OPT_DUMP_DEPMAP, NULL, 0,
+ N_("dump dependency map"), GRP+1 },
+#undef GRP
+ { NULL }
+};
+
+static error_t
+parse_opt (int key, char *arg, struct argp_state *state)
+{
+ static struct mu_argp_node_list lst;
+
+ switch (key)
+ {
+ case OPT_FOREGROUND:
+ foreground = 1;
+ break;
+
+ case OPT_RELOAD:
+ case OPT_STATUS:
+ case OPT_STOP:
+ case OPT_RESTART:
+ case OPT_DUMP_PREREQ:
+ case OPT_DUMP_DEPMAP:
+ log_to_stderr = 1;
+ command = key;
+ break;
+
+ case OPT_SYSLOG:
+ log_to_stderr = 0;
+ break;
+
+ case OPT_STDERR:
+ log_to_stderr = 1;
+ break;
+
+ case 'x':
+ mu_argp_node_list_new (&lst, "debug", arg);
+ break;
+
+ case OPT_FORCE:
+ force_option = 1;
+ break;
+
+ case OPT_LOG_TAG:
+ log_tag = arg;
+ break;
+
+ case ARGP_KEY_INIT:
+ mu_argp_node_list_init (&lst);
+ break;
+
+ case ARGP_KEY_FINI:
+ mu_argp_node_list_finish (&lst, NULL, NULL);
+ break;
+
+ default:
+ return ARGP_ERR_UNKNOWN;
+ }
+ return 0;
+}
+
+static struct argp argp = {
+ options,
+ parse_opt,
+ args_doc,
+ doc,
+ NULL,
+ NULL,
+ NULL
+};
+
+
+#define ACTION_CONT 0
+#define ACTION_STOP 1
+#define ACTION_RESTART 2
+#define ACTION_COMPRELOAD 3
+#define ACTION_DUMPSTATS 4
+
+int action = ACTION_CONT;
+int children_cleanup = 0;
+int got_alarm = 0;
+
+RETSIGTYPE
+sig_handler (int sig)
+{
+ switch (sig)
+ {
+ case SIGCHLD:
+ children_cleanup = 1;
+ break;
+
+ case SIGTERM:
+ case SIGINT:
+ case SIGQUIT:
+ action = ACTION_STOP;
+ mu_diag_output (MU_DIAG_NOTICE, "received signal %d", sig);
+ break;
+
+ case SIGHUP:
+ mu_diag_output (MU_DIAG_NOTICE, "received signal %d", sig);
+ action = ACTION_RESTART;
+ break;
+
+ case SIGALRM:
+ got_alarm = 1;
+ break;
+
+ case SIGUSR1:
+ action = ACTION_COMPRELOAD;
+ break;
+
+ case SIGUSR2:
+ action = ACTION_DUMPSTATS;
+ break;
+ }
+ signal (sig, sig_handler);
+}
+
+void
+signal_setup (RETSIGTYPE (*sf)(int))
+{
+ signal (SIGCHLD, sf);
+ signal (SIGTERM, sf);
+ signal (SIGQUIT, sf);
+ signal (SIGINT, sf);
+ signal (SIGHUP, sf);
+ signal (SIGALRM, sf);
+ signal (SIGUSR1, sf);
+ signal (SIGUSR2, sf);
+}
+
+
+pid_t
+pidfile_read (int must_exist)
+{
+ int c;
+ pid_t n = 0;
+ FILE *fp = fopen (pidfile, "r");
+ if (!fp)
+ {
+ if (must_exist && errno != ENOENT)
+ mu_diag_output (MU_DIAG_ERR,
+ _("cannot open pid file `%s': %s"),
+ pidfile,
+ mu_strerror (errno));
+ return -1;
+ }
+
+ while ((c = fgetc (fp)) != EOF)
+ {
+ if (isdigit (c))
+ n = n * 10 + c - '0';
+ else if (c == '\n')
+ break;
+ else
+ {
+ mu_diag_output (MU_DIAG_ERR,
+ _("unexpected character %#03o in pidfile `%s'"),
+ c, pidfile);
+ return -1;
+ }
+ }
+ fclose (fp);
+ if (kill (n, 0))
+ {
+ mu_diag_output (MU_DIAG_ERR,
+ _("cannot signal master process %lu: %s"),
+ (unsigned long) n, mu_strerror (errno));
+ if (errno == EPERM)
+ return n; /* be on the safe side */
+ return -1;
+ }
+ return n;
+}
+
+
+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 == -1)
+ 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;
+
+ mu_diag_output (MU_DIAG_INFO, _("stopping components"));
+
+ fp = fopen (ctlfile, "r");
+ if (!fp)
+ {
+ mu_error (_("cannot open control file `%s': %s"),
+ ctlfile, mu_strerror (errno));
+ return;
+ }
+ if (unlink (ctlfile))
+ {
+ mu_error (_("cannot unlink control file `%s': %s"),
+ ctlfile, mu_strerror (errno));
+ fclose (fp);
+ return;
+ }
+
+ while (getline (&buf, &size, fp) > 0)
+ {
+ size_t len = strlen (buf);
+ if (len == 0)
+ continue;
+ if (buf[len-1] == '\n')
+ buf[len-1] = 0;
+ progman_stop_component (buf);
+ }
+
+ free (buf);
+ fclose (fp);
+}
+
+int
+request_restart_components (char **argv)
+{
+ FILE *fp;
+ pid_t pid = pidfile_read (1);
+
+ if (pid == -1)
+ return 1;
+
+ fp = fopen (ctlfile, "w");
+ if (!fp)
+ {
+ mu_error (_("cannot open control file `%s': %s"),
+ ctlfile, mu_strerror (errno));
+ return 1;
+ }
+ for (; *argv; argv++)
+ fprintf (fp, "%s\n", *argv);
+ fclose (fp);
+
+ kill (pid, SIGUSR1);
+ return 0;
+}
+
+
+int
+pies_reload ()
+{
+ pid_t pid = pidfile_read (1);
+
+ if (pid == -1)
+ {
+ mu_diag_output (MU_DIAG_CRIT, _("pies is not running"));
+ return 1;
+ }
+
+ mu_diag_output (MU_DIAG_INFO, _("reloading pies at PID %lu"),
+ (unsigned long) pid);
+ return kill (pid, SIGHUP) ? EX_SOFTWARE : 0;
+}
+
+int
+pies_status ()
+{
+ pid_t pid;
+ FILE *fp;
+
+ if (unlink (statfile) && errno != ENOENT)
+ mu_diag_output (MU_DIAG_ERR, _("cannot unlink statfile `%s': %s"),
+ statfile, mu_strerror (errno));
+ switch (pies_check_status (&pid))
+ {
+ case pies_status_ctr:
+ mu_diag_output (MU_DIAG_INFO, _("pies is not running"));
+ break;
+
+ case pies_status_stale:
+ mu_diag_output (MU_DIAG_INFO,
+ _("pies is not running, but a pidfile "
+ "is found (pid %lu)"),
+ (unsigned long) pid);
+ return 1; /* FIXME: hm? */
+
+ case pies_status_noresp:
+ mu_diag_output (MU_DIAG_INFO,
+ _("pies seems to run with pid %lu, but is not responding"),
+ (unsigned long) pid);
+ if (unlink (statfile))
+ mu_diag_output (MU_DIAG_ERR, _("cannot unlink statfile `%s': %s"),
+ statfile, mu_strerror (errno));
+ return 2;
+
+ case pies_status_running:
+ fp = fopen (statfile, "r");
+ if (!fp)
+ mu_diag_output (MU_DIAG_ERR, _("cannot open statfile `%s': %s"),
+ statfile, mu_strerror (errno));
+ else
+ {
+ char c;
+
+ if (unlink (statfile))
+ mu_diag_output (MU_DIAG_ERR, _("cannot unlink statfile `%s': %s"),
+ statfile, mu_strerror (errno));
+ while ((c = fgetc (fp)) != EOF)
+ fputc (c, stdout);
+ fclose (fp);
+ }
+ }
+ return 0;
+}
+
+int
+pies_stop ()
+{
+ pid_t pid = pidfile_read (1);
+
+ if (pid == -1)
+ {
+ mu_diag_output (MU_DIAG_CRIT, _("pies is not running"));
+ return EX_USAGE;
+ }
+
+ mu_diag_output (MU_DIAG_INFO,
+ _("stopping pies at PID %lu"), (unsigned long) pid);
+ return kill (pid, SIGTERM) ? EX_SOFTWARE : 0;
+}
+
+
+static int
+pies_cfg_verifier ()
+{
+ return progman_build_depmap ();
+}
+
+
+int
+main (int argc, char **argv)
+{
+ int rc;
+ int index;
+ pid_t pid;
+ extern char **environ;
+ extern char *program_invocation_short_name; /* FIXME */
+
+ mf_init_nls ();
+ mf_proctitle_init (argc, argv, environ);
+ if (!program_invocation_short_name)
+ program_invocation_short_name = argv[0];
+
+ /* Set default logging */
+ log_setup (!stderr_closed_p ());
+ mu_acl_cfg_init ();
+ return_code_cfg_init ();
+ component_cfg_init ();
+ mu_app_cfg_verifier = pies_cfg_verifier;
+ mu_argp_init (program_version, package_bugreport);
+ mu_register_all_mailer_formats ();
+ rc = mu_app_init (&argp, capa, pies_cfg_param, argc, argv, 0, &index, NULL);
+ if (rc)
+ exit (EX_CONFIG);
+
+ log_setup (log_to_stderr);
+
+ if (argc != index && command != 'R')
+ {
+ mu_error ("extra command line arguments");
+ exit (EX_CONFIG);
+ }
+
+ progman_build_depmap ();
+ switch (command)
+ {
+ case OPT_RESTART:
+ mf_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:
+ mf_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)
+ {
+ mu_error (_("another pies instance may be running (pid %lu), "
+ "use --force to override"),
+ (unsigned long) pid);
+ exit (EX_USAGE);
+ }
+ break;
+
+ case pies_status_running:
+ mu_error (_("another pies instance already running (pid %lu)"),
+ (unsigned long) pid);
+ exit (EX_USAGE);
+ }
+
+ mu_diag_output (MU_DIAG_INFO, _("%s starting"), program_version);
+
+ if (!foreground && daemon (0, 0) == -1)
+ {
+ mu_error (_("cannot become a daemon: %s"),
+ mu_strerror (errno));
+ exit (EX_SOFTWARE);
+ }
+
+ rc = mu_daemon_create_pidfile (pidfile);
+ if (rc)
+ mu_error (_("cannot create PID file `%s': %s"),
+ pidfile, mu_strerror (rc));
+
+ if (argv[0][0] != '/')
+ mu_diag_output (MU_DIAG_NOTICE,
+ N_("not started as an absolute pathname; "
+ "SIGHUP will not work"));
+
+ signal_setup (sig_handler);
+
+ progman_create_sockets ();
+ progman_start ();
+
+ do
+ {
+ if (!children_cleanup)
+ pies_pause ();
+ switch (action)
+ {
+ case ACTION_COMPRELOAD:
+ stop_components ();
+ progman_cleanup (0);
+ progman_start ();
+ action = ACTION_CONT;
+ break;
+
+ case ACTION_DUMPSTATS:
+ progman_dump_stats (statfile);
+ action = ACTION_CONT;
+ break;
+ }
+ if (action == ACTION_CONT)
+ {
+ if (children_cleanup)
+ {
+ children_cleanup = 0;
+ progman_cleanup (0);
+ }
+ if (got_alarm)
+ {
+ got_alarm = 0;
+ progman_wake_sleeping ();
+ }
+ }
+ }
+ while (action == ACTION_CONT);
+
+ progman_stop ();
+
+ if (action == ACTION_RESTART && argv[0][0] == '/')
+ {
+ int i;
+
+ for (i = getmaxfd (); i > 0; i--)
+ close (i);
+
+ mu_daemon_remove_pidfile ();
+ signal_setup (SIG_DFL);
+
+ execv (argv[0], argv);
+ }
+
+ mu_diag_output (MU_DIAG_INFO, _("%s terminated"), program_version);
+ exit (EX_OK);
+}
+
+void
+xalloc_die ()
+{
+ mu_error ("not enough memory");
+ abort ();
+}
+
+/*
+ Local Variables:
+ c-file-style: "gnu"
+ End:
+*/
+/* EOF */
diff --git a/src/pies.h b/src/pies.h
new file mode 100644
index 0000000..add175c
--- /dev/null
+++ b/src/pies.h
@@ -0,0 +1,205 @@
+/* This file is part of Mailfromd.
+ Copyright (C) 2008, 2009 Sergey Poznyakoff
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <getopt.h>
+#include <errno.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+#include <signal.h>
+#include <sysexits.h>
+
+#include <mailutils/mailutils.h>
+#include <mailutils/daemon.h>
+#include <mailutils/libargp.h>
+#include <mailutils/syslog.h>
+
+#include "inttostr.h"
+#include "c-ctype.h"
+#include "xalloc.h"
+#include "libpies.h"
+
+#define TESTTIME 2*60
+#define SLEEPTIME 5*60
+#define MAXSPAWN 10
+
+#define RETR_OUT 0
+#define RETR_ERR 1
+
+enum redir_type
+{
+ redir_null,
+ redir_syslog,
+ redir_file
+};
+
+struct redirector
+{
+ enum redir_type type;
+ union
+ {
+ int prio;
+ char *file;
+ } v;
+};
+
+typedef struct limits_rec *limits_record_t;
+
+enum return_action
+{
+ action_restart,
+ action_disable,
+};
+
+#define STATUS_SIG_BIT 0x80000000
+#define STATUS_CODE(c) ((c) & ~STATUS_SIG_BIT)
+
+struct action
+{
+ struct action *next;
+ size_t nstat;
+ unsigned *status;
+ enum return_action act; /* Action to take when the component terminates */
+ mu_address_t addr; /* Addresses to notify about it. */
+ char *message; /* Notification mail. */
+ char *command; /* Execute this command */
+};
+
+enum pies_comp_mode
+ {
+ /* Execute the component, no sockets are opened. This is the default
+ Pies mode. */
+ pies_comp_exec,
+ /* Open a socket and start a component with stdin/stdout bound to that
+ socket. Corresponds to MeTA1 notion of `start_action = accept'.
+ */
+ pies_comp_accept,
+ /* Inetd mode: like above, but start the component only when an
+ incoming connection is requested. Corresponds to
+ `start_action = nostartaccept' in MeTA1.
+ */
+ pies_comp_inetd,
+ /* Open a socket, start a component, and pass the socket fd to the
+ component via the UNIX domain socket. Corresponds to
+ `start_action = pass' in MeTA1. */
+ pies_comp_pass_fd
+ };
+
+struct component
+{
+ enum pies_comp_mode mode;
+ char *tag; /* Entry tag (for diagnostics purposes) */
+ char *program; /* Program name */
+ char **argv; /* Program command line */
+ char **env; /* Program environment */
+ char *dir; /* Working directory */
+ mu_list_t prereq; /* Prerequisites */
+ mu_list_t depend; /* Dependency targets */
+ unsigned settle_timeout; /* Time needed for started prerequisites to
+ settle */
+ /* FIXME: disabled and precious can be encoded as bits in mode */
+ int disabled; /* The componenet is disabled */
+ int precious; /* The component is precious (cannot be disabled) */
+ char *rmfile; /* Try to remove this file before starting */
+ struct mf_privs privs; /* UID/GIDS+groups to run under */
+ mode_t umask; /* Umask to install before starting */
+ limits_record_t limits; /* System limits */
+ mu_url_t socket_url; /* Socket to listen on
+ (if mode != pies_comp_exec) */
+ char *pass_fd_socket; /* Socket to pass fd on
+ (if mode == pies_comp_pass_fd) */
+ mu_acl_t acl;
+ /* Retranslators: */
+ int facility; /* Syslog facility. */
+ struct redirector redir[2]; /* Repeaters for stdout and stderr */
+ /* Actions to execute on various exit codes: */
+ struct action *act_head, *act_tail;
+ struct action act_temp; /* Auxiliary object used during configuration */
+};
+
+extern char *syslog_tag;
+extern unsigned long shutdown_timeout;
+extern struct component default_component;
+extern mu_acl_t pies_acl;
+extern mu_debug_t pies_debug;
+extern limits_record_t pies_limits;
+
+void register_prog (struct component *comp);
+size_t progman_running_count (void);
+void progman_start (void);
+void progman_wake_sleeping (void);
+void progman_stop (void);
+void progman_cleanup (int expect_term);
+void progman_stop_component (const char *name);
+void progman_dump_stats (const char *filename);
+void progman_dump_prereq (void);
+void progman_dump_depmap (void);
+int progman_accept (int socket);
+int progman_build_depmap (void);
+void progman_create_sockets (void);
+struct component *progman_lookup_component (const char *tag);
+
+void log_setup (int want_stderr);
+void signal_setup (RETSIGTYPE (*sf)(int));
+
+typedef struct pies_depmap *pies_depmap_t;
+typedef struct pies_depmap_pos *pies_depmap_pos_t;
+enum pies_depmap_direction
+ {
+ depmap_row = 0,
+ depmap_col = !depmap_row
+ };
+
+pies_depmap_t depmap_alloc (unsigned count);
+pies_depmap_t depmap_copy (pies_depmap_t dpm);
+void depmap_set (pies_depmap_t dmap, unsigned row, unsigned col);
+int depmap_isset (pies_depmap_t dmap, unsigned row, unsigned col);
+void depmap_tc (pies_depmap_t dmap);
+unsigned depmap_first (pies_depmap_t dmap, enum pies_depmap_direction dir,
+ unsigned coord, pies_depmap_pos_t *ppos);
+unsigned depmap_next (pies_depmap_t dmap, pies_depmap_pos_t pos);
+
+
+void pies_pause (void);
+int register_listener (int fd);
+int pass_fd (const char *socket, int fd);
+int create_socket (mu_url_t url, const char *user, mode_t umask);
+
+
+int parse_limits (limits_record_t *plrec, char *str, char **endp);
+int set_limits (const char *name, limits_record_t lrec);
+
+
+void meta1_parser_set_debug (void);
+int meta1lex (void);
+int meta1error (char *s);
+int meta1parse (void);
+
diff --git a/src/pies.rcin b/src/pies.rcin
new file mode 100644
index 0000000..85a2504
--- /dev/null
+++ b/src/pies.rcin
@@ -0,0 +1,31 @@
+# Sample pies configuration for running pmult and mcp
+
+# Special handling for exit codes that mean the program was used incorrectly
+# or misconfigured.
+return-code (EX_USAGE, EX_CONFIG) {
+ action disable;
+ notify "root";
+ message
+ "From: Pies <>\n"
+ "X-Agent: ${canonical-program-name} (${package} ${version})\n"
+ "Subject: Component ${component} disabled.\n"
+ "\n"
+ "Component \"${component}\" has terminated with code ${retcode},\n"
+ "which means it encountered some configuration problem.\n"
+ "I will not restart it automatically. Please fix its configuration\n"
+ "and restart it manually at your earliest convenience.\n"
+ "\n"
+ "To restart, run `pies --start ${component}'\n"
+ "---\n"
+ "Wuff-wuff,\nPies";
+}
+
+component pmult {
+ command "SBINDIR/pmult";
+ user meta1s;
+ stderr info;
+ stdout info;
+}
+
+include-meta1 "/etc/meta1/meta1.conf";
+
diff --git a/src/progman.c b/src/progman.c
new file mode 100644
index 0000000..9383aae
--- /dev/null
+++ b/src/progman.c
@@ -0,0 +1,1741 @@
+/* This file is part of Mailfromd.
+ Copyright (C) 2007, 2008, 2009 Sergey Poznyakoff
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 3 of the License, or (at your
+ option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "pies.h"
+
+#define TYPE_COMPONENT 0
+#define TYPE_RETR 1
+
+enum prog_status
+ {
+ status_enabled, /* Component enabled. prog->pid!=0 shows if it is
+ actually running */
+ status_disabled, /* Component is disabled. */
+ status_listener, /* Component is an inetd listener */
+ status_sleeping, /* Component is sleeping. An attempt to start it will
+ be made at prog->v.p.timestamp + SLEEPTIME */
+ status_stopping, /* Component is being stopped */
+ };
+
+struct prog
+{
+ struct prog *next, *prev;
+ int type;
+ pid_t pid; /* PID */
+ char *tag; /* Entry tag (for diagnostics purposes) */
+ int idx; /* Numeric identifier */
+ char **prereq;
+ int facility;
+ union
+ {
+ struct
+ {
+ struct component *comp;
+ int argc; /* Number of elements in comp->argv */
+ int socket;
+ struct prog *redir[2]; /* Pointers to redirectors */
+ time_t timestamp; /* Time of last startup */
+ size_t count; /* Number of failed starts since timestamp */
+ enum prog_status status; /* Current component status */
+ } p;
+
+ struct
+ {
+ struct prog *master;
+ } r;
+ } v;
+};
+
+#define IS_PROG(p) ((p)->type == TYPE_COMPONENT)
+
+static int numprog;
+static struct prog *proghead, *progtail;
+static pies_depmap_t depmap;
+static int recompute_alarm;
+
+static struct prog *
+prog_lookup_by_pid (pid_t pid)
+{
+ struct prog *prog;
+
+ for (prog = proghead; prog; prog = prog->next)
+ if (prog->pid == pid)
+ break;
+ return prog;
+}
+
+static struct prog *
+prog_lookup_by_socket (int fd)
+{
+ struct prog *prog;
+
+ for (prog = proghead; prog; prog = prog->next)
+ if (IS_PROG (prog) && prog->v.p.socket == fd)
+ break;
+ return prog;
+}
+
+struct prog *
+prog_lookup_by_tag (const char *tag)
+{
+ struct prog *prog;
+ for (prog = proghead; prog; prog = prog->next)
+ if (strcmp (prog->tag, tag) == 0)
+ break;
+ return prog;
+}
+
+struct component *
+progman_lookup_component (const char *tag)
+{
+ struct prog *prog;
+ for (prog = proghead; prog; prog = prog->next)
+ if (IS_PROG (prog) && strcmp (prog->tag, tag) == 0)
+ return prog->v.p.comp;
+ return NULL;
+}
+
+struct prog *
+prog_lookup_by_idx (unsigned idx)
+{
+ struct prog *prog;
+ for (prog = proghead; prog; prog = prog->next)
+ if (prog->idx == idx)
+ break;
+ return prog;
+}
+
+static void prog_stop (struct prog *prog, int sig);
+static int prog_start_prerequisites (struct prog *prog);
+
+void
+link_prog (struct prog *pp, int prepend)
+{
+ if (prepend)
+ {
+ if (proghead)
+ proghead->prev = pp;
+ pp->prev = NULL;
+ pp->next = proghead;
+ proghead = pp;
+ if (!progtail)
+ progtail = pp;
+ }
+ else
+ {
+ pp->next = NULL;
+ pp->prev = progtail;
+ if (progtail)
+ progtail->next = pp;
+ else
+ proghead = pp;
+ progtail = pp;
+ }
+}
+
+void
+unlink_prog (struct prog *pp)
+{
+ struct prog *x;
+ if (x = pp->prev)
+ x->next = pp->next;
+ else
+ proghead = pp->next;
+ if (x = pp->next)
+ x->prev = pp->prev;
+ else
+ progtail = pp->prev;
+}
+
+void
+destroy_prog (struct prog **pp)
+{
+ unlink_prog (*pp);
+ free (*pp);
+ *pp = NULL;
+}
+
+static char *
+redir_tag (struct prog *master, int type)
+{
+ static char *redirstr[2] = { "stdout", "stderr" };
+ char *str = NULL;
+ if (type < MU_ARRAY_SIZE(redirstr))
+ asprintf (&str, "%s/%s", master->tag, redirstr[type]);
+ else
+ asprintf (&str, "%s/%d", master->tag, type);
+ if (!str)
+ xalloc_die ();
+ return str;
+}
+
+static struct prog *
+register_redir (int type, struct prog *master)
+{
+ char *tag = redir_tag (master, type);
+ char *pstr;
+ struct prog *pp = xzalloc (sizeof (*pp) + strlen (tag) + 1 +
+ 2 * sizeof (char**));
+
+ pp->type = TYPE_RETR;
+ pstr = (char *) (pp + 1);
+ pp->tag = pstr;
+ strcpy (pp->tag, tag);
+ pstr += strlen (pp->tag) + 1;
+ free (tag);
+ pp->v.r.master = master;
+ pp->idx = -1; /* Retranslators are not indexed */
+ pp->prereq = (char**)pstr;
+ pp->prereq[0] = master->tag;
+ pp->prereq[1] = NULL;
+ link_prog (pp, 1);
+ return pp;
+}
+
+void
+update_redir (int type, struct prog *master, pid_t pid)
+{
+ struct prog *pp;
+ if (master->v.p.redir[type] == NULL)
+ {
+ pp = register_redir (type, master);
+ master->v.p.redir[type] = pp;
+ }
+ else
+ {
+ pp = master->v.p.redir[type];
+ prog_stop (pp, SIGKILL); /* Just in case */
+ }
+ pp->pid = pid;
+}
+
+static struct prog *
+register_prog0 (struct component *comp, int index)
+{
+ struct prog *newp;
+ int i;
+
+ newp = xzalloc (sizeof (*newp));
+ newp->type = TYPE_COMPONENT;
+ newp->tag = comp->tag;
+ newp->pid = 0;
+ newp->idx = index;
+ newp->facility = comp->facility;
+ newp->v.p.comp = comp;
+ for (i = 0; comp->argv[i]; i++)
+ ;
+ newp->v.p.argc = i;
+ newp->v.p.socket = -1;
+ if (comp->disabled)
+ newp->v.p.status = status_disabled;
+ else if (comp->mode == pies_comp_inetd)
+ newp->v.p.status = status_listener;
+
+ if (comp->mode != pies_comp_exec)
+ comp->redir[RETR_OUT].type = redir_null;
+
+ link_prog (newp, 0);
+ return newp;
+}
+
+void
+prog_rebuild_prerequisites (struct prog *prog)
+{
+ struct component *comp = prog->v.p.comp;
+ struct prog *p;
+ int dep_all = 0;
+ size_t depc = 0;
+
+ if (comp->prereq)
+ {
+ mu_list_count (comp->prereq, &depc);
+ if (depc == 1)
+ {
+ char *item;
+ mu_list_get (comp->prereq, 0, (void**)&item);
+ if (strcmp (item, "all") == 0)
+ {
+ dep_all = 1;
+ for (p = proghead; p; p = p->next)
+ if (p->type == TYPE_COMPONENT)
+ depc++;
+ }
+ else if (strcmp (item, "none") == 0)
+ mu_list_destroy (&comp->prereq);
+ }
+ }
+
+ if (depc == 0)
+ return;
+
+ prog->prereq = xcalloc (depc + 1, sizeof (prog->prereq[0]));
+
+ depc = 0;
+ if (comp->prereq)
+ {
+ if (dep_all)
+ {
+ for (p = proghead; p; p = p->next)
+ if (p->type == TYPE_COMPONENT)
+ prog->prereq[depc++] = p->tag;
+ }
+ else
+ {
+ mu_iterator_t itr = NULL;
+ mu_list_get_iterator (comp->prereq, &itr);
+ for (mu_iterator_first (itr), depc = 0;
+ !mu_iterator_is_done (itr); mu_iterator_next (itr), depc++)
+ {
+ char *str;
+ mu_iterator_current (itr, (void**)&str);
+ prog->prereq[depc] = str;
+ }
+ mu_iterator_destroy (&itr);
+ }
+ }
+ prog->prereq[depc] = NULL;
+}
+
+void
+register_prog (struct component *comp)
+{
+ register_prog0 (comp, numprog++);
+}
+
+size_t
+progman_running_count ()
+{
+ size_t size = 0;
+ struct prog *prog;
+
+ for (prog = proghead; prog; prog = prog->next)
+ if (prog->pid > 0)
+ size++;
+ return size;
+}
+
+RETSIGTYPE
+redir_exit (int sig)
+{
+ _exit (0);
+}
+
+int
+redirect_to_file (struct prog *master, int stream)
+{
+ struct passwd *pw;
+ int fd = open (master->v.p.comp->redir[stream].v.file, O_RDWR|O_CREAT,
+ 0644 & ~master->v.p.comp->umask);
+ if (fd == -1)
+ {
+ mu_error (_("cannot open output file %s: %s"),
+ master->v.p.comp->redir[stream].v.file,
+ mu_strerror (errno));
+ return -1;
+ }
+ /* Fix file ownership */
+ pw = getpwnam (master->v.p.comp->privs.user);
+ if (pw)
+ chown (master->v.p.comp->redir[stream].v.file, pw->pw_uid, pw->pw_gid);
+ return fd;
+}
+
+int
+open_redirector (struct prog *master, int stream)
+{
+ int p[2];
+ FILE *fp;
+ char *buf = NULL;
+ size_t size = 0;
+ pid_t pid;
+ int i, prio;
+ char *tag;
+
+ switch (master->v.p.comp->redir[stream].type)
+ {
+ case redir_null:
+ return -1;
+
+ case redir_file:
+ return redirect_to_file (master, stream);
+
+ case redir_syslog:
+ break;
+ }
+
+ pipe (p);
+ switch (pid = fork ())
+ {
+ case 0:
+ /* Redirector process */
+ tag = redir_tag (master, stream);
+ mf_proctitle_format ("%s redirector", tag);
+ free (tag);
+
+ for (i = getmaxfd (); i >= 0; i--)
+ {
+ if (i != p[0])
+ close (i);
+ }
+
+ log_setup (0);
+ signal_setup (redir_exit);
+
+ close (p[1]);
+ fp = fdopen (p[0], "r");
+ if (fp == NULL)
+ _exit (1);
+ openlog (master->tag, LOG_PID, master->facility);
+ prio = master->v.p.comp->redir[stream].v.prio;
+ while (getline (&buf, &size, fp) > 0)
+ syslog (prio, "%s", buf);
+ _exit (0);
+
+ case -1:
+ mu_diag_output (MU_DIAG_CRIT,
+ _("cannot run redirector `%s': fork failed: %s"),
+ master->tag, mu_strerror (errno));
+ return -1;
+
+ default:
+ update_redir (stream, master, pid);
+ close (p[0]);
+ return p[1];
+ }
+}
+
+extern char **environ;
+
+static char *
+find_env (const char *name, int val)
+{
+ int nlen = strcspn (name, "+=");
+ int i;
+
+ for (i = 0; environ[i]; i++)
+ {
+ size_t elen = strcspn (environ[i], "=");
+ if (elen == nlen && memcmp (name, environ[i], nlen) == 0)
+ return val ? environ[i] + elen + 1 : environ[i];
+ }
+ return NULL;
+}
+
+static int
+locate_unset (char **env, const char *name)
+{
+ int i;
+ int nlen = strcspn (name, "=");
+
+ for (i = 0; env[i]; i++)
+ {
+ if (env[i][0] == '-')
+ {
+ size_t elen = strcspn (env[i] + 1, "=");
+ if (elen == nlen && memcmp (name, env[i] + 1, nlen) == 0)
+ {
+ if (env[i][nlen + 1])
+ return strcmp (name + nlen, env[i] + 1 + nlen) == 0;
+ else
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+static char *
+env_concat (const char *name, size_t namelen, const char *a, const char *b)
+{
+ char *res;
+ size_t len;
+
+ if (a && b)
+ {
+ res = xmalloc (namelen + 1 + strlen (a) + strlen (b) + 1);
+ strcpy (res + namelen + 1, a);
+ strcat (res, b);
+ }
+ else if (a)
+ {
+ len = strlen (a);
+ if (c_ispunct (a[len-1]))
+ len--;
+ res = xmalloc (namelen + 1 + len + 1);
+ memcpy (res + namelen + 1, a, len);
+ res[namelen + 1 + len] = 0;
+ }
+ else /* if (a == NULL) */
+ {
+ if (c_ispunct (b[0]))
+ b++;
+ res = xmalloc (namelen + 1 + len + 1);
+ strcpy (res + namelen + 1, b);
+ }
+ memcpy (res, name, namelen);
+ res[namelen] = '=';
+ return res;
+}
+
+static char **
+env_setup (char **env)
+{
+ char **old_env = environ;
+ char **new_env;
+ int count, i, n;
+
+ if (!env)
+ return old_env;
+
+ if (strcmp (env[0], "-") == 0)
+ {
+ old_env = NULL;
+ env++;
+ }
+
+ /* Count new environment size */
+ count = 0;
+ if (old_env)
+ for (i = 0; old_env[i]; i++)
+ count++;
+
+ for (i = 0; env[i]; i++)
+ count++;
+
+ /* Allocate the new environment. */
+ new_env = xcalloc (count + 1, sizeof new_env[0]);
+
+ /* Populate the environment. */
+ n = 0;
+
+ if (old_env)
+ for (i = 0; old_env[i]; i++)
+ {
+ if (!locate_unset (env, old_env[i]))
+ new_env[n++] = old_env[i];
+ }
+
+ for (i = 0; env[i]; i++)
+ {
+ char *p;
+
+ if (env[i][0] == '-')
+ {
+ /* Skip unset directives. */
+ continue;
+ }
+ if ((p = strchr (env[i], '=')))
+ {
+ if (p == env[i])
+ continue; /* Ignore erroneous entry */
+ if (p[-1] == '+')
+ new_env[n++] = env_concat (env[i], p - env[i] - 1,
+ find_env(env[i], 1), p + 1);
+ else if (p[1] == '+')
+ new_env[n++] = env_concat (env[i], p - env[i],
+ p + 2, find_env(env[i], 1));
+ else
+ new_env[n++] = env[i];
+ }
+ else
+ {
+ p = find_env (env[i], 0);
+ if (p)
+ new_env[n++] = p;
+ }
+ }
+ new_env[n] = NULL;
+ return new_env;
+}
+
+static void
+prog_start (struct prog *prog)
+{
+ int i;
+ pid_t pid;
+ time_t now;
+ int redir[2];
+
+ if (prog->pid > 0 || !IS_PROG (prog))
+ return;
+
+ /* This call returns 1 in two cases: Either prog is marked as disabled,
+ in which case there's nothing more to do, or one or more of its
+ prerequisites are in status_stopping. In the latter case either the
+ components in question will exit or a SIGALRM will get delivered. In
+ both cases, it will cause further processing of `prog'. */
+ if (prog_start_prerequisites (prog))
+ return;
+
+ time (&now);
+
+ if (prog->v.p.timestamp + TESTTIME > now)
+ prog->v.p.count++;
+ else
+ {
+ prog->v.p.count = 0;
+ prog->v.p.timestamp = now;
+ }
+
+ if (prog->v.p.count > MAXSPAWN)
+ {
+ mu_error (ngettext (
+ "%s is respawning too fast, disabled for %d minute",
+ "%s is respawning too fast, disabled for %d minutes",
+ SLEEPTIME / 60),
+ prog->tag, SLEEPTIME / 60);
+ prog->v.p.timestamp = now;
+ prog->v.p.status = status_sleeping;
+ recompute_alarm = 1;
+ return;
+ }
+
+ switch (prog->v.p.comp->mode)
+ {
+ case pies_comp_exec:
+ break;
+
+ case pies_comp_pass_fd:
+ MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE1, _("unlinking %s\n"),
+ prog->v.p.comp->pass_fd_socket);
+ if (unlink (prog->v.p.comp->pass_fd_socket) && errno != ENOENT)
+ {
+ mu_error (_("cannot unlink %s: %s"), prog->v.p.comp->pass_fd_socket,
+ mu_strerror (errno));
+ return;
+ }
+ /* fall through */
+
+ case pies_comp_accept:
+ prog->v.p.socket = create_socket (prog->v.p.comp->socket_url,
+ prog->v.p.comp->privs.user,
+ prog->v.p.comp->umask);
+ if (prog->v.p.socket == -1)
+ {
+ prog->v.p.status = status_disabled;
+ return;
+ }
+ if (listen (prog->v.p.socket, 8))
+ {
+ mu_error ("listen: %s", mu_strerror (errno));
+ close (prog->v.p.socket);
+ prog->v.p.socket = -1;
+ prog->v.p.status = status_disabled;
+ return;
+ }
+ break;
+
+ case pies_comp_inetd:
+ /* Wait until an incoming connection is requested */
+ if (prog->v.p.socket == -1)
+ return;
+ }
+
+ MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE1, _("starting %s\n"), prog->tag);
+
+ if (prog->v.p.comp->rmfile)
+ {
+ MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE1, _("unlinking %s\n"),
+ prog->v.p.comp->rmfile);
+ if (unlink (prog->v.p.comp->rmfile) && errno != ENOENT)
+ mu_error (_("%s: cannot remove file `%s': %s"),
+ prog->tag, prog->v.p.comp->rmfile, mu_strerror (errno));
+ }
+
+ redir[RETR_OUT] = open_redirector (prog, RETR_OUT);
+ redir[RETR_ERR] = open_redirector (prog, RETR_ERR);
+
+ switch (pid = fork ())
+ {
+ /* The child branch. */
+ case 0:
+ signal_setup (SIG_DFL);
+ if (prog->v.p.comp->dir)
+ {
+ MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE1, _("chdir %s\n"),
+ prog->v.p.comp->dir);
+ if (chdir (prog->v.p.comp->dir))
+ mu_error (_("%s: cannot change to directory %s: %s"),
+ prog->tag, prog->v.p.comp->dir, mu_strerror (errno));
+ }
+
+ environ = env_setup (prog->v.p.comp->env);
+ if (mu_debug_check_level (pies_debug, MU_DEBUG_TRACE4))
+ {
+ int i;
+ for (i = 0; environ[i]; i++)
+ __MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE4, "%s ", environ[i]);
+ mu_debug_printf (pies_debug, MU_DEBUG_TRACE4, "\n");
+ }
+ mf_priv_setup (&prog->v.p.comp->privs);
+ if (prog->v.p.comp->umask)
+ umask (prog->v.p.comp->umask);
+
+ set_limits (prog->tag,
+ prog->v.p.comp->limits ?
+ prog->v.p.comp->limits : pies_limits);
+
+ if (mu_debug_check_level (pies_debug, MU_DEBUG_TRACE1))
+ {
+ char *cmdline;
+ if (mu_argcv_string (prog->v.p.argc, prog->v.p.comp->argv,
+ &cmdline) == 0)
+ {
+ __MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE1,
+ "executing %s\n", cmdline);
+ free (cmdline);
+ }
+ }
+
+ switch (prog->v.p.comp->mode)
+ {
+ case pies_comp_pass_fd:
+ case pies_comp_exec:
+ if (redir[RETR_OUT] == -1)
+ {
+ close (1);
+ open ("/dev/null", O_WRONLY);
+ }
+ else if (redir[RETR_OUT] != 1)
+ {
+ dup2 (redir[RETR_OUT], 1);
+ }
+ break;
+
+ case pies_comp_accept:
+ case pies_comp_inetd:
+ dup2 (prog->v.p.socket, 0);
+ dup2 (prog->v.p.socket, 1);
+ close (prog->v.p.socket);
+ prog->v.p.socket = -1;
+ break;
+ }
+
+ if (redir[RETR_ERR] == -1)
+ {
+ close (2);
+ open ("/dev/null", O_WRONLY);
+ }
+ else if (redir[RETR_ERR] != 1)
+ {
+ dup2 (redir[RETR_ERR], 2);
+ }
+
+ /* Close unneded descripitors */
+ for (i = getmaxfd (); i > 2; i--)
+ {
+ if (prog->v.p.comp->mode == pies_comp_pass_fd
+ && i == prog->v.p.socket)
+ continue;
+ close (i);
+ }
+
+ execvp (prog->v.p.comp->program ?
+ prog->v.p.comp->program : prog->v.p.comp->argv[0],
+ prog->v.p.comp->argv);
+ openlog (MU_LOG_TAG (), LOG_PID, mu_log_facility);
+ syslog (LOG_CRIT, _("cannot start `%s': %s"), prog->tag,
+ mu_strerror (errno));
+ _exit (EX_SOFTWARE);
+
+ case -1:
+ mu_diag_output (MU_DIAG_CRIT,
+ _("cannot run `%s': fork failed: %s"),
+ prog->tag, mu_strerror (errno));
+ break;
+
+ default:
+ if (prog->v.p.comp->mode == pies_comp_pass_fd)
+ {
+ sleep(1);
+ pass_fd (prog->v.p.comp->pass_fd_socket, prog->v.p.socket);
+ /* FIXME: Error code */;
+ }
+ if (prog->v.p.comp->mode != pies_comp_exec)
+ close (prog->v.p.socket);
+ prog->pid = pid;
+ prog->v.p.status = status_enabled;
+ }
+}
+
+int
+pies_check_acl (mu_acl_t acl, struct sockaddr *s, int salen)
+{
+ mu_acl_result_t res;
+ int rc;
+
+ if (!acl)
+ return 0;
+
+ rc = mu_acl_check_sockaddr (acl, s, salen, &res);
+ if (rc)
+ {
+ char *p = mu_sockaddr_to_astr (s, salen);
+ mu_error (_("access from %s blocked: cannot check ACLs: %s"),
+ p, mu_strerror (rc));
+ free (p);
+ return 1;
+ }
+
+ switch (res)
+ {
+ case mu_acl_result_undefined:
+ {
+ char *p = mu_sockaddr_to_astr (s, salen);
+ mu_diag_output (MU_DIAG_INFO,
+ _("%s: undefined ACL result; access allowed"),
+ p);
+ free (p);
+ }
+ break;
+
+ case mu_acl_result_accept:
+ break;
+
+ case mu_acl_result_deny:
+ {
+ char *p = mu_sockaddr_to_astr (s, salen);
+ mu_error (_("Access from %s blocked."), p);
+ free (p);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int
+progman_accept (int socket)
+{
+ int fd;
+ struct prog *p;
+ union
+ {
+ struct sockaddr s_in;
+ struct sockaddr s_un;
+ } addr;
+ socklen_t addrlen = sizeof addr;
+
+ fd = accept (socket, (struct sockaddr*) &addr, &addrlen);
+ if (fd == -1)
+ {
+ mu_error (_("accept failed: %s"), mu_strerror (errno));
+ return 1;
+ }
+
+ p = prog_lookup_by_socket (socket);
+ if (!p)
+ {
+ mu_error (_("INTERNAL ERROR: no matching prog for fd %d"), socket);
+ close (fd);
+ return 1;
+ }
+
+ if (mu_debug_check_level (pies_debug, MU_DEBUG_TRACE1))
+ {
+ char *s = mu_sockaddr_to_astr ((struct sockaddr *)&addr, addrlen);
+ __MU_DEBUG2 (pies_debug, MU_DEBUG_TRACE1, "%s wants %s\n", s, p->tag);
+ free (s);
+ }
+
+ if (pies_check_acl (p->v.p.comp->acl, (struct sockaddr *)&addr, addrlen)
+ || pies_check_acl (pies_acl, (struct sockaddr *)&addr, addrlen))
+ {
+ close (fd);
+ return 1;
+ }
+
+ p = register_prog0 (p->v.p.comp, -1);
+ p->v.p.socket = fd;
+ prog_start (p);
+ close (fd);
+ p->v.p.socket = -1;
+ return 0;
+}
+
+
+void
+component_fixup_depend (struct component *comp)
+{
+ mu_iterator_t itr;
+
+ if (comp->depend == NULL)
+ return;
+
+ mu_list_get_iterator (comp->depend, &itr);
+ for (mu_iterator_first (itr);
+ !mu_iterator_is_done (itr); mu_iterator_next (itr))
+ {
+ char *tag;
+ struct component *tgt;
+
+ mu_iterator_current (itr, (void**)&tag);
+ tgt = progman_lookup_component (tag);
+ if (!tgt)
+ {
+ mu_error (_("component %s declares dependency target %s, "
+ "which is not declared"),
+ comp->tag, tag);
+ continue;
+ }
+ if (!tgt->prereq)
+ {
+ int rc = mu_list_create (&tgt->prereq);
+ if (rc)
+ {
+ mu_error (_("cannot create list: %s"), mu_strerror (rc));
+ continue;
+ }
+ }
+ /* FIXME: memory allocation */
+ mu_list_append (tgt->prereq, xstrdup (comp->tag));
+ }
+ mu_list_destroy (&comp->depend);
+}
+
+void
+fixup_prerequisites ()
+{
+ struct prog *prog;
+
+ for (prog = proghead; prog; prog = prog->next)
+ if (IS_PROG (prog))
+ component_fixup_depend (prog->v.p.comp);
+}
+
+void
+rebuild_prerequisites ()
+{
+ struct prog *prog;
+ for (prog = proghead; prog; prog = prog->next)
+ if (IS_PROG (prog))
+ prog_rebuild_prerequisites (prog);
+}
+
+void
+print_dep (struct prog *prog)
+{
+ pies_depmap_pos_t pos;
+ unsigned n;
+
+ mu_diag_printf (MU_DIAG_NOTICE, "%s -> ", prog->tag);
+ for (n = depmap_first (depmap, depmap_col, prog->idx, &pos);
+ n != (unsigned)-1;
+ n = depmap_next (depmap, pos))
+ {
+ struct prog *dp = prog_lookup_by_idx (n);
+ mu_diag_printf (MU_DIAG_NOTICE, "%s -> ", dp->tag);
+ }
+ mu_diag_printf (MU_DIAG_NOTICE, "%s\n", prog->tag);
+}
+
+void
+progman_dump_prereq ()
+{
+ struct prog *prog;
+ for (prog = proghead; prog; prog = prog->next)
+ if (prog->prereq)
+ {
+ int i;
+ printf ("%s:", prog->tag);
+ for (i = 0; prog->prereq[i]; i++)
+ printf (" %s", prog->prereq[i]);
+ printf ("\n");
+ }
+}
+
+void
+progman_dump_depmap ()
+{
+ struct prog *prog;
+ unsigned i, j;
+
+ printf ("%s:\n", _("Dependency map"));
+ printf (" ");
+ for (i = 0; i < numprog; i++)
+ printf (" %2d", i);
+ printf ("\n");
+ for (i = 0; i < numprog; i++)
+ {
+ printf ("%2d ", i);
+ for (j = 0; j < numprog; j++)
+ printf (" %c ", depmap_isset (depmap, i, j) ? 'X' : ' ');
+ printf ("\n");
+ }
+ printf ("\n%s:\n", _("Legend"));
+ for (i = 0; i < numprog; i++)
+ {
+ prog = prog_lookup_by_idx (i);
+ if (prog)
+ printf ("%2d: %s\n", i, prog->tag);
+ }
+}
+
+int
+progman_build_depmap ()
+{
+ int rc = 0;
+ unsigned i;
+ struct prog *prog;
+ pies_depmap_t dp;
+
+ fixup_prerequisites ();
+ rebuild_prerequisites ();
+ depmap = depmap_alloc (numprog);
+ for (prog = proghead; prog; prog = prog->next)
+ if (prog->prereq)
+ {
+ for (i = 0; prog->prereq[i]; i++)
+ {
+ struct prog *dep = prog_lookup_by_tag (prog->prereq[i]);
+ if (!dep)
+ {
+ prog->v.p.status = status_disabled;
+ mu_error (_("component %s depends on %s, "
+ "which is not declared"),
+ prog->tag, prog->prereq[i]);
+ rc++;
+ }
+ else
+ depmap_set (depmap, prog->idx, dep->idx);
+ }
+ }
+ dp = depmap_copy (depmap);
+ depmap_tc (dp);
+ for (i = 0; i < numprog; i++)
+ if (depmap_isset (dp, i, i))
+ {
+ prog = prog_lookup_by_idx (i);
+ mu_error (_("component %s depends on itself"), prog->tag);
+ print_dep (prog);
+ prog->v.p.status = status_disabled;
+ rc++;
+ }
+ free (dp);
+ return rc;
+}
+
+
+void
+progman_create_sockets ()
+{
+ struct prog *prog;
+
+ for (prog = proghead; prog; prog = prog->next)
+ {
+ if (IS_PROG (prog))
+ {
+ struct component *comp = prog->v.p.comp;
+ if (comp->mode == pies_comp_inetd)
+ {
+ int fd = create_socket (comp->socket_url,
+ comp->privs.user, comp->umask);
+ if (fd == -1)
+ prog->v.p.status = status_disabled;
+ else if (register_listener (fd))
+ {
+ close (fd);
+ prog->v.p.status = status_disabled;
+ }
+ else
+ prog->v.p.socket = fd;
+ }
+ }
+ }
+}
+
+
+void
+progman_recompute_alarm ()
+{
+ struct prog *prog;
+ time_t now = time (NULL);
+ time_t alarm_time = 0, x;
+
+ recompute_alarm = 0;
+ MU_DEBUG (pies_debug, MU_DEBUG_TRACE2, "Recomputing alarm settings\n");
+ for (prog = proghead; prog; prog = prog->next)
+ if (IS_PROG (prog))
+ {
+ switch (prog->v.p.status)
+ {
+ case status_sleeping:
+ x = SLEEPTIME - (now - prog->v.p.timestamp);
+ if (alarm_time == 0 || x < alarm_time)
+ alarm_time = x;
+ break;
+
+ case status_stopping:
+ x = shutdown_timeout - (now - prog->v.p.timestamp);
+ if (alarm_time == 0 || x < alarm_time)
+ alarm_time = x;
+ break;
+
+ default:
+ break;
+ }
+ }
+ MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE2, "alarm=%lu\n",
+ (unsigned long)alarm_time);
+ if (alarm_time)
+ alarm (alarm_time);
+}
+
+
+
+void
+progman_start ()
+{
+ struct prog *prog;
+
+ recompute_alarm = 0;
+ MU_DEBUG (pies_debug, MU_DEBUG_TRACE1, "Starting components\n");
+ for (prog = proghead; prog; prog = prog->next)
+ if (IS_PROG (prog)
+ && ((prog->v.p.status == status_enabled && prog->pid == 0)
+ || prog->v.p.status == status_sleeping))
+ prog_start (prog);
+}
+
+static void
+check_stopping (struct prog *prog, time_t now)
+{
+ if (now - prog->v.p.timestamp >= shutdown_timeout)
+ {
+ if (prog->pid == 0)
+ mu_error (_("INTERNAL ERROR: attempting to kill unexisting process %s"),
+ prog->tag);
+ else
+ kill (prog->pid, SIGKILL);
+ }
+ else
+ recompute_alarm = 1;
+}
+
+void
+progman_wake_sleeping ()
+{
+ struct prog *prog;
+ time_t now = time (NULL);
+
+ MU_DEBUG (pies_debug, MU_DEBUG_TRACE1,
+ "Managing sleeping/stopping components\n");
+
+ for (prog = proghead; prog; prog = prog->next)
+ if (IS_PROG (prog))
+ {
+ switch (prog->v.p.status)
+ {
+ case status_sleeping:
+ if (now - prog->v.p.timestamp >= SLEEPTIME)
+ {
+ prog->v.p.status = status_enabled;
+ prog->v.p.count = 0;
+ prog->v.p.timestamp = 0;
+ prog_start (prog);
+ }
+ break;
+
+ case status_stopping:
+ check_stopping (prog, now);
+ break;
+
+ case status_enabled:
+ if (prog->pid == 0)
+ prog_start (prog);
+ break;
+
+ default:
+ break;
+ }
+ }
+ if (recompute_alarm)
+ progman_recompute_alarm ();
+}
+
+static int
+prog_start_prerequisites (struct prog *prog)
+{
+ int i;
+ int ret;
+ unsigned settle_timeout = 0;
+
+ if (!prog->prereq)
+ return 0; /* Ok to startup */
+ MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE1, "Starting prerequisites of %s\n",
+ prog->tag);
+ ret = 0;
+ for (i = 0; prog->prereq[i]; i++)
+ {
+ struct prog *dp = prog_lookup_by_tag (prog->prereq[i]);
+ if (!IS_PROG (dp)) /* Skip redirectors */
+ continue;
+ if (prog->v.p.comp->precious)
+ continue;
+ switch (dp->v.p.status)
+ {
+ case status_enabled:
+ if (prog->pid != 0)
+ continue;
+ break;
+
+ case status_disabled:
+ prog->v.p.status = status_disabled;
+ return 1;
+
+ case status_listener:
+ continue;
+
+ case status_sleeping:
+ /* FIXME: What to do in this case? */
+ break;
+
+ case status_stopping:
+ check_stopping (dp, time (NULL));
+ ret = 1;
+ continue;
+ }
+ prog_start (prog_lookup_by_tag (prog->prereq[i]));
+ if (!(dp->v.p.status == status_enabled && dp->pid))
+ ret = 1;
+ else
+ settle_timeout = prog->v.p.comp->settle_timeout;
+ }
+ if (ret == 0 && settle_timeout)
+ sleep (settle_timeout);
+ return ret;
+}
+
+void
+prog_stop_redirectors (struct prog *prog)
+{
+ if (prog->v.p.redir[RETR_OUT])
+ prog_stop (prog->v.p.redir[RETR_OUT], SIGTERM);
+ if (prog->v.p.redir[RETR_ERR])
+ prog_stop (prog->v.p.redir[RETR_ERR], SIGTERM);
+}
+
+void
+prog_stop_dependents (struct prog *prog)
+{
+ pies_depmap_pos_t pos;
+ unsigned n;
+ int warned = 0;
+
+ prog_stop_redirectors (prog);
+ for (n = depmap_first (depmap, depmap_row, prog->idx, &pos);
+ n != (unsigned)-1;
+ n = depmap_next (depmap, pos))
+ {
+ struct prog *dp = prog_lookup_by_idx (n);
+ if (!dp) /* should not happen */
+ continue;
+ if (!warned && dp->pid)
+ {
+ MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE1,
+ "Stopping dependencies of %s\n",
+ prog->tag);
+ warned = 1;
+ }
+ prog_stop (dp, SIGTERM);
+ }
+ free (pos);
+}
+
+static void
+prog_stop (struct prog *prog, int sig)
+{
+ if (prog->pid == 0)
+ return;
+ if (prog->type == TYPE_COMPONENT)
+ {
+ if (prog->v.p.status == status_enabled)
+ {
+ prog->v.p.status = status_stopping;
+ prog->v.p.timestamp = time (NULL);
+ recompute_alarm = 1;
+ }
+ }
+ MU_DEBUG2 (pies_debug, MU_DEBUG_TRACE1,
+ "Stopping %s (%lu)\n",
+ prog->tag, (unsigned long) prog->pid);
+ kill (prog->pid, sig);
+}
+
+static void
+prog_stop_all (int sig)
+{
+ struct prog *prog;
+
+ MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE1,
+ "Stopping all components (signal %d)\n", sig);
+ for (prog = progtail; prog; prog = prog->prev)
+ if (IS_PROG (prog)
+ && (prog->v.p.status == status_enabled
+ || prog->v.p.status == status_stopping))
+ prog_stop (prog, sig);
+}
+
+void
+progman_stop ()
+{
+ unsigned long i;
+
+ prog_stop_all (SIGTERM);
+ for (i = 0; i < shutdown_timeout; i++)
+ {
+ progman_cleanup (1);
+ if (progman_running_count () == 0)
+ return;
+ sleep (1);
+ }
+ prog_stop_all (SIGKILL);
+}
+
+static void
+print_status (char *tag, pid_t pid, int status, int expect_term)
+{
+ if (WIFEXITED (status))
+ {
+ if (WEXITSTATUS (status) == 0)
+ MU_DEBUG2 (pies_debug, MU_DEBUG_TRACE1,
+ _("%s (%lu) exited successfully\n"),
+ tag, (unsigned long) pid);
+ else
+ mu_diag_output (MU_DIAG_ERR,
+ _("%s (%lu) failed with status %d"),
+ tag, (unsigned long) pid,
+ WEXITSTATUS (status));
+ }
+ else if (WIFSIGNALED (status))
+ {
+ int prio;
+
+ if (expect_term && WTERMSIG (status) == SIGTERM)
+ prio = MU_DIAG_DEBUG;
+ else
+ prio = MU_DIAG_ERR;
+
+ mu_diag_output (prio,
+ _("%s (%lu) terminated on signal %d"),
+ tag, (unsigned long) pid,
+ WTERMSIG (status));
+ }
+ else if (WIFSTOPPED (status))
+ mu_diag_output (MU_DIAG_ERR,
+ _("%s (%lu) stopped on signal %d"),
+ tag, (unsigned long) pid,
+ WSTOPSIG (status));
+#ifdef WCOREDUMP
+ else if (WCOREDUMP (status))
+ mu_diag_output (MU_DIAG_ERR,
+ _("%s (%lu) dumped core"),
+ tag, (unsigned long) pid);
+#endif
+ else
+ mu_diag_output (MU_DIAG_ERR,
+ _("%s (%lu) terminated with unrecognized status"),
+ tag, (unsigned long) pid);
+}
+
+static void
+send_msg (mu_address_t rcpt, mu_message_t msg)
+{
+ mu_mailer_t mailer;
+ int rc;
+ const char *s;
+
+ mu_address_sget_email (rcpt, 1, &s);
+ MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE1, "Sending email to %s\n", s);
+
+ rc = mu_mailer_create (&mailer, NULL);
+ if (rc)
+ {
+ const char *mailer_url;
+ mu_mailer_get_url_default (&mailer_url);
+
+ mu_error (_("Cannot create mailer `%s': %s"),
+ mailer_url, mu_strerror (rc));
+ return;
+ }
+
+ /* FIXME: mailer flags? */
+ rc = mu_mailer_open (mailer, 0);
+ if (rc)
+ {
+ const char *mailer_url;
+ mu_mailer_get_url_default (&mailer_url);
+
+ mu_mailer_destroy(&mailer);
+ mu_error (_("opening mailer `%s' failed: %s"),
+ mailer_url, mu_strerror (rc));
+ return;
+ }
+
+ rc = mu_mailer_send_message (mailer, msg, NULL, rcpt);
+ mu_mailer_destroy (&mailer);
+ if (rc)
+ mu_error (_("cannot send message: %s"), mu_strerror (rc));
+}
+
+static const char default_termination_message[] =
+"From: <>\n"
+"X-Agent: ${canonical-program-name} (${package} ${version})\n"
+"Subject: Component ${component} terminated with code ${retcode}.\n"
+"\n";
+
+static void
+vartab_define_project (mu_vartab_t vtab)
+{
+ static struct {
+ char *name;
+ char *value;
+ } ptab[] = {
+ { "canonical-program-name", "pies" },
+ { "package", PACKAGE },
+ { "version", PACKAGE_VERSION },
+ { "mu-version", MAILUTILS_VERSION },
+ { NULL }
+ };
+ int i;
+
+ for (i = 0; ptab[i].name; i++)
+ mu_vartab_define (vtab, ptab[i].name, ptab[i].value, 1);
+}
+
+
+static void
+notify (const char *tag, int status, struct action *act)
+{
+ mu_vartab_t vtab;
+ char *msg_text = NULL;
+ mu_message_t msg = NULL;
+ mu_stream_t stream = NULL;
+ int rc;
+ char buf[INT_BUFSIZE_BOUND (uintmax_t)];
+
+ mu_vartab_create (&vtab);
+ mu_vartab_define (vtab, "component", tag, 1);
+ mu_vartab_define (vtab, "retcode", umaxtostr (status, buf), 1);
+ mu_vartab_define (vtab, "program-name", mu_program_name, 1);
+ vartab_define_project (vtab);
+ rc = mu_vartab_expand (vtab,
+ act->message ?
+ act->message : default_termination_message,
+ &msg_text);
+ mu_vartab_destroy (&vtab);
+ if (rc)
+ {
+ mu_error (_("cannot expand message body: %s"), mu_strerror (rc));
+ /* FIXME: Notify anyway? */
+ return;
+ }
+
+ rc = mu_message_create (&msg, NULL);
+ if (rc)
+ {
+ mu_error (_("cannot create message: %s"), mu_strerror (rc));
+ free (msg_text);
+ }
+
+ mu_message_get_stream (msg, &stream);
+ mu_stream_write (stream, msg_text, strlen (msg_text), 0, NULL);
+ free (msg_text);
+
+ send_msg (act->addr, msg);
+ mu_message_destroy (&msg, mu_message_get_owner (msg));
+}
+
+static int
+status_matches_p (struct action *act, unsigned status)
+{
+ int i;
+
+ if (act->nstat == 0)
+ return 1;
+ for (i = 0; i < act->nstat; i++)
+ if (act->status[i] == status)
+ return 1;
+ return 0;
+}
+
+static void
+run_command (struct action *act, struct prog *prog, unsigned retcode,
+ pid_t child_pid)
+{
+ pid_t pid;
+ char *argv[4];
+ char buf[INT_BUFSIZE_BOUND (uintmax_t)];
+ static RETSIGTYPE (*saved_handler) (int sig);
+ int status;
+
+ saved_handler = signal (SIGPIPE, SIG_IGN);
+
+ pid = fork ();
+
+ if (pid == (pid_t) -1)
+ {
+ mu_error ("fork: %s", mu_strerror (errno));
+ return;
+ }
+
+ if (pid == 0)
+ {
+ int i;
+
+ MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE1,
+ _("executing %s\n"), act->command);
+ /* Child */
+ setenv ("PIES_VERSION", PACKAGE_VERSION, 1);
+ setenv ("PIES_COMPONENT", prog->tag, 1);
+ setenv ("PIES_PID", umaxtostr (child_pid, buf), 1);
+ if (retcode & STATUS_SIG_BIT)
+ setenv ("PIES_SIGNAL", umaxtostr (STATUS_CODE (retcode), buf), 1);
+ else
+ setenv ("PIES_STATUS", umaxtostr (STATUS_CODE (retcode), buf), 1);
+
+ for (i = getmaxfd (); i >= 0; i--)
+ close (i);
+
+ argv[0] = "/bin/sh";
+ argv[1] = "-c";
+ argv[2] = act->command;
+ argv[3] = NULL;
+
+ execv (argv[0], argv);
+ exit (127);
+ }
+
+ /* Master */
+ while (waitpid (pid, &status, 0) == -1)
+ if (errno != EINTR)
+ {
+ mu_error ("waitpid: %s", mu_strerror (errno));
+ break;
+ }
+ signal (SIGPIPE, saved_handler);
+
+ print_status (act->command, pid, status, 1);
+}
+
+void
+progman_cleanup (int expect_term)
+{
+ pid_t pid;
+ int status;
+ while ((pid = waitpid (-1, &status, WNOHANG)) > 0)
+ {
+ struct prog *prog = prog_lookup_by_pid (pid);
+ if (!prog)
+ {
+ mu_diag_output (MU_DIAG_NOTICE,
+ _("subprocess %lu finished"),
+ (unsigned long) pid);
+ continue;
+ }
+ print_status (prog->tag, prog->pid, status, expect_term);
+ prog->pid = 0;
+ if (IS_PROG (prog))
+ {
+ if (prog->v.p.comp->mode == pies_comp_inetd)
+ {
+ prog_stop_redirectors (prog);
+ destroy_prog (&prog);
+ }
+ else
+ {
+ prog->v.p.status = status_enabled;
+ prog_stop_dependents (prog);
+ if (!expect_term)
+ {
+ unsigned retcode;
+ struct action *act = prog->v.p.comp->act_head;
+
+ if (!act)
+ act = default_component.act_head;
+
+ if (WIFEXITED (status))
+ retcode = WEXITSTATUS (status);
+ else if (WIFSIGNALED (status))
+ retcode = STATUS_SIG_BIT | WTERMSIG (status);
+ else
+ {
+ /* Enforce default action: */
+ act = NULL;
+ }
+
+ for (; act; act = act->next)
+ {
+ if (status_matches_p (act, retcode))
+ {
+ if (act->command)
+ {
+ run_command (act, prog, retcode, pid);
+ }
+
+ switch (act->act)
+ {
+ case action_restart:
+ prog_start (prog);
+ break;
+
+ case action_disable:
+ mu_diag_output (MU_DIAG_NOTICE,
+ _("disabling component %s: "
+ "exited with code %d"),
+ prog->tag, status);
+ prog->v.p.status = status_disabled;
+ }
+ if (act->addr)
+ notify (prog->tag, status, act);
+ break;
+ }
+ }
+
+ if (!act)
+ /* Default action: */
+ prog_start (prog);
+ }
+ }
+ }
+ else
+ {
+ /* It was a redirector of an already finished inetd process. */
+ MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE1,
+ _("removing inetd redirector %s\n"), prog->tag);
+ destroy_prog (&prog);
+ }
+ }
+
+ if (!expect_term)
+ /* This will also recompute alarm settings, if necessary */
+ progman_wake_sleeping ();
+}
+
+void
+progman_stop_component (const char *name)
+{
+ struct prog *prog;
+
+ mu_diag_output (MU_DIAG_INFO, _("stopping component `%s'"), name);
+ for (prog = proghead; prog; prog = prog->next)
+ if (IS_PROG (prog) && strcmp (prog->tag, name) == 0)
+ {
+ switch (prog->v.p.status)
+ {
+ case status_enabled:
+ case status_listener:
+ prog_stop (prog, SIGTERM);
+ break;
+
+ case status_disabled:
+ if (!prog->v.p.comp->disabled)
+ prog->v.p.status = status_enabled;
+ break;
+
+ default:
+ mu_diag_output (MU_DIAG_INFO,
+ _("stopping component `%s': "
+ "component not started"),
+ name);
+ }
+ }
+}
+
+void
+progman_dump_stats (const char *filename)
+{
+ FILE *fp;
+ struct prog *prog;
+ char *s;
+ int rc;
+ char *tmpfile = NULL;
+
+ asprintf (&tmpfile, "%s.%lu", filename, (unsigned long) getpid ());
+ if (!tmpfile)
+ {
+ mu_error ("%s", mu_strerror (ENOMEM));
+ return;
+ }
+ mu_diag_output (MU_DIAG_INFO, _("dumping statistics to `%s'"), tmpfile);
+ fp = fopen (tmpfile, "w");
+ if (!fp)
+ {
+ mu_error (_("cannot open file `%s' for writing: %s"),
+ tmpfile, mu_strerror (errno));
+ return;
+ }
+
+ for (prog = proghead; prog; prog = prog->next)
+ {
+ switch (prog->type)
+ {
+ case TYPE_COMPONENT:
+ fprintf (fp, "component %s ", prog->tag);
+ if (prog->pid)
+ {
+ fprintf (fp, "%lu", (unsigned long) prog->pid);
+ if (prog->v.p.status == status_stopping)
+ fprintf (fp, " (stopping)");
+ }
+ else if (prog->v.p.status == status_sleeping)
+ {
+ char buf[48];
+ time_t t = prog->v.p.timestamp + SLEEPTIME;
+ strftime (buf, sizeof buf, "%c",
+ localtime (&t));
+ fprintf (fp, _("[disabled; scheduled for %s]"), buf);
+ }
+ else if (prog->v.p.status == status_disabled)
+ fprintf (fp, _("[disabled]"));
+ else if (prog->v.p.status == status_listener)
+ fprintf (fp, _("[listener]"));
+ else
+ fprintf (fp, _("[not running]"));
+ if (rc = mu_argcv_string (prog->v.p.argc, prog->v.p.comp->argv, &s))
+ {
+ mu_error (_("cannot convert argument list: %s"),
+ mu_strerror (rc));
+ }
+ else
+ {
+ fprintf (fp, " %s", s);
+ free (s);
+ }
+ fputc ('\n', fp);
+ break;
+
+ case TYPE_RETR:
+ fprintf (fp, _("redirector %s %lu\n"), prog->tag,
+ (unsigned long) prog->pid);
+ }
+ }
+ fclose (fp);
+ unlink (filename);
+ if (rename (tmpfile, filename))
+ {
+ mu_error (_("cannot rename %s to %s: %s"), tmpfile, filename,
+ mu_strerror (errno));
+ unlink (tmpfile);
+ }
+ free (tmpfile);
+}
+
+/*
+ Local Variables:
+ c-file-style: "gnu"
+ End:
+*/
+/* EOF */
diff --git a/src/socket.c b/src/socket.c
new file mode 100644
index 0000000..03ee59c
--- /dev/null
+++ b/src/socket.c
@@ -0,0 +1,446 @@
+/* This file is part of Mailfromd.
+ Copyright (C) 2007, 2008 Sergey Poznyakoff
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 3 of the License, or (at your
+ option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "pies.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+
+static void
+switch_eids (uid_t *puid, gid_t *pgid, mode_t *pumask)
+{
+ uid_t ouid = geteuid ();
+ gid_t ogid = getegid ();
+ mode_t omask = umask (*pumask);
+
+ if (setegid (*pgid))
+ mu_error (_("Cannot switch to EGID %lu: %s"),
+ (unsigned long) *pgid, mu_strerror (errno));
+ if (seteuid (*puid))
+ mu_error (_("Cannot switch to EUID %lu: %s"),
+ (unsigned long) *puid, mu_strerror (errno));
+ *puid = ouid;
+ *pgid = ogid;
+ *pumask = omask;
+}
+
+int
+create_socket (mu_url_t url, const char *user, mode_t umaskval)
+{
+ int rc;
+ int fd;
+ union
+ {
+ struct sockaddr sa;
+ struct sockaddr_in s_in;
+ struct sockaddr_un s_un;
+ } addr;
+ socklen_t socklen;
+ const char *scheme;
+ uid_t uid = 0;
+ gid_t gid = 0;
+ int switch_back;
+
+ rc = mu_url_sget_scheme (url, &scheme);
+ if (rc)
+ {
+ mu_error ("mu_url_sget_scheme: %s", mu_strerror (rc));
+ return -1;
+ }
+
+ if (strcmp (scheme, "unix") == 0
+ || strcmp (scheme, "file") == 0 || strcmp (scheme, "socket") == 0)
+ {
+ const char *path = NULL;
+ struct stat st;
+ size_t vpc;
+ char **vpairs;
+ char *group = NULL;
+
+ mu_url_sget_user (url, &user);
+ if (mu_url_sget_fvpairs (url, &vpc, &vpairs) == 0)
+ {
+ size_t i;
+ for (i = 0; i < vpc; i++)
+ {
+ size_t len = strcspn (vpairs[i], "=");
+ if (strncmp (vpairs[i], "user", len) == 0)
+ user = vpairs[i] + len + 1;
+ else if (strncmp (vpairs[i], "group", len) == 0)
+ group = vpairs[i] + len + 1;
+ else if (strncmp (vpairs[i], "umask", len) == 0)
+ {
+ char *p;
+ unsigned long n = strtoul (vpairs[i] + len + 1, &p, 8);
+ if (*p)
+ mu_error (_("%s: invalid octal number (%s)"),
+ mu_url_to_string (url), vpairs[i] + len + 1);
+ else if (n & ~0777)
+ mu_error (_("%s: invalid umask (%s)"),
+ mu_url_to_string (url), vpairs[i] + len + 1);
+ else
+ umaskval = n & 0777;
+ }
+ else if (strncmp (vpairs[i], "mode", len) == 0)
+ {
+ char *p;
+ unsigned long n = strtoul (vpairs[i] + len + 1, &p, 8);
+ if (*p)
+ mu_error (_("%s: invalid octal number (%s)"),
+ mu_url_to_string (url), vpairs[i] + len + 1);
+ else if (n & ~0777)
+ mu_error (_("%s: invalid mode (%s)"),
+ mu_url_to_string (url), vpairs[i] + len + 1);
+ else
+ umaskval = 0777 & ~n;
+ }
+ }
+ }
+
+ if (user)
+ {
+ struct passwd *pw = getpwnam (user);
+ if (!pw)
+ {
+ mu_error (_("No such user: %s"), user);
+ return -1;
+ }
+ uid = pw->pw_uid;
+ gid = pw->pw_gid;
+ }
+
+ if (group)
+ {
+ struct group *grp = getgrnam (group);
+ if (!grp)
+ {
+ mu_error (_("No such group: %s"), user);
+ return -1;
+ }
+ gid = grp->gr_gid;
+ }
+
+ rc = mu_url_sget_path (url, &path);
+ if (rc)
+ {
+ mu_error ("mu_url_sget_path: %s", mu_strerror (rc));
+ return -1;
+ }
+
+ if (strlen (path) > sizeof addr.s_un.sun_path)
+ {
+ errno = EINVAL;
+ mu_error (_("%s: UNIX socket name too long"),
+ mu_url_to_string (url));
+ return -1;
+ }
+ addr.sa.sa_family = PF_UNIX;
+ socklen = sizeof (addr.s_un);
+ strcpy (addr.s_un.sun_path, path);
+ if (stat(path, &st))
+ {
+ if (errno != ENOENT)
+ {
+ mu_error (_("%s: cannot stat socket: %s"),
+ mu_url_to_string (url),
+ mu_strerror (errno));
+ return -1;
+ }
+ }
+ else
+ {
+ /* FIXME: Check permissions? */
+ if (!S_ISSOCK(st.st_mode))
+ {
+ mu_error (_("%s: not a socket"), mu_url_to_string (url));
+ return -1;
+ }
+ if (/*rmsocket && */ unlink (path))
+ {
+ mu_error (_("%s: cannot unlink: %s"),
+ path, mu_strerror (errno));
+ return -1;
+ }
+ }
+ }
+ else if (strcmp (scheme, "inet") == 0)
+ {
+ long n;
+ const char *host = NULL;
+ short port = 0;
+
+ rc = mu_url_get_port (url, &n);
+ if (rc)
+ {
+ mu_error ("mu_url_get_port: %s", mu_strerror (rc));
+ return -1;
+ }
+
+ if (n == 0 || (port = n) != n)
+ {
+ mu_error (_("%s: port out of range"), mu_url_to_string (url));
+ return -1;
+ }
+
+ rc = mu_url_sget_host (url, &host);
+ if (rc == MU_ERR_NOENT)
+ host = NULL;
+ else if (rc)
+ {
+ mu_error ("mu_url_sget_host: %s", mu_strerror (rc));
+ return -1;
+ }
+
+ addr.sa.sa_family = PF_INET;
+ socklen = sizeof (addr.s_in);
+
+ if (!host)
+ addr.s_in.sin_addr.s_addr = INADDR_ANY;
+ else
+ {
+ struct hostent *hp = gethostbyname (host);
+ if (!hp)
+ {
+ mu_error (_("%s: Unknown host name %s"),
+ mu_url_to_string (url), host);
+ return -1;
+ }
+ addr.sa.sa_family = hp->h_addrtype;
+ switch (hp->h_addrtype)
+ {
+ case AF_INET:
+ memmove (&addr.s_in.sin_addr, hp->h_addr, 4);
+ addr.s_in.sin_port = htons (port);
+ break;
+
+ default:
+ mu_error (_("%s: unsupported address family"),
+ mu_url_to_string (url));
+ return -1;
+ }
+ }
+ }
+ else
+ {
+ mu_error ("%s: unknown scheme", mu_url_to_string (url));
+ return -1;
+ }
+
+ fd = socket (addr.sa.sa_family, SOCK_STREAM, 0);
+ if (fd == -1)
+ {
+ mu_error (_("%s: Cannot create socket: %s"),
+ mu_url_to_string (url), mu_strerror (errno));
+ return -1;
+ }
+
+ rc = 1;
+ if (addr.sa.sa_family != PF_UNIX
+ && setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *) &rc,
+ sizeof (rc)) == -1)
+ {
+ mu_error (_("%s: set reuseaddr failed (%s)"),
+ mu_url_to_string (url), mu_strerror (errno));
+ close(fd);
+ return -1;
+ }
+
+ if (uid || gid || umaskval)
+ {
+ switch_eids (&uid, &gid, &umaskval);
+ switch_back = 1;
+ }
+ else
+ switch_back = 0;
+ rc = bind (fd, &addr.sa, socklen);
+ if (switch_back)
+ switch_eids (&uid, &gid, &umaskval);
+ if (rc < 0)
+ {
+ mu_error (_("%s: Cannot bind: %s"),
+ mu_url_to_string (url), mu_strerror (errno));
+ close (fd);
+ return -1;
+ }
+ return fd;
+}
+
+static int
+open_unix_socket (const char *socket_name)
+{
+ int fd;
+ struct sockaddr_un addr;
+
+ if (strlen (socket_name) > sizeof addr.sun_path)
+ {
+ mu_error (_("%s: UNIX socket name too long"), socket_name);
+ return -1;
+ }
+
+ fd = socket (PF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1)
+ {
+ mu_error ("socket: %s", mu_strerror (errno));
+ return -1;
+ }
+ addr.sun_family = AF_UNIX;
+ strcpy (addr.sun_path, socket_name);
+
+ if (connect (fd, (struct sockaddr *) &addr, sizeof (addr)))
+ {
+ mu_error (_("%s: connect failed: %s"), socket_name, mu_strerror (errno));
+ close (fd);
+ return -1;
+ }
+
+ return fd;
+}
+
+static int
+pass_fd0 (int fd, int payload)
+{
+ struct msghdr msg;
+ struct iovec iov[1];
+
+#if HAVE_STRUCT_MSGHDR_MSG_CONTROL
+# ifndef CMSG_LEN
+# define CMSG_LEN(size) (sizeof(struct cmsghdr) + (size))
+# endif /* ! CMSG_LEN */
+# ifndef CMSG_SPACE
+# define CMSG_SPACE(size) (sizeof(struct cmsghdr) + (size))
+# endif /* ! CMSG_SPACE */
+
+ char control[CMSG_SPACE (sizeof (int))];
+ struct cmsghdr *cmptr;
+
+ msg.msg_control = (caddr_t) control;
+ msg.msg_controllen = CMSG_LEN (sizeof (int));
+
+ cmptr = CMSG_FIRSTHDR (&msg);
+ cmptr->cmsg_len = CMSG_LEN (sizeof(int));
+ cmptr->cmsg_level = SOL_SOCKET;
+ cmptr->cmsg_type = SCM_RIGHTS;
+ *((int *) CMSG_DATA (cmptr)) = payload;
+#elif HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS
+ msg.msg_accrights = (caddr_t) &payload;
+ msg.msg_accrightslen = sizeof (int);
+#else
+ mu_error (_("no way to send fd"));
+ return 1;
+#endif /* HAVE_MSGHDR_MSG_CONTROL */
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+
+ iov[0].iov_base = "";
+ iov[0].iov_len = 1;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+ return sendmsg (fd, &msg, 0) == -1;
+}
+
+int
+pass_fd (const char *socket, int fd)
+{
+ int i;
+ struct stat st;
+
+ for (i = 0; i < 10; i++)
+ {
+ if (stat (socket, &st) == 0)
+ {
+ int rc;
+ int sockfd;
+
+ if (!S_ISSOCK (st.st_mode))
+ {
+ mu_error (_("%s: not a socket"), socket);
+ break;
+ }
+ sockfd = open_unix_socket (socket);
+ if (sockfd == -1)
+ rc = 1;
+ else
+ {
+ rc = pass_fd0 (sockfd, fd);
+ close (sockfd);
+ }
+ return rc;
+ }
+ if (errno != ENOENT)
+ {
+ mu_error (_("cannot stat %s: %s"), socket, mu_strerror (errno));
+ break;
+ }
+ }
+ return -1;
+}
+
+
+fd_set listenset;
+int fd_max;
+
+int
+register_listener (int fd)
+{
+ if (listen (fd, 8) == -1)
+ {
+ mu_error (_("listen: %s"), mu_strerror (errno));
+ return 1;
+ }
+ FD_SET (fd, &listenset);
+ if (fd > fd_max)
+ fd_max = fd;
+ return 0;
+}
+
+void
+pies_pause ()
+{
+ while (1)
+ {
+ fd_set rdset = listenset;
+ int rc = select (fd_max + 1, &rdset, NULL, NULL, NULL);
+ if (rc > 0)
+ {
+ int i;
+ for (i = 0; i <= fd_max; i++)
+ {
+ if (FD_ISSET (i, &rdset))
+ progman_accept (i);
+ }
+ }
+ else if (rc < 0)
+ {
+ if (errno != EINTR)
+ mu_error ("select: %s", mu_strerror (errno));
+ break;
+ }
+ }
+}
+

Return to:

Send suggestions and report system problems to the System administrator.