#!/bin/sh set -eu set -f DA_BIN="${MAIL_LOGIN_DA_BIN:-/usr/bin/da}" CONFIG_FILE="${MAIL_LOGIN_HELPER_CONFIG:-/usr/local/hitme_plugins/mail-login-helper/config/helper-settings.conf}" AUDIT_LOG="${MAIL_LOGIN_AUDIT_LOG:-/usr/local/hitme_plugins/mail-login-helper/logs/audit.log}" STATE_DIR="${MAIL_LOGIN_STATE_DIR:-/usr/local/hitme_plugins/mail-login-helper/state}" if [ -f "$CONFIG_FILE" ]; then # shellcheck disable=SC1090 . "$CONFIG_FILE" fi audit() { dir=$(dirname "$AUDIT_LOG") mkdir -p "$dir" chmod 700 "$dir" php -r '$fields=["timestamp"=>gmdate("c"),"event"=>$argv[1],"result"=>$argv[2],"correlation"=>$argv[3],"username"=>$argv[4],"source_host"=>$argv[5],"client_ip"=>$argv[6]]; echo json_encode($fields, JSON_UNESCAPED_SLASHES)."\n";' \ "$1" "$2" "$3" "$4" "$5" "$6" >> "$AUDIT_LOG" chmod 600 "$AUDIT_LOG" } fail() { audit "create_login_url" "failure" "${CORRELATION:-unknown}" "${USERNAME_ARG:-unknown}" "${SOURCE_HOST:-unknown}" "${CLIENT_IP:-unknown}" echo "mail-login helper failed" >&2 exit 1 } clean_label() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g; s/^-*//; s/-*$//' } first_label() { printf '%s' "$1" | sed 's/:.*$//' | awk -F. '{print $1}' } host_allowed() { allowed="${MAIL_LOGIN_ALLOWED_SOURCE_HOSTS:-}" [ -n "$allowed" ] || return 1 old_ifs=$IFS IFS=, for host in $allowed; do normalized=$(printf '%s' "$host" | tr '[:upper:]' '[:lower:]' | sed 's#^https\?://##; s#/.*$##; s/:[0-9][0-9]*$//; s/^ *//; s/ *$//') [ "$normalized" = "$SOURCE_HOST" ] && IFS=$old_ifs && return 0 done IFS=$old_ifs return 1 } valid_ip() { php -r 'exit(filter_var($argv[1], FILTER_VALIDATE_IP) ? 0 : 1);' "$1" } if [ "$#" -eq 0 ] && [ -n "${SSH_ORIGINAL_COMMAND:-}" ]; then # The authorized_keys forced command receives the requested command here. # All accepted fields are validated below before use. set -- $SSH_ORIGINAL_COMMAND fi COMMAND="${1:-}" USERNAME_ARG="${2:-}" SOURCE_HOST="${3:-}" SERVER_NAME="${4:-}" TTL="${5:-}" USER_IP_BOUND="${6:-}" CLIENT_IP="${7:-}" REDIRECT_PATH="${8:-}" TIMESTAMP_ARG="${9:-}" [ "$COMMAND" = "mail-login-create-url" ] || fail printf '%s' "$USERNAME_ARG" | grep -Eq '^[A-Za-z0-9][A-Za-z0-9._-]{0,31}$' || fail printf '%s' "$SOURCE_HOST" | grep -Eq '^[A-Za-z0-9.-]+$' || fail SOURCE_HOST=$(printf '%s' "$SOURCE_HOST" | tr '[:upper:]' '[:lower:]') host_allowed || fail SERVER_NAME=$(first_label "$SOURCE_HOST") SERVER_NAME=$(clean_label "$SERVER_NAME") printf '%s' "$SERVER_NAME" | grep -Eq '^[a-z0-9-]{1,32}$' || fail printf '%s' "$TTL" | grep -Eq '^[0-9]+$' || fail [ "$TTL" -ge 5 ] && [ "$TTL" -le 300 ] || fail [ "$USER_IP_BOUND" = "0" ] || [ "$USER_IP_BOUND" = "1" ] || fail valid_ip "$CLIENT_IP" || fail case "$REDIRECT_PATH" in /*) ;; *) fail ;; esac case "$REDIRECT_PATH" in //*|*\\*) fail ;; esac printf '%s' "$REDIRECT_PATH" | grep -Eq '^/[A-Za-z0-9._~%/?=+-]*$' || fail printf '%s' "$TIMESTAMP_ARG" | grep -Eq '^[0-9]+$' || fail SERVER_CLEAN=$(clean_label "$SERVER_NAME") USER_CLEAN=$(clean_label "$USERNAME_ARG") TIMESTAMP="$TIMESTAMP_ARG" CORRELATION="autologin-$SERVER_CLEAN-$USER_CLEAN-$TIMESTAMP" if [ ${#CORRELATION} -gt 96 ]; then HASH=$(printf '%s' "$CORRELATION" | openssl dgst -sha256 -r 2>/dev/null | awk '{print substr($1,1,10)}') SUFFIX="-$HASH-$TIMESTAMP" BODY=$(printf '%s' "autologin-$SERVER_CLEAN-$USER_CLEAN" | cut -c 1-$((96 - ${#SUFFIX})) | sed 's/-*$//') CORRELATION="$BODY$SUFFIX" fi mkdir -p "$STATE_DIR" chmod 700 "$STATE_DIR" if [ "$USER_IP_BOUND" = "1" ]; then OUTPUT=$("$DA_BIN" login-url "--user=$USERNAME_ARG" "--redirect-url=$REDIRECT_PATH" "--expiry=${TTL}s" "--ip=$CLIENT_IP") || fail else OUTPUT=$("$DA_BIN" login-url "--user=$USERNAME_ARG" "--redirect-url=$REDIRECT_PATH" "--expiry=${TTL}s") || fail audit "ip_bound_disabled" "warning" "$CORRELATION" "$USERNAME_ARG" "$SOURCE_HOST" "$CLIENT_IP" fi URL=$(printf '%s\n' "$OUTPUT" | awk '/https:\/\// {for (i=1;i<=NF;i++) if ($i ~ /^https:\/\//) {print $i; exit}}') [ -n "$URL" ] || fail case "$URL" in https://*/api/login/url?key=*) ;; *) fail ;; esac LOGIN_URL_ID=$(printf '%s\n' "$OUTPUT" | awk 'match($0,/HASHURL[A-Z0-9]+/) {print substr($0,RSTART,RLENGTH); exit}') php -r '$fields=["timestamp"=>(int)$argv[1],"expires"=>(int)$argv[2],"correlation"=>$argv[3],"id"=>$argv[4],"username"=>$argv[5],"source_host"=>$argv[6],"client_ip"=>$argv[7],"ttl"=>(int)$argv[8],"ip_bound"=>(int)$argv[9],"redirect_path"=>$argv[10],"status"=>"created"]; echo json_encode($fields, JSON_UNESCAPED_SLASHES)."\n";' \ "$TIMESTAMP" "$((TIMESTAMP + TTL))" "$CORRELATION" "$LOGIN_URL_ID" "$USERNAME_ARG" "$SOURCE_HOST" "$CLIENT_IP" "$TTL" "$USER_IP_BOUND" "$REDIRECT_PATH" > "$STATE_DIR/$CORRELATION.json" chmod 600 "$STATE_DIR/$CORRELATION.json" audit "create_login_url" "success" "$CORRELATION" "$USERNAME_ARG" "$SOURCE_HOST" "$CLIENT_IP" printf 'URL: %s\n' "$URL"