/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley * 4.3 BSD under license from the Regents of the University of * California. */ #include "mt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern bool_t __svc_get_door_ucred(const SVCXPRT *, ucred_t *); /* * This routine is typically called on the server side if the server * wants to know the caller ucred. Called typically by rpcbind. It * depends upon the t_optmgmt call to local transport driver so that * return the uid value in options in T_CONN_IND, T_CONN_CON and * T_UNITDATA_IND. * With the advent of the credential in the mblk, this is simply * extended to all transports when the packet travels over the * loopback network; for UDP we use a special socket option and for * tcp we don't need to do any setup, we just call getpeerucred() * later. */ /* * Version for Solaris with new local transport code and ucred. */ int __rpc_negotiate_uid(int fd) { struct strioctl strioc; unsigned int set = 1; /* For tcp we use getpeerucred and it needs no initialization. */ if (ioctl(fd, I_FIND, "tcp") > 0) return (0); strioc.ic_cmd = TL_IOC_UCREDOPT; strioc.ic_timout = -1; strioc.ic_len = (int)sizeof (unsigned int); strioc.ic_dp = (char *)&set; if (ioctl(fd, I_STR, &strioc) == -1 && __rpc_tli_set_options(fd, SOL_SOCKET, SO_RECVUCRED, 1) == -1) { syslog(LOG_ERR, "rpc_negotiate_uid (%s): %m", "ioctl:I_STR:TL_IOC_UCREDOPT/SO_RECVUCRED"); return (-1); } return (0); } void svc_fd_negotiate_ucred(int fd) { (void) __rpc_negotiate_uid(fd); } /* * This returns the ucred of the caller. It assumes that the optbuf * information is stored at xprt->xp_p2. * There are three distinct cases: the option buffer is headed * with a "struct opthdr" and the credential option is the only * one, or it's a T_opthdr and our option may follow others; or there * are no options and we attempt getpeerucred(). */ static int find_ucred_opt(const SVCXPRT *trans, ucred_t *uc, bool_t checkzone) { /* LINTED pointer alignment */ struct netbuf *abuf = (struct netbuf *)trans->xp_p2; char *bufp, *maxbufp; struct opthdr *opth; static zoneid_t myzone = MIN_ZONEID - 1; /* invalid */ if (abuf == NULL || abuf->buf == NULL) { if (getpeerucred(trans->xp_fd, &uc) == 0) goto verifyzone; return (-1); } #ifdef RPC_DEBUG syslog(LOG_INFO, "find_ucred_opt %p %x", abuf->buf, abuf->len); #endif /* LINTED pointer cast */ opth = (struct opthdr *)abuf->buf; if (opth->level == TL_PROT_LEVEL && opth->name == TL_OPT_PEER_UCRED && opth->len + sizeof (*opth) == abuf->len) { #ifdef RPC_DEBUG syslog(LOG_INFO, "find_ucred_opt (opthdr): OK!"); #endif (void) memcpy(uc, &opth[1], opth->len); /* * Always from inside our zone because zones use a separate name * space for loopback; at this time, the kernel may send a * packet pretending to be from the global zone when it's * really from our zone so we skip the zone check. */ return (0); } bufp = abuf->buf; maxbufp = bufp + abuf->len; while (bufp + sizeof (struct T_opthdr) < maxbufp) { /* LINTED pointer cast */ struct T_opthdr *opt = (struct T_opthdr *)bufp; #ifdef RPC_DEBUG syslog(LOG_INFO, "find_ucred_opt opt: %p %x, %d %d", opt, opt->len, opt->name, opt->level); #endif if (opt->len > maxbufp - bufp || (opt->len & 3)) return (-1); if (opt->level == SOL_SOCKET && opt->name == SCM_UCRED && opt->len - sizeof (struct T_opthdr) <= ucred_size()) { #ifdef RPC_DEBUG syslog(LOG_INFO, "find_ucred_opt (T_opthdr): OK!"); #endif (void) memcpy(uc, &opt[1], opt->len - sizeof (struct T_opthdr)); goto verifyzone; } bufp += opt->len; } if (getpeerucred(trans->xp_fd, &uc) != 0) return (-1); verifyzone: if (!checkzone) return (0); if (myzone == MIN_ZONEID - 1) myzone = getzoneid(); /* Return 0 only for the local zone */ return (ucred_getzoneid(uc) == myzone ? 0 : -1); } /* * Version for Solaris with new local transport code */ int __rpc_get_local_uid(SVCXPRT *trans, uid_t *uid_out) { ucred_t *uc = alloca(ucred_size()); int err; /* LINTED - pointer alignment */ if (svc_type(trans) == SVC_DOOR) err = __svc_get_door_ucred(trans, uc) == FALSE; else err = find_ucred_opt(trans, uc, B_TRUE); if (err != 0) return (-1); *uid_out = ucred_geteuid(uc); return (0); } /* * Return local credentials. */ bool_t __rpc_get_local_cred(SVCXPRT *xprt, svc_local_cred_t *lcred) { ucred_t *uc = alloca(ucred_size()); int err; /* LINTED - pointer alignment */ if (svc_type(xprt) == SVC_DOOR) err = __svc_get_door_ucred(xprt, uc) == FALSE; else err = find_ucred_opt(xprt, uc, B_TRUE); if (err != 0) return (FALSE); lcred->euid = ucred_geteuid(uc); lcred->egid = ucred_getegid(uc); lcred->ruid = ucred_getruid(uc); lcred->rgid = ucred_getrgid(uc); lcred->pid = ucred_getpid(uc); return (TRUE); } /* * Return local ucred. */ int svc_getcallerucred(const SVCXPRT *trans, ucred_t **uc) { ucred_t *ucp = *uc; int err; if (ucp == NULL) { ucp = malloc(ucred_size()); if (ucp == NULL) return (-1); } /* LINTED - pointer alignment */ if (svc_type(trans) == SVC_DOOR) err = __svc_get_door_ucred(trans, ucp) == FALSE; else err = find_ucred_opt(trans, ucp, B_FALSE); if (err != 0) { if (*uc == NULL) free(ucp); return (-1); } if (*uc == NULL) *uc = ucp; return (0); } /* * get local ip address */ int __rpc_get_ltaddr(struct netbuf *nbufp, struct netbuf *ltaddr) { unsigned int total_optlen; struct T_opthdr *opt, *opt_start = NULL, *opt_end; struct sockaddr_in *ipv4sa; struct sockaddr_in6 *ipv6sa; int s; struct sioc_addrreq areq; if (nbufp == (struct netbuf *)0 || ltaddr == (struct netbuf *)0) { t_errno = TBADOPT; return (-1); } total_optlen = nbufp->len; if (total_optlen == 0) return (1); /* LINTED pointer alignment */ opt_start = (struct T_opthdr *)nbufp->buf; if (opt_start == NULL) { t_errno = TBADOPT; return (-1); } /* Make sure the start of the buffer is aligned */ if (!(__TPI_TOPT_ISALIGNED(opt_start))) { t_errno = TBADOPT; return (-1); } /* LINTED pointer alignment */ opt_end = (struct T_opthdr *)((uchar_t *)opt_start + total_optlen); opt = opt_start; /* * Look for the desired option header */ do { if (((uchar_t *)opt + sizeof (struct T_opthdr)) > (uchar_t *)opt_end) { t_errno = TBADOPT; return (-1); } if (opt->len < sizeof (struct T_opthdr)) { t_errno = TBADOPT; return (-1); } if (((uchar_t *)opt + opt->len) > (uchar_t *)opt_end) { t_errno = TBADOPT; return (-1); } switch (opt->level) { case IPPROTO_IP: if (opt->name == IP_RECVDSTADDR) { struct sockaddr_in v4tmp; opt++; if (((uchar_t *)opt + sizeof (struct in_addr)) > (uchar_t *)opt_end) { t_errno = TBADOPT; return (-1); } bzero(&v4tmp, sizeof (v4tmp)); v4tmp.sin_family = AF_INET; v4tmp.sin_addr = *(struct in_addr *)opt; #ifdef RPC_DEBUG { struct in_addr ia; char str[INET_ADDRSTRLEN]; ia = *(struct in_addr *)opt; (void) inet_ntop(AF_INET, &ia, str, sizeof (str)); syslog(LOG_INFO, "__rpc_get_ltaddr for IP_RECVDSTADDR: %s", str); } #endif if ((s = open("/dev/udp", O_RDONLY)) < 0) { #ifdef RPC_DEBUG syslog(LOG_ERR, "__rpc_get_ltaddr: " "dev udp open failed"); #endif return (1); } (void) memcpy(&areq.sa_addr, &v4tmp, sizeof (v4tmp)); areq.sa_res = -1; if (ioctl(s, SIOCTMYADDR, (caddr_t)&areq) < 0) { syslog(LOG_ERR, "get_ltaddr:ioctl for udp failed"); (void) close(s); return (1); } (void) close(s); if (areq.sa_res == 1) { /* LINTED pointer cast */ ipv4sa = (struct sockaddr_in *)ltaddr->buf; ipv4sa->sin_family = AF_INET; ipv4sa->sin_addr = *(struct in_addr *)opt; return (0); } else return (1); } break; case IPPROTO_IPV6: if (opt->name == IPV6_PKTINFO) { struct sockaddr_in6 v6tmp; opt++; if (((uchar_t *)opt + sizeof (struct in6_pktinfo)) > (uchar_t *)opt_end) { t_errno = TBADOPT; return (-1); } bzero(&v6tmp, sizeof (v6tmp)); v6tmp.sin6_family = AF_INET6; v6tmp.sin6_addr = ((struct in6_pktinfo *)opt)->ipi6_addr; #ifdef RPC_DEBUG { struct in6_pktinfo *in6_pkt; char str[INET6_ADDRSTRLEN]; in6_pkt = (struct in6_pktinfo *)opt; (void) inet_ntop(AF_INET6, &in6_pkt->ipi6_addr, str, sizeof (str)); syslog(LOG_INFO, "__rpc_get_ltaddr for IPV6_PKTINFO: %s", str); } #endif if ((s = open("/dev/udp6", O_RDONLY)) < 0) { #ifdef RPC_DEBUG syslog(LOG_ERR, "__rpc_get_ltaddr: " "dev udp6 open failed"); #endif return (1); } (void) memcpy(&areq.sa_addr, &v6tmp, sizeof (v6tmp)); areq.sa_res = -1; if (ioctl(s, SIOCTMYADDR, (caddr_t)&areq) < 0) { syslog(LOG_ERR, "get_ltaddr:ioctl for udp6 failed"); (void) close(s); return (1); } (void) close(s); if (areq.sa_res == 1) { /* LINTED pointer cast */ ipv6sa = (struct sockaddr_in6 *)ltaddr->buf; ipv6sa->sin6_family = AF_INET6; ipv6sa->sin6_addr = ((struct in6_pktinfo *)opt)->ipi6_addr; return (0); } else return (1); } break; default: break; } /* LINTED improper alignment */ opt = (struct T_opthdr *)((uchar_t *)opt + __TPI_ALIGN(opt->len)); } while (opt < opt_end); return (1); } #define __TRANSPORT_INDSZ 128 int __rpc_tli_set_options(int fd, int optlevel, int optname, int optval) { struct t_optmgmt oreq, ores; struct opthdr *topt; int *ip; int optsz; char buf[__TRANSPORT_INDSZ]; switch (optname) { case SO_DONTLINGER: { struct linger *ling; /* LINTED */ ling = (struct linger *) (buf + sizeof (struct opthdr)); ling->l_onoff = 0; optsz = sizeof (struct linger); break; } case SO_LINGER: { struct linger *ling; /* LINTED */ ling = (struct linger *) (buf + sizeof (struct opthdr)); ling->l_onoff = 1; ling->l_linger = (int)optval; optsz = sizeof (struct linger); break; } case IP_RECVDSTADDR: case IPV6_RECVPKTINFO: case SO_DEBUG: case SO_KEEPALIVE: case SO_DONTROUTE: case SO_USELOOPBACK: case SO_REUSEADDR: case SO_DGRAM_ERRIND: case SO_RECVUCRED: case SO_ANON_MLP: case SO_MAC_EXEMPT: case SO_EXCLBIND: case TCP_EXCLBIND: case UDP_EXCLBIND: /* LINTED */ ip = (int *)(buf + sizeof (struct opthdr)); *ip = optval; optsz = sizeof (int); break; default: return (-1); } /* LINTED */ topt = (struct opthdr *)buf; topt->level = optlevel; topt->name = optname; topt->len = optsz; oreq.flags = T_NEGOTIATE; oreq.opt.len = sizeof (struct opthdr) + optsz; oreq.opt.buf = buf; ores.flags = 0; ores.opt.buf = buf; ores.opt.maxlen = __TRANSPORT_INDSZ; if (t_optmgmt(fd, &oreq, &ores) < 0 || ores.flags != T_SUCCESS) { return (-1); } return (0); } /* * Format an error message corresponding to the given TLI and system error * codes. */ void __tli_sys_strerror(char *buf, size_t buflen, int tli_err, int sys_err) { char *errorstr; if (tli_err == TSYSERR) { errorstr = strerror(sys_err); if (errorstr == NULL) (void) snprintf(buf, buflen, dgettext(__nsl_dom, "Unknown system error %d"), sys_err); else (void) strlcpy(buf, errorstr, buflen); } else { errorstr = t_strerror(tli_err); (void) strlcpy(buf, errorstr, buflen); } } /* * Depending on the specified RPC number, attempt to set mac_exempt * option on the opened socket; these requests need to be able to do MAC * MAC read-down operations. Privilege is needed to set this option. */ void __rpc_set_mac_options(int fd, const struct netconfig *nconf, rpcprog_t prognum) { int ret = 0; if (!is_system_labeled()) return; if (strcmp(nconf->nc_protofmly, NC_INET) != 0 && strcmp(nconf->nc_protofmly, NC_INET6) != 0) return; if (is_multilevel(prognum)) { ret = __rpc_tli_set_options(fd, SOL_SOCKET, SO_MAC_EXEMPT, 1); if (ret < 0) { char errorstr[100]; __tli_sys_strerror(errorstr, sizeof (errorstr), t_errno, errno); (void) syslog(LOG_ERR, "rpc_set_mac_options: %s", errorstr); } } }