blob: 17069bc7d237424de34a2873243bfcca354394cf (
plain) (
tree)
|
|
#!/bin/ash -- sourced
# Run 'account' or 'auth' PAM mode
# account:
# See if user is allowed to login (=exists in our case)
# auth:
# Try logging in via ldapsearch
# parameters supplied via
# /opt/openslx/pam/slx-ldap.d/*
WAIT=
unset_ldap_vars() {
local vn
for vn in $(set | grep -Eo '^(SHARE|LDAP)_[^=]+'); do
unset "$vn"
done
unset USER_DN USER_UID USER_GID USER_GROUP REAL_ACCOUNT NETWORK_HOME HOME_MOUNT_OPTS
}
logwait() {
slxlog "$@"
WAIT=1
}
# ldapsearch can return fields either as
# field: value
# or
# field:: base64encode(value)
# -- handle both cases and return the value
# extract_field <fieldname> <resultfile> [another resultfile [...]]
extract_field() {
local l len file
local field=$1
shift
while [ $# -gt 0 ]; do
file=$1
shift
l=$( grep -m 1 -i "^${field}: " "$file" )
if [ -n "$l" ]; then
len=$(( ${#field} + 2 ))
echo "${l:$len}"
return
fi
l=$( grep -m 1 -i "^${field}:: " "$file" )
if [ -n "$l" ]; then
len=$(( ${#field} + 3 ))
echo "${l:$len}" | base64 -d
return
fi
done
}
run_auth() {
local BINDDN SEARCH_ANON SEARCH_USER PW RET uid
if [ -n "$LDAP_CACERT" ]; then
export LDAPTLS_CACERT="$LDAP_CACERT"
else
unset LDAPTLS_CACERT
fi
# See if user exists
SEARCH_ANON=$(mktemp)
TEMPFILES_LDAP="$TEMPFILES_LDAP $SEARCH_ANON"
for retries in 0 1 1 2 3 END; do
ldapsearch -x -l 3 -o nettimeout=3 -o ldif-wrap=no \
-H "$LDAP_URI" -b "$LDAP_BASE" uid="${PAM_USER}" \
dn distinguishedName homeMount homeDirectory realAccount uid uidNumber gidNumber ${LDAP_ATTR_MOUNT_OPTS} &> "${SEARCH_ANON}"
RET=$?
case "$RET" in
0) break # OK
;;
51|52) sleep 3 # busy, let's try again
;;
255) # Network error
# grep the output to see whether the bind succeeded, it should have returned anything but -1
< "$SEARCH_ANON" grep -q '^ldap_bind:.*(-1)$' || break # If == -1, continue
;;
*)
logwait "pam-slxldap-ldapsearch" "Initial ldapsearch for $PAM_USER returned $RET" "$SEARCH_ANON"
break
;;
esac
sleep "$retries" &> /dev/null # lazy END handling
done
BINDDN=$(extract_field "dn" "$SEARCH_ANON")
log "ldap search for $PAM_USER return code $RET, result $BINDDN"
[ -z "$BINDDN" ] && BINDDN=$(extract_field "distinguishedName" "$SEARCH_ANON")
[ -z "$BINDDN" ] && return 1
# User exists
# Get proper capitalization
RET=$(extract_field "uid" "$SEARCH_ANON")
[ -n "$RET" ] && USER_NAME="$RET"
uid=$(extract_field "uidNumber" "$SEARCH_ANON")
if [ "$PAM_TYPE" = "account" ]; then
# 'account' checks just if the user is allowed to log in, bail out
USER_UID=$uid
USER_GID=$(extract_field "gidNumber" "$SEARCH_ANON")
USER_HOME=$(extract_field "homeDirectory" "$SEARCH_ANON")
return 0
fi
SEARCH_USER=$(mktemp)
TEMPFILES_LDAP="$TEMPFILES_LDAP $SEARCH_USER"
if [ -z "$SCRIPT_USER" ] || [ "$SCRIPT_USER" = "root" ]; then
PW="/run/pw.${RANDOM}.${PAM_USER}.${RANDOM}.$$"
else
PW="/run/user/${uid}/pw.${RANDOM}.${PAM_USER}.${RANDOM}.$$"
fi
for retries in 0 1 1 0; do
if ! mkfifo -m 0600 "${PW}"; then
logwait "pam-slxldap-fifo" "Could not create FIFO at ${PW}"
return 1
fi
( # Blocking write to FIFO, fork into bg. Make sure to use a shell that understands echo -n (busybox ash does)
echo -n "${USER_PASSWORD}" > "${PW}"
) &
# unquoted LDAP_ATTR_*
# Use "-s base" and BINDDN as search base so Active Directory will return transitive group memberships
ldapsearch -s base -x -l 5 -o nettimeout=5 -o ldif-wrap=no \
-H "$LDAP_URI" -b "$BINDDN" -y "${PW}" -D "$BINDDN" uid="${PAM_USER}" msds-memberOfTransitive "*" &> "${SEARCH_USER}"
RET=$?
rm -f -- "${PW}"
case "$RET" in
0|49|50) break # Either success, or access denied / invalid user/pass
;;
51|52) sleep 3 # busy, let's try again
;;
255) # Network error
# grep the output to see whether the bind succeeded, it should have returned anything but -1
< "$SEARCH_USER" grep -q '^ldap_bind:.*(-1)$' || break # If == -1, continue
;;
*)
logwait "pam-slxldap-ldapsearch" "User-bind for $PAM_USER returned $RET" "$SEARCH_USER"
break
;;
esac
sleep "$retries"
done
log "LDAP bind for '$BINDDN' as $PAM_USER returned $RET"
[ "$RET" = 0 ] || return 1
USER_UID=$(extract_field "uidNumber" "$SEARCH_USER" "$SEARCH_ANON")
if [ -z "$USER_UID" ]; then
logwait --echo "pam-slxldap-nouid" "User $PAM_USER found in ldap, but has no uidNumber" "$SEARCH_USER"
return 1
fi
USER_GID=$(extract_field "gidNumber" "$SEARCH_USER" "$SEARCH_ANON")
if [ -z "$USER_GID" ]; then
logwait --echo "pam-slxldap-nogid" "User $PAM_USER / $USER_UID found in ldap, but has no gidNumber" "$SEARCH_USER"
unset USER_UID
return 1
fi
USER_DN="$BINDDN"
REAL_ACCOUNT=$(extract_field "realAccount" "$SEARCH_USER" "$SEARCH_ANON")
[ -z "$REAL_ACCOUNT" ] && REAL_ACCOUNT=$(extract_field "uid" "$SEARCH_USER" "$SEARCH_ANON")
NETWORK_HOME=$(extract_field "homeMount" "$SEARCH_USER" "$SEARCH_ANON")
USER_HOME=$(extract_field "homeDirectory" "$SEARCH_USER" "$SEARCH_ANON")
HOME_MOUNT_OPTS="${SHARE_HOME_MOUNT_OPTS}"
if [ -n "$LDAP_ATTR_HOME_MOUNT_OPTS" ]; then
RET=$(extract_field "${LDAP_ATTR_HOME_MOUNT_OPTS}" "$SEARCH_USER" "$SEARCH_ANON")
[ -n "$RET" ] && HOME_MOUNT_OPTS="$RET"
fi
# Group - might need another round trip to LDAP
USER_GROUP=$( awk -F: '{ if ($3 == '"$USER_GID"') { print $1; exit } }' /etc/group )
if [ -z "$USER_GROUP" ]; then
ldapsearch -x -LLL -l 2 -o nettimeout=2 -o ldif-wrap=no \
-H "$LDAP_URI" -b "$LDAP_BASE" "(&(objectClass=posixGroup)(gidNumber=${USER_GID}))" \
cn &> "${SEARCH_ANON}"
USER_GROUP=$(extract_field "cn" "$SEARCH_ANON")
fi
USER_INFO_FILE=$(mktemp)
cp "$SEARCH_USER" "$USER_INFO_FILE"
return 0
}
TEMPFILES_LDAP=
SLX_LDAP_FILE=
USER_INFO_FILE=
for s_file in /opt/openslx/pam/slx-ldap.d/*; do
unset_ldap_vars
[ -f "$s_file" ] || continue
. "$s_file"
if [ -z "$LDAP_URI" ] || [ -z "$LDAP_BASE" ]; then
logwait --echo "pam-slxldap-empty" "LDAP config '$s_file' is missing URI or BASE"
continue
fi
if run_auth; then
SLX_LDAP_FILE="$s_file"
break
fi
done
if [ -z "$USER_UID" ]; then
unset_ldap_vars
fi
[ -n "$WAIT" ] && sleep 1
if [ -n "$TEMPFILES_LDAP" ]; then
rm -f -- $TEMPFILES_LDAP # No quotes -- is a list
fi
true
|