460 lines
17 KiB
C++
460 lines
17 KiB
C++
/**
|
|
* libtransport -- C++ library for easy XMPP Transports development
|
|
*
|
|
* Copyright (C) 2011, Jan Kaluza <hanzz.k@gmail.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
|
|
*/
|
|
|
|
#include "XMPPUserRegistration.h"
|
|
#include "XMPPRosterManager.h"
|
|
#include "transport/UserManager.h"
|
|
#include "transport/StorageBackend.h"
|
|
#include "transport/Transport.h"
|
|
#include "transport/RosterManager.h"
|
|
#include "transport/User.h"
|
|
#include "transport/Logging.h"
|
|
#include "formutils.h"
|
|
#include "transport/Buddy.h"
|
|
#include "transport/Config.h"
|
|
#include "Swiften/Elements/ErrorPayload.h"
|
|
#include "Swiften/EventLoop/SimpleEventLoop.h"
|
|
#include "Swiften/Network/BoostNetworkFactories.h"
|
|
#include "Swiften/Client/Client.h"
|
|
#include <boost/shared_ptr.hpp>
|
|
#include <boost/thread.hpp>
|
|
#include <boost/date_time/posix_time/posix_time.hpp>
|
|
#include <boost/regex.hpp>
|
|
#if HAVE_SWIFTEN_3
|
|
#include <Swiften/Elements/Form.h>
|
|
#endif
|
|
#include "XMPPFrontend.h"
|
|
|
|
using namespace Swift;
|
|
|
|
namespace Transport {
|
|
|
|
DEFINE_LOGGER(logger, "XMPPUserRegistration");
|
|
|
|
XMPPUserRegistration::XMPPUserRegistration(Component *component, UserManager *userManager,
|
|
StorageBackend *storageBackend)
|
|
: UserRegistration(component, userManager, storageBackend), Swift::Responder<Swift::InBandRegistrationPayload>(static_cast<XMPPFrontend *>(component->getFrontend())->getIQRouter()) {
|
|
m_component = component;
|
|
m_config = m_component->getConfig();
|
|
m_storageBackend = storageBackend;
|
|
m_userManager = userManager;
|
|
}
|
|
|
|
XMPPUserRegistration::~XMPPUserRegistration(){
|
|
}
|
|
|
|
bool XMPPUserRegistration::doUserRegistration(const UserInfo &row) {
|
|
// Check if the server supports remoteroster XEP by sending request for the registered user's roster.
|
|
AddressedRosterRequest::ref request = AddressedRosterRequest::ref(new AddressedRosterRequest(static_cast<XMPPFrontend *>(m_component->getFrontend())->getIQRouter(), row.jid));
|
|
request->onResponse.connect(boost::bind(&XMPPUserRegistration::handleRegisterRemoteRosterResponse, this, _1, _2, row));
|
|
request->send();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool XMPPUserRegistration::doUserUnregistration(const UserInfo &row) {
|
|
// Check if the server supports remoteroster XEP by sending request for the registered user's roster.
|
|
AddressedRosterRequest::ref request = AddressedRosterRequest::ref(new AddressedRosterRequest(static_cast<XMPPFrontend *>(m_component->getFrontend())->getIQRouter(), row.jid));
|
|
request->onResponse.connect(boost::bind(&XMPPUserRegistration::handleUnregisterRemoteRosterResponse, this, _1, _2, row));
|
|
request->send();
|
|
|
|
return true;
|
|
}
|
|
|
|
void XMPPUserRegistration::handleRegisterRemoteRosterResponse(SWIFTEN_SHRPTR_NAMESPACE::shared_ptr<Swift::RosterPayload> payload, Swift::ErrorPayload::ref remoteRosterNotSupported, const UserInfo &row){
|
|
if (remoteRosterNotSupported || !payload) {
|
|
// Remote roster is not support, so send normal Subscribe presence to add transport.
|
|
Swift::Presence::ref response = Swift::Presence::create();
|
|
response->setFrom(m_component->getJID());
|
|
response->setTo(Swift::JID(row.jid));
|
|
response->setType(Swift::Presence::Subscribe);
|
|
m_component->getFrontend()->sendPresence(response);
|
|
}
|
|
else {
|
|
// Remote roster is support, so use remoteroster XEP to add transport.
|
|
Swift::RosterPayload::ref payload = Swift::RosterPayload::ref(new Swift::RosterPayload());
|
|
Swift::RosterItemPayload item;
|
|
item.setJID(m_component->getJID());
|
|
item.setSubscription(Swift::RosterItemPayload::Both);
|
|
payload->addItem(item);
|
|
Swift::SetRosterRequest::ref request = Swift::SetRosterRequest::create(payload, row.jid, static_cast<XMPPFrontend *>(m_component->getFrontend())->getIQRouter());
|
|
request->send();
|
|
}
|
|
|
|
onUserRegistered(row);
|
|
|
|
// If the JID for registration notification is configured, send the notification message.
|
|
std::vector<std::string> const &x = CONFIG_VECTOR(m_component->getConfig(),"registration.notify_jid");
|
|
BOOST_FOREACH(const std::string ¬ify_jid, x) {
|
|
SWIFTEN_SHRPTR_NAMESPACE::shared_ptr<Swift::Message> msg(new Swift::Message());
|
|
msg->setBody(std::string("registered: ") + row.jid);
|
|
msg->setTo(notify_jid);
|
|
msg->setFrom(m_component->getJID());
|
|
m_component->getFrontend()->sendMessage(msg);
|
|
}
|
|
}
|
|
|
|
void XMPPUserRegistration::handleUnregisterRemoteRosterResponse(SWIFTEN_SHRPTR_NAMESPACE::shared_ptr<Swift::RosterPayload> payload, Swift::ErrorPayload::ref remoteRosterNotSupported, const UserInfo &userInfo) {
|
|
if (remoteRosterNotSupported || !payload) {
|
|
// Remote roster is ont support, so get the buddies from database
|
|
// and send Unsubsribe and Unsubscribed presence to them.
|
|
std::list<BuddyInfo> roster;
|
|
m_storageBackend->getBuddies(userInfo.id, roster);
|
|
for(std::list<BuddyInfo>::iterator u = roster.begin(); u != roster.end() ; u++){
|
|
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();
|
|
response->setTo(Swift::JID(userInfo.jid));
|
|
response->setFrom(Swift::JID(name, m_component->getJID().toString()));
|
|
response->setType(Swift::Presence::Unsubscribe);
|
|
m_component->getFrontend()->sendPresence(response);
|
|
|
|
response = Swift::Presence::create();
|
|
response->setTo(Swift::JID(userInfo.jid));
|
|
response->setFrom(Swift::JID(name, m_component->getJID().toString()));
|
|
response->setType(Swift::Presence::Unsubscribed);
|
|
m_component->getFrontend()->sendPresence(response);
|
|
}
|
|
}
|
|
else {
|
|
// Remote roster is support, so iterate over all buddies we received
|
|
// from the XMPP server and remove them using remote roster.
|
|
BOOST_FOREACH(Swift::RosterItemPayload it, payload->getItems()) {
|
|
Swift::RosterPayload::ref p = Swift::RosterPayload::ref(new Swift::RosterPayload());
|
|
Swift::RosterItemPayload item;
|
|
item.setJID(it.getJID());
|
|
item.setSubscription(Swift::RosterItemPayload::Remove);
|
|
|
|
p->addItem(item);
|
|
|
|
Swift::SetRosterRequest::ref request = Swift::SetRosterRequest::create(p, userInfo.jid, static_cast<XMPPFrontend *>(m_component->getFrontend())->getIQRouter());
|
|
request->send();
|
|
}
|
|
}
|
|
|
|
// Remove user from database
|
|
m_storageBackend->removeUser(userInfo.id);
|
|
|
|
// Disconnect the user
|
|
User *user = m_userManager->getUser(userInfo.jid);
|
|
if (user) {
|
|
m_userManager->removeUser(user);
|
|
}
|
|
|
|
// Remove the transport contact itself the same way as the buddies.
|
|
if (remoteRosterNotSupported || !payload) {
|
|
Swift::Presence::ref response;
|
|
response = Swift::Presence::create();
|
|
response->setTo(Swift::JID(userInfo.jid));
|
|
response->setFrom(m_component->getJID());
|
|
response->setType(Swift::Presence::Unsubscribe);
|
|
m_component->getFrontend()->sendPresence(response);
|
|
|
|
response = Swift::Presence::create();
|
|
response->setTo(Swift::JID(userInfo.jid));
|
|
response->setFrom(m_component->getJID());
|
|
response->setType(Swift::Presence::Unsubscribed);
|
|
m_component->getFrontend()->sendPresence(response);
|
|
}
|
|
else {
|
|
Swift::RosterPayload::ref payload = Swift::RosterPayload::ref(new Swift::RosterPayload());
|
|
Swift::RosterItemPayload item;
|
|
item.setJID(m_component->getJID());
|
|
item.setSubscription(Swift::RosterItemPayload::Remove);
|
|
payload->addItem(item);
|
|
|
|
Swift::SetRosterRequest::ref request = Swift::SetRosterRequest::create(payload, userInfo.jid, static_cast<XMPPFrontend *>(m_component->getFrontend())->getIQRouter());
|
|
request->send();
|
|
}
|
|
|
|
// If the JID for registration notification is configured, send the notification message.
|
|
std::vector<std::string> const &x = CONFIG_VECTOR(m_component->getConfig(),"registration.notify_jid");
|
|
BOOST_FOREACH(const std::string ¬ify_jid, x) {
|
|
SWIFTEN_SHRPTR_NAMESPACE::shared_ptr<Swift::Message> msg(new Swift::Message());
|
|
msg->setBody(std::string("unregistered: ") + userInfo.jid);
|
|
msg->setTo(notify_jid);
|
|
msg->setFrom(m_component->getJID());
|
|
m_component->getFrontend()->sendMessage(msg);
|
|
}
|
|
}
|
|
|
|
Form::ref XMPPUserRegistration::generateRegistrationForm(const UserInfo &res, bool registered) {
|
|
Form::ref form(new Form(Form::FormType));
|
|
form->setTitle("Registration");
|
|
form->setInstructions(CONFIG_STRING(m_config, "registration.instructions"));
|
|
|
|
FormUtils::addHiddenField(form, "FORM_TYPE", "jabber:iq:register");
|
|
FormUtils::addTextSingleField(form, "username", res.uin,
|
|
CONFIG_STRING(m_config, "registration.username_label"),
|
|
true);
|
|
|
|
if (CONFIG_BOOL_DEFAULTED(m_config, "registration.needPassword", true)) {
|
|
FormUtils::addTextPrivateField(form, "password", "Password", true);
|
|
}
|
|
|
|
std::string defLanguage = CONFIG_STRING(m_config, "registration.language");
|
|
Swift::FormField::Option languages(defLanguage, defLanguage);
|
|
FormUtils::addListSingleField(form, "language", languages, "Language",
|
|
registered ? res.language : defLanguage);
|
|
|
|
|
|
if (registered) {
|
|
FormUtils::addBooleanField(form, "unregister", "0", "Remove your registration");
|
|
}
|
|
else if (CONFIG_BOOL(m_config,"registration.require_local_account")) {
|
|
std::string localUsernameField = CONFIG_STRING(m_config, "registration.local_username_label");
|
|
FormUtils::addTextSingleField(form, "local_username", "", localUsernameField, true);
|
|
FormUtils::addTextSingleField(form, "local_password", "", "Local password", true);
|
|
}
|
|
|
|
return form;
|
|
}
|
|
|
|
SWIFTEN_SHRPTR_NAMESPACE::shared_ptr<InBandRegistrationPayload> XMPPUserRegistration::generateInBandRegistrationPayload(const Swift::JID& from) {
|
|
SWIFTEN_SHRPTR_NAMESPACE::shared_ptr<InBandRegistrationPayload> reg(new InBandRegistrationPayload());
|
|
|
|
UserInfo res;
|
|
bool registered = m_storageBackend->getUser(from.toBare().toString(), res);
|
|
|
|
reg->setInstructions(CONFIG_STRING(m_config, "registration.instructions"));
|
|
reg->setRegistered(registered);
|
|
reg->setUsername(res.uin);
|
|
|
|
if (CONFIG_BOOL_DEFAULTED(m_config, "registration.needPassword", true)) {
|
|
reg->setPassword("");
|
|
}
|
|
|
|
Form::ref form = generateRegistrationForm(res, registered);
|
|
reg->setForm(form);
|
|
|
|
return reg;
|
|
}
|
|
|
|
bool XMPPUserRegistration::handleGetRequest(const Swift::JID& from, const Swift::JID& to, const std::string& id, SWIFTEN_SHRPTR_NAMESPACE::shared_ptr<Swift::InBandRegistrationPayload> payload) {
|
|
// TODO: backend should say itself if registration is needed or not...
|
|
if (CONFIG_STRING(m_config, "service.protocol") == "irc") {
|
|
sendError(from, id, ErrorPayload::BadRequest, ErrorPayload::Modify);
|
|
return true;
|
|
}
|
|
|
|
if (!CONFIG_BOOL(m_config,"registration.enable_public_registration")) {
|
|
std::vector<std::string> const &x = CONFIG_VECTOR(m_config,"service.allowed_servers");
|
|
if (std::find(x.begin(), x.end(), from.getDomain()) == x.end()) {
|
|
LOG4CXX_INFO(logger, from.toBare().toString() << ": This user has no permissions to register an account");
|
|
sendError(from, id, ErrorPayload::BadRequest, ErrorPayload::Modify);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
SWIFTEN_SHRPTR_NAMESPACE::shared_ptr<InBandRegistrationPayload> reg = generateInBandRegistrationPayload(from);
|
|
sendResponse(from, id, reg);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool XMPPUserRegistration::handleSetRequest(const Swift::JID& from, const Swift::JID& to, const std::string& id, SWIFTEN_SHRPTR_NAMESPACE::shared_ptr<Swift::InBandRegistrationPayload> payload) {
|
|
// TODO: backend should say itself if registration is needed or not...
|
|
if (CONFIG_STRING(m_config, "service.protocol") == "irc") {
|
|
sendError(from, id, ErrorPayload::BadRequest, ErrorPayload::Modify);
|
|
return true;
|
|
}
|
|
|
|
std::string barejid = from.toBare().toString();
|
|
|
|
if (!CONFIG_BOOL(m_config,"registration.enable_public_registration")) {
|
|
std::vector<std::string> const &x = CONFIG_VECTOR(m_config,"service.allowed_servers");
|
|
if (std::find(x.begin(), x.end(), from.getDomain()) == x.end()) {
|
|
LOG4CXX_INFO(logger, barejid << ": This user has no permissions to register an account");
|
|
sendError(from, id, ErrorPayload::BadRequest, ErrorPayload::Modify);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
UserInfo res;
|
|
bool registered = m_storageBackend->getUser(barejid, res);
|
|
|
|
std::string encoding;
|
|
std::string language;
|
|
std::string local_username;
|
|
std::string local_password;
|
|
|
|
Form::ref form = payload->getForm();
|
|
if (form) {
|
|
std::string value;
|
|
|
|
value = FormUtils::fieldValue(form, "username", "");
|
|
if (!value.empty()) {
|
|
payload->setUsername(value);
|
|
}
|
|
|
|
value = FormUtils::fieldValue(form, "password", "");
|
|
if (!value.empty()) {
|
|
payload->setPassword(value);
|
|
}
|
|
|
|
value = FormUtils::fieldValue(form, "unregister", "");
|
|
if (value == "1" || value == "true") {
|
|
payload->setRemove(true);
|
|
}
|
|
|
|
encoding = FormUtils::fieldValue(form, "encoding", "");
|
|
local_username = FormUtils::fieldValue(form, "local_username", "");
|
|
local_password = FormUtils::fieldValue(form, "local_password", "");
|
|
language = FormUtils::fieldValue(form, "language", "");
|
|
}
|
|
|
|
if (payload->isRemove()) {
|
|
unregisterUser(barejid);
|
|
sendResponse(from, id, InBandRegistrationPayload::ref());
|
|
return true;
|
|
}
|
|
|
|
if (CONFIG_BOOL(m_config,"registration.require_local_account")) {
|
|
/* if (!local_username || !local_password) {
|
|
sendResponse(from, id, InBandRegistrationPayload::ref());
|
|
return true
|
|
} else */ if (local_username == "" || local_password == "") {
|
|
sendResponse(from, id, InBandRegistrationPayload::ref());
|
|
return true;
|
|
}
|
|
// Swift::logging = true;
|
|
bool validLocal = false;
|
|
std::string localLookupServer = CONFIG_STRING(m_config, "registration.local_account_server");
|
|
std::string localLookupJID = local_username + std::string("@") + localLookupServer;
|
|
SimpleEventLoop localLookupEventLoop;
|
|
BoostNetworkFactories localLookupNetworkFactories(&localLookupEventLoop);
|
|
Client localLookupClient(localLookupJID, local_password, &localLookupNetworkFactories);
|
|
|
|
// TODO: this is neccessary on my server ... but should maybe omitted
|
|
localLookupClient.setAlwaysTrustCertificates();
|
|
localLookupClient.connect();
|
|
|
|
class SimpleLoopRunner {
|
|
public:
|
|
SimpleLoopRunner() {};
|
|
|
|
static void run(SimpleEventLoop * loop) {
|
|
loop->run();
|
|
};
|
|
};
|
|
|
|
// TODO: Really ugly and hacky solution, any other ideas more than welcome!
|
|
boost::thread thread(boost::bind(&(SimpleLoopRunner::run), &localLookupEventLoop));
|
|
thread.timed_join(boost::posix_time::millisec(CONFIG_INT(m_config, "registration.local_account_server_timeout")));
|
|
localLookupEventLoop.stop();
|
|
thread.join();
|
|
validLocal = localLookupClient.isAvailable();
|
|
localLookupClient.disconnect();
|
|
if (!validLocal) {
|
|
sendError(from, id, ErrorPayload::NotAuthorized, ErrorPayload::Modify);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!payload->getUsername() || (!payload->getPassword() && CONFIG_BOOL_DEFAULTED(m_config, "registration.needPassword", true))) {
|
|
sendError(from, id, ErrorPayload::NotAcceptable, ErrorPayload::Modify);
|
|
return true;
|
|
}
|
|
|
|
if (!payload->getPassword()) {
|
|
payload->setPassword("");
|
|
}
|
|
|
|
// Register or change password
|
|
if (payload->getUsername()->empty()) {
|
|
sendError(from, id, ErrorPayload::NotAcceptable, ErrorPayload::Modify);
|
|
return true;
|
|
}
|
|
|
|
// TODO: Move this check to backend somehow
|
|
if (CONFIG_STRING(m_config, "service.protocol") == "prpl-jabber") {
|
|
// User tries to register himself.
|
|
if ((Swift::JID(*payload->getUsername()).toBare() == from.toBare())) {
|
|
sendError(from, id, ErrorPayload::NotAcceptable, ErrorPayload::Modify);
|
|
return true;
|
|
}
|
|
|
|
// User tries to register someone who's already registered.
|
|
UserInfo user_row;
|
|
bool registered = m_storageBackend->getUser(Swift::JID(*payload->getUsername()).toBare().toString(), user_row);
|
|
if (registered) {
|
|
sendError(from, id, ErrorPayload::NotAcceptable, ErrorPayload::Modify);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
std::string username = *payload->getUsername();
|
|
|
|
std::string newUsername(username);
|
|
if (!CONFIG_STRING(m_config, "registration.username_mask").empty()) {
|
|
newUsername = CONFIG_STRING(m_config, "registration.username_mask");
|
|
boost::replace_all(newUsername, "$username", username);
|
|
}
|
|
|
|
//TODO: Part of spectrum1 registration stuff, this should be potentially rewritten for S2 too
|
|
// if (!m_component->protocol()->isValidUsername(newUsername)) {
|
|
// Log("XMPPUserRegistration", "This is not valid username: "<< newUsername);
|
|
// sendError(from, id, ErrorPayload::NotAcceptable, ErrorPayload::Modify);
|
|
// return true;
|
|
// }
|
|
|
|
if (!CONFIG_STRING(m_config, "registration.allowed_usernames").empty()) {
|
|
boost::regex expression(CONFIG_STRING(m_config, "registration.allowed_usernames"));
|
|
if (!regex_match(newUsername, expression)) {
|
|
LOG4CXX_INFO(logger, "This is not valid username: " << newUsername);
|
|
sendError(from, id, ErrorPayload::NotAcceptable, ErrorPayload::Modify);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!registered) {
|
|
res.jid = barejid;
|
|
res.uin = newUsername;
|
|
res.password = *payload->getPassword();
|
|
res.language = language;
|
|
res.encoding = encoding;
|
|
res.vip = 0;
|
|
res.id = 0;
|
|
registerUser(res);
|
|
}
|
|
else {
|
|
res.jid = barejid;
|
|
res.uin = newUsername;
|
|
res.password = *payload->getPassword();
|
|
res.language = language;
|
|
res.encoding = encoding;
|
|
m_storageBackend->setUser(res);
|
|
// onUserUpdated(res);
|
|
}
|
|
|
|
sendResponse(from, id, InBandRegistrationPayload::ref());
|
|
return true;
|
|
}
|
|
|
|
}
|