root/tools/regression/sockets/unix_cmsg/uc_common.c
/*-
 * Copyright (c) 2005 Andrey Simonenko
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <err.h>
#include <fcntl.h>
#include <errno.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

#include "uc_common.h"

#ifndef LISTENQ
# define LISTENQ        1
#endif

#ifndef TIMEOUT
# define TIMEOUT        2
#endif

#define SYNC_SERVER     0
#define SYNC_CLIENT     1
#define SYNC_RECV       0
#define SYNC_SEND       1

#define LOGMSG_SIZE     128

void
uc_output(const char *format, ...)
{
        char buf[LOGMSG_SIZE];
        va_list ap;

        va_start(ap, format);
        if (vsnprintf(buf, sizeof(buf), format, ap) < 0)
                err(EXIT_FAILURE, "output: vsnprintf failed");
        write(STDOUT_FILENO, buf, strlen(buf));
        va_end(ap);
}

void
uc_logmsg(const char *format, ...)
{
        char buf[LOGMSG_SIZE];
        va_list ap;
        int errno_save;

        errno_save = errno;
        va_start(ap, format);
        if (vsnprintf(buf, sizeof(buf), format, ap) < 0)
                err(EXIT_FAILURE, "logmsg: vsnprintf failed");
        if (errno_save == 0)
                uc_output("%s: %s\n", uc_cfg.proc_name, buf);
        else
                uc_output("%s: %s: %s\n", uc_cfg.proc_name, buf,
                    strerror(errno_save));
        va_end(ap);
        errno = errno_save;
}

void
uc_vlogmsgx(const char *format, va_list ap)
{
        char buf[LOGMSG_SIZE];

        if (vsnprintf(buf, sizeof(buf), format, ap) < 0)
                err(EXIT_FAILURE, "uc_logmsgx: vsnprintf failed");
        uc_output("%s: %s\n", uc_cfg.proc_name, buf);
}

void
uc_logmsgx(const char *format, ...)
{
        va_list ap;

        va_start(ap, format);
        uc_vlogmsgx(format, ap);
        va_end(ap);
}

void
uc_dbgmsg(const char *format, ...)
{
        va_list ap;

        if (uc_cfg.debug) {
                va_start(ap, format);
                uc_vlogmsgx(format, ap);
                va_end(ap);
        }
}

int
uc_socket_create(void)
{
        struct timeval tv;
        int fd;

        fd = socket(PF_LOCAL, uc_cfg.sock_type, 0);
        if (fd < 0) {
                uc_logmsg("socket_create: socket(PF_LOCAL, %s, 0)", uc_cfg.sock_type_str);
                return (-1);
        }
        if (uc_cfg.server_flag)
                uc_cfg.serv_sock_fd = fd;

        tv.tv_sec = TIMEOUT;
        tv.tv_usec = 0;
        if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0 ||
            setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0) {
                uc_logmsg("socket_create: setsockopt(SO_RCVTIMEO/SO_SNDTIMEO)");
                goto failed;
        }

        if (uc_cfg.server_flag) {
                if (bind(fd, (struct sockaddr *)&uc_cfg.serv_addr_sun,
                    uc_cfg.serv_addr_sun.sun_len) < 0) {
                        uc_logmsg("socket_create: bind(%s)",
                            uc_cfg.serv_addr_sun.sun_path);
                        goto failed;
                }
                if (uc_cfg.sock_type == SOCK_STREAM) {
                        int val;

                        if (listen(fd, LISTENQ) < 0) {
                                uc_logmsg("socket_create: listen");
                                goto failed;
                        }
                        val = fcntl(fd, F_GETFL, 0);
                        if (val < 0) {
                                uc_logmsg("socket_create: fcntl(F_GETFL)");
                                goto failed;
                        }
                        if (fcntl(fd, F_SETFL, val | O_NONBLOCK) < 0) {
                                uc_logmsg("socket_create: fcntl(F_SETFL)");
                                goto failed;
                        }
                }
        }

        return (fd);

failed:
        if (close(fd) < 0)
                uc_logmsg("socket_create: close");
        if (uc_cfg.server_flag)
                if (unlink(uc_cfg.serv_addr_sun.sun_path) < 0)
                        uc_logmsg("socket_close: unlink(%s)",
                            uc_cfg.serv_addr_sun.sun_path);
        return (-1);
}

int
uc_socket_close(int fd)
{
        int rv;

        rv = 0;
        if (close(fd) < 0) {
                uc_logmsg("socket_close: close");
                rv = -1;
        }
        if (uc_cfg.server_flag && fd == uc_cfg.serv_sock_fd)
                if (unlink(uc_cfg.serv_addr_sun.sun_path) < 0) {
                        uc_logmsg("socket_close: unlink(%s)",
                            uc_cfg.serv_addr_sun.sun_path);
                        rv = -1;
                }
        return (rv);
}

int
uc_socket_connect(int fd)
{
        uc_dbgmsg("connect");

        if (connect(fd, (struct sockaddr *)&uc_cfg.serv_addr_sun,
            uc_cfg.serv_addr_sun.sun_len) < 0) {
                uc_logmsg("socket_connect: connect(%s)", uc_cfg.serv_addr_sun.sun_path);
                return (-1);
        }
        return (0);
}

int
uc_sync_recv(void)
{
        ssize_t ssize;
        int fd;
        char buf;

        uc_dbgmsg("sync: wait");

        fd = uc_cfg.sync_fd[uc_cfg.server_flag ? SYNC_SERVER : SYNC_CLIENT][SYNC_RECV];

        ssize = read(fd, &buf, 1);
        if (ssize < 0) {
                uc_logmsg("sync_recv: read");
                return (-1);
        }
        if (ssize < 1) {
                uc_logmsgx("sync_recv: read %zd of 1 byte", ssize);
                return (-1);
        }

        uc_dbgmsg("sync: received");

        return (0);
}

int
uc_sync_send(void)
{
        ssize_t ssize;
        int fd;

        uc_dbgmsg("sync: send");

        fd = uc_cfg.sync_fd[uc_cfg.server_flag ? SYNC_CLIENT : SYNC_SERVER][SYNC_SEND];

        ssize = write(fd, "", 1);
        if (ssize < 0) {
                uc_logmsg("uc_sync_send: write");
                return (-1);
        }
        if (ssize < 1) {
                uc_logmsgx("uc_sync_send: sent %zd of 1 byte", ssize);
                return (-1);
        }

        return (0);
}

int
uc_message_send(int fd, const struct msghdr *msghdr)
{
        const struct cmsghdr *cmsghdr;
        size_t size;
        ssize_t ssize;

        size = msghdr->msg_iov != 0 ? msghdr->msg_iov->iov_len : 0;
        uc_dbgmsg("send: data size %zu", size);
        uc_dbgmsg("send: msghdr.msg_controllen %u",
            (u_int)msghdr->msg_controllen);
        cmsghdr = CMSG_FIRSTHDR(msghdr);
        if (cmsghdr != NULL)
                uc_dbgmsg("send: cmsghdr.cmsg_len %u",
                    (u_int)cmsghdr->cmsg_len);

        ssize = sendmsg(fd, msghdr, 0);
        if (ssize < 0) {
                uc_logmsg("message_send: sendmsg");
                return (-1);
        }
        if ((size_t)ssize != size) {
                uc_logmsgx("message_send: sendmsg: sent %zd of %zu bytes",
                    ssize, size);
                return (-1);
        }

        if (!uc_cfg.send_data_flag)
                if (uc_sync_send() < 0)
                        return (-1);

        return (0);
}

int
uc_message_sendn(int fd, struct msghdr *msghdr)
{
        u_int i;

        for (i = 1; i <= uc_cfg.ipc_msg.msg_num; ++i) {
                uc_dbgmsg("message #%u", i);
                if (uc_message_send(fd, msghdr) < 0)
                        return (-1);
        }
        return (0);
}

int
uc_message_recv(int fd, struct msghdr *msghdr)
{
        const struct cmsghdr *cmsghdr;
        size_t size;
        ssize_t ssize;

        if (!uc_cfg.send_data_flag)
                if (uc_sync_recv() < 0)
                        return (-1);

        size = msghdr->msg_iov != NULL ? msghdr->msg_iov->iov_len : 0;
        ssize = recvmsg(fd, msghdr, MSG_WAITALL);
        if (ssize < 0) {
                uc_logmsg("message_recv: recvmsg");
                return (-1);
        }
        if ((size_t)ssize != size) {
                uc_logmsgx("message_recv: recvmsg: received %zd of %zu bytes",
                    ssize, size);
                return (-1);
        }

        uc_dbgmsg("recv: data size %zd", ssize);
        uc_dbgmsg("recv: msghdr.msg_controllen %u",
            (u_int)msghdr->msg_controllen);
        cmsghdr = CMSG_FIRSTHDR(msghdr);
        if (cmsghdr != NULL)
                uc_dbgmsg("recv: cmsghdr.cmsg_len %u",
                    (u_int)cmsghdr->cmsg_len);

        if (memcmp(uc_cfg.ipc_msg.buf_recv, uc_cfg.ipc_msg.buf_send, size) != 0) {
                uc_logmsgx("message_recv: received message has wrong content");
                return (-1);
        }

        return (0);
}

int
uc_socket_accept(int listenfd)
{
        fd_set rset;
        struct timeval tv;
        int fd, rv, val;

        uc_dbgmsg("accept");

        FD_ZERO(&rset);
        FD_SET(listenfd, &rset);
        tv.tv_sec = TIMEOUT;
        tv.tv_usec = 0;
        rv = select(listenfd + 1, &rset, (fd_set *)NULL, (fd_set *)NULL, &tv);
        if (rv < 0) {
                uc_logmsg("socket_accept: select");
                return (-1);
        }
        if (rv == 0) {
                uc_logmsgx("socket_accept: select timeout");
                return (-1);
        }

        fd = accept(listenfd, (struct sockaddr *)NULL, (socklen_t *)NULL);
        if (fd < 0) {
                uc_logmsg("socket_accept: accept");
                return (-1);
        }

        val = fcntl(fd, F_GETFL, 0);
        if (val < 0) {
                uc_logmsg("socket_accept: fcntl(F_GETFL)");
                goto failed;
        }
        if (fcntl(fd, F_SETFL, val & ~O_NONBLOCK) < 0) {
                uc_logmsg("socket_accept: fcntl(F_SETFL)");
                goto failed;
        }

        return (fd);

failed:
        if (close(fd) < 0)
                uc_logmsg("socket_accept: close");
        return (-1);
}

int
uc_check_msghdr(const struct msghdr *msghdr, size_t size)
{
        if (msghdr->msg_flags & MSG_TRUNC) {
                uc_logmsgx("msghdr.msg_flags has MSG_TRUNC");
                return (-1);
        }
        if (msghdr->msg_flags & MSG_CTRUNC) {
                uc_logmsgx("msghdr.msg_flags has MSG_CTRUNC");
                return (-1);
        }
        if (msghdr->msg_controllen < size) {
                uc_logmsgx("msghdr.msg_controllen %u < %zu",
                    (u_int)msghdr->msg_controllen, size);
                return (-1);
        }
        if (msghdr->msg_controllen > 0 && size == 0) {
                uc_logmsgx("msghdr.msg_controllen %u > 0",
                    (u_int)msghdr->msg_controllen);
                return (-1);
        }
        return (0);
}

int
uc_check_cmsghdr(const struct cmsghdr *cmsghdr, int type, size_t size)
{
        if (cmsghdr == NULL) {
                uc_logmsgx("cmsghdr is NULL");
                return (-1);
        }
        if (cmsghdr->cmsg_level != SOL_SOCKET) {
                uc_logmsgx("cmsghdr.cmsg_level %d != SOL_SOCKET",
                    cmsghdr->cmsg_level);
                return (-1);
        }
        if (cmsghdr->cmsg_type != type) {
                uc_logmsgx("cmsghdr.cmsg_type %d != %d",
                    cmsghdr->cmsg_type, type);
                return (-1);
        }
        if (cmsghdr->cmsg_len != CMSG_LEN(size)) {
                uc_logmsgx("cmsghdr.cmsg_len %u != %zu",
                    (u_int)cmsghdr->cmsg_len, CMSG_LEN(size));
                return (-1);
        }
        return (0);
}

static void
uc_msghdr_init_generic(struct msghdr *msghdr, struct iovec *iov, void *cmsg_data)
{
        msghdr->msg_name = NULL;
        msghdr->msg_namelen = 0;
        if (uc_cfg.send_data_flag) {
                iov->iov_base = uc_cfg.server_flag ?
                    uc_cfg.ipc_msg.buf_recv : uc_cfg.ipc_msg.buf_send;
                iov->iov_len = uc_cfg.ipc_msg.buf_size;
                msghdr->msg_iov = iov;
                msghdr->msg_iovlen = 1;
        } else {
                msghdr->msg_iov = NULL;
                msghdr->msg_iovlen = 0;
        }
        msghdr->msg_control = cmsg_data;
        msghdr->msg_flags = 0;
}

void
uc_msghdr_init_server(struct msghdr *msghdr, struct iovec *iov,
    void *cmsg_data, size_t cmsg_size)
{
        uc_msghdr_init_generic(msghdr, iov, cmsg_data);
        msghdr->msg_controllen = cmsg_size;
        uc_dbgmsg("init: data size %zu", msghdr->msg_iov != NULL ?
            msghdr->msg_iov->iov_len : (size_t)0);
        uc_dbgmsg("init: msghdr.msg_controllen %u",
            (u_int)msghdr->msg_controllen);
}

void
uc_msghdr_init_client(struct msghdr *msghdr, struct iovec *iov,
    void *cmsg_data, size_t cmsg_size, int type, size_t arr_size)
{
        struct cmsghdr *cmsghdr;

        uc_msghdr_init_generic(msghdr, iov, cmsg_data);
        if (cmsg_data != NULL) {
                if (uc_cfg.send_array_flag)
                        uc_dbgmsg("sending an array");
                else
                        uc_dbgmsg("sending a scalar");
                msghdr->msg_controllen = uc_cfg.send_array_flag ?
                    cmsg_size : CMSG_SPACE(0);
                cmsghdr = CMSG_FIRSTHDR(msghdr);
                cmsghdr->cmsg_level = SOL_SOCKET;
                cmsghdr->cmsg_type = type;
                cmsghdr->cmsg_len = CMSG_LEN(uc_cfg.send_array_flag ? arr_size : 0);
        } else
                msghdr->msg_controllen = 0;
}

int
uc_client_fork(void)
{
        int fd1, fd2;

        if (pipe(uc_cfg.sync_fd[SYNC_SERVER]) < 0 ||
            pipe(uc_cfg.sync_fd[SYNC_CLIENT]) < 0) {
                uc_logmsg("client_fork: pipe");
                return (-1);
        }
        uc_cfg.client_pid = fork();
        if (uc_cfg.client_pid == (pid_t)-1) {
                uc_logmsg("client_fork: fork");
                return (-1);
        }
        if (uc_cfg.client_pid == 0) {
                uc_cfg.proc_name = "CLIENT";
                uc_cfg.server_flag = false;
                fd1 = uc_cfg.sync_fd[SYNC_SERVER][SYNC_RECV];
                fd2 = uc_cfg.sync_fd[SYNC_CLIENT][SYNC_SEND];
        } else {
                fd1 = uc_cfg.sync_fd[SYNC_SERVER][SYNC_SEND];
                fd2 = uc_cfg.sync_fd[SYNC_CLIENT][SYNC_RECV];
        }
        if (close(fd1) < 0 || close(fd2) < 0) {
                uc_logmsg("client_fork: close");
                return (-1);
        }
        return (uc_cfg.client_pid != 0);
}

void
uc_client_exit(int rv)
{
        if (close(uc_cfg.sync_fd[SYNC_SERVER][SYNC_SEND]) < 0 ||
            close(uc_cfg.sync_fd[SYNC_CLIENT][SYNC_RECV]) < 0) {
                uc_logmsg("client_exit: close");
                rv = -1;
        }
        rv = rv == 0 ? EXIT_SUCCESS : -rv;
        uc_dbgmsg("exit: code %d", rv);
        _exit(rv);
}

int
uc_client_wait(void)
{
        int status;
        pid_t pid;

        uc_dbgmsg("waiting for client");

        if (close(uc_cfg.sync_fd[SYNC_SERVER][SYNC_RECV]) < 0 ||
            close(uc_cfg.sync_fd[SYNC_CLIENT][SYNC_SEND]) < 0) {
                uc_logmsg("client_wait: close");
                return (-1);
        }

        pid = waitpid(uc_cfg.client_pid, &status, 0);
        if (pid == (pid_t)-1) {
                uc_logmsg("client_wait: waitpid");
                return (-1);
        }

        if (WIFEXITED(status)) {
                if (WEXITSTATUS(status) != EXIT_SUCCESS) {
                        uc_logmsgx("client exit status is %d",
                            WEXITSTATUS(status));
                        return (-WEXITSTATUS(status));
                }
        } else {
                if (WIFSIGNALED(status))
                        uc_logmsgx("abnormal termination of client, signal %d%s",
                            WTERMSIG(status), WCOREDUMP(status) ?
                            " (core file generated)" : "");
                else
                        uc_logmsgx("termination of client, unknown status");
                return (-1);
        }

        return (0);
}

int
uc_check_groups(const char *gid_arr_str, const gid_t *gid_arr,
    const char *gid_num_str, int gid_num, bool all_gids)
{
        int i;

        for (i = 0; i < gid_num; ++i)
                uc_dbgmsg("%s[%d] %lu", gid_arr_str, i, (u_long)gid_arr[i]);

        if (all_gids) {
                if (gid_num != uc_cfg.proc_cred.gid_num) {
                        uc_logmsgx("%s %d != %d", gid_num_str, gid_num,
                            uc_cfg.proc_cred.gid_num);
                        return (-1);
                }
        } else {
                if (gid_num > uc_cfg.proc_cred.gid_num) {
                        uc_logmsgx("%s %d > %d", gid_num_str, gid_num,
                            uc_cfg.proc_cred.gid_num);
                        return (-1);
                }
        }
        if (memcmp(gid_arr, uc_cfg.proc_cred.gid_arr,
            gid_num * sizeof(*gid_arr)) != 0) {
                uc_logmsgx("%s content is wrong", gid_arr_str);
                for (i = 0; i < gid_num; ++i)
                        if (gid_arr[i] != uc_cfg.proc_cred.gid_arr[i]) {
                                uc_logmsgx("%s[%d] %lu != %lu",
                                    gid_arr_str, i, (u_long)gid_arr[i],
                                    (u_long)uc_cfg.proc_cred.gid_arr[i]);
                                break;
                        }
                return (-1);
        }
        return (0);
}

int
uc_check_scm_creds_cmsgcred(struct cmsghdr *cmsghdr)
{
        const struct cmsgcred *cmcred;
        int rc;

        if (uc_check_cmsghdr(cmsghdr, SCM_CREDS, sizeof(struct cmsgcred)) < 0)
                return (-1);

        cmcred = (struct cmsgcred *)CMSG_DATA(cmsghdr);

        uc_dbgmsg("cmsgcred.cmcred_pid %ld", (long)cmcred->cmcred_pid);
        uc_dbgmsg("cmsgcred.cmcred_uid %lu", (u_long)cmcred->cmcred_uid);
        uc_dbgmsg("cmsgcred.cmcred_euid %lu", (u_long)cmcred->cmcred_euid);
        uc_dbgmsg("cmsgcred.cmcred_gid %lu", (u_long)cmcred->cmcred_gid);
        uc_dbgmsg("cmsgcred.cmcred_ngroups %d", cmcred->cmcred_ngroups);

        rc = 0;

        if (cmcred->cmcred_pid != uc_cfg.client_pid) {
                uc_logmsgx("cmsgcred.cmcred_pid %ld != %ld",
                    (long)cmcred->cmcred_pid, (long)uc_cfg.client_pid);
                rc = -1;
        }
        if (cmcred->cmcred_uid != uc_cfg.proc_cred.uid) {
                uc_logmsgx("cmsgcred.cmcred_uid %lu != %lu",
                    (u_long)cmcred->cmcred_uid, (u_long)uc_cfg.proc_cred.uid);
                rc = -1;
        }
        if (cmcred->cmcred_euid != uc_cfg.proc_cred.euid) {
                uc_logmsgx("cmsgcred.cmcred_euid %lu != %lu",
                    (u_long)cmcred->cmcred_euid, (u_long)uc_cfg.proc_cred.euid);
                rc = -1;
        }
        if (cmcred->cmcred_gid != uc_cfg.proc_cred.gid) {
                uc_logmsgx("cmsgcred.cmcred_gid %lu != %lu",
                    (u_long)cmcred->cmcred_gid, (u_long)uc_cfg.proc_cred.gid);
                rc = -1;
        }
        if (cmcred->cmcred_ngroups == 0) {
                uc_logmsgx("cmsgcred.cmcred_ngroups == 0");
                rc = -1;
        }
        if (cmcred->cmcred_ngroups < 0) {
                uc_logmsgx("cmsgcred.cmcred_ngroups %d < 0",
                    cmcred->cmcred_ngroups);
                rc = -1;
        }
        if (cmcred->cmcred_ngroups > CMGROUP_MAX) {
                uc_logmsgx("cmsgcred.cmcred_ngroups %d > %d",
                    cmcred->cmcred_ngroups, CMGROUP_MAX);
                rc = -1;
        }
        if (cmcred->cmcred_groups[0] != uc_cfg.proc_cred.egid) {
                uc_logmsgx("cmsgcred.cmcred_groups[0] %lu != %lu (EGID)",
                    (u_long)cmcred->cmcred_groups[0], (u_long)uc_cfg.proc_cred.egid);
                rc = -1;
        }
        if (uc_check_groups("cmsgcred.cmcred_groups", cmcred->cmcred_groups,
            "cmsgcred.cmcred_ngroups", cmcred->cmcred_ngroups, false) < 0)
                rc = -1;
        return (rc);
}

int
uc_check_scm_creds_sockcred(struct cmsghdr *cmsghdr)
{
        const struct sockcred *sc;
        int rc;

        if (uc_check_cmsghdr(cmsghdr, SCM_CREDS,
            SOCKCREDSIZE(uc_cfg.proc_cred.gid_num)) < 0)
                return (-1);

        sc = (struct sockcred *)CMSG_DATA(cmsghdr);

        rc = 0;

        uc_dbgmsg("sockcred.sc_uid %lu", (u_long)sc->sc_uid);
        uc_dbgmsg("sockcred.sc_euid %lu", (u_long)sc->sc_euid);
        uc_dbgmsg("sockcred.sc_gid %lu", (u_long)sc->sc_gid);
        uc_dbgmsg("sockcred.sc_egid %lu", (u_long)sc->sc_egid);
        uc_dbgmsg("sockcred.sc_ngroups %d", sc->sc_ngroups);

        if (sc->sc_uid != uc_cfg.proc_cred.uid) {
                uc_logmsgx("sockcred.sc_uid %lu != %lu",
                    (u_long)sc->sc_uid, (u_long)uc_cfg.proc_cred.uid);
                rc = -1;
        }
        if (sc->sc_euid != uc_cfg.proc_cred.euid) {
                uc_logmsgx("sockcred.sc_euid %lu != %lu",
                    (u_long)sc->sc_euid, (u_long)uc_cfg.proc_cred.euid);
                rc = -1;
        }
        if (sc->sc_gid != uc_cfg.proc_cred.gid) {
                uc_logmsgx("sockcred.sc_gid %lu != %lu",
                    (u_long)sc->sc_gid, (u_long)uc_cfg.proc_cred.gid);
                rc = -1;
        }
        if (sc->sc_egid != uc_cfg.proc_cred.egid) {
                uc_logmsgx("sockcred.sc_egid %lu != %lu",
                    (u_long)sc->sc_egid, (u_long)uc_cfg.proc_cred.egid);
                rc = -1;
        }
        if (sc->sc_ngroups == 0) {
                uc_logmsgx("sockcred.sc_ngroups == 0");
                rc = -1;
        }
        if (sc->sc_ngroups < 0) {
                uc_logmsgx("sockcred.sc_ngroups %d < 0",
                    sc->sc_ngroups);
                rc = -1;
        }
        if (sc->sc_ngroups != uc_cfg.proc_cred.gid_num) {
                uc_logmsgx("sockcred.sc_ngroups %d != %u",
                    sc->sc_ngroups, uc_cfg.proc_cred.gid_num);
                rc = -1;
        }
        if (uc_check_groups("sockcred.sc_groups", sc->sc_groups,
            "sockcred.sc_ngroups", sc->sc_ngroups, true) < 0)
                rc = -1;
        return (rc);
}