/* * Copyright 2021, Jaidyn Levesque. All rights reserved. * Copyright 2010, Pier Luigi Fiorini. All rights reserved. * Distributed under the terms of the GPL v2 License. * * Authors: * Pier Luigi Fiorini, pierluigi.fiorini@gmail.com * Jaidyn Levesque, jadedctrl@teknik.io */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "JabberHandler.h" static status_t connect_thread(void* data) { JabberHandler* handler = (JabberHandler*)data; if (!handler) return B_BAD_VALUE; gloox::Client* client = handler->Client(); if (!client) return B_BAD_VALUE; gloox::ConnectionError e; while ((e = client->recv(10000000)) == gloox::ConnNoError); if (e != gloox::ConnUserDisconnected) handler->HandleConnectionError(e); return B_OK; } JabberHandler::JabberHandler() : fClient(NULL), fVCardManager(NULL), fSession(NULL) { fAvatars = new BList(); } JabberHandler::~JabberHandler() { Shutdown(); BString* item = NULL; for (int32 i = 0; (item = (BString*)fAvatars->ItemAt(i)); i++) delete item; delete fAvatars; } status_t JabberHandler::Init(CayaProtocolMessengerInterface* messenger) { fServerMessenger = messenger; return B_OK; } status_t JabberHandler::Process(BMessage* msg) { if (msg->what != IM_MESSAGE) return B_ERROR; int32 im_what = 0; msg->FindInt32("im_what", &im_what); switch (im_what) { case IM_SET_OWN_STATUS: { int32 status = msg->FindInt32("status"); BString status_msg = msg->FindString("message"); switch (status) { case CAYA_ONLINE: // Log in if we still need to resume_thread(fRecvThread); break; case CAYA_OFFLINE: kill_thread(fRecvThread); break; default: break; } break; } case IM_SEND_MESSAGE: { const char* id = msg->FindString("chat_id"); const char* subject = msg->FindString("subject"); const char* body = msg->FindString("body"); gloox::MUCRoom* room = fRooms.ValueFor(id); if (!id || !body) return B_ERROR; // Send JabberHandler message gloox::Message jm(gloox::Message::Chat, gloox::JID(id), body, (subject ? subject : gloox::EmptyString)); if (room != NULL) { room->send(body); break; } else fClient->send(jm); // If non-MUC, tell Caya we actually sent the message // (An MUC should echo the message back to us later, see // handleMUCMessage) _MessageSent(id, subject, body); break; } case IM_CREATE_CHAT: { const char* user_id = msg->FindString("user_id"); // TODO: Contact validation, make sure permssion is granted if (!user_id) return B_ERROR; _EnsureUserChat(user_id); _ChatCreatedMsg(user_id); break; } case IM_JOIN_ROOM: { BString chat_id; if (msg->FindString("chat_id", &chat_id) == B_OK) _JoinRoom(chat_id.String()); break; } case IM_LEAVE_ROOM: { BString chat_id = msg->FindString("chat_id"); gloox::MUCRoom* room = fRooms.ValueFor(chat_id); // MUCs are special, one-on-ones we can just drop if (room != NULL) room->leave(); // We've gotta let Caya know! BMessage left(IM_MESSAGE); left.AddInt32("im_what", IM_ROOM_LEFT); left.AddString("chat_id", chat_id); _SendMessage(&left); break; } case IM_ROOM_INVITE_ACCEPT: { BString chat_id; if (msg->FindString("chat_id", &chat_id) != B_OK) break; BStringList splitAtPassword; chat_id.Split("#", false, splitAtPassword); chat_id = splitAtPassword.StringAt(0); _JoinRoom(chat_id.String()); break; } case IM_ROOM_SEND_INVITE: { BString chat_id = msg->FindString("chat_id"); gloox::MUCRoom* room = fRooms.ValueFor(chat_id); BString user_id; if (room == NULL || msg->FindString("user_id", &user_id) != B_OK) break; room->invite(gloox::JID(user_id.String()), ""); break; } case IM_GET_ROOM_PARTICIPANTS: { BString chat_id = msg->FindString("chat_id"); gloox::MUCRoom* room = fRooms.ValueFor(chat_id); if (room != NULL) room->getRoomItems(); else if (fUserChats.HasString(chat_id) == true) { BMessage users(IM_MESSAGE); users.AddInt32("im_what", IM_ROOM_PARTICIPANTS); users.AddString("user_id", chat_id); } break; } case IM_GET_ROOM_METADATA: { BString chat_id = msg->FindString("chat_id"); gloox::MUCRoom* room = fRooms.ValueFor(chat_id); if (room != NULL) room->getRoomInfo(); if (fUserChats.HasString(chat_id) == true) { BMessage metadata(IM_MESSAGE); metadata.AddInt32("im_what", IM_ROOM_METADATA); metadata.AddString("chat_id", chat_id); metadata.AddInt32("room_default_flags", 0 | ROOM_LOG_LOCALLY | ROOM_POPULATE_LOGS); metadata.AddInt32("room_disallowed_flags", 0 | ROOM_AUTOJOIN | ROOM_AUTOCREATE); _SendMessage(&metadata); } break; } case IM_ROOM_KICK_PARTICIPANT: case IM_ROOM_BAN_PARTICIPANT: case IM_ROOM_UNBAN_PARTICIPANT: case IM_ROOM_MUTE_PARTICIPANT: case IM_ROOM_UNMUTE_PARTICIPANT: _MUCModeration(msg); break; default: return B_ERROR; } return B_OK; } status_t JabberHandler::Shutdown() { if (fVCardManager) fVCardManager->cancelVCardOperations(this); if (fClient) fClient->disposeMessageSession(fSession); kill_thread(fRecvThread); return B_OK; } void JabberHandler::SetAddOnPath(BPath path) { fPath = path; } BPath JabberHandler::AddOnPath() { return fPath; } void JabberHandler::SetName(const char* name) { fName = name; } const char* JabberHandler::GetName() { return fName.String(); } status_t JabberHandler::UpdateSettings(BMessage* msg) { const char* username = msg->FindString("username"); const char* password = msg->FindString("password"); const char* server = msg->FindString("server"); const char* resource = msg->FindString("resource"); // Sanity check if (!username || !password || !resource) return B_ERROR; // Store settings fUsername = username; fNick = username; fPassword = password; fServer = server; fResource = resource; fPort = 5222; // Give the add-on a change to override settings OverrideSettings(); // Compose JID BString jid = ComposeJID(); fJid.setJID(jid.String()); // Register our client fClient = new gloox::Client(fJid, fPassword.String()); fClient->setServer(fServer.String()); if (fPort > 0) fClient->setPort(fPort); fClient->registerConnectionListener(this); fClient->registerMessageSessionHandler(this); fClient->registerMUCInvitationHandler(new InviteHandler(fClient, this)); fClient->rosterManager()->registerRosterListener(this); fClient->disco()->setVersion("Caya", VERSION); fClient->disco()->setIdentity("client", "caya"); fClient->logInstance().registerLogHandler(gloox::LogLevelDebug, gloox::LogAreaAll, this); // Register vCard manager fVCardManager = new gloox::VCardManager(fClient); // Connect to the server fClient->connect(false); // Read data from another thread fRecvThread = spawn_thread(connect_thread, "connect_thread", B_NORMAL_PRIORITY, (void*)this); if (fRecvThread < B_OK) return B_ERROR; return B_OK; } uint32 JabberHandler::GetEncoding() { return 0xffff; } CayaProtocolMessengerInterface* JabberHandler::MessengerInterface() const { return fServerMessenger; } gloox::Client* JabberHandler::Client() const { return fClient; } void JabberHandler::HandleConnectionError(gloox::ConnectionError& e) { // Handle error BMessage errMsg(IM_ERROR); switch (e) { case gloox::ConnStreamError: { gloox::StreamError streamError = fClient->streamError(); errMsg.AddString("error", "Bad or malformed XML stream."); switch (streamError) { case gloox::StreamErrorBadFormat: errMsg.AddString("detail", "The entity has sent XML that " "cannot be processed"); break; case gloox::StreamErrorBadNamespacePrefix: errMsg.AddString("detail", "The entity has sent a namespace " "prefix which is not supported, or has sent no namespace " "prefix on an element that requires such prefix."); break; case gloox::StreamErrorConflict: errMsg.AddString("detail", "The server is closing the active " "stream for this entity because a new stream has been " "initiated that conflicts with the existing stream."); break; case gloox::StreamErrorConnectionTimeout: errMsg.AddString("detail", "The entity has not generated any " "traffic over the stream for some period of time."); break; case gloox::StreamErrorHostGone: errMsg.AddString("detail", "The host initially used corresponds " "to a hostname that is no longer hosted by the server."); break; case gloox::StreamErrorHostUnknown: errMsg.AddString("detail", "The host initially used does not " "correspond to a hostname that is hosted by the server."); break; case gloox::StreamErrorImproperAddressing: errMsg.AddString("detail", "A stanza sent between two servers " "lacks a 'to' or 'from' attribute (or the attribute has no " "value."); break; case gloox::StreamErrorInternalServerError: errMsg.AddString("detail", "The Server has experienced a " "misconfiguration or an otherwise-undefined internal error " "that prevents it from servicing the stream."); break; case gloox::StreamErrorInvalidFrom: errMsg.AddString("detail", "The JID or hostname provided in a " "'from' address does not match an authorized JID or validated " "domain negotiation between servers via SASL or dialback, or " "between a client and a server via authentication and resource " "binding."); break; case gloox::StreamErrorInvalidId: errMsg.AddString("detail", "The stream ID or dialback ID is invalid " "or does not match and ID previously provdided."); break; case gloox::StreamErrorInvalidNamespace: errMsg.AddString("detail", "The streams namespace name is something " "other than \"http://etherx.jabber.org/streams\" or the dialback " "namespace name is something other than \"jabber:server:dialback\"."); break; case gloox::StreamErrorInvalidXml: errMsg.AddString("detail", "The entity has sent invalid XML over the " "stream to a server that performs validation."); break; case gloox::StreamErrorNotAuthorized: errMsg.AddString("detail", "The entity has attempted to send data before " "the stream has been authenticated, or otherwise is not authorized to " "perform an action related to stream negotiation; the receiving entity " "must not process the offending stanza before sending the stream error."); break; case gloox::StreamErrorPolicyViolation: errMsg.AddString("detail", "The entity has violated some local service " "policy; the server may choose to specify the policy in the " "element or an application-specific condition element."); break; case gloox::StreamErrorRemoteConnectionFailed: errMsg.AddString("detail", "The server is unable to properly connect to " "a remote entit that is required for authentication."); break; case gloox::StreamErrorResourceConstraint: errMsg.AddString("detail", "The server lacks the system resources necessary " "to service the stream."); break; case gloox::StreamErrorRestrictedXml: errMsg.AddString("detail", "The entity has attempted to send restricted XML " "features such as a comment, processing instruction, DTD, entity reference " "or unescaped character."); break; case gloox::StreamErrorSeeOtherHost: errMsg.AddString("detail", "The server will not provide service to the initiating " "entity but is redirecting traffic to another host; the server should specify " "the alternate hostname or IP address (which MUST be a valid domain identifier) " "as the XML characted data of the element."); break; case gloox::StreamErrorSystemShutdown: errMsg.AddString("detail", "The server is being shut down and all active streams " "are being closed."); break; case gloox::StreamErrorUndefinedCondition: errMsg.AddString("detail", "The error condition is not one of those defined by the " "other condition in this list; this error condition should be used only in " "conjunction with an application-specific condition."); break; case gloox::StreamErrorUnsupportedEncoding: errMsg.AddString("detail", "The initiating entity has encoded the stream in an " "encoding that is not supported by the server."); break; case gloox::StreamErrorUnsupportedStanzaType: errMsg.AddString("detail", "The initiating entity has sent a first-level child " "of the stream that is not supported by the server."); break; case gloox::StreamErrorUnsupportedVersion: errMsg.AddString("detail", "The value of the 'version' attribute provided by the " "initiating entity in the stream header specifies a version of XMPP that is not " "supported by the server; the server may specify the version(s) it supports in " "the element."); break; case gloox::StreamErrorXmlNotWellFormed: errMsg.AddString("detail", "The initiating entity has sent XML that is not " "well-formed as defined by XML."); break; default: break; } break; } case gloox::ConnStreamVersionError: errMsg.AddString("detail", "The incoming stream's version is not " "supported."); break; case gloox::ConnStreamClosed: errMsg.AddString("detail", "The stream has been closed by the server."); break; case gloox::ConnProxyAuthRequired: errMsg.AddString("detail", "The HTTP/SOCKS5 proxy requires authentication."); break; case gloox::ConnProxyAuthFailed: errMsg.AddString("detail", "HTTP/SOCKS5 proxy authentication failed."); break; case gloox::ConnProxyNoSupportedAuth: errMsg.AddString("detail", "The HTTP/SOCKS5 proxy requires an unsupported " "authentication mechanism."); break; case gloox::ConnIoError: errMsg.AddString("detail", "Input/output error."); break; case gloox::ConnParseError: errMsg.AddString("detail", "A XML parse error occurred."); break; case gloox::ConnConnectionRefused: errMsg.AddString("detail", "The connection was refused by the server " "on the socket level."); break; case gloox::ConnDnsError: errMsg.AddString("detail", "Server's hostname resolution failed."); break; case gloox::ConnOutOfMemory: errMsg.AddString("detail", "Out of memory."); break; case gloox::ConnTlsFailed: errMsg.AddString("detail", "The server's certificate could not be verified or " "the TLS handshake did not complete successfully."); break; case gloox::ConnTlsNotAvailable: errMsg.AddString("detail", "The server didn't offer TLS while it was set to be " "required, or TLS was not compiled in."); break; case gloox::ConnCompressionFailed: errMsg.AddString("detail", "Negotiating or initializing compression failed."); break; case gloox::ConnAuthenticationFailed: { gloox::AuthenticationError authError = fClient->authError(); errMsg.AddString("error", "Authentication failed. Username or password wrong " "or account does not exist."); switch (authError) { case gloox::SaslAborted: errMsg.AddString("detail", "The receiving entity acknowledges an " "element sent by initiating entity; sent in reply to the " "element."); break; case gloox::SaslIncorrectEncoding: errMsg.AddString("detail", "The data provided by the initiating entity " "could not be processed because the base64 encoding is incorrect."); break; case gloox::SaslInvalidAuthzid: errMsg.AddString("detail", "The authid provided by the initiating entity " "is invalid, either because it is incorrectly formatted or because " "the initiating entity does not have permissions to authorize that ID; " "sent in reply to a element or an element with " "initial response data."); break; case gloox::SaslInvalidMechanism: errMsg.AddString("detail", "The initiating element did not provide a " "mechanism or requested a mechanism that is not supported by the " "receiving entity; sent in reply to an element."); break; case gloox::SaslMalformedRequest: errMsg.AddString("detail", "The request is malformed (e.g., the " "element includes an initial response but the mechanism does not " "allow that); sent in reply to an , , , or " " element."); break; case gloox::SaslMechanismTooWeak: errMsg.AddString("detail", "The mechanism requested by the initiating entity " "is weaker than server policy permits for that initiating entity; sent in " "reply to a element or an element with initial " "response data."); break; case gloox::SaslNotAuthorized: errMsg.AddString("detail", "The authentication failed because the initiating " "entity did not provide valid credentials (this includes but is not " "limited to the case of an unknown username); sent in reply to a " " element or an element with initial response data."); break; case gloox::SaslTemporaryAuthFailure: errMsg.AddString("detail", "The authentication failed because of a temporary " "error condition within the receiving entity; sent in reply to an " " element or element."); break; case gloox::NonSaslConflict: errMsg.AddString("detail", "Resource conflict, see XEP-0078."); break; case gloox::NonSaslNotAcceptable: errMsg.AddString("detail", "Required information not provided, " "see XEP-0078."); break; case gloox::NonSaslNotAuthorized: errMsg.AddString("detail", "Incorrect credentials."); break; default: break; } break; } case gloox::ConnNotConnected: errMsg.AddString("error", "There is no active connection."); break; default: break; } _SendMessage(&errMsg); } void JabberHandler::HandleStanzaError(gloox::StanzaError error) { BMessage errMsg(IM_ERROR); errMsg.AddString("error", "Stanza-related error"); BString detail; switch (error) { case gloox::StanzaErrorBadRequest: detail = "The sender has sent XML that is malformed or that cannot " "be processed."; break; case gloox::StanzaErrorConflict: detail = "Access cannot be granted because an existing resource or " "session exists with the same name or address."; break; case gloox::StanzaErrorFeatureNotImplemented: detail = "This feature hasn't been implemented by the recipient or " "by the server."; break; case gloox::StanzaErrorForbidden: detail = "You don't have permssion to do this."; break; case gloox::StanzaErrorGone: detail = "The recipient or server can no longer be contacted at " "this address. Try again later, or with a different address."; break; case gloox::StanzaErrorInternalServerError: detail = "The server could not process the stanza because of a " "misconfiguration or an otherwise-undefined internal server error."; break; case gloox::StanzaErrorItemNotFound: detail = "The addressed JID or item requested cannot be found."; break; case gloox::StanzaErrorJidMalformed: detail = "An invalid XMPP address or identifier was given. If you " "can, please try a different one."; break; case gloox::StanzaErrorNotAcceptable: detail = "The server or user refuses to accept this, because some " "criteria hasn't been met (e.g., a local policy regarding " "acceptable words in messages)."; break; case gloox::StanzaErrorNotAllowed: detail = "You aren't allowed to do this by the server or recepient."; break; case gloox::StanzaErrorNotAuthorized: detail = "You need to be properily authenticated before doing this."; case gloox::StanzaErrorNotModified: detail = "The item requested has not changed since it was last " "requested."; case gloox::StanzaErrorPaymentRequired: detail = "The server refuses to offer service, because payment is " "required."; break; case gloox::StanzaErrorRecipientUnavailable: detail = "The recipient is temporarily unavailable."; break; case gloox::StanzaErrorRedirect: detail = "The recipient or server is redirecting requests for this " "information to another entity, usually temporarily."; break; case gloox::StanzaErrorRegistrationRequired: detail = "You can't do this before registration! Be sure your " "finished registering for your account."; break; case gloox::StanzaErrorRemoteServerNotFound: detail = "That user's server doesn't exist."; break; case gloox::StanzaErrorRemoteServerTimeout: detail = "Connection to that user's server has timed out."; break; case gloox::StanzaErrorResourceConstraint: detail = "The server or recipient are too busy right now; try " "again later."; break; case gloox::StanzaErrorServiceUnavailable: detail = "The server or recipient don't provide this service."; break; case gloox::StanzaErrorSubscribtionRequired: detail = "You can't access this unless you are subscribed."; break; } errMsg.AddString("detail", detail); _SendMessage(&errMsg); } void JabberHandler::_SendMessage(BMessage* msg) { // Skip invalid messages if (!msg) return; msg->AddString("protocol", Signature()); fServerMessenger->SendMessage(msg); } void JabberHandler::_Notify(notification_type type, const char* title, const char* message) { BMessage msg(IM_MESSAGE); msg.AddInt32("im_what", IM_NOTIFICATION); msg.AddInt32("type", (int32)type); msg.AddString("title", title); msg.AddString("message", message); _SendMessage(&msg); } void JabberHandler::_NotifyProgress(const char* title, const char* message, float progress) { BMessage msg(IM_MESSAGE); msg.AddInt32("im_what", IM_PROGRESS); msg.AddString("title", title); msg.AddString("message", message); msg.AddFloat("progress", progress); _SendMessage(&msg); } void JabberHandler::_MessageSent(const char* id, const char* subject, const char* body) { BMessage msg(IM_MESSAGE); msg.AddInt32("im_what", IM_MESSAGE_SENT); msg.AddString("user_id", fJid.bare().c_str()); msg.AddString("chat_id", id); msg.AddString("subject", subject); msg.AddString("body", body); _SendMessage(&msg); } void JabberHandler::_ChatCreatedMsg(const char* id) { BMessage msg(IM_MESSAGE); msg.AddInt32("im_what", IM_CHAT_CREATED); msg.AddString("chat_id", id); msg.AddString("user_id", id); _SendMessage(&msg); } void JabberHandler::_RoleChangedMsg(BString chat_id, BString user_id, gloox::MUCRoomRole role, gloox::MUCRoomAffiliation aff) { BMessage roleMsg(IM_MESSAGE); roleMsg.AddInt32("im_what", IM_ROOM_ROLECHANGED); roleMsg.AddString("user_id", user_id); roleMsg.AddString("chat_id", chat_id); roleMsg.AddString("role_title", _RoleTitle(role, aff)); roleMsg.AddInt32("role_perms", _RolePerms(role, aff)); roleMsg.AddInt32("role_priority", _RolePriority(role, aff)); _SendMessage(&roleMsg); } void JabberHandler::_UserLeftMsg(BString chat_id, gloox::MUCRoomParticipant participant) { BString user_id; const char* nick = participant.nick->resource().c_str(); bool isSelf = _MUCUserId(chat_id, nick, &user_id); if (user_id.IsEmpty() == true) return; int32 im_what = IM_ROOM_PARTICIPANT_LEFT; int flags = participant.flags; if (flags & gloox::UserBanned) im_what = IM_ROOM_PARTICIPANT_BANNED; else if (flags & gloox::UserKicked) im_what = IM_ROOM_PARTICIPANT_KICKED; BMessage leftMsg(IM_MESSAGE); leftMsg.AddInt32("im_what", im_what); leftMsg.AddString("chat_id", chat_id); leftMsg.AddString("user_id", user_id); leftMsg.AddString("user_name", nick); if (participant.reason.empty() == false) leftMsg.AddString("body", participant.reason.c_str()); _SendMessage(&leftMsg); } void JabberHandler::_StatusSetMsg(const char* user_id, gloox::Presence::PresenceType type, const char* message, const char* resource) { BMessage msg(IM_MESSAGE); msg.AddInt32("im_what", IM_STATUS_SET); msg.AddString("user_id", user_id); msg.AddInt32("status", _GlooxStatusToCaya(type)); if (BString(resource).IsEmpty() == false) msg.AddString("resource", resource); if (BString(message).IsEmpty() == false) msg.AddString("message", message); _SendMessage(&msg); } void JabberHandler::_EnsureUserChat(const char* chat_id) { if (fUserChats.HasString(BString(chat_id)) == false) fUserChats.Add(BString(chat_id)); } status_t JabberHandler::_SetupAvatarCache() { if (fAvatarCachePath.InitCheck() == B_OK) return B_OK; BPath path; if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) return B_ERROR; path.Append("Caya"); path.Append("Cache"); path.Append(GetName()); if (create_directory(path.Path(), 0755) != B_OK) return B_ERROR; fCachePath = path; path.Append("avatar-cache"); BFile file(path.Path(), B_READ_ONLY); fAvatarCache.Unflatten(&file); fAvatarCachePath = path; return B_OK; } status_t JabberHandler::_SaveAvatarCache() { if (fAvatarCachePath.InitCheck() != B_OK) return B_ERROR; BFile file(fAvatarCachePath.Path(), B_CREATE_FILE | B_WRITE_ONLY | B_ERASE_FILE); return fAvatarCache.Flatten(&file); } void JabberHandler::_CacheAvatar(const char* id, const char* binimage, size_t length) { if (!id || !binimage || length <= 0) return; // Calculate avatar hash CSHA1 s1; char hash[256]; s1.Reset(); s1.Update((uchar*)binimage, length); s1.Final(); s1.ReportHash(hash, CSHA1::REPORT_HEX); BString sha1; sha1.SetTo(hash, 256); BString oldSha1; if (fAvatarCache.FindString(id, &oldSha1) != B_OK || oldSha1 == "" || sha1 != oldSha1) { // Replace old hash and save cache fAvatarCache.RemoveName(id); fAvatarCache.AddString(id, sha1); _SaveAvatarCache(); if (oldSha1 != "") { BPath path(fCachePath); path.Append(oldSha1); // Remove old image file BEntry entry(path.Path()); entry.Remove(); // Remove old hash from the list BString* item = NULL; for (int32 i = 0; (item = (BString*)fAvatars->ItemAt(i)); i++) { if (item->Compare(oldSha1) == 0) { fAvatars->RemoveItem(item); break; } } } } // Determine file path BPath path(fCachePath); path.Append(sha1); BEntry entry(path.Path()); if (!entry.Exists()) { // Save to file BFile file(path.Path(), B_CREATE_FILE | B_WRITE_ONLY | B_ERASE_FILE); file.Write(binimage, length); } // Do we need to notify Caya? bool found = false; BString* item = NULL; for (int32 i = 0; (item = (BString*)fAvatars->ItemAt(i)); i++) { if (item->Compare(sha1) == 0) { found = true; break; } } if (!found) { // Add new hash to the list if needed fAvatars->AddItem(new BString(sha1)); // Notify Caya that the avatar has changed _AvatarChanged(id, path.Path()); } } void JabberHandler::_AvatarChanged(const char* id, const char* filename) { entry_ref ref; if (get_ref_for_path(filename, &ref) != B_OK) return; BMessage msg(IM_MESSAGE); if (fJid.bare() == id) msg.AddInt32("im_what", IM_OWN_AVATAR_SET); else { msg.AddInt32("im_what", IM_AVATAR_SET); msg.AddString("user_id", id); } msg.AddRef("ref", &ref); _SendMessage(&msg); } CayaStatus JabberHandler::_GlooxStatusToCaya(gloox::Presence::PresenceType type) { switch (type) { case gloox::Presence::Available: case gloox::Presence::Chat: return CAYA_ONLINE; case gloox::Presence::Away: return CAYA_AWAY; case gloox::Presence::XA: return CAYA_CUSTOM_STATUS; case gloox::Presence::DND: return CAYA_DO_NOT_DISTURB; case gloox::Presence::Unavailable: return CAYA_OFFLINE; default: break; } return CAYA_OFFLINE; } BString JabberHandler::_MUCChatId(gloox::MUCRoom* room) { BString chat_id(room->name().c_str()); chat_id << "@" << room->service().c_str(); BStringList parts; chat_id.Split("/", false, parts); return parts.StringAt(0); } bool JabberHandler::_MUCUserId(BString chat_id, const char* nick, BString* id) { BString chat(chat_id); chat << "/" << nick; // If sent from own user, use normal ID if (nick == fNick) { *id = fJid.bare().c_str(); return true; } *id = chat; return false; } void JabberHandler::_JoinRoom(const char* chat_id) { BString join_id(chat_id); join_id << "/" << fNick; gloox::MUCRoom* room = fRooms.ValueFor(chat_id); if (room == NULL) room = new gloox::MUCRoom(fClient, gloox::JID(join_id.String()), this, this); room->join(); fRooms.AddItem(BString(chat_id), room); } void JabberHandler::_MUCModeration(BMessage* msg) { BString chat_id = msg->FindString("chat_id"); BString user_id; BString body = msg->FindString("body"); gloox::MUCRoom* room = fRooms.ValueFor(chat_id); if (room == NULL || msg->FindString("user_id", &user_id) != B_OK) return; std::string nick = gloox::JID(user_id.String()).resource(); switch (msg->FindInt32("im_what")) { case IM_ROOM_KICK_PARTICIPANT: room->kick(nick, body.String()); break; case IM_ROOM_BAN_PARTICIPANT: room->ban(nick, body.String()); break; case IM_ROOM_MUTE_PARTICIPANT: room->revokeVoice(nick, body.String()); break; case IM_ROOM_UNMUTE_PARTICIPANT: room->grantVoice(nick, body.String()); break; } } const char* JabberHandler::_RoleTitle(gloox::MUCRoomRole role, gloox::MUCRoomAffiliation aff) { switch (role) { case gloox::RoleVisitor: return "Visitor"; case gloox::RoleParticipant: return "Member"; case gloox::RoleModerator: if (aff == gloox::AffiliationOwner) return "Owner"; return "Moderator"; } return "Invalid"; } int32 JabberHandler::_RolePerms(gloox::MUCRoomRole role, gloox::MUCRoomAffiliation aff) { switch (role) { case gloox::RoleVisitor: return 0 | PERM_READ | PERM_NICK; case gloox::RoleParticipant: return 0 | PERM_READ | PERM_WRITE | PERM_ROOM_SUBJECT; case gloox::RoleModerator: { int32 perm = 0 | PERM_READ | PERM_WRITE | PERM_ROOM_SUBJECT | PERM_KICK | PERM_MUTE; if (aff == gloox::AffiliationOwner) perm = perm | PERM_ROLECHANGE | PERM_BAN; return perm; } } return 0; } int32 JabberHandler::_RolePriority(gloox::MUCRoomRole role, gloox::MUCRoomAffiliation aff) { switch (role) { case gloox::RoleParticipant: return 1; case gloox::RoleModerator: if (aff == gloox::AffiliationOwner) return 3; return 2; } return 0; } BMessage JabberHandler::_SettingsTemplate(const char* username, bool serverOption) { BMessage stemplate('IMst'); BMessage usernameText; usernameText.AddString("name", "username"); usernameText.AddString("description", username); usernameText.AddInt32("type", 'CSTR'); stemplate.AddMessage("setting", &usernameText); BMessage passwordText; passwordText.AddString("name", "password"); passwordText.AddString("description", "Password"); passwordText.AddInt32("type", 'CSTR'); passwordText.AddBool("is_secret", true); stemplate.AddMessage("setting", &passwordText); BMessage serverText; serverText.AddString("name", "server"); serverText.AddString("description", "Server"); serverText.AddInt32("type", 'CSTR'); if (serverOption == true) stemplate.AddMessage("setting", &serverText); BMessage resourceText; resourceText.AddString("name", "resource"); resourceText.AddString("description", "Resource"); resourceText.AddInt32("type", 'CSTR'); resourceText.AddString("default", "Caya"); stemplate.AddMessage("setting", &resourceText); return stemplate; } /*********************************************************************** * gloox callbacks **********************************************************************/ void JabberHandler::onConnect() { // We are online BMessage msg(IM_MESSAGE); msg.AddInt32("im_what", IM_OWN_STATUS_SET); msg.AddInt32("status", CAYA_ONLINE); _SendMessage(&msg); fVCardManager->fetchVCard(fJid, this); } void JabberHandler::onDisconnect(gloox::ConnectionError e) { // We are offline BMessage msg(IM_MESSAGE); msg.AddInt32("im_what", IM_OWN_STATUS_SET); msg.AddInt32("status", CAYA_OFFLINE); _SendMessage(&msg); if (e == gloox::ConnNoError) { // Notify our offline status BString content(fUsername); content << " has logged out!"; _Notify(B_INFORMATION_NOTIFICATION, "Disconnected", content.String()); return; } // Handle error HandleConnectionError(e); } bool JabberHandler::onTLSConnect(const gloox::CertInfo& info) { return true; } void JabberHandler::onResourceBindError(const gloox::Error* error) { } void JabberHandler::handleRoster(const gloox::Roster& roster) { std::list msgs; BMessage contactListMsg(IM_MESSAGE); contactListMsg.AddInt32("im_what", IM_CONTACT_LIST); gloox::Roster::const_iterator it = roster.begin(); for (; it != roster.end(); ++it) { const char* jid = (*it).second->jidJID().full().c_str(); const char* name = (*it).second->name().c_str(); int32 subscription = (*it).second->subscription(); // Add jid to the server based contact list message contactListMsg.AddString("user_id", jid); // Contact information message BMessage infoMsg(IM_MESSAGE); infoMsg.AddInt32("im_what", IM_CONTACT_INFO); infoMsg.AddString("user_id", jid); infoMsg.AddString("user_name", name); infoMsg.AddInt32("status", CAYA_OFFLINE); // Groups gloox::StringList g = (*it).second->groups(); gloox::StringList::const_iterator it_g = g.begin(); for (; it_g != g.end(); ++it_g) infoMsg.AddString("group", (*it_g).c_str()); // Resources gloox::RosterItem::ResourceMap::const_iterator rit = (*it).second->resources().begin(); for (; rit != (*it).second->resources().end(); ++rit) infoMsg.AddString("resource", (*rit).first.c_str()); // Store contact info message to be sent later msgs.push_back(infoMsg); } // Send server based contact list _SendMessage(&contactListMsg); // Contact list and vCard request std::list::iterator msgsIt; for (msgsIt = msgs.begin(); msgsIt != msgs.end(); ++msgsIt) { BMessage msg = (*msgsIt); const char* jid = msg.FindString("user_id"); _SendMessage(&msg); fVCardManager->fetchVCard(gloox::JID(jid), this); } // This is a safe spot to say "READY", since this is called immediately // after logging in, whether the user has friends in the roster or not― // especially since loading all the users from the roster can take a while. BMessage readyMsg(IM_MESSAGE); readyMsg.AddInt32("im_what", IM_PROTOCOL_READY); _SendMessage(&readyMsg); } void JabberHandler::handleMessageSession(gloox::MessageSession* session) { // Delete previous session if (fSession) fClient->disposeMessageSession(fSession); fSession = session; // Register message handler fSession->registerMessageHandler(this); // Message event filter gloox::MessageEventFilter* messageFilter = new gloox::MessageEventFilter(fSession); messageFilter->registerMessageEventHandler(this); // Chat state filter gloox::ChatStateFilter* chatFilter = new gloox::ChatStateFilter(fSession); chatFilter->registerChatStateHandler(this); } void JabberHandler::handleMessage(const gloox::Message& m, gloox::MessageSession*) { // Only chat messages are handled now if (m.subtype() != gloox::Message::Chat) return; // We need a body if (m.body() == "") return; _EnsureUserChat(m.from().bare().c_str()); // Notify that a chat message was received BMessage msg(IM_MESSAGE); msg.AddString("user_id", m.from().bare().c_str()); msg.AddString("chat_id", m.from().bare().c_str()); msg.AddInt32("im_what", IM_MESSAGE_RECEIVED); if (m.subject() != "") msg.AddString("subject", m.subject().c_str()); msg.AddString("body", m.body().c_str()); _SendMessage(&msg); } void JabberHandler::handleMessageEvent(const gloox::JID& from, gloox::MessageEventType event) { } void JabberHandler::handleChatState(const gloox::JID& from, gloox::ChatStateType state) { printf("------ %d\n", state); // We're interested only in some states if (state == gloox::ChatStateActive || state == gloox::ChatStateInvalid) return; _EnsureUserChat(from.bare().c_str()); BMessage msg(IM_MESSAGE); msg.AddString("user_id", from.bare().c_str()); msg.AddString("chat_id", from.bare().c_str()); switch (state) { case gloox::ChatStateComposing: msg.AddInt32("im_what", IM_USER_STARTED_TYPING); break; case gloox::ChatStatePaused: msg.AddInt32("im_what", IM_USER_STOPPED_TYPING); break; case gloox::ChatStateGone: // TODO break; default: break; } _SendMessage(&msg); } void JabberHandler::handleMUCParticipantPresence(gloox::MUCRoom *room, const gloox::MUCRoomParticipant participant, const gloox::Presence &presence) { // participant.flags, particpiant.role // 0-0 (left), 0-* (joined/rolechange) gloox::MUCRoomRole role = participant.role; gloox::MUCRoomAffiliation aff = participant.affiliation; const char* nick = participant.nick->resource().c_str(); BString user_id; BString chat_id = _MUCChatId(room); bool isSelf = _MUCUserId(chat_id, nick, &user_id); if (chat_id.IsEmpty() == true || user_id.IsEmpty() == true) return; if (isSelf == true) { int im_what = IM_ROOM_JOINED; if (presence.presence() == 5) im_what = IM_ROOM_LEFT; BMessage joinedMsg(IM_MESSAGE); joinedMsg.AddInt32("im_what", im_what); joinedMsg.AddString("chat_id", chat_id); _SendMessage(&joinedMsg); _RoleChangedMsg(chat_id, user_id, role, aff); return; } _StatusSetMsg(user_id.String(), presence.presence(), presence.status().c_str(), ""); // If unavailable (disconnected/left chat) if (presence.presence() == 5) { _UserLeftMsg(chat_id, participant); return; } BMessage joinMsg(IM_MESSAGE); joinMsg.AddInt32("im_what", IM_ROOM_PARTICIPANT_JOINED); joinMsg.AddString("user_id", user_id); joinMsg.AddString("user_name", nick); joinMsg.AddString("chat_id", chat_id); _SendMessage(&joinMsg); _RoleChangedMsg(chat_id, user_id, role, aff); } void JabberHandler::handleMUCMessage(gloox::MUCRoom *room, const gloox::Message &m, bool priv) { BString user_id; BString chat_id = _MUCChatId(room); bool isSelf = _MUCUserId(chat_id, m.from().resource().c_str(), &user_id); if (chat_id.IsEmpty() == true || user_id.IsEmpty() == true) return; int32 im_what = IM_MESSAGE_RECEIVED; // We need a body if (m.body() == "") return; // If sent from own user, then IM_MESSAGE_SENT if (isSelf == true) im_what = IM_MESSAGE_SENT; // when() is only nonzero when sending backdated messages (logs) if (m.when() != 0) im_what = IM_LOGS_RECEIVED; // Notify that a chat message was received BMessage msg(IM_MESSAGE); msg.AddInt32("im_what", im_what); msg.AddString("user_id", user_id); msg.AddString("chat_id", chat_id); if (m.subject() != "") msg.AddString("subject", m.subject().c_str()); msg.AddString("body", m.body().c_str()); _SendMessage(&msg); } bool JabberHandler::handleMUCRoomCreation(gloox::MUCRoom *room) { return true; } void JabberHandler::handleMUCSubject(gloox::MUCRoom *room, const std::string &nick, const std::string &subject) { BString user_id; BString chat_id = _MUCChatId(room); bool isSelf = _MUCUserId(chat_id, nick.c_str(), &user_id); if (chat_id.IsEmpty() == true) return; BMessage msg(IM_MESSAGE); msg.AddInt32("im_what", IM_ROOM_SUBJECT_SET); msg.AddString("subject", subject.c_str()); msg.AddString("chat_id", chat_id); if (user_id.IsEmpty() == false) msg.AddString("user_id", user_id); _SendMessage(&msg); } void JabberHandler::handleMUCInviteDecline(gloox::MUCRoom *room, const gloox::JID &invitee, const std::string &reason) { } void JabberHandler::handleMUCError(gloox::MUCRoom *room, gloox::StanzaError error) { HandleStanzaError(error); } void JabberHandler::handleMUCInfo(gloox::MUCRoom *room, int features, const std::string &name, const gloox::DataForm *infoForm) { BString chat_id = _MUCChatId(room); BMessage metadata(IM_MESSAGE); metadata.AddInt32("im_what", IM_ROOM_METADATA); metadata.AddString("chat_id", chat_id); metadata.AddString("chat_name", name.c_str()); metadata.AddInt32("room_default_flags", 0 | ROOM_AUTOJOIN | ROOM_LOG_LOCALLY | ROOM_POPULATE_LOGS); metadata.AddInt32("room_disallowed_flags", 0 | ROOM_AUTOCREATE); _SendMessage(&metadata); } void JabberHandler::handleMUCItems(gloox::MUCRoom *room, const gloox::Disco::ItemList &items) { BString chat_id = _MUCChatId(room); BStringList nicks; BStringList ids; if (chat_id.IsEmpty() == true) return; for (auto item: items) { BString nick = item->jid().resource().c_str(); nicks.Add(nick); // Unless it's the user, derive ID from room resource if (fNick != nick) { BString user_id(chat_id); user_id << "/" << nick; ids.Add(user_id); } else ids.Add(BString(fJid.bare().c_str())); } BMessage msg(IM_MESSAGE); msg.AddStrings("user_id", ids); msg.AddStrings("user_name", nicks); msg.AddString("chat_id", chat_id); msg.AddInt32("im_what", IM_ROOM_PARTICIPANTS); _SendMessage(&msg); } void JabberHandler::handleMUCConfigList(gloox::MUCRoom* room, const gloox::MUCListItemList &items, gloox::MUCOperation operation) { } void JabberHandler::handleMUCConfigForm(gloox::MUCRoom* room, const gloox::DataForm &form) { } void JabberHandler::handleMUCConfigResult(gloox::MUCRoom* room, bool success, gloox::MUCOperation operation) { } void JabberHandler::handleMUCRequest(gloox::MUCRoom* room, const gloox::DataForm &form) { } void JabberHandler::handleItemAdded(const gloox::JID&) { } void JabberHandler::handleItemSubscribed(const gloox::JID&) { } void JabberHandler::handleItemUnsubscribed(const gloox::JID&) { } void JabberHandler::handleItemRemoved(const gloox::JID&) { } void JabberHandler::handleItemUpdated(const gloox::JID&) { } void JabberHandler::handleRosterPresence(const gloox::RosterItem& item, const std::string& resource, gloox::Presence::PresenceType type, const std::string& presenceMsg) { _StatusSetMsg(item.jidJID().full().c_str(), type, presenceMsg.c_str(), resource.c_str()); } void JabberHandler::handleSelfPresence(const gloox::RosterItem& item, const std::string&, gloox::Presence::PresenceType type, const std::string& presenceMsg) { BMessage msg(IM_MESSAGE); msg.AddInt32("im_what", IM_OWN_CONTACT_INFO); msg.AddString("protocol", Signature()); msg.AddString("user_id", item.jidJID().full().c_str()); msg.AddString("user_name", item.name().c_str()); msg.AddInt32("subscription", item.subscription()); msg.AddInt32("status", _GlooxStatusToCaya(type)); msg.AddString("message", presenceMsg.c_str()); // Groups gloox::StringList g = item.groups(); gloox::StringList::const_iterator it_g = g.begin(); for (; it_g != g.end(); ++it_g) msg.AddString("group", (*it_g).c_str()); // Resources gloox::RosterItem::ResourceMap::const_iterator rit = item.resources().begin(); for (; rit != item.resources().end(); ++rit) msg.AddString("resource", (*rit).first.c_str()); _SendMessage(&msg); } bool JabberHandler::handleSubscriptionRequest(const gloox::JID&, const std::string&) { return true; } bool JabberHandler::handleUnsubscriptionRequest(const gloox::JID&, const std::string&) { return true; } void JabberHandler::handleNonrosterPresence(const gloox::Presence&) { } void JabberHandler::handleRosterError(const gloox::IQ&) { } void JabberHandler::handleLog(gloox::LogLevel level, gloox::LogArea, const std::string& msg) { if (level >= gloox::LogLevelWarning) printf("%s\n", msg.c_str()); } void JabberHandler::handleVCard(const gloox::JID& jid, const gloox::VCard* card) { if (!card) return; gloox::VCard::Name name = card->name(); gloox::VCard::Photo photo = card->photo(); std::string fullName = name.family + " " + name.given; BMessage msg(IM_MESSAGE); msg.AddInt32("im_what", IM_EXTENDED_CONTACT_INFO); msg.AddString("user_id", jid.bare().c_str()); msg.AddString("user_name", card->nickname().c_str()); msg.AddString("family_name", name.family.c_str()); msg.AddString("given_name", name.given.c_str()); msg.AddString("middle_name", name.middle.c_str()); msg.AddString("prefix", name.prefix.c_str()); msg.AddString("suffix", name.suffix.c_str()); msg.AddString("full_name", fullName.c_str()); _SendMessage(&msg); // Return if there's no avatar icon if (!photo.binval.c_str()) return; if (_SetupAvatarCache() == B_OK) // Cache avatar icon and notify the change _CacheAvatar(jid.bare().c_str(), photo.binval.c_str(), photo.binval.length()); } void JabberHandler::handleVCardResult(gloox::VCardHandler::VCardContext context, const gloox::JID& jid, gloox::StanzaError) { //if (context == gloox::VCardHandler::FetchVCard) //else } InviteHandler::InviteHandler(gloox::ClientBase* client, JabberHandler* handler) : gloox::MUCInvitationHandler(client), fHandler(handler) { } void InviteHandler::handleMUCInvitation(const gloox::JID& room, const gloox::JID& from, const std::string& reason, const std::string& body, const std::string& password, bool cont, const std::string& thread) { std::string chat_name = room.resource().c_str(); BString chat_id = room.full().c_str(); if (chat_name.empty() == true) chat_name = chat_id.String(); if (password.empty() == false) chat_id << "#" << password.c_str(); BMessage invite(IM_MESSAGE); invite.AddInt32("im_what", IM_ROOM_INVITE_RECEIVED); invite.AddString("chat_id", chat_id); invite.AddString("chat_name", chat_name.c_str()); invite.AddString("user_id", from.bare().c_str()); if (reason.empty() == false) invite.AddString("body", reason.c_str()); invite.AddString("protocol", fHandler->Signature()); fHandler->MessengerInterface()->SendMessage(new BMessage(invite)); }