]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/creds/creds.c
creds-tool: add --user/--uid= to operate with scoped credentials
[thirdparty/systemd.git] / src / creds / creds.c
CommitLineData
5945640e
LP
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include <getopt.h>
3c9fbb99 4#include <unistd.h>
5945640e 5
d6b4d1c7 6#include "build.h"
caef0bc3 7#include "bus-polkit.h"
5945640e
LP
8#include "creds-util.h"
9#include "dirent-util.h"
10#include "escape.h"
11#include "fileio.h"
12#include "format-table.h"
13#include "hexdecoct.h"
14#include "io-util.h"
15#include "json.h"
16#include "main-func.h"
17#include "memory-util.h"
18#include "missing_magic.h"
19#include "pager.h"
20#include "parse-argument.h"
21#include "pretty-print.h"
22#include "process-util.h"
23#include "stat-util.h"
24#include "string-table.h"
25#include "terminal-util.h"
e0e1f4f7 26#include "tpm2-pcr.h"
5945640e 27#include "tpm2-util.h"
644f19c7
LP
28#include "user-util.h"
29#include "varlink.h"
30#include "varlink-io.systemd.Credentials.h"
5945640e
LP
31#include "verbs.h"
32
33typedef enum TranscodeMode {
34 TRANSCODE_OFF,
35 TRANSCODE_BASE64,
36 TRANSCODE_UNBASE64,
37 TRANSCODE_HEX,
38 TRANSCODE_UNHEX,
39 _TRANSCODE_MAX,
40 _TRANSCODE_INVALID = -EINVAL,
41} TranscodeMode;
42
43static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
44static PagerFlags arg_pager_flags = 0;
45static bool arg_legend = true;
46static bool arg_system = false;
47static TranscodeMode arg_transcode = TRANSCODE_OFF;
48static int arg_newline = -1;
571d829e 49static sd_id128_t arg_with_key = _CRED_AUTO;
5945640e
LP
50static const char *arg_tpm2_device = NULL;
51static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
75ddec93
LP
52static char *arg_tpm2_public_key = NULL;
53static uint32_t arg_tpm2_public_key_pcr_mask = UINT32_MAX;
54static char *arg_tpm2_signature = NULL;
5945640e
LP
55static const char *arg_name = NULL;
56static bool arg_name_any = false;
57static usec_t arg_timestamp = USEC_INFINITY;
58static usec_t arg_not_after = USEC_INFINITY;
59static bool arg_pretty = false;
6e0cb815 60static bool arg_quiet = false;
644f19c7 61static bool arg_varlink = false;
c85b68f6 62static uid_t arg_uid = UID_INVALID;
5945640e 63
75ddec93
LP
64STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep);
65STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep);
66
5945640e
LP
67static const char* transcode_mode_table[_TRANSCODE_MAX] = {
68 [TRANSCODE_OFF] = "off",
69 [TRANSCODE_BASE64] = "base64",
70 [TRANSCODE_UNBASE64] = "unbase64",
71 [TRANSCODE_HEX] = "hex",
72 [TRANSCODE_UNHEX] = "unhex",
73};
74
75DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(transcode_mode, TranscodeMode);
76
05eb896f 77static int open_credential_directory(
07a442ef 78 bool encrypted,
05eb896f 79 DIR **ret_dir,
07a442ef 80 const char **ret_prefix) {
05eb896f 81
5945640e
LP
82 const char *p;
83 DIR *d;
84 int r;
85
05eb896f
LP
86 assert(ret_dir);
87
88 if (arg_system)
89 /* PID 1 ensures that system credentials are always accessible under the same fixed path. It
90 * will create symlinks if necessary to guarantee that. */
91 p = encrypted ?
92 ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY :
93 SYSTEM_CREDENTIALS_DIRECTORY;
94 else {
95 /* Otherwise take the dirs from the env vars we got passed */
96 r = (encrypted ? get_encrypted_credentials_dir : get_credentials_dir)(&p);
97 if (r == -ENXIO) /* No environment variable? */
98 goto not_found;
5945640e 99 if (r < 0)
05eb896f
LP
100 return log_error_errno(r, "Failed to get credentials directory: %m");
101 }
5945640e 102
05eb896f
LP
103 d = opendir(p);
104 if (!d) {
105 /* No such dir? Then no creds where passed. (We conditionalize this on arg_system, since for
106 * the per-service case a non-existing path would indicate an issue since the env var would
107 * be set incorrectly in that case.) */
108 if (arg_system && errno == ENOENT)
109 goto not_found;
110
111 return log_error_errno(errno, "Failed to open credentials directory '%s': %m", p);
112 }
5945640e 113
05eb896f 114 *ret_dir = d;
5945640e 115
05eb896f
LP
116 if (ret_prefix)
117 *ret_prefix = p;
5945640e 118
05eb896f
LP
119 return 1;
120
121not_found:
122 *ret_dir = NULL;
123
124 if (ret_prefix)
125 *ret_prefix = NULL;
5945640e 126
5945640e
LP
127 return 0;
128}
129
05eb896f 130static int add_credentials_to_table(Table *t, bool encrypted) {
5d2a48da 131 _cleanup_closedir_ DIR *d = NULL;
05eb896f 132 const char *prefix;
5945640e
LP
133 int r;
134
05eb896f 135 assert(t);
5945640e 136
07a442ef 137 r = open_credential_directory(encrypted, &d, &prefix);
05eb896f
LP
138 if (r < 0)
139 return r;
140 if (!d)
141 return 0; /* No creds dir set */
5945640e
LP
142
143 for (;;) {
05eb896f 144 _cleanup_free_ char *j = NULL;
5945640e 145 const char *secure, *secure_color = NULL;
254d1313 146 _cleanup_close_ int fd = -EBADF;
5945640e
LP
147 struct dirent *de;
148 struct stat st;
149
150 errno = 0;
151 de = readdir_no_dot(d);
152 if (!de) {
153 if (errno == 0)
154 break;
155
156 return log_error_errno(errno, "Failed to read credentials directory: %m");
157 }
158
159 if (!IN_SET(de->d_type, DT_REG, DT_UNKNOWN))
160 continue;
161
162 if (!credential_name_valid(de->d_name))
163 continue;
164
165 fd = openat(dirfd(d), de->d_name, O_PATH|O_CLOEXEC|O_NOFOLLOW);
166 if (fd < 0) {
167 if (errno == ENOENT) /* Vanished by now? */
168 continue;
169
170 return log_error_errno(errno, "Failed to open credential '%s': %m", de->d_name);
171 }
172
173 if (fstat(fd, &st) < 0)
174 return log_error_errno(errno, "Failed to stat credential '%s': %m", de->d_name);
175
176 if (!S_ISREG(st.st_mode))
177 continue;
178
05eb896f
LP
179 if (encrypted) {
180 secure = "encrypted";
181 secure_color = ansi_highlight_green();
182 } else if ((st.st_mode & 0377) != 0) {
5945640e
LP
183 secure = "insecure"; /* Anything that is accessible more than read-only to its owner is insecure */
184 secure_color = ansi_highlight_red();
185 } else {
186 r = fd_is_fs_type(fd, RAMFS_MAGIC);
187 if (r < 0)
188 return log_error_errno(r, "Failed to determine backing file system of '%s': %m", de->d_name);
189
79de6eb1
MY
190 secure = r > 0 ? "secure" : "weak"; /* ramfs is not swappable, hence "secure", everything else is "weak" */
191 secure_color = r > 0 ? ansi_highlight_green() : ansi_highlight_yellow4();
5945640e
LP
192 }
193
05eb896f
LP
194 j = path_join(prefix, de->d_name);
195 if (!j)
196 return log_oom();
197
5945640e
LP
198 r = table_add_many(
199 t,
200 TABLE_STRING, de->d_name,
201 TABLE_STRING, secure,
202 TABLE_SET_COLOR, secure_color,
05eb896f
LP
203 TABLE_SIZE, (uint64_t) st.st_size,
204 TABLE_STRING, j);
5945640e
LP
205 if (r < 0)
206 return table_log_add_error(r);
207 }
208
05eb896f
LP
209 return 1; /* Creds dir set */
210}
211
212static int verb_list(int argc, char **argv, void *userdata) {
213 _cleanup_(table_unrefp) Table *t = NULL;
214 int r, q;
215
216 t = table_new("name", "secure", "size", "path");
217 if (!t)
218 return log_oom();
219
220 (void) table_set_align_percent(t, table_get_cell(t, 0, 2), 100);
221
222 r = add_credentials_to_table(t, /* encrypted= */ true);
223 if (r < 0)
224 return r;
225
226 q = add_credentials_to_table(t, /* encrypted= */ false);
227 if (q < 0)
228 return q;
229
230 if (r == 0 && q == 0) {
231 if (arg_system)
232 return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No credentials passed to system.");
233
234 return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No credentials passed. (i.e. $CREDENTIALS_DIRECTORY not set.)");
235 }
236
2413a0fa 237 if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF) && table_isempty(t)) {
5945640e
LP
238 log_info("No credentials");
239 return 0;
240 }
241
242 return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
243}
244
245static int transcode(
246 const void *input,
247 size_t input_size,
248 void **ret_output,
249 size_t *ret_output_size) {
250
251 int r;
252
253 assert(input);
254 assert(input_size);
255 assert(ret_output);
256 assert(ret_output_size);
257
258 switch (arg_transcode) {
259
260 case TRANSCODE_BASE64: {
bc1f27ff 261 char *buf;
5945640e
LP
262 ssize_t l;
263
264 l = base64mem_full(input, input_size, 79, &buf);
265 if (l < 0)
266 return l;
267
268 *ret_output = buf;
269 *ret_output_size = l;
270 return 0;
271 }
272
273 case TRANSCODE_UNBASE64:
274 r = unbase64mem_full(input, input_size, true, ret_output, ret_output_size);
275 if (r == -EPIPE) /* Uneven number of chars */
276 return -EINVAL;
277
278 return r;
279
280 case TRANSCODE_HEX: {
281 char *buf;
282
283 buf = hexmem(input, input_size);
284 if (!buf)
285 return -ENOMEM;
286
287 *ret_output = buf;
288 *ret_output_size = input_size * 2;
289 return 0;
290 }
291
292 case TRANSCODE_UNHEX:
293 r = unhexmem_full(input, input_size, true, ret_output, ret_output_size);
294 if (r == -EPIPE) /* Uneven number of chars */
295 return -EINVAL;
296
297 return r;
298
299 default:
04499a70 300 assert_not_reached();
5945640e
LP
301 }
302}
303
304static int print_newline(FILE *f, const char *data, size_t l) {
305 int fd;
306
307 assert(f);
308 assert(data || l == 0);
309
310 /* If turned off explicitly, don't print newline */
311 if (arg_newline == 0)
312 return 0;
313
314 /* If data already has newline, don't print either */
315 if (l > 0 && data[l-1] == '\n')
316 return 0;
317
318 /* Don't bother unless this is a tty */
319 fd = fileno(f);
dd9c8da8 320 if (fd >= 0 && !isatty_safe(fd))
5945640e
LP
321 return 0;
322
323 if (fputc('\n', f) != '\n')
324 return log_error_errno(errno, "Failed to write trailing newline: %m");
325
326 return 1;
327}
328
329static int write_blob(FILE *f, const void *data, size_t size) {
330 _cleanup_(erase_and_freep) void *transcoded = NULL;
331 int r;
332
333 if (arg_transcode == TRANSCODE_OFF &&
334 arg_json_format_flags != JSON_FORMAT_OFF) {
5945640e
LP
335 _cleanup_(erase_and_freep) char *suffixed = NULL;
336 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
337
7153213e
LP
338 r = make_cstring(data, size, MAKE_CSTRING_REFUSE_TRAILING_NUL, &suffixed);
339 if (r < 0)
340 return log_error_errno(r, "Unable to convert binary string to C string: %m");
5945640e
LP
341
342 r = json_parse(suffixed, JSON_PARSE_SENSITIVE, &v, NULL, NULL);
343 if (r < 0)
344 return log_error_errno(r, "Failed to parse JSON: %m");
345
346 json_variant_dump(v, arg_json_format_flags, f, NULL);
347 return 0;
348 }
349
350 if (arg_transcode != TRANSCODE_OFF) {
351 r = transcode(data, size, &transcoded, &size);
352 if (r < 0)
353 return log_error_errno(r, "Failed to transcode data: %m");
354
355 data = transcoded;
356 }
357
358 if (fwrite(data, 1, size, f) != size)
513412a6 359 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write credential data.");
5945640e
LP
360
361 r = print_newline(f, data, size);
362 if (r < 0)
363 return r;
364
f9568765
ZJS
365 r = fflush_and_check(f);
366 if (r < 0)
367 return log_error_errno(r, "Failed to flush output: %m");
5945640e
LP
368
369 return 0;
370}
371
372static int verb_cat(int argc, char **argv, void *userdata) {
05eb896f 373 usec_t timestamp;
5945640e 374 int r, ret = 0;
5945640e 375
05eb896f 376 timestamp = arg_timestamp != USEC_INFINITY ? arg_timestamp : now(CLOCK_REALTIME);
5945640e
LP
377
378 STRV_FOREACH(cn, strv_skip(argv, 1)) {
379 _cleanup_(erase_and_freep) void *data = NULL;
380 size_t size = 0;
05eb896f 381 int encrypted;
5945640e
LP
382
383 if (!credential_name_valid(*cn)) {
384 log_error("Credential name '%s' is not valid.", *cn);
385 if (ret >= 0)
386 ret = -EINVAL;
387 continue;
388 }
389
05eb896f 390 /* Look both in regular and in encrypted credentials */
07a442ef 391 for (encrypted = 0; encrypted < 2; encrypted++) {
5d2a48da 392 _cleanup_closedir_ DIR *d = NULL;
05eb896f 393
07a442ef 394 r = open_credential_directory(encrypted, &d, NULL);
05eb896f
LP
395 if (r < 0)
396 return log_error_errno(r, "Failed to open credentials directory: %m");
397 if (!d) /* Not set */
398 continue;
399
400 r = read_full_file_full(
401 dirfd(d), *cn,
402 UINT64_MAX, SIZE_MAX,
403 READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE,
404 NULL,
405 (char**) &data, &size);
406 if (r == -ENOENT) /* Not found */
407 continue;
408 if (r >= 0) /* Found */
409 break;
410
5945640e
LP
411 log_error_errno(r, "Failed to read credential '%s': %m", *cn);
412 if (ret >= 0)
413 ret = r;
05eb896f
LP
414 }
415
416 if (encrypted >= 2) { /* Found nowhere */
417 log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Credential '%s' not set.", *cn);
418 if (ret >= 0)
419 ret = -ENOENT;
420
5945640e
LP
421 continue;
422 }
423
05eb896f 424 if (encrypted) {
8d042bc4 425 _cleanup_(iovec_done_erase) struct iovec plaintext = {};
05eb896f
LP
426
427 r = decrypt_credential_and_warn(
428 *cn,
429 timestamp,
430 arg_tpm2_device,
75ddec93 431 arg_tpm2_signature,
c85b68f6 432 uid_is_valid(arg_uid) ? arg_uid : getuid(),
8d042bc4 433 &IOVEC_MAKE(data, size),
48d67957 434 CREDENTIAL_ANY_SCOPE,
8d042bc4 435 &plaintext);
05eb896f
LP
436 if (r < 0)
437 return r;
438
439 erase_and_free(data);
8d042bc4
LP
440 data = TAKE_PTR(plaintext.iov_base);
441 size = plaintext.iov_len;
05eb896f
LP
442 }
443
5945640e
LP
444 r = write_blob(stdout, data, size);
445 if (r < 0)
446 return r;
447 }
448
449 return ret;
450}
451
452static int verb_encrypt(int argc, char **argv, void *userdata) {
8d042bc4 453 _cleanup_(iovec_done_erase) struct iovec plaintext = {}, output = {};
5945640e 454 _cleanup_free_ char *base64_buf = NULL, *fname = NULL;
5945640e 455 const char *input_path, *output_path, *name;
5945640e
LP
456 ssize_t base64_size;
457 usec_t timestamp;
458 int r;
459
460 assert(argc == 3);
461
2f092762 462 input_path = empty_or_dash(argv[1]) ? NULL : argv[1];
5945640e
LP
463
464 if (input_path)
8d042bc4 465 r = read_full_file_full(AT_FDCWD, input_path, UINT64_MAX, CREDENTIAL_SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, (char**) &plaintext.iov_base, &plaintext.iov_len);
5945640e 466 else
8d042bc4 467 r = read_full_stream_full(stdin, NULL, UINT64_MAX, CREDENTIAL_SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER, (char**) &plaintext.iov_base, &plaintext.iov_len);
5945640e
LP
468 if (r == -E2BIG)
469 return log_error_errno(r, "Plaintext too long for credential (allowed size: %zu).", (size_t) CREDENTIAL_SIZE_MAX);
470 if (r < 0)
471 return log_error_errno(r, "Failed to read plaintext: %m");
472
2f092762 473 output_path = empty_or_dash(argv[2]) ? NULL : argv[2];
5945640e
LP
474
475 if (arg_name_any)
476 name = NULL;
477 else if (arg_name)
478 name = arg_name;
479 else if (output_path) {
480 r = path_extract_filename(output_path, &fname);
481 if (r < 0)
482 return log_error_errno(r, "Failed to extract filename from '%s': %m", output_path);
483 if (r == O_DIRECTORY)
484 return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Path '%s' refers to directory, refusing.", output_path);
485
486 name = fname;
487 } else {
488 log_warning("No credential name specified, not embedding credential name in encrypted data. (Disable this warning with --name=)");
489 name = NULL;
490 }
491
492 timestamp = arg_timestamp != USEC_INFINITY ? arg_timestamp : now(CLOCK_REALTIME);
493
494 if (arg_not_after != USEC_INFINITY && arg_not_after < timestamp)
495 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential is invalidated before it is valid.");
496
497 r = encrypt_credential_and_warn(
498 arg_with_key,
499 name,
500 timestamp,
501 arg_not_after,
502 arg_tpm2_device,
503 arg_tpm2_pcr_mask,
75ddec93
LP
504 arg_tpm2_public_key,
505 arg_tpm2_public_key_pcr_mask,
c85b68f6 506 arg_uid,
8d042bc4 507 &plaintext,
9c3d8db9 508 /* flags= */ 0,
8d042bc4 509 &output);
5945640e
LP
510 if (r < 0)
511 return r;
512
8d042bc4 513 base64_size = base64mem_full(output.iov_base, output.iov_len, arg_pretty ? 69 : 79, &base64_buf);
5945640e
LP
514 if (base64_size < 0)
515 return base64_size;
516
c74e13a5
FS
517 /* Pretty print makes sense only if we're printing stuff to stdout
518 * and if a cred name is provided via --name= (since we can't use
519 * the output file name as the cred name here) */
520 if (arg_pretty && !output_path && name) {
5945640e
LP
521 _cleanup_free_ char *escaped = NULL, *indented = NULL, *j = NULL;
522
c74e13a5
FS
523 escaped = cescape(name);
524 if (!escaped)
525 return log_oom();
5945640e
LP
526
527 indented = strreplace(base64_buf, "\n", " \\\n ");
528 if (!indented)
529 return log_oom();
530
c74e13a5 531 j = strjoin("SetCredentialEncrypted=", escaped, ": \\\n ", indented, "\n");
5945640e
LP
532 if (!j)
533 return log_oom();
534
535 free_and_replace(base64_buf, j);
536 }
537
538 if (output_path)
539 r = write_string_file(output_path, base64_buf, WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_CREATE);
540 else
541 r = write_string_stream(stdout, base64_buf, 0);
542 if (r < 0)
543 return log_error_errno(r, "Failed to write result: %m");
544
545 return EXIT_SUCCESS;
546}
547
548static int verb_decrypt(int argc, char **argv, void *userdata) {
8d042bc4
LP
549 _cleanup_(iovec_done_erase) struct iovec input = {}, plaintext = {};
550 _cleanup_free_ char *fname = NULL;
5945640e
LP
551 _cleanup_fclose_ FILE *output_file = NULL;
552 const char *input_path, *output_path, *name;
5945640e
LP
553 usec_t timestamp;
554 FILE *f;
555 int r;
556
557 assert(IN_SET(argc, 2, 3));
558
2f092762 559 input_path = empty_or_dash(argv[1]) ? NULL : argv[1];
5945640e
LP
560
561 if (input_path)
8d042bc4 562 r = read_full_file_full(AT_FDCWD, argv[1], UINT64_MAX, CREDENTIAL_ENCRYPTED_SIZE_MAX, READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, (char**) &input, &input.iov_len);
5945640e 563 else
8d042bc4 564 r = read_full_stream_full(stdin, NULL, UINT64_MAX, CREDENTIAL_ENCRYPTED_SIZE_MAX, READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, (char**) &input, &input.iov_len);
5945640e
LP
565 if (r == -E2BIG)
566 return log_error_errno(r, "Data too long for encrypted credential (allowed size: %zu).", (size_t) CREDENTIAL_ENCRYPTED_SIZE_MAX);
567 if (r < 0)
568 return log_error_errno(r, "Failed to read encrypted credential data: %m");
569
c6661401 570 output_path = (argc < 3 || empty_or_dash(argv[2])) ? NULL : argv[2];
5945640e
LP
571
572 if (arg_name_any)
573 name = NULL;
574 else if (arg_name)
575 name = arg_name;
576 else if (input_path) {
577 r = path_extract_filename(input_path, &fname);
578 if (r < 0)
579 return log_error_errno(r, "Failed to extract filename from '%s': %m", input_path);
580 if (r == O_DIRECTORY)
581 return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Path '%s' refers to directory, refusing.", input_path);
582
583 name = fname;
584 } else {
585 log_warning("No credential name specified, not validating credential name embedded in encrypted data. (Disable this warning with --name=.)");
586 name = NULL;
587 }
588
589 timestamp = arg_timestamp != USEC_INFINITY ? arg_timestamp : now(CLOCK_REALTIME);
590
591 r = decrypt_credential_and_warn(
592 name,
593 timestamp,
594 arg_tpm2_device,
75ddec93 595 arg_tpm2_signature,
c85b68f6 596 arg_uid,
8d042bc4 597 &input,
9c3d8db9 598 /* flags= */ 0,
8d042bc4 599 &plaintext);
5945640e
LP
600 if (r < 0)
601 return r;
602
603 if (output_path) {
604 output_file = fopen(output_path, "we");
605 if (!output_file)
606 return log_error_errno(errno, "Failed to create output file '%s': %m", output_path);
607
608 f = output_file;
609 } else
610 f = stdout;
611
8d042bc4 612 r = write_blob(f, plaintext.iov_base, plaintext.iov_len);
5945640e
LP
613 if (r < 0)
614 return r;
615
616 return EXIT_SUCCESS;
617}
618
619static int verb_setup(int argc, char **argv, void *userdata) {
8d042bc4 620 _cleanup_(iovec_done_erase) struct iovec host_key = {};
5945640e
LP
621 int r;
622
8d042bc4 623 r = get_credential_host_secret(CREDENTIAL_SECRET_GENERATE|CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED, &host_key);
5945640e
LP
624 if (r < 0)
625 return log_error_errno(r, "Failed to setup credentials host key: %m");
626
8d042bc4 627 log_info("%zu byte credentials host key set up.", host_key.iov_len);
5945640e
LP
628
629 return EXIT_SUCCESS;
630}
631
6e0cb815
LP
632static int verb_has_tpm2(int argc, char **argv, void *userdata) {
633 Tpm2Support s;
634
635 s = tpm2_support();
636
637 if (!arg_quiet) {
638 if (s == TPM2_SUPPORT_FULL)
639 puts("yes");
640 else if (s == TPM2_SUPPORT_NONE)
641 puts("no");
642 else
643 puts("partial");
644
645 printf("%sfirmware\n"
646 "%sdriver\n"
300bba79 647 "%ssystem\n"
33931049
DDM
648 "%ssubsystem\n"
649 "%slibraries\n",
6e0cb815
LP
650 plus_minus(s & TPM2_SUPPORT_FIRMWARE),
651 plus_minus(s & TPM2_SUPPORT_DRIVER),
300bba79 652 plus_minus(s & TPM2_SUPPORT_SYSTEM),
33931049
DDM
653 plus_minus(s & TPM2_SUPPORT_SUBSYSTEM),
654 plus_minus(s & TPM2_SUPPORT_LIBRARIES));
6e0cb815
LP
655 }
656
657 /* Return inverted bit flags. So that TPM2_SUPPORT_FULL becomes EXIT_SUCCESS and the other values
658 * become some reasonable values 1…7. i.e. the flags we return here tell what is missing rather than
8ac6b05b 659 * what is there, acknowledging the fact that for process exit statuses it is customary to return
6e0cb815
LP
660 * zero (EXIT_FAILURE) when all is good, instead of all being bad. */
661 return ~s & TPM2_SUPPORT_FULL;
662}
663
5945640e
LP
664static int verb_help(int argc, char **argv, void *userdata) {
665 _cleanup_free_ char *link = NULL;
666 int r;
667
668 r = terminal_urlify_man("systemd-creds", "1", &link);
669 if (r < 0)
670 return log_oom();
671
672 printf("%1$s [OPTIONS...] COMMAND ...\n"
673 "\n%5$sDisplay and Process Credentials.%6$s\n"
674 "\n%3$sCommands:%4$s\n"
675 " list Show installed and available versions\n"
676 " cat CREDENTIAL... Show specified credentials\n"
677 " setup Generate credentials host key, if not existing yet\n"
678 " encrypt INPUT OUTPUT Encrypt plaintext credential file and write to\n"
679 " ciphertext credential file\n"
680 " decrypt INPUT [OUTPUT] Decrypt ciphertext credential file and write to\n"
681 " plaintext credential file\n"
6e0cb815 682 " has-tpm2 Report whether TPM2 support is available\n"
5945640e
LP
683 " -h --help Show this help\n"
684 " --version Show package version\n"
685 "\n%3$sOptions:%4$s\n"
686 " --no-pager Do not pipe output into a pager\n"
687 " --no-legend Do not show the headers and footers\n"
688 " --json=pretty|short|off\n"
689 " Generate JSON output\n"
690 " --system Show credentials passed to system\n"
691 " --transcode=base64|unbase64|hex|unhex\n"
692 " Transcode credential data\n"
693 " --newline=auto|yes|no\n"
694 " Suffix output with newline\n"
695 " -p --pretty Output as SetCredentialEncrypted= line\n"
696 " --name=NAME Override filename included in encrypted credential\n"
697 " --timestamp=TIME Include specified timestamp in encrypted credential\n"
698 " --not-after=TIME Include specified invalidation time in encrypted\n"
699 " credential\n"
b6553329 700 " --with-key=host|tpm2|host+tpm2|tpm2-absent|auto|auto-initrd\n"
5945640e
LP
701 " Which keys to encrypt with\n"
702 " -H Shortcut for --with-key=host\n"
703 " -T Shortcut for --with-key=tpm2\n"
704 " --tpm2-device=PATH\n"
705 " Pick TPM2 device\n"
706 " --tpm2-pcrs=PCR1+PCR2+PCR3+…\n"
75ddec93
LP
707 " Specify TPM2 PCRs to seal against (fixed hash)\n"
708 " --tpm2-public-key=PATH\n"
709 " Specify PEM certificate to seal against\n"
710 " --tpm2-public-key-pcrs=PCR1+PCR2+PCR3+…\n"
711 " Specify TPM2 PCRs to seal against (public key)\n"
712 " --tpm2-signature=PATH\n"
713 " Specify signature for public key PCR policy\n"
c85b68f6
LP
714 " --user Select user-scoped credential encryption\n"
715 " --uid=UID Select user for scoped credentials\n"
6e0cb815 716 " -q --quiet Suppress output for 'has-tpm2' verb\n"
5945640e
LP
717 "\nSee the %2$s for details.\n"
718 , program_invocation_short_name
719 , link
720 , ansi_underline(), ansi_normal()
721 , ansi_highlight(), ansi_normal()
722 );
723
724 return 0;
725}
726
727static int parse_argv(int argc, char *argv[]) {
728
729 enum {
730 ARG_VERSION = 0x100,
731 ARG_NO_PAGER,
732 ARG_NO_LEGEND,
733 ARG_JSON,
734 ARG_SYSTEM,
735 ARG_TRANSCODE,
736 ARG_NEWLINE,
737 ARG_WITH_KEY,
738 ARG_TPM2_DEVICE,
739 ARG_TPM2_PCRS,
75ddec93
LP
740 ARG_TPM2_PUBLIC_KEY,
741 ARG_TPM2_PUBLIC_KEY_PCRS,
742 ARG_TPM2_SIGNATURE,
5945640e
LP
743 ARG_NAME,
744 ARG_TIMESTAMP,
745 ARG_NOT_AFTER,
c85b68f6
LP
746 ARG_USER,
747 ARG_UID,
5945640e
LP
748 };
749
750 static const struct option options[] = {
75ddec93
LP
751 { "help", no_argument, NULL, 'h' },
752 { "version", no_argument, NULL, ARG_VERSION },
753 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
754 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
755 { "json", required_argument, NULL, ARG_JSON },
756 { "system", no_argument, NULL, ARG_SYSTEM },
757 { "transcode", required_argument, NULL, ARG_TRANSCODE },
758 { "newline", required_argument, NULL, ARG_NEWLINE },
759 { "pretty", no_argument, NULL, 'p' },
760 { "with-key", required_argument, NULL, ARG_WITH_KEY },
761 { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
762 { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
763 { "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY },
764 { "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS },
765 { "tpm2-signature", required_argument, NULL, ARG_TPM2_SIGNATURE },
766 { "name", required_argument, NULL, ARG_NAME },
767 { "timestamp", required_argument, NULL, ARG_TIMESTAMP },
768 { "not-after", required_argument, NULL, ARG_NOT_AFTER },
769 { "quiet", no_argument, NULL, 'q' },
c85b68f6
LP
770 { "user", no_argument, NULL, ARG_USER },
771 { "uid", required_argument, NULL, ARG_UID },
5945640e
LP
772 {}
773 };
774
775 int c, r;
776
777 assert(argc >= 0);
778 assert(argv);
779
6e0cb815 780 while ((c = getopt_long(argc, argv, "hHTpq", options, NULL)) >= 0) {
5945640e
LP
781
782 switch (c) {
783
784 case 'h':
785 return verb_help(0, NULL, NULL);
786
787 case ARG_VERSION:
788 return version();
789
790 case ARG_NO_PAGER:
791 arg_pager_flags |= PAGER_DISABLE;
792 break;
793
794 case ARG_NO_LEGEND:
795 arg_legend = false;
796 break;
797
798 case ARG_JSON:
799 r = parse_json_argument(optarg, &arg_json_format_flags);
800 if (r <= 0)
801 return r;
802
803 break;
804
805 case ARG_SYSTEM:
806 arg_system = true;
807 break;
808
809 case ARG_TRANSCODE:
810 if (parse_boolean(optarg) == 0) /* If specified as "false", turn transcoding off */
811 arg_transcode = TRANSCODE_OFF;
812 else {
813 TranscodeMode m;
814
815 m = transcode_mode_from_string(optarg);
816 if (m < 0)
817 return log_error_errno(m, "Failed to parse transcode mode: %m");
818
819 arg_transcode = m;
820 }
821
822 break;
823
824 case ARG_NEWLINE:
825 if (isempty(optarg) || streq(optarg, "auto"))
826 arg_newline = -1;
827 else {
51214cf4 828 r = parse_boolean_argument("--newline=", optarg, NULL);
5945640e
LP
829 if (r < 0)
830 return r;
831
51214cf4 832 arg_newline = r;
5945640e
LP
833 }
834 break;
835
836 case 'p':
837 arg_pretty = true;
838 break;
839
840 case ARG_WITH_KEY:
841 if (isempty(optarg) || streq(optarg, "auto"))
571d829e 842 arg_with_key = _CRED_AUTO;
b6553329
LP
843 else if (streq(optarg, "auto-initrd"))
844 arg_with_key = _CRED_AUTO_INITRD;
5945640e
LP
845 else if (streq(optarg, "host"))
846 arg_with_key = CRED_AES256_GCM_BY_HOST;
847 else if (streq(optarg, "tpm2"))
848 arg_with_key = CRED_AES256_GCM_BY_TPM2_HMAC;
75ddec93
LP
849 else if (streq(optarg, "tpm2-with-public-key"))
850 arg_with_key = CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK;
5945640e
LP
851 else if (STR_IN_SET(optarg, "host+tpm2", "tpm2+host"))
852 arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC;
75ddec93
LP
853 else if (STR_IN_SET(optarg, "host+tpm2-with-public-key", "tpm2-with-public-key+host"))
854 arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK;
6d78dc28
LP
855 else if (STR_IN_SET(optarg, "null", "tpm2-absent"))
856 arg_with_key = CRED_AES256_GCM_BY_NULL;
5945640e
LP
857 else
858 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown key type: %s", optarg);
859
860 break;
861
862 case 'H':
863 arg_with_key = CRED_AES256_GCM_BY_HOST;
864 break;
865
866 case 'T':
867 arg_with_key = CRED_AES256_GCM_BY_TPM2_HMAC;
868 break;
869
d1829af9 870 case ARG_TPM2_DEVICE:
5945640e
LP
871 if (streq(optarg, "list"))
872 return tpm2_list_devices();
873
d1829af9 874 arg_tpm2_device = streq(optarg, "auto") ? NULL : optarg;
5945640e 875 break;
5945640e 876
75ddec93 877 case ARG_TPM2_PCRS: /* For fixed hash PCR policies only */
07c04061 878 r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_pcr_mask);
5945640e
LP
879 if (r < 0)
880 return r;
881
5945640e 882 break;
5945640e 883
75ddec93
LP
884 case ARG_TPM2_PUBLIC_KEY:
885 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_public_key);
886 if (r < 0)
887 return r;
888
889 break;
890
891 case ARG_TPM2_PUBLIC_KEY_PCRS: /* For public key PCR policies only */
07c04061 892 r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_public_key_pcr_mask);
75ddec93
LP
893 if (r < 0)
894 return r;
895
896 break;
897
898 case ARG_TPM2_SIGNATURE:
899 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_signature);
900 if (r < 0)
901 return r;
902
903 break;
904
5945640e
LP
905 case ARG_NAME:
906 if (isempty(optarg)) {
907 arg_name = NULL;
908 arg_name_any = true;
909 break;
910 }
911
912 if (!credential_name_valid(optarg))
913 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid credential name: %s", optarg);
914
915 arg_name = optarg;
916 arg_name_any = false;
917 break;
918
919 case ARG_TIMESTAMP:
920 r = parse_timestamp(optarg, &arg_timestamp);
921 if (r < 0)
922 return log_error_errno(r, "Failed to parse timestamp: %s", optarg);
923
924 break;
925
926 case ARG_NOT_AFTER:
927 r = parse_timestamp(optarg, &arg_not_after);
928 if (r < 0)
929 return log_error_errno(r, "Failed to parse --not-after= timestamp: %s", optarg);
930
931 break;
932
c85b68f6
LP
933 case ARG_USER:
934 if (!uid_is_valid(arg_uid))
935 arg_uid = getuid();
936
937 break;
938
939 case ARG_UID:
940 if (isempty(optarg))
941 arg_uid = UID_INVALID;
942 else if (streq(optarg, "self"))
943 arg_uid = getuid();
944 else {
945 const char *name = optarg;
946
947 r = get_user_creds(
948 &name,
949 &arg_uid,
950 /* ret_gid= */ NULL,
951 /* ret_home= */ NULL,
952 /* ret_shell= */ NULL,
953 /* flags= */ 0);
954 if (r < 0)
955 return log_error_errno(r, "Failed to resolve user '%s': %m", optarg);
956 }
957 break;
958
6e0cb815
LP
959 case 'q':
960 arg_quiet = true;
961 break;
962
5945640e
LP
963 case '?':
964 return -EINVAL;
965
966 default:
04499a70 967 assert_not_reached();
5945640e
LP
968 }
969 }
970
c85b68f6
LP
971 if (uid_is_valid(arg_uid)) {
972 /* If a UID is specified, then switch to scoped credentials */
973
974 if (sd_id128_equal(arg_with_key, _CRED_AUTO))
975 arg_with_key = _CRED_AUTO_SCOPED;
976 else if (sd_id128_in_set(arg_with_key, CRED_AES256_GCM_BY_HOST, CRED_AES256_GCM_BY_HOST_SCOPED))
977 arg_with_key = CRED_AES256_GCM_BY_HOST_SCOPED;
978 else if (sd_id128_in_set(arg_with_key, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED))
979 arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED;
980 else if (sd_id128_in_set(arg_with_key, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED))
981 arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED;
982 else
983 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Selected key not available in --uid= scoped mode, refusing.");
984 }
985
5945640e
LP
986 if (arg_tpm2_pcr_mask == UINT32_MAX)
987 arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT;
75ddec93 988 if (arg_tpm2_public_key_pcr_mask == UINT32_MAX)
2099cd62 989 arg_tpm2_public_key_pcr_mask = UINT32_C(1) << TPM2_PCR_KERNEL_BOOT;
5945640e 990
644f19c7
LP
991 r = varlink_invocation(VARLINK_ALLOW_ACCEPT);
992 if (r < 0)
993 return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
994 arg_varlink = r;
995
5945640e
LP
996 return 1;
997}
998
999static int creds_main(int argc, char *argv[]) {
1000
1001 static const Verb verbs[] = {
6e0cb815
LP
1002 { "list", VERB_ANY, 1, VERB_DEFAULT, verb_list },
1003 { "cat", 2, VERB_ANY, 0, verb_cat },
1004 { "encrypt", 3, 3, 0, verb_encrypt },
1005 { "decrypt", 2, 3, 0, verb_decrypt },
1006 { "setup", VERB_ANY, 1, 0, verb_setup },
1007 { "help", VERB_ANY, 1, 0, verb_help },
1008 { "has-tpm2", VERB_ANY, 1, 0, verb_has_tpm2 },
5945640e
LP
1009 {}
1010 };
1011
1012 return dispatch_verb(argc, argv, verbs, NULL);
1013}
1014
644f19c7
LP
1015typedef struct MethodEncryptParameters {
1016 const char *name;
1017 const char *text;
1018 struct iovec data;
1019 uint64_t timestamp;
1020 uint64_t not_after;
1021} MethodEncryptParameters;
1022
1023static void method_encrypt_parameters_done(MethodEncryptParameters *p) {
1024 assert(p);
1025
1026 iovec_done_erase(&p->data);
1027}
1028
1029static int vl_method_encrypt(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
1030
1031 static const JsonDispatch dispatch_table[] = {
1032 { "name", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodEncryptParameters, name), 0 },
1033 { "text", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodEncryptParameters, text), 0 },
1034 { "data", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodEncryptParameters, data), 0 },
1035 { "timestamp", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(MethodEncryptParameters, timestamp), 0 },
1036 { "notAfter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(MethodEncryptParameters, not_after), 0 },
caef0bc3 1037 VARLINK_DISPATCH_POLKIT_FIELD,
644f19c7
LP
1038 {}
1039 };
1040 _cleanup_(method_encrypt_parameters_done) MethodEncryptParameters p = {
1041 .timestamp = UINT64_MAX,
1042 .not_after = UINT64_MAX,
1043 };
1044 _cleanup_(iovec_done) struct iovec output = {};
caef0bc3 1045 Hashmap **polkit_registry = ASSERT_PTR(userdata);
644f19c7
LP
1046 int r;
1047
1048 assert(link);
1049
644f19c7
LP
1050 r = varlink_dispatch(link, parameters, dispatch_table, &p);
1051 if (r != 0)
1052 return r;
1053
1054 if (p.name && !credential_name_valid(p.name))
1055 return varlink_error_invalid_parameter_name(link, "name");
1056 /* Specifying both or neither the text string and the binary data is not allowed */
1057 if (!!p.text == !!p.data.iov_base)
1058 return varlink_error_invalid_parameter_name(link, "data");
1059 if (p.timestamp == UINT64_MAX)
1060 p.timestamp = now(CLOCK_REALTIME);
1061 if (p.not_after != UINT64_MAX && p.not_after < p.timestamp)
1062 return varlink_error_invalid_parameter_name(link, "notAfter");
1063
caef0bc3
LP
1064 r = varlink_verify_polkit_async(
1065 link,
1066 /* bus= */ NULL,
1067 "io.systemd.credentials.encrypt",
1068 /* details= */ NULL,
1069 /* good_user= */ UID_INVALID,
1070 polkit_registry);
1071 if (r <= 0)
1072 return r;
1073
644f19c7
LP
1074 r = encrypt_credential_and_warn(
1075 arg_with_key,
1076 p.name,
1077 p.timestamp,
1078 p.not_after,
1079 arg_tpm2_device,
1080 arg_tpm2_pcr_mask,
1081 arg_tpm2_public_key,
1082 arg_tpm2_public_key_pcr_mask,
c85b68f6 1083 arg_uid,
8d042bc4 1084 p.text ? &IOVEC_MAKE_STRING(p.text) : &p.data,
9c3d8db9 1085 /* flags= */ 0,
8d042bc4 1086 &output);
644f19c7
LP
1087 if (r < 0)
1088 return r;
1089
1090 _cleanup_(json_variant_unrefp) JsonVariant *reply = NULL;
1091
1092 r = json_build(&reply, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_IOVEC_BASE64("blob", &output)));
1093 if (r < 0)
1094 return r;
1095
1096 /* Let's also mark the (theoretically encrypted) reply as sensitive, in case the NULL encryption scheme was used. */
1097 json_variant_sensitive(reply);
1098
1099 return varlink_reply(link, reply);
1100}
1101
1102typedef struct MethodDecryptParameters {
1103 const char *name;
1104 struct iovec blob;
1105 uint64_t timestamp;
1106} MethodDecryptParameters;
1107
1108static void method_decrypt_parameters_done(MethodDecryptParameters *p) {
1109 assert(p);
1110
1111 iovec_done_erase(&p->blob);
1112}
1113
1114static int vl_method_decrypt(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
1115
1116 static const JsonDispatch dispatch_table[] = {
caef0bc3
LP
1117 { "name", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodDecryptParameters, name), 0 },
1118 { "blob", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodDecryptParameters, blob), JSON_MANDATORY },
1119 { "timestamp", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(MethodDecryptParameters, timestamp), 0 },
1120 VARLINK_DISPATCH_POLKIT_FIELD,
644f19c7
LP
1121 {}
1122 };
1123 _cleanup_(method_decrypt_parameters_done) MethodDecryptParameters p = {
1124 .timestamp = UINT64_MAX,
1125 };
1126 _cleanup_(iovec_done_erase) struct iovec output = {};
caef0bc3 1127 Hashmap **polkit_registry = ASSERT_PTR(userdata);
644f19c7
LP
1128 int r;
1129
1130 assert(link);
1131
644f19c7
LP
1132 r = varlink_dispatch(link, parameters, dispatch_table, &p);
1133 if (r != 0)
1134 return r;
1135
1136 if (p.name && !credential_name_valid(p.name))
1137 return varlink_error_invalid_parameter_name(link, "name");
644f19c7
LP
1138 if (p.timestamp == UINT64_MAX)
1139 p.timestamp = now(CLOCK_REALTIME);
1140
caef0bc3
LP
1141 r = varlink_verify_polkit_async(
1142 link,
1143 /* bus= */ NULL,
1144 "io.systemd.credentials.decrypt",
1145 /* details= */ NULL,
1146 /* good_user= */ UID_INVALID,
1147 polkit_registry);
1148 if (r <= 0)
1149 return r;
1150
644f19c7
LP
1151 r = decrypt_credential_and_warn(
1152 p.name,
1153 p.timestamp,
1154 arg_tpm2_device,
1155 arg_tpm2_signature,
c85b68f6 1156 arg_uid,
8d042bc4 1157 &p.blob,
9c3d8db9 1158 /* flags= */ 0,
8d042bc4 1159 &output);
644f19c7
LP
1160 if (r == -EBADMSG)
1161 return varlink_error(link, "io.systemd.Credentials.BadFormat", NULL);
1162 if (r == -EREMOTE)
1163 return varlink_error(link, "io.systemd.Credentials.NameMismatch", NULL);
1164 if (r == -ESTALE)
1165 return varlink_error(link, "io.systemd.Credentials.TimeMismatch", NULL);
1166 if (r < 0)
1167 return r;
1168
1169 _cleanup_(json_variant_unrefp) JsonVariant *reply = NULL;
1170
1171 r = json_build(&reply, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_IOVEC_BASE64("data", &output)));
1172 if (r < 0)
1173 return r;
1174
1175 json_variant_sensitive(reply);
1176
1177 return varlink_reply(link, reply);
1178}
1179
5945640e
LP
1180static int run(int argc, char *argv[]) {
1181 int r;
1182
1183 log_setup();
1184
1185 r = parse_argv(argc, argv);
1186 if (r <= 0)
1187 return r;
1188
644f19c7
LP
1189 if (arg_varlink) {
1190 _cleanup_(varlink_server_unrefp) VarlinkServer *varlink_server = NULL;
caef0bc3 1191 _cleanup_(hashmap_freep) Hashmap *polkit_registry = NULL;
644f19c7
LP
1192
1193 /* Invocation as Varlink service */
1194
a570877c 1195 r = varlink_server_new(&varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA|VARLINK_SERVER_INPUT_SENSITIVE);
644f19c7
LP
1196 if (r < 0)
1197 return log_error_errno(r, "Failed to allocate Varlink server: %m");
1198
1199 r = varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_Credentials);
1200 if (r < 0)
1201 return log_error_errno(r, "Failed to add Varlink interface: %m");
1202
1203 r = varlink_server_bind_method_many(
1204 varlink_server,
1205 "io.systemd.Credentials.Encrypt", vl_method_encrypt,
1206 "io.systemd.Credentials.Decrypt", vl_method_decrypt);
1207 if (r < 0)
1208 return log_error_errno(r, "Failed to bind Varlink methods: %m");
1209
caef0bc3
LP
1210 varlink_server_set_userdata(varlink_server, &polkit_registry);
1211
644f19c7
LP
1212 r = varlink_server_loop_auto(varlink_server);
1213 if (r < 0)
1214 return log_error_errno(r, "Failed to run Varlink event loop: %m");
1215
1216 return 0;
1217 }
1218
5945640e
LP
1219 return creds_main(argc, argv);
1220}
1221
1222DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);