• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 16093324388

05 Jul 2025 04:42PM UTC coverage: 72.057% (-0.04%) from 72.095%
16093324388

push

github

yuwata
analyze: fix typo

Follow-up for 9a08000d1.

1 of 1 new or added line in 1 file covered. (100.0%)

219 existing lines in 40 files now uncovered.

300686 of 417288 relevant lines covered (72.06%)

714287.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

35.86
/src/shared/ask-password-api.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <fcntl.h>
4
#include <poll.h>
5
#include <stdio.h>
6
#include <sys/inotify.h>
7
#include <sys/signalfd.h>
8
#include <sys/stat.h>
9
#include <termios.h>
10
#include <unistd.h>
11

12
#include "alloc-util.h"
13
#include "ansi-color.h"
14
#include "ask-password-api.h"
15
#include "creds-util.h"
16
#include "fd-util.h"
17
#include "fileio.h"
18
#include "format-util.h"
19
#include "fs-util.h"
20
#include "glyph-util.h"
21
#include "inotify-util.h"
22
#include "io-util.h"
23
#include "iovec-util.h"
24
#include "keyring-util.h"
25
#include "log.h"
26
#include "missing_syscall.h"
27
#include "nulstr-util.h"
28
#include "parse-util.h"
29
#include "path-lookup.h"
30
#include "plymouth-util.h"
31
#include "process-util.h"
32
#include "random-util.h"
33
#include "signal-util.h"
34
#include "socket-util.h"
35
#include "string-table.h"
36
#include "string-util.h"
37
#include "strv.h"
38
#include "terminal-util.h"
39
#include "time-util.h"
40
#include "tmpfile-util.h"
41
#include "umask-util.h"
42
#include "utf8.h"
43

44
#define KEYRING_TIMEOUT_USEC ((5 * USEC_PER_MINUTE) / 2)
45

46
static const char* keyring_table[] = {
47
        [-KEY_SPEC_THREAD_KEYRING]       = "thread",
48
        [-KEY_SPEC_PROCESS_KEYRING]      = "process",
49
        [-KEY_SPEC_SESSION_KEYRING]      = "session",
50
        [-KEY_SPEC_USER_KEYRING]         = "user",
51
        [-KEY_SPEC_USER_SESSION_KEYRING] = "user-session",
52
        [-KEY_SPEC_GROUP_KEYRING]        = "group",
53
};
54

55
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(keyring, int);
×
56

57
static int lookup_key(const char *keyname, key_serial_t *ret) {
4✔
58
        key_serial_t serial;
4✔
59

60
        assert(keyname);
4✔
61
        assert(ret);
4✔
62

63
        serial = request_key("user", keyname, /* callout_info= */ NULL, /* destringid= */ 0);
4✔
64
        if (serial == -1)
4✔
65
                return negative_errno();
4✔
66

67
        *ret = serial;
×
68
        return 0;
×
69
}
70

71
static int retrieve_key(key_serial_t serial, char ***ret) {
×
72
        _cleanup_(erase_and_freep) void *p = NULL;
×
73
        char **l;
×
74
        size_t n;
×
75
        int r;
×
76

77
        assert(ret);
×
78

79
        r = keyring_read(serial, &p, &n);
×
80
        if (r < 0)
×
81
                return r;
82

83
        l = strv_parse_nulstr(p, n);
×
84
        if (!l)
×
85
                return -ENOMEM;
86

87
        *ret = l;
×
88
        return 0;
×
89
}
90

91
static int get_ask_password_directory_for_flags(AskPasswordFlags flags, char **ret) {
1✔
92
        if (FLAGS_SET(flags, ASK_PASSWORD_USER))
1✔
93
                return acquire_user_ask_password_directory(ret);
×
94

95
        return strdup_to_full(ret, "/run/systemd/ask-password/"); /* Returns 1, indicating there's a suitable directory */
1✔
96
}
97

98
static int touch_ask_password_directory(AskPasswordFlags flags) {
×
99
        int r;
×
100

101
        _cleanup_free_ char *p = NULL;
×
102
        r = get_ask_password_directory_for_flags(flags, &p);
×
103
        if (r <= 0)
×
104
                return r;
105

106
        _cleanup_close_ int fd = open_mkdir(p, O_CLOEXEC, 0755);
×
107
        if (fd < 0)
×
108
                return fd;
109

110
        r = touch_fd(fd, USEC_INFINITY);
×
111
        if (r < 0)
×
112
                return r;
×
113

114
        return 1; /* did something */
115
}
116

117
static usec_t keyring_cache_timeout(void) {
×
118
        static usec_t saved_timeout = KEYRING_TIMEOUT_USEC;
×
119
        static bool saved_timeout_set = false;
×
120
        int r;
×
121

122
        if (saved_timeout_set)
×
123
                return saved_timeout;
×
124

125
        const char *e = secure_getenv("SYSTEMD_ASK_PASSWORD_KEYRING_TIMEOUT_SEC");
×
126
        if (e) {
×
127
                r = parse_sec(e, &saved_timeout);
×
128
                if (r < 0)
×
129
                        log_debug_errno(r, "Invalid value in $SYSTEMD_ASK_PASSWORD_KEYRING_TIMEOUT_SEC, ignoring: %s", e);
×
130
        }
131

132
        saved_timeout_set = true;
×
133

134
        return saved_timeout;
×
135
}
136

137
static key_serial_t keyring_cache_type(void) {
×
138
        static key_serial_t saved_keyring = KEY_SPEC_USER_KEYRING;
×
139
        static bool saved_keyring_set = false;
×
140
        int r;
×
141

142
        if (saved_keyring_set)
×
143
                return saved_keyring;
×
144

145
        const char *e = secure_getenv("SYSTEMD_ASK_PASSWORD_KEYRING_TYPE");
×
146
        if (e) {
×
147
                key_serial_t keyring;
×
148

149
                r = safe_atoi32(e, &keyring);
×
150
                if (r >= 0)
×
151
                        if (keyring < 0)
×
152
                                log_debug_errno(keyring, "Invalid value in $SYSTEMD_ASK_PASSWORD_KEYRING_TYPE, ignoring: %s", e);
×
153
                        else
154
                                saved_keyring = keyring;
×
155
                else {
156
                        keyring = keyring_from_string(e);
×
157
                        if (keyring < 0)
×
158
                                log_debug_errno(keyring, "Invalid value in $SYSTEMD_ASK_PASSWORD_KEYRING_TYPE, ignoring: %s", e);
×
159
                        else
160
                                saved_keyring = -keyring;
×
161
                }
162
        }
163

164
        saved_keyring_set = true;
×
165

166
        return saved_keyring;
×
167
}
168

169
static int add_to_keyring(const char *keyname, AskPasswordFlags flags, char **passwords) {
×
170
        _cleanup_strv_free_erase_ char **l = NULL;
×
171
        _cleanup_(erase_and_freep) char *p = NULL;
×
172
        key_serial_t serial;
×
173
        size_t n;
×
174
        int r;
×
175

176
        assert(keyname);
×
177

178
        if (!FLAGS_SET(flags, ASK_PASSWORD_PUSH_CACHE) || keyring_cache_timeout() == 0)
×
179
                return 0;
×
180
        if (strv_isempty(passwords))
×
181
                return 0;
182

183
        r = lookup_key(keyname, &serial);
×
184
        if (r >= 0) {
×
185
                r = retrieve_key(serial, &l);
×
186
                if (r < 0)
×
187
                        return r;
188
        } else if (r != -ENOKEY)
×
189
                return r;
190

191
        r = strv_extend_strv(&l, passwords, /* filter_duplicates= */ true);
×
192
        if (r <= 0)
×
193
                return r;
194

195
        r = strv_make_nulstr(l, &p, &n);
×
196
        if (r < 0)
×
197
                return r;
198

199
        /* chop off the final NUL byte. We do this because we want to use the separator NUL bytes only if we
200
         * have multiple passwords. */
201
        n = LESS_BY(n, (size_t) 1);
×
202

203
        serial = add_key("user", keyname, p, n, keyring_cache_type());
×
204
        if (serial == -1)
×
205
                return -errno;
×
206

207
        if (keyring_cache_timeout() != USEC_INFINITY &&
×
208
                keyctl(KEYCTL_SET_TIMEOUT,
×
209
                       (unsigned long) serial,
210
                       (unsigned long) DIV_ROUND_UP(keyring_cache_timeout(), USEC_PER_SEC), 0, 0) < 0)
×
211
                log_debug_errno(errno, "Failed to adjust kernel keyring key timeout: %m");
×
212

213
        /* Tell everyone to check the keyring */
214
        (void) touch_ask_password_directory(flags);
×
215

216
        log_debug("Added key to kernel keyring as %" PRIi32 ".", serial);
×
217

218
        return 1;
219
}
220

221
static int add_to_keyring_and_log(const char *keyname, AskPasswordFlags flags, char **passwords) {
×
222
        int r;
×
223

224
        assert(keyname);
×
225

226
        r = add_to_keyring(keyname, flags, passwords);
×
227
        if (r < 0)
×
228
                return log_debug_errno(r, "Failed to add password to kernel keyring: %m");
×
229

230
        return 0;
231
}
232

233
static int ask_password_keyring(const AskPasswordRequest *req, AskPasswordFlags flags, char ***ret) {
4✔
234
        key_serial_t serial;
4✔
235
        int r;
4✔
236

237
        assert(req);
4✔
238
        assert(ret);
4✔
239

240
        if (!FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED))
4✔
241
                return -EUNATCH;
4✔
242

243
        r = lookup_key(req->keyring, &serial);
4✔
244
        if (ERRNO_IS_NEG_NOT_SUPPORTED(r) || IN_SET(r, -EPERM, -ENOKEY))
4✔
245
                /* When retrieving, the distinction between "kernel or container manager don't support or
246
                 * allow this" and "no matching key known" doesn't matter. Note that we propagate EACCESS
247
                 * here (even if EPERM not) since that is used if the keyring is available, but we lack
248
                 * access to the key. */
249
                return -ENOKEY;
UNCOV
250
        if (r < 0)
×
UNCOV
251
                return log_debug_errno(r, "Failed to look up key %s in keyring: %m", req->keyring);
×
252

253
        _cleanup_strv_free_erase_ char **l = NULL;
×
254
        r = retrieve_key(serial, &l);
×
255
        if (r < 0)
×
256
                return r;
257

258
        if (strv_isempty(l))
×
259
                return log_debug_errno(SYNTHETIC_ERRNO(ENOKEY), "Found an empty password from keyring.");
×
260

261
        *ret = TAKE_PTR(l);
×
262
        return 0;
×
263
}
264

265
static int backspace_chars(int ttyfd, size_t p) {
×
266
        if (ttyfd < 0)
×
267
                return 0;
×
268

269
        _cleanup_free_ char *buf = malloc_multiply(3, p);
×
270
        if (!buf)
×
271
                return log_oom();
×
272

273
        for (size_t i = 0; i < p; i++)
×
274
                memcpy(buf + 3 * i, "\b \b", 3);
×
275

276
        return loop_write(ttyfd, buf, 3 * p);
×
277
}
278

279
static int backspace_string(int ttyfd, const char *str) {
×
280
        assert(str);
×
281

282
        /* Backspaces through enough characters to entirely undo printing of the specified string. */
283

284
        if (ttyfd < 0)
×
285
                return 0;
286

287
        size_t m = utf8_n_codepoints(str);
×
288
        if (m == SIZE_MAX)
×
289
                m = strlen(str); /* Not a valid UTF-8 string? If so, let's backspace the number of bytes
×
290
                                  * output. Most likely this happened because we are not in a UTF-8 locale,
291
                                  * and in that case that is the correct thing to do. And even if it's not,
292
                                  * terminals tend to stop backspacing at the leftmost column, hence
293
                                  * backspacing too much should be mostly OK. */
294

295
        return backspace_chars(ttyfd, m);
×
296
}
297

298
int ask_password_plymouth(
×
299
                const AskPasswordRequest *req,
300
                AskPasswordFlags flags,
301
                char ***ret) {
302

303
        _cleanup_close_ int fd = -EBADF, inotify_fd = -EBADF;
×
304
        _cleanup_free_ char *packet = NULL;
×
305
        ssize_t k;
×
306
        int r, n;
×
307
        char buffer[LINE_MAX];
×
308
        size_t p = 0;
×
309

310
        assert(req);
×
311
        assert(ret);
×
312

313
        if (FLAGS_SET(flags, ASK_PASSWORD_HEADLESS))
×
314
                return -ENOEXEC;
315

316
        const char *message = req->message ?: "Password:";
×
317

318
        if (req->flag_file) {
×
319
                inotify_fd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
×
320
                if (inotify_fd < 0)
×
321
                        return -errno;
×
322

323
                if (inotify_add_watch(inotify_fd, req->flag_file, IN_ATTRIB) < 0) /* for the link count */
×
324
                        return -errno;
×
325
        }
326

327
        fd = plymouth_connect(SOCK_NONBLOCK);
×
328
        if (fd < 0)
×
329
                return fd;
330

331
        if (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED)) {
×
332
                packet = strdup("c");
×
333
                n = 1;
×
334
        } else if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0)
×
335
                packet = NULL;
×
336
        if (!packet)
×
337
                return -ENOMEM;
338

339
        r = loop_write_full(fd, packet, n + 1, USEC_INFINITY);
×
340
        if (r < 0)
×
341
                return r;
342

343
        CLEANUP_ERASE(buffer);
×
344

345
        enum {
×
346
                POLL_SOCKET,
347
                POLL_TWO,
348
                POLL_THREE,
349
                _POLL_MAX,
350
        };
351

352
        struct pollfd pollfd[_POLL_MAX] = {
×
353
                [POLL_SOCKET] = {
354
                        .fd = fd,
355
                        .events = POLLIN,
356
                },
357
        };
358
        size_t n_pollfd = POLL_SOCKET + 1, inotify_idx = SIZE_MAX, hup_fd_idx = SIZE_MAX;
×
359
        if (inotify_fd >= 0)
×
360
                pollfd[inotify_idx = n_pollfd++] = (struct pollfd) {
×
361
                        .fd = inotify_fd,
362
                        .events = POLLIN,
363
                };
364
        if (req->hup_fd >= 0)
×
365
                pollfd[hup_fd_idx = n_pollfd++] = (struct pollfd) {
×
366
                        .fd = req->hup_fd,
367
                        .events = POLLHUP,
368
                };
369

370
        assert(n_pollfd <= _POLL_MAX);
×
371

372
        for (;;) {
×
373
                usec_t timeout;
×
374

375
                if (req->until > 0)
×
376
                        timeout = usec_sub_unsigned(req->until, now(CLOCK_MONOTONIC));
×
377
                else
378
                        timeout = USEC_INFINITY;
379

380
                if (req->flag_file && access(req->flag_file, F_OK) < 0)
×
381
                        return -errno;
×
382

383
                r = ppoll_usec(pollfd, n_pollfd, timeout);
×
384
                if (r == -EINTR)
×
385
                        continue;
×
386
                if (r < 0)
×
387
                        return r;
388
                if (r == 0)
×
389
                        return -ETIME;
390

391
                if (req->hup_fd >= 0 && pollfd[hup_fd_idx].revents & POLLHUP)
×
392
                        return -ECONNRESET;
393

394
                if (inotify_fd >= 0 && pollfd[inotify_idx].revents != 0)
×
395
                        (void) flush_fd(inotify_fd);
×
396

397
                if (pollfd[POLL_SOCKET].revents == 0)
×
398
                        continue;
×
399

400
                k = read(fd, buffer + p, sizeof(buffer) - p);
×
401
                if (k < 0) {
×
402
                        if (ERRNO_IS_TRANSIENT(errno))
×
403
                                continue;
×
404

405
                        return -errno;
×
406
                }
407
                if (k == 0)
×
408
                        return -EIO;
409

410
                p += k;
×
411

412
                if (buffer[0] == 5) {
×
413

414
                        if (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED)) {
×
415
                                /* Hmm, first try with cached
416
                                 * passwords failed, so let's retry
417
                                 * with a normal password request */
418
                                packet = mfree(packet);
×
419

420
                                if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0)
×
421
                                        return -ENOMEM;
422

423
                                r = loop_write_full(fd, packet, n + 1, USEC_INFINITY);
×
424
                                if (r < 0)
×
425
                                        return r;
426

427
                                flags &= ~ASK_PASSWORD_ACCEPT_CACHED;
×
428
                                p = 0;
×
429
                                continue;
×
430
                        }
431

432
                        /* No password, because UI not shown */
433
                        return -ENOENT;
434

435
                } else if (IN_SET(buffer[0], 2, 9)) {
×
436
                        _cleanup_strv_free_erase_ char **l = NULL;
×
437
                        uint32_t size;
×
438

439
                        /* One or more answers */
440
                        if (p < 5)
×
441
                                continue;
×
442

443
                        memcpy(&size, buffer+1, sizeof(size));
×
444
                        size = le32toh(size);
×
445
                        if (size + 5 > sizeof(buffer))
×
446
                                return -EIO;
447

448
                        if (p-5 < size)
×
449
                                continue;
×
450

451
                        l = strv_parse_nulstr(buffer + 5, size);
×
452
                        if (!l)
×
453
                                return -ENOMEM;
454

455
                        if (strv_isempty(l))
×
456
                                return log_debug_errno(SYNTHETIC_ERRNO(ECANCELED), "Received an empty password.");
×
457

458
                        *ret = TAKE_PTR(l);
×
459
                        return 0;
×
460

461
                } else
462
                        /* Unknown packet */
463
                        return -EIO;
464
        }
465
}
466

467
#define NO_ECHO "(no echo) "
468
#define PRESS_TAB "(press TAB for no echo) "
469
#define SKIPPED "(skipped)"
470

471
int ask_password_tty(
1✔
472
                const AskPasswordRequest *req,
473
                AskPasswordFlags flags,
474
                char ***ret) {
475

476
        bool reset_tty = false, dirty = false, use_color = false, press_tab_visible = false;
1✔
477
        _cleanup_close_ int cttyfd = -EBADF, inotify_fd = -EBADF;
1✔
478
        struct termios old_termios, new_termios;
1✔
479
        char passphrase[LINE_MAX + 1] = {}, *x;
1✔
480
        _cleanup_strv_free_erase_ char **l = NULL;
1✔
481
        size_t p = 0, codepoint = 0;
1✔
482
        int r;
1✔
483

484
        assert(req);
1✔
485
        assert(ret);
1✔
486

487
        if (FLAGS_SET(flags, ASK_PASSWORD_HEADLESS))
1✔
488
                return -ENOEXEC;
489

490
        if (FLAGS_SET(flags, ASK_PASSWORD_NO_TTY))
1✔
491
                return -EUNATCH;
492

493
        const char *message = req->message ?: "Password:";
1✔
494
        const char *keyring = req->keyring;
1✔
495

496
        if (!FLAGS_SET(flags, ASK_PASSWORD_HIDE_EMOJI) && emoji_enabled())
1✔
497
                message = strjoina(glyph(GLYPH_LOCK_AND_KEY), " ", message);
×
498

499
        if (req->flag_file || (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && keyring)) {
1✔
UNCOV
500
                inotify_fd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
×
UNCOV
501
                if (inotify_fd < 0)
×
502
                        return -errno;
×
503
        }
504
        if (req->flag_file)
1✔
UNCOV
505
                if (inotify_add_watch(inotify_fd, req->flag_file, IN_ATTRIB /* for the link count */) < 0)
×
UNCOV
506
                        return -errno;
×
507
        if (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && keyring) {
1✔
508
                r = ask_password_keyring(req, flags, ret);
×
509
                if (r >= 0)
×
510
                        return 0;
×
511
                if (r != -ENOKEY)
×
512
                        return r;
513

514
                /* Let's watch the askpw directory for mtime changes, which we issue above whenever the
515
                 * keyring changes */
516
                _cleanup_free_ char *watch_path = NULL;
×
517
                r = get_ask_password_directory_for_flags(flags, &watch_path);
×
518
                if (r < 0)
×
519
                        return r;
520
                if (r > 0) {
×
521
                        _cleanup_close_ int watch_fd = open_mkdir(watch_path, O_CLOEXEC|O_RDONLY, 0755);
×
522
                        if (watch_fd < 0)
×
523
                                return watch_fd;
524

525
                        r = inotify_add_watch_fd(inotify_fd, watch_fd, IN_ONLYDIR|IN_ATTRIB /* for mtime */);
×
526
                        if (r < 0)
×
527
                                return r;
528
                }
529
        }
530

531
        CLEANUP_ERASE(passphrase);
1✔
532

533
        /* If the caller didn't specify a TTY, then use the controlling tty, if we can. */
534
        int ttyfd;
1✔
535
        if (req->tty_fd < 0)
1✔
536
                ttyfd = cttyfd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC);
1✔
537
        else
538
                ttyfd = req->tty_fd;
539

540
        if (ttyfd >= 0) {
1✔
541
                if (tcgetattr(ttyfd, &old_termios) < 0)
×
542
                        return -errno;
×
543

544
                if (FLAGS_SET(flags, ASK_PASSWORD_CONSOLE_COLOR))
×
545
                        use_color = dev_console_colors_enabled();
×
546
                else
547
                        use_color = colors_enabled();
×
548

549
                if (use_color)
×
550
                        (void) loop_write(ttyfd, ANSI_HIGHLIGHT, SIZE_MAX);
×
551

552
                (void) loop_write(ttyfd, message, SIZE_MAX);
×
553
                (void) loop_write(ttyfd, " ", 1);
×
554

555
                if (!FLAGS_SET(flags, ASK_PASSWORD_SILENT) && !FLAGS_SET(flags, ASK_PASSWORD_ECHO)) {
×
556
                        if (use_color)
×
557
                                (void) loop_write(ttyfd, ansi_grey(), SIZE_MAX);
×
558

559
                        (void) loop_write(ttyfd, PRESS_TAB, SIZE_MAX);
×
560
                        press_tab_visible = true;
561
                }
562

563
                if (use_color)
×
564
                        (void) loop_write(ttyfd, ANSI_NORMAL, SIZE_MAX);
×
565

566
                new_termios = old_termios;
×
567
                termios_disable_echo(&new_termios);
×
568

569
                r = RET_NERRNO(tcsetattr(ttyfd, TCSANOW, &new_termios));
×
570
                if (r < 0)
×
571
                        goto finish;
×
572

573
                reset_tty = true;
574
        }
575

576
        enum {
1✔
577
                POLL_TTY,
578
                POLL_TWO,
579
                POLL_THREE,
580
                _POLL_MAX,
581
        };
582

583
        struct pollfd pollfd[_POLL_MAX] = {
1✔
584
                [POLL_TTY]     = {
585
                        .fd = ttyfd >= 0 ? ttyfd : STDIN_FILENO,
1✔
586
                        .events = POLLIN,
587
                },
588
        };
589
        size_t n_pollfd = POLL_TTY + 1, inotify_idx = SIZE_MAX, hup_fd_idx = SIZE_MAX;
1✔
590

591
        if (inotify_fd >= 0)
1✔
592
                pollfd[inotify_idx = n_pollfd++] = (struct pollfd) {
×
593
                        .fd = inotify_fd,
594
                        .events = POLLIN,
595
                };
596
        if (req->hup_fd >= 0)
1✔
597
                pollfd[hup_fd_idx = n_pollfd++] = (struct pollfd) {
×
598
                        .fd = req->hup_fd,
599
                        .events = POLLHUP,
600
                };
601

602
        assert(n_pollfd <= _POLL_MAX);
1✔
603

604
        for (;;) {
1✔
605
                _cleanup_(erase_char) char c;
×
606
                usec_t timeout;
1✔
607
                ssize_t n;
1✔
608

609
                if (req->until > 0)
1✔
610
                        timeout = usec_sub_unsigned(req->until, now(CLOCK_MONOTONIC));
1✔
611
                else
612
                        timeout = USEC_INFINITY;
613

614
                if (req->flag_file) {
1✔
615
                        r = RET_NERRNO(access(req->flag_file, F_OK));
×
616
                        if (r < 0)
×
617
                                goto finish;
×
618
                }
619

620
                r = ppoll_usec(pollfd, n_pollfd, timeout);
1✔
621
                if (r == -EINTR)
1✔
622
                        continue;
×
623
                if (r < 0)
1✔
624
                        goto finish;
×
625
                if (r == 0) {
1✔
626
                        r = -ETIME;
×
627
                        goto finish;
×
628
                }
629

630
                if (req->hup_fd >= 0 && pollfd[hup_fd_idx].revents & POLLHUP) {
1✔
631
                        r = -ECONNRESET;
×
632
                        goto finish;
×
633
                }
634

635
                if (inotify_fd >= 0 && pollfd[inotify_idx].revents != 0 && keyring) {
1✔
636
                        (void) flush_fd(inotify_fd);
×
637

638
                        r = ask_password_keyring(req, flags, ret);
×
639
                        if (r >= 0) {
×
640
                                r = 0;
×
641
                                goto finish;
×
642
                        } else if (r != -ENOKEY)
×
643
                                goto finish;
×
644
                }
645

646
                if (pollfd[POLL_TTY].revents == 0)
1✔
647
                        continue;
×
648

649
                n = read(ttyfd >= 0 ? ttyfd : STDIN_FILENO, &c, 1);
1✔
650
                if (n < 0) {
1✔
651
                        if (ERRNO_IS_TRANSIENT(errno))
×
652
                                continue;
×
653

654
                        r = -errno;
×
655
                        goto finish;
×
656

657
                }
658

659
                if (press_tab_visible) {
1✔
660
                        assert(ttyfd >= 0);
×
661
                        backspace_chars(ttyfd, strlen(PRESS_TAB));
×
662
                        press_tab_visible = false;
663
                }
664

665
                /* We treat EOF, newline and NUL byte all as valid end markers */
666
                if (n == 0 || c == '\n' || c == 0)
1✔
667
                        break;
668

669
                if (c == 4) { /* C-d also known as EOT */
×
670
                        if (ttyfd >= 0)
×
671
                                (void) loop_write(ttyfd, SKIPPED, SIZE_MAX);
×
672

673
                        goto skipped;
×
674
                }
675

676
                if (c == 21) { /* C-u */
×
677

678
                        if (!FLAGS_SET(flags, ASK_PASSWORD_SILENT))
×
679
                                (void) backspace_string(ttyfd, passphrase);
×
680

681
                        explicit_bzero_safe(passphrase, sizeof(passphrase));
×
682
                        p = codepoint = 0;
×
683

684
                } else if (IN_SET(c, '\b', 127)) {
×
685

686
                        if (p > 0) {
×
687
                                size_t q;
×
688

689
                                if (!FLAGS_SET(flags, ASK_PASSWORD_SILENT))
×
690
                                        (void) backspace_chars(ttyfd, 1);
×
691

692
                                /* Remove a full UTF-8 codepoint from the end. For that, figure out where the
693
                                 * last one begins */
694
                                q = 0;
×
695
                                for (;;) {
×
696
                                        int z;
×
697

698
                                        z = utf8_encoded_valid_unichar(passphrase + q, SIZE_MAX);
×
699
                                        if (z <= 0) {
×
700
                                                q = SIZE_MAX; /* Invalid UTF8! */
701
                                                break;
702
                                        }
703

704
                                        if (q + z >= p) /* This one brings us over the edge */
×
705
                                                break;
706

707
                                        q += z;
708
                                }
709

710
                                p = codepoint = q == SIZE_MAX ? p - 1 : q;
×
711
                                explicit_bzero_safe(passphrase + p, sizeof(passphrase) - p);
×
712

713
                        } else if (!dirty && !FLAGS_SET(flags, ASK_PASSWORD_SILENT)) {
×
714

715
                                flags |= ASK_PASSWORD_SILENT;
×
716

717
                                /* There are two ways to enter silent mode. Either by pressing backspace as
718
                                 * first key (and only as first key), or ... */
719

720
                                if (ttyfd >= 0)
×
721
                                        (void) loop_write(ttyfd, NO_ECHO, SIZE_MAX);
×
722

723
                        } else if (ttyfd >= 0)
×
724
                                (void) loop_write(ttyfd, "\a", 1);
×
725

726
                } else if (c == '\t' && !FLAGS_SET(flags, ASK_PASSWORD_SILENT)) {
×
727

728
                        (void) backspace_string(ttyfd, passphrase);
×
729
                        flags |= ASK_PASSWORD_SILENT;
×
730

731
                        /* ... or by pressing TAB at any time. */
732

733
                        if (ttyfd >= 0)
×
734
                                (void) loop_write(ttyfd, NO_ECHO, SIZE_MAX);
×
735

736
                } else if (char_is_cc(c) || p >= sizeof(passphrase)-1) {
×
737
                        /* Don't accept control chars or overly long passphrases */
738
                        if (ttyfd >= 0)
×
739
                                (void) loop_write(ttyfd, "\a", 1);
×
740

741
                } else {
742
                        passphrase[p++] = c;
×
743

744
                        if (!FLAGS_SET(flags, ASK_PASSWORD_SILENT) && ttyfd >= 0) {
×
745
                                /* Check if we got a complete UTF-8 character now. If so, let's output one '*'. */
746
                                n = utf8_encoded_valid_unichar(passphrase + codepoint, SIZE_MAX);
×
747
                                if (n >= 0) {
×
748
                                        if (FLAGS_SET(flags, ASK_PASSWORD_ECHO))
×
749
                                                (void) loop_write(ttyfd, passphrase + codepoint, n);
×
750
                                        else
751
                                                (void) loop_write(ttyfd,
×
752
                                                                  glyph(GLYPH_BULLET),
×
753
                                                                  SIZE_MAX);
754
                                        codepoint = p;
755
                                }
756
                        }
757

758
                        dirty = true;
759
                }
760
        }
761

762
        x = strndup(passphrase, p);
1✔
763
        if (!x) {
1✔
764
                r = -ENOMEM;
×
765
                goto finish;
×
766
        }
767

768
        r = strv_consume(&l, x);
1✔
769
        if (r < 0)
1✔
770
                goto finish;
×
771

772
skipped:
1✔
773
        if (strv_isempty(l))
1✔
774
                r = log_debug_errno(SYNTHETIC_ERRNO(ECANCELED), "Password query was cancelled.");
×
775
        else {
776
                if (keyring)
1✔
777
                        (void) add_to_keyring_and_log(keyring, flags, l);
×
778

779
                *ret = TAKE_PTR(l);
1✔
780
                r = 0;
1✔
781
        }
782

783
finish:
1✔
784
        if (ttyfd >= 0 && reset_tty) {
1✔
785
                (void) loop_write(ttyfd, "\n", 1);
×
786
                (void) tcsetattr(ttyfd, TCSANOW, &old_termios);
×
787
        }
788

789
        return r;
790
}
791

792
static int create_socket(const char *askpwdir, char **ret) {
1✔
793
        _cleanup_free_ char *path = NULL;
1✔
794
        union sockaddr_union sa;
1✔
795
        socklen_t sa_len;
1✔
796
        _cleanup_close_ int fd = -EBADF;
1✔
797
        int r;
1✔
798

799
        assert(askpwdir);
1✔
800
        assert(ret);
1✔
801

802
        fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
1✔
803
        if (fd < 0)
1✔
804
                return -errno;
×
805

806
        if (asprintf(&path, "%s/sck.%" PRIx64, askpwdir, random_u64()) < 0)
1✔
807
                return -ENOMEM;
808

809
        r = sockaddr_un_set_path(&sa.un, path);
1✔
810
        if (r < 0)
1✔
811
                return r;
812
        sa_len = r;
1✔
813

814
        WITH_UMASK(0177)
2✔
815
                if (bind(fd, &sa.sa, sa_len) < 0)
1✔
816
                        return -errno;
×
817

818
        r = setsockopt_int(fd, SOL_SOCKET, SO_PASSCRED, true);
1✔
819
        if (r < 0)
1✔
820
                return r;
821

822
        (void) setsockopt_int(fd, SOL_SOCKET, SO_PASSRIGHTS, false);
1✔
823

824
        *ret = TAKE_PTR(path);
1✔
825
        return TAKE_FD(fd);
1✔
826
}
827

828
int ask_password_agent(
1✔
829
                const AskPasswordRequest *req,
830
                AskPasswordFlags flags,
831
                char ***ret) {
832

833
        _cleanup_close_ int socket_fd = -EBADF, signal_fd = -EBADF, inotify_fd = -EBADF, dfd = -EBADF;
3✔
834
        _cleanup_(unlink_and_freep) char *socket_name = NULL;
×
835
        _cleanup_free_ char *temp = NULL, *final = NULL;
1✔
836
        _cleanup_strv_free_erase_ char **l = NULL;
×
837
        _cleanup_fclose_ FILE *f = NULL;
1✔
838
        sigset_t mask, oldmask;
1✔
839
        int r;
1✔
840

841
        assert(req);
1✔
842
        assert(ret);
1✔
843

844
        if (FLAGS_SET(flags, ASK_PASSWORD_HEADLESS))
1✔
845
                return -ENOEXEC;
846

847
        if (FLAGS_SET(flags, ASK_PASSWORD_NO_AGENT))
1✔
848
                return -EUNATCH;
849

850
        /* We don't support the flag file concept for now when querying via the agent logic */
851
        if (req->flag_file)
1✔
852
                return -EOPNOTSUPP;
853

854
        assert_se(sigemptyset(&mask) >= 0);
1✔
855
        assert_se(sigset_add_many(&mask, SIGINT, SIGTERM) >= 0);
1✔
856
        assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) >= 0);
1✔
857

858
        _cleanup_free_ char *askpwdir = NULL;
1✔
859
        r = get_ask_password_directory_for_flags(flags, &askpwdir);
1✔
860
        if (r < 0)
1✔
861
                goto finish;
×
862
        if (r == 0) {
1✔
863
                r = -ENXIO;
×
864
                goto finish;
×
865
        }
866

867
        dfd = open_mkdir(askpwdir, O_RDONLY|O_CLOEXEC, 0755);
1✔
868
        if (dfd < 0) {
1✔
869
                r = log_debug_errno(dfd, "Failed to open directory '%s': %m", askpwdir);
×
870
                goto finish;
×
871
        }
872

873
        if (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && req->keyring) {
1✔
874
                r = ask_password_keyring(req, flags, ret);
×
875
                if (r >= 0) {
×
876
                        r = 0;
×
877
                        goto finish;
×
878
                } else if (r != -ENOKEY)
×
879
                        goto finish;
×
880

881
                inotify_fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
×
882
                if (inotify_fd < 0) {
×
883
                        r = -errno;
×
884
                        goto finish;
×
885
                }
886

887
                r = inotify_add_watch_fd(inotify_fd, dfd, IN_ONLYDIR|IN_ATTRIB /* for mtime */);
×
888
                if (r < 0)
×
889
                        goto finish;
×
890
        }
891

892
        if (asprintf(&final, "ask.%" PRIu64, random_u64()) < 0) {
1✔
893
                r = -ENOMEM;
×
894
                goto finish;
×
895
        }
896

897
        r = fopen_temporary_at(dfd, final, &f, &temp);
1✔
898
        if (r < 0)
1✔
899
                goto finish;
×
900

901
        signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
1✔
902
        if (signal_fd < 0) {
1✔
903
                r = -errno;
×
904
                goto finish;
×
905
        }
906

907
        socket_fd = create_socket(askpwdir, &socket_name);
1✔
908
        if (socket_fd < 0) {
1✔
909
                r = socket_fd;
×
910
                goto finish;
×
911
        }
912

913
        fprintf(f,
1✔
914
                "[Ask]\n"
915
                "PID="PID_FMT"\n"
916
                "Socket=%s\n"
917
                "AcceptCached=%i\n"
918
                "Echo=%i\n"
919
                "NotAfter="USEC_FMT"\n"
920
                "Silent=%i\n",
921
                getpid_cached(),
922
                socket_name,
923
                FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED),
924
                FLAGS_SET(flags, ASK_PASSWORD_ECHO),
1✔
925
                req->until,
1✔
926
                FLAGS_SET(flags, ASK_PASSWORD_SILENT));
1✔
927

928
        if (req->message)
1✔
929
                fprintf(f, "Message=%s\n", req->message);
×
930

931
        if (req->icon)
1✔
932
                fprintf(f, "Icon=%s\n", req->icon);
×
933

934
        if (req->id)
1✔
935
                fprintf(f, "Id=%s\n", req->id);
×
936

937
        if (fchmod(fileno(f), 0644) < 0) {
1✔
938
                r = -errno;
×
939
                goto finish;
×
940
        }
941

942
        r = fflush_and_check(f);
1✔
943
        if (r < 0)
1✔
944
                goto finish;
×
945

946
        if (renameat(dfd, temp, dfd, final) < 0) {
1✔
947
                r = -errno;
×
948
                goto finish;
×
949
        }
950

951
        temp = mfree(temp);
1✔
952

953
        enum {
1✔
954
                POLL_SOCKET,
955
                POLL_SIGNAL,
956
                POLL_THREE,
957
                POLL_FOUR,
958
                _POLL_MAX
959
        };
960

961
        struct pollfd pollfd[_POLL_MAX] = {
1✔
962
                [POLL_SOCKET]  = { .fd = socket_fd,  .events = POLLIN },
963
                [POLL_SIGNAL]  = { .fd = signal_fd,  .events = POLLIN },
964
        };
965
        size_t n_pollfd = POLL_SIGNAL + 1, inotify_idx = SIZE_MAX, hup_fd_idx = SIZE_MAX;
1✔
966

967
        if (inotify_fd >= 0)
1✔
968
                pollfd[inotify_idx = n_pollfd++] = (struct pollfd) {
×
969
                        .fd = inotify_fd,
970
                        .events = POLLIN,
971
                };
972
        if (req->hup_fd >= 0)
1✔
973
                pollfd[hup_fd_idx = n_pollfd ++] = (struct pollfd) {
×
974
                        .fd = req->hup_fd,
975
                        .events = POLLHUP,
976
                };
977

978
        assert(n_pollfd <= _POLL_MAX);
1✔
979

980
        for (;;) {
1✔
981
                CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred))) control;
1✔
982
                char passphrase[LINE_MAX+1];
1✔
983
                struct iovec iovec;
1✔
984
                struct ucred *ucred;
1✔
985
                usec_t timeout;
1✔
986
                ssize_t n;
1✔
987

988
                if (req->until > 0)
1✔
989
                        timeout = usec_sub_unsigned(req->until, now(CLOCK_MONOTONIC));
1✔
990
                else
991
                        timeout = USEC_INFINITY;
992

993
                r = ppoll_usec(pollfd, n_pollfd, timeout);
1✔
994
                if (r == -EINTR)
1✔
995
                        continue;
×
996
                if (r < 0)
1✔
997
                        goto finish;
×
998
                if (r == 0) {
1✔
999
                        r = -ETIME;
×
1000
                        goto finish;
×
1001
                }
1002

1003
                if (pollfd[POLL_SIGNAL].revents & POLLIN) {
1✔
1004
                        r = -EINTR;
×
1005
                        goto finish;
×
1006
                }
1007

1008
                if (req->hup_fd >= 0 && pollfd[hup_fd_idx].revents & POLLHUP)
1✔
1009
                        return -ECONNRESET;
×
1010

1011
                if (inotify_fd >= 0 && pollfd[inotify_idx].revents != 0) {
1✔
1012
                        (void) flush_fd(inotify_fd);
×
1013

1014
                        if (req->keyring) {
×
1015
                                r = ask_password_keyring(req, flags, ret);
×
1016
                                if (r >= 0) {
×
1017
                                        r = 0;
×
1018
                                        goto finish;
×
1019
                                } else if (r != -ENOKEY)
×
1020
                                        goto finish;
×
1021
                        }
1022
                }
1023

1024
                if (pollfd[POLL_SOCKET].revents == 0)
1✔
1025
                        continue;
×
1026

1027
                if (pollfd[POLL_SOCKET].revents != POLLIN) {
1✔
1028
                        r = -EIO;
×
1029
                        goto finish;
×
1030
                }
1031

1032
                iovec = IOVEC_MAKE(passphrase, sizeof(passphrase));
1✔
1033

1034
                struct msghdr msghdr = {
1✔
1035
                        .msg_iov = &iovec,
1036
                        .msg_iovlen = 1,
1037
                        .msg_control = &control,
1038
                        .msg_controllen = sizeof(control),
1039
                };
1040

1041
                n = recvmsg_safe(socket_fd, &msghdr, 0);
1✔
1042
                if (ERRNO_IS_NEG_TRANSIENT(n))
1✔
1043
                        continue;
×
1044
                if (n == -ECHRNG) {
1✔
1045
                        log_debug_errno(n, "Got message with truncated control data (unexpected fds sent?), ignoring.");
×
1046
                        continue;
×
1047
                }
1048
                if (n == -EXFULL) {
1✔
1049
                        log_debug_errno(n, "Got message with truncated payload data, ignoring.");
×
1050
                        continue;
×
1051
                }
1052
                if (n < 0) {
1✔
1053
                        r = (int) n;
×
1054
                        goto finish;
×
1055
                }
1056

1057
                CLEANUP_ERASE(passphrase);
×
1058

1059
                cmsg_close_all(&msghdr);
1✔
1060

1061
                if (n == 0) {
1✔
1062
                        log_debug("Message too short");
×
1063
                        continue;
×
1064
                }
1065

1066
                ucred = CMSG_FIND_DATA(&msghdr, SOL_SOCKET, SCM_CREDENTIALS, struct ucred);
1✔
1067
                if (!ucred) {
1✔
1068
                        log_debug("Received message without credentials. Ignoring.");
×
1069
                        continue;
×
1070
                }
1071

1072
                if (ucred->uid != getuid() && ucred->uid != 0) {
1✔
1073
                        log_debug("Got response from bad user. Ignoring.");
×
1074
                        continue;
×
1075
                }
1076

1077
                if (passphrase[0] == '+') {
1✔
1078
                        /* An empty message refers to the empty password */
1079
                        if (n == 1)
1✔
1080
                                l = strv_new("");
×
1081
                        else
1082
                                l = strv_parse_nulstr(passphrase+1, n-1);
1✔
1083
                        if (!l) {
1✔
1084
                                r = -ENOMEM;
×
1085
                                goto finish;
×
1086
                        }
1087

1088
                        if (strv_isempty(l)) {
1✔
1089
                                l = strv_free(l);
×
1090
                                log_debug("Invalid packet");
×
1091
                                continue;
×
1092
                        }
1093

1094
                        break;
1✔
1095
                }
1096

1097
                if (passphrase[0] == '-') {
×
1098
                        r = -ECANCELED;
×
1099
                        goto finish;
×
1100
                }
1101

1102
                log_debug("Invalid packet");
×
1103
        }
1104

1105
        if (req->keyring)
1✔
1106
                (void) add_to_keyring_and_log(req->keyring, flags, l);
×
1107

1108
        *ret = TAKE_PTR(l);
1✔
1109
        r = 0;
1✔
1110

1111
finish:
1✔
1112
        if (temp) {
1✔
1113
                assert(dfd >= 0);
×
1114
                (void) unlinkat(dfd, temp, 0);
×
1115
        } else if (final) {
1✔
1116
                assert(dfd >= 0);
1✔
1117
                (void) unlinkat(dfd, final, 0);
1✔
1118
        }
1119

1120
        assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0);
1✔
1121
        return r;
1122
}
1123

1124
static int ask_password_credential(const AskPasswordRequest *req, AskPasswordFlags flags, char ***ret) {
83✔
1125
        _cleanup_(erase_and_freep) char *buffer = NULL;
83✔
1126
        _cleanup_strv_free_erase_ char **l = NULL;
83✔
1127
        size_t size;
83✔
1128
        int r;
83✔
1129

1130
        assert(req);
83✔
1131
        assert(req->credential);
83✔
1132
        assert(ret);
83✔
1133

1134
        r = read_credential(req->credential, (void**) &buffer, &size);
83✔
1135
        if (IN_SET(r, -ENXIO, -ENOENT)) /* No credentials passed or this credential not defined? */
83✔
1136
                return -ENOKEY;
1137

1138
        l = strv_parse_nulstr(buffer, size);
2✔
1139
        if (!l)
2✔
1140
                return -ENOMEM;
1141

1142
        if (strv_isempty(l))
2✔
1143
                return log_debug_errno(SYNTHETIC_ERRNO(ENOKEY), "Found an empty password in credential.");
×
1144

1145
        *ret = TAKE_PTR(l);
2✔
1146
        return 0;
2✔
1147
}
1148

1149
int ask_password_auto(
83✔
1150
                const AskPasswordRequest *req,
1151
                AskPasswordFlags flags,
1152
                char ***ret) {
1153

1154
        int r;
83✔
1155

1156
        assert(req);
83✔
1157
        assert(ret);
83✔
1158

1159
        /* Returns the following well-known errors:
1160
         *
1161
         *      -ETIME → a timeout was specified and hit
1162
         *    -EUNATCH → couldn't ask interactively and no cached password available either
1163
         *     -ENOENT → the specified flag file disappeared
1164
         *  -ECANCELED → the user explicitly cancelled the request
1165
         *      -EINTR → SIGINT/SIGTERM where received during the query
1166
         *    -ENOEXEC → headless mode was requested but no password could be acquired non-interactively
1167
         * -ECONNRESET → a POLLHUP has been seen on the specified hup_fd
1168
         */
1169

1170
        if (!FLAGS_SET(flags, ASK_PASSWORD_NO_CREDENTIAL) && req->credential) {
83✔
1171
                r = ask_password_credential(req, flags, ret);
83✔
1172
                if (r != -ENOKEY)
83✔
1173
                        return r;
1174
        }
1175

1176
        if (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) &&
81✔
1177
            req->keyring &&
4✔
1178
            (FLAGS_SET(flags, ASK_PASSWORD_NO_TTY) || !isatty_safe(STDIN_FILENO)) &&
4✔
1179
            FLAGS_SET(flags, ASK_PASSWORD_NO_AGENT)) {
4✔
1180
                r = ask_password_keyring(req, flags, ret);
4✔
1181
                if (r != -ENOKEY)
4✔
1182
                        return r;
1183
        }
1184

1185
        if (!FLAGS_SET(flags, ASK_PASSWORD_NO_TTY) && isatty_safe(STDIN_FILENO))
81✔
1186
                return ask_password_tty(req, flags, ret);
×
1187

1188
        if (!FLAGS_SET(flags, ASK_PASSWORD_NO_AGENT))
81✔
1189
                return ask_password_agent(req, flags, ret);
1✔
1190

1191
        return -EUNATCH;
1192
}
1193

1194
int acquire_user_ask_password_directory(char **ret) {
4✔
1195
        int r;
4✔
1196

1197
        r = xdg_user_runtime_dir("systemd/ask-password", ret);
4✔
1198
        if (r == -ENXIO) {
4✔
1199
                if (ret)
4✔
1200
                        *ret = NULL;
4✔
1201
                return 0;
4✔
1202
        }
1203
        if (r < 0)
×
1204
                return r;
×
1205

1206
        return 1;
1207
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc