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

systemd / systemd / 16583193808

28 Jul 2025 10:16PM UTC coverage: 72.215% (+0.05%) from 72.164%
16583193808

push

github

web-flow
basic: follow-ups for CHASE_MUST_BE_DIRECTORY/REGULAR (#38390)

Follow-up for 90b9f7a07.

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

208 existing lines in 28 files now uncovered.

302739 of 419218 relevant lines covered (72.22%)

761118.59 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 "nulstr-util.h"
27
#include "parse-util.h"
28
#include "path-lookup.h"
29
#include "plymouth-util.h"
30
#include "process-util.h"
31
#include "random-util.h"
32
#include "signal-util.h"
33
#include "socket-util.h"
34
#include "string-table.h"
35
#include "string-util.h"
36
#include "strv.h"
37
#include "terminal-util.h"
38
#include "time-util.h"
39
#include "tmpfile-util.h"
40
#include "umask-util.h"
41
#include "utf8.h"
42

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

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

54
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(keyring, int);
×
55

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

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

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

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

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

76
        assert(ret);
×
77

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

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

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

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

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

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

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

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

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

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

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

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

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

131
        saved_timeout_set = true;
×
132

133
        return saved_timeout;
×
134
}
135

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

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

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

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

163
        saved_keyring_set = true;
×
164

165
        return saved_keyring;
×
166
}
167

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

175
        assert(keyname);
×
176

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

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

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

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

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

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

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

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

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

217
        return 1;
218
}
219

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

223
        assert(keyname);
×
224

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

229
        return 0;
230
}
231

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

342
        CLEANUP_ERASE(buffer);
×
343

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

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

369
        assert(n_pollfd <= _POLL_MAX);
×
370

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

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

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

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

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

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

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

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

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

409
                p += k;
×
410

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

530
        CLEANUP_ERASE(passphrase);
1✔
531

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

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

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

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

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

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

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

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

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

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

572
                reset_tty = true;
573
        }
574

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

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

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

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

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

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

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

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

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

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

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

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

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

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

656
                }
657

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

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

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

672
                        goto skipped;
×
673
                }
674

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

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

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

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

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

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

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

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

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

706
                                        q += z;
707
                                }
708

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

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

714
                                flags |= ASK_PASSWORD_SILENT;
×
715

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

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

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

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

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

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

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

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

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

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

757
                        dirty = true;
758
                }
759
        }
760

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

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

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

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

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

788
        return r;
789
}
790

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

950
        temp = mfree(temp);
1✔
951

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1056
                CLEANUP_ERASE(passphrase);
×
1057

1058
                cmsg_close_all(&msghdr);
1✔
1059

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

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

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

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

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

1093
                        break;
1✔
1094
                }
1095

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

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

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

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

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

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

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

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

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

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

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

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

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

1153
        int r;
83✔
1154

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

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

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

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

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

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

1190
        return -EUNATCH;
1191
}
1192

1193
int acquire_user_ask_password_directory(char **ret) {
3✔
1194
        int r;
3✔
1195

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

1205
        return 1;
1206
}
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