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