- djm@cvs.openbsd.org 2008/06/12 04:06:00
     [clientloop.h ssh.c clientloop.c]
     maintain an ordered queue of outstanding global requests that we
     expect replies to, similar to the per-channel confirmation queue.
     Use this queue to verify success or failure for remote forward
     establishment in a race free way.
     ok dtucker@
diff --git a/clientloop.c b/clientloop.c
index b45e729..37cecf5 100644
--- a/clientloop.c
+++ b/clientloop.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: clientloop.c,v 1.195 2008/06/12 03:40:52 djm Exp $ */
+/* $OpenBSD: clientloop.c,v 1.196 2008/06/12 04:06:00 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -174,6 +174,17 @@
 	int id, do_close;
 };
 
+/* Global request success/failure callbacks */
+struct global_confirm {
+	TAILQ_ENTRY(global_confirm) entry;
+	global_confirm_cb *cb;
+	void *ctx;
+	int ref_count;
+};
+TAILQ_HEAD(global_confirms, global_confirm);
+static struct global_confirms global_confirms =
+    TAILQ_HEAD_INITIALIZER(global_confirms);
+
 /*XXX*/
 extern Kex *xxx_kex;
 
@@ -468,8 +479,19 @@
 static void
 client_global_request_reply(int type, u_int32_t seq, void *ctxt)
 {
+	struct global_confirm *gc;
+
+	if ((gc = TAILQ_FIRST(&global_confirms)) == NULL)
+		return;
+	if (gc->cb != NULL)
+		gc->cb(type, seq, gc->ctx);
+	if (--gc->ref_count <= 0) {
+		TAILQ_REMOVE(&global_confirms, gc, entry);
+		bzero(gc, sizeof(*gc));
+		xfree(gc);
+	}
+
 	keep_alive_timeouts = 0;
-	client_global_request_reply_fwd(type, seq, ctxt);
 }
 
 static void
@@ -483,6 +505,8 @@
 	packet_put_cstring("keepalive@openssh.com");
 	packet_put_char(1);     /* boolean: want reply */
 	packet_send();
+	/* Insert an empty placeholder to maintain ordering */
+	client_register_global_confirm(NULL, NULL);
 }
 
 /*
@@ -702,6 +726,27 @@
 	    client_abandon_status_confirm, cr);
 }
 
+void
+client_register_global_confirm(global_confirm_cb *cb, void *ctx)
+{
+	struct global_confirm *gc, *first_gc;
+
+	/* Coalesce identical callbacks */
+	first_gc = TAILQ_FIRST(&global_confirms);
+	if (first_gc && first_gc->cb == cb && first_gc->ctx == ctx) {
+		if (++first_gc->ref_count >= INT_MAX)
+			fatal("%s: first_gc->ref_count = %d",
+			    __func__, first_gc->ref_count);
+		return;
+	}
+
+	gc = xmalloc(sizeof(*gc));
+	gc->cb = cb;
+	gc->ctx = ctx;
+	gc->ref_count = 1;
+	TAILQ_INSERT_TAIL(&global_confirms, gc, entry);
+}
+
 static void
 process_cmdline(void)
 {