/* 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; }; 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) { component_unlink (comp); free (comp->tag); free (comp->program); free (comp->command); argv_free (comp->argv); envop_free (comp->envop); 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; comp = comp->next, i++) { 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; } /* 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); } 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->flags & CF_SHELL) { comp->argc = 3; comp->argv = grecs_calloc (comp->argc + 1, sizeof (comp->argv[0])); comp->argv[0] = grecs_strdup (comp->program ? comp->program : "/bin/sh"); comp->argv[1] = grecs_strdup ("-c"); comp->argv[2] = grecs_strdup (comp->command); comp->argv[3] = NULL; } else { struct wordsplit ws; if (wordsplit (comp->command, &ws, WRDSF_DEFFLAGS)) { grecs_error (locus, 0, "wordsplit: %s", wordsplit_strerror (&ws)); component_free (comp); return; } wordsplit_get_words (&ws, &comp->argc, &comp->argv); wordsplit_free (&ws); } 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; }