pgp stuff

v1
Tony Olagbaiye 3 years ago
parent 7d48adaf24
commit 1d67aaf8a7
No known key found for this signature in database
GPG Key ID: 9E2FF3BDEBDFC910

@ -29,15 +29,28 @@ use_guix()
# Miscellaneous packages.
ENVIRONMENTS=(
weechat
weechat # Debug runs
)
# Environment packages.
PACKAGES=(
autoconf autoconf-archive automake libtool
make cmake gcc-toolchain pkg-config patchelf
weechat libxml2 libstrophe
libgcrypt libsignal-protocol-c
autoconf # Deps with autoreconf
autoconf-archive # Deps with m4 tooling
automake # Deps with automake
libtool # Deps with libtool
make # Makefile and deps with makefiles
cmake # Deps with cmake
gcc-toolchain # Compilation
pkg-config # Deps configuration and configuration of deps deps
patchelf # Fix linkage (guix)
bear # Generate compile_commands.json for language servers
universal-ctags # Generate tags (make tags)
weechat # Weechat includes
libxml2 # Dep (libxml2)
libstrophe # Dep (strophe)
libgcrypt # Dep (gcrypt)
libsignal-protocol-c # Dep (libsignal)
rnp # Dep (rnpgp)
)
# Thanks <https://lists.gnu.org/archive/html/guix-devel/2016-09/msg00859.html>
@ -46,4 +59,9 @@ use_guix()
export CC=gcc
}
use guix --with-debug-info=weechat --with-debug-info=libstrophe gdb
use guix \
--with-debug-info=weechat\
--with-debug-info=libstrophe\
--with-debug-info=libsignal-protocol-c\
--with-debug-info=rnp\
clang:extra gdb

1
.gitignore vendored

@ -1,6 +1,7 @@
.direnv
*~
compile_commands.json
.cache
cscope*
.depend
*.d

@ -4,10 +4,10 @@ ifdef DEBUG
endif
RM=rm -f
FIND=find
INCLUDES=-Ilibstrophe $(shell xml2-config --cflags) $(shell pkg-config --cflags libsignal-protocol-c)
INCLUDES=-Ilibstrophe $(shell xml2-config --cflags) $(shell pkg-config --cflags librnp-0) $(shell pkg-config --cflags libsignal-protocol-c)
CFLAGS+=$(DBGCFLAGS) -fno-omit-frame-pointer -fPIC -std=gnu99 -g -Wall -Wextra -Werror-implicit-function-declaration -Wno-missing-field-initializers -D_XOPEN_SOURCE=700 $(INCLUDES)
LDFLAGS+=$(DBGLDFLAGS) -shared -g $(DBGCFLAGS)
LDLIBS=-lstrophe -lpthread $(shell xml2-config --libs) $(shell pkg-config --libs libsignal-protocol-c) -lgcrypt
LDLIBS=-lstrophe -lpthread $(shell xml2-config --libs) $(shell pkg-config --libs librnp-0) $(shell pkg-config --libs libsignal-protocol-c) -lgcrypt
PREFIX ?= /usr/local
LIBDIR ?= $(PREFIX)/lib
@ -23,6 +23,7 @@ SRCS=plugin.c \
input.c \
message.c \
omemo.c \
pgp.c \
user.c \
util.c \
xmpp/presence.c \
@ -41,6 +42,7 @@ xmpp.so: $(OBJS) $(DEPS)
patchelf --shrink-rpath xmpp.so || true
diff/libdiff.a:
git submodule update --init --recursive
cd diff && ./configure
$(MAKE) -C diff CFLAGS=-fPIC
diff: diff/libdiff.a
@ -67,7 +69,6 @@ clean:
$(MAKE) -C diff clean || true
git submodule foreach --recursive git clean -xfd || true
git submodule foreach --recursive git reset --hard || true
git submodule update --init --recursive || true
distclean: clean
$(RM) *~ .depend
@ -86,7 +87,7 @@ endif
.PHONY: tags cs
tags:
$(CC) $(CFLAGS) -M $(SRCS) | sed -e "s/[\\ ]/\n/g" | sed -e "/^$$/d" -e "/\.o:[ \t]*$$/d" | sort | uniq | ctags -e -L - -f .git/tags -R --c-kinds=+px --c++-kinds=+px --fields=+iaS --extra=+fq
$(CC) $(CFLAGS) -M $(SRCS) | sed -e "s/[\\ ]/\n/g" | sed -e "/^$$/d" -e "/\.o:[ \t]*$$/d" | sort | uniq | ctags -e -L - -f .git/tags -R --c-kinds=+px --c++-kinds=+px --fields=+iaS --extras=+fq
cs:
cscope -RUbq

@ -32,6 +32,8 @@ char *account_options[ACCOUNT_NUM_OPTIONS][2] =
{ "autoconnect", "" },
{ "resource", "" },
{ "status", "probably about to segfault" },
{ "pgp_pubring_path", "${weechat_data_dir}/pubring.gpg" },
{ "pgp_secring_path", "${weechat_data_dir}/secring.gpg" },
};
struct t_account *account__search(const char *name)

@ -17,6 +17,8 @@ enum t_account_option
ACCOUNT_OPTION_AUTOCONNECT,
ACCOUNT_OPTION_RESOURCE,
ACCOUNT_OPTION_STATUS,
ACCOUNT_OPTION_PGP_PUBRING_PATH,
ACCOUNT_OPTION_PGP_SECRING_PATH,
ACCOUNT_NUM_OPTIONS,
};
@ -54,6 +56,10 @@ enum t_account_option
weechat_config_string(account->options[ACCOUNT_OPTION_RESOURCE])
#define account_status(account) \
weechat_config_string(account->options[ACCOUNT_OPTION_STATUS])
#define account_pgp_pubring_path(account) \
weechat_config_string(account->options[ACCOUNT_OPTION_PGP_PUBRING_PATH])
#define account_pgp_secring_path(account) \
weechat_config_string(account->options[ACCOUNT_OPTION_PGP_SECRING_PATH])
struct t_device
{
@ -86,6 +92,7 @@ struct t_account
char *buffer_as_string;
struct t_omemo *omemo;
struct t_pgp *pgp;
struct t_device *devices;
struct t_device *last_device;

@ -17,6 +17,30 @@
#include "channel.h"
#include "input.h"
#include "buffer.h"
#include "pgp.h"
struct t_account *channel__account(struct t_channel *channel)
{
struct t_account *ptr_account;
struct t_channel *ptr_channel;
if (!channel)
return NULL;
for (ptr_account = accounts; ptr_account;
ptr_account = ptr_account->next_account)
{
for (ptr_channel = ptr_account->channels; ptr_channel;
ptr_channel = ptr_channel->next_channel)
{
if (ptr_channel == channel)
return ptr_account;
}
}
/* account not found */
return NULL;
}
struct t_channel *channel__search(struct t_account *account,
const char *id)
@ -187,7 +211,7 @@ struct t_channel *channel__new(struct t_account *account,
{
struct t_channel *new_channel, *ptr_channel;
struct t_gui_buffer *ptr_buffer;
struct t_hook *typing_timer;
struct t_hook *typing_timer, *self_typing_timer;
if (!account || !id || !name || !name[0])
return NULL;
@ -209,9 +233,14 @@ struct t_channel *channel__new(struct t_account *account,
&channel__typing_cb,
new_channel, NULL);
self_typing_timer = weechat_hook_timer(1 * 1000, 0, 0,
&channel__self_typing_cb,
new_channel, NULL);
new_channel->type = type;
new_channel->id = strdup(id);
new_channel->name = strdup(name);
new_channel->pgp_id = NULL;
new_channel->topic.value = NULL;
new_channel->topic.creator = NULL;
@ -223,8 +252,11 @@ struct t_channel *channel__new(struct t_account *account,
new_channel->unread_count_display = 0;
new_channel->typing_hook_timer = typing_timer;
new_channel->self_typing_hook_timer = self_typing_timer;
new_channel->members_speaking[0] = NULL;
new_channel->members_speaking[1] = NULL;
new_channel->self_typings = NULL;
new_channel->last_self_typing = NULL;
new_channel->typings = NULL;
new_channel->last_typing = NULL;
new_channel->members = NULL;
@ -441,10 +473,11 @@ struct t_channel_typing *channel__typing_search(struct t_channel *channel,
return NULL;
}
void channel__add_typing(struct t_channel *channel,
struct t_user *user)
int channel__add_typing(struct t_channel *channel,
struct t_user *user)
{
struct t_channel_typing *new_typing;
int ret = 0;
new_typing = channel__typing_search(channel, user->id);
if (!new_typing)
@ -460,10 +493,140 @@ void channel__add_typing(struct t_channel *channel,
else
channel->typings = new_typing;
channel->last_typing = new_typing;
ret = 1;
}
new_typing->ts = time(NULL);
channel__typing_cb(channel, NULL, 0);
return ret;
}
void channel__self_typing_free(struct t_channel *channel,
struct t_channel_typing *typing)
{
struct t_channel_typing *new_typings;
if (!channel || !typing)
return;
/* remove typing from typings list */
if (channel->last_self_typing == typing)
channel->last_self_typing = typing->prev_typing;
if (typing->prev_typing)
{
(typing->prev_typing)->next_typing = typing->next_typing;
new_typings = channel->self_typings;
}
else
new_typings = typing->next_typing;
if (typing->next_typing)
(typing->next_typing)->prev_typing = typing->prev_typing;
/* free typing data */
if (typing->name)
free(typing->name);
free(typing);
channel->self_typings = new_typings;
}
void channel__self_typing_free_all(struct t_channel *channel)
{
while (channel->self_typings)
channel__self_typing_free(channel, channel->self_typings);
}
int channel__self_typing_cb(const void *pointer,
void *data,
int remaining_calls)
{
struct t_channel_typing *ptr_typing, *next_typing;
struct t_account *account;
struct t_channel *channel;
time_t now;
(void) data;
(void) remaining_calls;
if (!pointer)
return WEECHAT_RC_ERROR;
channel = (struct t_channel *)pointer;
account = channel__account(channel);
now = time(NULL);
for (ptr_typing = channel->self_typings; ptr_typing;
ptr_typing = ptr_typing->next_typing)
{
next_typing = ptr_typing->next_typing;
while (ptr_typing && now - ptr_typing->ts > 10)
{
channel__send_paused(account, channel, ptr_typing->user);
channel__self_typing_free(channel, ptr_typing);
ptr_typing = next_typing;
if (ptr_typing)
next_typing = ptr_typing->next_typing;
}
if (!ptr_typing)
break;
}
return WEECHAT_RC_OK;
}
struct t_channel_typing *channel__self_typing_search(struct t_channel *channel,
struct t_user *user)
{
struct t_channel_typing *ptr_typing;
if (!channel)
return NULL;
for (ptr_typing = channel->self_typings; ptr_typing;
ptr_typing = ptr_typing->next_typing)
{
if (user == ptr_typing->user)
return ptr_typing;
}
return NULL;
}
int channel__add_self_typing(struct t_channel *channel,
struct t_user *user)
{
struct t_channel_typing *new_typing;
int ret = 0;
new_typing = channel__self_typing_search(channel, user);
if (!new_typing)
{
new_typing = malloc(sizeof(*new_typing));
new_typing->user = user;
new_typing->name = user ? strdup(user->profile.display_name) : NULL;
new_typing->prev_typing = channel->last_self_typing;
new_typing->next_typing = NULL;
new_typing->ts = time(NULL);
if (channel->last_self_typing)
(channel->last_self_typing)->next_typing = new_typing;
else
channel->self_typings = new_typing;
channel->last_self_typing = new_typing;
ret = 1;
}
channel__self_typing_cb(channel, NULL, 0);
return ret;
}
void channel__member_free(struct t_channel *channel,
@ -532,8 +695,11 @@ void channel__free(struct t_account *account,
/* free hooks */
if (channel->typing_hook_timer)
weechat_unhook(channel->typing_hook_timer);
if (channel->self_typing_hook_timer)
weechat_unhook(channel->self_typing_hook_timer);
/* free linked lists */
channel__self_typing_free_all(channel);
channel__typing_free_all(channel);
channel__member_free_all(channel);
@ -542,6 +708,8 @@ void channel__free(struct t_account *account,
free(channel->id);
if (channel->name)
free(channel->name);
if (channel->pgp_id)
free(channel->pgp_id);
if (channel->topic.value)
free(channel->topic.value);
if (channel->topic.creator)
@ -593,6 +761,17 @@ struct t_channel_member *channel__add_member(struct t_account *account,
struct t_channel_member *member;
struct t_user *user;
user = user__search(account, id);
if (weechat_strcasecmp(user->id, channel->id) == 0
&& channel->type == CHANNEL_TYPE_MUC)
{
weechat_printf_date_tags(channel->buffer, 0, "log2", "%sMUC: %s",
weechat_prefix("network"),
id);
return NULL;
}
member = malloc(sizeof(struct t_channel_member));
member->id = strdup(id);
@ -607,18 +786,12 @@ struct t_channel_member *channel__add_member(struct t_account *account,
channel->members = member;
channel->last_member = member;
user = user__search(account, id);
if (user)
user__nicklist_add(account, channel, user);
char *jid_bare = xmpp_jid_bare(account->context, user->id);
char *jid_resource = xmpp_jid_resource(account->context, user->id);
if (weechat_strcasecmp(user->id, channel->id) == 0
&& channel->type == CHANNEL_TYPE_MUC)
weechat_printf_date_tags(channel->buffer, 0, "log2", "%sMUC: %s",
weechat_prefix("network"),
user->id);
else if (weechat_strcasecmp(jid_bare, channel->id) == 0
if (weechat_strcasecmp(jid_bare, channel->id) == 0
&& channel->type == CHANNEL_TYPE_MUC)
weechat_printf_date_tags(channel->buffer, 0, "xmpp_presence,enter,log4", "%s%s %s%s%s %sentered%s %s %s%s%s",
weechat_prefix("join"),
@ -757,6 +930,26 @@ void channel__send_message(struct t_account *account, struct t_channel *channel,
xmpp_message_set_body(message, body);
char *url = strstr(body, "http");
if (channel->pgp_id)
{
xmpp_stanza_t *message__x = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(message__x, "x");
xmpp_stanza_set_ns(message__x, "jabber:x:encrypted");
xmpp_stanza_t *message__x__text = xmpp_stanza_new(account->context);
char *ciphertext = pgp__encrypt(account->pgp, channel->pgp_id, body);
if (ciphertext)
xmpp_stanza_set_text(message__x__text, ciphertext);
free(ciphertext);
xmpp_stanza_add_child(message__x, message__x__text);
xmpp_stanza_release(message__x__text);
xmpp_stanza_add_child(message, message__x);
xmpp_stanza_release(message__x);
weechat_printf(channel->buffer, "[~]\t%s%s: PGP", weechat_color("gray"), account_jid(account));
}
if (url)
{
xmpp_stanza_t *message__x = xmpp_stanza_new(account->context);
@ -787,3 +980,44 @@ void channel__send_message(struct t_account *account, struct t_channel *channel,
user__as_prefix_raw(account, account_jid(account)),
body);
}
void channel__send_typing(struct t_account *account, struct t_channel *channel,
struct t_user *user)
{
if (channel__add_self_typing(channel, user))
{
xmpp_stanza_t *message = xmpp_message_new(account->context,
channel->type == CHANNEL_TYPE_MUC
? "groupchat" : "chat",
user ? user->id : channel->id, NULL);
xmpp_stanza_t *message__composing = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(message__composing, "composing");
xmpp_stanza_set_ns(message__composing, "http://jabber.org/protocol/chatstates");
xmpp_stanza_add_child(message, message__composing);
xmpp_stanza_release(message__composing);
xmpp_send(account->connection, message);
xmpp_stanza_release(message);
}
}
void channel__send_paused(struct t_account *account, struct t_channel *channel,
struct t_user *user)
{
xmpp_stanza_t *message = xmpp_message_new(account->context,
channel->type == CHANNEL_TYPE_MUC
? "groupchat" : "chat",
user ? user->id : channel->id, NULL);
xmpp_stanza_t *message__paused = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(message__paused, "paused");
xmpp_stanza_set_ns(message__paused, "http://jabber.org/protocol/chatstates");
xmpp_stanza_add_child(message, message__paused);
xmpp_stanza_release(message__paused);
xmpp_send(account->connection, message);
xmpp_stanza_release(message);
}

@ -15,7 +15,10 @@ enum t_channel_type
struct t_channel_typing
{
char *id;
union {
char *id;
struct t_user *user;
};
char *name;
time_t ts;
@ -46,6 +49,7 @@ struct t_channel
enum t_channel_type type;
char *id;
char *name;
char *pgp_id;
struct t_channel_topic topic;
@ -56,7 +60,10 @@ struct t_channel
int unread_count_display;
struct t_hook *typing_hook_timer;
struct t_hook *self_typing_hook_timer;
struct t_weelist *members_speaking[2];
struct t_channel_typing *self_typings;
struct t_channel_typing *last_self_typing;
struct t_channel_typing *typings;
struct t_channel_typing *last_typing;
struct t_channel_member *members;
@ -68,6 +75,8 @@ struct t_channel
struct t_channel *next_channel;
};
struct t_account *channel__account(struct t_channel *channel);
struct t_channel *channel__search(struct t_account *account,
const char *id);
@ -101,8 +110,23 @@ int channel__typing_cb(const void *pointer,
struct t_channel_typing *channel__typing_search(struct t_channel *channel,
const char *id);
void channel__add_typing(struct t_channel *channel,
struct t_user *user);
int channel__add_typing(struct t_channel *channel,
struct t_user *user);
void channel__self_typing_free(struct t_channel *channel,
struct t_channel_typing *typing);
void channel__self_typing_free_all(struct t_channel *channel);
int channel__self_typing_cb(const void *pointer,
void *data,
int remaining_calls);
struct t_channel_typing *channel__self_typing_search(struct t_channel *channel,
struct t_user *user);
int channel__add_self_typing(struct t_channel *channel,
struct t_user *user);
void channel__free(struct t_account *account,
struct t_channel *channel);
@ -139,4 +163,10 @@ struct t_channel_member *channel__remove_member(struct t_account *account,
void channel__send_message(struct t_account *account, struct t_channel *channel,
const char *to, const char *body);
void channel__send_typing(struct t_account *account, struct t_channel *channel,
struct t_user *user);
void channel__send_paused(struct t_account *account, struct t_channel *channel,
struct t_user *user);
#endif /*WEECHAT_XMPP_CHANNEL_H*/

@ -648,13 +648,55 @@ int command__me(const void *pointer, void *data,
weechat_printf_date_tags(ptr_channel->buffer, 0,
"xmpp_message,message,action,private,notify_none,self_msg,log1",
"%s%s %s",
weechat_prefix("action"), account_jid(ptr_account),
weechat_prefix("action"),
user__as_prefix_raw(ptr_account, account_jid(ptr_account)),
strlen(text) > strlen("/me ") ? text+4 : "");
}
return WEECHAT_RC_OK;
}
int command__pgp(const void *pointer, void *data,
struct t_gui_buffer *buffer, int argc,
char **argv, char **argv_eol)
{
struct t_account *ptr_account = NULL;
struct t_channel *ptr_channel = NULL;
xmpp_stanza_t *message;
char *keyid;
(void) pointer;
(void) data;
(void) argv;
buffer__get_account_and_channel(buffer, &ptr_account, &ptr_channel);
if (!ptr_account)
return WEECHAT_RC_ERROR;
if (!ptr_channel)
{
weechat_printf (
ptr_account->buffer,
_("%s%s: \"%s\" command can not be executed on a account buffer"),
weechat_prefix("error"), WEECHAT_XMPP_PLUGIN_NAME, "me");
return WEECHAT_RC_OK;
}
if (argc > 1)
{
keyid = argv_eol[1];
ptr_channel->pgp_id = strdup(keyid);
}
else
{
ptr_channel->pgp_id = NULL;
}
return WEECHAT_RC_OK;
}
int command__xml(const void *pointer, void *data,
struct t_gui_buffer *buffer, int argc,
char **argv, char **argv_eol)
@ -756,6 +798,15 @@ void command__init()
if (!hook)
weechat_printf(NULL, "Failed to setup command /me");
hook = weechat_hook_command(
"pgp",
N_("set the target pgp key for the current channel"),
N_("<keyid>"),
N_("keyid: recipient keyid"),
NULL, &command__pgp, NULL, NULL);
if (!hook)
weechat_printf(NULL, "Failed to setup command /pgp");
hook = weechat_hook_command(
"xml",
N_("send a raw xml stanza"),

@ -186,6 +186,38 @@ config__account_new_option (struct t_config_file *config_file,
callback_change_data,
NULL, NULL, NULL);
break;
case ACCOUNT_OPTION_PGP_PUBRING_PATH:
new_option = weechat_config_new_option (
config_file, section,
option_name, "string",
N_("XMPP Account PGP Public Keyring Path"),
NULL, 0, 0,
default_value, value,
null_value_allowed,
callback_check_value,
callback_check_value_pointer,
callback_check_value_data,
callback_change,
callback_change_pointer,
callback_change_data,
NULL, NULL, NULL);
break;
case ACCOUNT_OPTION_PGP_SECRING_PATH:
new_option = weechat_config_new_option (
config_file, section,
option_name, "string",
N_("XMPP Account PGP Secure Keyring Path"),
NULL, 0, 0,
default_value, value,
null_value_allowed,
callback_check_value,
callback_check_value_pointer,
callback_check_value_data,
callback_change,
callback_change_pointer,
callback_change_data,
NULL, NULL, NULL);
break;
case ACCOUNT_NUM_OPTIONS:
break;
}

@ -20,6 +20,7 @@
#include "channel.h"
#include "connection.h"
#include "omemo.h"
#include "pgp.h"
#include "util.h"
void connection__init()
@ -86,7 +87,7 @@ int connection__presence_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void
struct t_user *user;
struct t_channel *channel;
xmpp_stanza_t *iq__x, *iq__x__item, *iq__c, *iq__status;
const char *from, *from_bare, *role = NULL, *affiliation = NULL, *jid = NULL;
const char *from, *from_bare, *from_res, *role = NULL, *affiliation = NULL, *jid = NULL;
const char *node = NULL, *ver = NULL;
char *clientid = NULL, *status;
@ -94,6 +95,7 @@ int connection__presence_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void
if (from == NULL)
return 1;
from_bare = xmpp_jid_bare(account->context, from);
from_res = xmpp_jid_resource(account->context, from);
iq__x = xmpp_stanza_get_child_by_name_and_ns(
stanza, "x", "http://jabber.org/protocol/muc#user");
if (iq__x)
@ -121,15 +123,18 @@ int connection__presence_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void
iq__status = xmpp_stanza_get_child_by_name(stanza, "status");
status = iq__status ? xmpp_stanza_get_text(iq__status) : NULL;
channel = channel__search(account, from_bare);
if (!iq__x && !channel)
channel = channel__new(account, CHANNEL_TYPE_PM, from_bare, from_bare);
user = user__search(account, from);
if (!user)
user = user__new(account, from, from);
user = user__new(account, from,
channel && weechat_strcasecmp(from_bare, channel->id) == 0
? from_res : from);
channel = channel__search(account, from_bare);
if (!iq__x)
{
if (!channel)
channel = channel__new(account, CHANNEL_TYPE_PM, from_bare, from_bare);
channel__add_member(account, channel, from, clientid, status);
}
else if (channel)
@ -154,9 +159,9 @@ int connection__message_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *
struct t_account *account = (struct t_account *)userdata;
struct t_channel *channel;
xmpp_stanza_t *body, *delay, *topic, *replace, *composing, *sent, *received, *forwarded;
xmpp_stanza_t *x, *body, *delay, *topic, *replace, *composing, *sent, *received, *forwarded;
const char *type, *from, *nick, *from_bare, *to, *to_bare, *id, *replace_id, *timestamp;
char *intext, *difftext = NULL;
char *text, *intext, *difftext = NULL, *cleartext = NULL;
struct tm time = {0};
time_t date = 0;
@ -188,14 +193,20 @@ int connection__message_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *
if (from == NULL)
return 1;
from_bare = xmpp_jid_bare(account->context, from);
nick = xmpp_jid_resource(account->context, from);
channel = channel__search(account, from_bare);
if (!channel)
return 1;
struct t_user *user = user__search(account, from);
if (!user)
user = user__new(account, from, from);
user = user__new(account, from,
weechat_strcasecmp(from_bare, channel->id) == 0
? nick : from);
channel__add_typing(channel, user);
weechat_printf(channel->buffer, "%s typing...", from);
weechat_printf(channel->buffer, "...\t%s%s typing",
weechat_color("gray"),
weechat_strcasecmp(from_bare, channel->id) == 0
? nick : from);
}
sent = xmpp_stanza_get_child_by_name_and_ns(
@ -230,13 +241,24 @@ int connection__message_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *
"urn:xmpp:message-correct:0");
replace_id = replace ? xmpp_stanza_get_id(replace) : NULL;
x = xmpp_stanza_get_child_by_name_and_ns(stanza, "x", "jabber:x:encrypted");
intext = xmpp_stanza_get_text(body);
if (x)
{
char *ciphertext = xmpp_stanza_get_text(x);
cleartext = pgp__decrypt(account->pgp, ciphertext);
xmpp_free(account->context, ciphertext);
}
text = cleartext ? cleartext : intext;
const char *channel_id = weechat_strcasecmp(account_jid(account), from_bare)
== 0 ? to_bare : from_bare;
channel = channel__search(account, channel_id);
if (!channel)
channel = channel__new(account, CHANNEL_TYPE_PM, channel_id, channel_id);
channel = channel__new(account,
weechat_strcasecmp(type, "groupchat") == 0
? CHANNEL_TYPE_MUC : CHANNEL_TYPE_PM,
channel_id, channel_id);
if (replace)
{
const char *orig = NULL;
@ -323,7 +345,7 @@ int connection__message_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *
if (orig)
{
struct diff result;
if (diff(&result, char_cmp, 1, orig, strlen(orig), intext, strlen(intext)) > 0)
if (diff(&result, char_cmp, 1, orig, strlen(orig), text, strlen(text)) > 0)
{
char **visual = weechat_string_dyn_alloc(256);
char ch[2] = {0};
@ -393,7 +415,7 @@ int connection__message_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *
if (channel->type == CHANNEL_TYPE_PM)
weechat_string_dyn_concat(dyn_tags, ",private", -1);
if (weechat_string_match(intext, "/me *", 0))
if (weechat_string_match(text, "/me *", 0))
weechat_string_dyn_concat(dyn_tags, ",xmpp_action", -1);
if (replace)
{
@ -404,24 +426,27 @@ int connection__message_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *
if (date != 0)
weechat_string_dyn_concat(dyn_tags, ",notify_none", -1);
else if (channel->type == CHANNEL_TYPE_PM)
else if (channel->type == CHANNEL_TYPE_PM
&& weechat_strcasecmp(from_bare, account_jid(account)) != 0)
weechat_string_dyn_concat(dyn_tags, ",notify_private", -1);
else
weechat_string_dyn_concat(dyn_tags, ",log1", -1);
const char *edit = replace ? "* " : ""; // Losing which message was edited, sadly
if (x && text == cleartext)
weechat_printf(channel->buffer, "[~]\t%s%s: PGP", weechat_color("gray"), nick);
if (channel_id == from_bare && strcmp(to, channel->id) == 0)
weechat_printf_date_tags(channel->buffer, date, *dyn_tags, "%s%s\t[to %s]: %s",
edit, user__as_prefix_raw(account, nick),
to, difftext ? difftext : intext ? intext : "");
else if (weechat_string_match(intext, "/me *", 0))
to, difftext ? difftext : text ? text : "");
else if (weechat_string_match(text, "/me *", 0))
weechat_printf_date_tags(channel->buffer, date, *dyn_tags, "%s%s\t%s %s",
edit, weechat_prefix("action"), nick,
difftext ? difftext+4 : intext ? intext+4 : "");
edit, weechat_prefix("action"), user__as_prefix_raw(account, nick),
difftext ? difftext+4 : text ? text+4 : "");
else
weechat_printf_date_tags(channel->buffer, date, *dyn_tags, "%s%s\t%s",
edit, user__as_prefix_raw(account, nick),
difftext ? difftext : intext ? intext : "");
difftext ? difftext : text ? text : "");
weechat_string_dyn_free(dyn_tags, 1);
@ -429,6 +454,8 @@ int connection__message_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *
xmpp_free(account->context, intext);
if (difftext)
free(difftext);
if (cleartext)
free(cleartext);
return 1;
}
@ -1002,6 +1029,13 @@ void connection__handler(xmpp_conn_t *conn, xmpp_conn_event_t status,
weechat_command(account->buffer, *command);
weechat_string_dyn_free(command, 1);
}
pgp__init(&account->pgp,
weechat_string_eval_expression(account_pgp_pubring_path(account),
NULL, NULL, NULL),
weechat_string_eval_expression(account_pgp_secring_path(account),
NULL, NULL, NULL));
}
else
{

@ -54,3 +54,30 @@ int input__data_cb(const void *pointer, void *data,
return input__data(buffer, text);
}
int input__typing(struct t_gui_buffer *buffer)
{
struct t_account *account = NULL;
struct t_channel *channel = NULL;
buffer__get_account_and_channel(buffer, &account, &channel);
if (account && account->is_connected && channel)
{
channel__send_typing(account, channel, NULL);
}
return WEECHAT_RC_OK;
}
int input__text_changed_cb(const void *pointer, void *data,
const char *signal, const char *type_data,
void *signal_data)
{
(void) pointer;
(void) data;
(void) signal;
(void) type_data;
return input__typing(signal_data);
}

@ -6,7 +6,11 @@
#define _WEECHAT_XMPP_INPUT_H_
int input__data_cb(const void *pointer, void *data,
struct t_gui_buffer *buffer,
const char *input_data);
struct t_gui_buffer *buffer,
const char *input_data);
int input__text_changed_cb(const void *pointer, void *data,
const char *signal, const char *type_data,
void *signal_data);
#endif /*WEECHAT_XMPP_INPUT_H*/

360
pgp.c

@ -0,0 +1,360 @@
// This Source Code Form is subject to the terms of the Mozilla PublicAA
// License, version 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <rnp/rnp.h>
#include <weechat/weechat-plugin.h>
#include "plugin.h"
#include "pgp.h"
#define RNP_SUCCESS 0
#define PGP_MESSAGE_HEADER "-----BEGIN PGP MESSAGE-----\r\n\r\n"
#define PGP_MESSAGE_FOOTER "\r\n-----END PGP MESSAGE-----\r\n"
const char *PGP_ADVICE = "[PGP encrypted message (XEP-0027)]";
void pgp__init(struct t_pgp **pgp, const char *pub, const char *sec)
{
struct t_pgp *new_pgp;
rnp_input_t keyring;
new_pgp = calloc(1, sizeof(**pgp));
if (rnp_ffi_create(&new_pgp->context,
RNP_KEYSTORE_GPG, RNP_KEYSTORE_GPG) != RNP_SUCCESS) {
return;
}
if (rnp_input_from_path(&keyring, pub) == RNP_SUCCESS) {
if (rnp_load_keys(new_pgp->context, RNP_KEYSTORE_GPG,
keyring, RNP_LOAD_SAVE_PUBLIC_KEYS) == RNP_SUCCESS) {
rnp_input_destroy(keyring);
}
}
if (rnp_input_from_path(&keyring, sec) == RNP_SUCCESS) {
if (rnp_load_keys(new_pgp->context, RNP_KEYSTORE_GPG,
keyring, RNP_LOAD_SAVE_SECRET_KEYS) == RNP_SUCCESS) {
rnp_input_destroy(keyring);
}
}
*pgp = new_pgp;
}
void pgp__free(struct t_pgp *pgp)
{
if (pgp)
{
if (pgp->context)
free(pgp->context);
free(pgp);
}
}
/* this simple helper function just prints armored key, searched by userid, to stdout */
static bool pgp__print_key(rnp_ffi_t rnp, const char *uid, bool secret)
{
rnp_output_t keydata = NULL;
rnp_key_handle_t key = NULL;
uint32_t flags = RNP_KEY_EXPORT_ARMORED | RNP_KEY_EXPORT_SUBKEYS;
uint8_t * buf = NULL;
size_t buf_len = 0;
bool result = false;
/* you may search for the key via userid, keyid, fingerprint, grip */
if (rnp_locate_key(rnp, "userid", uid, &key) != RNP_SUCCESS) {
return false;
}
if (!key) {
return false;
}
/* create in-memory output structure to later use buffer */
if (rnp_output_to_memory(&keydata, 0) != RNP_SUCCESS) {
goto finish;
}
flags = flags | (secret ? RNP_KEY_EXPORT_SECRET : RNP_KEY_EXPORT_PUBLIC);
if (rnp_key_export(key, keydata, flags) != RNP_SUCCESS) {
goto finish;
}
/* get key's contents from the output structure */
if (rnp_output_memory_get_buf(keydata, &buf, &buf_len, false) != RNP_SUCCESS) {
goto finish;
}
weechat_printf(NULL, "pgp: %.*s", (int) buf_len, buf);
result = true;
finish:
rnp_key_handle_destroy(key);
rnp_output_destroy(keydata);
return result;
}
static bool pgp__export_key(rnp_ffi_t rnp, const char *uid, bool secret)
{
rnp_output_t keyfile = NULL;
rnp_key_handle_t key = NULL;
uint32_t flags = RNP_KEY_EXPORT_ARMORED | RNP_KEY_EXPORT_SUBKEYS;
char filename[32] = {0};
char * keyid = NULL;
bool result = false;
/* you may search for the key via userid, keyid, fingerprint, grip */
if (rnp_locate_key(rnp, "userid", uid, &key) != RNP_SUCCESS) {
return false;
}
if (!key) {
return false;
}
/* get key's id and build filename */
if (rnp_key_get_keyid(key, &keyid) != RNP_SUCCESS) {
goto finish;
}
snprintf(filename, sizeof(filename), "key-%s-%s.asc", keyid, secret ? "sec" : "pub");
rnp_buffer_destroy(keyid);
/* create file output structure */
if (rnp_output_to_path(&keyfile, filename) != RNP_SUCCESS) {
goto finish;
}
flags = flags | (secret ? RNP_KEY_EXPORT_SECRET : RNP_KEY_EXPORT_PUBLIC);
if (rnp_key_export(key, keyfile, flags) != RNP_SUCCESS) {
goto finish;
}
result = true;
finish:
rnp_key_handle_destroy(key);
rnp_output_destroy(keyfile);
return result;
}
char *pgp__encrypt(struct t_pgp *pgp, const char *target, const char *message)
{
rnp_op_encrypt_t encrypt = NULL;
rnp_key_handle_t key = NULL;
rnp_input_t keyfile = NULL;
rnp_input_t input = NULL;
rnp_output_t output = NULL;
char * result = NULL;
rnp_result_t ret;
/* create memory input and file output objects for the message and encrypted message */
if ((ret = rnp_input_from_memory(&input, (uint8_t *)message, strlen(message), false)) !=
RNP_SUCCESS) {
const char *reason = rnp_result_to_string(ret);
weechat_printf(NULL, "pgp: failed to create input object: %s\n", reason);
goto encrypt_finish;
}
if ((ret = rnp_output_to_memory(&output, 0)) != RNP_SUCCESS) {
const char *reason = rnp_result_to_string(ret);
weechat_printf(NULL, "pgp: failed to create output object: %s\n", reason);
goto encrypt_finish;
}
/* create encryption operation */
if ((ret = rnp_op_encrypt_create(&encrypt, pgp->context, input, output)) != RNP_SUCCESS) {
const char *reason = rnp_result_to_string(ret);
weechat_printf(NULL, "pgp: failed to create encrypt operation: %s\n", reason);
goto encrypt_finish;
}
/* setup encryption parameters */
rnp_op_encrypt_set_armor(encrypt, true);
rnp_op_encrypt_set_file_name(encrypt, "message.txt");
rnp_op_encrypt_set_file_mtime(encrypt, time(NULL));
rnp_op_encrypt_set_compression(encrypt, "ZIP", 6);
rnp_op_encrypt_set_cipher(encrypt, RNP_ALGNAME_AES_256);
rnp_op_encrypt_set_aead(encrypt, "None");
/* locate recipient's key and add it to the operation context. */
if ((ret = rnp_locate_key(pgp->context, "keyid", target, &key)) != RNP_SUCCESS) {
const char *reason = rnp_result_to_string(ret);
weechat_printf(NULL, "pgp: failed to locate recipient key: %s\n", reason);
goto encrypt_finish;
}
if ((ret = rnp_op_encrypt_add_recipient(encrypt, key)) != RNP_SUCCESS) {
const char *reason = rnp_result_to_string(ret);
weechat_printf(NULL, "pgp: failed to add recipient: %s\n", reason);
goto encrypt_finish;
}
rnp_key_handle_destroy(key);
key = NULL;
/* execute encryption operation */
if ((ret = rnp_op_encrypt_execute(encrypt)) != RNP_SUCCESS) {
const char *reason = rnp_result_to_string(ret);
weechat_printf(NULL, "pgp: encryption failed: %s\n", reason);
goto encrypt_finish;
}
uint8_t *buf;
size_t buf_len;
rnp_output_memory_get_buf(output, &buf, &buf_len, false);
result = strndup((char *)buf + strlen(PGP_MESSAGE_HEADER),
buf_len - strlen(PGP_MESSAGE_HEADER) - strlen(PGP_MESSAGE_FOOTER));
encrypt_finish:
rnp_op_encrypt_destroy(encrypt);
rnp_input_destroy(keyfile);
rnp_input_destroy(input);
rnp_output_destroy(output);
rnp_key_handle_destroy(key);
return result;
}
//"hQIMAzlgcSFDGLKEAQ//cGG3DFughC5xBF7xeXz1RdayOfhBAPfoZIq62MVuSnfS\nMfig65Zxz1LtAnnFq90TZY7hiHPBtVlYqg47AbSoYweMdpXsKgbUrd3NNf6k2nsZ\nUkChCtyGuHi8pTzclfle7gT0nNXJ1WcLCZ4ORZCrg3D5A+YTO9tdmE8GQsTT6TdV\nbbxF5yR4JF5SzFhuFL3ZoXPXrWylcwKXarYfoOTa6M2vSsCwApVIXQgJ/FI46sLT\nb0B/EVCjFvcvjkNr7+K7mQtth+x0a0pC4BtEhRvnIRAe/sdGp8NY+DP76clx4U+k\nIDG4H92F632pR6eEIoZttnBoaj0O4sTVAJCao5AoecR4w2FDqBWWtIyQp5vbo17/\nMtzungkk5vQP6Jhu36wa+JKpbHoxomVpHPZfAtIoyaY6pzQ0bUomIlSVpbZDvF68\nZKTlFd89Pm5x0JO5gsVYvf+N9Ed33d34n/0CFz5K5Tgu4Bk0v4LWEy3wtNsuQB4p\nkBSZJk7I2BakcRwP0zwld6rRHFIX1pb7zqThBPZGB9RkWPltiktUTibOII12tWhi\nksFpQJ8l1A8h9vM5kUXIeD6H2yP0CBUEIZF3Sf+jiSRZ/1/n3KoUrKEzkf/y4xgv\n1LA4pMjNLEr6J2fqGyYRFv4Bxv3PIvF17V5CwOtguxGRJHJXdIzm1BSHSqXxHezS\nYAFXMUb9fw3QX7Ed23KiyZjzd/LRsQBqMs9RsYyZB2PqF9x84lQYYbE8lErrryvK\nUEtmJKPw3Hvb7kgGox5vl5+KCg9q64EU9TgQpufYNShKtDz7Fsvc+ncgZoshDUeo\npw==\n=euIB"
char *pgp__decrypt(struct t_pgp *pgp, const char *ciphertext)
{
rnp_input_t input = NULL;
rnp_output_t output = NULL;
uint8_t * buf = NULL;
size_t buf_len = 0;
char * result = NULL;
rnp_result_t ret;
buf_len = strlen(PGP_MESSAGE_HEADER) + strlen(ciphertext) + strlen(PGP_MESSAGE_FOOTER) + 1;
buf = malloc(sizeof(char) * buf_len);
buf_len = snprintf((char *)buf, buf_len, PGP_MESSAGE_HEADER "%s" PGP_MESSAGE_FOOTER, ciphertext);
/* create file input and memory output objects for the encrypted message and decrypted
* message */
if ((ret = rnp_input_from_memory(&input, buf, buf_len, false)) != RNP_SUCCESS) {
const char *reason = rnp_result_to_string(ret);
weechat_printf(NULL, "pgp: failed to create input object: %s\n", reason);
goto decrypt_finish;
}
if ((ret = rnp_output_to_memory(&output, 0)) != RNP_SUCCESS) {
const char *reason = rnp_result_to_string(ret);
weechat_printf(NULL, "pgp: failed to create output object: %s\n", reason);
goto decrypt_finish;
}
if ((ret = rnp_decrypt(pgp->context, input, output)) != RNP_SUCCESS) {
const char *reason = rnp_result_to_string(ret);
weechat_printf(NULL, "pgp: public-key decryption failed: %s\n", reason);
goto decrypt_finish;
}
free(buf);
/* get the decrypted message from the output structure */
if (rnp_output_memory_get_buf(output, &buf, &buf_len, false) != RNP_SUCCESS) {
goto decrypt_finish;
}
result = strndup((const char *)buf, (int)buf_len);
decrypt_finish:
rnp_input_destroy(input);
rnp_output_destroy(output);
return result;
}
char *pgp__verify(struct t_pgp *pgp)
{
rnp_op_verify_t verify = NULL;
rnp_input_t input = NULL;
rnp_output_t output = NULL;
uint8_t * buf = NULL;
size_t buf_len = 0;
size_t sigcount = 0;
char * result = NULL;
rnp_result_t ret;
/* create file input and memory output objects for the signed message and verified
* message */
if ((ret = rnp_input_from_path(&input, "signed.asc")) != RNP_SUCCESS) {
const char *reason = rnp_result_to_string(ret);
weechat_printf(NULL, "pgp: failed to open file 'signed.asc'. Did you run the sign example?: %s\n", reason);
goto verify_finish;
}
if ((ret = rnp_output_to_memory(&output, 0)) != RNP_SUCCESS) {
const char *reason = rnp_result_to_string(ret);
weechat_printf(NULL, "pgp: failed to create output object: %s\n", reason);
goto verify_finish;
}
if ((ret = rnp_op_verify_create(&verify, pgp->context, input, output)) != RNP_SUCCESS) {
const char *reason = rnp_result_to_string(ret);
weechat_printf(NULL, "pgp: failed to create verification context: %s\n", reason);
goto verify_finish;
}
if ((ret = rnp_op_verify_execute(verify)) != RNP_SUCCESS) {
const char *reason = rnp_result_to_string(ret);
weechat_printf(NULL, "pgp: failed to execute verification operation: %s\n", reason);
goto verify_finish;
}
/* now check signatures and get some info about them */
if ((ret = rnp_op_verify_get_signature_count(verify, &sigcount)) != RNP_SUCCESS) {
const char *reason = rnp_result_to_string(ret);
weechat_printf(NULL, "pgp: failed to get signature count: %s\n", reason);
goto verify_finish;
}
for (size_t i = 0; i < sigcount; i++) {
rnp_op_verify_signature_t sig = NULL;
rnp_result_t sigstatus = RNP_SUCCESS;
rnp_key_handle_t key = NULL;
char * keyid = NULL;
if ((ret = rnp_op_verify_get_signature_at(verify, i, &sig)) != RNP_SUCCESS) {
const char *reason = rnp_result_to_string(ret);
weechat_printf(NULL, "pgp: failed to get signature %d: %s\n", (int)i, reason);
goto verify_finish;
}
if ((ret = rnp_op_verify_signature_get_key(sig, &key)) != RNP_SUCCESS) {
const char *reason = rnp_result_to_string(ret);
weechat_printf(NULL, "pgp: failed to get signature's %d key: %s\n", (int)i, reason);
goto verify_finish;
}
if ((ret = rnp_key_get_keyid(key, &keyid)) != RNP_SUCCESS) {
const char *reason = rnp_result_to_string(ret);
weechat_printf(NULL, "pgp: failed to get key id %d: %s\n", (int)i, reason);
rnp_key_handle_destroy(key);
goto verify_finish;
}
sigstatus = rnp_op_verify_signature_get_status(sig);
weechat_printf(NULL, "pgp: Status for signature from key %s : %d\n", keyid, (int)sigstatus);
result = strdup(keyid);
rnp_buffer_destroy(keyid);
rnp_key_handle_destroy(key);
}
/* get the verified message from the output structure */
if ((ret = rnp_output_memory_get_buf(output, &buf, &buf_len, false)) != RNP_SUCCESS) {
const char *reason = rnp_result_to_string(ret);
(void) reason;
goto verify_finish;
}
weechat_printf(NULL, "pgp: Verified message:\n%.*s\n", (int)buf_len, buf);
verify_finish:
rnp_op_verify_destroy(verify);
rnp_input_destroy(input);
rnp_output_destroy(output);
return result;
}

22
pgp.h

@ -0,0 +1,22 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#ifndef _WEECHAT_XMPP_PGP_H_
#define _WEECHAT_XMPP_PGP_H_
struct t_pgp
{
struct rnp_ffi_st *context;
const char *keyid;
};
void pgp__init(struct t_pgp **pgp, const char *pub, const char *sec);
void pgp__free(struct t_pgp *pgp);
char *pgp__decrypt(struct t_pgp *pgp, const char *ciphertext);
char *pgp__encrypt(struct t_pgp *pgp, const char *target, const char *message);
#endif /*WEECHAT_XMPP_PGP_H*/

@ -13,6 +13,7 @@
#include "account.h"
#include "connection.h"
#include "command.h"
#include "input.h"
#include "buffer.h"
#include "completion.h"
@ -64,6 +65,8 @@ int weechat_plugin_init(struct t_weechat_plugin *plugin, int argc, char *argv[])
&buffer__typing_bar_cb,
NULL, NULL);
weechat_hook_signal("input_text_changed", &input__text_changed_cb, NULL, NULL);
return WEECHAT_RC_OK;
}

@ -140,14 +140,11 @@ struct t_user *user__new(struct t_account *account,
{
struct t_user *new_user, *ptr_user;
if (!account || !id || !display_name)
if (!account || !id)
{
return NULL;
}
if (!display_name[0] && strcmp("USLACKBOT", id) == 0)
return NULL;
if (!account->users)
channel__add_nicklist_groups(account, NULL);
@ -173,23 +170,13 @@ struct t_user *user__new(struct t_account *account,
new_user->id = strdup(id);
new_user->name = NULL;
new_user->team_id = NULL;
new_user->real_name = NULL;
new_user->colour = NULL;
new_user->deleted = 0;
new_user->tz = NULL;
new_user->tz_label = NULL;
new_user->tz_offset = 0;
new_user->locale = NULL;
new_user->profile.avatar_hash = NULL;
new_user->profile.status_text = NULL;
new_user->profile.status_emoji = NULL;
new_user->profile.real_name = NULL;
new_user->profile.display_name = display_name[0] ?
strdup(display_name) :
strdup("???");
new_user->profile.display_name = display_name ?
strdup(display_name) : strdup("");
new_user->profile.real_name_normalized = NULL;
new_user->profile.email = NULL;
new_user->profile.team = NULL;
@ -197,16 +184,6 @@ struct t_user *user__new(struct t_account *account,
new_user->updated = 0;
new_user->is_away = 0;
new_user->is_admin = 0;
new_user->is_owner = 0;
new_user->is_primary_owner = 0;
new_user->is_restricted = 0;
new_user->is_ultra_restricted = 0;
new_user->is_bot = 0;
new_user->is_stranger = 0;
new_user->is_app_user = 0;
new_user->has_2fa = 0;
user__nicklist_add(account, NULL, new_user);
return new_user;
@ -239,18 +216,6 @@ void user__free(struct t_account *account,
free(user->id);
if (user->name)
free(user->name);
if (user->team_id)
free(user->team_id);
if (user->real_name)
free(user->real_name);
if (user->colour)
free(user->colour);
if (user->tz)
free(user->tz);
if (user->tz_label)
free(user->tz_label);
if (user->locale)
free(user->locale);
if (user->profile.avatar_hash)
free(user->profile.avatar_hash);
if (user->profile.status_text)

@ -22,30 +22,11 @@ struct t_user
{
char *id;
char *name;
char *team_id;
char *real_name;
char *colour;
int deleted;
char *tz;
char *tz_label;
int tz_offset;
char *locale;
struct t_user_profile profile;
int updated;
int is_away;
int is_admin;
int is_owner;
int is_primary_owner;
int is_restricted;
int is_ultra_restricted;
int is_bot;
int is_stranger;
int is_app_user;
int has_2fa;
struct t_user *prev_user;
struct t_user *next_user;
};

Loading…
Cancel
Save