New keyring: store passwords using Windows credentials soc.2008.masterpassword

Mon, 29 Apr 2013 03:35:54 +0200

author
Tomasz Wasilczyk <tomkiewicz@cpw.pidgin.im>
date
Mon, 29 Apr 2013 03:35:54 +0200
branch
soc.2008.masterpassword
changeset 34171
a23f3228c465
parent 34170
94102637e7c7
child 34172
db03a0efc8b4

New keyring: store passwords using Windows credentials

libpurple/plugins/keyrings/Makefile.mingw file | annotate | diff | comparison | revisions
libpurple/plugins/keyrings/wincred.c file | annotate | diff | comparison | revisions
libpurple/util.c file | annotate | diff | comparison | revisions
libpurple/util.h file | annotate | diff | comparison | revisions
--- a/libpurple/plugins/keyrings/Makefile.mingw	Sun Apr 28 15:55:57 2013 +0200
+++ b/libpurple/plugins/keyrings/Makefile.mingw	Mon Apr 29 03:35:54 2013 +0200
@@ -11,6 +11,7 @@
 ## VARIABLE DEFINITIONS
 ##
 TARGET_INTERNAL = internalkeyring
+TARGET_WINCRED = wincred
 
 ##
 ## INCLUDE PATHS
@@ -34,6 +35,9 @@
 C_SRC_INTERNAL = internalkeyring.c
 OBJECTS_INTERNAL = $(C_SRC_INTERNAL:%.c=%.o)
 
+C_SRC_WINCRED = wincred.c
+OBJECTS_WINCRED = $(C_SRC_WINCRED:%.c=%.o)
+
 ##
 ## LIBRARIES
 ##
@@ -50,10 +54,11 @@
 ##
 .PHONY: all install clean
 
-all: $(TARGET_INTERNAL).dll
+all: $(TARGET_INTERNAL).dll $(TARGET_WINCRED).dll
 
 install: all $(PURPLE_INSTALL_PLUGINS_DIR) $(PURPLE_INSTALL_DIR)
 	cp $(TARGET_INTERNAL).dll $(PURPLE_INSTALL_PLUGINS_DIR)
+	cp $(TARGET_WINCRED).dll $(PURPLE_INSTALL_PLUGINS_DIR)
 
 $(OBJECTS_INTERNAL): $(PURPLE_CONFIG_H)
 
@@ -63,10 +68,14 @@
 $(TARGET_INTERNAL).dll: $(PURPLE_DLL) $(OBJECTS_INTERNAL)
 	$(CC) -shared $(OBJECTS_INTERNAL) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET_INTERNAL).dll
 
+$(TARGET_WINCRED).dll: $(PURPLE_DLL) $(OBJECTS_WINCRED)
+	$(CC) -shared $(OBJECTS_WINCRED) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET_WINCRED).dll
+
 ##
 ## CLEAN RULES
 ##
 clean:
 	rm -f $(OBJECTS_INTERNAL) $(TARGET_INTERNAL).dll
+	rm -f $(OBJECTS_WINCRED) $(TARGET_WINCRED).dll
 
 include $(PIDGIN_COMMON_TARGETS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/plugins/keyrings/wincred.c	Mon Apr 29 03:35:54 2013 +0200
@@ -0,0 +1,307 @@
+/**
+ * @file wincred.c Passwords storage using Windows credentials
+ * @ingroup plugins
+ */
+
+/* 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 "debug.h"
+#include "internal.h"
+#include "keyring.h"
+#include "plugin.h"
+#include "version.h"
+
+#include <wincred.h>
+
+#define WINCRED_NAME        N_("Windows credentials")
+#define WINCRED_SUMMARY     N_("Store passwords using Windows credentials")
+#define WINCRED_DESCRIPTION N_("This plugin stores passwords using Windows " \
+	"credentials.")
+#define WINCRED_AUTHOR      "Tomek Wasilczyk (tomkiewicz@cpw.pidgin.im)"
+#define WINCRED_ID          "keyring-wincred"
+
+#define WINCRED_MAX_TARGET_NAME 256
+
+static PurpleKeyring *keyring_handler = NULL;
+
+static gunichar2 *
+wincred_get_target_name(PurpleAccount *account)
+{
+	gchar target_name_utf8[WINCRED_MAX_TARGET_NAME];
+	gunichar2 *target_name_utf16;
+
+	g_return_val_if_fail(account != NULL, 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, NULL);
+
+	if (target_name_utf16 == NULL) {
+		purple_debug_fatal("keyring-wincred", "Couldn't convert target "
+			"name\n");
+	}
+
+	return target_name_utf16;
+}
+
+static void
+wincred_read(PurpleAccount *account, PurpleKeyringReadCallback cb,
+	gpointer data)
+{
+	GError *error = NULL;
+	gunichar2 *target_name = NULL;
+	gchar *password;
+	PCREDENTIALW credential;
+
+	g_return_if_fail(account != NULL);
+
+	target_name = wincred_get_target_name(account);
+	g_return_if_fail(target_name != NULL);
+
+	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_KEYRING_ERROR,
+				PURPLE_KEYRING_ERROR_NOPASSWORD,
+				"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_KEYRING_ERROR,
+				PURPLE_KEYRING_ERROR_ACCESSDENIED,
+				"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_KEYRING_ERROR,
+				PURPLE_KEYRING_ERROR_BACKENDFAIL,
+				"Cannot read password, error %lx", error_code);
+		}
+
+		if (cb != NULL)
+			cb(account, NULL, error, data);
+		g_error_free(error);
+		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_KEYRING_ERROR,
+			PURPLE_KEYRING_ERROR_BACKENDFAIL,
+			"Cannot convert password");
+	}
+
+	if (cb != NULL)
+		cb(account, password, error, data);
+	if (error != NULL)
+		g_error_free(error);
+
+	purple_str_wipe(password);
+}
+
+static void
+wincred_save(PurpleAccount *account, const gchar *password,
+	PurpleKeyringSaveCallback cb, gpointer data)
+{
+	GError *error = NULL;
+	gunichar2 *target_name = NULL;
+	gunichar2 *username_utf16 = NULL;
+	gunichar2 *password_utf16 = NULL;
+	CREDENTIALW credential;
+
+	g_return_if_fail(account != NULL);
+
+	target_name = wincred_get_target_name(account);
+	g_return_if_fail(target_name != NULL);
+
+	if (password == NULL)
+	{
+		if (CredDeleteW(target_name, CRED_TYPE_GENERIC, 0)) {
+			purple_debug_misc("keyring-wincred", "Password for "
+				"account %s removed\n",
+				purple_account_get_username(account));
+		} else {
+			DWORD error_code = GetLastError();
+
+			if (error_code == ERROR_NOT_FOUND) {
+				/* Pasword doesn't existed. */
+			} else if (error_code == ERROR_NO_SUCH_LOGON_SESSION) {
+				purple_debug_error("keyring-wincred",
+					"Cannot remove password, no valid "
+					"logon session\n");
+				error = g_error_new(PURPLE_KEYRING_ERROR,
+					PURPLE_KEYRING_ERROR_ACCESSDENIED,
+					"Cannot remove password, no valid "
+					"logon session");
+			} else {
+				purple_debug_error("keyring-wincred",
+					"Cannot remove password, error %lx\n",
+					error_code);
+				error = g_error_new(PURPLE_KEYRING_ERROR,
+					PURPLE_KEYRING_ERROR_BACKENDFAIL,
+					"Cannot remove password, error %lx",
+					error_code);
+			}
+		}
+
+		if (cb != NULL)
+			cb(account, error, data);
+		if (error != NULL)
+			g_error_free(error);
+		return;
+	}
+
+	username_utf16 = g_utf8_to_utf16(purple_account_get_username(account),
+		-1, NULL, NULL, NULL);
+	password_utf16 = g_utf8_to_utf16(password, -1, NULL, NULL, NULL);
+
+	if (username_utf16 == NULL || password_utf16 == NULL) {
+		g_free(username_utf16);
+		purple_utf16_wipe(password_utf16);
+
+		purple_debug_fatal("keyring-wincred", "Couldn't convert "
+			"username or password\n");
+		g_return_if_reached();
+	}
+
+	memset(&credential, 0, sizeof(CREDENTIALW));
+	credential.Type = CRED_TYPE_GENERIC;
+	credential.TargetName = target_name;
+	credential.CredentialBlobSize = purple_utf16_size(password_utf16) - 2;
+	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\n");
+			error = g_error_new(PURPLE_KEYRING_ERROR,
+				PURPLE_KEYRING_ERROR_ACCESSDENIED,
+				"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_KEYRING_ERROR,
+				PURPLE_KEYRING_ERROR_BACKENDFAIL,
+				"Cannot store password, error %lx", error_code);
+		}
+	}
+
+	g_free(target_name);
+	g_free(username_utf16);
+	purple_utf16_wipe(password_utf16);
+
+	if (cb != NULL)
+		cb(account, error, data);
+	if (error != NULL)
+		g_error_free(error);
+}
+
+static gboolean
+wincred_load(PurplePlugin *plugin)
+{
+	keyring_handler = purple_keyring_new();
+
+	purple_keyring_set_name(keyring_handler, WINCRED_NAME);
+	purple_keyring_set_id(keyring_handler, WINCRED_ID);
+	purple_keyring_set_read_password(keyring_handler, wincred_read);
+	purple_keyring_set_save_password(keyring_handler, wincred_save);
+
+	purple_keyring_register(keyring_handler);
+
+	return TRUE;
+}
+
+static gboolean
+wincred_unload(PurplePlugin *plugin)
+{
+	if (purple_keyring_get_inuse() == keyring_handler) {
+		purple_debug_warning("keyring-wincred",
+			"keyring in use, cannot unload\n");
+		return FALSE;
+	}
+
+	purple_keyring_unregister(keyring_handler);
+	purple_keyring_free(keyring_handler);
+	keyring_handler = NULL;
+
+	return TRUE;
+}
+
+PurplePluginInfo plugininfo =
+{
+	PURPLE_PLUGIN_MAGIC,		/* magic */
+	PURPLE_MAJOR_VERSION,		/* major_version */
+	PURPLE_MINOR_VERSION,		/* minor_version */
+	PURPLE_PLUGIN_STANDARD,		/* type */
+	NULL,				/* ui_requirement */
+	PURPLE_PLUGIN_FLAG_INVISIBLE,	/* flags */
+	NULL,				/* dependencies */
+	PURPLE_PRIORITY_DEFAULT,	/* priority */
+	WINCRED_ID,			/* id */
+	WINCRED_NAME,			/* name */
+	DISPLAY_VERSION,		/* version */
+	WINCRED_SUMMARY,		/* summary */
+	WINCRED_DESCRIPTION,		/* description */
+	WINCRED_AUTHOR,			/* author */
+	PURPLE_WEBSITE,			/* homepage */
+	wincred_load,			/* load */
+	wincred_unload,			/* unload */
+	NULL,				/* destroy */
+	NULL,				/* ui_info */
+	NULL,				/* extra_info */
+	NULL,				/* prefs_info */
+	NULL,				/* actions */
+	NULL, NULL, NULL, NULL		/* padding */
+};
+
+static void
+init_plugin(PurplePlugin *plugin)
+{
+}
+
+PURPLE_INIT_PLUGIN(wincred_keyring, init_plugin, plugininfo)
--- a/libpurple/util.c	Sun Apr 28 15:55:57 2013 +0200
+++ b/libpurple/util.c	Mon Apr 29 03:35:54 2013 +0200
@@ -3762,6 +3762,23 @@
 	return g_string_free(ret, FALSE);
 }
 
+size_t
+purple_utf16_size(const gunichar2 *str)
+{
+	/* UTF16 cannot contain two consequent NUL bytes starting at even
+	 * position - see Unicode standards Chapter 3.9 D91 or RFC2781
+	 * Chapter 2.
+	 */
+
+	size_t i = 0;
+
+	g_return_val_if_fail(str != NULL, 0);
+
+	while (str[i++]);
+
+	return i * sizeof(gunichar2);
+}
+
 void
 purple_str_wipe(gchar *str)
 {
@@ -3771,6 +3788,15 @@
 	g_free(str);
 }
 
+void
+purple_utf16_wipe(gunichar2 *str)
+{
+	if (str == NULL)
+		return;
+	memset(str, 0, purple_utf16_size(str));
+	g_free(str);
+}
+
 /**************************************************************************
  * URI/URL Functions
  **************************************************************************/
--- a/libpurple/util.h	Sun Apr 28 15:55:57 2013 +0200
+++ b/libpurple/util.h	Mon Apr 29 03:35:54 2013 +0200
@@ -1134,6 +1134,14 @@
 char *purple_str_binary_to_ascii(const unsigned char *binary, guint len);
 
 /**
+ * Calculates UTF-16 string size (in bytes).
+ *
+ * @param str String to check.
+ * @return    Number of bytes (including NUL character) that string occupies.
+ */
+size_t purple_utf16_size(const gunichar2 *str);
+
+/**
  * Fills a NUL-terminated string with zeros and frees it.
  *
  * It should be used to free sensitive data, like passwords.
@@ -1141,6 +1149,16 @@
  * @param str A NUL-terminated string to free, or a NULL-pointer.
  */
 void purple_str_wipe(gchar *str);
+
+/**
+ * Fills a NUL-terminated UTF-16 string with zeros and frees it.
+ *
+ * It should be used to free sensitive data, like passwords.
+ *
+ * @param str A NUL-terminated string to free, or a NULL-pointer.
+ */
+void purple_utf16_wipe(gunichar2 *str);
+
 /*@}*/
 
 

mercurial