summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordefanor <defanor@uberspace.net>2023-11-17 18:34:47 +0300
committerdefanor <defanor@uberspace.net>2023-11-17 18:34:47 +0300
commitecbef993632c9b3bdf442b381e02e1ad24bc1c87 (patch)
treee431ae6eabc88974685d7ef7f4d08bb9a0968308 /src
parentdb7a0b227e8e51d40908471206f5cd287c99fa92 (diff)
Implement MUC self-ping (XEP-0410)
Diffstat (limited to 'src')
-rw-r--r--src/rexmpp.c228
-rw-r--r--src/rexmpp.h64
-rw-r--r--src/rexmpp.rs41
-rw-r--r--src/rexmpp_console.c106
4 files changed, 388 insertions, 51 deletions
diff --git a/src/rexmpp.c b/src/rexmpp.c
index 5bfa375..7edf610 100644
--- a/src/rexmpp.c
+++ b/src/rexmpp.c
@@ -112,6 +112,142 @@ void rexmpp_log (rexmpp_t *s, int priority, const char *format, ...)
}
}
+rexmpp_err_t rexmpp_muc_ping_set (rexmpp_t *s,
+ const char *occupant_jid,
+ const char *password,
+ unsigned int delay)
+{
+ /* At first try to edit an existing record. */
+ rexmpp_muc_ping_t *mp = s->muc_ping;
+ while (mp != NULL) {
+ if (strcmp(mp->jid, occupant_jid) == 0) {
+ mp->delay = delay;
+ return REXMPP_SUCCESS;
+ }
+ mp = mp->next;
+ }
+
+ /* No existing self-ping record for this occupant JID; create a new
+ one. */
+ mp = malloc(sizeof(rexmpp_muc_ping_t));
+ if (mp == NULL) {
+ rexmpp_log(s, LOG_ERR,
+ "Failed to allocate memory for a MUC self-ping record: %s",
+ strerror(errno));
+ return REXMPP_E_MALLOC;
+ }
+ mp->jid = strdup(occupant_jid);
+ if (mp->jid == NULL) {
+ rexmpp_log(s, LOG_ERR,
+ "Failed to duplicate JID string for a MUC self-ping record: %s",
+ strerror(errno));
+ free(mp);
+ return REXMPP_E_OTHER;
+ }
+ if (password != NULL) {
+ mp->password = strdup(password);
+ } else {
+ mp->password = NULL;
+ }
+
+ mp->delay = delay;
+ mp->requested = 0;
+ mp->last_activity.tv_sec = 0;
+ mp->last_activity.tv_nsec = 0;
+ mp->next = s->muc_ping;
+ s->muc_ping = mp;
+ return REXMPP_SUCCESS;
+}
+
+rexmpp_err_t rexmpp_muc_ping_remove (rexmpp_t *s, const char *occupant_jid) {
+ rexmpp_muc_ping_t **mp = &(s->muc_ping);
+ while (*mp != NULL) {
+ if (strcmp(occupant_jid, (*mp)->jid) == 0) {
+ rexmpp_muc_ping_t *found = *mp;
+ *mp = found->next;
+ free(found->jid);
+ if (found->password != NULL) {
+ free(found->password);
+ }
+ free(found);
+ return REXMPP_SUCCESS;
+ }
+ mp = &((*mp)->next);
+ }
+ rexmpp_log(s, LOG_WARNING,
+ "Removal of MUC self-ping record for JID %s is requested, "
+ "but no such record is found",
+ occupant_jid);
+ return REXMPP_E_OTHER;
+}
+
+rexmpp_err_t rexmpp_muc_join (rexmpp_t *s,
+ const char *occupant_jid,
+ const char *password,
+ unsigned int ping_delay)
+{
+ rexmpp_xml_t *presence =
+ rexmpp_xml_new_elem("presence", "jabber:client");
+ rexmpp_xml_add_id(presence);
+ rexmpp_xml_add_attr(presence, "from", s->assigned_jid.full);
+ rexmpp_xml_add_attr(presence, "to", occupant_jid);
+ rexmpp_xml_t *x =
+ rexmpp_xml_new_elem("x", "http://jabber.org/protocol/muc");
+ rexmpp_xml_add_child(presence, x);
+ rexmpp_err_t ret = rexmpp_send(s, presence);
+ if (ping_delay > 0) {
+ rexmpp_muc_ping_set(s, occupant_jid, password, ping_delay);
+ }
+ return ret;
+}
+
+rexmpp_err_t rexmpp_muc_leave (rexmpp_t *s, const char *occupant_jid) {
+ rexmpp_muc_ping_remove(s, occupant_jid);
+ rexmpp_xml_t *presence =
+ rexmpp_xml_new_elem("presence", "jabber:client");
+ rexmpp_xml_add_id(presence);
+ rexmpp_xml_add_attr(presence, "from", s->assigned_jid.full);
+ rexmpp_xml_add_attr(presence, "to", occupant_jid);
+ rexmpp_xml_add_attr(presence, "type", "unavailable");
+ return rexmpp_send(s, presence);
+}
+
+void rexmpp_muc_pong (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ rexmpp_muc_ping_t *mp = ptr;
+ clock_gettime(CLOCK_MONOTONIC, &(mp->last_activity));
+ mp->requested = 0;
+ if (! success) {
+ const char *jid = rexmpp_xml_find_attr_val(req, "to");
+ rexmpp_xml_t *error = rexmpp_xml_first_elem_child(response);
+ if (error == NULL) {
+ rexmpp_log(s, LOG_ERR,
+ "MUC self-ping failure for %s, and no error element received",
+ jid);
+ rexmpp_muc_ping_remove(s, jid);
+ } else {
+ rexmpp_log(s, LOG_WARNING, "MUC self-ping failure for %s: %s",
+ jid, error->alt.elem.qname.name);
+ if (rexmpp_xml_match(error, NULL, "service-unavailable") ||
+ rexmpp_xml_match(error, NULL, "feature-not-implemented") ||
+ rexmpp_xml_match(error, NULL, "remote-server-not-found") ||
+ rexmpp_xml_match(error, NULL, "remote-server-timeout")) {
+ rexmpp_log(s, LOG_WARNING, "Giving up on pinging it");
+ rexmpp_muc_ping_remove(s, jid);
+ } else if (rexmpp_xml_match(error, NULL, "item-not-found")) {
+ /* Ignore and keep pinging? */
+ } else {
+ /* Some other error, re-join. */
+ rexmpp_muc_join(s, jid, NULL, 0);
+ }
+ }
+ }
+}
+
char *rexmpp_capabilities_hash (rexmpp_t *s,
rexmpp_xml_t *info)
{
@@ -474,6 +610,7 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s,
s->local_address = NULL;
s->jingle_prefer_rtcp_mux = 1;
s->path_mtu_discovery = -1;
+ s->muc_ping_default_delay = 600;
s->send_buffer = NULL;
s->send_queue = NULL;
s->server_srv = NULL;
@@ -517,6 +654,7 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s,
s->ping_requested = 0;
s->last_network_activity.tv_sec = 0;
s->last_network_activity.tv_nsec = 0;
+ s->muc_ping = NULL;
s->disco_info = NULL;
s->jingle_rtp_description =
@@ -747,6 +885,12 @@ void rexmpp_done (rexmpp_t *s) {
free(s->roster_ver);
s->roster_ver = NULL;
}
+ while (s->muc_ping != NULL) {
+ rexmpp_muc_ping_t *mp_next = s->muc_ping->next;
+ free(s->muc_ping->jid);
+ free(s->muc_ping);
+ s->muc_ping = mp_next;
+ }
if (s->disco_info != NULL) {
rexmpp_xml_free_list(s->disco_info);
s->disco_info = NULL;
@@ -1983,36 +2127,32 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, rexmpp_xml_t *elem) {
if (item_id == NULL) {
continue;
}
- const char *autojoin = rexmpp_xml_find_attr_val(conference, "autojoin");
+ const char *autojoin =
+ rexmpp_xml_find_attr_val(conference, "autojoin");
if (autojoin == NULL) {
continue;
}
if (strcmp(autojoin, "true") == 0 ||
strcmp(autojoin, "1") == 0) {
- rexmpp_xml_t *presence =
- rexmpp_xml_new_elem("presence", "jabber:client");
- rexmpp_xml_add_id(presence);
- rexmpp_xml_add_attr(presence, "from",
- s->assigned_jid.full);
rexmpp_xml_t *nick =
rexmpp_xml_find_child(conference,
"urn:xmpp:bookmarks:1",
"nick");
- const char *nick_str;
+ const char *nick_str = NULL;
if (nick != NULL) {
nick_str = rexmpp_xml_text_child(nick);
- } else {
+ }
+ if (nick_str == NULL) {
nick_str = s->initial_jid.local;
}
- char *jid = malloc(strlen(item_id) + strlen(nick_str) + 2);
- sprintf(jid, "%s/%s", item_id, nick_str);
- rexmpp_xml_add_attr(presence, "to", jid);
- free(jid);
- rexmpp_xml_t *x =
- rexmpp_xml_new_elem("x",
- "http://jabber.org/protocol/muc");
- rexmpp_xml_add_child(presence, x);
- rexmpp_send(s, presence);
+ char *occupant_jid =
+ malloc(strlen(item_id) + strlen(nick_str) + 2);
+ sprintf(occupant_jid, "%s/%s", item_id, nick_str);
+ const char *password =
+ rexmpp_xml_find_attr_val(conference, "password");
+ rexmpp_muc_join(s, occupant_jid, password,
+ s->muc_ping_default_delay);
+ free(occupant_jid);
}
}
}
@@ -2446,8 +2586,41 @@ rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
}
}
+ /* MUC self-pinging. */
+ if (s->tcp_state == REXMPP_TCP_CONNECTED &&
+ s->stream_state == REXMPP_STREAM_READY)
+ {
+ rexmpp_muc_ping_t *mp = s->muc_ping;
+ while (mp != NULL) {
+ if (mp->last_activity.tv_sec + mp->delay <= now.tv_sec) {
+ if (mp->requested == 0) {
+ clock_gettime(CLOCK_MONOTONIC, &(mp->last_activity));
+ mp->requested = 1;
+ rexmpp_xml_t *ping_cmd =
+ rexmpp_xml_new_elem("ping", "urn:xmpp:ping");
+ rexmpp_iq_new(s, "get", mp->jid,
+ ping_cmd, rexmpp_muc_pong, mp);
+ } else {
+ /* Requested already, and delay time passed again, without
+ a reply (not even an error). Warn the user, remove this
+ MUC. */
+ char *occupant_jid_to_remove = mp->jid;
+ rexmpp_log(s, LOG_WARNING,
+ "No MUC self-ping reply for %s, "
+ "disabling self-ping for it",
+ mp->jid);
+ mp = mp->next;
+ rexmpp_muc_ping_remove(s, occupant_jid_to_remove);
+ continue;
+ }
+ }
+ mp = mp->next;
+ }
+ }
+
/* Pinging the server. */
if (s->tcp_state == REXMPP_TCP_CONNECTED &&
+ s->stream_state == REXMPP_STREAM_READY &&
s->last_network_activity.tv_sec + s->ping_delay <= now.tv_sec) {
if (s->ping_requested == 0) {
s->ping_requested = 1;
@@ -2456,6 +2629,9 @@ rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
rexmpp_iq_new(s, "get", s->initial_jid.domain,
ping_cmd, rexmpp_pong, NULL);
} else {
+ /* Last network activity is updated on sending as well as on
+ receiving, so this will not be triggered right after sending
+ the request. */
rexmpp_log(s, LOG_WARNING, "Ping timeout, reconnecting.");
rexmpp_cleanup(s);
rexmpp_schedule_reconnect(s);
@@ -2609,6 +2785,24 @@ struct timespec *rexmpp_timeout (rexmpp_t *s,
}
if (s->tcp_state == REXMPP_TCP_CONNECTED &&
+ s->stream_state == REXMPP_STREAM_READY) {
+ rexmpp_muc_ping_t *mp = s->muc_ping;
+ while (mp != NULL) {
+ if (mp->last_activity.tv_sec + mp->delay > now.tv_sec) {
+ time_t next_ping =
+ mp->last_activity.tv_sec + mp->delay - now.tv_sec;
+ if (ret == NULL || next_ping < ret->tv_sec) {
+ tv->tv_sec = next_ping;
+ tv->tv_nsec = 0;
+ ret = tv;
+ }
+ }
+ mp = mp->next;
+ }
+ }
+
+ if (s->tcp_state == REXMPP_TCP_CONNECTED &&
+ s->stream_state == REXMPP_STREAM_READY &&
s->last_network_activity.tv_sec + s->ping_delay > now.tv_sec) {
time_t next_ping =
s->last_network_activity.tv_sec + s->ping_delay - now.tv_sec;
diff --git a/src/rexmpp.h b/src/rexmpp.h
index e9d833f..1f7ab32 100644
--- a/src/rexmpp.h
+++ b/src/rexmpp.h
@@ -227,6 +227,24 @@ struct rexmpp_iq
rexmpp_iq_t *next;
};
+typedef struct rexmpp_muc_ping rexmpp_muc_ping_t;
+
+/** @brief MUC self-ping data. */
+struct rexmpp_muc_ping
+{
+ /** @brief Own occupant JID to ping. */
+ char *jid;
+ /** @brief Optional password to rejoin with. */
+ char *password;
+ /** @brief Ping delay, in seconds. */
+ unsigned int delay;
+ /** @brief Whether a ping is requested (pending) already. */
+ int requested;
+ /** @brief When the MUC was active. */
+ struct timespec last_activity;
+ rexmpp_muc_ping_t *next;
+};
+
typedef void (*log_function_t) (rexmpp_t *s, int priority, const char *format, va_list args);
typedef int (*sasl_property_cb_t) (rexmpp_t *s, rexmpp_sasl_property prop);
typedef int (*xml_in_cb_t) (rexmpp_t *s, rexmpp_xml_t *node);
@@ -281,6 +299,8 @@ struct rexmpp
int jingle_prefer_rtcp_mux;
int path_mtu_discovery; /* An IP_MTU_DISCOVER parameter for
TCP sockets, or -1 to not set it */
+ /* A delay in seconds, to use for MUC self-ping by default */
+ unsigned int muc_ping_default_delay;
/* Resource limits. */
uint32_t stanza_queue_size;
@@ -337,10 +357,13 @@ struct rexmpp
char *stream_id;
/* Server ping configuration and state. */
- int ping_delay;
+ unsigned int ping_delay;
int ping_requested;
struct timespec last_network_activity;
+ /* MUC self-ping */
+ rexmpp_muc_ping_t *muc_ping;
+
/* DNS-related structures. */
rexmpp_dns_ctx_t resolver;
rexmpp_dns_result_t *server_srv;
@@ -584,4 +607,43 @@ rexmpp_disco_find_feature (rexmpp_t *s,
int fresh,
int max_requests);
+/**
+ @brief Add a MUC JID to self-ping
+ @param[in,out] s ::rexmpp
+ @param[in] jid Own occupant JID to ping
+ @param[in] password Optional password to rejoin with
+ @param[in] delay How often to ping, in seconds
+*/
+rexmpp_err_t rexmpp_muc_ping_set (rexmpp_t *s,
+ const char *occupant_jid,
+ const char *password,
+ unsigned int delay);
+
+/**
+ @brief Remove a MUC JID to self-ping
+ @param[in,out] s ::rexmpp
+ @param[in] jid Own occupant JID
+*/
+rexmpp_err_t rexmpp_muc_ping_remove (rexmpp_t *s,
+ const char *occupant_jid);
+
+/**
+ @brief Join a MUC, optionally setting self-ping
+ @param[in,out] s ::rexmpp
+ @param[in] occupant_jid Occupant JID
+ @param[in] password Optional password
+ @param[in] ping_delay MUC self-ping delay, 0 to not set it
+*/
+rexmpp_err_t rexmpp_muc_join (rexmpp_t *s,
+ const char *occupant_jid,
+ const char *password,
+ unsigned int ping_delay);
+
+/**
+ @brief Leave a MUC, stop self-pinging it
+ @param[in,out] s ::rexmpp
+ @param[in] occupant_jid Occupant JID
+*/
+rexmpp_err_t rexmpp_muc_leave (rexmpp_t *s, const char *occupant_jid);
+
#endif
diff --git a/src/rexmpp.rs b/src/rexmpp.rs
index 5531ca2..a6950a4 100644
--- a/src/rexmpp.rs
+++ b/src/rexmpp.rs
@@ -103,6 +103,16 @@ pub struct RexmppIQ {
}
#[repr(C)]
+pub struct RexmppMUCPing {
+ pub jid: *mut c_char,
+ pub password: *mut c_char,
+ pub delay: c_uint,
+ pub requested: bool,
+ pub last_activity: timespec,
+ pub next: *mut RexmppMUCPing
+}
+
+#[repr(C)]
pub struct Rexmpp {
pub resolver_state: ResolverState,
pub tcp_state: TCPState,
@@ -118,7 +128,7 @@ pub struct Rexmpp {
// Manual host/port configuration
pub manual_host: *const c_char,
pub manual_port: u16,
- pub manual_direct_tls: c_int,
+ pub manual_direct_tls: bool,
// Miscellaneous settings
pub disco_node: *const c_char,
@@ -128,24 +138,26 @@ pub struct Rexmpp {
pub socks_port: u16,
// Various knobs (these are used instead of loadable modules)
- pub enable_carbons: c_int, // XEP-0280
- pub manage_roster: c_int,
+ pub enable_carbons: bool, // XEP-0280
+ pub manage_roster: bool,
pub roster_cache_file: *const c_char,
- pub track_roster_presence: c_int,
- pub track_roster_events: c_int, // XEP-0163
- pub nick_notifications: c_int, // XEP-0172
- pub retrieve_openpgp_keys: c_int, // XEP-0373
- pub autojoin_bookmarked_mucs: c_int, // XEP-0402
+ pub track_roster_presence: bool,
+ pub track_roster_events: bool, // XEP-0163
+ pub nick_notifications: bool, // XEP-0172
+ pub retrieve_openpgp_keys: bool, // XEP-0373
+ pub autojoin_bookmarked_mucs: bool, // XEP-0402
pub tls_policy: TLSPolicy,
- pub enable_jingle: c_int,
+ pub enable_jingle: bool,
pub client_name: *const c_char, // XEP-0030, XEP-0092
pub client_type: *const c_char, // XEP-0030
pub client_version: *const c_char, // XEP-0092
pub local_address: *const c_char, // For ICE, XEP-0176
- pub jingle_prefer_rtcp_mux: c_int,
+ pub jingle_prefer_rtcp_mux: bool,
// An IP_MTU_DISCOVER parameter for TCP sockets, or -1 to not set
// it
pub path_mtu_discovery: c_int,
+ // A delay in seconds, to use for MUC self-ping by default
+ pub muc_ping_default_delay: c_uint,
// Resource limits
pub stanza_queue_size: u32,
pub send_queue_size: u32,
@@ -203,10 +215,13 @@ pub struct Rexmpp {
pub stream_id: *mut c_char,
// Server ping configuration and state
- pub ping_delay: c_int,
- pub ping_requested: c_int,
+ pub ping_delay: c_uint,
+ pub ping_requested: bool,
pub last_network_activity: timespec,
+ // MUC self-ping
+ pub muc_ping: *mut RexmppMUCPing,
+
// DNS-related structures
pub resolver: *mut c_void,
pub server_srv: *mut rexmpp_dns::RexmppDNSResult,
@@ -222,7 +237,7 @@ pub struct Rexmpp {
// The primary socket used for communication with the server
pub server_socket: c_int,
// Whether the address it's connected to was verified with DNSSEC
- pub server_socket_dns_secure: c_int,
+ pub server_socket_dns_secure: bool,
// A structure used to establish a TCP connection
pub server_connection: rexmpp_tcp::RexmppTCPConnection,
diff --git a/src/rexmpp_console.c b/src/rexmpp_console.c
index 64e5493..a6f859a 100644
--- a/src/rexmpp_console.c
+++ b/src/rexmpp_console.c
@@ -351,6 +351,38 @@ void rexmpp_console_pubsub_node_deleted (rexmpp_t *s,
}
}
+void rexmpp_bookmark_added (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)ptr;
+ (void)req;
+ (void)response;
+ if (success) {
+ rexmpp_console_printf(s, "Added a bookmark.\n");
+ } else {
+ rexmpp_console_printf(s, "Failed to add a bookmark.\n");
+ }
+}
+
+void rexmpp_bookmark_removed (rexmpp_t *s,
+ void *ptr,
+ rexmpp_xml_t *req,
+ rexmpp_xml_t *response,
+ int success)
+{
+ (void)ptr;
+ (void)req;
+ (void)response;
+ if (success) {
+ rexmpp_console_printf(s, "Removed a bookmark.\n");
+ } else {
+ rexmpp_console_printf(s, "Failed to remove a bookmark.\n");
+ }
+}
+
void rexmpp_console_blocklist (rexmpp_t *s,
void *ptr,
rexmpp_xml_t *req,
@@ -440,6 +472,8 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) {
"muc join <conference> [as] <nick>\n"
"muc leave <conference> [as] <nick>\n"
"muc tell <conference> <message>\n"
+ "bookmark add <conference> [autojoin] [nick] [password]\n"
+ "bookmark remove <conference>\n"
"roster list\n"
"roster add <jid>\n"
"roster delete <jid>\n"
@@ -564,24 +598,20 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) {
return;
}
word = strtok_r(NULL, " ", &words_save_ptr);
+ if (word == NULL) {
+ return;
+ }
if (! strcmp(word, "as")) {
word = strtok_r(NULL, " ", &words_save_ptr);
}
if (word == NULL) {
return;
}
- char *full_jid = malloc(strlen(jid.bare) + strlen(word) + 2);
- snprintf(full_jid, strlen(jid_str) + strlen(word) + 2, "%s/%s",
+ char *occupant_jid = malloc(strlen(jid.bare) + strlen(word) + 2);
+ snprintf(occupant_jid, strlen(jid_str) + strlen(word) + 2, "%s/%s",
jid.bare, word);
- presence = rexmpp_xml_new_elem("presence", "jabber:client");
- rexmpp_xml_add_id(presence);
- rexmpp_xml_add_attr(presence, "from", s->assigned_jid.full);
- rexmpp_xml_add_attr(presence, "to", full_jid);
- rexmpp_xml_t *x =
- rexmpp_xml_new_elem("x", "http://jabber.org/protocol/muc");
- rexmpp_xml_add_child(presence, x);
- rexmpp_send(s, presence);
- free(full_jid);
+ rexmpp_muc_join(s, occupant_jid, NULL, s->muc_ping_default_delay);
+ free(occupant_jid);
}
if (! strcmp(word, "leave")) {
jid_str = strtok_r(NULL, " ", &words_save_ptr);
@@ -589,22 +619,58 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) {
return;
}
word = strtok_r(NULL, " ", &words_save_ptr);
+ if (word == NULL) {
+ return;
+ }
if (! strcmp(word, "as")) {
word = strtok_r(NULL, " ", &words_save_ptr);
}
if (word == NULL) {
return;
}
- char *full_jid = malloc(strlen(jid.bare) + strlen(word) + 2);
- snprintf(full_jid, strlen(jid_str) + strlen(word) + 2, "%s/%s",
+ char *occupant_jid = malloc(strlen(jid.bare) + strlen(word) + 2);
+ snprintf(occupant_jid, strlen(jid_str) + strlen(word) + 2, "%s/%s",
jid.bare, word);
- presence = rexmpp_xml_new_elem("presence", "jabber:client");
- rexmpp_xml_add_id(presence);
- rexmpp_xml_add_attr(presence, "from", s->assigned_jid.full);
- rexmpp_xml_add_attr(presence, "to", full_jid);
- rexmpp_xml_add_attr(presence, "type", "unavailable");
- rexmpp_send(s, presence);
- free(full_jid);
+ rexmpp_muc_leave(s, occupant_jid);
+ free(occupant_jid);
+ }
+ }
+
+ if (! strcmp(word, "bookmark")) {
+ word = strtok_r(NULL, " ", &words_save_ptr);
+ if (! strcmp(word, "add")) {
+ char *muc_jid = strtok_r(NULL, " ", &words_save_ptr);
+ if (muc_jid == NULL) {
+ return;
+ }
+ char *autojoin = strtok_r(NULL, " ", &words_save_ptr);
+ char *nick_str = strtok_r(NULL, " ", &words_save_ptr);
+ char *password = strtok_r(NULL, " ", &words_save_ptr);
+
+ rexmpp_xml_t *conference =
+ rexmpp_xml_new_elem("conference", "urn:xmpp:bookmarks:1");
+ if (autojoin != NULL) {
+ rexmpp_xml_add_attr(conference, "autojoin", autojoin);
+ }
+ if (password != NULL) {
+ rexmpp_xml_add_attr(conference, "password", password);
+ }
+ if (nick_str != NULL) {
+ rexmpp_xml_t *nick =
+ rexmpp_xml_new_elem("nick", "urn:xmpp:bookmarks:1");
+ rexmpp_xml_add_text(nick, nick_str);
+ rexmpp_xml_add_child(conference, nick);
+ }
+ rexmpp_pubsub_item_publish(s, NULL, "urn:xmpp:bookmarks:1", muc_jid,
+ conference, rexmpp_bookmark_added, NULL);
+ }
+ if (! strcmp(word, "remove")) {
+ char *muc_jid = strtok_r(NULL, " ", &words_save_ptr);
+ if (muc_jid == NULL) {
+ return;
+ }
+ rexmpp_pubsub_item_retract(s, NULL, "urn:xmpp:bookmarks:1", muc_jid,
+ rexmpp_bookmark_removed, NULL);
}
}