+# WARN: not used in this repo, but gpgconf --list-dirs does not read
+# gpg-agent.conf to get socket path, see dev.gnupg.org/T3108
+
### unset unwanted options that could be set in /etc/zshenv
unsetopt SH_WORD_SPLIT KSH_ARRAYS
export LC_CTYPE=$LANG
## path
+ # path and fpath should already be linked to PATH and FPATH
+ # we don't want duplicates so turn on unique mode if it isn't already
typeset -U path fpath
- if [[ $SHLVL == 1 ]] {
+ # do not run more than once per session, even if resetting shell
+ if [[ $SHLVL == 1 ]] {
# take a backup before any customizations
export _sev_sys_PATH=$PATH
export _sev_sys_FPATH=$FPATH
/usr/local/{s,}bin /usr/games)
PATH=$PATH:$_sev_sys_PATH
fpath=(${ZDOTDIR:-$HOME/.zsh}/functions/{*,Completions/*}(N))
- FPATH=$FPATH:$_sev_sys_FPATH
+ # fpath is not exported by default
+ export FPATH=$FPATH:$_sev_sys_FPATH
# take another backup, explained in .zprofile
typeset -U _backup_path
_backup_path=("${path[@]}")
## xdg
+ # TODO: check for and merge existing XDG env vars
export XDG_CONFIG_HOME=~/etc
export XDG_CONFIG_DIRS=~/.config:/usr/pkg/etc/xdg:/usr/local/etc/xdg:/etc/xdg
export XDG_DATA_HOME=~/share
export XDG_DATA_DIRS=~/.local/share:/usr/pkg/share:/usr/local/share:/usr/share
export XDG_CACHE_HOME=~/tmp
export XDG_RUNTIME_DIR=~/tmp
+ if [[ -e $XDG_CONFIG_HOME/user-dirs.dirs ]] {
+ source $XDG_CONFIG_HOME/user-dirs.dirs
+ }
## create tmp link
- t=${TMPDIR:-/tmp}/home-$LOGNAME
- h=$HOME/tmp
+ t=${TMPDIR:-/tmp}/.home-$LOGNAME
if [[ ! -e $t ]] {
- mkdir -m 700 $t > /dev/null 2>&1
- # TODO: check if dir exists after mkdir
+ mkdir -m 700 $t 2>/dev/null
+ if [[ ! -d $t ]] {
+ [[ -o interactive ]] &&
+ print -P "%F{red}!!! Can't create temp folder $t%f"
+ [[ -h $XDG_RUNTIME_DIR ]] && unlink $XDG_RUNTIME_DIR 2>/dev/null
+ [[ ! -e $XDG_RUNTIME_DIR ]] && mkdir $XDG_RUNTIME_DIR 2>/dev/null
+ }
}
# allow opaque entries to override link creation
- if [[ ! -e $h ]] {
- ln -sf $t $h > /dev/null 2>&1
+ [[ ! -e $XDG_RUNTIME_DIR ]] && ln -sf $t $XDG_RUNTIME_DIR 2>/dev/null
+ unset t
+
+ ## gpg forwarding
+ # NOTE: while ssh automatically sets SSH_AUTH_SOCK with the ForwardSsh
+ # directive, GPG must be forwarded manually. to support this, we
+ # forward the restricted gpg-agent extra socket to the remote host
+ # with a RemoteForward rule in ~/.ssh/config that uses the
+ # _GNUPG_SOCK_* env vars.
+ # to avoid conflicts with other ssh sessions where the same user is
+ # connecting to the same host from different machines, gpg in each
+ # environment should utilize its own forwarded socket, rather than
+ # replace the sockets in GNUPGHOME which will be overridden on the
+ # next connection. previously, you could provide a path to the agent
+ # socket in GPG_AGENT_INFO, but that was deprecated in GPG v2.1.
+ # instead, we must clone GNUPGHOME and replace the agent sockets
+ # there with the forwarded one.
+ # HACK: without SendEnv, which is disabled by default in most sshd configs,
+ # there is no foolproof way to prevent race conditions or filename
+ # collisions, pass the forward path to the remote host environment,
+ # or even know if the forward path exists and is writable. we just
+ # have to guess this path is good on the desination host, and assume
+ # the newest matching socket is the correct one after connecting. in
+ # theory we could occlude the ssh binary on PATH with an alias or
+ # script that communicates with the remote host before opening a
+ # shell, but that would open up too many edge cases where it wouldn't
+ # work to make it worth the effort and extra overhead.
+ # do not run more than once per session, even if resetting shell
+ if [[ $SHLVL == 1 && -v commands[gpg] ]] {
+ export _GNUPG_SOCK_DEST_BASE=/tmp/.gpg-agent-forward
+ export _GNUPG_SOCK_DEST_EXT=$(date +%s).$RANDOM
+ export _GNUPG_SOCK_DEST=$_GNUPG_SOCK_DEST_BASE.$_GNUPG_SOCK_DEST_EXT
+ _sev_gpg_forward_dir=${GNUPGHOME:-~/.gnupg}/.ssh_forward
+ s=($_GNUPG_SOCK_DEST_BASE*(N=oc[1]))
+ # clean up forwards if its session is dead or we ask for it
+ if [[ -d $_sev_gpg_forward_dir ]] {
+ find $_sev_gpg_forward_dir -type d -mindepth 1 -maxdepth 1 |
+ while read -r x; do
+ # NOTE: the only way we can get here is if we are SHLVL 1. if
+ # our own pid already has a dir, it is most likely stale,
+ # or something is very broken—assume the former.
+ p=$(basename $x)
+ if [[ -v _sev_gpg_forward_clean || $$ == $p ]] ||
+ ! kill -0 $p 2>/dev/null; then
+ find $x -mindepth 1 -maxdepth 1 | while read -r y; do
+ unlink $y
+ done
+ rmdir $x
+ fi
+ done
+ unset x p y
+ }
+ # create new forward dir
+ if [[ -n $s && -v SSH_CLIENT ]] {
+ export _sev_gpg_forwarded=
+ mkdir -pm700 $_sev_gpg_forward_dir
+ h=$_sev_gpg_forward_dir/$$
+ mkdir -pm700 $h
+ # XXX: is it safe to link scdaemon socket? can its name be changed?
+ for x in S.scdaemon gpg.conf gpg-agent.conf sshcontrol \
+ pubring.kbx trustdb.gpg private-keys-v1.d crls.d; do
+ ln -s ${GNUPGHOME:-~/.gnupg}/$x $h
+ done
+ export GNUPGHOME=$h
+ unset h
+ for x in $(gpgconf --list-dirs | grep 'agent-.*-\?socket:'); do
+ # dirs are prefixed and percent-encoded—strip and decode
+ # https://stackoverflow.com/a/64312099
+ x=${${x/#agent-*socket:/}//(#b)%([[:xdigit:]](#c2))/${(#):-0x$match[1]}}
+ if [[ ! -v orig ]] {
+ mv $s $x
+ orig=$x
+ } else {
+ ln -s $orig $x
+ }
+ done
+ unset x orig
+ }
+ unset s
+
+ # what we will forward if we start a new ssh connection
+ # NOTE: do this after setting up GNUPGHOME to pick up new socket path;
+ # if already connected over SSH, extra should be the remote one
+ export _GNUPG_SOCK_SRC=$(gpgconf --list-dirs agent-extra-socket)
+ } else {
+ # required for RemoteForward to not error out if the vars are unset
+ export _GNUPG_SOCK_SRC=/nonexistent
+ export _GNUPG_SOCK_DEST=/nonexistent
+ }
+
+ ## gpg agent
+ # always try to start agent during setup
+ if [[ SHLVL == 1 ]] {
+ gpg-connect-agent /bye >/dev/null 2>&1
+ [[ $? -ne 0 && -o interactive ]] &&
+ print -P "%F{red}!!! Can't communicate with GPG agent%f"
+ }
+ # set up tty if it isn't, and we're interactive or in xorg & not forwarded
+ # do not run more than once per session, even if resetting shell
+ if [[ -v commands[gpg-connect-agent] &&
+ ! -v _sev_gpg_forward && ! -v GPG_TTY &&
+ ( -o interactive || -v DISPLAY ) ]] {
+ export GPG_TTY=$(tty)
+ export PINENTRY_USER_DATA=USE_TTY=$((!${+DISPLAY}))
+ gpg-connect-agent UPDATESTARTUPTTY /bye >/dev/null 2>&1
}
- unset t h
## ssh agents
# NOTE: preferred order of agents to check: okcagent, gnupg, openssh
# first block takes care of okcagent and openssh, second gnupg
- print -nP "%F{blue}>>>%f SSH: %F{green}"
+ [[ -o interactive ]] && print -nP "%F{blue}>>>%f SSH: %F{green}"
if [[ ! -v SSH_AUTH_SOCK && ( -v commands[okc-ssh-agent] ||
( -v commands[ssh-agent] && ! -v commands[gpg] ) ) ]] {
okc=${commands[okc-ssh-agent]:+okc-}
IFS=$'\0' read -r sock pid <$agentfile
}
if [[ -S $sock && $pid > 0 ]] && kill -0 $pid; then
- echo "Reusing agent pid $pid"
+ [[ -o interactive ]] && echo "Reusing agent PID $pid"
export SSH_AUTH_SOCK=$sock
export SSH_AGENT_PID=$pid
else
# TODO: ensure ssh-agent path looks legit
# to avoid unsafe eval?
- eval `${okc}ssh-agent`
+ # NOTE: no way around doing redirection like this I think
+ e=${okc}ssh-agent
+ if [[ -o interactive ]] {
+ eval `$e`
+ } else {
+ eval `$e` >/dev/null 2>&1
+ }
echo -n $SSH_AUTH_SOCK$'\0'$SSH_AGENT_PID >!$agentfile
fi
unset okc agentfile sock pid
- } elif [[ -v commands[gpg] && ! -S $_GNUPG_SOCK_DEST && \
- ( ! -v SSH_AUTH_SOCK || -v DISPLAY ) ]] {
- export GPG_TTY=$(tty)
- export PINENTRY_USER_DATA=USE_TTY=$((!${+DISPLAY}))
- gpg-connect-agent UPDATESTARTUPTTY /bye >/dev/null 2>&1
- gpg-connect-agent /subst /serverpid \
- '/echo GPG agent pid ${get serverpid}' /bye
- [[ ! -v SSH_AUTH_SOCK ]] && \
- export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
- } else {
- echo "Pre-existing or remote agent"
- }
-
- ## gpg ssh forwarding
- # ssh automatically tunnels SSH_AUTH_SOCK with the right config, but GPG
- # doesn't—we use a RemoteForward rule in ~/.ssh/config that uses these env
- # vars to push the gpg extra socket through when connecting via ssh
- # HACK: this entire thing sucks but there is no other easy way that works
- # out of the box with other systems
- if [[ -v commands[gpgconf] ]] {
- # if already connected over SSH, reuse forwarded socket for future
- # connections; else use extra socket
- sock=${SSH_CLIENT:+agent-socket}
- export _GNUPG_SOCK_SRC=$(gpgconf --list-dirs ${sock:-agent-extra-socket})
- unset sock
- # XXX: multiple SSH sessions to the same host will overwrite this
- # socket, no way to send unique paths without configuring explicit
- # SendEnv and AcceptEnv exclusions on client and host respectively
- export _GNUPG_SOCK_DEST=/tmp/.gpg-agent-forward
- # if socket exists already, we are on a RemoteForwarded client, so copy
- # it over so that GPG sees it
- # XXX: race condition if connecting multiple terminals at once
- if [[ -S $_GNUPG_SOCK_DEST ]] {
- unlink $_GNUPG_SOCK_SRC >/dev/null 2>&1
- mv $_GNUPG_SOCK_DEST $_GNUPG_SOCK_SRC >/dev/null
+ } elif [[ ! -v SSH_AUTH_SOCK && -v commands[gpg] ]] {
+ # since gpg agent was started above, we just have to export and notify
+ if [[ -o interactive ]] {
+ if [[ -v _sev_gpg_forwarded ]] {
+ echo 'Remote GPG agent'
+ } else {
+ gpg-connect-agent /subst /serverpid \
+ '/echo GPG agent PID ${get serverpid}' /bye
+ }
}
+ export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
+ } elif [[ -v SSH_AUTH_SOCK ]] {
+ [[ -o interactive ]] && echo "Preconfigured agent"
+ } else {
+ [[ -o interactive ]] && print -P "%F{red}No agent available"
}
}
+
### load site-specific
if [[ -f ~/.zshenv.local ]] { source ~/.zshenv.local }