cred documentation
[hband-tools.git] / user-tools / cred
blob53170af0604b5b3c71e5126fa7c192286fdd2079
1 #!/bin/bash
3 true <<'EOF'
4 =pod
6 =head1 NAME
8 cred - Credentials and secrets management in command line
10 =head1 SYNOPSIS
12 cred I<SUBCOMMAND> I<SITE> [I<ARGUMENTS>]
14 cred site I<SITE> I<SUBCOMMAND> [I<ARGUMENTS>]
16 =head1 DESCRIPTION
18 I<SITE> is a container of one or more properties, most often a website name.
19 It is represented in a directory in the credentials base dir.
20 You may also enter a directory path on the filesystem for I<SITE>.
21 You don't need to create a I<SITE>: it's created automatically when you write in it.
23 =head1 SUBCOMMANDS
25 =over 4
27 =item compscript
29 Output a bash script to setup tab-completion for the C<cred> command.
30 Use it by eg: C<eval "$(cred compscript)">
32 =item list-sites
34 =item dump [reveal-secrets | mask-secrets | hash-secrets]
36 Display all properties (and their values) of a given site.
37 Optional parameter is how secrets are displayed:
38 B<mask-secrets> is the default and replaces a secret string's characters by asterisk (C<*>).
39 B<hash-secrets> replaces secrets by a hash and the checksum algorithm' name
40 is appended to the hash with a tab, like: <TAB>B<hash-algo=>I<NAME>.
41 Finally B<reveal-secrets> displays secret strings in clear text just like ordinary properties.
43 =item generate-password
45 Generate a new password and put in B<PASSWORD> property.
46 And append its old value to the B<OLDPASSWORDS> property.
48 =item list-props
50 =item prop I<PROPERTY> [set I<NEW-VALUE> | edit | read | del | show | clip]
52 Manage properties of a given site.
53 The B<edit> instruction opens up the B<$EDITOR> (falling back to B<$VISUAL>) to edit the given property's value.
54 B<read> reads the new value from the STDIN (readline is supported if bash does support it, see C<help read> in bash(1)).
55 Secrets are read in no-echo mode.
56 The B<clip> instruction does the same as B<clip> subcommand, see below.
58 =item clip I<PROPERTY>
60 By B<clip> you may copy the value to the clipboard.
61 If you use CopyQ(1), secrets are prevented to get to CopyQ's clipboard items history.
63 =item fill-form I<PROPERTY> [I<PROPERTY> [...]]
65 Takes one or more property names and types their values to the window accessible by pressing Alt+Tab on your desktop.
66 Also presses <TAB> after each string, but does not press <RETURN>.
67 Obviously it's useful only with a B<$DESKTOP>.
68 Depends on xdotool(1).
70 =back
72 =head1 FILES
74 Hardcoded credentials' directory is F<~/cred>.
76 =head1 SEE ALSO
78 =over 4
80 =back
82 =cut
84 EOF
88 # need to set these shell options before they are relied upon when bash parses the big { ... } block later down.
89 shopt -s nocasematch
90 shopt -s expand_aliases
91 # aliases in non-interactive bash script are not evaluated in the same scope where they are defined,
92 # but they are in the big { ... } block below.
93 alias set_site='if [ -z "${site:-}" ]; then set_site_func "${1:-}"; shift; fi;'
96 set -eE
97 set -o pipefail
98 set -u
100 . /usr/lib/tool/bash-utils
101 . /usr/lib/tool/ansi-codes
103 TAB=$'\t'
105 print_traceback()
107 local i stack_size=${#FUNCNAME[@]}
108 for (( i=1; i<stack_size; i++ )); do
109 local func="${FUNCNAME[$i]}"
110 [[ $func = "" ]] && func=MAIN
111 local linen="${BASH_LINENO[$(( i - 1 ))]}"
112 local src="${BASH_SOURCE[$i]}"
113 [[ "$src" = "" ]] && src=non_file_source
115 echo -n "$ANSI_bold$ANSI_fg_black"
116 echo " function: $func"
117 echo " file: $src (line $linen)"
118 echo -n "$ANSI_reset"
120 local line_number_text_sep='| '
121 nl -ba -w ${#linen} -s " $line_number_text_sep" "$src" | grep -C 2 -E "^\s*$linen " |\
122 prefixlines " " |\
123 sed -e "s/^\(\s*$linen\) \(.\{${#line_number_text_sep}\}\)\(.*\)/\1-\2$ANSI_italic\3$ANSI_normal/" |\
124 sd '^' "$ANSI_bold$ANSI_fg_black" | sd '$' "$ANSI_reset"
125 done
128 trap 'print_traceback >&2' ERR
130 set_site_func()
132 local param=$1
133 if [ -z "$1" ]
134 then
135 errx 1 "Enter site name or directory!"
136 elif [ -d "$param" ]
137 then
138 site=`basename "$param"`
139 else
140 site=$1
144 is_secret()
146 [[ $1 =~ password|key|cvc|secret|pin ]]
149 do_clip()
151 local prop=$1
152 if type copyq 1>/dev/null 2>&1 && is_secret "$prop"
153 then
154 copyq_monitoring=`copyq monitoring`
155 if [ "$copyq_monitoring" = true ]
156 then
157 copyq disable
159 copyq copy -
160 if [ "$copyq_monitoring" = true ]
161 then
162 copyq enable
164 else
165 xclip -i -selection clipboard
170 basedir=~/cred
171 site=''
174 if [ "${1:-}" = site ]
175 then
176 shift || true
177 set_site
180 subcmd=${1:-}
181 shift || true
185 case "$subcmd" in
186 (dump)
187 set_site
188 options=("$@")
191 cd "$basedir/$site"
192 GLOBIGNORE=${GLOBIGNORE:-}${GLOBIGNORE:+:}".:..:.[!/.]*:..[!/]*:-*"
193 for file in *
195 if [ ! -f "$file" ]
196 then
197 continue
199 if is_secret "$file"
200 then
201 if in_list reveal-secrets "${options[@]}"
202 then
203 true
204 elif in_list hash-secrets "${options[@]}"
205 then
206 hash=`cat "$file" | md5sum | cut -c 1-32`
207 echo "$file$TAB$hash$TAB""hash-algo=md5"
208 continue
209 else
210 echo "$file"$'\t'"*****"
211 continue
215 grep . "$file" | prefixlines "$file"$'\t'
216 done
218 site=${site%/}
219 if [[ $site =~ / ]]
220 then
221 parent=${site%/*}
222 cred dump "$parent" "${options[@]}"
226 (list-sites)
227 find -L "$basedir" -path "$basedir/${1:-}*" -type d -printf "%P\n"
229 (prop|clip)
230 set_site
231 prop=${1:?'enter property name!'}
232 shift
233 if [ $# = 0 ]
234 then
235 case "$subcmd" in
236 (prop)
237 if is_secret "$prop"
238 then
239 warnx "$prop is a secret."
240 warnx "enter 'cred [...] prop [...] $prop show' explicitely, or"
241 warnx "more preferably 'cred [...] prop [...] $prop clip' to copy to the clipboard!"
242 false
243 else
244 cat "$basedir/$site/$prop"
247 (clip)
248 cat "$basedir/$site/$prop" | do_clip "$prop"
250 esac
251 elif [ "$subcmd" = prop ]
252 then
253 case "$1" in
254 (set)
255 shift
256 val=$*
257 mkdir -p "$basedir/$site"
258 printf '%s' "$val" > "$basedir/$site/$prop"
260 (edit)
261 mkdir -p "$basedir/$site"
262 "${EDITOR:-$VISUAL}" "$basedir/$site/$prop"
264 (read)
265 if is_secret "$prop"
266 then
267 read -s -p "$prop: " -e val
268 else
269 read -p "$prop: " -e val
271 printf '%s' "$val" > "$basedir/$site/$prop"
273 (del)
274 rm "$basedir/$site/$prop"
276 (show)
277 cat "$basedir/$site/$prop"
279 (clip)
280 cat "$basedir/$site/$prop" | do_clip "$prop"
283 false "invalid argument"
285 esac
288 (fill-form)
289 set_site
290 if [ $# -gt 0 ]
291 then
292 strings=''
293 for prop in "$@"
295 val=`cat "$basedir/$site/$prop"`
296 strings="$strings$val"$'\t'
297 done
298 xdotool key Alt+Tab type "$strings"
299 else
300 errx 1 "Enter property names in order to fill the form on the window brought to focus by Alt+Tab."
303 (list-props)
304 set_site
305 find "$basedir/$site" -type f -printf "%P\n"
307 (generate-password)
308 set_site
309 newpwd=$(pwgen -y 8 1)$(pwgen -y 8 1)
310 [ -n "$newpwd" ]
311 pwdfile=$basedir/$site/PASSWORD
312 mkdir -p "$basedir/$site"
313 if [ -s "$pwdfile" ]
314 then
315 cur_pwd=`cat "$pwdfile"`
316 atime=`stat -c %x "$pwdfile"`
317 now=`date +'%F %T.%N %z'`
318 echo "$atime $now $cur_pwd" >> "$basedir/$site/OLDPASSWORDS"
320 printf '%s' "$newpwd" > "$pwdfile"
322 (compscript)
323 cat <<'EOF'
324 _autocomplete_cred() {
325 local compreply
326 local curr_word=${COMP_WORDS[$COMP_CWORD]}
327 local prev_word=${COMP_WORDS[$[COMP_CWORD - 1]]}
328 local site_subcmds="list-sites dump generate-password list-props prop clip fill-form"
330 case $COMP_CWORD in
332 compreply="compscript site $site_subcmds"
335 local site=$curr_word; site=${site/#'~/'/$HOME/}
336 compreply=`cred list-sites "$site"`
339 local subcmd=${COMP_WORDS[1]}
340 local site=${COMP_WORDS[2]}; site=${site/#'~/'/$HOME/}
341 local cword_idx=$COMP_CWORD
342 if [ "$subcmd" = site ]
343 then
344 if [ $COMP_CWORD = 3 ]
345 then
346 compreply="$site_subcmds"
347 else
348 subcmd=${COMP_WORDS[3]}
349 let cword_idx-=1
352 case "$subcmd" in
353 (prop)
354 case $cword_idx in
356 compreply=`cred list-props "$site"`
359 compreply="set edit read del show clip"
361 esac
363 (clip|fill-form)
364 compreply=`cred list-props "$site"`
366 (dump)
367 case $cword_idx in
369 compreply="reveal-secrets mask-secrets hash-secrets"
371 esac
372 esac
374 esac
375 COMPREPLY=($(compgen -W "$compreply" -- "${COMP_WORDS[$COMP_CWORD]}"))
376 return 0
378 complete -F _autocomplete_cred cred
379 # use this in your bash session by eg: eval "$(cred compscript)"
383 warnx 'Use tab-completion!'
384 warnx 'Example: eval "$(cred compscript)"'
385 false
387 esac
389 exit