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