Branch data Line data Source code
1 : : /* SPDX-License-Identifier: LGPL-2.1+ */
2 : :
3 : : #include <getopt.h>
4 : : #include <sys/epoll.h>
5 : : #include <sys/prctl.h>
6 : : #include <sys/socket.h>
7 : : #include <sys/wait.h>
8 : : #include <unistd.h>
9 : :
10 : : #include "sd-daemon.h"
11 : :
12 : : #include "alloc-util.h"
13 : : #include "errno-util.h"
14 : : #include "escape.h"
15 : : #include "fd-util.h"
16 : : #include "log.h"
17 : : #include "macro.h"
18 : : #include "pretty-print.h"
19 : : #include "process-util.h"
20 : : #include "signal-util.h"
21 : : #include "socket-util.h"
22 : : #include "string-util.h"
23 : : #include "strv.h"
24 : : #include "terminal-util.h"
25 : : #include "util.h"
26 : :
27 : : static char **arg_listen = NULL;
28 : : static bool arg_accept = false;
29 : : static int arg_socket_type = SOCK_STREAM;
30 : : static char **arg_args = NULL;
31 : : static char **arg_setenv = NULL;
32 : : static char **arg_fdnames = NULL;
33 : : static bool arg_inetd = false;
34 : :
35 : 0 : static int add_epoll(int epoll_fd, int fd) {
36 : 0 : struct epoll_event ev = {
37 : : .events = EPOLLIN,
38 : : .data.fd = fd,
39 : : };
40 : :
41 [ # # ]: 0 : assert(epoll_fd >= 0);
42 [ # # ]: 0 : assert(fd >= 0);
43 : :
44 [ # # ]: 0 : if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0)
45 [ # # ]: 0 : return log_error_errno(errno, "Failed to add event on epoll fd:%d for fd:%d: %m", epoll_fd, fd);
46 : :
47 : 0 : return 0;
48 : : }
49 : :
50 : 0 : static int open_sockets(int *epoll_fd, bool accept) {
51 : : char **address;
52 : 0 : int n, fd, r, count = 0;
53 : :
54 : 0 : n = sd_listen_fds(true);
55 [ # # ]: 0 : if (n < 0)
56 [ # # ]: 0 : return log_error_errno(n, "Failed to read listening file descriptors from environment: %m");
57 [ # # ]: 0 : if (n > 0) {
58 [ # # ]: 0 : log_info("Received %i descriptors via the environment.", n);
59 : :
60 [ # # ]: 0 : for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) {
61 : 0 : r = fd_cloexec(fd, arg_accept);
62 [ # # ]: 0 : if (r < 0)
63 : 0 : return r;
64 : :
65 : 0 : count++;
66 : : }
67 : : }
68 : :
69 : : /* Close logging and all other descriptors */
70 [ # # ]: 0 : if (arg_listen) {
71 [ # # ]: 0 : _cleanup_free_ int *except = NULL;
72 : : int i;
73 : :
74 : 0 : except = new(int, n);
75 [ # # ]: 0 : if (!except)
76 : 0 : return log_oom();
77 : :
78 [ # # ]: 0 : for (i = 0; i < n; i++)
79 : 0 : except[i] = SD_LISTEN_FDS_START + i;
80 : :
81 : 0 : log_close();
82 : 0 : r = close_all_fds(except, n);
83 [ # # ]: 0 : if (r < 0)
84 [ # # ]: 0 : return log_error_errno(r, "Failed to close all file descriptors: %m");
85 : : }
86 : :
87 : : /** Note: we leak some fd's on error here. I doesn't matter
88 : : * much, since the program will exit immediately anyway, but
89 : : * would be a pain to fix.
90 : : */
91 : :
92 [ # # # # ]: 0 : STRV_FOREACH(address, arg_listen) {
93 : 0 : fd = make_socket_fd(LOG_DEBUG, *address, arg_socket_type, (arg_accept * SOCK_CLOEXEC));
94 [ # # ]: 0 : if (fd < 0) {
95 : 0 : log_open();
96 [ # # ]: 0 : return log_error_errno(fd, "Failed to open '%s': %m", *address);
97 : : }
98 : :
99 [ # # ]: 0 : assert(fd == SD_LISTEN_FDS_START + count);
100 : 0 : count++;
101 : : }
102 : :
103 [ # # ]: 0 : if (arg_listen)
104 : 0 : log_open();
105 : :
106 : 0 : *epoll_fd = epoll_create1(EPOLL_CLOEXEC);
107 [ # # ]: 0 : if (*epoll_fd < 0)
108 [ # # ]: 0 : return log_error_errno(errno, "Failed to create epoll object: %m");
109 : :
110 [ # # ]: 0 : for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + count; fd++) {
111 [ # # ]: 0 : _cleanup_free_ char *name = NULL;
112 : :
113 : 0 : getsockname_pretty(fd, &name);
114 [ # # ]: 0 : log_info("Listening on %s as %i.", strna(name), fd);
115 : :
116 : 0 : r = add_epoll(*epoll_fd, fd);
117 [ # # ]: 0 : if (r < 0)
118 : 0 : return r;
119 : : }
120 : :
121 : 0 : return count;
122 : : }
123 : :
124 : 0 : static int exec_process(const char *name, char **argv, char **env, int start_fd, size_t n_fds) {
125 : :
126 : 0 : _cleanup_strv_free_ char **envp = NULL;
127 : 0 : _cleanup_free_ char *joined = NULL;
128 : 0 : size_t n_env = 0, length;
129 : : const char *tocopy;
130 : : char **s;
131 : : int r;
132 : :
133 [ # # # # ]: 0 : if (arg_inetd && n_fds != 1)
134 [ # # ]: 0 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
135 : : "--inetd only supported for single file descriptors.");
136 : :
137 : 0 : length = strv_length(arg_setenv);
138 : :
139 : : /* PATH, TERM, HOME, USER, LISTEN_FDS, LISTEN_PID, LISTEN_FDNAMES, NULL */
140 [ # # ]: 0 : envp = new0(char *, length + 8);
141 [ # # ]: 0 : if (!envp)
142 : 0 : return log_oom();
143 : :
144 [ # # # # ]: 0 : STRV_FOREACH(s, arg_setenv) {
145 : :
146 [ # # ]: 0 : if (strchr(*s, '=')) {
147 : : char *k;
148 : :
149 : 0 : k = strdup(*s);
150 [ # # ]: 0 : if (!k)
151 : 0 : return log_oom();
152 : :
153 : 0 : envp[n_env++] = k;
154 : : } else {
155 [ # # # ]: 0 : _cleanup_free_ char *p;
156 : : const char *n;
157 : :
158 : 0 : p = strjoin(*s, "=");
159 [ # # ]: 0 : if (!p)
160 : 0 : return log_oom();
161 : :
162 : 0 : n = strv_find_prefix(env, p);
163 [ # # ]: 0 : if (!n)
164 : 0 : continue;
165 : :
166 : 0 : envp[n_env] = strdup(n);
167 [ # # ]: 0 : if (!envp[n_env])
168 : 0 : return log_oom();
169 : :
170 : 0 : n_env++;
171 : : }
172 : : }
173 : :
174 [ # # ]: 0 : FOREACH_STRING(tocopy, "TERM=", "PATH=", "USER=", "HOME=") {
175 : : const char *n;
176 : :
177 : 0 : n = strv_find_prefix(env, tocopy);
178 [ # # ]: 0 : if (!n)
179 : 0 : continue;
180 : :
181 : 0 : envp[n_env] = strdup(n);
182 [ # # ]: 0 : if (!envp[n_env])
183 : 0 : return log_oom();
184 : :
185 : 0 : n_env++;
186 : : }
187 : :
188 [ # # ]: 0 : if (arg_inetd) {
189 [ # # ]: 0 : assert(n_fds == 1);
190 : :
191 : 0 : r = rearrange_stdio(start_fd, start_fd, STDERR_FILENO); /* invalidates start_fd on success + error */
192 [ # # ]: 0 : if (r < 0)
193 [ # # ]: 0 : return log_error_errno(r, "Failed to move fd to stdin+stdout: %m");
194 : :
195 : : } else {
196 [ # # ]: 0 : if (start_fd != SD_LISTEN_FDS_START) {
197 [ # # ]: 0 : assert(n_fds == 1);
198 : :
199 [ # # ]: 0 : if (dup2(start_fd, SD_LISTEN_FDS_START) < 0)
200 [ # # ]: 0 : return log_error_errno(errno, "Failed to dup connection: %m");
201 : :
202 : 0 : safe_close(start_fd);
203 : 0 : start_fd = SD_LISTEN_FDS_START;
204 : : }
205 : :
206 [ # # ]: 0 : if (asprintf((char **) (envp + n_env++), "LISTEN_FDS=%zu", n_fds) < 0)
207 : 0 : return log_oom();
208 : :
209 [ # # ]: 0 : if (asprintf((char **) (envp + n_env++), "LISTEN_PID=" PID_FMT, getpid_cached()) < 0)
210 : 0 : return log_oom();
211 : :
212 [ # # ]: 0 : if (arg_fdnames) {
213 [ # # ]: 0 : _cleanup_free_ char *names = NULL;
214 : : size_t len;
215 : : char *e;
216 : :
217 : 0 : len = strv_length(arg_fdnames);
218 [ # # ]: 0 : if (len == 1) {
219 : : size_t i;
220 : :
221 [ # # ]: 0 : for (i = 1; i < n_fds; i++) {
222 : 0 : r = strv_extend(&arg_fdnames, arg_fdnames[0]);
223 [ # # ]: 0 : if (r < 0)
224 [ # # ]: 0 : return log_error_errno(r, "Failed to extend strv: %m");
225 : : }
226 [ # # ]: 0 : } else if (len != n_fds)
227 [ # # ]: 0 : log_warning("The number of fd names is different than number of fds: %zu vs %zu", len, n_fds);
228 : :
229 : 0 : names = strv_join(arg_fdnames, ":");
230 [ # # ]: 0 : if (!names)
231 : 0 : return log_oom();
232 : :
233 : 0 : e = strjoin("LISTEN_FDNAMES=", names);
234 [ # # ]: 0 : if (!e)
235 : 0 : return log_oom();
236 : :
237 : 0 : envp[n_env++] = e;
238 : : }
239 : : }
240 : :
241 : 0 : joined = strv_join(argv, " ");
242 [ # # ]: 0 : if (!joined)
243 : 0 : return log_oom();
244 : :
245 [ # # ]: 0 : log_info("Execing %s (%s)", name, joined);
246 : 0 : execvpe(name, argv, envp);
247 : :
248 [ # # ]: 0 : return log_error_errno(errno, "Failed to execp %s (%s): %m", name, joined);
249 : : }
250 : :
251 : 0 : static int fork_and_exec_process(const char *child, char **argv, char **env, int fd) {
252 : 0 : _cleanup_free_ char *joined = NULL;
253 : : pid_t child_pid;
254 : : int r;
255 : :
256 : 0 : joined = strv_join(argv, " ");
257 [ # # ]: 0 : if (!joined)
258 : 0 : return log_oom();
259 : :
260 : 0 : r = safe_fork("(activate)",
261 : : FORK_RESET_SIGNALS | FORK_DEATHSIG | FORK_RLIMIT_NOFILE_SAFE | FORK_LOG,
262 : : &child_pid);
263 [ # # ]: 0 : if (r < 0)
264 : 0 : return r;
265 [ # # ]: 0 : if (r == 0) {
266 : : /* In the child */
267 : 0 : exec_process(child, argv, env, fd, 1);
268 : 0 : _exit(EXIT_FAILURE);
269 : : }
270 : :
271 [ # # ]: 0 : log_info("Spawned %s (%s) as PID " PID_FMT ".", child, joined, child_pid);
272 : 0 : return 0;
273 : : }
274 : :
275 : 0 : static int do_accept(const char *name, char **argv, char **envp, int fd) {
276 : 0 : _cleanup_free_ char *local = NULL, *peer = NULL;
277 : 0 : _cleanup_close_ int fd_accepted = -1;
278 : :
279 : 0 : fd_accepted = accept4(fd, NULL, NULL, 0);
280 [ # # ]: 0 : if (fd_accepted < 0) {
281 [ # # ]: 0 : if (ERRNO_IS_ACCEPT_AGAIN(errno))
282 : 0 : return 0;
283 : :
284 [ # # ]: 0 : return log_error_errno(errno, "Failed to accept connection on fd:%d: %m", fd);
285 : : }
286 : :
287 : 0 : (void) getsockname_pretty(fd_accepted, &local);
288 : 0 : (void) getpeername_pretty(fd_accepted, true, &peer);
289 [ # # ]: 0 : log_info("Connection from %s to %s", strna(peer), strna(local));
290 : :
291 : 0 : return fork_and_exec_process(name, argv, envp, fd_accepted);
292 : : }
293 : :
294 : : /* SIGCHLD handler. */
295 : 0 : static void sigchld_hdl(int sig) {
296 : 0 : PROTECT_ERRNO;
297 : :
298 : 0 : for (;;) {
299 : : siginfo_t si;
300 : : int r;
301 : :
302 : 0 : si.si_pid = 0;
303 : 0 : r = waitid(P_ALL, 0, &si, WEXITED | WNOHANG);
304 [ # # ]: 0 : if (r < 0) {
305 [ # # ]: 0 : if (errno != ECHILD)
306 [ # # ]: 0 : log_error_errno(errno, "Failed to reap children: %m");
307 : 0 : return;
308 : : }
309 [ # # ]: 0 : if (si.si_pid == 0)
310 : 0 : return;
311 : :
312 [ # # ]: 0 : log_info("Child %d died with code %d", si.si_pid, si.si_status);
313 : : }
314 : : }
315 : :
316 : 0 : static int install_chld_handler(void) {
317 : : static const struct sigaction act = {
318 : : .sa_flags = SA_NOCLDSTOP | SA_RESTART,
319 : : .sa_handler = sigchld_hdl,
320 : : };
321 : :
322 [ # # ]: 0 : if (sigaction(SIGCHLD, &act, 0) < 0)
323 [ # # ]: 0 : return log_error_errno(errno, "Failed to install SIGCHLD handler: %m");
324 : :
325 : 0 : return 0;
326 : : }
327 : :
328 : 12 : static int help(void) {
329 : 12 : _cleanup_free_ char *link = NULL;
330 : : int r;
331 : :
332 : 12 : r = terminal_urlify_man("systemd-socket-activate", "1", &link);
333 [ - + ]: 12 : if (r < 0)
334 : 0 : return log_oom();
335 : :
336 : 12 : printf("%s [OPTIONS...]\n\n"
337 : : "Listen on sockets and launch child on connection.\n\n"
338 : : "Options:\n"
339 : : " -h --help Show this help and exit\n"
340 : : " --version Print version string and exit\n"
341 : : " -l --listen=ADDR Listen for raw connections at ADDR\n"
342 : : " -d --datagram Listen on datagram instead of stream socket\n"
343 : : " --seqpacket Listen on SOCK_SEQPACKET instead of stream socket\n"
344 : : " -a --accept Spawn separate child for each connection\n"
345 : : " -E --setenv=NAME[=VALUE] Pass an environment variable to children\n"
346 : : " --fdname=NAME[:NAME...] Specify names for file descriptors\n"
347 : : " --inetd Enable inetd file descriptor passing protocol\n"
348 : : "\nNote: file descriptors from sd_listen_fds() will be passed through.\n"
349 : : "\nSee the %s for details.\n"
350 : : , program_invocation_short_name
351 : : , link
352 : : );
353 : :
354 : 12 : return 0;
355 : : }
356 : :
357 : 16 : static int parse_argv(int argc, char *argv[]) {
358 : : enum {
359 : : ARG_VERSION = 0x100,
360 : : ARG_FDNAME,
361 : : ARG_SEQPACKET,
362 : : ARG_INETD,
363 : : };
364 : :
365 : : static const struct option options[] = {
366 : : { "help", no_argument, NULL, 'h' },
367 : : { "version", no_argument, NULL, ARG_VERSION },
368 : : { "datagram", no_argument, NULL, 'd' },
369 : : { "seqpacket", no_argument, NULL, ARG_SEQPACKET },
370 : : { "listen", required_argument, NULL, 'l' },
371 : : { "accept", no_argument, NULL, 'a' },
372 : : { "setenv", required_argument, NULL, 'E' },
373 : : { "environment", required_argument, NULL, 'E' }, /* legacy alias */
374 : : { "fdname", required_argument, NULL, ARG_FDNAME },
375 : : { "inetd", no_argument, NULL, ARG_INETD },
376 : : {}
377 : : };
378 : :
379 : : int c, r;
380 : :
381 [ - + ]: 16 : assert(argc >= 0);
382 [ - + ]: 16 : assert(argv);
383 : :
384 [ + - ]: 16 : while ((c = getopt_long(argc, argv, "+hl:aE:d", options, NULL)) >= 0)
385 [ + - - - : 16 : switch (c) {
- - - - -
+ - ]
386 : 12 : case 'h':
387 : 12 : return help();
388 : :
389 : 0 : case ARG_VERSION:
390 : 0 : return version();
391 : :
392 : 0 : case 'l':
393 : 0 : r = strv_extend(&arg_listen, optarg);
394 [ # # ]: 0 : if (r < 0)
395 : 0 : return log_oom();
396 : :
397 : 0 : break;
398 : :
399 : 0 : case 'd':
400 [ # # ]: 0 : if (arg_socket_type == SOCK_SEQPACKET)
401 [ # # ]: 0 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
402 : : "--datagram may not be combined with --seqpacket.");
403 : :
404 : 0 : arg_socket_type = SOCK_DGRAM;
405 : 0 : break;
406 : :
407 : 0 : case ARG_SEQPACKET:
408 [ # # ]: 0 : if (arg_socket_type == SOCK_DGRAM)
409 [ # # ]: 0 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
410 : : "--seqpacket may not be combined with --datagram.");
411 : :
412 : 0 : arg_socket_type = SOCK_SEQPACKET;
413 : 0 : break;
414 : :
415 : 0 : case 'a':
416 : 0 : arg_accept = true;
417 : 0 : break;
418 : :
419 : 0 : case 'E':
420 : 0 : r = strv_extend(&arg_setenv, optarg);
421 [ # # ]: 0 : if (r < 0)
422 : 0 : return log_oom();
423 : :
424 : 0 : break;
425 : :
426 : 0 : case ARG_FDNAME: {
427 [ # # ]: 0 : _cleanup_strv_free_ char **names;
428 : : char **s;
429 : :
430 : 0 : names = strv_split(optarg, ":");
431 [ # # ]: 0 : if (!names)
432 : 0 : return log_oom();
433 : :
434 [ # # # # ]: 0 : STRV_FOREACH(s, names)
435 [ # # ]: 0 : if (!fdname_is_valid(*s)) {
436 : 0 : _cleanup_free_ char *esc;
437 : :
438 : 0 : esc = cescape(*s);
439 [ # # ]: 0 : log_warning("File descriptor name \"%s\" is not valid.", esc);
440 : : }
441 : :
442 : : /* Empty optargs means one empty name */
443 [ # # ]: 0 : r = strv_extend_strv(&arg_fdnames,
444 : 0 : strv_isempty(names) ? STRV_MAKE("") : names,
445 : : false);
446 [ # # ]: 0 : if (r < 0)
447 [ # # ]: 0 : return log_error_errno(r, "strv_extend_strv: %m");
448 : 0 : break;
449 : : }
450 : :
451 : 0 : case ARG_INETD:
452 : 0 : arg_inetd = true;
453 : 0 : break;
454 : :
455 : 4 : case '?':
456 : 4 : return -EINVAL;
457 : :
458 : 0 : default:
459 : 0 : assert_not_reached("Unhandled option");
460 : : }
461 : :
462 [ # # ]: 0 : if (optind == argc)
463 [ # # ]: 0 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
464 : : "%s: command to execute is missing.",
465 : : program_invocation_short_name);
466 : :
467 [ # # # # ]: 0 : if (arg_socket_type == SOCK_DGRAM && arg_accept)
468 [ # # ]: 0 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
469 : : "Datagram sockets do not accept connections. "
470 : : "The --datagram and --accept options may not be combined.");
471 : :
472 : 0 : arg_args = argv + optind;
473 : :
474 : 0 : return 1 /* work to do */;
475 : : }
476 : :
477 : 16 : int main(int argc, char **argv, char **envp) {
478 : : int r, n;
479 : 16 : int epoll_fd = -1;
480 : :
481 : 16 : log_show_color(true);
482 : 16 : log_parse_environment();
483 : 16 : log_open();
484 : :
485 : 16 : r = parse_argv(argc, argv);
486 [ + - ]: 16 : if (r <= 0)
487 : 16 : return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
488 : :
489 : 0 : r = install_chld_handler();
490 [ # # ]: 0 : if (r < 0)
491 : 0 : return EXIT_FAILURE;
492 : :
493 : 0 : n = open_sockets(&epoll_fd, arg_accept);
494 [ # # ]: 0 : if (n < 0)
495 : 0 : return EXIT_FAILURE;
496 [ # # ]: 0 : if (n == 0) {
497 [ # # ]: 0 : log_error("No sockets to listen on specified or passed in.");
498 : 0 : return EXIT_FAILURE;
499 : : }
500 : :
501 : 0 : for (;;) {
502 : : struct epoll_event event;
503 : :
504 [ # # ]: 0 : if (epoll_wait(epoll_fd, &event, 1, -1) < 0) {
505 [ # # ]: 0 : if (errno == EINTR)
506 : 0 : continue;
507 : :
508 [ # # ]: 0 : log_error_errno(errno, "epoll_wait() failed: %m");
509 : 0 : return EXIT_FAILURE;
510 : : }
511 : :
512 [ # # ]: 0 : log_info("Communication attempt on fd %i.", event.data.fd);
513 [ # # ]: 0 : if (arg_accept) {
514 : 0 : r = do_accept(argv[optind], argv + optind, envp, event.data.fd);
515 [ # # ]: 0 : if (r < 0)
516 : 0 : return EXIT_FAILURE;
517 : : } else
518 : 0 : break;
519 : : }
520 : :
521 : 0 : exec_process(argv[optind], argv + optind, envp, SD_LISTEN_FDS_START, (size_t) n);
522 : :
523 : 0 : return EXIT_SUCCESS;
524 : : }
|