1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "errno-util.h"
4 #include "fd-util.h"
5 #include "fileio.h"
6 #include "format-util.h"
7 #include "path-util.h"
8 #include "stdio-util.h"
9 #include "user-util.h"
10 #include "userdb-dropin.h"
11
load_user(FILE * f,const char * path,const char * name,uid_t uid,UserDBFlags flags,UserRecord ** ret)12 static int load_user(
13 FILE *f,
14 const char *path,
15 const char *name,
16 uid_t uid,
17 UserDBFlags flags,
18 UserRecord **ret) {
19
20 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
21 _cleanup_(user_record_unrefp) UserRecord *u = NULL;
22 bool have_privileged;
23 int r;
24
25 assert(f);
26
27 r = json_parse_file(f, path, 0, &v, NULL, NULL);
28 if (r < 0)
29 return r;
30
31 if (FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW) || !path || !(name || uid_is_valid(uid)))
32 have_privileged = false;
33 else {
34 _cleanup_(json_variant_unrefp) JsonVariant *privileged_v = NULL;
35 _cleanup_free_ char *d = NULL, *j = NULL;
36
37 /* Let's load the "privileged" section from a companion file. But only if USERDB_AVOID_SHADOW
38 * is not set. After all, the privileged section kinda takes the role of the data from the
39 * shadow file, hence it makes sense to use the same flag here.
40 *
41 * The general assumption is that whoever provides these records makes the .user file
42 * world-readable, but the .privilege file readable to root and the assigned UID only. But we
43 * won't verify that here, as it would be too late. */
44
45 r = path_extract_directory(path, &d);
46 if (r < 0)
47 return r;
48
49 if (name) {
50 j = strjoin(d, "/", name, ".user-privileged");
51 if (!j)
52 return -ENOMEM;
53 } else {
54 assert(uid_is_valid(uid));
55 if (asprintf(&j, "%s/" UID_FMT ".user-privileged", d, uid) < 0)
56 return -ENOMEM;
57 }
58
59 r = json_parse_file(NULL, j, JSON_PARSE_SENSITIVE, &privileged_v, NULL, NULL);
60 if (ERRNO_IS_PRIVILEGE(r))
61 have_privileged = false;
62 else if (r == -ENOENT)
63 have_privileged = true; /* if the privileged file doesn't exist, we are complete */
64 else if (r < 0)
65 return r;
66 else {
67 r = json_variant_merge(&v, privileged_v);
68 if (r < 0)
69 return r;
70
71 have_privileged = true;
72 }
73 }
74
75 u = user_record_new();
76 if (!u)
77 return -ENOMEM;
78
79 r = user_record_load(
80 u, v,
81 USER_RECORD_REQUIRE_REGULAR|
82 USER_RECORD_ALLOW_PER_MACHINE|
83 USER_RECORD_ALLOW_BINDING|
84 USER_RECORD_ALLOW_SIGNATURE|
85 (have_privileged ? USER_RECORD_ALLOW_PRIVILEGED : 0)|
86 USER_RECORD_PERMISSIVE);
87 if (r < 0)
88 return r;
89
90 if (name && !streq_ptr(name, u->user_name))
91 return -EINVAL;
92
93 if (uid_is_valid(uid) && uid != u->uid)
94 return -EINVAL;
95
96 u->incomplete = !have_privileged;
97
98 if (ret)
99 *ret = TAKE_PTR(u);
100
101 return 0;
102 }
103
dropin_user_record_by_name(const char * name,const char * path,UserDBFlags flags,UserRecord ** ret)104 int dropin_user_record_by_name(const char *name, const char *path, UserDBFlags flags, UserRecord **ret) {
105 _cleanup_free_ char *found_path = NULL;
106 _cleanup_fclose_ FILE *f = NULL;
107 int r;
108
109 assert(name);
110
111 if (path) {
112 f = fopen(path, "re");
113 if (!f)
114 return errno == ENOENT ? -ESRCH : -errno; /* We generally want ESRCH to indicate no such user */
115 } else {
116 const char *j;
117
118 j = strjoina(name, ".user");
119 if (!filename_is_valid(j)) /* Doesn't qualify as valid filename? Then it's definitely not provided as a drop-in */
120 return -ESRCH;
121
122 r = search_and_fopen_nulstr(j, "re", NULL, USERDB_DROPIN_DIR_NULSTR("userdb"), &f, &found_path);
123 if (r == -ENOENT)
124 return -ESRCH;
125 if (r < 0)
126 return r;
127
128 path = found_path;
129 }
130
131 return load_user(f, path, name, UID_INVALID, flags, ret);
132 }
133
dropin_user_record_by_uid(uid_t uid,const char * path,UserDBFlags flags,UserRecord ** ret)134 int dropin_user_record_by_uid(uid_t uid, const char *path, UserDBFlags flags, UserRecord **ret) {
135 _cleanup_free_ char *found_path = NULL;
136 _cleanup_fclose_ FILE *f = NULL;
137 int r;
138
139 assert(uid_is_valid(uid));
140
141 if (path) {
142 f = fopen(path, "re");
143 if (!f)
144 return errno == ENOENT ? -ESRCH : -errno;
145 } else {
146 char buf[DECIMAL_STR_MAX(uid_t) + STRLEN(".user") + 1];
147
148 xsprintf(buf, UID_FMT ".user", uid);
149 /* Note that we don't bother to validate this as a filename, as this is generated from a decimal
150 * integer, i.e. is definitely OK as a filename */
151
152 r = search_and_fopen_nulstr(buf, "re", NULL, USERDB_DROPIN_DIR_NULSTR("userdb"), &f, &found_path);
153 if (r == -ENOENT)
154 return -ESRCH;
155 if (r < 0)
156 return r;
157
158 path = found_path;
159 }
160
161 return load_user(f, path, NULL, uid, flags, ret);
162 }
163
load_group(FILE * f,const char * path,const char * name,gid_t gid,UserDBFlags flags,GroupRecord ** ret)164 static int load_group(
165 FILE *f,
166 const char *path,
167 const char *name,
168 gid_t gid,
169 UserDBFlags flags,
170 GroupRecord **ret) {
171
172 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
173 _cleanup_(group_record_unrefp) GroupRecord *g = NULL;
174 bool have_privileged;
175 int r;
176
177 assert(f);
178
179 r = json_parse_file(f, path, 0, &v, NULL, NULL);
180 if (r < 0)
181 return r;
182
183 if (FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW) || !path || !(name || gid_is_valid(gid)))
184 have_privileged = false;
185 else {
186 _cleanup_(json_variant_unrefp) JsonVariant *privileged_v = NULL;
187 _cleanup_free_ char *d = NULL, *j = NULL;
188
189 r = path_extract_directory(path, &d);
190 if (r < 0)
191 return r;
192
193 if (name) {
194 j = strjoin(d, "/", name, ".group-privileged");
195 if (!j)
196 return -ENOMEM;
197 } else {
198 assert(gid_is_valid(gid));
199 if (asprintf(&j, "%s/" GID_FMT ".group-privileged", d, gid) < 0)
200 return -ENOMEM;
201 }
202
203 r = json_parse_file(NULL, j, JSON_PARSE_SENSITIVE, &privileged_v, NULL, NULL);
204 if (ERRNO_IS_PRIVILEGE(r))
205 have_privileged = false;
206 else if (r == -ENOENT)
207 have_privileged = true; /* if the privileged file doesn't exist, we are complete */
208 else if (r < 0)
209 return r;
210 else {
211 r = json_variant_merge(&v, privileged_v);
212 if (r < 0)
213 return r;
214
215 have_privileged = true;
216 }
217 }
218
219 g = group_record_new();
220 if (!g)
221 return -ENOMEM;
222
223 r = group_record_load(
224 g, v,
225 USER_RECORD_REQUIRE_REGULAR|
226 USER_RECORD_ALLOW_PER_MACHINE|
227 USER_RECORD_ALLOW_BINDING|
228 USER_RECORD_ALLOW_SIGNATURE|
229 (have_privileged ? USER_RECORD_ALLOW_PRIVILEGED : 0)|
230 USER_RECORD_PERMISSIVE);
231 if (r < 0)
232 return r;
233
234 if (name && !streq_ptr(name, g->group_name))
235 return -EINVAL;
236
237 if (gid_is_valid(gid) && gid != g->gid)
238 return -EINVAL;
239
240 g->incomplete = !have_privileged;
241
242 if (ret)
243 *ret = TAKE_PTR(g);
244
245 return 0;
246 }
247
dropin_group_record_by_name(const char * name,const char * path,UserDBFlags flags,GroupRecord ** ret)248 int dropin_group_record_by_name(const char *name, const char *path, UserDBFlags flags, GroupRecord **ret) {
249 _cleanup_free_ char *found_path = NULL;
250 _cleanup_fclose_ FILE *f = NULL;
251 int r;
252
253 assert(name);
254
255 if (path) {
256 f = fopen(path, "re");
257 if (!f)
258 return errno == ENOENT ? -ESRCH : -errno;
259 } else {
260 const char *j;
261
262 j = strjoina(name, ".group");
263 if (!filename_is_valid(j)) /* Doesn't qualify as valid filename? Then it's definitely not provided as a drop-in */
264 return -ESRCH;
265
266 r = search_and_fopen_nulstr(j, "re", NULL, USERDB_DROPIN_DIR_NULSTR("userdb"), &f, &found_path);
267 if (r == -ENOENT)
268 return -ESRCH;
269 if (r < 0)
270 return r;
271
272 path = found_path;
273 }
274
275 return load_group(f, path, name, GID_INVALID, flags, ret);
276 }
277
dropin_group_record_by_gid(gid_t gid,const char * path,UserDBFlags flags,GroupRecord ** ret)278 int dropin_group_record_by_gid(gid_t gid, const char *path, UserDBFlags flags, GroupRecord **ret) {
279 _cleanup_free_ char *found_path = NULL;
280 _cleanup_fclose_ FILE *f = NULL;
281 int r;
282
283 assert(gid_is_valid(gid));
284
285 if (path) {
286 f = fopen(path, "re");
287 if (!f)
288 return errno == ENOENT ? -ESRCH : -errno;
289 } else {
290 char buf[DECIMAL_STR_MAX(gid_t) + STRLEN(".group") + 1];
291
292 xsprintf(buf, GID_FMT ".group", gid);
293
294 r = search_and_fopen_nulstr(buf, "re", NULL, USERDB_DROPIN_DIR_NULSTR("userdb"), &f, &found_path);
295 if (r == -ENOENT)
296 return -ESRCH;
297 if (r < 0)
298 return r;
299
300 path = found_path;
301 }
302
303 return load_group(f, path, NULL, gid, flags, ret);
304 }
305