From 0c1bb4fbf1a07495aa163806e00ea56872c97286 Mon Sep 17 00:00:00 2001
From: Dima Dorfman <dd@FreeBSD.org>
Date: Fri, 17 Aug 2001 22:01:18 +0000
Subject: [PATCH] Implement a LOCAL_PEERCRED socket option which returns a
 `struct xucred` with the credentials of the connected peer. Obviously this
 only works (and makes sense) on SOCK_STREAM sockets.  This works for both the
 connect(2) and listen(2) callers.

There is precise documentation of the semantics in unix(4).

Reviewed by:	dwmalone (eyeballed)
---
 share/man/man4/unix.4  | 31 +++++++++++++++-
 sys/kern/uipc_proto.c  |  2 +-
 sys/kern/uipc_usrreq.c | 83 +++++++++++++++++++++++++++++++++++++++++-
 sys/sys/un.h           |  4 ++
 sys/sys/unpcb.h        | 19 ++++++++++
 5 files changed, 135 insertions(+), 4 deletions(-)

diff --git a/share/man/man4/unix.4 b/share/man/man4/unix.4
index 7356edb19b0d..53535b63d352 100644
--- a/share/man/man4/unix.4
+++ b/share/man/man4/unix.4
@@ -32,7 +32,7 @@
 .\"     @(#)unix.4	8.1 (Berkeley) 6/9/93
 .\" $FreeBSD$
 .\"
-.Dd June 9, 1993
+.Dd July 15, 2001
 .Dt UNIX 4
 .Os
 .Sh NAME
@@ -147,6 +147,35 @@ passed to a receiver.
 Descriptors that are awaiting delivery, or that are
 purposely not received, are automatically closed by the system
 when the destination socket is closed.
+.Pp
+The effective credentials (i.e., the user ID and group list) the of a
+peer on a
+.Dv SOCK_STREAM
+socket may be obtained using the
+.Dv LOCAL_PEERCRED
+socket option.
+This may be used by a server to obtain and verify the credentials of
+its client, and vice versa by the client to verify the credentials
+of the server.
+These will arrive in the form of a filled in
+.Ar struct xucred
+(defined in
+.Pa sys/ucred.h ) .
+The credentials presented to the server (the
+.Xr listen 2
+caller) are those of the client when it called
+.Xr connect 2 ;
+the credentials presented to the client (the
+.Xr connect 2
+caller) are those of the server when it called
+.Xr listen 2 .
+This mechanism is reliable; there is no way for either party to influence
+the credentials presented to its peer except by calling the appropriate
+system call (e.g.,
+.Xr connect 2
+or
+.Xr listen 2 )
+under different effective credentials.
 .Sh SEE ALSO
 .Xr socket 2 ,
 .Xr intro 4
diff --git a/sys/kern/uipc_proto.c b/sys/kern/uipc_proto.c
index ac01aafe781c..74dab7864a9a 100644
--- a/sys/kern/uipc_proto.c
+++ b/sys/kern/uipc_proto.c
@@ -51,7 +51,7 @@
 
 static struct protosw localsw[] = {
 { SOCK_STREAM,	&localdomain,	0,	PR_CONNREQUIRED|PR_WANTRCVD|PR_RIGHTS,
-  0,		0,		0,		0,
+  0,		0,		0,		&uipc_ctloutput,
   0,
   0,		0,		0,		0,
   &uipc_usrreqs
diff --git a/sys/kern/uipc_usrreq.c b/sys/kern/uipc_usrreq.c
index 2907b4e64f0c..600fb5c766ec 100644
--- a/sys/kern/uipc_usrreq.c
+++ b/sys/kern/uipc_usrreq.c
@@ -91,6 +91,7 @@ static void    unp_scan __P((struct mbuf *, void (*)(struct file *)));
 static void    unp_mark __P((struct file *));
 static void    unp_discard __P((struct file *));
 static int     unp_internalize __P((struct mbuf *, struct proc *));
+static int     unp_listen __P((struct unpcb *, struct proc *));
 
 static int
 uipc_abort(struct socket *so)
@@ -199,7 +200,7 @@ uipc_listen(struct socket *so, struct proc *p)
 
 	if (unp == 0 || unp->unp_vnode == 0)
 		return EINVAL;
-	return 0;
+	return unp_listen(unp, p);
 }
 
 static int
@@ -434,6 +435,41 @@ struct pr_usrreqs uipc_usrreqs = {
 	uipc_send, uipc_sense, uipc_shutdown, uipc_sockaddr,
 	sosend, soreceive, sopoll
 };
+
+int
+uipc_ctloutput(so, sopt)
+	struct socket *so;
+	struct sockopt *sopt;
+{
+	struct unpcb *unp = sotounpcb(so);
+	int error;
+
+	switch (sopt->sopt_dir) {
+	case SOPT_GET:
+		switch (sopt->sopt_name) {
+		case LOCAL_PEERCRED:
+			if (unp->unp_flags & UNP_HAVEPC)
+				error = sooptcopyout(sopt, &unp->unp_peercred,
+				    sizeof(unp->unp_peercred));
+			else {
+				if (so->so_type == SOCK_STREAM)
+					error = ENOTCONN;
+				else
+					error = EINVAL;
+			}
+			break;
+		default:
+			error = EOPNOTSUPP;
+			break;
+		}
+		break;
+	case SOPT_SET:
+	default:
+		error = EOPNOTSUPP;
+		break;
+	}
+	return (error);
+}
 	
 /*
  * Both send and receive buffers are allocated PIPSIZ bytes of buffering
@@ -609,7 +645,7 @@ unp_connect(so, nam, p)
 	register struct sockaddr_un *soun = (struct sockaddr_un *)nam;
 	register struct vnode *vp;
 	register struct socket *so2, *so3;
-	struct unpcb *unp2, *unp3;
+	struct unpcb *unp, *unp2, *unp3;
 	int error, len;
 	struct nameidata nd;
 	char buf[SOCK_MAXADDRLEN];
@@ -648,12 +684,40 @@ unp_connect(so, nam, p)
 			error = ECONNREFUSED;
 			goto bad;
 		}
+		unp = sotounpcb(so);
 		unp2 = sotounpcb(so2);
 		unp3 = sotounpcb(so3);
 		if (unp2->unp_addr)
 			unp3->unp_addr = (struct sockaddr_un *)
 				dup_sockaddr((struct sockaddr *)
 					     unp2->unp_addr, 1);
+
+		/*
+		 * unp_peercred management:
+		 *
+		 * The connecter's (client's) credentials are copied
+		 * from its process structure at the time of connect()
+		 * (which is now).
+		 */
+		memset(&unp3->unp_peercred, '\0', sizeof(unp3->unp_peercred));
+		unp3->unp_peercred.cr_uid = p->p_ucred->cr_uid;
+		unp3->unp_peercred.cr_ngroups = p->p_ucred->cr_ngroups;
+		memcpy(unp3->unp_peercred.cr_groups, p->p_ucred->cr_groups,
+		    sizeof(unp3->unp_peercred.cr_groups));
+		unp3->unp_flags |= UNP_HAVEPC;
+		/*
+		 * The receiver's (server's) credentials are copied
+		 * from the unp_peercred member of socket on which the
+		 * former called listen(); unp_listen() cached that
+		 * process's credentials at that time so we can use
+		 * them now.
+		 */
+		KASSERT(unp2->unp_flags & UNP_HAVEPCCACHED,
+		    ("unp_connect: listener without cached peercred"));
+		memcpy(&unp->unp_peercred, &unp2->unp_peercred,
+		    sizeof(unp->unp_peercred));
+		unp->unp_flags |= UNP_HAVEPC;
+
 		so2 = so3;
 	}
 	error = unp_connect2(so, so2);
@@ -1244,6 +1308,21 @@ unp_dispose(m)
 		unp_scan(m, unp_discard);
 }
 
+static int
+unp_listen(unp, p)
+	struct unpcb *unp;
+	struct proc *p;
+{
+
+	bzero(&unp->unp_peercred, sizeof(unp->unp_peercred));
+	unp->unp_peercred.cr_uid = p->p_ucred->cr_uid;
+	unp->unp_peercred.cr_ngroups = p->p_ucred->cr_ngroups;
+	bcopy(p->p_ucred->cr_groups, unp->unp_peercred.cr_groups,
+	    sizeof(unp->unp_peercred.cr_groups));
+	unp->unp_flags |= UNP_HAVEPCCACHED;
+	return (0);
+}
+
 static void
 unp_scan(m0, op)
 	register struct mbuf *m0;
diff --git a/sys/sys/un.h b/sys/sys/un.h
index e7c370195a50..710f995b7614 100644
--- a/sys/sys/un.h
+++ b/sys/sys/un.h
@@ -46,12 +46,16 @@ struct	sockaddr_un {
 	char	sun_path[104];		/* path name (gag) */
 };
 
+/* Socket options. */
+#define LOCAL_PEERCRED		0x001		/* retrieve peer credentails */
+
 #ifdef _KERNEL
 struct mbuf;
 struct socket;
 
 int	uipc_usrreq __P((struct socket *so, int req, struct mbuf *m,
 		struct mbuf *nam, struct mbuf *control));
+int	uipc_ctloutput __P((struct socket *so, struct sockopt *sopt));
 int	unp_connect2 __P((struct socket *so, struct socket *so2));
 void	unp_dispose __P((struct mbuf *m));
 int	unp_externalize __P((struct mbuf *rights));
diff --git a/sys/sys/unpcb.h b/sys/sys/unpcb.h
index c0b8a373a197..cb9d1faab1c7 100644
--- a/sys/sys/unpcb.h
+++ b/sys/sys/unpcb.h
@@ -38,6 +38,7 @@
 #define _SYS_UNPCB_H_
 
 #include <sys/queue.h>
+#include <sys/ucred.h>
 
 /*
  * Protocol control block for an active
@@ -80,8 +81,26 @@ struct	unpcb {
 	int	unp_cc;			/* copy of rcv.sb_cc */
 	int	unp_mbcnt;		/* copy of rcv.sb_mbcnt */
 	unp_gen_t unp_gencnt;		/* generation count of this instance */
+	int	unp_flags;		/* flags */
+	struct	xucred unp_peercred;	/* peer credentials, if applicable */
 };
 
+/*
+ * Flags in unp_flags.
+ *
+ * UNP_HAVEPC - indicates that the unp_peercred member is filled in
+ * and is really the credentials of the connected peer.  This is used
+ * to determine whether the contents should be sent to the user or
+ * not.
+ *
+ * UNP_HAVEPCCACHED - indicates that the unp_peercred member is filled
+ * in, but does *not* contain the credentials of the connected peer
+ * (there may not even be a peer).  This is set in unp_listen() when
+ * it fills in unp_peercred for later consumption by unp_connect().
+ */
+#define UNP_HAVEPC			0x001
+#define UNP_HAVEPCCACHED		0x002
+
 #define	sotounpcb(so)	((struct unpcb *)((so)->so_pcb))
 
 /* Hack alert -- this structure depends on <sys/socketvar.h>. */