]> Pileus Git - ~andy/lamechat/blob - xmpp.c
Use net for IRC
[~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
35 /* XMPP types */
36 typedef enum {
37         XMPP_DEAD,
38         XMPP_SEND_STREAM,
39         XMPP_RECV_FEATURES,
40         XMPP_SEND_STARTTLS,
41         XMPP_RECV_PROCEED,
42         XMPP_SEND_AUTH,
43         XMPP_RECV_SUCCESS,
44         XMPP_SEND_BIND,
45         XMPP_RECV_JID,
46         XMPP_PRESENCE,
47         XMPP_ENCRYPT,
48         XMPP_RESTART,
49         XMPP_READY,
50 } xmpp_state_t;
51
52 typedef struct xmpp_server_t {
53         const char  *name;
54         const char  *protocol;
55         int          connect;
56         const char  *host;
57         int          port;
58         const char  *muc;
59         const char  *nick;
60         const char  *jid;
61         const char  *user;
62         const char  *pass;
63
64         net_t        net;
65         XML_Parser   expat;
66         xmpp_state_t state;
67         buf_t        buf;
68         int          indent;
69
70         int          in_error;
71
72         struct xmpp_server_t *next;
73 } xmpp_server_t;
74
75 typedef struct xmpp_channel_t {
76         const char  *name;
77         const char  *channel;
78         const char  *server;
79         int          join;
80         struct xmpp_channel_t *next;
81 } xmpp_channel_t;
82
83 /* Local data */
84 static xmpp_server_t  *servers;
85 static xmpp_channel_t *channels;
86
87 /* Local functions */
88 const char *find_attr(const char **attrs, const char *name)
89 {
90         for (int i = 0; attrs[i] && attrs[i+1]; i += 2)
91                 if (match(attrs[i+0], name))
92                         return attrs[i+1];
93         return NULL;
94 }
95
96 static xmpp_server_t *find_server(const char *name, int create)
97 {
98         xmpp_server_t *cur = NULL, *last = NULL;
99         for (cur = servers; cur; last = cur, cur = cur->next)
100                 if (match(cur->name, name))
101                         break;
102         if (!cur && create) {
103                 cur = new0(xmpp_server_t);
104                 cur->name = get_name(name);
105                 if (last)
106                         last->next = cur;
107                 else
108                         servers = cur;
109         }
110         return cur;
111 }
112
113 static xmpp_channel_t *find_channel(const char *name, int create)
114 {
115         xmpp_channel_t *cur = NULL, *last = NULL;
116         for (cur = channels; cur; last = cur, cur = cur->next)
117                 if (match(cur->name, name))
118                         break;
119         if (!cur && create) {
120                 cur = new0(xmpp_channel_t);
121                 cur->name = get_name(name);
122                 if (last)
123                         last->next = cur;
124                 else
125                         channels = cur;
126         }
127         return cur;
128 }
129
130 static void on_start(void *_srv, const char *tag, const char **attrs)
131 {
132         xmpp_server_t *srv = _srv;
133
134         /* Debug print */
135         debug("%*s<%s>",
136                 srv->indent*4, "", tag);
137         for (int i = 0; attrs[i] && attrs[i+1]; i += 2) {
138                 debug("%*s%s=\"%s\"%s",
139                         srv->indent*4+8, "",
140                         attrs[i+0], attrs[i+1],
141                         attrs[i+2] ? "" : ">");
142         }
143         srv->indent++;
144
145         /* Start TLS */
146         if (srv->state == XMPP_RECV_FEATURES) {
147                 if (match(tag, "starttls")) {
148                         debug("xmpp: features -> starttls");
149                         srv->state = XMPP_SEND_STARTTLS;
150                 }
151                 if (match(tag, "mechanisms")) {
152                         debug("xmpp: features -> auth");
153                         srv->state = XMPP_SEND_AUTH;
154                 }
155                 if (match(tag, "bind")) {
156                         debug("xmpp: features -> bind");
157                         srv->state = XMPP_SEND_BIND;
158                 }
159         }
160         if (srv->state == XMPP_RECV_PROCEED) {
161                 if (match(tag, "proceed")) {
162                         debug("xmpp: proceed -> encrypt");
163                         srv->state = XMPP_ENCRYPT;
164                 }
165         }
166
167         /* Authentication */
168         if (srv->state == XMPP_RECV_SUCCESS) {
169                 if (match(tag, "success")) {
170                         debug("xmpp: success -> restart");
171                         srv->state = XMPP_RESTART;
172                 }
173         }
174         if (srv->state == XMPP_RECV_JID) {
175                 if (match(tag, "jid")) {
176                         debug("xmpp: jid -> presence");
177                         srv->state = XMPP_PRESENCE;
178                 }
179         }
180
181         /* Info queries */
182         if (srv->state == XMPP_READY) {
183                 if (match(tag, "item"))
184                         chat_notice(NULL, NULL, "item: [%s] %s",
185                                         find_attr(attrs, "jid"),
186                                         find_attr(attrs, "name"));
187                 if (match(tag, "identity"))
188                         chat_notice(NULL, NULL, "identity: %s",
189                                         find_attr(attrs, "name"));
190                 if (match(tag, "feature"))
191                         chat_notice(NULL, NULL, "feature: %s",
192                                         find_attr(attrs, "var"));
193         }
194
195         /* Error handling */
196         if (match(tag, "stream:error"))
197                 srv->in_error = 1;
198 }
199
200 static void on_end(void *_srv, const char *tag)
201 {
202         xmpp_server_t *srv = _srv;
203         const char *data = srv->buf.data;
204
205         /* Debug print */
206         if (srv->buf.len) {
207                 debug("%*s \"%s\"",
208                         srv->indent*4, "",
209                         (char*)srv->buf.data);
210                 srv->buf.len = 0;
211         }
212         srv->indent--;
213
214         /* Receive messages */
215         if (srv->state == XMPP_READY) {
216                 if (match(tag, "body")) {
217                         debug("xmpp: jid -> ready");
218                         chat_recv("#test", NULL, data);
219                 }
220         }
221
222         /* Error handling */
223         if (match(tag, "stream:error"))
224                 srv->in_error = 0;
225         if (srv->in_error) {
226                 if (match(tag, "text")) {
227                         debug("xmpp: error: %s", data);
228                         chat_notice(NULL, NULL, "error: %s", data);
229                 }
230         }
231 }
232
233 static void on_data(void *_srv, const char *data, int len)
234 {
235         xmpp_server_t *srv = _srv;
236
237         /* Debug print */
238         append(&srv->buf, data, len);
239 }
240
241 static void on_recv(void *_srv, char *buf, int len)
242 {
243         static char plain[AUTH_LEN];
244         static char coded[AUTH_LEN];
245         const char *resource;
246
247         xmpp_server_t *srv = _srv;
248
249         /* Parse input */
250         if (len > 0)
251                 XML_Parse(srv->expat, buf, len, 0);
252
253         /* Stream restart */
254         if (srv->state == XMPP_ENCRYPT) {
255                 /* Encrypt connection */
256                 net_encrypt(&srv->net);
257
258                 /* Reset Expat */
259                 if (!(XML_ParserReset(srv->expat, NULL)))
260                         error("Error resetting XML parser");
261                 XML_SetUserData(srv->expat, srv);
262                 XML_SetStartElementHandler(srv->expat, on_start);
263                 XML_SetEndElementHandler(srv->expat, on_end);
264                 XML_SetCharacterDataHandler(srv->expat, on_data);
265
266                 /* Reset server */
267                 debug("xmpp: encrypt -> stream");
268                 srv->state = XMPP_SEND_STREAM;
269         }
270         if (srv->state == XMPP_RESTART) {
271                 /* Reset Expat */
272                 if (!(XML_ParserReset(srv->expat, NULL)))
273                         error("Error resetting XML parser");
274                 XML_SetUserData(srv->expat, srv);
275                 XML_SetStartElementHandler(srv->expat, on_start);
276                 XML_SetEndElementHandler(srv->expat, on_end);
277                 XML_SetCharacterDataHandler(srv->expat, on_data);
278
279                 /* Reset server */
280                 debug("xmpp: restart -> stream");
281                 srv->state = XMPP_SEND_STREAM;
282         }
283
284         /* State machine */
285         if (srv->state == XMPP_SEND_STREAM) {
286                 if (net_print(&srv->net,
287                     "<?xml version='1.0'?>"
288                     "<stream:stream"
289                     " from='%s'"
290                     " to='%s'"
291                     " version='1.0'"
292                     " xml:lang='en'"
293                     " xmlns='jabber:client'"
294                     " xmlns:stream='http://etherx.jabber.org/streams'>",
295                     srv->jid, srv->host)) {
296                         debug("xmpp: stream -> features");
297                         srv->state = XMPP_RECV_FEATURES;
298                 }
299         }
300         if (srv->state == XMPP_SEND_STARTTLS) {
301                 if (net_print(&srv->net,
302                     "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>")) {
303                         debug("xmpp: startls -> proceed");
304                         srv->state = XMPP_RECV_PROCEED;
305                 }
306         }
307         if (srv->state == XMPP_SEND_AUTH) {
308                 len = snprintf(plain, AUTH_LEN, "%s%c%s%c%s",
309                                 srv->user, '\0', srv->user, '\0', srv->pass);
310                 len = base64(plain, len, coded, AUTH_LEN);
311                 if (net_print(&srv->net,
312                     "<auth"
313                     " xmlns='urn:ietf:params:xml:ns:xmpp-sasl'"
314                     " mechanism='PLAIN'>%.*s</auth>",
315                         len, coded)) {
316                         debug("xmpp: auth -> success");
317                         srv->state = XMPP_RECV_SUCCESS;
318                 }
319         }
320         if (srv->state == XMPP_SEND_BIND) {
321                 resource = srv->jid;
322                 while (*resource && *resource != '/')
323                         resource++;
324                 while (*resource && *resource == '/')
325                         resource++;
326                 if (net_print(&srv->net,
327                     "<iq id='bind' type='set'>"
328                     "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>"
329                     "<resource>%s</resource>"
330                     "</bind>"
331                     "</iq>",
332                         resource)) {
333                         debug("xmpp: bind -> jid");
334                         srv->state = XMPP_RECV_JID;
335                 }
336         }
337         if (srv->state == XMPP_PRESENCE) {
338                 for (xmpp_channel_t *chan = channels; chan; chan = chan->next) {
339                         if (!chan->join)
340                                 continue;
341                         net_print(&srv->net,
342                                 "<presence id='join' from='%s' to='%s@%s/%s'>"
343                                 "<x xmlns='http://jabber.org/protocol/muc'/>"
344                                 "</presence>",
345                                 srv->jid, chan->channel, srv->muc, srv->nick);
346                 }
347                 srv->state = XMPP_READY;
348         }
349 }
350
351 static void xmpp_connect(xmpp_server_t *srv)
352 {
353         /* Net connect */
354         srv->net.recv = on_recv;
355         srv->net.data = srv;
356         net_open(&srv->net, srv->host, srv->port);
357
358         /* Setup Expat */
359         if (!(srv->expat = XML_ParserCreate(NULL)))
360                 error("Error creating XML parser");
361         XML_SetUserData(srv->expat, srv);
362         XML_SetStartElementHandler(srv->expat, on_start);
363         XML_SetEndElementHandler(srv->expat, on_end);
364         XML_SetCharacterDataHandler(srv->expat, on_data);
365
366         /* Setup server */
367         srv->state = XMPP_SEND_STREAM;
368 }
369
370 /* XMPP functions */
371 void xmpp_init(void)
372 {
373         for (xmpp_server_t *cur = servers; cur; cur = cur->next) {
374                 if (!match(cur->protocol, "xmpp"))
375                         continue;
376                 if (!cur->port)
377                         cur->port = 5222;
378                 if (!cur->jid)
379                         error("jid is required");
380                 if (cur->connect)
381                         xmpp_connect(cur);
382         }
383 }
384
385 void xmpp_config(const char *group, const char *name, const char *key, const char *value)
386 {
387         xmpp_server_t  *srv;
388         xmpp_channel_t *chan;
389
390         if (match(group, "server")) {
391                 srv = find_server(name, 1);
392                 if (match(key, "protocol") &&
393                     match(value, "xmpp"))
394                         srv->protocol = get_string(value);
395                 if (match(srv->protocol, "xmpp")) {
396                         if (match(key, "connect"))
397                                 srv->connect = get_bool(value);
398                         else if (match(key, "host"))
399                                 srv->host = get_string(value);
400                         else if (match(key, "port"))
401                                 srv->port = get_number(value);
402                         else if (match(key, "muc"))
403                                 srv->muc = get_string(value);
404                         else if (match(key, "nick"))
405                                 srv->nick = get_string(value);
406                         else if (match(key, "jid"))
407                                 srv->jid = get_string(value);
408                         else if (match(key, "user"))
409                                 srv->user = get_string(value);
410                         else if (match(key, "pass"))
411                                 srv->pass = get_string(value);
412                 }
413         } else if (match(group, "channel")) {
414                 chan = find_channel(name, 1);
415                 if (match(key, "server") &&
416                     find_server(value, 0))
417                         chan->server = get_string(value);
418                 if (chan->server) {
419                         if (match(key, "channel"))
420                                 chan->channel = get_string(value);
421                         else if (match(key, "join"))
422                                 chan->join = get_bool(value);
423                 }
424         }
425 }
426
427 void xmpp_send(const char *channel, const char *msg)
428 {
429         const char *arg;
430
431         xmpp_channel_t *chan = find_channel(channel, 0);
432         if (!chan)
433                 return;
434         xmpp_server_t *srv = find_server(chan->server, 0);
435         if (!srv || !srv->protocol)
436                 return;
437
438         /* Handle commands */
439         if (msg[0] == '/') {
440                 if (prefix(msg, "/items", &arg)) {
441                         net_print(&srv->net,
442                                 "<iq id='items' type='get' from='%s' to='%s'>"
443                                 "<query xmlns='http://jabber.org/protocol/disco#items' />"
444                                 "</iq>",
445                                 srv->jid, arg ?: srv->host);
446                 }
447                 else if (prefix(msg, "/info", &arg)) {
448                         net_print(&srv->net,
449                                 "<iq id='info' type='get' from='%s' to='%s'>"
450                                 "<query xmlns='http://jabber.org/protocol/disco#info' />"
451                                 "</iq>",
452                                 srv->jid, arg ?: srv->host);
453                 }
454                 else if (prefix(msg, "/list", &arg)) {
455                         net_print(&srv->net,
456                                 "<iq id='list' type='get' from='%s' to='%s'>"
457                                 "<query xmlns='http://jabber.org/protocol/disco#items' />"
458                                 "</iq>",
459                                 srv->jid, arg ?: srv->muc);
460                 }
461                 else if (prefix(msg, "/join", &arg)) {
462                         if (!arg) {
463                                 chat_notice(NULL, NULL, "usage: /join <channel>");
464                                 return;
465                         }
466                         net_print(&srv->net,
467                                 "<presence id='join' from='%s' to='%s@%s/%s'>"
468                                 "<x xmlns='http://jabber.org/protocol/muc'/>"
469                                 "</presence>",
470                                 srv->jid, arg, srv->muc, srv->nick);
471                 }
472                 else {
473                         debug("unknown: [%s]", msg);
474                         chat_notice(NULL, NULL,
475                                 "unknown command %s", msg);
476                 }
477         } else {
478                 debug("message: [%s]", msg);
479                 net_print(&srv->net,
480                         "<message to='%s'><body>%s</body></message>",
481                         "andy@pileus.org", msg);
482         }
483 }
484
485 void xmpp_exit(void)
486 {
487 }