Fixed a segfault during an SSH connection failure.
[libpwmd.git] / src / pwmc.c
blob695b1137916463cb2c742e43dde79d569916a6c0
1 #define DEBUG 1
2 /* vim:tw=78:ts=8:sw=4:set ft=c: */
3 /*
4 Copyright (C) 2007-2009 Ben Kibbey <bjk@luxsci.net>
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <unistd.h>
23 #include <err.h>
24 #include <errno.h>
25 #include <string.h>
26 #include <libpwmd.h>
27 #include <assuan.h>
28 #ifdef DEBUG
29 #include <sys/select.h>
30 #endif
31 #include <fcntl.h>
33 #ifdef HAVE_CONFIG_H
34 #include <config.h>
35 #endif
37 #ifdef HAVE_LOCALE_H
38 #include <locale.h>
39 #endif
41 #ifdef HAVE_GETOPT_LONG
42 #ifdef HAVE_GETOPT_H
43 #include <getopt.h>
44 #endif
45 #else
46 #include "getopt_long.h"
47 #endif
49 #include "gettext.h"
50 #define N_(msgid) gettext(msgid)
52 #include "mem.h"
54 #define DEFAULT_PORT 22
55 pwm_t *pwm;
57 static void show_error(gpg_error_t error)
59 fprintf(stderr, "ERR %i %s\n", gpg_err_code(error), pwmd_strerror(error));
62 static void usage(const char *pn, int status)
64 fprintf(status == EXIT_FAILURE ? stderr : stdout, N_(
65 "Read a PWMD protocol command from standard input.\n\n"
66 "Usage: pwmc [options] [file]\n"
67 #ifdef DEBUG
68 " --debug <N>\n"
69 " pinentry method (0=pwmd, 1=libpwmd, 2=pwmd async, "
70 "3=libpwmd async)\n"
71 #endif
72 "\n"
73 " --tries <N>\n"
74 " number of pinentry tries before failing (3)\n"
75 "\n"
76 #ifdef WITH_TCP
77 " --host, -h <hostname>\n"
78 " connect to the specified hostname\n"
79 "\n"
80 " --port\n"
81 " alterate port (22)\n"
82 "\n"
83 " --user\n"
84 " SSH username (default is the invoking user)\n"
85 "\n"
86 " --identity, -i <filename>\n"
87 " SSH identity file\n"
88 "\n"
89 " --known-hosts, -k <filename>\n"
90 " known host's file (for server validation)\n"
91 "\n"
92 " --get-hostkey, -g\n"
93 " retrieve the remote SSH host key and exit\n"
94 "\n"
95 " --ipv4, -4\n"
96 " try connecting via IPv4 only\n"
97 "\n"
98 " --ipv6, -6\n"
99 " try connecting via IPv6 only\n"
100 "\n"
101 #endif
102 " --url <string>\n"
103 " a url string to parse\n"
104 "\n"
105 " --timeout <seconds>\n"
106 " pinentry timeout\n"
107 "\n"
108 " --no-status\n"
109 " disable showing of status messages from the server\n"
110 "\n"
111 " --name, -n <string>\n"
112 " set the client name\n"
113 "\n"
114 " --socket <filename>\n"
115 " local socket to connect to (~/.pwmd/socket)\n"
116 "\n"
117 " --passphrase, -P <string>\n"
118 " passphrase to use (disables pinentry use)\n"
119 "\n"
120 " --pinentry <path>\n"
121 " the full path to the pinentry binary (server default)\n"
122 "\n"
123 " --ttyname, -y <path>\n"
124 " tty that pinentry will use\n"
125 "\n"
126 " --ttytype, -t <string>\n"
127 " pinentry terminal type (default is TERM)\n"
128 "\n"
129 " --display, -d\n"
130 " pinentry display (default is DISPLAY)\n"
131 "\n"
132 " --lc-ctype <string>\n"
133 " locale setting for pinentry\n"
134 "\n"
135 " --lc-messages <string>\n"
136 " locale setting for pinentry\n"
137 "\n"
138 " --output-fd <FD>\n"
139 " redirect command output to the specified file descriptor\n"
140 "\n"
141 " --inquire-fd <FD>\n"
142 " read inquire data from the specified file descriptor\n"
143 "\n"
144 " --save, -S\n"
145 " send the SAVE command before exiting\n"
146 "\n"
147 " --iterations, -I <N>\n"
148 " encrypt with the specified number of iterations when saving\n"
149 "\n"
150 " --version\n"
151 " --help\n"));
152 fprintf(status == EXIT_FAILURE ? stderr : stdout, N_(
153 "\n"
154 "A url string (specified with --url) may be in the form of:\n"
155 " socket://[path/to/socket]\n"
156 #ifdef WITH_TCP
157 " ssh[46]://[username@]hostname[:port],identity,known_hosts\n"
158 #endif
160 exit(status);
163 struct inquire_s {
164 FILE *fp;
165 char *data;
168 static gpg_error_t do_inquire(void *data, const char *keyword, gpg_error_t rc,
169 char **result, size_t *result_len)
171 int c;
172 static char buf[ASSUAN_LINELENGTH];
173 char *p;
174 size_t len = 0;
175 struct inquire_s *inq = (struct inquire_s *)data;
177 if (rc) {
178 memset(buf, 0, sizeof(buf));
179 return rc;
182 buf[0] = 0;
183 p = buf;
185 if (inq->data) {
186 snprintf(buf, sizeof(buf), "%s", inq->data);
187 pwmd_free(inq->data);
188 inq->data = NULL;
189 len = strlen(buf);
190 p = buf + len;
193 while ((c = fgetc(inq->fp)) != EOF) {
194 if (len == sizeof(buf)) {
195 ungetc(c, inq->fp);
196 break;
199 *p++ = c;
200 len++;
203 if (!buf[0]) {
204 memset(buf, 0, sizeof(buf));
205 return GPG_ERR_EOF;
208 *result = buf;
209 *result_len = len;
210 return 0;
213 static int status_msg_cb(void *data, const char *line)
215 fprintf(stderr, "%s\n", line);
216 return 0;
219 static gpg_error_t process_cmd(pwm_t *pwm, char **result, int input)
221 gpg_error_t rc;
222 pwmd_async_t s;
224 do {
225 int i, n;
226 fd_set rfds;
227 int nfds = 5;
228 pwmd_fd_t pfds[nfds];
230 FD_ZERO(&rfds);
231 rc = pwmd_get_fds(pwm, pfds, &nfds);
233 if (rc)
234 return rc;
236 if (!nfds)
237 return 0;
239 for (i = 0, n = 0; i < nfds; i++) {
240 FD_SET(pfds[i].fd, &rfds);
241 n = pfds[i].fd > n ? pfds[i].fd : n;
244 if (input)
245 FD_SET(STDIN_FILENO, &rfds);
247 nfds = select(n+1, &rfds, NULL, NULL, NULL);
249 if (nfds == -1) {
250 rc = gpg_error_from_errno(errno);
251 return rc;
254 if (input && FD_ISSET(STDIN_FILENO, &rfds))
255 return 0;
257 s = pwmd_process(pwm, &rc, result);
258 } while (s == ASYNC_PROCESS);
260 return rc;
263 int main(int argc, char *argv[])
265 int opt;
266 char *password = NULL;
267 char *filename = NULL;
268 char *socketpath = NULL;
269 char command[ASSUAN_LINELENGTH], *p;
270 int ret = EXIT_SUCCESS;
271 gpg_error_t error;
272 char *result = NULL;
273 int save = 0;
274 char *pinentry_path = NULL;
275 char *display = NULL, *tty = NULL, *ttytype = NULL, *lcctype = NULL,
276 *lcmessages = NULL;
277 int outfd = STDOUT_FILENO;
278 FILE *outfp = stdout;
279 int inquirefd = STDIN_FILENO;
280 FILE *inquirefp = stdin;
281 int show_status = 1;
282 char *clientname = "pwmc";
283 char *inquire = NULL;
284 long iter = -2;
285 int have_iter = 0;
286 int timeout = 0;
287 #ifdef WITH_TCP
288 char *host = NULL;
289 int port = DEFAULT_PORT;
290 char *username = NULL;
291 char *ident = NULL;
292 char *known_hosts = NULL;
293 int get = 0;
294 int prot = PWMD_IP_ANY;
295 #endif
296 int tries = 0;
297 #ifdef DEBUG
298 int method = 0;
299 fd_set rfds;
300 #endif
301 char *url_string = NULL;
302 /* The order is important. */
303 enum {
304 #ifdef DEBUG
305 OPT_DEBUG,
306 #endif
307 #ifdef WITH_TCP
308 OPT_HOST, OPT_PORT, OPT_IDENTITY, OPT_KNOWN_HOSTS, OPT_USER,
309 OPT_GET_HOSTKEY, OPT_IPV4, OPT_IPV6,
310 #endif
311 OPT_URL, OPT_TTYNAME, OPT_TTYTYPE, OPT_DISPLAY, OPT_LC_CTYPE,
312 OPT_LC_MESSAGES, OPT_TIMEOUT, OPT_TRIES, OPT_PINENTRY, OPT_PASSPHRASE,
313 OPT_SOCKET, OPT_SAVE, OPT_ITERATIONS, OPT_OUTPUT_FD, OPT_INQUIRE_FD,
314 OPT_NO_STATUS, OPT_NAME, OPT_VERSION, OPT_HELP,
316 const struct option long_opts[] = {
317 #ifdef DEBUG
318 { "debug", 1, 0, 0 },
319 #endif
320 #ifdef WITH_TCP
321 { "host", 1, 0, 'h' },
322 { "port", 1, 0, 'p' },
323 { "identity", 1, 0, 'i' },
324 { "known-hosts", 1, 0, 'k' },
325 { "user", 1, 0, 'u' },
326 { "get-hostkey", 0, 0, 'g' },
327 { "ipv4", 0, 0, '4' },
328 { "ipv6", 0, 0, '6' },
329 #endif
330 { "url", 1, 0, 0 },
331 { "ttyname", 1, 0, 'y' },
332 { "ttytype", 1, 0, 't' },
333 { "display", 1, 0, 'd' },
334 { "lc-ctype", 1, 0, 0 },
335 { "lc-messages", 1, 0, 0 },
336 { "timeout", 1, 0, 0 },
337 { "tries", 1, 0, 0 },
338 { "pinentry", 1, 0, 0 },
339 { "passphrase", 1, 0, 'P' },
340 { "socket", 1, 0, 0 },
341 { "save", 0, 0, 'S' },
342 { "iterations", 1, 0, 'I' },
343 { "output-fd", 1, 0, 0 },
344 { "inquire-fd", 1, 0, 0 },
345 { "no-status", 0, 0, 0 },
346 { "name", 1, 0, 'n' },
347 { "version", 0, 0, 0 },
348 { "help", 0, 0, 0 },
349 { 0, 0, 0, 0}
351 #ifdef WITH_TCP
352 const char *optstring = "46h:p:i:k:u:gy:t:d:P:I:Sn:";
353 #else
354 const char *optstring = "y:t:d:P:I:Sn:";
355 #endif
356 int opt_index = 0;
358 #ifdef ENABLE_NLS
359 setlocale(LC_ALL, "");
360 bindtextdomain("libpwmd", LOCALEDIR);
361 #endif
363 while ((opt = getopt_long(argc, argv, optstring, long_opts, &opt_index)) != -1) {
364 switch (opt) {
365 /* Handle long options without a short option part. */
366 case 0:
367 switch (opt_index) {
368 #ifdef DEBUG
369 case OPT_DEBUG:
370 method = atoi(optarg);
372 if (method > 3)
373 method = 3;
374 break;
375 #endif
376 case OPT_URL:
377 url_string = optarg;
378 break;
379 case OPT_LC_CTYPE:
380 lcctype = pwmd_strdup(optarg);
381 break;
382 case OPT_LC_MESSAGES:
383 lcmessages = pwmd_strdup(optarg);
384 break;
385 case OPT_TIMEOUT:
386 timeout = atoi(optarg);
387 break;
388 case OPT_TRIES:
389 tries = atoi(optarg);
390 break;
391 case OPT_SOCKET:
392 socketpath = pwmd_strdup(optarg);
393 break;
394 case OPT_INQUIRE_FD:
395 inquirefd = atoi(optarg);
396 inquirefp = fdopen(inquirefd, "r");
398 if (!inquirefp) {
399 pwmd_free(password);
400 err(EXIT_FAILURE, "%i", inquirefd);
402 break;
403 case OPT_OUTPUT_FD:
404 outfd = atoi(optarg);
405 outfp = fdopen(outfd, "w");
407 if (!outfp) {
408 pwmd_free(password);
409 err(EXIT_FAILURE, "%i", outfd);
411 break;
412 case OPT_NO_STATUS:
413 show_status = 0;
414 break;
415 case OPT_VERSION:
416 pwmd_free(password);
417 printf("%s (pwmc)\n%s\n", PACKAGE_STRING, PACKAGE_BUGREPORT);
418 exit(EXIT_SUCCESS);
419 case OPT_PINENTRY:
420 pinentry_path = optarg;
421 break;
422 case OPT_HELP:
423 usage(argv[0], EXIT_SUCCESS);
424 default:
425 usage(argv[0], EXIT_FAILURE);
428 break;
429 #ifdef WITH_TCP
430 case '4':
431 prot = PWMD_IPV4;
432 break;
433 case '6':
434 prot = PWMD_IPV6;
435 break;
436 case 'h':
437 host = pwmd_strdup(optarg);
438 break;
439 case 'p':
440 port = atoi(optarg);
441 break;
442 case 'i':
443 ident = pwmd_strdup(optarg);
444 break;
445 case 'u':
446 username = pwmd_strdup(optarg);
447 break;
448 case 'k':
449 known_hosts = pwmd_strdup(optarg);
450 break;
451 case 'g':
452 get = 1;
453 break;
454 #endif
455 case 'y':
456 tty = optarg;
457 break;
458 case 't':
459 ttytype = optarg;
460 break;
461 case 'd':
462 display = optarg;
463 break;
464 case 'S':
465 save = 1;
466 break;
467 case 'I':
468 iter = strtol(optarg, NULL, 10);
469 have_iter = 1;
470 break;
471 case 'P':
472 password = pwmd_strdup(optarg);
473 memset(optarg, 0, strlen(optarg));
474 break;
475 case 'n':
476 clientname = optarg;
477 break;
478 default:
479 pwmd_free(password);
480 usage(argv[0], EXIT_FAILURE);
484 #ifdef DEBUG
485 if (!url_string) {
486 #endif
487 #ifdef WITH_TCP
488 if (host && !get && (!known_hosts || !ident)) {
489 pwmd_free(password);
490 usage(argv[0], EXIT_FAILURE);
493 if (get && !host) {
494 pwmd_free(password);
495 usage(argv[0], EXIT_FAILURE);
497 #endif
498 #ifdef DEBUG
500 #endif
502 filename = argv[optind];
503 pwmd_init();
504 pwm = pwmd_new(clientname);
505 #ifdef DEBUG
506 FD_ZERO(&rfds);
507 #endif
509 #ifdef WITH_TCP
510 if (host) {
511 if (prot != PWMD_IP_ANY) {
512 error = pwmd_setopt(pwm, PWMD_OPTION_IP_VERSION, prot);
514 if (error)
515 goto done;
518 #ifdef DEBUG
519 if (method >= 2) {
520 if (get) {
521 char *hostkey;
523 error = pwmd_get_hostkey_async(pwm, host, port);
525 if (error)
526 errx(EXIT_FAILURE, "%s: %s", host, pwmd_strerror(error));
528 error = process_cmd(pwm, &hostkey, 0);
530 if (error)
531 goto done;
533 printf("%s\n", hostkey);
534 pwmd_free(hostkey);
535 pwmd_free(password);
536 pwmd_close(pwm);
537 exit(EXIT_SUCCESS);
540 if (url_string)
541 error = pwmd_connect_url_async(pwm, url_string);
542 else
543 error = pwmd_ssh_connect_async(pwm, host, port, ident, username,
544 known_hosts);
546 if (error)
547 goto done;
549 error = process_cmd(pwm, NULL, 0);
551 if (error)
552 goto done;
554 else {
555 #endif
556 if (get) {
557 char *hostkey;
559 error = pwmd_get_hostkey(pwm, host, port, &hostkey);
561 if (error)
562 goto done;
564 printf("%s\n", hostkey);
565 pwmd_free(hostkey);
566 pwmd_free(password);
567 pwmd_close(pwm);
568 exit(EXIT_SUCCESS);
571 if (url_string)
572 error = pwmd_connect_url(pwm, url_string);
573 else
574 error = pwmd_ssh_connect(pwm, host, port, ident, username, known_hosts);
576 if (error)
577 goto done;
578 #ifdef DEBUG
580 #endif
582 else {
583 #endif
584 if (url_string)
585 error = pwmd_connect_url(pwm, url_string);
586 else
587 error = pwmd_connect(pwm, socketpath);
589 if (error)
590 goto done;
591 #ifdef WITH_TCP
593 #endif
595 if (have_iter) {
596 error = pwmd_command(pwm, &result, "VERSION");
598 if (error && error != GPG_ERR_ASS_UNKNOWN_CMD)
599 goto done;
601 pwmd_free(result);
603 if (error == GPG_ERR_ASS_UNKNOWN_CMD) {
604 if (iter < -1) {
605 pwmd_free(password);
606 pwmd_close(pwm);
607 usage(argv[0], EXIT_FAILURE);
610 else {
611 /* pwmd version 2 or later. */
612 if (iter < 0) {
613 pwmd_free(password);
614 pwmd_close(pwm);
615 usage(argv[0], EXIT_FAILURE);
620 if (timeout > 0) {
621 error = pwmd_setopt(pwm, PWMD_OPTION_PINENTRY_TIMEOUT, timeout);
623 if (error)
624 goto done;
627 if (password) {
628 error = pwmd_setopt(pwm, PWMD_OPTION_PASSPHRASE, password);
630 if (error)
631 goto done;
633 else {
634 if (pinentry_path) {
635 error = pwmd_setopt(pwm, PWMD_OPTION_PINENTRY_PATH, pinentry_path);
637 if (error)
638 goto done;
641 if (display) {
642 error = pwmd_setopt(pwm, PWMD_OPTION_PINENTRY_DISPLAY, display);
644 if (error)
645 goto done;
648 if (tty) {
649 error = pwmd_setopt(pwm, PWMD_OPTION_PINENTRY_TTY, tty);
651 if (error)
652 goto done;
655 if (ttytype) {
656 error = pwmd_setopt(pwm, PWMD_OPTION_PINENTRY_TERM, ttytype);
658 if (error)
659 goto done;
662 if (lcctype) {
663 error = pwmd_setopt(pwm, PWMD_OPTION_PINENTRY_LC_CTYPE, lcctype);
665 if (error)
666 goto done;
669 if (lcmessages) {
670 error = pwmd_setopt(pwm, PWMD_OPTION_PINENTRY_LC_MESSAGES,
671 lcmessages);
673 if (error)
674 goto done;
677 if (tries > 0) {
678 error = pwmd_setopt(pwm, PWMD_OPTION_PINENTRY_TRIES, tries);
680 if (error)
681 goto done;
685 if (show_status) {
686 error = pwmd_setopt(pwm, PWMD_OPTION_STATUS_CB, status_msg_cb);
688 if (error)
689 goto done;
692 if (filename) {
693 #ifdef DEBUG
694 switch (method) {
695 case 0:
696 error = pwmd_open(pwm, filename);
697 break;
698 case 1:
699 error = pwmd_open2(pwm, filename);
700 break;
701 case 2:
702 error = pwmd_open_async(pwm, filename);
704 break;
705 case 3:
706 error = pwmd_open_async2(pwm, filename);
707 break;
710 if (error)
711 goto done;
713 if (method >= 2)
714 error = process_cmd(pwm, &result, 0);
715 #else
716 error = pwmd_open(pwm, filename);
717 #endif
719 if (error)
720 goto done;
723 if (filename) {
724 error = pwmd_command(pwm, &result, "LOCK");
726 if (error)
727 goto done;
730 fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
732 for (;;) {
733 ssize_t n;
735 error = process_cmd(pwm, NULL, 1);
737 if (error)
738 goto done;
740 n = read(STDIN_FILENO, command, sizeof(command));
742 if (n == -1) {
743 if (errno == EAGAIN)
744 continue;
746 error = gpg_error_from_errno(errno);
747 goto done;
749 else if (!n)
750 goto done;
752 command[n] = 0;
754 if (n && command[strlen(command)-1] == '\n')
755 command[strlen(command)-1] = 0;
757 p = command;
758 break;
761 if (!p || !*p)
762 goto done;
765 * This is a known INQUIRE command. We use pwmd_inquire() to send the
766 * data from the do_inquire() callback function.
768 if (strncasecmp(p, "STORE ", 6) == 0) {
769 p += 6;
770 inquire = (char *)"STORE";
772 else if (strncasecmp(p, "IMPORT ", 7) == 0) {
773 p += 7;
774 inquire = (char *)"IMPORT";
777 if (inquire) {
778 struct inquire_s *inq = (struct inquire_s *)pwmd_malloc(sizeof(struct inquire_s));
780 if (!inq) {
781 error = gpg_error_from_errno(ENOMEM);
782 goto done;
785 inq->data = pwmd_strdup(p);
786 inq->fp = inquirefp;
787 error = pwmd_inquire(pwm, inquire, do_inquire, inq);
788 pwmd_free(inq);
789 goto done;
792 if (strcasecmp(p, "BYE") == 0)
793 goto done;
795 error = pwmd_command(pwm, &result, command);
796 memset(command, 0, sizeof(command));
798 if (error)
799 goto done;
801 if (result) {
802 fwrite(result, 1, strlen(result), outfp);
803 pwmd_free(result);
806 done:
807 memset(command, 0, sizeof(command));
808 pwmd_free(password);
810 if (!error && save) {
811 if (iter != -2) {
812 error = pwmd_command(pwm, &result, "OPTION ITERATIONS=%i", iter);
814 if (error)
815 goto done;
818 #ifdef DEBUG
819 switch (method) {
820 case 0:
821 error = pwmd_save(pwm);
822 break;
823 case 1:
824 error = pwmd_save2(pwm);
825 break;
826 case 2:
827 error = pwmd_save_async(pwm);
828 break;
829 case 3:
830 error = pwmd_save_async2(pwm);
831 break;
834 if (!error && method >= 2)
835 error = process_cmd(pwm, NULL, 0);
837 #else
838 error = pwmd_save(pwm);
839 #endif
842 if (!error && filename)
843 error = pwmd_command(pwm, &result, "UNLOCK");
845 if (error) {
846 show_error(error);
847 ret = EXIT_FAILURE;
850 pwmd_close(pwm);
852 if (socketpath)
853 pwmd_free(socketpath);
855 exit(ret);