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