From 301cc5218b36cb6f15602191eb2394877cdc930c Mon Sep 17 00:00:00 2001 From: Mike Kelly Date: Fri, 16 Dec 2011 14:52:21 -0500 Subject: Add -o idmap=file, -o uidmap=FILE, -o gidmap=FILE These options allow you to create a pair of local files, with username:uid/groupname:gid pairs, one per line. Alternatively, files can be in standard /etc/passwd / /etc/group format. The uid/gids are for the remote server, their local counterparts are looked up with a local getpwnam/getgrnam call. Any stat() calls will show with the remapped local uid/gid, and any chown() calls will be remapped back to the remote uid/gid. --- ChangeLog | 7 ++ sshfs.1 | 12 ++++ sshfs.c | 223 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 240 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 9b3fb9c..2fa431e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2011-12-16 Mike Kelly + + * Add -o idmap=file, -o uidmap=FILE, -o gidmap=FILE. These options + allow you to create a pair of local files, similar to /etc/passwd or + /etc/group files from the remote server, and use those to remap all + the given UIDs/GIDs. + 2011-11-25 Miklos Szeredi * Make chown respect the UID mapping policy. Reported and tested diff --git a/sshfs.1 b/sshfs.1 index d103ad3..9dcd8aa 100644 --- a/sshfs.1 +++ b/sshfs.1 @@ -102,6 +102,18 @@ no translation of the ID space (default) .TP user only translate UID of connecting user +.TP +file +translate UIDs/GIDs based upon the contents of \fBuidfile \fR and +\fBgidfile\fR +.RE +.TP +\fB\-o\fR uidfile=FILE +file containing username:uid mappings for \fBidmap=file\fR +.RE +.TP +\fB\-o\fR gidfile=FILE +file containing groupname:gid mappings for \fBidmap=file\fR .RE .TP \fB\-o\fR ssh_command=CMD diff --git a/sshfs.c b/sshfs.c index d0517ff..bf7e956 100644 --- a/sshfs.c +++ b/sshfs.c @@ -35,6 +35,8 @@ #include #include #include +#include +#include #include "cache.h" @@ -198,6 +200,13 @@ struct sshfs { int follow_symlinks; int no_check_root; int detect_uid; + int idmap; + char *uid_file; + char *gid_file; + GHashTable *uid_map; + GHashTable *gid_map; + GHashTable *r_uid_map; + GHashTable *r_gid_map; unsigned max_read; unsigned max_write; unsigned ssh_ver; @@ -306,6 +315,12 @@ enum { KEY_CONFIGFILE, }; +enum { + IDMAP_NONE, + IDMAP_USER, + IDMAP_FILE, +}; + #define SSHFS_OPT(t, p, v) { t, offsetof(struct sshfs, p), v } static struct fuse_opt sshfs_opts[] = { @@ -317,8 +332,11 @@ static struct fuse_opt sshfs_opts[] = { SSHFS_OPT("ssh_protocol=%u", ssh_ver, 0), SSHFS_OPT("-1", ssh_ver, 1), SSHFS_OPT("workaround=%s", workarounds, 0), - SSHFS_OPT("idmap=none", detect_uid, 0), - SSHFS_OPT("idmap=user", detect_uid, 1), + SSHFS_OPT("idmap=none", idmap, IDMAP_NONE), + SSHFS_OPT("idmap=user", idmap, IDMAP_USER), + SSHFS_OPT("idmap=file", idmap, IDMAP_FILE), + SSHFS_OPT("uidfile=%s", uid_file, 0), + SSHFS_OPT("gidfile=%s", gid_file, 0), SSHFS_OPT("sshfs_sync", sync_write, 1), SSHFS_OPT("no_readahead", sync_read, 1), SSHFS_OPT("sshfs_debug", debug, 1), @@ -444,6 +462,15 @@ static int list_empty(const struct list_head *head) return head->next == head; } +/* given a pointer to the uid/gid, and the mapping table, remap the + * uid/gid, if necessary */ +static inline void translate_id(uint32_t *id, GHashTable *map) +{ + gpointer id_p; + if (g_hash_table_lookup_extended(map, GUINT_TO_POINTER(*id), NULL, &id_p)) + *id = GPOINTER_TO_UINT(id_p); +} + static inline void buf_init(struct buffer *buf, size_t size) { if (size) { @@ -681,6 +708,10 @@ static int buf_get_attrs(struct buffer *buf, struct stat *stbuf, int *flagsp) if (sshfs.remote_uid_detected && uid == sshfs.remote_uid) uid = sshfs.local_uid; + if (sshfs.idmap == IDMAP_FILE && sshfs.uid_map) + translate_id(&uid, sshfs.uid_map); + if (sshfs.idmap == IDMAP_FILE && sshfs.gid_map) + translate_id(&gid, sshfs.gid_map); memset(stbuf, 0, sizeof(struct stat)); stbuf->st_mode = mode; @@ -2158,6 +2189,10 @@ static int sshfs_chown(const char *path, uid_t uid, gid_t gid) if (sshfs.remote_uid_detected && uid == sshfs.local_uid) uid = sshfs.remote_uid; + if (sshfs.idmap == IDMAP_FILE && sshfs.r_uid_map) + translate_id(&uid, sshfs.r_uid_map); + if (sshfs.idmap == IDMAP_FILE && sshfs.r_gid_map) + translate_id(&gid, sshfs.r_gid_map); buf_init(&buf, 0); buf_add_path(&buf, path); @@ -3105,6 +3140,9 @@ static void usage(const char *progname) " -o idmap=TYPE user/group ID mapping, possible types are:\n" " none no translation of the ID space (default)\n" " user only translate UID of connecting user\n" +" file translate UIDs/GIDs contained in uidfile/gidfile\n" +" -o uidfile=FILE file containing username:remote_uid mappings\n" +" -o gidfile=FILE file containing groupname:remote_gid mappings\n" " -o ssh_command=CMD execute CMD instead of 'ssh'\n" " -o ssh_protocol=N ssh protocol to use (default: 2)\n" " -o sftp_server=SERV path to sftp server or subsystem (default: sftp)\n" @@ -3421,6 +3459,166 @@ static int ssh_connect(void) return 0; } +/* remove trailing '\n', like the perl func of the same name */ +static inline void chomp(char *line) +{ + char *p = line; + if ((p = strrchr(line, '\n'))) + *p = '\0'; +} + +/* number of ':' separated fields in a passwd/group file that we care + * about */ +#define IDMAP_FIELDS 3 + +/* given a line from a uidmap or gidmap, parse out the name and id */ +static void parse_idmap_line(char *line, const char* filename, + const unsigned int lineno, uint32_t *ret_id, char **ret_name) +{ + chomp(line); + char *tokens[IDMAP_FIELDS]; + char *tok; + int i; + for (i = 0; (tok = strsep(&line, ":")) && (i < IDMAP_FIELDS) ; i++) { + tokens[i] = tok; + } + + char *name_tok, *id_tok; + if (i == 2) { + /* assume name:id format */ + name_tok = tokens[0]; + id_tok = tokens[1]; + } else if (i >= IDMAP_FIELDS) { + /* assume passwd/group file format */ + name_tok = tokens[0]; + id_tok = tokens[2]; + } else { + fprintf(stderr, "Unknown format at line %u of '%s'\n", + lineno, filename); + exit(1); + } + + errno = 0; + uint32_t remote_id = strtoul(id_tok, NULL, 10); + if (errno) { + fprintf(stderr, "Invalid id number on line %u of '%s': %s\n", + lineno, filename, strerror(errno)); + exit(1); + } + + *ret_name = strdup(name_tok); + *ret_id = remote_id; +} + +/* read a uidmap or gidmap */ +static void read_id_map(char *file, uint32_t *(*map_fn)(char *), + const char *name_id, GHashTable **idmap, GHashTable **r_idmap) +{ + *idmap = g_hash_table_new(NULL, NULL); + *r_idmap = g_hash_table_new(NULL, NULL); + FILE *fp; + char *line = NULL; + size_t len = 0; + unsigned int lineno = 0; + + fp = fopen(file, "r"); + if (fp == NULL) { + fprintf(stderr, "failed to open '%s': %s\n", + file, strerror(errno)); + exit(1); + } + + while (getline(&line, &len, fp) != EOF) { + lineno++; + uint32_t remote_id; + char *name; + + parse_idmap_line(line, file, lineno, &remote_id, &name); + + uint32_t *local_id = map_fn(name); + if (local_id == NULL) { + /* not found */ + DEBUG("%s(%u): no local %s\n", name, remote_id, name_id); + free(name); + continue; + } + + DEBUG("%s: remote %s %u => local %s %u\n", + name, name_id, remote_id, name_id, *local_id); + g_hash_table_insert(*idmap, GUINT_TO_POINTER(remote_id), GUINT_TO_POINTER(*local_id)); + g_hash_table_insert(*r_idmap, GUINT_TO_POINTER(*local_id), GUINT_TO_POINTER(remote_id)); + free(name); + free(local_id); + } + + if (fclose(fp) == EOF) { + fprintf(stderr, "failed to close '%s': %s", + file, strerror(errno)); + exit(1); + } + + if (line) + free(line); +} + +/* given a username, return a pointer to its uid, or NULL if it doesn't + * exist on this system */ +static uint32_t *username_to_uid(char *name) +{ + errno = 0; + struct passwd *pw = getpwnam(name); + if (pw == NULL) { + if (errno == 0) { + /* "does not exist" */ + return NULL; + } + fprintf(stderr, "Failed to look up user '%s': %s\n", + name, strerror(errno)); + exit(1); + } + uint32_t *r = malloc(sizeof(uint32_t)); + if (r == NULL) { + fprintf(stderr, "sshfs: memory allocation failed\n"); + abort(); + } + *r = pw->pw_uid; + return r; +} + +/* given a groupname, return a pointer to its gid, or NULL if it doesn't + * exist on this system */ +static uint32_t *groupname_to_gid(char *name) +{ + errno = 0; + struct group *gr = getgrnam(name); + if (gr == NULL) { + if (errno == 0) { + /* "does not exist" */ + return NULL; + } + fprintf(stderr, "Failed to look up group '%s': %s\n", + name, strerror(errno)); + exit(1); + } + uint32_t *r = malloc(sizeof(uint32_t)); + if (r == NULL) { + fprintf(stderr, "sshfs: memory allocation failed\n"); + abort(); + } + *r = gr->gr_gid; + return r; +} + +static inline void load_uid_map(void) +{ + read_id_map(sshfs.uid_file, &username_to_uid, "uid", &sshfs.uid_map, &sshfs.r_uid_map); +} + +static inline void load_gid_map(void) +{ + read_id_map(sshfs.gid_file, &groupname_to_gid, "gid", &sshfs.gid_map, &sshfs.r_gid_map); +} + int main(int argc, char *argv[]) { int res; @@ -3447,6 +3645,8 @@ int main(int argc, char *argv[]) sshfs.ptyfd = -1; sshfs.ptyslavefd = -1; sshfs.delay_connect = 0; + sshfs.detect_uid = 0; + sshfs.idmap = IDMAP_NONE; ssh_add_arg("ssh"); ssh_add_arg("-x"); ssh_add_arg("-a"); @@ -3456,6 +3656,25 @@ int main(int argc, char *argv[]) parse_workarounds() == -1) exit(1); + if (sshfs.idmap == IDMAP_USER) + sshfs.detect_uid = 1; + else if (sshfs.idmap == IDMAP_FILE) { + sshfs.uid_map = NULL; + sshfs.gid_map = NULL; + sshfs.r_uid_map = NULL; + sshfs.r_gid_map = NULL; + if (!sshfs.uid_file && !sshfs.gid_file) { + fprintf(stderr, "need a uid_file or gid_file with idmap=file\n"); + exit(1); + } + if (sshfs.uid_file) + load_uid_map(); + if (sshfs.gid_file) + load_gid_map(); + } + free(sshfs.uid_file); + free(sshfs.gid_file); + DEBUG("SSHFS version %s\n", PACKAGE_VERSION); if (sshfs.password_stdin) { -- cgit v1.2.3