diff options
Diffstat (limited to 'sshverstring.c')
-rw-r--r-- | sshverstring.c | 628 |
1 files changed, 0 insertions, 628 deletions
diff --git a/sshverstring.c b/sshverstring.c deleted file mode 100644 index 8951e4cb..00000000 --- a/sshverstring.c +++ /dev/null @@ -1,628 +0,0 @@ -/* - * Code to handle the initial SSH version string exchange. - */ - -#include <assert.h> -#include <string.h> -#include <stdlib.h> - -#include "putty.h" -#include "ssh.h" -#include "sshbpp.h" -#include "sshcr.h" - -#define PREFIX_MAXLEN 64 - -struct ssh_verstring_state { - int crState; - - Conf *conf; - ptrlen prefix_wanted; - char *our_protoversion; - struct ssh_version_receiver *receiver; - - bool send_early; - - bool found_prefix; - int major_protoversion; - int remote_bugs; - char prefix[PREFIX_MAXLEN]; - char *impl_name; - strbuf *vstring; - char *protoversion; - const char *softwareversion; - - char *our_vstring; - int i; - - BinaryPacketProtocol bpp; -}; - -static void ssh_verstring_free(BinaryPacketProtocol *bpp); -static void ssh_verstring_handle_input(BinaryPacketProtocol *bpp); -static void ssh_verstring_handle_output(BinaryPacketProtocol *bpp); -static PktOut *ssh_verstring_new_pktout(int type); -static void ssh_verstring_queue_disconnect(BinaryPacketProtocol *bpp, - const char *msg, int category); - -static const BinaryPacketProtocolVtable ssh_verstring_vtable = { - .free = ssh_verstring_free, - .handle_input = ssh_verstring_handle_input, - .handle_output = ssh_verstring_handle_output, - .new_pktout = ssh_verstring_new_pktout, - .queue_disconnect = ssh_verstring_queue_disconnect, - .packet_size_limit = 0xFFFFFFFF, /* no special limit for this bpp */ -}; - -static void ssh_detect_bugs(struct ssh_verstring_state *s); -static bool ssh_version_includes_v1(const char *ver); -static bool ssh_version_includes_v2(const char *ver); - -BinaryPacketProtocol *ssh_verstring_new( - Conf *conf, LogContext *logctx, bool bare_connection_mode, - const char *protoversion, struct ssh_version_receiver *rcv, - bool server_mode, const char *impl_name) -{ - struct ssh_verstring_state *s = snew(struct ssh_verstring_state); - - memset(s, 0, sizeof(struct ssh_verstring_state)); - - if (!bare_connection_mode) { - s->prefix_wanted = PTRLEN_LITERAL("SSH-"); - } else { - /* - * Ordinary SSH begins with the banner "SSH-x.y-...". Here, - * we're going to be speaking just the ssh-connection - * subprotocol, extracted and given a trivial binary packet - * protocol, so we need a new banner. - * - * The new banner is like the ordinary SSH banner, but - * replaces the prefix 'SSH-' at the start with a new name. In - * proper SSH style (though of course this part of the proper - * SSH protocol _isn't_ subject to this kind of - * DNS-domain-based extension), we define the new name in our - * extension space. - */ - s->prefix_wanted = PTRLEN_LITERAL( - "SSHCONNECTION@putty.projects.tartarus.org-"); - } - assert(s->prefix_wanted.len <= PREFIX_MAXLEN); - - s->conf = conf_copy(conf); - s->bpp.logctx = logctx; - s->our_protoversion = dupstr(protoversion); - s->receiver = rcv; - s->impl_name = dupstr(impl_name); - s->vstring = strbuf_new(); - - /* - * We send our version string early if we can. But if it includes - * SSH-1, we can't, because we have to take the other end into - * account too (see below). - * - * In server mode, we do send early. - */ - s->send_early = server_mode || !ssh_version_includes_v1(protoversion); - - s->bpp.vt = &ssh_verstring_vtable; - ssh_bpp_common_setup(&s->bpp); - return &s->bpp; -} - -void ssh_verstring_free(BinaryPacketProtocol *bpp) -{ - struct ssh_verstring_state *s = - container_of(bpp, struct ssh_verstring_state, bpp); - conf_free(s->conf); - sfree(s->impl_name); - strbuf_free(s->vstring); - sfree(s->protoversion); - sfree(s->our_vstring); - sfree(s->our_protoversion); - sfree(s); -} - -static int ssh_versioncmp(const char *a, const char *b) -{ - char *ae, *be; - unsigned long av, bv; - - av = strtoul(a, &ae, 10); - bv = strtoul(b, &be, 10); - if (av != bv) - return (av < bv ? -1 : +1); - if (*ae == '.') - ae++; - if (*be == '.') - be++; - av = strtoul(ae, &ae, 10); - bv = strtoul(be, &be, 10); - if (av != bv) - return (av < bv ? -1 : +1); - return 0; -} - -static bool ssh_version_includes_v1(const char *ver) -{ - return ssh_versioncmp(ver, "2.0") < 0; -} - -static bool ssh_version_includes_v2(const char *ver) -{ - return ssh_versioncmp(ver, "1.99") >= 0; -} - -static void ssh_verstring_send(struct ssh_verstring_state *s) -{ - BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */ - char *p; - int sv_pos; - - /* - * Construct our outgoing version string. - */ - s->our_vstring = dupprintf( - "%.*s%s-%s%s", - (int)s->prefix_wanted.len, (const char *)s->prefix_wanted.ptr, - s->our_protoversion, s->impl_name, sshver); - sv_pos = s->prefix_wanted.len + strlen(s->our_protoversion) + 1; - - /* Convert minus signs and spaces in the software version string - * into underscores. */ - for (p = s->our_vstring + sv_pos; *p; p++) { - if (*p == '-' || *p == ' ') - *p = '_'; - } - -#ifdef FUZZING - /* - * Replace the first character of the string with an "I" if we're - * compiling this code for fuzzing - i.e. the protocol prefix - * becomes "ISH-" instead of "SSH-". - * - * This is irrelevant to any real client software (the only thing - * reading the output of PuTTY built for fuzzing is the fuzzer, - * which can adapt to whatever it sees anyway). But it's a safety - * precaution making it difficult to accidentally run such a - * version of PuTTY (which would be hugely insecure) against a - * live peer implementation. - * - * (So the replacement prefix "ISH" notionally stands for - * 'Insecure Shell', of course.) - */ - s->our_vstring[0] = 'I'; -#endif - - /* - * Now send that version string, plus trailing \r\n or just \n - * (the latter in SSH-1 mode). - */ - bufchain_add(s->bpp.out_raw, s->our_vstring, strlen(s->our_vstring)); - if (ssh_version_includes_v2(s->our_protoversion)) - bufchain_add(s->bpp.out_raw, "\015", 1); - bufchain_add(s->bpp.out_raw, "\012", 1); - - bpp_logevent("We claim version: %s", s->our_vstring); -} - -#define BPP_WAITFOR(minlen) do \ - { \ - bool success; \ - crMaybeWaitUntilV( \ - (success = (bufchain_size(s->bpp.in_raw) >= (minlen))) || \ - s->bpp.input_eof); \ - if (!success) \ - goto eof; \ - } while (0) - -void ssh_verstring_handle_input(BinaryPacketProtocol *bpp) -{ - struct ssh_verstring_state *s = - container_of(bpp, struct ssh_verstring_state, bpp); - - crBegin(s->crState); - - /* - * If we're sending our version string up front before seeing the - * other side's, then do it now. - */ - if (s->send_early) - ssh_verstring_send(s); - - /* - * Search for a line beginning with the protocol name prefix in - * the input. - */ - s->i = 0; - while (1) { - /* - * Every time round this loop, we're at the start of a new - * line, so look for the prefix. - */ - BPP_WAITFOR(s->prefix_wanted.len); - bufchain_fetch(s->bpp.in_raw, s->prefix, s->prefix_wanted.len); - if (!memcmp(s->prefix, s->prefix_wanted.ptr, s->prefix_wanted.len)) { - bufchain_consume(s->bpp.in_raw, s->prefix_wanted.len); - ssh_check_frozen(s->bpp.ssh); - break; - } - - /* - * If we didn't find it, consume data until we see a newline. - */ - while (1) { - ptrlen data; - char *nl; - - /* Wait to receive at least 1 byte, but then consume more - * than that if it's there. */ - BPP_WAITFOR(1); - data = bufchain_prefix(s->bpp.in_raw); - if ((nl = memchr(data.ptr, '\012', data.len)) != NULL) { - bufchain_consume(s->bpp.in_raw, nl - (char *)data.ptr + 1); - ssh_check_frozen(s->bpp.ssh); - break; - } else { - bufchain_consume(s->bpp.in_raw, data.len); - ssh_check_frozen(s->bpp.ssh); - } - } - } - - s->found_prefix = true; - - /* - * Copy the greeting line so far into vstring. - */ - put_data(s->vstring, s->prefix_wanted.ptr, s->prefix_wanted.len); - - /* - * Now read the rest of the greeting line. - */ - s->i = 0; - do { - ptrlen data; - char *nl; - - BPP_WAITFOR(1); - data = bufchain_prefix(s->bpp.in_raw); - if ((nl = memchr(data.ptr, '\012', data.len)) != NULL) { - data.len = nl - (char *)data.ptr + 1; - } - - put_datapl(s->vstring, data); - bufchain_consume(s->bpp.in_raw, data.len); - ssh_check_frozen(s->bpp.ssh); - - } while (s->vstring->s[s->vstring->len-1] != '\012'); - - /* - * Trim \r and \n from the version string, and replace them with - * a NUL terminator. - */ - while (s->vstring->len > 0 && - (s->vstring->s[s->vstring->len-1] == '\r' || - s->vstring->s[s->vstring->len-1] == '\n')) - strbuf_shrink_by(s->vstring, 1); - - bpp_logevent("Remote version: %s", s->vstring->s); - - /* - * Pick out the protocol version and software version. The former - * goes in a separately allocated string, so that s->vstring - * remains intact for later use in key exchange; the latter is the - * tail of s->vstring, so it doesn't need to be allocated. - */ - { - const char *pv_start = s->vstring->s + s->prefix_wanted.len; - int pv_len = strcspn(pv_start, "-"); - s->protoversion = dupprintf("%.*s", pv_len, pv_start); - s->softwareversion = pv_start + pv_len; - if (*s->softwareversion) { - assert(*s->softwareversion == '-'); - s->softwareversion++; - } - } - - ssh_detect_bugs(s); - - /* - * Figure out what actual SSH protocol version we're speaking. - */ - if (ssh_version_includes_v2(s->our_protoversion) && - ssh_version_includes_v2(s->protoversion)) { - /* - * We're doing SSH-2. - */ - s->major_protoversion = 2; - } else if (ssh_version_includes_v1(s->our_protoversion) && - ssh_version_includes_v1(s->protoversion)) { - /* - * We're doing SSH-1. - */ - s->major_protoversion = 1; - - /* - * There are multiple minor versions of SSH-1, and the - * protocol does not specify that the minimum of client - * and server versions is used. So we must adjust our - * outgoing protocol version to be no higher than that of - * the other side. - */ - if (!s->send_early && - ssh_versioncmp(s->our_protoversion, s->protoversion) > 0) { - sfree(s->our_protoversion); - s->our_protoversion = dupstr(s->protoversion); - } - } else { - /* - * Unable to agree on a major protocol version at all. - */ - if (!ssh_version_includes_v2(s->our_protoversion)) { - ssh_sw_abort(s->bpp.ssh, - "SSH protocol version 1 required by our " - "configuration but not provided by remote"); - } else { - ssh_sw_abort(s->bpp.ssh, - "SSH protocol version 2 required by our " - "configuration but remote only provides " - "(old, insecure) SSH-1"); - } - crStopV; - } - - bpp_logevent("Using SSH protocol version %d", s->major_protoversion); - - if (!s->send_early) { - /* - * If we didn't send our version string early, construct and - * send it now, because now we know what it is. - */ - ssh_verstring_send(s); - } - - /* - * And we're done. Notify our receiver that we now know our - * protocol version. This will cause it to disconnect us from the - * input stream and ultimately free us, because our job is now - * done. - */ - s->receiver->got_ssh_version(s->receiver, s->major_protoversion); - return; - - eof: - ssh_remote_error(s->bpp.ssh, - "Remote side unexpectedly closed network connection"); - return; /* avoid touching s now it's been freed */ - - crFinishV; -} - -static PktOut *ssh_verstring_new_pktout(int type) -{ - unreachable("Should never try to send packets during SSH version " - "string exchange"); -} - -static void ssh_verstring_handle_output(BinaryPacketProtocol *bpp) -{ - if (pq_peek(&bpp->out_pq)) { - unreachable("Should never try to send packets during SSH version " - "string exchange"); - } -} - -/* - * Examine the remote side's version string, and compare it against a - * list of known buggy implementations. - */ -static void ssh_detect_bugs(struct ssh_verstring_state *s) -{ - BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */ - const char *imp = s->softwareversion; - - s->remote_bugs = 0; - - /* - * General notes on server version strings: - * - Not all servers reporting "Cisco-1.25" have all the bugs listed - * here -- in particular, we've heard of one that's perfectly happy - * with SSH1_MSG_IGNOREs -- but this string never seems to change, - * so we can't distinguish them. - */ - if (conf_get_int(s->conf, CONF_sshbug_ignore1) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_ignore1) == AUTO && - (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") || - !strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") || - !strcmp(imp, "1.2.22") || !strcmp(imp, "Cisco-1.25") || - !strcmp(imp, "OSU_1.4alpha3") || !strcmp(imp, "OSU_1.5alpha4")))) { - /* - * These versions don't support SSH1_MSG_IGNORE, so we have - * to use a different defence against password length - * sniffing. - */ - s->remote_bugs |= BUG_CHOKES_ON_SSH1_IGNORE; - bpp_logevent("We believe remote version has SSH-1 ignore bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_plainpw1) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_plainpw1) == AUTO && - (!strcmp(imp, "Cisco-1.25") || !strcmp(imp, "OSU_1.4alpha3")))) { - /* - * These versions need a plain password sent; they can't - * handle having a null and a random length of data after - * the password. - */ - s->remote_bugs |= BUG_NEEDS_SSH1_PLAIN_PASSWORD; - bpp_logevent("We believe remote version needs a " - "plain SSH-1 password"); - } - - if (conf_get_int(s->conf, CONF_sshbug_rsa1) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_rsa1) == AUTO && - (!strcmp(imp, "Cisco-1.25")))) { - /* - * These versions apparently have no clue whatever about - * RSA authentication and will panic and die if they see - * an AUTH_RSA message. - */ - s->remote_bugs |= BUG_CHOKES_ON_RSA; - bpp_logevent("We believe remote version can't handle SSH-1 " - "RSA authentication"); - } - - if (conf_get_int(s->conf, CONF_sshbug_hmac2) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_hmac2) == AUTO && - !wc_match("* VShell", imp) && - (wc_match("2.1.0*", imp) || wc_match("2.0.*", imp) || - wc_match("2.2.0*", imp) || wc_match("2.3.0*", imp) || - wc_match("2.1 *", imp)))) { - /* - * These versions have the HMAC bug. - */ - s->remote_bugs |= BUG_SSH2_HMAC; - bpp_logevent("We believe remote version has SSH-2 HMAC bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_derivekey2) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_derivekey2) == AUTO && - !wc_match("* VShell", imp) && - (wc_match("2.0.0*", imp) || wc_match("2.0.10*", imp) ))) { - /* - * These versions have the key-derivation bug (failing to - * include the literal shared secret in the hashes that - * generate the keys). - */ - s->remote_bugs |= BUG_SSH2_DERIVEKEY; - bpp_logevent("We believe remote version has SSH-2 " - "key-derivation bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_rsapad2) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_rsapad2) == AUTO && - (wc_match("OpenSSH_2.[5-9]*", imp) || - wc_match("OpenSSH_3.[0-2]*", imp) || - wc_match("mod_sftp/0.[0-8]*", imp) || - wc_match("mod_sftp/0.9.[0-8]", imp)))) { - /* - * These versions have the SSH-2 RSA padding bug. - */ - s->remote_bugs |= BUG_SSH2_RSA_PADDING; - bpp_logevent("We believe remote version has SSH-2 RSA padding bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_pksessid2) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_pksessid2) == AUTO && - wc_match("OpenSSH_2.[0-2]*", imp))) { - /* - * These versions have the SSH-2 session-ID bug in - * public-key authentication. - */ - s->remote_bugs |= BUG_SSH2_PK_SESSIONID; - bpp_logevent("We believe remote version has SSH-2 " - "public-key-session-ID bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_rekey2) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_rekey2) == AUTO && - (wc_match("DigiSSH_2.0", imp) || - 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) || - /* All versions <= 1.2.6 (they changed their format in 1.2.7) */ - wc_match("WeOnlyDo-*", imp)))) { - /* - * These versions have the SSH-2 rekey bug. - */ - s->remote_bugs |= BUG_SSH2_REKEY; - bpp_logevent("We believe remote version has SSH-2 rekey bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_maxpkt2) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_maxpkt2) == AUTO && - (wc_match("1.36_sshlib GlobalSCAPE", imp) || - wc_match("1.36 sshlib: GlobalScape", imp)))) { - /* - * This version ignores our makpkt and needs to be throttled. - */ - s->remote_bugs |= BUG_SSH2_MAXPKT; - bpp_logevent("We believe remote version ignores SSH-2 " - "maximum packet size"); - } - - if (conf_get_int(s->conf, CONF_sshbug_ignore2) == FORCE_ON) { - /* - * Servers that don't support SSH2_MSG_IGNORE. Currently, - * none detected automatically. - */ - s->remote_bugs |= BUG_CHOKES_ON_SSH2_IGNORE; - bpp_logevent("We believe remote version has SSH-2 ignore bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_oldgex2) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_oldgex2) == AUTO && - (wc_match("OpenSSH_2.[235]*", imp)))) { - /* - * These versions only support the original (pre-RFC4419) - * SSH-2 GEX request, and disconnect with a protocol error if - * we use the newer version. - */ - s->remote_bugs |= BUG_SSH2_OLDGEX; - bpp_logevent("We believe remote version has outdated SSH-2 GEX"); - } - - if (conf_get_int(s->conf, CONF_sshbug_winadj) == FORCE_ON) { - /* - * Servers that don't support our winadj request for one - * reason or another. Currently, none detected automatically. - */ - s->remote_bugs |= BUG_CHOKES_ON_WINADJ; - bpp_logevent("We believe remote version has winadj bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_chanreq) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_chanreq) == AUTO && - (wc_match("OpenSSH_[2-5].*", imp) || - wc_match("OpenSSH_6.[0-6]*", imp) || - wc_match("dropbear_0.[2-4][0-9]*", imp) || - wc_match("dropbear_0.5[01]*", imp)))) { - /* - * These versions have the SSH-2 channel request bug. - * OpenSSH 6.7 and above do not: - * https://bugzilla.mindrot.org/show_bug.cgi?id=1818 - * dropbear_0.52 and above do not: - * https://secure.ucc.asn.au/hg/dropbear/rev/cd02449b709c - */ - s->remote_bugs |= BUG_SENDS_LATE_REQUEST_REPLY; - bpp_logevent("We believe remote version has SSH-2 " - "channel request bug"); - } -} - -const char *ssh_verstring_get_remote(BinaryPacketProtocol *bpp) -{ - struct ssh_verstring_state *s = - container_of(bpp, struct ssh_verstring_state, bpp); - return s->vstring->s; -} - -const char *ssh_verstring_get_local(BinaryPacketProtocol *bpp) -{ - struct ssh_verstring_state *s = - container_of(bpp, struct ssh_verstring_state, bpp); - return s->our_vstring; -} - -int ssh_verstring_get_bugs(BinaryPacketProtocol *bpp) -{ - struct ssh_verstring_state *s = - container_of(bpp, struct ssh_verstring_state, bpp); - return s->remote_bugs; -} - -static void ssh_verstring_queue_disconnect(BinaryPacketProtocol *bpp, - const char *msg, int category) -{ - /* No way to send disconnect messages at this stage of the protocol! */ -} |