////////////////////////////////////////////////// // Blabber [JabberSpeak.cpp] ////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include #include #include #include "support/LogHandler.h" #include "ui/ModalAlertFactory.h" #include "BlabberApp.h" #include "AgentList.h" #include "GenericFunctions.h" #include "JabberSpeak.h" #include "JRoster.h" #include "MessageRepeater.h" #include "Messages.h" #include "TalkManager.h" #include "UserID.h" #include "XMLEntity.h" #include #include #include #include #include JabberSpeak *JabberSpeak::_instance = NULL; ////////////////////////////////////////////////// // CREATORS ////////////////////////////////////////////////// // FIXME use the GlooxHandler instead for all XMPP things JabberSpeak *JabberSpeak::Instance() { if (_instance == NULL) { _instance = new JabberSpeak(); } return _instance; } JabberSpeak::JabberSpeak() : XMLReader() , fClient(NULL) { // grab a handle to the settings now for convenience later _blabber_settings = BlabberSettings::Instance(); } JabberSpeak::~JabberSpeak() { _instance = NULL; delete fVCardManager; } void JabberSpeak::Reset() { if (!_reconnecting) { BlabberMainWindow::Instance()->Lock(); BlabberMainWindow::Instance()->ShowLogin(); BlabberMainWindow::Instance()->Unlock(); } if (!_reconnecting) { BookmarkManager::Instance().Disconnect(); TalkManager::Instance()->Reset(); } JRoster::Instance()->Lock(); JRoster::Instance()->RemoveAllUsers(); JRoster::Instance()->Unlock(); JRoster::Instance()->RefreshRoster(); // reset agent list AgentList::Instance()->RemoveAllAgents(); } void JabberSpeak::JabberSpeakReset() { _curr_realname = ""; _curr_login = ""; _password = ""; _am_logged_in = false; _reconnecting = false; _got_some_agent_info = false; _iq_map.clear(); } ////////////////////////////////////////////////// // STANDARD METHODS ////////////////////////////////////////////////// const string JabberSpeak::CurrentRealName() const { return _curr_realname; } const string JabberSpeak::CurrentLogin() const { return _curr_login; } ////////////////////////////////////////////////// // INCOMING COMMUNICATION ////////////////////////////////////////////////// void JabberSpeak::OnTag(XMLEntity *entity) { char buffer[4096]; // general buffer space string iq_id; // used for IQ tags static int seen_streams = 0; // handle disconnection if (entity->IsCompleted() && !strcasecmp(entity->Name(), "stream:stream")) { ++seen_streams; if (seen_streams % 2 == 1) { Reset(); MessageRepeater::Instance()->PostMessage(JAB_DISCONNECTED); } return; } // handle IQs (always closing tags) if (entity->IsCompleted() && !strcasecmp(entity->Name(), "iq")) { if (entity->Attribute("id")) { iq_id = entity->Attribute("id"); } if (!strcasecmp(entity->Attribute("type"), "error")) { // get the intent of the IQ message if (_iq_map.count(iq_id) > 0) { // process based on the intent iq_intent intent = _iq_map[iq_id]; // for errors on registration if (intent == REGISTER) { if (entity->Attribute("from") && AgentList::Instance()->GetAgentByID(entity->Attribute("from"))) { const char *agent_name = AgentList::Instance()->GetAgentByID(entity->Attribute("from"))->Name().c_str(); sprintf(buffer, "You were refused registration information from the %s for the following reason:\n\n%s", agent_name, entity->Child("error")->Data()); } else { sprintf(buffer, "You were refused registration information from an unidentifying Jabber service for the following reason:\n\n%s", entity->Child("error")->Data()); } ModalAlertFactory::Alert(buffer, "Oh, well.", NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT); } // for errors on registration if (intent == SEND_REGISTER) { if (entity->Attribute("from") && AgentList::Instance()->GetAgentByID(entity->Attribute("from"))) { const char *agent_name = AgentList::Instance()->GetAgentByID(entity->Attribute("from"))->Name().c_str(); sprintf(buffer, "Your registration attempt was refused by the %s for the following reason:\n\n%s", agent_name, entity->Child("error")->Data()); } else { sprintf(buffer, "Your registration attempt was refused by an unidentifying Jabber service for the following reason:\n\n%s", entity->Child("error")->Data()); } ModalAlertFactory::Alert(buffer, "Oh, well.", NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT); } // for errors on unregistration if (intent == UNREGISTER) { if (entity->Attribute("from") && AgentList::Instance()->GetAgentByID(entity->Attribute("from"))) { const char *agent_name = AgentList::Instance()->GetAgentByID(entity->Attribute("from"))->Name().c_str(); sprintf(buffer, "You were refused unregistration information from the %s for the following reason:\n\n%s", agent_name, entity->Child("error")->Data()); } else { sprintf(buffer, "You were refused unregistration information from an unidentifying Jabber service for the following reason:\n\n%s", entity->Child("error")->Data()); } ModalAlertFactory::Alert(buffer, "Oh, well.", NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT); } } } // handle RESULT only (success) if (!strcasecmp(entity->Attribute("type"), "result")) { // get the intent of the IQ message if (_iq_map.count(iq_id) > 0) { // process based on the intent iq_intent intent = _iq_map[iq_id]; // for the agents list if (intent == AGENTS) { _ParseAgentList(entity); } // for completed registration if (intent == SEND_REGISTER) { if (entity->Attribute("from") && AgentList::Instance()->GetAgentByID(entity->Attribute("from"))) { const char *agent_name = AgentList::Instance()->GetAgentByID(entity->Attribute("from"))->Name().c_str(); sprintf(buffer, "Your registration attempt with the %s has been accepted.", agent_name); AgentList::Instance()->GetAgentByID(entity->Attribute("from"))->SetRegisteredFlag(true); } else { sprintf(buffer, "Your registration attempt with an unidentifying Jabber service has been accepted."); } ModalAlertFactory::Alert(buffer, "Yeah!", NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT); } } } } } void JabberSpeak::_ParseAgentList(XMLEntity *iq_agent_entity) { XMLEntity *entity = iq_agent_entity; // go one level deep to query if (entity->Child("query")) { entity = entity->Child("query"); } else { return; } // iterate through child 'item' tags for (int i=0; iCountChildren(); ++i) { // handle the item child if (!strcasecmp(entity->Child(i)->Name(), "agent")) { // create a new agent Agent *agent = new Agent(); // add data about the agent if (entity->Child(i)->Attribute("jid")) { agent->SetJID(entity->Child(i)->Attribute("jid")); } if (entity->Child(i)->Child("name")) { agent->SetName(entity->Child(i)->Child("name")->Data()); } if (entity->Child(i)->Child("description")) { agent->SetDescription(entity->Child(i)->Child("description")->Data()); } if (entity->Child(i)->Child("service")) { agent->SetService(entity->Child(i)->Child("service")->Data()); } agent->SetRegisterableFlag(entity->Child(i)->Child("register")); if (entity->Child(i)->Child("transport")) { agent->SetTransport(entity->Child(i)->Child("transport")->Data()); } agent->SetSearchableFlag(entity->Child(i)->Child("search")); // add to the AgentList BUGBUG need a return value AgentList::Instance()->AddAgent(agent); } } _got_some_agent_info = true; _am_logged_in = true; } ////////////////////////////////////////////////// // OUTGOING COMMUNICATION ////////////////////////////////////////////////// void JabberSpeak::SendConnect(string username, string password, string realname, bool suppress_auto_connect) { // if there's another application instance running, suppress auto-login BList *teams = new BList; // query for this app signature be_roster->GetAppList("application/jabber", teams); if ((username.size() == 0 || password.size() == 0) && teams->CountItems() > 1) { suppress_auto_connect = true; } // if we don't have all the data, query for it if (username.size() == 0 || password.size() == 0 || gloox::JID(username).server().size() == 0) { // check auto-login if (suppress_auto_connect == false && _blabber_settings->Tag("auto-login")) { // last login data should be used (make sure it's there though) realname = (_blabber_settings->Data("last-realname")) ? _blabber_settings->Data("last-realname") : ""; username = (_blabber_settings->Data("last-login")) ? _blabber_settings->Data("last-login") : ""; password = (_blabber_settings->Data("last-password")) ? _blabber_settings->Data("last-password") : ""; } // if we still don't have all the data, query for it if (username.size() == 0 || password.size() == 0 || gloox::JID(username).server().size() == 0) { BlabberMainWindow::Instance()->Lock(); BlabberMainWindow::Instance()->ShowLogin(); BlabberMainWindow::Instance()->Unlock(); return; } } // save username/password _curr_realname = realname; _curr_login = username; _password = password; // PLACEHOLDER // spawn listener thread (communication from remote machine) resume_thread(_connection_thread_id = spawn_thread(JabberSpeak::_SpawnConnectionThread, "connection_listener", B_LOW_PRIORITY, this)); } int32 JabberSpeak::_SpawnConnectionThread(void *obj) { ((JabberSpeak *)obj)->_ConnectionThread(); // Don't care about the return value return 1; } string JabberSpeak::GetRealServer() { return gloox::JID(_curr_login).server(); } int JabberSpeak::GetRealPort() { return 5222; //default jabber port. } void JabberSpeak::_ConnectionThread() { gloox::JID jid(_curr_login); fClient = new gloox::Client(jid, _password); // Prepare for handling carbons fClient->registerStanzaExtension(new gloox::Forward()); fClient->registerStanzaExtension(new gloox::Carbons()); // Register for roster events fClient->rosterManager()->registerRosterListener(JRoster::Instance()); // Register for logging fClient->logInstance().registerLogHandler(gloox::LogLevelDebug, gloox::LogAreaXmlOutgoing, new LogHandler); // Register for connection events fClient->registerConnectionListener(this); // Register for incoming chat sessions fClient->registerMessageSessionHandler(TalkManager::Instance()); // Setup for handling VCard fVCardManager = new gloox::VCardManager(fClient); // Prepare our answer to version requests for disco _ProcessVersionRequest(); // Set up for getting avatar notifications fClient->disco()->addFeature("urn:xmpp:avatar:metadata+notify"); fPubSubManager = new gloox::PubSub::Manager(fClient); fClient->registerStanzaExtension(new gloox::PubSub::Event()); // And let's go! fClient->connect(); } void JabberSpeak::SendDisconnect() { _am_logged_in = false; fClient->disconnect(); } void JabberSpeak::SendSubscriptionRequest(string username) { gloox::Subscription subscription(gloox::Subscription::Subscribe, gloox::JID(username)); fClient->send(subscription); } void JabberSpeak::SendUnsubscriptionRequest(string username) { gloox::Subscription subscription(gloox::Subscription::Unsubscribe, gloox::JID(username)); fClient->send(subscription); } void JabberSpeak::SetFriendlyName(const gloox::JID& who, BString friendlyName) { gloox::RosterItem* item = fClient->rosterManager()->getRosterItem(who); if (item) { item->setName(friendlyName.String()); fClient->rosterManager()->synchronize(); } else { const gloox::ConferenceListItem* bookmark = BookmarkManager::Instance().GetBookmark(who.full().c_str()); if (bookmark) { BookmarkManager::Instance().SetBookmark(who.full().c_str(), bookmark->nick.c_str(), friendlyName, bookmark->autojoin); } else { debugger("User not found."); } } } void JabberSpeak::RemoveFromRoster(const UserID *removed_user) { fClient->rosterManager()->remove(gloox::JID(removed_user->Handle())); } void JabberSpeak::SendMessage(const gloox::Message::MessageType type, const gloox::JID& user, string body, string thread_id) { gloox::Message message(type, user, body, gloox::EmptyString, thread_id); fClient->send(message); } void JabberSpeak::SendMessage(const gloox::Message::MessageType type, string group_room, string body) { gloox::Message message(type, gloox::JID(group_room), body); fClient->send(message); } void JabberSpeak::SendPresence(gloox::Presence::PresenceType type, string status) { gloox::Presence presence(type, gloox::JID(), status); fClient->send(presence); } void JabberSpeak::SendLastPresence() { bool last_used_custom = BlabberSettings::Instance()->Tag("last-used-custom-status"); gloox::Presence::PresenceType last_status = (gloox::Presence::PresenceType)atoi( BlabberSettings::Instance()->Data("last-custom-exact-status")); const char *last_custom_status = BlabberSettings::Instance()->Data("last-custom-more-exact-status"); if (last_used_custom) { SendPresence(last_status, last_custom_status); BlabberMainWindow::Instance()->SetCustomStatus(last_custom_status); } else { if (last_status == gloox::Presence::Chat) { BlabberMainWindow::Instance()->PostMessage(BLAB_AVAILABLE_FOR_CHAT); } else if (last_status == gloox::Presence::Away) { BlabberMainWindow::Instance()->PostMessage(BLAB_AWAY_TEMPORARILY); } else if (last_status == gloox::Presence::XA) { BlabberMainWindow::Instance()->PostMessage(BLAB_AWAY_EXTENDED); } else if (last_status == gloox::Presence::DND) { BlabberMainWindow::Instance()->PostMessage(BLAB_DO_NOT_DISTURB); } else { BlabberMainWindow::Instance()->PostMessage(BLAB_AVAILABLE_FOR_CHAT); } } } void JabberSpeak::SendGroupUnvitation(string _group_room, string _group_username) { // assemble group ID string group_presence = _group_room + "/" + _group_username; // Send presence Stanza gloox::Presence presence(gloox::Presence::Unavailable, gloox::JID(group_presence)); fClient->send(presence); // Disable autologin in bookmarks and store username const gloox::ConferenceListItem* item = BookmarkManager::Instance().GetBookmark(_group_room.c_str()); if (item) { BookmarkManager::Instance().SetBookmark(_group_room.c_str(), _group_username.c_str(), item->name.c_str(), false); } } void JabberSpeak::RegisterWithAgent(string) { #if 0 XMLEntity *entity; // assemble attributes; strcpy(atts[0], "type"); strcpy(atts[1], "get"); strcpy(atts[2], "id"); //strcpy(atts[3], GenericFunctions::GenerateUniqueID().c_str()); strcpy(atts[4], "to"); strcpy(atts[5], AgentList::Instance()->GetAgentByService(agent)->JID().c_str()); strcpy(atts_query[0], "xmlns"); strcpy(atts_query[1], "jabber:iq:register"); // send XML command entity = new XMLEntity("iq", (const char **)atts); entity->AddChild("query", (const char **)atts_query, NULL); // log command _iq_map[atts[3]] = REGISTER; char *str = entity->ToString(); free(str); delete entity; #endif } void JabberSpeak::UnregisterWithAgent(string agent) { // Find registration in roster JRoster::Instance()->Lock(); UserID *user = NULL; if (AgentList::Instance()->GetAgentByService(agent)) { user = JRoster::Instance()->FindUser(JRoster::TRANSPORT_ID, AgentList::Instance()->GetAgentByService(agent)->JID()); } // call RemoveFromRoster() if (user) { RemoveFromRoster(user); } JRoster::Instance()->Unlock(); } void JabberSpeak::RequestVCard(const gloox::JID& jid) { fVCardManager->fetchVCard(jid, this); } void JabberSpeak::RequestPubSubItem(const gloox::JID& jid, const std::string& node, const std::string& subid, gloox::PubSub::ResultHandler* handler) { fPubSubManager->requestItems(jid, node, subid, 1, handler); } void JabberSpeak::_ProcessVersionRequest(void) { string strVersion("Haiku"); BPath path; if (find_directory(B_BEOS_LIB_DIRECTORY, &path) == B_OK) { path.Append("libbe.so"); BAppFileInfo appFileInfo; version_info versionInfo; BFile file; if (file.SetTo(path.Path(), B_READ_ONLY) == B_OK && appFileInfo.SetTo(&file) == B_OK && appFileInfo.GetVersionInfo(&versionInfo, B_APP_VERSION_KIND) == B_OK && versionInfo.short_info[0] != '\0') strVersion = versionInfo.short_info; if (strVersion == "Walter") strVersion = "Haiku"; BString appVersion; appVersion << " " << versionInfo.major << "." << versionInfo.middle; if (versionInfo.minor > 0) appVersion << "." << versionInfo.minor; // Add the version variety const char* variety = NULL; switch (versionInfo.variety) { case B_DEVELOPMENT_VERSION: variety = "development"; break; case B_ALPHA_VERSION: variety = "alpha"; break; case B_BETA_VERSION: variety = "beta"; break; case B_GAMMA_VERSION: variety = "gamma"; break; case B_GOLDEN_MASTER_VERSION: variety = "gold master"; break; } if (variety) appVersion << "-" << variety; strVersion += appVersion; } fClient->disco()->setVersion("Renga", APP_VERSION, strVersion); } void JabberSpeak::onConnect() { fprintf(stderr, "Logged in!\n"); MessageRepeater::Instance()->PostMessage(JAB_LOGGED_IN); //SendLastPresence(); BookmarkManager::Instance().Connect(); // Enable message carbons gloox::IQ iq(gloox::IQ::Set, gloox::JID()); iq.addExtension(new gloox::Carbons(gloox::Carbons::Enable)); fClient->send(iq, this, 1); _reconnecting = false; } void JabberSpeak::onDisconnect(gloox::ConnectionError e) { BookmarkManager::Instance().Disconnect(); if (e == gloox::ConnAuthenticationFailed) { gloox::AuthenticationError ae = fClient->authError(); if (ae == gloox::SaslNotAuthorized) { // Incorrect login or password, highlight them in main window // The code below gets us back to login screen SendNotices(kAuthenticationFailed); } else { fprintf(stderr, "%s(%d) -> %d\n", __PRETTY_FUNCTION__, e, ae); } } else { fprintf(stderr, "%s(%d)\n", __PRETTY_FUNCTION__, e); } // reset XMLReader XMLReader::Reset(); if (_am_logged_in) { // automatic reconnection _reconnecting = true; MessageRepeater::Instance()->PostMessage(JAB_RECONNECTING); _got_some_agent_info = false; _am_logged_in = false; // reset networking Reset(); SendConnect(); } } bool JabberSpeak::onTLSConnect(const gloox::CertInfo& info) { // Certificate is verified as valid by gloox already return info.status == gloox::CertOk; } bool JabberSpeak::handleIq(const gloox::IQ&) { return true; } void JabberSpeak::handleIqID(const gloox::IQ&, int) { } //#pragma mark - VCardHandler void JabberSpeak::handleVCard(const gloox::JID& jid, const gloox::VCard* vcard) { BMessage message; message.AddString("jid", jid.bare().c_str()); if (!vcard->formattedname().empty()) message.AddString("Name", vcard->formattedname().c_str()); if (!vcard->nickname().empty()) message.AddString("Nickname", vcard->nickname().c_str()); if (!vcard->url().empty()) message.AddString("url", vcard->url().c_str()); if (!vcard->bday().empty()) message.AddString("Birhtday", vcard->bday().c_str()); if (!vcard->title().empty()) message.AddString("Title", vcard->title().c_str()); if (!vcard->role().empty()) message.AddString("Role", vcard->role().c_str()); if (!vcard->note().empty()) message.AddString("Note", vcard->note().c_str()); if (!vcard->desc().empty()) message.AddString("Description", vcard->desc().c_str()); if (!vcard->mailer().empty()) message.AddString("Mailer", vcard->mailer().c_str()); if (!vcard->rev().empty()) message.AddString("Revision", vcard->rev().c_str()); if (!vcard->uid().empty()) message.AddString("User ID", vcard->uid().c_str()); if (!vcard->tz().empty()) message.AddString("Timezone", vcard->tz().c_str()); if (!vcard->sortstring().empty()) message.AddString("Sort String", vcard->sortstring().c_str()); if (!vcard->org().name.empty()) message.AddString("Organization", vcard->org().name.c_str()); for (std::string unit: vcard->org().units) message.AddString("Unit", unit.c_str()); // Things not meant for direct display as strings for (gloox::VCard::Email email: vcard->emailAddresses()) { message.AddString("mail:address", email.userid.c_str()); int32 flags = 0; if (email.pref) flags |= 1; if (email.home) flags |= 2; if (email.work) flags |= 4; if (email.internet) flags |= 8; if (email.x400) flags |= 16; message.AddInt32("mail:flags", flags); } for (gloox::VCard::Telephone tel: vcard->telephone()) { message.AddString("phone:number", tel.number.c_str()); int32 flags = 0; if (tel.pref) flags |= 1; if (tel.home) flags |= 2; if (tel.work) flags |= 4; if (tel.voice) flags |= 8; if (tel.fax) flags |= 16; if (tel.pager) flags |= 32; if (tel.msg) flags |= 64; if (tel.cell) flags |= 128; if (tel.video) flags |= 256; if (tel.bbs) flags |= 512; if (tel.modem) flags |= 1024; if (tel.isdn) flags |= 2048; if (tel.pcs) flags |= 4096; message.AddInt32("phone:flags", flags); } for (gloox::VCard::Label label: vcard->labels()) { BString str; for (auto line: label.lines) str << line.c_str() << "\n"; message.AddString("label:lines", str); int32 flags = 0; if (label.pref) flags |= 1; if (label.home) flags |= 2; if (label.work) flags |= 4; if (label.postal) flags |= 8; if (label.parcel) flags |= 16; if (label.dom) flags |= 32; if (label.intl) flags |= 64; message.AddInt32("label:flags", flags); } for (gloox::VCard::Address address: vcard->addresses()) { // Allow empty strings because we don't want to mix different addresses // and all we get to differenciate them is the offset in the BMessage message.AddString("address:pobox", address.pobox.c_str()); message.AddString("address:extadd", address.extadd.c_str()); message.AddString("address:street", address.street.c_str()); message.AddString("address:locality", address.locality.c_str()); message.AddString("address:region", address.region.c_str()); message.AddString("address:postcode", address.pcode.c_str()); message.AddString("address:country", address.ctry.c_str()); int32 flags = 0; if (address.pref) flags |= 1; if (address.home) flags |= 2; if (address.work) flags |= 4; if (address.postal) flags |= 8; if (address.parcel) flags |= 16; if (address.dom) flags |= 32; if (address.intl) flags |= 64; message.AddInt32("address:flags", flags); } if (!vcard->geo().latitude.empty()) message.AddString("geo:lat", vcard->geo().latitude.c_str()); if (!vcard->geo().longitude.empty()) message.AddString("geo:lon", vcard->geo().longitude.c_str()); message.AddInt32("classification", vcard->classification()); if (!vcard->photo().binval.empty()) { message.AddData("photo:binval", B_RAW_TYPE, vcard->photo().binval.c_str(), vcard->photo().binval.length()); } if (!vcard->photo().extval.empty()) message.AddString("photo:url", vcard->photo().extval.c_str()); if (!vcard->photo().type.empty()) { message.AddData("photo:type", B_MIME_TYPE, vcard->photo().type.c_str(), vcard->photo().type.length()); } if (!vcard->logo().binval.empty()) { message.AddData("logo:binval", B_RAW_TYPE, vcard->logo().binval.c_str(), vcard->logo().binval.length()); } if (!vcard->logo().extval.empty()) message.AddString("logo:url", vcard->logo().extval.c_str()); if (!vcard->logo().type.empty()) { message.AddData("logo:type", B_MIME_TYPE, vcard->logo().type.c_str(), vcard->logo().type.length()); } SendNotices(kVCardReceived, &message); } void JabberSpeak::handleVCardResult(VCardContext, const gloox::JID&, gloox::StanzaError) { puts(__func__); }