/* * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef SYSV #include #endif /* SYSV */ #ifndef NCARGS #define NCARGS 5120 #endif /* NCARGS */ #ifdef SYSV #define rindex strrchr #define killpg(a, b) kill(-(a), (b)) #else char *sprintf(); #endif /* SYSV */ #define MAXFD(A, B) ((A) > (B) ? (A) : (B)) #define _PATH_DEFAULT_LOGIN "/etc/default/login" static void error(char *fmt, ...); static void doit(int f, struct sockaddr_storage *fromp); static void getstr(char *buf, int cnt, char *err); static int legalenvvar(char *s); /* Function decls. for functions not in any header file. (Grrrr.) */ extern int audit_rexecd_setup(void); extern int audit_rexecd_success(char *, char *, char *); extern int audit_rexecd_fail(char *, char *, char *, char *); extern int audit_settid(int); /* set termnal ID */ /* PAM conversation function */ static int rexec_conv(int, struct pam_message **, struct pam_response **, void *); static pam_handle_t *pamh; /* authentication handle */ static struct pam_conv conv = { rexec_conv, NULL }; /* * remote execute server: * username\0 * password\0 * command\0 * data * * in.rexecd has been modified to run as the user invoking it. Hence there is no * need to limit any privileges. */ /*ARGSUSED*/ int main(int argc, char **argv) { struct sockaddr_storage from; socklen_t fromlen; openlog("rexec", LOG_PID | LOG_ODELAY, LOG_DAEMON); (void) audit_rexecd_setup(); /* BSM */ fromlen = (socklen_t)sizeof (from); if (getpeername(0, (struct sockaddr *)&from, &fromlen) < 0) { (void) fprintf(stderr, "%s: ", argv[0]); perror("getpeername"); exit(1); } if (audit_settid(0) != 0) { perror("settid"); exit(1); } doit(0, &from); return (0); } static char username[20] = "USER="; static char homedir[64] = "HOME="; static char shell[64] = "SHELL="; static char *envinit[] = #ifdef SYSV {homedir, shell, (char *)0, username, (char *)0, (char *)0, (char *)0, (char *)0, (char *)0, (char *)0, (char *)0, (char *)0, (char *)0, (char *)0, (char *)0, (char *)0, (char *)0, (char *)0, (char *)0, (char *)0, (char *)0}; #define ENVINIT_PATH 2 /* position of PATH in envinit[] */ #define PAM_ENV_ELIM 16 /* max PAM environment variables */ /* * See PSARC opinion 1992/025 */ static char userpath[] = "PATH=/usr/bin:"; static char rootpath[] = "PATH=/usr/sbin:/usr/bin"; #else {homedir, shell, "PATH=:/usr/ucb:/bin:/usr/bin", username, 0}; #endif /* SYSV */ static struct sockaddr_storage asin; static char pass[16]; static void doit(int f, struct sockaddr_storage *fromp) { char cmdbuf[NCARGS+1], *cp; char user[16]; char hostname [MAXHOSTNAMELEN + 1]; struct passwd *pwd, pw_data; char pwdbuf[NSS_BUFLEN_PASSWD]; int s; ushort_t port; pid_t pid; int pv[2], cc; fd_set readfrom, ready; char buf[BUFSIZ], sig; int one = 1; int idx = 0, end_env = 0; char **pam_env; int status = PAM_AUTH_ERR; char abuf[INET6_ADDRSTRLEN]; struct in_addr v4dst; socklen_t fromplen; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; int pam_flags = 0; (void) signal(SIGINT, SIG_DFL); (void) signal(SIGQUIT, SIG_DFL); (void) signal(SIGTERM, SIG_DFL); #ifdef DEBUG { int t = open("/dev/tty", 2); if (t >= 0) { #ifdef SYSV (void) setsid(); #else (void) ioctl(t, TIOCNOTTY, (char *)0); #endif /* SYSV */ (void) close(t); } } #endif if (fromp->ss_family == AF_INET) { sin = (struct sockaddr_in *)fromp; fromplen = sizeof (struct sockaddr_in); asin.ss_family = AF_INET; /* used for bind */ } else if (fromp->ss_family == AF_INET6) { sin6 = (struct sockaddr_in6 *)fromp; fromplen = sizeof (struct sockaddr_in6); asin.ss_family = AF_INET6; /* used for bind */ } else { syslog(LOG_ERR, "unknown address family %d\n", fromp->ss_family); exit(1); } /* * store common info. for audit record */ if (getnameinfo((const struct sockaddr *) fromp, fromplen, hostname, sizeof (hostname), NULL, 0, 0) != 0) { 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); inet_ntop(AF_INET, &ipv4_addr, abuf, sizeof (abuf)); } else { inet_ntop(AF_INET6, &sin6->sin6_addr, abuf, sizeof (abuf)); } } else if (fromp->ss_family == AF_INET) { inet_ntop(AF_INET, &sin->sin_addr, abuf, sizeof (abuf)); } (void) strncpy(hostname, abuf, sizeof (hostname)); } (void) dup2(f, 0); (void) dup2(f, 1); (void) dup2(f, 2); (void) alarm(60); port = 0; for (;;) { char c; if (read(f, &c, 1) != 1) exit(1); if (c == 0) break; port = port * 10 + c - '0'; } (void) alarm(0); if (port != 0) { s = socket(fromp->ss_family, SOCK_STREAM, 0); if (s < 0) exit(1); if (bind(s, (struct sockaddr *)&asin, fromplen) < 0) exit(1); (void) alarm(60); 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) exit(1); (void) alarm(0); } getstr(user, sizeof (user), "username"); getstr(pass, sizeof (pass), "password"); getstr(cmdbuf, sizeof (cmdbuf), "command"); pwd = getpwnam_r(user, &pw_data, pwdbuf, sizeof (pwdbuf)); if (pwd == NULL) { (void) audit_rexecd_fail("Login incorrect", hostname, user, cmdbuf); /* BSM */ error("Login incorrect.\n"); exit(1); } if (defopen(_PATH_DEFAULT_LOGIN) == 0) { int flags; char *p; flags = defcntl(DC_GETFLAGS, 0); TURNOFF(flags, DC_CASE); (void) defcntl(DC_SETFLAGS, flags); if ((p = defread("PASSREQ=")) != NULL && strcasecmp(p, "YES") == 0) { pam_flags |= PAM_DISALLOW_NULL_AUTHTOK; } defopen(NULL); } if (pam_start("rexec", user, &conv, &pamh) != PAM_SUCCESS) { exit(1); } if (pam_set_item(pamh, PAM_RHOST, hostname) != PAM_SUCCESS) { exit(1); } if ((status = pam_authenticate(pamh, pam_flags)) != PAM_SUCCESS) { switch (status) { case PAM_USER_UNKNOWN: (void) audit_rexecd_fail("Login incorrect", hostname, user, cmdbuf); /* BSM */ error("Login incorrect.\n"); break; default: (void) audit_rexecd_fail("Password incorrect", hostname, user, cmdbuf); /* BSM */ error("Password incorrect.\n"); } pam_end(pamh, status); exit(1); } if ((status = pam_acct_mgmt(pamh, pam_flags)) != PAM_SUCCESS) { (void) audit_rexecd_fail("Account or Password Expired", hostname, user, cmdbuf); switch (status) { case PAM_NEW_AUTHTOK_REQD: error("Password Expired.\n"); break; case PAM_PERM_DENIED: error("Account Expired.\n"); break; case PAM_AUTHTOK_EXPIRED: error("Password Expired.\n"); break; default: error("Login incorrect.\n"); break; } pam_end(pamh, status); exit(1); } (void) write(2, "\0", 1); if (setgid((gid_t)pwd->pw_gid) < 0) { (void) audit_rexecd_fail("Can't setgid", hostname, user, cmdbuf); /* BSM */ error("setgid"); pam_end(pamh, PAM_ABORT); exit(1); } (void) initgroups(pwd->pw_name, pwd->pw_gid); if ((status = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) { (void) audit_rexecd_fail("Unable to establish credentials", hostname, user, cmdbuf); /* BSM */ error("Unable to establish credentials.\n"); pam_end(pamh, PAM_SUCCESS); } (void) audit_rexecd_success(hostname, user, cmdbuf); /* BSM */ if (setuid((uid_t)pwd->pw_uid) < 0) { (void) audit_rexecd_fail("Can't setuid", hostname, user, cmdbuf); /* BSM */ error("setuid"); pam_end(pamh, PAM_ABORT); exit(1); } if (port) { (void) pipe(pv); pid = fork(); if (pid == (pid_t)-1) { error("Try again.\n"); pam_end(pamh, PAM_ABORT); exit(1); } if (pid) { /* * since the daemon is running as the user no need * to prune privileges. */ (void) close(0); (void) close(1); (void) close(2); (void) close(f); (void) close(pv[1]); FD_ZERO(&readfrom); FD_SET(s, &readfrom); FD_SET(pv[0], &readfrom); (void) ioctl(pv[0], FIONBIO, (char *)&one); /* should set s nbio! */ do { ready = readfrom; if (select(MAXFD(s, pv[0])+1, &ready, NULL, NULL, NULL) < 0) { perror("select:"); exit(1); } if (FD_ISSET(s, &ready)) { if (read(s, &sig, 1) <= 0) FD_CLR(s, &readfrom); else (void) killpg(pid, sig); } if (FD_ISSET(pv[0], &ready)) { cc = read(pv[0], buf, sizeof (buf)); if (cc <= 0) { (void) shutdown(s, 1+1); FD_CLR(pv[0], &readfrom); } else (void) write(s, buf, cc); } } while (FD_ISSET(s, &readfrom) || FD_ISSET(pv[0], &readfrom)); exit(0); } /* setpgrp(0, getpid()); */ (void) setsid(); /* Should be the same as above. */ (void) close(s); (void)close(pv[0]); (void) dup2(pv[1], 2); } if (*pwd->pw_shell == '\0') pwd->pw_shell = "/bin/sh"; if (f > 2) (void) close(f); /* Change directory only after becoming the appropriate user. */ if (chdir(pwd->pw_dir) < 0) { error("No remote directory.\n"); pam_end(pamh, PAM_ABORT); exit(1); } #ifdef SYSV if (pwd->pw_uid) envinit[ENVINIT_PATH] = userpath; else envinit[ENVINIT_PATH] = rootpath; #endif /* SYSV */ (void) strncat(homedir, pwd->pw_dir, sizeof (homedir) - 6); (void) strncat(shell, pwd->pw_shell, sizeof (shell) - 7); (void) strncat(username, pwd->pw_name, sizeof (username) - 6); /* * add PAM environment variables set by modules * -- only allowed 16 (PAM_ENV_ELIM) * -- check to see if the environment variable is legal */ for (end_env = 0; envinit[end_env] != 0; end_env++) ; if ((pam_env = pam_getenvlist(pamh)) != 0) { while (pam_env[idx] != 0) { if (idx < PAM_ENV_ELIM && legalenvvar(pam_env[idx])) { envinit[end_env + idx] = pam_env[idx]; } idx++; } } pam_end(pamh, PAM_SUCCESS); cp = rindex(pwd->pw_shell, '/'); if (cp) cp++; else cp = pwd->pw_shell; (void) execle(pwd->pw_shell, cp, "-c", cmdbuf, (char *)0, envinit); perror(pwd->pw_shell); exit(1); } static void getstr(char *buf, int cnt, char *err) { char c; do { if (read(0, &c, 1) != 1) exit(1); *buf++ = c; if (--cnt == 0) { error("%s too long\n", err); exit(1); } } while (c != 0); } static void error(char *fmt, ...) { va_list ap; char buf[BUFSIZ]; buf[0] = 1; va_start(ap, fmt); (void) vsprintf(buf+1, fmt, ap); va_end(ap); (void) write(2, buf, strlen(buf)); } static char *illegal[] = { "SHELL=", "HOME=", "LOGNAME=", #ifndef NO_MAIL "MAIL=", #endif "CDPATH=", "IFS=", "PATH=", "USER=", 0 }; /* * legalenvvar - can PAM 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); } /* * rexec_conv - This is the conv (conversation) function called from * a PAM authentication module to print error messages * or garner information from the user. */ /* ARGSUSED3 */ static int rexec_conv(int num_msg, struct pam_message **msg, struct pam_response **response, void *appdata_ptr) { struct pam_message *m; struct pam_response *r; int i; if (num_msg <= 0) return (PAM_CONV_ERR); *response = calloc(num_msg, sizeof (struct pam_response)); if (*response == NULL) return (PAM_BUF_ERR); m = *msg; r = *response; if (m->msg_style == PAM_PROMPT_ECHO_OFF) { if (pass[0] != '\0') { r->resp = strdup(pass); if (r->resp == NULL) { /* free responses */ r = *response; for (i = 0; i < num_msg; i++, r++) { if (r->resp) free(r->resp); } free(*response); *response = NULL; return (PAM_BUF_ERR); } } } return (PAM_SUCCESS); }