2015-08-14 23:53:52 +02:00
|
|
|
#!/bin/bash -e
|
|
|
|
help() {
|
|
|
|
blink=$'\e[1;31m' reset=$'\e[0m'
|
|
|
|
cat <<EOF
|
|
|
|
This script can only be used as a userscript for qutebrowser
|
|
|
|
2015, Thorsten Wißmann <edu _at_ thorsten-wissmann _dot_ de>
|
|
|
|
In case of questions or suggestions, do not hesitate to send me an E-Mail or to
|
|
|
|
directly ask me via IRC (nickname thorsten\`) in #qutebrowser on freenode.
|
|
|
|
|
|
|
|
$blink!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$reset
|
|
|
|
WARNING: the passwords are stored in qutebrowser's
|
|
|
|
debug log reachable via the url qute:log
|
|
|
|
$blink!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$reset
|
|
|
|
|
|
|
|
Usage: run as a userscript form qutebrowser, e.g.:
|
|
|
|
spawn --userscript ~/.config/qutebrowser/password_fill
|
|
|
|
|
2015-12-11 16:54:16 +01:00
|
|
|
Pass backend: (see also passwordstore.org)
|
|
|
|
This script expects pass to store the credentials of each page in an extra
|
|
|
|
file, where the filename (or filepath) contains the domain of the respective
|
|
|
|
page. The first line of the file must contain the password, the login name
|
|
|
|
must be contained in a later line beginning with "user:", "login:", or
|
|
|
|
"username:" (configurable by the user_pattern variable).
|
|
|
|
|
2015-08-14 23:53:52 +02:00
|
|
|
Behaviour:
|
|
|
|
It will try to find a username/password entry in the configured backend
|
|
|
|
(currently only pass) for the current website and will load that pair of
|
|
|
|
username and password to any form on the current page that has some password
|
2015-12-05 12:26:14 +01:00
|
|
|
entry field. If multiple entries are found, a zenity menu is offered.
|
|
|
|
|
|
|
|
If no entry is found, then it crops subdomains from the url if at least one
|
|
|
|
entry is found in the backend. (In that case, it always shows a menu)
|
2015-08-14 23:53:52 +02:00
|
|
|
|
2015-12-11 16:54:16 +01:00
|
|
|
Configuration:
|
|
|
|
This script loads the bash script ~/.config/qutebrowser/password_fill_rc (if
|
|
|
|
it exists), so you can change any configuration variable and overwrite any
|
|
|
|
function you like.
|
|
|
|
|
2015-08-14 23:53:52 +02:00
|
|
|
EOF
|
|
|
|
}
|
|
|
|
|
|
|
|
set -o pipefail
|
2015-12-11 16:54:16 +01:00
|
|
|
shopt -s nocasematch # make regexp matching in bash case insensitive
|
2015-08-14 23:53:52 +02:00
|
|
|
|
|
|
|
if [ -z "$QUTE_FIFO" ] ; then
|
|
|
|
help
|
|
|
|
exit
|
|
|
|
fi
|
|
|
|
|
|
|
|
error() {
|
|
|
|
local msg="$*"
|
|
|
|
echo "message-error '${msg//\'/\\\'}'" >> "$QUTE_FIFO"
|
|
|
|
}
|
|
|
|
msg() {
|
|
|
|
local msg="$*"
|
|
|
|
echo "message-info '${msg//\'/\\\'}'" >> "$QUTE_FIFO"
|
|
|
|
}
|
|
|
|
die() {
|
|
|
|
error "$*"
|
|
|
|
exit 0
|
|
|
|
}
|
|
|
|
|
|
|
|
# ======================================================= #
|
|
|
|
# CONFIGURATION
|
|
|
|
# ======================================================= #
|
|
|
|
# The configuration file is per default located in
|
|
|
|
# ~/.config/qutebrowser/password_fill_rc and is a bash script that is loaded
|
|
|
|
# later in the present script. So basically you can replace all of the
|
|
|
|
# following definitions and make them fit your needs.
|
|
|
|
|
|
|
|
# The following simplifies a URL to the domain (e.g. "wiki.qutebrowser.org")
|
|
|
|
# which is later used to search the correct entries in the password backend. If
|
|
|
|
# you e.g. don't want the "www." to be removed or if you want to distinguish
|
|
|
|
# between different paths on the same domain.
|
|
|
|
|
|
|
|
simplify_url() {
|
|
|
|
simple_url="${1##*://}" # remove protocoll specification
|
|
|
|
simple_url="${simple_url%%\?*}" # remove GET parameters
|
|
|
|
simple_url="${simple_url%%/*}" # remove directory path
|
2015-12-05 12:11:10 +01:00
|
|
|
simple_url="${simple_url%:*}" # remove port
|
2015-08-14 23:53:52 +02:00
|
|
|
simple_url="${simple_url##www.}" # remove www. subdomain
|
|
|
|
}
|
|
|
|
|
2015-12-05 12:11:10 +01:00
|
|
|
# no_entries_found() is called if the first query_entries() call did not find
|
|
|
|
# any matching entries. Multiple implementations are possible:
|
|
|
|
# The easiest behaviour is to quit:
|
|
|
|
#no_entries_found() {
|
|
|
|
# if [ 0 -eq "${#files[@]}" ] ; then
|
|
|
|
# die "No entry found for »$simple_url«"
|
|
|
|
# fi
|
|
|
|
#}
|
|
|
|
# But you could also fill the files array with all entries from your pass db
|
|
|
|
# if the first db query did not find anything
|
|
|
|
# no_entries_found() {
|
|
|
|
# if [ 0 -eq "${#files[@]}" ] ; then
|
|
|
|
# query_entries ""
|
|
|
|
# if [ 0 -eq "${#files[@]}" ] ; then
|
|
|
|
# die "No entry found for »$simple_url«"
|
|
|
|
# fi
|
|
|
|
# fi
|
|
|
|
# }
|
|
|
|
|
|
|
|
# Another beahviour is to drop another level of subdomains until search hits
|
|
|
|
# are found:
|
|
|
|
no_entries_found() {
|
|
|
|
while [ 0 -eq "${#files[@]}" ] && [ -n "$simple_url" ]; do
|
2015-12-09 23:25:55 +01:00
|
|
|
shorter_simple_url=$(sed 's,^[^.]*\.,,' <<< "$simple_url")
|
|
|
|
if [ "$shorter_simple_url" = "$simple_url" ] ; then
|
|
|
|
# if no dot, then even remove the top level domain
|
|
|
|
simple_url=""
|
|
|
|
query_entries "$simple_url"
|
|
|
|
break
|
|
|
|
fi
|
|
|
|
simple_url="$shorter_simple_url"
|
2015-12-05 12:11:10 +01:00
|
|
|
query_entries "$simple_url"
|
|
|
|
#die "No entry found for »$simple_url«"
|
2015-12-05 12:26:14 +01:00
|
|
|
# enforce menu if we do "fuzzy" matching
|
|
|
|
menu_if_one_entry=1
|
2015-12-05 12:11:10 +01:00
|
|
|
done
|
|
|
|
if [ 0 -eq "${#files[@]}" ] ; then
|
|
|
|
die "No entry found for »$simple_url«"
|
|
|
|
fi
|
|
|
|
}
|
2015-08-14 23:53:52 +02:00
|
|
|
|
|
|
|
# Backend implementations tell, how the actual password store is accessed.
|
|
|
|
# Right now, there is only one fully functional password backend, namely for
|
|
|
|
# the program "pass".
|
|
|
|
# A password backend consists of three actions:
|
|
|
|
# - init() initializes backend-specific things and does sanity checks.
|
|
|
|
# - query_entries() is called with a simplified url and is expected to fill
|
|
|
|
# the bash array $files with the names of matching password entries. There
|
|
|
|
# are no requirements how these names should look like.
|
|
|
|
# - open_entry() is called with some specific entry of the $files array and is
|
|
|
|
# expected to write the username of that entry to the $username variable and
|
|
|
|
# the corresponding password to $password
|
|
|
|
|
|
|
|
reset_backend() {
|
|
|
|
init() { true ; }
|
|
|
|
query_entries() { true ; }
|
|
|
|
open_entry() { true ; }
|
|
|
|
}
|
|
|
|
|
|
|
|
# choose_entry() is expected to choose one entry from the array $files and
|
|
|
|
# write it to the variable $file.
|
|
|
|
choose_entry() {
|
2015-12-05 12:11:10 +01:00
|
|
|
choose_entry_zenity
|
2015-08-14 23:53:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# The default implementation chooses a random entry from the array. So if there
|
|
|
|
# are multiple matching entries, multiple calls to this userscript will
|
|
|
|
# eventually pick the "correct" entry. I.e. if this userscript is bound to
|
|
|
|
# "zl", the user has to press "zl" until the correct username shows up in the
|
|
|
|
# login form.
|
|
|
|
choose_entry_random() {
|
|
|
|
local nr=${#files[@]}
|
|
|
|
file="${files[$((RANDOM % nr))]}"
|
|
|
|
# Warn user, that there might be other matching password entries
|
|
|
|
if [ "$nr" -gt 1 ] ; then
|
|
|
|
msg "Picked $file out of $nr entries: ${files[*]}"
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
# another implementation would be to ask the user via some menu (like rofi or
|
|
|
|
# dmenu or zenity or even qutebrowser completion in future?) which entry to
|
|
|
|
# pick
|
|
|
|
MENU_COMMAND=( head -n 1 )
|
2015-12-05 12:26:14 +01:00
|
|
|
# whether to show the menu if there is only one entrie in it
|
|
|
|
menu_if_one_entry=0
|
2015-08-14 23:53:52 +02:00
|
|
|
choose_entry_menu() {
|
|
|
|
local nr=${#files[@]}
|
2015-12-05 12:26:14 +01:00
|
|
|
if [ "$nr" -eq 1 ] && ! ((menu_if_one_entry)) ; then
|
2015-08-14 23:53:52 +02:00
|
|
|
file="${files[0]}"
|
|
|
|
else
|
|
|
|
file=$( printf "%s\n" "${files[@]}" | "${MENU_COMMAND[@]}" )
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
choose_entry_rofi() {
|
|
|
|
MENU_COMMAND=( rofi -p "qutebrowser> " -dmenu
|
|
|
|
-mesg $'Pick a password entry for <b>'"${QUTE_URL//&/&}"'</b>' )
|
|
|
|
choose_entry_menu || true
|
|
|
|
}
|
|
|
|
|
|
|
|
choose_entry_zenity() {
|
|
|
|
MENU_COMMAND=( zenity --list --title "Qutebrowser password fill"
|
|
|
|
--text "Pick the password entry:"
|
|
|
|
--column "Name" )
|
|
|
|
choose_entry_menu || true
|
|
|
|
}
|
|
|
|
|
|
|
|
choose_entry_zenity_radio() {
|
|
|
|
zenity_helper() {
|
|
|
|
awk '{ print $0 ; print $0 }' \
|
|
|
|
| zenity --list --radiolist \
|
|
|
|
--title "Qutebrowser password fill" \
|
|
|
|
--text "Pick the password entry:" \
|
|
|
|
--column " " --column "Name"
|
|
|
|
}
|
|
|
|
MENU_COMMAND=( zenity_helper )
|
|
|
|
choose_entry_menu || true
|
|
|
|
}
|
|
|
|
|
|
|
|
# =======================================================
|
|
|
|
# backend: PASS
|
|
|
|
|
|
|
|
# configuration options:
|
|
|
|
match_filename=1 # whether allowing entry match by filepath
|
2015-12-05 12:11:10 +01:00
|
|
|
match_line=0 # whether allowing entry match by URL-Pattern in file
|
|
|
|
# Note: match_line=1 gets very slow, even for small password stores!
|
2015-08-14 23:53:52 +02:00
|
|
|
match_line_pattern='^url: .*' # applied using grep -iE
|
2015-12-11 16:54:16 +01:00
|
|
|
user_pattern='^(user|username|login): '
|
2015-08-14 23:53:52 +02:00
|
|
|
|
|
|
|
GPG_OPTS=( "--quiet" "--yes" "--compress-algo=none" "--no-encrypt-to" )
|
|
|
|
GPG="gpg"
|
|
|
|
export GPG_TTY="${GPG_TTY:-$(tty 2>/dev/null)}"
|
|
|
|
which gpg2 &>/dev/null && GPG="gpg2"
|
|
|
|
[[ -n $GPG_AGENT_INFO || $GPG == "gpg2" ]] && GPG_OPTS+=( "--batch" "--use-agent" )
|
|
|
|
|
|
|
|
pass_backend() {
|
|
|
|
init() {
|
|
|
|
PREFIX="${PASSWORD_STORE_DIR:-$HOME/.password-store}"
|
|
|
|
if ! [ -d "$PREFIX" ] ; then
|
|
|
|
die "Can not open password store dir »$PREFIX«"
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
query_entries() {
|
|
|
|
local url="$1"
|
|
|
|
|
|
|
|
if ((match_line)) ; then
|
|
|
|
# add entries with matching URL-tag
|
|
|
|
while read -r -d "" passfile ; do
|
|
|
|
if $GPG "${GPG_OPTS}" -d "$passfile" \
|
|
|
|
| grep --max-count=1 -iE "${match_line_pattern}${url}" > /dev/null
|
|
|
|
then
|
|
|
|
passfile="${passfile#$PREFIX}"
|
|
|
|
passfile="${passfile#/}"
|
|
|
|
files+=( "${passfile%.gpg}" )
|
|
|
|
fi
|
|
|
|
done < <(find -L "$PREFIX" -iname '*.gpg' -print0)
|
|
|
|
fi
|
|
|
|
if ((match_filename)) ; then
|
|
|
|
# add entries wth matching filepath
|
|
|
|
while read -r passfile ; do
|
|
|
|
passfile="${passfile#$PREFIX}"
|
|
|
|
passfile="${passfile#/}"
|
|
|
|
files+=( "${passfile%.gpg}" )
|
|
|
|
done < <(find -L "$PREFIX" -iname '*.gpg' | grep "$url")
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
open_entry() {
|
|
|
|
local path="$PREFIX/${1}.gpg"
|
|
|
|
password=""
|
|
|
|
local firstline=1
|
|
|
|
while read -r line ; do
|
|
|
|
if ((firstline)) ; then
|
|
|
|
password="$line"
|
|
|
|
firstline=0
|
|
|
|
else
|
|
|
|
if [[ $line =~ $user_pattern ]] ; then
|
|
|
|
# remove the matching prefix "user: " from the beginning of the line
|
|
|
|
username=${line#${BASH_REMATCH[0]}}
|
|
|
|
break
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
done < <($GPG "${GPG_OPTS}" -d "$path" )
|
|
|
|
}
|
|
|
|
}
|
|
|
|
# =======================================================
|
|
|
|
|
|
|
|
# load some sane default backend
|
|
|
|
reset_backend
|
|
|
|
pass_backend
|
|
|
|
# load configuration
|
|
|
|
QUTE_CONFIG_DIR=${QUTE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/qutebrowser/}
|
2015-12-05 12:11:10 +01:00
|
|
|
PWFILL_CONFIG=${PWFILL_CONFIG:-${QUTE_CONFIG_DIR}/password_fill_rc}
|
|
|
|
if [ -f "$PWFILL_CONFIG" ] ; then
|
|
|
|
source "$PWFILL_CONFIG"
|
2015-08-14 23:53:52 +02:00
|
|
|
fi
|
|
|
|
init
|
|
|
|
|
|
|
|
simplify_url "$QUTE_URL"
|
|
|
|
query_entries "${simple_url}"
|
2015-12-05 12:11:10 +01:00
|
|
|
no_entries_found
|
|
|
|
# remove duplicates
|
|
|
|
mapfile -t files < <(printf "%s\n" "${files[@]}" | sort | uniq )
|
2015-08-14 23:53:52 +02:00
|
|
|
choose_entry
|
|
|
|
if [ -z "$file" ] ; then
|
|
|
|
# choose_entry didn't want any of these entries
|
|
|
|
exit 0
|
|
|
|
fi
|
|
|
|
open_entry "$file"
|
|
|
|
#username="$(date)"
|
|
|
|
#password="XYZ"
|
2015-12-05 12:11:10 +01:00
|
|
|
#msg "$username, ${#password}"
|
2015-08-14 23:53:52 +02:00
|
|
|
|
|
|
|
[ -n "$username" ] || die "Username not set in entry $file"
|
|
|
|
[ -n "$password" ] || die "Password not set in entry $file"
|
|
|
|
|
|
|
|
js() {
|
|
|
|
cat <<EOF
|
|
|
|
function hasPasswordField(form) {
|
|
|
|
var inputs = form.getElementsByTagName("input");
|
|
|
|
for (var j = 0; j < inputs.length; j++) {
|
|
|
|
var input = inputs[j];
|
|
|
|
if (input.type == "password") {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
function loadData2Form (form) {
|
|
|
|
var inputs = form.getElementsByTagName("input");
|
|
|
|
for (var j = 0; j < inputs.length; j++) {
|
|
|
|
var input = inputs[j];
|
|
|
|
if (input.type == "text" || input.type == "email") {
|
|
|
|
input.value = "$username";
|
|
|
|
}
|
|
|
|
if (input.type == "password") {
|
2015-12-11 16:23:47 +01:00
|
|
|
input.value = "${password//\"/\\\"}";
|
2015-08-14 23:53:52 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
var forms = document.getElementsByTagName("form");
|
|
|
|
for (i = 0; i < forms.length; i++) {
|
|
|
|
if (hasPasswordField(forms[i])) {
|
|
|
|
loadData2Form(forms[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
EOF
|
|
|
|
}
|
|
|
|
|
|
|
|
printjs() {
|
|
|
|
js | sed 's,//.*$,,' | tr '\n' ' '
|
|
|
|
}
|
|
|
|
echo "jseval -q $(printjs)" >> "$QUTE_FIFO"
|