1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "format-util.h"
4 #include "machined-varlink.h"
5 #include "mkdir.h"
6 #include "user-util.h"
7 #include "varlink.h"
8
9 typedef struct LookupParameters {
10 const char *user_name;
11 const char *group_name;
12 union {
13 uid_t uid;
14 gid_t gid;
15 };
16 const char *service;
17 } LookupParameters;
18
build_user_json(const char * user_name,uid_t uid,const char * real_name,JsonVariant ** ret)19 static int build_user_json(const char *user_name, uid_t uid, const char *real_name, JsonVariant **ret) {
20 assert(user_name);
21 assert(uid_is_valid(uid));
22 assert(ret);
23
24 return json_build(ret, JSON_BUILD_OBJECT(
25 JSON_BUILD_PAIR("record", JSON_BUILD_OBJECT(
26 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(user_name)),
27 JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(uid)),
28 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(GID_NOBODY)),
29 JSON_BUILD_PAIR_CONDITION(!isempty(real_name), "realName", JSON_BUILD_STRING(real_name)),
30 JSON_BUILD_PAIR("homeDirectory", JSON_BUILD_CONST_STRING("/")),
31 JSON_BUILD_PAIR("shell", JSON_BUILD_STRING(NOLOGIN)),
32 JSON_BUILD_PAIR("locked", JSON_BUILD_BOOLEAN(true)),
33 JSON_BUILD_PAIR("service", JSON_BUILD_CONST_STRING("io.systemd.Machine")),
34 JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("container"))))));
35 }
36
user_match_lookup_parameters(LookupParameters * p,const char * name,uid_t uid)37 static bool user_match_lookup_parameters(LookupParameters *p, const char *name, uid_t uid) {
38 assert(p);
39
40 if (p->user_name && !streq(name, p->user_name))
41 return false;
42
43 if (uid_is_valid(p->uid) && uid != p->uid)
44 return false;
45
46 return true;
47 }
48
user_lookup_uid(Manager * m,uid_t uid,char ** ret_name,char ** ret_real_name)49 static int user_lookup_uid(Manager *m, uid_t uid, char **ret_name, char **ret_real_name) {
50 _cleanup_free_ char *n = NULL, *rn = NULL;
51 uid_t converted_uid;
52 Machine *machine;
53 int r;
54
55 assert(m);
56 assert(uid_is_valid(uid));
57 assert(ret_name);
58 assert(ret_real_name);
59
60 if (uid < 0x10000) /* Host UID range */
61 return -ESRCH;
62
63 r = manager_find_machine_for_uid(m, uid, &machine, &converted_uid);
64 if (r < 0)
65 return r;
66 if (!r)
67 return -ESRCH;
68
69 if (asprintf(&n, "vu-%s-" UID_FMT, machine->name, converted_uid) < 0)
70 return -ENOMEM;
71
72 /* Don't synthesize invalid user/group names (too long...) */
73 if (!valid_user_group_name(n, 0))
74 return -ESRCH;
75
76 if (asprintf(&rn, "UID " UID_FMT " of Container %s", converted_uid, machine->name) < 0)
77 return -ENOMEM;
78
79 /* Don't synthesize invalid real names either, but since this field doesn't matter much, simply invalidate things */
80 if (!valid_gecos(rn))
81 rn = mfree(rn);
82
83 *ret_name = TAKE_PTR(n);
84 *ret_real_name = TAKE_PTR(rn);
85 return 0;
86 }
87
user_lookup_name(Manager * m,const char * name,uid_t * ret_uid,char ** ret_real_name)88 static int user_lookup_name(Manager *m, const char *name, uid_t *ret_uid, char **ret_real_name) {
89 _cleanup_free_ char *mn = NULL, *rn = NULL;
90 uid_t uid, converted_uid;
91 Machine *machine;
92 const char *e, *d;
93 int r;
94
95 assert(m);
96 assert(ret_uid);
97 assert(ret_real_name);
98
99 if (!valid_user_group_name(name, 0))
100 return -ESRCH;
101
102 e = startswith(name, "vu-");
103 if (!e)
104 return -ESRCH;
105
106 d = strrchr(e, '-');
107 if (!d)
108 return -ESRCH;
109
110 if (parse_uid(d + 1, &uid) < 0)
111 return -ESRCH;
112
113 mn = strndup(e, d - e);
114 if (!mn)
115 return -ENOMEM;
116
117 machine = hashmap_get(m->machines, mn);
118 if (!machine)
119 return -ESRCH;
120
121 if (machine->class != MACHINE_CONTAINER)
122 return -ESRCH;
123
124 r = machine_translate_uid(machine, uid, &converted_uid);
125 if (r < 0)
126 return r;
127
128 if (asprintf(&rn, "UID " UID_FMT " of Container %s", uid, machine->name) < 0)
129 return -ENOMEM;
130 if (!valid_gecos(rn))
131 rn = mfree(rn);
132
133 *ret_uid = converted_uid;
134 *ret_real_name = TAKE_PTR(rn);
135 return 0;
136 }
137
vl_method_get_user_record(Varlink * link,JsonVariant * parameters,VarlinkMethodFlags flags,void * userdata)138 static int vl_method_get_user_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
139
140 static const JsonDispatch dispatch_table[] = {
141 { "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(LookupParameters, uid), 0 },
142 { "userName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, user_name), JSON_SAFE },
143 { "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 },
144 {}
145 };
146
147 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
148 LookupParameters p = {
149 .uid = UID_INVALID,
150 };
151 _cleanup_free_ char *found_name = NULL, *found_real_name = NULL;
152 uid_t found_uid = UID_INVALID, uid;
153 Manager *m = userdata;
154 const char *un;
155 int r;
156
157 assert(parameters);
158 assert(m);
159
160 r = json_dispatch(parameters, dispatch_table, NULL, 0, &p);
161 if (r < 0)
162 return r;
163
164 if (!streq_ptr(p.service, "io.systemd.Machine"))
165 return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL);
166
167 if (uid_is_valid(p.uid))
168 r = user_lookup_uid(m, p.uid, &found_name, &found_real_name);
169 else if (p.user_name)
170 r = user_lookup_name(m, p.user_name, &found_uid, &found_real_name);
171 else
172 return varlink_error(link, "io.systemd.UserDatabase.EnumerationNotSupported", NULL);
173 if (r == -ESRCH)
174 return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
175 if (r < 0)
176 return r;
177
178 uid = uid_is_valid(found_uid) ? found_uid : p.uid;
179 un = found_name ?: p.user_name;
180
181 if (!user_match_lookup_parameters(&p, un, uid))
182 return varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL);
183
184 r = build_user_json(un, uid, found_real_name, &v);
185 if (r < 0)
186 return r;
187
188 return varlink_reply(link, v);
189 }
190
build_group_json(const char * group_name,gid_t gid,const char * description,JsonVariant ** ret)191 static int build_group_json(const char *group_name, gid_t gid, const char *description, JsonVariant **ret) {
192 assert(group_name);
193 assert(gid_is_valid(gid));
194 assert(ret);
195
196 return json_build(ret, JSON_BUILD_OBJECT(
197 JSON_BUILD_PAIR("record", JSON_BUILD_OBJECT(
198 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(group_name)),
199 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(gid)),
200 JSON_BUILD_PAIR_CONDITION(!isempty(description), "description", JSON_BUILD_STRING(description)),
201 JSON_BUILD_PAIR("service", JSON_BUILD_CONST_STRING("io.systemd.Machine")),
202 JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("container"))))));
203 }
204
group_match_lookup_parameters(LookupParameters * p,const char * name,gid_t gid)205 static bool group_match_lookup_parameters(LookupParameters *p, const char *name, gid_t gid) {
206 assert(p);
207
208 if (p->group_name && !streq(name, p->group_name))
209 return false;
210
211 if (gid_is_valid(p->gid) && gid != p->gid)
212 return false;
213
214 return true;
215 }
216
group_lookup_gid(Manager * m,gid_t gid,char ** ret_name,char ** ret_description)217 static int group_lookup_gid(Manager *m, gid_t gid, char **ret_name, char **ret_description) {
218 _cleanup_free_ char *n = NULL, *d = NULL;
219 gid_t converted_gid;
220 Machine *machine;
221 int r;
222
223 assert(m);
224 assert(gid_is_valid(gid));
225 assert(ret_name);
226 assert(ret_description);
227
228 if (gid < 0x10000) /* Host GID range */
229 return -ESRCH;
230
231 r = manager_find_machine_for_gid(m, gid, &machine, &converted_gid);
232 if (r < 0)
233 return r;
234 if (!r)
235 return -ESRCH;
236
237 if (asprintf(&n, "vg-%s-" GID_FMT, machine->name, converted_gid) < 0)
238 return -ENOMEM;
239
240 if (!valid_user_group_name(n, 0))
241 return -ESRCH;
242
243 if (asprintf(&d, "GID " GID_FMT " of Container %s", converted_gid, machine->name) < 0)
244 return -ENOMEM;
245 if (!valid_gecos(d))
246 d = mfree(d);
247
248 *ret_name = TAKE_PTR(n);
249 *ret_description = TAKE_PTR(d);
250
251 return 0;
252 }
253
group_lookup_name(Manager * m,const char * name,gid_t * ret_gid,char ** ret_description)254 static int group_lookup_name(Manager *m, const char *name, gid_t *ret_gid, char **ret_description) {
255 _cleanup_free_ char *mn = NULL, *desc = NULL;
256 gid_t gid, converted_gid;
257 Machine *machine;
258 const char *e, *d;
259 int r;
260
261 assert(m);
262 assert(ret_gid);
263 assert(ret_description);
264
265 if (!valid_user_group_name(name, 0))
266 return -ESRCH;
267
268 e = startswith(name, "vg-");
269 if (!e)
270 return -ESRCH;
271
272 d = strrchr(e, '-');
273 if (!d)
274 return -ESRCH;
275
276 if (parse_gid(d + 1, &gid) < 0)
277 return -ESRCH;
278
279 mn = strndup(e, d - e);
280 if (!mn)
281 return -ENOMEM;
282
283 machine = hashmap_get(m->machines, mn);
284 if (!machine)
285 return -ESRCH;
286
287 if (machine->class != MACHINE_CONTAINER)
288 return -ESRCH;
289
290 r = machine_translate_gid(machine, gid, &converted_gid);
291 if (r < 0)
292 return r;
293
294 if (asprintf(&desc, "GID " GID_FMT " of Container %s", gid, machine->name) < 0)
295 return -ENOMEM;
296 if (!valid_gecos(desc))
297 desc = mfree(desc);
298
299 *ret_gid = converted_gid;
300 *ret_description = TAKE_PTR(desc);
301 return 0;
302 }
303
vl_method_get_group_record(Varlink * link,JsonVariant * parameters,VarlinkMethodFlags flags,void * userdata)304 static int vl_method_get_group_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
305
306 static const JsonDispatch dispatch_table[] = {
307 { "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(LookupParameters, gid), 0 },
308 { "groupName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, group_name), JSON_SAFE },
309 { "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 },
310 {}
311 };
312
313 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
314 LookupParameters p = {
315 .gid = GID_INVALID,
316 };
317 _cleanup_free_ char *found_name = NULL, *found_description = NULL;
318 uid_t found_gid = GID_INVALID, gid;
319 Manager *m = userdata;
320 const char *gn;
321 int r;
322
323 assert(parameters);
324 assert(m);
325
326 r = json_dispatch(parameters, dispatch_table, NULL, 0, &p);
327 if (r < 0)
328 return r;
329
330 if (!streq_ptr(p.service, "io.systemd.Machine"))
331 return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL);
332
333 if (gid_is_valid(p.gid))
334 r = group_lookup_gid(m, p.gid, &found_name, &found_description);
335 else if (p.group_name)
336 r = group_lookup_name(m, p.group_name, (uid_t*) &found_gid, &found_description);
337 else
338 return varlink_error(link, "io.systemd.UserDatabase.EnumerationNotSupported", NULL);
339 if (r == -ESRCH)
340 return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
341 if (r < 0)
342 return r;
343
344 gid = gid_is_valid(found_gid) ? found_gid : p.gid;
345 gn = found_name ?: p.group_name;
346
347 if (!group_match_lookup_parameters(&p, gn, gid))
348 return varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL);
349
350 r = build_group_json(gn, gid, found_description, &v);
351 if (r < 0)
352 return r;
353
354 return varlink_reply(link, v);
355 }
356
vl_method_get_memberships(Varlink * link,JsonVariant * parameters,VarlinkMethodFlags flags,void * userdata)357 static int vl_method_get_memberships(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
358
359 static const JsonDispatch dispatch_table[] = {
360 { "userName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, user_name), JSON_SAFE },
361 { "groupName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, group_name), JSON_SAFE },
362 { "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 },
363 {}
364 };
365
366 LookupParameters p = {};
367 int r;
368
369 assert(parameters);
370
371 r = json_dispatch(parameters, dispatch_table, NULL, 0, &p);
372 if (r < 0)
373 return r;
374
375 if (!streq_ptr(p.service, "io.systemd.Machine"))
376 return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL);
377
378 /* We don't support auxiliary groups for machines. */
379 return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
380 }
381
manager_varlink_init(Manager * m)382 int manager_varlink_init(Manager *m) {
383 _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL;
384 int r;
385
386 assert(m);
387
388 if (m->varlink_server)
389 return 0;
390
391 r = varlink_server_new(&s, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA);
392 if (r < 0)
393 return log_error_errno(r, "Failed to allocate varlink server object: %m");
394
395 varlink_server_set_userdata(s, m);
396
397 r = varlink_server_bind_method_many(
398 s,
399 "io.systemd.UserDatabase.GetUserRecord", vl_method_get_user_record,
400 "io.systemd.UserDatabase.GetGroupRecord", vl_method_get_group_record,
401 "io.systemd.UserDatabase.GetMemberships", vl_method_get_memberships);
402 if (r < 0)
403 return log_error_errno(r, "Failed to register varlink methods: %m");
404
405 (void) mkdir_p("/run/systemd/userdb", 0755);
406
407 r = varlink_server_listen_address(s, "/run/systemd/userdb/io.systemd.Machine", 0666);
408 if (r < 0)
409 return log_error_errno(r, "Failed to bind to varlink socket: %m");
410
411 r = varlink_server_attach_event(s, m->event, SD_EVENT_PRIORITY_NORMAL);
412 if (r < 0)
413 return log_error_errno(r, "Failed to attach varlink connection to event loop: %m");
414
415 m->varlink_server = TAKE_PTR(s);
416 return 0;
417 }
418
manager_varlink_done(Manager * m)419 void manager_varlink_done(Manager *m) {
420 assert(m);
421
422 m->varlink_server = varlink_server_unref(m->varlink_server);
423 }
424