/* View init */
void chat_init(void)
{
- chat_log = log_buf.data;
+ chat_log = (log_t*)log_buf.data;
chat_len = 0;
irc_init();
void chat_recv(const char *channel, const char *from, const char *msg)
{
append(&log_buf, NULL, sizeof(log_t));
- chat_log = log_buf.data;
+ chat_log = (log_t*)log_buf.data;
log_t *log = &chat_log[chat_len];
log->when = time(NULL);
void chat_send(const char *channel, const char *msg)
{
append(&log_buf, NULL, sizeof(log_t));
- chat_log = log_buf.data;
+ chat_log = (log_t*)log_buf.data;
log_t *log = &chat_log[chat_len];
log->when = time(NULL);
chat_recv(dst, from, msg);
}
-static void on_poll(void *_srv, int fd)
+static void on_poll(void *_srv)
{
static char buf[BUF_LEN];
static char hostname[512];
int len;
/* Handle Input */
- len = recv(fd, buf, sizeof(buf), 0);
+ len = recv(srv->poll.fd, buf, sizeof(buf), 0);
if (len < 0) {
debug("recv: error: %s -- %s", srv->name, strerror(errno));
}
/* Handle Output */
if (srv->out_len > 0) {
- len = send(fd, &srv->out_buf[srv->out_pos],
+ len = send(srv->poll.fd, &srv->out_buf[srv->out_pos],
srv->out_len-srv->out_pos, 0);
if (len > 0)
srv->out_pos += len;
/* Handle Errors */
int err = 0;
socklen_t elen = sizeof(err);
- if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &elen))
+ if (getsockopt(srv->poll.fd, SOL_SOCKET, SO_ERROR, &err, &elen))
error("Error getting socket opt");
if (err) {
debug("disconnect: %s -- %s", srv->name, strerror(err));
#include <signal.h>
#include "util.h"
+#include "net.h"
#include "args.h"
#include "conf.h"
#include "chat.h"
/* Initialize */
util_init();
+ net_init();
args_setup(argc, argv);
conf_setup(".lamechatrc", on_config);
# Compiler
GCC ?= gcc
CFLAGS ?= -Wall --std=c99
-LDFLAGS ?= -lncursesw -lexpat -lcrypto
+LDFLAGS ?= -lncursesw -lexpat -lcrypto -lssl
# Sources
PROG ?= lamechat
-SOURCES ?= main util args conf view chat irc xmpp
+SOURCES ?= main util args conf view chat net irc xmpp
# Targets
all: $(PROG)
--- /dev/null
+/*
+ * Copyright (C) 2017 Andy Spencer <andy753421@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <expat.h>
+
+#include <openssl/bio.h>
+#include <openssl/ssl.h>
+
+#include "util.h"
+#include "net.h"
+
+/* Local functions */
+static void on_poll(void *_net)
+{
+ static char buf[NET_BUFFER];
+ net_t *net = _net;
+ int len;
+
+ /* Handle Errors */
+ int err = 0;
+ socklen_t elen = sizeof(err);
+ if (getsockopt(net->poll.fd, SOL_SOCKET, SO_ERROR, &err, &elen))
+ error("Error getting socket opt");
+ if (err)
+ net_close(net);
+
+ /* State machine */
+ if (net->state == NET_CONNECT) {
+ debug("net: connect");
+ net->state = NET_READY;
+ }
+ if (net->state == NET_READY) {
+ debug("net: ready");
+ len = recv(net->poll.fd, buf, sizeof(buf), 0);
+ if (len == 0)
+ net_close(net);
+ net->recv(net->data, buf, len);
+ }
+ if (net->state == NET_READY) {
+ if (net->out_len > 0) {
+ debug("net: output plain");
+ len = send(net->poll.fd, &net->out_buf[net->out_pos],
+ net->out_len-net->out_pos, 0);
+ if (len > 0)
+ net->out_pos += len;
+ if (net->out_pos == net->out_len) {
+ net->out_pos = 0;
+ net->out_len = 0;
+ }
+ }
+ }
+ if (net->state == NET_HANDSHAKE) {
+ debug("net: handshake");
+ len = recv(net->poll.fd, buf, sizeof(buf), 0);
+ if (len == 0)
+ net_close(net);
+ if (len > 0)
+ BIO_write(net->in, buf, len);
+
+ SSL_do_handshake(net->ssl);
+
+ while ((len = BIO_read(net->out, buf, sizeof(buf))) > 0)
+ send(net->poll.fd, buf, len, 0);
+
+ if (SSL_is_init_finished(net->ssl))
+ net->state = NET_ENCRYPTED;
+ }
+ if (net->state == NET_ENCRYPTED) {
+ debug("net: encrypted");
+ len = recv(net->poll.fd, buf, sizeof(buf), 0);
+ if (len == 0)
+ net_close(net);
+ if (len > 0)
+ BIO_write(net->in, buf, len);
+
+ while ((len = SSL_read(net->ssl, buf, sizeof(buf))) > 0)
+ net->recv(net->data, buf, len);
+ }
+ if (net->state == NET_ENCRYPTED) {
+ if (net->out_len > 0) {
+ debug("net: output crypto");
+ len = SSL_write(net->ssl,
+ &net->out_buf[net->out_pos],
+ net->out_len-net->out_pos);
+ if (len > 0)
+ net->out_pos += len;
+ if (net->out_pos == net->out_len) {
+ net->out_pos = 0;
+ net->out_len = 0;
+ }
+ }
+
+ while ((len = BIO_read(net->out, buf, sizeof(buf))) > 0)
+ send(net->poll.fd, buf, len, 0);
+ }
+
+ /* Enable output poll */
+ if ((net->out_len > 0) &&
+ (net->state == NET_READY ||
+ net->state == NET_ENCRYPTED))
+ poll_ctl(&net->poll, 1, 1, 1);
+ else
+ poll_ctl(&net->poll, 1, 0, 1);
+}
+
+void net_encrypt(net_t *net)
+{
+ debug("net: encrypt");
+
+ net->ctx = SSL_CTX_new(TLSv1_2_client_method());
+ net->ssl = SSL_new(net->ctx);
+
+ net->in = BIO_new(BIO_s_mem());
+ net->out = BIO_new(BIO_s_mem());
+
+ BIO_set_mem_eof_return(net->in, -1);
+ BIO_set_mem_eof_return(net->out, -1);
+
+ SSL_set_bio(net->ssl, net->in, net->out);
+ SSL_set_connect_state(net->ssl);
+
+ net->state = NET_HANDSHAKE;
+}
+
+void net_close(net_t *net)
+{
+ debug("net_close: %s:%d",
+ net->host, net->port);
+ net->state = NET_CLOSED;
+ poll_del(&net->poll);
+}
+
+void net_open(net_t *net, const char *host, int port)
+{
+ int sock, flags;
+ struct addrinfo *addrs = NULL;
+ struct addrinfo hints = {};
+ char service[16];
+
+ net->host = strcopy(host);
+ net->port = port;
+
+ snprintf(service, sizeof(service), "%d", net->port);
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+
+ /* Setup address */
+ if (getaddrinfo(net->host, service, &hints, &addrs))
+ error("Error getting net address info");
+
+ if ((sock = socket(addrs->ai_family,
+ addrs->ai_socktype,
+ addrs->ai_protocol)) < 0)
+ error("Error opening net socket");
+
+ if ((flags = fcntl(sock, F_GETFL, 0)) < 0)
+ error("Error getting net socket flags");
+
+ if (fcntl(sock, F_SETFL, flags|O_NONBLOCK) < 0)
+ error("Error setting net socket non-blocking");
+
+ if (connect(sock, addrs->ai_addr, addrs->ai_addrlen) < 0)
+ if (errno != EINPROGRESS)
+ error("Error connecting socket");
+
+ freeaddrinfo(addrs);
+
+ /* Setup server */
+ net->state = NET_CONNECT;
+ poll_add(&net->poll, sock, on_poll, net);
+}
+
+int net_send(net_t *net, const char *buf, int len)
+{
+ if (net->out_len)
+ return 0;
+
+ debug("net: send");
+
+ if (len <= 0)
+ return 0;
+ if (len > NET_BUFFER)
+ len = NET_BUFFER;
+ memcpy(net->out_buf, buf, len);
+
+ net->out_len = len;
+ net->out_pos = 0;
+ return 1;
+}
+
+int net_print(net_t *net, const char *fmt, ...)
+{
+ int len;
+ va_list ap;
+ if (net->out_len)
+ return 0;
+
+ debug("net: print");
+
+ va_start(ap, fmt);
+ len = vsnprintf(net->out_buf, NET_BUFFER, fmt, ap);
+ va_end(ap);
+ if (len <= 0)
+ return 0;
+ if (len > NET_BUFFER)
+ len = NET_BUFFER;
+
+ net->out_len = len;
+ net->out_pos = 0;
+ return 1;
+}
+
+void net_init(void)
+{
+ SSL_library_init();
+ SSL_load_error_strings();
+ ERR_load_BIO_strings();
+ OpenSSL_add_all_algorithms();
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 Andy Spencer <andy753421@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Networking Constants */
+#define NET_BUFFER 1024
+
+/* OpenSSL Types */
+typedef struct ssl_ctx_st SSL_CTX;
+typedef struct ssl_st SSL;
+typedef struct bio_st BIO;
+
+/* Networking Callbacks */
+typedef void (*recv_t)(void *data, char *buf, int len);
+typedef void (*err_t)(void *data, int errno);
+
+/* Networking Types */
+typedef enum {
+ NET_CLOSED,
+ NET_CONNECT,
+ NET_READY,
+ NET_HANDSHAKE,
+ NET_ENCRYPTED,
+} nstate_t;
+
+typedef struct {
+ char *host;
+ int port;
+ recv_t recv;
+ err_t err;
+ void *data;
+
+ SSL_CTX *ctx;
+ SSL *ssl;
+ BIO *in;
+ BIO *out;
+
+ poll_t poll;
+ nstate_t state;
+
+ char out_buf[NET_BUFFER];
+ int out_pos;
+ int out_len;
+} net_t;
+
+/* Networking Functions */
+void net_init(void);
+void net_open(net_t *net, const char *host, int port);
+void net_encrypt(net_t *net);
+int net_send(net_t *net, const char *buf, int len);
+int net_print(net_t *net, const char *fmt, ...);
+int net_poll(net_t *net);
+void net_close(net_t *net);
return data;
}
-void append(buf_t *buf, const void *data, int len)
+void append(buf_t *buf, const char *data, int len)
{
if (buf->len + len + 1 > buf->max) {
buf->max += (((buf->len+len)/4096)+1)*4096;
// event.events & EPOLLWAKEUP ? " wakeup" : "",
// event.events & EPOLLONESHOT ? " oneshot" : "",
// event.events & EPOLLET ? " et" : "");
- poll->cb(poll->data, poll->fd);
+ poll->cb(poll->data);
return running;
}
return running;
#define new0(type) alloc0(sizeof(type))
/* Types */
-typedef void (*cb_t)(void *data, int fd);
+typedef void (*cb_t)(void *data);
typedef struct {
- void *data;
+ char *data;
int len;
int max;
} buf_t;
/* Memory functions */
void *alloc0(int size);
-void append(buf_t *buf, const void *data, int len);
+void append(buf_t *buf, const char *data, int len);
void release(buf_t *buf);
/* File functions */
#include "util.h"
#include "conf.h"
+#include "net.h"
/* XMPP Constants */
#define BUF_LEN 1024
/* XMPP types */
typedef enum {
XMPP_DEAD,
- XMPP_STREAM,
- XMPP_STARTTLS,
- XMPP_PROCEED,
+ XMPP_SEND_STREAM,
+ XMPP_RECV_FEATURES,
+ XMPP_SEND_STARTTLS,
+ XMPP_RECV_PROCEED,
+ XMPP_ENCRYPT,
XMPP_READY,
} xmpp_state_t;
const char *jid;
const char *pass;
+ net_t net;
XML_Parser expat;
xmpp_state_t state;
- poll_t poll;
-
- char in_buf[BUF_LEN];
- int in_len;
- char out_buf[BUF_LEN];
- int out_pos;
- int out_len;
-
- buf_t buf;
- int indent;
+ buf_t buf;
+ int indent;
struct xmpp_server_t *next;
} xmpp_server_t;
return cur;
}
-static int send_xml(xmpp_server_t *srv, const char *fmt, ...)
-{
- int len, sent;
- va_list ap;
- if (srv->out_len)
- return 0;
-
- va_start(ap, fmt);
- len = vsnprintf(srv->out_buf, BUF_LEN, fmt, ap);
- va_end(ap);
- if (len <= 0)
- return 0;
- if (len > BUF_LEN)
- len = BUF_LEN;
-
- //debug("xmpp send: [%.*s]", len, srv->out_buf);
- sent = send(srv->poll.fd, srv->out_buf, len, 0);
- if (sent == len)
- return 1;
-
- srv->out_len = len;
- srv->out_pos = sent;
- return 0;
-}
-
static void on_start(void *_srv, const char *tag, const char **attrs)
{
xmpp_server_t *srv = _srv;
debug("%*s<%s>",
srv->indent*4, "", tag);
for (int i = 0; attrs[i] && attrs[i+1]; i += 2) {
- debug("%*s -- %s=\"%s\"%s",
- srv->indent*4+6, "",
+ debug("%*s%s=\"%s\"%s",
+ srv->indent*4+8, "",
attrs[i+0], attrs[i+1],
attrs[i+2] ? "" : ">");
}
srv->indent++;
/* Start TLS */
- if (srv->state == XMPP_STARTTLS)
- if (match(tag, "starttls"))
- srv->state = XMPP_PROCEED;
+ if (srv->state == XMPP_RECV_FEATURES) {
+ if (match(tag, "starttls")) {
+ debug("xmpp: features -> starttls");
+ srv->state = XMPP_SEND_STARTTLS;
+ }
+ }
+ if (srv->state == XMPP_RECV_PROCEED) {
+ if (match(tag, "proceed")) {
+ debug("xmpp: proceed -> encrypt");
+ srv->state = XMPP_ENCRYPT;
+ }
+ }
}
static void on_end(void *_srv, const char *tag)
xmpp_server_t *srv = _srv;
/* Debug print */
+ if (srv->buf.len) {
+ debug("%*s \"%s\"",
+ srv->indent*4, "",
+ (char*)srv->buf.data);
+ srv->buf.len = 0;
+ }
srv->indent--;
}
static void on_data(void *_srv, const char *data, int len)
{
+ xmpp_server_t *srv = _srv;
+
+ /* Debug print */
+ append(&srv->buf, data, len);
}
-static void on_poll(void *_srv, int fd)
+static void on_recv(void *_srv, char *buf, int len)
{
xmpp_server_t *srv = _srv;
- static char buf[BUF_LEN];
- int len;
-
- /* Handle Input */
- len = recv(srv->poll.fd, buf, sizeof(buf), 0);
- if ((len < 0) && (errno != EAGAIN))
- debug("xmpp recv: error, %s -- %s", srv->name, strerror(errno));
- if (len == 0) {
- debug("xmpp recv: close, %s", srv->name);
- srv->state = XMPP_DEAD;
- poll_del(&srv->poll);
- return;
- }
- if (len > 0) {
- //debug("xmpp recv: ready, %s -- [%.*s]", srv->name, len, buf);
- XML_Parse(srv->expat, buf, len, 0);
- }
-
- /* Handle Output */
- if (srv->out_len > 0) {
- len = send(fd, &srv->out_buf[srv->out_pos],
- srv->out_len-srv->out_pos, 0);
- if (len > 0)
- srv->out_pos += len;
- if (srv->out_pos == srv->out_len) {
- srv->out_pos = 0;
- srv->out_len = 0;
- }
- }
- /* Handle Errors */
- int err = 0;
- socklen_t elen = sizeof(err);
- if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &elen))
- error("Error getting socket opt");
- if (err) {
- debug("disconnect: %s -- %s", srv->name, strerror(err));
- srv->state = XMPP_DEAD;
- poll_del(&srv->poll);
- return;
- }
+ /* Parse input */
+ if (len > 0)
+ XML_Parse(srv->expat, buf, len, 0);
/* State machine */
- if (srv->state == XMPP_STREAM) {
- if (send_xml(srv,
- "<?xml version='1.0'?>"
- "<stream:stream"
- " from='%s'"
- " to='%s'"
- " version='1.0'"
- " xml:lang='en'"
- " xmlns='jabber:client'"
- " xmlns:stream='http://etherx.jabber.org/streams'>",
- srv->jid, srv->host))
- srv->state = XMPP_STARTTLS;
+output:
+ if (srv->state == XMPP_SEND_STREAM) {
+ if (net_print(&srv->net,
+ "<?xml version='1.0'?>"
+ "<stream:stream"
+ " from='%s'"
+ " to='%s'"
+ " version='1.0'"
+ " xml:lang='en'"
+ " xmlns='jabber:client'"
+ " xmlns:stream='http://etherx.jabber.org/streams'>",
+ srv->jid, srv->host)) {
+ debug("xmpp: stream -> features");
+ srv->state = XMPP_RECV_FEATURES;
+ }
}
- if (srv->state == XMPP_PROCEED) {
- if (send_xml(srv, "<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>")) {
- debug("ready");
- srv->state = XMPP_READY;
+ if (srv->state == XMPP_SEND_STARTTLS) {
+ if (net_print(&srv->net,
+ "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>")) {
+ debug("xmpp: startls -> proceed");
+ srv->state = XMPP_RECV_PROCEED;
}
}
-
- /* Enable output poll */
- if (srv->out_len) {
- poll_ctl(&srv->poll, 1, 1, 1);
- } else {
- poll_ctl(&srv->poll, 1, 0, 1);
+ if (srv->state == XMPP_ENCRYPT) {
+ /* Encrypt connection */
+ net_encrypt(&srv->net);
+
+ /* Reset Expat */
+ if (!(XML_ParserReset(srv->expat, NULL)))
+ error("Error resetting XML parser");
+ XML_SetUserData(srv->expat, srv);
+ XML_SetStartElementHandler(srv->expat, on_start);
+ XML_SetEndElementHandler(srv->expat, on_end);
+ XML_SetCharacterDataHandler(srv->expat, on_data);
+
+ /* Reset server */
+ debug("xmpp: encrypt -> stream");
+ srv->state = XMPP_SEND_STREAM;
+ goto output;
}
}
static void xmpp_connect(xmpp_server_t *srv)
{
- int sock, flags;
- struct addrinfo *addrs = NULL;
- struct addrinfo hints = {};
- char service[16];
-
- snprintf(service, sizeof(service), "%d", srv->port);
- hints.ai_family = AF_INET;
- hints.ai_socktype = SOCK_STREAM;
-
- /* Setup address */
- if (getaddrinfo(srv->host, service, &hints, &addrs))
- error("Error getting xmpp address info");
-
- if ((sock = socket(addrs->ai_family,
- addrs->ai_socktype,
- addrs->ai_protocol)) < 0)
- error("Error opening xmpp socket");
-
- if ((flags = fcntl(sock, F_GETFL, 0)) < 0)
- error("Error getting xmpp socket flags");
-
- if (fcntl(sock, F_SETFL, flags|O_NONBLOCK) < 0)
- error("Error setting xmpp socket non-blocking");
-
- if (connect(sock, addrs->ai_addr, addrs->ai_addrlen) < 0)
- if (errno != EINPROGRESS)
- error("Error connecting socket");
-
- freeaddrinfo(addrs);
+ /* Net connect */
+ srv->net.recv = on_recv;
+ srv->net.data = srv;
+ net_open(&srv->net, srv->host, srv->port);
/* Setup Expat */
if (!(srv->expat = XML_ParserCreate(NULL)))
XML_SetCharacterDataHandler(srv->expat, on_data);
/* Setup server */
- srv->state = XMPP_STREAM;
- poll_add(&srv->poll, sock, on_poll, srv);
+ srv->state = XMPP_SEND_STREAM;
}
/* XMPP functions */