2 * Copyright (C) 2017 Andy Spencer <andy753421@gmail.com>
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.
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.
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/>.
52 typedef struct xmpp_server_t {
72 struct xmpp_server_t *next;
75 typedef struct xmpp_channel_t {
80 struct xmpp_channel_t *next;
84 static xmpp_server_t *servers;
85 static xmpp_channel_t *channels;
88 const char *find_attr(const char **attrs, const char *name)
90 for (int i = 0; attrs[i] && attrs[i+1]; i += 2)
91 if (match(attrs[i+0], name))
96 static xmpp_server_t *find_server(const char *name, int create)
98 xmpp_server_t *cur = NULL, *last = NULL;
99 for (cur = servers; cur; last = cur, cur = cur->next)
100 if (match(cur->name, name))
102 if (!cur && create) {
103 cur = new0(xmpp_server_t);
104 cur->name = get_name(name);
113 static xmpp_channel_t *find_channel(const char *name, int create)
115 xmpp_channel_t *cur = NULL, *last = NULL;
116 for (cur = channels; cur; last = cur, cur = cur->next)
117 if (match(cur->name, name))
119 if (!cur && create) {
120 cur = new0(xmpp_channel_t);
121 cur->name = get_name(name);
130 static void on_start(void *_srv, const char *tag, const char **attrs)
132 xmpp_server_t *srv = _srv;
136 srv->indent*4, "", tag);
137 for (int i = 0; attrs[i] && attrs[i+1]; i += 2) {
138 debug("%*s%s=\"%s\"%s",
140 attrs[i+0], attrs[i+1],
141 attrs[i+2] ? "" : ">");
146 if (srv->state == XMPP_RECV_FEATURES) {
147 if (match(tag, "starttls")) {
148 debug("xmpp: features -> starttls");
149 srv->state = XMPP_SEND_STARTTLS;
151 if (match(tag, "mechanisms")) {
152 debug("xmpp: features -> auth");
153 srv->state = XMPP_SEND_AUTH;
155 if (match(tag, "bind")) {
156 debug("xmpp: features -> bind");
157 srv->state = XMPP_SEND_BIND;
160 if (srv->state == XMPP_RECV_PROCEED) {
161 if (match(tag, "proceed")) {
162 debug("xmpp: proceed -> encrypt");
163 srv->state = XMPP_ENCRYPT;
168 if (srv->state == XMPP_RECV_SUCCESS) {
169 if (match(tag, "success")) {
170 debug("xmpp: success -> restart");
171 srv->state = XMPP_RESTART;
174 if (srv->state == XMPP_RECV_JID) {
175 if (match(tag, "jid")) {
176 debug("xmpp: jid -> presence");
177 srv->state = XMPP_PRESENCE;
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"));
196 if (match(tag, "stream:error"))
200 static void on_end(void *_srv, const char *tag)
202 xmpp_server_t *srv = _srv;
203 const char *data = srv->buf.data;
209 (char*)srv->buf.data);
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);
223 if (match(tag, "stream:error"))
226 if (match(tag, "text")) {
227 debug("xmpp: error: %s", data);
228 chat_notice(NULL, NULL, "error: %s", data);
233 static void on_data(void *_srv, const char *data, int len)
235 xmpp_server_t *srv = _srv;
238 append(&srv->buf, data, len);
241 static void on_recv(void *_srv, char *buf, int len)
243 static char plain[AUTH_LEN];
244 static char coded[AUTH_LEN];
245 const char *resource;
247 xmpp_server_t *srv = _srv;
251 XML_Parse(srv->expat, buf, len, 0);
254 if (srv->state == XMPP_ENCRYPT) {
255 /* Encrypt connection */
256 net_encrypt(&srv->net);
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);
267 debug("xmpp: encrypt -> stream");
268 srv->state = XMPP_SEND_STREAM;
270 if (srv->state == XMPP_RESTART) {
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);
280 debug("xmpp: restart -> stream");
281 srv->state = XMPP_SEND_STREAM;
285 if (srv->state == XMPP_SEND_STREAM) {
286 if (net_print(&srv->net,
287 "<?xml version='1.0'?>"
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;
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;
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,
313 " xmlns='urn:ietf:params:xml:ns:xmpp-sasl'"
314 " mechanism='PLAIN'>%.*s</auth>",
316 debug("xmpp: auth -> success");
317 srv->state = XMPP_RECV_SUCCESS;
320 if (srv->state == XMPP_SEND_BIND) {
322 while (*resource && *resource != '/')
324 while (*resource && *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>"
333 debug("xmpp: bind -> jid");
334 srv->state = XMPP_RECV_JID;
337 if (srv->state == XMPP_PRESENCE) {
338 for (xmpp_channel_t *chan = channels; chan; chan = chan->next) {
342 "<presence id='join' from='%s' to='%s@%s/%s'>"
343 "<x xmlns='http://jabber.org/protocol/muc'/>"
345 srv->jid, chan->channel, srv->muc, srv->nick);
347 srv->state = XMPP_READY;
351 static void xmpp_connect(xmpp_server_t *srv)
354 srv->net.recv = on_recv;
356 net_open(&srv->net, srv->host, srv->port);
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);
367 srv->state = XMPP_SEND_STREAM;
373 for (xmpp_server_t *cur = servers; cur; cur = cur->next) {
374 if (!match(cur->protocol, "xmpp"))
379 error("jid is required");
385 void xmpp_config(const char *group, const char *name, const char *key, const char *value)
388 xmpp_channel_t *chan;
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);
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);
419 if (match(key, "channel"))
420 chan->channel = get_string(value);
421 else if (match(key, "join"))
422 chan->join = get_bool(value);
427 void xmpp_send(const char *channel, const char *msg)
431 xmpp_channel_t *chan = find_channel(channel, 0);
434 xmpp_server_t *srv = find_server(chan->server, 0);
435 if (!srv || !srv->protocol)
438 /* Handle commands */
440 if (prefix(msg, "/items", &arg)) {
442 "<iq id='items' type='get' from='%s' to='%s'>"
443 "<query xmlns='http://jabber.org/protocol/disco#items' />"
445 srv->jid, arg ?: srv->host);
447 else if (prefix(msg, "/info", &arg)) {
449 "<iq id='info' type='get' from='%s' to='%s'>"
450 "<query xmlns='http://jabber.org/protocol/disco#info' />"
452 srv->jid, arg ?: srv->host);
454 else if (prefix(msg, "/list", &arg)) {
456 "<iq id='list' type='get' from='%s' to='%s'>"
457 "<query xmlns='http://jabber.org/protocol/disco#items' />"
459 srv->jid, arg ?: srv->muc);
461 else if (prefix(msg, "/join", &arg)) {
463 chat_notice(NULL, NULL, "usage: /join <channel>");
467 "<presence id='join' from='%s' to='%s@%s/%s'>"
468 "<x xmlns='http://jabber.org/protocol/muc'/>"
470 srv->jid, arg, srv->muc, srv->nick);
473 debug("unknown: [%s]", msg);
474 chat_notice(NULL, NULL,
475 "unknown command %s", msg);
478 debug("message: [%s]", msg);
480 "<message to='%s'><body>%s</body></message>",
481 "andy@pileus.org", msg);