1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
3 Copyright (C) 2007-2009 Ben Kibbey <bjk@luxsci.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA
28 #include <sys/select.h>
31 #include <sys/types.h>
42 #ifdef HAVE_GETOPT_LONG
47 #include "getopt_long.h"
51 #define N_(msgid) gettext(msgid)
55 #define DEFAULT_PORT 22
56 #define PWMD_LINEMAX ASSUAN_LINELENGTH
60 static void show_error(gpg_error_t error
)
62 fprintf(stderr
, "ERR %i %s\n", gpg_err_code(error
), pwmd_strerror(error
));
65 static void usage(const char *pn
, int status
)
67 fprintf(status
== EXIT_FAILURE
? stderr
: stdout
, N_(
68 "Read a PWMD protocol command from standard input.\n\n"
69 "Usage: pwmc [options] [file]\n"
72 " pinentry method (0=pwmd, 1=libpwmd, 2=pwmd async, "
77 " --host, -h <hostname>\n"
78 " connect to the specified hostname\n"
81 " alterate port (22)\n"
84 " SSH username (default is the invoking user)\n"
86 " --identity, -i <filename>\n"
87 " SSH identity file\n"
89 " --known-hosts, -k <filename>\n"
90 " known host's file (for server validation)\n"
92 " --get-hostkey, -g\n"
93 " retrieve the remote SSH host key and exit\n"
96 " try connecting via IPv4 only\n"
99 " try connecting via IPv6 only\n"
103 " a url string to parse\n"
106 " disable showing of status messages from the server\n"
108 " --name, -n <string>\n"
109 " set the client name\n"
111 " --socket <filename>\n"
112 " local socket to connect to (~/.pwmd/socket)\n"
114 " --passphrase, -P <string>\n"
115 " passphrase to use (disables pinentry use)\n"
117 " --timeout <seconds>\n"
118 " pinentry timeout\n"
121 " number of pinentry tries before failing (3)\n"
123 " --pinentry <path>\n"
124 " the full path to the pinentry binary (server default)\n"
126 " --ttyname, -y <path>\n"
127 " tty that pinentry will use\n"
129 " --ttytype, -t <string>\n"
130 " pinentry terminal type (default is TERM)\n"
133 " pinentry display (default is DISPLAY)\n"
135 " --lc-ctype <string>\n"
136 " locale setting for pinentry\n"
138 " --lc-messages <string>\n"
139 " locale setting for pinentry\n"
142 " --local-pinentry\n"
143 " force using a local pinentry\n"
145 " --output-fd <FD>\n"
146 " redirect command output to the specified file descriptor\n"
148 " --inquire-fd <FD>\n"
149 " read inquire data from the specified file descriptor\n"
151 " --cipher <string>\n"
152 " the cipher to use when saving\n"
155 " send the SAVE command before exiting\n"
158 " like --save, but ask for a passphrase\n"
160 " --iterations, -I <N>\n"
161 " encrypt with the specified number of iterations when saving\n"
165 fprintf(status
== EXIT_FAILURE
? stderr
: stdout
, N_(
167 "A url string (specified with --url) may be in the form of:\n"
168 " file://[path/to/socket]\n"
170 " ssh[46]://[username@]hostname[:port],identity,known_hosts\n"
183 static gpg_error_t
do_inquire(void *data
, const char *keyword
, gpg_error_t rc
,
184 char **result
, size_t *result_len
)
189 struct inquire_s
*inq
= (struct inquire_s
*)data
;
202 inq
->p
= pwmd_malloc(PWMD_LINEMAX
);
205 return GPG_ERR_ENOMEM
;
210 memcpy(inq
->p
, inq
->data
, inq
->len
);
211 pwmd_free(inq
->data
);
217 while (len
< PWMD_LINEMAX
&& (c
= fgetc(inq
->fp
)) != EOF
) {
230 static int status_msg_cb(void *data
, const char *line
)
232 fprintf(stderr
, "%s\n", line
);
236 static gpg_error_t
process_cmd(pwm_t
*pwm
, char **result
, int input
)
245 pwmd_fd_t pfds
[nfds
];
248 rc
= pwmd_get_fds(pwm
, pfds
, &nfds
);
254 s
= pwmd_process(pwm
, &rc
, result
);
258 for (i
= 0, n
= 0; i
< nfds
; i
++) {
259 FD_SET(pfds
[i
].fd
, &rfds
);
260 n
= pfds
[i
].fd
> n
? pfds
[i
].fd
: n
;
264 FD_SET(STDIN_FILENO
, &rfds
);
266 nfds
= select(n
+1, &rfds
, NULL
, NULL
, NULL
);
269 rc
= gpg_error_from_errno(errno
);
273 if (input
&& FD_ISSET(STDIN_FILENO
, &rfds
))
276 s
= pwmd_process(pwm
, &rc
, result
);
277 } while (s
== ASYNC_PROCESS
);
282 static gpg_error_t
knownhost_cb(void *data
, const char *host
, const char *key
,
286 char *buf
= pwmd_strdup_printf(N_("Password Manager Daemon: %s\n\nWhile attempting an SSH connection to %s, there was a problem verifying it's hostkey against the known and trusted hosts file because it's hostkey was not found.\n\nWould you like to treat this connection as trusted for this and future connections by adding %s's hostkey to the known hosts file?"), (char *)data
, host
, host
);
288 rc
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TITLE
, buf
);
294 return pwmd_getpin(pwm
, NULL
, NULL
, PWMD_PINENTRY_CONFIRM
);
297 int main(int argc
, char *argv
[])
300 char *password
= NULL
;
301 char *filename
= NULL
;
302 char *socketpath
= NULL
;
303 char command
[PWMD_LINEMAX
], *p
;
304 int ret
= EXIT_SUCCESS
;
308 char *pinentry_path
= NULL
;
309 char *display
= NULL
, *tty
= NULL
, *ttytype
= NULL
, *lcctype
= NULL
,
311 int outfd
= STDOUT_FILENO
;
312 FILE *outfp
= stdout
;
313 int inquirefd
= STDIN_FILENO
;
314 FILE *inquirefp
= stdin
;
316 char *clientname
= "pwmc";
317 char *inquire
= NULL
;
326 int port
= DEFAULT_PORT
;
327 char *username
= NULL
;
329 char *known_hosts
= NULL
;
331 int prot
= PWMD_IP_ANY
;
341 char *url_string
= NULL
;
342 /* The order is important. */
348 OPT_HOST
, OPT_PORT
, OPT_IDENTITY
, OPT_KNOWN_HOSTS
, OPT_USER
,
349 OPT_GET_HOSTKEY
, OPT_IPV4
, OPT_IPV6
,
351 OPT_URL
, OPT_LOCAL
, OPT_FORCE_SAVE
, OPT_TTYNAME
, OPT_TTYTYPE
,
352 OPT_DISPLAY
, OPT_LC_CTYPE
, OPT_LC_MESSAGES
, OPT_TIMEOUT
, OPT_TRIES
,
353 OPT_PINENTRY
, OPT_PASSPHRASE
, OPT_SOCKET
, OPT_SAVE
, OPT_ITERATIONS
,
354 OPT_OUTPUT_FD
, OPT_INQUIRE_FD
, OPT_NO_STATUS
, OPT_NAME
, OPT_VERSION
,
355 OPT_HELP
, OPT_CIPHER
,
357 const struct option long_opts
[] = {
359 { "debug", 1, 0, 0 },
362 { "host", 1, 0, 'h' },
363 { "port", 1, 0, 'p' },
364 { "identity", 1, 0, 'i' },
365 { "known-hosts", 1, 0, 'k' },
366 { "user", 1, 0, 'u' },
367 { "get-hostkey", 0, 0, 'g' },
368 { "ipv4", 0, 0, '4' },
369 { "ipv6", 0, 0, '6' },
372 { "local-pinentry", 0, 0 },
373 { "force-save", 0, 0 },
374 { "ttyname", 1, 0, 'y' },
375 { "ttytype", 1, 0, 't' },
376 { "display", 1, 0, 'd' },
377 { "lc-ctype", 1, 0, 0 },
378 { "lc-messages", 1, 0, 0 },
379 { "timeout", 1, 0, 0 },
380 { "tries", 1, 0, 0 },
381 { "pinentry", 1, 0, 0 },
382 { "passphrase", 1, 0, 'P' },
383 { "socket", 1, 0, 0 },
384 { "save", 0, 0, 'S' },
385 { "iterations", 1, 0, 'I' },
386 { "output-fd", 1, 0, 0 },
387 { "inquire-fd", 1, 0, 0 },
388 { "no-status", 0, 0, 0 },
389 { "name", 1, 0, 'n' },
390 { "version", 0, 0, 0 },
392 { "cipher", 1, 0, 0 },
396 const char *optstring
= "46h:p:i:k:u:gy:t:d:P:I:Sn:";
398 const char *optstring
= "y:t:d:P:I:Sn:";
403 setlocale(LC_ALL
, "");
404 bindtextdomain("libpwmd", LOCALEDIR
);
407 while ((opt
= getopt_long(argc
, argv
, optstring
, long_opts
, &opt_index
)) != -1) {
409 /* Handle long options without a short option part. */
414 method
= atoi(optarg
);
427 save
= force_save
= 1;
430 lcctype
= pwmd_strdup(optarg
);
432 case OPT_LC_MESSAGES
:
433 lcmessages
= pwmd_strdup(optarg
);
436 timeout
= atoi(optarg
);
439 tries
= atoi(optarg
);
443 socketpath
= pwmd_strdup(optarg
);
446 inquirefd
= atoi(optarg
);
447 inquirefp
= fdopen(inquirefd
, "r");
451 err(EXIT_FAILURE
, "%i", inquirefd
);
455 outfd
= atoi(optarg
);
456 outfp
= fdopen(outfd
, "w");
460 err(EXIT_FAILURE
, "%i", outfd
);
468 printf("%s (pwmc)\n%s\n\n"
469 "Compile-time features:\n"
491 , PACKAGE_STRING
, PACKAGE_BUGREPORT
);
494 pinentry_path
= optarg
;
497 usage(argv
[0], EXIT_SUCCESS
);
502 usage(argv
[0], EXIT_FAILURE
);
514 host
= pwmd_strdup(optarg
);
520 ident
= pwmd_strdup(optarg
);
523 username
= pwmd_strdup(optarg
);
526 known_hosts
= pwmd_strdup(optarg
);
545 iter
= strtol(optarg
, NULL
, 10);
549 password
= pwmd_strdup(optarg
);
550 memset(optarg
, 0, strlen(optarg
));
557 usage(argv
[0], EXIT_FAILURE
);
565 if (host
&& !get
&& (!known_hosts
|| !ident
)) {
567 usage(argv
[0], EXIT_FAILURE
);
572 usage(argv
[0], EXIT_FAILURE
);
579 filename
= argv
[optind
];
581 pwm
= pwmd_new(clientname
);
588 if (prot
!= PWMD_IP_ANY
) {
589 error
= pwmd_setopt(pwm
, PWMD_OPTION_IP_VERSION
, prot
);
595 error
= pwmd_setopt(pwm
, PWMD_OPTION_KNOWNHOST_CB
, knownhost_cb
);
600 error
= pwmd_setopt(pwm
, PWMD_OPTION_KNOWNHOST_DATA
, clientname
);
610 error
= pwmd_get_hostkey_async(pwm
, host
, port
);
613 errx(EXIT_FAILURE
, "%s: %s", host
, pwmd_strerror(error
));
615 error
= process_cmd(pwm
, &hostkey
, 0);
620 printf("%s", hostkey
);
628 error
= pwmd_connect_url_async(pwm
, url_string
);
630 error
= pwmd_ssh_connect_async(pwm
, host
, port
, ident
, username
,
636 error
= process_cmd(pwm
, NULL
, 0);
646 error
= pwmd_get_hostkey(pwm
, host
, port
, &hostkey
);
651 printf("%s", hostkey
);
659 error
= pwmd_connect_url(pwm
, url_string
);
661 error
= pwmd_ssh_connect(pwm
, host
, port
, ident
, username
, known_hosts
);
672 error
= pwmd_connect_url(pwm
, url_string
);
674 error
= pwmd_connect(pwm
, socketpath
);
682 error
= pwmd_socket_type(pwm
, &s
);
687 if (s
== PWMD_SOCKET_SSH
&& force_save
&& !local_pin
) {
688 error
= GPG_ERR_WRONG_KEY_USAGE
;
693 error
= pwmd_command(pwm
, &result
, "VERSION");
703 usage(argv
[0], EXIT_FAILURE
);
708 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TIMEOUT
, timeout
);
714 if (local_pin
&& filename
) {
715 error
= pwmd_command(pwm
, NULL
, "ISCACHED %s", filename
);
717 if (error
== GPG_ERR_NOT_FOUND
) {
719 if (try++ == local_tries
)
728 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TIMEOUT
, 0);
730 error
= pwmd_getpin(pwm
, filename
, &password
,
731 try > 1 ? PWMD_PINENTRY_OPEN_FAILED
: PWMD_PINENTRY_OPEN
);
736 error
= pwmd_setopt(pwm
, PWMD_OPTION_PASSPHRASE
, password
);
744 else if (error
&& error
!= GPG_ERR_ENOENT
)
749 error
= pwmd_setopt(pwm
, PWMD_OPTION_PASSPHRASE
, password
);
756 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_PATH
, pinentry_path
);
763 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_DISPLAY
, display
);
770 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TTY
, tty
);
777 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TERM
, ttytype
);
784 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_LC_CTYPE
, lcctype
);
791 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_LC_MESSAGES
,
799 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TRIES
, tries
);
807 error
= pwmd_setopt(pwm
, PWMD_OPTION_STATUS_CB
, status_msg_cb
);
818 error
= pwmd_open(pwm
, filename
);
821 error
= pwmd_open2(pwm
, filename
);
824 error
= pwmd_open_async(pwm
, filename
);
828 error
= pwmd_open_async2(pwm
, filename
);
832 if (error
&& local_pin
&& error
== GPG_ERR_INV_PASSPHRASE
)
839 error
= process_cmd(pwm
, &result
, 0);
841 error
= pwmd_open(pwm
, filename
);
843 if (error
&& local_pin
&& error
== GPG_ERR_INV_PASSPHRASE
)
852 error
= pwmd_command(pwm
, &result
, "LOCK");
858 fcntl(STDIN_FILENO
, F_SETFL
, O_NONBLOCK
);
862 error
= process_cmd(pwm
, NULL
, 1);
867 n
= read(STDIN_FILENO
, command
, sizeof(command
));
873 error
= gpg_error_from_errno(errno
);
887 * This is a known INQUIRE command. We use pwmd_inquire() to send the
888 * data from the do_inquire() callback function.
890 if (n
> 6 && (!memcmp(p
, "STORE ", 6) || !memcmp(p
, "store ", 6))) {
893 inquire
= (char *)"STORE";
895 else if (n
> 7 && (!memcmp(p
, "IMPORT ", 7) || !memcmp(p
, "import ", 7))) {
898 inquire
= (char *)"IMPORT";
902 struct inquire_s
*inq
= (struct inquire_s
*)pwmd_malloc(sizeof(struct inquire_s
));
907 error
= gpg_error_from_errno(ENOMEM
);
911 fd
= fileno(inquirefp
);
914 error
= gpg_error_from_errno(errno
);
918 if (fstat(fd
, &st
) == -1) {
919 error
= gpg_error_from_errno(errno
);
923 error
= pwmd_setopt(pwm
, PWMD_OPTION_INQUIRE_TOTAL
,
924 st
.st_size
? (size_t)st
.st_size
+strlen(p
) : 0);
929 inq
->data
= pwmd_malloc(n
);
932 error
= GPG_ERR_ENOMEM
;
936 memcpy(inq
->data
, p
, n
);
940 error
= pwmd_inquire(pwm
, inquire
, do_inquire
, inq
);
945 if (strcasecmp(p
, "BYE") == 0)
948 error
= pwmd_command(pwm
, &result
, command
);
949 memset(command
, 0, sizeof(command
));
955 fwrite(result
, 1, strlen(result
), outfp
);
960 memset(command
, 0, sizeof(command
));
964 if (!error
&& save
&& filename
) {
966 error
= pwmd_command(pwm
, &result
, "SET ITERATIONS=%i", iter
);
973 error
= pwmd_command(pwm
, NULL
, "SET CIPHER=%s", cipher
);
983 error
= pwmd_command(pwm
, NULL
, "ISCACHED %s", filename
);
985 if (error
&& error
!= GPG_ERR_NOT_FOUND
&&
986 error
!= GPG_ERR_ENOENT
)
992 error
= pwmd_getpin(pwm
, filename
, &p1
, PWMD_PINENTRY_SAVE
);
997 error
= pwmd_getpin(pwm
, filename
, &password
,
998 PWMD_PINENTRY_SAVE_CONFIRM
);
1005 if ((p1
|| password
) && ((!p1
&& password
) || (!password
&& p1
) ||
1006 strcmp(p1
, password
))) {
1008 pwmd_free(password
);
1016 error
= pwmd_setopt(pwm
, PWMD_OPTION_PASSPHRASE
, password
);
1019 pwmd_free(password
);
1026 error
= pwmd_command(pwm
, NULL
, "CLEARCACHE %s", filename
);
1032 error
= pwmd_setopt(pwm
, PWMD_OPTION_PASSPHRASE
, NULL
);
1043 error
= pwmd_save(pwm
);
1046 error
= pwmd_save2(pwm
);
1049 error
= pwmd_save_async(pwm
);
1052 error
= pwmd_save_async2(pwm
);
1056 if (!error
&& method
>= 2)
1057 error
= process_cmd(pwm
, NULL
, 0);
1060 error
= pwmd_save(pwm
);
1064 if (!error
&& filename
)
1065 error
= pwmd_command(pwm
, &result
, "UNLOCK");
1075 pwmd_free(socketpath
);