This is a discussion on ftp-server patch - restrict user to directory within the OpenSSH Development forums, part of the Networking and Network Related category; --DKU6Jbt7q3WqK7+M Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Hi, please find a patch against openssh-4....
|
|||||||
| FAQ | Members List | Calendar | Search | Today's Posts | Mark Forums Read |
|
|||
|
--DKU6Jbt7q3WqK7+M Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Hi, please find a patch against openssh-4.7p1 This patch: 1) Allows for an optional configuration file 2) Allows a user to be restricted to a directory and it's children. Enjoy -- Alain Williams Linux Consultant - Mail systems, Web sites, Networking, Programmer, IT Lecturer. +44 (0) 787 668 0256 http://www.phcomp.co.uk/ Parliament Hill Computers Ltd. Registration Information: http://www.phcomp.co.uk/contact.php Chairman of UKUUG: http://www.ukuug.org/ #include <std_disclaimer.h> --DKU6Jbt7q3WqK7+M Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="sftp-server.patch" --- sftp-server.c.orig 2007-05-20 06:09:05.000000000 +0100 +++ sftp-server.c 2007-11-11 23:25:29.000000000 +0000 @@ -31,7 +31,6 @@ #include <stdlib.h> #include <stdio.h> #include <string.h> -#include <pwd.h> #include <time.h> #include <unistd.h> #include <stdarg.h> @@ -44,6 +43,7 @@ #include "sftp.h" #include "sftp-common.h" +#include "pathnames.h" /* helper */ #define get_int64() buffer_get_int64(&iqueue); @@ -74,6 +74,141 @@ Attrib attrib; }; +/* Name of the server configuration file. */ +char *config_file_name = _PATH_SFTP_CONFIG_FILE; + +/* If not NULL restrict the user to under this directory */ +char* RestrictDirectory; + +/* **** Start parsing config file code **** */ + +/* Read the optional config file. If mandatory the file must exist. + * Exit on error. + */ +static void +load_sftp_config(char* file_name, int mandatory) +{ + char line[1024], *cp, *arg, *opt; + FILE *f; + int lineno = 0; + + debug2("%s: filename %s mandatory %d", __func__, file_name, mandatory); + if ((f = fopen(file_name, "r")) == NULL) { + if( ! mandatory) /* config file may be optional */ + return; + + perror(file_name); + exit(1); + } + + /* Read a line at a time */ + while(fgets(line, sizeof(line), f)) { + lineno++; + cp = line + strspn(line, " \t"); + if(*cp == '#' || *cp == '\0' || *cp == '\n') /* Ignore comments and empty lines */ + continue; + if( ! (arg = strchr(cp, '\n'))) + fatal("Line %d in file %.100s too long", lineno, file_name); + *arg = '\0'; + + opt = strdelim(&cp); /* Get directive */ + debug3("opt='%s' rest='%s'\n", opt, cp); + + /* Find what argument we have */ + if( ! strcmp(opt, "RestrictDirectory")) { + RestrictDirectory = xstrdup(strdelim(&cp)); /* Get pathname */ + if(cp) + fatal("Line %d in file %.100s: too many arguments to %s", lineno, file_name, opt); + } else + fatal("Line %d in file %.100s: unknown directive: %.100s", lineno, file_name, opt); + } + + /* All done */ + fclose(f); + debug2("%s: done config, lines %d", __func__, lineno); +} + +/* **** End parsing config file code **** */ + +/* Set up options once we know who the user is. + * Look at pw for details. + */ +static void +set_user_opts(void) +{ + /* Does the restict directory start "~/xxx" or have the value "~" ? + * If so relace with the user's home directory. + * Don't attempt to expand arbitrary "~user/". + * If a directory has a trailing "/" then listings of that directory will not be allowed. + */ + if(RestrictDirectory && RestrictDirectory[0] == '~' && + (RestrictDirectory[1] == '/' || RestrictDirectory[1] == '\0')) { + char* tmp = xmalloc(strlen(RestrictDirectory) + strlen(pw->pw_dir)); + strcat(strcpy(tmp, pw->pw_dir), RestrictDirectory + 1); + free(RestrictDirectory); + RestrictDirectory = tmp; + } + + /* It is possible, if unlikely, that the restricted directory will have been specified with + * a symlink or .. in it. That will totally blow comparisions in allowed_access(). Resolve this. + */ + if(RestrictDirectory) { + char resolvedname[MAXPATHLEN]; + char* tmp; + + if( ! realpath(RestrictDirectory, resolvedname)) + fatal("Can't get realpath on %.100s as: %s", RestrictDirectory, strerror(errno)); + + tmp = xstrdup(resolvedname); + free(RestrictDirectory); + RestrictDirectory = tmp; + } +} + +/* Check that the user is allowed to access the path for the purpose reason. + * This implements the RestrictDirectory option. + * Return true if allowed. + * Log failed access attempts. + */ +static int +allowed_access(char* path, char* reason) +{ + char resolvedname[MAXPATHLEN]; + int restlen; + + if( ! RestrictDirectory) /* No restriction, allow */ + return(1); + + /* If we can't convert the name - deem it an error */ + if( ! realpath(path, resolvedname)) { + error("Can't get realpath on %.100s as: %s", path, strerror(errno)); + return(0); + } + + restlen = strlen(RestrictDirectory); + + /* Allow the directory itself. + * Trap where the canny user with a '~/' restriction tries to list '/home/joe/' - ie adds the '/'. + */ + if( ! strcmp(resolvedname, RestrictDirectory) && RestrictDirectory[restlen - 1] != '/') + return(1); + + /* Find length before '/' of restricting directory */ + if(RestrictDirectory[restlen - 1] == '/') + restlen--; + + /* If the first bit matches it is acceptable. + * Check for a '/' else ~fred will allow access to ~freddy. + */ + if( ! strncmp(resolvedname, RestrictDirectory, restlen) && + resolvedname[restlen] == '/') + return(1); + + error("Restricted access, %s disallowed for %.100s", reason, path); + + return(0); +} + static int errno_to_portable(int unixerrno) { @@ -813,20 +948,24 @@ id = get_int(); path = get_string(NULL); debug3("request %u: opendir", id); - logit("opendir \"%s\"", path); - dirp = opendir(path); - if (dirp == NULL) { - status = errno_to_portable(errno); - } else { - handle = handle_new(HANDLE_DIR, path, 0, dirp); - if (handle < 0) { - closedir(dirp); + + if(allowed_access(path, "opendir")) { /* RestrictDirectory ? */ + logit("opendir \"%s\"", path); + dirp = opendir(path); + if (dirp == NULL) { + status = errno_to_portable(errno); } else { - send_handle(id, handle); - status = SSH2_FX_OK; + handle = handle_new(HANDLE_DIR, path, 0, dirp); + if (handle < 0) { + closedir(dirp); + } else { + send_handle(id, handle); + status = SSH2_FX_OK; + } } + } else + status = errno_to_portable(EPERM); - } if (status != SSH2_FX_OK) send_status(id, status); xfree(path); @@ -899,9 +1038,14 @@ id = get_int(); name = get_string(NULL); debug3("request %u: remove", id); - logit("remove name \"%s\"", name); - ret = unlink(name); - status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; + + if(allowed_access(name, "remove")) { /* RestrictDirectory ? */ + logit("remove name \"%s\"", name); + ret = unlink(name); + status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; + } else + status = errno_to_portable(EPERM); + send_status(id, status); xfree(name); } @@ -920,9 +1064,13 @@ mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? a->perm & 0777 : 0777; debug3("request %u: mkdir", id); - logit("mkdir name \"%s\" mode 0%o", name, mode); - ret = mkdir(name, mode); - status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; + if(allowed_access(name, "mkdir")) { /* RestrictDirectory ? */ + logit("mkdir name \"%s\" mode 0%o", name, mode); + ret = mkdir(name, mode); + status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; + } else + status = errno_to_portable(EPERM); + send_status(id, status); xfree(name); } @@ -937,9 +1085,13 @@ id = get_int(); name = get_string(NULL); debug3("request %u: rmdir", id); - logit("rmdir name \"%s\"", name); - ret = rmdir(name); - status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; + if(allowed_access(name, "rmdir")) { /* RestrictDirectory ? */ + logit("rmdir name \"%s\"", name); + ret = rmdir(name); + status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; + } else + status = errno_to_portable(EPERM); + send_status(id, status); xfree(name); } @@ -950,6 +1102,7 @@ char resolvedname[MAXPATHLEN]; u_int32_t id; char *path; + static int num_realpaths = 0; id = get_int(); path = get_string(NULL); @@ -958,15 +1111,24 @@ path = xstrdup("."); } debug3("request %u: realpath", id); - verbose("realpath \"%s\"", path); - if (realpath(path, resolvedname) == NULL) { - send_status(id, errno_to_portable(errno)); - } else { - Stat s; - attrib_clear(&s.attrib); - s.name = s.long_name = resolvedname; - send_names(id, 1, &s); - } + + /* RestrictDirectory ? - could be used to probe the file system. + * Need to allow on realpath since that is done automatically to get the $HOME. + * The user must then cd <whereever> before s/he can do anything else. + */ + if(num_realpaths++ == 0 || allowed_access(path, "realpath")) { + verbose("realpath \"%s\"", path); + if (realpath(path, resolvedname) == NULL) { + send_status(id, errno_to_portable(errno)); + } else { + Stat s; + attrib_clear(&s.attrib); + s.name = s.long_name = resolvedname; + send_names(id, 1, &s); + } + } else + send_status(id, errno_to_portable(EPERM)); + xfree(path); } @@ -982,6 +1144,14 @@ oldpath = get_string(NULL); newpath = get_string(NULL); debug3("request %u: rename", id); + + if( ! allowed_access(oldpath, "rename") || ! allowed_access(newpath, "rename")) { /* RestrictDirectory ? */ + send_status(id, errno_to_portable(EPERM)); + xfree(oldpath); + xfree(newpath); + return; + } + logit("rename old \"%s\" new \"%s\"", oldpath, newpath); status = SSH2_FX_FAILURE; if (lstat(oldpath, &sb) == -1) @@ -1038,17 +1208,22 @@ id = get_int(); path = get_string(NULL); debug3("request %u: readlink", id); - verbose("readlink \"%s\"", path); - if ((len = readlink(path, buf, sizeof(buf) - 1)) == -1) - send_status(id, errno_to_portable(errno)); - else { - Stat s; - - buf[len] = '\0'; - attrib_clear(&s.attrib); - s.name = s.long_name = buf; - send_names(id, 1, &s); - } + + if(allowed_access(path, "readlink")) { /* RestrictDirectory ? */ + verbose("readlink \"%s\"", path); + if ((len = readlink(path, buf, sizeof(buf) - 1)) == -1) + send_status(id, errno_to_portable(errno)); + else { + Stat s; + + buf[len] = '\0'; + attrib_clear(&s.attrib); + s.name = s.long_name = buf; + send_names(id, 1, &s); + } + } else + send_status(id, errno_to_portable(EPERM)); + xfree(path); } @@ -1063,10 +1238,15 @@ oldpath = get_string(NULL); newpath = get_string(NULL); debug3("request %u: symlink", id); - logit("symlink old \"%s\" new \"%s\"", oldpath, newpath); - /* this will fail if 'newpath' exists */ - ret = symlink(oldpath, newpath); - status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; + + if( ! allowed_access(oldpath, "symlink") || ! allowed_access(newpath, "symlink")) { /* RestrictDirectory ? */ + logit("symlink old \"%s\" new \"%s\"", oldpath, newpath); + /* this will fail if 'newpath' exists */ + ret = symlink(oldpath, newpath); + status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; + } else + status = errno_to_portable(EPERM); + send_status(id, status); xfree(oldpath); xfree(newpath); @@ -1203,7 +1383,7 @@ extern char *__progname; fprintf(stderr, - "usage: %s [-he] [-l log_level] [-f log_facility]\n", __progname); + "usage: %s [-he] [-l log_level] [-F config_file] [-f log_facility]\n", __progname); exit(1); } @@ -1215,6 +1395,7 @@ ssize_t len, olen, set_size; SyslogFacility log_facility = SYSLOG_FACILITY_AUTH; char *cp, buf[4*4096]; + int config_file_mandatory = 0; extern char *optarg; extern char *__progname; @@ -1225,7 +1406,7 @@ __progname = ssh_get_progname(argv[0]); log_init(__progname, log_level, log_facility, log_stderr); - while (!skipargs && (ch = getopt(argc, argv, "C:f:l:che")) != -1) { + while (!skipargs && (ch = getopt(argc, argv, "C:f:F:l:che")) != -1) { switch (ch) { case 'c': /* @@ -1247,12 +1428,18 @@ if (log_level == SYSLOG_FACILITY_NOT_SET) error("Invalid log facility \"%s\"", optarg); break; + case 'F': + config_file_name = optarg; + config_file_mandatory = 1; /* Since it is specified, it is a must */ + break; case 'h': default: usage(); } } + load_sftp_config(config_file_name, config_file_mandatory); + log_init(__progname, log_level, log_facility, log_stderr); if ((cp = getenv("SSH_CONNECTION")) != NULL) { @@ -1271,6 +1458,8 @@ logit("session opened for local user %s from [%s]", pw->pw_name, client_addr); + set_user_opts(); + handle_init(); in = dup(STDIN_FILENO); --- sftp-server.8.orig 2007-06-05 09:27:13.000000000 +0100 +++ sftp-server.8 2007-11-11 23:15:51.000000000 +0000 @@ -60,6 +60,12 @@ The possible values are: DAEMON, USER, AUTH, LOCAL0, LOCAL1, LOCAL2, LOCAL3, LOCAL4, LOCAL5, LOCAL6, LOCAL7. The default is AUTH. +.It Fl F Ar configfile +Specifies a configuration file that shall be used, if this file is not found +a fatal error is generated. +The defualt file is +.Xr /etc/ssh/sftp_config +if this file is not found no error is generated. .It Fl l Ar log_level Specifies which messages will be logged by .Nm . @@ -72,6 +78,27 @@ DEBUG2 and DEBUG3 each specify higher levels of debugging output. The default is ERROR. .El +.Sh CONFIGURATION FILE FORMAT +The file contains keyword-argument pairs, one per line. +Lines starting with +.Ql # +and empty lines are interpreted as comments. +Arguments may optionally be enclosed in double quotes +.Pq \&" +in order to represent arguments containing spaces. +.Bl -tag -width Ds +.It Cm RestrictDirectory +Restrict the user's activities to the argument directory and the directories below it. +If the directory is +.Ql ~ +the user's home directory is used. +Expansion of arbitrary +.Ql ~user/ +is not done. +If a directory has a trailing +.Ql / +then listings of that directory will not be allowed. +.El .Sh SEE ALSO .Xr sftp 1 , .Xr ssh 1 , --DKU6Jbt7q3WqK7+M Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline _______________________________________________ openssh-unix-dev mailing list openssh-unix-dev@mindrot.org https://lists.mindrot.org/mailman/li...enssh-unix-dev --DKU6Jbt7q3WqK7+M-- |