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

systemd / systemd / 29132483780

10 Jul 2026 08:43PM UTC coverage: 72.912% (+0.2%) from 72.702%
29132483780

push

github

bluca
man: run forgotten 'update-man-rules'

344600 of 472622 relevant lines covered (72.91%)

1365091.83 hits per line

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

0.0
/src/ssh-generator/ssh-generator.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <unistd.h>
4

5
#include "alloc-util.h"
6
#include "creds-util.h"
7
#include "dlopen-note.h"
8
#include "errno-util.h"
9
#include "fd-util.h"
10
#include "fileio.h"
11
#include "generator.h"
12
#include "install.h"
13
#include "log.h"
14
#include "parse-util.h"
15
#include "path-lookup.h"
16
#include "path-util.h"
17
#include "proc-cmdline.h"
18
#include "socket-netlink.h"
19
#include "socket-util.h"
20
#include "special.h"
21
#include "ssh-util.h"
22
#include "string-util.h"
23
#include "strv.h"
24
#include "virt.h"
25

26
/* A small generator binding potentially five or more SSH sockets:
27
 *
28
 *     1. Listen on AF_VSOCK port 22 if we run in a VM with AF_VSOCK enabled
29
 *     2. Listen on AF_UNIX socket /run/host/unix-export/ssh if we run in a container with /run/host/ support
30
 *     3. Listen on AF_UNIX socket /run/ssh-unix-local/socket (always)
31
 *     4. Listen on any socket specified via kernel command line option systemd.ssh_listen=
32
 *     5. Similar, but from system credential ssh.listen
33
 *
34
 * The first two provide a nice way for hosts to connect to containers and VMs they invoke via the usual SSH
35
 * logic, but without waiting for networking or suchlike. The third allows the same for local clients. */
36

37
static bool arg_auto = true;
38
static char **arg_listen_extra = NULL;
39

40
STATIC_DESTRUCTOR_REGISTER(arg_listen_extra, strv_freep);
×
41

42
static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
×
43
        int r;
×
44

45
        assert(key);
×
46

47
        if (proc_cmdline_key_streq(key, "systemd.ssh_auto")) {
×
48
                r = value ? parse_boolean(value) : 1;
×
49
                if (r < 0)
×
50
                        log_warning_errno(r, "Failed to parse systemd.ssh_auto switch \"%s\", ignoring: %m", value);
×
51
                else
52
                        arg_auto = r;
×
53

54
        } else if (proc_cmdline_key_streq(key, "systemd.ssh_listen")) {
×
55

56
                if (proc_cmdline_value_missing(key, value))
×
57
                        return 0;
×
58

59
                SocketAddress sa;
×
60
                r = socket_address_parse(&sa, value);
×
61
                if (r < 0)
×
62
                        log_warning_errno(r, "Failed to parse systemd.ssh_listen= expression, ignoring: %s", value);
×
63
                else {
64
                        _cleanup_free_ char *s = NULL;
×
65
                        r = socket_address_print(&sa, &s);
×
66
                        if (r < 0)
×
67
                                return log_error_errno(r, "Failed to format socket address: %m");
×
68

69
                        if (strv_consume(&arg_listen_extra, TAKE_PTR(s)) < 0)
×
70
                                return log_oom();
×
71
                }
72
        }
73

74
        return 0;
75
}
76

77
static int make_sshd_template_unit(
×
78
                const char *dest,
79
                const char *template,
80
                const char *sshd_binary,
81
                const char *found_sshd_template_service,
82
                char **generated_sshd_template_unit) {
83

84
        int r;
×
85

86
        assert(dest);
×
87
        assert(template);
×
88
        assert(sshd_binary);
×
89
        assert(generated_sshd_template_unit);
×
90

91
        /* If the system has a suitable template already, symlink it under the name we want to use */
92
        if (found_sshd_template_service)
×
93
                return generator_add_symlink(
×
94
                                dest,
95
                                template,
96
                                /* dep_type= */ NULL,
97
                                found_sshd_template_service);
98

99
        if (!*generated_sshd_template_unit) {
×
100
                _cleanup_fclose_ FILE *f = NULL;
×
101

102
                /* We use a generic name for the unit, since we'll use it for both AF_UNIX and AF_VSOCK  */
103
                r = generator_open_unit_file_full(
×
104
                                dest,
105
                                /* source= */ NULL,
106
                                "sshd-generated@.service",
107
                                &f,
108
                                generated_sshd_template_unit,
109
                                /* ret_temp_path= */ NULL);
110
                if (r < 0)
×
111
                        return r;
112

113
                /* sshd reads AuthorizedKeysFile after dropping to the authenticating user's UID, so the
114
                 * 0400 credential file under $CREDENTIALS_DIRECTORY is unreadable for non-root users.
115
                 * Materialize a 0444 copy in a RuntimeDirectory so the ephemeral key works for any user. */
116
                fprintf(f,
×
117
                        "[Unit]\n"
118
                        "Description=OpenSSH Per-Connection Server Daemon\n"
119
                        "Documentation=man:systemd-ssh-generator(8) man:sshd(8)\n"
120
                        "\n"
121
                        "[Service]\n"
122
                        "ExecStartPre=systemd-tmpfiles --create --inline 'f^ /run/sshd-generated-%%i/authorized_keys 0444 root root - ssh.ephemeral-authorized_keys-all'\n"
123
                        "ExecStart=-%s -i -o \"AuthorizedKeysFile /run/sshd-generated-%%i/authorized_keys .ssh/authorized_keys\"\n"
124
                        "StandardInput=socket\n"
125
                        "ImportCredential=ssh.ephemeral-authorized_keys-all\n"
126
                        "RuntimeDirectory=sshd-generated-%%i\n",
127
                        sshd_binary);
128

129
                r = fflush_and_check(f);
×
130
                if (r < 0)
×
131
                        return log_error_errno(r, "Failed to write sshd template: %m");
×
132
        }
133

134
        return generator_add_symlink(
×
135
                        dest,
136
                        template,
137
                        /* dep_type= */ NULL,
138
                        *generated_sshd_template_unit);
139
}
140

141
static int write_socket_unit(
×
142
                const char *dest,
143
                const char *unit,
144
                const char *listen_stream,
145
                const char *comment,
146
                const char *extra,
147
                bool with_ssh_access_target_dependency) {
148

149
        int r;
×
150

151
        assert(dest);
×
152
        assert(unit);
×
153
        assert(listen_stream);
×
154
        assert(comment);
×
155

156
        _cleanup_fclose_ FILE *f = NULL;
×
157
        r = generator_open_unit_file(
×
158
                        dest,
159
                        /* source= */ NULL,
160
                        unit,
161
                        &f);
162
        if (r < 0)
×
163
                return r;
164

165
        fprintf(f,
×
166
                "[Unit]\n"
167
                "Description=OpenSSH Server Socket (systemd-ssh-generator, %s)\n"
168
                "Documentation=man:systemd-ssh-generator(8)\n",
169
                comment);
170

171
        /* When this is a remotely accessible socket let's mark this with a milestone: ssh-access.target */
172
        if (with_ssh_access_target_dependency)
×
173
                fputs("Wants=ssh-access.target\n"
×
174
                      "Before=ssh-access.target\n",
175
                      f);
176

177
        fprintf(f,
×
178
                "\n[Socket]\n"
179
                "ListenStream=%s\n"
180
                "Accept=yes\n"
181
                "PollLimitIntervalSec=30s\n"
182
                "PollLimitBurst=50\n",
183
                listen_stream);
184

185
        if (extra)
×
186
                fputs(extra, f);
×
187

188
        r = fflush_and_check(f);
×
189
        if (r < 0)
×
190
                return log_error_errno(r, "Failed to write %s SSH socket unit: %m", comment);
×
191

192
        r = generator_add_symlink(
×
193
                        dest,
194
                        SPECIAL_SOCKETS_TARGET,
195
                        "wants",
196
                        unit);
197
        if (r < 0)
×
198
                return r;
×
199

200
        return 0;
201
}
202

203
static int add_vsock_socket(
×
204
                const char *dest,
205
                const char *sshd_binary,
206
                const char *found_sshd_template_unit,
207
                char **generated_sshd_template_unit) {
208

209
        int r;
×
210

211
        assert(dest);
×
212
        assert(generated_sshd_template_unit);
×
213

214
        Virtualization v = detect_virtualization();
×
215
        if (v < 0)
×
216
                return log_error_errno(v, "Failed to detect if we run in a VM: %m");
×
217
        if (!VIRTUALIZATION_IS_VM(v)) {
×
218
                /* NB: if we are running in a container inside a VM, then we'll *not* do AF_VSOCK stuff */
219
                log_debug("Not running in a VM, not listening on AF_VSOCK.");
×
220
                return 0;
221
        }
222

223
        r = vsock_open_or_warn(/* ret= */ NULL);
×
224
        if (r <= 0)
×
225
                return r;
226

227
        /* Determine the local CID so that we can log it to help users to connect to this VM */
228
        unsigned local_cid;
×
229
        r = vsock_get_local_cid_or_warn(&local_cid);
×
230
        if (r <= 0)
×
231
                return r;
232

233
        r = make_sshd_template_unit(
×
234
                        dest,
235
                        "sshd-vsock@.service",
236
                        sshd_binary,
237
                        found_sshd_template_unit,
238
                        generated_sshd_template_unit);
239
        if (r < 0)
×
240
                return r;
241

242
        r = write_socket_unit(
×
243
                        dest,
244
                        "sshd-vsock.socket",
245
                        "vsock::22",
246
                        "AF_VSOCK",
247
                        "ExecStartPost=-/usr/lib/systemd/systemd-ssh-issue make-vsock\n"
248
                        "ExecStopPre=-/usr/lib/systemd/systemd-ssh-issue rm-vsock\n",
249
                        /* with_ssh_access_target_dependency= */ true);
250
        if (r < 0)
×
251
                return r;
252

253
        log_debug("Binding SSH to AF_VSOCK vsock::22.\n"
×
254
                  "→ connect via 'ssh vsock/%u' from host", local_cid);
255
        return 0;
256
}
257

258
static int add_local_unix_socket(
×
259
                const char *dest,
260
                const char *sshd_binary,
261
                const char *found_sshd_template_unit,
262
                char **generated_sshd_template_unit) {
263

264
        int r;
×
265

266
        assert(dest);
×
267
        assert(sshd_binary);
×
268
        assert(generated_sshd_template_unit);
×
269

270
        r = make_sshd_template_unit(
×
271
                        dest,
272
                        "sshd-unix-local@.service",
273
                        sshd_binary,
274
                        found_sshd_template_unit,
275
                        generated_sshd_template_unit);
276
        if (r < 0)
×
277
                return r;
278

279
        r = write_socket_unit(
×
280
                        dest,
281
                        "sshd-unix-local.socket",
282
                        "/run/ssh-unix-local/socket",
283
                        "AF_UNIX Local",
284
                        /* extra= */ NULL,
285
                        /* with_ssh_access_target_dependency= */ false);
286
        if (r < 0)
×
287
                return r;
288

289
        log_debug("Binding SSH to AF_UNIX socket /run/ssh-unix-local/socket.\n"
×
290
                  "→ connect via 'ssh .host' locally");
291
        return 0;
292
}
293

294
static int add_export_unix_socket(
×
295
                const char *dest,
296
                const char *sshd_binary,
297
                const char *found_sshd_template_unit,
298
                char **generated_sshd_template_unit) {
299

300
        int r;
×
301

302
        assert(dest);
×
303
        assert(sshd_binary);
×
304
        assert(generated_sshd_template_unit);
×
305

306
        Virtualization v = detect_container();
×
307
        if (v < 0)
×
308
                return log_error_errno(v, "Failed to detect if we run in a container: %m");
×
309
        if (v == VIRTUALIZATION_NONE) {
×
310
                log_debug("Not running in container, not listening on /run/host/unix-export/ssh");
×
311
                return 0;
312
        }
313

314
        if (access("/run/host/unix-export/", W_OK) < 0) {
×
315
                if (errno == ENOENT) {
×
316
                        log_debug("Container manager does not provide /run/host/unix-export/ mount, not binding AF_UNIX socket there.");
×
317
                        return 0;
318
                }
319
                if (ERRNO_IS_FS_WRITE_REFUSED(errno)) {
×
320
                        log_debug("Container manager does not provide write access to /run/host/unix-export/, not binding AF_UNIX socket there.");
×
321
                        return 0;
322
                }
323

324
                return log_error_errno(errno, "Unable to check if /run/host/unix-export exists: %m");
×
325
        }
326

327
        r = make_sshd_template_unit(
×
328
                        dest,
329
                        "sshd-unix-export@.service",
330
                        sshd_binary,
331
                        found_sshd_template_unit,
332
                        generated_sshd_template_unit);
333
        if (r < 0)
×
334
                return r;
335

336
        r = write_socket_unit(
×
337
                        dest,
338
                        "sshd-unix-export.socket",
339
                        "/run/host/unix-export/ssh",
340
                        "AF_UNIX Export",
341
                        /* extra= */ NULL,
342
                        /* with_ssh_access_target_dependency= */ true);
343
        if (r < 0)
×
344
                return r;
345

346
        log_debug("Binding SSH to AF_UNIX socket /run/host/unix-export/ssh\n"
×
347
                  "→ connect via 'ssh unix/run/systemd/nspawn/\?\?\?/unix-export/ssh' from host");
348

349
        return 0;
350
}
351

352
static int add_extra_sockets(
×
353
                const char *dest,
354
                const char *sshd_binary,
355
                const char *found_sshd_template_unit,
356
                char **generated_sshd_template_unit) {
357

358
        unsigned n = 1;
×
359
        int r;
×
360

361
        assert(dest);
×
362
        assert(sshd_binary);
×
363
        assert(generated_sshd_template_unit);
×
364

365
        STRV_FOREACH(i, arg_listen_extra) {
×
366
                _cleanup_free_ char *service = NULL, *socket = NULL;
×
367

368
                if (n > 1) {
×
369
                        if (asprintf(&service, "sshd-extra-%u@.service", n) < 0)
×
370
                                return log_oom();
×
371

372
                        if (asprintf(&socket, "sshd-extra-%u.socket", n) < 0)
×
373
                                return log_oom();
×
374
                }
375

376
                r = make_sshd_template_unit(
×
377
                                dest,
378
                                service ?: "sshd-extra@.service",
×
379
                                sshd_binary,
380
                                found_sshd_template_unit,
381
                                generated_sshd_template_unit);
382
                if (r < 0)
×
383
                        return r;
384

385
                r = write_socket_unit(
×
386
                                dest,
387
                                socket ?: "sshd-extra.socket",
×
388
                                *i,
389
                                *i,
390
                                /* extra= */ NULL,
391
                                /* with_ssh_access_target_dependency= */ true);
392
                if (r < 0)
×
393
                        return r;
394

395
                log_debug("Binding SSH to socket %s.", *i);
×
396
                n++;
×
397
        }
398

399
        return 0;
400
}
401

402
static int parse_credentials(void) {
×
403
        _cleanup_free_ char *b = NULL;
×
404
        size_t sz = 0;
×
405
        int r;
×
406

407
        r = read_credential_with_decryption("ssh.listen", (void**) &b, &sz);
×
408
        if (r <= 0)
×
409
                return r;
410

411
        _cleanup_fclose_ FILE *f = NULL;
×
412
        f = fmemopen_unlocked(b, sz, "r");
×
413
        if (!f)
×
414
                return log_oom();
×
415

416
        for (;;) {
×
417
                _cleanup_free_ char *item = NULL;
×
418

419
                r = read_stripped_line(f, LINE_MAX, &item);
×
420
                if (r == 0)
×
421
                        break;
422
                if (r < 0) {
×
423
                        log_error_errno(r, "Failed to parse credential 'ssh.listen': %m");
×
424
                        break;
425
                }
426

427
                if (startswith(item, "#"))
×
428
                        continue;
×
429

430
                SocketAddress sa;
×
431
                r = socket_address_parse(&sa, item);
×
432
                if (r < 0) {
×
433
                        log_warning_errno(r, "Failed to parse systemd.ssh_listen= expression, ignoring: %s", item);
×
434
                        continue;
×
435
                }
436

437
                _cleanup_free_ char *s = NULL;
×
438
                r = socket_address_print(&sa, &s);
×
439
                if (r < 0)
×
440
                        return log_error_errno(r, "Failed to format socket address: %m");
×
441

442
                if (strv_consume(&arg_listen_extra, TAKE_PTR(s)) < 0)
×
443
                        return log_oom();
×
444
        }
445

446
        return 0;
×
447
}
448

449
static int run(const char *dest, const char *dest_early, const char *dest_late) {
×
450
        int r;
×
451

452
        assert(dest);
×
453

454
        LIBCRYPTO_NOTE(suggested);
×
455
        TPM2_NOTE(suggested);
×
456

457
        r = proc_cmdline_parse(parse_proc_cmdline_item, /* userdata= */ NULL, /* flags= */ 0);
×
458
        if (r < 0)
×
459
                log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
×
460

461
        (void) parse_credentials();
×
462

463
        strv_sort_uniq(arg_listen_extra);
×
464

465
        if (!arg_auto && strv_isempty(arg_listen_extra)) {
×
466
                log_debug("Disabling SSH generator logic, because it has been turned off explicitly.");
×
467
                return 0;
×
468
        }
469

470
        _cleanup_free_ char *sshd_binary = NULL;
×
471
        r = find_executable("sshd", &sshd_binary);
×
472
        if (r == -ENOENT) {
×
473
                log_debug("Disabling SSH generator logic, since sshd is not installed.");
×
474
                return 0;
475
        }
476
        if (r < 0)
×
477
                return log_error_errno(r, "Failed to determine if sshd is installed: %m");
×
478

479
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
×
480
        r = lookup_paths_init_or_warn(&lp, RUNTIME_SCOPE_SYSTEM, LOOKUP_PATHS_EXCLUDE_GENERATED, /* root_dir= */ NULL);
×
481
        if (r < 0)
×
482
                return r;
483

484
        _cleanup_free_ char *found_sshd_template_unit = NULL;
×
485
        r = unit_file_exists_full(RUNTIME_SCOPE_SYSTEM, &lp,
×
486
                                  SEARCH_FOLLOW_CONFIG_SYMLINKS,
487
                                  "sshd@.service",
488
                                  &found_sshd_template_unit);
489
        if (r < 0)
×
490
                return log_error_errno(r, "Unable to detect if sshd@.service exists: %m");
×
491

492
        _cleanup_free_ char *generated_sshd_template_unit = NULL;
×
493
        RET_GATHER(r, add_extra_sockets(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
×
494

495
        if (arg_auto) {
×
496
                RET_GATHER(r, add_vsock_socket(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
×
497
                RET_GATHER(r, add_local_unix_socket(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
×
498
                RET_GATHER(r, add_export_unix_socket(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
×
499
        }
500

501
        return r;
×
502
}
503

504
DEFINE_MAIN_GENERATOR_FUNCTION(run);
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc