clone/update: mark project changed on failure
[girocco.git] / jailsetup.sh
blob056730d6a9821b3c15f96988dc4a6ee0bbaf9ea6
1 #!/bin/sh
2 # The Girocco jail setup script
4 # If the first parameter is "dbonly", setup the database only
6 # We are designed to set up the chroot based on the output of
7 # `uname -s` by sourcing a suitable system-specific script.
8 # Unrecognized systems will generate an error. When using
9 # "dbonly" the setup of the chroot binaries is skipped so the
10 # output of `uname -s` does not matter in that case.
12 set -e
14 curdir="$(pwd)"
15 srcdir="$curdir/src"
16 getent="$srcdir/getent"
17 . ./shlib.sh
19 # find_std_utility should always come up with the full path to the standard
20 # version of the utility whose name is passed as "$1"
21 getconf="/usr/bin/getconf"
22 [ -x "$getconf" ] || getconf="/bin/getconf"
23 [ -x "$getconf" ] || getconf="getconf"
24 stdpath="$("unset" -f command; "command" "$getconf" "PATH" 2>/dev/null)" || :
25 ":" "${stdpath:=/bin:/usr/bin}"
26 stdpath="$stdpath:/sbin:/usr/sbin"
27 find_std_utility() (
28 "unset" -f unalias command "$1" >/dev/null 2>&1 || :
29 "unalias" -a >/dev/null 2>&1 || :
30 PATH="$stdpath" && "export" PATH || :
31 "command" -v "$1"
32 ) 2>/dev/null
34 dbonly=
35 [ "$1" != "dbonly" ] || dbonly=1
37 reserved_users="root sshd _sshd mob git lock bundle nobody everyone $cfg_cgi_user $cfg_mirror_user"
39 # Require either sshd or _sshd user unless "dbonly"
40 sshd_user=sshd
41 if ! "$getent" passwd sshd >/dev/null && ! "$getent" passwd _sshd >/dev/null; then
42 if [ -n "$dbonly" ]; then
43 if ! [ -s etc/passwd ]; then
44 # Only complain on initial etc/passwd creation
45 echo "WARNING: no sshd or _sshd user, omitting entries from chroot etc/passwd"
47 sshd_user=
48 else
49 echo "*** Error: You do not have required sshd or _sshd user in system." >&2
50 exit 1
52 else
53 "$getent" passwd sshd >/dev/null || sshd_user=_sshd
56 # Verify we have all we need
57 if ! "$getent" passwd "$cfg_mirror_user" >/dev/null; then
58 echo "*** Error: You do not have \"$cfg_mirror_user\" user in system yet." >&2
59 exit 1
61 if ! "$getent" passwd "$cfg_cgi_user" >/dev/null; then
62 echo "*** Error: You do not have \"$cfg_cgi_user\" user in system yet." >&2
63 exit 1
65 if [ -n "$dbonly" ] && [ -z "$cfg_owning_group" ]; then
66 cfg_owning_group="$("$getent" passwd "$cfg_mirror_user" | cut -d : -f 4)"
67 elif ! "$getent" group "$cfg_owning_group" >/dev/null; then
68 echo "*** Error: You do not have \"$cfg_owning_group\" group in system yet." >&2
69 exit 1
72 # One last paranoid check before we go writing all over everything
73 if [ -z "$cfg_chroot" ] || [ "$cfg_chroot" = "/" ]; then
74 echo "*** Error: chroot location is not set or is invalid." >&2
75 echo "*** Error: perhaps you have an incorrect Config.pm?" >&2
76 exit 1
79 umask 022
80 mkdir -p "$cfg_chroot"
81 cd "$cfg_chroot"
82 chmod 755 "$cfg_chroot" ||
83 echo "WARNING: Cannot chmod $cfg_chroot"
85 mkdir -p var/empty
86 chmod 0555 var/empty ||
87 echo "WARNING: Cannot chmod a=rx $cfg_chroot/var/empty"
89 # Set up basic user/group configuration; if there isn't any already
90 mobpass=
91 [ -n "$cfg_mob" ] || mobpass='x'
92 mkdir -p etc
93 if ! [ -s etc/passwd ]; then
94 cat >etc/passwd <<EOT
95 root:x:0:0:system administrator:/var/empty:/bin/false
96 nobody:x:$("$getent" passwd nobody | cut -d : -f 3-4):unprivileged user:/var/empty:/bin/false
97 EOT
98 [ -z "$sshd_user" ] || cat >>etc/passwd <<EOT
99 sshd:x:$("$getent" passwd $sshd_user | cut -d : -f 3-4):privilege separation:/var/empty:/bin/false
100 _sshd:x:$("$getent" passwd $sshd_user | cut -d : -f 3-4):privilege separation:/var/empty:/bin/false
102 [ "$cfg_cgi_user" = "$cfg_mirror_user" ] || cat >>etc/passwd <<EOT
103 $cfg_cgi_user:x:$("$getent" passwd "$cfg_cgi_user" | cut -d : -f 3-5):/:/bin/true
105 cat >>etc/passwd <<EOT
106 $cfg_mirror_user:x:$("$getent" passwd "$cfg_mirror_user" | cut -d : -f 3-5):/:/bin/true
107 everyone:x:65537:$("$getent" group "$cfg_owning_group" | cut -d : -f 3):every user:/:/bin/false
108 mob:$mobpass:65538:$("$getent" group "$cfg_owning_group" | cut -d : -f 3):the mob:/:/bin/git-shell-verify
109 git::65539:$("$getent" passwd nobody | cut -d : -f 4):read-only access:/:/bin/git-shell-verify
111 elif [ -z "$dbonly" ]; then
112 # Make sure an sshd entry is present
113 if ! grep -q '^sshd:' etc/passwd; then
114 echo "*** Error: chroot etc/passwd exists but lacks sshd entry." >&2
115 exit 1
119 if ! [ -s etc/group ]; then
120 cat >etc/group <<EOT
121 _repo:x:$("$getent" group "$cfg_owning_group" | cut -d : -f 3):$cfg_mirror_user
125 # Set up basic default Git configuration
126 # Initialize one if none exists or update critical variables for an existing one
127 mkdir -p etc/girocco
128 didchmod=
129 if [ -e etc/girocco/.gitconfig ] && ! [ -f etc/girocco/.gitconfig ]; then
130 echo "*** Error: chroot etc/girocco/.gitconfig exists but is not a file." >&2
131 exit 1
133 if [ -f etc/girocco/.gitconfig ]; then
134 gcerr=0
135 x="$(git config --file etc/girocco/.gitconfig --get "no--such--section.no such subsection.no--such--key")" || gcerr=$?
136 if [ $gcerr -gt 1 ]; then
137 echo "*** Error: chroot etc/girocco/.gitconfig exists but is corrupt." >&2
138 echo "*** Error: either remove it or edit it to correct the problem." >&2
139 exit 1
142 if ! [ -s etc/girocco/.gitconfig ]; then
143 chmod u+w etc/girocco
144 didchmod=1
145 cat >etc/girocco/.gitconfig <<EOT
146 # Any values set here will take effect whenever Girocco runs a git command
150 # $1 => name, $2 => value, $3 => overwrite_flag
151 # if $3 is "2" and $2 is "" value will be unset
152 update_config_item() {
153 _existsnot=
154 _oldval=
155 _oldval="$(git config --file etc/girocco/.gitconfig --get "$1")" || _existsnot=1
156 if [ -n "$_existsnot" ]; then
157 [ -n "$2" ] || [ "$3" != "2" ] || return 0
158 else
159 [ -n "$3" ] || return 0
160 [ "$_oldval" != "$2" ] || { [ "$3" = "2" ] && [ -z "$2" ]; } || return 0
162 [ -n "$didchmod" ] || { chmod u+w etc/girocco; didchmod=1; }
163 if [ "$3" = "2" ] && [ -z "$2" ]; then
164 git config --file etc/girocco/.gitconfig --unset "$1"
165 else
166 git config --file etc/girocco/.gitconfig "$1" "$2"
168 if [ -n "$_existsnot" ]; then
169 echo "chroot: etc/girocco/.gitconfig: config $1: (created) \"$2\""
170 elif [ "$3" = "2" ] && [ -z "$2" ]; then
171 echo "chroot: etc/girocco/.gitconfig: config $1: (removed)"
172 else
173 echo "chroot: etc/girocco/.gitconfig: config $1: \"$_oldval\" -> \"$2\""
176 if [ -n "$cfg_git_no_mmap" ]; then
177 update_config_item core.packedGitWindowSize 1m 1
178 else
179 update_config_item core.packedGitWindowSize 32m 1
181 if [ -n "$var_window_memory" ]; then
182 update_config_item pack.windowMemory "$var_window_memory" 1
184 if [ -n "$cfg_jgit_compatible_bitmaps" ]; then
185 update_config_item pack.writeBitmapHashCache false 1
186 else
187 update_config_item pack.writeBitmapHashCache true 1
189 update_config_item core.pager "cat" 1
190 update_config_item core.compression 5
191 update_config_item transfer.unpackLimit 1 1
192 update_config_item http.lowSpeedLimit 1
193 update_config_item http.lowSpeedTime 600
194 update_config_item receive.advertisePushOptions false 1
195 update_config_item receive.maxInputSize "${cfg_max_receive_size:-0}" 1
196 update_config_item girocco.notifyHook "${cfg_default_notifyhook}" 2
197 [ -z "$didchmod" ] || chmod a-w etc/girocco
199 mkdir -p etc/sshkeys etc/sshcerts etc/sshactive
200 for ruser in $reserved_users; do
201 touch etc/sshkeys/$ruser
202 done
203 chgrp $cfg_owning_group etc etc/sshkeys etc/sshcerts etc/sshactive ||
204 echo "WARNING: Cannot chgrp $cfg_owning_group the etc directories"
205 chgrp $cfg_owning_group etc/passwd ||
206 echo "WARNING: Cannot chgrp $cfg_owning_group $cfg_chroot/etc/passwd"
207 chgrp $cfg_owning_group etc/group ||
208 echo "WARNING: Cannot chgrp $cfg_owning_group $cfg_chroot/etc/group"
209 chgrp $cfg_owning_group etc/girocco etc/girocco/.gitconfig ||
210 echo "WARNING: Cannot chgrp $cfg_owning_group $cfg_chroot/etc/girocco"
211 chmod g+s etc etc/sshkeys etc/sshcerts etc/sshactive ||
212 echo "WARNING: Cannot chmod g+s the etc directories"
213 chmod g+w etc etc/sshkeys etc/sshcerts etc/sshactive ||
214 echo "WARNING: Cannot chmod g+w the etc directories"
215 chmod g+w etc/passwd etc/group ||
216 echo "WARNING: Cannot chmod g+w the etc/passwd and/or etc/group files"
217 chmod go-w etc/passwd etc/girocco etc/girocco/.gitconfig ||
218 echo "WARNING: Cannot chmod go-w etc/girocco and/or etc/girocco/.gitconfig"
219 chmod a-w etc/girocco ||
220 echo "WARNING: Cannot chmod a-w etc/girocco"
221 chmod -R g+w etc/sshkeys etc/sshcerts etc/sshactive 2>/dev/null ||
222 echo "WARNING: Cannot chmod g+w the sshkeys, sshcerts and/or sshactive files"
224 # Note time of last install
225 >etc/sshactive/_install
227 [ -z "$dbonly" ] || exit 0
229 # Make sure the system type is supported for chroot
230 sysname="$(uname -s | tr A-Z a-z)" || :
231 : ${sysname:=linux}
232 nosshdir=
233 # These equivalents may need to be expanded at some point
234 case "$sysname" in
235 *kfreebsd*)
236 sysname=linux;;
237 *darwin*)
238 sysname=darwin;;
239 *dragonfly*)
240 sysname=dragonfly;;
241 *freebsd*)
242 sysname=freebsd;;
243 *linux*)
244 sysname=linux;;
245 esac
247 chrootsetup="$curdir/chrootsetup_$sysname.sh"
248 if ! [ -f "$chrootsetup" ] || ! [ -r "$chrootsetup" ] || ! [ -s "$chrootsetup" ]; then
249 echo "*** Error: $chrootsetup not found" >&2
250 echo "*** Error: creating a chroot for a $(uname -s) system is not supported" >&2
251 exit 1
254 # validate reporoot, chroot, jailreporoot and sshd_bin before doing anything more
256 # validates the passed in dir if a second argument is not empty dir must NOT
257 # start with / otherwise it must. A trailing '/' is removed and any duplicated
258 # // are removed and a sole / or empty is disallowed.
259 make_valid_dir() {
260 _check="$(echo "$1" | tr -s /)"
261 _check="${_check%/}"
262 [ -n "$_check" ] && [ "$_check" != "/" ] || return 1
263 if [ -z "$2" ]; then
264 # must start with '/'
265 case "$_check" in /*) :;; *) return 1; esac
266 else
267 # must NOT start with '/'
268 case "$_check" in /*) return 1; esac
270 echo "$_check"
273 if ! reporoot="$(make_valid_dir "$cfg_reporoot")"; then
274 echo "*** Error: invalid Config::reporoot: $cfg_reporoot" >&2
275 echo "*** Error: MUST start with '/' and MUST NOT be '/'" >&2
276 exit 1
278 if ! chroot="$(make_valid_dir "$cfg_chroot")"; then
279 echo "*** Error: invalid Config::chroot: $cfg_chroot" >&2
280 echo "*** Error: MUST start with '/' and MUST NOT be '/'" >&2
281 exit 1
283 if ! jailreporoot="$(make_valid_dir "$cfg_jailreporoot" 1)"; then
284 echo "*** Error: invalid Config::jailreporoot: $cfg_jailreporoot" >&2
285 echo "*** Error: MUST NOT start with '/' and MUST NOT be ''" >&2
286 exit 1
289 # chroot MUST NOT be reporoot
290 if [ "$chroot" = "$reporoot" ]; then
291 echo "*** Error: invalid Config::reporoot: $cfg_reporoot" >&2
292 echo "*** Error: invalid Config::chroot: $cfg_chroot" >&2
293 echo "*** Error: reporoot and chroot MUST NOT be the same" >&2
294 exit 1
297 # chroot MUST NOT be a subdirectory of reporoot
298 case "$chroot" in "$reporoot"/*)
299 echo "*** Error: invalid Config::reporoot: $cfg_reporoot" >&2
300 echo "*** Error: invalid Config::chroot: $cfg_chroot" >&2
301 echo "*** Error: chroot MUST NOT be a subdirectory of reporoot" >&2
302 exit 1
303 esac
305 # chroot/jailreporoot MUST NOT be a subdirectory of reporoot
306 case "$chroot/$jailreporoot" in "$reporoot"/*)
307 echo "*** Error: invalid Config::reporoot: $cfg_reporoot" >&2
308 echo "*** Error: invalid Config::chroot: $cfg_chroot" >&2
309 echo "*** Error: invalid Config::jailreporoot: $cfg_jailreporoot" >&2
310 echo "*** Error: chroot/jailreporoot MUST NOT be a subdirectory of reporoot" >&2
311 exit 1
312 esac
314 # reporoot MUST NOT be a subdirectory of chroot/jailreporoot
315 case "$reporoot" in "$chroot/$jailreporoot"/*)
316 echo "*** Error: invalid Config::reporoot: $cfg_reporoot" >&2
317 echo "*** Error: invalid Config::chroot: $cfg_chroot" >&2
318 echo "*** Error: invalid Config::jailreporoot: $cfg_jailreporoot" >&2
319 echo "*** Error: reporoot MUST NOT be a subdirectory of chroot/jailreporoot" >&2
320 exit 1
321 esac
323 # sshd_bin MUST be undef (or empty) or a full absolute path
324 sshd_bin_bad=
325 case "$cfg_sshd_bin" in *"/../"*) sshd_bin_bad=1;; ""|/?*) :;; *) sshd_bin_bad=1;; esac
326 [ -z "$sshd_bin_bad" ] || {
327 echo "*** Error: invalid Config::sshd_bin $cfg_sshd_bin" >&2
328 echo "*** Error: if set, sshd_bin must be an absolute path" >&2
329 exit 1
331 sshd_bin="$cfg_sshd_bin"
332 [ -n "$sshd_bin" ] || sshd_bin="$(find_std_utility "sshd")" || {
333 echo "*** Error: Config::sshd_bin is not set and no sshd could be found" >&2
334 echo "*** Error: please set Config::sshd_bin to an absolute path to sshd" >&2
335 exit 1
337 [ -x "$sshd_bin" ] && [ -r "$sshd_bin" ] && [ -f "$sshd_bin" ] || {
338 echo "*** Error: the selected sshd ('$sshd_bin') was not found, not readable or not executable" >&2
339 exit 1
342 # Set the user and group on the top of the chroot before creating anything else
343 chown 0:0 "$chroot"
345 # When we create a fork, the alternates always have an absolute path.
346 # If reporoot is not --bind mounted at the same location in chroot we must
347 # create a suitable symlink so the absolute path alternates continue to work
348 # in the ssh chroot or else forks will be broken in there.
349 if [ "$reporoot" != "/$jailreporoot" ]; then
350 mkdirp="$(dirname "${reporoot#/}")"
351 [ "$mkdirp" = "." ] && mkdirp=
352 lnback=
353 [ -z "$mkdirp" ] || lnback="$(echo "$mkdirp/" | sed -e 's,[^/]*/,../,g')"
354 [ -z "$mkdirp" ] || mkdir -p "$chroot/$mkdirp"
355 (umask 0; ln -s -f -n "$lnback$jailreporoot" "$chroot$reporoot")
356 [ $? -eq 0 ] || exit 1
359 # First, setup basic platform-independent directory structure
360 mkdir -p bin dev etc lib sbin var/empty var/run "$jailreporoot"
361 chmod 0555 var/empty
362 rm -rf usr local
363 ln -s . usr
364 ln -s . local
366 # Now source the platform-specific script that is responsible for dev device
367 # setup, proc setup (if needed), lib64 setup (if needed) and basic library
368 # installation to make a chroot operational. Additionally it will define a
369 # pull_in_bin function that can be used to add executables and their library
370 # dependencies to the chroot and finally will install a suitable nc.openbsd
371 # compatible version of netcat that supports connections to unix sockets.
372 . "$chrootsetup"
374 # Now, bring in sshd, sh etc.
375 # The $chrootsetup script should have already provided a suitable nc.openbsd
376 install -p "$cfg_basedir/bin/git-shell-verify" bin/git-shell-verify.new
377 install -p "$cfg_basedir/bin/git-askpass-password" bin/git-askpass-password.new
378 perl -i -p \
379 -e 's|^#!.*|#!/bin/sh| if $. == 1;' \
380 -e 'close ARGV if eof;' \
381 bin/git-shell-verify.new bin/git-askpass-password.new
382 mv -f bin/git-askpass-password.new bin/git-askpass-password
383 mv -f bin/git-shell-verify.new bin/git-shell-verify
384 pull_in_bin "$cfg_basedir/bin/can_user_push" bin
385 pull_in_bin "$cfg_basedir/bin/list_packs" bin
386 pull_in_bin "$cfg_basedir/bin/strftime" bin
387 pull_in_bin "$var_sh_bin" bin/sh
388 # be paranoid since these are going into the chroot and make sure
389 # that we get the "standard" versions of them (they are all standard "POSIX"
390 # utilities) not some wayward version picked up by a haphazard PATH
391 pull_in_bin "$(find_std_utility cat )" bin
392 pull_in_bin "$(find_std_utility chmod )" bin
393 pull_in_bin "$(find_std_utility date )" bin
394 pull_in_bin "$(find_std_utility find )" bin
395 pull_in_bin "$(find_std_utility mkdir )" bin
396 pull_in_bin "$(find_std_utility mv )" bin
397 pull_in_bin "$(find_std_utility rm )" bin
398 pull_in_bin "$(find_std_utility sleep )" bin
399 pull_in_bin "$(find_std_utility sort )" bin
400 pull_in_bin "$(find_std_utility touch )" bin
401 pull_in_bin "$(find_std_utility tr )" bin
402 pull_in_bin "$(find_std_utility wc )" bin
403 # this one's already been validated and might be in a non-standard location
404 pull_in_bin "$sshd_bin" sbin
406 # ...and the bits of git we need,
407 # being sure to use the configured git and its --exec-path to find the pieces
408 for i in git git-index-pack git-receive-pack git-shell git-update-server-info \
409 git-upload-archive git-upload-pack git-unpack-objects git-config \
410 git-for-each-ref git-rev-list git-rev-parse git-symbolic-ref; do
411 pull_in_bin "$var_git_exec_path/$i" bin git
412 done
414 # ...and any extras identified by install.sh
415 # these are also all standard "POSIX" utilities
416 # ones that a decent sh implementation would have built-in already...
417 if [ -n "$GIROCCO_CHROOT_EXTRA_INSTALLS" ]; then
418 for i in $GIROCCO_CHROOT_EXTRA_INSTALLS; do
419 pull_in_bin "$(find_std_utility "$(basename "$i")")" bin
420 done
423 # Note time of last jailsetup
424 >etc/sshactive/_jailsetup
426 # Update permissions on the database files
427 chown $cfg_cgi_user:$cfg_owning_group etc/passwd etc/group
428 chown -R $cfg_cgi_user:$cfg_owning_group etc/sshkeys etc/sshcerts etc/sshactive
429 chown $cfg_mirror_user:$cfg_owning_group etc etc/girocco etc/girocco/.gitconfig
431 # Set up basic sshd configuration:
432 if [ -n "$nosshdir" ]; then
433 rm -rf etc/ssh
434 ln -s . etc/ssh
435 ! [ -f /etc/moduli ] || { cp -p /etc/moduli etc/; chown 0:0 etc/moduli; }
436 else
437 ! [ -e etc/ssh ] || [ -d etc/ssh ] || rm -rf etc/ssh
438 mkdir -p etc/ssh
439 ! [ -f /etc/ssh/moduli ] || { cp -p /etc/ssh/moduli etc/ssh/; chown 0:0 etc/ssh/moduli; }
441 mkdir -p var/run/sshd
442 if ! [ -s etc/ssh/sshd_config ]; then
443 cat >etc/ssh/sshd_config <<EOT
444 Protocol 2
445 Port $cfg_sshd_jail_port
446 UsePAM no
447 X11Forwarding no
448 AllowAgentForwarding no
449 AllowTcpForwarding no
450 PermitTunnel no
451 IgnoreUserKnownHosts yes
452 PrintLastLog no
453 PrintMotd no
454 UseDNS no
455 PermitRootLogin no
456 UsePrivilegeSeparation yes
458 HostKey /etc/ssh/ssh_host_rsa_key
460 if [ -z "$cfg_disable_dsa" ]; then
461 cat >>etc/ssh/sshd_config <<EOT
462 HostKey /etc/ssh/ssh_host_dsa_key
465 cat >>etc/ssh/sshd_config <<EOT
466 AuthorizedKeysFile /etc/sshkeys/%u
467 StrictModes no
469 # mob and git users:
470 PermitEmptyPasswords yes
471 ChallengeResponseAuthentication no
472 PasswordAuthentication yes
475 if ! [ -s etc/ssh/ssh_host_rsa_key ]; then
476 bits=2048
477 if [ "$cfg_rsakeylength" -gt "$bits" ] 2>/dev/null; then
478 bits="$cfg_rsakeylength"
480 yes | ssh-keygen -b "$bits" -t rsa -N "" -C Girocco -f etc/ssh/ssh_host_rsa_key
482 if [ -z "$cfg_disable_dsa" ] && ! [ -s etc/ssh/ssh_host_dsa_key ]; then
483 # ssh-keygen can only create 1024 bit DSA keys
484 yes | ssh-keygen -b 1024 -t dsa -N "" -C Girocco -f etc/ssh/ssh_host_dsa_key
487 # Set the final permissions on the binaries and perform any final twiddling
488 chroot_update_permissions
490 # Change the owner of the sshd-related files
491 chown 0:0 etc/ssh/ssh_* etc/ssh/sshd_*
493 echo "--- Add to your boot scripts: mount --bind $reporoot $chroot/$jailreporoot"
494 echo "--- Add to your boot scripts: mount --bind /proc $chroot/proc"
495 echo "--- Add to your syslog configuration: listening on socket $chroot/dev/log"
496 echo "--- To restart a running jail's sshd: sudo kill -HUP \$(cat $chroot/var/run/sshd.pid)"