blob: a9d2a1426b27c5b388336f216099b86d0f4f3f1e [file] [log] [blame]
Damien Miller792c5111999-10-29 09:47:09 +10001/* $OpenBSD: ssh-agent.c,v 1.15 1999/10/28 08:43:10 markus Exp $ */
2
Damien Millerd4a8b7e1999-10-27 13:42:43 +10003/*
4
5ssh-agent.c
6
7Author: Tatu Ylonen <ylo@cs.hut.fi>
8
9Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
10 All rights reserved
11
12Created: Wed Mar 29 03:46:59 1995 ylo
13
14The authentication agent program.
15
16*/
17
Damien Miller7f6ea021999-10-28 13:25:17 +100018#include "config.h"
Damien Millerd4a8b7e1999-10-27 13:42:43 +100019#include "includes.h"
Damien Miller792c5111999-10-29 09:47:09 +100020RCSID("$OpenBSD: ssh-agent.c,v 1.15 1999/10/28 08:43:10 markus Exp $");
Damien Millerd4a8b7e1999-10-27 13:42:43 +100021
22#include "ssh.h"
23#include "rsa.h"
24#include "authfd.h"
25#include "buffer.h"
26#include "bufaux.h"
27#include "xmalloc.h"
28#include "packet.h"
29#include "getput.h"
30#include "mpaux.h"
31
Damien Miller7f6ea021999-10-28 13:25:17 +100032#ifdef HAVE_OPENSSL
Damien Millerd4a8b7e1999-10-27 13:42:43 +100033#include <openssl/md5.h>
Damien Miller7f6ea021999-10-28 13:25:17 +100034#endif
35#ifdef HAVE_SSL
36#include <ssl/md5.h>
37#endif
Damien Millerd4a8b7e1999-10-27 13:42:43 +100038
39typedef struct
40{
41 int fd;
42 enum { AUTH_UNUSED, AUTH_SOCKET, AUTH_CONNECTION } type;
43 Buffer input;
44 Buffer output;
45} SocketEntry;
46
47unsigned int sockets_alloc = 0;
48SocketEntry *sockets = NULL;
49
50typedef struct
51{
52 RSA *key;
53 char *comment;
54} Identity;
55
56unsigned int num_identities = 0;
57Identity *identities = NULL;
58
59int max_fd = 0;
60
61/* pid of shell == parent of agent */
62int parent_pid = -1;
63
64/* pathname and directory for AUTH_SOCKET */
65char socket_name[1024];
66char socket_dir[1024];
67
68void
69process_request_identity(SocketEntry *e)
70{
71 Buffer msg;
72 int i;
73
74 buffer_init(&msg);
75 buffer_put_char(&msg, SSH_AGENT_RSA_IDENTITIES_ANSWER);
76 buffer_put_int(&msg, num_identities);
77 for (i = 0; i < num_identities; i++)
78 {
79 buffer_put_int(&msg, BN_num_bits(identities[i].key->n));
80 buffer_put_bignum(&msg, identities[i].key->e);
81 buffer_put_bignum(&msg, identities[i].key->n);
82 buffer_put_string(&msg, identities[i].comment,
83 strlen(identities[i].comment));
84 }
85 buffer_put_int(&e->output, buffer_len(&msg));
86 buffer_append(&e->output, buffer_ptr(&msg), buffer_len(&msg));
87 buffer_free(&msg);
88}
89
90void
91process_authentication_challenge(SocketEntry *e)
92{
93 int i, pub_bits, len;
94 BIGNUM *pub_e, *pub_n, *challenge;
95 Buffer msg;
96 MD5_CTX md;
97 unsigned char buf[32], mdbuf[16], session_id[16];
98 unsigned int response_type;
99
100 buffer_init(&msg);
101 pub_e = BN_new();
102 pub_n = BN_new();
103 challenge = BN_new();
104 pub_bits = buffer_get_int(&e->input);
105 buffer_get_bignum(&e->input, pub_e);
106 buffer_get_bignum(&e->input, pub_n);
107 buffer_get_bignum(&e->input, challenge);
108 if (buffer_len(&e->input) == 0)
109 {
110 /* Compatibility code for old servers. */
111 memset(session_id, 0, 16);
112 response_type = 0;
113 }
114 else
115 {
116 /* New code. */
117 buffer_get(&e->input, (char *)session_id, 16);
118 response_type = buffer_get_int(&e->input);
119 }
120 for (i = 0; i < num_identities; i++)
121 if (pub_bits == BN_num_bits(identities[i].key->n) &&
122 BN_cmp(pub_e, identities[i].key->e) == 0 &&
123 BN_cmp(pub_n, identities[i].key->n) == 0)
124 {
125 /* Decrypt the challenge using the private key. */
126 rsa_private_decrypt(challenge, challenge, identities[i].key);
127
128 /* Compute the desired response. */
129 switch (response_type)
130 {
131 case 0: /* As of protocol 1.0 */
132 /* This response type is no longer supported. */
133 log("Compatibility with ssh protocol 1.0 no longer supported.");
134 buffer_put_char(&msg, SSH_AGENT_FAILURE);
135 goto send;
136
137 case 1: /* As of protocol 1.1 */
138 /* The response is MD5 of decrypted challenge plus session id. */
139 len = BN_num_bytes(challenge);
140 assert(len <= 32 && len);
141 memset(buf, 0, 32);
142 BN_bn2bin(challenge, buf + 32 - len);
143 MD5_Init(&md);
144 MD5_Update(&md, buf, 32);
145 MD5_Update(&md, session_id, 16);
146 MD5_Final(mdbuf, &md);
147 break;
148
149 default:
150 fatal("process_authentication_challenge: bad response_type %d",
151 response_type);
152 break;
153 }
154
155 /* Send the response. */
156 buffer_put_char(&msg, SSH_AGENT_RSA_RESPONSE);
157 for (i = 0; i < 16; i++)
158 buffer_put_char(&msg, mdbuf[i]);
159
160 goto send;
161 }
162 /* Unknown identity. Send failure. */
163 buffer_put_char(&msg, SSH_AGENT_FAILURE);
164 send:
165 buffer_put_int(&e->output, buffer_len(&msg));
166 buffer_append(&e->output, buffer_ptr(&msg),
167 buffer_len(&msg));
168 buffer_free(&msg);
169 BN_clear_free(pub_e);
170 BN_clear_free(pub_n);
171 BN_clear_free(challenge);
172}
173
174void
175process_remove_identity(SocketEntry *e)
176{
177 unsigned int bits;
178 unsigned int i;
179 BIGNUM *dummy, *n;
180
181 dummy = BN_new();
182 n = BN_new();
183
184 /* Get the key from the packet. */
185 bits = buffer_get_int(&e->input);
186 buffer_get_bignum(&e->input, dummy);
187 buffer_get_bignum(&e->input, n);
188
189 /* Check if we have the key. */
190 for (i = 0; i < num_identities; i++)
191 if (BN_cmp(identities[i].key->n, n) == 0)
192 {
193 /* We have this key. Free the old key. Since we don\'t want to leave
194 empty slots in the middle of the array, we actually free the
195 key there and copy data from the last entry. */
196 RSA_free(identities[i].key);
197 xfree(identities[i].comment);
198 if (i < num_identities - 1)
199 identities[i] = identities[num_identities - 1];
200 num_identities--;
201 BN_clear_free(dummy);
202 BN_clear_free(n);
203
204 /* Send success. */
205 buffer_put_int(&e->output, 1);
206 buffer_put_char(&e->output, SSH_AGENT_SUCCESS);
207 return;
208 }
209 /* We did not have the key. */
210 BN_clear(dummy);
211 BN_clear(n);
212
213 /* Send failure. */
214 buffer_put_int(&e->output, 1);
215 buffer_put_char(&e->output, SSH_AGENT_FAILURE);
216}
217
218/* Removes all identities from the agent. */
219
220void
221process_remove_all_identities(SocketEntry *e)
222{
223 unsigned int i;
224
225 /* Loop over all identities and clear the keys. */
226 for (i = 0; i < num_identities; i++)
227 {
228 RSA_free(identities[i].key);
229 xfree(identities[i].comment);
230 }
231
232 /* Mark that there are no identities. */
233 num_identities = 0;
234
235 /* Send success. */
236 buffer_put_int(&e->output, 1);
237 buffer_put_char(&e->output, SSH_AGENT_SUCCESS);
238 return;
239}
240
241/* Adds an identity to the agent. */
242
243void
244process_add_identity(SocketEntry *e)
245{
246 RSA *k;
247 int i;
248 BIGNUM *aux;
249 BN_CTX *ctx;
250
251 if (num_identities == 0)
252 identities = xmalloc(sizeof(Identity));
253 else
254 identities = xrealloc(identities, (num_identities + 1) * sizeof(Identity));
255
256 identities[num_identities].key = RSA_new();
257 k = identities[num_identities].key;
258 buffer_get_int(&e->input); /* bits */
259 k->n = BN_new();
260 buffer_get_bignum(&e->input, k->n);
261 k->e = BN_new();
262 buffer_get_bignum(&e->input, k->e);
263 k->d = BN_new();
264 buffer_get_bignum(&e->input, k->d);
265 k->iqmp = BN_new();
266 buffer_get_bignum(&e->input, k->iqmp);
267 /* SSH and SSL have p and q swapped */
268 k->q = BN_new();
269 buffer_get_bignum(&e->input, k->q); /* p */
270 k->p = BN_new();
271 buffer_get_bignum(&e->input, k->p); /* q */
272
273 /* Generate additional parameters */
274 aux = BN_new();
275 ctx = BN_CTX_new();
276
277 BN_sub(aux, k->q, BN_value_one());
278 k->dmq1 = BN_new();
279 BN_mod(k->dmq1, k->d, aux, ctx);
280
281 BN_sub(aux, k->p, BN_value_one());
282 k->dmp1 = BN_new();
283 BN_mod(k->dmp1, k->d, aux, ctx);
284
285 BN_clear_free(aux);
286 BN_CTX_free(ctx);
287
288 identities[num_identities].comment = buffer_get_string(&e->input, NULL);
289
290 /* Check if we already have the key. */
291 for (i = 0; i < num_identities; i++)
292 if (BN_cmp(identities[i].key->n, k->n) == 0)
293 {
294 /* We already have this key. Clear and free the new data and
295 return success. */
296 RSA_free(k);
297 xfree(identities[num_identities].comment);
298
299 /* Send success. */
300 buffer_put_int(&e->output, 1);
301 buffer_put_char(&e->output, SSH_AGENT_SUCCESS);
302 return;
303 }
304
305 /* Increment the number of identities. */
306 num_identities++;
307
308 /* Send a success message. */
309 buffer_put_int(&e->output, 1);
310 buffer_put_char(&e->output, SSH_AGENT_SUCCESS);
311}
312
313void
314process_message(SocketEntry *e)
315{
316 unsigned int msg_len;
317 unsigned int type;
318 unsigned char *cp;
319 if (buffer_len(&e->input) < 5)
320 return; /* Incomplete message. */
321 cp = (unsigned char *)buffer_ptr(&e->input);
322 msg_len = GET_32BIT(cp);
323 if (msg_len > 256 * 1024)
324 {
325 shutdown(e->fd, SHUT_RDWR);
326 close(e->fd);
327 e->type = AUTH_UNUSED;
328 return;
329 }
330 if (buffer_len(&e->input) < msg_len + 4)
331 return;
332 buffer_consume(&e->input, 4);
333 type = buffer_get_char(&e->input);
334
335 switch (type)
336 {
337 case SSH_AGENTC_REQUEST_RSA_IDENTITIES:
338 process_request_identity(e);
339 break;
340 case SSH_AGENTC_RSA_CHALLENGE:
341 process_authentication_challenge(e);
342 break;
343 case SSH_AGENTC_ADD_RSA_IDENTITY:
344 process_add_identity(e);
345 break;
346 case SSH_AGENTC_REMOVE_RSA_IDENTITY:
347 process_remove_identity(e);
348 break;
349 case SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
350 process_remove_all_identities(e);
351 break;
352 default:
353 /* Unknown message. Respond with failure. */
354 error("Unknown message %d", type);
355 buffer_clear(&e->input);
356 buffer_put_int(&e->output, 1);
357 buffer_put_char(&e->output, SSH_AGENT_FAILURE);
358 break;
359 }
360}
361
362void
363new_socket(int type, int fd)
364{
365 unsigned int i, old_alloc;
366 if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
367 error("fcntl O_NONBLOCK: %s", strerror(errno));
368
369 if (fd > max_fd)
370 max_fd = fd;
371
372 for (i = 0; i < sockets_alloc; i++)
373 if (sockets[i].type == AUTH_UNUSED)
374 {
375 sockets[i].fd = fd;
376 sockets[i].type = type;
377 buffer_init(&sockets[i].input);
378 buffer_init(&sockets[i].output);
379 return;
380 }
381 old_alloc = sockets_alloc;
382 sockets_alloc += 10;
383 if (sockets)
384 sockets = xrealloc(sockets, sockets_alloc * sizeof(sockets[0]));
385 else
386 sockets = xmalloc(sockets_alloc * sizeof(sockets[0]));
387 for (i = old_alloc; i < sockets_alloc; i++)
388 sockets[i].type = AUTH_UNUSED;
389 sockets[old_alloc].type = type;
390 sockets[old_alloc].fd = fd;
391 buffer_init(&sockets[old_alloc].input);
392 buffer_init(&sockets[old_alloc].output);
393}
394
395void
396prepare_select(fd_set *readset, fd_set *writeset)
397{
398 unsigned int i;
399 for (i = 0; i < sockets_alloc; i++)
400 switch (sockets[i].type)
401 {
402 case AUTH_SOCKET:
403 case AUTH_CONNECTION:
404 FD_SET(sockets[i].fd, readset);
405 if (buffer_len(&sockets[i].output) > 0)
406 FD_SET(sockets[i].fd, writeset);
407 break;
408 case AUTH_UNUSED:
409 break;
410 default:
411 fatal("Unknown socket type %d", sockets[i].type);
412 break;
413 }
414}
415
416void after_select(fd_set *readset, fd_set *writeset)
417{
418 unsigned int i;
419 int len, sock;
420 char buf[1024];
421 struct sockaddr_un sunaddr;
422
423 for (i = 0; i < sockets_alloc; i++)
424 switch (sockets[i].type)
425 {
426 case AUTH_UNUSED:
427 break;
428 case AUTH_SOCKET:
429 if (FD_ISSET(sockets[i].fd, readset))
430 {
431 len = sizeof(sunaddr);
432 sock = accept(sockets[i].fd, (struct sockaddr *)&sunaddr, &len);
433 if (sock < 0)
434 {
435 perror("accept from AUTH_SOCKET");
436 break;
437 }
438 new_socket(AUTH_CONNECTION, sock);
439 }
440 break;
441 case AUTH_CONNECTION:
442 if (buffer_len(&sockets[i].output) > 0 &&
443 FD_ISSET(sockets[i].fd, writeset))
444 {
445 len = write(sockets[i].fd, buffer_ptr(&sockets[i].output),
446 buffer_len(&sockets[i].output));
447 if (len <= 0)
448 {
449 shutdown(sockets[i].fd, SHUT_RDWR);
450 close(sockets[i].fd);
451 sockets[i].type = AUTH_UNUSED;
452 break;
453 }
454 buffer_consume(&sockets[i].output, len);
455 }
456 if (FD_ISSET(sockets[i].fd, readset))
457 {
458 len = read(sockets[i].fd, buf, sizeof(buf));
459 if (len <= 0)
460 {
461 shutdown(sockets[i].fd, SHUT_RDWR);
462 close(sockets[i].fd);
463 sockets[i].type = AUTH_UNUSED;
464 break;
465 }
466 buffer_append(&sockets[i].input, buf, len);
467 process_message(&sockets[i]);
468 }
469 break;
470 default:
471 fatal("Unknown type %d", sockets[i].type);
472 }
473}
474
475void
476check_parent_exists(int sig)
477{
478 if (kill(parent_pid, 0) < 0)
479 {
480 /* printf("Parent has died - Authentication agent exiting.\n"); */
481 exit(1);
482 }
483 signal(SIGALRM, check_parent_exists);
484 alarm(10);
485}
486
Damien Miller792c5111999-10-29 09:47:09 +1000487void
488cleanup_socket(void)
489{
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000490 remove(socket_name);
491 rmdir(socket_dir);
492}
493
Damien Miller792c5111999-10-29 09:47:09 +1000494void
495cleanup_exit(int i)
496{
497 cleanup_socket();
498 exit(i);
499}
500
501void
502usage()
503{
504 extern char *__progname;
505
506 fprintf(stderr, "ssh-agent version %s\n", SSH_VERSION);
507 fprintf(stderr, "Usage: %s [-c | -s] [-k] [command {args...]]\n",
508 __progname);
509 exit(1);
510}
511
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000512int
513main(int ac, char **av)
514{
515 fd_set readset, writeset;
Damien Miller792c5111999-10-29 09:47:09 +1000516 int sock, c_flag = 0, k_flag = 0, s_flag = 0, ch;
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000517 struct sockaddr_un sunaddr;
Damien Miller792c5111999-10-29 09:47:09 +1000518 pid_t pid;
519 char *shell, *format, *pidstr, pidstrbuf[1 + 3 * sizeof pid];
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000520
521 /* check if RSA support exists */
522 if (rsa_alive() == 0) {
523 extern char *__progname;
524 fprintf(stderr,
525 "%s: no RSA support in libssl and libcrypto. See ssl(8).\n",
526 __progname);
527 exit(1);
528 }
529
Damien Miller792c5111999-10-29 09:47:09 +1000530 while ((ch = getopt(ac, av, "cks")) != -1)
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000531 {
Damien Miller792c5111999-10-29 09:47:09 +1000532 switch (ch)
533 {
534 case 'c':
535 if (s_flag)
536 usage();
537 c_flag++;
538 break;
539 case 'k':
540 k_flag++;
541 break;
542 case 's':
543 if (c_flag)
544 usage();
545 s_flag++;
546 break;
547 default:
548 usage();
549 }
550 }
551 ac -= optind;
552 av += optind;
553
554 if (ac > 0 && (c_flag || k_flag || s_flag))
555 usage();
556
557 if (ac == 0 && !c_flag && !k_flag && !s_flag)
558 {
559 shell = getenv("SHELL");
560 if (shell != NULL && strncmp(shell + strlen(shell) - 3, "csh", 3) == 0)
561 c_flag = 1;
562 }
563
564 if (k_flag)
565 {
566 pidstr = getenv(SSH_AGENTPID_ENV_NAME);
567 if (pidstr == NULL)
568 {
569 fprintf(stderr, "%s not set, cannot kill agent\n",
570 SSH_AGENTPID_ENV_NAME);
571 exit(1);
572 }
573 pid = atoi(pidstr);
574 if (pid < 1) /* XXX PID_MAX check too */
575 {
576 fprintf(stderr, "%s=\"%s\", which is not a good PID\n",
577 SSH_AGENTPID_ENV_NAME, pidstr);
578 exit(1);
579 }
580 if (kill(pid, SIGTERM) == -1)
581 {
582 perror("kill");
583 exit(1);
584 }
585 format = c_flag ? "unsetenv %s;\n" : "unset %s;\n";
586 printf(format, SSH_AUTHSOCKET_ENV_NAME);
587 printf(format, SSH_AGENTPID_ENV_NAME);
588 printf("echo Agent pid %d killed;\n", pid);
589 exit(0);
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000590 }
591
592 parent_pid = getpid();
593
594 /* Create private directory for agent socket */
595 strlcpy(socket_dir, "/tmp/ssh-XXXXXXXX", sizeof socket_dir);
596 if (mkdtemp(socket_dir) == NULL) {
597 perror("mkdtemp: private socket dir");
598 exit(1);
599 }
Damien Miller792c5111999-10-29 09:47:09 +1000600 snprintf(socket_name, sizeof socket_name, "%s/agent.%d", socket_dir,
601 parent_pid);
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000602
Damien Miller792c5111999-10-29 09:47:09 +1000603 /* Create socket early so it will exist before command gets run from
604 the parent. */
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000605 sock = socket(AF_UNIX, SOCK_STREAM, 0);
606 if (sock < 0)
607 {
608 perror("socket");
Damien Miller792c5111999-10-29 09:47:09 +1000609 cleanup_exit(1);
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000610 }
611 memset(&sunaddr, 0, sizeof(sunaddr));
612 sunaddr.sun_family = AF_UNIX;
613 strlcpy(sunaddr.sun_path, socket_name, sizeof(sunaddr.sun_path));
614 if (bind(sock, (struct sockaddr *)&sunaddr, sizeof(sunaddr)) < 0)
615 {
616 perror("bind");
Damien Miller792c5111999-10-29 09:47:09 +1000617 cleanup_exit(1);
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000618 }
619 if (listen(sock, 5) < 0)
620 {
621 perror("listen");
Damien Miller792c5111999-10-29 09:47:09 +1000622 cleanup_exit(1);
623 }
624
625 /* Fork, and have the parent execute the command, if any, or present the
626 socket data. The child continues as the authentication agent. */
627 pid = fork();
628 if (pid == -1)
629 {
630 perror("fork");
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000631 exit(1);
632 }
Damien Miller792c5111999-10-29 09:47:09 +1000633 if (pid != 0)
634 { /* Parent - execute the given command. */
635 close(sock);
636 snprintf(pidstrbuf, sizeof pidstrbuf, "%d", pid);
637 if (ac == 0)
638 {
639 format = c_flag ? "setenv %s %s;\n" : "%s=%s; export %s;\n";
640 printf(format, SSH_AUTHSOCKET_ENV_NAME, socket_name,
641 SSH_AUTHSOCKET_ENV_NAME);
642 printf(format, SSH_AGENTPID_ENV_NAME, pidstrbuf,
643 SSH_AGENTPID_ENV_NAME);
644 printf("echo Agent pid %d;\n", pid);
645 exit(0);
646 }
647
648 setenv(SSH_AUTHSOCKET_ENV_NAME, socket_name, 1);
649 setenv(SSH_AGENTPID_ENV_NAME, pidstrbuf, 1);
650 execvp(av[0], av);
651 perror(av[0]);
652 exit(1);
653 }
654
655 close(0);
656 close(1);
657 close(2);
658
659 if (ac == 0 && setsid() == -1)
660 cleanup_exit(1);
661
662 if (atexit(cleanup_socket) < 0)
663 cleanup_exit(1);
664
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000665 new_socket(AUTH_SOCKET, sock);
Damien Miller792c5111999-10-29 09:47:09 +1000666 if (ac > 0)
667 {
668 signal(SIGALRM, check_parent_exists);
669 alarm(10);
670 }
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000671
672 signal(SIGINT, SIG_IGN);
Damien Miller792c5111999-10-29 09:47:09 +1000673 signal(SIGPIPE, SIG_IGN);
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000674 while (1)
675 {
676 FD_ZERO(&readset);
677 FD_ZERO(&writeset);
678 prepare_select(&readset, &writeset);
679 if (select(max_fd + 1, &readset, &writeset, NULL, NULL) < 0)
680 {
681 if (errno == EINTR)
682 continue;
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000683 exit(1);
684 }
685 after_select(&readset, &writeset);
686 }
687 /*NOTREACHED*/
688}