xref: /titanic_52/usr/src/cmd/cmd-inet/usr.sbin/in.rshd.c (revision 24fe0b3bf671e123467ce1df0b67cadd3614c8e4)
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 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1983-1989 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 /*
31  * Portions of this source code were derived from Berkeley 4.3 BSD
32  * under license from the Regents of the University of California.
33  */
34 
35 #define	_FILE_OFFSET_BITS 64
36 
37 /*
38  * remote shell server:
39  *	remuser\0
40  *	locuser\0
41  *	command\0
42  *	data
43  */
44 #include <sys/types.h>
45 #include <sys/ioctl.h>
46 #include <sys/telioctl.h>
47 #include <sys/param.h>
48 #include <sys/socket.h>
49 #include <sys/time.h>
50 #include <sys/stat.h>
51 #include <sys/file.h>
52 #include <sys/select.h>
53 
54 #include <netinet/in.h>
55 
56 #include <arpa/inet.h>
57 
58 #include <unistd.h>
59 #include <string.h>
60 #include <stdio.h>
61 #include <stdarg.h>
62 #include <errno.h>
63 #include <pwd.h>
64 #include <grp.h>
65 #include <signal.h>
66 #include <netdb.h>
67 #include <syslog.h>
68 #include <fcntl.h>
69 #include <ctype.h>
70 #include <locale.h>
71 
72 #include <sys/resource.h>
73 #include <sys/filio.h>
74 #include <shadow.h>
75 #include <stdlib.h>
76 
77 #include <security/pam_appl.h>
78 #include <deflt.h>
79 
80 #include <k5-int.h>
81 #include <krb5_repository.h>
82 #include <com_err.h>
83 #include <kcmd.h>
84 
85 #include <addr_match.h>
86 #include <store_forw_creds.h>
87 
88 #ifndef NCARGS
89 #define	NCARGS	5120
90 #endif /* !NCARGS */
91 
92 static void error(char *, ...);
93 static void doit(int, struct sockaddr_storage *, char **);
94 static void getstr(int, char *, int, char *);
95 
96 static int legalenvvar(char *);
97 static void add_to_envinit(char *);
98 static int locale_envmatch(char *, char *);
99 
100 /* Function decls. for functions not in any header file.  (Grrrr.) */
101 extern int audit_rshd_setup(void);
102 extern int audit_rshd_success(char *, char *, char *, char *);
103 extern int audit_rshd_fail(char *, char *, char *, char *, char *);
104 extern int audit_settid(int);
105 
106 static int do_encrypt = 0;
107 static pam_handle_t *pamh;
108 
109 /*
110  * This is the shell/kshell daemon. The very basic protocol for checking
111  * authentication and authorization is:
112  * 1) Check authentication.
113  * 2) Check authorization via the access-control files:
114  *    ~/.k5login (using krb5_kuserok) and/or
115  * Execute command if configured authoriztion checks pass, else deny
116  * permission.
117  *
118  * The configuration is done either by command-line arguments passed by inetd,
119  * or by the name of the daemon. If command-line arguments are present, they
120  * take priority. The options are:
121  * -k allow kerberos authentication (krb5 only; krb4 support is not provided)
122  * -5 same as `-k', mainly for compatability with MIT
123  * -e allow encrypted session
124  * -c demand authenticator checksum
125  * -i ignore authenticator checksum
126  * -U Refuse connections that cannot be mapped to a name via `gethostbyname'
127  * -s <tos>	Set the IP TOS option
128  * -S <keytab>	Set the keytab file to use
129  * -M <realm>	Set the Kerberos realm to use
130  */
131 
132 #define	ARGSTR	"ek5ciUD:M:S:L:?:"
133 #define	RSHD_BUFSIZ	(50 * 1024)
134 
135 static krb5_context bsd_context;
136 static krb5_keytab keytab = NULL;
137 static krb5_ccache ccache = NULL;
138 static krb5_keyblock *sessionkey = NULL;
139 
140 static int require_encrypt = 0;
141 static int resolve_hostname = 0;
142 static int krb5auth_flag = 0;	/* Flag set, when KERBEROS is enabled */
143 static enum kcmd_proto kcmd_protocol;
144 
145 #ifdef DEBUG
146 static int debug_port = 0;
147 #endif /* DEBUG */
148 
149 /*
150  * There are two authentication related masks:
151  * auth_ok and auth_sent.
152  * The auth_ok mask is the or'ing of authentication
153  * systems any one of which can be used.
154  * The auth_sent mask is the or'ing of one or more authentication/authorization
155  * systems that succeeded.  If the and'ing
156  * of these two masks is true, then authorization is successful.
157  */
158 
159 #define	AUTH_KRB5	(0x2)
160 static int auth_ok = 0;
161 static int auth_sent = 0;
162 static int checksum_required = 0;
163 static int checksum_ignored = 0;
164 
165 /*
166  * Leave room for 4 environment variables to be passed.
167  * The "-L env_var" option has been added primarily to
168  * maintain compatability with MIT.
169  */
170 #define	MAXENV	4
171 static char *save_env[MAXENV];
172 static int num_env = 0;
173 
174 static void usage(void);
175 static krb5_error_code recvauth(int, int *);
176 
177 /*ARGSUSED*/
178 int
179 main(int argc, char **argv, char **renvp)
180 {
181 	struct linger linger;
182 	int on = 1, fromlen;
183 	struct sockaddr_storage from;
184 	int fd = 0;
185 
186 	extern int opterr, optind;
187 	extern char *optarg;
188 	int ch;
189 	int tos = -1;
190 	krb5_error_code status;
191 
192 	openlog("rsh", LOG_PID | LOG_ODELAY, LOG_DAEMON);
193 	(void) audit_rshd_setup();	/* BSM */
194 	fromlen = sizeof (from);
195 
196 	(void) setlocale(LC_ALL, "");
197 
198 	/*
199 	 * Analyze parameters.
200 	 */
201 	opterr = 0;
202 	while ((ch = getopt(argc, argv, ARGSTR)) != EOF)
203 		switch (ch) {
204 		case '5':
205 		case 'k':
206 			auth_ok |= AUTH_KRB5;
207 			krb5auth_flag++;
208 			break;
209 
210 		case 'c':
211 			checksum_required = 1;
212 			krb5auth_flag++;
213 			break;
214 		case 'i':
215 			checksum_ignored = 1;
216 			krb5auth_flag++;
217 			break;
218 
219 		case 'e':
220 			require_encrypt = 1;
221 			krb5auth_flag++;
222 			break;
223 #ifdef DEBUG
224 		case 'D':
225 			debug_port = atoi(optarg);
226 			break;
227 #endif /* DEBUG */
228 		case 'U':
229 			resolve_hostname = 1;
230 			break;
231 
232 		case 'M':
233 			krb5_set_default_realm(bsd_context, optarg);
234 			krb5auth_flag++;
235 			break;
236 
237 		case 'S':
238 			if ((status = krb5_kt_resolve(bsd_context, optarg,
239 				&keytab))) {
240 				com_err("rsh", status,
241 					gettext("while resolving "
242 						"srvtab file %s"), optarg);
243 				exit(2);
244 			}
245 			krb5auth_flag++;
246 			break;
247 
248 		case 's':
249 			if (optarg == NULL || ((tos = atoi(optarg)) < 0) ||
250 				(tos > 255)) {
251 				syslog(LOG_ERR, "rshd: illegal tos value: "
252 				    "%s\n", optarg);
253 			}
254 			break;
255 
256 		case 'L':
257 			if (num_env < MAXENV) {
258 				save_env[num_env] = strdup(optarg);
259 				if (!save_env[num_env++]) {
260 					com_err("rsh", ENOMEM,
261 						gettext("in saving env"));
262 					exit(2);
263 				}
264 			} else {
265 				(void) fprintf(stderr, gettext("rshd: Only %d"
266 						" -L arguments allowed\n"),
267 						MAXENV);
268 				exit(2);
269 			}
270 			break;
271 
272 		case '?':
273 		default:
274 			usage();
275 			exit(1);
276 			break;
277 		}
278 
279 	if (optind == 0) {
280 		usage();
281 		exit(1);
282 	}
283 	argc -= optind;
284 	argv += optind;
285 
286 	if (krb5auth_flag > 0) {
287 		status = krb5_init_context(&bsd_context);
288 		if (status) {
289 			syslog(LOG_ERR, "Error initializing krb5: %s",
290 			    error_message(status));
291 			exit(1);
292 		}
293 	}
294 
295 	if (!checksum_required && !checksum_ignored)
296 		checksum_ignored = 1;
297 
298 	if (checksum_required && checksum_ignored) {
299 		syslog(LOG_CRIT, gettext("Checksums are required and ignored."
300 		"These options are mutually exclusive"
301 		"--check the documentation."));
302 		error("Configuration error: mutually exclusive "
303 				"options specified.\n");
304 		exit(1);
305 	}
306 
307 #ifdef DEBUG
308 	if (debug_port) {
309 		int s;
310 		struct sockaddr_in sin;
311 
312 		if ((s = socket(AF_INET, SOCK_STREAM, PF_UNSPEC)) < 0) {
313 			fprintf(stderr, gettext("Error in socket: %s\n"),
314 					strerror(errno));
315 			exit(2);
316 		}
317 		(void) memset((char *)&sin, 0, sizeof (sin));
318 		sin.sin_family = AF_INET;
319 		sin.sin_port = htons(debug_port);
320 		sin.sin_addr.s_addr = INADDR_ANY;
321 
322 		(void) setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
323 			(char *)&on, sizeof (on));
324 
325 		if ((bind(s, (struct sockaddr *)&sin, sizeof (sin))) < 0) {
326 			(void) fprintf(stderr, gettext("Error in bind: %s\n"),
327 					strerror(errno));
328 			exit(2);
329 		}
330 		if ((listen(s, 5)) < 0) {
331 			(void) fprintf(stderr, gettext("Error in listen: %s\n"),
332 					strerror(errno));
333 			exit(2);
334 		}
335 		if ((fd = accept(s, (struct sockaddr *)&from,
336 					&fromlen)) < 0) {
337 			(void) fprintf(stderr, gettext("Error in accept: %s\n"),
338 					strerror(errno));
339 			exit(2);
340 		}
341 		(void) close(s);
342 	}
343 	else
344 #endif /* DEBUG */
345 	{
346 		if (getpeername(STDIN_FILENO, (struct sockaddr *)&from,
347 				(socklen_t *)&fromlen) < 0) {
348 			(void) fprintf(stderr, "rshd: ");
349 			perror("getpeername");
350 			_exit(1);
351 		}
352 		fd = STDIN_FILENO;
353 	}
354 
355 	if (audit_settid(fd) != 0) {
356 		perror("settid");
357 		exit(1);
358 	}
359 
360 	if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *)&on,
361 	    sizeof (on)) < 0)
362 		syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m");
363 	linger.l_onoff = 1;
364 	linger.l_linger = 60;			/* XXX */
365 	if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *)&linger,
366 	    sizeof (linger)) < 0)
367 		syslog(LOG_WARNING, "setsockopt (SO_LINGER): %m");
368 
369 	if ((tos != -1) && (setsockopt(fd, IPPROTO_IP, IP_TOS, (char *)&tos,
370 				sizeof (tos)) < 0) &&
371 				(errno != ENOPROTOOPT)) {
372 		syslog(LOG_ERR, "setsockopt (IP_TOS %d): %m");
373 	}
374 
375 	doit(dup(fd), &from, renvp);
376 	return (0);
377 }
378 
379 /*
380  * locale environments to be passed to shells.
381  */
382 static char *localeenv[] = {
383 	"LANG",
384 	"LC_CTYPE", "LC_NUMERIC", "LC_TIME", "LC_COLLATE",
385 	"LC_MONETARY", "LC_MESSAGES", "LC_ALL", NULL};
386 
387 /*
388  * The following is for the environment variable list
389  * used in the call to execle().  envinit is declared here,
390  * but populated after the call to getpwnam().
391  */
392 static char	*homedir;	/* "HOME=" */
393 static char	*shell;		/* "SHELL=" */
394 static char	*username;	/* "USER=" */
395 static char	*tz;		/* "TZ=" */
396 
397 static char	homestr[] = "HOME=";
398 static char	shellstr[] = "SHELL=";
399 static char	userstr[] = "USER=";
400 static char	tzstr[] = "TZ=";
401 
402 static char	**envinit;
403 #define	PAM_ENV_ELIM	16	/* allow 16 PAM environment variables */
404 #define	USERNAME_LEN	16	/* maximum number of characters in user name */
405 
406 /*
407  *	See PSARC opinion 1992/025
408  */
409 static char	userpath[] = "PATH=/usr/bin:";
410 static char	rootpath[] = "PATH=/usr/sbin:/usr/bin";
411 
412 static char cmdbuf[NCARGS+1];
413 static char hostname [MAXHOSTNAMELEN + 1];
414 static char locuser[USERNAME_LEN + 1];
415 static char remuser[USERNAME_LEN + 1];
416 
417 #define	KRB5_RECVAUTH_V5	5
418 #define	SIZEOF_INADDR sizeof	(struct in_addr)
419 
420 #define	MAX_REPOSITORY_LEN	255
421 static char repository[MAX_REPOSITORY_LEN];
422 
423 static char *kremuser;
424 static krb5_principal client = NULL;
425 
426 static char	remote_addr[64];
427 static char	local_addr[64];
428 
429 #define	_PATH_DEFAULT_LOGIN "/etc/default/login"
430 
431 static void
432 doit(int f, struct sockaddr_storage *fromp, char **renvp)
433 {
434 	char *cp;
435 
436 	struct passwd *pwd;
437 	char *path;
438 	char *tzenv;
439 	struct spwd *shpwd;
440 	struct stat statb;
441 	char **lenvp;
442 
443 	krb5_error_code status;
444 	int valid_checksum;
445 	int cnt;
446 	int sin_len;
447 	struct sockaddr_in localaddr;
448 
449 	int s;
450 	in_port_t port;
451 	pid_t pid;
452 	int pv[2], pw[2], px[2], cc;
453 	char buf[RSHD_BUFSIZ];
454 	char sig;
455 	int one = 1;
456 	int v = 0;
457 	int err = 0;
458 	int idx = 0;
459 	char **pam_env;
460 	char abuf[INET6_ADDRSTRLEN];
461 	struct sockaddr_in *sin;
462 	struct sockaddr_in6 *sin6;
463 	int fromplen;
464 	int homedir_len, shell_len, username_len, tz_len;
465 	int no_name;
466 	boolean_t bad_port;
467 	int netf = 0;
468 
469 	(void) signal(SIGINT, SIG_DFL);
470 	(void) signal(SIGQUIT, SIG_DFL);
471 	(void) signal(SIGTERM, SIG_DFL);
472 	(void) signal(SIGXCPU, SIG_DFL);
473 	(void) signal(SIGXFSZ, SIG_DFL);
474 	(void) sigset(SIGCHLD, SIG_IGN);
475 	(void) signal(SIGPIPE, SIG_DFL);
476 	(void) signal(SIGHUP, SIG_DFL);
477 
478 #ifdef DEBUG
479 	{ int t = open("/dev/tty", 2);
480 	    if (t >= 0) {
481 		(void) setsid();
482 		(void) close(t);
483 	    }
484 	}
485 #endif
486 	if (fromp->ss_family == AF_INET) {
487 		sin = (struct sockaddr_in *)fromp;
488 		port = ntohs((ushort_t)sin->sin_port);
489 		fromplen = sizeof (struct sockaddr_in);
490 	} else if (fromp->ss_family == AF_INET6) {
491 		sin6 = (struct sockaddr_in6 *)fromp;
492 		port = ntohs((ushort_t)sin6->sin6_port);
493 		fromplen = sizeof (struct sockaddr_in6);
494 	} else {
495 		syslog(LOG_ERR, "wrong address family\n");
496 		exit(1);
497 	}
498 
499 	if (fromp->ss_family == AF_INET6) {
500 		if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
501 			struct in_addr ipv4_addr;
502 
503 			IN6_V4MAPPED_TO_INADDR(&sin6->sin6_addr, &ipv4_addr);
504 			(void) inet_ntop(AF_INET, &ipv4_addr, abuf,
505 			    sizeof (abuf));
506 		} else {
507 			(void) inet_ntop(AF_INET6, &sin6->sin6_addr, abuf,
508 			    sizeof (abuf));
509 		}
510 	} else if (fromp->ss_family == AF_INET) {
511 		(void) inet_ntop(AF_INET, &sin->sin_addr, abuf, sizeof (abuf));
512 	}
513 
514 	sin_len = sizeof (struct sockaddr_in);
515 	if (getsockname(f, (struct sockaddr *)&localaddr, &sin_len) < 0) {
516 		perror("getsockname");
517 		exit(1);
518 	}
519 
520 	netf = f;
521 
522 	bad_port = (port >= IPPORT_RESERVED ||
523 		port < (uint_t)(IPPORT_RESERVED/2));
524 
525 	/* Get the name of the client side host to use later */
526 	no_name = (getnameinfo((const struct sockaddr *) fromp, fromplen,
527 		hostname, sizeof (hostname), NULL, 0, 0) != 0);
528 
529 	if (bad_port || no_name != 0) {
530 		/*
531 		 * If there is no host name available then use the
532 		 * IP address to identify the host in the PAM call
533 		 * below.  Do the same if a bad port was used, to
534 		 * prevent untrustworthy authentication.
535 		 */
536 		(void) strlcpy(hostname, abuf, sizeof (hostname));
537 	}
538 
539 	if (no_name != 0) {
540 		/*
541 		 * If the '-U' option was given on the cmd line,
542 		 * we must be able to lookup the hostname
543 		 */
544 		if (resolve_hostname) {
545 			syslog(LOG_ERR, "rshd: Couldn't resolve your "
546 			    "address into a host name.\r\n Please "
547 			    "contact your net administrator");
548 			exit(1);
549 		}
550 	} else {
551 		/*
552 		 * Even if getnameinfo() succeeded, we still have to check
553 		 * for spoofing.
554 		 */
555 		check_address("rshd", fromp, sin, sin6, abuf, hostname,
556 		    sizeof (hostname));
557 	}
558 
559 	if (!krb5auth_flag && bad_port) {
560 		if (no_name)
561 			syslog(LOG_NOTICE, "connection from %s - "
562 			    "bad port\n", abuf);
563 		else
564 			syslog(LOG_NOTICE, "connection from %s (%s) - "
565 			    "bad port\n", hostname, abuf);
566 		exit(1);
567 	}
568 
569 	(void) alarm(60);
570 	port = 0;
571 	for (;;) {
572 		char c;
573 		if ((cc = read(f, &c, 1)) != 1) {
574 			if (cc < 0)
575 				syslog(LOG_NOTICE, "read: %m");
576 			(void) shutdown(f, 1+1);
577 			exit(1);
578 		}
579 		if (c == 0)
580 			break;
581 		port = port * 10 + c - '0';
582 	}
583 	(void) alarm(0);
584 	if (port != 0) {
585 		int lport = 0;
586 		struct sockaddr_storage ctl_addr;
587 		int addrlen;
588 
589 		(void) memset(&ctl_addr, 0, sizeof (ctl_addr));
590 		addrlen = sizeof (ctl_addr);
591 		if (getsockname(f, (struct sockaddr *)&ctl_addr,
592 			&addrlen) < 0) {
593 			syslog(LOG_ERR, "getsockname: %m");
594 			exit(1);
595 		}
596 get_port:
597 		/*
598 		 * 0 means that rresvport_addr() will bind to a port in
599 		 * the anonymous priviledged port range.
600 		 */
601 		if (krb5auth_flag) {
602 			/*
603 			 * Kerberos does not support IPv6 yet.
604 			 */
605 			lport = IPPORT_RESERVED - 1;
606 		}
607 		s = rresvport_addr(&lport, &ctl_addr);
608 
609 		if (s < 0) {
610 			syslog(LOG_ERR, "can't get stderr port: %m");
611 			exit(1);
612 		}
613 		if (!krb5auth_flag && (port >= IPPORT_RESERVED)) {
614 			syslog(LOG_ERR, "2nd port not reserved\n");
615 			exit(1);
616 		}
617 		if (fromp->ss_family == AF_INET) {
618 			sin->sin_port = htons((ushort_t)port);
619 		} else if (fromp->ss_family == AF_INET6) {
620 			sin6->sin6_port = htons((ushort_t)port);
621 		}
622 		if (connect(s, (struct sockaddr *)fromp, fromplen) < 0) {
623 			if (errno == EADDRINUSE) {
624 				(void) close(s);
625 				goto get_port;
626 			}
627 			syslog(LOG_INFO, "connect second port: %m");
628 			exit(1);
629 		}
630 	}
631 	(void) dup2(f, 0);
632 	(void) dup2(f, 1);
633 	(void) dup2(f, 2);
634 
635 #ifdef DEBUG
636 	syslog(LOG_NOTICE, "rshd: Client hostname = %s", hostname);
637 	if (debug_port)
638 		syslog(LOG_NOTICE, "rshd: Debug port is %d", debug_port);
639 	if (krb5auth_flag > 0)
640 		syslog(LOG_NOTICE, "rshd: Kerberos mode is ON");
641 	else
642 		syslog(LOG_NOTICE, "rshd: Kerberos mode is OFF");
643 #endif /* DEBUG */
644 
645 	if (krb5auth_flag > 0) {
646 		if ((status = recvauth(f, &valid_checksum))) {
647 			syslog(LOG_ERR, gettext("Kerberos Authentication "
648 					"failed \n"));
649 			error("Authentication failed: %s\n",
650 					error_message(status));
651 			(void) audit_rshd_fail("Kerberos Authentication "
652 				"failed", hostname, remuser, locuser, cmdbuf);
653 			exit(1);
654 		}
655 
656 		if (checksum_required && !valid_checksum &&
657 			kcmd_protocol == KCMD_OLD_PROTOCOL) {
658 			syslog(LOG_WARNING, "Client did not supply required"
659 					" checksum--connection rejected.");
660 			error("Client did not supply required"
661 				"checksum--connection rejected.\n");
662 			(void) audit_rshd_fail("Client did not supply required"
663 				" checksum--connection rejected.", hostname,
664 				remuser, locuser, cmdbuf);	/* BSM */
665 			goto signout;
666 		}
667 
668 		/*
669 		 * Authentication has succeeded, we now need
670 		 * to check authorization.
671 		 *
672 		 * krb5_kuserok returns 1 if OK.
673 		 */
674 		if (client && krb5_kuserok(bsd_context, client, locuser)) {
675 			auth_sent |= AUTH_KRB5;
676 		} else {
677 			syslog(LOG_ERR, "Principal %s (%s@%s) for local user "
678 				"%s failed krb5_kuserok.\n",
679 				kremuser, remuser, hostname, locuser);
680 		}
681 	} else {
682 		getstr(netf, remuser, sizeof (remuser), "remuser");
683 		getstr(netf, locuser, sizeof (locuser), "locuser");
684 		getstr(netf, cmdbuf, sizeof (cmdbuf), "command");
685 	}
686 
687 #ifdef DEBUG
688 	syslog(LOG_NOTICE, "rshd: locuser = %s, remuser = %s, cmdbuf = %s",
689 			locuser, remuser, cmdbuf);
690 #endif /* DEBUG */
691 
692 	/*
693 	 * Note that there is no rsh conv functions at present.
694 	 */
695 	if (krb5auth_flag > 0) {
696 		if ((err = pam_start("krsh", locuser, NULL, &pamh))
697 				!= PAM_SUCCESS) {
698 			syslog(LOG_ERR, "pam_start() failed: %s\n",
699 				pam_strerror(0, err));
700 			exit(1);
701 		}
702 	}
703 	else
704 	{
705 		if ((err = pam_start("rsh", locuser, NULL, &pamh))
706 				!= PAM_SUCCESS) {
707 			syslog(LOG_ERR, "pam_start() failed: %s\n",
708 				pam_strerror(0, err));
709 			exit(1);
710 		}
711 	}
712 	if ((err = pam_set_item(pamh, PAM_RHOST, hostname)) != PAM_SUCCESS) {
713 		syslog(LOG_ERR, "pam_set_item() failed: %s\n",
714 			pam_strerror(pamh, err));
715 		exit(1);
716 	}
717 	if ((err = pam_set_item(pamh, PAM_RUSER, remuser)) != PAM_SUCCESS) {
718 		syslog(LOG_ERR, "pam_set_item() failed: %s\n",
719 			pam_strerror(pamh, err));
720 		exit(1);
721 	}
722 
723 	pwd = getpwnam(locuser);
724 	shpwd = getspnam(locuser);
725 	if ((pwd == NULL) || (shpwd == NULL)) {
726 		if (krb5auth_flag > 0)
727 			syslog(LOG_ERR, "Principal %s (%s@%s) for local user "
728 				"%s has no account.\n", kremuser, remuser,
729 							hostname, locuser);
730 		error("permission denied.\n");
731 		(void) audit_rshd_fail("Login incorrect", hostname,
732 			remuser, locuser, cmdbuf);	/* BSM */
733 		exit(1);
734 	}
735 
736 	if (krb5auth_flag > 0) {
737 		(void) snprintf(repository, sizeof (repository),
738 					KRB5_REPOSITORY_NAME);
739 		/*
740 		 * We currently only support special handling of the
741 		 * KRB5 PAM repository
742 		 */
743 		if (strlen(locuser) != 0) {
744 			krb5_repository_data_t krb5_data;
745 			pam_repository_t pam_rep_data;
746 
747 			krb5_data.principal = locuser;
748 			krb5_data.flags = SUNW_PAM_KRB5_ALREADY_AUTHENTICATED;
749 
750 			pam_rep_data.type = repository;
751 			pam_rep_data.scope = (void *)&krb5_data;
752 			pam_rep_data.scope_len = sizeof (krb5_data);
753 
754 			(void) pam_set_item(pamh, PAM_REPOSITORY,
755 					(void *)&pam_rep_data);
756 		}
757 	}
758 
759 	if (shpwd->sp_pwdp != 0) {
760 		if (*shpwd->sp_pwdp != '\0') {
761 			if ((v = pam_authenticate(pamh, 0)) != PAM_SUCCESS) {
762 				error("permission denied\n");
763 				(void) audit_rshd_fail("Permission denied",
764 				    hostname, remuser, locuser, cmdbuf);
765 				(void) pam_end(pamh, v);
766 				exit(1);
767 			}
768 		} else {
769 			int flags;
770 			char *p;
771 			/*
772 			 * maintain 2.1 and 4.* and BSD semantics with
773 			 * anonymous rshd unless PASSREQ is set to YES in
774 			 * /etc/default/login: then we deny logins with empty
775 			 * passwords.
776 			 */
777 			if (defopen(_PATH_DEFAULT_LOGIN) == 0) {
778 				flags = defcntl(DC_GETFLAGS, 0);
779 				TURNOFF(flags, DC_CASE);
780 				(void) defcntl(DC_SETFLAGS, flags);
781 
782 				if ((p = defread("PASSREQ=")) != NULL &&
783 				    strcasecmp(p, "YES") == 0) {
784 					error("permission denied\n");
785 					(void) audit_rshd_fail(
786 					    "Permission denied", hostname,
787 					    remuser, locuser, cmdbuf);
788 					(void) pam_end(pamh, PAM_ABORT);
789 					(void) defopen(NULL);
790 					syslog(LOG_AUTH|LOG_NOTICE,
791 					    "empty password not allowed for "
792 					    "%s from %s.", locuser, hostname);
793 					exit(1);
794 				}
795 				(void) defopen(NULL);
796 			}
797 			/*
798 			 * /etc/default/login not found or PASSREQ not set
799 			 * to YES. Allow logins without passwords.
800 			 */
801 		}
802 	}
803 
804 	if (krb5auth_flag > 0) {
805 		if (require_encrypt && (!do_encrypt)) {
806 			error("You must use encryption.\n");
807 			(void) audit_rshd_fail("You must use encryption.",
808 				hostname, remuser, locuser, cmdbuf); /* BSM */
809 			goto signout;
810 		}
811 
812 		if (!(auth_ok & auth_sent)) {
813 			if (auth_sent) {
814 				error("Another authentication mechanism "
815 				    "must be used to access this host.\n");
816 				(void) audit_rshd_fail("Another authentication"
817 					" mechanism must be used to access"
818 					" this host.\n", hostname, remuser,
819 					locuser, cmdbuf); /* BSM */
820 				goto signout;
821 			} else {
822 				error("Permission denied.\n");
823 				(void) audit_rshd_fail("Permission denied.",
824 					hostname, remuser, locuser, cmdbuf);
825 					/* BSM */
826 				goto signout;
827 			}
828 		}
829 
830 
831 		if (pwd->pw_uid && !access("/etc/nologin", F_OK)) {
832 			error("Logins currently disabled.\n");
833 			(void) audit_rshd_fail("Logins currently disabled.",
834 				hostname, remuser, locuser, cmdbuf);
835 			goto signout;
836 		}
837 
838 		/* Log access to account */
839 		if (pwd && (pwd->pw_uid == 0)) {
840 			syslog(LOG_NOTICE, "Executing %s for user %s (%s@%s)"
841 			    " as ROOT", cmdbuf,
842 			    kremuser, remuser, hostname);
843 		}
844 	}
845 
846 	if ((v = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS) {
847 		switch (v) {
848 		case PAM_NEW_AUTHTOK_REQD:
849 			error("password expired\n");
850 			(void) audit_rshd_fail("Password expired", hostname,
851 				remuser, locuser, cmdbuf); /* BSM */
852 			break;
853 		case PAM_PERM_DENIED:
854 			error("account expired\n");
855 			(void) audit_rshd_fail("Account expired", hostname,
856 				remuser, locuser, cmdbuf); /* BSM */
857 			break;
858 		case PAM_AUTHTOK_EXPIRED:
859 			error("password expired\n");
860 			(void) audit_rshd_fail("Password expired", hostname,
861 				remuser, locuser, cmdbuf); /* BSM */
862 			break;
863 		default:
864 			error("login incorrect\n");
865 			(void) audit_rshd_fail("Permission denied", hostname,
866 				remuser, locuser, cmdbuf); /* BSM */
867 			break;
868 		}
869 		(void) pam_end(pamh, PAM_ABORT);
870 		exit(1);
871 	}
872 
873 	if (chdir(pwd->pw_dir) < 0) {
874 		(void) chdir("/");
875 #ifdef notdef
876 		error("No remote directory.\n");
877 
878 		exit(1);
879 #endif
880 	}
881 
882 	/*
883 	 * XXX There is no session management currently being done
884 	 */
885 
886 	(void) write(STDERR_FILENO, "\0", 1);
887 	if (port || do_encrypt) {
888 		if ((pipe(pv) < 0)) {
889 			error("Can't make pipe.\n");
890 			(void) pam_end(pamh, PAM_ABORT);
891 			exit(1);
892 		}
893 		if (do_encrypt) {
894 			if (pipe(pw) < 0) {
895 				error("Can't make pipe 2.\n");
896 				(void) pam_end(pamh, PAM_ABORT);
897 				exit(1);
898 			}
899 			if (pipe(px) < 0) {
900 				error("Can't make pipe 3.\n");
901 				(void) pam_end(pamh, PAM_ABORT);
902 				exit(1);
903 			}
904 		}
905 		pid = fork();
906 		if (pid == (pid_t)-1)  {
907 			error("Fork (to start shell) failed on server.  "
908 				"Please try again later.\n");
909 			(void) pam_end(pamh, PAM_ABORT);
910 			exit(1);
911 		}
912 		if (pid) {
913 			fd_set ready;
914 			fd_set readfrom;
915 
916 			(void) close(STDIN_FILENO);
917 			(void) close(STDOUT_FILENO);
918 			(void) close(STDERR_FILENO);
919 			(void) close(pv[1]);
920 			if (do_encrypt) {
921 				(void) close(pw[1]);
922 				(void) close(px[0]);
923 			} else {
924 				(void) close(f);
925 			}
926 
927 			(void) FD_ZERO(&readfrom);
928 
929 			FD_SET(pv[0], &readfrom);
930 			if (do_encrypt) {
931 				FD_SET(pw[0], &readfrom);
932 				FD_SET(f, &readfrom);
933 			}
934 			if (port)
935 				FD_SET(s, &readfrom);
936 
937 			/* read f (net), write to px[1] (child stdin) */
938 			/* read pw[0] (child stdout), write to f (net) */
939 			/* read s (alt. channel), signal child */
940 			/* read pv[0] (child stderr), write to s */
941 			if (ioctl(pv[0], FIONBIO, (char *)&one) == -1)
942 				syslog(LOG_INFO, "ioctl FIONBIO: %m");
943 			if (do_encrypt &&
944 				ioctl(pw[0], FIONBIO, (char *)&one) == -1)
945 				syslog(LOG_INFO, "ioctl FIONBIO: %m");
946 			do {
947 				ready = readfrom;
948 				if (select(FD_SETSIZE, &ready, NULL,
949 					NULL, NULL) < 0) {
950 					if (errno == EINTR) {
951 						continue;
952 					} else {
953 						break;
954 					}
955 				}
956 				/*
957 				 * Read from child stderr, write to net
958 				 */
959 				if (port && FD_ISSET(pv[0], &ready)) {
960 					errno = 0;
961 					cc = read(pv[0], buf, sizeof (buf));
962 					if (cc <= 0) {
963 						(void) shutdown(s, 2);
964 						FD_CLR(pv[0], &readfrom);
965 					} else {
966 						(void) deswrite(s, buf, cc, 1);
967 					}
968 				}
969 				/*
970 				 * Read from alternate channel, signal child
971 				 */
972 				if (port && FD_ISSET(s, &ready)) {
973 					if ((int)desread(s, &sig, 1, 1) <= 0)
974 						FD_CLR(s, &readfrom);
975 					else
976 						(void) killpg(pid, sig);
977 				}
978 				/*
979 				 * Read from child stdout, write to net
980 				 */
981 				if (do_encrypt && FD_ISSET(pw[0], &ready)) {
982 					errno = 0;
983 					cc = read(pw[0], buf, sizeof (buf));
984 					if (cc <= 0) {
985 						(void) shutdown(f, 2);
986 						FD_CLR(pw[0], &readfrom);
987 					} else {
988 						(void) deswrite(f, buf, cc, 0);
989 					}
990 				}
991 				/*
992 				 * Read from the net, write to child stdin
993 				 */
994 				if (do_encrypt && FD_ISSET(f, &ready)) {
995 					errno = 0;
996 					cc = desread(f, buf, sizeof (buf), 0);
997 					if (cc <= 0) {
998 						(void) close(px[1]);
999 						FD_CLR(f, &readfrom);
1000 					} else {
1001 						int wcc;
1002 						wcc = write(px[1], buf, cc);
1003 						if (wcc == -1) {
1004 							/*
1005 							 * pipe closed,
1006 							 * don't read any
1007 							 * more
1008 							 *
1009 							 * might check for
1010 							 * EPIPE
1011 							 */
1012 						    (void) close(px[1]);
1013 						    FD_CLR(f, &readfrom);
1014 						} else if (wcc != cc) {
1015 						    /* CSTYLED */
1016 						    syslog(LOG_INFO, gettext("only wrote %d/%d to child"),
1017 						    wcc, cc);
1018 						}
1019 					}
1020 				}
1021 			} while ((port && FD_ISSET(s, &readfrom)) ||
1022 				(port && FD_ISSET(pv[0], &readfrom)) ||
1023 				(do_encrypt && FD_ISSET(f, &readfrom)) ||
1024 				(do_encrypt && FD_ISSET(pw[0], &readfrom)));
1025 #ifdef DEBUG
1026 			syslog(LOG_INFO, "Shell process completed.");
1027 #endif /* DEBUG */
1028 			if (ccache)
1029 				(void) pam_close_session(pamh, 0);
1030 			(void) pam_end(pamh, PAM_SUCCESS);
1031 
1032 			exit(0);
1033 		} /* End of Parent block */
1034 
1035 		(void) setsid();	/* Should be the same as above. */
1036 		(void) close(pv[0]);
1037 		(void) dup2(pv[1], 2);
1038 		(void) close(pv[1]);
1039 		if (port)
1040 			(void) close(s);
1041 		if (do_encrypt) {
1042 			(void) close(f);
1043 			(void) close(pw[0]);
1044 			(void) close(px[1]);
1045 
1046 			(void) dup2(px[0], 0);
1047 			(void) dup2(pw[1], 1);
1048 
1049 			(void) close(px[0]);
1050 			(void) close(pw[1]);
1051 		}
1052 	}
1053 
1054 	if (*pwd->pw_shell == '\0')
1055 		pwd->pw_shell = "/bin/sh";
1056 	if (!do_encrypt)
1057 		(void) close(f);
1058 	/*
1059 	 * write audit record before making uid switch
1060 	 */
1061 	(void) audit_rshd_success(hostname, remuser, locuser, cmdbuf); /* BSM */
1062 
1063 	/* set the real (and effective) GID */
1064 	if (setgid(pwd->pw_gid) == -1) {
1065 		error("Invalid gid.\n");
1066 		(void) pam_end(pamh, PAM_ABORT);
1067 		exit(1);
1068 	}
1069 
1070 	/*
1071 	 * Initialize the supplementary group access list.
1072 	 */
1073 	if (strlen(locuser) == 0) {
1074 		error("No local user.\n");
1075 		(void) pam_end(pamh, PAM_ABORT);
1076 		exit(1);
1077 	}
1078 	if (initgroups(locuser, pwd->pw_gid) == -1) {
1079 		error("Initgroup failed.\n");
1080 		(void) pam_end(pamh, PAM_ABORT);
1081 		exit(1);
1082 	}
1083 
1084 	if ((v = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) {
1085 		error("Insufficient credentials.\n");
1086 		(void) pam_end(pamh, v);
1087 		exit(1);
1088 	}
1089 
1090 	/* set the real (and effective) UID */
1091 	if (setuid(pwd->pw_uid) == -1) {
1092 		error("Invalid uid.\n");
1093 		(void) pam_end(pamh, PAM_ABORT);
1094 		exit(1);
1095 	}
1096 
1097 	/* Change directory only after becoming the appropriate user. */
1098 	if (chdir(pwd->pw_dir) < 0) {
1099 		(void) chdir("/");
1100 		if (krb5auth_flag > 0) {
1101 			syslog(LOG_ERR, "Principal %s  (%s@%s) for local user"
1102 				" %s has no home directory.",
1103 				kremuser, remuser, hostname, locuser);
1104 			error("No remote directory.\n");
1105 			goto signout;
1106 		}
1107 #ifdef notdef
1108 		error("No remote directory.\n");
1109 		exit(1);
1110 #endif
1111 	}
1112 
1113 	path = (pwd->pw_uid == 0) ? rootpath : userpath;
1114 
1115 	/*
1116 	 * Space for the following environment variables are dynamically
1117 	 * allocated because their lengths are not known before calling
1118 	 * getpwnam().
1119 	 */
1120 	homedir_len = strlen(pwd->pw_dir) + strlen(homestr) + 1;
1121 	shell_len = strlen(pwd->pw_shell) + strlen(shellstr) + 1;
1122 	username_len = strlen(pwd->pw_name) + strlen(userstr) + 1;
1123 	homedir = (char *)malloc(homedir_len);
1124 	shell = (char *)malloc(shell_len);
1125 	username = (char *)malloc(username_len);
1126 	if (homedir == NULL || shell == NULL || username == NULL) {
1127 		perror("malloc");
1128 		exit(1);
1129 	}
1130 	(void) snprintf(homedir, homedir_len, "%s%s", homestr, pwd->pw_dir);
1131 	(void) snprintf(shell, shell_len, "%s%s", shellstr, pwd->pw_shell);
1132 	(void) snprintf(username, username_len, "%s%s", userstr, pwd->pw_name);
1133 
1134 	/* Pass timezone to executed command. */
1135 	if (tzenv = getenv("TZ")) {
1136 		tz_len = strlen(tzenv) + strlen(tzstr) + 1;
1137 		tz = malloc(tz_len);
1138 		if (tz != NULL)
1139 			(void) snprintf(tz, tz_len, "%s%s", tzstr, tzenv);
1140 	}
1141 
1142 	add_to_envinit(homedir);
1143 	add_to_envinit(shell);
1144 	add_to_envinit(path);
1145 	add_to_envinit(username);
1146 	add_to_envinit(tz);
1147 
1148 	if (krb5auth_flag > 0) {
1149 		int length;
1150 		char *buffer;
1151 
1152 		/*
1153 		 * If we have KRB5CCNAME set, then copy into the child's
1154 		 * environment.  This can't really have a fixed position
1155 		 * because `tz' may or may not be set.
1156 		 */
1157 		if (getenv("KRB5CCNAME")) {
1158 			length = (int)strlen(getenv("KRB5CCNAME")) +
1159 					(int)strlen("KRB5CCNAME=") + 1;
1160 			buffer = (char *)malloc(length);
1161 
1162 			if (buffer) {
1163 				(void) snprintf(buffer, length, "KRB5CCNAME=%s",
1164 						getenv("KRB5CCNAME"));
1165 				add_to_envinit(buffer);
1166 			}
1167 		} {
1168 			/* These two are covered by ADDRPAD */
1169 			length = strlen(inet_ntoa(localaddr.sin_addr)) + 1 +
1170 					strlen("KRB5LOCALADDR=");
1171 			(void) snprintf(local_addr, length, "KRB5LOCALADDR=%s",
1172 				inet_ntoa(localaddr.sin_addr));
1173 			add_to_envinit(local_addr);
1174 
1175 			length = strlen(inet_ntoa(sin->sin_addr)) + 1 +
1176 					strlen("KRB5REMOTEADDR=");
1177 			(void) snprintf(remote_addr, length,
1178 				"KRB5REMOTEADDR=%s", inet_ntoa(sin->sin_addr));
1179 			add_to_envinit(remote_addr);
1180 		}
1181 
1182 		/*
1183 		 * If we do anything else, make sure there is
1184 		 * space in the array.
1185 		 */
1186 		for (cnt = 0; cnt < num_env; cnt++) {
1187 			char *buf;
1188 
1189 			if (getenv(save_env[cnt])) {
1190 				length = (int)strlen(getenv(save_env[cnt])) +
1191 					(int)strlen(save_env[cnt]) + 2;
1192 
1193 				buf = (char *)malloc(length);
1194 				if (buf) {
1195 					(void) snprintf(buf, length, "%s=%s",
1196 						save_env[cnt],
1197 						getenv(save_env[cnt]));
1198 					add_to_envinit(buf);
1199 				}
1200 			}
1201 		}
1202 
1203 	}
1204 
1205 	/*
1206 	 * add PAM environment variables set by modules
1207 	 * -- only allowed 16 (PAM_ENV_ELIM)
1208 	 * -- check to see if the environment variable is legal
1209 	 */
1210 	if ((pam_env = pam_getenvlist(pamh)) != 0) {
1211 		while (pam_env[idx] != 0) {
1212 			if (idx < PAM_ENV_ELIM &&
1213 			    legalenvvar(pam_env[idx])) {
1214 				add_to_envinit(pam_env[idx]);
1215 			}
1216 			idx++;
1217 		}
1218 	}
1219 
1220 	(void) pam_end(pamh, PAM_SUCCESS);
1221 
1222 	/*
1223 	 * Pick up locale environment variables, if any.
1224 	 */
1225 	lenvp = renvp;
1226 	while (*lenvp != NULL) {
1227 		int	index;
1228 
1229 		for (index = 0; localeenv[index] != NULL; index++)
1230 			/*
1231 			 * locale_envmatch() returns 1 if
1232 			 * *lenvp is localenev[index] and valid.
1233 			 */
1234 			if (locale_envmatch(localeenv[index], *lenvp)) {
1235 				add_to_envinit(*lenvp);
1236 				break;
1237 			}
1238 
1239 		lenvp++;
1240 	}
1241 
1242 	cp = strrchr(pwd->pw_shell, '/');
1243 	if (cp != NULL)
1244 		cp++;
1245 	else
1246 		cp = pwd->pw_shell;
1247 	/*
1248 	 * rdist has been moved to /usr/bin, so /usr/ucb/rdist might not
1249 	 * be present on a system.  So if it doesn't exist we fall back
1250 	 * and try for it in /usr/bin.  We take care to match the space
1251 	 * after the name because the only purpose of this is to protect
1252 	 * the internal call from old rdist's, not humans who type
1253 	 * "rsh foo /usr/ucb/rdist".
1254 	 */
1255 #define	RDIST_PROG_NAME	"/usr/ucb/rdist -Server"
1256 	if (strncmp(cmdbuf, RDIST_PROG_NAME, strlen(RDIST_PROG_NAME)) == 0) {
1257 		if (stat("/usr/ucb/rdist", &statb) != 0) {
1258 			(void) strncpy(cmdbuf + 5, "bin", 3);
1259 		}
1260 	}
1261 
1262 #ifdef DEBUG
1263 	syslog(LOG_NOTICE, "rshd: cmdbuf = %s", cmdbuf);
1264 	if (do_encrypt)
1265 		syslog(LOG_NOTICE, "rshd: cmd to be exec'ed = %s",
1266 			((char *)cmdbuf + 3));
1267 #endif /* DEBUG */
1268 
1269 	if (do_encrypt && (strncmp(cmdbuf, "-x ", 3) == 0)) {
1270 		(void) execle(pwd->pw_shell, cp, "-c", (char *)cmdbuf + 3,
1271 				NULL, envinit);
1272 	} else {
1273 		(void) execle(pwd->pw_shell, cp, "-c", cmdbuf, NULL,
1274 				envinit);
1275 	}
1276 
1277 	perror(pwd->pw_shell);
1278 	exit(1);
1279 
1280 signout:
1281 	if (ccache)
1282 		(void) pam_close_session(pamh, 0);
1283 	ccache = NULL;
1284 	(void) pam_end(pamh, PAM_ABORT);
1285 	exit(1);
1286 }
1287 
1288 static void
1289 getstr(fd, buf, cnt, err)
1290 	int fd;
1291 	char *buf;
1292 	int cnt;
1293 	char *err;
1294 {
1295 	char c;
1296 
1297 	do {
1298 		if (read(fd, &c, 1) != 1)
1299 			exit(1);
1300 		if (cnt-- == 0) {
1301 			error("%s too long\n", err);
1302 			exit(1);
1303 		}
1304 		*buf++ = c;
1305 	} while (c != 0);
1306 }
1307 
1308 /*PRINTFLIKE1*/
1309 static void
1310 error(char *fmt, ...)
1311 {
1312 	va_list ap;
1313 	char buf[RSHD_BUFSIZ];
1314 
1315 	buf[0] = 1;
1316 	va_start(ap, fmt);
1317 	(void) vsnprintf(&buf[1], sizeof (buf) - 1, fmt, ap);
1318 	va_end(ap);
1319 	(void) write(STDERR_FILENO, buf, strlen(buf));
1320 }
1321 
1322 static char *illegal[] = {
1323 	"SHELL=",
1324 	"HOME=",
1325 	"LOGNAME=",
1326 #ifndef NO_MAIL
1327 	"MAIL=",
1328 #endif
1329 	"CDPATH=",
1330 	"IFS=",
1331 	"PATH=",
1332 	"USER=",
1333 	"TZ=",
1334 	0
1335 };
1336 
1337 /*
1338  * legalenvvar - can PAM modules insert this environmental variable?
1339  */
1340 
1341 static int
1342 legalenvvar(char *s)
1343 {
1344 	register char **p;
1345 
1346 	for (p = illegal; *p; p++)
1347 		if (strncmp(s, *p, strlen(*p)) == 0)
1348 			return (0);
1349 
1350 	if (s[0] == 'L' && s[1] == 'D' && s[2] == '_')
1351 		return (0);
1352 
1353 	return (1);
1354 }
1355 
1356 /*
1357  * Add a string to the environment of the new process.
1358  */
1359 
1360 static void
1361 add_to_envinit(char *string)
1362 {
1363 	/*
1364 	 * Reserve space for 2 * 8 = 16 environment entries initially which
1365 	 * should be enough to avoid reallocation of "envinit" in most cases.
1366 	 */
1367 	static int	size = 8;
1368 	static int	index = 0;
1369 
1370 	if (string == NULL)
1371 		return;
1372 
1373 	if ((envinit == NULL) || (index == size)) {
1374 		size *= 2;
1375 		envinit = realloc(envinit, (size + 1) * sizeof (char *));
1376 		if (envinit == NULL) {
1377 			perror("malloc");
1378 			exit(1);
1379 		}
1380 	}
1381 
1382 	envinit[index++] = string;
1383 	envinit[index] = NULL;
1384 }
1385 
1386 /*
1387  * Check if lenv and penv matches or not.
1388  */
1389 static int
1390 locale_envmatch(char *lenv, char *penv)
1391 {
1392 	while ((*lenv == *penv) && (*lenv != '\0') && (*penv != '=')) {
1393 		lenv++;
1394 		penv++;
1395 	}
1396 
1397 	/*
1398 	 * '/' is eliminated for security reason.
1399 	 */
1400 	return ((*lenv == '\0' && *penv == '=' && *(penv + 1) != '/'));
1401 }
1402 
1403 #ifndef	KRB_SENDAUTH_VLEN
1404 #define	KRB_SENDAUTH_VLEN	8	/* length for version strings */
1405 #endif
1406 
1407 /* MUST be KRB_SENDAUTH_VLEN chars */
1408 #define	KRB_SENDAUTH_VERS	"AUTHV0.1"
1409 #define	SIZEOF_INADDR sizeof (struct in_addr)
1410 
1411 static krb5_error_code
1412 recvauth(int netf, int *valid_checksum)
1413 {
1414 	krb5_auth_context auth_context = NULL;
1415 	krb5_error_code status;
1416 	struct sockaddr_in laddr;
1417 	int len;
1418 	krb5_data inbuf;
1419 	krb5_authenticator *authenticator;
1420 	krb5_ticket *ticket;
1421 	krb5_rcache rcache;
1422 	krb5_data version;
1423 	krb5_encrypt_block eblock;	/* eblock for encrypt/decrypt */
1424 	krb5_data desinbuf;
1425 	krb5_data desoutbuf;
1426 	char des_inbuf[2 * RSHD_BUFSIZ];
1427 			/* needs to be > largest read size */
1428 	char des_outbuf[2 * RSHD_BUFSIZ + 4];
1429 			/* needs to be > largest write size */
1430 
1431 	*valid_checksum = 0;
1432 	len = sizeof (laddr);
1433 
1434 	if (getsockname(netf, (struct sockaddr *)&laddr, &len)) {
1435 		exit(1);
1436 	}
1437 
1438 	if (status = krb5_auth_con_init(bsd_context, &auth_context))
1439 		return (status);
1440 
1441 	if (status = krb5_auth_con_genaddrs(bsd_context, auth_context, netf,
1442 		KRB5_AUTH_CONTEXT_GENERATE_REMOTE_FULL_ADDR))
1443 		return (status);
1444 
1445 	status = krb5_auth_con_getrcache(bsd_context, auth_context, &rcache);
1446 	if (status)
1447 		return (status);
1448 
1449 	if (!rcache) {
1450 		krb5_principal server;
1451 
1452 		status = krb5_sname_to_principal(bsd_context, 0, 0,
1453 			KRB5_NT_SRV_HST, &server);
1454 		if (status)
1455 			return (status);
1456 
1457 		status = krb5_get_server_rcache(bsd_context,
1458 			krb5_princ_component(bsd_context, server, 0),
1459 			&rcache);
1460 		krb5_free_principal(bsd_context, server);
1461 		if (status)
1462 			return (status);
1463 
1464 		status = krb5_auth_con_setrcache(bsd_context, auth_context,
1465 							rcache);
1466 		if (status)
1467 			return (status);
1468 	}
1469 
1470 	status = krb5_recvauth_version(bsd_context, &auth_context, &netf,
1471 		NULL,		/* Specify daemon principal */
1472 		0,		/* no flags */
1473 		keytab,		/* normally NULL to use v5srvtab */
1474 		&ticket,	/* return ticket */
1475 		&version);	/* application version string */
1476 
1477 
1478 	if (status) {
1479 		getstr(netf, locuser, sizeof (locuser), "locuser");
1480 		getstr(netf, cmdbuf, sizeof (cmdbuf), "command");
1481 		getstr(netf, remuser, sizeof (locuser), "remuser");
1482 		return (status);
1483 	}
1484 	getstr(netf, locuser, sizeof (locuser), "locuser");
1485 	getstr(netf, cmdbuf, sizeof (cmdbuf), "command");
1486 
1487 	/* Must be V5  */
1488 
1489 	kcmd_protocol = KCMD_UNKNOWN_PROTOCOL;
1490 	if (version.length != 9 || version.data == NULL) {
1491 		syslog(LOG_ERR, "bad application version length");
1492 		error(gettext("bad application version length\n"));
1493 		exit(1);
1494 	}
1495 	if (strncmp(version.data, "KCMDV0.1", 9) == 0) {
1496 		kcmd_protocol = KCMD_OLD_PROTOCOL;
1497 	} else if (strncmp(version.data, "KCMDV0.2", 9) == 0) {
1498 		kcmd_protocol = KCMD_NEW_PROTOCOL;
1499 	} else {
1500 		syslog(LOG_ERR, "Unrecognized KCMD protocol (%s)",
1501 			(char *)version.data);
1502 		error(gettext("Unrecognized KCMD protocol (%s)"),
1503 			(char *)version.data);
1504 		exit(1);
1505 	}
1506 	getstr(netf, remuser, sizeof (locuser), "remuser");
1507 
1508 	if ((status = krb5_unparse_name(bsd_context, ticket->enc_part2->client,
1509 			&kremuser)))
1510 		return (status);
1511 
1512 	if ((status = krb5_copy_principal(bsd_context,
1513 				ticket->enc_part2->client, &client)))
1514 		return (status);
1515 
1516 
1517 	if (checksum_required && (kcmd_protocol == KCMD_OLD_PROTOCOL)) {
1518 		if ((status = krb5_auth_con_getauthenticator(bsd_context,
1519 			auth_context, &authenticator)))
1520 			return (status);
1521 
1522 		if (authenticator->checksum && checksum_required) {
1523 			struct sockaddr_in adr;
1524 			int adr_length = sizeof (adr);
1525 			int chksumsize = strlen(cmdbuf) + strlen(locuser) + 32;
1526 			krb5_data input;
1527 			krb5_keyblock key;
1528 
1529 			char *chksumbuf = (char *)malloc(chksumsize);
1530 
1531 			if (chksumbuf == 0)
1532 				goto error_cleanup;
1533 			if (getsockname(netf, (struct sockaddr *)&adr,
1534 					&adr_length) != 0)
1535 				goto error_cleanup;
1536 
1537 			(void) snprintf(chksumbuf, chksumsize, "%u:",
1538 					ntohs(adr.sin_port));
1539 			if (strlcat(chksumbuf, cmdbuf,
1540 					chksumsize) >= chksumsize) {
1541 				syslog(LOG_ERR, "cmd buffer too long.");
1542 				free(chksumbuf);
1543 				return (-1);
1544 			}
1545 			if (strlcat(chksumbuf, locuser,
1546 					chksumsize) >= chksumsize) {
1547 				syslog(LOG_ERR, "locuser too long.");
1548 				free(chksumbuf);
1549 				return (-1);
1550 			}
1551 
1552 			input.data = chksumbuf;
1553 			input.length = strlen(chksumbuf);
1554 			key.magic = ticket->enc_part2->session->magic;
1555 			key.enctype = ticket->enc_part2->session->enctype;
1556 			key.contents = ticket->enc_part2->session->contents;
1557 			key.length = ticket->enc_part2->session->length;
1558 
1559 			status = krb5_c_verify_checksum(bsd_context,
1560 			    &key, 0, &input, authenticator->checksum,
1561 			    (unsigned int *)valid_checksum);
1562 
1563 			if (status == 0 && *valid_checksum == 0)
1564 			    status = KRB5KRB_AP_ERR_BAD_INTEGRITY;
1565 error_cleanup:
1566 			if (chksumbuf)
1567 				krb5_xfree(chksumbuf);
1568 			if (status) {
1569 				krb5_free_authenticator(bsd_context,
1570 						authenticator);
1571 				return (status);
1572 			}
1573 		}
1574 		krb5_free_authenticator(bsd_context, authenticator);
1575 	}
1576 
1577 
1578 	if ((strncmp(cmdbuf, "-x ", 3) == 0)) {
1579 		if (krb5_privacy_allowed()) {
1580 			do_encrypt = 1;
1581 		} else {
1582 			syslog(LOG_ERR, "rshd: Encryption not supported");
1583 			error("rshd: Encryption not supported. \n");
1584 			exit(2);
1585 		}
1586 
1587 		status = krb5_auth_con_getremotesubkey(bsd_context,
1588 						    auth_context,
1589 						    &sessionkey);
1590 		if (status) {
1591 			syslog(LOG_ERR, "Error getting KRB5 session subkey");
1592 			error(gettext("Error getting KRB5 session subkey"));
1593 			exit(1);
1594 		}
1595 		/*
1596 		 * The "new" protocol requires that a subkey be sent.
1597 		 */
1598 		if (sessionkey == NULL && kcmd_protocol == KCMD_NEW_PROTOCOL) {
1599 			syslog(LOG_ERR, "No KRB5 session subkey sent");
1600 			error(gettext("No KRB5 session subkey sent"));
1601 			exit(1);
1602 		}
1603 		/*
1604 		 * The "old" protocol does not permit an authenticator subkey.
1605 		 * The key is taken from the ticket instead (see below).
1606 		 */
1607 		if (sessionkey != NULL && kcmd_protocol == KCMD_OLD_PROTOCOL) {
1608 			syslog(LOG_ERR, "KRB5 session subkey not permitted "
1609 				"with old KCMD protocol");
1610 			error(gettext("KRB5 session subkey not permitted "
1611 				"with old KCMD protocol"));
1612 			exit(1);
1613 		}
1614 		/*
1615 		 * If no key at this point, use the session key from
1616 		 * the ticket.
1617 		 */
1618 		if (sessionkey == NULL) {
1619 			/*
1620 			 * Save the session key so we can configure the crypto
1621 			 * module later.
1622 			 */
1623 			status = krb5_copy_keyblock(bsd_context,
1624 						ticket->enc_part2->session,
1625 						&sessionkey);
1626 			if (status) {
1627 				syslog(LOG_ERR, "krb5_copy_keyblock failed");
1628 				error(gettext("krb5_copy_keyblock failed"));
1629 				exit(1);
1630 			}
1631 		}
1632 		/*
1633 		 * If session key still cannot be found, we must
1634 		 * exit because encryption is required here
1635 		 * when encr_flag (-x) is set.
1636 		 */
1637 		if (sessionkey == NULL) {
1638 			syslog(LOG_ERR, "Could not find an encryption key");
1639 			error(gettext("Could not find an encryption key"));
1640 			exit(1);
1641 		}
1642 
1643 		/*
1644 		 * Initialize parameters/buffers for desread & deswrite here.
1645 		 */
1646 		desinbuf.data = des_inbuf;
1647 		desoutbuf.data = des_outbuf;
1648 		desinbuf.length = sizeof (des_inbuf);
1649 		desoutbuf.length = sizeof (des_outbuf);
1650 
1651 		eblock.crypto_entry = sessionkey->enctype;
1652 		eblock.key = (krb5_keyblock *)sessionkey;
1653 
1654 		init_encrypt(do_encrypt, bsd_context, kcmd_protocol,
1655 				&desinbuf, &desoutbuf, SERVER, &eblock);
1656 	}
1657 
1658 	ticket->enc_part2->session = 0;
1659 
1660 	if ((status = krb5_read_message(bsd_context, (krb5_pointer) & netf,
1661 				&inbuf))) {
1662 		error(gettext("Error reading message: %s\n"),
1663 				error_message(status));
1664 		exit(1);
1665 	}
1666 
1667 	if (inbuf.length) {
1668 		krb5_creds **creds = NULL;
1669 
1670 		/* Forwarding being done, read creds */
1671 		if ((status = krb5_rd_cred(bsd_context,
1672 					    auth_context, &inbuf, &creds,
1673 					    NULL))) {
1674 			error("Can't get forwarded credentials: %s\n",
1675 				error_message(status));
1676 			exit(1);
1677 		}
1678 
1679 		/* Store the forwarded creds in the ccache */
1680 		if ((status = store_forw_creds(bsd_context,
1681 					    creds, ticket, locuser,
1682 					    &ccache))) {
1683 			error("Can't store forwarded credentials: %s\n",
1684 				error_message(status));
1685 			exit(1);
1686 		}
1687 		krb5_free_creds(bsd_context, *creds);
1688 	}
1689 
1690 	krb5_free_ticket(bsd_context, ticket);
1691 	return (0);
1692 }
1693 
1694 static void
1695 usage(void)
1696 {
1697 	(void) fprintf(stderr, gettext("%s: rshd [-k5eciU] "
1698 			"[-P path] [-M realm] [-s tos] "
1699 #ifdef DEBUG
1700 			"[-D port] "
1701 #endif /* DEBUG */
1702 			"[-S keytab]"), gettext("usage"));
1703 
1704 	syslog(LOG_ERR, "%s: rshd [-k5eciU] [-P path] [-M realm] [-s tos] "
1705 #ifdef DEBUG
1706 			"[-D port] "
1707 #endif /* DEBUG */
1708 			"[-S keytab]", gettext("usage"));
1709 }
1710