/src/systemd/src/shared/osc-context.c
Line | Count | Source |
1 | | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | | |
3 | | #include <sys/auxv.h> |
4 | | |
5 | | #include "escape.h" |
6 | | #include "format-util.h" |
7 | | #include "hostname-setup.h" |
8 | | #include "id128-util.h" |
9 | | #include "osc-context.h" |
10 | | #include "pidfd-util.h" |
11 | | #include "process-util.h" |
12 | | #include "string-util.h" |
13 | | #include "strv.h" |
14 | | #include "terminal-util.h" |
15 | | #include "user-util.h" |
16 | | |
17 | | /* This currently generates open sequences for OSC 3008 types "boot", "container", "vm", "elevate", "chpriv", |
18 | | * "subcontext", "session", "service". |
19 | | * |
20 | | * NB: Not generated by systemd: "remote" (would have to be generated from the SSH client), "app". */ |
21 | | |
22 | 0 | static int strextend_escaped(char **s, const char *prefix, const char *value) { |
23 | 0 | assert(s); |
24 | 0 | assert(value); |
25 | |
|
26 | 0 | if (!strextend(s, prefix)) |
27 | 0 | return -ENOMEM; |
28 | | |
29 | 0 | _cleanup_free_ char *e = xescape(value, ";\\"); |
30 | 0 | if (!e) |
31 | 0 | return -ENOMEM; |
32 | | |
33 | 0 | if (!strextend(s, e)) |
34 | 0 | return -ENOMEM; |
35 | | |
36 | 0 | return 0; |
37 | 0 | } |
38 | | |
39 | 0 | static int osc_append_identity(char **s) { |
40 | 0 | int r; |
41 | |
|
42 | 0 | assert(s); |
43 | |
|
44 | 0 | _cleanup_free_ char *u = getusername_malloc(); |
45 | 0 | if (u) { |
46 | 0 | r = strextend_escaped(s, ";user=", u); |
47 | 0 | if (r < 0) |
48 | 0 | return r; |
49 | 0 | } |
50 | | |
51 | 0 | _cleanup_free_ char *h = gethostname_malloc(); |
52 | 0 | if (h) { |
53 | 0 | r = strextend_escaped(s, ";hostname=", h); |
54 | 0 | if (r < 0) |
55 | 0 | return r; |
56 | 0 | } |
57 | | |
58 | 0 | sd_id128_t id; |
59 | 0 | if (sd_id128_get_machine(&id) >= 0) { |
60 | 0 | r = strextendf(s, ";machineid=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id)); |
61 | 0 | if (r < 0) |
62 | 0 | return r; |
63 | 0 | } |
64 | | |
65 | 0 | if (sd_id128_get_boot(&id) >= 0) { |
66 | 0 | r = strextendf(s, ";bootid=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id)); |
67 | 0 | if (r < 0) |
68 | 0 | return r; |
69 | 0 | } |
70 | | |
71 | 0 | r = strextendf(s, ";pid=" PID_FMT, getpid_cached()); |
72 | 0 | if (r < 0) |
73 | 0 | return r; |
74 | | |
75 | 0 | uint64_t pidfdid; |
76 | 0 | if (pidfd_get_inode_id_self_cached(&pidfdid) >= 0) { |
77 | 0 | r = strextendf(s, ";pidfdid=%" PRIu64, pidfdid); |
78 | 0 | if (r < 0) |
79 | 0 | return r; |
80 | 0 | } |
81 | | |
82 | 0 | r = strextend_escaped(s, ";comm=", program_invocation_short_name); |
83 | 0 | if (r < 0) |
84 | 0 | return r; |
85 | | |
86 | 0 | return 0; |
87 | 0 | } |
88 | | |
89 | 0 | static void osc_context_default_id(sd_id128_t *ret_id) { |
90 | | |
91 | | /* Usually we only want one context ID per tool. Since we don't want to store the ID let's just hash |
92 | | * one from process credentials */ |
93 | |
|
94 | 0 | struct { |
95 | 0 | const char label[32]; |
96 | 0 | uint64_t pidfdid; |
97 | 0 | uint8_t auxval[16]; |
98 | 0 | pid_t pid; |
99 | 0 | } data = { |
100 | 0 | .label = "systemd context hash bytes v1", |
101 | 0 | .pid = getpid_cached(), |
102 | 0 | }; |
103 | |
|
104 | 0 | assert(ret_id); |
105 | |
|
106 | 0 | memcpy(data.auxval, ULONG_TO_PTR(getauxval(AT_RANDOM)), sizeof(data.auxval)); |
107 | 0 | (void) pidfd_get_inode_id_self_cached(&data.pidfdid); |
108 | |
|
109 | 0 | *ret_id = id128_digest(&data, sizeof(data)); |
110 | 0 | } |
111 | | |
112 | 0 | static int osc_context_intro_raw(sd_id128_t id, char **ret_seq) { |
113 | 0 | int r; |
114 | |
|
115 | 0 | assert(ret_seq); |
116 | |
|
117 | 0 | _cleanup_free_ char *seq = NULL; |
118 | 0 | if (asprintf(&seq, ANSI_OSC "3008;start=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id)) < 0) |
119 | 0 | return -ENOMEM; |
120 | | |
121 | 0 | r = osc_append_identity(&seq); |
122 | 0 | if (r < 0) |
123 | 0 | return r; |
124 | | |
125 | 0 | *ret_seq = TAKE_PTR(seq); |
126 | 0 | return 0; |
127 | 0 | } |
128 | | |
129 | 0 | static int osc_context_intro(char **ret_seq, sd_id128_t *ret_context_id) { |
130 | 0 | int r; |
131 | |
|
132 | 0 | assert(ret_seq); |
133 | | |
134 | | /* If the user passed us a buffer for the context ID generate a randomized one, since we have a place |
135 | | * to store it. The user should pass the ID back to osc_context_close() later on. If the user did not |
136 | | * pass us a buffer, we'll use a session ID hashed from process properties that remain stable as long |
137 | | * our process exists. It hence also remains stable across reexec and similar. */ |
138 | 0 | sd_id128_t id; |
139 | 0 | if (ret_context_id) { |
140 | 0 | r = sd_id128_randomize(&id); |
141 | 0 | if (r < 0) |
142 | 0 | return r; |
143 | 0 | } else |
144 | 0 | osc_context_default_id(&id); |
145 | | |
146 | 0 | r = osc_context_intro_raw(id, ret_seq); |
147 | 0 | if (r < 0) |
148 | 0 | return r; |
149 | | |
150 | 0 | if (ret_context_id) |
151 | 0 | *ret_context_id = id; |
152 | |
|
153 | 0 | return 0; |
154 | 0 | } |
155 | | |
156 | 0 | static int osc_context_outro(char *_seq, sd_id128_t id, char **ret_seq, sd_id128_t *ret_context_id) { |
157 | 0 | _cleanup_free_ char *seq = TAKE_PTR(_seq); /* We take possession of the string no matter what */ |
158 | |
|
159 | 0 | if (ret_seq) |
160 | 0 | *ret_seq = TAKE_PTR(seq); |
161 | 0 | else { |
162 | 0 | fputs(seq, stdout); |
163 | 0 | fflush(stdout); |
164 | 0 | } |
165 | |
|
166 | 0 | if (ret_context_id) |
167 | 0 | *ret_context_id = id; |
168 | |
|
169 | 0 | return 0; |
170 | 0 | } |
171 | | |
172 | 0 | int osc_context_open_boot(char **ret_seq) { |
173 | 0 | int r; |
174 | |
|
175 | 0 | _cleanup_free_ char *seq = NULL; |
176 | 0 | r = osc_context_intro(&seq, /* ret_context_id= */ NULL); |
177 | 0 | if (r < 0) |
178 | 0 | return r; |
179 | | |
180 | 0 | if (!strextend(&seq, ";type=boot" ANSI_ST)) |
181 | 0 | return -ENOMEM; |
182 | | |
183 | 0 | return osc_context_outro(TAKE_PTR(seq), /* context_id= */ SD_ID128_NULL, ret_seq, /* ret_context_id= */ NULL); |
184 | 0 | } |
185 | | |
186 | 0 | int osc_context_open_container(const char *name, char **ret_seq, sd_id128_t *ret_context_id) { |
187 | 0 | int r; |
188 | |
|
189 | 0 | _cleanup_free_ char *seq = NULL; |
190 | 0 | sd_id128_t id = SD_ID128_NULL; |
191 | 0 | r = osc_context_intro(&seq, ret_context_id ? &id : NULL); |
192 | 0 | if (r < 0) |
193 | 0 | return r; |
194 | | |
195 | 0 | if (name) { |
196 | 0 | r = strextend_escaped(&seq, ";container=", name); |
197 | 0 | if (r < 0) |
198 | 0 | return r; |
199 | 0 | } |
200 | | |
201 | 0 | if (!strextend(&seq, ";type=container" ANSI_ST)) |
202 | 0 | return -ENOMEM; |
203 | | |
204 | 0 | return osc_context_outro(TAKE_PTR(seq), id, ret_seq, ret_context_id); |
205 | 0 | } |
206 | | |
207 | 0 | int osc_context_open_vm(const char *name, char **ret_seq, sd_id128_t *ret_context_id) { |
208 | 0 | int r; |
209 | |
|
210 | 0 | assert(name); |
211 | |
|
212 | 0 | _cleanup_free_ char *seq = NULL; |
213 | 0 | sd_id128_t id = SD_ID128_NULL; |
214 | 0 | r = osc_context_intro(&seq, ret_context_id ? &id : NULL); |
215 | 0 | if (r < 0) |
216 | 0 | return r; |
217 | | |
218 | 0 | r = strextend_escaped(&seq, ";vm=", name); |
219 | 0 | if (r < 0) |
220 | 0 | return r; |
221 | | |
222 | 0 | if (!strextend(&seq, ";type=vm" ANSI_ST)) |
223 | 0 | return -ENOMEM; |
224 | | |
225 | 0 | return osc_context_outro(TAKE_PTR(seq), id, ret_seq, ret_context_id); |
226 | 0 | } |
227 | | |
228 | 0 | int osc_context_open_chpriv(const char *target_user, char **ret_seq, sd_id128_t *ret_context_id) { |
229 | 0 | int r; |
230 | |
|
231 | 0 | assert(target_user); |
232 | |
|
233 | 0 | _cleanup_free_ char *seq = NULL; |
234 | 0 | sd_id128_t id = SD_ID128_NULL; |
235 | 0 | r = osc_context_intro(&seq, ret_context_id ? &id : NULL); |
236 | 0 | if (r < 0) |
237 | 0 | return r; |
238 | | |
239 | 0 | if (is_this_me(target_user) > 0) { |
240 | 0 | if (!strextend(&seq, ";type=subcontext" ANSI_ST)) |
241 | 0 | return -ENOMEM; |
242 | 0 | } else if (STR_IN_SET(target_user, "root", "0")) { |
243 | 0 | if (!strextend(&seq, ";type=elevate" ANSI_ST)) |
244 | 0 | return -ENOMEM; |
245 | 0 | } else { |
246 | 0 | r = strextend_escaped(&seq, ";targetuser=", target_user); |
247 | 0 | if (r < 0) |
248 | 0 | return r; |
249 | | |
250 | 0 | if (!strextend(&seq, ";type=chpriv" ANSI_ST)) |
251 | 0 | return -ENOMEM; |
252 | 0 | } |
253 | | |
254 | 0 | return osc_context_outro(TAKE_PTR(seq), id, ret_seq, ret_context_id); |
255 | 0 | } |
256 | | |
257 | 0 | int osc_context_open_session(const char *user, const char *session_id, char **ret_seq, sd_id128_t *ret_context_id) { |
258 | 0 | int r; |
259 | |
|
260 | 0 | _cleanup_free_ char *seq = NULL; |
261 | 0 | sd_id128_t id = SD_ID128_NULL; |
262 | 0 | r = osc_context_intro(&seq, ret_context_id ? &id : NULL); |
263 | 0 | if (r < 0) |
264 | 0 | return r; |
265 | | |
266 | 0 | if (user) { |
267 | 0 | r = strextend_escaped(&seq, ";targetuser=", user); |
268 | 0 | if (r < 0) |
269 | 0 | return r; |
270 | 0 | } |
271 | | |
272 | 0 | if (session_id) { |
273 | 0 | r = strextend_escaped(&seq, ";sessionid=", session_id); |
274 | 0 | if (r < 0) |
275 | 0 | return r; |
276 | 0 | } |
277 | | |
278 | 0 | if (!strextend(&seq, ";type=session" ANSI_ST)) |
279 | 0 | return -ENOMEM; |
280 | | |
281 | 0 | return osc_context_outro(TAKE_PTR(seq), id, ret_seq, ret_context_id); |
282 | 0 | } |
283 | | |
284 | 0 | int osc_context_open_service(const char *unit, sd_id128_t invocation_id, char **ret_seq) { |
285 | 0 | int r; |
286 | |
|
287 | 0 | sd_id128_t id = SD_ID128_NULL; |
288 | 0 | r = osc_context_id_from_invocation_id(invocation_id, &id); |
289 | 0 | if (r < 0) |
290 | 0 | return r; |
291 | | |
292 | 0 | _cleanup_free_ char *seq = NULL; |
293 | 0 | r = osc_context_intro_raw(id, &seq); |
294 | 0 | if (r < 0) |
295 | 0 | return r; |
296 | | |
297 | 0 | if (unit) { |
298 | 0 | r = strextend_escaped(&seq, ";servicename=", unit); |
299 | 0 | if (r < 0) |
300 | 0 | return r; |
301 | 0 | } |
302 | | |
303 | 0 | r = strextendf(&seq, ";invocationid=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(invocation_id)); |
304 | 0 | if (r < 0) |
305 | 0 | return r; |
306 | | |
307 | 0 | if (!strextend(&seq, ";type=service" ANSI_ST)) |
308 | 0 | return -ENOMEM; |
309 | | |
310 | 0 | return osc_context_outro(TAKE_PTR(seq), id, ret_seq, /* ret_context_id= */ NULL); |
311 | 0 | } |
312 | | |
313 | 0 | int osc_context_close(sd_id128_t id, char **ret_seq) { |
314 | |
|
315 | 0 | if (sd_id128_is_null(id)) { |
316 | | /* nil uuid: no session opened */ |
317 | 0 | if (ret_seq) |
318 | 0 | *ret_seq = NULL; |
319 | 0 | return 0; |
320 | 0 | } |
321 | | |
322 | 0 | if (sd_id128_is_allf(id)) /* max uuid: default session opened */ |
323 | 0 | osc_context_default_id(&id); |
324 | |
|
325 | 0 | _cleanup_free_ char *seq = NULL; |
326 | 0 | if (asprintf(&seq, ANSI_OSC "3008;end=" SD_ID128_FORMAT_STR ANSI_ST, SD_ID128_FORMAT_VAL(id)) < 0) |
327 | 0 | return -ENOMEM; |
328 | | |
329 | 0 | return osc_context_outro(TAKE_PTR(seq), SD_ID128_NULL, ret_seq, /* ret_context_id= */ NULL); |
330 | 0 | } |
331 | | |
332 | 0 | int osc_context_id_from_invocation_id(sd_id128_t id, sd_id128_t *ret) { |
333 | 0 | static const sd_id128_t app_id = SD_ID128_MAKE(5d,63,a5,8d,96,fd,45,d0,a0,f0,63,50,fc,d8,9a,cd); |
334 | |
|
335 | 0 | assert(ret); |
336 | | |
337 | | /* Hash the context id from the invocation id, so that we don't have to store the context id (as long |
338 | | * as we have the invocation ID). We don't use the invocation ID literally in order not to make it too |
339 | | * easy to cancel our service context because the invocation ID is reported literally via the |
340 | | * $INVOCATION_ID env var. */ |
341 | 0 | return sd_id128_get_app_specific(id, app_id, ret); |
342 | 0 | } |