/* $Id: ssh_sys_linux.c,v 1.2 2001/02/11 03:35:14 tls Exp $ */ /* * Copyright 1999 RedBack Networks, Incorporated. * All rights reserved. * * This software is not in the public domain. It is distributed * under the terms of the license in the file LICENSE in the * same directory as this file. If you have received a copy of this * software without the LICENSE file (which means that whoever gave * you this software violated its license) you may obtain a copy from * http://www.panix.com/~tls/LICENSE.txt */ /* * Copyright (c) 2000, 2001 Eric Haszlakiewicz, Brian Nigito, and * Thor Lancelot Simon. * 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. The names of the authors may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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. */ #define FDMSG_LEN (sizeof(struct cmsghdr) + sizeof(int)) #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SSH_EVENT_STRINGS #include "options.h" #include "sshd.h" #include "ssh_buffer.h" #include "ssh_crypto.h" #include "ssh_global.h" #include "ssh_paths.h" #include "ssh_sys.h" #include "ssh_util.h" #include "ssh_transport.h" #include "ssh_parse.h" #include "ssh_keyfile.h" #include "ssh_event.h" #include "pathnames.h" #ifndef __linux__ #error This file only works on Linux systems! #endif static char *childenv[16]; /* XXX */ static char shell[MAXINTERP + 1]; /* * ssh_sys_allocpty: doesn't actually allocate the pty on 4.4BSD! * * Instead, it sets everything up, and ssh_sys_execcmd uses a * function from libutil which includes the pty allocation. * This means the other end may think we successfully * allocated a pty, but then failed to execute the requested * command, if, for example, we're out of ptys. NBD. */ int ssh_sys_allocpty(ssh_context_t * context, char *term, int y_row, int x_col, int x_pix, int y_pix, u_int8_t * modes, size_t msize) { static char envterm[255 + 1]; char **newenv; if ((context->term = malloc(strlen(term) + 1)) == NULL) { SSH_ERROR("ssh_sys_allocpty: unable to alloc mem for term: " "%s\n", strerror(errno)); return (1); } strncpy(context->term, term, strlen(term)); snprintf(envterm, sizeof(envterm), "TERM=%s", term); for (newenv = childenv; *newenv != NULL; newenv++); *newenv = envterm; newenv++; *newenv = NULL; context->modes = malloc(msize); if (context->modes == NULL) { SSH_ERROR("ssh_sys_allocpty: unable to alloc mem for tty " "modes: %s\n", strerror(errno)); free(context->term); context->term = NULL; return (1); } memcpy(context->modes, modes, msize); context->msize = msize; /* Set window size. */ context->win.ws_row = y_row; context->win.ws_col = x_col; context->win.ws_xpixel = x_pix; context->win.ws_ypixel = y_pix; /* Set flag: ssh_sys_execcmd will check this and allocate a pty then. */ context->usepty = 1; return (0); } /* * ssh_sys_setbufsize: Set socket buffer size to increase throughput. */ void ssh_sys_setbufsize(ssh_context_t * context, int fd0, int fd1) { int size; int a, b, c, d; size = (context->transport_ctx.get_sendsize) (&context->transport_ctx); if (size > SSH_MAX_READSIZE) size = SSH_MAX_READSIZE; if (size < SSH_MIN_READSIZE) size = SSH_MIN_READSIZE; while (size > SSH_MIN_READSIZE) { a = setsockopt(fd0, SOL_SOCKET, SO_SNDBUF, &size, sizeof(int)); b = setsockopt(fd0, SOL_SOCKET, SO_RCVBUF, &size, sizeof(int)); c = setsockopt(fd1, SOL_SOCKET, SO_SNDBUF, &size, sizeof(int)); d = setsockopt(fd1, SOL_SOCKET, SO_RCVBUF, &size, sizeof(int)); if (a == 0 && b == 0 && c == 0 && d == 0) break; size -= 2048; } /* * Set the amount to read from the primary FD to the size of * the buffer we just set on it. */ if (size < SSH_MIN_READSIZE) context->v1_ctx.readsize = SSH_MIN_READSIZE; else context->v1_ctx.readsize = size; } /* * ssh_sys_execcmd: execute a command. */ int ssh_sys_execcmd(ssh_context_t * context, char *cmd, int *fds) { int stdfd[2]; int errfd[2]; int mfd; char slave_name[MAXPATHLEN + 1]; struct termios term; char *shell_name; int cpid; SSH_DLOG(3, ("ssh_sys_execcmd: CMD=%s\n", cmd)); if (context->usepty) { SSH_DLOG(4, ("Executing command using pty\n")); if ((cpid = forkpty(&mfd, slave_name, NULL, &context->win)) == 0) { /* child: */ if (tcgetattr(0, &term) < 0) { SSH_ERROR("ssh_sys_execcmd: couldn't get " "modes for pty.\n"); exit(1); } if (ssh_sys_set_tty_modes(&term, context->modes, context->msize) != 0) { SSH_ERROR("ssh_sys_execcmd: set_tty_modes " "failed: %s\n", strerror(errno)); exit(1); } if (tcsetattr(0, TCSANOW, &term) < 0) { SSH_ERROR("ssh_sys_execcmd: couldn't set " "modes for pty.\n"); exit(1); } if (close_files_for_exec(context) < 0) { SSH_ERROR("ssh_sys_execcmd: unable to close " "files.\n"); exit(1); } #if 0 clear_sensitive_data(); #endif reset_signals(); /* * XXX set environment: LOGNAME, TZ, SSH_CLIENT, * SSH_TTY */ if (cmd != NULL && strlen(cmd) != 0) { shell_name = strrchr(shell, '/'); execle(shell, (shell_name++ == NULL) ? shell : shell_name, "-c", cmd, NULL, childenv); } else { /* XXX fix linux login */ /* From LOGIN(1) * -f Used to skip a second login authentication. This specifically does * not work for root, and does not appear to work well under Linux. */ shell_name = strrchr(shell, '/'); execle(shell, (shell_name++ == NULL) ? shell : shell_name, "-", NULL, childenv); /* execle(PATH_LOGIN, "login", "-f", "-p", #if 0 "-h", inet_ntoa(context->saddr.sin_addr), #endif "--", context->username, NULL, childenv); */ } SSH_DLOG(1, ("exec failed: %s\n", strerror(errno))); exit(1); } else { int size, len; int ret; /* parent: */ SSH_DLOG(3, ("Command pid: %d\n", cpid)); fds[0] = mfd; fds[1] = mfd; len = sizeof(int); ret = getsockopt(mfd, SOL_SOCKET, SO_RCVBUF, &size, &len); /* XAX Huh? why don't we use setbufsize()? */ if (ret < 0 || size < SSH_MIN_READSIZE) context->v1_ctx.readsize = SSH_MIN_READSIZE; else context->v1_ctx.readsize = size; } } else { SSH_DLOG(4, ("Executing command using socketpairs\n")); /* create socket pairs. */ if (socketpair(AF_UNIX, SOCK_STREAM, 0, stdfd) < 0 || socketpair(AF_UNIX, SOCK_STREAM, 0, errfd) < 0) { SSH_DLOG(1, ("doExecCmd: socketpair failed: %s\n", strerror(errno))); return (1); } ssh_sys_setbufsize(context, stdfd[0], stdfd[1]); if ((cpid = fork()) == 0) { /* child: */ { int i; for (i = 0; i <= NSIG; i++) signal(i, SIG_DFL); } if (dup2(stdfd[0], fileno(stdin)) < 0) { SSH_ERROR("doExecCmd: dup2 stdfd -> stdin " "failed: %s\n", strerror(errno)); exit(1); } if (dup2(stdfd[0], fileno(stdout)) < 0) { SSH_ERROR("doExecCmd: dup2 stdfd -> stdout " "failed: %s\n", strerror(errno)); exit(1); } close(stdfd[0]); close(stdfd[1]); if (dup2(errfd[0], fileno(stderr)) < 0) { SSH_ERROR("doExecCmd: dup2 errfd -> stderr " "failed: %s\n", strerror(errno)); exit(1); } close(errfd[0]); close(errfd[1]); if (close_files_for_exec(context) < 0) { SSH_ERROR("ssh_sys_execcmd: unable to close " "files.\n"); exit(1); } #if 0 clear_sensitive_data(); #endif reset_signals(); shell_name = strrchr(shell, '/'); if (cmd != NULL && strlen(cmd) != 0) { execle(shell, (shell_name++ == NULL) ? shell : shell_name, "-c", cmd, NULL, childenv); } else { SSH_DLOG(1, ("exec : %s %s\n", shell, (shell_name++ == NULL) ? shell : shell_name )); /* (shell_name++ == NULL) ? execle(shell, (shell_name++ == NULL) ? shell : shell_name, "-", NULL, childenv); */ execle("/bin/login", "login", "-f", "-p", #if 0 "-h", inet_ntoa(context->saddr.sin_addr), #endif "--", context->username, NULL, childenv); } SSH_DLOG(1, ("exec failed: %s\n", strerror(errno))); exit(1); } else { /* parent: */ /* Close child's side descriptors. */ close(errfd[0]); close(stdfd[0]); /* Save the descriptors. */ fds[0] = stdfd[1]; fds[1] = errfd[1]; } } context->child_pid = cpid; return (0); } /* * PRNG wrapper functions. Replace with calls to your favorite PRNG if * desired. * * A note on randinit/randclean: If you have hardware randomness, you * may want to modify randinit to use it. On NetBSD, we use the system * RNG/PRNG, specifying the device node which generates to a PRNG if no * "hard" random data is available so that we don't block at startup. We * do mix in some other gunk just in case, but you *really* want the kernel * random source turned on -- no great effort is made elsewise. * * In such an implementation, randclean just zeroes out the internal PRNG * state for neatness/safety. But an alternate implementation might try to * preserve PRNG state across runs; then randclean would save the state, and * randinit would load it, hopefully mixing in some new seed randomness. * */ /* * ssh_sys_randinit: initialize random number generator. */ void ssh_sys_randinit() { struct timeval tv; int urandom; u_int8_t ubuf[128]; gettimeofday(&tv, (struct timezone *) NULL); ssh_rand_feed((void *) &(tv.tv_sec), sizeof(tv.tv_sec)); ssh_rand_feed((void *) &(tv.tv_usec), sizeof(tv.tv_usec)); memset(&tv, 0, sizeof(tv)); #if 0 XXX Instead of this, execute a configurable script in / etc / ssh XXX Make sure that permissions are ok first. system("/usr/bin/wc -c /usr/share/dict/words > /dev/null 2>&1"); system("/bin/ps auxl > /dev/null 2>&1"); system("/bin/ls -lR /home > /dev/null 2>&1"); #endif urandom = open(_PATH_URANDOM, O_RDONLY); if (urandom >= 0) { if (read(urandom, ubuf, sizeof(ubuf)) != sizeof(ubuf)) { close(urandom); goto wingit; } else { close(urandom); ssh_rand_feed(ubuf, sizeof(ubuf)); } } else { /* * System PRNG unavailable -- seed with some more * data and pray */ wingit: { /* XXX Fixme */ SSH_ERROR("No suitable PRNG available!"); ssh_sys_exit(1); } } gettimeofday(&tv, NULL); ssh_rand_feed((void *) &(tv.tv_usec), sizeof(tv.tv_usec)); memset(ubuf, 0, sizeof(ubuf)); memset(&tv, 0, sizeof(tv)); } /* * ssh_sys_randclean: clean up (possibly save) PRNG state. */ void ssh_sys_randclean() { ssh_rand_clean(); } /* * ssh_sys_randadd: Add some external randomness to the mix. Must be * fast, called during normal operation. */ void ssh_sys_randadd() { struct timeval tv; int urandom; u_int8_t ubuf[128]; urandom = open(_PATH_URANDOM, O_RDONLY); if (urandom < 0) { gettimeofday(&tv, NULL); ssh_rand_feed((void *) &(tv.tv_usec), sizeof(tv.tv_usec)); } else { if (read(urandom, ubuf, sizeof(ubuf)) != sizeof(ubuf)) { close(urandom); gettimeofday(&tv, NULL); ssh_rand_feed((void *) &(tv.tv_usec), sizeof(tv.tv_usec)); } else { close(urandom); ssh_rand_feed(ubuf, sizeof(ubuf)); } } memset(&tv, 0, sizeof(struct timeval)); memset(ubuf, 0, sizeof(ubuf)); } void ssh_sys_exit(int retval) { /* cleanup whatever. */ SSH_DLOG(5, ("ssh_sys_exit: exiting with %d\n", retval)); exit(retval); } /* * ssh_sys_setuser: * Set up the initial environment. * Switch uid/gid. * chdir to home directory. */ int ssh_sys_setuser(ssh_context_t * context, char *uname) { char **newenv = childenv; struct ssh_password *pwent; static char ename[5 + MAXLOGNAME + 1]; static char ehome[5 + MAXPATHLEN + 1]; static char eshell[6 + MAXINTERP + 1]; static char epath[32]; if (!context->pwent.pw_name) if (ssh_sys_lookupuser(context, uname) != 0) return (-1); pwent = &context->pwent; (void) setgid(pwent->pw_gid); (void) initgroups(pwent->pw_name, pwent->pw_gid); (void) setuid(pwent->pw_uid); snprintf(ename, sizeof(ename), "USER=%s", pwent->pw_name); snprintf(ehome, sizeof(ehome), "HOME=%s", pwent->pw_dir); snprintf(eshell, sizeof(eshell), "SHELL=%s", pwent->pw_shell); snprintf(epath, sizeof(epath), "PATH=/bin:/usr/bin:/usr/pkg/bin"); *newenv = ename; newenv++; *newenv = ehome; newenv++; *newenv = eshell; newenv++; *newenv = epath; newenv++; *newenv = NULL; chdir(pwent->pw_dir); snprintf(shell, sizeof(shell), "%s", pwent->pw_shell); return 0; } int ssh_sys_daemonize() { return daemon(0, 0); } int ssh_sys_create_eventq(struct ssh_context * context) { int opt = sizeof(struct ssh_event), s = 0, saved_errno; if (socketpair(AF_LOCAL, SOCK_STREAM /* DGRAM */ , 0, context->eventq) < 0) return -1; s += setsockopt(context->eventq[0], SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt)); s += setsockopt(context->eventq[0], SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)); s += setsockopt(context->eventq[1], SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt)); s += setsockopt(context->eventq[1], SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)); if (s < 0) { saved_errno = errno; close(context->eventq[0]); close(context->eventq[1]); context->eventq[0] = context->eventq[1] = -1; errno = saved_errno; return -1; } return 0; } void ssh_sys_collapse_eventq(struct ssh_context * context) { if (context->thread_id == SSH_THREAD_RECV) { /* swap em, so that we always close [1] and always use [0] */ int t = context->eventq[0]; context->eventq[0] = context->eventq[1]; context->eventq[1] = t; } close(context->eventq[1]); } int ssh_sys_select(struct ssh_context * context, int nfds, fd_set * readfds, fd_set * writefds, fd_set * exceptfds, struct timeval * timeout, struct ssh_event * ev) { int num; int my_eventfd = -1; my_eventfd = context->eventq[0]; ev->event_type = SSH_EVENT_NONE; if (my_eventfd > 0) { FD_SET(my_eventfd, readfds); if ((my_eventfd + 1) > nfds) nfds = my_eventfd + 1; num = select(nfds, readfds, writefds, exceptfds, timeout); if (num > 0 && FD_ISSET(my_eventfd, readfds)) { int resid = sizeof(struct ssh_event), howmuch; void *tmpev = ev; while (resid != 0) { howmuch = read(my_eventfd, tmpev, resid); if (howmuch <= 0) { if (errno == EINTR) continue; else { SSH_ERROR("Short message" "read: %s", strerror(errno)); return -1; } } resid -= howmuch; tmpev += howmuch; } FD_CLR(my_eventfd, readfds); num--; if (ev->event_type == SSH_EVENT_PASSFD) { struct ssh_ev_passfd *evfd; struct iovec iov[1]; struct msghdr msg; struct cmsghdr *fdmsg; char fdmsg_buf[FDMSG_LEN]; char dummy[1]; fdmsg = (struct cmsghdr *) fdmsg_buf; iov[0].iov_base = dummy; iov[0].iov_len = 1; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_control = (void *) fdmsg; msg.msg_controllen = FDMSG_LEN; msg.msg_flags = MSG_WAITALL; /* Get the fd. */ if (recvmsg(my_eventfd, &msg, 0) < 0 || msg.msg_controllen != FDMSG_LEN || msg.msg_flags & (MSG_TRUNC | MSG_CTRUNC)) { SSH_ERROR("Failure reading passed " "FD:%s\n", strerror(errno)); return (-1); } evfd = (struct ssh_ev_passfd *) ev->event_data; evfd->fd = *(int *) CMSG_DATA(fdmsg); } } } else num = select(nfds, readfds, writefds, exceptfds, timeout); if (num < 0) SSH_DLOG(3, ("select returned -1: %d %s\n", errno, strerror(errno))); return num; } int ssh_sys_sendevent(struct ssh_context * context, struct ssh_event * ev) { int dest_eventfd = -1; int resid = sizeof(struct ssh_event), howmuch; void *tmpev = ev; #ifndef SSH_EVENT_STRINGS SSH_DLOG(4, ("start. type: %d\n", ev->event_type)); #else /* SSH_EVENT_STRINGS */ SSH_DLOG(4, ("start. type %s (%d)\n", event_name[ev->event_type], ev->event_type)); #endif /* SSH_EVENT_STRINGS */ dest_eventfd = context->eventq[0]; if (context->thread_id == SSH_THREAD_SEND) { SSH_ERROR("Tried to send message from THREAD_SEND\n"); return -1; } if (ev->event_type == SSH_EVENT_NONE) { SSH_ERROR("Tried to send EVENT_NONE\n"); return -1; } while (resid != 0) { howmuch = write(dest_eventfd, ev, resid); if (howmuch < 0) { if (errno == EINTR) continue; else { SSH_ERROR("Short message write: %s", strerror(errno)); return -1; } } resid -= howmuch; tmpev += howmuch; } #ifndef SSH_EVENT_STRINGS SSH_DLOG(4, ("done. type: %d\n", ev->event_type)); #else /* SSH_EVENT_STRINGS */ SSH_DLOG(4, ("done. type %s (%d)\n", event_name[ev->event_type], ev->event_type)); #endif /* SSH_EVENT_STRINGS */ return 0; } /* * ssh_sys_send_fd: * Send a fd to the sendThread across a unix domain socket. * * Returns: * 0 All OK * -1 Failure. */ int ssh_sys_send_fd(ssh_context_t * context, int fd, int type, int channel_id) { FUNC_DECL(ssh_sys_send_fd); int my_eventfd; struct ssh_event ev; struct ssh_ev_passfd *evfd; struct iovec iov[1]; struct msghdr msg; struct cmsghdr *fdmsg; char fdmsg_buf[FDMSG_LEN]; char dummy[1]; #ifdef BROKEN_LOCAL_FLOW_CONTROL int send_loops = 0; #endif if (context->thread_id != SSH_THREAD_RECV) { SSH_ERROR("ssh_sys_send_fd called from other than recvThread"); ssh_exit(context, 1, EXIT_NOW); } my_eventfd = context->eventq[0]; ev.event_type = SSH_EVENT_PASSFD; evfd = (struct ssh_ev_passfd *) ev.event_data; evfd->fd_type = type; evfd->fd = -1; evfd->channel_id = channel_id; EVT_SEND(&ev, context); /* Set the fd in the control message */ fdmsg = (struct cmsghdr *) fdmsg_buf; fdmsg->cmsg_level = SOL_SOCKET; fdmsg->cmsg_type = SCM_RIGHTS; fdmsg->cmsg_len = FDMSG_LEN; *(int *) CMSG_DATA(fdmsg) = fd; /* Setup a dummy io msg containing the fd */ iov[0].iov_base = dummy; iov[0].iov_len = 1; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_control = (void *) fdmsg; msg.msg_controllen = FDMSG_LEN; msg.msg_flags = 0; while (sendmsg(my_eventfd, &msg, 0) < 0) { #ifdef BROKEN_LOCAL_FLOW_CONTROL if (errno == EMSGSIZE && send_loops < 10) { SSH_DLOG(3, ("sendmsg on local socket flow control bug " "workaround triggered. (EMSGSIZE)\n")); usleep(100); send_loops++; continue; } #endif SSH_ERROR("sendmsg failed sending fd: %d, %s\n", errno, strerror(errno)); return (-1); } return (0); } void * ssh_sys_readin(const char *filename, off_t *len) { int d; void *v; if ((d = open(filename, O_RDONLY, 0600)) < 0) return NULL; *len = lseek(d, 0, SEEK_END); v = mmap(NULL, (size_t) * len, PROT_READ, MAP_PRIVATE, d, (off_t) 0); close(d); return v; } int ssh_sys_checkpw(char *uname, const char *pw) { int ret; struct spwd *pwent; pwent = getspnam(uname); if (pwent == NULL) return -1; if (pwent->sp_pwdp == NULL) { ret = 0; goto out; } if (!strcmp(crypt(pw, pwent->sp_pwdp), pwent->sp_pwdp)) ret = 0; else ret = -1; out: memset(pwent->sp_pwdp, 0, strlen(pwent->sp_pwdp)); endspent(); return (ret); }