From caebeeca953239f345bdaf41694ca735e4dab891 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Fri, 25 Mar 2016 21:03:17 -0500 Subject: [PATCH] wsockd: add skeleton for future websockets helper (ref #78) --- .gitignore | 1 + Makefile.am | 1 + configure.ac | 1 + wsockd/Makefile.am | 7 + wsockd/wsockd.c | 432 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 442 insertions(+) create mode 100644 wsockd/Makefile.am create mode 100644 wsockd/wsockd.c diff --git a/.gitignore b/.gitignore index 35560150..5166a02e 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,7 @@ ircd/ircd_lexer.c ircd/version.c ircd/version.c.last ssld/ssld +wsockd/wsockd tools/charybdis-mkpasswd tools/genssl tools/mkpasswd diff --git a/Makefile.am b/Makefile.am index 7463c147..85513d2c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -9,6 +9,7 @@ endif SUBDIRS += ircd \ ssld \ + wsockd \ authd \ bandb \ tools \ diff --git a/configure.ac b/configure.ac index a3678625..956ad882 100644 --- a/configure.ac +++ b/configure.ac @@ -656,6 +656,7 @@ AC_CONFIG_FILES( \ authd/Makefile \ bandb/Makefile \ ssld/Makefile \ + wsockd/Makefile \ extensions/Makefile \ ircd/Makefile \ modules/Makefile \ diff --git a/wsockd/Makefile.am b/wsockd/Makefile.am new file mode 100644 index 00000000..d4524698 --- /dev/null +++ b/wsockd/Makefile.am @@ -0,0 +1,7 @@ +pkglibexec_PROGRAMS = wsockd +AM_CFLAGS=$(WARNFLAGS) +AM_CPPFLAGS = -I../include -I../librb/include + + +wsockd_SOURCES = wsockd.c +wsockd_LDADD = ../librb/src/librb.la diff --git a/wsockd/wsockd.c b/wsockd/wsockd.c new file mode 100644 index 00000000..5c5262cc --- /dev/null +++ b/wsockd/wsockd.c @@ -0,0 +1,432 @@ +/* + * wsockd.c: charybdis websockets helper + * Copyright (C) 2007 Aaron Sethman + * Copyright (C) 2007 ircd-ratbox development team + * Copyright (C) 2016 William Pitcock + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include "stdinc.h" + +#define MAXPASSFD 4 +#ifndef READBUF_SIZE +#define READBUF_SIZE 16384 +#endif + +static void setup_signals(void); +static pid_t ppid; + +static inline uint32_t +buf_to_uint32(uint8_t *buf) +{ + uint32_t x; + memcpy(&x, buf, sizeof(x)); + return x; +} + +static inline void +uint32_to_buf(uint8_t *buf, uint32_t x) +{ + memcpy(buf, &x, sizeof(x)); + return; +} + +typedef struct _mod_ctl_buf +{ + rb_dlink_node node; + uint8_t *buf; + size_t buflen; + rb_fde_t *F[MAXPASSFD]; + int nfds; +} mod_ctl_buf_t; + +typedef struct _mod_ctl +{ + rb_dlink_node node; + int cli_count; + rb_fde_t *F; + rb_fde_t *F_pipe; + rb_dlink_list readq; + rb_dlink_list writeq; +} mod_ctl_t; + +static mod_ctl_t *mod_ctl; + +typedef struct _conn +{ + rb_dlink_node node; + mod_ctl_t *ctl; + rawbuf_head_t *modbuf_out; + rawbuf_head_t *plainbuf_out; + + uint32_t id; + + rb_fde_t *mod_fd; + rb_fde_t *plain_fd; + uint64_t mod_out; + uint64_t mod_in; + uint64_t plain_in; + uint64_t plain_out; + uint8_t flags; + void *stream; +} conn_t; + +#define FLAG_CORK 0x01 +#define FLAG_DEAD 0x02 +#define FLAG_WSOCK 0x04 + +#define IsCork(x) ((x)->flags & FLAG_CORK) +#define IsDead(x) ((x)->flags & FLAG_DEAD) +#define IsWS(x) ((x)->flags & FLAG_WSOCK) + +#define SetCork(x) ((x)->flags |= FLAG_CORK) +#define SetDead(x) ((x)->flags |= FLAG_DEAD) +#define SetWS(x) ((x)->flags |= FLAG_WSOCK) + +#define ClearCork(x) ((x)->flags &= ~FLAG_CORK) +#define ClearDead(x) ((x)->flags &= ~FLAG_DEAD) +#define ClearWS(x) ((x)->flags &= ~FLAG_WSOCK) + +#define NO_WAIT 0x0 +#define WAIT_PLAIN 0x1 + +#define HASH_WALK_SAFE(i, max, ptr, next, table) for(i = 0; i < max; i++) { RB_DLINK_FOREACH_SAFE(ptr, next, table[i].head) +#define HASH_WALK_END } +#define CONN_HASH_SIZE 2000 +#define connid_hash(x) (&connid_hash_table[(x % CONN_HASH_SIZE)]) + +static rb_dlink_list connid_hash_table[CONN_HASH_SIZE]; +static rb_dlink_list dead_list; + +#ifndef _WIN32 +static void +dummy_handler(int sig) +{ + return; +} +#endif + +static void +setup_signals() +{ +#ifndef _WIN32 + struct sigaction act; + + act.sa_flags = 0; + act.sa_handler = SIG_IGN; + sigemptyset(&act.sa_mask); + sigaddset(&act.sa_mask, SIGPIPE); + sigaddset(&act.sa_mask, SIGALRM); +#ifdef SIGTRAP + sigaddset(&act.sa_mask, SIGTRAP); +#endif + +#ifdef SIGWINCH + sigaddset(&act.sa_mask, SIGWINCH); + sigaction(SIGWINCH, &act, 0); +#endif + sigaction(SIGPIPE, &act, 0); +#ifdef SIGTRAP + sigaction(SIGTRAP, &act, 0); +#endif + + act.sa_handler = dummy_handler; + sigaction(SIGALRM, &act, 0); +#endif +} + +static int +maxconn(void) +{ +#if defined(RLIMIT_NOFILE) && defined(HAVE_SYS_RESOURCE_H) + struct rlimit limit; + + if(!getrlimit(RLIMIT_NOFILE, &limit)) + { + return limit.rlim_cur; + } +#endif /* RLIMIT_FD_MAX */ + return MAXCONNECTIONS; +} + +static conn_t * +conn_find_by_id(uint32_t id) +{ + rb_dlink_node *ptr; + conn_t *conn; + + RB_DLINK_FOREACH(ptr, (connid_hash(id))->head) + { + conn = ptr->data; + if(conn->id == id && !IsDead(conn)) + return conn; + } + return NULL; +} + +static void +conn_add_id_hash(conn_t * conn, uint32_t id) +{ + conn->id = id; + rb_dlinkAdd(conn, &conn->node, connid_hash(id)); +} + +static void +free_conn(conn_t * conn) +{ + rb_free_rawbuffer(conn->modbuf_out); + rb_free_rawbuffer(conn->plainbuf_out); + rb_free(conn); +} + +static void +clean_dead_conns(void *unused) +{ + conn_t *conn; + rb_dlink_node *ptr, *next; + + RB_DLINK_FOREACH_SAFE(ptr, next, dead_list.head) + { + conn = ptr->data; + free_conn(conn); + } + + dead_list.tail = dead_list.head = NULL; +} + +static conn_t * +make_conn(mod_ctl_t * ctl, rb_fde_t *mod_fd, rb_fde_t *plain_fd) +{ + 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; + conn->stream = NULL; + rb_set_nb(mod_fd); + rb_set_nb(plain_fd); + return conn; +} + +static void +cleanup_bad_message(mod_ctl_t * ctl, mod_ctl_buf_t * ctlb) +{ + int i; + + /* XXX should log this somehow */ + for (i = 0; i < ctlb->nfds; i++) + rb_close(ctlb->F[i]); +} + +static void +wsock_process_accept(mod_ctl_t * ctl, mod_ctl_buf_t * ctlb) +{ + conn_t *conn; + uint32_t id; + + conn = make_conn(ctl, ctlb->F[0], ctlb->F[1]); + + id = buf_to_uint32(&ctlb->buf[1]); + conn_add_id_hash(conn, id); + SetWS(conn); + + if(rb_get_type(conn->mod_fd) & RB_FD_UNKNOWN) + rb_set_type(conn->mod_fd, RB_FD_SOCKET); + + if(rb_get_type(conn->plain_fd) == RB_FD_UNKNOWN) + rb_set_type(conn->plain_fd, RB_FD_SOCKET); + + // XXX todo +} + +static void +mod_process_cmd_recv(mod_ctl_t * ctl) +{ + rb_dlink_node *ptr, *next; + mod_ctl_buf_t *ctl_buf; + + RB_DLINK_FOREACH_SAFE(ptr, next, ctl->readq.head) + { + ctl_buf = ptr->data; + + switch (*ctl_buf->buf) + { + case 'A': + { + if (ctl_buf->nfds != 2 || ctl_buf->buflen != 5) + { + cleanup_bad_message(ctl, ctl_buf); + break; + } + wsock_process_accept(ctl, ctl_buf); + break; + } + default: + break; + /* Log unknown commands */ + } + rb_dlinkDelete(ptr, &ctl->readq); + rb_free(ctl_buf->buf); + rb_free(ctl_buf); + } + +} + +static void +mod_read_ctl(rb_fde_t *F, void *data) +{ + mod_ctl_buf_t *ctl_buf; + mod_ctl_t *ctl = data; + int retlen; + int i; + + do + { + ctl_buf = rb_malloc(sizeof(mod_ctl_buf_t)); + ctl_buf->buf = rb_malloc(READBUF_SIZE); + ctl_buf->buflen = READBUF_SIZE; + retlen = rb_recv_fd_buf(ctl->F, ctl_buf->buf, ctl_buf->buflen, ctl_buf->F, + MAXPASSFD); + if(retlen <= 0) + { + rb_free(ctl_buf->buf); + rb_free(ctl_buf); + } + else + { + ctl_buf->buflen = retlen; + rb_dlinkAddTail(ctl_buf, &ctl_buf->node, &ctl->readq); + for (i = 0; i < MAXPASSFD && ctl_buf->F[i] != NULL; i++) + ; + ctl_buf->nfds = i; + } + } + while(retlen > 0); + + if(retlen == 0 || (retlen < 0 && !rb_ignore_errno(errno))) + exit(0); + + mod_process_cmd_recv(ctl); + rb_setselect(ctl->F, RB_SELECT_READ, mod_read_ctl, ctl); +} + +static void +mod_write_ctl(rb_fde_t *F, void *data) +{ + mod_ctl_t *ctl = data; + mod_ctl_buf_t *ctl_buf; + rb_dlink_node *ptr, *next; + int retlen, x; + + RB_DLINK_FOREACH_SAFE(ptr, next, ctl->writeq.head) + { + ctl_buf = ptr->data; + retlen = rb_send_fd_buf(ctl->F, ctl_buf->F, ctl_buf->nfds, ctl_buf->buf, + ctl_buf->buflen, ppid); + if(retlen > 0) + { + rb_dlinkDelete(ptr, &ctl->writeq); + for(x = 0; x < ctl_buf->nfds; x++) + rb_close(ctl_buf->F[x]); + rb_free(ctl_buf->buf); + rb_free(ctl_buf); + + } + if(retlen == 0 || (retlen < 0 && !rb_ignore_errno(errno))) + exit(0); + + } + if(rb_dlink_list_length(&ctl->writeq) > 0) + rb_setselect(ctl->F, RB_SELECT_WRITE, mod_write_ctl, ctl); +} + +static void +read_pipe_ctl(rb_fde_t *F, void *data) +{ + char inbuf[READBUF_SIZE]; + int retlen; + while((retlen = rb_read(F, inbuf, sizeof(inbuf))) > 0) + { + ;; /* we don't do anything with the pipe really, just care if the other process dies.. */ + } + if(retlen == 0 || (retlen < 0 && !rb_ignore_errno(errno))) + exit(0); + rb_setselect(F, RB_SELECT_READ, read_pipe_ctl, NULL); +} + +int +main(int argc, char **argv) +{ + const char *s_ctlfd, *s_pipe, *s_pid; + int ctlfd, pipefd, x, maxfd; + maxfd = maxconn(); + + s_ctlfd = getenv("CTL_FD"); + s_pipe = getenv("CTL_PIPE"); + s_pid = getenv("CTL_PPID"); + + if(s_ctlfd == NULL || s_pipe == NULL || s_pid == NULL) + { + fprintf(stderr, + "This is the charybdis wsockd for internal ircd use.\n"); + fprintf(stderr, + "You aren't supposed to run me directly. Exiting.\n"); + exit(1); + } + + ctlfd = atoi(s_ctlfd); + pipefd = atoi(s_pipe); + ppid = atoi(s_pid); + x = 0; +#ifndef _WIN32 + for(x = 0; x < maxfd; x++) + { + if(x != ctlfd && x != pipefd && x > 2) + close(x); + } + x = open("/dev/null", O_RDWR); + + if(x >= 0) + { + if(ctlfd != 0 && pipefd != 0) + dup2(x, 0); + if(ctlfd != 1 && pipefd != 1) + dup2(x, 1); + if(ctlfd != 2 && pipefd != 2) + dup2(x, 2); + if(x > 2) + close(x); + } +#endif + setup_signals(); + rb_lib_init(NULL, NULL, NULL, 0, maxfd, 1024, 4096); + rb_init_rawbuffers(1024); + + mod_ctl = rb_malloc(sizeof(mod_ctl_t)); + mod_ctl->F = rb_open(ctlfd, RB_FD_SOCKET, "ircd control socket"); + mod_ctl->F_pipe = rb_open(pipefd, RB_FD_PIPE, "ircd pipe"); + rb_set_nb(mod_ctl->F); + rb_set_nb(mod_ctl->F_pipe); + rb_event_addish("clean_dead_conns", clean_dead_conns, NULL, 10); + read_pipe_ctl(mod_ctl->F_pipe, NULL); + mod_read_ctl(mod_ctl->F, mod_ctl); + + rb_lib_loop(0); + return 0; +}