]> Pileus Git - ~andy/lamechat/blob - xmpp.c
XMPP Chat
[~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 <fcntl.h>
26 #include <netdb.h>
27 #include <expat.h>
28
29 #include "util.h"
30 #include "chat.h"
31 #include "conf.h"
32 #include "net.h"
33
34 /* Constants */
35 #define AUTH_LEN 512
36
37 /* XMPP types */
38 typedef enum {
39         XMPP_DEAD,
40         XMPP_SEND_STREAM,
41         XMPP_RECV_FEATURES,
42         XMPP_SEND_STARTTLS,
43         XMPP_RECV_PROCEED,
44         XMPP_SEND_AUTH,
45         XMPP_RECV_SUCCESS,
46         XMPP_SEND_BIND,
47         XMPP_RECV_JID,
48         XMPP_ENCRYPT,
49         XMPP_RESTART,
50         XMPP_READY,
51 } xmpp_state_t;
52
53 typedef struct xmpp_server_t {
54         const char  *name;
55         const char  *protocol;
56         int          connect;
57         const char  *host;
58         int          port;
59         const char  *jid;
60         const char  *user;
61         const char  *pass;
62
63         net_t        net;
64         XML_Parser   expat;
65         xmpp_state_t state;
66         buf_t        buf;
67         int          indent;
68
69         struct xmpp_server_t *next;
70 } xmpp_server_t;
71
72 typedef struct xmpp_channel_t {
73         const char  *name;
74         const char  *channel;
75         const char  *server;
76         int          join;
77         struct xmpp_channel_t *next;
78 } xmpp_channel_t;
79
80 /* Local data */
81 static xmpp_server_t  *servers;
82 static xmpp_channel_t *channels;
83
84 /* Local functions */
85 static xmpp_server_t *find_server(const char *name, int create)
86 {
87         xmpp_server_t *cur = NULL, *last = NULL;
88         for (cur = servers; cur; last = cur, cur = cur->next)
89                 if (match(cur->name, name))
90                         break;
91         if (!cur && create) {
92                 cur = new0(xmpp_server_t);
93                 cur->name = get_name(name);
94                 if (last)
95                         last->next = cur;
96                 else
97                         servers = cur;
98         }
99         return cur;
100 }
101
102 static xmpp_channel_t *find_channel(const char *name, int create)
103 {
104         xmpp_channel_t *cur = NULL, *last = NULL;
105         for (cur = channels; cur; last = cur, cur = cur->next)
106                 if (match(cur->name, name))
107                         break;
108         if (!cur && create) {
109                 cur = new0(xmpp_channel_t);
110                 cur->name = get_name(name);
111                 if (last)
112                         last->next = cur;
113                 else
114                         channels = cur;
115         }
116         return cur;
117 }
118
119 static void on_start(void *_srv, const char *tag, const char **attrs)
120 {
121         xmpp_server_t *srv = _srv;
122
123         /* Debug print */
124         debug("%*s<%s>",
125                 srv->indent*4, "", tag);
126         for (int i = 0; attrs[i] && attrs[i+1]; i += 2) {
127                 debug("%*s%s=\"%s\"%s",
128                         srv->indent*4+8, "",
129                         attrs[i+0], attrs[i+1],
130                         attrs[i+2] ? "" : ">");
131         }
132         srv->indent++;
133
134         /* Start TLS */
135         if (srv->state == XMPP_RECV_FEATURES) {
136                 if (match(tag, "starttls")) {
137                         debug("xmpp: features -> starttls");
138                         srv->state = XMPP_SEND_STARTTLS;
139                 }
140                 if (match(tag, "mechanisms")) {
141                         debug("xmpp: features -> auth");
142                         srv->state = XMPP_SEND_AUTH;
143                 }
144                 if (match(tag, "bind")) {
145                         debug("xmpp: features -> bind");
146                         srv->state = XMPP_SEND_BIND;
147                 }
148         }
149         if (srv->state == XMPP_RECV_PROCEED) {
150                 if (match(tag, "proceed")) {
151                         debug("xmpp: proceed -> encrypt");
152                         srv->state = XMPP_ENCRYPT;
153                 }
154         }
155
156         /* Authentication */
157         if (srv->state == XMPP_RECV_SUCCESS) {
158                 if (match(tag, "success")) {
159                         debug("xmpp: success -> restart");
160                         srv->state = XMPP_RESTART;
161                 }
162         }
163         if (srv->state == XMPP_RECV_JID) {
164                 if (match(tag, "jid")) {
165                         debug("xmpp: jid -> ready");
166                         srv->state = XMPP_READY;
167                 }
168         }
169 }
170
171 static void on_end(void *_srv, const char *tag)
172 {
173         xmpp_server_t *srv = _srv;
174
175         /* Debug print */
176         if (srv->buf.len) {
177                 debug("%*s \"%s\"",
178                         srv->indent*4, "",
179                         (char*)srv->buf.data);
180                 srv->buf.len = 0;
181         }
182         srv->indent--;
183
184         /* Receive messages */
185         if (srv->state == XMPP_READY) {
186                 if (match(tag, "body")) {
187                         debug("xmpp: jid -> ready");
188                         chat_recv("#test", NULL, data);
189                 }
190         }
191 }
192
193 static void on_data(void *_srv, const char *data, int len)
194 {
195         xmpp_server_t *srv = _srv;
196
197         /* Debug print */
198         append(&srv->buf, data, len);
199 }
200
201 static void on_recv(void *_srv, char *buf, int len)
202 {
203         static char plain[AUTH_LEN];
204         static char coded[AUTH_LEN];
205         const char *resource;
206
207         xmpp_server_t *srv = _srv;
208
209         /* Parse input */
210         if (len > 0)
211                 XML_Parse(srv->expat, buf, len, 0);
212
213         /* State machine */
214 output:
215         if (srv->state == XMPP_SEND_STREAM) {
216                 if (net_print(&srv->net,
217                     "<?xml version='1.0'?>"
218                     "<stream:stream"
219                     " from='%s'"
220                     " to='%s'"
221                     " version='1.0'"
222                     " xml:lang='en'"
223                     " xmlns='jabber:client'"
224                     " xmlns:stream='http://etherx.jabber.org/streams'>",
225                     srv->jid, srv->host)) {
226                         debug("xmpp: stream -> features");
227                         srv->state = XMPP_RECV_FEATURES;
228                 }
229         }
230         if (srv->state == XMPP_SEND_STARTTLS) {
231                 if (net_print(&srv->net,
232                     "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>")) {
233                         debug("xmpp: startls -> proceed");
234                         srv->state = XMPP_RECV_PROCEED;
235                 }
236         }
237         if (srv->state == XMPP_SEND_AUTH) {
238                 len = snprintf(plain, AUTH_LEN, "%s%c%s%c%s",
239                                 srv->user, '\0', srv->user, '\0', srv->pass);
240                 len = base64(plain, len, coded, AUTH_LEN);
241                 if (net_print(&srv->net,
242                     "<auth"
243                     " xmlns='urn:ietf:params:xml:ns:xmpp-sasl'"
244                     " mechanism='PLAIN'>%.*s</auth>",
245                         len, coded)) {
246                         debug("xmpp: auth -> success");
247                         srv->state = XMPP_RECV_SUCCESS;
248                 }
249         }
250         if (srv->state == XMPP_SEND_BIND) {
251                 resource = srv->jid;
252                 while (*resource && *resource != '/')
253                         resource++;
254                 while (*resource && *resource == '/')
255                         resource++;
256                 if (net_print(&srv->net,
257                     "<iq id='bind' type='set'>"
258                     "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>"
259                     "<resource>%s</resource>"
260                     "</bind>"
261                     "</iq>",
262                         resource)) {
263                         debug("xmpp: bind -> jid");
264                         srv->state = XMPP_RECV_JID;
265                 }
266         }
267         if (srv->state == XMPP_ENCRYPT) {
268                 /* Encrypt connection */
269                 net_encrypt(&srv->net);
270
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: encrypt -> stream");
281                 srv->state = XMPP_SEND_STREAM;
282                 goto output;
283         }
284         if (srv->state == XMPP_RESTART) {
285                 /* Reset Expat */
286                 if (!(XML_ParserReset(srv->expat, NULL)))
287                         error("Error resetting XML parser");
288                 XML_SetUserData(srv->expat, srv);
289                 XML_SetStartElementHandler(srv->expat, on_start);
290                 XML_SetEndElementHandler(srv->expat, on_end);
291                 XML_SetCharacterDataHandler(srv->expat, on_data);
292
293                 /* Reset server */
294                 debug("xmpp: restart -> stream");
295                 srv->state = XMPP_SEND_STREAM;
296                 goto output;
297         }
298 }
299
300 static void xmpp_connect(xmpp_server_t *srv)
301 {
302         /* Net connect */
303         srv->net.recv = on_recv;
304         srv->net.data = srv;
305         net_open(&srv->net, srv->host, srv->port);
306
307         /* Setup Expat */
308         if (!(srv->expat = XML_ParserCreate(NULL)))
309                 error("Error creating XML parser");
310         XML_SetUserData(srv->expat, srv);
311         XML_SetStartElementHandler(srv->expat, on_start);
312         XML_SetEndElementHandler(srv->expat, on_end);
313         XML_SetCharacterDataHandler(srv->expat, on_data);
314
315         /* Setup server */
316         srv->state = XMPP_SEND_STREAM;
317 }
318
319 /* XMPP functions */
320 void xmpp_init(void)
321 {
322         for (xmpp_server_t *cur = servers; cur; cur = cur->next) {
323                 if (!match(cur->protocol, "xmpp"))
324                         continue;
325                 if (!cur->port)
326                         cur->port = 5222;
327                 if (!cur->jid)
328                         error("jid is required");
329                 if (cur->connect)
330                         xmpp_connect(cur);
331         }
332 }
333
334 void xmpp_config(const char *group, const char *name, const char *key, const char *value)
335 {
336         xmpp_server_t  *srv;
337         xmpp_channel_t *chan;
338
339         if (match(group, "server")) {
340                 srv = find_server(name, 1);
341                 if (match(key, "protocol") &&
342                     match(value, "xmpp"))
343                         srv->protocol = get_string(value);
344                 if (match(srv->protocol, "xmpp")) {
345                         if (match(key, "connect"))
346                                 srv->connect = get_bool(value);
347                         else if (match(key, "host"))
348                                 srv->host = get_string(value);
349                         else if (match(key, "port"))
350                                 srv->port = get_number(value);
351                         else if (match(key, "jid"))
352                                 srv->jid = get_string(value);
353                         else if (match(key, "user"))
354                                 srv->user = get_string(value);
355                         else if (match(key, "pass"))
356                                 srv->pass = get_string(value);
357                 }
358         } else if (match(group, "channel")) {
359                 chan = find_channel(name, 1);
360                 if (match(key, "server") &&
361                     find_server(value, 0))
362                         chan->server = get_string(value);
363                 if (chan->server) {
364                         if (match(key, "channel"))
365                                 chan->channel = get_string(value);
366                         else if (match(key, "join"))
367                                 chan->join = get_bool(value);
368                 }
369         }
370 }
371
372 void xmpp_send(const char *channel, const char *msg)
373 {
374         xmpp_channel_t *chan = find_channel(channel, 0);
375         if (!chan)
376                 return;
377         xmpp_server_t *srv = find_server(chan->server, 0);
378         if (!srv || !srv->protocol)
379                 return;
380
381         net_print(&srv->net,
382                 "<message to='%s'><body>%s</body></message>",
383                 "andy@pileus.org", msg);
384 }
385
386 void xmpp_exit(void)
387 {
388 }