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