diff -N -U 5 -u openssh-4.2p1/Makefile.in sshop-4.2p1/Makefile.in --- openssh-4.2p1/Makefile.in Sun May 29 03:22:29 2005 +++ sshop-4.2p1/Makefile.in Fri Oct 21 16:02:25 2005 @@ -58,11 +58,11 @@ EXEEXT=@EXEEXT@ INSTALL_SSH_PRNG_CMDS=@INSTALL_SSH_PRNG_CMDS@ INSTALL_SSH_RAND_HELPER=@INSTALL_SSH_RAND_HELPER@ -TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-keysign${EXEEXT} ssh-agent$(EXEEXT) scp$(EXEEXT) ssh-rand-helper${EXEEXT} sftp-server$(EXEEXT) sftp$(EXEEXT) +TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-keysign${EXEEXT} ssh-agent$(EXEEXT) scp$(EXEEXT) ssh-rand-helper${EXEEXT} sftp-server$(EXEEXT) sftp$(EXEEXT) sshhop_test$(EXEEXT) LIBSSH_OBJS=acss.o authfd.o authfile.o bufaux.o buffer.o \ canohost.o channels.o cipher.o cipher-acss.o cipher-aes.o \ cipher-bf1.o cipher-ctr.o cipher-3des1.o cleanup.o \ compat.o compress.o crc32.o deattack.o fatal.o hostfile.o \ @@ -72,11 +72,11 @@ monitor_fdpass.o rijndael.o ssh-dss.o ssh-rsa.o dh.o kexdh.o \ kexgex.o kexdhc.o kexgexc.o scard.o msg.o progressmeter.o dns.o \ entropy.o scard-opensc.o gss-genr.o SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \ - sshconnect.o sshconnect1.o sshconnect2.o + sshconnect.o sshconnect1.o sshconnect2.o sshhop.o SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o auth-rsa.o auth-rh-rsa.o \ sshpty.o sshlogin.o servconf.o serverloop.o \ auth.o auth1.o auth2.o auth-options.o session.o \ auth-chall.o auth2-chall.o groupaccess.o \ @@ -162,10 +162,13 @@ sftp$(EXEEXT): $(LIBCOMPAT) libssh.a sftp.o sftp-client.o sftp-common.o sftp-glob.o progressmeter.o $(LD) -o $@ progressmeter.o sftp.o sftp-client.o sftp-common.o sftp-glob.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) $(LIBEDIT) ssh-rand-helper${EXEEXT}: $(LIBCOMPAT) libssh.a ssh-rand-helper.o $(LD) -o $@ ssh-rand-helper.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) + +sshhop_test$(EXEEXT): $(LIBCOMPAT) libssh.a sshhop.o sshhop_test.o + $(LD) -o $@ sshhop.o sshhop_test.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) $(LIBEDIT) # test driver for the loginrec code - not built by default logintest: logintest.o $(LIBCOMPAT) libssh.a loginrec.o $(LD) -o $@ logintest.o $(LDFLAGS) loginrec.o -lopenbsd-compat -lssh $(LIBS) diff -N -U 5 -u openssh-4.2p1/README.sshhop sshop-4.2p1/README.sshhop --- openssh-4.2p1/README.sshhop Wed Dec 31 19:00:00 1969 +++ sshop-4.2p1/README.sshhop Thu Dec 22 09:26:12 2005 @@ -0,0 +1,398 @@ +SSH is a protocol used to open a command shell on a remote host via an +encrypted communications channel. When a firewall or other network +roadblock prevents a user on host A from directly opening an SSH connection +to a host C it is often necessary to connect through an intermediary. +Users will often enter a command on host A to establish an SSH connection +to the intermediate host B, and then enter a command to host B via +that connection to establish an SSH connection from host B to host C. +If an attacker controls host B, the attacker can either capture the +credentials (password) used to establish the connection between the user +and host C or may simply capture the connection after it is established. + +The SSH hop modifications, discussed in this README, introduce a +mechanism to facilitate a more secure use of SSH for establishing remote +command shells via connections through intermediate hosts. As before, +an encrypted channel is established from host A and host B. As before, +a connection is then opened from host B to host C. However, this +connection is used to establish an encrypted channel between host A and +host C, preventing the owner of host B from eavesdropping on or modifying +its contents. + +The OpenBSD project's version of SSH, OpenSSH, supports those secure +connections via intermediary hosts by configuring a sequence of port +forwarding SSH connections. In the general case, SSH port forwarding +allows a user process to make a TCP/IP connection to a specially +configured SSH client which forwards communications from the user process +to a server on a remote host via the SSH tunnel between the SSH client +and SSH server. These four processes may be running on up to four +separate hosts. Barring any application-dependent encryption between +the user process and remote server, only the communications between +the SSH client and SSH server are encrypted. When port forwarding is +used for SSH hopping as described above, the user process is another +SSH client process and the server on the remote host is an SSH server. +In that case, the four processes are running on at least three hosts +and the communications between the user process and its remote server +are encrypted as they transit the intermediate host. + +Setting up such a port forwarding connection for SSH hopping involves +spawning multiple SSH client processes at the command line. Although +not difficult to set up, it does require some thought and a deeper +knowledge of the SSH options than would be possessed by a casual user. +In addition, the TCP/IP port provided by the specially configured SSH +client process to be used for port forwarding is potentially accessible +to other users on that host, which would give them access to the remote +host. + +The SSH hop modifications described below seek to improve the usability +of the SSH connection chaining to encourage use of a more secure +connection strategy through intermediaries than might otherwise be used. + +Each host in the chain of encrypted connections from the source host +to the ultimate destination host represents a single "hop". The +authentication credentials are revealed only at the source host and at +the host for which those credentials are intended. The following SSH +hop command line syntax is provided to access the destination host +(hostn) via a chain of hosts (...) with host1 as the first intermediate +host in the chain: + + ssh [ssh_global_options] + -H [ssh_local_options] [-p port1] [user1@]host1 + ... + -H [ssh_local_options] [-p portn] [usern@]hostn + [command] + +The "ssh_global_options" and "ssh_local_options" represent any SSH +options, other than -H, that are relevant in, respectively, the global +and local contexts. The global options appear before the first -H in +the command and are applied, as appropriate, to every hop in the chain. +The effect is as if those options preceded every set of hop specific +options for every hop in the chain. The local options follow a -H and +are specific only to that one hop. For more information on which +SSH options can be used as global options and which can be used +as local options, please see the next section ("SSH option processing +in conjunction with the -H option"). + +A host name or IP address must be provided following each -H. The user +name of the account at each hop is determined by matching the first of +the following rules to apply: + 1) user name attached to the host name (e.g., -H user@host) + 2) user name specified in the local options (using -l option) + (e.g., -H -l user host) + 3) user name specified in the global options (using -l option) + (e.g., ssh -l user -H ...) + 4) the name of the user account issuing the SSH command + +Example: + +-------+ +------------+ +--------------------+ + |student| | hobbes | | calvin | + |@school| -----> |@gateway.com| -----> |@spaceship.world.net| + +-------+ +------------+ +--------------------+ + Suppose "student@school" wants to log into "spaceship.world.net" + as "calvin" via his "hobbes" account at "gateway.com". The + following command could be used: + + %school> ssh -l calvin -H hobbes@gateway.com -H spaceship.world.net + + That command will create a chain of subprocesses on the host + "school" to construct two encrypted tunnels. The first encrypted + tunnel will connect "student@school" to "hobbes@gateway.com". The + second encrypted tunnel will connect "student@school" to + "calvin@spaceship.world.net" via the first encrypted tunnel. + +To make SSH hopping more secure on the source host, support was also +added for using Unix Domain Sockets, rather than TCP/IP sockets, for +the ports on that host used for port forwarding (port forwarded ports). +The disadvantage to using TCP/IP for the port forwarded port is that +other users on the source host might be able to connect to the TCP/IP +port and gain access to a remote host to which they should not have +access. Unlike TCP/IP sockets, Unix Domain Sockets can be protected +from access by users other than the one who created the port forwarding +tunnel. By extension, the connection to the remote host will also be +protected from unauthorized access. To support use of Unix Domain Sockets +for port forwarding, two options were added to SSH. + +The -U and -u options were added to support use of Unix Domain Sockets +for port forwarding. The syntax is as follows: +-U ,, + Local port forwarding information used for creating this + connection. The parameters are: the path for the Unix Domain + Socket (UDS) to be created, the remote host to which to + authenticate, and the SSH port on the remote host. +-u + Path to the Unix Domain Socket (UDS) to which SSH should connect. + The UDS will have been created from the -U option in a previous + SSH command. + + +Example: + In the previous example, two encryption tunnels were created to + connect "student@school" to "calvin@spaceship.world.net". The + tunnels are created by spawning two subprocesses Those two + subprocesses will be described next. + + The first subprocess creates an SSH tunnel from the "student" + account on "school" to the "hobbes" account on "gateway.com". + It runs in the background and prohibits use of stdin, remote + commands, and X11 forwarding (-n -N -x). It also creates a Unix + Domain Socket on the local host to be used for port forwarding + (-U). As noted above, the -U option specifies the three parameters + needed for port forwarding in a comma separated list. The Unix + Domain Socket is created in the user's .ssh directory and its + name contains information about the remote endpoint of the tunnel. + Note that the remote account is calvin@spaceship.world.net and + that the remote port for contacting the SSH daemon is the standard + SSH port, 22. + + ssh -n -N -x \ + -U ~/.ssh/.ssh_184_513_calvin@spaceship.world.net_22,calvin@spaceship.world.net,22 \ + hobbes@gateway.world.com + + The second process creates a tunnel from the "student" account + on "school" to the "calvin" account on "spaceship.world.net" + through the tunnel from "student@school" to "hobbes@gateway.com". + It forces allocation of a pseudo-tty (-t -t) for command line + input and sets the name of the remote host (-o). If the name + of the remote host is not set using the -o option, it will be + shown as localhost since that is where the entry point for the + forwarding port resides. The Unix Domain Socket created by the + first process is used for accessing the remote machine (-u). If + the Unix Domain Socket specified in the -u option does not exist, + the second process will report an error and exit. + + ssh -t -t \ + -u ~/.ssh/.ssh_1034_513_calvin@spaceship.world.net_22 \ + -o HostKeyAlias=spaceship.world.net \ + calvin@localhost + + +Please see the ssh.1 man page for more information. + +============================================================================= +SSH option processing in conjunction with -H option. + +The following table contains a list of SSH options and how those +options are handled in the global and local contexts. In general, +the options are handled the same in both contexts. Keys to the +symbols and terminology precede the table. + +--------------------------------------------------------------------- +KEY: +Column labels: +Option = SSH command line option +Global = options specified before the first -H option +Local = options specified after a single -H option + +Symbols in Global and Local columns: +f = only on first hop +l = only on last hop +y = all applicable hops + +Key to numbers in comment field: +1 - will be inserted by the top level (hopper) process to all but + the last child +2 - will be inserted by the top level (hopper) process to all but + the first child +--------------------------------------------------------------------- + +Option Global Local Comments +------ ------ ----- --------------------------------------------- +a y y +A l y +b f y +c y y +e l y +f l l 1 +g l l +i y y +I y y +k y y +l y y +m y y +n l l 1 +N l l 1 +o y y 2 for -o HostKeyAlias= +p y* y* Apply when port not previously specified. + Order of precedence for port assignment: + 1) first local p + 2) first global p + 3) default SSH port +P y y Deprecated but still supported (use privileged port) +q y y +s l l +t l l +T l l +v y y +x l l 1 +X l l +Y l l +C y y +F y y +L l l +R l l +D l l +U l l 1 +u f f 2 +O l l +M l l +S l l +1 y y +2 y y +4 y y +6 y y +============================================================================= + +TODO: +1) SCP +2) SFTP + +============================================================================= + +Overview of changes to the source files: + +Makefile.in +=========== +Added target for sshhop_test. +Added sshhop.o to dependency list and object list for sshhop_test and ssh. + +README.sshhop +============= +New. This file. + +channels.c +========== +Added function port_open_helper_base(). + +Additional cleanup added for unix domain sockets. + +Function port_open_helper() functions as previously but the bulk of the +code now resides in port_open_helper_base() which is invoked from +port_open_helper() or directly. Port_open_helper_base() is called directly +to omit looking up the ip address and port when Unix Domain Sockets are +in use. + +The original function channel_setup_fwd_listener() is renamed +channel_setup_fwd_port_listener(). + +A new function channel_setup_fwd_uds_listener() is added to handle +processing for Unix Domain Sockets. + +The new channel_setup_fwd_listener() function calls one of two functions: +channel_setup_fwd_port_listener() and channel_setup_fwd_uds_listener() +depending on whether ports or unix domain sockets are being used. + +channels.h +========== +Added fwd_path to Channel structure to support use of Unix Domain Sockets +for port forwarding. + +clientloop.c +============ +Added remote prompt id flag to read_passphrase() call. + +misc.c +====== +Added #include "pathnames.h". Needed for new construct_uds_path() function. + +Added comma (,) as separator for forwarding information. Colon (:) and +slash (/) could both be used in a unix domain socket designator and thus +cannot be used as separators. + +Added function construct_uds_path() used to ensure a consistent naming +convention for Unix Domain Sockets. + +misc.h +====== +Added function declaration for construct_uds_path(). + +Added bit code for remote prompt id flag. + +readconf.c +========== +Added configuration options: + "PrivateLocalForward" (takes a UDS path as the forwarding port) + "UnixDomainSocket" (UnixDomainSocket to which to connect) +Added handling of listen_port value when Unix Domain Socket specified +(i.e., listen_port < 0). + +readconf.h +========== +Revised information on listen_port (to reflect use with Unix Domain Sockets). +Added uds_path configuration parameter. + +readpass.c +========== +Added function add_id_to_prompt() to add remote process id to prompt. +Added conditional to call add_id_to_prompt() if RP_ADD_ID flag is set. + +scard.c +======= +Added remote prompt id flag to read_passphrase() call. + +ssh.1 +===== +Added descriptions of new options (-u -U -H) and new config +parameters (PrivateLocalForward and UnixDomainSocket) + +ssh.c +===== +Declared variables static that are used only in scope of ssh.c +Updated usage information to reflect new usage (-H option). +Moved option parsing from main() into new function parse_options() +(the diff output is somewhat confusing since lines from main() show up +as being deleted from parse_options() and then added to the "new" main()). +Use SSH_CMD_LINE_OPTIONS #define for option list so it can be shared with +code in sshhop.c +Added -u and -U options. +Added check for config option for uds_path variable. +Added call to sshop_parse_options() and additional processing if multiple +hops were specified. Functions to support processing of multiple hop +information are all located in sshhop.c (new). If no -H options (no hop +processing) is indicated then options are parsed and the program continues +as before. +Added tilde expansion check for uds path. +Modified connection logic to handle UDS connection. + +ssh.h +===== +Added SSH_CMDLINE_OPTIONS + +sshconnect.c +============ +Added function ssh_connect_uds() to support using a Unix Domain Socket as +a connection endpoint instead of a regular socket. The Unix Domain Socket +is a protected resource of an individual rather than a resource that may +be accessible to any user on the machine. +Added remote prompt id flag to read_passphrase() call. +Added AF_UNIX processing to check_host_key() to support use of Unix +Domain Sockets for port forwarding: a) new case (AF_UNIX) added to switch +and b) conditional based on the address family added when no proxy command. + +sshconnect.h +============ +Added function declaration for ssh_connect_uds(). + +sshconnect1.c +============ +Added remote prompt id flag to read_passphrase() calls. + +sshconnect2.c +============ +Includes patch supplied by openSSH folks to provide hostname in prompt +(patch is not included in openSSH distribution). +Added remote prompt id flag to read_passphrase() calls. + +sshhop.c +======== +New. Contains command line processing for new -H option. +Contains functions for properly configuring argument lists for multiple +SSH subprocesses, functions for starting the SSH subprocesses, and +functions for terminating the multiple SSH subprocesses. The top-level +control for the multiple hop processing is in ssh.c. + +sshhop.h +======== +New. Function declarations for functions used in sshhop.c + +sshhop_test.c +============= +New. Program to test sshhop.c functionality outside of ssh. diff -N -U 5 -u openssh-4.2p1/channels.c sshop-4.2p1/channels.c --- openssh-4.2p1/channels.c Sun Jul 17 03:22:45 2005 +++ sshop-4.2p1/channels.c Fri Oct 21 16:02:27 2005 @@ -136,10 +136,11 @@ /* AF_UNSPEC or AF_INET or AF_INET6 */ static int IPv4or6 = AF_UNSPEC; /* helper */ static void port_open_helper(Channel *c, char *rtype); +static void port_open_helper_base(Channel *c, char *rtype, char *remote_ipaddr, u_short remote_port); /* -- channel core */ Channel * channel_lookup(int id) @@ -351,10 +352,16 @@ buffer_free(&c->extended); if (c->remote_name) { xfree(c->remote_name); c->remote_name = NULL; } + if ((c->type == SSH_CHANNEL_PORT_LISTENER) && + (c->fwd_path != NULL)) { + unlink(c->fwd_path); + xfree(c->fwd_path); + c->fwd_path = NULL; + } channels[c->self] = NULL; xfree(c); } void @@ -1174,27 +1181,28 @@ } xfree(remote_ipaddr); } } +/* Send port information to the other end of the ssh connection. */ static void -port_open_helper(Channel *c, char *rtype) +port_open_helper_base(Channel *c, char *rtype, char *remote_ipaddr, + u_short remote_port) { int direct; char buf[1024]; - char *remote_ipaddr = get_peer_ipaddr(c->sock); - int remote_port = get_peer_port(c->sock); direct = (strcmp(rtype, "direct-tcpip") == 0); snprintf(buf, sizeof buf, "%s: listening port %d for %.100s port %d, " "connect from %.200s port %d", rtype, c->listening_port, c->path, c->host_port, remote_ipaddr, remote_port); - xfree(c->remote_name); + if (c->remote_name) + xfree(c->remote_name); c->remote_name = xstrdup(buf); if (compat20) { packet_start(SSH2_MSG_CHANNEL_OPEN); packet_put_cstring(rtype); @@ -1222,10 +1230,25 @@ if (packet_get_protocol_flags() & SSH_PROTOFLAG_HOST_IN_FWD_OPEN) packet_put_cstring(c->remote_name); packet_send(); } +} + +/* + * Send port information to the other side of the connection when + * socket used for communication is a regular IP socket (i.e., not + * a Unix Domain Socket). + */ +static void +port_open_helper(Channel *c, char *rtype) +{ + char *remote_ipaddr = get_peer_ipaddr(c->sock); + u_short remote_port = get_peer_port(c->sock); + + port_open_helper_base(c, rtype, remote_ipaddr, remote_port); + xfree(remote_ipaddr); } /* * This socket is listening for connections to a forwarded TCP/IP port. @@ -1236,10 +1259,12 @@ Channel *nc; struct sockaddr addr; int newsock, nextstate; socklen_t addrlen; char *rtype; + char hostname[MAXHOSTNAMELEN]; + struct hostent *he; if (FD_ISSET(c->sock, readset)) { debug("Connection to port %d forwarding " "to %.100s port %d requested.", c->listening_port, c->path, c->host_port); @@ -1276,11 +1301,35 @@ * this flag has been reset by a pre-handler. * otherwise the FD_ISSET calls might overflow */ nc->delayed = 1; } else { - port_open_helper(nc, rtype); + /* If there is no unix domain socket, use the regular + * function to set up the channel. (Gets the IP + * address from the socket connection). + */ + if (c->fwd_path == NULL) { + port_open_helper(nc, rtype); + /* Otherwise, we are using a unix domain socket, so + * use the IP address for the local host. + */ + } else { + nc->fwd_path = xstrdup(c->fwd_path); + memset(hostname, '\0', sizeof(hostname)); + gethostname(hostname, sizeof(hostname)); + /* Get the host IP address */ + if ((he = gethostbyname(hostname)) != 0) { + port_open_helper_base(nc, rtype, + he->h_addr_list[0], 0); + /* Couldn't get the host IP address, use the + * address for localhost. + */ + } else { + port_open_helper_base(nc, rtype, + "127.0.0.1", 0); + } + } } } } /* @@ -2182,11 +2231,11 @@ { IPv4or6 = af; } static int -channel_setup_fwd_listener(int type, const char *listen_addr, u_short listen_port, +channel_setup_fwd_port_listener(int type, const char *listen_addr, u_short listen_port, const char *host_to_connect, u_short port_to_connect, int gateway_ports) { Channel *c; int sock, r, success = 0, on = 1, wildcard = 0, is_client; struct addrinfo hints, *ai, *aitop; @@ -2313,10 +2362,119 @@ } if (success == 0) error("channel_setup_fwd_listener: cannot listen to port: %d", listen_port); freeaddrinfo(aitop); + return success; +} + +/* + * Create and activate the Unix Domain Socket (UDS) to be used for + * forwarding. The name of the UDS is in listen_addr. + */ +static int +channel_setup_fwd_uds_listener(int type, const char *listen_addr, + u_short listen_port, const char *host, u_short port_to_connect) +{ + Channel *c; + struct sockaddr_un addr; + mode_t old_umask; + int sock; + int addr_len; + int success = 0; + const char *uds_path = listen_addr; + struct stat stat_info; + + /* File doesn't exist, create Unix Domain Socket */ + if (uds_path != NULL && stat(uds_path, &stat_info) == -1) { + /* Format the address info */ + memset(&addr, '\0', sizeof(addr)); + addr.sun_family = AF_UNIX; + addr_len = offsetof(struct sockaddr_un, sun_path) + + strlen(uds_path) + 1; + if (strlcpy(addr.sun_path, uds_path, sizeof(addr.sun_path)) + >= sizeof(addr.sun_path)) + fatal("Forwarding Socket path too long"); + + /* Create the socket */ + if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) + fatal("%s socket(): %s\n", __func__, strerror(errno)); + + /* Bind the socket to the address. Note that the + * permissions are set to be fairly restrictive. + */ + old_umask = umask(0177); + if (bind(sock, (struct sockaddr*)&addr, addr_len) == -1) { + sock = -1; + if (errno == EINVAL) + fatal("Forwarding Socket %s already exists", + uds_path); + else + fatal("%s bind(%s): %s\n", __func__, + addr.sun_path, strerror(errno)); + } + umask(old_umask); + + /* Start listening on the socket */ + if (listen(sock, SSH_LISTEN_BACKLOG) == -1) + fatal("%s listen(): %s\n", __func__, strerror(errno)); + + set_nonblock(sock); + + /* Allocate a channel number for the socket. */ + c = channel_new("port listener", type, sock, sock, -1, + CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, + 0, "port listener", 1); + strlcpy(c->path, host, sizeof(c->path)); + c->host_port = port_to_connect; + c->fwd_path = xstrdup(uds_path); + c->listening_port = listen_port; + success = 1; + } + else { + fatal("Forwarding Socket %s already exists", uds_path); + } + + return(success); +} + +/* + * Initiate the proper initialization procedure for setting up port + * forwarding based on the inputs. + */ +static int +channel_setup_fwd_listener(int type, const char *listen_addr, u_short listen_port, + const char *host_to_connect, u_short port_to_connect, int gateway_ports) +{ + int success = 0; + const char *host; + + host = (type == SSH_CHANNEL_RPORT_LISTENER) ? + listen_addr : host_to_connect; + + if (host == NULL) { + error("No forward host name."); + return success; + } + if (strlen(host) > SSH_CHANNEL_PATH_LEN - 1) { + error("Forward host name too long."); + return success; + } + /* If this is a local listener and it is not a regular socket, + * since the port=0, set up a Unix Domain Socket. + */ + if ((type != SSH_CHANNEL_RPORT_LISTENER) && !gateway_ports && + (listen_addr != NULL) && (listen_port == 0)) { + success = channel_setup_fwd_uds_listener(type, listen_addr, + listen_port, host, port_to_connect); + /* Otherwise, set up a regular socket. */ + } else { + success = channel_setup_fwd_port_listener(type, listen_addr, + listen_port, host, port_to_connect, + gateway_ports); + } + return success; } int channel_cancel_rport_listener(const char *host, u_short port) diff -N -U 5 -u openssh-4.2p1/channels.h sshop-4.2p1/channels.h --- openssh-4.2p1/channels.h Sun Jul 17 03:19:25 2005 +++ sshop-4.2p1/channels.h Fri Oct 21 16:02:27 2005 @@ -90,10 +90,12 @@ char path[SSH_CHANNEL_PATH_LEN]; /* path for unix domain sockets, or host name for forwards */ int listening_port; /* port being listened for forwards */ int host_port; /* remote port to connect for forwards */ char *remote_name; /* remote hostname */ + char *fwd_path; + /* path for unix domain sockets used for forwards */ u_int remote_window; u_int remote_maxpacket; u_int local_window; u_int local_window_max; diff -N -U 5 -u openssh-4.2p1/clientloop.c sshop-4.2p1/clientloop.c --- openssh-4.2p1/clientloop.c Sun Jul 17 03:02:10 2005 +++ sshop-4.2p1/clientloop.c Fri Oct 21 16:02:27 2005 @@ -897,11 +897,11 @@ u_short cancel_port; Forward fwd; leave_raw_mode(); handler = signal(SIGINT, SIG_IGN); - cmd = s = read_passphrase("\r\nssh> ", RP_ECHO); + cmd = s = read_passphrase("\r\nssh> ", RP_ECHO | RP_ADD_ID); if (s == NULL) goto out; while (*s && isspace(*s)) s++; if (*s == '-') Common subdirectories: openssh-4.2p1/contrib and sshop-4.2p1/contrib diff -N -U 5 -u openssh-4.2p1/misc.c sshop-4.2p1/misc.c --- openssh-4.2p1/misc.c Thu Jul 14 03:05:02 2005 +++ sshop-4.2p1/misc.c Fri Oct 21 16:02:28 2005 @@ -27,10 +27,11 @@ RCSID("$OpenBSD: misc.c,v 1.34 2005/07/08 09:26:18 dtucker Exp $"); #include "misc.h" #include "log.h" #include "xmalloc.h" +#include "pathnames.h" /* remove newline at end of string */ char * chop(char *s) { @@ -305,10 +306,11 @@ *cp = NULL; /* no more fields*/ break; case ':': case '/': + case ',': *s = '\0'; /* terminate */ *cp = s + 1; break; default: @@ -519,7 +521,86 @@ for (i = 0; i < l; i++) { snprintf(b, sizeof(b), "%02x", d[i]); strlcat(r, b, hl); } return (r); +} + +/* + * Given information about the local side and remote side of the connection, + * construct a path for a UNIX Domain Socket to be used for the communication. + * If no directory is specified, the socket path will start in the user's + * .ssh directory. The path has the structure: + * /.ssh___@_ + * /.ssh____ + * The second variant is only used if the user can't be established. + * Note that for Unix Domain Sockets, buffer should be set to + * &sockadder_un->sun_path to prevent problems later. + * + * The result is placed into buffer. Upon success the length of the path + * name is returned. Otherwise, -1 is returned. + */ +int +construct_uds_path(const char *dirpath, const char *host, u_short uniqueid, + char **host_user, u_short host_port, char *buffer, int buffer_size) +{ + uid_t uid; + struct passwd *pw = NULL; + char *local_user = NULL, + *remote_user = NULL; + char *buf = buffer; + int len = 0; + + /* Problem with the inputs */ + if ((buffer == NULL) || (buffer_size < 32) || (host == NULL)) + return -1; + + /* Get the identity of the local user */ + uid = getuid(); + + /* Get the password entry for this user */ + pw = getpwuid(uid); + + /* Identify the remote user */ + if ((host_user != NULL) && (*host_user != NULL)) { + remote_user = *host_user; + /* No remote user specified, assume same as local user */ + } else if (pw) { + *host_user = xstrdup(pw->pw_name); + remote_user = *host_user; + } + + /* Construct a path name */ + /* First determine the directory it should be in */ + memset(buffer, '\0', buffer_size); + if (dirpath != NULL) { + snprintf(buffer, buffer_size, "%.100s", dirpath); + } else if (pw) { + snprintf(buffer, buffer_size, "%.100s%s%.100s", + pw->pw_dir, + strcmp(pw->pw_dir, "/") ? "/" : "", _PATH_SSH_USER_DIR); + } else { + fatal("construct_uds_path: no uds directory specified and can't get local user name"); + /* + * Another option would be to use /tmp, but could have + * problems with protecting access to the socket file + * so we won't do that. + */ + } + /* Then construct the name */ + len = strlen(buffer); + buf = buffer + len; + buffer_size -= len; + if (buffer_size < 12) { + fatal("construct_uds_path: buffer too small for pathname"); + } else if (remote_user != NULL) { + snprintf(buf, buffer_size, "/.ssh_%d_%d_%s@%s_%d", + uniqueid, uid, remote_user, host, host_port); + } else { + snprintf(buf, buffer_size, "/.ssh_%d_%d_%s_%d", + uniqueid, uid, host, host_port); + } + + endpwent(); + return strlen(buffer); } diff -N -U 5 -u openssh-4.2p1/misc.h sshop-4.2p1/misc.h --- openssh-4.2p1/misc.h Thu Jul 14 03:07:21 2005 +++ sshop-4.2p1/misc.h Fri Oct 21 16:02:28 2005 @@ -25,10 +25,12 @@ char *colon(char *); long convtime(const char *); char *tilde_expand_filename(const char *, uid_t); char *percent_expand(const char *, ...) __attribute__((__sentinel__)); char *tohex(const u_char *, u_int); +int construct_uds_path(const char *, const char *, u_short, + char **, u_short, char *, int); struct passwd *pwcopy(struct passwd *); typedef struct arglist arglist; struct arglist { @@ -42,9 +44,10 @@ #define RP_ECHO 0x0001 #define RP_ALLOW_STDIN 0x0002 #define RP_ALLOW_EOF 0x0004 #define RP_USE_ASKPASS 0x0008 +#define RP_ADD_ID 0x0010 char *read_passphrase(const char *, int); int ask_permission(const char *, ...) __attribute__((format(printf, 1, 2))); int read_keyfile_line(FILE *, const char *, char *, size_t, u_long *); Common subdirectories: openssh-4.2p1/openbsd-compat and sshop-4.2p1/openbsd-compat diff -N -U 5 -u openssh-4.2p1/readconf.c sshop-4.2p1/readconf.c --- openssh-4.2p1/readconf.c Fri Aug 12 08:11:18 2005 +++ sshop-4.2p1/readconf.c Fri Oct 21 16:02:29 2005 @@ -105,10 +105,11 @@ oClearAllForwardings, oNoHostAuthenticationForLocalhost, oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout, oAddressFamily, oGssAuthentication, oGssDelegateCreds, oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly, oSendEnv, oControlPath, oControlMaster, oHashKnownHosts, + oPrivateLocalForward, oUnixDomainSocket, oDeprecated, oUnsupported } OpCodes; /* Textual representations of the tokens. */ @@ -196,10 +197,12 @@ { "serveralivecountmax", oServerAliveCountMax }, { "sendenv", oSendEnv }, { "controlpath", oControlPath }, { "controlmaster", oControlMaster }, { "hashknownhosts", oHashKnownHosts }, + { "privatelocalforward", oPrivateLocalForward }, + { "unixdomainsocket", oUnixDomainSocket }, { NULL, oBadOption } }; /* * Adds a local TCP/IP port forward to options. Never returns if there is an @@ -210,11 +213,12 @@ add_local_forward(Options *options, const Forward *newfwd) { Forward *fwd; #ifndef NO_IPPORT_RESERVED_CONCEPT extern uid_t original_real_uid; - if (newfwd->listen_port < IPPORT_RESERVED && original_real_uid != 0) + if ((newfwd->listen_port > 0) && + (newfwd->listen_port < IPPORT_RESERVED) && original_real_uid != 0) fatal("Privileged ports can only be forwarded by root."); #endif if (options->num_local_forwards >= SSH_MAX_FORWARDS_PER_DIRECTION) fatal("Too many local forwards (max %d).", SSH_MAX_FORWARDS_PER_DIRECTION); fwd = &options->local_forwards[options->num_local_forwards++]; @@ -294,10 +298,13 @@ process_config_line(Options *options, const char *host, char *line, const char *filename, int linenum, int *activep) { char *s, **charptr, *endofnumber, *keyword, *arg, *arg2, fwdarg[256]; + u_short fwd_host_port; + char sfwd_host_port[6]; + char sfwd_uds_path[1024]; int opcode, *intptr, value; size_t len; Forward fwd; /* Strip trailing whitespace */ @@ -702,10 +709,34 @@ filename, linenum); if (*activep) add_local_forward(options, &fwd); break; + case oPrivateLocalForward: + arg = strdelim(&s); + memset(&fwd, '\0', sizeof(fwd)); + /* parse the input */ + if (sscanf(arg, "%1023[^,],%255[^,],%5[0123456789]", + sfwd_uds_path, fwdarg, sfwd_host_port) < 3) { + fatal("%.200s line %d: Bad domain socket specification.", + filename, linenum); + } + + /* check the port info */ + if ((fwd_host_port = a2port(sfwd_host_port)) == 0) { + fatal("%.200s line %d: Remote port not defined.", + filename, linenum); + } + fwd.listen_host = xstrdup(sfwd_uds_path); + fwd.listen_port = 0; + fwd.connect_host = xstrdup(fwdarg); + fwd.connect_host = cleanhostname(fwd.connect_host); + fwd.connect_port = fwd_host_port; + if (*activep) + add_local_forward(options, &fwd); + break; + case oClearAllForwardings: intptr = &options->clear_forwardings; goto parse_flag; case oHost: @@ -820,10 +851,14 @@ case oHashKnownHosts: intptr = &options->hash_known_hosts; goto parse_flag; + case oUnixDomainSocket: + charptr = &options->uds_path; + goto parse_string; + case oDeprecated: debug("%s line %d: Deprecated option \"%s\"", filename, linenum, keyword); return 0; @@ -964,10 +999,11 @@ options->server_alive_count_max = -1; options->num_send_env = 0; options->control_path = NULL; options->control_master = -1; options->hash_known_hosts = -1; + options->uds_path = NULL; } /* * Called after processing other sources of option data, this fills those * options for which no value has been specified with their default values. @@ -1093,10 +1129,11 @@ /* options->proxy_command should not be set by default */ /* options->user will be set in the main program if appropriate */ /* options->hostname will be set in the main program if appropriate */ /* options->host_key_alias should not be set by default */ /* options->preferred_authentications will be set in ssh */ + /* options->uds_path should not be set by default */ } /* * parse_forward * parses a string containing a port forwarding specification of the form: diff -N -U 5 -u openssh-4.2p1/readconf.h sshop-4.2p1/readconf.h --- openssh-4.2p1/readconf.h Wed Jun 15 23:19:42 2005 +++ sshop-4.2p1/readconf.h Fri Oct 21 16:02:29 2005 @@ -20,11 +20,12 @@ /* Data structure for representing a forwarding request. */ typedef struct { char *listen_host; /* Host (address) to listen on. */ - u_short listen_port; /* Port to forward. */ + u_short listen_port; /* Port to forward. if 0, listen_host + * is a unix domain socket. */ char *connect_host; /* Host to connect. */ u_short connect_port; /* Port to connect on connect_host. */ } Forward; /* Data structure for representing option data. */ @@ -112,10 +113,13 @@ char *control_path; int control_master; int hash_known_hosts; + + char *uds_path; /* Path for unix domain socket to connect to + * (used in place of port). */ } Options; #define SSHCTL_MASTER_NO 0 #define SSHCTL_MASTER_YES 1 #define SSHCTL_MASTER_AUTO 2 diff -N -U 5 -u openssh-4.2p1/readpass.c sshop-4.2p1/readpass.c --- openssh-4.2p1/readpass.c Wed May 25 22:07:48 2005 +++ sshop-4.2p1/readpass.c Fri Oct 21 16:02:29 2005 @@ -89,20 +89,76 @@ memset(buf, 0, sizeof(buf)); return pass; } /* + * Function to prepend the process id to every line in a prompt. + */ +void +add_id_to_prompt(const char *prompt, char *newprompt, int newprompt_size) +{ + char *np; + const char *p = prompt; + int len, cnt; + int pid = getpid(); + + if (newprompt == NULL) + return; + + np = newprompt; + len = newprompt_size; + + while (p != NULL) { + cnt = snprintf(np, len, "[%d] ", pid); + + /* Able to write the entire label */ + if (cnt < len) { + len -= cnt; + np += cnt; + /* Out of space */ + } else { + np += len; + len = 0; + } + /* Copy characters until out of space, or an + * EOL or newline is found. + */ + for (; *p != '\0' && *p != '\n' && len > 0; + p++, np++, len--) { + *np = *p; + } + /* Out of space */ + if (len == 0) { + *(np-1) = '\0'; + p = NULL; + /* Copy the EOL or newline. */ + } else { + *np = *p; + /* EOL, terminate the loop */ + if (*p == '\0') { + p = NULL; + /* Otherwise, move to next character */ + } else { + p++; + np++; + } + } + } +} + +/* * Reads a passphrase from /dev/tty with echo turned off/on. Returns the * passphrase (allocated with xmalloc). Exits if EOF is encountered. If * RP_ALLOW_STDIN is set, the passphrase will be read from stdin if no * tty is available */ char * read_passphrase(const char *prompt, int flags) { char *askpass = NULL, *ret, buf[1024]; - int rppflags, use_askpass = 0, ttyfd; + int rppflags, use_askpass = 0, ttyfd, len; + char newprompt[1024]; rppflags = (flags & RP_ECHO) ? RPP_ECHO_ON : RPP_ECHO_OFF; if (flags & RP_USE_ASKPASS) use_askpass = 1; else if (flags & RP_ALLOW_STDIN) { @@ -119,26 +175,35 @@ debug("read_passphrase: can't open %s: %s", _PATH_TTY, strerror(errno)); use_askpass = 1; } } + /* Prepend the process id to each line in the prompt */ + if ((flags & RP_ADD_ID)) { + add_id_to_prompt(prompt, newprompt, sizeof(newprompt)); + /* Nothing to be added to the prompt, use the original */ + } else { + len = sizeof(newprompt); + strncpy(newprompt, prompt, len); + newprompt[len-1] = '\0'; + } if ((flags & RP_USE_ASKPASS) && getenv("DISPLAY") == NULL) return (flags & RP_ALLOW_EOF) ? NULL : xstrdup(""); if (use_askpass && getenv("DISPLAY")) { if (getenv(SSH_ASKPASS_ENV)) askpass = getenv(SSH_ASKPASS_ENV); else askpass = _PATH_SSH_ASKPASS_DEFAULT; - if ((ret = ssh_askpass(askpass, prompt)) == NULL) + if ((ret = ssh_askpass(askpass, newprompt)) == NULL) if (!(flags & RP_ALLOW_EOF)) return xstrdup(""); return ret; } - if (readpassphrase(prompt, buf, sizeof buf, rppflags) == NULL) { + if (readpassphrase(newprompt, buf, sizeof buf, rppflags) == NULL) { if (flags & RP_ALLOW_EOF) return NULL; return xstrdup(""); } Common subdirectories: openssh-4.2p1/regress and sshop-4.2p1/regress Common subdirectories: openssh-4.2p1/scard and sshop-4.2p1/scard diff -N -U 5 -u openssh-4.2p1/scard.c sshop-4.2p1/scard.c --- openssh-4.2p1/scard.c Thu May 13 02:15:48 2004 +++ sshop-4.2p1/scard.c Wed May 11 17:54:01 2005 @@ -429,11 +429,11 @@ static int get_AUT0(u_char *aut0) { char *pass; - pass = read_passphrase("Enter passphrase for smartcard: ", RP_ALLOW_STDIN); + pass = read_passphrase("Enter passphrase for smartcard: ", RP_ALLOW_STDIN|RP_ADD_ID); if (pass == NULL) return -1; if (!strcmp(pass, "-")) { memcpy(aut0, DEFAUT0, sizeof DEFAUT0); return 0; diff -N -U 5 -u openssh-4.2p1/ssh.1 sshop-4.2p1/ssh.1 --- openssh-4.2p1/ssh.1 Thu Jul 14 03:04:18 2005 +++ sshop-4.2p1/ssh.1 Fri Oct 21 16:02:31 2005 @@ -66,13 +66,25 @@ .Sm off .Oo Ar bind_address : Oc .Ar port : host : hostport .Sm on .Oc +.Op Fl u Ar socket_path +.Oo Fl U\ \& +.Sm off +.Ar socket_path : host : hostport +.Sm on +.Oc .Op Fl S Ar ctl_path .Oo Ar user Ns @ Oc Ns Ar hostname .Op Ar command +.Nm ssh +.Op Ar ssh_options +.Op Fl H Oo Ar ssh_options Oc Oo Ar user Ns @ Oc Ns Ar remote_host +.Op Ar ... +.Fl H Oo Ar ssh_options Oc Oo Ar user Ns @ Oc Ns Ar remote_host +.Op Ar command .Ek .Sh DESCRIPTION .Nm (SSH client) is a program for logging into a remote machine and for executing commands on a remote machine. @@ -542,10 +554,20 @@ The recommended way to start X11 programs at a remote site is with something like .Ic ssh -f host xterm . .It Fl g Allows remote hosts to connect to local forwarded ports. +.It Fl H Xo +.Oo Ar ssh_options Oc +.Oo Ar user Ns @ Oc Ns Ar remote_host +.Xc +Specifies a remote host in a SSH hop chain. +SSH options specific to that host can be provided following the H. +For example, +.Fl p +can be used to specify a non-standard SSH port number +on the remote host. .It Fl I Ar smartcard_device Specifies which smartcard device to use. The argument is the device .Nm should use to communicate with a smartcard used for storing the user's @@ -716,10 +738,11 @@ .It NumberOfPasswordPrompts .It PasswordAuthentication .It Port .It PreferredAuthentications .It Protocol +.It PrivateLocalForward .It ProxyCommand .It PubkeyAuthentication .It RemoteForward .It RhostsRSAAuthentication .It RSAAuthentication @@ -727,10 +750,11 @@ .It ServerAliveInterval .It ServerAliveCountMax .It SmartcardDevice .It StrictHostKeyChecking .It TCPKeepAlive +.It UnixDomainSocket .It UsePrivilegedPort .It User .It UserKnownHostsFile .It VerifyHostKeyDNS .It XAuthLocation @@ -813,10 +837,30 @@ Multiple .Fl t options force tty allocation, even if .Nm has no local tty. +.It Fl U Xo +.Sm off +.Ar socket_path , host , hostport +.Sm on +.Xc +Specifies that the given Unix Domain Socket on the local (client) host is to be +forwarded to the given host and port on the remote side. +This works by allocating a socket to listen to +.Ar socket_path +on the local side +Whenever a connection is made to this socket, the +connection is forwarded over the secure channel, and a connection is +made to +.Ar host +port +.Ar hostport +from the remote machine. +These forwardings can also be specified in the configuration file. +.It Fl u Ar socket_path +Path to a Unix Domain Socket being used for local forwarding. .It Fl V Display the version number and exit. .It Fl v Verbose mode. Causes diff -N -U 5 -u openssh-4.2p1/ssh.c sshop-4.2p1/ssh.c --- openssh-4.2p1/ssh.c Fri Aug 12 08:10:56 2005 +++ sshop-4.2p1/ssh.c Fri Oct 21 16:02:31 2005 @@ -70,180 +70,147 @@ #include "sshpty.h" #include "match.h" #include "msg.h" #include "monitor_fdpass.h" #include "uidswap.h" +#include "sshhop.h" #ifdef SMARTCARD #include "scard.h" #endif extern char *__progname; -/* Flag indicating whether debug mode is on. This can be set on the command line. */ +/* Flag indicating whether debug mode is on. This can be set on the + * command line. This variable is exported (global). + */ int debug_flag = 0; /* Flag indicating whether a tty should be allocated */ -int tty_flag = 0; -int no_tty_flag = 0; -int force_tty_flag = 0; +static int tty_flag = 0; +static int no_tty_flag = 0; +static int force_tty_flag = 0; -/* don't exec a shell */ +/* don't exec a shell. This is an exported variable (global). */ int no_shell_flag = 0; /* * Flag indicating that nothing should be read from stdin. This can be set - * on the command line. + * on the command line. This variable is exported (global). */ int stdin_null_flag = 0; /* * Flag indicating that ssh should fork after authentication. This is useful * so that the passphrase can be entered manually, and then ssh goes to the * background. */ -int fork_after_authentication_flag = 0; +static int fork_after_authentication_flag = 0; /* * General data structure for command line options and options configurable - * in configuration files. See readconf.h. + * in configuration files. See readconf.h. This variable is exported + * (global). */ Options options; /* optional user configfile */ -char *config = NULL; +static char *config = NULL; /* * Name of the host we are connecting to. This is the name given on the * command line, or the HostName specified for the user-supplied name in a - * configuration file. + * configuration file. This variable is exported (global). */ char *host; /* socket address the host resolves to */ -struct sockaddr_storage hostaddr; +static struct sockaddr_storage hostaddr; /* Private host keys. */ -Sensitive sensitive_data; +static Sensitive sensitive_data; -/* Original real UID. */ +/* Original real UID. These variables are exported (global) */ uid_t original_real_uid; uid_t original_effective_uid; /* command to be executed */ -Buffer command; +static Buffer command; /* Should we execute a command or invoke a subsystem? */ -int subsystem_flag = 0; +static int subsystem_flag = 0; /* # of replies received for global requests */ static int client_global_request_id = 0; -/* pid of proxycommand child process */ +/* pid of proxycommand child process. This variable is exported (global) */ pid_t proxy_command_pid = 0; -/* fd to control socket */ +/* fd to control socket. This variable is exported (global). */ int control_fd = -1; /* Multiplexing control command */ static u_int mux_command = 0; /* Only used in control client mode */ volatile sig_atomic_t control_client_terminate = 0; -u_int control_server_pid = 0; +static u_int control_server_pid = 0; /* Prints a help message to the user. This function never returns. */ - static void usage(void) { fprintf(stderr, + /* Standard SSH command line syntax */ "usage: ssh [-1246AaCfgkMNnqsTtVvXxY] [-b bind_address] [-c cipher_spec]\n" " [-D port] [-e escape_char] [-F configfile]\n" " [-i identity_file] [-L [bind_address:]port:host:hostport]\n" " [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]\n" " [-R [bind_address:]port:host:hostport] [-S ctl_path]\n" " [user@]hostname [command]\n" +" or\n" + /* SSH hop command line syntax. + * Command line syntax for automatically creating a chain of SSH + * processes to securely SSH from this host to the destination host + * through intermediary hosts. Each host in this chain represents a + * single "hop". The authentication credentials are only revealed + * at this host and at the host for which those credentials are + * intended. + */ +" ssh [ssh_options] -H [ssh_options] [-p port1] [user1@]host1\n" +" ... -H [ssh_options] [-p portn] [usern@]hostn\n" +" [command]\n" ); exit(1); } static int ssh_session(void); static int ssh_session2(void); static void load_public_identity_files(void); static void control_client(const char *path); -/* - * Main program for the ssh client. - */ int -main(int ac, char **av) +parse_options(int ac, char **av) { - int i, opt, exit_status; + int opt; + u_short fwd_host_port; + char sfwd_host_port[6]; + char sfwd_uds_path[1024]; char *p, *cp, *line, buf[256]; struct stat st; - struct passwd *pw; int dummy; extern int optind, optreset; extern char *optarg; - struct servent *sp; Forward fwd; - - __progname = ssh_get_progname(av[0]); - init_rng(); - - /* - * Save the original real uid. It will be needed later (uid-swapping - * may clobber the real uid). - */ - original_real_uid = getuid(); - original_effective_uid = geteuid(); - - /* - * Use uid-swapping to give up root privileges for the duration of - * option processing. We will re-instantiate the rights when we are - * ready to create the privileged port, and will permanently drop - * them when the port has been created (actually, when the connection - * has been made, as we may need to create the port several times). - */ - PRIV_END; - -#ifdef HAVE_SETRLIMIT - /* If we are installed setuid root be careful to not drop core. */ - if (original_real_uid != original_effective_uid) { - struct rlimit rlim; - rlim.rlim_cur = rlim.rlim_max = 0; - if (setrlimit(RLIMIT_CORE, &rlim) < 0) - fatal("setrlimit failed: %.100s", strerror(errno)); - } -#endif - /* Get user data. */ - pw = getpwuid(original_real_uid); - if (!pw) { - logit("You don't exist, go away!"); - exit(1); - } - /* Take a copy of the returned structure. */ - pw = pwcopy(pw); - - /* - * Set our umask to something reasonable, as some files are created - * with the default umask. This will make them world-readable but - * writable only by the owner, which is ok for all files for which we - * don't set the modes explicitly. - */ - umask(022); - - /* Initialize option structure to indicate that no values have been set. */ - initialize_options(&options); + optind = 1; + optreset = 1; /* Parse command-line arguments. */ host = NULL; again: - while ((opt = getopt(ac, av, - "1246ab:c:e:fgi:kl:m:no:p:qstvxACD:F:I:L:MNO:PR:S:TVXY")) != -1) { + while ((opt = getopt(ac, av, SSH_CMDLINE_OPTIONS)) != -1) { switch (opt) { case '1': options.protocol = SSH_PROTO_1; break; case '2': @@ -481,10 +448,41 @@ options.bind_address = optarg; break; case 'F': config = optarg; break; + case 'u': + if (options.uds_path != NULL) { + free(options.uds_path); + } + options.uds_path = xstrdup(optarg); + break; + case 'U': + memset(&fwd, '\0', sizeof(fwd)); + /* parse the input */ + if (sscanf(optarg, "%1023[^,],%255[^,],%5[0123456789]", + sfwd_uds_path, buf, sfwd_host_port) < 3) { + fprintf(stderr, + "Bad domain socket specification '%s'\n", + optarg); + usage(); + /* NOTREACHED */ + } + + /* check the port info */ + if ((fwd_host_port = a2port(sfwd_host_port)) == 0) { + fprintf(stderr, + "Remote port not defined.\n"); + usage(); + } + fwd.listen_host = xstrdup(sfwd_uds_path); + fwd.listen_port = 0; + fwd.connect_host = xstrdup(buf); + fwd.connect_host = cleanhostname(fwd.connect_host); + fwd.connect_port = fwd_host_port; + add_local_forward(&options, &fwd); + break; default: usage(); } } @@ -511,10 +509,146 @@ /* Check that we got a host name. */ if (!host) usage(); + /* Check for consistency between uds_path and host name */ + if (options.uds_path != NULL && strcmp(host, "localhost") != 0) { + char hostname[MAXHOSTNAMELEN]; + memset(hostname, '\0', sizeof(hostname)); + if (gethostname(hostname, sizeof(hostname)) != 0 || + strcmp(host, hostname) != 0) { + fprintf(stderr, + "Can't use Unix Domain Sockets on a remote host\n"); + exit(1); + } + } + return(ac); +} + +/* + * Main program for the ssh client. + */ +int +main(int ac, char **av) +{ + int i, exit_status, hops; + char *p, *cp, buf[256]; + struct stat st; + struct passwd *pw; + struct servent *sp; + + __progname = ssh_get_progname(av[0]); + init_rng(); + + /* + * Save the original real uid. It will be needed later (uid-swapping + * may clobber the real uid). + */ + original_real_uid = getuid(); + original_effective_uid = geteuid(); + + /* + * Use uid-swapping to give up root privileges for the duration of + * option processing. We will re-instantiate the rights when we are + * ready to create the privileged port, and will permanently drop + * them when the port has been created (actually, when the connection + * has been made, as we may need to create the port several times). + */ + PRIV_END; + +#ifdef HAVE_SETRLIMIT + /* If we are installed setuid root be careful to not drop core. */ + if (original_real_uid != original_effective_uid) { + struct rlimit rlim; + rlim.rlim_cur = rlim.rlim_max = 0; + if (setrlimit(RLIMIT_CORE, &rlim) < 0) + fatal("setrlimit failed: %.100s", strerror(errno)); + } +#endif + /* Get user data. */ + pw = getpwuid(original_real_uid); + if (!pw) { + logit("You don't exist, go away!"); + exit(1); + } + /* Take a copy of the returned structure. */ + pw = pwcopy(pw); + + /* + * Set our umask to something reasonable, as some files are created + * with the default umask. This will make them world-readable but + * writable only by the owner, which is ok for all files for which we + * don't set the modes explicitly. + */ + umask(022); + + /* Initialize option structure to indicate that no values have been set. */ + initialize_options(&options); + + /* Check for hops indicated with the -H option on the command line. + * Each hop is a connection from this host to the next host in + * the chain of hosts. The chain extends from this host to the + * final destination through a string of intermediary hosts. + */ + hops = sshop_parse_options(ac, av, usage); + + /* If either no -H options or only one -H option then no hopping + * is needed. Free any memory allocated during hop processing and + * then continue with the standard processing. + */ + if (hops <= 1) { + sshop_cleanup(); + /* Continue below */ + } else { + /* + * Otherwise, multiple -H options (hops) indicated; process the hops. + * Since a separate ssh process is invoked for each hop, we will + * exit when hop processing is completed and not continue with the + * standard ssh session processing. + */ + int error; + /* + * Initialize "log" output. Since we are the client, all output + * actually goes to stderr. + */ + log_init(av[0], + options.log_level == -1 ? SYSLOG_LEVEL_INFO : options.log_level, + SYSLOG_FACILITY_USER, 1); + + /* Make sure the user name is set for all hops */ + sshop_set_default_user(pw->pw_name); + + /* Build the chain of SSH subprocesses to implement the + * secured connection from one host to another through + * intermediate hosts. This involves processing command + * line options and invoking SSH for each hop. + * None of the processes in this chain are shared with other + * hop chains. Thus, the processes will all need to be + * terminated when the session ends. */ + error = sshop_build_chain(av[0]); + + /* Hop chain established successfully, wait to clean up + * intermediate processes when session is completed. + */ + if (error == 0) { + sshop_wait_for_exit(); + } else { + /* The SSH subprocesses were not started properly, shut + * everything down. + */ + sshop_kill_subprocesses(); + sshop_wait_for_exit(); + } + /* Make sure we don't fall into regular SSH processing */ + exit(0); + /* Exit here */ + } + + /* Parse command-line arguments. */ + ac = parse_options(ac, av); + SSLeay_add_all_algorithms(); ERR_load_crypto_strings(); /* Initialize the command to execute on remote host. */ buffer_init(&command); @@ -614,10 +748,16 @@ } if (options.proxy_command != NULL && strcmp(options.proxy_command, "none") == 0) options.proxy_command = NULL; + + if (options.uds_path != NULL) { + options.uds_path = tilde_expand_filename( + options.uds_path, original_real_uid); + } + if (options.control_path != NULL && strcmp(options.control_path, "none") == 0) options.control_path = NULL; if (options.control_path != NULL) { @@ -631,20 +771,28 @@ if (mux_command != 0 && options.control_path == NULL) fatal("No ControlPath specified for \"-O\" command"); if (options.control_path != NULL) control_client(options.control_path); - /* Open a connection to the remote host. */ - if (ssh_connect(host, &hostaddr, options.port, - options.address_family, options.connection_attempts, + /* Open a connection to the remote host. We need to use different + * connection algorithms, depending on whether we are using regular + * sockets or Unix Domain Sockets (UDS). + */ + if (options.uds_path == NULL) { + if (ssh_connect(host, &hostaddr, options.port, + options.address_family, options.connection_attempts, #ifdef HAVE_CYGWIN - options.use_privileged_port, + options.use_privileged_port, #else - original_effective_uid == 0 && options.use_privileged_port, + original_effective_uid == 0 && options.use_privileged_port, #endif - options.proxy_command) != 0) - exit(1); + options.proxy_command) != 0) + exit(1); + } else { + if(ssh_connect_uds(options.uds_path, &hostaddr) != 0) + exit(1); + } /* * If we successfully made the connection, load the host private key * in case we will need it later for combined rsa-rhosts * authentication. This must be done before releasing extra diff -N -U 5 -u openssh-4.2p1/ssh.h sshop-4.2p1/ssh.h --- openssh-4.2p1/ssh.h Mon Dec 6 06:47:42 2004 +++ sshop-4.2p1/ssh.h Tue May 10 14:36:17 2005 @@ -111,6 +111,7 @@ #define SSH_RSA_MINIMUM_MODULUS_SIZE 768 /* Listen backlog for sshd, ssh-agent and forwarding sockets */ #define SSH_LISTEN_BACKLOG 128 +#define SSH_CMDLINE_OPTIONS "1246ab:c:e:fgi:kl:m:no:p:qstu:vxACD:F:HI:L:MNO:PR:S:TU:VXY" #endif /* SSH_H */ diff -N -U 5 -u openssh-4.2p1/sshconnect.c sshop-4.2p1/sshconnect.c --- openssh-4.2p1/sshconnect.c Sun Jul 17 03:22:46 2005 +++ sshop-4.2p1/sshconnect.c Fri Oct 21 16:02:31 2005 @@ -383,10 +383,47 @@ packet_set_connection(sock, sock); return 0; } +int +ssh_connect_uds(const char *uds_path, struct sockaddr_storage * hostaddr) +{ + struct sockaddr_un addr; + int sock, addr_len; + + if (uds_path == NULL) + return -1; + else if (strlen(uds_path) == 0) + return -1; + + memset(&addr, '\0', sizeof(addr)); + addr.sun_family = AF_UNIX; + addr_len = offsetof(struct sockaddr_un, sun_path) + + strlen(uds_path) + 1; + + if (strlcpy(addr.sun_path, uds_path, + sizeof(addr.sun_path)) >= sizeof(addr.sun_path)) + fatal("Path for socket too long"); + + if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) + fatal("%s socket(): %s", __func__, strerror(errno)); + + /* Return failure if we didn't get a successful connection. */ + if (connect(sock, (struct sockaddr*)&addr, addr_len) == -1) { + logit("ssh: Couldn't connect to %s: %s", uds_path, strerror(errno)); + return errno; + } + + memcpy(hostaddr, (void *)&addr, addr_len); + + /* Set the connection. */ + packet_set_connection(sock, sock); + + return 0; +} + /* * Waits for the server identification string, and sends our own * identification string. */ static void @@ -498,11 +535,11 @@ int ret = -1; if (options.batch_mode) return 0; for (msg = prompt;;msg = again) { - p = read_passphrase(msg, RP_ECHO); + p = read_passphrase(msg, RP_ECHO|RP_ADD_ID); if (p == NULL || (p[0] == '\0') || (p[0] == '\n') || strncasecmp(p, "no", 2) == 0) ret = 0; if (p && strncasecmp(p, "yes", 3) == 0) @@ -543,10 +580,13 @@ * essentially disables host authentication for localhost; however, * this is probably not a real problem. */ /** hostaddr == 0! */ switch (hostaddr->sa_family) { + case AF_UNIX: + local = 1; + break; case AF_INET: local = (ntohl(((struct sockaddr_in *)hostaddr)-> sin_addr.s_addr) >> 24) == IN_LOOPBACKNET; salen = sizeof(struct sockaddr_in); break; @@ -570,14 +610,24 @@ /* * We don't have the remote ip-address for connections * using a proxy command */ if (options.proxy_command == NULL) { - if (getnameinfo(hostaddr, salen, ntop, sizeof(ntop), - NULL, 0, NI_NUMERICHOST) != 0) - fatal("check_host_key: getnameinfo failed"); - ip = xstrdup(ntop); + /* Will work for the IP address families only */ + if ((hostaddr->sa_family == AF_INET) || + (hostaddr->sa_family == AF_INET6)) { + if (getnameinfo(hostaddr, salen, ntop, sizeof(ntop), + NULL, 0, NI_NUMERICHOST) != 0) + fatal("check_host_key: getnameinfo failed"); + ip = xstrdup(ntop); + /* Unix Domain Socket is local so we won't check IP */ + } else if (hostaddr->sa_family == AF_UNIX) { + ip = xstrdup(""); + /* Unsupported address family */ + } else { + fatal("check_host_key: can't get name info"); + } } else { ip = xstrdup(""); } /* * Turn off check_host_ip if the connection is to localhost, via proxy diff -N -U 5 -u openssh-4.2p1/sshconnect.h sshop-4.2p1/sshconnect.h --- openssh-4.2p1/sshconnect.h Thu Jun 20 20:41:53 2002 +++ sshop-4.2p1/sshconnect.h Tue May 3 17:45:23 2005 @@ -34,10 +34,12 @@ }; int ssh_connect(const char *, struct sockaddr_storage *, u_short, int, int, int, const char *); +int +ssh_connect_uds(const char *, struct sockaddr_storage *); void ssh_login(Sensitive *, const char *, struct sockaddr *, struct passwd *); int verify_host_key(char *, struct sockaddr *, Key *); diff -N -U 5 -u openssh-4.2p1/sshconnect1.c sshop-4.2p1/sshconnect1.c --- openssh-4.2p1/sshconnect1.c Thu Jun 16 22:59:35 2005 +++ sshop-4.2p1/sshconnect1.c Fri Oct 21 16:02:31 2005 @@ -246,11 +246,11 @@ private = key_load_private_type(KEY_RSA1, authfile, "", NULL); if (private == NULL && !options.batch_mode) { snprintf(buf, sizeof(buf), "Enter passphrase for RSA key '%.100s': ", comment); for (i = 0; i < options.number_of_password_prompts; i++) { - passphrase = read_passphrase(buf, 0); + passphrase = read_passphrase(buf, RP_ADD_ID); if (strcmp(passphrase, "") != 0) { private = key_load_private_type(KEY_RSA1, authfile, passphrase, NULL); quit = 0; } else { @@ -404,11 +404,11 @@ if (i != 0) error("Permission denied, please try again."); if (options.cipher == SSH_CIPHER_NONE) logit("WARNING: Encryption is disabled! " "Response will be transmitted in clear text."); - response = read_passphrase(prompt, 0); + response = read_passphrase(prompt, RP_ADD_ID); if (strcmp(response, "") == 0) { xfree(response); break; } packet_start(SSH_CMSG_AUTH_TIS_RESPONSE); @@ -441,11 +441,11 @@ if (options.cipher == SSH_CIPHER_NONE) logit("WARNING: Encryption is disabled! Password will be transmitted in clear text."); for (i = 0; i < options.number_of_password_prompts; i++) { if (i != 0) error("Permission denied, please try again."); - password = read_passphrase(prompt, 0); + password = read_passphrase(prompt, RP_ADD_ID); packet_start(SSH_CMSG_AUTH_PASSWORD); ssh_put_password(password); memset(password, 0, strlen(password)); xfree(password); packet_send(); diff -N -U 5 -u openssh-4.2p1/sshconnect2.c sshop-4.2p1/sshconnect2.c --- openssh-4.2p1/sshconnect2.c Wed Aug 31 05:46:27 2005 +++ sshop-4.2p1/sshconnect2.c Fri Oct 21 16:02:31 2005 @@ -724,20 +724,22 @@ userauth_passwd(Authctxt *authctxt) { static int attempt = 0; char prompt[150]; char *password; + const char *host = options.host_key_alias ? options.host_key_alias : + authctxt->host; if (attempt++ >= options.number_of_password_prompts) return 0; if (attempt != 1) error("Permission denied, please try again."); snprintf(prompt, sizeof(prompt), "%.30s@%.128s's password: ", - authctxt->server_user, authctxt->host); - password = read_passphrase(prompt, 0); + authctxt->server_user, host); + password = read_passphrase(prompt, RP_ADD_ID); packet_start(SSH2_MSG_USERAUTH_REQUEST); packet_put_cstring(authctxt->server_user); packet_put_cstring(authctxt->service); packet_put_cstring(authctxt->method->name); packet_put_char(0); @@ -759,10 +761,12 @@ input_userauth_passwd_changereq(int type, u_int32_t seqnr, void *ctxt) { Authctxt *authctxt = ctxt; char *info, *lang, *password = NULL, *retype = NULL; char prompt[150]; + const char *host = options.host_key_alias ? options.host_key_alias : + authctxt->host; debug2("input_userauth_passwd_changereq"); if (authctxt == NULL) fatal("input_userauth_passwd_changereq: " @@ -779,29 +783,29 @@ packet_put_cstring(authctxt->service); packet_put_cstring(authctxt->method->name); packet_put_char(1); /* additional info */ snprintf(prompt, sizeof(prompt), "Enter %.30s@%.128s's old password: ", - authctxt->server_user, authctxt->host); - password = read_passphrase(prompt, 0); + authctxt->server_user, host); + password = read_passphrase(prompt, RP_ADD_ID); packet_put_cstring(password); memset(password, 0, strlen(password)); xfree(password); password = NULL; while (password == NULL) { snprintf(prompt, sizeof(prompt), "Enter %.30s@%.128s's new password: ", - authctxt->server_user, authctxt->host); - password = read_passphrase(prompt, RP_ALLOW_EOF); + authctxt->server_user, host); + password = read_passphrase(prompt, RP_ALLOW_EOF|RP_ADD_ID); if (password == NULL) { /* bail out */ return; } snprintf(prompt, sizeof(prompt), "Retype %.30s@%.128s's new password: ", - authctxt->server_user, authctxt->host); - retype = read_passphrase(prompt, 0); + authctxt->server_user, host); + retype = read_passphrase(prompt, RP_ADD_ID); if (strcmp(password, retype) != 0) { memset(password, 0, strlen(password)); xfree(password); logit("Mismatch; try again, EOF to quit."); password = NULL; @@ -975,11 +979,11 @@ if (options.batch_mode) return NULL; snprintf(prompt, sizeof prompt, "Enter passphrase for key '%.100s': ", filename); for (i = 0; i < options.number_of_password_prompts; i++) { - passphrase = read_passphrase(prompt, 0); + passphrase = read_passphrase(prompt, RP_ADD_ID); if (strcmp(passphrase, "") != 0) { private = key_load_private_type(KEY_UNSPEC, filename, passphrase, NULL); quit = 0; } else { @@ -1200,11 +1204,12 @@ debug2("input_userauth_info_req: num_prompts %d", num_prompts); for (i = 0; i < num_prompts; i++) { prompt = packet_get_string(NULL); echo = packet_get_char(); - response = read_passphrase(prompt, echo ? RP_ECHO : 0); + response = read_passphrase(prompt, + echo ? RP_ECHO|RP_ADD_ID : RP_ADD_ID); packet_put_cstring(response); memset(response, 0, strlen(response)); xfree(response); xfree(prompt); diff -N -U 5 -u openssh-4.2p1/sshhop.c sshop-4.2p1/sshhop.c --- openssh-4.2p1/sshhop.c Wed Dec 31 19:00:00 1969 +++ sshop-4.2p1/sshhop.c Fri Oct 21 17:54:48 2005 @@ -0,0 +1,1256 @@ +/* + * Copyright (c) 2005 Massachusetts Institute of Technology + * All rights reserved + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +/* + * Functions for implementing SSH hopping as a single command line input. + */ + +#include "includes.h" +RCSID("$OpenBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "log.h" +#include "misc.h" +#include "pathnames.h" +#include "ssh.h" +#include "readconf.h" +#include "xmalloc.h" +#include "sshhop.h" + +/* Structure to encapsulate the information needed to represent each hop + * in the chain connecting this host to the final destination. A hop is + * an invocation of SSH that connects this host to an intermediary host or + * a destination host, where there is at least one intermediary host between + * this host (source) and the destination host. + * command line syntax: ssh [ssh_options] -H [ssh_options] [user1@]host1 ... + */ +struct Hop { + char *user; /* User name at remote host for this hop */ + char *host; /* Remote host to hop to (name or IP address) */ + int address_type; /* AF_INET | AF_INET6 (for "host" value) */ + pid_t pid; /* Process id for spawned SSH subprocess */ + u_short port; /* SSH port number on this hop's remote host */ + int argc; /* Number of command line options for this hop */ + char **argv; /* List of command line options for this hop */ + int cmdc; /* Number of words in command to be invoked + * on this hop's remote host by ssh + */ + char **cmdv; /* Command to be invoked by SSH on this hop's + * remote host + */ +}; +typedef struct Hop Hop; +#define MAX_HOPS 10 /* Allow no more hops than this in the chain */ + +/* "Class" variables. We avoid passing these variables around as arguments + * since 1) it simplifies the API in ssh.c and 2) it protects the integrity + * of the information by limiting access to functions in this module. + */ +static Hop s_hoplist[MAX_HOPS]; /* Information for each hop */ +static int s_numhops = 0; /* Number of hops */ +static char **s_global_argv = NULL; /* List of options that apply to all hops */ +static int s_global_argc = 0; /* Number of options that apply to all hops */ + +/* External variables used for command line option parsing in getopt. */ +extern int optind; +extern int optreset; + +/* Local functions */ +static int sshop_get_option_list(int argc, char **argv, int starting_index, + int *subset_argc, char ***subset_argv, int *hostloc, usage_fn *usage); +static int sshop_parse_hop_info(char *input_str); +static void sshop_copy_global_args(Hop *hl, int hl_index, int g_index, int num); +static void sshop_process_global_options(usage_fn *usage); +static void sshop_process_local_options(Hop *hl, usage_fn *usage); +static int sshop_set_hop_args(int i, char *host, struct sockaddr_un *tmp_uds, + int max_argc, char **new_argv); +static int sshop_start_ssh_client(char *argv[], Hop *hopinfo, char *uds_path); + + +/*========================= Functions used in ssh.c =========================*/ + +/* sshop_parse_options + * Parse the command line options to process -H options + * + * Inputs: + * argc command line argument count (argc from input to main()) + * argv command line argument vector (argv from input to main()) + * usage function to print command usage information + * Outputs: + * s_global_argv + * s_global_argc + * s_hoplist + * s_numhops + * Returns: number of hops + * -1 if error + */ +int +sshop_parse_options(int argc, char **argv, usage_fn *usage) +{ + int new_argc, local_argc = 0; /* argument counts */ + char **local_argv = NULL; /* temp storage for arg list */ + int hops = 0; /* return variable */ + int hop; /* loop index */ + int hoploc; /* position in arg list of -H */ + + /* Get the global options (options before the first -H option or EOL) */ + new_argc = sshop_get_option_list(argc, argv, 1, &s_global_argc, + &s_global_argv, &hoploc, usage); + + /* While options remain, get information for next hop */ + while (new_argc > 0) { + /* Make sure there is room for more hops */ + if (s_numhops == MAX_HOPS) { + hops = -1; + break; + } + /* Get option list for the next hop (to the next -H or EOL) */ + argv += (argc - new_argc); + argc = new_argc; + local_argv = NULL; + local_argc = 0; + new_argc = sshop_get_option_list(argc, argv, 0, &local_argc, + &local_argv, &hoploc, usage); + + /* Process and store information about the next hop */ + if ((local_argc > 0) && (-1 < hoploc)) { + /* Parse the user/hostname string */ + hop = sshop_parse_hop_info(argv[hoploc]); + /* If user and host was successfully loaded (no parse + * errors), store the option information. + */ + if (hop > 0) { + Hop *hl = &s_hoplist[s_numhops]; + hl->argc = hoploc; + /* Commands follow the options for this hop */ + if (hl->argc > 0) { + hl->argv = local_argv; + hl->cmdc = local_argc - hoploc - 1; + /* No commands follow options for this hop */ + } else if (local_argc > 0) { + hl->argv = local_argv; + hl->cmdc = 0; + /* No options for this hop */ + } else if (local_argv) { + xfree(local_argv); + hl->argv = NULL; + hl->cmdc = 0; + } + /* Number of command words > 0, store command */ + if (hl->cmdc > 0) { + hl->cmdv = local_argv + hoploc + 1; + /* Otherwise, no command */ + } else { + hl->cmdv = NULL; + } + local_argv = NULL; + s_numhops++; + /* Otherwise, user/host parse error. End processing */ + } else { + hops = -1; + break; + } + /* The beginning of the option list for this host must be -H. + * If it isn't, move on to the next host but indicate an error. + */ + } else if (strcmp(argv[0], "-H") != 0) { + hops = -1; + } + } + + /* Too many hops -- error */ + if (s_numhops == MAX_HOPS) { + fprintf(stderr, "Limit of %d hops\n", MAX_HOPS-1); + hops = -1; + /* No errors and multiple hops. Prepare the hop option lists. */ + } else if (hops == 0 && 1 < s_numhops) { + for (hop = 0; hop < s_numhops; hop++) { + sshop_process_local_options(&s_hoplist[hop], usage); + } + sshop_process_global_options(usage); + hops = s_numhops; + /* No multiple hops, thus no need for any additional preparation of the + * option lists. Free function memory and return. */ + } else if (local_argv != NULL) { + xfree(local_argv); + local_argv = NULL; + } + + return(hops); +} + +/* sshop_set_default_user + * Set the value of the user field for each hop where it is not already set. + * + * Inputs: + * user default user name + * s_hoplist + * s_numhops + * Outputs: + * s_hoplist (changes user value) + * Returns: none + */ +void +sshop_set_default_user(char *user) +{ + int i; + Hop *hl; + + if (user == NULL) + return; + + for (i = 0; i < s_numhops; i++) { + hl = &s_hoplist[i]; + if (hl->user == NULL) { + hl->user = xstrdup(user); + } + } +} + +/* sshop_build_chain + * Build the hop chain by starting each SSH subprocess in turn + * + * Inputs: + * ssh_path path to SSH executable + * s_global_argc + * s_hoplist + * s_numhops + * Outputs: none + * Returns: 0 if all hops established successfully + * -1 otherwise + */ +int +sshop_build_chain(char *ssh_path) +{ + char *localhost = "localhost"; + char *host = s_hoplist[0].host;/* Source end of all connections */ + char **new_argv; /* Argument list used for ssh call */ + int new_argc = 0; /* Number of args for single ssh call */ + int max_argc = 0; /* Length of new_argv */ + int i; /* Loop counter */ + int error = 0; /* Return variable */ + struct sockaddr_un tmp_uds; /* Unix domain socket */ + + /* (Over-)Allocate space for the new, larger, argument list + * Number of elements is the sum of: + * #command line args specific to this hop + * #words in command + * max # options added by rote (hard-coded value) + * #command line args applied globally + */ + for (i = 0; i < s_numhops; i++) { + new_argc = s_hoplist[i].argc + s_hoplist[i].cmdc; + if (max_argc < new_argc) { + max_argc = new_argc; + } + } + max_argc += (15 + s_global_argc); + + /* Expand the size of the arg list and copy the arguments */ + new_argv = (char **)xmalloc(max_argc*sizeof(char *)); + /* Set the command arg: */ + if (new_argv) { + new_argv[0] = ssh_path; + } else { + exit(1); + } + + memset(&tmp_uds, '\0', sizeof(tmp_uds)); + + /* Create a connection for each hop in the chain */ + for (i = 0; (i < s_numhops) && (error == 0); i++) { + /* Fill in the argument list for this hop */ + new_argc = sshop_set_hop_args(i, host, &tmp_uds, + max_argc, new_argv); + + /* Start a new SSH session */ + if (new_argc > 5) { + error = sshop_start_ssh_client(new_argv, + &s_hoplist[i], tmp_uds.sun_path); + } else { + error = -1; + } + + /* Reset for the next pass */ + host = localhost; + } + + return(error); +} + +/* sshop_kill_subprocesses + * Kill the subprocesses. Doesn't guarantee that all processes have been + * killed. (See sshop_wait_for_exit()) + * + * Inputs: + * s_hoplist + * s_numhops + * Outputs: + * s_hoplist (changes pid value) + * Returns: 0 if all processes killed successfully + * 1 if some processes may still be running + */ +int +sshop_kill_subprocesses() +{ + int i; /* Loop counter */ + pid_t pid; /* Process id of killed process */ + int looping = 0; /* Loop status */ + int status = 0; /* Wait status */ + Hop *hl; /* Hop pointer */ + + /* Debugging printout */ + debug("%d hop processes to kill", s_numhops); + for (i = 0; i < s_numhops; i++) { + hl = &s_hoplist[i]; + debug("hop %d: host: %s pid: %d port: %d", + i, hl->host, hl->pid, hl->port); + } + + for (i = 0; i < s_numhops; i++) { + hl = &s_hoplist[i]; + /* Send a TERM signal to running process */ + if (hl->pid > 0) { + kill(hl->pid, SIGTERM); + sleep(1); + pid = waitpid(hl->pid, &status, WNOHANG); + if (pid == hl->pid) { + printf("process %d exited (host %s)\n", + pid, hl->host); + hl->pid = 0; + } else { + looping = 1; + } + } + } + return(looping); +} + +/* sshop_wait_for_exit + * Wait for any one of the subprocesses to exit and then clean up the rest. + * Doesn't return until all processes are killed and reaped. + * + * Inputs: + * s_hoplist + * s_numhops + * Outputs: + * s_hoplist (changes pid value) + * Returns: none + */ +void +sshop_wait_for_exit() +{ + int i; /* Loop counter */ + pid_t pid; /* Process id for process to kill */ + int looping = 0; /* Whether to keep looping or not */ + int status = 0; /* Wait status */ + Hop *hl; /* Pointer to hop info */ + + /* Debugging printout */ + debug("%d hop process settings", s_numhops); + for (i = 0; i < s_numhops; i++) { + hl = &s_hoplist[i]; + debug("hop %d: host: %s pid: %d port: %d", + i, hl->host, hl->pid, hl->port); + } + + /* Make sure there are processes to wait for */ + for (i = 0; (i < s_numhops) && !looping; i++) { + looping = s_hoplist[i].pid != 0; + } + + /* Wait for all subprocesses to exit */ + while (looping) { + pid = wait(&status); + for (i = 0; (i < s_numhops) && (pid != s_hoplist[i].pid); i++); + + /* If any process in the hop chain has exited, kill the rest */ + if (i < s_numhops) { + printf("process %d exited (host %s)\n", pid, + s_hoplist[i].host); + s_hoplist[i].pid = 0; + looping = sshop_kill_subprocesses(); + } + } +} + +/* sshop_cleanup + * Free memory associated with hop processing. + * + * Inputs: + * s_global_argv + * s_global_argc + * s_hoplist + * s_numhops + * Outputs: + * s_global_argv + * s_global_argc + * s_hoplist + * s_numhops + * Returns: None + */ +void +sshop_cleanup() +{ + int i; + Hop *hl; + + /* Free the list of arguments applied to all hops */ + if (s_global_argv) { + xfree(s_global_argv); + s_global_argv = NULL; + } + s_global_argc = 0; + + /* Free the information associated with each hop */ + for (i = 0; i < s_numhops; i++) { + hl = &s_hoplist[i]; + if (hl->user) { + xfree(hl->user); + hl->user = NULL; + } + if (hl->host) { + xfree(hl->host); + hl->host = NULL; + } + if (hl->argv) { + xfree(hl->argv); + hl->argv = NULL; + } + hl->argc = 0; + hl->cmdc = 0; + } + s_numhops = 0; +} + +/*====================== Internal functions =============================*/ + +/* + * Store the command line options for a single subsection. Each + * subsection ends with either -H or '\0' (for the last subsection). + * + * Inputs: + * argc command line argument count + * argv command line argument vector + * starting_index which argv to start with (usually 0 or 1) + * usage function to print command usage information + * s_global_argc + * Outputs: + * subset_argc number of args in subsection + * subset_argv list of args in subsection + * hostloc location of host name in arg list + * Returns: number of arguments remaining to be processed + */ +static int +sshop_get_option_list(int argc, char **argv, int starting_index, + int *subset_argc, char ***subset_argv, int *hostloc, usage_fn *usage) +{ + int size; /* Memory allocation size */ + int hop_not_found = 1; /* Loop terminator = -H or \0 not yet found */ + char ch; /* Option value */ + int new_argc = 0; /* Return value */ + int next_arg = 1; /* Argument list index */ + int i; /* Loop counter */ + + /* Initialize */ + *subset_argv = NULL; + *subset_argc = 0; + *hostloc = -1; + optind = starting_index; + optreset = 1; + + /* Since the hop info includes hostname and possibly command + * information, + */ + while (hop_not_found && + (ch = getopt(argc, argv, SSH_CMDLINE_OPTIONS)) != -1) { + switch (ch) { + /* Hop info, -H signals beginning of next hop information + * We use it here to signal the end of the previous set of + * ssh options. If there is a hostname, we will not find + * the -H before exiting the option list. Thus, this case + * will be used only for finding the end of the global options. + */ + case 'H': + new_argc = argc - optind; + *subset_argc = optind - 1; + *hostloc = optind - 2; + hop_not_found = 0; + break; + /* We'll copy all the other options later, keep track of + * where we are. + */ + default: + if (ch != '?') { + next_arg = optind; + } else { + usage(); + } + break; + } + } + /* -H not found yet (probably hit a hostname first), continue looking */ + if (hop_not_found && (optind <= argc)) { + *hostloc = optind; + for (i = next_arg; (i < argc) && (strcmp(argv[i], "-H") != 0); + i++); + if (i <= argc) { + new_argc = argc - i; + *subset_argc = i; + hop_not_found = 0; + } + } + /* Copy the arguments up to the next hop info or the end of the + * args, whichever came first. We will eventually use the allocated + * arg list for storing global arguments as well as hop specific + * arguments, so size it accordingly. Note that the first time + * this function is called, it will read in the global arguments. + * So the global arg list will not have extra space allocated. + */ + if ((s_global_argc + *subset_argc) > 0) { + size = (s_global_argc + *subset_argc) * sizeof(*argv); + *subset_argv = (char **)xmalloc(size); + memset((void *)*subset_argv, '\0', size); + if (*subset_argc > 0) { + memcpy((void *)*subset_argv, (void *)argv, + *subset_argc * sizeof(*argv)); + } + } + + return(new_argc); +} + +/* sshop_parse_hop_info + * Parse the hop argument and store the username and hostname in + * the hop structure. Uses s_numhops but does not change its value. + * Format of input_str: [@] + * + * Inputs: + * input_str string of the form [@] + * s_hoplist + * s_numhops + * Outputs: + * s_hoplist + * Returns: number of hops, including this one, on success + * -1 on failure + */ +static int +sshop_parse_hop_info(char *input_str) +{ + char *p, buf[512]; /* if buf size changes, change the sscanf + * format statement + */ + Hop *hl = NULL; /* Ptr to hop information */ + char *hopstr; /* Temporary string with user/host */ + int hops = 0; /* return variable */ + + hopstr = xstrdup(input_str); + /* Proceed only if the string is valid */ + if (hopstr && (*hopstr != '\0') && (s_numhops < MAX_HOPS)) { + /* Skip over whitespace at beginning of string */ + while (isspace(*hopstr)) + hopstr++; + /* If there are characters left, parse the string + * The string should have the format: [user@]host + */ + if (*hopstr != '\0') { + memset(buf, '\0', sizeof(buf)); + /* Nothing scanned: Bad format */ + if (sscanf(hopstr, "%511c", buf) < 1) { + fprintf(stderr, + "Bad SSH hop specification '%s'\n", + hopstr); + hops = -1; + /* Otherwise, input formatted ok, store the information. + * Don't increment hop count here. Just store the info. + */ + } else { + hl = &s_hoplist[s_numhops]; + hl->pid = 0; + /* User name specified */ + if ((p = strrchr(buf, '@'))) { + *p = '\0'; + hl->user = xstrdup(buf); + hl->host = xstrdup(++p); + /* No user name specified */ + } else { + hl->user = NULL; + hl->host = xstrdup(buf); + } + hl->port = 0; + /* Host string looks like IPv6 address */ + if (strchr(hl->host, ':')) { + hl->address_type = AF_INET6; + /* Otherwise, assume IPv4 address */ + } else { + hl->address_type = AF_INET; + } + /* Set return value */ + hops = s_numhops + 1; + } + } + } + + xfree(hopstr); + + return(hops); +} + +/* sshop_copy_global_args + * Copy consecutive arguments from the global list to the beginning of + * the local list. Assumes the array was sized to allow addition of + * the max number of global arguments. + * + * Inputs: + * hl pointer to hop list item + * hl_index location in arg list to copy to + * ga_index index of the last global arg to be copied + * num num of global arguments to copy + * s_global_argv + * s_global_argc + * Outputs: + * hl (changes argv and argc values) + * Returns: none + */ +static void +sshop_copy_global_args(Hop *hl, int hl_index, int ga_index, int num) +{ + int i, j; + + if (hl->argv && (0 < num) && (num < s_global_argc)) { + /* Make space for the global arguments at the indicated + * position in the hop argument list. + */ + bcopy(hl->argv+hl_index, hl->argv+num+hl_index, + (hl->argc-hl_index)*sizeof(*hl->argv)); + /* Copy in the global args. Note that the address is + * being copied, not the contents. + */ + for (i=hl_index, j=ga_index-num; i < hl_index+num; i++, j++) { + hl->argv[i] = s_global_argv[j]; + hl->argc++; + } + } else { + fatal("sshop_copy_global_args: no argument list to copy"); + } +} + +/* sshop_process_global_options + * Examine each command line option to be associated with all hops and + * copy the options into the individual hop option lists, as appropriate. + * Assumes that the individual option lists were sized large enough + * to hold all of the global options, in addition to the local options. + * + * Inputs: + * usage function to print command usage information + * s_global_argv + * s_global_argc + * s_hoplist + * s_numhops + * Outputs: + * s_global_argv + * s_global_argc + * s_hoplist (possibly update arguments) + * Returns: none + */ +static void +sshop_process_global_options(usage_fn *usage) +{ + extern Options options; /* from ssh.c */ + extern int debug_flag; /* from ssh.c */ + + int i, j; /* loop counters */ + u_short port; /* port number specified as global option */ + char ch; /* option variable */ + Hop *hl; /* pointer to information for a single hop */ + int hl_index0 = 0, hl_indexn = 0; + optind = 1; /* option index for getopt */ + optreset = 1; /* getopt to treat this as a new option list */ + + /* Loop to process the global option list. The options will be + * added to the argument lists of the appropriate hops. + */ + while ((ch = getopt(s_global_argc, s_global_argv, SSH_CMDLINE_OPTIONS)) != -1) { + switch (ch) { + /* All hops, no arguments */ + case '1': + case '2': + case '4': + case '6': + case 'a': + case 'C': + case 'k': + case 'P': + case 'q': + /* Retain these options */ + break; + + /* Verbosity, retain this option for subprocesses and apply + * it to the main process as well. + */ + case 'v': + if (debug_flag == 0) { + debug_flag = 1; + options.log_level = SYSLOG_LEVEL_DEBUG1; + } else { + if (options.log_level < SYSLOG_LEVEL_DEBUG3) + options.log_level++; + break; + } + /* FALLTHROUGH */ + + /* Version, retain this option for subprocesses and apply + * it to the main process as well. + */ + case 'V': + fprintf(stderr, "%s, %s\n", + SSH_RELEASE, SSLeay_version(SSLEAY_VERSION)); + break; + + /* All hops, with arguments */ + case 'c': + case 'F': + case 'i': + case 'I': + case 'm': + case 'o': + /* Retain these options */ + break; + + /* Applies to all hops, copy the name */ + case 'l': + for (i = 0; i < s_numhops; i++) { + if (s_hoplist[i].user == 0) { + s_hoplist[i].user = xstrdup(optarg); + } + } + s_global_argv[optind-2] = NULL; + s_global_argv[optind-1] = NULL; + break; + + /* Use as default when port not specified */ + case 'p': + port = a2port(optarg); + if (0 < port) { + for (i = 0; i < s_numhops; i++) { + if (s_hoplist[i].port == 0) { + s_hoplist[i].port = port; + } + } + } + s_global_argv[optind-2] = NULL; + s_global_argv[optind-1] = NULL; + break; + + /* First hop only, no arguments + * Prepend this option to the list of options */ + case 'b': + hl = &s_hoplist[0]; + sshop_copy_global_args(hl, hl_index0, optind, 1); + hl_index0++; + s_global_argv[optind-1] = NULL; + break; + + /* First hop only, with arguments + * Prepend this option to the list of options */ + case 'u': + hl = &s_hoplist[0]; + sshop_copy_global_args(hl, hl_index0, optind, 2); + hl_index0 += 2; + s_global_argv[optind-2] = NULL; + s_global_argv[optind-1] = NULL; + break; + + /* Last hop only, no arguments + * Prepend this option to the list of options */ + case 'A': + case 'f': + case 'g': + case 'n': + case 'M': + case 'N': + case 's': + case 't': + case 'T': + case 'x': + case 'X': + case 'Y': + hl = &s_hoplist[s_numhops-1]; + sshop_copy_global_args(hl, hl_indexn, optind, 1); + hl_indexn++; + s_global_argv[optind-1] = NULL; + break; + + /* Last hop only, with arguments + * Prepend this option to the list of options */ + case 'D': + case 'e': + case 'L': + case 'O': + case 'R': + case 'S': + case 'U': + hl = &s_hoplist[s_numhops-1]; + sshop_copy_global_args(hl, hl_indexn, optind, 2); + hl_indexn += 2; + s_global_argv[optind-2] = NULL; + s_global_argv[optind-1] = NULL; + break; + + default: + usage(); + break; + } + } + + /* Remove all the nulls (from deleted args) from the global argv list + * and update the global arg count + */ + i = 0; + while (i < s_global_argc) { + for (; i < s_global_argc && s_global_argv[i] != NULL; i++); + for (j = i; j < s_global_argc && s_global_argv[j] == NULL; j++); + if (i < s_global_argc && j < s_global_argc) { + bcopy(s_global_argv+j, s_global_argv+i, + (s_global_argc - j) * sizeof(*s_global_argv)); + s_global_argc -= (j - i); + } else if (i < s_global_argc && j >= s_global_argc) { + s_global_argc = i; + } + } +} + +/* sshop_process_local_options + * Examine each command line option associated with a single hop and + * determine whether the option should be retained or not + * + * Inputs: + * hl hop for which to process options + * usage function to print command usage information + * s_hoplist + * s_numhops + * Outputs: + * hl (updates argv and argc) + * Returns: none + */ +static void +sshop_process_local_options(Hop *hl, usage_fn *usage) +{ + int i, j; /* Loop counters */ + char ch; /* option value */ + optind = 0; /* option index to start with */ + optreset = 1; /* getopt to treat this as a new option list */ + + /* Check for valid input */ + if (hl == NULL) { + return; + } + if (hl->argc == 0) { + return; + } + + /* Loop to process the local option list for a single hop. Delete + * any options that are disallowed for this hop (based on whether + * it is the first, last, or an intermediate hop). + */ + while ((ch = getopt(hl->argc, hl->argv, SSH_CMDLINE_OPTIONS)) != -1) { + switch (ch) { + /* All hops, no arguments */ + case '1': + case '2': + case '4': + case '6': + case 'a': + case 'A': + case 'b': + case 'C': + case 'k': + case 'P': + case 'q': + case 'v': + case 'V': + /* Retain these options */ + break; + + /* All hops, with arguments */ + case 'c': + case 'e': + case 'F': + case 'i': + case 'I': + case 'm': + case 'o': + /* Retain these options */ + break; + + /* Copy the name into an empty user field. Whether copied + * or not, delete the option since it is extraneous. + */ + case 'l': + if (hl->user == NULL) { + hl->user = xstrdup(optarg); + } + hl->argv[optind-1] = NULL; + hl->argv[optind-2] = NULL; + break; + + /* First hop only, with arguments */ + case 'u': + /* If not the first hop, delete this option and its + * argument. */ + if (hl != &s_hoplist[0]) { + hl->argv[optind-1] = NULL; + hl->argv[optind-2] = NULL; + } + break; + + /* Last hop only, no arguments */ + case 'f': + case 'g': + case 'n': + case 'M': + case 'N': + case 'p': + case 's': + case 't': + case 'T': + case 'x': + case 'X': + case 'Y': + /* If not the last hop, delete this option */ + if (hl != &s_hoplist[s_numhops-1]) { + hl->argv[optind-1] = NULL; + } + break; + + /* Last hop only, with arguments */ + case 'D': + case 'L': + case 'O': + case 'R': + case 'S': + case 'U': + /* If not the last hop, delete this option and its + * argument. */ + if (hl != &s_hoplist[s_numhops-1]) { + hl->argv[optind-1] = NULL; + hl->argv[optind-2] = NULL; + } + break; + + default: + usage(); + break; + } + } + + /* Remove all the nulls (from deleted options) from the local + * argv list and update the arg count + */ + i = 0; + while (i < hl->argc) { + for (; i < hl->argc && hl->argv[i] != NULL; i++); + for (j = i; j < hl->argc && hl->argv[j] == NULL; j++); + if (i < hl->argc && j < hl->argc) { + bcopy(hl->argv+j, hl->argv+i, + (hl->argc - j) * sizeof(*hl->argv)); + hl->argc -= (j - i); + } else if (i < hl->argc && j >= hl->argc) { + hl->argc = i - 1; + } + } + +} + +/* sshop_set_hop_args + * Non-re-entrant function + * Fill in the argument list for the next ssh command + * Returns the number of arguments in the argument list -- should be > 5 + * for a valid option list. + * + * Inputs: + * i hop index for this hop + * host + * tmp_uds Unix domain socket used for the last hop + * max_argc maximum length of the argument list + * new_argv argument list that will be used to start ssh + * s_global_argv + * s_global_argc + * s_hoplist (contains ssh path as first arg in arg list) + * s_numhops + * Outputs: + * tmp_uds Unix domain socket used for this hop + * s_hoplist (argv and argc updated) + * Returns: Number of arguments in the argument list + */ +static int +sshop_set_hop_args(int i, char *host, struct sockaddr_un *tmp_uds, + int max_argc, char **new_argv) +{ + static char *c_bckgnd_flag = "-n"; + /* Don't use -f since we don't want to fork a new process */ + static char *c_no_cmd_flag = "-N"; + static char *c_force_tty_flag = "-t"; + static char *c_no_forward_x11 = "-x"; + static char tmp_port_arg[8]; + static char tmp_fwd_arg[372]; + static char tmp_host_alias[272]; + static struct sockaddr_un tmp_uds_last; + + char tmp_dst_host_arg[1024]; + int new_argc; /* return variable */ + pid_t pid = getpid(); + char *user = NULL; + char delimiter = ','; + struct servent *sp; /* Used to get the SSH port # */ + int j = i + 1; + + /*----- Set up -----*/ + /* Save the last value to be used later */ + memcpy(tmp_uds_last.sun_path, tmp_uds->sun_path, + sizeof(tmp_uds_last.sun_path)); + + /* Generate the argument to the -U option for forwarding. + * Since -U is not used for the last ssh invocation (no + * more forwarding), we only generate the argument for + * s_numhops - 1 hops. + */ + if (j < s_numhops) { + /* If needed, set the remote ssh port number */ + if (s_hoplist[j].port == 0) { + sp = getservbyname(SSH_SERVICE_NAME, "tcp"); + if (sp) + s_hoplist[j].port = ntohs(sp->s_port); + else + s_hoplist[j].port = SSH_DEFAULT_PORT; + } + /* Get the user name on the remote host */ + if (s_hoplist[j].user) { + user = xstrdup(s_hoplist[j].user); + } else { + user = NULL; + } + /* Create a name for the unix domain socket that + * will be used for forwarding. + */ + construct_uds_path(NULL, s_hoplist[j].host, pid, + &user, s_hoplist[j].port, + tmp_uds->sun_path, sizeof(tmp_uds->sun_path)); + /* Create the argument in the form: + * local_path,remote_host,remote_port + */ + snprintf(tmp_fwd_arg, sizeof(tmp_fwd_arg), "%s%c%s%c%d", + tmp_uds->sun_path, delimiter, s_hoplist[j].host, + delimiter, s_hoplist[j].port); + /* Clean up */ + if (user) + xfree(user); + + /* Last hop in the chain, zero out values to signal that + * they shouldn't be used. */ + } else { + memset(tmp_fwd_arg, '\0', sizeof(tmp_fwd_arg)); + memset(tmp_uds->sun_path, '\0', + sizeof(tmp_uds->sun_path)); + } + + /* Construct the host argument (will follow all other options + * and precede command) + */ + if (s_hoplist[i].user) { + snprintf(tmp_dst_host_arg, sizeof(tmp_dst_host_arg), + "%s@%s", s_hoplist[i].user, host); + } else { + strncpy(tmp_dst_host_arg, host, + sizeof(tmp_dst_host_arg)); + } + /* -o HostKeyAlias= argument */ + if (strcmp(host, "localhost") == 0) { + snprintf(tmp_host_alias, sizeof(tmp_host_alias), + "HostKeyAlias=%s", s_hoplist[i].host); + } else { + memset(tmp_host_alias, '\0', sizeof(tmp_host_alias)); + } + + /*--- Start building the argument list ----*/ + + /* Copy in the new arguments. Do something different for the last + * hop in the chain since it will be an interactive session. */ + new_argc = 1; + if (j < s_numhops) { + /* ssh -n -N -X */ + new_argv[new_argc++] = c_bckgnd_flag; + new_argv[new_argc++] = c_no_cmd_flag; + new_argv[new_argc++] = c_no_forward_x11; + } else { + /* ssh -t -t */ + new_argv[new_argc++] = c_force_tty_flag; + new_argv[new_argc++] = c_force_tty_flag; + } + /* Tell ssh to use the key for the real hostname + * -o HostKeyAlias= */ + if (strlen(tmp_host_alias) > 0) { + new_argv[new_argc++] = "-o"; + new_argv[new_argc++] = tmp_host_alias; + } + /* Port info for the first hop only + * [-p ] */ + if (i == 0 && 0 < s_hoplist[i].port) { + snprintf(tmp_port_arg, sizeof(tmp_port_arg), "%d", + s_hoplist[i].port); + new_argv[new_argc++] = "-p"; + new_argv[new_argc++] = tmp_port_arg; + } + /* SSH forwarding information + * -U ,, */ + if (strlen(tmp_fwd_arg) > 0) { + new_argv[new_argc++] = "-U"; + new_argv[new_argc++] = tmp_fwd_arg; + } + /* Unix domain socket to connect to for SSH connection + * -u */ + if (strlen(tmp_uds_last.sun_path) > 0) { + new_argv[new_argc++] = "-u"; + new_argv[new_argc++] = tmp_uds_last.sun_path; + } + /* Copy in the global options, if any. Skip the first + * since that is the program path + * ssh [] + */ + if (s_global_argc > 1) { + memcpy(new_argv+new_argc, s_global_argv+1, + (s_global_argc-1) * sizeof(char *)); + new_argc += (s_global_argc - 1); + } + /* Copy in the options specific to this host, if any + * ssh [] + * [] + */ + if (s_hoplist[i].argc > 0) { + memcpy(new_argv+new_argc, s_hoplist[i].argv, + s_hoplist[i].argc * sizeof(char *)); + new_argc += s_hoplist[i].argc; + } + /* Copy in the destination host + * ssh [] + * [] [@] + */ + new_argv[new_argc++] = tmp_dst_host_arg; + /* Copy in any commands + * ssh [] + * [] [@] [] + */ + if (s_hoplist[i].cmdc > 0) { + memcpy(new_argv+new_argc, s_hoplist[i].cmdv, + s_hoplist[i].cmdc * sizeof(char *)); + new_argc += s_hoplist[i].cmdc; + } + /* Terminate argument list */ + new_argv[new_argc++] = NULL; + + return(new_argc); +} + +/* sshop_start_ssh_client + * Spawn a subprocess + * Inputs: + * argv - null terminated argument vector for invoking ssh + * hopinfo - information about this hop + * uds_path - path to the unix domain socket. The existence + * of this socket is monitored to determine when + * ssh process has made the remote connection. + * Outputs: + * hopinfo (pid updated) + * Returns: 0 if the remote connection appears to have been established + * -1 otherwise + */ +static int +sshop_start_ssh_client(char *argv[], Hop *hopinfo, char *uds_path) +{ + pid_t pid; /* return value from fork() */ + int error = 0; /* return variable */ + int status; /* wait status */ + + pid = fork(); + /* Child process; exec the command */ + if (pid == 0) { + /* Should never return from this call */ + execv(argv[0], argv); + /* If we do return, kill off the process immediately. */ + fatal("sshop_start_ssh_client: exec(%s): %s", argv[0], + strerror(errno)); + /* Parent; store the pid for the child */ + } else if (pid > 0) { + /* Store the process id for the child so we can kill it later */ + hopinfo->pid = pid; + + /* Make sure the process didn't have issues at startup */ + if (waitpid(pid, &status, WNOHANG) == pid) { + hopinfo->pid = 0; + return -1; + } + + /* Put up notification that SSH subprocess is starting */ + if (hopinfo->user != NULL) { + fprintf(stdout, "[%d]\n[%d] login for %s@%s\n", + hopinfo->pid, hopinfo->pid, hopinfo->user, + hopinfo->host); + fflush(stdout); + } else { + fprintf(stdout, "[%d]\n[%d] login to %s\n", + hopinfo->pid, hopinfo->pid, hopinfo->host); + fflush(stdout); + } + /* Wait for the process to establish the connection + * If there is a problem, we will sit here indefinitely. + * The user will need to kill the process (Ctl-C) + * Since the last process in the chain does not create + * a unix domain socket, it will return immediately. + */ + if (uds_path && (strlen(uds_path) > 0)) { + while (access(uds_path, F_OK) == -1 && error == 0) { + /* Make sure the process is still running */ + pid = hopinfo->pid; + if (waitpid(pid, &status, WNOHANG) == pid) { + hopinfo->pid = 0; + error = -1; + } else { + sleep(1); + } + } + sleep(1); + } + /* Error */ + } else { + fprintf(stderr, + "sshop_start_ssh_client: Unable to fork child process\n"); + error = -1; + } + + return(error); +} diff -N -U 5 -u openssh-4.2p1/sshhop.h sshop-4.2p1/sshhop.h --- openssh-4.2p1/sshhop.h Wed Dec 31 19:00:00 1969 +++ sshop-4.2p1/sshhop.h Fri Oct 21 15:48:24 2005 @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2005 Massachusetts Institute of Technology + * All rights reserved + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef SSHHOP_H +#define SSHHOP_H + +typedef void usage_fn(void); + +int sshop_build_chain(char *ssh_path); +void sshop_cleanup(); +int sshop_kill_subprocesses(); +int sshop_parse_options(int argc, char **argv, usage_fn *); +void sshop_set_default_user(char *user); +void sshop_wait_for_exit(); + +#endif /* SSHHOP_H */ diff -N -U 5 -u openssh-4.2p1/sshhop_test.c sshop-4.2p1/sshhop_test.c --- openssh-4.2p1/sshhop_test.c Wed Dec 31 19:00:00 1969 +++ sshop-4.2p1/sshhop_test.c Mon Nov 28 13:18:07 2005 @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2005 Massachusetts Institute of Technology + * All rights reserved + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" +RCSID("$OpenBSD: readconf.c,v 1.137 2005/03/04 08:48:06 djm Exp $"); + +#include +#include +#include +#include "log.h" +#include "misc.h" +#include "sshhop.h" +#include "xmalloc.h" +#include "ssh.h" +#include "readconf.h" + +#undef _PATH_SSH_PROGRAM +#define _PATH_SSH_PROGRAM "./ssh" + +Options options; +int debug_flag; + +/* !!!NOTE: Toy program -- no signal handlers !!!*/ + +/* Prints a help message to the user. This function never returns. */ +static void +usage(void) +{ + fprintf(stderr, +"usage: sshhop_test [ssh_options] -H [ssh_options] [-p port1] [user1@]host1\n" +" ... -H [ssh_options] [-p portn] [usern@]hostn\n" +" [command]\n" + ); + exit(1); +} + +/* + * Main for sshhop test program. + */ +int +main(int argc, char *argv[]) +{ + int hops; + int error; + int original_real_uid; + char *ssh_path = xstrdup(_PATH_SSH_PROGRAM); + struct passwd *pw; + + /* May be enough arguments */ + if (argc > 4) { + original_real_uid = getuid(); + /* Get user data. */ + pw = getpwuid(original_real_uid); + if (!pw) { + logit("You don't exist, go away!"); + exit(1); + } + /* Take a copy of the returned structure. */ + pw = pwcopy(pw); + + hops = sshop_parse_options(argc, argv, usage); + /* No hosts given, exit after printing usage */ + if (hops <= 0) { + usage(); + } + /* Only one host, run the standard SSH. + * Overwrite this process */ + else if (hops == 1) { + argv[0] = ssh_path; + execv(argv[0], argv); + } + /* More than one hop, set up the hop chain */ + else { + /* + * Initialize "log" output. Since we are the client all output + * actually goes to stderr. + */ + log_init(argv[0], + options.log_level == -1 ? SYSLOG_LEVEL_INFO : options.log_level, + SYSLOG_FACILITY_USER, 1); + + /* Make sure the user is set */ + sshop_set_default_user(pw->pw_name); + + /* Build the hop chain */ + error = sshop_build_chain(ssh_path); + + /* Hop chain established successfully, wait to clean + * processes up. + */ + if (error == 0) { + sshop_wait_for_exit(); + /* Things were not started properly, shutdown */ + } else { + sshop_kill_subprocesses(); + sshop_wait_for_exit(); + } + } + /* Not enough arguments */ + } else { + usage(); + } + return(0); +} +