diff options
-rw-r--r-- | share/man/man4/unix.4 | 50 | ||||
-rw-r--r-- | sys/kern/uipc_usrreq.c | 120 | ||||
-rw-r--r-- | sys/sys/socket.h | 19 | ||||
-rw-r--r-- | sys/sys/un.h | 4 | ||||
-rw-r--r-- | sys/sys/unpcb.h | 2 |
5 files changed, 179 insertions, 16 deletions
diff --git a/share/man/man4/unix.4 b/share/man/man4/unix.4 index a192fa0..c556185 100644 --- a/share/man/man4/unix.4 +++ b/share/man/man4/unix.4 @@ -194,6 +194,56 @@ system call (e.g., or .Xr listen 2 ) under different effective credentials. +.Pp + +.Tn UNIX +domain sockets support a number of socket options which can be set with +.Xr setsockopt 2 +and tested with +.Xr getsockopt 2 : +.Bl -tag -width ".Dv LOCAL_CONNWAIT" +.It Dv LOCAL_CREDS +This option may be enabled on a +.Dv SOCK_DGRAM +or a +.Dv SOCK_STREAM +socket. This option provides a mechanism for the receiver to +receive the credentials of the process as a +.Xr recvmsg 2 +control message. The msg_control field in the msghdr structure points +to a buffer that contains a cmsghdr structure followed by a variable +length sockcred structure, defined in +.Pa \*[Lt]sys/socket.h\*[Gt] +as follows: +.Bd -literal +struct sockcred { + id_t sc_uid; /* real user id */ + uid_t sc_euid; /* effective user id */ + gid_t sc_gid; /* real group id */ + gid_t sc_egid; /* effective group id */ + int sc_ngroups; /* number of supplemental groups */ + gid_t sc_groups[1]; /* variable length */ +}; +.Ed +.Pp +The +.Fn SOCKCREDSIZE +macro computes the size of the sockcred structure for a specified number +of groups. +The cmsghdr fields have the following values: +.Bd -literal +cmsg_len = sizeof(struct cmsghdr) + SOCKCREDSIZE(ngroups) +cmsg_level = SOL_SOCKET +cmsg_type = SCM_CREDS +.Ed +.It Dv LOCAL_CONNWAIT +Used with +.Dv SOCK_STREAM +sockets, this option causes the +.Xr connect 2 +function to block until +.Xr accept 2 +has been called on the listening socket. .Sh SEE ALSO .Xr socket 2 , .Xr intro 4 diff --git a/sys/kern/uipc_usrreq.c b/sys/kern/uipc_usrreq.c index 256c4fb..e3ebf9b 100644 --- a/sys/kern/uipc_usrreq.c +++ b/sys/kern/uipc_usrreq.c @@ -81,6 +81,7 @@ static struct unp_head unp_shead, unp_dhead; */ static const struct sockaddr sun_noname = { sizeof(sun_noname), AF_LOCAL }; static ino_t unp_ino; /* prototype for fake inode numbers */ +struct mbuf *unp_addsockcred(struct thread *, struct mbuf *); /* * Currently, UNIX domain sockets are protected by a single subsystem lock, @@ -114,7 +115,7 @@ static int unp_attach(struct socket *); static void unp_detach(struct unpcb *); static int unp_bind(struct unpcb *,struct sockaddr *, struct thread *); static int unp_connect(struct socket *,struct sockaddr *, struct thread *); -static int unp_connect2(struct socket *so, struct socket *so2); +static int unp_connect2(struct socket *so, struct socket *so2, int); static void unp_disconnect(struct unpcb *); static void unp_shutdown(struct unpcb *); static void unp_drop(struct unpcb *, int); @@ -233,7 +234,7 @@ uipc_connect2(struct socket *so1, struct socket *so2) UNP_UNLOCK(); return (EINVAL); } - error = unp_connect2(so1, so2); + error = unp_connect2(so1, so2, PRU_CONNECT2); UNP_UNLOCK(); return (error); } @@ -421,6 +422,8 @@ uipc_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, from = (struct sockaddr *)unp->unp_addr; else from = &sun_noname; + if (unp->unp_conn->unp_flags & UNP_WANTCRED) + control = unp_addsockcred(td, control); SOCKBUF_LOCK(&so2->so_rcv); if (sbappendaddr_locked(&so2->so_rcv, from, m, control)) { sorwakeup_locked(so2); @@ -462,6 +465,14 @@ uipc_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, panic("uipc_send connected but no connection?"); so2 = unp->unp_conn->unp_socket; SOCKBUF_LOCK(&so2->so_rcv); + if (unp->unp_conn->unp_flags & UNP_WANTCRED) { + /* + * Credentials are passed only once on + * SOCK_STREAM. + */ + unp->unp_conn->unp_flags &= ~UNP_WANTCRED; + control = unp_addsockcred(td, control); + } /* * Send to paired receive port, and then reduce * send buffer hiwater marks to maintain backpressure. @@ -604,20 +615,20 @@ uipc_ctloutput(struct socket *so, struct sockopt *sopt) { struct unpcb *unp; struct xucred xu; - int error; + int error, optval; + + UNP_LOCK(); + unp = sotounpcb(so); + if (unp == NULL) { + UNP_UNLOCK(); + return (EINVAL); + } + error = 0; switch (sopt->sopt_dir) { case SOPT_GET: switch (sopt->sopt_name) { case LOCAL_PEERCRED: - error = 0; - UNP_LOCK(); - unp = sotounpcb(so); - if (unp == NULL) { - UNP_UNLOCK(); - error = EINVAL; - break; - } if (unp->unp_flags & UNP_HAVEPC) xu = unp->unp_peercred; else { @@ -626,20 +637,58 @@ uipc_ctloutput(struct socket *so, struct sockopt *sopt) else error = EINVAL; } - UNP_UNLOCK(); if (error == 0) error = sooptcopyout(sopt, &xu, sizeof(xu)); break; + case LOCAL_CREDS: + optval = unp->unp_flags & UNP_WANTCRED ? 1 : 0; + error = sooptcopyout(sopt, &optval, sizeof(optval)); + break; + case LOCAL_CONNWAIT: + optval = unp->unp_flags & UNP_CONNWAIT ? 1 : 0; + error = sooptcopyout(sopt, &optval, sizeof(optval)); + break; default: error = EOPNOTSUPP; break; } break; case SOPT_SET: + switch (sopt->sopt_name) { + case LOCAL_CREDS: + case LOCAL_CONNWAIT: + error = sooptcopyin(sopt, &optval, sizeof(optval), + sizeof(optval)); + if (error) + break; + +#define OPTSET(bit) \ + if (optval) \ + unp->unp_flags |= bit; \ + else \ + unp->unp_flags &= ~bit; + + switch (sopt->sopt_name) { + case LOCAL_CREDS: + OPTSET(UNP_WANTCRED); + break; + case LOCAL_CONNWAIT: + OPTSET(UNP_CONNWAIT); + break; + default: + break; + } + break; +#undef OPTSET + default: + error = ENOPROTOOPT; + break; + } default: error = EOPNOTSUPP; break; } + UNP_UNLOCK(); return (error); } @@ -965,7 +1014,7 @@ unp_connect(struct socket *so, struct sockaddr *nam, struct thread *td) so2 = so3; } - error = unp_connect2(so, so2); + error = unp_connect2(so, so2, PRU_CONNECT); bad2: UNP_UNLOCK(); mtx_lock(&Giant); @@ -980,7 +1029,7 @@ bad: } static int -unp_connect2(struct socket *so, struct socket *so2) +unp_connect2(struct socket *so, struct socket *so2, int req) { struct unpcb *unp = sotounpcb(so); struct unpcb *unp2; @@ -1000,7 +1049,11 @@ unp_connect2(struct socket *so, struct socket *so2) case SOCK_STREAM: unp2->unp_conn = unp; - soisconnected(so); + if (req == PRU_CONNECT && + ((unp->unp_flags | unp2->unp_flags) & UNP_CONNWAIT)) + soisconnecting(so); + else + soisconnected(so); soisconnected(so2); break; @@ -1484,6 +1537,43 @@ out: return (error); } +struct mbuf * +unp_addsockcred(struct thread *td, struct mbuf *control) +{ + struct mbuf *m, *n; + struct sockcred *sc; + int ngroups; + int i; + + ngroups = MIN(td->td_ucred->cr_ngroups, CMGROUP_MAX); + + m = sbcreatecontrol(NULL, SOCKCREDSIZE(ngroups), SCM_CREDS, SOL_SOCKET); + if (m == NULL) + return (control); + m->m_next = NULL; + + sc = (struct sockcred *) CMSG_DATA(mtod(m, struct cmsghdr *)); + sc->sc_uid = td->td_ucred->cr_ruid; + sc->sc_euid = td->td_ucred->cr_uid; + sc->sc_gid = td->td_ucred->cr_rgid; + sc->sc_egid = td->td_ucred->cr_gid; + sc->sc_ngroups = ngroups; + for (i = 0; i < sc->sc_ngroups; i++) + sc->sc_groups[i] = td->td_ucred->cr_groups[i]; + + /* + * If a control message already exists, append us to the end. + */ + if (control != NULL) { + for (n = control; n->m_next != NULL; n = n->m_next) + ; + n->m_next = m; + } else + control = m; + + return (control); +} + /* * unp_defer is thread-local during garbage collection, and does not require * explicit synchronization. unp_gcing prevents other threads from entering diff --git a/sys/sys/socket.h b/sys/sys/socket.h index 27e5294..176343d 100644 --- a/sys/sys/socket.h +++ b/sys/sys/socket.h @@ -439,6 +439,25 @@ struct cmsgcred { short cmcred_ngroups; /* number or groups */ gid_t cmcred_groups[CMGROUP_MAX]; /* groups */ }; + +/* + * Socket credentials. + */ +struct sockcred { + uid_t sc_uid; /* real user id */ + uid_t sc_euid; /* effective user id */ + gid_t sc_gid; /* real group id */ + gid_t sc_egid; /* effective group id */ + int sc_ngroups; /* number of supplemental groups */ + gid_t sc_groups[1]; /* variable length */ +}; + +/* + * Compute size of a sockcred structure with groups. + */ +#define SOCKCREDSIZE(ngrps) \ + (sizeof(struct sockcred) + (sizeof(gid_t) * ((ngrps) - 1))) + #endif /* __BSD_VISIBLE */ /* given pointer to struct cmsghdr, return pointer to data */ diff --git a/sys/sys/un.h b/sys/sys/un.h index e8caf35..97aebb5 100644 --- a/sys/sys/un.h +++ b/sys/sys/un.h @@ -53,7 +53,9 @@ struct sockaddr_un { #if __BSD_VISIBLE /* Socket options. */ -#define LOCAL_PEERCRED 0x001 /* retrieve peer credentails */ +#define LOCAL_PEERCRED 0x001 /* retrieve peer credentials */ +#define LOCAL_CREDS 0x002 /* pass credentials to receiver */ +#define LOCAL_CONNWAIT 0x004 /* connects block until accepted */ #ifdef _KERNEL struct mbuf; diff --git a/sys/sys/unpcb.h b/sys/sys/unpcb.h index b630570..a1186de 100644 --- a/sys/sys/unpcb.h +++ b/sys/sys/unpcb.h @@ -95,6 +95,8 @@ struct unpcb { */ #define UNP_HAVEPC 0x001 #define UNP_HAVEPCCACHED 0x002 +#define UNP_WANTCRED 0x004 /* credentials wanted */ +#define UNP_CONNWAIT 0x008 /* connect blocks until accepted */ #define sotounpcb(so) ((struct unpcb *)((so)->so_pcb)) |