diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e1c2792..2f14df81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,23 @@ message(STATUS "|- log4cxx : -DLOG4CXX_INCLUDE_DIR, -DLOG4CXX_LIBRARY") message(STATUS "|- purple : -DPURPLE_INCLUDE_DIR, -DPURPLE_LIBRARY") message(STATUS " : -DPURPLE_NOT_RUNTIME - enables compilation with libpurple.lib") +option(ENABLE_SQLITE3 "Build with SQLite3 support" ON) +option(ENABLE_MYSQL "Build with MySQL support" ON) +option(ENABLE_PQXX "Build with Postgres supoort" ON) + +option(ENABLE_FROTZ "Build Frotz plugin" ON) +option(ENABLE_IRC "Build IRC plugin" ON) +option(ENABLE_PURPLE "Build Libpurple plugin" ON) +option(ENABLE_SMSTOOLS3 "Build SMSTools3 plugin" ON) +option(ENABLE_SKYPE "Build Skype plugin" ON) +option(ENABLE_SWIFTEN "Build Swiften plugin" ON) +option(ENABLE_TWITTER "Build Twitter plugin" ON) +option(ENABLE_YAHOO2 "Build Libyahoo2 plugin" ON) + +option(ENABLE_DOCS "Build Docs" ON) +option(ENABLE_LOG "Build with logging using Log4cxx" ON) +option(ENABLE_TESTS "Build Tests using CppUnit" OFF) + MACRO(LIST_CONTAINS var value) SET(${var}) FOREACH (value2 ${ARGN}) @@ -30,69 +47,114 @@ endif() set(CMAKE_MODULE_PATH "cmake_modules") -# FIND CPPUNIT -set(cppunit_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") -find_package(cppunit) +###### Prerequisites ###### -if(NOT CPPUNIT_FOUND AND CPPUNIT_INCLUDE_DIR AND CPPUNIT_LIBRARY) - set(CCPUNIT_LIBRARIES ${CPPUNIT_LIBRARY}) - set(CPPUNIT_FOUND 1) - message(STATUS "Using cppunit: ${CPPUNIT_INCLUDE_DIR} ${CPPUNIT_LIBRARIES}") -else() +# FIND BOOST +set(Boost_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") +if (WIN32) + set(Boost_USE_STATIC_LIBS ON) + set(Boost_USE_MULTITHREADED ON) + set(Boost_USE_STATIC_RUNTIME OFF) + find_package(Boost COMPONENTS program_options date_time system filesystem regex thread signals REQUIRED) +else(WIN32) + LIST_CONTAINS(contains -lboost_program_options ${SWIFTEN_LIBRARY}) + if(contains) + message(STATUS "Using non-multithreaded boost") + set(Boost_USE_MULTITHREADED 0) + endif(contains) + set(Boost_FIND_QUIETLY ON) + find_package(Boost COMPONENTS program_options date_time system filesystem regex thread-mt signals) + if (NOT Boost_FOUND) + set(Boost_FIND_QUIETLY OFF) + find_package(Boost COMPONENTS program_options date_time system filesystem regex thread signals REQUIRED) + endif() +endif(WIN32) +message( STATUS "Found Boost: ${Boost_LIBRARIES}, ${Boost_INCLUDE_DIR}") + +# FIND POPT +if (NOT WIN32) + set(popt_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") + find_package(popt REQUIRED) endif() +###### Database ###### + # FIND SQLITE3 -if (NOT CMAKE_COMPILER_IS_GNUCXX) -ADD_SUBDIRECTORY(msvc-deps) -else() -if (WIN32) -ADD_SUBDIRECTORY(msvc-deps/sqlite3) -else() -set(sqlite3_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") -find_package(sqlite3) -endif() +if (ENABLE_SQLITE3) + if (NOT CMAKE_COMPILER_IS_GNUCXX) + ADD_SUBDIRECTORY(msvc-deps) + else() + if (WIN32) + ADD_SUBDIRECTORY(msvc-deps/sqlite3) + else() + set(sqlite3_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") + find_package(sqlite3) + endif() + endif() endif() # FIND MYSQL -set(mysql_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") -find_package(mysql) +if(ENABLE_MYSQL) + set(mysql_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") + find_package(mysql) +endif() + +# FIND PQXX +if(ENABLE_PQXX) + set(pqxx_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") + find_package(pqxx) +endif() + +###### Plugins ###### # FIND LIBPURPLE -set(purple_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") -find_package(purple) +if(ENABLE_PURPLE) + set(purple_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") + find_package(purple) -if (WIN32) - if (PURPLE_NOT_RUNTIME) + if (WIN32) + if (PURPLE_NOT_RUNTIME) + add_definitions(-DPURPLE_RUNTIME=0) + else(PURPLE_NOT_RUNTIME) + add_definitions(-DPURPLE_RUNTIME=1) + endif(PURPLE_NOT_RUNTIME) + else() add_definitions(-DPURPLE_RUNTIME=0) - else(PURPLE_NOT_RUNTIME) - add_definitions(-DPURPLE_RUNTIME=1) - endif(PURPLE_NOT_RUNTIME) -else() - add_definitions(-DPURPLE_RUNTIME=0) + endif() + + # FIND LIBEVENT + set(event_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") + find_package(event) endif() # FIND GLIB -# if (GLIB2_INCLUDE_DIR AND GLIB2_LIBRARIES) -# set(GLIB2_FOUND TRUE) -# else() - set(glib_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") - find_package(glib) -# endif() +if(ENABLE_SKYPE OR ENABLE_PURPLE) +# if (GLIB2_INCLUDE_DIR AND GLIB2_LIBRARIES) +# set(GLIB2_FOUND TRUE) +# else() + set(glib_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") + find_package(glib) +# endif() +endif() # FIND LIBXML2 # set(libxml2_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") # find_package(libxml2) -# FIND POPT -if (NOT WIN32) - set(popt_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") - find_package(popt) -endif() +# FIND PROTOBUF +set(Protobuf_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") +find_package(Protobuf REQUIRED) -# FIND LIBEVENT -set(event_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") -find_package(event) +if (NOT PROTOBUF_FOUND AND PROTOBUF_INCLUDE_DIR AND PROTOBUF_LIBRARY) + set(PROTOBUF_FOUND 1) + set(PROTOBUF_INCLUDE_DIRS ${PROTOBUF_INCLUDE_DIR}) + if (PROTOBUF_PROTOC_EXECUTABLE) + else() + set(PROTOBUF_PROTOC_EXECUTABLE protoc) + endif() + message(STATUS "Using protobuf: ${PROTOBUF_INCLUDE_DIRS} ${PROTOBUF_LIBRARY}") +endif() # FIND SWIFTEN @@ -132,78 +194,58 @@ set(openssl_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") find_package(openssl) endif() -# FIND BOOST -set(Boost_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") -if (WIN32) - set(Boost_USE_STATIC_LIBS ON) - set(Boost_USE_MULTITHREADED ON) - set(Boost_USE_STATIC_RUNTIME OFF) - find_package(Boost COMPONENTS program_options date_time system filesystem regex thread signals REQUIRED) -else(WIN32) - LIST_CONTAINS(contains -lboost_program_options ${SWIFTEN_LIBRARY}) - if(contains) - message(STATUS "Using non-multithreaded boost") - set(Boost_USE_MULTITHREADED 0) - endif(contains) - set(Boost_FIND_QUIETLY ON) - find_package(Boost COMPONENTS program_options date_time system filesystem regex thread-mt signals) - if (NOT Boost_FOUND) - set(Boost_FIND_QUIETLY OFF) - find_package(Boost COMPONENTS program_options date_time system filesystem regex thread signals REQUIRED) - endif() -endif(WIN32) +if(ENABLE_IRC) + set(Communi_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") + find_package(Communi) -message( STATUS "Found Boost: ${Boost_LIBRARIES}, ${Boost_INCLUDE_DIR}") - -set(Protobuf_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") -find_package(Protobuf) - -# FIND PROTOBUF -if (NOT PROTOBUF_FOUND AND PROTOBUF_INCLUDE_DIR AND PROTOBUF_LIBRARY) - set(PROTOBUF_FOUND 1) - set(PROTOBUF_INCLUDE_DIRS ${PROTOBUF_INCLUDE_DIR}) - if (PROTOBUF_PROTOC_EXECUTABLE) - else() - set(PROTOBUF_PROTOC_EXECUTABLE protoc) - endif() - message(STATUS "Using protobuf: ${PROTOBUF_INCLUDE_DIRS} ${PROTOBUF_LIBRARY}") -endif() - - -set(Communi_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") -find_package(Communi) - -if(LOG4CXX_INCLUDE_DIR AND LOG4CXX_LIBRARY) - set(LOG4CXX_LIBRARIES ${LOG4CXX_LIBRARY}) - set(LOG4CXX_FOUND 1) - message(STATUS "Using log4cxx: ${CPPUNIT_INCLUDE_DIR} ${LOG4CXX_INCLUDE_DIR}") -else() - set(log4cxx_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") - find_package(log4cxx) + INCLUDE(FindQt4) + FIND_PACKAGE(Qt4 COMPONENTS QtCore QtNetwork) + # ADD_DEFINITIONS(${SWIFTEN_CFLAGS}) + ADD_DEFINITIONS(-DSUPPORT_LEGACY_CAPS) + # ADD_DEFINITIONS(-DBOOST_FILESYSTEM_VERSION=2) endif() set(event_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") find_package(event) -set(pqxx_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") -find_package(pqxx) - -if (NOT WIN32) +if (NOT WIN32 AND ENABLE_SKYPE) set(dbus_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") find_package(dbus) endif() -set(yahoo2_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") -find_package(yahoo2) +if(ENABLE_YAHOO2) + set(yahoo2_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") + find_package(yahoo2) +endif() -find_package(Doxygen) +####### Miscallanous ###### -INCLUDE(FindQt4) -FIND_PACKAGE(Qt4 COMPONENTS QtCore QtNetwork) +if(ENABLE_DOCS) + find_package(Doxygen) +endif() -# ADD_DEFINITIONS(${SWIFTEN_CFLAGS}) -ADD_DEFINITIONS(-DSUPPORT_LEGACY_CAPS) -# ADD_DEFINITIONS(-DBOOST_FILESYSTEM_VERSION=2) +if(ENABLE_LOG) + if(LOG4CXX_INCLUDE_DIR AND LOG4CXX_LIBRARY) + set(LOG4CXX_LIBRARIES ${LOG4CXX_LIBRARY}) + set(LOG4CXX_FOUND 1) + message(STATUS "Using log4cxx: ${CPPUNIT_INCLUDE_DIR} ${LOG4CXX_INCLUDE_DIR}") + else() + set(log4cxx_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") + find_package(log4cxx) + endif() +endif() + +# FIND CPPUNIT +if(ENABLE_TESTS) + set(cppunit_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") + find_package(cppunit) + + if(NOT CPPUNIT_FOUND AND CPPUNIT_INCLUDE_DIR AND CPPUNIT_LIBRARY) + set(CCPUNIT_LIBRARIES ${CPPUNIT_LIBRARY}) + set(CPPUNIT_FOUND 1) + message(STATUS "Using cppunit: ${CPPUNIT_INCLUDE_DIR} ${CPPUNIT_LIBRARIES}") + endif() +endif() message(" Supported features") message("-----------------------") @@ -234,14 +276,18 @@ if (SQLITE3_FOUND) include_directories(${SQLITE3_INCLUDE_DIR}) message("SQLite3 : yes") else (SQLITE3_FOUND) -if (WIN32) - ADD_DEFINITIONS(-DWITH_SQLITE) - include_directories(msvc-deps/sqlite3) - message("SQLite3 : bundled") -else() - set(SQLITE3_LIBRARIES "") - message("SQLite3 : no") -endif() + if (WIN32) + ADD_DEFINITIONS(-DWITH_SQLITE) + include_directories(msvc-deps/sqlite3) + message("SQLite3 : bundled") + else() + set(SQLITE3_LIBRARIES "") + if(ENABLE_SQLITE3) + message("SQLite3 : no (install sqlite3)") + else(ENABLE_SQLITE3) + message("SQLite3 : no (user disabled)") + endif() + endif() endif (SQLITE3_FOUND) if (MYSQL_FOUND) @@ -250,7 +296,11 @@ if (MYSQL_FOUND) message("MySQL : yes") else (MYSQL_FOUND) set(MYSQL_LIBRARIES "") - message("MySQL : no (install mysql-devel)") + if(ENABLE_MYSQL) + message("MySQL : no (install mysql-devel)") + else(ENABLE_MYSQL) + message("MySQL : no (user disabled)") + endif() endif (MYSQL_FOUND) if (PQXX_FOUND) @@ -260,7 +310,11 @@ if (PQXX_FOUND) else (PQXX_FOUND) set(PQXX_LIBRARY "") set(PQ_LIBRARY "") - message("PostgreSQL : no (install libpqxx-devel)") + if(ENABLE_PQXX) + message("PostgreSQL : no (install libpqxx-devel)") + else(ENABLE_PQXX) + message("PostgreSQL : no (user disabled)") + endif() endif (PQXX_FOUND) if (PROTOBUF_FOUND) @@ -273,7 +327,11 @@ if (PROTOBUF_FOUND) include_directories(${PURPLE_INCLUDE_DIR}) include_directories(${GLIB2_INCLUDE_DIR}) else() - message("Libpurple plugin : no (install libpurple)") + if(ENABLE_PURPLE) + message("Libpurple plugin : no (install libpurple)") + else(ENABLE_PURPLE) + message("Libpurple plugin : no (user disabled)") + endif() endif() if (HAVE_EVENT) @@ -281,7 +339,9 @@ if (PROTOBUF_FOUND) include_directories(${EVENT_INCLUDE_DIRS}) message(" libev eventloop : yes") else() - message(" libev eventloop : no (install libev-devel)") + if(ENABLE_PURPLE) + message(" libev eventloop : no (install libev-devel)") + endif() endif() if(IRC_FOUND) @@ -291,35 +351,62 @@ if (PROTOBUF_FOUND) include_directories(${IRC_INCLUDE_DIR}) include(${QT_USE_FILE}) else() - message("IRC plugin : no (install libCommuni and libprotobuf-dev)") + if(ENABLE_IRC) + message("IRC plugin : no (install libCommuni and libprotobuf-dev)") + else(ENABLE_IRC) + message("IRC plugin : no (user disabled)") + endif() endif() -if (NOT WIN32) - message("Frotz plugin : yes") - message("SMSTools3 plugin : yes") - if(${LIBDBUSGLIB_FOUND}) - message("Skype plugin : yes") - include_directories(${LIBDBUSGLIB_INCLUDE_DIRS}) + if (NOT WIN32) + if(ENABLE_FROTZ) + message("Frotz plugin : yes") + else() + message("Frotz plugin : no (user disabled)") + endif() + if(ENABLE_SMSTOOLS3) + message("SMSTools3 plugin : yes") + else() + message("SMSTools3 plugin : no (user disabled)") + endif() + if(${LIBDBUSGLIB_FOUND}) + message("Skype plugin : yes") + include_directories(${LIBDBUSGLIB_INCLUDE_DIRS}) + else() + if(ENABLE_SKYPE) + message("Skype plugin : no (install dbus-glib-devel)") + else(ENABLE_SKYPE) + message("Skype plugin : no (user disabled)") + endif() + endif() + if(ENABLE_TWITTER) + message("Twitter plugin : yes") + else(ENABLE_TWITTER) + message("Twitter plugin : no (user disabled)") + endif() else() - message("Skype plugin : no (install dbus-glib-devel)") + message("Frotz plugin : no (does not run on Win32)") + message("SMSTools3 plugin : no (does not run on Win32)") + message("Skype plugin : no (does not run on Win32)") + message("Twitter plugin : no (does not run on Win32)") endif() -else() - message("Frotz plugin : no") - message("SMSTools3 plugin : no") - message("Skype plugin : no") -endif() -# We have our own copy now... -# if(YAHOO2_FOUND) + if(YAHOO2_FOUND) message("Libyahoo2 plugin : yes") -# include_directories(${YAHOO2_INCLUDE_DIR}) -# else() -# message("Libyahoo2 plugin : no (install libyahoo2-devel)") -# endif() - - message("Swiften plugin : yes") - message("Twitter plugin : yes") + include_directories(${YAHOO2_INCLUDE_DIR}) + else() + if(ENABLE_YAHOO2) + message("Libyahoo2 plugin : no (install libyahoo2-devel)") + else(ENABLE_YAHOO2) + message("Libyahoo2 plugin : no (user disabled)") + endif() + endif() + if(ENABLE_SWIFTEN) + message("Swiften plugin : yes") + else() + message("Swiften plugin : no (user disabled)") + endif() else() message("Network plugins : no (install libprotobuf-dev)") message("Libpurple plugin : no (install libpurple and libprotobuf-dev)") @@ -336,22 +423,26 @@ if (LOG4CXX_FOUND) ADD_DEFINITIONS(-DWITH_LOG4CXX) else() set(LOG4CXX_LIBRARIES "") - message("Log4cxx : no (install log4cxx-devel)") + if(ENABLE_LOG) + message("Log4cxx : no (install log4cxx-devel)") + else(ENABLE_LOG) + message("Log4cxx : no (user disabled)") + endif() endif() if (WIN32) -ADD_DEFINITIONS(-DLOG4CXX_STATIC) -ADD_DEFINITIONS(-D_WIN32_WINNT=0x501) -ADD_DEFINITIONS(-DWIN32_LEAN_AND_MEAN) -ADD_DEFINITIONS(-DBOOST_USE_WINDOWS_H) -ADD_DEFINITIONS(-DBOOST_THREAD_USE_LIB) + ADD_DEFINITIONS(-DLOG4CXX_STATIC) + ADD_DEFINITIONS(-D_WIN32_WINNT=0x501) + ADD_DEFINITIONS(-DWIN32_LEAN_AND_MEAN) + ADD_DEFINITIONS(-DBOOST_USE_WINDOWS_H) + ADD_DEFINITIONS(-DBOOST_THREAD_USE_LIB) endif() if(CMAKE_BUILD_TYPE MATCHES Debug) -if (CMAKE_COMPILER_IS_GNUCXX) - ADD_DEFINITIONS(-O0) - ADD_DEFINITIONS(-ggdb) -endif() + if (CMAKE_COMPILER_IS_GNUCXX) + ADD_DEFINITIONS(-O0) + ADD_DEFINITIONS(-ggdb) + endif() ADD_DEFINITIONS(-DDEBUG) message("Debug : yes") else(CMAKE_BUILD_TYPE MATCHES Debug) @@ -386,14 +477,31 @@ if (CPPUNIT_FOUND) message("tests : yes") include_directories(${CPPUNIT_INCLUDE_DIR}) else() - message("tests : no (install CPPUnit)") + if(ENABLE_TESTS) + message("tests : no (install CPPUnit)") + else(ENABLE_TESTS) + message("tests : no (user disabled)") + endif() endif() if(DOXYGEN_FOUND) message("Docs : yes") ADD_SUBDIRECTORY(docs) else(DOXYGEN_FOUND) - message("Docs : no (install doxygen)") + if(ENABLE_DOCS) + message("Docs : no (install doxygen)") + else(ENABLE_DOCS) + message("Docs : no (user disabled)") + endif() endif(DOXYGEN_FOUND) message("----------------------") + +if(NOT SQLITE3_FOUND AND NOT MYSQL_FOUND AND NOT PQXX_FOUND) + if(ENABLE_SQLITE3 OR ENABLE_MYSQL OR ENABLE_PQXX) + message("Could not find any database - Please install at least one of sqlite3-devel, mysql-devel or pqxx-devel if you want to use transport mode.") + else(ENABLE_SQLITE3 OR ENABLE_MYSQL OR ENABLE_PQXX) + message("Please enable at least one of SQLITE3, MYSQL, PQXX databases to use transport mode.") + endif() +endif() + diff --git a/backends/CMakeLists.txt b/backends/CMakeLists.txt index ac9b6f0b..70918f94 100644 --- a/backends/CMakeLists.txt +++ b/backends/CMakeLists.txt @@ -7,21 +7,27 @@ if (PROTOBUF_FOUND) ADD_SUBDIRECTORY(libcommuni) endif() - ADD_SUBDIRECTORY(swiften) + if (ENABLE_SWIFTEN) + ADD_SUBDIRECTORY(swiften) + endif() ADD_SUBDIRECTORY(template) - -if (NOT WIN32) - ADD_SUBDIRECTORY(smstools3) - ADD_SUBDIRECTORY(frotz) -# if(YAHOO2_FOUND) - ADD_SUBDIRECTORY(libyahoo2) -# endif() - ADD_SUBDIRECTORY(twitter) - if (${LIBDBUSGLIB_FOUND}) - ADD_SUBDIRECTORY(skype) + if (NOT WIN32) + if(ENABLE_SMSTOOLS3) + ADD_SUBDIRECTORY(smstools3) + endif() + if(ENABLE_FROTZ) + ADD_SUBDIRECTORY(frotz) + endif() + if(YAHOO2_FOUND) + ADD_SUBDIRECTORY(libyahoo2) + endif() + if(ENABLE_TWITTER) + ADD_SUBDIRECTORY(twitter) + endif() + if (${LIBDBUSGLIB_FOUND}) + ADD_SUBDIRECTORY(skype) + endif() endif() endif() - -endif() diff --git a/backends/libcommuni/CMakeLists.txt b/backends/libcommuni/CMakeLists.txt index 38e7507b..699b02bd 100644 --- a/backends/libcommuni/CMakeLists.txt +++ b/backends/libcommuni/CMakeLists.txt @@ -1,10 +1,14 @@ cmake_minimum_required(VERSION 2.6) FILE(GLOB SRC *.cpp) FILE(GLOB HEADERS *.h) -QT4_WRAP_CPP(SRC ${HEADERS}) +QT4_WRAP_CPP(SRC ${HEADERS} OPTIONS -DBOOST_TT_HAS_OPERATOR_HPP_INCLUDED) ADD_EXECUTABLE(spectrum2_libcommuni_backend ${SRC}) -target_link_libraries(spectrum2_libcommuni_backend ${IRC_LIBRARY} ${QT_LIBRARIES} transport pthread) +if (NOT WIN32) + target_link_libraries(spectrum2_libcommuni_backend ${IRC_LIBRARY} ${QT_LIBRARIES} transport pthread) +else () + target_link_libraries(spectrum2_libcommuni_backend ${IRC_LIBRARY} ${QT_LIBRARIES} transport) +endif() INSTALL(TARGETS spectrum2_libcommuni_backend RUNTIME DESTINATION bin) diff --git a/backends/libcommuni/ircnetworkplugin.cpp b/backends/libcommuni/ircnetworkplugin.cpp index 90e4d663..bda4cc4d 100644 --- a/backends/libcommuni/ircnetworkplugin.cpp +++ b/backends/libcommuni/ircnetworkplugin.cpp @@ -11,6 +11,7 @@ DEFINE_LOGGER(logger, "IRCNetworkPlugin"); IRCNetworkPlugin::IRCNetworkPlugin(Config *config, Swift::QtEventLoop *loop, const std::string &host, int port) { this->config = config; m_currentServer = 0; + m_firstPing = true; m_socket = new QTcpSocket(); m_socket->connectToHost(FROM_UTF8(host), port); connect(m_socket, SIGNAL(readyRead()), this, SLOT(readData())); @@ -48,6 +49,17 @@ void IRCNetworkPlugin::readData() { if (availableBytes == 0) return; + if (m_firstPing) { + m_firstPing = false; + // Users can join the network without registering if we allow + // one user to connect multiple IRC networks. + if (m_servers.empty()) { + NetworkPlugin::PluginConfig cfg; + cfg.setNeedRegistration(false); + sendConfig(cfg); + } + } + std::string d = std::string(m_socket->readAll().data(), availableBytes); handleDataRead(d); } @@ -142,7 +154,13 @@ void IRCNetworkPlugin::handleMessageSendRequest(const std::string &user, const s std::string target = getTargetName(legacyName); LOG4CXX_INFO(logger, user << ": Session name: " << session << ", message to " << target); - m_sessions[session]->sendCommand(IrcCommand::createMessage(FROM_UTF8(target), FROM_UTF8(message))); + + if (message.find("/me") == 0) { + m_sessions[session]->sendCommand(IrcCommand::createCtcpAction(FROM_UTF8(target), FROM_UTF8(message.substr(4)))); + } + else { + m_sessions[session]->sendCommand(IrcCommand::createMessage(FROM_UTF8(target), FROM_UTF8(message))); + } if (target.find("#") == 0) { handleMessage(user, legacyName, message, TO_UTF8(m_sessions[session]->nickName())); diff --git a/backends/libcommuni/ircnetworkplugin.h b/backends/libcommuni/ircnetworkplugin.h index 043089cd..d4a84d73 100644 --- a/backends/libcommuni/ircnetworkplugin.h +++ b/backends/libcommuni/ircnetworkplugin.h @@ -46,4 +46,5 @@ class IRCNetworkPlugin : public QObject, public NetworkPlugin { std::vector m_servers; int m_currentServer; std::string m_identify; + bool m_firstPing; }; \ No newline at end of file diff --git a/backends/libcommuni/session.cpp b/backends/libcommuni/session.cpp index 2eabfe96..025de37a 100644 --- a/backends/libcommuni/session.cpp +++ b/backends/libcommuni/session.cpp @@ -23,6 +23,8 @@ DEFINE_LOGGER(logger, "IRCSession"); +static bool sentList; + MyIrcSession::MyIrcSession(const std::string &user, IRCNetworkPlugin *np, const std::string &suffix, QObject* parent) : IrcSession(parent) { this->np = np; @@ -40,8 +42,14 @@ void MyIrcSession::on_connected() { m_connected = true; if (suffix.empty()) { np->handleConnected(user); +// if (!sentList) { +// sendCommand(IrcCommand::createList("", "")); +// sentList = true; +// } } + sendCommand(IrcCommand::createCapability("REQ", QStringList("away-notify"))); + for(AutoJoinMap::iterator it = m_autoJoin.begin(); it != m_autoJoin.end(); it++) { sendCommand(IrcCommand::createJoin(FROM_UTF8(it->second->getChannel()), FROM_UTF8(it->second->getPassword()))); } @@ -80,8 +88,8 @@ void MyIrcSession::on_joined(IrcMessage *message) { bool flags = 0; std::string nickname = TO_UTF8(m->sender().name()); flags = correctNickname(nickname); - np->handleParticipantChanged(user, nickname, TO_UTF8(m->channel()), (int) flags, pbnetwork::STATUS_ONLINE); - LOG4CXX_INFO(logger, user << ": Joined " << TO_UTF8(m->parameters()[0])); + np->handleParticipantChanged(user, nickname, TO_UTF8(m->channel()) + suffix, (int) flags, pbnetwork::STATUS_ONLINE); + LOG4CXX_INFO(logger, user << ": " << nickname << " joined " << TO_UTF8(m->channel()) + suffix); } @@ -158,34 +166,56 @@ void MyIrcSession::on_messageReceived(IrcMessage *message) { } } + QString msg = m->message(); + if (m->isAction()) { + msg = QString("/me ") + msg; + } + std::string target = TO_UTF8(m->target()); LOG4CXX_INFO(logger, user << ": Message from " << target); if (target.find("#") == 0) { bool flags = 0; std::string nickname = TO_UTF8(m->sender().name()); flags = correctNickname(nickname); - np->handleMessage(user, target + suffix, TO_UTF8(m->message()), nickname); + np->handleMessage(user, target + suffix, TO_UTF8(msg), nickname); } else { bool flags = 0; std::string nickname = TO_UTF8(m->sender().name()); flags = correctNickname(nickname); LOG4CXX_INFO(logger, nickname + suffix); - np->handleMessage(user, nickname + suffix, TO_UTF8(m->message())); + np->handleMessage(user, nickname + suffix, TO_UTF8(msg)); } } void MyIrcSession::on_numericMessageReceived(IrcMessage *message) { QString channel; QStringList members; + std::string nick; IrcNumericMessage *m = (IrcNumericMessage *) message; switch (m->code()) { + case 301: + break; case 332: m_topicData = TO_UTF8(m->parameters().value(2)); break; case 333: - np->handleSubject(user, TO_UTF8(m->parameters().value(1)) + suffix, m_topicData, TO_UTF8(m->parameters().value(2))); + nick = TO_UTF8(m->parameters().value(2)); + if (nick.find("!") != std::string::npos) { + nick = nick.substr(0, nick.find("!")); + } + if (nick.find("/") != std::string::npos) { + nick = nick.substr(0, nick.find("/")); + } + np->handleSubject(user, TO_UTF8(m->parameters().value(1)) + suffix, m_topicData, nick); + break; + case 352: + channel = m->parameters().value(1); + nick = TO_UTF8(m->parameters().value(5)); + if (m->parameters().value(6).toUpper().startsWith("G")) { + np->handleParticipantChanged(user, nick, TO_UTF8(channel) + suffix, m_modes[TO_UTF8(channel) + nick], pbnetwork::STATUS_AWAY); + } break; case 353: channel = m->parameters().value(2); @@ -199,10 +229,24 @@ void MyIrcSession::on_numericMessageReceived(IrcMessage *message) { m_modes[TO_UTF8(channel) + nickname] = flags; np->handleParticipantChanged(user, nickname, TO_UTF8(channel) + suffix,(int) flags, pbnetwork::STATUS_ONLINE); } + + // ask /who to get away states + sendCommand(IrcCommand::createWho(channel)); break; case 432: np->handleDisconnected(user, pbnetwork::CONNECTION_ERROR_INVALID_USERNAME, "Erroneous Nickname"); break; + case 321: + m_rooms.clear(); + m_names.clear(); + break; + case 322: + m_rooms.push_back(TO_UTF8(m->parameters().value(1))); + m_names.push_back(TO_UTF8(m->parameters().value(1))); + break; + case 323: + np->handleRoomList("", m_rooms, m_names); + break; default: break; } diff --git a/backends/libcommuni/session.h b/backends/libcommuni/session.h index 507feb93..d398dd25 100644 --- a/backends/libcommuni/session.h +++ b/backends/libcommuni/session.h @@ -41,6 +41,7 @@ public: MyIrcSession(const std::string &user, IRCNetworkPlugin *np, const std::string &suffix = "", QObject* parent = 0); std::map m_modes; + std::map m_away; std::string suffix; int rooms; @@ -85,39 +86,8 @@ protected: AutoJoinMap m_autoJoin; std::string m_topicData; bool m_connected; + std::list m_rooms; + std::list m_names; }; -//class MyIrcBuffer : public Irc::Buffer -//{ -// Q_OBJECT - -//public: -// MyIrcBuffer(const QString& receiver, const std::string &user, NetworkPlugin *np, const std::string &suffix, Irc::Session* parent); -// NetworkPlugin *np; -// std::string user; -// MyIrcSession *p; -// std::string m_topicData; -// std::string suffix; - -//protected Q_SLOTS: -// void on_receiverChanged(const QString& receiver); -// void on_joined(const QString& origin); -// void on_parted(const QString& origin, const QString& message); -// void on_quit(const QString& origin, const QString& message); -// void on_nickChanged(const QString& origin, const QString& nick); -// void on_modeChanged(const QString& origin, const QString& mode, const QString& args); -// void on_topicChanged(const QString& origin, const QString& topic); -// void on_invited(const QString& origin, const QString& receiver, const QString& channel); -// void on_kicked(const QString& origin, const QString& nick, const QString& message); -// void on_messageReceived(const QString& origin, const QString& message, Irc::Buffer::MessageFlags flags); -// void on_noticeReceived(const QString& origin, const QString& notice, Irc::Buffer::MessageFlags flags); -// void on_ctcpRequestReceived(const QString& origin, const QString& request, Irc::Buffer::MessageFlags flags); -// void on_ctcpReplyReceived(const QString& origin, const QString& reply, Irc::Buffer::MessageFlags flags); -// void on_ctcpActionReceived(const QString& origin, const QString& action, Irc::Buffer::MessageFlags flags); -// void on_numericMessageReceived(const QString& origin, uint code, const QStringList& params); -// void on_unknownMessageReceived(const QString& origin, const QStringList& params); - -// bool correctNickname(std::string &nickname); -//}; - #endif // SESSION_H diff --git a/backends/libpurple/main.cpp b/backends/libpurple/main.cpp index 1c3f8c48..06df61ee 100644 --- a/backends/libpurple/main.cpp +++ b/backends/libpurple/main.cpp @@ -933,14 +933,21 @@ static void conv_write_im(PurpleConversation *conv, const char *who, const char xhtml_ = ""; } + std::string timestamp; + if (mtime && (unsigned long) time(NULL)-10 > (unsigned long) mtime/* && (unsigned long) time(NULL) - 31536000 < (unsigned long) mtime*/) { + char buf[80]; + strftime(buf, sizeof(buf), "%Y%m%dT%H%M%S", gmtime(&mtime)); + timestamp = buf; + } + // LOG4CXX_INFO(logger, "Received message body='" << message_ << "' xhtml='" << xhtml_ << "'"); if (purple_conversation_get_type_wrapped(conv) == PURPLE_CONV_TYPE_IM) { - np->handleMessage(np->m_accounts[account], w, message_, "", xhtml_); + np->handleMessage(np->m_accounts[account], w, message_, "", xhtml_, timestamp); } else { LOG4CXX_INFO(logger, "Received message body='" << message_ << "' name='" << purple_conversation_get_name_wrapped(conv) << "' " << w); - np->handleMessage(np->m_accounts[account], purple_conversation_get_name_wrapped(conv), message_, w, xhtml_); + np->handleMessage(np->m_accounts[account], purple_conversation_get_name_wrapped(conv), message_, w, xhtml_, timestamp); } } diff --git a/backends/libpurple/utils.cpp b/backends/libpurple/utils.cpp index 9ae0d37a..c375421e 100644 --- a/backends/libpurple/utils.cpp +++ b/backends/libpurple/utils.cpp @@ -36,14 +36,14 @@ #ifndef WIN32 #include "sys/wait.h" #include "sys/signal.h" -#include -#include -#include -#include -#include -#include -#include "sys/socket.h" +#include +#include +#include #include +#include +#include +#include +#include #include #include #else @@ -137,30 +137,32 @@ void spectrum_sigchld_handler(int sig) #endif int create_socket(const char *host, int portno) { - struct sockaddr_in serv_addr; - - int main_socket = 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); + struct sockaddr_in stSockAddr; + int SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); - hostent *hos; // Resolve name + if (-1 == SocketFD) { + return 0; + } + + hostent *hos; 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(main_socket, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { - close(main_socket); - main_socket = 0; + return 0; } -// int flags = fcntl(main_socket, F_GETFL); -// flags |= O_NONBLOCK; -// fcntl(main_socket, F_SETFL, flags); - return main_socket; + memset(&stSockAddr, 0, sizeof(stSockAddr)); + + stSockAddr.sin_family = AF_INET; + stSockAddr.sin_port = htons(portno); + memcpy(&(stSockAddr.sin_addr.s_addr), hos->h_addr, hos->h_length); + + if (-1 == connect(SocketFD, (struct sockaddr *)&stSockAddr, sizeof(stSockAddr))) { + close(SocketFD); + return 0; + } + + return SocketFD; } #ifdef _WIN32 diff --git a/backends/skype/main.cpp b/backends/skype/main.cpp index 99d77bc2..7cb7c59f 100644 --- a/backends/skype/main.cpp +++ b/backends/skype/main.cpp @@ -19,7 +19,9 @@ #include "sys/wait.h" #include "sys/signal.h" // #include "valgrind/memcheck.h" +#ifndef __FreeBSD__ #include "malloc.h" +#endif #include diff --git a/backends/swiften/main.cpp b/backends/swiften/main.cpp index d9199aeb..6db0d86c 100644 --- a/backends/swiften/main.cpp +++ b/backends/swiften/main.cpp @@ -14,8 +14,10 @@ #include "sys/signal.h" #endif +#ifndef __FreeBSD__ // malloc_trim #include "malloc.h" +#endif // Boost #include @@ -61,14 +63,15 @@ class SwiftenPlugin : public NetworkPlugin { void handleSwiftDisconnected(const std::string &user, const boost::optional &error) { std::string message = ""; + bool reconnect = false; if (error) { switch(error->getType()) { - case Swift::ClientError::UnknownError: message = ("Unknown Error"); break; + case Swift::ClientError::UnknownError: message = ("Unknown Error"); reconnect = true; break; case Swift::ClientError::DomainNameResolveError: message = ("Unable to find server"); break; case Swift::ClientError::ConnectionError: message = ("Error connecting to server"); break; - case Swift::ClientError::ConnectionReadError: message = ("Error while receiving server data"); break; - case Swift::ClientError::ConnectionWriteError: message = ("Error while sending data to the server"); break; - case Swift::ClientError::XMLError: message = ("Error parsing server data"); break; + case Swift::ClientError::ConnectionReadError: message = ("Error while receiving server data"); reconnect = true; break; + case Swift::ClientError::ConnectionWriteError: message = ("Error while sending data to the server"); reconnect = true; break; + case Swift::ClientError::XMLError: message = ("Error parsing server data"); reconnect = true; break; case Swift::ClientError::AuthenticationFailedError: message = ("Login/password invalid"); break; case Swift::ClientError::CompressionFailedError: message = ("Error while compressing stream"); break; case Swift::ClientError::ServerVerificationFailedError: message = ("Server verification failed"); break; @@ -95,7 +98,7 @@ class SwiftenPlugin : public NetworkPlugin { } } LOG4CXX_INFO(logger, user << ": Disconnected " << message); - handleDisconnected(user, 3, message); + handleDisconnected(user, reconnect ? 0 : 3, message); boost::shared_ptr client = m_users[user]; if (client) { @@ -106,8 +109,10 @@ class SwiftenPlugin : public NetworkPlugin { } #ifndef WIN32 +#ifndef __FreeBSD__ // force returning of memory chunks allocated by libxml2 to kernel malloc_trim(0); +#endif #endif } diff --git a/backends/twitter/TwitterPlugin.cpp b/backends/twitter/TwitterPlugin.cpp index e6a0d52e..ef84bbf3 100644 --- a/backends/twitter/TwitterPlugin.cpp +++ b/backends/twitter/TwitterPlugin.cpp @@ -322,7 +322,7 @@ void TwitterPlugin::pollForTweets() std::set::iterator it = onlineUsers.begin(); while(it != onlineUsers.end()) { std::string user = *it; - tp->runAsThread(new TimelineRequest(userdb[user].sessions, user, "", userdb[user].mostRecentTweetID, + tp->runAsThread(new TimelineRequest(userdb[user].sessions, user, "", getMostRecentTweetIDUnsafe(user), boost::bind(&TwitterPlugin::displayTweets, this, _1, _2, _3, _4))); it++; } @@ -517,14 +517,39 @@ void TwitterPlugin::updateLastTweetID(const std::string user, const std::string { boost::mutex::scoped_lock lock(userlock); userdb[user].mostRecentTweetID = ID; + + UserInfo info; + if(storagebackend->getUser(user, info) == false) { + LOG4CXX_ERROR(logger, "Didn't find entry for " << user << " in the database!") + return; + } + + storagebackend->updateUserSetting((long)info.id, "twitter_last_tweet", ID); +} + +std::string TwitterPlugin::getMostRecentTweetIDUnsafe(const std::string user) +{ + std::string ID = ""; + if(onlineUsers.count(user)) { + ID = userdb[user].mostRecentTweetID; + if (ID.empty()) { + int type; + UserInfo info; + if(storagebackend->getUser(user, info) == false) { + LOG4CXX_ERROR(logger, "Didn't find entry for " << user << " in the database!") + } + else { + storagebackend->getUserSetting(info.id, "twitter_last_tweet", type, ID); + } + } + } + return ID; } std::string TwitterPlugin::getMostRecentTweetID(const std::string user) -{ - boost::mutex::scoped_lock lock(userlock); - std::string ID = "-1"; - if(onlineUsers.count(user)) ID = userdb[user].mostRecentTweetID; - return ID; +{ + boost::mutex::scoped_lock lock(userlock); + return getMostRecentTweetIDUnsafe(user); } void TwitterPlugin::updateLastDMID(const std::string user, const std::string ID) @@ -622,16 +647,17 @@ void TwitterPlugin::displayTweets(std::string &user, std::string &userRequested, std::map lastTweet; std::map::iterator it; - for(int i=0 ; i= 0 ; i--) { if(userdb[user].twitterMode != CHATROOM) { - timeline += " - " + tweets[i].getUserData().getScreenName() + ": " + tweets[i].getTweet() + " (MsgId: " + tweets[i].getID() + ")\n"; + std::string m = " - " + tweets[i].getUserData().getScreenName() + ": " + tweets[i].getTweet() + " (MsgId: " + tweets[i].getID() + ")\n"; + handleMessage(user, adminLegacyName, m, "", "", tweets[i].getCreationTime()); std::string scrname = tweets[i].getUserData().getScreenName(); if(lastTweet.count(scrname) == 0 || cmp(tweets[lastTweet[scrname]].getID(), tweets[i].getID()) <= 0) lastTweet[scrname] = i; } else { handleMessage(user, userdb[user].twitterMode == CHATROOM ? adminChatRoom : adminLegacyName, - tweets[i].getTweet() + " (MsgId: " + tweets[i].getID() + ")", tweets[i].getUserData().getScreenName()); + tweets[i].getTweet() + " (MsgId: " + tweets[i].getID() + ")", tweets[i].getUserData().getScreenName(), "", tweets[i].getCreationTime()); } } diff --git a/backends/twitter/TwitterPlugin.h b/backends/twitter/TwitterPlugin.h index b49ea866..ab4aca3b 100644 --- a/backends/twitter/TwitterPlugin.h +++ b/backends/twitter/TwitterPlugin.h @@ -134,6 +134,8 @@ class TwitterPlugin : public NetworkPlugin { /***********************************************************************************/ private: + std::string getMostRecentTweetIDUnsafe(const std::string user); + enum status {NEW, WAITING_FOR_PIN, CONNECTED, DISCONNECTED}; enum mode {SINGLECONTACT, MULTIPLECONTACT, CHATROOM}; diff --git a/backends/twitter/TwitterResponseParser.cpp b/backends/twitter/TwitterResponseParser.cpp index 4ccc5e85..b908b770 100644 --- a/backends/twitter/TwitterResponseParser.cpp +++ b/backends/twitter/TwitterResponseParser.cpp @@ -1,5 +1,6 @@ #include "TwitterResponseParser.h" #include "transport/logging.h" +#include "boost/algorithm/string.hpp" #include DEFINE_LOGGER(logger, "TwitterResponseParser") @@ -11,6 +12,25 @@ static std::string tolowercase(std::string inp) return out; } +static std::string unescape(std::string data) { + using boost::algorithm::replace_all; + replace_all(data, "&", "&"); + replace_all(data, """, "\""); + replace_all(data, "'", "\'"); + replace_all(data, "<", "<"); + replace_all(data, ">", ">"); + return data; +} + +static std::string toIsoTime(std::string in) { + time_t now = time(0); + struct tm *mtime = gmtime(&now); + strptime(in.c_str(), "%a %b %d %H:%M:%S %z %Y", mtime); + char buf[80]; + strftime(buf, sizeof(buf), "%Y%m%dT%H%M%S", mtime); + return buf; +} + EmbeddedStatus getEmbeddedStatus(const Swift::ParserElement::ref &element, const std::string xmlns) { EmbeddedStatus status; @@ -19,9 +39,10 @@ EmbeddedStatus getEmbeddedStatus(const Swift::ParserElement::ref &element, const return status; } - status.setCreationTime( std::string( element->getChild(TwitterReponseTypes::created_at, xmlns)->getText() ) ); + status.setCreationTime( toIsoTime(std::string( element->getChild(TwitterReponseTypes::created_at, xmlns)->getText() ) ) ); + status.setID( std::string( element->getChild(TwitterReponseTypes::id, xmlns)->getText() ) ); - status.setTweet( std::string( element->getChild(TwitterReponseTypes::text, xmlns)->getText() ) ); + status.setTweet( unescape (std::string( element->getChild(TwitterReponseTypes::text, xmlns)->getText() ) ) ); status.setTruncated( std::string( element->getChild(TwitterReponseTypes::truncated, xmlns)->getText() )=="true" ); status.setReplyToStatusID( std::string( element->getChild(TwitterReponseTypes::in_reply_to_status_id, xmlns)->getText() ) ); status.setReplyToUserID( std::string( element->getChild(TwitterReponseTypes::in_reply_to_user_id, xmlns)->getText() ) ); @@ -60,9 +81,9 @@ Status getStatus(const Swift::ParserElement::ref &element, const std::string xml return status; } - status.setCreationTime( std::string( element->getChild(TwitterReponseTypes::created_at, xmlns)->getText() ) ); + status.setCreationTime( toIsoTime ( std::string( element->getChild(TwitterReponseTypes::created_at, xmlns)->getText() ) ) ); status.setID( std::string( element->getChild(TwitterReponseTypes::id, xmlns)->getText() ) ); - status.setTweet( std::string( element->getChild(TwitterReponseTypes::text, xmlns)->getText() ) ); + status.setTweet( unescape ( std::string( element->getChild(TwitterReponseTypes::text, xmlns)->getText() ) ) ); status.setTruncated( std::string( element->getChild(TwitterReponseTypes::truncated, xmlns)->getText() )=="true" ); status.setReplyToStatusID( std::string( element->getChild(TwitterReponseTypes::in_reply_to_status_id, xmlns)->getText() ) ); status.setReplyToUserID( std::string( element->getChild(TwitterReponseTypes::in_reply_to_user_id, xmlns)->getText() ) ); @@ -82,9 +103,9 @@ DirectMessage getDirectMessage(const Swift::ParserElement::ref &element, const s return DM; } - DM.setCreationTime( std::string( element->getChild(TwitterReponseTypes::created_at, xmlns)->getText() ) ); + DM.setCreationTime( toIsoTime ( std::string( element->getChild(TwitterReponseTypes::created_at, xmlns)->getText() ) ) ); DM.setID( std::string( element->getChild(TwitterReponseTypes::id, xmlns)->getText() ) ); - DM.setMessage( std::string( element->getChild(TwitterReponseTypes::text, xmlns)->getText() ) ); + DM.setMessage( unescape ( std::string( element->getChild(TwitterReponseTypes::text, xmlns)->getText() ) ) ); DM.setSenderID( std::string( element->getChild(TwitterReponseTypes::sender_id, xmlns)->getText() ) ); DM.setRecipientID( std::string( element->getChild(TwitterReponseTypes::recipient_id, xmlns)->getText() ) ); DM.setSenderScreenName( std::string( element->getChild(TwitterReponseTypes::sender_screen_name, xmlns)->getText() ) ); diff --git a/backends/twitter/main.cpp b/backends/twitter/main.cpp index b3404d82..bb28d601 100644 --- a/backends/twitter/main.cpp +++ b/backends/twitter/main.cpp @@ -27,61 +27,21 @@ int main (int argc, char* argv[]) { return -1; } - std::string configFile; - boost::program_options::variables_map vm; - boost::program_options::options_description desc("Usage: spectrum \nAllowed options"); - desc.add_options() - ("help", "help") - ("host,h", boost::program_options::value(&host)->default_value(""), "Host to connect to") - ("port,p", boost::program_options::value(&port)->default_value(10000), "Port to connect to") - ("config", boost::program_options::value(&configFile)->default_value(""), "Config file") - ; - - try - { - boost::program_options::positional_options_description p; - p.add("config", -1); - boost::program_options::store(boost::program_options::command_line_parser(argc, argv). - options(desc).positional(p).allow_unregistered().run(), vm); - boost::program_options::notify(vm); - - if(vm.count("help")) - { - std::cout << desc << "\n"; - return 1; - } - - if(vm.count("config") == 0) { - std::cout << desc << "\n"; - return 1; - } - } - catch (std::runtime_error& e) - { - std::cout << desc << "\n"; - return 1; - } - catch (...) - { - std::cout << desc << "\n"; - return 1; - } - - Config config(argc, argv); - if (!config.load(configFile)) { - std::cerr << "Can't open " << argv[1] << " configuration file.\n"; - return 1; - } - - Logging::initBackendLogging(&config); - std::string error; + Config *cfg = Config::createFromArgs(argc, argv, error, host, port); + if (cfg == NULL) { + std::cerr << error; + return 1; + } + + Logging::initBackendLogging(cfg); + StorageBackend *storagebackend; - - storagebackend = StorageBackend::createBackend(&config, error); + storagebackend = StorageBackend::createBackend(cfg, error); if (storagebackend == NULL) { - LOG4CXX_ERROR(logger, "Error creating StorageBackend! " << error) - return -2; + LOG4CXX_ERROR(logger, "Error creating StorageBackend! " << error); + LOG4CXX_ERROR(logger, "Twitter backend needs storage backend configured to work! " << error); + return NetworkPlugin::StorageBackendNeeded; } else if (!storagebackend->connect()) { @@ -91,7 +51,7 @@ int main (int argc, char* argv[]) { Swift::SimpleEventLoop eventLoop; loop_ = &eventLoop; - np = new TwitterPlugin(&config, &eventLoop, storagebackend, host, port); + np = new TwitterPlugin(cfg, &eventLoop, storagebackend, host, port); loop_->run(); return 0; diff --git a/docs/guide/debian_ubuntu.textile b/docs/guide/debian_ubuntu.textile index eff14e2c..d034dec0 100644 --- a/docs/guide/debian_ubuntu.textile +++ b/docs/guide/debian_ubuntu.textile @@ -33,7 +33,7 @@ h2. Install spectrum2 - development version After you have done that, simply do:
-apt-get install spectrum2-git spectrum2-backend-libpurple-git
+apt-get install spectrum2 spectrum2-backend-libpurple
 
Note that these repositories pull in quite a few dependencies, depending on the distribution you use. diff --git a/docs/guide/libcommuni.textile b/docs/guide/libcommuni.textile index 4a0b6b12..395aca5f 100644 --- a/docs/guide/libcommuni.textile +++ b/docs/guide/libcommuni.textile @@ -32,7 +32,7 @@ irc_server=irc.freenode.org h3. 2.2 One transport for more IRC networks -In this mode users can connect more IRC networks, but they can't connect the network without being in the room. Currently this mode is not finished yet in Spectrum 2. +In this mode users can connect more IRC networks, but they can't connect the network without being in the room. To connect the network, user has to join the room in following format: #room%irc.freenode.org@irc.domain.tld. The nickname used in the first join request is used as a nickname for the IRC connection. h2. 3. All configuration variables diff --git a/docs/guide/logging.textile b/docs/guide/logging.textile index fa1c9640..49052811 100644 --- a/docs/guide/logging.textile +++ b/docs/guide/logging.textile @@ -95,7 +95,7 @@ log4j.additivity.Component.XML=false # Create new RollingFileAppender logger and set the file name log4j.appender.XML=org.apache.log4j.RollingFileAppender -log4j.appender.XML.File=/var/log/spectrum2/${jid}/spectrum2.log +log4j.appender.XML.File=/var/log/spectrum2/${jid}/spectrum2_xml.log # Set MaxFileSize. Log will be rotated automatically when this limit is reached log4j.appender.XML.MaxFileSize=100000KB @@ -107,6 +107,25 @@ log4j.appender.XML.layout=org.apache.log4j.PatternLayout log4j.appender.XML.layout.ConversionPattern=%d %-5p %c: %m%n +h3. Disable XML logging + +
+# We create two rootLoggers:
+#   - "debug" is internal logger used by log4cxx
+#   - "stdout" is name of our ConsoleAppender logger
+log4j.rootLogger=debug, stdout
+
+# Create new ConsoleAppender logger with custom PatternLayout
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+
+# Define the output pattern. Characters are mentioned here: http://logging.apache.org/log4cxx/apidocs/classlog4cxx_1_1_pattern_layout.html
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d %-5p %c: %m%n
+
+# Disable XML category
+log4j.category.Component.XML = OFF
+
+ h3. Disable logging To disable logging, you still *must have* one logger created (probably the ConsoleAppender), but you can set log4j.threshold = OFF to not log everything later: diff --git a/include/transport/conversation.h b/include/transport/conversation.h index e137b566..ff109aa2 100644 --- a/include/transport/conversation.h +++ b/include/transport/conversation.h @@ -148,6 +148,8 @@ class Conversation { Swift::JID m_jid; std::list m_jids; std::map m_participants; + boost::shared_ptr m_subject; + bool m_sentInitialPresence; }; } diff --git a/include/transport/conversationmanager.h b/include/transport/conversationmanager.h index 016914c8..865736b2 100644 --- a/include/transport/conversationmanager.h +++ b/include/transport/conversationmanager.h @@ -69,6 +69,8 @@ class ConversationManager { /// \param conv Conversation. void removeConversation(Conversation *conv); + void deleteAllConversations(); + void resetResources(); void removeJID(const Swift::JID &jid); diff --git a/include/transport/discoitemsresponder.h b/include/transport/discoitemsresponder.h index 44bdc364..9b17e832 100644 --- a/include/transport/discoitemsresponder.h +++ b/include/transport/discoitemsresponder.h @@ -28,21 +28,30 @@ namespace Transport { class Component; +class DiscoInfoResponder; class DiscoItemsResponder : public Swift::GetResponder { public: DiscoItemsResponder(Component *component); ~DiscoItemsResponder(); + Swift::CapsInfo &getBuddyCapsInfo(); + void addAdHocCommand(const std::string &node, const std::string &name); // void removeAdHocCommand(const std::string &node); + void addRoom(const std::string &node, const std::string &name); + void clearRooms(); + private: virtual bool handleGetRequest(const Swift::JID& from, const Swift::JID& to, const std::string& id, boost::shared_ptr payload); + private: Component *m_component; boost::shared_ptr m_commands; + boost::shared_ptr m_rooms; + DiscoInfoResponder *m_discoInfoResponder; }; } \ No newline at end of file diff --git a/include/transport/factory.h b/include/transport/factory.h index 85b7f37a..0c4595d7 100644 --- a/include/transport/factory.h +++ b/include/transport/factory.h @@ -40,7 +40,7 @@ class RosterManager; class Factory { public: - virtual Conversation *createConversation(ConversationManager *conversationManager, const std::string &legacyName) = 0; + virtual Conversation *createConversation(ConversationManager *conversationManager, const std::string &legacyName, bool isMuc = false) = 0; virtual Buddy *createBuddy(RosterManager *rosterManager, const BuddyInfo &buddyInfo) = 0; }; diff --git a/include/transport/networkplugin.h b/include/transport/networkplugin.h index ea594225..a5e123f7 100644 --- a/include/transport/networkplugin.h +++ b/include/transport/networkplugin.h @@ -24,6 +24,7 @@ #include "transport/protocol.pb.h" // #include "conversation.h" #include +#include namespace Transport { @@ -34,6 +35,7 @@ namespace Transport { /// development. class NetworkPlugin { public: + enum ExitCode { StorageBackendNeeded = -2 }; class PluginConfig { public: @@ -109,7 +111,7 @@ class NetworkPlugin { /// \param message Plain text message. /// \param nickname Nickname of buddy in room. Empty if it's normal chat message. /// \param xhtml XHTML message. - void handleMessage(const std::string &user, const std::string &legacyName, const std::string &message, const std::string &nickname = "", const std::string &xhtml = ""); + void handleMessage(const std::string &user, const std::string &legacyName, const std::string &message, const std::string &nickname = "", const std::string &xhtml = "", const std::string ×tamp = ""); /// Call this function when subject in room changed. /// \param user XMPP JID of user for which this event occurs. You can get it from NetworkPlugin::handleLoginRequest(). (eg. "user%gmail.com@xmpp.domain.tld") @@ -164,6 +166,8 @@ class NetworkPlugin { void handleFTData(unsigned long ftID, const std::string &data); + void handleRoomList(const std::string &user, const std::list &rooms, const std::list &names); + /// Called when XMPP user wants to connect legacy network. /// You should connect him to legacy network and call handleConnected or handleDisconnected function later. /// \param user XMPP JID of user for which this event occurs. diff --git a/include/transport/networkpluginserver.h b/include/transport/networkpluginserver.h index 62fc5b08..b0d742b8 100644 --- a/include/transport/networkpluginserver.h +++ b/include/transport/networkpluginserver.h @@ -43,6 +43,7 @@ class RosterResponder; class BlockResponder; class DummyReadBytestream; class AdminInterface; +class DiscoItemsResponder; class NetworkPluginServer { public: @@ -60,10 +61,12 @@ class NetworkPluginServer { std::string id; }; - NetworkPluginServer(Component *component, Config *config, UserManager *userManager, FileTransferManager *ftManager); + NetworkPluginServer(Component *component, Config *config, UserManager *userManager, FileTransferManager *ftManager, DiscoItemsResponder *discoItemsResponder); virtual ~NetworkPluginServer(); + void start(); + void setAdminInterface(AdminInterface *adminInterface) { m_adminInterface = adminInterface; } @@ -86,7 +89,7 @@ class NetworkPluginServer { void handleMessageReceived(NetworkConversation *conv, boost::shared_ptr &message); - private: + public: void handleNewClientConnection(boost::shared_ptr c); void handleSessionFinished(Backend *c); void handlePongReceived(Backend *c); @@ -109,6 +112,7 @@ class NetworkPluginServer { void handleFTDataPayload(Backend *b, const std::string &payload); void handleQueryPayload(Backend *b, const std::string &payload); void handleBackendConfigPayload(const std::string &payload); + void handleRoomListPayload(const std::string &payload); void handleUserCreated(User *user); void handleRoomJoined(User *user, const Swift::JID &who, const std::string &room, const std::string &nickname, const std::string &password); @@ -131,6 +135,7 @@ class NetworkPluginServer { void handleFTRejected(User *user, const std::string &buddyName, const std::string &fileName, unsigned long size); void handleFTDataNeeded(Backend *b, unsigned long ftid); + private: void send(boost::shared_ptr &, const std::string &data); void pingTimeout(); @@ -154,6 +159,7 @@ class NetworkPluginServer { std::vector m_crashedBackends; AdminInterface *m_adminInterface; bool m_startingBackend; + DiscoItemsResponder *m_discoItemsResponder; }; } diff --git a/include/transport/protocol.proto b/include/transport/protocol.proto index 929c02b7..4475b707 100644 --- a/include/transport/protocol.proto +++ b/include/transport/protocol.proto @@ -69,6 +69,7 @@ message ConversationMessage { required string message = 3; optional string nickname = 4; optional string xhtml = 5; + optional string timestamp = 6; } message Room { @@ -78,6 +79,11 @@ message Room { optional string password = 4; } +message RoomList { + repeated string room = 1; + repeated string name = 2; +} + message Participant { required string userName = 1; required string room = 2; @@ -159,6 +165,7 @@ message WrapperMessage { TYPE_EXIT = 29; TYPE_BACKEND_CONFIG = 30; TYPE_QUERY = 31; + TYPE_ROOM_LIST = 32; } required Type type = 1; optional bytes payload = 2; diff --git a/include/transport/storagebackend.h b/include/transport/storagebackend.h index 52890104..a5d101be 100644 --- a/include/transport/storagebackend.h +++ b/include/transport/storagebackend.h @@ -86,6 +86,14 @@ class Config; class StorageBackend { public: + static std::string encryptPassword(const std::string &password, const std::string &key); + + static std::string decryptPassword(std::string &encrypted, const std::string &key); + + static std::string serializeGroups(const std::vector &groups); + + static std::vector deserializeGroups(std::string &groups); + /// Virtual desctructor. virtual ~StorageBackend() {} diff --git a/include/transport/transport.h b/include/transport/transport.h index e6f0610d..c69ca396 100644 --- a/include/transport/transport.h +++ b/include/transport/transport.h @@ -47,7 +47,6 @@ namespace Transport { // } SpectrumImportantFeatures; // class StorageBackend; - class DiscoInfoResponder; class Factory; class UserRegistry; @@ -81,8 +80,6 @@ namespace Transport { /// \return Swift::StanzaChannel associated with this Transport::Component. Swift::StanzaChannel *getStanzaChannel(); - Swift::CapsInfo &getBuddyCapsInfo(); - /// Returns Swift::IQRouter associated with this Component. /// \return Swift::IQRouter associated with this Component. @@ -104,18 +101,6 @@ namespace Transport { void start(); void stop(); - /// Sets disco#info features which are sent as answer to disco#info IQ-get. - - /// This sets features of transport contact (For example "j2j.domain.tld"). - /// \param features list of features as sent in disco#info response - void setTransportFeatures(std::list &features); - - /// Sets disco#info features which are sent as answer to disco#info IQ-get. - - /// This sets features of legacy network buddies (For example "me\40gmail.com@j2j.domain.tld"). - /// \param features list of features as sent in disco#info response - void setBuddyFeatures(std::list &features); - /// Returns Jabber ID of this transport. /// \return Jabber ID of this transport @@ -186,7 +171,6 @@ namespace Transport { Transport::UserRegistry *m_userRegistry; StorageBackend *m_storageBackend; - DiscoInfoResponder *m_discoInfoResponder; int m_reconnectCount; Config* m_config; std::string m_protocol; diff --git a/include/transport/user.h b/include/transport/user.h index 11c253b0..1719b13d 100644 --- a/include/transport/user.h +++ b/include/transport/user.h @@ -71,12 +71,14 @@ class User : public Swift::EntityCapsProvider { Component *getComponent() { return m_component; } + UserManager *getUserManager() { return m_userManager; } + void setData(void *data) { m_data = data; } void *getData() { return m_data; } /// Handles presence from XMPP JID associated with this user. /// \param presence Swift::Presence. - void handlePresence(Swift::Presence::ref presence); + void handlePresence(Swift::Presence::ref presence, bool forceJoin = false); void handleSubscription(Swift::Presence::ref presence); @@ -142,6 +144,7 @@ class User : public Swift::EntityCapsProvider { std::vector > m_filetransfers; int m_resources; int m_reconnectCounter; + std::list m_joinedRooms; }; } diff --git a/include/transport/usermanager.h b/include/transport/usermanager.h index 2601be21..38a5c7bc 100644 --- a/include/transport/usermanager.h +++ b/include/transport/usermanager.h @@ -32,6 +32,7 @@ class Component; class StorageBackend; class StorageResponder; class RosterResponder; +class DiscoItemsResponder; /// Manages online XMPP Users. @@ -55,7 +56,7 @@ class UserManager : public Swift::EntityCapsProvider { /// Creates new UserManager. /// \param component Component which's presence will be handled /// \param storageBackend Storage backend used to fetch UserInfos - UserManager(Component *component, UserRegistry *userRegistry, StorageBackend *storageBackend = NULL); + UserManager(Component *component, UserRegistry *userRegistry, DiscoItemsResponder *discoItemsResponder, StorageBackend *storageBackend = NULL); /// Destroys UserManager. ~UserManager(); @@ -84,6 +85,8 @@ class UserManager : public Swift::EntityCapsProvider { Swift::DiscoInfo::ref getCaps(const Swift::JID&) const; + DiscoItemsResponder *getDiscoResponder() { return m_discoItemsResponder; } + /// Called when new User class is created. /// \param user newly created User class boost::signal onUserCreated; @@ -128,6 +131,7 @@ class UserManager : public Swift::EntityCapsProvider { void handleMessageReceived(Swift::Message::ref message); void handleGeneralPresenceReceived(Swift::Presence::ref presence); void handleProbePresence(Swift::Presence::ref presence); + void handleErrorPresence(Swift::Presence::ref presence); void handleSubscription(Swift::Presence::ref presence); void handleRemoveTimeout(const std::string jid, User *user, bool reconnect); void handleDiscoInfo(const Swift::JID& jid, boost::shared_ptr info); @@ -143,6 +147,7 @@ class UserManager : public Swift::EntityCapsProvider { Swift::Timer::ref m_removeTimer; unsigned long m_sentToXMPP; unsigned long m_sentToBackend; + DiscoItemsResponder *m_discoItemsResponder; friend class RosterResponder; }; diff --git a/include/transport/util.h b/include/transport/util.h index 3301ad90..490724bf 100644 --- a/include/transport/util.h +++ b/include/transport/util.h @@ -26,20 +26,17 @@ #include #include "Swiften/StringCodecs/Base64.h" +#include +#include "transport/config.h" + namespace Transport { namespace Util { +void createDirectories(Transport::Config *config, const boost::filesystem::path& ph); + void removeEverythingOlderThan(const std::vector &dirs, time_t t); -std::string encryptPassword(const std::string &password, const std::string &key); - -std::string decryptPassword(std::string &encrypted, const std::string &key); - -std::string serializeGroups(const std::vector &groups); - -std::vector deserializeGroups(std::string &groups); - int getRandomPort(const std::string &s); #ifdef _WIN32 diff --git a/plugin/cpp/CMakeLists.txt b/plugin/cpp/CMakeLists.txt index d80b8f59..75ea2aa1 100644 --- a/plugin/cpp/CMakeLists.txt +++ b/plugin/cpp/CMakeLists.txt @@ -5,6 +5,7 @@ FILE(GLOB HEADERS ../include/transport/*.h) set(EXTRA_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/../../src/memoryusage.cpp) set(EXTRA_SOURCES ${EXTRA_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../../src/logging.cpp) set(EXTRA_SOURCES ${EXTRA_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../../src/config.cpp) +set(EXTRA_SOURCES ${EXTRA_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../../src/util.cpp) set(EXTRA_SOURCES ${EXTRA_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../../include/transport/protocol.pb.cc) if (NOT WIN32) diff --git a/plugin/cpp/networkplugin.cpp b/plugin/cpp/networkplugin.cpp index eba6dcff..3dde9c53 100644 --- a/plugin/cpp/networkplugin.cpp +++ b/plugin/cpp/networkplugin.cpp @@ -63,7 +63,7 @@ NetworkPlugin::~NetworkPlugin() { } void NetworkPlugin::sendConfig(const PluginConfig &cfg) { - std::string data = "[registration]"; + std::string data = "[registration]\n"; data += std::string("needPassword=") + (cfg.m_needPassword ? "1" : "0") + "\n"; data += std::string("needRegistration=") + (cfg.m_needRegistration ? "1" : "0") + "\n"; @@ -82,13 +82,14 @@ void NetworkPlugin::sendConfig(const PluginConfig &cfg) { send(message); } -void NetworkPlugin::handleMessage(const std::string &user, const std::string &legacyName, const std::string &msg, const std::string &nickname, const std::string &xhtml) { +void NetworkPlugin::handleMessage(const std::string &user, const std::string &legacyName, const std::string &msg, const std::string &nickname, const std::string &xhtml, const std::string ×tamp) { pbnetwork::ConversationMessage m; m.set_username(user); m.set_buddyname(legacyName); m.set_message(msg); m.set_nickname(nickname); m.set_xhtml(xhtml); + m.set_timestamp(timestamp); std::string message; m.SerializeToString(&message); @@ -336,6 +337,24 @@ void NetworkPlugin::handleFTData(unsigned long ftID, const std::string &data) { send(message); } +void NetworkPlugin::handleRoomList(const std::string &user, const std::list &rooms, const std::list &names) { + pbnetwork::RoomList d; + for (std::list::const_iterator it = rooms.begin(); it != rooms.end(); it++) { + d.add_room(*it); + } + + for (std::list::const_iterator it = names.begin(); it != names.end(); it++) { + d.add_name(*it); + } + + std::string message; + d.SerializeToString(&message); + + WRAP(message, pbnetwork::WrapperMessage_Type_TYPE_ROOM_LIST); + + send(message); +} + void NetworkPlugin::handleLoginPayload(const std::string &data) { pbnetwork::Login payload; if (payload.ParseFromString(data) == false) { diff --git a/spectrum/src/CMakeLists.txt b/spectrum/src/CMakeLists.txt index 2dd5b0da..fb38f5f0 100644 --- a/spectrum/src/CMakeLists.txt +++ b/spectrum/src/CMakeLists.txt @@ -1,13 +1,13 @@ cmake_minimum_required(VERSION 2.6) FILE(GLOB SRC *.cpp) -# if (WIN32) -# FILE(GLOB WIN_SRC win32/*.cpp) -# include_directories(win32) -# ADD_EXECUTABLE(spectrum2 ${SRC} ${WIN_SRC}) -# else() +if (WIN32) +FILE(GLOB WIN_SRC win32/*.cpp) +include_directories(win32) +ADD_EXECUTABLE(spectrum2 ${SRC} ${WIN_SRC}) +else() ADD_EXECUTABLE(spectrum2 ${SRC}) -# endif() +endif() diff --git a/spectrum/src/main.cpp b/spectrum/src/main.cpp index 75169b1a..21f6a1c4 100644 --- a/spectrum/src/main.cpp +++ b/spectrum/src/main.cpp @@ -22,15 +22,18 @@ #include #ifndef WIN32 #include "sys/signal.h" +#include "sys/stat.h" #include #include #include #include "libgen.h" +#ifndef __FreeBSD__ #include +#endif #else #include #define getpid _getpid -// #include "win32/SpectrumService.h" +#include "win32/ServiceWrapper.h" #endif #include @@ -42,6 +45,13 @@ DEFINE_LOGGER(logger, "Spectrum"); Swift::SimpleEventLoop *eventLoop_ = NULL; Component *component_ = NULL; UserManager *userManager_ = NULL; +Config *config_ = NULL; + +void stop() { + userManager_->removeAllUsers(false); + component_->stop(); + eventLoop_->stop(); +} static void stop_spectrum() { userManager_->removeAllUsers(false); @@ -119,20 +129,157 @@ static void daemonize(const char *cwd, const char *lock_file) { #endif +int mainloop() { + +#ifndef WIN32 + mode_t old_cmask = umask(0007); +#endif + + Logging::initMainLogging(config_); + +#ifndef WIN32 + if (!CONFIG_STRING(config_, "service.group").empty() ||!CONFIG_STRING(config_, "service.user").empty() ) { + struct rlimit limit; + getrlimit(RLIMIT_CORE, &limit); + + if (!CONFIG_STRING(config_, "service.group").empty()) { + struct group *gr; + if ((gr = getgrnam(CONFIG_STRING(config_, "service.group").c_str())) == NULL) { + std::cerr << "Invalid service.group name " << CONFIG_STRING(config_, "service.group") << "\n"; + return 1; + } + + if (((setgid(gr->gr_gid)) != 0) || (initgroups(CONFIG_STRING(config_, "service.user").c_str(), gr->gr_gid) != 0)) { + std::cerr << "Failed to set service.group name " << CONFIG_STRING(config_, "service.group") << " - " << gr->gr_gid << ":" << strerror(errno) << "\n"; + return 1; + } + } + + if (!CONFIG_STRING(config_, "service.user").empty()) { + struct passwd *pw; + if ((pw = getpwnam(CONFIG_STRING(config_, "service.user").c_str())) == NULL) { + std::cerr << "Invalid service.user name " << CONFIG_STRING(config_, "service.user") << "\n"; + return 1; + } + + if ((setuid(pw->pw_uid)) != 0) { + std::cerr << "Failed to set service.user name " << CONFIG_STRING(config_, "service.user") << " - " << pw->pw_uid << ":" << strerror(errno) << "\n"; + return 1; + } + } + setrlimit(RLIMIT_CORE, &limit); + } + + struct rlimit limit; + limit.rlim_max = RLIM_INFINITY; + limit.rlim_cur = RLIM_INFINITY; + setrlimit(RLIMIT_CORE, &limit); +#endif + + Swift::SimpleEventLoop eventLoop; + + Swift::BoostNetworkFactories *factories = new Swift::BoostNetworkFactories(&eventLoop); + UserRegistry userRegistry(config_, factories); + + Component transport(&eventLoop, factories, config_, NULL, &userRegistry); + component_ = &transport; +// Logger logger(&transport); + + std::string error; + StorageBackend *storageBackend = StorageBackend::createBackend(config_, error); + if (storageBackend == NULL) { + if (!error.empty()) { + std::cerr << error << "\n"; + return -2; + } + } + else if (!storageBackend->connect()) { + std::cerr << "Can't connect to database. Check the log to find out the reason.\n"; + return -1; + } + + Logging::redirect_stderr(); + + DiscoItemsResponder discoItemsResponder(&transport); + discoItemsResponder.start(); + + UserManager userManager(&transport, &userRegistry, &discoItemsResponder, storageBackend); + userManager_ = &userManager; + + UserRegistration *userRegistration = NULL; + UsersReconnecter *usersReconnecter = NULL; + if (storageBackend) { + userRegistration = new UserRegistration(&transport, &userManager, storageBackend); + userRegistration->start(); + + usersReconnecter = new UsersReconnecter(&transport, storageBackend); + } + + FileTransferManager ftManager(&transport, &userManager); + + NetworkPluginServer plugin(&transport, config_, &userManager, &ftManager, &discoItemsResponder); + plugin.start(); + + AdminInterface adminInterface(&transport, &userManager, &plugin, storageBackend, userRegistration); + plugin.setAdminInterface(&adminInterface); + + StatsResponder statsResponder(&transport, &userManager, &plugin, storageBackend); + statsResponder.start(); + + GatewayResponder gatewayResponder(transport.getIQRouter(), &userManager); + gatewayResponder.start(); + + AdHocManager adhocmanager(&transport, &discoItemsResponder, &userManager, storageBackend); + adhocmanager.start(); + + SettingsAdHocCommandFactory settings; + adhocmanager.addAdHocCommand(&settings); + + eventLoop_ = &eventLoop; + + eventLoop.run(); + +#ifndef WIN32 + umask(old_cmask); +#endif + + if (userRegistration) { + userRegistration->stop(); + delete userRegistration; + } + + if (usersReconnecter) { + delete usersReconnecter; + } + + delete storageBackend; + delete factories; + return 0; +} + + int main(int argc, char **argv) { Config config(argc, argv); - + config_ = &config; boost::program_options::variables_map vm; bool no_daemon = false; std::string config_file; std::string jid; - +#ifdef WIN32 + std::string install_service_name, uninstall_service_name, run_service_name; + // determine the name of the currently executing file + char szFilePath[MAX_PATH]; + GetModuleFileNameA(NULL, szFilePath, sizeof(szFilePath)); + std::string exe_file(szFilePath); +#endif setlocale(LC_ALL, ""); #ifndef WIN32 +#ifndef __FreeBSD__ mallopt(M_CHECK_ACTION, 2); mallopt(M_PERTURB, 0xb); #endif +#endif #ifndef WIN32 if (signal(SIGINT, spectrum_sigint_handler) == SIG_ERR) { @@ -155,9 +302,10 @@ int main(int argc, char **argv) ("version,v", "Shows Spectrum version") ; #ifdef WIN32 -// desc.add_options() -// ("install-service,i", "Install spectrum as Windows service") -// ("uninstall-service,u", "Uninstall Windows service"); + desc.add_options() + ("install-service,i", boost::program_options::value(&install_service_name)->default_value(""), "Install spectrum as Windows service") + ("uninstall-service,u", boost::program_options::value(&uninstall_service_name)->default_value(""), "Uninstall Windows service") + ("run-as-service,r", boost::program_options::value(&run_service_name)->default_value(""), "stub for Windows Service Manager"); #endif try { @@ -186,46 +334,41 @@ int main(int argc, char **argv) if(vm.count("no-daemonize")) { no_daemon = true; } -#ifdef WIN32 -#if 0 - if (vm.count("install-service")) { - SpectrumService ntservice; +#ifdef WIN32 + if (!install_service_name.empty()) { + // build command line for Service Manager + std::string service_path = exe_file + std::string(" --config ") + vm["config"].as() + + std::string(" --run-as-service ") + install_service_name; + + ServiceWrapper ntservice((char *)install_service_name.c_str()); if (!ntservice.IsInstalled()) { - // determine the name of the currently executing file - char szFilePath[MAX_PATH]; - GetModuleFileName(NULL, szFilePath, sizeof(szFilePath)); - std::string exe_file(szFilePath); - std::string config_file = exe_file.replace(exe_file.end() - 4, exe_file.end(), ".cfg"); - std::string service_path = std::string(szFilePath) + std::string(" --config ") + config_file; - - if (ntservice.Install(service_path.c_str())) { - std::cout << "Successfully installed" << std::endl; + if (ntservice.Install((char *)service_path.c_str())) { + std::cout << "Successfully installed " << install_service_name << std::endl; return 0; } else { std::cout << "Error installing service, are you an Administrator?" << std::endl; return 1; } } else { - std::cout << "Already installed" << std::endl; + std::cout << "Already installed " << install_service_name << std::endl; return 1; } } - if (vm.count("uninstall-service")) { - SpectrumService ntservice; + if (!uninstall_service_name.empty()) { + ServiceWrapper ntservice((char *)uninstall_service_name.c_str()); if (ntservice.IsInstalled()) { - if (ntservice.Remove()) { - std::cout << "Successfully removed" << std::endl; + if (ntservice.UnInstall()) { + std::cout << "Successfully removed " << uninstall_service_name << std::endl; return 0; } else { std::cout << "Error removing service, are you an Administrator?" << std::endl; return 1; } } else { - std::cout << "Service not installed" << std::endl; + std::cout << "Service not installed: " << uninstall_service_name << std::endl; return 1; } - } -#endif + } #endif } catch (std::runtime_error& e) @@ -246,7 +389,8 @@ int main(int argc, char **argv) // create directories try { - boost::filesystem::create_directories(CONFIG_STRING(&config, "service.working_dir")); + + Transport::Util::createDirectories(&config, CONFIG_STRING(&config, "service.working_dir")); } catch (...) { std::cerr << "Can't create service.working_dir directory " << CONFIG_STRING(&config, "service.working_dir") << ".\n"; @@ -280,20 +424,6 @@ int main(int argc, char **argv) #endif #ifndef WIN32 - if (!CONFIG_STRING(&config, "service.group").empty() ||!CONFIG_STRING(&config, "service.user").empty() ) { - struct group *gr; - if ((gr = getgrnam(CONFIG_STRING(&config, "service.group").c_str())) == NULL) { - std::cerr << "Invalid service.group name " << CONFIG_STRING(&config, "service.group") << "\n"; - return 1; - } - struct passwd *pw; - if ((pw = getpwnam(CONFIG_STRING(&config, "service.user").c_str())) == NULL) { - std::cerr << "Invalid service.user name " << CONFIG_STRING(&config, "service.user") << "\n"; - return 1; - } - chown(CONFIG_STRING(&config, "service.working_dir").c_str(), pw->pw_uid, gr->gr_gid); - } - char backendport[20]; FILE* port_file_f; port_file_f = fopen(CONFIG_STRING(&config, "service.portfile").c_str(), "w+"); @@ -314,120 +444,19 @@ int main(int argc, char **argv) // removeOldIcons(CONFIG_STRING(&config, "service.working_dir") + "/icons"); } #endif - - Logging::initMainLogging(&config); - -#ifndef WIN32 - if (!CONFIG_STRING(&config, "service.group").empty() ||!CONFIG_STRING(&config, "service.user").empty() ) { - struct rlimit limit; - getrlimit(RLIMIT_CORE, &limit); - - if (!CONFIG_STRING(&config, "service.group").empty()) { - struct group *gr; - if ((gr = getgrnam(CONFIG_STRING(&config, "service.group").c_str())) == NULL) { - std::cerr << "Invalid service.group name " << CONFIG_STRING(&config, "service.group") << "\n"; - return 1; - } - - if (((setgid(gr->gr_gid)) != 0) || (initgroups(CONFIG_STRING(&config, "service.user").c_str(), gr->gr_gid) != 0)) { - std::cerr << "Failed to set service.group name " << CONFIG_STRING(&config, "service.group") << " - " << gr->gr_gid << ":" << strerror(errno) << "\n"; - return 1; - } +#ifdef WIN32 + if (!run_service_name.empty()) { + ServiceWrapper ntservice((char *)run_service_name.c_str()); + if (ntservice.IsInstalled()) { + ntservice.RunService(); + } else { + std::cerr << "Service not installed: " << run_service_name << std::endl; + return 1; } - - if (!CONFIG_STRING(&config, "service.user").empty()) { - struct passwd *pw; - if ((pw = getpwnam(CONFIG_STRING(&config, "service.user").c_str())) == NULL) { - std::cerr << "Invalid service.user name " << CONFIG_STRING(&config, "service.user") << "\n"; - return 1; - } - - if ((setuid(pw->pw_uid)) != 0) { - std::cerr << "Failed to set service.user name " << CONFIG_STRING(&config, "service.user") << " - " << pw->pw_uid << ":" << strerror(errno) << "\n"; - return 1; - } - } - setrlimit(RLIMIT_CORE, &limit); + } else { + mainloop(); } - - struct rlimit limit; - limit.rlim_max = RLIM_INFINITY; - limit.rlim_cur = RLIM_INFINITY; - setrlimit(RLIMIT_CORE, &limit); +#else + mainloop(); #endif - - Swift::SimpleEventLoop eventLoop; - - Swift::BoostNetworkFactories *factories = new Swift::BoostNetworkFactories(&eventLoop); - UserRegistry userRegistry(&config, factories); - - Component transport(&eventLoop, factories, &config, NULL, &userRegistry); - component_ = &transport; -// Logger logger(&transport); - - std::string error; - StorageBackend *storageBackend = StorageBackend::createBackend(&config, error); - if (storageBackend == NULL) { - if (!error.empty()) { - std::cerr << error << "\n"; - return -2; - } - } - else if (!storageBackend->connect()) { - std::cerr << "Can't connect to database. Check the log to find out the reason.\n"; - return -1; - } - - Logging::redirect_stderr(); - - UserManager userManager(&transport, &userRegistry, storageBackend); - userManager_ = &userManager; - - UserRegistration *userRegistration = NULL; - UsersReconnecter *usersReconnecter = NULL; - if (storageBackend) { - userRegistration = new UserRegistration(&transport, &userManager, storageBackend); - userRegistration->start(); - - usersReconnecter = new UsersReconnecter(&transport, storageBackend); - } - - FileTransferManager ftManager(&transport, &userManager); - - NetworkPluginServer plugin(&transport, &config, &userManager, &ftManager); - - AdminInterface adminInterface(&transport, &userManager, &plugin, storageBackend, userRegistration); - plugin.setAdminInterface(&adminInterface); - - StatsResponder statsResponder(&transport, &userManager, &plugin, storageBackend); - statsResponder.start(); - - GatewayResponder gatewayResponder(transport.getIQRouter(), &userManager); - gatewayResponder.start(); - - DiscoItemsResponder discoItemsResponder(&transport); - discoItemsResponder.start(); - - AdHocManager adhocmanager(&transport, &discoItemsResponder, &userManager, storageBackend); - adhocmanager.start(); - - SettingsAdHocCommandFactory settings; - adhocmanager.addAdHocCommand(&settings); - - eventLoop_ = &eventLoop; - - eventLoop.run(); - - if (userRegistration) { - userRegistration->stop(); - delete userRegistration; - } - - if (usersReconnecter) { - delete usersReconnecter; - } - - delete storageBackend; - delete factories; - return 0; } diff --git a/spectrum/src/sample.cfg b/spectrum/src/sample.cfg index 389c4872..d795b60e 100644 --- a/spectrum/src/sample.cfg +++ b/spectrum/src/sample.cfg @@ -13,15 +13,16 @@ admin_password=test #cert=server.pfx #patch to PKCS#12 certificate #cert_password=test #password to that certificate if any users_per_backend=10 -backend=../..//backends/libpurple/spectrum2_libpurple_backend +backend=../..//backends/swiften/spectrum2_swiften_backend #backend=../../backends/twitter/spectrum2_twitter_backend -#backend=/home/hanzz/code/libtransport/backends/libpurple/spectrum2_communi_backend -protocol=prpl-icq +#backend=/home/hanzz/code/libtransport/backends/libcommuni/spectrum2_libcommuni_backend +protocol=prpl-jabber #protocol=prpl-msn #protocol=any #protocol=prpl-icq working_dir=./ portfile=$jid.port +irc_server=irc.freenode.org [backend] #default_avatar=catmelonhead.jpg diff --git a/spectrum/src/sample2.cfg b/spectrum/src/sample2.cfg index 3181a6a1..0da3b1ec 100644 --- a/spectrum/src/sample2.cfg +++ b/spectrum/src/sample2.cfg @@ -39,7 +39,7 @@ users_per_backend=10 # Full path to backend binary. backend=/usr/bin/spectrum2_libpurple_backend -#backend=/usr/bin/spectrum2_libircclient-qt_backend +#backend=/usr/bin/spectrum2_libcommuni_backend # For skype: #backend=/usr/bin/xvfb-run -n BACKEND_ID -s "-screen 0 10x10x8" -f /tmp/x-skype-gw /usr/bin/spectrum2_skype_backend diff --git a/spectrum/src/win32/ServiceWrapper.cpp b/spectrum/src/win32/ServiceWrapper.cpp new file mode 100644 index 00000000..adb988d2 --- /dev/null +++ b/spectrum/src/win32/ServiceWrapper.cpp @@ -0,0 +1,120 @@ +#include "ServiceWrapper.h" + + +LPSTR ServiceName; +SERVICE_STATUS ServiceStatus; +SERVICE_STATUS_HANDLE ServiceStatusHandle; + +bool doAction (int action, int desiredAccess, LPSTR path = NULL); + +enum actions { + DO_INSTALL, DO_DELETE, DO_CHECK +}; + + +ServiceWrapper::ServiceWrapper(LPSTR serviceName) +{ + ServiceName = serviceName; + ServiceStatusHandle = 0; +} + +ServiceWrapper::~ServiceWrapper(void) +{ +} + +bool ServiceWrapper::Install(LPSTR commandLine) { + return doAction(DO_INSTALL, SC_MANAGER_ALL_ACCESS, commandLine); +} + +bool ServiceWrapper::UnInstall() { + return doAction(DO_DELETE, SC_MANAGER_ALL_ACCESS); +} + +bool ServiceWrapper::IsInstalled() { + return doAction(DO_CHECK, SC_MANAGER_CONNECT); +} + +bool doAction(int action, int desiredAccess, LPSTR path) { + SC_HANDLE scm = OpenSCManager(NULL, NULL, desiredAccess); + SC_HANDLE service = NULL; + if (!scm) return FALSE; + + switch(action) { + case DO_INSTALL: + service = CreateServiceA( + scm, + ServiceName, + ServiceName, + SERVICE_ALL_ACCESS, + SERVICE_WIN32_OWN_PROCESS, + SERVICE_DEMAND_START, + SERVICE_ERROR_NORMAL, + path, + NULL, + NULL, + NULL, + NULL, + NULL + ); + return (service != NULL); + break; + case DO_DELETE: + service = OpenServiceA(scm, ServiceName, DELETE); + if (service == NULL) + return FALSE; + if (DeleteService(service)) + return TRUE; + break; + case DO_CHECK: + service = OpenServiceA(scm, ServiceName, SERVICE_QUERY_STATUS); + return (service != NULL); + default: + return FALSE; + } + CloseServiceHandle(service); + CloseServiceHandle(scm); + return FALSE; +} + +void WINAPI ServiceControlHandler(DWORD controlCode) { + switch (controlCode) { + case SERVICE_CONTROL_INTERROGATE: + break; + case SERVICE_CONTROL_STOP: + ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING; + break; + default: + break; + } + SetServiceStatus(ServiceStatusHandle, &ServiceStatus); + stop(); +} + +void WINAPI ServiceMain(DWORD argc, LPSTR *argv) { + ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + ServiceStatus.dwServiceSpecificExitCode = NO_ERROR; + + ServiceStatusHandle = RegisterServiceCtrlHandlerA(ServiceName, ServiceControlHandler); + if (ServiceStatusHandle) { + ServiceStatus.dwCurrentState = SERVICE_START_PENDING; + SetServiceStatus(ServiceStatusHandle, &ServiceStatus); + ServiceStatus.dwControlsAccepted |= (SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN); + ServiceStatus.dwCurrentState = SERVICE_RUNNING; + SetServiceStatus(ServiceStatusHandle, &ServiceStatus); + mainloop(); + ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING; + SetServiceStatus(ServiceStatusHandle, &ServiceStatus); + ServiceStatus.dwControlsAccepted &= ~(SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN); + ServiceStatus.dwCurrentState = SERVICE_STOPPED; + SetServiceStatus(ServiceStatusHandle, &ServiceStatus); + } +} + +void ServiceWrapper::RunService() { + SERVICE_TABLE_ENTRYA serviceTable[] = { + { ServiceName, ServiceMain }, + { NULL, NULL} + }; + + StartServiceCtrlDispatcherA(serviceTable); +} \ No newline at end of file diff --git a/spectrum/src/win32/ServiceWrapper.h b/spectrum/src/win32/ServiceWrapper.h new file mode 100644 index 00000000..9ee2fb0d --- /dev/null +++ b/spectrum/src/win32/ServiceWrapper.h @@ -0,0 +1,19 @@ +#pragma once +#include "transport/config.h" +#include +#include + +class ServiceWrapper +{ +public: + ServiceWrapper(LPSTR serviceName); + ~ServiceWrapper(void); + bool Install(LPSTR commandLine); + bool UnInstall(); + bool IsInstalled(); + void RunService(); +}; + +int mainloop(); +void stop(); + diff --git a/spectrum/src/win32/SpectrumService.cpp b/spectrum/src/win32/SpectrumService.cpp deleted file mode 100644 index 1fbcacd3..00000000 --- a/spectrum/src/win32/SpectrumService.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "SpectrumService.h" - -SpectrumService::SpectrumService(void) { - serviceName = "Spectrum2"; - displayName = "Spectrum2 XMPP Transport"; - username = NULL; - password = NULL; -} - -SpectrumService::~SpectrumService(void) {} - -void SpectrumService::Stop() { - ReportStatus((DWORD)SERVICE_STOP_PENDING); -} - -void SpectrumService::Run(DWORD argc, LPTSTR *argv) { - ReportStatus((DWORD)SERVICE_RUNNING); - main(argc, argv); -} \ No newline at end of file diff --git a/spectrum/src/win32/SpectrumService.h b/spectrum/src/win32/SpectrumService.h deleted file mode 100644 index 404b1057..00000000 --- a/spectrum/src/win32/SpectrumService.h +++ /dev/null @@ -1,14 +0,0 @@ -#include -#include "WindowsService.h" - -class SpectrumService : public WindowsService { - -public: - SpectrumService(void); - ~SpectrumService(void); -protected: - void Stop(); - void Run(DWORD argc, LPTSTR *argv); -}; - -int main(int argc, char **argv); \ No newline at end of file diff --git a/spectrum/src/win32/WindowsService.cpp b/spectrum/src/win32/WindowsService.cpp deleted file mode 100644 index 0a749378..00000000 --- a/spectrum/src/win32/WindowsService.cpp +++ /dev/null @@ -1,214 +0,0 @@ -/* Copyright (C) 2005 MySQL AB - - 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; version 2 of the License. - - 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ - -#include -#include -#include ".\windowsservice.h" - -static WindowsService *gService; - -WindowsService::WindowsService(void) : - statusCheckpoint(0), - serviceName(NULL), - inited(false), - dwAcceptedControls(SERVICE_ACCEPT_STOP), - debugging(false) -{ - gService= this; - status.dwServiceType= SERVICE_WIN32_OWN_PROCESS; - status.dwServiceSpecificExitCode= 0; -} - -WindowsService::~WindowsService(void) -{ -} - -BOOL WindowsService::Install(const char *szFilePath) -{ - bool ret_val= false; - SC_HANDLE newService; - SC_HANDLE scm; - - if (IsInstalled()) return true; - - // open a connection to the SCM - if (!(scm= OpenSCManager(0, 0,SC_MANAGER_CREATE_SERVICE))) - return false; - - newService= CreateService(scm, serviceName, displayName, - SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, - SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, - szFilePath, NULL, NULL, NULL, username, - password); - - if (newService) - { - CloseServiceHandle(newService); - ret_val= true; - } - - CloseServiceHandle(scm); - return ret_val; -} - -BOOL WindowsService::Init() -{ - assert(serviceName != NULL); - - if (inited) return true; - - SERVICE_TABLE_ENTRY stb[] = - { - { (LPSTR)serviceName, (LPSERVICE_MAIN_FUNCTION) ServiceMain}, - { NULL, NULL } - }; - inited= true; - return StartServiceCtrlDispatcher(stb); //register with the Service Manager -} - -BOOL WindowsService::Remove() -{ - bool ret_val= false; - - if (! IsInstalled()) - return true; - - // open a connection to the SCM - SC_HANDLE scm= OpenSCManager(0, 0,SC_MANAGER_CREATE_SERVICE); - if (! scm) - return false; - - SC_HANDLE service= OpenService(scm, serviceName, DELETE); - if (service) - { - if (DeleteService(service)) - ret_val= true; - DWORD dw= ::GetLastError(); - CloseServiceHandle(service); - } - - CloseServiceHandle(scm); - return ret_val; -} - -BOOL WindowsService::IsInstalled() -{ - BOOL ret_val= FALSE; - - SC_HANDLE scm= ::OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); - SC_HANDLE serv_handle= ::OpenService(scm, serviceName, SERVICE_QUERY_STATUS); - - ret_val= serv_handle != NULL; - - ::CloseServiceHandle(serv_handle); - ::CloseServiceHandle(scm); - - return ret_val; -} - -void WindowsService::SetAcceptedControls(DWORD acceptedControls) -{ - dwAcceptedControls= acceptedControls; -} - - -BOOL WindowsService::ReportStatus(DWORD currentState, DWORD waitHint, - DWORD dwError) -{ - if(debugging) return TRUE; - - if(currentState == SERVICE_START_PENDING) - status.dwControlsAccepted= 0; - else - status.dwControlsAccepted= dwAcceptedControls; - - status.dwCurrentState= currentState; - status.dwWin32ExitCode= dwError != 0 ? - ERROR_SERVICE_SPECIFIC_ERROR : NO_ERROR; - status.dwWaitHint= waitHint; - status.dwServiceSpecificExitCode= dwError; - - if(currentState == SERVICE_RUNNING || currentState == SERVICE_STOPPED) - { - status.dwCheckPoint= 0; - statusCheckpoint= 0; - } - else - status.dwCheckPoint= ++statusCheckpoint; - - // Report the status of the service to the service control manager. - BOOL result= SetServiceStatus(statusHandle, &status); - if (!result) - Log("ReportStatus failed"); - - return result; -} - -void WindowsService::RegisterAndRun(DWORD argc, LPTSTR *argv) -{ - statusHandle= ::RegisterServiceCtrlHandler(serviceName, ControlHandler); - if (statusHandle && ReportStatus(SERVICE_START_PENDING)) - Run(argc, argv); - ReportStatus(SERVICE_STOPPED); -} - -void WindowsService::HandleControlCode(DWORD opcode) -{ - // Handle the requested control code. - switch(opcode) { - case SERVICE_CONTROL_STOP: - // Stop the service. - status.dwCurrentState= SERVICE_STOP_PENDING; - Stop(); - break; - - case SERVICE_CONTROL_PAUSE: - status.dwCurrentState= SERVICE_PAUSE_PENDING; - Pause(); - break; - - case SERVICE_CONTROL_CONTINUE: - status.dwCurrentState= SERVICE_CONTINUE_PENDING; - Continue(); - break; - - case SERVICE_CONTROL_SHUTDOWN: - Shutdown(); - break; - - case SERVICE_CONTROL_INTERROGATE: - ReportStatus(status.dwCurrentState); - break; - - default: - // invalid control code - break; - } -} - -void WINAPI WindowsService::ServiceMain(DWORD argc, LPTSTR *argv) -{ - assert(gService != NULL); - - // register our service control handler: - gService->RegisterAndRun(argc, argv); -} - -void WINAPI WindowsService::ControlHandler(DWORD opcode) -{ - assert(gService != NULL); - - return gService->HandleControlCode(opcode); -} diff --git a/spectrum/src/win32/WindowsService.h b/spectrum/src/win32/WindowsService.h deleted file mode 100644 index 55e91d8b..00000000 --- a/spectrum/src/win32/WindowsService.h +++ /dev/null @@ -1,58 +0,0 @@ -/* Copyright (C) 2005 MySQL AB - - 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; version 2 of the License. - - 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ - -#pragma once - -class WindowsService -{ -protected: - bool inited; - const char *serviceName; - const char *displayName; - const char *username; - const char *password; - SERVICE_STATUS_HANDLE statusHandle; - DWORD statusCheckpoint; - SERVICE_STATUS status; - DWORD dwAcceptedControls; - bool debugging; - -public: - WindowsService(void); - ~WindowsService(void); - - BOOL Install(const char *szFilePath); - BOOL Remove(); - BOOL Init(); - BOOL IsInstalled(); - void SetAcceptedControls(DWORD acceptedControls); - void Debug(bool debugFlag) { debugging= debugFlag; } - -public: - static void WINAPI ServiceMain(DWORD argc, LPTSTR *argv); - static void WINAPI ControlHandler(DWORD CtrlType); - -protected: - virtual void Run(DWORD argc, LPTSTR *argv)= 0; - virtual void Stop() {} - virtual void Shutdown() {} - virtual void Pause() {} - virtual void Continue() {} - virtual void Log(const char *msg) {} - - BOOL ReportStatus(DWORD currentStatus, DWORD waitHint= 3000, DWORD dwError=0); - void HandleControlCode(DWORD opcode); - void RegisterAndRun(DWORD argc, LPTSTR *argv); -}; diff --git a/spectrum_manager/src/main.cpp b/spectrum_manager/src/main.cpp index 636172a6..94d29345 100644 --- a/spectrum_manager/src/main.cpp +++ b/spectrum_manager/src/main.cpp @@ -139,7 +139,10 @@ int main(int argc, char **argv) } else if (command[0] == "server") { Server server(&config); - server.start(); + if (server.start() == false) { + std::cerr << "Can't set up server handler.\n"; + return 1; + } while (1) { sleep(10); } } else { diff --git a/spectrum_manager/src/mongoose.c b/spectrum_manager/src/mongoose.c index 976870d3..8d52417b 100644 --- a/spectrum_manager/src/mongoose.c +++ b/spectrum_manager/src/mongoose.c @@ -18,6 +18,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +#include + #if defined(_WIN32) #define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2005 #else diff --git a/spectrum_manager/src/server.cpp b/spectrum_manager/src/server.cpp index cc86e927..510fb3a4 100644 --- a/spectrum_manager/src/server.cpp +++ b/spectrum_manager/src/server.cpp @@ -149,7 +149,9 @@ Server::Server(ManagerConfig *config) { } Server::~Server() { - mg_stop(ctx); + if (ctx) { + mg_stop(ctx); + } } @@ -446,7 +448,12 @@ void *Server::event_handler(enum mg_event event, struct mg_connection *conn) { // try to serve the request. processed = NULL; } - } else { + } + else if (event == MG_EVENT_LOG) { + // Called by Mongoose's cry() + std::cerr << "Mongoose error: " << request_info->log_message << "\n"; + } + else { processed = NULL; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6c815564..27fbfae7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,6 +15,7 @@ if (CPPUNIT_FOUND) FILE(GLOB SRC_TEST tests/*.cpp) ADD_EXECUTABLE(libtransport_test ${SRC_TEST}) + set_target_properties(libtransport_test PROPERTIES COMPILE_DEFINITIONS LIBTRANSPORT_TEST=1) target_link_libraries(libtransport_test transport ${CPPUNIT_LIBRARY} ${Boost_LIBRARIES}) endif() diff --git a/src/buddy.cpp b/src/buddy.cpp index 3a32c558..07d1a76c 100644 --- a/src/buddy.cpp +++ b/src/buddy.cpp @@ -23,6 +23,8 @@ #include "transport/user.h" #include "transport/transport.h" #include "transport/BlockPayload.h" +#include "transport/usermanager.h" +#include "transport/discoitemsresponder.h" namespace Transport { @@ -106,7 +108,7 @@ Swift::Presence::ref Buddy::generatePresenceStanza(int features, bool only_new) if (presence->getType() != Swift::Presence::Unavailable) { // caps - presence->addPayload(boost::shared_ptr(new Swift::CapsInfo(m_rosterManager->getUser()->getComponent()->getBuddyCapsInfo()))); + presence->addPayload(boost::shared_ptr(new Swift::CapsInfo(m_rosterManager->getUser()->getUserManager()->getDiscoResponder()->getBuddyCapsInfo()))); // if (features & 0/*TRANSPORT_FEATURE_AVATARS*/) { presence->addPayload(boost::shared_ptr(new Swift::VCardUpdate (getIconHash()))); diff --git a/src/config.cpp b/src/config.cpp index ce49f6d6..6bf3a4b2 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -97,6 +97,10 @@ bool Config::load(std::istream &ifs, boost::program_options::options_description ("service.more_resources", value()->default_value(false), "Allow more resources to be connected in server mode at the same time.") ("service.enable_privacy_lists", value()->default_value(true), "") ("service.enable_xhtml", value()->default_value(true), "") + ("service.max_room_list_size", value()->default_value(100), "") + ("service.jid_escaping", value()->default_value(true), "") + ("service.vip_only", value()->default_value(false), "") + ("service.vip_message", value()->default_value(""), "") ("vhosts.vhost", value >()->multitoken(), "") ("identity.name", value()->default_value("Spectrum 2 Transport"), "Name showed in service discovery.") ("identity.category", value()->default_value("gateway"), "Disco#info identity category. 'gateway' by default.") @@ -123,6 +127,7 @@ bool Config::load(std::istream &ifs, boost::program_options::options_description ("database.port", value()->default_value(0), "Database port.") ("database.prefix", value()->default_value(""), "Prefix of tables in database") ("database.encryption_key", value()->default_value(""), "Encryption key.") + ("database.vip_statement", value()->default_value(""), "Encryption key.") ("logging.config", value()->default_value(""), "Path to log4cxx config file which is used for Spectrum 2 instance") ("logging.backend_config", value()->default_value(""), "Path to log4cxx config file which is used for backends") ("backend.default_avatar", value()->default_value(""), "Full path to default avatar") diff --git a/src/conversation.cpp b/src/conversation.cpp index f6105461..9cd8f739 100644 --- a/src/conversation.cpp +++ b/src/conversation.cpp @@ -33,6 +33,7 @@ Conversation::Conversation(ConversationManager *conversationManager, const std:: // m_conversationManager->addConversation(this); m_muc = isMUC; m_jid = m_conversationManager->getUser()->getJID().toBare(); + m_sentInitialPresence = false; } Conversation::~Conversation() { @@ -91,7 +92,17 @@ void Conversation::handleMessage(boost::shared_ptr &message, con message->setFrom(buddy->getJID()); } else { - message->setFrom(Swift::JID(Swift::JID::getEscapedNode(m_legacyName), m_conversationManager->getComponent()->getJID().toBare())); + std::string name = m_legacyName; + if (CONFIG_BOOL_DEFAULTED(m_conversationManager->getComponent()->getConfig(), "service.jid_escaping", true)) { + name = Swift::JID::getEscapedNode(m_legacyName); + } + else { + if (name.find_last_of("@") != std::string::npos) { + name.replace(name.find_last_of("@"), 1, "%"); + } + } + + message->setFrom(Swift::JID(name, m_conversationManager->getComponent()->getJID().toBare(), "bot")); } } // PM message @@ -118,6 +129,11 @@ void Conversation::handleMessage(boost::shared_ptr &message, con BOOST_FOREACH(const Swift::JID &jid, m_jids) { message->setTo(jid); message->setFrom(Swift::JID(legacyName, m_conversationManager->getComponent()->getJID().toBare(), n)); + // Subject has to be sent after our own presence (the one with code 110) + if (!message->getSubject().empty() && m_sentInitialPresence == false) { + m_subject = message; + return; + } m_conversationManager->getComponent()->getStanzaChannel()->sendMessage(message); } } @@ -159,6 +175,7 @@ Swift::Presence::ref Conversation::generatePresence(const std::string &nick, int Swift::MUCUserPayload::StatusCode c; c.code = 110; p->addStatusCode(c); + m_sentInitialPresence = true; } @@ -207,6 +224,11 @@ void Conversation::handleParticipantChanged(const std::string &nick, int flag, i if (!newname.empty()) { handleParticipantChanged(newname, flag, status, statusMessage); } + + if (m_sentInitialPresence && m_subject) { + m_conversationManager->getComponent()->getStanzaChannel()->sendMessage(m_subject); + m_subject.reset(); + } } } diff --git a/src/conversationmanager.cpp b/src/conversationmanager.cpp index 4fc23afc..066a4603 100644 --- a/src/conversationmanager.cpp +++ b/src/conversationmanager.cpp @@ -47,6 +47,15 @@ ConversationManager::~ConversationManager() { } } +void ConversationManager::deleteAllConversations() { + while(!m_convs.empty()) { + LOG4CXX_INFO(logger, m_user->getJID().toString() << ": Removing conversation " << (*m_convs.begin()).first); + (*m_convs.begin()).second->destroyRoom(); + delete (*m_convs.begin()).second; + m_convs.erase(m_convs.begin()); + } +} + Conversation *ConversationManager::getConversation(const std::string &name) { if (m_convs.find(name) != m_convs.end()) return m_convs[name]; diff --git a/src/discoinforesponder.cpp b/src/discoinforesponder.cpp index 6d701088..2ca5746c 100644 --- a/src/discoinforesponder.cpp +++ b/src/discoinforesponder.cpp @@ -22,15 +22,19 @@ #include #include +#include #include "Swiften/Disco/DiscoInfoResponder.h" #include "Swiften/Queries/IQRouter.h" #include "Swiften/Elements/DiscoInfo.h" #include "Swiften/Swiften.h" #include "transport/config.h" +#include "transport/logging.h" using namespace Swift; using namespace boost; +DEFINE_LOGGER(logger, "DiscoInfoResponder"); + namespace Transport { DiscoInfoResponder::DiscoInfoResponder(Swift::IQRouter *router, Config *config) : Swift::GetResponder(router) { @@ -80,19 +84,51 @@ void DiscoInfoResponder::setBuddyFeatures(std::list &f) { onBuddyCapsInfoChanged(m_capsInfo); } -bool DiscoInfoResponder::handleGetRequest(const Swift::JID& from, const Swift::JID& to, const std::string& id, boost::shared_ptr info) { - if (!info->getNode().empty()) { - sendError(from, id, ErrorPayload::ItemNotFound, ErrorPayload::Cancel); - return true; - } +void DiscoInfoResponder::addRoom(const std::string &jid, const std::string &name) { + std::string j = jid; + boost::algorithm::to_lower(j); + m_rooms[j] = name; +} - // presence for transport +void DiscoInfoResponder::clearRooms() { + m_rooms.clear(); +} + +void DiscoInfoResponder::addAdHocCommand(const std::string &node, const std::string &name) { + m_commands[node] = node; +} + +bool DiscoInfoResponder::handleGetRequest(const Swift::JID& from, const Swift::JID& to, const std::string& id, boost::shared_ptr info) { + // disco#info for transport if (to.getNode().empty()) { - boost::shared_ptr res(new DiscoInfo(m_transportInfo)); - res->setNode(info->getNode()); - sendResponse(from, id, res); + // Adhoc command + if (m_commands.find(info->getNode()) != m_commands.end()) { + boost::shared_ptr res(new DiscoInfo()); + res->addFeature("http://jabber.org/protocol/commands"); + res->addFeature("jabber:x:data"); + res->addIdentity(DiscoInfo::Identity(m_commands[info->getNode()], "automation", "command-node")); + res->setNode(info->getNode()); + sendResponse(from, to, id, res); + } + else { + if (!info->getNode().empty()) { + sendError(from, id, ErrorPayload::ItemNotFound, ErrorPayload::Cancel); + return true; + } + + boost::shared_ptr res(new DiscoInfo(m_transportInfo)); + res->setNode(info->getNode()); + sendResponse(from, id, res); + } } - // presence for buddy + // disco#info for room + else if (m_rooms.find(to.toBare().toString()) != m_rooms.end()) { + boost::shared_ptr res(new DiscoInfo()); + res->addIdentity(DiscoInfo::Identity(m_rooms[to.toBare().toString()], "conference", "text")); + res->setNode(info->getNode()); + sendResponse(from, to, id, res); + } + // disco#info for buddy else { boost::shared_ptr res(new DiscoInfo(m_buddyInfo)); res->setNode(info->getNode()); diff --git a/src/discoinforesponder.h b/src/discoinforesponder.h index 7e374702..a3109222 100644 --- a/src/discoinforesponder.h +++ b/src/discoinforesponder.h @@ -38,6 +38,11 @@ class DiscoInfoResponder : public Swift::GetResponder { void setTransportFeatures(std::list &features); void setBuddyFeatures(std::list &features); + void addRoom(const std::string &jid, const std::string &name); + void clearRooms(); + + void addAdHocCommand(const std::string &node, const std::string &name); + boost::signal onBuddyCapsInfoChanged; Swift::CapsInfo &getBuddyCapsInfo() { @@ -51,6 +56,8 @@ class DiscoInfoResponder : public Swift::GetResponder { Swift::DiscoInfo m_buddyInfo; Config *m_config; Swift::CapsInfo m_capsInfo; + std::map m_rooms; + std::map m_commands; }; } \ No newline at end of file diff --git a/src/discoitemsresponder.cpp b/src/discoitemsresponder.cpp index 2365b27e..72ac8390 100644 --- a/src/discoitemsresponder.cpp +++ b/src/discoitemsresponder.cpp @@ -26,6 +26,7 @@ #include "Swiften/Swiften.h" #include "transport/transport.h" #include "transport/logging.h" +#include "discoinforesponder.h" using namespace Swift; using namespace boost; @@ -38,14 +39,36 @@ DiscoItemsResponder::DiscoItemsResponder(Component *component) : Swift::GetRespo m_component = component; m_commands = boost::shared_ptr(new DiscoItems()); m_commands->setNode("http://jabber.org/protocol/commands"); + + m_rooms = boost::shared_ptr(new DiscoItems()); + m_discoInfoResponder = new DiscoInfoResponder(component->getIQRouter(), component->getConfig()); + m_discoInfoResponder->start(); } DiscoItemsResponder::~DiscoItemsResponder() { - + delete m_discoInfoResponder; } void DiscoItemsResponder::addAdHocCommand(const std::string &node, const std::string &name) { m_commands->addItem(DiscoItems::Item(name, m_component->getJID(), node)); + m_discoInfoResponder->addAdHocCommand(node, name); +} + +void DiscoItemsResponder::addRoom(const std::string &node, const std::string &name) { + if (m_rooms->getItems().size() > CONFIG_INT(m_component->getConfig(), "service.max_room_list_size")) { + return; + } + m_rooms->addItem(DiscoItems::Item(name, node)); + m_discoInfoResponder->addRoom(node, name); +} + +void DiscoItemsResponder::clearRooms() { + m_rooms = boost::shared_ptr(new DiscoItems()); + m_discoInfoResponder->clearRooms(); +} + +Swift::CapsInfo &DiscoItemsResponder::getBuddyCapsInfo() { + return m_discoInfoResponder->getBuddyCapsInfo(); } @@ -55,7 +78,7 @@ bool DiscoItemsResponder::handleGetRequest(const Swift::JID& from, const Swift:: sendResponse(from, id, m_commands); } else if (to.getNode().empty()) { - sendResponse(from, id, boost::shared_ptr(new DiscoItems())); + sendResponse(from, id, m_rooms); } else { sendResponse(from, id, boost::shared_ptr(new DiscoItems())); diff --git a/src/logging.cpp b/src/logging.cpp index f724993e..a7f8478b 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -20,6 +20,7 @@ #include "transport/logging.h" #include "transport/config.h" +#include "transport/util.h" #include #include #include @@ -31,6 +32,7 @@ #ifndef WIN32 #include "sys/signal.h" +#include #include #include #include @@ -136,35 +138,42 @@ static void initLogging(Config *config, std::string key) { break; } } - + mode_t old_cmask; if (!dir.empty()) { // create directories - try { - boost::filesystem::create_directories( - boost::filesystem::path(dir).parent_path().string() - ); - } - catch (...) { - std::cerr << "Can't create logging directory directory " << boost::filesystem::path(dir).parent_path().string() << ".\n"; - } - #ifndef WIN32 - if (!CONFIG_STRING(config, "service.group").empty() && !CONFIG_STRING(config, "service.user").empty()) { - struct group *gr; - if ((gr = getgrnam(CONFIG_STRING(config, "service.group").c_str())) == NULL) { - std::cerr << "Invalid service.group name " << CONFIG_STRING(config, "service.group") << "\n"; - } - struct passwd *pw; - if ((pw = getpwnam(CONFIG_STRING(config, "service.user").c_str())) == NULL) { - std::cerr << "Invalid service.user name " << CONFIG_STRING(config, "service.user") << "\n"; - } - chown(dir.c_str(), pw->pw_uid, gr->gr_gid); - } - + old_cmask = umask(0007); #endif + try { + Transport::Util::createDirectories(config, boost::filesystem::path(dir).parent_path()); + } + catch (const boost::filesystem::filesystem_error &e) { + std::cerr << "Can't create logging directory directory " << boost::filesystem::path(dir).parent_path().string() << ": " << e.what() << ".\n"; + } } log4cxx::PropertyConfigurator::configure(p); + + // Change owner of main log file +#ifndef WIN32 + if (!CONFIG_STRING(config, "service.group").empty() && !CONFIG_STRING(config, "service.user").empty()) { + struct group *gr; + if ((gr = getgrnam(CONFIG_STRING(config, "service.group").c_str())) == NULL) { + std::cerr << "Invalid service.group name " << CONFIG_STRING(config, "service.group") << "\n"; + } + struct passwd *pw; + if ((pw = getpwnam(CONFIG_STRING(config, "service.user").c_str())) == NULL) { + std::cerr << "Invalid service.user name " << CONFIG_STRING(config, "service.user") << "\n"; + } + chown(dir.c_str(), pw->pw_uid, gr->gr_gid); + } +#endif + +#ifndef WIN32 + if (!dir.empty()) { + umask(old_cmask); + } +#endif } } diff --git a/src/mysqlbackend.cpp b/src/mysqlbackend.cpp index 67aa6508..7d28d506 100644 --- a/src/mysqlbackend.cpp +++ b/src/mysqlbackend.cpp @@ -421,7 +421,7 @@ bool MySQLBackend::exec(const std::string &query) { void MySQLBackend::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")); + encrypted = StorageBackend::encryptPassword(encrypted, CONFIG_STRING(m_config, "database.encryption_key")); } *m_setUser << user.jid << user.uin << encrypted << user.language << user.encoding << user.vip << user.uin << encrypted; EXEC(m_setUser, setUser(user)); @@ -439,7 +439,24 @@ bool MySQLBackend::getUser(const std::string &barejid, UserInfo &user) { *m_getUser >> user.id >> user.jid >> user.uin >> user.password >> user.encoding >> user.language >> user.vip; if (!CONFIG_STRING(m_config, "database.encryption_key").empty()) { - user.password = Util::decryptPassword(user.password, CONFIG_STRING(m_config, "database.encryption_key")); + user.password = StorageBackend::decryptPassword(user.password, CONFIG_STRING(m_config, "database.encryption_key")); + } + } + + if (!CONFIG_STRING(m_config, "database.vip_statement").empty()) { + std::string query = CONFIG_STRING(m_config, "database.vip_statement"); + boost::replace_all(query, "$barejid", barejid); + LOG4CXX_INFO(logger, "Executing '" << query << "' to find out if user " << barejid << " is VIP"); + if (exec(query)) { + MYSQL_RES *result = mysql_store_result(&m_conn); + if (result && mysql_num_rows(result) > 0) { + LOG4CXX_INFO(logger, "User " << barejid << " is VIP"); + user.vip = 1; + } + else { + LOG4CXX_INFO(logger, "User " << barejid << " is not VIP"); + user.vip = 0; + } } } @@ -467,7 +484,7 @@ bool MySQLBackend::getOnlineUsers(std::vector &users) { long MySQLBackend::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); + std::string groups = StorageBackend::serializeGroups(buddyInfo.groups); *m_addBuddy << userId << buddyInfo.legacyName << buddyInfo.subscription; *m_addBuddy << groups; *m_addBuddy << buddyInfo.alias << buddyInfo.flags; @@ -517,7 +534,7 @@ void MySQLBackend::removeBuddy(long id) { void MySQLBackend::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); + std::string groups = StorageBackend::serializeGroups(buddyInfo.groups); *m_updateBuddy << groups; *m_updateBuddy << buddyInfo.alias << buddyInfo.flags << buddyInfo.subscription; *m_updateBuddy << userId << buddyInfo.legacyName; @@ -547,7 +564,7 @@ bool MySQLBackend::getBuddies(long id, std::list &roster) { *m_getBuddies >> b.id >> b.legacyName >> b.subscription >> b.alias >> group >> b.flags; if (!group.empty()) { - b.groups = Util::deserializeGroups(group); + b.groups = StorageBackend::deserializeGroups(group); } roster.push_back(b); diff --git a/src/networkpluginserver.cpp b/src/networkpluginserver.cpp index a7efd8fb..5061d1ce 100644 --- a/src/networkpluginserver.cpp +++ b/src/networkpluginserver.cpp @@ -44,6 +44,7 @@ #include "Swiften/Elements/SpectrumErrorPayload.h" #include "transport/protocol.pb.h" #include "transport/util.h" +#include "transport/discoitemsresponder.h" #include "utf8.h" @@ -92,8 +93,8 @@ class NetworkFactory : public Factory { virtual ~NetworkFactory() {} // Creates new conversation (NetworkConversation in this case) - Conversation *createConversation(ConversationManager *conversationManager, const std::string &legacyName) { - NetworkConversation *nc = new NetworkConversation(conversationManager, legacyName); + Conversation *createConversation(ConversationManager *conversationManager, const std::string &legacyName, bool isMuc) { + NetworkConversation *nc = new NetworkConversation(conversationManager, legacyName, isMuc); nc->onMessageToSend.connect(boost::bind(&NetworkPluginServer::handleMessageReceived, m_nps, _1, _2)); return nc; } @@ -184,6 +185,11 @@ static unsigned long exec_(const std::string& exePath, const char *host, const c pid_t pid = fork(); if ( pid == 0 ) { setsid(); + // close all files + int maxfd=sysconf(_SC_OPEN_MAX); + for(int fd=3; fd groups; for (int i = 0; i < payload.group_size(); i++) { - groups.push_back(payload.group(i)); + std::string group = payload.group(i); + utf8::replace_invalid(payload.group(i).begin(), payload.group(i).end(), group.begin(), '_'); + groups.push_back(group); } if (!groups.empty()) { buddy->setGroups(groups); @@ -242,7 +250,7 @@ static void handleBuddyPayload(LocalBuddy *buddy, const pbnetwork::Buddy &payloa buddy->setBlocked(payload.blocked()); } -NetworkPluginServer::NetworkPluginServer(Component *component, Config *config, UserManager *userManager, FileTransferManager *ftManager) { +NetworkPluginServer::NetworkPluginServer(Component *component, Config *config, UserManager *userManager, FileTransferManager *ftManager, DiscoItemsResponder *discoItemsResponder) { m_ftManager = ftManager; m_userManager = userManager; m_config = config; @@ -250,6 +258,7 @@ NetworkPluginServer::NetworkPluginServer(Component *component, Config *config, U m_isNextLongRun = false; m_adminInterface = NULL; m_startingBackend = false; + m_discoItemsResponder = discoItemsResponder; m_component->m_factory = new NetworkFactory(this); m_userManager->onUserCreated.connect(boost::bind(&NetworkPluginServer::handleUserCreated, this, _1)); m_userManager->onUserDestroyed.connect(boost::bind(&NetworkPluginServer::handleUserDestroyed, this, _1)); @@ -281,40 +290,6 @@ NetworkPluginServer::NetworkPluginServer(Component *component, Config *config, U m_server = component->getNetworkFactories()->getConnectionServerFactory()->createConnectionServer(Swift::HostAddress(CONFIG_STRING(m_config, "service.backend_host")), boost::lexical_cast(CONFIG_STRING(m_config, "service.backend_port"))); m_server->onNewConnection.connect(boost::bind(&NetworkPluginServer::handleNewClientConnection, this, _1)); - m_server->start(); - - LOG4CXX_INFO(logger, "Listening on host " << CONFIG_STRING(m_config, "service.backend_host") << " port " << CONFIG_STRING(m_config, "service.backend_port")); - - while (true) { - unsigned long pid = exec_(CONFIG_STRING(m_config, "service.backend"), CONFIG_STRING(m_config, "service.backend_host").c_str(), CONFIG_STRING(m_config, "service.backend_port").c_str(), m_config->getCommandLineArgs().c_str()); - LOG4CXX_INFO(logger, "Tried to spawn first backend with pid " << pid); - LOG4CXX_INFO(logger, "Backend should now connect to Spectrum2 instance. Spectrum2 won't accept any connection before backend connects"); - -#ifndef _WIN32 - // wait if the backend process will still be alive after 1 second - sleep(1); - pid_t result; - int status; - result = waitpid(-1, &status, WNOHANG); - if (result != 0) { - if (WIFEXITED(status)) { - if (WEXITSTATUS(status) != 0) { - LOG4CXX_ERROR(logger, "Backend can not be started, exit_code=" << WEXITSTATUS(status) << ", possible error: " << strerror(WEXITSTATUS(status))); - continue; - } - } - else { - LOG4CXX_ERROR(logger, "Backend can not be started"); - continue; - } - } - - signal(SIGCHLD, SigCatcher); -#endif - // quit the while loop - break; - } - } NetworkPluginServer::~NetworkPluginServer() { @@ -338,6 +313,48 @@ NetworkPluginServer::~NetworkPluginServer() { delete m_blockResponder; } +void NetworkPluginServer::start() { + m_server->start(); + + LOG4CXX_INFO(logger, "Listening on host " << CONFIG_STRING(m_config, "service.backend_host") << " port " << CONFIG_STRING(m_config, "service.backend_port")); + + while (true) { + unsigned long pid = exec_(CONFIG_STRING(m_config, "service.backend"), CONFIG_STRING(m_config, "service.backend_host").c_str(), CONFIG_STRING(m_config, "service.backend_port").c_str(), m_config->getCommandLineArgs().c_str()); + LOG4CXX_INFO(logger, "Tried to spawn first backend with pid " << pid); + LOG4CXX_INFO(logger, "Backend should now connect to Spectrum2 instance. Spectrum2 won't accept any connection before backend connects"); + +#ifndef _WIN32 + // wait if the backend process will still be alive after 1 second + sleep(1); + pid_t result; + int status; + result = waitpid(-1, &status, WNOHANG); + if (result != 0) { + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) { + if (status == 254) { + LOG4CXX_ERROR(logger, "Backend can not be started, because it needs database to store data, but the database backend is not configured."); + } + else { + LOG4CXX_ERROR(logger, "Backend can not be started, exit_code=" << WEXITSTATUS(status) << ", possible error: " << strerror(WEXITSTATUS(status))); + } + LOG4CXX_ERROR(logger, "Check backend log for more details"); + continue; + } + } + else { + LOG4CXX_ERROR(logger, "Backend can not be started"); + continue; + } + } + + signal(SIGCHLD, SigCatcher); +#endif + // quit the while loop + break; + } +} + void NetworkPluginServer::handleNewClientConnection(boost::shared_ptr c) { // Create new Backend instance Backend *client = new Backend; @@ -475,11 +492,14 @@ void NetworkPluginServer::handleAuthorizationPayload(const std::string &data) { response->setTo(user->getJID()); std::string name = payload.buddyname(); - name = Swift::JID::getEscapedNode(name); - -// if (name.find_last_of("@") != std::string::npos) { // OK when commented -// name.replace(name.find_last_of("@"), 1, "%"); // OK when commented -// } + if (CONFIG_BOOL_DEFAULTED(m_config, "service.jid_escaping", true)) { + name = Swift::JID::getEscapedNode(name); + } + else { + if (name.find_last_of("@") != std::string::npos) { + name.replace(name.find_last_of("@"), 1, "%"); + } + } response->setFrom(Swift::JID(name, m_component->getJID().toString())); response->setType(Swift::Presence::Subscribe); @@ -528,15 +548,25 @@ void NetworkPluginServer::handleBuddyChangedPayload(const std::string &data) { buddy->handleBuddyChanged(); } else { + if (payload.buddyname() == user->getUserInfo().uin) { + return; + } + std::vector groups; for (int i = 0; i < payload.group_size(); i++) { groups.push_back(payload.group(i)); } - buddy = new LocalBuddy(user->getRosterManager(), -1, payload.buddyname(), payload.alias(), groups, BUDDY_JID_ESCAPING); + if (CONFIG_BOOL_DEFAULTED(m_config, "service.jid_escaping", true)) { + buddy = new LocalBuddy(user->getRosterManager(), -1, payload.buddyname(), payload.alias(), groups, BUDDY_JID_ESCAPING); + } + else { + buddy = new LocalBuddy(user->getRosterManager(), -1, payload.buddyname(), payload.alias(), groups, BUDDY_NO_FLAG); + } if (!buddy->isValid()) { delete buddy; return; } + buddy->setStatus(Swift::StatusShow((Swift::StatusShow::Type) payload.status()), payload.statusmessage()); buddy->setIconHash(payload.iconhash()); buddy->setBlocked(payload.blocked()); @@ -624,8 +654,21 @@ void NetworkPluginServer::handleConvMessagePayload(const std::string &data, bool msg->addPayload(boost::make_shared(payload.xhtml())); } - // Create new Conversation if it does not exist + if (!payload.timestamp().empty()) { + boost::posix_time::ptime timestamp = boost::posix_time::from_iso_string(payload.timestamp()); + msg->addPayload(boost::make_shared(timestamp)); + } + + NetworkConversation *conv = (NetworkConversation *) user->getConversationManager()->getConversation(payload.buddyname()); + + // We can't create Conversation for payload with nickname, because this means the message is from room, + // but this user is not in any room, so it's OK to just reject this message + if (!conv && !payload.nickname().empty()) { + return; + } + + // Create new Conversation if it does not exist if (!conv) { conv = new NetworkConversation(user->getConversationManager(), payload.buddyname()); user->getConversationManager()->addConversation(conv); @@ -746,6 +789,11 @@ void NetworkPluginServer::handleFTDataPayload(Backend *b, const std::string &dat // if (!user) // return; + if (m_filetransfers.find(payload.ftid()) == m_filetransfers.end()) { + LOG4CXX_ERROR(logger, "Uknown filetransfer with id " << payload.ftid()); + return; + } + FileTransferManager::Transfer &transfer = m_filetransfers[payload.ftid()]; MemoryReadBytestream *bytestream = (MemoryReadBytestream *) transfer.readByteStream.get(); @@ -848,6 +896,19 @@ void NetworkPluginServer::handleBackendConfigPayload(const std::string &data) { m_config->updateBackendConfig(payload.config()); } +void NetworkPluginServer::handleRoomListPayload(const std::string &data) { + pbnetwork::RoomList payload; + if (payload.ParseFromString(data) == false) { + // TODO: ERROR + return; + } + + m_discoItemsResponder->clearRooms(); + for (int i = 0; i < payload.room_size() && i < payload.name_size(); i++) { + m_discoItemsResponder->addRoom(Swift::JID::getEscapedNode(payload.room(i)) + "@" + m_component->getJID().toString(), payload.name(i)); + } +} + void NetworkPluginServer::handleDataRead(Backend *c, boost::shared_ptr data) { // Append data to buffer c->data.insert(c->data.end(), data->begin(), data->end()); @@ -945,6 +1006,9 @@ void NetworkPluginServer::handleDataRead(Backend *c, boost::shared_ptrconnection, message); - - NetworkConversation *conv = new NetworkConversation(user->getConversationManager(), r, true); - user->getConversationManager()->addConversation(conv); - conv->onMessageToSend.connect(boost::bind(&NetworkPluginServer::handleMessageReceived, this, _1, _2)); - conv->setNickname(nickname); - conv->addJID(who); } void NetworkPluginServer::handleRoomLeft(User *user, const std::string &r) { diff --git a/src/pqxxbackend.cpp b/src/pqxxbackend.cpp index c2ae4580..90fffb87 100644 --- a/src/pqxxbackend.cpp +++ b/src/pqxxbackend.cpp @@ -157,7 +157,7 @@ bool PQXXBackend::exec(pqxx::nontransaction &txn, const std::string &query, bool 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")); + encrypted = StorageBackend::encryptPassword(encrypted, CONFIG_STRING(m_config, "database.encryption_key")); } try { pqxx::nontransaction txn(*m_conn); @@ -236,7 +236,7 @@ long PQXXBackend::addBuddy(long userId, const BuddyInfo &buddyInfo) { + "(" + pqxx::to_string(userId) + "," + quote(txn, buddyInfo.legacyName) + "," + quote(txn, buddyInfo.subscription) + "," - + quote(txn, Util::serializeGroups(buddyInfo.groups)) + "," + + quote(txn, StorageBackend::serializeGroups(buddyInfo.groups)) + "," + quote(txn, buddyInfo.alias) + "," + pqxx::to_string(buddyInfo.flags) + ") RETURNING id"); @@ -263,7 +263,7 @@ long PQXXBackend::addBuddy(long userId, const BuddyInfo &buddyInfo) { void PQXXBackend::updateBuddy(long userId, const BuddyInfo &buddyInfo) { try { pqxx::nontransaction txn(*m_conn); - txn.exec("UPDATE " + m_prefix + "buddies SET groups=" + quote(txn, Util::serializeGroups(buddyInfo.groups)) + ", nickname=" + quote(txn, buddyInfo.alias) + ", flags=" + pqxx::to_string(buddyInfo.flags) + ", subscription=" + quote(txn, buddyInfo.subscription) + " WHERE user_id=" + pqxx::to_string(userId) + " AND uin=" + quote(txn, buddyInfo.legacyName)); + txn.exec("UPDATE " + m_prefix + "buddies SET groups=" + quote(txn, StorageBackend::serializeGroups(buddyInfo.groups)) + ", nickname=" + quote(txn, buddyInfo.alias) + ", flags=" + pqxx::to_string(buddyInfo.flags) + ", subscription=" + quote(txn, buddyInfo.subscription) + " WHERE user_id=" + pqxx::to_string(userId) + " AND uin=" + quote(txn, buddyInfo.legacyName)); } catch (std::exception& e) { LOG4CXX_ERROR(logger, e.what()); @@ -287,7 +287,7 @@ bool PQXXBackend::getBuddies(long id, std::list &roster) { b.flags = r[0][5].as(); if (!group.empty()) { - b.groups = Util::deserializeGroups(group); + b.groups = StorageBackend::deserializeGroups(group); } roster.push_back(b); diff --git a/src/rostermanager.cpp b/src/rostermanager.cpp index 77ed09a3..0abec9f6 100644 --- a/src/rostermanager.cpp +++ b/src/rostermanager.cpp @@ -181,7 +181,7 @@ void RosterManager::sendBuddyRosterPush(Buddy *buddy) { if (buddy->getSubscription() != Buddy::Both) { buddy->setSubscription(Buddy::Both); - handleBuddyChanged(buddy); + storeBuddy(buddy); } } @@ -446,13 +446,15 @@ void RosterManager::handleSubscription(Swift::Presence::ref presence) { } if (buddy->getSubscription() != Buddy::Both) { buddy->setSubscription(Buddy::Both); - handleBuddyChanged(buddy); + storeBuddy(buddy); } break; // remove buddy case Swift::Presence::Unsubscribe: response->setType(Swift::Presence::Unsubscribed); onBuddyRemoved(buddy); + removeBuddy(buddy->getName()); + buddy = NULL; break; // just send response case Swift::Presence::Unsubscribed: @@ -462,13 +464,13 @@ void RosterManager::handleSubscription(Swift::Presence::ref presence) { // to be send later again if (buddy->getSubscription() != Buddy::Both) { buddy->setSubscription(Buddy::Both); - handleBuddyChanged(buddy); + storeBuddy(buddy); } break; case Swift::Presence::Subscribed: if (buddy->getSubscription() != Buddy::Both) { buddy->setSubscription(Buddy::Both); - handleBuddyChanged(buddy); + storeBuddy(buddy); } return; default: @@ -491,10 +493,19 @@ void RosterManager::handleSubscription(Swift::Presence::ref presence) { onBuddyAdded(buddy); response->setType(Swift::Presence::Subscribed); break; - // buddy is already there, so nothing to do, just answer case Swift::Presence::Unsubscribe: + buddyInfo.id = -1; + buddyInfo.alias = ""; + buddyInfo.legacyName = Buddy::JIDToLegacyName(presence->getTo()); + buddyInfo.subscription = "both"; + buddyInfo.flags = Buddy::buddyFlagsFromJID(presence->getTo()); + response->setType(Swift::Presence::Unsubscribed); -// onBuddyRemoved(buddy); + + buddy = m_component->getFactory()->createBuddy(this, buddyInfo); + onBuddyRemoved(buddy); + delete buddy; + buddy = NULL; break; // just send response case Swift::Presence::Unsubscribed: diff --git a/src/rosterresponder.cpp b/src/rosterresponder.cpp index 9c0be47d..6bc496c1 100644 --- a/src/rosterresponder.cpp +++ b/src/rosterresponder.cpp @@ -74,6 +74,10 @@ bool RosterResponder::handleSetRequest(const Swift::JID& from, const Swift::JID& Swift::RosterItemPayload item = payload->getItems()[0]; + if (item.getJID().getNode().empty()) { + return true; + } + Buddy *buddy = user->getRosterManager()->getBuddy(Buddy::JIDToLegacyName(item.getJID())); if (buddy) { if (item.getSubscription() == Swift::RosterItemPayload::Remove) { diff --git a/src/sqlite3backend.cpp b/src/sqlite3backend.cpp index 3de7371b..b95deb3f 100644 --- a/src/sqlite3backend.cpp +++ b/src/sqlite3backend.cpp @@ -287,7 +287,7 @@ long SQLite3Backend::addBuddy(long userId, const BuddyInfo &buddyInfo) { BIND_INT(m_addBuddy, userId); BIND_STR(m_addBuddy, buddyInfo.legacyName); BIND_STR(m_addBuddy, buddyInfo.subscription); - BIND_STR(m_addBuddy, Util::serializeGroups(buddyInfo.groups)); + BIND_STR(m_addBuddy, StorageBackend::serializeGroups(buddyInfo.groups)); BIND_STR(m_addBuddy, buddyInfo.alias); BIND_INT(m_addBuddy, buddyInfo.flags); @@ -313,7 +313,7 @@ long SQLite3Backend::addBuddy(long userId, const BuddyInfo &buddyInfo) { void SQLite3Backend::updateBuddy(long userId, const BuddyInfo &buddyInfo) { // UPDATE " + m_prefix + "buddies SET groups=?, nickname=?, flags=?, subscription=? WHERE user_id=? AND uin=? BEGIN(m_updateBuddy); - BIND_STR(m_updateBuddy, Util::serializeGroups(buddyInfo.groups)); + BIND_STR(m_updateBuddy, StorageBackend::serializeGroups(buddyInfo.groups)); BIND_STR(m_updateBuddy, buddyInfo.alias); BIND_INT(m_updateBuddy, buddyInfo.flags); BIND_STR(m_updateBuddy, buddyInfo.subscription); @@ -355,7 +355,7 @@ bool SQLite3Backend::getBuddies(long id, std::list &roster) { b.subscription = GET_STR(m_getBuddies); b.alias = GET_STR(m_getBuddies); std::string groups = GET_STR(m_getBuddies); - b.groups = Util::deserializeGroups(groups); + b.groups = StorageBackend::deserializeGroups(groups); b.flags = GET_INT(m_getBuddies); if (buddy_id == b.id) { diff --git a/src/storagebackend.cpp b/src/storagebackend.cpp index 2247dee9..ce27dfae 100644 --- a/src/storagebackend.cpp +++ b/src/storagebackend.cpp @@ -5,13 +5,14 @@ #include "transport/mysqlbackend.h" #include "transport/pqxxbackend.h" +#include "Swiften/Swiften.h" + namespace Transport { StorageBackend *StorageBackend::createBackend(Config *config, std::string &error) { StorageBackend *storageBackend = NULL; #ifdef WITH_SQLITE - if (CONFIG_STRING(config, "database.type") == "sqlite3" || - (CONFIG_STRING(config, "database.type") == "none" && !CONFIG_BOOL(config, "service.server_mode"))) { + if (CONFIG_STRING(config, "database.type") == "sqlite3") { storageBackend = new SQLite3Backend(config); } #else @@ -48,4 +49,56 @@ StorageBackend *StorageBackend::createBackend(Config *config, std::string &error return storageBackend; } +std::string StorageBackend::encryptPassword(const std::string &password, const std::string &key) { + std::string encrypted; + encrypted.resize(password.size()); + for (int i = 0; i < password.size(); i++) { + char c = password[i]; + char keychar = key[i % key.size()]; + c += keychar; + encrypted[i] = c; + } + + encrypted = Swift::Base64::encode(Swift::createByteArray(encrypted)); + return encrypted; +} + +std::string StorageBackend::decryptPassword(std::string &encrypted, const std::string &key) { + encrypted = Swift::byteArrayToString(Swift::Base64::decode(encrypted)); + std::string password; + password.resize(encrypted.size()); + for (int i = 0; i < encrypted.size(); i++) { + char c = encrypted[i]; + char keychar = key[i % key.size()]; + c -= keychar; + password[i] = c; + } + + return password; +} + +std::string StorageBackend::serializeGroups(const std::vector &groups) { + std::string ret; + BOOST_FOREACH(const std::string &group, groups) { + ret += group + "\n"; + } + if (!ret.empty()) { + ret.erase(ret.end() - 1); + } + return ret; +} + +std::vector StorageBackend::deserializeGroups(std::string &groups) { + std::vector ret; + if (groups.empty()) { + return ret; + } + + boost::split(ret, groups, boost::is_any_of("\n")); + if (ret.back().empty()) { + ret.erase(ret.end() - 1); + } + return ret; +} + } diff --git a/src/tests/basictest.cpp b/src/tests/basictest.cpp index 9a893e7d..1c0b2eed 100644 --- a/src/tests/basictest.cpp +++ b/src/tests/basictest.cpp @@ -57,14 +57,14 @@ void BasicTest::setMeUp (void) { component = new Component(loop, factories, cfg, factory, userRegistry); component->start(); - userManager = new UserManager(component, userRegistry, storage); + itemsResponder = new DiscoItemsResponder(component); + itemsResponder->start(); + + userManager = new UserManager(component, userRegistry, itemsResponder, storage); userRegistration = new UserRegistration(component, userManager, storage); userRegistration->start(); - itemsResponder = new DiscoItemsResponder(component); - itemsResponder->start(); - payloadSerializers = new Swift::FullPayloadSerializerCollection(); payloadParserFactories = new Swift::FullPayloadParserFactoryCollection(); @@ -202,6 +202,7 @@ void BasicTest::connectUser() { CPPUNIT_ASSERT_EQUAL(2, (int) received.size()); CPPUNIT_ASSERT(getStanza(received[0])->getPayload()); + received.clear(); receivedData.clear(); } @@ -236,6 +237,7 @@ void BasicTest::connectSecondResource() { } void BasicTest::disconnectUser() { + received.clear(); userManager->disconnectUser("user@localhost"); dynamic_cast(factories->getTimerFactory())->setTime(10); loop->processEvents(); diff --git a/src/tests/basictest.h b/src/tests/basictest.h index 554ab5b6..a1d9a0f1 100644 --- a/src/tests/basictest.h +++ b/src/tests/basictest.h @@ -67,8 +67,8 @@ class TestingFactory : public Factory { } // Creates new conversation (NetworkConversation in this case) - Conversation *createConversation(ConversationManager *conversationManager, const std::string &legacyName) { - TestingConversation *nc = new TestingConversation(conversationManager, legacyName); + Conversation *createConversation(ConversationManager *conversationManager, const std::string &legacyName, bool isMuc = false) { + TestingConversation *nc = new TestingConversation(conversationManager, legacyName, isMuc); nc->onMessageToSend.connect(boost::bind(&TestingFactory::handleMessageToSend, this, _1, _2)); return nc; } @@ -225,6 +225,7 @@ class BasicTest : public Swift::XMPPParserClient { user.jid = "user@localhost"; user.uin = "legacyname"; user.password = "password"; + user.vip = 0; storage->setUser(user); } diff --git a/src/tests/conversationmanager.cpp b/src/tests/conversationmanager.cpp index 9aa739fa..0869ec8a 100644 --- a/src/tests/conversationmanager.cpp +++ b/src/tests/conversationmanager.cpp @@ -94,26 +94,35 @@ class ConversationManagerTest : public CPPUNIT_NS :: TestFixture, public BasicTe void handleSubjectMessages() { User *user = userManager->getUser("user@localhost"); - - TestingConversation *conv = new TestingConversation(user->getConversationManager(), "buddy1"); - user->getConversationManager()->addConversation(conv); + TestingConversation *conv = new TestingConversation(user->getConversationManager(), "#room", true); + conv->onMessageToSend.connect(boost::bind(&ConversationManagerTest::handleMessageReceived, this, _1, _2)); + conv->setNickname("nickname"); + conv->addJID("user@localhost/resource"); boost::shared_ptr msg(new Swift::Message()); msg->setSubject("subject"); + msg->setType(Swift::Message::Groupchat); - // Forward it conv->handleMessage(msg); loop->processEvents(); + + // No response, because presence with code 110 has not been sent yet and we must not send + // subject before this one. + CPPUNIT_ASSERT_EQUAL(0, (int) received.size()); + + // this user presence - status code 110 + conv->handleParticipantChanged("nickname", 1, Swift::StatusShow::Away, "my status message"); + loop->processEvents(); - CPPUNIT_ASSERT_EQUAL(1, (int) received.size()); - CPPUNIT_ASSERT(dynamic_cast(getStanza(received[0]))); - CPPUNIT_ASSERT_EQUAL(std::string("subject"), dynamic_cast(getStanza(received[0]))->getSubject()); + CPPUNIT_ASSERT_EQUAL(2, (int) received.size()); + CPPUNIT_ASSERT(dynamic_cast(getStanza(received[1]))); + CPPUNIT_ASSERT_EQUAL(std::string("subject"), dynamic_cast(getStanza(received[1]))->getSubject()); received.clear(); // send response msg->setFrom("user@localhost/resource"); - msg->setTo("buddy1@localhost/bot"); + msg->setTo("#room@localhost"); injectMessage(msg); loop->processEvents(); @@ -127,12 +136,12 @@ class ConversationManagerTest : public CPPUNIT_NS :: TestFixture, public BasicTe void handleNormalMessages() { User *user = userManager->getUser("user@localhost"); - TestingConversation *conv = new TestingConversation(user->getConversationManager(), "buddy1"); + TestingConversation *conv = new TestingConversation(user->getConversationManager(), "buddy1@test"); user->getConversationManager()->addConversation(conv); conv->onMessageToSend.connect(boost::bind(&ConversationManagerTest::handleMessageReceived, this, _1, _2)); boost::shared_ptr msg(new Swift::Message()); - msg->setBody("hi there!"); + msg->setBody("hi there<>!"); // Forward it conv->handleMessage(msg); @@ -140,22 +149,22 @@ class ConversationManagerTest : public CPPUNIT_NS :: TestFixture, public BasicTe CPPUNIT_ASSERT_EQUAL(1, (int) received.size()); CPPUNIT_ASSERT(dynamic_cast(getStanza(received[0]))); - CPPUNIT_ASSERT_EQUAL(std::string("hi there!"), dynamic_cast(getStanza(received[0]))->getBody()); + CPPUNIT_ASSERT_EQUAL(std::string("hi there<>!"), dynamic_cast(getStanza(received[0]))->getBody()); CPPUNIT_ASSERT_EQUAL(std::string("user@localhost"), dynamic_cast(getStanza(received[0]))->getTo().toString()); - CPPUNIT_ASSERT_EQUAL(std::string("buddy1@localhost/bot"), dynamic_cast(getStanza(received[0]))->getFrom().toString()); + CPPUNIT_ASSERT_EQUAL(std::string("buddy1\\40test@localhost/bot"), dynamic_cast(getStanza(received[0]))->getFrom().toString()); received.clear(); // send response msg->setFrom("user@localhost/resource"); - msg->setTo("buddy1@localhost/bot"); - msg->setBody("response!"); + msg->setTo("buddy1\\40test@localhost/bot"); + msg->setBody("response<>!"); injectMessage(msg); loop->processEvents(); CPPUNIT_ASSERT_EQUAL(0, (int) received.size()); CPPUNIT_ASSERT(m_msg); - CPPUNIT_ASSERT_EQUAL(std::string("response!"), m_msg->getBody()); + CPPUNIT_ASSERT_EQUAL(std::string("response<>!"), m_msg->getBody()); // send another message from legacy network, should be sent to user@localhost/resource now boost::shared_ptr msg2(new Swift::Message()); @@ -169,20 +178,28 @@ class ConversationManagerTest : public CPPUNIT_NS :: TestFixture, public BasicTe CPPUNIT_ASSERT(dynamic_cast(getStanza(received[0]))); CPPUNIT_ASSERT_EQUAL(std::string("hi there!"), dynamic_cast(getStanza(received[0]))->getBody()); CPPUNIT_ASSERT_EQUAL(std::string("user@localhost/resource"), dynamic_cast(getStanza(received[0]))->getTo().toString()); - CPPUNIT_ASSERT_EQUAL(std::string("buddy1@localhost/bot"), dynamic_cast(getStanza(received[0]))->getFrom().toString()); + CPPUNIT_ASSERT_EQUAL(std::string("buddy1\\40test@localhost/bot"), dynamic_cast(getStanza(received[0]))->getFrom().toString()); received.clear(); + // disable jid_escaping + std::istringstream ifs("service.server_mode = 1\nservice.jid_escaping=0\nservice.jid=localhost\nservice.more_resources=1\n"); + cfg->load(ifs); + // and now to bare JID again... user->getConversationManager()->resetResources(); conv->handleMessage(msg2); loop->processEvents(); + + // enable jid_escaping again + std::istringstream ifs2("service.server_mode = 1\nservice.jid_escaping=1\nservice.jid=localhost\nservice.more_resources=1\n"); + cfg->load(ifs2); CPPUNIT_ASSERT_EQUAL(1, (int) received.size()); CPPUNIT_ASSERT(dynamic_cast(getStanza(received[0]))); CPPUNIT_ASSERT_EQUAL(std::string("hi there!"), dynamic_cast(getStanza(received[0]))->getBody()); CPPUNIT_ASSERT_EQUAL(std::string("user@localhost"), dynamic_cast(getStanza(received[0]))->getTo().toString()); - CPPUNIT_ASSERT_EQUAL(std::string("buddy1@localhost/bot"), dynamic_cast(getStanza(received[0]))->getFrom().toString()); + CPPUNIT_ASSERT_EQUAL(std::string("buddy1%test@localhost/bot"), dynamic_cast(getStanza(received[0]))->getFrom().toString()); received.clear(); } diff --git a/src/tests/discoitemsresponder.cpp b/src/tests/discoitemsresponder.cpp new file mode 100644 index 00000000..dfdf3619 --- /dev/null +++ b/src/tests/discoitemsresponder.cpp @@ -0,0 +1,99 @@ +#include "transport/userregistry.h" +#include "transport/userregistration.h" +#include "transport/config.h" +#include "transport/storagebackend.h" +#include "transport/user.h" +#include "transport/transport.h" +#include "transport/conversation.h" +#include "transport/usermanager.h" +#include "transport/localbuddy.h" +#include "transport/settingsadhoccommand.h" +#include "transport/adhocmanager.h" +#include +#include +#include +#include +#include +#include +#include +#include "Swiften/Server/ServerStanzaChannel.h" +#include "Swiften/Server/ServerFromClientSession.h" +#include "Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h" +#include "basictest.h" + +using namespace Transport; + +class DiscoItemsResponderTest : public CPPUNIT_NS :: TestFixture, public BasicTest { + CPPUNIT_TEST_SUITE(DiscoItemsResponderTest); + CPPUNIT_TEST(roomList); + CPPUNIT_TEST(roomInfo); + CPPUNIT_TEST(clearRooms); + CPPUNIT_TEST_SUITE_END(); + + public: + + void setUp (void) { + setMeUp(); + } + + void tearDown (void) { + received.clear(); + tearMeDown(); + } + + void roomList() { + itemsResponder->addRoom("#room@localhost", "#room"); + + boost::shared_ptr payload(new Swift::DiscoItems()); + boost::shared_ptr iq = Swift::IQ::createRequest(Swift::IQ::Get, Swift::JID("localhost"), "id", payload); + iq->setFrom("user@localhost"); + injectIQ(iq); + loop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, (int) received.size()); + CPPUNIT_ASSERT(dynamic_cast(getStanza(received[0]))); + CPPUNIT_ASSERT_EQUAL(Swift::IQ::Result, dynamic_cast(getStanza(received[0]))->getType()); + CPPUNIT_ASSERT(getStanza(received[0])->getPayload()); + CPPUNIT_ASSERT_EQUAL(std::string("#room@localhost"), getStanza(received[0])->getPayload()->getItems()[0].getJID().toString()); + CPPUNIT_ASSERT_EQUAL(std::string("#room"), getStanza(received[0])->getPayload()->getItems()[0].getName()); + } + + void roomInfo() { + itemsResponder->addRoom("#room@localhost", "#room"); + + boost::shared_ptr payload(new Swift::DiscoInfo()); + boost::shared_ptr iq = Swift::IQ::createRequest(Swift::IQ::Get, Swift::JID("localhost"), "id", payload); + iq->setFrom("user@localhost"); + iq->setTo("#room@localhost"); + injectIQ(iq); + loop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, (int) received.size()); + CPPUNIT_ASSERT(dynamic_cast(getStanza(received[0]))); + CPPUNIT_ASSERT_EQUAL(Swift::IQ::Result, dynamic_cast(getStanza(received[0]))->getType()); + CPPUNIT_ASSERT(getStanza(received[0])->getPayload()); + CPPUNIT_ASSERT_EQUAL(std::string("#room"), getStanza(received[0])->getPayload()->getIdentities()[0].getName()); + CPPUNIT_ASSERT_EQUAL(std::string("conference"), getStanza(received[0])->getPayload()->getIdentities()[0].getCategory()); + CPPUNIT_ASSERT_EQUAL(std::string("text"), getStanza(received[0])->getPayload()->getIdentities()[0].getType()); + } + + void clearRooms() { + itemsResponder->addRoom("#room@localhost", "#room"); + itemsResponder->clearRooms(); + + boost::shared_ptr payload(new Swift::DiscoItems()); + boost::shared_ptr iq = Swift::IQ::createRequest(Swift::IQ::Get, Swift::JID("localhost"), "id", payload); + iq->setFrom("user@localhost"); + injectIQ(iq); + loop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, (int) received.size()); + CPPUNIT_ASSERT(dynamic_cast(getStanza(received[0]))); + CPPUNIT_ASSERT_EQUAL(Swift::IQ::Result, dynamic_cast(getStanza(received[0]))->getType()); + CPPUNIT_ASSERT(getStanza(received[0])->getPayload()); + CPPUNIT_ASSERT(getStanza(received[0])->getPayload()->getItems().empty()); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION (DiscoItemsResponderTest); diff --git a/src/tests/localbuddy.cpp b/src/tests/localbuddy.cpp index fdc6fb87..3a0ee795 100644 --- a/src/tests/localbuddy.cpp +++ b/src/tests/localbuddy.cpp @@ -25,6 +25,7 @@ class LocalBuddyTest : public CPPUNIT_NS :: TestFixture, public BasicTest { CPPUNIT_TEST(createWithInvalidName); CPPUNIT_TEST(buddyFlagsFromJID); CPPUNIT_TEST(JIDToLegacyName); + CPPUNIT_TEST(getSafeName); CPPUNIT_TEST(handleBuddyChanged); CPPUNIT_TEST(setAlias); CPPUNIT_TEST_SUITE_END(); @@ -70,6 +71,20 @@ class LocalBuddyTest : public CPPUNIT_NS :: TestFixture, public BasicTest { CPPUNIT_ASSERT_EQUAL(std::string("hanzz@test"), Buddy::JIDToLegacyName("hanzz%test@localhost/bot")); } + void getSafeName() { + User *user = userManager->getUser("user@localhost"); + CPPUNIT_ASSERT(user); + + std::vector grp; + grp.push_back("group1"); + LocalBuddy *buddy = new LocalBuddy(user->getRosterManager(), -1, "buddy1@test", "Buddy 1", grp, BUDDY_JID_ESCAPING); + + CPPUNIT_ASSERT_EQUAL(std::string("buddy1\\40test"), buddy->getSafeName()); + + buddy->setFlags(BUDDY_NO_FLAG); + CPPUNIT_ASSERT_EQUAL(std::string("buddy1%test"), buddy->getSafeName()); + } + void buddyFlagsFromJID() { CPPUNIT_ASSERT_EQUAL(BUDDY_JID_ESCAPING, Buddy::buddyFlagsFromJID("hanzz\\40test@localhost/bot")); CPPUNIT_ASSERT_EQUAL(BUDDY_NO_FLAG, Buddy::buddyFlagsFromJID("hanzz%test@localhost/bot")); diff --git a/src/tests/main.cpp b/src/tests/main.cpp index aea740f0..218d66fb 100644 --- a/src/tests/main.cpp +++ b/src/tests/main.cpp @@ -22,6 +22,16 @@ int main (int argc, char* argv[]) root->addAppender(new FileAppender(new PatternLayout("%d %-5p %c: %m%n"), "libtransport_test.log", false)); #endif + std::vector testsToRun; + for (int i = 1; i < argc; ++i) { + std::string param(argv[i]); + testsToRun.push_back(param); + } + + if (testsToRun.empty()) { + testsToRun.push_back(""); + } + // informs test-listener about testresults CPPUNIT_NS :: TestResult testresult; @@ -36,7 +46,15 @@ int main (int argc, char* argv[]) // insert test-suite at test-runner by registry CPPUNIT_NS :: TestRunner testrunner; testrunner.addTest (CPPUNIT_NS :: TestFactoryRegistry :: getRegistry ().makeTest ()); - testrunner.run (testresult); + for (std::vector::const_iterator i = testsToRun.begin(); i != testsToRun.end(); ++i) { + try { + testrunner.run(testresult, *i); + } + catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return -1; + } + } // output results in compiler-format CPPUNIT_NS :: CompilerOutputter compileroutputter (&collectedresults, std::cerr); diff --git a/src/tests/networkpluginserver.cpp b/src/tests/networkpluginserver.cpp new file mode 100644 index 00000000..d9e1655d --- /dev/null +++ b/src/tests/networkpluginserver.cpp @@ -0,0 +1,109 @@ +#include "transport/userregistry.h" +#include "transport/userregistration.h" +#include "transport/config.h" +#include "transport/storagebackend.h" +#include "transport/user.h" +#include "transport/transport.h" +#include "transport/conversation.h" +#include "transport/usermanager.h" +#include "transport/localbuddy.h" +#include "transport/settingsadhoccommand.h" +#include "transport/adhocmanager.h" +#include "transport/protocol.pb.h" +#include "transport/networkpluginserver.h" +#include +#include +#include +#include +#include +#include +#include +#include "Swiften/Server/ServerStanzaChannel.h" +#include "Swiften/Server/ServerFromClientSession.h" +#include "Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h" +#include "basictest.h" + +using namespace Transport; + +class NetworkPluginServerTest : public CPPUNIT_NS :: TestFixture, public BasicTest { + CPPUNIT_TEST_SUITE(NetworkPluginServerTest); + CPPUNIT_TEST(handleBuddyChangedPayload); + CPPUNIT_TEST(handleBuddyChangedPayloadNoEscaping); + CPPUNIT_TEST(handleBuddyChangedPayloadUserContactInRoster); + CPPUNIT_TEST_SUITE_END(); + + public: + NetworkPluginServer *serv; + + void setUp (void) { + setMeUp(); + + serv = new NetworkPluginServer(component, cfg, userManager, NULL, NULL); + connectUser(); + received.clear(); + } + + void tearDown (void) { + received.clear(); + disconnectUser(); + delete serv; + tearMeDown(); + } + + void handleBuddyChangedPayload() { + User *user = userManager->getUser("user@localhost"); + + pbnetwork::Buddy buddy; + buddy.set_username("user@localhost"); + buddy.set_buddyname("buddy1@test"); + + std::string message; + buddy.SerializeToString(&message); + + serv->handleBuddyChangedPayload(message); + CPPUNIT_ASSERT_EQUAL(1, (int) received.size()); + Swift::RosterPayload::ref payload1 = getStanza(received[0])->getPayload(); + CPPUNIT_ASSERT_EQUAL(1, (int) payload1->getItems().size()); + Swift::RosterItemPayload item = payload1->getItems()[0]; + CPPUNIT_ASSERT_EQUAL(std::string("buddy1\\40test@localhost"), item.getJID().toString()); + } + + void handleBuddyChangedPayloadNoEscaping() { + std::istringstream ifs("service.server_mode = 1\nservice.jid_escaping=0\nservice.jid=localhost\nservice.more_resources=1\n"); + cfg->load(ifs); + User *user = userManager->getUser("user@localhost"); + + pbnetwork::Buddy buddy; + buddy.set_username("user@localhost"); + buddy.set_buddyname("buddy1@test"); + + std::string message; + buddy.SerializeToString(&message); + + serv->handleBuddyChangedPayload(message); + CPPUNIT_ASSERT_EQUAL(1, (int) received.size()); + Swift::RosterPayload::ref payload1 = getStanza(received[0])->getPayload(); + CPPUNIT_ASSERT_EQUAL(1, (int) payload1->getItems().size()); + Swift::RosterItemPayload item = payload1->getItems()[0]; + CPPUNIT_ASSERT_EQUAL(std::string("buddy1%test@localhost"), item.getJID().toString()); + + std::istringstream ifs2("service.server_mode = 1\nservice.jid_escaping=1\nservice.jid=localhost\nservice.more_resources=1\n"); + cfg->load(ifs2); + } + + void handleBuddyChangedPayloadUserContactInRoster() { + User *user = userManager->getUser("user@localhost"); + + pbnetwork::Buddy buddy; + buddy.set_username("user@localhost"); + buddy.set_buddyname("user"); + + std::string message; + buddy.SerializeToString(&message); + + serv->handleBuddyChangedPayload(message); + CPPUNIT_ASSERT_EQUAL(0, (int) received.size()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION (NetworkPluginServerTest); diff --git a/src/tests/rosterresponder.cpp b/src/tests/rosterresponder.cpp new file mode 100644 index 00000000..2c6cf86a --- /dev/null +++ b/src/tests/rosterresponder.cpp @@ -0,0 +1,86 @@ +#include "transport/userregistry.h" +#include "transport/config.h" +#include "transport/storagebackend.h" +#include "transport/user.h" +#include "transport/transport.h" +#include "transport/conversation.h" +#include "transport/rosterresponder.h" +#include "transport/usermanager.h" +#include "transport/localbuddy.h" +#include +#include +#include +#include +#include +#include +#include +#include "Swiften/Server/ServerStanzaChannel.h" +#include "Swiften/Server/ServerFromClientSession.h" +#include "Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h" +#include "basictest.h" + +using namespace Transport; + +class RosterResponderTest : public CPPUNIT_NS :: TestFixture, public BasicTest { + CPPUNIT_TEST_SUITE(RosterResponderTest); + CPPUNIT_TEST(addEmptyBuddy); + CPPUNIT_TEST_SUITE_END(); + + public: + RosterResponder *m_rosterResponder; + std::string m_buddy; + + void setUp (void) { + m_buddy = "none"; + setMeUp(); + connectUser(); + + m_rosterResponder = new RosterResponder(component->getIQRouter(), userManager); + m_rosterResponder->onBuddyAdded.connect(boost::bind(&RosterResponderTest::handleBuddyAdded, this, _1, _2)); + m_rosterResponder->onBuddyRemoved.connect(boost::bind(&RosterResponderTest::handleBuddyRemoved, this, _1)); + m_rosterResponder->onBuddyUpdated.connect(boost::bind(&RosterResponderTest::handleBuddyUpdated, this, _1, _2)); + m_rosterResponder->start(); + + received.clear(); + } + + void tearDown (void) { + received.clear(); + disconnectUser(); + tearMeDown(); + } + + void handleBuddyAdded(Buddy *buddy, const Swift::RosterItemPayload &item) { + m_buddy = buddy->getName(); + } + + void handleBuddyRemoved(Buddy *buddy) { + m_buddy = buddy->getName(); + } + + void handleBuddyUpdated(Buddy *buddy, const Swift::RosterItemPayload &item) { + m_buddy = buddy->getName(); + } + + void addEmptyBuddy() { + Swift::RosterPayload::ref p = Swift::RosterPayload::ref(new Swift::RosterPayload()); + Swift::RosterItemPayload item; + item.setJID("icq.localhost"); + item.setSubscription(Swift::RosterItemPayload::Both); + + p->addItem(item); + Swift::SetRosterRequest::ref request = Swift::SetRosterRequest::create(p, "user@localhost", component->getIQRouter()); + + boost::shared_ptr iq(new Swift::IQ(Swift::IQ::Set)); + iq->setTo("icq.localhost"); + iq->setFrom("user@localhost"); + iq->addPayload(p); + iq->setID("123"); + injectIQ(iq); + + CPPUNIT_ASSERT_EQUAL(std::string("none"), m_buddy); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION (RosterResponderTest); diff --git a/src/tests/settingsadhoccommand.cpp b/src/tests/settingsadhoccommand.cpp index e3e14d9c..3e9602be 100644 --- a/src/tests/settingsadhoccommand.cpp +++ b/src/tests/settingsadhoccommand.cpp @@ -26,6 +26,7 @@ using namespace Transport; class SettingsAdHocCommandTest : public CPPUNIT_NS :: TestFixture, public BasicTest { CPPUNIT_TEST_SUITE(SettingsAdHocCommandTest); CPPUNIT_TEST(getItems); + CPPUNIT_TEST(getInfo); CPPUNIT_TEST(execute); CPPUNIT_TEST(executeBadSessionID); CPPUNIT_TEST(executeNotRegistered); @@ -70,6 +71,22 @@ class SettingsAdHocCommandTest : public CPPUNIT_NS :: TestFixture, public BasicT CPPUNIT_ASSERT_EQUAL(std::string("settings"), getStanza(received[0])->getPayload()->getItems()[0].getNode()); } + void getInfo() { + boost::shared_ptr payload(new Swift::DiscoInfo()); + payload->setNode("settings"); + boost::shared_ptr iq = Swift::IQ::createRequest(Swift::IQ::Get, Swift::JID("localhost"), "id", payload); + iq->setFrom("user@localhost"); + injectIQ(iq); + loop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, (int) received.size()); + CPPUNIT_ASSERT(dynamic_cast(getStanza(received[0]))); + CPPUNIT_ASSERT_EQUAL(Swift::IQ::Result, dynamic_cast(getStanza(received[0]))->getType()); + CPPUNIT_ASSERT(getStanza(received[0])->getPayload()); + CPPUNIT_ASSERT_EQUAL(std::string("automation"), getStanza(received[0])->getPayload()->getIdentities()[0].getCategory()); + CPPUNIT_ASSERT_EQUAL(std::string("command-node"), getStanza(received[0])->getPayload()->getIdentities()[0].getType()); + } + void executeNotRegistered() { boost::shared_ptr payload(new Swift::Command("settings")); boost::shared_ptr iq = Swift::IQ::createRequest(Swift::IQ::Set, Swift::JID("localhost"), "id", payload); diff --git a/src/tests/stringtreeparser.cpp b/src/tests/stringtreeparser.cpp new file mode 100644 index 00000000..e25b24d8 --- /dev/null +++ b/src/tests/stringtreeparser.cpp @@ -0,0 +1,47 @@ +#include "transport/userregistry.h" +#include "transport/config.h" +#include "transport/storagebackend.h" +#include "transport/user.h" +#include "transport/transport.h" +#include "transport/storagebackend.h" +#include "transport/conversation.h" +#include "transport/usermanager.h" +#include "transport/localbuddy.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "Swiften/Server/ServerStanzaChannel.h" +#include "Swiften/Server/ServerFromClientSession.h" +#include "Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h" +#include "basictest.h" + +#include "transport/util.h" + +using namespace Transport; + +class StringTreeParserTest : public CPPUNIT_NS :: TestFixture{ + CPPUNIT_TEST_SUITE(StringTreeParserTest); + CPPUNIT_TEST(parseEscapedCharacters); + CPPUNIT_TEST_SUITE_END(); + + public: + void setUp (void) { + } + + void tearDown (void) { + + } + + void parseEscapedCharacters() { + Swift::ParserElement::ref root = Swift::StringTreeParser::parse("<test>"); + CPPUNIT_ASSERT_EQUAL(std::string(""), root->getText()); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION (StringTreeParserTest); diff --git a/src/tests/user.cpp b/src/tests/user.cpp index bbafedbb..054cc100 100644 --- a/src/tests/user.cpp +++ b/src/tests/user.cpp @@ -30,8 +30,10 @@ class UserTest : public CPPUNIT_NS :: TestFixture, public BasicTest { CPPUNIT_TEST(handlePresenceLeaveRoomTwoResources); CPPUNIT_TEST(handlePresenceLeaveRoomTwoResourcesOneDisconnects); CPPUNIT_TEST(leaveJoinedRoom); + CPPUNIT_TEST(joinRoomBeforeConnected); CPPUNIT_TEST(handleDisconnected); CPPUNIT_TEST(handleDisconnectedReconnect); + CPPUNIT_TEST(joinRoomHandleDisconnectedRejoin); CPPUNIT_TEST_SUITE_END(); public: @@ -318,6 +320,56 @@ class UserTest : public CPPUNIT_NS :: TestFixture, public BasicTest { user = userManager->getUser("user@localhost"); CPPUNIT_ASSERT(user); CPPUNIT_ASSERT(readyToConnect); + + Swift::Presence::ref response = Swift::Presence::create(); + response->setTo("localhost"); + response->setFrom("user@localhost/resource"); + injectPresence(response); + loop->processEvents(); + } + + void joinRoomBeforeConnected() { + User *user = userManager->getUser("user@localhost"); + user->setConnected(false); + + Swift::Presence::ref response = Swift::Presence::create(); + response->setTo("#room@localhost/hanzz"); + response->setFrom("user@localhost/resource"); + + Swift::MUCPayload *payload = new Swift::MUCPayload(); + payload->setPassword("password"); + response->addPayload(boost::shared_ptr(payload)); + injectPresence(response); + loop->processEvents(); + + // no presence received in server mode, just disco#info + CPPUNIT_ASSERT_EQUAL(1, (int) received.size()); + CPPUNIT_ASSERT(getStanza(received[0])->getPayload()); + + CPPUNIT_ASSERT_EQUAL(std::string(""), room); + CPPUNIT_ASSERT_EQUAL(std::string(""), roomNickname); + CPPUNIT_ASSERT_EQUAL(std::string(""), roomPassword); + + user->setConnected(true); + CPPUNIT_ASSERT_EQUAL(std::string("#room"), room); + CPPUNIT_ASSERT_EQUAL(std::string("hanzz"), roomNickname); + CPPUNIT_ASSERT_EQUAL(std::string("password"), roomPassword); + } + + void joinRoomHandleDisconnectedRejoin() { + User *user = userManager->getUser("user@localhost"); + handlePresenceJoinRoom(); + handleDisconnectedReconnect(); + room = ""; + roomNickname = ""; + roomPassword = ""; + received.clear(); + user->setConnected(true); + + CPPUNIT_ASSERT_EQUAL(std::string("#room"), room); + CPPUNIT_ASSERT_EQUAL(std::string("hanzz"), roomNickname); + CPPUNIT_ASSERT_EQUAL(std::string("password"), roomPassword); + CPPUNIT_ASSERT_EQUAL(0, (int) received.size()); } }; diff --git a/src/tests/usermanager.cpp b/src/tests/usermanager.cpp index 1f03575d..8f59ab8f 100644 --- a/src/tests/usermanager.cpp +++ b/src/tests/usermanager.cpp @@ -27,6 +27,7 @@ class UserManagerTest : public CPPUNIT_NS :: TestFixture, public BasicTest { CPPUNIT_TEST(connectUserTransportDisabled); CPPUNIT_TEST(connectUserRegistrationNeeded); CPPUNIT_TEST(connectUserRegistrationNeededRegistered); + CPPUNIT_TEST(connectUserVipOnlyNonVip); CPPUNIT_TEST(handleProbePresence); CPPUNIT_TEST(disconnectUser); CPPUNIT_TEST_SUITE_END(); @@ -71,7 +72,32 @@ class UserManagerTest : public CPPUNIT_NS :: TestFixture, public BasicTest { CPPUNIT_ASSERT(!streamEnded); } + void connectUserVipOnlyNonVip() { + addUser(); + std::istringstream ifs("service.server_mode = 1\nservice.jid_escaping=0\nservice.jid=localhost\nservice.vip_only=1\nservice.vip_message=Ahoj\n"); + cfg->load(ifs); + CPPUNIT_ASSERT_EQUAL(0, userManager->getUserCount()); + userRegistry->isValidUserPassword(Swift::JID("user@localhost/resource"), serverFromClientSession.get(), Swift::createSafeByteArray("password")); + loop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(3, (int) received.size()); + CPPUNIT_ASSERT(dynamic_cast(getStanza(received[1]))); + CPPUNIT_ASSERT_EQUAL(std::string("Ahoj"), dynamic_cast(getStanza(received[1]))->getBody()); + CPPUNIT_ASSERT_EQUAL(std::string("user@localhost/resource"), dynamic_cast(getStanza(received[1]))->getTo().toString()); + CPPUNIT_ASSERT_EQUAL(std::string("localhost"), dynamic_cast(getStanza(received[1]))->getFrom().toString()); + + CPPUNIT_ASSERT_EQUAL(0, userManager->getUserCount()); + CPPUNIT_ASSERT(streamEnded); + std::istringstream ifs2("service.server_mode = 1\nservice.jid_escaping=1\nservice.jid=localhost\nservice.more_resources=1\n"); + cfg->load(ifs2); + } + void handleProbePresence() { + UserInfo info; + info.id = 1; + info.jid = "user@localhost"; + storage->setUser(info); + Swift::Presence::ref response = Swift::Presence::create(); response->setTo("localhost"); response->setFrom("user@localhost/resource"); @@ -79,12 +105,63 @@ class UserManagerTest : public CPPUNIT_NS :: TestFixture, public BasicTest { dynamic_cast(component->getStanzaChannel())->onPresenceReceived(response); loop->processEvents(); - CPPUNIT_ASSERT_EQUAL(2, (int) received.size()); + CPPUNIT_ASSERT_EQUAL(3, (int) received.size()); CPPUNIT_ASSERT(getStanza(received[0])->getPayload()); Swift::Presence *presence = dynamic_cast(getStanza(received[1])); CPPUNIT_ASSERT(presence); CPPUNIT_ASSERT_EQUAL(Swift::Presence::Unavailable, presence->getType()); + + presence = dynamic_cast(getStanza(received[2])); + CPPUNIT_ASSERT(presence); + CPPUNIT_ASSERT_EQUAL(Swift::Presence::Probe, presence->getType()); + + received.clear(); + response = Swift::Presence::create(); + response->setTo("localhost"); + response->setFrom("user@localhost"); + response->setType(Swift::Presence::Unsubscribed); + dynamic_cast(component->getStanzaChannel())->onPresenceReceived(response); + loop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(2, (int) received.size()); + presence = dynamic_cast(getStanza(received[1])); + CPPUNIT_ASSERT(presence); + CPPUNIT_ASSERT_EQUAL(Swift::Presence::Subscribe, presence->getType()); + + received.clear(); + response = Swift::Presence::create(); + response->setTo("localhost"); + response->setFrom("user@localhost"); + response->setType(Swift::Presence::Error); + dynamic_cast(component->getStanzaChannel())->onPresenceReceived(response); + loop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(0, (int) received.size()); + + response = Swift::Presence::create(); + response->setTo("localhost"); + response->setFrom("user@localhost"); + response->setType(Swift::Presence::Error); + response->addPayload(boost::shared_ptr(new Swift::ErrorPayload(Swift::ErrorPayload::SubscriptionRequired))); + dynamic_cast(component->getStanzaChannel())->onPresenceReceived(response); + loop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, (int) received.size()); + presence = dynamic_cast(getStanza(received[0])); + CPPUNIT_ASSERT(presence); + CPPUNIT_ASSERT_EQUAL(Swift::Presence::Subscribe, presence->getType()); + + storage->removeUser(1); + received.clear(); + response = Swift::Presence::create(); + response->setTo("localhost"); + response->setFrom("user@localhost"); + response->setType(Swift::Presence::Unsubscribed); + dynamic_cast(component->getStanzaChannel())->onPresenceReceived(response); + loop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(1, (int) received.size()); } void connectTwoResources() { diff --git a/src/tests/util.cpp b/src/tests/util.cpp index f5c66819..c091f2ec 100644 --- a/src/tests/util.cpp +++ b/src/tests/util.cpp @@ -3,6 +3,7 @@ #include "transport/storagebackend.h" #include "transport/user.h" #include "transport/transport.h" +#include "transport/storagebackend.h" #include "transport/conversation.h" #include "transport/usermanager.h" #include "transport/localbuddy.h" @@ -36,8 +37,8 @@ class UtilTest : public CPPUNIT_NS :: TestFixture{ } void encryptDecryptPassword() { - std::string encrypted = Util::encryptPassword("password", "key"); - CPPUNIT_ASSERT_EQUAL(std::string("password"), Util::decryptPassword(encrypted, "key")); + std::string encrypted = StorageBackend::encryptPassword("password", "key"); + CPPUNIT_ASSERT_EQUAL(std::string("password"), StorageBackend::decryptPassword(encrypted, "key")); } }; diff --git a/src/transport.cpp b/src/transport.cpp index 99ef21d9..f4e0480b 100644 --- a/src/transport.cpp +++ b/src/transport.cpp @@ -26,7 +26,6 @@ #include "transport/factory.h" #include "transport/userregistry.h" #include "transport/logging.h" -#include "discoinforesponder.h" #include "storageparser.h" #ifdef _WIN32 #include @@ -158,8 +157,7 @@ Component::Component(Swift::EventLoop *loop, Swift::NetworkFactories *factories, m_presenceOracle = new Transport::PresenceOracle(m_stanzaChannel); m_presenceOracle->onPresenceChange.connect(bind(&Component::handlePresence, this, _1)); - m_discoInfoResponder = new DiscoInfoResponder(m_iqRouter, m_config); - m_discoInfoResponder->start(); + // // m_registerHandler = new SpectrumRegisterHandler(m_component); @@ -171,7 +169,6 @@ Component::~Component() { delete m_entityCapsManager; delete m_capsManager; delete m_capsMemoryStorage; - delete m_discoInfoResponder; if (m_component) delete m_component; if (m_server) { @@ -188,19 +185,6 @@ Transport::PresenceOracle *Component::getPresenceOracle() { return m_presenceOracle; } -void Component::setTransportFeatures(std::list &features) { - m_discoInfoResponder->setTransportFeatures(features); -} - -Swift::CapsInfo &Component::getBuddyCapsInfo() { - return m_discoInfoResponder->getBuddyCapsInfo(); -} - -void Component::setBuddyFeatures(std::list &features) { - // TODO: handle caps change - m_discoInfoResponder->setBuddyFeatures(features); -} - void Component::start() { if (m_component && !m_component->isAvailable()) { LOG4CXX_INFO(logger, "Connecting XMPP server " << CONFIG_STRING(m_config, "service.server") << " port " << CONFIG_INT(m_config, "service.port")); @@ -297,9 +281,16 @@ void Component::handlePresence(Swift::Presence::ref presence) { return; } - if (presence->getType() == Presence::Error) { - return; - } + switch (presence->getType()) { + case Presence::Error: + case Presence::Subscribe: + case Presence::Subscribed: + case Presence::Unsubscribe: + case Presence::Unsubscribed: + return; + default: + break; + }; // check if we have this client's capabilities and ask for them if (presence->getType() != Swift::Presence::Unavailable) { diff --git a/src/user.cpp b/src/user.cpp index f791b992..558138a2 100644 --- a/src/user.cpp +++ b/src/user.cpp @@ -176,14 +176,21 @@ void User::setConnected(bool connected) { updateLastActivity(); sendCurrentPresence(); + + if (m_connected) { + BOOST_FOREACH(Swift::Presence::ref &presence, m_joinedRooms) { + handlePresence(presence, true); + } + } } -void User::handlePresence(Swift::Presence::ref presence) { +void User::handlePresence(Swift::Presence::ref presence, bool forceJoin) { int currentResourcesCount = m_presenceOracle->getAllPresence(m_jid).size(); m_conversationManager->resetResources(); + LOG4CXX_INFO(logger, "PRESENCE " << presence->getFrom().toString() << " " << presence->getTo().toString()); if (!m_connected) { // we are not connected to legacy network, so we should do it when disco#info arrive :) if (m_readyForConnect == false) { @@ -229,6 +236,13 @@ void User::handlePresence(Swift::Presence::ref presence) { LOG4CXX_INFO(logger, m_jid.toString() << ": Going to left room " << room); onRoomLeft(room); + BOOST_FOREACH(Swift::Presence::ref &p, m_joinedRooms) { + if (p->getTo() == presence->getTo()) { + m_joinedRooms.remove(p); + break; + } + } + if (conv) { m_conversationManager->removeConversation(conv); delete conv; @@ -241,7 +255,13 @@ void User::handlePresence(Swift::Presence::ref presence) { m_readyForConnect = true; onReadyToConnect(); } + std::string room = Buddy::JIDToLegacyName(presence->getTo()); + std::string password = ""; + if (presence->getPayload() != NULL) { + password = presence->getPayload()->getPassword() ? *presence->getPayload()->getPassword() : ""; + } + Conversation *conv = m_conversationManager->getConversation(room); if (conv != NULL) { if (std::find(conv->getJIDs().begin(), conv->getJIDs().end(), presence->getFrom()) != conv->getJIDs().end()) { @@ -251,14 +271,35 @@ void User::handlePresence(Swift::Presence::ref presence) { conv->addJID(presence->getFrom()); conv->sendParticipants(presence->getFrom()); } + + if (forceJoin) { + onRoomJoined(presence->getFrom(), room, presence->getTo().getResource(), password); + } + return; + } + + bool isInJoined = false; + BOOST_FOREACH(Swift::Presence::ref &p, m_joinedRooms) { + if (p->getTo() == presence->getTo()) { + isInJoined = true; + } + } + if (!isInJoined) { + m_joinedRooms.push_back(presence); + } + + if (!m_connected) { + LOG4CXX_INFO(logger, m_jid.toString() << ": Joining room " << room << " postponed, because use is not connected to legacy network yet."); return; } LOG4CXX_INFO(logger, m_jid.toString() << ": Going to join room " << room << " as " << presence->getTo().getResource()); - std::string password = ""; - if (presence->getPayload() != NULL) { - password = presence->getPayload()->getPassword() ? *presence->getPayload()->getPassword() : ""; - } + + conv = m_component->getFactory()->createConversation(m_conversationManager, room, true); + m_conversationManager->addConversation(conv); + conv->setNickname(presence->getTo().getResource()); + conv->addJID(presence->getFrom()); + onRoomJoined(presence->getFrom(), room, presence->getTo().getResource(), password); } return; diff --git a/src/usermanager.cpp b/src/usermanager.cpp index 98c75dcf..fd4305ce 100644 --- a/src/usermanager.cpp +++ b/src/usermanager.cpp @@ -26,6 +26,7 @@ #include "transport/rostermanager.h" #include "transport/userregistry.h" #include "transport/logging.h" +#include "transport/discoitemsresponder.h" #include "storageresponder.h" #include "Swiften/Swiften.h" @@ -40,7 +41,7 @@ namespace Transport { DEFINE_LOGGER(logger, "UserManager"); -UserManager::UserManager(Component *component, UserRegistry *userRegistry, StorageBackend *storageBackend) { +UserManager::UserManager(Component *component, UserRegistry *userRegistry, DiscoItemsResponder *discoItemsResponder, StorageBackend *storageBackend) { m_cachedUser = NULL; m_onlineBuddies = 0; m_sentToXMPP = 0; @@ -49,6 +50,7 @@ UserManager::UserManager(Component *component, UserRegistry *userRegistry, Stora m_storageBackend = storageBackend; m_storageResponder = NULL; m_userRegistry = userRegistry; + m_discoItemsResponder = discoItemsResponder; if (m_storageBackend) { m_storageResponder = new StorageResponder(component->getIQRouter(), m_storageBackend, this); @@ -182,14 +184,27 @@ void UserManager::handlePresence(Swift::Presence::ref presence) { } } + UserInfo res; + bool registered = m_storageBackend ? m_storageBackend->getUser(userkey, res) : false; + // No user and unavailable presence -> answer with unavailable - if (presence->getType() == Swift::Presence::Unavailable) { + if (presence->getType() == Swift::Presence::Unavailable || presence->getType() == Swift::Presence::Probe) { Swift::Presence::ref response = Swift::Presence::create(); response->setTo(presence->getFrom()); response->setFrom(presence->getTo()); response->setType(Swift::Presence::Unavailable); m_component->getStanzaChannel()->sendPresence(response); + // bother him with probe presence, just to be + // sure he is subscribed to us. + if (/*registered && */presence->getType() == Swift::Presence::Probe) { + Swift::Presence::ref response = Swift::Presence::create(); + response->setTo(presence->getFrom()); + response->setFrom(presence->getTo()); + response->setType(Swift::Presence::Probe); + m_component->getStanzaChannel()->sendPresence(response); + } + // Set user offline in database if (m_storageBackend) { UserInfo res; @@ -201,9 +216,6 @@ void UserManager::handlePresence(Swift::Presence::ref presence) { return; } - UserInfo res; - bool registered = m_storageBackend ? m_storageBackend->getUser(userkey, res) : false; - // In server mode, we don't need registration normally, but for networks like IRC // or Twitter where there's no real authorization using password, we have to force // registration otherwise some data (like bookmarked rooms) could leak. @@ -235,7 +247,8 @@ void UserManager::handlePresence(Swift::Presence::ref presence) { // We allow auto_register feature in gateway-mode. This allows IRC user to register // the transport just by joining the room. if (!m_component->inServerMode()) { - if (!registered && CONFIG_BOOL(m_component->getConfig(), "registration.auto_register")) { + if (!registered && (CONFIG_BOOL(m_component->getConfig(), "registration.auto_register") || + !CONFIG_BOOL_DEFAULTED(m_component->getConfig(), "registration.needRegistration", true))) { res.password = ""; res.jid = userkey; @@ -254,7 +267,7 @@ void UserManager::handlePresence(Swift::Presence::ref presence) { registered = m_storageBackend->getUser(userkey, res); } else { - registered = false; + registered = true; } } } @@ -265,6 +278,22 @@ void UserManager::handlePresence(Swift::Presence::ref presence) { return; } + if (CONFIG_BOOL(m_component->getConfig(), "service.vip_only") && res.vip == false) { + if (!CONFIG_STRING(m_component->getConfig(), "service.vip_message").empty()) { + boost::shared_ptr msg(new Swift::Message()); + msg->setBody(CONFIG_STRING(m_component->getConfig(), "service.vip_message")); + msg->setTo(presence->getFrom()); + msg->setFrom(m_component->getJID()); + m_component->getStanzaChannel()->sendMessage(msg); + } + + LOG4CXX_WARN(logger, "Non VIP user " << userkey << " tried to login"); + if (m_component->inServerMode()) { + m_userRegistry->onPasswordInvalid(presence->getFrom()); + } + return; + } + bool transport_enabled = true; if (m_storageBackend) { std::string value = "1"; @@ -370,12 +399,20 @@ void UserManager::handleGeneralPresenceReceived(Swift::Presence::ref presence) { case Swift::Presence::Probe: handleProbePresence(presence); break; + case Swift::Presence::Error: + handleErrorPresence(presence); + break; default: break; }; } void UserManager::handleProbePresence(Swift::Presence::ref presence) { + // Don't let RosterManager to handle presences for us + if (presence->getTo().getNode().empty()) { + return; + } + User *user = getUser(presence->getFrom().toBare().toString()); if (user) { @@ -390,7 +427,34 @@ void UserManager::handleProbePresence(Swift::Presence::ref presence) { } } +void UserManager::handleErrorPresence(Swift::Presence::ref presence) { + // Don't let RosterManager to handle presences for us + if (!presence->getTo().getNode().empty()) { + return; + } + + if (!presence->getPayload()) { + return; + } + + if (presence->getPayload()->getCondition() != Swift::ErrorPayload::SubscriptionRequired) { + return; + } + + std::string userkey = presence->getFrom().toBare().toString(); + UserInfo res; + bool registered = m_storageBackend ? m_storageBackend->getUser(userkey, res) : false; + if (registered) { + Swift::Presence::ref response = Swift::Presence::create(); + response->setFrom(presence->getTo().toBare()); + response->setTo(presence->getFrom().toBare()); + response->setType(Swift::Presence::Subscribe); + m_component->getStanzaChannel()->sendPresence(response); + } +} + void UserManager::handleSubscription(Swift::Presence::ref presence) { + // answer to subscibe for transport itself if (presence->getType() == Swift::Presence::Subscribe && presence->getTo().getNode().empty()) { Swift::Presence::ref response = Swift::Presence::create(); @@ -406,6 +470,19 @@ void UserManager::handleSubscription(Swift::Presence::ref presence) { // m_component->getStanzaChannel()->sendPresence(response); return; } + else if (presence->getType() == Swift::Presence::Unsubscribed && presence->getTo().getNode().empty()) { + std::string userkey = presence->getFrom().toBare().toString(); + UserInfo res; + bool registered = m_storageBackend ? m_storageBackend->getUser(userkey, res) : false; + if (registered) { + Swift::Presence::ref response = Swift::Presence::create(); + response->setFrom(presence->getTo().toBare()); + response->setTo(presence->getFrom().toBare()); + response->setType(Swift::Presence::Subscribe); + m_component->getStanzaChannel()->sendPresence(response); + } + return; + } // Don't let RosterManager to handle presences for us if (presence->getTo().getNode().empty()) { diff --git a/src/userregistration.cpp b/src/userregistration.cpp index a13006ca..4160f9e0 100644 --- a/src/userregistration.cpp +++ b/src/userregistration.cpp @@ -94,7 +94,15 @@ void UserRegistration::handleUnregisterRemoteRosterResponse(boost::shared_ptr roster; m_storageBackend->getBuddies(userInfo.id, roster); for(std::list::iterator u = roster.begin(); u != roster.end() ; u++){ - std::string name = Swift::JID::getEscapedNode((*u).legacyName); + std::string name = (*u).legacyName; + if ((*u).flags & BUDDY_JID_ESCAPING) { + name = Swift::JID::getEscapedNode((*u).legacyName); + } + else { + if (name.find_last_of("@") != std::string::npos) { + name.replace(name.find_last_of("@"), 1, "%"); + } + } Swift::Presence::ref response; response = Swift::Presence::create(); diff --git a/src/util.cpp b/src/util.cpp index 7589f213..b8c3adc5 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -19,6 +19,7 @@ */ #include "transport/util.h" +#include "transport/config.h" #include #include #include @@ -27,6 +28,19 @@ #include #include +#ifndef WIN32 +#include "sys/signal.h" +#include +#include +#include +#include +#include "libgen.h" +#else +#include +#include +#define getpid _getpid +#endif + using namespace boost::filesystem; using namespace boost; @@ -35,6 +49,32 @@ namespace Transport { namespace Util { +void createDirectories(Transport::Config *config, const boost::filesystem::path& ph) { + if (ph.empty() || exists(ph)) { + return; + } + + // First create branch, by calling ourself recursively + createDirectories(config, ph.branch_path()); + + // Now that parent's path exists, create the directory + create_directory(ph); + +#ifndef WIN32 + if (!CONFIG_STRING(config, "service.group").empty() && !CONFIG_STRING(config, "service.user").empty()) { + struct group *gr; + if ((gr = getgrnam(CONFIG_STRING(config, "service.group").c_str())) == NULL) { + std::cerr << "Invalid service.group name " << CONFIG_STRING(config, "service.group") << "\n"; + } + struct passwd *pw; + if ((pw = getpwnam(CONFIG_STRING(config, "service.user").c_str())) == NULL) { + std::cerr << "Invalid service.user name " << CONFIG_STRING(config, "service.user") << "\n"; + } + chown(ph.string().c_str(), pw->pw_uid, gr->gr_gid); + } +#endif +} + void removeEverythingOlderThan(const std::vector &dirs, time_t t) { BOOST_FOREACH(const std::string &dir, dirs) { path p(dir); @@ -58,7 +98,7 @@ void removeEverythingOlderThan(const std::vector &dirs, time_t t) { std::vector nextDirs; nextDirs.push_back(itr->path().string()); removeEverythingOlderThan(nextDirs, t); - if (is_empty(itr->path())) { + if (boost::filesystem::is_empty(itr->path())) { remove_all(itr->path()); } } @@ -77,58 +117,6 @@ void removeEverythingOlderThan(const std::vector &dirs, time_t t) { } } -std::string encryptPassword(const std::string &password, const std::string &key) { - std::string encrypted; - encrypted.resize(password.size()); - for (int i = 0; i < password.size(); i++) { - char c = password[i]; - char keychar = key[i % key.size()]; - c += keychar; - encrypted[i] = c; - } - - encrypted = Swift::Base64::encode(Swift::createByteArray(encrypted)); - return encrypted; -} - -std::string decryptPassword(std::string &encrypted, const std::string &key) { - encrypted = Swift::byteArrayToString(Swift::Base64::decode(encrypted)); - std::string password; - password.resize(encrypted.size()); - for (int i = 0; i < encrypted.size(); i++) { - char c = encrypted[i]; - char keychar = key[i % key.size()]; - c -= keychar; - password[i] = c; - } - - return password; -} - -std::string serializeGroups(const std::vector &groups) { - std::string ret; - BOOST_FOREACH(const std::string &group, groups) { - ret += group + "\n"; - } - if (!ret.empty()) { - ret.erase(ret.end() - 1); - } - return ret; -} - -std::vector deserializeGroups(std::string &groups) { - std::vector ret; - if (groups.empty()) { - return ret; - } - - boost::split(ret, groups, boost::is_any_of("\n")); - if (ret.back().empty()) { - ret.erase(ret.end() - 1); - } - return ret; -} - int getRandomPort(const std::string &s) { unsigned long r = 0; BOOST_FOREACH(char c, s) {