# bash completion for john # # john can either be globally installed (e.g. in /usr/bin/john), # or it can be installed locally somewhere in a user's home directory. # It can be an official version, or a community encanced (jumbo) version, # with a variety of patches. # # Trying to build a perfect completion script will be hard. # So let's start with a first humble attempt. # If possible, I'd like to avoid the need to maintain this script whenever # john gets a new option. # # The code is ugly, many things can probably be done more efficiently. # At least, grep and sed are the only external commands used # # FIXME: # I put this file into the /etc/bash_completion.d directory. # A proper rollout is probably not that easy. # (Of course, this file is anything but ready for a rollout to end users.) # # TODO: # --wordlist=~user/filename or --wordlist=~/dir/file doesn't work, # but pressing [tab] expands this to something useful # Does anybody know a command where tab expansion and globbing works correctly? # # --rules= or --single= should use names of existing [List.Rules:...] sections # Currently a hard coded list of section names (NT single wordlist) is used # --incremental should use names of [Incremental:...] sections # Currently a hard coded list is used (All Alpha Digits Alnum LanMan) # --external should use names of [List.External:..-.] sections # (Just sections without a generate() function, if --wordlist, --incremental or --single # is present on the command line; with a generate() function, if none of these # options is used - WHAT IF the user adds a --wordlist option later?) # Currently a hard coded List is used: # Filter_Alpha Filter_Digits Filter_Alnum Filter_LanMan LanMan Double Parallel Strip Keyboard # DumbForce KnownForce DateTime Repeats Subsets AtLeast1-Simple AtLeast1-Generic Policy AppendLuhn # --rules=, --single=, --incremental=, --external= need to take into account a --config file # which might have been specified on the command line, correct default john.conf locations # (/etc/, ~/.john/, or ./, depending on build options), and included config files... # FIXME: The user might have used -co: instead of --config..., the user might specify # --config=... later (should we really scan config files for sections referenced by other # command line options, to skip config files which do not have the right sections?) # Currently -opt is expanded to --option or --option=... # FIXME: With includes all this doesn't get easier (what if the john version which is used # doesn't even support these fancy additions?) # FIXME: To be able to locate the correct .conf and .rec files, we might even need a new # john option to know the values of JOHN_SYSTEMWIDE ... (e.g. --build-options) # --restore= and --status= expansion should search for existing .rec files in the correct directory. # --mkpc and other options which are not mentioned in the usage output are currently ignored # Several other options probably are still not considered. # Should I support -option instead of --option? (currently -option gets replaced with --option # during expansion. # Should I support --option:val, -option:val, or even -opt:val instead of just --option=val? # # grep,sed, and ls are used to process john's usage info or to list .rec files... have grep && have sed && have ls && _john() { local first cur options valopts compreplya compreplyb encodings formats subformats sessions COMPREPLY=() _get_comp_words_by_ref -n = cur # we need to make sure we run the correct program, not some other program # called john which is located somewhere in $PATH first="${COMP_WORDS[0]}" #first=`which ${first} 2>/dev/null` # Most options are listed at the begin of the line, but the line with the --pipe option # does have trailing spaces, and --stdin is mentioned after --wordlist=FILE. # # all options (the '=' will be emoved for options with an optional value) options="" # FIXME: How do I suppress the error message if someone tries to be clever: cd run; ./john --[tab] ??? options=`${first} |grep '^ *--'|sed 's#^ *\([a-z=-]*\).*$#\1#'|sed 's#--wordlist=#--wordlist=\n--stdin#'|sed 's#--subformat=#--subformat=LIST#'` if [[ "_$options_" == "__" ]] ; then compopt -F _filedir_xspec return 0 fi # Just the options that can be used together with a value, even if that value is optional # (That means, for a jumbo build, --rules doesn't get a trailing space, but for the john version # distributed by fedora16, --rules does get a trailing space during expansion. # The same applies for --show) valopts=`${first}|grep '^ *--[a-z\[-]*='|grep -v '^ *--subformat='|sed 's#^ *\([a-z=-]*\).*$#\1#'` case "$cur" in # --config= could be restricted to *.conf files (or *.ini files on Windows?), # --pot= to *.pot files, # --make-charset= should probably excluded, to make overwriting existing files harder # (otherwise it should be restricted to *.chr files) --markov=*) return 0 ;; --markov*) if echo "${options}" | grep ".*--subformat" > /dev/null ; then COMPREPLY=( $(compgen -W "--markov --markov=LEVEL[:START[:END[:LENGTH]]]" -- ${cur}) ) fi return 0 ;; --wordlist=*|--make-charset=*) #cur=${cur#*=} #_filedir expansion of --wordlist=~/te doesn't work # _filedir_xspec "_xspecs: bad array subscript" written to stderr _filedir_xspec 2> /dev/null return 0 ;; --pot=*) cur=${cur#*=} _filedir "pot" return 0 ;; --config=*) ## either expansion for tilde filename #_filedir_xspec ## or restrict completion to config files cur=${cur#*=} _filedir '@(conf|ini)' return 0 ;; --restore|--status|--incremental) COMPREPLY=( $(compgen -W "${cur}=" -- ${cur}) ) compopt -o nospace return 0 ;; --restore=*|--status=*) cur=${cur#*=} sessions=`ls *.rec|sed 's#\.rec$##'` COMPREPLY=( $(compgen -W "${sessions}" -- ${cur}) ) return 0 ;; ## May be I should not suggest all dynamic formats immediately. ## Instead, just suggest "dynamic" first, don't append a space ## if the current value gets expanded to --format=dynamic, ## and add a --format=dynamic*) switch before --format=*) # --format=*) # cur=${cur#*=} # formats=`${first} |grep -A 100 '^--format='|sed 's#^--format=[A-Za-z]*##'|sed 's#force hash type NAME:##'|sed 's#/# #g'|sed 's#dynamic_n##'|grep -v '^--'` # if echo "${options}" | grep ".*--subformat" > /dev/null ; then # subformats=`${first} --subformat=LIST|sed 's#^User##'|sed 's#^Format = \(dynamic_[0-9]*\).*$#\1#'` # else # subformats="" # fi # COMPREPLY=( $(compgen -W "${formats} ${subformats}" -- ${cur}) ) # return 0 # ;; --format=dynamic*) if echo "${options}" | grep ".*--subformat" > /dev/null ; then subformats=`${first} --subformat=LIST|sed 's#^User##'|sed 's#^Format = \(dynamic_[0-9]*\).*$#\1#'` cur=${cur#*=} COMPREPLY=( $(compgen -W "${subformats}" -- ${cur}) ) fi return 0 ;; --format=dy|--format=dyn|--format=dyna|--format=dynam|--format=dynami) if echo "${options}" | grep ".*--subformat" > /dev/null ; then cur=${cur#*=} COMPREPLY=( $(compgen -W "dynamic" -- ${cur}) ) compopt -o nospace fi return 0 ;; --format=*) cur=${cur#*=} formats=`${first} |grep -A 100 '^--format='|sed 's#^--format=[A-Za-z]*##'|sed 's#force hash type NAME:##'|sed 's#/# #g'|sed 's#dynamic_n#dynamic#'|grep -v '^--'` COMPREPLY=( $(compgen -W "${formats}" -- ${cur}) ) return 0 ;; --encoding=*) if echo "${options}" | grep ".*--encoding" > /dev/null ; then cur=${cur#*=} encodings=`${first} --encoding=LIST 2>&1|grep -v 'Supported encodings'|sed 's#[,)]##g'|sed 's#(or ##g'` COMPREPLY=( $(compgen -W "${encodings}" -- ${cur}) ) fi return 0 ;; --show) if echo "${valopts}" | grep ".*--rules" > /dev/null ; then COMPREPLY=( $(compgen -W "--show --show=LEFT" -- ${cur}) ) else COMPREPLY=( $(compgen -W "--show" -- ${cur}) ) fi return 0 ;; --show=l*) if echo "${valopts}" | grep ".*--rules" > /dev/null ; then cur=${cur#*=} COMPREPLY=( $(compgen -W "left" -- ${cur}) ) fi return 0 ;; --show=*) if echo "${valopts}" | grep ".*--rules" > /dev/null ; then cur=${cur#*=} COMPREPLY=( $(compgen -W "LEFT" -- ${cur}) ) fi return 0 ;; --rules) if echo "${valopts}" | grep ".*--rules" > /dev/null ; then COMPREPLY=( $(compgen -W "--rules --rules=NT --rules=single --rules=wordlist" -- ${cur}) ) else COMPREPLY=( $(compgen -W "--rules" -- ${cur}) ) fi return 0 ;; --single) if echo "${valopts}" | grep ".*--single" > /dev/null ; then COMPREPLY=( $(compgen -W "--single --single=NT --single=single --single=wordlist" -- ${cur}) ) else COMPREPLY=( $(compgen -W "--single" -- ${cur}) ) fi return 0 ;; --rules=*|--single=*) if echo "${valopts}" | grep ".*--rules" > /dev/null ; then cur=${cur#*=} COMPREPLY=( $(compgen -W "NT single wordlist" -- ${cur}) ) fi return 0 ;; --incremental=*) cur=${cur#*=} COMPREPLY=( $(compgen -W "All Alpha Digits Alnum LanMan" -- ${cur}) ) return 0 ;; --external=*) cur=${cur#*=} COMPREPLY=( $(compgen -W "Filter_Alpha Filter_Digits Filter_Alnum Filter_LanMan LanMan Double Parallel Strip Keyboard DumbForce KnownForce DateTime Repeats Subsets AtLeast1-Simple AtLeast1-Generic Policy AppendLuhn" -- ${cur}) ) return 0 ;; --regen-lost-salts=*) if echo "${options}" | grep ".*--regen-lost-salts" > /dev/null ; then cur=${cur#*=} COMPREPLY=( $(compgen -W "1 2 3 4 5" -- ${cur}) ) fi return 0 ;; --save-memory=*) cur=${cur#*=} COMPREPLY=( $(compgen -W "1 2 3" -- ${cur}) ) return 0 ;; --subformat=*) if echo "${options}" | grep ".*--subformat" > /dev/null ; then cur=${cur#*=} COMPREPLY=( $(compgen -W "LIST" -- ${cur}) ) fi return 0 ;; --session=|--users=|--groups=|--shells=|--salts=|--mem-file-size=|--field-separator-char=|--fix-state-delay=|--max-run-time=|--regen-lost-salts=) return 0 ;; #--regen-lost-salts=N regenerate lost salts for some hashes (see OPTIONS) #--plugin=NAME[,..] load this (these) dynamic plugin(s) -*) compreplya=`compgen -W "${options}" -- ${cur}` if [[ "_${compreplya}_" == "__" ]] ; then cur="-${cur}" compreplya=`compgen -W "${options}" -- ${cur}` fi compreplyb=`compgen -W "${valopts}" -- ${cur}` COMPREPLY=( $(compgen -W "${options}" -- ${cur}) ) if [[ "_$compreplya" == "_$compreplyb" ]] ; then compopt -o nospace fi return 0 ;; *) _filedir return 0 ;; esac } && complete -F _john john