xref: /titanic_52/usr/src/cmd/cmd-inet/usr.sbin/in.rshd.c (revision 3f7d54a6b84904c8f4d8daa4c7b577bede7df8b9)
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 2010 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 			(void) 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 	{
480 	    int t = open("/dev/tty", 2);
481 
482 	    if (t >= 0) {
483 		(void) setsid();
484 		(void) close(t);
485 	    }
486 	}
487 #endif
488 	if (fromp->ss_family == AF_INET) {
489 		sin = (struct sockaddr_in *)fromp;
490 		port = ntohs((ushort_t)sin->sin_port);
491 		fromplen = sizeof (struct sockaddr_in);
492 	} else if (fromp->ss_family == AF_INET6) {
493 		sin6 = (struct sockaddr_in6 *)fromp;
494 		port = ntohs((ushort_t)sin6->sin6_port);
495 		fromplen = sizeof (struct sockaddr_in6);
496 	} else {
497 		syslog(LOG_ERR, "wrong address family\n");
498 		exit(1);
499 	}
500 
501 	if (fromp->ss_family == AF_INET6) {
502 		if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
503 			struct in_addr ipv4_addr;
504 
505 			IN6_V4MAPPED_TO_INADDR(&sin6->sin6_addr, &ipv4_addr);
506 			(void) inet_ntop(AF_INET, &ipv4_addr, abuf,
507 			    sizeof (abuf));
508 		} else {
509 			(void) inet_ntop(AF_INET6, &sin6->sin6_addr, abuf,
510 			    sizeof (abuf));
511 		}
512 	} else if (fromp->ss_family == AF_INET) {
513 		(void) inet_ntop(AF_INET, &sin->sin_addr, abuf, sizeof (abuf));
514 	}
515 
516 	sin_len = sizeof (struct sockaddr_in);
517 	if (getsockname(f, (struct sockaddr *)&localaddr, &sin_len) < 0) {
518 		perror("getsockname");
519 		exit(1);
520 	}
521 
522 	netf = f;
523 
524 	bad_port = (port >= IPPORT_RESERVED ||
525 		port < (uint_t)(IPPORT_RESERVED/2));
526 
527 	/* Get the name of the client side host to use later */
528 	no_name = (getnameinfo((const struct sockaddr *) fromp, fromplen,
529 		hostname, sizeof (hostname), NULL, 0, 0) != 0);
530 
531 	if (bad_port || no_name != 0) {
532 		/*
533 		 * If there is no host name available then use the
534 		 * IP address to identify the host in the PAM call
535 		 * below.  Do the same if a bad port was used, to
536 		 * prevent untrustworthy authentication.
537 		 */
538 		(void) strlcpy(hostname, abuf, sizeof (hostname));
539 	}
540 
541 	if (no_name != 0) {
542 		/*
543 		 * If the '-U' option was given on the cmd line,
544 		 * we must be able to lookup the hostname
545 		 */
546 		if (resolve_hostname) {
547 			syslog(LOG_ERR, "rshd: Couldn't resolve your "
548 			    "address into a host name.\r\n Please "
549 			    "contact your net administrator");
550 			exit(1);
551 		}
552 	} else {
553 		/*
554 		 * Even if getnameinfo() succeeded, we still have to check
555 		 * for spoofing.
556 		 */
557 		check_address("rshd", fromp, sin, sin6, abuf, hostname,
558 		    sizeof (hostname));
559 	}
560 
561 	if (!krb5auth_flag && bad_port) {
562 		if (no_name)
563 			syslog(LOG_NOTICE, "connection from %s - "
564 			    "bad port\n", abuf);
565 		else
566 			syslog(LOG_NOTICE, "connection from %s (%s) - "
567 			    "bad port\n", hostname, abuf);
568 		exit(1);
569 	}
570 
571 	(void) alarm(60);
572 	port = 0;
573 	for (;;) {
574 		char c;
575 		if ((cc = read(f, &c, 1)) != 1) {
576 			if (cc < 0)
577 				syslog(LOG_NOTICE, "read: %m");
578 			(void) shutdown(f, 1+1);
579 			exit(1);
580 		}
581 		if (c == 0)
582 			break;
583 		port = port * 10 + c - '0';
584 	}
585 	(void) alarm(0);
586 	if (port != 0) {
587 		int lport = 0;
588 		struct sockaddr_storage ctl_addr;
589 		int addrlen;
590 
591 		(void) memset(&ctl_addr, 0, sizeof (ctl_addr));
592 		addrlen = sizeof (ctl_addr);
593 		if (getsockname(f, (struct sockaddr *)&ctl_addr,
594 			&addrlen) < 0) {
595 			syslog(LOG_ERR, "getsockname: %m");
596 			exit(1);
597 		}
598 get_port:
599 		/*
600 		 * 0 means that rresvport_addr() will bind to a port in
601 		 * the anonymous priviledged port range.
602 		 */
603 		if (krb5auth_flag) {
604 			/*
605 			 * Kerberos does not support IPv6 yet.
606 			 */
607 			lport = IPPORT_RESERVED - 1;
608 		}
609 		s = rresvport_addr(&lport, &ctl_addr);
610 
611 		if (s < 0) {
612 			syslog(LOG_ERR, "can't get stderr port: %m");
613 			exit(1);
614 		}
615 		if (!krb5auth_flag && (port >= IPPORT_RESERVED)) {
616 			syslog(LOG_ERR, "2nd port not reserved\n");
617 			exit(1);
618 		}
619 		if (fromp->ss_family == AF_INET) {
620 			sin->sin_port = htons((ushort_t)port);
621 		} else if (fromp->ss_family == AF_INET6) {
622 			sin6->sin6_port = htons((ushort_t)port);
623 		}
624 		if (connect(s, (struct sockaddr *)fromp, fromplen) < 0) {
625 			if (errno == EADDRINUSE) {
626 				(void) close(s);
627 				goto get_port;
628 			}
629 			syslog(LOG_INFO, "connect second port: %m");
630 			exit(1);
631 		}
632 	}
633 	(void) dup2(f, 0);
634 	(void) dup2(f, 1);
635 	(void) dup2(f, 2);
636 
637 #ifdef DEBUG
638 	syslog(LOG_NOTICE, "rshd: Client hostname = %s", hostname);
639 	if (debug_port)
640 		syslog(LOG_NOTICE, "rshd: Debug port is %d", debug_port);
641 	if (krb5auth_flag > 0)
642 		syslog(LOG_NOTICE, "rshd: Kerberos mode is ON");
643 	else
644 		syslog(LOG_NOTICE, "rshd: Kerberos mode is OFF");
645 #endif /* DEBUG */
646 
647 	if (krb5auth_flag > 0) {
648 		if ((status = recvauth(f, &valid_checksum))) {
649 			syslog(LOG_ERR, gettext("Kerberos Authentication "
650 					"failed \n"));
651 			error("Authentication failed: %s\n",
652 					error_message(status));
653 			(void) audit_rshd_fail("Kerberos Authentication "
654 				"failed", hostname, remuser, locuser, cmdbuf);
655 			exit(1);
656 		}
657 
658 		if (checksum_required && !valid_checksum &&
659 			kcmd_protocol == KCMD_OLD_PROTOCOL) {
660 			syslog(LOG_WARNING, "Client did not supply required"
661 					" checksum--connection rejected.");
662 			error("Client did not supply required"
663 				"checksum--connection rejected.\n");
664 			(void) audit_rshd_fail("Client did not supply required"
665 				" checksum--connection rejected.", hostname,
666 				remuser, locuser, cmdbuf);	/* BSM */
667 			goto signout;
668 		}
669 
670 		/*
671 		 * Authentication has succeeded, we now need
672 		 * to check authorization.
673 		 *
674 		 * krb5_kuserok returns 1 if OK.
675 		 */
676 		if (client && krb5_kuserok(bsd_context, client, locuser)) {
677 			auth_sent |= AUTH_KRB5;
678 		} else {
679 			syslog(LOG_ERR, "Principal %s (%s@%s) for local user "
680 				"%s failed krb5_kuserok.\n",
681 				kremuser, remuser, hostname, locuser);
682 		}
683 	} else {
684 		getstr(netf, remuser, sizeof (remuser), "remuser");
685 		getstr(netf, locuser, sizeof (locuser), "locuser");
686 		getstr(netf, cmdbuf, sizeof (cmdbuf), "command");
687 	}
688 
689 #ifdef DEBUG
690 	syslog(LOG_NOTICE, "rshd: locuser = %s, remuser = %s, cmdbuf = %s",
691 			locuser, remuser, cmdbuf);
692 #endif /* DEBUG */
693 
694 	/*
695 	 * Note that there is no rsh conv functions at present.
696 	 */
697 	if (krb5auth_flag > 0) {
698 		if ((err = pam_start("krsh", locuser, NULL, &pamh))
699 				!= PAM_SUCCESS) {
700 			syslog(LOG_ERR, "pam_start() failed: %s\n",
701 				pam_strerror(0, err));
702 			exit(1);
703 		}
704 	}
705 	else
706 	{
707 		if ((err = pam_start("rsh", locuser, NULL, &pamh))
708 				!= PAM_SUCCESS) {
709 			syslog(LOG_ERR, "pam_start() failed: %s\n",
710 				pam_strerror(0, err));
711 			exit(1);
712 		}
713 	}
714 	if ((err = pam_set_item(pamh, PAM_RHOST, hostname)) != PAM_SUCCESS) {
715 		syslog(LOG_ERR, "pam_set_item() failed: %s\n",
716 			pam_strerror(pamh, err));
717 		exit(1);
718 	}
719 	if ((err = pam_set_item(pamh, PAM_RUSER, remuser)) != PAM_SUCCESS) {
720 		syslog(LOG_ERR, "pam_set_item() failed: %s\n",
721 			pam_strerror(pamh, err));
722 		exit(1);
723 	}
724 
725 	pwd = getpwnam(locuser);
726 	shpwd = getspnam(locuser);
727 	if ((pwd == NULL) || (shpwd == NULL)) {
728 		if (krb5auth_flag > 0)
729 			syslog(LOG_ERR, "Principal %s (%s@%s) for local user "
730 				"%s has no account.\n", kremuser, remuser,
731 							hostname, locuser);
732 		error("permission denied.\n");
733 		(void) audit_rshd_fail("Login incorrect", hostname,
734 			remuser, locuser, cmdbuf);	/* BSM */
735 		exit(1);
736 	}
737 
738 	if (krb5auth_flag > 0) {
739 		(void) snprintf(repository, sizeof (repository),
740 					KRB5_REPOSITORY_NAME);
741 		/*
742 		 * We currently only support special handling of the
743 		 * KRB5 PAM repository
744 		 */
745 		if (strlen(locuser) != 0) {
746 			krb5_repository_data_t krb5_data;
747 			pam_repository_t pam_rep_data;
748 
749 			krb5_data.principal = locuser;
750 			krb5_data.flags = SUNW_PAM_KRB5_ALREADY_AUTHENTICATED;
751 
752 			pam_rep_data.type = repository;
753 			pam_rep_data.scope = (void *)&krb5_data;
754 			pam_rep_data.scope_len = sizeof (krb5_data);
755 
756 			(void) pam_set_item(pamh, PAM_REPOSITORY,
757 					(void *)&pam_rep_data);
758 		}
759 	}
760 
761 	if (shpwd->sp_pwdp != 0) {
762 		if (*shpwd->sp_pwdp != '\0') {
763 			if ((v = pam_authenticate(pamh, 0)) != PAM_SUCCESS) {
764 				error("permission denied\n");
765 				(void) audit_rshd_fail("Permission denied",
766 				    hostname, remuser, locuser, cmdbuf);
767 				(void) pam_end(pamh, v);
768 				exit(1);
769 			}
770 		} else {
771 			int flags;
772 			char *p;
773 			/*
774 			 * maintain 2.1 and 4.* and BSD semantics with
775 			 * anonymous rshd unless PASSREQ is set to YES in
776 			 * /etc/default/login: then we deny logins with empty
777 			 * passwords.
778 			 */
779 			if (defopen(_PATH_DEFAULT_LOGIN) == 0) {
780 				flags = defcntl(DC_GETFLAGS, 0);
781 				TURNOFF(flags, DC_CASE);
782 				(void) defcntl(DC_SETFLAGS, flags);
783 
784 				if ((p = defread("PASSREQ=")) != NULL &&
785 				    strcasecmp(p, "YES") == 0) {
786 					error("permission denied\n");
787 					(void) audit_rshd_fail(
788 					    "Permission denied", hostname,
789 					    remuser, locuser, cmdbuf);
790 					(void) pam_end(pamh, PAM_ABORT);
791 					(void) defopen(NULL);
792 					syslog(LOG_AUTH|LOG_NOTICE,
793 					    "empty password not allowed for "
794 					    "%s from %s.", locuser, hostname);
795 					exit(1);
796 				}
797 				(void) defopen(NULL);
798 			}
799 			/*
800 			 * /etc/default/login not found or PASSREQ not set
801 			 * to YES. Allow logins without passwords.
802 			 */
803 		}
804 	}
805 
806 	if (krb5auth_flag > 0) {
807 		if (require_encrypt && (!do_encrypt)) {
808 			error("You must use encryption.\n");
809 			(void) audit_rshd_fail("You must use encryption.",
810 				hostname, remuser, locuser, cmdbuf); /* BSM */
811 			goto signout;
812 		}
813 
814 		if (!(auth_ok & auth_sent)) {
815 			if (auth_sent) {
816 				error("Another authentication mechanism "
817 				    "must be used to access this host.\n");
818 				(void) audit_rshd_fail("Another authentication"
819 					" mechanism must be used to access"
820 					" this host.\n", hostname, remuser,
821 					locuser, cmdbuf); /* BSM */
822 				goto signout;
823 			} else {
824 				error("Permission denied.\n");
825 				(void) audit_rshd_fail("Permission denied.",
826 					hostname, remuser, locuser, cmdbuf);
827 					/* BSM */
828 				goto signout;
829 			}
830 		}
831 
832 
833 		if (pwd->pw_uid && !access("/etc/nologin", F_OK)) {
834 			error("Logins currently disabled.\n");
835 			(void) audit_rshd_fail("Logins currently disabled.",
836 				hostname, remuser, locuser, cmdbuf);
837 			goto signout;
838 		}
839 
840 		/* Log access to account */
841 		if (pwd && (pwd->pw_uid == 0)) {
842 			syslog(LOG_NOTICE, "Executing %s for user %s (%s@%s)"
843 			    " as ROOT", cmdbuf,
844 			    kremuser, remuser, hostname);
845 		}
846 	}
847 
848 	if ((v = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS) {
849 		switch (v) {
850 		case PAM_NEW_AUTHTOK_REQD:
851 			error("password expired\n");
852 			(void) audit_rshd_fail("Password expired", hostname,
853 				remuser, locuser, cmdbuf); /* BSM */
854 			break;
855 		case PAM_PERM_DENIED:
856 			error("account expired\n");
857 			(void) audit_rshd_fail("Account expired", hostname,
858 				remuser, locuser, cmdbuf); /* BSM */
859 			break;
860 		case PAM_AUTHTOK_EXPIRED:
861 			error("password expired\n");
862 			(void) audit_rshd_fail("Password expired", hostname,
863 				remuser, locuser, cmdbuf); /* BSM */
864 			break;
865 		default:
866 			error("login incorrect\n");
867 			(void) audit_rshd_fail("Permission denied", hostname,
868 				remuser, locuser, cmdbuf); /* BSM */
869 			break;
870 		}
871 		(void) pam_end(pamh, PAM_ABORT);
872 		exit(1);
873 	}
874 
875 	if (chdir(pwd->pw_dir) < 0) {
876 		(void) chdir("/");
877 #ifdef notdef
878 		error("No remote directory.\n");
879 
880 		exit(1);
881 #endif
882 	}
883 
884 	/*
885 	 * XXX There is no session management currently being done
886 	 */
887 
888 	(void) write(STDERR_FILENO, "\0", 1);
889 	if (port || do_encrypt) {
890 		if ((pipe(pv) < 0)) {
891 			error("Can't make pipe.\n");
892 			(void) pam_end(pamh, PAM_ABORT);
893 			exit(1);
894 		}
895 		if (do_encrypt) {
896 			if (pipe(pw) < 0) {
897 				error("Can't make pipe 2.\n");
898 				(void) pam_end(pamh, PAM_ABORT);
899 				exit(1);
900 			}
901 			if (pipe(px) < 0) {
902 				error("Can't make pipe 3.\n");
903 				(void) pam_end(pamh, PAM_ABORT);
904 				exit(1);
905 			}
906 		}
907 		pid = fork();
908 		if (pid == (pid_t)-1)  {
909 			error("Fork (to start shell) failed on server.  "
910 				"Please try again later.\n");
911 			(void) pam_end(pamh, PAM_ABORT);
912 			exit(1);
913 		}
914 		if (pid) {
915 			fd_set ready;
916 			fd_set readfrom;
917 
918 			(void) close(STDIN_FILENO);
919 			(void) close(STDOUT_FILENO);
920 			(void) close(STDERR_FILENO);
921 			(void) close(pv[1]);
922 			if (do_encrypt) {
923 				(void) close(pw[1]);
924 				(void) close(px[0]);
925 			} else {
926 				(void) close(f);
927 			}
928 
929 			(void) FD_ZERO(&readfrom);
930 
931 			FD_SET(pv[0], &readfrom);
932 			if (do_encrypt) {
933 				FD_SET(pw[0], &readfrom);
934 				FD_SET(f, &readfrom);
935 			}
936 			if (port)
937 				FD_SET(s, &readfrom);
938 
939 			/* read f (net), write to px[1] (child stdin) */
940 			/* read pw[0] (child stdout), write to f (net) */
941 			/* read s (alt. channel), signal child */
942 			/* read pv[0] (child stderr), write to s */
943 			if (ioctl(pv[0], FIONBIO, (char *)&one) == -1)
944 				syslog(LOG_INFO, "ioctl FIONBIO: %m");
945 			if (do_encrypt &&
946 				ioctl(pw[0], FIONBIO, (char *)&one) == -1)
947 				syslog(LOG_INFO, "ioctl FIONBIO: %m");
948 			do {
949 				ready = readfrom;
950 				if (select(FD_SETSIZE, &ready, NULL,
951 					NULL, NULL) < 0) {
952 					if (errno == EINTR) {
953 						continue;
954 					} else {
955 						break;
956 					}
957 				}
958 				/*
959 				 * Read from child stderr, write to net
960 				 */
961 				if (port && FD_ISSET(pv[0], &ready)) {
962 					errno = 0;
963 					cc = read(pv[0], buf, sizeof (buf));
964 					if (cc <= 0) {
965 						(void) shutdown(s, 2);
966 						FD_CLR(pv[0], &readfrom);
967 					} else {
968 						(void) deswrite(s, buf, cc, 1);
969 					}
970 				}
971 				/*
972 				 * Read from alternate channel, signal child
973 				 */
974 				if (port && FD_ISSET(s, &ready)) {
975 					if ((int)desread(s, &sig, 1, 1) <= 0)
976 						FD_CLR(s, &readfrom);
977 					else
978 						(void) killpg(pid, sig);
979 				}
980 				/*
981 				 * Read from child stdout, write to net
982 				 */
983 				if (do_encrypt && FD_ISSET(pw[0], &ready)) {
984 					errno = 0;
985 					cc = read(pw[0], buf, sizeof (buf));
986 					if (cc <= 0) {
987 						(void) shutdown(f, 2);
988 						FD_CLR(pw[0], &readfrom);
989 					} else {
990 						(void) deswrite(f, buf, cc, 0);
991 					}
992 				}
993 				/*
994 				 * Read from the net, write to child stdin
995 				 */
996 				if (do_encrypt && FD_ISSET(f, &ready)) {
997 					errno = 0;
998 					cc = desread(f, buf, sizeof (buf), 0);
999 					if (cc <= 0) {
1000 						(void) close(px[1]);
1001 						FD_CLR(f, &readfrom);
1002 					} else {
1003 						int wcc;
1004 						wcc = write(px[1], buf, cc);
1005 						if (wcc == -1) {
1006 							/*
1007 							 * pipe closed,
1008 							 * don't read any
1009 							 * more
1010 							 *
1011 							 * might check for
1012 							 * EPIPE
1013 							 */
1014 						    (void) close(px[1]);
1015 						    FD_CLR(f, &readfrom);
1016 						} else if (wcc != cc) {
1017 						    /* CSTYLED */
1018 						    syslog(LOG_INFO, gettext("only wrote %d/%d to child"),
1019 						    wcc, cc);
1020 						}
1021 					}
1022 				}
1023 			} while ((port && FD_ISSET(s, &readfrom)) ||
1024 				(port && FD_ISSET(pv[0], &readfrom)) ||
1025 				(do_encrypt && FD_ISSET(f, &readfrom)) ||
1026 				(do_encrypt && FD_ISSET(pw[0], &readfrom)));
1027 #ifdef DEBUG
1028 			syslog(LOG_INFO, "Shell process completed.");
1029 #endif /* DEBUG */
1030 			if (ccache)
1031 				(void) pam_close_session(pamh, 0);
1032 			(void) pam_end(pamh, PAM_SUCCESS);
1033 
1034 			exit(0);
1035 		} /* End of Parent block */
1036 
1037 		(void) setsid();	/* Should be the same as above. */
1038 		(void) close(pv[0]);
1039 		(void) dup2(pv[1], 2);
1040 		(void) close(pv[1]);
1041 		if (port)
1042 			(void) close(s);
1043 		if (do_encrypt) {
1044 			(void) close(f);
1045 			(void) close(pw[0]);
1046 			(void) close(px[1]);
1047 
1048 			(void) dup2(px[0], 0);
1049 			(void) dup2(pw[1], 1);
1050 
1051 			(void) close(px[0]);
1052 			(void) close(pw[1]);
1053 		}
1054 	}
1055 
1056 	if (*pwd->pw_shell == '\0')
1057 		pwd->pw_shell = "/bin/sh";
1058 	if (!do_encrypt)
1059 		(void) close(f);
1060 	/*
1061 	 * write audit record before making uid switch
1062 	 */
1063 	(void) audit_rshd_success(hostname, remuser, locuser, cmdbuf); /* BSM */
1064 
1065 	/* set the real (and effective) GID */
1066 	if (setgid(pwd->pw_gid) == -1) {
1067 		error("Invalid gid.\n");
1068 		(void) pam_end(pamh, PAM_ABORT);
1069 		exit(1);
1070 	}
1071 
1072 	/*
1073 	 * Initialize the supplementary group access list.
1074 	 */
1075 	if (strlen(locuser) == 0) {
1076 		error("No local user.\n");
1077 		(void) pam_end(pamh, PAM_ABORT);
1078 		exit(1);
1079 	}
1080 	if (initgroups(locuser, pwd->pw_gid) == -1) {
1081 		error("Initgroup failed.\n");
1082 		(void) pam_end(pamh, PAM_ABORT);
1083 		exit(1);
1084 	}
1085 
1086 	if ((v = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) {
1087 		error("Insufficient credentials.\n");
1088 		(void) pam_end(pamh, v);
1089 		exit(1);
1090 	}
1091 
1092 	/* set the real (and effective) UID */
1093 	if (setuid(pwd->pw_uid) == -1) {
1094 		error("Invalid uid.\n");
1095 		(void) pam_end(pamh, PAM_ABORT);
1096 		exit(1);
1097 	}
1098 
1099 	/* Change directory only after becoming the appropriate user. */
1100 	if (chdir(pwd->pw_dir) < 0) {
1101 		(void) chdir("/");
1102 		if (krb5auth_flag > 0) {
1103 			syslog(LOG_ERR, "Principal %s  (%s@%s) for local user"
1104 				" %s has no home directory.",
1105 				kremuser, remuser, hostname, locuser);
1106 			error("No remote directory.\n");
1107 			goto signout;
1108 		}
1109 #ifdef notdef
1110 		error("No remote directory.\n");
1111 		exit(1);
1112 #endif
1113 	}
1114 
1115 	path = (pwd->pw_uid == 0) ? rootpath : userpath;
1116 
1117 	/*
1118 	 * Space for the following environment variables are dynamically
1119 	 * allocated because their lengths are not known before calling
1120 	 * getpwnam().
1121 	 */
1122 	homedir_len = strlen(pwd->pw_dir) + strlen(homestr) + 1;
1123 	shell_len = strlen(pwd->pw_shell) + strlen(shellstr) + 1;
1124 	username_len = strlen(pwd->pw_name) + strlen(userstr) + 1;
1125 	homedir = (char *)malloc(homedir_len);
1126 	shell = (char *)malloc(shell_len);
1127 	username = (char *)malloc(username_len);
1128 	if (homedir == NULL || shell == NULL || username == NULL) {
1129 		perror("malloc");
1130 		exit(1);
1131 	}
1132 	(void) snprintf(homedir, homedir_len, "%s%s", homestr, pwd->pw_dir);
1133 	(void) snprintf(shell, shell_len, "%s%s", shellstr, pwd->pw_shell);
1134 	(void) snprintf(username, username_len, "%s%s", userstr, pwd->pw_name);
1135 
1136 	/* Pass timezone to executed command. */
1137 	if (tzenv = getenv("TZ")) {
1138 		tz_len = strlen(tzenv) + strlen(tzstr) + 1;
1139 		tz = malloc(tz_len);
1140 		if (tz != NULL)
1141 			(void) snprintf(tz, tz_len, "%s%s", tzstr, tzenv);
1142 	}
1143 
1144 	add_to_envinit(homedir);
1145 	add_to_envinit(shell);
1146 	add_to_envinit(path);
1147 	add_to_envinit(username);
1148 	add_to_envinit(tz);
1149 
1150 	if (krb5auth_flag > 0) {
1151 		int length;
1152 		char *buffer;
1153 
1154 		/*
1155 		 * If we have KRB5CCNAME set, then copy into the child's
1156 		 * environment.  This can't really have a fixed position
1157 		 * because `tz' may or may not be set.
1158 		 */
1159 		if (getenv("KRB5CCNAME")) {
1160 			length = (int)strlen(getenv("KRB5CCNAME")) +
1161 					(int)strlen("KRB5CCNAME=") + 1;
1162 			buffer = (char *)malloc(length);
1163 
1164 			if (buffer) {
1165 				(void) snprintf(buffer, length, "KRB5CCNAME=%s",
1166 						getenv("KRB5CCNAME"));
1167 				add_to_envinit(buffer);
1168 			}
1169 		} {
1170 			/* These two are covered by ADDRPAD */
1171 			length = strlen(inet_ntoa(localaddr.sin_addr)) + 1 +
1172 					strlen("KRB5LOCALADDR=");
1173 			(void) snprintf(local_addr, length, "KRB5LOCALADDR=%s",
1174 				inet_ntoa(localaddr.sin_addr));
1175 			add_to_envinit(local_addr);
1176 
1177 			length = strlen(inet_ntoa(sin->sin_addr)) + 1 +
1178 					strlen("KRB5REMOTEADDR=");
1179 			(void) snprintf(remote_addr, length,
1180 				"KRB5REMOTEADDR=%s", inet_ntoa(sin->sin_addr));
1181 			add_to_envinit(remote_addr);
1182 		}
1183 
1184 		/*
1185 		 * If we do anything else, make sure there is
1186 		 * space in the array.
1187 		 */
1188 		for (cnt = 0; cnt < num_env; cnt++) {
1189 			char *buf;
1190 
1191 			if (getenv(save_env[cnt])) {
1192 				length = (int)strlen(getenv(save_env[cnt])) +
1193 					(int)strlen(save_env[cnt]) + 2;
1194 
1195 				buf = (char *)malloc(length);
1196 				if (buf) {
1197 					(void) snprintf(buf, length, "%s=%s",
1198 						save_env[cnt],
1199 						getenv(save_env[cnt]));
1200 					add_to_envinit(buf);
1201 				}
1202 			}
1203 		}
1204 
1205 	}
1206 
1207 	/*
1208 	 * add PAM environment variables set by modules
1209 	 * -- only allowed 16 (PAM_ENV_ELIM)
1210 	 * -- check to see if the environment variable is legal
1211 	 */
1212 	if ((pam_env = pam_getenvlist(pamh)) != 0) {
1213 		while (pam_env[idx] != 0) {
1214 			if (idx < PAM_ENV_ELIM &&
1215 			    legalenvvar(pam_env[idx])) {
1216 				add_to_envinit(pam_env[idx]);
1217 			}
1218 			idx++;
1219 		}
1220 	}
1221 
1222 	(void) pam_end(pamh, PAM_SUCCESS);
1223 
1224 	/*
1225 	 * Pick up locale environment variables, if any.
1226 	 */
1227 	lenvp = renvp;
1228 	while (*lenvp != NULL) {
1229 		int	index;
1230 
1231 		for (index = 0; localeenv[index] != NULL; index++)
1232 			/*
1233 			 * locale_envmatch() returns 1 if
1234 			 * *lenvp is localenev[index] and valid.
1235 			 */
1236 			if (locale_envmatch(localeenv[index], *lenvp)) {
1237 				add_to_envinit(*lenvp);
1238 				break;
1239 			}
1240 
1241 		lenvp++;
1242 	}
1243 
1244 	cp = strrchr(pwd->pw_shell, '/');
1245 	if (cp != NULL)
1246 		cp++;
1247 	else
1248 		cp = pwd->pw_shell;
1249 	/*
1250 	 * rdist has been moved to /usr/bin, so /usr/ucb/rdist might not
1251 	 * be present on a system.  So if it doesn't exist we fall back
1252 	 * and try for it in /usr/bin.  We take care to match the space
1253 	 * after the name because the only purpose of this is to protect
1254 	 * the internal call from old rdist's, not humans who type
1255 	 * "rsh foo /usr/ucb/rdist".
1256 	 */
1257 #define	RDIST_PROG_NAME	"/usr/ucb/rdist -Server"
1258 	if (strncmp(cmdbuf, RDIST_PROG_NAME, strlen(RDIST_PROG_NAME)) == 0) {
1259 		if (stat("/usr/ucb/rdist", &statb) != 0) {
1260 			(void) strncpy(cmdbuf + 5, "bin", 3);
1261 		}
1262 	}
1263 
1264 #ifdef DEBUG
1265 	syslog(LOG_NOTICE, "rshd: cmdbuf = %s", cmdbuf);
1266 	if (do_encrypt)
1267 		syslog(LOG_NOTICE, "rshd: cmd to be exec'ed = %s",
1268 			((char *)cmdbuf + 3));
1269 #endif /* DEBUG */
1270 
1271 	if (do_encrypt && (strncmp(cmdbuf, "-x ", 3) == 0)) {
1272 		(void) execle(pwd->pw_shell, cp, "-c", (char *)cmdbuf + 3,
1273 				NULL, envinit);
1274 	} else {
1275 		(void) execle(pwd->pw_shell, cp, "-c", cmdbuf, NULL,
1276 				envinit);
1277 	}
1278 
1279 	perror(pwd->pw_shell);
1280 	exit(1);
1281 
1282 signout:
1283 	if (ccache)
1284 		(void) pam_close_session(pamh, 0);
1285 	ccache = NULL;
1286 	(void) pam_end(pamh, PAM_ABORT);
1287 	exit(1);
1288 }
1289 
1290 static void
1291 getstr(fd, buf, cnt, err)
1292 	int fd;
1293 	char *buf;
1294 	int cnt;
1295 	char *err;
1296 {
1297 	char c;
1298 
1299 	do {
1300 		if (read(fd, &c, 1) != 1)
1301 			exit(1);
1302 		if (cnt-- == 0) {
1303 			error("%s too long\n", err);
1304 			exit(1);
1305 		}
1306 		*buf++ = c;
1307 	} while (c != 0);
1308 }
1309 
1310 /*PRINTFLIKE1*/
1311 static void
1312 error(char *fmt, ...)
1313 {
1314 	va_list ap;
1315 	char buf[RSHD_BUFSIZ];
1316 
1317 	buf[0] = 1;
1318 	va_start(ap, fmt);
1319 	(void) vsnprintf(&buf[1], sizeof (buf) - 1, fmt, ap);
1320 	va_end(ap);
1321 	(void) write(STDERR_FILENO, buf, strlen(buf));
1322 }
1323 
1324 static char *illegal[] = {
1325 	"SHELL=",
1326 	"HOME=",
1327 	"LOGNAME=",
1328 #ifndef NO_MAIL
1329 	"MAIL=",
1330 #endif
1331 	"CDPATH=",
1332 	"IFS=",
1333 	"PATH=",
1334 	"USER=",
1335 	"TZ=",
1336 	0
1337 };
1338 
1339 /*
1340  * legalenvvar - can PAM modules insert this environmental variable?
1341  */
1342 
1343 static int
1344 legalenvvar(char *s)
1345 {
1346 	register char **p;
1347 
1348 	for (p = illegal; *p; p++)
1349 		if (strncmp(s, *p, strlen(*p)) == 0)
1350 			return (0);
1351 
1352 	if (s[0] == 'L' && s[1] == 'D' && s[2] == '_')
1353 		return (0);
1354 
1355 	return (1);
1356 }
1357 
1358 /*
1359  * Add a string to the environment of the new process.
1360  */
1361 
1362 static void
1363 add_to_envinit(char *string)
1364 {
1365 	/*
1366 	 * Reserve space for 2 * 8 = 16 environment entries initially which
1367 	 * should be enough to avoid reallocation of "envinit" in most cases.
1368 	 */
1369 	static int	size = 8;
1370 	static int	index = 0;
1371 
1372 	if (string == NULL)
1373 		return;
1374 
1375 	if ((envinit == NULL) || (index == size)) {
1376 		size *= 2;
1377 		envinit = realloc(envinit, (size + 1) * sizeof (char *));
1378 		if (envinit == NULL) {
1379 			perror("malloc");
1380 			exit(1);
1381 		}
1382 	}
1383 
1384 	envinit[index++] = string;
1385 	envinit[index] = NULL;
1386 }
1387 
1388 /*
1389  * Check if lenv and penv matches or not.
1390  */
1391 static int
1392 locale_envmatch(char *lenv, char *penv)
1393 {
1394 	while ((*lenv == *penv) && (*lenv != '\0') && (*penv != '=')) {
1395 		lenv++;
1396 		penv++;
1397 	}
1398 
1399 	/*
1400 	 * '/' is eliminated for security reason.
1401 	 */
1402 	return ((*lenv == '\0' && *penv == '=' && *(penv + 1) != '/'));
1403 }
1404 
1405 #ifndef	KRB_SENDAUTH_VLEN
1406 #define	KRB_SENDAUTH_VLEN	8	/* length for version strings */
1407 #endif
1408 
1409 /* MUST be KRB_SENDAUTH_VLEN chars */
1410 #define	KRB_SENDAUTH_VERS	"AUTHV0.1"
1411 #define	SIZEOF_INADDR sizeof (struct in_addr)
1412 
1413 static krb5_error_code
1414 recvauth(int netf, int *valid_checksum)
1415 {
1416 	krb5_auth_context auth_context = NULL;
1417 	krb5_error_code status;
1418 	struct sockaddr_in laddr;
1419 	int len;
1420 	krb5_data inbuf;
1421 	krb5_authenticator *authenticator;
1422 	krb5_ticket *ticket;
1423 	krb5_rcache rcache;
1424 	krb5_data version;
1425 	krb5_encrypt_block eblock;	/* eblock for encrypt/decrypt */
1426 	krb5_data desinbuf;
1427 	krb5_data desoutbuf;
1428 	char des_inbuf[2 * RSHD_BUFSIZ];
1429 			/* needs to be > largest read size */
1430 	char des_outbuf[2 * RSHD_BUFSIZ + 4];
1431 			/* needs to be > largest write size */
1432 
1433 	*valid_checksum = 0;
1434 	len = sizeof (laddr);
1435 
1436 	if (getsockname(netf, (struct sockaddr *)&laddr, &len)) {
1437 		exit(1);
1438 	}
1439 
1440 	if (status = krb5_auth_con_init(bsd_context, &auth_context))
1441 		return (status);
1442 
1443 	if (status = krb5_auth_con_genaddrs(bsd_context, auth_context, netf,
1444 		KRB5_AUTH_CONTEXT_GENERATE_REMOTE_FULL_ADDR))
1445 		return (status);
1446 
1447 	status = krb5_auth_con_getrcache(bsd_context, auth_context, &rcache);
1448 	if (status)
1449 		return (status);
1450 
1451 	if (!rcache) {
1452 		krb5_principal server;
1453 
1454 		status = krb5_sname_to_principal(bsd_context, 0, 0,
1455 			KRB5_NT_SRV_HST, &server);
1456 		if (status)
1457 			return (status);
1458 
1459 		status = krb5_get_server_rcache(bsd_context,
1460 			krb5_princ_component(bsd_context, server, 0),
1461 			&rcache);
1462 		krb5_free_principal(bsd_context, server);
1463 		if (status)
1464 			return (status);
1465 
1466 		status = krb5_auth_con_setrcache(bsd_context, auth_context,
1467 							rcache);
1468 		if (status)
1469 			return (status);
1470 	}
1471 
1472 	status = krb5_recvauth_version(bsd_context, &auth_context, &netf,
1473 		NULL,		/* Specify daemon principal */
1474 		0,		/* no flags */
1475 		keytab,		/* normally NULL to use v5srvtab */
1476 		&ticket,	/* return ticket */
1477 		&version);	/* application version string */
1478 
1479 
1480 	if (status) {
1481 		getstr(netf, locuser, sizeof (locuser), "locuser");
1482 		getstr(netf, cmdbuf, sizeof (cmdbuf), "command");
1483 		getstr(netf, remuser, sizeof (locuser), "remuser");
1484 		return (status);
1485 	}
1486 	getstr(netf, locuser, sizeof (locuser), "locuser");
1487 	getstr(netf, cmdbuf, sizeof (cmdbuf), "command");
1488 
1489 	/* Must be V5  */
1490 
1491 	kcmd_protocol = KCMD_UNKNOWN_PROTOCOL;
1492 	if (version.length != 9 || version.data == NULL) {
1493 		syslog(LOG_ERR, "bad application version length");
1494 		error(gettext("bad application version length\n"));
1495 		exit(1);
1496 	}
1497 	if (strncmp(version.data, "KCMDV0.1", 9) == 0) {
1498 		kcmd_protocol = KCMD_OLD_PROTOCOL;
1499 	} else if (strncmp(version.data, "KCMDV0.2", 9) == 0) {
1500 		kcmd_protocol = KCMD_NEW_PROTOCOL;
1501 	} else {
1502 		syslog(LOG_ERR, "Unrecognized KCMD protocol (%s)",
1503 			(char *)version.data);
1504 		error(gettext("Unrecognized KCMD protocol (%s)"),
1505 			(char *)version.data);
1506 		exit(1);
1507 	}
1508 	getstr(netf, remuser, sizeof (locuser), "remuser");
1509 
1510 	if ((status = krb5_unparse_name(bsd_context, ticket->enc_part2->client,
1511 			&kremuser)))
1512 		return (status);
1513 
1514 	if ((status = krb5_copy_principal(bsd_context,
1515 				ticket->enc_part2->client, &client)))
1516 		return (status);
1517 
1518 
1519 	if (checksum_required && (kcmd_protocol == KCMD_OLD_PROTOCOL)) {
1520 		if ((status = krb5_auth_con_getauthenticator(bsd_context,
1521 			auth_context, &authenticator)))
1522 			return (status);
1523 
1524 		if (authenticator->checksum && checksum_required) {
1525 			struct sockaddr_in adr;
1526 			int adr_length = sizeof (adr);
1527 			int chksumsize = strlen(cmdbuf) + strlen(locuser) + 32;
1528 			krb5_data input;
1529 			krb5_keyblock key;
1530 
1531 			char *chksumbuf = (char *)malloc(chksumsize);
1532 
1533 			if (chksumbuf == 0)
1534 				goto error_cleanup;
1535 			if (getsockname(netf, (struct sockaddr *)&adr,
1536 					&adr_length) != 0)
1537 				goto error_cleanup;
1538 
1539 			(void) snprintf(chksumbuf, chksumsize, "%u:",
1540 					ntohs(adr.sin_port));
1541 			if (strlcat(chksumbuf, cmdbuf,
1542 					chksumsize) >= chksumsize) {
1543 				syslog(LOG_ERR, "cmd buffer too long.");
1544 				free(chksumbuf);
1545 				return (-1);
1546 			}
1547 			if (strlcat(chksumbuf, locuser,
1548 					chksumsize) >= chksumsize) {
1549 				syslog(LOG_ERR, "locuser too long.");
1550 				free(chksumbuf);
1551 				return (-1);
1552 			}
1553 
1554 			input.data = chksumbuf;
1555 			input.length = strlen(chksumbuf);
1556 			key.magic = ticket->enc_part2->session->magic;
1557 			key.enctype = ticket->enc_part2->session->enctype;
1558 			key.contents = ticket->enc_part2->session->contents;
1559 			key.length = ticket->enc_part2->session->length;
1560 
1561 			status = krb5_c_verify_checksum(bsd_context,
1562 			    &key, 0, &input, authenticator->checksum,
1563 			    (unsigned int *)valid_checksum);
1564 
1565 			if (status == 0 && *valid_checksum == 0)
1566 			    status = KRB5KRB_AP_ERR_BAD_INTEGRITY;
1567 error_cleanup:
1568 			if (chksumbuf)
1569 				krb5_xfree(chksumbuf);
1570 			if (status) {
1571 				krb5_free_authenticator(bsd_context,
1572 						authenticator);
1573 				return (status);
1574 			}
1575 		}
1576 		krb5_free_authenticator(bsd_context, authenticator);
1577 	}
1578 
1579 
1580 	if ((strncmp(cmdbuf, "-x ", 3) == 0)) {
1581 		if (krb5_privacy_allowed()) {
1582 			do_encrypt = 1;
1583 		} else {
1584 			syslog(LOG_ERR, "rshd: Encryption not supported");
1585 			error("rshd: Encryption not supported. \n");
1586 			exit(2);
1587 		}
1588 
1589 		status = krb5_auth_con_getremotesubkey(bsd_context,
1590 						    auth_context,
1591 						    &sessionkey);
1592 		if (status) {
1593 			syslog(LOG_ERR, "Error getting KRB5 session subkey");
1594 			error(gettext("Error getting KRB5 session subkey"));
1595 			exit(1);
1596 		}
1597 		/*
1598 		 * The "new" protocol requires that a subkey be sent.
1599 		 */
1600 		if (sessionkey == NULL && kcmd_protocol == KCMD_NEW_PROTOCOL) {
1601 			syslog(LOG_ERR, "No KRB5 session subkey sent");
1602 			error(gettext("No KRB5 session subkey sent"));
1603 			exit(1);
1604 		}
1605 		/*
1606 		 * The "old" protocol does not permit an authenticator subkey.
1607 		 * The key is taken from the ticket instead (see below).
1608 		 */
1609 		if (sessionkey != NULL && kcmd_protocol == KCMD_OLD_PROTOCOL) {
1610 			syslog(LOG_ERR, "KRB5 session subkey not permitted "
1611 				"with old KCMD protocol");
1612 			error(gettext("KRB5 session subkey not permitted "
1613 				"with old KCMD protocol"));
1614 			exit(1);
1615 		}
1616 		/*
1617 		 * If no key at this point, use the session key from
1618 		 * the ticket.
1619 		 */
1620 		if (sessionkey == NULL) {
1621 			/*
1622 			 * Save the session key so we can configure the crypto
1623 			 * module later.
1624 			 */
1625 			status = krb5_copy_keyblock(bsd_context,
1626 						ticket->enc_part2->session,
1627 						&sessionkey);
1628 			if (status) {
1629 				syslog(LOG_ERR, "krb5_copy_keyblock failed");
1630 				error(gettext("krb5_copy_keyblock failed"));
1631 				exit(1);
1632 			}
1633 		}
1634 		/*
1635 		 * If session key still cannot be found, we must
1636 		 * exit because encryption is required here
1637 		 * when encr_flag (-x) is set.
1638 		 */
1639 		if (sessionkey == NULL) {
1640 			syslog(LOG_ERR, "Could not find an encryption key");
1641 			error(gettext("Could not find an encryption key"));
1642 			exit(1);
1643 		}
1644 
1645 		/*
1646 		 * Initialize parameters/buffers for desread & deswrite here.
1647 		 */
1648 		desinbuf.data = des_inbuf;
1649 		desoutbuf.data = des_outbuf;
1650 		desinbuf.length = sizeof (des_inbuf);
1651 		desoutbuf.length = sizeof (des_outbuf);
1652 
1653 		eblock.crypto_entry = sessionkey->enctype;
1654 		eblock.key = (krb5_keyblock *)sessionkey;
1655 
1656 		init_encrypt(do_encrypt, bsd_context, kcmd_protocol,
1657 				&desinbuf, &desoutbuf, SERVER, &eblock);
1658 	}
1659 
1660 	ticket->enc_part2->session = 0;
1661 
1662 	if ((status = krb5_read_message(bsd_context, (krb5_pointer) & netf,
1663 				&inbuf))) {
1664 		error(gettext("Error reading message: %s\n"),
1665 				error_message(status));
1666 		exit(1);
1667 	}
1668 
1669 	if (inbuf.length) {
1670 		krb5_creds **creds = NULL;
1671 
1672 		/* Forwarding being done, read creds */
1673 		if ((status = krb5_rd_cred(bsd_context,
1674 					    auth_context, &inbuf, &creds,
1675 					    NULL))) {
1676 			error("Can't get forwarded credentials: %s\n",
1677 				error_message(status));
1678 			exit(1);
1679 		}
1680 
1681 		/* Store the forwarded creds in the ccache */
1682 		if ((status = store_forw_creds(bsd_context,
1683 					    creds, ticket, locuser,
1684 					    &ccache))) {
1685 			error("Can't store forwarded credentials: %s\n",
1686 				error_message(status));
1687 			exit(1);
1688 		}
1689 		krb5_free_creds(bsd_context, *creds);
1690 	}
1691 
1692 	krb5_free_ticket(bsd_context, ticket);
1693 	return (0);
1694 }
1695 
1696 static void
1697 usage(void)
1698 {
1699 	(void) fprintf(stderr, gettext("%s: rshd [-k5eciU] "
1700 			"[-P path] [-M realm] [-s tos] "
1701 #ifdef DEBUG
1702 			"[-D port] "
1703 #endif /* DEBUG */
1704 			"[-S keytab]"), gettext("usage"));
1705 
1706 	syslog(LOG_ERR, "%s: rshd [-k5eciU] [-P path] [-M realm] [-s tos] "
1707 #ifdef DEBUG
1708 			"[-D port] "
1709 #endif /* DEBUG */
1710 			"[-S keytab]", gettext("usage"));
1711 }
1712