]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/core/dynamic-user.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2016 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
25 #include "clean-ipc.h"
26 #include "dynamic-user.h"
31 #include "parse-util.h"
32 #include "random-util.h"
33 #include "stdio-util.h"
34 #include "string-util.h"
35 #include "user-util.h"
37 /* Takes a value generated randomly or by hashing and turns it into a UID in the right range */
38 #define UID_CLAMP_INTO_RANGE(rnd) (((uid_t) (rnd) % (DYNAMIC_UID_MAX - DYNAMIC_UID_MIN + 1)) + DYNAMIC_UID_MIN)
40 static DynamicUser
* dynamic_user_free ( DynamicUser
* d
) {
45 ( void ) hashmap_remove ( d
-> manager
-> dynamic_users
, d
-> name
);
47 safe_close_pair ( d
-> storage_socket
);
51 static int dynamic_user_add ( Manager
* m
, const char * name
, int storage_socket
[ 2 ], DynamicUser
** ret
) {
52 DynamicUser
* d
= NULL
;
57 assert ( storage_socket
);
59 r
= hashmap_ensure_allocated (& m
-> dynamic_users
, & string_hash_ops
);
63 d
= malloc0 ( offsetof ( DynamicUser
, name
) + strlen ( name
) + 1 );
67 strcpy ( d
-> name
, name
);
69 d
-> storage_socket
[ 0 ] = storage_socket
[ 0 ];
70 d
-> storage_socket
[ 1 ] = storage_socket
[ 1 ];
72 r
= hashmap_put ( m
-> dynamic_users
, d
-> name
, d
);
86 static int dynamic_user_acquire ( Manager
* m
, const char * name
, DynamicUser
** ret
) {
87 _cleanup_close_pair_
int storage_socket
[ 2 ] = { - 1 , - 1 };
94 /* Return the DynamicUser structure for a specific user name. Note that this won't actually allocate a UID for
95 * it, but just prepare the data structure for it. The UID is allocated only on demand, when it's really
96 * needed, and in the child process we fork off, since allocation involves NSS checks which are not OK to do
97 * from PID 1. To allow the children and PID 1 share information about allocated UIDs we use an anonymous
98 * AF_UNIX/SOCK_DGRAM socket (called the "storage socket") that contains at most one datagram with the
99 * allocated UID number, plus an fd referencing the lock file for the UID
100 * (i.e. /run/systemd/dynamic-uid/$UID). Why involve the socket pair? So that PID 1 and all its children can
101 * share the same storage for the UID and lock fd, simply by inheriting the storage socket fds. The socket pair
102 * may exist in three different states:
104 * a) no datagram stored. This is the initial state. In this case the dynamic user was never realized.
106 * b) a datagram containing a UID stored, but no lock fd attached to it. In this case there was already a
107 * statically assigned UID by the same name, which we are reusing.
109 * c) a datagram containing a UID stored, and a lock fd is attached to it. In this case we allocated a dynamic
110 * UID and locked it in the file system, using the lock fd.
112 * As PID 1 and various children might access the socket pair simultaneously, and pop the datagram or push it
113 * back in any time, we also maintain a lock on the socket pair. Note one peculiarity regarding locking here:
114 * the UID lock on disk is protected via a BSD file lock (i.e. an fd-bound lock), so that the lock is kept in
115 * place as long as there's a reference to the fd open. The lock on the storage socket pair however is a POSIX
116 * file lock (i.e. a process-bound lock), as all users share the same fd of this (after all it is anonymous,
117 * nobody else could get any access to it except via our own fd) and we want to synchronize access between all
118 * processes that have access to it. */
120 d
= hashmap_get ( m
-> dynamic_users
, name
);
122 /* We already have a structure for the dynamic user, let's increase the ref count and reuse it */
128 if (! valid_user_group_name_or_id ( name
))
131 if ( socketpair ( AF_UNIX
, SOCK_DGRAM
| SOCK_CLOEXEC
, 0 , storage_socket
) < 0 )
134 r
= dynamic_user_add ( m
, name
, storage_socket
, & d
);
138 storage_socket
[ 0 ] = storage_socket
[ 1 ] = - 1 ;
148 static int make_uid_symlinks ( uid_t uid
, const char * name
, bool b
) {
150 char path1
[ strlen ( "/run/systemd/dynamic-uid/direct:" ) + DECIMAL_STR_MAX ( uid_t
) + 1 ];
154 /* Add direct additional symlinks for direct lookups of dynamic UIDs and their names by userspace code. The
155 * only reason we have this is because dbus-daemon cannot use D-Bus for resolving users and groups (since it
156 * would be its own client then). We hence keep these world-readable symlinks in place, so that the
157 * unprivileged dbus user can read the mappings when it needs them via these symlinks instead of having to go
158 * via the bus. Ideally, we'd use the lock files we keep for this anyway, but we can't since we use BSD locks
159 * on them and as those may be taken by any user with read access we can't make them world-readable. */
161 xsprintf ( path1
, "/run/systemd/dynamic-uid/direct:" UID_FMT
, uid
);
162 if ( unlink ( path1
) < 0 && errno
!= ENOENT
)
165 if ( b
&& symlink ( name
, path1
) < 0 ) {
166 k
= log_warning_errno ( errno
, "Failed to symlink \" %s \" : %m" , path1
);
171 path2
= strjoina ( "/run/systemd/dynamic-uid/direct:" , name
);
172 if ( unlink ( path2
) < 0 && errno
!= ENOENT
) {
178 if ( b
&& symlink ( path1
+ strlen ( "/run/systemd/dynamic-uid/direct:" ), path2
) < 0 ) {
179 k
= log_warning_errno ( errno
, "Failed to symlink \" %s \" : %m" , path2
);
187 static int pick_uid ( char ** suggested_paths
, const char * name
, uid_t
* ret_uid
) {
189 /* Find a suitable free UID. We use the following strategy to find a suitable UID:
191 * 1. Initially, we try to read the UID of a number of specified paths. If any of these UIDs works, we use
192 * them. We use in order to increase the chance of UID reuse, if StateDirectory=, CacheDirectory= or
193 * LogDirectory= are used, as reusing the UID these directories are owned by saves us from having to
194 * recursively chown() them to new users.
196 * 2. If that didn't yield a currently unused UID, we hash the user name, and try to use that. This should be
197 * pretty good, as the use ris by default derived from the unit name, and hence the same service and same
198 * user should usually get the same UID as long as our hashing doesn't clash.
200 * 3. Finally, if that didn't work, we randomly pick UIDs, until we find one that is empty.
202 * Since the dynamic UID space is relatively small we'll stop trying after 100 iterations, giving up. */
205 PHASE_SUGGESTED
, /* the first phase, reusing directory ownership UIDs */
206 PHASE_HASHED
, /* the second phase, deriving a UID from the username by hashing */
207 PHASE_RANDOM
, /* the last phase, randomly picking UIDs */
208 } phase
= PHASE_SUGGESTED
;
210 static const uint8_t hash_key
[] = {
211 0x37 , 0x53 , 0x7e , 0x31 , 0xcf , 0xce , 0x48 , 0xf5 ,
212 0x8a , 0xbb , 0x39 , 0x57 , 0x8d , 0xd9 , 0xec , 0x59
215 unsigned n_tries
= 100 , current_suggested
= 0 ;
218 ( void ) mkdir ( "/run/systemd/dynamic-uid" , 0755 );
221 char lock_path
[ strlen ( "/run/systemd/dynamic-uid/" ) + DECIMAL_STR_MAX ( uid_t
) + 1 ];
222 _cleanup_close_
int lock_fd
= - 1 ;
226 if (-- n_tries
<= 0 ) /* Give up retrying eventually */
231 case PHASE_SUGGESTED
: {
234 if (! suggested_paths
|| ! suggested_paths
[ current_suggested
]) {
235 /* We reached the end of the suggested paths list, let's try by hashing the name */
236 phase
= PHASE_HASHED
;
240 if ( stat ( suggested_paths
[ current_suggested
++], & st
) < 0 )
241 continue ; /* We can't read the UID of this path, but that doesn't matter, just try the next */
243 candidate
= st
. st_uid
;
248 /* A static user by this name does not exist yet. Let's find a free ID then, and use that. We
249 * start with a UID generated as hash from the user name. */
250 candidate
= UID_CLAMP_INTO_RANGE ( siphash24 ( name
, strlen ( name
), hash_key
));
252 /* If this one fails, we should proceed with random tries */
253 phase
= PHASE_RANDOM
;
258 /* Pick another random UID, and see if that works for us. */
259 random_bytes (& candidate
, sizeof ( candidate
));
260 candidate
= UID_CLAMP_INTO_RANGE ( candidate
);
264 assert_not_reached ( "unknown phase" );
267 /* Make sure whatever we picked here actually is in the right range */
268 if (! uid_is_dynamic ( candidate
))
271 xsprintf ( lock_path
, "/run/systemd/dynamic-uid/" UID_FMT
, candidate
);
276 lock_fd
= open ( lock_path
, O_CREAT
| O_RDWR
| O_NOFOLLOW
| O_CLOEXEC
| O_NOCTTY
, 0600 );
280 r
= flock ( lock_fd
, LOCK_EX
| LOCK_NB
); /* Try to get a BSD file lock on the UID lock file */
282 if ( IN_SET ( errno
, EBUSY
, EAGAIN
))
283 goto next
; /* already in use */
288 if ( fstat ( lock_fd
, & st
) < 0 )
293 /* Oh, bummer, we got the lock, but the file was unlinked between the time we opened it and
294 * got the lock. Close it, and try again. */
295 lock_fd
= safe_close ( lock_fd
);
298 /* Some superficial check whether this UID/GID might already be taken by some static user */
299 if ( getpwuid ( candidate
) ||
300 getgrgid (( gid_t
) candidate
) ||
301 search_ipc ( candidate
, ( gid_t
) candidate
) != 0 ) {
302 ( void ) unlink ( lock_path
);
306 /* Let's store the user name in the lock file, so that we can use it for looking up the username for a UID */
309 IOVEC_INIT_STRING ( name
),
310 IOVEC_INIT (( char [ 1 ]) { ' \n ' }, 1 ),
314 ( void ) unlink ( lock_path
);
318 ( void ) ftruncate ( lock_fd
, l
);
319 ( void ) make_uid_symlinks ( candidate
, name
, true ); /* also add direct lookup symlinks */
321 * ret_uid
= candidate
;
332 static int dynamic_user_pop ( DynamicUser
* d
, uid_t
* ret_uid
, int * ret_lock_fd
) {
333 uid_t uid
= UID_INVALID
;
334 struct iovec iov
= IOVEC_INIT (& uid
, sizeof ( uid
));
336 struct cmsghdr cmsghdr
;
337 uint8_t buf
[ CMSG_SPACE ( sizeof ( int ))];
340 . msg_control
= & control
,
341 . msg_controllen
= sizeof ( control
),
345 struct cmsghdr
* cmsg
;
354 /* Read the UID and lock fd that is stored in the storage AF_UNIX socket. This should be called with the lock
355 * on the socket taken. */
357 k
= recvmsg ( d
-> storage_socket
[ 0 ], & mh
, MSG_DONTWAIT
| MSG_NOSIGNAL
| MSG_CMSG_CLOEXEC
);
361 cmsg
= cmsg_find (& mh
, SOL_SOCKET
, SCM_RIGHTS
, CMSG_LEN ( sizeof ( int )));
363 lock_fd
= *( int *) CMSG_DATA ( cmsg
);
365 cmsg_close_all (& mh
); /* just in case... */
368 * ret_lock_fd
= lock_fd
;
373 static int dynamic_user_push ( DynamicUser
* d
, uid_t uid
, int lock_fd
) {
374 struct iovec iov
= IOVEC_INIT (& uid
, sizeof ( uid
));
376 struct cmsghdr cmsghdr
;
377 uint8_t buf
[ CMSG_SPACE ( sizeof ( int ))];
380 . msg_control
= & control
,
381 . msg_controllen
= sizeof ( control
),
389 /* Store the UID and lock_fd in the storage socket. This should be called with the socket pair lock taken. */
392 struct cmsghdr
* cmsg
;
394 cmsg
= CMSG_FIRSTHDR (& mh
);
395 cmsg
-> cmsg_level
= SOL_SOCKET
;
396 cmsg
-> cmsg_type
= SCM_RIGHTS
;
397 cmsg
-> cmsg_len
= CMSG_LEN ( sizeof ( int ));
398 memcpy ( CMSG_DATA ( cmsg
), & lock_fd
, sizeof ( int ));
400 mh
. msg_controllen
= CMSG_SPACE ( sizeof ( int ));
402 mh
. msg_control
= NULL
;
403 mh
. msg_controllen
= 0 ;
406 k
= sendmsg ( d
-> storage_socket
[ 1 ], & mh
, MSG_DONTWAIT
| MSG_NOSIGNAL
);
413 static void unlink_uid_lock ( int lock_fd
, uid_t uid
, const char * name
) {
414 char lock_path
[ strlen ( "/run/systemd/dynamic-uid/" ) + DECIMAL_STR_MAX ( uid_t
) + 1 ];
419 xsprintf ( lock_path
, "/run/systemd/dynamic-uid/" UID_FMT
, uid
);
420 ( void ) unlink ( lock_path
);
422 ( void ) make_uid_symlinks ( uid
, name
, false ); /* remove direct lookup symlinks */
425 static int lockfp ( int fd
, int * fd_lock
) {
426 if ( lockf ( fd
, F_LOCK
, 0 ) < 0 )
432 static void unlockfp ( int * fd_lock
) {
435 lockf (* fd_lock
, F_ULOCK
, 0 );
439 static int dynamic_user_realize (
441 char ** suggested_dirs
,
442 uid_t
* ret_uid
, gid_t
* ret_gid
,
445 _cleanup_ ( unlockfp
) int storage_socket0_lock
= - 1 ;
446 _cleanup_close_
int uid_lock_fd
= - 1 ;
447 _cleanup_close_
int etc_passwd_lock_fd
= - 1 ;
448 uid_t num
= UID_INVALID
; /* a uid if is_user, and a gid otherwise */
449 gid_t gid
= GID_INVALID
; /* a gid if is_user, ignored otherwise */
453 assert ( is_user
== !! ret_uid
);
456 /* Acquire a UID for the user name. This will allocate a UID for the user name if the user doesn't exist
457 * yet. If it already exists its existing UID/GID will be reused. */
459 r
= lockfp ( d
-> storage_socket
[ 0 ], & storage_socket0_lock
);
463 r
= dynamic_user_pop ( d
, & num
, & uid_lock_fd
);
471 /* OK, nothing stored yet, let's try to find something useful. While we are working on this release the
472 * lock however, so that nobody else blocks on our NSS lookups. */
473 unlockfp (& storage_socket0_lock
);
475 /* Let's see if a proper, static user or group by this name exists. Try to take the lock on
476 * /etc/passwd, if that fails with EROFS then /etc is read-only. In that case it's fine if we don't
477 * take the lock, given that users can't be added there anyway in this case. */
478 etc_passwd_lock_fd
= take_etc_passwd_lock ( NULL
);
479 if ( etc_passwd_lock_fd
< 0 && etc_passwd_lock_fd
!= - EROFS
)
480 return etc_passwd_lock_fd
;
482 /* First, let's parse this as numeric UID */
483 r
= parse_uid ( d
-> name
, & num
);
489 /* OK, this is not a numeric UID. Let's see if there's a user by this name */
490 p
= getpwnam ( d
-> name
);
495 /* if the user does not exist but the group with the same name exists, refuse operation */
496 g
= getgrnam ( d
-> name
);
501 /* Let's see if there's a group by this name */
502 g
= getgrnam ( d
-> name
);
504 num
= ( uid_t
) g
-> gr_gid
;
506 /* if the group does not exist but the user with the same name exists, refuse operation */
507 p
= getpwnam ( d
-> name
);
514 if ( num
== UID_INVALID
) {
515 /* No static UID assigned yet, excellent. Let's pick a new dynamic one, and lock it. */
517 uid_lock_fd
= pick_uid ( suggested_dirs
, d
-> name
, & num
);
522 /* So, we found a working UID/lock combination. Let's see if we actually still need it. */
523 r
= lockfp ( d
-> storage_socket
[ 0 ], & storage_socket0_lock
);
525 unlink_uid_lock ( uid_lock_fd
, num
, d
-> name
);
529 r
= dynamic_user_pop ( d
, & new_uid
, & new_uid_lock_fd
);
532 /* OK, something bad happened, let's get rid of the bits we acquired. */
533 unlink_uid_lock ( uid_lock_fd
, num
, d
-> name
);
537 /* Great! Nothing is stored here, still. Store our newly acquired data. */
539 /* Hmm, so as it appears there's now something stored in the storage socket. Throw away what we
540 * acquired, and use what's stored now. */
542 unlink_uid_lock ( uid_lock_fd
, num
, d
-> name
);
543 safe_close ( uid_lock_fd
);
546 uid_lock_fd
= new_uid_lock_fd
;
550 /* If the UID/GID was already allocated dynamically, push the data we popped out back in. If it was already
551 * allocated statically, push the UID back too, but do not push the lock fd in. If we allocated the UID
552 * dynamically right here, push that in along with the lock fd for it. */
553 r
= dynamic_user_push ( d
, num
, uid_lock_fd
);
559 * ret_gid
= gid
!= GID_INVALID
? gid
: num
;
566 static int dynamic_user_current ( DynamicUser
* d
, uid_t
* ret
) {
567 _cleanup_ ( unlockfp
) int storage_socket0_lock
= - 1 ;
568 _cleanup_close_
int lock_fd
= - 1 ;
575 /* Get the currently assigned UID for the user, if there's any. This simply pops the data from the storage socket, and pushes it back in right-away. */
577 r
= lockfp ( d
-> storage_socket
[ 0 ], & storage_socket0_lock
);
581 r
= dynamic_user_pop ( d
, & uid
, & lock_fd
);
585 r
= dynamic_user_push ( d
, uid
, lock_fd
);
593 static DynamicUser
* dynamic_user_ref ( DynamicUser
* d
) {
597 assert ( d
-> n_ref
> 0 );
603 static DynamicUser
* dynamic_user_unref ( DynamicUser
* d
) {
607 /* Note that this doesn't actually release any resources itself. If a dynamic user should be fully destroyed
608 * and its UID released, use dynamic_user_destroy() instead. NB: the dynamic user table may contain entries
609 * with no references, which is commonly the case right before a daemon reload. */
611 assert ( d
-> n_ref
> 0 );
617 static int dynamic_user_close ( DynamicUser
* d
) {
618 _cleanup_ ( unlockfp
) int storage_socket0_lock
= - 1 ;
619 _cleanup_close_
int lock_fd
= - 1 ;
623 /* Release the user ID, by releasing the lock on it, and emptying the storage socket. After this the user is
624 * unrealized again, much like it was after it the DynamicUser object was first allocated. */
626 r
= lockfp ( d
-> storage_socket
[ 0 ], & storage_socket0_lock
);
630 r
= dynamic_user_pop ( d
, & uid
, & lock_fd
);
632 /* User wasn't realized yet, nothing to do. */
637 /* This dynamic user was realized and dynamically allocated. In this case, let's remove the lock file. */
638 unlink_uid_lock ( lock_fd
, uid
, d
-> name
);
642 static DynamicUser
* dynamic_user_destroy ( DynamicUser
* d
) {
646 /* Drop a reference to a DynamicUser object, and destroy the user completely if this was the last
647 * reference. This is called whenever a service is shut down and wants its dynamic UID gone. Note that
648 * dynamic_user_unref() is what is called whenever a service is simply freed, for example during a reload
649 * cycle, where the dynamic users should not be destroyed, but our datastructures should. */
651 dynamic_user_unref ( d
);
656 ( void ) dynamic_user_close ( d
);
657 return dynamic_user_free ( d
);
660 int dynamic_user_serialize ( Manager
* m
, FILE * f
, FDSet
* fds
) {
668 /* Dump the dynamic user database into the manager serialization, to deal with daemon reloads. */
670 HASHMAP_FOREACH ( d
, m
-> dynamic_users
, i
) {
673 copy0
= fdset_put_dup ( fds
, d
-> storage_socket
[ 0 ]);
677 copy1
= fdset_put_dup ( fds
, d
-> storage_socket
[ 1 ]);
681 fprintf ( f
, "dynamic-user=%s %i %i \n " , d
-> name
, copy0
, copy1
);
687 void dynamic_user_deserialize_one ( Manager
* m
, const char * value
, FDSet
* fds
) {
688 _cleanup_free_
char * name
= NULL
, * s0
= NULL
, * s1
= NULL
;
695 /* Parse the serialization again, after a daemon reload */
697 r
= extract_many_words (& value
, NULL
, 0 , & name
, & s0
, & s1
, NULL
);
698 if ( r
!= 3 || ! isempty ( value
)) {
699 log_debug ( "Unable to parse dynamic user line." );
703 if ( safe_atoi ( s0
, & fd0
) < 0 || ! fdset_contains ( fds
, fd0
)) {
704 log_debug ( "Unable to process dynamic user fd specification." );
708 if ( safe_atoi ( s1
, & fd1
) < 0 || ! fdset_contains ( fds
, fd1
)) {
709 log_debug ( "Unable to process dynamic user fd specification." );
713 r
= dynamic_user_add ( m
, name
, ( int []) { fd0
, fd1
}, NULL
);
715 log_debug_errno ( r
, "Failed to add dynamic user: %m" );
719 ( void ) fdset_remove ( fds
, fd0
);
720 ( void ) fdset_remove ( fds
, fd1
);
723 void dynamic_user_vacuum ( Manager
* m
, bool close_user
) {
729 /* Empty the dynamic user database, optionally cleaning up orphaned dynamic users, i.e. destroy and free users
730 * to which no reference exist. This is called after a daemon reload finished, in order to destroy users which
731 * might not be referenced anymore. */
733 HASHMAP_FOREACH ( d
, m
-> dynamic_users
, i
) {
738 log_debug ( "Removing orphaned dynamic user %s" , d
-> name
);
739 ( void ) dynamic_user_close ( d
);
742 dynamic_user_free ( d
);
746 int dynamic_user_lookup_uid ( Manager
* m
, uid_t uid
, char ** ret
) {
747 char lock_path
[ strlen ( "/run/systemd/dynamic-uid/" ) + DECIMAL_STR_MAX ( uid_t
) + 1 ];
748 _cleanup_free_
char * user
= NULL
;
755 /* A friendly way to translate a dynamic user's UID into a name. */
756 if (! uid_is_dynamic ( uid
))
759 xsprintf ( lock_path
, "/run/systemd/dynamic-uid/" UID_FMT
, uid
);
760 r
= read_one_line_file ( lock_path
, & user
);
766 /* The lock file might be stale, hence let's verify the data before we return it */
767 r
= dynamic_user_lookup_name ( m
, user
, & check_uid
);
770 if ( check_uid
!= uid
) /* lock file doesn't match our own idea */
779 int dynamic_user_lookup_name ( Manager
* m
, const char * name
, uid_t
* ret
) {
787 /* A friendly call for translating a dynamic user's name into its UID */
789 d
= hashmap_get ( m
-> dynamic_users
, name
);
793 r
= dynamic_user_current ( d
, ret
);
794 if ( r
== - EAGAIN
) /* not realized yet? */
800 int dynamic_creds_acquire ( DynamicCreds
* creds
, Manager
* m
, const char * user
, const char * group
) {
801 bool acquired
= false ;
807 /* A DynamicUser object encapsulates an allocation of both a UID and a GID for a specific name. However, some
808 * services use different user and groups. For cases like that there's DynamicCreds containing a pair of user
809 * and group. This call allocates a pair. */
811 if (! creds
-> user
&& user
) {
812 r
= dynamic_user_acquire ( m
, user
, & creds
-> user
);
821 if ( creds
-> user
&& (! group
|| streq_ptr ( user
, group
)))
822 creds
-> group
= dynamic_user_ref ( creds
-> user
);
824 r
= dynamic_user_acquire ( m
, group
, & creds
-> group
);
827 creds
-> user
= dynamic_user_unref ( creds
-> user
);
836 int dynamic_creds_realize ( DynamicCreds
* creds
, char ** suggested_paths
, uid_t
* uid
, gid_t
* gid
) {
837 uid_t u
= UID_INVALID
;
838 gid_t g
= GID_INVALID
;
845 /* Realize both the referenced user and group */
848 r
= dynamic_user_realize ( creds
-> user
, suggested_paths
, & u
, & g
, true );
853 if ( creds
-> group
&& creds
-> group
!= creds
-> user
) {
854 r
= dynamic_user_realize ( creds
-> group
, suggested_paths
, NULL
, & g
, false );
864 void dynamic_creds_unref ( DynamicCreds
* creds
) {
867 creds
-> user
= dynamic_user_unref ( creds
-> user
);
868 creds
-> group
= dynamic_user_unref ( creds
-> group
);
871 void dynamic_creds_destroy ( DynamicCreds
* creds
) {
874 creds
-> user
= dynamic_user_destroy ( creds
-> user
);
875 creds
-> group
= dynamic_user_destroy ( creds
-> group
);