*/
#define SSH2_PKTCTX_DHGROUP 0x0001
#define SSH2_PKTCTX_DHGEX 0x0002
+#define SSH2_PKTCTX_KEX_MASK 0x000F
#define SSH2_PKTCTX_PUBLICKEY 0x0010
#define SSH2_PKTCTX_PASSWORD 0x0020
#define SSH2_PKTCTX_KBDINTER 0x0040
#define BUG_CHOKES_ON_RSA 8
#define BUG_SSH2_RSA_PADDING 16
#define BUG_SSH2_DERIVEKEY 32
-/* 64 was BUG_SSH2_DH_GEX, now spare */
+#define BUG_SSH2_REKEY 64
#define BUG_SSH2_PK_SESSIONID 128
#define translate(x) if (type == x) return #x
#define SSH1_BUFFER_LIMIT 32768
#define SSH_MAX_BACKLOG 32768
#define OUR_V2_WINSIZE 16384
+#define OUR_V2_MAXPKT 0x4000UL
const static struct ssh_signkey *hostkey_algs[] = { &ssh_rsa, &ssh_dss };
static void ssh_throttle_all(Ssh ssh, int enable, int bufsize);
static void ssh2_set_window(struct ssh_channel *c, unsigned newwin);
static int ssh_sendbuffer(void *handle);
-static void ssh_do_close(Ssh ssh);
+static int ssh_do_close(Ssh ssh, int notify_exit);
static unsigned long ssh_pkt_getuint32(struct Packet *pkt);
static int ssh2_pkt_getbool(struct Packet *pkt);
static void ssh_pkt_getstring(struct Packet *pkt, char **p, int *length);
tree234 *channels; /* indexed by local id */
struct ssh_channel *mainchan; /* primary session channel */
int exitcode;
+ int close_expected;
tree234 *rportfwds, *portfwds;
unsigned long max_data_size;
int kex_in_progress;
long next_rekey, last_rekey;
+ char *deferred_rekey_reason; /* points to STATIC string; don't free */
};
#define logevent(s) logevent(ssh->frontend, s)
#define bombout(msg) \
do { \
char *text = dupprintf msg; \
- ssh_do_close(ssh); \
+ ssh_do_close(ssh, FALSE); \
logevent(text); \
connection_fatal(ssh->frontend, "%s", text); \
sfree(text); \
if (!ssh->kex_in_progress &&
ssh->max_data_size != 0 &&
ssh->outgoing_data_size > ssh->max_data_size)
- do_ssh2_transport(ssh, "Initiating key re-exchange "
- "(too much data sent)", -1, NULL);
+ do_ssh2_transport(ssh, "too much data sent", -1, NULL);
ssh_free_packet(pkt);
}
if (!ssh->kex_in_progress &&
ssh->max_data_size != 0 &&
ssh->outgoing_data_size > ssh->max_data_size)
- do_ssh2_transport(ssh, "Initiating key re-exchange "
- "(too much data sent)", -1, NULL);
+ do_ssh2_transport(ssh, "too much data sent", -1, NULL);
ssh->deferred_data_size = 0;
}
ssh->remote_bugs |= BUG_SSH2_PK_SESSIONID;
logevent("We believe remote version has SSH2 public-key-session-ID bug");
}
+
+ if (ssh->cfg.sshbug_rekey2 == FORCE_ON ||
+ (ssh->cfg.sshbug_rekey2 == AUTO &&
+ (wc_match("OpenSSH_2.[0-4]*", imp) ||
+ wc_match("OpenSSH_2.5.[0-3]*", imp) ||
+ wc_match("Sun_SSH_1.0", imp) ||
+ wc_match("Sun_SSH_1.0.1", imp)))) {
+ /*
+ * These versions have the SSH2 rekey bug.
+ */
+ ssh->remote_bugs |= BUG_SSH2_REKEY;
+ logevent("We believe remote version has SSH2 rekey bug");
+ }
}
/*
crFinishV;
}
-static void ssh_do_close(Ssh ssh)
+static int ssh_do_close(Ssh ssh, int notify_exit)
{
- int i;
+ int i, ret = 0;
struct ssh_channel *c;
ssh->state = SSH_STATE_CLOSED;
if (ssh->s) {
sk_close(ssh->s);
ssh->s = NULL;
- notify_remote_exit(ssh->frontend);
+ if (notify_exit)
+ notify_remote_exit(ssh->frontend);
+ else
+ ret = 1;
}
/*
* Now we must shut down any port and X forwardings going
sfree(c);
}
}
+
+ return ret;
}
static int ssh_closing(Plug plug, const char *error_msg, int error_code,
int calling_back)
{
Ssh ssh = (Ssh) plug;
- ssh_do_close(ssh);
+ int need_notify = ssh_do_close(ssh, FALSE);
+
+ if (!error_msg && !ssh->close_expected) {
+ error_msg = "Server unexpectedly closed network connection";
+ }
+
if (error_msg) {
/* A socket error has occurred. */
logevent(error_msg);
connection_fatal(ssh->frontend, "%s", error_msg);
} else {
- /* Otherwise, the remote side closed the connection normally. */
+ logevent("Server closed network connection");
}
+ if (need_notify)
+ notify_remote_exit(ssh->frontend);
return 0;
}
Ssh ssh = (Ssh) plug;
ssh_gotdata(ssh, (unsigned char *)data, len);
if (ssh->state == SSH_STATE_CLOSED) {
- ssh_do_close(ssh);
+ ssh_do_close(ssh, TRUE);
return 0;
}
return 1;
}
/* Warn about chosen cipher if necessary. */
- if (warn)
+ if (warn) {
+ sk_set_frozen(ssh->s, 1);
askalg(ssh->frontend, "cipher", cipher_string);
+ sk_set_frozen(ssh->s, 0);
+ }
}
switch (s->cipher_type) {
* Terminate.
*/
logevent("No username provided. Abandoning session.");
+ ssh->close_expected = TRUE;
ssh_closing((Plug)ssh, NULL, 0, 0);
crStop(1);
}
PKT_END);
logevent("Unable to authenticate");
connection_fatal(ssh->frontend, "Unable to authenticate");
+ ssh->close_expected = TRUE;
ssh_closing((Plug)ssh, NULL, 0, 0);
crStop(1);
}
* encrypted packet, we close the session once
* we've sent EXIT_CONFIRMATION.
*/
+ ssh->close_expected = TRUE;
ssh_closing((Plug)ssh, NULL, 0, 0);
}
int n_preferred_ciphers;
const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
const struct ssh_compress *preferred_comp;
- int first_kex;
+ int got_session_id, activated_authconn;
struct Packet *pktout;
};
crState(do_ssh2_transport_state);
s->csmac_tobe = s->scmac_tobe = NULL;
s->cscomp_tobe = s->sccomp_tobe = NULL;
- s->first_kex = 1;
+ s->got_session_id = s->activated_authconn = FALSE;
+
+ /*
+ * Be prepared to work around the buggy MAC problem.
+ */
+ if (ssh->remote_bugs & BUG_SSH2_HMAC)
+ s->maclist = buggymacs, s->nmacs = lenof(buggymacs);
+ else
+ s->maclist = macs, s->nmacs = lenof(macs);
+ begin_key_exchange:
+ ssh->pkt_ctx &= ~SSH2_PKTCTX_KEX_MASK;
{
- int i;
+ int i, j, commalist_started;
+
/*
* Set up the preferred key exchange. (NULL => warn below here)
*/
break;
}
}
- }
- {
- int i;
/*
* Set up the preferred ciphers. (NULL => warn below here)
*/
break;
}
}
- }
-
- /*
- * Set up preferred compression.
- */
- if (ssh->cfg.compression)
- s->preferred_comp = &ssh_zlib;
- else
- s->preferred_comp = &ssh_comp_none;
- /*
- * Be prepared to work around the buggy MAC problem.
- */
- if (ssh->remote_bugs & BUG_SSH2_HMAC)
- s->maclist = buggymacs, s->nmacs = lenof(buggymacs);
- else
- s->maclist = macs, s->nmacs = lenof(macs);
-
- begin_key_exchange:
- {
- int i, j, commalist_started;
+ /*
+ * Set up preferred compression.
+ */
+ if (ssh->cfg.compression)
+ s->preferred_comp = &ssh_zlib;
+ else
+ s->preferred_comp = &ssh_comp_none;
/*
* Enable queueing of outgoing auth- or connection-layer
ssh->kex = k;
}
if (ssh->kex) {
- if (s->warn)
+ if (s->warn) {
+ sk_set_frozen(ssh->s, 1);
askalg(ssh->frontend, "key-exchange algorithm",
ssh->kex->name);
+ sk_set_frozen(ssh->s, 0);
+ }
break;
}
}
}
}
if (s->cscipher_tobe) {
- if (s->warn)
+ if (s->warn) {
+ sk_set_frozen(ssh->s, 1);
askalg(ssh->frontend, "client-to-server cipher",
s->cscipher_tobe->name);
+ sk_set_frozen(ssh->s, 0);
+ }
break;
}
}
}
}
if (s->sccipher_tobe) {
- if (s->warn)
+ if (s->warn) {
+ sk_set_frozen(ssh->s, 1);
askalg(ssh->frontend, "server-to-client cipher",
s->sccipher_tobe->name);
+ sk_set_frozen(ssh->s, 0);
+ }
break;
}
}
*/
s->keystr = ssh->hostkey->fmtkey(s->hkey);
s->fingerprint = ssh->hostkey->fingerprint(s->hkey);
+ sk_set_frozen(ssh->s, 1);
verify_ssh_host_key(ssh->frontend,
ssh->savedhost, ssh->savedport, ssh->hostkey->keytype,
s->keystr, s->fingerprint);
- if (s->first_kex) { /* don't bother logging this in rekeys */
+ sk_set_frozen(ssh->s, 0);
+ if (!s->got_session_id) { /* don't bother logging this in rekeys */
logevent("Host key fingerprint is:");
logevent(s->fingerprint);
}
* the session id, used in session key construction and
* authentication.
*/
- if (s->first_kex)
+ if (!s->got_session_id) {
memcpy(ssh->v2_session_id, s->exchange_hash,
sizeof(s->exchange_hash));
+ s->got_session_id = TRUE;
+ }
/*
* Send SSH2_MSG_NEWKEYS.
}
/*
- * Key exchange is over. Schedule a timer for our next rekey.
+ * Key exchange is over. Loop straight back round if we have a
+ * deferred rekey reason.
+ */
+ if (ssh->deferred_rekey_reason) {
+ logevent(ssh->deferred_rekey_reason);
+ pktin = NULL;
+ ssh->deferred_rekey_reason = NULL;
+ goto begin_key_exchange;
+ }
+
+ /*
+ * Otherwise, schedule a timer for our next rekey.
*/
ssh->kex_in_progress = FALSE;
ssh->last_rekey = GETTICKCOUNT();
if (ssh->cfg.ssh_rekey_time != 0)
ssh->next_rekey = schedule_timer(ssh->cfg.ssh_rekey_time*60*TICKSPERSEC,
ssh2_timer, ssh);
-
+
/*
* If this is the first key exchange phase, we must pass the
* SSH2_MSG_NEWKEYS packet to the next layer, not because it
* exchange phases, we don't pass SSH2_MSG_NEWKEYS on, because
* it would only confuse the layer above.
*/
- if (!s->first_kex) {
+ if (s->activated_authconn) {
crReturn(1);
}
- s->first_kex = 0;
+ s->activated_authconn = TRUE;
/*
* Now we're encrypting. Begin returning 1 to the protocol main
*/
while (!((pktin && pktin->type == SSH2_MSG_KEXINIT) ||
(!pktin && inlen == -1))) {
+ wait_for_rekey:
crReturn(1);
}
if (pktin) {
logevent("Server initiated key re-exchange");
} else {
- logevent((char *)in);
+ /*
+ * Special case: if the server bug is set that doesn't
+ * allow rekeying, we give a different log message and
+ * continue waiting. (If such a server _initiates_ a rekey,
+ * we process it anyway!)
+ */
+ if ((ssh->remote_bugs & BUG_SSH2_REKEY)) {
+ logeventf(ssh, "Server bug prevents key re-exchange (%s)",
+ (char *)in);
+ /* Reset the counters, so that at least this message doesn't
+ * hit the event log _too_ often. */
+ ssh->outgoing_data_size = 0;
+ ssh->incoming_data_size = 0;
+ if (ssh->cfg.ssh_rekey_time != 0) {
+ ssh->next_rekey =
+ schedule_timer(ssh->cfg.ssh_rekey_time*60*TICKSPERSEC,
+ ssh2_timer, ssh);
+ }
+ goto wait_for_rekey; /* this is utterly horrid */
+ } else {
+ logeventf(ssh, "Initiating key re-exchange (%s)", (char *)in);
+ }
}
goto begin_key_exchange;
ssh2_pkt_addstring(s->pktout, "en"); /* language tag */
ssh2_pkt_send_noqueue(ssh, s->pktout);
#endif
+ ssh->close_expected = TRUE;
ssh_closing((Plug)ssh, NULL, 0, 0);
}
}
ssh2_pkt_addstring(pktout, "en"); /* language tag */
ssh2_pkt_send_noqueue(ssh, pktout);
connection_fatal(ssh->frontend, "%s", buf);
+ ssh->close_expected = TRUE;
ssh_closing((Plug)ssh, NULL, 0, 0);
return;
}
ssh2_pkt_adduint32(pktout, c->remoteid);
ssh2_pkt_adduint32(pktout, c->localid);
ssh2_pkt_adduint32(pktout, c->v.v2.locwindow);
- ssh2_pkt_adduint32(pktout, 0x4000UL); /* our max pkt size */
+ ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */
ssh2_pkt_send(ssh, pktout);
}
}
AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET
} type;
int gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter;
- int tried_pubkey_config, tried_agent, tried_keyb_inter;
- int kbd_inter_running;
+ int tried_pubkey_config, tried_agent;
+ int kbd_inter_running, kbd_inter_refused;
int we_are_in;
int num_prompts, curr_prompt, echo;
char username[100];
* Terminate.
*/
logevent("No username provided. Abandoning session.");
+ ssh->close_expected = TRUE;
ssh_closing((Plug)ssh, NULL, 0, 0);
crStopV;
}
s->tried_pubkey_config = FALSE;
s->tried_agent = FALSE;
- s->tried_keyb_inter = FALSE;
s->kbd_inter_running = FALSE;
+ s->kbd_inter_refused = FALSE;
/* Load the pub half of ssh->cfg.keyfile so we notice if it's in Pageant */
if (!filename_is_null(ssh->cfg.keyfile)) {
int keytype;
*/
if (!s->gotit)
s->curr_prompt = 0;
+ } else if (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) {
+ /* FIXME: perhaps we should support this? */
+ bombout(("PASSWD_CHANGEREQ not yet supported"));
+ crStopV;
} else if (pktin->type != SSH2_MSG_USERAUTH_FAILURE) {
bombout(("Strange packet received during authentication: type %d",
pktin->type));
}
}
- if (!s->method && s->can_keyb_inter && !s->tried_keyb_inter) {
+ if (!s->method && s->can_keyb_inter && !s->kbd_inter_refused &&
+ !s->kbd_inter_running) {
s->method = AUTH_KEYBOARD_INTERACTIVE;
s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
- s->tried_keyb_inter = TRUE;
ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
ssh->pkt_ctx |= SSH2_PKTCTX_KBDINTER;
s->gotit = TRUE;
logevent("Keyboard-interactive authentication refused");
s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET;
+ s->kbd_inter_refused = TRUE; /* don't try it again */
continue;
}
if (s->kbd_inter_running) {
s->method = AUTH_KEYBOARD_INTERACTIVE;
s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
- s->tried_keyb_inter = TRUE;
ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
ssh->pkt_ctx |= SSH2_PKTCTX_KBDINTER;
logevent("Unable to authenticate");
connection_fatal(ssh->frontend,
"Unable to authenticate");
+ ssh->close_expected = TRUE;
ssh_closing((Plug)ssh, NULL, 0, 0);
crStopV;
}
" methods available");
ssh2_pkt_addstring(s->pktout, "en"); /* language tag */
ssh2_pkt_send_noqueue(ssh, s->pktout);
+ ssh->close_expected = TRUE;
ssh_closing((Plug)ssh, NULL, 0, 0);
crStopV;
}
ssh2_pkt_adduint32(s->pktout, ssh->mainchan->localid);
ssh->mainchan->v.v2.locwindow = OUR_V2_WINSIZE;
ssh2_pkt_adduint32(s->pktout, ssh->mainchan->v.v2.locwindow);/* our window size */
- ssh2_pkt_adduint32(s->pktout, 0x4000UL); /* our max pkt size */
+ ssh2_pkt_adduint32(s->pktout, OUR_V2_MAXPKT); /* our max pkt size */
ssh2_pkt_send(ssh, s->pktout);
crWaitUntilV(pktin);
if (pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
if (!ssh->kex_in_progress && ssh->cfg.ssh_rekey_time != 0 &&
now - ssh->next_rekey >= 0) {
- do_ssh2_transport(ssh, "Initiating key re-exchange (timeout)",
- -1, NULL);
+ do_ssh2_transport(ssh, "timeout", -1, NULL);
}
}
if (!ssh->kex_in_progress &&
ssh->max_data_size != 0 &&
ssh->incoming_data_size > ssh->max_data_size)
- do_ssh2_transport(ssh, "Initiating key re-exchange "
- "(too much data received)", -1, NULL);
+ do_ssh2_transport(ssh, "too much data received", -1, NULL);
}
if (pktin && ssh->packet_dispatch[pktin->type]) {
ssh->kex_ctx = NULL;
ssh->hostkey = NULL;
ssh->exitcode = -1;
+ ssh->close_expected = FALSE;
ssh->state = SSH_STATE_PREPACKET;
ssh->size_needed = FALSE;
ssh->eof_needed = FALSE;
ssh->queuelen = ssh->queuesize = 0;
ssh->queueing = FALSE;
ssh->qhead = ssh->qtail = NULL;
+ ssh->deferred_rekey_reason = NULL;
*backend_handle = ssh;
ssh->crcda_ctx = NULL;
}
if (ssh->s)
- ssh_do_close(ssh);
+ ssh_do_close(ssh, TRUE);
expire_timer_context(ssh);
if (ssh->pinger)
pinger_free(ssh->pinger);
static void ssh_reconfig(void *handle, Config *cfg)
{
Ssh ssh = (Ssh) handle;
- char *rekeying = NULL;
+ char *rekeying = NULL, rekey_mandatory = FALSE;
unsigned long old_max_data_size;
pinger_reconfig(ssh->pinger, &ssh->cfg, cfg);
long now = GETTICKCOUNT();
if (new_next - now < 0) {
- rekeying = "Initiating key re-exchange (timeout shortened)";
+ rekeying = "timeout shortened";
} else {
ssh->next_rekey = schedule_timer(new_next - now, ssh2_timer, ssh);
}
ssh->max_data_size != 0) {
if (ssh->outgoing_data_size > ssh->max_data_size ||
ssh->incoming_data_size > ssh->max_data_size)
- rekeying = "Initiating key re-exchange (data limit lowered)";
+ rekeying = "data limit lowered";
+ }
+
+ if (ssh->cfg.compression != cfg->compression) {
+ rekeying = "compression setting changed";
+ rekey_mandatory = TRUE;
}
- if (rekeying && !ssh->kex_in_progress) {
- do_ssh2_transport(ssh, rekeying, -1, NULL);
+ if (ssh->cfg.ssh2_des_cbc != cfg->ssh2_des_cbc ||
+ memcmp(ssh->cfg.ssh_cipherlist, cfg->ssh_cipherlist,
+ sizeof(ssh->cfg.ssh_cipherlist))) {
+ rekeying = "cipher settings changed";
+ rekey_mandatory = TRUE;
}
ssh->cfg = *cfg; /* STRUCTURE COPY */
+
+ if (rekeying) {
+ if (!ssh->kex_in_progress) {
+ do_ssh2_transport(ssh, rekeying, -1, NULL);
+ } else if (rekey_mandatory) {
+ ssh->deferred_rekey_reason = rekeying;
+ }
+ }
}
/*
}
} else if (code == TS_REKEY) {
if (!ssh->kex_in_progress && ssh->version == 2) {
- do_ssh2_transport(ssh, "Initiating key re-exchange at"
- " user request", -1, NULL);
+ do_ssh2_transport(ssh, "at user request", -1, NULL);
}
} else if (code == TS_BRK) {
if (ssh->state == SSH_STATE_CLOSED
ssh2_pkt_adduint32(pktout, c->localid);
c->v.v2.locwindow = OUR_V2_WINSIZE;
ssh2_pkt_adduint32(pktout, c->v.v2.locwindow);/* our window size */
- ssh2_pkt_adduint32(pktout, 0x4000UL); /* our max pkt size */
+ ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */
ssh2_pkt_addstring(pktout, hostname);
ssh2_pkt_adduint32(pktout, port);
/*