/* This file is part of GNU Pies.
Copyright (C) 2016-2019 Sergey Poznyakoff
GNU Pies is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GNU Pies is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU Pies. If not, see . */
#include "pies.h"
#include "prog.h"
#include
struct complist
{
struct component *head;
struct component *tail;
};
/* 0 on the first load, and 1 on all subsequent reloads. Tells the
component_config_commit whether we're starting from scratch or just
updating an already loaded configuration */
static int loaded;
static struct complist comp_list[2];
static int cur;
static struct component **comp_array;
static size_t comp_count;
static pies_depmap_t depmap;
static inline int
next_index (void)
{
return (cur + 1) % ARRAY_SIZE (comp_list);
}
static inline int
prev_index (void)
{
return (cur + ARRAY_SIZE (comp_list) - 1) % ARRAY_SIZE (comp_list);
}
void
component_link (struct component *comp, struct component *ref)
{
if (!ref)
{
struct complist *list = &comp_list[comp->listidx];
comp->prev = NULL;
comp->next = list->head;
if (list->head)
list->head->prev = comp;
else
list->tail = comp;
list->head = comp;
}
else
{
struct complist *list = &comp_list[comp->listidx];
struct component *x;
assert (comp->listidx == ref->listidx);
comp->prev = ref;
comp->next = ref->next;
if ((x = ref->next))
x->prev = comp;
else
list->tail = comp;
ref->next = comp;
}
}
void
component_append (struct component *comp)
{
component_link (comp, comp_list[comp->listidx].tail);
}
void
comp_array_remove (size_t i)
{
struct component *comp = comp_array[i];
depmap_remove (depmap, i);
while (i < comp_count -1)
{
comp_array[i] = comp_array[i+1];
comp_array[i]->arridx = i;
i++;
}
component_free (comp);
comp_count--;
}
void
component_unlink (struct component *comp)
{
struct complist *list = &comp_list[comp->listidx];
struct component *x;
if ((x = comp->prev))
x->next = comp->next;
else
list->head = comp->next;
if ((x = comp->next))
x->prev = comp->prev;
else
list->tail = comp->prev;
}
int
component_list_is_empty (void)
{
return !comp_list[cur].head;
}
struct component *
component_lookup_tag (int idx, const char *tag)
{
struct complist *list = &comp_list[idx];
struct component *comp;
for (comp = list->head; comp; comp = comp->next)
if (strcmp (comp->tag, tag) == 0)
break;
return comp;
}
ssize_t
component_lookup_index (const char *tag)
{
size_t i;
for (i = 0; i < comp_count; i++)
if (strcmp (comp_array[i]->tag, tag) == 0)
return i;
return -1;
}
struct component *
component_create (const char *name)
{
struct component *comp = component_lookup_tag (cur, name);
if (!comp)
{
comp = grecs_zalloc (sizeof (*comp));
comp->listidx = cur;
comp->facility = log_facility;
comp->redir[RETR_OUT].type = comp->redir[RETR_ERR].type = redir_null;
comp->tag = grecs_strdup (name);
comp->socket_type = SOCK_STREAM;
component_append (comp);
}
return comp;
}
void
component_free (struct component *comp)
{
size_t i;
component_unlink (comp);
free (comp->tag);
free (comp->program);
if (comp->argv)
{
for (i = 0; i < comp->argc; i++)
free (comp->argv[i]);
free (comp->argv);
}
if (comp->env)
{
for (i = 0; comp->env[i]; i++)
free (comp->env[i]);
free (comp->env);
}
free (comp->dir);
grecs_list_free (comp->prereq);
grecs_list_free (comp->depend);
free (comp->rmfile);
pies_privs_free (&comp->privs);
free (comp->runlevels);
free_limits (comp->limits);
free (comp->service);
pies_url_destroy (&comp->socket_url);
free (comp->pass_fd_socket);
free (comp->tcpmux);
free (comp->access_denied_message);
free (comp->max_instances_message);
free (comp->max_ip_connections_message);
free_redirector (&comp->redir[0]);
free_redirector (&comp->redir[1]);
grecs_list_free (comp->act_list);
pies_acl_free (comp->acl);
pies_acl_free (comp->list_acl);
pies_acl_free (comp->adm_acl);
free (comp);
}
void
component_ref_incr (struct component *comp)
{
++comp->ref_count;
}
void
component_ref_decr (struct component *comp)
{
assert (comp->ref_count > 0);
if (--comp->ref_count == 0)
{
if (component_is_active (comp))
comp_array_remove (comp->arridx);
else
component_free (comp);
}
}
static int
argvcmp (char **a, char **b)
{
size_t i;
if (!a != !b)
return 1;
for (i = 0; a[i]; i++)
if (!b[i] || strcmp (b[i], a[i]))
return 1;
return !!b[i];
}
static int
urlcmp (struct pies_url *a, struct pies_url *b)
{
if (!a)
return !!b;
else if (!b)
return 1;
return safe_strcmp (a->string, b->string);
}
static int
redirector_cmp (struct redirector const *a, struct redirector const *b)
{
if (a->type != b->type)
return 1;
switch (a->type)
{
case redir_null:
break;
case redir_syslog:
if (a->v.prio != b->v.prio)
return 1;
break;
case redir_file:
if (safe_strcmp (a->v.file, b->v.file))
return 1;
}
return 0;
}
static int
component_match (struct component *comp, struct component *ref)
{
#define MATCH(cond) do if (cond) return 1; while (0)
#define EQ(memb) MATCH (comp->memb != ref->memb)
#define FN(memb,fun) MATCH (fun (comp->memb, ref->memb))
#define FNP(memb,fun) MATCH (fun (&comp->memb, &ref->memb))
EQ (mode);
FN (tag, safe_strcmp);
FN (program, safe_strcmp);
EQ (argc);
FN (argv, argvcmp);
FN (dir, safe_strcmp);
FN (prereq, grecs_list_compare);
FN (depend, grecs_list_compare);
EQ (flags);
EQ (max_instances);
FN (rmfile, safe_strcmp);
FNP (privs, pies_privs_cmp);
FN (limits, limits_cmp);
FN (runlevels, safe_strcmp);
EQ (max_rate);
EQ (max_ip_connections);
EQ (socket_type);
EQ (builtin);
FN (service, safe_strcmp);
FN (socket_url, urlcmp);
FN (pass_fd_socket, safe_strcmp);
EQ (pass_fd_timeout);
FN (acl, pies_acl_cmp);
FN (tcpmux, safe_strcmp);
EQ (facility);
FNP (redir[0], redirector_cmp);
FNP (redir[1], redirector_cmp);
#undef MATCH
#undef EQ
#undef FN
#undef FNP
return 0;
}
static struct component *
complist_find_match (int idx, struct component *ref)
{
struct complist *list = &comp_list[idx];
struct component *comp;
for (comp = list->head; comp && component_match (comp, ref);
comp = comp->next)
;
return comp;
}
static void
strasgn (char **dst, char **src)
{
free (*dst);
*dst = *src;
*src = NULL;
}
void
component_merge (struct component *comp, struct component *ref)
{
strasgn (&comp->tag, &ref->tag);
strasgn (&comp->access_denied_message, &ref->access_denied_message);
strasgn (&comp->max_instances_message, &ref->max_instances_message);
strasgn (&comp->max_ip_connections_message,
&ref->max_ip_connections_message);
grecs_list_free (comp->act_list);
comp->act_list = ref->act_list;
ref->act_list = NULL;
pies_acl_free (comp->list_acl);
comp->list_acl = ref->list_acl;
ref->list_acl = NULL;
pies_acl_free (comp->adm_acl);
comp->adm_acl = ref->adm_acl;
ref->adm_acl = NULL;
}
int
component_is_active (struct component *comp)
{
return comp->listidx == cur;
}
void
component_config_begin (void)
{
cur = next_index ();
}
void
component_config_rollback (void)
{
struct complist *list = &comp_list[cur];
while (list->head)
component_free (list->head);
cur = prev_index ();
}
/* Return true if PROG is a leftover from previous configuration */
static int
prog_is_leftover (struct prog *prog)
{
return IS_COMPONENT (prog) && !component_is_active (prog->v.p.comp);
}
/* If PROG is a leftover, mark it for termination. If it is a listener,
terminate it immediately. This ensures that all decommissioned sockets
are closed before the subsequent call to progman_create_sockets, which
might need to reopen some of them.
*/
static int
mark_prog (struct prog *prog, void *data)
{
if (prog_is_leftover (prog))
{
prog->stop = 1;
if (prog->v.p.status == status_listener)
progman_stop_component (&prog);
}
return 0;
}
static int
list_str_cmp (const void *a, const void *b)
{
return safe_strcmp (a, b);
}
/* Report cyclic dependency starting at IDX. Mark each element with
CF_REMOVE for subsequent removal.
DP is a transitive closure of depmap.
*/
static void
report_cyclic_dependency (pies_depmap_t dp, size_t idx)
{
size_t i;
i = idx;
do
{
size_t n;
pies_depmap_pos_t pos;
logmsg_printf (LOG_NOTICE, "%s -> ", comp_array[i]->tag);
comp_array[i]->flags |= CF_REMOVE;
for (n = depmap_first (depmap, depmap_col, i, &pos);
n != (size_t)-1;
n = depmap_next (depmap, pos))
{
if (n == i)
continue;
if (depmap_isset (dp, n, i) && depmap_isset (dp, n, n))
break;
}
depmap_end (pos);
if (n == (size_t)-1)
break;
i = n;
}
while (i != idx);
logmsg_printf (LOG_NOTICE, "%s\n", comp_array[idx]->tag);
}
void
component_build_depmap (void)
{
size_t i;
pies_depmap_t dp;
free (depmap);
depmap = depmap_alloc (comp_count);
for (i = 0; i < comp_count; )
{
struct component *comp = comp_array[i];
struct grecs_list_entry *ep;
if (comp->prereq)
for (ep = comp->prereq->head; ep; ep = ep->next)
{
char const *tag = ep->data;
ssize_t tgt = component_lookup_index (tag);
if (tgt < 0)
{
logmsg (LOG_ERR,
_("component %s depends on %s, "
"which is not declared"),
comp->tag, tag);
comp_array_remove (i);
continue;
}
depmap_set (depmap, i, tgt);
}
if (comp->depend)
for (ep = comp->depend->head; ep; ep = ep->next)
{
char const *tag = ep->data;
ssize_t tgt = component_lookup_index (tag);
if (tgt < 0)
{
logmsg (LOG_ERR,
_("undefined component %s depends on %s"),
tag, comp->tag);
continue;
}
depmap_set (depmap, tgt, i);
}
i++;
}
dp = depmap_copy (depmap);
depmap_tc (dp);
for (i = 0; i < comp_count; i++)
if (!(comp_array[i]->flags & CF_REMOVE) && depmap_isset (dp, i, i))
{
logmsg (LOG_ERR, _("component %s depends on itself"),
comp_array[i]->tag);
report_cyclic_dependency (dp, i);
}
for (i = 0; i < comp_count;)
if (comp_array[i]->flags & CF_REMOVE)
comp_array_remove (i);
else
i++;
free (dp);
}
void
component_config_commit (void)
{
struct complist *list = &comp_list[cur];
struct component *comp, *match;
int prev = prev_index ();
size_t i;
/* Count available components and allocate array for them */
for (comp = list->head, i = 0; comp; comp = comp->next, i++)
/* nothing */;
if (i == 0)
{
free (comp_array);
comp_array = NULL;
}
else
comp_array = grecs_realloc (comp_array, i * sizeof (comp_array[0]));
comp_count = i;
/* Rearrange components, registering entries for the new ones */
for (comp = list->head, i = 0; comp; )
{
struct component *next = comp->next;
if (loaded && comp->mode == pies_comp_startup)
{
/* Ignore startup components */
component_unlink (comp);
component_free (comp);
}
else
{
match = complist_find_match (prev, comp);
if (match)
{
component_merge (match, comp);
component_unlink (match);
match->listidx = cur;
component_link (match, comp->prev);
component_free (comp);
comp = match;
}
comp_array[i] = comp;
comp->arridx = i;
i++;
}
comp = next;
}
/* Adjust comp_count */
comp_count = i;
/* Mark orphaned progs for termination */
list = &comp_list[prev];
if (list->head)
{
progman_foreach (mark_prog, NULL);
pies_schedule_children (PIES_CHLD_GC);
}
/* Build dependency map */
component_build_depmap ();
/* Register new progs */
for (comp = comp_list[cur].head; comp; comp = comp->next)
if (!comp->prog)
register_prog (comp);
loaded = 1;
}
static int
component_verify (struct component *comp, grecs_locus_t *locus)
{
int header = 0;
int i;
#define COMPERR(func, fmt, arg) \
do \
{ \
if (!header) \
{ \
grecs_warning (locus, 0, _("in component %s:"), comp->tag); \
header = 1; \
} \
func (locus, 0, fmt, arg); \
} \
while (0)
if (comp->flags & CF_INTERNAL)
{
comp->mode = pies_comp_inetd;
if (!comp->service)
/* TRANSLATORS: do not translate quoted words, they are keywords. */
COMPERR (grecs_error,
"%s", _("\"internal\" used without \"service\""));
else
{
comp->builtin = inetd_builtin_lookup (comp->service,
comp->socket_type);
if (!comp->builtin)
COMPERR (grecs_error,
"%s", _("unknown internal service"));
if (comp->argv)
/* TRANSLATORS: do not translate quoted words, they are
keywords. */
COMPERR (grecs_error,
"%s", _("\"internal\" used with \"command\""));
}
}
else if (!comp->argv)
COMPERR (grecs_error,
"%s", _("missing command line"));
if (ISCF_TCPMUX (comp->flags))
{
comp->mode = pies_comp_inetd;
if ((comp->flags & (CF_TCPMUX | CF_TCPMUXPLUS))
== (CF_TCPMUX | CF_TCPMUXPLUS))
COMPERR (grecs_error,
"%s", _("both \"tcpmux\" and \"tcpmuxplus\" used"));
else if (!comp->service)
/* TRANSLATORS: do not translate quoted words, they are keywords. */
COMPERR (grecs_error,
"%s", _("\"internal\" used without \"service\""));
}
if (comp->pass_fd_socket && comp->mode != pies_comp_pass_fd)
COMPERR (grecs_error,
"%s", _("pass-fd-socket ignored: wrong mode"));
switch (comp->mode)
{
case pies_comp_exec:
if (comp->socket_url)
COMPERR (grecs_error,
"%s", _("socket ignored: wrong mode"));
break;
case pies_comp_pass_fd:
if (!comp->pass_fd_socket)
COMPERR (grecs_error,
"%s", _("must supply pass-fd-socket in this mode"));
else if (comp->pass_fd_socket[0] != '/')
{
if (comp->dir)
{
char *p = mkfilename (comp->dir, comp->pass_fd_socket, NULL);
/*free (comp->pass_fd_socket);*/
comp->pass_fd_socket = p;
}
else
COMPERR (grecs_error,
"%s", _("pass-fd-socket must be an absolute "
"file name or chdir must be specified"));
}
/* Fall through */
case pies_comp_accept:
if (!comp->socket_url)
{
COMPERR (grecs_error,
"%s", _("socket must be specified in this mode"));
return 1;
}
break;
case pies_comp_inetd:
if (ISCF_TCPMUX (comp->flags))
{
pies_url_destroy (&comp->socket_url);
if (!comp->tcpmux)
{
COMPERR (grecs_warning,
"%s",
_("TCPMUX master not specified, assuming \"tcpmux\""));
comp->tcpmux = grecs_strdup ("tcpmux");
}
}
else if (comp->tcpmux)
{
comp->flags |= CF_TCPMUX;
pies_url_destroy (&comp->socket_url);
}
else if (!comp->socket_url)
{
COMPERR (grecs_error,
"%s", _("socket must be specified in this mode"));
return 1;
}
default:
/* FIXME: more checks perhaps */
break;
}
if (comp->mode == pies_comp_inetd)
{
if ((comp->flags & CF_WAIT) && comp->socket_type == SOCK_STREAM)
{
if (comp->max_instances)
COMPERR (grecs_error, "%s", _("max-instances ignored"));
else
comp->max_instances = 1;
}
}
else if (comp->flags & CF_WAIT)
{
/* TRANSLATORS: `wait' is a keywords, do not translate. */
COMPERR (grecs_error, "%s", _("wait is useless in this mode"));
comp->flags &= ~CF_WAIT;
}
switch (comp->mode)
{
case pies_comp_accept:
case pies_comp_inetd:
if (comp->redir[RETR_OUT].type != redir_null)
{
COMPERR (grecs_error,
"%s", _("stdout redirection invalid in this mode"));
comp->redir[RETR_OUT].type = redir_null;
}
default:
break;
}
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 = mkfilename (comp->dir, comp->redir[i].v.file, NULL);
free (comp->redir[i].v.file);
comp->redir[i].v.file = p;
}
else
COMPERR (grecs_error,
_("%s: must be an absolute "
"file name or chdir must be specified"),
comp->redir[i].v.file);
}
}
return header;
#undef COMPERR
}
void
component_finish (struct component *comp, grecs_locus_t *locus)
{
if (comp->prereq)
comp->prereq->cmp = list_str_cmp;
if (comp->depend)
comp->depend->cmp = list_str_cmp;
if (comp->privs.groups)
comp->privs.groups->cmp = list_str_cmp;
if (component_verify (comp, locus))
{
component_free (comp);
}
else
{
size_t n = grecs_list_size (comp->prereq);
if (n == 1)
{
const char *item = grecs_list_index (comp->prereq, 0);
if (strcmp (item, "all") == 0)
{
struct component *p;
grecs_list_clear (comp->prereq);
for (p = comp->prev; p; p = p->prev)
grecs_list_push (comp->prereq, grecs_strdup (comp->tag));
}
else if (strcmp (item, "none") == 0)
{
grecs_list_free (comp->prereq);
comp->prereq = NULL;
}
}
}
}
struct component *
component_get (size_t n)
{
if (n >= comp_count)
return NULL;
return comp_array[n];
}
void
depmap_dump (pies_depmap_t dpm)
{
size_t i, j;
printf ("%s:\n", _("Dependency map"));
printf (" ");
for (i = 0; i < comp_count; i++)
printf (" %2lu", (unsigned long)i);
printf ("\n");
for (i = 0; i < comp_count; i++)
{
printf ("%2lu", (unsigned long)i);
for (j = 0; j < comp_count; j++)
printf (" %c", depmap_isset (dpm, i, j) ? 'X' : ' ');
printf ("\n");
}
printf ("\n%s:\n", _("Legend"));
for (i = 0; i < comp_count; i++)
printf ("%2lu: %s\n", (unsigned long)i, comp_array[i]->tag);
}
void
components_dump_depmap (void)
{
depmap_dump (depmap);
}
void
component_trace (size_t idx, enum pies_depmap_direction dir)
{
pies_depmap_pos_t pos;
size_t n;
int delim = ':';
logmsg_printf (LOG_NOTICE, "%s", comp_array[idx]->tag);
for (n = depmap_first (depmap, dir, idx, &pos);
n != (size_t)-1;
n = depmap_next (depmap, pos))
{
logmsg_printf (LOG_NOTICE, "%c %s", delim, comp_array[n]->tag);
delim = ',';
}
depmap_end (pos);
logmsg_printf (LOG_NOTICE, "\n");
}
void
components_trace (char **argv, enum pies_depmap_direction dir)
{
if (*argv)
for (; *argv; ++argv)
{
ssize_t idx = component_lookup_index (*argv);
if (idx < 0)
logmsg (LOG_ERR, "%s: no such component", *argv);
else
component_trace (idx, dir);
}
else
{
size_t i;
for (i = 0; i < comp_count; i++)
component_trace (i, dir);
}
}
struct component *
component_depmap_first (enum pies_depmap_direction dir, size_t idx,
pies_depmap_pos_t *ppos)
{
size_t n = depmap_first (depmap, dir, idx, ppos);
if (n == (size_t)-1)
return NULL;
return comp_array[n];
}
struct component *
component_depmap_next (pies_depmap_pos_t pos)
{
size_t n = depmap_next (depmap, pos);
if (n == (size_t)-1)
return NULL;
return comp_array[n];
}
int
component_foreach (int (*filter) (struct component *, void *), void *data)
{
struct component *comp;
int rc = 0;
struct complist *list = &comp_list[cur];
for (comp = list->head; comp; )
{
struct component *next = comp->next;
if ((rc = filter (comp, data)) != 0)
break;
comp = next;
}
return rc;
}