apache.conf.in: include input byte count if available
[girocco.git] / bin / git-http-backend-verify
blob11cdc99805c38de25cb32c3087724f935f38962b
1 #!/bin/sh
3 # Abort any push early if the pushing user doesn't have any push permissions
4 # at all. This avoids unnecessary traffic and unpacked object pollution.
6 # Set GIT_HTTP_BACKEND_BIN to change the default http-backend binary from
7 # the default of Config.pm $git_http_backend_bin (which itself has a default
8 # of "/usr/lib/git-core/git-http-backend")
10 # Note that GIT_PROJECT_ROOT is automatically set to $cfg_reporoot and exported
11 # any incoming value for it will be ignored.
13 # Note that GIT_HTTP_EXPORT_ALL is automatically set to 1 and exported.
15 # Also prevents standard error output from git-http-backend cluttering up the
16 # server's log unless GIT_HTTP_BACKEND_SHOW_ERRORS is set to a non-empty value.
18 # Bundle fetches are handled in this script as well.
20 set -e
22 . @basedir@/shlib.sh
24 unset GIT_USER_AGENT
25 unset GIT_HTTP_USER_AGENT
26 if [ -n "$defined_cfg_git_server_ua" ]; then
27 GIT_USER_AGENT="$cfg_git_server_ua"
28 export GIT_USER_AGENT
29 GIT_HTTP_USER_AGENT="$cfg_git_server_ua"
30 export GIT_HTTP_USER_AGENT
32 if [ -n "$cfg_SmartHTTPOnly" -a "$cfg_SmartHTTPOnly" != "0" ]; then
33 git_add_config "http.getanyfile=false"
36 [ -z "$GIT_HTTP_BACKEND_BIN" ] || cfg_git_http_backend_bin="$GIT_HTTP_BACKEND_BIN"
37 [ -n "$cfg_git_http_backend_bin" ] ||
38 cfg_git_http_backend_bin="$var_git_exec_path/git-http-backend"
40 GIT_PROJECT_ROOT="$cfg_reporoot"
41 GIT_HTTP_EXPORT_ALL=1
42 export GIT_PROJECT_ROOT
43 export GIT_HTTP_EXPORT_ALL
45 # This script is called for both fetch and push.
46 # Only the following conditions trigger a push permissions check:
48 # 1. REQUEST_METHOD=GET
49 # and PATH_INFO ends with "/info/refs"
50 # and QUERY_STRING has "service=git-receive-pack"
52 # 2. REQUEST_METHOD=POST
53 # and PATH_INFO ends with "/git-receive-pack"
55 # Note that there is no check for PATH_INFO being under a certain root as
56 # GIT_PROJECT_ROOT will be exported and set so all PATH_INFO values are
57 # effectively forced under the desired root.
59 # The REQUEST_METHOD is validated. For smart HTTP requests only the project
60 # name is extracted and validated and the corresponding project directory must
61 # exist under $cfg_reporoot. Non-smart HTTP fetch requests (GET or HEAD) are
62 # passed on unchanged and unchecked.
64 errorhdrsct()
66 _ct="$1"; shift
67 printf '%s\r\n' "Status: $1 $2"
68 printf '%s\r\n' "Expires: Fri, 01 Jan 1980 00:00:00 GMT"
69 printf '%s\r\n' "Pragma: no-cache"
70 printf '%s\r\n' "Cache-Control: no-cache,max-age=0,must-revalidate"
71 [ -z "$3" ] || printf '%s\r\n' "$3"
72 printf '%s\r\n' "Content-Type: $_ct"
73 printf '\r\n'
76 errorhdrs()
78 errorhdrsct 'text/plain' "$@"
81 msglines()
83 while [ $# -gt 0 ]; do
84 printf '%s\n' "$1"
85 shift
86 done
89 internalerr()
91 errorhdrs 500 "Internal Server Error"
92 if [ $# -eq 0 ]; then
93 msglines "Internal Server Error"
94 echo "Internal Server Error" >&2
95 else
96 msglines "$@"
97 while [ $# -gt 0 ]; do
98 printf '%s\n' "$1" >&2
99 shift
100 done
102 exit 0
105 methodnotallowed()
107 errorhdrs 405 "Method Not Allowed" "Allow: GET,HEAD,POST"
108 if [ $# -eq 0 ]; then
109 msglines "Method Not Allowed"
110 else
111 msglines "$@"
113 exit 0
116 forbidden()
118 errorhdrs 403 Forbidden
119 if [ $# -eq 0 ]; then
120 msglines "Forbidden"
121 else
122 msglines "$@"
124 exit 0
127 notfound()
129 errorhdrs 404 "Not Found"
130 if [ $# -eq 0 ]; then
131 msglines "Not Found"
132 else
133 msglines "$@"
135 exit 0
138 needsauth()
140 unset AUTHREQUIRED_MESSAGE
141 msg="$*"
142 if [ -n "$msg" ]; then
143 AUTHREQUIRED_MESSAGE="$msg"
144 export AUTHREQUIRED_MESSAGE
146 exec "$cfg_cgiroot/authrequired.cgi" || :
147 # fallback in case exec fails
148 errorhdrs 401 "Authorization Required"
149 if [ $# -eq 0 ]; then
150 msglines "Authorization Required"
151 else
152 msglines "$@"
154 exit 0
157 # Single argument is an absolute PATH (NOT a URI) to 302 redirect to
158 # The appropriate http pull URL path prefix is automatically inserted
159 redir()
161 _pullurl="$cfg_httpbundleurl"
162 [ -n "$_pullurl" ] || _pullurl="$cfg_httpspushurl"
163 [ -n "$_pullurl" ] || _pullurl="$cfg_httppullurl"
164 _absbase="${_pullurl%/}/"
165 _absbase="${absbase##*://}"
166 _absbase="${absbase##*/}"
167 [ -z "$_absbase" ] || _absbase="/$_absbase"
168 _loc="https"
169 [ "$HTTPS" = "on" ] || _loc="http"
170 _loc="$_loc://$SERVER_NAME"
171 [ "$HTTPS" != "on" -o "$SERVER_PORT" = "443" ] || _loc="$_loc:$SERVER_PORT"
172 [ "$HTTPS" = "on" -o "$SERVER_PORT" = "80" ] || _loc="$_loc:$SERVER_PORT"
173 _loc="$_loc$_absbase"
174 case "$1" in /*) :;; *) _loc="$_loc/";; esac
175 _loc="$_loc$1"
176 errorhdrsct 'text/html' 302 "Found" "Location: $_loc"
177 if [ "$REQUEST_METHOD" != "HEAD" ]; then
178 printf '<p>Temporarily redirected to <a href="%s">%s</a></p>\n' \
179 "$_loc" "$_loc"
181 exit 0
184 # A quick sanity check
185 if [ -z "$cfg_git_http_backend_bin" ] || ! [ -x "$cfg_git_http_backend_bin" ]; then
186 internalerr "bad cfg_git_http_backend_bin: $cfg_git_http_backend_bin"
187 exit 1
189 case "$cfg_reporoot" in /?*) :;; *)
190 internalerr "bad reporoot: $cfg_reporoot"
191 exit 1
192 esac
193 [ -n "$GIT_PROJECT_ROOT" ] || { internalerr 'GIT_PROJECT_ROOT must be set'; exit 1; }
195 PATH="$(dirname "$cfg_git_http_backend_bin"):$PATH"
196 export PATH
198 digit='[0-9]'
199 digit6="$digit$digit$digit$digit$digit$digit"
200 digit8="$digit6$digit$digit"
201 proj=
202 smart=
203 bundle=
204 suffix=
205 needsauthcheck=
206 pathcheck="${PATH_INFO#/}"
207 if [ "$REQUEST_METHOD" = "GET" -o "$REQUEST_METHOD" = "HEAD" ]; then
208 # We do not currently validate non-smart GET/HEAD requests.
209 # There are only 8 possible suffix values that need to be allowed for
210 # non-smart HTTP GET/HEAD fetches (see http-backend.c):
211 # /HEAD
212 # /info/refs
213 # /objects/info/alternates
214 # /objects/info/http-alternates
215 # /objects/info/packs
216 # /objects/[0-9a-f]{2}/[0-9a-f]{38}
217 # /objects/pack/pack-[0-9a-f]{40}.idx
218 # /objects/pack/pack-[0-9a-f]{40}.pack
219 # We do, however, need to recognize a /*.bundle fetch so that
220 # we can properly handle it.
221 case "$pathcheck" in
222 *"/info/refs")
223 proj="${pathcheck%/info/refs}"
224 case "&$QUERY_STRING&" in
225 *"&service=git-receive-pack&"*)
226 smart=1
227 needsauthcheck=1
228 suffix=info/refs
230 *"&service=git-upload-pack&"*)
231 smart=1
232 suffix=info/refs
234 esac
236 */*[!./].bundle)
237 bundle=1
238 smart=1
239 proj="${pathcheck%/*.bundle}"
240 suffix="${pathcheck#$proj/}"
242 esac
243 elif [ "$REQUEST_METHOD" = "POST" ]; then
244 case "$pathcheck" in
245 *"/git-receive-pack")
246 smart=1
247 needsauthcheck=1
248 proj="${pathcheck%/git-receive-pack}"
249 suffix=git-receive-pack
251 *"/git-upload-pack")
252 smart=1
253 proj="${pathcheck%/git-upload-pack}"
254 suffix=git-upload-pack
257 forbidden
258 exit 1
260 esac
261 else
262 methodnotallowed
263 exit 1
266 # Reject any project names that start with _ or contain ..
267 case "$pathcheck" in _*|*..*)
268 forbidden
269 esac
271 if [ -n "$smart" ]; then
272 # add a missing trailing .git
273 case "$proj" in
274 *.git) :;;
276 proj="$proj.git"
277 esac
279 projbare="${proj%.git}"
280 reporoot="$cfg_reporoot"
281 dir="$reporoot/$proj"
283 # Valid project names never end in .git (we add that automagically), so a valid
284 # fork can never have .git at the end of any path component except the last.
285 # We check this to avoid a situation where a certain collection of pushed refs
286 # could be mistaken for a GIT_DIR. Git would ultimately complain, but some
287 # undesirable things could happen along the way.
289 # Remove the leading $reporoot and trailing .git to get a test string
290 testpath="${dir#$reporoot/}"
291 testpath="${testpath%.git}"
292 case "$testpath/" in *.[Gg][Ii][Tt]/*|_*)
293 forbidden
294 exit 1
295 esac
297 if ! [ -d "$dir" ] || ! [ -f "$dir/HEAD" ] || ! [ -d "$dir/objects" ]; then
298 forbidden
299 exit 1
303 if [ -n "$bundle" ]; then
304 # We support two kinds of bundles:
305 # 1) /path/to/foo.git/clone.bundle
306 # 2) /path/to/foo.git/foo-????????.bundle
307 # The first ALWAYS returns a 302 or 404 response
308 # The second ALWAYS returns a 404 or success
309 isredir=
310 projbase="${projbare##*/}"
311 case "$suffix" in
312 "clone.bundle")
313 isredir=1
315 "$projbase-"$octet4".bundle")
318 forbidden
319 exit 1
320 esac
321 if [ -n "$isredir" ]; then
322 # A bundles/latest symlink must exist and
323 # point to an existing file in the same directory
324 # matching the magic format (\d{8}_\d{6}-$octet4)
325 if ! [ -L "$dir/bundles/latest" -a -f "$dir/bundles/latest" ]; then
326 notfound
327 exit 0
329 linked="$(readlink "$dir/bundles/latest")" || { notfound; exit 0; }
330 case "$linked" in ${digit8}_$digit6-$octet4) :;; *)
331 notfound
332 exit 0
333 esac
334 bundlefile="$dir/bundles/$linked"
335 linked="$projbase-${linked#????????_??????-}"
336 else
337 bundleid="${suffix%.bundle}"
338 bundleid="${bundleid##*-}"
339 bundlepat="${digit8}_$digit6-$bundleid"
340 bundlefile="$(echo "$dir/bundles/"$bundlepat 2>/dev/null || :)"
341 if [ "$dir/bundles/$bundlepat" = "$bundlefile" ] || ! [ -f "$bundlefile" ]; then
342 notfound
343 exit 0
347 read -r bundlehdr || :
348 read -r bundlepck || :
349 } <"$bundlefile"; } 2>/dev/null
350 [ -n "$bundlehdr" -a -n "$bundlepck" ] || { notfound; exit 0; }
351 # Non-absolute paths are relative to the repository's objects/pack dir
352 case "$bundlehdr" in /*) :;; *)
353 bundlehdr="$dir/objects/pack/$bundlehdr"
354 esac
355 case "$bundlepck" in /*) :;; *)
356 bundlepck="$dir/objects/pack/$bundlepck"
357 esac
358 [ -f "$bundlehdr" -a -f "$bundlepck" ] || { notfound; exit 0; }
359 [ -s "$bundlehdr" -o -s "$bundlepck" ] || { notfound; exit 0; }
360 [ -z "$isredir" ] || { redir "/$proj/$linked.bundle"; exit 0; }
361 exec "$cfg_basedir/bin/rangecgi" -c 'application/x-git-bundle' -e 180 \
362 -m 1 "$bundlehdr" "$bundlepck"
363 internalerr "exec failed: $cfg_basedir/bin/rangecgi"
364 exit 1
367 if [ -z "$needsauthcheck" ] || [ -z "$smart" ]; then
368 [ -z "$var_upload_window" ] || [ -z "$smart" ] || \
369 git_add_config "pack.window=$var_upload_window"
370 if [ -n "$GIT_HTTP_BACKEND_SHOW_ERRORS" ]; then
371 exec "$cfg_git_http_backend_bin" "$@"
372 else
373 exec "$cfg_git_http_backend_bin" "$@" 2>/dev/null
375 internalerr "exec failed: $cfg_git_http_backend_bin"
376 exit 1
379 if ! [ -f "$dir/.nofetch" ]; then
380 forbidden "The $proj project is a mirror and may not be pushed to, sorry"
381 exit 1
384 git_add_config 'receive.unpackLimit=1'
385 # Note the git config documentation is wrong
386 # transfer.unpackLimit, if set, overrides receive.unpackLimit
387 git_add_config 'transfer.unpackLimit=1'
389 if [ "${SSL_CLIENT_VERIFY+set}" = "set" ] && [ "$SSL_CLIENT_VERIFY" != "SUCCESS" ]; then
390 needsauth "Only validated client certificates may push, sorry."
391 exit 1
393 authuser="${REMOTE_USER#/UID=}"
394 authuuid="${authuser}"
395 authuser="${authuser%/dnQualifier=*}"
396 authuuid="${authuuid#$authuser}"
397 authuuid="${authuuid#/dnQualifier=}"
398 if [ -z "$authuser" ]; then
399 needsauth "Only authenticated users may push, sorry."
400 exit 1
402 if [ "$authuser" != "mob" -o "$cfg_mob" != "mob" ]; then
403 if ! useruuid="$("$cfg_basedir/bin/get_user_uuid" "$authuser")" || [ "$useruuid" != "$authuuid" ]; then
404 forbidden "The user '$authuser' certificate being used is no longer valid." \
405 "You may download a new user certificate at $cfg_webadmurl/edituser.cgi"
406 exit 1
410 if ! "$cfg_basedir/bin/can_user_push_http" "$projbare" "$authuser"; then
411 # If mob is enabled and mob has push permissions and
412 # the current user is not the mob then it's a personal mob push
413 # presuming the special mob directory has been set up
414 if [ "$cfg_mob" = "mob" -a "$authuser" != "mob" -a -d "$cfg_reporoot/$proj/mob" ] &&
415 "$cfg_basedir/bin/can_user_push_http" "$projbare" "mob"; then
417 umask 113
418 > "$cfg_chroot/etc/sshactive/${authuser},"
419 mv -f "$cfg_chroot/etc/sshactive/${authuser}," "$cfg_chroot/etc/sshactive/${authuser}"
420 ! [ -e "$dir/.delaygc" ] || > "$dir/.allowgc" || :
422 PATH_INFO="/$proj/mob/$suffix"
423 export PATH_INFO
424 if [ -n "$GIT_HTTP_BACKEND_SHOW_ERRORS" ]; then
425 exec "$cfg_git_http_backend_bin" "$@"
426 else
427 exec "$cfg_git_http_backend_bin" "$@" 2>/dev/null
429 internalerr "exec failed: $cfg_git_http_backend_bin"
430 exit 1
432 forbidden "The user '$authuser' does not have push permissions for project '$proj'." \
433 "You may adjust push permissions at $cfg_webadmurl/editproj.cgi?name=$proj"
434 exit 1
438 umask 113
439 > "$cfg_chroot/etc/sshactive/${authuser},"
440 mv -f "$cfg_chroot/etc/sshactive/${authuser}," "$cfg_chroot/etc/sshactive/${authuser}"
441 ! [ -e "$dir/.delaygc" ] || > "$dir/.allowgc" || :
443 if [ -n "$GIT_HTTP_BACKEND_SHOW_ERRORS" ]; then
444 exec "$cfg_git_http_backend_bin" "$@"
445 else
446 exec "$cfg_git_http_backend_bin" "$@" 2>/dev/null
448 internalerr "exec failed: $cfg_git_http_backend_bin"
449 exit 1