diff options
42 files changed, 1423 insertions, 586 deletions
@@ -1,3 +1,15 @@ +0.51 - Thu 27 March 2008 + +- Make a copy of password fields rather erroneously relying on getwpnam() + to be safe to call multiple times + +- If $SSH_ASKPASS_ALWAYS environment variable is set (and $SSH_ASKPASS is + as well) always use that program, ignoring isatty() and $DISPLAY + +- Wait until a process exits before the server closes a connection, so + that an exit code can be sent. This fixes problems with exit codes not + being returned, which could cause scp to fail. + 0.50 - Wed 8 August 2007 - Add DROPBEAR_PASSWORD environment variable to specify a dbclient password diff --git a/Makefile.in b/Makefile.in index f5b111f..3e6c855 100644 --- a/Makefile.in +++ b/Makefile.in @@ -23,7 +23,7 @@ COMMONOBJS=dbutil.o buffer.o \ atomicio.o compat.o fake-rfc2553.o SVROBJS=svr-kex.o svr-algo.o svr-auth.o sshpty.o \ - svr-authpasswd.o svr-authpubkey.o svr-session.o svr-service.o \ + svr-authpasswd.o svr-authpubkey.o svr-authpubkeyoptions.o svr-session.o svr-service.o \ svr-chansession.o svr-runopts.o svr-agentfwd.o svr-main.o svr-x11fwd.o\ svr-tcpfwd.o svr-authpam.o @@ -26,6 +26,7 @@ #define _AUTH_H_ #include "includes.h" +#include "chansession.h" void svr_authinitialise(); void cli_authinitialise(); @@ -38,6 +39,25 @@ void svr_auth_password(); void svr_auth_pubkey(); void svr_auth_pam(); +#ifdef ENABLE_SVR_PUBKEY_OPTIONS +int svr_pubkey_allows_agentfwd(); +int svr_pubkey_allows_tcpfwd(); +int svr_pubkey_allows_x11fwd(); +int svr_pubkey_allows_pty(); +void svr_pubkey_set_forced_command(struct ChanSess *chansess); +void svr_pubkey_options_cleanup(); +int svr_add_pubkey_options(buffer *options_buf, int line_num, const char* filename); +#else +/* no option : success */ +#define svr_pubkey_allows_agentfwd() 1 +#define svr_pubkey_allows_tcpfwd() 1 +#define svr_pubkey_allows_x11fwd() 1 +#define svr_pubkey_allows_pty() 1 +static inline void svr_pubkey_set_forced_command(struct ChanSess *chansess) { } +static inline void svr_pubkey_options_cleanup() { } +#define svr_add_pubkey_options(x,y,z) DROPBEAR_SUCCESS +#endif + /* Client functions */ void recv_msg_userauth_failure(); void recv_msg_userauth_success(); @@ -91,8 +111,15 @@ struct AuthState { logged. */ /* These are only used for the server */ - char *printableuser; /* stripped of control chars, used for logs etc */ - struct passwd * pw; + uid_t pw_uid; + gid_t pw_gid; + char *pw_dir; + char *pw_shell; + char *pw_name; + char *pw_passwd; +#ifdef ENABLE_SVR_PUBKEY_OPTIONS + struct PubKeyOptions* pubkey_options; +#endif }; @@ -113,4 +140,18 @@ struct SignKeyList { }; +#ifdef ENABLE_SVR_PUBKEY_OPTIONS +struct PubKeyOptions; +struct PubKeyOptions { + /* Flags */ + int no_port_forwarding_flag; + int no_agent_forwarding_flag; + int no_x11_forwarding_flag; + int no_pty_flag; + /* "command=" option. */ + unsigned char * forced_command; + +}; +#endif + #endif /* _AUTH_H_ */ diff --git a/chansession.h b/chansession.h index 213c285..4513b1a 100644 --- a/chansession.h +++ b/chansession.h @@ -78,6 +78,9 @@ void addnewvar(const char* param, const char* var); 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 --git a/cli-authinteract.c b/cli-authinteract.c index 5fe5bf1..7851578 100644 --- a/cli-authinteract.c +++ b/cli-authinteract.c @@ -77,6 +77,11 @@ void recv_msg_userauth_info_request() { 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 --git a/cli-chansession.c b/cli-chansession.c index c92f902..dc8e641 100644 --- a/cli-chansession.c +++ b/cli-chansession.c @@ -321,7 +321,11 @@ static void send_chansess_shell_req(struct Channel *channel) { TRACE(("enter send_chansess_shell_req")) if (cli_opts.cmd) { - reqtype = "exec"; + if (cli_opts.is_subsystem) { + reqtype = "subsystem"; + } else { + reqtype = "exec"; + } } else { reqtype = "shell"; } @@ -338,9 +342,8 @@ static void send_chansess_shell_req(struct Channel *channel) { 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 +354,12 @@ static int cli_initchansess(struct Channel *channel) { 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 +372,48 @@ static int cli_initchansess(struct Channel *channel) { } 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 +424,16 @@ void cli_send_chansess_request() { 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 @@ -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 cli_main(int argc, char ** argv) { 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 @@ int main(int argc, char ** argv) { 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 @@ int main(int argc, char ** argv) { 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 @@ static void cli_dropbear_log(int UNUSED(priority), 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 --git a/cli-runopts.c b/cli-runopts.c index c7d9b8b..d49caa2 100644 --- a/cli-runopts.c +++ b/cli-runopts.c @@ -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 <remoteport>\n" "-l <username>\n" @@ -53,6 +58,7 @@ static void printhelp() { "-N Don't run a remote command\n" "-f Run in background after auth\n" "-y Always accept remote host key if unknown\n" + "-s Request a subsystem (use for sftp)\n" #ifdef ENABLE_CLI_PUBKEY_AUTH "-i <identityfile> (multiple allowed)\n" #endif @@ -68,6 +74,12 @@ static void printhelp() { #endif "-W <receive_window_buffer> (default %d, larger may be faster, max 1MB)\n" "-K <keepalive> (0 is never, default %d)\n" +#ifdef ENABLE_CLI_NETCAT + "-B <endhost:endport> Netcat-alike bouncing\n" +#endif +#ifdef ENABLE_CLI_PROXYCMD + "-J <proxy_program> Use program rather than tcp connection\n" +#endif #ifdef DEBUG_TRACE "-v verbose\n" #endif @@ -90,6 +102,9 @@ void cli_getopts(int argc, char ** argv) { #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; @@ -105,6 +120,7 @@ void cli_getopts(int argc, char ** argv) { cli_opts.backgrounded = 0; cli_opts.wantpty = 9; /* 9 means "it hasn't been touched", gets set later */ cli_opts.always_accept_key = 0; + cli_opts.is_subsystem = 0; #ifdef ENABLE_CLI_PUBKEY_AUTH cli_opts.privkeys = NULL; #endif @@ -119,12 +135,17 @@ void cli_getopts(int argc, char ** argv) { cli_opts.agent_fwd = 0; cli_opts.agent_keys_loaded = 0; #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 @@ -151,6 +172,14 @@ void cli_getopts(int argc, char ** argv) { 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]; @@ -193,6 +222,9 @@ void cli_getopts(int argc, char ** argv) { case 'f': cli_opts.backgrounded = 1; break; + case 's': + cli_opts.is_subsystem = 1; + break; #ifdef ENABLE_CLI_LOCALTCPFWD case 'L': nextislocal = 1; @@ -206,6 +238,16 @@ void cli_getopts(int argc, char ** argv) { 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; @@ -266,9 +308,11 @@ void cli_getopts(int argc, char ** argv) { /* 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 @@ -295,6 +339,8 @@ void cli_getopts(int argc, char ** argv) { } } + /* And now a few sanity checks and setup */ + if (cli_opts.remotehost == NULL) { printhelp(); exit(EXIT_FAILURE); @@ -319,21 +365,23 @@ void cli_getopts(int argc, char ** argv) { 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 } @@ -363,16 +411,77 @@ static void loadidentityfile(const char* filename) { } #endif +#ifdef ENABLE_CLI_MULTIHOP + +/* 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); + } + + 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; + } -/* Parses a [user@]hostname argument. userhostarg is the argv[i] corresponding - * - note that it will be modified */ -static void parsehostname(char* orighostarg) { + parse_hostname(userhostarg); - uid_t uid; - struct passwd *pw = NULL; + 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, '@'); @@ -387,14 +496,13 @@ static void parsehostname(char* orighostarg) { } 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') { @@ -402,10 +510,61 @@ static void parsehostname(char* orighostarg) { } } +#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; @@ -441,15 +600,13 @@ static void addforward(char* origstr, struct TCPFwdList** fwdlist) { /* 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 --git a/cli-session.c b/cli-session.c index e176bd9..8c087d4 100644 --- a/cli-session.c +++ b/cli-session.c @@ -77,13 +77,13 @@ static const struct ChanType *cli_chantypes[] = { 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); @@ -200,20 +200,6 @@ static void cli_sessionloop() { 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) { @@ -238,7 +224,13 @@ static void cli_sessionloop() { #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")) @@ -297,8 +289,10 @@ static void cli_remoteclosed() { /* 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 --git a/common-channel.c b/common-channel.c index 97fd4a8..2105184 100644 --- a/common-channel.c +++ b/common-channel.c @@ -261,6 +261,7 @@ static unsigned int write_pending(struct Channel * channel) { /* EOF/close handling */ static void check_close(struct Channel *channel) { + int close_allowed = 0; TRACE(("check_close: writefd %d, readfd %d, errfd %d, sent_close %d, recv_close %d", channel->writefd, channel->readfd, @@ -274,8 +275,17 @@ static void check_close(struct Channel *channel) { { channel->flushing = 1; } + + // if a type-specific check_close is defined we will only exit + // once that has been triggered. this is only used for a server "session" + // channel, to ensure that the shell has exited (and the exit status + // retrieved) before we close things up. + if (!channel->type->check_close + || channel->type->check_close(channel)) { + close_allowed = 1; + } - if (channel->recv_close && !write_pending(channel)) { + if (channel->recv_close && !write_pending(channel) && close_allowed) { if (!channel->sent_close) { TRACE(("Sending MSG_CHANNEL_CLOSE in response to same.")) send_msg_channel_close(channel); @@ -312,9 +322,10 @@ static void check_close(struct Channel *channel) { } /* And if we can't receive any more data from them either, close up */ - if (!channel->sent_close - && channel->readfd == FD_CLOSED + if (channel->readfd == FD_CLOSED && (ERRFD_IS_WRITE(channel) || channel->errfd == FD_CLOSED) + && !channel->sent_close + && close_allowed && !write_pending(channel)) { TRACE(("sending close, readfd is closed")) send_msg_channel_close(channel); @@ -561,6 +572,11 @@ void recv_msg_channel_request() { 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 --git a/common-session.c b/common-session.c index 79313f2..3d759b5 100644 --- a/common-session.c +++ b/common-session.c @@ -52,14 +52,15 @@ int exitflag = 0; /* GLOBAL */ /* 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 @@ void session_loop(void(*loophandler)()) { 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 @@ void session_loop(void(*loophandler)()) { 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 @@ void session_identification() { 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 @@ static long select_timeout() { 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); +} + @@ -86,6 +86,19 @@ useful for working around firewalls or routers that drop connections after a certain period of inactivity. The trade-off is that a session may be closed if there is a temporary lapse of network connectivity. A setting if 0 disables keepalives. +.SH ENVIRONMENT +.TP +.B SSH_ASKPASS +dbclient can use an external program to request a password from a user. +SSH_ASKPASS should be set to the path of a program that will return a password +on standard output. This program will only be used if either DISPLAY is set and +standard input is not a TTY, or the environment variable SSH_ASKPASS_ALWAYS is +set. +.TP +.B DROPBEAR_PASSWORD +A password to use for remote authentication can be specified in the environment +variable DROPBEAR_PASSWORD. Care should be taken that the password is not +exposed to other users on a multi-user system, or stored in accessible files. .SH AUTHOR Matt Johnston (matt@ucc.asn.au). .br @@ -146,7 +146,7 @@ void dropbear_trace(const char* format, ...) { } va_start(param, format); - fprintf(stderr, "TRACE: "); + fprintf(stderr, "TRACE (%d): ", getpid()); vfprintf(stderr, format, param); fprintf(stderr, "\n"); va_end(param); @@ -338,9 +338,10 @@ int connect_remote(const char* remotehost, const char* remoteport, 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; @@ -398,6 +399,141 @@ int connect_remote(const char* remotehost, const char* remoteport, 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) { @@ -708,3 +844,17 @@ void disallow_core() { 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; + } +} @@ -49,6 +49,9 @@ char * stripcontrol(const char * text); 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); #ifdef ENABLE_CONNECT_UNIX int connect_unix(const char* addr); #endif @@ -67,6 +70,7 @@ void __m_free(void* ptr); 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 --git a/debian/README.runit b/debian/README.runit index 4ac2814..0a32176 100644 --- a/debian/README.runit +++ b/debian/README.runit @@ -31,16 +31,16 @@ run script # vi /etc/dropbear/run -Finally enable the service by linking dropbear's service directory to -/var/service/. The service will be started within five seconds, and -automatically at boot time. The sysv init script is disabled; see the -runsvctrl(8) program for information on how to control services handled by -runit. See the svlogd(8) program on how to configure the log service. +Finally enable the service through runit's update-service(8) program, the +service will be started within five seconds, and automatically at boot +time, and the sysv init script will automatically be disabled; see the +sv(8) program for information on how to control services handled by runit. +See the svlogd(8) program on how to configure the log service. - # ln -s /etc/dropbear /var/service/ + # update-service --add /etc/dropbear Optionally check the status of the service a few seconds later - # runsvstat -l /var/service/dropbear + # sv status dropbear - -- Gerrit Pape <pape@smarden.org>, Sun, 16 May 2004 15:52:34 +0000 + -- Gerrit Pape <pape@smarden.org>, Fri, 02 Mar 2007 20:41:08 +0000 diff --git a/debian/changelog b/debian/changelog index e7d4141..f51769b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,14 +1,75 @@ -dropbear (0.50-0.1) unstable; urgency=low +dropbear (0.51-0.1) unstable; urgency=low * New upstream release. - -- Matt Johnston <matt@ucc.asn.au> Wed, 8 Aug 2007 11:22:33 +0800 + -- Matt Johnston <matt@ucc.asn.au> Thu, 27 Mar 2008 19:14:00 +0900 -dropbear (0.49-0.1) unstable; urgency=low +dropbear (0.50-4) unstable; urgency=low - * New upstream release. + * debian/dropbear.init: apply patch from Petter Reinholdtsen: add LSB + formatted dependency info in init.d script (closes: #466257). + * debian/rules: no longer include symlinks for ./supervise/ subdirectories. + * debian/dropbear.postinst: upgrade from << 0.50-4: if dropbear is managed + by runit, remove service, and re-add using update-service(8). + * debian/control: Standards-Version: 3.7.3.0. + * debian/rules: target clean: don't ignore errors but check for readable + ./Makefile. + + -- Gerrit Pape <pape@smarden.org> Thu, 06 Mar 2008 19:06:58 +0000 + +dropbear (0.50-3) unstable; urgency=low + + * debian/dropbear.init: use the update-service(8) program from the runit + package instead of directly checking for the symlink in /var/service/. + * debian/README.runit: talk about update-service(8) instead of symlinks + in /var/service/. + + -- Gerrit Pape <pape@smarden.org> Fri, 15 Feb 2008 00:32:37 +0000 + +dropbear (0.50-2) unstable; urgency=low + + * debian/dropbear.README.Debian: no longer talk about entropy from + /dev/random, /dev/urandom is now used by default (thx Joey Hess, + closes: #441515). + + -- Gerrit Pape <pape@smarden.org> Mon, 24 Sep 2007 16:49:17 +0000 + +dropbear (0.50-1) unstable; urgency=low + + * debian/README.runit: minor. + * new upstream version. + * debian/diff/0001-options.h-use-dev-urandom-instead-of-dev-random-a.diff: + remove; fixed upstream. + + -- Gerrit Pape <pape@smarden.org> Thu, 09 Aug 2007 23:01:01 +0000 + +dropbear (0.49-2) unstable; urgency=low + + * debian/rules: apply diffs from debian/diff/ with patch -p1 instead of + -p0. + * debian/diff/0001-options.h-use-dev-urandom-instead-of-dev-random-a.diff: + new; options.h: use /dev/urandom instead of /dev/random as + DROPBEAR_RANDOM_DEV (closes: #386976). + * debian/rules: target clean: remove libtomcrypt/Makefile, + libtommath/Makefile. + + -- Gerrit Pape <pape@smarden.org> Sat, 09 Jun 2007 08:59:59 +0000 + +dropbear (0.49-1) unstable; urgency=high + + * new upstream release, fixes + * CVE-2007-1099: dropbear dbclient insufficient warning on hostkey + mismatch (closes: #412899). + * dbclient uses static "Password:" prompt instead of using the server's + prompt (closes: #394996). + * debian/control: Suggests: openssh-client, not ssh (closes: #405686); + Standards-Version: 3.7.2.2. + * debian/README.Debian: ssh -> openssh-server, openssh-client; remove + 'Replacing OpenSSH "sshd" with Dropbear' part, this is simply done by not + installing the openssh-server package. + * debian/README.runit: runsvstat -> sv status. - -- Matt Johnston <matt@ucc.asn.au> Fri, 23 Feb 2007 00:44:00 +0900 + -- Gerrit Pape <pape@smarden.org> Fri, 2 Mar 2007 20:48:18 +0000 dropbear (0.48.1-1) unstable; urgency=medium diff --git a/debian/control b/debian/control index 81835b3..e2731f6 100644 --- a/debian/control +++ b/debian/control @@ -3,12 +3,12 @@ Section: net Priority: optional Maintainer: Gerrit Pape <pape@smarden.org> Build-Depends: libz-dev -Standards-Version: 3.6.2.1 +Standards-Version: 3.7.3.0 Package: dropbear Architecture: any Depends: ${shlibs:Depends} -Suggests: ssh, runit +Suggests: openssh-client, runit Description: lightweight SSH2 server and client dropbear is a SSH 2 server and client designed to be small enough to be used in small memory environments, while still being functional and diff --git a/debian/dropbear.README.Debian b/debian/dropbear.README.Debian index 7eec3e6..0ce1874 100644 --- a/debian/dropbear.README.Debian +++ b/debian/dropbear.README.Debian @@ -1,52 +1,19 @@ Dropbear for Debian ------------------- -This package will attempt to listen on port 22. If the OpenSSH -package ("ssh") is installed, the file /etc/default/dropbear -will be set up so that the server does not start by default. +This package will attempt to setup the Dropbear ssh server to listen on +port 22. If the OpenSSH server package ("openssh-server") is installed, +the file /etc/default/dropbear will be set up so that the server does not +start by default. -You can run Dropbear concurrently with OpenSSH 'sshd' by -modifying /etc/default/dropbear so that "NO_START" is set to -"0" and changing the port number that Dropbear runs on. Follow -the instructions in the file. +You can run Dropbear concurrently with OpenSSH 'sshd' by modifying +/etc/default/dropbear so that "NO_START" is set to "0", and changing the +port number that Dropbear runs on. Follow the instructions in the file. -This package suggests you install the "ssh" package. This package -provides the "ssh" client program, as well as the "/usr/bin/scp" -binary you will need to be able to retrieve files from a server -running Dropbear via SCP. - -Replacing OpenSSH "sshd" with Dropbear --------------------------------------- - -You will still want to have the "ssh" package installed, as it -provides the "ssh" and "scp" binaries. When you install this -package, it checks for existing OpenSSH host keys and if found, -converts them to the Dropbear format. - -If this appears to have worked, you should be able to change over -by following these steps: - -1. Stop the OpenSSH server - % /etc/init.d/ssh stop -2. Prevent the OpenSSH server from starting in the future - % touch /etc/ssh/sshd_not_to_be_run -3. Modify the Dropbear defaults file, set NO_START to 0 and - ensure DROPBEAR_PORT is set to 22. - % editor /etc/default/dropbear -4. Restart the Dropbear server. - % /etc/init.d/dropbear restart +This package suggests you install the "openssh-client" package, which +provides the "ssh" client program, as well as the "/usr/bin/scp" binary +you will need to be able to retrieve files via SCP from a server running +Dropbear. See the Dropbear homepage for more information: http://matt.ucc.asn.au/dropbear/dropbear.html - - -Entropy from /dev/random ------------------------- - -The dropbear binary package is configured at compile time to read -entropy from /dev/random. If /dev/random on a system blocks when -reading data from it, client logins may be delayed until the client -times out. The dropbear server writes a notice to the logs when it -sees /dev/random blocking. A workaround for such systems is to -re-compile the package with DROPBEAR_RANDOM_DEV set to /dev/urandom -in options.h. diff --git a/debian/dropbear.init b/debian/dropbear.init index ee69076..1705330 100644 --- a/debian/dropbear.init +++ b/debian/dropbear.init @@ -1,4 +1,11 @@ #!/bin/sh +### BEGIN INIT INFO +# Provides: dropbear +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +### END INIT INFO # # Do not configure this file. Edit /etc/default/dropbear instead! # @@ -17,8 +24,8 @@ set -e cancel() { echo "$1" >&2; exit 0; }; test ! -r /etc/default/dropbear || . /etc/default/dropbear test -x "$DAEMON" || cancel "$DAEMON does not exist or is not executable." -test ! -h /var/service/dropbear || \ - cancel '/var/service/dropbear exists, service is controlled through runit.' +test ! -x /usr/sbin/update-service || ! update-service --check dropbear || + cancel 'The dropbear service is controlled through runit, use the sv(8) program' test -z "$DROPBEAR_BANNER" || \ DROPBEAR_EXTRA_ARGS="$DROPBEAR_EXTRA_ARGS -b $DROPBEAR_BANNER" diff --git a/debian/dropbear.postinst b/debian/dropbear.postinst index e51e2b7..7c95cfa 100644 --- a/debian/dropbear.postinst +++ b/debian/dropbear.postinst @@ -69,3 +69,11 @@ if test -x /etc/init.d/dropbear; then /etc/init.d/dropbear restart fi fi + +if test -n "$2" && dpkg --compare-versions "$2" lt '0.50-4' && +update-service --check dropbear; then + update-service --remove /etc/dropbear 2>/dev/null || : + sleep 6 + rm -rf /var/run/dropbear /var/run/dropbear.log + update-service --add /etc/dropbear || : +fi diff --git a/debian/rules b/debian/rules index 52c3ea8..605754e 100755 --- a/debian/rules +++ b/debian/rules @@ -28,7 +28,7 @@ DIR =$(shell pwd)/debian/dropbear patch: deb-checkdir patch-stamp patch-stamp: for i in `ls -1 debian/diff/*.diff || :`; do \ - patch -p0 <$$i || exit 1; \ + patch -p1 <$$i || exit 1; \ done touch patch-stamp @@ -46,10 +46,11 @@ build-stamp: config.status touch build-stamp clean: deb-checkdir deb-checkuid - -$(MAKE) distclean + test ! -r Makefile || $(MAKE) distclean + rm -f libtomcrypt/Makefile libtommath/Makefile test ! -e patch-stamp || \ for i in `ls -1r debian/diff/*.diff || :`; do \ - patch -p0 -R <$$i; \ + patch -p1 -R <$$i; \ done rm -f patch-stamp build-stamp config.log config.status rm -rf '$(DIR)' @@ -76,8 +77,6 @@ install: deb-checkdir deb-checkuid build-stamp install -d -m0755 '$(DIR)'/etc/dropbear/log install -m0755 debian/service/log '$(DIR)'/etc/dropbear/log/run ln -s /var/log/dropbear '$(DIR)'/etc/dropbear/log/main - ln -s /var/run/dropbear '$(DIR)'/etc/dropbear/supervise - ln -s /var/run/dropbear.log '$(DIR)'/etc/dropbear/log/supervise # man pages install -d -m0755 '$(DIR)'/usr/share/man/man8 for i in dropbear.8 dropbearkey.8; do \ @@ -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 @@ -67,6 +67,11 @@ #define TRACE(X) #endif /*DEBUG_TRACE*/ +/* To debug with GDB it is easier to run with no forking of child processes. + You will need to pass "-F" as well. */ +/* #define DEBUG_NOFORK */ + + /* For testing as non-root on shadowed systems, include the crypt of a password * here. You can then log in as any user with this password. Ensure that you * make your own password, and are careful about using this. This will also @@ -62,17 +62,13 @@ rsa_key * gen_rsa_priv_key(unsigned int size) { 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 --git a/keyimport.c b/keyimport.c index 1d3e3f5..76b92f1 100644 --- a/keyimport.c +++ b/keyimport.c @@ -701,7 +701,6 @@ static int openssh_write(const char *filename, sign_key *key, 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; @@ -60,6 +60,10 @@ etc) slower (perhaps by 50%). Recommended for most small systems. */ #define ENABLE_CLI_LOCALTCPFWD #define ENABLE_CLI_REMOTETCPFWD +/* Allow using -J <proxycommand> 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 @@ -67,6 +71,10 @@ etc) slower (perhaps by 50%). Recommended for most small systems. */ #define ENABLE_SVR_AGENTFWD #define ENABLE_CLI_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 @@ -134,9 +142,15 @@ etc) slower (perhaps by 50%). Recommended for most small systems. */ * You can't enable both PASSWORD and PAM. */ #define ENABLE_SVR_PASSWORD_AUTH -/*#define ENABLE_SVR_PAM_AUTH */ /* requires ./configure --enable-pam */ +/* PAM requires ./configure --enable-pam */ +/* #define ENABLE_SVR_PAM_AUTH */ #define ENABLE_SVR_PUBKEY_AUTH +/* Wether to ake public key options in authorized_keys file into account */ +#ifdef ENABLE_SVR_PUBKEY_AUTH +#define ENABLE_SVR_PUBKEY_OPTIONS +#endif + #define ENABLE_CLI_PASSWORD_AUTH #define ENABLE_CLI_PUBKEY_AUTH #define ENABLE_CLI_INTERACT_AUTH @@ -236,209 +250,8 @@ etc) slower (perhaps by 50%). Recommended for most small systems. */ be overridden at runtime with -K. 0 disables keepalives */ #define DEFAULT_KEEPALIVE 0 -/******************************************************************* - * You shouldn't edit below here unless you know you need to. - *******************************************************************/ - -#ifndef DROPBEAR_VERSION -#define DROPBEAR_VERSION "0.50" -#endif - -#define LOCAL_IDENT "SSH-2.0-dropbear_" DROPBEAR_VERSION -#define PROGNAME "dropbear" - -/* Spec recommends after one hour or 1 gigabyte of data. One hour - * is a bit too verbose, so we try 8 hours */ -#ifndef KEX_REKEY_TIMEOUT -#define KEX_REKEY_TIMEOUT (3600 * 8) -#endif -#ifndef KEX_REKEY_DATA -#define KEX_REKEY_DATA (1<<30) /* 2^30 == 1GB, this value must be < INT_MAX */ -#endif -/* Close connections to clients which haven't authorised after AUTH_TIMEOUT */ -#ifndef AUTH_TIMEOUT -#define AUTH_TIMEOUT 300 /* we choose 5 minutes */ -#endif - -/* Minimum key sizes for DSS and RSA */ -#ifndef MIN_DSS_KEYLEN -#define MIN_DSS_KEYLEN 512 -#endif -#ifndef MIN_RSA_KEYLEN -#define MIN_RSA_KEYLEN 512 -#endif - -#define MAX_BANNER_SIZE 2000 /* this is 25*80 chars, any more is foolish */ -#define MAX_BANNER_LINES 20 /* How many lines the client will display */ - -/* the number of NAME=VALUE pairs to malloc for environ, if we don't have - * the clearenv() function */ -#define ENV_SIZE 100 - -#define MAX_CMD_LEN 1024 /* max length of a command */ -#define MAX_TERM_LEN 200 /* max length of TERM name */ - -#define MAX_HOST_LEN 254 /* max hostname len for tcp fwding */ -#define MAX_IP_LEN 15 /* strlen("255.255.255.255") == 15 */ - -#define DROPBEAR_MAX_PORTS 10 /* max number of ports which can be specified, - ipv4 and ipv6 don't count twice */ - -/* Each port might have at least a v4 and a v6 address */ -#define MAX_LISTEN_ADDR (DROPBEAR_MAX_PORTS*3) - -#define _PATH_TTY "/dev/tty" - -#define _PATH_CP "/bin/cp" - -/* success/failure defines */ -#define DROPBEAR_SUCCESS 0 -#define DROPBEAR_FAILURE -1 - -/* various algorithm identifiers */ -#define DROPBEAR_KEX_DH_GROUP1 0 - -#define DROPBEAR_SIGNKEY_ANY 0 -#define DROPBEAR_SIGNKEY_RSA 1 -#define DROPBEAR_SIGNKEY_DSS 2 -#define DROPBEAR_SIGNKEY_NONE 3 - -#define DROPBEAR_COMP_NONE 0 -#define DROPBEAR_COMP_ZLIB 1 - -/* Required for pubkey auth */ -#if defined(ENABLE_SVR_PUBKEY_AUTH) || defined(DROPBEAR_CLIENT) -#define DROPBEAR_SIGNKEY_VERIFY -#endif - -/* SHA1 is 20 bytes == 160 bits */ -#define SHA1_HASH_SIZE 20 -/* SHA512 is 64 bytes == 512 bits */ -#define SHA512_HASH_SIZE 64 -/* MD5 is 16 bytes = 128 bits */ -#define MD5_HASH_SIZE 16 - -/* largest of MD5 and SHA1 */ -#define MAX_MAC_LEN SHA1_HASH_SIZE - - -#define MAX_KEY_LEN 32 /* 256 bits for aes256 etc */ -#define MAX_IV_LEN 20 /* must be same as max blocksize, - and >= SHA1_HASH_SIZE */ -#define MAX_MAC_KEY 20 - -#define MAX_NAME_LEN 64 /* maximum length of a protocol name, isn't - explicitly specified for all protocols (just - for algos) but seems valid */ - -#define MAX_PROPOSED_ALGO 20 - -/* size/count limits */ -#define MIN_PACKET_LEN 16 - -#define RECV_MAX_PACKET_LEN (MAX(35000, ((RECV_MAX_PAYLOAD_LEN)+100))) - -/* for channel code */ -#define TRANS_MAX_WINDOW 500000000 /* 500MB is sufficient, stopping overflow */ -#define TRANS_MAX_WIN_INCR 500000000 /* overflow prevention */ - -#define RECV_WINDOWEXTEND (opts.recv_window / 3) /* We send a "window extend" every - RECV_WINDOWEXTEND bytes */ -#define MAX_RECV_WINDOW (1024*1024) /* 1 MB should be enough */ - -#define MAX_CHANNELS 100 /* simple mem restriction, includes each tcp/x11 - connection, so can't be _too_ small */ - -#define MAX_STRING_LEN 1400 /* ~= MAX_PROPOSED_ALGO * MAX_NAME_LEN, also - is the max length for a password etc */ - -/* For a 4096 bit DSS key, empirically determined */ -#define MAX_PUBKEY_SIZE 1700 -/* For a 4096 bit DSS key, empirically determined */ -#define MAX_PRIVKEY_SIZE 1700 - -/* The maximum size of the bignum portion of the kexhash buffer */ -/* Sect. 8 of the transport draft, K_S + e + f + K */ -#define KEXHASHBUF_MAX_INTS (1700 + 130 + 130 + 130) - -#define DROPBEAR_MAX_SOCKS 2 /* IPv4, IPv6 are all we'll get for now. Revisit - in a few years time.... */ - -#define DROPBEAR_MAX_CLI_PASS 1024 - -#define DROPBEAR_MAX_CLI_INTERACT_PROMPTS 80 /* The number of prompts we'll - accept for keyb-interactive - auth */ - -#if defined(DROPBEAR_AES256_CBC) || defined(DROPBEAR_AES128_CBC) -#define DROPBEAR_AES_CBC -#endif - -#if defined(DROPBEAR_TWOFISH256_CBC) || defined(DROPBEAR_TWOFISH128_CBC) -#define DROPBEAR_TWOFISH_CBC -#endif - -#ifndef ENABLE_X11FWD -#define DISABLE_X11FWD -#endif - -#ifndef ENABLE_AGENTFWD -#define DISABLE_AGENTFWD -#endif - -#if defined(DROPBEAR_PRNGD_SOCKET) || defined(ENABLE_CLI_AGENTFWD) -#define ENABLE_CONNECT_UNIX -#endif - -#if defined(ENABLE_CLI_REMOTETCPFWD) || defined(ENABLE_CLI_LOCALTCPFWD) -#define ENABLE_CLI_ANYTCPFWD -#endif - -#if defined(ENABLE_CLI_LOCALTCPFWD) || defined(ENABLE_SVR_REMOTETCPFWD) -#define DROPBEAR_TCP_ACCEPT -#endif - -#if defined(ENABLE_CLI_REMOTETCPFWD) || defined(ENABLE_CLI_LOCALTCPFWD) || \ - defined(ENABLE_SVR_REMOTETCPFWD) || defined(ENABLE_SVR_LOCALTCPFWD) || \ - defined(ENABLE_AGENTFWD) || defined(ENABLE_X11FWD) -#define USING_LISTENERS -#endif - -#if defined(DROPBEAR_CLIENT) || defined(ENABLE_SVR_PUBKEY_AUTH) -#define DROPBEAR_KEY_LINES /* ie we're using authorized_keys or known_hosts */ -#endif - -#if defined(ENABLE_SVR_PASSWORD_AUTH) && defined(ENABLE_SVR_PAM_AUTH) -#error "You can't turn on PASSWORD and PAM auth both at once. Fix it in options.h" -#endif - -#if defined(DROPBEAR_RANDOM_DEV) && defined(DROPBEAR_PRNGD_SOCKET) -#error "You can't turn on DROPBEAR_PRNGD_SOCKET and DROPBEAR_RANDOM_DEV at once" -#endif - -#if !defined(DROPBEAR_RANDOM_DEV) && !defined(DROPBEAR_PRNGD_SOCKET) -#error "You must choose one of DROPBEAR_PRNGD_SOCKET or DROPBEAR_RANDOM_DEV in options.h" -#endif - -/* We use dropbear_client and dropbear_server as shortcuts to avoid redundant - * code, if we're just compiling as client or server */ -#if defined(DROPBEAR_SERVER) && defined(DROPBEAR_CLIENT) - -#define IS_DROPBEAR_SERVER (ses.isserver == 1) -#define IS_DROPBEAR_CLIENT (ses.isserver == 0) - -#elif defined(DROPBEAR_SERVER) - -#define IS_DROPBEAR_SERVER 1 -#define IS_DROPBEAR_CLIENT 0 - -#elif defined(DROPBEAR_CLIENT) - -#define IS_DROPBEAR_SERVER 0 -#define IS_DROPBEAR_CLIENT 1 - -#else -#error You must compiled with either DROPBEAR_CLIENT or DROPBEAR_SERVER selected -#endif +/* Some other defines (that mostly should be left alone) are defined + * in sysoptions.h */ +#include "sysoptions.h" #endif /* _OPTIONS_H_ */ @@ -61,7 +61,7 @@ void write_packet() { 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 @@ void read_packet() { * 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 @@ static void read_packet_init() { 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(); @@ -37,7 +37,7 @@ typedef struct runopts { int listen_fwd_all; #endif unsigned int recv_window; - time_t keepalive_secs; + unsigned int keepalive_secs; } runopts; @@ -101,6 +101,7 @@ typedef struct cli_runopts { char *remotehost; char *remoteport; + char *own_user; char *username; char *cmd; @@ -108,6 +109,7 @@ typedef struct cli_runopts { int always_accept_key; int no_cmd; int backgrounded; + int is_subsystem; #ifdef ENABLE_CLI_PUBKEY_AUTH struct SignKeyList *privkeys; /* Keys to use for public-key auth */ #endif @@ -123,6 +125,14 @@ typedef struct cli_runopts { list of keys held by the agent */ #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; @@ -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_exit(int exitcode, const char* format, va_list param); 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 @@ struct sshsession { (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 --git a/svr-agentfwd.c b/svr-agentfwd.c index f4909d5..b86c447 100644 --- a/svr-agentfwd.c +++ b/svr-agentfwd.c @@ -39,6 +39,7 @@ #include "buffer.h" #include "random.h" #include "listener.h" +#include "auth.h" #define AGENTDIRPREFIX "/tmp/dropbear-" @@ -52,6 +53,10 @@ int agentreq(struct ChanSess * chansess) { int fd; + if (!svr_pubkey_allows_agentfwd()) { + return DROPBEAR_FAILURE; + } + if (chansess->agentlistener != NULL) { return DROPBEAR_FAILURE; } @@ -150,8 +155,8 @@ void agentcleanup(struct ChanSess * chansess) { * for themselves */ uid = getuid(); gid = getgid(); - if ((setegid(ses.authstate.pw->pw_gid)) < 0 || - (seteuid(ses.authstate.pw->pw_uid)) < 0) { + if ((setegid(ses.authstate.pw_gid)) < 0 || + (seteuid(ses.authstate.pw_uid)) < 0) { dropbear_exit("failed to set euid"); } @@ -213,8 +218,8 @@ static int bindagent(int fd, struct ChanSess * chansess) { /* drop to user privs to make the dir/file */ uid = getuid(); gid = getgid(); - if ((setegid(ses.authstate.pw->pw_gid)) < 0 || - (seteuid(ses.authstate.pw->pw_uid)) < 0) { + if ((setegid(ses.authstate.pw_gid)) < 0 || + (seteuid(ses.authstate.pw_uid)) < 0) { dropbear_exit("failed to set euid"); } @@ -42,6 +42,10 @@ static void send_msg_userauth_banner(); void svr_authinitialise() { ses.authstate.failcount = 0; + ses.authstate.pw_name = NULL; + ses.authstate.pw_dir = NULL; + ses.authstate.pw_shell = NULL; + ses.authstate.pw_passwd = NULL; authclear(); } @@ -60,7 +64,19 @@ static void authclear() { ses.authstate.authtypes |= AUTH_TYPE_PASSWORD; } #endif - + if (ses.authstate.pw_name) { + m_free(ses.authstate.pw_name); + } + if (ses.authstate.pw_shell) { + m_free(ses.authstate.pw_shell); + } + if (ses.authstate.pw_dir) { + m_free(ses.authstate.pw_dir); + } + if (ses.authstate.pw_passwd) { + m_free(ses.authstate.pw_passwd); + } + } /* Send a banner message if specified to the client. The client might @@ -143,7 +159,7 @@ void recv_msg_userauth_request() { #ifdef ENABLE_SVR_PASSWORD_AUTH if (!svr_opts.noauthpass && - !(svr_opts.norootpass && ses.authstate.pw->pw_uid == 0) ) { + !(svr_opts.norootpass && ses.authstate.pw_uid == 0) ) { /* user wants to try password auth */ if (methodlen == AUTH_METHOD_PASSWORD_LEN && strncmp(methodname, AUTH_METHOD_PASSWORD, @@ -156,7 +172,7 @@ void recv_msg_userauth_request() { #ifdef ENABLE_SVR_PAM_AUTH if (!svr_opts.noauthpass && - !(svr_opts.norootpass && ses.authstate.pw->pw_uid == 0) ) { + !(svr_opts.norootpass && ses.authstate.pw_uid == 0) ) { /* user wants to try password auth */ if (methodlen == AUTH_METHOD_PASSWORD_LEN && strncmp(methodname, AUTH_METHOD_PASSWORD, @@ -187,6 +203,7 @@ out: m_free(methodname); } + /* Check that the username exists, has a non-empty password, and has a valid * shell. * returns DROPBEAR_SUCCESS on valid username, DROPBEAR_FAILURE on failure */ @@ -194,7 +211,6 @@ static int checkusername(unsigned char *username, unsigned int userlen) { char* listshell = NULL; char* usershell = NULL; - TRACE(("enter checkusername")) if (userlen > MAX_USERNAME_LEN) { return DROPBEAR_FAILURE; @@ -210,13 +226,12 @@ static int checkusername(unsigned char *username, unsigned int userlen) { m_free(ses.authstate.username); } authclear(); - ses.authstate.pw = getpwnam((char*)username); + fill_passwd(username); ses.authstate.username = m_strdup(username); - m_free(ses.authstate.printableuser); } /* check that user exists */ - if (ses.authstate.pw == NULL) { + if (!ses.authstate.pw_name) { TRACE(("leave checkusername: user '%s' doesn't exist", username)) dropbear_log(LOG_WARNING, "login attempt for nonexistent user from %s", @@ -225,11 +240,8 @@ static int checkusername(unsigned char *username, unsigned int userlen) { return DROPBEAR_FAILURE; } - /* We can set it once we know its a real user */ - ses.authstate.printableuser = m_strdup(ses.authstate.pw->pw_name); - /* check for non-root if desired */ - if (svr_opts.norootlogin && ses.authstate.pw->pw_uid == 0) { + if (svr_opts.norootlogin && ses.authstate.pw_uid == 0) { TRACE(("leave checkusername: root login disabled")) dropbear_log(LOG_WARNING, "root login rejected"); send_msg_userauth_failure(0, 1); @@ -237,18 +249,18 @@ static int checkusername(unsigned char *username, unsigned int userlen) { } /* check for an empty password */ - if (ses.authstate.pw->pw_passwd[0] == '\0') { + if (ses.authstate.pw_passwd[0] == '\0') { TRACE(("leave checkusername: empty pword")) dropbear_log(LOG_WARNING, "user '%s' has blank password, rejected", - ses.authstate.printableuser); + ses.authstate.pw_name); send_msg_userauth_failure(0, 1); return DROPBEAR_FAILURE; } - TRACE(("shell is %s", ses.authstate.pw->pw_shell)) + TRACE(("shell is %s", ses.authstate.pw_shell)) /* check that the shell is set */ - usershell = ses.authstate.pw->pw_shell; + usershell = ses.authstate.pw_shell; if (usershell[0] == '\0') { /* empty shell in /etc/passwd means /bin/sh according to passwd(5) */ usershell = "/bin/sh"; @@ -269,7 +281,7 @@ static int checkusername(unsigned char *username, unsigned int userlen) { endusershell(); TRACE(("no matching shell")) dropbear_log(LOG_WARNING, "user '%s' has invalid shell, rejected", - ses.authstate.printableuser); + ses.authstate.pw_name); send_msg_userauth_failure(0, 1); return DROPBEAR_FAILURE; @@ -277,7 +289,7 @@ goodshell: endusershell(); TRACE(("matching shell")) - TRACE(("uid = %d", ses.authstate.pw->pw_uid)) + TRACE(("uid = %d", ses.authstate.pw_uid)) TRACE(("leave checkusername")) return DROPBEAR_SUCCESS; @@ -334,10 +346,10 @@ void send_msg_userauth_failure(int partial, int incrfail) { /* XXX - send disconnect ? */ TRACE(("Max auth tries reached, exiting")) - if (ses.authstate.printableuser == NULL) { + if (ses.authstate.pw_name == NULL) { userstr = "is invalid"; } else { - userstr = ses.authstate.printableuser; + userstr = ses.authstate.pw_name; } dropbear_exit("Max auth tries reached - user '%s' from %s", userstr, svr_ses.addrstring); @@ -360,7 +372,7 @@ void send_msg_userauth_success() { ses.connect_time = 0; - if (ses.authstate.pw->pw_uid == 0) { + if (ses.authstate.pw_uid == 0) { ses.allowprivport = 1; } diff --git a/svr-authpam.c b/svr-authpam.c index ee7d6ba..8d6a6e7 100644 --- a/svr-authpam.c +++ b/svr-authpam.c @@ -195,7 +195,7 @@ void svr_auth_pam() { /* used to pass data to the PAM conversation function - don't bother with * strdup() etc since these are touched only by our own conversation * function (above) which takes care of it */ - userData.user = ses.authstate.printableuser; + userData.user = ses.authstate.pw_name; userData.passwd = password; /* Init pam */ @@ -221,7 +221,7 @@ void svr_auth_pam() { rc, pam_strerror(pamHandlep, rc)); dropbear_log(LOG_WARNING, "bad PAM password attempt for '%s' from %s", - ses.authstate.printableuser, + ses.authstate.pw_name, svr_ses.addrstring); send_msg_userauth_failure(0, 1); goto cleanup; @@ -232,7 +232,7 @@ void svr_auth_pam() { rc, pam_strerror(pamHandlep, rc)); dropbear_log(LOG_WARNING, "bad PAM password attempt for '%s' from %s", - ses.authstate.printableuser, + ses.authstate.pw_name, svr_ses.addrstring); send_msg_userauth_failure(0, 1); goto cleanup; @@ -240,7 +240,7 @@ void svr_auth_pam() { /* successful authentication */ dropbear_log(LOG_NOTICE, "PAM password auth succeeded for '%s' from %s", - ses.authstate.printableuser, + ses.authstate.pw_name, svr_ses.addrstring); send_msg_userauth_success(); diff --git a/svr-authpasswd.c b/svr-authpasswd.c index 5be1e2a..53550a2 100644 --- a/svr-authpasswd.c +++ b/svr-authpasswd.c @@ -46,10 +46,10 @@ void svr_auth_password() { unsigned int changepw; - passwdcrypt = ses.authstate.pw->pw_passwd; + passwdcrypt = ses.authstate.pw_passwd; #ifdef HAVE_SHADOW_H /* get the shadow password if possible */ - spasswd = getspnam(ses.authstate.printableuser); + spasswd = getspnam(ses.authstate.pw_name); if (spasswd != NULL && spasswd->sp_pwdp != NULL) { passwdcrypt = spasswd->sp_pwdp; } @@ -65,7 +65,7 @@ void svr_auth_password() { * in auth.c */ if (passwdcrypt[0] == '\0') { dropbear_log(LOG_WARNING, "user '%s' has blank password, rejected", - ses.authstate.printableuser); + ses.authstate.pw_name); send_msg_userauth_failure(0, 1); return; } @@ -89,13 +89,13 @@ void svr_auth_password() { /* successful authentication */ dropbear_log(LOG_NOTICE, "password auth succeeded for '%s' from %s", - ses.authstate.printableuser, + ses.authstate.pw_name, svr_ses.addrstring); send_msg_userauth_success(); } else { dropbear_log(LOG_WARNING, "bad password attempt for '%s' from %s", - ses.authstate.printableuser, + ses.authstate.pw_name, svr_ses.addrstring); send_msg_userauth_failure(0, 1); } diff --git a/svr-authpubkey.c b/svr-authpubkey.c index d611c89..de72766 100644 --- a/svr-authpubkey.c +++ b/svr-authpubkey.c @@ -21,6 +21,37 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ +/* + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright (c) 2000 Markus Friedl. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This copyright and permission notice applies to the code parsing public keys + * options string which can also be found in OpenSSH auth2-pubkey.c file + * (user_key_allowed2). It has been adapted to work with buffers. + * + */ /* Process a pubkey auth request */ @@ -105,12 +136,12 @@ void svr_auth_pubkey() { signbuf->len) == DROPBEAR_SUCCESS) { dropbear_log(LOG_NOTICE, "pubkey auth succeeded for '%s' with key %s from %s", - ses.authstate.printableuser, fp, svr_ses.addrstring); + ses.authstate.pw_name, fp, svr_ses.addrstring); send_msg_userauth_success(); } else { dropbear_log(LOG_WARNING, "pubkey auth bad signature for '%s' with key %s from %s", - ses.authstate.printableuser, fp, svr_ses.addrstring); + ses.authstate.pw_name, fp, svr_ses.addrstring); send_msg_userauth_failure(0, 1); } m_free(fp); @@ -159,14 +190,16 @@ static int checkpubkey(unsigned char* algo, unsigned int algolen, int ret = DROPBEAR_FAILURE; buffer * line = NULL; unsigned int len, pos; - + buffer * options_buf = NULL; + int line_num; + TRACE(("enter checkpubkey")) /* check that we can use the algo */ if (have_algo(algo, algolen, sshhostkey) == DROPBEAR_FAILURE) { dropbear_log(LOG_WARNING, "pubkey auth attempt with unknown algo for '%s' from %s", - ses.authstate.printableuser, svr_ses.addrstring); + ses.authstate.pw_name, svr_ses.addrstring); goto out; } @@ -178,12 +211,12 @@ static int checkpubkey(unsigned char* algo, unsigned int algolen, /* we don't need to check pw and pw_dir for validity, since * its been done in checkpubkeyperms. */ - len = strlen(ses.authstate.pw->pw_dir); + len = strlen(ses.authstate.pw_dir); /* allocate max required pathname storage, * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */ filename = m_malloc(len + 22); snprintf(filename, len + 22, "%s/.ssh/authorized_keys", - ses.authstate.pw->pw_dir); + ses.authstate.pw_dir); /* open the file */ authfile = fopen(filename, "r"); @@ -193,25 +226,82 @@ static int checkpubkey(unsigned char* algo, unsigned int algolen, TRACE(("checkpubkey: opened authorized_keys OK")) line = buf_new(MAX_AUTHKEYS_LINE); + line_num = 0; /* iterate through the lines */ do { + /* new line : potentially new options */ + if (options_buf) { + buf_free(options_buf); + options_buf = NULL; + } if (buf_getline(line, authfile) == DROPBEAR_FAILURE) { /* EOF reached */ TRACE(("checkpubkey: authorized_keys EOF reached")) break; } + line_num++; if (line->len < MIN_AUTHKEYS_LINE) { TRACE(("checkpubkey: line too short")) continue; /* line is too short for it to be a valid key */ } - /* check the key type - this also stops us from using keys - * which have options with them */ + /* check the key type - will fail if there are options */ + TRACE(("a line!")) + if (strncmp(buf_getptr(line, algolen), algo, algolen) != 0) { - continue; + int is_comment = 0; + char *options_start = NULL; + int options_len = 0; + int escape, quoted; + + /* skip over any comments or leading whitespace */ + while (line->pos < line->len) { + const char c = buf_getbyte(line); + if (c == ' ' || c == '\t') { + continue; + } else if (c == '#') { + is_comment = 1; + break; + } + buf_incrpos(line, -1); + break; + } + if (is_comment) { + /* next line */ + continue; + } + + /* remember start of options */ + options_start = buf_getptr(line, 1); + quoted = 0; + escape = 0; + options_len = 0; + + /* figure out where the options are */ + while (line->pos < line->len) { + const char c = buf_getbyte(line); + if (!quoted && (c == ' ' || c == '\t')) { + break; + } + escape = (!escape && c == '\\'); + if (!escape && c == '"') { + quoted = !quoted; + } + options_len++; + } + options_buf = buf_new(options_len); + buf_putbytes(options_buf, options_start, options_len); + + /* compare the algorithm */ + if (line->pos + algolen > line->len) { + continue; + } + if (strncmp(buf_getptr(line, algolen), algo, algolen) != 0) { + continue; + } } buf_incrpos(line, algolen); @@ -232,6 +322,11 @@ static int checkpubkey(unsigned char* algo, unsigned int algolen, TRACE(("checkpubkey: line pos = %d len = %d", line->pos, line->len)) ret = cmp_base64_key(keyblob, keybloblen, algo, algolen, line, NULL); + + if (ret == DROPBEAR_SUCCESS && options_buf) { + ret = svr_add_pubkey_options(options_buf, line_num, filename); + } + if (ret == DROPBEAR_SUCCESS) { break; } @@ -248,6 +343,9 @@ out: buf_free(line); } m_free(filename); + if (options_buf) { + buf_free(options_buf); + } TRACE(("leave checkpubkey: ret=%d", ret)) return ret; } @@ -266,18 +364,18 @@ static int checkpubkeyperms() { TRACE(("enter checkpubkeyperms")) - if (ses.authstate.pw->pw_dir == NULL) { + if (ses.authstate.pw_dir == NULL) { goto out; } - if ((len = strlen(ses.authstate.pw->pw_dir)) == 0) { + if ((len = strlen(ses.authstate.pw_dir)) == 0) { goto out; } /* allocate max required pathname storage, * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */ filename = m_malloc(len + 22); - strncpy(filename, ses.authstate.pw->pw_dir, len+1); + strncpy(filename, ses.authstate.pw_dir, len+1); /* check ~ */ if (checkfileperm(filename) != DROPBEAR_SUCCESS) { @@ -320,7 +418,7 @@ static int checkfileperm(char * filename) { return DROPBEAR_FAILURE; } /* check ownership - user or root only*/ - if (filestat.st_uid != ses.authstate.pw->pw_uid + if (filestat.st_uid != ses.authstate.pw_uid && filestat.st_uid != 0) { badperm = 1; TRACE(("wrong ownership")) @@ -343,5 +441,4 @@ static int checkfileperm(char * filename) { return DROPBEAR_SUCCESS; } - -#endif +#endif diff --git a/svr-authpubkeyoptions.c b/svr-authpubkeyoptions.c new file mode 100644 index 0000000..8a92d62 --- /dev/null +++ b/svr-authpubkeyoptions.c @@ -0,0 +1,202 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2008 Frederic Moulins + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Author: Tatu Ylonen <ylo@cs.hut.fi> + * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + * All rights reserved + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + * + * This copyright and permission notice applies to the code parsing public keys + * options string which can also be found in OpenSSH auth-options.c file + * (auth_parse_options). + * + */ + +/* Process pubkey options during a pubkey auth request */ +#include "includes.h" +#include "session.h" +#include "dbutil.h" +#include "signkey.h" +#include "auth.h" + +#ifdef ENABLE_SVR_PUBKEY_OPTIONS + +/* Returns 1 if pubkey allows agent forwarding, + * 0 otherwise */ +int svr_pubkey_allows_agentfwd() { + if (ses.authstate.pubkey_options + && ses.authstate.pubkey_options->no_agent_forwarding_flag) { + return 0; + } + return 1; +} + +/* Returns 1 if pubkey allows tcp forwarding, + * 0 otherwise */ +int svr_pubkey_allows_tcpfwd() { + if (ses.authstate.pubkey_options + && ses.authstate.pubkey_options->no_port_forwarding_flag) { + return 0; + } + return 1; +} + +/* Returns 1 if pubkey allows x11 forwarding, + * 0 otherwise */ +int svr_pubkey_allows_x11fwd() { + if (ses.authstate.pubkey_options + && ses.authstate.pubkey_options->no_x11_forwarding_flag) { + return 0; + } + return 1; +} + +/* Returns 1 if pubkey allows pty, 0 otherwise */ +int svr_pubkey_allows_pty() { + if (ses.authstate.pubkey_options + && ses.authstate.pubkey_options->no_pty_flag) { + return 0; + } + return 1; +} + +/* Set chansession command to the one forced by 'command' public key option */ +void svr_pubkey_set_forced_command(struct ChanSess *chansess) { + if (ses.authstate.pubkey_options) + chansess->cmd = ses.authstate.pubkey_options->forced_command; +} + +/* Free potential public key options */ +void svr_pubkey_options_cleanup() { + if (ses.authstate.pubkey_options) { + m_free(ses.authstate.pubkey_options); + ses.authstate.pubkey_options = NULL; + } +} + +/* helper for svr_add_pubkey_options. returns DROPBEAR_SUCCESS if the option is matched, + and increments the options_buf */ +static int match_option(buffer *options_buf, const char *opt_name) { + const int len = strlen(opt_name); + if (options_buf->len - options_buf->pos < len) { + return DROPBEAR_FAILURE; + } + if (strncasecmp(buf_getptr(options_buf, len), opt_name, len) == 0) { + buf_incrpos(options_buf, len); + return DROPBEAR_SUCCESS; + } + return DROPBEAR_FAILURE; +} + +/* Parse pubkey options and set ses.authstate.pubkey_options accordingly. + * Returns DROPBEAR_SUCCESS if key is ok for auth, DROPBEAR_FAILURE otherwise */ +int svr_add_pubkey_options(buffer *options_buf, int line_num, const char* filename) { + int ret = DROPBEAR_FAILURE; + + TRACE(("enter addpubkeyoptions")) + + ses.authstate.pubkey_options = (struct PubKeyOptions*)m_malloc(sizeof( struct PubKeyOptions )); + memset(ses.authstate.pubkey_options, '\0', sizeof(*ses.authstate.pubkey_options)); + + buf_setpos(options_buf, 0); + while (options_buf->pos < options_buf->len) { + if (match_option(options_buf, "no-port-forwarding") == DROPBEAR_SUCCESS) { + dropbear_log(LOG_WARNING, "Port forwarding disabled."); + ses.authstate.pubkey_options->no_port_forwarding_flag = 1; + goto next_option; + } +#ifdef ENABLE_AGENTFWD + if (match_option(options_buf, "no-agent-forwarding") == DROPBEAR_SUCCESS) { + dropbear_log(LOG_WARNING, "Agent forwarding disabled."); + ses.authstate.pubkey_options->no_agent_forwarding_flag = 1; + goto next_option; + } +#endif +#ifdef ENABLE_X11FWD + if (match_option(options_buf, "no-X11-forwarding") == DROPBEAR_SUCCESS) { + dropbear_log(LOG_WARNING, "X11 forwarding disabled."); + ses.authstate.pubkey_options->no_x11_forwarding_flag = 1; + goto next_option; + } +#endif + if (match_option(options_buf, "no-pty") == DROPBEAR_SUCCESS) { + dropbear_log(LOG_WARNING, "Pty allocation disabled."); + ses.authstate.pubkey_options->no_pty_flag = 1; + goto next_option; + } + if (match_option(options_buf, "command=\"") == DROPBEAR_SUCCESS) { + int escaped = 0; + const unsigned char* command_start = buf_getptr(options_buf, 0); + while (options_buf->pos < options_buf->len) { + const char c = buf_getbyte(options_buf); + if (!escaped && c == '"') { + const int command_len = buf_getptr(options_buf, 0) - command_start; + ses.authstate.pubkey_options->forced_command = m_malloc(command_len); + memcpy(ses.authstate.pubkey_options->forced_command, + command_start, command_len-1); + ses.authstate.pubkey_options->forced_command[command_len-1] = '\0'; + dropbear_log(LOG_WARNING, "Forced command '%s'", + ses.authstate.pubkey_options->forced_command); + goto next_option; + } + escaped = (!escaped && c == '\\'); + } + dropbear_log(LOG_WARNING, "Badly formatted command= authorized_keys option"); + goto bad_option; + } + +next_option: + /* + * Skip the comma, and move to the next option + * (or break out if there are no more). + */ + if (options_buf->pos < options_buf->len + && buf_getbyte(options_buf) != ',') { + goto bad_option; + } + /* Process the next option. */ + } + /* parsed all options with no problem */ + ret = DROPBEAR_SUCCESS; + goto end; + +bad_option: + ret = DROPBEAR_FAILURE; + m_free(ses.authstate.pubkey_options); + ses.authstate.pubkey_options = NULL; + dropbear_log(LOG_WARNING, "Bad public key options at %s:%d", filename, line_num); + +end: + TRACE(("leave addpubkeyoptions")) + return ret; +} + +#endif diff --git a/svr-chansession.c b/svr-chansession.c index 619a451..9b2a412 100644 --- a/svr-chansession.c +++ b/svr-chansession.c @@ -37,6 +37,7 @@ #include "x11fwd.h" #include "agentfwd.h" #include "runopts.h" +#include "auth.h" /* Handles sessions (either shells or programs) requested by the client */ @@ -47,7 +48,7 @@ static int sessionsignal(struct ChanSess *chansess); 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); @@ -524,8 +525,15 @@ static int sessionpty(struct ChanSess * chansess) { unsigned int termlen; unsigned char namebuf[65]; + struct passwd * pw = NULL; TRACE(("enter sessionpty")) + + if (!svr_pubkey_allows_pty()) { + TRACE(("leave sessionpty : pty forbidden by public key option")) + return DROPBEAR_FAILURE; + } + chansess->term = buf_getstring(ses.payload, &termlen); if (termlen > MAX_TERM_LEN) { /* TODO send disconnect ? */ @@ -547,7 +555,10 @@ static int sessionpty(struct ChanSess * chansess) { dropbear_exit("out of memory"); /* TODO disconnect */ } - pty_setowner(ses.authstate.pw, chansess->tty); + pw = getpwnam(ses.authstate.pw_name); + if (!pw) + dropbear_exit("getpwnam failed after succeeding previously"); + pty_setowner(pw, chansess->tty); /* Set up the rows/col counts */ sessionwinchange(chansess); @@ -578,14 +589,19 @@ static int sessioncommand(struct Channel *channel, struct ChanSess *chansess, return DROPBEAR_FAILURE; } + /* take public key option 'command' into account */ + svr_pubkey_set_forced_command(chansess); + if (iscmd) { /* "exec" */ - chansess->cmd = buf_getstring(ses.payload, &cmdlen); + if (chansess->cmd == NULL) { + chansess->cmd = buf_getstring(ses.payload, &cmdlen); - if (cmdlen > MAX_CMD_LEN) { - m_free(chansess->cmd); - /* TODO - send error - too long ? */ - return DROPBEAR_FAILURE; + if (cmdlen > MAX_CMD_LEN) { + m_free(chansess->cmd); + /* TODO - send error - too long ? */ + return DROPBEAR_FAILURE; + } } if (issubsys) { #ifdef SFTPSERVER_PATH @@ -604,10 +620,10 @@ static int sessioncommand(struct Channel *channel, struct ChanSess *chansess, #ifdef LOG_COMMANDS if (chansess->cmd) { dropbear_log(LOG_INFO, "user %s executing '%s'", - ses.authstate.printableuser, chansess->cmd); + ses.authstate.pw_name, chansess->cmd); } else { dropbear_log(LOG_INFO, "user %s executing login shell", - ses.authstate.printableuser); + ses.authstate.pw_name); } #endif @@ -629,100 +645,37 @@ static int sessioncommand(struct Channel *channel, struct ChanSess *chansess, * 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")) + ret = spawn_command(execchild, chansess, + &channel->writefd, &channel->readfd, &channel->errfd, + &chansess->pid); - /* 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; - -#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 */ -#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 */ + if (ret == DROPBEAR_FAILURE) { + return ret; + } - } else { - /* parent */ - TRACE(("continue noptycommand: parent")) - chansess->pid = pid; - TRACE(("child pid is %d", pid)) + ses.maxfd = MAX(ses.maxfd, channel->writefd); + ses.maxfd = MAX(ses.maxfd, channel->readfd); + ses.maxfd = MAX(ses.maxfd, channel->errfd); - addchildpid(chansess, pid); + addchildpid(chansess, 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; @@ -794,11 +747,11 @@ static int ptycommand(struct Channel *channel, struct ChanSess *chansess) { if (svr_opts.domotd) { /* don't show the motd if ~/.hushlogin exists */ - /* 11 == strlen("/hushlogin\0") */ - len = strlen(ses.authstate.pw->pw_dir) + 11; + /* 12 == strlen("/.hushlogin\0") */ + len = strlen(ses.authstate.pw_dir) + 12; hushpath = m_malloc(len); - snprintf(hushpath, len, "%s/hushlogin", ses.authstate.pw->pw_dir); + snprintf(hushpath, len, "%s/.hushlogin", ses.authstate.pw_dir); if (stat(hushpath, &sb) < 0) { /* more than a screenful is stupid IMHO */ @@ -867,12 +820,9 @@ static void addchildpid(struct ChanSess *chansess, pid_t pid) { /* 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 */ @@ -885,12 +835,6 @@ static void execchild(struct ChanSess *chansess) { 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. */ @@ -908,12 +852,12 @@ static void execchild(struct ChanSess *chansess) { /* We can only change uid/gid as root ... */ if (getuid() == 0) { - if ((setgid(ses.authstate.pw->pw_gid) < 0) || - (initgroups(ses.authstate.pw->pw_name, - ses.authstate.pw->pw_gid) < 0)) { + if ((setgid(ses.authstate.pw_gid) < 0) || + (initgroups(ses.authstate.pw_name, + ses.authstate.pw_gid) < 0)) { dropbear_exit("error changing user group"); } - if (setuid(ses.authstate.pw->pw_uid) < 0) { + if (setuid(ses.authstate.pw_uid) < 0) { dropbear_exit("error changing user"); } } else { @@ -924,29 +868,22 @@ static void execchild(struct ChanSess *chansess) { * usernames with the same uid, but differing groups, then the * differing groups won't be set (as with initgroups()). The solution * is for the sysadmin not to give out the UID twice */ - if (getuid() != ses.authstate.pw->pw_uid) { + if (getuid() != ses.authstate.pw_uid) { dropbear_exit("couldn't change user as non-root"); } } - /* an empty shell should be interpreted as "/bin/sh" */ - if (ses.authstate.pw->pw_shell[0] == '\0') { - usershell = "/bin/sh"; - } else { - usershell = ses.authstate.pw->pw_shell; - } - /* set env vars */ - addnewvar("USER", ses.authstate.pw->pw_name); - addnewvar("LOGNAME", ses.authstate.pw->pw_name); - addnewvar("HOME", ses.authstate.pw->pw_dir); - addnewvar("SHELL", usershell); + addnewvar("USER", ses.authstate.pw_name); + addnewvar("LOGNAME", ses.authstate.pw_name); + addnewvar("HOME", ses.authstate.pw_dir); + addnewvar("SHELL", get_user_shell()); if (chansess->term != NULL) { addnewvar("TERM", chansess->term); } /* change directory */ - if (chdir(ses.authstate.pw->pw_dir) < 0) { + if (chdir(ses.authstate.pw_dir) < 0) { dropbear_exit("error changing directory"); } @@ -959,32 +896,8 @@ static void execchild(struct ChanSess *chansess) { 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"); @@ -266,7 +266,11 @@ void main_noinetd() { goto out; } +#ifdef DEBUG_NOFORK + fork_ret = 0; +#else fork_ret = fork(); +#endif if (fork_ret < 0) { dropbear_log(LOG_WARNING, "error forking: %s", strerror(errno)); goto out; @@ -292,9 +296,11 @@ void main_noinetd() { addrstring = getaddrstring(&remoteaddr, 1); dropbear_log(LOG_INFO, "Child connection from %s", addrstring); +#ifndef DEBUG_NOFORK if (setsid() < 0) { dropbear_exit("setsid: %s", strerror(errno)); } +#endif /* make sure we close sockets */ for (i = 0; i < listensockcount; i++) { diff --git a/svr-runopts.c b/svr-runopts.c index 83c75c3..c8b6585 100644 --- a/svr-runopts.c +++ b/svr-runopts.c @@ -284,16 +284,13 @@ void svr_getopts(int argc, char ** argv) { 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 --git a/svr-session.c b/svr-session.c index 18fab6b..eaccfe5 100644 --- a/svr-session.c +++ b/svr-session.c @@ -80,7 +80,7 @@ void svr_session(int sock, int childpipe, 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; @@ -130,12 +130,12 @@ void svr_dropbear_exit(int exitcode, const char* format, va_list param) { /* user has authenticated */ snprintf(fmtbuf, sizeof(fmtbuf), "exit after auth (%s): %s", - ses.authstate.printableuser, format); - } else if (ses.authstate.printableuser) { + ses.authstate.pw_name, format); + } else if (ses.authstate.pw_name) { /* we have a potential user */ snprintf(fmtbuf, sizeof(fmtbuf), "exit before auth (user '%s', %d fails): %s", - ses.authstate.printableuser, ses.authstate.failcount, format); + ses.authstate.pw_name, ses.authstate.failcount, format); } else { /* before userauth */ snprintf(fmtbuf, sizeof(fmtbuf), @@ -144,6 +144,9 @@ void svr_dropbear_exit(int exitcode, const char* format, va_list param) { _dropbear_log(LOG_INFO, fmtbuf, param); + /* free potential public key options */ + svr_pubkey_options_cleanup(); + /* must be after we've done with username etc */ common_session_cleanup(); @@ -183,7 +186,7 @@ void svr_dropbear_log(int priority, const char* format, va_list param) { 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); } @@ -192,8 +195,10 @@ void svr_dropbear_log(int priority, const char* format, va_list param) { /* 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"); } diff --git a/svr-tcpfwd.c b/svr-tcpfwd.c index d4dca6b..a55361b 100644 --- a/svr-tcpfwd.c +++ b/svr-tcpfwd.c @@ -32,6 +32,7 @@ #include "packet.h" #include "listener.h" #include "runopts.h" +#include "auth.h" #ifdef ENABLE_SVR_REMOTETCPFWD @@ -72,7 +73,7 @@ void recv_msg_global_request_remotetcp() { TRACE(("enter recv_msg_global_request_remotetcp")) - if (svr_opts.noremotetcp) { + if (svr_opts.noremotetcp || !svr_pubkey_allows_tcpfwd()) { TRACE(("leave recv_msg_global_request_remotetcp: remote tcp forwarding disabled")) goto out; } @@ -236,7 +237,7 @@ static int newtcpdirect(struct Channel * channel) { int len; int err = SSH_OPEN_ADMINISTRATIVELY_PROHIBITED; - if (svr_opts.nolocaltcp) { + if (svr_opts.nolocaltcp || !svr_pubkey_allows_tcpfwd()) { TRACE(("leave newtcpdirect: local tcp forwarding disabled")) goto out; } diff --git a/svr-x11fwd.c b/svr-x11fwd.c index cbc8a79..7618e3b 100644 --- a/svr-x11fwd.c +++ b/svr-x11fwd.c @@ -33,6 +33,7 @@ #include "channel.h" #include "packet.h" #include "buffer.h" +#include "auth.h" #define X11BASEPORT 6000 #define X11BINDBASE 6010 @@ -47,6 +48,10 @@ int x11req(struct ChanSess * chansess) { int fd; + if (!svr_pubkey_allows_x11fwd()) { + return DROPBEAR_FAILURE; + } + /* we already have an x11 connection */ if (chansess->x11listener != NULL) { return DROPBEAR_FAILURE; diff --git a/sysoptions.h b/sysoptions.h new file mode 100644 index 0000000..6b17151 --- /dev/null +++ b/sysoptions.h @@ -0,0 +1,205 @@ +/******************************************************************* + * You shouldn't edit this file unless you know you need to. + * This file is only included from options.h + *******************************************************************/ + +#ifndef DROPBEAR_VERSION +#define DROPBEAR_VERSION "0.51" +#endif + +#define LOCAL_IDENT "SSH-2.0-dropbear_" DROPBEAR_VERSION +#define PROGNAME "dropbear" + +/* Spec recommends after one hour or 1 gigabyte of data. One hour + * is a bit too verbose, so we try 8 hours */ +#ifndef KEX_REKEY_TIMEOUT +#define KEX_REKEY_TIMEOUT (3600 * 8) +#endif +#ifndef KEX_REKEY_DATA +#define KEX_REKEY_DATA (1<<30) /* 2^30 == 1GB, this value must be < INT_MAX */ +#endif +/* Close connections to clients which haven't authorised after AUTH_TIMEOUT */ +#ifndef AUTH_TIMEOUT +#define AUTH_TIMEOUT 300 /* we choose 5 minutes */ +#endif + +/* Minimum key sizes for DSS and RSA */ +#ifndef MIN_DSS_KEYLEN +#define MIN_DSS_KEYLEN 512 +#endif +#ifndef MIN_RSA_KEYLEN +#define MIN_RSA_KEYLEN 512 +#endif + +#define MAX_BANNER_SIZE 2000 /* this is 25*80 chars, any more is foolish */ +#define MAX_BANNER_LINES 20 /* How many lines the client will display */ + +/* the number of NAME=VALUE pairs to malloc for environ, if we don't have + * the clearenv() function */ +#define ENV_SIZE 100 + +#define MAX_CMD_LEN 1024 /* max length of a command */ +#define MAX_TERM_LEN 200 /* max length of TERM name */ + +#define MAX_HOST_LEN 254 /* max hostname len for tcp fwding */ +#define MAX_IP_LEN 15 /* strlen("255.255.255.255") == 15 */ + +#define DROPBEAR_MAX_PORTS 10 /* max number of ports which can be specified, + ipv4 and ipv6 don't count twice */ + +/* Each port might have at least a v4 and a v6 address */ +#define MAX_LISTEN_ADDR (DROPBEAR_MAX_PORTS*3) + +#define _PATH_TTY "/dev/tty" + +#define _PATH_CP "/bin/cp" + +/* success/failure defines */ +#define DROPBEAR_SUCCESS 0 +#define DROPBEAR_FAILURE -1 + +/* various algorithm identifiers */ +#define DROPBEAR_KEX_DH_GROUP1 0 + +#define DROPBEAR_SIGNKEY_ANY 0 +#define DROPBEAR_SIGNKEY_RSA 1 +#define DROPBEAR_SIGNKEY_DSS 2 +#define DROPBEAR_SIGNKEY_NONE 3 + +#define DROPBEAR_COMP_NONE 0 +#define DROPBEAR_COMP_ZLIB 1 + +/* Required for pubkey auth */ +#if defined(ENABLE_SVR_PUBKEY_AUTH) || defined(DROPBEAR_CLIENT) +#define DROPBEAR_SIGNKEY_VERIFY +#endif + +/* SHA1 is 20 bytes == 160 bits */ +#define SHA1_HASH_SIZE 20 +/* SHA512 is 64 bytes == 512 bits */ +#define SHA512_HASH_SIZE 64 +/* MD5 is 16 bytes = 128 bits */ +#define MD5_HASH_SIZE 16 + +/* largest of MD5 and SHA1 */ +#define MAX_MAC_LEN SHA1_HASH_SIZE + + +#define MAX_KEY_LEN 32 /* 256 bits for aes256 etc */ +#define MAX_IV_LEN 20 /* must be same as max blocksize, + and >= SHA1_HASH_SIZE */ +#define MAX_MAC_KEY 20 + +#define MAX_NAME_LEN 64 /* maximum length of a protocol name, isn't + explicitly specified for all protocols (just + for algos) but seems valid */ + +#define MAX_PROPOSED_ALGO 20 + +/* size/count limits */ +#define MIN_PACKET_LEN 16 + +#define RECV_MAX_PACKET_LEN (MAX(35000, ((RECV_MAX_PAYLOAD_LEN)+100))) + +/* for channel code */ +#define TRANS_MAX_WINDOW 500000000 /* 500MB is sufficient, stopping overflow */ +#define TRANS_MAX_WIN_INCR 500000000 /* overflow prevention */ + +#define RECV_WINDOWEXTEND (opts.recv_window / 3) /* We send a "window extend" every + RECV_WINDOWEXTEND bytes */ +#define MAX_RECV_WINDOW (1024*1024) /* 1 MB should be enough */ + +#define MAX_CHANNELS 100 /* simple mem restriction, includes each tcp/x11 + connection, so can't be _too_ small */ + +#define MAX_STRING_LEN 1400 /* ~= MAX_PROPOSED_ALGO * MAX_NAME_LEN, also + is the max length for a password etc */ + +/* For a 4096 bit DSS key, empirically determined */ +#define MAX_PUBKEY_SIZE 1700 +/* For a 4096 bit DSS key, empirically determined */ +#define MAX_PRIVKEY_SIZE 1700 + +/* The maximum size of the bignum portion of the kexhash buffer */ +/* Sect. 8 of the transport draft, K_S + e + f + K */ +#define KEXHASHBUF_MAX_INTS (1700 + 130 + 130 + 130) + +#define DROPBEAR_MAX_SOCKS 2 /* IPv4, IPv6 are all we'll get for now. Revisit + in a few years time.... */ + +#define DROPBEAR_MAX_CLI_PASS 1024 + +#define DROPBEAR_MAX_CLI_INTERACT_PROMPTS 80 /* The number of prompts we'll + accept for keyb-interactive + auth */ + +#if defined(DROPBEAR_AES256_CBC) || defined(DROPBEAR_AES128_CBC) +#define DROPBEAR_AES_CBC +#endif + +#if defined(DROPBEAR_TWOFISH256_CBC) || defined(DROPBEAR_TWOFISH128_CBC) +#define DROPBEAR_TWOFISH_CBC +#endif + +#ifndef ENABLE_X11FWD +#define DISABLE_X11FWD +#endif + +#ifndef ENABLE_AGENTFWD +#define DISABLE_AGENTFWD +#endif + +#if defined(ENABLE_CLI_REMOTETCPFWD) || defined(ENABLE_CLI_LOCALTCPFWD) +#define ENABLE_CLI_ANYTCPFWD +#endif + +#if defined(ENABLE_CLI_LOCALTCPFWD) || defined(ENABLE_SVR_REMOTETCPFWD) +#define DROPBEAR_TCP_ACCEPT +#endif + +#if defined(ENABLE_CLI_REMOTETCPFWD) || defined(ENABLE_CLI_LOCALTCPFWD) || \ + defined(ENABLE_SVR_REMOTETCPFWD) || defined(ENABLE_SVR_LOCALTCPFWD) || \ + defined(ENABLE_AGENTFWD) || defined(ENABLE_X11FWD) +#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 + +#if defined(ENABLE_SVR_PASSWORD_AUTH) && defined(ENABLE_SVR_PAM_AUTH) +#error "You can't turn on PASSWORD and PAM auth both at once. Fix it in options.h" +#endif + +#if defined(DROPBEAR_RANDOM_DEV) && defined(DROPBEAR_PRNGD_SOCKET) +#error "You can't turn on DROPBEAR_PRNGD_SOCKET and DROPBEAR_RANDOM_DEV at once" +#endif + +#if !defined(DROPBEAR_RANDOM_DEV) && !defined(DROPBEAR_PRNGD_SOCKET) +#error "You must choose one of DROPBEAR_PRNGD_SOCKET or DROPBEAR_RANDOM_DEV in options.h" +#endif + +/* We use dropbear_client and dropbear_server as shortcuts to avoid redundant + * code, if we're just compiling as client or server */ +#if defined(DROPBEAR_SERVER) && defined(DROPBEAR_CLIENT) + +#define IS_DROPBEAR_SERVER (ses.isserver == 1) +#define IS_DROPBEAR_CLIENT (ses.isserver == 0) + +#elif defined(DROPBEAR_SERVER) + +#define IS_DROPBEAR_SERVER 1 +#define IS_DROPBEAR_CLIENT 0 + +#elif defined(DROPBEAR_CLIENT) + +#define IS_DROPBEAR_SERVER 0 +#define IS_DROPBEAR_CLIENT 1 + +#else +#error You must compiled with either DROPBEAR_CLIENT or DROPBEAR_SERVER selected +#endif @@ -55,6 +55,7 @@ struct TCPFwdList { /* Server */ void recv_msg_global_request_remotetcp(); + extern const struct ChanType svr_chan_tcpdirect; /* Client */ |