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
59 static void show_error(gpg_error_t error
)
61 fprintf(stderr
, "ERR %i %s\n", gpg_err_code(error
), pwmd_strerror(error
));
64 static void usage(const char *pn
, int status
)
66 fprintf(status
== EXIT_FAILURE
? stderr
: stdout
, N_(
67 "Read a PWMD protocol command from standard input.\n\n"
68 "Usage: pwmc [options] [file]\n"
71 " pinentry method (0=pwmd, 1=libpwmd, 2=pwmd async, "
76 " --host, -h <hostname>\n"
77 " connect to the specified hostname\n"
80 " alterate port (22)\n"
82 " --user <username>\n"
83 " SSH username (default is the invoking user)\n"
85 " --identity, -i <filename>\n"
86 " SSH identity file\n"
88 " --known-hosts, -k <filename>\n"
89 " known host's file (for server validation)\n"
91 " --get-hostkey, -g\n"
92 " retrieve the remote SSH host key and exit\n"
95 " try connecting via IPv4 only\n"
98 " try connecting via IPv6 only\n"
102 " a url string to parse\n"
105 " disable showing of status messages from the server\n"
107 " --name, -n <string>\n"
108 " set the client name\n"
110 " --socket <filename>\n"
111 " local socket to connect to (~/.pwmd/socket)\n"
113 " --passphrase, -P <string>\n"
114 " passphrase to use (disables pinentry use)\n"
116 " --key-file <filename>\n"
117 " obtain the passphrase from the specified filename\n"
120 " the passphrase is base64 encoded\n"
122 " --timeout <seconds>\n"
123 " pinentry timeout\n"
126 " number of pinentry tries before failing (3)\n"
128 " --pinentry <path>\n"
129 " the full path to the pinentry binary (server default)\n"
131 " --ttyname, -y <path>\n"
132 " tty that pinentry will use\n"
134 " --ttytype, -t <string>\n"
135 " pinentry terminal type (default is $TERM)\n"
138 " pinentry display (default is $DISPLAY)\n"
140 " --lc-ctype <string>\n"
141 " locale setting for pinentry\n"
143 " --lc-messages <string>\n"
144 " locale setting for pinentry\n"
147 " --local-pinentry\n"
148 " force using a local pinentry\n"
150 " --output-fd <FD>\n"
151 " redirect command output to the specified file descriptor\n"
153 " --inquire <COMMAND>\n"
154 " the specified command (with any options) uses a server inquire while\n"
155 " command data is read via the inquire file descriptor (stdin)\n"
157 " --inquire-fd <FD>\n"
158 " read inquire data from the specified file descriptor (stdin)\n"
160 " --cipher <string>\n"
161 " the cipher to use when saving (see pwmd(1))\n"
164 " send the SAVE command before exiting\n"
167 " like --save, but ask for a passphrase\n"
169 " --iterations, -I <N>\n"
170 " encrypt with the specified number of iterations when saving\n"
174 fprintf(status
== EXIT_FAILURE
? stderr
: stdout
, N_(
176 "A url string (specified with --url) may be in the form of:\n"
177 " file://[path/to/socket]\n"
179 " ssh[46]://[username@]hostname[:port],identity,known_hosts\n"
191 static gpg_error_t
inquire_cb(void *user
, const char *cmd
, gpg_error_t rc
,
192 char **data
, size_t *len
)
194 struct inquire_s
*inq
= user
;
202 /* The first part of the command data. */
210 *len
= read(inq
->fd
, inq
->line
, ASSUAN_LINELENGTH
);
213 return gpg_error_from_syserror();
218 return *len
? 0 : GPG_ERR_EOF
;
221 static int status_msg_cb(void *data
, const char *line
)
223 fprintf(stderr
, "%s\n", line
);
227 static gpg_error_t
process_cmd(pwm_t
*pwm
, char **result
, int input
)
236 pwmd_fd_t pfds
[nfds
];
239 rc
= pwmd_get_fds(pwm
, pfds
, &nfds
);
245 s
= pwmd_process(pwm
, &rc
, result
);
249 for (i
= 0, n
= 0; i
< nfds
; i
++) {
250 FD_SET(pfds
[i
].fd
, &rfds
);
251 n
= pfds
[i
].fd
> n
? pfds
[i
].fd
: n
;
255 FD_SET(STDIN_FILENO
, &rfds
);
257 nfds
= select(n
+1, &rfds
, NULL
, NULL
, NULL
);
260 rc
= gpg_error_from_errno(errno
);
264 if (input
&& FD_ISSET(STDIN_FILENO
, &rfds
))
267 s
= pwmd_process(pwm
, &rc
, result
);
268 } while (s
== ASYNC_PROCESS
);
274 static gpg_error_t
knownhost_cb(void *data
, const char *host
, const char *key
,
278 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
);
280 rc
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TITLE
, buf
);
286 return pwmd_getpin(pwm
, NULL
, NULL
, PWMD_PINENTRY_CONFIRM
);
289 static int is_remote_url(const char *str
)
291 if (strstr(str
, "file://") || strstr(str
, "local://"))
298 int main(int argc
, char *argv
[])
301 char *password
= NULL
;
302 char *keyfile
= NULL
;
304 char *filename
= NULL
;
305 char *socketpath
= NULL
;
306 char command
[ASSUAN_LINELENGTH
], *p
;
307 int ret
= EXIT_SUCCESS
;
311 char *pinentry_path
= NULL
;
312 char *display
= NULL
, *tty
= NULL
, *ttytype
= NULL
, *lcctype
= NULL
,
314 int outfd
= STDOUT_FILENO
;
315 FILE *outfp
= stdout
;
316 int inquirefd
= STDIN_FILENO
;
317 FILE *inquirefp
= stdin
;
319 char *clientname
= "pwmc";
320 char *inquire
= NULL
;
329 int port
= DEFAULT_PORT
;
330 char *username
= NULL
;
332 char *known_hosts
= NULL
;
334 int prot
= PWMD_IP_ANY
;
340 int lock_on_open
= 1;
345 char *url_string
= NULL
;
346 /* The order is important. */
352 OPT_HOST
, OPT_PORT
, OPT_IDENTITY
, OPT_KNOWN_HOSTS
, OPT_USER
,
353 OPT_GET_HOSTKEY
, OPT_IPV4
, OPT_IPV6
,
355 OPT_URL
, OPT_LOCAL
, OPT_FORCE_SAVE
, OPT_TTYNAME
, OPT_TTYTYPE
,
356 OPT_DISPLAY
, OPT_LC_CTYPE
, OPT_LC_MESSAGES
, OPT_TIMEOUT
, OPT_TRIES
,
357 OPT_PINENTRY
, OPT_PASSPHRASE
, OPT_KEYFILE
, OPT_BASE64
, OPT_SOCKET
,
358 OPT_NOLOCK
, OPT_SAVE
, OPT_ITERATIONS
, OPT_OUTPUT_FD
, OPT_INQUIRE
,
359 OPT_INQUIRE_FD
, OPT_NO_STATUS
, OPT_NAME
, OPT_VERSION
, OPT_HELP
,
362 const struct option long_opts
[] = {
364 { "debug", 1, 0, 0 },
367 { "host", 1, 0, 'h' },
368 { "port", 1, 0, 'p' },
369 { "identity", 1, 0, 'i' },
370 { "known-hosts", 1, 0, 'k' },
371 { "user", 1, 0, 'u' },
372 { "get-hostkey", 0, 0, 'g' },
373 { "ipv4", 0, 0, '4' },
374 { "ipv6", 0, 0, '6' },
377 { "local-pinentry", 0, 0 },
378 { "force-save", 0, 0 },
379 { "ttyname", 1, 0, 'y' },
380 { "ttytype", 1, 0, 't' },
381 { "display", 1, 0, 'd' },
382 { "lc-ctype", 1, 0, 0 },
383 { "lc-messages", 1, 0, 0 },
384 { "timeout", 1, 0, 0 },
385 { "tries", 1, 0, 0 },
386 { "pinentry", 1, 0, 0 },
387 { "passphrase", 1, 0, 'P' },
388 { "key-file", 1, 0, 0 },
389 { "base64", 0, 0, 0 },
390 { "socket", 1, 0, 0 },
391 { "no-lock", 0, 0, 0 },
392 { "save", 0, 0, 'S' },
393 { "iterations", 1, 0, 'I' },
394 { "output-fd", 1, 0, 0 },
395 { "inquire", 1, 0, 0 },
396 { "inquire-fd", 1, 0, 0 },
397 { "no-status", 0, 0, 0 },
398 { "name", 1, 0, 'n' },
399 { "version", 0, 0, 0 },
401 { "cipher", 1, 0, 0 },
405 const char *optstring
= "46h:p:i:k:u:gy:t:d:P:I:Sn:";
407 const char *optstring
= "y:t:d:P:I:Sn:";
412 setlocale(LC_ALL
, "");
413 bindtextdomain("libpwmd", LOCALEDIR
);
416 while ((opt
= getopt_long(argc
, argv
, optstring
, long_opts
, &opt_index
)) != -1) {
418 /* Handle long options without a short option part. */
423 method
= atoi(optarg
);
445 save
= force_save
= 1;
448 lcctype
= pwmd_strdup(optarg
);
450 case OPT_LC_MESSAGES
:
451 lcmessages
= pwmd_strdup(optarg
);
454 timeout
= atoi(optarg
);
457 tries
= atoi(optarg
);
461 socketpath
= pwmd_strdup(optarg
);
467 inquirefd
= atoi(optarg
);
468 inquirefp
= fdopen(inquirefd
, "r");
472 err(EXIT_FAILURE
, "%i", inquirefd
);
476 outfd
= atoi(optarg
);
477 outfp
= fdopen(outfd
, "w");
481 err(EXIT_FAILURE
, "%i", outfd
);
489 printf("%s (pwmc)\n%s\n\n"
490 "Compile-time features:\n"
512 , PACKAGE_STRING
, PACKAGE_BUGREPORT
);
515 pinentry_path
= optarg
;
518 usage(argv
[0], EXIT_SUCCESS
);
523 usage(argv
[0], EXIT_FAILURE
);
535 host
= pwmd_strdup(optarg
);
541 ident
= pwmd_strdup(optarg
);
544 username
= pwmd_strdup(optarg
);
547 known_hosts
= pwmd_strdup(optarg
);
566 iter
= strtol(optarg
, NULL
, 10);
570 password
= pwmd_strdup(optarg
);
571 memset(optarg
, 0, strlen(optarg
));
578 usage(argv
[0], EXIT_FAILURE
);
586 if (host
&& !get
&& !ident
) {
588 usage(argv
[0], EXIT_FAILURE
);
593 usage(argv
[0], EXIT_FAILURE
);
600 filename
= argv
[optind
];
602 pwm
= pwmd_new(clientname
);
608 if (host
|| url_string
) {
611 if (prot
!= PWMD_IP_ANY
) {
612 error
= pwmd_setopt(pwm
, PWMD_OPTION_IP_VERSION
, prot
);
618 error
= pwmd_setopt(pwm
, PWMD_OPTION_KNOWNHOST_CB
, knownhost_cb
);
623 error
= pwmd_setopt(pwm
, PWMD_OPTION_KNOWNHOST_DATA
, clientname
);
633 error
= pwmd_get_hostkey_async(pwm
, host
, port
);
636 errx(EXIT_FAILURE
, "%s: %s", host
, pwmd_strerror(error
));
638 error
= process_cmd(pwm
, &hostkey
, 0);
643 printf("%s", hostkey
);
651 error
= pwmd_connect_url_async(pwm
, url_string
);
653 error
= pwmd_ssh_connect_async(pwm
, host
, port
, ident
, username
,
659 error
= process_cmd(pwm
, NULL
, 0);
669 error
= pwmd_get_hostkey(pwm
, host
, port
, &hostkey
);
674 printf("%s", hostkey
);
682 error
= pwmd_connect_url(pwm
, url_string
);
684 error
= pwmd_ssh_connect(pwm
, host
, port
, ident
, username
, known_hosts
);
695 error
= pwmd_connect_url(pwm
, url_string
);
697 error
= pwmd_connect(pwm
, socketpath
);
705 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TITLE
, NULL
);
706 error
= pwmd_socket_type(pwm
, &s
);
711 if (s
== PWMD_SOCKET_SSH
&& force_save
&& !local_pin
) {
712 error
= GPG_ERR_WRONG_KEY_USAGE
;
717 error
= pwmd_command(pwm
, &result
, "VERSION");
727 usage(argv
[0], EXIT_FAILURE
);
731 if (filename
&& lock_on_open
) {
732 error
= pwmd_setopt(pwm
, PWMD_OPTION_LOCK_ON_OPEN
, 1);
739 error
= pwmd_setopt(pwm
, PWMD_OPTION_BASE64
, 1);
746 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TIMEOUT
, timeout
);
752 if (local_pin
&& filename
&& !keyfile
) {
753 error
= pwmd_command(pwm
, NULL
, "ISCACHED %s", filename
);
755 if (error
== GPG_ERR_NOT_FOUND
) {
757 if (try++ == local_tries
)
766 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TIMEOUT
, 0);
768 error
= pwmd_getpin(pwm
, filename
, &password
,
769 try > 1 ? PWMD_PINENTRY_OPEN_FAILED
: PWMD_PINENTRY_OPEN
);
774 error
= pwmd_setopt(pwm
, PWMD_OPTION_PASSPHRASE
, password
);
782 else if (error
&& error
!= GPG_ERR_ENOENT
)
787 error
= pwmd_setopt(pwm
, PWMD_OPTION_PASSPHRASE
, password
);
794 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_PATH
, pinentry_path
);
801 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_DISPLAY
, display
);
808 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TTY
, tty
);
815 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TERM
, ttytype
);
822 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_LC_CTYPE
, lcctype
);
829 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_LC_MESSAGES
,
837 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TRIES
, tries
);
845 error
= pwmd_setopt(pwm
, PWMD_OPTION_STATUS_CB
, status_msg_cb
);
854 if (keyfile
&& (!local_pin
|| host
|| is_remote_url(url_string
))) {
855 struct inquire_s inq
;
857 memset(&inq
, 0, sizeof(inq
));
858 inq
.fd
= open(keyfile
, O_RDONLY
);
861 error
= gpg_error_from_syserror();
865 inq
.line
= pwmd_calloc(1, ASSUAN_LINELENGTH
);
868 error
= GPG_ERR_ENOMEM
;
872 error
= pwmd_open_inquire(pwm
, filename
, inquire_cb
, &inq
);
885 error
= pwmd_open(pwm
, filename
);
888 error
= pwmd_open2(pwm
, filename
);
891 error
= pwmd_open_async(pwm
, filename
);
895 error
= pwmd_open_async2(pwm
, filename
);
899 if (error
&& local_pin
&& error
== GPG_ERR_INV_PASSPHRASE
)
906 error
= process_cmd(pwm
, &result
, 0);
910 error
= pwmd_open2(pwm
, filename
);
913 error
= pwmd_open(pwm
, filename
);
915 if (error
&& local_pin
&& error
== GPG_ERR_INV_PASSPHRASE
)
927 struct inquire_s inq
;
930 memset(&inq
, 0, sizeof(inq
));
931 inq
.fd
= fileno(inquirefp
);
934 error
= gpg_error_from_syserror();
938 if (fstat(inq
.fd
, &st
) == -1) {
939 error
= gpg_error_from_syserror();
943 error
= pwmd_setopt(pwm
, PWMD_OPTION_INQUIRE_TOTAL
,
944 st
.st_size
? (size_t)st
.st_size
+strlen(p
) : 0);
949 inq
.line
= pwmd_calloc(1, ASSUAN_LINELENGTH
);
952 error
= GPG_ERR_ENOMEM
;
957 error
= pwmd_inquire(pwm
, inquire
, inquire_cb
, &inq
);
963 fcntl(STDIN_FILENO
, F_SETFL
, O_NONBLOCK
);
967 error
= process_cmd(pwm
, NULL
, 1);
972 n
= read(STDIN_FILENO
, command
, sizeof(command
));
978 error
= gpg_error_from_errno(errno
);
992 if (strcasecmp(p
, "BYE") == 0)
995 error
= pwmd_command(pwm
, &result
, command
);
996 memset(command
, 0, sizeof(command
));
1002 fwrite(result
, 1, strlen(result
), outfp
);
1007 memset(command
, 0, sizeof(command
));
1010 pwmd_free(password
);
1014 if (!error
&& save
&& filename
) {
1016 error
= pwmd_setopt(pwm
, PWMD_OPTION_ITERATIONS
, iter
);
1023 error
= pwmd_setopt(pwm
, PWMD_OPTION_CIPHER
, cipher
);
1033 error
= pwmd_command(pwm
, NULL
, "ISCACHED %s", filename
);
1035 if (error
&& error
!= GPG_ERR_NOT_FOUND
&&
1036 error
!= GPG_ERR_ENOENT
)
1042 error
= pwmd_getpin(pwm
, filename
, &p1
, PWMD_PINENTRY_SAVE
);
1047 error
= pwmd_getpin(pwm
, filename
, &password
,
1048 PWMD_PINENTRY_SAVE_CONFIRM
);
1055 if ((p1
|| password
) && ((!p1
&& password
) || (!password
&& p1
) ||
1056 strcmp(p1
, password
))) {
1058 pwmd_free(password
);
1066 error
= pwmd_setopt(pwm
, PWMD_OPTION_PASSPHRASE
, password
);
1069 pwmd_free(password
);
1076 error
= pwmd_command(pwm
, NULL
, "CLEARCACHE %s", filename
);
1082 error
= pwmd_setopt(pwm
, PWMD_OPTION_PASSPHRASE
, NULL
);
1090 if (keyfile
&& !local_pin
) {
1091 struct inquire_s inq
;
1093 memset(&inq
, 0, sizeof(inq
));
1094 inq
.fd
= open(keyfile
, O_RDONLY
);
1097 error
= gpg_error_from_syserror();
1101 inq
.line
= pwmd_calloc(1, ASSUAN_LINELENGTH
);
1104 error
= GPG_ERR_ENOMEM
;
1108 error
= pwmd_save_inquire(pwm
, inquire_cb
, &inq
);
1109 pwmd_free(inq
.line
);
1116 error
= pwmd_save(pwm
);
1119 error
= pwmd_save2(pwm
);
1122 error
= pwmd_save_async(pwm
);
1125 error
= pwmd_save_async2(pwm
);
1129 if (!error
&& method
>= 2)
1130 error
= process_cmd(pwm
, NULL
, 0);
1135 error
= pwmd_save2(pwm
);
1138 error
= pwmd_save(pwm
);
1152 pwmd_free(socketpath
);