pidgin/pidgin

Added tag v2.14.2 for changeset 2eb25613d054
release-2.x.y
2021-04-01, Gary Kramlich
1dd6e5170860
Added tag v2.14.2 for changeset 2eb25613d054
/**
* @file core.c Purple Core API
* @ingroup core
*/
/* 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 "internal.h"
#include "cipher.h"
#include "certificate.h"
#include "cmds.h"
#include "connection.h"
#include "conversation.h"
#include "core.h"
#include "debug.h"
#include "dnsquery.h"
#include "ft.h"
#include "idle.h"
#include "imgstore.h"
#include "network.h"
#include "notify.h"
#include "plugin.h"
#include "pounce.h"
#include "prefs.h"
#include "privacy.h"
#include "proxy.h"
#include "savedstatuses.h"
#include "signals.h"
#include "smiley.h"
#include "sound.h"
#include "sound-theme-loader.h"
#include "sslconn.h"
#include "status.h"
#include "stun.h"
#include "theme-manager.h"
#include "util.h"
#ifdef HAVE_DBUS
# ifndef DBUS_API_SUBJECT_TO_CHANGE
# define DBUS_API_SUBJECT_TO_CHANGE
# endif
# include <dbus/dbus.h>
# include "dbus-purple.h"
# include "dbus-server.h"
# include "dbus-bindings.h"
#endif
struct PurpleCore
{
char *ui;
void *reserved;
};
static PurpleCoreUiOps *_ops = NULL;
static PurpleCore *_core = NULL;
STATIC_PROTO_INIT
gboolean
purple_core_init(const char *ui)
{
PurpleCoreUiOps *ops;
PurpleCore *core;
g_return_val_if_fail(ui != NULL, FALSE);
g_return_val_if_fail(purple_get_core() == NULL, FALSE);
#ifdef ENABLE_NLS
bindtextdomain(PACKAGE, LOCALEDIR);
#endif
#ifdef _WIN32
wpurple_init();
#endif
#if !GLIB_CHECK_VERSION(2, 36, 0)
/* GLib type system is automaticaly initialized since 2.36. */
g_type_init();
#endif
_core = core = g_new0(PurpleCore, 1);
core->ui = g_strdup(ui);
core->reserved = NULL;
ops = purple_core_get_ui_ops();
/* The signals subsystem is important and should be first. */
purple_signals_init();
purple_util_init();
purple_signal_register(core, "uri-handler",
purple_marshal_BOOLEAN__POINTER_POINTER_POINTER,
purple_value_new(PURPLE_TYPE_BOOLEAN), 3,
purple_value_new(PURPLE_TYPE_STRING), /* Protocol */
purple_value_new(PURPLE_TYPE_STRING), /* Command */
purple_value_new(PURPLE_TYPE_BOXED, "GHashTable *")); /* Parameters */
purple_signal_register(core, "quitting", purple_marshal_VOID, NULL, 0);
/* The prefs subsystem needs to be initialized before static protocols
* for protocol prefs to work. */
purple_prefs_init();
purple_debug_init();
if (ops != NULL)
{
if (ops->ui_prefs_init != NULL)
ops->ui_prefs_init();
if (ops->debug_ui_init != NULL)
ops->debug_ui_init();
}
#ifdef HAVE_DBUS
purple_dbus_init();
#endif
purple_ciphers_init();
purple_cmds_init();
/* Since plugins get probed so early we should probably initialize their
* subsystem right away too.
*/
purple_plugins_init();
/* Initialize all static protocols. */
static_proto_init();
purple_plugins_probe(G_MODULE_SUFFIX);
purple_theme_manager_init();
/* The buddy icon code uses the imgstore, so init it early. */
purple_imgstore_init();
/* Accounts use status, buddy icons and connection signals, so
* initialize these before accounts
*/
purple_status_init();
purple_buddy_icons_init();
purple_connections_init();
purple_accounts_init();
purple_savedstatuses_init();
purple_notify_init();
purple_certificate_init();
purple_conversations_init();
purple_blist_init();
purple_log_init();
purple_network_init();
purple_privacy_init();
purple_pounces_init();
purple_proxy_init();
purple_dnsquery_init();
purple_sound_init();
purple_ssl_init();
purple_stun_init();
purple_xfers_init();
purple_idle_init();
purple_smileys_init();
/*
* Call this early on to try to auto-detect our IP address and
* hopefully save some time later.
*/
purple_network_get_my_ip(-1);
if (ops != NULL && ops->ui_init != NULL)
ops->ui_init();
/* The UI may have registered some theme types, so refresh them */
purple_theme_manager_refresh();
return TRUE;
}
void
purple_core_quit(void)
{
PurpleCoreUiOps *ops;
PurpleCore *core = purple_get_core();
g_return_if_fail(core != NULL);
/* The self destruct sequence has been initiated */
purple_signal_emit(purple_get_core(), "quitting");
/* Transmission ends */
purple_connections_disconnect_all();
/*
* Certificates must be destroyed before the SSL plugins, because
* PurpleCertificates contain pointers to PurpleCertificateSchemes,
* and the PurpleCertificateSchemes will be unregistered when the
* SSL plugin is uninit.
*/
purple_certificate_uninit();
/* The SSL plugins must be uninit before they're unloaded */
purple_ssl_uninit();
/* Unload all non-loader, non-prpl plugins before shutting down
* subsystems. */
purple_debug_info("main", "Unloading normal plugins\n");
purple_plugins_unload(PURPLE_PLUGIN_STANDARD);
/* Save .xml files, remove signals, etc. */
purple_smileys_uninit();
purple_idle_uninit();
purple_pounces_uninit();
purple_blist_uninit();
purple_ciphers_uninit();
purple_notify_uninit();
purple_conversations_uninit();
purple_connections_uninit();
purple_buddy_icons_uninit();
purple_savedstatuses_uninit();
purple_status_uninit();
purple_accounts_uninit();
purple_sound_uninit();
purple_theme_manager_uninit();
purple_xfers_uninit();
purple_proxy_uninit();
purple_dnsquery_uninit();
purple_imgstore_uninit();
purple_network_uninit();
/* Everything after unloading all plugins must not fail if prpls aren't
* around */
purple_debug_info("main", "Unloading all plugins\n");
purple_plugins_destroy_all();
ops = purple_core_get_ui_ops();
if (ops != NULL && ops->quit != NULL)
ops->quit();
/* Everything after prefs_uninit must not try to read any prefs */
purple_prefs_uninit();
purple_plugins_uninit();
#ifdef HAVE_DBUS
purple_dbus_uninit();
#endif
purple_cmds_uninit();
/* Everything after util_uninit cannot try to write things to the confdir */
purple_util_uninit();
purple_log_uninit();
purple_signals_uninit();
g_free(core->ui);
g_free(core);
#ifdef _WIN32
wpurple_cleanup();
#endif
_core = NULL;
}
gboolean
purple_core_quit_cb(gpointer unused)
{
purple_core_quit();
return FALSE;
}
const char *
purple_core_get_version(void)
{
return VERSION;
}
const char *
purple_core_get_ui(void)
{
PurpleCore *core = purple_get_core();
g_return_val_if_fail(core != NULL, NULL);
return core->ui;
}
PurpleCore *
purple_get_core(void)
{
return _core;
}
void
purple_core_set_ui_ops(PurpleCoreUiOps *ops)
{
_ops = ops;
}
PurpleCoreUiOps *
purple_core_get_ui_ops(void)
{
return _ops;
}
#ifdef HAVE_DBUS
static char *purple_dbus_owner_user_dir(void)
{
DBusMessage *msg = NULL, *reply = NULL;
DBusConnection *dbus_connection = NULL;
DBusError dbus_error;
char *remote_user_dir = NULL;
if ((dbus_connection = purple_dbus_get_connection()) == NULL)
return NULL;
if ((msg = dbus_message_new_method_call(DBUS_SERVICE_PURPLE, DBUS_PATH_PURPLE, DBUS_INTERFACE_PURPLE, "PurpleUserDir")) == NULL)
return NULL;
dbus_error_init(&dbus_error);
reply = dbus_connection_send_with_reply_and_block(dbus_connection, msg, 5000, &dbus_error);
dbus_message_unref(msg);
dbus_error_free(&dbus_error);
if (reply)
{
dbus_error_init(&dbus_error);
dbus_message_get_args(reply, &dbus_error, DBUS_TYPE_STRING, &remote_user_dir, DBUS_TYPE_INVALID);
remote_user_dir = g_strdup(remote_user_dir);
dbus_error_free(&dbus_error);
dbus_message_unref(reply);
}
return remote_user_dir;
}
#endif /* HAVE_DBUS */
gboolean
purple_core_ensure_single_instance()
{
gboolean is_single_instance = TRUE;
#ifdef HAVE_DBUS
/* in the future, other mechanisms might have already set this to FALSE */
if (is_single_instance)
{
if (!purple_dbus_is_owner())
{
const char *user_dir = purple_user_dir();
char *dbus_owner_user_dir = purple_dbus_owner_user_dir();
is_single_instance = !purple_strequal(dbus_owner_user_dir, user_dir);
g_free(dbus_owner_user_dir);
}
}
#endif /* HAVE_DBUS */
return is_single_instance;
}
static gboolean
move_and_symlink_dir(const char *path, const char *basename, const char *old_base, const char *new_base, const char *relative)
{
char *new_name = g_build_filename(new_base, basename, NULL);
#ifndef _WIN32
char *old_name;
#endif
if (g_rename(path, new_name))
{
purple_debug_error("core", "Error renaming %s to %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
path, new_name, g_strerror(errno));
g_free(new_name);
return FALSE;
}
g_free(new_name);
#ifndef _WIN32
/* NOTE: This new_name is relative. */
new_name = g_build_filename(relative, basename, NULL);
old_name = g_build_filename(old_base, basename, NULL);
if (symlink(new_name, old_name))
{
purple_debug_warning("core", "Error symlinking %s to %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
old_name, new_name, g_strerror(errno));
}
g_free(old_name);
g_free(new_name);
#endif
return TRUE;
}
gboolean
purple_core_migrate(void)
{
const char *user_dir = purple_user_dir();
char *old_user_dir = g_strconcat(purple_home_dir(),
G_DIR_SEPARATOR_S ".gaim", NULL);
char *status_file;
FILE *fp;
GDir *dir;
GError *err;
const char *entry;
#ifndef _WIN32
char *logs_dir;
#endif
char *old_icons_dir;
if (!g_file_test(old_user_dir, G_FILE_TEST_EXISTS))
{
/* ~/.gaim doesn't exist, so there's nothing to migrate. */
g_free(old_user_dir);
return TRUE;
}
status_file = g_strconcat(user_dir, G_DIR_SEPARATOR_S "migrating", NULL);
if (g_file_test(user_dir, G_FILE_TEST_EXISTS))
{
/* If we're here, we have both ~/.gaim and .purple. */
if (!g_file_test(status_file, G_FILE_TEST_EXISTS))
{
/* There's no "migrating" status file,
* so ~/.purple is all up to date. */
g_free(status_file);
g_free(old_user_dir);
return TRUE;
}
}
/* If we're here, it's time to migrate from ~/.gaim to ~/.purple. */
/* Ensure the user directory exists */
if (!g_file_test(user_dir, G_FILE_TEST_IS_DIR))
{
if (g_mkdir(user_dir, S_IRUSR | S_IWUSR | S_IXUSR) == -1)
{
purple_debug_error("core", "Error creating directory %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
user_dir, g_strerror(errno));
g_free(status_file);
g_free(old_user_dir);
return FALSE;
}
}
/* This writes ~/.purple/migrating, which allows us to detect
* incomplete migrations and properly retry. */
if (!(fp = g_fopen(status_file, "w")))
{
purple_debug_error("core", "Error opening file %s for writing: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
status_file, g_strerror(errno));
g_free(status_file);
g_free(old_user_dir);
return FALSE;
}
fclose(fp);
/* Open ~/.gaim so we can loop over its contents. */
err = NULL;
if (!(dir = g_dir_open(old_user_dir, 0, &err)))
{
purple_debug_error("core", "Error opening directory %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
status_file,
(err ? err->message : "Unknown error"));
if (err)
g_error_free(err);
g_free(status_file);
g_free(old_user_dir);
return FALSE;
}
/* Loop over the contents of ~/.gaim */
while ((entry = g_dir_read_name(dir)))
{
char *name = g_build_filename(old_user_dir, entry, NULL);
#ifndef _WIN32
/* Deal with symlinks... */
if (g_file_test(name, G_FILE_TEST_IS_SYMLINK))
{
/* We're only going to duplicate a logs symlink. */
if (purple_strequal(entry, "logs"))
{
char *link;
err = NULL;
if ((link = g_file_read_link(name, &err)) == NULL)
{
char *name_utf8 = g_filename_to_utf8(name, -1, NULL, NULL, NULL);
purple_debug_error("core", "Error reading symlink %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
name_utf8 ? name_utf8 : name, err->message);
g_free(name_utf8);
g_error_free(err);
g_free(name);
g_dir_close(dir);
g_free(status_file);
g_free(old_user_dir);
return FALSE;
}
logs_dir = g_build_filename(user_dir, "logs", NULL);
if (purple_strequal(link, "../.purple/logs") ||
purple_strequal(link, logs_dir))
{
/* If the symlink points to the new directory, we're
* likely just trying again after a failed migration,
* so there's no need to fail here. */
g_free(link);
g_free(logs_dir);
continue;
}
/* In case we are trying again after a failed migration, we need
* to unlink any existing symlink. If it's a directory, this
* will fail, and so will the symlink below, which is good
* because the user should sort things out. */
g_unlink(logs_dir);
/* Relative links will most likely still be
* valid from ~/.purple, though it's not
* guaranteed. Oh well. */
if (symlink(link, logs_dir))
{
purple_debug_error("core", "Error symlinking %s to %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
logs_dir, link, g_strerror(errno));
g_free(link);
g_free(name);
g_free(logs_dir);
g_dir_close(dir);
g_free(status_file);
g_free(old_user_dir);
return FALSE;
}
g_free(link);
g_free(logs_dir);
continue;
}
/* Ignore all other symlinks. */
continue;
}
#endif
/* Deal with directories... */
if (g_file_test(name, G_FILE_TEST_IS_DIR))
{
if (purple_strequal(entry, "icons"))
{
/* This is a special case for the Album plugin, which
* stores data in the icons folder. We're not copying
* the icons directory over because previous bugs
* meant that it filled up with junk for many users.
* This is a great time to purge it. */
GDir *icons_dir;
char *new_icons_dir;
const char *icons_entry;
err = NULL;
if (!(icons_dir = g_dir_open(name, 0, &err)))
{
purple_debug_error("core", "Error opening directory %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
name,
(err ? err->message : "Unknown error"));
if (err)
g_error_free(err);
g_free(name);
g_dir_close(dir);
g_free(status_file);
g_free(old_user_dir);
return FALSE;
}
new_icons_dir = g_build_filename(user_dir, "icons", NULL);
/* Ensure the new icon directory exists */
if (!g_file_test(new_icons_dir, G_FILE_TEST_IS_DIR))
{
if (g_mkdir(new_icons_dir, S_IRUSR | S_IWUSR | S_IXUSR) == -1)
{
purple_debug_error("core", "Error creating directory %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
new_icons_dir, g_strerror(errno));
g_free(new_icons_dir);
g_dir_close(icons_dir);
g_free(name);
g_dir_close(dir);
g_free(status_file);
g_free(old_user_dir);
return FALSE;
}
}
while ((icons_entry = g_dir_read_name(icons_dir)))
{
char *icons_name = g_build_filename(name, icons_entry, NULL);
if (g_file_test(icons_name, G_FILE_TEST_IS_DIR))
{
if (!move_and_symlink_dir(icons_name, icons_entry,
name, new_icons_dir, "../../.purple/icons"))
{
g_free(icons_name);
g_free(new_icons_dir);
g_dir_close(icons_dir);
g_free(name);
g_dir_close(dir);
g_free(status_file);
g_free(old_user_dir);
return FALSE;
}
}
g_free(icons_name);
}
g_dir_close(icons_dir);
}
else if (purple_strequal(entry, "plugins"))
{
/* Do nothing, because we broke plugin compatibility.
* This means that the plugins directory gets left behind. */
}
else
{
/* All other directories are moved and symlinked. */
if (!move_and_symlink_dir(name, entry, old_user_dir, user_dir, "../.purple"))
{
g_free(name);
g_dir_close(dir);
g_free(status_file);
g_free(old_user_dir);
return FALSE;
}
}
}
else if (g_file_test(name, G_FILE_TEST_IS_REGULAR))
{
/* Regular files are copied. */
char *new_name;
FILE *new_file;
if (!(fp = g_fopen(name, "rb")))
{
purple_debug_error("core", "Error opening file %s for reading: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
name, g_strerror(errno));
g_free(name);
g_dir_close(dir);
g_free(status_file);
g_free(old_user_dir);
return FALSE;
}
new_name = g_build_filename(user_dir, entry, NULL);
if (!(new_file = g_fopen(new_name, "wb")))
{
purple_debug_error("core", "Error opening file %s for writing: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
new_name, g_strerror(errno));
fclose(fp);
g_free(new_name);
g_free(name);
g_dir_close(dir);
g_free(status_file);
g_free(old_user_dir);
return FALSE;
}
while (!feof(fp))
{
unsigned char buf[256];
size_t size;
size = fread(buf, 1, sizeof(buf), fp);
if (size != sizeof(buf) && !feof(fp))
{
purple_debug_error("core", "Error reading %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
name, g_strerror(errno));
fclose(new_file);
fclose(fp);
g_free(new_name);
g_free(name);
g_dir_close(dir);
g_free(status_file);
g_free(old_user_dir);
return FALSE;
}
if (!fwrite(buf, size, 1, new_file) && ferror(new_file) != 0)
{
purple_debug_error("core", "Error writing %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
new_name, g_strerror(errno));
fclose(new_file);
fclose(fp);
g_free(new_name);
g_free(name);
g_dir_close(dir);
g_free(status_file);
g_free(old_user_dir);
return FALSE;
}
}
if (fclose(new_file))
{
purple_debug_error("core", "Error writing: %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
new_name, g_strerror(errno));
}
if (fclose(fp))
{
purple_debug_warning("core", "Error closing %s: %s\n",
name, g_strerror(errno));
}
g_free(new_name);
}
else
purple_debug_warning("core", "Not a regular file or directory: %s\n", name);
g_free(name);
}
/* The migration was successful, so delete the status file. */
if (g_unlink(status_file))
{
purple_debug_error("core", "Error unlinking file %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
status_file, g_strerror(errno));
g_free(status_file);
return FALSE;
}
old_icons_dir = g_build_filename(old_user_dir, "icons", NULL);
_purple_buddy_icon_set_old_icons_dir(old_icons_dir);
g_free(old_icons_dir);
g_free(old_user_dir);
g_free(status_file);
return TRUE;
}
GHashTable* purple_core_get_ui_info() {
PurpleCoreUiOps *ops = purple_core_get_ui_ops();
if(NULL == ops || NULL == ops->get_ui_info)
return NULL;
return ops->get_ui_info();
}