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