libpurple/plugins/wincred/wincred.c

Thu, 21 Nov 2024 22:12:24 -0600

author
Elliott Sales de Andrade <quantum.analyst@gmail.com>
date
Thu, 21 Nov 2024 22:12:24 -0600
changeset 43074
0ff905ef6259
parent 43027
581af7cd0603
child 43087
7607f319161f
permissions
-rw-r--r--

Fix uses of PurpleAccount as a PurpleContactInfo

This was changed from is-a to has-a relationship in /r/3157.

Testing Done:
Compiled only (and not even that for `wincred`, tbh)

Reviewed at https://reviews.imfreedom.org/r/3665/

/* purple
 *
 * Purple is the legal property of its developers, whose names are too numerous
 * to list here.  Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
 */

#include <glib/gi18n-lib.h>

#include <gplugin.h>
#include <gplugin-native.h>

#include <purple.h>

#include <wincred.h>

#define WINCRED_ID          "keyring-wincred"
#define WINCRED_NAME        N_("Windows credentials")
#define WINCRED_DESCRIPTION N_("The built-in credential manager for Windows.")

#define WINCRED_MAX_TARGET_NAME 256

/******************************************************************************
 * Globals
 *****************************************************************************/
static PurpleCredentialProvider *instance = NULL;

#define PURPLE_TYPE_WINCRED (purple_wincred_get_type())
G_DECLARE_FINAL_TYPE(PurpleWinCred, purple_wincred, PURPLE, SECRET_SERVICE,
                     PurpleCredentialProvider)

struct _PurpleWinCred {
	PurpleCredentialProvider parent;
};

#define PURPLE_WINCRED_ERROR (g_quark_from_static_string("wincred"))

G_DEFINE_DYNAMIC_TYPE_EXTENDED(PurpleWinCred, purple_wincred,
                               PURPLE_TYPE_CREDENTIAL_PROVIDER,
                               G_TYPE_FLAG_FINAL, {})

/******************************************************************************
 * PurpleCredentialProvider Implementation
 *****************************************************************************/

static gunichar2 *
wincred_get_target_name(PurpleAccount *account, GError **error)
{
	gchar target_name_utf8[WINCRED_MAX_TARGET_NAME];
	gunichar2 *target_name_utf16;

	g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL);

	g_snprintf(target_name_utf8, WINCRED_MAX_TARGET_NAME, "libpurple_%s_%s",
		purple_account_get_protocol_id(account),
		purple_account_get_username(account));

	target_name_utf16 =
	        g_utf8_to_utf16(target_name_utf8, -1, NULL, NULL, error);

	if (target_name_utf16 == NULL) {
		purple_debug_error("keyring-wincred", "Couldn't convert target name");
		return NULL;
	}

	return target_name_utf16;
}

static void
purple_wincred_read_password_async(PurpleCredentialProvider *provider,
                                   PurpleAccount *account,
                                   GCancellable *cancellable,
                                   GAsyncReadyCallback callback, gpointer data)
{
	GTask *task = NULL;
	GError *error = NULL;
	gunichar2 *target_name = NULL;
	gchar *password = NULL;
	PCREDENTIALW credential = NULL;

	task = g_task_new(provider, cancellable, callback, data);
	g_task_set_source_tag(task, purple_wincred_read_password_async);

	target_name = wincred_get_target_name(account, &error);
	if (target_name == NULL) {
		g_task_return_error(task, error);
		g_object_unref(task);
		return;
	}

	if (!CredReadW(target_name, CRED_TYPE_GENERIC, 0, &credential)) {
		DWORD error_code = GetLastError();

		if (error_code == ERROR_NOT_FOUND) {
			if (purple_debug_is_verbose()) {
				purple_debug_misc("keyring-wincred",
				                  "No password found for account %s\n",
				                  purple_account_get_username(account));
			}
			error = g_error_new(PURPLE_WINCRED_ERROR,
				error_code,
				_("Password not found."));
		} else if (error_code == ERROR_NO_SUCH_LOGON_SESSION) {
			purple_debug_error("keyring-wincred",
				"Cannot read password, no valid logon "
				"session\n");
			error = g_error_new(PURPLE_WINCRED_ERROR,
				error_code,
				_("Cannot read password, no valid logon "
				"session."));
		} else {
			purple_debug_error("keyring-wincred",
				"Cannot read password, error %lx\n",
				error_code);
			error = g_error_new(PURPLE_WINCRED_ERROR,
				error_code,
				_("Cannot read password (error %lx)."), error_code);
		}

		g_task_return_error(task, error);
		g_object_unref(task);
		return;
	}

	password = g_utf16_to_utf8((gunichar2*)credential->CredentialBlob,
		credential->CredentialBlobSize / sizeof(gunichar2),
		NULL, NULL, NULL);

	memset(credential->CredentialBlob, 0, credential->CredentialBlobSize);
	CredFree(credential);

	if (password == NULL) {
		purple_debug_error("keyring-wincred",
			"Cannot convert password\n");
		error = g_error_new(PURPLE_WINCRED_ERROR,
			0,
			_("Cannot read password (unicode error)."));
		g_task_return_error(task, error);
		g_object_unref(task);
		return;
	} else {
		purple_debug_misc("keyring-wincred",
		                  _("Got password for account %s.\n"),
		                  purple_account_get_username(account));
	}

	g_task_return_pointer(task, password, g_free);
	g_object_unref(task);
}

static gchar *
purple_wincred_read_password_finish(PurpleCredentialProvider *provider,
                                    GAsyncResult *result, GError **error)
{
	g_return_val_if_fail(PURPLE_IS_CREDENTIAL_PROVIDER(provider), FALSE);
	g_return_val_if_fail(G_IS_ASYNC_RESULT(result), FALSE);

	return g_task_propagate_pointer(G_TASK(result), error);
}

static void
purple_wincred_write_password_async(PurpleCredentialProvider *provider,
                                    PurpleAccount *account,
                                    const gchar *password,
                                    GCancellable *cancellable,
                                    GAsyncReadyCallback callback, gpointer data)
{
	GTask *task = NULL;
	GError *error = NULL;
	const char *username_utf8 = NULL;
	gunichar2 *target_name = NULL;
	gunichar2 *username_utf16 = NULL;
	gunichar2 *password_utf16 = NULL;
	glong password_len = 0;
	CREDENTIALW credential;

	task = g_task_new(provider, cancellable, callback, data);
	g_task_set_source_tag(task, purple_wincred_write_password_async);

	target_name = wincred_get_target_name(account, &error);
	if (target_name == NULL) {
		g_task_return_error(task, error);
		g_object_unref(task);
		return;
	}

	username_utf8 = purple_account_get_username(account);
	username_utf16 = g_utf8_to_utf16(username_utf8, -1, NULL, NULL, &error);
	if (username_utf16 == NULL) {
		g_free(target_name);
		purple_debug_error("keyring-wincred", "Couldn't convert username");
		g_task_return_error(task, error);
		g_object_unref(task);
		return;
	}

	password_utf16 = g_utf8_to_utf16(password, -1, NULL, &password_len, &error);
	if (password_utf16 == NULL) {
		g_free(username_utf16);
		g_free(target_name);
		purple_debug_error("keyring-wincred", "Couldn't convert password");
		g_task_return_error(task, error);
		g_object_unref(task);
		return;
	}

	memset(&credential, 0, sizeof(CREDENTIALW));
	credential.Type = CRED_TYPE_GENERIC;
	credential.TargetName = target_name;
	credential.CredentialBlobSize = password_len * sizeof(gunichar2);
	credential.CredentialBlob = (LPBYTE)password_utf16;
	credential.Persist = CRED_PERSIST_LOCAL_MACHINE;
	credential.UserName = username_utf16;

	if (!CredWriteW(&credential, 0)) {
		DWORD error_code = GetLastError();

		if (error_code == ERROR_NO_SUCH_LOGON_SESSION) {
			purple_debug_error("keyring-wincred",
			                   "Cannot store password, no valid logon session");
			error = g_error_new(
			        PURPLE_WINCRED_ERROR, error_code,
			        _("Cannot remove password, no valid logon session."));
		} else {
			purple_debug_error("keyring-wincred",
				"Cannot store password, error %lx\n",
				error_code);
			error = g_error_new(PURPLE_WINCRED_ERROR, error_code,
				_("Cannot store password (error %lx)."), error_code);
		}
	} else {
		purple_debug_misc("keyring-wincred", "Password updated for account %s.",
		                  username_utf8);
	}

	g_free(target_name);
	g_free(username_utf16);
	g_free(password_utf16);

	if (error != NULL) {
		g_task_return_error(task, error);
	} else {
		g_task_return_boolean(task, TRUE);
	}

	g_object_unref(task);
}

static gboolean
purple_wincred_write_password_finish(PurpleCredentialProvider *provider,
                                     GAsyncResult *result, GError **error)
{
	g_return_val_if_fail(PURPLE_IS_CREDENTIAL_PROVIDER(provider), FALSE);
	g_return_val_if_fail(G_IS_ASYNC_RESULT(result), FALSE);

	return g_task_propagate_boolean(G_TASK(result), error);
}

static void
purple_wincred_clear_password_async(PurpleCredentialProvider *provider,
                                    PurpleAccount *account,
                                    GCancellable *cancellable,
                                    GAsyncReadyCallback callback, gpointer data)
{
	GTask *task = NULL;
	GError *error = NULL;
	gunichar2 *target_name = NULL;

	task = g_task_new(provider, cancellable, callback, data);
	g_task_set_source_tag(task, purple_wincred_clear_password_async);

	target_name = wincred_get_target_name(account, &error);
	if (target_name == NULL) {
		g_task_return_error(task, error);
		g_object_unref(task);
		return;
	}

	if (CredDeleteW(target_name, CRED_TYPE_GENERIC, 0)) {
		purple_debug_misc("keyring-wincred", "Password for account %s removed",
		                  purple_account_get_username(account));
		g_task_return_boolean(task, TRUE);
	} else {
		DWORD error_code = GetLastError();

		if (error_code == ERROR_NOT_FOUND) {
			/* If there was no password we just return TRUE. */
			g_task_return_boolean(task, TRUE);
		} else {
			if (error_code == ERROR_NO_SUCH_LOGON_SESSION) {
				purple_debug_error(
				        "keyring-wincred",
				        "Cannot remove password, no valid logon session");
				error = g_error_new(
				        PURPLE_WINCRED_ERROR, error_code,
				        _("Cannot remove password, no valid logon session."));
			} else {
				purple_debug_error("keyring-wincred",
				                   "Cannot remove password, error %lx", error_code);
				error = g_error_new(
				        PURPLE_WINCRED_ERROR, error_code,
				        _("Cannot remove password (error %lx)."), error_code);
			}

			g_task_return_error(task, error);
		}
	}

	g_object_unref(task);
}

static gboolean
purple_wincred_clear_password_finish(PurpleCredentialProvider *provider,
                                     GAsyncResult *result, GError **error)
{
	g_return_val_if_fail(PURPLE_IS_CREDENTIAL_PROVIDER(provider), FALSE);
	g_return_val_if_fail(G_IS_ASYNC_RESULT(result), FALSE);

	return g_task_propagate_boolean(G_TASK(result), error);
}

/******************************************************************************
 * GObject Implementation
 *****************************************************************************/
static void
purple_wincred_init(PurpleWinCred *wincred)
{
}

static void
purple_wincred_class_init(PurpleWinCredClass *klass)
{
	PurpleCredentialProviderClass *provider_class = NULL;

	provider_class = PURPLE_CREDENTIAL_PROVIDER_CLASS(klass);
	provider_class->read_password_async = purple_wincred_read_password_async;
	provider_class->read_password_finish = purple_wincred_read_password_finish;
	provider_class->write_password_async = purple_wincred_write_password_async;
	provider_class->write_password_finish =
	        purple_wincred_write_password_finish;
	provider_class->clear_password_async = purple_wincred_clear_password_async;
	provider_class->clear_password_finish =
	        purple_wincred_clear_password_finish;
}

static void
purple_wincred_class_finalize(PurpleWinCredClass *klass)
{
}

/******************************************************************************
 * API
 *****************************************************************************/
static PurpleCredentialProvider *
purple_wincred_new(void)
{
	return g_object_new(
		PURPLE_TYPE_WINCRED,
		"id", WINCRED_ID,
		"name", _(WINCRED_NAME),
		"description", _(WINCRED_DESCRIPTION),
		NULL);
}

/******************************************************************************
 * Plugin Exports
 *****************************************************************************/
static GPluginPluginInfo *
wincred_query(G_GNUC_UNUSED GError **error) {
	const gchar * const authors[] = {
		"Pidgin Developers <devel@pidgin.im>",
		NULL
	};

	return GPLUGIN_PLUGIN_INFO(purple_plugin_info_new(
		"id",           WINCRED_ID,
		"name",         WINCRED_NAME,
		"version",      DISPLAY_VERSION,
		"category",     _("Keyring"),
		"summary",      _("Store passwords using Windows credentials"),
		"description",  _("This plugin stores passwords using Windows credentials."),
		"authors",      authors,
		"website",      PURPLE_WEBSITE,
		"abi-version",  PURPLE_ABI_VERSION,
		"flags",        PURPLE_PLUGIN_INFO_FLAGS_INTERNAL |
		                PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD,
		NULL
	));
}

static gboolean
wincred_load(GPluginPlugin *plugin, GError **error) {
	PurpleCredentialManager *manager = NULL;

	purple_wincred_register_type(G_TYPE_MODULE(plugin));

	manager = purple_credential_manager_get_default();

	instance = purple_wincred_new();

	return purple_credential_manager_add(manager, instance, error);
}

static gboolean
wincred_unload(G_GNUC_UNUSED GPluginPlugin *plugin,
               G_GNUC_UNUSED gboolean shutdown,
               GError **error)
{
	PurpleCredentialManager *manager = NULL;
	gboolean ret = FALSE;

	manager = purple_credential_manager_get_default();
	ret = purple_credential_manager_remove(manager, instance, error);
	if (!ret) {
		return ret;
	}

	g_clear_object(&instance);

	return TRUE;
}

GPLUGIN_NATIVE_PLUGIN_DECLARE(wincred)

mercurial