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