various: add read-only mode support
[girocco.git] / bin / git-shell-verify
blob76180a01a49977766fbcc7bf482fb280a94d5063
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 # This script is intended for use from within the chroot jail and may or may
7 # not work properly outside it.
9 set -e
11 # git_add_config "some.var=value"
12 # every ' in value must be replaced with the 4-character sequence '\'' before
13 # calling this function or Git will barf. Will not be effective unless running
14 # Git version 1.7.3 or later.
15 git_add_config() {
16 GIT_CONFIG_PARAMETERS="${GIT_CONFIG_PARAMETERS:+$GIT_CONFIG_PARAMETERS }'$1'"
17 export GIT_CONFIG_PARAMETERS
20 TMPDIR="/tmp"
21 GIT_CONFIG_NOSYSTEM=1
22 GIT_ATTR_NOSYSTEM=1
23 GIT_NO_REPLACE_OBJECTS=1
24 GIT_TERMINAL_PROMPT=0
25 GIT_PAGER="cat"
26 PAGER="cat"
27 GIROCCO_SUPPRESS_AUTO_GC_UPDATE=1
29 if ! [ -x @perlbin@ ]; then
30 # We are INSIDE the chroot
31 PATH=/bin && export PATH
32 reporoot=/@jailreporoot@
33 XDG_CONFIG_HOME=/var/empty
34 HOME=/etc/girocco
35 GIT_ASKPASS=/bin/git-askpass-password
36 rofile=/etc/readonly
37 else
38 # We are NOT INSIDE the chroot
39 reporoot=@reporoot@
40 XDG_CONFIG_HOME=@chroot@/var/empty
41 HOME=@chroot@/etc/girocco
42 GIT_ASKPASS=@basedir@/bin/git-askpass-password
43 rofile=@chroot@/etc/readonly
45 mob=@mob@
46 webadmurl=@webadmurl@
47 ua=@git_server_ua@
48 defined_ua=@defined_git_server_ua@
49 cfg_git_no_mmap=@git_no_mmap@
50 var_big_file_threshold=@big_file_threshold@
51 var_upload_window=@upload_pack_window@
52 cfg_fetch_stash_refs=@fetch_stash_refs@
53 cfg_suppress_git_ssh_logging=@suppress_git_ssh_logging@
54 cfg_max_file_size512=@max_file_size512@
55 cfg_name=@cfg_name@
57 export XDG_CONFIG_HOME
58 export HOME
59 export TMPDIR
60 export GIT_CONFIG_NOSYSTEM
61 export GIT_ATTR_NOSYSTEM
62 export GIT_NO_REPLACE_OBJECTS
63 export GIT_TERMINAL_PROMPT
64 export GIT_PAGER
65 export PAGER
66 export GIT_ASKPASS
67 export GIROCCO_SUPPRESS_AUTO_GC_UPDATE
68 unset GIT_USER_AGENT
69 unset GIT_HTTP_USER_AGENT
70 if [ -n "$defined_ua" ]; then
71 GIT_USER_AGENT="$ua"
72 export GIT_USER_AGENT
74 unset GIT_CONFIG_PARAMETERS
75 git_add_config "core.ignoreCase=false"
76 git_add_config "core.pager=cat"
77 if [ -n "$cfg_git_no_mmap" ]; then
78 # Just like compiling with NO_MMAP
79 git_add_config "core.packedGitWindowSize=1m"
80 else
81 # Always use the 32-bit default (32m) even on 64-bit to avoid memory blowout
82 git_add_config "core.packedGitWindowSize=32m"
84 # Always use the 32-bit default (256m) even on 64-bit to avoid memory blowout
85 git_add_config "core.packedGitLimit=256m"
86 [ -z "$var_big_file_threshold" ] ||
87 git_add_config "core.bigFileThreshold=$var_big_file_threshold"
88 git_add_config "gc.auto=0"
90 if [ "${cfg_fetch_stash_refs:-0}" = "0" ]; then
91 git_add_config "uploadpack.hiderefs=refs/stash"
92 git_add_config "uploadpack.hiderefs=refs/tgstash"
95 fromip="${SSH_CLIENT%% *}"
96 fromip="${fromip:+$fromip }"
98 logmsg()
100 [ "${cfg_suppress_git_ssh_logging:-0}" = "0" ] || return 0
101 _msg="$(LC_ALL=C tr -c '\040-\176' '[?*]' <<EOT
102 $fromip$* $LOGNAME
104 )" && _msg="${_msg%[?]}" &&
105 logger -t "${0##*/}[$$]" <<EOT
106 $_msg
110 # When accessing Git via ssh, there should never be a terminal on 0, 1 or 2
111 # The "no-pty" flag should be set in all the ssh keys files to prevent this
112 # However, just in case, check for it here and die as a safety fallback
113 if [ -t 0 ] || [ -t 1 ] || [ -t 2 ]; then
114 logmsg "denied pty"
115 echo forbidden >&2
116 exit 1
119 # Only the following commands are allowed:
121 # git-shell -c "git-receive-pack 'dir'"
122 # git-shell -c "git receive-pack 'dir'"
123 # git-shell -c "git-upload-pack 'dir'"
124 # git-shell -c "git upload-pack 'dir'"
125 # git-shell -c "git-upload-archive 'dir'"
126 # git-shell -c "git upload-archive 'dir'"
128 # where dir must start with $reporoot/ but a leading/trailing '/' is optional
129 # as well as the final .git however if $dir does not start with $reporoot but
130 # adding a $reporoot prefix makes it work then the $reporoot prefix will be
131 # silently added.
133 if [ "$1" != "-c" ]; then
134 echo forbidden >&2
135 exit 1
138 dir="$2"
139 type=''
140 case "$2" in
141 "git-receive-pack "*) type='receive-pack'; dir="${dir#git-receive-pack }";;
142 "git receive-pack "*) type='receive-pack'; dir="${dir#git receive-pack }";;
143 "git-upload-pack "*) type='upload-pack'; dir="${dir#git-upload-pack }";;
144 "git upload-pack "*) type='upload-pack'; dir="${dir#git upload-pack }";;
145 "git-upload-archive "*) type='upload-archive'; dir="${dir#git-upload-archive }";;
146 "git upload-archive "*) type='upload-archive'; dir="${dir#git upload-archive }";;
148 echo forbidden >&2
149 exit 1
150 esac
152 # valid project names only allow 0-9A-Za-z._+- plus the / separator and they
153 # are always single quoted so the only valid directory names will always start
154 # with a single quote and end with a single quote and not contain any internal
155 # character that needs to be escaped.
157 case "$dir" in
158 "'"*) :;;
160 logmsg "denied $type $dir"
161 echo forbidden >&2
162 exit 1
163 esac
164 case "$dir" in
165 *"'") :;;
167 logmsg "denied $type $dir"
168 echo forbidden >&2
169 exit 1
170 esac
172 # Some shells do not properly handle quoting after # or % so we cannot
173 # put an explicit ' there in a way that works for all shells. Instead
174 # just remove a single character since we've already verified it's a '.
175 dir="${dir#?}"; dir="${dir%?}"
177 # add a missing leading /
178 case "$dir" in
179 /*) :;;
181 dir="/$dir"
182 esac
184 # remove a trailing /
185 case "$dir" in
186 *?/)
187 dir="${dir%/}"
188 esac
190 # add a missing trailing .git
191 case "$dir" in
192 *.git) :;;
194 dir="$dir.git"
195 esac
197 # do not allow any .. sequence
198 case "$dir" in *..*)
199 logmsg "denied $type $dir"
200 echo forbidden >&2
201 exit 1
202 esac
204 odir="$dir"
205 case "$dir" in
206 "$reporoot/"*) :;;
208 # Allow it if prefixing with $reporoot matches an existing directory
209 if [ -d "$reporoot$dir" ]; then
210 dir="$reporoot$dir"
211 else
212 logmsg "denied $type $dir"
213 echo forbidden >&2
214 exit 1
216 esac
218 # Valid project names never end in .git (we add that automagically), so a valid
219 # fork can never have .git at the end of any path component except the last.
220 # We check this to avoid a situation where a certain collection of pushed refs
221 # could be mistaken for a GIT_DIR. Git would ultimately complain, but some
222 # undesirable things could happen along the way.
224 # Remove the leading $reporoot and trailing .git to get a test string
225 testpath="${dir#$reporoot/}"
226 testpath="${testpath%.git}"
227 case "$testpath/" in *.[Gg][Ii][Tt]/*|_*)
228 logmsg "denied $type $odir"
229 echo forbidden >&2
230 exit 1
231 esac
233 if ! [ -d "$dir" ] || ! [ -f "$dir/HEAD" ] || ! [ -d "$dir/objects" ]; then
234 logmsg "denied $type $odir"
235 echo forbidden >&2
236 exit 1
239 proj="${dir#$reporoot/}"; projbare="${proj%.git}"
241 if [ "$type" = 'receive-pack' ] && [ "$LOGNAME" = 'git' ]; then
242 logmsg "denied $type $odir"
243 echo "The user '$LOGNAME' may only be used for fetches, sorry" >&2
244 exit 3
247 if [ "$type" = 'receive-pack' ] && ! [ -f "$dir/.nofetch" ]; then
248 logmsg "denied $type $odir"
249 echo "The $proj project is a mirror and may not be pushed to, sorry" >&2
250 exit 3
253 if [ -f "$rofile" ]; then
254 msgro="$cfg_name is currently read-only, please try again later."
255 msgro2=""
256 if [ -s "$rofile" ]; then
257 msgro2="$(cat "$rofile" 2>/dev/null)" || :
259 echo "$msgro" >&2
260 [ -z "$msgro2" ] || echo "$msgro2" >&2
261 exit 3
264 GIT_SHELL='git-shell'
265 if [ "$type" = 'receive-pack' ]; then
266 git_add_config 'receive.unpackLimit=1'
267 # Note the git config documentation is wrong
268 # transfer.unpackLimit, if set, overrides receive.unpackLimit
269 git_add_config 'transfer.unpackLimit=1'
270 # set up the correct git-shell command if cfg_max_file_size512 > 0
271 if [ "${cfg_max_file_size512:-0}" != "0" ]; then
272 GIT_SHELL='ulimit512 -i -f "$cfg_max_file_size512" -- git-shell'
276 if ! [ -x @perlbin@ ] && [ "$type" = 'receive-pack' ]; then
277 # We are INSIDE the chroot trying to push
279 if ! can_user_push "$projbare"; then
280 # If mob is enabled and mob has push permissions and
281 # the current user is not the mob then it's a personal mob push
282 # presuming the special mob directory has been set up
283 if [ "$mob" = "mob" ] && [ "$LOGNAME" != "mob" ] && [ -d "$reporoot/$proj/mob" ] &&
284 can_user_push "$projbare" mob; then
286 umask 113
287 >"/etc/sshactive/$LOGNAME,"
288 mv -f "/etc/sshactive/$LOGNAME," "/etc/sshactive/$LOGNAME"
289 ! [ -e "$dir/.delaygc" ] || >"$dir/.allowgc" || :
291 logmsg "accepted $type $odir mob"
292 eval 'exec '"$GIT_SHELL"' -c "git-receive-pack '\''$reporoot/$proj/mob'\''"'
293 exit 1
295 logmsg "denied $type $odir noperms"
296 echo "The user '$LOGNAME' does not have push permissions for project '$proj'" >&2
297 echo "You may adjust push permissions at $webadmurl/editproj.cgi?name=$proj" >&2
298 exit 3
301 umask 113
302 >"/etc/sshactive/$LOGNAME,"
303 mv -f "/etc/sshactive/$LOGNAME," "/etc/sshactive/$LOGNAME"
304 ! [ -e "$dir/.delaygc" ] || >"$dir/.allowgc" || :
308 [ -z "$var_upload_window" ] || [ "$type" != "upload-pack" ] ||
309 git_add_config "pack.window=$var_upload_window"
311 logmsg "accepted $type $odir"
312 eval 'exec '"$GIT_SHELL"' -c "git-$type '\''$dir'\''"'
313 exit 1