about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--src/command/command.c17
-rw-r--r--src/command/commands.c25
-rw-r--r--src/command/commands.h1
-rw-r--r--src/event/server_events.c25
-rw-r--r--src/event/server_events.h1
-rw-r--r--src/jid.c1
-rw-r--r--src/xmpp/iq.c119
-rw-r--r--src/xmpp/stanza.c19
-rw-r--r--src/xmpp/stanza.h3
-rw-r--r--src/xmpp/xmpp.h1
-rw-r--r--tests/unittests/xmpp/stub_xmpp.c1
11 files changed, 211 insertions, 2 deletions
diff --git a/src/command/command.c b/src/command/command.c
index 186155b0..603b0f12 100644
--- a/src/command/command.c
+++ b/src/command/command.c
@@ -695,6 +695,23 @@ static struct cmd_t command_defs[] =
             "/disco info myfriend@server.com/laptop")
     },
 
+    { "/lastactivity",
+        cmd_lastactivity, parse_args, 0, 1, NULL,
+        CMD_TAGS(
+            CMD_TAG_PRESENCE)
+        CMD_SYN(
+            "/lastactivity [<jid>]")
+        CMD_DESC(
+            "Send a last activity query to the supplied JID, omitting the JID will send the query to your server.")
+        CMD_ARGS(
+            { "<jid>", "The JID of the entity to which the query will be sent." })
+        CMD_EXAMPLES(
+            "/lastactivity",
+            "/lastactivity alice@securechat.org",
+            "/lastactivity alice@securechat.org/laptop",
+            "/lastactivity someserver.com")
+    },
+
     { "/nick",
         cmd_nick, parse_args_with_freetext, 1, 1, NULL,
         CMD_TAGS(
diff --git a/src/command/commands.c b/src/command/commands.c
index e6d1e277..6b301057 100644
--- a/src/command/commands.c
+++ b/src/command/commands.c
@@ -3262,6 +3262,31 @@ cmd_disco(ProfWin *window, const char * const command, gchar **args)
 }
 
 gboolean
+cmd_lastactivity(ProfWin *window, const char * const command, gchar **args)
+{
+    jabber_conn_status_t conn_status = jabber_get_connection_status();
+
+    if (conn_status != JABBER_CONNECTED) {
+        cons_show("You are not currenlty connected.");
+        return TRUE;
+    }
+
+    if (args[0] == NULL) {
+        Jid *jidp = jid_create(jabber_get_fulljid());
+        GString *jid = g_string_new(jidp->domainpart);
+
+        iq_last_activity_request(jid->str);
+
+        g_string_free(jid, TRUE);
+        jid_destroy(jidp);
+    } else {
+        iq_last_activity_request(args[0]);
+    }
+
+    return TRUE;
+}
+
+gboolean
 cmd_nick(ProfWin *window, const char * const command, gchar **args)
 {
     jabber_conn_status_t conn_status = jabber_get_connection_status();
diff --git a/src/command/commands.h b/src/command/commands.h
index e456e291..b3f83a70 100644
--- a/src/command/commands.h
+++ b/src/command/commands.h
@@ -85,6 +85,7 @@ gboolean cmd_connect(ProfWin *window, const char * const command, gchar **args);
 gboolean cmd_tls(ProfWin *window, const char * const command, gchar **args);
 gboolean cmd_decline(ProfWin *window, const char * const command, gchar **args);
 gboolean cmd_disco(ProfWin *window, const char * const command, gchar **args);
+gboolean cmd_lastactivity(ProfWin *window, const char * const command, gchar **args);
 gboolean cmd_disconnect(ProfWin *window, const char * const command, gchar **args);
 gboolean cmd_dnd(ProfWin *window, const char * const command, gchar **args);
 gboolean cmd_flash(ProfWin *window, const char * const command, gchar **args);
diff --git a/src/event/server_events.c b/src/event/server_events.c
index 4eb4f785..7b24f6b9 100644
--- a/src/event/server_events.c
+++ b/src/event/server_events.c
@@ -730,3 +730,28 @@ sv_ev_certfail(const char * const errormsg, const char * const certname, const c
         return 0;
     }
 }
+
+void
+sv_ev_lastactivity_response(const char * const from, const int seconds, const char * const msg)
+{
+    Jid *jidp = jid_create(from);
+
+    if (!jidp) {
+        return;
+    }
+
+    // full jid or bare jid
+    if (jidp->resourcepart || jidp->localpart) {
+        if (msg) {
+            cons_show("%s last active %d, status: %s", from, seconds, msg);
+        } else {
+            cons_show("%s last active %d", from, seconds);
+        }
+
+    // domain only
+    } else {
+        cons_show("%s uptime %d seconds", from, seconds);
+    }
+
+    jid_destroy(jidp);
+}
diff --git a/src/event/server_events.h b/src/event/server_events.h
index 2aa90754..3770dd94 100644
--- a/src/event/server_events.h
+++ b/src/event/server_events.h
@@ -88,5 +88,6 @@ void sv_ev_roster_update(const char * const barejid, const char * const name,
 void sv_ev_roster_received(void);
 int sv_ev_certfail(const char * const errormsg, const char * const certname, const char * const certfp,
     const char * const notbefore, const char * const notafter);
+void sv_ev_lastactivity_response(const char * const from, const int seconds, const char * const msg);
 
 #endif
diff --git a/src/jid.c b/src/jid.c
index 4eb05e87..8b40d16a 100644
--- a/src/jid.c
+++ b/src/jid.c
@@ -79,7 +79,6 @@ jid_create(const gchar * const str)
     gchar *slashp = g_utf8_strchr(trimmed, -1, '/');
     gchar *domain_start = trimmed;
 
-
     if (atp) {
         result->localpart = g_utf8_substring(trimmed, 0, g_utf8_pointer_to_offset(trimmed, atp));
         domain_start = atp + 1;
diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c
index f1f7d212..29aa0944 100644
--- a/src/xmpp/iq.c
+++ b/src/xmpp/iq.c
@@ -40,7 +40,7 @@
 
 #include <stdlib.h>
 #include <string.h>
-
+#include <stdio.h>
 #include <glib.h>
 
 #ifdef HAVE_LIBMESODE
@@ -76,6 +76,8 @@ static int _version_get_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const
 static int _version_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
 static int _disco_info_get_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
 static int _disco_info_response_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _last_activity_get_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _last_activity_response_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
 static int _room_info_response_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
 static int _disco_items_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
 static int _disco_items_get_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
@@ -108,6 +110,8 @@ iq_add_handlers(void)
     HANDLE(XMPP_NS_DISCO_ITEMS, STANZA_TYPE_GET,    _disco_items_get_handler);
     HANDLE(XMPP_NS_DISCO_ITEMS, STANZA_TYPE_RESULT, _disco_items_result_handler);
 
+    HANDLE("jabber:iq:last",    STANZA_TYPE_GET,    _last_activity_get_handler);
+
     HANDLE(STANZA_NS_VERSION,   STANZA_TYPE_GET,    _version_get_handler);
 
     HANDLE(STANZA_NS_PING,      STANZA_TYPE_GET,    _ping_get_handler);
@@ -190,6 +194,22 @@ iq_disco_info_request(gchar *jid)
 }
 
 void
+iq_last_activity_request(gchar *jid)
+{
+    xmpp_conn_t * const conn = connection_get_conn();
+    xmpp_ctx_t * const ctx = connection_get_ctx();
+    char *id = create_unique_id("lastactivity");
+    xmpp_stanza_t *iq = stanza_create_last_activity_iq(ctx, id, jid);
+
+    xmpp_id_handler_add(conn, _last_activity_response_handler, id, NULL);
+
+    free(id);
+
+    xmpp_send(conn, iq);
+    xmpp_stanza_release(iq);
+}
+
+void
 iq_room_info_request(const char * const room, gboolean display_result)
 {
     xmpp_conn_t * const conn = connection_get_conn();
@@ -209,6 +229,8 @@ iq_room_info_request(const char * const room, gboolean display_result)
     xmpp_stanza_release(iq);
 }
 
+
+
 void
 iq_send_caps_request_for_jid(const char * const to, const char * const id,
     const char * const node, const char * const ver)
@@ -1065,6 +1087,46 @@ _disco_items_get_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     return 1;
 }
 
+static int
+_last_activity_get_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
+    void * const userdata)
+{
+    xmpp_ctx_t *ctx = (xmpp_ctx_t *)userdata;
+    const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
+
+    /*
+<iq from='juliet@capulet.com/balcony'
+    id='last2'
+    to='romeo@montague.net/orchard'
+    type='result'>
+  <query xmlns='jabber:iq:last' seconds='123'/>
+</iq>
+     */
+
+    if (from) {
+        int idls_secs = ui_get_idle_time() / 1000;
+        char str[50];
+        sprintf(str, "%d", idls_secs);
+
+        xmpp_stanza_t *response = xmpp_stanza_new(ctx);
+        xmpp_stanza_set_name(response, STANZA_NAME_IQ);
+        xmpp_stanza_set_id(response, xmpp_stanza_get_id(stanza));
+        xmpp_stanza_set_attribute(response, STANZA_ATTR_TO, from);
+        xmpp_stanza_set_type(response, STANZA_TYPE_RESULT);
+
+        xmpp_stanza_t *query = xmpp_stanza_new(ctx);
+        xmpp_stanza_set_attribute(query, STANZA_ATTR_XMLNS, "jabber:iq:last");
+        xmpp_stanza_set_attribute(query, "seconds", str);
+
+        xmpp_stanza_add_child(response, query);
+        xmpp_send(conn, response);
+
+        xmpp_stanza_release(query);
+        xmpp_stanza_release(response);
+    }
+
+    return 1;
+}
 
 static int
 _disco_info_get_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
@@ -1490,6 +1552,61 @@ _room_info_response_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stan
 }
 
 static int
+_last_activity_response_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
+    void * const userdata)
+{
+    const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
+    if (!from) {
+        cons_show_error("Invalid last activity response received.");
+        log_info("Received last activity response with no from attribute.");
+        return 0;
+    }
+
+    const char *type = xmpp_stanza_get_type(stanza);
+
+    // handle error responses
+    if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) {
+        char *error_message = stanza_get_error_message(stanza);
+        if (from) {
+            cons_show_error("Last activity request failed for %s: %s", from, error_message);
+        } else {
+            cons_show_error("Last activity request failed: %s", error_message);
+        }
+        free(error_message);
+        return 0;
+    }
+
+    xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY);
+
+    if (!query) {
+        cons_show_error("Invalid last activity response received.");
+        log_info("Received last activity response with no query element.");
+        return 0;
+    }
+
+    char *seconds_str = xmpp_stanza_get_attribute(query, "seconds");
+    if (!seconds_str) {
+        cons_show_error("Invalid last activity response received.");
+        log_info("Received last activity response with no seconds attribute.");
+        return 0;
+    }
+
+    int seconds = atoi(seconds_str);
+    if (seconds < 0) {
+        cons_show_error("Invalid last activity response received.");
+        log_info("Received last activity response with negative value.");
+        return 0;
+    }
+
+    char *msg = xmpp_stanza_get_text(query);
+
+    sv_ev_lastactivity_response(from, seconds, msg);
+
+    xmpp_free(connection_get_ctx(), msg);
+    return 0;
+}
+
+static int
 _disco_info_response_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     void * const userdata)
 {
diff --git a/src/xmpp/stanza.c b/src/xmpp/stanza.c
index 68ace83c..7bd648f7 100644
--- a/src/xmpp/stanza.c
+++ b/src/xmpp/stanza.c
@@ -966,6 +966,25 @@ stanza_create_disco_items_iq(xmpp_ctx_t *ctx, const char * const id,
 }
 
 xmpp_stanza_t *
+stanza_create_last_activity_iq(xmpp_ctx_t *ctx, const char * const id, const char * const to)
+{
+    xmpp_stanza_t *iq = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(iq, STANZA_NAME_IQ);
+    xmpp_stanza_set_type(iq, STANZA_TYPE_GET);
+    xmpp_stanza_set_attribute(iq, STANZA_ATTR_TO, to);
+    xmpp_stanza_set_id(iq, id);
+
+    xmpp_stanza_t *query = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(query, STANZA_NAME_QUERY);
+    xmpp_stanza_set_ns(query, "jabber:iq:last");
+
+    xmpp_stanza_add_child(iq, query);
+    xmpp_stanza_release(query);
+
+    return iq;
+}
+
+xmpp_stanza_t *
 stanza_create_room_config_submit_iq(xmpp_ctx_t *ctx, const char * const room, DataForm *form)
 {
     xmpp_stanza_t *iq = xmpp_stanza_new(ctx);
diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h
index dbb09ab3..80a4fd93 100644
--- a/src/xmpp/stanza.h
+++ b/src/xmpp/stanza.h
@@ -227,6 +227,9 @@ xmpp_stanza_t* stanza_create_ping_iq(xmpp_ctx_t *ctx, const char * const target)
 xmpp_stanza_t* stanza_create_disco_info_iq(xmpp_ctx_t *ctx, const char * const id,
     const char * const to, const char * const node);
 
+xmpp_stanza_t* stanza_create_last_activity_iq(xmpp_ctx_t *ctx, const char * const id,
+    const char * const to);
+
 xmpp_stanza_t* stanza_create_invite(xmpp_ctx_t *ctx, const char * const room,
     const char * const contact, const char * const reason, const char * const password);
 xmpp_stanza_t* stanza_create_mediated_invite(xmpp_ctx_t *ctx, const char * const room,
diff --git a/src/xmpp/xmpp.h b/src/xmpp/xmpp.h
index 5c5fa3ef..2a8cbb59 100644
--- a/src/xmpp/xmpp.h
+++ b/src/xmpp/xmpp.h
@@ -186,6 +186,7 @@ void iq_send_software_version(const char * const fulljid);
 void iq_room_list_request(gchar *conferencejid);
 void iq_disco_info_request(gchar *jid);
 void iq_disco_items_request(gchar *jid);
+void iq_last_activity_request(gchar *jid);
 void iq_set_autoping(int seconds);
 void iq_confirm_instant_room(const char * const room_jid);
 void iq_destroy_room(const char * const room_jid);
diff --git a/tests/unittests/xmpp/stub_xmpp.c b/tests/unittests/xmpp/stub_xmpp.c
index 79f2595a..34c52a63 100644
--- a/tests/unittests/xmpp/stub_xmpp.c
+++ b/tests/unittests/xmpp/stub_xmpp.c
@@ -165,6 +165,7 @@ void iq_room_kick_occupant(const char * const room, const char * const nick, con
 void iq_room_role_set(const char * const room, const char * const nick, char *role,
     const char * const reason) {}
 void iq_room_role_list(const char * const room, char *role) {}
+void iq_last_activity_request(gchar *jid) {}
 
 // caps functions
 Capabilities* caps_lookup(const char * const jid)