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
58 static void show_error(gpg_error_t error
)
60 fprintf(stderr
, "ERR %i %s\n", gpg_err_code(error
), pwmd_strerror(error
));
63 static void usage(const char *pn
, int status
)
65 fprintf(status
== EXIT_FAILURE
? stderr
: stdout
, N_(
66 "Read a PWMD protocol command from standard input.\n\n"
67 "Usage: pwmc [options] [file]\n"
70 " pinentry method (0=pwmd, 1=libpwmd, 2=pwmd async, "
75 " --host, -h <hostname>\n"
76 " connect to the specified hostname\n"
79 " alterate port (22)\n"
82 " SSH username (default is the invoking user)\n"
84 " --identity, -i <filename>\n"
85 " SSH identity file\n"
87 " --known-hosts, -k <filename>\n"
88 " known host's file (for server validation)\n"
90 " --get-hostkey, -g\n"
91 " retrieve the remote SSH host key and exit\n"
94 " try connecting via IPv4 only\n"
97 " try connecting via IPv6 only\n"
101 " a url string to parse\n"
104 " disable showing of status messages from the server\n"
106 " --name, -n <string>\n"
107 " set the client name\n"
109 " --socket <filename>\n"
110 " local socket to connect to (~/.pwmd/socket)\n"
112 " --passphrase, -P <string>\n"
113 " passphrase to use (disables pinentry use)\n"
115 " --timeout <seconds>\n"
116 " pinentry timeout\n"
119 " number of pinentry tries before failing (3)\n"
121 " --pinentry <path>\n"
122 " the full path to the pinentry binary (server default)\n"
124 " --ttyname, -y <path>\n"
125 " tty that pinentry will use\n"
127 " --ttytype, -t <string>\n"
128 " pinentry terminal type (default is TERM)\n"
131 " pinentry display (default is DISPLAY)\n"
133 " --lc-ctype <string>\n"
134 " locale setting for pinentry\n"
136 " --lc-messages <string>\n"
137 " locale setting for pinentry\n"
140 " --local-pinentry\n"
141 " force using a local pinentry\n"
143 " --output-fd <FD>\n"
144 " redirect command output to the specified file descriptor\n"
146 " --inquire-fd <FD>\n"
147 " read inquire data from the specified file descriptor\n"
149 " --cipher <string>\n"
150 " the cipher to use when saving\n"
153 " send the SAVE command before exiting\n"
156 " like --save, but ask for a passphrase\n"
158 " --iterations, -I <N>\n"
159 " encrypt with the specified number of iterations when saving\n"
163 fprintf(status
== EXIT_FAILURE
? stderr
: stdout
, N_(
165 "A url string (specified with --url) may be in the form of:\n"
166 " file://[path/to/socket]\n"
168 " ssh[46]://[username@]hostname[:port],identity,known_hosts\n"
179 static gpg_error_t
do_inquire(void *data
, const char *keyword
, gpg_error_t rc
,
180 char **result
, size_t *result_len
)
183 static char buf
[ASSUAN_LINELENGTH
];
186 struct inquire_s
*inq
= (struct inquire_s
*)data
;
189 memset(buf
, 0, sizeof(buf
));
197 snprintf(buf
, sizeof(buf
), "%s", inq
->data
);
198 pwmd_free(inq
->data
);
204 while ((c
= fgetc(inq
->fp
)) != EOF
) {
205 if (len
== sizeof(buf
)) {
215 memset(buf
, 0, sizeof(buf
));
224 static int status_msg_cb(void *data
, const char *line
)
226 fprintf(stderr
, "%s\n", line
);
230 static gpg_error_t
process_cmd(pwm_t
*pwm
, char **result
, int input
)
239 pwmd_fd_t pfds
[nfds
];
242 rc
= pwmd_get_fds(pwm
, pfds
, &nfds
);
248 s
= pwmd_process(pwm
, &rc
, result
);
252 for (i
= 0, n
= 0; i
< nfds
; i
++) {
253 FD_SET(pfds
[i
].fd
, &rfds
);
254 n
= pfds
[i
].fd
> n
? pfds
[i
].fd
: n
;
258 FD_SET(STDIN_FILENO
, &rfds
);
260 nfds
= select(n
+1, &rfds
, NULL
, NULL
, NULL
);
263 rc
= gpg_error_from_errno(errno
);
267 if (input
&& FD_ISSET(STDIN_FILENO
, &rfds
))
270 s
= pwmd_process(pwm
, &rc
, result
);
271 } while (s
== ASYNC_PROCESS
);
276 static gpg_error_t
knownhost_cb(void *data
, const char *host
, const char *key
,
280 char *buf
= pwmd_strdup_printf(N_("Password Manager Daemon: %s\n\nWhile attepting an SSH connection to %s, there was a problem verifying it's hostkey against the known and trusted hosts file. Would you like to treat this connection as trusted for this and future connections?"), (char *)data
, host
);
282 rc
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TITLE
, buf
);
288 return pwmd_getpin(pwm
, NULL
, NULL
, PWMD_PINENTRY_CONFIRM
);
291 int main(int argc
, char *argv
[])
294 char *password
= NULL
;
295 char *filename
= NULL
;
296 char *socketpath
= NULL
;
297 char command
[ASSUAN_LINELENGTH
], *p
;
298 int ret
= EXIT_SUCCESS
;
302 char *pinentry_path
= NULL
;
303 char *display
= NULL
, *tty
= NULL
, *ttytype
= NULL
, *lcctype
= NULL
,
305 int outfd
= STDOUT_FILENO
;
306 FILE *outfp
= stdout
;
307 int inquirefd
= STDIN_FILENO
;
308 FILE *inquirefp
= stdin
;
310 char *clientname
= "pwmc";
311 char *inquire
= NULL
;
320 int port
= DEFAULT_PORT
;
321 char *username
= NULL
;
323 char *known_hosts
= NULL
;
325 int prot
= PWMD_IP_ANY
;
335 char *url_string
= NULL
;
336 /* The order is important. */
342 OPT_HOST
, OPT_PORT
, OPT_IDENTITY
, OPT_KNOWN_HOSTS
, OPT_USER
,
343 OPT_GET_HOSTKEY
, OPT_IPV4
, OPT_IPV6
,
345 OPT_URL
, OPT_LOCAL
, OPT_FORCE_SAVE
, OPT_TTYNAME
, OPT_TTYTYPE
,
346 OPT_DISPLAY
, OPT_LC_CTYPE
, OPT_LC_MESSAGES
, OPT_TIMEOUT
, OPT_TRIES
,
347 OPT_PINENTRY
, OPT_PASSPHRASE
, OPT_SOCKET
, OPT_SAVE
, OPT_ITERATIONS
,
348 OPT_OUTPUT_FD
, OPT_INQUIRE_FD
, OPT_NO_STATUS
, OPT_NAME
, OPT_VERSION
,
349 OPT_HELP
, OPT_CIPHER
,
351 const struct option long_opts
[] = {
353 { "debug", 1, 0, 0 },
356 { "host", 1, 0, 'h' },
357 { "port", 1, 0, 'p' },
358 { "identity", 1, 0, 'i' },
359 { "known-hosts", 1, 0, 'k' },
360 { "user", 1, 0, 'u' },
361 { "get-hostkey", 0, 0, 'g' },
362 { "ipv4", 0, 0, '4' },
363 { "ipv6", 0, 0, '6' },
366 { "local-pinentry", 0, 0 },
367 { "force-save", 0, 0 },
368 { "ttyname", 1, 0, 'y' },
369 { "ttytype", 1, 0, 't' },
370 { "display", 1, 0, 'd' },
371 { "lc-ctype", 1, 0, 0 },
372 { "lc-messages", 1, 0, 0 },
373 { "timeout", 1, 0, 0 },
374 { "tries", 1, 0, 0 },
375 { "pinentry", 1, 0, 0 },
376 { "passphrase", 1, 0, 'P' },
377 { "socket", 1, 0, 0 },
378 { "save", 0, 0, 'S' },
379 { "iterations", 1, 0, 'I' },
380 { "output-fd", 1, 0, 0 },
381 { "inquire-fd", 1, 0, 0 },
382 { "no-status", 0, 0, 0 },
383 { "name", 1, 0, 'n' },
384 { "version", 0, 0, 0 },
386 { "cipher", 1, 0, 0 },
390 const char *optstring
= "46h:p:i:k:u:gy:t:d:P:I:Sn:";
392 const char *optstring
= "y:t:d:P:I:Sn:";
397 setlocale(LC_ALL
, "");
398 bindtextdomain("libpwmd", LOCALEDIR
);
401 while ((opt
= getopt_long(argc
, argv
, optstring
, long_opts
, &opt_index
)) != -1) {
403 /* Handle long options without a short option part. */
408 method
= atoi(optarg
);
421 save
= force_save
= 1;
424 lcctype
= pwmd_strdup(optarg
);
426 case OPT_LC_MESSAGES
:
427 lcmessages
= pwmd_strdup(optarg
);
430 timeout
= atoi(optarg
);
433 tries
= atoi(optarg
);
437 socketpath
= pwmd_strdup(optarg
);
440 inquirefd
= atoi(optarg
);
441 inquirefp
= fdopen(inquirefd
, "r");
445 err(EXIT_FAILURE
, "%i", inquirefd
);
449 outfd
= atoi(optarg
);
450 outfp
= fdopen(outfd
, "w");
454 err(EXIT_FAILURE
, "%i", outfd
);
462 printf("%s (pwmc)\n%s\n\n"
463 "Compile-time features:\n"
485 , PACKAGE_STRING
, PACKAGE_BUGREPORT
);
488 pinentry_path
= optarg
;
491 usage(argv
[0], EXIT_SUCCESS
);
496 usage(argv
[0], EXIT_FAILURE
);
508 host
= pwmd_strdup(optarg
);
514 ident
= pwmd_strdup(optarg
);
517 username
= pwmd_strdup(optarg
);
520 known_hosts
= pwmd_strdup(optarg
);
539 iter
= strtol(optarg
, NULL
, 10);
543 password
= pwmd_strdup(optarg
);
544 memset(optarg
, 0, strlen(optarg
));
551 usage(argv
[0], EXIT_FAILURE
);
559 if (host
&& !get
&& (!known_hosts
|| !ident
)) {
561 usage(argv
[0], EXIT_FAILURE
);
566 usage(argv
[0], EXIT_FAILURE
);
573 filename
= argv
[optind
];
575 pwm
= pwmd_new(clientname
);
582 if (prot
!= PWMD_IP_ANY
) {
583 error
= pwmd_setopt(pwm
, PWMD_OPTION_IP_VERSION
, prot
);
589 error
= pwmd_setopt(pwm
, PWMD_OPTION_KNOWNHOST_CB
, knownhost_cb
);
594 error
= pwmd_setopt(pwm
, PWMD_OPTION_KNOWNHOST_DATA
, clientname
);
604 error
= pwmd_get_hostkey_async(pwm
, host
, port
);
607 errx(EXIT_FAILURE
, "%s: %s", host
, pwmd_strerror(error
));
609 error
= process_cmd(pwm
, &hostkey
, 0);
614 printf("%s", hostkey
);
622 error
= pwmd_connect_url_async(pwm
, url_string
);
624 error
= pwmd_ssh_connect_async(pwm
, host
, port
, ident
, username
,
630 error
= process_cmd(pwm
, NULL
, 0);
640 error
= pwmd_get_hostkey(pwm
, host
, port
, &hostkey
);
645 printf("%s", hostkey
);
653 error
= pwmd_connect_url(pwm
, url_string
);
655 error
= pwmd_ssh_connect(pwm
, host
, port
, ident
, username
, known_hosts
);
666 error
= pwmd_connect_url(pwm
, url_string
);
668 error
= pwmd_connect(pwm
, socketpath
);
676 error
= pwmd_socket_type(pwm
, &s
);
681 if (s
== PWMD_SOCKET_SSH
&& force_save
&& !local_pin
) {
682 error
= GPG_ERR_WRONG_KEY_USAGE
;
687 error
= pwmd_command(pwm
, &result
, "VERSION");
697 usage(argv
[0], EXIT_FAILURE
);
702 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TIMEOUT
, timeout
);
708 if (local_pin
&& filename
) {
709 error
= pwmd_command(pwm
, NULL
, "ISCACHED %s", filename
);
711 if (error
== GPG_ERR_NOT_FOUND
) {
713 if (try++ == local_tries
)
722 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TIMEOUT
, 0);
724 error
= pwmd_getpin(pwm
, filename
, &password
,
725 try > 1 ? PWMD_PINENTRY_OPEN_FAILED
: PWMD_PINENTRY_OPEN
);
730 error
= pwmd_setopt(pwm
, PWMD_OPTION_PASSPHRASE
, password
);
738 else if (error
&& error
!= GPG_ERR_ENOENT
)
743 error
= pwmd_setopt(pwm
, PWMD_OPTION_PASSPHRASE
, password
);
750 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_PATH
, pinentry_path
);
757 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_DISPLAY
, display
);
764 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TTY
, tty
);
771 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TERM
, ttytype
);
778 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_LC_CTYPE
, lcctype
);
785 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_LC_MESSAGES
,
793 error
= pwmd_setopt(pwm
, PWMD_OPTION_PINENTRY_TRIES
, tries
);
801 error
= pwmd_setopt(pwm
, PWMD_OPTION_STATUS_CB
, status_msg_cb
);
812 error
= pwmd_open(pwm
, filename
);
815 error
= pwmd_open2(pwm
, filename
);
818 error
= pwmd_open_async(pwm
, filename
);
822 error
= pwmd_open_async2(pwm
, filename
);
826 if (error
&& local_pin
&& error
== GPG_ERR_INV_PASSPHRASE
)
833 error
= process_cmd(pwm
, &result
, 0);
835 error
= pwmd_open(pwm
, filename
);
837 if (error
&& local_pin
&& error
== GPG_ERR_INV_PASSPHRASE
)
846 error
= pwmd_command(pwm
, &result
, "LOCK");
852 fcntl(STDIN_FILENO
, F_SETFL
, O_NONBLOCK
);
857 error
= process_cmd(pwm
, NULL
, 1);
862 n
= read(STDIN_FILENO
, command
, sizeof(command
));
868 error
= gpg_error_from_errno(errno
);
876 if (n
&& command
[strlen(command
)-1] == '\n')
877 command
[strlen(command
)-1] = 0;
887 * This is a known INQUIRE command. We use pwmd_inquire() to send the
888 * data from the do_inquire() callback function.
890 if (strncasecmp(p
, "STORE ", 6) == 0) {
892 inquire
= (char *)"STORE";
894 else if (strncasecmp(p
, "IMPORT ", 7) == 0) {
896 inquire
= (char *)"IMPORT";
900 struct inquire_s
*inq
= (struct inquire_s
*)pwmd_malloc(sizeof(struct inquire_s
));
905 error
= gpg_error_from_errno(ENOMEM
);
909 fd
= fileno(inquirefp
);
912 error
= gpg_error_from_errno(errno
);
916 if (fstat(fd
, &st
) == -1) {
917 error
= gpg_error_from_errno(errno
);
921 error
= pwmd_setopt(pwm
, PWMD_OPTION_INQUIRE_TOTAL
,
922 st
.st_size
? (size_t)st
.st_size
+strlen(p
) : 0);
927 inq
->data
= pwmd_strdup(p
);
929 error
= pwmd_inquire(pwm
, inquire
, do_inquire
, inq
);
934 if (strcasecmp(p
, "BYE") == 0)
937 error
= pwmd_command(pwm
, &result
, command
);
938 memset(command
, 0, sizeof(command
));
944 fwrite(result
, 1, strlen(result
), outfp
);
949 memset(command
, 0, sizeof(command
));
953 if (!error
&& save
&& filename
) {
955 error
= pwmd_command(pwm
, &result
, "SET ITERATIONS=%i", iter
);
962 error
= pwmd_command(pwm
, NULL
, "SET CIPHER=%s", cipher
);
972 error
= pwmd_command(pwm
, NULL
, "ISCACHED %s", filename
);
974 if (error
&& error
!= GPG_ERR_NOT_FOUND
&&
975 error
!= GPG_ERR_ENOENT
)
981 error
= pwmd_getpin(pwm
, filename
, &p1
, PWMD_PINENTRY_SAVE
);
986 error
= pwmd_getpin(pwm
, filename
, &password
,
987 PWMD_PINENTRY_SAVE_CONFIRM
);
994 if ((p1
|| password
) && ((!p1
&& password
) || (!password
&& p1
) ||
995 strcmp(p1
, password
))) {
1005 error
= pwmd_setopt(pwm
, PWMD_OPTION_PASSPHRASE
, password
);
1008 pwmd_free(password
);
1015 error
= pwmd_command(pwm
, NULL
, "CLEARCACHE %s", filename
);
1021 error
= pwmd_setopt(pwm
, PWMD_OPTION_PASSPHRASE
, NULL
);
1032 error
= pwmd_save(pwm
);
1035 error
= pwmd_save2(pwm
);
1038 error
= pwmd_save_async(pwm
);
1041 error
= pwmd_save_async2(pwm
);
1045 if (!error
&& method
>= 2)
1046 error
= process_cmd(pwm
, NULL
, 0);
1049 error
= pwmd_save(pwm
);
1053 if (!error
&& filename
)
1054 error
= pwmd_command(pwm
, &result
, "UNLOCK");
1064 pwmd_free(socketpath
);