pidgin/pidgin

16ef7725e459
Parents df47db24cee6
Children 0b6f1d75bd40
IRCv3: Port to our new SASL library named Hasl

Testing Done:
Connected to a local ergo and verfied that `PLAIN` worked with the right password and that it fell back to `EXTERNAL` with the wrong password, before giving up.

Reviewed at https://reviews.imfreedom.org/r/2236/
--- a/libpurple/protocols/ircv3/meson.build Thu Feb 23 01:33:50 2023 -0600
+++ b/libpurple/protocols/ircv3/meson.build Thu Feb 23 06:00:48 2023 -0600
@@ -29,7 +29,7 @@
ircv3_prpl = shared_library('ircv3', IRCV3_SOURCES + IRCV3_HEADERS,
c_args : ['-DG_LOG_USE_STRUCTURED', '-DG_LOG_DOMAIN="Purple-IRCv3"'],
- dependencies : [libpurple_dep, glib, gio, gsasl, ws2_32],
+ dependencies : [libpurple_dep, glib, gio, hasl, ws2_32],
install : true,
install_dir : PURPLE_PLUGINDIR)
--- a/libpurple/protocols/ircv3/purpleircv3sasl.c Thu Feb 23 01:33:50 2023 -0600
+++ b/libpurple/protocols/ircv3/purpleircv3sasl.c Thu Feb 23 06:00:48 2023 -0600
@@ -18,7 +18,7 @@
#include <glib/gi18n-lib.h>
-#include <gsasl.h>
+#include <hasl.h>
#include "purpleircv3sasl.h"
@@ -28,22 +28,10 @@
#define PURPLE_IRCV3_SASL_DATA_KEY ("sasl-data")
-/* Workarounds for old versions of gsasl. By defining these values when they're
- * not yet defined, upgrades of gsasl should continue to work without needing
- * to recompile this code.
- */
-#ifndef GSASL_CB_TLS_EXPORTER
-# define GSASL_CB_TLS_EXPORTER (25)
-#endif
-
typedef struct {
PurpleConnection *connection;
- Gsasl *ctx;
- Gsasl_session *session;
-
- char *current_mechanism;
- GString *mechanisms;
+ HaslContext *ctx;
GString *server_in_buffer;
} PurpleIRCv3SASLData;
@@ -67,87 +55,11 @@
}
/******************************************************************************
- * SASL Callbacks
- *****************************************************************************/
-static int
-purple_ircv3_sasl_callback(G_GNUC_UNUSED Gsasl *ctx, Gsasl_session *session,
- Gsasl_property property)
-{
- PurpleIRCv3SASLData *data = NULL;
- int res = GSASL_NO_CALLBACK;
-
- data = gsasl_session_hook_get(session);
-
- switch(property) {
- case GSASL_AUTHID:
- gsasl_property_set(session, GSASL_AUTHID,
- purple_ircv3_sasl_get_username(data->connection));
- res = GSASL_OK;
- break;
- case GSASL_AUTHZID:
- /* AUTHZID is only used implemented for the PLAIN mechanism. If we
- * return a value for SCRAM, it needs to match AUTHID, which we
- * always set which would be redundant.
- *
- * So instead, we just check if the mechanism is PLAIN and if so
- * set it to empty string because it's the user logging in on their
- * own behalf and IRCv3 doesn't really let an admin login as a
- * normal user.
- *
- * See https://www.gnu.org/software/gsasl/manual/gsasl.html#PLAIN
- * for further explanation.
- */
- if(purple_strequal(data->current_mechanism, "PLAIN")) {
- gsasl_property_set(session, GSASL_AUTHZID, "");
- res = GSASL_OK;
- }
- break;
- case GSASL_PASSWORD:
- gsasl_property_set(session, GSASL_PASSWORD,
- purple_connection_get_password(data->connection));
- res = GSASL_OK;
- break;
- case GSASL_SCRAM_SALTED_PASSWORD:
- /* Ignored, we let gsasl figure it out from the password above. */
- break;
- case GSASL_CB_TLS_UNIQUE:
- case GSASL_CB_TLS_EXPORTER:
- /* TODO: implement these in the near future when we're implementing
- * EXTERNAL support for PIDGIN-17741.
- */
- break;
- default:
- g_warning("Unknown property %d", property);
- break;
- }
-
- return res;
-}
-
-/******************************************************************************
* SASL Helpers
*****************************************************************************/
static void
-purple_ircv3_sasl_connection_error(PurpleConnection *connection, int res,
- int err, const char *msg)
-{
- GError *error = NULL;
-
- error = g_error_new(PURPLE_CONNECTION_ERROR, err, "%s: %s", msg,
- gsasl_strerror(res));
-
- purple_connection_take_error(connection, error);
-}
-
-static void
purple_ircv3_sasl_data_free(PurpleIRCv3SASLData *data) {
- g_clear_object(&data->connection);
- g_clear_pointer(&data->session, gsasl_finish);
- g_clear_pointer(&data->ctx, gsasl_done);
- g_clear_pointer(&data->current_mechanism, g_free);
-
- g_string_free(data->mechanisms, TRUE);
- data->mechanisms = NULL;
+ g_clear_object(&data->ctx);
g_string_free(data->server_in_buffer, TRUE);
@@ -155,11 +67,8 @@
}
static void
-purple_ircv3_sasl_data_add(PurpleConnection *connection, Gsasl *ctx,
- const char *mechanisms)
-{
+purple_ircv3_sasl_data_add(PurpleConnection *connection, HaslContext *ctx) {
PurpleIRCv3SASLData *data = NULL;
- GStrv parts = NULL;
data = g_new0(PurpleIRCv3SASLData, 1);
g_object_set_data_full(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY,
@@ -171,122 +80,41 @@
*/
data->connection = connection;
data->ctx = ctx;
- gsasl_callback_set(data->ctx, purple_ircv3_sasl_callback);
/* We truncate the server_in_buffer when we need to so that we can minimize
* allocations and simplify the logic involved with it.
*/
data->server_in_buffer = g_string_new("");
-
- /* Create a GString for the mechanisms with a leading and trailing ` `.
- * This is so we can easily remove attempted mechanism by removing
- * ` <attempted_mechanism> ` from the string which will make sure we're
- * always removing the proper mechanism. This is necessary because some
- * mechanisms have the same prefix, and others have the same suffix, which
- * could lead to incorrect removals.
- *
- * For example, if the list contains `EAP-AES128-PLUS EAP-AES128` and we
- * try `EAP-AES128` first, that means we would remove `EAP-AES128` from the
- * list which would first find `EAP-AES128-PLUS` and replace it with an
- * empty string, leaving the list as `-PLUS EAP-AES128` which is obviously
- * wrong. Instead the additional spaces mean our replace can be
- * ` EAP-AES128 `, which will get the proper value and update the
- * list to just contain ` EAP-AES128-PLUS `.
- *
- * For a list of mechanisms see
- * https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml
- *
- */
- data->mechanisms = g_string_new("");
-
- parts = g_strsplit(mechanisms, ",", -1);
- for(int i = 0; parts[i] != NULL; i++) {
- g_string_append_printf(data->mechanisms, " %s ", parts[i]);
- }
- g_strfreev(parts);
-}
-
-static void
-purple_ircv3_sasl_attempt_mechanism(PurpleIRCv3Connection *connection,
- PurpleIRCv3SASLData *data,
- const char *next_mechanism)
-{
- int res = GSASL_OK;
-
- g_free(data->current_mechanism);
- data->current_mechanism = g_strdup(next_mechanism);
-
- res = gsasl_client_start(data->ctx, next_mechanism, &data->session);
- if(res != GSASL_OK) {
- purple_ircv3_sasl_connection_error(PURPLE_CONNECTION(connection),
- res,
- PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
- _("Failed to setup SASL client"));
- return;
- }
- gsasl_session_hook_set(data->session, data);
-
- purple_ircv3_connection_writef(connection, "AUTHENTICATE %s",
- next_mechanism);
}
static void
purple_ircv3_sasl_attempt(PurpleIRCv3Connection *connection) {
PurpleIRCv3SASLData *data = NULL;
- PurpleAccount *account = NULL;
const char *next_mechanism = NULL;
- gboolean allow_plain = TRUE;
- gboolean good_mechanism = FALSE;
+ const char *current = NULL;
data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY);
- /* If this is not our first attempt, remove the previous mechanism from the
- * list of mechanisms to try.
- */
- if(data->current_mechanism != NULL) {
- char *to_remove = g_strdup_printf(" %s ", data->current_mechanism);
-
- g_message("SASL '%s' mechanism failed", data->current_mechanism);
-
- g_string_replace(data->mechanisms, to_remove, "", 0);
- g_free(to_remove);
- }
-
- account = purple_connection_get_account(PURPLE_CONNECTION(connection));
- if(!purple_account_get_bool(account, "use-tls", TRUE)) {
- if(!purple_account_get_bool(account, "plain-sasl-in-clear", FALSE)) {
- allow_plain = FALSE;
- }
+ current = hasl_context_get_current_mechanism(data->ctx);
+ if(current != NULL) {
+ g_message("SASL '%s' mechanism failed", current);
}
- while(!good_mechanism) {
- good_mechanism = TRUE;
-
- next_mechanism = gsasl_client_suggest_mechanism(data->ctx,
- data->mechanisms->str);
-
- if(next_mechanism == NULL) {
- GError *error = g_error_new(PURPLE_CONNECTION_ERROR,
- PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
- _("No valid SASL mechanisms found"));
+ next_mechanism = hasl_context_next(data->ctx);
+ if(next_mechanism == NULL) {
+ GError *error = g_error_new(PURPLE_CONNECTION_ERROR,
+ PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
+ _("No valid SASL mechanisms found"));
- purple_connection_take_error(PURPLE_CONNECTION(connection), error);
-
- return;
- }
+ purple_connection_take_error(PURPLE_CONNECTION(connection), error);
- if(purple_strequal(next_mechanism, "PLAIN") && !allow_plain) {
- g_message("skipping SASL 'PLAIN' as it's not allowed without tls");
-
- good_mechanism = FALSE;
-
- g_string_replace(data->mechanisms, " PLAIN ", "", 0);
- }
+ return;
}
g_message("trying SASL '%s' mechanism", next_mechanism);
- purple_ircv3_sasl_attempt_mechanism(connection, data, next_mechanism);
+ purple_ircv3_connection_writef(connection, "AUTHENTICATE %s",
+ next_mechanism);
}
static void
@@ -294,28 +122,22 @@
PurpleIRCv3Connection *connection = NULL;
PurpleAccount *account = NULL;
PurpleConnection *purple_connection = NULL;
- Gsasl *ctx = NULL;
+ HaslContext *ctx = NULL;
const char *mechanisms = NULL;
- gint res;
+ gboolean toggle = FALSE;
connection = purple_ircv3_capabilities_get_connection(caps);
purple_connection = PURPLE_CONNECTION(connection);
account = purple_connection_get_account(purple_connection);
- res = gsasl_init(&ctx);
- if(res != GSASL_OK) {
- purple_ircv3_sasl_connection_error(purple_connection, res,
- PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
- _("Failed to initialize SASL"));
+ ctx = hasl_context_new();
- return;
- }
-
- /* At this point we are ready to start our sasl negotiation, so add a wait
+ /* At this point we are ready to start our SASL negotiation, so add a wait
* counter to the capabilities and start the negotiations!
*/
purple_ircv3_capabilities_add_wait(caps);
+ /* Determine what mechanisms we're allowing and tell the context. */
mechanisms = purple_account_get_string(account, "sasl-mechanisms", "");
if(purple_strempty(mechanisms)) {
/* If the user didn't specify any mechanisms, grab the mechanisms that
@@ -323,9 +145,20 @@
*/
mechanisms = purple_ircv3_capabilities_lookup(caps, "sasl", NULL);
}
+ hasl_context_set_allowed_mechanisms(ctx, mechanisms);
+
+ /* Add the values we know to the context. */
+ hasl_context_set_username(ctx, purple_ircv3_sasl_get_username(purple_connection));
+ hasl_context_set_password(ctx, purple_connection_get_password(purple_connection));
+
+ toggle = purple_account_get_bool(account, "use-tls", TRUE);
+ hasl_context_set_tls(ctx, toggle);
+
+ toggle = purple_account_get_bool(account, "plain-sasl-in-clear", FALSE);
+ hasl_context_set_allow_clear_text(ctx, toggle);
/* Create our SASLData object, add it to the connection. */
- purple_ircv3_sasl_data_add(purple_connection, ctx, mechanisms);
+ purple_ircv3_sasl_data_add(purple_connection, ctx);
/* Make it go! */
purple_ircv3_sasl_attempt(connection);
@@ -477,7 +310,7 @@
purple_ircv3_capabilities_remove_wait(capabilities);
g_message("successfully authenticated with SASL '%s' mechanism.",
- data->current_mechanism);
+ hasl_context_get_current_mechanism(data->ctx));
return TRUE;
}
@@ -679,49 +512,49 @@
}
if(done) {
- char *server_in = NULL;
- char *client_out = NULL;
+ HaslMechanismResult res = 0;
+ GError *local_error = NULL;
+ guint8 *server_in = NULL;
+ guint8 *client_out = NULL;
gsize server_in_length = 0;
size_t client_out_length = 0;
- int res = 0;
/* If we have a buffer, base64 decode it, and then truncate it. */
if(data->server_in_buffer->len > 0) {
- server_in = (char *)g_base64_decode(data->server_in_buffer->str,
- &server_in_length);
+ server_in = g_base64_decode(data->server_in_buffer->str,
+ &server_in_length);
g_string_truncate(data->server_in_buffer, 0);
}
/* Try to move to the next step of the sasl client. */
- res = gsasl_step(data->session, server_in, server_in_length,
- &client_out, &client_out_length);
+ res = hasl_context_step(data->ctx, server_in, server_in_length,
+ &client_out, &client_out_length, &local_error);
/* We should be done with server_in, so free it.*/
g_clear_pointer(&server_in, g_free);
- /* If we didn't get ok or needs more, it's an error.
- *
- * We allow needs more as SCRAM will is client first and returns
- * GSASL_NEEDS_MORE on the first step. We could tie this now a bit
- * more, but this seems to be fine for now. -- GK 2023-01-28
- */
- if(res != GSASL_OK && res != GSASL_NEEDS_MORE) {
- g_set_error(error, PURPLE_CONNECTION_ERROR,
- PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
- _("SASL authentication failed: %s"),
- gsasl_strerror(res));
+ if(res == HASL_MECHANISM_RESULT_ERROR) {
+ g_propagate_error(error, local_error);
return FALSE;
}
+ if(local_error != NULL) {
+ g_warning("hasl_context_step returned an error without an error "
+ "status: %s", local_error->message);
+
+ g_clear_error(&local_error);
+ }
+
/* If we got an output for the client, write it out. */
if(client_out_length > 0) {
- char *encoded = g_base64_encode((guchar *)client_out,
- client_out_length);
+ char *encoded = NULL;
+
+ encoded = g_base64_encode(client_out, client_out_length);
+ g_clear_pointer(&client_out, g_free);
purple_ircv3_connection_writef(connection, "AUTHENTICATE %s",
encoded);
-
g_free(encoded);
} else {
purple_ircv3_connection_writef(connection, "AUTHENTICATE +");
--- a/libpurple/protocols/ircv3/tests/meson.build Thu Feb 23 01:33:50 2023 -0600
+++ b/libpurple/protocols/ircv3/tests/meson.build Thu Feb 23 06:00:48 2023 -0600
@@ -5,7 +5,7 @@
foreach prog : TESTS
e = executable(
f'test_ircv3_@prog@', f'test_ircv3_@prog@.c',
- dependencies : [libpurple_dep, glib, gsasl],
+ dependencies : [libpurple_dep, glib, hasl],
objects : ircv3_prpl.extract_all_objects())
test(f'ircv3_@prog@', e)
--- a/meson.build Thu Feb 23 01:33:50 2023 -0600
+++ b/meson.build Thu Feb 23 06:00:48 2023 -0600
@@ -482,9 +482,9 @@
#######################################################################
# Check for Cyrus-SASL (for xmpp/irc)
#######################################################################
-sasl = dependency('libsasl2', version : '>= 2.0')
+hasl = dependency('hasl', version : '>= 0.1.0')
-gsasl = dependency('libgsasl', version : '>= 1.8.0')
+sasl = dependency('libsasl2', version : '>= 2.0')
#AC_MSG_CHECKING(for me pot o' gold)
#AC_MSG_RESULT(no)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/subprojects/hasl.wrap Thu Feb 23 06:00:48 2023 -0600
@@ -0,0 +1,5 @@
+[wrap-hg]
+directory = hasl
+url = https://keep.imfreedom.org/hasl/hasl/
+revision = 6a247642b28a
+