*/
#include "stdinc.h"
+#include "sha1.h"
#define MAXPASSFD 4
#ifndef READBUF_SIZE
#define READBUF_SIZE 16384
#endif
+#define WEBSOCKET_SERVER_KEY "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+#define WEBSOCKET_ANSWER_STRING_1 "HTTP/1.1 101 Switching Protocols\r\nAccess-Control-Allow-Origin: *\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "
+#define WEBSOCKET_ANSWER_STRING_2 "\r\n\r\n"
+
static void setup_signals(void);
static pid_t ppid;
{
rb_dlink_node node;
mod_ctl_t *ctl;
+
rawbuf_head_t *modbuf_out;
- rawbuf_head_t *plainbuf_out;
+ rawbuf_head_t *modbuf_in;
+
+ buf_head_t plainbuf_out;
+ buf_head_t plainbuf_in;
uint32_t id;
uint64_t plain_in;
uint64_t plain_out;
uint8_t flags;
+
+ char client_key[37]; /* maximum 36 bytes + nul */
} conn_t;
+#define WEBSOCKET_OPCODE_CONTINUATION_FRAME 0
+#define WEBSOCKET_OPCODE_TEXT_FRAME 1
+#define WEBSOCKET_OPCODE_BINARY_FRAME 2
+#define WEBSOCKET_OPCODE_CLOSE_FRAME 8
+#define WEBSOCKET_OPCODE_PING_FRAME 9
+#define WEBSOCKET_OPCODE_PONG_FRAME 10
+
+#define WEBSOCKET_MASK_LENGTH 4
+
+#define WEBSOCKET_MAX_UNEXTENDED_PAYLOAD_DATA_LENGTH 125
+
+typedef struct {
+ uint8_t opcode_rsv_fin; // opcode: 4, rsv1: 1, rsv2: 1, rsv3: 1, fin: 1
+ uint8_t payload_length_mask; // payload_length: 7, mask: 1
+} ws_frame_hdr_t;
+
+typedef struct {
+ ws_frame_hdr_t header;
+ uint8_t payload_data[WEBSOCKET_MAX_UNEXTENDED_PAYLOAD_DATA_LENGTH];
+} ws_frame_payload_t;
+
+typedef struct {
+ ws_frame_hdr_t header;
+} ws_frame_t;
+
+typedef struct {
+ ws_frame_hdr_t header;
+ uint16_t payload_length_extended;
+} ws_frame_ext_t;
+
+typedef struct {
+ ws_frame_hdr_t header;
+ uint64_t payload_length_extended;
+} ws_frame_ext2_t;
+
+static inline int
+ws_frame_get_opcode(ws_frame_hdr_t *header)
+{
+ return header->opcode_rsv_fin & 0xF;
+}
+
+static inline void
+ws_frame_set_opcode(ws_frame_hdr_t *header, int opcode)
+{
+ header->opcode_rsv_fin &= ~0xF;
+ header->opcode_rsv_fin |= opcode & 0xF;
+}
+
+static inline int
+ws_frame_get_fin(ws_frame_hdr_t *header)
+{
+ return (header->opcode_rsv_fin >> 7) & 0x1;
+}
+
+static inline void
+ws_frame_set_fin(ws_frame_hdr_t *header, int fin)
+{
+ header->opcode_rsv_fin &= ~(0x1 << 7);
+ header->opcode_rsv_fin |= (fin << 7) & (0x1 << 7);
+}
+
+#ifdef _WIN32
+char *
+strcasestr(const char *s, const char *find)
+{
+ char c, sc;
+ size_t len;
+
+ if ((c = *find++) != 0) {
+ c = tolower((unsigned char)c);
+ len = strlen(find);
+ do {
+ do {
+ if ((sc = *s++) == 0)
+ return (NULL);
+ } while ((char)tolower((unsigned char)sc) != c);
+ } while (strnicmp(s, find, len) != 0);
+ s--;
+ }
+ return ((char *)s);
+}
+#endif
+
+static void close_conn(conn_t * conn, int wait_plain, const char *fmt, ...);
+static void conn_mod_read_cb(rb_fde_t *fd, void *data);
+static void conn_plain_read_cb(rb_fde_t *fd, void *data);
+static void conn_plain_process_recvq(conn_t *conn);
+
#define FLAG_CORK 0x01
#define FLAG_DEAD 0x02
#define FLAG_WSOCK 0x04
+#define FLAG_KEYED 0x08
#define IsCork(x) ((x)->flags & FLAG_CORK)
#define IsDead(x) ((x)->flags & FLAG_DEAD)
#define IsWS(x) ((x)->flags & FLAG_WSOCK)
+#define IsKeyed(x) ((x)->flags & FLAG_KEYED)
#define SetCork(x) ((x)->flags |= FLAG_CORK)
#define SetDead(x) ((x)->flags |= FLAG_DEAD)
#define SetWS(x) ((x)->flags |= FLAG_WSOCK)
+#define SetKeyed(x) ((x)->flags |= FLAG_KEYED)
#define ClearCork(x) ((x)->flags &= ~FLAG_CORK)
#define ClearDead(x) ((x)->flags &= ~FLAG_DEAD)
#define ClearWS(x) ((x)->flags &= ~FLAG_WSOCK)
+#define ClearKeyed(x) ((x)->flags &= ~FLAG_KEYED)
#define NO_WAIT 0x0
#define WAIT_PLAIN 0x1
#define CONN_HASH_SIZE 2000
#define connid_hash(x) (&connid_hash_table[(x % CONN_HASH_SIZE)])
+static const char *remote_closed = "Remote host closed the connection";
+
static rb_dlink_list connid_hash_table[CONN_HASH_SIZE];
static rb_dlink_list dead_list;
static void
free_conn(conn_t * conn)
{
+ rb_linebuf_donebuf(&conn->plainbuf_in);
+ rb_linebuf_donebuf(&conn->plainbuf_out);
+
+ rb_free_rawbuffer(conn->modbuf_in);
rb_free_rawbuffer(conn->modbuf_out);
- rb_free_rawbuffer(conn->plainbuf_out);
+
rb_free(conn);
}
dead_list.tail = dead_list.head = NULL;
}
+static void
+conn_mod_write_sendq(rb_fde_t *fd, void *data)
+{
+ conn_t *conn = data;
+ const char *err;
+ int retlen;
+
+ if(IsDead(conn))
+ return;
+
+ while((retlen = rb_rawbuf_flush(conn->modbuf_out, fd)) > 0)
+ conn->mod_out += retlen;
+
+ if(retlen == 0 || (retlen < 0 && !rb_ignore_errno(errno)))
+ {
+ if(retlen == 0)
+ close_conn(conn, WAIT_PLAIN, "%s", remote_closed);
+ err = strerror(errno);
+ close_conn(conn, WAIT_PLAIN, "Write error: %s", err);
+ return;
+ }
+
+ if(rb_rawbuf_length(conn->modbuf_out) > 0)
+ rb_setselect(conn->mod_fd, RB_SELECT_WRITE, conn_mod_write_sendq, conn);
+ else
+ rb_setselect(conn->mod_fd, RB_SELECT_WRITE, NULL, NULL);
+
+ if(IsCork(conn) && rb_rawbuf_length(conn->modbuf_out) == 0)
+ {
+ ClearCork(conn);
+ conn_plain_read_cb(conn->plain_fd, conn);
+ }
+}
+
+static void
+conn_mod_write(conn_t * conn, void *data, size_t len)
+{
+ if(IsDead(conn)) /* no point in queueing to a dead man */
+ return;
+ rb_rawbuf_append(conn->modbuf_out, data, len);
+}
+
+static void
+conn_mod_write_short_frame(conn_t * conn, void *data, int len)
+{
+ ws_frame_hdr_t hdr;
+
+ ws_frame_set_opcode(&hdr, WEBSOCKET_OPCODE_BINARY_FRAME);
+ hdr.payload_length_mask = (len + 2) & 0x7f;
+
+ conn_mod_write(conn, &hdr, sizeof(hdr));
+ conn_mod_write(conn, data, len);
+ conn_mod_write(conn, "\r\n", 2);
+}
+
+static void
+conn_mod_write_long_frame(conn_t * conn, void *data, int len)
+{
+ ws_frame_ext_t hdr;
+
+ ws_frame_set_opcode(&hdr.header, WEBSOCKET_OPCODE_BINARY_FRAME);
+ hdr.header.payload_length_mask = 126;
+ hdr.payload_length_extended = htons(len + 2);
+
+ conn_mod_write(conn, &hdr, sizeof(hdr));
+ conn_mod_write(conn, data, len);
+ conn_mod_write(conn, "\r\n", 2);
+}
+
+static void
+conn_mod_write_frame(conn_t *conn, void *data, int len)
+{
+ if(IsDead(conn)) /* no point in queueing to a dead man */
+ return;
+
+ if (len < 123)
+ return conn_mod_write_short_frame(conn, data, len);
+
+ return conn_mod_write_long_frame(conn, data, len);
+}
+
+static void
+conn_plain_write(conn_t * conn, void *data, size_t len)
+{
+ if(IsDead(conn)) /* again no point in queueing to dead men */
+ return;
+ rb_linebuf_put(&conn->plainbuf_out, data, len);
+}
+
static void
mod_write_ctl(rb_fde_t *F, void *data)
{
if(IsDead(conn))
return;
+ if (IsKeyed(conn))
+ conn_plain_process_recvq(conn);
+
rb_rawbuf_flush(conn->modbuf_out, conn->mod_fd);
- rb_rawbuf_flush(conn->plainbuf_out, conn->plain_fd);
+ rb_linebuf_flush(conn->plain_fd, &conn->plainbuf_out);
rb_close(conn->mod_fd);
SetDead(conn);
{
conn_t *conn = rb_malloc(sizeof(conn_t));
conn->ctl = ctl;
- conn->modbuf_out = rb_new_rawbuffer();
- conn->plainbuf_out = rb_new_rawbuffer();
conn->mod_fd = mod_fd;
conn->plain_fd = plain_fd;
conn->id = -1;
rb_set_nb(mod_fd);
rb_set_nb(plain_fd);
+
+ rb_linebuf_newbuf(&conn->plainbuf_in);
+ rb_linebuf_newbuf(&conn->plainbuf_out);
+
+ conn->modbuf_in = rb_new_rawbuffer();
+ conn->modbuf_out = rb_new_rawbuffer();
+
return conn;
}
}
static void
-conn_mod_handshake_cb(rb_fde_t *fd, void *data)
+conn_mod_handshake_process(conn_t *conn)
{
char inbuf[READBUF_SIZE];
+
+ memset(inbuf, 0, sizeof inbuf);
+
+ while (1)
+ {
+ char *p = NULL;
+
+ int dolen = rb_rawbuf_get(conn->modbuf_in, inbuf, sizeof inbuf);
+ if (!dolen)
+ break;
+
+ if ((p = strcasestr(inbuf, "Sec-WebSocket-Key:")) != NULL)
+ {
+ char *start, *end;
+
+ start = p + strlen("Sec-WebSocket-Key:");
+
+ for (; start < (inbuf + READBUF_SIZE) && *start; start++)
+ {
+ if (*start != ' ' && *start != '\t')
+ break;
+ }
+
+ for (end = start; end < (inbuf + READBUF_SIZE) && *end; end++)
+ {
+ if (*end == '\r' || *end == '\n')
+ {
+ *end = '\0';
+ break;
+ }
+ }
+
+ rb_strlcpy(conn->client_key, start, sizeof(conn->client_key));
+ SetKeyed(conn);
+ }
+ }
+
+ if (IsKeyed(conn))
+ {
+ SHA1 sha1;
+ uint8_t digest[SHA1_DIGEST_LENGTH];
+ char *resp;
+
+ sha1_init(&sha1);
+ sha1_update(&sha1, (uint8_t *) conn->client_key, strlen(conn->client_key));
+ sha1_update(&sha1, (uint8_t *) WEBSOCKET_SERVER_KEY, strlen(WEBSOCKET_SERVER_KEY));
+ sha1_final(&sha1, digest);
+
+ resp = (char *) rb_base64_encode(digest, SHA1_DIGEST_LENGTH);
+
+ conn_mod_write(conn, WEBSOCKET_ANSWER_STRING_1, strlen(WEBSOCKET_ANSWER_STRING_1));
+ conn_mod_write(conn, resp, strlen(resp));
+ conn_mod_write(conn, WEBSOCKET_ANSWER_STRING_2, strlen(WEBSOCKET_ANSWER_STRING_2));
+
+ rb_free(resp);
+ }
+
+ conn_mod_write_sendq(conn->mod_fd, conn);
+}
+
+static void
+conn_mod_read_cb(rb_fde_t *fd, void *data)
+{
+ char inbuf[READBUF_SIZE];
+
+ memset(inbuf, 0, sizeof inbuf);
+
conn_t *conn = data;
int length = 0;
if (conn == NULL)
if (IsDead(conn))
return;
- length = rb_read(conn->plain_fd, inbuf, sizeof(inbuf));
- if (length == 0 || (length < 0 && !rb_ignore_errno(errno)))
+ length = rb_read(fd, inbuf, sizeof(inbuf));
+
+ if (length < 0)
+ {
+ if (rb_ignore_errno(errno))
+ rb_setselect(fd, RB_SELECT_READ, conn_mod_read_cb, conn);
+ else
+ close_conn(conn, NO_WAIT, "Connection closed");
+
+ return;
+ }
+ else if (length == 0)
{
close_conn(conn, NO_WAIT, "Connection closed");
return;
}
+
+ rb_rawbuf_append(conn->modbuf_in, inbuf, length);
+ if (!IsKeyed(conn))
+ conn_mod_handshake_process(conn);
+
+ if (length < sizeof(inbuf))
+ {
+ rb_setselect(fd, RB_SELECT_READ, conn_mod_read_cb, conn);
+ return;
+ }
}
}
+static bool
+plain_check_cork(conn_t * conn)
+{
+ if(rb_rawbuf_length(conn->modbuf_out) >= 4096)
+ {
+ /* if we have over 4k pending outbound, don't read until
+ * we've cleared the queue */
+ SetCork(conn);
+ rb_setselect(conn->plain_fd, RB_SELECT_READ, NULL, NULL);
+ /* try to write */
+ if (IsKeyed(conn))
+ conn_mod_write_sendq(conn->mod_fd, conn);
+ return true;
+ }
+
+ return false;
+}
+
static void
-conn_mod_read_cb(rb_fde_t *fd, void *data)
+conn_plain_process_recvq(conn_t *conn)
{
+ char inbuf[READBUF_SIZE];
+
+ memset(inbuf, 0, sizeof inbuf);
+
+ while (1)
+ {
+ int dolen = rb_linebuf_get(&conn->plainbuf_in, inbuf, sizeof inbuf, LINEBUF_COMPLETE, LINEBUF_PARSED);
+ if (!dolen)
+ break;
+
+ conn_mod_write_frame(conn, inbuf, dolen);
+ }
+
+ if (IsKeyed(conn))
+ conn_mod_write_sendq(conn->mod_fd, conn);
}
static void
conn_plain_read_cb(rb_fde_t *fd, void *data)
{
+ char inbuf[READBUF_SIZE];
+
+ memset(inbuf, 0, sizeof inbuf);
+
+ conn_t *conn = data;
+ int length = 0;
+ if(conn == NULL)
+ return;
+
+ if(IsDead(conn))
+ return;
+
+ if(plain_check_cork(conn))
+ return;
+
+ while(1)
+ {
+ if(IsDead(conn))
+ return;
+
+ length = rb_read(conn->plain_fd, inbuf, sizeof(inbuf));
+
+ if(length == 0 || (length < 0 && !rb_ignore_errno(errno)))
+ {
+ close_conn(conn, NO_WAIT, NULL);
+ return;
+ }
+
+ if(length < 0)
+ {
+ rb_setselect(conn->plain_fd, RB_SELECT_READ, conn_plain_read_cb, conn);
+ if (IsKeyed(conn))
+ conn_plain_process_recvq(conn);
+ return;
+ }
+ conn->plain_in += length;
+
+ (void) rb_linebuf_parse(&conn->plainbuf_in, inbuf, length, 0);
+
+ if(IsDead(conn))
+ return;
+ if(plain_check_cork(conn))
+ return;
+ }
}
static void
if(rb_get_type(conn->plain_fd) == RB_FD_UNKNOWN)
rb_set_type(conn->plain_fd, RB_FD_SOCKET);
- conn_mod_handshake_cb(conn->mod_fd, conn);
+ conn_mod_read_cb(conn->mod_fd, conn);
+ conn_plain_read_cb(conn->plain_fd, conn);
}
static void
#endif
setup_signals();
rb_lib_init(NULL, NULL, NULL, 0, maxfd, 1024, 4096);
- rb_init_rawbuffers(1024);
+ rb_linebuf_init(4096);
+ rb_init_rawbuffers(4096);
mod_ctl = rb_malloc(sizeof(mod_ctl_t));
mod_ctl->F = rb_open(ctlfd, RB_FD_SOCKET, "ircd control socket");