浏览代码

PuTTY snapshot 242c0746 (Move low-level functions out into sshcommon.c - 2018-09-19)

Source commit: 84cf12647196ab02f3a0dd0327c25a7838b11251
Martin Prikryl 6 年之前
父节点
当前提交
59b8d4707c
共有 10 个文件被更改,包括 1223 次插入890 次删除
  1. 33 0
      source/putty/misc.c
  2. 15 0
      source/putty/misc.h
  3. 0 2
      source/putty/putty.h
  4. 81 786
      source/putty/ssh.c
  5. 62 12
      source/putty/ssh.h
  6. 18 1
      source/putty/sshbpp.h
  7. 161 0
      source/putty/sshcommon.c
  8. 602 0
      source/putty/sshverstring.c
  9. 216 89
      source/putty/tree234.c
  10. 35 0
      source/putty/tree234.h

+ 33 - 0
source/putty/misc.c

@@ -337,6 +337,12 @@ void burnstr(char *string)             /* sfree(str), only clear it first */
     }
 }
 
+void logevent_and_free(Frontend *frontend, char *s)
+{
+    logevent(frontend, s);
+    sfree(s);
+}
+
 int toint(unsigned u)
 {
     /*
@@ -820,6 +826,33 @@ int bufchain_try_fetch_consume(bufchain *ch, void *data, int len)
     }
 }
 
+/* ----------------------------------------------------------------------
+ * Sanitise terminal output that we have reason not to trust, e.g.
+ * because it appears in the login banner or password prompt from a
+ * server, which we'd rather not permit to use arbitrary escape
+ * sequences.
+ */
+
+void sanitise_term_data(bufchain *out, const void *vdata, int len)
+{
+    const char *data = (const char *)vdata;
+    int i;
+
+    /*
+     * FIXME: this method of sanitisation is ASCII-centric. It would
+     * be nice to permit SSH banners and the like to contain printable
+     * Unicode, but that would need a lot more complicated code here
+     * (not to mention knowing what character set it should interpret
+     * the data as).
+     */
+    for (i = 0; i < len; i++) {
+        if (data[i] == '\n')
+            bufchain_add(out, "\r\n", 2);
+        else if (data[i] >= ' ' && data[i] < 0x7F)
+            bufchain_add(out, data + i, 1);
+    }
+}
+
 /* ----------------------------------------------------------------------
  * My own versions of malloc, realloc and free. Because I want
  * malloc and realloc to bomb out and exit the program if they run

+ 15 - 0
source/putty/misc.h

@@ -31,6 +31,13 @@ char *dupprintf(const char *fmt, ...)
 char *dupvprintf(const char *fmt, va_list ap);
 void burnstr(char *string);
 
+/*
+ * Pass a dynamically allocated string to logevent and immediately
+ * free it. Intended for use by wrapper macros which pass the return
+ * value of dupprintf straight to this.
+ */
+void logevent_and_free(Frontend *frontend, char *msg);
+
 struct strbuf {
     char *s;
     unsigned char *u;
@@ -83,6 +90,8 @@ void bufchain_fetch(bufchain *ch, void *data, int len);
 void bufchain_fetch_consume(bufchain *ch, void *data, int len);
 int bufchain_try_fetch_consume(bufchain *ch, void *data, int len);
 
+void sanitise_term_data(bufchain *out, const void *vdata, int len);
+
 int validate_manual_hostkey(char *key);
 
 struct tm ltime(void);
@@ -100,6 +109,12 @@ int string_length_for_printf(size_t);
 /* Derive two printf arguments from a ptrlen, suitable for "%.*s" */
 #define PTRLEN_PRINTF(pl) \
     string_length_for_printf((pl).len), (const char *)(pl).ptr
+/* Make a ptrlen out of a compile-time string literal. We try to
+ * enforce that it _is_ a string literal by token-pasting "" on to it,
+ * which should provoke a compile error if it's any other kind of
+ * string. */
+#define PTRLEN_LITERAL(stringlit) \
+    TYPECHECK("" stringlit "", make_ptrlen(stringlit, sizeof(stringlit)-1))
 
 /* Wipe sensitive data out of memory that's about to be freed. Simpler
  * than memset because we don't need the fill char parameter; also

+ 0 - 2
source/putty/putty.h

@@ -712,7 +712,6 @@ void frontend_echoedit_update(Frontend *frontend, int echo, int edit);
  * shutdown. */
 void update_specials_menu(Frontend *frontend);
 int from_backend(Frontend *frontend, int is_stderr, const void *data, int len);
-int from_backend_untrusted(Frontend *frontend, const void *data, int len);
 /* Called when the back end wants to indicate that EOF has arrived on
  * the server-to-client stream. Returns FALSE to indicate that we
  * intend to keep the session open in the other direction, or TRUE to
@@ -1130,7 +1129,6 @@ void term_request_copy(Terminal *, const int *clipboards, int n_clipboards);
 void term_request_paste(Terminal *, int clipboard);
 void term_seen_key_event(Terminal *); 
 int term_data(Terminal *, int is_stderr, const void *data, int len);
-int term_data_untrusted(Terminal *, const void *data, int len);
 void term_provide_backend(Terminal *term, Backend *backend);
 void term_provide_logctx(Terminal *term, LogContext *logctx);
 void term_set_focus(Terminal *term, int has_focus);

文件差异内容过多而无法显示
+ 81 - 786
source/putty/ssh.c


+ 62 - 12
source/putty/ssh.h

@@ -81,20 +81,46 @@ typedef struct PktOut {
     unsigned downstream_id;
     const char *additional_log_text;
 
+    PacketQueueNode qnode;  /* for linking this packet on to a queue */
     BinarySink_IMPLEMENTATION;
 } PktOut;
 
-typedef struct PacketQueue {
+typedef struct PacketQueueBase {
     PacketQueueNode end;
-} PacketQueue;
-
-void pq_init(struct PacketQueue *pq);
-void pq_push(struct PacketQueue *pq, PktIn *pkt);
-void pq_push_front(struct PacketQueue *pq, PktIn *pkt);
-PktIn *pq_peek(struct PacketQueue *pq);
-PktIn *pq_pop(struct PacketQueue *pq);
-void pq_clear(struct PacketQueue *pq);
-int pq_empty_on_to_front_of(struct PacketQueue *src, struct PacketQueue *dest);
+} PacketQueueBase;
+
+typedef struct PktInQueue {
+    PacketQueueBase pqb;
+    PktIn *(*get)(PacketQueueBase *, int pop);
+} PktInQueue;
+
+typedef struct PktOutQueue {
+    PacketQueueBase pqb;
+    PktOut *(*get)(PacketQueueBase *, int pop);
+} PktOutQueue;
+
+void pq_base_init(PacketQueueBase *pqb);
+void pq_base_push(PacketQueueBase *pqb, PacketQueueNode *node);
+void pq_base_push_front(PacketQueueBase *pqb, PacketQueueNode *node);
+int pq_base_empty_on_to_front_of(PacketQueueBase *src, PacketQueueBase *dest);
+
+void pq_in_init(PktInQueue *pq);
+void pq_out_init(PktOutQueue *pq);
+void pq_in_clear(PktInQueue *pq);
+void pq_out_clear(PktOutQueue *pq);
+
+#define pq_push(pq, pkt)                                \
+    TYPECHECK((pq)->get(&(pq)->pqb, FALSE) == pkt,      \
+              pq_base_push(&(pq)->pqb, &(pkt)->qnode))
+#define pq_push_front(pq, pkt)                                  \
+    TYPECHECK((pq)->get(&(pq)->pqb, FALSE) == pkt,              \
+              pq_base_push_front(&(pq)->pqb, &(pkt)->qnode))
+#define pq_peek(pq) ((pq)->get(&(pq)->pqb, FALSE))
+#define pq_pop(pq) ((pq)->get(&(pq)->pqb, TRUE))
+#define pq_empty_on_to_front_of(src, dst)                               \
+    TYPECHECK((src)->get(&(src)->pqb, FALSE) ==                         \
+              (dst)->get(&(dst)->pqb, FALSE),                           \
+              pq_base_empty_on_to_front_of(&(src)->pqb, &(dst)->pqb))
 
 /*
  * Packet type contexts, so that ssh2_pkt_type can correctly decode
@@ -777,8 +803,6 @@ int random_byte(void);
 void random_add_noise(void *noise, int length);
 void random_add_heavynoise(void *noise, int length);
 
-void logevent(Frontend *, const char *);
-
 /* Exports from x11fwd.c */
 enum {
     X11_TRANS_IPV4 = 0, X11_TRANS_IPV6 = 6, X11_TRANS_UNIX = 256
@@ -1268,3 +1292,29 @@ const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type);
  * format.
  */
 void old_keyfile_warning(void);
+
+/*
+ * Flags indicating implementation bugs that we know how to mitigate
+ * if we think the other end has them.
+ */
+#define SSH_IMPL_BUG_LIST(X)                    \
+    X(BUG_CHOKES_ON_SSH1_IGNORE)                \
+    X(BUG_SSH2_HMAC)                            \
+    X(BUG_NEEDS_SSH1_PLAIN_PASSWORD)            \
+    X(BUG_CHOKES_ON_RSA)                        \
+    X(BUG_SSH2_RSA_PADDING)                     \
+    X(BUG_SSH2_DERIVEKEY)                       \
+    X(BUG_SSH2_REKEY)                           \
+    X(BUG_SSH2_PK_SESSIONID)                    \
+    X(BUG_SSH2_MAXPKT)                          \
+    X(BUG_CHOKES_ON_SSH2_IGNORE)                \
+    X(BUG_CHOKES_ON_WINADJ)                     \
+    X(BUG_SENDS_LATE_REQUEST_REPLY)             \
+    X(BUG_SSH2_OLDGEX)                          \
+    /* end of list */
+#define TMP_DECLARE_LOG2_ENUM(thing) log2_##thing,
+enum { SSH_IMPL_BUG_LIST(TMP_DECLARE_LOG2_ENUM) };
+#undef TMP_DECLARE_LOG2_ENUM
+#define TMP_DECLARE_REAL_ENUM(thing) thing = 1 << log2_##thing,
+enum { SSH_IMPL_BUG_LIST(TMP_DECLARE_REAL_ENUM) };
+#undef TMP_DECLARE_REAL_ENUM

+ 18 - 1
source/putty/sshbpp.h

@@ -17,7 +17,7 @@ struct BinaryPacketProtocolVtable {
 struct BinaryPacketProtocol {
     const struct BinaryPacketProtocolVtable *vt;
     bufchain *in_raw, *out_raw;
-    PacketQueue *in_pq;
+    PktInQueue *in_pq;
     PacketLogSettings *pls;
     LogContext *logctx;
 
@@ -50,4 +50,21 @@ void ssh2_bpp_new_incoming_crypto(
 
 BinaryPacketProtocol *ssh2_bare_bpp_new(void);
 
+/*
+ * The initial code to handle the SSH version exchange is also
+ * structured as an implementation of BinaryPacketProtocol, because
+ * that makes it easy to switch from that to the next BPP once it
+ * tells us which one we're using.
+ */
+struct ssh_version_receiver {
+    void (*got_ssh_version)(struct ssh_version_receiver *rcv,
+                            int major_version);
+};
+BinaryPacketProtocol *ssh_verstring_new(
+    Conf *conf, Frontend *frontend, int bare_connection_mode,
+    const char *protoversion, struct ssh_version_receiver *rcv);
+const char *ssh_verstring_get_remote(BinaryPacketProtocol *);
+const char *ssh_verstring_get_local(BinaryPacketProtocol *);
+int ssh_verstring_get_bugs(BinaryPacketProtocol *);
+
 #endif /* PUTTY_SSHBPP_H */

+ 161 - 0
source/putty/sshcommon.c

@@ -0,0 +1,161 @@
+/*
+ * Supporting routines used in common by all the various components of
+ * the SSH system.
+ */
+
+#include <assert.h>
+
+#include "ssh.h"
+#include "sshchan.h"
+
+/* ----------------------------------------------------------------------
+ * Implementation of PacketQueue.
+ */
+
+void pq_base_push(PacketQueueBase *pqb, PacketQueueNode *node)
+{
+    assert(!node->next);
+    assert(!node->prev);
+    node->next = &pqb->end;
+    node->prev = pqb->end.prev;
+    node->next->prev = node;
+    node->prev->next = node;
+}
+
+void pq_base_push_front(PacketQueueBase *pqb, PacketQueueNode *node)
+{
+    assert(!node->next);
+    assert(!node->prev);
+    node->prev = &pqb->end;
+    node->next = pqb->end.next;
+    node->next->prev = node;
+    node->prev->next = node;
+}
+
+static PktIn *pq_in_get(PacketQueueBase *pqb, int pop)
+{
+    PacketQueueNode *node = pqb->end.next;
+    if (node == &pqb->end)
+        return NULL;
+
+    if (pop) {
+        node->next->prev = node->prev;
+        node->prev->next = node->next;
+        node->prev = node->next = NULL;
+    }
+
+    return FROMFIELD(node, PktIn, qnode);
+}
+
+static PktOut *pq_out_get(PacketQueueBase *pqb, int pop)
+{
+    PacketQueueNode *node = pqb->end.next;
+    if (node == &pqb->end)
+        return NULL;
+
+    if (pop) {
+        node->next->prev = node->prev;
+        node->prev->next = node->next;
+        node->prev = node->next = NULL;
+    }
+
+    return FROMFIELD(node, PktOut, qnode);
+}
+
+void pq_in_init(PktInQueue *pq)
+{
+    pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end;
+    pq->get = pq_in_get;
+}
+
+void pq_out_init(PktOutQueue *pq)
+{
+    pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end;
+    pq->get = pq_out_get;
+}
+
+void pq_in_clear(PktInQueue *pq)
+{
+    PktIn *pkt;
+    while ((pkt = pq_pop(pq)) != NULL)
+        ssh_unref_packet(pkt);
+}
+
+void pq_out_clear(PktOutQueue *pq)
+{
+    PktOut *pkt;
+    while ((pkt = pq_pop(pq)) != NULL)
+        ssh_free_pktout(pkt);
+}
+
+int pq_base_empty_on_to_front_of(PacketQueueBase *src, PacketQueueBase *dest)
+{
+    struct PacketQueueNode *srcfirst, *srclast;
+
+    if (src->end.next == &src->end)
+        return FALSE;
+
+    srcfirst = src->end.next;
+    srclast = src->end.prev;
+    srcfirst->prev = &dest->end;
+    srclast->next = dest->end.next;
+    srcfirst->prev->next = srcfirst;
+    srclast->next->prev = srclast;
+    src->end.next = src->end.prev = &src->end;
+
+    return TRUE;
+}
+
+/* ----------------------------------------------------------------------
+ * Low-level functions for the packet structures themselves.
+ */
+
+static void ssh_pkt_BinarySink_write(BinarySink *bs,
+                                     const void *data, size_t len);
+PktOut *ssh_new_packet(void)
+{
+    PktOut *pkt = snew(PktOut);
+
+    BinarySink_INIT(pkt, ssh_pkt_BinarySink_write);
+    pkt->data = NULL;
+    pkt->length = 0;
+    pkt->maxlen = 0;
+    pkt->downstream_id = 0;
+    pkt->additional_log_text = NULL;
+    pkt->qnode.next = pkt->qnode.prev = NULL;
+
+    return pkt;
+}
+
+static void ssh_pkt_ensure(PktOut *pkt, int length)
+{
+    if (pkt->maxlen < length) {
+        pkt->maxlen = length + 256;
+        pkt->data = sresize(pkt->data, pkt->maxlen, unsigned char);
+    }
+}
+static void ssh_pkt_adddata(PktOut *pkt, const void *data, int len)
+{
+    pkt->length += len;
+    ssh_pkt_ensure(pkt, pkt->length);
+    memcpy(pkt->data + pkt->length - len, data, len);
+}
+
+static void ssh_pkt_BinarySink_write(BinarySink *bs,
+                                     const void *data, size_t len)
+{
+    PktOut *pkt = BinarySink_DOWNCAST(bs, PktOut);
+    ssh_pkt_adddata(pkt, data, len);
+}
+
+void ssh_unref_packet(PktIn *pkt)
+{
+    if (--pkt->refcount <= 0)
+        sfree(pkt);
+}
+
+void ssh_free_pktout(PktOut *pkt)
+{
+    sfree(pkt->data);
+    sfree(pkt);
+}

+ 602 - 0
source/putty/sshverstring.c

@@ -0,0 +1,602 @@
+/*
+ * 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;
+    Frontend *frontend;
+    ptrlen prefix_wanted;
+    char *our_protoversion;
+    struct ssh_version_receiver *receiver;
+
+    int send_early;
+
+    int found_prefix;
+    int major_protoversion;
+    int remote_bugs;
+    char prefix[PREFIX_MAXLEN];
+    char *vstring;
+    int vslen, vstrsize;
+    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 PktOut *ssh_verstring_new_pktout(int type);
+static void ssh_verstring_format_packet(BinaryPacketProtocol *bpp, PktOut *);
+
+const struct BinaryPacketProtocolVtable ssh_verstring_vtable = {
+    ssh_verstring_free,
+    ssh_verstring_handle_input,
+    ssh_verstring_new_pktout,
+    ssh_verstring_format_packet,
+};
+
+static void ssh_detect_bugs(struct ssh_verstring_state *s);
+static int ssh_version_includes_v1(const char *ver);
+static int ssh_version_includes_v2(const char *ver);
+
+BinaryPacketProtocol *ssh_verstring_new(
+    Conf *conf, Frontend *frontend, int bare_connection_mode,
+    const char *protoversion, struct ssh_version_receiver *rcv)
+{
+    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(
+            "[email protected]");
+    }
+    assert(s->prefix_wanted.len <= PREFIX_MAXLEN);
+
+    s->conf = conf_copy(conf);
+    s->frontend = frontend;
+    s->our_protoversion = dupstr(protoversion);
+    s->receiver = rcv;
+
+    /*
+     * 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).
+     */
+    s->send_early = !ssh_version_includes_v1(protoversion);
+
+    s->bpp.vt = &ssh_verstring_vtable;
+    return &s->bpp;
+}
+
+void ssh_verstring_free(BinaryPacketProtocol *bpp)
+{
+    struct ssh_verstring_state *s =
+        FROMFIELD(bpp, struct ssh_verstring_state, bpp);
+    conf_free(s->conf);
+    sfree(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 int ssh_version_includes_v1(const char *ver)
+{
+    return ssh_versioncmp(ver, "2.0") < 0;
+}
+
+static int ssh_version_includes_v2(const char *ver)
+{
+    return ssh_versioncmp(ver, "1.99") >= 0;
+}
+
+#define vs_logevent(printf_args) \
+    logevent_and_free(s->frontend, dupprintf printf_args)
+
+static void ssh_verstring_send(struct ssh_verstring_state *s)
+{
+    char *p;
+    int sv_pos;
+
+    /*
+     * Construct our outgoing version string.
+     */
+    s->our_vstring = dupprintf(
+        "%.*s%s-%s",
+        (int)s->prefix_wanted.len, (const char *)s->prefix_wanted.ptr,
+        s->our_protoversion, 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);
+
+    vs_logevent(("We claim version: %s", s->our_vstring));
+}
+
+void ssh_verstring_handle_input(BinaryPacketProtocol *bpp)
+{
+    struct ssh_verstring_state *s =
+        FROMFIELD(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.
+         */
+        crMaybeWaitUntilV(bufchain_size(s->bpp.in_raw) >=
+                          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);
+            break;
+        }
+
+        /*
+         * If we didn't find it, consume data until we see a newline.
+         */
+        while (1) {
+            int len;
+            void *data;
+            char *nl;
+
+            crMaybeWaitUntilV(bufchain_size(s->bpp.in_raw) > 0);
+            bufchain_prefix(s->bpp.in_raw, &data, &len);
+            if ((nl = memchr(data, '\012', len)) != NULL) {
+                bufchain_consume(s->bpp.in_raw, nl - (char *)data + 1);
+                break;
+            } else {
+                bufchain_consume(s->bpp.in_raw, len);
+            }
+        }
+    }
+
+    s->found_prefix = TRUE;
+
+    /*
+     * Start a buffer to store the full greeting line.
+     */
+    s->vstrsize = s->prefix_wanted.len + 16;
+    s->vstring = snewn(s->vstrsize, char);
+    memcpy(s->vstring, s->prefix_wanted.ptr, s->prefix_wanted.len);
+    s->vslen = s->prefix_wanted.len;
+
+    /*
+     * Now read the rest of the greeting line.
+     */
+    s->i = 0;
+    do {
+        int len;
+        void *data;
+        char *nl;
+
+        crMaybeWaitUntilV(bufchain_size(s->bpp.in_raw) > 0);
+        bufchain_prefix(s->bpp.in_raw, &data, &len);
+        if ((nl = memchr(data, '\012', len)) != NULL) {
+            len = nl - (char *)data + 1;
+        }
+
+        if (s->vslen + len >= s->vstrsize - 1) {
+            s->vstrsize = (s->vslen + len) * 5 / 4 + 32;
+            s->vstring = sresize(s->vstring, s->vstrsize, char);
+        }
+
+        memcpy(s->vstring + s->vslen, data, len);
+        s->vslen += len;
+        bufchain_consume(s->bpp.in_raw, len);
+
+    } while (s->vstring[s->vslen-1] != '\012');
+
+    /*
+     * Trim \r and \n from the version string, and replace them with
+     * a NUL terminator.
+     */
+    while (s->vslen > 0 &&
+           (s->vstring[s->vslen-1] == '\r' ||
+            s->vstring[s->vslen-1] == '\n'))
+        s->vslen--;
+    s->vstring[s->vslen] = '\0';
+
+    vs_logevent(("Remote version: %s", s->vstring));
+
+    /*
+     * 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->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)) {
+            s->bpp.error = dupstr(
+                "SSH protocol version 1 required by our configuration "
+                "but not provided by remote");
+        } else {
+            s->bpp.error = dupstr(
+                "SSH protocol version 2 required by our configuration "
+                "but remote only provides (old, insecure) SSH-1");
+        }
+        crStopV;
+    }
+
+    vs_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);
+
+    crFinishV;
+}
+
+static PktOut *ssh_verstring_new_pktout(int type)
+{
+    assert(0 && "Should never try to send packets during SSH version "
+           "string exchange");
+    return NULL;
+}
+
+static void ssh_verstring_format_packet(BinaryPacketProtocol *bpp, PktOut *pkg)
+{
+    assert(0 && "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)
+{
+    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;
+        vs_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;
+        vs_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;
+        vs_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;
+        vs_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;
+        vs_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;
+        vs_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;
+        vs_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;
+        vs_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;
+        vs_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;
+        vs_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;
+        vs_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;
+        vs_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;
+        vs_logevent(("We believe remote version has SSH-2 "
+                     "channel request bug"));
+    }
+}
+
+const char *ssh_verstring_get_remote(BinaryPacketProtocol *bpp)
+{
+    struct ssh_verstring_state *s =
+        FROMFIELD(bpp, struct ssh_verstring_state, bpp);
+    return s->vstring;
+}
+
+const char *ssh_verstring_get_local(BinaryPacketProtocol *bpp)
+{
+    struct ssh_verstring_state *s =
+        FROMFIELD(bpp, struct ssh_verstring_state, bpp);
+    return s->our_vstring;
+}
+
+int ssh_verstring_get_bugs(BinaryPacketProtocol *bpp)
+{
+    struct ssh_verstring_state *s =
+        FROMFIELD(bpp, struct ssh_verstring_state, bpp);
+    return s->remote_bugs;
+}

+ 216 - 89
source/putty/tree234.c

@@ -107,6 +107,18 @@ static int countnode234(node234 * n)
     return count;
 }
 
+/*
+ * Internal function to return the number of elements in a node.
+ */
+static int elements234(node234 *n)
+{
+    int i;
+    for (i = 0; i < 3; i++)
+        if (!n->elems[i])
+            break;
+    return i;
+}
+
 /*
  * Count the elements in a tree.
  */
@@ -514,99 +526,66 @@ void *index234(tree234 * t, int index)
 void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp,
 		    int relation, int *index)
 {
-    node234 *n;
-    void *ret;
-    int c;
-    int idx, ecount, kcount, cmpret;
+    search234_state ss;
+    int reldir = (relation == REL234_LT || relation == REL234_LE ? -1 :
+                  relation == REL234_GT || relation == REL234_GE ? +1 : 0);
+    int equal_permitted = (relation != REL234_LT && relation != REL234_GT);
+    void *toret;
 
-    if (t->root == NULL)
-	return NULL;
+    /* Only LT / GT relations are permitted with a null query element. */
+    assert(!(equal_permitted && !e));
 
     if (cmp == NULL)
 	cmp = t->cmp;
 
-    n = t->root;
+    search234_start(&ss, t);
+    while (ss.element) {
+        int cmpret;
+
+        if (e) {
+            cmpret = cmp(e, ss.element);
+        } else {
+            cmpret = -reldir;          /* invent a fixed compare result */
+        }
+
+        if (cmpret == 0) {
+            /*
+             * We've found an element that compares exactly equal to
+             * the query element.
+             */
+            if (equal_permitted) {
+                /* If our search relation permits equality, we've
+                 * finished already. */
+                if (index)
+                    *index = ss.index;
+                return ss.element;
+            } else {
+                /* Otherwise, pretend this element was slightly too
+                 * big/small, according to the direction of search. */
+                cmpret = reldir;
+            }
+        }
+
+        search234_step(&ss, cmpret);
+    }
+
     /*
-     * Attempt to find the element itself.
+     * No element compares equal to the one we were after, but
+     * ss.index indicates the index that element would have if it were
+     * inserted.
+     *
+     * So if our search relation is EQ, we must simply return failure.
      */
-    idx = 0;
-    ecount = -1;
+    if (relation == REL234_EQ)
+        return NULL;
+
     /*
-     * Prepare a fake `cmp' result if e is NULL.
+     * Otherwise, we must do an index lookup for the previous index
+     * (if we're going left - LE or LT) or this index (if we're going
+     * right - GE or GT).
      */
-    cmpret = 0;
-    if (e == NULL) {
-	assert(relation == REL234_LT || relation == REL234_GT);
-	if (relation == REL234_LT)
-	    cmpret = +1;	       /* e is a max: always greater */
-	else if (relation == REL234_GT)
-	    cmpret = -1;	       /* e is a min: always smaller */
-    }
-    while (1) {
-	for (kcount = 0; kcount < 4; kcount++) {
-	    if (kcount >= 3 || n->elems[kcount] == NULL ||
-		(c = cmpret ? cmpret : cmp(e, n->elems[kcount])) < 0) {
-		break;
-	    }
-	    if (n->kids[kcount])
-		idx += n->counts[kcount];
-	    if (c == 0) {
-		ecount = kcount;
-		break;
-	    }
-	    idx++;
-	}
-	if (ecount >= 0)
-	    break;
-	if (n->kids[kcount])
-	    n = n->kids[kcount];
-	else
-	    break;
-    }
-
-    if (ecount >= 0) {
-	/*
-	 * We have found the element we're looking for. It's
-	 * n->elems[ecount], at tree index idx. If our search
-	 * relation is EQ, LE or GE we can now go home.
-	 */
-	if (relation != REL234_LT && relation != REL234_GT) {
-	    if (index)
-		*index = idx;
-	    return n->elems[ecount];
-	}
-
-	/*
-	 * Otherwise, we'll do an indexed lookup for the previous
-	 * or next element. (It would be perfectly possible to
-	 * implement these search types in a non-counted tree by
-	 * going back up from where we are, but far more fiddly.)
-	 */
-	if (relation == REL234_LT)
-	    idx--;
-	else
-	    idx++;
-    } else {
-	/*
-	 * We've found our way to the bottom of the tree and we
-	 * know where we would insert this node if we wanted to:
-	 * we'd put it in in place of the (empty) subtree
-	 * n->kids[kcount], and it would have index idx
-	 * 
-	 * But the actual element isn't there. So if our search
-	 * relation is EQ, we're doomed.
-	 */
-	if (relation == REL234_EQ)
-	    return NULL;
-
-	/*
-	 * Otherwise, we must do an index lookup for index idx-1
-	 * (if we're going left - LE or LT) or index idx (if we're
-	 * going right - GE or GT).
-	 */
-	if (relation == REL234_LT || relation == REL234_LE) {
-	    idx--;
-	}
+    if (relation == REL234_LT || relation == REL234_LE) {
+        ss.index--;
     }
 
     /*
@@ -614,10 +593,10 @@ void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp,
      * to do the rest. This will return NULL if the index is out of
      * bounds, which is exactly what we want.
      */
-    ret = index234(t, idx);
-    if (ret && index)
-	*index = idx;
-    return ret;
+    toret = index234(t, ss.index);
+    if (toret && index)
+        *index = ss.index;
+    return toret;
 }
 void *find234(tree234 * t, void *e, cmpfn234 cmp)
 {
@@ -632,6 +611,80 @@ void *findpos234(tree234 * t, void *e, cmpfn234 cmp, int *index)
     return findrelpos234(t, e, cmp, REL234_EQ, index);
 }
 
+void search234_start(search234_state *state, tree234 *t)
+{
+    state->_node = t->root;
+    state->_base = 0; /* index of first element in this node's subtree */
+    state->_last = -1; /* indicate that this node is not previously visted */
+    search234_step(state, 0);
+}
+void search234_step(search234_state *state, int direction)
+{
+    node234 *node = state->_node;
+    int i;
+
+    if (!node) {
+        state->element = NULL;
+        state->index = 0;
+        return;
+    }
+
+    if (state->_last != -1) {
+        /*
+         * We're already pointing at some element of a node, so we
+         * should restrict to the elements left or right of it,
+         * depending on the requested search direction.
+         */
+        assert(direction);
+        assert(node);
+
+        if (direction > 0) {
+            state->_lo = state->_last + 1;
+            direction = +1;
+        } else {
+            state->_hi = state->_last - 1;
+            direction = -1;
+        }
+
+        if (state->_lo > state->_hi) {
+            /*
+             * We've run out of elements in this node, i.e. we've
+             * narrowed to nothing but a child pointer. Descend to
+             * that child, and update _base to the leftmost index of
+             * its subtree.
+             */
+            for (i = 0; i < state->_lo; i++)
+                state->_base += 1 + node->counts[i];
+            state->_node = node = node->kids[state->_lo];
+            state->_last = -1;
+        }
+    }
+
+    if (state->_last == -1) {
+        /*
+         * We've just entered a new node - either because of the above
+         * code, or because we were called from search234_start - and
+         * anything in that node is a viable answer.
+         */
+        state->_lo = 0;
+        state->_hi = node ? elements234(node)-1 : 0;
+    }
+
+    /*
+     * Now we've got something we can return.
+     */
+    if (!node) {
+        state->element = NULL;
+        state->index = state->_base;
+    } else {
+        state->_last = (state->_lo + state->_hi) / 2;
+        state->element = node->elems[state->_last];
+        state->index = state->_base + state->_last;
+        for (i = 0; i <= state->_last; i++)
+            state->index += node->counts[i];
+    }
+}
+
 /*
  * Delete an element e in a 2-3-4 tree. Does not free the element,
  * merely removes all links to it from the tree nodes.
@@ -1014,6 +1067,9 @@ void *del234(tree234 * t, void *e)
  */
 
 #include <stdarg.h>
+#include <string.h>
+
+int n_errors = 0;
 
 /*
  * Error reporting function.
@@ -1026,6 +1082,7 @@ void error(char *fmt, ...)
     vfprintf(stdout, fmt, ap);
     va_end(ap);
     printf("\n");
+    n_errors++;
 }
 
 /* The array representation of the data. */
@@ -1414,6 +1471,73 @@ int findtest(void)
     }
 }
 
+void searchtest_recurse(search234_state ss, int lo, int hi,
+                        char **expected, char *directionbuf,
+                        char *directionptr)
+{
+    *directionptr = '\0';
+
+    if (!ss.element) {
+        if (lo != hi) {
+            error("search234(%s) gave NULL for non-empty interval [%d,%d)",
+                  directionbuf, lo, hi);
+        } else if (ss.index != lo) {
+            error("search234(%s) gave index %d should be %d",
+                  directionbuf, ss.index, lo);
+        } else {
+            printf("%*ssearch234(%s) gave NULL,%d\n",
+                   (int)(directionptr-directionbuf) * 2, "", directionbuf,
+                   ss.index);
+        }
+    } else if (lo == hi) {
+        error("search234(%s) gave %s for empty interval [%d,%d)",
+              directionbuf, (char *)ss.element, lo, hi);
+    } else if (ss.element != expected[ss.index]) {
+        error("search234(%s) gave element %s should be %s",
+              directionbuf, (char *)ss.element, expected[ss.index]);
+    } else if (ss.index < lo || ss.index >= hi) {
+        error("search234(%s) gave index %d should be in [%d,%d)",
+              directionbuf, ss.index, lo, hi);
+        return;
+    } else {
+        search234_state next;
+
+        printf("%*ssearch234(%s) gave %s,%d\n",
+               (int)(directionptr-directionbuf) * 2, "", directionbuf,
+               (char *)ss.element, ss.index);
+
+        next = ss;
+        search234_step(&next, -1);
+        *directionptr = '-';
+        searchtest_recurse(next, lo, ss.index,
+                           expected, directionbuf, directionptr+1);
+
+        next = ss;
+        search234_step(&next, +1);
+        *directionptr = '+';
+        searchtest_recurse(next, ss.index+1, hi,
+                           expected, directionbuf, directionptr+1);
+    }
+}
+
+void searchtest(void)
+{
+    char *expected[NSTR], *p;
+    char directionbuf[NSTR * 10];
+    int n;
+    search234_state ss;
+
+    printf("beginning searchtest:");
+    for (n = 0; (p = index234(tree, n)) != NULL; n++) {
+        expected[n] = p;
+        printf(" %d=%s", n, p);
+    }
+    printf(" count=%d\n", n);
+
+    search234_start(&ss, tree);
+    searchtest_recurse(ss, 0, n, expected, directionbuf, directionbuf);
+}
+
 int main(void)
 {
     int in[NSTR];
@@ -1428,6 +1552,7 @@ int main(void)
     cmp = mycmp;
 
     verify();
+    searchtest();
     for (i = 0; i < 10000; i++) {
 	j = randomnumber(&seed);
 	j %= NSTR;
@@ -1442,6 +1567,7 @@ int main(void)
 	    in[j] = 1;
 	}
 	findtest();
+        searchtest();
     }
 
     while (arraylen > 0) {
@@ -1480,7 +1606,8 @@ int main(void)
 	delpostest(j);
     }
 
-    return 0;
+    printf("%d errors found\n", n_errors);
+    return (n_errors != 0);
 }
 
 #endif

+ 35 - 0
source/putty/tree234.h

@@ -132,6 +132,41 @@ void *findpos234(tree234 * t, void *e, cmpfn234 cmp, int *index);
 void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp, int relation,
 		    int *index);
 
+/*
+ * A more general search type still. Use search234_start() to
+ * initialise one of these state structures; it will fill in
+ * state->element with an element of the tree, and state->index with
+ * the index of that element. If you don't like that element, call
+ * search234_step, with direction == -1 if you want an element earlier
+ * in the tree, or +1 if you want a later one.
+ *
+ * If either function returns state->element == NULL, then you've
+ * narrowed the search to a point between two adjacent elements, so
+ * there are no further elements left to return consistent with the
+ * constraints you've imposed. In this case, state->index tells you
+ * how many elements come before the point you narrowed down to. After
+ * this, you mustn't call search234_step again (unless the state
+ * structure is first reinitialised).
+ *
+ * The use of this search system is that you get both the candidate
+ * element _and_ its index at every stage, so you can use both of them
+ * to make your decision. Also, you can remember element pointers from
+ * earlier in the search.
+ *
+ * The fields beginning with underscores are private to the
+ * implementation, and only exposed so that clients can know how much
+ * space to allocate for the structure as a whole. Don't modify them.
+ * (Except that it's safe to copy the whole structure.)
+ */
+typedef struct search234_state {
+    void *element;
+    int index;
+    int _lo, _hi, _last, _base;
+    void *_node;
+} search234_state;
+void search234_start(search234_state *state, tree234 *t);
+void search234_step(search234_state *state, int direction);
+
 /*
  * Delete an element e in a 2-3-4 tree. Does not free the element,
  * merely removes all links to it from the tree nodes.

部分文件因为文件数量过多而无法显示