make getpeername() return the original socket address which before it was intercepted
[hband-tools.git] / user-tools / cred
blob5c7285732998093a55d9b0d4f318601b60773501
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>, most often a website name, is a container of one or more properties.
19 But it can be anything you want to tie properties to,
20 typically passwords, keys, pin codes, API tokens as secrets and
21 username, email address, etc. as ordinary properties.
23 I<SITE> is represented in a directory in the credentials base dir.
24 You may also enter a directory path on the filesystem for I<SITE>.
25 You don't need to create a I<SITE>: it's created automatically when you write in it.
27 For websites and other services you have more than one account or identity for,
28 recommended to organize them into subdirectories like: I<SITE>/I<IDENTITY>,
29 eg: C<mail.example.net/joe@example.net> and C<mail.example.net/jane@example.net>.
31 =head1 SUBCOMMANDS
33 =over 4
35 =item compscript
37 Output a bash script to setup tab-completion for the C<cred> command.
38 Use it by eg: C<eval "$(cred compscript)">
40 =item list-sites
42 =item dump [reveal-secrets | mask-secrets | hash-secrets]
44 Display all properties (and their values) of a given site.
45 Optional parameter is how secrets are displayed:
46 B<mask-secrets> is the default and replaces a secret string with 5 asterisks (C<*****>) uniformly (so number of chars are not leaked).
47 B<hash-secrets> replaces secrets by a hash and the checksum algorithm' name
48 is appended to the hash with a tab, like: <TAB>B<hash-algo=>I<NAME>.
49 Finally B<reveal-secrets> displays secret strings in clear text just like ordinary properties.
51 Those properies are considered to be secret at the moment which contain at least one of these words (case insensitive) :
52 B<pass>, B<key>, B<cvc>, B<secret>, B<pin>, B<code>, B<token>.
54 =item generate-password
56 Generate a new password and put in B<PASSWORD> property;
57 append its old value to the B<OLDPASSWORDS> property;
58 copy the new one to the clipboard.
60 =item list-props
62 =item prop I<PROPERTY> [set I<NEW-VALUE> | edit | read | del | show | reveal | clip]
64 Manage properties of a given site.
65 See individual instruction descriptions at the subcommands below which are aliases to these B<prop ...> commands.
67 =item set I<PROPERTY> I<NEW-VALUE>
69 =item edit I<PROPERTY>
71 Open up the B<$EDITOR> (falling back to B<$VISUAL>) to edit the given property's value.
72 =item read I<PROPERTY>
74 Read the new value from the STDIN (readline is supported if bash does support it, see C<help read> in bash(1)).
75 Secrets are read in no-echo mode.
77 =item del I<PROPERTY>
79 =item show I<PROPERTY>
81 =item reveal I<PROPERTY>
83 Subcommand B<show> shows only non-secrets.
84 Enter B<reveal> to show secrets as well.
86 =item clip I<PROPERTY>
88 By B<clip> you may copy the value to the clipboard.
89 If you use CopyQ(1), secrets are prevented to get to CopyQ's clipboard items history.
91 =item fill-form I<PROPERTY> [I<PROPERTY> [...]]
93 Takes one or more property names and types their values to the window accessible by pressing Alt+Tab on your desktop.
94 Also presses <TAB> after each string, but does not press <RETURN>.
95 A single dot (C<.>) is a pseudo I<PROPERTY> name: if it's given, nothing will be typed in its place,
96 but <TAB> is still pressed after it.
97 Use it if the form has fields which you don't want to fill in.
98 Obviously it's useful only with a B<$DESKTOP>.
99 Depends on xdotool(1).
101 =back
103 =head1 FILES
105 Credentials directory is hardcoded to F<~/cred>.
107 =head1 SEE ALSO
109 =over 4
111 =back
113 =cut
119 # need to set these shell options before they are relied upon when bash parses the big { ... } block later down.
120 shopt -s nocasematch
121 shopt -s expand_aliases
122 # aliases in non-interactive bash script are not evaluated in the same scope where they are defined,
123 # but they are in the big { ... } block below.
124 alias set_site='if [ -z "${site:-}" ]; then set_site_func "${1:-}"; shift; fi;'
127 set -eE
128 set -o pipefail
129 set -u
131 . /usr/lib/tool/bash-utils
133 colors_supported()
135 if [ -n "${NO_COLOR:-}" ]; then return 1; fi
136 if [ -n "${CLICOLOR_FORCE:-}" ]; then return 0; fi
137 [ -t 1 ]
139 if colors_supported; then . /usr/lib/tool/ansi-codes; fi
141 TAB=$'\t'
143 print_traceback()
145 local i stack_size=${#FUNCNAME[@]}
146 for (( i=1; i<stack_size; i++ )); do
147 local func="${FUNCNAME[$i]}"
148 [[ $func = "" ]] && func=MAIN
149 local linen="${BASH_LINENO[$(( i - 1 ))]}"
150 local src="${BASH_SOURCE[$i]}"
151 [[ "$src" = "" ]] && src=non_file_source
153 echo -n "${ANSI_bold:-}${ANSI_fg_black:-}"
154 echo " function: $func"
155 echo " file: $src (line $linen)"
156 echo -n "${ANSI_reset:-}"
158 local line_number_text_sep='| '
159 nl -ba -w ${#linen} -s " $line_number_text_sep" "$src" | grep -C 2 -E "^\s*$linen " |\
160 prefixlines " " |\
161 sed -e "s/^\(\s*$linen\) \(.\{${#line_number_text_sep}\}\)\(.*\)/\1-\2${ANSI_italic:-}\3${ANSI_normal:-}/" |\
162 sd '^' "${ANSI_bold:-}${ANSI_fg_black:-}" | sd '$' "${ANSI_reset:-}"
163 done
166 trap 'print_traceback >&2' ERR
168 set_site_func()
170 local param=$1
171 if [ -z "$1" ]
172 then
173 errx 1 "Enter site name or directory!"
174 elif [ -d "$param" ]
175 then
176 site=`basename "$param"`
177 else
178 site=$1
182 is_secret()
184 [[ $1 =~ pass|key|cvc|secret|pin|code|token ]]
187 do_clip()
189 local prop=$1
190 if type copyq 1>/dev/null 2>&1 && is_secret "$prop"
191 then
192 copyq_monitoring=`copyq monitoring`
193 if [ "$copyq_monitoring" = true ]
194 then
195 copyq disable
197 copyq copy -
198 if [ "$copyq_monitoring" = true ]
199 then
200 copyq enable
202 else
203 xclip -i -selection clipboard
207 prop()
209 local prop=${1:?'enter property name!'}
210 shift
211 local instruct=${1:?enter an instruction: set, edit, read, del, show, reveal, clip}
212 shift
213 case "$instruct" in
214 (set)
215 [ $# -gt 0 ] # 'prop ... set' needs at least 1 argument
216 val=$*
217 mkdir -p "$basedir/$site"
218 printf '%s' "$val" > "$basedir/$site/$prop"
220 (edit)
221 mkdir -p "$basedir/$site"
222 "${EDITOR:-$VISUAL}" "$basedir/$site/$prop"
224 (read)
225 if is_secret "$prop"
226 then
227 read -s -p "$site/$prop: " -e val
228 else
229 read -p "$site/$prop: " -e val
231 mkdir -p "$basedir/$site"
232 printf '%s' "$val" > "$basedir/$site/$prop"
234 (del)
235 rm "$basedir/$site/$prop"
237 (show)
238 if is_secret "$prop"
239 then
240 false # this property is deemed to be a secret
241 else
242 cat "$basedir/$site/$prop"
245 (reveal)
246 cat "$basedir/$site/$prop"
248 (clip)
249 cat "$basedir/$site/$prop" | do_clip "$prop"
252 false # invalid instruction given
254 esac
258 basedir=~/cred
259 site=''
262 if [ "${1:-}" = site ]
263 then
264 shift || true
265 set_site
268 subcmd=${1:-}
269 shift || true
273 case "$subcmd" in
274 (dump)
275 set_site
276 options=("$@")
279 cd "$basedir/$site"
280 GLOBIGNORE=${GLOBIGNORE:-}${GLOBIGNORE:+:}".:..:.[!/.]*:..[!/]*:-*"
281 for file in *
283 if [ ! -f "$file" ]
284 then
285 continue
287 if is_secret "$file"
288 then
289 if in_list reveal-secrets "${options[@]}"
290 then
291 true
292 elif in_list hash-secrets "${options[@]}"
293 then
294 hash=`cat "$file" | md5sum | cut -c 1-32`
295 echo "$file$TAB$hash$TAB""hash-algo=md5"
296 continue
297 else
298 echo "$file"$'\t'"*****"
299 continue
303 grep . "$file" | prefixlines "$file"$'\t' || true
304 done
306 site=${site%/}
307 if [[ $site =~ / ]]
308 then
309 parent=${site%/*}
310 cred dump "$parent" "${options[@]}"
314 (list-sites)
315 find -L "$basedir" -path "$basedir/${1:-}*" -type d -printf "%P\n"
317 (prop)
318 set_site
319 prop "$@"
321 (set|edit|read|del|show|reveal|clip)
322 set_site
323 prop=$1
324 shift
325 prop "$prop" "$subcmd" "$@"
327 (fill-form)
328 set_site
329 if [ $# -gt 0 ]
330 then
331 strings=''
332 for prop in "$@"
334 if [ "$prop" = . ]
335 then
336 val=''
337 else
338 val=`cat "$basedir/$site/$prop"`
340 strings="$strings$val"$'\t'
341 done
342 xdotool key Alt+Tab type "$strings"
343 else
344 errx 1 "Enter property names in order to fill the form on the window brought to focus by Alt+Tab."
347 (list-props)
348 set_site
349 if [ -e "$basedir/$site" ]
350 then
351 find "$basedir/$site" -type f -printf "%P\n"
354 (generate-password)
355 set_site
356 newpwd=$(pwgen -y 8 1)$(pwgen -y 8 1)
357 [ -n "$newpwd" ]
358 pwdfile=$basedir/$site/PASSWORD
359 mkdir -p "$basedir/$site"
360 if [ -s "$pwdfile" ]
361 then
362 cur_pwd=`cat "$pwdfile"`
363 atime=`stat -c %x "$pwdfile"`
364 now=`date +'%F %T.%N %z'`
365 echo "$atime $now $cur_pwd" >> "$basedir/$site/OLDPASSWORDS"
367 printf '%s' "$newpwd" > "$pwdfile"
368 printf '%s' "$newpwd" | do_clip PASSWORD
370 (compscript)
371 cat <<'EOF'
372 _autocomplete_cred() {
373 local compreply
374 local curr_word=${COMP_WORDS[$COMP_CWORD]}
375 local prev_word=${COMP_WORDS[$[COMP_CWORD - 1]]}
376 # NOTE if change $prop_subcmds, align it with the switch case below!
377 local prop_subcmds="set edit read del show reveal clip"
378 local site_subcmds="list-sites dump generate-password list-props prop $prop_subcmds fill-form"
380 case $COMP_CWORD in
382 compreply="compscript site $site_subcmds"
385 local site=$curr_word; site=${site/#'~/'/$HOME/}
386 compreply=`cred list-sites "$site"`
389 local subcmd=${COMP_WORDS[1]}
390 local site=${COMP_WORDS[2]}; site=${site/#'~/'/$HOME/}
391 local cword_idx=$COMP_CWORD
392 if [ "$subcmd" = site ]
393 then
394 if [ $COMP_CWORD = 3 ]
395 then
396 compreply="$site_subcmds"
397 else
398 subcmd=${COMP_WORDS[3]}
399 let cword_idx-=1
402 case "$subcmd" in
403 (prop)
404 case $cword_idx in
406 compreply=`cred list-props "$site"`
409 compreply=$prop_subcmds
411 esac
413 # NOTE: align with $prop_subcmds!
414 (fill-form|set|edit|read|del|show|reveal|clip)
415 compreply=`cred list-props "$site"`
416 if [ "$subcmd" = set ]
417 then
418 common_properties="EMAIL USERNAME PASSWORD PIN KEY TOKEN"
419 compreply="$compreply $common_properties"
422 (dump)
423 case $cword_idx in
425 compreply="reveal-secrets mask-secrets hash-secrets"
427 esac
428 esac
430 esac
431 COMPREPLY=($(compgen -W "$compreply" -- "${COMP_WORDS[$COMP_CWORD]}"))
432 return 0
434 complete -F _autocomplete_cred cred
435 # use this in your bash session by eg: eval "$(cred compscript)"
439 warnx 'Use tab-completion!'
440 warnx 'Example: eval "$(cred compscript)"'
441 false
443 esac
445 exit