*/
#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 };
int type;
unsigned sport, dport;
char *saddr, *daddr;
+ char *sserv, *dserv;
struct ssh_rportfwd *remote;
int addressfamily;
void *local;
};
#define free_portfwd(pf) ( \
- ((pf) ? (sfree((pf)->saddr), sfree((pf)->daddr)) : (void)0 ), sfree(pf) )
+ ((pf) ? (sfree((pf)->saddr), sfree((pf)->daddr), \
+ sfree((pf)->sserv), sfree((pf)->dserv)) : (void)0 ), sfree(pf) )
struct Packet {
long length;
+ long forcepad; /* Force padding to at least this length */
int type;
unsigned long sequence;
unsigned char *data;
struct logblank_t *blanks;
};
-static void ssh1_protocol(Ssh ssh, unsigned char *in, int inlen,
+static void ssh1_protocol(Ssh ssh, void *vin, int inlen,
struct Packet *pktin);
-static void ssh2_protocol(Ssh ssh, unsigned char *in, int inlen,
+static void ssh2_protocol(Ssh ssh, void *vin, int inlen,
struct Packet *pktin);
static void ssh1_protocol_setup(Ssh ssh);
static void ssh2_protocol_setup(Ssh ssh);
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);
static void ssh2_timer(void *ctx, long now);
-static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen,
+static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
struct Packet *pktin);
struct rdpkt1_state_tag {
tree234 *channels; /* indexed by local id */
struct ssh_channel *mainchan; /* primary session channel */
int exitcode;
+ int close_expected;
tree234 *rportfwds, *portfwds;
int overall_bufsize;
int throttled_all;
int v1_stdout_throttling;
- int v2_outgoing_sequence;
+ unsigned long v2_outgoing_sequence;
int ssh1_rdpkt_crstate;
int ssh2_rdpkt_crstate;
/* ssh1 and ssh2 use this for different things, but both use it */
int protocol_initial_phase_done;
- void (*protocol) (Ssh ssh, unsigned char *in, int inlen,
+ void (*protocol) (Ssh ssh, void *vin, int inlen,
struct Packet *pkt);
struct Packet *(*s_rdpkt) (Ssh ssh, unsigned char **data, int *datalen);
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); \
{
struct Packet *pkt = ssh_new_packet();
pkt->length = 5;
+ pkt->forcepad = 0;
ssh2_pkt_addbyte(pkt, (unsigned char) pkt_type);
return pkt;
}
/*
* Add padding. At least four bytes, and must also bring total
* length (minus MAC) up to a multiple of the block size.
+ * If pkt->forcepad is set, make sure the packet is at least that size
+ * after padding.
*/
cipherblk = ssh->cscipher ? ssh->cscipher->blksize : 8; /* block size */
cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */
padding = 4;
+ if (pkt->length + padding < pkt->forcepad)
+ padding = pkt->forcepad - pkt->length;
padding +=
(cipherblk - (pkt->length + padding) % cipherblk) % cipherblk;
+ assert(padding <= 255);
maclen = ssh->csmac ? ssh->csmac->len : 0;
ssh2_pkt_ensure(pkt, pkt->length + padding + maclen);
pkt->data[4] = padding;
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);
}
ssh2_pkt_send_noqueue(ssh, pkt);
}
+#if 0 /* disused */
/*
* Either queue or defer a packet, depending on whether queueing is
* set.
else
ssh2_pkt_defer_noqueue(ssh, pkt);
}
+#endif
/*
* Send the whole deferred data block constructed by
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 void ssh_log(Plug plug, int type, SockAddr addr, int port,
+ const char *error_msg, int error_code)
+{
+ Ssh ssh = (Ssh) plug;
+ char addrbuf[256], *msg;
+
+ sk_getaddr(addr, addrbuf, lenof(addrbuf));
+
+ if (type == 0)
+ msg = dupprintf("Connecting to %s port %d", addrbuf, port);
+ else
+ msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg);
+
+ logevent(msg);
}
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;
char **realhost, int nodelay, int keepalive)
{
static const struct plug_function_table fn_table = {
+ ssh_log,
ssh_closing,
ssh_receive,
ssh_sent,
/*
* Open socket.
*/
- {
- char addrbuf[100];
- sk_getaddr(addr, addrbuf, 100);
- logeventf(ssh, "Connecting to %s port %d", addrbuf, port);
- }
ssh->fn = &fn_table;
ssh->s = new_connection(addr, *realhost, port,
0, 1, nodelay, keepalive, (Plug) ssh, &ssh->cfg);
}
/* 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);
}
pfrec = snew(struct ssh_portfwd);
pfrec->type = type;
pfrec->saddr = *saddr ? dupstr(saddr) : NULL;
+ pfrec->sserv = sserv ? dupstr(sports) : NULL;
pfrec->sport = sport;
pfrec->daddr = *host ? dupstr(host) : NULL;
+ pfrec->dserv = dserv ? dupstr(dports) : NULL;
pfrec->dport = dport;
pfrec->local = NULL;
pfrec->remote = NULL;
for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++)
if (epf->status == CREATE) {
char *sportdesc, *dportdesc;
- sportdesc = dupprintf("%s%s%d",
+ sportdesc = dupprintf("%s%s%s%s%d%s",
epf->saddr ? epf->saddr : "",
epf->saddr ? ":" : "",
- epf->sport);
+ epf->sserv ? epf->sserv : "",
+ epf->sserv ? "(" : "",
+ epf->sport,
+ epf->sserv ? ")" : "");
if (epf->type == 'D') {
dportdesc = NULL;
} else {
- dportdesc = dupprintf("%s:%d", epf->daddr, epf->dport);
+ dportdesc = dupprintf("%s:%s%s%d%s",
+ epf->daddr,
+ epf->dserv ? epf->dserv : "",
+ epf->dserv ? "(" : "",
+ epf->dport,
+ epf->dserv ? ")" : "");
}
if (epf->type == 'L') {
/* Data sent down one of our channels. */
int i = ssh_pkt_getuint32(pktin);
char *p;
- unsigned int len;
+ int len;
struct ssh_channel *c;
ssh_pkt_getstring(pktin, &p, &len);
* encrypted packet, we close the session once
* we've sent EXIT_CONFIRMATION.
*/
+ ssh->close_expected = TRUE;
ssh_closing((Plug)ssh, NULL, 0, 0);
}
ssh->packet_dispatch[SSH1_MSG_DEBUG] = ssh1_msg_debug;
}
-static void ssh1_protocol(Ssh ssh, unsigned char *in, int inlen,
+static void ssh1_protocol(Ssh ssh, void *vin, int inlen,
struct Packet *pktin)
{
+ unsigned char *in=(unsigned char*)vin;
if (ssh->state == SSH_STATE_CLOSED)
return;
}
/*
+ * Similar routine for checking whether we have the first string in a list.
+ */
+static int first_in_commasep_string(char *needle, char *haystack, int haylen)
+{
+ int needlen;
+ if (!needle || !haystack) /* protect against null pointers */
+ return 0;
+ needlen = strlen(needle);
+ /*
+ * Is it at the start of the string?
+ */
+ if (haylen >= needlen && /* haystack is long enough */
+ !memcmp(needle, haystack, needlen) && /* initial match */
+ (haylen == needlen || haystack[needlen] == ',')
+ /* either , or EOS follows */
+ )
+ return 1;
+ return 0;
+}
+
+
+/*
* SSH2 key creation method.
*/
static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H,
/*
* Handle the SSH2 transport layer.
*/
-static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen,
+static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
struct Packet *pktin)
{
+ unsigned char *in = (unsigned char *)vin;
struct do_ssh2_transport_state {
int nbits, pbits, warn;
Bignum p, g, e, f, K;
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
*/
{
char *str;
- int i, j, len;
+ int i, j, len, guessok;
if (pktin->type != SSH2_MSG_KEXINIT) {
bombout(("expected key exchange packet from server"));
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;
}
}
str ? str : "(null)"));
crStop(0);
}
+ /*
+ * Note that the server's guess is considered wrong if it doesn't match
+ * the first algorithm in our list, even if it's still the algorithm
+ * we end up using.
+ */
+ guessok =
+ first_in_commasep_string(s->preferred_kex[0]->name, str, len);
ssh_pkt_getstring(pktin, &str, &len); /* host key algorithms */
for (i = 0; i < lenof(hostkey_algs); i++) {
if (in_commasep_string(hostkey_algs[i]->name, str, len)) {
break;
}
}
+ guessok = guessok &&
+ first_in_commasep_string(hostkey_algs[0]->name, str, len);
ssh_pkt_getstring(pktin, &str, &len); /* client->server cipher */
s->warn = 0;
for (i = 0; i < s->n_preferred_ciphers; i++) {
}
}
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;
}
}
break;
}
}
+ ssh_pkt_getstring(pktin, &str, &len); /* client->server language */
+ ssh_pkt_getstring(pktin, &str, &len); /* server->client language */
+ if (ssh2_pkt_getbool(pktin) && !guessok) /* first_kex_packet_follows */
+ crWaitUntil(pktin); /* Ignore packet */
}
/*
*/
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;
if (c->closes != 0)
return;
- if (newwin > c->v.v2.locwindow) {
+ /*
+ * Only send a WINDOW_ADJUST if there's significantly more window
+ * available than the other end thinks there is. This saves us
+ * sending a WINDOW_ADJUST for every character in a shell session.
+ *
+ * "Significant" is arbitrarily defined as half the window size.
+ */
+ if (newwin > c->v.v2.locwindow * 2) {
struct Packet *pktout;
pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
{
char *data;
- unsigned int length;
+ int length;
unsigned i = ssh_pkt_getuint32(pktin);
struct ssh_channel *c;
c = find234(ssh->channels, &i, ssh_channelfind);
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;
}
if (q >= 0 && q+4 <= len) { \
q = q + 4 + GET_32BIT(p+q); \
if (q >= 0 && q+4 <= len && \
- (q = q + 4 + GET_32BIT(p+q)) && q == len) \
+ ((q = q + 4 + GET_32BIT(p+q))!= 0) && q == len) \
result = TRUE; \
} \
} while(0)
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;
}
}
} else if (s->method == AUTH_PASSWORD) {
/*
- * We send the password packet lumped tightly together with
- * an SSH_MSG_IGNORE packet. The IGNORE packet contains a
- * string long enough to make the total length of the two
- * packets constant. This should ensure that a passive
- * listener doing traffic analyis can't work out the length
- * of the password.
+ * We pad out the password packet to 256 bytes to make
+ * it harder for an attacker to find the length of the
+ * user's password.
*
- * For this to work, we need an assumption about the
- * maximum length of the password packet. I think 256 is
- * pretty conservative. Anyone using a password longer than
- * that probably doesn't have much to worry about from
+ * Anyone using a password longer than 256 bytes
+ * probably doesn't have much to worry about from
* people who find out how long their password is!
*/
s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
+ s->pktout->forcepad = 256;
ssh2_pkt_addstring(s->pktout, s->username);
ssh2_pkt_addstring(s->pktout, "ssh-connection"); /* service requested */
ssh2_pkt_addstring(s->pktout, "password");
ssh2_pkt_addstring(s->pktout, s->password);
memset(s->password, 0, sizeof(s->password));
end_log_omission(ssh, s->pktout);
- ssh2_pkt_defer(ssh, s->pktout);
- /*
- * We'll include a string that's an exact multiple of the
- * cipher block size. If the cipher is NULL for some
- * reason, we don't do this trick at all because we gain
- * nothing by it.
- */
- if (ssh->cscipher) {
- int stringlen, i;
-
- stringlen = (256 - ssh->deferred_len);
- stringlen += ssh->cscipher->blksize - 1;
- stringlen -= (stringlen % ssh->cscipher->blksize);
- if (ssh->cscomp) {
- /*
- * Temporarily disable actual compression,
- * so we can guarantee to get this string
- * exactly the length we want it. The
- * compression-disabling routine should
- * return an integer indicating how many
- * bytes we should adjust our string length
- * by.
- */
- stringlen -=
- ssh->cscomp->disable_compression(ssh->cs_comp_ctx);
- }
- s->pktout = ssh2_pkt_init(SSH2_MSG_IGNORE);
- ssh2_pkt_addstring_start(s->pktout);
- for (i = 0; i < stringlen; i++) {
- char c = (char) random_byte();
- ssh2_pkt_addstring_data(s->pktout, &c, 1);
- }
- ssh2_pkt_defer(ssh, s->pktout);
- }
- ssh_pkt_defersend(ssh);
+ ssh2_pkt_send(ssh, s->pktout);
logevent("Sent password");
s->type = AUTH_TYPE_PASSWORD;
} else if (s->method == AUTH_KEYBOARD_INTERACTIVE) {
if (s->curr_prompt == 0) {
s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE);
+ s->pktout->forcepad = 256;
ssh2_pkt_adduint32(s->pktout, s->num_prompts);
}
if (s->need_pw) { /* only add pw if we just got one! */
" 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);
}
}
-static void ssh2_protocol(Ssh ssh, unsigned char *in, int inlen,
+static void ssh2_protocol(Ssh ssh, void *vin, int inlen,
struct Packet *pktin)
{
+ unsigned char *in = (unsigned char *)vin;
if (ssh->state == SSH_STATE_CLOSED)
return;
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);
/*