9.8 Performing Authentication with Unix Domain Sockets
9.8.1 Problem
Using a Unix domain socket, you want to
find out information about the process that is on the other end of
the connection, such as its user and group IDs.
9.8.2 Solution
Most Unix domain socket implementations provide support for receiving
the credentials of the peer process involved in a Unix domain socket
connection. Using this information, we can discover the user ID and
group ID of the process on the other end of the connection.
Credential information is not passed automatically. For all
implementations, the receiver must explicitly ask for the
information. With some implementations, the information must be
explicitly sent. In general, when you're designing a
system that will exchange credentials, you should be sure to
coordinate on both ends exactly when the credentials will be
requested and sent.
This recipe works on FreeBSD,
Linux, and NetBSD. Unfortunately, not all Unix domain socket
implementations provide support for credentials. At the time of this
writing, the Darwin kernel (the core of MacOS X), OpenBSD, and
Solaris do not support credentials.
9.8.3 Discussion
In addition to the previously mentioned platform support limitations
with credentials, a second problem is that different implementations
exchange the information in different ways. On FreeBSD systems, for
example, the information must be explicitly sent, and the receiver
must be able to handle receiving it. On Linux systems, the
information is automatically sent if the receiver asks for it.
A third problem is that not all implementations pass the same
information. Linux passes the process ID, user ID, and group ID of
the sending process. FreeBSD includes all groups that the process is
a member of, but it does not include the process ID. At a minimum,
you can expect to get the process's user and group
IDs and nothing more.
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#if !defined(linux) && !defined(__NetBSD__)
#include <sys/ucred.h>
#endif
#ifndef SCM_CREDS
#define SCM_CREDS SCM_CREDENTIALS
#endif
#ifndef linux
# ifndef __NetBSD__
# define SPC_PEER_UID(c) ((c)->cr_uid)
# define SPC_PEER_GID(c) ((c)->cr_groups[0])
# else
# define SPC_PEER_UID(c) ((c)->sc_uid)
# define SPC_PEER_GID(c) ((c)->sc_gid)
# endif
#else
# define SPC_PEER_UID(c) ((c)->uid)
# define SPC_PEER_GID(c) ((c)->gid)
#endif
#ifdef __NetBSD__
typedef struct sockcred spc_credentials;
#else
typedef struct ucred spc_credentials;
#endif
spc_credentials *spc_get_credentials(int sd) {
int nb, sync;
char ctrl[CMSG_SPACE(sizeof(struct ucred))];
size_t size;
struct iovec iov[1] = { { 0, 0 } };
struct msghdr msg = { 0, 0, iov, 1, ctrl, sizeof(ctrl), 0 };
struct cmsghdr *cmptr;
spc_credentials *credentials;
#ifdef LOCAL_CREDS
nb = 1;
if (setsockopt(sd, 0, LOCAL_CREDS, &nb, sizeof(nb)) == -1) return 0;
#else
#ifdef SO_PASSCRED
nb = 1;
if (setsockopt(sd, SOL_SOCKET, SO_PASSCRED, &nb, sizeof(nb)) == -1)
return 0;
#endif
#endif
do {
msg.msg_iov->iov_base = (void *)&sync;
msg.msg_iov->iov_len = sizeof(sync);
nb = recvmsg(sd, &msg, 0);
} while (nb == -1 && (errno == EINTR || errno == EAGAIN));
if (nb == -1) return 0;
if (msg.msg_controllen < sizeof(struct cmsghdr)) return 0;
cmptr = CMSG_FIRSTHDR(&msg);
#ifndef __NetBSD__
size = sizeof(spc_credentials);
#else
if (cmptr->cmsg_len < SOCKCREDSIZE(0)) return 0;
size = SOCKCREDSIZE(((cred *)CMSG_DATA(cmptr))->sc_ngroups);
#endif
if (cmptr->cmsg_len != CMSG_LEN(size)) return 0;
if (cmptr->cmsg_level != SOL_SOCKET) return 0;
if (cmptr->cmsg_type != SCM_CREDS) return 0;
if (!(credentials = (spc_credentials *)malloc(size))) return 0;
*credentials = *(spc_credentials *)CMSG_DATA(cmptr);
return credentials;
}
int spc_send_credentials(int sd) {
int sync = 0x11223344;
struct iovec iov[1] = { { 0, 0, } };
struct msghdr msg = { 0, 0, iov, 1, 0, 0, 0 };
#if !defined(linux) && !defined(__NetBSD__)
char ctrl[CMSG_SPACE(sizeof(spc_credentials))];
struct cmsghdr *cmptr;
msg.msg_control = ctrl;
msg.msg_controllen = sizeof(ctrl);
cmptr = CMSG_FIRSTHDR(&msg);
cmptr->cmsg_len = CMSG_LEN(sizeof(spc_credentials));
cmptr->cmsg_level = SOL_SOCKET;
cmptr->cmsg_type = SCM_CREDS;
memset(CMSG_DATA(cmptr), 0, sizeof(spc_credentials));
#endif
msg.msg_iov->iov_base = (void *)&sync;
msg.msg_iov->iov_len = sizeof(sync);
return (sendmsg(sd, &msg, 0) != -1);
}
On all platforms, it is possible to obtain credentials from a peer at
any point during the connection; however, it often makes the most
sense to get the information immediately after the connection is
established. For example, if your server needs to get the credentials
of each client that connects, the server code might look something
like this:
typedef void (*spc_client_fn)(spc_socket_t *, spc_credentials *, void *);
void spc_unix_server(spc_client_fn callback, void *arg) {
spc_socket_t *client, *listener;
spc_credentials *credentials;
listener = spc_socket_listen(SOCK_STREAM, 0, "127.0.0.1", 2222);
while ((client = spc_socket_accept(listener)) != 0) {
if (!(credentials = spc_get_credentials(client->sd))) {
printf("Unable to get credentials from connecting client!\n");
spc_socket_close(client);
} else {
printf("Client credentials:\n\tuid: %d\n\tgid: %d\n",
SPC_PEER_UID(credentials), SPC_PEER_GID(credentials));
/* do something with the credentials and the connection ... */
callback(client, credentials, arg);
}
}
}
The corresponding client code might look something like
this:
spc_socket_t *spc_unix_connect(void) {
spc_socket_t *conn;
if (!(conn = spc_socket_connect("127.0.0.1", 2222))) {
printf("Unable to connect to the server!\n");
return 0;
}
if (!spc_send_credentials(conn->sd)) {
printf("Unable to send credentials to the server!\n");
spc_socket_close(conn);
return 0;
}
printf("Credentials were successfully sent to the server.\n");
return conn;
}
Note finally that while it is possible to obtain credentials from a
peer at any point during the connection, many implementations will
send the credentials only once. If you need the credential
information at more than one point during a conversation, you should
make sure to save the information that was obtained the first time it
was needed.
|