1/*	$NetBSD: sftp-usergroup.c,v 1.3 2023/10/25 20:19:57 christos Exp $	*/
2
3/*
4 * Copyright (c) 2022 Damien Miller <djm@mindrot.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19/* sftp client user/group lookup and caching */
20#include "includes.h"
21__RCSID("$NetBSD: sftp-usergroup.c,v 1.3 2023/10/25 20:19:57 christos Exp $");
22
23#include <sys/types.h>
24#include <sys/tree.h>
25
26#include <glob.h>
27#include <stdlib.h>
28#include <stdarg.h>
29#include <string.h>
30
31#include "log.h"
32#include "xmalloc.h"
33
34#include "sftp-common.h"
35#include "sftp-client.h"
36#include "sftp-usergroup.h"
37
38/* Tree of id, name */
39struct idname {
40        u_int id;
41	char *name;
42        RB_ENTRY(idname) entry;
43	/* XXX implement bounded cache as TAILQ */
44};
45static int
46idname_cmp(struct idname *a, struct idname *b)
47{
48	if (a->id == b->id)
49		return 0;
50	return a->id > b->id ? 1 : -1;
51}
52RB_HEAD(idname_tree, idname);
53RB_GENERATE_STATIC(idname_tree, idname, entry, idname_cmp)
54
55static struct idname_tree user_idname = RB_INITIALIZER(&user_idname);
56static struct idname_tree group_idname = RB_INITIALIZER(&group_idname);
57
58static void
59idname_free(struct idname *idname)
60{
61	if (idname == NULL)
62		return;
63	free(idname->name);
64	free(idname);
65}
66
67static void
68idname_enter(struct idname_tree *tree, u_int id, const char *name)
69{
70	struct idname *idname;
71
72	if ((idname = xcalloc(1, sizeof(*idname))) == NULL)
73		fatal_f("alloc");
74	idname->id = id;
75	idname->name = xstrdup(name);
76	if (RB_INSERT(idname_tree, tree, idname) != NULL)
77		idname_free(idname);
78}
79
80static const char *
81idname_lookup(struct idname_tree *tree, u_int id)
82{
83	struct idname idname, *found;
84
85	memset(&idname, 0, sizeof(idname));
86	idname.id = id;
87	if ((found = RB_FIND(idname_tree, tree, &idname)) != NULL)
88		return found->name;
89	return NULL;
90}
91
92static void
93freenames(char **names, u_int nnames)
94{
95	u_int i;
96
97	if (names == NULL)
98		return;
99	for (i = 0; i < nnames; i++)
100		free(names[i]);
101	free(names);
102}
103
104static void
105lookup_and_record(struct sftp_conn *conn,
106    u_int *uids, u_int nuids, u_int *gids, u_int ngids)
107{
108	int r;
109	u_int i;
110	char **usernames = NULL, **groupnames = NULL;
111
112	if ((r = sftp_get_users_groups_by_id(conn, uids, nuids, gids, ngids,
113	    &usernames, &groupnames)) != 0) {
114		debug_fr(r, "sftp_get_users_groups_by_id");
115		return;
116	}
117	for (i = 0; i < nuids; i++) {
118		if (usernames[i] == NULL) {
119			debug3_f("uid %u not resolved", uids[i]);
120			continue;
121		}
122		debug3_f("record uid %u => \"%s\"", uids[i], usernames[i]);
123		idname_enter(&user_idname, uids[i], usernames[i]);
124	}
125	for (i = 0; i < ngids; i++) {
126		if (groupnames[i] == NULL) {
127			debug3_f("gid %u not resolved", gids[i]);
128			continue;
129		}
130		debug3_f("record gid %u => \"%s\"", gids[i], groupnames[i]);
131		idname_enter(&group_idname, gids[i], groupnames[i]);
132	}
133	freenames(usernames, nuids);
134	freenames(groupnames, ngids);
135}
136
137static int
138has_id(u_int id, u_int *ids, u_int nids)
139{
140	u_int i;
141
142	if (nids == 0)
143		return 0;
144
145	/* XXX O(N^2) */
146	for (i = 0; i < nids; i++) {
147		if (ids[i] == id)
148			break;
149	}
150	return i < nids;
151}
152
153static void
154collect_ids_from_glob(glob_t *g, int user, u_int **idsp, u_int *nidsp)
155{
156	u_int id, i, n = 0, *ids = NULL;
157
158	for (i = 0; g->gl_pathv[i] != NULL; i++) {
159		struct stat *stp;
160#if GLOB_KEEPSTAT != 0
161		stp = g->gl_statv[i];
162#else
163		struct stat st;
164		if (lstat(g->gl_pathv[i], stp = &st) == -1) {
165			error("no stat information for %s", g->gl_pathv[i]);
166			continue;
167		}
168#endif
169		if (user) {
170			if (ruser_name(stp->st_uid) != NULL)
171				continue; /* Already seen */
172			id = (u_int)stp->st_uid;
173		} else {
174			if (rgroup_name(stp->st_gid) != NULL)
175				continue; /* Already seen */
176			id = (u_int)stp->st_gid;
177		}
178		if (has_id(id, ids, n))
179			continue;
180		ids = xrecallocarray(ids, n, n + 1, sizeof(*ids));
181		ids[n++] = id;
182	}
183	*idsp = ids;
184	*nidsp = n;
185}
186
187void
188get_remote_user_groups_from_glob(struct sftp_conn *conn, glob_t *g)
189{
190	u_int *uids = NULL, nuids = 0, *gids = NULL, ngids = 0;
191
192	if (!sftp_can_get_users_groups_by_id(conn))
193		return;
194
195	collect_ids_from_glob(g, 1, &uids, &nuids);
196	collect_ids_from_glob(g, 0, &gids, &ngids);
197	lookup_and_record(conn, uids, nuids, gids, ngids);
198	free(uids);
199	free(gids);
200}
201
202static void
203collect_ids_from_dirents(SFTP_DIRENT **d, int user, u_int **idsp, u_int *nidsp)
204{
205	u_int id, i, n = 0, *ids = NULL;
206
207	for (i = 0; d[i] != NULL; i++) {
208		if (user) {
209			if (ruser_name((uid_t)(d[i]->a.uid)) != NULL)
210				continue; /* Already seen */
211			id = d[i]->a.uid;
212		} else {
213			if (rgroup_name((gid_t)(d[i]->a.gid)) != NULL)
214				continue; /* Already seen */
215			id = d[i]->a.gid;
216		}
217		if (has_id(id, ids, n))
218			continue;
219		ids = xrecallocarray(ids, n, n + 1, sizeof(*ids));
220		ids[n++] = id;
221	}
222	*idsp = ids;
223	*nidsp = n;
224}
225
226void
227get_remote_user_groups_from_dirents(struct sftp_conn *conn, SFTP_DIRENT **d)
228{
229	u_int *uids = NULL, nuids = 0, *gids = NULL, ngids = 0;
230
231	if (!sftp_can_get_users_groups_by_id(conn))
232		return;
233
234	collect_ids_from_dirents(d, 1, &uids, &nuids);
235	collect_ids_from_dirents(d, 0, &gids, &ngids);
236	lookup_and_record(conn, uids, nuids, gids, ngids);
237	free(uids);
238	free(gids);
239}
240
241const char *
242ruser_name(uid_t uid)
243{
244	return idname_lookup(&user_idname, (u_int)uid);
245}
246
247const char *
248rgroup_name(uid_t gid)
249{
250	return idname_lookup(&group_idname, (u_int)gid);
251}
252
253