git/http transports: do more project name validation
[girocco.git] / bin / git-http-backend-verify
blob8577d9bbc93f258fefccd1dd62130b762caa9bb0
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 must be set to use this script.
12 # Also prevents standard error output from git-http-backend cluttering up the
13 # server's log unless GIT_HTTP_BACKEND_SHOW_ERRORS is set to a non-empty value.
15 set -e
17 . @basedir@/shlib.sh
19 [ -z "$GIT_HTTP_BACKEND_BIN" ] || cfg_git_http_backend_bin="$GIT_HTTP_BACKEND_BIN"
20 [ -n "$cfg_git_http_backend_bin" ] ||
21 cfg_git_http_backend_bin=/usr/lib/git-core/git-http-backend
23 # This script is called for both fetch and push.
24 # Only the following conditions trigger a push permissions check:
26 # 1. REQUEST_METHOD=GET
27 # and PATH_INFO ends with "/info/refs"
28 # and QUERY_STRING has "service=git-receive-pack"
30 # 2. REQUEST_METHOD=POST
31 # and PATH_INFO ends with "/git-receive-pack"
33 # Note that there is no check for PATH_INFO being under a certain root as
34 # it's presumed that GIT_PROJECT_ROOT has been set and so all PATH_INFO
35 # values are effectively forced under the desired root.
37 # The REQUEST_METHOD is validated. For smart HTTP requests only the project
38 # name is extracted and validated and the corresponding project directory must
39 # exist under $cfg_reporoot. Non-smart HTTP fetch requests (GET or HEAD) are
40 # passed on unchanged and unchecked.
42 errorhdrs()
44 printf '%s\r\n' "Status: $1 $2"
45 printf '%s\r\n' "Expires: Fri, 01 Jan 1980 00:00:00 GMT"
46 printf '%s\r\n' "Pragma: no-cache"
47 printf '%s\r\n' "Cache-Control: no-cache, max-age=0, must-revalidate"
48 [ -z "$3" ] || printf '%s\r\n' "$3"
49 printf '%s\r\n' "Content-Type: text/plain"
50 printf '\r\n'
53 msglines()
55 while [ $# -gt 0 ]; do
56 printf '%s\n' "$1"
57 shift
58 done
61 internalerr()
63 errorhdrs 500 "Internal Server Error"
64 if [ $# -eq 0 ]; then
65 msglines "Internal Server Error"
66 echo "Internal Server Error" >&2
67 else
68 msglines "$@"
69 while [ $# -gt 0 ]; do
70 printf '%s\n' "$1" >&2
71 shift
72 done
74 exit 0
77 methodnotallowed()
79 errorhdrs 405 "Method Not Allowed" "Allow: GET, HEAD, POST"
80 if [ $# -eq 0 ]; then
81 msglines "Method Not Allowed"
82 else
83 msglines "$@"
85 exit 0
88 forbidden()
90 errorhdrs 403 Forbidden
91 if [ $# -eq 0 ]; then
92 msglines "Forbidden"
93 else
94 msglines "$@"
96 exit 0
99 needsauth()
101 errorhdrs 401 "Authorization Required"
102 if [ $# -eq 0 ]; then
103 msglines "Authorization Required"
104 else
105 msglines "$@"
107 exit 0
110 [ -n "$GIT_PROJECT_ROOT" ] || { internalerr 'GIT_PROJECT_ROOT must be set'; exit 1; }
112 proj=
113 smart=
114 suffix=
115 needsauthcheck=
116 pathcheck="${PATH_INFO#/}"
117 if [ "$REQUEST_METHOD" = "GET" -o "$REQUEST_METHOD" = "HEAD" ]; then
118 # We do not currently validate non-smart GET/HEAD requests.
119 # There are only 8 possible suffix values that need to be allowed for
120 # non-smart HTTP GET/HEAD fetches (see http-backend.c):
121 # /HEAD
122 # /info/refs
123 # /objects/info/alternates
124 # /objects/info/http-alternates
125 # /objects/info/packs
126 # /objects/[0-9a-f]{2}/[0-9a-f]{38}
127 # /objects/pack/pack-[0-9a-f]{40}.idx
128 # /objects/pack/pack-[0-9a-f]{40}.pack
129 case "$pathcheck" in *"/info/refs")
130 case "&$QUERY_STRING&" in
131 *"&service=git-receive-pack&"*)
132 smart=1
133 needsauthcheck=1
134 proj="${pathcheck%/info/refs}"
135 suffix=info/refs
137 *"&service=git-upload-pack&"*)
138 smart=1
139 proj="${pathcheck%/info/refs}"
140 suffix=info/refs
142 esac
143 esac
144 elif [ "$REQUEST_METHOD" = "POST" ]; then
145 case "$pathcheck" in
146 *"/git-receive-pack")
147 smart=1
148 needsauthcheck=1
149 proj="${pathcheck%/git-receive-pack}"
150 suffix=git-receive-pack
152 *"/git-upload-pack")
153 smart=1
154 proj="${pathcheck%/git-upload-pack}"
155 suffix=git-upload-pack
158 forbidden
159 exit 1
161 esac
162 else
163 methodnotallowed
164 exit 1
167 if [ -n "$smart" ]; then
168 # add a missing trailing .git
169 case "$proj" in
170 *.git) :;;
172 proj="$proj.git"
173 esac
175 reporoot="$cfg_reporoot"
176 dir="$reporoot/$proj"
178 # Valid project names never end in .git (we add that automagically), so a valid
179 # fork can never have .git at the end of any path component except the last.
180 # We check this to avoid a situation where a certain collection of pushed refs
181 # could be mistaken for a GIT_DIR. Git would ultimately complain, but some
182 # undesirable things could happen along the way.
184 # Remove the leading $reporoot and trailing .git to get a test string
185 testpath="${dir#$reporoot/}"
186 testpath="${testpath%.git}"
187 case "$testpath/" in *.[Gg][Ii][Tt]/*)
188 forbidden
189 exit 1
190 esac
192 if ! [ -d "$dir" ] || ! [ -f "$dir/HEAD" ] || ! [ -d "$dir/objects" ]; then
193 forbidden
194 exit 1
198 if [ -z "$needsauthcheck" ] || [ -z "$smart" ]; then
199 if [ -n "$GIT_HTTP_BACKEND_SHOW_ERRORS" ]; then
200 exec "$cfg_git_http_backend_bin" "$@"
201 else
202 exec "$cfg_git_http_backend_bin" "$@" 2>/dev/null
204 internalerr "exec failed: $cfg_git_http_backend_bin"
205 exit 1
208 projbare="${proj%.git}"
210 if ! [ -f "$dir/.nofetch" ]; then
211 forbidden "The $proj project is a mirror and may not be pushed to, sorry"
212 exit 1
215 authuser="${REMOTE_USER#/UID=}"
216 authuuid="${authuser}"
217 authuser="${authuser%/dnQualifier=*}"
218 authuuid="${authuuid#$authuser}"
219 authuuid="${authuuid#/dnQualifier=}"
220 if [ -z "$authuser" ]; then
221 needsauth "Only authenticated users may push, sorry"
222 exit 1
224 if [ "$authuser" != "mob" -o "$cfg_mob" != "mob" ]; then
225 if ! useruuid="$("$cfg_basedir/bin/get_user_uuid" "$authuser")" || [ "$useruuid" != "$authuuid" ]; then
226 forbidden "The user '$authuser' certificate being used is no longer valid." \
227 "You may download a new user certificate at $cfg_webadmurl/edituser.cgi"
228 exit 1
232 if ! "$cfg_basedir/bin/can_user_push_http" "$projbare" "$authuser"; then
233 # If mob is enabled and mob has push permissions and
234 # the current user is not the mob then it's a personal mob push
235 # presuming the special mob directory has been set up
236 if [ "$cfg_mob" = "mob" -a "$authuser" != "mob" -a -d "$cfg_reporoot/$proj/mob" ] &&
237 "$cfg_basedir/bin/can_user_push_http" "$projbare" "mob"; then
238 export PATH_INFO="/$proj/mob/$suffix"
239 if [ -n "$GIT_HTTP_BACKEND_SHOW_ERRORS" ]; then
240 exec "$cfg_git_http_backend_bin" "$@"
241 else
242 exec "$cfg_git_http_backend_bin" "$@" 2>/dev/null
244 internalerr "exec failed: $cfg_git_http_backend_bin"
245 exit 1
247 forbidden "The user '$authuser' does not have push permissions for project '$proj'." \
248 "You may adjust push permissions at $cfg_webadmurl/editproj.cgi?name=$proj"
249 exit 1
252 if [ -n "$GIT_HTTP_BACKEND_SHOW_ERRORS" ]; then
253 exec "$cfg_git_http_backend_bin" "$@"
254 else
255 exec "$cfg_git_http_backend_bin" "$@" 2>/dev/null
257 internalerr "exec failed: $cfg_git_http_backend_bin"
258 exit 1