# HG changeset patch
# Node ID f863f7e94c8d8fe6b281df7a92116a1f16aa2aae
# Parent  253dcd531cb97840ac5173642919f9d66fc19e38
diff --git a/ibis-cli/ibiscli.c b/ibis-cli/ibiscli.c
--- a/ibis-cli/ibiscli.c
+++ b/ibis-cli/ibiscli.c
@@ -249,6 +249,19 @@
 	return TRUE;
 }
 
+static gboolean
+ibiscli_message_after_cb(G_GNUC_UNUSED IbisClient *client,
+                         G_GNUC_UNUSED const char *command,
+                         G_GNUC_UNUSED IbisMessage *message,
+                         G_GNUC_UNUSED gpointer data)
+{
+	/* We should be logging everything, so all we need to do here is tell Ibis
+	 * that we've handled everything we were supposed to.
+	 */
+
+	return TRUE;
+}
+
 static void
 ibiscli_wrote_message_cb(G_GNUC_UNUSED IbisClient *client,
                          IbisMessage *message,
@@ -272,6 +285,8 @@
 	                 G_CALLBACK(ibiscli_network_changed_cb), NULL);
 	g_signal_connect(client, "message",
 	                 G_CALLBACK(ibiscli_message_cb), NULL);
+	g_signal_connect_after(client, "message",
+	                       G_CALLBACK(ibiscli_message_after_cb), NULL);
 	g_signal_connect(client, "wrote-message",
 	                 G_CALLBACK(ibiscli_wrote_message_cb), NULL);
 
diff --git a/ibis/ibiscapabilities.c b/ibis/ibiscapabilities.c
--- a/ibis/ibiscapabilities.c
+++ b/ibis/ibiscapabilities.c
@@ -56,6 +56,7 @@
 	GHashTable *caps;
 	GPtrArray *requests;
 
+	gboolean negotiating;
 	gatomicrefcount wait_counters;
 };
 
@@ -74,6 +75,11 @@
 	ibis_message_set_params(message, "END", NULL);
 	ibis_client_write(capabilities->client, message);
 
+	/* Set negotiating to false before emitting the signal in case anyone
+	 * tries to add a wait in the signal handler.
+	 */
+	capabilities->negotiating = FALSE;
+
 	g_signal_emit(capabilities, signals[SIG_DONE], 0);
 }
 
@@ -319,6 +325,7 @@
 	                                           g_free);
 	capabilities->requests = g_ptr_array_new_full(0, g_free);
 
+	capabilities->negotiating = FALSE;
 	g_atomic_ref_count_init(&capabilities->wait_counters);
 }
 
@@ -334,8 +341,8 @@
 	 * Emitted when @self has finished receiving the list of
 	 * capabilities from the server at startup.
 	 *
-	 * For dynamically added capabilities see the [signal@Capabilities::new] and
-	 * [signal@Capabilities::del] signals.
+	 * For dynamically added capabilities see the [signal@Capabilities::new]
+	 * and [signal@Capabilities::del] signals.
 	 *
 	 * Since: 0.1
 	 */
@@ -409,6 +416,10 @@
 	 * Emitted when all of the requested capabilities have been either ack'd or
 	 * nak'd by the server.
 	 *
+	 * This will only be emitted for the initial negotiation during
+	 * registration and will not be emitted as a result of CAP NEW or CAP DEL
+	 * messages.
+	 *
 	 * Since: 0.1
 	 */
 	signals[SIG_DONE] = g_signal_new_class_handler(
@@ -496,12 +507,13 @@
 	g_return_if_fail(IBIS_IS_CAPABILITIES(capabilities));
 	g_return_if_fail(IBIS_IS_CLIENT(client));
 
-	if(IBIS_IS_CLIENT(capabilities->client)) {
+	if(IBIS_IS_CLIENT(capabilities->client) || capabilities->negotiating) {
 		g_warning("capabilities %p is already started.", capabilities);
 
 		return;
 	}
 
+	capabilities->negotiating = TRUE;
 	capabilities->client = g_object_ref(client);
 	g_signal_connect_object(capabilities->client, "message::" IBIS_MSG_CAP,
 	                        G_CALLBACK(ibis_capabilities_message_cb),
@@ -585,14 +597,18 @@
 ibis_capabilities_add_wait(IbisCapabilities *capabilities) {
 	g_return_if_fail(IBIS_IS_CAPABILITIES(capabilities));
 
-	g_atomic_ref_count_inc(&capabilities->wait_counters);
+	if(capabilities->negotiating) {
+		g_atomic_ref_count_inc(&capabilities->wait_counters);
+	}
 }
 
 void
 ibis_capabilities_remove_wait(IbisCapabilities *capabilities) {
 	g_return_if_fail(IBIS_IS_CAPABILITIES(capabilities));
 
-	if(g_atomic_ref_count_dec(&capabilities->wait_counters)) {
-		ibis_capabilities_finish(capabilities);
+	if(capabilities->negotiating) {
+		if(g_atomic_ref_count_dec(&capabilities->wait_counters)) {
+			ibis_capabilities_finish(capabilities);
+		}
 	}
 }
diff --git a/ibis/ibiscapabilities.h b/ibis/ibiscapabilities.h
--- a/ibis/ibiscapabilities.h
+++ b/ibis/ibiscapabilities.h
@@ -71,6 +71,11 @@
  *
  * This will send the `CAP LS` command.
  *
+ * While negotiating, you may call [method@Capabilities.add_wait] to add a wait
+ * counter that will delay the sending of the `CAP END` message. The `CAP END`
+ * message will be sent once all of the wait counters have been removed via
+ * matching calls to [method@Capabilities.remove_wait].
+ *
  * Since: 0.1
  */
 IBIS_AVAILABLE_IN_0_1
@@ -131,9 +136,14 @@
 /**
  * ibis_capabilities_add_wait:
  *
- * Adds a wait counter to @capabilities. This counter is used to delay the
- * call of `CAP END` until all capability negotiation has completed. This is
- * necessary for SASL and may be necessary for other capabilities as well.
+ * Adds a wait counter to @capabilities.
+ *
+ * This counter is used to delay the call of `CAP END` until all capability
+ * negotiation has completed. This is necessary for SASL and may be necessary
+ * for other capabilities as well.
+ *
+ * Since 0.11 this method will do nothing if called after
+ * [signal@Capabilities::done] has been emitted.
  *
  * Since: 0.1
  */
@@ -143,11 +153,14 @@
 /**
  * ibis_capabilities_remove_wait:
  *
- * Removes a wait counter from @capabilities. Only when this counter reaches 0,
- * will `CAP END` be called and registration completed.
+ * Removes a wait counter from @capabilities.
  *
- * This is necessary for SASL and may be necessary for other capabilities as
- * well.
+ * Only when this counter reaches 0, will `CAP END` be called and registration
+ * completed. This is necessary for SASL and may be necessary for other
+ * capabilities as well.
+ *
+ * Since 0.11 this method will do nothing if called after
+ * [signal@Capabilities::done] has been emitted.
  *
  * Since: 0.1
  */
diff --git a/ibis/ibissasl.c b/ibis/ibissasl.c
--- a/ibis/ibissasl.c
+++ b/ibis/ibissasl.c
@@ -73,6 +73,8 @@
 		GError *error = g_error_new(IBIS_DOMAIN, 0,
 		                            _("No valid SASL mechanisms found"));
 
+		hasl_context_done(hasl_context);
+
 		ibis_client_disconnect(client, error, NULL);
 
 		return;
@@ -144,12 +146,12 @@
 	HaslContext *hasl_context = NULL;
 
 	capabilities = ibis_client_get_capabilities(client);
-	hasl_context = ibis_client_get_hasl_context(client);
-
 	ibis_capabilities_remove_wait(capabilities);
 
+	hasl_context = ibis_client_get_hasl_context(client);
 	g_message("successfully authenticated with SASL '%s' mechanism.",
 	          hasl_context_get_current_mechanism(hasl_context));
+	hasl_context_done(hasl_context);
 
 	return TRUE;
 }
@@ -186,33 +188,35 @@
 }
 
 static gboolean
-ibis_sasl_aborted(G_GNUC_UNUSED IbisClient *client,
+ibis_sasl_aborted(IbisClient *client,
                   G_GNUC_UNUSED const char *command,
                   G_GNUC_UNUSED IbisMessage *message,
                   G_GNUC_UNUSED gpointer data)
 {
-	/* This is supposed to get sent, when the client sends `AUTHENTICATE *`,
-	 * but we don't do this, so I guess we'll note it for now...?
-	 * --GK 2023-01-12
-	 */
-	g_warning("The server claims we aborted SASL authentication.");
+	HaslContext *hasl_context = NULL;
 
-	return TRUE;
+	hasl_context = ibis_client_get_hasl_context(client);
+	hasl_context_done(hasl_context);
+
+	/* Return FALSE so the clients can handle this how they like. */
+
+	return FALSE;
 }
 
 static gboolean
-ibis_sasl_already_authed(G_GNUC_UNUSED IbisClient *client,
+ibis_sasl_already_authed(IbisClient *client,
                          G_GNUC_UNUSED const char *command,
                          G_GNUC_UNUSED IbisMessage *message,
                          G_GNUC_UNUSED gpointer data)
 {
-	/* Similar to aborted above, we don't allow this, so just note that it
-	 * happened.
-	 * -- GK 2023-01-12
-	 */
-	g_warning("Server claims we tried to SASL authenticate again.");
+	HaslContext *hasl_context = NULL;
 
-	return TRUE;
+	hasl_context = ibis_client_get_hasl_context(client);
+	hasl_context_done(hasl_context);
+
+	/* Return FALSE so clients can handle this how they like. */
+
+	return FALSE;
 }
 
 static gboolean
@@ -364,27 +368,22 @@
 }
 
 static void
-ibis_sasl_ack_cb(IbisCapabilities *capabilities,
-                 IbisClient *client,
-                 G_GNUC_UNUSED const char *capability,
-                 G_GNUC_UNUSED gpointer data)
-{
+ibis_sasl_setup(IbisCapabilities *capabilities, IbisClient *client) {
 	HaslContext *hasl_context = NULL;
-	const char *existing_mechanisms = NULL;
+	const char *server_mechanisms = NULL;
+	gboolean found = FALSE;
 
-	/* Add the SASL mechanisms that the server advertised. */
 	hasl_context = ibis_client_get_hasl_context(client);
 
-	/* If the user hasn't specified any mechanisms, we use whatever the server
-	 * advertised which could be empty.
-	 */
-	existing_mechanisms = hasl_context_get_allowed_mechanisms(hasl_context);
-	if(ibis_str_is_empty(existing_mechanisms)) {
-		const char *mechanisms = NULL;
+	/* Reset the context to its initial state. */
+	hasl_context_done(hasl_context);
 
-		mechanisms = ibis_capabilities_lookup(capabilities,
-		                                      IBIS_CAPABILITY_SASL, NULL);
-		hasl_context_set_allowed_mechanisms(hasl_context, mechanisms);
+	/* Set our server mechanisms to whatever the server advertised. */
+	server_mechanisms = ibis_capabilities_lookup(capabilities,
+	                                             IBIS_CAPABILITY_SASL,
+	                                             &found);
+	if(found) {
+		hasl_context_set_server_mechanisms(hasl_context, server_mechanisms);
 	}
 
 	/* There isn't a good way to know if our handlers are here or not, so we
@@ -446,6 +445,34 @@
 	ibis_sasl_attempt(client);
 }
 
+static void
+ibis_sasl_ack_cb(IbisCapabilities *capabilities,
+                 IbisClient *client,
+                 G_GNUC_UNUSED const char *capability,
+                 G_GNUC_UNUSED gpointer data)
+{
+	ibis_sasl_setup(capabilities, client);
+}
+
+static void
+ibis_sasl_new_cb(IbisCapabilities *capabilities,
+                 G_GNUC_UNUSED IbisClient *client,
+                 GStrv caps,
+                 G_GNUC_UNUSED gpointer data)
+{
+	/* We have to run through the new caps as we only want to re-authenticate
+	 * when SASL comes back and not every time a new capability is added.
+	 */
+
+	for(guint i = 0; caps[i] != NULL; i++) {
+		if(g_str_has_prefix(caps[i], IBIS_CAPABILITY_SASL "=")) {
+			ibis_capabilities_request(capabilities, IBIS_CAPABILITY_SASL);
+
+			break;
+		}
+	}
+}
+
 /******************************************************************************
  * Internal API
  *****************************************************************************/
@@ -455,4 +482,6 @@
 
 	g_signal_connect(capabilities, "ack::" IBIS_CAPABILITY_SASL,
 	                 G_CALLBACK(ibis_sasl_ack_cb), NULL);
+
+	g_signal_connect(capabilities, "new", G_CALLBACK(ibis_sasl_new_cb), NULL);
 }
diff --git a/meson.build b/meson.build
--- a/meson.build
+++ b/meson.build
@@ -38,8 +38,8 @@
 glib_dep = dependency('glib-2.0', version : '>=2.76.0')
 gio_dep = dependency('gio-2.0')
 gobject_dep = dependency('gobject-2.0')
-birb_dep = dependency('birb')
-hasl_dep = dependency('hasl')
+birb_dep = dependency('birb', version: '>=0.2.0')
+hasl_dep = dependency('hasl', version : '>=0.4.0')
 
 add_project_arguments(
   '-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_76',
diff --git a/subprojects/hasl.wrap b/subprojects/hasl.wrap
--- a/subprojects/hasl.wrap
+++ b/subprojects/hasl.wrap
@@ -1,4 +1,4 @@
 [wrap-hg]
 directory = hasl
 url = https://keep.imfreedom.org/hasl/hasl/
-revision = v0.3.2
+revision = v0.4.0
