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