# HG changeset patch # User Matt Johnston # Date 1222175782 0 # Node ID 9f583f4d59a6892372fd687f32d45f18a220dde0 # Parent cd02449b709cb042a4933f469c2eb92941b9a552# Parent df7f7da7f6e41ce5811bb4ee16b61e9ef8daf943 propagate from branch 'au.asn.ucc.matt.dropbear.pubkey-options' (head 537a6ebebb46424b967ffe787f0f8560e5f447e8) to branch 'au.asn.ucc.matt.dropbear' (head 10b2f286b9886364db39dfbb4f8f46e49e345d87) diff -r df7f7da7f6e4 -r 9f583f4d59a6 chansession.h --- a/chansession.h Fri Sep 12 17:23:56 2008 +0000 +++ b/chansession.h Tue Sep 23 13:16:22 2008 +0000 @@ -78,6 +78,9 @@ void cli_send_chansess_request(); void cli_tty_cleanup(); void cli_chansess_winchange(); +#ifdef ENABLE_CLI_NETCAT +void cli_send_netcat_request(); +#endif void svr_chansessinitialise(); extern const struct ChanType svrchansess; diff -r df7f7da7f6e4 -r 9f583f4d59a6 cli-authinteract.c --- a/cli-authinteract.c Fri Sep 12 17:23:56 2008 +0000 +++ b/cli-authinteract.c Tue Sep 23 13:16:22 2008 +0000 @@ -77,6 +77,11 @@ TRACE(("enter recv_msg_recv_userauth_info_request")) + /* Let the user know what password/host they are authing for */ + if (!cli_ses.interact_request_received) { + fprintf(stderr, "Login for %s@%s\n", cli_opts.username, + cli_opts.remotehost); + } cli_ses.interact_request_received = 1; name = buf_getstring(ses.payload, NULL); diff -r df7f7da7f6e4 -r 9f583f4d59a6 cli-chansession.c --- a/cli-chansession.c Fri Sep 12 17:23:56 2008 +0000 +++ b/cli-chansession.c Tue Sep 23 13:16:22 2008 +0000 @@ -338,9 +338,8 @@ TRACE(("leave send_chansess_shell_req")) } -static int cli_initchansess(struct Channel *channel) { - - +/* Shared for normal client channel and netcat-alike */ +static int cli_init_stdpipe_sess(struct Channel *channel) { channel->writefd = STDOUT_FILENO; setnonblocking(STDOUT_FILENO); @@ -351,6 +350,12 @@ setnonblocking(STDERR_FILENO); channel->extrabuf = cbuf_new(opts.recv_window); + return 0; +} + +static int cli_initchansess(struct Channel *channel) { + + cli_init_stdpipe_sess(channel); if (cli_opts.wantpty) { send_chansess_pty_req(channel); @@ -363,12 +368,48 @@ } return 0; /* Success */ +} +#ifdef ENABLE_CLI_NETCAT + +void cli_send_netcat_request() { + + const unsigned char* source_host = "127.0.0.1"; + const int source_port = 22; + + const struct ChanType cli_chan_netcat = { + 0, /* sepfds */ + "direct-tcpip", + cli_init_stdpipe_sess, /* inithandler */ + NULL, + NULL, + cli_closechansess + }; + + cli_opts.wantpty = 0; + + if (send_msg_channel_open_init(STDIN_FILENO, &cli_chan_netcat) + == DROPBEAR_FAILURE) { + dropbear_exit("Couldn't open initial channel"); + } + + buf_putstring(ses.writepayload, cli_opts.netcat_host, + strlen(cli_opts.netcat_host)); + buf_putint(ses.writepayload, cli_opts.netcat_port); + + /* originator ip - localhost is accurate enough */ + buf_putstring(ses.writepayload, source_host, strlen(source_host)); + buf_putint(ses.writepayload, source_port); + + encrypt_packet(); + TRACE(("leave cli_send_chansess_request")) } +#endif void cli_send_chansess_request() { TRACE(("enter cli_send_chansess_request")) + if (send_msg_channel_open_init(STDIN_FILENO, &clichansess) == DROPBEAR_FAILURE) { dropbear_exit("Couldn't open initial channel"); @@ -379,3 +420,16 @@ TRACE(("leave cli_send_chansess_request")) } + + +#if 0 + while (cli_opts.localfwds != NULL) { + ret = cli_localtcp(cli_opts.localfwds->listenport, + cli_opts.localfwds->connectaddr, + cli_opts.localfwds->connectport); + if (ret == DROPBEAR_FAILURE) { + dropbear_log(LOG_WARNING, "Failed local port forward %d:%s:%d", + cli_opts.localfwds->listenport, + cli_opts.localfwds->connectaddr, + cli_opts.localfwds->connectport); +#endif diff -r df7f7da7f6e4 -r 9f583f4d59a6 cli-main.c --- a/cli-main.c Fri Sep 12 17:23:56 2008 +0000 +++ b/cli-main.c Tue Sep 23 13:16:22 2008 +0000 @@ -32,6 +32,8 @@ static void cli_dropbear_exit(int exitcode, const char* format, va_list param); static void cli_dropbear_log(int priority, const char* format, va_list param); +static void cli_proxy_cmd(int *sock_in, int *sock_out); + #if defined(DBMULTI_dbclient) || !defined(DROPBEAR_MULTI) #if defined(DBMULTI_dbclient) && defined(DROPBEAR_MULTI) int cli_main(int argc, char ** argv) { @@ -39,7 +41,7 @@ int main(int argc, char ** argv) { #endif - int sock; + int sock_in, sock_out; char* error = NULL; char* hostandport; int len; @@ -58,10 +60,18 @@ dropbear_exit("signal() error"); } - sock = connect_remote(cli_opts.remotehost, cli_opts.remoteport, - 0, &error); +#ifdef ENABLE_CLI_PROXYCMD + if (cli_opts.proxycmd) { + cli_proxy_cmd(&sock_in, &sock_out); + } else +#endif + { + int sock = connect_remote(cli_opts.remotehost, cli_opts.remoteport, + 0, &error); + sock_in = sock_out = sock; + } - if (sock < 0) { + if (sock_in < 0) { dropbear_exit("%s", error); } @@ -72,7 +82,7 @@ snprintf(hostandport, len, "%s:%s", cli_opts.remotehost, cli_opts.remoteport); - cli_session(sock, hostandport); + cli_session(sock_in, sock_out, hostandport); /* not reached */ return -1; @@ -112,3 +122,25 @@ fprintf(stderr, "%s: %s\n", cli_opts.progname, printbuf); } + +static void exec_proxy_cmd(void *user_data_cmd) { + const char *cmd = user_data_cmd; + char *usershell; + + usershell = m_strdup(get_user_shell()); + run_shell_command(cmd, ses.maxfd, usershell); + dropbear_exit("Failed to run '%s'\n", cmd); +} + +static void cli_proxy_cmd(int *sock_in, int *sock_out) { + int ret; + + fill_passwd(cli_opts.own_user); + + ret = spawn_command(exec_proxy_cmd, cli_opts.proxycmd, + sock_out, sock_in, NULL, NULL); + if (ret == DROPBEAR_FAILURE) { + dropbear_exit("Failed running proxy command"); + *sock_in = *sock_out = -1; + } +} diff -r df7f7da7f6e4 -r 9f583f4d59a6 cli-runopts.c --- a/cli-runopts.c Fri Sep 12 17:23:56 2008 +0000 +++ b/cli-runopts.c Tue Sep 23 13:16:22 2008 +0000 @@ -33,18 +33,23 @@ cli_runopts cli_opts; /* GLOBAL */ static void printhelp(); -static void parsehostname(char* userhostarg); +static void parse_hostname(const char* orighostarg); +static void parse_multihop_hostname(const char* orighostarg, const char* argv0); +static void fill_own_user(); #ifdef ENABLE_CLI_PUBKEY_AUTH static void loadidentityfile(const char* filename); #endif #ifdef ENABLE_CLI_ANYTCPFWD -static void addforward(char* str, struct TCPFwdList** fwdlist); +static void addforward(const char* str, struct TCPFwdList** fwdlist); +#endif +#ifdef ENABLE_CLI_NETCAT +static void add_netcat(const char *str); #endif static void printhelp() { fprintf(stderr, "Dropbear client v%s\n" - "Usage: %s [options] [user@]host [command]\n" + "Usage: %s [options] [user@]host[/port] [command]\n" "Options are:\n" "-p \n" "-l \n" @@ -65,6 +70,12 @@ #endif "-W (default %d, larger may be faster, max 1MB)\n" "-K (0 is never, default %d)\n" +#ifdef ENABLE_CLI_NETCAT + "-B Netcat-alike bouncing\n" +#endif +#ifdef ENABLE_CLI_PROXYCMD + "-J Use program rather than tcp connection\n" +#endif #ifdef DEBUG_TRACE "-v verbose\n" #endif @@ -87,6 +98,9 @@ #ifdef ENABLE_CLI_REMOTETCPFWD int nextisremote = 0; #endif +#ifdef ENABLE_CLI_NETCAT + int nextisnetcat = 0; +#endif char* dummy = NULL; /* Not used for anything real */ char* recv_window_arg = NULL; @@ -112,12 +126,17 @@ #ifdef ENABLE_CLI_REMOTETCPFWD cli_opts.remotefwds = NULL; #endif +#ifdef ENABLE_CLI_PROXYCMD + cli_opts.proxycmd = NULL; +#endif /* not yet opts.ipv4 = 1; opts.ipv6 = 1; */ opts.recv_window = DEFAULT_RECV_WINDOW; + fill_own_user(); + /* Iterate all the arguments */ for (i = 1; i < (unsigned int)argc; i++) { #ifdef ENABLE_CLI_PUBKEY_AUTH @@ -144,6 +163,14 @@ continue; } #endif +#ifdef ENABLE_CLI_NETCAT + if (nextisnetcat) { + TRACE(("nextisnetcat true")) + add_netcat(argv[i]); + nextisnetcat = 0; + continue; + } +#endif if (next) { /* The previous flag set a value to assign */ *next = argv[i]; @@ -199,6 +226,16 @@ nextisremote = 1; break; #endif +#ifdef ENABLE_CLI_NETCAT + case 'B': + nextisnetcat = 1; + break; +#endif +#ifdef ENABLE_CLI_PROXYCMD + case 'J': + next = &cli_opts.proxycmd; + break; +#endif case 'l': next = &cli_opts.username; break; @@ -254,9 +291,11 @@ /* Either the hostname or commands */ if (cli_opts.remotehost == NULL) { - - parsehostname(argv[i]); - +#ifdef ENABLE_CLI_MULTIHOP + parse_multihop_hostname(argv[i], argv[0]); +#else + parse_hostname(argv[i]); +#endif } else { /* this is part of the commands to send - after this we @@ -283,6 +322,8 @@ } } + /* And now a few sanity checks and setup */ + if (cli_opts.remotehost == NULL) { printhelp(); exit(EXIT_FAILURE); @@ -307,21 +348,23 @@ dropbear_exit("command required for -f"); } - if (recv_window_arg) - { + if (recv_window_arg) { opts.recv_window = atol(recv_window_arg); - if (opts.recv_window == 0 || opts.recv_window > MAX_RECV_WINDOW) - { + if (opts.recv_window == 0 || opts.recv_window > MAX_RECV_WINDOW) { dropbear_exit("Bad recv window '%s'", recv_window_arg); } } if (keepalive_arg) { - opts.keepalive_secs = strtoul(keepalive_arg, NULL, 10); - if (opts.keepalive_secs == 0 && errno == EINVAL) - { + if (m_str_to_uint(keepalive_arg, &opts.keepalive_secs) == DROPBEAR_FAILURE) { dropbear_exit("Bad keepalive '%s'", keepalive_arg); } } + +#ifdef ENABLE_CLI_NETCAT + if (cli_opts.cmd && cli_opts.netcat_host) { + dropbear_log(LOG_INFO, "Ignoring command '%s' in netcat mode", cli_opts.cmd); + } +#endif } @@ -350,16 +393,77 @@ } #endif +#ifdef ENABLE_CLI_MULTIHOP -/* Parses a [user@]hostname argument. userhostarg is the argv[i] corresponding - * - note that it will be modified */ -static void parsehostname(char* orighostarg) { +/* Sets up 'onion-forwarding' connections. This will spawn + * a separate dbclient process for each hop. + * As an example, if the cmdline is + * dbclient wrt,madako,canyons + * then we want to run: + * dbclient -J "dbclient -B canyons:22 wrt,madako" canyons + * and then the inner dbclient will recursively run: + * dbclient -J "dbclient -B madako:22 wrt" madako + * etc for as many hosts as we want. + * + * Ports for hosts can be specified as host/port. + */ +static void parse_multihop_hostname(const char* orighostarg, const char* argv0) { + char *userhostarg = NULL; + char *last_hop = NULL;; + char *remainder = NULL; + + /* both scp and rsync parse a user@host argument + * and turn it into "-l user host". This breaks + * for our multihop syntax, so we suture it back together. + * This will break usernames that have both '@' and ',' in them, + * though that should be fairly uncommon. */ + if (cli_opts.username + && strchr(cli_opts.username, ',') + && strchr(cli_opts.username, '@')) { + unsigned int len = strlen(orighostarg) + strlen(cli_opts.username) + 2; + userhostarg = m_malloc(len); + snprintf(userhostarg, len, "%s@%s", cli_opts.username, orighostarg); + } else { + userhostarg = m_strdup(orighostarg); + } - uid_t uid; - struct passwd *pw = NULL; + last_hop = strrchr(userhostarg, ','); + if (last_hop) { + if (last_hop == userhostarg) { + dropbear_exit("Bad multi-hop hostnames"); + } + *last_hop = '\0'; + last_hop++; + remainder = userhostarg; + userhostarg = last_hop; + } + + parse_hostname(userhostarg); + + if (last_hop) { + /* Set up the proxycmd */ + unsigned int cmd_len = 0; + if (cli_opts.proxycmd) { + dropbear_exit("-J can't be used with multihop mode"); + } + if (cli_opts.remoteport == NULL) { + cli_opts.remoteport = "22"; + } + cmd_len = strlen(remainder) + + strlen(cli_opts.remotehost) + strlen(cli_opts.remoteport) + + strlen(argv0) + 30; + cli_opts.proxycmd = m_malloc(cmd_len); + snprintf(cli_opts.proxycmd, cmd_len, "%s -B %s:%s %s", + argv0, cli_opts.remotehost, cli_opts.remoteport, remainder); + } +} +#endif /* !ENABLE_CLI_MULTIHOP */ + +/* Parses a [user@]hostname[/port] argument. */ +static void parse_hostname(const char* orighostarg) { char *userhostarg = NULL; + char *port = NULL; - /* We probably don't want to be editing argvs */ userhostarg = m_strdup(orighostarg); cli_opts.remotehost = strchr(userhostarg, '@'); @@ -374,14 +478,13 @@ } if (cli_opts.username == NULL) { - uid = getuid(); - - pw = getpwuid(uid); - if (pw == NULL || pw->pw_name == NULL) { - dropbear_exit("Unknown own user"); - } + cli_opts.username = m_strdup(cli_opts.own_user); + } - cli_opts.username = m_strdup(pw->pw_name); + port = strchr(cli_opts.remotehost, '/'); + if (port) { + *port = '\0'; + cli_opts.remoteport = port+1; } if (cli_opts.remotehost[0] == '\0') { @@ -389,10 +492,61 @@ } } +#ifdef ENABLE_CLI_NETCAT +static void add_netcat(const char* origstr) { + char *portstr = NULL; + + char * str = m_strdup(origstr); + + portstr = strchr(str, ':'); + if (portstr == NULL) { + TRACE(("No netcat port")) + goto fail; + } + *portstr = '\0'; + portstr++; + + if (strchr(portstr, ':')) { + TRACE(("Multiple netcat colons")) + goto fail; + } + + if (m_str_to_uint(portstr, &cli_opts.netcat_port) == DROPBEAR_FAILURE) { + TRACE(("bad netcat port")) + goto fail; + } + + if (cli_opts.netcat_port > 65535) { + TRACE(("too large netcat port")) + goto fail; + } + + cli_opts.netcat_host = str; + return; + +fail: + dropbear_exit("Bad netcat endpoint '%s'", origstr); +} +#endif + +static void fill_own_user() { + uid_t uid; + struct passwd *pw = NULL; + + uid = getuid(); + + pw = getpwuid(uid); + if (pw == NULL || pw->pw_name == NULL) { + dropbear_exit("Unknown own user"); + } + + cli_opts.own_user = m_strdup(pw->pw_name); +} + #ifdef ENABLE_CLI_ANYTCPFWD /* Turn a "listenport:remoteaddr:remoteport" string into into a forwarding * set, and add it to the forwarding list */ -static void addforward(char* origstr, struct TCPFwdList** fwdlist) { +static void addforward(const char* origstr, struct TCPFwdList** fwdlist) { char * listenport = NULL; char * connectport = NULL; @@ -428,15 +582,13 @@ /* Now we check the ports - note that the port ints are unsigned, * the check later only checks for >= MAX_PORT */ - newfwd->listenport = strtol(listenport, NULL, 10); - if (errno != 0) { - TRACE(("bad listenport strtol")) + if (m_str_to_uint(listenport, &newfwd->listenport) == DROPBEAR_FAILURE) { + TRACE(("bad listenport strtoul")) goto fail; } - newfwd->connectport = strtol(connectport, NULL, 10); - if (errno != 0) { - TRACE(("bad connectport strtol")) + if (m_str_to_uint(connectport, &newfwd->connectport) == DROPBEAR_FAILURE) { + TRACE(("bad connectport strtoul")) goto fail; } diff -r df7f7da7f6e4 -r 9f583f4d59a6 cli-session.c --- a/cli-session.c Fri Sep 12 17:23:56 2008 +0000 +++ b/cli-session.c Tue Sep 23 13:16:22 2008 +0000 @@ -74,13 +74,13 @@ NULL /* Null termination */ }; -void cli_session(int sock, char* remotehost) { +void cli_session(int sock_in, int sock_out, char* remotehost) { seedrandom(); crypto_init(); - common_session_init(sock, remotehost); + common_session_init(sock_in, sock_out, remotehost); chaninitialise(cli_chantypes); @@ -197,20 +197,6 @@ TRACE(("leave cli_sessionloop: cli_auth_try")) return; - /* - case USERAUTH_SUCCESS_RCVD: - send_msg_service_request(SSH_SERVICE_CONNECTION); - cli_ses.state = SERVICE_CONN_REQ_SENT; - TRACE(("leave cli_sessionloop: sent ssh-connection service req")) - return; - - case SERVICE_CONN_ACCEPT_RCVD: - cli_send_chansess_request(); - TRACE(("leave cli_sessionloop: cli_send_chansess_request")) - cli_ses.state = SESSION_RUNNING; - return; - */ - case USERAUTH_SUCCESS_RCVD: if (cli_opts.backgrounded) { @@ -235,7 +221,13 @@ #ifdef ENABLE_CLI_REMOTETCPFWD setup_remotetcp(); #endif - if (!cli_opts.no_cmd) { + +#ifdef ENABLE_CLI_NETCAT + if (cli_opts.netcat_host) { + cli_send_netcat_request(); + } else +#endif + if (!cli_opts.no_cmd) { cli_send_chansess_request(); } TRACE(("leave cli_sessionloop: running")) @@ -294,8 +286,10 @@ /* XXX TODO perhaps print a friendlier message if we get this but have * already sent/received disconnect message(s) ??? */ - close(ses.sock); - ses.sock = -1; + m_close(ses.sock_in); + m_close(ses.sock_out); + ses.sock_in = -1; + ses.sock_out = -1; dropbear_exit("remote closed the connection"); } diff -r df7f7da7f6e4 -r 9f583f4d59a6 common-channel.c --- a/common-channel.c Fri Sep 12 17:23:56 2008 +0000 +++ b/common-channel.c Tue Sep 23 13:16:22 2008 +0000 @@ -572,6 +572,11 @@ channel = getchannel(); + if (channel->sent_close) { + TRACE(("leave recv_msg_channel_request: already closed channel")) + return; + } + if (channel->type->reqhandler) { channel->type->reqhandler(channel); } else { diff -r df7f7da7f6e4 -r 9f583f4d59a6 common-session.c --- a/common-session.c Fri Sep 12 17:23:56 2008 +0000 +++ b/common-session.c Tue Sep 23 13:16:22 2008 +0000 @@ -52,14 +52,15 @@ /* called only at the start of a session, set up initial state */ -void common_session_init(int sock, char* remotehost) { +void common_session_init(int sock_in, int sock_out, char* remotehost) { TRACE(("enter session_init")) ses.remotehost = remotehost; - ses.sock = sock; - ses.maxfd = sock; + ses.sock_in = sock_in; + ses.sock_out = sock_out; + ses.maxfd = MAX(sock_in, sock_out); ses.connect_time = 0; ses.last_packet_time = 0; @@ -137,11 +138,11 @@ FD_ZERO(&writefd); FD_ZERO(&readfd); dropbear_assert(ses.payload == NULL); - if (ses.sock != -1) { - FD_SET(ses.sock, &readfd); - if (!isempty(&ses.writequeue)) { - FD_SET(ses.sock, &writefd); - } + if (ses.sock_in != -1) { + FD_SET(ses.sock_in, &readfd); + } + if (ses.sock_out != -1 && !isempty(&ses.writequeue)) { + FD_SET(ses.sock_out, &writefd); } /* We get woken up when signal handlers write to this pipe. @@ -183,12 +184,14 @@ checktimeouts(); /* process session socket's incoming/outgoing data */ - if (ses.sock != -1) { - if (FD_ISSET(ses.sock, &writefd) && !isempty(&ses.writequeue)) { + if (ses.sock_out != -1) { + if (FD_ISSET(ses.sock_out, &writefd) && !isempty(&ses.writequeue)) { write_packet(); } + } - if (FD_ISSET(ses.sock, &readfd)) { + if (ses.sock_in != -1) { + if (FD_ISSET(ses.sock_in, &readfd)) { read_packet(); } @@ -248,14 +251,14 @@ int i; /* write our version string, this blocks */ - if (atomicio(write, ses.sock, LOCAL_IDENT "\r\n", + if (atomicio(write, ses.sock_out, LOCAL_IDENT "\r\n", strlen(LOCAL_IDENT "\r\n")) == DROPBEAR_FAILURE) { ses.remoteclosed(); } /* If they send more than 50 lines, something is wrong */ for (i = 0; i < 50; i++) { - len = ident_readln(ses.sock, linebuf, sizeof(linebuf)); + len = ident_readln(ses.sock_in, linebuf, sizeof(linebuf)); if (len < 0 && errno != EINTR) { /* It failed */ @@ -411,3 +414,35 @@ ret = MIN(opts.keepalive_secs, ret); return ret; } + +const char* get_user_shell() { + /* an empty shell should be interpreted as "/bin/sh" */ + if (ses.authstate.pw_shell[0] == '\0') { + return "/bin/sh"; + } else { + return ses.authstate.pw_shell; + } +} +void fill_passwd(const char* username) { + struct passwd *pw = NULL; + if (ses.authstate.pw_name) + m_free(ses.authstate.pw_name); + if (ses.authstate.pw_dir) + m_free(ses.authstate.pw_dir); + if (ses.authstate.pw_shell) + m_free(ses.authstate.pw_shell); + if (ses.authstate.pw_passwd) + m_free(ses.authstate.pw_passwd); + + pw = getpwnam(username); + if (!pw) { + return; + } + ses.authstate.pw_uid = pw->pw_uid; + ses.authstate.pw_gid = pw->pw_gid; + ses.authstate.pw_name = m_strdup(pw->pw_name); + ses.authstate.pw_dir = m_strdup(pw->pw_dir); + ses.authstate.pw_shell = m_strdup(pw->pw_shell); + ses.authstate.pw_passwd = m_strdup(pw->pw_passwd); +} + diff -r df7f7da7f6e4 -r 9f583f4d59a6 dbutil.c --- a/dbutil.c Fri Sep 12 17:23:56 2008 +0000 +++ b/dbutil.c Tue Sep 23 13:16:22 2008 +0000 @@ -146,7 +146,7 @@ } va_start(param, format); - fprintf(stderr, "TRACE: "); + fprintf(stderr, "TRACE (%d): ", getpid()); vfprintf(stderr, format, param); fprintf(stderr, "\n"); va_end(param); @@ -321,9 +321,10 @@ if (err) { if (errstring != NULL && *errstring == NULL) { int len; - len = 20 + strlen(gai_strerror(err)); + len = 100 + strlen(gai_strerror(err)); *errstring = (char*)m_malloc(len); - snprintf(*errstring, len, "Error resolving: %s", gai_strerror(err)); + snprintf(*errstring, len, "Error resolving '%s' port '%s'. %s", + remotehost, remoteport, gai_strerror(err)); } TRACE(("Error resolving: %s", gai_strerror(err))) return -1; @@ -389,6 +390,141 @@ return sock; } +/* Sets up a pipe for a, returning three non-blocking file descriptors + * and the pid. exec_fn is the function that will actually execute the child process, + * it will be run after the child has fork()ed, and is passed exec_data. + * If ret_errfd == NULL then stderr will not be captured. + * ret_pid can be passed as NULL to discard the pid. */ +int spawn_command(void(*exec_fn)(void *user_data), void *exec_data, + int *ret_writefd, int *ret_readfd, int *ret_errfd, pid_t *ret_pid) { + int infds[2]; + int outfds[2]; + int errfds[2]; + pid_t pid; + + const int FDIN = 0; + const int FDOUT = 1; + + /* redirect stdin/stdout/stderr */ + if (pipe(infds) != 0) { + return DROPBEAR_FAILURE; + } + if (pipe(outfds) != 0) { + return DROPBEAR_FAILURE; + } + if (ret_errfd && pipe(errfds) != 0) { + return DROPBEAR_FAILURE; + } + +#ifdef __uClinux__ + pid = vfork(); +#else + pid = fork(); +#endif + + if (pid < 0) { + return DROPBEAR_FAILURE; + } + + if (!pid) { + /* child */ + + TRACE(("back to normal sigchld")) + /* Revert to normal sigchld handling */ + if (signal(SIGCHLD, SIG_DFL) == SIG_ERR) { + dropbear_exit("signal() error"); + } + + /* redirect stdin/stdout */ + + if ((dup2(infds[FDIN], STDIN_FILENO) < 0) || + (dup2(outfds[FDOUT], STDOUT_FILENO) < 0) || + (ret_errfd && dup2(errfds[FDOUT], STDERR_FILENO) < 0)) { + TRACE(("leave noptycommand: error redirecting FDs")) + dropbear_exit("child dup2() failure"); + } + + close(infds[FDOUT]); + close(infds[FDIN]); + close(outfds[FDIN]); + close(outfds[FDOUT]); + if (ret_errfd) + { + close(errfds[FDIN]); + close(errfds[FDOUT]); + } + + exec_fn(exec_data); + /* not reached */ + return DROPBEAR_FAILURE; + } else { + /* parent */ + close(infds[FDIN]); + close(outfds[FDOUT]); + + setnonblocking(outfds[FDIN]); + setnonblocking(infds[FDOUT]); + + if (ret_errfd) { + close(errfds[FDOUT]); + setnonblocking(errfds[FDIN]); + } + + if (ret_pid) { + *ret_pid = pid; + } + + *ret_writefd = infds[FDOUT]; + *ret_readfd = outfds[FDIN]; + if (ret_errfd) { + *ret_errfd = errfds[FDIN]; + } + return DROPBEAR_SUCCESS; + } +} + +/* Runs a command with "sh -c". Will close FDs (except stdin/stdout/stderr) and + * re-enabled SIGPIPE. If cmd is NULL, will run a login shell. + */ +void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) { + char * argv[4]; + char * baseshell = NULL; + unsigned int i; + + baseshell = basename(usershell); + + if (cmd != NULL) { + argv[0] = baseshell; + } else { + /* a login shell should be "-bash" for "/bin/bash" etc */ + int len = strlen(baseshell) + 2; /* 2 for "-" */ + argv[0] = (char*)m_malloc(len); + snprintf(argv[0], len, "-%s", baseshell); + } + + if (cmd != NULL) { + argv[1] = "-c"; + argv[2] = (char*)cmd; + argv[3] = NULL; + } else { + /* construct a shell of the form "-bash" etc */ + argv[1] = NULL; + } + + /* Re-enable SIGPIPE for the executed process */ + if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) { + dropbear_exit("signal() error"); + } + + /* close file descriptors except stdin/stdout/stderr + * Need to be sure FDs are closed here to avoid reading files as root */ + for (i = 3; i <= maxfd; i++) { + m_close(i); + } + + execv(usershell, argv); +} + /* Return a string representation of the socket address passed. The return * value is allocated with malloc() */ unsigned char * getaddrstring(struct sockaddr_storage* addr, int withport) { @@ -699,3 +835,17 @@ lim.rlim_cur = lim.rlim_max = 0; setrlimit(RLIMIT_CORE, &lim); } + +/* Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE, with the result in *val */ +int m_str_to_uint(const char* str, unsigned int *val) { + errno = 0; + *val = strtoul(str, NULL, 10); + /* The c99 spec doesn't actually seem to define EINVAL, but most platforms + * I've looked at mention it in their manpage */ + if ((*val == 0 && errno == EINVAL) + || (*val == ULONG_MAX && errno == ERANGE)) { + return DROPBEAR_FAILURE; + } else { + return DROPBEAR_SUCCESS; + } +} diff -r df7f7da7f6e4 -r 9f583f4d59a6 dbutil.h --- a/dbutil.h Fri Sep 12 17:23:56 2008 +0000 +++ b/dbutil.h Tue Sep 23 13:16:22 2008 +0000 @@ -49,6 +49,9 @@ unsigned char * getaddrstring(struct sockaddr_storage* addr, int withport); int dropbear_listen(const char* address, const char* port, int *socks, unsigned int sockcount, char **errstring, int *maxfd); +int spawn_command(void(*exec_fn)(void *user_data), void *exec_data, + int *writefd, int *readfd, int *errfd, pid_t *pid); +void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell); int connect_remote(const char* remotehost, const char* remoteport, int nonblocking, char ** errstring); char* getaddrhostname(struct sockaddr_storage * addr); @@ -64,6 +67,7 @@ void m_burn(void* data, unsigned int len); void setnonblocking(int fd); void disallow_core(); +int m_str_to_uint(const char* str, unsigned int *val); /* Used to force mp_ints to be initialised */ #define DEF_MP_INT(X) mp_int X = {0, 0, 0, NULL} diff -r df7f7da7f6e4 -r 9f583f4d59a6 debug.h --- a/debug.h Fri Sep 12 17:23:56 2008 +0000 +++ b/debug.h Tue Sep 23 13:16:22 2008 +0000 @@ -39,7 +39,7 @@ * Caution: Don't use this in an unfriendly environment (ie unfirewalled), * since the printing may not sanitise strings etc. This will add a reasonable * amount to your executable size. */ -/*#define DEBUG_TRACE*/ +#define DEBUG_TRACE /* All functions writing to the cleartext payload buffer call * CHECKCLEARTOWRITE() before writing. This is only really useful if you're diff -r df7f7da7f6e4 -r 9f583f4d59a6 genrsa.c --- a/genrsa.c Fri Sep 12 17:23:56 2008 +0000 +++ b/genrsa.c Tue Sep 23 13:16:22 2008 +0000 @@ -62,17 +62,13 @@ exit(1); } - /* PuTTY doesn't like it if the modulus isn't a multiple of 8 bits, - * so we just generate them until we get one which is OK */ getrsaprime(key->p, &pminus, key->e, size/2); - do { - getrsaprime(key->q, &qminus, key->e, size/2); + getrsaprime(key->q, &qminus, key->e, size/2); - if (mp_mul(key->p, key->q, key->n) != MP_OKAY) { - fprintf(stderr, "rsa generation failed\n"); - exit(1); - } - } while (mp_count_bits(key->n) % 8 != 0); + if (mp_mul(key->p, key->q, key->n) != MP_OKAY) { + fprintf(stderr, "rsa generation failed\n"); + exit(1); + } /* lcm(p-1, q-1) */ if (mp_lcm(&pminus, &qminus, &lcm) != MP_OKAY) { diff -r df7f7da7f6e4 -r 9f583f4d59a6 keyimport.c --- a/keyimport.c Fri Sep 12 17:23:56 2008 +0000 +++ b/keyimport.c Tue Sep 23 13:16:22 2008 +0000 @@ -701,7 +701,6 @@ int nnumbers = -1, pos, len, seqlen, i; char *header = NULL, *footer = NULL; char zero[1]; - unsigned char iv[8]; int ret = 0; FILE *fp; int keytype = -1; diff -r df7f7da7f6e4 -r 9f583f4d59a6 options.h --- a/options.h Fri Sep 12 17:23:56 2008 +0000 +++ b/options.h Tue Sep 23 13:16:22 2008 +0000 @@ -60,12 +60,20 @@ #define ENABLE_CLI_LOCALTCPFWD #define ENABLE_CLI_REMOTETCPFWD +/* Allow using -J to run the connection through a + pipe to a program, rather the normal TCP connection */ +#define ENABLE_CLI_PROXYCMD + #define ENABLE_SVR_LOCALTCPFWD #define ENABLE_SVR_REMOTETCPFWD /* Enable Authentication Agent Forwarding - server only for now */ #define ENABLE_AGENTFWD +/* Enable "Netcat mode". TODO describe here. */ +#define ENABLE_CLI_NETCAT + + /* Encryption - at least one required. * RFC Draft requires 3DES and recommends AES128 for interoperability. * Including multiple keysize variants the same cipher @@ -132,8 +140,8 @@ * but there's an interface via a PAM module - don't bother using it otherwise. * You can't enable both PASSWORD and PAM. */ -#define ENABLE_SVR_PASSWORD_AUTH -/*#define ENABLE_SVR_PAM_AUTH */ /* requires ./configure --enable-pam */ +//#define ENABLE_SVR_PASSWORD_AUTH +#define ENABLE_SVR_PAM_AUTH /* requires ./configure --enable-pam */ #define ENABLE_SVR_PUBKEY_AUTH /* Wether to ake public key options in authorized_keys file into account */ @@ -404,6 +412,10 @@ #define USING_LISTENERS #endif +#if defined(ENABLE_CLI_NETCAT) && defined(ENABLE_CLI_PROXYCMD) +#define ENABLE_CLI_MULTIHOP +#endif + #if defined(DROPBEAR_CLIENT) || defined(ENABLE_SVR_PUBKEY_AUTH) #define DROPBEAR_KEY_LINES /* ie we're using authorized_keys or known_hosts */ #endif diff -r df7f7da7f6e4 -r 9f583f4d59a6 packet.c --- a/packet.c Fri Sep 12 17:23:56 2008 +0000 +++ b/packet.c Tue Sep 23 13:16:22 2008 +0000 @@ -61,7 +61,7 @@ len = writebuf->len - writebuf->pos; dropbear_assert(len > 0); /* Try to write as much as possible */ - written = write(ses.sock, buf_getptr(writebuf, len), len); + written = write(ses.sock_out, buf_getptr(writebuf, len), len); if (written < 0) { if (errno == EINTR) { @@ -122,7 +122,7 @@ * mightn't be any available (EAGAIN) */ dropbear_assert(ses.readbuf != NULL); maxlen = ses.readbuf->len - ses.readbuf->pos; - len = read(ses.sock, buf_getptr(ses.readbuf, maxlen), maxlen); + len = read(ses.sock_in, buf_getptr(ses.readbuf, maxlen), maxlen); if (len == 0) { ses.remoteclosed(); @@ -171,7 +171,7 @@ maxlen = blocksize - ses.readbuf->pos; /* read the rest of the packet if possible */ - len = read(ses.sock, buf_getwriteptr(ses.readbuf, maxlen), + len = read(ses.sock_in, buf_getwriteptr(ses.readbuf, maxlen), maxlen); if (len == 0) { ses.remoteclosed(); diff -r df7f7da7f6e4 -r 9f583f4d59a6 runopts.h --- a/runopts.h Fri Sep 12 17:23:56 2008 +0000 +++ b/runopts.h Tue Sep 23 13:16:22 2008 +0000 @@ -37,7 +37,7 @@ int listen_fwd_all; #endif unsigned int recv_window; - time_t keepalive_secs; + unsigned int keepalive_secs; } runopts; @@ -101,6 +101,7 @@ char *remotehost; char *remoteport; + char *own_user; char *username; char *cmd; @@ -118,6 +119,14 @@ struct TCPFwdList * localfwds; #endif +#ifdef ENABLE_CLI_NETCAT + char *netcat_host; + unsigned int netcat_port; +#endif +#ifdef ENABLE_CLI_PROXYCMD + char *proxycmd; +#endif + } cli_runopts; extern cli_runopts cli_opts; diff -r df7f7da7f6e4 -r 9f583f4d59a6 session.h --- a/session.h Fri Sep 12 17:23:56 2008 +0000 +++ b/session.h Tue Sep 23 13:16:22 2008 +0000 @@ -41,12 +41,14 @@ extern int sessinitdone; /* Is set to 0 somewhere */ extern int exitflag; -void common_session_init(int sock, char* remotehost); +void common_session_init(int sock_in, int sock_out, char* remotehost); void session_loop(void(*loophandler)()); void common_session_cleanup(); void session_identification(); void send_msg_ignore(); +const char* get_user_shell(); +void fill_passwd(const char* username); /* Server */ void svr_session(int sock, int childpipe, char *remotehost, char *addrstring); @@ -54,7 +56,7 @@ void svr_dropbear_log(int priority, const char* format, va_list param); /* Client */ -void cli_session(int sock, char *remotehost); +void cli_session(int sock_in, int sock_out, char *remotehost); void cli_session_cleanup(); void cleantext(unsigned char* dirtytext); @@ -97,7 +99,8 @@ (cleared after auth once we're not respecting AUTH_TIMEOUT any more) */ - int sock; + int sock_in; + int sock_out; unsigned char *remotehost; /* the peer hostname */ diff -r df7f7da7f6e4 -r 9f583f4d59a6 svr-auth.c --- a/svr-auth.c Fri Sep 12 17:23:56 2008 +0000 +++ b/svr-auth.c Tue Sep 23 13:16:22 2008 +0000 @@ -203,29 +203,6 @@ m_free(methodname); } -static void fill_passwd(const char* username) { - struct passwd *pw = NULL; - if (ses.authstate.pw_name) - m_free(ses.authstate.pw_name); - if (ses.authstate.pw_dir) - m_free(ses.authstate.pw_dir); - if (ses.authstate.pw_shell) - m_free(ses.authstate.pw_shell); - if (ses.authstate.pw_passwd) - m_free(ses.authstate.pw_passwd); - - pw = getpwnam(username); - if (!pw) { - return; - } - ses.authstate.pw_uid = pw->pw_uid; - ses.authstate.pw_gid = pw->pw_gid; - ses.authstate.pw_name = m_strdup(pw->pw_name); - ses.authstate.pw_dir = m_strdup(pw->pw_dir); - ses.authstate.pw_shell = m_strdup(pw->pw_shell); - ses.authstate.pw_passwd = m_strdup(pw->pw_passwd); -} - /* Check that the username exists, has a non-empty password, and has a valid * shell. diff -r df7f7da7f6e4 -r 9f583f4d59a6 svr-chansession.c --- a/svr-chansession.c Fri Sep 12 17:23:56 2008 +0000 +++ b/svr-chansession.c Tue Sep 23 13:16:22 2008 +0000 @@ -48,7 +48,7 @@ static int noptycommand(struct Channel *channel, struct ChanSess *chansess); static int ptycommand(struct Channel *channel, struct ChanSess *chansess); static int sessionwinchange(struct ChanSess *chansess); -static void execchild(struct ChanSess *chansess); +static void execchild(void *user_data_chansess); static void addchildpid(struct ChanSess *chansess, pid_t pid); static void sesssigchild_handler(int val); static void closechansess(struct Channel *channel); @@ -645,100 +645,37 @@ * pty. * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ static int noptycommand(struct Channel *channel, struct ChanSess *chansess) { - - int infds[2]; - int outfds[2]; - int errfds[2]; - pid_t pid; - unsigned int i; + int ret; TRACE(("enter noptycommand")) - - /* redirect stdin/stdout/stderr */ - if (pipe(infds) != 0) - return DROPBEAR_FAILURE; - if (pipe(outfds) != 0) - return DROPBEAR_FAILURE; - if (pipe(errfds) != 0) - return DROPBEAR_FAILURE; + ret = spawn_command(execchild, chansess, + &channel->writefd, &channel->readfd, &channel->errfd, + &chansess->pid); -#ifdef __uClinux__ - pid = vfork(); -#else - pid = fork(); -#endif - - if (pid < 0) - return DROPBEAR_FAILURE; + if (ret == DROPBEAR_FAILURE) { + return ret; + } - if (!pid) { - /* child */ - - TRACE(("back to normal sigchld")) - /* Revert to normal sigchld handling */ - if (signal(SIGCHLD, SIG_DFL) == SIG_ERR) { - dropbear_exit("signal() error"); - } + ses.maxfd = MAX(ses.maxfd, channel->writefd); + ses.maxfd = MAX(ses.maxfd, channel->readfd); + ses.maxfd = MAX(ses.maxfd, channel->errfd); - /* redirect stdin/stdout */ -#define FDIN 0 -#define FDOUT 1 - if ((dup2(infds[FDIN], STDIN_FILENO) < 0) || - (dup2(outfds[FDOUT], STDOUT_FILENO) < 0) || - (dup2(errfds[FDOUT], STDERR_FILENO) < 0)) { - TRACE(("leave noptycommand: error redirecting FDs")) - return DROPBEAR_FAILURE; - } - - close(infds[FDOUT]); - close(infds[FDIN]); - close(outfds[FDIN]); - close(outfds[FDOUT]); - close(errfds[FDIN]); - close(errfds[FDOUT]); - - execchild(chansess); - /* not reached */ + addchildpid(chansess, chansess->pid); - } else { - /* parent */ - TRACE(("continue noptycommand: parent")) - chansess->pid = pid; - TRACE(("child pid is %d", pid)) - - addchildpid(chansess, pid); - - if (svr_ses.lastexit.exitpid != -1) { - TRACE(("parent side: lastexitpid is %d", svr_ses.lastexit.exitpid)) - /* The child probably exited and the signal handler triggered - * possibly before we got around to adding the childpid. So we fill - * out its data manually */ - for (i = 0; i < svr_ses.childpidsize; i++) { - if (svr_ses.childpids[i].pid == svr_ses.lastexit.exitpid) { - TRACE(("found match for lastexitpid")) - svr_ses.childpids[i].chansess->exit = svr_ses.lastexit; - svr_ses.lastexit.exitpid = -1; - } + if (svr_ses.lastexit.exitpid != -1) { + TRACE(("parent side: lastexitpid is %d", svr_ses.lastexit.exitpid)) + /* The child probably exited and the signal handler triggered + * possibly before we got around to adding the childpid. So we fill + * out its data manually */ + int i; + for (i = 0; i < svr_ses.childpidsize; i++) { + if (svr_ses.childpids[i].pid == svr_ses.lastexit.exitpid) { + TRACE(("found match for lastexitpid")) + svr_ses.childpids[i].chansess->exit = svr_ses.lastexit; + svr_ses.lastexit.exitpid = -1; } } - - close(infds[FDIN]); - close(outfds[FDOUT]); - close(errfds[FDOUT]); - channel->writefd = infds[FDOUT]; - channel->readfd = outfds[FDIN]; - channel->errfd = errfds[FDIN]; - ses.maxfd = MAX(ses.maxfd, channel->writefd); - ses.maxfd = MAX(ses.maxfd, channel->readfd); - ses.maxfd = MAX(ses.maxfd, channel->errfd); - - setnonblocking(channel->readfd); - setnonblocking(channel->writefd); - setnonblocking(channel->errfd); - } -#undef FDIN -#undef FDOUT TRACE(("leave noptycommand")) return DROPBEAR_SUCCESS; @@ -883,12 +820,9 @@ /* Clean up, drop to user privileges, set up the environment and execute * the command/shell. This function does not return. */ -static void execchild(struct ChanSess *chansess) { - - char *argv[4]; - char * usershell = NULL; - char * baseshell = NULL; - unsigned int i; +static void execchild(void *user_data) { + struct ChanSess *chansess = user_data; + char *usershell = NULL; /* with uClinux we'll have vfork()ed, so don't want to overwrite the * hostkey. can't think of a workaround to clear it */ @@ -901,12 +835,6 @@ reseedrandom(); #endif - /* close file descriptors except stdin/stdout/stderr - * Need to be sure FDs are closed here to avoid reading files as root */ - for (i = 3; i <= (unsigned int)ses.maxfd; i++) { - m_close(i); - } - /* clear environment */ /* if we're debugging using valgrind etc, we need to keep the LD_PRELOAD * etc. This is hazardous, so should only be used for debugging. */ @@ -945,18 +873,11 @@ } } - /* an empty shell should be interpreted as "/bin/sh" */ - if (ses.authstate.pw_shell[0] == '\0') { - usershell = "/bin/sh"; - } else { - usershell = ses.authstate.pw_shell; - } - /* set env vars */ addnewvar("USER", ses.authstate.pw_name); addnewvar("LOGNAME", ses.authstate.pw_name); addnewvar("HOME", ses.authstate.pw_dir); - addnewvar("SHELL", usershell); + addnewvar("SHELL", get_user_shell()); if (chansess->term != NULL) { addnewvar("TERM", chansess->term); } @@ -975,32 +896,8 @@ agentset(chansess); #endif - /* Re-enable SIGPIPE for the executed process */ - if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) { - dropbear_exit("signal() error"); - } - - baseshell = basename(usershell); - - if (chansess->cmd != NULL) { - argv[0] = baseshell; - } else { - /* a login shell should be "-bash" for "/bin/bash" etc */ - int len = strlen(baseshell) + 2; /* 2 for "-" */ - argv[0] = (char*)m_malloc(len); - snprintf(argv[0], len, "-%s", baseshell); - } - - if (chansess->cmd != NULL) { - argv[1] = "-c"; - argv[2] = chansess->cmd; - argv[3] = NULL; - } else { - /* construct a shell of the form "-bash" etc */ - argv[1] = NULL; - } - - execv(usershell, argv); + usershell = m_strdup(get_user_shell()); + run_shell_command(chansess->cmd, ses.maxfd, usershell); /* only reached on error */ dropbear_exit("child failed"); diff -r df7f7da7f6e4 -r 9f583f4d59a6 svr-runopts.c --- a/svr-runopts.c Fri Sep 12 17:23:56 2008 +0000 +++ b/svr-runopts.c Tue Sep 23 13:16:22 2008 +0000 @@ -284,16 +284,13 @@ if (recv_window_arg) { opts.recv_window = atol(recv_window_arg); - if (opts.recv_window == 0 || opts.recv_window > MAX_RECV_WINDOW) - { + if (opts.recv_window == 0 || opts.recv_window > MAX_RECV_WINDOW) { dropbear_exit("Bad recv window '%s'", recv_window_arg); } } if (keepalive_arg) { - opts.keepalive_secs = strtoul(keepalive_arg, NULL, 10); - if (opts.keepalive_secs == 0 && errno == EINVAL) - { + if (m_str_to_uint(keepalive_arg, &opts.keepalive_secs) == DROPBEAR_FAILURE) { dropbear_exit("Bad keepalive '%s'", keepalive_arg); } } diff -r df7f7da7f6e4 -r 9f583f4d59a6 svr-session.c --- a/svr-session.c Fri Sep 12 17:23:56 2008 +0000 +++ b/svr-session.c Tue Sep 23 13:16:22 2008 +0000 @@ -80,7 +80,7 @@ reseedrandom(); crypto_init(); - common_session_init(sock, remotehost); + common_session_init(sock, sock, remotehost); /* Initialise server specific parts of the session */ svr_ses.childpipe = childpipe; @@ -186,7 +186,7 @@ localtime(×ec)) == 0) { /* upon failure, just print the epoch-seconds time. */ - snprintf(datestr, sizeof(datestr), "%d", timesec); + snprintf(datestr, sizeof(datestr), "%d", (int)timesec); } fprintf(stderr, "[%d] %s %s\n", getpid(), datestr, printbuf); } @@ -195,8 +195,10 @@ /* called when the remote side closes the connection */ static void svr_remoteclosed() { - close(ses.sock); - ses.sock = -1; + m_close(ses.sock_in); + m_close(ses.sock_out); + ses.sock_in = -1; + ses.sock_out = -1; dropbear_close("Exited normally"); }