26 Oktober 2019

gloox ist eine CPP Bibliothek für XMPP. In diesem Artikel werden wir einen kleinen XMPP Bot in CPP schreiben.

Vorbereitung

gloox

Die offizielle Homepage von gloox ist https://camaya.net/gloox. Hier findet ihr auch die API https://camaya.net/api/gloox-1.0.22 zur aktuellen Version.

boost

Für ein paar Dinge werden wir die boost Bibliothek verwendet. https://www.boost.org/

Build System

Als Build System verwenden wir https://cmake.org/

Installation auf einem Debian System

aptitude install libgloox17 libgloox-dev libboost-dev libboost-doc cmake g++

Projektstruktur

mkdir src doc build test
touch bootstap.sh src/CMakeLists.txt src/main.cpp
chmod a+x bootstap.sh
$ tree
 tree
.
├── bootstap.sh
├── build
├── doc
├── src
│   ├── CMakeLists.txt
│   └── main.cpp
└── test

4 directories, 3 files

In die bootstap.sh tragen wir jetzt die Ausführung von cmake im Build Verzeichnis ein. Die Inhalt der bootstap.sh Datei sieht dann wie folgt aus

cd build
cmake ../src
cmake_minimum_required(VERSION 3.13)
project (hawkbit-bot)

file(GLOB SOURCES *.cpp)

find_package(Boost 1.67 COMPONENTS log program_options)
include(FindPkgConfig)
pkg_check_modules(Gloox  gloox>=1.0.22)
if(Boost_FOUND)
  include_directories(${Boost_INCLUDE_DIRS})
  SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
  add_executable(hawkbit-bot ${SOURCES})
  target_link_libraries(hawkbit-bot ${Boost_LIBRARIES} ${Gloox_LIBRARIES} )
endif()
#include <stdlib.h>
#include <iostream>

int main(int argc, char *argv[]) {
  std::cout << "Hello" << std::endl;
  return EXIT_SUCCESS;
}
./bootstap.sh
cd build
make
./xmpp-messenger-bot

Konfiguration

Es ist sicherlich sinnvoll, dass ein Bot eine Konfigurationsdatei hat. Hier können beispielsweise JID und Passwort sowie die Räume eingetragen werden.

[account]
jid = local@domain.tld
password = mypasswd
resource = hawkbit-bot
nick = Tux

[muc]
auto.join = muc1@conference.domain.tld
auto.join = muc2@conference.domain.tld
touch src/HawkbitBotBasic.hpp src/ConfigFileParser.hpp src/ConfigFileParser.cpp
#ifndef HAWKBIT_BOT_BASIC_H_
#define HAWKBIT_BOT_BASIC_H_

// Application parameters
#define HAWKBIT_BOT_NAME "Hawkbit-Bot"
#define HAWKBIT_BOT_VERSION "0.0.1-DEV"
#define HAWKBIT_BOT_HASH "$Id: $"

// Defaults
#define HAWKBIT_BOT_DEFAULT_CONFIG "hawkbit-bot.conf"

// Boots Logging

#define BOOST_LOG_DYN_LINK 1
#include <boost/log/trivial.hpp>

#endif // HAWKBIT_BOT_BASIC_H_
#ifndef HAWKBIT_BOT_CONFIG_FILE_PARSER_H_
#define HAWKBIT_BOT_CONFIG_FILE_PARSER_H_

#include <boost/program_options.hpp>
using namespace boost::program_options;

class ConfigFileParser {

public:
  ConfigFileParser(std::string file);
  variables_map getVariablesMap();
  options_description getDescription();

private:
  variables_map vm;
  options_description desc;
};

#endif // HAWKBIT_BOT_CONFIG_FILE_PARSER_H_
#include "ConfigFileParser.hpp"
#include "HawkbitBotBasic.hpp"
#include <fstream>

ConfigFileParser::ConfigFileParser(std::string file) {
  BOOST_LOG_TRIVIAL(info) << "Reading config file " << file;
  try {
    desc.add_options()("account.jid", value<std::string>()->required(), "JID")(
        "account.password", value<std::string>()->required(),
        "Password")("account.resource", value<std::string>(), "Resource")(
        "account.nick", value<std::string>(), "Nickname")(
        "muc.auto.join", value<std::vector<std::string>>()->composing(),
        "Auto Join MUCs");

    std::ifstream ifs(file.c_str());
    store(parse_config_file(ifs, desc), vm);
    notify(vm);
    ifs.close();

  } catch (const error &ex) {
    BOOST_LOG_TRIVIAL(error) << ex.what();
  }
}
variables_map ConfigFileParser::getVariablesMap() { return vm; }
#include "HawkbitBotBasic.hpp"
#include "ConfigFileParser.hpp"

#include <stdlib.h>
#include <iostream>
#include <boost/algorithm/string.hpp>

int main(int argc, char *argv[]) {
  BOOST_LOG_TRIVIAL(info) << "Starting " << HAWKBIT_BOT_NAME
                          << " Version: " HAWKBIT_BOT_VERSION << " ("
                          << HAWKBIT_BOT_HASH << ")";

  ConfigFileParser conf(HAWKBIT_BOT_DEFAULT_CONFIG);

  std::vector<std::string> strs;
  boost::split(strs, conf.getVariablesMap()["account.jid"].as<std::string>(),
               boost::is_any_of("@"));
  std::string user = strs.at(0);
  std::string host = strs.at(1);
  std::string resource =
      conf.getVariablesMap()["account.resource"].as<std::string>();
  std::string passwd =
      conf.getVariablesMap()["account.password"].as<std::string>();

  BOOST_LOG_TRIVIAL(info) << "Config for " << user << "@" << host << "/"
                          << resource;
  return EXIT_SUCCESS;
}
touch hawkbit-bot.conf
[account]
jid = local@domain.tld
password = mypasswd
resource = hawkbit-bot
nick = Tux

[muc]
auto.join = muc1@conference.domain.tld
auto.join = muc2@conference.domain.tld
$ ./hawkbit-bot
[2019-10-25 21:39:21.998635] [0x00007fc02f4c3980] [info]    Starting Hawkbit-Bot Version: 0.0.1-DEV ($Id$)
[2019-10-25 21:39:21.998664] [0x00007fc02f4c3980] [info]    Reading config file hawkbit-bot.conf
[2019-10-25 21:39:21.998819] [0x00007fc02f4c3980] [info]    Config for local@domain.tld/hawkbit-bot

XMPP Client

#ifndef XMPP_CLIENT_SESSION_H___
#define XMPP_CLIENT_SESSION_H___
#include "HawkbitBotBasic.hpp"

#include <gloox/chatstatehandler.h>
#include <gloox/client.h>
#include <gloox/connectionlistener.h>
#include <gloox/disco.h>
#include <gloox/message.h>
#include <gloox/messageeventhandler.h>
#include <gloox/messagehandler.h>
#include <gloox/messagesessionhandler.h>
#include <gloox/mucroom.h>
#include <gloox/rostermanager.h>

using namespace gloox;

class XmppClientSession : public MessageHandler,
                          ConnectionListener,
                          MessageEventHandler,
                          MessageSessionHandler,
                          MUCRoomHandler,
			  LogHandler {

public:
  bool setup(std::string user, std::string host, std::string resource,
             std::string password);
  void connect();
  void join(std::string mucroom);

  virtual void handleMessage(const Message &stanza, MessageSession *session);
  virtual void handleMessageEvent(const JID &from, MessageEventType event);
  virtual void handleMessageSession(MessageSession *session);
  virtual void onConnect();
  virtual void onDisconnect(ConnectionError e);
  virtual bool onTLSConnect(const CertInfo &info);
  virtual void onResourceBind(const std::string &resource);
  virtual void onResourceBindError(const Error *error);
  virtual void onSessionCreateError(const Error *error);

  virtual void
  handleMUCParticipantPresence(MUCRoom *room,
                               const MUCRoomParticipant participant,
                               const Presence &presence);
  virtual void handleMUCMessage(MUCRoom *room, const Message &msg, bool priv);

  virtual bool handleMUCRoomCreation(MUCRoom *room);

  virtual void handleMUCSubject(MUCRoom *room, const std::string &nick,
                                const std::string &subject);

  virtual void handleMUCInviteDecline(MUCRoom *room, const JID &invitee,
                                      const std::string &reason);

  virtual void handleMUCError(MUCRoom *room, StanzaError error);
  virtual void handleMUCInfo(MUCRoom *room, int features,
                             const std::string &name, const DataForm *infoForm);

  virtual void handleMUCItems(MUCRoom *room, const Disco::ItemList &items);
  virtual void handleLog( LogLevel level, LogArea area, const std::string& message );
private:
  Client *j;
};

#endif // XMPP_CLIENT_SESSION_H___
#include "XmppClientSession.hpp"

#include <gloox/client.h>
#include <gloox/connectionlistener.h>
#include <gloox/disco.h>
#include <gloox/message.h>
#include <gloox/messagehandler.h>
#include <gloox/messagesession.h>
#include <gloox/mucroom.h>
#include <gloox/rostermanager.h>

using namespace gloox;

bool XmppClientSession::setup(std::string user, std::string host,
                              std::string resource, std::string password) {
  std::string id(user + '@' + host + '/' + resource);
  JID jid(id);
  j = new Client(jid, password);
  j->disco()->setVersion(HAWKBIT_BOT_NAME, HAWKBIT_BOT_VERSION);
  j->disco()->setIdentity("client", HAWKBIT_BOT_NAME);
  j->registerConnectionListener(this);
  j->registerMessageHandler(this);
  j->logInstance().registerLogHandler(LogLevel::LogLevelDebug, LogArea::LogAreaAll, this);
  return true;
}

void XmppClientSession::connect() {
  BOOST_LOG_TRIVIAL(info) << "Connecting... ";
j->connect();
  BOOST_LOG_TRIVIAL(info) << "Connecting... done!";
}

void XmppClientSession::join(std::string mucroom) {
  JID roomJID(mucroom);
  MUCRoom *m_room = new MUCRoom(j, roomJID, this, 0);
  BOOST_LOG_TRIVIAL(info) << "Join room: " << mucroom;
  m_room->join();
}

void XmppClientSession::handleMessageSession(MessageSession *session) {
  BOOST_LOG_TRIVIAL(info) << "handleMessageSession";
}

void XmppClientSession::handleMessage(const Message &stanza,
                                      MessageSession *session = 0) {
  BOOST_LOG_TRIVIAL(info) << "Message: " << stanza.id() << " "
                          << stanza.xmlLang();
}

void XmppClientSession::handleMessageEvent(const JID &from,
                                           MessageEventType event) {
  BOOST_LOG_TRIVIAL(info) << "Message: ";
}

void XmppClientSession::onConnect() { BOOST_LOG_TRIVIAL(info) << "onConnect"; }

void XmppClientSession::onDisconnect(ConnectionError e) {
  BOOST_LOG_TRIVIAL(error) << "onDisconnect:" << e;
  BOOST_LOG_TRIVIAL(error) << this->j->streamError();
}

bool XmppClientSession::onTLSConnect(const CertInfo &info) {
  BOOST_LOG_TRIVIAL(info) << "onTLSConnect:";
 	BOOST_LOG_TRIVIAL(info) << "Cert Status: "  << info.status;
 	BOOST_LOG_TRIVIAL(info) << "Cert Chain: " <<  info.chain;
 	BOOST_LOG_TRIVIAL(info) << "Cert Issuer: " << info.issuer;
 	BOOST_LOG_TRIVIAL(info) << "Cert Server: " << info.server;
	time_t date_from_t = (time_t) info.date_from;
	time_t date_to_t = (time_t) info.date_to;
	BOOST_LOG_TRIVIAL(info) << "Cert from: " << ctime(&date_from_t);
	BOOST_LOG_TRIVIAL(info) << "Cert to: " << ctime(&date_to_t);
 	BOOST_LOG_TRIVIAL(info) << "Cert protocol: " << info.protocol;
 	BOOST_LOG_TRIVIAL(info) << "Cert cipher: " << info.cipher;
 	BOOST_LOG_TRIVIAL(info) << "Cert mac: " << info.mac;
 	BOOST_LOG_TRIVIAL(info) << "Cert compression: " << info.compression;

  return true;
}

void XmppClientSession::onResourceBind(const std::string &resource) {
  BOOST_LOG_TRIVIAL(info) << "onResourceBind:" << resource;
}

void XmppClientSession::onResourceBindError(const Error *error) {
  BOOST_LOG_TRIVIAL(error) << "SessionCreateError:" << error;
}
void XmppClientSession::onSessionCreateError(const Error *error) {
  BOOST_LOG_TRIVIAL(error) << "SessionCreateError:" << error;
}

void XmppClientSession::handleMUCParticipantPresence(
    MUCRoom *room, const MUCRoomParticipant participant,
    const Presence &presence) {
  BOOST_LOG_TRIVIAL(info) << "handleMUCParticipantPresence";
}

void XmppClientSession::handleMUCMessage(MUCRoom *room, const Message &msg,
                                         bool priv) {
  BOOST_LOG_TRIVIAL(info) << "handleMUCMessage: " << msg.body();
}

bool XmppClientSession::handleMUCRoomCreation(MUCRoom *room) {
  BOOST_LOG_TRIVIAL(info) << "handleMUCRoomCreation";
  return handleMUCRoomCreation(room);
}

void XmppClientSession::handleMUCSubject(MUCRoom *room, const std::string &nick,
                                         const std::string &subject) {
  BOOST_LOG_TRIVIAL(info) << "handleMUCSubject";
}

void XmppClientSession::handleMUCInviteDecline(MUCRoom *room,
                                               const JID &invitee,
                                               const std::string &reason) {
  BOOST_LOG_TRIVIAL(info) << "handleMUCInviteDecline";
}

void XmppClientSession::handleMUCError(MUCRoom *room, StanzaError error) {
  BOOST_LOG_TRIVIAL(info) << "handleMUCError";
}

void XmppClientSession::handleMUCInfo(MUCRoom *room, int features,
                                      const std::string &name,
                                      const DataForm *infoForm) {
  BOOST_LOG_TRIVIAL(info) << "handleMUCInfo";
}

void XmppClientSession::handleMUCItems(MUCRoom *room,
                                       const Disco::ItemList &items) {
  BOOST_LOG_TRIVIAL(info) << "handleMUCItems";
}

void XmppClientSession::handleLog( LogLevel level, LogArea area, const std::string& message ) {
	BOOST_LOG_TRIVIAL(info) << message;
}
void run_session(XmppClientSession *session) {
  BOOST_LOG_TRIVIAL(info) << "Starting Xmpp Client Session ...";
  session->connect();
}
  XmppClientSession *session = new XmppClientSession();
  session->setup(user, host, resource, passwd);

  std::thread session_thread(run_session, session);
  int wait_sec = 60;
  BOOST_LOG_TRIVIAL(info) << "Waiting " << wait_sec << " sec to connect befor join MUCs...";
  sleep(wait_sec);
  BOOST_LOG_TRIVIAL(info) << "Waiting " << wait_sec << " sec to join MUCs... done!";

  std::vector<std::string> auto_join_mucs =
      conf.getVariablesMap()["muc.auto.join"].as<std::vector<std::string>>();

  for (std::vector<std::string>::iterator it = auto_join_mucs.begin();
       it != auto_join_mucs.end(); ++it) {
    session->join(*it + "/" +
                  conf.getVariablesMap()["account.nick"].as<std::string>());
  }

  session_thread.join();