[ Team LiB ] |
15.8 Receiving Sender CredentialsIn 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. ExampleAs 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–4 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–31 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–23 If credentials were returned, they are printed. 24–25 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. |
[ Team LiB ] |