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