diff --git a/backends/CMakeLists.txt b/backends/CMakeLists.txt index 9ecb3b5c..1c056d11 100644 --- a/backends/CMakeLists.txt +++ b/backends/CMakeLists.txt @@ -9,6 +9,7 @@ if (PROTOBUF_FOUND) if (NOT WIN32) ADD_SUBDIRECTORY(frotz) + ADD_SUBDIRECTORY(skype) endif() endif() diff --git a/backends/skype/CMakeLists.txt b/backends/skype/CMakeLists.txt new file mode 100644 index 00000000..fa6e9266 --- /dev/null +++ b/backends/skype/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 2.6) +FILE(GLOB SRC *.cpp) + +include_directories(/usr/include/dbus-1.0/) +include_directories(/usr/lib/dbus-1.0/include/) +include_directories(/usr/lib64/dbus-1.0/include/) + +ADD_EXECUTABLE(spectrum2_skype_backend ${SRC}) + +target_link_libraries(spectrum2_skype_backend ${GLIB2_LIBRARIES} ${EVENT_LIBRARIES} transport pthread dbus-glib-1 dbus-1 gobject-2.0 transport-plugin) + +INSTALL(TARGETS spectrum2_skype_backend RUNTIME DESTINATION bin) + diff --git a/backends/skype/geventloop.cpp b/backends/skype/geventloop.cpp new file mode 100644 index 00000000..d00d7a56 --- /dev/null +++ b/backends/skype/geventloop.cpp @@ -0,0 +1,248 @@ +/** + * XMPP - libpurple transport + * + * Copyright (C) 2009, Jan Kaluza + * + * 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 "geventloop.h" +#ifdef _WIN32 +#include "win32/win32dep.h" +#endif +#ifdef WITH_LIBEVENT +#include "event.h" +#endif + +typedef struct _PurpleIOClosure { + PurpleInputFunction function; + guint result; + gpointer data; +#ifdef WITH_LIBEVENT + GSourceFunc function2; + struct timeval timeout; + struct event evfifo; +#endif +} PurpleIOClosure; + +static gboolean io_invoke(GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + PurpleIOClosure *closure = (PurpleIOClosure* )data; + PurpleInputCondition purple_cond = (PurpleInputCondition)0; + + int tmp = 0; + if (condition & READ_COND) + { + tmp |= PURPLE_INPUT_READ; + purple_cond = (PurpleInputCondition)tmp; + } + if (condition & WRITE_COND) + { + tmp |= PURPLE_INPUT_WRITE; + purple_cond = (PurpleInputCondition)tmp; + } + + closure->function(closure->data, g_io_channel_unix_get_fd(source), purple_cond); + + return TRUE; +} + +static void io_destroy(gpointer data) +{ + g_free(data); +} + +static guint input_add(gint fd, + PurpleInputCondition condition, + PurpleInputFunction function, + gpointer data) +{ + PurpleIOClosure *closure = g_new0(PurpleIOClosure, 1); + GIOChannel *channel; + GIOCondition cond = (GIOCondition)0; + closure->function = function; + closure->data = data; + + int tmp = 0; + if (condition & PURPLE_INPUT_READ) + { + tmp |= READ_COND; + cond = (GIOCondition)tmp; + } + if (condition & PURPLE_INPUT_WRITE) + { + tmp |= WRITE_COND; + cond = (GIOCondition)tmp; + } + +#ifdef WIN32 + channel = wpurple_g_io_channel_win32_new_socket(fd); +#else + channel = g_io_channel_unix_new(fd); +#endif + closure->result = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, cond, + io_invoke, closure, io_destroy); + + g_io_channel_unref(channel); + return closure->result; +} + +static PurpleEventLoopUiOps eventLoopOps = +{ + g_timeout_add, + g_source_remove, + input_add, + g_source_remove, + NULL, +#if GLIB_CHECK_VERSION(2,14,0) + g_timeout_add_seconds, +#else + NULL, +#endif + + NULL, + NULL, + NULL +}; + +#ifdef WITH_LIBEVENT + +static GHashTable *events = NULL; +static unsigned long id = 0; + +static void event_io_destroy(gpointer data) +{ + PurpleIOClosure *closure = (PurpleIOClosure* )data; + event_del(&closure->evfifo); + g_free(data); +} + +static void event_io_invoke(int fd, short event, void *data) +{ + PurpleIOClosure *closure = (PurpleIOClosure* )data; + PurpleInputCondition purple_cond = (PurpleInputCondition)0; + int tmp = 0; + if (event & EV_READ) + { + tmp |= PURPLE_INPUT_READ; + purple_cond = (PurpleInputCondition)tmp; + } + if (event & EV_WRITE) + { + tmp |= PURPLE_INPUT_WRITE; + purple_cond = (PurpleInputCondition)tmp; + } + if (event & EV_TIMEOUT) + { +// tmp |= PURPLE_INPUT_WRITE; +// purple_cond = (PurpleInputCondition)tmp; + if (closure->function2(closure->data)) + evtimer_add(&closure->evfifo, &closure->timeout); +// else +// event_io_destroy(data); + return; + } + + closure->function(closure->data, fd, purple_cond); +} + +static gboolean event_input_remove(guint handle) +{ + PurpleIOClosure *closure = (PurpleIOClosure *) g_hash_table_lookup(events, &handle); + if (closure) + event_io_destroy(closure); + return TRUE; +} + +static guint event_input_add(gint fd, + PurpleInputCondition condition, + PurpleInputFunction function, + gpointer data) +{ + PurpleIOClosure *closure = g_new0(PurpleIOClosure, 1); + GIOChannel *channel; + GIOCondition cond = (GIOCondition)0; + closure->function = function; + closure->data = data; + + int tmp = EV_PERSIST; + if (condition & PURPLE_INPUT_READ) + { + tmp |= EV_READ; + } + if (condition & PURPLE_INPUT_WRITE) + { + tmp |= EV_WRITE; + } + + event_set(&closure->evfifo, fd, tmp, event_io_invoke, closure); + event_add(&closure->evfifo, NULL); + + int *f = (int *) g_malloc(sizeof(int)); + *f = id; + id++; + g_hash_table_replace(events, f, closure); + + return *f; +} + +static guint event_timeout_add (guint interval, GSourceFunc function, gpointer data) { + struct timeval timeout; + PurpleIOClosure *closure = g_new0(PurpleIOClosure, 1); + closure->function2 = function; + closure->data = data; + + timeout.tv_sec = interval/1000; + timeout.tv_usec = (interval%1000)*1000; + evtimer_set(&closure->evfifo, event_io_invoke, closure); + evtimer_add(&closure->evfifo, &timeout); + closure->timeout = timeout; + + guint *f = (guint *) g_malloc(sizeof(guint)); + *f = id; + id++; + g_hash_table_replace(events, f, closure); + return *f; +} + +static PurpleEventLoopUiOps libEventLoopOps = +{ + event_timeout_add, + event_input_remove, + event_input_add, + event_input_remove, + NULL, +// #if GLIB_CHECK_VERSION(2,14,0) +// g_timeout_add_seconds, +// #else + NULL, +// #endif + + NULL, + NULL, + NULL +}; + +#endif /* WITH_LIBEVENT*/ + +PurpleEventLoopUiOps * getEventLoopUiOps(void){ + return &eventLoopOps; +#ifdef WITH_LIBEVENT + events = g_hash_table_new_full(g_int_hash, g_int_equal, g_free, NULL); + return &libEventLoopOps; +#endif +} diff --git a/backends/skype/geventloop.h b/backends/skype/geventloop.h new file mode 100644 index 00000000..3febd65e --- /dev/null +++ b/backends/skype/geventloop.h @@ -0,0 +1,33 @@ +/** + * XMPP - libpurple transport + * + * Copyright (C) 2009, Jan Kaluza + * + * 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 + */ + +#ifndef _HI_EVENTLOOP_H +#define _HI_EVENTLOOP_H + +#include +#include "purple.h" +#include "eventloop.h" + +#define READ_COND (G_IO_IN | G_IO_HUP | G_IO_ERR) +#define WRITE_COND (G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL) + +PurpleEventLoopUiOps * getEventLoopUiOps(void); + +#endif diff --git a/backends/skype/main.cpp b/backends/skype/main.cpp new file mode 100644 index 00000000..5a135410 --- /dev/null +++ b/backends/skype/main.cpp @@ -0,0 +1,791 @@ +#include "glib.h" +#include + +#include "transport/config.h" +#include "transport/transport.h" +#include "transport/usermanager.h" +#include "transport/logger.h" +#include "transport/sqlite3backend.h" +#include "transport/userregistration.h" +#include "transport/user.h" +#include "transport/storagebackend.h" +#include "transport/rostermanager.h" +#include "transport/conversation.h" +#include "transport/networkplugin.h" +#include "spectrumeventloop.h" +#include +#include "geventloop.h" +#include "log4cxx/logger.h" +#include "log4cxx/consoleappender.h" +#include "log4cxx/patternlayout.h" +#include "log4cxx/propertyconfigurator.h" +#include "log4cxx/helpers/properties.h" +#include "log4cxx/helpers/fileinputstream.h" +#include "sys/wait.h" +#include "sys/signal.h" +// #include "valgrind/memcheck.h" +#include "malloc.h" +#include + + +using namespace log4cxx; + +static LoggerPtr logger = Logger::getLogger("backend"); + +using namespace Transport; + +class SpectrumNetworkPlugin; + + +SpectrumNetworkPlugin *np; + +static gboolean nodaemon = FALSE; +static gchar *logfile = NULL; +static gchar *lock_file = NULL; +static gchar *host = NULL; +static int port = 10000; +static gboolean ver = FALSE; +static gboolean list_purple_settings = FALSE; + +int m_sock; +static int writeInput; + +static GOptionEntry options_entries[] = { + { "nodaemon", 'n', 0, G_OPTION_ARG_NONE, &nodaemon, "Disable background daemon mode", NULL }, + { "logfile", 'l', 0, G_OPTION_ARG_STRING, &logfile, "Set file to log", NULL }, + { "pidfile", 'p', 0, G_OPTION_ARG_STRING, &lock_file, "File where to write transport PID", NULL }, + { "version", 'v', 0, G_OPTION_ARG_NONE, &ver, "Shows Spectrum version", NULL }, + { "list-purple-settings", 's', 0, G_OPTION_ARG_NONE, &list_purple_settings, "Lists purple settings which can be used in config file", NULL }, + { "host", 'h', 0, G_OPTION_ARG_STRING, &host, "Host to connect to", NULL }, + { "port", 'p', 0, G_OPTION_ARG_INT, &port, "Port to connect to", NULL }, + { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, "", NULL } +}; + +DBusHandlerResult skype_notify_handler(DBusConnection *connection, DBusMessage *message, gpointer user_data); + +static pbnetwork::StatusType getStatus(const std::string &st) { + pbnetwork::StatusType status = pbnetwork::STATUS_ONLINE; + if (st == "SKYPEOUT" || st == "OFFLINE") { + status = pbnetwork::STATUS_NONE; + } + else if (st == "DND") { + status = pbnetwork::STATUS_DND; + } + else if (st == "NA") { + status = pbnetwork::STATUS_XA; + } + else if (st == "AWAY") { + status = pbnetwork::STATUS_AWAY; + } + return status; +} + +class Skype { + public: + Skype(const std::string &user, const std::string &username, const std::string &password); + ~Skype() { logout(); } + void login(); + void logout(); + std::string send_command(const std::string &message); + + const std::string &getUser() { + return m_user; + } + + const std::string &getUsername() { + return m_username; + } + + private: + std::string m_username; + std::string m_password; + GPid m_pid; + DBusGConnection *m_connection; + DBusGProxy *m_proxy; + std::string m_user; +}; + +class SpectrumNetworkPlugin : public NetworkPlugin { + public: + SpectrumNetworkPlugin(Config *config, const std::string &host, int port) : NetworkPlugin() { + this->config = config; + } + + ~SpectrumNetworkPlugin() { + for (std::map::iterator it = m_accounts.begin(); it != m_accounts.end(); it++) { + delete (*it).first; + } + } + + void handleLoginRequest(const std::string &user, const std::string &legacyName, const std::string &password) { + std::string name = legacyName; + name = name.substr(name.find(".") + 1); + LOG4CXX_INFO(logger, "Creating account with name '" << name); + + Skype *skype = new Skype(user, name, password); + m_sessions[user] = skype; + m_accounts[skype] = user; + + skype->login(); + } + + void handleLogoutRequest(const std::string &user, const std::string &legacyName) { + Skype *skype = m_sessions[user]; + if (skype) { + skype->logout(); + exit(1); + } + } + + void handleStatusChangeRequest(const std::string &user, int status, const std::string &statusMessage) { + Skype *skype = m_sessions[user]; + if (!skype) + return; + + std::string st; + switch(status) { + case Swift::StatusShow::Away: { + st = "AWAY"; + break; + } + case Swift::StatusShow::DND: { + st = "DND"; + break; + } + case Swift::StatusShow::XA: { + st = "NA"; + break; + } + case Swift::StatusShow::None: { + break; + } + case pbnetwork::STATUS_INVISIBLE: + st = "INVISIBLE"; + break; + default: + st = "ONLINE"; + break; + } + skype->send_command("SET USERSTATUS " + st); + + if (!statusMessage.empty()) { + skype->send_command("SET PROFILE MOOD_TEXT " + statusMessage); + } + } + + void handleMessageSendRequest(const std::string &user, const std::string &legacyName, const std::string &message, const std::string &xhtml) { + Skype *skype = m_sessions[user]; + if (skype) { + skype->send_command("MESSAGE " + legacyName + " " + message); + } + + } + + void handleVCardRequest(const std::string &user, const std::string &legacyName, unsigned int id) { + Skype *skype = m_sessions[user]; + if (skype) { + std::string name = legacyName; + if (name.find("skype.") == 0) { + name = name.substr(6); + } + std::string photo; + gchar *filename = NULL; + gchar *new_filename = NULL; + gchar *image_data = NULL; + gsize image_data_len = 0; + gchar *ret; + int fh; + GError *error; + const gchar *userfiles[] = {"user256", "user1024", "user4096", "user16384", "user32768", "user65536", + "profile256", "profile1024", "profile4096", "profile16384", "profile32768", + NULL}; + char *username = g_strdup_printf("\x03\x10%s", name.c_str()); + for (fh = 0; userfiles[fh]; fh++) { + filename = g_strconcat("/tmp/skype/", skype->getUsername().c_str(), "/", skype->getUsername().c_str(), "/", userfiles[fh], ".dbb", NULL); + std::cout << "getting filename:" << filename << "\n"; + if (g_file_get_contents(filename, &image_data, &image_data_len, NULL)) + { + std::cout << "got\n"; + char *start = (char *)memmem(image_data, image_data_len, username, strlen(username)+1); + if (start != NULL) + { + char *next = image_data; + char *last = next; + //find last index of l33l + while ((next = (char *)memmem(next+4, start-next-4, "l33l", 4))) + { + last = next; + } + start = last; + if (start != NULL) + { + char *img_start; + //find end of l33l block + char *end = (char *)memmem(start+4, image_data+image_data_len-start-4, "l33l", 4); + if (!end) end = image_data+image_data_len; + + //look for start of JPEG block + img_start = (char *)memmem(start, end-start, "\xFF\xD8", 2); + if (img_start) + { + //look for end of JPEG block + char *img_end = (char *)memmem(img_start, end-img_start, "\xFF\xD9", 2); + if (img_end) + { + image_data_len = img_end - img_start + 2; + photo = std::string(img_start, image_data_len); + } + } + } + } + g_free(image_data); + } + g_free(filename); + } + g_free(username); + + std::string alias = ""; + std::cout << skype->getUsername() << " " << name << "\n"; + if (skype->getUsername() == name) { + alias = skype->send_command("GET PROFILE FULLNAME"); + alias = alias.substr(17); + } + handleVCard(user, id, legacyName, "", alias, photo); + } + } + + void sendData(const std::string &string) { + write(m_sock, string.c_str(), string.size()); +// if (writeInput == 0) +// writeInput = purple_input_add(m_sock, PURPLE_INPUT_WRITE, &transportDataReceived, NULL); + } + + void handleVCardUpdatedRequest(const std::string &user, const std::string &p, const std::string &nickname) { + } + + void handleBuddyRemovedRequest(const std::string &user, const std::string &buddyName, const std::vector &groups) { + } + + void handleBuddyUpdatedRequest(const std::string &user, const std::string &buddyName, const std::string &alias, const std::vector &groups) { + + } + + void handleBuddyBlockToggled(const std::string &user, const std::string &buddyName, bool blocked) { + + } + + void handleTypingRequest(const std::string &user, const std::string &buddyName) { + + } + + void handleTypedRequest(const std::string &user, const std::string &buddyName) { + + } + + void handleStoppedTypingRequest(const std::string &user, const std::string &buddyName) { + + } + + void handleAttentionRequest(const std::string &user, const std::string &buddyName, const std::string &message) { + + } + + std::map m_sessions; + std::map m_accounts; + std::map m_vcards; + Config *config; + +}; + + Skype::Skype(const std::string &user, const std::string &username, const std::string &password) { + m_username = username; + m_user = user; + m_password = password; + m_pid = 0; + m_connection = 0; + m_proxy = 0; + } + + void Skype::login() { + boost::filesystem::path path(std::string("/tmp/skype/") + m_username); + if (!boost::filesystem::exists(path)) { + boost::filesystem::create_directories(path); + boost::filesystem::path path2(std::string("/tmp/skype/") + m_username + "/" + m_username ); + boost::filesystem::create_directories(path2); + } + + std::string shared_xml = "\n" + "(time(NULL)) + ".0\">\n" + "\n" + "2\n" + "en\n" + "\n" + "\n"; + g_file_set_contents(std::string(std::string("/tmp/skype/") + m_username + "/shared.xml").c_str(), shared_xml.c_str(), -1, NULL); + + std::string config_xml = "\n" + "(time(NULL)) + ".0\">\n" + "\n" + "\n" + "30000000\n" + "300000000\n" + "" + boost::lexical_cast(time(NULL)) + "\n" + "\n" + "\n" + "\n" + "\n" + "Spectrum\n" + "\n" + "\n" + "\n" + "\n"; + g_file_set_contents(std::string(std::string("/tmp/skype/") + m_username + "/" + m_username +"/config.xml").c_str(), config_xml.c_str(), -1, NULL); + std::string db_path = std::string("/tmp/skype/") + m_username; + char *db = (char *) malloc(db_path.size() + 1); + strcpy(db, db_path.c_str()); + LOG4CXX_INFO(logger, m_username << ": Spawning new Skype instance dbpath=" << db); + gchar* argv[6] = {"skype", "--disable-cleanlooks", "--pipelogin", "--dbpath", db, 0}; + + int fd; + int fd_output; + g_spawn_async_with_pipes(NULL, + argv, + NULL /*envp*/, + G_SPAWN_SEARCH_PATH, + NULL /*child_setup*/, + NULL /*user_data*/, + &m_pid /*child_pid*/, + &fd, + NULL, + &fd_output, + NULL /*error*/); + std::string login_data = std::string(m_username + " " + m_password + "\n"); + LOG4CXX_INFO(logger, m_username << ": Login data=" << login_data); + write(fd, login_data.c_str(), login_data.size()); + close(fd); + + fcntl (fd_output, F_SETFL, O_NONBLOCK); + + free(db); + + sleep(2); + + GError *error = NULL; + DBusObjectPathVTable vtable; + + //Initialise threading + dbus_threads_init_default(); + + if (m_connection == NULL) + { + m_connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error); + if (m_connection == NULL && error != NULL) + { + LOG4CXX_INFO(logger, m_username << ": DBUS Error: " << error->message); + g_error_free(error); + return; + } + } + + if (m_proxy == NULL) + { + m_proxy = dbus_g_proxy_new_for_name_owner (m_connection, + "com.Skype.API", + "/com/Skype", + "com.Skype.API", + &error); + if (m_proxy == NULL && error != NULL) + { + LOG4CXX_INFO(logger, m_username << ":" << error->message); + g_error_free(error); + } + + vtable.message_function = &skype_notify_handler; + dbus_connection_register_object_path(dbus_g_connection_get_connection(m_connection), "/com/Skype/Client", &vtable, this); + } + + int counter = 0; + std::string re = "CONNSTATUS OFFLINE"; + while (re == "CONNSTATUS OFFLINE" || re.empty()) { + sleep(1); + gchar buffer[1024]; + int bytes_read; + bytes_read = read (fd_output, buffer, 1023); + if (bytes_read > 0) { + buffer[bytes_read] = 0; + np->handleDisconnected(m_user, 0, buffer); + close(fd_output); + logout(); + return; + } + re = send_command("NAME Spectrum"); + if (counter++ > 15) + break; + } + + close(fd_output); + + if (send_command("PROTOCOL 7") != "PROTOCOL 7") { + np->handleDisconnected(m_user, 0, "Skype is not ready"); + logout(); + return; + } + + np->handleConnected(m_user); + + std::map group_map; + std::string groups = send_command("SEARCH GROUPS CUSTOM"); + groups = groups.substr(groups.find(' ') + 1); + std::vector grps; + boost::split(grps, groups, boost::is_any_of(",")); + BOOST_FOREACH(std::string grp, grps) { + std::vector data; + std::string name = send_command("GET GROUP " + grp + " DISPLAYNAME"); + boost::split(data, name, boost::is_any_of(" ")); + name = name.substr(name.find("DISPLAYNAME") + 12); + + std::string users = send_command("GET GROUP " + data[1] + " USERS"); + users = name.substr(name.find("USERS") + 6); + boost::split(data, users, boost::is_any_of(",")); + BOOST_FOREACH(std::string u, data) { + group_map[u] = grp; + } + } + + std::string friends = send_command("GET AUTH_CONTACTS_PROFILES"); + + char **full_friends_list = g_strsplit((strchr(friends.c_str(), ' ')+1), ";", 0); + if (full_friends_list && full_friends_list[0]) + { + //in the format of: username;full name;phone;office phone;mobile phone; + // online status;friendly name;voicemail;mood + // (comma-seperated lines, usernames can have comma's) + + for (int i=0; full_friends_list[i] && *full_friends_list[i] != '\0'; i+=8) + { + std::string buddy = full_friends_list[i]; + + if (buddy[0] == ',') { + buddy.erase(buddy.begin()); + } + std::cout << "BUDDY '" << buddy << "'\n"; + std::string st = full_friends_list[i + 5]; + + pbnetwork::StatusType status = getStatus(st); + + std::string alias = full_friends_list[i + 6]; + + std::string mood_text = ""; + if (full_friends_list[i + 8] && *full_friends_list[i + 8] != '\0' && *full_friends_list[i + 8] != ',') { + mood_text = full_friends_list[i + 8]; + i++; + } + + std::vector groups; + groups.push_back(group_map[buddy]); + np->handleBuddyChanged(m_user, buddy, alias, groups, status, mood_text); + } + } + g_strfreev(full_friends_list); + + send_command("SET AUTOAWAY OFF"); + } + + void Skype::logout() { + if (m_pid != 0) { + send_command("SET USERSTATUS INVISIBLE"); + send_command("SET USERSTATUS OFFLINE"); + sleep(2); + g_object_unref(m_proxy); + LOG4CXX_INFO(logger, m_username << ": Killing Skype instance"); + kill((int) m_pid, SIGTERM); + m_pid = 0; + } + } + + std::string Skype::send_command(const std::string &message) { + GError *error = NULL; + gchar *str = NULL; +// int message_num; +// gchar error_return[30]; + + if (!dbus_g_proxy_call (m_proxy, "Invoke", &error, G_TYPE_STRING, message.c_str(), G_TYPE_INVALID, + G_TYPE_STRING, &str, G_TYPE_INVALID)) + { + if (error && error->message) + { + LOG4CXX_INFO(logger, m_username << ": DBUS Error: " << error->message); + g_error_free(error); + } else { + LOG4CXX_INFO(logger, m_username << ": DBUS no response"); + } + + } + if (str != NULL) + { + LOG4CXX_INFO(logger, m_username << ": DBUS:" << str); + } + return str ? std::string(str) : std::string(); + } + +static void handle_skype_message(std::string &message, Skype *sk) { + std::vector cmd; + boost::split(cmd, message, boost::is_any_of(" ")); + + if (cmd[0] == "USER") { + if (cmd[1] == sk->getUsername()) { + return; + } + + if (cmd[2] == "ONLINESTATUS") { + if (cmd[3] == "SKYPEOUT" || cmd[3] == "UNKNOWN") { + return; + } + else { + pbnetwork::StatusType status = getStatus(cmd[3]); + std::string mood_text = sk->send_command("GET USER " + cmd[1] + " MOOD_TEXT"); + mood_text = mood_text.substr(mood_text.find("MOOD_TEXT") + 10); + + std::string alias = sk->send_command("GET USER " + cmd[1] + " FULLNAME"); + alias = alias.substr(alias.find("FULLNAME") + 9); + + std::vector groups; + np->handleBuddyChanged(sk->getUser(), cmd[1], alias, groups, status, mood_text); + } + } + else if (cmd[2] == "MOOD_TEXT") { + std::string st = sk->send_command("GET USER " + cmd[1] + " ONLINESTATUS"); + st = st.substr(st.find("ONLINESTATUS") + 13); + pbnetwork::StatusType status = getStatus(st); + + std::string mood_text = message.substr(message.find("MOOD_TEXT") + 10); + + std::vector groups; + np->handleBuddyChanged(sk->getUser(), cmd[1], "", groups, status, mood_text); + } + else if (cmd[2] == "BUDDYSTATUS" && cmd[3] == "3") { + std::string st = sk->send_command("GET USER " + cmd[1] + " ONLINESTATUS"); + st = st.substr(st.find("ONLINESTATUS") + 13); + pbnetwork::StatusType status = getStatus(st); + + std::string mood_text = message.substr(message.find("MOOD_TEXT") + 10); + + std::vector groups; + np->handleBuddyChanged(sk->getUser(), cmd[1], "", groups, status, mood_text); + } + } + else if (cmd[0] == "CHATMESSAGE") { + if (cmd[3] == "RECEIVED") { + std::string body = sk->send_command("GET CHATMESSAGE " + cmd[1] + " BODY"); + body = body.substr(body.find("BODY") + 5); + + std::string chatname = sk->send_command("GET CHATMESSAGE " + cmd[1] + " CHATNAME"); + size_t start = chatname.find("$") + 1; + size_t len = chatname.find(";") - start; + std::string from = chatname.substr(start, len); + + std::string from_handle = sk->send_command("GET CHATMESSAGE " + cmd[1] + " FROM_HANDLE"); + from_handle = from_handle.substr(from_handle.find("FROM_HANDLE") + 12); + +// if (from_handle != sk->getUsername()) { + from = from_handle; +// } + if (from_handle == sk->getUsername()) + return; + + np->handleMessage(sk->getUser(), from, body); + } + } +} + +DBusHandlerResult skype_notify_handler(DBusConnection *connection, DBusMessage *message, gpointer user_data) { + DBusMessageIter iterator; + gchar *message_temp; + DBusMessage *temp_message; + + temp_message = dbus_message_ref(message); + dbus_message_iter_init(temp_message, &iterator); + if (dbus_message_iter_get_arg_type(&iterator) != DBUS_TYPE_STRING) + { + dbus_message_unref(message); + return (DBusHandlerResult) FALSE; + } + + do { + dbus_message_iter_get_basic(&iterator, &message_temp); + std::string m(message_temp); + LOG4CXX_INFO(logger,"DBUS message: " << m); + handle_skype_message(m, (Skype *) user_data); + } while(dbus_message_iter_has_next(&iterator) && dbus_message_iter_next(&iterator)); + + dbus_message_unref(message); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static void spectrum_sigchld_handler(int sig) +{ + int status; + pid_t pid; + + do { + pid = waitpid(-1, &status, WNOHANG); + } while (pid != 0 && pid != (pid_t)-1); + + if ((pid == (pid_t) - 1) && (errno != ECHILD)) { + char errmsg[BUFSIZ]; + snprintf(errmsg, BUFSIZ, "Warning: waitpid() returned %d", pid); + perror(errmsg); + } +} + +static int create_socket(char *host, int portno) { + struct sockaddr_in serv_addr; + + int m_sock = socket(AF_INET, SOCK_STREAM, 0); + memset((char *) &serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(portno); + + hostent *hos; // Resolve name + if ((hos = gethostbyname(host)) == NULL) { + // strerror() will not work for gethostbyname() and hstrerror() + // is supposedly obsolete + exit(1); + } + serv_addr.sin_addr.s_addr = *((unsigned long *) hos->h_addr_list[0]); + + if (connect(m_sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { + close(m_sock); + m_sock = 0; + } + + int flags = fcntl(m_sock, F_GETFL); + flags |= O_NONBLOCK; + fcntl(m_sock, F_SETFL, flags); + return m_sock; +} + + +static gboolean transportDataReceived(GIOChannel *source, GIOCondition condition, gpointer data) { + char buffer[65535]; + char *ptr = buffer; + ssize_t n = read(m_sock, ptr, sizeof(buffer)); + if (n <= 0) { + LOG4CXX_INFO(logger, "Diconnecting from spectrum2 server"); + exit(errno); + } + std::string d = std::string(buffer, n); + np->handleDataRead(d); + return TRUE; +} + +static void io_destroy(gpointer data) { + exit(1); +} + +int main(int argc, char **argv) { + GError *error = NULL; + GOptionContext *context; + context = g_option_context_new("config_file_name or profile name"); + g_option_context_add_main_entries(context, options_entries, ""); + if (!g_option_context_parse (context, &argc, &argv, &error)) { + std::cout << "option parsing failed: " << error->message << "\n"; + return -1; + } + + if (ver) { +// std::cout << VERSION << "\n"; + std::cout << "verze\n"; + g_option_context_free(context); + return 0; + } + + if (argc != 2) { +#ifdef WIN32 + std::cout << "Usage: spectrum.exe \n"; +#else + +#if GLIB_CHECK_VERSION(2,14,0) + std::cout << g_option_context_get_help(context, FALSE, NULL); +#else + std::cout << "Usage: spectrum \n"; + std::cout << "See \"man spectrum\" for more info.\n"; +#endif + +#endif + } + else { +#ifndef WIN32 + signal(SIGPIPE, SIG_IGN); + + if (signal(SIGCHLD, spectrum_sigchld_handler) == SIG_ERR) { + std::cout << "SIGCHLD handler can't be set\n"; + g_option_context_free(context); + return -1; + } +// +// if (signal(SIGINT, spectrum_sigint_handler) == SIG_ERR) { +// std::cout << "SIGINT handler can't be set\n"; +// g_option_context_free(context); +// return -1; +// } +// +// if (signal(SIGTERM, spectrum_sigterm_handler) == SIG_ERR) { +// std::cout << "SIGTERM handler can't be set\n"; +// g_option_context_free(context); +// return -1; +// } +// +// struct sigaction sa; +// memset(&sa, 0, sizeof(sa)); +// sa.sa_handler = spectrum_sighup_handler; +// if (sigaction(SIGHUP, &sa, NULL)) { +// std::cout << "SIGHUP handler can't be set\n"; +// g_option_context_free(context); +// return -1; +// } +#endif + Config config; + if (!config.load(argv[1])) { + std::cout << "Can't open " << argv[1] << " configuration file.\n"; + return 1; + } + + if (CONFIG_STRING(&config, "logging.backend_config").empty()) { + LoggerPtr root = log4cxx::Logger::getRootLogger(); + root->addAppender(new ConsoleAppender(new PatternLayout("%d %-5p %c: %m%n"))); + } + else { + log4cxx::helpers::Properties p; + log4cxx::helpers::FileInputStream *istream = new log4cxx::helpers::FileInputStream(CONFIG_STRING(&config, "logging.backend_config")); + + p.load(istream); + p.setProperty("pid", boost::lexical_cast(getpid())); + log4cxx::PropertyConfigurator::configure(p); + } + +// initPurple(config); + + g_type_init(); + + m_sock = create_socket(host, port); + + + GIOChannel *channel; + GIOCondition cond = (GIOCondition) READ_COND; + channel = g_io_channel_unix_new(m_sock); + g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, cond, transportDataReceived, NULL, io_destroy); + + np = new SpectrumNetworkPlugin(&config, host, port); + + GMainLoop *m_loop; + m_loop = g_main_loop_new(NULL, FALSE); + + if (m_loop) { + g_main_loop_run(m_loop); + } + } + + g_option_context_free(context); +} diff --git a/backends/skype/spectrumeventloop.cpp b/backends/skype/spectrumeventloop.cpp new file mode 100644 index 00000000..20286e58 --- /dev/null +++ b/backends/skype/spectrumeventloop.cpp @@ -0,0 +1,90 @@ +/** + * XMPP - libpurple transport + * + * Copyright (C) 2009, Jan Kaluza + * + * 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 "spectrumeventloop.h" +#include "glib.h" + +#include + +#ifdef WITH_LIBEVENT +#include +#endif + + +using namespace Swift; + +// Fires the event's callback and frees the event +static gboolean processEvent(void *data) { + Event *ev = (Event *) data; + ev->callback(); + delete ev; + return FALSE; +} + +SpectrumEventLoop::SpectrumEventLoop() : m_isRunning(false) { + m_loop = NULL; + if (true) { + m_loop = g_main_loop_new(NULL, FALSE); + } +#ifdef WITH_LIBEVENT + else { + /*struct event_base *base = (struct event_base *)*/ + event_init(); + } +#endif +} + +SpectrumEventLoop::~SpectrumEventLoop() { + stop(); +} + +void SpectrumEventLoop::run() { + m_isRunning = true; + if (m_loop) { + g_main_loop_run(m_loop); + } +#ifdef WITH_LIBEVENT + else { + event_loop(0); + } +#endif +} + +void SpectrumEventLoop::stop() { + std::cout << "stopped loop\n"; + if (!m_isRunning) + return; + if (m_loop) { + g_main_loop_quit(m_loop); + g_main_loop_unref(m_loop); + m_loop = NULL; + } +#ifdef WITH_LIBEVENT + else { + event_loopexit(NULL); + } +#endif +} + +void SpectrumEventLoop::post(const Event& event) { + // pass copy of event to main thread + Event *ev = new Event(event.owner, event.callback); + g_timeout_add(0, processEvent, ev); +} diff --git a/backends/skype/spectrumeventloop.h b/backends/skype/spectrumeventloop.h new file mode 100644 index 00000000..7e811c89 --- /dev/null +++ b/backends/skype/spectrumeventloop.h @@ -0,0 +1,49 @@ +/** + * XMPP - libpurple transport + * + * Copyright (C) 2009, Jan Kaluza + * + * 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 + */ + +#ifndef SPECTRUM_EVENT_LOOP_H +#define SPECTRUM_EVENT_LOOP_H + +#include +#include "Swiften/EventLoop/EventLoop.h" +#include "glib.h" + +// Event loop implementation for Spectrum +class SpectrumEventLoop : public Swift::EventLoop { + public: + // Creates event loop according to CONFIG().eventloop settings. + SpectrumEventLoop(); + ~SpectrumEventLoop(); + + // Executes the eventloop. + void run(); + + // Stops tht eventloop. + void stop(); + + // Posts new Swift::Event to main thread. + virtual void post(const Swift::Event& event); + + private: + bool m_isRunning; + GMainLoop *m_loop; +}; + +#endif diff --git a/spectrum/src/sample2.cfg b/spectrum/src/sample2.cfg index 91ab58fc..d2de8992 100644 --- a/spectrum/src/sample2.cfg +++ b/spectrum/src/sample2.cfg @@ -40,6 +40,8 @@ users_per_backend=10 # Full path to backend binary. backend=/usr/bin/spectrum2_libpurple_backend #backend=/usr/bin/spectrum2_libircclient-qt_backend +# For skype: +#backend=/usr/bin/setsid /usr/bin/xvfb-run -n BACKEND_ID -s "-screen 0 10x10x8" -f /tmp/x-skype-gw /usr/bin/spectrum2_skype_backend # Libpurple protocol-id for spectrum_libpurple_backend protocol=prpl-jabber