/* bot/context.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <glib.h>
#include <strophe.h>
#include <openssl/sha.h> /* For SHA1 */
#include <unistd.h>
#include <pthread.h>
#include "context.h"
/* static lifetime but only accessible here and through get and set functions below */
static struct BotXmppCtx *g_bctx = NULL;
pthread_mutex_t g_ctxLock = PTHREAD_MUTEX_INITIALIZER;
BotXmppCtx * const bot_get_ctx()
{
return g_bctx;
}
void bot_set_ctx(BotXmppCtx * const bctx)
{
pthread_mutex_lock(&bctx->lock);
if(g_bctx) {
fprintf(stderr, "Dev error: cannot set XMPP ctx twice in the same program run.\n");
} else {
g_bctx = bctx;
}
pthread_mutex_unlock(&bctx->lock);
}
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?):
* - base64 encoded data is sent in the <data> node
* - SHA1 hash is computed from the original PNG file (not the base64 encoded)
*/
char * bot_xmpp_set_avatar_pubsub(struct BotXmppCtx * const bctx, bool setData)
{
gchar *avatar;
gsize avatarLenght;
gchar *png;
unsigned char hash[SHA_DIGEST_LENGTH]; /* == 20 */
char hexHash[SHA_DIGEST_LENGTH*2+1]; // always use two characters for hex encoding one byte
xmpp_stanza_t *iq, *pubsub, *publish, *item, *data, *metadata, *info;
int i;
/* 1. Create IQ 'set' stanza */
iq = xmpp_stanza_new(bctx->ctx);
xmpp_stanza_set_type(iq, "set");
xmpp_stanza_set_name(iq, "iq");
if(setData) {
xmpp_stanza_set_id(iq, "publish1");
} else {
xmpp_stanza_set_id(iq, "publish2");
}
/* Create <pubsub> protocol element*/
pubsub = xmpp_stanza_new(bctx->ctx);
xmpp_stanza_set_ns(pubsub, "http://jabber.org/protocol/pubsub");
xmpp_stanza_set_name(pubsub, "pubsub");
xmpp_stanza_add_child(iq, pubsub);
/* Create <publish> element */
publish = xmpp_stanza_new(bctx->ctx);
if(setData) {
xmpp_stanza_set_attribute(publish, "node", "urn:xmpp:avatar:data");
} else {
xmpp_stanza_set_attribute(publish, "node", "urn:xmpp:avatar:metadata");
}
xmpp_stanza_set_name(publish, "publish");
xmpp_stanza_add_child(pubsub, publish);
/* Add <item> where id should be the sha1sum of the image */
item = xmpp_stanza_new(bctx->ctx);
/* return the avatar ID computed as SHA1 of the actual png file. TODO avoid doing this twice (one for data, one for metadata)! */
memset(hash, 0, SHA_DIGEST_LENGTH);
memset(hexHash, 0, 2*SHA_DIGEST_LENGTH+1);
if(g_file_get_contents(bctx->avatarFile->str, &png, &avatarLenght, NULL)) {
SHA1((const unsigned char*)png, sizeof(png)-1, hash);
for(i=0; i<SHA_DIGEST_LENGTH; i++) {
// we use a const string for convenience, can index it
hexHash[i*2] = "0123456789ABCDEF"[hash[i] >> 4];
hexHash[i*2+1] = "0123456789ABCDEF"[hash[i] & 0x0F];
}
}
xmpp_stanza_set_id(item, hexHash);
xmpp_stanza_set_name(item, "item");
xmpp_stanza_add_child(publish, item);
if(setData) {
/* Add <data> with base64 data */
data = xmpp_stanza_new(bctx->ctx);
/* get contents of file */
if(g_file_get_contents(bctx->avatarBase64->str, &avatar, &avatarLenght, NULL)) {
xmpp_stanza_set_text(data, avatar);
} else {
fprintf(stderr, "Unable to read BASE64-encoded avatar.\n");
}
xmpp_stanza_set_name(data, "data");
xmpp_stanza_set_ns(data, "urn:xmpp:avatar:data");
xmpp_stanza_add_child(item, data);
} else {
struct stat fileInfo;
char * bytes;
int enough = 0;
/* Add <metadata> with info about png file */
metadata = xmpp_stanza_new(bctx->ctx);
xmpp_stanza_set_name(metadata, "metadata");
xmpp_stanza_add_child(item, metadata);
/* Add <info> stanza */
stat(bctx->avatarFile->str, &fileInfo);
enough = (int)((ceil(log10(fileInfo.st_size))+1)*sizeof(char));
bytes = malloc(enough);
memset(bytes, 0, enough);
sprintf(bytes, "%ld", fileInfo.st_size);
info = xmpp_stanza_new(bctx->ctx);
xmpp_stanza_set_id(info, hexHash);
xmpp_stanza_set_attribute(info, "height", "64");
xmpp_stanza_set_attribute(info, "width", "64");
xmpp_stanza_set_attribute(info, "type", "image/png");
xmpp_stanza_set_attribute(info, "bytes", bytes);
xmpp_stanza_add_child(metadata, info);
}
/* 6. Send stanza and release it + all children */
xmpp_send(bctx->conn, iq);
xmpp_stanza_release(iq);
return strdup(hexHash);
}
/* implement vcard-based avatars. This doesn't work ATM. TODO
* https://xmpp.org/extensions/xep-0153.html
* returns the SHA1 hash of the actual PNG file encoded as hexadecimal
*/
char * bot_xmpp_set_avatar(struct BotXmppCtx * const bctx)
{
gchar *avatar;
gsize avatarLenght;
gchar *png;
unsigned char hash[SHA_DIGEST_LENGTH]; /* == 20 */
char hexHash[SHA_DIGEST_LENGTH*2+1]; // always use two characters for hex encoding one byte
xmpp_stanza_t *iq, *vcard, *photo, *type, *binval;
int i = 0;
/* 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, "vc1");
/* 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/png) */
type = xmpp_stanza_new(bctx->ctx);
// gotcha: calling set_text AFTER set_name will fail with XMPP_EINVOP (invalid operation)
xmpp_stanza_set_text(type, "image/png");
xmpp_stanza_set_name(type, "TYPE");
xmpp_stanza_add_child(photo, type);
/* Add <BINVAL> with base64 data */
binval = xmpp_stanza_new(bctx->ctx);
if(g_file_get_contents(bctx->avatarBase64->str, &avatar, &avatarLenght, NULL)) {
xmpp_stanza_set_text(binval, avatar);
} else {
fprintf(stderr, "Unable to read BASE64-encoded avatar.\n");
}
xmpp_stanza_set_name(binval, "BINVAL");
xmpp_stanza_add_child(photo, binval);
/* 6. Send stanza and release it + all children */
xmpp_send(bctx->conn, iq);
xmpp_stanza_release(iq);
/* return the avatar ID computed as SHA1 of the actual png file */
memset(hash, 0, SHA_DIGEST_LENGTH);
memset(hexHash, 0, 2*SHA_DIGEST_LENGTH+1);
if(g_file_get_contents(bctx->avatarFile->str, &png, &avatarLenght, NULL)) {
SHA1((const unsigned char*)png, sizeof(png)-1, hash);
for(i=0; i<SHA_DIGEST_LENGTH; i++) {
// we use a const string for convenience, can index it
hexHash[i*2] = "0123456789ABCDEF"[hash[i] >> 4];
hexHash[i*2+1] = "0123456789ABCDEF"[hash[i] & 0x0F];
}
return strdup(hexHash);
}
return NULL;
}
int bot_xmpp_startup(const struct BotUserInfo user, GString * const avatarFile, GString * const avatarBase64)
{
xmpp_log_t* newLog;
xmpp_ctx_t* newCtx;
xmpp_conn_t* newCon;
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);
/* no need to lock here as this is the only thread */
pthread_mutex_init (&bctx->lock, NULL);
bctx->log = newLog;
bctx->ctx = newCtx;
bctx->conn = newCon;
bctx->avatarBase64 = avatarBase64;
bctx->avatarFile = 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);
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();
}