/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983-1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ #pragma ident "%Z%%M% %I% %E% SMI" #define _FILE_OFFSET_BITS 64 /* * remote shell server: * remuser\0 * locuser\0 * command\0 * data */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef NCARGS #define NCARGS 5120 #endif /* !NCARGS */ static void error(char *, ...); static void doit(int, struct sockaddr_storage *, char **); static void getstr(int, char *, int, char *); static int legalenvvar(char *); static void add_to_envinit(char *); static int locale_envmatch(char *, char *); /* Function decls. for functions not in any header file. (Grrrr.) */ extern int audit_rshd_setup(void); extern int audit_rshd_success(char *, char *, char *, char *); extern int audit_rshd_fail(char *, char *, char *, char *, char *); extern int audit_settid(int); static int do_encrypt = 0; static pam_handle_t *pamh; /* * This is the shell/kshell daemon. The very basic protocol for checking * authentication and authorization is: * 1) Check authentication. * 2) Check authorization via the access-control files: * ~/.k5login (using krb5_kuserok) and/or * Execute command if configured authoriztion checks pass, else deny * permission. * * The configuration is done either by command-line arguments passed by inetd, * or by the name of the daemon. If command-line arguments are present, they * take priority. The options are: * -k allow kerberos authentication (krb5 only; krb4 support is not provided) * -5 same as `-k', mainly for compatability with MIT * -e allow encrypted session * -c demand authenticator checksum * -i ignore authenticator checksum * -U Refuse connections that cannot be mapped to a name via `gethostbyname' * -s Set the IP TOS option * -S Set the keytab file to use * -M Set the Kerberos realm to use */ #define ARGSTR "ek5ciUD:M:S:L:?:" #define RSHD_BUFSIZ (50 * 1024) static krb5_context bsd_context; static krb5_keytab keytab = NULL; static krb5_ccache ccache = NULL; static krb5_keyblock *sessionkey = NULL; static int require_encrypt = 0; static int resolve_hostname = 0; static int krb5auth_flag = 0; /* Flag set, when KERBEROS is enabled */ static enum kcmd_proto kcmd_protocol; #ifdef DEBUG static int debug_port = 0; #endif /* DEBUG */ /* * There are two authentication related masks: * auth_ok and auth_sent. * The auth_ok mask is the or'ing of authentication * systems any one of which can be used. * The auth_sent mask is the or'ing of one or more authentication/authorization * systems that succeeded. If the and'ing * of these two masks is true, then authorization is successful. */ #define AUTH_KRB5 (0x2) static int auth_ok = 0; static int auth_sent = 0; static int checksum_required = 0; static int checksum_ignored = 0; /* * Leave room for 4 environment variables to be passed. * The "-L env_var" option has been added primarily to * maintain compatability with MIT. */ #define MAXENV 4 static char *save_env[MAXENV]; static int num_env = 0; static void usage(void); static krb5_error_code recvauth(int, int *); /*ARGSUSED*/ int main(int argc, char **argv, char **renvp) { struct linger linger; int on = 1, fromlen; struct sockaddr_storage from; int fd = 0; extern int opterr, optind; extern char *optarg; int ch; int tos = -1; krb5_error_code status; openlog("rsh", LOG_PID | LOG_ODELAY, LOG_DAEMON); (void) audit_rshd_setup(); /* BSM */ fromlen = sizeof (from); (void) setlocale(LC_ALL, ""); /* * Analyze parameters. */ opterr = 0; while ((ch = getopt(argc, argv, ARGSTR)) != EOF) switch (ch) { case '5': case 'k': auth_ok |= AUTH_KRB5; krb5auth_flag++; break; case 'c': checksum_required = 1; krb5auth_flag++; break; case 'i': checksum_ignored = 1; krb5auth_flag++; break; case 'e': require_encrypt = 1; krb5auth_flag++; break; #ifdef DEBUG case 'D': debug_port = atoi(optarg); break; #endif /* DEBUG */ case 'U': resolve_hostname = 1; break; case 'M': krb5_set_default_realm(bsd_context, optarg); krb5auth_flag++; break; case 'S': if ((status = krb5_kt_resolve(bsd_context, optarg, &keytab))) { com_err("rsh", status, gettext("while resolving " "srvtab file %s"), optarg); exit(2); } krb5auth_flag++; break; case 's': if (optarg == NULL || ((tos = atoi(optarg)) < 0) || (tos > 255)) { syslog(LOG_ERR, "rshd: illegal tos value: " "%s\n", optarg); } break; case 'L': if (num_env < MAXENV) { save_env[num_env] = strdup(optarg); if (!save_env[num_env++]) { com_err("rsh", ENOMEM, gettext("in saving env")); exit(2); } } else { (void) fprintf(stderr, gettext("rshd: Only %d" " -L arguments allowed\n"), MAXENV); exit(2); } break; case '?': default: usage(); exit(1); break; } if (optind == 0) { usage(); exit(1); } argc -= optind; argv += optind; if (krb5auth_flag > 0) { status = krb5_init_context(&bsd_context); if (status) { syslog(LOG_ERR, "Error initializing krb5: %s", error_message(status)); exit(1); } } if (!checksum_required && !checksum_ignored) checksum_ignored = 1; if (checksum_required && checksum_ignored) { syslog(LOG_CRIT, gettext("Checksums are required and ignored." "These options are mutually exclusive" "--check the documentation.")); error("Configuration error: mutually exclusive " "options specified.\n"); exit(1); } #ifdef DEBUG if (debug_port) { int s; struct sockaddr_in sin; if ((s = socket(AF_INET, SOCK_STREAM, PF_UNSPEC)) < 0) { fprintf(stderr, gettext("Error in socket: %s\n"), strerror(errno)); exit(2); } (void) memset((char *)&sin, 0, sizeof (sin)); sin.sin_family = AF_INET; sin.sin_port = htons(debug_port); sin.sin_addr.s_addr = INADDR_ANY; (void) setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof (on)); if ((bind(s, (struct sockaddr *)&sin, sizeof (sin))) < 0) { (void) fprintf(stderr, gettext("Error in bind: %s\n"), strerror(errno)); exit(2); } if ((listen(s, 5)) < 0) { (void) fprintf(stderr, gettext("Error in listen: %s\n"), strerror(errno)); exit(2); } if ((fd = accept(s, (struct sockaddr *)&from, &fromlen)) < 0) { (void) fprintf(stderr, gettext("Error in accept: %s\n"), strerror(errno)); exit(2); } (void) close(s); } else #endif /* DEBUG */ { if (getpeername(STDIN_FILENO, (struct sockaddr *)&from, (socklen_t *)&fromlen) < 0) { (void) fprintf(stderr, "rshd: "); perror("getpeername"); _exit(1); } fd = STDIN_FILENO; } if (audit_settid(fd) != 0) { perror("settid"); exit(1); } if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *)&on, sizeof (on)) < 0) syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m"); linger.l_onoff = 1; linger.l_linger = 60; /* XXX */ if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *)&linger, sizeof (linger)) < 0) syslog(LOG_WARNING, "setsockopt (SO_LINGER): %m"); if ((tos != -1) && (setsockopt(fd, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof (tos)) < 0) && (errno != ENOPROTOOPT)) { syslog(LOG_ERR, "setsockopt (IP_TOS %d): %m"); } doit(dup(fd), &from, renvp); return (0); } /* * locale environments to be passed to shells. */ static char *localeenv[] = { "LANG", "LC_CTYPE", "LC_NUMERIC", "LC_TIME", "LC_COLLATE", "LC_MONETARY", "LC_MESSAGES", "LC_ALL", NULL}; /* * The following is for the environment variable list * used in the call to execle(). envinit is declared here, * but populated after the call to getpwnam(). */ static char *homedir; /* "HOME=" */ static char *shell; /* "SHELL=" */ static char *username; /* "USER=" */ static char *tz; /* "TZ=" */ static char homestr[] = "HOME="; static char shellstr[] = "SHELL="; static char userstr[] = "USER="; static char tzstr[] = "TZ="; static char **envinit; #define PAM_ENV_ELIM 16 /* allow 16 PAM environment variables */ #define USERNAME_LEN 16 /* maximum number of characters in user name */ /* * See PSARC opinion 1992/025 */ static char userpath[] = "PATH=/usr/bin:"; static char rootpath[] = "PATH=/usr/sbin:/usr/bin"; static char cmdbuf[NCARGS+1]; static char hostname [MAXHOSTNAMELEN + 1]; static char locuser[USERNAME_LEN + 1]; static char remuser[USERNAME_LEN + 1]; #define KRB5_RECVAUTH_V5 5 #define SIZEOF_INADDR sizeof (struct in_addr) #define MAX_REPOSITORY_LEN 255 static char repository[MAX_REPOSITORY_LEN]; static char *kremuser; static krb5_principal client = NULL; static char remote_addr[64]; static char local_addr[64]; static void doit(int f, struct sockaddr_storage *fromp, char **renvp) { char *cp; struct passwd *pwd; char *path; char *tzenv; struct spwd *shpwd; struct stat statb; char **lenvp; krb5_error_code status; int valid_checksum; int cnt; int sin_len; struct sockaddr_in localaddr; int s; in_port_t port; pid_t pid; int pv[2], pw[2], px[2], cc; char buf[RSHD_BUFSIZ]; char sig; int one = 1; int v = 0; int err = 0; int idx = 0; char **pam_env; char abuf[INET6_ADDRSTRLEN]; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; int fromplen; int homedir_len, shell_len, username_len, tz_len; int no_name; boolean_t bad_port; int netf = 0; (void) signal(SIGINT, SIG_DFL); (void) signal(SIGQUIT, SIG_DFL); (void) signal(SIGTERM, SIG_DFL); (void) signal(SIGXCPU, SIG_DFL); (void) signal(SIGXFSZ, SIG_DFL); (void) sigset(SIGCHLD, SIG_IGN); (void) signal(SIGPIPE, SIG_DFL); (void) signal(SIGHUP, SIG_DFL); #ifdef DEBUG { int t = open("/dev/tty", 2); if (t >= 0) { (void) setsid(); (void) close(t); } } #endif if (fromp->ss_family == AF_INET) { sin = (struct sockaddr_in *)fromp; port = ntohs((ushort_t)sin->sin_port); fromplen = sizeof (struct sockaddr_in); } else if (fromp->ss_family == AF_INET6) { sin6 = (struct sockaddr_in6 *)fromp; port = ntohs((ushort_t)sin6->sin6_port); fromplen = sizeof (struct sockaddr_in6); } else { syslog(LOG_ERR, "wrong address family\n"); exit(1); } if (fromp->ss_family == AF_INET6) { if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { struct in_addr ipv4_addr; IN6_V4MAPPED_TO_INADDR(&sin6->sin6_addr, &ipv4_addr); (void) inet_ntop(AF_INET, &ipv4_addr, abuf, sizeof (abuf)); } else { (void) inet_ntop(AF_INET6, &sin6->sin6_addr, abuf, sizeof (abuf)); } } else if (fromp->ss_family == AF_INET) { (void) inet_ntop(AF_INET, &sin->sin_addr, abuf, sizeof (abuf)); } sin_len = sizeof (struct sockaddr_in); if (getsockname(f, (struct sockaddr *)&localaddr, &sin_len) < 0) { perror("getsockname"); exit(1); } netf = f; bad_port = (port >= IPPORT_RESERVED || port < (uint_t)(IPPORT_RESERVED/2)); /* Get the name of the client side host to use later */ no_name = (getnameinfo((const struct sockaddr *) fromp, fromplen, hostname, sizeof (hostname), NULL, 0, 0) != 0); if (bad_port || no_name != 0) { /* * If there is no host name available then use the * IP address to identify the host in the PAM call * below. Do the same if a bad port was used, to * prevent untrustworthy authentication. */ (void) strlcpy(hostname, abuf, sizeof (hostname)); } if (no_name != 0) { /* * If the '-U' option was given on the cmd line, * we must be able to lookup the hostname */ if (resolve_hostname) { syslog(LOG_ERR, "rshd: Couldn't resolve your " "address into a host name.\r\n Please " "contact your net administrator"); exit(1); } } else { /* * Even if getnameinfo() succeeded, we still have to check * for spoofing. */ check_address("rshd", fromp, sin, sin6, abuf, hostname, sizeof (hostname)); } if (!krb5auth_flag && bad_port) { if (no_name) syslog(LOG_NOTICE, "connection from %s - " "bad port\n", abuf); else syslog(LOG_NOTICE, "connection from %s (%s) - " "bad port\n", hostname, abuf); exit(1); } (void) alarm(60); port = 0; for (;;) { char c; if ((cc = read(f, &c, 1)) != 1) { if (cc < 0) syslog(LOG_NOTICE, "read: %m"); (void) shutdown(f, 1+1); exit(1); } if (c == 0) break; port = port * 10 + c - '0'; } (void) alarm(0); if (port != 0) { int lport = 0; struct sockaddr_storage ctl_addr; int addrlen; (void) memset(&ctl_addr, 0, sizeof (ctl_addr)); addrlen = sizeof (ctl_addr); if (getsockname(f, (struct sockaddr *)&ctl_addr, &addrlen) < 0) { syslog(LOG_ERR, "getsockname: %m"); exit(1); } get_port: /* * 0 means that rresvport_addr() will bind to a port in * the anonymous priviledged port range. */ if (krb5auth_flag) { /* * Kerberos does not support IPv6 yet. */ lport = IPPORT_RESERVED - 1; } s = rresvport_addr(&lport, &ctl_addr); if (s < 0) { syslog(LOG_ERR, "can't get stderr port: %m"); exit(1); } if (!krb5auth_flag && (port >= IPPORT_RESERVED)) { syslog(LOG_ERR, "2nd port not reserved\n"); exit(1); } if (fromp->ss_family == AF_INET) { sin->sin_port = htons((ushort_t)port); } else if (fromp->ss_family == AF_INET6) { sin6->sin6_port = htons((ushort_t)port); } if (connect(s, (struct sockaddr *)fromp, fromplen) < 0) { if (errno == EADDRINUSE) { (void) close(s); goto get_port; } syslog(LOG_INFO, "connect second port: %m"); exit(1); } } (void) dup2(f, 0); (void) dup2(f, 1); (void) dup2(f, 2); #ifdef DEBUG syslog(LOG_NOTICE, "rshd: Client hostname = %s", hostname); if (debug_port) syslog(LOG_NOTICE, "rshd: Debug port is %d", debug_port); if (krb5auth_flag > 0) syslog(LOG_NOTICE, "rshd: Kerberos mode is ON"); else syslog(LOG_NOTICE, "rshd: Kerberos mode is OFF"); #endif /* DEBUG */ if (krb5auth_flag > 0) { if ((status = recvauth(f, &valid_checksum))) { syslog(LOG_ERR, gettext("Kerberos Authentication " "failed \n")); error("Authentication failed: %s\n", error_message(status)); (void) audit_rshd_fail("Kerberos Authentication " "failed", hostname, remuser, locuser, cmdbuf); exit(1); } if (checksum_required && !valid_checksum && kcmd_protocol == KCMD_OLD_PROTOCOL) { syslog(LOG_WARNING, "Client did not supply required" " checksum--connection rejected."); error("Client did not supply required" "checksum--connection rejected.\n"); (void) audit_rshd_fail("Client did not supply required" " checksum--connection rejected.", hostname, remuser, locuser, cmdbuf); /* BSM */ goto signout; } /* * Authentication has succeeded, we now need * to check authorization. * * krb5_kuserok returns 1 if OK. */ if (client && krb5_kuserok(bsd_context, client, locuser)) { auth_sent |= AUTH_KRB5; } else { syslog(LOG_ERR, "Principal %s (%s@%s) for local user " "%s failed krb5_kuserok.\n", kremuser, remuser, hostname, locuser); } } else { getstr(netf, remuser, sizeof (remuser), "remuser"); getstr(netf, locuser, sizeof (locuser), "locuser"); getstr(netf, cmdbuf, sizeof (cmdbuf), "command"); } #ifdef DEBUG syslog(LOG_NOTICE, "rshd: locuser = %s, remuser = %s, cmdbuf = %s", locuser, remuser, cmdbuf); #endif /* DEBUG */ /* * Note that there is no rsh conv functions at present. */ if (krb5auth_flag > 0) { if ((err = pam_start("krsh", locuser, NULL, &pamh)) != PAM_SUCCESS) { syslog(LOG_ERR, "pam_start() failed: %s\n", pam_strerror(0, err)); exit(1); } } else { if ((err = pam_start("rsh", locuser, NULL, &pamh)) != PAM_SUCCESS) { syslog(LOG_ERR, "pam_start() failed: %s\n", pam_strerror(0, err)); exit(1); } } if ((err = pam_set_item(pamh, PAM_RHOST, hostname)) != PAM_SUCCESS) { syslog(LOG_ERR, "pam_set_item() failed: %s\n", pam_strerror(pamh, err)); exit(1); } if ((err = pam_set_item(pamh, PAM_RUSER, remuser)) != PAM_SUCCESS) { syslog(LOG_ERR, "pam_set_item() failed: %s\n", pam_strerror(pamh, err)); exit(1); } pwd = getpwnam(locuser); shpwd = getspnam(locuser); if ((pwd == NULL) || (shpwd == NULL)) { if (krb5auth_flag > 0) syslog(LOG_ERR, "Principal %s (%s@%s) for local user " "%s has no account.\n", kremuser, remuser, hostname, locuser); error("permission denied.\n"); (void) audit_rshd_fail("Login incorrect", hostname, remuser, locuser, cmdbuf); /* BSM */ exit(1); } if (krb5auth_flag > 0) { (void) snprintf(repository, sizeof (repository), KRB5_REPOSITORY_NAME); /* * We currently only support special handling of the * KRB5 PAM repository */ if (strlen(locuser) != 0) { krb5_repository_data_t krb5_data; pam_repository_t pam_rep_data; krb5_data.principal = locuser; krb5_data.flags = SUNW_PAM_KRB5_ALREADY_AUTHENTICATED; pam_rep_data.type = repository; pam_rep_data.scope = (void *)&krb5_data; pam_rep_data.scope_len = sizeof (krb5_data); (void) pam_set_item(pamh, PAM_REPOSITORY, (void *)&pam_rep_data); } } /* * maintain 2.1 and 4.* and BSD semantics with anonymous rshd */ if (shpwd->sp_pwdp != 0 && *shpwd->sp_pwdp != '\0' && (v = pam_authenticate(pamh, 0)) != PAM_SUCCESS) { error("permission denied\n"); (void) audit_rshd_fail("Permission denied", hostname, remuser, locuser, cmdbuf); /* BSM */ (void) pam_end(pamh, v); exit(1); } if (krb5auth_flag > 0) { if (require_encrypt && (!do_encrypt)) { error("You must use encryption.\n"); (void) audit_rshd_fail("You must use encryption.", hostname, remuser, locuser, cmdbuf); /* BSM */ goto signout; } if (!(auth_ok & auth_sent)) { if (auth_sent) { error("Another authentication mechanism " "must be used to access this host.\n"); (void) audit_rshd_fail("Another authentication" " mechanism must be used to access" " this host.\n", hostname, remuser, locuser, cmdbuf); /* BSM */ goto signout; } else { error("Permission denied.\n"); (void) audit_rshd_fail("Permission denied.", hostname, remuser, locuser, cmdbuf); /* BSM */ goto signout; } } if (pwd->pw_uid && !access("/etc/nologin", F_OK)) { error("Logins currently disabled.\n"); (void) audit_rshd_fail("Logins currently disabled.", hostname, remuser, locuser, cmdbuf); goto signout; } /* Log access to account */ if (pwd && (pwd->pw_uid == 0)) { syslog(LOG_NOTICE, "Executing %s for user %s (%s@%s)" " as ROOT", cmdbuf, kremuser, remuser, hostname); } } if ((v = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS) { switch (v) { case PAM_NEW_AUTHTOK_REQD: error("password expired\n"); (void) audit_rshd_fail("Password expired", hostname, remuser, locuser, cmdbuf); /* BSM */ break; case PAM_PERM_DENIED: error("account expired\n"); (void) audit_rshd_fail("Account expired", hostname, remuser, locuser, cmdbuf); /* BSM */ break; case PAM_AUTHTOK_EXPIRED: error("password expired\n"); (void) audit_rshd_fail("Password expired", hostname, remuser, locuser, cmdbuf); /* BSM */ break; default: error("login incorrect\n"); (void) audit_rshd_fail("Permission denied", hostname, remuser, locuser, cmdbuf); /* BSM */ break; } (void) pam_end(pamh, PAM_ABORT); exit(1); } if (chdir(pwd->pw_dir) < 0) { (void) chdir("/"); #ifdef notdef error("No remote directory.\n"); exit(1); #endif } /* * XXX There is no session management currently being done */ (void) write(STDERR_FILENO, "\0", 1); if (port || do_encrypt) { if ((pipe(pv) < 0)) { error("Can't make pipe.\n"); (void) pam_end(pamh, PAM_ABORT); exit(1); } if (do_encrypt) { if (pipe(pw) < 0) { error("Can't make pipe 2.\n"); (void) pam_end(pamh, PAM_ABORT); exit(1); } if (pipe(px) < 0) { error("Can't make pipe 3.\n"); (void) pam_end(pamh, PAM_ABORT); exit(1); } } pid = fork(); if (pid == (pid_t)-1) { error("Fork (to start shell) failed on server. " "Please try again later.\n"); (void) pam_end(pamh, PAM_ABORT); exit(1); } if (pid) { fd_set ready; fd_set readfrom; (void) close(STDIN_FILENO); (void) close(STDOUT_FILENO); (void) close(STDERR_FILENO); (void) close(pv[1]); if (do_encrypt) { (void) close(pw[1]); (void) close(px[0]); } else { (void) close(f); } (void) FD_ZERO(&readfrom); FD_SET(pv[0], &readfrom); if (do_encrypt) { FD_SET(pw[0], &readfrom); FD_SET(f, &readfrom); } if (port) FD_SET(s, &readfrom); /* read f (net), write to px[1] (child stdin) */ /* read pw[0] (child stdout), write to f (net) */ /* read s (alt. channel), signal child */ /* read pv[0] (child stderr), write to s */ if (ioctl(pv[0], FIONBIO, (char *)&one) == -1) syslog(LOG_INFO, "ioctl FIONBIO: %m"); if (do_encrypt && ioctl(pw[0], FIONBIO, (char *)&one) == -1) syslog(LOG_INFO, "ioctl FIONBIO: %m"); do { ready = readfrom; if (select(FD_SETSIZE, &ready, NULL, NULL, NULL) < 0) { if (errno == EINTR) { continue; } else { break; } } /* * Read from child stderr, write to net */ if (port && FD_ISSET(pv[0], &ready)) { errno = 0; cc = read(pv[0], buf, sizeof (buf)); if (cc <= 0) { (void) shutdown(s, 2); FD_CLR(pv[0], &readfrom); } else { (void) deswrite(s, buf, cc, 1); } } /* * Read from alternate channel, signal child */ if (port && FD_ISSET(s, &ready)) { if ((int)desread(s, &sig, 1, 1) <= 0) FD_CLR(s, &readfrom); else (void) killpg(pid, sig); } /* * Read from child stdout, write to net */ if (do_encrypt && FD_ISSET(pw[0], &ready)) { errno = 0; cc = read(pw[0], buf, sizeof (buf)); if (cc <= 0) { (void) shutdown(f, 2); FD_CLR(pw[0], &readfrom); } else { (void) deswrite(f, buf, cc, 0); } } /* * Read from the net, write to child stdin */ if (do_encrypt && FD_ISSET(f, &ready)) { errno = 0; cc = desread(f, buf, sizeof (buf), 0); if (cc <= 0) { (void) close(px[1]); FD_CLR(f, &readfrom); } else { int wcc; wcc = write(px[1], buf, cc); if (wcc == -1) { /* * pipe closed, * don't read any * more * * might check for * EPIPE */ (void) close(px[1]); FD_CLR(f, &readfrom); } else if (wcc != cc) { /* CSTYLED */ syslog(LOG_INFO, gettext("only wrote %d/%d to child"), wcc, cc); } } } } while ((port && FD_ISSET(s, &readfrom)) || (port && FD_ISSET(pv[0], &readfrom)) || (do_encrypt && FD_ISSET(f, &readfrom)) || (do_encrypt && FD_ISSET(pw[0], &readfrom))); #ifdef DEBUG syslog(LOG_INFO, "Shell process completed."); #endif /* DEBUG */ if (ccache) (void) pam_close_session(pamh, 0); (void) pam_end(pamh, PAM_SUCCESS); exit(0); } /* End of Parent block */ (void) setsid(); /* Should be the same as above. */ (void) close(pv[0]); (void) dup2(pv[1], 2); (void) close(pv[1]); if (port) (void) close(s); if (do_encrypt) { (void) close(f); (void) close(pw[0]); (void) close(px[1]); (void) dup2(px[0], 0); (void) dup2(pw[1], 1); (void) close(px[0]); (void) close(pw[1]); } } if (*pwd->pw_shell == '\0') pwd->pw_shell = "/bin/sh"; if (!do_encrypt) (void) close(f); /* * write audit record before making uid switch */ (void) audit_rshd_success(hostname, remuser, locuser, cmdbuf); /* BSM */ /* set the real (and effective) GID */ if (setgid(pwd->pw_gid) == -1) { error("Invalid gid.\n"); (void) pam_end(pamh, PAM_ABORT); exit(1); } /* * Initialize the supplementary group access list. */ if (strlen(locuser) == 0) { error("No local user.\n"); (void) pam_end(pamh, PAM_ABORT); exit(1); } if (initgroups(locuser, pwd->pw_gid) == -1) { error("Initgroup failed.\n"); (void) pam_end(pamh, PAM_ABORT); exit(1); } if ((v = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) { error("Insufficient credentials.\n"); (void) pam_end(pamh, v); exit(1); } /* set the real (and effective) UID */ if (setuid(pwd->pw_uid) == -1) { error("Invalid uid.\n"); (void) pam_end(pamh, PAM_ABORT); exit(1); } /* Change directory only after becoming the appropriate user. */ if (chdir(pwd->pw_dir) < 0) { (void) chdir("/"); if (krb5auth_flag > 0) { syslog(LOG_ERR, "Principal %s (%s@%s) for local user" " %s has no home directory.", kremuser, remuser, hostname, locuser); error("No remote directory.\n"); goto signout; } #ifdef notdef error("No remote directory.\n"); exit(1); #endif } path = (pwd->pw_uid == 0) ? rootpath : userpath; /* * Space for the following environment variables are dynamically * allocated because their lengths are not known before calling * getpwnam(). */ homedir_len = strlen(pwd->pw_dir) + strlen(homestr) + 1; shell_len = strlen(pwd->pw_shell) + strlen(shellstr) + 1; username_len = strlen(pwd->pw_name) + strlen(userstr) + 1; homedir = (char *)malloc(homedir_len); shell = (char *)malloc(shell_len); username = (char *)malloc(username_len); if (homedir == NULL || shell == NULL || username == NULL) { perror("malloc"); exit(1); } (void) snprintf(homedir, homedir_len, "%s%s", homestr, pwd->pw_dir); (void) snprintf(shell, shell_len, "%s%s", shellstr, pwd->pw_shell); (void) snprintf(username, username_len, "%s%s", userstr, pwd->pw_name); /* Pass timezone to executed command. */ if (tzenv = getenv("TZ")) { tz_len = strlen(tzenv) + strlen(tzstr) + 1; tz = malloc(tz_len); if (tz != NULL) (void) snprintf(tz, tz_len, "%s%s", tzstr, tzenv); } add_to_envinit(homedir); add_to_envinit(shell); add_to_envinit(path); add_to_envinit(username); add_to_envinit(tz); if (krb5auth_flag > 0) { int length; char *buffer; /* * If we have KRB5CCNAME set, then copy into the child's * environment. This can't really have a fixed position * because `tz' may or may not be set. */ if (getenv("KRB5CCNAME")) { length = (int)strlen(getenv("KRB5CCNAME")) + (int)strlen("KRB5CCNAME=") + 1; buffer = (char *)malloc(length); if (buffer) { (void) snprintf(buffer, length, "KRB5CCNAME=%s", getenv("KRB5CCNAME")); add_to_envinit(buffer); } } { /* These two are covered by ADDRPAD */ length = strlen(inet_ntoa(localaddr.sin_addr)) + 1 + strlen("KRB5LOCALADDR="); (void) snprintf(local_addr, length, "KRB5LOCALADDR=%s", inet_ntoa(localaddr.sin_addr)); add_to_envinit(local_addr); length = strlen(inet_ntoa(sin->sin_addr)) + 1 + strlen("KRB5REMOTEADDR="); (void) snprintf(remote_addr, length, "KRB5REMOTEADDR=%s", inet_ntoa(sin->sin_addr)); add_to_envinit(remote_addr); } /* * If we do anything else, make sure there is * space in the array. */ for (cnt = 0; cnt < num_env; cnt++) { char *buf; if (getenv(save_env[cnt])) { length = (int)strlen(getenv(save_env[cnt])) + (int)strlen(save_env[cnt]) + 2; buf = (char *)malloc(length); if (buf) { (void) snprintf(buf, length, "%s=%s", save_env[cnt], getenv(save_env[cnt])); add_to_envinit(buf); } } } } /* * add PAM environment variables set by modules * -- only allowed 16 (PAM_ENV_ELIM) * -- check to see if the environment variable is legal */ if ((pam_env = pam_getenvlist(pamh)) != 0) { while (pam_env[idx] != 0) { if (idx < PAM_ENV_ELIM && legalenvvar(pam_env[idx])) { add_to_envinit(pam_env[idx]); } idx++; } } (void) pam_end(pamh, PAM_SUCCESS); /* * Pick up locale environment variables, if any. */ lenvp = renvp; while (*lenvp != NULL) { int index; for (index = 0; localeenv[index] != NULL; index++) /* * locale_envmatch() returns 1 if * *lenvp is localenev[index] and valid. */ if (locale_envmatch(localeenv[index], *lenvp)) { add_to_envinit(*lenvp); break; } lenvp++; } cp = strrchr(pwd->pw_shell, '/'); if (cp != NULL) cp++; else cp = pwd->pw_shell; /* * rdist has been moved to /usr/bin, so /usr/ucb/rdist might not * be present on a system. So if it doesn't exist we fall back * and try for it in /usr/bin. We take care to match the space * after the name because the only purpose of this is to protect * the internal call from old rdist's, not humans who type * "rsh foo /usr/ucb/rdist". */ #define RDIST_PROG_NAME "/usr/ucb/rdist -Server" if (strncmp(cmdbuf, RDIST_PROG_NAME, strlen(RDIST_PROG_NAME)) == 0) { if (stat("/usr/ucb/rdist", &statb) != 0) { (void) strncpy(cmdbuf + 5, "bin", 3); } } #ifdef DEBUG syslog(LOG_NOTICE, "rshd: cmdbuf = %s", cmdbuf); if (do_encrypt) syslog(LOG_NOTICE, "rshd: cmd to be exec'ed = %s", ((char *)cmdbuf + 3)); #endif /* DEBUG */ if (do_encrypt && (strncmp(cmdbuf, "-x ", 3) == 0)) { (void) execle(pwd->pw_shell, cp, "-c", (char *)cmdbuf + 3, NULL, envinit); } else { (void) execle(pwd->pw_shell, cp, "-c", cmdbuf, NULL, envinit); } perror(pwd->pw_shell); exit(1); signout: if (ccache) (void) pam_close_session(pamh, 0); ccache = NULL; (void) pam_end(pamh, PAM_ABORT); exit(1); } static void getstr(fd, buf, cnt, err) int fd; char *buf; int cnt; char *err; { char c; do { if (read(fd, &c, 1) != 1) exit(1); if (cnt-- == 0) { error("%s too long\n", err); exit(1); } *buf++ = c; } while (c != 0); } /*PRINTFLIKE1*/ static void error(char *fmt, ...) { va_list ap; char buf[RSHD_BUFSIZ]; buf[0] = 1; va_start(ap, fmt); (void) vsnprintf(&buf[1], sizeof (buf) - 1, fmt, ap); va_end(ap); (void) write(STDERR_FILENO, buf, strlen(buf)); } static char *illegal[] = { "SHELL=", "HOME=", "LOGNAME=", #ifndef NO_MAIL "MAIL=", #endif "CDPATH=", "IFS=", "PATH=", "USER=", "TZ=", 0 }; /* * legalenvvar - can PAM modules insert this environmental variable? */ static int legalenvvar(char *s) { register char **p; for (p = illegal; *p; p++) if (strncmp(s, *p, strlen(*p)) == 0) return (0); if (s[0] == 'L' && s[1] == 'D' && s[2] == '_') return (0); return (1); } /* * Add a string to the environment of the new process. */ static void add_to_envinit(char *string) { /* * Reserve space for 2 * 8 = 16 environment entries initially which * should be enough to avoid reallocation of "envinit" in most cases. */ static int size = 8; static int index = 0; if (string == NULL) return; if ((envinit == NULL) || (index == size)) { size *= 2; envinit = realloc(envinit, (size + 1) * sizeof (char *)); if (envinit == NULL) { perror("malloc"); exit(1); } } envinit[index++] = string; envinit[index] = NULL; } /* * Check if lenv and penv matches or not. */ static int locale_envmatch(char *lenv, char *penv) { while ((*lenv == *penv) && (*lenv != '\0') && (*penv != '=')) { lenv++; penv++; } /* * '/' is eliminated for security reason. */ return ((*lenv == '\0' && *penv == '=' && *(penv + 1) != '/')); } #ifndef KRB_SENDAUTH_VLEN #define KRB_SENDAUTH_VLEN 8 /* length for version strings */ #endif /* MUST be KRB_SENDAUTH_VLEN chars */ #define KRB_SENDAUTH_VERS "AUTHV0.1" #define SIZEOF_INADDR sizeof (struct in_addr) static krb5_error_code recvauth(int netf, int *valid_checksum) { krb5_auth_context auth_context = NULL; krb5_error_code status; struct sockaddr_in laddr; int len; krb5_data inbuf; krb5_authenticator *authenticator; krb5_ticket *ticket; krb5_rcache rcache; krb5_data version; krb5_encrypt_block eblock; /* eblock for encrypt/decrypt */ krb5_data desinbuf; krb5_data desoutbuf; char des_inbuf[2 * RSHD_BUFSIZ]; /* needs to be > largest read size */ char des_outbuf[2 * RSHD_BUFSIZ + 4]; /* needs to be > largest write size */ *valid_checksum = 0; len = sizeof (laddr); if (getsockname(netf, (struct sockaddr *)&laddr, &len)) { exit(1); } if (status = krb5_auth_con_init(bsd_context, &auth_context)) return (status); if (status = krb5_auth_con_genaddrs(bsd_context, auth_context, netf, KRB5_AUTH_CONTEXT_GENERATE_REMOTE_FULL_ADDR)) return (status); status = krb5_auth_con_getrcache(bsd_context, auth_context, &rcache); if (status) return (status); if (!rcache) { krb5_principal server; status = krb5_sname_to_principal(bsd_context, 0, 0, KRB5_NT_SRV_HST, &server); if (status) return (status); status = krb5_get_server_rcache(bsd_context, krb5_princ_component(bsd_context, server, 0), &rcache); krb5_free_principal(bsd_context, server); if (status) return (status); status = krb5_auth_con_setrcache(bsd_context, auth_context, rcache); if (status) return (status); } status = krb5_recvauth_version(bsd_context, &auth_context, &netf, NULL, /* Specify daemon principal */ 0, /* no flags */ keytab, /* normally NULL to use v5srvtab */ &ticket, /* return ticket */ &version); /* application version string */ if (status) { getstr(netf, locuser, sizeof (locuser), "locuser"); getstr(netf, cmdbuf, sizeof (cmdbuf), "command"); getstr(netf, remuser, sizeof (locuser), "remuser"); return (status); } getstr(netf, locuser, sizeof (locuser), "locuser"); getstr(netf, cmdbuf, sizeof (cmdbuf), "command"); /* Must be V5 */ kcmd_protocol = KCMD_UNKNOWN_PROTOCOL; if (version.length != 9 || version.data == NULL) { syslog(LOG_ERR, "bad application version length"); error(gettext("bad application version length\n")); exit(1); } if (strncmp(version.data, "KCMDV0.1", 9) == 0) { kcmd_protocol = KCMD_OLD_PROTOCOL; } else if (strncmp(version.data, "KCMDV0.2", 9) == 0) { kcmd_protocol = KCMD_NEW_PROTOCOL; } else { syslog(LOG_ERR, "Unrecognized KCMD protocol (%s)", (char *)version.data); error(gettext("Unrecognized KCMD protocol (%s)"), (char *)version.data); exit(1); } getstr(netf, remuser, sizeof (locuser), "remuser"); if ((status = krb5_unparse_name(bsd_context, ticket->enc_part2->client, &kremuser))) return (status); if ((status = krb5_copy_principal(bsd_context, ticket->enc_part2->client, &client))) return (status); if (checksum_required && (kcmd_protocol == KCMD_OLD_PROTOCOL)) { if ((status = krb5_auth_con_getauthenticator(bsd_context, auth_context, &authenticator))) return (status); if (authenticator->checksum && checksum_required) { struct sockaddr_in adr; int adr_length = sizeof (adr); int chksumsize = strlen(cmdbuf) + strlen(locuser) + 32; krb5_data input; krb5_keyblock key; char *chksumbuf = (char *)malloc(chksumsize); if (chksumbuf == 0) goto error_cleanup; if (getsockname(netf, (struct sockaddr *)&adr, &adr_length) != 0) goto error_cleanup; (void) snprintf(chksumbuf, chksumsize, "%u:", ntohs(adr.sin_port)); if (strlcat(chksumbuf, cmdbuf, chksumsize) >= chksumsize) { syslog(LOG_ERR, "cmd buffer too long."); free(chksumbuf); return (-1); } if (strlcat(chksumbuf, locuser, chksumsize) >= chksumsize) { syslog(LOG_ERR, "locuser too long."); free(chksumbuf); return (-1); } input.data = chksumbuf; input.length = strlen(chksumbuf); key.magic = ticket->enc_part2->session->magic; key.enctype = ticket->enc_part2->session->enctype; key.contents = ticket->enc_part2->session->contents; key.length = ticket->enc_part2->session->length; status = krb5_c_verify_checksum(bsd_context, &key, 0, &input, authenticator->checksum, (unsigned int *)valid_checksum); if (status == 0 && *valid_checksum == 0) status = KRB5KRB_AP_ERR_BAD_INTEGRITY; error_cleanup: if (chksumbuf) krb5_xfree(chksumbuf); if (status) { krb5_free_authenticator(bsd_context, authenticator); return (status); } } krb5_free_authenticator(bsd_context, authenticator); } if ((strncmp(cmdbuf, "-x ", 3) == 0)) { if (krb5_privacy_allowed()) { do_encrypt = 1; } else { syslog(LOG_ERR, "rshd: Encryption not supported"); error("rshd: Encryption not supported. \n"); exit(2); } status = krb5_auth_con_getremotesubkey(bsd_context, auth_context, &sessionkey); if (status) { syslog(LOG_ERR, "Error getting KRB5 session subkey"); error(gettext("Error getting KRB5 session subkey")); exit(1); } /* * The "new" protocol requires that a subkey be sent. */ if (sessionkey == NULL && kcmd_protocol == KCMD_NEW_PROTOCOL) { syslog(LOG_ERR, "No KRB5 session subkey sent"); error(gettext("No KRB5 session subkey sent")); exit(1); } /* * The "old" protocol does not permit an authenticator subkey. * The key is taken from the ticket instead (see below). */ if (sessionkey != NULL && kcmd_protocol == KCMD_OLD_PROTOCOL) { syslog(LOG_ERR, "KRB5 session subkey not permitted " "with old KCMD protocol"); error(gettext("KRB5 session subkey not permitted " "with old KCMD protocol")); exit(1); } /* * If no key at this point, use the session key from * the ticket. */ if (sessionkey == NULL) { /* * Save the session key so we can configure the crypto * module later. */ status = krb5_copy_keyblock(bsd_context, ticket->enc_part2->session, &sessionkey); if (status) { syslog(LOG_ERR, "krb5_copy_keyblock failed"); error(gettext("krb5_copy_keyblock failed")); exit(1); } } /* * If session key still cannot be found, we must * exit because encryption is required here * when encr_flag (-x) is set. */ if (sessionkey == NULL) { syslog(LOG_ERR, "Could not find an encryption key"); error(gettext("Could not find an encryption key")); exit(1); } /* * Initialize parameters/buffers for desread & deswrite here. */ desinbuf.data = des_inbuf; desoutbuf.data = des_outbuf; desinbuf.length = sizeof (des_inbuf); desoutbuf.length = sizeof (des_outbuf); eblock.crypto_entry = sessionkey->enctype; eblock.key = (krb5_keyblock *)sessionkey; init_encrypt(do_encrypt, bsd_context, kcmd_protocol, &desinbuf, &desoutbuf, SERVER, &eblock); } ticket->enc_part2->session = 0; if ((status = krb5_read_message(bsd_context, (krb5_pointer) & netf, &inbuf))) { error(gettext("Error reading message: %s\n"), error_message(status)); exit(1); } if (inbuf.length) { krb5_creds **creds = NULL; /* Forwarding being done, read creds */ if ((status = krb5_rd_cred(bsd_context, auth_context, &inbuf, &creds, NULL))) { error("Can't get forwarded credentials: %s\n", error_message(status)); exit(1); } /* Store the forwarded creds in the ccache */ if ((status = store_forw_creds(bsd_context, creds, ticket, locuser, &ccache))) { error("Can't store forwarded credentials: %s\n", error_message(status)); exit(1); } krb5_free_creds(bsd_context, *creds); } krb5_free_ticket(bsd_context, ticket); return (0); } static void usage(void) { (void) fprintf(stderr, gettext("%s: rshd [-k5eciU] " "[-P path] [-M realm] [-s tos] " #ifdef DEBUG "[-D port] " #endif /* DEBUG */ "[-S keytab]"), gettext("usage")); syslog(LOG_ERR, "%s: rshd [-k5eciU] [-P path] [-M realm] [-s tos] " #ifdef DEBUG "[-D port] " #endif /* DEBUG */ "[-S keytab]", gettext("usage")); }