/*-
 * 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);
}