1 #!/usr/bin/env bash
2 #
3 # Automatically types the username and password from the pass entry specified
4 # as argument one. The required password-store backend is pass, the standard
5 # unix password manager.
6 #
7 # A simple workflow for this may be to go to a website requiring a username and
8 # password. Select the username field, open a program launcher such as rofi or
9 # dmenu, then execute 'quicktype password-name'.
10 #
11 # Note that for this to work, each pass entry must match the following criteria
12 # * The password is on the first line (this is the pass standard)
13 # * The username can be anywhere in the file, but the line must look like
14 # 'username: <password>'
15 #
16 # Depends on: pass xdotool
17 #
18
19
20 export PASSWORD_STORE_DIR=~/.password-store
21
22
23 #
24 # Writes log output to terminal and notification daemon, if one is present.
25 #
26 function log {
27 local msg="${@}"
28
29 if [[ $(type -p notify-send) != '' ]]; then
30 notify-send "$(basename ${0}):" "${msg}"
31 fi
32 printf -- '%s\n' "${msg}"
33 }
34
35
36 #
37 # Ensures required tools are available, returning code 1 and log if any of them
38 # are not.
39 #
40 function check_env {
41 # Verify pass is installed and in the PATH.
42 if [[ $(type -p pass) == '' ]]; then
43 log "Error: Could not find pass." && return 1
44 fi
45
46 # Verify xdotool is installed and in the path
47 if [[ $(type -p xdotool) == '' ]]; then
48 log "Error: Could not find xdotool." && return 1
49 fi
50
51 # Verify /dev/null is a character device, on the off chance someone with root
52 # access is trying to capture output redirected there.
53 local devstat=$(ls -l /dev/null)
54 if [[ ${devstat:0:1} != 'c' ]]; then
55 log "Error: /dev/null is not a character device."
56 return 1
57 fi
58
59 return 0
60 }
61
62
63 #
64 # Gets a field's value from the specified [multiline] string, using the
65 # specified delimiter.
66 #
67 # @param content Content to extract field value from
68 # @param delim Delimiter seperating fields and values
69 # @param field Field name to get value for
70 #
71 getfield() {
72 local _content="${1:-}"
73 local _delim="${2:-}"
74 local _field="${3:-}"
75
76 printf -- "%s" "${_content}" \
77 | sed -n "s/${_field}${_delim} *\(.*\)/\1/p" \
78 | head -n 1
79 }
80
81
82 typefields() {
83 local _user="${1:-}"
84 local _pass="${2:-}"
85 local _delim="${3:-}"
86
87 # Type the password
88 xdotool type --delay 15 "${_user}"
89 xdotool key "${_delim}"
90 xdotool type --delay 15 "${_pass}"
91 }
92
93
94 pastefields() {
95 local _user="${1:-}"
96 local _pass="${2:-}"
97 local _delim="${3:-}"
98
99 # Save clipboard first
100 local _origclipboard="$(xclip -o -selection clipboard)"
101
102 # Sleep .2 for those apps that read only "key up" events without first reading
103 # "key down", causing it to read pressing enter to execute pt as submitting
104 # the form (bad js devs, bad!).
105 sleep .2
106 # Save username to clipboard and paste
107 printf -- "%s\n" "${_user}" | xclip -selection clipboard
108 xdotool key "Control+v" "${_delim}"
109
110 # Save password to clipboard and paste
111 printf -- "%s\n" "${_pass}" | xclip -selection clipboard
112 xdotool key "Control+v"
113
114 # Restore original clipboard value
115 printf -- "%s\n" "${_origclipboard}" | xclip -selection clipboard
116 }
117
118
119 function main {
120 local _passentry # Contents of the requested pass entry
121 local _passpassword # Password from the pass entry
122 local _passusername # Username field from the pass entry
123 local _passdelim # UI Field delimiter (Tab, Return), from the pass entry
124 local _passsubmit # Boolean submit (1,y,yes...), from the pass entry
125 local _passtype # Boolean type (1,y,yes...), from the pass entry
126
127 local _delim=Tab # Default delimiter if not specified in pass entry
128 local _submit=0 # Default submit value if not specified in pass entry
129 local _type=0 # Whether or not to type or paste (type=0 will paste)
130
131 # Exit failure if no password was specified
132 [[ -z ${1} ]] && log "Please specify a password to be typed" && exit 1
133
134 check_env || exit 0
135
136 # Check if the password exists in the store
137 # Also, copy the contents into a variable if it exists
138 _passentry="$(pass ${@} 2>/dev/null)"
139 [[ $? -gt 0 ]] && log "Error: '${@}' is not in the password store" && exit 1
140
141 # Parse pass output into appropriate variables
142 _passpassword=$(printf -- "%s" "${_passentry}" | head -n1 | sed 's/`/\`/g')
143 _passusername=$(getfield "${_passentry}" ':' 'username')
144 _passdelim=$(getfield "${_passentry}" ':' 'delim')
145 _passsubmit=$(getfield "${_passentry}" ':' 'submit')
146 _passtype=$(getfield "${_passentry}" ':' 'type')
147
148 # If any of the known 'submit' values are specified, set submit to 1
149 if [ "${_passsubmit}" = '1' ] \
150 || [ "${_passsubmit}" = 'y' ] \
151 || [ "${_passsubmit}" = 'yes' ] \
152 || [ "${_passsubmit}" = 'true' ]; then
153 _submit=1
154 fi
155
156 # If any of the known 'type' values are specified, set type to 1
157 if [ "${_passtype}" = '1' ] \
158 || [ "${_passtype}" = 'y' ] \
159 || [ "${_passtype}" = 'yes' ] \
160 || [ "${_passtype}" = 'true' ]; then
161 _type=1
162 fi
163
164 # Type username/password if type == 1, otherwise paste username/password
165 if [ "${_type}" = '1' ]; then
166 typefields "${_passusername}" "${_passpassword}" "${_delim}"
167 else
168 pastefields "${_passusername}" "${_passpassword}" "${_delim}"
169 fi
170
171 [ "${_submit}" = 1 ] && xdotool key Return
172 }
173
174 main ${@}
|