Line | Count | Source (jump to first uncovered line) |
1 | | /* $OpenBSD: ssh-agent.c,v 1.310 2025/02/18 08:02:48 djm Exp $ */ |
2 | | /* |
3 | | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
4 | | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
5 | | * All rights reserved |
6 | | * The authentication agent program. |
7 | | * |
8 | | * As far as I am concerned, the code I have written for this software |
9 | | * can be used freely for any purpose. Any derived versions of this |
10 | | * software must be clearly marked as such, and if the derived work is |
11 | | * incompatible with the protocol description in the RFC file, it must be |
12 | | * called by a name other than "ssh" or "Secure Shell". |
13 | | * |
14 | | * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. |
15 | | * |
16 | | * Redistribution and use in source and binary forms, with or without |
17 | | * modification, are permitted provided that the following conditions |
18 | | * are met: |
19 | | * 1. Redistributions of source code must retain the above copyright |
20 | | * notice, this list of conditions and the following disclaimer. |
21 | | * 2. Redistributions in binary form must reproduce the above copyright |
22 | | * notice, this list of conditions and the following disclaimer in the |
23 | | * documentation and/or other materials provided with the distribution. |
24 | | * |
25 | | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
26 | | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
27 | | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
28 | | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
29 | | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
30 | | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
31 | | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
32 | | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
33 | | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
34 | | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
35 | | */ |
36 | | |
37 | | #include "includes.h" |
38 | | |
39 | | #include <sys/types.h> |
40 | | #include <sys/resource.h> |
41 | | #include <sys/stat.h> |
42 | | #include <sys/socket.h> |
43 | | #include <sys/wait.h> |
44 | | #ifdef HAVE_SYS_TIME_H |
45 | | # include <sys/time.h> |
46 | | #endif |
47 | | #ifdef HAVE_SYS_UN_H |
48 | | # include <sys/un.h> |
49 | | #endif |
50 | | #include "openbsd-compat/sys-queue.h" |
51 | | |
52 | | #ifdef WITH_OPENSSL |
53 | | #include <openssl/evp.h> |
54 | | #include "openbsd-compat/openssl-compat.h" |
55 | | #endif |
56 | | |
57 | | #include <errno.h> |
58 | | #include <fcntl.h> |
59 | | #include <limits.h> |
60 | | #ifdef HAVE_PATHS_H |
61 | | # include <paths.h> |
62 | | #endif |
63 | | #ifdef HAVE_POLL_H |
64 | | # include <poll.h> |
65 | | #endif |
66 | | #include <signal.h> |
67 | | #include <stdarg.h> |
68 | | #include <stdio.h> |
69 | | #include <stdlib.h> |
70 | | #include <time.h> |
71 | | #include <string.h> |
72 | | #include <unistd.h> |
73 | | #ifdef HAVE_UTIL_H |
74 | | # include <util.h> |
75 | | #endif |
76 | | |
77 | | #include "xmalloc.h" |
78 | | #include "ssh.h" |
79 | | #include "ssh2.h" |
80 | | #include "sshbuf.h" |
81 | | #include "sshkey.h" |
82 | | #include "authfd.h" |
83 | | #include "log.h" |
84 | | #include "misc.h" |
85 | | #include "digest.h" |
86 | | #include "ssherr.h" |
87 | | #include "match.h" |
88 | | #include "msg.h" |
89 | | #include "pathnames.h" |
90 | | #include "ssh-pkcs11.h" |
91 | | #include "sk-api.h" |
92 | | #include "myproposal.h" |
93 | | |
94 | | #ifndef DEFAULT_ALLOWED_PROVIDERS |
95 | 0 | # define DEFAULT_ALLOWED_PROVIDERS "/usr/lib*/*,/usr/local/lib*/*" |
96 | | #endif |
97 | | #ifndef DEFAULT_WEBSAFE_ALLOWLIST |
98 | 0 | # define DEFAULT_WEBSAFE_ALLOWLIST "ssh:*" |
99 | | #endif |
100 | | |
101 | | /* Maximum accepted message length */ |
102 | 0 | #define AGENT_MAX_LEN (256*1024) |
103 | | /* Maximum bytes to read from client socket */ |
104 | 0 | #define AGENT_RBUF_LEN (4096) |
105 | | /* Maximum number of recorded session IDs/hostkeys per connection */ |
106 | 0 | #define AGENT_MAX_SESSION_IDS 16 |
107 | | /* Maximum size of session ID */ |
108 | 0 | #define AGENT_MAX_SID_LEN 128 |
109 | | /* Maximum number of destination constraints to accept on a key */ |
110 | 0 | #define AGENT_MAX_DEST_CONSTRAINTS 1024 |
111 | | /* Maximum number of associated certificate constraints to accept on a key */ |
112 | 0 | #define AGENT_MAX_EXT_CERTS 1024 |
113 | | |
114 | | /* XXX store hostkey_sid in a refcounted tree */ |
115 | | |
116 | | typedef enum { |
117 | | AUTH_UNUSED = 0, |
118 | | AUTH_SOCKET = 1, |
119 | | AUTH_CONNECTION = 2, |
120 | | } sock_type; |
121 | | |
122 | | struct hostkey_sid { |
123 | | struct sshkey *key; |
124 | | struct sshbuf *sid; |
125 | | int forwarded; |
126 | | }; |
127 | | |
128 | | typedef struct socket_entry { |
129 | | int fd; |
130 | | sock_type type; |
131 | | struct sshbuf *input; |
132 | | struct sshbuf *output; |
133 | | struct sshbuf *request; |
134 | | size_t nsession_ids; |
135 | | struct hostkey_sid *session_ids; |
136 | | int session_bind_attempted; |
137 | | } SocketEntry; |
138 | | |
139 | | u_int sockets_alloc = 0; |
140 | | SocketEntry *sockets = NULL; |
141 | | |
142 | | typedef struct identity { |
143 | | TAILQ_ENTRY(identity) next; |
144 | | struct sshkey *key; |
145 | | char *comment; |
146 | | char *provider; |
147 | | time_t death; |
148 | | u_int confirm; |
149 | | char *sk_provider; |
150 | | struct dest_constraint *dest_constraints; |
151 | | size_t ndest_constraints; |
152 | | } Identity; |
153 | | |
154 | | struct idtable { |
155 | | int nentries; |
156 | | TAILQ_HEAD(idqueue, identity) idlist; |
157 | | }; |
158 | | |
159 | | /* private key table */ |
160 | | struct idtable *idtab; |
161 | | |
162 | | int max_fd = 0; |
163 | | |
164 | | /* pid of shell == parent of agent */ |
165 | | pid_t parent_pid = -1; |
166 | | time_t parent_alive_interval = 0; |
167 | | |
168 | | static sig_atomic_t signalled_exit; |
169 | | static sig_atomic_t signalled_keydrop; |
170 | | |
171 | | /* pid of process for which cleanup_socket is applicable */ |
172 | | pid_t cleanup_pid = 0; |
173 | | |
174 | | /* pathname and directory for AUTH_SOCKET */ |
175 | | char socket_name[PATH_MAX]; |
176 | | char socket_dir[PATH_MAX]; |
177 | | |
178 | | /* Pattern-list of allowed PKCS#11/Security key paths */ |
179 | | static char *allowed_providers; |
180 | | |
181 | | /* |
182 | | * Allows PKCS11 providers or SK keys that use non-internal providers to |
183 | | * be added over a remote connection (identified by session-bind@openssh.com). |
184 | | */ |
185 | | static int remote_add_provider; |
186 | | |
187 | | /* locking */ |
188 | 0 | #define LOCK_SIZE 32 |
189 | | #define LOCK_SALT_SIZE 16 |
190 | 0 | #define LOCK_ROUNDS 1 |
191 | | int locked = 0; |
192 | | u_char lock_pwhash[LOCK_SIZE]; |
193 | | u_char lock_salt[LOCK_SALT_SIZE]; |
194 | | |
195 | | extern char *__progname; |
196 | | |
197 | | /* Default lifetime in seconds (0 == forever) */ |
198 | | static int lifetime = 0; |
199 | | |
200 | | static int fingerprint_hash = SSH_FP_HASH_DEFAULT; |
201 | | |
202 | | /* Refuse signing of non-SSH messages for web-origin FIDO keys */ |
203 | | static int restrict_websafe = 1; |
204 | | static char *websafe_allowlist; |
205 | | |
206 | | static void |
207 | | close_socket(SocketEntry *e) |
208 | 0 | { |
209 | 0 | size_t i; |
210 | |
|
211 | 0 | close(e->fd); |
212 | 0 | sshbuf_free(e->input); |
213 | 0 | sshbuf_free(e->output); |
214 | 0 | sshbuf_free(e->request); |
215 | 0 | for (i = 0; i < e->nsession_ids; i++) { |
216 | 0 | sshkey_free(e->session_ids[i].key); |
217 | 0 | sshbuf_free(e->session_ids[i].sid); |
218 | 0 | } |
219 | 0 | free(e->session_ids); |
220 | 0 | memset(e, '\0', sizeof(*e)); |
221 | 0 | e->fd = -1; |
222 | 0 | e->type = AUTH_UNUSED; |
223 | 0 | } |
224 | | |
225 | | static void |
226 | | idtab_init(void) |
227 | 0 | { |
228 | 0 | idtab = xcalloc(1, sizeof(*idtab)); |
229 | 0 | TAILQ_INIT(&idtab->idlist); |
230 | 0 | idtab->nentries = 0; |
231 | 0 | } |
232 | | |
233 | | static void |
234 | | free_dest_constraint_hop(struct dest_constraint_hop *dch) |
235 | 0 | { |
236 | 0 | u_int i; |
237 | |
|
238 | 0 | if (dch == NULL) |
239 | 0 | return; |
240 | 0 | free(dch->user); |
241 | 0 | free(dch->hostname); |
242 | 0 | for (i = 0; i < dch->nkeys; i++) |
243 | 0 | sshkey_free(dch->keys[i]); |
244 | 0 | free(dch->keys); |
245 | 0 | free(dch->key_is_ca); |
246 | 0 | } |
247 | | |
248 | | static void |
249 | | free_dest_constraints(struct dest_constraint *dcs, size_t ndcs) |
250 | 0 | { |
251 | 0 | size_t i; |
252 | |
|
253 | 0 | for (i = 0; i < ndcs; i++) { |
254 | 0 | free_dest_constraint_hop(&dcs[i].from); |
255 | 0 | free_dest_constraint_hop(&dcs[i].to); |
256 | 0 | } |
257 | 0 | free(dcs); |
258 | 0 | } |
259 | | |
260 | | #ifdef ENABLE_PKCS11 |
261 | | static void |
262 | | dup_dest_constraint_hop(const struct dest_constraint_hop *dch, |
263 | | struct dest_constraint_hop *out) |
264 | 0 | { |
265 | 0 | u_int i; |
266 | 0 | int r; |
267 | |
|
268 | 0 | out->user = dch->user == NULL ? NULL : xstrdup(dch->user); |
269 | 0 | out->hostname = dch->hostname == NULL ? NULL : xstrdup(dch->hostname); |
270 | 0 | out->is_ca = dch->is_ca; |
271 | 0 | out->nkeys = dch->nkeys; |
272 | 0 | out->keys = out->nkeys == 0 ? NULL : |
273 | 0 | xcalloc(out->nkeys, sizeof(*out->keys)); |
274 | 0 | out->key_is_ca = out->nkeys == 0 ? NULL : |
275 | 0 | xcalloc(out->nkeys, sizeof(*out->key_is_ca)); |
276 | 0 | for (i = 0; i < dch->nkeys; i++) { |
277 | 0 | if (dch->keys[i] != NULL && |
278 | 0 | (r = sshkey_from_private(dch->keys[i], |
279 | 0 | &(out->keys[i]))) != 0) |
280 | 0 | fatal_fr(r, "copy key"); |
281 | 0 | out->key_is_ca[i] = dch->key_is_ca[i]; |
282 | 0 | } |
283 | 0 | } |
284 | | |
285 | | static struct dest_constraint * |
286 | | dup_dest_constraints(const struct dest_constraint *dcs, size_t ndcs) |
287 | 0 | { |
288 | 0 | size_t i; |
289 | 0 | struct dest_constraint *ret; |
290 | |
|
291 | 0 | if (ndcs == 0) |
292 | 0 | return NULL; |
293 | 0 | ret = xcalloc(ndcs, sizeof(*ret)); |
294 | 0 | for (i = 0; i < ndcs; i++) { |
295 | 0 | dup_dest_constraint_hop(&dcs[i].from, &ret[i].from); |
296 | 0 | dup_dest_constraint_hop(&dcs[i].to, &ret[i].to); |
297 | 0 | } |
298 | 0 | return ret; |
299 | 0 | } |
300 | | #endif /* ENABLE_PKCS11 */ |
301 | | |
302 | | #ifdef DEBUG_CONSTRAINTS |
303 | | static void |
304 | | dump_dest_constraint_hop(const struct dest_constraint_hop *dch) |
305 | | { |
306 | | u_int i; |
307 | | char *fp; |
308 | | |
309 | | debug_f("user %s hostname %s is_ca %d nkeys %u", |
310 | | dch->user == NULL ? "(null)" : dch->user, |
311 | | dch->hostname == NULL ? "(null)" : dch->hostname, |
312 | | dch->is_ca, dch->nkeys); |
313 | | for (i = 0; i < dch->nkeys; i++) { |
314 | | fp = NULL; |
315 | | if (dch->keys[i] != NULL && |
316 | | (fp = sshkey_fingerprint(dch->keys[i], |
317 | | SSH_FP_HASH_DEFAULT, SSH_FP_DEFAULT)) == NULL) |
318 | | fatal_f("fingerprint failed"); |
319 | | debug_f("key %u/%u: %s%s%s key_is_ca %d", i, dch->nkeys, |
320 | | dch->keys[i] == NULL ? "" : sshkey_ssh_name(dch->keys[i]), |
321 | | dch->keys[i] == NULL ? "" : " ", |
322 | | dch->keys[i] == NULL ? "none" : fp, |
323 | | dch->key_is_ca[i]); |
324 | | free(fp); |
325 | | } |
326 | | } |
327 | | #endif /* DEBUG_CONSTRAINTS */ |
328 | | |
329 | | static void |
330 | | dump_dest_constraints(const char *context, |
331 | | const struct dest_constraint *dcs, size_t ndcs) |
332 | 0 | { |
333 | | #ifdef DEBUG_CONSTRAINTS |
334 | | size_t i; |
335 | | |
336 | | debug_f("%s: %zu constraints", context, ndcs); |
337 | | for (i = 0; i < ndcs; i++) { |
338 | | debug_f("constraint %zu / %zu: from: ", i, ndcs); |
339 | | dump_dest_constraint_hop(&dcs[i].from); |
340 | | debug_f("constraint %zu / %zu: to: ", i, ndcs); |
341 | | dump_dest_constraint_hop(&dcs[i].to); |
342 | | } |
343 | | debug_f("done for %s", context); |
344 | | #endif /* DEBUG_CONSTRAINTS */ |
345 | 0 | } |
346 | | |
347 | | static void |
348 | | free_identity(Identity *id) |
349 | 0 | { |
350 | 0 | sshkey_free(id->key); |
351 | 0 | free(id->provider); |
352 | 0 | free(id->comment); |
353 | 0 | free(id->sk_provider); |
354 | 0 | free_dest_constraints(id->dest_constraints, id->ndest_constraints); |
355 | 0 | free(id); |
356 | 0 | } |
357 | | |
358 | | /* |
359 | | * Match 'key' against the key/CA list in a destination constraint hop |
360 | | * Returns 0 on success or -1 otherwise. |
361 | | */ |
362 | | static int |
363 | | match_key_hop(const char *tag, const struct sshkey *key, |
364 | | const struct dest_constraint_hop *dch) |
365 | 0 | { |
366 | 0 | const char *reason = NULL; |
367 | 0 | const char *hostname = dch->hostname ? dch->hostname : "(ORIGIN)"; |
368 | 0 | u_int i; |
369 | 0 | char *fp; |
370 | |
|
371 | 0 | if (key == NULL) |
372 | 0 | return -1; |
373 | | /* XXX logspam */ |
374 | 0 | if ((fp = sshkey_fingerprint(key, SSH_FP_HASH_DEFAULT, |
375 | 0 | SSH_FP_DEFAULT)) == NULL) |
376 | 0 | fatal_f("fingerprint failed"); |
377 | 0 | debug3_f("%s: entering hostname %s, requested key %s %s, %u keys avail", |
378 | 0 | tag, hostname, sshkey_type(key), fp, dch->nkeys); |
379 | 0 | free(fp); |
380 | 0 | for (i = 0; i < dch->nkeys; i++) { |
381 | 0 | if (dch->keys[i] == NULL) |
382 | 0 | return -1; |
383 | | /* XXX logspam */ |
384 | 0 | if ((fp = sshkey_fingerprint(dch->keys[i], SSH_FP_HASH_DEFAULT, |
385 | 0 | SSH_FP_DEFAULT)) == NULL) |
386 | 0 | fatal_f("fingerprint failed"); |
387 | 0 | debug3_f("%s: key %u: %s%s %s", tag, i, |
388 | 0 | dch->key_is_ca[i] ? "CA " : "", |
389 | 0 | sshkey_type(dch->keys[i]), fp); |
390 | 0 | free(fp); |
391 | 0 | if (!sshkey_is_cert(key)) { |
392 | | /* plain key */ |
393 | 0 | if (dch->key_is_ca[i] || |
394 | 0 | !sshkey_equal(key, dch->keys[i])) |
395 | 0 | continue; |
396 | 0 | return 0; |
397 | 0 | } |
398 | | /* certificate */ |
399 | 0 | if (!dch->key_is_ca[i]) |
400 | 0 | continue; |
401 | 0 | if (key->cert == NULL || key->cert->signature_key == NULL) |
402 | 0 | return -1; /* shouldn't happen */ |
403 | 0 | if (!sshkey_equal(key->cert->signature_key, dch->keys[i])) |
404 | 0 | continue; |
405 | 0 | if (sshkey_cert_check_host(key, hostname, 1, |
406 | 0 | SSH_ALLOWED_CA_SIGALGS, &reason) != 0) { |
407 | 0 | debug_f("cert %s / hostname %s rejected: %s", |
408 | 0 | key->cert->key_id, hostname, reason); |
409 | 0 | continue; |
410 | 0 | } |
411 | 0 | return 0; |
412 | 0 | } |
413 | 0 | return -1; |
414 | 0 | } |
415 | | |
416 | | /* Check destination constraints on an identity against the hostkey/user */ |
417 | | static int |
418 | | permitted_by_dest_constraints(const struct sshkey *fromkey, |
419 | | const struct sshkey *tokey, Identity *id, const char *user, |
420 | | const char **hostnamep) |
421 | 0 | { |
422 | 0 | size_t i; |
423 | 0 | struct dest_constraint *d; |
424 | |
|
425 | 0 | if (hostnamep != NULL) |
426 | 0 | *hostnamep = NULL; |
427 | 0 | for (i = 0; i < id->ndest_constraints; i++) { |
428 | 0 | d = id->dest_constraints + i; |
429 | | /* XXX remove logspam */ |
430 | 0 | debug2_f("constraint %zu %s%s%s (%u keys) > %s%s%s (%u keys)", |
431 | 0 | i, d->from.user ? d->from.user : "", |
432 | 0 | d->from.user ? "@" : "", |
433 | 0 | d->from.hostname ? d->from.hostname : "(ORIGIN)", |
434 | 0 | d->from.nkeys, |
435 | 0 | d->to.user ? d->to.user : "", d->to.user ? "@" : "", |
436 | 0 | d->to.hostname ? d->to.hostname : "(ANY)", d->to.nkeys); |
437 | | |
438 | | /* Match 'from' key */ |
439 | 0 | if (fromkey == NULL) { |
440 | | /* We are matching the first hop */ |
441 | 0 | if (d->from.hostname != NULL || d->from.nkeys != 0) |
442 | 0 | continue; |
443 | 0 | } else if (match_key_hop("from", fromkey, &d->from) != 0) |
444 | 0 | continue; |
445 | | |
446 | | /* Match 'to' key */ |
447 | 0 | if (tokey != NULL && match_key_hop("to", tokey, &d->to) != 0) |
448 | 0 | continue; |
449 | | |
450 | | /* Match user if specified */ |
451 | 0 | if (d->to.user != NULL && user != NULL && |
452 | 0 | !match_pattern(user, d->to.user)) |
453 | 0 | continue; |
454 | | |
455 | | /* successfully matched this constraint */ |
456 | 0 | if (hostnamep != NULL) |
457 | 0 | *hostnamep = d->to.hostname; |
458 | 0 | debug2_f("allowed for hostname %s", |
459 | 0 | d->to.hostname == NULL ? "*" : d->to.hostname); |
460 | 0 | return 0; |
461 | 0 | } |
462 | | /* no match */ |
463 | 0 | debug2_f("%s identity \"%s\" not permitted for this destination", |
464 | 0 | sshkey_type(id->key), id->comment); |
465 | 0 | return -1; |
466 | 0 | } |
467 | | |
468 | | /* |
469 | | * Check whether hostkeys on a SocketEntry and the optionally specified user |
470 | | * are permitted by the destination constraints on the Identity. |
471 | | * Returns 0 on success or -1 otherwise. |
472 | | */ |
473 | | static int |
474 | | identity_permitted(Identity *id, SocketEntry *e, char *user, |
475 | | const char **forward_hostnamep, const char **last_hostnamep) |
476 | 0 | { |
477 | 0 | size_t i; |
478 | 0 | const char **hp; |
479 | 0 | struct hostkey_sid *hks; |
480 | 0 | const struct sshkey *fromkey = NULL; |
481 | 0 | const char *test_user; |
482 | 0 | char *fp1, *fp2; |
483 | | |
484 | | /* XXX remove logspam */ |
485 | 0 | debug3_f("entering: key %s comment \"%s\", %zu socket bindings, " |
486 | 0 | "%zu constraints", sshkey_type(id->key), id->comment, |
487 | 0 | e->nsession_ids, id->ndest_constraints); |
488 | 0 | if (id->ndest_constraints == 0) |
489 | 0 | return 0; /* unconstrained */ |
490 | 0 | if (e->session_bind_attempted && e->nsession_ids == 0) { |
491 | 0 | error_f("previous session bind failed on socket"); |
492 | 0 | return -1; |
493 | 0 | } |
494 | 0 | if (e->nsession_ids == 0) |
495 | 0 | return 0; /* local use */ |
496 | | /* |
497 | | * Walk through the hops recorded by session_id and try to find a |
498 | | * constraint that satisfies each. |
499 | | */ |
500 | 0 | for (i = 0; i < e->nsession_ids; i++) { |
501 | 0 | hks = e->session_ids + i; |
502 | 0 | if (hks->key == NULL) |
503 | 0 | fatal_f("internal error: no bound key"); |
504 | | /* XXX remove logspam */ |
505 | 0 | fp1 = fp2 = NULL; |
506 | 0 | if (fromkey != NULL && |
507 | 0 | (fp1 = sshkey_fingerprint(fromkey, SSH_FP_HASH_DEFAULT, |
508 | 0 | SSH_FP_DEFAULT)) == NULL) |
509 | 0 | fatal_f("fingerprint failed"); |
510 | 0 | if ((fp2 = sshkey_fingerprint(hks->key, SSH_FP_HASH_DEFAULT, |
511 | 0 | SSH_FP_DEFAULT)) == NULL) |
512 | 0 | fatal_f("fingerprint failed"); |
513 | 0 | debug3_f("socketentry fd=%d, entry %zu %s, " |
514 | 0 | "from hostkey %s %s to user %s hostkey %s %s", |
515 | 0 | e->fd, i, hks->forwarded ? "FORWARD" : "AUTH", |
516 | 0 | fromkey ? sshkey_type(fromkey) : "(ORIGIN)", |
517 | 0 | fromkey ? fp1 : "", user ? user : "(ANY)", |
518 | 0 | sshkey_type(hks->key), fp2); |
519 | 0 | free(fp1); |
520 | 0 | free(fp2); |
521 | | /* |
522 | | * Record the hostnames for the initial forwarding and |
523 | | * the final destination. |
524 | | */ |
525 | 0 | hp = NULL; |
526 | 0 | if (i == e->nsession_ids - 1) |
527 | 0 | hp = last_hostnamep; |
528 | 0 | else if (i == 0) |
529 | 0 | hp = forward_hostnamep; |
530 | | /* Special handling for final recorded binding */ |
531 | 0 | test_user = NULL; |
532 | 0 | if (i == e->nsession_ids - 1) { |
533 | | /* Can only check user at final hop */ |
534 | 0 | test_user = user; |
535 | | /* |
536 | | * user is only presented for signature requests. |
537 | | * If this is the case, make sure last binding is not |
538 | | * for a forwarding. |
539 | | */ |
540 | 0 | if (hks->forwarded && user != NULL) { |
541 | 0 | error_f("tried to sign on forwarding hop"); |
542 | 0 | return -1; |
543 | 0 | } |
544 | 0 | } else if (!hks->forwarded) { |
545 | 0 | error_f("tried to forward though signing bind"); |
546 | 0 | return -1; |
547 | 0 | } |
548 | 0 | if (permitted_by_dest_constraints(fromkey, hks->key, id, |
549 | 0 | test_user, hp) != 0) |
550 | 0 | return -1; |
551 | 0 | fromkey = hks->key; |
552 | 0 | } |
553 | | /* |
554 | | * Another special case: if the last bound session ID was for a |
555 | | * forwarding, and this function is not being called to check a sign |
556 | | * request (i.e. no 'user' supplied), then only permit the key if |
557 | | * there is a permission that would allow it to be used at another |
558 | | * destination. This hides keys that are allowed to be used to |
559 | | * authenticate *to* a host but not permitted for *use* beyond it. |
560 | | */ |
561 | 0 | hks = &e->session_ids[e->nsession_ids - 1]; |
562 | 0 | if (hks->forwarded && user == NULL && |
563 | 0 | permitted_by_dest_constraints(hks->key, NULL, id, |
564 | 0 | NULL, NULL) != 0) { |
565 | 0 | debug3_f("key permitted at host but not after"); |
566 | 0 | return -1; |
567 | 0 | } |
568 | | |
569 | | /* success */ |
570 | 0 | return 0; |
571 | 0 | } |
572 | | |
573 | | static int |
574 | | socket_is_remote(SocketEntry *e) |
575 | 0 | { |
576 | 0 | return e->session_bind_attempted || (e->nsession_ids != 0); |
577 | 0 | } |
578 | | |
579 | | /* return matching private key for given public key */ |
580 | | static Identity * |
581 | | lookup_identity(struct sshkey *key) |
582 | 0 | { |
583 | 0 | Identity *id; |
584 | |
|
585 | 0 | TAILQ_FOREACH(id, &idtab->idlist, next) { |
586 | 0 | if (sshkey_equal(key, id->key)) |
587 | 0 | return (id); |
588 | 0 | } |
589 | 0 | return (NULL); |
590 | 0 | } |
591 | | |
592 | | /* Check confirmation of keysign request */ |
593 | | static int |
594 | | confirm_key(Identity *id, const char *extra) |
595 | 0 | { |
596 | 0 | char *p; |
597 | 0 | int ret = -1; |
598 | |
|
599 | 0 | p = sshkey_fingerprint(id->key, fingerprint_hash, SSH_FP_DEFAULT); |
600 | 0 | if (p != NULL && |
601 | 0 | ask_permission("Allow use of key %s?\nKey fingerprint %s.%s%s", |
602 | 0 | id->comment, p, |
603 | 0 | extra == NULL ? "" : "\n", extra == NULL ? "" : extra)) |
604 | 0 | ret = 0; |
605 | 0 | free(p); |
606 | |
|
607 | 0 | return (ret); |
608 | 0 | } |
609 | | |
610 | | static void |
611 | | send_status(SocketEntry *e, int success) |
612 | 0 | { |
613 | 0 | int r; |
614 | |
|
615 | 0 | if ((r = sshbuf_put_u32(e->output, 1)) != 0 || |
616 | 0 | (r = sshbuf_put_u8(e->output, success ? |
617 | 0 | SSH_AGENT_SUCCESS : SSH_AGENT_FAILURE)) != 0) |
618 | 0 | fatal_fr(r, "compose"); |
619 | 0 | } |
620 | | |
621 | | /* send list of supported public keys to 'client' */ |
622 | | static void |
623 | | process_request_identities(SocketEntry *e) |
624 | 0 | { |
625 | 0 | Identity *id; |
626 | 0 | struct sshbuf *msg, *keys; |
627 | 0 | int r; |
628 | 0 | u_int i = 0, nentries = 0; |
629 | 0 | char *fp; |
630 | |
|
631 | 0 | debug2_f("entering"); |
632 | |
|
633 | 0 | if ((msg = sshbuf_new()) == NULL || (keys = sshbuf_new()) == NULL) |
634 | 0 | fatal_f("sshbuf_new failed"); |
635 | 0 | TAILQ_FOREACH(id, &idtab->idlist, next) { |
636 | 0 | if ((fp = sshkey_fingerprint(id->key, SSH_FP_HASH_DEFAULT, |
637 | 0 | SSH_FP_DEFAULT)) == NULL) |
638 | 0 | fatal_f("fingerprint failed"); |
639 | 0 | debug_f("key %u / %u: %s %s", i++, idtab->nentries, |
640 | 0 | sshkey_ssh_name(id->key), fp); |
641 | 0 | dump_dest_constraints(__func__, |
642 | 0 | id->dest_constraints, id->ndest_constraints); |
643 | 0 | free(fp); |
644 | | /* identity not visible, don't include in response */ |
645 | 0 | if (identity_permitted(id, e, NULL, NULL, NULL) != 0) |
646 | 0 | continue; |
647 | 0 | if ((r = sshkey_puts_opts(id->key, keys, |
648 | 0 | SSHKEY_SERIALIZE_INFO)) != 0 || |
649 | 0 | (r = sshbuf_put_cstring(keys, id->comment)) != 0) { |
650 | 0 | error_fr(r, "compose key/comment"); |
651 | 0 | continue; |
652 | 0 | } |
653 | 0 | nentries++; |
654 | 0 | } |
655 | 0 | debug2_f("replying with %u allowed of %u available keys", |
656 | 0 | nentries, idtab->nentries); |
657 | 0 | if ((r = sshbuf_put_u8(msg, SSH2_AGENT_IDENTITIES_ANSWER)) != 0 || |
658 | 0 | (r = sshbuf_put_u32(msg, nentries)) != 0 || |
659 | 0 | (r = sshbuf_putb(msg, keys)) != 0) |
660 | 0 | fatal_fr(r, "compose"); |
661 | 0 | if ((r = sshbuf_put_stringb(e->output, msg)) != 0) |
662 | 0 | fatal_fr(r, "enqueue"); |
663 | 0 | sshbuf_free(msg); |
664 | 0 | sshbuf_free(keys); |
665 | 0 | } |
666 | | |
667 | | |
668 | | static char * |
669 | | agent_decode_alg(struct sshkey *key, u_int flags) |
670 | 0 | { |
671 | 0 | if (key->type == KEY_RSA) { |
672 | 0 | if (flags & SSH_AGENT_RSA_SHA2_256) |
673 | 0 | return "rsa-sha2-256"; |
674 | 0 | else if (flags & SSH_AGENT_RSA_SHA2_512) |
675 | 0 | return "rsa-sha2-512"; |
676 | 0 | } else if (key->type == KEY_RSA_CERT) { |
677 | 0 | if (flags & SSH_AGENT_RSA_SHA2_256) |
678 | 0 | return "rsa-sha2-256-cert-v01@openssh.com"; |
679 | 0 | else if (flags & SSH_AGENT_RSA_SHA2_512) |
680 | 0 | return "rsa-sha2-512-cert-v01@openssh.com"; |
681 | 0 | } |
682 | 0 | return NULL; |
683 | 0 | } |
684 | | |
685 | | /* |
686 | | * Attempt to parse the contents of a buffer as a SSH publickey userauth |
687 | | * request, checking its contents for consistency and matching the embedded |
688 | | * key against the one that is being used for signing. |
689 | | * Note: does not modify msg buffer. |
690 | | * Optionally extract the username, session ID and/or hostkey from the request. |
691 | | */ |
692 | | static int |
693 | | parse_userauth_request(struct sshbuf *msg, const struct sshkey *expected_key, |
694 | | char **userp, struct sshbuf **sess_idp, struct sshkey **hostkeyp) |
695 | 0 | { |
696 | 0 | struct sshbuf *b = NULL, *sess_id = NULL; |
697 | 0 | char *user = NULL, *service = NULL, *method = NULL, *pkalg = NULL; |
698 | 0 | int r; |
699 | 0 | u_char t, sig_follows; |
700 | 0 | struct sshkey *mkey = NULL, *hostkey = NULL; |
701 | |
|
702 | 0 | if (userp != NULL) |
703 | 0 | *userp = NULL; |
704 | 0 | if (sess_idp != NULL) |
705 | 0 | *sess_idp = NULL; |
706 | 0 | if (hostkeyp != NULL) |
707 | 0 | *hostkeyp = NULL; |
708 | 0 | if ((b = sshbuf_fromb(msg)) == NULL) |
709 | 0 | fatal_f("sshbuf_fromb"); |
710 | | |
711 | | /* SSH userauth request */ |
712 | 0 | if ((r = sshbuf_froms(b, &sess_id)) != 0) |
713 | 0 | goto out; |
714 | 0 | if (sshbuf_len(sess_id) == 0) { |
715 | 0 | r = SSH_ERR_INVALID_FORMAT; |
716 | 0 | goto out; |
717 | 0 | } |
718 | 0 | if ((r = sshbuf_get_u8(b, &t)) != 0 || /* SSH2_MSG_USERAUTH_REQUEST */ |
719 | 0 | (r = sshbuf_get_cstring(b, &user, NULL)) != 0 || /* server user */ |
720 | 0 | (r = sshbuf_get_cstring(b, &service, NULL)) != 0 || /* service */ |
721 | 0 | (r = sshbuf_get_cstring(b, &method, NULL)) != 0 || /* method */ |
722 | 0 | (r = sshbuf_get_u8(b, &sig_follows)) != 0 || /* sig-follows */ |
723 | 0 | (r = sshbuf_get_cstring(b, &pkalg, NULL)) != 0 || /* alg */ |
724 | 0 | (r = sshkey_froms(b, &mkey)) != 0) /* key */ |
725 | 0 | goto out; |
726 | 0 | if (t != SSH2_MSG_USERAUTH_REQUEST || |
727 | 0 | sig_follows != 1 || |
728 | 0 | strcmp(service, "ssh-connection") != 0 || |
729 | 0 | !sshkey_equal(expected_key, mkey) || |
730 | 0 | sshkey_type_from_name(pkalg) != expected_key->type) { |
731 | 0 | r = SSH_ERR_INVALID_FORMAT; |
732 | 0 | goto out; |
733 | 0 | } |
734 | 0 | if (strcmp(method, "publickey-hostbound-v00@openssh.com") == 0) { |
735 | 0 | if ((r = sshkey_froms(b, &hostkey)) != 0) |
736 | 0 | goto out; |
737 | 0 | } else if (strcmp(method, "publickey") != 0) { |
738 | 0 | r = SSH_ERR_INVALID_FORMAT; |
739 | 0 | goto out; |
740 | 0 | } |
741 | 0 | if (sshbuf_len(b) != 0) { |
742 | 0 | r = SSH_ERR_INVALID_FORMAT; |
743 | 0 | goto out; |
744 | 0 | } |
745 | | /* success */ |
746 | 0 | r = 0; |
747 | 0 | debug3_f("well formed userauth"); |
748 | 0 | if (userp != NULL) { |
749 | 0 | *userp = user; |
750 | 0 | user = NULL; |
751 | 0 | } |
752 | 0 | if (sess_idp != NULL) { |
753 | 0 | *sess_idp = sess_id; |
754 | 0 | sess_id = NULL; |
755 | 0 | } |
756 | 0 | if (hostkeyp != NULL) { |
757 | 0 | *hostkeyp = hostkey; |
758 | 0 | hostkey = NULL; |
759 | 0 | } |
760 | 0 | out: |
761 | 0 | sshbuf_free(b); |
762 | 0 | sshbuf_free(sess_id); |
763 | 0 | free(user); |
764 | 0 | free(service); |
765 | 0 | free(method); |
766 | 0 | free(pkalg); |
767 | 0 | sshkey_free(mkey); |
768 | 0 | sshkey_free(hostkey); |
769 | 0 | return r; |
770 | 0 | } |
771 | | |
772 | | /* |
773 | | * Attempt to parse the contents of a buffer as a SSHSIG signature request. |
774 | | * Note: does not modify buffer. |
775 | | */ |
776 | | static int |
777 | | parse_sshsig_request(struct sshbuf *msg) |
778 | 0 | { |
779 | 0 | int r; |
780 | 0 | struct sshbuf *b; |
781 | |
|
782 | 0 | if ((b = sshbuf_fromb(msg)) == NULL) |
783 | 0 | fatal_f("sshbuf_fromb"); |
784 | | |
785 | 0 | if ((r = sshbuf_cmp(b, 0, "SSHSIG", 6)) != 0 || |
786 | 0 | (r = sshbuf_consume(b, 6)) != 0 || |
787 | 0 | (r = sshbuf_get_cstring(b, NULL, NULL)) != 0 || /* namespace */ |
788 | 0 | (r = sshbuf_get_string_direct(b, NULL, NULL)) != 0 || /* reserved */ |
789 | 0 | (r = sshbuf_get_cstring(b, NULL, NULL)) != 0 || /* hashalg */ |
790 | 0 | (r = sshbuf_get_string_direct(b, NULL, NULL)) != 0) /* H(msg) */ |
791 | 0 | goto out; |
792 | 0 | if (sshbuf_len(b) != 0) { |
793 | 0 | r = SSH_ERR_INVALID_FORMAT; |
794 | 0 | goto out; |
795 | 0 | } |
796 | | /* success */ |
797 | 0 | r = 0; |
798 | 0 | out: |
799 | 0 | sshbuf_free(b); |
800 | 0 | return r; |
801 | 0 | } |
802 | | |
803 | | /* |
804 | | * This function inspects a message to be signed by a FIDO key that has a |
805 | | * web-like application string (i.e. one that does not begin with "ssh:". |
806 | | * It checks that the message is one of those expected for SSH operations |
807 | | * (pubkey userauth, sshsig, CA key signing) to exclude signing challenges |
808 | | * for the web. |
809 | | */ |
810 | | static int |
811 | | check_websafe_message_contents(struct sshkey *key, struct sshbuf *data) |
812 | 0 | { |
813 | 0 | if (parse_userauth_request(data, key, NULL, NULL, NULL) == 0) { |
814 | 0 | debug_f("signed data matches public key userauth request"); |
815 | 0 | return 1; |
816 | 0 | } |
817 | 0 | if (parse_sshsig_request(data) == 0) { |
818 | 0 | debug_f("signed data matches SSHSIG signature request"); |
819 | 0 | return 1; |
820 | 0 | } |
821 | | |
822 | | /* XXX check CA signature operation */ |
823 | | |
824 | 0 | error("web-origin key attempting to sign non-SSH message"); |
825 | 0 | return 0; |
826 | 0 | } |
827 | | |
828 | | static int |
829 | | buf_equal(const struct sshbuf *a, const struct sshbuf *b) |
830 | 0 | { |
831 | 0 | if (sshbuf_ptr(a) == NULL || sshbuf_ptr(b) == NULL) |
832 | 0 | return SSH_ERR_INVALID_ARGUMENT; |
833 | 0 | if (sshbuf_len(a) != sshbuf_len(b)) |
834 | 0 | return SSH_ERR_INVALID_FORMAT; |
835 | 0 | if (timingsafe_bcmp(sshbuf_ptr(a), sshbuf_ptr(b), sshbuf_len(a)) != 0) |
836 | 0 | return SSH_ERR_INVALID_FORMAT; |
837 | 0 | return 0; |
838 | 0 | } |
839 | | |
840 | | /* ssh2 only */ |
841 | | static void |
842 | | process_sign_request2(SocketEntry *e) |
843 | 0 | { |
844 | 0 | u_char *signature = NULL; |
845 | 0 | size_t slen = 0; |
846 | 0 | u_int compat = 0, flags; |
847 | 0 | int r, ok = -1, retried = 0; |
848 | 0 | char *fp = NULL, *pin = NULL, *prompt = NULL; |
849 | 0 | char *user = NULL, *sig_dest = NULL; |
850 | 0 | const char *fwd_host = NULL, *dest_host = NULL; |
851 | 0 | struct sshbuf *msg = NULL, *data = NULL, *sid = NULL; |
852 | 0 | struct sshkey *key = NULL, *hostkey = NULL; |
853 | 0 | struct identity *id; |
854 | 0 | struct notifier_ctx *notifier = NULL; |
855 | |
|
856 | 0 | debug_f("entering"); |
857 | |
|
858 | 0 | if ((msg = sshbuf_new()) == NULL || (data = sshbuf_new()) == NULL) |
859 | 0 | fatal_f("sshbuf_new failed"); |
860 | 0 | if ((r = sshkey_froms(e->request, &key)) != 0 || |
861 | 0 | (r = sshbuf_get_stringb(e->request, data)) != 0 || |
862 | 0 | (r = sshbuf_get_u32(e->request, &flags)) != 0) { |
863 | 0 | error_fr(r, "parse"); |
864 | 0 | goto send; |
865 | 0 | } |
866 | | |
867 | 0 | if ((id = lookup_identity(key)) == NULL) { |
868 | 0 | verbose_f("%s key not found", sshkey_type(key)); |
869 | 0 | goto send; |
870 | 0 | } |
871 | 0 | if ((fp = sshkey_fingerprint(key, SSH_FP_HASH_DEFAULT, |
872 | 0 | SSH_FP_DEFAULT)) == NULL) |
873 | 0 | fatal_f("fingerprint failed"); |
874 | | |
875 | 0 | if (id->ndest_constraints != 0) { |
876 | 0 | if (e->nsession_ids == 0) { |
877 | 0 | logit_f("refusing use of destination-constrained key " |
878 | 0 | "to sign on unbound connection"); |
879 | 0 | goto send; |
880 | 0 | } |
881 | 0 | if (parse_userauth_request(data, key, &user, &sid, |
882 | 0 | &hostkey) != 0) { |
883 | 0 | logit_f("refusing use of destination-constrained key " |
884 | 0 | "to sign an unidentified signature"); |
885 | 0 | goto send; |
886 | 0 | } |
887 | | /* XXX logspam */ |
888 | 0 | debug_f("user=%s", user); |
889 | 0 | if (identity_permitted(id, e, user, &fwd_host, &dest_host) != 0) |
890 | 0 | goto send; |
891 | | /* XXX display fwd_host/dest_host in askpass UI */ |
892 | | /* |
893 | | * Ensure that the session ID is the most recent one |
894 | | * registered on the socket - it should have been bound by |
895 | | * ssh immediately before userauth. |
896 | | */ |
897 | 0 | if (buf_equal(sid, |
898 | 0 | e->session_ids[e->nsession_ids - 1].sid) != 0) { |
899 | 0 | error_f("unexpected session ID (%zu listed) on " |
900 | 0 | "signature request for target user %s with " |
901 | 0 | "key %s %s", e->nsession_ids, user, |
902 | 0 | sshkey_type(id->key), fp); |
903 | 0 | goto send; |
904 | 0 | } |
905 | | /* |
906 | | * Ensure that the hostkey embedded in the signature matches |
907 | | * the one most recently bound to the socket. An exception is |
908 | | * made for the initial forwarding hop. |
909 | | */ |
910 | 0 | if (e->nsession_ids > 1 && hostkey == NULL) { |
911 | 0 | error_f("refusing use of destination-constrained key: " |
912 | 0 | "no hostkey recorded in signature for forwarded " |
913 | 0 | "connection"); |
914 | 0 | goto send; |
915 | 0 | } |
916 | 0 | if (hostkey != NULL && !sshkey_equal(hostkey, |
917 | 0 | e->session_ids[e->nsession_ids - 1].key)) { |
918 | 0 | error_f("refusing use of destination-constrained key: " |
919 | 0 | "mismatch between hostkey in request and most " |
920 | 0 | "recently bound session"); |
921 | 0 | goto send; |
922 | 0 | } |
923 | 0 | xasprintf(&sig_dest, "public key authentication request for " |
924 | 0 | "user \"%s\" to listed host", user); |
925 | 0 | } |
926 | 0 | if (id->confirm && confirm_key(id, sig_dest) != 0) { |
927 | 0 | verbose_f("user refused key"); |
928 | 0 | goto send; |
929 | 0 | } |
930 | 0 | if (sshkey_is_sk(id->key)) { |
931 | 0 | if (restrict_websafe && |
932 | 0 | match_pattern_list(id->key->sk_application, |
933 | 0 | websafe_allowlist, 0) != 1 && |
934 | 0 | !check_websafe_message_contents(key, data)) { |
935 | | /* error already logged */ |
936 | 0 | goto send; |
937 | 0 | } |
938 | 0 | if (id->key->sk_flags & SSH_SK_USER_PRESENCE_REQD) { |
939 | 0 | notifier = notify_start(0, |
940 | 0 | "Confirm user presence for key %s %s%s%s", |
941 | 0 | sshkey_type(id->key), fp, |
942 | 0 | sig_dest == NULL ? "" : "\n", |
943 | 0 | sig_dest == NULL ? "" : sig_dest); |
944 | 0 | } |
945 | 0 | } |
946 | 0 | retry_pin: |
947 | 0 | if ((r = sshkey_sign(id->key, &signature, &slen, |
948 | 0 | sshbuf_ptr(data), sshbuf_len(data), agent_decode_alg(key, flags), |
949 | 0 | id->sk_provider, pin, compat)) != 0) { |
950 | 0 | debug_fr(r, "sshkey_sign"); |
951 | 0 | if (pin == NULL && !retried && sshkey_is_sk(id->key) && |
952 | 0 | r == SSH_ERR_KEY_WRONG_PASSPHRASE) { |
953 | 0 | notify_complete(notifier, NULL); |
954 | 0 | notifier = NULL; |
955 | | /* XXX include sig_dest */ |
956 | 0 | xasprintf(&prompt, "Enter PIN%sfor %s key %s: ", |
957 | 0 | (id->key->sk_flags & SSH_SK_USER_PRESENCE_REQD) ? |
958 | 0 | " and confirm user presence " : " ", |
959 | 0 | sshkey_type(id->key), fp); |
960 | 0 | pin = read_passphrase(prompt, RP_USE_ASKPASS); |
961 | 0 | retried = 1; |
962 | 0 | goto retry_pin; |
963 | 0 | } |
964 | 0 | error_fr(r, "sshkey_sign"); |
965 | 0 | goto send; |
966 | 0 | } |
967 | | /* Success */ |
968 | 0 | ok = 0; |
969 | 0 | debug_f("good signature"); |
970 | 0 | send: |
971 | 0 | notify_complete(notifier, "User presence confirmed"); |
972 | |
|
973 | 0 | if (ok == 0) { |
974 | 0 | if ((r = sshbuf_put_u8(msg, SSH2_AGENT_SIGN_RESPONSE)) != 0 || |
975 | 0 | (r = sshbuf_put_string(msg, signature, slen)) != 0) |
976 | 0 | fatal_fr(r, "compose"); |
977 | 0 | } else if ((r = sshbuf_put_u8(msg, SSH_AGENT_FAILURE)) != 0) |
978 | 0 | fatal_fr(r, "compose failure"); |
979 | | |
980 | 0 | if ((r = sshbuf_put_stringb(e->output, msg)) != 0) |
981 | 0 | fatal_fr(r, "enqueue"); |
982 | | |
983 | 0 | sshbuf_free(sid); |
984 | 0 | sshbuf_free(data); |
985 | 0 | sshbuf_free(msg); |
986 | 0 | sshkey_free(key); |
987 | 0 | sshkey_free(hostkey); |
988 | 0 | free(fp); |
989 | 0 | free(signature); |
990 | 0 | free(sig_dest); |
991 | 0 | free(user); |
992 | 0 | free(prompt); |
993 | 0 | if (pin != NULL) |
994 | 0 | freezero(pin, strlen(pin)); |
995 | 0 | } |
996 | | |
997 | | /* shared */ |
998 | | static void |
999 | | process_remove_identity(SocketEntry *e) |
1000 | 0 | { |
1001 | 0 | int r, success = 0; |
1002 | 0 | struct sshkey *key = NULL; |
1003 | 0 | Identity *id; |
1004 | |
|
1005 | 0 | debug2_f("entering"); |
1006 | 0 | if ((r = sshkey_froms(e->request, &key)) != 0) { |
1007 | 0 | error_fr(r, "parse key"); |
1008 | 0 | goto done; |
1009 | 0 | } |
1010 | 0 | if ((id = lookup_identity(key)) == NULL) { |
1011 | 0 | debug_f("key not found"); |
1012 | 0 | goto done; |
1013 | 0 | } |
1014 | | /* identity not visible, cannot be removed */ |
1015 | 0 | if (identity_permitted(id, e, NULL, NULL, NULL) != 0) |
1016 | 0 | goto done; /* error already logged */ |
1017 | | /* We have this key, free it. */ |
1018 | 0 | if (idtab->nentries < 1) |
1019 | 0 | fatal_f("internal error: nentries %d", idtab->nentries); |
1020 | 0 | TAILQ_REMOVE(&idtab->idlist, id, next); |
1021 | 0 | free_identity(id); |
1022 | 0 | idtab->nentries--; |
1023 | 0 | success = 1; |
1024 | 0 | done: |
1025 | 0 | sshkey_free(key); |
1026 | 0 | send_status(e, success); |
1027 | 0 | } |
1028 | | |
1029 | | static void |
1030 | | remove_all_identities(void) |
1031 | 0 | { |
1032 | 0 | Identity *id; |
1033 | |
|
1034 | 0 | debug2_f("entering"); |
1035 | | /* Loop over all identities and clear the keys. */ |
1036 | 0 | for (id = TAILQ_FIRST(&idtab->idlist); id; |
1037 | 0 | id = TAILQ_FIRST(&idtab->idlist)) { |
1038 | 0 | TAILQ_REMOVE(&idtab->idlist, id, next); |
1039 | 0 | free_identity(id); |
1040 | 0 | } |
1041 | | |
1042 | | /* Mark that there are no identities. */ |
1043 | 0 | idtab->nentries = 0; |
1044 | 0 | } |
1045 | | |
1046 | | static void |
1047 | | process_remove_all_identities(SocketEntry *e) |
1048 | 0 | { |
1049 | 0 | remove_all_identities(); |
1050 | | |
1051 | | /* Send success. */ |
1052 | 0 | send_status(e, 1); |
1053 | 0 | } |
1054 | | |
1055 | | /* removes expired keys and returns number of seconds until the next expiry */ |
1056 | | static time_t |
1057 | | reaper(void) |
1058 | 0 | { |
1059 | 0 | time_t deadline = 0, now = monotime(); |
1060 | 0 | Identity *id, *nxt; |
1061 | |
|
1062 | 0 | for (id = TAILQ_FIRST(&idtab->idlist); id; id = nxt) { |
1063 | 0 | nxt = TAILQ_NEXT(id, next); |
1064 | 0 | if (id->death == 0) |
1065 | 0 | continue; |
1066 | 0 | if (now >= id->death) { |
1067 | 0 | debug("expiring key '%s'", id->comment); |
1068 | 0 | TAILQ_REMOVE(&idtab->idlist, id, next); |
1069 | 0 | free_identity(id); |
1070 | 0 | idtab->nentries--; |
1071 | 0 | } else |
1072 | 0 | deadline = (deadline == 0) ? id->death : |
1073 | 0 | MINIMUM(deadline, id->death); |
1074 | 0 | } |
1075 | 0 | if (deadline == 0 || deadline <= now) |
1076 | 0 | return 0; |
1077 | 0 | else |
1078 | 0 | return (deadline - now); |
1079 | 0 | } |
1080 | | |
1081 | | static int |
1082 | | parse_dest_constraint_hop(struct sshbuf *b, struct dest_constraint_hop *dch) |
1083 | 0 | { |
1084 | 0 | u_char key_is_ca; |
1085 | 0 | size_t elen = 0; |
1086 | 0 | int r; |
1087 | 0 | struct sshkey *k = NULL; |
1088 | 0 | char *fp; |
1089 | |
|
1090 | 0 | memset(dch, '\0', sizeof(*dch)); |
1091 | 0 | if ((r = sshbuf_get_cstring(b, &dch->user, NULL)) != 0 || |
1092 | 0 | (r = sshbuf_get_cstring(b, &dch->hostname, NULL)) != 0 || |
1093 | 0 | (r = sshbuf_get_string_direct(b, NULL, &elen)) != 0) { |
1094 | 0 | error_fr(r, "parse"); |
1095 | 0 | goto out; |
1096 | 0 | } |
1097 | 0 | if (elen != 0) { |
1098 | 0 | error_f("unsupported extensions (len %zu)", elen); |
1099 | 0 | r = SSH_ERR_FEATURE_UNSUPPORTED; |
1100 | 0 | goto out; |
1101 | 0 | } |
1102 | 0 | if (*dch->hostname == '\0') { |
1103 | 0 | free(dch->hostname); |
1104 | 0 | dch->hostname = NULL; |
1105 | 0 | } |
1106 | 0 | if (*dch->user == '\0') { |
1107 | 0 | free(dch->user); |
1108 | 0 | dch->user = NULL; |
1109 | 0 | } |
1110 | 0 | while (sshbuf_len(b) != 0) { |
1111 | 0 | dch->keys = xrecallocarray(dch->keys, dch->nkeys, |
1112 | 0 | dch->nkeys + 1, sizeof(*dch->keys)); |
1113 | 0 | dch->key_is_ca = xrecallocarray(dch->key_is_ca, dch->nkeys, |
1114 | 0 | dch->nkeys + 1, sizeof(*dch->key_is_ca)); |
1115 | 0 | if ((r = sshkey_froms(b, &k)) != 0 || |
1116 | 0 | (r = sshbuf_get_u8(b, &key_is_ca)) != 0) |
1117 | 0 | goto out; |
1118 | 0 | if ((fp = sshkey_fingerprint(k, SSH_FP_HASH_DEFAULT, |
1119 | 0 | SSH_FP_DEFAULT)) == NULL) |
1120 | 0 | fatal_f("fingerprint failed"); |
1121 | 0 | debug3_f("%s%s%s: adding %skey %s %s", |
1122 | 0 | dch->user == NULL ? "" : dch->user, |
1123 | 0 | dch->user == NULL ? "" : "@", |
1124 | 0 | dch->hostname, key_is_ca ? "CA " : "", sshkey_type(k), fp); |
1125 | 0 | free(fp); |
1126 | 0 | dch->keys[dch->nkeys] = k; |
1127 | 0 | dch->key_is_ca[dch->nkeys] = key_is_ca != 0; |
1128 | 0 | dch->nkeys++; |
1129 | 0 | k = NULL; /* transferred */ |
1130 | 0 | } |
1131 | | /* success */ |
1132 | 0 | r = 0; |
1133 | 0 | out: |
1134 | 0 | sshkey_free(k); |
1135 | 0 | return r; |
1136 | 0 | } |
1137 | | |
1138 | | static int |
1139 | | parse_dest_constraint(struct sshbuf *m, struct dest_constraint *dc) |
1140 | 0 | { |
1141 | 0 | struct sshbuf *b = NULL, *frombuf = NULL, *tobuf = NULL; |
1142 | 0 | int r; |
1143 | 0 | size_t elen = 0; |
1144 | |
|
1145 | 0 | debug3_f("entering"); |
1146 | |
|
1147 | 0 | memset(dc, '\0', sizeof(*dc)); |
1148 | 0 | if ((r = sshbuf_froms(m, &b)) != 0 || |
1149 | 0 | (r = sshbuf_froms(b, &frombuf)) != 0 || |
1150 | 0 | (r = sshbuf_froms(b, &tobuf)) != 0 || |
1151 | 0 | (r = sshbuf_get_string_direct(b, NULL, &elen)) != 0) { |
1152 | 0 | error_fr(r, "parse"); |
1153 | 0 | goto out; |
1154 | 0 | } |
1155 | 0 | if ((r = parse_dest_constraint_hop(frombuf, &dc->from)) != 0 || |
1156 | 0 | (r = parse_dest_constraint_hop(tobuf, &dc->to)) != 0) |
1157 | 0 | goto out; /* already logged */ |
1158 | 0 | if (elen != 0) { |
1159 | 0 | error_f("unsupported extensions (len %zu)", elen); |
1160 | 0 | r = SSH_ERR_FEATURE_UNSUPPORTED; |
1161 | 0 | goto out; |
1162 | 0 | } |
1163 | 0 | debug2_f("parsed %s (%u keys) > %s%s%s (%u keys)", |
1164 | 0 | dc->from.hostname ? dc->from.hostname : "(ORIGIN)", dc->from.nkeys, |
1165 | 0 | dc->to.user ? dc->to.user : "", dc->to.user ? "@" : "", |
1166 | 0 | dc->to.hostname ? dc->to.hostname : "(ANY)", dc->to.nkeys); |
1167 | | /* check consistency */ |
1168 | 0 | if ((dc->from.hostname == NULL) != (dc->from.nkeys == 0) || |
1169 | 0 | dc->from.user != NULL) { |
1170 | 0 | error_f("inconsistent \"from\" specification"); |
1171 | 0 | r = SSH_ERR_INVALID_FORMAT; |
1172 | 0 | goto out; |
1173 | 0 | } |
1174 | 0 | if (dc->to.hostname == NULL || dc->to.nkeys == 0) { |
1175 | 0 | error_f("incomplete \"to\" specification"); |
1176 | 0 | r = SSH_ERR_INVALID_FORMAT; |
1177 | 0 | goto out; |
1178 | 0 | } |
1179 | | /* success */ |
1180 | 0 | r = 0; |
1181 | 0 | out: |
1182 | 0 | sshbuf_free(b); |
1183 | 0 | sshbuf_free(frombuf); |
1184 | 0 | sshbuf_free(tobuf); |
1185 | 0 | return r; |
1186 | 0 | } |
1187 | | |
1188 | | static int |
1189 | | parse_key_constraint_extension(struct sshbuf *m, char **sk_providerp, |
1190 | | struct dest_constraint **dcsp, size_t *ndcsp, int *cert_onlyp, |
1191 | | struct sshkey ***certs, size_t *ncerts) |
1192 | 0 | { |
1193 | 0 | char *ext_name = NULL; |
1194 | 0 | int r; |
1195 | 0 | struct sshbuf *b = NULL; |
1196 | 0 | u_char v; |
1197 | 0 | struct sshkey *k; |
1198 | |
|
1199 | 0 | if ((r = sshbuf_get_cstring(m, &ext_name, NULL)) != 0) { |
1200 | 0 | error_fr(r, "parse constraint extension"); |
1201 | 0 | goto out; |
1202 | 0 | } |
1203 | 0 | debug_f("constraint ext %s", ext_name); |
1204 | 0 | if (strcmp(ext_name, "sk-provider@openssh.com") == 0) { |
1205 | 0 | if (sk_providerp == NULL) { |
1206 | 0 | error_f("%s not valid here", ext_name); |
1207 | 0 | r = SSH_ERR_INVALID_FORMAT; |
1208 | 0 | goto out; |
1209 | 0 | } |
1210 | 0 | if (*sk_providerp != NULL) { |
1211 | 0 | error_f("%s already set", ext_name); |
1212 | 0 | r = SSH_ERR_INVALID_FORMAT; |
1213 | 0 | goto out; |
1214 | 0 | } |
1215 | 0 | if ((r = sshbuf_get_cstring(m, sk_providerp, NULL)) != 0) { |
1216 | 0 | error_fr(r, "parse %s", ext_name); |
1217 | 0 | goto out; |
1218 | 0 | } |
1219 | 0 | } else if (strcmp(ext_name, |
1220 | 0 | "restrict-destination-v00@openssh.com") == 0) { |
1221 | 0 | if (*dcsp != NULL) { |
1222 | 0 | error_f("%s already set", ext_name); |
1223 | 0 | r = SSH_ERR_INVALID_FORMAT; |
1224 | 0 | goto out; |
1225 | 0 | } |
1226 | 0 | if ((r = sshbuf_froms(m, &b)) != 0) { |
1227 | 0 | error_fr(r, "parse %s outer", ext_name); |
1228 | 0 | goto out; |
1229 | 0 | } |
1230 | 0 | while (sshbuf_len(b) != 0) { |
1231 | 0 | if (*ndcsp >= AGENT_MAX_DEST_CONSTRAINTS) { |
1232 | 0 | error_f("too many %s constraints", ext_name); |
1233 | 0 | r = SSH_ERR_INVALID_FORMAT; |
1234 | 0 | goto out; |
1235 | 0 | } |
1236 | 0 | *dcsp = xrecallocarray(*dcsp, *ndcsp, *ndcsp + 1, |
1237 | 0 | sizeof(**dcsp)); |
1238 | 0 | if ((r = parse_dest_constraint(b, |
1239 | 0 | *dcsp + (*ndcsp)++)) != 0) |
1240 | 0 | goto out; /* error already logged */ |
1241 | 0 | } |
1242 | 0 | } else if (strcmp(ext_name, |
1243 | 0 | "associated-certs-v00@openssh.com") == 0) { |
1244 | 0 | if (certs == NULL || ncerts == NULL || cert_onlyp == NULL) { |
1245 | 0 | error_f("%s not valid here", ext_name); |
1246 | 0 | r = SSH_ERR_INVALID_FORMAT; |
1247 | 0 | goto out; |
1248 | 0 | } |
1249 | 0 | if (*certs != NULL) { |
1250 | 0 | error_f("%s already set", ext_name); |
1251 | 0 | r = SSH_ERR_INVALID_FORMAT; |
1252 | 0 | goto out; |
1253 | 0 | } |
1254 | 0 | if ((r = sshbuf_get_u8(m, &v)) != 0 || |
1255 | 0 | (r = sshbuf_froms(m, &b)) != 0) { |
1256 | 0 | error_fr(r, "parse %s", ext_name); |
1257 | 0 | goto out; |
1258 | 0 | } |
1259 | 0 | *cert_onlyp = v != 0; |
1260 | 0 | while (sshbuf_len(b) != 0) { |
1261 | 0 | if (*ncerts >= AGENT_MAX_EXT_CERTS) { |
1262 | 0 | error_f("too many %s constraints", ext_name); |
1263 | 0 | r = SSH_ERR_INVALID_FORMAT; |
1264 | 0 | goto out; |
1265 | 0 | } |
1266 | 0 | *certs = xrecallocarray(*certs, *ncerts, *ncerts + 1, |
1267 | 0 | sizeof(**certs)); |
1268 | 0 | if ((r = sshkey_froms(b, &k)) != 0) { |
1269 | 0 | error_fr(r, "parse key"); |
1270 | 0 | goto out; |
1271 | 0 | } |
1272 | 0 | (*certs)[(*ncerts)++] = k; |
1273 | 0 | } |
1274 | 0 | } else { |
1275 | 0 | error_f("unsupported constraint \"%s\"", ext_name); |
1276 | 0 | r = SSH_ERR_FEATURE_UNSUPPORTED; |
1277 | 0 | goto out; |
1278 | 0 | } |
1279 | | /* success */ |
1280 | 0 | r = 0; |
1281 | 0 | out: |
1282 | 0 | free(ext_name); |
1283 | 0 | sshbuf_free(b); |
1284 | 0 | return r; |
1285 | 0 | } |
1286 | | |
1287 | | static int |
1288 | | parse_key_constraints(struct sshbuf *m, struct sshkey *k, time_t *deathp, |
1289 | | u_int *secondsp, int *confirmp, char **sk_providerp, |
1290 | | struct dest_constraint **dcsp, size_t *ndcsp, |
1291 | | int *cert_onlyp, size_t *ncerts, struct sshkey ***certs) |
1292 | 0 | { |
1293 | 0 | u_char ctype; |
1294 | 0 | int r; |
1295 | 0 | u_int seconds, maxsign = 0; |
1296 | |
|
1297 | 0 | while (sshbuf_len(m)) { |
1298 | 0 | if ((r = sshbuf_get_u8(m, &ctype)) != 0) { |
1299 | 0 | error_fr(r, "parse constraint type"); |
1300 | 0 | goto out; |
1301 | 0 | } |
1302 | 0 | switch (ctype) { |
1303 | 0 | case SSH_AGENT_CONSTRAIN_LIFETIME: |
1304 | 0 | if (*deathp != 0) { |
1305 | 0 | error_f("lifetime already set"); |
1306 | 0 | r = SSH_ERR_INVALID_FORMAT; |
1307 | 0 | goto out; |
1308 | 0 | } |
1309 | 0 | if ((r = sshbuf_get_u32(m, &seconds)) != 0) { |
1310 | 0 | error_fr(r, "parse lifetime constraint"); |
1311 | 0 | goto out; |
1312 | 0 | } |
1313 | 0 | *deathp = monotime() + seconds; |
1314 | 0 | *secondsp = seconds; |
1315 | 0 | break; |
1316 | 0 | case SSH_AGENT_CONSTRAIN_CONFIRM: |
1317 | 0 | if (*confirmp != 0) { |
1318 | 0 | error_f("confirm already set"); |
1319 | 0 | r = SSH_ERR_INVALID_FORMAT; |
1320 | 0 | goto out; |
1321 | 0 | } |
1322 | 0 | *confirmp = 1; |
1323 | 0 | break; |
1324 | 0 | case SSH_AGENT_CONSTRAIN_MAXSIGN: |
1325 | 0 | if (k == NULL) { |
1326 | 0 | error_f("maxsign not valid here"); |
1327 | 0 | r = SSH_ERR_INVALID_FORMAT; |
1328 | 0 | goto out; |
1329 | 0 | } |
1330 | 0 | if (maxsign != 0) { |
1331 | 0 | error_f("maxsign already set"); |
1332 | 0 | r = SSH_ERR_INVALID_FORMAT; |
1333 | 0 | goto out; |
1334 | 0 | } |
1335 | 0 | if ((r = sshbuf_get_u32(m, &maxsign)) != 0) { |
1336 | 0 | error_fr(r, "parse maxsign constraint"); |
1337 | 0 | goto out; |
1338 | 0 | } |
1339 | 0 | if ((r = sshkey_enable_maxsign(k, maxsign)) != 0) { |
1340 | 0 | error_fr(r, "enable maxsign"); |
1341 | 0 | goto out; |
1342 | 0 | } |
1343 | 0 | break; |
1344 | 0 | case SSH_AGENT_CONSTRAIN_EXTENSION: |
1345 | 0 | if ((r = parse_key_constraint_extension(m, |
1346 | 0 | sk_providerp, dcsp, ndcsp, |
1347 | 0 | cert_onlyp, certs, ncerts)) != 0) |
1348 | 0 | goto out; /* error already logged */ |
1349 | 0 | break; |
1350 | 0 | default: |
1351 | 0 | error_f("Unknown constraint %d", ctype); |
1352 | 0 | r = SSH_ERR_FEATURE_UNSUPPORTED; |
1353 | 0 | goto out; |
1354 | 0 | } |
1355 | 0 | } |
1356 | | /* success */ |
1357 | 0 | r = 0; |
1358 | 0 | out: |
1359 | 0 | return r; |
1360 | 0 | } |
1361 | | |
1362 | | static void |
1363 | | process_add_identity(SocketEntry *e) |
1364 | 0 | { |
1365 | 0 | Identity *id; |
1366 | 0 | int success = 0, confirm = 0; |
1367 | 0 | char *fp, *comment = NULL, *sk_provider = NULL; |
1368 | 0 | char canonical_provider[PATH_MAX]; |
1369 | 0 | time_t death = 0; |
1370 | 0 | u_int seconds = 0; |
1371 | 0 | struct dest_constraint *dest_constraints = NULL; |
1372 | 0 | size_t ndest_constraints = 0; |
1373 | 0 | struct sshkey *k = NULL; |
1374 | 0 | int r = SSH_ERR_INTERNAL_ERROR; |
1375 | |
|
1376 | 0 | debug2_f("entering"); |
1377 | 0 | if ((r = sshkey_private_deserialize(e->request, &k)) != 0 || |
1378 | 0 | k == NULL || |
1379 | 0 | (r = sshbuf_get_cstring(e->request, &comment, NULL)) != 0) { |
1380 | 0 | error_fr(r, "parse"); |
1381 | 0 | goto out; |
1382 | 0 | } |
1383 | 0 | if (parse_key_constraints(e->request, k, &death, &seconds, &confirm, |
1384 | 0 | &sk_provider, &dest_constraints, &ndest_constraints, |
1385 | 0 | NULL, NULL, NULL) != 0) { |
1386 | 0 | error_f("failed to parse constraints"); |
1387 | 0 | sshbuf_reset(e->request); |
1388 | 0 | goto out; |
1389 | 0 | } |
1390 | 0 | dump_dest_constraints(__func__, dest_constraints, ndest_constraints); |
1391 | |
|
1392 | 0 | if (sk_provider != NULL) { |
1393 | 0 | if (!sshkey_is_sk(k)) { |
1394 | 0 | error("Cannot add provider: %s is not an " |
1395 | 0 | "authenticator-hosted key", sshkey_type(k)); |
1396 | 0 | goto out; |
1397 | 0 | } |
1398 | 0 | if (strcasecmp(sk_provider, "internal") == 0) { |
1399 | 0 | debug_f("internal provider"); |
1400 | 0 | } else { |
1401 | 0 | if (socket_is_remote(e) && !remote_add_provider) { |
1402 | 0 | verbose("failed add of SK provider \"%.100s\": " |
1403 | 0 | "remote addition of providers is disabled", |
1404 | 0 | sk_provider); |
1405 | 0 | goto out; |
1406 | 0 | } |
1407 | 0 | if (realpath(sk_provider, canonical_provider) == NULL) { |
1408 | 0 | verbose("failed provider \"%.100s\": " |
1409 | 0 | "realpath: %s", sk_provider, |
1410 | 0 | strerror(errno)); |
1411 | 0 | goto out; |
1412 | 0 | } |
1413 | 0 | free(sk_provider); |
1414 | 0 | sk_provider = xstrdup(canonical_provider); |
1415 | 0 | if (match_pattern_list(sk_provider, |
1416 | 0 | allowed_providers, 0) != 1) { |
1417 | 0 | error("Refusing add key: " |
1418 | 0 | "provider %s not allowed", sk_provider); |
1419 | 0 | goto out; |
1420 | 0 | } |
1421 | 0 | } |
1422 | 0 | } |
1423 | 0 | if ((r = sshkey_shield_private(k)) != 0) { |
1424 | 0 | error_fr(r, "shield private"); |
1425 | 0 | goto out; |
1426 | 0 | } |
1427 | 0 | if (lifetime && !death) |
1428 | 0 | death = monotime() + lifetime; |
1429 | 0 | if ((id = lookup_identity(k)) == NULL) { |
1430 | 0 | id = xcalloc(1, sizeof(Identity)); |
1431 | 0 | TAILQ_INSERT_TAIL(&idtab->idlist, id, next); |
1432 | | /* Increment the number of identities. */ |
1433 | 0 | idtab->nentries++; |
1434 | 0 | } else { |
1435 | | /* identity not visible, do not update */ |
1436 | 0 | if (identity_permitted(id, e, NULL, NULL, NULL) != 0) |
1437 | 0 | goto out; /* error already logged */ |
1438 | | /* key state might have been updated */ |
1439 | 0 | sshkey_free(id->key); |
1440 | 0 | free(id->comment); |
1441 | 0 | free(id->sk_provider); |
1442 | 0 | free_dest_constraints(id->dest_constraints, |
1443 | 0 | id->ndest_constraints); |
1444 | 0 | } |
1445 | | /* success */ |
1446 | 0 | id->key = k; |
1447 | 0 | id->comment = comment; |
1448 | 0 | id->death = death; |
1449 | 0 | id->confirm = confirm; |
1450 | 0 | id->sk_provider = sk_provider; |
1451 | 0 | id->dest_constraints = dest_constraints; |
1452 | 0 | id->ndest_constraints = ndest_constraints; |
1453 | |
|
1454 | 0 | if ((fp = sshkey_fingerprint(k, SSH_FP_HASH_DEFAULT, |
1455 | 0 | SSH_FP_DEFAULT)) == NULL) |
1456 | 0 | fatal_f("sshkey_fingerprint failed"); |
1457 | 0 | debug_f("add %s %s \"%.100s\" (life: %u) (confirm: %u) " |
1458 | 0 | "(provider: %s) (destination constraints: %zu)", |
1459 | 0 | sshkey_ssh_name(k), fp, comment, seconds, confirm, |
1460 | 0 | sk_provider == NULL ? "none" : sk_provider, ndest_constraints); |
1461 | 0 | free(fp); |
1462 | | /* transferred */ |
1463 | 0 | k = NULL; |
1464 | 0 | comment = NULL; |
1465 | 0 | sk_provider = NULL; |
1466 | 0 | dest_constraints = NULL; |
1467 | 0 | ndest_constraints = 0; |
1468 | 0 | success = 1; |
1469 | 0 | out: |
1470 | 0 | free(sk_provider); |
1471 | 0 | free(comment); |
1472 | 0 | sshkey_free(k); |
1473 | 0 | free_dest_constraints(dest_constraints, ndest_constraints); |
1474 | 0 | send_status(e, success); |
1475 | 0 | } |
1476 | | |
1477 | | /* XXX todo: encrypt sensitive data with passphrase */ |
1478 | | static void |
1479 | | process_lock_agent(SocketEntry *e, int lock) |
1480 | 0 | { |
1481 | 0 | int r, success = 0, delay; |
1482 | 0 | char *passwd; |
1483 | 0 | u_char passwdhash[LOCK_SIZE]; |
1484 | 0 | static u_int fail_count = 0; |
1485 | 0 | size_t pwlen; |
1486 | |
|
1487 | 0 | debug2_f("entering"); |
1488 | | /* |
1489 | | * This is deliberately fatal: the user has requested that we lock, |
1490 | | * but we can't parse their request properly. The only safe thing to |
1491 | | * do is abort. |
1492 | | */ |
1493 | 0 | if ((r = sshbuf_get_cstring(e->request, &passwd, &pwlen)) != 0) |
1494 | 0 | fatal_fr(r, "parse"); |
1495 | 0 | if (pwlen == 0) { |
1496 | 0 | debug("empty password not supported"); |
1497 | 0 | } else if (locked && !lock) { |
1498 | 0 | if (bcrypt_pbkdf(passwd, pwlen, lock_salt, sizeof(lock_salt), |
1499 | 0 | passwdhash, sizeof(passwdhash), LOCK_ROUNDS) < 0) |
1500 | 0 | fatal("bcrypt_pbkdf"); |
1501 | 0 | if (timingsafe_bcmp(passwdhash, lock_pwhash, LOCK_SIZE) == 0) { |
1502 | 0 | debug("agent unlocked"); |
1503 | 0 | locked = 0; |
1504 | 0 | fail_count = 0; |
1505 | 0 | explicit_bzero(lock_pwhash, sizeof(lock_pwhash)); |
1506 | 0 | success = 1; |
1507 | 0 | } else { |
1508 | | /* delay in 0.1s increments up to 10s */ |
1509 | 0 | if (fail_count < 100) |
1510 | 0 | fail_count++; |
1511 | 0 | delay = 100000 * fail_count; |
1512 | 0 | debug("unlock failed, delaying %0.1lf seconds", |
1513 | 0 | (double)delay/1000000); |
1514 | | // usleep(delay); |
1515 | 0 | } |
1516 | 0 | explicit_bzero(passwdhash, sizeof(passwdhash)); |
1517 | 0 | } else if (!locked && lock) { |
1518 | 0 | debug("agent locked"); |
1519 | 0 | locked = 1; |
1520 | 0 | arc4random_buf(lock_salt, sizeof(lock_salt)); |
1521 | 0 | if (bcrypt_pbkdf(passwd, pwlen, lock_salt, sizeof(lock_salt), |
1522 | 0 | lock_pwhash, sizeof(lock_pwhash), LOCK_ROUNDS) < 0) |
1523 | 0 | fatal("bcrypt_pbkdf"); |
1524 | 0 | success = 1; |
1525 | 0 | } |
1526 | 0 | freezero(passwd, pwlen); |
1527 | 0 | send_status(e, success); |
1528 | 0 | } |
1529 | | |
1530 | | static void |
1531 | | no_identities(SocketEntry *e) |
1532 | 0 | { |
1533 | 0 | struct sshbuf *msg; |
1534 | 0 | int r; |
1535 | |
|
1536 | 0 | if ((msg = sshbuf_new()) == NULL) |
1537 | 0 | fatal_f("sshbuf_new failed"); |
1538 | 0 | if ((r = sshbuf_put_u8(msg, SSH2_AGENT_IDENTITIES_ANSWER)) != 0 || |
1539 | 0 | (r = sshbuf_put_u32(msg, 0)) != 0 || |
1540 | 0 | (r = sshbuf_put_stringb(e->output, msg)) != 0) |
1541 | 0 | fatal_fr(r, "compose"); |
1542 | 0 | sshbuf_free(msg); |
1543 | 0 | } |
1544 | | |
1545 | | #ifdef ENABLE_PKCS11 |
1546 | | /* Add an identity to idlist; takes ownership of 'key' and 'comment' */ |
1547 | | static void |
1548 | | add_p11_identity(struct sshkey *key, char *comment, const char *provider, |
1549 | | time_t death, u_int confirm, struct dest_constraint *dest_constraints, |
1550 | | size_t ndest_constraints) |
1551 | 0 | { |
1552 | 0 | Identity *id; |
1553 | |
|
1554 | 0 | if (lookup_identity(key) != NULL) { |
1555 | 0 | sshkey_free(key); |
1556 | 0 | free(comment); |
1557 | 0 | return; |
1558 | 0 | } |
1559 | 0 | id = xcalloc(1, sizeof(Identity)); |
1560 | 0 | id->key = key; |
1561 | 0 | id->comment = comment; |
1562 | 0 | id->provider = xstrdup(provider); |
1563 | 0 | id->death = death; |
1564 | 0 | id->confirm = confirm; |
1565 | 0 | id->dest_constraints = dup_dest_constraints(dest_constraints, |
1566 | 0 | ndest_constraints); |
1567 | 0 | id->ndest_constraints = ndest_constraints; |
1568 | 0 | TAILQ_INSERT_TAIL(&idtab->idlist, id, next); |
1569 | 0 | idtab->nentries++; |
1570 | 0 | } |
1571 | | |
1572 | | static void |
1573 | | process_add_smartcard_key(SocketEntry *e) |
1574 | 0 | { |
1575 | 0 | char *provider = NULL, *pin = NULL, canonical_provider[PATH_MAX]; |
1576 | 0 | char **comments = NULL; |
1577 | 0 | int r, i, count = 0, success = 0, confirm = 0; |
1578 | 0 | u_int seconds = 0; |
1579 | 0 | time_t death = 0; |
1580 | 0 | struct sshkey **keys = NULL, *k; |
1581 | 0 | struct dest_constraint *dest_constraints = NULL; |
1582 | 0 | size_t j, ndest_constraints = 0, ncerts = 0; |
1583 | 0 | struct sshkey **certs = NULL; |
1584 | 0 | int cert_only = 0; |
1585 | |
|
1586 | 0 | debug2_f("entering"); |
1587 | 0 | if ((r = sshbuf_get_cstring(e->request, &provider, NULL)) != 0 || |
1588 | 0 | (r = sshbuf_get_cstring(e->request, &pin, NULL)) != 0) { |
1589 | 0 | error_fr(r, "parse"); |
1590 | 0 | goto send; |
1591 | 0 | } |
1592 | 0 | if (parse_key_constraints(e->request, NULL, &death, &seconds, &confirm, |
1593 | 0 | NULL, &dest_constraints, &ndest_constraints, &cert_only, |
1594 | 0 | &ncerts, &certs) != 0) { |
1595 | 0 | error_f("failed to parse constraints"); |
1596 | 0 | goto send; |
1597 | 0 | } |
1598 | 0 | dump_dest_constraints(__func__, dest_constraints, ndest_constraints); |
1599 | 0 | if (socket_is_remote(e) && !remote_add_provider) { |
1600 | 0 | verbose("failed PKCS#11 add of \"%.100s\": remote addition of " |
1601 | 0 | "providers is disabled", provider); |
1602 | 0 | goto send; |
1603 | 0 | } |
1604 | 0 | if (realpath(provider, canonical_provider) == NULL) { |
1605 | 0 | verbose("failed PKCS#11 add of \"%.100s\": realpath: %s", |
1606 | 0 | provider, strerror(errno)); |
1607 | 0 | goto send; |
1608 | 0 | } |
1609 | 0 | if (match_pattern_list(canonical_provider, allowed_providers, 0) != 1) { |
1610 | 0 | verbose("refusing PKCS#11 add of \"%.100s\": " |
1611 | 0 | "provider not allowed", canonical_provider); |
1612 | 0 | goto send; |
1613 | 0 | } |
1614 | 0 | debug_f("add %.100s", canonical_provider); |
1615 | 0 | if (lifetime && !death) |
1616 | 0 | death = monotime() + lifetime; |
1617 | |
|
1618 | 0 | count = pkcs11_add_provider(canonical_provider, pin, &keys, &comments); |
1619 | 0 | for (i = 0; i < count; i++) { |
1620 | 0 | if (comments[i] == NULL || comments[i][0] == '\0') { |
1621 | 0 | free(comments[i]); |
1622 | 0 | comments[i] = xstrdup(canonical_provider); |
1623 | 0 | } |
1624 | 0 | for (j = 0; j < ncerts; j++) { |
1625 | 0 | if (!sshkey_is_cert(certs[j])) |
1626 | 0 | continue; |
1627 | 0 | if (!sshkey_equal_public(keys[i], certs[j])) |
1628 | 0 | continue; |
1629 | 0 | if (pkcs11_make_cert(keys[i], certs[j], &k) != 0) |
1630 | 0 | continue; |
1631 | 0 | add_p11_identity(k, xstrdup(comments[i]), |
1632 | 0 | canonical_provider, death, confirm, |
1633 | 0 | dest_constraints, ndest_constraints); |
1634 | 0 | success = 1; |
1635 | 0 | } |
1636 | 0 | if (!cert_only && lookup_identity(keys[i]) == NULL) { |
1637 | 0 | add_p11_identity(keys[i], comments[i], |
1638 | 0 | canonical_provider, death, confirm, |
1639 | 0 | dest_constraints, ndest_constraints); |
1640 | 0 | keys[i] = NULL; /* transferred */ |
1641 | 0 | comments[i] = NULL; /* transferred */ |
1642 | 0 | success = 1; |
1643 | 0 | } |
1644 | | /* XXX update constraints for existing keys */ |
1645 | 0 | sshkey_free(keys[i]); |
1646 | 0 | free(comments[i]); |
1647 | 0 | } |
1648 | 0 | send: |
1649 | 0 | free(pin); |
1650 | 0 | free(provider); |
1651 | 0 | free(keys); |
1652 | 0 | free(comments); |
1653 | 0 | free_dest_constraints(dest_constraints, ndest_constraints); |
1654 | 0 | for (j = 0; j < ncerts; j++) |
1655 | 0 | sshkey_free(certs[j]); |
1656 | 0 | free(certs); |
1657 | 0 | send_status(e, success); |
1658 | 0 | } |
1659 | | |
1660 | | static void |
1661 | | process_remove_smartcard_key(SocketEntry *e) |
1662 | 0 | { |
1663 | 0 | char *provider = NULL, *pin = NULL, canonical_provider[PATH_MAX]; |
1664 | 0 | int r, success = 0; |
1665 | 0 | Identity *id, *nxt; |
1666 | |
|
1667 | 0 | debug2_f("entering"); |
1668 | 0 | if ((r = sshbuf_get_cstring(e->request, &provider, NULL)) != 0 || |
1669 | 0 | (r = sshbuf_get_cstring(e->request, &pin, NULL)) != 0) { |
1670 | 0 | error_fr(r, "parse"); |
1671 | 0 | goto send; |
1672 | 0 | } |
1673 | 0 | free(pin); |
1674 | |
|
1675 | 0 | if (realpath(provider, canonical_provider) == NULL) { |
1676 | 0 | verbose("failed PKCS#11 add of \"%.100s\": realpath: %s", |
1677 | 0 | provider, strerror(errno)); |
1678 | 0 | goto send; |
1679 | 0 | } |
1680 | | |
1681 | 0 | debug_f("remove %.100s", canonical_provider); |
1682 | 0 | for (id = TAILQ_FIRST(&idtab->idlist); id; id = nxt) { |
1683 | 0 | nxt = TAILQ_NEXT(id, next); |
1684 | | /* Skip file--based keys */ |
1685 | 0 | if (id->provider == NULL) |
1686 | 0 | continue; |
1687 | 0 | if (!strcmp(canonical_provider, id->provider)) { |
1688 | 0 | TAILQ_REMOVE(&idtab->idlist, id, next); |
1689 | 0 | free_identity(id); |
1690 | 0 | idtab->nentries--; |
1691 | 0 | } |
1692 | 0 | } |
1693 | 0 | if (pkcs11_del_provider(canonical_provider) == 0) |
1694 | 0 | success = 1; |
1695 | 0 | else |
1696 | 0 | error_f("pkcs11_del_provider failed"); |
1697 | 0 | send: |
1698 | 0 | free(provider); |
1699 | 0 | send_status(e, success); |
1700 | 0 | } |
1701 | | #endif /* ENABLE_PKCS11 */ |
1702 | | |
1703 | | static int |
1704 | | process_ext_session_bind(SocketEntry *e) |
1705 | 0 | { |
1706 | 0 | int r, sid_match, key_match; |
1707 | 0 | struct sshkey *key = NULL; |
1708 | 0 | struct sshbuf *sid = NULL, *sig = NULL; |
1709 | 0 | char *fp = NULL; |
1710 | 0 | size_t i; |
1711 | 0 | u_char fwd = 0; |
1712 | |
|
1713 | 0 | debug2_f("entering"); |
1714 | 0 | e->session_bind_attempted = 1; |
1715 | 0 | if ((r = sshkey_froms(e->request, &key)) != 0 || |
1716 | 0 | (r = sshbuf_froms(e->request, &sid)) != 0 || |
1717 | 0 | (r = sshbuf_froms(e->request, &sig)) != 0 || |
1718 | 0 | (r = sshbuf_get_u8(e->request, &fwd)) != 0) { |
1719 | 0 | error_fr(r, "parse"); |
1720 | 0 | goto out; |
1721 | 0 | } |
1722 | 0 | if (sshbuf_len(sid) > AGENT_MAX_SID_LEN) { |
1723 | 0 | error_f("session ID too long"); |
1724 | 0 | goto out; |
1725 | 0 | } |
1726 | 0 | if ((fp = sshkey_fingerprint(key, SSH_FP_HASH_DEFAULT, |
1727 | 0 | SSH_FP_DEFAULT)) == NULL) |
1728 | 0 | fatal_f("fingerprint failed"); |
1729 | | /* check signature with hostkey on session ID */ |
1730 | 0 | if ((r = sshkey_verify(key, sshbuf_ptr(sig), sshbuf_len(sig), |
1731 | 0 | sshbuf_ptr(sid), sshbuf_len(sid), NULL, 0, NULL)) != 0) { |
1732 | 0 | error_fr(r, "sshkey_verify for %s %s", sshkey_type(key), fp); |
1733 | 0 | goto out; |
1734 | 0 | } |
1735 | | /* check whether sid/key already recorded */ |
1736 | 0 | for (i = 0; i < e->nsession_ids; i++) { |
1737 | 0 | if (!e->session_ids[i].forwarded) { |
1738 | 0 | error_f("attempt to bind session ID to socket " |
1739 | 0 | "previously bound for authentication attempt"); |
1740 | 0 | r = -1; |
1741 | 0 | goto out; |
1742 | 0 | } |
1743 | 0 | sid_match = buf_equal(sid, e->session_ids[i].sid) == 0; |
1744 | 0 | key_match = sshkey_equal(key, e->session_ids[i].key); |
1745 | 0 | if (sid_match && key_match) { |
1746 | 0 | debug_f("session ID already recorded for %s %s", |
1747 | 0 | sshkey_type(key), fp); |
1748 | 0 | r = 0; |
1749 | 0 | goto out; |
1750 | 0 | } else if (sid_match) { |
1751 | 0 | error_f("session ID recorded against different key " |
1752 | 0 | "for %s %s", sshkey_type(key), fp); |
1753 | 0 | r = -1; |
1754 | 0 | goto out; |
1755 | 0 | } |
1756 | | /* |
1757 | | * new sid with previously-seen key can happen, e.g. multiple |
1758 | | * connections to the same host. |
1759 | | */ |
1760 | 0 | } |
1761 | | /* record new key/sid */ |
1762 | 0 | if (e->nsession_ids >= AGENT_MAX_SESSION_IDS) { |
1763 | 0 | error_f("too many session IDs recorded"); |
1764 | 0 | r = -1; |
1765 | 0 | goto out; |
1766 | 0 | } |
1767 | 0 | e->session_ids = xrecallocarray(e->session_ids, e->nsession_ids, |
1768 | 0 | e->nsession_ids + 1, sizeof(*e->session_ids)); |
1769 | 0 | i = e->nsession_ids++; |
1770 | 0 | debug_f("recorded %s %s (slot %zu of %d)", sshkey_type(key), fp, i, |
1771 | 0 | AGENT_MAX_SESSION_IDS); |
1772 | 0 | e->session_ids[i].key = key; |
1773 | 0 | e->session_ids[i].forwarded = fwd != 0; |
1774 | 0 | key = NULL; /* transferred */ |
1775 | | /* can't transfer sid; it's refcounted and scoped to request's life */ |
1776 | 0 | if ((e->session_ids[i].sid = sshbuf_new()) == NULL) |
1777 | 0 | fatal_f("sshbuf_new"); |
1778 | 0 | if ((r = sshbuf_putb(e->session_ids[i].sid, sid)) != 0) |
1779 | 0 | fatal_fr(r, "sshbuf_putb session ID"); |
1780 | | /* success */ |
1781 | 0 | r = 0; |
1782 | 0 | out: |
1783 | 0 | free(fp); |
1784 | 0 | sshkey_free(key); |
1785 | 0 | sshbuf_free(sid); |
1786 | 0 | sshbuf_free(sig); |
1787 | 0 | return r == 0 ? 1 : 0; |
1788 | 0 | } |
1789 | | |
1790 | | static void |
1791 | | process_extension(SocketEntry *e) |
1792 | 0 | { |
1793 | 0 | int r, success = 0; |
1794 | 0 | char *name; |
1795 | |
|
1796 | 0 | debug2_f("entering"); |
1797 | 0 | if ((r = sshbuf_get_cstring(e->request, &name, NULL)) != 0) { |
1798 | 0 | error_fr(r, "parse"); |
1799 | 0 | goto send; |
1800 | 0 | } |
1801 | 0 | if (strcmp(name, "session-bind@openssh.com") == 0) |
1802 | 0 | success = process_ext_session_bind(e); |
1803 | 0 | else |
1804 | 0 | debug_f("unsupported extension \"%s\"", name); |
1805 | 0 | free(name); |
1806 | 0 | send: |
1807 | 0 | send_status(e, success); |
1808 | 0 | } |
1809 | | /* |
1810 | | * dispatch incoming message. |
1811 | | * returns 1 on success, 0 for incomplete messages or -1 on error. |
1812 | | */ |
1813 | | static int |
1814 | | process_message(u_int socknum) |
1815 | 0 | { |
1816 | 0 | u_int msg_len; |
1817 | 0 | u_char type; |
1818 | 0 | const u_char *cp; |
1819 | 0 | int r; |
1820 | 0 | SocketEntry *e; |
1821 | |
|
1822 | 0 | if (socknum >= sockets_alloc) |
1823 | 0 | fatal_f("sock %u >= allocated %u", socknum, sockets_alloc); |
1824 | 0 | e = &sockets[socknum]; |
1825 | |
|
1826 | 0 | if (sshbuf_len(e->input) < 5) |
1827 | 0 | return 0; /* Incomplete message header. */ |
1828 | 0 | cp = sshbuf_ptr(e->input); |
1829 | 0 | msg_len = PEEK_U32(cp); |
1830 | 0 | if (msg_len > AGENT_MAX_LEN) { |
1831 | 0 | debug_f("socket %u (fd=%d) message too long %u > %u", |
1832 | 0 | socknum, e->fd, msg_len, AGENT_MAX_LEN); |
1833 | 0 | return -1; |
1834 | 0 | } |
1835 | 0 | if (sshbuf_len(e->input) < msg_len + 4) |
1836 | 0 | return 0; /* Incomplete message body. */ |
1837 | | |
1838 | | /* move the current input to e->request */ |
1839 | 0 | sshbuf_reset(e->request); |
1840 | 0 | if ((r = sshbuf_get_stringb(e->input, e->request)) != 0 || |
1841 | 0 | (r = sshbuf_get_u8(e->request, &type)) != 0) { |
1842 | 0 | if (r == SSH_ERR_MESSAGE_INCOMPLETE || |
1843 | 0 | r == SSH_ERR_STRING_TOO_LARGE) { |
1844 | 0 | error_fr(r, "parse"); |
1845 | 0 | return -1; |
1846 | 0 | } |
1847 | 0 | fatal_fr(r, "parse"); |
1848 | 0 | } |
1849 | | |
1850 | 0 | debug_f("socket %u (fd=%d) type %d", socknum, e->fd, type); |
1851 | | |
1852 | | /* check whether agent is locked */ |
1853 | 0 | if (locked && type != SSH_AGENTC_UNLOCK) { |
1854 | 0 | sshbuf_reset(e->request); |
1855 | 0 | switch (type) { |
1856 | 0 | case SSH2_AGENTC_REQUEST_IDENTITIES: |
1857 | | /* send empty lists */ |
1858 | 0 | no_identities(e); |
1859 | 0 | break; |
1860 | 0 | default: |
1861 | | /* send a fail message for all other request types */ |
1862 | 0 | send_status(e, 0); |
1863 | 0 | } |
1864 | 0 | return 1; |
1865 | 0 | } |
1866 | | |
1867 | 0 | switch (type) { |
1868 | 0 | case SSH_AGENTC_LOCK: |
1869 | 0 | case SSH_AGENTC_UNLOCK: |
1870 | 0 | process_lock_agent(e, type == SSH_AGENTC_LOCK); |
1871 | 0 | break; |
1872 | 0 | case SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES: |
1873 | 0 | process_remove_all_identities(e); /* safe for !WITH_SSH1 */ |
1874 | 0 | break; |
1875 | | /* ssh2 */ |
1876 | 0 | case SSH2_AGENTC_SIGN_REQUEST: |
1877 | 0 | process_sign_request2(e); |
1878 | 0 | break; |
1879 | 0 | case SSH2_AGENTC_REQUEST_IDENTITIES: |
1880 | 0 | process_request_identities(e); |
1881 | 0 | break; |
1882 | 0 | case SSH2_AGENTC_ADD_IDENTITY: |
1883 | 0 | case SSH2_AGENTC_ADD_ID_CONSTRAINED: |
1884 | 0 | process_add_identity(e); |
1885 | 0 | break; |
1886 | 0 | case SSH2_AGENTC_REMOVE_IDENTITY: |
1887 | 0 | process_remove_identity(e); |
1888 | 0 | break; |
1889 | 0 | case SSH2_AGENTC_REMOVE_ALL_IDENTITIES: |
1890 | 0 | process_remove_all_identities(e); |
1891 | 0 | break; |
1892 | 0 | #ifdef ENABLE_PKCS11 |
1893 | 0 | case SSH_AGENTC_ADD_SMARTCARD_KEY: |
1894 | 0 | case SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED: |
1895 | 0 | process_add_smartcard_key(e); |
1896 | 0 | break; |
1897 | 0 | case SSH_AGENTC_REMOVE_SMARTCARD_KEY: |
1898 | 0 | process_remove_smartcard_key(e); |
1899 | 0 | break; |
1900 | 0 | #endif /* ENABLE_PKCS11 */ |
1901 | 0 | case SSH_AGENTC_EXTENSION: |
1902 | 0 | process_extension(e); |
1903 | 0 | break; |
1904 | 0 | default: |
1905 | | /* Unknown message. Respond with failure. */ |
1906 | 0 | error("Unknown message %d", type); |
1907 | 0 | sshbuf_reset(e->request); |
1908 | 0 | send_status(e, 0); |
1909 | 0 | break; |
1910 | 0 | } |
1911 | 0 | return 1; |
1912 | 0 | } |
1913 | | |
1914 | | static void |
1915 | | new_socket(sock_type type, int fd) |
1916 | 0 | { |
1917 | 0 | u_int i, old_alloc, new_alloc; |
1918 | |
|
1919 | 0 | debug_f("type = %s", type == AUTH_CONNECTION ? "CONNECTION" : |
1920 | 0 | (type == AUTH_SOCKET ? "SOCKET" : "UNKNOWN")); |
1921 | 0 | set_nonblock(fd); |
1922 | |
|
1923 | 0 | if (fd > max_fd) |
1924 | 0 | max_fd = fd; |
1925 | |
|
1926 | 0 | for (i = 0; i < sockets_alloc; i++) |
1927 | 0 | if (sockets[i].type == AUTH_UNUSED) { |
1928 | 0 | sockets[i].fd = fd; |
1929 | 0 | if ((sockets[i].input = sshbuf_new()) == NULL || |
1930 | 0 | (sockets[i].output = sshbuf_new()) == NULL || |
1931 | 0 | (sockets[i].request = sshbuf_new()) == NULL) |
1932 | 0 | fatal_f("sshbuf_new failed"); |
1933 | 0 | sockets[i].type = type; |
1934 | 0 | return; |
1935 | 0 | } |
1936 | 0 | old_alloc = sockets_alloc; |
1937 | 0 | new_alloc = sockets_alloc + 10; |
1938 | 0 | sockets = xrecallocarray(sockets, old_alloc, new_alloc, |
1939 | 0 | sizeof(sockets[0])); |
1940 | 0 | for (i = old_alloc; i < new_alloc; i++) |
1941 | 0 | sockets[i].type = AUTH_UNUSED; |
1942 | 0 | sockets_alloc = new_alloc; |
1943 | 0 | sockets[old_alloc].fd = fd; |
1944 | 0 | if ((sockets[old_alloc].input = sshbuf_new()) == NULL || |
1945 | 0 | (sockets[old_alloc].output = sshbuf_new()) == NULL || |
1946 | 0 | (sockets[old_alloc].request = sshbuf_new()) == NULL) |
1947 | 0 | fatal_f("sshbuf_new failed"); |
1948 | 0 | sockets[old_alloc].type = type; |
1949 | 0 | } |
1950 | | |
1951 | | static int |
1952 | | handle_socket_read(u_int socknum) |
1953 | 0 | { |
1954 | 0 | struct sockaddr_un sunaddr; |
1955 | 0 | socklen_t slen; |
1956 | 0 | uid_t euid; |
1957 | 0 | gid_t egid; |
1958 | 0 | int fd; |
1959 | |
|
1960 | 0 | slen = sizeof(sunaddr); |
1961 | 0 | fd = accept(sockets[socknum].fd, (struct sockaddr *)&sunaddr, &slen); |
1962 | 0 | if (fd == -1) { |
1963 | 0 | error("accept from AUTH_SOCKET: %s", strerror(errno)); |
1964 | 0 | return -1; |
1965 | 0 | } |
1966 | 0 | if (getpeereid(fd, &euid, &egid) == -1) { |
1967 | 0 | error("getpeereid %d failed: %s", fd, strerror(errno)); |
1968 | 0 | close(fd); |
1969 | 0 | return -1; |
1970 | 0 | } |
1971 | 0 | if ((euid != 0) && (getuid() != euid)) { |
1972 | 0 | error("uid mismatch: peer euid %u != uid %u", |
1973 | 0 | (u_int) euid, (u_int) getuid()); |
1974 | 0 | close(fd); |
1975 | 0 | return -1; |
1976 | 0 | } |
1977 | 0 | new_socket(AUTH_CONNECTION, fd); |
1978 | 0 | return 0; |
1979 | 0 | } |
1980 | | |
1981 | | static int |
1982 | | handle_conn_read(u_int socknum) |
1983 | 0 | { |
1984 | 0 | char buf[AGENT_RBUF_LEN]; |
1985 | 0 | ssize_t len; |
1986 | 0 | int r; |
1987 | |
|
1988 | 0 | if ((len = read(sockets[socknum].fd, buf, sizeof(buf))) <= 0) { |
1989 | 0 | if (len == -1) { |
1990 | 0 | if (errno == EAGAIN || errno == EINTR) |
1991 | 0 | return 0; |
1992 | 0 | error_f("read error on socket %u (fd %d): %s", |
1993 | 0 | socknum, sockets[socknum].fd, strerror(errno)); |
1994 | 0 | } |
1995 | 0 | return -1; |
1996 | 0 | } |
1997 | 0 | if ((r = sshbuf_put(sockets[socknum].input, buf, len)) != 0) |
1998 | 0 | fatal_fr(r, "compose"); |
1999 | 0 | explicit_bzero(buf, sizeof(buf)); |
2000 | 0 | for (;;) { |
2001 | 0 | if ((r = process_message(socknum)) == -1) |
2002 | 0 | return -1; |
2003 | 0 | else if (r == 0) |
2004 | 0 | break; |
2005 | 0 | } |
2006 | 0 | return 0; |
2007 | 0 | } |
2008 | | |
2009 | | static int |
2010 | | handle_conn_write(u_int socknum) |
2011 | 0 | { |
2012 | 0 | ssize_t len; |
2013 | 0 | int r; |
2014 | |
|
2015 | 0 | if (sshbuf_len(sockets[socknum].output) == 0) |
2016 | 0 | return 0; /* shouldn't happen */ |
2017 | 0 | if ((len = write(sockets[socknum].fd, |
2018 | 0 | sshbuf_ptr(sockets[socknum].output), |
2019 | 0 | sshbuf_len(sockets[socknum].output))) <= 0) { |
2020 | 0 | if (len == -1) { |
2021 | 0 | if (errno == EAGAIN || errno == EINTR) |
2022 | 0 | return 0; |
2023 | 0 | error_f("read error on socket %u (fd %d): %s", |
2024 | 0 | socknum, sockets[socknum].fd, strerror(errno)); |
2025 | 0 | } |
2026 | 0 | return -1; |
2027 | 0 | } |
2028 | 0 | if ((r = sshbuf_consume(sockets[socknum].output, len)) != 0) |
2029 | 0 | fatal_fr(r, "consume"); |
2030 | 0 | return 0; |
2031 | 0 | } |
2032 | | |
2033 | | static void |
2034 | | after_poll(struct pollfd *pfd, size_t npfd, u_int maxfds) |
2035 | 0 | { |
2036 | 0 | size_t i; |
2037 | 0 | u_int socknum, activefds = npfd; |
2038 | |
|
2039 | 0 | for (i = 0; i < npfd; i++) { |
2040 | 0 | if (pfd[i].revents == 0) |
2041 | 0 | continue; |
2042 | | /* Find sockets entry */ |
2043 | 0 | for (socknum = 0; socknum < sockets_alloc; socknum++) { |
2044 | 0 | if (sockets[socknum].type != AUTH_SOCKET && |
2045 | 0 | sockets[socknum].type != AUTH_CONNECTION) |
2046 | 0 | continue; |
2047 | 0 | if (pfd[i].fd == sockets[socknum].fd) |
2048 | 0 | break; |
2049 | 0 | } |
2050 | 0 | if (socknum >= sockets_alloc) { |
2051 | 0 | error_f("no socket for fd %d", pfd[i].fd); |
2052 | 0 | continue; |
2053 | 0 | } |
2054 | | /* Process events */ |
2055 | 0 | switch (sockets[socknum].type) { |
2056 | 0 | case AUTH_SOCKET: |
2057 | 0 | if ((pfd[i].revents & (POLLIN|POLLERR)) == 0) |
2058 | 0 | break; |
2059 | 0 | if (npfd > maxfds) { |
2060 | 0 | debug3("out of fds (active %u >= limit %u); " |
2061 | 0 | "skipping accept", activefds, maxfds); |
2062 | 0 | break; |
2063 | 0 | } |
2064 | 0 | if (handle_socket_read(socknum) == 0) |
2065 | 0 | activefds++; |
2066 | 0 | break; |
2067 | 0 | case AUTH_CONNECTION: |
2068 | 0 | if ((pfd[i].revents & (POLLIN|POLLHUP|POLLERR)) != 0 && |
2069 | 0 | handle_conn_read(socknum) != 0) |
2070 | 0 | goto close_sock; |
2071 | 0 | if ((pfd[i].revents & (POLLOUT|POLLHUP)) != 0 && |
2072 | 0 | handle_conn_write(socknum) != 0) { |
2073 | 0 | close_sock: |
2074 | 0 | if (activefds == 0) |
2075 | 0 | fatal("activefds == 0 at close_sock"); |
2076 | 0 | close_socket(&sockets[socknum]); |
2077 | 0 | activefds--; |
2078 | 0 | break; |
2079 | 0 | } |
2080 | 0 | break; |
2081 | 0 | default: |
2082 | 0 | break; |
2083 | 0 | } |
2084 | 0 | } |
2085 | 0 | } |
2086 | | |
2087 | | static int |
2088 | | prepare_poll(struct pollfd **pfdp, size_t *npfdp, struct timespec *timeoutp, u_int maxfds) |
2089 | 0 | { |
2090 | 0 | struct pollfd *pfd = *pfdp; |
2091 | 0 | size_t i, j, npfd = 0; |
2092 | 0 | time_t deadline; |
2093 | 0 | int r; |
2094 | | |
2095 | | /* Count active sockets */ |
2096 | 0 | for (i = 0; i < sockets_alloc; i++) { |
2097 | 0 | switch (sockets[i].type) { |
2098 | 0 | case AUTH_SOCKET: |
2099 | 0 | case AUTH_CONNECTION: |
2100 | 0 | npfd++; |
2101 | 0 | break; |
2102 | 0 | case AUTH_UNUSED: |
2103 | 0 | break; |
2104 | 0 | default: |
2105 | 0 | fatal("Unknown socket type %d", sockets[i].type); |
2106 | 0 | break; |
2107 | 0 | } |
2108 | 0 | } |
2109 | 0 | if (npfd != *npfdp && |
2110 | 0 | (pfd = recallocarray(pfd, *npfdp, npfd, sizeof(*pfd))) == NULL) |
2111 | 0 | fatal_f("recallocarray failed"); |
2112 | 0 | *pfdp = pfd; |
2113 | 0 | *npfdp = npfd; |
2114 | |
|
2115 | 0 | for (i = j = 0; i < sockets_alloc; i++) { |
2116 | 0 | switch (sockets[i].type) { |
2117 | 0 | case AUTH_SOCKET: |
2118 | 0 | if (npfd > maxfds) { |
2119 | 0 | debug3("out of fds (active %zu >= limit %u); " |
2120 | 0 | "skipping arming listener", npfd, maxfds); |
2121 | 0 | break; |
2122 | 0 | } |
2123 | 0 | pfd[j].fd = sockets[i].fd; |
2124 | 0 | pfd[j].revents = 0; |
2125 | 0 | pfd[j].events = POLLIN; |
2126 | 0 | j++; |
2127 | 0 | break; |
2128 | 0 | case AUTH_CONNECTION: |
2129 | 0 | pfd[j].fd = sockets[i].fd; |
2130 | 0 | pfd[j].revents = 0; |
2131 | | /* |
2132 | | * Only prepare to read if we can handle a full-size |
2133 | | * input read buffer and enqueue a max size reply.. |
2134 | | */ |
2135 | 0 | if ((r = sshbuf_check_reserve(sockets[i].input, |
2136 | 0 | AGENT_RBUF_LEN)) == 0 && |
2137 | 0 | (r = sshbuf_check_reserve(sockets[i].output, |
2138 | 0 | AGENT_MAX_LEN)) == 0) |
2139 | 0 | pfd[j].events = POLLIN; |
2140 | 0 | else if (r != SSH_ERR_NO_BUFFER_SPACE) |
2141 | 0 | fatal_fr(r, "reserve"); |
2142 | 0 | if (sshbuf_len(sockets[i].output) > 0) |
2143 | 0 | pfd[j].events |= POLLOUT; |
2144 | 0 | j++; |
2145 | 0 | break; |
2146 | 0 | default: |
2147 | 0 | break; |
2148 | 0 | } |
2149 | 0 | } |
2150 | 0 | deadline = reaper(); |
2151 | 0 | if (parent_alive_interval != 0) |
2152 | 0 | deadline = (deadline == 0) ? parent_alive_interval : |
2153 | 0 | MINIMUM(deadline, parent_alive_interval); |
2154 | 0 | if (deadline != 0) |
2155 | 0 | ptimeout_deadline_sec(timeoutp, deadline); |
2156 | 0 | return (1); |
2157 | 0 | } |
2158 | | |
2159 | | static void |
2160 | | cleanup_socket(void) |
2161 | 0 | { |
2162 | 0 | if (cleanup_pid != 0 && getpid() != cleanup_pid) |
2163 | 0 | return; |
2164 | 0 | debug_f("cleanup"); |
2165 | 0 | if (socket_name[0]) |
2166 | 0 | unlink(socket_name); |
2167 | 0 | if (socket_dir[0]) |
2168 | 0 | rmdir(socket_dir); |
2169 | 0 | } |
2170 | | |
2171 | | void |
2172 | | cleanup_exit(int i) |
2173 | 0 | { |
2174 | 0 | cleanup_socket(); |
2175 | 0 | #ifdef ENABLE_PKCS11 |
2176 | 0 | pkcs11_terminate(); |
2177 | 0 | #endif |
2178 | 0 | _exit(i); |
2179 | 0 | } |
2180 | | |
2181 | | static void |
2182 | | cleanup_handler(int sig) |
2183 | 0 | { |
2184 | 0 | signalled_exit = sig; |
2185 | 0 | } |
2186 | | |
2187 | | static void |
2188 | | keydrop_handler(int sig) |
2189 | 0 | { |
2190 | 0 | signalled_keydrop = sig; |
2191 | 0 | } |
2192 | | |
2193 | | static void |
2194 | | check_parent_exists(void) |
2195 | 0 | { |
2196 | | /* |
2197 | | * If our parent has exited then getppid() will return (pid_t)1, |
2198 | | * so testing for that should be safe. |
2199 | | */ |
2200 | 0 | if (parent_pid != -1 && getppid() != parent_pid) { |
2201 | | /* printf("Parent has died - Authentication agent exiting.\n"); */ |
2202 | 0 | cleanup_socket(); |
2203 | 0 | _exit(2); |
2204 | 0 | } |
2205 | 0 | } |
2206 | | |
2207 | | static void |
2208 | | usage(void) |
2209 | 0 | { |
2210 | 0 | fprintf(stderr, |
2211 | 0 | "usage: hpnssh-agent [-c | -s] [-Dd] [-a bind_address] [-E fingerprint_hash]\n" |
2212 | 0 | " [-O option] [-P allowed_providers] [-t life]\n" |
2213 | 0 | " hpnssh-agent [-a bind_address] [-E fingerprint_hash] [-O option]\n" |
2214 | 0 | " [-P allowed_providers] [-t life] command [arg ...]\n" |
2215 | 0 | " hpnssh-agent [-c | -s] -k\n"); |
2216 | 0 | exit(1); |
2217 | 0 | } |
2218 | | |
2219 | | int |
2220 | | main(int ac, char **av) |
2221 | 0 | { |
2222 | 0 | int c_flag = 0, d_flag = 0, D_flag = 0, k_flag = 0, s_flag = 0; |
2223 | 0 | int sock = -1, ch, result, saved_errno; |
2224 | 0 | char *shell, *format, *fdstr, *pidstr, *agentsocket = NULL; |
2225 | 0 | const char *errstr = NULL; |
2226 | 0 | const char *ccp; |
2227 | 0 | #ifdef HAVE_SETRLIMIT |
2228 | 0 | struct rlimit rlim; |
2229 | 0 | #endif |
2230 | 0 | extern int optind; |
2231 | 0 | extern char *optarg; |
2232 | 0 | pid_t pid; |
2233 | 0 | char pidstrbuf[1 + 3 * sizeof pid]; |
2234 | 0 | size_t len; |
2235 | 0 | mode_t prev_mask; |
2236 | 0 | struct timespec timeout; |
2237 | 0 | struct pollfd *pfd = NULL; |
2238 | 0 | size_t npfd = 0; |
2239 | 0 | u_int maxfds; |
2240 | 0 | sigset_t nsigset, osigset; |
2241 | | |
2242 | | /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ |
2243 | 0 | sanitise_stdfd(); |
2244 | | |
2245 | | /* drop */ |
2246 | 0 | (void)setegid(getgid()); |
2247 | 0 | (void)setgid(getgid()); |
2248 | |
|
2249 | 0 | platform_disable_tracing(0); /* strict=no */ |
2250 | |
|
2251 | 0 | #ifdef RLIMIT_NOFILE |
2252 | 0 | if (getrlimit(RLIMIT_NOFILE, &rlim) == -1) |
2253 | 0 | fatal("%s: getrlimit: %s", __progname, strerror(errno)); |
2254 | 0 | #endif |
2255 | | |
2256 | 0 | __progname = ssh_get_progname(av[0]); |
2257 | 0 | seed_rng(); |
2258 | |
|
2259 | 0 | while ((ch = getopt(ac, av, "cDdksE:a:O:P:t:")) != -1) { |
2260 | 0 | switch (ch) { |
2261 | 0 | case 'E': |
2262 | 0 | fingerprint_hash = ssh_digest_alg_by_name(optarg); |
2263 | 0 | if (fingerprint_hash == -1) |
2264 | 0 | fatal("Invalid hash algorithm \"%s\"", optarg); |
2265 | 0 | break; |
2266 | 0 | case 'c': |
2267 | 0 | if (s_flag) |
2268 | 0 | usage(); |
2269 | 0 | c_flag++; |
2270 | 0 | break; |
2271 | 0 | case 'k': |
2272 | 0 | k_flag++; |
2273 | 0 | break; |
2274 | 0 | case 'O': |
2275 | 0 | if (strcmp(optarg, "no-restrict-websafe") == 0) |
2276 | 0 | restrict_websafe = 0; |
2277 | 0 | else if (strcmp(optarg, "allow-remote-pkcs11") == 0) |
2278 | 0 | remote_add_provider = 1; |
2279 | 0 | else if ((ccp = strprefix(optarg, |
2280 | 0 | "websafe-allow=", 0)) != NULL) { |
2281 | 0 | if (websafe_allowlist != NULL) |
2282 | 0 | fatal("websafe-allow already set"); |
2283 | 0 | websafe_allowlist = xstrdup(ccp); |
2284 | 0 | } else |
2285 | 0 | fatal("Unknown -O option"); |
2286 | 0 | break; |
2287 | 0 | case 'P': |
2288 | 0 | if (allowed_providers != NULL) |
2289 | 0 | fatal("-P option already specified"); |
2290 | 0 | allowed_providers = xstrdup(optarg); |
2291 | 0 | break; |
2292 | 0 | case 's': |
2293 | 0 | if (c_flag) |
2294 | 0 | usage(); |
2295 | 0 | s_flag++; |
2296 | 0 | break; |
2297 | 0 | case 'd': |
2298 | 0 | if (d_flag || D_flag) |
2299 | 0 | usage(); |
2300 | 0 | d_flag++; |
2301 | 0 | break; |
2302 | 0 | case 'D': |
2303 | 0 | if (d_flag || D_flag) |
2304 | 0 | usage(); |
2305 | 0 | D_flag++; |
2306 | 0 | break; |
2307 | 0 | case 'a': |
2308 | 0 | agentsocket = optarg; |
2309 | 0 | break; |
2310 | 0 | case 't': |
2311 | 0 | if ((lifetime = convtime(optarg)) == -1) { |
2312 | 0 | fprintf(stderr, "Invalid lifetime\n"); |
2313 | 0 | usage(); |
2314 | 0 | } |
2315 | 0 | break; |
2316 | 0 | default: |
2317 | 0 | usage(); |
2318 | 0 | } |
2319 | 0 | } |
2320 | 0 | ac -= optind; |
2321 | 0 | av += optind; |
2322 | |
|
2323 | 0 | if (ac > 0 && (c_flag || k_flag || s_flag || d_flag || D_flag)) |
2324 | 0 | usage(); |
2325 | |
|
2326 | 0 | if (allowed_providers == NULL) |
2327 | 0 | allowed_providers = xstrdup(DEFAULT_ALLOWED_PROVIDERS); |
2328 | 0 | if (websafe_allowlist == NULL) |
2329 | 0 | websafe_allowlist = xstrdup(DEFAULT_WEBSAFE_ALLOWLIST); |
2330 | |
|
2331 | 0 | if (ac == 0 && !c_flag && !s_flag) { |
2332 | 0 | shell = getenv("SHELL"); |
2333 | 0 | if (shell != NULL && (len = strlen(shell)) > 2 && |
2334 | 0 | strncmp(shell + len - 3, "csh", 3) == 0) |
2335 | 0 | c_flag = 1; |
2336 | 0 | } |
2337 | 0 | if (k_flag) { |
2338 | 0 | pidstr = getenv(SSH_AGENTPID_ENV_NAME); |
2339 | 0 | if (pidstr == NULL) { |
2340 | 0 | fprintf(stderr, "%s not set, cannot kill agent\n", |
2341 | 0 | SSH_AGENTPID_ENV_NAME); |
2342 | 0 | exit(1); |
2343 | 0 | } |
2344 | 0 | pid = (int)strtonum(pidstr, 2, INT_MAX, &errstr); |
2345 | 0 | if (errstr) { |
2346 | 0 | fprintf(stderr, |
2347 | 0 | "%s=\"%s\", which is not a good PID: %s\n", |
2348 | 0 | SSH_AGENTPID_ENV_NAME, pidstr, errstr); |
2349 | 0 | exit(1); |
2350 | 0 | } |
2351 | 0 | if (kill(pid, SIGTERM) == -1) { |
2352 | 0 | perror("kill"); |
2353 | 0 | exit(1); |
2354 | 0 | } |
2355 | 0 | format = c_flag ? "unsetenv %s;\n" : "unset %s;\n"; |
2356 | 0 | printf(format, SSH_AUTHSOCKET_ENV_NAME); |
2357 | 0 | printf(format, SSH_AGENTPID_ENV_NAME); |
2358 | 0 | printf("echo Agent pid %ld killed;\n", (long)pid); |
2359 | 0 | exit(0); |
2360 | 0 | } |
2361 | | |
2362 | | /* |
2363 | | * Minimum file descriptors: |
2364 | | * stdio (3) + listener (1) + syslog (1 maybe) + connection (1) + |
2365 | | * a few spare for libc / stack protectors / sanitisers, etc. |
2366 | | */ |
2367 | 0 | #define SSH_AGENT_MIN_FDS (3+1+1+1+4) |
2368 | 0 | if (rlim.rlim_cur < SSH_AGENT_MIN_FDS) |
2369 | 0 | fatal("%s: file descriptor rlimit %lld too low (minimum %u)", |
2370 | 0 | __progname, (long long)rlim.rlim_cur, SSH_AGENT_MIN_FDS); |
2371 | 0 | maxfds = rlim.rlim_cur - SSH_AGENT_MIN_FDS; |
2372 | |
|
2373 | 0 | parent_pid = getpid(); |
2374 | | |
2375 | | /* Has the socket been provided via socket activation? */ |
2376 | 0 | if (agentsocket == NULL && ac == 0 && (d_flag || D_flag) && |
2377 | 0 | (pidstr = getenv("LISTEN_PID")) != NULL && |
2378 | 0 | (fdstr = getenv("LISTEN_FDS")) != NULL) { |
2379 | 0 | if (strcmp(fdstr, "1") != 0) { |
2380 | 0 | fatal("unexpected LISTEN_FDS contents " |
2381 | 0 | "(want: \"1\" got\"%s\"", fdstr); |
2382 | 0 | } |
2383 | 0 | if (fcntl(3, F_GETFL) == -1) |
2384 | 0 | fatal("LISTEN_FDS set but fd 3 unavailable"); |
2385 | 0 | pid = (int)strtonum(pidstr, 1, INT_MAX, &errstr); |
2386 | 0 | if (errstr != NULL) |
2387 | 0 | fatal("invalid LISTEN_PID: %s", errstr); |
2388 | 0 | if (pid != getpid()) |
2389 | 0 | fatal("bad LISTEN_PID: %d vs pid %d", pid, getpid()); |
2390 | 0 | debug("using socket activation on fd=3"); |
2391 | 0 | sock = 3; |
2392 | 0 | } |
2393 | | |
2394 | | /* Otherwise, create private directory for agent socket */ |
2395 | 0 | if (sock == -1) { |
2396 | 0 | if (agentsocket == NULL) { |
2397 | 0 | mktemp_proto(socket_dir, sizeof(socket_dir)); |
2398 | 0 | if (mkdtemp(socket_dir) == NULL) { |
2399 | 0 | perror("mkdtemp: private socket dir"); |
2400 | 0 | exit(1); |
2401 | 0 | } |
2402 | 0 | snprintf(socket_name, sizeof socket_name, |
2403 | 0 | "%s/agent.%ld", socket_dir, |
2404 | 0 | (long)parent_pid); |
2405 | 0 | } else { |
2406 | | /* Try to use specified agent socket */ |
2407 | 0 | socket_dir[0] = '\0'; |
2408 | 0 | strlcpy(socket_name, agentsocket, sizeof socket_name); |
2409 | 0 | } |
2410 | 0 | } |
2411 | | |
2412 | 0 | closefrom(sock == -1 ? STDERR_FILENO + 1 : sock + 1); |
2413 | | |
2414 | | /* |
2415 | | * Create socket early so it will exist before command gets run from |
2416 | | * the parent. |
2417 | | */ |
2418 | 0 | if (sock == -1) { |
2419 | 0 | prev_mask = umask(0177); |
2420 | 0 | sock = unix_listener(socket_name, SSH_LISTEN_BACKLOG, 0); |
2421 | 0 | if (sock < 0) { |
2422 | | /* XXX - unix_listener() calls error() not perror() */ |
2423 | 0 | *socket_name = '\0'; /* Don't unlink existing file */ |
2424 | 0 | cleanup_exit(1); |
2425 | 0 | } |
2426 | 0 | umask(prev_mask); |
2427 | 0 | } |
2428 | | |
2429 | | /* |
2430 | | * Fork, and have the parent execute the command, if any, or present |
2431 | | * the socket data. The child continues as the authentication agent. |
2432 | | */ |
2433 | 0 | if (D_flag || d_flag) { |
2434 | 0 | log_init(__progname, |
2435 | 0 | d_flag ? SYSLOG_LEVEL_DEBUG3 : SYSLOG_LEVEL_INFO, |
2436 | 0 | SYSLOG_FACILITY_AUTH, 1); |
2437 | 0 | if (socket_name[0] != '\0') { |
2438 | 0 | format = c_flag ? |
2439 | 0 | "setenv %s %s;\n" : "%s=%s; export %s;\n"; |
2440 | 0 | printf(format, SSH_AUTHSOCKET_ENV_NAME, socket_name, |
2441 | 0 | SSH_AUTHSOCKET_ENV_NAME); |
2442 | 0 | printf("echo Agent pid %ld;\n", (long)parent_pid); |
2443 | 0 | fflush(stdout); |
2444 | 0 | } |
2445 | 0 | goto skip; |
2446 | 0 | } |
2447 | 0 | pid = fork(); |
2448 | 0 | if (pid == -1) { |
2449 | 0 | perror("fork"); |
2450 | 0 | cleanup_exit(1); |
2451 | 0 | } |
2452 | 0 | if (pid != 0) { /* Parent - execute the given command. */ |
2453 | 0 | close(sock); |
2454 | 0 | snprintf(pidstrbuf, sizeof pidstrbuf, "%ld", (long)pid); |
2455 | 0 | if (ac == 0) { |
2456 | 0 | format = c_flag ? "setenv %s %s;\n" : "%s=%s; export %s;\n"; |
2457 | 0 | printf(format, SSH_AUTHSOCKET_ENV_NAME, socket_name, |
2458 | 0 | SSH_AUTHSOCKET_ENV_NAME); |
2459 | 0 | printf(format, SSH_AGENTPID_ENV_NAME, pidstrbuf, |
2460 | 0 | SSH_AGENTPID_ENV_NAME); |
2461 | 0 | printf("echo Agent pid %ld;\n", (long)pid); |
2462 | 0 | exit(0); |
2463 | 0 | } |
2464 | 0 | if (setenv(SSH_AUTHSOCKET_ENV_NAME, socket_name, 1) == -1 || |
2465 | 0 | setenv(SSH_AGENTPID_ENV_NAME, pidstrbuf, 1) == -1) { |
2466 | 0 | perror("setenv"); |
2467 | 0 | exit(1); |
2468 | 0 | } |
2469 | 0 | execvp(av[0], av); |
2470 | 0 | perror(av[0]); |
2471 | 0 | exit(1); |
2472 | 0 | } |
2473 | | /* child */ |
2474 | 0 | log_init(__progname, SYSLOG_LEVEL_INFO, SYSLOG_FACILITY_AUTH, 0); |
2475 | |
|
2476 | 0 | if (setsid() == -1) { |
2477 | 0 | error("setsid: %s", strerror(errno)); |
2478 | 0 | cleanup_exit(1); |
2479 | 0 | } |
2480 | | |
2481 | 0 | (void)chdir("/"); |
2482 | 0 | if (stdfd_devnull(1, 1, 1) == -1) |
2483 | 0 | error_f("stdfd_devnull failed"); |
2484 | |
|
2485 | 0 | #ifdef HAVE_SETRLIMIT |
2486 | | /* deny core dumps, since memory contains unencrypted private keys */ |
2487 | 0 | rlim.rlim_cur = rlim.rlim_max = 0; |
2488 | 0 | if (setrlimit(RLIMIT_CORE, &rlim) == -1) { |
2489 | 0 | error("setrlimit RLIMIT_CORE: %s", strerror(errno)); |
2490 | 0 | cleanup_exit(1); |
2491 | 0 | } |
2492 | 0 | #endif |
2493 | | |
2494 | 0 | skip: |
2495 | |
|
2496 | 0 | cleanup_pid = getpid(); |
2497 | |
|
2498 | 0 | #ifdef ENABLE_PKCS11 |
2499 | 0 | pkcs11_init(0); |
2500 | 0 | #endif |
2501 | 0 | new_socket(AUTH_SOCKET, sock); |
2502 | 0 | if (ac > 0) |
2503 | 0 | parent_alive_interval = 10; |
2504 | 0 | idtab_init(); |
2505 | 0 | ssh_signal(SIGPIPE, SIG_IGN); |
2506 | 0 | ssh_signal(SIGINT, (d_flag | D_flag) ? cleanup_handler : SIG_IGN); |
2507 | 0 | ssh_signal(SIGHUP, cleanup_handler); |
2508 | 0 | ssh_signal(SIGTERM, cleanup_handler); |
2509 | 0 | ssh_signal(SIGUSR1, keydrop_handler); |
2510 | |
|
2511 | 0 | sigemptyset(&nsigset); |
2512 | 0 | sigaddset(&nsigset, SIGINT); |
2513 | 0 | sigaddset(&nsigset, SIGHUP); |
2514 | 0 | sigaddset(&nsigset, SIGTERM); |
2515 | 0 | sigaddset(&nsigset, SIGUSR1); |
2516 | |
|
2517 | 0 | if (pledge("stdio rpath cpath unix id proc exec", NULL) == -1) |
2518 | 0 | fatal("%s: pledge: %s", __progname, strerror(errno)); |
2519 | 0 | platform_pledge_agent(); |
2520 | |
|
2521 | 0 | while (1) { |
2522 | 0 | sigprocmask(SIG_BLOCK, &nsigset, &osigset); |
2523 | 0 | if (signalled_exit != 0) { |
2524 | 0 | logit("exiting on signal %d", (int)signalled_exit); |
2525 | 0 | cleanup_exit(2); |
2526 | 0 | } |
2527 | 0 | if (signalled_keydrop) { |
2528 | 0 | logit("signal %d received; removing all keys", |
2529 | 0 | signalled_keydrop); |
2530 | 0 | remove_all_identities(); |
2531 | 0 | signalled_keydrop = 0; |
2532 | 0 | } |
2533 | 0 | ptimeout_init(&timeout); |
2534 | 0 | prepare_poll(&pfd, &npfd, &timeout, maxfds); |
2535 | 0 | result = ppoll(pfd, npfd, ptimeout_get_tsp(&timeout), &osigset); |
2536 | 0 | sigprocmask(SIG_SETMASK, &osigset, NULL); |
2537 | 0 | saved_errno = errno; |
2538 | 0 | if (parent_alive_interval != 0) |
2539 | 0 | check_parent_exists(); |
2540 | 0 | (void) reaper(); /* remove expired keys */ |
2541 | 0 | if (result == -1) { |
2542 | 0 | if (saved_errno == EINTR) |
2543 | 0 | continue; |
2544 | 0 | fatal("poll: %s", strerror(saved_errno)); |
2545 | 0 | } else if (result > 0) |
2546 | 0 | after_poll(pfd, npfd, maxfds); |
2547 | 0 | } |
2548 | | /* NOTREACHED */ |
2549 | 0 | } |