projtool.pl: do not attempt to check unset error codes
[girocco.git] / bin / git-http-backend-verify
blob67a5c0712648ef53fc234689daae64883e821ad4
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
30 if [ -n "$cfg_SmartHTTPOnly" ] && [ "$cfg_SmartHTTPOnly" != "0" ]; then
31 git_add_config "http.getanyfile=false"
34 [ -z "$GIT_HTTP_BACKEND_BIN" ] || cfg_git_http_backend_bin="$GIT_HTTP_BACKEND_BIN"
35 [ -n "$cfg_git_http_backend_bin" ] ||
36 cfg_git_http_backend_bin="$var_git_exec_path/git-http-backend"
38 GIT_PROJECT_ROOT="$cfg_reporoot"
39 GIT_HTTP_EXPORT_ALL=1
40 export GIT_PROJECT_ROOT
41 export GIT_HTTP_EXPORT_ALL
43 # This script is called for both fetch and push.
44 # Only the following conditions trigger a push permissions check:
46 # 1. REQUEST_METHOD=GET
47 # and PATH_INFO ends with "/info/refs"
48 # and QUERY_STRING has "service=git-receive-pack"
50 # 2. REQUEST_METHOD=POST
51 # and PATH_INFO ends with "/git-receive-pack"
53 # Note that there is no check for PATH_INFO being under a certain root as
54 # GIT_PROJECT_ROOT will be exported and set so all PATH_INFO values are
55 # effectively forced under the desired root.
57 # The REQUEST_METHOD is validated. For smart HTTP requests only the project
58 # name is extracted and validated and the corresponding project directory must
59 # exist under $cfg_reporoot. Non-smart HTTP fetch requests (GET or HEAD) are
60 # passed on unchanged and unchecked.
62 errorhdrsct()
64 _ct="$1"; shift
65 printf '%s\r\n' "Status: $1 $2"
66 printf '%s\r\n' "Expires: Fri, 01 Jan 1980 00:00:00 GMT"
67 printf '%s\r\n' "Pragma: no-cache"
68 printf '%s\r\n' "Cache-Control: no-cache,max-age=0,must-revalidate"
69 [ -z "$3" ] || printf '%s\r\n' "$3"
70 printf '%s\r\n' "Content-Type: $_ct"
71 printf '\r\n'
74 errorhdrs()
76 errorhdrsct 'text/plain; charset=utf-8; format=fixed' "$@"
79 msglines()
81 [ $# -le 0 ] || printf '%s\n' "$@"
84 internalerr()
86 errorhdrs 500 "Internal Server Error"
87 [ $# -gt 0 ] || set -- "Internal Server Error"
88 msglines "$@" >&2
89 msglines "$@"
90 exit 0
93 badrequest()
95 errorhdrs 400 "Bad Request"
96 [ $# -gt 0 ] || set -- "Bad Request"
97 msglines "$@"
98 exit 0
101 methodnotallowed()
103 errorhdrs 405 "Method Not Allowed" "Allow: GET,HEAD,POST"
104 [ $# -gt 0 ] || set -- "Method Not Allowed"
105 msglines "$@"
106 exit 0
109 forbidden()
111 errorhdrs 403 Forbidden
112 [ $# -gt 0 ] || set -- "Forbidden"
113 msglines "$@"
114 exit 0
117 notfound()
119 errorhdrs 404 "Not Found"
120 [ $# -gt 0 ] || set -- "Not Found"
121 msglines "$@"
122 exit 0
125 needsauth()
127 unset AUTHREQUIRED_MESSAGE
128 msg="$*"
129 if [ -n "$msg" ]; then
130 AUTHREQUIRED_MESSAGE="$msg"
131 export AUTHREQUIRED_MESSAGE
133 exec "$cfg_cgiroot/authrequired.cgi" || :
134 # fallback in case exec fails
135 errorhdrs 401 "Authorization Required"
136 [ $# -gt 0 ] || set -- "Authorization Required"
137 msglines "$@"
138 exit 0
141 # Single argument is an absolute PATH (NOT a URI) to 302 redirect to
142 # The appropriate http pull URL path prefix is automatically inserted
143 redir()
145 _pullurl="$cfg_httpbundleurl"
146 [ -n "$_pullurl" ] || _pullurl="$cfg_httpspushurl"
147 [ -n "$_pullurl" ] || _pullurl="$cfg_httppullurl"
148 _absbase="${_pullurl%/}/"
149 _absbase="${absbase##*://}"
150 _absbase="${absbase##*/}"
151 [ -z "$_absbase" ] || _absbase="/$_absbase"
152 _loc="https"
153 [ "$HTTPS" = "on" ] || _loc="http"
154 _loc="$_loc://$SERVER_NAME"
155 [ "$HTTPS" != "on" ] || [ "$SERVER_PORT" = "443" ] || _loc="$_loc:$SERVER_PORT"
156 [ "$HTTPS" = "on" ] || [ "$SERVER_PORT" = "80" ] || _loc="$_loc:$SERVER_PORT"
157 _loc="$_loc$_absbase"
158 case "$1" in /*) :;; *) _loc="$_loc/";; esac
159 _loc="$_loc$1"
160 errorhdrsct 'text/html; charset=utf-8' 302 "Found" "Location: $_loc"
161 if [ "$REQUEST_METHOD" != "HEAD" ]; then
162 printf '<p>Temporarily redirected to <a href="%s">%s</a></p>\n' \
163 "$_loc" "$_loc"
165 exit 0
168 # A quick sanity check
169 if [ -z "$cfg_git_http_backend_bin" ] || ! [ -x "$cfg_git_http_backend_bin" ]; then
170 internalerr "bad cfg_git_http_backend_bin: $cfg_git_http_backend_bin"
171 exit 1
173 case "$cfg_reporoot" in /?*) :;; *)
174 internalerr "bad reporoot: $cfg_reporoot"
175 exit 1
176 esac
177 [ -n "$GIT_PROJECT_ROOT" ] || { internalerr 'GIT_PROJECT_ROOT must be set'; exit 1; }
179 PATH="$(dirname "$cfg_git_http_backend_bin"):$PATH"
180 export PATH
182 digit='[0-9]'
183 digit6="$digit$digit$digit$digit$digit$digit"
184 digit8="$digit6$digit$digit"
185 proj=
186 smart=
187 bundle=
188 suffix=
189 needsauthcheck=
190 pathcheck="${PATH_INFO#/}"
191 if [ "$REQUEST_METHOD" = "GET" ] || [ "$REQUEST_METHOD" = "HEAD" ]; then
192 # We do not currently validate non-smart GET/HEAD requests.
193 # There are only 8 possible suffix values that need to be allowed for
194 # non-smart HTTP GET/HEAD fetches (see http-backend.c):
195 # /HEAD
196 # /info/refs
197 # /objects/info/alternates
198 # /objects/info/http-alternates
199 # /objects/info/packs
200 # /objects/[0-9a-f]{2}/[0-9a-f]{38}
201 # /objects/pack/pack-[0-9a-f]{40}.idx
202 # /objects/pack/pack-[0-9a-f]{40}.pack
203 # We do, however, need to recognize a /*.bundle fetch so that
204 # we can properly handle it.
205 case "$pathcheck" in
206 *"/info/refs")
207 proj="${pathcheck%/info/refs}"
208 case "&$QUERY_STRING&" in
209 *"%"*)
210 # we reject any requests with url-encoded characters
211 # in the QUERY_STRING as a precaution since they should
212 # never be required for either supported request type
213 badrequest "invalid query string"
214 exit 1
216 *"&service=git-receive-pack&"*)
217 smart=1
218 needsauthcheck=1
219 suffix=info/refs
221 *"&service=git-upload-pack&"*)
222 smart=1
223 suffix=info/refs
225 esac
227 */*[!./].bundle)
228 bundle=1
229 smart=1
230 proj="${pathcheck%/*.bundle}"
231 suffix="${pathcheck#$proj/}"
233 esac
234 elif [ "$REQUEST_METHOD" = "POST" ]; then
235 case "$pathcheck" in
236 *"/git-receive-pack")
237 smart=1
238 needsauthcheck=1
239 proj="${pathcheck%/git-receive-pack}"
240 suffix=git-receive-pack
242 *"/git-upload-pack")
243 smart=1
244 proj="${pathcheck%/git-upload-pack}"
245 suffix=git-upload-pack
248 forbidden
249 exit 1
251 esac
252 else
253 methodnotallowed
254 exit 1
257 # Reject any project names that start with _ or contain ..
258 case "$pathcheck" in _*|*..*)
259 forbidden
260 esac
262 if [ -n "$smart" ]; then
263 # add a missing trailing .git
264 case "$proj" in
265 *.git) :;;
267 proj="$proj.git"
268 esac
270 projbare="${proj%.git}"
271 reporoot="$cfg_reporoot"
272 dir="$reporoot/$proj"
274 # Valid project names never end in .git (we add that automagically), so a valid
275 # fork can never have .git at the end of any path component except the last.
276 # We check this to avoid a situation where a certain collection of pushed refs
277 # could be mistaken for a GIT_DIR. Git would ultimately complain, but some
278 # undesirable things could happen along the way.
280 # Remove the leading $reporoot and trailing .git to get a test string
281 testpath="${dir#$reporoot/}"
282 testpath="${testpath%.git}"
283 case "$testpath/" in *.[Gg][Ii][Tt]/*|_*)
284 forbidden
285 exit 1
286 esac
288 if ! [ -d "$dir" ] || ! [ -f "$dir/HEAD" ] || ! [ -d "$dir/objects" ]; then
289 forbidden
290 exit 1
294 if [ -n "$bundle" ]; then
295 # We support two kinds of bundles:
296 # 1) /path/to/foo.git/clone.bundle
297 # 2) /path/to/foo.git/foo-????????.bundle
298 # The first ALWAYS returns a 302 or 404 response
299 # The second ALWAYS returns a 404 or success
300 isredir=
301 projbase="${projbare##*/}"
302 case "$suffix" in
303 "clone.bundle")
304 isredir=1
306 "$projbase-"$octet4".bundle")
309 forbidden
310 exit 1
311 esac
312 if [ -n "$isredir" ]; then
313 # A bundles/latest symlink must exist and
314 # point to an existing file in the same directory
315 # matching the magic format (\d{8}_\d{6}-$octet4)
316 if ! [ -L "$dir/bundles/latest" ] || ! [ -f "$dir/bundles/latest" ]; then
317 notfound
318 exit 0
320 linked="$(readlink "$dir/bundles/latest")" || { notfound; exit 0; }
321 case "$linked" in ${digit8}_$digit6-$octet4) :;; *)
322 notfound
323 exit 0
324 esac
325 bundlefile="$dir/bundles/$linked"
326 linked="$projbase-${linked#????????_??????-}"
327 else
328 bundleid="${suffix%.bundle}"
329 bundleid="${bundleid##*-}"
330 bundlepat="${digit8}_$digit6-$bundleid"
331 bundlefile="$(printf '%s\n' "$dir/bundles/"$bundlepat 2>/dev/null)" || :
332 if [ "$dir/bundles/$bundlepat" = "$bundlefile" ] || ! [ -f "$bundlefile" ]; then
333 notfound
334 exit 0
338 read -r bundlehdr || :
339 read -r bundlepck || :
340 } <"$bundlefile"; } 2>/dev/null || :
341 [ -n "$bundlehdr" ] && [ -n "$bundlepck" ] || { notfound; exit 0; }
342 # Non-absolute paths are relative to the repository's objects/pack dir
343 case "$bundlehdr" in /*) :;; *)
344 bundlehdr="$dir/objects/pack/$bundlehdr"
345 esac
346 case "$bundlepck" in /*) :;; *)
347 bundlepck="$dir/objects/pack/$bundlepck"
348 esac
349 [ -f "$bundlehdr" ] && [ -f "$bundlepck" ] || { notfound; exit 0; }
350 [ -s "$bundlehdr" ] || [ -s "$bundlepck" ] || { notfound; exit 0; }
351 [ -z "$isredir" ] || { redir "/$proj/$linked.bundle"; exit 0; }
352 exec "$cfg_basedir/bin/rangecgi" -c 'application/x-git-bundle' \
353 -f "$suffix" -e 180 -m 1 "$bundlehdr" "$bundlepck"
354 internalerr "exec failed: $cfg_basedir/bin/rangecgi"
355 exit 1
358 if [ "${cfg_fetch_stash_refs:-0}" = "0" ] && [ -n "$var_have_git_235" ]; then
359 git_add_config "uploadpack.hiderefs=refs/stash"
360 git_add_config "uploadpack.hiderefs=refs/tgstash"
363 if [ -z "$needsauthcheck" ] || [ -z "$smart" ]; then
364 [ -z "$var_upload_window" ] || [ -z "$smart" ] ||
365 git_add_config "pack.window=$var_upload_window"
366 if [ -n "$GIT_HTTP_BACKEND_SHOW_ERRORS" ]; then
367 exec "$cfg_git_http_backend_bin" "$@"
368 else
369 exec "$cfg_git_http_backend_bin" "$@" 2>/dev/null
371 internalerr "exec failed: $cfg_git_http_backend_bin"
372 exit 1
375 if ! [ -f "$dir/.nofetch" ]; then
376 forbidden "The $proj project is a mirror and may not be pushed to, sorry"
377 exit 1
380 if [ -f "$cfg_chroot/etc/readonly" ]; then
381 msgro="$cfg_name is currently read-only, please try again later."
382 msgro2=""
383 if [ -s "$cfg_chroot/etc/readonly" ]; then
384 msgro2="$(cat "$cfg_chroot/etc/readonly" 2>/dev/null)" || :
386 if [ -n "$msgro2" ]; then
387 forbidden "$msgro" "$msgro2"
388 else
389 forbidden "$msgro"
393 # Set up the correct backend command depending on cfg_max_file_size512
394 if [ "${cfg_max_file_size512:-0}" = "0" ]; then
395 GIT_HTTP_BACKEND='"$cfg_git_http_backend_bin"'
396 else
397 GIT_HTTP_BACKEND='"$cfg_basedir/bin/ulimit512" -i -f "$cfg_max_file_size512" -- "$cfg_git_http_backend_bin"'
400 git_add_config 'receive.unpackLimit=1'
401 # Note the git config documentation is wrong
402 # transfer.unpackLimit, if set, overrides receive.unpackLimit
403 git_add_config 'transfer.unpackLimit=1'
405 # The sample apache.conf file does not export SSL_CLIENT_VERIFY, but it does
406 # verify that a valid client certificate was provided and SSL_CLIENT_VERIFY = SUCCESS
407 # for any git https push requests. (The sample apache.conf file can be modified
408 # to export SSL_CLIENT_VERIFY by adding "SSLOptions +StdEnvVars" inside the section
409 # that matches git-http-backend-verify requests for a tiny efficiency penalty.)
411 # The REMOTE_USER variable is not exported unless user credentials were provided or
412 # they were set from a valid client certificate (courtesy of +FakeBasicAuth).
414 # If SSL_CLIENT_VERIFY has been exported to this script, then it's required
415 # to have the value SUCCESS or the push will be aborted. No exceptions.
417 # Nevertheless, exporting the environment variable REQUIRE_SSL_CLIENT_VERIFY_SUCCESS
418 # (to any value) will force this script to ALWAYS require SSL_CLIENT_VERIFY = SUCCESS for
419 # pushes whether or not SSL_CLIENT_VERIFY has been exported. As mentioned above, the
420 # default apache.conf does NOT export SSL_CLIENT_VERIFY because it checks it directly
421 # before running this script. Do not set REQUIRE_SSL_CLIENT_VERIFY_SUCCESS when using
422 # the default apache.conf file unless "SSLOptions +StdEnvVars" has been added to it
423 # (it needs to be added to lines that already have the "+FakeBasicAuth" option on them).
425 [ "${SSL_CLIENT_VERIFY+set}" = "set" ] || [ "${REQUIRE_SSL_CLIENT_VERIFY_SUCCESS+set}" = "set" ] &&
426 [ "$SSL_CLIENT_VERIFY" != "SUCCESS" ]
427 then
428 if [ "${SSL_CLIENT_VERIFY+set}" = "set" ]; then
429 needsauth "Only validated client certificates may push, sorry."
430 else
431 needsauth "A client certificate is required to push, sorry."
433 exit 1
435 authuser="${REMOTE_USER#/UID=}"
436 authuser="${authuser#UID = }"
437 authuuid="$authuser"
438 authuser="${authuser%/dnQualifier=*}"
439 authuser="${authuser%, dnQualifier = *}"
440 authuuid="${authuuid#$authuser}"
441 authuuid="${authuuid#/dnQualifier=}"
442 authuuid="${authuuid#, dnQualifier = }"
443 if [ -z "$authuser" ]; then
444 needsauth "Only authenticated users may push, sorry."
445 exit 1
447 if [ "$authuser" != "mob" ] || [ "$cfg_mob" != "mob" ]; then
448 if ! useruuid="$("$cfg_basedir/bin/get_user_uuid" "$authuser")" || [ "$useruuid" != "$authuuid" ]; then
449 forbidden "The user '$authuser' certificate being used is no longer valid." \
450 "You may download a new user certificate at $cfg_webadmurl/edituser.cgi"
451 exit 1
455 if ! "$cfg_basedir/bin/can_user_push_http" "$projbare" "$authuser"; then
456 # If mob is enabled and mob has push permissions and
457 # the current user is not the mob then it's a personal mob push
458 # presuming the special mob directory has been set up
459 if [ "$cfg_mob" = "mob" ] && [ "$authuser" != "mob" ] && [ -d "$cfg_reporoot/$proj/mob" ] &&
460 "$cfg_basedir/bin/can_user_push_http" "$projbare" "mob"; then
462 umask 113
463 >"$cfg_chroot/etc/sshactive/$authuser,"
464 mv -f "$cfg_chroot/etc/sshactive/$authuser," "$cfg_chroot/etc/sshactive/$authuser"
465 ! [ -e "$dir/.delaygc" ] || >"$dir/.allowgc" || :
467 PATH_INFO="/$proj/mob/$suffix"
468 export PATH_INFO
469 if [ -n "$GIT_HTTP_BACKEND_SHOW_ERRORS" ]; then
470 eval 'exec '"$GIT_HTTP_BACKEND"' "$@"'
471 else
472 eval 'exec '"$GIT_HTTP_BACKEND"' "$@" 2>/dev/null'
474 internalerr "exec failed: $cfg_git_http_backend_bin"
475 exit 1
477 forbidden "The user '$authuser' does not have push permissions for project '$proj'." \
478 "You may adjust push permissions at $cfg_webadmurl/editproj.cgi?name=$proj"
479 exit 1
483 umask 113
484 >"$cfg_chroot/etc/sshactive/$authuser,"
485 mv -f "$cfg_chroot/etc/sshactive/$authuser," "$cfg_chroot/etc/sshactive/$authuser"
486 ! [ -e "$dir/.delaygc" ] || >"$dir/.allowgc" || :
488 if [ -n "$GIT_HTTP_BACKEND_SHOW_ERRORS" ]; then
489 eval 'exec '"$GIT_HTTP_BACKEND"' "$@"'
490 else
491 eval 'exec '"$GIT_HTTP_BACKEND"' "$@" 2>/dev/null'
493 internalerr "exec failed: $cfg_git_http_backend_bin"
494 exit 1