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