summaryrefslogtreecommitdiffstats
path: root/core/modules/pam-bwidm/data/opt/openslx/scripts/pam_bwidm
blob: 011256a07e4b9651878f9c1dd8d4f6ed18e4206d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#!/bin/ash
#
# This script is to be called by PAM (specifically pam_exec).
# We expect the username in the form: username@organisation
# If it is in that form, we will query the masterserver for the list
# of supported IdPs and if one matches the user's organisation
# we will try to authenticate against it.

# fix PATH as PAM clears it
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/openslx/sbin:/opt/openslx/bin"

# grab the password from stdin asap, since there is no guarantee some tool just reads it
unset USER_PASSWORD
if [ "x$PAM_TYPE" == "xauth" ]; then
	read -r USER_PASSWORD > /dev/null 2>&1
	readonly USER_PASSWORD
	[ -z "$USER_PASSWORD" ] && echo "No password given." && exit 1
fi

# sanity check on PAM_USER: contains '@'?
if [ -z "$PAM_USER" ] || [ "x${PAM_USER}" == "x${PAM_USER%@*}" ]; then
	# no @ contained, invalid username, abort
	#echo "Invalid username '$PAM_USER'. Aborting."
	exit 1
fi

if ! busybox which curl || ! busybox which mktemp; then
	echo "'curl/mktemp' missing. This script won't work without it."
	exit 1
fi

# determine proper tmp dir, prefer one in RAM
for TMPDIR in "/run" "/run/user/$(id -u)" "/dev/shm" "/home/$(whoami)" "/tmp"; do
	[ -d "$TMPDIR" ] && [ -w "$TMPDIR" ] && [ -r "$TMPDIR" ] && break
done
readonly TMPDIR

# redirect stdout/stderr to temporary logfile
readonly LOGFILE="$(mktemp -p "$TMPDIR")"
# URL to query masterserver for IDPs
readonly IDP_QUERY_URL="https://bwlp-masterserver.ruf.uni-freiburg.de/webif/pam.php"
readonly IDP_QUERY_CACHE="/run/openslx/bwlp-idp"

# everything in a subshell in an effort to hide sensitive information
# from this script's environment
###
( #
###
# redirect stdout and stderr to logfile
exec > "${LOGFILE}" 2>&1

# check if we are allowed to run
. /opt/openslx/config
if [ "x${SLX_BWIDM_AUTH}" = "xyes" ]; then
	: # Allow everything
elif [ "x${SLX_BWIDM_AUTH}" = "xselective"  ]; then
	if [ -z "${SLX_BWIDM_ORGS}" ]; then
		echo "bwIDM selective mode with empty org list - exiting"
		exit 1
	fi
else
	echo "bwIDM login disabled in openslx-config."
	exit 1
fi

# valid username, we can already split it here
readonly USER_USERNAME="${PAM_USER%@*}"
readonly USER_ORGANISATION="${PAM_USER#*@}"
[ -z "$USER_ORGANISATION" ] && echo "Could not parse organisation from given login: ${PAM_USER}. Aborting." && exit 1
[ -z "$USER_USERNAME" ] && echo "Could not parse user from given login: ${PAM_USER}. Aborting." && exit 1

# Check if we're in selective mode and if so, whether the user's organization is whitelisted
if [ "x${SLX_BWIDM_AUTH}" = "xselective" ]; then
	FOUND=
	for org in ${SLX_BWIDM_ORGS}; do
		if [ "x$org" = "x$USER_ORGANISATION" ]; then
			FOUND=ya
			break
		fi
	done
	if [ -z "$FOUND" ]; then
		echo "bwIDM organization $USER_ORGANISATION not in whitelist, abort"
		exit 1
	fi
fi

# The given username is valid. Now we get the list of IdPs from the bwlp masterserver
# and try to find the user's organisation

mkdir -p /run/openslx

# check if we have a (non-zero bytes) cached copy of the list
if ! [ -s "${IDP_QUERY_CACHE}" ]; then
	if ! [ -w "/run/openslx" ]; then
		echo "No IDP info cached, cache path not writable for current user."
		exit 7
	fi
	idpret="$(curl -w "%{http_code}" -o "${IDP_QUERY_CACHE}" --connect-timeout 5 --max-time 15 "$IDP_QUERY_URL")"
	if [ "${#idpret}" != 3 ] || [ "x${idpret:0:1}" != "x2" ]; then
		echo "Could not download the list of identity providers from '$IDP_QUERY_URL'. Aborting."
		rm -f -- "$IDP_QUERY_CACHE"
		exit 7
	fi
fi
# here we have the cache for sure, search for the given organisation's ECP URL
USER_ECP_URL="$(awk -v idp="${USER_ORGANISATION}" -F '=' '{if($1==idp) print $2}' < "$IDP_QUERY_CACHE")"
[ -z "$USER_ECP_URL" ] && echo "Could not determine ECP URL for '${USER_ORGANISATION}'" && exit 1

# recap: here we have validated
#	- username
#	- organisation
#	- ECP URL for that organisation

# now create the bwidm group: find the first free GID from 1000 "downwards" to 100
BWIDM_GROUP="$(getent group bwidm)"
if [ -z "$BWIDM_GROUP" ]; then
	BWIDM_GID=999
	while [ "$BWIDM_GID" -gt 100 ]; do
		getent group "$BWIDM_GID" || break
		let BWIDM_GID--
	done
	if [ "$BWIDM_GID" -eq 100 ]; then
		# use demo's gid as a fallback
		readonly BWIDM_GID="$(id -g "demo")"
		[ -z "$BWIDM_GID" ] && echo "Could not determine the GID of 'demo'. Cannot use it as fallback. Aborting." && exit 1
	fi

	# now create the group
	if ! echo "bwidm:x:$BWIDM_GID:" >> /etc/group; then
		echo "Could not create 'bwidm' group with gid '$BWIDM_GID'. Aborting."
		exit 1
	fi
else
	readonly BWIDM_GID="$(echo $BWIDM_GROUP | cut -d: -f3)"
fi
if [ -z "$BWIDM_GID" ]; then
	echo "Could not determine BWIDM-GID. Aborting."
	exit 1
fi
readonly USER_GID="$BWIDM_GID"

# path to the SOAP envelope we are gonna need soon
readonly SOAP_ENVELOPE="/opt/openslx/bwidm_soap.xml"
[ ! -f "${SOAP_ENVELOPE}" ] && echo "Failed to find the SOAP envelope at '${SOAP_ENVELOPE}'. Aborting." && exit 1

# now the pam-type specific part starts
if [ "x$PAM_TYPE" == "xauth" ]; then
	CT='Content-Type: text/xml; charset=utf-8'
	NOW=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
	HOST=$(echo "${USER_ECP_URL}" | awk -F '/' '{print $3}')
	RID="_c${RANDOM}a${RANDOM}f${RANDOM}f${RANDOM}e${RANDOM}e${RANDOM}"
	RID="${RID:0:32}"
	REQUEST=$(sed "s/%TIMESTAMP%/${NOW}/g;s/%REQUESTID%/${RID}/g" "${SOAP_ENVELOPE}")
	NETRC=$(mktemp -p "$TMPDIR")
	[ -z "$NETRC" ] && NETRC="$TMPDIR/netrc_$$_${USER}_${RANDOM}.tmp"
	touch "$NETRC"
	chmod 0600 "$NETRC"
	# now we are ready to actually send the credentials to the IdP
	# to be sure everything is working as expected
	# we will first send a wrong password and expect a 401
	echo "machine ${HOST} login ${USER_USERNAME} password ___invalid-INVALID++~" > "${NETRC}"
	ret=$(curl --connect-timeout 5 --max-time 15 -o /dev/null -w "%{http_code}" -d "${REQUEST}" -H "$CT" --basic --netrc-file "$NETRC" "$USER_ECP_URL")

	if [ "x$ret" != "x401" ]; then
		# this means something else is bad, just exit
		echo "False authentication attempt did not return 401 as expected but: $ret"
		rm -f -- "${NETRC}"
		exit 7
	fi
	# the fake auth call behaved as expected, do the actual login
	echo "machine ${HOST} login ${USER_USERNAME} password ${USER_PASSWORD}" > "${NETRC}"
	ret=$(curl --connect-timeout 5 --max-time 15 -o /dev/null -w "%{http_code}" -d "${REQUEST}" -H "$CT" --basic --netrc-file "$NETRC" "$USER_ECP_URL")
	echo "machine ${HOST} login ${USER_USERNAME} password ********************" > "${NETRC}" # It should be a tmpfs but you never know
	rm -f -- "${NETRC}"
	
	if [ "${#ret}" = 3 ] && [ "x${ret:0:1}" == "x2" ]; then
		# 2xx code, auth succeeded, lets create a local user representing the bwIDM user
		echo "Login for '$USER_USERNAME' on '$USER_ORGANISATION' succeeded."
		gexp="$( printf "%s" "${PAM_USER}" | sed 's/[][$^\.*]/\\&/g' )" # Basic regexp
		if ! grep -q "^${gexp}:" /etc/passwd; then
			# create a random 6digit UID
			LOOPS=0
			while [ "$LOOPS" -lt 5 ]; do
				USER_UID="$(( 100000 + $RANDOM ))"
				# check existence of this UID, if its free, use it
				getent passwd "$USER_UID" || break
				let LOOPS++
			done
			if [ "$LOOPS" -eq 5 ]; then
				# could not find an empty random 6-digit UID, so we will use demo's UID...
				USER_UID="$(id -u demo)"
				[ -z "$USER_UID" ] && echo "Could not use UID of 'demo' as a fallback, aborting..." && exit 1
			fi

			# we have a uid, gid, lets just create the local user now
			echo "${PAM_USER}:x:${USER_UID}:${USER_GID}:${PAM_USER}:/home/${PAM_USER}:/bin/bash" >> /etc/passwd
		fi
		exit 0
	elif [ "x$ret" != "x401" ]; then
		# not 200, not 401, some other kind of error occured, inform slx-admin
		echo "Unexpected http response code for the login attempt: $ret"
		exit 7
	fi
	exit 1
fi

if [ "x$PAM_TYPE" == "xaccount" ]; then
	# the sanity checks we did before reacting to PAM_TYPE is enough to validate
	# the given username as a valid bwIDM username
	# ('@' contained and IdP found in the idp list fetched from the masterserver)
	# so just "accept"
	exit 0
fi

# script should never get to the following line
echo "$0 called for unsupported PAM_TYPE '$PAM_TYPE'. Aborting."
exit 1
###
) #
# #
## main script
mainret=$?
if [ "x$mainret" == "x7" ]; then
	# exit code 7 is our marker to push the logfile to the sat
	slxlog --delete "pam-bwidm" "Internal error during bwIDM authentication" "${LOGFILE}"
	exit 1
else
	rm -- "${LOGFILE}"
fi
exit "${mainret}"