Newer
Older
xmpp-bot / src / core / context.c
/* bot/context.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <glib.h>
#include <strophe.h>
#include <openssl/sha.h> /* For SHA1 */
#include <unistd.h>
#include <signal.h>
#include "context.h"

extern struct BotXmppCtx *g_bctx = NULL;

struct BotXmppCtx *bot_get_ctx()
{
    return g_bctx;
}

void bot_set_ctx(struct BotXmppCtx *ctx)
{
    if(g_bctx) {
        fprintf(stderr, "Dev error: cannot set XMPP ctx twice in the same program run.\n");
    } else {
        g_bctx = ctx;
    }
}

void bot_free_user_data(struct BotUserInfo user, GString *filename, bool freeSegment)
{
    g_string_free(filename, freeSegment);
    g_string_free(user.jid, freeSegment);
    g_string_free(user.pass, freeSegment);
}

/* Implement https://xmpp.org/extensions/xep-0084.html */
/* TODO need to understand the format for data stanza encoding (bytes? base64?) */
/* int bot_xmpp_set_avatar_user(struct BotXmppCtx * const bctx, const GString *avatarFile) */
/* { */
/*     gchar *avatar; */
/*     gsize avatarLenght; */
/*     xmpp_stanza_t *iq, *pubsub, *publish, *item, *data; */

/*     /\* 1. Create IQ 'set' stanza *\/ */
/*     iq = xmpp_stanza_new(bctx->ctx); */
/*     xmpp_stanza_set_name(iq, "iq"); */
/*     xmpp_stanza_set_type(iq, "set"); */
/*     xmpp_stanza_set_id(iq, "publish1"); */

/*     /\* Create <pubsub> protocol element*\/ */
/*     pubsub = xmpp_stanza_new(bctx->ctx); */
/*     xmpp_stanza_set_name(pubsub, "pubsub"); */
/*     xmpp_stanza_set_ns(pubsub, "http://jabber.org/protocol/pubsub"); */
/*     xmpp_stanza_add_child(iq, pubsub); */

/*     /\* Create <publish> element *\/ */
/*     publish = xmpp_stanza_new(bctx->ctx); */
/*     xmpp_stanza_set_name(publish, "publish"); */
/*     xmpp_stanza_set_attribute(publish, "node", "urn:xmpp:avatar:data"); */
/*     xmpp_stanza_add_child(pubsub, publish); */

/*     /\* Add <item> where id should be the sha1sum of the image *\/ */
/*     item = xmpp_stanza_new(bctx->ctx); */
/*     xmpp_stanza_set_name(item, "item"); */
/* 	xmpp_stanza_set_id(item, "SHA1SUM"); */
/*     xmpp_stanza_add_child(publish, item); */

/*     /\* Add <data> with base64 data *\/ */
/*     data = xmpp_stanza_new(bctx->ctx); */
/*     xmpp_stanza_set_name(data, "data"); */
/*     xmpp_stanza_set_ns(data, "urn:xmpp:avatar:data"); */
/*     if(g_file_get_contents(avatarFile->str, &avatar, &avatarLenght, NULL)) { */
/*         xmpp_stanza_set_text(data, avatar); */
/*     } else { */
/*         fprintf(stderr, "Unable to read png avatar.\n"); */
/*     } */
/*     xmpp_stanza_set_text(data, "DATA"); */
/*     xmpp_stanza_add_child(item, data); */

/*     /\* 6. Send stanza and release it + all children *\/ */
/*     xmpp_send(bctx->conn, iq); */
/*     xmpp_stanza_release(iq); */

/*     return 0; */
/* } */


/* implement https://xmpp.org/extensions/xep-0153.html */
int bot_xmpp_set_avatar(struct BotXmppCtx * const bctx, const GString *avatarFile)
{
    gchar *avatar;
    gsize avatarLenght;
    /* GString *avatarID; */ // TODO
    unsigned char hash[SHA_DIGEST_LENGTH]; /* == 20 */
    xmpp_stanza_t *iq, *vcard, *photo, *type, *binval;

    /* 1. Create IQ 'set' stanza */
    iq = xmpp_stanza_new(bctx->ctx);
    xmpp_stanza_set_name(iq, "iq");
    xmpp_stanza_set_type(iq, "set");
    xmpp_stanza_set_id(iq, "vcard_set_1");

    /* Create <vCard xmlns='vcard-temp' */
    vcard = xmpp_stanza_new(bctx->ctx);
    xmpp_stanza_set_name(vcard, "vCard");
    xmpp_stanza_set_ns(vcard, "vcard-temp");
    xmpp_stanza_add_child(iq, vcard);

    /* Create <PHOTO> element */
    photo = xmpp_stanza_new(bctx->ctx);
    xmpp_stanza_set_name(photo, "PHOTO");
    xmpp_stanza_add_child(vcard, photo);

    /* Add <TYPE> (e.g., image/jpeg) */
    type = xmpp_stanza_new(bctx->ctx);
    xmpp_stanza_set_name(type, "TYPE");
    xmpp_stanza_set_text(type, "image/jpeg");
    xmpp_stanza_add_child(photo, type);

    /* Add <BINVAL> with base64 data */
    binval = xmpp_stanza_new(bctx->ctx);
    xmpp_stanza_set_name(binval, "BINVAL");

    if(g_file_get_contents(avatarFile->str, &avatar, &avatarLenght, NULL)) {
        SHA1((const unsigned char*)avatarFile->str, sizeof(avatarFile->str)-1, hash);
        xmpp_stanza_set_text(binval, avatar);
    } else {
        fprintf(stderr, "Unable to read BASE64-encoded avatar.\n");
    }
    xmpp_stanza_add_child(photo, binval);

    /* 6. Send stanza and release it + all children */
    xmpp_send(bctx->conn, iq);
    xmpp_stanza_release(iq);

    return 0;
}

int bot_xmpp_startup(struct BotUserInfo user, GString *avatarFile)
{
    xmpp_log_t* newLog;
    xmpp_ctx_t* newCtx;
    xmpp_conn_t* newCon;
    /* GString *avatarID; */ // TODO
    int connectStatus;
    struct BotXmppCtx *bctx = malloc(sizeof *bctx);

    /* init library */
    xmpp_initialize();

    /* create a context */
    newLog = xmpp_get_default_logger(XMPP_LEVEL_DEBUG); /* pass NULL instead to silence output */
    newCtx = xmpp_ctx_new(NULL, newLog);

    if (!(newLog && newCtx)) {
        fprintf(stderr, "Unable to startup. Context creation failed.");
        return 1;
    }

    /* create a connection */
    newCon = xmpp_conn_new(newCtx);

    if (!newCon) {
        fprintf(stderr, "Unable to startup. Connection failed.");
        return 1;
    }

    /* setup authentication information */
    xmpp_conn_set_jid(newCon, user.jid->str);
    xmpp_conn_set_pass(newCon, user.pass->str);

    bctx->log = newLog;
    bctx->ctx = newCtx;
    bctx->conn = newCon;

    /* TODO properly set avatarID in presence stanza */
    bot_xmpp_set_avatar(bctx, avatarFile);

    /* initiate connection */
    connectStatus = xmpp_connect_client(newCon, NULL, 0, conn_handler, newCtx);
    if(connectStatus != 0) {
        /* connecting went wrong */
        return connectStatus;
    }

    /* store ctx as global */
    bot_set_ctx(bctx);

    /* install signal handler */
    signal(SIGALRM, bot_reminder_alarm_func);
    return 0;
}

void bot_xmpp_shutdown()
{
    struct BotXmppCtx *bctx = bot_get_ctx();

    /* enter the event loop - 
       our connect handler will trigger an exit */
    xmpp_run(bctx->ctx);

    /* release our connection and context */
    xmpp_conn_release(bctx->conn);
    xmpp_ctx_free(bctx->ctx);

    /* final shutdown of the library */
    xmpp_shutdown();
}

/* send a message when SIGALRM is triggered */
void bot_reminder_alarm_func(int s)
{
    unsigned int minutes;
    BotReminder *reminder = bot_get_last_reminder();
    struct BotXmppCtx *bctx = bot_get_ctx();
    GString *msg = g_string_new(NULL);

    if(!reminder) {
        fprintf(stderr, "SIGALRM was raised but no reminder was set.\n");
        return;
    }

    minutes = reminder->minutes;
    g_string_printf(msg, "OHI! Segna le spese! Te lo ricordo di nuovo tra %d minuti.\n", minutes);
    send_message(bctx->conn, reminder->from, bctx->ctx, msg);
    g_string_free(msg, true);

    /* reinstall alarm */
    alarm(minutes * 60);
}