diff --git a/src/dcc.c b/src/dcc.c index cb6e686a6..ffee55230 100644 --- a/src/dcc.c +++ b/src/dcc.c @@ -1284,7 +1284,10 @@ static void dcc_telnet(int idx, char *buf, int i) return; } /* Buffer data received on this socket. */ - sockoptions(sock, EGG_OPTION_SET, SOCK_BUFFER); + if (!strcmp(dcc[idx].nick, "(webui)")) + sockoptions(sock, EGG_OPTION_SET, SOCK_BUFFER | SOCK_WEBUI); + else + sockoptions(sock, EGG_OPTION_SET, SOCK_BUFFER); if (port < 1024) { putlog(LOG_BOTS, "*", DCC_BADSRC, iptostr(&dcc[i].sockname.addr.sa), port); @@ -1343,6 +1346,8 @@ void dcc_telnet_hostresolved2(int i, int idx) { ident_target_port = getenv("EGGDROP_TEST") ? 1113 : 113; snprintf(userhost, sizeof userhost, "telnet@%s", dcc[i].host); + changeover_dcc(i, &DCC_IDENTWAIT, 0); + /* Skip ident lookup if disabled */ if (identtimeout <= 0) { dcc[i].u.ident_sock = dcc[idx].sock; @@ -1350,7 +1355,6 @@ void dcc_telnet_hostresolved2(int i, int idx) { return; } - changeover_dcc(i, &DCC_IDENTWAIT, 0); dcc[i].timeval = now; dcc[i].u.ident_sock = dcc[idx].sock; sock = -1; diff --git a/src/dccutil.c b/src/dccutil.c index 3ae31b242..eef06e67b 100644 --- a/src/dccutil.c +++ b/src/dccutil.c @@ -353,6 +353,18 @@ void lostdcc(int n) dcc[n].type = &DCC_LOST; } +/* Mark an entry as lost, to be reaped in the mainloop asynchronously. + * Avoids reentrancy issues + */ +void lostdcc_deferred(int n) +{ + /* sanity check */ + if (n < 0 || n >= max_dcc) { + return; + } + dcc[n].status |= STAT_LOSTDCC; +} + /* Remove entry from dcc list. Think twice before using this function, * because it invalidates any variables that point to a specific dcc * entry! @@ -385,6 +397,12 @@ void dcc_remove_lost(void) dcc[i].sock = -1; removedcc(i); i--; + } else if (dcc[i].status & STAT_LOSTDCC) { + /* intentionally after DCC_LOST cleanup, + * so it doesn't clean immediately + */ + dcc[i].status &= ~STAT_LOSTDCC; + lostdcc(i); } } } diff --git a/src/eggdrop.h b/src/eggdrop.h index 37c7a69a2..3ee93309b 100644 --- a/src/eggdrop.h +++ b/src/eggdrop.h @@ -482,6 +482,7 @@ struct dupwait_info { #define STAT_PAGE 0x00080 /* page output to the user */ #define STAT_SERV 0x00100 /* this is a server connection */ #define STAT_WS 0x00200 /* webui websocket */ +#define STAT_LOSTDCC 0x00400 /* closed by remote, call lostdcc() */ /* For stripping out mIRC codes. */ #define STRIP_COLOR 0x00001 /* remove mIRC color codes */ @@ -594,7 +595,8 @@ 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 */ +#define SOCK_WEBUI 0x1000 /* webui websocket pre-upgrade */ +#define SOCK_WS 0x2000 /* webui websocket after framed upgrade */ /* Flags to sock_has_data */ diff --git a/src/main.c b/src/main.c index 0847138b0..4b58342aa 100644 --- a/src/main.c +++ b/src/main.c @@ -163,8 +163,10 @@ void fatal(const char *s, int recoverable) putlog(LOG_MISC, "*", "* %s", s); for (i = 0; i < dcc_total; i++) - if (dcc[i].sock >= 0) + if (dcc[i].sock >= 0) { killsock(dcc[i].sock); + dcc[i].sock = -1; + } #ifdef TLS ssl_cleanup(); #endif diff --git a/src/modules.c b/src/modules.c index 1388ae9f6..00c7770e3 100644 --- a/src/modules.c +++ b/src/modules.c @@ -162,9 +162,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, int) = 0; -size_t (*webui_frame) (char **, char *, size_t) = 0; -void (*webui_unframe) (int, char *, int *) = 0; +void (*webui_dcc_telnet_hostresolved) (int, int) = (void (*)(int, int)) null_func; +size_t (*webui_frame) (char **, char *, size_t) = (size_t (*)(char **, char * ,size_t)) null_func; +void (*webui_unframe) (int, char *, int *) = (void (*)(int, char *, int *)) null_func; module_entry *module_list; dependancy *dependancy_list = NULL; diff --git a/src/net.c b/src/net.c index 8099f962b..032461f68 100644 --- a/src/net.c +++ b/src/net.c @@ -420,8 +420,8 @@ void killsock(int sock) int i; struct threaddata *td = threaddata(); - /* Ignore invalid sockets. */ - if (sock < 0) + /* Ignore invalid sockets and stdout/stderr. */ + if ((sock < 0) || (sock == STDOUT) || (sock == STDERR)) return; for (i = 0; i < td->MAXSOCKS; i++) { @@ -1366,12 +1366,17 @@ void tputs(int z, char *s, unsigned int len) else len = webui_frame(&s2, s, len); if (socklist[i].ssl) { + ERR_clear_error(); x = SSL_write(socklist[i].ssl, s2, len); if (x < 0) { int err = SSL_get_error(socklist[i].ssl, x); - if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) + if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) { errno = EAGAIN; - else if (!inhere) { /* Out there, somewhere */ + } else if (err == SSL_ERROR_ZERO_RETURN) { + /* Peer sent close notify, lostdcc_deferred() was already + * scheduled from ssl_info(). Don't queue more data. */ + return; + } else if (!inhere) { /* Out there, somewhere */ inhere = 1; debug1("tputs(): SSL error = %s", ERR_error_string(ERR_get_error(), 0)); @@ -1469,6 +1474,7 @@ void dequeue_sockets() errno = 0; #ifdef TLS if (socklist[i].ssl) { + ERR_clear_error(); x = SSL_write(socklist[i].ssl, socklist[i].handler.sock.outbuf, socklist[i].handler.sock.outbuflen); if (x < 0) { diff --git a/src/proto.h b/src/proto.h index 5b834e884..f7a641e17 100644 --- a/src/proto.h +++ b/src/proto.h @@ -154,6 +154,7 @@ extern void (*sharein) (int, char *); void chanout_but(int x, int chan, const char *format, ...) ATTRIBUTE_FORMAT(printf,3,4); void dcc_chatter(int); void lostdcc(int); +void lostdcc_deferred(int); void killtransfer(int); void removedcc(int); void makepass(char *); diff --git a/src/tls.c b/src/tls.c index 5b8cc72a3..e8d7429a7 100644 --- a/src/tls.c +++ b/src/tls.c @@ -136,21 +136,19 @@ static X509 *ssl_getcert(int sock) * Return value: ptr to the hexadecimal representation of the fingerprint or * NULL in case of error. */ -static char *ssl_getfp_from_cert(X509 *cert) +static char *ssl_getfp_from_cert(X509 *cert, const EVP_MD *type) { char *p; unsigned int i; - static char fp[SHA_DIGEST_LENGTH * 3]; - unsigned char md[SHA_DIGEST_LENGTH]; + static char fp[EVP_MAX_MD_SIZE * 3]; + unsigned char md[EVP_MAX_MD_SIZE]; - if (!X509_digest(cert, EVP_sha1(), md, &i)) { + if (!X509_digest(cert, type, 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); @@ -173,15 +171,17 @@ char *ssl_getfp(int sock) if (!(cert = ssl_getcert(sock))) return NULL; - fp = ssl_getfp_from_cert(cert); + fp = ssl_getfp_from_cert(cert, EVP_sha1()); #if OPENSSL_VERSION_NUMBER < 0x30000000L /* 3.0.0 */ X509_free(cert); #endif return fp; } +// FIXME: Assumption is fingerprint stays the same if path doesn't change void verify_cert_expiry(int idx) { X509 *x509; + static char last_tls_certfile[sizeof tls_certfile]; #if OPENSSL_VERSION_NUMBER >= 0x10002000L /* 1.0.2 */ x509 = SSL_CTX_get0_certificate(ssl_ctx); /* The returned pointer must not be freed by the caller. */ #else @@ -191,6 +191,12 @@ void verify_cert_expiry(int idx) { x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); #endif if (x509) { + if (strcmp(tls_certfile, last_tls_certfile)) { + const char *fp = ssl_getfp_from_cert(x509, EVP_sha256()); + putlog(LOG_MISC, "*", "Certificate loaded: %s (sha256 fingerprint %s)", + tls_certfile, fp ? fp : "(error getting fp)"); + strlcpy(last_tls_certfile, tls_certfile, sizeof last_tls_certfile); + } #if OPENSSL_VERSION_NUMBER >= 0x40000000L /* 4.0.0 */ int e; if (!X509_check_certificate_times(NULL, x509, &e) && (e == X509_V_ERR_CERT_HAS_EXPIRED)) { @@ -264,23 +270,6 @@ int ssl_init() tls_certfile, ERR_error_string(ERR_get_error(), NULL)); fatal("Unable to load TLS certificate (ssl-certificate config setting)!", 0); } - - /* TODO: sha256 fingerprint - * print this fingerprint to every user / every partyline login - * maybe only print it when webui is enabled - * compatibility to older openssl is possible, similar to #1411 - * this functionality could be sepped into a side-PR - * or functionality of #1411 can be reused once merged - * - * for now, just disable the fingerprint for openssl < 1.0.2 - */ -#if OPENSSL_VERSION_NUMBER >= 0x10002000L /* 1.0.2 */ - // TODO: ssl_getfp_from_cert() must not free the cert from SSL_CTX_get0_certificate() - putlog(LOG_MISC, "*", "Certificate loaded: %s (sha1 fingerprint %s)", - tls_certfile, - ssl_getfp_from_cert(SSL_CTX_get0_certificate(ssl_ctx))); -#endif - verify_cert_expiry(0); 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", @@ -950,9 +939,17 @@ static void ssl_info(const SSL *ssl, int where, int ret) !strcmp(SSL_alert_desc_string(ret), "RO")) putlog(LOG_MISC, "*", "TLS: Long TLSCiphertext field received, connection failed. Is this really a TLS port?"); } else { - /* Ignore close notify warnings */ - debug1("TLS: Received close notify during %s", - (where & SSL_CB_READ) ? "read" : "write"); + /* Ignore close notify warnings and stop writing to sock */ + sock = SSL_get_fd(ssl); + debug2("TLS: Received close notify during %s sock %i", + (where & SSL_CB_READ) ? "read" : "write", sock); + if (where & SSL_CB_WRITE) { + int i = findsock(sock); + if (i >= 0 && (threaddata()->socklist[i].flags & SOCK_WEBUI)) { + int idx = findidx(sock); + lostdcc_deferred(idx); + } + } } } else if (where & SSL_CB_EXIT) { /* SSL_CB_EXIT may point to soft error for non-blocking! */ @@ -1097,7 +1094,8 @@ int ssl_handshake(int sock, int flags, int verify, int loglevel, char *host, return 0; } if ((err = ERR_peek_error())) { - if (ERR_GET_LIB(ERR_peek_error()) == ERR_LIB_SSL && + if ((td->socklist[i].flags & SOCK_WEBUI) && + ERR_GET_LIB(ERR_peek_error()) == ERR_LIB_SSL && ERR_GET_REASON(err) == SSL_R_HTTP_REQUEST) { /* We dont have access to real port, host or dcc information here */ putlog(LOG_MISC, "*", "TLS: error: HTTP request received on an SSL port"); @@ -1105,7 +1103,7 @@ int ssl_handshake(int sock, int flags, int verify, int loglevel, char *host, char *response; char *body = "Error: HTTP request received on an SSL port, please try HTTPS"; j = snprintf(NULL, 0, - "HTTP/1.1 200 \r\n" /* textual phrase is OPTIONAL */ + "HTTP/1.1 400 \r\n" /* textual phrase is OPTIONAL */ "Content-Length: %zu\r\n" "Content-Type: text/plain; charset=utf-8\r\n" "Server: %s\r\n" @@ -1114,7 +1112,7 @@ int ssl_handshake(int sock, int flags, int verify, int loglevel, char *host, body); response = nmalloc(j + 1); sprintf(response, - "HTTP/1.1 200 \r\n" /* textual phrase is OPTIONAL */ + "HTTP/1.1 400 \r\n" /* textual phrase is OPTIONAL */ "Content-Length: %zu\r\n" "Content-Type: text/plain; charset=utf-8\r\n" "Server: %s\r\n" @@ -1123,7 +1121,6 @@ int ssl_handshake(int sock, int flags, int verify, int loglevel, char *host, 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, "*",