snapshot of stuff

--HG--
extra : convert_revision : 2903853ba24669d01547710986ad531357602633
This commit is contained in:
Matt Johnston 2004-07-26 02:44:20 +00:00
parent f6fce0981d
commit a9c38fb37f
20 changed files with 750 additions and 321 deletions

5
algo.h
View File

@ -66,10 +66,9 @@ void crypto_init();
int have_algo(char* algo, size_t algolen, algo_type algos[]);
void buf_put_algolist(buffer * buf, algo_type localalgos[]);
algo_type * common_buf_match_algo(buffer* buf, algo_type localalgos[],
int *goodguess);
algo_type * svr_buf_match_algo(buffer* buf, algo_type localalgos[],
int *goodguess);
algo_type * cli_buf_match_algo(buffer* buf, algo_type localalgos[]);
algo_type * cli_buf_match_algo(buffer* buf, algo_type localalgos[],
int *goodguess);
#endif /* _ALGO_H_ */

View File

@ -33,7 +33,8 @@
* direction MUST be the first algorithm on the client's list
* that is also on the server's list.
*/
algo_type * cli_buf_match_algo(buffer* buf, algo_type localalgos[]) {
algo_type * cli_buf_match_algo(buffer* buf, algo_type localalgos[],
int *goodguess) {
unsigned char * algolist = NULL;
unsigned char * remotealgos[MAX_PROPOSED_ALGO];
@ -41,6 +42,8 @@ algo_type * cli_buf_match_algo(buffer* buf, algo_type localalgos[]) {
unsigned int count, i, j;
algo_type * ret = NULL;
*goodguess = 0;
/* get the comma-separated list from the buffer ie "algo1,algo2,algo3" */
algolist = buf_getstring(buf, &len);
TRACE(("cli_buf_match_algo: %s", algolist));
@ -78,6 +81,10 @@ algo_type * cli_buf_match_algo(buffer* buf, algo_type localalgos[]) {
if (len == strlen(remotealgos[i])
&& strncmp(localalgos[j].name,
remotealgos[i], len) == 0) {
if (i == 0 && j == 0) {
/* was a good guess */
*goodguess = 1;
}
ret = &localalgos[j];
goto out;
}

91
cli-kex.c Normal file
View File

@ -0,0 +1,91 @@
/*
* Dropbear - a SSH2 server
*
* Copyright (c) 2002,2003 Matt Johnston
* 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. */
#include "includes.h"
#include "session.h"
#include "dbutil.h"
#include "algo.h"
#include "buffer.h"
#include "session.h"
#include "kex.h"
#include "ssh.h"
#include "packet.h"
#include "bignum.h"
#include "random.h"
#include "runopts.h"
void send_msg_kexdh_init() {
cli_ses.dh_e = (mp_int*)m_malloc(sizeof(mp_int));
cli_ses.dh_x = (mp_int*)m_malloc(sizeof(mp_int));
m_mp_init_multi(cli_ses.dh_e, cli_ses.dh_x);
gen_kexdh_vals(cli_ses.dh_e, cli_ses.dh_x);
CHECKCLEARTOWRITE();
buf_putbyte(ses.writepayload, SSH_MSG_KEXDH_INIT);
buf_putmpint(ses.writepayload, cli_ses.dh_e);
encrypt_packet();
ses.requirenext = SSH_MSG_KEXDH_REPLY;
}
/* Handle a diffie-hellman key exchange reply. */
void recv_msg_kexdh_reply() {
mp_int dh_f;
sign_key *hostkey = NULL;
int type;
type = ses.newkeys->algo_hostkey;
hostkey = new_sign_key();
if (buf_get_pub_key(ses.payload, hostkey, &type) != DROPBEAR_SUCCESS) {
dropbear_exit("Bad KEX packet");
}
m_mp_init(&dh_f);
if (buf_getmpint(ses.payload, &dh_f) != DROPBEAR_SUCCESS) {
dropbear_exit("Bad KEX packet");
}
kexdh_comb_key(cli_ses.dh_e, cli_ses.dh_x, &dh_f, hostkey);
mp_clear(&dh_f);
if (buf_verify(ses.payload, hostkey, ses.hash, SHA1_HASH_SIZE)
!= DROPBEAR_SUCCESS) {
dropbear_exit("Bad hostkey signature");
}
/* XXX TODO */
dropbear_log(LOG_WARNING,"Not checking hostkey fingerprint for the moment");
sign_key_free(hostkey);
hostkey = NULL;
send_msg_newkeys();
ses.requirenext = SSH_MSG_NEWKEYS;
TRACE(("leave recv_msg_kexdh_init"));
}

33
cli-main.c Normal file
View File

@ -0,0 +1,33 @@
#include <includes.h>
int main(int argc, char ** argv) {
int sock;
char* error = NULL;
char* hostandport;
int len;
_dropbear_exit = cli_dropbear_exit;
_dropbear_log = cli_dropbear_log;
cli_getopts(argc, argv);
sock = connect_remote(cli_opts.remotehost, cli_opts.remoteport,
0, &error);
if (sock < 0) {
dropbear_exit("%s", error);
}
/* Set up the host:port log */
len = strlen(cli_opts.remotehost);
len += 10; /* 16 bit port and leeway*/
hostandport = (char*)m_malloc(len);
snprintf(hostandport, len, "%s%d",
cli_opts.remotehost, cli_opts.remoteport);
cli_session(sock, hostandport);
/* not reached */
return -1;
}

101
cli-session.c Normal file
View File

@ -0,0 +1,101 @@
#include "includes.h"
#include "session.h"
#include "dbutil.h"
#include "kex.h"
#include "ssh.h"
#include "packet.h"
#include "tcpfwd-direct.h"
#include "tcpfwd-remote.h"
#include "channel.h"
#include "random.h"
static void cli_remoteclosed();
static void cli_sessionloop();
struct clientsession cli_ses; /* GLOBAL */
static const packettype cli_packettypes[] = {
/* TYPE, AUTHREQUIRED, FUNCTION */
{SSH_MSG_KEXINIT, recv_msg_kexinit},
{SSH_MSG_KEXDH_REPLY, recv_msg_kexdh_reply}, // client
{SSH_MSG_NEWKEYS, recv_msg_newkeys},
{SSH_MSG_CHANNEL_DATA, recv_msg_channel_data},
{SSH_MSG_CHANNEL_WINDOW_ADJUST, recv_msg_channel_window_adjust},
{SSH_MSG_GLOBAL_REQUEST, recv_msg_global_request_remotetcp},
{SSH_MSG_CHANNEL_REQUEST, recv_msg_channel_request},
{SSH_MSG_CHANNEL_OPEN, recv_msg_channel_open},
{SSH_MSG_CHANNEL_EOF, recv_msg_channel_eof},
{SSH_MSG_CHANNEL_CLOSE, recv_msg_channel_close},
{SSH_MSG_CHANNEL_OPEN_CONFIRMATION, recv_msg_channel_open_confirmation},
{SSH_MSG_CHANNEL_OPEN_FAILURE, recv_msg_channel_open_failure},
{0, 0} /* End */
};
static const struct ChanType *cli_chantypes[] = {
// &clichansess,
/* &chan_tcpdirect etc, though need to only allow if we've requested
* that forwarding */
NULL /* Null termination */
};
void cli_session(int sock, char* remotehost) {
crypto_init();
common_session_init(sock, remotehost);
chaninitialise(cli_chantypes);
/* For printing "remote host closed" for the user */
session_remoteclosed = cli_remoteclosed;
/* packet handlers */
ses.packettypes = cli_packettypes;
/* Ready to go */
sessinitdone = 1;
/* Exchange identification */
session_identification();
seedrandom();
send_msg_kexinit();
/* XXX here we do stuff differently */
session_loop(cli_sessionloop);
/* Not reached */
}
static void cli_sessionloop() {
switch (cli_ses.state) {
KEXINIT_RCVD:
/* We initiate the KEX. If DH wasn't the correct type, the KEXINIT
* negotiation would have failed. */
send_msg_kexdh_init();
cli_ses.state = KEXDH_INIT_SENT;
break;
default:
break;
}
if (cli_ses.donefirstkex && !cli_ses.authdone) {
}
/* called when the remote side closes the connection */
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;
dropbear_exit("%s closed the connection", ses.remotehost);
}

View File

@ -193,7 +193,6 @@ void kexinitialise() {
ses.kexstate.sentnewkeys = 0;
/* first_packet_follows */
/* TODO - currently not handled */
ses.kexstate.firstfollows = 0;
ses.kexstate.datatrans = 0;
@ -402,47 +401,54 @@ void recv_msg_kexinit() {
if (IS_DROPBEAR_CLIENT) {
#ifdef DROPBEAR_CLIENT
/* read the peer's choice of algos */
cli_read_kex();
/* read the peer's choice of algos */
read_kex_algos(cli_buf_match_algo);
/* V_C, the client's version string (CR and NL excluded) */
/* V_C, the client's version string (CR and NL excluded) */
buf_putstring(ses.kexhashbuf,
(unsigned char*)LOCAL_IDENT, strlen(LOCAL_IDENT));
/* V_S, the server's version string (CR and NL excluded) */
/* V_S, the server's version string (CR and NL excluded) */
buf_putstring(ses.kexhashbuf,
ses.remoteident, strlen((char*)ses.remoteident));
/* I_C, the payload of the client's SSH_MSG_KEXINIT */
/* I_C, the payload of the client's SSH_MSG_KEXINIT */
buf_putstring(ses.kexhashbuf,
buf_getptr(ses.transkexinit, ses.transkexinit->len),
ses.transkexinit->len);
/* I_S, the payload of the server's SSH_MSG_KEXINIT */
/* I_S, the payload of the server's SSH_MSG_KEXINIT */
buf_setpos(ses.payload, 0);
buf_putstring(ses.kexhashbuf,
buf_getptr(ses.payload, ses.payload->len),
ses.payload->len);
cli_ses.state = KEXINIT_RCVD;
#endif
} else {
/* SERVER */
#ifdef DROPBEAR_SERVER
/* read the peer's choice of algos */
svr_read_kex();
/* V_C, the client's version string (CR and NL excluded) */
/* read the peer's choice of algos */
read_kex_algos(svr_buf_match_algo);
/* V_C, the client's version string (CR and NL excluded) */
buf_putstring(ses.kexhashbuf,
ses.remoteident, strlen((char*)ses.remoteident));
/* V_S, the server's version string (CR and NL excluded) */
/* V_S, the server's version string (CR and NL excluded) */
buf_putstring(ses.kexhashbuf,
(unsigned char*)LOCAL_IDENT, strlen(LOCAL_IDENT));
/* I_C, the payload of the client's SSH_MSG_KEXINIT */
/* I_C, the payload of the client's SSH_MSG_KEXINIT */
buf_setpos(ses.payload, 0);
buf_putstring(ses.kexhashbuf,
buf_getptr(ses.payload, ses.payload->len),
ses.payload->len);
/* I_S, the payload of the server's SSH_MSG_KEXINIT */
/* I_S, the payload of the server's SSH_MSG_KEXINIT */
buf_putstring(ses.kexhashbuf,
buf_getptr(ses.transkexinit, ses.transkexinit->len),
ses.transkexinit->len);
ses.requirenext = SSH_MSG_KEXDH_INIT;
#endif
}
buf_free(ses.transkexinit);
@ -450,9 +456,233 @@ void recv_msg_kexinit() {
/* the rest of ses.kexhashbuf will be done after DH exchange */
ses.kexstate.recvkexinit = 1;
ses.requirenext = SSH_MSG_KEXDH_INIT;
// ses.expecting = 0; // client matt
TRACE(("leave recv_msg_kexinit"));
}
/* Initialises and generate one side of the diffie-hellman key exchange values.
* See the ietf-secsh-transport draft, section 6, for details */
void gen_kexdh_vals(mp_int *dh_pub, mp_int *dh_priv) {
mp_int dh_p, dh_q, dh_g;
unsigned char randbuf[DH_P_LEN];
int dh_q_len;
TRACE(("enter send_msg_kexdh_reply"));
m_mp_init_multi(&dh_g, &dh_p, &dh_q, dh_priv, dh_pub, NULL);
/* read the prime and generator*/
if (mp_read_unsigned_bin(&dh_p, (unsigned char*)dh_p_val, DH_P_LEN)
!= MP_OKAY) {
dropbear_exit("Diffie-Hellman error");
}
if (mp_set_int(&dh_g, DH_G_VAL) != MP_OKAY) {
dropbear_exit("Diffie-Hellman error");
}
/* calculate q = (p-1)/2 */
/* dh_priv is just a temp var here */
if (mp_sub_d(&dh_p, 1, dh_priv) != MP_OKAY) {
dropbear_exit("Diffie-Hellman error");
}
if (mp_div_2(dh_priv, &dh_q) != MP_OKAY) {
dropbear_exit("Diffie-Hellman error");
}
dh_q_len = mp_unsigned_bin_size(&dh_q);
/* calculate our random value dh_y */
do {
assert((unsigned int)dh_q_len <= sizeof(randbuf));
genrandom(randbuf, dh_q_len);
if (mp_read_unsigned_bin(dh_priv, randbuf, dh_q_len) != MP_OKAY) {
dropbear_exit("Diffie-Hellman error");
}
} while (mp_cmp(dh_priv, &dh_q) == MP_GT || mp_cmp_d(dh_priv, 0) != MP_GT);
/* f = g^y mod p */
if (mp_exptmod(&dh_g, dh_priv, &dh_p, dh_pub) != MP_OKAY) {
dropbear_exit("Diffie-Hellman error");
}
mp_clear_multi(&dh_g, &dh_p, &dh_q, NULL);
}
/* This function is fairly common between client/server, with some substitution
* of dh_e/dh_f etc. Hence these arguments:
* dh_pub_us is 'e' for the client, 'f' for the server. dh_pub_them is
* vice-versa. dh_priv is the x/y value corresponding to dh_pub_us */
void kexdh_comb_key(mp_int *dh_pub_us, mp_int *dh_priv, mp_int *dh_pub_them,
sign_key *hostkey) {
mp_int dh_p;
mp_int *dh_e = NULL, *dh_f = NULL;
hash_state hs;
/* read the prime and generator*/
mp_init(&dh_p);
if (mp_read_unsigned_bin(&dh_p, (unsigned char*)dh_p_val, DH_P_LEN)
!= MP_OKAY) {
dropbear_exit("Diffie-Hellman error");
}
/* Check that dh_pub_them (dh_e or dh_f) is in the range [1, p-1] */
if (mp_cmp(dh_pub_them, &dh_p) != MP_LT
|| mp_cmp_d(dh_pub_them, 0) != MP_GT) {
dropbear_exit("Diffie-Hellman error");
}
/* K = e^y mod p = f^x mod p */
ses.dh_K = (mp_int*)m_malloc(sizeof(mp_int));
m_mp_init(ses.dh_K);
if (mp_exptmod(dh_pub_them, dh_priv, &dh_p, ses.dh_K) != MP_OKAY) {
dropbear_exit("Diffie-Hellman error");
}
/* clear no longer needed vars */
mp_clear_multi(&dh_p, NULL);
/* From here on, the code needs to work with the _same_ vars on each side,
* not vice-versaing for client/server */
if (IS_DROPBEAR_CLIENT) {
dh_e = dh_pub_us;
dh_f = dh_pub_them;
} else {
dh_e = dh_pub_them;
dh_f = dh_pub_us;
}
/* Create the remainder of the hash buffer, to generate the exchange hash */
/* K_S, the host key */
buf_put_pub_key(ses.kexhashbuf, hostkey, ses.newkeys->algo_hostkey);
/* e, exchange value sent by the client */
buf_putmpint(ses.kexhashbuf, dh_e);
/* f, exchange value sent by the server */
buf_putmpint(ses.kexhashbuf, dh_f);
/* K, the shared secret */
buf_putmpint(ses.kexhashbuf, ses.dh_K);
/* calculate the hash H to sign */
sha1_init(&hs);
buf_setpos(ses.kexhashbuf, 0);
sha1_process(&hs, buf_getptr(ses.kexhashbuf, ses.kexhashbuf->len),
ses.kexhashbuf->len);
sha1_done(&hs, ses.hash);
buf_free(ses.kexhashbuf);
ses.kexhashbuf = NULL;
/* first time around, we set the session_id to H */
if (ses.session_id == NULL) {
/* create the session_id, this never needs freeing */
ses.session_id = (unsigned char*)m_malloc(SHA1_HASH_SIZE);
memcpy(ses.session_id, ses.hash, SHA1_HASH_SIZE);
}
}
/* read the other side's algo list. buf_match_algo is a callback to match
* algos for the client or server. */
void read_kex_algos(
algo_type*(buf_match_algo)(buffer*buf, algo_type localalgos[],
int *goodguess)) {
algo_type * algo;
char * erralgo = NULL;
int goodguess = 0;
int allgood = 1; /* we AND this with each goodguess and see if its still
true after */
buf_incrpos(ses.payload, 16); /* start after the cookie */
ses.newkeys = (struct key_context*)m_malloc(sizeof(struct key_context));
/* kex_algorithms */
algo = buf_match_algo(ses.payload, sshkex, &goodguess);
allgood &= goodguess;
if (algo == NULL) {
erralgo = "kex";
goto error;
}
ses.newkeys->algo_kex = algo->val;
/* server_host_key_algorithms */
algo = buf_match_algo(ses.payload, sshhostkey, &goodguess);
allgood &= goodguess;
if (algo == NULL) {
erralgo = "hostkey";
goto error;
}
ses.newkeys->algo_hostkey = algo->val;
/* encryption_algorithms_client_to_server */
algo = buf_match_algo(ses.payload, sshciphers, &goodguess);
if (algo == NULL) {
erralgo = "enc c->s";
goto error;
}
ses.newkeys->recv_algo_crypt = (struct dropbear_cipher*)algo->data;
/* encryption_algorithms_server_to_client */
algo = buf_match_algo(ses.payload, sshciphers, &goodguess);
if (algo == NULL) {
erralgo = "enc s->c";
goto error;
}
ses.newkeys->trans_algo_crypt = (struct dropbear_cipher*)algo->data;
/* mac_algorithms_client_to_server */
algo = buf_match_algo(ses.payload, sshhashes, &goodguess);
if (algo == NULL) {
erralgo = "mac c->s";
goto error;
}
ses.newkeys->recv_algo_mac = (struct dropbear_hash*)algo->data;
/* mac_algorithms_server_to_client */
algo = buf_match_algo(ses.payload, sshhashes, &goodguess);
if (algo == NULL) {
erralgo = "mac s->c";
goto error;
}
ses.newkeys->trans_algo_mac = (struct dropbear_hash*)algo->data;
/* compression_algorithms_client_to_server */
algo = buf_match_algo(ses.payload, sshcompress, &goodguess);
if (algo == NULL) {
erralgo = "comp c->s";
goto error;
}
ses.newkeys->recv_algo_comp = algo->val;
/* compression_algorithms_server_to_client */
algo = buf_match_algo(ses.payload, sshcompress, &goodguess);
if (algo == NULL) {
erralgo = "comp s->c";
goto error;
}
ses.newkeys->trans_algo_comp = algo->val;
/* languages_client_to_server */
buf_eatstring(ses.payload);
/* languages_server_to_client */
buf_eatstring(ses.payload);
/* first_kex_packet_follows */
if (buf_getbyte(ses.payload)) {
ses.kexstate.firstfollows = 1;
/* if the guess wasn't good, we ignore the packet sent */
if (!allgood) {
ses.ignorenext = 1;
}
}
/* reserved for future extensions */
buf_getint(ses.payload);
return;
error:
dropbear_exit("no matching algo %s", erralgo);
}

View File

@ -35,6 +35,7 @@
#include "channel.h"
#include "atomicio.h"
struct sshsession ses; /* GLOBAL */
/* need to know if the session struct has been initialised, this way isn't the
@ -44,19 +45,18 @@ int sessinitdone = 0; /* GLOBAL */
/* this is set when we get SIGINT or SIGTERM, the handler is in main.c */
int exitflag = 0; /* GLOBAL */
static int ident_readln(int fd, char* buf, int count);
void(*session_remoteclosed)() = NULL;
static void checktimeouts();
static int ident_readln(int fd, char* buf, int count);
/* called only at the start of a session, set up initial state */
void common_session_init(int sock) {
void common_session_init(int sock, char* remotehost) {
TRACE(("enter session_init"));
ses.remoteaddr = NULL;
ses.remotehost = NULL;
ses.remotehost = remotehost;
ses.sock = sock;
ses.maxfd = sock;
@ -114,6 +114,86 @@ void common_session_init(int sock) {
TRACE(("leave session_init"));
}
void session_loop(void(*loophandler)()) {
fd_set readfd, writefd;
struct timeval timeout;
int val;
/* main loop, select()s for all sockets in use */
for(;;) {
timeout.tv_sec = SELECT_TIMEOUT;
timeout.tv_usec = 0;
FD_ZERO(&writefd);
FD_ZERO(&readfd);
assert(ses.payload == NULL);
if (ses.sock != -1) {
FD_SET(ses.sock, &readfd);
if (!isempty(&ses.writequeue)) {
FD_SET(ses.sock, &writefd);
}
}
/* set up for channels which require reading/writing */
if (ses.dataallowed) {
setchannelfds(&readfd, &writefd);
}
val = select(ses.maxfd+1, &readfd, &writefd, NULL, &timeout);
if (exitflag) {
dropbear_exit("Terminated by signal");
}
if (val < 0) {
if (errno == EINTR) {
continue;
} else {
dropbear_exit("Error in select");
}
}
/* check for auth timeout, rekeying required etc */
checktimeouts();
if (val == 0) {
/* timeout */
TRACE(("select timeout"));
continue;
}
/* process session socket's incoming/outgoing data */
if (ses.sock != -1) {
if (FD_ISSET(ses.sock, &writefd) && !isempty(&ses.writequeue)) {
write_packet();
}
if (FD_ISSET(ses.sock, &readfd)) {
read_packet();
}
/* Process the decrypted packet. After this, the read buffer
* will be ready for a new packet */
if (ses.payload != NULL) {
process_packet();
}
}
/* process pipes etc for the channels, ses.dataallowed == 0
* during rekeying ) */
if (ses.dataallowed) {
channelio(&readfd, &writefd);
}
if (loophandler) {
loophandler();
}
} /* for(;;) */
/* Not reached */
}
/* clean up a session on exit */
void common_session_cleanup() {
@ -134,35 +214,7 @@ void common_session_cleanup() {
TRACE(("leave session_cleanup"));
}
/* Check all timeouts which are required. Currently these are the time for
* user authentication, and the automatic rekeying. */
void checktimeouts() {
struct timeval tv;
long secs;
if (gettimeofday(&tv, 0) < 0) {
dropbear_exit("Error getting time");
}
secs = tv.tv_sec;
if (ses.connecttimeout != 0 && secs > ses.connecttimeout) {
dropbear_close("Timeout before auth");
}
/* we can't rekey if we haven't done remote ident exchange yet */
if (ses.remoteident == NULL) {
return;
}
if (!ses.kexstate.sentkexinit
&& (secs - ses.kexstate.lastkextime >= KEX_REKEY_TIMEOUT
|| ses.kexstate.datarecv+ses.kexstate.datatrans >= KEX_REKEY_DATA)){
TRACE(("rekeying after timeout or max data reached"));
send_msg_kexinit();
}
}
void session_identification() {
/* max length of 255 chars */
@ -268,3 +320,33 @@ static int ident_readln(int fd, char* buf, int count) {
return pos+1;
}
/* Check all timeouts which are required. Currently these are the time for
* user authentication, and the automatic rekeying. */
static void checktimeouts() {
struct timeval tv;
long secs;
if (gettimeofday(&tv, 0) < 0) {
dropbear_exit("Error getting time");
}
secs = tv.tv_sec;
if (ses.connecttimeout != 0 && secs > ses.connecttimeout) {
dropbear_close("Timeout before auth");
}
/* we can't rekey if we haven't done remote ident exchange yet */
if (ses.remoteident == NULL) {
return;
}
if (!ses.kexstate.sentkexinit
&& (secs - ses.kexstate.lastkextime >= KEX_REKEY_TIMEOUT
|| ses.kexstate.datarecv+ses.kexstate.datatrans >= KEX_REKEY_DATA)){
TRACE(("rekeying after timeout or max data reached"));
send_msg_kexinit();
}
}

View File

@ -113,6 +113,95 @@ void dropbear_trace(const char* format, ...) {
}
#endif /* DEBUG_TRACE */
/* Connect via TCP to a host. Connection will try ipv4 or ipv6, will
* return immediately if nonblocking is set */
int connect_remote(const char* remotehost, const char* remoteport,
int nonblocking, char ** errstring) {
struct addrinfo *res0 = NULL, *res = NULL, hints;
int sock;
int err;
TRACE(("enter connect_remote"));
if (errstring != NULL) {
*errstring = NULL;
}
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
hints.ai_family = PF_UNSPEC;
err = getaddrinfo(remotehost, remoteport, &hints, &res0);
if (err) {
if (errstring != NULL && *errstring == NULL) {
int len;
len = 20 + strlen(gai_strerror(err));
*errstring = (char*)m_malloc(len);
snprintf(*errstring, len, "Error resolving: %s", gai_strerror(err));
}
TRACE(("Error resolving: %s", gai_strerror(err)));
return -1;
}
sock = -1;
err = EADDRNOTAVAIL;
for (res = res0; res; res = res->ai_next) {
sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sock < 0) {
err = errno;
continue;
}
if (nonblocking) {
if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) {
close(sock);
sock = -1;
if (errstring != NULL && *errstring == NULL) {
*errstring = m_strdup("Failed non-blocking");
}
TRACE(("Failed non-blocking: %s", strerror(errno)));
continue;
}
}
if (connect(sock, res->ai_addr, res->ai_addrlen) < 0) {
if (errno == EINPROGRESS) {
TRACE(("Connect in progress"));
break;
} else {
err = errno;
close(sock);
sock = -1;
continue;
}
}
break; /* Success */
}
if (sock < 0) {
/* Failed */
if (errstring != NULL && *errstring == NULL) {
int len;
len = 20 + strlen(strerror(err));
*errstring = (char*)m_malloc(len);
snprintf(*errstring, len, "Error connecting: %s", strerror(err));
}
TRACE(("Error connecting: %s", strerror(err)));
} else {
/* Success */
/* (err is used as a dummy var here) */
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&err, sizeof(err));
}
freeaddrinfo(res0);
TRACE(("leave connect_remote: sock %d", sock));
return sock;
}
/* Return a string representation of the socket address passed. The return
* value is allocated with malloc() */
unsigned char * getaddrstring(struct sockaddr * addr) {
@ -304,3 +393,4 @@ void m_burn(void *data, unsigned int len) {
*p++ = 0x66;
}
}

View File

@ -45,6 +45,8 @@ void printhex(unsigned char* buf, int len);
#endif
char * stripcontrol(const char * text);
unsigned char * getaddrstring(struct sockaddr * addr);
int connect_remote(const char* remotehost, const char* remoteport,
int nonblocking, char ** errstring);
char* getaddrhostname(struct sockaddr * addr);
int buf_readfile(buffer* buf, const char* filename);
@ -56,4 +58,7 @@ void * m_realloc(void* ptr, size_t size);
void __m_free(void* ptr);
void m_burn(void* data, unsigned int len);
/* Used to force mp_ints to be initialised */
#define DEF_MP_INT(X) mp_int X = {0, 0, 0, NULL}
#endif /* _DBUTIL_H_ */

View File

@ -36,7 +36,7 @@
/* Define this to print trace statements - very verbose */
/* Caution: Don't use this in an unfriendly environment (ie unfirewalled),
* since the printing does not sanitise strings etc */
#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

16
kex.h
View File

@ -26,17 +26,25 @@
#define _KEX_H_
#include "includes.h"
#include "algo.h"
void send_msg_kexinit();
void recv_msg_kexinit();
void send_dh_kex();
void recv_msg_kexdh_init();
void send_msg_newkeys();
void recv_msg_newkeys();
void kexinitialise();
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);
void svr_read_kex();
void cli_read_kex();
void read_kex_algos(
algo_type*(buf_match_algo)(buffer*buf, algo_type localalgos[],
int *goodguess));
void recv_msg_kexdh_init(); // server
void send_msg_kexdh_init(); // client
void recv_msg_kexdh_reply(); // client
extern const unsigned char dh_p_val[];
#define DH_P_LEN 128 /* The length of the dh_p_val array */

4
main.c
View File

@ -240,8 +240,10 @@ int main(int argc, char ** argv)
if (m_close(childpipe[0]) == DROPBEAR_FAILURE) {
dropbear_exit("Couldn't close socket");
}
/* start the session */
svr_session(childsock, childpipe[1], &remoteaddr);
svr_session(childsock, childpipe[1],
getaddrhostname(&remoteaddr));
/* don't return */
assert(0);
}

View File

@ -30,7 +30,7 @@
* parts are to allow for commandline -DDROPBEAR_XXX options etc.
******************************************************************/
#define DROPBEAR_SERVER
/* #define DROPBEAR_CLIENT */
//#define DROPBEAR_CLIENT
#ifndef DROPBEAR_PORT
#define DROPBEAR_PORT 22
@ -48,6 +48,7 @@
* perhaps 20% slower for pubkey operations (it is probably worth experimenting
* if you want to use this) */
/*#define NO_FAST_EXPTMOD*/
#define DROPBEAR_SMALL_CODE
/* Enable X11 Forwarding */
#define ENABLE_X11FWD
@ -181,7 +182,7 @@
*******************************************************************/
#ifndef DROPBEAR_VERSION
#define DROPBEAR_VERSION "0.41"
#define DROPBEAR_VERSION "0.41-and-client"
#endif
#define LOCAL_IDENT "SSH-2.0-dropbear_" DROPBEAR_VERSION

View File

@ -104,9 +104,11 @@ void process_packet() {
*/
/* check that we aren't expecting a particular packet */
#if 0
if (cli_ses.expecting && cli_ses.expecting == type) {
cli_ses.expecting = 0;
}
#endif
}
#endif

View File

@ -26,6 +26,7 @@
#define _SESSION_H_
#include "includes.h"
#include "options.h"
#include "buffer.h"
#include "signkey.h"
#include "kex.h"
@ -38,7 +39,8 @@
extern int sessinitdone; /* Is set to 0 somewhere */
extern int exitflag;
void common_session_init(int sock);
void common_session_init(int sock, char* remotehost);
void session_loop(void(*loophandler)());
void common_session_cleanup();
void checktimeouts();
void session_identification();
@ -46,10 +48,14 @@ void session_identification();
extern void(*session_remoteclosed)();
/* Server */
void svr_session(int sock, int childpipe, struct sockaddr *remoteaddr);
void svr_session(int sock, int childpipe, char *remotehost);
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_dropbear_exit(int exitcode, const char* format, va_list param);
void cli_dropbear_log(int priority, const char* format, va_list param);
struct key_context {
@ -85,8 +91,8 @@ struct sshsession {
int sock;
struct sockaddr *remoteaddr;
unsigned char *remotehost; /* the peer hostname */
unsigned char *remoteident;
int maxfd; /* the maximum file descriptor to check with select() */
@ -166,11 +172,20 @@ struct serversession {
};
typedef enum {
NOTHING,
KEXINIT_RCVD,
KEXDH_INIT_SENT,
KEXDH_REPLY_RCVD,
} cli_state;
struct clientsession {
mp_int *dh_e, *dh_x; /* Used during KEX */
cli_state state; /* Used to progress the KEX/auth/channelsession etc */
int something; /* XXX */
unsigned donefirstkex : 1; /* Set when we set sentnewkeys, never reset */
};
@ -182,7 +197,7 @@ extern struct serversession svr_ses;
#endif /* DROPBEAR_SERVER */
#ifdef DROPBEAR_CLIENT
extern struct serversession cli_ses;
extern struct clientsession cli_ses;
#endif /* DROPBEAR_CLIENT */
#endif /* _SESSION_H_ */

View File

@ -45,7 +45,8 @@ sign_key * new_sign_key() {
}
/* returns DROPBEAR_SUCCESS on success, DROPBEAR_FAILURE on fail.
* type is set to hold the type returned */
* type should be set by the caller to specify the type to read, and
* on return is set to the type read (useful when type = _ANY) */
int buf_get_pub_key(buffer *buf, sign_key *key, int *type) {
unsigned char* ident;

View File

@ -16,6 +16,8 @@ algo_type * svr_buf_match_algo(buffer* buf, algo_type localalgos[],
unsigned int count, i, j;
algo_type * ret = NULL;
*goodguess = 0;
/* get the comma-separated list from the buffer ie "algo1,algo2,algo3" */
algolist = buf_getstring(buf, &len);
/* Debug this */
@ -57,8 +59,6 @@ algo_type * svr_buf_match_algo(buffer* buf, algo_type localalgos[],
/* set if it was a good guess */
if (i == 0 && j == 0) {
*goodguess = 1;
} else {
*goodguess = 0;
}
/* set the algo to return */
ret = &localalgos[j];

182
svr-kex.c
View File

@ -70,87 +70,15 @@ void recv_msg_kexdh_init() {
* See the ietf-secsh-transport draft, section 6, for details */
static void send_msg_kexdh_reply(mp_int *dh_e) {
mp_int dh_p, dh_q, dh_g, dh_y, dh_f;
unsigned char randbuf[DH_P_LEN];
int dh_q_len;
hash_state hs;
mp_int dh_y, dh_f;
TRACE(("enter send_msg_kexdh_reply"));
m_mp_init_multi(&dh_g, &dh_p, &dh_q, &dh_y, &dh_f, NULL);
gen_kexdh_vals(&dh_f, &dh_y);
/* read the prime and generator*/
if (mp_read_unsigned_bin(&dh_p, (unsigned char*)dh_p_val, DH_P_LEN)
!= MP_OKAY) {
dropbear_exit("Diffie-Hellman error");
}
if (mp_set_int(&dh_g, DH_G_VAL) != MP_OKAY) {
dropbear_exit("Diffie-Hellman error");
}
kexdh_comb_key(&dh_f, &dh_y, dh_e, svr_opts.hostkey);
mp_clear(&dh_y);
/* calculate q = (p-1)/2 */
if (mp_sub_d(&dh_p, 1, &dh_y) != MP_OKAY) { /*dh_y is just a temp var here*/
dropbear_exit("Diffie-Hellman error");
}
if (mp_div_2(&dh_y, &dh_q) != MP_OKAY) {
dropbear_exit("Diffie-Hellman error");
}
dh_q_len = mp_unsigned_bin_size(&dh_q);
/* calculate our random value dh_y */
do {
assert((unsigned int)dh_q_len <= sizeof(randbuf));
genrandom(randbuf, dh_q_len);
if (mp_read_unsigned_bin(&dh_y, randbuf, dh_q_len) != MP_OKAY) {
dropbear_exit("Diffie-Hellman error");
}
} while (mp_cmp(&dh_y, &dh_q) == MP_GT || mp_cmp_d(&dh_y, 0) != MP_GT);
/* f = g^y mod p */
if (mp_exptmod(&dh_g, &dh_y, &dh_p, &dh_f) != MP_OKAY) {
dropbear_exit("Diffie-Hellman error");
}
mp_clear(&dh_g);
/* K = e^y mod p */
ses.dh_K = (mp_int*)m_malloc(sizeof(mp_int));
m_mp_init(ses.dh_K);
if (mp_exptmod(dh_e, &dh_y, &dh_p, ses.dh_K) != MP_OKAY) {
dropbear_exit("Diffie-Hellman error");
}
/* clear no longer needed vars */
mp_clear_multi(&dh_y, &dh_p, &dh_q, NULL);
/* Create the remainder of the hash buffer, to generate the exchange hash */
/* K_S, the host key */
buf_put_pub_key(ses.kexhashbuf, svr_opts.hostkey,
ses.newkeys->algo_hostkey);
/* e, exchange value sent by the client */
buf_putmpint(ses.kexhashbuf, dh_e);
/* f, exchange value sent by the server */
buf_putmpint(ses.kexhashbuf, &dh_f);
/* K, the shared secret */
buf_putmpint(ses.kexhashbuf, ses.dh_K);
/* calculate the hash H to sign */
sha1_init(&hs);
buf_setpos(ses.kexhashbuf, 0);
sha1_process(&hs, buf_getptr(ses.kexhashbuf, ses.kexhashbuf->len),
ses.kexhashbuf->len);
sha1_done(&hs, ses.hash);
buf_free(ses.kexhashbuf);
ses.kexhashbuf = NULL;
/* first time around, we set the session_id to H */
if (ses.session_id == NULL) {
/* create the session_id, this never needs freeing */
ses.session_id = (unsigned char*)m_malloc(SHA1_HASH_SIZE);
memcpy(ses.session_id, ses.hash, SHA1_HASH_SIZE);
}
/* we can start creating the kexdh_reply packet */
CHECKCLEARTOWRITE();
buf_putbyte(ses.writepayload, SSH_MSG_KEXDH_REPLY);
@ -171,105 +99,3 @@ static void send_msg_kexdh_reply(mp_int *dh_e) {
TRACE(("leave send_msg_kexdh_reply"));
}
/* read the client's choice of algorithms */
void svr_read_kex() {
algo_type * algo;
char * erralgo = NULL;
int goodguess = 0;
int allgood = 1; /* we AND this with each goodguess and see if its still
true after */
buf_incrpos(ses.payload, 16); /* start after the cookie */
ses.newkeys = (struct key_context*)m_malloc(sizeof(struct key_context));
/* kex_algorithms */
algo = svr_buf_match_algo(ses.payload, sshkex, &goodguess);
allgood &= goodguess;
if (algo == NULL) {
erralgo = "kex";
goto error;
}
ses.newkeys->algo_kex = algo->val;
/* server_host_key_algorithms */
algo = svr_buf_match_algo(ses.payload, sshhostkey, &goodguess);
allgood &= goodguess;
if (algo == NULL) {
erralgo = "hostkey";
goto error;
}
ses.newkeys->algo_hostkey = algo->val;
/* encryption_algorithms_client_to_server */
algo = svr_buf_match_algo(ses.payload, sshciphers, &goodguess);
if (algo == NULL) {
erralgo = "enc c->s";
goto error;
}
ses.newkeys->recv_algo_crypt = (struct dropbear_cipher*)algo->data;
/* encryption_algorithms_server_to_client */
algo = svr_buf_match_algo(ses.payload, sshciphers, &goodguess);
if (algo == NULL) {
erralgo = "enc s->c";
goto error;
}
ses.newkeys->trans_algo_crypt = (struct dropbear_cipher*)algo->data;
/* mac_algorithms_client_to_server */
algo = svr_buf_match_algo(ses.payload, sshhashes, &goodguess);
if (algo == NULL) {
erralgo = "mac c->s";
goto error;
}
ses.newkeys->recv_algo_mac = (struct dropbear_hash*)algo->data;
/* mac_algorithms_server_to_client */
algo = svr_buf_match_algo(ses.payload, sshhashes, &goodguess);
if (algo == NULL) {
erralgo = "mac s->c";
goto error;
}
ses.newkeys->trans_algo_mac = (struct dropbear_hash*)algo->data;
/* compression_algorithms_client_to_server */
algo = svr_buf_match_algo(ses.payload, sshcompress, &goodguess);
if (algo == NULL) {
erralgo = "comp c->s";
goto error;
}
ses.newkeys->recv_algo_comp = algo->val;
/* compression_algorithms_server_to_client */
algo = svr_buf_match_algo(ses.payload, sshcompress, &goodguess);
if (algo == NULL) {
erralgo = "comp s->c";
goto error;
}
ses.newkeys->trans_algo_comp = algo->val;
/* languages_client_to_server */
buf_eatstring(ses.payload);
/* languages_server_to_client */
buf_eatstring(ses.payload);
/* first_kex_packet_follows */
if (buf_getbyte(ses.payload)) {
ses.kexstate.firstfollows = 1;
/* if the guess wasn't good, we ignore the packet sent */
if (!allgood) {
ses.ignorenext = 1;
}
}
/* reserved for future extensions */
buf_getint(ses.payload);
return;
error:
dropbear_exit("no matching algo %s", erralgo);
}

View File

@ -50,7 +50,7 @@ static const packettype svr_packettypes[] = {
{SSH_MSG_SERVICE_REQUEST, recv_msg_service_request}, // server
{SSH_MSG_USERAUTH_REQUEST, recv_msg_userauth_request}, //server
{SSH_MSG_KEXINIT, recv_msg_kexinit},
{SSH_MSG_KEXDH_INIT, recv_msg_kexdh_init},
{SSH_MSG_KEXDH_INIT, recv_msg_kexdh_init}, // server
{SSH_MSG_NEWKEYS, recv_msg_newkeys},
{SSH_MSG_CHANNEL_DATA, recv_msg_channel_data},
{SSH_MSG_CHANNEL_WINDOW_ADJUST, recv_msg_channel_window_adjust},
@ -70,17 +70,12 @@ static const struct ChanType *svr_chantypes[] = {
NULL /* Null termination is mandatory. */
};
void svr_session(int sock, int childpipe, struct sockaddr* remoteaddr) {
void svr_session(int sock, int childpipe, char* remotehost) {
fd_set readfd, writefd;
struct timeval timeout;
int val;
crypto_init();
common_session_init(sock);
ses.remoteaddr = remoteaddr;
ses.remotehost = getaddrhostname(remoteaddr);
common_session_init(sock, remotehost);
/* Initialise server specific parts of the session */
svr_ses.childpipe = childpipe;
@ -111,75 +106,12 @@ void svr_session(int sock, int childpipe, struct sockaddr* remoteaddr) {
/* start off with key exchange */
send_msg_kexinit();
FD_ZERO(&readfd);
FD_ZERO(&writefd);
/* Run the main for loop. NULL is for the dispatcher - only the client
* code makes use of it */
session_loop(NULL);
/* main loop, select()s for all sockets in use */
for(;;) {
/* Not reached */
timeout.tv_sec = SELECT_TIMEOUT;
timeout.tv_usec = 0;
FD_ZERO(&writefd);
FD_ZERO(&readfd);
assert(ses.payload == NULL);
if (ses.sock != -1) {
FD_SET(ses.sock, &readfd);
if (!isempty(&ses.writequeue)) {
FD_SET(ses.sock, &writefd);
}
}
/* set up for channels which require reading/writing */
if (ses.dataallowed) {
setchannelfds(&readfd, &writefd);
}
val = select(ses.maxfd+1, &readfd, &writefd, NULL, &timeout);
if (exitflag) {
dropbear_exit("Terminated by signal");
}
if (val < 0) {
if (errno == EINTR) {
continue;
} else {
dropbear_exit("Error in select");
}
}
/* check for auth timeout, rekeying required etc */
checktimeouts();
if (val == 0) {
/* timeout */
TRACE(("select timeout"));
continue;
}
/* process session socket's incoming/outgoing data */
if (ses.sock != -1) {
if (FD_ISSET(ses.sock, &writefd) && !isempty(&ses.writequeue)) {
write_packet();
}
if (FD_ISSET(ses.sock, &readfd)) {
read_packet();
}
/* Process the decrypted packet. After this, the read buffer
* will be ready for a new packet */
if (ses.payload != NULL) {
process_packet();
}
}
/* process pipes etc for the channels, ses.dataallowed == 0
* during rekeying ) */
if (ses.dataallowed) {
channelio(&readfd, &writefd);
}
} /* for(;;) */
}
/* failure exit - format must be <= 100 chars */

View File

@ -27,6 +27,7 @@ static int newtcpdirect(struct Channel * channel) {
unsigned int destport;
unsigned char* orighost = NULL;
unsigned int origport;
char portstring[6];
int sock;
int len;
int ret = DROPBEAR_FAILURE;
@ -58,7 +59,8 @@ static int newtcpdirect(struct Channel * channel) {
goto out;
}
sock = newtcp(desthost, destport);
snprintf(portstring, sizeof(portstring), "%d", destport);
sock = connect_remote(desthost, portstring, 1, NULL);
if (sock < 0) {
TRACE(("leave newtcpdirect: sock failed"));
goto out;
@ -86,6 +88,7 @@ out:
* returned will need to be checked for success when it is first written.
* Similarities with OpenSSH's connect_to() are not coincidental.
* Returns -1 on failure */
#if 0
static int newtcp(const char * host, int port) {
int sock = -1;
@ -152,4 +155,5 @@ static int newtcp(const char * host, int port) {
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&val, sizeof(val));
return sock;
}
#endif
#endif /* DISABLE_TCPFWD_DIRECT */