message.c

  1/*!
  2 * @file message.c
  3 *
  4 * vim: expandtab:ts=2:sts=2:sw=2
  5 *
  6 * @authors
  7 * Copyright (C) 2020 Anoxinon e.V. 
  8 *
  9 * @copyright 
 10 * This file is part of xmppc.
 11 *
 12 * xmppc is free software: you can redistribute it and/or modify
 13 * it under the terms of the GNU General Public License as published by
 14 * the Free Software Foundation, either version 3 of the License, or
 15 * (at your option) any later version.
 16 *
 17 * xmppc is distributed in the hope that it will be useful,
 18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 20 * GNU General Public License for more details.
 21 *
 22 * You should have received a copy of the GNU General Public License
 23 * along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
 24 *
 25 * German
 26 *
 27 * Diese Datei ist Teil von xmppc.
 28 *
 29 * xmppc ist Freie Software: Sie können es unter den Bedingungen
 30 * der GNU General Public License, wie von der Free Software Foundation,
 31 * Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren
 32 * veröffentlichten Version, weiter verteilen und/oder modifizieren.
 33 *
 34 * xmppc wird in der Hoffnung, dass es nützlich sein wird, aber
 35 * OHNE JEDE GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite
 36 * Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK.
 37 * Siehe die GNU General Public License für weitere Details.
 38 *
 39 * Sie sollten eine Kopie der GNU General Public License zusammen mit diesem
 40 * Programm erhalten haben. Wenn nicht, siehe <https://www.gnu.org/licenses/>.
 41 */
 42
 43#include "message.h"
 44#include "glib.h"
 45#include "stdbool.h"
 46#include "stdlib.h"
 47#include "string.h"
 48
 49#define MAM_NS "urn:xmpp:mam:2"
 50#define FORWARD_NS "urn:xmpp:forward:0"
 51#define RSM_NS "http://jabber.org/protocol/rsm"
 52#define SID_NS "urn:xmpp:sid:0"
 53
 54#define MESSAGE_ARCHIVE_LOOKUP_TIMEOUT_SECONDS 15
 55#define MESSAGE_ARCHIVE_LOOKUP_POLL_MS 1000
 56
 57typedef struct {
 58  xmppc_t *xmppc;
 59  char *to;
 60  char *message_id;
 61  char *start;
 62  char *queryid;
 63  char *archive_id;
 64  gint64 deadline_us;
 65  bool done;
 66} message_archive_lookup_t;
 67
 68static void _message_send_text(xmppc_t *xmppc, char* to, char* message);
 69static void _message_lookup_archive_id(xmppc_t *xmppc, const char *to, const char *message_id);
 70static void _message_send_archive_query(message_archive_lookup_t *lookup);
 71static int _message_archive_result_handler(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata);
 72static int _message_archive_query_finished(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata);
 73static int _message_archive_query_poll(xmpp_conn_t *const conn, void *const userdata);
 74static int _message_archive_lookup_timeout(xmpp_conn_t *const conn, void *const userdata);
 75static bool _message_archive_result_matches(message_archive_lookup_t *lookup, xmpp_stanza_t *result);
 76static bool _message_has_origin_id(xmpp_stanza_t *message, const char *id);
 77static void _message_add_text_child(xmpp_ctx_t *ctx, xmpp_stanza_t *parent, const char *name, const char *text);
 78static void _message_add_field(xmpp_ctx_t *ctx, xmpp_stanza_t *x, const char *var, const char *value, const char *type);
 79static char *_message_lookup_start_time(void);
 80static void _message_finish_sent(xmpp_conn_t *conn, message_archive_lookup_t *lookup, bool delete_id_handler);
 81static void _message_archive_lookup_free(message_archive_lookup_t *lookup);
 82static void _message_print_sent(message_archive_lookup_t *lookup);
 83static void _message_print_escaped_attr(const char *text);
 84
 85void message_execute_command(xmppc_t *xmppc, int argc, char *argv[]) {
 86  if( argc == 3 && ( strcmp("chat", argv[0] ) == 0)) {
 87    _message_send_text(xmppc, argv[1],argv[2]);
 88  } else {
 89    logError(xmppc, "Befehl unbekannt");
 90    xmpp_disconnect(xmppc->conn);
 91  }
 92}
 93
 94void _message_send_text(xmppc_t *xmppc, char* to, char* text) {
 95  xmpp_conn_t *conn = xmppc->conn;
 96  xmpp_ctx_t *ctx = xmpp_conn_get_context(conn);
 97  xmpp_stanza_t *message;
 98  char* id = xmpp_uuid_gen(xmppc->ctx);
 99  message = xmpp_message_new(ctx, "chat", to, id);
100  int res = xmpp_message_set_body(message, text);
101  if(res == 0) {
102    xmpp_stanza_t *origin = xmpp_stanza_new(ctx);
103    xmpp_stanza_set_name(origin, "origin-id");
104    xmpp_stanza_set_ns(origin, SID_NS);
105    xmpp_stanza_set_attribute(origin, "id", id);
106    xmpp_stanza_add_child(message, origin);
107    xmpp_stanza_release(origin);
108
109    xmpp_send(conn, message);
110    _message_lookup_archive_id(xmppc, to, id);
111  } else {
112    xmpp_disconnect(conn);
113  }
114  xmpp_stanza_release(message);
115  free(id);
116}
117
118static void _message_lookup_archive_id(xmppc_t *xmppc, const char *to, const char *message_id) {
119  message_archive_lookup_t *lookup = g_new0(message_archive_lookup_t, 1);
120  lookup->xmppc = xmppc;
121  lookup->to = g_strdup(to);
122  lookup->message_id = g_strdup(message_id);
123  lookup->start = _message_lookup_start_time();
124  lookup->deadline_us = g_get_monotonic_time() + ((gint64)MESSAGE_ARCHIVE_LOOKUP_TIMEOUT_SECONDS * G_USEC_PER_SEC);
125
126  xmpp_handler_add(xmppc->conn, _message_archive_result_handler, NULL, "message", NULL, lookup);
127  xmpp_timed_handler_add(xmppc->conn, _message_archive_lookup_timeout, MESSAGE_ARCHIVE_LOOKUP_TIMEOUT_SECONDS * 1000, lookup);
128  _message_send_archive_query(lookup);
129}
130
131static void _message_send_archive_query(message_archive_lookup_t *lookup) {
132  xmppc_t *xmppc = lookup->xmppc;
133  xmpp_ctx_t *ctx = xmppc->ctx;
134  char* id = xmpp_uuid_gen(ctx);
135  g_free(lookup->queryid);
136  lookup->queryid = g_strdup(id);
137
138  xmpp_stanza_t *iq = xmpp_iq_new(ctx, "set", id);
139  xmpp_stanza_t *query = xmpp_stanza_new(ctx);
140  xmpp_stanza_set_name(query, "query");
141  xmpp_stanza_set_ns(query, MAM_NS);
142  xmpp_stanza_set_attribute(query, "queryid", id);
143
144  xmpp_stanza_t* x = xmpp_stanza_new(ctx);
145  xmpp_stanza_set_name(x,"x");
146  xmpp_stanza_set_ns(x, "jabber:x:data");
147  xmpp_stanza_set_type(x,"submit");
148  xmpp_stanza_add_child(query,x);
149
150  _message_add_field(ctx, x, "FORM_TYPE", MAM_NS, "hidden");
151  _message_add_field(ctx, x, "with", lookup->to, NULL);
152  _message_add_field(ctx, x, "start", lookup->start, NULL);
153
154  xmpp_stanza_t *set = xmpp_stanza_new(ctx);
155  xmpp_stanza_set_name(set, "set");
156  xmpp_stanza_set_ns(set, RSM_NS);
157  _message_add_text_child(ctx, set, "max", "50");
158  _message_add_text_child(ctx, set, "before", "");
159  xmpp_stanza_add_child(query, set);
160
161  xmpp_stanza_add_child(iq, query);
162  xmpp_id_handler_add(xmppc->conn, _message_archive_query_finished, id, lookup);
163  xmpp_send(xmppc->conn, iq);
164
165  xmpp_stanza_release(iq);
166  xmpp_stanza_release(query);
167  xmpp_stanza_release(x);
168  xmpp_stanza_release(set);
169  free(id);
170}
171
172static int _message_archive_result_handler(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata) {
173  message_archive_lookup_t *lookup = (message_archive_lookup_t *)userdata;
174  xmpp_stanza_t* result = xmpp_stanza_get_child_by_name(stanza,"result");
175
176  if (!lookup->done && result && _message_archive_result_matches(lookup, result)) {
177    const char *archive_id = xmpp_stanza_get_attribute(result, "id");
178    if (archive_id) {
179      g_free(lookup->archive_id);
180      lookup->archive_id = g_strdup(archive_id);
181    }
182  }
183
184  return 1;
185}
186
187static int _message_archive_query_finished(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata) {
188  message_archive_lookup_t *lookup = (message_archive_lookup_t *)userdata;
189  const char *type = xmpp_stanza_get_type(stanza);
190
191  if (lookup->done) {
192    return 0;
193  }
194
195  if (lookup->archive_id) {
196    _message_finish_sent(conn, lookup, false);
197    return 0;
198  }
199
200  if (type && strcmp(type, "error") == 0) {
201    logError(lookup->xmppc, "MAM archive lookup failed\n");
202    _message_finish_sent(conn, lookup, false);
203    return 0;
204  }
205
206  if (g_get_monotonic_time() < lookup->deadline_us) {
207    xmpp_timed_handler_add(conn, _message_archive_query_poll, MESSAGE_ARCHIVE_LOOKUP_POLL_MS, lookup);
208  } else {
209    logError(lookup->xmppc, "MAM archive lookup timed out\n");
210    _message_finish_sent(conn, lookup, false);
211  }
212
213  return 0;
214}
215
216static int _message_archive_query_poll(xmpp_conn_t *const conn, void *const userdata) {
217  message_archive_lookup_t *lookup = (message_archive_lookup_t *)userdata;
218
219  if (lookup->done) {
220    return 0;
221  }
222
223  if (g_get_monotonic_time() >= lookup->deadline_us) {
224    logError(lookup->xmppc, "MAM archive lookup timed out\n");
225    _message_finish_sent(conn, lookup, false);
226    return 0;
227  }
228
229  _message_send_archive_query(lookup);
230  return 0;
231}
232
233static int _message_archive_lookup_timeout(xmpp_conn_t *const conn, void *const userdata) {
234  message_archive_lookup_t *lookup = (message_archive_lookup_t *)userdata;
235
236  if (!lookup->done) {
237    logError(lookup->xmppc, "MAM archive lookup timed out\n");
238    _message_finish_sent(conn, lookup, true);
239  }
240
241  return 0;
242}
243
244static bool _message_archive_result_matches(message_archive_lookup_t *lookup, xmpp_stanza_t *result) {
245  const char *ns = xmpp_stanza_get_ns(result);
246  if (!ns || strcmp(MAM_NS, ns) != 0) {
247    return false;
248  }
249
250  const char *queryid = xmpp_stanza_get_attribute(result, "queryid");
251  if (queryid && lookup->queryid && strcmp(queryid, lookup->queryid) != 0) {
252    return false;
253  }
254
255  xmpp_stanza_t* forwarded = xmpp_stanza_get_child_by_ns(result, FORWARD_NS);
256  xmpp_stanza_t* message = forwarded ? xmpp_stanza_get_child_by_name(forwarded, "message") : NULL;
257  if (!message) {
258    return false;
259  }
260
261  const char *message_id = xmpp_stanza_get_id(message);
262  if (message_id && strcmp(message_id, lookup->message_id) == 0) {
263    return true;
264  }
265
266  return _message_has_origin_id(message, lookup->message_id);
267}
268
269static bool _message_has_origin_id(xmpp_stanza_t *message, const char *id) {
270  for (xmpp_stanza_t *child = xmpp_stanza_get_children(message); child; child = xmpp_stanza_get_next(child)) {
271    const char *name = xmpp_stanza_get_name(child);
272    const char *ns = xmpp_stanza_get_ns(child);
273    const char *origin_id = xmpp_stanza_get_attribute(child, "id");
274
275    if (name && ns && origin_id && strcmp(name, "origin-id") == 0 && strcmp(ns, SID_NS) == 0 && strcmp(origin_id, id) == 0) {
276      return true;
277    }
278  }
279
280  return false;
281}
282
283static void _message_add_text_child(xmpp_ctx_t *ctx, xmpp_stanza_t *parent, const char *name, const char *text) {
284  xmpp_stanza_t *child = xmpp_stanza_new(ctx);
285  xmpp_stanza_set_name(child, name);
286
287  xmpp_stanza_t *body = xmpp_stanza_new(ctx);
288  xmpp_stanza_set_text(body, text ? text : "");
289  xmpp_stanza_add_child(child, body);
290  xmpp_stanza_release(body);
291
292  xmpp_stanza_add_child(parent, child);
293  xmpp_stanza_release(child);
294}
295
296static void _message_add_field(xmpp_ctx_t *ctx, xmpp_stanza_t *x, const char *var, const char *value, const char *type) {
297  xmpp_stanza_t* field = xmpp_stanza_new(ctx);
298  xmpp_stanza_set_name(field,"field");
299  xmpp_stanza_set_attribute(field, "var", var);
300  if (type) {
301    xmpp_stanza_set_type(field, type);
302  }
303
304  _message_add_text_child(ctx, field, "value", value);
305  xmpp_stanza_add_child(x, field);
306  xmpp_stanza_release(field);
307}
308
309static char *_message_lookup_start_time(void) {
310  GDateTime *now = g_date_time_new_now_utc();
311  GDateTime *then = g_date_time_add_seconds(now, -30);
312  char *formatted = g_date_time_format(then, "%Y-%m-%dT%H:%M:%SZ");
313  g_date_time_unref(then);
314  g_date_time_unref(now);
315  return formatted;
316}
317
318static void _message_finish_sent(xmpp_conn_t *conn, message_archive_lookup_t *lookup, bool delete_id_handler) {
319  if (lookup->done) {
320    return;
321  }
322
323  lookup->done = true;
324  _message_print_sent(lookup);
325  xmpp_handler_delete(conn, _message_archive_result_handler);
326  xmpp_timed_handler_delete(conn, _message_archive_query_poll);
327  xmpp_timed_handler_delete(conn, _message_archive_lookup_timeout);
328  if (delete_id_handler && lookup->queryid) {
329    xmpp_id_handler_delete(conn, _message_archive_query_finished, lookup->queryid);
330  }
331  _message_archive_lookup_free(lookup);
332  xmpp_disconnect(conn);
333}
334
335static void _message_archive_lookup_free(message_archive_lookup_t *lookup) {
336  g_free(lookup->to);
337  g_free(lookup->message_id);
338  g_free(lookup->start);
339  g_free(lookup->queryid);
340  g_free(lookup->archive_id);
341  g_free(lookup);
342}
343
344static void _message_print_sent(message_archive_lookup_t *lookup) {
345  printf("<sent archive-id=\"");
346  _message_print_escaped_attr(lookup->archive_id ? lookup->archive_id : "");
347  printf("\" message-id=\"");
348  _message_print_escaped_attr(lookup->message_id);
349  printf("\" to=\"");
350  _message_print_escaped_attr(lookup->to);
351  printf("\"/>\n");
352  fflush(stdout);
353}
354
355static void _message_print_escaped_attr(const char *text) {
356  const unsigned char *p = (const unsigned char *)(text ? text : "");
357
358  while (*p) {
359    switch (*p) {
360      case '&':
361        printf("&amp;");
362        break;
363      case '<':
364        printf("&lt;");
365        break;
366      case '>':
367        printf("&gt;");
368        break;
369      case '"':
370        printf("&quot;");
371        break;
372      case '\n':
373        printf("&#10;");
374        break;
375      case '\r':
376        printf("&#13;");
377        break;
378      case '\t':
379        printf("&#9;");
380        break;
381      default:
382        if (*p < 0x20) {
383          printf("&#x%02X;", *p);
384        } else {
385          putchar(*p);
386        }
387        break;
388    }
389    p++;
390  }
391}