]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/user-util.c
util-lib: split out allocation calls into alloc-util.[ch]
[thirdparty/systemd.git] / src / basic / user-util.c
CommitLineData
b1d4f8e1
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2010 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
22#include <pwd.h>
23#include <grp.h>
24
b5efdb8a 25#include "alloc-util.h"
e929bee0 26#include "fd-util.h"
b1d4f8e1 27#include "macro.h"
6bedfcbb 28#include "parse-util.h"
b1d4f8e1 29#include "path-util.h"
6bedfcbb
LP
30#include "string-util.h"
31#include "user-util.h"
32#include "util.h"
b1d4f8e1
LP
33
34bool uid_is_valid(uid_t uid) {
35
36 /* Some libc APIs use UID_INVALID as special placeholder */
b1d52773 37 if (uid == (uid_t) UINT32_C(0xFFFFFFFF))
b1d4f8e1
LP
38 return false;
39
40 /* A long time ago UIDs where 16bit, hence explicitly avoid the 16bit -1 too */
b1d52773 41 if (uid == (uid_t) UINT32_C(0xFFFF))
b1d4f8e1
LP
42 return false;
43
44 return true;
45}
46
b1d52773
LP
47int parse_uid(const char *s, uid_t *ret) {
48 uint32_t uid = 0;
b1d4f8e1
LP
49 int r;
50
51 assert(s);
52
b1d52773
LP
53 assert_cc(sizeof(uid_t) == sizeof(uint32_t));
54 r = safe_atou32(s, &uid);
b1d4f8e1
LP
55 if (r < 0)
56 return r;
57
b1d4f8e1
LP
58 if (!uid_is_valid(uid))
59 return -ENXIO; /* we return ENXIO instead of EINVAL
60 * here, to make it easy to distuingish
61 * invalid numeric uids invalid
62 * strings. */
63
b1d52773
LP
64 if (ret)
65 *ret = uid;
b1d4f8e1
LP
66
67 return 0;
68}
69
b1d4f8e1
LP
70char* getlogname_malloc(void) {
71 uid_t uid;
72 struct stat st;
73
74 if (isatty(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0)
75 uid = st.st_uid;
76 else
77 uid = getuid();
78
d0260817 79 return uid_to_name(uid);
b1d4f8e1
LP
80}
81
82char *getusername_malloc(void) {
83 const char *e;
84
85 e = getenv("USER");
86 if (e)
87 return strdup(e);
88
d0260817 89 return uid_to_name(getuid());
b1d4f8e1
LP
90}
91
92int get_user_creds(
93 const char **username,
94 uid_t *uid, gid_t *gid,
95 const char **home,
96 const char **shell) {
97
98 struct passwd *p;
99 uid_t u;
100
101 assert(username);
102 assert(*username);
103
104 /* We enforce some special rules for uid=0: in order to avoid
105 * NSS lookups for root we hardcode its data. */
106
107 if (streq(*username, "root") || streq(*username, "0")) {
108 *username = "root";
109
110 if (uid)
111 *uid = 0;
112
113 if (gid)
114 *gid = 0;
115
116 if (home)
117 *home = "/root";
118
119 if (shell)
120 *shell = "/bin/sh";
121
122 return 0;
123 }
124
125 if (parse_uid(*username, &u) >= 0) {
126 errno = 0;
127 p = getpwuid(u);
128
129 /* If there are multiple users with the same id, make
130 * sure to leave $USER to the configured value instead
131 * of the first occurrence in the database. However if
132 * the uid was configured by a numeric uid, then let's
133 * pick the real username from /etc/passwd. */
134 if (p)
135 *username = p->pw_name;
136 } else {
137 errno = 0;
138 p = getpwnam(*username);
139 }
140
141 if (!p)
142 return errno > 0 ? -errno : -ESRCH;
143
67c7c892
LP
144 if (uid) {
145 if (!uid_is_valid(p->pw_uid))
146 return -EBADMSG;
147
b1d4f8e1 148 *uid = p->pw_uid;
67c7c892
LP
149 }
150
151 if (gid) {
152 if (!gid_is_valid(p->pw_gid))
153 return -EBADMSG;
b1d4f8e1 154
b1d4f8e1 155 *gid = p->pw_gid;
67c7c892 156 }
b1d4f8e1
LP
157
158 if (home)
159 *home = p->pw_dir;
160
161 if (shell)
162 *shell = p->pw_shell;
163
164 return 0;
165}
166
167int get_group_creds(const char **groupname, gid_t *gid) {
168 struct group *g;
169 gid_t id;
170
171 assert(groupname);
172
173 /* We enforce some special rules for gid=0: in order to avoid
174 * NSS lookups for root we hardcode its data. */
175
176 if (streq(*groupname, "root") || streq(*groupname, "0")) {
177 *groupname = "root";
178
179 if (gid)
180 *gid = 0;
181
182 return 0;
183 }
184
185 if (parse_gid(*groupname, &id) >= 0) {
186 errno = 0;
187 g = getgrgid(id);
188
189 if (g)
190 *groupname = g->gr_name;
191 } else {
192 errno = 0;
193 g = getgrnam(*groupname);
194 }
195
196 if (!g)
197 return errno > 0 ? -errno : -ESRCH;
198
67c7c892
LP
199 if (gid) {
200 if (!gid_is_valid(g->gr_gid))
201 return -EBADMSG;
202
b1d4f8e1 203 *gid = g->gr_gid;
67c7c892 204 }
b1d4f8e1
LP
205
206 return 0;
207}
208
209char* uid_to_name(uid_t uid) {
d0260817
LP
210 char *ret;
211 int r;
b1d4f8e1 212
d0260817 213 /* Shortcut things to avoid NSS lookups */
b1d4f8e1
LP
214 if (uid == 0)
215 return strdup("root");
216
d0260817
LP
217 if (uid_is_valid(uid)) {
218 long bufsize;
219
220 bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
221 if (bufsize <= 0)
222 bufsize = 4096;
223
224 for (;;) {
225 struct passwd pwbuf, *pw = NULL;
226 _cleanup_free_ char *buf = NULL;
227
228 buf = malloc(bufsize);
229 if (!buf)
230 return NULL;
231
232 r = getpwuid_r(uid, &pwbuf, buf, (size_t) bufsize, &pw);
233 if (r == 0 && pw)
234 return strdup(pw->pw_name);
235 if (r != ERANGE)
236 break;
237
238 bufsize *= 2;
239 }
240 }
b1d4f8e1 241
d0260817 242 if (asprintf(&ret, UID_FMT, uid) < 0)
b1d4f8e1
LP
243 return NULL;
244
d0260817 245 return ret;
b1d4f8e1
LP
246}
247
248char* gid_to_name(gid_t gid) {
d0260817
LP
249 char *ret;
250 int r;
b1d4f8e1
LP
251
252 if (gid == 0)
253 return strdup("root");
254
d0260817
LP
255 if (gid_is_valid(gid)) {
256 long bufsize;
257
258 bufsize = sysconf(_SC_GETGR_R_SIZE_MAX);
259 if (bufsize <= 0)
260 bufsize = 4096;
261
262 for (;;) {
263 struct group grbuf, *gr = NULL;
264 _cleanup_free_ char *buf = NULL;
265
266 buf = malloc(bufsize);
267 if (!buf)
268 return NULL;
269
270 r = getgrgid_r(gid, &grbuf, buf, (size_t) bufsize, &gr);
271 if (r == 0 && gr)
272 return strdup(gr->gr_name);
273 if (r != ERANGE)
274 break;
275
276 bufsize *= 2;
277 }
278 }
b1d4f8e1 279
d0260817 280 if (asprintf(&ret, GID_FMT, gid) < 0)
b1d4f8e1
LP
281 return NULL;
282
d0260817 283 return ret;
b1d4f8e1
LP
284}
285
286int in_gid(gid_t gid) {
287 gid_t *gids;
288 int ngroups_max, r, i;
289
290 if (getgid() == gid)
291 return 1;
292
293 if (getegid() == gid)
294 return 1;
295
67c7c892
LP
296 if (!gid_is_valid(gid))
297 return -EINVAL;
298
b1d4f8e1
LP
299 ngroups_max = sysconf(_SC_NGROUPS_MAX);
300 assert(ngroups_max > 0);
301
302 gids = alloca(sizeof(gid_t) * ngroups_max);
303
304 r = getgroups(ngroups_max, gids);
305 if (r < 0)
306 return -errno;
307
308 for (i = 0; i < r; i++)
309 if (gids[i] == gid)
310 return 1;
311
312 return 0;
313}
314
315int in_group(const char *name) {
316 int r;
317 gid_t gid;
318
319 r = get_group_creds(&name, &gid);
320 if (r < 0)
321 return r;
322
323 return in_gid(gid);
324}
325
326int get_home_dir(char **_h) {
327 struct passwd *p;
328 const char *e;
329 char *h;
330 uid_t u;
331
332 assert(_h);
333
334 /* Take the user specified one */
335 e = secure_getenv("HOME");
336 if (e && path_is_absolute(e)) {
337 h = strdup(e);
338 if (!h)
339 return -ENOMEM;
340
341 *_h = h;
342 return 0;
343 }
344
345 /* Hardcode home directory for root to avoid NSS */
346 u = getuid();
347 if (u == 0) {
348 h = strdup("/root");
349 if (!h)
350 return -ENOMEM;
351
352 *_h = h;
353 return 0;
354 }
355
356 /* Check the database... */
357 errno = 0;
358 p = getpwuid(u);
359 if (!p)
360 return errno > 0 ? -errno : -ESRCH;
361
362 if (!path_is_absolute(p->pw_dir))
363 return -EINVAL;
364
365 h = strdup(p->pw_dir);
366 if (!h)
367 return -ENOMEM;
368
369 *_h = h;
370 return 0;
371}
372
373int get_shell(char **_s) {
374 struct passwd *p;
375 const char *e;
376 char *s;
377 uid_t u;
378
379 assert(_s);
380
381 /* Take the user specified one */
382 e = getenv("SHELL");
383 if (e) {
384 s = strdup(e);
385 if (!s)
386 return -ENOMEM;
387
388 *_s = s;
389 return 0;
390 }
391
392 /* Hardcode home directory for root to avoid NSS */
393 u = getuid();
394 if (u == 0) {
395 s = strdup("/bin/sh");
396 if (!s)
397 return -ENOMEM;
398
399 *_s = s;
400 return 0;
401 }
402
403 /* Check the database... */
404 errno = 0;
405 p = getpwuid(u);
406 if (!p)
407 return errno > 0 ? -errno : -ESRCH;
408
409 if (!path_is_absolute(p->pw_shell))
410 return -EINVAL;
411
412 s = strdup(p->pw_shell);
413 if (!s)
414 return -ENOMEM;
415
416 *_s = s;
417 return 0;
418}
419
420int reset_uid_gid(void) {
421
422 if (setgroups(0, NULL) < 0)
423 return -errno;
424
425 if (setresgid(0, 0, 0) < 0)
426 return -errno;
427
428 if (setresuid(0, 0, 0) < 0)
429 return -errno;
430
431 return 0;
432}
e929bee0
LP
433
434int take_etc_passwd_lock(const char *root) {
435
436 struct flock flock = {
437 .l_type = F_WRLCK,
438 .l_whence = SEEK_SET,
439 .l_start = 0,
440 .l_len = 0,
441 };
442
443 const char *path;
444 int fd, r;
445
446 /* This is roughly the same as lckpwdf(), but not as awful. We
447 * don't want to use alarm() and signals, hence we implement
448 * our own trivial version of this.
449 *
450 * Note that shadow-utils also takes per-database locks in
451 * addition to lckpwdf(). However, we don't given that they
452 * are redundant as they they invoke lckpwdf() first and keep
453 * it during everything they do. The per-database locks are
454 * awfully racy, and thus we just won't do them. */
455
456 if (root)
457 path = prefix_roota(root, "/etc/.pwd.lock");
458 else
459 path = "/etc/.pwd.lock";
460
461 fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
462 if (fd < 0)
463 return -errno;
464
465 r = fcntl(fd, F_SETLKW, &flock);
466 if (r < 0) {
467 safe_close(fd);
468 return -errno;
469 }
470
471 return fd;
472}