Introduction
Introduction Statistics Contact Development Disclaimer Help
password_fill - dotfiles - These are my dotfiles. There are many like it, but t…
git clone git://jay.scot/dotfiles
Log
Files
Refs
README
---
password_fill (13378B)
---
1 #!/usr/bin/env bash
2 help() {
3 blink=$'\e[1;31m' reset=$'\e[0m'
4 cat <<EOF
5 This script can only be used as a userscript for qutebrowser
6 2015, Thorsten Wißmann <edu _at_ thorsten-wissmann _dot_ de>
7 In case of questions or suggestions, do not hesitate to send me an E-Mai…
8 directly ask me via IRC (nickname thorsten\`) in #qutebrowser on Libera …
9
10 $blink!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$reset
11 WARNING: the passwords are stored in qutebrowser's
12 debug log reachable via the url qute://log
13 $blink!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$reset
14
15 Usage: run as a userscript form qutebrowser, e.g.:
16 spawn --userscript ~/.config/qutebrowser/password_fill
17
18 Pass backend: (see also passwordstore.org)
19 This script expects pass to store the credentials of each page in an e…
20 file, where the filename (or filepath) contains the domain of the resp…
21 page. The first line of the file must contain the password, the login …
22 must be contained in a later line beginning with "user:", "login:", or
23 "username:" (configurable by the user_pattern variable).
24
25 Behavior:
26 It will try to find a username/password entry in the configured backend
27 (currently only pass) for the current website and will load that pair …
28 username and password to any form on the current page that has some pa…
29 entry field. If multiple entries are found, a zenity menu is offered.
30
31 If no entry is found, then it crops subdomains from the url if at leas…
32 entry is found in the backend. (In that case, it always shows a menu)
33
34 Configuration:
35 This script loads the bash script ~/.config/qutebrowser/password_fill_…
36 it exists), so you can change any configuration variable and overwrite…
37 function you like.
38
39 EOF
40 }
41
42 set -o errexit
43 set -o pipefail
44 shopt -s nocasematch # make regexp matching in bash case insensitive
45
46 if [ -z "$QUTE_FIFO" ] ; then
47 help
48 exit
49 fi
50
51 error() {
52 local msg="$*"
53 echo "message-error '${msg//\'/\\\'}'" >> "$QUTE_FIFO"
54 }
55 msg() {
56 local msg="$*"
57 echo "message-info '${msg//\'/\\\'}'" >> "$QUTE_FIFO"
58 }
59 die() {
60 error "$*"
61 exit 0
62 }
63
64 javascript_escape() {
65 # print the first argument in an escaped way, such that it can safely
66 # be used within javascripts double quotes
67 # shellcheck disable=SC2001
68 sed "s,[\\\\'\"],\\\\&,g" <<< "$1"
69 }
70
71 # ======================================================= #
72 # CONFIGURATION
73 # ======================================================= #
74 # The configuration file is per default located in
75 # ~/.config/qutebrowser/password_fill_rc and is a bash script that is lo…
76 # later in the present script. So basically you can replace all of the
77 # following definitions and make them fit your needs.
78
79 # The following simplifies a URL to the domain (e.g. "wiki.qutebrowser.o…
80 # which is later used to search the correct entries in the password back…
81 # you e.g. don't want the "www." to be removed or if you want to disting…
82 # between different paths on the same domain.
83
84 simplify_url() {
85 simple_url="${1##*://}" # remove protocol specification
86 simple_url="${simple_url%%\?*}" # remove GET parameters
87 simple_url="${simple_url%%/*}" # remove directory path
88 simple_url="${simple_url%:*}" # remove port
89 simple_url="${simple_url##www.}" # remove www. subdomain
90 }
91
92 # no_entries_found() is called if the first query_entries() call did not…
93 # any matching entries. Multiple implementations are possible:
94 # The easiest behavior is to quit:
95 #no_entries_found() {
96 # if [ 0 -eq "${#files[@]}" ] ; then
97 # die "No entry found for »$simple_url«"
98 # fi
99 #}
100 # But you could also fill the files array with all entries from your pas…
101 # if the first db query did not find anything
102 # no_entries_found() {
103 # if [ 0 -eq "${#files[@]}" ] ; then
104 # query_entries ""
105 # if [ 0 -eq "${#files[@]}" ] ; then
106 # die "No entry found for »$simple_url«"
107 # fi
108 # fi
109 # }
110
111 # Another behavior is to drop another level of subdomains until search h…
112 # are found:
113 no_entries_found() {
114 while [ 0 -eq "${#files[@]}" ] && [ -n "$simple_url" ]; do
115 # shellcheck disable=SC2001
116 shorter_simple_url=$(sed 's,^[^.]*\.,,' <<< "$simple_url")
117 if [ "$shorter_simple_url" = "$simple_url" ] ; then
118 # if no dot, then even remove the top level domain
119 simple_url=""
120 query_entries "$simple_url"
121 break
122 fi
123 simple_url="$shorter_simple_url"
124 query_entries "$simple_url"
125 #die "No entry found for »$simple_url«"
126 # enforce menu if we do "fuzzy" matching
127 menu_if_one_entry=1
128 done
129 if [ 0 -eq "${#files[@]}" ] ; then
130 die "No entry found for »$simple_url«"
131 fi
132 }
133
134 # Backend implementations tell, how the actual password store is accesse…
135 # Right now, there is only one fully functional password backend, namely…
136 # the program "pass".
137 # A password backend consists of three actions:
138 # - init() initializes backend-specific things and does sanity checks.
139 # - query_entries() is called with a simplified url and is expected to …
140 # the bash array $files with the names of matching password entries. …
141 # are no requirements how these names should look like.
142 # - open_entry() is called with some specific entry of the $files array…
143 # expected to write the username of that entry to the $username varia…
144 # the corresponding password to $password
145
146 # shellcheck disable=SC2329
147 reset_backend() {
148 init() { true ; }
149 query_entries() { true ; }
150 open_entry() { true ; }
151 }
152
153 # choose_entry() is expected to choose one entry from the array $files a…
154 # write it to the variable $file.
155 choose_entry() {
156 choose_entry_zenity
157 }
158
159 # The default implementation chooses a random entry from the array. So i…
160 # are multiple matching entries, multiple calls to this userscript will
161 # eventually pick the "correct" entry. I.e. if this userscript is bound …
162 # "zl", the user has to press "zl" until the correct username shows up i…
163 # login form.
164 choose_entry_random() {
165 local nr=${#files[@]}
166 file="${files[$((RANDOM % nr))]}"
167 # Warn user, that there might be other matching password entries
168 if [ "$nr" -gt 1 ] ; then
169 msg "Picked $file out of $nr entries: ${files[*]}"
170 fi
171 }
172
173 # another implementation would be to ask the user via some menu (like ro…
174 # dmenu or zenity or even qutebrowser completion in future?) which entry…
175 # pick
176 MENU_COMMAND=( head -n 1 )
177 # whether to show the menu if there is only one entry in it
178 menu_if_one_entry=0
179 choose_entry_menu() {
180 local nr=${#files[@]}
181 if [ "$nr" -eq 1 ] && ! ((menu_if_one_entry)) ; then
182 file="${files[0]}"
183 else
184 file=$( printf '%s\n' "${files[@]}" | "${MENU_COMMAND[@]}" )
185 fi
186 }
187
188 choose_entry_rofi() {
189 MENU_COMMAND=( rofi -p "qutebrowser> " -dmenu
190 -mesg $'Pick a password entry for <b>'"${QUTE_UR…
191 choose_entry_menu || true
192 }
193
194 choose_entry_zenity() {
195 MENU_COMMAND=( zenity --list --title "qutebrowser password fill"
196 --text "Pick the password entry:"
197 --column "Name" )
198 choose_entry_menu || true
199 }
200
201 choose_entry_zenity_radio() {
202 # shellcheck disable=SC2329
203 zenity_helper() {
204 awk '{ print $0 ; print $0 }' \
205 | zenity --list --radiolist \
206 --title "qutebrowser password fill" \
207 --text "Pick the password entry:" \
208 --column " " --column "Name"
209 }
210 MENU_COMMAND=( zenity_helper )
211 choose_entry_menu || true
212 }
213
214 # =======================================================
215 # backend: PASS
216
217 # configuration options:
218 match_filename=1 # whether allowing entry match by filepath
219 match_line=0 # whether allowing entry match by URL-Pattern in file
220 # Note: match_line=1 gets very slow, even for small pas…
221 match_line_pattern='^url: .*' # applied using grep -iE
222 user_pattern='^(user|username|login): '
223
224 GPG_OPTS=( "--quiet" "--yes" "--compress-algo=none" "--no-encrypt-to" )
225 GPG="gpg"
226 export GPG_TTY="${GPG_TTY:-$(tty 2>/dev/null)}"
227 command -v gpg2 &>/dev/null && GPG="gpg2"
228 [[ -n $GPG_AGENT_INFO || $GPG == "gpg2" ]] && GPG_OPTS+=( "--batch" "--u…
229
230 pass_backend() {
231 init() {
232 PREFIX="${PASSWORD_STORE_DIR:-$HOME/.password-store}"
233 if ! [ -d "$PREFIX" ] ; then
234 die "Can not open password store dir »$PREFIX«"
235 fi
236 }
237 query_entries() {
238 local url="$1"
239
240 if ((match_line)) ; then
241 # add entries with matching URL-tag
242 while read -r -d "" passfile ; do
243 if $GPG "${GPG_OPTS[@]}" -d "$passfile" \
244 | grep --max-count=1 -iE "${match_line_pattern}${ur…
245 then
246 passfile="${passfile#"$PREFIX"}"
247 passfile="${passfile#/}"
248 files+=( "${passfile%.gpg}" )
249 fi
250 done < <(find -L "$PREFIX" -iname '*.gpg' -print0)
251 fi
252 if ((match_filename)) ; then
253 # add entries with matching filepath
254 while read -r passfile ; do
255 passfile="${passfile#"$PREFIX"}"
256 passfile="${passfile#/}"
257 files+=( "${passfile%.gpg}" )
258 done < <(find -L "$PREFIX" -iname '*.gpg' | grep "$url")
259 fi
260 }
261 open_entry() {
262 local path="$PREFIX/${1}.gpg"
263 password=""
264 local firstline=1
265 while read -r line ; do
266 if ((firstline)) ; then
267 password="$line"
268 firstline=0
269 else
270 if [[ $line =~ $user_pattern ]] ; then
271 # remove the matching prefix "user: " from the begin…
272 username=${line#"${BASH_REMATCH[0]}"}
273 break
274 fi
275 fi
276 done < <($GPG "${GPG_OPTS[@]}" -d "$path" | awk 1 )
277 }
278 }
279 # =======================================================
280
281 # =======================================================
282 # backend: secret
283 # shellcheck disable=SC2329
284 secret_backend() {
285 init() {
286 return
287 }
288 query_entries() {
289 local domain="$1"
290 while read -r line ; do
291 if [[ "$line" == "attribute.username = "* ]] ; then
292 files+=("$domain ${line:21}")
293 fi
294 done < <( secret-tool search --unlock --all domain "$domain" 2>&…
295 }
296 open_entry() {
297 local domain="${1%% *}"
298 username="${1#* }"
299 password=$(secret-tool lookup domain "$domain" username "$userna…
300 }
301 }
302 # =======================================================
303
304 # load some sane default backend
305 reset_backend
306 pass_backend
307 # load configuration
308 QUTE_CONFIG_DIR=${QUTE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/qut…
309 PWFILL_CONFIG=${PWFILL_CONFIG:-${QUTE_CONFIG_DIR}/password_fill_rc}
310 if [ -f "$PWFILL_CONFIG" ] ; then
311 # shellcheck source=/dev/null
312 source "$PWFILL_CONFIG"
313 fi
314 init
315
316 simplify_url "$QUTE_URL"
317 query_entries "${simple_url}"
318 no_entries_found
319 # remove duplicates
320 mapfile -t files < <(printf '%s\n' "${files[@]}" | sort | uniq )
321 choose_entry
322 if [ -z "$file" ] ; then
323 # choose_entry didn't want any of these entries
324 exit 0
325 fi
326 open_entry "$file"
327 #username="$(date)"
328 #password="XYZ"
329 #msg "$username, ${#password}"
330
331 [ -n "$username" ] || die "Username not set in entry $file"
332 [ -n "$password" ] || die "Password not set in entry $file"
333
334 js() {
335 cat <<EOF
336 function isVisible(elem) {
337 var style = elem.ownerDocument.defaultView.getComputedStyle(elem…
338
339 if (style.getPropertyValue("visibility") !== "visible" ||
340 style.getPropertyValue("display") === "none" ||
341 style.getPropertyValue("opacity") === "0") {
342 return false;
343 }
344
345 return elem.offsetWidth > 0 && elem.offsetHeight > 0;
346 };
347 function hasPasswordField(form) {
348 var inputs = form.getElementsByTagName("input");
349 for (var j = 0; j < inputs.length; j++) {
350 var input = inputs[j];
351 if (input.type == "password") {
352 return true;
353 }
354 }
355 return false;
356 };
357 function loadData2Form (form) {
358 var inputs = form.getElementsByTagName("input");
359 for (var j = 0; j < inputs.length; j++) {
360 var input = inputs[j];
361 if (isVisible(input) && (input.type == "text" || input.type …
362 input.focus();
363 input.value = "$(javascript_escape "${username}")";
364 input.dispatchEvent(new Event('change'));
365 input.blur();
366 }
367 if (input.type == "password") {
368 input.focus();
369 input.value = "$(javascript_escape "${password}")";
370 input.dispatchEvent(new Event('change'));
371 input.blur();
372 }
373 }
374 };
375
376 var forms = document.getElementsByTagName("form");
377 for (i = 0; i < forms.length; i++) {
378 if (hasPasswordField(forms[i])) {
379 loadData2Form(forms[i]);
380 }
381 }
382 EOF
383 }
384
385 printjs() {
386 js | sed 's,//.*$,,' | tr '\n' ' '
387 }
388 echo "jseval -q $(printjs)" >> "$QUTE_FIFO"
You are viewing proxied material from jay.scot. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.