diff --git a/CMakeLists.txt b/CMakeLists.txt index d7cb5928..3f4802e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,9 @@ find_package(log4cxx) set(event_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") find_package(event) +set(pqxx_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") +find_package(pqxx) + find_package(Doxygen) INCLUDE(FindQt4) @@ -101,6 +104,16 @@ else (MYSQL_FOUND) message("MySQL : no (install mysql-devel)") endif (MYSQL_FOUND) +if (PQXX_FOUND) + ADD_DEFINITIONS(-DWITH_PQXX) + include_directories(${PQXX_INCLUDE_DIR}) + message("PostgreSQL : yes") +else (PQXX_FOUND) + set(PQXX_LIBRARY "") + set(PQ_LIBRARY "") + message("PostgreSQL : no (install libpqxx-devel)") +endif (PQXX_FOUND) + if (PROTOBUF_FOUND) ADD_DEFINITIONS(-DWITH_PROTOBUF) include_directories(${PROTOBUF_INCLUDE_DIRS}) diff --git a/ChangeLog b/ChangeLog index 3372f46a..36a65268 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +Version 2.0.0-beta (X-X-X): + General: + * Fixed registration from Pidgin. + * Unsubscribe presence sent to some buddy doesn't disconnect the account. + * Remote Roster requests are not sent to resources, but to bare JID. + * Added automatic reconnection in case of non-fatal error. + * Added more error messages. + version 2.0.0 alpha (2011-12-06): General: * First Spectrum 2.0.0 alpha release, check more on diff --git a/backends/libcommuni/session.cpp b/backends/libcommuni/session.cpp index 088ebd1a..971c2c0c 100644 --- a/backends/libcommuni/session.cpp +++ b/backends/libcommuni/session.cpp @@ -26,6 +26,7 @@ MyIrcSession::MyIrcSession(const std::string &user, NetworkPlugin *np, const std this->np = np; this->user = user; this->suffix = suffix; + m_connected = false; rooms = 0; connect(this, SIGNAL(disconnected()), SLOT(on_disconnected())); connect(this, SIGNAL(connected()), SLOT(on_connected())); @@ -33,6 +34,7 @@ MyIrcSession::MyIrcSession(const std::string &user, NetworkPlugin *np, const std } void MyIrcSession::on_connected() { + m_connected = true; if (suffix.empty()) { np->handleConnected(user); } @@ -51,6 +53,7 @@ void MyIrcSession::on_connected() { void MyIrcSession::on_disconnected() { if (suffix.empty()) np->handleDisconnected(user, 0, ""); + m_connected = false; } bool MyIrcSession::correctNickname(std::string &nickname) { @@ -156,6 +159,9 @@ void MyIrcSession::on_messageReceived(IrcMessage *message) { } void MyIrcSession::on_numericMessageReceived(IrcMessage *message) { + QString channel; + QStringList members; + IrcNumericMessage *m = (IrcNumericMessage *) message; switch (m->code()) { case 332: @@ -165,8 +171,8 @@ void MyIrcSession::on_numericMessageReceived(IrcMessage *message) { np->handleSubject(user, m->parameters().value(1).toStdString() + suffix, m_topicData, m->parameters().value(2).toStdString()); break; case 353: - QString channel = m->parameters().value(2); - QStringList members = m->parameters().value(3).split(" "); + channel = m->parameters().value(2); + members = m->parameters().value(3).split(" "); for (int i = 0; i < members.size(); i++) { bool flags = 0; @@ -176,6 +182,13 @@ void MyIrcSession::on_numericMessageReceived(IrcMessage *message) { np->handleParticipantChanged(user, nickname, channel.toStdString() + suffix,(int) flags, pbnetwork::STATUS_ONLINE); } break; + case 432: + if (m_connected) { + np->handleDisconnected(user, pbnetwork::CONNECTION_ERROR_INVALID_USERNAME, "Erroneous Nickname"); + } + break; + default: + break; } //qDebug() << "numeric message received:" << receiver() << origin << code << params; @@ -208,5 +221,6 @@ void MyIrcSession::onMessageReceived(IrcMessage *message) { case IrcMessage::Numeric: on_numericMessageReceived(message); break; + default:break; } } diff --git a/backends/libcommuni/session.h b/backends/libcommuni/session.h index 3589dcb4..8b31da58 100644 --- a/backends/libcommuni/session.h +++ b/backends/libcommuni/session.h @@ -65,6 +65,7 @@ protected: std::string m_identify; std::list m_autoJoin; std::string m_topicData; + bool m_connected; }; //class MyIrcBuffer : public Irc::Buffer diff --git a/backends/libpurple/main.cpp b/backends/libpurple/main.cpp index 2c04df27..86a6f201 100644 --- a/backends/libpurple/main.cpp +++ b/backends/libpurple/main.cpp @@ -441,6 +441,16 @@ static void * requestInput(const char *title, const char *primary,const char *se ((PurpleRequestInputCb) ok_cb)(user_data, "Please authorize me."); return NULL; } + else if (primaryString == "Authorization Request Message:") { + LOG4CXX_INFO(logger, "Authorization Request Message: calling ok_cb(...)"); + ((PurpleRequestInputCb) ok_cb)(user_data, "Please authorize me."); + return NULL; + } + else if (primaryString == "Authorization Denied Message:") { + LOG4CXX_INFO(logger, "Authorization Deined Message: calling ok_cb(...)"); + ((PurpleRequestInputCb) ok_cb)(user_data, "Authorization denied."); + return NULL; + } else { LOG4CXX_WARN(logger, "Unhandled request input. primary=" << primaryString); } @@ -604,11 +614,13 @@ class SpectrumNetworkPlugin : public NetworkPlugin { return; } - LOG4CXX_INFO(logger, "Creating account with name '" << name.c_str() << "' and protocol '" << protocol << "'"); - if (purple_accounts_find(name.c_str(), protocol.c_str()) != NULL){ + + if (purple_accounts_find(name.c_str(), protocol.c_str()) != NULL) { + LOG4CXX_INFO(logger, "Using previously created account with name '" << name.c_str() << "' and protocol '" << protocol << "'"); account = purple_accounts_find(name.c_str(), protocol.c_str()); } else { + LOG4CXX_INFO(logger, "Creating account with name '" << name.c_str() << "' and protocol '" << protocol << "'"); account = purple_account_new(name.c_str(), protocol.c_str()); purple_accounts_add(account); } @@ -1078,6 +1090,8 @@ static void buddyListNewNode(PurpleBlistNode *node) { PurpleBuddy *buddy = (PurpleBuddy *) node; PurpleAccount *account = purple_buddy_get_account(buddy); + LOG4CXX_INFO(logger, "Buddy updated " << np->m_accounts[account] << " " << purple_buddy_get_name(buddy) << " " << getAlias(buddy)); + // Status pbnetwork::StatusType status = pbnetwork::STATUS_NONE; std::string message; diff --git a/cmake_modules/pqxxConfig.cmake b/cmake_modules/pqxxConfig.cmake new file mode 100644 index 00000000..9c53550c --- /dev/null +++ b/cmake_modules/pqxxConfig.cmake @@ -0,0 +1,16 @@ +FIND_PATH(PQXX_INCLUDE_DIR pqxx/pqxx PATHS) +MARK_AS_ADVANCED(PQXX_INCLUDE_DIR) + +FIND_LIBRARY(PQXX_LIBRARY pqxx ) +MARK_AS_ADVANCED(PQXX_LIBRARY) + +FIND_LIBRARY(PQ_LIBRARY pq ) +MARK_AS_ADVANCED(PQ_LIBRARY) + +if(PQXX_LIBRARY AND PQ_LIBRARY AND PQXX_INCLUDE_DIR) + set( PQXX_FOUND 1 ) + message( STATUS "Found pqxx: ${PQXX_LIBRARY}, ${PQ_LIBRARY}, ${PQXX_INCLUDE_DIR}") +else() + message(STATUS "Could NOT find pqxx and pq library") +endif() + diff --git a/include/Swiften/FileTransfer/CombinedOutgoingFileTransferManager.cpp b/include/Swiften/FileTransfer/CombinedOutgoingFileTransferManager.cpp index 710b8f83..ac06ab75 100644 --- a/include/Swiften/FileTransfer/CombinedOutgoingFileTransferManager.cpp +++ b/include/Swiften/FileTransfer/CombinedOutgoingFileTransferManager.cpp @@ -18,13 +18,12 @@ #include #include #include -#include #include namespace Swift { -CombinedOutgoingFileTransferManager::CombinedOutgoingFileTransferManager(JingleSessionManager* jingleSessionManager, IQRouter* router, EntityCapsProvider* capsProvider, RemoteJingleTransportCandidateSelectorFactory* remoteFactory, LocalJingleTransportCandidateGeneratorFactory* localFactory, SOCKS5BytestreamRegistry* bytestreamRegistry, SOCKS5BytestreamProxy* bytestreamProxy, PresenceOracle *presOracle, SOCKS5BytestreamServer *bytestreamServer) : jsManager(jingleSessionManager), iqRouter(router), capsProvider(capsProvider), remoteFactory(remoteFactory), localFactory(localFactory), bytestreamRegistry(bytestreamRegistry), bytestreamProxy(bytestreamProxy), presenceOracle(presOracle), bytestreamServer(bytestreamServer) { +CombinedOutgoingFileTransferManager::CombinedOutgoingFileTransferManager(JingleSessionManager* jingleSessionManager, IQRouter* router, EntityCapsProvider* capsProvider, RemoteJingleTransportCandidateSelectorFactory* remoteFactory, LocalJingleTransportCandidateGeneratorFactory* localFactory, SOCKS5BytestreamRegistry* bytestreamRegistry, SOCKS5BytestreamProxy* bytestreamProxy, Transport::PresenceOracle *presOracle, SOCKS5BytestreamServer *bytestreamServer) : jsManager(jingleSessionManager), iqRouter(router), capsProvider(capsProvider), remoteFactory(remoteFactory), localFactory(localFactory), bytestreamRegistry(bytestreamRegistry), bytestreamProxy(bytestreamProxy), presenceOracle(presOracle), bytestreamServer(bytestreamServer) { idGenerator = new IDGenerator(); } diff --git a/include/Swiften/FileTransfer/CombinedOutgoingFileTransferManager.h b/include/Swiften/FileTransfer/CombinedOutgoingFileTransferManager.h index 765e6ba2..17e7fc58 100644 --- a/include/Swiften/FileTransfer/CombinedOutgoingFileTransferManager.h +++ b/include/Swiften/FileTransfer/CombinedOutgoingFileTransferManager.h @@ -11,6 +11,8 @@ #include +#include "transport/presenceoracle.h" + namespace Swift { class JingleSessionManager; @@ -30,7 +32,7 @@ class PresenceOracle; class CombinedOutgoingFileTransferManager { public: - CombinedOutgoingFileTransferManager(JingleSessionManager* jingleSessionManager, IQRouter* router, EntityCapsProvider* capsProvider, RemoteJingleTransportCandidateSelectorFactory* remoteFactory, LocalJingleTransportCandidateGeneratorFactory* localFactory, SOCKS5BytestreamRegistry* bytestreamRegistry, SOCKS5BytestreamProxy* bytestreamProxy, PresenceOracle* presOracle, SOCKS5BytestreamServer *server); + CombinedOutgoingFileTransferManager(JingleSessionManager* jingleSessionManager, IQRouter* router, EntityCapsProvider* capsProvider, RemoteJingleTransportCandidateSelectorFactory* remoteFactory, LocalJingleTransportCandidateGeneratorFactory* localFactory, SOCKS5BytestreamRegistry* bytestreamRegistry, SOCKS5BytestreamProxy* bytestreamProxy, Transport::PresenceOracle* presOracle, SOCKS5BytestreamServer *server); ~CombinedOutgoingFileTransferManager(); boost::shared_ptr createOutgoingFileTransfer(const JID& from, const JID& to, boost::shared_ptr, const StreamInitiationFileInfo&); @@ -46,7 +48,7 @@ private: IDGenerator *idGenerator; SOCKS5BytestreamRegistry* bytestreamRegistry; SOCKS5BytestreamProxy* bytestreamProxy; - PresenceOracle* presenceOracle; + Transport::PresenceOracle* presenceOracle; SOCKS5BytestreamServer *bytestreamServer; }; diff --git a/include/transport/pqxxbackend.h b/include/transport/pqxxbackend.h new file mode 100644 index 00000000..e13c6b03 --- /dev/null +++ b/include/transport/pqxxbackend.h @@ -0,0 +1,109 @@ +/** + * libtransport -- C++ library for easy XMPP Transports development + * + * Copyright (C) 2011, 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 + */ + +#pragma once + +#ifdef WITH_PQXX + +#include +#include +#include "Swiften/Swiften.h" +#include "transport/storagebackend.h" +#include "transport/config.h" +#include + +namespace Transport { + +/// Used to store transport data into SQLite3 database. +class PQXXBackend : public StorageBackend +{ + public: + /// Creates new PQXXBackend instance. + /// \param config cofiguration, this class uses following Config values: + /// - database.database - path to SQLite3 database file, database file is created automatically + /// - service.prefix - prefix for tables created by createDatabase method + PQXXBackend(Config *config); + + /// Destructor. + ~PQXXBackend(); + + /// Connects to the database and creates it if it's needed. This method call createDatabase() function + /// automatically. + /// \return true if database is opened successfully. + bool connect(); + void disconnect(); + + /// Creates database structure. + /// \see connect() + /// \return true if database structure has been created successfully. Note that it returns True also if database structure + /// already exists. + bool createDatabase(); + + /// Stores user into database. + /// \param user user struct containing all information about user which have to be stored + void setUser(const UserInfo &user); + + /// Gets user data from database and stores them into user reference. + /// \param barejid barejid of user + /// \param user UserInfo object where user data will be stored + /// \return true if user has been found in database + bool getUser(const std::string &barejid, UserInfo &user); + + /// Changes users online state variable in database. + /// \param id id of user - UserInfo.id + /// \param online online state + void setUserOnline(long id, bool online); + + /// Removes user and all connected data from database. + /// \param id id of user - UserInfo.id + /// \return true if user has been found in database and removed + bool removeUser(long id); + + /// Returns JIDs of all buddies in user's roster. + /// \param id id of user - UserInfo.id + /// \param roster string list used to store user's roster + /// \return true if user has been found in database and roster has been fetched + bool getBuddies(long id, std::list &roster); + + bool getOnlineUsers(std::vector &users); + + long addBuddy(long userId, const BuddyInfo &buddyInfo); + + void updateBuddy(long userId, const BuddyInfo &buddyInfo); + void removeBuddy(long id) {} + + void getUserSetting(long userId, const std::string &variable, int &type, std::string &value); + void updateUserSetting(long userId, const std::string &variable, const std::string &value); + + void beginTransaction(); + void commitTransaction(); + + private: + bool exec(const std::string &query, bool show_error = true); + bool exec(pqxx::work &txn, const std::string &query, bool show_error = true); + Config *m_config; + std::string m_prefix; + + pqxx::connection *m_conn; +}; + +} + +#endif diff --git a/include/transport/presenceoracle.h b/include/transport/presenceoracle.h new file mode 100644 index 00000000..763cf83d --- /dev/null +++ b/include/transport/presenceoracle.h @@ -0,0 +1,57 @@ +/** + * 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 + */ + +#pragma once + +#include + +#include +#include +#include + +#include + +namespace Transport { + +class PresenceOracle { + public: + PresenceOracle(Swift::StanzaChannel* stanzaChannel); + ~PresenceOracle(); + + Swift::Presence::ref getLastPresence(const Swift::JID&) const; + Swift::Presence::ref getHighestPriorityPresence(const Swift::JID& bareJID) const; + std::vector getAllPresence(const Swift::JID& bareJID) const; + + public: + boost::signal onPresenceChange; + + private: + void handleIncomingPresence(Swift::Presence::ref presence); + void handleStanzaChannelAvailableChanged(bool); + + private: + typedef std::map PresenceMap; + typedef std::map PresencesMap; + PresencesMap entries_; + Swift::StanzaChannel* stanzaChannel_; +}; + +} + diff --git a/include/transport/transport.h b/include/transport/transport.h index 40442dcc..4a875754 100644 --- a/include/transport/transport.h +++ b/include/transport/transport.h @@ -27,7 +27,6 @@ #include "Swiften/Disco/EntityCapsManager.h" #include "Swiften/Disco/CapsManager.h" #include "Swiften/Disco/CapsMemoryStorage.h" -#include "Swiften/Presence/PresenceOracle.h" #include "Swiften/Network/BoostTimerFactory.h" #include "Swiften/Network/BoostIOServiceThread.h" #include "Swiften/Server/UserRegistry.h" @@ -37,6 +36,7 @@ #include #include "transport/config.h" #include "transport/factory.h" +#include "transport/presenceoracle.h" namespace Transport { // typedef enum { CLIENT_FEATURE_ROSTERX = 2, @@ -92,7 +92,7 @@ namespace Transport { /// You can use it to check current resource connected for particular user. /// \return Swift::PresenceOracle associated with this Transport::Component. - Swift::PresenceOracle *getPresenceOracle(); + PresenceOracle *getPresenceOracle(); /// Returns True if the component is in server mode. @@ -179,7 +179,7 @@ namespace Transport { Swift::EntityCapsManager *m_entityCapsManager; Swift::CapsManager *m_capsManager; Swift::CapsMemoryStorage *m_capsMemoryStorage; - Swift::PresenceOracle *m_presenceOracle; + PresenceOracle *m_presenceOracle; Swift::StanzaChannel *m_stanzaChannel; Swift::IQRouter *m_iqRouter; diff --git a/include/transport/user.h b/include/transport/user.h index de134634..4e98c4a8 100644 --- a/include/transport/user.h +++ b/include/transport/user.h @@ -22,7 +22,6 @@ #include #include "Swiften/Swiften.h" -#include "Swiften/Presence/PresenceOracle.h" #include "Swiften/Disco/EntityCapsManager.h" #include "Swiften/Disco/EntityCapsProvider.h" #include "storagebackend.h" @@ -35,6 +34,7 @@ class Component; class RosterManager; class ConversationManager; class UserManager; +class PresenceOracle; struct UserInfo; /// Represents online XMPP user. @@ -125,7 +125,7 @@ class User : public Swift::EntityCapsProvider { UserManager *m_userManager; ConversationManager *m_conversationManager; Swift::EntityCapsManager *m_entityCapsManager; - Swift::PresenceOracle *m_presenceOracle; + PresenceOracle *m_presenceOracle; UserInfo m_userInfo; void *m_data; bool m_connected; diff --git a/include/transport/util.h b/include/transport/util.h index fe171115..d79a555f 100644 --- a/include/transport/util.h +++ b/include/transport/util.h @@ -40,6 +40,8 @@ std::string serializeGroups(const std::vector &groups); std::vector deserializeGroups(std::string &groups); +int getRandomPort(const std::string &s); + } } diff --git a/spectrum/src/main.cpp b/spectrum/src/main.cpp index c0b8fb9b..c57573ea 100644 --- a/spectrum/src/main.cpp +++ b/spectrum/src/main.cpp @@ -5,6 +5,7 @@ #include "transport/logger.h" #include "transport/sqlite3backend.h" #include "transport/mysqlbackend.h" +#include "transport/pqxxbackend.h" #include "transport/userregistration.h" #include "transport/networkpluginserver.h" #include "transport/admininterface.h" @@ -379,7 +380,23 @@ int main(int argc, char **argv) } #endif - if (CONFIG_STRING(&config, "database.type") != "mysql" && CONFIG_STRING(&config, "database.type") != "sqlite3") { +#ifdef WITH_PQXX + if (CONFIG_STRING(&config, "database.type") == "pqxx") { + storageBackend = new PQXXBackend(&config); + if (!storageBackend->connect()) { + std::cerr << "Can't connect to database. Check the log to find out the reason.\n"; + return -1; + } + } +#else + if (CONFIG_STRING(&config, "database.type") == "pqxx") { + std::cerr << "Spectrum2 is not compiled with pqxx backend.\n"; + return -2; + } +#endif + + if (CONFIG_STRING(&config, "database.type") != "mysql" && CONFIG_STRING(&config, "database.type") != "sqlite3" + && CONFIG_STRING(&config, "database.type") != "pqxx" && CONFIG_STRING(&config, "database.type") != "none") { std::cerr << "Unknown storage backend " << CONFIG_STRING(&config, "database.type") << "\n"; return -2; } diff --git a/spectrum/src/sample2.cfg b/spectrum/src/sample2.cfg index ae1f0d73..91ab58fc 100644 --- a/spectrum/src/sample2.cfg +++ b/spectrum/src/sample2.cfg @@ -24,7 +24,9 @@ port = 5222 backend_host = localhost # Port on which Spectrum listens for backends. -backend_port=10001 +# By default Spectrum chooses random backend port and there's +# no need to change it normally +#backend_port=10001 # Full path to PKCS#12 cetficiate used for TLS in server mode. #cert= diff --git a/spectrum_manager/src/CMakeLists.txt b/spectrum_manager/src/CMakeLists.txt index 5024e70c..5f93dd9c 100644 --- a/spectrum_manager/src/CMakeLists.txt +++ b/spectrum_manager/src/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 2.6) FILE(GLOB SRC *.cpp) -ADD_EXECUTABLE(spectrum2_manager ${SRC} ../../src/config.cpp) +ADD_EXECUTABLE(spectrum2_manager ${SRC} ../../src/config.cpp ../../src/util.cpp) target_link_libraries(spectrum2_manager ${SWIFTEN_LIBRARY}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7a258e7a..ac077b20 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,9 +35,9 @@ if (CMAKE_COMPILER_IS_GNUCXX) endif() if (WIN32) - TARGET_LINK_LIBRARIES(transport ${SQLITE3_LIBRARIES} ${MYSQL_LIBRARIES} ${SWIFTEN_LIBRARY} ${PROTOBUF_LIBRARIES} ${LOG4CXX_LIBRARIES}) + TARGET_LINK_LIBRARIES(transport ${PQXX_LIBRARY} ${PQ_LIBRARY} ${SQLITE3_LIBRARIES} ${MYSQL_LIBRARIES} ${SWIFTEN_LIBRARY} ${PROTOBUF_LIBRARIES} ${LOG4CXX_LIBRARIES}) else (WIN32) - TARGET_LINK_LIBRARIES(transport ${SQLITE3_LIBRARIES} ${MYSQL_LIBRARIES} ${SWIFTEN_LIBRARY} ${PROTOBUF_LIBRARIES} ${LOG4CXX_LIBRARIES} ${POPT_LIBRARY}) + TARGET_LINK_LIBRARIES(transport ${PQXX_LIBRARY} ${PQ_LIBRARY} ${SQLITE3_LIBRARIES} ${MYSQL_LIBRARIES} ${SWIFTEN_LIBRARY} ${PROTOBUF_LIBRARIES} ${LOG4CXX_LIBRARIES} ${POPT_LIBRARY}) endif(WIN32) SET_TARGET_PROPERTIES(transport PROPERTIES diff --git a/src/config.cpp b/src/config.cpp index b72caa86..c5ca48b5 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -19,6 +19,7 @@ */ #include "transport/config.h" +#include "transport/util.h" #include #ifdef _MSC_VER #include @@ -120,13 +121,7 @@ bool Config::load(std::istream &ifs, boost::program_options::options_description else if (opt.string_key == "service.backend_port") { found_backend_port = true; if (opt.value[0] == "0") { - unsigned long r = 0; - BOOST_FOREACH(char c, _jid) { - r += (int) c; - } - srand(time(NULL) + r); - int randomPort = 30000 + rand() % 10000; - opt.value[0] = boost::lexical_cast(randomPort); + opt.value[0] = boost::lexical_cast(Util::getRandomPort(_jid.empty() ? jid : _jid)); } } else if (opt.string_key == "service.working_dir") { @@ -148,14 +143,9 @@ bool Config::load(std::istream &ifs, boost::program_options::options_description parsed.options.push_back(boost::program_options::basic_option("service.pidfile", value)); } if (!found_backend_port) { - unsigned long r = 0; - BOOST_FOREACH(char c, _jid) { - r += (int) c; - } - srand(time(NULL) + r); - int randomPort = 30000 + rand() % 10000; std::vector value; - value.push_back(boost::lexical_cast(randomPort)); + std::string p = boost::lexical_cast(Util::getRandomPort(_jid.empty() ? jid : _jid)); + value.push_back(p); parsed.options.push_back(boost::program_options::basic_option("service.backend_port", value)); } diff --git a/src/pqxxbackend.cpp b/src/pqxxbackend.cpp new file mode 100644 index 00000000..af0efd36 --- /dev/null +++ b/src/pqxxbackend.cpp @@ -0,0 +1,373 @@ +/** + * libtransport -- C++ library for easy XMPP Transports development + * + * Copyright (C) 2011, 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 + */ + +#ifdef WITH_PQXX + +#include "transport/pqxxbackend.h" +#include "transport/util.h" +#include +#include "log4cxx/logger.h" + +using namespace log4cxx; +using namespace boost; + +namespace Transport { + +static LoggerPtr logger = Logger::getLogger("PQXXBackend"); + +PQXXBackend::PQXXBackend(Config *config) { + m_config = config; + m_prefix = CONFIG_STRING(m_config, "database.prefix"); +} + +PQXXBackend::~PQXXBackend(){ + disconnect(); +} + +void PQXXBackend::disconnect() { + LOG4CXX_INFO(logger, "Disconnecting"); + + delete m_conn; +} + +bool PQXXBackend::connect() { + LOG4CXX_INFO(logger, "Connecting PostgreSQL server " << CONFIG_STRING(m_config, "database.server") << ", user " << + CONFIG_STRING(m_config, "database.user") << ", database " << CONFIG_STRING(m_config, "database.database") << + ", port " << CONFIG_INT(m_config, "database.port") + ); + + std::string str = "dbname="; + str += CONFIG_STRING(m_config, "database.database") + " "; + + str += "user=" + CONFIG_STRING(m_config, "database.user") + " "; + m_conn = new pqxx::connection(str); + + createDatabase(); + + return true; +} + +bool PQXXBackend::createDatabase() { + + int exist = exec("SELECT * FROM " + m_prefix + "buddies_settings LIMIT 1;", false); + + if (!exist) { + exec("CREATE TABLE " + m_prefix + "buddies_settings (" + "user_id integer NOT NULL," + "buddy_id integer NOT NULL," + "var varchar(50) NOT NULL," + "type smallint NOT NULL," + "value varchar(255) NOT NULL," + "PRIMARY KEY (buddy_id,var)" + ");"); + + exec("CREATE TYPE Subscription AS ENUM ('to','from','both','ask','none');"); + exec("CREATE TABLE " + m_prefix + "buddies (" + "id SERIAL," + "user_id integer NOT NULL," + "uin varchar(255) NOT NULL," + "subscription Subscription NOT NULL," + "nickname varchar(255) NOT NULL," + "groups varchar(255) NOT NULL," + "flags smallint NOT NULL DEFAULT '0'," + "PRIMARY KEY (id)," + "UNIQUE (user_id,uin)" + ");"); + + exec("CREATE TABLE " + m_prefix + "users (" + "id SERIAL," + "jid varchar(255) NOT NULL," + "uin varchar(4095) NOT NULL," + "password varchar(255) NOT NULL," + "language varchar(25) NOT NULL," + "encoding varchar(50) NOT NULL default 'utf8'," + "last_login timestamp," + "vip boolean NOT NULL default '0'," + "online boolean NOT NULL default '0'," + "PRIMARY KEY (id)," + "UNIQUE (jid)" + ");"); + + exec("CREATE TABLE " + m_prefix + "users_settings (" + "user_id integer NOT NULL," + "var varchar(50) NOT NULL," + "type smallint NOT NULL," + "value varchar(255) NOT NULL," + "PRIMARY KEY (user_id,var)" + ");"); + + exec("CREATE TABLE " + m_prefix + "db_version (" + "ver integer NOT NULL default '1'," + "UNIQUE (ver)" + ");"); + +// exec("INSERT INTO db_version (ver) VALUES ('2');"); + } + + return true; +} + +bool PQXXBackend::exec(const std::string &query, bool show_error) { + pqxx::work txn(*m_conn); + return exec(txn, query, show_error); +} + +bool PQXXBackend::exec(pqxx::work &txn, const std::string &query, bool show_error) { + try { + txn.exec(query); + txn.commit(); + } + catch (std::exception& e) { + if (show_error) + LOG4CXX_ERROR(logger, e.what()); + return false; + } + return true; +} + +void PQXXBackend::setUser(const UserInfo &user) { + std::string encrypted = user.password; + if (!CONFIG_STRING(m_config, "database.encryption_key").empty()) { + encrypted = Util::encryptPassword(encrypted, CONFIG_STRING(m_config, "database.encryption_key")); + } + pqxx::work txn(*m_conn); + exec(txn, "UPDATE " + m_prefix + "users SET uin=" + txn.quote(user.uin) + ", password=" + txn.quote(encrypted) + ";" + "INSERT INTO " + m_prefix + "users (jid, uin, password, language, encoding, last_login, vip) VALUES " + "(" + txn.quote(user.jid) + "," + + txn.quote(user.uin) + "," + + txn.quote(encrypted) + "," + + txn.quote(user.language) + "," + + txn.quote(user.encoding) + "," + + "NOW()," + + txn.quote(user.vip) +")"); +} + +bool PQXXBackend::getUser(const std::string &barejid, UserInfo &user) { + try { + pqxx::work txn(*m_conn); + + pqxx::result r = txn.exec("SELECT id, jid, uin, password, encoding, language, vip FROM " + m_prefix + "users WHERE jid=" + + txn.quote(barejid)); + + if (r.size() == 0) { + return false; + } + + user.id = r[0][0].as(); + user.jid = r[0][1].as(); + user.uin = r[0][2].as(); + user.password = r[0][3].as(); + user.encoding = r[0][4].as(); + user.language = r[0][5].as(); + user.vip = r[0][6].as(); + } + catch (std::exception& e) { + LOG4CXX_ERROR(logger, e.what()); + return false; + } + + return true; +} + +void PQXXBackend::setUserOnline(long id, bool online) { + try { + pqxx::work txn(*m_conn); + exec(txn, "UPDATE " + m_prefix + "users SET online=" + txn.quote(online) + ", last_login=NOW() WHERE id=" + txn.quote(id)); + } + catch (std::exception& e) { + LOG4CXX_ERROR(logger, e.what()); + } +} + +bool PQXXBackend::getOnlineUsers(std::vector &users) { + try { + pqxx::work txn(*m_conn); + pqxx::result r = txn.exec("SELECT jid FROM " + m_prefix + "users WHERE online=1"); + + for (pqxx::result::const_iterator it = r.begin(); it != r.end(); it++) { + users.push_back((*it)[0].as()); + } + } + catch (std::exception& e) { + LOG4CXX_ERROR(logger, e.what()); + return false; + } + + return true; +} + +long PQXXBackend::addBuddy(long userId, const BuddyInfo &buddyInfo) { +// "INSERT INTO " + m_prefix + "buddies (user_id, uin, subscription, groups, nickname, flags) VALUES (?, ?, ?, ?, ?, ?)" +// std::string groups = Util::serializeGroups(buddyInfo.groups); +// *m_addBuddy << userId << buddyInfo.legacyName << buddyInfo.subscription; +// *m_addBuddy << groups; +// *m_addBuddy << buddyInfo.alias << buddyInfo.flags; + +// EXEC(m_addBuddy, addBuddy(userId, buddyInfo)); + +// long id = (long) mysql_insert_id(&m_conn); + +// INSERT OR REPLACE INTO " + m_prefix + "buddies_settings (user_id, buddy_id, var, type, value) VALUES (?, ?, ?, ?, ?) +// if (!buddyInfo.settings.find("icon_hash")->second.s.empty()) { +// *m_updateBuddySetting << userId << id << buddyInfo.settings.find("icon_hash")->first << (int) TYPE_STRING << buddyInfo.settings.find("icon_hash")->second.s << buddyInfo.settings.find("icon_hash")->second.s; +// EXEC(m_updateBuddySetting, addBuddy(userId, buddyInfo)); +// } + + return 0; +} + +void PQXXBackend::updateBuddy(long userId, const BuddyInfo &buddyInfo) { +// "UPDATE " + m_prefix + "buddies SET groups=?, nickname=?, flags=?, subscription=? WHERE user_id=? AND uin=?" +// std::string groups = Util::serializeGroups(buddyInfo.groups); +// *m_updateBuddy << groups; +// *m_updateBuddy << buddyInfo.alias << buddyInfo.flags << buddyInfo.subscription; +// *m_updateBuddy << userId << buddyInfo.legacyName; + +// EXEC(m_updateBuddy, updateBuddy(userId, buddyInfo)); +} + +bool PQXXBackend::getBuddies(long id, std::list &roster) { +// SELECT id, uin, subscription, nickname, groups, flags FROM " + m_prefix + "buddies WHERE user_id=? ORDER BY id ASC +// *m_getBuddies << id; + +// "SELECT buddy_id, type, var, value FROM " + m_prefix + "buddies_settings WHERE user_id=? ORDER BY buddy_id ASC" +// *m_getBuddiesSettings << id; + +// SettingVariableInfo var; +// long buddy_id = -1; +// std::string key; + +// EXEC(m_getBuddies, getBuddies(id, roster)); +// if (!exec_ok) +// return false; + +// while (m_getBuddies->fetch() == 0) { +// BuddyInfo b; + +// std::string group; +// *m_getBuddies >> b.id >> b.legacyName >> b.subscription >> b.alias >> group >> b.flags; + +// if (!group.empty()) { +// b.groups = Util::deserializeGroups(group); +// } + +// roster.push_back(b); +// } + +// EXEC(m_getBuddiesSettings, getBuddies(id, roster)); +// if (!exec_ok) +// return false; + +// BOOST_FOREACH(BuddyInfo &b, roster) { +// if (buddy_id == b.id) { +//// std::cout << "Adding buddy info setting " << key << "\n"; +// b.settings[key] = var; +// buddy_id = -1; +// } + +// while(buddy_id == -1 && m_getBuddiesSettings->fetch() == 0) { +// std::string val; +// *m_getBuddiesSettings >> buddy_id >> var.type >> key >> val; + +// switch (var.type) { +// case TYPE_BOOLEAN: +// var.b = atoi(val.c_str()); +// break; +// case TYPE_STRING: +// var.s = val; +// break; +// default: +// if (buddy_id == b.id) { +// buddy_id = -1; +// } +// continue; +// break; +// } +// if (buddy_id == b.id) { +//// std::cout << "Adding buddy info setting " << key << "=" << val << "\n"; +// b.settings[key] = var; +// buddy_id = -1; +// } +// } +// } + +// while(m_getBuddiesSettings->fetch() == 0) { +// // TODO: probably remove those settings, because there's no buddy for them. +// // It should not happend, but one never know... +// } + + return true; +} + +bool PQXXBackend::removeUser(long id) { +// *m_removeUser << (int) id; +// EXEC(m_removeUser, removeUser(id)); +// if (!exec_ok) +// return false; + +// *m_removeUserSettings << (int) id; +// EXEC(m_removeUserSettings, removeUser(id)); +// if (!exec_ok) +// return false; + +// *m_removeUserBuddies << (int) id; +// EXEC(m_removeUserBuddies, removeUser(id)); +// if (!exec_ok) +// return false; + +// *m_removeUserBuddiesSettings << (int) id; +// EXEC(m_removeUserBuddiesSettings, removeUser(id)); +// if (!exec_ok) +// return false; + + return true; +} + +void PQXXBackend::getUserSetting(long id, const std::string &variable, int &type, std::string &value) { +//// "SELECT type, value FROM " + m_prefix + "users_settings WHERE user_id=? AND var=?" +// *m_getUserSetting << id << variable; +// EXEC(m_getUserSetting, getUserSetting(id, variable, type, value)); +// if (m_getUserSetting->fetch() != 0) { +//// "INSERT INTO " + m_prefix + "users_settings (user_id, var, type, value) VALUES (?,?,?,?)" +// *m_setUserSetting << id << variable << type << value; +// EXEC(m_setUserSetting, getUserSetting(id, variable, type, value)); +// } +// else { +// *m_getUserSetting >> type >> value; +// } +} + +void PQXXBackend::updateUserSetting(long id, const std::string &variable, const std::string &value) { +//// "UPDATE " + m_prefix + "users_settings SET value=? WHERE user_id=? AND var=?" +// *m_updateUserSetting << value << id << variable; +// EXEC(m_updateUserSetting, updateUserSetting(id, variable, value)); +} + +void PQXXBackend::beginTransaction() { +// exec("START TRANSACTION;"); +} + +void PQXXBackend::commitTransaction() { +// exec("COMMIT;"); +} + +} + +#endif diff --git a/src/presenceoracle.cpp b/src/presenceoracle.cpp new file mode 100644 index 00000000..a3245502 --- /dev/null +++ b/src/presenceoracle.cpp @@ -0,0 +1,136 @@ +/** + * 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 "transport/presenceoracle.h" +#include "Swiften/Swiften.h" + +#include + +using namespace Swift; + +namespace Transport { + +PresenceOracle::PresenceOracle(StanzaChannel* stanzaChannel) { + stanzaChannel_ = stanzaChannel; + stanzaChannel_->onPresenceReceived.connect(boost::bind(&PresenceOracle::handleIncomingPresence, this, _1)); + stanzaChannel_->onAvailableChanged.connect(boost::bind(&PresenceOracle::handleStanzaChannelAvailableChanged, this, _1)); +} + +PresenceOracle::~PresenceOracle() { + stanzaChannel_->onPresenceReceived.disconnect(boost::bind(&PresenceOracle::handleIncomingPresence, this, _1)); + stanzaChannel_->onAvailableChanged.disconnect(boost::bind(&PresenceOracle::handleStanzaChannelAvailableChanged, this, _1)); +} + +void PresenceOracle::handleStanzaChannelAvailableChanged(bool available) { + if (available) { + entries_.clear(); + } +} + + +void PresenceOracle::handleIncomingPresence(Presence::ref presence) { + // ignore presences for some contact, we're checking only presences for the transport itself here. + bool isMUC = presence->getPayload() != NULL || *presence->getTo().getNode().c_str() == '#'; + // filter out login/logout presence spam + if (!presence->getTo().getNode().empty() && isMUC == false) + return; + + JID bareJID(presence->getFrom().toBare()); + if (presence->getType() == Presence::Subscribe || presence->getType() == Presence::Subscribed) { + } + else { + Presence::ref passedPresence = presence; + if (presence->getType() == Presence::Unsubscribe || presence->getType() == Presence::Unsubscribed) { + /* 3921bis says that we don't follow up with an unavailable, so simulate this ourselves */ + passedPresence = Presence::ref(new Presence()); + passedPresence->setType(Presence::Unavailable); + passedPresence->setFrom(bareJID); + passedPresence->setStatus(presence->getStatus()); + } + std::map > jidMap = entries_[bareJID]; + if (passedPresence->getFrom().isBare() && presence->getType() == Presence::Unavailable) { + /* Have a bare-JID only presence of offline */ + jidMap.clear(); + } else if (passedPresence->getType() == Presence::Available) { + /* Don't have a bare-JID only offline presence once there are available presences */ + jidMap.erase(bareJID); + } + if (passedPresence->getType() == Presence::Unavailable && jidMap.size() > 1) { + jidMap.erase(passedPresence->getFrom()); + } else { + jidMap[passedPresence->getFrom()] = passedPresence; + } + entries_[bareJID] = jidMap; + onPresenceChange(passedPresence); + } +} + +Presence::ref PresenceOracle::getLastPresence(const JID& jid) const { + PresencesMap::const_iterator i = entries_.find(jid.toBare()); + if (i == entries_.end()) { + return Presence::ref(); + } + PresenceMap presenceMap = i->second; + PresenceMap::const_iterator j = presenceMap.find(jid); + if (j != presenceMap.end()) { + return j->second; + } + else { + return Presence::ref(); + } +} + +std::vector PresenceOracle::getAllPresence(const JID& bareJID) const { + std::vector results; + PresencesMap::const_iterator i = entries_.find(bareJID); + if (i == entries_.end()) { + return results; + } + PresenceMap presenceMap = i->second; + PresenceMap::const_iterator j = presenceMap.begin(); + for (; j != presenceMap.end(); ++j) { + Presence::ref current = j->second; + results.push_back(current); + } + return results; +} + +Presence::ref PresenceOracle::getHighestPriorityPresence(const JID& bareJID) const { + PresencesMap::const_iterator i = entries_.find(bareJID); + if (i == entries_.end()) { + return Presence::ref(); + } + PresenceMap presenceMap = i->second; + PresenceMap::const_iterator j = presenceMap.begin(); + Presence::ref highest; + for (; j != presenceMap.end(); ++j) { + Presence::ref current = j->second; + if (!highest + || current->getPriority() > highest->getPriority() + || (current->getPriority() == highest->getPriority() + && StatusShow::typeToAvailabilityOrdering(current->getShow()) > StatusShow::typeToAvailabilityOrdering(highest->getShow()))) { + highest = current; + } + + } + return highest; +} + +} diff --git a/src/storageresponder.cpp b/src/storageresponder.cpp index 84423593..b5f4e5aa 100644 --- a/src/storageresponder.cpp +++ b/src/storageresponder.cpp @@ -71,11 +71,19 @@ bool StorageResponder::handleSetRequest(const Swift::JID& from, const Swift::JID return true; } - StorageSerializer serializer; - std::string value = serializer.serializePayload(boost::dynamic_pointer_cast(payload->getPayload())); - m_storageBackend->updateUserSetting(user->getUserInfo().id, "storage", value); - LOG4CXX_INFO(logger, from.toBare().toString() << ": Storing jabber:iq:storage"); - sendResponse(from, id, boost::shared_ptr()); + boost::shared_ptr storage = boost::dynamic_pointer_cast(payload->getPayload()); + + if (storage) { + StorageSerializer serializer; + std::string value = serializer.serializePayload(boost::dynamic_pointer_cast(payload->getPayload())); + m_storageBackend->updateUserSetting(user->getUserInfo().id, "storage", value); + LOG4CXX_INFO(logger, from.toBare().toString() << ": Storing jabber:iq:storage"); + sendResponse(from, id, boost::shared_ptr()); + } + else { + LOG4CXX_INFO(logger, from.toBare().toString() << ": Unknown element. Libtransport does not support serialization of this."); + sendError(from, id, ErrorPayload::NotAcceptable, ErrorPayload::Cancel); + } return true; } diff --git a/src/transport.cpp b/src/transport.cpp index ddcda381..0d1d06ea 100644 --- a/src/transport.cpp +++ b/src/transport.cpp @@ -45,6 +45,7 @@ #include "log4cxx/consoleappender.h" #include "log4cxx/patternlayout.h" #include "log4cxx/propertyconfigurator.h" +#include "Swiften/Swiften.h" using namespace Swift; using namespace boost; @@ -137,7 +138,7 @@ Component::Component(Swift::EventLoop *loop, Swift::NetworkFactories *factories, m_entityCapsManager = new EntityCapsManager(m_capsManager, m_stanzaChannel); m_entityCapsManager->onCapsChanged.connect(boost::bind(&Component::handleCapsChanged, this, _1)); - m_presenceOracle = new PresenceOracle(m_stanzaChannel); + m_presenceOracle = new Transport::PresenceOracle(m_stanzaChannel); m_presenceOracle->onPresenceChange.connect(bind(&Component::handlePresence, this, _1)); m_discoInfoResponder = new DiscoInfoResponder(m_iqRouter, m_config); @@ -170,7 +171,7 @@ Swift::StanzaChannel *Component::getStanzaChannel() { return m_stanzaChannel; } -Swift::PresenceOracle *Component::getPresenceOracle() { +Transport::PresenceOracle *Component::getPresenceOracle() { return m_presenceOracle; } diff --git a/src/user.cpp b/src/user.cpp index 61740525..0467ee86 100644 --- a/src/user.cpp +++ b/src/user.cpp @@ -24,6 +24,7 @@ #include "transport/rostermanager.h" #include "transport/usermanager.h" #include "transport/conversationmanager.h" +#include "transport/presenceoracle.h" #include "Swiften/Swiften.h" #include "Swiften/Server/ServerStanzaChannel.h" #include "Swiften/Elements/StreamError.h" diff --git a/src/userregistration.cpp b/src/userregistration.cpp index 646722ed..9160465f 100644 --- a/src/userregistration.cpp +++ b/src/userregistration.cpp @@ -286,6 +286,10 @@ bool UserRegistration::handleSetRequest(const Swift::JID& from, const Swift::JID else if (textSingle->getName() == "encoding") { encoding = textSingle->getValue(); } + // Pidgin sends it as textSingle, not sure why... + else if (textSingle->getName() == "password") { + payload->setPassword(textSingle->getValue()); + } continue; } diff --git a/src/util.cpp b/src/util.cpp index 7053ea06..315726cb 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -128,6 +128,15 @@ std::vector deserializeGroups(std::string &groups) { return ret; } +int getRandomPort(const std::string &s) { + unsigned long r = 0; + BOOST_FOREACH(char c, s) { + r += (int) c; + } + srand(time(NULL) + r); + return 30000 + rand() % 10000; +} + } }