#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); }