password-store

Simple password manager using gpg and ordinary unix directories
git clone https://git.zx2c4.com/password-store
Log | Files | Refs | README | LICENSE

commit 7ed0a70aa6d1fd502ff5443bced462d6dedd8511
parent 7efccbc1c2bed9ad94102c5a962054e6b87b5390
Author: Jason A. Donenfeld <Jason@zx2c4.com>
Date:   Mon, 14 Apr 2014 14:59:07 +0200

find: add find/search command

This relies on a patched version of tree to work, unfortunately.
Hopefully upstream will accept our patch.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>

Diffstat:
Acontrib/tree-1.6.0-matchdirs.patch | 162+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mman/pass.1 | 24++++++++++++++++++++++++
Msrc/completion/pass.bash-completion | 2+-
Msrc/completion/pass.fish-completion | 2++
Msrc/completion/pass.zsh-completion | 1+
Msrc/password-store.sh | 23++++++++++++++++++++++-
6 files changed, 212 insertions(+), 2 deletions(-)

diff --git a/contrib/tree-1.6.0-matchdirs.patch b/contrib/tree-1.6.0-matchdirs.patch @@ -0,0 +1,162 @@ +From 57f931a7a8564379e7b2e5c31301dcd6c0e84b50 Mon Sep 17 00:00:00 2001 +From: "Jason A. Donenfeld" <Jason@zx2c4.com> +Date: Thu, 4 Apr 2013 08:43:05 -0700 +Subject: [PATCH] Add --matchdirs to check patterns against directories + +This causes pattern matching to include the full contents of any +directories that match the pattern, including sub-directories. +--- + doc/tree.1 | 10 +++++++++- + tree.c | 46 ++++++++++++++++++++++++++++++++++++++++------ + 2 files changed, 49 insertions(+), 7 deletions(-) + +diff --git a/doc/tree.1 b/doc/tree.1 +index 4b80852..7765f34 100644 +--- a/doc/tree.1 ++++ b/doc/tree.1 +@@ -21,7 +21,7 @@ + .SH NAME + tree \- list contents of directories in a tree-like format. + .SH SYNOPSIS +-\fBtree\fP [\fB-acdfghilnpqrstuvxACDFQNSUX\fP] [\fB-L\fP \fIlevel\fP [\fB-R\fP]] [\fB-H\fP \fIbaseHREF\fP] [\fB-T\fP \fItitle\fP] [\fB-o\fP \fIfilename\fP] [\fB--nolinks\fP] [\fB-P\fP \fIpattern\fP] [\fB-I\fP \fIpattern\fP] [\fB--inodes\fP] [\fB--device\fP] [\fB--noreport\fP] [\fB--dirsfirst\fP] [\fB--version\fP] [\fB--help\fP] [\fB--filelimit\fP \fI#\fP] [\fB--si\fP] [\fB--prune\fP] [\fB--du\fP] [\fB--timefmt\fP \fIformat\fP] [\fIdirectory\fP ...] ++\fBtree\fP [\fB-acdfghilnpqrstuvxACDFQNSUX\fP] [\fB-L\fP \fIlevel\fP [\fB-R\fP]] [\fB-H\fP \fIbaseHREF\fP] [\fB-T\fP \fItitle\fP] [\fB-o\fP \fIfilename\fP] [\fB--nolinks\fP] [\fB-P\fP \fIpattern\fP] [\fB-I\fP \fIpattern\fP] [\fB--inodes\fP] [\fB--device\fP] [\fB--noreport\fP] [\fB--dirsfirst\fP] [\fB--version\fP] [\fB--help\fP] [\fB--filelimit\fP \fI#\fP] [\fB--si\fP] [\fB--prune\fP] [\fB--du\fP] [\fB--timefmt\fP \fIformat\fP] [\fB--matchdirs\fP] [\fIdirectory\fP ...] + .br + .SH DESCRIPTION + \fITree\fP is a recursive directory listing program that produces a depth +@@ -123,6 +123,14 @@ Prints (implies -D) and formats the date according to the format string + which uses the \fBstrftime\fP(3) syntax. + .PP + .TP ++.B --matchdirs ++If a match pattern is specified by the -P option, this will cause the pattern ++to be applied to directory names (in addition to filenames). In the event of a ++match on the directory name, matching is disabled for the directory's ++contents.If the --prune option is used, empty folders that match the pattern ++will not be pruned. ++.PP ++.TP + .B -o \fIfilename\fP + Send output to \fIfilename\fP. + .PP +diff --git a/tree.c b/tree.c +index 19cf368..187613d 100644 +--- a/tree.c ++++ b/tree.c +@@ -28,7 +28,7 @@ static char *hversion="\t\t tree v1.6.0 %s 1996 - 2011 by Steve Baker and Thomas + bool dflag, lflag, pflag, sflag, Fflag, aflag, fflag, uflag, gflag; + bool qflag, Nflag, Qflag, Dflag, inodeflag, devflag, hflag, Rflag; + bool Hflag, siflag, cflag, Xflag, duflag, pruneflag; +-bool noindent, force_color, nocolor, xdev, noreport, nolinks, flimit, dirsfirst, nosort; ++bool noindent, force_color, nocolor, xdev, noreport, nolinks, flimit, dirsfirst, nosort, matchdirs; + char *pattern = NULL, *ipattern = NULL, *host = NULL, *title = "Directory Tree", *sp = " "; + char *timefmt = NULL; + const char *charset = NULL; +@@ -75,12 +75,13 @@ int main(int argc, char **argv) + char sizebuf[64]; + off_t size = 0; + mode_t mt; ++ bool needfulltree; + + q = p = dtotal = ftotal = 0; + aflag = dflag = fflag = lflag = pflag = sflag = Fflag = uflag = gflag = FALSE; + Dflag = qflag = Nflag = Qflag = Rflag = hflag = Hflag = siflag = cflag = FALSE; + noindent = force_color = nocolor = xdev = noreport = nolinks = FALSE; +- dirsfirst = nosort = inodeflag = devflag = Xflag = FALSE; ++ matchdirs = dirsfirst = nosort = inodeflag = devflag = Xflag = FALSE; + duflag = pruneflag = FALSE; + flimit = 0; + dirs = xmalloc(sizeof(int) * (maxdirs=4096)); +@@ -350,6 +351,11 @@ int main(int argc, char **argv) + Dflag = TRUE; + break; + } ++ if (!strncmp("--matchdirs",argv[i],11)) { ++ j = strlen(argv[i])-1; ++ matchdirs = TRUE; ++ break; ++ } + } + default: + fprintf(stderr,"tree: Invalid argument -`%c'.\n",argv[i][j]); +@@ -387,16 +393,17 @@ int main(int argc, char **argv) + parse_dir_colors(); + initlinedraw(0); + ++ needfulltree = duflag || pruneflag || matchdirs; + /* Set our listdir function and sanity check options. */ + if (Hflag) { +- listdir = (duflag || pruneflag)? html_rlistdir : html_listdir; ++ listdir = needfulltree ? html_rlistdir : html_listdir; + Xflag = FALSE; + } else if (Xflag) { +- listdir = (duflag || pruneflag)? xml_rlistdir : xml_listdir; ++ listdir = needfulltree ? xml_rlistdir : xml_listdir; + colorize = FALSE; + colored = FALSE; /* Do people want colored XML output? */ + } else { +- listdir = (duflag || pruneflag)? unix_rlistdir : unix_listdir; ++ listdir = needfulltree ? unix_rlistdir : unix_listdir; + } + if (dflag) pruneflag = FALSE; /* You'll just get nothing otherwise. */ + +@@ -534,6 +541,7 @@ void usage(int n) + " --charset X Use charset X for terminal/HTML and indentation line output.\n" + " --filelimit # Do not descend dirs with more than # files in them.\n" + " --timefmt <f> Print and format time according to the format <f>.\n" ++ " --matchdirs Include directory names in -P pattern matching.\n" + " -o filename Output to file instead of stdout.\n" + " -------- File options ---------\n" + " -q Print non-printable characters as '?'.\n" +@@ -689,6 +697,8 @@ struct _info **getfulltree(char *d, u_long lev, dev_t dev, off_t *size, char **e + struct _info **dir, **sav, **p, *sp; + struct stat sb; + int n; ++ u_long lev_tmp; ++ char *tmp_pattern = NULL, *start_rel_path; + + *err = NULL; + if (Level >= 0 && lev > Level) return NULL; +@@ -696,7 +706,29 @@ struct _info **getfulltree(char *d, u_long lev, dev_t dev, off_t *size, char **e + stat(d,&sb); + dev = sb.st_dev; + } ++ ++ // if the directory name matches, turn off pattern matching for contents ++ if (matchdirs && pattern) { ++ lev_tmp = lev; ++ for (start_rel_path = d + strlen(d); start_rel_path != d; --start_rel_path) { ++ if (*start_rel_path == '/') ++ --lev_tmp; ++ if (lev_tmp <= 0) { ++ if (*start_rel_path) ++ ++start_rel_path; ++ break; ++ } ++ } ++ if (patmatch(start_rel_path,pattern) == 1) { ++ tmp_pattern = pattern; ++ pattern = NULL; ++ } ++ } + sav = dir = read_dir(d,&n); ++ if (tmp_pattern) { ++ pattern = tmp_pattern; ++ tmp_pattern = NULL; ++ } + if (dir == NULL) { + *err = scopy("error opening dir"); + return NULL; +@@ -745,7 +777,9 @@ struct _info **getfulltree(char *d, u_long lev, dev_t dev, off_t *size, char **e + saveino((*dir)->inode, (*dir)->dev); + (*dir)->child = getfulltree(path,lev+1,dev,&((*dir)->size),&((*dir)->err)); + } +- if (pruneflag && (*dir)->child == NULL) { ++ // prune empty folders, unless they match the requested pattern ++ if (pruneflag && (*dir)->child == NULL && ++ !(matchdirs && pattern && patmatch((*dir)->name,pattern) == 1)) { + sp = *dir; + for(p=dir;*p;p++) *p = *(p+1); + n--; +-- +1.9.2 + diff --git a/man/pass.1 b/man/pass.1 @@ -72,6 +72,11 @@ by using the .BR tree (1) program. This command is alternatively named \fBlist\fP. .TP +\fBfind\fP \fIpass-names\fP... +List names of passwords inside the tree that match \fIpass-names\fP by using the +.BR tree (1) +program. This command is alternatively named \fBsearch\fP. +.TP \fBshow\fP [ \fI--clip\fP, \fI-c\fP ] \fIpass-name\fP Decrypt and print a password named \fIpass-name\fP. If \fI--clip\fP or \fI-c\fP is specified, do not print the password but instead copy the first line to the @@ -169,6 +174,25 @@ Password Store .br Alternatively, "\fBpass ls\fP". .TP +Find existing passwords in store that match .com +.B zx2c4@laptop ~ $ pass find .com +.br +Search Terms: .com +.br +\[u251C]\[u2500]\[u2500] Business +.br +\[u2502] \[u251C]\[u2500]\[u2500] some-silly-business-site.com +.br +\[u2514]\[u2500]\[u2500] Email +.br + \[u251C]\[u2500]\[u2500] donenfeld.com +.br + \[u2514]\[u2500]\[u2500] zx2c4.com +.br + +.br +Alternatively, "\fBpass search .com\fP". +.TP Show existing password .B zx2c4@laptop ~ $ pass Email/zx2c4.com .br diff --git a/src/completion/pass.bash-completion b/src/completion/pass.bash-completion @@ -57,7 +57,7 @@ _pass() { COMPREPLY=() local cur="${COMP_WORDS[COMP_CWORD]}" - local commands="init ls show insert generate edit rm git help version" + local commands="init ls find show insert generate edit rm git help version" if [[ $COMP_CWORD -gt 1 ]]; then local lastarg="${COMP_WORDS[$COMP_CWORD-1]}" case "${COMP_WORDS[1]}" in diff --git a/src/completion/pass.fish-completion b/src/completion/pass.fish-completion @@ -106,3 +106,5 @@ complete -c $PROG -f -A -n '__fish_pass_uses_command git' -a 'commit' -d 'Commit complete -c $PROG -f -A -n '__fish_pass_uses_command git' -a 'push' -d 'Push changes to remote repo' complete -c $PROG -f -A -n '__fish_pass_uses_command git' -a 'pull' -d 'Pull changes from remote repo' complete -c $PROG -f -A -n '__fish_pass_uses_command git' -a 'log' -d 'View changelog' + +complete -c $PROG -f -A -n '__fish_pass_needs_command' -a find -d 'Command: find a password file or directory matching pattern' diff --git a/src/completion/pass.zsh-completion b/src/completion/pass.zsh-completion @@ -77,6 +77,7 @@ _pass () { subcommands=( "init:Initialize new password storage" "ls:List passwords" + "find:Find password files or directories based on pattern" "show:Decrypt and print a password" "insert:Insert a new password" "generate:Generate a new password using pwgen" diff --git a/src/password-store.sh b/src/password-store.sh @@ -43,6 +43,8 @@ usage() { Optionally reencrypt existing passwords using new gpg-id. $program [ls] [subfolder] List passwords. + $program find pass-names... + List passwords that match pass-names. $program [show] [--clip,-c] pass-name Show existing password and optionally put it on the clipboard. If put on the clipboard, it will be cleared in $CLIP_TIME seconds. @@ -71,7 +73,7 @@ usage() { } is_command() { case "$1" in - init|ls|list|show|insert|edit|generate|remove|rm|delete|git|help|--help|version|--version) return 0 ;; + init|ls|list|find|search|show|insert|edit|generate|remove|rm|delete|git|help|--help|version|--version) return 0 ;; *) return 1 ;; esac } @@ -268,6 +270,25 @@ case "$command" in exit 1 fi ;; + find|search) + if [[ -z "$@" ]]; then + echo "Usage: $program $command pass-names..." + exit 1 + fi + if ! tree --help |& grep -q "^ --matchdirs"; then + echo "ERROR: $program: incompatible tree command" + echo + echo "Your version of the tree command is missing the relevent patch to add the" + echo "--matchdirs switch. Please ask your distribution to patch your version of" + echo "tree with:" + echo " http://git.zx2c4.com/password-store/plain/contrib/tree-1.6.0-matchdirs.patch" + echo "Sorry for the inconvenience." + exit 1 + fi + terms="$@" + echo "Search Terms: $terms" + tree -l --noreport -P "*${terms// /*|*}*" --prune --matchdirs "$PREFIX" | tail -n +2 | sed 's/\.gpg$//' + ;; insert) multiline=0 noecho=1