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
32 #include <sys/select.h>
35 #include <sys/types.h>
42 #ifdef HAVE_GETOPT_LONG
47 #include "getopt_long.h"
50 #ifdef HAVE_LIBREADLINE
51 # if defined(HAVE_READLINE_READLINE_H)
52 # include <readline/readline.h>
53 # elif defined(HAVE_READLINE_H)
54 # include <readline.h>
55 # endif /* !defined(HAVE_READLINE_H) */
56 #endif /* HAVE_LIBREADLINE */
58 #ifdef HAVE_READLINE_HISTORY
59 # if defined(HAVE_READLINE_HISTORY_H)
60 # include <readline/history.h>
61 # elif defined(HAVE_HISTORY_H)
64 #endif /* HAVE_READLINE_HISTORY */
67 #define N_(msgid) gettext(msgid)
71 #define DEFAULT_PORT 22
81 static void show_error(gpg_error_t error
)
83 fprintf(stderr
, "ERR %i %s\n", gpg_err_code(error
), pwmd_strerror(error
));
86 static void usage(const char *pn
, int status
)
88 fprintf(status
== EXIT_FAILURE
? stderr
: stdout
, N_(
89 "Read a PWMD protocol command from standard input.\n\n"
90 "Usage: pwmc [options] [file]\n"
93 " pinentry method (0=pwmd, 1=libpwmd, 2=pwmd async, "
98 " --host, -h <hostname>\n"
99 " connect to the specified hostname\n"
102 " alterate port (22)\n"
104 " --user <username>\n"
105 " SSH username (default is the invoking user)\n"
108 " use the ssh agent for authentication\n"
110 " --identity, -i <filename>\n"
111 " SSH identity file\n"
113 " --known-hosts, -k <filename>\n"
114 " known host's file (for server validation)\n"
116 " --get-hostkey, -g\n"
117 " retrieve the remote SSH host key and exit\n"
120 " try connecting via IPv4 only\n"
123 " try connecting via IPv6 only\n"
127 " a url string to parse\n"
130 " disable showing of status messages from the server\n"
132 " --name, -n <string>\n"
133 " set the client name\n"
135 " --socket <filename>\n"
136 " local socket to connect to (~/.pwmd/socket)\n"
138 " --passphrase, -P <string>\n"
139 " passphrase to use (disables pinentry use)\n"
141 " --key-file <filename>\n"
142 " obtain the passphrase from the specified filename\n"
145 " the passphrase is base64 encoded\n"
147 " --timeout <seconds>\n"
148 " pinentry timeout\n"
151 " number of pinentry tries before failing (3)\n"
153 " --pinentry <path>\n"
154 " the full path to the pinentry binary (server default)\n"
156 " --ttyname, -y <path>\n"
157 " tty that pinentry will use\n"
159 " --ttytype, -t <string>\n"
160 " pinentry terminal type (default is $TERM)\n"
163 " pinentry display (default is $DISPLAY)\n"
165 " --lc-ctype <string>\n"
166 " locale setting for pinentry\n"
168 " --lc-messages <string>\n"
169 " locale setting for pinentry\n"
172 " --local-pinentry\n"
173 " force using a local pinentry\n"
176 " use a shell like interface to pwmd (allows more than one command)\n"
178 " --output-fd <FD>\n"
179 " redirect command output to the specified file descriptor\n"
181 " --inquire <COMMAND>\n"
182 " the specified command (with any options) uses a server inquire while\n"
183 " command data is read via the inquire file descriptor (stdin)\n"
185 " --inquire-fd <FD>\n"
186 " read inquire data from the specified file descriptor (stdin)\n"
188 " --cipher <string>\n"
189 " the cipher to use when saving (see pwmd(1))\n"
192 " send the SAVE command before exiting\n"
195 " like --save, but ask for a passphrase\n"
197 " --iterations, -I <N>\n"
198 " encrypt with the specified number of iterations when saving\n"
202 fprintf(status
== EXIT_FAILURE
? stderr
: stdout
, N_(
204 "A url string (specified with --url) may be in the form of:\n"
205 " file://[path/to/socket]\n"
207 " ssh[46]://[username@]hostname[:port][,identity,known_hosts]\n"
213 static gpg_error_t
inquire_cb(void *user
, const char *cmd
, gpg_error_t rc
,
214 char **data
, size_t *len
)
216 struct inquire_s
*inq
= user
;
224 /* The first part of the command data. */
232 *len
= read(inq
->fd
, inq
->line
, ASSUAN_LINELENGTH
);
235 return gpg_error_from_syserror();
240 return *len
? 0 : GPG_ERR_EOF
;
243 static int status_msg_cb(void *data
, const char *line
)
245 fprintf(stderr
, "%s\n", line
);
249 static gpg_error_t
process_cmd(pwm_t
*pwm
, char **result
, int input
, int once
)
258 pwmd_fd_t pfds
[nfds
];
259 struct timeval tv
= {0, 0};
262 rc
= pwmd_get_fds(pwm
, pfds
, &nfds
);
268 s
= pwmd_process(pwm
, &rc
, result
);
272 for (i
= 0, n
= 0; i
< nfds
; i
++) {
273 FD_SET(pfds
[i
].fd
, &rfds
);
274 n
= pfds
[i
].fd
> n
? pfds
[i
].fd
: n
;
278 FD_SET(STDIN_FILENO
, &rfds
);
280 nfds
= select(n
+1, &rfds
, NULL
, NULL
, once
? &tv
: NULL
);
283 rc
= gpg_error_from_errno(errno
);
287 if (input
&& FD_ISSET(STDIN_FILENO
, &rfds
))
290 s
= pwmd_process(pwm
, &rc
, result
);
294 } while (s
== ASYNC_PROCESS
);
300 static gpg_error_t
knownhost_cb(void *data
, const char *host
, const char *key
,
304 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
);
306 rc
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TITLE
, buf
);
312 return pwmd_getpin(pwm
, NULL
, NULL
, PWMD_PINENTRY_CONFIRM
);
315 static int is_remote_url(const char *str
)
317 if (strstr(str
, "file://") || strstr(str
, "local://"))
324 static gpg_error_t
send_inquire(int fd
, const char *command
)
326 struct inquire_s inq
;
330 if (fstat(fd
, &st
) == -1)
331 return gpg_error_from_syserror();
333 rc
= pwmd_setopt(pwm
, PWMD_OPTION_INQUIRE_TOTAL
, st
.st_size
);
338 memset(&inq
, 0, sizeof(inq
));
340 inq
.line
= pwmd_calloc(1, ASSUAN_LINELENGTH
);
343 return GPG_ERR_ENOMEM
;
346 rc
= pwmd_inquire(pwm
, command
, inquire_cb
, &inq
);
351 #ifdef HAVE_LIBREADLINE
352 static int interactive_hook(void)
354 process_cmd(pwm
, NULL
, 0, 1);
358 static gpg_error_t
do_interactive()
362 fprintf(stderr
, "WARNING: interactive mode doesn't use secure memory!\n");
364 rl_event_hook
= &interactive_hook
;
370 line
= readline("pwm> ");
379 #ifdef HAVE_READLINE_HISTORY
383 /* INQUIRE <COMMAND [options ...]> */
384 if (!strncasecmp(line
, "inquire ", 8)) {
385 fprintf(stderr
, "Press CTRL-D to end.\n");
386 rc
= send_inquire(STDIN_FILENO
, line
+8);
388 /* INQUIRE_FILE <FILENAME> <COMMAND [options ...]> */
389 else if (!strncasecmp(line
, "inquire_file ", 13)) {
390 char *p
= strchr(line
+13, ' ');
391 char *f
= p
? line
+13 : NULL
;
394 if (!f
|| !*f
|| !*p
) {
395 fprintf(stderr
, "Invalid number of command arguments.\n");
400 f
[strlen(f
) - strlen(p
++)] = 0;
401 fd
= open(f
, O_RDONLY
);
404 fprintf(stderr
, "%s\n", strerror(errno
));
409 rc
= send_inquire(fd
, p
);
414 rc
= pwmd_command(pwm
, &result
, line
);
419 fprintf(stderr
, "ERR %i: %s\n", rc
, pwmd_strerror(rc
));
420 else if (result
&& *result
)
421 printf("%s%s", result
, result
[strlen(result
)-1] != '\n' ? "\n" : "");
431 int main(int argc
, char *argv
[])
434 #ifdef HAVE_LIBREADLINE
437 char *password
= NULL
;
438 char *keyfile
= NULL
;
440 char *filename
= NULL
;
441 char *socketpath
= NULL
;
442 char command
[ASSUAN_LINELENGTH
], *p
;
443 int ret
= EXIT_SUCCESS
;
447 char *pinentry_path
= NULL
;
448 char *display
= NULL
, *tty
= NULL
, *ttytype
= NULL
, *lcctype
= NULL
,
450 int outfd
= STDOUT_FILENO
;
451 FILE *outfp
= stdout
;
452 int inquirefd
= STDIN_FILENO
;
453 FILE *inquirefp
= stdin
;
455 char *clientname
= "pwmc";
456 char *inquire
= NULL
;
465 int port
= DEFAULT_PORT
;
466 char *username
= NULL
;
468 char *known_hosts
= NULL
;
470 int prot
= PWMD_IP_ANY
;
477 int lock_on_open
= 1;
482 char *url_string
= NULL
;
483 /* The order is important. */
489 OPT_HOST
, OPT_PORT
, OPT_IDENTITY
, OPT_KNOWN_HOSTS
, OPT_USER
,
490 OPT_GET_HOSTKEY
, OPT_IPV4
, OPT_IPV6
, OPT_USE_AGENT
,
492 OPT_URL
, OPT_LOCAL
, OPT_FORCE_SAVE
, OPT_TTYNAME
, OPT_TTYTYPE
,
493 OPT_DISPLAY
, OPT_LC_CTYPE
, OPT_LC_MESSAGES
, OPT_TIMEOUT
, OPT_TRIES
,
494 OPT_PINENTRY
, OPT_PASSPHRASE
, OPT_KEYFILE
, OPT_BASE64
, OPT_SOCKET
,
495 OPT_NOLOCK
, OPT_SAVE
, OPT_ITERATIONS
, OPT_OUTPUT_FD
, OPT_INQUIRE
,
496 OPT_INQUIRE_FD
, OPT_NO_STATUS
, OPT_NAME
, OPT_VERSION
, OPT_HELP
,
498 #ifdef HAVE_LIBREADLINE
502 const struct option long_opts
[] = {
504 { "debug", 1, 0, 0 },
507 { "host", 1, 0, 'h' },
508 { "port", 1, 0, 'p' },
509 { "identity", 1, 0, 'i' },
510 { "known-hosts", 1, 0, 'k' },
511 { "user", 1, 0, 'u' },
512 { "get-hostkey", 0, 0, 'g' },
513 { "ipv4", 0, 0, '4' },
514 { "ipv6", 0, 0, '6' },
515 { "use-agent", 0, 0, 0 },
518 { "local-pinentry", 0, 0 },
519 { "force-save", 0, 0 },
520 { "ttyname", 1, 0, 'y' },
521 { "ttytype", 1, 0, 't' },
522 { "display", 1, 0, 'd' },
523 { "lc-ctype", 1, 0, 0 },
524 { "lc-messages", 1, 0, 0 },
525 { "timeout", 1, 0, 0 },
526 { "tries", 1, 0, 0 },
527 { "pinentry", 1, 0, 0 },
528 { "passphrase", 1, 0, 'P' },
529 { "key-file", 1, 0, 0 },
530 { "base64", 0, 0, 0 },
531 { "socket", 1, 0, 0 },
532 { "no-lock", 0, 0, 0 },
533 { "save", 0, 0, 'S' },
534 { "iterations", 1, 0, 'I' },
535 { "output-fd", 1, 0, 0 },
536 { "inquire", 1, 0, 0 },
537 { "inquire-fd", 1, 0, 0 },
538 { "no-status", 0, 0, 0 },
539 { "name", 1, 0, 'n' },
540 { "version", 0, 0, 0 },
542 { "cipher", 1, 0, 0 },
543 #ifdef HAVE_LIBREADLINE
544 { "interactive", 0, 0 },
549 const char *optstring
= "46h:p:i:k:u:gy:t:d:P:I:Sn:";
551 const char *optstring
= "y:t:d:P:I:Sn:";
556 setlocale(LC_ALL
, "");
557 bindtextdomain("libpwmd", LOCALEDIR
);
560 if (!strcmp(basename(argv
[0]), "pwmsh"))
563 while ((opt
= getopt_long(argc
, argv
, optstring
, long_opts
, &opt_index
)) != -1) {
565 /* Handle long options without a short option part. */
570 method
= atoi(optarg
);
597 save
= force_save
= 1;
600 lcctype
= pwmd_strdup(optarg
);
602 case OPT_LC_MESSAGES
:
603 lcmessages
= pwmd_strdup(optarg
);
606 timeout
= atoi(optarg
);
609 tries
= atoi(optarg
);
613 socketpath
= pwmd_strdup(optarg
);
619 inquirefd
= atoi(optarg
);
620 inquirefp
= fdopen(inquirefd
, "r");
624 err(EXIT_FAILURE
, "%i", inquirefd
);
628 outfd
= atoi(optarg
);
629 outfp
= fdopen(outfd
, "w");
633 err(EXIT_FAILURE
, "%i", outfd
);
641 printf("%s (pwmc)\n%s\n\n"
642 "Compile-time features:\n"
663 #ifdef HAVE_LIBREADLINE
669 , PACKAGE_STRING
, PACKAGE_BUGREPORT
);
672 pinentry_path
= optarg
;
675 usage(argv
[0], EXIT_SUCCESS
);
679 #ifdef HAVE_LIBREADLINE
680 case OPT_INTERACTIVE
:
685 usage(argv
[0], EXIT_FAILURE
);
697 host
= pwmd_strdup(optarg
);
703 ident
= pwmd_strdup(optarg
);
706 username
= pwmd_strdup(optarg
);
709 known_hosts
= pwmd_strdup(optarg
);
728 iter
= strtol(optarg
, NULL
, 10);
732 password
= pwmd_strdup(optarg
);
733 memset(optarg
, 0, strlen(optarg
));
740 usage(argv
[0], EXIT_FAILURE
);
748 if (host
&& !get
&& !ident
) {
750 usage(argv
[0], EXIT_FAILURE
);
755 usage(argv
[0], EXIT_FAILURE
);
762 filename
= argv
[optind
];
764 pwm
= pwmd_new(clientname
);
770 if (host
|| url_string
) {
773 if (prot
!= PWMD_IP_ANY
) {
774 error
= pwmd_setopt(pwm
, PWMD_OPTION_IP_VERSION
, prot
);
780 error
= pwmd_setopt(pwm
, PWMD_OPTION_KNOWNHOST_CB
, knownhost_cb
);
785 error
= pwmd_setopt(pwm
, PWMD_OPTION_KNOWNHOST_DATA
, clientname
);
790 error
= pwmd_setopt(pwm
, PWMD_OPTION_SSH_AGENT
, use_agent
);
800 error
= pwmd_get_hostkey_async(pwm
, host
, port
);
803 errx(EXIT_FAILURE
, "%s: %s", host
, pwmd_strerror(error
));
805 error
= process_cmd(pwm
, &hostkey
, 0, 0);
810 printf("%s", hostkey
);
818 error
= pwmd_connect_url_async(pwm
, url_string
);
820 error
= pwmd_ssh_connect_async(pwm
, host
, port
, ident
, username
,
826 error
= process_cmd(pwm
, NULL
, 0, 0);
836 error
= pwmd_get_hostkey(pwm
, host
, port
, &hostkey
);
841 printf("%s", hostkey
);
849 error
= pwmd_connect_url(pwm
, url_string
);
851 error
= pwmd_ssh_connect(pwm
, host
, port
, ident
, username
, known_hosts
);
862 error
= pwmd_connect_url(pwm
, url_string
);
864 error
= pwmd_connect(pwm
, socketpath
);
872 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TITLE
, NULL
);
873 error
= pwmd_socket_type(pwm
, &s
);
878 if (s
== PWMD_SOCKET_SSH
&& force_save
&& !local_pin
) {
879 error
= GPG_ERR_WRONG_KEY_USAGE
;
884 error
= pwmd_command(pwm
, &result
, "VERSION");
894 usage(argv
[0], EXIT_FAILURE
);
898 if (filename
&& lock_on_open
) {
899 error
= pwmd_setopt(pwm
, PWMD_OPTION_LOCK_ON_OPEN
, 1);
906 error
= pwmd_setopt(pwm
, PWMD_OPTION_BASE64
, 1);
913 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TIMEOUT
, timeout
);
919 if (local_pin
&& filename
&& !keyfile
) {
920 error
= pwmd_command(pwm
, NULL
, "ISCACHED %s", filename
);
922 if (error
== GPG_ERR_NOT_FOUND
) {
924 if (try++ == local_tries
)
933 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TIMEOUT
, 0);
935 error
= pwmd_getpin(pwm
, filename
, &password
,
936 try > 1 ? PWMD_PINENTRY_OPEN_FAILED
: PWMD_PINENTRY_OPEN
);
941 error
= pwmd_setopt(pwm
, PWMD_OPTION_PASSPHRASE
, password
);
949 else if (error
&& error
!= GPG_ERR_ENOENT
)
954 error
= pwmd_setopt(pwm
, PWMD_OPTION_PASSPHRASE
, password
);
961 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_PATH
, pinentry_path
);
968 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_DISPLAY
, display
);
975 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TTY
, tty
);
982 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TERM
, ttytype
);
989 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_LC_CTYPE
, lcctype
);
996 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_LC_MESSAGES
,
1004 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TRIES
, tries
);
1012 error
= pwmd_setopt(pwm
, PWMD_OPTION_STATUS_CB
, status_msg_cb
);
1021 if (keyfile
&& (!local_pin
|| host
|| is_remote_url(url_string
))) {
1022 struct inquire_s inq
;
1024 memset(&inq
, 0, sizeof(inq
));
1025 inq
.fd
= open(keyfile
, O_RDONLY
);
1028 error
= gpg_error_from_syserror();
1032 inq
.line
= pwmd_calloc(1, ASSUAN_LINELENGTH
);
1035 error
= GPG_ERR_ENOMEM
;
1039 error
= pwmd_open_inquire(pwm
, filename
, inquire_cb
, &inq
);
1040 pwmd_free(inq
.line
);
1052 error
= pwmd_open(pwm
, filename
);
1055 error
= pwmd_open2(pwm
, filename
);
1058 error
= pwmd_open_async(pwm
, filename
);
1062 error
= pwmd_open_async2(pwm
, filename
);
1066 if (error
&& local_pin
&& error
== GPG_ERR_INV_PASSPHRASE
)
1067 goto local_password
;
1073 error
= process_cmd(pwm
, &result
, 0, 0);
1077 error
= pwmd_open2(pwm
, filename
);
1080 error
= pwmd_open(pwm
, filename
);
1082 if (error
&& local_pin
&& error
== GPG_ERR_INV_PASSPHRASE
)
1083 goto local_password
;
1093 #ifdef HAVE_LIBREADLINE
1095 error
= do_interactive();
1105 int fd
= fileno(inquirefp
);
1108 error
= gpg_error_from_syserror();
1112 error
= send_inquire(fd
, inquire
);
1117 fcntl(STDIN_FILENO
, F_SETFL
, O_NONBLOCK
);
1121 error
= process_cmd(pwm
, NULL
, 1, 0);
1126 n
= read(STDIN_FILENO
, command
, sizeof(command
));
1129 if (errno
== EAGAIN
)
1132 error
= gpg_error_from_errno(errno
);
1146 if (strcasecmp(p
, "BYE") == 0)
1149 error
= pwmd_command(pwm
, &result
, command
);
1155 fwrite(result
, 1, strlen(result
), outfp
);
1161 pwmd_free(password
);
1165 if (!error
&& save
&& filename
) {
1167 error
= pwmd_setopt(pwm
, PWMD_OPTION_ITERATIONS
, iter
);
1174 error
= pwmd_setopt(pwm
, PWMD_OPTION_CIPHER
, cipher
);
1184 error
= pwmd_command(pwm
, NULL
, "ISCACHED %s", filename
);
1186 if (error
&& error
!= GPG_ERR_NOT_FOUND
&&
1187 error
!= GPG_ERR_ENOENT
)
1193 error
= pwmd_getpin(pwm
, filename
, &p1
, PWMD_PINENTRY_SAVE
);
1198 error
= pwmd_getpin(pwm
, filename
, &password
,
1199 PWMD_PINENTRY_SAVE_CONFIRM
);
1206 if ((p1
|| password
) && ((!p1
&& password
) || (!password
&& p1
) ||
1207 strcmp(p1
, password
))) {
1209 pwmd_free(password
);
1217 error
= pwmd_setopt(pwm
, PWMD_OPTION_PASSPHRASE
, password
);
1220 pwmd_free(password
);
1227 error
= pwmd_command(pwm
, NULL
, "CLEARCACHE %s", filename
);
1233 error
= pwmd_setopt(pwm
, PWMD_OPTION_PASSPHRASE
, NULL
);
1241 if (keyfile
&& !local_pin
) {
1242 struct inquire_s inq
;
1244 memset(&inq
, 0, sizeof(inq
));
1245 inq
.fd
= open(keyfile
, O_RDONLY
);
1248 error
= gpg_error_from_syserror();
1252 inq
.line
= pwmd_calloc(1, ASSUAN_LINELENGTH
);
1255 error
= GPG_ERR_ENOMEM
;
1259 error
= pwmd_save_inquire(pwm
, inquire_cb
, &inq
);
1260 pwmd_free(inq
.line
);
1267 error
= pwmd_save(pwm
);
1270 error
= pwmd_save2(pwm
);
1273 error
= pwmd_save_async(pwm
);
1276 error
= pwmd_save_async2(pwm
);
1280 if (!error
&& method
>= 2)
1281 error
= process_cmd(pwm
, NULL
, 0, 0);
1286 error
= pwmd_save2(pwm
);
1289 error
= pwmd_save(pwm
);
1295 memset(command
, 0, sizeof(command
));
1305 pwmd_free(socketpath
);