xref: /illumos-gate/usr/src/lib/libnsl/rpc/ti_opts.c (revision 789d94c2889bedf502063bc22addcabfa798a438)
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 
63 extern const char *inet_ntop(int, const void *, char *, socklen_t);
64 extern 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  */
83 int
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 
107 void
108 svc_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  */
122 static int
123 find_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);
184 verifyzone:
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  */
198 int
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  */
219 bool_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  */
245 int
246 svc_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  */
279 int
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 
457 int
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 
532 void
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