]> Pileus Git - ~andy/lamechat/blob - xmpp.c
Fix timestamps
[~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_SESSION,
51         XMPP_SEND_PRESENCE,
52         XMPP_SEND_JOIN,
53         XMPP_READY,
54         XMPP_IN_IQ,
55         XMPP_IN_MESSAGE,
56         XMPP_IN_PRESENCE,
57 } xmpp_state_t;
58
59 typedef struct {
60         channel_t       channel;
61         char            dest[JID_LEN];
62
63         const char     *room;
64         int             join;
65 } xmpp_channel_t;
66
67 typedef struct {
68         server_t        server;
69         xmpp_channel_t  system;
70
71         int             connect;
72         const char     *host;
73         int             port;
74         const char     *muc;
75         const char     *nick;
76         const char     *jid;
77         const char     *user;
78         const char     *pass;
79         int             quiet;
80
81         net_t           net;
82         XML_Parser      expat;
83         xmpp_state_t    state;
84         buf_t           buf;
85         int             indent;
86
87         char           *body;
88         stamp_t         stamp;
89
90         int             in_error;
91
92         char            msg_jid[JID_LEN];
93         char            msg_usr[JID_LEN];
94         char            msg_srv[JID_LEN];
95         char            msg_res[JID_LEN];
96         char           *msg_from;
97         xmpp_channel_t *msg_chan;
98 } xmpp_server_t;
99
100 /* Helper functions */
101 static void srv_notice(xmpp_server_t *srv, const char *fmt, ...)
102 {
103         static char buf[1024];
104
105         va_list ap;
106         va_start(ap, fmt);
107         vsnprintf(buf, sizeof(buf), fmt, ap);
108         va_end(ap);
109
110         chat_recv(&srv->system.channel, NULL, buf);
111 }
112
113 static void chan_notice(xmpp_channel_t *chan, const char *fmt, ...)
114 {
115         static char buf[1024];
116
117         va_list ap;
118         va_start(ap, fmt);
119         vsnprintf(buf, sizeof(buf), fmt, ap);
120         va_end(ap);
121
122         chat_recv(&chan->channel, NULL, buf);
123 }
124
125 static void split_jid(const char *jid, char *usr, char *srv, char *res)
126 {
127         char *ptr = usr;
128         int   pos = 0;
129
130         if (usr) usr[0] = '\0';
131         if (srv) srv[0] = '\0';
132         if (res) res[0] = '\0';
133
134         for (int i = 0; jid && jid[i]; i++) {
135                 switch (jid[i]) {
136                         case '@':
137                                 ptr = srv;
138                                 pos = 0;
139                                 continue;
140                         case '/':
141                                 ptr = res;
142                                 pos = 0;
143                                 continue;
144                 }
145                 if (ptr && (pos+1) < JID_LEN) {
146                         ptr[pos++] = jid[i];
147                         ptr[pos] = '\0';
148                 }
149         }
150
151         //debug("JID: '%s' usr=[%s] srv=[%s] res=[%s]",
152         //              jid, usr, srv, res);
153 }
154
155 static xmpp_channel_t *find_dest(xmpp_server_t *srv,
156                 const char *jid, int is_muc)
157 {
158         static char jid_usr[JID_LEN];
159         static char jid_srv[JID_LEN];
160         static char dest[JID_LEN];
161         xmpp_channel_t *chan;
162
163         split_jid(jid, jid_usr, jid_srv, NULL);
164         snprintf(dest, JID_LEN, "%s@%s", jid_usr,
165                  jid_srv[0] ? jid_srv  :
166                  is_muc     ? srv->muc : srv->host);
167
168         /* Server channels */
169         if (match(jid, srv->host))
170                 return &srv->system;
171
172         /* Find existing channels */
173         for (channel_t *cur = channels; cur; cur = cur->next) {
174                 if (cur->server != &srv->server)
175                         continue;
176                 chan = (xmpp_channel_t *)cur;
177                 if (match(chan->dest, dest))
178                         return chan;
179         }
180
181         /* Create a new channel */
182         chan = (xmpp_channel_t *)add_channel(jid_usr, &srv->server);
183         strncpy(chan->dest, dest, JID_LEN);
184         return chan;
185 }
186
187 static const char *find_attr(const char **attrs, const char *name)
188 {
189         for (int i = 0; attrs[i] && attrs[i+1]; i += 2)
190                 if (match(attrs[i+0], name))
191                         return attrs[i+1];
192         return NULL;
193 }
194
195 /* Callback functions */
196 static void xmpp_run(xmpp_server_t *srv,
197                 const char *start, const char **attrs,
198                 const char *end, const char *data);
199
200 static void on_start(void *_srv, const char *tag, const char **attrs)
201 {
202         xmpp_server_t *srv = _srv;
203         xmpp_run(srv, tag, attrs, NULL, reset(&srv->buf));
204 }
205
206 static void on_data(void *_srv, const char *data, int len)
207 {
208         xmpp_server_t *srv = _srv;
209         append(&srv->buf, data, len);
210 }
211
212 static void on_end(void *_srv, const char *tag)
213 {
214         xmpp_server_t *srv = _srv;
215         xmpp_run(srv, NULL, NULL, tag, reset(&srv->buf));
216 }
217
218 static void on_send(void *_srv)
219 {
220         xmpp_server_t *srv = _srv;
221         xmpp_run(srv, NULL, NULL, NULL, NULL);
222 }
223
224 static void on_recv(void *_srv, char *buf, int len)
225 {
226         xmpp_server_t *srv = _srv;
227         if (len > 0)
228                 XML_Parse(srv->expat, buf, len, 0);
229         xmpp_run(srv, NULL, NULL, NULL, NULL);
230 }
231
232 static void on_err(void *_srv, int errno)
233 {
234         xmpp_server_t *srv = _srv;
235         xmpp_run(srv, NULL, NULL, NULL, NULL);
236 }
237
238 /* XMPP State machine */
239 static void xmpp_run(xmpp_server_t *srv,
240                 const char *start, const char **attrs,
241                 const char *end, const char *data)
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 -> session");
394                         srv->state = XMPP_SEND_SESSION;
395                 }
396         }
397         if (srv->state == XMPP_SEND_SESSION) {
398                 if (net_print(&srv->net,
399                     "<iq id='session' type='set' to='%s'>"
400                     "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>"
401                     "</iq>",
402                     srv->host)) {
403                         debug("xmpp: session -> presence");
404                         srv->state = XMPP_SEND_PRESENCE;
405                 }
406         }
407         if (srv->state == XMPP_SEND_PRESENCE) {
408                 if (net_print(&srv->net, "<presence/>")) {
409                         debug("xmpp: presence -> join");
410                         srv->state = XMPP_SEND_JOIN;
411                 }
412         }
413         if (srv->state == XMPP_SEND_JOIN) {
414                 for (channel_t *cur = channels; cur; cur = cur->next) {
415                         if (cur->server != &srv->server)
416                                 continue;
417                         xmpp_channel_t *chan = (xmpp_channel_t *)cur;
418                         if (!chan->join)
419                                 continue;
420                         net_print(&srv->net,
421                                 "<presence id='join' from='%s' to='%s/%s'>"
422                                 "<x xmlns='http://jabber.org/protocol/muc'/>"
423                                 "</presence>",
424                                 srv->jid, chan->dest, srv->nick);
425                 }
426                 debug("xmpp: join -> ready");
427                 srv->state = XMPP_READY;
428         }
429
430         /* Start message */
431         xmpp_channel_t *chan = NULL;
432
433         if (srv->state == XMPP_READY) {
434                 srv->state = match(start, "iq")       ? XMPP_IN_IQ       :
435                              match(start, "message")  ? XMPP_IN_MESSAGE  :
436                              match(start, "presence") ? XMPP_IN_PRESENCE :
437                                                         XMPP_READY;
438                 if (srv->state != XMPP_READY) {
439                         const char *from = find_attr(attrs, "from") ?: "";
440                         strncpy(srv->msg_jid, from, JID_LEN);
441                         split_jid(srv->msg_jid, srv->msg_usr,
442                                   srv->msg_srv, srv->msg_res);
443
444                         if (match(srv->msg_srv, srv->muc)) {
445                                 srv->msg_from = srv->msg_res[0] ? srv->msg_res : NULL;
446                                 srv->msg_chan = find_dest(srv, srv->msg_jid, 1);
447                         } else {
448                                 srv->msg_from = srv->msg_usr[0] ? srv->msg_usr : NULL;
449                                 srv->msg_chan = find_dest(srv, srv->msg_jid, 0);
450                         }
451
452                         debug("xmpp: %s -- jid=[%s] from=[%s] chan=[%s]",
453                                 start,
454                                 srv->msg_jid, srv->msg_from,
455                                 srv->msg_chan->channel.name);
456                 }
457         }
458         if (srv->state == XMPP_IN_IQ ||
459             srv->state == XMPP_IN_MESSAGE ||
460             srv->state == XMPP_IN_PRESENCE) {
461                 if (srv->msg_chan && srv->msg_chan != &srv->system)
462                         chan = srv->msg_chan;
463         }
464
465         /* Info/Queries */
466         if (srv->state == XMPP_IN_IQ) {
467                 if (match(start, "item") && chan) {
468                         static char res[JID_LEN];
469                         split_jid(find_attr(attrs, "jid"),
470                                   NULL, NULL, res);
471                         chan_notice(chan, "user: %s", res);
472                 }
473                 if (match(start, "item") && !chan) {
474                         srv_notice(srv, "item: [%s] %s",
475                                         find_attr(attrs, "jid"),
476                                         find_attr(attrs, "name"));
477                 }
478                 if (match(start, "identity")) {
479                         srv_notice(srv, "identity: %s",
480                                         find_attr(attrs, "name"));
481                 }
482                 if (match(start, "feature")) {
483                         srv_notice(srv, "feature: %s",
484                                         find_attr(attrs, "var"));
485                 }
486                 if (match(start, "field")) {
487                         debug("xmpp: %s -- type=[%s] label=[%s]", end,
488                                 find_attr(attrs, "type"),
489                                 find_attr(attrs, "label"));
490                         if (!find_attr(attrs, "label"))
491                                 return;
492                         chan_notice(chan, "%-36s -- %s (%s)",
493                                 find_attr(attrs, "var"),
494                                 find_attr(attrs, "label"),
495                                 find_attr(attrs, "type"));
496                 }
497                 if (match(end, "title")) {
498                         debug("xmpp: title -- jid=[%s]",
499                                 end, srv->msg_jid);
500                         chan_notice(chan, "Title: %s", data);
501                 }
502                 if (match(end, "instructions")) {
503                         debug("xmpp: instructions -- jid=[%s]",
504                                 end, srv->msg_jid);
505                         chan_notice(chan, "%s", data);
506                 }
507         }
508
509         /* Messages */
510         if (srv->state == XMPP_IN_MESSAGE) {
511                 if (match(start, "delay")) {
512                         const char *ts = find_attr(attrs, "stamp");
513                         if (ts) {
514                                 struct tm tm = {};
515                                 strptime(ts, "%Y-%m-%dT%H:%M:%S", &tm);
516                                 srv->stamp = timegm(&tm);
517                         }
518                 }
519                 if (match(end, "body")) {
520                         strset(&srv->body, data);
521                 }
522                 if (match(end, "message")) {
523                         debug("xmpp: body (%s) -- chan=[%s] jid=[%s] from=[%s]",
524                                 srv->msg_from == srv->msg_usr ? "user" : "chat" ,
525                                 srv->msg_chan->channel.name,
526                                 srv->msg_jid, srv->msg_from);
527                         if (srv->body) {
528                                 chat_recv(&chan->channel, srv->msg_from, srv->body);
529                                 message_t *msg = &messages[history-1];
530                                 msg->when = srv->stamp ?: msg->when;
531                         }
532                         srv->stamp = 0;
533                         strset(&srv->body, NULL);
534                 }
535         }
536
537         /* Presence */
538         if (srv->state == XMPP_IN_PRESENCE) {
539                 static char alias[JID_LEN];
540                 const char *jid;
541                 if (match(start, "item")) {
542                         if ((jid = find_attr(attrs, "jid")))
543                                 strncpy(alias, jid, JID_LEN);
544                 }
545                 if (match(end, "presence") && chan) {
546                         if (alias[0] && !srv->quiet)
547                                 chan_notice(chan, "%s (%s) entered room.",
548                                                 srv->msg_from, alias);
549                         else if (!srv->quiet)
550                                 chan_notice(chan, "%s entered room.",
551                                                 srv->msg_from);
552                         alias[0] = '\0';
553                 }
554         }
555
556         /* End messages */
557         if (srv->state == XMPP_IN_IQ ||
558             srv->state == XMPP_IN_MESSAGE ||
559             srv->state == XMPP_IN_PRESENCE) {
560                 if (match(end, "iq") ||
561                     match(end, "message") ||
562                     match(end, "presence")) {
563                         srv->state = XMPP_READY;
564
565                         srv->msg_jid[0] = '\0';
566                         srv->msg_usr[0] = '\0';
567                         srv->msg_srv[0] = '\0';
568                         srv->msg_res[0] = '\0';
569                         srv->msg_from   = NULL;
570                         srv->msg_chan   = NULL;
571
572                         static time_t due = 0;
573                         static time_t now = 0;
574                         now = time(NULL);
575                         if (now > due) {
576                                 net_print(&srv->net, "<presence/>");
577                                 due = now + 30;
578                         }
579                 }
580         }
581         /* Error handling */
582         if (match(start, "stream:error"))
583                 srv->in_error = 1;
584         if (match(end, "stream:error"))
585                 srv->in_error = 0;
586         if (srv->in_error) {
587                 if (match(end, "text")) {
588                         debug("xmpp: error: %s", data);
589                         srv_notice(srv, "error: %s", data);
590                 }
591         }
592 }
593
594 /* XMPP functions */
595 void xmpp_init(void)
596 {
597         for (server_t *cur = servers; cur; cur = cur->next) {
598                 if (cur->protocol != XMPP)
599                         continue;
600
601                 xmpp_server_t *srv = (xmpp_server_t*)cur;
602                 srv->system.channel.server = &srv->server;
603                 srv->system.channel.name   = srv->server.name;
604                 srv_notice(srv, "XMPP Server: %s", srv->server.name);
605
606                 if (!srv->port)
607                         srv->port = 5222;
608                 if (!srv->jid)
609                         error("jid is required");
610                 if (srv->connect)
611                         xmpp_run(srv, NULL, NULL, NULL, NULL);
612         }
613         for (channel_t *cur = channels; cur; cur = cur->next) {
614                 if (cur->server->protocol != XMPP)
615                         continue;
616
617                 xmpp_channel_t *chan = (xmpp_channel_t*)cur;
618                 xmpp_server_t *srv = (xmpp_server_t*)cur->server;
619                 if (!chan->room)
620                         chan->room = strcopy(cur->name);
621                 snprintf(chan->dest, JID_LEN, "%s@%s",
622                          chan->room, srv->muc);
623         }
624 }
625
626 server_t *xmpp_server(void)
627 {
628         return new0(xmpp_server_t);
629 }
630
631 channel_t *xmpp_channel(void)
632 {
633         return new0(xmpp_channel_t);
634 }
635
636 void xmpp_config(server_t *server, channel_t *channel,
637                  const char *group, const char *name,
638                  const char *key, const char *value)
639 {
640         xmpp_server_t  *srv  = (xmpp_server_t*)server;
641         xmpp_channel_t *chan = (xmpp_channel_t*)channel;
642
643         if (server) {
644                 if (match(key, "connect"))
645                         srv->connect = get_bool(value);
646                 else if (match(key, "host"))
647                         srv->host = get_string(value);
648                 else if (match(key, "port"))
649                         srv->port = get_number(value);
650                 else if (match(key, "muc"))
651                         srv->muc = get_string(value);
652                 else if (match(key, "nick"))
653                         srv->nick = get_string(value);
654                 else if (match(key, "jid"))
655                         srv->jid = get_string(value);
656                 else if (match(key, "user"))
657                         srv->user = get_string(value);
658                 else if (match(key, "pass"))
659                         srv->pass = get_string(value);
660                 else if (match(key, "quiet"))
661                         srv->quiet = get_bool(value);
662         }
663         if (channel) {
664                 if (match(key, "room"))
665                         chan->room = get_string(value);
666                 else if (match(key, "join"))
667                         chan->join = get_bool(value);
668         }
669 }
670
671 void xmpp_send(channel_t *channel, const char *text)
672 {
673         xmpp_channel_t *chan = (xmpp_channel_t*)channel;
674         xmpp_server_t  *srv  = (xmpp_server_t*)channel->server;
675         const char *arg;
676
677         /* Handle commands */
678         if (text[0] == '/') {
679                 if (prefix(text, "/items", &arg)) {
680                         net_print(&srv->net,
681                                 "<iq id='items' type='get' from='%s' to='%s'>"
682                                 "<query xmlns='http://jabber.org/protocol/disco#items'/>"
683                                 "</iq>",
684                                 srv->jid, arg ?: srv->host);
685                 }
686                 else if (prefix(text, "/info", &arg)) {
687                         net_print(&srv->net,
688                                 "<iq id='info' type='get' from='%s' to='%s'>"
689                                 "<query xmlns='http://jabber.org/protocol/disco#info'/>"
690                                 "</iq>",
691                                 srv->jid, arg ?: srv->host);
692                 }
693                 else if (prefix(text, "/names", &arg)) {
694                         if (arg)
695                                 chan = find_dest(srv, arg, 1);
696                         if (chan == &srv->system) {
697                                 chan_notice(chan, "Cannot get names from server");
698                                 return;
699                         }
700                         net_print(&srv->net,
701                                 "<iq id='list' type='get' from='%s' to='%s'>"
702                                 "<query xmlns='http://jabber.org/protocol/disco#items'/>"
703                                 "</iq>",
704                                 srv->jid, chan->dest);
705                 }
706                 else if (prefix(text, "/join", &arg)) {
707                         if (!arg) {
708                                 chan_notice(chan, "usage: /join <channel>");
709                                 return;
710                         }
711                         net_print(&srv->net,
712                                 "<presence id='join' from='%s' to='%s/%s'>"
713                                 "<x xmlns='http://jabber.org/protocol/muc'/>"
714                                 "</presence>",
715                                 srv->jid, chan->dest, srv->nick);
716                         chan = find_dest(srv, arg, 1);
717                         chan_notice(chan, "Room: %s", arg);
718                 }
719                 else if (prefix(text, "/config", &arg)) {
720                         if (arg) {
721                                 chan_notice(chan, "Unimplemented: /config <arg>");
722                                 return;
723                         }
724                         net_print(&srv->net,
725                                 "<iq id='config' type='get' from='%s' to='%s'>"
726                                 "<query xmlns='http://jabber.org/protocol/muc#owner'/>"
727                                 "</iq>",
728                                 srv->jid, chan->dest);
729                 }
730                 else if (prefix(text, "/query", &arg)) {
731                         if (!arg) {
732                                 chan_notice(chan, "usage: /query <user>");
733                                 return;
734                         }
735                         chan = find_dest(srv, arg, 0);
736                         chan_notice(chan, "User: %s", arg);
737                 }
738                 else {
739                         chan_notice(chan, "Unknown command %s", text);
740                 }
741         } else {
742                 debug("message: [%s]", text);
743                 if (chan == &srv->system) {
744                         chan_notice(chan, "Cannot send to server");
745                 }
746                 else if (!chan->dest) {
747                         chan_notice(chan, "No destination for message");
748                 }
749                 else if (chan->room) {
750                         net_print(&srv->net,
751                                 "<message id='chat' from='%s' to='%s' type='groupchat'>"
752                                 "<body>%s</body>"
753                                 "</message>",
754                                 srv->jid, chan->dest, text);
755                 } else {
756                         net_print(&srv->net,
757                                 "<message id='chat' from='%s' to='%s'>"
758                                 "<body>%s</body>"
759                                 "</message>",
760                                 srv->jid, chan->dest, text);
761                         chat_recv(channel, srv->nick, text);
762                 }
763         }
764 }
765
766 void xmpp_exit(void)
767 {
768 }