Hi everyone I would like to enable unprivileged users to share only certain directories using SFTP without acquiring root, without setting capabilities using public-key-based forced commands. In another use case unprivileged users could write scripts that evaluate "$SSH_ORIGINAL_COMMAND" and then either execute sftp-server in a jail "$SSH_ORIGINAL_COMMAND" after "review" (e.g. matches a certain regexp) and fortification. External users would access the system in a user-defined restricted way to: upload data, trigger processing of data, download processed data. I created a patch against the debian sid distribution of OpenSSH 6.4-p1-1 "sftp-server.c" that introduces a command line option "-j" (original file and patch attached) It's not yet in a fully polished, robust and tested state, but I think at least it is serves as PoC. Would you accept such a patch if finalized? Do you have any recommendations - regarding my patch? - to what I would like to achieve? (basic C programming hints welcome too, I usually don't code in C, first serious use) Thank you & Kind Regards Dimitri -------------- next part -------------- 75a76,78 > /* Restrict access to this directory */ > char* jail; > 85a89,213 > /* Concatenate 2 path parts in a way that one doesn't need to care about leading/trailing slashes. Returned pointer can be freed. */ > static char* concat_path(char* parent, char* child) { > size_t parent_len = strlen(parent); > if (parent_len < 1) > return xstrdup(child); > size_t child_len = strlen(child); > if (child_len < 1) > return xstrdup(parent); > > if (*child == '/') { > child++; > child_len--; > } > > char* cat; > if (*(parent + parent_len - 1) == '/') { > size_t cat_len = sizeof(char) * (parent_len + child_len + 1); > cat = xmalloc(sizeof(char) * cat_len); > *cat = '\0'; > strlcat(cat,parent,cat_len); > strlcat(cat,child,cat_len); > return cat; > } else { > size_t cat_len = sizeof(char) * (parent_len + child_len + 2); > cat = xmalloc(sizeof(char) * cat_len); > *cat = '\0'; > strlcat(cat,parent,cat_len); > strlcat(cat,"/",cat_len); > strlcat(cat,child,cat_len); > return cat; > } > } > > /* shorten the path by removing occurences of "//" and any relative (backward) directory references (".", ".."), ignoring backward references > * that traverse the current directory or root, maintaining leading "/" and maintaining trailing "/" if possible. > * The passed string is modified. > */ > static char* shorten_path(char* path) { > unsigned int path_len = strlen(path)+1; > //char* short_path = path; malloc(sizeof(char) * short_path_len); > size_t j = 0; > size_t i = 0; > //Make sure path doesn't contain any "//" > for (i = 0; i < path_len; i++) { > if (*(path+i) == '/' && j>0 && *(path+j-1) == '/') > j--; > > *(path+j) = *(path+i); > j++; > } > > j = 0; > int dot_count = 0; > i = 0; > path_len = strlen(path)+1; > char* real_path = path; > if (*path == '/') { > path++; > path_len--; > } > > // Execute actual ".." resolution with a path that doesn't care if it is absolute or relative > for (i = 0; i < path_len; i++) { > if (*(path+i) == '/' || *(path+i) == '\0') { > if (dot_count == 2) { > while (j>0 && *(path+j-1) != '/') { > j--; > } > if (j>0) j--; > while (j>0 && *(path+j-1) != '/') { > j--; > } > if (j<1 && *(path+i) == '/') i++; > } > else if (dot_count == 1) { > while (j>0 && *(path+j-1) != '/') { > j--; > } > if (j<1 && *(path+i) == '/') i++; > } > dot_count = *(path+i) == '.' ? 1 : 0; > } > else if (*(path+i) == '.') dot_count++; > else dot_count = 3; > > if (*(path+i) == '/' && j>0 && *(path+j-1) == '/') > j--; > > *(path+j) = *(path+i); > j++; > } > return real_path; > } > /* Takes a path from jail perspective and converts it to the actual path. This function frees path (so expects a freeable pointer) or reuses it so in any case returns a freeable pointer. */ > static char* jail_to_actual(char* path) { > if (jail == NULL) return path; > char* shortened = shorten_path(path); > char* translated = concat_path(jail,shortened); > free(shortened); > return translated; > } > /* The opposite of jail_to_actual, returns NULL if path is not in jail, This function frees path (so expects a freeable pointer) or reuses it so in any case returns a freeable pointer. */ > static char* actual_to_jail(char* path) { > if (jail == NULL) return path; > unsigned int jail_len = strlen(jail); > if (strncmp(jail,path,jail_len) != 0) return NULL; > char* actual = xstrdup(path+jail_len); > free(path); > if (strlen(actual) < 1) { > free(actual); > actual = xstrdup("/"); > } > return actual; > } > /* Removes trailing slashes, except a leading one, modifies the passed string */ > static void rtrim_slash(char* path) { > unsigned int len = strlen(path); > int i; > for (i = len-1; i > 0; i--) { > if (*(path+i) == '/') > *(path+i) = '\0'; > else break; > } > } > 523d650 < 552d678 < 554a681,696 > name = jail_to_actual(name); > if (jail != NULL) { > char resolvedname[MAXPATHLEN]; > if (realpath(name, resolvedname) == NULL) { > send_status(id, errno_to_portable(errno)); > free(name); > return; > } > char* jailed_resolvedname = actual_to_jail(xstrdup(resolvedname)); > if (jailed_resolvedname == NULL) { > send_status(id,SSH2_FX_FAILURE); > free(name); > return; > } > > } 589d730 < 695a837 > name = jail_to_actual(name); 771a914 > name = jail_to_actual(name); 889a1033,1048 > path = jail_to_actual(path); > if (jail != NULL) { > char resolvedname[MAXPATHLEN]; > if (realpath(path, resolvedname) == NULL) { > send_status(id, errno_to_portable(errno)); > free(path); > return; > } > char* jailed_resolvedname = actual_to_jail(xstrdup(resolvedname)); > if (jailed_resolvedname == NULL) { > send_status(id,SSH2_FX_FAILURE); > free(path); > return; > } > > } 975a1135 > name = jail_to_actual(name); 997a1158 > name = jail_to_actual(name); 1021a1183 > name = jail_to_actual(name); 1042a1205 > path = jail_to_actual(path); 1054,1055c1217,1233 < s.name = s.long_name = resolvedname; < send_names(id, 1, &s); --- > if (jail != NULL) { > char* jailed_resolvedname = actual_to_jail(xstrdup(resolvedname)); > /* Note that only the resolved string needs to point inside the jail. During resolution it may visit links outside jail > * The SFTP-user, however, is not able to create links to the outside anyway. > */ > if (jailed_resolvedname == NULL) send_status(id,SSH2_FX_FAILURE); > else { > s.name = s.long_name = jailed_resolvedname; > send_names(id, 1, &s); > } > free(jailed_resolvedname); > } > else { > s.name = s.long_name = resolvedname; > send_names(id, 1, &s); > } > 1070a1249,1250 > oldpath = jail_to_actual(oldpath); > newpath = jail_to_actual(newpath); 1131a1312 > path = jail_to_actual(path); 1156a1338,1339 > oldpath = jail_to_actual(oldpath); > newpath = jail_to_actual(newpath); 1178a1362,1363 > oldpath = jail_to_actual(oldpath); > newpath = jail_to_actual(newpath); 1198a1384 > path = jail_to_actual(path); 1235a1422,1423 > oldpath = jail_to_actual(oldpath); > newpath = jail_to_actual(newpath); 1406a1595 > jail = NULL; 1414d1602 < 1417c1605 < while (!skipargs && (ch = getopt(argc, argv, "d:f:l:u:cehR")) != -1) { --- > while (!skipargs && (ch = getopt(argc, argv, "d:f:l:u:j:cehR")) != -1) { 1455a1644,1657 > case 'j': > if (strlen(optarg) > 0 && *optarg == '/') > jail = xstrdup(optarg); > else { > char* wd = get_current_dir_name(); > jail = concat_path(wd,optarg); > free(wd); > } > rtrim_slash(jail); > if (*jail == '\0' || (strlen(jail) == 1 && *jail == '/' )) { > jail = NULL; > break; > } > break; 1498c1700 < --- > 1500,1501c1702,1709 < if (chdir(homedir) != 0) { < error("chdir to \"%s\" failed: %s", homedir, --- > char* jailed_homedir; > if (jail != NULL) { > jailed_homedir = concat_path(jail,homedir); > } > else > jailed_homedir = xstrdup(homedir); > if (chdir(jailed_homedir) != 0) { > error("chdir to \"%s\" failed: %s", jailed_homedir, 1503a1712 > free(jailed_homedir); -------------- next part -------------- A non-text attachment was scrubbed... Name: sftp-server.c Type: text/x-csrc Size: 33990 bytes Desc: not available URL: <http://lists.mindrot.org/pipermail/openssh-unix-dev/attachments/20140101/8703378a/attachment-0001.bin>