1/*	$OpenBSD: ids.c,v 1.15 2021/06/30 13:10:04 claudio Exp $ */
2/*
3 * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17#include <assert.h>
18#include <grp.h>
19#include <inttypes.h>
20#include <pwd.h>
21#include <stdio.h>
22#include <stdlib.h>
23#include <string.h>
24#include <unistd.h>
25
26#include "extern.h"
27
28/*
29 * Free a list of struct ident previously allocated with idents_add().
30 * Does nothing if the pointer is NULL.
31 */
32void
33idents_free(struct ident *p, size_t sz)
34{
35	size_t	 i;
36
37	if (p == NULL)
38		return;
39	for (i = 0; i < sz; i++)
40		free(p[i].name);
41	free(p);
42}
43
44/*
45 * Given a list of files with the identifiers as set by the sender,
46 * re-assign the identifiers from the list of remapped ones.
47 * Don't ever remap wheel/root.
48 * If we can't find the gid in the list (when, e.g., being sent by the
49 * daemon), don't try to map it.
50 */
51void
52idents_assign_gid(struct sess *sess, struct flist *fl, size_t flsz,
53	const struct ident *ids, size_t idsz)
54{
55	size_t	 i, j;
56
57	assert(!sess->opts->numeric_ids);
58
59	for (i = 0; i < flsz; i++) {
60		if (fl[i].st.gid == 0)
61			continue;
62		for (j = 0; j < idsz; j++)
63			if ((int32_t)fl[i].st.gid == ids[j].id)
64				break;
65		if (j < idsz)
66			fl[i].st.gid = ids[j].mapped;
67	}
68}
69
70/*
71 * Like idents_assign_gid().
72 */
73void
74idents_assign_uid(struct sess *sess, struct flist *fl, size_t flsz,
75	const struct ident *ids, size_t idsz)
76{
77	size_t	 i, j;
78
79	assert(!sess->opts->numeric_ids);
80
81	for (i = 0; i < flsz; i++) {
82		if (fl[i].st.uid == 0)
83			continue;
84		for (j = 0; j < idsz; j++)
85			if ((int32_t)fl[i].st.uid == ids[j].id)
86				break;
87		if (j < idsz)
88			fl[i].st.uid = ids[j].mapped;
89	}
90}
91
92/*
93 * Given a list of identifiers from the remote host, fill in our local
94 * identifiers of the same names.
95 * Use the remote numeric identifier if we can't find the identifier OR the
96 * identifier is zero (wheel/root).
97 * FIXME: we should at least warn when we can't find an identifier, use
98 * the numeric id, and that numeric id is assigned to a different user.
99 */
100void
101idents_remap(struct sess *sess, int isgid, struct ident *ids, size_t idsz)
102{
103	size_t		 i;
104	struct group	*grp;
105	struct passwd	*usr;
106	uint32_t	 id;
107	int		 valid;
108
109	assert(!sess->opts->numeric_ids);
110
111	for (i = 0; i < idsz; i++) {
112		assert(ids[i].id != 0);
113
114		/* Start by getting our local representation. */
115
116		valid = id = 0;
117		if (isgid) {
118			grp = getgrnam(ids[i].name);
119			if (grp) {
120				id = grp->gr_gid;
121				valid = 1;
122			}
123		} else {
124			usr = getpwnam(ids[i].name);
125			if (usr) {
126				id = usr->pw_uid;
127				valid = 1;
128			}
129		}
130
131		/*
132		 * (1) Empty names inherit.
133		 * (2) Unknown identifier names inherit.
134		 * (3) Wheel/root inherits.
135		 * (4) Otherwise, use the local identifier.
136		 */
137
138		if (ids[i].name[0] == '\0')
139			ids[i].mapped = ids[i].id;
140		else if (!valid)
141			ids[i].mapped = ids[i].id;
142		else
143			ids[i].mapped = id;
144
145		LOG4("remapped identifier %s: %d -> %d",
146		    ids[i].name, ids[i].id, ids[i].mapped);
147	}
148}
149
150/*
151 * If "id" is not part of the list of known users or groups (depending
152 * upon "isgid", add it.
153 * This also verifies that the name isn't too long.
154 * Does nothing with user/group zero.
155 * Return zero on failure, non-zero on success.
156 */
157int
158idents_add(int isgid, struct ident **ids, size_t *idsz, int32_t id)
159{
160	struct group	*grp;
161	struct passwd	*usr;
162	size_t		 i, sz;
163	void		*pp;
164	const char	*name;
165
166	if (id == 0)
167		return 1;
168
169	for (i = 0; i < *idsz; i++)
170		if ((*ids)[i].id == id)
171			return 1;
172
173	/*
174	 * Look up the reference in a type-specific way.
175	 * Make sure that the name length is sane: we transmit it using
176	 * a single byte.
177	 */
178
179	assert(i == *idsz);
180	if (isgid) {
181		if ((grp = getgrgid((gid_t)id)) == NULL) {
182			ERR("%d: unknown gid", id);
183			return 0;
184		}
185		name = grp->gr_name;
186	} else {
187		if ((usr = getpwuid((uid_t)id)) == NULL) {
188			ERR("%d: unknown uid", id);
189			return 0;
190		}
191		name = usr->pw_name;
192	}
193
194	if ((sz = strlen(name)) > UINT8_MAX) {
195		ERRX("%d: name too long: %s", id, name);
196		return 0;
197	} else if (sz == 0) {
198		ERRX("%d: zero-length name", id);
199		return 0;
200	}
201
202	/* Add the identifier to the array. */
203
204	pp = reallocarray(*ids, *idsz + 1, sizeof(struct ident));
205	if (pp == NULL) {
206		ERR("reallocarray");
207		return 0;
208	}
209	*ids = pp;
210	(*ids)[*idsz].id = id;
211	(*ids)[*idsz].name = strdup(name);
212	if ((*ids)[*idsz].name == NULL) {
213		ERR("strdup");
214		return 0;
215	}
216
217	LOG4("adding identifier to list: %s (%u)",
218	    (*ids)[*idsz].name, (*ids)[*idsz].id);
219	(*idsz)++;
220	return 1;
221}
222
223/*
224 * Send a list of struct ident.
225 * See idents_recv().
226 * We should only do this if we're preserving gids/uids.
227 * Return zero on failure, non-zero on success.
228 */
229int
230idents_send(struct sess *sess,
231	int fd, const struct ident *ids, size_t idsz)
232{
233	size_t	 i, sz;
234
235	for (i = 0; i < idsz; i++) {
236		assert(ids[i].name != NULL);
237		assert(ids[i].id != 0);
238		sz = strlen(ids[i].name);
239		assert(sz > 0 && sz <= UINT8_MAX);
240		if (!io_write_uint(sess, fd, ids[i].id)) {
241			ERRX1("io_write_uint");
242			return 0;
243		} else if (!io_write_byte(sess, fd, sz)) {
244			ERRX1("io_write_byte");
245			return 0;
246		} else if (!io_write_buf(sess, fd, ids[i].name, sz)) {
247			ERRX1("io_write_buf");
248			return 0;
249		}
250	}
251
252	if (!io_write_int(sess, fd, 0)) {
253		ERRX1("io_write_int");
254		return 0;
255	}
256
257	return 1;
258}
259
260/*
261 * Receive a list of struct ident.
262 * See idents_send().
263 * We should only do this if we're preserving gids/uids.
264 * Return zero on failure, non-zero on success.
265 */
266int
267idents_recv(struct sess *sess,
268	int fd, struct ident **ids, size_t *idsz)
269{
270	uint32_t id;
271	uint8_t	 sz;
272	void	*pp;
273
274	for (;;) {
275		if (!io_read_uint(sess, fd, &id)) {
276			ERRX1("io_read_uint");
277			return 0;
278		} else if (id == 0)
279			break;
280
281		pp = reallocarray(*ids,
282			*idsz + 1, sizeof(struct ident));
283		if (pp == NULL) {
284			ERR("reallocarray");
285			return 0;
286		}
287		*ids = pp;
288		memset(&(*ids)[*idsz], 0, sizeof(struct ident));
289
290		/*
291		 * When reading the size, warn if we get a size of zero.
292		 * The spec doesn't allow this, but we might have a
293		 * noncomformant or adversarial sender.
294		 */
295
296		if (!io_read_byte(sess, fd, &sz)) {
297			ERRX1("io_read_byte");
298			return 0;
299		} else if (sz == 0)
300			WARNX("zero-length name in identifier list");
301
302		assert(id < INT32_MAX);
303		(*ids)[*idsz].id = id;
304		(*ids)[*idsz].name = calloc(sz + 1, 1);
305		if ((*ids)[*idsz].name == NULL) {
306			ERR("calloc");
307			return 0;
308		}
309		if (!io_read_buf(sess, fd, (*ids)[*idsz].name, sz)) {
310			ERRX1("io_read_buf");
311			return 0;
312		}
313		(*idsz)++;
314	}
315
316	return 1;
317}
318