/* This file is part of GNU Pies. Copyright (C) 2009-2011, 2013, 2016 Sergey Poznyakoff GNU Pies is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Pies is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Pies. If not, see . */ #include "pies.h" #include #include #define IFLD_SERVICE 0 /* service name */ #define IFLD_SOCKET 1 /* socket type */ #define IFLD_PROTOCOL 2 /* protocol */ #define IFLD_WAIT 3 /* wait/nowait */ #define IFLD_USER 4 /* user */ #define IFLD_SERVER_PATH 5 /* server program path */ #define IFLD_SERVER_ARGS 6 /* server program arguments */ #define IFLD_MIN_COUNT 6 /* Minimum number of fields in entry */ /* FIXME: Copied from grecs/src/tree.c */ static void listel_dispose(void *el) { free(el); } #define TCPMUX_PREFIX_STR "tcpmux/" #define TCPMUX_PREFIX_LEN (sizeof(TCPMUX_PREFIX_STR)-1) static int tcpmux_service (const char *service, char **psrv, int *pflag) { if (strncmp (service, TCPMUX_PREFIX_STR, TCPMUX_PREFIX_LEN) == 0) { service += TCPMUX_PREFIX_LEN; if (*service == '+') { *pflag |= CF_TCPMUXPLUS; service++; } else *pflag |= CF_TCPMUX; *psrv = (char *) service; return 1; } return 0; } static char * mktag (const char *address, const char *service) { char *str; if (address) { str = grecs_malloc (strlen (address) + 1 + strlen (service) + 1); strcpy (str, address); strcat (str, ":"); strcat (str, service); } else str = grecs_strdup (service); return str; } static int inetd_conf_file (const char *file) { FILE *fp; size_t size = 0; char *buf = NULL; unsigned long line_no = 0; struct wordsplit ws; char *dfl_address = NULL; int wsflags; fp = fopen (file, "r"); if (!fp) { logmsg (LOG_ERR, _("cannot open configuration file %s: %s"), file, strerror (errno)); return 1; } wsflags = WRDSF_NOVAR | WRDSF_NOCMD | WRDSF_WS | WRDSF_SQUEEZE_DELIMS; while (getline (&buf, &size, fp) >= 0) { char *p; struct component *comp; int socket_type; struct pies_url *url; size_t max_rate = 0; int flags = 0; char *str; char *user = NULL; char *group = NULL; char *address; char *service; size_t len; struct inetd_builtin *builtin; char *tag; line_no++; for (p = buf; *p && c_isblank (*p); p++) ; if (!p || *p == '\n' || *p == '#') continue; if (wordsplit (p, &ws, wsflags)) { logmsg (LOG_ERR, "wordsplit: %s", strerror (errno)); continue; } wsflags |= WRDSF_REUSE; if (ws.ws_wordc == 1) { size_t len = strlen (ws.ws_wordv[IFLD_SERVICE]); if (len > 0 && ws.ws_wordv[IFLD_SERVICE][len-1] == ':') { free (dfl_address); if (len == 2 && ws.ws_wordv[IFLD_SERVICE][0] == '*') dfl_address = NULL; else { dfl_address = grecs_malloc (len); memcpy (dfl_address, ws.ws_wordv[IFLD_SERVICE], len-1); dfl_address[len-1] = 0; } continue; } } if (ws.ws_wordc < IFLD_MIN_COUNT) { logmsg (LOG_ERR, "%s:%lu: too few fields", file, line_no); continue; } /* Parse service */ str = strchr (ws.ws_wordv[IFLD_SERVICE], ':'); if (str) { *str++ = 0; address = ws.ws_wordv[IFLD_SERVICE]; service = str; } else { address = dfl_address; service = ws.ws_wordv[IFLD_SERVICE]; } /* Parse socket type */ if (str_to_socket_type (ws.ws_wordv[IFLD_SOCKET], &socket_type)) { logmsg (LOG_ERR, "%s:%lu: %s", file, line_no, _("bad socket type")); continue; } tag = service; /* Handle eventual "tcpmux/" */ if (tcpmux_service (service, &service, &flags)) { if (strncmp (ws.ws_wordv[IFLD_PROTOCOL], "tcp", 3)) { logmsg (LOG_ERR, "%s:%lu: %s", file, line_no, _("bad protocol for tcpmux service")); continue; } if (socket_type != SOCK_STREAM) { logmsg (LOG_ERR, "%s:%lu: %s", file, line_no, _("bad socket type for tcpmux service")); continue; } url = NULL; } else { char *s = NULL; size_t l = 0; int rc; /* Create URL from protocol and service fields. */ if (grecs_asprintf (&s, &l, "inet+%s://%s:%s", ws.ws_wordv[IFLD_PROTOCOL], address ? address : "0.0.0.0", service)) grecs_alloc_die (); rc = pies_url_create (&url, s); free (s); if (rc) { /* FIXME: Better error message */ logmsg (LOG_ERR, "%s:%lu: %s", file, line_no, _("invalid socket address")); continue; } } /* Parse wait/nowait field */ str = strchr (ws.ws_wordv[IFLD_WAIT], '.'); if (str) { size_t n; *str++ = 0; n = strtoul(str, &p, 10); if (*p) logmsg (LOG_WARNING, "%s:%lu: invalid number (near %s)", file, line_no, p); else max_rate = n; } if (strcmp (ws.ws_wordv[IFLD_WAIT], "wait") == 0) flags |= CF_WAIT; else if (strcmp (ws.ws_wordv[IFLD_WAIT], "nowait")) { logmsg (LOG_ERR, "%s:%lu: %s", file, line_no, _("invalid wait field")); pies_url_destroy(&url); continue; } /* Parse user/group */ len = strcspn (ws.ws_wordv[IFLD_USER], ":."); if (ws.ws_wordv[IFLD_USER][len]) { ws.ws_wordv[IFLD_USER][len] = 0; group = ws.ws_wordv[IFLD_USER] + len + 1; } user = ws.ws_wordv[IFLD_USER]; /* Is it a built-in? */ if (strcmp (ws.ws_wordv[IFLD_SERVER_PATH], "internal") == 0) { builtin = inetd_builtin_lookup (service, socket_type); if (!builtin) { logmsg (LOG_ERR, "%s:%lu: %s", file, line_no, _("unknown internal service")); continue; } } else builtin = NULL; /* Create the component */ str = mktag (address, tag); comp = component_create (str); free (str); comp->mode = pies_comp_inetd; comp->socket_type = socket_type; comp->socket_url = url; comp->max_rate = max_rate; if (builtin) { comp->builtin = builtin; comp->flags = builtin->flags; } else comp->flags = flags; if (ISCF_TCPMUX (comp->flags)) comp->tcpmux = mktag (address, "tcpmux"); comp->service = grecs_strdup (service); comp->privs.user = grecs_strdup (user); if (group) { comp->privs.groups = grecs_list_create (); comp->privs.groups->free_entry = listel_dispose; grecs_list_append (comp->privs.groups, grecs_strdup (group)); } comp->program = grecs_strdup (ws.ws_wordv[IFLD_SERVER_PATH]); if (ws.ws_wordc > IFLD_MIN_COUNT) { size_t i, j; comp->argc = ws.ws_wordc - IFLD_MIN_COUNT; comp->argv = grecs_calloc (comp->argc + 1, sizeof (comp->argv[0])); for (i = IFLD_SERVER_ARGS, j = 0; i < ws.ws_wordc; i++, j++) comp->argv[j] = grecs_strdup (ws.ws_wordv[i]); } else { comp->argc = 1; comp->argv = grecs_calloc (comp->argc + 1, sizeof (comp->argv[0])); comp->argv[0] = grecs_strdup (comp->program); } } if (wsflags & WRDSF_REUSE) wordsplit_free (&ws); free (dfl_address); free (buf); fclose (fp); return 0; } #ifndef S_ISLNK # define S_ISLNK(m) 0 #endif #define NAME_INIT_ALLOC 16 static int inetd_conf_dir (const char *name) { DIR *dir; struct dirent *ent; int errs = 0; char *namebuf; size_t namebufsize; size_t namelen; dir = opendir (name); if (!dir) { logmsg (LOG_ERR, _("cannot open directory %s: %s"), name, strerror (errno)); return 1; } namelen = strlen (name); namebufsize = namelen; if (name[namelen-1] != '/') namebufsize++; namebufsize += NAME_INIT_ALLOC; namebuf = grecs_malloc (namebufsize); memcpy (namebuf, name, namelen); if (name[namelen-1] != '/') namebuf[namelen++] = 0; while ((ent = readdir (dir))) { struct stat st; if (stat (ent->d_name, &st)) { logmsg (LOG_ERR, _("cannot stat %s/%s: %s"), name, ent->d_name, strerror (errno)); errs |= 1; } else if (S_ISREG (st.st_mode) || S_ISLNK (st.st_mode)) { size_t len = strlen (ent->d_name); if (namelen + len >= namebufsize) { namebufsize = namelen + len + 1; namebuf = grecs_realloc (namebuf, namebufsize); } strcpy (namebuf + namelen, ent->d_name); errs |= inetd_conf_file (namebuf); } } free (namebuf); closedir (dir); return errs; } int inetd_config_parse (const char *file) { struct stat st; if (stat (file, &st)) { logmsg (LOG_ERR, _("cannot stat %s: %s"), file, strerror (errno)); return 1; } if (S_ISDIR (st.st_mode)) return inetd_conf_dir (file); return inetd_conf_file (file); }