# bash completion for john and unique commands (John the Ripper) # # This software is Copyright © 2012 Frank Dittrich # and hereby released to the general public under the following terms: # Redistribution and use in source and binary forms, with or without # modification, are permitted. # # Minor improvements suggested by Aleksey Cherepanov have been # incorporated. # # # This file needs to be copied into the /etc/bash_completion.d/. # To make the new completion rules work, you need to logout and login, # or source /etc/bash_completion instead. # # Alternatively, just add a line # . /john.bash_completion # to your ~/.bashrc and logout/login. # # To use the same completion rules not just for john, but for # differently named binaries (say john-omp, john-sse2i, john-avx, # john-cuda, john-gpu, ...), # just use this command to get the current completion rule settings: # complete -p john # # If the output is # complete -F _john john # you can use this command to activate the same completion rules # for john-omp: # complete -F _john john-omp # # To use these completion rules permanently, you might add # complete -F _john john-omp # to your ~/.bashrc file. # # # The code is still ugly, many things can probably be done more efficiently. # Currently, grep, tr, and sed are the only external commands used. # Trying to build a perfect completion script will be hard. # # If possible, I'd like to avoid the need to maintain this script whenever # john gets a new option. # # 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. # # FIXME: Is using __expand_tilde_by_ref OK? # # FIXME: For some reason completion for --option= only works # if the cursor is at the end of the command, # i.e. [[ ${COMP_POINT} -eq ${#COMP_LINE} ]] # not if some words follow the one to be completed... # If ${cur#*=} is not empty, completion works even in the middle # of the command line # This is annoying if I want to complete --rules= in # ./john --rules= --config=test.conf # # FIXME: If there is a word -- preceding the current word # which is to be completed, it cannot be an option, so file names # should be used for completion. # # FIXME: Should completion for --make-charset really list existing .chr files? # # FIXME: Should I generally use LC_ALL=C, not just in a few places? # (This could also be a little bit faster.) # # TODO: # --wordlist=~user/filename or --wordlist=~/dir/file doesn't work, # but pressing [tab] expands this to something useful # Where to fix this? In john? Some bash config option? # # --external should not use all names of [List.External:..-.] sections, but # just sections without a generate() function, if --wordlist, --incremental or --single # is present on the command line; and just those with a generate() function, # if none of these options is used - WHAT IF the user adds a --wordlist option later? # # Should expanding an abbreviated option to its long form also be done by john itself # (requires new john option)? # different implementations for completion logic for these options: # --rules --single --incremental --restore --status # for __john_completion=[2|any other value] # # john ## on my system, have() is a dummy function which always return "yes", so get rid of calling it... ## have grep && have sed && have tr _john() { local first cur options valopts compreplya compreplyb encodings formats subformats list hidden dir cmd i ver ver1 ver2 ver3 prev words COMPREPLY=() if [[ "${COMP_WORDBREAKS}" == *:* ]] ; then _get_comp_words_by_ref -n := cur prev words else _get_comp_words_by_ref -n = cur prev words # If the colon is not part of COMP_WORDBREAKS, e.g., due to # including this line into your ~/.bashrc, as mentioned in # /etc/bash_completion ... # export COMP_WORDBREAKS="${COMP_WORDBREAKS//:}" # just replace : with = in -opt:val if [[ "${cur}" == -*:* ]] ; then if [[ "${cur}" == -*[:=]*[:=]* ]] ; then return 0 fi cur="${cur//:/=}" COMPREPLY=( $(compgen -W "${cur}" -- ${cur}) ) compopt -o nospace return 0 fi fi # 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]}" # 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 removed 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} 2>/dev/null|sed -n '{ s#^ *\(--[a-z-]*=\?\(LIST\)\?\).*$#\1# }; /^--/ p'` --stdin" if [[ "_${options}" == "_ --stdin" ]] ; then _filedir_xspec 2> /dev/null return 0 fi # Just those options that can be used together with a value, even if that value is optional: valopts=`${first} 2>/dev/null|grep '^ *--[a-z\[-]*='|grep -v '^ *--subformat='|sed 's#^ *\([a-z=-]*\).*$#\1#'` # This is used to decide whether or not the completion should add a trailing space. # (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 completion. # The same applies for --show and single) # now add the "hidden options" (not mentioned in the usage output, but in doc/OPTIONS and # with --list=hidden-options # Currently, all hidden options do have mandatory values (--option=value), this makes # addition of these easier hidden=`${first} --list=hidden-options 2>/dev/null|sed 's#^\(--[a-z-]*=\?\).*$#\1#'` case "${cur}" in -?(-)f?(o|or|orm|orma|ormat)+(=|:)dynamic*) if [[ "${cur#*f}" == [=:]* || "${cur#*f}" == o[=:]* || "${cur#*f}" == or[=:]* ]] ; then if [[ `echo "${valopts}"|grep -c "^-*${cur%[=:]*}"` -ne 1 ]] ; then return 0 fi fi subformats=`${first} --list=subformats|sed 's#^\(User\)\?Format = \(dynamic_[0-9]*\).*$#\2#'` if [[ "_${subformats}" != _dynamic_0* ]] ; then subformats=`${first} --subformat=LIST|sed 's#^\(User\)\?Format = \(dynamic_[0-9]*\).*$#\2#'` if [[ "_${subformats}" != _dynamic_0* ]] ; then return 0 fi fi cur=${cur#*[=:]} COMPREPLY=( $(compgen -W "${subformats}" -- ${cur}) ) return 0 ;; -?(-)f?(o|or|orm|orma|ormat)+(=|:)*) if [[ "${cur#*f}" == [=:]* || "${cur#*f}" == o[=:]* || "${cur#*f}" == or[=:]* ]] ; then if [[ `echo "${valopts}"|grep -c "^-*${cur%[=:]*}"` -ne 1 ]] ; then return 0 fi fi cur=${cur#*[=:]} formats=`${first} |sed -n '/^--format/,$ { s#^--format=[ A-Za-z]*:##; /^--/ b; s#^ *##; s#\#dynamic#; s#[/ ]#\n#g; p }'` COMPREPLY=( $(compgen -W "${formats}" -- ${cur}) ) if [[ "${COMPREPLY[0]}_" == dynamic_ ]] ; then compopt -o nospace fi return 0 ;; --restore|--status) if [[ "_${__john_completion}" == "_2" ]] ; then COMPREPLY=( $(compgen -W "${cur}=" -- ${cur}) ) compopt -o nospace else prev="${cur}" cur="" _filedir "rec" for (( i=0; i < ${#COMPREPLY[@]}; i++)); do COMPREPLY[$i]="${prev}=${COMPREPLY[$i]%*.rec}" done COMPREPLY[${#COMPREPLY[@]}]="${prev}" fi return 0 ;; -?(-)re?(s|st|sto|stor|store)+(=|:)*|-?(-)sta?(t|tu|tus)+(=|:)*) if [[ "${cur}" == -re[=:]* || "${cur}" == --re[=:]* ]] ; then if [[ `echo "${valopts}"|grep -c "^--re"` -ne 1 ]] ; then return 0 fi fi # If there is no .rec file in the current directory, the old completion logic will show all files: ##echo _`for f in *.rec; do echo ${f%.rec};done`_ cur=${cur#*[=:]} # cd $JOHN/ or Private home for system-wide builds, if ./john --list=build-info works? # NO, this would be wrong! # .rec files are stored in the current directory (or a subdirectory if the session name contains a slash) __expand_tilde_by_ref cur 2>/dev/null _filedir "rec" for (( i=0; i < ${#COMPREPLY[@]}; i++)); do # Do I have to add the trailing / for directories? Apparently not! COMPREPLY[$i]="${COMPREPLY[$i]%*.rec}" done return 0 ;; -?(-)w?(o|or|ord|ordl|ordli|ordlis|ordlist)+(=|:)*) cur=${cur#*[=:]} __expand_tilde_by_ref cur 2>/dev/null _filedir return 0 ;; --rules|--single) if [[ "${valopts}" == *${cur}* ]] ; then if [[ "_${__john_completion}" == "_2" ]] ; then COMPREPLY=( $(compgen -W "${cur}=" -- ${cur}) ) compopt -o nospace else cmd=`echo ${COMP_LINE}|sed "s# ${cur}# --list=rules #"` list=`${cmd} 2>/dev/null` if [[ $? -ne 0 ]] ; then list=`${first} --list=rules 2>/dev/null` fi if [[ $? -ne 0 ]] ; then list="single wordlist NT" fi list=`echo "${list}"|sed 's# #\n#g'|sed "s#^\(.\)#${cur}=\1#"` list="${list} ${cur}" COMPREPLY=( $(compgen -W "${list}" -- ${cur}) ) fi else COMPREPLY=( $(compgen -W "${cur}" -- ${cur}) ) fi return 0 ;; -?(-)ru?(l|le|les)+(=|:)*|-?(-)si?(n|ng|ngl|ngle)+(=|:)*) # let's assume every john version which supports --single= # also supports --rules=, and vice versa if [[ "${valopts}" == *--rules* ]] ; then cmd=`echo ${COMP_LINE}|sed "s# ${cur}# --list=rules #"` list=`${cmd} 2>/dev/null` if [[ $? -ne 0 ]] ; then list=`${first} --list=rules 2>/dev/null` fi if [[ $? -eq 0 ]] ; then cur=`echo ${cur#*[=:]}|LC_ALL=C tr A-Z a-z` COMPREPLY=( $(compgen -W "${list}" -- ${cur}) ) else cur=${cur#*[=:]} COMPREPLY=( $(compgen -W "NT single wordlist" -- ${cur}) ) fi fi return 0 ;; -?(-)e?(x|xt|xte|xter|xtern|xterna|xternal)+(=|:)*) if [[ "${cur}" == -e[=:]* || "${cur}" == --e[=:]* ]] ; then if [[ `echo "${valopts}"|grep -c "^-*${cur%[=:]*}"` -ne 1 ]] ; then return 0 fi fi cmd=`echo ${COMP_LINE}|sed "s# ${cur}# --list=externals #"` list=`${cmd} 2>/dev/null` if [[ $? -ne 0 ]] ; then list=`${first} --list=externals 2>/dev/null` fi if [[ $? -ne 0 ]] ; then list="Filter_Alpha Filter_Digits Filter_Alnum Filter_LanMan LanMan Double Parallel Strip Keyboard" ver=`${first} 2>/dev/null|sed -n '/^John the Ripper password cracker, ver/ s#^John the Ripper password cracker, ver[a-z :]*\([0-9.]*\).*$#\1#p'` ver1=`echo $ver|sed 's#^\([0-9]*\).*$#\1#'` ver2=`echo $ver|sed 's#^[0-9]*.\([0-9]*\).*$#\1#'` ver3=`echo $ver|sed 's#^[0-9]*.[0-9]*.\([0-9]*\).*$#\1#'` if [[ "_${ver3}" == "_" ]] ; then ver3=0 fi if [[ $ver1 -eq 1 && $ver2 -eq 7 ]] ; then if [[ $ver3 -ge 3 ]] ; then list="${list} DumbForce KnownForce" fi if [[ $ver3 -ge 7 ]] ; then list="${list} DateTime Repeats Subsets AtLeast1-Simple AtLeast1-Generic Policy" fi if [[ $ver3 -ge 8 ]] ; then list="${list} AppendLuhn" fi if [[ $ver3 -ge 9 ]] ; then list="${list} AutoAbort AutoStatus" fi else if [[ $ver1 -gt 1 || $ver1 -eq 1 && ver2 -gt 7 ]] ; then list="${list} DumbForce KnownForce DateTime Repeats Subsets AtLeast1-Simple AtLeast1-Generic Policy AppendLuhn AutoAbort AutoStatus" fi fi cur=${cur#*[=:]} else cur=`echo ${cur#*[=:]}|LC_ALL=C tr A-Z a-z` fi COMPREPLY=( $(compgen -W "${list}" -- ${cur}) ) return 0 ;; --incremental) if [[ "_${__john_completion}" == "_2" ]] ; then COMPREPLY=( $(compgen -W "${cur}=" -- ${cur}) ) compopt -o nospace else cmd=`echo ${COMP_LINE}|sed "s# ${cur}# --list=inc-modes #"` list=`${cmd} 2>/dev/null` if [[ $? -ne 0 ]] ; then list=`${first} --list=inc-modes 2>/dev/null` fi if [[ $? -ne 0 ]] ; then list="All Alpha Digits Alnum LanMan" fi list=`echo "${list}"|sed 's# #\n#g'|sed "s#^\(.\)#${cur}=\1#"` list="${list} ${cur}" COMPREPLY=( $(compgen -W "${list}" -- ${cur}) ) fi return 0 ;; -?(-)i?(n|nc|ncr|ncre|ncrem|ncreme|ncremen|ncrement|ncrementa|ncremental)+(=|:)*) cmd=`echo ${COMP_LINE}|sed "s# ${cur}# --list=inc-modes #"` list=`${cmd} 2>/dev/null` if [[ $? -ne 0 ]] ; then list=`${first} --list=inc-modes 2>/dev/null` fi if [[ $? -eq 0 ]] ; then cur=`echo ${cur#*[=:]}|LC_ALL=C tr A-Z a-z` COMPREPLY=( $(compgen -W "${list}" -- ${cur}) ) else cur=${cur#*[=:]} COMPREPLY=( $(compgen -W "All Alpha Digits Alnum LanMan" -- ${cur}) ) fi return 0 ;; -?(-)m?(a|ak|ake|ake-|ake-c|ake-ch|ake-cha|ake-char|ake-chars|ake-charse|ake-charset)+(=|:)*) if [[ "${cur#*[=:]}" != *e* ]] ; then if [[ `echo "${valopts}"|grep -c "^-*${cur%[=:]*}"` -ne 1 ]] ; then return 0 fi fi cur=${cur#*[=:]} #redirect stderr just in case __expand_tilde_by_ref #doesn't exist everywhere #(I'm a bit worried because of the __ at the begin. #May be this function isn't part of an "official" API.) __expand_tilde_by_ref cur 2>/dev/null # FIXME: should I just use directories for completion, not files, # FIXME: to make overwriting existing files harder? _filedir "chr" return 0 ;; --stdout) COMPREPLY=( $(compgen -W "--stdout --stdout=LENGTH" -- ${cur}) ) return 0 ;; --markov) if [[ "${valopts}" == *${cur}* ]] ; then COMPREPLY=( $(compgen -W "--markov --markov=LEVEL[:START[:END[:LENGTH]]]" -- ${cur}) ) fi return 0 ;; --test) if [[ "${valopts}" == *${cur}* ]] ; then COMPREPLY=( $(compgen -W "--test --test=SECONDS" -- ${cur}) ) else COMPREPLY=( $(compgen -W "${cur}" -- ${cur}) ) fi return 0 ;; --show) if [[ "${valopts}" == *${cur}* ]] ; then if [[ "_${__john_completion}" == "_2" ]] ; then COMPREPLY=( $(compgen -W "${cur}=" -- ${cur}) ) compopt -o nospace else COMPREPLY=( $(compgen -W "--show --show=LEFT" -- ${cur}) ) fi else COMPREPLY=( $(compgen -W "--show" -- ${cur}) ) fi return 0 ;; -?(-)sho?(w)+(=|:)l*) if [[ "${valopts}" == *--show* ]] ; then cur=${cur#*[=:]} COMPREPLY=( $(compgen -W "left" -- ${cur}) ) fi return 0 ;; -?(-)sho?(w)+(=|:)*) if [[ "${valopts}" == *--show* ]] ; then cur=${cur#*[=:]} COMPREPLY=( $(compgen -W "LEFT" -- ${cur}) ) fi return 0 ;; --users=?(-)+(L|U)*|--groups=+(-|G)*|--shells=+(-|S)*|--salts=+(-|C)*) return 0 ;; --users=?(-)) cur=${cur#*=} COMPREPLY=( $(compgen -W "LOGIN,... UID,... -LOGIN,... -UID,..." -- ${cur}) ) return 0 ;; --groups=*) cur=${cur#*=} COMPREPLY=( $(compgen -W "GID,... -GID,..." -- ${cur}) ) return 0 ;; --shells=*) cur=${cur#*=} COMPREPLY=( $(compgen -W "SHELL,... -SHELL,..." -- ${cur}) ) return 0 ;; --salts=*) cur=${cur#*=} COMPREPLY=( $(compgen -W "COUNT -COUNT" -- ${cur}) ) return 0 ;; -?(-)en?(c|co|cod|codi|codin|coding)+(=|:)*) if [[ "${valopts}" == *--encoding=* ]] ; then # --encoding=LIST writes to stderr encodings=`${first} --encoding=LIST 2>&1|grep -v 'Supported encodings'|sed 's#[,)]##g'|sed 's#(or ##g'` cur=${cur#*[=:]} if [[ ${COMP_CWORD} -eq 2 || ${COMP_CWORD} -eq 3 && "_${cur}" != "_" ]] ; then encodings="${encodings} LIST" # make sure LIST will be the first option: LC_ALL=C fi COMPREPLY=( $(compgen -W "${encodings}" -- ${cur}) ) fi return 0 ;; -?(-)po?(t)+(=|:)*) if [[ "${valopts}" == *--pot=* ]] ; then # if --pot= is used, john always looks for the file $PWD # (tested with system-wide and local build of john) cur=${cur#*[=:]} #redirect stderr just in case __expand_tilde_by_ref #doesn't exist everywhere #(I'm a bit worried because of the __ at the begin. #May be this function isn't part of an "official" API.) __expand_tilde_by_ref cur 2>/dev/null _filedir "pot" fi return 0 ;; -?(-)co?(n|nf|nfi|nfig)+(=|:)*) if [[ "${valopts}" == *--config=* ]] ; then # if --config= is used, john always looks for files in $PWD # (tested for system-wide and local builds) cur=${cur#*[=:]} __expand_tilde_by_ref cur 2>/dev/null _filedir '@(conf|ini)' fi return 0 ;; -?(-)sav?(e|e-|e-m|e-me|e-mem|e-memo|e-memor|e-memory)+(=|:)*) cur=${cur#*[=:]} COMPREPLY=( $(compgen -W "1 2 3" -- ${cur}) ) return 0 ;; -?(-)reg?(e|en|en-|en-l|en-lo|en-los|en-lost|en-lost-|en-lost-s|en-lost-sa|en-lost-sal|en-lost-salt|en-lost-salts)+(=|:)*) if [[ "${valopts}" == *--regen-lost-salts=* ]] ; then cur=${cur#*[=:]} COMPREPLY=( $(compgen -W "1 2 3 4 5" -- ${cur}) ) fi return 0 ;; -?(-)su?(b|bf|bfo|bfor|bform|bforma|bformat)+(=|:)*) if [[ "${options}" == *--subformat=LIST* ]] ; then cur=${cur#*[=:]} COMPREPLY=( $(compgen -W "LIST" -- ${cur}) ) else if [[ "${hidden}" == *--subformat=* ]] ; then cur=${cur#*[=:]} # Should I test if --format=crypt (or -fo:crypt ...) is specified? # Should I really parse the output of # $[first} --test --format=crypt --subformat=? # (BTW: The output is wrong. It lists upper case formats, # but expects lower case instead) # should I even test this (with --test=0, and filter out those # with a message: # appears to be unsupported on this system; will not load such hashes. subformats=`${first} --test=0 --format=crypt --subformat=? 2>&1|sed -n 's#,# #g;/^Subformat / s#^[^:]*:\(.*\)$#\L\1# p'` COMPREPLY=( $(compgen -W "${subformats}" -- ${cur}) ) fi fi return 0 ;; -?(-)pla?(t|tf|tfo|tfor|tform)+(=|:)+(L|l)*|-?(-)dev?(i|ic|ice)+(=|:)+(L|l)*) # CUDA doesn't allow --device=LIST # workaround: check if --platform= is allowed if [[ "${valopts}" == *--platform=* ]] ; then cur=${cur#*[=:]} COMPREPLY=( $(compgen -W "LIST list" -- ${cur}) ) fi return 0 ;; -?(-)platform+(=|:)|-?(-)device+(=|:)) # --device=LIST isn't supported for CUDA, but for CUDA # --platform= is not a valid option if [[ "${valopts}" == *--platform=* ]] ; then # Calling john --platform=LIST just to find possible completions # will take too long cur=${cur#*[=:]} COMPREPLY=( $(compgen -W "LIST N" -- ${cur}) ) fi return 0 ;; -?(-)l?(i|is|ist)+(=|:)*) if [[ "${hidden}" == *--list=* || "${valopts}" == *--list=* ]] ; then cur=${cur#*[=:]} # the --list=? output changed, that's why a more complex regex is used # to cover all cases list=`${first} --list=? 2>/dev/null|sed 's#\(,\)\?\( or\)\?[ ]*[<].*$##; s#,##g'` if [[ $? -eq 0 ]] ; then # add "?" to the list of possible completions, but don't add any # section names like "Options"... COMPREPLY=( $(compgen -W "${list} ?" -- ${cur}) ) fi fi return 0 ;; -*+(=|:)) return 0; ;; -*) compreplya=`compgen -W "${options} ${hidden}" -- ${cur}` if [[ "_${compreplya}_" == "__" ]] ; then cur="-${cur}" compreplya=`compgen -W "${options} ${hidden}" -- ${cur}` fi compreplyb=`compgen -W "${valopts} ${hidden}" -- ${cur}` COMPREPLY=( $(compgen -W "${options} ${hidden}" -- ${cur}) ) if [[ "_${compreplya}" == "_${compreplyb}" ]] ; then compopt -o nospace fi return 0 ;; *) _filedir return 0 ;; esac } && complete -F _john john # unique ## have grep && have sed && _unique() { local first cur usage options valopts compreplya compreplyb COMPREPLY=() _get_comp_words_by_ref -n = cur # we need to make sure we run the correct program, not some other program # called unique which is located somewhere in $PATH first="${COMP_WORDS[0]}" usage=`${first}|grep '^Usage:'|sed 's#^Usage:\? \?[^ ]*unique *##'` case "_${cur}" in _|_${first}) if [[ "_${usage}" != "_OUTPUT-FILE" ]] ; then COMPREPLY=( $(compgen -W "${usage}" -- "") ) fi return 0 ;; _-cut=*|_-mem=*) return 0 ;; _-inp=*|_-ex_file=*|_-ex_file_only=*) cur=${cur#*=} __expand_tilde_by_ref cur 2>/dev/null _filedir return 0 ;; _-*) if [[ "_${usage}_" != "_OUTPUT-FILE_" ]] ; then options=`echo ${usage}|sed 's# #\n#g'|grep '^\[.*\]$'|sed 's#^.\(.*\).$#\1#'|sed 's#=.*$#=#'` valopts=`echo "${options}"|grep '='` compreplya=`compgen -W "${options}" -- ${cur}` compreplyb=`compgen -W "${valopts}" -- ${cur}` if [[ "_${compreplya}" == "_${compreplyb}" ]] ; then COMPREPLY=( $(compgen -W "${valopts}" -- "${cur}") ) compopt -o nospace else COMPREPLY=( $(compgen -W "${options}" -- "${cur}") ) fi fi return 0 ;; _*) return 0 ;; esac } && complete -F _unique unique