From 930c847e78aeb559d374dd09b5c2b65d35561c57 Mon Sep 17 00:00:00 2001 From: Michael Ortmann <41313082+michaelortmann@users.noreply.github.com> Date: Tue, 7 Feb 2023 02:01:00 +0100 Subject: [PATCH 01/76] Initial commit --- eggdrop-basic.conf | 1 + eggdrop.conf | 7 + src/dcc.c | 166 +++++++----- src/eggdrop.h | 5 + src/main.c | 4 - src/mod/module.h | 3 + src/mod/modvals.h | 63 ++--- src/mod/webui.mod/Makefile | 47 ++++ src/mod/webui.mod/webui.c | 489 +++++++++++++++++++++++++++++++++++ src/mod/webui.mod/webui.html | 43 +++ src/modules.c | 29 ++- src/net.c | 8 + src/proto.h | 5 + src/tcldcc.c | 6 +- src/tls.c | 120 +++++---- text/webui.html | 51 ++++ 16 files changed, 897 insertions(+), 150 deletions(-) create mode 100644 src/mod/webui.mod/Makefile create mode 100644 src/mod/webui.mod/webui.c create mode 100644 src/mod/webui.mod/webui.html create mode 100644 text/webui.html diff --git a/eggdrop-basic.conf b/eggdrop-basic.conf index d14753d0ee..0e07f5e2f7 100755 --- a/eggdrop-basic.conf +++ b/eggdrop-basic.conf @@ -37,6 +37,7 @@ loadmodule console ; # Console setting storage loadmodule uptime ; # Centralized uptime stat collection (http://uptime.eggheads.org) #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 1b8de6651b..9ba0f42642 100755 --- a/eggdrop.conf +++ b/eggdrop.conf @@ -1680,6 +1680,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/dcc.c b/src/dcc.c index 77cdb7e29a..c10fd22dce 100644 --- a/src/dcc.c +++ b/src/dcc.c @@ -732,6 +732,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 { @@ -1292,11 +1294,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 @@ -1310,12 +1319,68 @@ 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; + 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++) @@ -1374,54 +1439,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; - 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) @@ -1551,7 +1578,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); @@ -1808,14 +1834,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); + } } } @@ -2387,7 +2419,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)); @@ -2396,17 +2427,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 0b70156488..4d6c464aec 100644 --- a/src/eggdrop.h +++ b/src/eggdrop.h @@ -543,6 +543,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 */ @@ -654,6 +655,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 */ @@ -805,4 +807,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 24dc95a4dd..f9fc7f39da 100644 --- a/src/main.c +++ b/src/main.c @@ -178,10 +178,6 @@ int cx_line[16]; int cx_ptr = 0; #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 1fd109b5c6..189bdb50c6 100644 --- a/src/mod/module.h +++ b/src/mod/module.h @@ -527,6 +527,9 @@ typedef void (*chanout_butfunc)(int, int, const char *, ...) ATTRIBUTE_FORMAT(pr #define USERENTRY_ACCOUNT (*(struct user_entry_type *)(global[316])) #define get_user_by_account ((struct userrec * (*)(char *))global[317]) #define delaccount_by_handle ((int(*)(char *,char *))global[318]) +#define dcc_telnet_hostresolved2 ((void(*)(int, int))global[319]) +/* 320 - 323 */ +#define findsock ((int(*)(int))global[320]) diff --git a/src/mod/modvals.h b/src/mod/modvals.h index 5eda82948c..1f07970be9 100644 --- a/src/mod/modvals.h +++ b/src/mod/modvals.h @@ -23,36 +23,39 @@ #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 REAL_HOOKS 16 -#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_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 REAL_HOOKS 16 +#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 0000000000..e3042a41f3 --- /dev/null +++ b/src/mod/webui.mod/Makefile @@ -0,0 +1,47 @@ +# 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/mod/server.mod/server.h diff --git a/src/mod/webui.mod/webui.c b/src/mod/webui.mod/webui.c new file mode 100644 index 0000000000..d5da2e6cb4 --- /dev/null +++ b/src/mod/webui.mod/webui.c @@ -0,0 +1,489 @@ +/* + * webui.c -- part of webui.mod + */ +/* + * Copyright (C) 2023 Michael Ortmann MIT License + * Copyright (C) 2023 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. + * + */ + +#define MODULE_NAME "webui" + +#include +#include +#include /* base64 encode b64_ntop() and base64 decode b64_pton() */ +#include +#include +#include +#include +#include "src/version.h" +#include "src/mod/module.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, 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)")) { + debug0("webui: found (webui) dcc\n"); + break; + } + 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 (len == 511) { + /* 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, +}; + +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/mod/webui.mod/webui.html b/src/mod/webui.mod/webui.html new file mode 100644 index 0000000000..eb9cd7f2ad --- /dev/null +++ b/src/mod/webui.mod/webui.html @@ -0,0 +1,43 @@ + + + + Eggdrop + +

+    
+    
+  
+
diff --git a/src/modules.c b/src/modules.c
index 5bcab0e564..73f4cedf47 100644
--- a/src/modules.c
+++ b/src/modules.c
@@ -174,6 +174,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;
@@ -627,7 +630,10 @@ Function global_table[] = {
 /* 316 - 319 */
   (Function) & USERENTRY_ACCOUNT, /* struct user_entry_type *            */
   (Function) get_user_by_account,
-  (Function) delhost_by_handle
+  (Function) delhost_by_handle,
+  (Function) dcc_telnet_hostresolved2,
+/* 320 - 323 */
+  (Function) findsock
 };
 
 void init_modules(void)
@@ -1105,6 +1111,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;
     }
 }
 
@@ -1175,6 +1190,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 48a5c9e607..ae50e888ec 100644
--- a/src/net.c
+++ b/src/net.c
@@ -1006,6 +1006,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) {
@@ -1173,6 +1177,8 @@ int sockgets(char *s, int *len)
     s[0] = 0;
     return ret;
   }
+  debug5("sockread sock %i return %i >>%s<< %i binary? %i", socklist[ret].sock, ret, xx, *len, socklist[ret].flags & SOCK_BINARY);
+  debug1("flags %i", socklist[ret].flags);
   /* sockread can return binary data while socket still has connectflag, process first */
   if (socklist[ret].flags & SOCK_BINARY && *len > 0) {
     socklist[ret].flags &= ~SOCK_CONNECT;
@@ -1332,6 +1338,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 63cfc574cb..05a72751b4 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 */
@@ -134,6 +137,7 @@ int check_ansi(char *);
 void dupwait_notify(char *);
 #ifdef TLS
 int dcc_fingerprint(int);
+void dcc_telnet_hostresolved2(int, int);
 #endif
 
 /* dccutil.c */
@@ -310,6 +314,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 6a704de879..346eaed617 100644
--- a/src/tcldcc.c
+++ b/src/tcldcc.c
@@ -1218,6 +1218,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
@@ -1303,9 +1305,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 615a1d2acc..1feeab209f 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,14 @@ 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 */
+
+    putlog(LOG_MISC, "*", "Certificate loaded: %s (sha1 fingerprint %s)",
+           tls_certfile,
+           ssl_getfp_from_cert(SSL_CTX_get0_certificate(ssl_ctx)));
+
     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 +392,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.
  *
diff --git a/text/webui.html b/text/webui.html
new file mode 100644
index 0000000000..fef5e3ea58
--- /dev/null
+++ b/text/webui.html
@@ -0,0 +1,51 @@
+
+
+
+  
+    
+    Eggdrop
+  
+  
+    

+    
+    
+  
+

From 052416361d2036d036de12aac93a674fe34edb4e Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Tue, 7 May 2024 01:22:58 +0200
Subject: [PATCH 02/76] Enhance log

---
 src/tls.c | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/src/tls.c b/src/tls.c
index a71eabe2fe..f755c6bb6d 100644
--- a/src/tls.c
+++ b/src/tls.c
@@ -965,12 +965,20 @@ 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);
+    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())) {
+	printf("err = %ul\n", err);
+        debug1("TLS: %s", ERR_error_string(err, NULL));
+      }
+    }
   }
 
   /* Attempt failed, cleanup and abort */

From 3758fca3745aaf56594d4eed14d0ba83af8297b1 Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Tue, 7 May 2024 01:25:52 +0200
Subject: [PATCH 03/76] Remove debug log

---
 src/net.c | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/net.c b/src/net.c
index 4c80dde894..ba10f9515f 100644
--- a/src/net.c
+++ b/src/net.c
@@ -1182,8 +1182,6 @@ int sockgets(char *s, int *len)
     s[0] = 0;
     return ret;
   }
-  debug5("sockread sock %i return %i >>%s<< %i binary? %i", socklist[ret].sock, ret, xx, *len, socklist[ret].flags & SOCK_BINARY);
-  debug1("flags %i", socklist[ret].flags);
   /* sockread can return binary data while socket still has connectflag, process first */
   if (socklist[ret].flags & SOCK_BINARY && *len > 0) {
     socklist[ret].flags &= ~SOCK_CONNECT;

From 54e10f77fe9b0bca64e94c94b2a3eb2c69a40e81 Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Tue, 7 May 2024 03:51:54 +0200
Subject: [PATCH 04/76] Update Copyright

---
 src/mod/webui.mod/webui.c    | 4 ++--
 src/mod/webui.mod/webui.html | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/mod/webui.mod/webui.c b/src/mod/webui.mod/webui.c
index d5da2e6cb4..26311a75d3 100644
--- a/src/mod/webui.mod/webui.c
+++ b/src/mod/webui.mod/webui.c
@@ -2,8 +2,8 @@
  * webui.c -- part of webui.mod
  */
 /*
- * Copyright (C) 2023 Michael Ortmann MIT License
- * Copyright (C) 2023 Eggheads Development Team
+ * 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
diff --git a/src/mod/webui.mod/webui.html b/src/mod/webui.mod/webui.html
index eb9cd7f2ad..b5696e7b16 100644
--- a/src/mod/webui.mod/webui.html
+++ b/src/mod/webui.mod/webui.html
@@ -1,7 +1,7 @@
 
 
 

From 063fc5c8a001c3397e9183084ae5470a5452ff84 Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Tue, 7 May 2024 17:46:45 +0200
Subject: [PATCH 05/76] Renamed webui.html -> text/webui.html

---
 src/mod/webui.mod/{ => text}/webui.html | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename src/mod/webui.mod/{ => text}/webui.html (100%)

diff --git a/src/mod/webui.mod/webui.html b/src/mod/webui.mod/text/webui.html
similarity index 100%
rename from src/mod/webui.mod/webui.html
rename to src/mod/webui.mod/text/webui.html

From e23e9f291e397dbe5fdd3c22bb6a13b4b36e039f Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Tue, 7 May 2024 18:15:19 +0200
Subject: [PATCH 06/76] add character encoding

---
 src/mod/webui.mod/text/webui.html | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/src/mod/webui.mod/text/webui.html b/src/mod/webui.mod/text/webui.html
index b5696e7b16..b0f7e562e5 100644
--- a/src/mod/webui.mod/text/webui.html
+++ b/src/mod/webui.mod/text/webui.html
@@ -5,8 +5,11 @@
 -->
 
 
-  Eggdrop
-  
+  
+    
+    Eggdrop
+  
+   
     

     
     
-  
-

From cae744401d26e7134c0fdf27156d2317fa64072f Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Thu, 23 Oct 2025 21:35:21 +0200
Subject: [PATCH 56/76] Re-add console log foer echo on/off

---
 src/mod/webui.mod/index.html | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/mod/webui.mod/index.html b/src/mod/webui.mod/index.html
index 708412f802..7471f6e228 100644
--- a/src/mod/webui.mod/index.html
+++ b/src/mod/webui.mod/index.html
@@ -52,9 +52,11 @@
         if (event.data.indexOf(WS_ECHO_ON) > -1) {
           d.type = "text";
           echo = 1;
+          console.log("echo on");
         } else if (event.data.indexOf(WS_ECHO_OFF) > -1) {
           d.type = "password";
           echo = 0;
+          console.log("echo off");
         }
       });
       function b(c) {

From 43a941b62dc060e24440714319d75a43e69bf1f7 Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Thu, 23 Oct 2025 22:13:49 +0200
Subject: [PATCH 57/76] Add 404 and fix indent

---
 src/mod/webui.mod/webui.c | 113 +++++++++++++++++++++++---------------
 1 file changed, 69 insertions(+), 44 deletions(-)

diff --git a/src/mod/webui.mod/webui.c b/src/mod/webui.mod/webui.c
index c52c1ef21b..47a85cd52a 100644
--- a/src/mod/webui.mod/webui.c
+++ b/src/mod/webui.mod/webui.c
@@ -55,53 +55,78 @@ static void webui_http_eof(int idx)
   lostdcc(idx);
 }
 
+static void put_404(int idx) {
+  int i;
+  char *response;
+
+  i = snprintf(NULL, 0,
+    "HTTP/1.1 404 \r\n" /* textual phrase is OPTIONAL */
+    "Content-Length: 13\r\n"
+    "Content-Type: text/plain\r\n"
+    "Server: %s\r\n"
+    "\r\n404 Not found",
+      stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH);
+  response = nmalloc(i);
+  sprintf(response,
+    "HTTP/1.1 404 \r\n" /* textual phrase is OPTIONAL */
+    "Content-Length: 13\r\n"
+    "Content-Type: text/plain\r\n" /* at least firefox 144 needs this */
+    "Server: %s\r\n"
+    "\r\n404 Not found",
+      stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH);
+  tputs(dcc[idx].sock, response, i);
+  nfree(response);
+  killsock(dcc[idx].sock);
+  lostdcc(idx);
+}
+
 static void put_file(int idx, const char *filename, const char *content_type) {
-    int fd, i;
-    struct stat sb;
-    char *body;
-    char *response;
-
-    if ((fd = open(filename, O_RDONLY)) < 0) {
-      putlog(LOG_MISC, "*", "WEBUI error: open(%s): %s", filename, strerror(errno));
-      /* TODO: send 404 and/or lostdcc() killsock() */
-      return;
-    }
-    if (fstat(fd, &sb) < 0) {
-      putlog(LOG_MISC, "*", "WEBUI error: fstat(%s): %s", filename, strerror(errno));
-      return;
-    }
+  int fd, i;
+  struct stat sb;
+  char *body;
+  char *response;
 
-    if ((body = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE
+  if ((fd = open(filename, O_RDONLY)) < 0) {
+    putlog(LOG_MISC, "*", "WEBUI error: open(%s): %s", filename, strerror(errno));
+    /* TODO: send 404 and/or lostdcc() killsock() */
+    return;
+  }
+  if (fstat(fd, &sb) < 0) {
+    putlog(LOG_MISC, "*", "WEBUI error: fstat(%s): %s", filename, strerror(errno));
+    return;
+  }
+
+  if ((body = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE
 #ifdef MAP_POPULATE
-                        | MAP_POPULATE
+                   | MAP_POPULATE
 #endif
-                        , fd, 0)) == MAP_FAILED) {
-      putlog(LOG_MISC, "*", "WEBUI error: mmap(%s): %s\n", filename, strerror(errno));
-      return;
-    }
-    i = snprintf(NULL, 0,
-      "HTTP/1.1 200 \r\n" /* textual phrase is OPTIONAL */
-      "Content-Length: %li\r\n"
-      "Content-Type: %s\r\n" /* at least firefox 144 needs this */
-      "Server: %s\r\n"
-      "\r\n", sb.st_size, content_type,
-        stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH);
-    response = nmalloc(i + sb.st_size);
-    sprintf(response,
-      "HTTP/1.1 200 \r\n" /* textual phrase is OPTIONAL */
-      "Content-Length: %li\r\n"
-      "Content-Type: %s\r\n" /* at least firefox 144 needs this */
-      "Server: %s\r\n"
-      "\r\n", sb.st_size, content_type,
-        stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH);
-    memcpy(response + i, body, sb.st_size);
-    tputs(dcc[idx].sock, response, i + sb.st_size);
-    // debug2("webui: tputs(): >>>%s<<< %i", response, i);
-    nfree(response);
-    if (munmap(body, sb.st_size) < 0) {
-      putlog(LOG_MISC, "*", "WEBUI error: munmap(): %s", strerror(errno));
-      return;
-    }
+                   , fd, 0)) == MAP_FAILED) {
+    putlog(LOG_MISC, "*", "WEBUI error: mmap(%s): %s\n", filename, strerror(errno));
+    return;
+  }
+  i = snprintf(NULL, 0,
+    "HTTP/1.1 200 \r\n" /* textual phrase is OPTIONAL */
+    "Content-Length: %li\r\n"
+    "Content-Type: %s\r\n" /* at least firefox 144 needs this */
+    "Server: %s\r\n"
+    "\r\n", sb.st_size, content_type,
+      stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH);
+  response = nmalloc(i + sb.st_size);
+  sprintf(response,
+    "HTTP/1.1 200 \r\n" /* textual phrase is OPTIONAL */
+    "Content-Length: %li\r\n"
+    "Content-Type: %s\r\n" /* at least firefox 144 needs this */
+    "Server: %s\r\n"
+    "\r\n", sb.st_size, content_type,
+      stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH);
+  memcpy(response + i, body, sb.st_size);
+  tputs(dcc[idx].sock, response, i + sb.st_size);
+  // debug2("webui: tputs(): >>>%s<<< %i", response, i);
+  nfree(response);
+  if (munmap(body, sb.st_size) < 0) {
+    putlog(LOG_MISC, "*", "WEBUI error: munmap(): %s", strerror(errno));
+    return;
+  }
 }
 
 static void webui_http_activity(int idx, char *buf, int len)
@@ -227,7 +252,7 @@ static void webui_http_activity(int idx, char *buf, int len)
     put_file(idx, "webui/apple-touch-icon.png", "image/png");
   } else { /* TODO: send 404 or something ? */
     debug0("webui: 404");
-    debug0(buf);
+    put_404(idx);
   }
   if ((dcc[idx].sock != -1) && (len == 511)) { /* sock == -1 if lostdcc() in dcc_telnet_hostresolved2() */
     /* read probable remaining bytes */

From d5274a43ab1cb2d049805d0a5670d05174250cd6 Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Thu, 23 Oct 2025 22:26:08 +0200
Subject: [PATCH 58/76] Enhance 404

---
 src/mod/webui.mod/webui.c | 34 +++++++++++++++++++++-------------
 1 file changed, 21 insertions(+), 13 deletions(-)

diff --git a/src/mod/webui.mod/webui.c b/src/mod/webui.mod/webui.c
index 47a85cd52a..ebfec5157f 100644
--- a/src/mod/webui.mod/webui.c
+++ b/src/mod/webui.mod/webui.c
@@ -55,25 +55,31 @@ static void webui_http_eof(int idx)
   lostdcc(idx);
 }
 
-static void put_404(int idx) {
+static void put_404(int idx, const char *filename) {
   int i;
   char *response;
 
   i = snprintf(NULL, 0,
     "HTTP/1.1 404 \r\n" /* textual phrase is OPTIONAL */
-    "Content-Length: 13\r\n"
+    "Content-Length: %zu\r\n"
     "Content-Type: text/plain\r\n"
     "Server: %s\r\n"
-    "\r\n404 Not found",
-      stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH);
+    "\r\n"
+    "404 %s not found",
+    14 + strlen(filename),
+    stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH,
+    filename);
   response = nmalloc(i);
   sprintf(response,
     "HTTP/1.1 404 \r\n" /* textual phrase is OPTIONAL */
-    "Content-Length: 13\r\n"
+    "Content-Length: %zu\r\n"
     "Content-Type: text/plain\r\n" /* at least firefox 144 needs this */
     "Server: %s\r\n"
-    "\r\n404 Not found",
-      stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH);
+    "\r\n"
+    "404 %s not found",
+    14 + strlen(filename),
+    stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH,
+    filename);
   tputs(dcc[idx].sock, response, i);
   nfree(response);
   killsock(dcc[idx].sock);
@@ -88,7 +94,7 @@ static void put_file(int idx, const char *filename, const char *content_type) {
 
   if ((fd = open(filename, O_RDONLY)) < 0) {
     putlog(LOG_MISC, "*", "WEBUI error: open(%s): %s", filename, strerror(errno));
-    /* TODO: send 404 and/or lostdcc() killsock() */
+    put_404(idx, filename);
     return;
   }
   if (fstat(fd, &sb) < 0) {
@@ -109,16 +115,18 @@ static void put_file(int idx, const char *filename, const char *content_type) {
     "Content-Length: %li\r\n"
     "Content-Type: %s\r\n" /* at least firefox 144 needs this */
     "Server: %s\r\n"
-    "\r\n", sb.st_size, content_type,
-      stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH);
+    "\r\n",
+    sb.st_size, content_type,
+    stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH);
   response = nmalloc(i + sb.st_size);
   sprintf(response,
     "HTTP/1.1 200 \r\n" /* textual phrase is OPTIONAL */
     "Content-Length: %li\r\n"
     "Content-Type: %s\r\n" /* at least firefox 144 needs this */
     "Server: %s\r\n"
-    "\r\n", sb.st_size, content_type,
-      stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH);
+    "\r\n",
+    sb.st_size, content_type,
+    stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH);
   memcpy(response + i, body, sb.st_size);
   tputs(dcc[idx].sock, response, i + sb.st_size);
   // debug2("webui: tputs(): >>>%s<<< %i", response, i);
@@ -252,7 +260,7 @@ static void webui_http_activity(int idx, char *buf, int len)
     put_file(idx, "webui/apple-touch-icon.png", "image/png");
   } else { /* TODO: send 404 or something ? */
     debug0("webui: 404");
-    put_404(idx);
+    put_404(idx, "");
   }
   if ((dcc[idx].sock != -1) && (len == 511)) { /* sock == -1 if lostdcc() in dcc_telnet_hostresolved2() */
     /* read probable remaining bytes */

From adaa3a643d8e1e59e9c65a0ba13fa8591b7d53ff Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Fri, 24 Oct 2025 01:47:13 +0200
Subject: [PATCH 59/76] Cleanup

---
 src/mod/webui.mod/webui.c | 22 +++++++++-------------
 1 file changed, 9 insertions(+), 13 deletions(-)

diff --git a/src/mod/webui.mod/webui.c b/src/mod/webui.mod/webui.c
index ebfec5157f..a8ab6d542e 100644
--- a/src/mod/webui.mod/webui.c
+++ b/src/mod/webui.mod/webui.c
@@ -55,31 +55,27 @@ static void webui_http_eof(int idx)
   lostdcc(idx);
 }
 
-static void put_404(int idx, const char *filename) {
+static void put_404(int idx) {
   int i;
   char *response;
 
   i = snprintf(NULL, 0,
     "HTTP/1.1 404 \r\n" /* textual phrase is OPTIONAL */
-    "Content-Length: %zu\r\n"
+    "Content-Length: 13\r\n"
     "Content-Type: text/plain\r\n"
     "Server: %s\r\n"
     "\r\n"
-    "404 %s not found",
-    14 + strlen(filename),
-    stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH,
-    filename);
+    "404 Not Found",
+    stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH);
   response = nmalloc(i);
   sprintf(response,
     "HTTP/1.1 404 \r\n" /* textual phrase is OPTIONAL */
-    "Content-Length: %zu\r\n"
+    "Content-Length: 13\r\n"
     "Content-Type: text/plain\r\n" /* at least firefox 144 needs this */
     "Server: %s\r\n"
     "\r\n"
-    "404 %s not found",
-    14 + strlen(filename),
-    stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH,
-    filename);
+    "404 Not Found",
+    stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH);
   tputs(dcc[idx].sock, response, i);
   nfree(response);
   killsock(dcc[idx].sock);
@@ -94,7 +90,7 @@ static void put_file(int idx, const char *filename, const char *content_type) {
 
   if ((fd = open(filename, O_RDONLY)) < 0) {
     putlog(LOG_MISC, "*", "WEBUI error: open(%s): %s", filename, strerror(errno));
-    put_404(idx, filename);
+    put_404(idx);
     return;
   }
   if (fstat(fd, &sb) < 0) {
@@ -260,7 +256,7 @@ static void webui_http_activity(int idx, char *buf, int len)
     put_file(idx, "webui/apple-touch-icon.png", "image/png");
   } else { /* TODO: send 404 or something ? */
     debug0("webui: 404");
-    put_404(idx, "");
+    put_404(idx);
   }
   if ((dcc[idx].sock != -1) && (len == 511)) { /* sock == -1 if lostdcc() in dcc_telnet_hostresolved2() */
     /* read probable remaining bytes */

From 9d4179bb9492029099909fa06c183c34e3182c1a Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Fri, 24 Oct 2025 02:55:34 +0200
Subject: [PATCH 60/76] Cache file data by modification time

---
 src/mod/webui.mod/webui.c | 73 ++++++++++++++++++++++++---------------
 1 file changed, 45 insertions(+), 28 deletions(-)

diff --git a/src/mod/webui.mod/webui.c b/src/mod/webui.mod/webui.c
index a8ab6d542e..d8d36575e2 100644
--- a/src/mod/webui.mod/webui.c
+++ b/src/mod/webui.mod/webui.c
@@ -82,29 +82,50 @@ static void put_404(int idx) {
   lostdcc(idx);
 }
 
-static void put_file(int idx, const char *filename, const char *content_type) {
-  int fd, i;
+/* cache file data by modification time */
+struct file_cache_struct {
+  char filename[27];
+  char content_type[25];
+  struct timespec st_mtim;
+  char *data;
+} file_cache[3] = {
+  {"webui/apple-touch-icon.png", "image/png",                { .tv_sec = -1, .tv_nsec = -1 }, NULL},
+  {"webui/favicon.ico",          "image/x-icon",             { .tv_sec = -1, .tv_nsec = -1 }, NULL},
+  {"webui/index.html",           "text/html; charset=utf-8", { .tv_sec = -1, .tv_nsec = -1 }, NULL}
+};
+
+static void put_file(int idx, int file_cache_index) {
   struct stat sb;
-  char *body;
+  struct file_cache_struct *f = &file_cache[file_cache_index];
+  int fd, i;
   char *response;
 
-  if ((fd = open(filename, O_RDONLY)) < 0) {
-    putlog(LOG_MISC, "*", "WEBUI error: open(%s): %s", filename, strerror(errno));
-    put_404(idx);
+  if (stat(f->filename, &sb) < 0) {
+    putlog(LOG_MISC, "*", "WEBUI error: fstat(%s): %s", f->filename, strerror(errno));
     return;
   }
-  if (fstat(fd, &sb) < 0) {
-    putlog(LOG_MISC, "*", "WEBUI error: fstat(%s): %s", filename, 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(%s): %s\n", filename, strerror(errno));
-    return;
+  if ((f->st_mtim.tv_sec != sb.st_mtim.tv_sec) ||
+      (f->st_mtim.tv_nsec != sb.st_mtim.tv_nsec)) {
+    if ((fd = open(f->filename, O_RDONLY)) < 0) {
+      putlog(LOG_MISC, "*", "WEBUI error: open(%s): %s", f->filename, strerror(errno));
+      put_404(idx);
+      return;
+    }
+    f->data = nrealloc(f->data, sb.st_size); /* TODO: nfree() on module unloading */
+    if (read(fd, f->data, sb.st_size) < 0) {
+      putlog(LOG_MISC, "*", "WEBUI error: read(%s): %s", f->filename, strerror(errno));
+      if ((fd = close(fd)) < 0)
+        putlog(LOG_MISC, "*", "WEBUI error: close(%s): %s", f->filename, strerror(errno));
+      put_404(idx);
+      return;
+    }
+    if ((fd = close(fd)) < 0) {
+      putlog(LOG_MISC, "*", "WEBUI error: close(%s): %s", f->filename, strerror(errno));
+      put_404(idx);
+      return;
+    }
+    f->st_mtim.tv_sec = sb.st_mtim.tv_sec;
+    f->st_mtim.tv_nsec = sb.st_mtim.tv_nsec;
   }
   i = snprintf(NULL, 0,
     "HTTP/1.1 200 \r\n" /* textual phrase is OPTIONAL */
@@ -112,7 +133,7 @@ static void put_file(int idx, const char *filename, const char *content_type) {
     "Content-Type: %s\r\n" /* at least firefox 144 needs this */
     "Server: %s\r\n"
     "\r\n",
-    sb.st_size, content_type,
+    sb.st_size, f->content_type,
     stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH);
   response = nmalloc(i + sb.st_size);
   sprintf(response,
@@ -121,16 +142,12 @@ static void put_file(int idx, const char *filename, const char *content_type) {
     "Content-Type: %s\r\n" /* at least firefox 144 needs this */
     "Server: %s\r\n"
     "\r\n",
-    sb.st_size, content_type,
+    sb.st_size, f->content_type,
     stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH);
-  memcpy(response + i, body, sb.st_size);
+  memcpy(response + i, f->data, sb.st_size);
   tputs(dcc[idx].sock, response, i + sb.st_size);
   // debug2("webui: tputs(): >>>%s<<< %i", response, i);
   nfree(response);
-  if (munmap(body, sb.st_size) < 0) {
-    putlog(LOG_MISC, "*", "WEBUI error: munmap(): %s", strerror(errno));
-    return;
-  }
 }
 
 static void webui_http_activity(int idx, char *buf, int len)
@@ -162,10 +179,10 @@ static void webui_http_activity(int idx, char *buf, int len)
   debug0("webui: http()");
   if (buf[5] == ' ') {
     debug0("webui: GET /");
-    put_file(idx, "webui/index.html", "text/html; charset=utf-8");
+    put_file(idx, 2);
   } else if (buf[5] == 'f') {
     debug0("webui: GET /favicon.ico");
-    put_file(idx, "webui/favicon.ico", "image/x-icon");
+    put_file(idx, 1);
   } else if (buf[5] == 'w') {
     debug0("webui: GET /w");
     buf = strstr(buf, WS_KEY);
@@ -253,7 +270,7 @@ static void webui_http_activity(int idx, char *buf, int len)
     debug2("webui: CHANGEOVER -> idx %i sock %li", idx, dcc[idx].sock);
   } else if (buf[5] == 'a') {
     debug0("webui: GET /apple-touch-icon.png");
-    put_file(idx, "webui/apple-touch-icon.png", "image/png");
+    put_file(idx, 0);
   } else { /* TODO: send 404 or something ? */
     debug0("webui: 404");
     put_404(idx);

From 3934102a97f2e898bbb282fc2fa4a35976e8731d Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Fri, 24 Oct 2025 04:14:21 +0200
Subject: [PATCH 61/76] Cleanup

---
 src/mod/webui.mod/webui.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/mod/webui.mod/webui.c b/src/mod/webui.mod/webui.c
index d8d36575e2..3d73d4082c 100644
--- a/src/mod/webui.mod/webui.c
+++ b/src/mod/webui.mod/webui.c
@@ -71,7 +71,7 @@ static void put_404(int idx) {
   sprintf(response,
     "HTTP/1.1 404 \r\n" /* textual phrase is OPTIONAL */
     "Content-Length: 13\r\n"
-    "Content-Type: text/plain\r\n" /* at least firefox 144 needs this */
+    "Content-Type: text/plain\r\n"
     "Server: %s\r\n"
     "\r\n"
     "404 Not Found",

From aa1e19f73ca32acfeee748650c29aa5af0a4c361 Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Fri, 24 Oct 2025 05:59:38 +0200
Subject: [PATCH 62/76] Fix module unloading

---
 src/mod/webui.mod/webui.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/src/mod/webui.mod/webui.c b/src/mod/webui.mod/webui.c
index 3d73d4082c..53c4bc9cda 100644
--- a/src/mod/webui.mod/webui.c
+++ b/src/mod/webui.mod/webui.c
@@ -459,9 +459,26 @@ static void webui_unframe(char *buf, int *len)
 
 static char *webui_close(void)
 {
+  int idx;
+
   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);
+  /*
+  for (int j = 0; j < dcc_total; j++) {
+      debug4("dcc table %i %li %i %s", j, dcc[j].sock, dcc[j].ssl, dcc[j].host);
+      debug4("             %s %s %i %x", dcc[j].nick, dcc[j].type->name, findsock(dcc[j].sock), socklist[findsock(dcc[j].sock)].flags);
+    }
+  */
+  for (idx = 0; idx < dcc_total; idx++) {
+    if (!strcmp(dcc[idx].nick, "(webui)") ||
+        !strcmp(dcc[idx].type->name, "WEBUI_HTTP") ||
+        (socklist[findsock(dcc[idx].sock)].flags & SOCK_WS)) {
+      debug1("webui: webui_close(): closing idx %i", idx);
+      killsock(dcc[idx].sock);
+      lostdcc(idx);
+    }
+  }
   return NULL;
 }
 

From 162eea8fe030a48d86e4139b2fd9a19887c9ad34 Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Fri, 24 Oct 2025 06:03:01 +0200
Subject: [PATCH 63/76] Log sock instead if idx

---
 src/mod/webui.mod/webui.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/mod/webui.mod/webui.c b/src/mod/webui.mod/webui.c
index 53c4bc9cda..4be76072ec 100644
--- a/src/mod/webui.mod/webui.c
+++ b/src/mod/webui.mod/webui.c
@@ -474,7 +474,7 @@ static char *webui_close(void)
     if (!strcmp(dcc[idx].nick, "(webui)") ||
         !strcmp(dcc[idx].type->name, "WEBUI_HTTP") ||
         (socklist[findsock(dcc[idx].sock)].flags & SOCK_WS)) {
-      debug1("webui: webui_close(): closing idx %i", idx);
+      debug1("webui: webui_close(): closing sock %li", dcc[idx].sock);
       killsock(dcc[idx].sock);
       lostdcc(idx);
     }

From c753d8219c1c1be161b91caceda1006f39f998cd Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Fri, 24 Oct 2025 06:17:51 +0200
Subject: [PATCH 64/76] Cleanup

---
 src/mod/webui.mod/index.html | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/mod/webui.mod/index.html b/src/mod/webui.mod/index.html
index 7471f6e228..2da342e082 100644
--- a/src/mod/webui.mod/index.html
+++ b/src/mod/webui.mod/index.html
@@ -2,7 +2,8 @@
 SPDX-License-Identifier: GPL-2.0-only
 Copyright (C) 2023 - 2025 Michael Ortmann MIT License
 Copyright (C) 2025 Eggheads Development Team
-We use Prettier as a code formatter to keep the code style consistent (and to avoid off-topic discussions)
+We use Prettier as a code formatter to keep the code style consistent (and to
+avoid off-topic discussions)
 -->
 
 

From 6f139f7a268c5539e828537f553f92a8fdb7cb68 Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Fri, 24 Oct 2025 08:04:31 +0200
Subject: [PATCH 65/76] Add comment

---
 src/tls.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/tls.c b/src/tls.c
index e630c822f3..40412f7616 100644
--- a/src/tls.c
+++ b/src/tls.c
@@ -1080,6 +1080,8 @@ int ssl_handshake(int sock, int flags, int verify, int loglevel, char *host,
           stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH,
           body);
       write(sock, response, j); // TODO: after reading of remaining bytes / ssl shutdown ?
+			// we cannot use tputs() here, so we use write()
+			// we should check the result value for error / short write
     } else {
       putlog(data->loglevel, "*",
              "TLS: handshake failed due to the following error: %s",

From f20bc0c62e8483465bb15f65bd715837f35d62df Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Fri, 24 Oct 2025 20:21:05 +0200
Subject: [PATCH 66/76] Cleanup and comment

---
 src/dcc.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/src/dcc.c b/src/dcc.c
index cfe4e27836..abaff6e5b6 100644
--- a/src/dcc.c
+++ b/src/dcc.c
@@ -1326,12 +1326,12 @@ static void dcc_telnet(int idx, char *buf, int i)
  * 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 */
+  int sock, j;
+
   /* Skip ident lookup if disabled */
   if (identtimeout <= 0) {
     dcc[i].u.ident_sock = dcc[idx].sock;
-    dcc_telnet_got_ident(i, userhost);
+    dcc_telnet_got_ident(i, dcc[idx].host);
     return;
   }
 
@@ -1366,7 +1366,7 @@ void dcc_telnet_hostresolved2(int i, int idx) {
     }
   }
   if (j < 0) {
-    dcc_telnet_got_ident(i, userhost);
+    dcc_telnet_got_ident(i, dcc[idx].host);
     return;
   }
   dcc[j].sock = sock;
@@ -1382,6 +1382,7 @@ void dcc_telnet_hostresolved2(int i, int idx) {
   dprintf(j, "%d, %d\n", dcc[i].port, dcc[idx].port);
 }
 
+/* dcc[i].type == DNSWAIT */
 static void dcc_telnet_hostresolved(int i)
 {
   int idx;

From b64370ca21ce5ee500f2b632360908723b327f94 Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Fri, 24 Oct 2025 21:17:56 +0200
Subject: [PATCH 67/76] Indent, also add temp. assert while this PR is in
 development

---
 src/dcc.c | 27 +++++++++++++++------------
 1 file changed, 15 insertions(+), 12 deletions(-)

diff --git a/src/dcc.c b/src/dcc.c
index abaff6e5b6..eead71d220 100644
--- a/src/dcc.c
+++ b/src/dcc.c
@@ -2432,19 +2432,22 @@ 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 && strcmp(dcc[idx].nick, "(webui)"))
-      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, "(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? */
+  /* 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 */
+  debug2("sinnsuche: %s %s", dcc[i].nick, dcc[idx].host);
+  #include 
+  assert(!strcmp(dcc[idx].host, "*"));
+  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 : "*");

From 9bebb3497bdaa3457215d5467f17649b37f38920 Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Fri, 24 Oct 2025 22:47:09 +0200
Subject: [PATCH 68/76] Bufsize

---
 src/mod/webui.mod/webui.c |  2 +-
 src/tls.c                 | 20 +++++++++++++++-----
 2 files changed, 16 insertions(+), 6 deletions(-)

diff --git a/src/mod/webui.mod/webui.c b/src/mod/webui.mod/webui.c
index 4be76072ec..db815b8394 100644
--- a/src/mod/webui.mod/webui.c
+++ b/src/mod/webui.mod/webui.c
@@ -67,7 +67,7 @@ static void put_404(int idx) {
     "\r\n"
     "404 Not Found",
     stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH);
-  response = nmalloc(i);
+  response = nmalloc(i + 1);
   sprintf(response,
     "HTTP/1.1 404 \r\n" /* textual phrase is OPTIONAL */
     "Content-Length: 13\r\n"
diff --git a/src/tls.c b/src/tls.c
index 40412f7616..62d8e7b486 100644
--- a/src/tls.c
+++ b/src/tls.c
@@ -1069,9 +1069,9 @@ int ssl_handshake(int sock, int flags, int verify, int loglevel, char *host,
       /* We dont have access to real port, host or dcc information here */
       putlog(LOG_MISC, "*", "TLS: error: HTTP request received on an SSL port");
       int j;
-      char response[256];
+      char *response;
       char *body = "Error: HTTP request received on an SSL port, please try HTTPS";
-      j = snprintf(response, sizeof response,
+      j = snprintf(NULL, 0,
         "HTTP/1.1 200 \r\n" /* textual phrase is OPTIONAL */
         "Content-Length: %zu\r\n"
         "Content-Type: text/plain; charset=utf-8\r\n"
@@ -1079,9 +1079,19 @@ int ssl_handshake(int sock, int flags, int verify, int loglevel, char *host,
         "\r\n%s", strlen(body),
           stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH,
           body);
-      write(sock, response, j); // TODO: after reading of remaining bytes / ssl shutdown ?
-			// we cannot use tputs() here, so we use write()
-			// we should check the result value for error / short write
+      response = nmalloc(j + 1);
+      sprintf(response,
+        "HTTP/1.1 200 \r\n" /* textual phrase is OPTIONAL */
+        "Content-Length: %zu\r\n"
+        "Content-Type: text/plain; charset=utf-8\r\n"
+        "Server: %s\r\n"
+        "\r\n%s", strlen(body),
+          stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH,
+          body);
+      if (write(sock, response, j) < 0) /* tputs() cannot be used here */
+        putlog(LOG_MISC, "*", "TLS: error: write(sock %i): %s", sock, strerror(errno));
+      // TODO: after reading of remaining bytes / ssl shutdown ?
+      nfree(response);
     } else {
       putlog(data->loglevel, "*",
              "TLS: handshake failed due to the following error: %s",

From fb1750552e60950f88adf625a3fae5951cd52715 Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Sun, 26 Oct 2025 04:12:11 +0100
Subject: [PATCH 69/76] Fix telnet echo and esc bold codes

---
 src/dcc.c                 | 18 +++++-----------
 src/eggdrop.h             |  3 ---
 src/misc.c                |  6 +++---
 src/mod/webui.mod/webui.c | 44 ++++++++++++++++++++++++++++++++++++++-
 4 files changed, 51 insertions(+), 20 deletions(-)

diff --git a/src/dcc.c b/src/dcc.c
index eead71d220..073dd99561 100644
--- a/src/dcc.c
+++ b/src/dcc.c
@@ -737,10 +737,8 @@ static void dcc_chat_pass(int idx, char *buf, int atr)
       dcc[idx].u.chat->con_flags = (atr & USER_MASTER) ? conmask : 0;
       dcc[idx].u.chat->channel = -2;
       /* Turn echo back on for telnet sessions (send IAC WON'T ECHO). */
-      if (dcc[idx].status & STAT_TELNET)
+      if (dcc[idx].status & (STAT_TELNET | STAT_WS))
         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 {
@@ -1851,20 +1849,14 @@ static void dcc_telnet_pass(int idx, int atr)
      *       
      */
 
-    /* Turn off remote telnet echo */
-    char buf[512];
-    if (dcc[idx].status & STAT_TELNET) {
-      /* For telnet sessions send IAC WILL ECHO */
+    /* Turn off remote telnet echo (send IAC WILL ECHO). */
+    if (dcc[idx].status & (STAT_TELNET | STAT_WS)) {
+      char buf[512];
       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 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 {
+    } else
       dprintf(idx, "\n%s\n", DCC_ENTERPASS);
-    }
   }
 }
 
diff --git a/src/eggdrop.h b/src/eggdrop.h
index f360c99c7b..8b1f208ddc 100644
--- a/src/eggdrop.h
+++ b/src/eggdrop.h
@@ -758,7 +758,4 @@ struct dns_thread_node {
 extern struct dns_thread_node *dns_thread_head;
 #endif
 
-#define WS_ECHO_ON  "\x01" /* webui websocket echo on */
-#define WS_ECHO_OFF "\x02" /* webui websocket echo off */
-
 #endif /* _EGG_EGGDROP_H */
diff --git a/src/misc.c b/src/misc.c
index 590960d7b6..0d7e53613d 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -1218,7 +1218,7 @@ static int display_tellhelp(int idx, char *file, FILE *f,
 
   if (f) {
     help_subst(NULL, NULL, 0,
-               (dcc[idx].status & STAT_TELNET) ? 0 : HELP_IRC, NULL);
+               (dcc[idx].status & (STAT_TELNET | STAT_WS)) ? 0 : HELP_IRC, NULL);
     /* don't check for feof after fgets, skips last line if it has no \n (ie on windows) */
     while (!feof(f) && fgets(s, HELP_BUF_LEN, f) != NULL) {
       if (s[strlen(s) - 1] == '\n')
@@ -1309,7 +1309,7 @@ void sub_lang(int idx, char *text)
 
   get_user_flagrec(dcc[idx].user, &fr, dcc[idx].u.chat->con_chan);
   help_subst(NULL, NULL, 0,
-             (dcc[idx].status & STAT_TELNET) ? 0 : HELP_IRC, NULL);
+             (dcc[idx].status & (STAT_TELNET | STAT_WS)) ? 0 : HELP_IRC, NULL);
   strlcpy(s, text, sizeof s);
   if (s[strlen(s) - 1] == '\n')
     s[strlen(s) - 1] = 0;
@@ -1350,7 +1350,7 @@ void show_motd(int idx)
   dprintf(idx, "\n");
   /* reset the help_subst variables to their defaults */
   help_subst(NULL, NULL, 0,
-             (dcc[idx].status & STAT_TELNET) ? 0 : HELP_IRC, NULL);
+             (dcc[idx].status & (STAT_TELNET | STAT_WS)) ? 0 : HELP_IRC, NULL);
   /* don't check for feof after fgets, skips last line if it has no \n (ie on windows) */
   while (!feof(vv) && fgets(s, sizeof s, vv) != NULL) {
     if (s[strlen(s) - 1] == '\n')
diff --git a/src/mod/webui.mod/webui.c b/src/mod/webui.mod/webui.c
index db815b8394..b096f095b5 100644
--- a/src/mod/webui.mod/webui.c
+++ b/src/mod/webui.mod/webui.c
@@ -40,6 +40,8 @@
 #define WS_LEN    28 /* length of Sec-WebSocket-Accept header field value
                       * base64(len(sha1))
                       * import math; (4 * math.ceil(20 / 3)) */
+#define WS_ECHO_ON  0x01
+#define WS_ECHO_OFF 0x02
 
 static Function *global = NULL;
 
@@ -330,7 +332,7 @@ static size_t escape_html(char *dst, char *src, size_t size) {
   char *d = dst;
 
   for (i = 0; i < size; i++) {
-    switch (src[i]) {
+    switch ((unsigned char) src[i]) {
       case '"':
         *d++ = '&';
         *d++ = 'q';
@@ -366,6 +368,45 @@ static size_t escape_html(char *dst, char *src, size_t size) {
         *d++ = 't';
         *d++ = ';';
         break;
+      case ESC:
+        // debug0("escape_html(): ESC");
+        if ((i + 4) < size) {
+          if (     ((unsigned char) src[i + 1] == '[') &&
+                   ((unsigned char) src[i + 2] == '0') &&
+                   ((unsigned char) src[i + 3] == 'm')) {
+            *d++ = '<';
+            *d++ = 'b';
+            *d++ = '>';
+          } else if (((unsigned char) src[i + 1] == '[') &&
+                   ((unsigned char) src[i + 2] == '1') &&
+                   ((unsigned char) src[i + 3] == 'm')) {
+            *d++ = '<';
+            *d++ = 'b';
+            *d++ = '/';
+            *d++ = '>';
+          } else
+            debug3("webui: escape_html(): unknown escape sequence found, skipping, %x %x %x, PLEASE REPORT THIS BUG",
+                   (unsigned char) src[i + 1], (unsigned char) src[i + 2], (unsigned char) src[i + 3]);
+          i += 3;
+        } else
+          debug0("webui: escape_html(): unknown SHORT escape sequence found, skipping, PLEASE REPORT THIS BUG");
+        break;
+      case TLN_IAC:
+        // debug0("escape_html(): TLN_IAC_C");
+        if ((i + 2) < size) {
+          if (     ((unsigned char) src[i + 1] == TLN_WILL) &&
+                   ((unsigned char) src[i + 2] == TLN_ECHO))
+            *d++ = WS_ECHO_OFF;
+          else if (((unsigned char) src[i + 1] == TLN_WONT) &&
+                   ((unsigned char) src[i + 2] == TLN_ECHO))
+            *d++ = WS_ECHO_ON;
+          else
+            debug2("webui: escape_html(): unknown telnet command found, skipping, %x %x, PLEASE REPORT THIS BUG",
+                   (unsigned char) src[i + 1], (unsigned char) src[i + 2]);
+          i += 2;
+        } else
+          debug0("webui: escape_html(): unknown SHORT telnet command found, skipping, PLEASE REPORT THIS BUG");
+        break;
       default:
         *d++ = src[i];
     }
@@ -383,6 +424,7 @@ static size_t webui_frame(char **dst, char *src, size_t len) {
   len = escape_html(buf + 4, src, len);
   /* we must not use putlog() or debug() here or we get recursion */
   /* A server MUST NOT mask any frames that it sends to the client */
+  /* we use text, not binary, so escape_html() must output valid html */
   if (len < 0x7e) {
     buf[2] =0x81; /* FIN + text frame */
     buf[3] = len;

From 739378c707ad3d98d3475f218217ba214991f3b5 Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Sun, 26 Oct 2025 16:36:19 +0100
Subject: [PATCH 70/76] Fix bold - thank you CrazyCat

---
 src/mod/webui.mod/webui.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/mod/webui.mod/webui.c b/src/mod/webui.mod/webui.c
index b096f095b5..2bbedd0595 100644
--- a/src/mod/webui.mod/webui.c
+++ b/src/mod/webui.mod/webui.c
@@ -375,6 +375,7 @@ static size_t escape_html(char *dst, char *src, size_t size) {
                    ((unsigned char) src[i + 2] == '0') &&
                    ((unsigned char) src[i + 3] == 'm')) {
             *d++ = '<';
+            *d++ = '/';
             *d++ = 'b';
             *d++ = '>';
           } else if (((unsigned char) src[i + 1] == '[') &&
@@ -382,7 +383,6 @@ static size_t escape_html(char *dst, char *src, size_t size) {
                    ((unsigned char) src[i + 3] == 'm')) {
             *d++ = '<';
             *d++ = 'b';
-            *d++ = '/';
             *d++ = '>';
           } else
             debug3("webui: escape_html(): unknown escape sequence found, skipping, %x %x %x, PLEASE REPORT THIS BUG",

From 185d2aca39d86bd34f4e23009c279f9594cf756b Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Sun, 26 Oct 2025 20:46:15 +0100
Subject: [PATCH 71/76] Fix format specifier for off_t

---
 src/mod/webui.mod/webui.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/mod/webui.mod/webui.c b/src/mod/webui.mod/webui.c
index 2bbedd0595..863dc3dd9b 100644
--- a/src/mod/webui.mod/webui.c
+++ b/src/mod/webui.mod/webui.c
@@ -131,20 +131,20 @@ static void put_file(int idx, int file_cache_index) {
   }
   i = snprintf(NULL, 0,
     "HTTP/1.1 200 \r\n" /* textual phrase is OPTIONAL */
-    "Content-Length: %li\r\n"
+    "Content-Length: %jd\r\n"
     "Content-Type: %s\r\n" /* at least firefox 144 needs this */
     "Server: %s\r\n"
     "\r\n",
-    sb.st_size, f->content_type,
+    (intmax_t) sb.st_size, f->content_type,
     stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH);
   response = nmalloc(i + sb.st_size);
   sprintf(response,
     "HTTP/1.1 200 \r\n" /* textual phrase is OPTIONAL */
-    "Content-Length: %li\r\n"
+    "Content-Length: %jd\r\n"
     "Content-Type: %s\r\n" /* at least firefox 144 needs this */
     "Server: %s\r\n"
     "\r\n",
-    sb.st_size, f->content_type,
+    (intmax_t) sb.st_size, f->content_type,
     stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH);
   memcpy(response + i, f->data, sb.st_size);
   tputs(dcc[idx].sock, response, i + sb.st_size);

From 4562bcf0ccb87adc4f63930f3cb71d370772e67a Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Sun, 30 Nov 2025 18:01:42 +0100
Subject: [PATCH 72/76] Update Makefile.in

---
 Makefile.in | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Makefile.in b/Makefile.in
index 80d40960be..22ff1e722e 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -492,7 +492,7 @@ install-filesys: install-start
 install-webui: install-start
 	@if test ! -d $(DEST)/webui; then \
 		echo "Creating webui subdirectory."; \
-		$(top_srcdir)/misc/mkinstalldirs $(DEST)/webui >/dev/null; \
+		$(MKDIR_P) $(DEST)/webui >/dev/null; \
 	fi && \
 	if test ! -f $(DEST)/webui/apple-touch-icon.png; then \
 		$(INSTALL_DATA) $(srcdir)/src/mod/webui.mod/apple-touch-icon.png $(DEST)/webui/; \

From 457939e8e13f5354e304a05f90be6c0551c85782 Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Sun, 30 Nov 2025 19:11:01 +0100
Subject: [PATCH 73/76] Fix tls

---
 src/tls.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/tls.c b/src/tls.c
index b785ef9e93..7b24e9278e 100644
--- a/src/tls.c
+++ b/src/tls.c
@@ -240,6 +240,7 @@ int ssl_init()
 #if OPENSSL_VERSION_NUMBER < 0x10100000L /* 1.1.0 */
     ERR_free_strings();
 #endif
+
     return -1;
   }
   ssl_files_loaded = 0;
@@ -405,6 +406,8 @@ int ssl_init()
     ssl_ctx = NULL;
     return -3;
   }
+  const unsigned char sid_ctx[] = "0";
+  SSL_CTX_set_session_id_context(ssl_ctx, sid_ctx, (sizeof sid_ctx) - 1);
   return 0;
 }
 

From 462a287226e2a29b1b5a9ef59b4a63529bdc9053 Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Sun, 30 Nov 2025 20:21:11 +0100
Subject: [PATCH 74/76] Cleanup

---
 Makefile.in | 4 ++--
 src/dcc.c   | 5 +----
 src/tls.c   | 3 +--
 3 files changed, 4 insertions(+), 8 deletions(-)

diff --git a/Makefile.in b/Makefile.in
index 22ff1e722e..79e975ae4e 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -485,8 +485,8 @@ install-filesys: install-start
 		$(MKDIR_P) $(DEST)/filesys/incoming >/dev/null; \
 	fi
 
-# We cannot do this in webui.mod Makefile because there is no make target
-# and we cannot create such make target with breaking every other mod Makefile
+# We cannot do this in webui.mod Makefile because there is no make target and
+# we cannot create such make target without breaking every other mod Makefile.
 # The only alternative i see would be to hardcode the 3 files into char arrays
 # but this is ugly. File systems were invented for a reason.
 install-webui: install-start
diff --git a/src/dcc.c b/src/dcc.c
index 073dd99561..cc69222875 100644
--- a/src/dcc.c
+++ b/src/dcc.c
@@ -2436,10 +2436,7 @@ static void dcc_telnet_got_ident(int i, char *host)
   else if (!strcmp(dcc[idx].nick, "(webui)"))
     dcc[i].status |= STAT_WS;
   /* Copy acceptable-nick/host mask */
-  debug2("sinnsuche: %s %s", dcc[i].nick, dcc[idx].host);
-  #include 
-  assert(!strcmp(dcc[idx].host, "*"));
-  strlcpy(dcc[i].nick, dcc[idx].host, HANDLEN); /* wo ist hier der sinn? dcc[idx].host ist immer *, oder? */
+  strlcpy(dcc[i].nick, dcc[idx].host, sizeof dcc[i].nick);
 
   dcc[i].timeval = now;
   strcpy(dcc[i].u.chat->con_chan, chanset ? chanset->dname : "*");
diff --git a/src/tls.c b/src/tls.c
index 7b24e9278e..37611b3b5a 100644
--- a/src/tls.c
+++ b/src/tls.c
@@ -240,7 +240,6 @@ int ssl_init()
 #if OPENSSL_VERSION_NUMBER < 0x10100000L /* 1.1.0 */
     ERR_free_strings();
 #endif
-
     return -1;
   }
   ssl_files_loaded = 0;
@@ -406,7 +405,7 @@ int ssl_init()
     ssl_ctx = NULL;
     return -3;
   }
-  const unsigned char sid_ctx[] = "0";
+  const unsigned char sid_ctx[] = "0"; /* anything will do */
   SSL_CTX_set_session_id_context(ssl_ctx, sid_ctx, (sizeof sid_ctx) - 1);
   return 0;
 }

From fa42d01eebc2f83251f46af306cfd02d1eac36ca Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Sun, 7 Dec 2025 21:38:02 +0100
Subject: [PATCH 75/76] Cleanup

---
 src/mod/webui.mod/webui.c | 33 +++------------------------------
 1 file changed, 3 insertions(+), 30 deletions(-)

diff --git a/src/mod/webui.mod/webui.c b/src/mod/webui.mod/webui.c
index 863dc3dd9b..05950a526d 100644
--- a/src/mod/webui.mod/webui.c
+++ b/src/mod/webui.mod/webui.c
@@ -61,6 +61,7 @@ static void put_404(int idx) {
   int i;
   char *response;
 
+  debug1("webui: put_404() idx %i", idx);
   i = snprintf(NULL, 0,
     "HTTP/1.1 404 \r\n" /* textual phrase is OPTIONAL */
     "Content-Length: 13\r\n"
@@ -148,7 +149,6 @@ static void put_file(int idx, int file_cache_index) {
     stealth_telnets ? "nginx/1.28.0" : "Eggdrop/" EGG_STRINGVER "+" EGG_PATCH);
   memcpy(response + i, f->data, sb.st_size);
   tputs(dcc[idx].sock, response, i + sb.st_size);
-  // debug2("webui: tputs(): >>>%s<<< %i", response, i);
   nfree(response);
 }
 
@@ -178,7 +178,6 @@ static void webui_http_activity(int idx, char *buf, int len)
   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: GET /");
     put_file(idx, 2);
@@ -199,7 +198,6 @@ static void webui_http_activity(int idx, char *buf, int len)
         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();
@@ -239,7 +237,6 @@ static void webui_http_activity(int idx, char *buf, int len)
       "Sec-WebSocket-Accept: %s\r\n"
       "\r\n", out);
     tputs(dcc[idx].sock, response, i);
-    // debug2("webui: tputs(): >>>%s<<< %i", response, i);
     nfree(response);
 
     sock_list* socklist_i = &socklist[findsock(dcc[idx].sock)];
@@ -253,19 +250,12 @@ static void webui_http_activity(int idx, char *buf, int len)
     debug4("webui: set flag SOCK_WS socklist %i idx %i sock %li status %lu", 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? */
+    for (i = 0; i < dcc_total; i++) /* we need to link from idx, dont we? is there a better way to do it? */
       if (!strcmp(dcc[i].nick, "(webui)")) {
         debug1("webui: found (webui) dcc %i", 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);
 
@@ -273,10 +263,8 @@ static void webui_http_activity(int idx, char *buf, int len)
   } else if (buf[5] == 'a') {
     debug0("webui: GET /apple-touch-icon.png");
     put_file(idx, 0);
-  } else { /* TODO: send 404 or something ? */
-    debug0("webui: 404");
+  } else
     put_404(idx);
-  }
   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;
@@ -369,7 +357,6 @@ static size_t escape_html(char *dst, char *src, size_t size) {
         *d++ = ';';
         break;
       case ESC:
-        // debug0("escape_html(): ESC");
         if ((i + 4) < size) {
           if (     ((unsigned char) src[i + 1] == '[') &&
                    ((unsigned char) src[i + 2] == '0') &&
@@ -392,7 +379,6 @@ static size_t escape_html(char *dst, char *src, size_t size) {
           debug0("webui: escape_html(): unknown SHORT escape sequence found, skipping, PLEASE REPORT THIS BUG");
         break;
       case TLN_IAC:
-        // debug0("escape_html(): TLN_IAC_C");
         if ((i + 2) < size) {
           if (     ((unsigned char) src[i + 1] == TLN_WILL) &&
                    ((unsigned char) src[i + 2] == TLN_ECHO))
@@ -466,12 +452,6 @@ static void webui_unframe(char *buf, int *len)
   }
   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
@@ -490,7 +470,6 @@ static void webui_unframe(char *buf, int *len)
   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() */
@@ -506,12 +485,6 @@ 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);
-  /*
-  for (int j = 0; j < dcc_total; j++) {
-      debug4("dcc table %i %li %i %s", j, dcc[j].sock, dcc[j].ssl, dcc[j].host);
-      debug4("             %s %s %i %x", dcc[j].nick, dcc[j].type->name, findsock(dcc[j].sock), socklist[findsock(dcc[j].sock)].flags);
-    }
-  */
   for (idx = 0; idx < dcc_total; idx++) {
     if (!strcmp(dcc[idx].nick, "(webui)") ||
         !strcmp(dcc[idx].type->name, "WEBUI_HTTP") ||

From 676e84258e8345cea1022a0cd56793ad7c8464d2 Mon Sep 17 00:00:00 2001
From: Michael Ortmann 
Date: Sun, 14 Dec 2025 03:22:20 +0100
Subject: [PATCH 76/76] Disable webui mod by default

---
 disabled_modules | 1 +
 1 file changed, 1 insertion(+)

diff --git a/disabled_modules b/disabled_modules
index ca1a38c4be..9ca2f8a9e4 100644
--- a/disabled_modules
+++ b/disabled_modules
@@ -5,6 +5,7 @@
 #   -  Lines which start with a '#' character are ignored.
 #   -  Every module name needs to be on its own line
 
+webui
 # Woobie only serves as an example for module programming. No need to
 # compile it for normal bots ...
 woobie