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