An(other?) approach to chrooting sshd under OpenBSD

Table of contents
  1. Installing
  2. Building the jail
  3. My makejail script
  4. Testing the jail
  5. Adding users to the jail
  6. Testing as a user
  7. Not done yet
  8. Permissions
  9. Adding other programs to the jail
  10. Using other shells
  11. Concise instructions
  12. My version of the patch
  13. The End

Introduction and acknowledgments
I started my quest because I needed a way to host a directory that would allow 2 different groups of people to share files securely. One group is in the process of turning off Windows file sharing visible outside their subnet, and requested we run an sftp server, presumably because it simplifies their firewall ruleset by letting them do some equivalent of 'pass out all on intf_x keep state'. In any case, neither group has any need to poke around the rest of the machine, and I didn't want to have to set up a separate machine for them.

I began by doing the usual searching with Google, monkey.org's search of OpenBSD archives, and FreeBSD's search of their mailing lists. I didn't find quite what I wanted, so I posted a question to comp.unix.bsd.openbsd.misc. I got a couple of replies, which pointed me toward http://chrootssh.sourceforge.net and http://www.sublimation.org/scponly/. Both of these came close to what I wanted, but neither actually worked in my situation.

What is chroot()?
OK, here we go with the newbies guide, at least for a minute. chroot stands for change root directory, and that's what it does, for the user or process affected. There's both a C function (see man 2 chroot) and a command/program (see man 8 chroot) which do the same thing, depending on what you need. If you're wondering why I wrote command/program you'll see in a minute.

When you get chrooted, you can't see beyond the directory that you're chrooted into. Period. Actually there are some tricks for breaking out (see the links at the bottom of http://chrootssh.sourceforge.net/docs/chrootedsftp.html) but I'm being naive for the sake of this discussion. It may seem an odd analogy, but I'm about to get a new kitten. For several weeks, that kitten won't be allowed outside my apartment. Everything it eats, drinks, sees or does will be indoors. The apartment will become its world for that time, so that it will consider it home when I do let it out. I have to provide everything it will need. It will be chrooted to my apartment. It can look out the windows, which is more than someone chrooted into a directory can do. Think of it as an exercise in perspective.

It wasn't an accident that I wrote command/program above. If you're new to the unix world, you may not realize yet that when you type ls to list a directory or cp to copy a file, you're actually running programs named ls and cp. Most of the things we take for granted are actually separate programs. For a user to log into a chrooted directory with some GUI sftp client and see a list of files, there has to be an ls available. Which means there has to be a copy of ls inside the chrooted directory, because the user can't see or run the one outside. The same is true of cp, cat, mv, rm, mkdir, rmdir and others. This implies a responsibility to provide what they need, but it also gives you the power of not letting them at things they might cause mischief with like su, chmod, or a C compiler.

The way this modification to sshd works is by looking for the string "/./" in what is stored as being the user's home directory. It took me a while to figure this out from what I was reading, but there is never actually a directory with /./ in it. That's only a switch to tell sshd where to split the path and do the chroot. If it doesn't find it, it doesn't chroot. This is completely transparent to normal users - if there isn't /./ in your home directory you don't get chrooted.



GUI tree view of directories
chrooted on right

In my case, I put the chrooted directory on a separate physical drive, mounted as /drv2. My chrooted parent directory is called chrooted, and sits in the root of /drv2. My user directory I installed inside that is called r25.

The home directory for the r25 user is specified as /drv2/chrooted/./r25 (but the actual path is /drv2/chrooted/r25).

When the r25 user logs in, they're inside /drv2/chrooted/r25. They can cd .. or cd / to get to /drv2/chrooted, but to them that looks like /. When you set /bin/sh as their shell, you're really setting them up to use /drv2/chrooted/bin/sh.

This is a convenience to all concerned. Remember that they need to have all their programs, libraries, and a few other things within the space that they can see. If the chroot locked them into /drv2/chrooted/r25 all those would have to be inside, amounting to a lot of clutter. There would be a temptation to delete some of "that junk" if permissions were such that they could. They should not have permissions to delete or write outside of r25 at all. Their $PATH is "/bin:/usr/bin" so they can run the executables in /drv2/chrooted/bin and /drv2/chrooted/usr/bin but should not be able to change anything out there.

This is also a convenience to the administrator, since adding more chrooted accounts just means running adduser again to create more accounts with their home directories inside "/drv2/chrooted/." Since none of them can change anything in that shared area, (or in each other's) you don't have to re-create the whole thing for each one. Even better, if you've got a user who's being a problem, you can just move their home directory inside the chrooted area and change their official home directory to have the /./. They'll at least get the idea that they've been warned.

(Spiffy graphics courtesy of Total Commander and IrfanView)



Since the string "/./" always has the same length of 3, what the patch is doing is chrooting to the directory before that string, then setting the home directory to what comes after it. If I keep looking at these I might actually learn how to manipulate strings in C.

Installing
Exit newbie mode. If you're installing a chrooted OpenSSH for OpenBSD, which this is all about, get the latest version of OpenSSH that is specifically designed for OpenBSD. OpenSSH was written under OpenBSD, and when they made it portable/generic for other operating systems they somehow got it where it doesn't work quite the same way. At least it's got extra stuff you don't need. Go to http://www.openssh.org/openbsd.html to get a pure version.

Put it right into /usr/src/usr.bin/. If you've done this before, or you're overly confident or in a terrible hurry, just delete the ssh directory without backing it up first. Otherwise do something like tar -csv old_ssh.tar ssh/*. Then delete the ssh directory with something like rm -R ssh. If you really got the OpenBSD version it will make a new ssh directory when you extract it. It will also install everything in the proper places when you do a make install. The generic version won't.

So, you've got the new tgz file in /usr/src/usr.bin and you're removed the old /usr/src/usr.bin/ssh directory. Now you're ready to do something like (I still like the old 2-step process):
gunzip openssh*.tgz   then
tar -xvf openssh-3.6.1.tar   (Substitute your exact version here - tar doesn't like wildcards)

Now you have an ssh directory again. CD into it. The file you need to modify is session.c and it's a good idea to make a copy of it first by doing something like
cp session.c session_backup.c   Have patience, I'm from the old school.

session.c is what you need to modify and how you do it is up to you. I'm not fond of patches, and I wouldn't have spent most of last weekend figuring this out if the patch had worked. A patch is potentially a quick way of making the changes you need to make if it works. I present a patch later on that you can copy and paste into a file to apply if you want. This is a relatively simple change you need to make. You're just adding a function and inserting one line into an existing one to call it. The new function can be placed almost anywhere before the existing one (as long as you don't put it inside another function).

Here's the new function. You can copy and paste this:

/* I've isolated the part that does the chroot and put it in its own function */
void
do_chroot(struct passwd *pw){
  char *user_dir;
  char *new_root;

  user_dir = xstrdup(pw->pw_dir);
  new_root = user_dir + 1;
  while((new_root = strchr(new_root, '.')) != NULL) {
  	new_root--;
  	if(strncmp(new_root, "/./", 3) == 0) {
  	*new_root = '\0';
  	new_root += 2;

  	if(chroot(user_dir) != 0)
  		fatal("Couldn't chroot to user directory %s", user_dir);
  		pw->pw_dir = new_root;
  		break;
  	}
  	new_root += 2;
  }
} /* do_chroot */
That should go before the function do_setusercontext. (You can search for do_setusercontext and put your new code in the blank lines above that.)

The line you need to add is: (you can leave out the comment of course)
do_chroot(pw); /* insertion of this is the only change in the original code */

And it should go like this:
/* Set login name, uid, gid, and groups. */
void
do_setusercontext(struct passwd *pw)
{
do_chroot(pw); /* insertion of this is the only change in the original code */
	if (getuid() == 0 || geteuid() == 0) {
#ifdef HAVE_LOGIN_CAP

That's it: that's all there is to this patch. Simple, huh? I thought so after I'd spent hours poking around in a pre-patched version that wasn't working trying to figure out what the problem was and then saw the bare patch. In the pre-patched version the code for the patch ends up inside an area that's never even compiled due to some #ifdefs checking something that's always true in OpenBSD but apparently isn't in Linux where the patch was written. I kept sticking in printf statements to have some idea where the program flow was going wrong, but I was never seeing anything. The #ifdefs aren't just a branch in the code, they're an instruction to the preprocessor to leave out the section of code inside under certain conditions, so the patch and my printfs were never getting compiled into the executable at all.

How you modify the code is your business. Being sometimes not much of a purist I cheated. I FTPed session.c to a Windows machine as ASCII, modified it, and FTPed it back as ASCII. I don't have X running on anything convenient and this is a situation that lends itself well to copying and pasting. You could also isolate the new function into a text file by itself, then open session.c in an editor, put the cursor where the function goes, and read the block in. Ctrl-K R will read a block in Joe. vi? Emacs? I never use them.


So now you've got your modified session.c in place, you're in the /usr/src/usr.bin/ssh directory and you're ready to build. The basic commands to type are:

make obj
make cleandir
make depend
make
And then you pause.

If you haven't seen any errors up to this point, good. The only likely errors would be in the make step, and they'd most likely happen because something didn't go right in modifying the code. If you get errors compiling session.c you should have a line number showing after it stops, so look at the code and figure out what's wrong.

I did get a warning some times I compiled that has to do with not having a smartcard, something related to "scard" but that's it. I did most of my work on this at home on an Athlon XP 1800 running OpenBSD 3.3, but my target production machine is an old 200 MHZ Pentium 1 running OpenBSD 3.1. I just tried to reproduce the warning for the sake of documenting it here at home, but I couldn't find it. If you really want to scrutinize your output from the various make steps, use tee. Something like make | tee make_output.txt works well. Most of the time it either flies by too fast to read, or it goes so slowly you don't pay attention.

We're also paused here because the next step is going to replace your existing ssh daemon. If you're logged on remotely via ssh, this is the time to log out and walk to the machine for the next couple of steps. If that's absolutely impossible, like if you're installing from miles away, you can turn on telnet temporarily and use that to connect. Just do /usr/libexec/telnetd -debug to start it. I just tried it and the daemon seemed to die by itself when I disconnected.

Whatever method you use to talk to your box independantly of ssh, the process is about the same. You want to kill off any sshd instances that are around. I usually start by running top, and doing a k then entering the appropriate process number which I read from the screen as it's bouncing around. Most likely there will be more than one instance and you'll need to get them one at a time. When you don't see any more, do a cat /var/run/sshd.pid, then kill -9 (whatever process number you saw) . You'll probably see "No such process", and that's good. The ssh daemon is one of those, like Apache, that spawns children to always have some available for an incoming connection. The /var/run/sshd.pid file only contains the process number of the last one that was started. It gets overwritten every time a new one starts up. When you think you've gotten them all, try (as root): rm /usr/sbin/sshd. If you don't get a prompt back like "override r-xr-xr-x root/bin for sshd?" you're all set, sshd is shut down. If you do get the prompt, hit n and keep looking for more instances.

When you don't get any more resistance to deleting sshd, it's gone and you're ready to install the new one. Do a cd /usr/src/usr.bin/ssh then make install. It should only take a few seconds because you're just copying some files into place. Restart sshd by typing /usr/sbin/sshd. You should now be able to connect normally via ssh, so test that then either log out of your telnet connection or go back to your more comfortable seat.

Building the jail
We couldn't do this before we rebuilt sshd, because one of the files we need to copy into it, sftp-server, has also just been rebuilt. Using a script to build a jail is a concept I got from http://www.sublimation.org/scponly/. I couldn't use their approach because I need true sftp, not scp. I've ended up with both, and that's OK. Building a jail with a script works well when you're trying to tell someone how to do it, and in my case where I was practicing on one machine to do an install on a different one it worked well. I fiddled with this script until it did what I wanted at home, then when I ran it at work there were no surprises. As I've mentioned, it was written under OpenBSD 3.3 and worked fine under 3.1.

Before you can use the script, you need to decide where you're going to put your new jail. The filesystem can't be mounted nodev. I think in most systems there's at least one filesystem that's mounted nodev but / can't be. The way to check is to cat /etc/fstab   You should see something like:

  george# cat /etc/fstab
  /dev/sd0a / ffs rw 1 1
  /dev/sd0d /usr ffs rw,nodev 1 2
In this case, I can't put the jail on /dev/sd0d   Also in this case that was no big deal because I could put it elsewhere (I have a RAID 5 array that I mount manually, so it's not in fstab). Supposedly you can take the nodev out of fstab for that filesystem and either unmount and remount it or reboot without any great penalty, but I didn't need to.

If, when you start testing or using your chroot, you see some error like this:

   /bin/sh: No controlling tty (open /dev/tty: Device not configured)
   /bin/sh: warning: won't have full job control
That's the most likely reason. Another possibilty is that something went wrong with making the /dev/tty device inside the jail, but that's less likely. I think I've also seen this if I've tried to move an existing jail. You can't do that, you need to make a new one and move the user directories into it.

OK, here's my makejail.sh script. Create the directory that's going to become the jail where you want it, put the makejail.sh script inside it, cd into it, then, as root, run the script. Copy this off your web browser screen and paste it into a text file to create your own copy. It doesn't have the necessary magic to be executable by itself, so you'll have to run it by doing sh makejail.sh

mkdir bin
cp `whereis ls` bin
cp `whereis sh` bin
cp `whereis cp` bin
cp `whereis mkdir` bin
cp `whereis mv` bin
cp `whereis rm` bin
cp `whereis rmdir` bin
cp `whereis cat` bin
cp `whereis groups` bin

mkdir usr
mkdir usr/lib
cp /usr/lib/libutil.so* usr/lib
cp /usr/lib/libz.so* usr/lib
cp /usr/lib/libcrypto.so* usr/lib
cp /usr/lib/libc.so* usr/lib
mkdir usr/libexec
cp /usr/libexec/sftp-server usr/libexec
cp /usr/libexec/ld.so usr/libexec

#chmod +s usr/libexec/sftp-server

mkdir usr/bin
cp `whereis id` usr/bin
mkdir etc
cp /etc/group etc

mkdir dev
cd dev
mknod zero c 13 12
mknod null c 13 2
mknod tty c 1 0
chmod 666 *
cd ..

Notes about the script
I've tried to make it as version-independant as possible in a couple of ways. I'm using the whereis command to find executables, rather than expecting them to be at certain locations. By putting the whereis in backticks (upper left corner of the keyboard) I'm feeding the location found by whereis into cp. I've also not including a version number when copying libraries with lines like "cp /usr/lib/libutil.so*". On my machine, at present, there's only one version of each library anyway. After an upgrade things could get more complicated and there would end up being more junk in the jail lib directories. The line "#chmod +s usr/libexec/sftp-server" is commented out, because I found conflicting advice. One source says the sftp-server should be setuid root, another says there shouldn't be any setuid root programs in the jail. It's working without doing the setuid, so it's probably better this way from a security standpoint. I think the setuid was only intended to give access to port numbers this low. I just checked in Top with my user connected using an sftp client, and I see sftp-server, sshd and sh all running as that user.

It's best to move the makejail.sh script out of the jail after you've run it so users don't find it.

Testing the jail
So, if you didn't see any errors running the makejail.sh script, you're ready to test the chroot. Become root, whether by su or logging on as root. This is not testing the ability of sshd to chroot, it's testing the integrity of the chroot jail itself. Change the jail path here as appropriate when you type it:
chroot /drv2/chrooted /bin/sh
You should find yourself inside the jail. Do a cd / and then ls. You should only see the outer directories in your jail: bin, dev, etc and usr. You should not have gotten the error about no controlling tty as discussed above under the nodev filesystem. Try a command you know you didn't include, like su. You should get an error. Try breaking out, but don't bother trying very long. When you get bored type exit to return to your original process.

Adding users
Now it's time to add some user accounts inside this jail. You can do this with adduser from the command line, and if you're going to be adding a lot of them you probably want to write a script to automate it by doing them all at once. I'm not going to discuss the command line options here. See man adduser for those if you want to use it. I used it in interactive mode.

Before you do that, you should make a change in /etc/adduser.conf

Find this section:

# default HOME directory ("/home")
# home = "/home"
home = "/drv2/chrooted/."
Comment out the default home defined and add your own for the chrooted users on another line, as above. The new path should end with a period as above. When adduser makes a new directory it will append / and the new username. When you want to go back to adding normal users, comment that line out and uncomment the original line.

When that's done, type adduser. The script will prompt you through, and the session will look something like what's below. When in doubt about something, it's usually safe to accept the default by hitting enter.
leto# adduser
Use option ``-silent'' if you don't want to see all warnings and questions.

Reading /etc/shells
Check /etc/master.passwd
Check /etc/group

Ok, let's go.
Don't worry about mistakes. I will give you the chance later to correct any input.
Enter username [a-z0-9_-]: fred
Enter full name []: fred chrooted
Enter shell csh ksh nologin scponlyc sh tcsh [sh]:
Uid [1005]:
Login group fred [fred]:
Login group is ``fred''. Invite fred into other groups: guest no
[no]:
Enter password []:
Enter password again []:

Name:     fred
Password: ****
Fullname: fred chrooted
Uid:      1005
Gid:      1005 (fred)
Groups:   fred
HOME:     /drv2/chrooted/./fred
Shell:    /bin/sh
OK? (y/n) [y]: y
If you make a mistake, just hit n at the end when it asks if everything's OK. If you find out later you made a mistake, use rmuser to remove the account and create it over again. Something like rmuser fred and hit y at the confirmation prompts.

Test logging in as the user
Start an ssh client and try connecting to the machine where you built the jail, logging in with the account you just made. You should find yourself inside the same jail you were in before when you tested chroot, except you'll start out inside the new user's directory that was just created by adduser.
$ pwd
/r25
$ cd /
$ ls
bin          dev          etc          r25          usr
$
As you can see, we're seeing the same directories that we saw before when we were testing. This is not the real /, despite having done cd /. (pwd is built into sh).


Next, you should try connecting with an sftp client (or do sftp <machine name> from another Unix box if you've got a second one). I was lazy and used the Windows client we expect to use at work. This is from http://www.ssh.com and is the version that's free for non-commercial use (I work for a state university).

This is the root directory as the chrooted user will see it, and shows exactly the same things we can see from a command line when we do an ls after doing cd /.
The GUI SFTP client from ssh.com


WinSCP2 This is a Windows SCP client called WinSCP2, also freeware and with a homepage at http://winscp.vse.cz/eng/

I didn't care about having SCP work, but being compatible with it could come in handy. It worked before I got sftp working, and even better, it gave helpful error messages before it was working that told me what was missing from my jail.

These hints are why there's /etc/group, /bin/groups and /usr/bin/id in my jail. I don't know if they're needed other than for SCP.

SCP is similar to sftp, but not exactly the same thing. See man scp and man sftp for details. It works, and it got chrooted, that's what matters to me.


Are you done? Not yet.
Think about what we've done here. We've added a user and chrooted them when they're connected using ssh, sftp and scp. What about other methods of connecting? The question is what other methods you've got enabled. They can connect via telnet or traditional ftp and look around all they want, if you're running those daemons. You shouldn't be running telnet period, because of the plaintext passwords used for login. Turn off the daemon and get your users to run ssh instead. Freeware clients are available so there's no good excuse for not using it (except possibly that it's not built into Windows).

Traditional FTP is another matter. I haven't seen anything in sftp that's going to replace anonymous FTP for downloading files from some support directory at a company where you don't have an account. I still prefer getting drivers that way over having to use HTTP, although using wget helps. I've also noticed that sftp is significantly slower than FTP when there are slow machines involved because of the CPU overhead for encrypting all that data. Sometimes you don't care about encryption if the material isn't of a sensitive nature. Again, wget comes to the rescue. I just put what I want to download into a subdirectory that Apache can see and download via HTTP. wget has an amazing ability to get the data through, even when your ISP disconnects in the middle of the night and your system has to redial.

For restricting FTP users, there are about 2 options. You can put their usernames in /etc/ftpusers to prevent them from logging in that way at all, or you can put their usernames in /etc/ftpchroot and they'll get chrooted to their home directories when they log in. And yes, of course, FTP also uses unencrypted passwords.

For more options, and for restricting other login possibilities, see man hosts.allow.


File and directory permissions
Actually, the permissions on files and directories ended up pretty much the way I wanted them to, and that was more by accident than by design. I've looked carefully in all the subdirectories on both machines, and I haven't found anything I want to change.

Take a look for yourself:

total 6
drwxr-xr-x  2 root  wheel  512 Aug 18 09:06 bin
drwxr-xr-x  2 root  wheel  512 Aug 18 09:06 dev
drwxr-xr-x  2 root  wheel  512 Aug 18 09:06 etc
drwxr-xr-x  2 r25   r25    512 Aug 18 09:59 r25
drwxr-xr-x  5 root  wheel  512 Aug 18 09:06 usr

./bin:
total 1049
-r-xr-xr-x  1 root  wheel   53248 Aug 18 09:06 cat
-r-xr-xr-x  1 root  wheel   73728 Aug 18 09:06 cp
-r-xr-xr-x  1 root  wheel     116 Aug 18 09:06 groups
-r-xr-xr-x  1 root  wheel  167936 Aug 18 09:06 ls
-r-xr-xr-x  1 root  wheel   53248 Aug 18 09:06 mkdir
-r-xr-xr-x  1 root  wheel  159744 Aug 18 09:06 mv
-r-xr-xr-x  1 root  wheel  163840 Aug 18 09:06 rm
-r-xr-xr-x  1 root  wheel   49152 Aug 18 09:06 rmdir
-r-xr-xr-x  1 root  wheel  307200 Aug 18 09:06 sh

./dev:
total 0
crw-rw-rw-  1 root  wheel   13,   2 Aug 18 09:06 null
crw-rw-rw-  1 root  wheel    1,   0 Aug 18 09:06 tty
crw-rw-rw-  1 root  wheel   13,  12 Aug 18 09:06 zero

./etc:
total 1
-rw-r--r--  1 root  wheel  433 Aug 18 09:06 group

./r25:
total 2
-rw-r--r--  1 r25   r25  131 Aug 18 10:05 .profile
-rw-r--r--  1 root  r25   45 Aug 18 10:06 test.txt

./usr:
total 3
drwxr-xr-x  2 root  wheel  512 Aug 18 09:06 bin
drwxr-xr-x  2 root  wheel  512 Aug 18 09:06 lib
drwxr-xr-x  2 root  wheel  512 Aug 18 09:06 libexec

./usr/bin:
total 12
-r-xr-xr-x  1 root  wheel  12288 Aug 18 09:06 id

./usr/lib:
total 1484
-r--r--r--  1 root  wheel  598961 Aug 18 09:06 libc.so.28.3
-r--r--r--  1 root  wheel  801706 Aug 18 09:06 libcrypto.so.5.1
-r--r--r--  1 root  wheel   38246 Aug 18 09:06 libutil.so.7.1
-r--r--r--  1 root  wheel   55094 Aug 18 09:06 libz.so.1.4

./usr/libexec:
total 88
-r-xr-xr-x  1 root  wheel  61440 Aug 18 09:06 ld.so
-r-xr-xr-x  1 root  wheel  28672 Aug 18 09:06 sftp-server

A script like this is a sort of automated checklist. I kept finding other things I needed to add to the jail, adding them and testing that the program worked, then adding them to the script. Then I'd test the script by making another jail somewhere else. As I mentioned above (somewhere) this script was intended to be both part of this document, and my means of duplicating my jail on the target machine at work.

It's not an accident that I haven't provided any way for users to change their password. In this context, most of the use will be with GUI clients for sftp as pictured above. There isn't any way for them to change passwords with that interface anyway. Also, this same account will be used on both ends: by the group that's putting files in and by the group that's taking them out. None of this data is of a sensitive nature, and we've had bad experiences before with sharing an account between users when one could change the password and not inform the others.

Adding other programs to the jail
There's not all that much to it, you just copy them in. At least if they're statically linked.

(OK, newbie mode again: When you write a program, you can include all of the code needed to make it run (statically linked), or you can call functions in external libraries (dynamically linked). If you include all the code you make the program bigger. It's more efficient space-wise to call external libraries then to generate redundant code. On the other hand, a program's more portable and often runs faster if it's self-contained. The portability can be not just to a chroot jail, but to another machine maybe running a different version of OpenBSD, or maybe even something like FreeBSD with an emulation mode. You can also generally keep using a statically linked program without recompiling it when you upgrade your operating system by a few versions. That's not important for stuff built into the operating system anyway, but if you program and you've written utilities you use regularly you can just copy them and chmod them to be executable again.)
You can find out what programs are statically linked and which are dynamically linked by using ldd:
george# ldd /bin/sh
ldd: /bin/sh: not a dynamic executable
george# ldd /usr/libexec/sftp-server
/usr/libexec/sftp-server:
        -lcrypto.9 => /usr/lib/libcrypto.so.9.0 (0x40022000)
        -lc.29 => /usr/lib/libc.so.29.0 (0x40102000)
george# ldd /usr/local/bin/tcsh
/usr/local/bin/tcsh:
        -ltermlib.9 => /usr/lib/libtermlib.so.9.0 (0x40065000)
        -lc.29 => /usr/lib/libc.so.29.0 (0x400a4000)
george#
As you can see, the standard shell sh is statically linked, the sftp-server and the shell tcsh are dynamically linked. With most programs, if you try hard enough, you can configure them such that you can build a statically linked version. Whether that's worthwhile or not in this case is a good question. As you can see above, both sftp-server and tcsh need libc.so.29.0, and you'll find other common dependancies.

When copying things, I chose to replicate the original paths inside the jail. You might be able to take things from /usr/bin and copy them into /bin in your jail, but doing it this way seemed cleaner somehow.

Other shells
You can use other shells in your jail besides sh, it just takes more work. You need to include any libraries they need for starters. You should specify the alternate shell when you're running adduser, although you can change that later with chsh. I didn't seem to need an /etc/shells file, but if you're using something other than /bin/sh you probably will. Almost certainly adduser will complain unless you have the shell in place and add it to /etc/shells before you add a user that's using it.

Now you're done, at least I am.


A concise set of instructions, for the impatient. These were actually my notes to myself for doing the 2nd install:

 1. make the chrooted parent directory where it should be (I'm using /drv2/chrooted)
 2. cd /usr/src/usr.bin
 3. tar -cvf ssh_removed.tar ssh/*
 4. rm -R ssh
 5. wget ftp://ftp.openbsd.org/pub/OpenBSD/OpenSSH/openssh-3.6.1.tgz (or whatever the latest is)
 6. gunzip openssh-3.6.1.tgz
 7. tar -xvf openssh-3.6.1.tar
 8. cd ssh
 9. manually make the changes in session.c (or patch?)
10. make obj
11. make cleandir
12. make depend
13. make
14. from a physical console kill the sshd and delete it (/usr/sbin/sshd)
15. cd back to /usr/src/usr.bin/ssh and make install
16. restart the sshd daemon
17. test ssh login as yourself, go back to comfortable seat
18. edit /etc/adduser.conf so default home is "/drv2/chrooted/." (change back later)
19. Add the new chrooted user with adduser
20. cd into /drv2/chrooted
21. copy the makejail.sh script into /drv2/chrooted/, run it and delete it
22. cd into new user's directory
23. correct paths in user's dotfiles to remove non-existant ones
24. test by logging in from ssh as the user
25. test by using an sftp client as the user
26. optionally test by using a scp client (ie WinSCP2) as the user
27. if you want to add more chrooted users, do it now, then change /etc/adduser.conf back
28. remember that this chroot only applies through sshd.  Other daemons (ftpd, telnetd) will not chroot them.

A patch, for those that insist on one
Patches are fragile, and they often break when the version of the code being patched changes. This is a patch against session.c in openssh-3.6.1.tgz, the version that's specifically for OpenBSD.


*** session_unmodified.c	Wed Mar  5 17:33:43 2003
--- session.c	Mon Aug 18 08:54:20 2003
***************
*** 1018,1027 ****
--- 1018,1053 ----
  	}
  }

+ /* I've isolated the part that does the chroot and put it in its own function */
+ void
+ do_chroot(struct passwd *pw){
+   char *user_dir;
+   char *new_root;
+
+   user_dir = xstrdup(pw->pw_dir);
+   new_root = user_dir + 1;
+   while((new_root = strchr(new_root, '.')) != NULL) {
+   	new_root--;
+   	if(strncmp(new_root, "/./", 3) == 0) {
+   	*new_root = '\0';
+   	new_root += 2;
+
+   	if(chroot(user_dir) != 0)
+   		fatal("Couldn't chroot to user directory %s", user_dir);
+   		pw->pw_dir = new_root;
+   		break;
+   	}
+   	new_root += 2;
+   }
+ } /* do_chroot */
+
+
+
  /* Set login name, uid, gid, and groups. */
  void
  do_setusercontext(struct passwd *pw)
  {
+   do_chroot(pw); /* insertion of this is the only change in the original code */
  	if (getuid() == 0 || geteuid() == 0) {
  #ifdef HAVE_LOGIN_CAP
  		if (setusercontext(lc, pw, pw->pw_uid,

The End
That's the end of my saga. I hope what I learned and put here will be helpful to someone else. I wish I could put an email address here for contacting me, but really what I've put on this page is about all I know about the subject, and I'm approaching 200 spam emails per day now so I'm not putting my email address in any more places. I am subscribed to the mailing list at http://chrootssh.sourceforge.net with one email account and check that every few days.

Oh, and a thank you to all who helped, both through the mailing list and through comp.unix.bsd.openbsd.misc

Alan Corey
counter Valid HTML 4.0!