15.8 Receiving Sender
Credentials
In Figure 14.13, we
showed another type of data that can be passed along a Unix domain
socket as ancillary data: user credentials. Exactly how credentials
are packaged up and sent as ancillary data tends to be OS-specific.
We describe FreeBSD here, and other Unix variants are similar
(usually the challenge is determining which structure to use for
the credentials). We describe this feature, even though it is not
uniform across systems, because it is an important, yet simple,
addition to the Unix domain protocols. When a client and server
communicate using these protocols, the server often needs a way to
know exactly who the client is, to validate that the client has
permission to ask for the service being requested.
FreeBSD passes credentials in a
cmsgcred structure, which is defined by including the
<sys/socket.h> header.
struct cmsgcred {
pid_t cmcred_pid; /* PID of sending process */
uid_t cmcred_uid; /* real UID of sending process */
uid_t cmcred_euid; /* effective UID of sending process */
gid_t cmcred_gid; /* real GID of sending process */
short cmcred_ngroups; /* number of groups */
gid_t cmcred_groups[CMGROUP_MAX]; /* groups */
};
Normally, CMGROUP_MAX is 16.
cmcred_ngroups is always at least 1, with the first
element of the array the effective group ID.
This information is always available on a Unix
domain socket, although there are often special arrangments the
sender must make to have the information included when sending, and
there are often special arrangements (e.g., socket options) the
receiver must make to get the credentials. On our FreeBSD system,
the receiver doesn't have to do anything special other than call
recvmsg with an ancillary buffer large enough to hold the
credentials, as we show in Figure 15.14. The sender, however, must include a
cmsgcred structure when sending data using
sendmsg. It is important to note that although FreeBSD
requires the sender to include the structure, the contents are
filled in by the kernel and cannot be forged by the sender. This
makes the passing of credentials over a Unix domain socket a
reliable way to verify the client's identity.
Example
As an example of credential passing, we modify
our Unix domain stream server to ask for the client's credentials.
Figure 15.14 shows a new
function, read_cred, that is similar to read, but
also returns a cmsgcred structure containing the sender's
credentials.
3鈥?
The first three arguments are identical to read, with the
fourth argument being a pointer to an cmsgcred structure
that will be filled in.
22鈥?1
If credentials were returned, the length, level, and type of the
ancillary data are verified, and the resulting structure is copied
back to the caller. If no credentials were returned, we set the
structure to 0. Since the number of groups
(cmcred_ngroups) is always 1 or more, the value of 0
indicates to the caller that no credentials were returned by the
kernel.
The main function for our echo server,
Figure 15.3, is
unchanged. Figure 15.15
shows the new version of the str_echo function, modified
from Figure 5.3. This
function is called by the child after the parent has accepted a new
client connection and called fork.
11鈥?3
If credentials were returned, they are printed.
24鈥?5
The remainder of the loop is unchanged. This code reads buffers
from the client and writes them back to the client.
Our client from Figure 15.4 is only
changed minimally to pass an empty cmsgcred structure that
will be filled in when it calls sendmsg.
Figure 15.14
read_cred function: reads and returns sender's
credentials.
unixdomain/readcred.c
1 #include "unp.h"
2 #define CONTROL_LEN (sizeof(struct cmsghdr) + sizeof(struct cmsgcred))
3 ssize_t
4 read_cred(int fd, void *ptr, size_t nbytes, struct cmsgcred *cmsgcredptr)
5 {
6 struct msghdr msg;
7 struct iovec iov[1];
8 char control[CONTROL_LEN];
9 int n;
10 msg.msg_name = NULL;
11 msg.msg_namelen = 0;
12 iov[0].iov_base = ptr;
13 iov[0].iov_len = nbytes;
14 msg.msg_iov = iov;
15 msg.msg_iovlen = 1;
16 msg.msg_control = control;
17 msg.msg_controllen = sizeof(control);
18 msg.msg_flags = 0;
19 if ( (n = recvmsg(fd, &msg, 0)) < 0)
20 return (n);
21 cmsgcredptr->cmcred_ngroups = 0; /* indicates no credentials returned */
22 if (cmsgcredptr && msg.msg_controllen > 0) {
23 struct cmsghdr *cmptr = (struct cmsghdr *) control;
24 if (cmptr->cmsg_len < CONTROL_LEN)
25 err_quit("control length = %d", cmptr->cmsg_len);
26 if (cmptr->cmsg_level != SOL_SOCKET)
27 err_quit("control level != SOL_SOCKET");
28 if (cmptr->cmsg_type != SCM_CREDS)
29 err_quit("control type != SCM_CREDS");
30 memcpy(cmsgcredptr, CMSG_DATA(cmptr), sizeof(struct cmsgcred));
31 }
32 return (n);
33 }
Figure 15.15
str_echo function: asks for client's credentials.
unixdomain/strecho.c
1 #include "unp.h"
2 ssize_t read_cred(int, void *, size_t, struct cmsgcred *);
3 void
4 str_echo(int sockfd)
5 {
6 ssize_t n;
7 int i;
8 char buf[MAXLINE];
9 struct cmsgcred cred;
10 again:
11 while ( (n = read_cred(sockfd, buf, MAXLINE, &cred)) > 0) {
12 if (cred.cmcred_ngroups == 0) {
13 printf("(no credentials returned)\n");
14 } else {
15 printf("PID of sender = %d\n", cred.cmcred_pid);
16 printf("real user ID = %d\n", cred.cmcred_uid);
17 printf("real group ID = %d\n", cred.cmcred_gid);
18 printf("effective user ID = %d\n", cred.cmcred_euid);
19 printf("%d groups:", cred.cmcred_ngroups - 1);
20 for (i = 1; i < cred.cmcred_ngroups; i++)
21 printf(" %d", cred.cmcred_groups[i]);
22 printf("\n");
23 }
24 Writen(sockfd, buf, n);
25 }
26 if (n < 0 && errno == EINTR)
27 goto again;
28 else if (n < 0)
29 err_sys("str_echo: read error");
30 }
Before running the client, we can see our
current credentials using the id command.
freebsd % id
uid=1007(andy) gid=1007(andy) groups=1007(andy), 0(wheel)
Starting the server and then running the client
one time in another window produces the following output from the
server:
freebsd % unixstrserv02
PID of sender = 26881
real user ID = 1007
real group ID = 1007
effective user ID = 1007
2 groups: 1007 0
This information is output only after the client
has sent data to the server. We see that the information matches
what we saw with the id command.
|