Implement SASL for IRCv3 using GSASL
Right now we really only tested PLAIN but SCRAM _might_ work as well as
external. But we'll need to connect to an external server to test that stuff as
it's really a bit of work to get set up locally.
Testing Done:
Connected to a local ergo instance and verified everything there using the PLAIN mechanism.
Reviewed at https://reviews.imfreedom.org/r/2185/
--- a/libpurple/protocols/ircv3/meson.build Mon Jan 16 22:08:46 2023 -0600
+++ b/libpurple/protocols/ircv3/meson.build Tue Jan 17 01:20:45 2023 -0600
@@ -6,6 +6,7 @@
'purpleircv3protocolim.c',
@@ -16,6 +17,7 @@
'purpleircv3protocolim.h',
@@ -27,7 +29,7 @@
ircv3_prpl = shared_library('ircv3', IRCV3_SOURCES + IRCV3_HEADERS,
c_args : ['-DG_LOG_USE_STRUCTURED', '-DG_LOG_DOMAIN="Purple-IRCv3"'],
- dependencies : [sasl, libpurple_dep, glib, gio, ws2_32],
+ dependencies : [libpurple_dep, glib, gio, gsasl, ws2_32], install_dir : PURPLE_PLUGINDIR,
override_options : ['c_std=c99', 'warning_level=2'])
--- a/libpurple/protocols/ircv3/purpleircv3capabilities.c Mon Jan 16 22:08:46 2023 -0600
+++ b/libpurple/protocols/ircv3/purpleircv3capabilities.c Tue Jan 17 01:20:45 2023 -0600
@@ -20,6 +20,7 @@
#include "purpleircv3connection.h"
#include "purpleircv3core.h"
+#include "purpleircv3sasl.h" #define PURPLE_IRCV3_CAPABILITIES_VERSION "302"
@@ -105,8 +106,23 @@
purple_ircv3_capabilities_default_ready_cb(PurpleIRCv3Capabilities *capabilities)
+ PurpleAccount *account = NULL; + PurpleConnection *purple_connection = NULL; + purple_connection = PURPLE_CONNECTION(capabilities->connection); + account = purple_connection_get_account(purple_connection); + /* Don't request the sasl capability unless the user has selected the + * require-password option. + if(purple_account_get_require_password(account)) { + purple_ircv3_capabilities_lookup(capabilities, "sasl", &found); + purple_ircv3_sasl_request(capabilities); /* cap-notify is implied when we use CAP LS 302, so this is really just to
* make sure it's requested.
--- a/libpurple/protocols/ircv3/purpleircv3connection.c Mon Jan 16 22:08:46 2023 -0600
+++ b/libpurple/protocols/ircv3/purpleircv3connection.c Tue Jan 17 01:20:45 2023 -0600
@@ -151,7 +151,7 @@
parsed = purple_ircv3_parser_parse(connection->parser, line, &error,
- g_warning("failed to parse '%s': %s", line,
+ g_warning("failed to handle '%s': %s", line, error != NULL ? error->message : "unknown error");
--- a/libpurple/protocols/ircv3/purpleircv3connection.h Mon Jan 16 22:08:46 2023 -0600
+++ b/libpurple/protocols/ircv3/purpleircv3connection.h Tue Jan 17 01:20:45 2023 -0600
@@ -25,6 +25,8 @@
#include <gplugin-native.h>
--- a/libpurple/protocols/ircv3/purpleircv3parser.c Mon Jan 16 22:08:46 2023 -0600
+++ b/libpurple/protocols/ircv3/purpleircv3parser.c Tue Jan 17 01:20:45 2023 -0600
@@ -21,6 +21,7 @@
#include "purpleircv3capabilities.h"
#include "purpleircv3core.h"
#include "purpleircv3messagehandlers.h"
+#include "purpleircv3sasl.h" struct _PurpleIRCv3Parser {
@@ -398,6 +399,7 @@
purple_ircv3_parser_set_fallback_handler(parser,
purple_ircv3_message_handler_fallback);
+ /* Core functionality. */ purple_ircv3_parser_add_handler(parser, "CAP",
purple_ircv3_capabilities_message_handler);
purple_ircv3_parser_add_handler(parser, "NOTICE",
@@ -406,4 +408,26 @@
purple_ircv3_message_handler_ping);
purple_ircv3_parser_add_handler(parser, "PRIVMSG",
purple_ircv3_message_handler_privmsg);
+ purple_ircv3_parser_add_handler(parser, "900", + purple_ircv3_sasl_logged_in); + purple_ircv3_parser_add_handler(parser, "901", + purple_ircv3_sasl_logged_out); + purple_ircv3_parser_add_handler(parser, "902", + purple_ircv3_sasl_nick_locked); + purple_ircv3_parser_add_handler(parser, "903", + purple_ircv3_sasl_success); + purple_ircv3_parser_add_handler(parser, "904", + purple_ircv3_sasl_failed); + purple_ircv3_parser_add_handler(parser, "905", + purple_ircv3_sasl_message_too_long); + purple_ircv3_parser_add_handler(parser, "906", + purple_ircv3_sasl_aborted); + purple_ircv3_parser_add_handler(parser, "907", + purple_ircv3_sasl_already_authed); + purple_ircv3_parser_add_handler(parser, "908", + purple_ircv3_sasl_mechanisms); + purple_ircv3_parser_add_handler(parser, "AUTHENTICATE", + purple_ircv3_sasl_authenticate); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/ircv3/purpleircv3sasl.c Tue Jan 17 01:20:45 2023 -0600
@@ -0,0 +1,695 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. +#include <glib/gi18n-lib.h> +#include "purpleircv3sasl.h" +#include "purpleircv3capabilities.h" +#include "purpleircv3connection.h" +#include "purpleircv3core.h" +#define PURPLE_IRCV3_SASL_DATA_KEY ("sasl-data") + PurpleConnection *connection; + Gsasl_session *session; + char *current_mechanism; + GString *server_in_buffer; +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_sasl_get_username(PurpleConnection *connection) { + PurpleAccount *account = NULL; + const char *username = NULL; + account = purple_connection_get_account(connection); + username = purple_account_get_string(account, "sasl-login-name", ""); + if(username != NULL && username[0] != '\0') { + return purple_connection_get_display_name(connection); +/****************************************************************************** + *****************************************************************************/ +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); + res = gsasl_property_set(session, GSASL_AUTHID, + purple_ircv3_sasl_get_username(data->connection)); + /* AUTHZID is typically set to empty string because it's the user + * logging in on their own behalf. Since IRCv3 doesn't really let + * an admin login as a normal user, we always set it to empty + * See https://www.gnu.org/software/gsasl/manual/gsasl.html#PLAIN + * for further explanation. + res = gsasl_property_set(session, GSASL_AUTHZID, ""); + res = gsasl_property_set(session, GSASL_PASSWORD, + purple_connection_get_password(data->connection)); + g_warning("Unknown property %d", property); +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_sasl_connection_error(PurpleConnection *connection, int res, + int err, const char *msg) + error = g_error_new(PURPLE_CONNECTION_ERROR, err, "%s: %s", msg, + purple_connection_take_error(connection, error); +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; +purple_ircv3_sasl_data_add(PurpleConnection *connection, Gsasl *ctx, + const char *mechanisms) + PurpleIRCv3SASLData *data = NULL; + data = g_new0(PurpleIRCv3SASLData, 1); + g_object_set_data_full(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY, + data, (GDestroyNotify)purple_ircv3_sasl_data_free); + /* We don't reference this because the life cycle of this data is tied + * directly to the connection and adding a reference to the connection + * would keep both alive forever. + data->connection = connection; + gsasl_callback_set(data->ctx, purple_ircv3_sasl_callback); + /* 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]); +purple_ircv3_sasl_attempt_mechanism(PurpleIRCv3Connection *connection, + PurpleIRCv3SASLData *data, + const char *next_mechanism) + g_free(data->current_mechanism); + data->current_mechanism = g_strdup(next_mechanism); + res = gsasl_client_start(data->ctx, next_mechanism, &data->session); + purple_ircv3_sasl_connection_error(PURPLE_CONNECTION(connection), + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + _("Failed to setup SASL client")); + gsasl_session_hook_set(data->session, data); + purple_ircv3_connection_writef(connection, "AUTHENTICATE %s", +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; + 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); + 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)) { + while(!good_mechanism) { + 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")); + 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); + g_message("trying SASL '%s' mechanism", next_mechanism); + purple_ircv3_sasl_attempt_mechanism(connection, data, next_mechanism); +purple_ircv3_sasl_start(PurpleIRCv3Capabilities *caps) { + PurpleIRCv3Connection *connection = NULL; + PurpleConnection *purple_connection = NULL; + const char *advertised = NULL; + connection = purple_ircv3_capabilities_get_connection(caps); + purple_connection = PURPLE_CONNECTION(connection); + res = gsasl_init(&ctx); + purple_ircv3_sasl_connection_error(purple_connection, res, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + _("Failed to initialize SASL")); + /* 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); + /* Grab the mechanisms that the server advertised and save them on the + advertised = purple_ircv3_capabilities_lookup(caps, "sasl", NULL); + /* Create our SASLData object, add it to the connection. */ + purple_ircv3_sasl_data_add(purple_connection, ctx, advertised); + purple_ircv3_sasl_attempt(connection); +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_sasl_ack_cb(PurpleIRCv3Capabilities *caps, const char *capability, + G_GNUC_UNUSED gpointer data) + if(!purple_strequal(capability, "sasl")) { + purple_ircv3_sasl_start(caps); +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_sasl_request(PurpleIRCv3Capabilities *capabilities) { + purple_ircv3_capabilities_request(capabilities, "sasl"); + g_signal_connect(capabilities, "ack", + G_CALLBACK(purple_ircv3_sasl_ack_cb), NULL); +purple_ircv3_sasl_logged_in(G_GNUC_UNUSED GHashTable *tags, + G_GNUC_UNUSED const char *source, + G_GNUC_UNUSED const char *command, + G_GNUC_UNUSED guint n_params, + G_GNUC_UNUSED GStrv params, + G_GNUC_UNUSED GError **error, + PurpleIRCv3Connection *connection = user_data; + PurpleIRCv3SASLData *data = NULL; + data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY); + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "RPL_LOGGEDIN received with no SASL data " + /* At this point, we have the users authenticated username, we _may_ want + * to update the account's ID to this, but we'll need more testing to +purple_ircv3_sasl_logged_out(G_GNUC_UNUSED GHashTable *tags, + G_GNUC_UNUSED const char *source, + G_GNUC_UNUSED const char *command, + G_GNUC_UNUSED guint n_params, + G_GNUC_UNUSED GStrv params, + G_GNUC_UNUSED GError **error, + PurpleIRCv3Connection *connection = user_data; + PurpleIRCv3SASLData *data = NULL; + data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY); + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "RPL_LOGGEDOUT received with no SASL data " + /* Not sure how to trigger this or what we should do in this case to be + * honest, so just note it for now. + g_warning("Server sent SASL logged out"); +purple_ircv3_sasl_nick_locked(G_GNUC_UNUSED GHashTable *tags, + G_GNUC_UNUSED const char *source, + G_GNUC_UNUSED const char *command, + G_GNUC_UNUSED guint n_params, + PurpleIRCv3Connection *connection = user_data; + PurpleIRCv3SASLData *data = NULL; + data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY); + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "ERR_NICKLOCKED received with no SASL data " + message = g_strjoinv(" ", params); + g_set_error(error, PURPLE_CONNECTION_ERROR, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + _("Nick name is locked: %s"), message); +purple_ircv3_sasl_success(G_GNUC_UNUSED GHashTable *tags, + G_GNUC_UNUSED const char *source, + G_GNUC_UNUSED const char *command, + G_GNUC_UNUSED guint n_params, + G_GNUC_UNUSED GStrv params, + PurpleIRCv3Capabilities *capabilities = NULL; + PurpleIRCv3Connection *connection = user_data; + PurpleIRCv3SASLData *data = NULL; + capabilities = purple_ircv3_connection_get_capabilities(connection); + data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY); + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "RPL_SASLSUCCESS received with no SASL data " + /* This needs to be after we've checked the SASL data otherwise we might + * end up removing a wait that we don't own. + purple_ircv3_capabilities_remove_wait(capabilities); + g_message("successfully authenticated with SASL '%s' mechanism.", + data->current_mechanism); +purple_ircv3_sasl_failed(G_GNUC_UNUSED GHashTable *tags, + G_GNUC_UNUSED const char *source, + G_GNUC_UNUSED const char *command, + G_GNUC_UNUSED guint n_params, + G_GNUC_UNUSED GStrv params, + G_GNUC_UNUSED GError **error, + PurpleIRCv3Connection *connection = user_data; + PurpleIRCv3SASLData *data = NULL; + data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY); + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "ERR_SASLFAIL received with no SASL data present"); + purple_ircv3_sasl_attempt(connection); +purple_ircv3_sasl_message_too_long(G_GNUC_UNUSED GHashTable *tags, + G_GNUC_UNUSED const char *source, + G_GNUC_UNUSED const char *command, + G_GNUC_UNUSED guint n_params, + G_GNUC_UNUSED GStrv params, + G_GNUC_UNUSED GError **error, + PurpleIRCv3Connection *connection = user_data; + PurpleIRCv3SASLData *data = NULL; + data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY); + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "ERR_SASLTOOLONG received with no SASL data " +purple_ircv3_sasl_aborted(G_GNUC_UNUSED GHashTable *tags, + G_GNUC_UNUSED const char *source, + G_GNUC_UNUSED const char *command, + G_GNUC_UNUSED guint n_params, + G_GNUC_UNUSED GStrv params, + G_GNUC_UNUSED GError **error, + PurpleIRCv3Connection *connection = user_data; + PurpleIRCv3SASLData *data = NULL; + data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY); + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "ERR_SASLABORTED received with no SASL 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...? + g_warning("The server claims we aborted SASL authentication."); +purple_ircv3_sasl_already_authed(G_GNUC_UNUSED GHashTable *tags, + G_GNUC_UNUSED const char *source, + G_GNUC_UNUSED const char *command, + G_GNUC_UNUSED guint n_params, + G_GNUC_UNUSED GStrv params, + G_GNUC_UNUSED GError **error, + PurpleIRCv3Connection *connection = user_data; + PurpleIRCv3SASLData *data = NULL; + data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY); + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "ERR_SASLALREADY received with no SASL data " + /* Similar to aborted above, we don't allow this, so just note that it + g_warning("Server claims we tried to SASL authenticate again."); +purple_ircv3_sasl_mechanisms(G_GNUC_UNUSED GHashTable *tags, + G_GNUC_UNUSED const char *source, + G_GNUC_UNUSED const char *command, + G_GNUC_UNUSED GError **error, + PurpleIRCv3Connection *connection = user_data; + PurpleIRCv3SASLData *data = NULL; + data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY); + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "RPL_SASLMECHS received with no SASL data " + /* We need to find a server that sends this message. The specification says + * it _may_ be sent when the client sends AUTHENTICATE with an unknown + * mechanism, but ergo doesn't. + * We can't just blindly accept the new list either, as depending on how + * the server implements it, we'll need to remove mechanisms we've already + * tried in the event the server just dumps the entire list. As we're not + * currently tracking which mechanisms we've tried, this will have to be + char *message = g_strjoinv(" ", params); + g_message("Server sent the following SASL mechanisms: %s", message); + g_message("Server sent an empty list of SASL mechanisms"); +purple_ircv3_sasl_authenticate(G_GNUC_UNUSED GHashTable *tags, + G_GNUC_UNUSED const char *source, + G_GNUC_UNUSED const char *command, + PurpleIRCv3Connection *connection = user_data; + PurpleIRCv3SASLData *data = NULL; + g_set_error(error, PURPLE_IRCV3_DOMAIN, 0, + "ignoring AUTHENTICATE with %d parameters", n_params); + data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY); + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "AUTHENTICATE received with no SASL data present"); + /* If the server sent us a payload, combine the chunks. */ + if(payload[0] != '+') { + if(data->server_in_buffer == NULL) { + data->server_in_buffer = g_string_new(payload); + g_string_append(data->server_in_buffer, payload); + if(strlen(payload) < 400) { + /* The server sent a + which is an empty message or the final message + * ended on a 400 byte barrier. */ + char *server_in = NULL; + char *client_out = NULL; + gsize server_in_length = 0; + size_t client_out_length = 0; + /* If we have a buffer, base64 decode it, and then free it. */ + if(data->server_in_buffer != NULL) { + server_in = (char *)g_base64_decode(data->server_in_buffer->str, + g_string_free(data->server_in_buffer, TRUE); + /* 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); + /* We should be done with server_in, so free it.*/ + g_clear_pointer(&server_in, g_free); + /* If we didn't get ok or continue, it's an error. */ + g_set_error(error, PURPLE_CONNECTION_ERROR, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + _("SASL authentication failed: %s"), + /* If we got an output for the client, write it out. */ + if(client_out_length > 0) { + char *encoded = g_base64_encode((guchar *)client_out, + purple_ircv3_connection_writef(connection, "AUTHENTICATE %s", + purple_ircv3_connection_writef(connection, "AUTHENTICATE +"); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/ircv3/purpleircv3sasl.h Tue Jan 17 01:20:45 2023 -0600
@@ -0,0 +1,45 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. +#ifndef PURPLE_IRCV3_SASL_H +#define PURPLE_IRCV3_SASL_H +#include "purpleircv3capabilities.h" +G_GNUC_INTERNAL void purple_ircv3_sasl_request(PurpleIRCv3Capabilities *capabilities); +G_GNUC_INTERNAL gboolean purple_ircv3_sasl_logged_in(GHashTable *tags, const char *source, const char *command, guint n_params, GStrv params, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_sasl_logged_out(GHashTable *tags, const char *source, const char *command, guint n_params, GStrv params, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_sasl_nick_locked(GHashTable *tags, const char *source, const char *command, guint n_params, GStrv params, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_sasl_success(GHashTable *tags, const char *source, const char *command, guint n_params, GStrv params, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_sasl_failed(GHashTable *tags, const char *source, const char *command, guint n_params, GStrv params, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_sasl_message_too_long(GHashTable *tags, const char *source, const char *command, guint n_params, GStrv params, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_sasl_aborted(GHashTable *tags, const char *source, const char *command, guint n_params, GStrv params, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_sasl_already_authed(GHashTable *tags, const char *source, const char *command, guint n_params, GStrv params, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_sasl_mechanisms(GHashTable *tags, const char *source, const char *command, guint n_params, GStrv params, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_sasl_authenticate(GHashTable *tags, const char *source, const char *command, guint n_params, GStrv params, GError **error, gpointer data); +#endif /* PURPLE_IRCV3_SASL_H */ --- a/libpurple/protocols/ircv3/tests/meson.build Mon Jan 16 22:08:46 2023 -0600
+++ b/libpurple/protocols/ircv3/tests/meson.build Tue Jan 17 01:20:45 2023 -0600
@@ -5,7 +5,7 @@
f'test_ircv3_@prog@', f'test_ircv3_@prog@.c',
- dependencies : [libpurple_dep, glib],
+ dependencies : [libpurple_dep, glib, gsasl], objects : ircv3_prpl.extract_all_objects())
--- a/meson.build Mon Jan 16 22:08:46 2023 -0600
+++ b/meson.build Tue Jan 17 01:20:45 2023 -0600
@@ -483,6 +483,8 @@
#######################################################################
sasl = dependency('libsasl2', version : '>= 2.0')
+gsasl = dependency('libgsasl', version : '>= 2.0') #AC_MSG_CHECKING(for me pot o' gold)