/
niceSOFT
/
inetutils
Обзор
Документация
Войти
/
niceSOFT
/
inetutils
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/rshd.c
2 151 строка
55 KB
Simon Josefsson
maint: Run 'make update-copyright'
20 янв 2026, 11:50
Не верифицирован
20 янв 2026, 11:50
1ce61cd
Код
Авторство
О чём код?
/* Copyright (C) 1994-2026 Free Software Foundation, Inc. This file is part of GNU Inetutils. GNU Inetutils is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNU Inetutils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see `http://www.gnu.org/licenses/'. */ /* * Copyright (c) 1988, 1989, 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * PAM implementation by Mats Erik Andersson. * * TODO: Check cooperation between PAM and Shishi/Kerberos. * * Sample settings: * * GNU/Linux, et cetera: * * auth required pam_nologin.so * auth required pam_rhosts.so * auth required pam_env.so * auth required pam_group.so * account required pam_nologin.so * account required pam_unix.so * session required pam_unix.so * session required pam_lastlog.so silent * password required pam_deny.so * * OpenSolaris: * * auth required pam_rhosts_auth.so * auth required pam_unix_cred.so * account required pam_roles.so * account required pam_unix_account.so * session required pam_unix_session.so * password required pam_deny.so * * BSD: * * auth required pam_nologin.so # NetBSD * auth required pam_rhosts.so * account required pam_nologin.so # FreeBSD * account required pam_unix.so * session required pam_lastlog.so * password required pam_deny.so */ /* * remote shell server exchange protocol (server view!): * [port]\0 * remuser\0 * locuser\0 * command\0 * data * * Kerberized exchange delays the remote user name: * * \0 * locuser\0 * command\0 * remuser\0 * data */ #include <config.h> #include <alloca.h> #include <sys/param.h> #include <sys/ioctl.h> #include <sys/time.h> #include <time.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #ifdef HAVE_NETINET_IN_SYSTM_H # include <netinet/in_systm.h> /* n_time */ #endif #ifdef HAVE_NETINET_IP_H # include <netinet/ip.h> /* IPOPT_* */ #endif #include <unistd.h> #include <errno.h> #include <fcntl.h> #ifdef HAVE_SYS_FILIO_H # include <sys/filio.h> #endif #include <pwd.h> #ifdef HAVE_SHADOW_H # include <shadow.h> #endif #include <signal.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <syslog.h> #include <unistd.h> #include <grp.h> #include <sys/select.h> #include <sys/wait.h> #include <error.h> #include <progname.h> #include <argp.h> #include <attribute.h> #include <libinetutils.h> #include "xalloc.h" #ifdef HAVE_SECURITY_PAM_APPL_H # include <security/pam_appl.h> #endif #ifdef KRB4 # ifdef HAVE_KERBEROSIV_DES_H # include <kerberosIV/des.h> # endif # ifdef HAVE_KERBEROSIV_KRB_H # include <kerberosIV/krb.h> # endif #elif defined KRB5 /* !KRB4 */ # ifdef HAVE_KRB5_H # include <krb5.h> # endif # ifdef HAVE_COM_ERR_H # include <com_err.h> # endif #endif /* KRB4 || KRB5 */ #ifdef SHISHI # include <shishi.h> # include <shishi_def.h> #endif #ifndef MAX # define MAX(a,b) (((a) > (b)) ? (a) : (b)) #endif #ifndef DAY # define DAY (24 * 60 * 60) #endif int keepalive = 1; /* flag for SO_KEEPALIVE socket option */ int check_all; int log_success; /* If TRUE, log all successful accesses */ int reverse_required = 0; /* Demand IP to host name resolution. */ int sent_null; void doit (int, struct sockaddr *, socklen_t); void rshd_error (const char *, ...); char *getstr (const char *); int local_domain (const char *); const char *topdomain (const char *); #ifdef WITH_PAM static int pam_rc = PAM_AUTH_ERR; static pam_handle_t *pam_handle = NULL; static int rsh_conv (int, const struct pam_message **, struct pam_response **, void *); static struct pam_conv pam_conv = { rsh_conv, NULL }; #endif /* WITH_PAM */ #if defined KERBEROS || defined SHISHI # ifdef KRB4 Key_schedule schedule; char authbuf[sizeof (AUTH_DAT)]; char tickbuf[sizeof (KTEXT_ST)]; # elif defined KRB5 # elif defined(SHISHI) Shishi *h; Shishi_ap *ap; Shishi_key *enckey; shishi_ivector iv1, iv2, iv3, iv4; shishi_ivector *ivtab[4]; int protocol, uses_encryption = 0; # endif/* SHISHI */ # define VERSION_SIZE 9 # define SECURE_MESSAGE "This rsh session is using DES encryption for all transmissions.\r\n" int doencrypt, use_kerberos, vacuous; char *servername = NULL; #else #endif /* KERBEROS || SHISHI */ static struct argp_option options[] = { #define GRP 10 {"reverse-required", 'r', NULL, 0, "require reverse resolving of remote host IP", GRP}, {"verify-hostname", 'a', NULL, 0, "ask hostname for verification", GRP}, #ifdef HAVE___CHECK_RHOSTS_FILE {"no-rhosts", 'l', NULL, 0, "ignore .rhosts file", GRP}, #endif {"no-keepalive", 'n', NULL, 0, "do not set SO_KEEPALIVE", GRP}, {"log-sessions", 'L', NULL, 0, "log successful logins", GRP}, #undef GRP #if defined KERBEROS || defined SHISHI # define GRP 20 /* FIXME: The option semantics does not match that of other r* utilities */ {"kerberos", 'k', NULL, 0, "use kerberos authentication", GRP}, /* FIXME: Option name is misleading */ {"vacuous", 'v', NULL, 0, "fail for non-Kerberos authentication", GRP}, {"server-principal", 'S', "NAME", 0, "set Kerberos server name, overriding canonical hostname", GRP}, # if defined ENCRYPTION {"encrypt", 'x', NULL, 0, "fail for non-encrypted, Kerberized sessions", GRP}, # endif # undef GRP #endif /* KERBEROS || SHISHI */ {NULL, 0, NULL, 0, NULL, 0} }; #ifdef HAVE___CHECK_RHOSTS_FILE extern int __check_rhosts_file; /* hook in rcmd(3) */ #endif #ifndef WITH_PAM # if defined __GLIBC__ && defined WITH_IRUSEROK extern int iruserok (uint32_t raddr, int superuser, const char *ruser, const char *luser); # endif #endif /* WITH_PAM */ static error_t parse_opt (int key, char *arg, struct argp_state *state MAYBE_UNUSED) { switch (key) { case 'a': check_all = 1; break; #ifdef HAVE___CHECK_RHOSTS_FILE case 'l': __check_rhosts_file = 0; /* don't check .rhosts file */ break; #endif case 'n': keepalive = 0; /* don't enable SO_KEEPALIVE */ break; case 'r': reverse_required = 1; break; #if defined KERBEROS || defined SHISHI case 'k': use_kerberos = 1; break; case 'v': vacuous = 1; break; # ifdef ENCRYPTION case 'x': doencrypt = 1; break; # endif case 'S': servername = arg; break; #endif /* KERBEROS || SHISHI */ case 'L': log_success = 1; break; default: return ARGP_ERR_UNKNOWN; } return 0; } const char doc[] = #ifdef WITH_PAM # if defined KERBEROS || defined SHISHI "Remote shell server, using PAM services 'rsh' and 'krsh'."; # else "Remote shell server, using PAM service 'rsh'."; # endif #else /* !WITH_PAM */ "Remote shell server."; #endif static struct argp argp = { options, parse_opt, NULL, doc, NULL, NULL, NULL }; /* Remote shell server. We're invoked by the rcmd(3) function. */ int main (int argc, char *argv[]) { int index; struct linger linger; int on = 1; socklen_t fromlen; struct sockaddr_storage from; int sockfd; set_program_name (argv[0]); iu_argp_init ("rshd", default_program_authors); argp_parse (&argp, argc, argv, 0, &index, NULL); openlog ("rshd", LOG_PID | LOG_ODELAY, LOG_DAEMON); argc -= index; if (argc > 0) { syslog (LOG_ERR, "%d extra arguments", argc); exit (EXIT_FAILURE); } #if defined KERBEROS || defined SHISHI if (use_kerberos && vacuous) { syslog (LOG_ERR, "only one of -k and -v allowed"); exit (EXIT_FAILURE); } # ifdef ENCRYPTION if (doencrypt && !use_kerberos) { syslog (LOG_ERR, "-k is required for -x"); exit (EXIT_FAILURE); } # endif/* ENCRYPTION */ #endif /* KERBEROS || SHISHI */ /* * We assume we're invoked by inetd, so the socket that the * connection is on, is open on descriptors 0, 1 and 2. * STD{IN,OUT,ERR}_FILENO. * We may in the future make it standalone for certain platform. */ sockfd = STDIN_FILENO; /* * First get the Internet address of the client process. * This is requored for all the authentication we perform. */ fromlen = sizeof from; if (getpeername (sockfd, (struct sockaddr *) &from, &fromlen) < 0) { syslog (LOG_ERR, "getpeername: %m"); _exit (EXIT_FAILURE); } /* Set the socket options: SO_KEEPALIVE and SO_LINGER */ if (keepalive && setsockopt (sockfd, 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 (sockfd, SOL_SOCKET, SO_LINGER, (char *) &linger, sizeof linger) < 0) syslog (LOG_WARNING, "setsockopt (SO_LINGER): %m"); doit (sockfd, (struct sockaddr *) &from, fromlen); return 0; } char username[32 + sizeof ("USER=")] = "USER="; char logname[32 + sizeof ("LOGNAME=")] = "LOGNAME="; char homedir[256 + sizeof ("HOME=")] = "HOME="; char shell[64 + sizeof ("SHELL=")] = "SHELL="; char path[sizeof (PATH_DEFPATH) + sizeof ("PATH=") + 1] = "PATH="; char rhost[128 + sizeof ("RHOST=")] = "RHOST="; #ifndef WITH_PAM char *envinit[] = { homedir, shell, path, logname, username, rhost, NULL }; #endif void doit (int sockfd, struct sockaddr *fromp, socklen_t fromlen) { #ifdef HAVE___RCMD_ERRSTR extern char *__rcmd_errstr; /* syslog hook from libc/net/rcmd.c. */ #endif #ifdef HAVE_GETPWNAM_R char *pwbuf; int ret, pwbuflen; struct passwd *pwd, pwstor; #else /* !HAVE_GETPWNAM_R */ struct passwd *pwd; #endif unsigned short port, inport; fd_set ready, readfrom; int cc, nfd, pv[2], pid, s = sockfd; int rc, one = 1; char portstr[8], addrstr[INET6_ADDRSTRLEN]; #if HAVE_DECL_GETNAMEINFO char addrname[NI_MAXHOST]; #else /* !HAVE_DECL_GETNAMEINFO */ struct hostent *hp; #endif const char *hostname, *errorstr, *errorhost = NULL; char *cp, sig, buf[BUFSIZ]; char *cmdbuf, *locuser, *remuser; char *rprincipal = NULL; #if defined WITH_IRUSEROK_AF && !defined WITH_PAM void *fromaddrp; /* Pointer to remote address. */ #endif #ifdef WITH_PAM char *service; #endif #ifdef KERBEROS # ifdef KRB4 AUTH_DAT *kdata = (AUTH_DAT *) NULL; KTEXT ticket = (KTEXT) NULL; char instance[INST_SZ], version[VERSION_SIZE]; # elif defined KRB5 /* !KRB4 */ krb5_context context; krb5_auth_context auth_ctx; krb5_authenticator *author; krb5_principal client; krb5_rcache rcache; krb5_keytab keytab; krb5_ticket *ticket; # endif/* KRB4 || KRB5 */ struct sockaddr_in fromaddr; long authopts; int pv1[2], pv2[2]; fd_set wready, writeto; #elif defined SHISHI /* !KERBEROS */ int n; int pv1[2], pv2[2]; fd_set wready, writeto; int keytype, keylen; int cksumtype; size_t cksumlen; char *cksum = NULL; #endif /* KERBEROS || SHISHI */ #ifdef KERBEROS memcpy (&fromaddr, fromp, sizeof (fromaddr)); #endif #ifdef HAVE_GETPWNAM_R pwbuflen = sysconf (_SC_GETPW_R_SIZE_MAX); if (pwbuflen <= 0) pwbuflen = 1024; /* Guessing only. */ pwbuf = xmalloc (pwbuflen); #endif /* HAVE_GETPWNAM_R */ signal (SIGINT, SIG_DFL); signal (SIGQUIT, SIG_DFL); signal (SIGTERM, SIG_DFL); #ifdef DEBUG { int t = open (PATH_TTY, O_RDWR); if (t >= 0) { ioctl (t, TIOCNOTTY, (char *) 0); close (t); } } #endif #if HAVE_DECL_GETNAMEINFO rc = getnameinfo (fromp, fromlen, addrstr, sizeof (addrstr), portstr, sizeof (portstr), NI_NUMERICHOST | NI_NUMERICSERV); if (rc != 0) { syslog (LOG_WARNING, "getnameinfo: %s", gai_strerror (rc)); exit (EXIT_FAILURE); } inport = atoi (portstr); #else /* !HAVE_DECL_GETNAMEINFO */ strncpy (addrstr, inet_ntoa (((struct sockaddr_in *) fromp)->sin_addr), sizeof (addrstr)); inport = ntohs (((struct sockaddr_in *) fromp)->sin_port); snprintf (portstr, sizeof (portstr), "%u", inport); #endif /* Verify that the client's address is an Internet address. */ #ifdef KERBEROS if (fromp->sa_family != AF_INET) { syslog (LOG_ERR, "malformed originating address (af %d)\n", fromp->sa_family); exit (EXIT_FAILURE); } #endif /* KERBEROS */ #ifdef IP_OPTIONS { unsigned char optbuf[BUFSIZ / 3], *cp; char lbuf[BUFSIZ], *lp; socklen_t optsize = sizeof (optbuf); int ipproto; struct protoent *ip; ip = getprotobyname ("ip"); if (ip != NULL) ipproto = ip->p_proto; else ipproto = IPPROTO_IP; if (!getsockopt (sockfd, ipproto, IP_OPTIONS, (char *) optbuf, &optsize) && optsize != 0) { lp = lbuf; /* The client has set IP options. This isn't allowed. * Use syslog() to record the fact. Only the option * types are printed, not their contents. */ for (cp = optbuf; optsize > 0;) { sprintf (lp, " %2.2x", *cp); lp += 3; if (*cp == IPOPT_SSRR || *cp == IPOPT_LSRR) { /* Already the TCP handshake suffices for * a man-in-the-middle attack vector. */ syslog (LOG_NOTICE, "Discarding connection from %s with set source routing", addrstr); exit (EXIT_FAILURE); } if (*cp == IPOPT_EOL) break; if (*cp == IPOPT_NOP) cp++, optsize--; else { /* Options using a length octet, see RFC 791. */ int inc = cp[1]; optsize -= inc; cp += inc; } } /* At this point presumably harmless options are present. * Make a report about them, erase them, and continue. */ syslog (LOG_NOTICE, "Connection received from %s using IP options (erased):%s", addrstr, lbuf); /* Turn off the options. If this doesn't work, we quit. */ if (setsockopt (sockfd, ipproto, IP_OPTIONS, (char *) NULL, optsize) != 0) { syslog (LOG_ERR, "setsockopt IP_OPTIONS NULL: %m"); exit (EXIT_FAILURE); } } } #endif /* Verify that the client's address was bound to a reserved port */ #if defined KERBEROS || defined SHISHI if (!use_kerberos) #endif if (inport >= IPPORT_RESERVED || inport < IPPORT_RESERVED / 2) { syslog (LOG_NOTICE | LOG_AUTH, "Connection from %s on illegal port %s", addrstr, portstr); exit (EXIT_FAILURE); } /* Read the ASCII string specifying the secondary port# from * the socket. We set a timer of 60 seconds to do this read, * else we assume something is wrong. If the client doesn't want * the secondary port, they just send the terminating null byte. */ alarm (60); port = 0; for (;;) { char c; cc = read (sockfd, &c, 1); if (cc != 1) { if (cc < 0) syslog (LOG_NOTICE, "read: %m"); shutdown (sockfd, SHUT_RDWR); exit (EXIT_FAILURE); } /* null byte terminates the string */ if (c == 0) break; port = port * 10 + c - '0'; } alarm (0); if (port != 0) { /* If the secondary port# is non-zero, then we have to * connect to that port (which the client has already * created and is listening on). The secondary port# * that the client tells us to connect to has also to be * a reserved port#. Also, our end of this secondary * connection has also to have a reserved TCP port bound * to it, plus. */ int lport = IPPORT_RESERVED - 1; #ifdef WITH_RRESVPORT_AF s = rresvport_af (&lport, fromp->sa_family); #else s = rresvport (&lport); #endif if (s < 0) { syslog (LOG_ERR, "can't get stderr port: %m"); exit (EXIT_FAILURE); } #if defined KERBEROS || defined SHISHI if (!use_kerberos) #endif if (port >= IPPORT_RESERVED || port < IPPORT_RESERVED / 2) { syslog (LOG_ERR, "Second port outside reserved range."); exit (EXIT_FAILURE); } /* Use the fromp structure that we already have available. * The 32-bit Internet address is obviously that of the * client; just change the port# to the one specified * as secondary port by the client. */ switch (fromp->sa_family) { case AF_INET6: ((struct sockaddr_in6 *) fromp)->sin6_port = htons (port); break; case AF_INET: default: ((struct sockaddr_in *) fromp)->sin_port = htons (port); } if (connect (s, fromp, fromlen) < 0) { syslog (LOG_INFO, "connect second port %d: %m", port); exit (EXIT_FAILURE); } } #if defined KERBEROS || defined SHISHI if (vacuous) { rshd_error ("rshd: remote host requires Kerberos authentication\n"); exit (EXIT_FAILURE); } #endif /* KERBEROS || SHISHI */ /* from inetd, socket is already on 0, 1, 2 */ if (sockfd != STDIN_FILENO) { dup2 (sockfd, STDIN_FILENO); dup2 (sockfd, STDOUT_FILENO); dup2 (sockfd, STDERR_FILENO); } /* Get the "name" of the client from its Internet address. This is * used for the authentication below. */ errorstr = NULL; #if HAVE_DECL_GETNAMEINFO rc = getnameinfo (fromp, fromlen, addrname, sizeof (addrname), NULL, 0, NI_NAMEREQD); if (rc == 0) { hostname = addrname; # if defined KERBEROS || defined SHISHI if (!use_kerberos) # endif if (check_all || local_domain (addrname)) { struct addrinfo hints, *ai, *res; errorhost = addrname; memset (&hints, 0, sizeof (hints)); hints.ai_family = fromp->sa_family; hints.ai_socktype = SOCK_STREAM; rc = getaddrinfo (hostname, NULL, &hints, &res); if (rc != 0) { syslog (LOG_INFO, "Could not resolve address for %s.", hostname); errorstr = "Could not resolve address for your host (%s).\n"; hostname = addrstr; } else { for (ai = res; ai; ai = ai->ai_next) { char astr[INET6_ADDRSTRLEN] = ""; if (getnameinfo (ai->ai_addr, ai->ai_addrlen, astr, sizeof (astr), NULL, 0, NI_NUMERICHOST)) continue; if (!strcmp (addrstr, astr)) { hostname = addrname; break; /* equal, OK */ } } freeaddrinfo (res); if (ai == NULL) { syslog (LOG_NOTICE, "Host addr %s not listed for host %s.", addrstr, hostname); errorstr = "Host address mismatch for %s.\n"; hostname = addrstr; } } } } #else /* !HAVE_DECL_GETNAMEINFO */ switch (fromp->sa_family) { case AF_INET6: hp = gethostbyaddr ((void *) &((struct sockaddr_in6 *) fromp)->sin6_addr, sizeof (struct in6_addr), fromp->sa_family); break; case AF_INET: default: hp = gethostbyaddr ((void *) &((struct sockaddr_in *) fromp)->sin_addr, sizeof (struct in_addr), fromp->sa_family); } if (hp) { /* * If name returned by gethostbyaddr is in our domain, * attempt to verify that we haven't been fooled by someone * in a remote net; look up the name and check that this * address corresponds to the name. */ hostname = strdup (hp->h_name); # if defined KERBEROS || defined SHISHI if (!use_kerberos) # endif if (check_all || local_domain (hp->h_name)) { char *remotehost = alloca (strlen (hostname) + 1); if (!remotehost) errorstr = "Out of memory.\n"; else { strcpy (remotehost, hostname); errorhost = remotehost; hp = gethostbyname (remotehost); if (hp == NULL) { syslog (LOG_INFO, "Couldn't look up address for %s.", remotehost); errorstr = "Couldn't look up address for your host (%s).\n"; hostname = addrstr; } else for (;; hp->h_addr_list++) { if (hp->h_addr_list[0] == NULL) { syslog (LOG_NOTICE, "Host addr %s not listed for host %s.", addrstr, hp->h_name); errorstr = "Host address mismatch for %s.\n"; hostname = addrstr; break; } if (!memcmp (hp->h_addr_list[0], (fromp->sa_family == AF_INET6) ? (void *) &((struct sockaddr_in6 *) fromp)->sin6_addr : (void *) &((struct sockaddr_in *) fromp)->sin_addr, hp->h_length)) { hostname = strdup (hp->h_name); break; /* equal, OK */ } } } } } #endif /* !HAVE_DECL_GETNAMEINFO */ else if (reverse_required) { syslog (LOG_NOTICE, "Could not resolve remote %s.", addrstr); rshd_error ("Permission denied.\n"); exit (EXIT_FAILURE); } else errorhost = hostname = addrstr; #ifdef KRB4 if (use_kerberos) { kdata = (AUTH_DAT *) authbuf; ticket = (KTEXT) tickbuf; authopts = 0L; strcpy (instance, "*"); version[VERSION_SIZE - 1] = '\0'; # ifdef ENCRYPTION if (doencrypt) { struct sockaddr_in local_addr; rc = sizeof local_addr; if (getsockname (STDIN_FILENO, (struct sockaddr *) &local_addr, &rc) < 0) { syslog (LOG_ERR, "getsockname: %m"); rshd_error ("rshd: getsockname: %s", strerror (errno)); exit (EXIT_FAILURE); } authopts = KOPT_DO_MUTUAL; rc = krb_recvauth (authopts, 0, ticket, "rcmd", instance, &fromaddr, &local_addr, kdata, "", schedule, version); des_set_key (kdata->session, schedule); } else # endif/* ENCRYPTION */ rc = krb_recvauth (authopts, 0, ticket, "rcmd", instance, &fromaddr, (struct sockaddr_in *) 0, kdata, "", (bit_64 *) 0, version); if (rc != KSUCCESS) { rshd_error ("Kerberos authentication failure: %s\n", krb_err_txt[rc]); exit (EXIT_FAILURE); } } else #elif defined KRB5 if (use_kerberos) { krb5_principal server; /* Set up context data. */ rc = krb5_init_context (&context); if (!rc && servername && *servername) { rc = krb5_parse_name (context, servername, &server); /* A realm name missing in `servername' has been augmented * by krb5_parse_name(), so setting it again is harmless. */ if (!rc) { rc = krb5_set_default_realm (context, krb5_princ_realm (context, server)->data); krb5_free_principal (context, server); } } if (!rc) rc = krb5_auth_con_init (context, &auth_ctx); if (!rc) rc = krb5_auth_con_genaddrs (context, auth_ctx, sockfd, KRB5_AUTH_CONTEXT_GENERATE_REMOTE_FULL_ADDR); if (!rc) rc = krb5_auth_con_getrcache (context, auth_ctx, &rcache); if (!rc && !rcache) { rc = krb5_sname_to_principal (context, 0, 0, KRB5_NT_SRV_HST, &server); if (!rc) { krb5_data *pdata; pdata = krb5_princ_component (context, server, 0); rc = krb5_get_server_rcache (context, pdata, &rcache); krb5_free_principal (context, server); if (!rc) rc = krb5_auth_con_setrcache (context, auth_ctx, rcache); } } if (rc) { syslog (LOG_ERR, "Error initializing krb5: %s", error_message (rc)); rshd_error ("Permission denied.\n"); exit (EXIT_FAILURE); } # ifdef ENCRYPTION if (doencrypt) { struct sockaddr_in local_addr; rc = sizeof local_addr; if (getsockname (STDIN_FILENO, (struct sockaddr *) &local_addr, &rc) < 0) { syslog (LOG_ERR, "getsockname: %m"); rshd_error ("rshd: getsockname: %s", strerror (errno)); exit (EXIT_FAILURE); } authopts = KOPT_DO_MUTUAL; rc = krb_recvauth (authopts, 0, ticket, "rcmd", instance, &fromaddr, &local_addr, kdata, "", schedule, version); des_set_key (kdata->session, schedule); } else # endif/* ENCRYPTION */ rc = krb5_recvauth (context, &auth_ctx, &sockfd, "rcmd", 0, 0, keytab, &ticket); if (!rc) rc = krb5_auth_con_getauthenticator (context, auth_ctx, &author); if (!rc) { rshd_error ("Kerberos authentication failure: %s\n", error_message (rc)); exit (EXIT_FAILURE); } } else #elif defined (SHISHI) /* !KRB4 && !KRB5 */ if (use_kerberos) { int rc; const char *err_msg; rc = get_auth (STDIN_FILENO, &h, &ap, &enckey, &err_msg, &protocol, &cksumtype, &cksum, &cksumlen, servername); if (rc != SHISHI_OK) { rshd_error ("Kerberos authentication failure: %s\n", (err_msg && *err_msg) ? err_msg : shishi_strerror (rc)); exit (EXIT_FAILURE); } } else #endif /* KERBEROS || SHISHI */ remuser = getstr ("remuser"); /* The requesting user! */ /* Read three strings from the client. */ locuser = getstr ("locuser"); /* The acting user! */ cmdbuf = getstr ("command"); #ifdef SHISHI if (use_kerberos) { int error; int rc; char *compcksum; size_t compcksumlen; char cksumdata[100]; struct sockaddr_storage sock; socklen_t socklen; if (strlen (cmdbuf) >= 3) if (!strncmp (cmdbuf, "-x ", 3)) # ifdef ENCRYPTION { int i; uses_encryption = 1; ivtab[0] = &iv1; ivtab[1] = &iv2; ivtab[2] = &iv3; ivtab[3] = &iv4; keytype = shishi_key_type (enckey); keylen = shishi_cipher_blocksize (keytype); for (i = 0; i < 4; i++) { ivtab[i]->ivlen = keylen; switch (keytype) { case SHISHI_DES_CBC_CRC: case SHISHI_DES_CBC_MD4: case SHISHI_DES_CBC_MD5: case SHISHI_DES_CBC_NONE: case SHISHI_DES3_CBC_HMAC_SHA1_KD: ivtab[i]->keyusage = SHISHI_KEYUSAGE_KCMD_DES; ivtab[i]->iv = xmalloc (ivtab[i]->ivlen); memset (ivtab[i]->iv, 2 * i - 3 * (i >= 2), ivtab[i]->ivlen); ivtab[i]->ctx = shishi_crypto (h, enckey, ivtab[i]->keyusage, shishi_key_type (enckey), ivtab[i]->iv, ivtab[i]->ivlen); break; case SHISHI_ARCFOUR_HMAC: case SHISHI_ARCFOUR_HMAC_EXP: ivtab[i]->keyusage = SHISHI_KEYUSAGE_KCMD_DES + 4 * (i < 2) + 2 + 2 * (i % 2); ivtab[i]->ctx = shishi_crypto (h, enckey, ivtab[i]->keyusage, shishi_key_type (enckey), NULL, 0); break; default: ivtab[i]->keyusage = SHISHI_KEYUSAGE_KCMD_DES + 4 * (i < 2) + 2 + 2 * (i % 2); ivtab[i]->iv = xmalloc (ivtab[i]->ivlen); memset (ivtab[i]->iv, 0, ivtab[i]->ivlen); if (protocol == 2) ivtab[i]->ctx = shishi_crypto (h, enckey, ivtab[i]->keyusage, shishi_key_type (enckey), ivtab[i]->iv, ivtab[i]->ivlen); } } } # else/* !ENCRYPTION */ { shishi_ap_done (ap); rshd_error ("Encrypted sessions are not supported.\n"); exit (EXIT_FAILURE); } # endif/* ENCRYPTION */ remuser = getstr ("remuser"); /* The requesting user! */ rc = read (STDIN_FILENO, &error, sizeof (int)); /* XXX: not protocol */ if ((rc != sizeof (int)) || error) exit (EXIT_FAILURE); /* verify checksum */ { unsigned short pport; socklen = sizeof (sock); if (getsockname (STDIN_FILENO, (struct sockaddr *) &sock, &socklen) < 0) { syslog (LOG_ERR, "Can't get sock name"); exit (EXIT_FAILURE); } pport = (sock.ss_family == AF_INET6) ? ((struct sockaddr_in6 *) &sock)->sin6_port : ((struct sockaddr_in *) &sock)->sin_port; snprintf (cksumdata, 100, "%u:%s%s", ntohs (pport), cmdbuf, locuser); } rc = shishi_checksum (h, enckey, 0, cksumtype, cksumdata, strlen (cksumdata), &compcksum, &compcksumlen); if (rc != SHISHI_OK || compcksumlen != cksumlen || memcmp (compcksum, cksum, cksumlen) != 0) { /* err_msg crash ? */ /* *err_msg = "checksum verify failed"; */ syslog (LOG_ERR, "checksum verify failed: %s", shishi_error (h)); free (compcksum); shishi_ap_done (ap); rshd_error ("Authentication exchange failed.\n"); exit (EXIT_FAILURE); } if (doencrypt && !uses_encryption) { syslog (LOG_INFO, "non-encrypted session denied from %s", hostname); free (compcksum); shishi_ap_done (ap); rshd_error ("Only encrypted sessions are allowed.\n"); exit (EXIT_FAILURE); } else doencrypt = uses_encryption; rc = shishi_authorized_p (h, shishi_ap_tkt (ap), locuser); if (!rc) { syslog (LOG_AUTH | LOG_ERR, "User %s@%s is not authorized to run as: %s.", remuser, hostname, locuser); shishi_ap_done (ap); rshd_error ("Failed to get authorized as `%s'.\n", locuser); exit (EXIT_FAILURE); } free (compcksum); rc = shishi_encticketpart_clientrealm (h, shishi_tkt_encticketpart (shishi_ap_tkt (ap)), &rprincipal, NULL); if (rc != SHISHI_OK) rprincipal = NULL; shishi_ap_done (ap); } #elif defined KRB5 /* !SHISHI */ if (use_kerberos) { remuser = getstr ("remuser"); /* The requesting user! */ rc = krb5_copy_principal (context, ticket->enc_part2->client, &client); if (rc) goto fail; /* FIXME: Temporary handler. */ if (client && !krb5_kuserok (context, client, locuser)) goto fail; /* FIXME: Temporary handler. */ rprincipal = NULL; krb5_unparse_name (context, client, &rprincipal); } #endif /* KRB5 || SHISHI */ /* Look up locuser in the passwd file. The locuser has to be a * valid account on this system. */ setpwent (); #ifdef HAVE_GETPWNAM_R ret = getpwnam_r (locuser, &pwstor, pwbuf, pwbuflen, &pwd); if (ret || pwd == NULL) #else /* !HAVE_GETPWNAM_R */ pwd = getpwnam (locuser); if (pwd == NULL) #endif /* HAVE_GETPWNAM_R */ { syslog (LOG_INFO | LOG_AUTH, "%s@%s as %s: unknown login. cmd='%.80s'", remuser, hostname, locuser, cmdbuf); if (errorstr == NULL) errorstr = "Login incorrect.\n"; goto fail; } #ifdef WITH_PAM # if defined KERBEROS || defined SHISHI if (use_kerberos) service = "krsh"; else # endif service = "rsh"; pam_rc = pam_start (service, locuser, &pam_conv, &pam_handle); if (pam_rc == PAM_SUCCESS) pam_rc = pam_set_item (pam_handle, PAM_RHOST, hostname); if (pam_rc == PAM_SUCCESS) pam_rc = pam_set_item (pam_handle, PAM_RUSER, remuser); if (pam_rc == PAM_SUCCESS) pam_rc = pam_set_item (pam_handle, PAM_TTY, service); if (pam_rc != PAM_SUCCESS) { errorstr = "Try again.\n"; goto fail; } /* Checks existence of account, and more. */ pam_rc = pam_authenticate (pam_handle, PAM_SILENT); if (pam_rc != PAM_SUCCESS) { switch (pam_rc) { case PAM_ABORT: pam_end (pam_handle, pam_rc); exit (EXIT_FAILURE); case PAM_NEW_AUTHTOK_REQD: pam_rc = pam_chauthtok (pam_handle, PAM_CHANGE_EXPIRED_AUTHTOK); if (pam_rc == PAM_SUCCESS) { pam_rc = pam_authenticate (pam_handle, PAM_SILENT); if (pam_rc == PAM_SUCCESS) break; } default: errorstr = "Password incorrect.\n"; goto fail; } } /* Checks expiration of account, and more. */ pam_rc = pam_acct_mgmt (pam_handle, PAM_SILENT); if (pam_rc != PAM_SUCCESS) { switch (pam_rc) { case PAM_NEW_AUTHTOK_REQD: pam_rc = pam_chauthtok (pam_handle, PAM_CHANGE_EXPIRED_AUTHTOK); if (pam_rc == PAM_SUCCESS) { pam_rc = pam_acct_mgmt (pam_handle, PAM_SILENT); if (pam_rc == PAM_SUCCESS) break; } case PAM_AUTH_ERR: errorstr = "Password incorrect.\n"; goto fail; break; case PAM_ACCT_EXPIRED: case PAM_PERM_DENIED: case PAM_USER_UNKNOWN: default: errorstr = "Permission denied.\n"; goto fail; break; } } /* Renew client information, since the PAM stack may have * mapped the request onto another identity. */ free (locuser); locuser = NULL; pam_rc = pam_get_item (pam_handle, PAM_USER, (const void **) &locuser); if (pam_rc != PAM_SUCCESS) { syslog (LOG_NOTICE | LOG_AUTH, "pam_get_item(PAM_USER): %s", pam_strerror (pam_handle, pam_rc)); /* Intentionally let `locuser' be ill defined. */ } # ifdef HAVE_GETPWNAM_R ret = getpwnam_r (locuser, &pwstor, pwbuf, pwbuflen, &pwd); if (ret || pwd == NULL) # else/* !HAVE_GETPWNAM_R */ pwd = getpwnam (locuser); if (pwd == NULL) # endif/* HAVE_GETPWNAM_R */ { syslog (LOG_INFO | LOG_AUTH, "%s@%s as %s: unknown login. cmd='%.80s'", remuser, hostname, locuser, cmdbuf); errorstr = "Login incorrect.\n"; goto fail; } #else /* !WITH_PAM */ /* * The account exists by a previous call to getpwnam(). * Is the account locked, or has it expired? */ { time_t now; # ifdef HAVE_GETSPNAM struct spwd *spwd; /* * GNU/Linux, Solaris * * Locked account? */ spwd = getspnam (pwd->pw_name); if (!spwd) { syslog (LOG_ERR | LOG_AUTH, "No access to encrypted password."); if (errorstr == NULL) errorstr = "Login incorrect.\n"; goto fail; } else { /* Locked accounts have their passwords prefixed with a blocker. */ if (!strncmp ("!", spwd->sp_pwdp, strlen ("!")) || !strncmp ("*LK*", spwd->sp_pwdp, strlen ("*OK*"))) { syslog (LOG_INFO | LOG_AUTH, "%s@%s as %s: account is locked. cmd='%.80s'", remuser, hostname, locuser, cmdbuf); if (errorstr == NULL) errorstr = "Permission denied.\n"; goto fail; } } /* * Expired account? */ time (&now); if (spwd->sp_expire > 0) { time_t end_acct = DAY * spwd->sp_expire; if (difftime (now, end_acct) > 0) { syslog (LOG_INFO | LOG_AUTH, "%s@%s as %s: account is expired. cmd='%.80s'", remuser, hostname, locuser, cmdbuf); if (errorstr == NULL) errorstr = "Permission denied.\n"; goto fail; } } # else/* !HAVE_GETSPNAM */ /* * BSD systems. * * Locked account? */ if (!strncmp ("*LOCKED*", pwd->pw_passwd, strlen ("*LOCKED*"))) { syslog (LOG_INFO | LOG_AUTH, "%s@%s as %s: account is locked. cmd='%.80s'", remuser, hostname, locuser, cmdbuf); if (errorstr == NULL) errorstr = "Permission denied.\n"; goto fail; } /* * Expired account? */ # ifdef HAVE_STRUCT_PASSWD_PW_EXPIRE time (&now); /* * Negative `pw_expire' indicates on NetBSD * an immediate need for change of password. */ if (((pwd->pw_expire > 0) && (difftime (now, pwd->pw_expire) > 0)) || (pwd->pw_expire < 0)) { syslog (LOG_INFO | LOG_AUTH, "%s@%s as %s: account is expired. cmd='%.80s'", remuser, hostname, locuser, cmdbuf); if (errorstr == NULL) errorstr = "Permission denied.\n"; goto fail; } # endif /* HAVE_STRUCT_PASSWD_PW_EXPIRE */ # endif/* !HAVE_GETSPNAM */ } # if defined WITH_IRUSEROK_AF switch (fromp->sa_family) { case AF_INET6: fromaddrp = (void *) &((struct sockaddr_in6 *) fromp)->sin6_addr; break; case AF_INET: default: fromaddrp = (void *) &((struct sockaddr_in *) fromp)->sin_addr; } # endif/* !WITH_IRUSEROK_AF */ #endif /* !WITH_PAM */ #ifdef KRB4 if (use_kerberos) { if (pwd->pw_passwd != 0 && *pwd->pw_passwd != '\0') { if (kuserok (kdata, locuser) != 0) { syslog (LOG_INFO | LOG_AUTH, "Kerberos rsh denied to %s.%s@%s", kdata->pname, kdata->pinst, kdata->prealm); rshd_error ("Permission denied.\n"); exit (EXIT_FAILURE); } } } else #elif defined KRB5 /* !KRB4 */ if (use_kerberos) { if (pwd->pw_passwd != 0 && *pwd->pw_passwd != '\0' && client) { if (krb5_kuserok (context, client, locuser) != 0) { syslog (LOG_INFO | LOG_AUTH, "Kerberos rsh denied to %s.%s@%s", "kdata->pname", "kdata->pinst", "kdata->prealm"); rshd_error ("Permission denied.\n"); exit (EXIT_FAILURE); } } } else #elif defined(SHISHI) /* !KERBEROS */ if (use_kerberos) { /* if (pwd->pw_passwd != 0 && *pwd->pw_passwd != '\0') { if (kuserok (kdata, locuser) != 0) { syslog (LOG_INFO|LOG_AUTH, "Kerberos rsh denied to %s.%s@%s", kdata->pname, kdata->pinst, kdata->prealm); rshd_error ("Permission denied.\n"); exit (EXIT_FAILURE); } } */ } else #endif /* KERBEROS || SHISHI */ #ifndef WITH_PAM # ifdef WITH_IRUSEROK_SA if (errorstr || (pwd->pw_passwd != 0 && *pwd->pw_passwd != '\0' && (iruserok_sa ((void *) fromp, fromlen, pwd->pw_uid == 0, remuser, locuser)) < 0)) # elif defined WITH_IRUSEROK_AF if (errorstr || (pwd->pw_passwd != 0 && *pwd->pw_passwd != '\0' && (iruserok_af (fromaddrp, pwd->pw_uid == 0, remuser, locuser, fromp->sa_family)) < 0)) # elif defined WITH_IRUSEROK if (errorstr || (pwd->pw_passwd != 0 && *pwd->pw_passwd != '\0' && (iruserok (((struct sockaddr_in *) fromp)->sin_addr.s_addr, pwd->pw_uid == 0, remuser, locuser)) < 0)) # elif defined WITH_RUSEROK_AF if (errorstr || (pwd->pw_passwd != 0 && *pwd->pw_passwd != '\0' && (ruserok_af (addrstr, pwd->pw_uid == 0, remuser, locuser, fromp->sa_family)) < 0)) # elif defined WITH_RUSEROK if (errorstr || (pwd->pw_passwd != 0 && *pwd->pw_passwd != '\0' && (ruserok (addrstr, pwd->pw_uid == 0, remuser, locuser)) < 0)) # else/* !WITH_IRUSEROK* && !WITH_RUSEROK* */ # error Unable to use mandatory iruserok/ruserok. This should not happen. # endif/* !WITH_IRUSEROK* && !WITH_RUSEROK* */ #else /* WITH_PAM */ if (0) /* Wrapper for `fail' jump label. */ #endif /* !WITH_PAM */ { #ifdef HAVE___RCMD_ERRSTR if (__rcmd_errstr) syslog (LOG_INFO | LOG_AUTH, "%s@%s as %s: permission denied (%s). cmd='%.80s'", remuser, hostname, locuser, __rcmd_errstr, cmdbuf); else #endif /* HAVE___RCMD_ERRSTR */ syslog (LOG_INFO | LOG_AUTH, "%s@%s as %s: permission denied. cmd='%.80s'", remuser, hostname, locuser, cmdbuf); fail: #ifdef WITH_PAM if (pam_handle) { if (pam_rc != PAM_SUCCESS) syslog (LOG_NOTICE | LOG_AUTH, "%s@%s as %s, PAM: %s", remuser, hostname, locuser, pam_strerror (pam_handle, pam_rc)); pam_end (pam_handle, pam_rc); } #endif /* WITH_PAM */ if (errorstr == NULL) errorstr = "Permission denied.\n"; rshd_error (errorstr, errorhost); exit (EXIT_FAILURE); } /* If the locuser isn't root, then check if logins are disabled. */ if (pwd->pw_uid && !access (PATH_NOLOGIN, F_OK)) { rshd_error ("Logins currently disabled.\n"); exit (EXIT_FAILURE); } /* Now write the null byte back to the client, telling it * that everything is OK. * * Note that this means that any error message that we generate * from now on (such as the perror() if the execl() fails), won't * be seen by the rcmd() function, but it will be seen by the * application that called rcmd() once it reads from the socket. */ if (write (STDERR_FILENO, "\0", 1) < 0) { rshd_error ("Lost connection.\n"); exit (EXIT_FAILURE); } sent_null = 1; if (port) { /* We need a secondary channel. Here is where we create * the control process that will handle this secondary * channel. * First create a pipe to use for communication between * the parent and child, then fork. */ if (pipe (pv) < 0) { rshd_error ("Can't make pipe.\n"); exit (EXIT_FAILURE); } #ifdef ENCRYPTION # if defined KERBEROS || defined SHISHI if (doencrypt) { if (pipe (pv1) < 0) { rshd_error ("Can't make 2nd pipe.\n"); exit (EXIT_FAILURE); } if (pipe (pv2) < 0) { rshd_error ("Can't make 3rd pipe.\n"); exit (EXIT_FAILURE); } } # endif/* KERBEROS || SHISHI */ #endif /* ENCRYPTION */ pid = fork (); if (pid == -1) { rshd_error ("Can't fork; try again.\n"); exit (EXIT_FAILURE); } if (pid) { /* Parent process == control process. * We: (1) read from the pipe and write to s; * (2) read from s and send corresponding * signal. */ #ifdef ENCRYPTION # if defined KERBEROS if (doencrypt) { static char msg[] = SECURE_MESSAGE; close (pv1[1]); close (pv2[1]); des_write (s, msg, sizeof (msg) - 1); } else # elif defined(SHISHI) /* !KERBEROS */ if (doencrypt) { close (pv1[1]); close (pv2[1]); } else # endif/* KERBEROS || SHISHI */ #endif /* ENCRYPTION */ { /* child handles the original socket */ close (STDIN_FILENO); /* (0, 1, and 2 were from inetd */ close (STDOUT_FILENO); } close (STDERR_FILENO); close (pv[1]); /* close write end of pipe */ FD_ZERO (&readfrom); FD_SET (s, &readfrom); FD_SET (pv[0], &readfrom); /* set max fd + 1 for select */ if (pv[0] > s) nfd = pv[0]; else nfd = s; #ifdef ENCRYPTION # if defined KERBEROS || defined SHISHI if (doencrypt) { FD_ZERO (&writeto); FD_SET (pv2[0], &writeto); FD_SET (pv1[0], &readfrom); nfd = MAX (nfd, pv2[0]); nfd = MAX (nfd, pv1[0]); } else # endif/* KERBEROS || SHISHI */ #endif /* ENCRYPTION */ ioctl (pv[0], FIONBIO, (char *) &one); /* should set s nbio! */ nfd++; do { ready = readfrom; #ifdef ENCRYPTION # if defined KERBEROS || defined SHISHI if (doencrypt) { # ifdef SHISHI wready = readfrom; # else /* KERBEROS && !SHISHI */ wready = writeto; # endif if (select (nfd, &ready, &wready, (fd_set *) 0, (struct timeval *) 0) < 0) break; } else # endif/* KERBEROS || SHISHI */ #endif /* ENCRYPTION */ if (select (nfd, &ready, (fd_set *) 0, (fd_set *) 0, (struct timeval *) 0) < 0) /* wait until there is something to read */ break; if (FD_ISSET (s, &ready)) { int ret; #ifdef ENCRYPTION # ifdef KERBEROS if (doencrypt) ret = des_read (s, &sig, 1); else # elif defined(SHISHI) /* !KERBEROS */ if (doencrypt) readenc (h, s, &sig, &ret, &iv2, enckey, protocol); else # endif/* KERBEROS || SHISHI */ #endif /* ENCRYPTION */ ret = read (s, &sig, 1); if (ret <= 0) FD_CLR (s, &readfrom); else killpg (pid, sig); } if (FD_ISSET (pv[0], &ready)) { errno = 0; cc = read (pv[0], buf, sizeof buf); if (cc <= 0) { shutdown (s, SHUT_RDWR); FD_CLR (pv[0], &readfrom); } else { #ifdef ENCRYPTION # ifdef KERBEROS if (doencrypt) des_write (s, buf, cc); else # elif defined(SHISHI) /* !KERBEROS */ if (doencrypt) writeenc (h, s, buf, cc, &n, &iv4, enckey, protocol); else # endif/* KERBEROS || SHISHI */ #endif /* ENCRYPTION */ write (s, buf, cc); } } #ifdef ENCRYPTION # if defined KERBEROS || defined SHISHI if (doencrypt && FD_ISSET (pv1[0], &ready)) { errno = 0; cc = read (pv1[0], buf, sizeof (buf)); if (cc <= 0) { shutdown (pv1[0], SHUT_RDWR); FD_CLR (pv1[0], &readfrom); } else # ifdef SHISHI writeenc (h, STDOUT_FILENO, buf, cc, &n, &iv3, enckey, protocol); # else /* KERBEROS */ des_write (STDOUT_FILENO, buf, cc); # endif } if (doencrypt && FD_ISSET (pv2[0], &wready)) { errno = 0; # ifdef SHISHI readenc (h, STDIN_FILENO, buf, &cc, &iv1, enckey, protocol); # else /* KERBEROS */ cc = des_read (STDIN_FILENO, buf, sizeof buf); # endif if (cc <= 0) { shutdown (pv2[0], SHUT_RDWR); FD_CLR (pv2[0], &writeto); } else write (pv2[0], buf, cc); } # endif/* KERBEROS || SHISHI */ #endif /* ENCRYPTION */ } while (FD_ISSET (s, &readfrom) || #ifdef ENCRYPTION # if defined KERBEROS || defined SHISHI (doencrypt && FD_ISSET (pv1[0], &readfrom)) || # endif #endif /* ENCRYPTION */ FD_ISSET (pv[0], &readfrom)); /* The pipe will generate an EOF when the shell * terminates. The socket will terminate when the * client process terminates. */ #ifdef WITH_PAM /* The child opened the session; now it * should be closed down properly. */ pam_rc = pam_close_session (pam_handle, PAM_SILENT); if (pam_rc != PAM_SUCCESS) syslog (LOG_WARNING | LOG_AUTH, "pam_close_session: %s", pam_strerror (pam_handle, pam_rc)); pam_rc = pam_setcred (pam_handle, PAM_SILENT | PAM_DELETE_CRED); if (pam_rc != PAM_SUCCESS) syslog (LOG_WARNING | LOG_AUTH, "pam_setcred: %s", pam_strerror (pam_handle, pam_rc)); pam_end (pam_handle, pam_rc); #endif /* WITH_PAM */ exit (EXIT_SUCCESS); } /* Parent process ends. */ close (s); /* control process handles this fd */ close (pv[0]); /* close read end of pipe */ #ifdef ENCRYPTION # if defined KERBEROS || defined SHISHI if (doencrypt) { close (pv1[0]); close (pv2[0]); dup2 (pv1[1], STDOUT_FILENO); dup2 (pv2[1], STDIN_FILENO); close (pv1[1]); close (pv2[1]); } # endif/* KERBEROS || SHISHI */ #endif /* ENCRYPTION */ #if defined SHISHI if (use_kerberos) { int i; shishi_done (h); # ifdef ENCRYPTION if (doencrypt) { shishi_key_done (enckey); for (i = 0; i < 4; i++) { shishi_crypto_close (ivtab[i]->ctx); free (ivtab[i]->iv); } } # endif/* ENCRYPTION */ } #endif /* SHISHI */ dup2 (pv[1], STDERR_FILENO); /* stderr of shell has to go pipe to control process */ close (pv[1]); } #ifdef WITH_PAM /* Session handling must end also in this case. */ else { pid = fork (); if (pid < 0) { rshd_error ("Can't fork; try again.\n"); exit (EXIT_FAILURE); } if (pid) { /* Parent: Wait for child and tear down * the PAM session. */ int status; while (wait (&status) < 0 && errno == EINTR) ; pam_rc = pam_close_session (pam_handle, PAM_SILENT); if (pam_rc != PAM_SUCCESS) syslog (LOG_WARNING | LOG_AUTH, "pam_close_session: %s", pam_strerror (pam_handle, pam_rc)); pam_rc = pam_setcred (pam_handle, PAM_SILENT | PAM_DELETE_CRED); if (pam_rc != PAM_SUCCESS) syslog (LOG_WARNING | LOG_AUTH, "pam_setcred: %s", pam_strerror (pam_handle, pam_rc)); pam_end (pam_handle, pam_rc); exit (WIFEXITED (status) ? WEXITSTATUS (status) : EXIT_FAILURE); } /* Parent process ends. */ } #endif /* WITH_PAM */ /* Child process, with and without handler for stderr. * Become a process group leader, so that the control * process above can send signals to all the processes * we may be the parent of. The process group ID * (the getpid() value below) equals the childpid value * from the fork above. */ #ifdef HAVE_SETLOGIN /* Not sufficient to call setpgid() on BSD systems. */ if (getsid ((pid_t) 0) != getpid ()) if (setsid () < 0) syslog (LOG_ERR, "setsid() failed: %m"); if (setlogin (pwd->pw_name) < 0) syslog (LOG_ERR, "setlogin() failed: %m"); #else /* !HAVE_SETLOGIN */ setpgid (0, getpid ()); #endif if (*pwd->pw_shell == '\0') pwd->pw_shell = PATH_BSHELL; /* Set the gid, then uid to become the user specified by "locuser" */ if (setegid ((gid_t) pwd->pw_gid) == -1) { rshd_error ("Cannot drop privileges (setegid() failed)\n"); exit (EXIT_FAILURE); } if (setgid ((gid_t) pwd->pw_gid) == -1) { rshd_error ("Cannot drop privileges (setgid() failed)\n"); exit (EXIT_FAILURE); } #ifdef HAVE_INITGROUPS initgroups (pwd->pw_name, pwd->pw_gid); /* BSD groups */ #endif #ifdef WITH_PAM pam_rc = pam_setcred (pam_handle, PAM_SILENT | PAM_ESTABLISH_CRED); if (pam_rc != PAM_SUCCESS) { syslog (LOG_ERR | LOG_AUTH, "pam_setcred: %s", pam_strerror (pam_handle, pam_rc)); pam_rc = PAM_SUCCESS; /* Only report the above anomaly. */ } pam_rc = pam_open_session (pam_handle, PAM_SILENT); if (pam_rc != PAM_SUCCESS) { syslog (LOG_ERR | LOG_AUTH, "pam_open_session: %s", pam_strerror (pam_handle, pam_rc)); pam_rc = PAM_SUCCESS; /* Only report the above anomaly. */ } #endif /* WITH_PAM */ if (setuid ((uid_t) pwd->pw_uid) == -1) { rshd_error ("Cannot drop privileges (setuid() failed)\n"); exit (EXIT_FAILURE); } /* We'll execute the client's command in the home directory * of locuser. Note, that the chdir must be executed after * setuid(), otherwise it may fail on NFS mounted directories * (root mapped to nobody). */ if (chdir (pwd->pw_dir) < 0) { syslog (LOG_INFO | LOG_AUTH, "%s@%s as %s: no home directory. cmd='%.80s'", remuser, hostname, locuser, cmdbuf); rshd_error ("No remote directory.\n"); if (chdir ("/") < 0) { syslog (LOG_ERR | LOG_AUTH, "%s@%s as %s: access denied to '/'", remuser, hostname, locuser); exit (EXIT_FAILURE); } } /* Set up an initial environment for the shell that we exec() */ strncat (homedir, pwd->pw_dir, sizeof (homedir) - sizeof ("HOME=") - 1); strncat (path, PATH_DEFPATH, sizeof (path) - sizeof ("PATH=") - 1); strncat (shell, pwd->pw_shell, sizeof (shell) - sizeof ("SHELL=") - 1); strncat (username, pwd->pw_name, sizeof (username) - sizeof ("USER=") - 1); strncat (logname, pwd->pw_name, sizeof (logname) - sizeof ("LOGNAME=") - 1); strncat (rhost, hostname, sizeof (rhost) - sizeof ("RHOST=") - 1); #ifdef WITH_PAM if (pam_getenv (pam_handle, "PATH") == NULL) (void) pam_putenv (pam_handle, path); if (pam_getenv (pam_handle, "HOME") == NULL) (void) pam_putenv (pam_handle, homedir); if (pam_getenv (pam_handle, "SHELL") == NULL) (void) pam_putenv (pam_handle, shell); if (pam_getenv (pam_handle, "USER") == NULL) (void) pam_putenv (pam_handle, username); if (pam_getenv (pam_handle, "LOGNAME") == NULL) (void) pam_putenv (pam_handle, logname); if (pam_getenv (pam_handle, "RHOST") == NULL) (void) pam_putenv (pam_handle, rhost); environ = pam_getenvlist (pam_handle); #else /* !WITH_PAM */ environ = envinit; #endif /* WITH_PAM */ cp = strrchr (pwd->pw_shell, '/'); if (cp) cp++; /* step past last slash */ else cp = pwd->pw_shell; /* no slash in shell string */ endpwent (); if (log_success || pwd->pw_uid == 0) { #ifdef KRB4 if (use_kerberos) syslog (LOG_INFO | LOG_AUTH, "Kerberos shell from %s.%s@%s on %s as %s, cmd='%.80s'", kdata->pname, kdata->pinst, kdata->prealm, hostname, locuser, cmdbuf); else #endif /* KRB4 */ syslog (LOG_INFO | LOG_AUTH, "%s%s from %s as '%s': cmd='%.80s'", #ifdef SHISHI !use_kerberos ? "" : !doencrypt ? "Kerberized " : "Kerberized and encrypted ", #else "", #endif rprincipal ? rprincipal : remuser, hostname, locuser, cmdbuf); } #ifdef SHISHI if (doencrypt) execl (pwd->pw_shell, cp, "-c", cmdbuf + 3, NULL); else #endif /* SHISHI */ execl (pwd->pw_shell, cp, "-c", cmdbuf, NULL); #ifdef WITH_PAM pam_end (pam_handle, PAM_SUCCESS); #endif syslog (LOG_ERR, "execl failed for \"%s\": %m", pwd->pw_name); error (EXIT_FAILURE, errno, "cannot execute %s", pwd->pw_shell); } /* * Report error to client. Note: can't be used until second socket has * connected to client, or older clients will hang waiting for that * connection first. */ void rshd_error (const char *fmt, ...) { va_list ap; int len; char *bp, buf[BUFSIZ]; va_start (ap, fmt); bp = buf; if (sent_null == 0) { *bp++ = 1; /* error indicator */ len = 1; } else len = 0; vsnprintf (bp, sizeof (buf) - 1, fmt, ap); va_end (ap); write (STDERR_FILENO, buf, len + strlen (bp)); } char * getstr (const char *err) { size_t buf_len = 100; char *buf = malloc (buf_len), *end = buf; if (!buf) { rshd_error ("Out of space reading %s\n", err); exit (EXIT_FAILURE); } do { /* Oh this is efficient, oh yes. [But what can be done?] */ int rd = read (STDIN_FILENO, end, 1); if (rd <= 0) { if (rd == 0) rshd_error ("EOF reading %s\n", err); else perror (err); exit (EXIT_FAILURE); } end += rd; if ((buf + buf_len - end) < (ssize_t) (buf_len >> 3)) { /* Not very much room left in our buffer, grow it. */ size_t end_offs = end - buf; buf_len += buf_len; buf = realloc (buf, buf_len); if (!buf) { rshd_error ("Out of space reading %s\n", err); exit (EXIT_FAILURE); } end = buf + end_offs; } } while (*(end - 1)); return buf; } /* * Check whether host h is in our local domain, * defined as sharing the last two components of the domain part, * or the entire domain part if the local domain has only one component. * If either name is unqualified (contains no '.'), * assume that the host is local, as it will be * interpreted as such. */ int local_domain (const char *h) { char *hostname = localhost (); if (!hostname) return 0; else { int is_local = 0; const char *p1 = topdomain (hostname); const char *p2 = topdomain (h); if (p1 == NULL || p2 == NULL || !strcasecmp (p1, p2)) is_local = 1; free (hostname); return is_local; } } const char * topdomain (const char *h) { const char *p, *maybe = NULL; int dots = 0; for (p = h + strlen (h); p >= h; p--) { if (*p == '.') { if (++dots == 2) return p; maybe = p; } } return maybe; } #ifdef WITH_PAM /* Call back function for passing user's information * to any PAM module requesting this information. */ static int rsh_conv (int num, const struct pam_message **pam_msg, struct pam_response **pam_resp, void *data MAYBE_UNUSED) { struct pam_response *resp; /* Reject composite calls at the time being. */ if (num <= 0 || num > 1) return PAM_CONV_ERR; /* Ensure an empty response. */ *pam_resp = NULL; switch ((*pam_msg)->msg_style) { case PAM_PROMPT_ECHO_OFF: /* Return an empty password. */ resp = (struct pam_response *) malloc (sizeof (*resp)); if (!resp) return PAM_BUF_ERR; resp->resp_retcode = 0; resp->resp = strdup (""); if (!resp->resp) { free (resp); return PAM_BUF_ERR; } if (log_success) syslog (LOG_NOTICE | LOG_AUTH, "PAM message \"%s\".", (*pam_msg)->msg); *pam_resp = resp; return PAM_SUCCESS; break; case PAM_TEXT_INFO: /* Not yet supported. */ case PAM_ERROR_MSG: /* Likewise. */ case PAM_PROMPT_ECHO_ON: /* Interactivity is not supported. */ default: return PAM_CONV_ERR; } return PAM_CONV_ERR; /* Never reached. */ } #endif /* WITH_PAM */