aboutsummaryrefslogtreecommitdiffstats
path: root/cli
diff options
context:
space:
mode:
Diffstat (limited to 'cli')
-rwxr-xr-xcli/icevault146
-rw-r--r--cli/icevault.193
2 files changed, 140 insertions, 99 deletions
diff --git a/cli/icevault b/cli/icevault
index 3b69b3a..c93a608 100755
--- a/cli/icevault
+++ b/cli/icevault
@@ -20,6 +20,7 @@ use strict;
use warnings;
our $VERSION = '0.1';
+my $NAME = 'icevault';
use Getopt::Long qw/:config posix_default no_ignore_case gnu_compat
bundling auto_version/;
use Encode qw/decode_utf8 encode_utf8/;
@@ -66,20 +67,6 @@ sub warning($@) {
myprintf \*STDERR, shift, @_;
}
-# Print usage and exit.
-sub usage($) {
- my $rv = shift;
- my $fh = $rv ? \*STDERR : \*STDOUT;
- print $fh "Usage: $0 [OPTIONS] [fill] scheme://hostname/identity\n"
- ." or: $0 [OPTIONS] insert [identity]\n"
- ." or: $0 [OPTIONS] dump scheme://hostname/identity\n"
- ." or: $0 [OPTIONS] clip scheme://hostname/identity\n"
- ." or: $0 [OPTIONS] edit scheme://hostname/identity\n"
- ." or: $0 [OPTIONS] ls [scheme://[hostname/[identity]]]\n"
- . "Consult the manual page for more information.\n";
- exit $rv;
-}
-
sub mysystem(@) {
system {$_[0]} @_;
error "C<%s> exited with value %d", $_[0], ($? >> 8) if $? and $? != -1;
@@ -594,35 +581,96 @@ sub sha256_file($) {
}
+
#######################################################################
-usage(1) unless @ARGV;
+unless (@ARGV) {
+ print STDERR "Usage: $NAME [COMMAND] [OPTION ...] [ARG ...]\n";
+ error "Missing command. Try C<%s> or consult the manpage for more information.", "$NAME --help";
+}
+
+my @USAGE = (
+ fill => "[-f, --force] [-p, --show-passwords] [-s, --socket=PATH] scheme://hostname/identity",
+ clip => "scheme://hostname/identity",
+ dump => "[-p, --show-passwords] scheme://hostname/identity",
+ edit => "scheme://hostname/identity",
+ insert => "[-f, --force] [-s, --socket=PATH] [identity]",
+ ls => "[-0, --zero] [scheme://[hostname/[identity]]]",
+);
+
+if ($ARGV[0] eq '--help' or $ARGV[0] eq '-?') {
+ my $default_cmd = shift @USAGE;
+ my $default_usage = shift @USAGE;
+ print "Usage: $NAME [$default_cmd] $default_usage\n";
+ while (@USAGE) {
+ my $cmd = shift @USAGE;
+ my $usage = shift @USAGE;
+ print " or: $NAME $cmd $usage\n";
+ }
+ myprintf "Try C<%s> or consult the manpage for more information.", "$NAME COMMAND --help";
+ exit 0;
+}
+
@ARGV = map { $LOCALE->decode($_) } @ARGV;
-my $confFilename = ($ENV{XDG_CONFIG_HOME} // "$ENV{HOME}/.data") . "/icevault";
-GetOptions(\%CONFIG, qw/debug show-passwords|p socket|s=s help|? zero|0/) or usage(1);
-usage(0) if $CONFIG{help};
+my $COMMAND = ($ARGV[0] =~ /\A[A-Za-z0-9-]+:\/\//aa or $ARGV[0] =~ /\A--?[^-]/) ? 'fill' : shift;
+
+# Print $COMMAND usage (detailed if --help)
+sub usage(@) {
+ my @opts = @_;
+ my %usage = @USAGE;
+ print "$NAME $COMMAND $usage{$COMMAND} \n";
+ if ($CONFIG{help}) {
+ if (@opts) {
+ print "Options:\n";
+ while (@opts) {
+ shift @opts;
+ print " ".shift(@opts)."\n";
+ }
+ }
+ printf "Consult the manpage for more information.\n";
+ exit 0;
+ } else {
+ myprintf "Try C<%s> or consult the manpage for more information.", "$NAME $COMMAND --help";
+ exit 1;
+ }
+}
+
+# Get options, load and validate config
+sub getopts(%) {
+ my @opts = @_;
+ my %opts = @opts;
+ usage(@opts) unless GetOptions(\%CONFIG, qw/debug help|?/, keys %opts) and !$CONFIG{help};
+ loadConfig();
+}
-# Load configuration
-my $command = $ARGV[0] =~ /\A[A-Za-z0-9-]+:\/\//aa ? 'fill' : shift;
+#######################################################################
# Process the commands
-if ($command eq '_complete') {
+
+if ($COMMAND eq '_complete') {
# used internaly for auto-completion
- usage(1) unless $#ARGV == 0;
+ GetOptions(\%CONFIG, qw/zero|0/) or die;
+ die unless $#ARGV == 0;
my $delim = $CONFIG{zero} ? "\0" : "\n";
print $LOCALE->encode($_), $delim foreach complete(shift @ARGV);
exit;
}
-elsif ($command eq '_geturi') {
+elsif ($COMMAND eq '_geturi') {
# used internaly for auto-completion
- usage(1) if @ARGV;
+ GetOptions(\%CONFIG, qw/socket|s=s/) or die;
+ die if @ARGV;
print $LOCALE->encode( &connect($CONFIG{socket}) ), "\n";
sendCommand 'QUIT';
exit;
}
-elsif ($command eq 'insert') {
- usage(1) unless $#ARGV < 1;
+
+elsif ($COMMAND eq 'insert') {
+ getopts( 'force|f' => "-f, --force \tOverwrite preexisting identity"
+ , 'socket|s=s' => "-s, --socket=PATH\tSpecifiy the path to the Icevault socket"
+ );
+ usage() unless $#ARGV < 1;
+
my $uri = &connect($CONFIG{socket});
myprintf "Importing HTML form from URI C<%s>", $uri;
@@ -660,7 +708,7 @@ elsif ($command eq 'insert') {
if ($r !~ /\A[^\P{Print}\/]+\z/) {
myprintf \*STDERR, "Invalid identity: C<%s>", $r;
}
- elsif (-e getIdentityFile "$uri/$r") {
+ elsif (-e getIdentityFile "$uri/$r" and !$CONFIG{force}) {
myprintf \*STDERR, "Identity C<%s> already exists", "$uri/$r";
}
else {
@@ -671,7 +719,7 @@ elsif ($command eq 'insert') {
}
my $filename = getIdentityFile "$uri/$id";
- error "Identity C<%s> already exists", "$uri/$id" if -e $filename;
+ error "Identity C<%s> already exists", "$uri/$id" if -e $filename and !$CONFIG{force};
my @passIdx = grepIdx { $_->{type} eq 'password' } @{$form->{fields}};
my @dontsave;
@@ -725,8 +773,13 @@ elsif ($command eq 'insert') {
saveIdentityFile $form, $filename;
}
-elsif ($command eq 'fill') {
- usage(1) unless $#ARGV == 0;
+elsif ($COMMAND eq 'fill') {
+ getopts( 'force|f' => "-f, --force \tDon't ask before updating the form"
+ , 'show-passwords|p=s' => "-p, --show-passwords\tDon't redact passwords"
+ , 'socket|s=s' => "-s, --socket=PATH \tSpecifiy the path to the Icevault socket"
+ );
+ usage() unless $#ARGV == 0;
+
my $id = shift;
my $filename = getIdentityFile $id;
error "No such identity C<%s>", $id unless -f $filename;
@@ -770,7 +823,7 @@ elsif ($command eq 'fill') {
$changed = 1;
}
- if ($pass->{value} eq '') { # fill the password with the known value
+ if ($pass->{value} eq '' or $CONFIG{force}) { # fill the password with the known value
$fill[$passIdx[0]] = $mypass->{value};
}
elsif ($mypass->{value} ne $pass->{value}) { # update the password
@@ -819,7 +872,8 @@ elsif ($command eq 'fill') {
my $myidx = shift @{$myfields{$name}};
my $idx = shift @{$fields{$name}};
next unless defined $myidx and defined $idx; # was taken care of before
- if ($form->{fields}->[$idx]->{value} eq '' and $myform->{fields}->[$myidx]->{value} ne '') {
+ if (($form->{fields}->[$idx]->{value} eq '' or $CONFIG{force})
+ and $myform->{fields}->[$myidx]->{value} ne '') {
# fill with the known value
$fill[$idx] = $myform->{fields}->[$myidx]->{value};
}
@@ -870,8 +924,10 @@ elsif ($command eq 'fill') {
}
}
-elsif ($command eq 'dump') {
- usage(1) unless $#ARGV == 0;
+elsif ($COMMAND eq 'dump') {
+ getopts('show-passwords|p=s' => "-p, --show-passwords\tDon't redact passwords");
+ usage() unless $#ARGV == 0;
+
my $id = shift;
my $filename = getIdentityFile $id;
error "No such identity C<%s>", $id unless -f $filename;
@@ -883,8 +939,10 @@ elsif ($command eq 'dump') {
print STDOUT (defined $LOCALE ? $LOCALE->encode($str) : $str)
}
-elsif ($command eq 'edit') {
- usage(1) unless $#ARGV == 0;
+elsif ($COMMAND eq 'edit') {
+ getopts();
+ usage() unless $#ARGV == 0;
+
my $id = shift;
my $filename = getIdentityFile $id;
error "No such identity C<%s>", $id unless -f $filename;
@@ -932,8 +990,10 @@ elsif ($command eq 'edit') {
}
}
-elsif ($command eq 'clip') {
- usage(1) unless $#ARGV == 0;
+elsif ($COMMAND eq 'clip') {
+ getopts();
+ usage() unless $#ARGV == 0;
+
my $id = shift;
my $filename = getIdentityFile $id;
error "No such identity C<%s>", $id unless -f $filename;
@@ -951,8 +1011,10 @@ elsif ($command eq 'clip') {
exit 0;
}
-elsif ($command eq 'ls') {
- usage(1) if $#ARGV > 0;
+elsif ($COMMAND eq 'ls') {
+ getopts( 'zero|0' => "-0, --zero\tUse NUL instead of newline as line delimiter" );
+ usage() if $#ARGV > 0;
+
my $prefix = shift @ARGV;
my @matches = complete $prefix // '', 1;
@@ -978,6 +1040,6 @@ elsif ($command eq 'ls') {
}
else {
- myprintf "Unknown command: C<%s>", $command;
- usage(1);
+ print STDERR "Usage: $NAME [COMMAND] [OPTION ...] [ARG ...]\n";
+ error "Unknown command C<%s>. Try C<%s> for more information.", $COMMAND, "$NAME --help";
}
diff --git a/cli/icevault.1 b/cli/icevault.1
index 7db6be9..0768b68 100644
--- a/cli/icevault.1
+++ b/cli/icevault.1
@@ -4,17 +4,7 @@
IceVault \- IceVault client user interface
.SH SYNOPSIS
-.B icevault\fR [\fIOPTIONS\fR] [\fBfill\fR] \fIscheme\fR://\fIhostname\fR/\fIidentity\fR
-.br
-.B icevault\fR [\fIOPTIONS\fR] \fBinsert\fR [\fIidentity\fR]
-.br
-.B icevault\fR [\fIOPTIONS\fR] \fBdump\fR \fIscheme\fR://\fIhostname\fR/\fIidentity\fR
-.br
-.B icevault\fR [\fIOPTIONS\fR] \fBclip\fR \fIscheme\fR://\fIhostname\fR/\fIidentity\fR
-.br
-.B icevault\fR [\fIOPTIONS\fR] \fBedit\fR \fIscheme\fR://\fIhostname\fR/\fIidentity\fR
-.br
-.B icevault\fR [\fIOPTIONS\fR] \fBls\fR [\fIscheme\fR://[\fIhostname\fR/[\fIidentity\fR]]]
+.B icevault\fR [\fICOMMAND\fR] [\fIOPTION\fR ...] [\fIARG\fR ...]
.SH DESCRIPTION
@@ -46,8 +36,10 @@ using \fIpwgen\fR(1).
.SH COMMANDS
+If \fICOMMAND\fR is omitted, \fBfill\fR is assumed.
+
.TP
-.B fill\fR \fIscheme\fR://\fIhostname\fR/\fIidentity\fR
+.B fill\fR [\fB-f\fR, \fB--force\fR] [\fB-p\fR, \fB--show-passwords\fR] [\fB-s\fR, \fB--socket=\fR\fIPATH\fR] \fIscheme\fR://\fIhostname\fR/\fIidentity\fR
If the scheme (resp. hostname) of the active tab of the active window is
not \fIscheme\fR (resp. \fIhostname\fR) the program assumes a phishing
attempt and aborts. Otherwise, the \fIidentity\fR file is decrypted and
@@ -60,11 +52,34 @@ If \fIidentity\fR has a single password whereas the webpage has 2 (resp.
3), a signup (resp. password changing) page is assumed, and a new
password is randomly generated using \fIpwgen\fR(1) if the fields are
left blank.
+Use \fB--socket=\fR\fIPATH\fR to specify the path to the IceVault
+socket. If \fB-f\fR is set, existing values on the browser are ignored.
+Passwords are redacted unless the flag \fB-p\fR is set.
+
+.TP
+.B clip\fR \fIscheme\fR://\fIhostname\fR/\fIidentity\fR
+Decrypt the \fIidentity\fR file and copy its first password to the
+clipboard using \fIxclip\fR(1), with a maximum number of pastes of 1.
+
+.TP
+.B dump\fR [\fB-p\fR, \fB--show-passwords\fR] \fIscheme\fR://\fIhostname\fR/\fIidentity\fR
+Decrypt the \fIidentity\fR file and dump its content on the standard
+output. Note that while the output is a valid YAML document, original
+formatting may not be preserved; in particular, comments and empty lines
+are stripped. Passwords are redacted unless the flag \fB-p\fR is set.
.TP
-.B insert\fR [\fIidentity\fR]
+.B edit\fR \fIscheme\fR://\fIhostname\fR/\fIidentity\fR
+Decrypt the \fIidentity\fR file to a temporary file and open it using
+the editor specified by the EDITOR environment variable (or \fIeditor\fR
+if EDITOR is unset). Upon exit, the file is reencrypted if the SHA-256
+digest of its content differs. Note that formatting and comments may
+not be preserved by subsequent updates of the \fIidentity\fR file.
+
+.TP
+.B insert\fR [\fB-f\fR, \fB--force\fR] [\fB-s\fR, \fB--socket=\fR\fIPATH\fR] [\fIidentity\fR]
Create a new \fIscheme\fR://\fIhostname\fR/\fIidentity\fR URI available
-for further \fBfill\fR and other commands.
+for further commands.
Store the first visible form on the active tab of the active window which
contains a password (or the first visible form with a non-empty field if
no visible form has a password). If \fIidentity\fR is omitted, it
@@ -74,33 +89,17 @@ password).
If the webpage has 2 (resp. 3), a signup (resp. password changing) page
is assumed, and a new password is randomly generated using
\fIpwgen\fR(1) if the fields are left blank.
+Use \fB--socket=\fR\fIPATH\fR to specify the path to the IceVault
+socket. If the flag \fB-f\fR is set, override the \fIidentity\fR file
+if it already exists (the default is to abort).
.TP
-.B dump\fR \fIscheme\fR://\fIhostname\fR/\fIidentity\fR
-Decrypt the \fIidentity\fR file and dump its content on the standard
-output. Note that while the output is a valid YAML document, original
-formatting may not be preserved; in particular, comments and empty lines
-are stripped.
+.B ls\fR [\fB-0\fR, \fB--zero\fR] [\fIscheme\fR://[\fIhostname\fR/[\fIidentity\fR]]]
+List content of the given identity prefix. If the flag \fB-0\fR is set,
+use NUL as line separator.
-.TP
-.B clip\fR \fIscheme\fR://\fIhostname\fR/\fIidentity\fR
-Decrypt the \fIidentity\fR file and copy the first password to the
-clipboard using \fIxclip\fR(1), with a maximum number of pastes of 1.
-.TP
-.B edit\fR \fIscheme\fR://\fIhostname\fR/\fIidentity\fR
-Decrypt the \fIidentity\fR file to a temporary file and opens it using
-the editor specified by the EDITOR environment variable. When the
-editor exits, the file is reencrypted if the SHA-256 digest of its
-content differs. Note that formatting and comments may not be preserved
-by subsequent updates of the \fIidentity\fR file.
-
-.TP
-.B ls\fR [\fIscheme\fR://[\fIhostname\fR/[\fIidentity\fR]]]
-List content of the given identity prefix.
-
-
-.SH OPTIONS
+.SH GLOBAL OPTIONS
.TP
.B \-\-debug
Turn on debug mode.
@@ -110,29 +109,9 @@ Turn on debug mode.
Output a brief help and exit.
.TP
-.B \-p\fR, \fB\-\-show\-passwords\fR
-By default passwords are redacted when printing forms to the standard
-output. This flags turns off this behavior.
-
-.TP
-.B \-s\fR \fIsockpath\fR, \fB\-\-socket=\fR\fIsockpath\fR
-Specify the path of the UNIX socket used to communicate with the
-browser. Can be an absolute path or a path relative to the default
-Firefox profile (or first profile found if there is no default profile)
-in the "~/.mozilla/firefox" directory.
-The socket path and permissions can be configured on the
-Iceweasel/Firefox side with the "extensions.icevault.socketPath" and
-"extensions.icevault.socketPerms" preferences in "about:config",
-respectively.
-
-.TP
.B \-\-version
Show the version number and exit.
-.TP
-.B \-0\fR, \fB\-\-zero
-With the \fBls\fR command, use NUL instead of newline as line delimiter.
-
.SH CONFIGURATION FILE