#!/bin/dash authorize() { local pkey line authorized_keys_line cmd [ "$SSH_USER_AUTH" -a -f "$SSH_USER_AUTH" ] || return read pkey line < "$SSH_USER_AUTH" || return [ "$pkey" = publickey ] || return authorized_keys_line="$line samizdat: password-authenticated from ${SSH_CONNECTION%% *}" sentinel='Samizdat - YES WE CAN' su - "$USER" -c 'mkdir -p "$HOME"/.ssh; touch "$HOME"/.ssh/authorized_keys' add_before_sentinel "$sentinel" \ "$authorized_keys_line" \ "$HOME"/.ssh/authorized_keys } add_before_sentinel() { local sentinel="$1" add_me="$2" target="$3" sed -i.samizdat~ \ -e "/$sentinel/i $add_me" \ "$target" } password_authentication() { [ "$USER" ] || { echo 'Error: no $USER' >&2; exit 1; } [ "$SSH_REMOTE_KEY" ] || { echo 'Error: no $SSH_REMOTE_KEY' >&2; exit 1; } tty=$(tty) && [ "$tty" != 'not a tty' ] || tty= if [ "$SSH_ORIGINAL_COMMAND" ]; then msg='You are not authorized to execute the command: %s\n' if ! [ "$tty" ]; then msg="$msg"'To authorize your public key via password, reconnect without a command, or with a terminal attached.\n' msg="$msg"'To attach a terminal, use the "-t" option to ssh.\n' else msg="$msg"'To authorize your public key and execute the command, enter your password.\n' fi printf "\n$msg\n" "$SSH_ORIGINAL_COMMAND" >&2 if ! [ "$tty" ]; then exit 1 fi else msg='You are not authorized to log in.\n' msg="$msg"'To authorize your public key and log in, enter your password.\n' printf "\n$msg\n" >&2 fi authorize || exit $? # TODO: blacklist after too many authentication failures if [ "$SSH_ORIGINAL_COMMAND" ]; then exec "$SSH_ORIGINAL_COMMAND" else exec $(getent passwd "$USER"|cut -d: -f7) -i fi exit $? # exec failed } die() { echo "Error: $*" >&2; exit 1; } dequote() { # Sorry about the slashes. The perl would be: s{ \\(.) | '([^']+)' }{ $1$2 }gx git_dir=$(echo -n "$git_dir" | sed -e "s/\\\\\\(.\\)\\|'\\([^']\\+\\)'/\\1\\2/g") } homedir_expand() { git_dir=$(homedir_expand_arg "$git_dir") && [ "$git_dir" ] || die "Could not expand home directory. HOME=$HOME, USER=$USER, id=$(id)" } homedir_expand_arg() { [ "$HOME" ] || die '$HOME is not set.' case "$1" in \~) echo "$HOME";; \~/*) echo "${HOME}${1#\~}";; \~*) local u u=${1#\~} u=${u%%/*} u=$(getent passwd "$u"|cut -f6 -d:) && [ "$u" ] || return 1 echo "$u/${1#*/}";; /*) echo "$1";; *) echo "$HOME/$1";; esac } initialize_git() { local git_dir="$1" anonymous="$2" if ! [ -e "$git_dir" ]; then mkdir -p "$(dirname "$git_dir")" git init --bare "$git_dir" >&2 if [ "$anonymous" ]; then git --git-dir "$git_dir" config samizdat.allow-anonymous-access true git --git-dir "$git_dir" config samizdat.anonymous-ssh-owner "$anonymous" fi fi } is_gitdir() { git rev-parse --resolve-git-dir "$1" >/dev/null 2>&1; } deny() { echo 'Error: permission denied.' >&2; exit 1; } valid_new_public_repo() { local git_dir="$1" [ ! -e "$git_dir" ] || return [ "$HOME" -a -d "$HOME"/public_git ] || return local dirname="$(dirname "$git_dir")" case "$git_dir" in *.git) ;; *) echo 'Error: public repos must be named *.git' >&2 return 1 ;; esac case "$dirname" in $HOME/public_git) return 0 ;; $HOME/public_git/*) # Ensure that no parent directory is named *.git # Also enforce a maximum depth of 4. # Valid: public_git/a/b/c/d.git # Invalid: public_git/a/b/c/d/e.git local n relative="${git_dir#$HOME/public_git/}" for n in 1 2 3 4; do local topmost="${relative%%/*}" case "$topmost" in "$relative") return 0;; *.git) return 1;; esac relative=${relative#$topmost/} done echo 'Error: directories nest too deep' >&2 return 1 ;; *) return 1 ;; esac } GET_NOMIC_USER() { local whitelist_dir="$1" a b keytype keyval keyname if [ "$NOMIC_USER" ] then return elif [ "$SSH_USER_AUTH" ] && [ -f "$SSH_USER_AUTH" ] then read authtype keytype keyval < "$SSH_USER_AUTH" [ "$authtype" = publickey ] || exit for keyname in "${whitelist_dir}"/* do while read a b _ do case "$a $b" in "$keytype $keyval") NOMIC_USER=${keyname#authorized_keys.d/} break ;; esac done < "$keyname" done true else warn "\$SSH_USER_AUTH missing. Try putting 'ExposeAuthInfo yes' in /etc/ssh/sshd_config" false fi } check_if_ssh_user_owns_repository() { git --git-dir "$git_dir" config --get-all samizdat.anonymous-ssh-owner | grep -xqF "$SSH_REMOTE_FINGERPRINT_TRIMMED" } ssh_user_owns_repository() { if [ -z "$SSH_USER_OWNS_REPOSITORY" ]; then check_if_ssh_user_owns_repository || GET_NOMIC_USER "$git_dir" SSH_USER_OWNS_REPOSITORY=$? fi return $SSH_USER_OWNS_REPOSITORY } authorized() { # TODO: check SSH_REMOTE_FINGERPRINT against a blacklist ssh_user_owns_repository && return test "$(git --git-dir "$1" config --bool --get samizdat.allow-anonymous-access)" = true 2>/dev/null && return 0 # TODO: check SSH_REMOTE_FINGERPRINT against a whitelist } maybe_initialize_heads() { [ "$GIT_NAMESPACE" ] || die 'Programmer error' heads=$git_dir/refs/namespaces/$GIT_NAMESPACE/refs/heads mkdir -p "$heads" found_file=$(find "$heads" -type f -print -quit) [ "$found_file" ] && return [ -e "$git_dir/refs/heads/master" ] && cp "$git_dir/refs/heads/master" "$heads" } if [ "$1" = "authorize-full-access" ]; then case "$SSH_ORIGINAL_COMMAND" in git-receive-pack\ *) git_cmd=git-receive-pack git_dir="${SSH_ORIGINAL_COMMAND#git-receive-pack }" dequote homedir_expand initialize_git "$git_dir" exec "$git_cmd" "$git_dir" ;; "") shell=$(getent passwd "$USER"|cut -d: -f7) argv0=-${shell##*/} exec chpst -b "$argv0" "$shell" ;; *) exec /bin/sh -c "$SSH_ORIGINAL_COMMAND" ;; esac fi # TODO: call password_authentication on all authorization failures SSH_REMOTE_FINGERPRINT_TRIMMED=$(echo $SSH_REMOTE_FINGERPRINT|tr -d :) # echo "SSH_ORIGINAL_COMMAND=$SSH_ORIGINAL_COMMAND" >&2 case "$SSH_ORIGINAL_COMMAND" in git-receive-pack\ *) git_cmd=git-receive-pack git_dir="${SSH_ORIGINAL_COMMAND#git-receive-pack }" export GIT_NAMESPACE="$SSH_REMOTE_FINGERPRINT_TRIMMED" ;; git-upload-pack\ *) git_cmd=git-upload-pack git_dir="${SSH_ORIGINAL_COMMAND#git-upload-pack }" ;; rsync\ --server\ --sender\ *) [ -d "$HOME"/public_rsync ] || { password_authentication; exit 1; } exec rrsync -ro "$HOME"/public_rsync exit 1 ;; rsync\ --server\ *) [ -d "$HOME"/incoming_rsync ] || { password_authentication; exit 1; } fp=$(echo $SSH_REMOTE_FINGERPRINT|tr -d :) destdir=$HOME/incoming_rsync/$fp/ mkdir -p "$destdir" && exec rrsync "$destdir" exit 1 ;; *) password_authentication exit 1 # unreached ;; esac dequote homedir_expand if [ -d "$git_dir" ]; then is_gitdir "$git_dir" || git_dir="$git_dir/.git" if ! is_gitdir "$git_dir"; then # git rev-parse --resolve-git-dir "${git_dir%/.git}" # show git's error message deny fi if [ "$git_cmd" = 'git-upload-pack' ]; then case "$git_dir" in $HOME/public_git/*|public_git/*) exec "$git_cmd" "$git_dir";; esac fi elif [ "$git_cmd" = 'git-receive-pack' ] && valid_new_public_repo "$git_dir"; then initialize_git "$git_dir" "$SSH_REMOTE_FINGERPRINT_TRIMMED" else deny fi if authorized "$git_dir"; then if [ "$git_cmd" = 'git-receive-pack' ]; then if ssh_user_owns_repository; then unset GIT_NAMESPACE else maybe_initialize_heads fi fi exec "$git_cmd" "$git_dir" else password_authentication echo 'Error: git access is unauthorized' >&2; exit 1 # unreached fi