├── .gitignore ├── Makefile ├── README.md ├── deb_control ├── deb_copyright ├── gnome-keyring.c ├── make_package.py └── pidgin-gnome-keyring.svg /.gitignore: -------------------------------------------------------------------------------- 1 | gnome-keyring.so 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET = gnome-keyring 2 | 3 | SECRETFLAGS = `pkg-config --libs --cflags libsecret-1` 4 | PURPLEFLAGS = `pkg-config --cflags purple` 5 | VERSION = $(shell cat VERSION) 6 | ifeq ($(strip $(VERSION)),) 7 | VERSION = `git describe --tags` 8 | endif 9 | 10 | PLUGINDIRPURPLE:=$(shell pkg-config --variable=plugindir purple) 11 | 12 | all: ${TARGET}.so 13 | 14 | clean: 15 | rm -f ${TARGET}.so ${TARGET}_*.tar.gz pidgin-${TARGET}_*.* *.pyc 16 | rm -rf pidgin-${TARGET}-* 17 | 18 | ${TARGET}.so: ${TARGET}.c 19 | 20 | ${CC} ${CFLAGS} ${LDFLAGS} -Wall -I. -g -O2 ${TARGET}.c -o ${TARGET}.so -shared -fPIC -DPIC -ggdb ${PURPLEFLAGS} ${SECRETFLAGS} -DVERSION=\"${VERSION}\" 21 | 22 | install: ${TARGET}.so 23 | mkdir -p ${DESTDIR}${PLUGINDIRPURPLE} 24 | cp ${TARGET}.so ${DESTDIR}${PLUGINDIRPURPLE} 25 | 26 | install_local: ${TARGET}.so 27 | mkdir -p ~/.purple/plugins 28 | cp ${TARGET}.so ~/.purple/plugins/ 29 | 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pidgin-gnome-keyring 2 | 3 | Pidgin usually stores passwords as plaintext. This plugin instead saves all 4 | passwords to the system keyring, which some would argue is a more secure form 5 | of password storage. 6 | 7 | After the plugin is enabled, whenever an account with a pidgin-stored password 8 | signs on, its password will automatically be saved to the keyring and removed 9 | from the plaintext accounts.xml file. 10 | 11 | The plugin is available as a ppa for Ubuntu: ```ppa:pidgin-gnome-keyring/ppa``` 12 | 13 | ## Building from source 14 | You will need the libpurple and libsecret development libraries, along with 15 | pkg-config. On Ubuntu, install these with 16 | ```sudo apt-get install pkg-config libsecret-1-dev libpurple-dev``` 17 | 18 | Afterwards, use git clone to download the source (the version is set by git), 19 | and run ```make``` to compile. For installation: 20 | - local ($HOME/.purple/plugins): ```make install_local``` 21 | - global (/usr/lib/purple-2): ```sudo make install``` 22 | -------------------------------------------------------------------------------- /deb_control: -------------------------------------------------------------------------------- 1 | Source: pidgin-gnome-keyring 2 | Section: universe/net 3 | Priority: optional 4 | Maintainer: Ali Ebrahim 5 | Build-Depends: debhelper (>= 9), pkg-config, libsecret-1-dev, libpurple-dev 6 | Standards-Version: 3.9.5 7 | Homepage: https://github.com/aebrahim/pidgin-gnome-keyring/ 8 | Vcs-git: https://github.com/aebrahim/pidgin-gnome-keyring.git 9 | Vcs-Browser: https://github.com/aebrahim/pidgin-gnome-keyring/ 10 | 11 | Package: pidgin-gnome-keyring 12 | Architecture: any 13 | Depends: ${misc:Depends}, libsecret-1-0, libpurple0 14 | Description: integrates pidgin (and libpurple) with the system keyring 15 | Pidgin usually stores passwords as plaintext with the "save password" function. This plugin instead saves all passwords to the system keyring, which some would argue is a more secure form of password storage. 16 | -------------------------------------------------------------------------------- /deb_copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: pidgin-gnome-keyring 3 | Upstream-Contact: Ali Ebrahim 4 | Source: https://github.com/aebrahim/pidgin-gnome-keyring 5 | 6 | Files: * 7 | Copyright: 2015 Ali Ebrahim 8 | License: GPL-2.0+ 9 | 10 | Files: debian/* 11 | Copyright: 2015 Ali Ebrahim 12 | License: GPL-2.0+ 13 | 14 | License: GPL-2.0+ 15 | This package is free software; you can redistribute it and/or modify 16 | it under the terms of the GNU General Public License as published by 17 | the Free Software Foundation; either version 2 of the License, or 18 | (at your option) any later version. 19 | . 20 | This package is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | . 25 | You should have received a copy of the GNU General Public License 26 | along with this program. If not, see 27 | . 28 | On Debian systems, the complete text of the GNU General 29 | Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". 30 | -------------------------------------------------------------------------------- /gnome-keyring.c: -------------------------------------------------------------------------------- 1 | #define PURPLE_PLUGINS 2 | 3 | #ifndef VERSION 4 | #define VERSION "experimental" 5 | #endif 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | /* function prototypes */ 19 | static void keyring_password_store(PurpleAccount *account, gchar *password); 20 | static void sign_in_cb(PurpleAccount *account, gpointer data); 21 | static void keyring_password_find_cb(GObject *source, GAsyncResult *res, 22 | gpointer data); 23 | static void keyring_password_store_cb(GObject *source, GAsyncResult *res, 24 | gpointer data); 25 | static void connecting_cb(PurpleAccount *account, gpointer data); 26 | static void memory_clearing_function(PurpleAccount *account); 27 | static PurplePluginPrefFrame * get_pref_frame(PurplePlugin *plugin); 28 | 29 | /* function definitions */ 30 | 31 | /* function called when the plugin starts */ 32 | static gboolean plugin_load(PurplePlugin *plugin) { 33 | GList *accountsList; 34 | void *accountshandle = purple_accounts_get_handle(); 35 | /* notFound will be a list of accounts not found 36 | * in the keyring */ 37 | GList *notFound = NULL; 38 | GList *notFound_iter; 39 | 40 | /* The first thing to do is set all the passwords. 41 | * This part is purposely written to be locking. If pidgin 42 | * tries to connect without a password it will result in annoying 43 | * prompts */ 44 | for (accountsList = purple_accounts_get_all(); 45 | accountsList != NULL; 46 | accountsList = accountsList->next) { 47 | PurpleAccount *account = (PurpleAccount *)accountsList->data; 48 | gchar *password; 49 | GError *error = NULL; 50 | /* if the password exists in the keyring, set it in pidgin */ 51 | password = secret_password_lookup_sync(SECRET_SCHEMA_COMPAT_NETWORK, 52 | NULL, &error, "user", account->username, 53 | "protocol", account->protocol_id, NULL); 54 | if (error != NULL) { 55 | fprintf(stderr, "lookup_sync error in plugin_load: %s\n", 56 | error->message); 57 | g_error_free(error); 58 | } else if (password != NULL) { 59 | /* set the account to not remember passwords */ 60 | purple_account_set_remember_password(account, FALSE); 61 | /* temporarily set a fake password, then the real one */ 62 | purple_account_set_password(account, "fakedoopdeedoop"); 63 | purple_account_set_password(account, password); 64 | secret_password_free(password); 65 | } 66 | else { 67 | /* add to the list of accounts not found in the keyring */ 68 | notFound = g_list_append(notFound, account); 69 | } 70 | } 71 | /* for the acccounts which were not found in the keyring */ 72 | for (notFound_iter = g_list_first(notFound); 73 | notFound_iter != NULL; 74 | notFound_iter = notFound_iter->next) { 75 | PurpleAccount *account = (PurpleAccount *)notFound_iter->data; 76 | /* if the password was saved by libpurple before then 77 | * save it in the keyring, and tell libpurple to forget it */ 78 | if (purple_account_get_remember_password(account)) { 79 | gchar *password = g_strdup(account->password); 80 | keyring_password_store(account, password); 81 | purple_account_set_remember_password(account, FALSE); 82 | /* temporarily set a fake password, then the real one again */ 83 | purple_account_set_password(account, "fakedoopdeedoop"); 84 | purple_account_set_password(account, password); 85 | g_free(password); 86 | } 87 | } 88 | /* done with the notFound, so free it */ 89 | g_list_free(notFound); 90 | 91 | /* create a signal which monitors whenever an account signs in, 92 | * so that the callback function can store/update the password */ 93 | purple_signal_connect(accountshandle, "account-signed-on", plugin, 94 | PURPLE_CALLBACK(sign_in_cb), NULL); 95 | /* create a signal which monitors whenever an account tries to connect 96 | * so that the callback can make sure the password is set in pidgin */ 97 | purple_signal_connect(accountshandle, "account-connecting", plugin, 98 | PURPLE_CALLBACK(connecting_cb), NULL); 99 | /* at this point, the plugin is set up */ 100 | return TRUE; 101 | } 102 | 103 | 104 | /* callback to whenever an account is signed in */ 105 | static void sign_in_cb(PurpleAccount *account, gpointer data) { 106 | /* Get the password. 107 | * The callback will check to see if it is already 108 | * saved in the keyring. 109 | * This will be run every time an account signs in. */ 110 | secret_password_lookup(SECRET_SCHEMA_COMPAT_NETWORK, 111 | NULL, keyring_password_find_cb, 112 | account, 113 | 114 | "user", account->username, 115 | "protocol", account->protocol_id, 116 | NULL); 117 | } 118 | 119 | /* callback to gnome keyring password finder 120 | * Will sync the password between the keyring and pidgin */ 121 | static void keyring_password_find_cb(GObject *source, GAsyncResult *res, 122 | gpointer data) { 123 | GError *error = NULL; 124 | gchar *password = secret_password_lookup_finish(res, &error); 125 | 126 | PurpleAccount *account = (PurpleAccount *)data; 127 | gboolean remember = purple_account_get_remember_password(account); 128 | /* set the purple account to not remember passwords */ 129 | purple_account_set_remember_password(account, FALSE); 130 | /* if the password was not found in the keyring 131 | * and the password exists in pidgin 132 | * and the password was set to be remembered 133 | */ 134 | if (error != NULL) { 135 | fprintf(stderr, "lookup_finish error in find_cb: %s\n", 136 | error->message); 137 | g_error_free(error); 138 | return; 139 | } 140 | if (password == NULL && 141 | account->password != NULL 142 | && remember) { 143 | /* copy it from pidgin to the keyring */ 144 | keyring_password_store(account, account->password); 145 | return; 146 | } 147 | /* if the stored passwords do not match */ 148 | if (password != NULL) { 149 | if (account->password != NULL && 150 | strcmp(password, account->password) != 0) { 151 | /* update the keyring with the pidgin password */ 152 | keyring_password_store(account, account->password); 153 | secret_password_free(password); 154 | return; 155 | } 156 | secret_password_free(password); 157 | } 158 | /* if this code is executed, it means that keyring_password_store was 159 | * not called, so the memory_clearing_function needs to be called now 160 | */ 161 | memory_clearing_function(account); 162 | } 163 | 164 | /* store a password in the keyring */ 165 | static void keyring_password_store(PurpleAccount *account, 166 | gchar *password) { 167 | secret_password_store( 168 | SECRET_SCHEMA_COMPAT_NETWORK, 169 | purple_prefs_get_string("/plugins/core/gnome-keyring/keyring_name"), 170 | "pidgin account password", 171 | password, NULL, 172 | keyring_password_store_cb, account, 173 | 174 | "user", account->username, 175 | "protocol", account->protocol_id, 176 | NULL); 177 | } 178 | 179 | /* this is mainly here for stability issues because the program may 180 | * crash if there is no callback function. 181 | * It does not actually do anything */ 182 | static void keyring_password_store_cb(GObject *source, GAsyncResult *res, 183 | gpointer data) { 184 | GError *error = NULL; 185 | 186 | secret_password_store_finish(res, &error); 187 | if (error != NULL) { 188 | fprintf(stderr, "store_finish error in store_cb: %s\n", 189 | error->message); 190 | g_error_free(error); 191 | } else { 192 | PurpleAccount *account = (PurpleAccount *)data; 193 | memory_clearing_function(account); 194 | } 195 | return; 196 | } 197 | 198 | static void memory_clearing_function(PurpleAccount *account) { 199 | gboolean clear_memory = purple_prefs_get_bool( 200 | "/plugins/core/gnome-keyring/clear_memory"); 201 | if (clear_memory) { 202 | if (account->password != NULL) { 203 | g_free(account->password); 204 | account->password = NULL; 205 | } 206 | } 207 | } 208 | 209 | /* callback to whenever a function tries to connect 210 | * this needs to ensure that there is a password 211 | * this may happen if the password was disabled, then later re-enabled */ 212 | static void connecting_cb(PurpleAccount *account, gpointer data) { 213 | if (account->password == NULL) { 214 | gchar *password; 215 | GError *error = NULL; 216 | 217 | password = secret_password_lookup_sync(SECRET_SCHEMA_COMPAT_NETWORK, 218 | NULL, &error, "user", account->username, 219 | "protocol", account->protocol_id, NULL); 220 | if (error != NULL) { 221 | fprintf(stderr, "lookup_sync error in connectinb_cb: %s\n", 222 | error->message); 223 | g_error_free(error); 224 | } else if (password != NULL) { 225 | purple_account_set_password(account, password); 226 | secret_password_free(password); 227 | } 228 | } 229 | } 230 | 231 | 232 | static gboolean plugin_unload(PurplePlugin *plugin) { 233 | /* disconnect from signals */ 234 | void *accounts_handle = purple_accounts_get_handle(); 235 | purple_signal_disconnect(accounts_handle, "account-signed-on", 236 | plugin, NULL); 237 | purple_signal_disconnect(accounts_handle, "account-connecting", 238 | plugin, NULL); 239 | return TRUE; 240 | } 241 | 242 | static PurplePluginUiInfo prefs_info = { 243 | get_pref_frame, 0, NULL, NULL, NULL, NULL, NULL 244 | }; 245 | 246 | static PurplePluginPrefFrame * get_pref_frame(PurplePlugin *plugin) { 247 | PurplePluginPrefFrame *frame = purple_plugin_pref_frame_new(); 248 | gchar *label = g_strdup_printf("Clear plaintext passwords from memory"); 249 | PurplePluginPref *pref = purple_plugin_pref_new_with_name_and_label( 250 | "/plugins/core/gnome-keyring/clear_memory", 251 | label); 252 | purple_plugin_pref_frame_add(frame, pref); 253 | purple_plugin_pref_frame_add(frame, 254 | purple_plugin_pref_new_with_name_and_label( 255 | "/plugins/core/gnome-keyring/keyring_name", "Keyring name" 256 | ) 257 | ); 258 | return frame; 259 | } 260 | 261 | 262 | static PurplePluginInfo info = { 263 | PURPLE_PLUGIN_MAGIC, PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION, 264 | PURPLE_PLUGIN_STANDARD, 265 | NULL, 266 | 0, 267 | NULL, 268 | PURPLE_PRIORITY_HIGHEST, 269 | 270 | "core-gnome-keyring", 271 | "Password Keyring", 272 | VERSION, 273 | "Save pidgin passwords to the system keyring instead of as plaintext", 274 | "Save pidgin passwords to the system keyring instead of as plaintext", 275 | "Ali Ebrahim", 276 | "https://github.com/aebrahim/pidgin-gnome-keyring/", 277 | 278 | plugin_load, 279 | plugin_unload, 280 | NULL, 281 | NULL, 282 | NULL, 283 | &prefs_info, 284 | NULL, 285 | NULL, 286 | NULL, 287 | NULL, 288 | NULL 289 | }; 290 | 291 | static void init_plugin(PurplePlugin *plugin) { 292 | purple_prefs_add_none("/plugins/core/gnome-keyring"); 293 | purple_prefs_add_bool("/plugins/core/gnome-keyring/clear_memory", FALSE); 294 | purple_prefs_add_string("/plugins/core/gnome-keyring/keyring_name", SECRET_COLLECTION_DEFAULT); 295 | } 296 | 297 | PURPLE_INIT_PLUGIN(gnome-keyring, init_plugin, info) 298 | -------------------------------------------------------------------------------- /make_package.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """helps automate building the package""" 3 | import shutil 4 | import os 5 | from subprocess import check_output 6 | from pygit2 import Repository, GIT_SORT_TIME 7 | import xml.dom.minidom 8 | import re 9 | import time 10 | 11 | # declare strings up here 12 | ubuntunames = ["trusty", "utopic", "vivid"] 13 | author_name = "Ali Ebrahim" 14 | email = "ali.ebrahim314@gmail.com" 15 | basename = "pidgin-gnome-keyring" 16 | installdir = "/usr/lib/purple-2/" 17 | 18 | version = check_output(["git", "describe"]).strip() 19 | 20 | for ubuntuname in ubuntunames: 21 | version_str = version + "~" + ubuntuname 22 | 23 | package_version_str = version_str + "-1" 24 | 25 | dirname = basename + "-" + version_str 26 | tarname = basename + "_" + version_str + ".orig" 27 | debname = dirname + "/debian/" 28 | print debname 29 | 30 | if os.path.isdir(dirname): 31 | os.system("rm -rf " + dirname) 32 | os.mkdir(dirname) 33 | os.mkdir(debname) 34 | shutil.copy2("gnome-keyring.c", dirname) 35 | # copy everything in Makefile except for the install part at the end 36 | shutil.copy2("Makefile", dirname + "/Makefile") 37 | with open(dirname + "/VERSION", "w") as outfile: 38 | outfile.write(version) 39 | 40 | shutil.make_archive(tarname, "gztar", root_dir=dirname) 41 | # run dh_make 42 | os.system("cd %s; dh_make -s -c gpl2 --createorig -y -a -e %s" % 43 | (dirname, email)) 44 | # remove extra files and copy source files 45 | os.system("cd %s; rm -rf *.ex *.EX README*" % debname) 46 | shutil.copy2("deb_control", debname+"control") 47 | shutil.copy2("deb_copyright", debname+"copyright") 48 | # make the dirs and install files 49 | os.system("echo '%s' > %sdirs" % (installdir, debname)) 50 | os.system("echo 'gnome-keyring.so %s' > %sinstall" 51 | % (installdir, debname)) 52 | # write the changelog 53 | changelog = open(debname + "changelog", "w") 54 | repo = Repository(".") 55 | for commit in repo.walk(repo.head.target, GIT_SORT_TIME): 56 | changelog.write("%s (%s) %s; urgency=low\n\n" % 57 | (basename, package_version_str, ubuntuname)) 58 | for commit_line in commit.message.split("\n"): 59 | if len(commit_line) > 0: 60 | changelog.write(" " + commit_line + "\n") 61 | changelog.write("\n") 62 | date = time.strftime("%a, %d %b %Y %X", 63 | time.gmtime(commit.commit_time)) 64 | offset = "%+0.04d" % (commit.commit_time_offset / 60 * 1000) 65 | changelog.write(" -- %s <%s> %s %s\n" % 66 | (author_name, email, date, offset)) 67 | changelog.close() 68 | # call debuild 69 | os.system("cd %s; debuild -S -sa" % dirname) 70 | 71 | -------------------------------------------------------------------------------- /pidgin-gnome-keyring.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 30 | 39 | 48 | 57 | 66 | 75 | 86 | 88 | 92 | 96 | 97 | 108 | 111 | 115 | 119 | 120 | 131 | 142 | 144 | 148 | 152 | 156 | 157 | 160 | 164 | 168 | 169 | 172 | 176 | 180 | 181 | 184 | 188 | 192 | 193 | 196 | 200 | 204 | 205 | 208 | 212 | 216 | 217 | 220 | 224 | 228 | 229 | 240 | 251 | 262 | 272 | 282 | 293 | 302 | 312 | 323 | 333 | 342 | 353 | 362 | 372 | 383 | 394 | 405 | 414 | 424 | 426 | 430 | 434 | 435 | 437 | 441 | 445 | 449 | 450 | 452 | 456 | 460 | 461 | 463 | 467 | 471 | 472 | 474 | 478 | 482 | 483 | 484 | 504 | 506 | 507 | 509 | image/svg+xml 510 | 512 | 513 | 514 | 515 | 516 | 521 | 525 | 535 | 542 | 548 | 555 | 562 | 572 | 579 | 586 | 593 | 600 | 607 | 617 | 627 | 634 | 641 | 651 | 661 | 671 | 678 | 685 | 692 | 699 | 700 | 705 | 711 | 717 | 723 | 726 | 731 | 736 | 737 | 742 | 748 | 749 | 750 | 751 | --------------------------------------------------------------------------------