mirror of
https://git.jabber.space/devs/cproof.git
synced 2026-07-25 22:26:21 +00:00
Add support for sending encrypted message in MUC
This commit is contained in:
@@ -218,7 +218,7 @@ cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg, const char *const oo
|
|||||||
#ifndef HAVE_LIBGPGME
|
#ifndef HAVE_LIBGPGME
|
||||||
#ifdef HAVE_OMEMO
|
#ifdef HAVE_OMEMO
|
||||||
if (chatwin->is_omemo) {
|
if (chatwin->is_omemo) {
|
||||||
omemo_on_message_send(chatwin, plugin_msg, request_receipt);
|
omemo_on_message_send((ProfWin *)chatwin, plugin_msg, request_receipt, FALSE);
|
||||||
} else {
|
} else {
|
||||||
char *id = message_send_chat(chatwin->barejid, plugin_msg, oob_url, request_receipt);
|
char *id = message_send_chat(chatwin->barejid, plugin_msg, oob_url, request_receipt);
|
||||||
chat_log_msg_out(chatwin->barejid, plugin_msg);
|
chat_log_msg_out(chatwin->barejid, plugin_msg);
|
||||||
@@ -238,7 +238,7 @@ cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg, const char *const oo
|
|||||||
#ifndef HAVE_LIBGPGME
|
#ifndef HAVE_LIBGPGME
|
||||||
#ifdef HAVE_OMEMO
|
#ifdef HAVE_OMEMO
|
||||||
if (chatwin->is_omemo) {
|
if (chatwin->is_omemo) {
|
||||||
omemo_on_message_send(chatwin, plugin_msg, request_receipt);
|
omemo_on_message_send((ProfWin *)chatwin, plugin_msg, request_receipt, FALSE);
|
||||||
} else {
|
} else {
|
||||||
gboolean handled = otr_on_message_send(chatwin, plugin_msg, request_receipt);
|
gboolean handled = otr_on_message_send(chatwin, plugin_msg, request_receipt);
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
@@ -261,7 +261,7 @@ cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg, const char *const oo
|
|||||||
#ifdef HAVE_LIBGPGME
|
#ifdef HAVE_LIBGPGME
|
||||||
#ifdef HAVE_OMEMO
|
#ifdef HAVE_OMEMO
|
||||||
if (chatwin->is_omemo) {
|
if (chatwin->is_omemo) {
|
||||||
omemo_on_message_send(chatwin, plugin_msg, request_receipt);
|
omemo_on_message_send((ProfWin *)chatwin, plugin_msg, request_receipt, FALSE);
|
||||||
} else if (chatwin->pgp_send) {
|
} else if (chatwin->pgp_send) {
|
||||||
char *id = message_send_chat_pgp(chatwin->barejid, plugin_msg, request_receipt);
|
char *id = message_send_chat_pgp(chatwin->barejid, plugin_msg, request_receipt);
|
||||||
chat_log_pgp_msg_out(chatwin->barejid, plugin_msg);
|
chat_log_pgp_msg_out(chatwin->barejid, plugin_msg);
|
||||||
@@ -286,7 +286,7 @@ cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg, const char *const oo
|
|||||||
#ifdef HAVE_LIBGPGME
|
#ifdef HAVE_LIBGPGME
|
||||||
#ifdef HAVE_OMEMO
|
#ifdef HAVE_OMEMO
|
||||||
if (chatwin->is_omemo) {
|
if (chatwin->is_omemo) {
|
||||||
omemo_on_message_send(chatwin, plugin_msg, request_receipt);
|
omemo_on_message_send((ProfWin *)chatwin, plugin_msg, request_receipt, FALSE);
|
||||||
} else if (chatwin->pgp_send) {
|
} else if (chatwin->pgp_send) {
|
||||||
char *id = message_send_chat_pgp(chatwin->barejid, plugin_msg, request_receipt);
|
char *id = message_send_chat_pgp(chatwin->barejid, plugin_msg, request_receipt);
|
||||||
chat_log_pgp_msg_out(chatwin->barejid, plugin_msg);
|
chat_log_pgp_msg_out(chatwin->barejid, plugin_msg);
|
||||||
@@ -334,10 +334,25 @@ cl_ev_send_muc_msg(ProfMucWin *mucwin, const char *const msg, const char *const
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_OMEMO
|
||||||
|
if (mucwin->is_omemo) {
|
||||||
|
omemo_on_message_send((ProfWin *)mucwin, plugin_msg, FALSE, TRUE);
|
||||||
|
} else {
|
||||||
|
message_send_groupchat(mucwin->roomjid, plugin_msg, oob_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins_post_room_message_send(mucwin->roomjid, plugin_msg);
|
||||||
|
free(plugin_msg);
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef HAVE_OMEMO
|
||||||
message_send_groupchat(mucwin->roomjid, plugin_msg, oob_url);
|
message_send_groupchat(mucwin->roomjid, plugin_msg, oob_url);
|
||||||
|
|
||||||
plugins_post_room_message_send(mucwin->roomjid, plugin_msg);
|
plugins_post_room_message_send(mucwin->roomjid, plugin_msg);
|
||||||
free(plugin_msg);
|
free(plugin_msg);
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
@@ -168,6 +169,7 @@ omemo_init(void)
|
|||||||
void
|
void
|
||||||
omemo_on_connect(ProfAccount *account)
|
omemo_on_connect(ProfAccount *account)
|
||||||
{
|
{
|
||||||
|
GError *error = NULL;
|
||||||
char *omemodir = files_get_data_path(DIR_OMEMO);
|
char *omemodir = files_get_data_path(DIR_OMEMO);
|
||||||
GString *basedir = g_string_new(omemodir);
|
GString *basedir = g_string_new(omemodir);
|
||||||
free(omemodir);
|
free(omemodir);
|
||||||
@@ -194,18 +196,26 @@ omemo_on_connect(ProfAccount *account)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
omemo_devicelist_subscribe();
|
||||||
|
|
||||||
omemo_ctx.identity_keyfile = g_key_file_new();
|
omemo_ctx.identity_keyfile = g_key_file_new();
|
||||||
if (g_key_file_load_from_file(omemo_ctx.identity_keyfile, omemo_ctx.identity_filename->str, G_KEY_FILE_KEEP_COMMENTS, NULL)) {
|
omemo_ctx.sessions_keyfile = g_key_file_new();
|
||||||
|
|
||||||
|
if (g_key_file_load_from_file(omemo_ctx.identity_keyfile, omemo_ctx.identity_filename->str, G_KEY_FILE_KEEP_COMMENTS, &error)) {
|
||||||
load_identity();
|
load_identity();
|
||||||
omemo_generate_short_term_crypto_materials(account);
|
omemo_generate_short_term_crypto_materials(account);
|
||||||
|
} else if (error->code != G_FILE_ERROR_NOENT) {
|
||||||
|
log_warning("OMEMO: error loading identity from: %s, %s", omemo_ctx.identity_filename->str, error->message);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
omemo_ctx.sessions_keyfile = g_key_file_new();
|
error = NULL;
|
||||||
if (g_key_file_load_from_file(omemo_ctx.sessions_keyfile, omemo_ctx.sessions_filename->str, G_KEY_FILE_KEEP_COMMENTS, NULL)) {
|
if (g_key_file_load_from_file(omemo_ctx.sessions_keyfile, omemo_ctx.sessions_filename->str, G_KEY_FILE_KEEP_COMMENTS, &error)) {
|
||||||
load_sessions();
|
load_sessions();
|
||||||
|
} else if (error->code != G_FILE_ERROR_NOENT) {
|
||||||
|
log_warning("OMEMO: error loading sessions from: %s, %s", omemo_ctx.sessions_filename->str, error->message);
|
||||||
}
|
}
|
||||||
|
|
||||||
omemo_devicelist_subscribe();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -533,22 +543,15 @@ out:
|
|||||||
}
|
}
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
omemo_on_message_send(ProfChatWin *chatwin, const char *const message, gboolean request_receipt)
|
omemo_on_message_send(ProfWin *win, const char *const message, gboolean request_receipt, gboolean muc)
|
||||||
{
|
{
|
||||||
int res;
|
int res;
|
||||||
gboolean ret = FALSE;
|
gboolean ret = FALSE;
|
||||||
Jid *jid = jid_create(connection_get_fulljid());
|
Jid *jid = jid_create(connection_get_fulljid());
|
||||||
GList *keys = NULL;
|
GList *keys = NULL;
|
||||||
|
|
||||||
GList *recipient_device_id = g_hash_table_lookup(omemo_ctx.device_list, chatwin->barejid);
|
|
||||||
if (!recipient_device_id) {
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
GList *sender_device_id = g_hash_table_lookup(omemo_ctx.device_list, jid->barejid);
|
GList *sender_device_id = g_hash_table_lookup(omemo_ctx.device_list, jid->barejid);
|
||||||
|
|
||||||
/* TODO generate fresh AES-GCM materials */
|
|
||||||
/* TODO encrypt message */
|
|
||||||
unsigned char *key;
|
unsigned char *key;
|
||||||
unsigned char *iv;
|
unsigned char *iv;
|
||||||
unsigned char *ciphertext;
|
unsigned char *ciphertext;
|
||||||
@@ -574,37 +577,72 @@ omemo_on_message_send(ProfChatWin *chatwin, const char *const message, gboolean
|
|||||||
memcpy(key_tag, key, AES128_GCM_KEY_LENGTH);
|
memcpy(key_tag, key, AES128_GCM_KEY_LENGTH);
|
||||||
memcpy(key_tag + AES128_GCM_KEY_LENGTH, tag, AES128_GCM_TAG_LENGTH);
|
memcpy(key_tag + AES128_GCM_KEY_LENGTH, tag, AES128_GCM_TAG_LENGTH);
|
||||||
|
|
||||||
GList *device_ids_iter;
|
GList *recipients = NULL;
|
||||||
for (device_ids_iter = recipient_device_id; device_ids_iter != NULL; device_ids_iter = device_ids_iter->next) {
|
if (muc) {
|
||||||
int res;
|
ProfMucWin *mucwin = (ProfMucWin *)win;
|
||||||
ciphertext_message *ciphertext;
|
assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
|
||||||
session_cipher *cipher;
|
GList *roster = muc_roster(mucwin->roomjid);
|
||||||
signal_protocol_address address = {
|
GList *iter;
|
||||||
.name = chatwin->barejid,
|
for (iter = roster; iter != NULL; iter = iter->next) {
|
||||||
.name_len = strlen(chatwin->barejid),
|
Occupant *occupant = (Occupant *)iter->data;
|
||||||
.device_id = GPOINTER_TO_INT(device_ids_iter->data)
|
Jid *jid = jid_create(occupant->jid);
|
||||||
};
|
if (!jid->barejid) {
|
||||||
|
log_warning("OMEMO: missing barejid for MUC %s occupant %s", mucwin->roomjid, occupant->nick);
|
||||||
res = session_cipher_create(&cipher, omemo_ctx.store, &address, omemo_ctx.signal);
|
} else {
|
||||||
if (res != 0) {
|
recipients = g_list_append(recipients, strdup(jid->barejid));
|
||||||
log_error("OMEMO: cannot create cipher for %s device id %d", address.name, address.device_id);
|
}
|
||||||
continue;
|
jid_destroy(jid);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
res = session_cipher_encrypt(cipher, key_tag, AES128_GCM_KEY_LENGTH + AES128_GCM_TAG_LENGTH, &ciphertext);
|
ProfChatWin *chatwin = (ProfChatWin *)win;
|
||||||
if (res != 0) {
|
assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
|
||||||
log_error("OMEMO: cannot encrypt key for %s device id %d", address.name, address.device_id);
|
recipients = g_list_append(recipients, strdup(chatwin->barejid));
|
||||||
continue;
|
|
||||||
}
|
|
||||||
signal_buffer *buffer = ciphertext_message_get_serialized(ciphertext);
|
|
||||||
omemo_key_t *key = malloc(sizeof(omemo_key_t));
|
|
||||||
key->data = signal_buffer_data(buffer);
|
|
||||||
key->length = signal_buffer_len(buffer);
|
|
||||||
key->device_id = GPOINTER_TO_INT(device_ids_iter->data);
|
|
||||||
key->prekey = ciphertext_message_get_type(ciphertext) == CIPHERTEXT_PREKEY_TYPE;
|
|
||||||
keys = g_list_append(keys, key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GList *device_ids_iter;
|
||||||
|
|
||||||
|
GList *recipients_iter;
|
||||||
|
for (recipients_iter = recipients; recipients_iter != NULL; recipients_iter = recipients_iter->next) {
|
||||||
|
GList *recipient_device_id = NULL;
|
||||||
|
recipient_device_id = g_hash_table_lookup(omemo_ctx.device_list, recipients_iter->data);
|
||||||
|
if (!recipient_device_id) {
|
||||||
|
log_warning("OMEMO: cannot find device ids for %s", recipients_iter->data);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (device_ids_iter = recipient_device_id; device_ids_iter != NULL; device_ids_iter = device_ids_iter->next) {
|
||||||
|
int res;
|
||||||
|
ciphertext_message *ciphertext;
|
||||||
|
session_cipher *cipher;
|
||||||
|
signal_protocol_address address = {
|
||||||
|
.name = recipients_iter->data,
|
||||||
|
.name_len = strlen(recipients_iter->data),
|
||||||
|
.device_id = GPOINTER_TO_INT(device_ids_iter->data)
|
||||||
|
};
|
||||||
|
|
||||||
|
res = session_cipher_create(&cipher, omemo_ctx.store, &address, omemo_ctx.signal);
|
||||||
|
if (res != 0) {
|
||||||
|
log_error("OMEMO: cannot create cipher for %s device id %d", address.name, address.device_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = session_cipher_encrypt(cipher, key_tag, AES128_GCM_KEY_LENGTH + AES128_GCM_TAG_LENGTH, &ciphertext);
|
||||||
|
if (res != 0) {
|
||||||
|
log_error("OMEMO: cannot encrypt key for %s device id %d", address.name, address.device_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
signal_buffer *buffer = ciphertext_message_get_serialized(ciphertext);
|
||||||
|
omemo_key_t *key = malloc(sizeof(omemo_key_t));
|
||||||
|
key->data = signal_buffer_data(buffer);
|
||||||
|
key->length = signal_buffer_len(buffer);
|
||||||
|
key->device_id = GPOINTER_TO_INT(device_ids_iter->data);
|
||||||
|
key->prekey = ciphertext_message_get_type(ciphertext) == CIPHERTEXT_PREKEY_TYPE;
|
||||||
|
keys = g_list_append(keys, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_list_free_full(recipients, free);
|
||||||
|
|
||||||
for (device_ids_iter = sender_device_id; device_ids_iter != NULL; device_ids_iter = device_ids_iter->next) {
|
for (device_ids_iter = sender_device_id; device_ids_iter != NULL; device_ids_iter = device_ids_iter->next) {
|
||||||
int res;
|
int res;
|
||||||
ciphertext_message *ciphertext;
|
ciphertext_message *ciphertext;
|
||||||
@@ -635,14 +673,23 @@ omemo_on_message_send(ProfChatWin *chatwin, const char *const message, gboolean
|
|||||||
keys = g_list_append(keys, key);
|
keys = g_list_append(keys, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
char *id = message_send_chat_omemo(chatwin->barejid, omemo_ctx.device_id, keys, iv, AES128_GCM_IV_LENGTH, ciphertext, ciphertext_len, request_receipt);
|
if (muc) {
|
||||||
chat_log_omemo_msg_out(chatwin->barejid, message);
|
ProfMucWin *mucwin = (ProfMucWin *)win;
|
||||||
chatwin_outgoing_msg(chatwin, message, id, PROF_MSG_OMEMO, request_receipt);
|
assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
|
||||||
|
char *id = message_send_chat_omemo(mucwin->roomjid, omemo_ctx.device_id, keys, iv, AES128_GCM_IV_LENGTH, ciphertext, ciphertext_len, request_receipt, TRUE);
|
||||||
|
free(id);
|
||||||
|
} else {
|
||||||
|
ProfChatWin *chatwin = (ProfChatWin *)win;
|
||||||
|
assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
|
||||||
|
char *id = message_send_chat_omemo(chatwin->barejid, omemo_ctx.device_id, keys, iv, AES128_GCM_IV_LENGTH, ciphertext, ciphertext_len, request_receipt, FALSE);
|
||||||
|
chat_log_omemo_msg_out(chatwin->barejid, message);
|
||||||
|
chatwin_outgoing_msg(chatwin, message, id, PROF_MSG_OMEMO, request_receipt);
|
||||||
|
free(id);
|
||||||
|
}
|
||||||
ret = TRUE;
|
ret = TRUE;
|
||||||
|
|
||||||
out:
|
out:
|
||||||
jid_destroy(jid);
|
jid_destroy(jid);
|
||||||
free(id);
|
|
||||||
g_list_free_full(keys, free);
|
g_list_free_full(keys, free);
|
||||||
free(ciphertext);
|
free(ciphertext);
|
||||||
gcry_free(key);
|
gcry_free(key);
|
||||||
@@ -659,7 +706,7 @@ omemo_on_message_recv(const char *const from_jid, uint32_t sid,
|
|||||||
const unsigned char *const payload, size_t payload_len, gboolean muc)
|
const unsigned char *const payload, size_t payload_len, gboolean muc)
|
||||||
{
|
{
|
||||||
unsigned char *plaintext = NULL;
|
unsigned char *plaintext = NULL;
|
||||||
Jid *sender;
|
Jid *sender = NULL;
|
||||||
Jid *from = jid_create(from_jid);
|
Jid *from = jid_create(from_jid);
|
||||||
if (!from) {
|
if (!from) {
|
||||||
log_error("Invalid jid %s", from_jid);
|
log_error("Invalid jid %s", from_jid);
|
||||||
|
|||||||
@@ -40,5 +40,5 @@ void omemo_start_muc_sessions(const char *const roomjid);
|
|||||||
void omemo_start_device_session(const char *const jid, uint32_t device_id, GList *prekeys, uint32_t signed_prekey_id, const unsigned char *const signed_prekey, size_t signed_prekey_len, const unsigned char *const signature, size_t signature_len, const unsigned char *const identity_key, size_t identity_key_len);
|
void omemo_start_device_session(const char *const jid, uint32_t device_id, GList *prekeys, uint32_t signed_prekey_id, const unsigned char *const signed_prekey, size_t signed_prekey_len, const unsigned char *const signature, size_t signature_len, const unsigned char *const identity_key, size_t identity_key_len);
|
||||||
|
|
||||||
gboolean omemo_loaded(void);
|
gboolean omemo_loaded(void);
|
||||||
gboolean omemo_on_message_send(ProfChatWin *chatwin, const char *const message, gboolean request_receipt);
|
gboolean omemo_on_message_send(ProfWin *win, const char *const message, gboolean request_receipt, gboolean muc);
|
||||||
char * omemo_on_message_recv(const char *const from, uint32_t sid, const unsigned char *const iv, size_t iv_len, GList *keys, const unsigned char *const payload, size_t payload_len, gboolean muc);
|
char * omemo_on_message_recv(const char *const from, uint32_t sid, const unsigned char *const iv, size_t iv_len, GList *keys, const unsigned char *const payload, size_t payload_len, gboolean muc);
|
||||||
|
|||||||
@@ -318,13 +318,19 @@ char*
|
|||||||
message_send_chat_omemo(const char *const jid, uint32_t sid, GList *keys,
|
message_send_chat_omemo(const char *const jid, uint32_t sid, GList *keys,
|
||||||
const unsigned char *const iv, size_t iv_len,
|
const unsigned char *const iv, size_t iv_len,
|
||||||
const unsigned char *const ciphertext, size_t ciphertext_len,
|
const unsigned char *const ciphertext, size_t ciphertext_len,
|
||||||
gboolean request_receipt)
|
gboolean request_receipt, gboolean muc)
|
||||||
{
|
{
|
||||||
char *state = chat_session_get_state(jid);
|
char *state = chat_session_get_state(jid);
|
||||||
xmpp_ctx_t * const ctx = connection_get_ctx();
|
xmpp_ctx_t * const ctx = connection_get_ctx();
|
||||||
char *id = connection_create_stanza_id("msg");
|
char *id;
|
||||||
|
xmpp_stanza_t *message;
|
||||||
xmpp_stanza_t *message = xmpp_message_new(ctx, STANZA_TYPE_CHAT, jid, id);
|
if (muc) {
|
||||||
|
id = connection_create_stanza_id("muc");
|
||||||
|
message = xmpp_message_new(ctx, STANZA_TYPE_GROUPCHAT, jid, id);
|
||||||
|
} else {
|
||||||
|
id = connection_create_stanza_id("msg");
|
||||||
|
message = xmpp_message_new(ctx, STANZA_TYPE_CHAT, jid, id);
|
||||||
|
}
|
||||||
|
|
||||||
xmpp_stanza_t *encrypted = xmpp_stanza_new(ctx);
|
xmpp_stanza_t *encrypted = xmpp_stanza_new(ctx);
|
||||||
xmpp_stanza_set_name(encrypted, "encrypted");
|
xmpp_stanza_set_name(encrypted, "encrypted");
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ char* message_send_chat(const char *const barejid, const char *const msg, const
|
|||||||
gboolean request_receipt);
|
gboolean request_receipt);
|
||||||
char* message_send_chat_otr(const char *const barejid, const char *const msg, gboolean request_receipt);
|
char* message_send_chat_otr(const char *const barejid, const char *const msg, gboolean request_receipt);
|
||||||
char* message_send_chat_pgp(const char *const barejid, const char *const msg, gboolean request_receipt);
|
char* message_send_chat_pgp(const char *const barejid, const char *const msg, gboolean request_receipt);
|
||||||
char* message_send_chat_omemo(const char *const jid, uint32_t sid, GList *keys, const unsigned char *const iv, size_t iv_len, const unsigned char *const ciphertext, size_t ciphertext_len, gboolean request_receipt);
|
char* message_send_chat_omemo(const char *const jid, uint32_t sid, GList *keys, const unsigned char *const iv, size_t iv_len, const unsigned char *const ciphertext, size_t ciphertext_len, gboolean request_receipt, gboolean muc);
|
||||||
void message_send_private(const char *const fulljid, const char *const msg, const char *const oob_url);
|
void message_send_private(const char *const fulljid, const char *const msg, const char *const oob_url);
|
||||||
void message_send_groupchat(const char *const roomjid, const char *const msg, const char *const oob_url);
|
void message_send_groupchat(const char *const roomjid, const char *const msg, const char *const oob_url);
|
||||||
void message_send_groupchat_subject(const char *const roomjid, const char *const subject);
|
void message_send_groupchat_subject(const char *const roomjid, const char *const subject);
|
||||||
|
|||||||
Reference in New Issue
Block a user