ftp-server patch - restrict user to directory

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....


Go Back   Usenet Forums > Networking and Network Related > OpenSSH Development

FAQ Members List Calendar Search Today's Posts Mark Forums Read
  #1 (permalink)  
Old 11-11-2007
Alain Williams
 
Posts: n/a
Default ftp-server patch - restrict user to directory


--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--
Reply With Quote
Reply
Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are Off
[IMG] code is Off
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are On



All times are GMT +1. The time now is 10:12 AM.


Powered by vBulletin® Version 3.7.3
Copyright ©2000 - 2008, Jelsoft Enterprises Ltd.
Content Relevant URLs by vBSEO 3.0.0