/* This file is part of GNU Pies.
Copyright (C) 2009-2020 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))
{
int ec = errno;
char *name = mkfilename (name, ent->d_name, NULL);
logfuncall ("stat", name, ec);
free (name);
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))
{
logfuncall ("stat", file, errno);
return 1;
}
if (S_ISDIR (st.st_mode))
return inetd_conf_dir (file);
return inetd_conf_file (file);
}