#!/usr/bin/env bash # # Automatically types the username and password from the pass entry specified # as argument one. The required password-store backend is pass, the standard # unix password manager. # # A simple workflow for this may be to go to a website requiring a username and # password. Select the username field, open a program launcher such as rofi or # dmenu, then execute 'quicktype password-name'. # # Note that for this to work, each pass entry must match the following criteria # * The password is on the first line (this is the pass standard) # * The username can be anywhere in the file, but the line must look like # 'username: ' # # Depends on: pass xdotool # export PASSWORD_STORE_DIR=~/.password-store # # Writes log output to terminal and notification daemon, if one is present. # function log { local msg="${@}" if [[ $(type -p notify-send) != '' ]]; then notify-send "$(basename ${0}):" "${msg}" fi printf -- '%s\n' "${msg}" } # # Ensures required tools are available, returning code 1 and log if any of them # are not. # function check_env { # Verify pass is installed and in the PATH. if [[ $(type -p pass) == '' ]]; then log "Error: Could not find pass." && return 1 fi # Verify xdotool is installed and in the path if [[ $(type -p xdotool) == '' ]]; then log "Error: Could not find xdotool." && return 1 fi # Verify /dev/null is a character device, on the off chance someone with root # access is trying to capture output redirected there. local devstat=$(ls -l /dev/null) if [[ ${devstat:0:1} != 'c' ]]; then log "Error: /dev/null is not a character device." return 1 fi return 0 } # # Gets a field's value from the specified [multiline] string, using the # specified delimiter. # # @param content Content to extract field value from # @param delim Delimiter seperating fields and values # @param field Field name to get value for # getfield() { local _content="${1:-}" local _delim="${2:-}" local _field="${3:-}" printf -- "%s" "${_content}" \ | sed -n "s/${_field}${_delim} *\(.*\)/\1/p" \ | head -n 1 } typefields() { local _user="${1:-}" local _pass="${2:-}" local _delim="${3:-}" # Type the password xdotool type --delay 15 "${_user}" xdotool key "${_delim}" xdotool type --delay 15 "${_pass}" } pastefields() { local _user="${1:-}" local _pass="${2:-}" local _delim="${3:-}" # Save clipboard first local _origclipboard="$(xclip -o -selection clipboard)" # Sleep .2 for those apps that read only "key up" events without first reading # "key down", causing it to read pressing enter to execute pt as submitting # the form (bad js devs, bad!). sleep .2 # Save username to clipboard and paste printf -- "%s\n" "${_user}" | xclip -selection clipboard xdotool key "Control+v" "${_delim}" # Save password to clipboard and paste printf -- "%s\n" "${_pass}" | xclip -selection clipboard xdotool key "Control+v" # Restore original clipboard value printf -- "%s\n" "${_origclipboard}" | xclip -selection clipboard } function main { local _passentry # Contents of the requested pass entry local _passpassword # Password from the pass entry local _passusername # Username field from the pass entry local _passdelim # UI Field delimiter (Tab, Return), from the pass entry local _passsubmit # Boolean submit (1,y,yes...), from the pass entry local _passtype # Boolean type (1,y,yes...), from the pass entry local _delim=Tab # Default delimiter if not specified in pass entry local _submit=0 # Default submit value if not specified in pass entry local _type=0 # Whether or not to type or paste (type=0 will paste) # Exit failure if no password was specified [[ -z ${1} ]] && log "Please specify a password to be typed" && exit 1 check_env || exit 0 # Check if the password exists in the store # Also, copy the contents into a variable if it exists _passentry="$(pass ${@} 2>/dev/null)" [[ $? -gt 0 ]] && log "Error: '${@}' is not in the password store" && exit 1 # Parse pass output into appropriate variables _passpassword=$(printf -- "%s" "${_passentry}" | head -n1 | sed 's/`/\`/g') _passusername=$(getfield "${_passentry}" ':' 'username') _passdelim=$(getfield "${_passentry}" ':' 'delim') _passsubmit=$(getfield "${_passentry}" ':' 'submit') _passtype=$(getfield "${_passentry}" ':' 'type') # If any of the known 'submit' values are specified, set submit to 1 if [ "${_passsubmit}" = '1' ] \ || [ "${_passsubmit}" = 'y' ] \ || [ "${_passsubmit}" = 'yes' ] \ || [ "${_passsubmit}" = 'true' ]; then _submit=1 fi # If any of the known 'type' values are specified, set type to 1 if [ "${_passtype}" = '1' ] \ || [ "${_passtype}" = 'y' ] \ || [ "${_passtype}" = 'yes' ] \ || [ "${_passtype}" = 'true' ]; then _type=1 fi # Type username/password if type == 1, otherwise paste username/password if [ "${_type}" = '1' ]; then typefields "${_passusername}" "${_passpassword}" "${_delim}" else pastefields "${_passusername}" "${_passpassword}" "${_delim}" fi [ "${_submit}" = 1 ] && xdotool key Return } main ${@}