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.
25 unset GIT_HTTP_USER_AGENT
26 if [ -n "$defined_cfg_git_server_ua" ]; then
27 GIT_USER_AGENT
="$cfg_git_server_ua"
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"
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.
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"
76 errorhdrsct
'text/plain; charset=utf-8; format=fixed' "$@"
81 [ $# -le 0 ] ||
printf '%s\n' "$@"
86 errorhdrs
500 "Internal Server Error"
87 [ $# -gt 0 ] ||
set -- "Internal Server Error"
95 errorhdrs
405 "Method Not Allowed" "Allow: GET,HEAD,POST"
96 [ $# -gt 0 ] ||
set -- "Method Not Allowed"
103 errorhdrs
403 Forbidden
104 [ $# -gt 0 ] ||
set -- "Forbidden"
111 errorhdrs
404 "Not Found"
112 [ $# -gt 0 ] ||
set -- "Not Found"
119 unset AUTHREQUIRED_MESSAGE
121 if [ -n "$msg" ]; then
122 AUTHREQUIRED_MESSAGE
="$msg"
123 export AUTHREQUIRED_MESSAGE
125 exec "$cfg_cgiroot/authrequired.cgi" ||
:
126 # fallback in case exec fails
127 errorhdrs
401 "Authorization Required"
128 [ $# -gt 0 ] ||
set -- "Authorization Required"
133 # Single argument is an absolute PATH (NOT a URI) to 302 redirect to
134 # The appropriate http pull URL path prefix is automatically inserted
137 _pullurl
="$cfg_httpbundleurl"
138 [ -n "$_pullurl" ] || _pullurl
="$cfg_httpspushurl"
139 [ -n "$_pullurl" ] || _pullurl
="$cfg_httppullurl"
140 _absbase
="${_pullurl%/}/"
141 _absbase
="${absbase##*://}"
142 _absbase
="${absbase##*/}"
143 [ -z "$_absbase" ] || _absbase
="/$_absbase"
145 [ "$HTTPS" = "on" ] || _loc
="http"
146 _loc
="$_loc://$SERVER_NAME"
147 [ "$HTTPS" != "on" ] ||
[ "$SERVER_PORT" = "443" ] || _loc
="$_loc:$SERVER_PORT"
148 [ "$HTTPS" = "on" ] ||
[ "$SERVER_PORT" = "80" ] || _loc
="$_loc:$SERVER_PORT"
149 _loc
="$_loc$_absbase"
150 case "$1" in /*) :;; *) _loc
="$_loc/";; esac
152 errorhdrsct
'text/html; charset=utf-8' 302 "Found" "Location: $_loc"
153 if [ "$REQUEST_METHOD" != "HEAD" ]; then
154 printf '<p>Temporarily redirected to <a href="%s">%s</a></p>\n' \
160 # A quick sanity check
161 if [ -z "$cfg_git_http_backend_bin" ] ||
! [ -x "$cfg_git_http_backend_bin" ]; then
162 internalerr
"bad cfg_git_http_backend_bin: $cfg_git_http_backend_bin"
165 case "$cfg_reporoot" in /?
*) :;; *)
166 internalerr
"bad reporoot: $cfg_reporoot"
169 [ -n "$GIT_PROJECT_ROOT" ] ||
{ internalerr
'GIT_PROJECT_ROOT must be set'; exit 1; }
171 PATH
="$(dirname "$cfg_git_http_backend_bin"):$PATH"
175 digit6
="$digit$digit$digit$digit$digit$digit"
176 digit8
="$digit6$digit$digit"
182 pathcheck
="${PATH_INFO#/}"
183 if [ "$REQUEST_METHOD" = "GET" ] ||
[ "$REQUEST_METHOD" = "HEAD" ]; then
184 # We do not currently validate non-smart GET/HEAD requests.
185 # There are only 8 possible suffix values that need to be allowed for
186 # non-smart HTTP GET/HEAD fetches (see http-backend.c):
189 # /objects/info/alternates
190 # /objects/info/http-alternates
191 # /objects/info/packs
192 # /objects/[0-9a-f]{2}/[0-9a-f]{38}
193 # /objects/pack/pack-[0-9a-f]{40}.idx
194 # /objects/pack/pack-[0-9a-f]{40}.pack
195 # We do, however, need to recognize a /*.bundle fetch so that
196 # we can properly handle it.
199 proj
="${pathcheck%/info/refs}"
200 case "&$QUERY_STRING&" in
201 *"&service=git-receive-pack&"*)
206 *"&service=git-upload-pack&"*)
215 proj
="${pathcheck%/*.bundle}"
216 suffix
="${pathcheck#$proj/}"
219 elif [ "$REQUEST_METHOD" = "POST" ]; then
221 *"/git-receive-pack")
224 proj
="${pathcheck%/git-receive-pack}"
225 suffix
=git-receive-pack
229 proj
="${pathcheck%/git-upload-pack}"
230 suffix
=git-upload-pack
242 # Reject any project names that start with _ or contain ..
243 case "$pathcheck" in _
*|
*..
*)
247 if [ -n "$smart" ]; then
248 # add a missing trailing .git
255 projbare
="${proj%.git}"
256 reporoot
="$cfg_reporoot"
257 dir
="$reporoot/$proj"
259 # Valid project names never end in .git (we add that automagically), so a valid
260 # fork can never have .git at the end of any path component except the last.
261 # We check this to avoid a situation where a certain collection of pushed refs
262 # could be mistaken for a GIT_DIR. Git would ultimately complain, but some
263 # undesirable things could happen along the way.
265 # Remove the leading $reporoot and trailing .git to get a test string
266 testpath
="${dir#$reporoot/}"
267 testpath
="${testpath%.git}"
268 case "$testpath/" in *.
[Gg
][Ii
][Tt
]/*|_
*)
273 if ! [ -d "$dir" ] ||
! [ -f "$dir/HEAD" ] ||
! [ -d "$dir/objects" ]; then
279 if [ -n "$bundle" ]; then
280 # We support two kinds of bundles:
281 # 1) /path/to/foo.git/clone.bundle
282 # 2) /path/to/foo.git/foo-????????.bundle
283 # The first ALWAYS returns a 302 or 404 response
284 # The second ALWAYS returns a 404 or success
286 projbase
="${projbare##*/}"
291 "$projbase-"$octet4".bundle")
297 if [ -n "$isredir" ]; then
298 # A bundles/latest symlink must exist and
299 # point to an existing file in the same directory
300 # matching the magic format (\d{8}_\d{6}-$octet4)
301 if ! [ -L "$dir/bundles/latest" ] ||
! [ -f "$dir/bundles/latest" ]; then
305 linked
="$(readlink "$dir/bundles
/latest
")" ||
{ notfound
; exit 0; }
306 case "$linked" in ${digit8}_
$digit6-$octet4) :;; *)
310 bundlefile
="$dir/bundles/$linked"
311 linked
="$projbase-${linked#????????_??????-}"
313 bundleid
="${suffix%.bundle}"
314 bundleid
="${bundleid##*-}"
315 bundlepat
="${digit8}_$digit6-$bundleid"
316 bundlefile
="$(printf '%s\n' "$dir/bundles
/"$bundlepat 2>/dev/null)" ||
:
317 if [ "$dir/bundles/$bundlepat" = "$bundlefile" ] ||
! [ -f "$bundlefile" ]; then
323 read -r bundlehdr ||
:
324 read -r bundlepck ||
:
325 } <"$bundlefile"; } 2>/dev
/null ||
:
326 [ -n "$bundlehdr" ] && [ -n "$bundlepck" ] ||
{ notfound
; exit 0; }
327 # Non-absolute paths are relative to the repository's objects/pack dir
328 case "$bundlehdr" in /*) :;; *)
329 bundlehdr
="$dir/objects/pack/$bundlehdr"
331 case "$bundlepck" in /*) :;; *)
332 bundlepck
="$dir/objects/pack/$bundlepck"
334 [ -f "$bundlehdr" ] && [ -f "$bundlepck" ] ||
{ notfound
; exit 0; }
335 [ -s "$bundlehdr" ] ||
[ -s "$bundlepck" ] ||
{ notfound
; exit 0; }
336 [ -z "$isredir" ] ||
{ redir
"/$proj/$linked.bundle"; exit 0; }
337 exec "$cfg_basedir/bin/rangecgi" -c 'application/x-git-bundle' \
338 -f "$suffix" -e 180 -m 1 "$bundlehdr" "$bundlepck"
339 internalerr
"exec failed: $cfg_basedir/bin/rangecgi"
343 if [ "${cfg_fetch_stash_refs:-0}" = "0" ] && [ -n "$var_have_git_235" ]; then
344 git_add_config
"uploadpack.hiderefs=refs/stash"
345 git_add_config
"uploadpack.hiderefs=refs/tgstash"
348 if [ -z "$needsauthcheck" ] ||
[ -z "$smart" ]; then
349 [ -z "$var_upload_window" ] ||
[ -z "$smart" ] ||
350 git_add_config
"pack.window=$var_upload_window"
351 if [ -n "$GIT_HTTP_BACKEND_SHOW_ERRORS" ]; then
352 exec "$cfg_git_http_backend_bin" "$@"
354 exec "$cfg_git_http_backend_bin" "$@" 2>/dev
/null
356 internalerr
"exec failed: $cfg_git_http_backend_bin"
360 if ! [ -f "$dir/.nofetch" ]; then
361 forbidden
"The $proj project is a mirror and may not be pushed to, sorry"
365 git_add_config
'receive.unpackLimit=1'
366 # Note the git config documentation is wrong
367 # transfer.unpackLimit, if set, overrides receive.unpackLimit
368 git_add_config
'transfer.unpackLimit=1'
370 if [ "${SSL_CLIENT_VERIFY+set}" = "set" ] && [ "$SSL_CLIENT_VERIFY" != "SUCCESS" ]; then
371 needsauth
"Only validated client certificates may push, sorry."
374 authuser
="${REMOTE_USER#/UID=}"
375 authuser
="${authuser#UID = }"
377 authuser
="${authuser%/dnQualifier=*}"
378 authuser
="${authuser%, dnQualifier = *}"
379 authuuid
="${authuuid#$authuser}"
380 authuuid
="${authuuid#/dnQualifier=}"
381 authuuid
="${authuuid#, dnQualifier = }"
382 if [ -z "$authuser" ]; then
383 needsauth
"Only authenticated users may push, sorry."
386 if [ "$authuser" != "mob" ] ||
[ "$cfg_mob" != "mob" ]; then
387 if ! useruuid
="$("$cfg_basedir/bin
/get_user_uuid
" "$authuser")" ||
[ "$useruuid" != "$authuuid" ]; then
388 forbidden
"The user '$authuser' certificate being used is no longer valid." \
389 "You may download a new user certificate at $cfg_webadmurl/edituser.cgi"
394 if ! "$cfg_basedir/bin/can_user_push_http" "$projbare" "$authuser"; then
395 # If mob is enabled and mob has push permissions and
396 # the current user is not the mob then it's a personal mob push
397 # presuming the special mob directory has been set up
398 if [ "$cfg_mob" = "mob" ] && [ "$authuser" != "mob" ] && [ -d "$cfg_reporoot/$proj/mob" ] &&
399 "$cfg_basedir/bin/can_user_push_http" "$projbare" "mob"; then
402 >"$cfg_chroot/etc/sshactive/$authuser,"
403 mv -f "$cfg_chroot/etc/sshactive/$authuser," "$cfg_chroot/etc/sshactive/$authuser"
404 ! [ -e "$dir/.delaygc" ] ||
>"$dir/.allowgc" ||
:
406 PATH_INFO
="/$proj/mob/$suffix"
408 if [ -n "$GIT_HTTP_BACKEND_SHOW_ERRORS" ]; then
409 exec "$cfg_git_http_backend_bin" "$@"
411 exec "$cfg_git_http_backend_bin" "$@" 2>/dev
/null
413 internalerr
"exec failed: $cfg_git_http_backend_bin"
416 forbidden
"The user '$authuser' does not have push permissions for project '$proj'." \
417 "You may adjust push permissions at $cfg_webadmurl/editproj.cgi?name=$proj"
423 >"$cfg_chroot/etc/sshactive/$authuser,"
424 mv -f "$cfg_chroot/etc/sshactive/$authuser," "$cfg_chroot/etc/sshactive/$authuser"
425 ! [ -e "$dir/.delaygc" ] ||
>"$dir/.allowgc" ||
:
427 if [ -n "$GIT_HTTP_BACKEND_SHOW_ERRORS" ]; then
428 exec "$cfg_git_http_backend_bin" "$@"
430 exec "$cfg_git_http_backend_bin" "$@" 2>/dev
/null
432 internalerr
"exec failed: $cfg_git_http_backend_bin"