1 /* $OpenBSD$ */
2 
3 /*
4  * Copyright (c) 2022 Nicholas Marriott <nicholas.marriott@gmail.com>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/un.h>
21 
22 #include <systemd/sd-bus.h>
23 #include <systemd/sd-daemon.h>
24 #include <systemd/sd-login.h>
25 #include <systemd/sd-id128.h>
26 
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 
31 #include "tmux.h"
32 
33 int
systemd_activated(void)34 systemd_activated(void)
35 {
36           return (sd_listen_fds(0) >= 1);
37 }
38 
39 int
systemd_create_socket(int flags,char ** cause)40 systemd_create_socket(int flags, char **cause)
41 {
42           int                           fds;
43           int                           fd;
44           struct sockaddr_un  sa;
45           socklen_t           addrlen = sizeof sa;
46 
47           fds = sd_listen_fds(0);
48           if (fds > 1) { /* too many file descriptors */
49                     errno = E2BIG;
50                     goto fail;
51           }
52 
53           if (fds == 1) { /* socket-activated */
54                     fd = SD_LISTEN_FDS_START;
55                     if (!sd_is_socket_unix(fd, SOCK_STREAM, 1, NULL, 0)) {
56                               errno = EPFNOSUPPORT;
57                               goto fail;
58                     }
59                     if (getsockname(fd, (struct sockaddr *)&sa, &addrlen) == -1)
60                               goto fail;
61                     socket_path = xstrdup(sa.sun_path);
62                     return (fd);
63           }
64 
65           return (server_create_socket(flags, cause));
66 
67 fail:
68           if (cause != NULL)
69                     xasprintf(cause, "systemd socket error (%s)", strerror(errno));
70           return (-1);
71 }
72 
73 int
systemd_move_pid_to_new_cgroup(pid_t pid,char ** cause)74 systemd_move_pid_to_new_cgroup(pid_t pid, char **cause)
75 {
76           sd_bus_error         error = SD_BUS_ERROR_NULL;
77           sd_bus_message      *m = NULL, *reply = NULL;
78           sd_bus              *bus = NULL;
79           char                *name, *desc, *slice;
80           sd_id128_t           uuid;
81           int                  r;
82           pid_t                parent_pid;
83 
84           /* Connect to the session bus. */
85           r = sd_bus_default_user(&bus);
86           if (r < 0) {
87                     xasprintf(cause, "failed to connect to session bus: %s",
88                         strerror(-r));
89                     goto finish;
90           }
91 
92           /* Start building the method call. */
93           r = sd_bus_message_new_method_call(bus, &m,
94               "org.freedesktop.systemd1",
95               "/org/freedesktop/systemd1",
96               "org.freedesktop.systemd1.Manager",
97               "StartTransientUnit");
98           if (r < 0) {
99                     xasprintf(cause, "failed to create bus message: %s",
100                         strerror(-r));
101                     goto finish;
102           }
103 
104           /* Generate a unique name for the new scope, to avoid collisions. */
105           r = sd_id128_randomize(&uuid);
106           if (r < 0) {
107                     xasprintf(cause, "failed to generate uuid: %s", strerror(-r));
108                     goto finish;
109           }
110           xasprintf(&name, "tmux-spawn-" SD_ID128_UUID_FORMAT_STR ".scope",
111               SD_ID128_FORMAT_VAL(uuid));
112           r = sd_bus_message_append(m, "s", name);
113           free(name);
114           if (r < 0) {
115                     xasprintf(cause, "failed to append to bus message: %s",
116                         strerror(-r));
117                     goto finish;
118           }
119 
120           /* Mode: fail if there's a queued unit with the same name. */
121           r = sd_bus_message_append(m, "s", "fail");
122           if (r < 0) {
123                     xasprintf(cause, "failed to append to bus message: %s",
124                         strerror(-r));
125                     goto finish;
126           }
127 
128           /* Start properties array. */
129           r = sd_bus_message_open_container(m, 'a', "(sv)");
130           if (r < 0) {
131                     xasprintf(cause, "failed to start properties array: %s",
132                         strerror(-r));
133                     goto finish;
134           }
135 
136           parent_pid = getpid();
137           xasprintf(&desc, "tmux child pane %ld launched by process %ld",
138               (long)pid, (long)parent_pid);
139           r = sd_bus_message_append(m, "(sv)", "Description", "s", desc);
140           free(desc);
141           if (r < 0) {
142                     xasprintf(cause, "failed to append to properties: %s",
143                         strerror(-r));
144                     goto finish;
145           }
146 
147           /*
148            * Make sure that the session shells are terminated with SIGHUP since
149            * bash and friends tend to ignore SIGTERM.
150            */
151           r = sd_bus_message_append(m, "(sv)", "SendSIGHUP", "b", 1);
152           if (r < 0) {
153                     xasprintf(cause, "failed to append to properties: %s",
154                         strerror(-r));
155                     goto finish;
156           }
157 
158           /*
159            * Inherit the slice from the parent process, or default to
160            * "app-tmux.slice" if that fails.
161            */
162           r = sd_pid_get_user_slice(parent_pid, &slice);
163           if (r < 0) {
164                     slice = xstrdup("app-tmux.slice");
165           }
166           r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice);
167           free(slice);
168           if (r < 0) {
169                     xasprintf(cause, "failed to append to properties: %s",
170                         strerror(-r));
171                     goto finish;
172           }
173 
174           /* PIDs to add to the scope: length - 1 array of uint32_t. */
175           r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, pid);
176           if (r < 0) {
177                     xasprintf(cause, "failed to append to properties: %s",
178                         strerror(-r));
179                     goto finish;
180           }
181 
182           /* Clean up the scope even if it fails. */
183           r = sd_bus_message_append(m, "(sv)", "CollectMode", "s",
184               "inactive-or-failed");
185           if (r < 0) {
186                     xasprintf(cause, "failed to append to properties: %s",
187                         strerror(-r));
188                     goto finish;
189           }
190 
191           /* End properties array. */
192           r = sd_bus_message_close_container(m);
193           if (r < 0) {
194                     xasprintf(cause, "failed to end properties array: %s",
195                         strerror(-r));
196                     goto finish;
197           }
198 
199           /* aux is currently unused and should be passed an empty array. */
200           r = sd_bus_message_append(m, "a(sa(sv))", 0);
201           if (r < 0) {
202                     xasprintf(cause, "failed to append to bus message: %s",
203                         strerror(-r));
204                     goto finish;
205           }
206 
207           /* Call the method with a timeout of 1 second = 1e6 us. */
208           r = sd_bus_call(bus, m, 1000000, &error, &reply);
209           if (r < 0) {
210                     if (error.message != NULL) {
211                               /* We have a specific error message from sd-bus. */
212                               xasprintf(cause, "StartTransientUnit call failed: %s",
213                                   error.message);
214                     } else {
215                               xasprintf(cause, "StartTransientUnit call failed: %s",
216                                   strerror(-r));
217                     }
218                     goto finish;
219           }
220 
221 finish:
222           sd_bus_error_free(&error);
223           sd_bus_message_unref(m);
224           sd_bus_message_unref(reply);
225           sd_bus_unref(bus);
226 
227           return (r);
228 }
229