1*a629fefcSchristos /* $NetBSD: sftp-usergroup.c,v 1.3 2023/10/25 20:19:57 christos Exp $ */
2e160b4e8Schristos
31b614e63Schristos /*
41b614e63Schristos * Copyright (c) 2022 Damien Miller <djm@mindrot.org>
51b614e63Schristos *
61b614e63Schristos * Permission to use, copy, modify, and distribute this software for any
71b614e63Schristos * purpose with or without fee is hereby granted, provided that the above
81b614e63Schristos * copyright notice and this permission notice appear in all copies.
91b614e63Schristos *
101b614e63Schristos * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
111b614e63Schristos * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
121b614e63Schristos * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
131b614e63Schristos * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
141b614e63Schristos * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
151b614e63Schristos * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
161b614e63Schristos * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
171b614e63Schristos */
181b614e63Schristos
191b614e63Schristos /* sftp client user/group lookup and caching */
20e160b4e8Schristos #include "includes.h"
21*a629fefcSchristos __RCSID("$NetBSD: sftp-usergroup.c,v 1.3 2023/10/25 20:19:57 christos Exp $");
221b614e63Schristos
231b614e63Schristos #include <sys/types.h>
241b614e63Schristos #include <sys/tree.h>
251b614e63Schristos
261b614e63Schristos #include <glob.h>
271b614e63Schristos #include <stdlib.h>
281b614e63Schristos #include <stdarg.h>
291b614e63Schristos #include <string.h>
301b614e63Schristos
311b614e63Schristos #include "log.h"
321b614e63Schristos #include "xmalloc.h"
331b614e63Schristos
341b614e63Schristos #include "sftp-common.h"
351b614e63Schristos #include "sftp-client.h"
361b614e63Schristos #include "sftp-usergroup.h"
371b614e63Schristos
381b614e63Schristos /* Tree of id, name */
391b614e63Schristos struct idname {
401b614e63Schristos u_int id;
411b614e63Schristos char *name;
421b614e63Schristos RB_ENTRY(idname) entry;
431b614e63Schristos /* XXX implement bounded cache as TAILQ */
441b614e63Schristos };
451b614e63Schristos static int
idname_cmp(struct idname * a,struct idname * b)461b614e63Schristos idname_cmp(struct idname *a, struct idname *b)
471b614e63Schristos {
481b614e63Schristos if (a->id == b->id)
491b614e63Schristos return 0;
501b614e63Schristos return a->id > b->id ? 1 : -1;
511b614e63Schristos }
521b614e63Schristos RB_HEAD(idname_tree, idname);
531b614e63Schristos RB_GENERATE_STATIC(idname_tree, idname, entry, idname_cmp)
541b614e63Schristos
551b614e63Schristos static struct idname_tree user_idname = RB_INITIALIZER(&user_idname);
561b614e63Schristos static struct idname_tree group_idname = RB_INITIALIZER(&group_idname);
571b614e63Schristos
581b614e63Schristos static void
idname_free(struct idname * idname)591b614e63Schristos idname_free(struct idname *idname)
601b614e63Schristos {
611b614e63Schristos if (idname == NULL)
621b614e63Schristos return;
631b614e63Schristos free(idname->name);
641b614e63Schristos free(idname);
651b614e63Schristos }
661b614e63Schristos
671b614e63Schristos static void
idname_enter(struct idname_tree * tree,u_int id,const char * name)681b614e63Schristos idname_enter(struct idname_tree *tree, u_int id, const char *name)
691b614e63Schristos {
701b614e63Schristos struct idname *idname;
711b614e63Schristos
721b614e63Schristos if ((idname = xcalloc(1, sizeof(*idname))) == NULL)
731b614e63Schristos fatal_f("alloc");
741b614e63Schristos idname->id = id;
751b614e63Schristos idname->name = xstrdup(name);
761b614e63Schristos if (RB_INSERT(idname_tree, tree, idname) != NULL)
771b614e63Schristos idname_free(idname);
781b614e63Schristos }
791b614e63Schristos
801b614e63Schristos static const char *
idname_lookup(struct idname_tree * tree,u_int id)811b614e63Schristos idname_lookup(struct idname_tree *tree, u_int id)
821b614e63Schristos {
831b614e63Schristos struct idname idname, *found;
841b614e63Schristos
851b614e63Schristos memset(&idname, 0, sizeof(idname));
861b614e63Schristos idname.id = id;
871b614e63Schristos if ((found = RB_FIND(idname_tree, tree, &idname)) != NULL)
881b614e63Schristos return found->name;
891b614e63Schristos return NULL;
901b614e63Schristos }
911b614e63Schristos
921b614e63Schristos static void
freenames(char ** names,u_int nnames)931b614e63Schristos freenames(char **names, u_int nnames)
941b614e63Schristos {
951b614e63Schristos u_int i;
961b614e63Schristos
971b614e63Schristos if (names == NULL)
981b614e63Schristos return;
991b614e63Schristos for (i = 0; i < nnames; i++)
1001b614e63Schristos free(names[i]);
1011b614e63Schristos free(names);
1021b614e63Schristos }
1031b614e63Schristos
1041b614e63Schristos static void
lookup_and_record(struct sftp_conn * conn,u_int * uids,u_int nuids,u_int * gids,u_int ngids)1051b614e63Schristos lookup_and_record(struct sftp_conn *conn,
1061b614e63Schristos u_int *uids, u_int nuids, u_int *gids, u_int ngids)
1071b614e63Schristos {
1081b614e63Schristos int r;
1091b614e63Schristos u_int i;
1101b614e63Schristos char **usernames = NULL, **groupnames = NULL;
1111b614e63Schristos
112*a629fefcSchristos if ((r = sftp_get_users_groups_by_id(conn, uids, nuids, gids, ngids,
1131b614e63Schristos &usernames, &groupnames)) != 0) {
114*a629fefcSchristos debug_fr(r, "sftp_get_users_groups_by_id");
1151b614e63Schristos return;
1161b614e63Schristos }
1171b614e63Schristos for (i = 0; i < nuids; i++) {
1181b614e63Schristos if (usernames[i] == NULL) {
1191b614e63Schristos debug3_f("uid %u not resolved", uids[i]);
1201b614e63Schristos continue;
1211b614e63Schristos }
1221b614e63Schristos debug3_f("record uid %u => \"%s\"", uids[i], usernames[i]);
1231b614e63Schristos idname_enter(&user_idname, uids[i], usernames[i]);
1241b614e63Schristos }
1251b614e63Schristos for (i = 0; i < ngids; i++) {
1261b614e63Schristos if (groupnames[i] == NULL) {
1271b614e63Schristos debug3_f("gid %u not resolved", gids[i]);
1281b614e63Schristos continue;
1291b614e63Schristos }
1301b614e63Schristos debug3_f("record gid %u => \"%s\"", gids[i], groupnames[i]);
1311b614e63Schristos idname_enter(&group_idname, gids[i], groupnames[i]);
1321b614e63Schristos }
1331b614e63Schristos freenames(usernames, nuids);
1341b614e63Schristos freenames(groupnames, ngids);
1351b614e63Schristos }
1361b614e63Schristos
1371b614e63Schristos static int
has_id(u_int id,u_int * ids,u_int nids)1381b614e63Schristos has_id(u_int id, u_int *ids, u_int nids)
1391b614e63Schristos {
1401b614e63Schristos u_int i;
1411b614e63Schristos
1421b614e63Schristos if (nids == 0)
1431b614e63Schristos return 0;
1441b614e63Schristos
1451b614e63Schristos /* XXX O(N^2) */
1461b614e63Schristos for (i = 0; i < nids; i++) {
1471b614e63Schristos if (ids[i] == id)
1481b614e63Schristos break;
1491b614e63Schristos }
1501b614e63Schristos return i < nids;
1511b614e63Schristos }
1521b614e63Schristos
1531b614e63Schristos static void
collect_ids_from_glob(glob_t * g,int user,u_int ** idsp,u_int * nidsp)1541b614e63Schristos collect_ids_from_glob(glob_t *g, int user, u_int **idsp, u_int *nidsp)
1551b614e63Schristos {
1561b614e63Schristos u_int id, i, n = 0, *ids = NULL;
1571b614e63Schristos
1581b614e63Schristos for (i = 0; g->gl_pathv[i] != NULL; i++) {
159e160b4e8Schristos struct stat *stp;
160e160b4e8Schristos #if GLOB_KEEPSTAT != 0
161e160b4e8Schristos stp = g->gl_statv[i];
162e160b4e8Schristos #else
163e160b4e8Schristos struct stat st;
164e160b4e8Schristos if (lstat(g->gl_pathv[i], stp = &st) == -1) {
165e160b4e8Schristos error("no stat information for %s", g->gl_pathv[i]);
166e160b4e8Schristos continue;
167e160b4e8Schristos }
168e160b4e8Schristos #endif
1691b614e63Schristos if (user) {
170e160b4e8Schristos if (ruser_name(stp->st_uid) != NULL)
1711b614e63Schristos continue; /* Already seen */
172e160b4e8Schristos id = (u_int)stp->st_uid;
1731b614e63Schristos } else {
174e160b4e8Schristos if (rgroup_name(stp->st_gid) != NULL)
1751b614e63Schristos continue; /* Already seen */
176e160b4e8Schristos id = (u_int)stp->st_gid;
1771b614e63Schristos }
1781b614e63Schristos if (has_id(id, ids, n))
1791b614e63Schristos continue;
1801b614e63Schristos ids = xrecallocarray(ids, n, n + 1, sizeof(*ids));
1811b614e63Schristos ids[n++] = id;
1821b614e63Schristos }
1831b614e63Schristos *idsp = ids;
1841b614e63Schristos *nidsp = n;
1851b614e63Schristos }
1861b614e63Schristos
1871b614e63Schristos void
get_remote_user_groups_from_glob(struct sftp_conn * conn,glob_t * g)1881b614e63Schristos get_remote_user_groups_from_glob(struct sftp_conn *conn, glob_t *g)
1891b614e63Schristos {
1901b614e63Schristos u_int *uids = NULL, nuids = 0, *gids = NULL, ngids = 0;
1911b614e63Schristos
192*a629fefcSchristos if (!sftp_can_get_users_groups_by_id(conn))
1931b614e63Schristos return;
1941b614e63Schristos
1951b614e63Schristos collect_ids_from_glob(g, 1, &uids, &nuids);
1961b614e63Schristos collect_ids_from_glob(g, 0, &gids, &ngids);
1971b614e63Schristos lookup_and_record(conn, uids, nuids, gids, ngids);
1981b614e63Schristos free(uids);
1991b614e63Schristos free(gids);
2001b614e63Schristos }
2011b614e63Schristos
2021b614e63Schristos static void
collect_ids_from_dirents(SFTP_DIRENT ** d,int user,u_int ** idsp,u_int * nidsp)2031b614e63Schristos collect_ids_from_dirents(SFTP_DIRENT **d, int user, u_int **idsp, u_int *nidsp)
2041b614e63Schristos {
2051b614e63Schristos u_int id, i, n = 0, *ids = NULL;
2061b614e63Schristos
2071b614e63Schristos for (i = 0; d[i] != NULL; i++) {
2081b614e63Schristos if (user) {
2091b614e63Schristos if (ruser_name((uid_t)(d[i]->a.uid)) != NULL)
2101b614e63Schristos continue; /* Already seen */
2111b614e63Schristos id = d[i]->a.uid;
2121b614e63Schristos } else {
2131b614e63Schristos if (rgroup_name((gid_t)(d[i]->a.gid)) != NULL)
2141b614e63Schristos continue; /* Already seen */
2151b614e63Schristos id = d[i]->a.gid;
2161b614e63Schristos }
2171b614e63Schristos if (has_id(id, ids, n))
2181b614e63Schristos continue;
2191b614e63Schristos ids = xrecallocarray(ids, n, n + 1, sizeof(*ids));
2201b614e63Schristos ids[n++] = id;
2211b614e63Schristos }
2221b614e63Schristos *idsp = ids;
2231b614e63Schristos *nidsp = n;
2241b614e63Schristos }
2251b614e63Schristos
2261b614e63Schristos void
get_remote_user_groups_from_dirents(struct sftp_conn * conn,SFTP_DIRENT ** d)2271b614e63Schristos get_remote_user_groups_from_dirents(struct sftp_conn *conn, SFTP_DIRENT **d)
2281b614e63Schristos {
2291b614e63Schristos u_int *uids = NULL, nuids = 0, *gids = NULL, ngids = 0;
2301b614e63Schristos
231*a629fefcSchristos if (!sftp_can_get_users_groups_by_id(conn))
2321b614e63Schristos return;
2331b614e63Schristos
2341b614e63Schristos collect_ids_from_dirents(d, 1, &uids, &nuids);
2351b614e63Schristos collect_ids_from_dirents(d, 0, &gids, &ngids);
2361b614e63Schristos lookup_and_record(conn, uids, nuids, gids, ngids);
2371b614e63Schristos free(uids);
2381b614e63Schristos free(gids);
2391b614e63Schristos }
2401b614e63Schristos
2411b614e63Schristos const char *
ruser_name(uid_t uid)2421b614e63Schristos ruser_name(uid_t uid)
2431b614e63Schristos {
2441b614e63Schristos return idname_lookup(&user_idname, (u_int)uid);
2451b614e63Schristos }
2461b614e63Schristos
2471b614e63Schristos const char *
rgroup_name(uid_t gid)2481b614e63Schristos rgroup_name(uid_t gid)
2491b614e63Schristos {
2501b614e63Schristos return idname_lookup(&group_idname, (u_int)gid);
2511b614e63Schristos }
2521b614e63Schristos
253