diff --git a/src/basic/missing.h b/src/basic/missing.h
index a6b10b16bb7..1280e6c4100 100644
--- a/src/basic/missing.h
+++ b/src/basic/missing.h
@@ -621,6 +621,10 @@ struct btrfs_ioctl_quota_ctl_args {
# define SO_REUSEPORT 15
#endif
+#ifndef SO_PEERGROUPS
+# define SO_PEERGROUPS 59
+#endif
+
#ifndef EVIOCREVOKE
# define EVIOCREVOKE _IOW('E', 0x91, int)
#endif
diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c
index 54d526a5217..56e0e8e434e 100644
--- a/src/basic/socket-util.c
+++ b/src/basic/socket-util.c
@@ -1019,6 +1019,39 @@ int getpeersec(int fd, char **ret) {
return 0;
}
+int getpeergroups(int fd, gid_t **ret) {
+ socklen_t n = sizeof(gid_t) * 64;
+ _cleanup_free_ gid_t *d = NULL;
+
+ assert(fd);
+ assert(ret);
+
+ for (;;) {
+ d = malloc(n);
+ if (!d)
+ return -ENOMEM;
+
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERGROUPS, d, &n) >= 0)
+ break;
+
+ if (errno != ERANGE)
+ return -errno;
+
+ d = mfree(d);
+ }
+
+ assert_se(n % sizeof(gid_t) == 0);
+ n /= sizeof(gid_t);
+
+ if ((socklen_t) (int) n != n)
+ return -E2BIG;
+
+ *ret = d;
+ d = NULL;
+
+ return (int) n;
+}
+
int send_one_fd_sa(
int transport_fd,
int fd,
diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h
index ba5be9b67d1..83af91dbef2 100644
--- a/src/basic/socket-util.h
+++ b/src/basic/socket-util.h
@@ -138,6 +138,7 @@ bool address_label_valid(const char *p);
int getpeercred(int fd, struct ucred *ucred);
int getpeersec(int fd, char **ret);
+int getpeergroups(int fd, gid_t **ret);
int send_one_fd_sa(int transport_fd,
int fd,
diff --git a/src/test/test-socket-util.c b/src/test/test-socket-util.c
index 6a91baf6d29..201ce6667de 100644
--- a/src/test/test-socket-util.c
+++ b/src/test/test-socket-util.c
@@ -18,6 +18,10 @@
along with systemd; If not, see .
***/
+#include
+#include
+#include
+
#include "alloc-util.h"
#include "async.h"
#include "fd-util.h"
@@ -474,6 +478,68 @@ static void test_in_addr_is_multicast(void) {
assert_se(in_addr_is_multicast(f, &b) == 0);
}
+static void test_getpeercred_getpeergroups(void) {
+ int r;
+
+ r = safe_fork("(getpeercred)", FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL);
+ assert_se(r >= 0);
+
+ if (r == 0) {
+ static const gid_t gids[] = { 3, 4, 5, 6, 7 };
+ gid_t *test_gids;
+ gid_t *peer_groups = NULL;
+ size_t n_test_gids;
+ uid_t test_uid;
+ gid_t test_gid;
+ struct ucred ucred;
+ int pair[2];
+
+ if (geteuid() == 0) {
+ test_uid = 1;
+ test_gid = 2;
+ test_gids = (gid_t*) gids;
+ n_test_gids = ELEMENTSOF(gids);
+
+ assert_se(setgroups(n_test_gids, test_gids) >= 0);
+ assert_se(setresgid(test_gid, test_gid, test_gid) >= 0);
+ assert_se(setresuid(test_uid, test_uid, test_uid) >= 0);
+
+ } else {
+ long ngroups_max;
+
+ test_uid = getuid();
+ test_gid = getgid();
+
+ ngroups_max = sysconf(_SC_NGROUPS_MAX);
+ assert(ngroups_max > 0);
+
+ test_gids = newa(gid_t, ngroups_max);
+
+ r = getgroups(ngroups_max, test_gids);
+ assert_se(r >= 0);
+ n_test_gids = (size_t) r;
+ }
+
+ assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, pair) >= 0);
+
+ assert_se(getpeercred(pair[0], &ucred) >= 0);
+
+ assert_se(ucred.uid == test_uid);
+ assert_se(ucred.gid == test_gid);
+ assert_se(ucred.pid == getpid_cached());
+
+ r = getpeergroups(pair[0], &peer_groups);
+ assert_se(r >= 0 || IN_SET(r, -EOPNOTSUPP, -ENOPROTOOPT));
+
+ if (r >= 0) {
+ assert_se((size_t) r == n_test_gids);
+ assert_se(memcmp(peer_groups, test_gids, sizeof(gid_t) * n_test_gids) == 0);
+ }
+
+ safe_close_pair(pair);
+ }
+}
+
int main(int argc, char *argv[]) {
log_set_max_level(LOG_DEBUG);
@@ -502,5 +568,7 @@ int main(int argc, char *argv[]) {
test_in_addr_is_multicast();
+ test_getpeercred_getpeergroups();
+
return 0;
}