diff --git a/eggdrop-basic.conf b/eggdrop-basic.conf index 9deb7cadb..46ae96437 100755 --- a/eggdrop-basic.conf +++ b/eggdrop-basic.conf @@ -37,6 +37,7 @@ loadmodule console ; # Console setting storage loadmodule uptime ; # Centralized uptime stat collection (https://www.eggheads.org/uptime/) #loadmodule ident ; # Ident support #loadmodule twitch ; # Twitch gaming service support +#loadmodule webui ; # WebUI support ##### BASIC SETTINGS ##### diff --git a/eggdrop.conf b/eggdrop.conf index 8ab6a1a72..325f5f376 100755 --- a/eggdrop.conf +++ b/eggdrop.conf @@ -1664,6 +1664,13 @@ loadmodule uptime # Set the ident port to use for ident-method 1. #set ident-port 113 + +#### WEBUI MODULE #### + +#loadmodule webui + +#listen +8080 webui + ##### AUTOSCRIPTS ##### # Load this script to enable the autoscripts functionality for Eggdrop. diff --git a/src/Makefile.in b/src/Makefile.in index 21e359131..0fcd3a374 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -212,7 +212,8 @@ tcluser.o: tcluser.c main.h ../config.h ../eggint.h ../lush.h lang.h \ tls.o: tls.c main.h ../config.h ../eggint.h ../lush.h lang.h eggdrop.h \ compat/in6.h flags.h proto.h misc_file.h cmdt.h tclegg.h tclhash.h \ chan.h users.h compat/compat.h compat/base64.h compat/inet_aton.h \ - ../src/main.h compat/snprintf.h compat/explicit_bzero.h compat/strlcpy.h + ../src/main.h compat/snprintf.h compat/explicit_bzero.h compat/strlcpy.h \ + version.h userent.o: userent.c main.h ../config.h ../eggint.h ../lush.h lang.h \ eggdrop.h compat/in6.h flags.h proto.h misc_file.h cmdt.h tclegg.h \ tclhash.h chan.h users.h compat/compat.h compat/base64.h \ diff --git a/src/dcc.c b/src/dcc.c index 3ce979ee5..8f4e62b3e 100644 --- a/src/dcc.c +++ b/src/dcc.c @@ -736,6 +736,8 @@ static void dcc_chat_pass(int idx, char *buf, int atr) /* Turn echo back on for telnet sessions (send IAC WON'T ECHO). */ if (dcc[idx].status & STAT_TELNET) tputs(dcc[idx].sock, TLN_IAC_C TLN_WONT_C TLN_ECHO_C "\n", 4); + else if (dcc[idx].status & STAT_WS) + tputs(dcc[idx].sock, WS_ECHO_ON, 1); dcc_chatter(idx); } } else { @@ -1296,11 +1298,18 @@ static void dcc_telnet(int idx, char *buf, int i) */ if (!(tls_vfyclients & TLS_VERIFYCN)) threaddata()->socklist[findsock(sock)].flags |= SOCK_VIRTUAL; - else if (ssl_handshake(dcc[i].sock, TLS_LISTEN, tls_vfyclients, - LOG_MISC, NULL, NULL)) { - killsock(dcc[i].sock); - lostdcc(i); - return; + else { +#ifdef TLS + if (!strcmp(dcc[idx].nick, "(webui)")) + ssl_cleanup(); /* reset ssl_ctx for websocket */ +#endif /* TLS */ + if (ssl_handshake(dcc[i].sock, TLS_LISTEN, + strcmp(dcc[idx].nick, "(webui)") ? tls_vfyclients : 0, + LOG_MISC, NULL, NULL)) { + killsock(dcc[i].sock); + lostdcc(i); + return; + } } } #endif @@ -1314,12 +1323,71 @@ static void dcc_telnet(int idx, char *buf, int i) dcc_dnshostbyip(&dcc[i].sockname); } +/* we need this for dcc_telnet_hostresolved() could now branch to DCC_TABLE + * and for either branch we need to continue here + */ +void dcc_telnet_hostresolved2(int i, int idx) { + int j, sock; + char *userhost = dcc[idx].host; /* TODO: writing host back to userhost looks like back and forth copying */ + /* Skip ident lookup if disabled */ + if (identtimeout <= 0) { + dcc[i].u.ident_sock = dcc[idx].sock; + dcc_telnet_got_ident(i, userhost); + return; + } + + changeover_dcc(i, &DCC_IDENTWAIT, 0); + dcc[i].timeval = now; + dcc[i].u.ident_sock = dcc[idx].sock; + sock = -1; + j = new_dcc(&DCC_IDENT, 0); + if (j < 0) + putlog(LOG_MISC, "*", DCC_IDENTFAIL, dcc[i].host, strerror(errno)); + else { + memcpy(&dcc[j].sockname, &dcc[i].sockname, sizeof(sockname_t)); + dcc[j].sock = getsock(dcc[j].sockname.family, 0); + if (dcc[j].sock >= 0) { + sockname_t name; + name.addrlen = sizeof(name.addr); + if (getsockname(dcc[i].sock, &name.addr.sa, &name.addrlen) < 0) + debug2("dcc: dcc_telnet_hostresolved(): getsockname() socket %ld error %s", dcc[i].sock, strerror(errno)); + setsnport(name, 0); + if (bind(dcc[j].sock, &name.addr.sa, name.addrlen) < 0) + debug2("dcc: dcc_telnet_hostresolved(): bind() socket %ld error %s", dcc[j].sock, strerror(errno)); + setsnport(dcc[j].sockname, 113); + if (connect(dcc[j].sock, &dcc[j].sockname.addr.sa, + dcc[j].sockname.addrlen) < 0 && (errno != EINPROGRESS)) { + killsock(dcc[j].sock); + lostdcc(j); + putlog(LOG_MISC, "*", DCC_IDENTFAIL, dcc[i].host, strerror(errno)); + j = 0; + } + sock = dcc[j].sock; + } + } + if (j < 0) { + dcc_telnet_got_ident(i, userhost); + return; + } + dcc[j].sock = sock; + dcc[j].port = 113; + dcc[j].addr = dcc[i].addr; + strcpy(dcc[j].host, dcc[i].host); + strcpy(dcc[j].nick, "*"); + dcc[j].u.ident_sock = dcc[i].sock; + dcc[j].timeval = now; +#ifdef CYGWIN_HACKS + threaddata()->socklist[findsock(dcc[j].sock)].flags = SOCK_CONNECT; +#endif + dprintf(j, "%d, %d\n", dcc[i].port, dcc[idx].port); +} + static void dcc_telnet_hostresolved(int i) { int idx; - int j = 0, sock; char s[sizeof lasttelnethost], *userhost; + debug0("dcc_telnet_hostresolved()"); strlcpy(dcc[i].host, dcc[i].u.dns->host, UHOSTLEN); for (idx = 0; idx < dcc_total; idx++) @@ -1378,57 +1446,16 @@ static void dcc_telnet_hostresolved(int i) return; } - /* Skip ident lookup if disabled */ - if (identtimeout <= 0) { - dcc[i].u.ident_sock = dcc[idx].sock; - dcc_telnet_got_ident(i, userhost); +#ifdef TLS + /* Skip ident lookup for webui http */ + if (!strcmp(dcc[idx].nick, "(webui)")) { + webui_dcc_telnet_hostresolved(i); return; } +#endif /* TLS */ - changeover_dcc(i, &DCC_IDENTWAIT, 0); - dcc[i].timeval = now; - dcc[i].u.ident_sock = dcc[idx].sock; - sock = -1; - j = new_dcc(&DCC_IDENT, 0); - if (j < 0) - putlog(LOG_MISC, "*", DCC_IDENTFAIL, dcc[i].host, strerror(errno)); - else { - memcpy(&dcc[j].sockname, &dcc[i].sockname, sizeof(sockname_t)); - dcc[j].sock = getsock(dcc[j].sockname.family, 0); - if (dcc[j].sock >= 0) { - sockname_t name; - name.addrlen = sizeof(name.addr); - if (getsockname(dcc[i].sock, &name.addr.sa, &name.addrlen) < 0) - debug2("dcc: dcc_telnet_hostresolved(): getsockname() socket %ld error %s", dcc[i].sock, strerror(errno)); - setsnport(name, 0); - if (bind(dcc[j].sock, &name.addr.sa, name.addrlen) < 0) - debug2("dcc: dcc_telnet_hostresolved(): bind() socket %ld error %s", dcc[j].sock, strerror(errno)); - setsnport(dcc[j].sockname, 113); - if (connect(dcc[j].sock, &dcc[j].sockname.addr.sa, - dcc[j].sockname.addrlen) < 0 && (errno != EINPROGRESS)) { - killsock(dcc[j].sock); - lostdcc(j); - putlog(LOG_MISC, "*", DCC_IDENTFAIL, dcc[i].host, strerror(errno)); - j = 0; - } - sock = dcc[j].sock; - } - } - if (j < 0) { - dcc_telnet_got_ident(i, userhost); - return; - } - dcc[j].sock = sock; - dcc[j].port = 113; - dcc[j].addr = dcc[i].addr; - strcpy(dcc[j].host, dcc[i].host); - strcpy(dcc[j].nick, "*"); - dcc[j].u.ident_sock = dcc[i].sock; - dcc[j].timeval = now; -#ifdef CYGWIN_HACKS - threaddata()->socklist[findsock(dcc[j].sock)].flags = SOCK_CONNECT; -#endif - dprintf(j, "%d, %d\n", dcc[i].port, dcc[idx].port); + strlcpy(dcc[i].host, userhost, UHOSTLEN); + dcc_telnet_hostresolved2(i, idx); } static void eof_dcc_telnet(int idx) @@ -1558,7 +1585,6 @@ static void dcc_telnet_id(int idx, char *buf, int atr) int ok = 0; struct flag_record fr = { FR_GLOBAL | FR_CHAN | FR_ANYWH, 0, 0, 0, 0, 0 }; struct dcc_table *old = dcc[idx].type; - if (detect_telnet((unsigned char *) buf)) { dcc[idx].status |= STAT_TELNET; strip_telnet(dcc[idx].sock, buf, &atr); @@ -1824,14 +1850,20 @@ static void dcc_telnet_pass(int idx, int atr) * */ - /* Turn off remote telnet echo (send IAC WILL ECHO). */ + /* Turn off remote telnet echo */ + char buf[512]; if (dcc[idx].status & STAT_TELNET) { - char buf[1030]; + /* For telnet sessions send IAC WILL ECHO */ egg_snprintf(buf, sizeof buf, "\n%s%s\r\n", escape_telnet(DCC_ENTERPASS), TLN_IAC_C TLN_WILL_C TLN_ECHO_C); tputs(dcc[idx].sock, buf, strlen(buf)); - } else + } else if (dcc[idx].status & STAT_WS) { + /* For webui sessions */ + snprintf(buf, sizeof buf, "\n%s" WS_ECHO_OFF "\n", DCC_ENTERPASS); + tputs(dcc[idx].sock, buf, strlen(buf)); + } else { dprintf(idx, "\n%s\n", DCC_ENTERPASS); + } } } @@ -2403,7 +2435,6 @@ static void dcc_telnet_got_ident(int i, char *host) /* Do not buffer data anymore. All received and stored data is passed * over to the dcc functions from now on. */ sockoptions(dcc[i].sock, EGG_OPTION_UNSET, SOCK_BUFFER); - dcc[i].type = &DCC_TELNET_ID; dcc[i].u.chat = get_data_ptr(sizeof(struct chat_info)); egg_bzero(dcc[i].u.chat, sizeof(struct chat_info)); @@ -2412,17 +2443,20 @@ static void dcc_telnet_got_ident(int i, char *host) * STATUS option as a hopefully harmless way to detect if the other * side is a telnet client or not. */ #ifdef TLS - if (!dcc[i].ssl) - dprintf(i, TLN_IAC_C TLN_WILL_C TLN_STATUS_C); + if (!dcc[i].ssl && strcmp(dcc[idx].nick, "(webui)")) + dprintf(i, TLN_IAC_C TLN_WILL_C TLN_STATUS_C); #endif - /* Copy acceptable-nick/host mask */ - dcc[i].status = STAT_TELNET | STAT_ECHO; - if (!strcmp(dcc[idx].nick, "(bots)")) - dcc[i].status |= STAT_BOTONLY; - if (!strcmp(dcc[idx].nick, "(users)")) - dcc[i].status |= STAT_USRONLY; - /* Copy acceptable-nick/host mask */ - strlcpy(dcc[i].nick, dcc[idx].host, HANDLEN); + /* Copy acceptable-nick/host mask */ + dcc[i].status = STAT_TELNET | STAT_ECHO; + if (!strcmp(dcc[idx].nick, "(users)")) + dcc[i].status |= STAT_USRONLY; + else if (!strcmp(dcc[idx].nick, "(bots)")) + dcc[i].status |= STAT_BOTONLY; + else if (!strcmp(dcc[idx].nick, "(webui)")) + dcc[i].status |= STAT_WS; + /* Copy acceptable-nick/host mask */ + strlcpy(dcc[i].nick, dcc[idx].host, HANDLEN); /* wo ist hier der sinn? dcc[idx].host ist immer *, oder? */ + dcc[i].timeval = now; strcpy(dcc[i].u.chat->con_chan, chanset ? chanset->dname : "*"); /* Displays a customizable banner. */ diff --git a/src/eggdrop.h b/src/eggdrop.h index 393de019d..f9139b3a4 100644 --- a/src/eggdrop.h +++ b/src/eggdrop.h @@ -491,6 +491,7 @@ struct dupwait_info { #define STAT_USRONLY 0x00040 /* telnet on users-only connect */ #define STAT_PAGE 0x00080 /* page output to the user */ #define STAT_SERV 0x00100 /* this is a server connection */ +#define STAT_WS 0x00200 /* webui websocket */ /* For stripping out mIRC codes. */ #define STRIP_COLOR 0x00001 /* remove mIRC color codes */ @@ -602,6 +603,7 @@ typedef struct { #define SOCK_VIRTUAL 0x0200 /* not-connected socket (dont read it!) */ #define SOCK_BUFFER 0x0400 /* buffer data; don't notify dcc funcs */ #define SOCK_TCL 0x0800 /* tcl socket, don't do anything on it */ +#define SOCK_WS 0x1000 /* webui websocket */ /* Flags to sock_has_data */ @@ -754,4 +756,7 @@ struct dns_thread_node { extern struct dns_thread_node *dns_thread_head; #endif +#define WS_ECHO_ON "\x01" /* echo on */ +#define WS_ECHO_OFF "\x02" /* echo off */ + #endif /* _EGG_EGGDROP_H */ diff --git a/src/main.c b/src/main.c index 42ddc61a6..20ee14d9e 100644 --- a/src/main.c +++ b/src/main.c @@ -158,10 +158,6 @@ unsigned long itraffic_unknown_today = 0; extern char last_bind_called[]; #endif -#ifdef TLS -int ssl_cleanup(); -#endif - void fatal(const char *s, int recoverable) { int i; diff --git a/src/mod/module.h b/src/mod/module.h index 41f74e5d1..0c62c2792 100644 --- a/src/mod/module.h +++ b/src/mod/module.h @@ -528,6 +528,8 @@ typedef void (*chanout_butfunc)(int, int, const char *, ...) ATTRIBUTE_FORMAT(pr /* 324 - 327 */ #define find_member_from_nick ((memberlist * (*) (char *))global[324]) #define get_user_from_member ((struct userrec * (*) (memberlist *))global[325]) +#define dcc_telnet_hostresolved2 ((void(*)(int, int))global[326]) +#define findsock ((int(*)(int))global[327]) /* hostmasking */ diff --git a/src/mod/modvals.h b/src/mod/modvals.h index a840e09cf..3cd0ff80b 100644 --- a/src/mod/modvals.h +++ b/src/mod/modvals.h @@ -23,40 +23,43 @@ #ifndef _EGG_MOD_MODVALS_H #define _EGG_MOD_MODVALS_H -/* #define HOOK_GET_FLAGREC 0 */ -/* #define HOOK_BUILD_FLAGREC 1 */ -/* #define HOOK_SET_FLAGREC 2 */ -#define HOOK_READ_USERFILE 3 -#define HOOK_REHASH 4 -#define HOOK_MINUTELY 5 -#define HOOK_DAILY 6 -#define HOOK_HOURLY 7 -#define HOOK_USERFILE 8 -#define HOOK_SECONDLY 9 -#define HOOK_PRE_REHASH 10 -#define HOOK_IDLE 11 -#define HOOK_5MINUTELY 12 -#define HOOK_LOADED 13 -#define HOOK_BACKUP 14 -#define HOOK_DIE 15 -#define HOOK_PRE_SELECT 16 -#define HOOK_POST_SELECT 17 +/* #define HOOK_GET_FLAGREC 0 */ +/* #define HOOK_BUILD_FLAGREC 1 */ +/* #define HOOK_SET_FLAGREC 2 */ +#define HOOK_READ_USERFILE 3 +#define HOOK_REHASH 4 +#define HOOK_MINUTELY 5 +#define HOOK_DAILY 6 +#define HOOK_HOURLY 7 +#define HOOK_USERFILE 8 +#define HOOK_SECONDLY 9 +#define HOOK_PRE_REHASH 10 +#define HOOK_IDLE 11 +#define HOOK_5MINUTELY 12 +#define HOOK_LOADED 13 +#define HOOK_BACKUP 14 +#define HOOK_DIE 15 +#define HOOK_PRE_SELECT 16 +#define HOOK_POST_SELECT 17 -#define REAL_HOOKS 18 +#define REAL_HOOKS 18 -#define HOOK_SHAREOUT 105 -#define HOOK_SHAREIN 106 -#define HOOK_ENCRYPT_PASS 107 -#define HOOK_QSERV 108 -#define HOOK_ADD_MODE 109 -#define HOOK_MATCH_NOTEREJ 110 -#define HOOK_RFC_CASECMP 111 -#define HOOK_DNS_HOSTBYIP 112 -#define HOOK_DNS_IPBYHOST 113 -#define HOOK_ENCRYPT_STRING 114 -#define HOOK_DECRYPT_STRING 115 -#define HOOK_ENCRYPT_PASS2 116 -#define HOOK_VERIFY_PASS2 117 +#define HOOK_SHAREOUT 105 +#define HOOK_SHAREIN 106 +#define HOOK_ENCRYPT_PASS 107 +#define HOOK_QSERV 108 +#define HOOK_ADD_MODE 109 +#define HOOK_MATCH_NOTEREJ 110 +#define HOOK_RFC_CASECMP 111 +#define HOOK_DNS_HOSTBYIP 112 +#define HOOK_DNS_IPBYHOST 113 +#define HOOK_ENCRYPT_STRING 114 +#define HOOK_DECRYPT_STRING 115 +#define HOOK_ENCRYPT_PASS2 116 +#define HOOK_VERIFY_PASS2 117 +#define HOOK_DCC_TELNET_HOSTRESOLVED 118 +#define HOOK_WEBUI_FRAME 119 +#define HOOK_WEBUI_UNFRAME 120 /* These are FIXED once they are in a release they STAY */ #define MODCALL_START 0 diff --git a/src/mod/webui.mod/Makefile b/src/mod/webui.mod/Makefile new file mode 100644 index 000000000..c890769fa --- /dev/null +++ b/src/mod/webui.mod/Makefile @@ -0,0 +1,46 @@ +# Makefile for src/mod/webui.mod/ + +srcdir = . + + +doofus: + @echo "" && \ + echo "Let's try this from the right directory..." && \ + echo "" && \ + cd ../../../ && $(MAKE) + +static: ../webui.o + +modules: ../../../webui.$(MOD_EXT) + +../webui.o: + $(CC) $(CFLAGS) $(CPPFLAGS) -DMAKING_MODS -c $(srcdir)/webui.c && mv -f webui.o ../ + +../../../webui.$(MOD_EXT): ../webui.o + $(LD) $(CFLAGS) -o ../../../webui.$(MOD_EXT) ../webui.o $(XLIBS) $(MODULE_XLIBS) && $(STRIP) ../../../webui.$(MOD_EXT) + +depend: + $(CC) $(CFLAGS) -MM $(srcdir)/webui.c -MT ../webui.o > .depend + +clean: + @rm -f .depend *.o *.$(MOD_EXT) *~ + +distclean: clean + +install: install-text + +install-text: + if test ! -f $(DEST)/text/webui.html; then \ + $(INSTALL_DATA) $(srcdir)/text/webui.html $(DEST)/text/; \ + fi + +#safety hash +../webui.o: .././webui.mod/webui.c ../../../src/mod/module.h \ + ../../../src/main.h ../../../config.h ../../../eggint.h ../../../lush.h \ + ../../../src/lang.h ../../../src/eggdrop.h ../../../src/compat/in6.h \ + ../../../src/flags.h ../../../src/cmdt.h ../../../src/tclegg.h \ + ../../../src/tclhash.h ../../../src/chan.h ../../../src/users.h \ + ../../../src/compat/compat.h ../../../src/compat/base64.h \ + ../../../src/compat/inet_aton.h ../../../src/compat/snprintf.h \ + ../../../src/compat/explicit_bzero.h ../../../src/compat/strlcpy.h \ + ../../../src/mod/modvals.h ../../../src/tandem.h ../../../src/version.h diff --git a/src/mod/webui.mod/text/webui.html b/src/mod/webui.mod/text/webui.html new file mode 100644 index 000000000..66f23812c --- /dev/null +++ b/src/mod/webui.mod/text/webui.html @@ -0,0 +1,46 @@ + + + + + + Eggdrop + + +

+    
+    
+  
+
diff --git a/src/mod/webui.mod/webui.c b/src/mod/webui.mod/webui.c
new file mode 100644
index 000000000..5137f7f52
--- /dev/null
+++ b/src/mod/webui.mod/webui.c
@@ -0,0 +1,501 @@
+/*
+ * webui.c -- part of webui.mod
+ */
+/*
+ * Copyright (C) 2023 - 2024 Michael Ortmann MIT License
+ * Copyright (C) 2024 Eggheads Development Team
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ */
+
+#include "src/mod/module.h"
+
+#ifdef TLS
+#define MODULE_NAME "webui"
+
+#include 
+#include 
+#include  /* base64 encode b64_ntop() and base64 decode b64_pton() */
+#include 
+#include 
+#include 
+#include 
+#include "src/version.h"
+
+
+#define WS_GUID   "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+#define WS_KEY    "Sec-WebSocket-Key:"
+#define WS_KEYLEN 24 /* key is padded, so its always 24 bytes */
+#define WS_LEN    28 /* length of Sec-WebSocket-Accept header field value
+                      * base64(len(sha1))
+                      * import math; (4 * math.ceil(20 / 3)) */
+
+static Function *global = NULL;
+
+/* 0x15 = TLS ContentType alert
+ * 0x0a = TLS Alert       unexpected_message
+ */
+static uint8_t alert[] = {0x15, 0x03, 0x01, 0x00, 0x02, 0x02, 0x0a};
+
+/* wget https://www.eggheads.org/favicon.ico
+ * xxd -i favicon.ico
+ */
+static unsigned char favicon_ico[] = {
+  0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x24, 0x00, 0x24, 0x00, 0xf7, 0x00,
+  0x00, 0x2e, 0x2c, 0x2d, 0x23, 0x22, 0x24, 0x31, 0x31, 0x32, 0x3c, 0x3b,
+  0x3c, 0x46, 0x41, 0x3f, 0x3a, 0x4a, 0x51, 0x3e, 0x5e, 0x71, 0x45, 0x44,
+  0x45, 0x49, 0x46, 0x47, 0x48, 0x47, 0x48, 0x4b, 0x4a, 0x4b, 0x44, 0x44,
+  0x49, 0x54, 0x53, 0x54, 0x54, 0x56, 0x58, 0x5c, 0x5b, 0x5c, 0x62, 0x60,
+  0x5e, 0x78, 0x68, 0x5f, 0x48, 0x65, 0x76, 0x46, 0x68, 0x7e, 0x57, 0x68,
+  0x76, 0x58, 0x69, 0x75, 0x63, 0x63, 0x65, 0x65, 0x66, 0x69, 0x6c, 0x6b,
+  0x6c, 0x70, 0x6c, 0x6c, 0x6f, 0x6d, 0x70, 0x6e, 0x71, 0x74, 0x6e, 0x76,
+  0x7e, 0x74, 0x73, 0x73, 0x74, 0x74, 0x78, 0x77, 0x78, 0x7b, 0x7b, 0x7b,
+  0x7c, 0x81, 0x6d, 0x62, 0x80, 0x7e, 0x7e, 0x98, 0x7f, 0x70, 0x85, 0x80,
+  0x7e, 0x1a, 0x5d, 0x82, 0x16, 0x6e, 0x9d, 0x22, 0x68, 0x8f, 0x3c, 0x67,
+  0x86, 0x38, 0x75, 0x97, 0x1d, 0x75, 0xa7, 0x10, 0x74, 0xac, 0x1c, 0x77,
+  0xa9, 0x09, 0x7e, 0xbc, 0x21, 0x76, 0xa9, 0x2b, 0x7f, 0xae, 0x24, 0x7b,
+  0xaa, 0x33, 0x7b, 0xa5, 0x49, 0x6d, 0x84, 0x57, 0x73, 0x86, 0x5a, 0x76,
+  0x87, 0x51, 0x79, 0x8e, 0x5b, 0x76, 0x8a, 0x4e, 0x7b, 0x95, 0x54, 0x7f,
+  0x97, 0x69, 0x79, 0x85, 0x74, 0x7c, 0x83, 0x7c, 0x7e, 0x80, 0x62, 0x7f,
+  0x91, 0x12, 0x80, 0xb9, 0x1f, 0x85, 0xb8, 0x34, 0x80, 0xa9, 0x22, 0x82,
+  0xb5, 0x2c, 0x85, 0xb5, 0x3f, 0x87, 0xb1, 0x7c, 0x80, 0x83, 0x65, 0x82,
+  0x98, 0x64, 0x88, 0x9c, 0x74, 0x88, 0x93, 0x47, 0x89, 0xad, 0x55, 0x89,
+  0xab, 0x44, 0x8d, 0xb7, 0x4a, 0x8c, 0xb4, 0x4c, 0x91, 0xb4, 0x5a, 0x95,
+  0xb9, 0x6a, 0x8d, 0xa0, 0x75, 0x92, 0xa2, 0x79, 0x93, 0xa3, 0x6a, 0x9c,
+  0xb5, 0x04, 0x81, 0xc4, 0x01, 0x86, 0xcc, 0x0a, 0x82, 0xc3, 0x84, 0x83,
+  0x84, 0x8a, 0x85, 0x85, 0x87, 0x88, 0x89, 0x8c, 0x8a, 0x8b, 0x80, 0x8e,
+  0x97, 0x90, 0x8e, 0x90, 0x85, 0x92, 0x9b, 0x94, 0x93, 0x94, 0x96, 0x96,
+  0x98, 0x95, 0x98, 0x9c, 0x9d, 0x9b, 0x9d, 0xa1, 0x90, 0x86, 0xa2, 0x9d,
+  0x9e, 0xa2, 0x9f, 0xa1, 0x9b, 0xa0, 0xa6, 0x8d, 0xa4, 0xb6, 0x91, 0xa6,
+  0xb3, 0xa2, 0xa2, 0xa3, 0xab, 0xa6, 0xa6, 0xa4, 0xa6, 0xaa, 0xa8, 0xa7,
+  0xa8, 0xa6, 0xaa, 0xaf, 0xac, 0xab, 0xac, 0xb8, 0xb1, 0xaf, 0xb4, 0xb3,
+  0xb4, 0xb9, 0xb7, 0xb7, 0xb5, 0xb7, 0xba, 0xb7, 0xb8, 0xb9, 0xbd, 0xbc,
+  0xbd, 0xc0, 0xbf, 0xbf, 0xc2, 0xc0, 0xbf, 0xb2, 0xc1, 0xcb, 0xc5, 0xc2,
+  0xc3, 0xca, 0xc5, 0xc5, 0xca, 0xc6, 0xc8, 0xcc, 0xcb, 0xcc, 0xd0, 0xcc,
+  0xcd, 0xd5, 0xd0, 0xcf, 0xcf, 0xcf, 0xd0, 0xd1, 0xcf, 0xd0, 0xcd, 0xd1,
+  0xd4, 0xcf, 0xd5, 0xd9, 0xd4, 0xd3, 0xd4, 0xdb, 0xd6, 0xd6, 0xda, 0xd8,
+  0xd7, 0xdd, 0xdc, 0xdd, 0xd3, 0xda, 0xdd, 0xe1, 0xdd, 0xdd, 0xe2, 0xe0,
+  0xdf, 0xde, 0xdf, 0xe0, 0xe0, 0xde, 0xe0, 0xdf, 0xe0, 0xe0, 0xe4, 0xe3,
+  0xe4, 0xe9, 0xe7, 0xe7, 0xe7, 0xe7, 0xe8, 0xe6, 0xe8, 0xea, 0xed, 0xec,
+  0xec, 0xf0, 0xee, 0xef, 0xf1, 0xf0, 0xef, 0xf4, 0xf3, 0xf3, 0xf8, 0xf7,
+  0xf7, 0xf8, 0xf8, 0xf7, 0xf8, 0xf7, 0xf8, 0xfe, 0xfe, 0xfe, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x21, 0xf9, 0x04, 0x01, 0x00, 0x00, 0x93, 0x00, 0x2c, 0x00, 0x00,
+  0x00, 0x00, 0x24, 0x00, 0x24, 0x00, 0x00, 0x08, 0xfe, 0x00, 0x27, 0x09,
+  0x1c, 0x48, 0xb0, 0xa0, 0x40, 0x0e, 0x02, 0xa7, 0x18, 0x5c, 0xc8, 0xb0,
+  0xa1, 0xc3, 0x87, 0x10, 0x09, 0xba, 0x59, 0x13, 0xb1, 0x62, 0xc1, 0x3a,
+  0x82, 0x1c, 0x39, 0x5a, 0xf4, 0xa7, 0x8e, 0x45, 0x88, 0x6f, 0x08, 0x09,
+  0x42, 0x24, 0xa9, 0xa4, 0x24, 0x48, 0x7d, 0x3e, 0x3a, 0xfc, 0x83, 0x88,
+  0x91, 0xc9, 0x97, 0x80, 0x54, 0x2e, 0xb4, 0x53, 0x08, 0x11, 0xa4, 0x97,
+  0x2f, 0xfd, 0xc8, 0x24, 0x38, 0xa7, 0x50, 0x46, 0x9c, 0x2f, 0x17, 0x51,
+  0xdc, 0x39, 0xc9, 0x8e, 0xa0, 0x45, 0x87, 0xd2, 0x60, 0x21, 0xd3, 0xe7,
+  0xa6, 0xa4, 0x47, 0x6f, 0xaa, 0xe8, 0xe8, 0x40, 0xf4, 0x0e, 0xa0, 0x3e,
+  0x65, 0xd6, 0xc4, 0x79, 0xb3, 0xa6, 0xce, 0x4d, 0x41, 0x73, 0xfa, 0xd8,
+  0xe9, 0x52, 0x95, 0xce, 0x97, 0x11, 0x17, 0x2c, 0x64, 0xe0, 0x30, 0xc5,
+  0xce, 0x20, 0x2b, 0x0d, 0x12, 0x28, 0x58, 0x20, 0x20, 0x80, 0xcc, 0x2f,
+  0x08, 0x04, 0x70, 0x58, 0x63, 0x27, 0xce, 0x14, 0x00, 0x07, 0x42, 0x0c,
+  0x58, 0x03, 0x08, 0xd1, 0x21, 0x37, 0x04, 0x54, 0x76, 0x28, 0x73, 0x41,
+  0x8b, 0xd3, 0x92, 0x75, 0x06, 0x38, 0x98, 0x82, 0x53, 0x61, 0xc5, 0x01,
+  0x00, 0x12, 0x24, 0x78, 0x60, 0xa7, 0xe4, 0x9f, 0x46, 0x25, 0xad, 0x00,
+  0xb0, 0x22, 0x29, 0x12, 0x1d, 0x47, 0x92, 0xb4, 0x1c, 0xa8, 0x28, 0x40,
+  0x81, 0x1a, 0x0f, 0x0d, 0xfe, 0x48, 0x5a, 0xc4, 0x20, 0x4d, 0xc9, 0x2e,
+  0x0b, 0x2e, 0x44, 0x6a, 0x54, 0x05, 0x90, 0xa4, 0x2e, 0x0e, 0x3e, 0xd2,
+  0x59, 0x83, 0xa1, 0x4f, 0xc9, 0x3e, 0x8b, 0x4e, 0x7a, 0x48, 0x53, 0xc1,
+  0x78, 0xa4, 0x93, 0x21, 0x64, 0xd2, 0xa1, 0x32, 0xe5, 0xd1, 0xcb, 0x36,
+  0x17, 0x16, 0x9d, 0xc9, 0x70, 0x08, 0x12, 0xa4, 0x38, 0x03, 0xa4, 0xfe,
+  0x03, 0x0a, 0x61, 0x05, 0x79, 0x52, 0x0b, 0x9d, 0x21, 0x91, 0xb1, 0xf0,
+  0xa1, 0xc3, 0x80, 0x06, 0x32, 0xe7, 0x20, 0x72, 0xb4, 0xe6, 0x83, 0x85,
+  0xc6, 0x87, 0x5e, 0x1e, 0xe2, 0x4a, 0x56, 0xba, 0x9f, 0x9b, 0x90, 0x2c,
+  0x82, 0x1a, 0x50, 0x92, 0x38, 0x12, 0xc7, 0x4e, 0x73, 0xdc, 0xe1, 0x12,
+  0x81, 0x2f, 0x25, 0x42, 0x54, 0x51, 0x7e, 0x30, 0xf8, 0x92, 0x1d, 0x0f,
+  0xc6, 0x81, 0x07, 0x49, 0x12, 0x22, 0xf2, 0xa0, 0x40, 0x70, 0xe4, 0xc7,
+  0xe0, 0x77, 0x1b, 0x0a, 0x74, 0x87, 0x23, 0x87, 0xf8, 0x71, 0x47, 0x1f,
+  0x70, 0xf8, 0xf6, 0x88, 0x1e, 0x21, 0x0e, 0xf4, 0x06, 0x07, 0x03, 0x80,
+  0x00, 0x01, 0x01, 0x54, 0xa4, 0x31, 0x54, 0x8b, 0x02, 0x35, 0x61, 0x40,
+  0x0e, 0x5e, 0x88, 0x40, 0x01, 0x0a, 0x47, 0xe0, 0x38, 0x90, 0x0c, 0x45,
+  0x88, 0x71, 0xc2, 0x03, 0x59, 0x8c, 0x71, 0xc3, 0x40, 0x76, 0x50, 0xa8,
+  0x52, 0x11, 0xc8, 0x5d, 0x95, 0x83, 0x10, 0x68, 0xc4, 0x50, 0x81, 0x16,
+  0x56, 0xe0, 0x40, 0x83, 0x13, 0x87, 0x38, 0x12, 0xc9, 0x22, 0x1f, 0xdd,
+  0x20, 0x87, 0x49, 0x72, 0xa4, 0x40, 0x42, 0x01, 0x38, 0xe4, 0xa0, 0x41,
+  0x0c, 0x2d, 0x40, 0x01, 0x84, 0x22, 0x25, 0x3d, 0xe2, 0x64, 0x44, 0x44,
+  0x24, 0x77, 0xd2, 0x13, 0x51, 0x40, 0x61, 0x42, 0x07, 0x53, 0xe0, 0xc0,
+  0x42, 0x14, 0x51, 0x00, 0x61, 0x48, 0x49, 0x8b, 0xcc, 0x61, 0xd1, 0x0d,
+  0x7c, 0x78, 0xe9, 0x88, 0x12, 0x79, 0xc2, 0xc0, 0x05, 0x18, 0x61, 0x94,
+  0x00, 0x68, 0x0f, 0x89, 0x12, 0xe2, 0xc4, 0x15, 0x11, 0xcd, 0xb1, 0x06,
+  0x0e, 0x3e, 0x20, 0x91, 0xc4, 0x12, 0x3f, 0x44, 0xa1, 0x02, 0x05, 0x6c,
+  0x74, 0x91, 0x46, 0x04, 0x3c, 0xe4, 0x19, 0x44, 0x12, 0x40, 0xd8, 0xd0,
+  0x9f, 0x43, 0x44, 0x6d, 0xf4, 0xa1, 0x06, 0x16, 0x4c, 0x18, 0x01, 0xc4,
+  0x0f, 0x2c, 0xbc, 0x50, 0x83, 0x15, 0x80, 0x80, 0xb1, 0x85, 0x0e, 0x35,
+  0xa0, 0xf0, 0xc2, 0x0f, 0x30, 0x0c, 0x61, 0xc5, 0x17, 0x16, 0xa5, 0x41,
+  0xc6, 0x1a, 0x66, 0xa4, 0x91, 0x85, 0x16, 0x53, 0x7c, 0xd1, 0x47, 0x1d,
+  0x6b, 0x9c, 0x61, 0x85, 0x15, 0x5b, 0x68, 0x51, 0x06, 0x16, 0x69, 0xc8,
+  0xb4, 0x46, 0x19, 0x77, 0x80, 0xe1, 0xd0, 0xab, 0x0b, 0x05, 0x04, 0x00,
+  0x3b
+};
+
+static void webui_http_eof(int idx)
+{
+  debug2("webui: webui_http_eof() idx %i sock %li", idx, dcc[idx].sock);
+  killsock(dcc[idx].sock);
+  lostdcc(idx);
+}
+
+static void webui_http_activity(int idx, char *buf, int len)
+{
+  struct rusage ru1, ru2;
+  int r, i;
+  char response[4096]; /* > sizeof webui.html
+                        * TODO: dynamic size? else buffer overflow ;)
+                        * we dont control webui.html size, that is user input!
+                        */
+
+  if (len < 6) { /* TODO: better len check */
+    putlog(LOG_MISC, "*",
+           "WEBUI error: %s sent something other than http GET request",
+           iptostr(&dcc[idx].sockname.addr.sa));
+    killsock(dcc[idx].sock);
+    lostdcc(idx);
+    return;
+  }
+  if (buf[0] == 0x16) { /* 0x16 = TLS handshake */
+      putlog(LOG_MISC, "*",
+             "WEBUI error: %s requested TLS handshake for non-ssl port",
+             iptostr(&dcc[idx].sockname.addr.sa));
+      tputs(dcc[idx].sock, (char *) alert, sizeof alert);
+      killsock(dcc[idx].sock);
+      lostdcc(idx);
+      return;
+  }
+  r = getrusage(RUSAGE_SELF, &ru1);
+  debug2("webui: webui_http_activity(): idx %i len %i", idx, len);
+  buf[len] = '\0'; /* TODO: is there no better way? we already know len */
+  debug0("webui: http()");
+  if (buf[5] == ' ') {
+    debug0("webui: webui: GET /");
+    #define PATH "text/webui.html"
+    int fd;
+    struct stat sb;
+    char *body;
+    if ((fd = open(PATH, O_RDONLY)) < 0) {
+      putlog(LOG_MISC, "*", "WEBUI error: open(" PATH "): %s", strerror(errno));
+      /* TODO: send 404 and/or lostdcc() killsock() */
+      return;
+    }
+    if (fstat(fd, &sb) < 0) {
+      putlog(LOG_MISC, "*", "WEBUI error: fstat(" PATH "): %s", strerror(errno));
+      return;
+    }
+
+    if ((body = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE
+#ifdef MAP_POPULATE
+                        | MAP_POPULATE
+#endif
+                        , fd, 0)) == MAP_FAILED) {
+      putlog(LOG_MISC, "*", "WEBUI error: mmap(" PATH "): %s\n", strerror(errno));
+      return;
+    }
+    i = snprintf(response, sizeof response,
+      "HTTP/1.1 200 \r\n" /* textual phrase is OPTIONAL */
+      "Content-Length: %li\r\n"
+      "Server: Eggdrop/%s+%s\r\n"
+      "\r\n%.*s", sb.st_size, EGG_STRINGVER, EGG_PATCH, (int) sb.st_size, body);
+    tputs(dcc[idx].sock, response, i);
+    debug2("webui: tputs(): >>>%s<<< %i", response, i);
+    if (munmap(body, sb.st_size) < 0) {
+      putlog(LOG_MISC, "*", "WEBUI error: munmap(): %s", strerror(errno));
+      return;
+    }
+  } else if (buf[5] == 'f') {
+    debug0("webui: GET /favicon.ico");
+    i = snprintf(response, sizeof response,
+      "HTTP/1.1 200 \r\n" /* textual phrase is OPTIONAL */
+      "Content-Length: %zu\r\n"
+      "Content-Type: image/x-icon\r\n"
+      "Server: Eggdrop/%s+%s\r\n" /* TODO: stealth_telnets */
+      "\r\n",
+      sizeof favicon_ico, EGG_STRINGVER, EGG_PATCH);
+    memcpy(response + i, favicon_ico, sizeof favicon_ico);
+    i += sizeof favicon_ico;
+
+    tputs(dcc[idx].sock, response, i);
+    debug1("webui: tputs(): %i", i);
+  } else if (buf[5] == 'w') {
+    debug0("webui: GET /w");
+    buf = strstr(buf, WS_KEY);
+    if (!buf) {
+      putlog(LOG_MISC, "*", "WEBUI error: Sec-WebSocket-Key not found");
+      return;
+    }
+    buf += sizeof WS_KEY;
+    for(i = 0; i < WS_KEYLEN; i++)
+      if (!buf[i]) {
+        putlog(LOG_MISC, "*", "WEBUI error: Sec-WebSocket-Key too short");
+        return;
+      }
+    debug0("webui: server requests websocket upgrade");
+
+    unsigned char hash[SHA_DIGEST_LENGTH];
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+    EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
+    const EVP_MD *md = EVP_sha1();
+    unsigned int md_len;
+    EVP_DigestInit_ex(mdctx, md, NULL);
+    EVP_DigestUpdate(mdctx, buf, WS_KEYLEN);
+    EVP_DigestUpdate(mdctx, WS_GUID, (sizeof WS_GUID) - 1);
+    EVP_DigestFinal_ex(mdctx, hash, &md_len);
+    EVP_MD_CTX_free(mdctx);
+#else
+    SHA_CTX c;
+    SHA1_Init(&c);
+    SHA1_Update(&c, buf, WS_KEYLEN);
+    SHA1_Update(&c, WS_GUID, (sizeof WS_GUID) - 1);
+    SHA1_Final(hash, &c);
+#endif
+
+    char out[WS_LEN + 1];
+    /* TODO: remove assert / debug */
+    if (b64_ntop(hash, sizeof hash, out, sizeof out) != WS_LEN) {
+      putlog(LOG_MISC, "*", "WEBUI error: b64_ntop() != WS_LEN");
+      return;
+    }
+
+    i = snprintf(response, sizeof response,
+      "HTTP/1.1 101 Switching Protocols\r\n"
+      "Upgrade: websocket\r\n"
+      "Connection: Upgrade\r\n"
+      "Sec-WebSocket-Accept: %s\r\n"
+      "\r\n", out);
+    tputs(dcc[idx].sock, response, i);
+    debug2("webui: tputs(): >>>%s<<< %i", response, i);
+
+    sock_list* socklist_i = &socklist[findsock(dcc[idx].sock)];
+    socklist_i->flags |= SOCK_WS;
+
+
+    socklist_i->flags &= ~ SOCK_BINARY; /* we need it for net.c sockgets(), is there better place to do this? */
+    debug1("webui: unset flag SOCK_BINARY sock %li\n", dcc[idx].sock);
+    strcpy(dcc[idx].host, "*"); /* important for later dcc_telnet_id wild_match, is there better place to do this? */
+    /* .host becomes .nick in change_to_dcc_telnet_id() */
+    debug4("webui: set flag SOCK_WS socklist %i idx %i sock %li status %lu\n", findsock(dcc[idx].sock), idx, dcc[idx].sock, dcc[idx].status);
+
+    dcc[idx].status |= STAT_USRONLY; /* magick */
+    for (i = 0; i < dcc_total; i++) /* quick hack, we need to link from idx, dont we? */
+      if (!strcmp(dcc[i].nick, "(webui)")) {
+        debug1("webui: found (webui) dcc %i\n", i);
+        break;
+      }
+
+    /*
+    for (int j = 0; j < dcc_total; j++) {
+      debug4("dcc table %i %i %i %s", j, dcc[j].sock, dcc[j].ssl, dcc[j].host);
+      debug2("             %s %s", dcc[j].nick, dcc[j].type->name);
+    }
+    */
+
+    dcc[idx].u.other = NULL; /* fix ATTEMPTING TO FREE NON-MALLOC'D PTR: dccutil.c (561) */
+    dcc_telnet_hostresolved2(idx, i);
+
+    debug2("webui: CHANGEOVER -> idx %i sock %li\n", idx, dcc[idx].sock);
+  } else /* TODO: send 404 or something ? */
+    debug0("webui: 404");
+  if ((dcc[idx].sock != -1) && (len == 511)) { /* sock == -1 if lostdcc() in dcc_telnet_hostresolved2() */
+    /* read probable remaining bytes */
+    SSL *ssl = socklist[findsock(dcc[idx].sock)].ssl;
+    if (ssl)
+      debug1("webui: SSL_read(): len %i", SSL_read(ssl, buf, 511));
+    else
+      debug1("webui: read(): len %li", read(dcc[idx].sock, buf, 511));
+  }
+  if (!r && !getrusage(RUSAGE_SELF, &ru2))
+    debug2("webui: webui_http_activity(): user %.3fms sys %.3fms",
+           (double) (ru2.ru_utime.tv_usec - ru1.ru_utime.tv_usec) / 1000 +
+           (double) (ru2.ru_utime.tv_sec  - ru1.ru_utime.tv_sec ) * 1000,
+           (double) (ru2.ru_stime.tv_usec - ru1.ru_stime.tv_usec) / 1000 +
+           (double) (ru2.ru_stime.tv_sec  - ru1.ru_stime.tv_sec ) * 1000);
+}
+
+static void webui_http_display(int idx, char *buf)
+{
+  if (!dcc[idx].ssl)
+    strcpy(buf, "webui http");
+  else
+    strcpy(buf, "webui https");
+}
+
+static struct dcc_table DCC_WEBUI_HTTP = {
+  "WEBUI_HTTP",
+  0,
+  webui_http_eof,
+  webui_http_activity,
+  NULL,
+  NULL,
+  webui_http_display,
+  NULL,
+  NULL,
+  NULL,
+  NULL
+};
+
+static void webui_dcc_telnet_hostresolved(int i)
+{
+    debug1("webui_dcc_telnet_hostresolved(%i)", i);
+    changeover_dcc(i, &DCC_WEBUI_HTTP, 0);
+    sockoptions(dcc[i].sock, EGG_OPTION_SET, SOCK_BINARY);
+    dcc[i].u.other = NULL; /* important, else nfree() error in lostdcc on eof */
+}
+
+static void webui_frame(char **buf, unsigned int *len) {
+  static uint8_t out[2048];
+
+  /* no debug() or putlog() here or recursion */
+  printf("webui: webui_frame() len %u\n", *len);
+  //printf(">>>%s<<<", *buf);
+  out[0] = 0x81; /* FIN + text frame */
+  /* A server MUST NOT mask any frames that it sends to the client */
+  if (*len < 0x7e) {
+    out[1] = *len;
+    /* TODO: we could offset buf and get rid of this memcpy() */
+    memcpy(out + 2, *buf, *len);
+    *buf = (char *) out;
+    *len = *len + 2;
+  } else {
+    out[1] = 0x7e;
+    uint16_t len2 = htons(*len);
+    memcpy(out + 2, &len2, 2);
+    /* TODO: we could offset buf and get rid of this memcpy() */
+    memcpy(out + 4, *buf, *len);
+    *buf = (char *) out;
+    *len = *len + 4;
+  }
+  /* FIXME:len > 0xffff */
+}
+
+/* TODO: return error code ? */
+static void webui_unframe(char *buf, int *len)
+{
+  int i;
+  uint8_t *key, *payload;
+
+  debug1("webui: webui_unframe(): len %i", *len);
+  if (*len < 6) { /* TODO: better len check */
+
+    /* TODO: return error code ? */
+    putlog(LOG_MISC, "*", "WEBUI error: someone sent something other than WebSocket protocol");
+    /*
+    putlog(LOG_MISC, "*",
+           "WEBUI error: %s sent something other than WebSocket protocol",
+           iptostr(&dcc[idx].sockname.addr.sa));
+    killsock(dcc[idx].sock);
+    lostdcc(idx);
+    */
+    return;
+  }
+  if (buf[0] & 0x08) {
+    putlog(LOG_MISC, "*", "WEBUI: fixme: sent connection close not handled yet");
+    /*
+    debug1("webui: webui_ws_activity(): %s sent connection close",
+           iptostr(&dcc[idx].sockname.addr.sa));
+    killsock(dcc[idx].sock);
+    lostdcc(idx);
+    */
+    return;
+  }
+  /* xor decrypt
+   */
+  key = (uint8_t *) buf;
+  if (key[1] < 0xfe) {
+    key += 2;
+    *len -= 6;
+  } else if (key[1] == 0xfe) {
+    key += 4;
+    *len -= 8;
+  } else {
+    key += 10;
+    *len -= 14;
+  }
+  payload = key + 4;
+  for (i = 0; i < *len; i++)
+    payload[i] = payload[i] ^ key[i % 4];
+  debug2("webui: webui_unframe(): payload >>>%.*s<<<", (int) *len, payload);
+
+  memmove(buf, payload, *len);
+  /* we switched back from binary sock to text sock for sockgets() needs this for dcc_telnet_id() */
+  /* so now we have to add \r\n here :/ */
+  strcpy(buf + *len, "\r\n");
+  *len+= 2;
+}
+
+static char *webui_close(void)
+{
+  del_hook(HOOK_DCC_TELNET_HOSTRESOLVED, (Function) webui_dcc_telnet_hostresolved);
+  del_hook(HOOK_WEBUI_FRAME, (Function) webui_frame);
+  del_hook(HOOK_WEBUI_UNFRAME, (Function) webui_unframe);
+  return NULL;
+}
+
+EXPORT_SCOPE char *webui_start();
+
+static Function webui_table[] = {
+  (Function) webui_start,
+  (Function) webui_close,
+  NULL,
+  NULL,
+};
+
+#endif
+char *webui_start(Function *global_funcs)
+{
+#ifdef TLS
+  global = global_funcs;
+  module_register(MODULE_NAME, webui_table, 0, 9);
+  if (!module_depend(MODULE_NAME, "eggdrop", 109, 0)) {
+    module_undepend(MODULE_NAME);
+    return "This module requires Eggdrop 1.9.0 or later.";
+  }
+  add_hook(HOOK_DCC_TELNET_HOSTRESOLVED, (Function) webui_dcc_telnet_hostresolved);
+  add_hook(HOOK_WEBUI_FRAME, (Function) webui_frame);
+  add_hook(HOOK_WEBUI_UNFRAME, (Function) webui_unframe);
+  return NULL;
+#else
+  return "Initialization failure: configured with --disable-tls or openssl not found";
+#endif
+}
diff --git a/src/modules.c b/src/modules.c
index 0fd005760..69664b0a6 100644
--- a/src/modules.c
+++ b/src/modules.c
@@ -171,6 +171,9 @@ int (*rfc_toupper) (int) = _rfc_toupper;
 int (*rfc_tolower) (int) = _rfc_tolower;
 void (*dns_hostbyip) (sockname_t *) = core_dns_hostbyip;
 void (*dns_ipbyhost) (char *) = core_dns_ipbyhost;
+void (*webui_dcc_telnet_hostresolved) (int) = 0;
+void (*webui_frame) (char **, unsigned int *) = 0;
+void (*webui_unframe) (char *, int *) = 0;
 
 module_entry *module_list;
 dependancy *dependancy_list = NULL;
@@ -626,6 +629,8 @@ Function global_table[] = {
 /* 324 - 327 */
   (Function) find_member_from_nick,
   (Function) get_user_from_member,
+  (Function) dcc_telnet_hostresolved2,
+  (Function) findsock
 };
 
 void init_modules(void)
@@ -1107,6 +1112,15 @@ void add_hook(int hook_num, Function func)
       if (dns_ipbyhost == core_dns_ipbyhost)
         dns_ipbyhost = (void (*)(char *)) func;
       break;
+    case HOOK_DCC_TELNET_HOSTRESOLVED:
+      webui_dcc_telnet_hostresolved = (void (*)(int)) func;
+      break;
+    case HOOK_WEBUI_FRAME:
+      webui_frame = (void (*)(char **, unsigned int *)) func;
+      break;
+    case HOOK_WEBUI_UNFRAME:
+      webui_unframe = (void (*)(char *, int *)) func;
+      break;
     }
 }
 
@@ -1177,6 +1191,18 @@ void del_hook(int hook_num, Function func)
       if (dns_ipbyhost == (void (*)(char *)) func)
         dns_ipbyhost = core_dns_ipbyhost;
       break;
+    case HOOK_DCC_TELNET_HOSTRESOLVED:
+      if (webui_dcc_telnet_hostresolved == (void (*)(int)) func)
+        webui_dcc_telnet_hostresolved = (void (*)(int)) null_func;
+      break;
+    case HOOK_WEBUI_FRAME:
+      if (webui_frame == (void (*)(char **, unsigned int *)) func)
+        webui_frame = (void (*)(char **, unsigned int *)) null_func;
+      break;
+    case HOOK_WEBUI_UNFRAME:
+      if (webui_unframe == (void (*)(char *, int *)) func)
+        webui_unframe = (void (*)(char *, int *)) null_func;
+      break;
     }
 }
 
diff --git a/src/net.c b/src/net.c
index 42be92b34..c5493b9c7 100644
--- a/src/net.c
+++ b/src/net.c
@@ -1015,6 +1015,10 @@ int sockread(char *s, int *len, sock_list *slist, int slistmax, int tclonly)
           continue;           /* EAGAIN */
         }
       }
+#ifdef TLS
+      if (socklist[i].flags & SOCK_WS)
+        webui_unframe(s, &x);
+#endif /* TLS */
       s[x] = 0;
       *len = x;
       if (slist[i].flags & SOCK_PROXYWAIT) {
@@ -1343,6 +1347,8 @@ void tputs(int z, char *s, unsigned int len)
         return;
       }
 #ifdef TLS
+      if (socklist[i].flags & SOCK_WS) /* TODO: early enough for un-tls ws? */
+        webui_frame(&s, &len);
       if (socklist[i].ssl) {
         x = SSL_write(socklist[i].ssl, s, len);
         if (x < 0) {
diff --git a/src/proto.h b/src/proto.h
index 0d95ae488..188112211 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -54,6 +54,9 @@ extern int (*rfc_ncasecmp) (const char *, const char *, int);
 extern int (*rfc_toupper) (int);
 extern int (*rfc_tolower) (int);
 extern int (*match_noterej) (struct userrec *, char *);
+extern void (*webui_dcc_telnet_hostresolved) (int);
+extern void (*webui_frame) (char **, unsigned int *);
+extern void (*webui_unframe) (char *, int *);
 #endif
 
 /* botcmd.c */
@@ -136,6 +139,7 @@ void dupwait_notify(char *);
 #ifdef TLS
 int dcc_fingerprint(int);
 #endif
+void dcc_telnet_hostresolved2(int, int);
 
 /* dccutil.c */
 int increase_socks_max(void);
@@ -309,6 +313,7 @@ int readtclprog(char *fname);
 
 /* tls.c */
 #ifdef TLS
+void ssl_cleanup();
 int ssl_handshake(int, int, int, int, char *, IntFunc);
 char *ssl_fpconv(char *in, char *out);
 const char *ssl_getuid(int sock);
diff --git a/src/tcldcc.c b/src/tcldcc.c
index 8c0cfbb3c..bbcbe44dc 100644
--- a/src/tcldcc.c
+++ b/src/tcldcc.c
@@ -1225,6 +1225,8 @@ static int setlisten(Tcl_Interp *irp, char *ip, char *portp, char *type, char *m
     strcpy(dcc[idx].nick, "(users)");
   else if (!strcmp(type, "all"))
     strcpy(dcc[idx].nick, "(telnet)");
+  else if (!strcmp(type, "webui"))
+    strcpy(dcc[idx].nick, "(webui)");
   if (maskproc[0])
     strlcpy(dcc[idx].host, maskproc, UHOSTMAX);
   else
@@ -1310,9 +1312,9 @@ static int tcl_listen STDVAR
   }
   if ((strcmp(argv[i], "bots")) && (strcmp(argv[i], "users"))
         && (strcmp(argv[i], "all")) && (strcmp(argv[i], "off"))
-        && (strcmp(argv[i], "script"))) {
+        && (strcmp(argv[i], "script")) && (strcmp(argv[i], "webui"))) {
     Tcl_AppendResult(irp, "invalid listen type: must be one of ",
-          "bots, users, all, off, script", NULL);
+          "bots, users, all, off, script, webui", NULL);
     return TCL_ERROR;
   }
   strlcpy(type, argv[i], sizeof(type));
diff --git a/src/tls.c b/src/tls.c
index d3af0c6ea..c57e48348 100644
--- a/src/tls.c
+++ b/src/tls.c
@@ -109,6 +109,71 @@ static int ssl_seed(void)
   return 0;
 }
 
+/* Get the certificate, corresponding to the connection
+ * identified by sock.
+ *
+ * Return value: pointer to a X509 certificate or NULL if we couldn't
+ * look up the certificate.
+ */
+static X509 *ssl_getcert(int sock)
+{
+  int i;
+  struct threaddata *td = threaddata();
+
+  i = findsock(sock);
+  if (i == -1 || !td->socklist[i].ssl)
+    return NULL;
+  return SSL_get_peer_certificate(td->socklist[i].ssl);
+}
+
+/* Get the certificate fingerprint of the connection corresponding
+ * to the socket.
+ *
+ * Return value: ptr to the hexadecimal representation of the fingerprint
+ * or NULL in case of error.
+ */
+static char *ssl_getfp_from_cert(X509 *cert)
+{
+  char *p;
+  unsigned int i;
+  static char fp[SHA_DIGEST_LENGTH * 3];
+  unsigned char md[SHA_DIGEST_LENGTH];
+
+  if (!X509_digest(cert, EVP_sha1(), md, &i)) {
+    putlog(LOG_MISC, "*", "ERROR: TLS: ssl_getfp_from_cert(): X509_digest()");
+    X509_free(cert);
+    return NULL;
+  }
+  if (!(p = OPENSSL_buf2hexstr(md, i))) {
+    putlog(LOG_MISC, "*", "ERROR: TLS: ssl_getfp_from_cert(): OPENSSL_buf2hexstr()");
+    X509_free(cert);
+    return NULL;
+  }
+  strlcpy(fp, p, sizeof fp);
+  OPENSSL_free(p);
+
+  return fp;
+}
+
+/* Get the certificate fingerprint of the connection corresponding
+ * to the socket.
+ *
+ * Return value: ptr to the hexadecimal representation of the fingerprint
+ * or NULL if there's no certificate associated with the connection or in case
+ * of error.
+ */
+char *ssl_getfp(int sock)
+{
+  char *fp;
+  X509 *cert;
+
+  if (!(cert = ssl_getcert(sock)))
+    return NULL;
+  fp = ssl_getfp_from_cert(cert);
+  X509_free(cert);
+  return fp;
+}
+
 /* Prepares and initializes SSL stuff
  *
  * Creates a context object, supporting SSLv2/v3 & TLSv1 protocols;
@@ -157,6 +222,22 @@ int ssl_init()
           tls_certfile, ERR_error_string(ERR_get_error(), NULL));
       fatal("Unable to load TLS certificate (ssl-certificate config setting)!", 0);
     }
+
+    /* TODO: sha256 fingerprint
+     *       print this fingerprint to every user / every partyline login
+     *       maybe only print it when webui is enabled
+     *       compatibility to older openssl is possible, similar to #1411
+     *       this functionality could be sepped into a side-PR
+     *       or functionality of #1411 can be reused once merged
+     *
+     *       for now, just disable the fingerprint for openssl < 1.0.2
+     */
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L /* 1.0.2 */
+    putlog(LOG_MISC, "*", "Certificate loaded: %s (sha1 fingerprint %s)",
+           tls_certfile,
+           ssl_getfp_from_cert(SSL_CTX_get0_certificate(ssl_ctx)));
+#endif
+
     if (SSL_CTX_use_PrivateKey_file(ssl_ctx, tls_keyfile, SSL_FILETYPE_PEM) != 1) {
       putlog(LOG_MISC, "*", "ERROR: TLS: unable to load private key from %s: %s",
           tls_keyfile, ERR_error_string(ERR_get_error(), NULL));
@@ -319,53 +400,6 @@ char *ssl_fpconv(char *in, char *out)
   return NULL;
 }
 
-/* Get the certificate, corresponding to the connection
- * identified by sock.
- *
- * Return value: pointer to a X509 certificate or NULL if we couldn't
- * look up the certificate.
- */
-static X509 *ssl_getcert(int sock)
-{
-  int i;
-  struct threaddata *td = threaddata();
-
-  i = findsock(sock);
-  if (i == -1 || !td->socklist[i].ssl)
-    return NULL;
-  return SSL_get_peer_certificate(td->socklist[i].ssl);
-}
-
-/* Get the certificate fingerprint of the connection corresponding
- * to the socket.
- *
- * Return value: ptr to the hexadecimal representation of the fingerprint
- * or NULL if there's no certificate associated with the connection.
- */
-char *ssl_getfp(int sock)
-{
-  char *p;
-  unsigned int i;
-  X509 *cert;
-  static char fp[64];
-  unsigned char md[EVP_MAX_MD_SIZE];
-
-  if (!(cert = ssl_getcert(sock)))
-    return NULL;
-  if (!X509_digest(cert, EVP_sha1(), md, &i)) {
-    X509_free(cert);
-    return NULL;
-  }
-  if (!(p = OPENSSL_buf2hexstr(md, i))) {
-    X509_free(cert);
-    return NULL;
-  }
-  strlcpy(fp, p, sizeof fp);
-  OPENSSL_free(p);
-  X509_free(cert);
-  return fp;
-}
-
 /* Get the UID field from the certificate subject name.
  * The certificate is looked up using the socket of the connection.
  *
@@ -963,12 +997,40 @@ int ssl_handshake(int sock, int flags, int verify, int loglevel, char *host,
     return 0;
   }
   if ((err = ERR_peek_error())) {
-    putlog(data->loglevel, "*",
-           "TLS: handshake failed due to the following error: %s",
-           ERR_reason_error_string(err));
-    debug0("TLS: handshake failed due to the following errors: ");
-    while ((err = ERR_get_error()))
-      debug1("TLS: %s", ERR_error_string(err, NULL));
+    if (ERR_GET_LIB(ERR_peek_error()) == ERR_LIB_SSL &&
+        ERR_GET_REASON(err) == SSL_R_HTTP_REQUEST) {
+
+
+
+      /* TODO: check if this is really a webui port, report source host (info in dcc[i]?) and give hint to use https:// */
+      putlog(LOG_MISC, "*", "TLS: error: plain HTTP request received on an SSL port, sock %i", sock);
+      #include "version.h"
+      int i;
+      char response[4096];
+      char *body = "(WIP) webui: plain HTTP request received on an SSL port";
+      i = snprintf(response, sizeof response,
+        "HTTP/1.1 200 \r\n" /* textual phrase is OPTIONAL */
+        "Content-Length: %zu\r\n"
+        "Server: Eggdrop/%s+%s\r\n"
+        "\r\n%.*s", strlen(body), EGG_STRINGVER, EGG_PATCH, (int) strlen(body), body);
+      write(sock, response, i); // TODO: tputs(sock, response, i); after reading of remaining bytes / ssl shutdown ?
+      /*
+      do/for/while read(sock, ...);
+      SSL_set_shutdown(td->socklist[i].ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
+      */
+      debug2("webui: tputs(): >>>%s<<< %i", response, i);
+
+
+
+    }
+    else {
+      putlog(data->loglevel, "*",
+             "TLS: handshake failed due to the following error: %s",
+             ERR_reason_error_string(err));
+      debug0("TLS: handshake failed due to the following errors: ");
+      while ((err = ERR_get_error()))
+        debug1("TLS: %s", ERR_error_string(err, NULL));
+    }
   }
 
   /* Attempt failed, cleanup and abort */
diff --git a/text/webui.html b/text/webui.html
new file mode 100644
index 000000000..fef5e3ea5
--- /dev/null
+++ b/text/webui.html
@@ -0,0 +1,51 @@
+
+
+
+  
+    
+    Eggdrop
+  
+  
+    

+    
+    
+  
+