]> Pileus Git - ~andy/lamechat/blob - xmpp.c
Usability fixes.
[~andy/lamechat] / xmpp.c
1 /*
2  * Copyright (C) 2017 Andy Spencer <andy753421@gmail.com>
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 #define _GNU_SOURCE
19
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <stdarg.h>
23 #include <string.h>
24 #include <errno.h>
25 #include <expat.h>
26 #include <time.h>
27
28 #include "util.h"
29 #include "conf.h"
30 #include "chat.h"
31 #include "net.h"
32
33 /* Constants */
34 #define AUTH_LEN 512
35 #define JID_LEN  256
36
37 /* XMPP types */
38 typedef enum {
39         XMPP_CONNECT,
40         XMPP_ENCRYPT,
41         XMPP_RESTART,
42         XMPP_SEND_STREAM,
43         XMPP_RECV_FEATURES,
44         XMPP_SEND_STARTTLS,
45         XMPP_RECV_PROCEED,
46         XMPP_SEND_AUTH,
47         XMPP_RECV_SUCCESS,
48         XMPP_SEND_BIND,
49         XMPP_RECV_JID,
50         XMPP_SEND_PRESENCE,
51         XMPP_READY,
52         XMPP_IN_IQ,
53         XMPP_IN_MESSAGE,
54         XMPP_IN_PRESENCE,
55 } xmpp_state_t;
56
57 typedef struct {
58         channel_t       channel;
59         char            dest[JID_LEN];
60
61         const char     *room;
62         int             join;
63 } xmpp_channel_t;
64
65 typedef struct {
66         server_t        server;
67         xmpp_channel_t  system;
68
69         int             connect;
70         const char     *host;
71         int             port;
72         const char     *muc;
73         const char     *nick;
74         const char     *jid;
75         const char     *user;
76         const char     *pass;
77         int             quiet;
78
79         net_t           net;
80         XML_Parser      expat;
81         xmpp_state_t    state;
82         buf_t           buf;
83         int             indent;
84
85         char           *body;
86         stamp_t         stamp;
87
88         int             in_error;
89
90         char            msg_jid[JID_LEN];
91         char            msg_usr[JID_LEN];
92         char            msg_srv[JID_LEN];
93         char            msg_res[JID_LEN];
94         char           *msg_from;
95         xmpp_channel_t *msg_chan;
96 } xmpp_server_t;
97
98 /* Helper functions */
99 static void srv_notice(xmpp_server_t *srv, const char *fmt, ...)
100 {
101         static char buf[1024];
102
103         va_list ap;
104         va_start(ap, fmt);
105         vsnprintf(buf, sizeof(buf), fmt, ap);
106         va_end(ap);
107
108         chat_recv(&srv->system.channel, NULL, buf);
109 }
110
111 static void chan_notice(xmpp_channel_t *chan, const char *fmt, ...)
112 {
113         static char buf[1024];
114
115         va_list ap;
116         va_start(ap, fmt);
117         vsnprintf(buf, sizeof(buf), fmt, ap);
118         va_end(ap);
119
120         chat_recv(&chan->channel, NULL, buf);
121 }
122
123 static void split_jid(const char *jid, char *usr, char *srv, char *res)
124 {
125         char *ptr = usr;
126         int   pos = 0;
127
128         if (usr) usr[0] = '\0';
129         if (srv) srv[0] = '\0';
130         if (res) res[0] = '\0';
131
132         for (int i = 0; jid && jid[i]; i++) {
133                 switch (jid[i]) {
134                         case '@':
135                                 ptr = srv;
136                                 pos = 0;
137                                 continue;
138                         case '/':
139                                 ptr = res;
140                                 pos = 0;
141                                 continue;
142                 }
143                 if (ptr && (pos+1) < JID_LEN) {
144                         ptr[pos++] = jid[i];
145                         ptr[pos] = '\0';
146                 }
147         }
148
149         //debug("JID: '%s' usr=[%s] srv=[%s] res=[%s]",
150         //              jid, usr, srv, res);
151 }
152
153 static xmpp_channel_t *find_dest(xmpp_server_t *srv,
154                 const char *jid, int is_muc)
155 {
156         static char jid_usr[JID_LEN];
157         static char jid_srv[JID_LEN];
158         static char dest[JID_LEN];
159         xmpp_channel_t *chan;
160
161         split_jid(jid, jid_usr, jid_srv, NULL);
162         snprintf(dest, JID_LEN, "%s@%s", jid_usr,
163                  jid_srv[0] ? jid_srv  :
164                  is_muc     ? srv->muc : srv->host);
165
166         /* Server channels */
167         if (match(jid, srv->host))
168                 return &srv->system;
169
170         /* Find existing channels */
171         for (channel_t *cur = channels; cur; cur = cur->next) {
172                 if (cur->server != &srv->server)
173                         continue;
174                 chan = (xmpp_channel_t *)cur;
175                 if (match(chan->dest, dest))
176                         return chan;
177         }
178
179         /* Create a new channel */
180         chan = (xmpp_channel_t *)add_channel(jid_usr, &srv->server);
181         strncpy(chan->dest, dest, JID_LEN);
182         return chan;
183 }
184
185 static const char *find_attr(const char **attrs, const char *name)
186 {
187         for (int i = 0; attrs[i] && attrs[i+1]; i += 2)
188                 if (match(attrs[i+0], name))
189                         return attrs[i+1];
190         return NULL;
191 }
192
193 /* Callback functions */
194 static void xmpp_run(xmpp_server_t *srv,
195                 const char *start, const char **attrs,
196                 const char *end, const char *data);
197
198 static void on_start(void *_srv, const char *tag, const char **attrs)
199 {
200         xmpp_server_t *srv = _srv;
201         xmpp_run(srv, tag, attrs, NULL, reset(&srv->buf));
202 }
203
204 static void on_data(void *_srv, const char *data, int len)
205 {
206         xmpp_server_t *srv = _srv;
207         append(&srv->buf, data, len);
208 }
209
210 static void on_end(void *_srv, const char *tag)
211 {
212         xmpp_server_t *srv = _srv;
213         xmpp_run(srv, NULL, NULL, tag, reset(&srv->buf));
214 }
215
216 static void on_send(void *_srv)
217 {
218         xmpp_server_t *srv = _srv;
219         xmpp_run(srv, NULL, NULL, NULL, NULL);
220 }
221
222 static void on_recv(void *_srv, char *buf, int len)
223 {
224         xmpp_server_t *srv = _srv;
225         if (len > 0)
226                 XML_Parse(srv->expat, buf, len, 0);
227         xmpp_run(srv, NULL, NULL, NULL, NULL);
228 }
229
230 static void on_err(void *_srv, int errno)
231 {
232         xmpp_server_t *srv = _srv;
233         xmpp_run(srv, NULL, NULL, NULL, NULL);
234 }
235
236 /* XMPP State machine */
237 static void xmpp_run(xmpp_server_t *srv,
238                 const char *start, const char **attrs,
239                 const char *end, const char *data)
240 {
241         xmpp_channel_t *chan = NULL;
242
243         /* Debug print */
244         if (data)
245                 debug("%*s \"%s\"", srv->indent*4, "", data);
246         if (start) {
247                 debug("%*s<%s>", srv->indent*4, "", start);
248                 for (int i = 0; attrs[i] && attrs[i+1]; i += 2) {
249                         debug("%*s%s=\"%s\"%s",
250                                 srv->indent*4+8, "",
251                                 attrs[i+0], attrs[i+1],
252                                 attrs[i+2] ? "" : ">");
253                 }
254         }
255         if (start)
256                 srv->indent++;
257         if (end)
258                 srv->indent--;
259
260         /* Connection Handling */
261         if (srv->state == XMPP_CONNECT && !start && !end) {
262                 srv->net.send = on_send;
263                 srv->net.recv = on_recv;
264                 srv->net.err  = on_err;
265                 srv->net.data = srv;
266                 net_open(&srv->net, srv->host, srv->port);
267
268                 if (!(srv->expat = XML_ParserCreate(NULL)))
269                         error("Error creating XML parser");
270                 XML_SetUserData(srv->expat, srv);
271                 XML_SetStartElementHandler(srv->expat, on_start);
272                 XML_SetEndElementHandler(srv->expat, on_end);
273                 XML_SetCharacterDataHandler(srv->expat, on_data);
274
275                 debug("xmpp: connect -> stream");
276                 srv->state = XMPP_SEND_STREAM;
277         }
278         if (srv->state == XMPP_ENCRYPT && !start && !end) {
279                 net_encrypt(&srv->net);
280
281                 if (!(XML_ParserReset(srv->expat, NULL)))
282                         error("Error resetting XML parser");
283                 XML_SetUserData(srv->expat, srv);
284                 XML_SetStartElementHandler(srv->expat, on_start);
285                 XML_SetEndElementHandler(srv->expat, on_end);
286                 XML_SetCharacterDataHandler(srv->expat, on_data);
287
288                 debug("xmpp: encrypt -> stream");
289                 srv->state = XMPP_SEND_STREAM;
290         }
291         if (srv->state == XMPP_RESTART && !start && !end) {
292                 if (!(XML_ParserReset(srv->expat, NULL)))
293                         error("Error resetting XML parser");
294                 XML_SetUserData(srv->expat, srv);
295                 XML_SetStartElementHandler(srv->expat, on_start);
296                 XML_SetEndElementHandler(srv->expat, on_end);
297                 XML_SetCharacterDataHandler(srv->expat, on_data);
298
299                 debug("xmpp: restart -> stream");
300                 srv->state = XMPP_SEND_STREAM;
301         }
302
303         /* Stream Start */
304         if (srv->state == XMPP_SEND_STREAM) {
305                 if (net_print(&srv->net,
306                     "<?xml version='1.0'?>"
307                     "<stream:stream"
308                     " from='%s'"
309                     " to='%s'"
310                     " version='1.0'"
311                     " xml:lang='en'"
312                     " xmlns='jabber:client'"
313                     " xmlns:stream='http://etherx.jabber.org/streams'>",
314                     srv->jid, srv->host)) {
315                         debug("xmpp: stream -> features");
316                         srv->state = XMPP_RECV_FEATURES;
317                 }
318         }
319         if (srv->state == XMPP_RECV_FEATURES) {
320                 if (match(start, "starttls")) {
321                         debug("xmpp: features -> starttls");
322                         srv->state = XMPP_SEND_STARTTLS;
323                 }
324                 if (match(start, "mechanisms")) {
325                         debug("xmpp: features -> auth");
326                         srv->state = XMPP_SEND_AUTH;
327                 }
328                 if (match(start, "bind")) {
329                         debug("xmpp: features -> bind");
330                         srv->state = XMPP_SEND_BIND;
331                 }
332         }
333
334         /* Start TLS */
335         if (srv->state == XMPP_SEND_STARTTLS) {
336                 if (net_print(&srv->net,
337                     "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>")) {
338                         debug("xmpp: startls -> proceed");
339                         srv->state = XMPP_RECV_PROCEED;
340                 }
341         }
342         if (srv->state == XMPP_RECV_PROCEED) {
343                 if (match(start, "proceed")) {
344                         debug("xmpp: proceed -> encrypt");
345                         srv->state = XMPP_ENCRYPT;
346                 }
347         }
348
349         /* Authentication */
350         if (srv->state == XMPP_SEND_AUTH) {
351                 static char plain[AUTH_LEN];
352                 static char coded[AUTH_LEN];
353                 int len;
354                 len = snprintf(plain, AUTH_LEN, "%s%c%s%c%s",
355                                 srv->user, '\0', srv->user, '\0', srv->pass);
356                 len = base64(plain, len, coded, AUTH_LEN);
357                 if (net_print(&srv->net,
358                     "<auth"
359                     " xmlns='urn:ietf:params:xml:ns:xmpp-sasl'"
360                     " mechanism='PLAIN'>%.*s</auth>",
361                         len, coded)) {
362                         debug("xmpp: auth -> success");
363                         srv->state = XMPP_RECV_SUCCESS;
364                 }
365         }
366         if (srv->state == XMPP_RECV_SUCCESS) {
367                 if (match(start, "success")) {
368                         debug("xmpp: success -> restart");
369                         srv->state = XMPP_RESTART;
370                 }
371         }
372
373         /* Binding */
374         if (srv->state == XMPP_SEND_BIND) {
375                 const char *resource = srv->jid;
376                 while (*resource && *resource != '/')
377                         resource++;
378                 while (*resource && *resource == '/')
379                         resource++;
380                 if (net_print(&srv->net,
381                     "<iq id='bind' type='set'>"
382                     "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>"
383                     "<resource>%s</resource>"
384                     "</bind>"
385                     "</iq>",
386                     resource)) {
387                         debug("xmpp: bind -> jid");
388                         srv->state = XMPP_RECV_JID;
389                 }
390         }
391         if (srv->state == XMPP_RECV_JID) {
392                 if (match(start, "jid")) {
393                         debug("xmpp: jid -> presence");
394                         srv->state = XMPP_SEND_PRESENCE;
395                 }
396         }
397         if (srv->state == XMPP_SEND_PRESENCE) {
398                 for (channel_t *cur = channels; cur; cur = cur->next) {
399                         xmpp_channel_t *chan = (xmpp_channel_t*)cur;
400                         if (cur->server != &srv->server || !chan->join)
401                                 continue;
402                         net_print(&srv->net,
403                                 "<presence id='join' from='%s' to='%s/%s'>"
404                                 "<x xmlns='http://jabber.org/protocol/muc'/>"
405                                 "</presence>",
406                                 srv->jid, chan->dest, srv->nick);
407                 }
408                 srv->state = XMPP_READY;
409         }
410
411         /* Start message */
412         if (srv->state == XMPP_READY) {
413                 srv->state = match(start, "iq")       ? XMPP_IN_IQ       :
414                              match(start, "message")  ? XMPP_IN_MESSAGE  :
415                              match(start, "presence") ? XMPP_IN_PRESENCE :
416                                                         XMPP_READY;
417                 if (srv->state != XMPP_READY) {
418                         strncpy(srv->msg_jid, find_attr(attrs, "from"), JID_LEN);
419                         split_jid(srv->msg_jid, srv->msg_usr,
420                                   srv->msg_srv, srv->msg_res);
421
422                         if (match(srv->msg_srv, srv->muc)) {
423                                 srv->msg_from = srv->msg_res[0] ? srv->msg_res : NULL;
424                                 srv->msg_chan = find_dest(srv, srv->msg_jid, 1);
425                         } else {
426                                 srv->msg_from = srv->msg_usr[0] ? srv->msg_usr : NULL;
427                                 srv->msg_chan = find_dest(srv, srv->msg_jid, 0);
428                         }
429
430                         debug("xmpp: %s -- jid=[%s] from=[%s] chan=[%s]",
431                                 start,
432                                 srv->msg_jid, srv->msg_from,
433                                 srv->msg_chan->channel.name);
434                 }
435         }
436         if (srv->state == XMPP_IN_IQ ||
437             srv->state == XMPP_IN_MESSAGE ||
438             srv->state == XMPP_IN_PRESENCE) {
439                 if (srv->msg_chan && srv->msg_chan != &srv->system)
440                         chan = srv->msg_chan;
441         }
442
443         /* Info/Queries */
444         if (srv->state == XMPP_IN_IQ) {
445                 if (match(start, "item") && chan) {
446                         static char res[JID_LEN];
447                         split_jid(find_attr(attrs, "jid"),
448                                   NULL, NULL, res);
449                         chan_notice(chan, "user: %s", res);
450                 }
451                 if (match(start, "item") && !chan) {
452                         srv_notice(srv, "item: [%s] %s",
453                                         find_attr(attrs, "jid"),
454                                         find_attr(attrs, "name"));
455                 }
456                 if (match(start, "identity")) {
457                         srv_notice(srv, "identity: %s",
458                                         find_attr(attrs, "name"));
459                 }
460                 if (match(start, "feature")) {
461                         srv_notice(srv, "feature: %s",
462                                         find_attr(attrs, "var"));
463                 }
464                 if (match(start, "field")) {
465                         debug("xmpp: %s -- type=[%s] label=[%s]", end,
466                                 find_attr(attrs, "type"),
467                                 find_attr(attrs, "label"));
468                         if (!find_attr(attrs, "label"))
469                                 return;
470                         chan_notice(chan, "%-36s -- %s (%s)",
471                                 find_attr(attrs, "var"),
472                                 find_attr(attrs, "label"),
473                                 find_attr(attrs, "type"));
474                 }
475                 if (match(end, "title")) {
476                         debug("xmpp: title -- jid=[%s]",
477                                 end, srv->msg_jid);
478                         chan_notice(chan, "Title: %s", data);
479                 }
480                 if (match(end, "instructions")) {
481                         debug("xmpp: instructions -- jid=[%s]",
482                                 end, srv->msg_jid);
483                         chan_notice(chan, "%s", data);
484                 }
485         }
486
487         /* Messages */
488         if (srv->state == XMPP_IN_MESSAGE) {
489                 if (match(start, "delay")) {
490                         const char *ts = find_attr(attrs, "stamp");
491                         if (ts) {
492                                 struct tm tm = {};
493                                 strptime(ts, "%Y-%m-%dT%H:%M:%S%z", &tm);
494                                 srv->stamp = mktime(&tm);
495                         }
496                 }
497                 if (match(end, "body")) {
498                         strset(&srv->body, data);
499                 }
500                 if (match(end, "message")) {
501                         debug("xmpp: body (%s) -- chan=[%s] jid=[%s] from=[%s]",
502                                 srv->msg_from == srv->msg_usr ? "user" : "chat" ,
503                                 srv->msg_chan->channel.name,
504                                 srv->msg_jid, srv->msg_from);
505                         if (srv->body) {
506                                 chat_recv(&chan->channel, srv->msg_from, srv->body);
507                                 message_t *msg = &messages[history-1];
508                                 msg->when = srv->stamp ?: msg->when;
509                         }
510                         srv->stamp = 0;
511                         strset(&srv->body, NULL);
512                 }
513         }
514
515         /* Presence */
516         if (srv->state == XMPP_IN_PRESENCE) {
517                 static char alias[JID_LEN];
518                 const char *jid;
519                 if (match(start, "item")) {
520                         if ((jid = find_attr(attrs, "jid")))
521                                 strncpy(alias, jid, JID_LEN);
522                 }
523                 if (match(end, "presence") && chan) {
524                         if (alias[0] && !srv->quiet)
525                                 chan_notice(chan, "%s (%s) entered room.",
526                                                 srv->msg_from, alias);
527                         else if (!srv->quiet)
528                                 chan_notice(chan, "%s entered room.",
529                                                 srv->msg_from);
530                         alias[0] = '\0';
531                 }
532         }
533
534         /* End messages */
535         if (srv->state == XMPP_IN_IQ ||
536             srv->state == XMPP_IN_MESSAGE ||
537             srv->state == XMPP_IN_PRESENCE) {
538                 if (match(end, "iq") ||
539                     match(end, "message") ||
540                     match(end, "presence")) {
541                         srv->state = XMPP_READY;
542
543                         srv->msg_jid[0] = '\0';
544                         srv->msg_usr[0] = '\0';
545                         srv->msg_srv[0] = '\0';
546                         srv->msg_res[0] = '\0';
547                         srv->msg_from   = NULL;
548                         srv->msg_chan   = NULL;
549                 }
550         }
551         /* Error handling */
552         if (match(start, "stream:error"))
553                 srv->in_error = 1;
554         if (match(end, "stream:error"))
555                 srv->in_error = 0;
556         if (srv->in_error) {
557                 if (match(end, "text")) {
558                         debug("xmpp: error: %s", data);
559                         srv_notice(srv, "error: %s", data);
560                 }
561         }
562 }
563
564 /* XMPP functions */
565 void xmpp_init(void)
566 {
567         for (server_t *cur = servers; cur; cur = cur->next) {
568                 if (cur->protocol != XMPP)
569                         continue;
570
571                 xmpp_server_t *srv = (xmpp_server_t*)cur;
572                 srv->system.channel.server = &srv->server;
573                 srv->system.channel.name   = srv->server.name;
574                 srv_notice(srv, "XMPP Server: %s", srv->server.name);
575
576                 if (!srv->port)
577                         srv->port = 5222;
578                 if (!srv->jid)
579                         error("jid is required");
580                 if (srv->connect)
581                         xmpp_run(srv, NULL, NULL, NULL, NULL);
582         }
583         for (channel_t *cur = channels; cur; cur = cur->next) {
584                 if (cur->server->protocol != XMPP)
585                         continue;
586
587                 xmpp_channel_t *chan = (xmpp_channel_t*)cur;
588                 xmpp_server_t *srv = (xmpp_server_t*)cur->server;
589                 if (!chan->room)
590                         chan->room = strcopy(cur->name);
591                 snprintf(chan->dest, JID_LEN, "%s@%s",
592                          chan->room, srv->muc);
593         }
594 }
595
596 server_t *xmpp_server(void)
597 {
598         return new0(xmpp_server_t);
599 }
600
601 channel_t *xmpp_channel(void)
602 {
603         return new0(xmpp_channel_t);
604 }
605
606 void xmpp_config(server_t *server, channel_t *channel,
607                  const char *group, const char *name,
608                  const char *key, const char *value)
609 {
610         xmpp_server_t  *srv  = (xmpp_server_t*)server;
611         xmpp_channel_t *chan = (xmpp_channel_t*)channel;
612
613         if (server) {
614                 if (match(key, "connect"))
615                         srv->connect = get_bool(value);
616                 else if (match(key, "host"))
617                         srv->host = get_string(value);
618                 else if (match(key, "port"))
619                         srv->port = get_number(value);
620                 else if (match(key, "muc"))
621                         srv->muc = get_string(value);
622                 else if (match(key, "nick"))
623                         srv->nick = get_string(value);
624                 else if (match(key, "jid"))
625                         srv->jid = get_string(value);
626                 else if (match(key, "user"))
627                         srv->user = get_string(value);
628                 else if (match(key, "pass"))
629                         srv->pass = get_string(value);
630                 else if (match(key, "quiet"))
631                         srv->quiet = get_bool(value);
632         }
633         if (channel) {
634                 if (match(key, "room"))
635                         chan->room = get_string(value);
636                 else if (match(key, "join"))
637                         chan->join = get_bool(value);
638         }
639 }
640
641 void xmpp_send(channel_t *channel, const char *text)
642 {
643         xmpp_channel_t *chan = (xmpp_channel_t*)channel;
644         xmpp_server_t  *srv  = (xmpp_server_t*)channel->server;
645         const char *arg;
646
647         /* Handle commands */
648         if (text[0] == '/') {
649                 if (prefix(text, "/items", &arg)) {
650                         net_print(&srv->net,
651                                 "<iq id='items' type='get' from='%s' to='%s'>"
652                                 "<query xmlns='http://jabber.org/protocol/disco#items'/>"
653                                 "</iq>",
654                                 srv->jid, arg ?: srv->host);
655                 }
656                 else if (prefix(text, "/info", &arg)) {
657                         net_print(&srv->net,
658                                 "<iq id='info' type='get' from='%s' to='%s'>"
659                                 "<query xmlns='http://jabber.org/protocol/disco#info'/>"
660                                 "</iq>",
661                                 srv->jid, arg ?: srv->host);
662                 }
663                 else if (prefix(text, "/names", &arg)) {
664                         if (arg)
665                                 chan = find_dest(srv, arg, 1);
666                         if (chan == &srv->system) {
667                                 chan_notice(chan, "Cannot get names from server");
668                                 return;
669                         }
670                         net_print(&srv->net,
671                                 "<iq id='list' type='get' from='%s' to='%s'>"
672                                 "<query xmlns='http://jabber.org/protocol/disco#items'/>"
673                                 "</iq>",
674                                 srv->jid, chan->dest);
675                 }
676                 else if (prefix(text, "/join", &arg)) {
677                         if (!arg) {
678                                 chan_notice(chan, "usage: /join <channel>");
679                                 return;
680                         }
681                         net_print(&srv->net,
682                                 "<presence id='join' from='%s' to='%s/%s'>"
683                                 "<x xmlns='http://jabber.org/protocol/muc'/>"
684                                 "</presence>",
685                                 srv->jid, chan->dest, srv->nick);
686                         chan = find_dest(srv, arg, 1);
687                         chan_notice(chan, "Room: %s", arg);
688                 }
689                 else if (prefix(text, "/config", &arg)) {
690                         if (arg) {
691                                 chan_notice(chan, "Unimplemented: /config <arg>");
692                                 return;
693                         }
694                         net_print(&srv->net,
695                                 "<iq id='config' type='get' from='%s' to='%s'>"
696                                 "<query xmlns='http://jabber.org/protocol/muc#owner'/>"
697                                 "</iq>",
698                                 srv->jid, chan->dest);
699                 }
700                 else if (prefix(text, "/query", &arg)) {
701                         if (!arg) {
702                                 chan_notice(chan, "usage: /query <user>");
703                                 return;
704                         }
705                         chan = find_dest(srv, arg, 0);
706                         chan_notice(chan, "User: %s", arg);
707                 }
708                 else {
709                         chan_notice(chan, "Unknown command %s", text);
710                 }
711         } else {
712                 debug("message: [%s]", text);
713                 if (chan == &srv->system) {
714                         chan_notice(chan, "Cannot send to server");
715                 }
716                 else if (!chan->dest) {
717                         chan_notice(chan, "No destination for message");
718                 }
719                 else if (chan->room) {
720                         net_print(&srv->net,
721                                 "<message id='chat' from='%s' to='%s' type='groupchat'>"
722                                 "<body>%s</body>"
723                                 "</message>",
724                                 srv->jid, chan->dest, text);
725                 } else {
726                         net_print(&srv->net,
727                                 "<message id='chat' from='%s' to='%s'>"
728                                 "<body>%s</body>"
729                                 "</message>",
730                                 srv->jid, chan->dest, text);
731                         chat_recv(channel, srv->nick, text);
732                 }
733         }
734 }
735
736 void xmpp_exit(void)
737 {
738 }