| /* |
| * Copyright (c) 2022 Damien Miller <djm@mindrot.org> |
| * |
| * Permission to use, copy, modify, and distribute this software for any |
| * purpose with or without fee is hereby granted, provided that the above |
| * copyright notice and this permission notice appear in all copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| */ |
| |
| /* sftp client user/group lookup and caching */ |
| |
| #include "includes.h" |
| |
| #include <sys/types.h> |
| #include <openbsd-compat/sys-tree.h> |
| |
| #include <stdlib.h> |
| #include <stdarg.h> |
| #include <string.h> |
| |
| #include "log.h" |
| #include "xmalloc.h" |
| |
| #include "sftp-common.h" |
| #include "sftp-client.h" |
| #include "sftp-usergroup.h" |
| |
| /* Tree of id, name */ |
| struct idname { |
| u_int id; |
| char *name; |
| RB_ENTRY(idname) entry; |
| /* XXX implement bounded cache as TAILQ */ |
| }; |
| static int |
| idname_cmp(struct idname *a, struct idname *b) |
| { |
| if (a->id == b->id) |
| return 0; |
| return a->id > b->id ? 1 : -1; |
| } |
| RB_HEAD(idname_tree, idname); |
| RB_GENERATE_STATIC(idname_tree, idname, entry, idname_cmp) |
| |
| static struct idname_tree user_idname = RB_INITIALIZER(&user_idname); |
| static struct idname_tree group_idname = RB_INITIALIZER(&group_idname); |
| |
| static void |
| idname_free(struct idname *idname) |
| { |
| if (idname == NULL) |
| return; |
| free(idname->name); |
| free(idname); |
| } |
| |
| static void |
| idname_enter(struct idname_tree *tree, u_int id, const char *name) |
| { |
| struct idname *idname; |
| |
| if ((idname = xcalloc(1, sizeof(*idname))) == NULL) |
| fatal_f("alloc"); |
| idname->id = id; |
| idname->name = xstrdup(name); |
| if (RB_INSERT(idname_tree, tree, idname) != NULL) |
| idname_free(idname); |
| } |
| |
| static const char * |
| idname_lookup(struct idname_tree *tree, u_int id) |
| { |
| struct idname idname, *found; |
| |
| memset(&idname, 0, sizeof(idname)); |
| idname.id = id; |
| if ((found = RB_FIND(idname_tree, tree, &idname)) != NULL) |
| return found->name; |
| return NULL; |
| } |
| |
| static void |
| freenames(char **names, u_int nnames) |
| { |
| u_int i; |
| |
| if (names == NULL) |
| return; |
| for (i = 0; i < nnames; i++) |
| free(names[i]); |
| free(names); |
| } |
| |
| static void |
| lookup_and_record(struct sftp_conn *conn, |
| u_int *uids, u_int nuids, u_int *gids, u_int ngids) |
| { |
| int r; |
| u_int i; |
| char **usernames = NULL, **groupnames = NULL; |
| |
| if ((r = sftp_get_users_groups_by_id(conn, uids, nuids, gids, ngids, |
| &usernames, &groupnames)) != 0) { |
| debug_fr(r, "sftp_get_users_groups_by_id"); |
| return; |
| } |
| for (i = 0; i < nuids; i++) { |
| if (usernames[i] == NULL) { |
| debug3_f("uid %u not resolved", uids[i]); |
| continue; |
| } |
| debug3_f("record uid %u => \"%s\"", uids[i], usernames[i]); |
| idname_enter(&user_idname, uids[i], usernames[i]); |
| } |
| for (i = 0; i < ngids; i++) { |
| if (groupnames[i] == NULL) { |
| debug3_f("gid %u not resolved", gids[i]); |
| continue; |
| } |
| debug3_f("record gid %u => \"%s\"", gids[i], groupnames[i]); |
| idname_enter(&group_idname, gids[i], groupnames[i]); |
| } |
| freenames(usernames, nuids); |
| freenames(groupnames, ngids); |
| } |
| |
| static int |
| has_id(u_int id, u_int *ids, u_int nids) |
| { |
| u_int i; |
| |
| if (nids == 0) |
| return 0; |
| |
| /* XXX O(N^2) */ |
| for (i = 0; i < nids; i++) { |
| if (ids[i] == id) |
| break; |
| } |
| return i < nids; |
| } |
| |
| static void |
| collect_ids_from_glob(glob_t *g, int user, u_int **idsp, u_int *nidsp) |
| { |
| u_int id, i, n = 0, *ids = NULL; |
| |
| for (i = 0; g->gl_pathv[i] != NULL; i++) { |
| if (user) { |
| if (ruser_name(g->gl_statv[i]->st_uid) != NULL) |
| continue; /* Already seen */ |
| id = (u_int)g->gl_statv[i]->st_uid; |
| } else { |
| if (rgroup_name(g->gl_statv[i]->st_gid) != NULL) |
| continue; /* Already seen */ |
| id = (u_int)g->gl_statv[i]->st_gid; |
| } |
| if (has_id(id, ids, n)) |
| continue; |
| ids = xrecallocarray(ids, n, n + 1, sizeof(*ids)); |
| ids[n++] = id; |
| } |
| *idsp = ids; |
| *nidsp = n; |
| } |
| |
| void |
| get_remote_user_groups_from_glob(struct sftp_conn *conn, glob_t *g) |
| { |
| u_int *uids = NULL, nuids = 0, *gids = NULL, ngids = 0; |
| |
| if (!sftp_can_get_users_groups_by_id(conn)) |
| return; |
| |
| collect_ids_from_glob(g, 1, &uids, &nuids); |
| collect_ids_from_glob(g, 0, &gids, &ngids); |
| lookup_and_record(conn, uids, nuids, gids, ngids); |
| free(uids); |
| free(gids); |
| } |
| |
| static void |
| collect_ids_from_dirents(SFTP_DIRENT **d, int user, u_int **idsp, u_int *nidsp) |
| { |
| u_int id, i, n = 0, *ids = NULL; |
| |
| for (i = 0; d[i] != NULL; i++) { |
| if (user) { |
| if (ruser_name((uid_t)(d[i]->a.uid)) != NULL) |
| continue; /* Already seen */ |
| id = d[i]->a.uid; |
| } else { |
| if (rgroup_name((gid_t)(d[i]->a.gid)) != NULL) |
| continue; /* Already seen */ |
| id = d[i]->a.gid; |
| } |
| if (has_id(id, ids, n)) |
| continue; |
| ids = xrecallocarray(ids, n, n + 1, sizeof(*ids)); |
| ids[n++] = id; |
| } |
| *idsp = ids; |
| *nidsp = n; |
| } |
| |
| void |
| get_remote_user_groups_from_dirents(struct sftp_conn *conn, SFTP_DIRENT **d) |
| { |
| u_int *uids = NULL, nuids = 0, *gids = NULL, ngids = 0; |
| |
| if (!sftp_can_get_users_groups_by_id(conn)) |
| return; |
| |
| collect_ids_from_dirents(d, 1, &uids, &nuids); |
| collect_ids_from_dirents(d, 0, &gids, &ngids); |
| lookup_and_record(conn, uids, nuids, gids, ngids); |
| free(uids); |
| free(gids); |
| } |
| |
| const char * |
| ruser_name(uid_t uid) |
| { |
| return idname_lookup(&user_idname, (u_int)uid); |
| } |
| |
| const char * |
| rgroup_name(uid_t gid) |
| { |
| return idname_lookup(&group_idname, (u_int)gid); |
| } |
| |