For some reason I assumed for a long time that NetBSD didn't support any kind of socket credentials. However, I recently discovered that it indeed supports them through the LOCAL_CREDS socket option. Unfortunately it behaves quite differently from other methods. This poses some annoying portability problems in applications not designed in the first place to support it (e.g. D-Bus, the specific program I'm fighting right now).
LOCAL_CREDS works as follows:
- The receiver interested in remote credentials uses setsockopt(2) to enable the LOCAL_CREDS option in the socket.
- The sender sends a message through the channel either with write(2) or sendmsg(2). It needn't do anything special other than ensuring that the message is sent after the receiver has enabled the LOCAL_CREDS option.
- The receiver gets the message using recvmsg(2) and parses the out of band data stored in the control buffer: a struct sockcred message that contains the remote credentials (UID, GID, etc.). This does not provide the PID of the remote process though, as other implementations do.
To ensure this restriction there needs to be some kind of synchronization protocol between the two peers. This is illustrated in the following example: it assumes a client/server model and a "go on" message used to synchronize. The server could do:
- Wait for client connection.
- Set LOCAL_CREDS option on remote socket.
- Send a "go on" message to client.
- Wait for a response, which carries the credentials.
- Parse the credentials.
- Connect to server.
- Wait until "go on" message.
- Send any message to the server.
#include <sys/param.h>
#include <sys/types.h>
#include <sys/inttypes.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int
main(void)
{
int sv[2];
int on = 1;
ssize_t len;
struct iovec iov;
struct msghdr msg;
struct {
struct cmsghdr hdr;
struct sockcred cred;
gid_t groups[NGROUPS - 1];
} cmsg;
/*
* Create a pair of interconnected sockets for simplicity:
* sv[0] - Receive end (this program).
* sv[1] - Write end (the remote program, theorically).
*/
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1)
err(EXIT_FAILURE, "socketpair");
/*
* Enable the LOCAL_CREDS option on the reception socket.
*/
if (setsockopt(sv[0], 0, LOCAL_CREDS, &on, sizeof(on)) == -1)
err(EXIT_FAILURE, "setsockopt");
/*
* The remote application writes the message AFTER setsockopt
* has been used by the receiver. If you move this above the
* setsockopt call, you will see how it does not work as
* expected.
*/
if (write(sv[1], &on, sizeof(on)) == -1)
err(EXIT_FAILURE, "write");
/*
* Prepare space to receive the credentials message.
*/
iov.iov_base = &on;
iov.iov_len = 1;
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = &cmsg;
msg.msg_controllen = sizeof(struct cmsghdr) +
SOCKCREDSIZE(NGROUPS);
memset(&cmsg, 0, sizeof(cmsg));
/*
* Receive the message.
*/
len = recvmsg(sv[0], &msg, 0);
if (len < 0)
err(EXIT_FAILURE, "recvmsg");
printf("Got %zu bytesn", len);
/*
* Print out credentials information, if received
* appropriately.
*/
if (cmsg.hdr.cmsg_type == SCM_CREDS) {
printf("UID: %" PRIdMAX "n",
(intmax_t)cmsg.cred.sc_uid);
printf("EUID: %" PRIdMAX "n",
(intmax_t)cmsg.cred.sc_euid);
printf("GID: %" PRIdMAX "n",
(intmax_t)cmsg.cred.sc_gid);
printf("EGID: %" PRIdMAX "n",
(intmax_t)cmsg.cred.sc_egid);
if (cmsg.cred.sc_ngroups > 0) {
int i;
printf("Supplementary groups:");
for (i = 0; i < cmsg.cred.sc_ngroups; i++)
printf(" %" PRIdMAX,
(intmax_t)cmsg.cred.sc_groups[i]);
printf("n");
}
} else
errx(EXIT_FAILURE, "Message did not include credentials");
close(sv[0]);
close(sv[1]);
return EXIT_SUCCESS;
}