1 /* This file is part of GNU Pies.
2    Copyright (C) 2009-2020 Sergey Poznyakoff
3 
4    GNU Pies is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 3, or (at your option)
7    any later version.
8 
9    GNU Pies is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with GNU Pies.  If not, see <http://www.gnu.org/licenses/>. */
16 
17 #include "pies.h"
18 #include <sys/types.h>
19 #include <dirent.h>
20 
21 #define IFLD_SERVICE      0	/* service name */
22 #define IFLD_SOCKET       1	/* socket type */
23 #define IFLD_PROTOCOL     2	/* protocol */
24 #define IFLD_WAIT         3	/* wait/nowait */
25 #define IFLD_USER         4	/* user */
26 #define IFLD_SERVER_PATH  5	/* server program path */
27 #define IFLD_SERVER_ARGS  6	/* server program arguments */
28 
29 #define IFLD_MIN_COUNT   6	/* Minimum number of fields in entry */
30 
31 /* FIXME: Copied from grecs/src/tree.c */
32 static void
listel_dispose(void * el)33 listel_dispose(void *el)
34 {
35   free(el);
36 }
37 
38 #define TCPMUX_PREFIX_STR "tcpmux/"
39 #define TCPMUX_PREFIX_LEN (sizeof(TCPMUX_PREFIX_STR)-1)
40 
41 static int
tcpmux_service(const char * service,char ** psrv,int * pflag)42 tcpmux_service (const char *service, char **psrv, int *pflag)
43 {
44   if (strncmp (service, TCPMUX_PREFIX_STR, TCPMUX_PREFIX_LEN) == 0)
45     {
46       service += TCPMUX_PREFIX_LEN;
47       if (*service == '+')
48 	{
49 	  *pflag |= CF_TCPMUXPLUS;
50 	  service++;
51 	}
52       else
53 	*pflag |= CF_TCPMUX;
54       *psrv = (char *) service;
55       return 1;
56     }
57   return 0;
58 }
59 
60 static char *
mktag(const char * address,const char * service)61 mktag (const char *address, const char *service)
62 {
63   char *str;
64 
65   if (address)
66     {
67       str = grecs_malloc (strlen (address) + 1 + strlen (service) + 1);
68       strcpy (str, address);
69       strcat (str, ":");
70       strcat (str, service);
71     }
72   else
73     str = grecs_strdup (service);
74   return str;
75 }
76 
77 
78 static int
inetd_conf_file(const char * file)79 inetd_conf_file (const char *file)
80 {
81   FILE *fp;
82   size_t size = 0;
83   char *buf = NULL;
84   unsigned long line_no = 0;
85   struct wordsplit ws;
86   char *dfl_address = NULL;
87   int wsflags;
88 
89   fp = fopen (file, "r");
90   if (!fp)
91     {
92       logmsg (LOG_ERR,
93 	      _("cannot open configuration file %s: %s"),
94 	      file, strerror (errno));
95       return 1;
96     }
97 
98   wsflags = WRDSF_NOVAR | WRDSF_NOCMD | WRDSF_WS | WRDSF_SQUEEZE_DELIMS;
99   while (grecs_getline (&buf, &size, fp) >= 0)
100     {
101       char *p;
102       struct component *comp;
103       int socket_type;
104       struct pies_url *url;
105       size_t max_rate = 0;
106       int flags = 0;
107       char *str;
108       char *user = NULL;
109       char *group = NULL;
110       char *address;
111       char *service;
112       size_t len;
113       struct inetd_builtin *builtin;
114       char *tag;
115 
116       line_no++;
117       for (p = buf; *p && c_isblank (*p); p++)
118 	;
119 
120       if (!p || *p == '\n' || *p == '#')
121 	continue;
122 
123       if (wordsplit (p, &ws, wsflags))
124 	{
125 	  logmsg (LOG_ERR, "wordsplit: %s", strerror (errno));
126 	  continue;
127 	}
128       wsflags |= WRDSF_REUSE;
129       if (ws.ws_wordc == 1)
130 	{
131 	  size_t len = strlen (ws.ws_wordv[IFLD_SERVICE]);
132 	  if (len > 0 && ws.ws_wordv[IFLD_SERVICE][len-1] == ':')
133 	    {
134 	      free (dfl_address);
135 
136 	      if (len == 2 && ws.ws_wordv[IFLD_SERVICE][0] == '*')
137 		dfl_address = NULL;
138 	      else
139 		{
140 		  dfl_address = grecs_malloc (len);
141 		  memcpy (dfl_address, ws.ws_wordv[IFLD_SERVICE], len-1);
142 		  dfl_address[len-1] = 0;
143 		}
144 	      continue;
145 	    }
146 	}
147 
148       if (ws.ws_wordc < IFLD_MIN_COUNT)
149 	{
150 	  logmsg (LOG_ERR, "%s:%lu: too few fields", file, line_no);
151 	  continue;
152 	}
153 
154       /* Parse service */
155       str = strchr (ws.ws_wordv[IFLD_SERVICE], ':');
156       if (str)
157 	{
158 	  *str++ = 0;
159 	  address = ws.ws_wordv[IFLD_SERVICE];
160 	  service = str;
161 	}
162       else
163 	{
164 	  address = dfl_address;
165 	  service = ws.ws_wordv[IFLD_SERVICE];
166 	}
167 
168       /* Parse socket type */
169       if (str_to_socket_type (ws.ws_wordv[IFLD_SOCKET], &socket_type))
170 	{
171 	  logmsg (LOG_ERR, "%s:%lu: %s",
172 		  file, line_no, _("bad socket type"));
173 	  continue;
174 	}
175 
176       tag = service;
177       /* Handle eventual "tcpmux/" */
178       if (tcpmux_service (service, &service, &flags))
179 	{
180 	  if (strncmp (ws.ws_wordv[IFLD_PROTOCOL], "tcp", 3))
181 	    {
182 	      logmsg (LOG_ERR, "%s:%lu: %s",
183 		      file, line_no, _("bad protocol for tcpmux service"));
184 	      continue;
185 	    }
186 	  if (socket_type != SOCK_STREAM)
187 	    {
188 	      logmsg (LOG_ERR, "%s:%lu: %s",
189 		      file, line_no, _("bad socket type for tcpmux service"));
190 	      continue;
191 	    }
192 	  url = NULL;
193 	}
194       else
195 	{
196 	  char *s = NULL;
197 	  size_t l = 0;
198 	  int rc;
199 
200 	  /* Create URL from protocol and service fields. */
201 	  if (grecs_asprintf (&s, &l, "inet+%s://%s:%s",
202 			      ws.ws_wordv[IFLD_PROTOCOL],
203 			      address ? address : "0.0.0.0",
204 			      service))
205 	    grecs_alloc_die ();
206 	  rc = pies_url_create (&url, s);
207 	  free (s);
208 	  if (rc)
209 	    {
210 	      /* FIXME: Better error message */
211 	      logmsg (LOG_ERR, "%s:%lu: %s",
212 		      file, line_no, _("invalid socket address"));
213 	      continue;
214 	    }
215 	}
216 
217       /* Parse wait/nowait field */
218       str = strchr (ws.ws_wordv[IFLD_WAIT], '.');
219       if (str)
220 	{
221 	  size_t n;
222 
223 	  *str++ = 0;
224 	  n = strtoul(str, &p, 10);
225 	  if (*p)
226 	    logmsg (LOG_WARNING, "%s:%lu: invalid number (near %s)",
227 		    file, line_no, p);
228 	  else
229 	    max_rate = n;
230 	}
231 
232       if (strcmp (ws.ws_wordv[IFLD_WAIT], "wait") == 0)
233 	flags |= CF_WAIT;
234       else if (strcmp (ws.ws_wordv[IFLD_WAIT], "nowait"))
235 	{
236 	  logmsg (LOG_ERR, "%s:%lu: %s",
237 		  file, line_no, _("invalid wait field"));
238 	  pies_url_destroy(&url);
239 	  continue;
240 	}
241 
242       /* Parse user/group */
243       len = strcspn (ws.ws_wordv[IFLD_USER], ":.");
244       if (ws.ws_wordv[IFLD_USER][len])
245 	{
246 	  ws.ws_wordv[IFLD_USER][len] = 0;
247 	  group = ws.ws_wordv[IFLD_USER] + len + 1;
248 	}
249       user = ws.ws_wordv[IFLD_USER];
250 
251       /* Is it a built-in? */
252       if (strcmp (ws.ws_wordv[IFLD_SERVER_PATH], "internal") == 0)
253 	{
254 	  builtin = inetd_builtin_lookup (service, socket_type);
255 	  if (!builtin)
256 	    {
257 	      logmsg (LOG_ERR, "%s:%lu: %s",
258 		      file, line_no, _("unknown internal service"));
259 	      continue;
260 	    }
261 	}
262       else
263 	builtin = NULL;
264 
265       /* Create the component */
266       str = mktag (address, tag);
267       comp = component_create (str);
268       free (str);
269 
270       comp->mode = pies_comp_inetd;
271       comp->socket_type = socket_type;
272       comp->socket_url = url;
273       comp->max_rate = max_rate;
274       if (builtin)
275 	{
276 	  comp->builtin = builtin;
277 	  comp->flags = builtin->flags;
278 	}
279       else
280 	comp->flags = flags;
281       if (ISCF_TCPMUX (comp->flags))
282 	comp->tcpmux = mktag (address, "tcpmux");
283       comp->service = grecs_strdup (service);
284       comp->privs.user = grecs_strdup (user);
285       if (group)
286 	{
287 	  comp->privs.groups = grecs_list_create ();
288 	  comp->privs.groups->free_entry = listel_dispose;
289 	  grecs_list_append (comp->privs.groups, grecs_strdup (group));
290 	}
291 
292       comp->program = grecs_strdup (ws.ws_wordv[IFLD_SERVER_PATH]);
293 
294       if (ws.ws_wordc > IFLD_MIN_COUNT)
295 	{
296 	  size_t i, j;
297 
298 	  comp->argc = ws.ws_wordc - IFLD_MIN_COUNT;
299 	  comp->argv = grecs_calloc (comp->argc + 1, sizeof (comp->argv[0]));
300 	  for (i = IFLD_SERVER_ARGS, j = 0; i < ws.ws_wordc; i++, j++)
301 	    comp->argv[j] = grecs_strdup (ws.ws_wordv[i]);
302 	}
303       else
304 	{
305 	  comp->argc = 1;
306 	  comp->argv = grecs_calloc (comp->argc + 1, sizeof (comp->argv[0]));
307 	  comp->argv[0] = grecs_strdup (comp->program);
308 	}
309     }
310 
311   if (wsflags & WRDSF_REUSE)
312     wordsplit_free (&ws);
313   free (dfl_address);
314   free (buf);
315   fclose (fp);
316   return 0;
317 }
318 
319 #ifndef S_ISLNK
320 # define S_ISLNK(m) 0
321 #endif
322 #define NAME_INIT_ALLOC 16
323 
324 static int
inetd_conf_dir(const char * name)325 inetd_conf_dir (const char *name)
326 {
327   DIR *dir;
328   struct dirent *ent;
329   int errs = 0;
330   char *namebuf;
331   size_t namebufsize;
332   size_t namelen;
333 
334   dir = opendir (name);
335   if (!dir)
336     {
337       logmsg (LOG_ERR, _("cannot open directory %s: %s"),
338 	      name, strerror (errno));
339       return 1;
340     }
341 
342   namelen = strlen (name);
343   namebufsize = namelen;
344   if (name[namelen-1] != '/')
345     namebufsize++;
346   namebufsize += NAME_INIT_ALLOC;
347   namebuf = grecs_malloc (namebufsize);
348   memcpy (namebuf, name, namelen);
349   if (name[namelen-1] != '/')
350     namebuf[namelen++] = 0;
351 
352   while ((ent = readdir (dir)))
353     {
354       struct stat st;
355 
356       if (stat (ent->d_name, &st))
357 	{
358 	  int ec = errno;
359 	  char *name = mkfilename (name, ent->d_name, NULL);
360 	  logfuncall ("stat", name, ec);
361 	  free (name);
362 	  errs |= 1;
363 	}
364       else if (S_ISREG (st.st_mode) || S_ISLNK (st.st_mode))
365 	{
366 	  size_t len = strlen (ent->d_name);
367 	  if (namelen + len >= namebufsize)
368 	    {
369 	      namebufsize = namelen + len + 1;
370 	      namebuf = grecs_realloc (namebuf, namebufsize);
371 	    }
372 	  strcpy (namebuf + namelen, ent->d_name);
373 	  errs |= inetd_conf_file (namebuf);
374 	}
375     }
376   free (namebuf);
377   closedir (dir);
378   return errs;
379 }
380 
381 int
inetd_config_parse(const char * file)382 inetd_config_parse (const char *file)
383 {
384   struct stat st;
385 
386   if (stat (file, &st))
387     {
388       logfuncall ("stat", file, errno);
389       return 1;
390     }
391   if (S_ISDIR (st.st_mode))
392     return inetd_conf_dir (file);
393 
394   return inetd_conf_file (file);
395 }
396