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