ti_opts.c revision 1219:f89f56c2d9ac
1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22
23/*
24 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
25 * Use is subject to license terms.
26 */
27
28/* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
29/* All Rights Reserved */
30/*
31 * Portions of this source code were derived from Berkeley
32 * 4.3 BSD under license from the Regents of the University of
33 * California.
34 */
35
36#pragma ident	"%Z%%M%	%I%	%E% SMI"
37
38#include "mt.h"
39#include <stdio.h>
40#include <netinet/in.h>
41#include <netinet/tcp.h>
42#include <netinet/udp.h>
43#include <inttypes.h>
44#include <sys/types.h>
45#include <tiuser.h>
46#include <sys/socket.h>
47#include <net/if.h>
48#include <sys/sockio.h>
49#include <rpc/rpc.h>
50#include <sys/tl.h>
51#include <sys/stropts.h>
52#include <errno.h>
53#include <libintl.h>
54#include <string.h>
55#include <strings.h>
56#include <syslog.h>
57#include <unistd.h>
58#include <ucred.h>
59#include <alloca.h>
60#include <stdlib.h>
61#include <zone.h>
62
63extern const char *inet_ntop(int, const void *, char *, socklen_t);
64extern bool_t __svc_get_door_ucred(const SVCXPRT *, ucred_t *);
65
66
67/*
68 * This routine is typically called on the server side if the server
69 * wants to know the caller ucred.  Called typically by rpcbind.  It
70 * depends upon the t_optmgmt call to local transport driver so that
71 * return the uid value in options in T_CONN_IND, T_CONN_CON and
72 * T_UNITDATA_IND.
73 * With the advent of the credential in the mblk, this is simply
74 * extended to all transports when the packet travels over the
75 * loopback network; for UDP we use a special socket option and for
76 * tcp we don't need to do any setup, we just call getpeerucred()
77 * later.
78 */
79
80/*
81 * Version for Solaris with new local transport code and ucred.
82 */
83int
84__rpc_negotiate_uid(int fd)
85{
86	struct strioctl strioc;
87	unsigned int set = 1;
88
89	/* For tcp we use getpeerucred and it needs no initialization. */
90	if (ioctl(fd, I_FIND, "tcp") > 0)
91		return (0);
92
93	strioc.ic_cmd = TL_IOC_UCREDOPT;
94	strioc.ic_timout = -1;
95	strioc.ic_len = (int)sizeof (unsigned int);
96	strioc.ic_dp = (char *)&set;
97
98	if (ioctl(fd, I_STR, &strioc) == -1 &&
99	    __rpc_tli_set_options(fd, SOL_SOCKET, SO_RECVUCRED, 1) == -1) {
100		syslog(LOG_ERR, "rpc_negotiate_uid (%s): %m",
101		    "ioctl:I_STR:TL_IOC_UCREDOPT/SO_RECVUCRED");
102		return (-1);
103	}
104	return (0);
105}
106
107void
108svc_fd_negotiate_ucred(int fd)
109{
110	(void) __rpc_negotiate_uid(fd);
111}
112
113
114/*
115 * This returns the ucred of the caller.  It assumes that the optbuf
116 * information is stored at xprt->xp_p2.
117 * There are three distinct cases: the option buffer is headed
118 * with a "struct opthdr" and the credential option is the only
119 * one, or it's a T_opthdr and our option may follow others; or there
120 * are no options and we attempt getpeerucred().
121 */
122static int
123find_ucred_opt(const SVCXPRT *trans, ucred_t *uc, bool_t checkzone)
124{
125	/* LINTED pointer alignment */
126	struct netbuf *abuf = (struct netbuf *)trans->xp_p2;
127	char *bufp, *maxbufp;
128	struct opthdr *opth;
129	static zoneid_t myzone = MIN_ZONEID - 1;	/* invalid */
130
131	if (abuf == NULL || abuf->buf == NULL) {
132		if (getpeerucred(trans->xp_fd, &uc) == 0)
133			goto verifyzone;
134		return (-1);
135	}
136
137#ifdef RPC_DEBUG
138	syslog(LOG_INFO, "find_ucred_opt %p %x", abuf->buf, abuf->len);
139#endif
140	/* LINTED pointer cast */
141	opth = (struct opthdr *)abuf->buf;
142	if (opth->level == TL_PROT_LEVEL &&
143	    opth->name == TL_OPT_PEER_UCRED &&
144	    opth->len + sizeof (*opth) == abuf->len) {
145#ifdef RPC_DEBUG
146		syslog(LOG_INFO, "find_ucred_opt (opthdr): OK!");
147#endif
148		(void) memcpy(uc, &opth[1], opth->len);
149		/*
150		 * Always from inside our zone because zones use a separate name
151		 * space for loopback; at this time, the kernel may send a
152		 * packet pretending to be from the global zone when it's
153		 * really from our zone so we skip the zone check.
154		 */
155		return (0);
156	}
157
158	bufp = abuf->buf;
159	maxbufp = bufp + abuf->len;
160
161	while (bufp + sizeof (struct T_opthdr) < maxbufp) {
162		/* LINTED pointer cast */
163		struct T_opthdr *opt = (struct T_opthdr *)bufp;
164
165#ifdef RPC_DEBUG
166		syslog(LOG_INFO, "find_ucred_opt opt: %p %x, %d %d", opt,
167			opt->len, opt->name, opt->level);
168#endif
169		if (opt->len > maxbufp - bufp || (opt->len & 3))
170			return (-1);
171		if (opt->level == SOL_SOCKET && opt->name == SCM_UCRED &&
172		    opt->len - sizeof (struct T_opthdr) <= ucred_size()) {
173#ifdef RPC_DEBUG
174			syslog(LOG_INFO, "find_ucred_opt (T_opthdr): OK!");
175#endif
176			(void) memcpy(uc, &opt[1],
177			    opt->len - sizeof (struct T_opthdr));
178			goto verifyzone;
179		}
180		bufp += opt->len;
181	}
182	if (getpeerucred(trans->xp_fd, &uc) != 0)
183		return (-1);
184verifyzone:
185	if (!checkzone)
186		return (0);
187
188	if (myzone == MIN_ZONEID - 1)
189		myzone = getzoneid();
190
191	/* Return 0 only for the local zone */
192	return (ucred_getzoneid(uc) == myzone ? 0 : -1);
193}
194
195/*
196 * Version for Solaris with new local transport code
197 */
198int
199__rpc_get_local_uid(SVCXPRT *trans, uid_t *uid_out)
200{
201	ucred_t *uc = alloca(ucred_size());
202	int err;
203
204	/* LINTED - pointer alignment */
205	if (svc_type(trans) == SVC_DOOR)
206		err = __svc_get_door_ucred(trans, uc) == FALSE;
207	else
208		err = find_ucred_opt(trans, uc, B_TRUE);
209
210	if (err != 0)
211		return (-1);
212	*uid_out = ucred_geteuid(uc);
213	return (0);
214}
215
216/*
217 * Return local credentials.
218 */
219bool_t
220__rpc_get_local_cred(SVCXPRT *xprt, svc_local_cred_t *lcred)
221{
222	ucred_t *uc = alloca(ucred_size());
223	int err;
224
225	/* LINTED - pointer alignment */
226	if (svc_type(xprt) == SVC_DOOR)
227		err = __svc_get_door_ucred(xprt, uc) == FALSE;
228	else
229		err = find_ucred_opt(xprt, uc, B_TRUE);
230
231	if (err != 0)
232		return (FALSE);
233
234	lcred->euid = ucred_geteuid(uc);
235	lcred->egid = ucred_getegid(uc);
236	lcred->ruid = ucred_getruid(uc);
237	lcred->rgid = ucred_getrgid(uc);
238	lcred->pid = ucred_getpid(uc);
239	return (TRUE);
240}
241
242/*
243 * Return local ucred.
244 */
245int
246svc_getcallerucred(const SVCXPRT *trans, ucred_t **uc)
247{
248	ucred_t *ucp = *uc;
249	int err;
250
251	if (ucp == NULL) {
252		ucp = malloc(ucred_size());
253		if (ucp == NULL)
254			return (-1);
255	}
256
257	/* LINTED - pointer alignment */
258	if (svc_type(trans) == SVC_DOOR)
259		err = __svc_get_door_ucred(trans, ucp) == FALSE;
260	else
261		err = find_ucred_opt(trans, ucp, B_FALSE);
262
263	if (err != 0) {
264		if (*uc == NULL)
265			free(ucp);
266		return (-1);
267	}
268
269	if (*uc == NULL)
270		*uc = ucp;
271
272	return (0);
273}
274
275
276/*
277 * get local ip address
278 */
279int
280__rpc_get_ltaddr(struct netbuf *nbufp, struct netbuf *ltaddr)
281{
282	unsigned int total_optlen;
283	struct T_opthdr *opt, *opt_start = NULL, *opt_end;
284	struct sockaddr_in *ipv4sa;
285	struct sockaddr_in6 *ipv6sa;
286	int s;
287	struct sioc_addrreq areq;
288
289	if (nbufp == (struct netbuf *)0 || ltaddr == (struct netbuf *)0) {
290		t_errno = TBADOPT;
291		return (-1);
292	}
293
294	total_optlen = nbufp->len;
295	if (total_optlen == 0)
296		return (1);
297
298	/* LINTED pointer alignment */
299	opt_start = (struct T_opthdr *)nbufp->buf;
300	if (opt_start == NULL) {
301		t_errno = TBADOPT;
302		return (-1);
303	}
304
305	/* Make sure the start of the buffer is aligned */
306	if (!(__TPI_TOPT_ISALIGNED(opt_start))) {
307		t_errno = TBADOPT;
308		return (-1);
309	}
310
311	/* LINTED pointer alignment */
312	opt_end = (struct T_opthdr *)((uchar_t *)opt_start + total_optlen);
313	opt = opt_start;
314
315	/*
316	 * Look for the desired option header
317	 */
318	do {
319		if (((uchar_t *)opt + sizeof (struct T_opthdr)) >
320		    (uchar_t *)opt_end) {
321			t_errno = TBADOPT;
322			return (-1);
323		}
324		if (opt->len < sizeof (struct T_opthdr)) {
325			t_errno = TBADOPT;
326			return (-1);
327		}
328		if (((uchar_t *)opt + opt->len) > (uchar_t *)opt_end) {
329			t_errno = TBADOPT;
330			return (-1);
331		}
332		switch (opt->level) {
333		case IPPROTO_IP:
334			if (opt->name == IP_RECVDSTADDR) {
335				struct sockaddr_in v4tmp;
336
337				opt++;
338				if (((uchar_t *)opt + sizeof (struct in_addr)) >
339				    (uchar_t *)opt_end) {
340					t_errno = TBADOPT;
341					return (-1);
342				}
343				bzero(&v4tmp, sizeof (v4tmp));
344				v4tmp.sin_family = AF_INET;
345				v4tmp.sin_addr = *(struct in_addr *)opt;
346#ifdef RPC_DEBUG
347				{
348				struct in_addr ia;
349				char str[INET_ADDRSTRLEN];
350
351				ia = *(struct in_addr *)opt;
352				(void) inet_ntop(AF_INET, &ia,
353						str, sizeof (str));
354				syslog(LOG_INFO,
355				    "__rpc_get_ltaddr for IP_RECVDSTADDR: %s",
356					str);
357				}
358#endif
359				if ((s = open("/dev/udp", O_RDONLY)) < 0) {
360#ifdef RPC_DEBUG
361					syslog(LOG_ERR, "__rpc_get_ltaddr: "
362					    "dev udp open failed");
363#endif
364					return (1);
365				}
366
367				(void) memcpy(&areq.sa_addr, &v4tmp,
368								sizeof (v4tmp));
369				areq.sa_res = -1;
370				if (ioctl(s, SIOCTMYADDR, (caddr_t)&areq) < 0) {
371					syslog(LOG_ERR,
372					    "get_ltaddr:ioctl for udp failed");
373					(void) close(s);
374					return (1);
375				}
376				(void) close(s);
377				if (areq.sa_res == 1) {
378				    /* LINTED pointer cast */
379				    ipv4sa = (struct sockaddr_in *)ltaddr->buf;
380				    ipv4sa->sin_family = AF_INET;
381				    ipv4sa->sin_addr = *(struct in_addr *)opt;
382				    return (0);
383				} else
384				    return (1);
385
386			}
387			break;
388		case IPPROTO_IPV6:
389			if (opt->name == IPV6_PKTINFO) {
390				struct sockaddr_in6 v6tmp;
391				opt++;
392				if (((uchar_t *)opt +
393				    sizeof (struct in6_pktinfo)) >
394				    (uchar_t *)opt_end) {
395					t_errno = TBADOPT;
396					return (-1);
397				}
398				bzero(&v6tmp, sizeof (v6tmp));
399				v6tmp.sin6_family = AF_INET6;
400				v6tmp.sin6_addr =
401					((struct in6_pktinfo *)opt)->ipi6_addr;
402#ifdef RPC_DEBUG
403				{
404				struct in6_pktinfo *in6_pkt;
405				char str[INET6_ADDRSTRLEN];
406
407				in6_pkt = (struct in6_pktinfo *)opt;
408				(void) inet_ntop(AF_INET6, &in6_pkt->ipi6_addr,
409						str, sizeof (str));
410				syslog(LOG_INFO,
411					"__rpc_get_ltaddr for IPV6_PKTINFO: %s",
412					str);
413				}
414#endif
415				if ((s = open("/dev/udp6", O_RDONLY)) < 0) {
416#ifdef RPC_DEBUG
417					syslog(LOG_ERR, "__rpc_get_ltaddr: "
418					    "dev udp6 open failed");
419#endif
420					return (1);
421				}
422
423				(void) memcpy(&areq.sa_addr, &v6tmp,
424								sizeof (v6tmp));
425				areq.sa_res = -1;
426				if (ioctl(s, SIOCTMYADDR, (caddr_t)&areq) < 0) {
427					syslog(LOG_ERR,
428					    "get_ltaddr:ioctl for udp6 failed");
429					(void) close(s);
430					return (1);
431				}
432				(void) close(s);
433				if (areq.sa_res == 1) {
434				    /* LINTED pointer cast */
435				    ipv6sa = (struct sockaddr_in6 *)ltaddr->buf;
436				    ipv6sa->sin6_family = AF_INET6;
437				    ipv6sa->sin6_addr =
438					((struct in6_pktinfo *)opt)->ipi6_addr;
439
440				    return (0);
441				} else
442				    return (1);
443			}
444			break;
445		default:
446			break;
447		}
448		/* LINTED improper alignment */
449		opt = (struct T_opthdr *)((uchar_t *)opt +
450			    __TPI_ALIGN(opt->len));
451	} while (opt < opt_end);
452	return (1);
453}
454
455#define	__TRANSPORT_INDSZ	128
456
457int
458__rpc_tli_set_options(int fd, int optlevel, int optname, int optval)
459{
460	struct t_optmgmt oreq, ores;
461	struct opthdr *topt;
462	int *ip;
463	int optsz;
464	char buf[__TRANSPORT_INDSZ];
465
466
467	switch (optname) {
468	case SO_DONTLINGER: {
469		struct linger *ling;
470		/* LINTED */
471		ling = (struct linger *)
472			(buf + sizeof (struct opthdr));
473		ling->l_onoff = 0;
474		optsz = sizeof (struct linger);
475		break;
476	}
477
478	case SO_LINGER: {
479		struct linger *ling;
480		/* LINTED */
481		ling = (struct linger *)
482			(buf + sizeof (struct opthdr));
483		ling->l_onoff = 1;
484		ling->l_linger = (int)optval;
485		optsz = sizeof (struct linger);
486		break;
487	}
488	case IP_RECVDSTADDR:
489	case IPV6_RECVPKTINFO:
490	case SO_DEBUG:
491	case SO_KEEPALIVE:
492	case SO_DONTROUTE:
493	case SO_USELOOPBACK:
494	case SO_REUSEADDR:
495	case SO_DGRAM_ERRIND:
496	case SO_RECVUCRED:
497	case TCP_EXCLBIND:
498	case UDP_EXCLBIND:
499		/* LINTED */
500		ip = (int *)(buf + sizeof (struct opthdr));
501		*ip = optval;
502		optsz = sizeof (int);
503		break;
504	default:
505		return (-1);
506	}
507
508	/* LINTED */
509	topt = (struct opthdr *)buf;
510	topt->level =  optlevel;
511	topt->name = optname;
512	topt->len = optsz;
513	oreq.flags = T_NEGOTIATE;
514	oreq.opt.len = sizeof (struct opthdr) + optsz;
515	oreq.opt.buf = buf;
516
517	ores.flags = 0;
518	ores.opt.buf = buf;
519	ores.opt.maxlen = __TRANSPORT_INDSZ;
520	if (t_optmgmt(fd, &oreq, &ores) < 0 ||
521	    ores.flags != T_SUCCESS) {
522		return (-1);
523	}
524	return (0);
525}
526
527/*
528 * Format an error message corresponding to the given TLI and system error
529 * codes.
530 */
531
532void
533__tli_sys_strerror(char *buf, size_t buflen, int tli_err, int sys_err)
534{
535	char *errorstr;
536
537	if (tli_err == TSYSERR) {
538		errorstr = strerror(sys_err);
539		if (errorstr == NULL)
540			(void) snprintf(buf, buflen,
541					dgettext(__nsl_dom,
542						"Unknown system error %d"),
543					sys_err);
544		else
545			(void) strlcpy(buf, errorstr, buflen);
546	} else {
547		errorstr = t_strerror(tli_err);
548		(void) strlcpy(buf, errorstr, buflen);
549	}
550}
551