d73491b0c8
Add a configurable userscript that fills login forms (i.e. the fiels "Username" and "Password) of websites using a configurable backend where the actual passwords are stored. The only backend yet is using the password store "pass".
277 lines
8.8 KiB
Bash
Executable File
277 lines
8.8 KiB
Bash
Executable File
#!/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
|
|
|
|
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
|
|
entry field.
|
|
|
|
EOF
|
|
}
|
|
|
|
set -o pipefail
|
|
shopt -s nocasematch
|
|
|
|
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
|
|
simple_url="${simple_url##www.}" # remove www. subdomain
|
|
}
|
|
|
|
|
|
# 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() {
|
|
choose_entry_random
|
|
}
|
|
|
|
# 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 )
|
|
choose_entry_menu() {
|
|
local nr=${#files[@]}
|
|
if [ "$nr" -eq 1 ] ; then
|
|
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
|
|
match_line=1 # whether allowing entry match by URL-Pattern in file
|
|
match_line_pattern='^url: .*' # applied using grep -iE
|
|
user_pattern='^(user|username): '
|
|
|
|
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/}
|
|
PWFILL_CONFIG=${PFILL_CONFIG:-${QUTE_CONFIG_DIR}/password_fill_rc}
|
|
if [ -f "$CONFIG_FILE" ] ; then
|
|
source "$CONFIG_FILE"
|
|
fi
|
|
init
|
|
|
|
simplify_url "$QUTE_URL"
|
|
query_entries "${simple_url}"
|
|
if [ 0 -eq "${#files[@]}" ] ; then
|
|
die "No entry found for »$simple_url«"
|
|
fi
|
|
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"
|
|
|
|
[ -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") {
|
|
input.value = "$password";
|
|
}
|
|
}
|
|
};
|
|
|
|
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"
|