diff options
-rw-r--r-- | CHANGES | 53 | ||||
-rw-r--r-- | LICENSE | 2 | ||||
-rw-r--r-- | Makefile.in | 2 | ||||
-rw-r--r-- | algo.h | 22 | ||||
-rw-r--r-- | auth.h | 3 | ||||
-rw-r--r-- | cli-auth.c | 4 | ||||
-rw-r--r-- | cli-authpubkey.c | 1 | ||||
-rw-r--r-- | cli-kex.c | 1 | ||||
-rw-r--r-- | cli-main.c | 5 | ||||
-rw-r--r-- | cli-runopts.c | 104 | ||||
-rw-r--r-- | cli-session.c | 4 | ||||
-rw-r--r-- | cli-tcpfwd.c | 38 | ||||
-rw-r--r-- | common-algo.c | 124 | ||||
-rw-r--r-- | common-channel.c | 10 | ||||
-rw-r--r-- | common-kex.c | 126 | ||||
-rw-r--r-- | common-session.c | 44 | ||||
-rw-r--r-- | configure.in | 3 | ||||
-rw-r--r-- | dbclient.1 | 47 | ||||
-rw-r--r-- | debian/changelog | 6 | ||||
-rw-r--r-- | debian/dropbear.postinst | 2 | ||||
-rw-r--r-- | debug.h | 2 | ||||
-rw-r--r-- | dropbear.8 | 62 | ||||
-rw-r--r-- | dropbearkey.8 | 11 | ||||
-rw-r--r-- | kex.h | 5 | ||||
-rw-r--r-- | libtomcrypt/src/headers/tomcrypt_custom.h | 12 | ||||
-rw-r--r-- | loginrec.c | 2 | ||||
-rw-r--r-- | options.h | 53 | ||||
-rw-r--r-- | packet.c | 327 | ||||
-rw-r--r-- | packet.h | 2 | ||||
-rw-r--r-- | process-packet.c | 1 | ||||
-rw-r--r-- | runopts.h | 1 | ||||
-rw-r--r-- | scp.c | 15 | ||||
-rw-r--r-- | session.h | 53 | ||||
-rw-r--r-- | svr-auth.c | 2 | ||||
-rw-r--r-- | svr-authpubkeyoptions.c | 2 | ||||
-rw-r--r-- | svr-chansession.c | 3 | ||||
-rw-r--r-- | svr-main.c | 2 | ||||
-rw-r--r-- | svr-runopts.c | 20 | ||||
-rw-r--r-- | sysoptions.h | 16 | ||||
-rw-r--r-- | tcpfwd.h | 4 |
40 files changed, 789 insertions, 407 deletions
@@ -1,3 +1,56 @@ +0.52 - Wed 12 November 2008 + +- Add "netcat-alike" option (-B) to dbclient, allowing Dropbear to tunnel + standard input/output to a TCP port-forwarded remote host. + +- Add "proxy command" support to dbclient, to allow using a spawned process for + IO rather than a direct TCP connection. eg + dbclient remotehost + is equivalent to + dbclient -J 'nc remotehost 22' remotehost + (the hostname is still provided purely for looking up saved host keys) + +- Combine netcat-alike and proxy support to allow "multihop" connections, with + comma-separated host syntax. Allows running + + dbclient user1@host1,user2@host2,user3@host3 + + to end up at host3 via the other two, using SSH TCP forwarding. It's a bit + like onion-routing. All connections are established from the local machine. + The comma-separated syntax can also be used for scp/rsync, eg + + rsync -a -e dbclient m@gateway,m2@host,martello:/home/matt/ ~/backup/ + + to bounce through a few hosts. + +- Add -I "idle timeout" option (contributed by Farrell Aultman) + +- Allow restrictions on authorized_keys logins such as restricting commands + to be run etc. This is a subset of those allowed by OpenSSH, doesn't + yet allow restricting source host. + +- Use vfork() for scp on uClinux + +- Default to PATH=/usr/bin:/bin for shells. + +- Report errors if -R forwarding fails + +- Add counter mode cipher support, which avoids some security problems with the + standard CBC mode. + +- Support zlib@openssh.com delayed compression for client/server. It can be + required for the Dropbear server with the '-Z' option. This is useful for + security as it avoids exposing the server to attacks on zlib by + unauthenticated remote users, though requires client side support. + +- options.h has been split into options.h (user-changable) and sysoptions.h + (less commonly changed) + +- Support "dbclient -s sftp" to specify a subsystem + +- Fix a bug in replies to channel requests that could be triggered by recent + versions of PuTTY + 0.51 - Thu 27 March 2008 - Make a copy of password fields rather erroneously relying on getwpnam() @@ -8,7 +8,7 @@ The majority of code is written by Matt Johnston, under the license below. Portions of the client-mode work are (c) 2004 Mihnea Stoenescu, under the same license: -Copyright (c) 2002-2006 Matt Johnston +Copyright (c) 2002-2008 Matt Johnston Portions copyright (c) 2004 Mihnea Stoenescu All rights reserved. diff --git a/Makefile.in b/Makefile.in index 3e6c855..4c8441f 100644 --- a/Makefile.in +++ b/Makefile.in @@ -25,7 +25,7 @@ COMMONOBJS=dbutil.o buffer.o \ SVROBJS=svr-kex.o svr-algo.o svr-auth.o sshpty.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 + svr-tcpfwd.o svr-authpam.o @CRYPTLIB@ CLIOBJS=cli-algo.o cli-main.o cli-auth.o cli-authpasswd.o cli-kex.o \ cli-session.o cli-service.o cli-runopts.o cli-chansession.o \ @@ -29,13 +29,18 @@ #include "includes.h" #include "buffer.h" +#define DROPBEAR_MODE_UNUSED 0 +#define DROPBEAR_MODE_CBC 1 +#define DROPBEAR_MODE_CTR 2 + struct Algo_Type { unsigned char *name; /* identifying name */ char val; /* a value for this cipher, or -1 for invalid */ - void *data; /* algorithm specific data */ - unsigned usable : 1; /* whether we can use this algorithm */ - + const void *data; /* algorithm specific data */ + char usable; /* whether we can use this algorithm */ + const void *mode; /* the mode, currently only used for ciphers, + points to a 'struct dropbear_cipher_mode' */ }; typedef struct Algo_Type algo_type; @@ -48,6 +53,7 @@ extern algo_type sshhashes[]; extern algo_type sshcompress[]; extern const struct dropbear_cipher dropbear_nocipher; +extern const struct dropbear_cipher_mode dropbear_mode_none; extern const struct dropbear_hash dropbear_nohash; struct dropbear_cipher { @@ -56,6 +62,16 @@ struct dropbear_cipher { unsigned char blocksize; }; +struct dropbear_cipher_mode { + int (*start)(int cipher, const unsigned char *IV, + const unsigned char *key, + int keylen, int num_rounds, void *cipher_state); + int (*encrypt)(const unsigned char *pt, unsigned char *ct, + unsigned long len, void *cipher_state); + int (*decrypt)(const unsigned char *ct, unsigned char *pt, + unsigned long len, void *cipher_state); +}; + struct dropbear_hash { const struct ltc_hash_descriptor *hashdesc; unsigned long keysize; @@ -135,7 +135,8 @@ struct SignKeyList { int type; /* The type of key */ struct SignKeyList *next; int source; - /* filename? or the buffer? for encrypted keys, so we can later get + char *filename; + /* the buffer? for encrypted keys, so we can later get * the private key portion */ }; @@ -91,7 +91,7 @@ void recv_msg_userauth_banner() { } } - printf("%s\n", banner); + fprintf(stderr, "%s\n", banner); out: m_free(banner); @@ -229,6 +229,8 @@ void recv_msg_userauth_failure() { void recv_msg_userauth_success() { TRACE(("received msg_userauth_success")) + /* Note: in delayed-zlib mode, setting authdone here + * will enable compression in the transport layer */ ses.authstate.authdone = 1; cli_ses.state = USERAUTH_SUCCESS_RCVD; cli_ses.lastauthtype = AUTH_TYPE_NONE; diff --git a/cli-authpubkey.c b/cli-authpubkey.c index 3b89eae..20277a1 100644 --- a/cli-authpubkey.c +++ b/cli-authpubkey.c @@ -53,6 +53,7 @@ void cli_pubkeyfail() { } sign_key_free(cli_ses.lastprivkey->key); /* It won't be used again */ + m_free(cli_ses.lastprivkey->filename); m_free(cli_ses.lastprivkey); TRACE(("leave cli_pubkeyfail")) @@ -327,4 +327,5 @@ out: if (line != NULL) { buf_free(line); } + m_free(fingerprint); } @@ -32,7 +32,9 @@ 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); +#ifdef ENABLE_CLI_PROXYCMD static void cli_proxy_cmd(int *sock_in, int *sock_out); +#endif #if defined(DBMULTI_dbclient) || !defined(DROPBEAR_MULTI) #if defined(DBMULTI_dbclient) && defined(DROPBEAR_MULTI) @@ -63,6 +65,7 @@ int main(int argc, char ** argv) { #ifdef ENABLE_CLI_PROXYCMD if (cli_opts.proxycmd) { cli_proxy_cmd(&sock_in, &sock_out); + m_free(cli_opts.proxycmd); } else #endif { @@ -132,6 +135,7 @@ static void exec_proxy_cmd(void *user_data_cmd) { dropbear_exit("Failed to run '%s'\n", cmd); } +#ifdef ENABLE_CLI_PROXYCMD static void cli_proxy_cmd(int *sock_in, int *sock_out) { int ret; @@ -144,3 +148,4 @@ static void cli_proxy_cmd(int *sock_in, int *sock_out) { *sock_in = *sock_out = -1; } } +#endif // ENABLE_CLI_PROXYCMD diff --git a/cli-runopts.c b/cli-runopts.c index d49caa2..a7c0f82 100644 --- a/cli-runopts.c +++ b/cli-runopts.c @@ -49,7 +49,11 @@ static void add_netcat(const char *str); static void printhelp() { fprintf(stderr, "Dropbear client v%s\n" +#ifdef ENABLE_CLI_MULTIHOP + "Usage: %s [options] [user@]host[/port][,[user@]host/port],...] [command]\n" +#else "Usage: %s [options] [user@]host[/port] [command]\n" +#endif "Options are:\n" "-p <remoteport>\n" "-l <username>\n" @@ -74,22 +78,22 @@ 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" + "-I <idle_timeout> (0 is never, default %d)\n" #ifdef ENABLE_CLI_NETCAT - "-B <endhost:endport> Netcat-alike bouncing\n" + "-B <endhost:endport> Netcat-alike forwarding\n" #endif #ifdef ENABLE_CLI_PROXYCMD - "-J <proxy_program> Use program rather than tcp connection\n" + "-J <proxy_program> Use program pipe rather than TCP connection\n" #endif #ifdef DEBUG_TRACE - "-v verbose\n" + "-v verbose (compiled with DEBUG_TRACE)\n" #endif ,DROPBEAR_VERSION, cli_opts.progname, - DEFAULT_RECV_WINDOW, DEFAULT_KEEPALIVE); + DEFAULT_RECV_WINDOW, DEFAULT_KEEPALIVE, DEFAULT_IDLE_TIMEOUT); } void cli_getopts(int argc, char ** argv) { - unsigned int i, j; char ** next = 0; unsigned int cmdlen; @@ -109,6 +113,8 @@ void cli_getopts(int argc, char ** argv) { char* recv_window_arg = NULL; char* keepalive_arg = NULL; + char* idle_timeout_arg = NULL; + char *host_arg = NULL; /* see printhelp() for options */ cli_opts.progname = argv[0]; @@ -264,6 +270,9 @@ void cli_getopts(int argc, char ** argv) { case 'K': next = &keepalive_arg; break; + case 'I': + next = &idle_timeout_arg; + break; #ifdef ENABLE_CLI_AGENTFWD case 'A': cli_opts.agent_fwd = 1; @@ -307,12 +316,8 @@ void cli_getopts(int argc, char ** argv) { /* Either the hostname or commands */ - if (cli_opts.remotehost == NULL) { -#ifdef ENABLE_CLI_MULTIHOP - parse_multihop_hostname(argv[i], argv[0]); -#else - parse_hostname(argv[i]); -#endif + if (host_arg == NULL) { + host_arg = argv[i]; } else { /* this is part of the commands to send - after this we @@ -341,7 +346,7 @@ void cli_getopts(int argc, char ** argv) { /* And now a few sanity checks and setup */ - if (cli_opts.remotehost == NULL) { + if (host_arg == NULL) { printhelp(); exit(EXIT_FAILURE); } @@ -377,12 +382,26 @@ void cli_getopts(int argc, char ** argv) { } } + if (idle_timeout_arg) { + if (m_str_to_uint(idle_timeout_arg, &opts.idle_timeout_secs) == DROPBEAR_FAILURE) { + dropbear_exit("Bad idle_timeout '%s'", idle_timeout_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 - + + /* The hostname gets set up last, since + * in multi-hop mode it will require knowledge + * of other flags such as -i */ +#ifdef ENABLE_CLI_MULTIHOP + parse_multihop_hostname(host_arg, argv[0]); +#else + parse_hostname(host_arg); +#endif } #ifdef ENABLE_CLI_PUBKEY_AUTH @@ -395,14 +414,12 @@ static void loadidentityfile(const char* filename) { key = new_sign_key(); keytype = DROPBEAR_SIGNKEY_ANY; if ( readhostkey(filename, key, &keytype) != DROPBEAR_SUCCESS ) { - fprintf(stderr, "Failed loading keyfile '%s'\n", filename); sign_key_free(key); - } else { - nextkey = (struct SignKeyList*)m_malloc(sizeof(struct SignKeyList)); nextkey->key = key; + nextkey->filename = m_strdup(filename); nextkey->next = cli_opts.privkeys; nextkey->type = keytype; nextkey->source = SIGNKEY_SOURCE_RAW_FILE; @@ -413,6 +430,39 @@ static void loadidentityfile(const char* filename) { #ifdef ENABLE_CLI_MULTIHOP +static char* +multihop_passthrough_args() { + char *ret; + int total; + unsigned int len = 0; + struct SignKeyList *nextkey; + /* Fill out -i and -W options that make sense for all + * the intermediate processes */ + for (nextkey = cli_opts.privkeys; nextkey; nextkey = nextkey->next) + { + len += 3 + strlen(nextkey->filename); + } + len += 20; // space for -W <size>, terminator. + ret = m_malloc(len); + total = 0; + + if (opts.recv_window != DEFAULT_RECV_WINDOW) + { + int written = snprintf(ret+total, len-total, "-W %d", opts.recv_window); + total += written; + } + + for (nextkey = cli_opts.privkeys; nextkey; nextkey = nextkey->next) + { + const size_t size = len - total; + int written = snprintf(ret+total, size, "-i %s", nextkey->filename); + dropbear_assert(written < size); + total += written; + } + + return ret; +} + /* Sets up 'onion-forwarding' connections. This will spawn * a separate dbclient process for each hop. * As an example, if the cmdline is @@ -427,6 +477,7 @@ static void loadidentityfile(const char* filename) { */ static void parse_multihop_hostname(const char* orighostarg, const char* argv0) { char *userhostarg = NULL; + char *hostbuf = NULL; char *last_hop = NULL;; char *remainder = NULL; @@ -439,11 +490,12 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0) && 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); + hostbuf = m_malloc(len); + snprintf(hostbuf, len, "%s@%s", cli_opts.username, orighostarg); } else { - userhostarg = m_strdup(orighostarg); + hostbuf = m_strdup(orighostarg); } + userhostarg = hostbuf; last_hop = strrchr(userhostarg, ','); if (last_hop) { @@ -461,19 +513,24 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0) if (last_hop) { /* Set up the proxycmd */ unsigned int cmd_len = 0; + char *passthrough_args = multihop_passthrough_args(); 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) + cmd_len = strlen(argv0) + strlen(remainder) + strlen(cli_opts.remotehost) + strlen(cli_opts.remoteport) - + strlen(argv0) + 30; + + strlen(passthrough_args) + + 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); + snprintf(cli_opts.proxycmd, cmd_len, "%s -B %s:%s %s %s", + argv0, cli_opts.remotehost, cli_opts.remoteport, + passthrough_args, remainder); + m_free(passthrough_args); } + m_free(hostbuf); } #endif /* !ENABLE_CLI_MULTIHOP */ @@ -622,6 +679,7 @@ static void addforward(const char* origstr, struct TCPFwdList** fwdlist) { goto badport; } + newfwd->have_reply = 0; newfwd->next = *fwdlist; *fwdlist = newfwd; diff --git a/cli-session.c b/cli-session.c index 8c087d4..4aa2366 100644 --- a/cli-session.c +++ b/cli-session.c @@ -64,6 +64,10 @@ static const packettype cli_packettypes[] = { {SSH_MSG_CHANNEL_OPEN_FAILURE, recv_msg_channel_open_failure}, {SSH_MSG_USERAUTH_BANNER, recv_msg_userauth_banner}, /* client */ {SSH_MSG_USERAUTH_SPECIFIC_60, recv_msg_userauth_specific_60}, /* client */ +#ifdef ENABLE_CLI_REMOTETCPFWD + {SSH_MSG_REQUEST_SUCCESS, cli_recv_msg_request_success}, /* client */ + {SSH_MSG_REQUEST_FAILURE, cli_recv_msg_request_failure}, /* client */ +#endif {0, 0} /* End */ }; diff --git a/cli-tcpfwd.c b/cli-tcpfwd.c index c3bfd4d..0e60090 100644 --- a/cli-tcpfwd.c +++ b/cli-tcpfwd.c @@ -128,7 +128,7 @@ static void send_msg_global_request_remotetcp(int port) { CHECKCLEARTOWRITE(); buf_putbyte(ses.writepayload, SSH_MSG_GLOBAL_REQUEST); buf_putstring(ses.writepayload, "tcpip-forward", 13); - buf_putbyte(ses.writepayload, 0); + buf_putbyte(ses.writepayload, 1); /* want_reply */ if (opts.listen_fwd_all) { listenspec = ""; } else { @@ -143,6 +143,42 @@ static void send_msg_global_request_remotetcp(int port) { TRACE(("leave send_msg_global_request_remotetcp")) } +/* The only global success/failure messages are for remotetcp. + * Since there isn't any identifier in these messages, we have to rely on them + * being in the same order as we sent the requests. This is the ordering + * of the cli_opts.remotefwds list */ +void cli_recv_msg_request_success() { + + /* Nothing in the packet. We just mark off that we have received the reply, + * so that we can report failure for later ones. */ + struct TCPFwdList * iter = NULL; + + iter = cli_opts.remotefwds; + while (iter != NULL) { + if (!iter->have_reply) + { + iter->have_reply = 1; + return; + } + iter = iter->next; + } +} + +void cli_recv_msg_request_failure() { + struct TCPFwdList * iter = NULL; + + iter = cli_opts.remotefwds; + while (iter != NULL) { + if (!iter->have_reply) + { + iter->have_reply = 1; + dropbear_log(LOG_WARNING, "Remote TCP forward request failed (port %d -> %s:%d)", iter->listenport, iter->connectaddr, iter->connectport); + return; + } + iter = iter->next; + } +} + void setup_remotetcp() { struct TCPFwdList * iter = NULL; diff --git a/common-algo.c b/common-algo.c index 21ac96a..892399f 100644 --- a/common-algo.c +++ b/common-algo.c @@ -29,32 +29,46 @@ /* This file (algo.c) organises the ciphers which can be used, and is used to * decide which ciphers/hashes/compression/signing to use during key exchange*/ +static int void_cipher(const unsigned char* in, unsigned char* out, + unsigned long len, void *cipher_state) { + if (in != out) { + memmove(out, in, len); + } + return CRYPT_OK; +} + +static int void_start(int cipher, const unsigned char *IV, + const unsigned char *key, + int keylen, int num_rounds, void *cipher_state) { + return CRYPT_OK; +} + /* Mappings for ciphers, parameters are {&cipher_desc, keysize, blocksize} */ /* NOTE: if keysize > 2*SHA1_HASH_SIZE, code such as hashkeys() needs revisiting */ -#ifdef DROPBEAR_AES256_CBC +#ifdef DROPBEAR_AES256 static const struct dropbear_cipher dropbear_aes256 = {&aes_desc, 32, 16}; #endif -#ifdef DROPBEAR_AES128_CBC +#ifdef DROPBEAR_AES128 static const struct dropbear_cipher dropbear_aes128 = {&aes_desc, 16, 16}; #endif -#ifdef DROPBEAR_BLOWFISH_CBC +#ifdef DROPBEAR_BLOWFISH static const struct dropbear_cipher dropbear_blowfish = {&blowfish_desc, 16, 8}; #endif -#ifdef DROPBEAR_TWOFISH256_CBC +#ifdef DROPBEAR_TWOFISH256 static const struct dropbear_cipher dropbear_twofish256 = {&twofish_desc, 32, 16}; #endif -#ifdef DROPBEAR_TWOFISH128_CBC +#ifdef DROPBEAR_TWOFISH128 static const struct dropbear_cipher dropbear_twofish128 = {&twofish_desc, 16, 16}; #endif -#ifdef DROPBEAR_3DES_CBC +#ifdef DROPBEAR_3DES static const struct dropbear_cipher dropbear_3des = {&des3_desc, 24, 8}; #endif @@ -63,6 +77,24 @@ static const struct dropbear_cipher dropbear_3des = const struct dropbear_cipher dropbear_nocipher = {NULL, 16, 8}; +/* A few void* s are required to silence warnings + * about the symmetric_CBC vs symmetric_CTR cipher_state pointer */ +const struct dropbear_cipher_mode dropbear_mode_cbc = + {(void*)cbc_start, (void*)cbc_encrypt, (void*)cbc_decrypt}; +const struct dropbear_cipher_mode dropbear_mode_none = + {void_start, void_cipher, void_cipher}; +#ifdef DROPBEAR_ENABLE_CTR_MODE +/* a wrapper to make ctr_start and cbc_start look the same */ +static int dropbear_big_endian_ctr_start(int cipher, + const unsigned char *IV, + const unsigned char *key, int keylen, + int num_rounds, symmetric_CTR *ctr) { + return ctr_start(cipher, IV, key, keylen, num_rounds, CTR_COUNTER_BIG_ENDIAN, ctr); +} +const struct dropbear_cipher_mode dropbear_mode_ctr = + {(void*)dropbear_big_endian_ctr_start, (void*)ctr_encrypt, (void*)ctr_decrypt}; +#endif + /* Mapping of ssh hashes to libtomcrypt hashes, including keysize etc. {&hash_desc, keysize, hashsize} */ @@ -83,65 +115,81 @@ const struct dropbear_hash dropbear_nohash = {NULL, 16, 0}; /* used initially */ -/* The following map ssh names to internal values */ +/* The following map ssh names to internal values. + * The ordering here is important for the client - the first mode + * that is also supported by the server will get used. */ algo_type sshciphers[] = { -#ifdef DROPBEAR_AES128_CBC - {"aes128-cbc", 0, (void*)&dropbear_aes128, 1}, +#ifdef DROPBEAR_ENABLE_CTR_MODE +#ifdef DROPBEAR_AES128 + {"aes128-ctr", 0, &dropbear_aes128, 1, &dropbear_mode_ctr}, +#endif +#ifdef DROPBEAR_3DES + {"3des-ctr", 0, &dropbear_3des, 1, &dropbear_mode_ctr}, +#endif +#ifdef DROPBEAR_AES256 + {"aes256-ctr", 0, &dropbear_aes256, 1, &dropbear_mode_ctr}, +#endif +#endif /* DROPBEAR_ENABLE_CTR_MODE */ + +/* CBC modes are always enabled */ +#ifdef DROPBEAR_AES128 + {"aes128-cbc", 0, &dropbear_aes128, 1, &dropbear_mode_cbc}, #endif -#ifdef DROPBEAR_3DES_CBC - {"3des-cbc", 0, (void*)&dropbear_3des, 1}, +#ifdef DROPBEAR_3DES + {"3des-cbc", 0, &dropbear_3des, 1, &dropbear_mode_cbc}, #endif -#ifdef DROPBEAR_AES256_CBC - {"aes256-cbc", 0, (void*)&dropbear_aes256, 1}, +#ifdef DROPBEAR_AES256 + {"aes256-cbc", 0, &dropbear_aes256, 1, &dropbear_mode_cbc}, #endif -#ifdef DROPBEAR_TWOFISH256_CBC - {"twofish256-cbc", 0, (void*)&dropbear_twofish256, 1}, - {"twofish-cbc", 0, (void*)&dropbear_twofish256, 1}, +#ifdef DROPBEAR_TWOFISH256 + {"twofish256-cbc", 0, &dropbear_twofish256, 1, &dropbear_mode_cbc}, + {"twofish-cbc", 0, &dropbear_twofish256, 1, &dropbear_mode_cbc}, #endif -#ifdef DROPBEAR_TWOFISH128_CBC - {"twofish128-cbc", 0, (void*)&dropbear_twofish128, 1}, +#ifdef DROPBEAR_TWOFISH128 + {"twofish128-cbc", 0, &dropbear_twofish128, 1, &dropbear_mode_cbc}, #endif -#ifdef DROPBEAR_BLOWFISH_CBC - {"blowfish-cbc", 0, (void*)&dropbear_blowfish, 1}, +#ifdef DROPBEAR_BLOWFISH + {"blowfish-cbc", 0, &dropbear_blowfish, 1, &dropbear_mode_cbc}, #endif - {NULL, 0, NULL, 0} + {NULL, 0, NULL, 0, NULL} }; algo_type sshhashes[] = { #ifdef DROPBEAR_SHA1_96_HMAC - {"hmac-sha1-96", 0, (void*)&dropbear_sha1_96, 1}, + {"hmac-sha1-96", 0, &dropbear_sha1_96, 1, NULL}, #endif #ifdef DROPBEAR_SHA1_HMAC - {"hmac-sha1", 0, (void*)&dropbear_sha1, 1}, + {"hmac-sha1", 0, &dropbear_sha1, 1, NULL}, #endif #ifdef DROPBEAR_MD5_HMAC - {"hmac-md5", 0, (void*)&dropbear_md5, 1}, + {"hmac-md5", 0, &dropbear_md5, 1, NULL}, #endif - {NULL, 0, NULL, 0} + {NULL, 0, NULL, 0, NULL} }; algo_type sshcompress[] = { #ifndef DISABLE_ZLIB - {"zlib", DROPBEAR_COMP_ZLIB, NULL, 1}, + {"zlib", DROPBEAR_COMP_ZLIB, NULL, 1, NULL}, + {"zlib@openssh.com", DROPBEAR_COMP_ZLIB_DELAY, NULL, 1, NULL}, #endif - {"none", DROPBEAR_COMP_NONE, NULL, 1}, - {NULL, 0, NULL, 0} + {"none", DROPBEAR_COMP_NONE, NULL, 1, NULL}, + {NULL, 0, NULL, 0, NULL} }; algo_type sshhostkey[] = { #ifdef DROPBEAR_RSA - {"ssh-rsa", DROPBEAR_SIGNKEY_RSA, NULL, 1}, + {"ssh-rsa", DROPBEAR_SIGNKEY_RSA, NULL, 1, NULL}, #endif #ifdef DROPBEAR_DSS - {"ssh-dss", DROPBEAR_SIGNKEY_DSS, NULL, 1}, + {"ssh-dss", DROPBEAR_SIGNKEY_DSS, NULL, 1, NULL}, #endif - {NULL, 0, NULL, 0} + {NULL, 0, NULL, 0, NULL} }; algo_type sshkex[] = { - {"diffie-hellman-group1-sha1", DROPBEAR_KEX_DH_GROUP1, NULL, 1}, - {NULL, 0, NULL, 0} + {"diffie-hellman-group1-sha1", DROPBEAR_KEX_DH_GROUP1, NULL, 1, NULL}, + {NULL, 0, NULL, 0, NULL} }; @@ -150,16 +198,16 @@ algo_type sshkex[] = { void crypto_init() { const struct ltc_cipher_descriptor *regciphers[] = { -#ifdef DROPBEAR_AES_CBC +#ifdef DROPBEAR_AES &aes_desc, #endif -#ifdef DROPBEAR_BLOWFISH_CBC +#ifdef DROPBEAR_BLOWFISH &blowfish_desc, #endif -#ifdef DROPBEAR_TWOFISH_CBC +#ifdef DROPBEAR_TWOFISH &twofish_desc, #endif -#ifdef DROPBEAR_3DES_CBC +#ifdef DROPBEAR_3DES &des3_desc, #endif NULL @@ -215,7 +263,7 @@ void buf_put_algolist(buffer * buf, algo_type localalgos[]) { unsigned int donefirst = 0; buffer *algolist = NULL; - algolist = buf_new(100); + algolist = buf_new(160); for (i = 0; localalgos[i].name != NULL; i++) { if (localalgos[i].usable) { if (donefirst) diff --git a/common-channel.c b/common-channel.c index 2105184..95fce78 100644 --- a/common-channel.c +++ b/common-channel.c @@ -276,10 +276,10 @@ 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 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; @@ -691,7 +691,7 @@ void common_recv_msg_channel_data(struct Channel *channel, int fd, dropbear_exit("received data after eof"); } - if (fd < 0) { + if (fd < 0) { /* If we have encountered failed write, the far side might still * be sending data without having yet received our close notification. * We just drop the data. */ diff --git a/common-kex.c b/common-kex.c index e9c655d..052324b 100644 --- a/common-kex.c +++ b/common-kex.c @@ -272,8 +272,8 @@ void gen_new_keys() { recv_IV = S2C_IV; trans_key = C2S_key; recv_key = S2C_key; - C2S_keysize = ses.newkeys->trans_algo_crypt->keysize; - S2C_keysize = ses.newkeys->recv_algo_crypt->keysize; + C2S_keysize = ses.newkeys->trans.algo_crypt->keysize; + S2C_keysize = ses.newkeys->recv.algo_crypt->keysize; mactransletter = 'E'; macrecvletter = 'F'; } else { @@ -281,8 +281,8 @@ void gen_new_keys() { recv_IV = C2S_IV; trans_key = S2C_key; recv_key = C2S_key; - C2S_keysize = ses.newkeys->recv_algo_crypt->keysize; - S2C_keysize = ses.newkeys->trans_algo_crypt->keysize; + C2S_keysize = ses.newkeys->recv.algo_crypt->keysize; + S2C_keysize = ses.newkeys->trans.algo_crypt->keysize; mactransletter = 'F'; macrecvletter = 'E'; } @@ -292,30 +292,33 @@ void gen_new_keys() { hashkeys(C2S_key, C2S_keysize, &hs, 'C'); hashkeys(S2C_key, S2C_keysize, &hs, 'D'); - recv_cipher = find_cipher(ses.newkeys->recv_algo_crypt->cipherdesc->name); + recv_cipher = find_cipher(ses.newkeys->recv.algo_crypt->cipherdesc->name); if (recv_cipher < 0) dropbear_exit("crypto error"); - - if (cbc_start(recv_cipher, recv_IV, recv_key, - ses.newkeys->recv_algo_crypt->keysize, 0, - &ses.newkeys->recv_symmetric_struct) != CRYPT_OK) { + if (ses.newkeys->recv.crypt_mode->start(recv_cipher, + recv_IV, recv_key, + ses.newkeys->recv.algo_crypt->keysize, 0, + &ses.newkeys->recv.cipher_state) != CRYPT_OK) { dropbear_exit("crypto error"); } - trans_cipher = find_cipher(ses.newkeys->trans_algo_crypt->cipherdesc->name); + + trans_cipher = find_cipher(ses.newkeys->trans.algo_crypt->cipherdesc->name); if (trans_cipher < 0) dropbear_exit("crypto error"); - - if (cbc_start(trans_cipher, trans_IV, trans_key, - ses.newkeys->trans_algo_crypt->keysize, 0, - &ses.newkeys->trans_symmetric_struct) != CRYPT_OK) { + if (ses.newkeys->trans.crypt_mode->start(trans_cipher, + trans_IV, trans_key, + ses.newkeys->trans.algo_crypt->keysize, 0, + &ses.newkeys->trans.cipher_state) != CRYPT_OK) { dropbear_exit("crypto error"); } /* MAC keys */ - hashkeys(ses.newkeys->transmackey, - ses.newkeys->trans_algo_mac->keysize, &hs, mactransletter); - hashkeys(ses.newkeys->recvmackey, - ses.newkeys->recv_algo_mac->keysize, &hs, macrecvletter); + hashkeys(ses.newkeys->trans.mackey, + ses.newkeys->trans.algo_mac->keysize, &hs, mactransletter); + hashkeys(ses.newkeys->recv.mackey, + ses.newkeys->recv.algo_mac->keysize, &hs, macrecvletter); + ses.newkeys->trans.hash_index = find_hash(ses.newkeys->trans.algo_mac->hashdesc->name), + ses.newkeys->recv.hash_index = find_hash(ses.newkeys->recv.algo_mac->hashdesc->name), #ifndef DISABLE_ZLIB gen_new_zstreams(); @@ -331,53 +334,68 @@ void gen_new_keys() { } #ifndef DISABLE_ZLIB + +int is_compress_trans() { + return ses.keys->trans.algo_comp == DROPBEAR_COMP_ZLIB + || (ses.authstate.authdone + && ses.keys->trans.algo_comp == DROPBEAR_COMP_ZLIB_DELAY); +} + +int is_compress_recv() { + return ses.keys->recv.algo_comp == DROPBEAR_COMP_ZLIB + || (ses.authstate.authdone + && ses.keys->recv.algo_comp == DROPBEAR_COMP_ZLIB_DELAY); +} + /* Set up new zlib compression streams, close the old ones. Only * called from gen_new_keys() */ static void gen_new_zstreams() { /* create new zstreams */ - if (ses.newkeys->recv_algo_comp == DROPBEAR_COMP_ZLIB) { - ses.newkeys->recv_zstream = (z_streamp)m_malloc(sizeof(z_stream)); - ses.newkeys->recv_zstream->zalloc = Z_NULL; - ses.newkeys->recv_zstream->zfree = Z_NULL; + if (ses.newkeys->recv.algo_comp == DROPBEAR_COMP_ZLIB + || ses.newkeys->recv.algo_comp == DROPBEAR_COMP_ZLIB_DELAY) { + ses.newkeys->recv.zstream = (z_streamp)m_malloc(sizeof(z_stream)); + ses.newkeys->recv.zstream->zalloc = Z_NULL; + ses.newkeys->recv.zstream->zfree = Z_NULL; - if (inflateInit(ses.newkeys->recv_zstream) != Z_OK) { + if (inflateInit(ses.newkeys->recv.zstream) != Z_OK) { dropbear_exit("zlib error"); } } else { - ses.newkeys->recv_zstream = NULL; + ses.newkeys->recv.zstream = NULL; } - if (ses.newkeys->trans_algo_comp == DROPBEAR_COMP_ZLIB) { - ses.newkeys->trans_zstream = (z_streamp)m_malloc(sizeof(z_stream)); - ses.newkeys->trans_zstream->zalloc = Z_NULL; - ses.newkeys->trans_zstream->zfree = Z_NULL; + if (ses.newkeys->trans.algo_comp == DROPBEAR_COMP_ZLIB + || ses.newkeys->trans.algo_comp == DROPBEAR_COMP_ZLIB_DELAY) { + ses.newkeys->trans.zstream = (z_streamp)m_malloc(sizeof(z_stream)); + ses.newkeys->trans.zstream->zalloc = Z_NULL; + ses.newkeys->trans.zstream->zfree = Z_NULL; - if (deflateInit(ses.newkeys->trans_zstream, Z_DEFAULT_COMPRESSION) + if (deflateInit(ses.newkeys->trans.zstream, Z_DEFAULT_COMPRESSION) != Z_OK) { dropbear_exit("zlib error"); } } else { - ses.newkeys->trans_zstream = NULL; + ses.newkeys->trans.zstream = NULL; } - + /* clean up old keys */ - if (ses.keys->recv_zstream != NULL) { - if (inflateEnd(ses.keys->recv_zstream) == Z_STREAM_ERROR) { + if (ses.keys->recv.zstream != NULL) { + if (inflateEnd(ses.keys->recv.zstream) == Z_STREAM_ERROR) { /* Z_DATA_ERROR is ok, just means that stream isn't ended */ dropbear_exit("crypto error"); } - m_free(ses.keys->recv_zstream); + m_free(ses.keys->recv.zstream); } - if (ses.keys->trans_zstream != NULL) { - if (deflateEnd(ses.keys->trans_zstream) == Z_STREAM_ERROR) { + if (ses.keys->trans.zstream != NULL) { + if (deflateEnd(ses.keys->trans.zstream) == Z_STREAM_ERROR) { /* Z_DATA_ERROR is ok, just means that stream isn't ended */ dropbear_exit("crypto error"); } - m_free(ses.keys->trans_zstream); + m_free(ses.keys->trans.zstream); } } -#endif +#endif /* DISABLE_ZLIB */ /* Executed upon receiving a kexinit message from the client to initiate @@ -682,28 +700,36 @@ static void read_kex_algos() { /* Handle the asymmetry */ if (IS_DROPBEAR_CLIENT) { - ses.newkeys->recv_algo_crypt = + ses.newkeys->recv.algo_crypt = (struct dropbear_cipher*)s2c_cipher_algo->data; - ses.newkeys->trans_algo_crypt = + ses.newkeys->trans.algo_crypt = (struct dropbear_cipher*)c2s_cipher_algo->data; - ses.newkeys->recv_algo_mac = + ses.newkeys->recv.crypt_mode = + (struct dropbear_cipher_mode*)s2c_cipher_algo->mode; + ses.newkeys->trans.crypt_mode = + (struct dropbear_cipher_mode*)c2s_cipher_algo->mode; + ses.newkeys->recv.algo_mac = (struct dropbear_hash*)s2c_hash_algo->data; - ses.newkeys->trans_algo_mac = + ses.newkeys->trans.algo_mac = (struct dropbear_hash*)c2s_hash_algo->data; - ses.newkeys->recv_algo_comp = s2c_comp_algo->val; - ses.newkeys->trans_algo_comp = c2s_comp_algo->val; + ses.newkeys->recv.algo_comp = s2c_comp_algo->val; + ses.newkeys->trans.algo_comp = c2s_comp_algo->val; } else { /* SERVER */ - ses.newkeys->recv_algo_crypt = + ses.newkeys->recv.algo_crypt = (struct dropbear_cipher*)c2s_cipher_algo->data; - ses.newkeys->trans_algo_crypt = + ses.newkeys->trans.algo_crypt = (struct dropbear_cipher*)s2c_cipher_algo->data; - ses.newkeys->recv_algo_mac = + ses.newkeys->recv.crypt_mode = + (struct dropbear_cipher_mode*)c2s_cipher_algo->mode; + ses.newkeys->trans.crypt_mode = + (struct dropbear_cipher_mode*)s2c_cipher_algo->mode; + ses.newkeys->recv.algo_mac = (struct dropbear_hash*)c2s_hash_algo->data; - ses.newkeys->trans_algo_mac = + ses.newkeys->trans.algo_mac = (struct dropbear_hash*)s2c_hash_algo->data; - ses.newkeys->recv_algo_comp = c2s_comp_algo->val; - ses.newkeys->trans_algo_comp = s2c_comp_algo->val; + ses.newkeys->recv.algo_comp = c2s_comp_algo->val; + ses.newkeys->trans.algo_comp = s2c_comp_algo->val; } /* reserved for future extensions */ diff --git a/common-session.c b/common-session.c index 3d759b5..1348da2 100644 --- a/common-session.c +++ b/common-session.c @@ -63,6 +63,7 @@ void common_session_init(int sock_in, int sock_out, char* remotehost) { ses.maxfd = MAX(sock_in, sock_out); ses.connect_time = 0; + ses.last_trx_packet_time = 0; ses.last_packet_time = 0; if (pipe(ses.signal_pipe) < 0) { @@ -70,6 +71,9 @@ void common_session_init(int sock_in, int sock_out, char* remotehost) { } setnonblocking(ses.signal_pipe[0]); setnonblocking(ses.signal_pipe[1]); + + ses.maxfd = MAX(ses.maxfd, ses.signal_pipe[0]); + ses.maxfd = MAX(ses.maxfd, ses.signal_pipe[1]); kexfirstinitialise(); /* initialise the kex state */ @@ -77,7 +81,6 @@ void common_session_init(int sock_in, int sock_out, char* remotehost) { ses.transseq = 0; ses.readbuf = NULL; - ses.decryptreadbuf = NULL; ses.payload = NULL; ses.recvseq = 0; @@ -94,20 +97,22 @@ void common_session_init(int sock_in, int sock_out, char* remotehost) { /* set all the algos to none */ ses.keys = (struct key_context*)m_malloc(sizeof(struct key_context)); ses.newkeys = NULL; - ses.keys->recv_algo_crypt = &dropbear_nocipher; - ses.keys->trans_algo_crypt = &dropbear_nocipher; + ses.keys->recv.algo_crypt = &dropbear_nocipher; + ses.keys->trans.algo_crypt = &dropbear_nocipher; + ses.keys->recv.crypt_mode = &dropbear_mode_none; + ses.keys->trans.crypt_mode = &dropbear_mode_none; - ses.keys->recv_algo_mac = &dropbear_nohash; - ses.keys->trans_algo_mac = &dropbear_nohash; + ses.keys->recv.algo_mac = &dropbear_nohash; + ses.keys->trans.algo_mac = &dropbear_nohash; ses.keys->algo_kex = -1; ses.keys->algo_hostkey = -1; - ses.keys->recv_algo_comp = DROPBEAR_COMP_NONE; - ses.keys->trans_algo_comp = DROPBEAR_COMP_NONE; + ses.keys->recv.algo_comp = DROPBEAR_COMP_NONE; + ses.keys->trans.algo_comp = DROPBEAR_COMP_NONE; #ifndef DISABLE_ZLIB - ses.keys->recv_zstream = NULL; - ses.keys->trans_zstream = NULL; + ses.keys->recv.zstream = NULL; + ses.keys->trans.zstream = NULL; #endif /* key exchange buffers */ @@ -256,7 +261,7 @@ void session_identification() { ses.remoteclosed(); } - /* If they send more than 50 lines, something is wrong */ + /* If they send more than 50 lines, something is wrong */ for (i = 0; i < 50; i++) { len = ident_readln(ses.sock_in, linebuf, sizeof(linebuf)); @@ -281,11 +286,11 @@ void session_identification() { memcpy(ses.remoteident, linebuf, len); } - /* Shall assume that 2.x will be backwards compatible. */ - if (strncmp(ses.remoteident, "SSH-2.", 6) != 0 - && strncmp(ses.remoteident, "SSH-1.99-", 9) != 0) { - dropbear_exit("Incompatible remote version '%s'", ses.remoteident); - } + /* Shall assume that 2.x will be backwards compatible. */ + if (strncmp(ses.remoteident, "SSH-2.", 6) != 0 + && strncmp(ses.remoteident, "SSH-1.99-", 9) != 0) { + dropbear_exit("Incompatible remote version '%s'", ses.remoteident); + } TRACE(("remoteident: %s", ses.remoteident)) @@ -397,9 +402,14 @@ static void checktimeouts() { } if (opts.keepalive_secs > 0 - && now - ses.last_packet_time >= opts.keepalive_secs) { + && now - ses.last_trx_packet_time >= opts.keepalive_secs) { send_msg_ignore(); } + + if (opts.idle_timeout_secs > 0 && ses.last_packet_time > 0 + && now - ses.last_packet_time >= opts.idle_timeout_secs) { + dropbear_close("Idle timeout"); + } } static long select_timeout() { @@ -412,6 +422,8 @@ static long select_timeout() { ret = MIN(AUTH_TIMEOUT, ret); if (opts.keepalive_secs > 0) ret = MIN(opts.keepalive_secs, ret); + if (opts.idle_timeout_secs > 0) + ret = MIN(opts.idle_timeout_secs, ret); return ret; } diff --git a/configure.in b/configure.in index 52a75e0..c7149e8 100644 --- a/configure.in +++ b/configure.in @@ -82,7 +82,8 @@ AC_CHECK_DECL(__UCLIBC__, ],,,) # Checks for libraries. -AC_CHECK_LIB(crypt, crypt, LIBS="$LIBS -lcrypt") +AC_CHECK_LIB(crypt, crypt, CRYPTLIB="-lcrypt") +AC_SUBST(CRYPTLIB) # Check if zlib is needed AC_ARG_WITH(zlib, @@ -10,6 +10,13 @@ dbclient \- lightweight SSH2 client .I l\fR:\fIh\fR:\fIr\fR] [\-l .IR user ] .I host +.RI [ command ] + +.B dbclient +[ +.I args ] +.I [user1]@host1[/port1],[user2]@host2[/port2],... + .SH DESCRIPTION .B dbclient is a SSH 2 client designed to be small enough to be used in small memory @@ -86,22 +93,52 @@ 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. +.TP +.B \-I \fIidle_timeout +Disconnect the session if no traffic is transmitted or received for \fIidle_timeout\fR seconds. +.TP +.B \-J \fIproxy_command +Use the standard input/output of the program \fIproxy_command\fR rather than using +a normal TCP connection. A hostname should be still be provided, as this is used for +comparing saved hostkeys. +.TP +.B \-B \fIendhost:endport +"Netcat-alike" mode, where Dropbear will connect to the given host, then create a +forwarded connection to \fIendhost\fR. This will then be presented as dbclient's +standard input/output. + +Dropbear will also allow multiple "hops" to be specified, separated by commas. In +this case a connection will be made to the first host, then a TCP forwarded +connection will be made through that to the second host, and so on. Hosts other than +the final destination will not see anything other than the encrypted SSH stream. +A port for a host can be specified with a slash (eg matt@martello/44 ). +This syntax can also be used with scp or rsync (specifying dbclient as the +ssh/rsh command). A file can be "bounced" through multiple SSH hops, eg + +scp -S dbclient matt@martello,root@wrt,canyons:/tmp/dump . + +Note that hostnames are resolved by the prior hop (so "canyons" would be resolved by the host "wrt") +in the example above, the same way as other -L TCP forwarded hosts are. Host keys are +checked locally based on the given hostname. + .SH ENVIRONMENT .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. +.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 +Mihnea Stoenescu wrote initial Dropbear client support +.br Gerrit Pape (pape@smarden.org) wrote this manual page. .SH SEE ALSO dropbear(8), dropbearkey(8) diff --git a/debian/changelog b/debian/changelog index f51769b..83639c9 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +dropbear (0.52-0.1) unstable; urgency=low + + * New upstream release. + + -- Matt Johnston <matt@ucc.asn.au> Wed, 12 Nov 2008 22:54:00 +0900 + dropbear (0.51-0.1) unstable; urgency=low * New upstream release. diff --git a/debian/dropbear.postinst b/debian/dropbear.postinst index 7c95cfa..2491e45 100644 --- a/debian/dropbear.postinst +++ b/debian/dropbear.postinst @@ -71,7 +71,7 @@ if test -x /etc/init.d/dropbear; then fi if test -n "$2" && dpkg --compare-versions "$2" lt '0.50-4' && -update-service --check dropbear; then +update-service --check dropbear 2>/dev/null; then update-service --remove /etc/dropbear 2>/dev/null || : sleep 6 rm -rf /var/run/dropbear /var/run/dropbear.log @@ -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 @@ -24,7 +24,10 @@ before user login (default: none). dsskeyfile. Use the contents of the file .I dsskey -for the dss host key (default: /etc/dropbear/dropbear_dss_host_key). +for the DSS host key (default: /etc/dropbear/dropbear_dss_host_key). +Note that +some SSH implementations +use the term "DSA" rather than "DSS", they mean the same thing. This file is generated with .BR dropbearkey (8). .TP @@ -94,6 +97,63 @@ 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. +.TP +.B \-I \fIidle_timeout +Disconnect the session if no traffic is transmitted or received for \fIidle_timeout\fR seconds. +.SH FILES + +.TP +Authorized Keys + +~/.ssh/authorized_keys can be set up to allow remote login with a RSA or DSS +key. Each line is of the form +.TP +[restrictions] ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIgAsp... [comment] + +and can be extracted from a Dropbear private host key with "dropbearkey -y". This is the same format as used by OpenSSH, though the restrictions are a subset (keys with unknown restrictions are ignored). +Restrictions are comma separated, with double quotes around spaces in arguments. +Available restrictions are: + +.TP +.B no-port-forwarding +Don't allow port forwarding for this connection + +.TP +.B no-agent-forwarding +Don't allow agent forwarding for this connection + +.TP +.B no-X11-forwarding +Don't allow X11 forwarding for this connection + +.TP +.B no-pty +Disable PTY allocation. Note that a user can still obtain most of the +same functionality with other means even if no-pty is set. + +.TP +.B command="\fIforced_command\fR" +Disregard the command provided by the user and always run \fIforced_command\fR. + +The authorized_keys file and its containing ~/.ssh directory must only be +writable by the user, otherwise Dropbear will not allow a login using public +key authentication. + +.TP +Host Key Files + +Host key files are read at startup from a standard location, by default +/etc/dropbear/dropbear_dss_host_key and /etc/dropbear/dropbear_rsa_host_key +or specified on the commandline with -d or -r. These are of the form generated +by dropbearkey. + +.TP +Message Of The Day + +By default the file /etc/motd will be printed for any login shell (unless +disabled at compile-time). This can also be disabled per-user +by creating a file ~/.hushlogin . + .SH AUTHOR Matt Johnston (matt@ucc.asn.au). .br diff --git a/dropbearkey.8 b/dropbearkey.8 index a093d85..b5745dd 100644 --- a/dropbearkey.8 +++ b/dropbearkey.8 @@ -11,13 +11,16 @@ dropbearkey \- create private keys for the use with dropbear(8) .IR bits ] .SH DESCRIPTION .B dropbearkey -generates a type -.I rsa +generates a +.I RSA or -.I dss -SSH private key, and saves it to a file for the use with the +.I DSS +format SSH private key, and saves it to a file for the use with the .BR dropbear (8) SSH 2 server. +Note that +some SSH implementations +use the term "DSA" rather than "DSS", they mean the same thing. .SH OPTIONS .TP .B \-t \fItype @@ -37,6 +37,11 @@ void gen_kexdh_vals(mp_int *dh_pub, mp_int *dh_priv); void kexdh_comb_key(mp_int *dh_pub_us, mp_int *dh_priv, mp_int *dh_pub_them, sign_key *hostkey); +#ifndef DISABLE_ZLIB +int is_compress_trans(); +int is_compress_recv(); +#endif + void recv_msg_kexdh_init(); /* server */ void send_msg_kexdh_init(); /* client */ diff --git a/libtomcrypt/src/headers/tomcrypt_custom.h b/libtomcrypt/src/headers/tomcrypt_custom.h index 440168b..928f54e 100644 --- a/libtomcrypt/src/headers/tomcrypt_custom.h +++ b/libtomcrypt/src/headers/tomcrypt_custom.h @@ -90,15 +90,15 @@ /* #define LTC_NO_BSWAP */ -#ifdef DROPBEAR_BLOWFISH_CBC +#ifdef DROPBEAR_BLOWFISH #define BLOWFISH #endif -#ifdef DROPBEAR_AES_CBC +#ifdef DROPBEAR_AES #define RIJNDAEL #endif -#ifdef DROPBEAR_TWOFISH_CBC +#ifdef DROPBEAR_TWOFISH #define TWOFISH /* enabling just TWOFISH_SMALL will make the binary ~1kB smaller, turning on @@ -108,12 +108,16 @@ /*#define TWOFISH_TABLES*/ #endif -#ifdef DROPBEAR_3DES_CBC +#ifdef DROPBEAR_3DES #define DES #endif #define LTC_CBC_MODE +#ifdef DROPBEAR_ENABLE_CTR_MODE +#define LTC_CTR_MODE +#endif + #if defined(DROPBEAR_DSS) && defined(DSS_PROTOK) #define SHA512 #endif @@ -1334,7 +1334,7 @@ lastlog_openseek(struct logininfo *li, int *fd, int filemode) return 0; } - *fd = open(lastlog_file, filemode); + *fd = open(lastlog_file, filemode, 0600); if ( *fd < 0) { dropbear_log(LOG_INFO, "lastlog_openseek: Couldn't open %s: %s", lastlog_file, strerror(errno)); @@ -46,9 +46,10 @@ /*#define NO_FAST_EXPTMOD*/ /* Set this if you want to use the DROPBEAR_SMALL_CODE option. This can save -several kB in binary size, however will make the symmetrical ciphers (AES, DES -etc) slower (perhaps by 50%). Recommended for most small systems. */ -#define DROPBEAR_SMALL_CODE +several kB in binary size however will make the symmetrical ciphers and hashes +slower, perhaps by 50%. Recommended for small systems that aren't doing +much traffic. */ +/*#define DROPBEAR_SMALL_CODE*/ /* Enable X11 Forwarding - server only */ #define ENABLE_X11FWD @@ -60,10 +61,6 @@ 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 @@ -71,23 +68,36 @@ 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 +/* Note: Both ENABLE_CLI_PROXYCMD and ENABLE_CLI_NETCAT must be set to + * allow multihop dbclient connections */ + +/* Allow using -J <proxycommand> to run the connection through a + pipe to a program, rather the normal TCP connection */ +#define ENABLE_CLI_PROXYCMD + +/* Enable "Netcat mode" option. This will forward standard input/output + * to a remote TCP-forwarded connection */ +#define ENABLE_CLI_NETCAT /* Encryption - at least one required. - * RFC Draft requires 3DES and recommends AES128 for interoperability. + * Protocol RFC requires 3DES and recommends AES128 for interoperability. * Including multiple keysize variants the same cipher * (eg AES256 as well as AES128) will result in a minimal size increase.*/ -#define DROPBEAR_AES128_CBC -#define DROPBEAR_3DES_CBC -#define DROPBEAR_AES256_CBC -#define DROPBEAR_BLOWFISH_CBC -#define DROPBEAR_TWOFISH256_CBC -#define DROPBEAR_TWOFISH128_CBC +#define DROPBEAR_AES128 +#define DROPBEAR_3DES +#define DROPBEAR_AES256 +#define DROPBEAR_BLOWFISH +#define DROPBEAR_TWOFISH256 +#define DROPBEAR_TWOFISH128 + +/* Enable "Counter Mode" for ciphers. This is more secure than normal + * CBC mode against certain attacks. This adds around 1kB to binary + * size and is recommended for most cases */ +#define DROPBEAR_ENABLE_CTR_MODE /* Message Integrity - at least one required. - * RFC Draft requires sha1 and recommends sha1-96. + * Protocol RFC requires sha1 and recommends sha1-96. * sha1-96 may be of use for slow links, as it has a smaller overhead. * * Note: there's no point disabling sha1 to save space, since it's used @@ -143,7 +153,7 @@ etc) slower (perhaps by 50%). Recommended for most small systems. */ #define ENABLE_SVR_PASSWORD_AUTH /* PAM requires ./configure --enable-pam */ -/* #define ENABLE_SVR_PAM_AUTH */ +/*#define ENABLE_SVR_PAM_AUTH*/ #define ENABLE_SVR_PUBKEY_AUTH /* Wether to ake public key options in authorized_keys file into account */ @@ -250,6 +260,13 @@ etc) slower (perhaps by 50%). Recommended for most small systems. */ be overridden at runtime with -K. 0 disables keepalives */ #define DEFAULT_KEEPALIVE 0 +/* Ensure that data is received within IDLE_TIMEOUT seconds. This can +be overridden at runtime with -I. 0 disables idle timeouts */ +#define DEFAULT_IDLE_TIMEOUT 0 + +/* The default path. This will often get replaced by the shell */ +#define DEFAULT_PATH "/usr/bin:/bin" + /* Some other defines (that mostly should be left alone) are defined * in sysoptions.h */ #include "sysoptions.h" @@ -35,9 +35,11 @@ #include "auth.h" #include "channel.h" -static void read_packet_init(); -static void writemac(buffer * outputbuffer, buffer * clearwritebuf); -static int checkmac(buffer* hashbuf, buffer* readbuf); +static int read_packet_init(); +static void make_mac(unsigned int seqno, const struct key_context_directional * key_state, + buffer * clear_buf, unsigned int clear_len, + unsigned char *output_mac); +static int checkmac(); #define ZLIB_COMPRESS_INCR 20 /* this is 12 bytes + 0.1% of 8000 bytes */ #define ZLIB_DECOMPRESS_INCR 100 @@ -72,6 +74,7 @@ void write_packet() { } } + ses.last_trx_packet_time = time(NULL); ses.last_packet_time = time(NULL); if (written == 0) { @@ -101,18 +104,18 @@ void read_packet() { unsigned char blocksize; TRACE(("enter read_packet")) - blocksize = ses.keys->recv_algo_crypt->blocksize; + blocksize = ses.keys->recv.algo_crypt->blocksize; if (ses.readbuf == NULL || ses.readbuf->len < blocksize) { + int ret; /* In the first blocksize of a packet */ /* Read the first blocksize of the packet, so we can decrypt it and * find the length of the whole packet */ - read_packet_init(); + ret = read_packet_init(); - /* If we don't have the length of decryptreadbuf, we didn't read - * a whole blocksize and should exit */ - if (ses.decryptreadbuf->len == 0) { + if (ret == DROPBEAR_FAILURE) { + /* didn't read enough to determine the length */ TRACE(("leave read_packet: packetinit done")) return; } @@ -120,7 +123,6 @@ void read_packet() { /* Attempt to read the remainder of the packet, note that there * mightn't be any available (EAGAIN) */ - dropbear_assert(ses.readbuf != NULL); maxlen = ses.readbuf->len - ses.readbuf->pos; len = read(ses.sock_in, buf_getptr(ses.readbuf, maxlen), maxlen); @@ -150,7 +152,9 @@ void read_packet() { /* Function used to read the initial portion of a packet, and determine the * length. Only called during the first BLOCKSIZE of a packet. */ -static void read_packet_init() { +/* Returns DROPBEAR_SUCCESS if the length is determined, + * DROPBEAR_FAILURE otherwise */ +static int read_packet_init() { unsigned int maxlen; int len; @@ -158,14 +162,12 @@ static void read_packet_init() { unsigned char macsize; - blocksize = ses.keys->recv_algo_crypt->blocksize; - macsize = ses.keys->recv_algo_mac->hashsize; + blocksize = ses.keys->recv.algo_crypt->blocksize; + macsize = ses.keys->recv.algo_mac->hashsize; if (ses.readbuf == NULL) { /* start of a new packet */ ses.readbuf = buf_new(INIT_READBUF); - dropbear_assert(ses.decryptreadbuf == NULL); - ses.decryptreadbuf = buf_new(blocksize); } maxlen = blocksize - ses.readbuf->pos; @@ -179,7 +181,7 @@ static void read_packet_init() { if (len < 0) { if (errno == EINTR) { TRACE(("leave read_packet_init: EINTR")) - return; + return DROPBEAR_FAILURE; } dropbear_exit("error reading: %s", strerror(errno)); } @@ -188,30 +190,22 @@ static void read_packet_init() { if ((unsigned int)len != maxlen) { /* don't have enough bytes to determine length, get next time */ - return; + return DROPBEAR_FAILURE; } /* now we have the first block, need to get packet length, so we decrypt * the first block (only need first 4 bytes) */ buf_setpos(ses.readbuf, 0); - if (ses.keys->recv_algo_crypt->cipherdesc == NULL) { - /* copy it */ - memcpy(buf_getwriteptr(ses.decryptreadbuf, blocksize), - buf_getptr(ses.readbuf, blocksize), - blocksize); - } else { - /* decrypt it */ - if (cbc_decrypt(buf_getptr(ses.readbuf, blocksize), - buf_getwriteptr(ses.decryptreadbuf,blocksize), - blocksize, - &ses.keys->recv_symmetric_struct) != CRYPT_OK) { - dropbear_exit("error decrypting"); - } + if (ses.keys->recv.crypt_mode->decrypt(buf_getptr(ses.readbuf, blocksize), + buf_getwriteptr(ses.readbuf, blocksize), + blocksize, + &ses.keys->recv.cipher_state) != CRYPT_OK) { + dropbear_exit("error decrypting"); } - buf_setlen(ses.decryptreadbuf, blocksize); - len = buf_getint(ses.decryptreadbuf) + 4 + macsize; + len = buf_getint(ses.readbuf) + 4 + macsize; + + TRACE(("packet size is %d, block %d mac %d", len, blocksize, macsize)) - buf_setpos(ses.readbuf, blocksize); /* check packet length */ if ((len > RECV_MAX_PACKET_LEN) || @@ -220,9 +214,12 @@ static void read_packet_init() { dropbear_exit("bad packet size %d", len); } - buf_resize(ses.readbuf, len); + if (len > ses.readbuf->size) { + buf_resize(ses.readbuf, len); + } buf_setlen(ses.readbuf, len); - + buf_setpos(ses.readbuf, blocksize); + return DROPBEAR_SUCCESS; } /* handle the received packet */ @@ -234,77 +231,58 @@ void decrypt_packet() { unsigned int len; TRACE(("enter decrypt_packet")) - blocksize = ses.keys->recv_algo_crypt->blocksize; - macsize = ses.keys->recv_algo_mac->hashsize; + blocksize = ses.keys->recv.algo_crypt->blocksize; + macsize = ses.keys->recv.algo_mac->hashsize; ses.kexstate.datarecv += ses.readbuf->len; /* we've already decrypted the first blocksize in read_packet_init */ buf_setpos(ses.readbuf, blocksize); - buf_resize(ses.decryptreadbuf, ses.readbuf->len - macsize); - buf_setlen(ses.decryptreadbuf, ses.decryptreadbuf->size); - buf_setpos(ses.decryptreadbuf, blocksize); - - /* decrypt if encryption is set, memcpy otherwise */ - if (ses.keys->recv_algo_crypt->cipherdesc == NULL) { - /* copy it */ - len = ses.readbuf->len - macsize - blocksize; - memcpy(buf_getwriteptr(ses.decryptreadbuf, len), - buf_getptr(ses.readbuf, len), len); - } else { - /* decrypt */ - while (ses.readbuf->pos < ses.readbuf->len - macsize) { - if (cbc_decrypt(buf_getptr(ses.readbuf, blocksize), - buf_getwriteptr(ses.decryptreadbuf, blocksize), - blocksize, - &ses.keys->recv_symmetric_struct) != CRYPT_OK) { - dropbear_exit("error decrypting"); - } - buf_incrpos(ses.readbuf, blocksize); - buf_incrwritepos(ses.decryptreadbuf, blocksize); - } + /* decrypt it in-place */ + len = ses.readbuf->len - macsize - ses.readbuf->pos; + if (ses.keys->recv.crypt_mode->decrypt( + buf_getptr(ses.readbuf, len), + buf_getwriteptr(ses.readbuf, len), + len, + &ses.keys->recv.cipher_state) != CRYPT_OK) { + dropbear_exit("error decrypting"); } + buf_incrpos(ses.readbuf, len); /* check the hmac */ - buf_setpos(ses.readbuf, ses.readbuf->len - macsize); - if (checkmac(ses.readbuf, ses.decryptreadbuf) != DROPBEAR_SUCCESS) { + if (checkmac() != DROPBEAR_SUCCESS) { dropbear_exit("Integrity error"); } - /* readbuf no longer required */ - buf_free(ses.readbuf); - ses.readbuf = NULL; - /* get padding length */ - buf_setpos(ses.decryptreadbuf, PACKET_PADDING_OFF); - padlen = buf_getbyte(ses.decryptreadbuf); + buf_setpos(ses.readbuf, PACKET_PADDING_OFF); + padlen = buf_getbyte(ses.readbuf); /* payload length */ /* - 4 - 1 is for LEN and PADLEN values */ - len = ses.decryptreadbuf->len - padlen - 4 - 1; + len = ses.readbuf->len - padlen - 4 - 1; if ((len > RECV_MAX_PAYLOAD_LEN) || (len < 1)) { dropbear_exit("bad packet size"); } - buf_setpos(ses.decryptreadbuf, PACKET_PAYLOAD_OFF); + buf_setpos(ses.readbuf, PACKET_PAYLOAD_OFF); #ifndef DISABLE_ZLIB - if (ses.keys->recv_algo_comp == DROPBEAR_COMP_ZLIB) { + if (is_compress_recv()) { /* decompress */ - ses.payload = buf_decompress(ses.decryptreadbuf, len); - + ses.payload = buf_decompress(ses.readbuf, len); } else #endif { /* copy payload */ ses.payload = buf_new(len); - memcpy(ses.payload->data, buf_getptr(ses.decryptreadbuf, len), len); + memcpy(ses.payload->data, buf_getptr(ses.readbuf, len), len); buf_incrlen(ses.payload, len); } - buf_free(ses.decryptreadbuf); - ses.decryptreadbuf = NULL; + buf_free(ses.readbuf); + ses.readbuf = NULL; buf_setpos(ses.payload, 0); ses.recvseq++; @@ -312,49 +290,22 @@ void decrypt_packet() { TRACE(("leave decrypt_packet")) } -/* Checks the mac in hashbuf, for the data in readbuf. +/* Checks the mac at the end of a decrypted readbuf. * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ -static int checkmac(buffer* macbuf, buffer* sourcebuf) { - - unsigned int macsize; - hmac_state hmac; - unsigned char tempbuf[MAX_MAC_LEN]; - unsigned long bufsize; - unsigned int len; - - macsize = ses.keys->recv_algo_mac->hashsize; - if (macsize == 0) { - return DROPBEAR_SUCCESS; - } +static int checkmac() { - /* calculate the mac */ - if (hmac_init(&hmac, - find_hash(ses.keys->recv_algo_mac->hashdesc->name), - ses.keys->recvmackey, - ses.keys->recv_algo_mac->keysize) - != CRYPT_OK) { - dropbear_exit("HMAC error"); - } + unsigned char mac_bytes[MAX_MAC_LEN]; + unsigned int mac_size, contents_len; - /* sequence number */ - STORE32H(ses.recvseq, tempbuf); - if (hmac_process(&hmac, tempbuf, 4) != CRYPT_OK) { - dropbear_exit("HMAC error"); - } + mac_size = ses.keys->trans.algo_mac->hashsize; + contents_len = ses.readbuf->len - mac_size; - buf_setpos(sourcebuf, 0); - len = sourcebuf->len; - if (hmac_process(&hmac, buf_getptr(sourcebuf, len), len) != CRYPT_OK) { - dropbear_exit("HMAC error"); - } - - bufsize = sizeof(tempbuf); - if (hmac_done(&hmac, tempbuf, &bufsize) != CRYPT_OK) { - dropbear_exit("HMAC error"); - } + buf_setpos(ses.readbuf, 0); + make_mac(ses.recvseq, &ses.keys->recv, ses.readbuf, contents_len, mac_bytes); /* compare the hash */ - if (memcmp(tempbuf, buf_getptr(macbuf, macsize), macsize) != 0) { + buf_setpos(ses.readbuf, contents_len); + if (memcmp(mac_bytes, buf_getptr(ses.readbuf, mac_size), mac_size) != 0) { return DROPBEAR_FAILURE; } else { return DROPBEAR_SUCCESS; @@ -369,7 +320,7 @@ static buffer* buf_decompress(buffer* buf, unsigned int len) { buffer * ret; z_streamp zstream; - zstream = ses.keys->recv_zstream; + zstream = ses.keys->recv.zstream; ret = buf_new(len); zstream->avail_in = len; @@ -465,10 +416,12 @@ void maybe_flush_reply_queue() { void encrypt_packet() { unsigned char padlen; - unsigned char blocksize, macsize; - buffer * writebuf; /* the packet which will go on the wire */ - buffer * clearwritebuf; /* unencrypted, possibly compressed */ + unsigned char blocksize, mac_size; + buffer * writebuf; /* the packet which will go on the wire. This is + encrypted in-place. */ unsigned char type; + unsigned int len, encrypt_buf_size; + unsigned char mac_bytes[MAX_MAC_LEN]; type = ses.writepayload->data[0]; TRACE(("enter encrypt_packet()")) @@ -482,33 +435,36 @@ void encrypt_packet() { return; } - blocksize = ses.keys->trans_algo_crypt->blocksize; - macsize = ses.keys->trans_algo_mac->hashsize; + blocksize = ses.keys->trans.algo_crypt->blocksize; + mac_size = ses.keys->trans.algo_mac->hashsize; /* Encrypted packet len is payload+5, then worst case is if we are 3 away * from a blocksize multiple. In which case we need to pad to the * multiple, then add another blocksize (or MIN_PACKET_LEN) */ - clearwritebuf = buf_new((ses.writepayload->len+4+1) + MIN_PACKET_LEN + 3 + encrypt_buf_size = (ses.writepayload->len+4+1) + MIN_PACKET_LEN + 3; + /* add space for the MAC at the end */ + encrypt_buf_size += mac_size; + #ifndef DISABLE_ZLIB - + ZLIB_COMPRESS_INCR /* bit of a kludge, but we can't know len*/ + encrypt_buf_size += ZLIB_COMPRESS_INCR; /* bit of a kludge, but we can't know len*/ #endif - ); - buf_setlen(clearwritebuf, PACKET_PAYLOAD_OFF); - buf_setpos(clearwritebuf, PACKET_PAYLOAD_OFF); + writebuf = buf_new(encrypt_buf_size); + buf_setlen(writebuf, PACKET_PAYLOAD_OFF); + buf_setpos(writebuf, PACKET_PAYLOAD_OFF); buf_setpos(ses.writepayload, 0); #ifndef DISABLE_ZLIB /* compression */ - if (ses.keys->trans_algo_comp == DROPBEAR_COMP_ZLIB) { - buf_compress(clearwritebuf, ses.writepayload, ses.writepayload->len); + if (is_compress_trans()) { + buf_compress(writebuf, ses.writepayload, ses.writepayload->len); } else #endif { - memcpy(buf_getwriteptr(clearwritebuf, ses.writepayload->len), + memcpy(buf_getwriteptr(writebuf, ses.writepayload->len), buf_getptr(ses.writepayload, ses.writepayload->len), ses.writepayload->len); - buf_incrwritepos(clearwritebuf, ses.writepayload->len); + buf_incrwritepos(writebuf, ses.writepayload->len); } /* finished with payload */ @@ -517,60 +473,45 @@ void encrypt_packet() { /* length of padding - packet length must be a multiple of blocksize, * with a minimum of 4 bytes of padding */ - padlen = blocksize - (clearwritebuf->len) % blocksize; + padlen = blocksize - (writebuf->len) % blocksize; if (padlen < 4) { padlen += blocksize; } /* check for min packet length */ - if (clearwritebuf->len + padlen < MIN_PACKET_LEN) { + if (writebuf->len + padlen < MIN_PACKET_LEN) { padlen += blocksize; } - buf_setpos(clearwritebuf, 0); + buf_setpos(writebuf, 0); /* packet length excluding the packetlength uint32 */ - buf_putint(clearwritebuf, clearwritebuf->len + padlen - 4); + buf_putint(writebuf, writebuf->len + padlen - 4); /* padding len */ - buf_putbyte(clearwritebuf, padlen); + buf_putbyte(writebuf, padlen); /* actual padding */ - buf_setpos(clearwritebuf, clearwritebuf->len); - buf_incrlen(clearwritebuf, padlen); - genrandom(buf_getptr(clearwritebuf, padlen), padlen); - - /* do the actual encryption */ - buf_setpos(clearwritebuf, 0); - /* create a new writebuffer, this is freed when it has been put on the - * wire by writepacket() */ - writebuf = buf_new(clearwritebuf->len + macsize); - - if (ses.keys->trans_algo_crypt->cipherdesc == NULL) { - /* copy it */ - memcpy(buf_getwriteptr(writebuf, clearwritebuf->len), - buf_getptr(clearwritebuf, clearwritebuf->len), - clearwritebuf->len); - buf_incrwritepos(writebuf, clearwritebuf->len); - } else { - /* encrypt it */ - while (clearwritebuf->pos < clearwritebuf->len) { - if (cbc_encrypt(buf_getptr(clearwritebuf, blocksize), - buf_getwriteptr(writebuf, blocksize), - blocksize, - &ses.keys->trans_symmetric_struct) != CRYPT_OK) { - dropbear_exit("error encrypting"); - } - buf_incrpos(clearwritebuf, blocksize); - buf_incrwritepos(writebuf, blocksize); - } - } + buf_setpos(writebuf, writebuf->len); + buf_incrlen(writebuf, padlen); + genrandom(buf_getptr(writebuf, padlen), padlen); - /* now add a hmac and we're done */ - writemac(writebuf, clearwritebuf); + make_mac(ses.transseq, &ses.keys->trans, writebuf, writebuf->len, mac_bytes); - /* clearwritebuf is finished with */ - buf_free(clearwritebuf); - clearwritebuf = NULL; + /* do the actual encryption, in-place */ + buf_setpos(writebuf, 0); + /* encrypt it in-place*/ + len = writebuf->len; + if (ses.keys->trans.crypt_mode->encrypt( + buf_getptr(writebuf, len), + buf_getwriteptr(writebuf, len), + len, + &ses.keys->trans.cipher_state) != CRYPT_OK) { + dropbear_exit("error encrypting"); + } + buf_incrpos(writebuf, len); - /* enqueue the packet for sending */ + /* stick the MAC on it */ + buf_putbytes(writebuf, mac_bytes, mac_size); + + /* enqueue the packet for sending. It will get freed after transmission. */ buf_setpos(writebuf, 0); enqueue(&ses.writequeue, (void*)writebuf); @@ -583,47 +524,43 @@ void encrypt_packet() { /* Create the packet mac, and append H(seqno|clearbuf) to the output */ -static void writemac(buffer * outputbuffer, buffer * clearwritebuf) { - - unsigned int macsize; +/* output_mac must have ses.keys->trans.algo_mac->hashsize bytes. */ +static void make_mac(unsigned int seqno, const struct key_context_directional * key_state, + buffer * clear_buf, unsigned int clear_len, + unsigned char *output_mac) { unsigned char seqbuf[4]; - unsigned char tempbuf[MAX_MAC_LEN]; unsigned long bufsize; hmac_state hmac; TRACE(("enter writemac")) - macsize = ses.keys->trans_algo_mac->hashsize; - if (macsize > 0) { + if (key_state->algo_mac->hashsize > 0) { /* calculate the mac */ if (hmac_init(&hmac, - find_hash(ses.keys->trans_algo_mac->hashdesc->name), - ses.keys->transmackey, - ses.keys->trans_algo_mac->keysize) != CRYPT_OK) { + key_state->hash_index, + key_state->mackey, + key_state->algo_mac->keysize) != CRYPT_OK) { dropbear_exit("HMAC error"); } /* sequence number */ - STORE32H(ses.transseq, seqbuf); + STORE32H(seqno, seqbuf); if (hmac_process(&hmac, seqbuf, 4) != CRYPT_OK) { dropbear_exit("HMAC error"); } /* the actual contents */ - buf_setpos(clearwritebuf, 0); + buf_setpos(clear_buf, 0); if (hmac_process(&hmac, - buf_getptr(clearwritebuf, - clearwritebuf->len), - clearwritebuf->len) != CRYPT_OK) { + buf_getptr(clear_buf, clear_len), + clear_len) != CRYPT_OK) { dropbear_exit("HMAC error"); } - bufsize = sizeof(tempbuf); - if (hmac_done(&hmac, tempbuf, &bufsize) - != CRYPT_OK) { + bufsize = MAX_MAC_LEN; + if (hmac_done(&hmac, output_mac, &bufsize) != CRYPT_OK) { dropbear_exit("HMAC error"); } - buf_putbytes(outputbuffer, tempbuf, macsize); } TRACE(("leave writemac")) } @@ -640,29 +577,29 @@ static void buf_compress(buffer * dest, buffer * src, unsigned int len) { while (1) { - ses.keys->trans_zstream->avail_in = endpos - src->pos; - ses.keys->trans_zstream->next_in = - buf_getptr(src, ses.keys->trans_zstream->avail_in); + ses.keys->trans.zstream->avail_in = endpos - src->pos; + ses.keys->trans.zstream->next_in = + buf_getptr(src, ses.keys->trans.zstream->avail_in); - ses.keys->trans_zstream->avail_out = dest->size - dest->pos; - ses.keys->trans_zstream->next_out = - buf_getwriteptr(dest, ses.keys->trans_zstream->avail_out); + ses.keys->trans.zstream->avail_out = dest->size - dest->pos; + ses.keys->trans.zstream->next_out = + buf_getwriteptr(dest, ses.keys->trans.zstream->avail_out); - result = deflate(ses.keys->trans_zstream, Z_SYNC_FLUSH); + result = deflate(ses.keys->trans.zstream, Z_SYNC_FLUSH); - buf_setpos(src, endpos - ses.keys->trans_zstream->avail_in); - buf_setlen(dest, dest->size - ses.keys->trans_zstream->avail_out); + buf_setpos(src, endpos - ses.keys->trans.zstream->avail_in); + buf_setlen(dest, dest->size - ses.keys->trans.zstream->avail_out); buf_setpos(dest, dest->len); if (result != Z_OK) { dropbear_exit("zlib error"); } - if (ses.keys->trans_zstream->avail_in == 0) { + if (ses.keys->trans.zstream->avail_in == 0) { break; } - dropbear_assert(ses.keys->trans_zstream->avail_out == 0); + dropbear_assert(ses.keys->trans.zstream->avail_out == 0); /* the buffer has been filled, we must extend. This only happens in * unusual circumstances where the data grows in size after deflate(), @@ -44,6 +44,6 @@ typedef struct PacketType { #define PACKET_PADDING_OFF 4 #define PACKET_PAYLOAD_OFF 5 -#define INIT_READBUF 200 +#define INIT_READBUF 128 #endif /* _PACKET_H_ */ diff --git a/process-packet.c b/process-packet.c index d96c1cb..fe4a543 100644 --- a/process-packet.c +++ b/process-packet.c @@ -70,6 +70,7 @@ void process_packet() { dropbear_close("Disconnect received"); } + ses.last_packet_time = time(NULL); /* This applies for KEX, where the spec says the next packet MUST be * NEWKEYS */ @@ -38,6 +38,7 @@ typedef struct runopts { #endif unsigned int recv_window; unsigned int keepalive_secs; + unsigned int idle_timeout_secs; } runopts; @@ -130,13 +130,22 @@ do_local_cmd(arglist *a) fprintf(stderr, " %s", a->list[i]); fprintf(stderr, "\n"); } - if ((pid = fork()) == -1) +#ifdef __uClinux__ + pid = vfork(); +#else + pid = fork(); +#endif /* __uClinux__ */ + if (pid == -1) fatal("do_local_cmd: fork: %s", strerror(errno)); if (pid == 0) { execvp(a->list[0], a->list); perror(a->list[0]); +#ifdef __uClinux__ + _exit(1); +#else exit(1); +#endif /* __uClinux__ */ } do_cmd_pid = pid; @@ -225,7 +234,11 @@ do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout, int argc) execvp(ssh_program, args.list); perror(ssh_program); +#ifndef __uClinux__ exit(1); +#else + _exit(1); +#endif /* __uClinux__ */ } else if (do_cmd_pid == -1) { fatal("fork: %s", strerror(errno)); } @@ -60,28 +60,36 @@ void cli_session(int sock_in, int sock_out, char *remotehost); void cli_session_cleanup(); void cleantext(unsigned char* dirtytext); +/* crypto parameters that are stored individually for transmit and receive */ +struct key_context_directional { + const struct dropbear_cipher *algo_crypt; /* NULL for none */ + const struct dropbear_cipher_mode *crypt_mode; + const struct dropbear_hash *algo_mac; /* NULL for none */ + int hash_index; /* lookup for libtomcrypt */ + char algo_comp; /* compression */ +#ifndef DISABLE_ZLIB + z_streamp zstream; +#endif + /* actual keys */ + union { + symmetric_CBC cbc; +#ifdef DROPBEAR_ENABLE_CTR_MODE + symmetric_CTR ctr; +#endif + } cipher_state; + unsigned char mackey[MAX_MAC_KEY]; +}; + struct key_context { - const struct dropbear_cipher *recv_algo_crypt; /* NULL for none */ - const struct dropbear_cipher *trans_algo_crypt; /* NULL for none */ - const struct dropbear_hash *recv_algo_mac; /* NULL for none */ - const struct dropbear_hash *trans_algo_mac; /* NULL for none */ + struct key_context_directional recv; + struct key_context_directional trans; + char algo_kex; char algo_hostkey; - char recv_algo_comp; /* compression */ - char trans_algo_comp; -#ifndef DISABLE_ZLIB - z_streamp recv_zstream; - z_streamp trans_zstream; -#endif - - /* actual keys */ - symmetric_CBC recv_symmetric_struct; - symmetric_CBC trans_symmetric_struct; - unsigned char recvmackey[MAX_MAC_KEY]; - unsigned char transmackey[MAX_MAC_KEY]; - + int allow_compress; /* whether compression has started (useful in + zlib@openssh.com delayed compression case) */ }; struct packetlist; @@ -114,8 +122,7 @@ struct sshsession { throughout the code, as handlers fill out this buffer with the packet to send. */ struct Queue writequeue; /* A queue of encrypted packets to send */ - buffer *readbuf; /* Encrypted */ - buffer *decryptreadbuf; /* Post-decryption */ + buffer *readbuf; /* From the wire, decrypted in-place */ buffer *payload; /* Post-decompression, the actual SSH packet */ unsigned int transseq, recvseq; /* Sequence IDs */ @@ -134,12 +141,16 @@ struct sshsession { unsigned char lastpacket; /* What the last received packet type was */ - int signal_pipe[2]; /* stores endpoints of a self-pipe used for + int signal_pipe[2]; /* stores endpoints of a self-pipe used for race-free signal handling */ - time_t last_packet_time; /* time of the last packet transmission, for + time_t last_trx_packet_time; /* time of the last packet transmission, for keepalive purposes */ + time_t last_packet_time; /* time of the last packet transmission or receive, for + idle timeout purposes */ + + /* KEX/encryption related */ struct KEXState kexstate; struct key_context *keys; @@ -368,6 +368,8 @@ void send_msg_userauth_success() { buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_SUCCESS); encrypt_packet(); + /* authdone must be set after encrypt_packet() for + * delayed-zlib mode */ ses.authstate.authdone = 1; ses.connect_time = 0; diff --git a/svr-authpubkeyoptions.c b/svr-authpubkeyoptions.c index 8a92d62..13a179d 100644 --- a/svr-authpubkeyoptions.c +++ b/svr-authpubkeyoptions.c @@ -105,7 +105,7 @@ void svr_pubkey_options_cleanup() { /* 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); + const unsigned int len = strlen(opt_name); if (options_buf->len - options_buf->pos < len) { return DROPBEAR_FAILURE; } diff --git a/svr-chansession.c b/svr-chansession.c index 9b2a412..23dad8c 100644 --- a/svr-chansession.c +++ b/svr-chansession.c @@ -663,11 +663,11 @@ static int noptycommand(struct Channel *channel, struct ChanSess *chansess) { addchildpid(chansess, chansess->pid); if (svr_ses.lastexit.exitpid != -1) { + unsigned int i; 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")) @@ -878,6 +878,7 @@ static void execchild(void *user_data) { addnewvar("LOGNAME", ses.authstate.pw_name); addnewvar("HOME", ses.authstate.pw_dir); addnewvar("SHELL", get_user_shell()); + addnewvar("PATH", DEFAULT_PATH); if (chansess->term != NULL) { addnewvar("TERM", chansess->term); } @@ -133,7 +133,7 @@ void main_noinetd() { for (i = 0; i < MAX_UNAUTH_CLIENTS; i++) { childpipes[i] = -1; } - bzero(preauth_addrs, sizeof(preauth_addrs)); + memset(preauth_addrs, 0x0, sizeof(preauth_addrs)); /* Set up the listening sockets */ listensockcount = listensockets(listensocks, MAX_LISTEN_ADDR, &maxsock); diff --git a/svr-runopts.c b/svr-runopts.c index c8b6585..4f1355a 100644 --- a/svr-runopts.c +++ b/svr-runopts.c @@ -82,8 +82,9 @@ static void printhelp(const char * progname) { #endif "-W <receive_window_buffer> (default %d, larger may be faster, max 1MB)\n" "-K <keepalive> (0 is never, default %d)\n" + "-I <idle_timeout> (0 is never, default %d)\n" #ifdef DEBUG_TRACE - "-v verbose\n" + "-v verbose (compiled with DEBUG_TRACE)\n" #endif ,DROPBEAR_VERSION, progname, #ifdef DROPBEAR_DSS @@ -93,7 +94,7 @@ static void printhelp(const char * progname) { RSA_PRIV_FILENAME, #endif DROPBEAR_MAX_PORTS, DROPBEAR_DEFPORT, DROPBEAR_PIDFILE, - DEFAULT_RECV_WINDOW, DEFAULT_KEEPALIVE); + DEFAULT_RECV_WINDOW, DEFAULT_KEEPALIVE, DEFAULT_IDLE_TIMEOUT); } void svr_getopts(int argc, char ** argv) { @@ -103,6 +104,7 @@ void svr_getopts(int argc, char ** argv) { int nextisport = 0; char* recv_window_arg = NULL; char* keepalive_arg = NULL; + char* idle_timeout_arg = NULL; /* see printhelp() for options */ svr_opts.rsakeyfile = NULL; @@ -134,7 +136,8 @@ void svr_getopts(int argc, char ** argv) { svr_opts.usingsyslog = 1; #endif opts.recv_window = DEFAULT_RECV_WINDOW; - opts.keepalive_secs = DEFAULT_KEEPALIVE; + opts.keepalive_secs = DEFAULT_KEEPALIVE; + opts.idle_timeout_secs = DEFAULT_IDLE_TIMEOUT; #ifdef ENABLE_SVR_REMOTETCPFWD opts.listen_fwd_all = 0; @@ -218,6 +221,9 @@ void svr_getopts(int argc, char ** argv) { case 'K': next = &keepalive_arg; break; + case 'I': + next = &idle_timeout_arg; + break; #if defined(ENABLE_SVR_PASSWORD_AUTH) || defined(ENABLE_SVR_PAM_AUTH) case 's': svr_opts.noauthpass = 1; @@ -253,7 +259,7 @@ void svr_getopts(int argc, char ** argv) { svr_opts.addresses[0] = m_strdup(DROPBEAR_DEFADDRESS); svr_opts.portcount = 1; } - + if (svr_opts.dsskeyfile == NULL) { svr_opts.dsskeyfile = DSS_PRIV_FILENAME; } @@ -294,6 +300,12 @@ void svr_getopts(int argc, char ** argv) { dropbear_exit("Bad keepalive '%s'", keepalive_arg); } } + + if (idle_timeout_arg) { + if (m_str_to_uint(idle_timeout_arg, &opts.idle_timeout_secs) == DROPBEAR_FAILURE) { + dropbear_exit("Bad idle_timeout '%s'", idle_timeout_arg); + } + } } static void addportandaddress(char* spec) { diff --git a/sysoptions.h b/sysoptions.h index 6b17151..2de1184 100644 --- a/sysoptions.h +++ b/sysoptions.h @@ -4,7 +4,7 @@ *******************************************************************/ #ifndef DROPBEAR_VERSION -#define DROPBEAR_VERSION "0.51" +#define DROPBEAR_VERSION "0.52" #endif #define LOCAL_IDENT "SSH-2.0-dropbear_" DROPBEAR_VERSION @@ -68,6 +68,7 @@ #define DROPBEAR_COMP_NONE 0 #define DROPBEAR_COMP_ZLIB 1 +#define DROPBEAR_COMP_ZLIB_DELAY 2 /* Required for pubkey auth */ #if defined(ENABLE_SVR_PUBKEY_AUTH) || defined(DROPBEAR_CLIENT) @@ -133,12 +134,12 @@ accept for keyb-interactive auth */ -#if defined(DROPBEAR_AES256_CBC) || defined(DROPBEAR_AES128_CBC) -#define DROPBEAR_AES_CBC +#if defined(DROPBEAR_AES256) || defined(DROPBEAR_AES128) +#define DROPBEAR_AES #endif -#if defined(DROPBEAR_TWOFISH256_CBC) || defined(DROPBEAR_TWOFISH128_CBC) -#define DROPBEAR_TWOFISH_CBC +#if defined(DROPBEAR_TWOFISH256) || defined(DROPBEAR_TWOFISH128) +#define DROPBEAR_TWOFISH #endif #ifndef ENABLE_X11FWD @@ -201,5 +202,8 @@ #define IS_DROPBEAR_CLIENT 1 #else -#error You must compiled with either DROPBEAR_CLIENT or DROPBEAR_SERVER selected +/* Just building key utils? */ +#define IS_DROPBEAR_SERVER 0 +#define IS_DROPBEAR_CLIENT 0 + #endif @@ -49,6 +49,8 @@ struct TCPFwdList { const unsigned char* connectaddr; unsigned int connectport; unsigned int listenport; + unsigned int have_reply; /* is set to 1 after a reply has been received + when setting up the forwarding */ struct TCPFwdList * next; }; @@ -62,6 +64,8 @@ extern const struct ChanType svr_chan_tcpdirect; void setup_localtcp(); void setup_remotetcp(); extern const struct ChanType cli_chan_tcpremote; +void cli_recv_msg_request_success(); +void cli_recv_msg_request_failure(); /* Common */ int listen_tcpfwd(struct TCPListener* tcpinfo); |