SSH, limiting to SCP or Rsync only

From FreeBSDwiki
Jump to: navigation, search

Fairly commonly, you may want to set something up so that a user can scp or rsync files to (or from) a server of yours, but you don't want to allow them a shell account. That's not ENTIRELY possible, but you can manage something along those lines by either creating a jail for them, or, more simply, by forcing them to use a very very neutered custom shell when they log in that will only allow them access to the commands that you want them to be able to use.

The general idea is to create a custom shell for user accounts that you want to be able to use scp, sftp, or rsync with SSH transport, but not to have an actual shell available. Remember that this limits "snoopiness" to some degree but is NOT any kind of hardcore lockout, as any files or directories that the user has read permissions on can be scp'ed or rsync'ed over to their machine for local perusal, and any which they have write permissions on can be OVERWRITTEN with versions scp'ed or rsync'ed over FROM their machine!

Note also that scpsftprsynconly, as written, wouldn't prevent a belligerent user from using the -S argument to scp to execute an arbitrary script or program instead of ssh. An enterprising soul could of course modify the code to prevent the use of a -S argument; the main reason I haven't bothered is, well, you're kidding yourself if you think either of these are secure ideas anyway - they're popular, and they'll keep clueless lusers in line, but they won't deter determined and knowledgeable types for long. (I don't know off the top of my head if the scponly shell is also vulnerable to this particular exploit - if somebody wants to go over its source and report back here, that would be nice.)


scponly is open-source software that offers this functionality. It is available through the ports collection under /usr/ports/shells/scponly.


An alternative is to use the small C program at the bottom of this article. Once you've saved the code to a work directory as scpsftprsynconly.c, you can compile it and assign it as a user's shell with pw or chsh:

ph34r# gcc scpsftprsynconly.c -o /usr/local/bin/scpsftprsynconly
ph34r# pw usermod dave -s /usr/local/bin/scpsftprsynconly

Now user dave can use scp or sftp (if sftp is set up) or rsync (if rsync is available) commands with ssh, but cannot actually log into the box - remotely OR locally I might add! Here's a test showing ssh and local logins failing, but an scp succeeding:

ph34r# ssh dave@localhost
This account is currently not available.
ph34r# su dave
This account is currently not available.
ph34r# scp dave@localhost:/usr/local/bin/scpsftprsynconly .
scpsftprsynconly                               100%  130KB 129.7KB/s   00:00

And there we have it - it works. Note that you might want to actually LOOK at the code; it's assuming that rsync and scp will be findable via the system's PATH environment variable. If that's not the case, or if you're feeling a little extra-paranoid and want to hard-code in assurance that ONLY the particular executable you want - say, /usr/local/bin/rsync - can be run, you might want to hack that in the way you want it; just keep in mind that if your custom shell is looking to allow just rsync, it WON'T allow /usr/local/bin/rsync and vice versa. So you might wind up having to mess about with flags for your scp and rsync commands from remote machines to specify paths, if you screw around with that stuff.


 #include <unistd.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
 **  Original by Patric Draper <>
 **  Changes on 13-Mar-2004 by Msquared <>
 **    * fixed bug in args to realloc()
 **    * fixed bug in parameter checks (validates entire command name)
 **    * Modified to work with OpenSSH SFTP
 **    * Added rsync support
 **  This code is in the public domain.  No warranty.  If it breaks,
 **  you can dispose of it as you see fit.
 **  Build with DEBUG to save calling arguments to /tmp/scpshell.log
 **  This is useful to add new protocols, debug existing calls, etc.
 char * restrictmsg = "This account is currently not available.\n";
 int main (int argc, char *argv []) {
         char **newargs = NULL;
         char *newbuff = NULL;
         int i;
         char *s;
 #ifdef DEBUG
         FILE * log = fopen("/tmp/scpshell.log","a+");
         if ( log ) {
                 char **par = argv;
                 while ( *par )
                         fprintf ( log, "[%s] ", *par++ );
                 fprintf ( log, "\n" );
         if (argc < 3) {
                 printf (restrictmsg);
                 return 1;
         if ((strncmp (argv [2], "scp ", 4) != 0) &&
             (strncmp (argv [2], "/usr/libexec/openssh/sftp-server", 33) != 0) &&
             (strncmp (argv [2], "rsync ", 6) != 0)) {
                 printf (restrictmsg);
                 return 2;
         i = 0;
         newbuff = strdup(argv[2]);
         s = strtok (newbuff, " ");
         do {
                 newargs = (char **) realloc (newargs, ++i*sizeof(*newargs));
                 newargs [i - 1] = strdup (s);
         } while ((s = strtok (NULL, " ")) != NULL);
         newargs = (char **) realloc (newargs, ++i*sizeof(*newargs));
         newargs [i - 1] = NULL;
         execvp (newargs [0], newargs);
         return 0;
Personal tools