*/
#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
* Hence, in SSH 1 this structure is indexed by destination
* host:port pair, whereas in SSH 2 it is indexed by source port.
*/
+struct ssh_portfwd; /* forward declaration */
+
struct ssh_rportfwd {
unsigned sport, dport;
char dhost[256];
char *sportdesc;
+ struct ssh_portfwd *pfrec;
+};
+#define free_rportfwd(pf) ( \
+ ((pf) ? (sfree((pf)->sportdesc)) : (void)0 ), sfree(pf) )
+
+/*
+ * Separately to the rportfwd tree (which is for looking up port
+ * open requests from the server), a tree of _these_ structures is
+ * used to keep track of all the currently open port forwardings,
+ * so that we can reconfigure in mid-session if the user requests
+ * it.
+ */
+struct ssh_portfwd {
+ enum { DESTROY, KEEP, CREATE } status;
+ 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), \
+ sfree((pf)->sserv), sfree((pf)->dserv)) : (void)0 ), sfree(pf) )
struct Packet {
long length;
struct ssh_channel *mainchan; /* primary session channel */
int exitcode;
- tree234 *rportfwds;
+ tree234 *rportfwds, *portfwds;
enum {
SSH_STATE_PREPACKET,
unsigned long incoming_data_size, outgoing_data_size, deferred_data_size;
unsigned long max_data_size;
int kex_in_progress;
- long next_rekey;
+ long next_rekey, last_rekey;
+ char *deferred_rekey_reason; /* points to STATIC string; don't free */
};
#define logevent(s) logevent(ssh->frontend, s)
return 0;
}
+/*
+ * Special form of strcmp which can cope with NULL inputs. NULL is
+ * defined to sort before even the empty string.
+ */
+static int nullstrcmp(const char *a, const char *b)
+{
+ if (a == NULL && b == NULL)
+ return 0;
+ if (a == NULL)
+ return -1;
+ if (b == NULL)
+ return +1;
+ return strcmp(a, b);
+}
+
+static int ssh_portcmp(void *av, void *bv)
+{
+ struct ssh_portfwd *a = (struct ssh_portfwd *) av;
+ struct ssh_portfwd *b = (struct ssh_portfwd *) bv;
+ int i;
+ if (a->type > b->type)
+ return +1;
+ if (a->type < b->type)
+ return -1;
+ if (a->addressfamily > b->addressfamily)
+ return +1;
+ if (a->addressfamily < b->addressfamily)
+ return -1;
+ if ( (i = nullstrcmp(a->saddr, b->saddr)) != 0)
+ return i < 0 ? -1 : +1;
+ if (a->sport > b->sport)
+ return +1;
+ if (a->sport < b->sport)
+ return -1;
+ if (a->type != 'D') {
+ if ( (i = nullstrcmp(a->daddr, b->daddr)) != 0)
+ return i < 0 ? -1 : +1;
+ if (a->dport > b->dport)
+ return +1;
+ if (a->dport < b->dport)
+ return -1;
+ }
+ return 0;
+}
+
static int alloc_channel_id(Ssh ssh)
{
const unsigned CHANNEL_NUMBER_OFFSET = 256;
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("Sun_SSH_1.0", imp))) {
+ /*
+ * These versions have the SSH2 ignore-rekey bug.
+ */
+ ssh->remote_bugs |= BUG_SSH2_REKEY;
+ logevent("We believe remote version has SSH2 ignore-rekey bug");
+ }
}
/*
/*
* Try to find host.
*/
- logeventf(ssh, "Looking up host \"%s\"", host);
- addr = name_lookup(host, port, realhost, &ssh->cfg);
+ logeventf(ssh, "Looking up host \"%s\"%s", host,
+ (ssh->cfg.addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" :
+ (ssh->cfg.addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : "")));
+ addr = name_lookup(host, port, realhost, &ssh->cfg,
+ ssh->cfg.addressfamily);
if ((err = sk_addr_error(addr)) != NULL) {
sk_addr_free(addr);
return err;
}
/* 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) {
rpf = del234(ssh->rportfwds, pf);
assert(rpf == pf);
- sfree(pf->sportdesc);
- sfree(pf);
+ free_rportfwd(pf);
}
}
static void ssh_setup_portfwd(Ssh ssh, const Config *cfg)
{
- char type;
- int n;
- int sport,dport,sserv,dserv;
- char sports[256], dports[256], saddr[256], host[256];
- const char *portfwd_strptr;
+ const char *portfwd_strptr = cfg->portfwd;
+ struct ssh_portfwd *epf;
+ int i;
- portfwd_strptr = cfg->portfwd;
+ if (!ssh->portfwds) {
+ ssh->portfwds = newtree234(ssh_portcmp);
+ } else {
+ /*
+ * Go through the existing port forwardings and tag them
+ * with status==DESTROY. Any that we want to keep will be
+ * re-enabled (status==KEEP) as we go through the
+ * configuration and find out which bits are the same as
+ * they were before.
+ */
+ struct ssh_portfwd *epf;
+ int i;
+ for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++)
+ epf->status = DESTROY;
+ }
while (*portfwd_strptr) {
- type = *portfwd_strptr++;
+ char address_family, type;
+ int sport,dport,sserv,dserv;
+ char sports[256], dports[256], saddr[256], host[256];
+ int n;
+
+ address_family = 'A';
+ type = 'L';
+ if (*portfwd_strptr == 'A' ||
+ *portfwd_strptr == '4' ||
+ *portfwd_strptr == '6')
+ address_family = *portfwd_strptr++;
+ if (*portfwd_strptr == 'L' ||
+ *portfwd_strptr == 'R' ||
+ *portfwd_strptr == 'D')
+ type = *portfwd_strptr++;
+
saddr[0] = '\0';
+
n = 0;
while (*portfwd_strptr && *portfwd_strptr != '\t') {
if (*portfwd_strptr == ':') {
}
if (sport && dport) {
/* Set up a description of the source port. */
- static char *sportdesc;
- sportdesc = dupprintf("%.*s%.*s%.*s%.*s%d%.*s",
- (int)(*saddr?strlen(saddr):0), *saddr?saddr:NULL,
- (int)(*saddr?1:0), ":",
- (int)(sserv ? strlen(sports) : 0), sports,
- sserv, "(", sport, sserv, ")");
- if (type == 'L') {
- /* Verbose description of the destination port */
- char *dportdesc = dupprintf("%s:%.*s%.*s%d%.*s",
- host,
- (int)(dserv ? strlen(dports) : 0), dports,
- dserv, "(", dport, dserv, ")");
- const char *err = pfd_addforward(host, dport,
- *saddr ? saddr : NULL,
- sport, ssh, &ssh->cfg);
- if (err) {
- logeventf(ssh, "Local port %s forward to %s"
- " failed: %s", sportdesc, dportdesc, err);
+ struct ssh_portfwd *pfrec, *epfrec;
+
+ 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;
+ pfrec->addressfamily = (address_family == '4' ? ADDRTYPE_IPV4 :
+ address_family == '6' ? ADDRTYPE_IPV6 :
+ ADDRTYPE_UNSPEC);
+
+ epfrec = add234(ssh->portfwds, pfrec);
+ if (epfrec != pfrec) {
+ /*
+ * We already have a port forwarding with precisely
+ * these parameters. Hence, no need to do anything;
+ * simply tag the existing one as KEEP.
+ */
+ epfrec->status = KEEP;
+ free_portfwd(pfrec);
+ } else {
+ pfrec->status = CREATE;
+ }
+ }
+ }
+
+ /*
+ * Now go through and destroy any port forwardings which were
+ * not re-enabled.
+ */
+ for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++)
+ if (epf->status == DESTROY) {
+ char *message;
+
+ message = dupprintf("%s port forwarding from %s%s%d",
+ epf->type == 'L' ? "local" :
+ epf->type == 'R' ? "remote" : "dynamic",
+ epf->saddr ? epf->saddr : "",
+ epf->saddr ? ":" : "",
+ epf->sport);
+
+ if (epf->type != 'D') {
+ char *msg2 = dupprintf("%s to %s:%d", message,
+ epf->daddr, epf->dport);
+ sfree(message);
+ message = msg2;
+ }
+
+ logeventf(ssh, "Cancelling %s", message);
+ sfree(message);
+
+ if (epf->remote) {
+ struct ssh_rportfwd *rpf = epf->remote;
+ struct Packet *pktout;
+
+ /*
+ * Cancel the port forwarding at the server
+ * end.
+ */
+ if (ssh->version == 1) {
+ /*
+ * We cannot cancel listening ports on the
+ * server side in SSH1! There's no message
+ * to support it. Instead, we simply remove
+ * the rportfwd record from the local end
+ * so that any connections the server tries
+ * to make on it are rejected.
+ */
} else {
- logeventf(ssh, "Local port %s forwarding to %s",
- sportdesc, dportdesc);
+ pktout = ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST);
+ ssh2_pkt_addstring(pktout, "cancel-tcpip-forward");
+ ssh2_pkt_addbool(pktout, 0);/* _don't_ want reply */
+ if (epf->saddr) {
+ ssh2_pkt_addstring(pktout, epf->saddr);
+ } else if (ssh->cfg.rport_acceptall) {
+ ssh2_pkt_addstring(pktout, "0.0.0.0");
+ } else {
+ ssh2_pkt_addstring(pktout, "127.0.0.1");
+ }
+ ssh2_pkt_adduint32(pktout, epf->sport);
+ ssh2_pkt_send(ssh, pktout);
}
- sfree(dportdesc);
- } else if (type == 'D') {
+
+ del234(ssh->rportfwds, rpf);
+ free_rportfwd(rpf);
+ } else if (epf->local) {
+ pfd_terminate(epf->local);
+ }
+
+ delpos234(ssh->portfwds, i);
+ free_portfwd(epf);
+ i--; /* so we don't skip one in the list */
+ }
+
+ /*
+ * And finally, set up any new port forwardings (status==CREATE).
+ */
+ for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++)
+ if (epf->status == CREATE) {
+ char *sportdesc, *dportdesc;
+ sportdesc = dupprintf("%s%s%s%s%d%s",
+ epf->saddr ? epf->saddr : "",
+ epf->saddr ? ":" : "",
+ epf->sserv ? epf->sserv : "",
+ epf->sserv ? "(" : "",
+ epf->sport,
+ epf->sserv ? ")" : "");
+ if (epf->type == 'D') {
+ dportdesc = NULL;
+ } else {
+ dportdesc = dupprintf("%s:%s%s%d%s",
+ epf->daddr,
+ epf->dserv ? epf->dserv : "",
+ epf->dserv ? "(" : "",
+ epf->dport,
+ epf->dserv ? ")" : "");
+ }
+
+ if (epf->type == 'L') {
+ const char *err = pfd_addforward(epf->daddr, epf->dport,
+ epf->saddr, epf->sport,
+ ssh, &ssh->cfg,
+ &epf->local,
+ epf->addressfamily);
+
+ logeventf(ssh, "Local %sport %s forwarding to %s%s%s",
+ epf->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " :
+ epf->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "",
+ sportdesc, dportdesc,
+ err ? " failed: " : "", err ? err : "");
+ } else if (epf->type == 'D') {
const char *err = pfd_addforward(NULL, -1,
- *saddr ? saddr : NULL,
- sport, ssh, &ssh->cfg);
- if (err) {
- logeventf(ssh, "Local port %s SOCKS dynamic forward"
- " setup failed: %s", sportdesc, err);
- } else {
- logeventf(ssh, "Local port %s doing SOCKS"
- " dynamic forwarding", sportdesc);
- }
+ epf->saddr, epf->sport,
+ ssh, &ssh->cfg,
+ &epf->local,
+ epf->addressfamily);
+
+ logeventf(ssh, "Local %sport %s SOCKS dynamic forwarding%s%s",
+ epf->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " :
+ epf->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "",
+ sportdesc,
+ err ? " failed: " : "", err ? err : "");
} else {
struct ssh_rportfwd *pf;
}
pf = snew(struct ssh_rportfwd);
- strcpy(pf->dhost, host);
- pf->dport = dport;
- pf->sport = sport;
+ strncpy(pf->dhost, epf->daddr, lenof(pf->dhost)-1);
+ pf->dhost[lenof(pf->dhost)-1] = '\0';
+ pf->dport = epf->dport;
+ pf->sport = epf->sport;
if (add234(ssh->rportfwds, pf) != pf) {
logeventf(ssh, "Duplicate remote port forwarding to %s:%d",
- host, dport);
+ epf->daddr, epf->dport);
sfree(pf);
} else {
logeventf(ssh, "Requesting remote port %s"
- " forward to %s:%.*s%.*s%d%.*s",
- sportdesc, host,
- (int)(dserv ? strlen(dports) : 0), dports,
- dserv, "(", dport, dserv, ")");
+ " forward to %s", sportdesc, dportdesc);
pf->sportdesc = sportdesc;
sportdesc = NULL;
+ epf->remote = pf;
+ pf->pfrec = epf;
if (ssh->version == 1) {
send_packet(ssh, SSH1_CMSG_PORT_FORWARD_REQUEST,
- PKT_INT, sport,
- PKT_STR, host,
- PKT_INT, dport,
+ PKT_INT, epf->sport,
+ PKT_STR, epf->daddr,
+ PKT_INT, epf->dport,
PKT_END);
ssh_queue_handler(ssh, SSH1_SMSG_SUCCESS,
SSH1_SMSG_FAILURE,
pktout = ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST);
ssh2_pkt_addstring(pktout, "tcpip-forward");
ssh2_pkt_addbool(pktout, 1);/* want reply */
- if (*saddr) {
- ssh2_pkt_addstring(pktout, saddr);
+ if (epf->saddr) {
+ ssh2_pkt_addstring(pktout, epf->saddr);
} else if (ssh->cfg.rport_acceptall) {
ssh2_pkt_addstring(pktout, "0.0.0.0");
} else {
ssh2_pkt_addstring(pktout, "127.0.0.1");
}
- ssh2_pkt_adduint32(pktout, sport);
+ ssh2_pkt_adduint32(pktout, epf->sport);
ssh2_pkt_send(ssh, pktout);
ssh_queue_handler(ssh, SSH2_MSG_REQUEST_SUCCESS,
}
}
sfree(sportdesc);
+ sfree(dportdesc);
}
- }
}
static void ssh1_smsg_stdout_stderr_data(Ssh ssh, struct Packet *pktin)
/* Remote side is trying to open a channel to talk to a
* forwarded port. Give them back a local channel number. */
struct ssh_channel *c;
- struct ssh_rportfwd pf;
+ struct ssh_rportfwd pf, *pfp;
int remoteid;
int hostsize, port;
char *host, buf[1024];
memcpy(pf.dhost, host, hostsize);
pf.dhost[hostsize] = '\0';
pf.dport = port;
+ pfp = find234(ssh->rportfwds, &pf, NULL);
- if (find234(ssh->rportfwds, &pf, NULL) == NULL) {
+ if (pfp == NULL) {
sprintf(buf, "Rejected remote port open request for %s:%d",
pf.dhost, port);
logevent(buf);
pf.dhost, port);
logevent(buf);
e = pfd_newconnect(&c->u.pfd.s, pf.dhost, port,
- c, &ssh->cfg);
+ c, &ssh->cfg, pfp->pfrec->addressfamily);
if (e != NULL) {
char buf[256];
sprintf(buf, "Port open failed: %s", e);
/* Data sent down one of our channels. */
int i = ssh_pkt_getuint32(pktin);
char *p;
- int len;
+ unsigned int len;
struct ssh_channel *c;
ssh_pkt_getstring(pktin, &p, &len);
/* Data for an agent message. Buffer it. */
while (len > 0) {
if (c->u.a.lensofar < 4) {
- int l = min(4 - c->u.a.lensofar, len);
+ unsigned int l = min(4 - c->u.a.lensofar, len);
memcpy(c->u.a.msglen + c->u.a.lensofar, p,
l);
p += l;
memcpy(c->u.a.message, c->u.a.msglen, 4);
}
if (c->u.a.lensofar >= 4 && len > 0) {
- int l =
+ unsigned int l =
min(c->u.a.totallen - c->u.a.lensofar,
len);
memcpy(c->u.a.message + c->u.a.lensofar, p,
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);
+ logevent((char *)in);
+ }
}
goto begin_key_exchange;
static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
{
char *data;
- int length;
+ unsigned int length;
unsigned i = ssh_pkt_getuint32(pktin);
struct ssh_channel *c;
c = find234(ssh->channels, &i, ssh_channelfind);
case CHAN_AGENT:
while (length > 0) {
if (c->u.a.lensofar < 4) {
- int l = min(4 - c->u.a.lensofar, length);
+ unsigned int l = min(4 - c->u.a.lensofar, length);
memcpy(c->u.a.msglen + c->u.a.lensofar,
data, l);
data += l;
memcpy(c->u.a.message, c->u.a.msglen, 4);
}
if (c->u.a.lensofar >= 4 && length > 0) {
- int l =
+ unsigned int l =
min(c->u.a.totallen - c->u.a.lensofar,
length);
memcpy(c->u.a.message + c->u.a.lensofar,
const char *e = pfd_newconnect(&c->u.pfd.s,
realpf->dhost,
realpf->dport, c,
- &ssh->cfg);
+ &ssh->cfg,
+ realpf->pfrec->addressfamily);
logeventf(ssh, "Attempting to forward remote port to "
"%s:%d", realpf->dhost, realpf->dport);
if (e != NULL) {
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];
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;
{
Ssh ssh = (Ssh)ctx;
- if (!ssh->kex_in_progress &&
+ 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->queuelen = ssh->queuesize = 0;
ssh->queueing = FALSE;
ssh->qhead = ssh->qtail = NULL;
+ ssh->deferred_rekey_reason = NULL;
*backend_handle = ssh;
ssh->channels = NULL;
ssh->rportfwds = NULL;
+ ssh->portfwds = NULL;
ssh->send_ok = 0;
ssh->editing = 0;
/*
* Reconfigure the SSH backend.
- *
- * Currently, this function does nothing very useful. In future,
- * however, we could do some handy things with it. For example, we
- * could make the port forwarding configurer active in the Change
- * Settings box, and this routine could close down existing
- * forwardings and open up new ones in response to changes.
*/
static void ssh_reconfig(void *handle, Config *cfg)
{
Ssh ssh = (Ssh) handle;
+ char *rekeying = NULL, rekey_mandatory = FALSE;
+ unsigned long old_max_data_size;
+
pinger_reconfig(ssh->pinger, &ssh->cfg, cfg);
+ ssh_setup_portfwd(ssh, cfg);
+
+ if (ssh->cfg.ssh_rekey_time != cfg->ssh_rekey_time &&
+ cfg->ssh_rekey_time != 0) {
+ long new_next = ssh->last_rekey + cfg->ssh_rekey_time*60*TICKSPERSEC;
+ long now = GETTICKCOUNT();
+
+ if (new_next - now < 0) {
+ rekeying = "timeout shortened";
+ } else {
+ ssh->next_rekey = schedule_timer(new_next - now, ssh2_timer, ssh);
+ }
+ }
+
+ old_max_data_size = ssh->max_data_size;
+ ssh->max_data_size = parse_blocksize(cfg->ssh_rekey_data);
+ if (old_max_data_size != ssh->max_data_size &&
+ ssh->max_data_size != 0) {
+ if (ssh->outgoing_data_size > ssh->max_data_size ||
+ ssh->incoming_data_size > ssh->max_data_size)
+ rekeying = "data limit lowered";
+ }
+
+ if (ssh->cfg.compression != cfg->compression) {
+ rekeying = "compression setting changed";
+ rekey_mandatory = TRUE;
+ }
+
+ 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
PKT_INT, c->localid,
PKT_STR, hostname,
PKT_INT, port,
- //PKT_STR, <org:orgport>,
+ /* PKT_STR, <org:orgport>, */
PKT_END);
} else {
pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
}
/*
+ * cfg_info for SSH is the currently running version of the
+ * protocol. (1 for 1; 2 for 2; 0 for not-decided-yet.)
+ */
+static int ssh_cfg_info(void *handle)
+{
+ Ssh ssh = (Ssh) handle;
+ return ssh->version;
+}
+
+/*
* Gross hack: pscp will try to start SFTP but fall back to scp1 if
* that fails. This variable is the means by which scp.c can reach
* into the SSH code and find out which one it got.
ssh_provide_ldisc,
ssh_provide_logctx,
ssh_unthrottle,
+ ssh_cfg_info,
22
};