]> Pileus Git - ~andy/lamechat/blob - xmpp.c
Combine state machines.
[~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_state_t;
52
53 typedef struct {
54         server_t     server;
55
56         int          connect;
57         const char  *host;
58         int          port;
59         const char  *muc;
60         const char  *nick;
61         const char  *jid;
62         const char  *user;
63         const char  *pass;
64
65         net_t        net;
66         XML_Parser   expat;
67         xmpp_state_t state;
68         buf_t        buf;
69         int          indent;
70
71         int          in_error;
72
73         char         msg_from[JID_LEN];
74         char         msg_usr[JID_LEN];
75         char         msg_srv[JID_LEN];
76         char         msg_res[JID_LEN];
77 } xmpp_server_t;
78
79 typedef struct {
80         channel_t    channel;
81         char         dest[JID_LEN];
82
83         const char  *room;
84         int          join;
85 } xmpp_channel_t;
86
87 /* Helper functions */
88 static void split_jid(const char *jid, char *usr, char *srv, char *res)
89 {
90         char *ptr = usr;
91         int   pos = 0;
92
93         if (usr) usr[0] = '\0';
94         if (srv) srv[0] = '\0';
95         if (res) res[0] = '\0';
96
97         for (int i = 0; jid && jid[i]; i++) {
98                 switch (jid[i]) {
99                         case '@':
100                                 ptr = srv;
101                                 pos = 0;
102                                 continue;
103                         case '/':
104                                 ptr = res;
105                                 pos = 0;
106                                 continue;
107                 }
108                 if (ptr && (pos+1) < JID_LEN) {
109                         ptr[pos++] = jid[i];
110                         ptr[pos] = '\0';
111                 }
112         }
113
114         debug("JID: '%s' usr=[%s] srv=[%s] res=[%s]",
115                         jid, usr, srv, res);
116 }
117
118 static xmpp_channel_t *find_dest(xmpp_server_t *srv,
119                 const char *jid, int is_muc)
120 {
121         static char jid_usr[JID_LEN];
122         static char jid_srv[JID_LEN];
123         static char dest[JID_LEN];
124         xmpp_channel_t *chan;
125
126         split_jid(jid, jid_usr, jid_srv, NULL);
127         snprintf(dest, JID_LEN, "%s@%s", jid_usr,
128                  jid_srv[0] ? jid_srv  :
129                  is_muc     ? srv->muc : srv->host);
130
131         /* Find existing channels */
132         for (channel_t *cur = channels; cur; cur = cur->next) {
133                 if (cur->server != &srv->server)
134                         continue;
135                 chan = (xmpp_channel_t *)cur;
136                 if (match(chan->dest, dest))
137                         return chan;
138         }
139
140         /* Create a new channel */
141         chan = (xmpp_channel_t *)add_channel(jid_usr, &srv->server);
142         strncpy(chan->dest, dest, JID_LEN);
143         return chan;
144 }
145
146 static const char *find_attr(const char **attrs, const char *name)
147 {
148         for (int i = 0; attrs[i] && attrs[i+1]; i += 2)
149                 if (match(attrs[i+0], name))
150                         return attrs[i+1];
151         return NULL;
152 }
153
154 /* Callback functions */
155 static void xmpp_run(xmpp_server_t *srv,
156                 const char *start, const char **attrs,
157                 const char *end, const char *data);
158
159 static void on_start(void *_srv, const char *tag, const char **attrs)
160 {
161         xmpp_server_t *srv = _srv;
162         xmpp_run(srv, tag, attrs, NULL, reset(&srv->buf));
163 }
164
165 static void on_data(void *_srv, const char *data, int len)
166 {
167         xmpp_server_t *srv = _srv;
168         append(&srv->buf, data, len);
169 }
170
171 static void on_end(void *_srv, const char *tag)
172 {
173         xmpp_server_t *srv = _srv;
174         xmpp_run(srv, NULL, NULL, tag, reset(&srv->buf));
175 }
176
177 static void on_send(void *_srv)
178 {
179         xmpp_server_t *srv = _srv;
180         xmpp_run(srv, NULL, NULL, NULL, NULL);
181 }
182
183 static void on_recv(void *_srv, char *buf, int len)
184 {
185         xmpp_server_t *srv = _srv;
186         if (len > 0)
187                 XML_Parse(srv->expat, buf, len, 0);
188         xmpp_run(srv, NULL, NULL, NULL, NULL);
189 }
190
191 static void on_err(void *_srv, int errno)
192 {
193         xmpp_server_t *srv = _srv;
194         xmpp_run(srv, NULL, NULL, NULL, NULL);
195 }
196
197 /* XMPP State machine */
198 static void xmpp_run(xmpp_server_t *srv,
199                 const char *start, const char **attrs,
200                 const char *end, const char *data)
201 {
202         /* Debug print */
203         if (data)
204                 debug("%*s \"%s\"", srv->indent*4, "", data);
205         if (start) {
206                 debug("%*s<%s>", srv->indent*4, "", start);
207                 for (int i = 0; attrs[i] && attrs[i+1]; i += 2) {
208                         debug("%*s%s=\"%s\"%s",
209                                 srv->indent*4+8, "",
210                                 attrs[i+0], attrs[i+1],
211                                 attrs[i+2] ? "" : ">");
212                 }
213         }
214         if (start)
215                 srv->indent++;
216         if (end)
217                 srv->indent--;
218
219         /* Connection Handling */
220         if (srv->state == XMPP_CONNECT && !start && !end) {
221                 srv->net.send = on_send;
222                 srv->net.recv = on_recv;
223                 srv->net.err  = on_err;
224                 srv->net.data = srv;
225                 net_open(&srv->net, srv->host, srv->port);
226
227                 if (!(srv->expat = XML_ParserCreate(NULL)))
228                         error("Error creating XML parser");
229                 XML_SetUserData(srv->expat, srv);
230                 XML_SetStartElementHandler(srv->expat, on_start);
231                 XML_SetEndElementHandler(srv->expat, on_end);
232                 XML_SetCharacterDataHandler(srv->expat, on_data);
233
234                 debug("xmpp: connect -> stream");
235                 srv->state = XMPP_SEND_STREAM;
236         }
237         if (srv->state == XMPP_ENCRYPT && !start && !end) {
238                 net_encrypt(&srv->net);
239
240                 if (!(XML_ParserReset(srv->expat, NULL)))
241                         error("Error resetting XML parser");
242                 XML_SetUserData(srv->expat, srv);
243                 XML_SetStartElementHandler(srv->expat, on_start);
244                 XML_SetEndElementHandler(srv->expat, on_end);
245                 XML_SetCharacterDataHandler(srv->expat, on_data);
246
247                 debug("xmpp: encrypt -> stream");
248                 srv->state = XMPP_SEND_STREAM;
249         }
250         if (srv->state == XMPP_RESTART && !start && !end) {
251                 if (!(XML_ParserReset(srv->expat, NULL)))
252                         error("Error resetting XML parser");
253                 XML_SetUserData(srv->expat, srv);
254                 XML_SetStartElementHandler(srv->expat, on_start);
255                 XML_SetEndElementHandler(srv->expat, on_end);
256                 XML_SetCharacterDataHandler(srv->expat, on_data);
257
258                 debug("xmpp: restart -> stream");
259                 srv->state = XMPP_SEND_STREAM;
260         }
261
262         /* Stream Start */
263         if (srv->state == XMPP_SEND_STREAM) {
264                 if (net_print(&srv->net,
265                     "<?xml version='1.0'?>"
266                     "<stream:stream"
267                     " from='%s'"
268                     " to='%s'"
269                     " version='1.0'"
270                     " xml:lang='en'"
271                     " xmlns='jabber:client'"
272                     " xmlns:stream='http://etherx.jabber.org/streams'>",
273                     srv->jid, srv->host)) {
274                         debug("xmpp: stream -> features");
275                         srv->state = XMPP_RECV_FEATURES;
276                 }
277         }
278         if (srv->state == XMPP_RECV_FEATURES) {
279                 if (match(start, "starttls")) {
280                         debug("xmpp: features -> starttls");
281                         srv->state = XMPP_SEND_STARTTLS;
282                 }
283                 if (match(start, "mechanisms")) {
284                         debug("xmpp: features -> auth");
285                         srv->state = XMPP_SEND_AUTH;
286                 }
287                 if (match(start, "bind")) {
288                         debug("xmpp: features -> bind");
289                         srv->state = XMPP_SEND_BIND;
290                 }
291         }
292
293         /* Start TLS */
294         if (srv->state == XMPP_SEND_STARTTLS) {
295                 if (net_print(&srv->net,
296                     "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>")) {
297                         debug("xmpp: startls -> proceed");
298                         srv->state = XMPP_RECV_PROCEED;
299                 }
300         }
301         if (srv->state == XMPP_RECV_PROCEED) {
302                 if (match(start, "proceed")) {
303                         debug("xmpp: proceed -> encrypt");
304                         srv->state = XMPP_ENCRYPT;
305                 }
306         }
307
308         /* Authentication */
309         if (srv->state == XMPP_SEND_AUTH) {
310                 static char plain[AUTH_LEN];
311                 static char coded[AUTH_LEN];
312                 int len;
313                 len = snprintf(plain, AUTH_LEN, "%s%c%s%c%s",
314                                 srv->user, '\0', srv->user, '\0', srv->pass);
315                 len = base64(plain, len, coded, AUTH_LEN);
316                 if (net_print(&srv->net,
317                     "<auth"
318                     " xmlns='urn:ietf:params:xml:ns:xmpp-sasl'"
319                     " mechanism='PLAIN'>%.*s</auth>",
320                         len, coded)) {
321                         debug("xmpp: auth -> success");
322                         srv->state = XMPP_RECV_SUCCESS;
323                 }
324         }
325         if (srv->state == XMPP_RECV_SUCCESS) {
326                 if (match(start, "success")) {
327                         debug("xmpp: success -> restart");
328                         srv->state = XMPP_RESTART;
329                 }
330         }
331
332         /* Binding */
333         if (srv->state == XMPP_SEND_BIND) {
334                 const char *resource = srv->jid;
335                 while (*resource && *resource != '/')
336                         resource++;
337                 while (*resource && *resource == '/')
338                         resource++;
339                 if (net_print(&srv->net,
340                     "<iq id='bind' type='set'>"
341                     "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>"
342                     "<resource>%s</resource>"
343                     "</bind>"
344                     "</iq>",
345                     resource)) {
346                         debug("xmpp: bind -> jid");
347                         srv->state = XMPP_RECV_JID;
348                 }
349         }
350         if (srv->state == XMPP_RECV_JID) {
351                 if (match(start, "jid")) {
352                         debug("xmpp: jid -> presence");
353                         srv->state = XMPP_SEND_PRESENCE;
354                 }
355         }
356         if (srv->state == XMPP_SEND_PRESENCE) {
357                 for (channel_t *cur = channels; cur; cur = cur->next) {
358                         xmpp_channel_t *chan = (xmpp_channel_t*)cur;
359                         if (cur->server != &srv->server || !chan->join)
360                                 continue;
361                         net_print(&srv->net,
362                                 "<presence id='join' from='%s' to='%s/%s'>"
363                                 "<x xmlns='http://jabber.org/protocol/muc'/>"
364                                 "</presence>",
365                                 srv->jid, chan->dest, srv->nick);
366                 }
367                 srv->state = XMPP_READY;
368         }
369
370         /* Info queries */
371         if (srv->state == XMPP_READY) {
372                 if (match(start, "item"))
373                         chat_notice(NULL, NULL, "item: [%s] %s",
374                                         find_attr(attrs, "jid"),
375                                         find_attr(attrs, "name"));
376                 if (match(start, "identity"))
377                         chat_notice(NULL, NULL, "identity: %s",
378                                         find_attr(attrs, "name"));
379                 if (match(start, "feature"))
380                         chat_notice(NULL, NULL, "feature: %s",
381                                         find_attr(attrs, "var"));
382         }
383
384         /* Receive messages */
385         if (srv->state == XMPP_READY) {
386                 if (match(start, "message")) {
387                         strncpy(srv->msg_from, find_attr(attrs,"from"),
388                                 JID_LEN);
389                         split_jid(srv->msg_from, srv->msg_usr,
390                                   srv->msg_srv, srv->msg_res);
391                 }
392                 if (match(end, "message")) {
393                         srv->msg_from[0] = '\0';
394                         srv->msg_usr[0]  = '\0';
395                         srv->msg_srv[0]  = '\0';
396                         srv->msg_res[0]  = '\0';
397                 }
398                 if (match(end, "body") && data) {
399                         xmpp_channel_t *chan;
400                         debug("xmpp: jid -> ready");
401                         if (match(srv->msg_srv, srv->muc)) {
402                                 chan = find_dest(srv, srv->msg_from, 1);
403                                 chat_recv(&chan->channel, srv->msg_res, data);
404                         } else {
405                                 chan = find_dest(srv, srv->msg_from, 0);
406                                 chat_recv(&chan->channel, srv->msg_usr, data);
407                         }
408                 }
409         }
410
411         /* Error handling */
412         if (match(start, "stream:error"))
413                 srv->in_error = 1;
414         if (match(end, "stream:error"))
415                 srv->in_error = 0;
416         if (srv->in_error) {
417                 if (match(end, "text")) {
418                         debug("xmpp: error: %s", data);
419                         chat_notice(NULL, NULL, "error: %s", data);
420                 }
421         }
422 }
423
424 /* XMPP functions */
425 void xmpp_init(void)
426 {
427         for (server_t *cur = servers; cur; cur = cur->next) {
428                 if (cur->protocol != XMPP)
429                         continue;
430
431                 xmpp_server_t *srv = (xmpp_server_t*)cur;
432                 if (!srv->port)
433                         srv->port = 5222;
434                 if (!srv->jid)
435                         error("jid is required");
436                 if (srv->connect)
437                         xmpp_run(srv, NULL, NULL, NULL, NULL);
438         }
439         for (channel_t *cur = channels; cur; cur = cur->next) {
440                 if (cur->server->protocol != XMPP)
441                         continue;
442
443                 xmpp_channel_t *chan = (xmpp_channel_t*)cur;
444                 xmpp_server_t *srv = (xmpp_server_t*)cur->server;
445                 if (!chan->room)
446                         chan->room = strcopy(cur->name);
447                 snprintf(chan->dest, JID_LEN, "%s@%s",
448                          chan->room, srv->muc);
449         }
450 }
451
452 server_t *xmpp_server(void)
453 {
454         return new0(xmpp_server_t);
455 }
456
457 channel_t *xmpp_channel(void)
458 {
459         return new0(xmpp_channel_t);
460 }
461
462 void xmpp_config(server_t *server, channel_t *channel,
463                  const char *group, const char *name,
464                  const char *key, const char *value)
465 {
466         xmpp_server_t  *srv  = (xmpp_server_t*)server;
467         xmpp_channel_t *chan = (xmpp_channel_t*)channel;
468
469         if (server) {
470                 if (match(key, "connect"))
471                         srv->connect = get_bool(value);
472                 else if (match(key, "host"))
473                         srv->host = get_string(value);
474                 else if (match(key, "port"))
475                         srv->port = get_number(value);
476                 else if (match(key, "muc"))
477                         srv->muc = get_string(value);
478                 else if (match(key, "nick"))
479                         srv->nick = get_string(value);
480                 else if (match(key, "jid"))
481                         srv->jid = get_string(value);
482                 else if (match(key, "user"))
483                         srv->user = get_string(value);
484                 else if (match(key, "pass"))
485                         srv->pass = get_string(value);
486         }
487         if (channel) {
488                 if (match(key, "room"))
489                         chan->room = get_string(value);
490                 else if (match(key, "join"))
491                         chan->join = get_bool(value);
492         }
493 }
494
495 void xmpp_send(message_t *msg)
496 {
497         xmpp_channel_t *chan = (xmpp_channel_t*)msg->channel;
498         xmpp_server_t  *srv  = (xmpp_server_t*)msg->channel->server;
499         const char *txt = msg->text;
500         const char *arg;
501
502         /* Handle commands */
503         if (txt[0] == '/') {
504                 if (prefix(txt, "/items", &arg)) {
505                         net_print(&srv->net,
506                                 "<iq id='items' type='get' from='%s' to='%s'>"
507                                 "<query xmlns='http://jabber.org/protocol/disco#items' />"
508                                 "</iq>",
509                                 srv->jid, arg ?: srv->host);
510                 }
511                 else if (prefix(txt, "/info", &arg)) {
512                         net_print(&srv->net,
513                                 "<iq id='info' type='get' from='%s' to='%s'>"
514                                 "<query xmlns='http://jabber.org/protocol/disco#info' />"
515                                 "</iq>",
516                                 srv->jid, arg ?: srv->host);
517                 }
518                 else if (prefix(txt, "/list", &arg)) {
519                         net_print(&srv->net,
520                                 "<iq id='list' type='get' from='%s' to='%s'>"
521                                 "<query xmlns='http://jabber.org/protocol/disco#items' />"
522                                 "</iq>",
523                                 srv->jid, arg ?: srv->muc);
524                 }
525                 else if (prefix(txt, "/join", &arg)) {
526                         if (!arg) {
527                                 chat_notice(NULL, NULL, "usage: /join <channel>");
528                                 return;
529                         }
530                         chan = find_dest(srv, arg, 1);
531                         net_print(&srv->net,
532                                 "<presence id='join' from='%s' to='%s/%s'>"
533                                 "<x xmlns='http://jabber.org/protocol/muc'/>"
534                                 "</presence>",
535                                 srv->jid, chan->dest, srv->nick);
536                 }
537                 else if (prefix(txt, "/query", &arg)) {
538                         if (!arg) {
539                                 chat_notice(NULL, NULL, "usage: /query <user>");
540                                 return;
541                         }
542                         chan = find_dest(srv, arg, 0);
543                         (void)chan;
544                 }
545                 else {
546                         debug("unknown: [%s]", txt);
547                         chat_notice(NULL, NULL,
548                                 "unknown command %s", txt);
549                 }
550         } else {
551                 debug("message: [%s]", txt);
552                 if (chan->room) {
553                         net_print(&srv->net,
554                                 "<message id='chat' from='%s' to='%s' type='groupchat'>"
555                                 "<body>%s</body>"
556                                 "</message>",
557                                 srv->jid, chan->dest, txt);
558                 } else {
559                         net_print(&srv->net,
560                                 "<message id='chat' from='%s' to='%s'>"
561                                 "<body>%s</body>"
562                                 "</message>",
563                                 srv->jid, chan->dest, txt);
564                 }
565         }
566 }
567
568 void xmpp_exit(void)
569 {
570 }