From 11e2894365d7ccef20ce45d8a83e65511a34be30 Mon Sep 17 00:00:00 2001 From: Michael Poole Date: Mon, 30 Dec 2019 22:00:47 -0500 Subject: [PATCH 01/20] Support SSL and TLS (UNTESTED) Add a user mode FLAG_TLS (+z) to indicate users connected via SSL or TLS. Add a channel mode MODE_TLSONLY (+Z) to only allow +z users to join or send messages to the channel. Add configuration settings and features to allow configuration of the underlying TLS library. The underlying TLS library is selected by ./configure --with-tls=, among several available implementations (include a stub, "none"). By design (to make behavior homogenous across a network), very little code depends on whether TLS is actually available or not. --- acinclude.m4 | 88 +++++++++ configure.ac | 2 + doc/example.conf | 54 +++++- doc/readme.features | 89 +++++++++ include/channel.h | 2 + include/client.h | 23 +++ include/handlers.h | 1 + include/ircd_events.h | 3 + include/ircd_features.h | 9 + include/ircd_tls.h | 166 ++++++++++++++++ include/listener.h | 5 + include/msgq.h | 1 + include/numeric.h | 2 + include/s_conf.h | 8 + include/s_misc.h | 2 + include/supported.h | 4 +- ircd/channel.c | 17 ++ ircd/client.c | 32 +++ ircd/ircd.c | 8 + ircd/ircd_features.c | 9 + ircd/ircd_parser.y | 91 ++++++++- ircd/list.c | 1 + ircd/listener.c | 14 ++ ircd/m_burst.c | 6 +- ircd/m_clearmode.c | 2 + ircd/m_info.c | 7 + ircd/m_join.c | 3 + ircd/m_oper.c | 9 + ircd/m_rehash.c | 6 + ircd/m_server.c | 9 + ircd/msgq.c | 35 ++++ ircd/s_auth.c | 29 ++- ircd/s_bsd.c | 79 +++++++- ircd/s_conf.c | 2 + ircd/s_err.c | 4 +- ircd/s_misc.c | 5 +- ircd/s_user.c | 12 +- ircd/tls_gnutls.c | 381 ++++++++++++++++++++++++++++++++++++ ircd/tls_libtls.c | 303 +++++++++++++++++++++++++++++ ircd/tls_none.c | 70 +++++++ ircd/tls_openssl.c | 417 ++++++++++++++++++++++++++++++++++++++++ ircd/whocmds.c | 2 + 42 files changed, 1991 insertions(+), 21 deletions(-) create mode 100644 include/ircd_tls.h create mode 100644 ircd/tls_gnutls.c create mode 100644 ircd/tls_libtls.c create mode 100644 ircd/tls_none.c create mode 100644 ircd/tls_openssl.c diff --git a/acinclude.m4 b/acinclude.m4 index 6376d47b..aa84378d 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -149,6 +149,94 @@ else AC_MSG_ERROR([Cannot find a type with size of 64 bits]) fi]) +dnl +dnl Macro: unet_LIBTLS +dnl +dnl Set unet_cv_with_libtls to "yes" if the system has an OpenBSD-style +dnl libtls, or to "no" otherwise. +dnl +AC_DEFUN([unet_LIBTLS], +[dnl OpenBSD libtls needs manual checks. +AC_MSG_CHECKING([for OpenBSD-style libtls]) +AC_CACHE_VAL(unet_cv_with_libtls, +[save_LIBS="$LIBS" +LIBS="$LIBS -ltls" +AC_LINK_IFELSE( + [AC_LANG_PROGRAM([#include ], [tls_init()])], + [unet_cv_with_libtls=yes], + [unet_cv_with_libtls=no]) +LIBS="$save_LIBS"]) +AC_MSG_RESULT([$unet_cv_with_libtls])]) + +dnl +dnl Macro: unet_TLS +dnl +dnl Set unet_cv_with_tls and TLS_C to an available TLS implementation. +dnl +AC_DEFUN([unet_TLS], +[dnl Perform some preliminary checks for system TLS libraries. +AX_CHECK_OPENSSL() +PKG_CHECK_MODULES([GNUTLS], [gnutls]) +unet_LIBTLS + +dnl --with-tls allows selection of the TLS library. +AC_MSG_CHECKING([for a TLS library]) +AC_ARG_WITH([tls], +[ --with-tls=library TLS library to use (none, openssl, gnutls, libtls)], +[unet_cv_with_tls=$with_tls], +[AC_CACHE_VAL(unet_cv_with_tls, +[unet_cv_with_tls=yes])]) +TLS_C="" + +dnl If --with-tls or --with-tls=yes, try to autodetect: OpenSSL first. +if test x"$unet_cv_with_tls" = xyes ; then + if test x"$OPENSSL_LIBS" != x ; then + unet_cv_with_tls=openssl + fi +fi +dnl Try gnutls next. +if test x"$unet_cv_with_tls" = xyes ; then + if test x"$GNUTLS_LIBS" != x ; then + unet_cv_with_tls=gnutls + fi +fi +dnl Try libtls next. +if test x"$unet_cv_with_libtls" = xyes ; then + unet_cv_with_tls=libtls +fi + +case x"$unet_cv_with_tls" in +xopenssl) + CFLAGS="$CFLAGS $OPENSSL_CFLAGS" + LDFLAGS="$LDFLAGS $OPENSSL_LDFLAGS" + LIBS="$LIBS $OPENSSL_LIBS" + TLS_C="tls_openssl.c" + ;; +xgnutls) + CFLAGS="$CFLAGS $GNUTLS_CFLAGS" + LDFLAGS="$LDFLAGS $GNUTLS_LDFLAGS" + LIBS="$LIBS $GNUTLS_LIBS" + TLS_C="tls_gnutls.c" + ;; +xlibtls) + LIBS="$LIBS -ltls" + TLS_C="tls_libtls.c" + ;; +xyes|xno) + TLS_C="tls_none.c" + ;; +esac +if test x"$TLS_C" = x ; then + AC_MSG_WARN([Unknown TLS library $unet_cv_with_tls]) + TLS_C="tls_none.c" +fi +AC_MSG_RESULT([$unet_cv_with_tls]) +AC_SUBST([TLS_C]) + +if test x"$unet_cv_with_tls" = xopenssl ; then + AC_CHECK_FUNCS([SSL_set_ciphersuites]) +fi]) + dnl Written by John Hawkinson . This code is in the Public dnl Domain. dnl diff --git a/configure.ac b/configure.ac index a4923e3d..73077391 100644 --- a/configure.ac +++ b/configure.ac @@ -724,6 +724,8 @@ AC_MSG_RESULT([$unet_cv_with_maxcon]) AC_DEFINE_UNQUOTED(MAXCONNECTIONS, $unet_cv_with_maxcon, [Maximum number of network connections]) +unet_TLS + dnl Finally really generate all output files: AC_CONFIG_FILES([Makefile ircd/Makefile ircd/test/Makefile]) AC_OUTPUT diff --git a/doc/example.conf b/doc/example.conf index a49bc8e1..b300e192 100644 --- a/doc/example.conf +++ b/doc/example.conf @@ -60,6 +60,8 @@ # dns vhost = "ipv6vhost"; # dns server = "ipaddress"; # dns server = "ipaddress2"; +# tls certfile = "ircd.pem"; +# tls keyfile = "ircd.key"; # }; # # If present, must contain a valid address in dotted @@ -84,6 +86,12 @@ # address, and the default DNS servers are read from /etc/resolv.conf. # In most cases, you do not need to specify either the dns vhost or # the dns server. +# +# The TLS certfile and keyfile contain, respectively, the public key +# (and associated signatures for authenticating the public key) and the +# private key for the server. Both must be present, and name valid +# files, for the server to establish or accept new TLS-protected +# connections. General { name = "London.UK.Eu.UnderNet.org"; description = "University of London, England"; @@ -272,6 +280,7 @@ Class { # ip = "user@ip"; # password = "password"; # class = "classname"; +# tls fingerprint = "tls-fingerprint-hex"; # }; # # Technical description (for examples, see below): @@ -358,6 +367,17 @@ Client maxlinks = 5; }; +# You may restrict a Client connection to using a TLS client certificate +# with a particular fingerprint. The fingerprint is an SHA-256 digest, +# so must be 64 hexadecimal characters long. If a password is also +# listed, both certificate and password must satisfy the configuration. +Client { + host = "*@*"; + ip = "*@*"; + class = "Other"; + tls fingerprint = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; +} + # You can put an expression in the maxlinks value, which will make ircd # only accept a client when the total number of connections to the network # from the same IP number doesn't exceed this number. @@ -564,6 +584,9 @@ Kill # maxhops = 2; # hub = "*.eu.undernet.org"; # autoconnect = no; +# tls = no; +# tls fingerprint = "tls-fingerprint-hex"; +# tls ciphers = ""; # }; # # The "port" field defines the default port the server tries to connect @@ -583,6 +606,13 @@ Kill # be introduced by a hub; the element 'hub;' is an alias for # 'hub = "*";'. # +# The "tls" field defines whether TLS is required for connections to or +# from this server. If "tls = yes", then TLS fingerprint and ciphers +# are used. If "tls fingerprint" is present, the other server must use +# a certificate with that fingerprint. If "tls ciphers" is present, it +# is given to the TLS library to restrict or prioritize particular +# algorithms. (See TLS_CIPHERS in readme.features for details.) +# # Our primary uplink. Connect { name = "Amsterdam.NL.Eu.UnderNet.org"; @@ -655,6 +685,7 @@ CRULE # name = "opername"; # password = "encryptedpass"; # class = "classname"; +# tls fingerprint = "tls-fingerprint-hex"; # # You can also set any operator privilege; see the Class block # # documentation for details. A privilege defined for a single # # Operator will override the privilege settings for the Class @@ -667,6 +698,11 @@ CRULE # mechanisms. If you use a password format that is NOT generated by # umkpasswd, ircu will not recognize the oper's password. # +# If the tls fingerprint field is present, the client must be using TLS +# with a certificate that has that fingerprint (in addition to any +# checks for hostname or IP and password by the rest of the Operator +# block). +# # All privileges are shown with their default values; if you wish to # override defaults, you should set only those privileges for the # operator. Listing defaulted privileges just makes things harder to @@ -718,10 +754,15 @@ Operator { # server = yes; # # Setting to yes makes the port "hidden" from stats. # hidden = yes; +# # Setting to yes makes the port a TLS-only port. +# tls = yes; # # Setting to yes makes this for webirc clients only. # # If the head-in-sand (HIS) webirc features are on, you probably # # want WebIRC ports to also be hidden. # WebIRC = yes; +# # Setting tls ciphers affects which TLS cipher suites are allowed on +# # this port. See TLS_CIPHERS in readme.features for more details. +# tls ciphers = ""; # }; # # The port and vhost lines allow you to specify one or both of "ipv4" @@ -763,10 +804,12 @@ Port { port = 6666; }; -# This is a hidden client port, listening on 168.8.21.107. +# This is a hidden client port, listening on 168.8.21.107, that starts +# each connection with TLS negotiation. Port { vhost = "168.8.21.107"; hidden = yes; + tls = yes; port = 7000; }; @@ -979,6 +1022,15 @@ features # "HIS_SERVERINFO" = "The Undernet Underworld"; # "HIS_URLSERVERS" = "http://www.undernet.org/servers.php"; # "URLREG" = "http://cservice.undernet.org/live/"; +# TLS_CACERTFILE" = "ircd-ca.pem"; +# "TLS_CACERTDIR" = "/etc/ssl/certs"; +# "TLS_CIPHERS" = ""; +# "TLS_SSLV2" = "FALSE"; +# "TLS_SSLV3" = "FALSE"; +# "TLS_V1P0" = "FALSE"; +# "TLS_V1P1" = "TRUE"; +# "TLS_V1P2" = "TRUE"; +# "TLS_V1P3" = "TRUE"; }; # Well, you have now reached the end of this sample configuration diff --git a/doc/readme.features b/doc/readme.features index a9fc01c3..dd7f040e 100644 --- a/doc/readme.features +++ b/doc/readme.features @@ -137,6 +137,7 @@ It's used for providing promotional space to providers as per CFV-202 KILL_IPMISMATCH * Type: boolean + * Default: FALSE When a client connects to your server, the IP address of the client is @@ -927,6 +928,94 @@ passwords, respectively) on channels where they are marked as channel managers. This feature must be disabled until all servers on the network are able to interpret and handle these modes correctly. +TLS_CACERTFILE + * Type: string + * Default: "" + +This optionally names a file that contains public keys for certificate +authorities (CAs) that are trusted to sign keys for clients and servers +connected to this server. Certificates in this file take precedence +over those in TLS_CACERTDIR. + +TLS_CACERTDIR + * Type: string + * Default: "" + +This optionally names a directory that contains public keys for +certificate authorities (CAs) that are trusted to sign keys for clients +and servers connected to this server. + +TLS_CIPHERS + * Type: string + * Default: "" + +This specifies the default list of ciphers to use. This list is used +for client connections that use STARTTLS, for outbound connections to +servers with no "tls ciphers" directive in their Connect blocks, and for +inbound connections on TLS-only ports with no "tls ciphers" directive in +their Port blocks. + +This cipher list is interpreted by, and thus depends on, the TLS library +in use: + + - For all libraries, an empty string represents use of the library's + default cipher suites. + - For OpenSSL, it is parsed as "L1 L2" (two lists, separated by spaces) + where L1 is used for TLSv1.2 and below (with SSL_set_cipher_list()) + and L2 is used for TLSv1.3 (with SSL_set_ciphersuites()). + - For GnuTLS, it is parsed as a "priority string" by the function + gnutls_priority_init() and then applied to sessions. + - For OpenBSD's libtls, it is passed to tls_config_set_ciphers(). + +TLS_SSLV2 + * Type: boolean + * Default: FALSE + +This controls whether SSLv2 connections are supported. DO NOT USE SSLv2 +unless you are aware of, and willing to accept, the serious security +weaknesses of the SSLv2 protocol. + +TLS_SSLV3 + * Type: boolean + * Default: FALSE + +This controls whether SSLv3 connections are supported. DO NOT USE SSLv3 +unless you are aware of, and willing to accept, the serious security +weaknesses of the SSLv3 protocol. + +TLS_V1P0 + * Type: boolean + * Default: FALSE + +This controls whether TLS 1.0 connections are supported. This is +strongly discouraged in favor of TLS 1.1 and later because of security +weaknesses in CBC modes of operation under TLS 1.1. + +TLS_V1P1 + * Type: boolean + * Default: TRUE + +This controls whether TLS 1.1 connections are supported. In most cases, +TLS 1.2 is strictly preferable to TLS 1.1, so a server might want to +disable TLS 1.1. + +TLS_V1P2 + * Type: boolean + * Default: TRUE + +This controls whether TLS 1.2 connections are supported. + +TLS_V1P3 + * Type: boolean + * Default: TRUE + +This controls whether TLS 1.3 connections are supported. This offers a +number of minor security improvements over TLS 1.2, but is incompatible +with some proxies, so it may break connections. At the time of writing +this documentation (December 2019), not all deployed TLS libraries +support TLS 1.3; this feature setting is effectively false for such +libraries. + ZANNELS * Type: boolean * Default: FALSE diff --git a/include/channel.h b/include/channel.h index 50b6444d..375a7d34 100644 --- a/include/channel.h +++ b/include/channel.h @@ -116,6 +116,8 @@ struct Client; * pending */ #define MODE_NOPARTMSGS 0x800000 /**< +P No part messages */ #define MODE_MODERATENOREG 0x1000000 /**< +M Moderate unauthed users */ +#define MODE_TLSONLY 0x1000000 /**< +Z TLS users only */ +#define MODE_MODERATENOREG 0x2000000 /**< +M Moderate unauthed users */ /** mode flags which take another parameter (With PARAmeterS) */ diff --git a/include/client.h b/include/client.h index 5ed61a7f..4a33ba4b 100644 --- a/include/client.h +++ b/include/client.h @@ -158,6 +158,8 @@ enum Flag FLAG_BURST_ACK, /**< Server is waiting for eob ack */ FLAG_IPCHECK, /**< Added or updated IPregistry data */ FLAG_IAUTH_STATS, /**< Wanted IAuth statistics */ + FLAG_NEGOTIATING_TLS, /**< TLS negotation ongoing */ + FLAG_LOCOP, /**< Local operator -- SRB */ FLAG_SERVNOTICE, /**< server notices such as kill */ FLAG_OPER, /**< Operator */ @@ -171,6 +173,7 @@ enum Flag FLAG_ACCOUNT, /**< account name has been set */ FLAG_HIDDENHOST, /**< user's host is hidden */ FLAG_CAP302, /**< client supports IRCv3.2 */ + FLAG_TLS, /**< user is using TLS */ FLAG_LAST_FLAG, /**< number of flags */ FLAG_LOCAL_UMODES = FLAG_LOCOP, /**< First local mode flag */ FLAG_GLOBAL_UMODES = FLAG_OPER /**< First global mode flag */ @@ -229,6 +232,7 @@ struct Connection char con_buffer[BUFSIZE]; /**< Incoming message buffer; or the error that caused this clients socket to close. */ + char con_tls_fingerprint[65]; /**< TLS SHA-256 fingerprint. */ struct Socket con_socket; /**< socket descriptor for client */ struct Timer con_proc; /**< process latent messages from @@ -238,8 +242,13 @@ struct Connection capset_t con_active; /**< Active capabilities (to us) */ struct AuthRequest* con_auth; /**< Auth request for client */ const struct wline* con_wline; /**< WebIRC authorization for client */ +<<<<<<< HEAD uint64_t con_sasl; /**< SASL session cookie */ struct Timer con_sasl_timer; /**< SASL timeout timer */ +======= + char* con_rexmit; /**< TLS retransmission data */ + size_t con_rexmit_len; /**, TLS retransmission length */ +>>>>>>> 1f0b6f4 (Support SSL and TLS (UNTESTED)) }; /** Magic constant to identify valid Connection structures. */ @@ -379,6 +388,8 @@ struct Client { #define cli_sockhost(cli) con_sockhost(cli_connect(cli)) /** Get the client's password. */ #define cli_passwd(cli) con_passwd(cli_connect(cli)) +/** Get the client's TLS fingerprint. */ +#define cli_tls_fingerprint(cli) con_tls_fingerprint(cli_connect(cli)) /** Get the unprocessed input buffer for a client's connection. */ #define cli_buffer(cli) con_buffer(cli_connect(cli)) /** Get the Socket structure for sending to a client. */ @@ -462,6 +473,8 @@ struct Client { #define con_sockhost(con) ((con)->con_sockhost) /** Get the password sent by the remote end of the connection. */ #define con_passwd(con) ((con)->con_passwd) +/** Get the fingerprint of the peer's TLS certificate. */ +#define con_tls_fingerprint(con) ((con)->con_tls_fingerprint) /** Get the buffer of unprocessed incoming data from the connection. */ #define con_buffer(con) ((con)->con_buffer) /** Get the Socket for the connection. */ @@ -614,6 +627,10 @@ struct Client { #define IsHiddenHost(x) HasFlag(x, FLAG_HIDDENHOST) /** Return non-zero if the client has an active PING request. */ #define IsPingSent(x) HasFlag(x, FLAG_PINGSENT) +/** Return non-zero if the client is using TLS. */ +#define IsTLS(x) HasFlag(x, FLAG_TLS) +/** Return non-zero if the client is (re-)negotiating TLS. */ +#define IsNegotiatingTLS(x) HasFlag(x, FLAG_NEGOTIATING_TLS) /** Return non-zero if the client has operator or server privileges. */ #define IsPrivileged(x) (IsAnOper(x) || IsServer(x)) @@ -660,6 +677,10 @@ struct Client { #define SetHiddenHost(x) SetFlag(x, FLAG_HIDDENHOST) /** Mark a client as having a pending PING. */ #define SetPingSent(x) SetFlag(x, FLAG_PINGSENT) +/** Mark a client as using TLS. */ +#define SetTLS(x) SetFlag(x, FLAG_TLS) +/** Mark a client as (re-)negotiating TLS. */ +#define SetNegotiatingTLS(x) SetFlag(x, FLAG_NEGOTIATING_TLS) /** Return non-zero if \a sptr sees \a acptr as an operator. */ #define SeeOper(sptr,acptr) (IsAnOper(acptr) && (HasPriv(acptr, PRIV_DISPLAY) \ @@ -695,6 +716,8 @@ struct Client { #define ClearPingSent(x) ClrFlag(x, FLAG_PINGSENT) /** Clear the client's HUB flag. */ #define ClearHub(x) ClrFlag(x, FLAG_HUB) +/** Mark a client's TLS negotation as complete. */ +#define ClearNegotiatingTLS(x) ClrFlag(x, FLAG_NEGOTIATING_TLS) /* free flags */ #define FREEFLAG_SOCKET 0x0001 /**< socket needs to be freed */ diff --git a/include/handlers.h b/include/handlers.h index 8bc59f50..f4e1afe3 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -128,6 +128,7 @@ extern int m_registered(struct Client*, struct Client*, int, char*[]); extern int m_sasl(struct Client*, struct Client*, int, char*[]); extern int ms_config(struct Client*, struct Client*, int, char*[]); extern int m_silence(struct Client*, struct Client*, int, char*[]); +extern int m_starttls(struct Client*, struct Client*, int, char*[]); extern int m_stats(struct Client*, struct Client*, int, char*[]); extern int m_time(struct Client*, struct Client*, int, char*[]); extern int m_topic(struct Client*, struct Client*, int, char*[]); diff --git a/include/ircd_events.h b/include/ircd_events.h index f8148a88..ddcbab3a 100644 --- a/include/ircd_events.h +++ b/include/ircd_events.h @@ -104,6 +104,7 @@ struct Socket { enum SocketState s_state; /**< state socket's in */ unsigned int s_events; /**< events socket is interested in */ int s_fd; /**< file descriptor for socket */ + void* s_tls; /**< TLS state for socket */ }; #define SOCK_EVENT_READABLE 0x0001 /**< interested in readable */ @@ -132,6 +133,8 @@ struct Socket { #define s_ed_ptr(sock) ((sock)->s_header.gh_engdata.ed_ptr) /** Retrieve whether the Socket \a sock is active. */ #define s_active(sock) ((sock)->s_header.gh_flags & GEN_ACTIVE) +/** Retrieve TLS context for \a sock. */ +#define s_tls(sock) ((sock)->s_tls) /** Signal event generator. */ struct Signal { diff --git a/include/ircd_features.h b/include/ircd_features.h index 41b03f62..7f248125 100644 --- a/include/ircd_features.h +++ b/include/ircd_features.h @@ -100,6 +100,15 @@ enum Feature { FEAT_IRCD_RES_TIMEOUT, FEAT_AUTH_TIMEOUT, FEAT_ANNOUNCE_INVITES, + FEAT_TLS_CACERTFILE, + FEAT_TLS_CACERTDIR, + FEAT_TLS_CIPHERS, + FEAT_TLS_SSLV2, + FEAT_TLS_SSLV3, + FEAT_TLS_V1P0, + FEAT_TLS_V1P1, + FEAT_TLS_V1P2, + FEAT_TLS_V1P3, /* features that affect all operators */ FEAT_CONFIG_OPERCMDS, diff --git a/include/ircd_tls.h b/include/ircd_tls.h new file mode 100644 index 00000000..591d0265 --- /dev/null +++ b/include/ircd_tls.h @@ -0,0 +1,166 @@ +/* + * IRC - Internet Relay Chat, include/ircd_tls.h + * Copyright (C) 2019 Michael Poole + * + * 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +/** @file + * @brief Functions for handling TLS-protected connections. + */ +#ifndef INCLUDED_ircd_tls_h +#define INCLUDED_ircd_tls_h + +#include "ircd_osdep.h" + +struct Client; +struct ConfItem; +struct Listener; +struct MsgQ; +struct Socket; + +/* The following variables and functions are provided by ircu2's core + * code, not by the TLS interface. + */ + +/** ircd_tls_keyfile holds this server's private key. */ +extern char *ircd_tls_keyfile; + +/** ircd_tls_certfile holds this server's public key certificate. */ +extern char *ircd_tls_certfile; + +/** ircd_tls_fingerprint_matches() returns non-zero if \a cptr uses a + * TLS certificate that matches the fingerprint in \a fingerprint. + * + * If \a fingerprint is null or empty (zero-length), any client matches + * it (even clients not using TLS). If \a fingerprint has length from + * 1 to 63, no client matches it. Otherwise, the first 64 characters of + * \a fingerprint are compared (as hexadecimal digits) to the SHA-256 + * digest of the X.509 DER representation of the peer's TLS certificate. + * + * \warning \a cptr must be directly connected to this server. + * + * @param[in] cptr Client to check. + * @param[in] fingerprint Empty string, or 64-digit hex string. + * \returns Non-zero if the client has a matching TLS certificate + * fingerprint, zero if the fingerprint does not match. + */ +int ircd_tls_fingerprint_matches(struct Client *cptr, + const char *fingerprint); + +/* The following variables are provided by the TLS interface. */ + +/** ircd_tls_version identifies the TLS library in current use. */ +extern const char *ircd_tls_version; + +/** ircd_tls_init() initializes the TLS library. + * + * Among any other global initialization that the library needs, this + * function loads #ircd_tls_keyfile and #ircd_tls_certfile. It should + * return zero (ideally without performing other work) if either of + * those strings are null or empty. + * + * This function is idempotent; it is called both at initial startup + * and upon "REHASH s". The TLS interface code must distinguish between + * those cases as needed. + * + * \returns Zero on success, non-zero to indicate failure. + */ +int ircd_tls_init(void); + +/** ircd_tls_accept() creates an inbound TLS session. + * + * If \a listener is NULL, the client connected on a plaintext port and + * used STARTTLS. Otherwise, the client connected on a TLS-only port + * configured with \a listener. The connection uses \a . + * + * @param[in] listener Listening socket that accepted the connection. + * @param[in] fd File descriptor for new connection. + * \returns NULL on failure, otherwise a valid new TLS session. + */ +void *ircd_tls_accept(struct Listener *listener, int fd); + +/** ircd_tls_connect() creates an outbound connection to another server. + * + * \a aconf represents the Connect block for the other server, and \a fd + * is the file descriptor of a (connected but not yet used) connection + * to that server. + * + * @param[in] aconf Connect block for the server we connected to. + * @param[in] fd File descriptor connected to that server. + */ +void *ircd_tls_connect(struct ConfItem *aconf, int fd); + +/** ircd_tls_close() destroys the TLS session \a ctx, optionally passing + * \a message as an explanation. + * + * @param[in] ctx TLS session to destroy. + * @param[in] message If not null and not empty, this string is sent to + * the peer as an explanation for the connection close. (This is + * intended for use by add_connection().) + */ +void ircd_tls_close(void *ctx, const char *message); + +/** ircd_tls_fingerprint() fills \a fingerprint with the TLS fingerprint + * used by the TLS session \a ctx. If \a ctx is null or there is some + * internal failure, \a fingerprint should be filled with zero bytes. + * + * @param[in] ctx TLS session to query. + * @param[out] fingerprint 32-byte binary buffer. + */ +void ircd_tls_fingerprint(void *ctx, char *fingerprint); + +/** ircd_tls_negotiate() attempts to continue an initial TLS handshake + * for \a cptr. If the handshake completes, this function calls + * \a ClearNegotiatingTLS(cptr) and returns 1. If the handshake failed, + * this function returns -1. Otherwise it updates event flags for the + * client's socket and returns 0. + * + * @param[in] cptr Locally connected client to perform handshake for. + * \returns 1 on completed handshake, 0 on continuing handshake, -1 on + * error. + */ +int ircd_tls_negotiate(struct Client *cptr); + +/** ircd_tls_recv() performs a non-blocking receive of TLS application + * data from \a cptr into \a buf. + * + * @param[in] cptr Locally connected client to read from. + * @param[out] buf Buffer to receive application data into. + * @param[in] length Length of \a buf. + * @param[out] count_out Number of bytes actually read into \a buf. + * \returns IO_FAILURE on error, IO_BLOCKED if no data is available, or + * IO_SUCCESS if any data was read into \a buf. + */ +IOResult ircd_tls_recv(struct Client *cptr, char *buf, + unsigned int length, unsigned int *count_out); + +/** ircd_tls_sendv() performs a non-blocking send of TLS application + * data from \a buf to \a cptr. + * + * This function must accomodate changes to \a buf for successive calls + * to \a cptr. The connection's \a con_rexmit and \a con_rexmit_len + * fields are provided to support this requirement. + * + * @param[in] cptr Locally connected client to send to. + * @param[in] buf Client's message queue. + * @param[out] count_in Total number of bytes in \a buf at entry. + * @param[out] count_out Number of bytes consumed from \a buf. + * \returns IO_FAILURE on error, IO_BLOCKED if no data could be sent, or + * IO_SUCCESS if any data was written from \a buf. + */ +IOResult ircd_tls_sendv(struct Client *cptr, struct MsgQ *buf, + unsigned int *count_in, unsigned int *count_out); + +#endif /* INCLUDED_ircd_tls_h */ diff --git a/include/listener.h b/include/listener.h index 2451e40c..2d2a8ac5 100644 --- a/include/listener.h +++ b/include/listener.h @@ -54,6 +54,8 @@ enum ListenerFlag { LISTEN_IPV6, /** Port accepts only webirc connections. */ LISTEN_WEBIRC, + /** Port uses TLS natively. */ + LISTEN_TLS, /** Sentinel for counting listener flags. */ LISTEN_LAST_FLAG }; @@ -70,6 +72,7 @@ struct Listener { unsigned char mask_bits; /**< number of bits in mask address */ int index; /**< index into poll array */ time_t last_accept; /**< last time listener accepted */ + char* tls_ciphers; /**< ciphers to use for TLS */ struct irc_sockaddr addr; /**< virtual address and port */ struct irc_in_addr mask; /**< listener hostmask */ struct Socket socket_v4; /**< describe IPv4 socket to event system */ @@ -79,9 +82,11 @@ struct Listener { #define listener_server(LISTENER) FlagHas(&(LISTENER)->flags, LISTEN_SERVER) #define listener_active(LISTENER) FlagHas(&(LISTENER)->flags, LISTEN_ACTIVE) #define listener_webirc(LISTENER) FlagHas(&(LISTENER)->flags, LISTEN_WEBIRC) +#define listener_tls(LISTENER) FlagHas(&(LISTENER)->flags, LISTEN_TLS) extern void add_listener(int port, const char* vaddr_ip, const char* mask, + const char* tls_ciphers, const struct ListenerFlags *flags); extern void close_listener(struct Listener* listener); extern void close_listeners(void); diff --git a/include/msgq.h b/include/msgq.h index 409daabc..bd2c0c46 100644 --- a/include/msgq.h +++ b/include/msgq.h @@ -81,6 +81,7 @@ extern void msgq_append(struct Client *dest, struct MsgBuf *mb, const char *format, ...); extern void msgq_clean(struct MsgBuf *mb); extern void msgq_add(struct MsgQ *mq, struct MsgBuf *mb, int prio); +extern void msgq_excise(struct MsgQ *mq, const char *buf, unsigned int len); extern void msgq_count_memory(struct Client *cptr, size_t *msg_alloc, size_t *msg_used); extern void msgq_histogram(struct Client *cptr, const struct StatDesc *sd, diff --git a/include/numeric.h b/include/numeric.h index e436233b..7742806d 100644 --- a/include/numeric.h +++ b/include/numeric.h @@ -388,6 +388,7 @@ extern const struct Numeric* get_error_numeric(int err); #define ERR_INVALIDUSERNAME 468 /* Undernet extension */ /* ERR_ONLYSERVERSCANCHANGE 468 Dalnet,unreal */ /* ERR_LINKSET 469 unreal */ +#define ERR_TLSONLYCHAN 469 /* Nefarious, Undernet */ /* ERR_LINKCHANNEL 470 unreal */ /* ERR_KICKEDFROMCHAN 470 aircd */ #define ERR_CHANNELISFULL 471 @@ -459,6 +460,7 @@ extern const struct Numeric* get_error_numeric(int err); ERR_WHOLIMEXCEED 523 dalnet */ #define ERR_QUARANTINED 524 /* Undernet extension -Vampire */ #define ERR_INVALIDKEY 525 /* Undernet extension */ +#define ERR_TLSCLIFINGERPRINT 632 /* Nefarious & Undernet extension */ #define ERR_NOTLOWEROPLEVEL 560 /* Undernet extension */ #define ERR_NOTMANAGER 561 /* Undernet extension */ diff --git a/include/s_conf.h b/include/s_conf.h index 4a2070f6..bae3b1f5 100644 --- a/include/s_conf.h +++ b/include/s_conf.h @@ -32,8 +32,14 @@ struct Message; #define CONF_OPERATOR 0x0020 /**< ConfItem describes an Operator block */ #define CONF_UWORLD 0x8000 /**< ConfItem describes a Uworld server */ +/* These flags apply to Connect blocks: */ #define CONF_AUTOCONNECT 0x0001 /**< Autoconnect to a server */ +#define CONF_CONNECT_TLS 0x0002 /**< Server connection uses TLS */ +/* These flags apply to Port blocks: */ +#define CONF_PORT_TLS 0x0001 /**< Port should use TLS */ + +/* These flags apply to UWorld blocks: */ #define CONF_UWORLD_OPER 0x0001 /**< UWorld server can remotely oper users */ /** Indicates ConfItem types that count associated clients. */ @@ -64,6 +70,8 @@ struct ConfItem char *name; /**< Name of peer */ char *hub_limit; /**< Mask that limits servers allowed behind this one. */ + char *tls_ciphers; /**< TLS cipher preference list. */ + char *tls_fingerprint; /**< Peer must have this TLS cert fingerprint. */ time_t hold; /**< Earliest time to attempt an outbound connect on this ConfItem. */ int dns_pending; /**< A dns request is pending. */ diff --git a/include/s_misc.h b/include/s_misc.h index 10939e40..3ddef25d 100644 --- a/include/s_misc.h +++ b/include/s_misc.h @@ -68,10 +68,12 @@ struct ServerStatistics { unsigned int is_ip_full; /**< client rejected: too many from IP */ unsigned int is_bad_socket; /**< client rejected: socket failure */ unsigned int is_throttled; /**< client rejected: IP connecting too fast */ + unsigned int is_bad_fingerprint; /**< client rejected: bad TLS fingerprint */ unsigned int is_not_hub; /**< server rejected: I am not a hub */ unsigned int is_crule_fail; /**< server rejected: CRULE rejected */ unsigned int is_not_server; /**< server rejected: no matching Connect block */ unsigned int is_bad_server; /**< server rejected: bad password */ + unsigned int is_wrong_server; /**< server rejected: bad TLS fingerprint */ unsigned int is_unco; /**< unknown commands */ unsigned int is_wrdi; /**< command going in wrong direction */ unsigned int is_unpf; /**< unknown prefix */ diff --git a/include/supported.h b/include/supported.h index b32289da..6dd97d70 100644 --- a/include/supported.h +++ b/include/supported.h @@ -66,8 +66,8 @@ feature_int(FEAT_CHANNELLEN), CHANNELLEN, \ (feature_bool(FEAT_LOCAL_CHANNELS) ? "#&" : "#"), "(ov)@+", "@+", \ (feature_bool(FEAT_OPLEVELS) \ - ? "b,AkU,l,imnpstrDdRcCPM" \ - : "b,k,l,imnpstrDdRcCPM"), \ + ? "b,AkU,l,imnpstrDdRcCPMZ" \ + : "b,k,l,imnpstrDdRcCPMZ"), \ "rfc1459", feature_str(FEAT_NETWORK) #endif /* INCLUDED_supported_h */ diff --git a/ircd/channel.c b/ircd/channel.c index 94b30f51..7b2cc48f 100644 --- a/ircd/channel.c +++ b/ircd/channel.c @@ -712,6 +712,10 @@ int member_can_send_to_channel(struct Membership* member, int reveal) if (member->channel->mode.mode & (MODE_MODERATENOREG|MODE_REGONLY) && !IsAccount(member->user)) return 0; + /* If only TLS-using users may join and you're not one, you can't speak. */ + if (member->channel->mode.mode & MODE_TLSONLY && !IsTLS(member->user)) + return 0; + /* If you're banned then you can't speak either. */ if (is_banned(member)) return 0; @@ -755,6 +759,10 @@ int client_can_send_to_channel(struct Client *cptr, struct Channel *chptr, int r if (chptr->mode.mode & (MODE_NOPRIVMSGS|MODE_MODERATED)) return 0; + /* TLS only channel and non-TLS client? */ + if ((chptr->mode.mode & MODE_TLSONLY) && !IsTLS(cptr)) + return 0; + /* .. or to a +r or +M channel when you are not logged in. */ if ((chptr->mode.mode & (MODE_REGONLY|MODE_MODERATENOREG)) && !IsAccount(cptr)) return 0; @@ -843,6 +851,8 @@ void channel_modes(struct Client *cptr, char *mbuf, char *pbuf, int buflen, *mbuf++ = 'P'; if (chptr->mode.mode & MODE_MODERATENOREG) *mbuf++ = 'M'; + if (chptr->mode.mode & MODE_TLSONLY) + *mbuf++ = 'Z'; if (chptr->mode.limit) { *mbuf++ = 'l'; ircd_snprintf(0, pbuf, buflen, "%u", chptr->mode.limit); @@ -1543,6 +1553,7 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all) MODE_NOCTCP, 'C', MODE_NOPARTMSGS, 'P', MODE_MODERATENOREG, 'M', + MODE_TLSONLY, 'Z', /* MODE_KEY, 'k', */ /* MODE_BAN, 'b', */ MODE_LIMIT, 'l', @@ -1974,7 +1985,11 @@ modebuf_mode(struct ModeBuf *mbuf, unsigned int mode) mode &= (MODE_ADD | MODE_DEL | MODE_PRIVATE | MODE_SECRET | MODE_MODERATED | MODE_TOPICLIMIT | MODE_INVITEONLY | MODE_NOPRIVMSGS | MODE_REGONLY | +<<<<<<< HEAD MODE_NOCOLOR | MODE_NOCTCP | MODE_NOPARTMSGS | MODE_MODERATENOREG | +======= + MODE_NOCOLOR | MODE_NOCTCP | MODE_MODERATENOREG | MODE_TLSONLY | +>>>>>>> 40846b2 (Support SSL and TLS (UNTESTED)) MODE_DELJOINS | MODE_WASDELJOINS | MODE_REGISTERED); if (!(mode & ~(MODE_ADD | MODE_DEL))) /* don't add empty modes... */ @@ -2112,6 +2127,7 @@ modebuf_extract(struct ModeBuf *mbuf, char *buf) MODE_NOCTCP, 'C', MODE_NOPARTMSGS, 'P', MODE_MODERATENOREG, 'M', + MODE_TLSONLY, 'Z', 0x0, 0x0 }; unsigned int add; @@ -3257,6 +3273,7 @@ mode_parse(struct ModeBuf *mbuf, struct Client *cptr, struct Client *sptr, MODE_NOCTCP, 'C', MODE_NOPARTMSGS, 'P', MODE_MODERATENOREG, 'M', + MODE_TLSONLY, 'Z', MODE_ADD, '+', MODE_DEL, '-', 0x0, 0x0 diff --git a/ircd/client.c b/ircd/client.c index e861c876..4fa0a515 100644 --- a/ircd/client.c +++ b/ircd/client.c @@ -28,6 +28,8 @@ #include "ircd_features.h" #include "ircd_log.h" #include "ircd_reply.h" +#include "ircd_string.h" +#include "ircd_tls.h" #include "list.h" #include "msgq.h" #include "numeric.h" @@ -284,3 +286,33 @@ client_report_privs(struct Client *to, struct Client *client) return 0; } + +int +ircd_tls_fingerprint_matches(struct Client *cptr, + const char *fingerprint) +{ + static const char hexdigits[] = "0123456789abcdef"; + void *ctx; + int i; + char tls_fp[32]; + + if (EmptyString(fingerprint)) + return 1; + + ctx = s_tls(&cli_socket(cptr)); + if (!ctx) + return 0; + + ircd_tls_fingerprint(ctx, tls_fp); + for (i = 0; i < sizeof(tls_fp); i++) + { + unsigned char ch = tls_fp[i]; + if ((hexdigits[ch >> 4] != ToLower(fingerprint[2*i+0])) + || (hexdigits[ch & 15] != ToLower(fingerprint[2*i+1]))) + { + return 0; + } + } + + return 1; +} diff --git a/ircd/ircd.c b/ircd/ircd.c index dedd5eb8..3ddee06f 100644 --- a/ircd/ircd.c +++ b/ircd/ircd.c @@ -38,6 +38,7 @@ #include "ircd_signal.h" #include "ircd_string.h" #include "ircd_crypt.h" +#include "ircd_tls.h" #include "jupe.h" #include "list.h" #include "match.h" @@ -111,6 +112,8 @@ time_t CurrentTime; /**< Updated every time we leave select( char *configfile = CPATH; /**< Server configuration file */ int debuglevel = -1; /**< Server debug level */ char *debugmode = ""; /**< Server debug level */ +char *ircd_tls_keyfile; /**< Private key file for TLS */ +char *ircd_tls_certfile; /**< Public key file for TLS */ static char *dpath = DPATH; /**< Working directory for daemon */ static char *dbg_client; /**< Client specifier for chkconf */ @@ -713,6 +716,11 @@ int main(int argc, char **argv) { return 7; } + if (ircd_tls_init()) { + log_write(LS_SYSTEM, L_CRIT, 0, "TLS initialization failed"); + return 10; + } + if (thisServer.bootopt & BOOT_CHKCONF) { if (dbg_client) conf_debug_iline(dbg_client); diff --git a/ircd/ircd_features.c b/ircd/ircd_features.c index 35a74f01..3649b98c 100644 --- a/ircd/ircd_features.c +++ b/ircd/ircd_features.c @@ -365,6 +365,15 @@ static struct FeatureDesc { F_I(IRCD_RES_TIMEOUT, 0, 4, 0), F_I(AUTH_TIMEOUT, 0, 9, 0), F_B(ANNOUNCE_INVITES, 0, 0, 0), + F_S(TLS_CACERTFILE, FEAT_NULL | FEAT_CASE | FEAT_OPER, 0, 0), + F_S(TLS_CACERTDIR, FEAT_NULL | FEAT_CASE | FEAT_OPER, 0, 0), + F_S(TLS_CIPHERS, FEAT_NULL | FEAT_CASE | FEAT_OPER, 0, 0), + F_B(TLS_SSLV2, 0, 0, 0), + F_B(TLS_SSLV3, 0, 0, 0), + F_B(TLS_V1P0, 0, 0, 0), + F_B(TLS_V1P1, 0, 1, 0), + F_B(TLS_V1P2, 0, 1, 0), + F_B(TLS_V1P3, 0, 1, 0), /* features that affect all operators */ F_B(CONFIG_OPERCMDS, 0, 0, 0), diff --git a/ircd/ircd_parser.y b/ircd/ircd_parser.y index 30c927ae..24c9110d 100644 --- a/ircd/ircd_parser.y +++ b/ircd/ircd_parser.y @@ -39,6 +39,7 @@ #include "ircd_reply.h" #include "ircd_snprintf.h" #include "ircd_string.h" +#include "ircd_tls.h" #include "list.h" #include "listener.h" #include "match.h" @@ -77,6 +78,7 @@ /* Now all the globals we need :/... */ int tping, tconn, maxflood, maxlinks, sendq, port, invert, stringno, flags; char *name, *pass, *host, *ip, *username, *origin, *hub_limit; + char *tls_certfile, *tls_ciphers, *tls_fingerprint, *tls_keyfile; struct SLink *hosts; char *stringlist[MAX_STRINGS]; struct ListenerFlags listen_flags; @@ -224,6 +226,11 @@ static void free_slist(struct SLink **link) { %token FROM %token TEOF %token TOKERR +%token TLS +%token CERTFILE +%token CIPHERS +%token FINGERPRINT +%token KEYFILE /* and now a lot of privileges... */ %token TPRIV_CHAN_LIMIT TPRIV_MODE_LCHAN TPRIV_DEOP_LCHAN TPRIV_WALK_LCHAN %token TPRIV_LOCAL_KILL TPRIV_REHASH TPRIV_RESTART TPRIV_DIE @@ -348,10 +355,30 @@ generalblock: GENERAL parse_error("Your General block must contain a name."); if (localConf.numeric == 0) parse_error("Your General block must contain a numeric (between 1 and 4095)."); + MyFree(ircd_tls_keyfile); + MyFree(ircd_tls_certfile); + if (tls_certfile && !tls_keyfile) + { + parse_error("General block has tls certfile but no tls keyfile; disabling TLS"); + MyFree(tls_certfile); + } + else if (tls_keyfile && !tls_certfile) + { + parse_error("General block has tls keyfile but no tls certfile; disabling TLS"); + MyFree(tls_keyfile); + } + else if (tls_certfile && tls_keyfile) + { + ircd_tls_certfile = tls_certfile; + ircd_tls_keyfile = tls_keyfile; + tls_certfile = NULL; + tls_keyfile = NULL; + } }; generalitems: generalitem generalitems | generalitem; generalitem: generalnumeric | generalname | generalvhost | generaldesc - | generaldnsvhost | generaldnsserver; + | generaldnsvhost | generaldnsserver + | generaltlscertfile | generaltlskeyfile; generalnumeric: NUMERIC '=' NUMBER ';' { @@ -428,6 +455,16 @@ generaldnsserver: DNS SERVER '=' QSTRING ';' MyFree(server); }; +generaltlscertfile: TLS CERTFILE '=' QSTRING ';' +{ + tls_certfile = $4; +}; + +generaltlskeyfile: TLS KEYFILE '=' QSTRING ';' +{ + tls_keyfile = $4; +}; + adminblock: ADMIN { if (!permitted(BLOCK_ADMIN)) YYERROR; @@ -551,6 +588,8 @@ connectblock: CONNECT aconf->conn_class = c_class; aconf->address.port = port; aconf->host = host; + aconf->tls_ciphers = tls_ciphers; + aconf->tls_fingerprint = tls_fingerprint; /* If the user specified a hub allowance, but not maximum links, * allow an effectively unlimited number of hops. */ @@ -565,15 +604,19 @@ connectblock: CONNECT MyFree(host); MyFree(origin); MyFree(hub_limit); + MyFree(tls_ciphers); + MyFree(tls_fingerprint); } name = pass = host = origin = hub_limit = NULL; + tls_ciphers = tls_fingerprint = NULL; c_class = NULL; port = flags = maxlinks = 0; }; connectitems: connectitem connectitems | connectitem; connectitem: connectname | connectpass | connectclass | connecthost | connectport | connectvhost | connectleaf | connecthub - | connecthublimit | connectmaxhops | connectauto; + | connecthublimit | connectmaxhops | connectauto + | connecttls | tlsfingerprint | tlsciphers; connectname: NAME '=' QSTRING ';' { MyFree(name); @@ -625,6 +668,14 @@ connectmaxhops: MAXHOPS '=' expr ';' }; connectauto: AUTOCONNECT '=' YES ';' { flags |= CONF_AUTOCONNECT; } | AUTOCONNECT '=' NO ';' { flags &= ~CONF_AUTOCONNECT; }; +connecttls: TLS '=' YES ';' +{ + flags |= CONF_CONNECT_TLS; +} +| TLS '=' NO ';' +{ + flags &= ~CONF_CONNECT_TLS; +}; uworldblock: UWORLD { if (!permitted(BLOCK_UWORLD)) YYERROR; @@ -668,9 +719,15 @@ operblock: OPER { aconf->conn_class = c_class; memcpy(&aconf->privs, &privs, sizeof(aconf->privs)); memcpy(&aconf->privs_dirty, &privs_dirty, sizeof(aconf->privs_dirty)); + if (tls_fingerprint) + { + aconf->tls_fingerprint = tls_fingerprint; + tls_fingerprint = NULL; + } } MyFree(name); MyFree(pass); + MyFree(tls_fingerprint); free_slist(&hosts); name = pass = NULL; c_class = NULL; @@ -678,7 +735,8 @@ operblock: OPER { memset(&privs_dirty, 0, sizeof(privs_dirty)); }; operitems: operitem | operitems operitem; -operitem: opername | operpass | operhost | operclass | priv; +operitem: opername | operpass | operhost | operclass | priv + | tlsfingerprint; opername: NAME '=' QSTRING ';' { MyFree(name); @@ -750,6 +808,17 @@ privtype: TPRIV_CHAN_LIMIT { $$ = PRIV_CHAN_LIMIT; } | yesorno: YES { $$ = 1; } | NO { $$ = 0; }; +tlsfingerprint: TLS FINGERPRINT '=' QSTRING ';' +{ + MyFree(tls_fingerprint); + tls_fingerprint = $4; +}; +tlsciphers: TLS CIPHERS '=' QSTRING ';' +{ + MyFree(tls_ciphers); + tls_ciphers = $4; +}; + /* not a recursive definition because some pedant will just come along * and whine that the parser accepts "ipv4 ipv4 ipv4 ipv4" */ @@ -775,6 +844,8 @@ portblock: PORT { link->next = hosts; hosts = link; } + if (!FlagHas(&listen_flags, LISTEN_TLS)) + MyFree(tls_ciphers); for (link = hosts; link != NULL; link = link->next) { memcpy(&flags_here, &listen_flags, sizeof(flags_here)); switch (link->flags & (USE_IPV4 | USE_IPV6)) { @@ -791,16 +862,18 @@ portblock: PORT { } if (link->flags & 65535) port = link->flags & 65535; - add_listener(port, link->value.cp, pass, &flags_here); + add_listener(port, link->value.cp, pass, tls_ciphers, &flags_here); } free_slist(&hosts); MyFree(pass); + MyFree(tls_ciphers); memset(&listen_flags, 0, sizeof(listen_flags)); pass = NULL; port = 0; }; portitems: portitem portitems | portitem; -portitem: portnumber | portvhost | portvhostnumber | portmask | portserver | portwebirc | porthidden; +portitem: portnumber | portvhost | portvhostnumber | portmask | portserver + | portwebirc | porthidden | porttls | tlsciphers; portnumber: PORT '=' address_family NUMBER ';' { if ($4 < 1 || $4 > 65535) { @@ -869,6 +942,14 @@ portwebirc: WEBIRC '=' YES ';' FlagClr(&listen_flags, LISTEN_WEBIRC); }; +porttls: TLS '=' YES ';' +{ + FlagSet(&listen_flags, LISTEN_TLS); +} | TLS '=' NO ';' +{ + FlagClr(&listen_flags, LISTEN_TLS); +}; + clientblock: CLIENT { if (!permitted(BLOCK_CLIENT)) YYERROR; diff --git a/ircd/list.c b/ircd/list.c index 26a2bb5e..a4ae97ad 100644 --- a/ircd/list.c +++ b/ircd/list.c @@ -228,6 +228,7 @@ struct Client* make_client(struct Client *from, int status) cli_status(cptr) = status; cli_hnext(cptr) = cptr; strcpy(cli_username(cptr), ""); + strcpy(cli_tls_fingerprint(cptr), ""); return cptr; } diff --git a/ircd/listener.c b/ircd/listener.c index 22658402..4987eafa 100644 --- a/ircd/listener.c +++ b/ircd/listener.c @@ -33,6 +33,7 @@ #include "ircd_reply.h" #include "ircd_snprintf.h" #include "ircd_string.h" +#include "ircd_tls.h" #include "match.h" #include "numeric.h" #include "s_bsd.h" @@ -155,6 +156,10 @@ void show_ports(struct Client* sptr, const struct StatDesc* sd, continue; flags[len++] = 'H'; } + if (FlagHas(&listener->flags, LISTEN_TLS)) + { + flags[len++] = 'E'; + } if (FlagHas(&listener->flags, LISTEN_IPV4)) { flags[len++] = '4'; @@ -284,9 +289,11 @@ static struct Listener* find_listener(int port, const struct irc_in_addr *addr) * @param[in] port Port number to listen on. * @param[in] vhost_ip Local address to listen on. * @param[in] mask Address mask to accept connections from. + * @param[in] tls_ciphers TLS cipher(s) to use. * @param[in] flags Flags describing listener options. */ void add_listener(int port, const char* vhost_ip, const char* mask, + const char* tls_ciphers, const struct ListenerFlags *flags) { struct Listener* listener; @@ -300,6 +307,9 @@ void add_listener(int port, const char* vhost_ip, const char* mask, */ if (0 == port) return; + /* if TLS requested but no implementation, skip this port */ + if (FlagHas(flags, LISTEN_TLS) && !ircd_tls_version) + return; memset(&vaddr, 0, sizeof(vaddr)); @@ -323,6 +333,10 @@ void add_listener(int port, const char* vhost_ip, const char* mask, } } else listener->mask_bits = 0; + if (!EmptyString(tls_ciphers)) + DupString(listener->tls_ciphers, tls_ciphers); + else + listener->tls_ciphers = NULL; #ifdef IPV6 if (FlagHas(&listener->flags, LISTEN_IPV6) diff --git a/ircd/m_burst.c b/ircd/m_burst.c index be9eaa26..b4f4b405 100644 --- a/ircd/m_burst.c +++ b/ircd/m_burst.c @@ -130,6 +130,9 @@ netride_modes(int parc, char **parv, const char *curr_key) case 'r': result |= MODE_REGONLY; break; + case 'Z': + result |= MODE_TLSONLY; + break; } } return result; @@ -308,7 +311,8 @@ int ms_burst(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) */ if (!(check_modes & MODE_KEY) && (!(check_modes & MODE_INVITEONLY) || IsAnOper(member->user)) - && (!(check_modes & MODE_REGONLY) || IsAccount(member->user))) + && (!(check_modes & MODE_REGONLY) || IsAccount(member->user)) + && (!(check_modes & MODE_TLSONLY) || IsTLS(member->user))) continue; sendcmdto_serv_butone(&me, CMD_KICK, NULL, "%H %C :Net Rider", chptr, member->user); sendcmdto_channel_butserv_butone(&his, CMD_KICK, chptr, NULL, 0, "%H %C :Net Rider", chptr, member->user); diff --git a/ircd/m_clearmode.c b/ircd/m_clearmode.c index 2476b15b..acaf0821 100644 --- a/ircd/m_clearmode.c +++ b/ircd/m_clearmode.c @@ -1,3 +1,4 @@ + /* * IRC - Internet Relay Chat, ircd/m_clearmode.c * Copyright (C) 1990 Jarkko Oikarinen and @@ -128,6 +129,7 @@ do_clearmode(struct Client *cptr, struct Client *sptr, struct Channel *chptr, MODE_NOCTCP, 'C', MODE_NOPARTMSGS, 'P', MODE_MODERATENOREG, 'M', + MODE_TLSONLY, 'Z', 0x0, 0x0 }; int *flag_p; diff --git a/ircd/m_info.c b/ircd/m_info.c index 7f041bca..3ed46cf3 100644 --- a/ircd/m_info.c +++ b/ircd/m_info.c @@ -86,6 +86,7 @@ #include "ircd_log.h" #include "ircd_reply.h" #include "ircd_string.h" +#include "ircd_tls.h" #include "msg.h" #include "numeric.h" #include "numnicks.h" @@ -118,6 +119,8 @@ int m_info(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) creation, generation); send_reply(sptr, SND_EXPLICIT | RPL_INFO, ":On-line since %s", myctime(cli_firsttime(&me))); + if (ircd_tls_version) + send_reply(sptr, SND_EXPLICIT | RPL_INFO, ":TLS library: %s", ircd_tls_version); send_reply(sptr, RPL_ENDOFINFO); return 0; @@ -153,6 +156,8 @@ int ms_info(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) creation, generation); send_reply(sptr, SND_EXPLICIT | RPL_INFO, ":On-line since %s", myctime(cli_firsttime(&me))); + if (ircd_tls_version) + send_reply(sptr, SND_EXPLICIT | RPL_INFO, ":TLS library: %s", ircd_tls_version); send_reply(sptr, RPL_ENDOFINFO); return 0; } @@ -186,6 +191,8 @@ int mo_info(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) creation, generation); send_reply(sptr, SND_EXPLICIT | RPL_INFO, ":On-line since %s", myctime(cli_firsttime(&me))); + if (ircd_tls_version) + send_reply(sptr, SND_EXPLICIT | RPL_INFO, ":TLS library: %s", ircd_tls_version); send_reply(sptr, RPL_ENDOFINFO); } return 0; diff --git a/ircd/m_join.c b/ircd/m_join.c index 47728043..7a925e63 100644 --- a/ircd/m_join.c +++ b/ircd/m_join.c @@ -223,6 +223,8 @@ int m_join(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) err = ERR_BANNEDFROMCHAN; else if (*chptr->mode.key && (!key || strcmp(key, chptr->mode.key))) err = ERR_BADCHANNELKEY; + else if ((chptr->mode.mode & MODE_TLSONLY) && !IsTLS(sptr)) + err = ERR_TLSONLYCHAN; /* An oper with WALK_LCHAN privilege can join a local channel * he otherwise could not join by using "OVERRIDE" as the key. @@ -247,6 +249,7 @@ int m_join(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) case ERR_BANNEDFROMCHAN: err = 'b'; break; case ERR_BADCHANNELKEY: err = 'k'; break; case ERR_NEEDREGGEDNICK: err = 'r'; break; + case ERR_TLSONLYCHAN: err = 'Z'; break; default: err = '?'; break; } /* send accountability notice */ diff --git a/ircd/m_oper.c b/ircd/m_oper.c index a66a42cc..3735fa92 100644 --- a/ircd/m_oper.c +++ b/ircd/m_oper.c @@ -89,6 +89,7 @@ #include "ircd_log.h" #include "ircd_reply.h" #include "ircd_string.h" +#include "ircd_tls.h" #include "ircd_crypt.h" #include "msg.h" #include "numeric.h" @@ -156,6 +157,14 @@ int m_oper(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) } assert(0 != (aconf->status & CONF_OPERATOR)); + if (!ircd_tls_fingerprint_matches(sptr, aconf->tls_fingerprint)) + { + send_reply(sptr, ERR_TLSCLIFINGERPRINT); + sendto_opmask_butone(0, SNO_OLDREALOP, "Failed OPER attempt by %s (%s@%s)", + parv[0], cli_user(sptr)->username, cli_sockhost(sptr)); + return 0; + } + if (oper_password_match(password, aconf->passwd)) { struct Flags old_mode = cli_flags(sptr); diff --git a/ircd/m_rehash.c b/ircd/m_rehash.c index 8e8c2db6..2014fde5 100644 --- a/ircd/m_rehash.c +++ b/ircd/m_rehash.c @@ -86,6 +86,7 @@ #include "ircd_log.h" #include "ircd_reply.h" #include "ircd_string.h" +#include "ircd_tls.h" #include "motd.h" #include "numeric.h" #include "s_conf.h" @@ -99,6 +100,7 @@ * parv[1] = 'm' flushes the MOTD cache and returns * parv[1] = 'l' reopens the log files and returns * parv[1] = 'q' to not rehash the resolver (optional) + * parv[1] = 's' to reload TLS certificate and private key files */ int mo_rehash(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) { @@ -116,6 +118,10 @@ int mo_rehash(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) send_reply(sptr, SND_EXPLICIT | RPL_REHASHING, ":Reopening log files"); log_reopen(); /* reopen log files */ return 0; + } else if (*parv[1] == 's') { + send_reply(sptr, SND_EXPLICIT | RPL_REHASHING, ":Reloading TLS files"); + ircd_tls_init(); + return 0; } else if (*parv[1] == 'q') flag = 2; } diff --git a/ircd/m_server.c b/ircd/m_server.c index 9c3332ad..1e72907f 100644 --- a/ircd/m_server.c +++ b/ircd/m_server.c @@ -34,6 +34,7 @@ #include "ircd_features.h" #include "ircd_reply.h" #include "ircd_string.h" +#include "ircd_tls.h" #include "jupe.h" #include "list.h" #include "match.h" @@ -607,6 +608,14 @@ int mr_server(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) "Access denied. No conf line for server %s", cli_name(cptr)); } + if (!ircd_tls_fingerprint_matches(cptr, aconf->tls_fingerprint)) { + ++ServerStats->is_wrong_server; + sendto_opmask_butone(0, SNO_OLDSNO, "Access denied (fingerprint mismatch) %s", + cli_name(cptr)); + return exit_client_msg(cptr, cptr, &me, + "Access denied. Bad TLS fingerprint for server %s", cli_name(cptr)); + } + if (*aconf->passwd && !!strcmp(aconf->passwd, cli_passwd(cptr))) { ++ServerStats->is_bad_server; sendto_opmask_butone(0, SNO_OLDSNO, "Access denied (passwd mismatch) %s", diff --git a/ircd/msgq.c b/ircd/msgq.c index ec320500..3df948c7 100644 --- a/ircd/msgq.c +++ b/ircd/msgq.c @@ -549,6 +549,41 @@ msgq_add(struct MsgQ *mq, struct MsgBuf *mb, int prio) mq->count++; /* and the queue count */ } +static int msgqlist_excise(struct MsgQ *mq, struct MsgQList *qlist, + const char *buf, unsigned int len) +{ + struct Msg *msg; + + msg = qlist->head; + if (!msg) + return 0; + + if (buf != msg->msg->msg) + return 0; + + assert(len == msg->msg->length); + msgq_delmsg(mq, qlist, &len); + return 1; +} + +/** Excise a message from the front of a message queue. + * + * This is used for TLS, where TLS libraries may return an EAGAIN-like + * condition for a send but also require the application to provide + * exactly the same contents for the next send. + * + * @warning \a buf must be at the front of one of \a mq's queues. + * @param[in] mq Message queue to operate on. + * @param[in] buf Buffered message to excise. + * @param[in] len Length of buffered message. + */ +void msgq_excise(struct MsgQ *mq, const char *buf, unsigned int len) +{ + if (!msgqlist_excise(mq, &mq->queue, buf, len) + && !msgqlist_excise(mq, &mq->prio, buf, len)) + assert(0 && "msgq_excise() could not find message to excise"); +} + /** Report memory statistics for message buffers. * @param[in] cptr Client requesting information. * @param[out] msg_alloc Receives number of bytes allocated in Msg structs. diff --git a/ircd/s_auth.c b/ircd/s_auth.c index dd762ca7..14866466 100644 --- a/ircd/s_auth.c +++ b/ircd/s_auth.c @@ -49,6 +49,7 @@ #include "ircd_reply.h" #include "ircd_snprintf.h" #include "ircd_string.h" +#include "ircd_tls.h" #include "list.h" #include "msg.h" /* for MAXPARA */ #include "numeric.h" @@ -142,10 +143,6 @@ typedef enum { REPORT_INVAL_DNS } ReportType; -/** Sends response \a r (from #ReportType) to client \a c. */ -#define sendheader(c, r) \ - send(cli_fd(c), HeaderMessages[(r)].message, HeaderMessages[(r)].length, 0) - /** Enumeration of IAuth connection flags. */ enum IAuthFlag { @@ -221,6 +218,21 @@ static int preregister_user(struct Client *cptr); typedef int (*iauth_cmd_handler)(struct IAuth *iauth, struct Client *cli, int parc, char **params); +/** Sends response \a r (from #ReportType) to client \a cptr. */ +static void sendheader(struct Client *cptr, ReportType r) +{ + if (IsTLS(cptr)) + { + sendrawto_one(cptr, "%.*s", HeaderMessages[r].length - 2, + HeaderMessages[r].message); + send_queued(cptr); + } + else + { + send(cli_fd(cptr), HeaderMessages[r].message, HeaderMessages[r].length, 0); + } +} + /** Copies a username, cleaning it in the process. * * @param[out] dest Destination buffer for user name. @@ -560,6 +572,15 @@ static int check_auth_finished(struct AuthRequest *auth, int bitclr) send_reply(cptr, ERR_PASSWDMISMATCH); res = exit_client(cptr, cptr, &me, "Bad Password"); } + + /* Check TLS fingerprint. */ + if ((res == 0) && aconf && !EmptyString(aconf->tls_fingerprint) + && !ircd_tls_fingerprint_matches(cptr, aconf->tls_fingerprint)) + { + ++ServerStats->is_bad_fingerprint; + send_reply(cptr, ERR_TLSCLIFINGERPRINT); + res = exit_client(cptr, cptr, &me, "Bad TLS fingerprint"); + } } if (res == 0) diff --git a/ircd/s_bsd.c b/ircd/s_bsd.c index 369faaca..3af80b4e 100644 --- a/ircd/s_bsd.c +++ b/ircd/s_bsd.c @@ -37,6 +37,7 @@ #include "ircd_reply.h" #include "ircd_snprintf.h" #include "ircd_string.h" +#include "ircd_tls.h" #include "ircd.h" #include "list.h" #include "listener.h" @@ -282,9 +283,14 @@ unsigned int deliver_it(struct Client *cptr, struct MsgQ *buf) { unsigned int bytes_written = 0; unsigned int bytes_count = 0; + IOResult io_result; + assert(0 != cptr); - switch (os_sendv_nonb(cli_fd(cptr), buf, &bytes_count, &bytes_written)) { + io_result = IsTLS(cptr) + ? ircd_tls_sendv(cptr, buf, &bytes_count, &bytes_written) + : os_sendv_nonb(cli_fd(cptr), buf, &bytes_count, &bytes_written); + switch (io_result) { case IO_SUCCESS: ClrFlag(cptr, FLAG_BLOCKED); @@ -340,6 +346,29 @@ static int completed_connection(struct Client* cptr) if (s_state(&(cli_socket(cptr))) == SS_CONNECTING) socket_state(&(cli_socket(cptr)), SS_CONNECTED); + if (aconf->flags & CONF_CONNECT_TLS) { + /* Should we start the TLS handshake? */ + if (!IsTLS(cptr)) { + void *tls; + + tls = ircd_tls_connect(aconf, cli_fd(cptr)); + if (!tls) { + sendto_opmask_butone(0, SNO_OLDSNO, "Unable to start TLS connection to %s", cli_name(cptr)); + return 0; + } + s_tls(&cli_socket(cptr)) = tls; + SetNegotiatingTLS(cptr); + SetTLS(cptr); + } + + /* Are we making progress? */ + if (IsNegotiatingTLS(cptr)) { + ircd_tls_negotiate(cptr); + if (IsNegotiatingTLS(cptr)) + return 1; + } + } + if (!EmptyString(aconf->passwd)) sendrawto_one(cptr, MSG_PASS " :%s", aconf->passwd); @@ -476,6 +505,7 @@ void add_connection(struct Listener* listener, int fd) { struct irc_sockaddr addr; struct Client *new_client; time_t next_target = 0; + void *tls; const char* const throttle_message = "ERROR :Your host is trying to (re)connect too fast -- throttled\r\n"; @@ -506,6 +536,8 @@ void add_connection(struct Listener* listener, int fd) { */ os_disable_options(fd); + tls = listener_tls(listener) ? ircd_tls_accept(listener, fd) : NULL; + if (listener_server(listener)) { new_client = make_client(0, STAT_UNKNOWN_SERVER); @@ -524,7 +556,10 @@ void add_connection(struct Listener* listener, int fd) { if (!IPcheck_local_connect(&addr.addr, &next_target)) { ++ServerStats->is_throttled; - write(fd, throttle_message, strlen(throttle_message)); + if (tls) + ircd_tls_close(tls, throttle_message); + else + write(fd, throttle_message, strlen(throttle_message)); close(fd); return; } @@ -547,7 +582,10 @@ void add_connection(struct Listener* listener, int fd) { if (!socket_add(&(cli_socket(new_client)), client_sock_callback, (void*) cli_connect(new_client), SS_CONNECTED, 0, fd)) { ++ServerStats->is_bad_socket; - write(fd, register_message, strlen(register_message)); + if (tls) + ircd_tls_close(tls, register_message); + else + write(fd, register_message, strlen(register_message)); close(fd); cli_fd(new_client) = -1; return; @@ -556,6 +594,14 @@ void add_connection(struct Listener* listener, int fd) { cli_listener(new_client) = listener; ++listener->ref_count; + s_tls(&cli_socket(new_client)) = tls; + if (tls) + { + SetTLS(new_client); + SetNegotiatingTLS(new_client); + ircd_tls_negotiate(new_client); + } + Count_newunknown(UserStats); /* if we've made it this far we can put the client on the auth query pile */ start_auth(new_client); @@ -594,7 +640,10 @@ static int read_packet(struct Client *cptr, int socket_ready) if (socket_ready && !(IsUser(cptr) && DBufLength(&(cli_recvQ(cptr))) > GetMaxFlood(cptr))) { - switch (os_recv_nonb(cli_fd(cptr), readbuf, sizeof(readbuf), &length)) { + IOResult io_result = IsTLS(cptr) + ? ircd_tls_recv(cptr, readbuf, sizeof(readbuf), &length) + : os_recv_nonb(cli_fd(cptr), readbuf, sizeof(readbuf), &length); + switch (io_result) { case IO_SUCCESS: if (length) { @@ -862,6 +911,10 @@ static void client_sock_callback(struct Event* ev) if (!con_freeflag(con) && !cptr) free_connection(con); + if (s_tls(&con_socket(con))) { + ircd_tls_close(s_tls(&con_socket(con)), NULL); + s_tls(&con_socket(con)) = NULL; + } break; case ET_CONNECT: /* socket connection completed */ @@ -902,6 +955,12 @@ static void client_sock_callback(struct Event* ev) break; case ET_WRITE: /* socket is writable */ + if (IsNegotiatingTLS(cptr)) { + if (ircd_tls_negotiate(cptr) <= 0) + break; + if (IsConnecting(cptr)) + completed_connection(cptr); + } ClrFlag(cptr, FLAG_BLOCKED); if (cli_listing(cptr) && MsgQLength(&(cli_sendQ(cptr))) < 2048) list_next_channels(cptr); @@ -912,6 +971,12 @@ static void client_sock_callback(struct Event* ev) case ET_READ: /* socket is readable */ if (!IsDead(cptr)) { Debug((DEBUG_DEBUG, "Reading data from %C", cptr)); + if (IsNegotiatingTLS(cptr)) { + if (ircd_tls_negotiate(cptr) <= 0) + break; + if (IsConnecting(cptr)) + completed_connection(cptr); + } if (read_packet(cptr, 1) == 0) /* error while reading packet */ fallback = "EOF from client"; } @@ -928,6 +993,12 @@ static void client_sock_callback(struct Event* ev) const char* msg = (cli_error(cptr)) ? strerror(cli_error(cptr)) : fallback; if (!msg) msg = "Unknown error"; + + if (s_tls(&con_socket(con))) { + ircd_tls_close(s_tls(&con_socket(con)), NULL); + s_tls(&con_socket(con)) = NULL; + } + exit_client_msg(cptr, cptr, &me, fmt, msg); } } diff --git a/ircd/s_conf.c b/ircd/s_conf.c index be5774e2..d9d3d25f 100644 --- a/ircd/s_conf.c +++ b/ircd/s_conf.c @@ -158,6 +158,8 @@ void free_conf(struct ConfItem *aconf) MyFree(aconf->passwd); MyFree(aconf->name); MyFree(aconf->hub_limit); + MyFree(aconf->tls_ciphers); + MyFree(aconf->tls_fingerprint); MyFree(aconf); --GlobalConfCount; } diff --git a/ircd/s_err.c b/ircd/s_err.c index bdffe399..69619f5b 100644 --- a/ircd/s_err.c +++ b/ircd/s_err.c @@ -970,7 +970,7 @@ static Numeric replyTable[] = { /* 468 */ { ERR_INVALIDUSERNAME, 0, "468" }, /* 469 */ - { 0 }, + { ERR_TLSONLYCHAN, "%s :Cannot join channel (+Z)", "469" }, /* 470 */ { 0 }, /* 471 */ @@ -1096,7 +1096,7 @@ static Numeric replyTable[] = { /* 531 */ { 0 }, /* 532 */ - { 0 }, + { ERR_TLSCLIFINGERPRINT, ":TLS certificate fingerprint did not match", "532" }, /* 533 */ { 0 }, /* 534 */ diff --git a/ircd/s_misc.c b/ircd/s_misc.c index d1e45193..6c1e7a28 100644 --- a/ircd/s_misc.c +++ b/ircd/s_misc.c @@ -608,7 +608,7 @@ void tstats(struct Client *cptr, const struct StatDesc *sd, char *param) + sp->is_bad_password + sp->is_no_client + sp->is_class_full + sp->is_ip_full + sp->is_bad_socket + sp->is_throttled + sp->is_not_hub + sp->is_crule_fail + sp->is_not_server - + sp->is_bad_server; + + sp->is_bad_server + sp->is_bad_fingerprint + sp->is_wrong_server; maxconn = MAXCONNECTIONS; send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":accepts %u refused %u", sp->is_ac, is_ref); @@ -628,6 +628,9 @@ void tstats(struct Client *cptr, const struct StatDesc *sd, char *param) ":not hub %u crule faile %u no server block %u bad password %u", sp->is_not_hub, sp->is_crule_fail, sp->is_not_server, sp->is_bad_server); + send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, + ":bad client fingerprint %u bad server fingerprint %u", + sp->is_bad_fingerprint, sp->is_wrong_server); send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":unknown commands %u prefixes %u", sp->is_unco, sp->is_unpf); send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, diff --git a/ircd/s_user.c b/ircd/s_user.c index 13c61cb1..0c884644 100644 --- a/ircd/s_user.c +++ b/ircd/s_user.c @@ -499,7 +499,8 @@ static const struct UserMode { { FLAG_CHSERV, 'k' }, { FLAG_DEBUG, 'g' }, { FLAG_ACCOUNT, 'r' }, - { FLAG_HIDDENHOST, 'x' } + { FLAG_HIDDENHOST, 'x' }, + { FLAG_TLS, 'z' } }; /** Length of #userModeList. */ @@ -1085,6 +1086,11 @@ int set_user_mode(struct Client *cptr, struct Client *sptr, int parc, } /* There is no -r */ break; + case 'z': + if (what == MODE_ADD) + SetTLS(sptr); + /* There is no -z */ + break; default: send_reply(sptr, ERR_UMODEUNKNOWNFLAG, *m); break; @@ -1103,6 +1109,10 @@ int set_user_mode(struct Client *cptr, struct Client *sptr, int parc, ClearLocOp(sptr); if (!FlagHas(&setflags, FLAG_ACCOUNT) && IsAccount(sptr)) ClrFlag(sptr, FLAG_ACCOUNT); + if (!FlagHas(&setflags, FLAG_TLS) && IsTLS(sptr)) + ClrFlag(sptr, FLAG_TLS); + else if (FlagHas(&setflags, FLAG_TLS) && !IsTLS(sptr)) + SetFlag(sptr, FLAG_TLS); /* * new umode; servers can set it, local users cannot; * prevents users from /kick'ing or /mode -o'ing diff --git a/ircd/tls_gnutls.c b/ircd/tls_gnutls.c new file mode 100644 index 00000000..546af01c --- /dev/null +++ b/ircd/tls_gnutls.c @@ -0,0 +1,381 @@ +/* + * IRC - Internet Relay Chat, ircd/tls_gnutls.c + * Copyright (C) 2019 Michael Poole + * + * 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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +/** @file + * @brief ircd TLS functions using gnutls. + * + * This relies on gnutls_session_t being (a typedef to) a pointer type. + */ + +#include "config.h" +#include "client.h" +#include "ircd_features.h" +#include "ircd_log.h" +#include "ircd_string.h" +#include "ircd_tls.h" +#include "listener.h" +#include "s_conf.h" +#include "s_debug.h" + +#include +#include +#include +#include + +#if defined(GNUTLS_AUTO_REAUTH) /* 3.6.4 */ +# define TLS_SESSION_FLAGS GNUTLS_NONBLOCK | GNUTLS_NO_SIGNAL \ + | GNUTLS_POST_HANDSHAKE_AUTH | GNUTLS_AUTH_REAUTH \ + | GNUTLS_ENABLE_EARLY_START +#elif defined(GNUTLS_ENABLE_EARLY_START) /* earlier in 3.6.4 */ +# define TLS_SESSION_FLAGS GNUTLS_NONBLOCK | GNUTLS_NO_SIGNAL \ + | GNUTLS_ENABLE_EARLY_START +#else +# define TLS_SESSION_FLAGS GNUTLS_NONBLOCK | GNUTLS_NO_SIGNAL +#endif + +const char *ircd_tls_version = "gnutls " GNUTLS_VERSION; + +static gnutls_priority_t tls_priority; +static gnutls_certificate_credentials_t tls_cert; + +int ircd_tls_init(void) +{ + static int once; + const char *str, *s_2; + int res; + + /* Early out? */ + if (EmptyString(ircd_tls_keyfile) || EmptyString(ircd_tls_certfile)) + return 0; + + if (!once) + { + once = 1; + + /* Global initialization is automatic for 3.3.0 and later. */ +#if GNUTLS_VERSION_NUMBER < 0x030300 + if (gnutls_global_init() != GNUTLS_E_SUCCESS) + return 1; +#endif + } + + str = feature_str(FEAT_TLS_CIPHERS); + if (str) + { + gnutls_priority_t new_priority; + res = gnutls_priority_init2(&new_priority, str, &str, + GNUTLS_PRIORITY_INIT_DEF_APPEND); + if (res == GNUTLS_E_SUCCESS) + { + if (tls_priority) + gnutls_priority_deinit(tls_priority); + tls_priority = new_priority; + } + else if (res == GNUTLS_E_INVALID_REQUEST) + log_write(LS_SYSTEM, L_ERROR, 0, "Invalid TLS_CIPHERS near '%s'", str); + else + log_write(LS_SYSTEM, L_ERROR, 0, "Unable to use TLS_CIPHERS: %s", + gnutls_strerror(res)); + /* But continue on failures. */ + } + else + { + if (tls_priority) + gnutls_priority_deinit(tls_priority); + tls_priority = NULL; + } + + if (1) + { + gnutls_certificate_credentials_t new_cert; + + gnutls_certificate_allocate_credentials(&new_cert); + res = gnutls_certificate_set_x509_key_file2(new_cert, + ircd_tls_certfile, ircd_tls_keyfile, GNUTLS_X509_FMT_PEM, "", + GNUTLS_PKCS_PLAIN | GNUTLS_PKCS_NULL_PASSWORD); + if (res < 0) /* may return a positive index */ + { + log_write(LS_SYSTEM, L_ERROR, 0, "Unable to load TLS keyfile and/or" + " certificate: %s", gnutls_strerror(res)); + gnutls_certificate_free_credentials(new_cert); + return 2; + } + + str = feature_str(FEAT_TLS_CACERTFILE); + s_2 = feature_str(FEAT_TLS_CACERTDIR); + if (!EmptyString(str) || !EmptyString(s_2)) + { + if (!EmptyString(s_2)) + { + res = gnutls_certificate_set_x509_trust_dir(new_cert, s_2, + GNUTLS_X509_FMT_PEM); + if (res < 0) + { + log_write(LS_SYSTEM, L_ERROR, 0, "Unable to read CA certs from" + " %s: %s", s_2, gnutls_strerror(res)); + } + } + + if (!EmptyString(str)) + { + res = gnutls_certificate_set_x509_trust_file(new_cert, str, + GNUTLS_X509_FMT_PEM); + if (res < 0) + { + log_write(LS_SYSTEM, L_ERROR, 0, "Unable to read CA certs from" + " %s: %s", str, gnutls_strerror(res)); + } + } + } + else + (void)gnutls_certificate_set_x509_system_trust(new_cert); + + if (tls_cert) + gnutls_certificate_free_credentials(tls_cert); + tls_cert = new_cert; + } + + return 0; +} + +static void *tls_create(int flag, int fd, const char *name, const char *tls_ciphers) +{ + gnutls_session_t tls; + int res; + + res = gnutls_init(&tls, flag | TLS_SESSION_FLAGS); + if (res != GNUTLS_E_SUCCESS) + return NULL; + + res = gnutls_credentials_set(tls, GNUTLS_CRD_CERTIFICATE, tls_cert); + if (res != GNUTLS_E_SUCCESS) + { + gnutls_deinit(tls); + return NULL; + } + + /* gnutls does not appear to allow an application to select which + * SSL/TLS protocol versions to support, except indirectly through + * priority strings. + */ + + if (tls_ciphers) + { + const char *sep; + res = gnutls_set_default_priority_append(tls, tls_ciphers, &sep, 0); + if (!name || res == GNUTLS_E_SUCCESS) + { + /* do not report error */ + } + else if (res == GNUTLS_E_INVALID_REQUEST) + log_write(LS_SYSTEM, L_ERROR, 0, "Invalid tls ciphers for %s near '%s'", + name, sep); + else + log_write(LS_SYSTEM, L_ERROR, 0, "Unable to set TLS ciphers for %s: %s", + name, gnutls_strerror(res)); + } + else if (tls_priority) + { + res = gnutls_priority_set(tls, tls_priority); + if (name && (res != GNUTLS_E_SUCCESS)) + log_write(LS_SYSTEM, L_ERROR, 0, "Unable to use default TLS ciphers" + " for %s: %s", name, gnutls_strerror(res)); + } + else + { + gnutls_set_default_priority(tls); + } + + if (flag & GNUTLS_SERVER) + gnutls_certificate_server_set_request(tls, GNUTLS_CERT_REQUEST); + + gnutls_handshake_set_timeout(tls, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); + + gnutls_transport_set_int(tls, fd); + + return tls; +} + +void *ircd_tls_accept(struct Listener *listener, int fd) +{ + return tls_create(GNUTLS_SERVER, fd, NULL, listener->tls_ciphers); +} + +void *ircd_tls_connect(struct ConfItem *aconf, int fd) +{ + return tls_create(GNUTLS_CLIENT, fd, aconf->name, aconf->tls_ciphers); +} + +void ircd_tls_close(void *ctx, const char *message) +{ + gnutls_bye(ctx, GNUTLS_SHUT_RDWR); + gnutls_deinit(ctx); +} + +void ircd_tls_fingerprint(void *ctx, char *fingerprint) +{ + memset(fingerprint, 0, 32); +} + +static void handle_blocked(struct Client *cptr, gnutls_session_t tls) +{ + if (gnutls_record_get_direction(tls)) + socket_events(&cli_socket(cptr), SOCK_EVENT_WRITABLE); + else + socket_events(&cli_socket(cptr), SOCK_EVENT_READABLE); +} + +int ircd_tls_negotiate(struct Client *cptr) +{ + gnutls_session_t tls; + int res; + + tls = s_tls(&cli_socket(cptr)); + + if (!tls) + return 1; + + res = gnutls_handshake(tls); + switch (res) + { + case GNUTLS_E_SUCCESS: + return 1; + case GNUTLS_E_INTERRUPTED: + case GNUTLS_E_AGAIN: + handle_blocked(cptr, tls); + /* and fall through */ + case GNUTLS_E_WARNING_ALERT_RECEIVED: + case GNUTLS_E_GOT_APPLICATION_DATA: + return 0; + default: + Debug((DEBUG_DEBUG, " ... gnutls_handshake() failed -> %s (%d)", + gnutls_strerror(res), res)); + return gnutls_error_is_fatal(res) ? -1 : 0; + } +} + +IOResult ircd_tls_recv(struct Client *cptr, char *buf, + unsigned int length, unsigned int *count_out) +{ + gnutls_session_t tls; + int res; + + *count_out = 0; + tls = s_tls(&cli_socket(cptr)); + if (!tls) + return IO_FAILURE; + if (IsNegotiatingTLS(cptr)) + { + res = gnutls_handshake(tls); + if (res == GNUTLS_E_INTERRUPTED || res == GNUTLS_E_AGAIN) + { + handle_blocked(cptr, tls); + return IO_BLOCKED; + } + if (res != GNUTLS_E_SUCCESS) + return gnutls_error_is_fatal(res) ? IO_FAILURE : IO_BLOCKED; + } + + res = gnutls_record_recv(tls, buf, length); + if (res >= 0) + { + *count_out = res; + return IO_SUCCESS; + } + if (res == GNUTLS_E_REHANDSHAKE) + { + res = gnutls_handshake(tls); + if (res >= 0) + return IO_SUCCESS; + } + if (res == GNUTLS_E_INTERRUPTED || GNUTLS_E_AGAIN) + handle_blocked(cptr, tls); + return gnutls_error_is_fatal(res) ? IO_FAILURE : IO_BLOCKED; +} + +IOResult ircd_tls_sendv(struct Client *cptr, struct MsgQ *buf, + unsigned int *count_in, unsigned int *count_out) +{ + struct iovec iov[512]; + gnutls_session_t tls; + struct Connection *con; + IOResult result = IO_BLOCKED; + ssize_t res; + int ii, count; + + con = cli_connect(cptr); + tls = s_tls(&con_socket(con)); + if (!tls) + return IO_FAILURE; + if (IsNegotiatingTLS(cptr)) + { + res = gnutls_handshake(tls); + if (res == GNUTLS_E_INTERRUPTED || res == GNUTLS_E_AGAIN) + { + handle_blocked(cptr, tls); + return IO_BLOCKED; + } + if (res != GNUTLS_E_SUCCESS) + return gnutls_error_is_fatal(res) ? IO_FAILURE : IO_BLOCKED; + } + + /* TODO: Try to use gnutls_record_cork()/_uncork()/_check_corked(). + * The exact semantics of check_corked()'s return value are not clear: + * What does "the size of the corked data" signify relative to what + * has been accepted or must be provided to a future call to + * gnutls_record_send()? + */ + *count_out = 0; + if (con->con_rexmit) + { + res = gnutls_record_send(tls, con->con_rexmit, con->con_rexmit_len); + if (res <= 0) + { + if (res == GNUTLS_E_INTERRUPTED || GNUTLS_E_AGAIN) + handle_blocked(cptr, tls); + return gnutls_error_is_fatal(res) ? IO_FAILURE : IO_BLOCKED; + } + msgq_excise(buf, con->con_rexmit, con->con_rexmit_len); + con->con_rexmit_len = 0; + con->con_rexmit = NULL; + result = IO_SUCCESS; + } + + count = msgq_mapiov(buf, iov, sizeof(iov) / sizeof(iov[0]), count_in); + for (ii = 0; ii < count; ++ii) + { + res = gnutls_record_send(tls, iov[ii].iov_base, iov[ii].iov_len); + if (res > 0) + { + *count_out += res; + result = IO_SUCCESS; + continue; + } + + if (res == GNUTLS_E_INTERRUPTED || res == GNUTLS_E_AGAIN) + { + handle_blocked(cptr, tls); + cli_connect(cptr)->con_rexmit = iov[ii].iov_base; + cli_connect(cptr)->con_rexmit_len = iov[ii].iov_len; + } + result = gnutls_error_is_fatal(res) ? IO_FAILURE : IO_BLOCKED; + break; + } + + return result; +} diff --git a/ircd/tls_libtls.c b/ircd/tls_libtls.c new file mode 100644 index 00000000..83bcbb9f --- /dev/null +++ b/ircd/tls_libtls.c @@ -0,0 +1,303 @@ +/* + * IRC - Internet Relay Chat, ircd/tls_gnutls.c + * Copyright (C) 2019 Michael Poole + * + * 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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +/** @file + * @brief ircd TLS functions using OpenBSD's libtls. + */ + +#include "config.h" +#include "client.h" +#include "ircd_features.h" +#include "ircd_log.h" +#include "ircd_string.h" +#include "ircd_tls.h" +#include "listener.h" +#include "s_debug.h" + +#include +#include +#include + +const char *ircd_tls_version = "libtls " #TLS_API; +static struct tls_config *tls_cfg; +static struct tls *tls_server; + +int ircd_tls_init(void) +{ + static int libtls_init; + struct tls_config *new_cfg = NULL; + struct tls *new_srv = NULL; + uint32_t protos; + const char *str; + int res; + + if (EmptyString(ircd_tls_keyfile) || EmptyString(ircd_tls_certfile)) + goto done; + + if (!libtls_init) + { + libtls_init = 1; + if (tls_init() < 0) + { + log_write(LS_SYSTEM, L_ERROR, 0, "tls_init() failed", ); + return 1; + } + } + + new_cfg = tls_config_new(); + if (!new_cfg) + { + log_write(LS_SYSTEM, L_ERROR, 0, "tls_config_new() failed"); + return 2; + } + + new_srv = tls_server(); + if (!new_srv) + { + log_write(LOS_SYSTEM, L_ERROR, 0, "unable to create new TLS server context"); + tls_config_free(new_cfg); + return 3; + } + + res = tls_config_add_keypair_file(new_cfg, ircd_tls_certfile, ircd_tls_keyfile); + if (res < 0) + { + log_write(LS_SYSTEM, L_ERROR, 0, "unable to load certificate and key: %s", + tls_config_error(new_cfg)); + tls_config_free(new_cfg); + tls_free(new_srv); + return 4; + } + + str = feature_str(FEAT_TLS_CACERTDIR); + if (!EmptyString(str)) + { + if (tls_config_set_ca_path(new_cfg, str) < 0) + { + log_write(LS_SYSTEM, L_ERROR, 0, "unable to set CA path to %s: %s", + str, tls_config_error(new_cfg)); + } + } + + str = feature_str(FEAT_TLS_CACERTFILE); + if (!EmptyString(str)) + { + if (tls_config_set_ca_file(new_cfg, str) < 0) + { + log_write(LS_SYSTEM, L_ERROR, 0, "unable to set CA file to %s: %s", + str, tls_config_error(new_cfg)); + } + } + + protos = 0; + /* libtls does not support SSLv2 or SSLv3. */ + if (feature_bool(FEAT_TLS_V1P0)) + protos |= TLS_PROTOCOL_TLSv1_0; + if (feature_bool(FEAT_TLS_V1P1)) + protos |= TLS_PROTOCOL_TLSv1_1; + if (feature_bool(FEAT_TLS_V1P2)) + protos |= TLS_PROTOCOL_TLSv1_2; +#ifdef TLS_PROTOCOL_TLSv1_3 + if (feature_bool(FEAT_TLS_V1P3)) + protos |= TLS_PROTOCOL_TLSv1_3; +#endif + if (tls_config_set_protocols(new_cfg, protos) < 0) + { + log_write(LS_SYSTEM, L_ERROR, 0, "unable to select TLS versions: %s", + tls_config_error(new_cfg)); + } + + str = feature_str(FEAT_TLS_CIPHERS); + if (!EmptyString(str)) + { + if (tls_config_set_ciphers(new_cfg, str) < 0) + { + log_write(LS_SYSTEM, L_ERROR, 0, "unable to select TLS ciphers: %s", + tls_config_error(new_cfg)); + } + } + + tls_config_verify_client_optional(new_cfg); + + if (tls_configure(new_srv, new_cfg) < 0) + { + log_write(LS_SYSTEM, L_ERROR, 0, "unable to configure TLS server context: %s", + tls_error(new_srv)); + tls_config_free(new_cfg); + tls_free(new_srv); + return 5; + } + +done: + tls_config_free(tls_cfg); + tls_cfg = new_cfg; + tls_free(tls_server); + tls_server = new_srv; + return 0; +} + +void *ircd_tls_accept(struct Listener *listener, int fd) +{ + struct tls *tls; + + /* TODO: adjust acceptable ciphers list */ + + if (tls_accept_socket(tls_server, &tls, fd) < 0) + { + log_write(LS_SYSTEM, L_ERROR, 0, "TLS accept failed: %s", + tls_error(tls_server)); + return NULL; + } + + return tls; +} + +void *ircd_tls_connect(struct ConfItem *aconf, int fd) +{ + struct tls *tls; + + tls = tls_client(); + if (!tls) + { + log_write(LS_SYSTEM, L_ERROR, 0, "tls_client() failed"); + return NULL; + } + + /* TODO: adjust acceptable ciphers list */ + + if (tls_configure(tls, tls_cfg) < 0) + { + log_write(LS_SYSTEM, L_ERROR, 0, "tls_configure failed for client: %s", + tls_error(tls)); + tls_free(tls); + return NULL; + } + + if (tls_connect_socket(tls, fd, aconf->name) < 0) + { + log_write(LS_SYSTEM, L_ERROR, 0, "tls_connect_socket failed for %s: %s", + aconf->name, tls_error(tls)); + tls_free(tls); + return NULL; + } + + return tls; +} + +void ircd_tls_close(void *ctx, const char *message) +{ + /* TODO: handle TLS_WANT_POLL{IN,OUT} from tls_close() */ + tls_close(ctx); + tls_free(ctx); +} + +void ircd_tls_fingerprint(void *ctx, char *fingerprint) +{ + const char *text; + + text = tls_peer_cert_hash(ctx); + if (text && !ircd_strncmp(text, "SHA256:", 7)) + { + /* TODO: convert from ASCII hex to binary */ + return; + } + memset(fingerprint, 0, 32); +} + +static int tls_handle_error(struct Client *cptr, struct tls *tls, int res) +{ + if (err == TLS_WANT_POLLIN) + { + socket_events(&cli_socket(cptr), SOCK_EVENT_READABLE); + return 0; + } + if (err == TLS_WANT_POLLOUT) + { + socket_events(&cli_socket(cptr), SOCK_EVENT_WRITABLE); + return 0; + } + Debug((DEBUG_DEBUG, "tls fatal error for %s: %s", cli_name(cptr), tls_error(tls))); + tls_free(tls); + s_tls(&cli_socket(cptr)) = NULL; + return -1; +} + +int ircd_tls_negotiate(struct Client *cptr) +{ + struct tls *tls; + int res; + + tls = s_tls(&cli_socket(cptr)); + if (!tls) + return 1; + + res = tls_handshake(tls); + if (res == 1) + return 1; + return tls_handle_error(cptr, tls, res); +} + +IOResult ircd_tls_recv(struct Client *cptr, char *buf, + unsigned int length, unsigned int *count_out) +{ + struct tls *tls; + int res; + + tls = s_tls(&cli_socket(cptr)); + if (!tls) + return IO_FAILURE; + + res = tls_read(tls, buf, length); + if (res >= 0) + { + *count_out = res; + return IO_SUCCESS; + } + return (tls_handle_error(cptr, tls, res) < 0) ? IO_FAILURE : IO_BLOCKED; +} + +IOResult ircd_tls_sendv(struct Client *cptr, struct MsgQ *buf, + unsigned int *count_in, unsigned int *count_out) +{ + struct iovec iov[512]; + struct tls *tls; + struct Connection *con; + int ii, count, res, orig_errno; + + con = cli_connect(cptr); + tls = s_tls(&con_socket(con)); + if (!tls) + return IO_FAILURE; + + /* tls_write() does not document any restriction on retries. */ + *count_out = 0; + count = msgq_mapiov(buf, iov, sizeof(iov) / sizeof(iov[0]), count_in); + for (ii = 0; ii < count; ++ii) + { + res = tls_write(tls, iov[ii].iov_base, iov[ii].iov_len); + if (res > 0) + { + *count_out += res; + break; + } + + return (tls_handle_error(cptr, tls, res) < 0) ? IO_FAILURE : IO_BLOCKED; + } + + return IO_SUCCESS; +} diff --git a/ircd/tls_none.c b/ircd/tls_none.c new file mode 100644 index 00000000..6d0fed87 --- /dev/null +++ b/ircd/tls_none.c @@ -0,0 +1,70 @@ +/* + * IRC - Internet Relay Chat, ircd/tls_none.c + * Copyright (C) 2019 Michael Poole + * + * 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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +/** @file + * @brief Stub (noop) implementation of ircd TLS functions. + */ + +#include "config.h" +#include "ircd_tls.h" +#include "client.h" +#include +#include + +const char *ircd_tls_version = NULL; + +int ircd_tls_init(void) +{ + return 0; +} + +void *ircd_tls_accept(struct Listener *listener, int fd) +{ + return NULL; +} + +void *ircd_tls_connect(struct ConfItem *aconf, int fd) +{ + return NULL; +} + +void ircd_tls_close(void *ctx, const char *message) +{ +} + +void ircd_tls_fingerprint(void *ctx, char *fingerprint) +{ + memset(fingerprint, 0, 32); +} + +int ircd_tls_negotiate(struct Client *cptr) +{ + return 1; +} + +IOResult ircd_tls_recv(struct Client *cptr, char *buf, + unsigned int length, unsigned int *count_out) +{ + return os_recv_nonb(cli_fd(cptr), buf, length, count_out); +} + +IOResult ircd_tls_sendv(struct Client *cptr, struct MsgQ *buf, + unsigned int *count_in, unsigned int *count_out) +{ + return os_sendv_nonb(cli_fd(cptr), buf, count_in, count_out); +} diff --git a/ircd/tls_openssl.c b/ircd/tls_openssl.c new file mode 100644 index 00000000..b78aa819 --- /dev/null +++ b/ircd/tls_openssl.c @@ -0,0 +1,417 @@ +/* + * IRC - Internet Relay Chat, ircd/tls_openssl.c + * Copyright (C) 2019 Michael Poole + * + * 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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +/** @file + * @brief ircd TLS functions using OpenSSL. + */ + +#include "config.h" +#include "client.h" +#include "ircd_alloc.h" +#include "ircd_features.h" +#include "ircd_log.h" +#include "ircd_string.h" +#include "ircd_tls.h" +#include "listener.h" +#include "s_conf.h" +#include "s_debug.h" + +#include +#include +#include +#include /* IOV_MAX */ + +const char *ircd_tls_version = OPENSSL_VERSION_TEXT; + +static SSL_CTX *base_ctx; +static const EVP_MD *fp_digest; + +static void ssl_log_error(const char *msg) +{ + unsigned long err; + char buf[BUFSIZE]; + + err = ERR_get_error(); + if (err) + { + ERR_error_string_n(err, buf, sizeof(buf)); + log_write(LS_SYSTEM, L_ERROR, 0, "OpenSSL %s: %s", msg, buf); + + while ((err = ERR_get_error()) != 0) + { + ERR_error_string_n(err, buf, sizeof(buf)); + log_write(LS_SYSTEM, L_ERROR, 0, " ... %s", buf); + } + } + else + { + log_write(LS_SYSTEM, L_ERROR, 0, "Unknown OpenSSL failure: %s", msg); + } +} + +void ircd_tls_fingerprint(void *ctx, char *fingerprint) +{ + SSL *tls; + X509 *cert; + int res; + + if (!ctx) + { + no_fingerprint: + memset(fingerprint, 0, 32); + return; + } + + tls = ctx; + cert = SSL_get_peer_certificate(tls); + if (!cert) + goto no_fingerprint; + + res = X509_digest(cert, fp_digest, (unsigned char *)fingerprint, NULL); + X509_free(cert); + if (res) + Debug((DEBUG_NOTICE, "X509_digest failed to make fingerprint")); +} + +static void ssl_set_ciphers(SSL_CTX *ctx, SSL *tls, const char *text) +{ + const char *sep; + + if (!text) + return; + + sep = strchr(text, ' '); + if (sep != NULL) + { + char *tmp; +#if HAVE_SSL_SET_CIPHERSUITES + if (ctx) + SSL_CTX_set_ciphersuites(ctx, sep + 1); + if (tls) + SSL_set_ciphersuites(tls, sep + 1); +#endif + + tmp = MyMalloc(sep + 1 - text); + ircd_strncpy(tmp, text, sep - text); + if (ctx) + SSL_CTX_set_cipher_list(ctx, tmp); + if (tls) + SSL_set_cipher_list(tls, tmp); + MyFree(tmp); + } + else if (*text != '\0') + { + if (ctx) + SSL_CTX_set_cipher_list(ctx, text); + if (tls) + SSL_set_cipher_list(tls, text); + } + else + { + if (ctx) + SSL_CTX_set_cipher_list(ctx, SSL_DEFAULT_CIPHER_LIST); + if (tls) + SSL_set_cipher_list(tls, SSL_DEFAULT_CIPHER_LIST); +#if HAVE_SSL_SET_CIPHERSUITES + if (ctx) + SSL_CTX_set_ciphersuites(ctx, TLS_DEFAULT_CIPHERSUITES); + if (tls) + SSL_set_ciphersuites(tls, TLS_DEFAULT_CIPHERSUITES); +#endif + } +} + +int ircd_tls_init(void) +{ + static int openssl_init; + SSL_CTX *new_ctx = NULL; + const char *str, *s_2; + int res; + + /* Early out if no private key or certificate file was given. */ + if (EmptyString(ircd_tls_keyfile) || EmptyString(ircd_tls_certfile)) + goto done; + + if (!openssl_init) + { + openssl_init = 1; + SSL_library_init(); + SSL_load_error_strings(); + + if (!RAND_poll()) + { + ssl_log_error("RAND_poll failed"); + return 1; + } + + fp_digest = EVP_sha256(); + } + + new_ctx = SSL_CTX_new(SSLv23_method()); + if (!new_ctx) + { + ssl_log_error("SSL_CTX_new failed"); + return 2; + } + + if (feature_bool(FEAT_TLS_SSLV2)) + SSL_CTX_set_options(new_ctx, SSL_OP_NO_SSLv2); + if (feature_bool(FEAT_TLS_SSLV3)) + SSL_CTX_set_options(new_ctx, SSL_OP_NO_SSLv3); + if (feature_bool(FEAT_TLS_V1P0)) + SSL_CTX_set_options(new_ctx, SSL_OP_NO_TLSv1); + if (feature_bool(FEAT_TLS_V1P1)) + SSL_CTX_set_options(new_ctx, SSL_OP_NO_TLSv1_1); + if (feature_bool(FEAT_TLS_V1P2)) + SSL_CTX_set_options(new_ctx, SSL_OP_NO_TLSv1_2); + + /* OpenSSL only defines this macro if it supports TLS 1.3. */ +#if defined(SSL_OP_NO_TLSv1_3) + if (feature_bool(FEAT_TLS_V1P3)) + SSL_CTX_set_options(new_ctx, SSL_OP_NO_TLSv1_3); +#endif + + res = SSL_CTX_use_certificate_chain_file(new_ctx, ircd_tls_certfile); + if (res != 1) + { + ssl_log_error("unable to load certificate file"); + SSL_CTX_free(new_ctx); + return 3; + } + + res = SSL_CTX_use_PrivateKey_file(new_ctx, ircd_tls_keyfile, SSL_FILETYPE_PEM); + if (res != 1) + { + ssl_log_error("unable to load private key"); + SSL_CTX_free(new_ctx); + return 4; + } + + res = SSL_CTX_check_private_key(new_ctx); + if (res != 1) + { + ssl_log_error("private key did not check out"); + SSL_CTX_free(new_ctx); + return 5; + } + + ssl_set_ciphers(new_ctx, NULL, feature_str(FEAT_TLS_CIPHERS)); + + str = feature_str(FEAT_TLS_CACERTFILE); + s_2 = feature_str(FEAT_TLS_CACERTDIR); + if (!EmptyString(str) || !EmptyString(s_2)) + { + res = SSL_CTX_load_verify_locations(new_ctx, str, s_2); + if (res != 1) + { + ssl_log_error("using TLS_CACERTFILE/TLS_CACERTDIR failed"); + /* but keep going */ + } + } + +done: + if (base_ctx) + SSL_CTX_free(base_ctx); + base_ctx = new_ctx; + return 0; +} + +void ircd_tls_close(void *ctx, const char *message) +{ + /* TODO: handle blocked I/O for shutdown */ + SSL_shutdown(ctx); + SSL_free(ctx); +} + +static void ssl_set_fd(SSL *tls, int fd) +{ + SSL_set_fd(tls, fd); + + BIO_set_nbio(SSL_get_rbio(tls), 1); + + BIO_set_nbio(SSL_get_wbio(tls), 1); +} + +void *ircd_tls_accept(struct Listener *listener, int fd) +{ + SSL *tls; + + tls = SSL_new(base_ctx); + if (!tls) + { + ssl_log_error("unable to create SSL session"); + return NULL; + } + + if (listener->tls_ciphers) + ssl_set_ciphers(NULL, tls, listener->tls_ciphers); + + ssl_set_fd(tls, fd); + + SSL_set_accept_state(tls); + + return tls; +} + +void *ircd_tls_connect(struct ConfItem *aconf, int fd) +{ + SSL *tls; + + tls = SSL_new(base_ctx); + if (!tls) + { + ssl_log_error("unable to create SSL session"); + return NULL; + } + + if (aconf->tls_ciphers) + ssl_set_ciphers(NULL, tls, aconf->tls_ciphers); + + ssl_set_fd(tls, fd); + + SSL_set_connect_state(tls); + + return tls; +} + +static int ssl_handle_error(struct Client *cptr, SSL *tls, int res, int orig_errno) +{ + int err = SSL_get_error(tls, res); + switch (err) + { + case SSL_ERROR_WANT_READ: + socket_events(&cli_socket(cptr), SOCK_EVENT_READABLE); + return 0; + case SSL_ERROR_WANT_WRITE: + socket_events(&cli_socket(cptr), SOCK_EVENT_WRITABLE); + return 0; + case SSL_ERROR_SYSCALL: + if (orig_errno == EINTR || orig_errno == EAGAIN || orig_errno == EWOULDBLOCK) + return 0; + break; + default: + /* Fatal error (or EOF). */ + SSL_set_shutdown(tls, SSL_RECEIVED_SHUTDOWN); + SSL_set_quiet_shutdown(tls, 1); + SSL_shutdown(tls); + break; + } + SSL_free(tls); + s_tls(&cli_socket(cptr)) = NULL; + return -1; +} + +int ircd_tls_negotiate(struct Client *cptr) +{ + SSL *tls; + int res; + + tls = s_tls(&cli_socket(cptr)); + if (!tls) + return 1; + + res = SSL_accept(tls); + if (res == 1) + { + ClearNegotiatingTLS(cptr); + } + else + { + int orig_errno = errno; + /* Handshake in progress. */ + res = (ssl_handle_error(cptr, tls, res, orig_errno) < 0) ? -1 : 0; + } + + return res; +} + +IOResult ircd_tls_recv(struct Client *cptr, char *buf, + unsigned int length, unsigned int *count_out) +{ + SSL *tls; + int res, orig_errno; + + tls = s_tls(&cli_socket(cptr)); + if (!tls) + return IO_FAILURE; + + res = SSL_read(tls, buf, length); + if (res > 0) + { + *count_out = res; + return IO_SUCCESS; + } + + orig_errno = errno; + *count_out = 0; + + return (ssl_handle_error(cptr, tls, res, orig_errno) < 0) ? IO_FAILURE : IO_BLOCKED; +} + +IOResult ircd_tls_sendv(struct Client *cptr, struct MsgQ *buf, + unsigned int *count_in, + unsigned int *count_out) +{ + struct iovec iov[512]; + SSL *tls; + struct Connection *con; + IOResult result = IO_BLOCKED; + int ii, count, res, orig_errno; + + con = cli_connect(cptr); + tls = s_tls(&con_socket(con)); + if (!tls) + return IO_FAILURE; + *count_out = 0; + if (con->con_rexmit) + { + res = SSL_write(tls, con->con_rexmit, con->con_rexmit_len); + if (res <= 0) + { + orig_errno = errno; + return (ssl_handle_error(cptr, tls, res, orig_errno) < 0) + ? IO_FAILURE : IO_BLOCKED; + } + msgq_excise(buf, con->con_rexmit, con->con_rexmit_len); + con->con_rexmit_len = 0; + con->con_rexmit = NULL; + result = IO_SUCCESS; + } + + count = msgq_mapiov(buf, iov, sizeof(iov) / sizeof(iov[0]), count_in); + for (ii = 0; ii < count; ++ii) + { + res = SSL_write(tls, iov[ii].iov_base, iov[ii].iov_len); + if (res > 0) + { + *count_out += res; + result = IO_SUCCESS; + continue; + } + + orig_errno = errno; + if (ssl_handle_error(cptr, tls, res, orig_errno) < 0) + return IO_FAILURE; + + cli_connect(cptr)->con_rexmit = iov[ii].iov_base; + cli_connect(cptr)->con_rexmit_len = iov[ii].iov_len; + break; + } + + return result; +} diff --git a/ircd/whocmds.c b/ircd/whocmds.c index 0b166ba0..9a02a231 100644 --- a/ircd/whocmds.c +++ b/ircd/whocmds.c @@ -208,6 +208,8 @@ void do_who(struct Client* sptr, struct Client* acptr, struct Channel* repchan, } if (HasHiddenHost(acptr)) *(p1++) = 'x'; + if (IsTLS(acptr)) + *(p1++) = 'z'; } if (!fields || (fields & WHO_FIELD_DIS)) From 8b2d40b3c87f7890873a292ceb0af654b7d8b1a2 Mon Sep 17 00:00:00 2001 From: Michael Poole Date: Tue, 31 Dec 2019 10:18:29 -0500 Subject: [PATCH 02/20] configure: Compile without OpenSSL or gnutls installed --- acinclude.m4 | 5 +++-- configure.ac | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/acinclude.m4 b/acinclude.m4 index aa84378d..750e94c6 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -175,8 +175,8 @@ dnl Set unet_cv_with_tls and TLS_C to an available TLS implementation. dnl AC_DEFUN([unet_TLS], [dnl Perform some preliminary checks for system TLS libraries. -AX_CHECK_OPENSSL() -PKG_CHECK_MODULES([GNUTLS], [gnutls]) +AX_CHECK_OPENSSL(, [:]) +PKG_CHECK_MODULES([GNUTLS], [gnutls], , [:]) unet_LIBTLS dnl --with-tls allows selection of the TLS library. @@ -223,6 +223,7 @@ xlibtls) TLS_C="tls_libtls.c" ;; xyes|xno) + unet_cv_with_tls="none" TLS_C="tls_none.c" ;; esac diff --git a/configure.ac b/configure.ac index 73077391..5b25f2a7 100644 --- a/configure.ac +++ b/configure.ac @@ -748,6 +748,7 @@ ircu is now hopefully configured for your system. CPath: $unet_cv_with_cpath LPath: $unet_cv_with_lpath Maximum connections: $unet_cv_with_maxcon + TLS implementation: $unet_cv_with_tls poll() engine: $unet_cv_enable_poll kqueue() engine: $unet_cv_enable_kqueue From 0bc3d142561bd8e3b15ea02328c372d1a81ed146 Mon Sep 17 00:00:00 2001 From: Michael Poole Date: Fri, 6 Mar 2020 17:29:43 -0500 Subject: [PATCH 03/20] tls_libtls: Fix compilation --- ircd/tls_libtls.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/ircd/tls_libtls.c b/ircd/tls_libtls.c index 83bcbb9f..07a3beb4 100644 --- a/ircd/tls_libtls.c +++ b/ircd/tls_libtls.c @@ -27,15 +27,17 @@ #include "ircd_string.h" #include "ircd_tls.h" #include "listener.h" +#include "s_conf.h" #include "s_debug.h" #include #include #include -const char *ircd_tls_version = "libtls " #TLS_API; +#define CONCAT(A, B) A # B +const char *ircd_tls_version = CONCAT("libtls ", TLS_API); static struct tls_config *tls_cfg; -static struct tls *tls_server; +static struct tls *tls_srv; int ircd_tls_init(void) { @@ -54,7 +56,7 @@ int ircd_tls_init(void) libtls_init = 1; if (tls_init() < 0) { - log_write(LS_SYSTEM, L_ERROR, 0, "tls_init() failed", ); + log_write(LS_SYSTEM, L_ERROR, 0, "tls_init() failed"); return 1; } } @@ -69,7 +71,7 @@ int ircd_tls_init(void) new_srv = tls_server(); if (!new_srv) { - log_write(LOS_SYSTEM, L_ERROR, 0, "unable to create new TLS server context"); + log_write(LS_SYSTEM, L_ERROR, 0, "unable to create new TLS server context"); tls_config_free(new_cfg); return 3; } @@ -146,8 +148,8 @@ int ircd_tls_init(void) done: tls_config_free(tls_cfg); tls_cfg = new_cfg; - tls_free(tls_server); - tls_server = new_srv; + tls_free(tls_srv); + tls_srv = new_srv; return 0; } @@ -157,10 +159,10 @@ void *ircd_tls_accept(struct Listener *listener, int fd) /* TODO: adjust acceptable ciphers list */ - if (tls_accept_socket(tls_server, &tls, fd) < 0) + if (tls_accept_socket(tls_srv, &tls, fd) < 0) { log_write(LS_SYSTEM, L_ERROR, 0, "TLS accept failed: %s", - tls_error(tls_server)); + tls_error(tls_srv)); return NULL; } @@ -219,7 +221,7 @@ void ircd_tls_fingerprint(void *ctx, char *fingerprint) memset(fingerprint, 0, 32); } -static int tls_handle_error(struct Client *cptr, struct tls *tls, int res) +static int tls_handle_error(struct Client *cptr, struct tls *tls, int err) { if (err == TLS_WANT_POLLIN) { From 8a7a3a7a0cca379654fd0098e1d3dc1ade118a1e Mon Sep 17 00:00:00 2001 From: Michael Poole Date: Fri, 6 Mar 2020 17:30:00 -0500 Subject: [PATCH 04/20] tls_openssl: Fix sense of FEAT_TLS_* version settings --- ircd/tls_openssl.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/ircd/tls_openssl.c b/ircd/tls_openssl.c index b78aa819..820be43d 100644 --- a/ircd/tls_openssl.c +++ b/ircd/tls_openssl.c @@ -36,6 +36,10 @@ #include #include /* IOV_MAX */ +#if OPENSSL_VERSION_NUMBER < 0x10100000L +# error ircu2 requires OpenSSL >= 1.1.0 +#endif + const char *ircd_tls_version = OPENSSL_VERSION_TEXT; static SSL_CTX *base_ctx; @@ -169,20 +173,20 @@ int ircd_tls_init(void) return 2; } - if (feature_bool(FEAT_TLS_SSLV2)) + if (!feature_bool(FEAT_TLS_SSLV2)) SSL_CTX_set_options(new_ctx, SSL_OP_NO_SSLv2); - if (feature_bool(FEAT_TLS_SSLV3)) + if (!feature_bool(FEAT_TLS_SSLV3)) SSL_CTX_set_options(new_ctx, SSL_OP_NO_SSLv3); - if (feature_bool(FEAT_TLS_V1P0)) + if (!feature_bool(FEAT_TLS_V1P0)) SSL_CTX_set_options(new_ctx, SSL_OP_NO_TLSv1); - if (feature_bool(FEAT_TLS_V1P1)) + if (!feature_bool(FEAT_TLS_V1P1)) SSL_CTX_set_options(new_ctx, SSL_OP_NO_TLSv1_1); - if (feature_bool(FEAT_TLS_V1P2)) + if (!feature_bool(FEAT_TLS_V1P2)) SSL_CTX_set_options(new_ctx, SSL_OP_NO_TLSv1_2); /* OpenSSL only defines this macro if it supports TLS 1.3. */ #if defined(SSL_OP_NO_TLSv1_3) - if (feature_bool(FEAT_TLS_V1P3)) + if (!feature_bool(FEAT_TLS_V1P3)) SSL_CTX_set_options(new_ctx, SSL_OP_NO_TLSv1_3); #endif From 9070c75ffd40a9319b0f11e7b647f72ab49c7a80 Mon Sep 17 00:00:00 2001 From: Michael Poole Date: Fri, 6 Mar 2020 20:07:23 -0500 Subject: [PATCH 05/20] readme.features: Better explain TLS version features --- doc/readme.features | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/doc/readme.features b/doc/readme.features index dd7f040e..cb39373b 100644 --- a/doc/readme.features +++ b/doc/readme.features @@ -975,6 +975,9 @@ This controls whether SSLv2 connections are supported. DO NOT USE SSLv2 unless you are aware of, and willing to accept, the serious security weaknesses of the SSLv2 protocol. +NOTE: This feature only affects ircu2 when using OpenSSL. gnutls and +libtls have no support for SSLv2. + TLS_SSLV3 * Type: boolean * Default: FALSE @@ -983,14 +986,20 @@ This controls whether SSLv3 connections are supported. DO NOT USE SSLv3 unless you are aware of, and willing to accept, the serious security weaknesses of the SSLv3 protocol. +NOTE: This feature only affects ircu2 when using OpenSSL. libtls has +no support for SSLv2. For gnutls, add "-VERS-SSL3.0" to TLS_CIPHERS. + TLS_V1P0 * Type: boolean - * Default: FALSE + * Default: FALSE (except for gnutls, which uses TRUE) This controls whether TLS 1.0 connections are supported. This is strongly discouraged in favor of TLS 1.1 and later because of security weaknesses in CBC modes of operation under TLS 1.1. +NOTE: This feature only affects ircu2 when using OpenSSL or libtls. For +gnutls, add "-VERS-TLS1.0" to TLS_CIPHERS. + TLS_V1P1 * Type: boolean * Default: TRUE @@ -999,12 +1008,18 @@ This controls whether TLS 1.1 connections are supported. In most cases, TLS 1.2 is strictly preferable to TLS 1.1, so a server might want to disable TLS 1.1. +NOTE: This feature only affects ircu2 when using OpenSSL or libtls. For +gnutls, add "-VERS-TLS1.1" to TLS_CIPHERS. + TLS_V1P2 * Type: boolean * Default: TRUE This controls whether TLS 1.2 connections are supported. +NOTE: This feature only affects ircu2 when using OpenSSL or libtls. For +gnutls, add "-VERS-TLS1.2" to TLS_CIPHERS. + TLS_V1P3 * Type: boolean * Default: TRUE @@ -1016,6 +1031,9 @@ this documentation (December 2019), not all deployed TLS libraries support TLS 1.3; this feature setting is effectively false for such libraries. +NOTE: This feature only affects ircu2 when using OpenSSL or libtls. For +gnutls, add "-VERS-TLS1.3" to TLS_CIPHERS. + ZANNELS * Type: boolean * Default: FALSE From 055d4664034dfcc5028cb3fc898b24f29571a401 Mon Sep 17 00:00:00 2001 From: Michael Poole Date: Thu, 16 Jul 2020 18:14:12 -0400 Subject: [PATCH 06/20] tls_gnutls: Fix typos in checking I/O results --- ircd/tls_gnutls.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ircd/tls_gnutls.c b/ircd/tls_gnutls.c index 546af01c..0089e187 100644 --- a/ircd/tls_gnutls.c +++ b/ircd/tls_gnutls.c @@ -303,7 +303,7 @@ IOResult ircd_tls_recv(struct Client *cptr, char *buf, if (res >= 0) return IO_SUCCESS; } - if (res == GNUTLS_E_INTERRUPTED || GNUTLS_E_AGAIN) + if (res == GNUTLS_E_INTERRUPTED || res == GNUTLS_E_AGAIN) handle_blocked(cptr, tls); return gnutls_error_is_fatal(res) ? IO_FAILURE : IO_BLOCKED; } @@ -346,7 +346,7 @@ IOResult ircd_tls_sendv(struct Client *cptr, struct MsgQ *buf, res = gnutls_record_send(tls, con->con_rexmit, con->con_rexmit_len); if (res <= 0) { - if (res == GNUTLS_E_INTERRUPTED || GNUTLS_E_AGAIN) + if (res == GNUTLS_E_INTERRUPTED || res == GNUTLS_E_AGAIN) handle_blocked(cptr, tls); return gnutls_error_is_fatal(res) ? IO_FAILURE : IO_BLOCKED; } From 852216118d287612c7682fd86a85055afe6fd5bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20T=C3=B8rrissen?= Date: Thu, 14 Aug 2025 10:54:38 +0200 Subject: [PATCH 07/20] Merge fix and update configure to not default to libtls when other implementation is specified in --with-tls --- acinclude.m4 | 38 +++++++++++++++----------------------- include/channel.h | 5 ++--- ircd/channel.c | 6 +----- 3 files changed, 18 insertions(+), 31 deletions(-) diff --git a/acinclude.m4 b/acinclude.m4 index 750e94c6..d47458e8 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -149,25 +149,6 @@ else AC_MSG_ERROR([Cannot find a type with size of 64 bits]) fi]) -dnl -dnl Macro: unet_LIBTLS -dnl -dnl Set unet_cv_with_libtls to "yes" if the system has an OpenBSD-style -dnl libtls, or to "no" otherwise. -dnl -AC_DEFUN([unet_LIBTLS], -[dnl OpenBSD libtls needs manual checks. -AC_MSG_CHECKING([for OpenBSD-style libtls]) -AC_CACHE_VAL(unet_cv_with_libtls, -[save_LIBS="$LIBS" -LIBS="$LIBS -ltls" -AC_LINK_IFELSE( - [AC_LANG_PROGRAM([#include ], [tls_init()])], - [unet_cv_with_libtls=yes], - [unet_cv_with_libtls=no]) -LIBS="$save_LIBS"]) -AC_MSG_RESULT([$unet_cv_with_libtls])]) - dnl dnl Macro: unet_TLS dnl @@ -177,7 +158,6 @@ AC_DEFUN([unet_TLS], [dnl Perform some preliminary checks for system TLS libraries. AX_CHECK_OPENSSL(, [:]) PKG_CHECK_MODULES([GNUTLS], [gnutls], , [:]) -unet_LIBTLS dnl --with-tls allows selection of the TLS library. AC_MSG_CHECKING([for a TLS library]) @@ -201,8 +181,18 @@ if test x"$unet_cv_with_tls" = xyes ; then fi fi dnl Try libtls next. -if test x"$unet_cv_with_libtls" = xyes ; then - unet_cv_with_tls=libtls +if test x"$unet_cv_with_tls" = xyes ; then + dnl First try pkg-config (Linux/ports with libtls.pc). Do not modify LIBS/CFLAGS yet. + PKG_CHECK_MODULES([LIBTLS], [libtls], [ + unet_cv_with_tls=libtls + ], [ + dnl Fallback for OpenBSD base (no .pc): header + symbol link test. + AC_CHECK_HEADER([tls.h], [ + AC_CHECK_LIB([tls], [tls_init], [ + unet_cv_with_tls=libtls + ]) + ]) + ]) fi case x"$unet_cv_with_tls" in @@ -219,7 +209,9 @@ xgnutls) TLS_C="tls_gnutls.c" ;; xlibtls) - LIBS="$LIBS -ltls" + CFLAGS="$CFLAGS $LIBTLS_CFLAGS" + LDFLAGS="$LDFLAGS $LIBTLS_LDFLAGS" + LIBS="$LIBS $LIBTLS_LIBS" TLS_C="tls_libtls.c" ;; xyes|xno) diff --git a/include/channel.h b/include/channel.h index 375a7d34..f2f9be03 100644 --- a/include/channel.h +++ b/include/channel.h @@ -114,10 +114,9 @@ struct Client; #define MODE_APASS 0x200000 #define MODE_WASDELJOINS 0x400000 /**< Not DELJOINS, but some joins * pending */ -#define MODE_NOPARTMSGS 0x800000 /**< +P No part messages */ +#define MODE_NOPARTMSGS 0x800000 /**< +P No part messages */ #define MODE_MODERATENOREG 0x1000000 /**< +M Moderate unauthed users */ -#define MODE_TLSONLY 0x1000000 /**< +Z TLS users only */ -#define MODE_MODERATENOREG 0x2000000 /**< +M Moderate unauthed users */ +#define MODE_TLSONLY 0x2000000 /**< +Z TLS users only */ /** mode flags which take another parameter (With PARAmeterS) */ diff --git a/ircd/channel.c b/ircd/channel.c index 7b2cc48f..4d112e70 100644 --- a/ircd/channel.c +++ b/ircd/channel.c @@ -1985,11 +1985,7 @@ modebuf_mode(struct ModeBuf *mbuf, unsigned int mode) mode &= (MODE_ADD | MODE_DEL | MODE_PRIVATE | MODE_SECRET | MODE_MODERATED | MODE_TOPICLIMIT | MODE_INVITEONLY | MODE_NOPRIVMSGS | MODE_REGONLY | -<<<<<<< HEAD - MODE_NOCOLOR | MODE_NOCTCP | MODE_NOPARTMSGS | MODE_MODERATENOREG | -======= - MODE_NOCOLOR | MODE_NOCTCP | MODE_MODERATENOREG | MODE_TLSONLY | ->>>>>>> 40846b2 (Support SSL and TLS (UNTESTED)) + MODE_NOCOLOR | MODE_NOCTCP | MODE_NOPARTMSGS | MODE_MODERATENOREG | MODE_TLSONLY | MODE_DELJOINS | MODE_WASDELJOINS | MODE_REGISTERED); if (!(mode & ~(MODE_ADD | MODE_DEL))) /* don't add empty modes... */ From d1f0d682306fe5bf096224bfb8864fa28b6de9b2 Mon Sep 17 00:00:00 2001 From: Michael Poole Date: Sun, 23 Feb 2025 20:32:33 -0500 Subject: [PATCH 08/20] tls: Improve fingerprint handling (not fully fixed yet) Add ircd_tls_listen() to configure a TLS listener. Require this to succeed on any TLS-enabled listening port. Delete ircd_tls_fingerprint_matches() in favor of cli_tls_fingerprint. --- include/ircd_tls.h | 31 ++--------- include/listener.h | 1 + ircd/client.c | 30 ---------- ircd/listener.c | 5 ++ ircd/m_oper.c | 3 +- ircd/m_server.c | 3 +- ircd/s_auth.c | 2 +- ircd/tls_gnutls.c | 51 +++++++++++++++-- ircd/tls_libtls.c | 134 ++++++++++++++++++++++++--------------------- ircd/tls_none.c | 5 +- ircd/tls_openssl.c | 53 ++++++++++-------- 11 files changed, 168 insertions(+), 150 deletions(-) diff --git a/include/ircd_tls.h b/include/ircd_tls.h index 591d0265..8a0343bd 100644 --- a/include/ircd_tls.h +++ b/include/ircd_tls.h @@ -40,25 +40,6 @@ extern char *ircd_tls_keyfile; /** ircd_tls_certfile holds this server's public key certificate. */ extern char *ircd_tls_certfile; -/** ircd_tls_fingerprint_matches() returns non-zero if \a cptr uses a - * TLS certificate that matches the fingerprint in \a fingerprint. - * - * If \a fingerprint is null or empty (zero-length), any client matches - * it (even clients not using TLS). If \a fingerprint has length from - * 1 to 63, no client matches it. Otherwise, the first 64 characters of - * \a fingerprint are compared (as hexadecimal digits) to the SHA-256 - * digest of the X.509 DER representation of the peer's TLS certificate. - * - * \warning \a cptr must be directly connected to this server. - * - * @param[in] cptr Client to check. - * @param[in] fingerprint Empty string, or 64-digit hex string. - * \returns Non-zero if the client has a matching TLS certificate - * fingerprint, zero if the fingerprint does not match. - */ -int ircd_tls_fingerprint_matches(struct Client *cptr, - const char *fingerprint); - /* The following variables are provided by the TLS interface. */ /** ircd_tls_version identifies the TLS library in current use. */ @@ -112,14 +93,14 @@ void *ircd_tls_connect(struct ConfItem *aconf, int fd); */ void ircd_tls_close(void *ctx, const char *message); -/** ircd_tls_fingerprint() fills \a fingerprint with the TLS fingerprint - * used by the TLS session \a ctx. If \a ctx is null or there is some - * internal failure, \a fingerprint should be filled with zero bytes. +/** ircd_tls_listen() configures any listener-specific TLS parameters. + * \a listener->tls_ciphers is populated on entry. \a listener->tls_ctx + * may be null or may have been previously set by the TLS implementation. * - * @param[in] ctx TLS session to query. - * @param[out] fingerprint 32-byte binary buffer. + * @param[in,out] listener Listener structure to configure. + * \returns Zero on success, non-zero to indicate failure. */ -void ircd_tls_fingerprint(void *ctx, char *fingerprint); +int ircd_tls_listen(struct Listener *listener); /** ircd_tls_negotiate() attempts to continue an initial TLS handshake * for \a cptr. If the handshake completes, this function calls diff --git a/include/listener.h b/include/listener.h index 2d2a8ac5..14db88a8 100644 --- a/include/listener.h +++ b/include/listener.h @@ -73,6 +73,7 @@ struct Listener { int index; /**< index into poll array */ time_t last_accept; /**< last time listener accepted */ char* tls_ciphers; /**< ciphers to use for TLS */ + void* tls_ctx; /**< TLS context for the listener */ struct irc_sockaddr addr; /**< virtual address and port */ struct irc_in_addr mask; /**< listener hostmask */ struct Socket socket_v4; /**< describe IPv4 socket to event system */ diff --git a/ircd/client.c b/ircd/client.c index 4fa0a515..6fbb2c4f 100644 --- a/ircd/client.c +++ b/ircd/client.c @@ -286,33 +286,3 @@ client_report_privs(struct Client *to, struct Client *client) return 0; } - -int -ircd_tls_fingerprint_matches(struct Client *cptr, - const char *fingerprint) -{ - static const char hexdigits[] = "0123456789abcdef"; - void *ctx; - int i; - char tls_fp[32]; - - if (EmptyString(fingerprint)) - return 1; - - ctx = s_tls(&cli_socket(cptr)); - if (!ctx) - return 0; - - ircd_tls_fingerprint(ctx, tls_fp); - for (i = 0; i < sizeof(tls_fp); i++) - { - unsigned char ch = tls_fp[i]; - if ((hexdigits[ch >> 4] != ToLower(fingerprint[2*i+0])) - || (hexdigits[ch & 15] != ToLower(fingerprint[2*i+1]))) - { - return 0; - } - } - - return 1; -} diff --git a/ircd/listener.c b/ircd/listener.c index 4987eafa..afccf47c 100644 --- a/ircd/listener.c +++ b/ircd/listener.c @@ -370,6 +370,11 @@ void add_listener(int port, const char* vhost_ip, const char* mask, listener->fd_v4 = -1; } + if (okay + && FlagHas(flags, LISTEN_TLS) + && ircd_tls_listen(listener)) + okay = 0; + if (!okay) free_listener(listener); else if (new_listener) { diff --git a/ircd/m_oper.c b/ircd/m_oper.c index 3735fa92..b49ccf15 100644 --- a/ircd/m_oper.c +++ b/ircd/m_oper.c @@ -157,7 +157,8 @@ int m_oper(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) } assert(0 != (aconf->status & CONF_OPERATOR)); - if (!ircd_tls_fingerprint_matches(sptr, aconf->tls_fingerprint)) + if (!EmptyString(aconf->tls_fingerprint) + && ircd_strcmp(cli_tls_fingerprint(sptr), aconf->tls_fingerprint)) { send_reply(sptr, ERR_TLSCLIFINGERPRINT); sendto_opmask_butone(0, SNO_OLDREALOP, "Failed OPER attempt by %s (%s@%s)", diff --git a/ircd/m_server.c b/ircd/m_server.c index 1e72907f..4468c480 100644 --- a/ircd/m_server.c +++ b/ircd/m_server.c @@ -608,7 +608,8 @@ int mr_server(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) "Access denied. No conf line for server %s", cli_name(cptr)); } - if (!ircd_tls_fingerprint_matches(cptr, aconf->tls_fingerprint)) { + if (!EmptyString(aconf->tls_fingerprint) + && ircd_strcmp(cli_tls_fingerprint(cptr), aconf->tls_fingerprint)) { ++ServerStats->is_wrong_server; sendto_opmask_butone(0, SNO_OLDSNO, "Access denied (fingerprint mismatch) %s", cli_name(cptr)); diff --git a/ircd/s_auth.c b/ircd/s_auth.c index 14866466..413727dc 100644 --- a/ircd/s_auth.c +++ b/ircd/s_auth.c @@ -575,7 +575,7 @@ static int check_auth_finished(struct AuthRequest *auth, int bitclr) /* Check TLS fingerprint. */ if ((res == 0) && aconf && !EmptyString(aconf->tls_fingerprint) - && !ircd_tls_fingerprint_matches(cptr, aconf->tls_fingerprint)) + && ircd_strcmp(cli_tls_fingerprint(cptr), aconf->tls_fingerprint)) { ++ServerStats->is_bad_fingerprint; send_reply(cptr, ERR_TLSCLIFINGERPRINT); diff --git a/ircd/tls_gnutls.c b/ircd/tls_gnutls.c index 0089e187..8ff3b208 100644 --- a/ircd/tls_gnutls.c +++ b/ircd/tls_gnutls.c @@ -227,9 +227,10 @@ void ircd_tls_close(void *ctx, const char *message) gnutls_deinit(ctx); } -void ircd_tls_fingerprint(void *ctx, char *fingerprint) +int ircd_tls_listen(struct Listener *listener) { - memset(fingerprint, 0, 32); + /* noop for gnutls */ + return 0; } static void handle_blocked(struct Client *cptr, gnutls_session_t tls) @@ -243,7 +244,11 @@ static void handle_blocked(struct Client *cptr, gnutls_session_t tls) int ircd_tls_negotiate(struct Client *cptr) { gnutls_session_t tls; + gnutls_x509_crt_t crt; + const gnutls_datum_t *datum; + size_t len; int res; + char buf[32]; tls = s_tls(&cli_socket(cptr)); @@ -253,8 +258,6 @@ int ircd_tls_negotiate(struct Client *cptr) res = gnutls_handshake(tls); switch (res) { - case GNUTLS_E_SUCCESS: - return 1; case GNUTLS_E_INTERRUPTED: case GNUTLS_E_AGAIN: handle_blocked(cptr, tls); @@ -262,6 +265,46 @@ int ircd_tls_negotiate(struct Client *cptr) case GNUTLS_E_WARNING_ALERT_RECEIVED: case GNUTLS_E_GOT_APPLICATION_DATA: return 0; + + case GNUTLS_E_SUCCESS: + ClearNegotiatingTLS(cptr); + + datum = gnutls_certificate_get_peers(tls, NULL); + if (!datum) + { + log_write(LS_SYSTEM, L_ERROR, 0, "gnutls_certificate_get_peers failed for %s", + cli_name(cptr)); + return 1; + } + + res = gnutls_x509_crt_init(&crt); + if (res) + { + log_write(LS_SYSTEM, L_ERROR, 0, "gnutls_x509_crt_init failed for %s: %d", + cli_name(cptr), res); + return 1; + } + + /* TODO: extract peer's X.509 certificate */ + res = gnutls_x509_crt_import(crt, datum, GNUTLS_X509_FMT_DER); + if (res) + { + log_write(LS_SYSTEM, L_ERROR, 0, "gnutls_x509_crt_import failed for %s: %d", + cli_name(cptr), res); + return 1; + } + + len = sizeof(buf); + res = gnutls_x509_crt_get_fingerprint(crt, GNUTLS_DIG_SHA256, buf, &len); + if (res) + { + log_write(LS_SYSTEM, L_ERROR, 0, "gnutls_x509_crt_get_fingerprint failed for %s: %d", + cli_name(cptr), res); + return 1; + } + /* TODO: convert buf to hex (into cli_tls_fingerprint(cptr)) */ + return 1; + default: Debug((DEBUG_DEBUG, " ... gnutls_handshake() failed -> %s (%d)", gnutls_strerror(res), res)); diff --git a/ircd/tls_libtls.c b/ircd/tls_libtls.c index 07a3beb4..945bb2d5 100644 --- a/ircd/tls_libtls.c +++ b/ircd/tls_libtls.c @@ -36,20 +36,13 @@ #define CONCAT(A, B) A # B const char *ircd_tls_version = CONCAT("libtls ", TLS_API); -static struct tls_config *tls_cfg; -static struct tls *tls_srv; int ircd_tls_init(void) { static int libtls_init; - struct tls_config *new_cfg = NULL; - struct tls *new_srv = NULL; - uint32_t protos; - const char *str; - int res; if (EmptyString(ircd_tls_keyfile) || EmptyString(ircd_tls_certfile)) - goto done; + return 0; if (!libtls_init) { @@ -61,29 +54,29 @@ int ircd_tls_init(void) } } + return 0; +} + +static struct tls_config *make_tls_config(const char *ciphers) +{ + struct tls_config *new_cfg; + uint32_t protos; + const char *str; + new_cfg = tls_config_new(); if (!new_cfg) { log_write(LS_SYSTEM, L_ERROR, 0, "tls_config_new() failed"); - return 2; - } - - new_srv = tls_server(); - if (!new_srv) - { - log_write(LS_SYSTEM, L_ERROR, 0, "unable to create new TLS server context"); - tls_config_free(new_cfg); - return 3; + return NULL; } - res = tls_config_add_keypair_file(new_cfg, ircd_tls_certfile, ircd_tls_keyfile); - if (res < 0) + if (tls_config_add_keypair_file(new_cfg, ircd_tls_certfile, ircd_tls_keyfile) < 0) { log_write(LS_SYSTEM, L_ERROR, 0, "unable to load certificate and key: %s", tls_config_error(new_cfg)); + fail: tls_config_free(new_cfg); - tls_free(new_srv); - return 4; + return NULL; } str = feature_str(FEAT_TLS_CACERTDIR); @@ -94,6 +87,7 @@ int ircd_tls_init(void) log_write(LS_SYSTEM, L_ERROR, 0, "unable to set CA path to %s: %s", str, tls_config_error(new_cfg)); } + goto fail; } str = feature_str(FEAT_TLS_CACERTFILE); @@ -104,6 +98,7 @@ int ircd_tls_init(void) log_write(LS_SYSTEM, L_ERROR, 0, "unable to set CA file to %s: %s", str, tls_config_error(new_cfg)); } + goto fail; } protos = 0; @@ -122,47 +117,33 @@ int ircd_tls_init(void) { log_write(LS_SYSTEM, L_ERROR, 0, "unable to select TLS versions: %s", tls_config_error(new_cfg)); + goto fail; } - str = feature_str(FEAT_TLS_CIPHERS); + str = ciphers; + if (EmptyString(str)) + str = feature_str(FEAT_TLS_CIPHERS); if (!EmptyString(str)) { if (tls_config_set_ciphers(new_cfg, str) < 0) { log_write(LS_SYSTEM, L_ERROR, 0, "unable to select TLS ciphers: %s", tls_config_error(new_cfg)); + goto fail; } } - tls_config_verify_client_optional(new_cfg); - - if (tls_configure(new_srv, new_cfg) < 0) - { - log_write(LS_SYSTEM, L_ERROR, 0, "unable to configure TLS server context: %s", - tls_error(new_srv)); - tls_config_free(new_cfg); - tls_free(new_srv); - return 5; - } - -done: - tls_config_free(tls_cfg); - tls_cfg = new_cfg; - tls_free(tls_srv); - tls_srv = new_srv; - return 0; + return new_cfg; } void *ircd_tls_accept(struct Listener *listener, int fd) { struct tls *tls; - /* TODO: adjust acceptable ciphers list */ - - if (tls_accept_socket(tls_srv, &tls, fd) < 0) + if (tls_accept_socket(listener->tls_ctx, &tls, fd) < 0) { log_write(LS_SYSTEM, L_ERROR, 0, "TLS accept failed: %s", - tls_error(tls_srv)); + tls_error(listener->tls_ctx)); return NULL; } @@ -171,8 +152,15 @@ void *ircd_tls_accept(struct Listener *listener, int fd) void *ircd_tls_connect(struct ConfItem *aconf, int fd) { + struct tls_config *cfg; struct tls *tls; + cfg = make_tls_config(aconf->tls_ciphers); + if (!cfg) + { + return NULL; + } + tls = tls_client(); if (!tls) { @@ -180,12 +168,11 @@ void *ircd_tls_connect(struct ConfItem *aconf, int fd) return NULL; } - /* TODO: adjust acceptable ciphers list */ - - if (tls_configure(tls, tls_cfg) < 0) + if (tls_configure(tls, cfg) < 0) { log_write(LS_SYSTEM, L_ERROR, 0, "tls_configure failed for client: %s", tls_error(tls)); + fail: tls_free(tls); return NULL; } @@ -194,8 +181,7 @@ void *ircd_tls_connect(struct ConfItem *aconf, int fd) { log_write(LS_SYSTEM, L_ERROR, 0, "tls_connect_socket failed for %s: %s", aconf->name, tls_error(tls)); - tls_free(tls); - return NULL; + goto fail; } return tls; @@ -208,19 +194,6 @@ void ircd_tls_close(void *ctx, const char *message) tls_free(ctx); } -void ircd_tls_fingerprint(void *ctx, char *fingerprint) -{ - const char *text; - - text = tls_peer_cert_hash(ctx); - if (text && !ircd_strncmp(text, "SHA256:", 7)) - { - /* TODO: convert from ASCII hex to binary */ - return; - } - memset(fingerprint, 0, 32); -} - static int tls_handle_error(struct Client *cptr, struct tls *tls, int err) { if (err == TLS_WANT_POLLIN) @@ -239,8 +212,37 @@ static int tls_handle_error(struct Client *cptr, struct tls *tls, int err) return -1; } +int ircd_tls_listen(struct Listener *listener) +{ + struct tls_config *cfg; + struct tls *srv; + + cfg = make_tls_config(listener->tls_ciphers); + if (!cfg) + return 1; + tls_config_verify_client_optional(cfg); + + if (listener->tls_ctx) + tls_reset(listener->tls_ctx); + else if (!(listener->tls_ctx = tls_server())) + { + log_write(LS_SYSTEM, L_ERROR, 0, "tls_server failed"); + return 2; + } + + if (tls_configure(listener->tls_ctx, cfg) < 0) + { + log_write(LS_SYSTEM, L_ERROR, 0, "unable to configure TLS server context: %s", + tls_error(listener->tls_ctx)); + return 3; + } + + return 0; +} + int ircd_tls_negotiate(struct Client *cptr) { + const char *hash; struct tls *tls; int res; @@ -249,8 +251,16 @@ int ircd_tls_negotiate(struct Client *cptr) return 1; res = tls_handshake(tls); - if (res == 1) + if (res == 0) + { + hash = tls_peer_cert_hash(tls); + if (hash && !ircd_strncmp(hash, "SHA256:", 7)) + { + ircd_strncpy(cli_tls_fingerprint(cptr), hash+7, 64); + } + ClearNegotiatingTLS(cptr); return 1; + } return tls_handle_error(cptr, tls, res); } diff --git a/ircd/tls_none.c b/ircd/tls_none.c index 6d0fed87..8b2ee2a3 100644 --- a/ircd/tls_none.c +++ b/ircd/tls_none.c @@ -47,13 +47,14 @@ void ircd_tls_close(void *ctx, const char *message) { } -void ircd_tls_fingerprint(void *ctx, char *fingerprint) +int ircd_tls_listen(struct Listener *listener) { - memset(fingerprint, 0, 32); + return 0; } int ircd_tls_negotiate(struct Client *cptr) { + ClearNegotiatingTLS(cptr); return 1; } diff --git a/ircd/tls_openssl.c b/ircd/tls_openssl.c index 820be43d..44d83eea 100644 --- a/ircd/tls_openssl.c +++ b/ircd/tls_openssl.c @@ -68,30 +68,6 @@ static void ssl_log_error(const char *msg) } } -void ircd_tls_fingerprint(void *ctx, char *fingerprint) -{ - SSL *tls; - X509 *cert; - int res; - - if (!ctx) - { - no_fingerprint: - memset(fingerprint, 0, 32); - return; - } - - tls = ctx; - cert = SSL_get_peer_certificate(tls); - if (!cert) - goto no_fingerprint; - - res = X509_digest(cert, fp_digest, (unsigned char *)fingerprint, NULL); - X509_free(cert); - if (res) - Debug((DEBUG_NOTICE, "X509_digest failed to make fingerprint")); -} - static void ssl_set_ciphers(SSL_CTX *ctx, SSL *tls, const char *text) { const char *sep; @@ -293,6 +269,12 @@ void *ircd_tls_connect(struct ConfItem *aconf, int fd) return tls; } +int ircd_tls_listen(struct Listener *listener) +{ + /* noop for OpenSSL */ + return 0; +} + static int ssl_handle_error(struct Client *cptr, SSL *tls, int res, int orig_errno) { int err = SSL_get_error(tls, res); @@ -323,7 +305,10 @@ static int ssl_handle_error(struct Client *cptr, SSL *tls, int res, int orig_err int ircd_tls_negotiate(struct Client *cptr) { SSL *tls; + X509 *cert; + unsigned int len; int res; + unsigned char buf[EVP_MAX_MD_SIZE]; tls = s_tls(&cli_socket(cptr)); if (!tls) @@ -332,6 +317,26 @@ int ircd_tls_negotiate(struct Client *cptr) res = SSL_accept(tls); if (res == 1) { + cert = SSL_get_peer_certificate(tls); + if (cert) + { + len = sizeof(buf); + res = X509_digest(cert, fp_digest, buf, &len); + X509_free(cert); + if (res) + { + log_write(LS_SYSTEM, L_ERROR, 0, "X509_digest failed for %s: %d", + cli_name(cptr), res); + } + if (len == 32) + { + /* TODO: convert fingerprint to hex (into cli_tls_fingerprint(cptr)) */ + } + else + { + memset(cli_tls_fingerprint(cptr), 0, 65); + } + } ClearNegotiatingTLS(cptr); } else From 7cb71c84741667c33ce122c632b58b39374196a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20T=C3=B8rrissen?= Date: Thu, 14 Aug 2025 12:29:09 +0200 Subject: [PATCH 09/20] Reworked openssl implementation --- doc/example.conf | 7 +- include/client.h | 11 +- include/handlers.h | 1 - include/ircd_features.h | 7 +- include/ircd_tls.h | 3 + include/numeric.h | 3 +- ircd/ircd_features.c | 7 +- ircd/ircd_lexer.c | 5 + ircd/m_clearmode.c | 1 - ircd/m_whois.c | 3 + ircd/s_bsd.c | 70 ++++++--- ircd/s_err.c | 2 + ircd/s_user.c | 31 +++- ircd/send.c | 4 + ircd/tls_none.c | 4 + ircd/tls_openssl.c | 307 +++++++++++++++++++++++++++++----------- 16 files changed, 333 insertions(+), 133 deletions(-) diff --git a/doc/example.conf b/doc/example.conf index b300e192..1baf2f90 100644 --- a/doc/example.conf +++ b/doc/example.conf @@ -1025,12 +1025,7 @@ features # TLS_CACERTFILE" = "ircd-ca.pem"; # "TLS_CACERTDIR" = "/etc/ssl/certs"; # "TLS_CIPHERS" = ""; -# "TLS_SSLV2" = "FALSE"; -# "TLS_SSLV3" = "FALSE"; -# "TLS_V1P0" = "FALSE"; -# "TLS_V1P1" = "TRUE"; -# "TLS_V1P2" = "TRUE"; -# "TLS_V1P3" = "TRUE"; +# "TLS_ALLOW_SELFSIGNED" = "TRUE"; }; # Well, you have now reached the end of this sample configuration diff --git a/include/client.h b/include/client.h index 4a33ba4b..e1ffd2fa 100644 --- a/include/client.h +++ b/include/client.h @@ -232,7 +232,6 @@ struct Connection char con_buffer[BUFSIZE]; /**< Incoming message buffer; or the error that caused this clients socket to close. */ - char con_tls_fingerprint[65]; /**< TLS SHA-256 fingerprint. */ struct Socket con_socket; /**< socket descriptor for client */ struct Timer con_proc; /**< process latent messages from @@ -242,13 +241,10 @@ struct Connection capset_t con_active; /**< Active capabilities (to us) */ struct AuthRequest* con_auth; /**< Auth request for client */ const struct wline* con_wline; /**< WebIRC authorization for client */ -<<<<<<< HEAD uint64_t con_sasl; /**< SASL session cookie */ struct Timer con_sasl_timer; /**< SASL timeout timer */ -======= char* con_rexmit; /**< TLS retransmission data */ size_t con_rexmit_len; /**, TLS retransmission length */ ->>>>>>> 1f0b6f4 (Support SSL and TLS (UNTESTED)) }; /** Magic constant to identify valid Connection structures. */ @@ -276,6 +272,7 @@ struct Client { char cli_name[HOSTLEN + 1]; /**< Unique name of the client, nick or host */ char cli_username[USERLEN + 1]; /**< Username determined by ident lookup */ char cli_info[REALLEN + 1]; /**< Free form additional client information */ + char cli_tls_fingerprint[65]; /**< TLS SHA-256 fingerprint. */ }; /** Magic constant to identify valid Client structures. */ @@ -337,6 +334,8 @@ struct Client { #define cli_info(cli) ((cli)->cli_info) /** Get client account string. */ #define cli_account(cli) (cli_user(cli) ? cli_user(cli)->account : "0") +/** Get the client's TLS fingerprint. */ +#define cli_tls_fingerprint(cli) ((cli)->cli_tls_fingerprint) /** Get number of incoming bytes queued for client. */ #define cli_count(cli) con_count(cli_connect(cli)) @@ -388,8 +387,6 @@ struct Client { #define cli_sockhost(cli) con_sockhost(cli_connect(cli)) /** Get the client's password. */ #define cli_passwd(cli) con_passwd(cli_connect(cli)) -/** Get the client's TLS fingerprint. */ -#define cli_tls_fingerprint(cli) con_tls_fingerprint(cli_connect(cli)) /** Get the unprocessed input buffer for a client's connection. */ #define cli_buffer(cli) con_buffer(cli_connect(cli)) /** Get the Socket structure for sending to a client. */ @@ -473,8 +470,6 @@ struct Client { #define con_sockhost(con) ((con)->con_sockhost) /** Get the password sent by the remote end of the connection. */ #define con_passwd(con) ((con)->con_passwd) -/** Get the fingerprint of the peer's TLS certificate. */ -#define con_tls_fingerprint(con) ((con)->con_tls_fingerprint) /** Get the buffer of unprocessed incoming data from the connection. */ #define con_buffer(con) ((con)->con_buffer) /** Get the Socket for the connection. */ diff --git a/include/handlers.h b/include/handlers.h index f4e1afe3..8bc59f50 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -128,7 +128,6 @@ extern int m_registered(struct Client*, struct Client*, int, char*[]); extern int m_sasl(struct Client*, struct Client*, int, char*[]); extern int ms_config(struct Client*, struct Client*, int, char*[]); extern int m_silence(struct Client*, struct Client*, int, char*[]); -extern int m_starttls(struct Client*, struct Client*, int, char*[]); extern int m_stats(struct Client*, struct Client*, int, char*[]); extern int m_time(struct Client*, struct Client*, int, char*[]); extern int m_topic(struct Client*, struct Client*, int, char*[]); diff --git a/include/ircd_features.h b/include/ircd_features.h index 7f248125..30966835 100644 --- a/include/ircd_features.h +++ b/include/ircd_features.h @@ -103,12 +103,7 @@ enum Feature { FEAT_TLS_CACERTFILE, FEAT_TLS_CACERTDIR, FEAT_TLS_CIPHERS, - FEAT_TLS_SSLV2, - FEAT_TLS_SSLV3, - FEAT_TLS_V1P0, - FEAT_TLS_V1P1, - FEAT_TLS_V1P2, - FEAT_TLS_V1P3, + FEAT_TLS_ALLOW_SELFSIGNED, /* features that affect all operators */ FEAT_CONFIG_OPERCMDS, diff --git a/include/ircd_tls.h b/include/ircd_tls.h index 8a0343bd..087a6e9d 100644 --- a/include/ircd_tls.h +++ b/include/ircd_tls.h @@ -30,6 +30,9 @@ struct Listener; struct MsgQ; struct Socket; +/** Timeout for TLS handshake in seconds */ +#define TLS_HANDSHAKE_TIMEOUT 5 + /* The following variables and functions are provided by ircu2's core * code, not by the TLS interface. */ diff --git a/include/numeric.h b/include/numeric.h index 7742806d..fedc299b 100644 --- a/include/numeric.h +++ b/include/numeric.h @@ -460,7 +460,7 @@ extern const struct Numeric* get_error_numeric(int err); ERR_WHOLIMEXCEED 523 dalnet */ #define ERR_QUARANTINED 524 /* Undernet extension -Vampire */ #define ERR_INVALIDKEY 525 /* Undernet extension */ -#define ERR_TLSCLIFINGERPRINT 632 /* Nefarious & Undernet extension */ +#define ERR_TLSCLIFINGERPRINT 532 /* Nefarious & Undernet extension */ #define ERR_NOTLOWEROPLEVEL 560 /* Undernet extension */ #define ERR_NOTMANAGER 561 /* Undernet extension */ @@ -470,6 +470,7 @@ extern const struct Numeric* get_error_numeric(int err); /* ERR_NOMANAGER_LONG 565 no longer used */ #define ERR_NOMANAGER 566 /* Undernet extension */ #define ERR_UPASS_SAME_APASS 567 /* Undernet extension */ +#define RPL_WHOISSECURE 671 /* Nefarious, Undernet */ /* RPL_LOGON 600 dalnet,unreal RPL_LOGOFF 601 dalnet,unreal diff --git a/ircd/ircd_features.c b/ircd/ircd_features.c index 3649b98c..19c0833a 100644 --- a/ircd/ircd_features.c +++ b/ircd/ircd_features.c @@ -368,12 +368,7 @@ static struct FeatureDesc { F_S(TLS_CACERTFILE, FEAT_NULL | FEAT_CASE | FEAT_OPER, 0, 0), F_S(TLS_CACERTDIR, FEAT_NULL | FEAT_CASE | FEAT_OPER, 0, 0), F_S(TLS_CIPHERS, FEAT_NULL | FEAT_CASE | FEAT_OPER, 0, 0), - F_B(TLS_SSLV2, 0, 0, 0), - F_B(TLS_SSLV3, 0, 0, 0), - F_B(TLS_V1P0, 0, 0, 0), - F_B(TLS_V1P1, 0, 1, 0), - F_B(TLS_V1P2, 0, 1, 0), - F_B(TLS_V1P3, 0, 1, 0), + F_B(TLS_ALLOW_SELFSIGNED, 0, 1, 0), /* features that affect all operators */ F_B(CONFIG_OPERCMDS, 0, 0, 0), diff --git a/ircd/ircd_lexer.c b/ircd/ircd_lexer.c index 8e573ad6..e747b6e0 100644 --- a/ircd/ircd_lexer.c +++ b/ircd/ircd_lexer.c @@ -69,7 +69,9 @@ static const struct lexer_token tokens[] = { { "b", BYTES }, { "badchan", TPRIV_BADCHAN }, { "bytes", BYTES }, + { "certfile", CERTFILE }, { "chan_limit", TPRIV_CHAN_LIMIT }, + { "ciphers", CIPHERS }, { "class", CLASS }, { "client", CLIENT }, { "connect", CONNECT }, @@ -87,6 +89,7 @@ static const struct lexer_token tokens[] = { { "fast", FAST }, { "features", FEATURES }, { "file", TFILE }, + { "fingerprint", FINGERPRINT }, { "force_local_opmode", TPRIV_FORCE_LOCAL_OPMODE }, { "force_opmode", TPRIV_FORCE_OPMODE }, { "gb", GBYTES }, @@ -107,6 +110,7 @@ static const struct lexer_token tokens[] = { { "jupe", JUPE }, { "kb", KBYTES }, { "kbytes", KBYTES }, + { "keyfile", KEYFILE }, { "kill", KILL }, { "kilobytes", KBYTES }, { "leaf", LEAF }, @@ -162,6 +166,7 @@ static const struct lexer_token tokens[] = { { "tb", TBYTES }, { "tbytes", TBYTES }, { "terabytes", TBYTES }, + { "tls", TLS }, { "unlimit_query", TPRIV_UNLIMIT_QUERY }, { "usermode", USERMODE }, { "username", USERNAME }, diff --git a/ircd/m_clearmode.c b/ircd/m_clearmode.c index acaf0821..684cb073 100644 --- a/ircd/m_clearmode.c +++ b/ircd/m_clearmode.c @@ -1,4 +1,3 @@ - /* * IRC - Internet Relay Chat, ircd/m_clearmode.c * Copyright (C) 1990 Jarkko Oikarinen and diff --git a/ircd/m_whois.c b/ircd/m_whois.c index 09664614..b56f835f 100644 --- a/ircd/m_whois.c +++ b/ircd/m_whois.c @@ -208,6 +208,9 @@ static void do_whois(struct Client* sptr, struct Client *acptr, int parc) if (user->away) send_reply(sptr, RPL_AWAY, name, user->away); + if (IsTLS(acptr)) + send_reply(sptr, RPL_WHOISSECURE, name); + if (SeeOper(sptr,acptr)) send_reply(sptr, RPL_WHOISOPERATOR, name); diff --git a/ircd/s_bsd.c b/ircd/s_bsd.c index 3af80b4e..afde5974 100644 --- a/ircd/s_bsd.c +++ b/ircd/s_bsd.c @@ -287,7 +287,7 @@ unsigned int deliver_it(struct Client *cptr, struct MsgQ *buf) assert(0 != cptr); - io_result = IsTLS(cptr) + io_result = IsTLS(cptr) && s_tls(&cli_socket(cptr)) ? ircd_tls_sendv(cptr, buf, &bytes_count, &bytes_written) : os_sendv_nonb(cli_fd(cptr), buf, &bytes_count, &bytes_written); switch (io_result) { @@ -398,6 +398,12 @@ static int completed_connection(struct Client* cptr) MAJOR_PROTOCOL, NumServCap(&me), feature_bool(FEAT_HUB) ? "h" : "", cli_info(&me)); + if (IsTLS(cptr) && !IsNegotiatingTLS(cptr)) { + Debug((DEBUG_DEBUG, "TLS connection completed for %s", cli_name(cptr))); + socket_events(&cli_socket(cptr), SOCK_EVENT_READABLE | SOCK_EVENT_WRITABLE); + send_queued(cptr); + } + return (IsDead(cptr)) ? 0 : 1; } @@ -445,6 +451,10 @@ void close_connection(struct Client *cptr) if (-1 < cli_fd(cptr)) { flush_connections(cptr); LocalClientArray[cli_fd(cptr)] = 0; + if (IsTLS(cptr) && s_tls(&cli_socket(cptr))) { + ircd_tls_close(s_tls(&cli_socket(cptr)), NULL); + s_tls(&cli_socket(cptr)) = NULL; + } close(cli_fd(cptr)); socket_del(&(cli_socket(cptr))); /* queue a socket delete */ cli_fd(cptr) = -1; @@ -536,8 +546,6 @@ void add_connection(struct Listener* listener, int fd) { */ os_disable_options(fd); - tls = listener_tls(listener) ? ircd_tls_accept(listener, fd) : NULL; - if (listener_server(listener)) { new_client = make_client(0, STAT_UNKNOWN_SERVER); @@ -556,10 +564,7 @@ void add_connection(struct Listener* listener, int fd) { if (!IPcheck_local_connect(&addr.addr, &next_target)) { ++ServerStats->is_throttled; - if (tls) - ircd_tls_close(tls, throttle_message); - else - write(fd, throttle_message, strlen(throttle_message)); + write(fd, throttle_message, strlen(throttle_message)); close(fd); return; } @@ -582,10 +587,7 @@ void add_connection(struct Listener* listener, int fd) { if (!socket_add(&(cli_socket(new_client)), client_sock_callback, (void*) cli_connect(new_client), SS_CONNECTED, 0, fd)) { ++ServerStats->is_bad_socket; - if (tls) - ircd_tls_close(tls, register_message); - else - write(fd, register_message, strlen(register_message)); + write(fd, register_message, strlen(register_message)); close(fd); cli_fd(new_client) = -1; return; @@ -594,17 +596,19 @@ void add_connection(struct Listener* listener, int fd) { cli_listener(new_client) = listener; ++listener->ref_count; + tls = listener_tls(listener) ? ircd_tls_accept(listener, fd) : NULL; s_tls(&cli_socket(new_client)) = tls; if (tls) { SetTLS(new_client); SetNegotiatingTLS(new_client); - ircd_tls_negotiate(new_client); + socket_events(&cli_socket(new_client), SOCK_EVENT_WRITABLE); } Count_newunknown(UserStats); /* if we've made it this far we can put the client on the auth query pile */ - start_auth(new_client); + if (!IsTLS(new_client)) + start_auth(new_client); } /** Determines whether to tell the events engine we're interested in @@ -639,8 +643,8 @@ static int read_packet(struct Client *cptr, int socket_ready) if (socket_ready && !(IsUser(cptr) && - DBufLength(&(cli_recvQ(cptr))) > GetMaxFlood(cptr))) { - IOResult io_result = IsTLS(cptr) + DBufLength(&(cli_recvQ(cptr))) > GetMaxFlood(cptr))) { + IOResult io_result = IsTLS(cptr) && s_tls(&cli_socket(cptr)) ? ircd_tls_recv(cptr, readbuf, sizeof(readbuf), &length) : os_recv_nonb(cli_fd(cptr), readbuf, sizeof(readbuf), &length); switch (io_result) { @@ -956,8 +960,23 @@ static void client_sock_callback(struct Event* ev) case ET_WRITE: /* socket is writable */ if (IsNegotiatingTLS(cptr)) { - if (ircd_tls_negotiate(cptr) <= 0) + int res = ircd_tls_negotiate(cptr); + if (res < 0) { + SetFlag(cptr, FLAG_DEADSOCKET); + ClrFlag(cptr, FLAG_NEGOTIATING_TLS); + /* Clean up TLS context if it still exists */ + if (s_tls(&cli_socket(cptr))) { + ircd_tls_close(s_tls(&cli_socket(cptr)), "TLS negotiation failed"); + s_tls(&cli_socket(cptr)) = NULL; + } + exit_client(cptr, cptr, &me, "TLS negotiation failed"); + return; + } + if (res == 0) { + /* Still negotiating */ break; + } + /* TLS negotiation succeeded */ if (IsConnecting(cptr)) completed_connection(cptr); } @@ -972,13 +991,28 @@ static void client_sock_callback(struct Event* ev) if (!IsDead(cptr)) { Debug((DEBUG_DEBUG, "Reading data from %C", cptr)); if (IsNegotiatingTLS(cptr)) { - if (ircd_tls_negotiate(cptr) <= 0) + int res = ircd_tls_negotiate(cptr); + if (res < 0) { + SetFlag(cptr, FLAG_DEADSOCKET); + ClrFlag(cptr, FLAG_NEGOTIATING_TLS); + /* Clean up TLS context if it still exists */ + if (s_tls(&cli_socket(cptr))) { + ircd_tls_close(s_tls(&cli_socket(cptr)), "TLS negotiation failed"); + s_tls(&cli_socket(cptr)) = NULL; + } + exit_client(cptr, cptr, &me, "TLS negotiation failed"); + return; + } + if (res == 0) { + /* Still negotiating */ break; + } + /* TLS negotiation succeeded */ if (IsConnecting(cptr)) completed_connection(cptr); } if (read_packet(cptr, 1) == 0) /* error while reading packet */ - fallback = "EOF from client"; + fallback = "EOF from client"; } break; diff --git a/ircd/s_err.c b/ircd/s_err.c index 69619f5b..dd13e9c6 100644 --- a/ircd/s_err.c +++ b/ircd/s_err.c @@ -1375,6 +1375,8 @@ static Numeric replyTable[] = { { 0 }, /* 671 */ { 0 }, +/* 671 */ + { RPL_WHOISSECURE, "%s :is using a secure connection", "671" }, /* 672 */ { 0 }, /* 673 */ diff --git a/ircd/s_user.c b/ircd/s_user.c index 0c884644..822bb25b 100644 --- a/ircd/s_user.c +++ b/ircd/s_user.c @@ -967,6 +967,7 @@ int set_user_mode(struct Client *cptr, struct Client *sptr, int parc, int prop = 0; int do_host_hiding = 0; char* account = NULL; + char* tls_fingerprint = NULL; what = MODE_ADD; @@ -1087,8 +1088,10 @@ int set_user_mode(struct Client *cptr, struct Client *sptr, int parc, /* There is no -r */ break; case 'z': - if (what == MODE_ADD) + if (what == MODE_ADD) { SetTLS(sptr); + tls_fingerprint = *(++p); + } /* There is no -z */ break; default: @@ -1183,6 +1186,12 @@ int set_user_mode(struct Client *cptr, struct Client *sptr, int parc, if (!FlagHas(&setflags, FLAG_HIDDENHOST) && do_host_hiding && allow_modes != ALLOWMODES_DEFAULT) hide_hostmask(sptr, FLAG_HIDDENHOST); + if (tls_fingerprint && tls_fingerprint[0] != '_') { + ircd_strncpy(cli_tls_fingerprint(sptr), tls_fingerprint, 64); + Debug((DEBUG_DEBUG, "Received TLS fingerprint in user mode; " + "fingerprint \"%s\"", cli_tls_fingerprint(sptr))); + } + if (IsRegistered(sptr)) { if (!FlagHas(&setflags, FLAG_OPER) && IsOper(sptr)) { /* user now oper */ @@ -1246,6 +1255,8 @@ char *umode_str(struct Client *cptr) while ((*m++ = *t++)) ; /* Empty loop */ + m--; /* back up over previous nul-termination */ + if (cli_user(cptr)->acc_id) { char nbuf[30]; Debug((DEBUG_DEBUG, "Sending account id in user mode for " @@ -1263,9 +1274,25 @@ char *umode_str(struct Client *cptr) ircd_snprintf(0, t = nbuf, sizeof(nbuf), ":%qu", cli_user(cptr)->acc_id); } - m--; /* back up over previous nul-termination */ while ((*m++ = *t++)) ; /* Empty loop */ + m--; /* back up over previous nul-termination */ + } + } + + /** If the client is on a secure connection (umode +z) we append the fingerprint. + * If the fingerprint is empty (client has not provided a certificate), + * we return _ in the place of the fingerprint. + */ + if (IsTLS(cptr)) + { + char* t = cli_tls_fingerprint(cptr); + + *m++ = ' '; + if (t && *t) { + while ((*m++ = *t++)); + } else { + *m++ = '_'; } } diff --git a/ircd/send.c b/ircd/send.c index 9dbd2921..f23e18b0 100644 --- a/ircd/send.c +++ b/ircd/send.c @@ -182,6 +182,10 @@ void send_queued(struct Client *to) if (IsBlocked(to) || !can_send(to)) return; /* Don't bother */ + /* If we're still negotiating TLS, don't try to send data yet */ + if (IsTLS(to) && IsNegotiatingTLS(to)) + return; + while (MsgQLength(&(cli_sendQ(to))) > 0) { unsigned int len; diff --git a/ircd/tls_none.c b/ircd/tls_none.c index 8b2ee2a3..977d79ab 100644 --- a/ircd/tls_none.c +++ b/ircd/tls_none.c @@ -23,6 +23,7 @@ #include "config.h" #include "ircd_tls.h" #include "client.h" +#include "s_auth.h" #include #include @@ -45,6 +46,7 @@ void *ircd_tls_connect(struct ConfItem *aconf, int fd) void ircd_tls_close(void *ctx, const char *message) { + return; } int ircd_tls_listen(struct Listener *listener) @@ -55,6 +57,8 @@ int ircd_tls_listen(struct Listener *listener) int ircd_tls_negotiate(struct Client *cptr) { ClearNegotiatingTLS(cptr); + if (!IsConnecting(cptr)) + start_auth(cptr); return 1; } diff --git a/ircd/tls_openssl.c b/ircd/tls_openssl.c index 44d83eea..0e89bdeb 100644 --- a/ircd/tls_openssl.c +++ b/ircd/tls_openssl.c @@ -27,22 +27,24 @@ #include "ircd_log.h" #include "ircd_string.h" #include "ircd_tls.h" +#include "ircd.h" #include "listener.h" #include "s_conf.h" #include "s_debug.h" +#include "s_auth.h" +#include "send.h" +#include "s_bsd.h" #include #include #include #include /* IOV_MAX */ - -#if OPENSSL_VERSION_NUMBER < 0x10100000L -# error ircu2 requires OpenSSL >= 1.1.0 -#endif +#include /* write() on failure of ssl_accept() */ const char *ircd_tls_version = OPENSSL_VERSION_TEXT; -static SSL_CTX *base_ctx; +static SSL_CTX *server_ctx; /* For incoming connections */ +static SSL_CTX *client_ctx; /* For outgoing connections */ static const EVP_MD *fp_digest; static void ssl_log_error(const char *msg) @@ -116,10 +118,23 @@ static void ssl_set_ciphers(SSL_CTX *ctx, SSL *tls, const char *text) } } +static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) +{ + if (!preverify_ok && feature_bool(FEAT_TLS_ALLOW_SELFSIGNED)) + { + int err = X509_STORE_CTX_get_error(ctx); + if (err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || + err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) + return 1; + } + return preverify_ok; +} + int ircd_tls_init(void) { static int openssl_init; - SSL_CTX *new_ctx = NULL; + SSL_CTX *new_server_ctx = NULL; + SSL_CTX *new_client_ctx = NULL; const char *str, *s_2; int res; @@ -142,80 +157,129 @@ int ircd_tls_init(void) fp_digest = EVP_sha256(); } - new_ctx = SSL_CTX_new(SSLv23_method()); - if (!new_ctx) + /* Create server context */ + new_server_ctx = SSL_CTX_new(TLS_server_method()); + if (!new_server_ctx) { - ssl_log_error("SSL_CTX_new failed"); + ssl_log_error("SSL_CTX_new failed for server"); return 2; } - if (!feature_bool(FEAT_TLS_SSLV2)) - SSL_CTX_set_options(new_ctx, SSL_OP_NO_SSLv2); - if (!feature_bool(FEAT_TLS_SSLV3)) - SSL_CTX_set_options(new_ctx, SSL_OP_NO_SSLv3); - if (!feature_bool(FEAT_TLS_V1P0)) - SSL_CTX_set_options(new_ctx, SSL_OP_NO_TLSv1); - if (!feature_bool(FEAT_TLS_V1P1)) - SSL_CTX_set_options(new_ctx, SSL_OP_NO_TLSv1_1); - if (!feature_bool(FEAT_TLS_V1P2)) - SSL_CTX_set_options(new_ctx, SSL_OP_NO_TLSv1_2); - - /* OpenSSL only defines this macro if it supports TLS 1.3. */ -#if defined(SSL_OP_NO_TLSv1_3) - if (!feature_bool(FEAT_TLS_V1P3)) - SSL_CTX_set_options(new_ctx, SSL_OP_NO_TLSv1_3); -#endif + /* Create client context */ + new_client_ctx = SSL_CTX_new(TLS_client_method()); + if (!new_client_ctx) + { + ssl_log_error("SSL_CTX_new failed for client"); + SSL_CTX_free(new_server_ctx); + return 2; + } - res = SSL_CTX_use_certificate_chain_file(new_ctx, ircd_tls_certfile); - if (res != 1) + /* Configure certificates and keys for both contexts */ + if (!(SSL_CTX_use_certificate_chain_file(new_server_ctx, ircd_tls_certfile) == 1 && + SSL_CTX_use_certificate_chain_file(new_client_ctx, ircd_tls_certfile) == 1)) { ssl_log_error("unable to load certificate file"); - SSL_CTX_free(new_ctx); - return 3; + goto fail; } - res = SSL_CTX_use_PrivateKey_file(new_ctx, ircd_tls_keyfile, SSL_FILETYPE_PEM); - if (res != 1) + if (!(SSL_CTX_use_PrivateKey_file(new_server_ctx, ircd_tls_keyfile, SSL_FILETYPE_PEM) == 1 && + SSL_CTX_use_PrivateKey_file(new_client_ctx, ircd_tls_keyfile, SSL_FILETYPE_PEM) == 1)) { ssl_log_error("unable to load private key"); - SSL_CTX_free(new_ctx); - return 4; + goto fail; } - res = SSL_CTX_check_private_key(new_ctx); - if (res != 1) + if (!(SSL_CTX_check_private_key(new_server_ctx) == 1 && + SSL_CTX_check_private_key(new_client_ctx) == 1)) { - ssl_log_error("private key did not check out"); - SSL_CTX_free(new_ctx); - return 5; + ssl_log_error("certificate and private key do not match"); + goto fail; } - ssl_set_ciphers(new_ctx, NULL, feature_str(FEAT_TLS_CIPHERS)); - + /* Configure CA certificates */ str = feature_str(FEAT_TLS_CACERTFILE); s_2 = feature_str(FEAT_TLS_CACERTDIR); if (!EmptyString(str) || !EmptyString(s_2)) { - res = SSL_CTX_load_verify_locations(new_ctx, str, s_2); - if (res != 1) + if (!EmptyString(s_2)) + { + if (!(SSL_CTX_load_verify_locations(new_server_ctx, NULL, s_2) == 1 && + SSL_CTX_load_verify_locations(new_client_ctx, NULL, s_2) == 1)) + { + ssl_log_error("unable to load CA certificates from directory"); + goto fail; + } + } + + if (!EmptyString(str)) + { + if (!(SSL_CTX_load_verify_locations(new_server_ctx, str, NULL) == 1 && + SSL_CTX_load_verify_locations(new_client_ctx, str, NULL) == 1)) + { + ssl_log_error("unable to load CA certificates from file"); + goto fail; + } + } + } + else + { + if (!(SSL_CTX_set_default_verify_paths(new_server_ctx) == 1 && + SSL_CTX_set_default_verify_paths(new_client_ctx) == 1)) { - ssl_log_error("using TLS_CACERTFILE/TLS_CACERTDIR failed"); - /* but keep going */ + ssl_log_error("unable to load default CA certificates"); + goto fail; } } + /* Set protocol versions. */ + SSL_CTX_set_min_proto_version(new_server_ctx, TLS1_2_VERSION); + SSL_CTX_set_min_proto_version(new_client_ctx, TLS1_2_VERSION); + + /* Configure verification */ + SSL_CTX_set_verify(new_server_ctx, SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE, verify_callback); + SSL_CTX_set_verify(new_client_ctx, SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE, verify_callback); + + SSL_CTX_set_mode(new_server_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + SSL_CTX_set_mode(new_client_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + + /* Configure ciphers */ + str = feature_str(FEAT_TLS_CIPHERS); + ssl_set_ciphers(new_server_ctx, NULL, str); + ssl_set_ciphers(new_client_ctx, NULL, str); + done: - if (base_ctx) - SSL_CTX_free(base_ctx); - base_ctx = new_ctx; + if (server_ctx) + SSL_CTX_free(server_ctx); + if (client_ctx) + SSL_CTX_free(client_ctx); + server_ctx = new_server_ctx; + client_ctx = new_client_ctx; return 0; + +fail: + if (new_server_ctx) + SSL_CTX_free(new_server_ctx); + if (new_client_ctx) + SSL_CTX_free(new_client_ctx); + return 6; } void ircd_tls_close(void *ctx, const char *message) { - /* TODO: handle blocked I/O for shutdown */ - SSL_shutdown(ctx); - SSL_free(ctx); + SSL *ssl = ctx; + assert(ssl != NULL); + + if (!ssl) + return; + + /* Only attempt graceful shutdown if the SSL handshake completed */ + if (SSL_is_init_finished(ssl)) { + SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN); + if (SSL_shutdown(ssl) == 0) + SSL_shutdown(ssl); + } + + SSL_free(ssl); } static void ssl_set_fd(SSL *tls, int fd) @@ -231,14 +295,14 @@ void *ircd_tls_accept(struct Listener *listener, int fd) { SSL *tls; - tls = SSL_new(base_ctx); + tls = SSL_new(server_ctx); if (!tls) { ssl_log_error("unable to create SSL session"); return NULL; } - if (listener->tls_ciphers) + if (listener && listener->tls_ciphers) ssl_set_ciphers(NULL, tls, listener->tls_ciphers); ssl_set_fd(tls, fd); @@ -252,7 +316,7 @@ void *ircd_tls_connect(struct ConfItem *aconf, int fd) { SSL *tls; - tls = SSL_new(base_ctx); + tls = SSL_new(client_ctx); if (!tls) { ssl_log_error("unable to create SSL session"); @@ -275,31 +339,59 @@ int ircd_tls_listen(struct Listener *listener) return 0; } -static int ssl_handle_error(struct Client *cptr, SSL *tls, int res, int orig_errno) +static void clear_tls_rexmit(struct Connection *con) +{ + if (con && con->con_rexmit) { + con->con_rexmit = NULL; + con->con_rexmit_len = 0; + } +} + +static IOResult ssl_handle_error(struct Client *cptr, SSL *tls, int res, int orig_errno) { int err = SSL_get_error(tls, res); + + Debug((DEBUG_DEBUG, "ssl_handle_error: SSL_get_error=%d, res=%d, orig_errno=%d for %s", + err, res, orig_errno, cli_name(cptr))); + switch (err) { case SSL_ERROR_WANT_READ: socket_events(&cli_socket(cptr), SOCK_EVENT_READABLE); - return 0; + return IO_BLOCKED; + case SSL_ERROR_WANT_WRITE: socket_events(&cli_socket(cptr), SOCK_EVENT_WRITABLE); - return 0; + return IO_BLOCKED; + case SSL_ERROR_SYSCALL: if (orig_errno == EINTR || orig_errno == EAGAIN || orig_errno == EWOULDBLOCK) - return 0; + return IO_BLOCKED; + break; + case SSL_ERROR_ZERO_RETURN: + Debug((DEBUG_DEBUG, "SSL_ERROR_ZERO_RETURN: peer closed connection for %s", cli_name(cptr))); + if (SSL_shutdown(tls) == 0) + SSL_shutdown(tls); break; + default: - /* Fatal error (or EOF). */ - SSL_set_shutdown(tls, SSL_RECEIVED_SHUTDOWN); - SSL_set_quiet_shutdown(tls, 1); - SSL_shutdown(tls); + /* Fatal SSL error */ + Debug((DEBUG_ERROR, "SSL fatal error %d for %s", err, cli_name(cptr))); + unsigned long e; + while ((e = ERR_get_error()) != 0) { + Debug((DEBUG_ERROR, "[SSL] %s", ERR_error_string(e, NULL))); + } break; } - SSL_free(tls); - s_tls(&cli_socket(cptr)) = NULL; - return -1; + + /* Fatal error - clean up SSL context */ + if (tls && s_tls(&cli_socket(cptr)) == tls) { + s_tls(&cli_socket(cptr)) = NULL; + /* Do not call SSL_shutdown() after fatal errors */ + SSL_free(tls); + } + + return IO_FAILURE; } int ircd_tls_negotiate(struct Client *cptr) @@ -309,17 +401,32 @@ int ircd_tls_negotiate(struct Client *cptr) unsigned int len; int res; unsigned char buf[EVP_MAX_MD_SIZE]; + const char* const error_ssl = "ERROR :SSL connection error\r\n"; tls = s_tls(&cli_socket(cptr)); if (!tls) return 1; - res = SSL_accept(tls); + /* Check for handshake timeout */ + if (CurrentTime - cli_firsttime(cptr) > TLS_HANDSHAKE_TIMEOUT) { + Debug((DEBUG_DEBUG, "SSL handshake timeout for %s", cli_name(cptr))); + return -1; + } + + /* For client connections, use SSL_connect */ + if (SSL_get_SSL_CTX(tls) == client_ctx) + res = SSL_connect(tls); + /* For server connections, use SSL_accept */ + else + res = SSL_accept(tls); + if (res == 1) { + Debug((DEBUG_DEBUG, "SSL handshake success for %s", cli_name(cptr))); cert = SSL_get_peer_certificate(tls); if (cert) { + Debug((DEBUG_DEBUG, "SSL_get_peer_certificate success for %s", cli_name(cptr))); len = sizeof(buf); res = X509_digest(cert, fp_digest, buf, &len); X509_free(cert); @@ -328,22 +435,39 @@ int ircd_tls_negotiate(struct Client *cptr) log_write(LS_SYSTEM, L_ERROR, 0, "X509_digest failed for %s: %d", cli_name(cptr), res); } - if (len == 32) - { - /* TODO: convert fingerprint to hex (into cli_tls_fingerprint(cptr)) */ + if (len == 32) { + /* Convert fingerprint to lowercase hex */ + char *p = cli_tls_fingerprint(cptr); + for (unsigned int i = 0; i < len; i++) { + sprintf(p + (i * 2), "%02x", buf[i]); + } + p[len * 2] = '\0'; + Debug((DEBUG_DEBUG, "Fingerprint for %s: %s", cli_name(cptr), cli_tls_fingerprint(cptr))); } - else - { + else { memset(cli_tls_fingerprint(cptr), 0, 65); + Debug((DEBUG_DEBUG, "Invalid length")); } } ClearNegotiatingTLS(cptr); + + /* For incoming connections, start auth */ + if (!IsConnecting(cptr)) { + start_auth(cptr); + } } else { int orig_errno = errno; /* Handshake in progress. */ - res = (ssl_handle_error(cptr, tls, res, orig_errno) < 0) ? -1 : 0; + IOResult ssl_result = ssl_handle_error(cptr, tls, res, orig_errno); + if (ssl_result == IO_FAILURE) { + Debug((DEBUG_DEBUG, "SSL handshake failed for %s", cli_name(cptr))); + write(cli_fd(cptr), error_ssl, strlen(error_ssl)); + return -1; + } + /* ssl_result == IO_BLOCKED - handshake still in progress */ + return 0; } return res; @@ -369,7 +493,7 @@ IOResult ircd_tls_recv(struct Client *cptr, char *buf, orig_errno = errno; *count_out = 0; - return (ssl_handle_error(cptr, tls, res, orig_errno) < 0) ? IO_FAILURE : IO_BLOCKED; + return ssl_handle_error(cptr, tls, res, orig_errno); } IOResult ircd_tls_sendv(struct Client *cptr, struct MsgQ *buf, @@ -389,37 +513,52 @@ IOResult ircd_tls_sendv(struct Client *cptr, struct MsgQ *buf, *count_out = 0; if (con->con_rexmit) { + ERR_clear_error(); res = SSL_write(tls, con->con_rexmit, con->con_rexmit_len); - if (res <= 0) - { + if (res <= 0) { orig_errno = errno; - return (ssl_handle_error(cptr, tls, res, orig_errno) < 0) - ? IO_FAILURE : IO_BLOCKED; + return ssl_handle_error(cptr, tls, res, orig_errno); + } + + // Only excise the message if the full message was sent + if (res == (int)con->con_rexmit_len) { + msgq_excise(buf, con->con_rexmit, con->con_rexmit_len); + con->con_rexmit_len = 0; + con->con_rexmit = NULL; + result = IO_SUCCESS; + } else { + // Partial send, update pointer and length for next retry + con->con_rexmit = (char *)con->con_rexmit + res; + con->con_rexmit_len -= res; + return IO_BLOCKED; } - msgq_excise(buf, con->con_rexmit, con->con_rexmit_len); - con->con_rexmit_len = 0; - con->con_rexmit = NULL; - result = IO_SUCCESS; } + // Process remaining messages in the queue count = msgq_mapiov(buf, iov, sizeof(iov) / sizeof(iov[0]), count_in); for (ii = 0; ii < count; ++ii) { + ERR_clear_error(); res = SSL_write(tls, iov[ii].iov_base, iov[ii].iov_len); if (res > 0) { *count_out += res; result = IO_SUCCESS; + if (res < (int)iov[ii].iov_len) { + // Partial send, store for retransmission + cli_connect(cptr)->con_rexmit = (char *)iov[ii].iov_base + res; + cli_connect(cptr)->con_rexmit_len = iov[ii].iov_len - res; + return IO_BLOCKED; + } + // else, full message sent, continue to next continue; } + /* We only reach this if the SSL_write failed. */ orig_errno = errno; - if (ssl_handle_error(cptr, tls, res, orig_errno) < 0) - return IO_FAILURE; - cli_connect(cptr)->con_rexmit = iov[ii].iov_base; cli_connect(cptr)->con_rexmit_len = iov[ii].iov_len; - break; + return ssl_handle_error(cptr, tls, res, orig_errno); } return result; From 046b2ed63aab9a25ab559b9af524cfd4fcd3ad99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20T=C3=B8rrissen?= Date: Thu, 14 Aug 2025 23:07:12 +0200 Subject: [PATCH 10/20] We don't register socket evnet upon reciving SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE --- ircd/tls_openssl.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/ircd/tls_openssl.c b/ircd/tls_openssl.c index 0e89bdeb..30acd07a 100644 --- a/ircd/tls_openssl.c +++ b/ircd/tls_openssl.c @@ -357,11 +357,9 @@ static IOResult ssl_handle_error(struct Client *cptr, SSL *tls, int res, int ori switch (err) { case SSL_ERROR_WANT_READ: - socket_events(&cli_socket(cptr), SOCK_EVENT_READABLE); return IO_BLOCKED; case SSL_ERROR_WANT_WRITE: - socket_events(&cli_socket(cptr), SOCK_EVENT_WRITABLE); return IO_BLOCKED; case SSL_ERROR_SYSCALL: From 13126414a55ca5d5d5db4e901cdc8a2a08d6bea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20T=C3=B8rrissen?= Date: Fri, 15 Aug 2025 00:05:54 +0200 Subject: [PATCH 11/20] Fix to gnutls implementation --- ircd/tls_gnutls.c | 125 ++++++++++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 53 deletions(-) diff --git a/ircd/tls_gnutls.c b/ircd/tls_gnutls.c index 8ff3b208..03631133 100644 --- a/ircd/tls_gnutls.c +++ b/ircd/tls_gnutls.c @@ -22,20 +22,22 @@ * This relies on gnutls_session_t being (a typedef to) a pointer type. */ -#include "config.h" -#include "client.h" -#include "ircd_features.h" +#include "ircd_tls.h" +#include "ircd.h" #include "ircd_log.h" #include "ircd_string.h" -#include "ircd_tls.h" -#include "listener.h" +#include "s_auth.h" +#include "send.h" #include "s_conf.h" #include "s_debug.h" +#include "listener.h" +#include "ircd_features.h" #include #include #include #include +#include #if defined(GNUTLS_AUTO_REAUTH) /* 3.6.4 */ # define TLS_SESSION_FLAGS GNUTLS_NONBLOCK | GNUTLS_NO_SIGNAL \ @@ -169,6 +171,13 @@ static void *tls_create(int flag, int fd, const char *name, const char *tls_ciph return NULL; } + /* Set verification callback if we allow self-signed certificates */ + if (feature_bool(FEAT_TLS_ALLOW_SELFSIGNED)) { + Debug((DEBUG_DEBUG, "GnuTLS: Allowing self-signed certificates for %s", name ? name : "server")); + /* Disable strict CA verification to allow self-signed certificates */ + gnutls_certificate_set_verify_flags(tls_cert, GNUTLS_VERIFY_DISABLE_CA_SIGN); + } + /* gnutls does not appear to allow an application to select which * SSL/TLS protocol versions to support, except indirectly through * priority strings. @@ -233,14 +242,6 @@ int ircd_tls_listen(struct Listener *listener) return 0; } -static void handle_blocked(struct Client *cptr, gnutls_session_t tls) -{ - if (gnutls_record_get_direction(tls)) - socket_events(&cli_socket(cptr), SOCK_EVENT_WRITABLE); - else - socket_events(&cli_socket(cptr), SOCK_EVENT_READABLE); -} - int ircd_tls_negotiate(struct Client *cptr) { gnutls_session_t tls; @@ -248,20 +249,24 @@ int ircd_tls_negotiate(struct Client *cptr) const gnutls_datum_t *datum; size_t len; int res; - char buf[32]; + unsigned char buf[32]; tls = s_tls(&cli_socket(cptr)); if (!tls) return 1; + /* Check for handshake timeout - use the constant from header */ + if (CurrentTime - cli_firsttime(cptr) > TLS_HANDSHAKE_TIMEOUT) { + Debug((DEBUG_DEBUG, "GnuTLS handshake timeout for %s", cli_name(cptr))); + return -1; + } + res = gnutls_handshake(tls); switch (res) { case GNUTLS_E_INTERRUPTED: case GNUTLS_E_AGAIN: - handle_blocked(cptr, tls); - /* and fall through */ case GNUTLS_E_WARNING_ALERT_RECEIVED: case GNUTLS_E_GOT_APPLICATION_DATA: return 0; @@ -285,24 +290,45 @@ int ircd_tls_negotiate(struct Client *cptr) return 1; } - /* TODO: extract peer's X.509 certificate */ + /* Complete the fingerprint extraction - convert buf to hex */ res = gnutls_x509_crt_import(crt, datum, GNUTLS_X509_FMT_DER); if (res) { log_write(LS_SYSTEM, L_ERROR, 0, "gnutls_x509_crt_import failed for %s: %d", cli_name(cptr), res); + gnutls_x509_crt_deinit(crt); return 1; } len = sizeof(buf); res = gnutls_x509_crt_get_fingerprint(crt, GNUTLS_DIG_SHA256, buf, &len); + gnutls_x509_crt_deinit(crt); if (res) { log_write(LS_SYSTEM, L_ERROR, 0, "gnutls_x509_crt_get_fingerprint failed for %s: %d", cli_name(cptr), res); - return 1; + return 1; + } + + /* Convert buf to hex like OpenSSL version */ + if (len == 32) { + char *p = cli_tls_fingerprint(cptr); + for (unsigned int i = 0; i < len; i++) { + sprintf(p + (i * 2), "%02x", buf[i]); + } + p[len * 2] = '\0'; + Debug((DEBUG_DEBUG, "Fingerprint for %s: %s", cli_name(cptr), cli_tls_fingerprint(cptr))); + } + else { + memset(cli_tls_fingerprint(cptr), 0, 65); + Debug((DEBUG_DEBUG, "Invalid fingerprint length: %zu", len)); + } + + /* For incoming connections, start auth like OpenSSL version */ + if (!IsConnecting(cptr)) { + start_auth(cptr); } - /* TODO: convert buf to hex (into cli_tls_fingerprint(cptr)) */ + return 1; default: @@ -322,20 +348,9 @@ IOResult ircd_tls_recv(struct Client *cptr, char *buf, tls = s_tls(&cli_socket(cptr)); if (!tls) return IO_FAILURE; - if (IsNegotiatingTLS(cptr)) - { - res = gnutls_handshake(tls); - if (res == GNUTLS_E_INTERRUPTED || res == GNUTLS_E_AGAIN) - { - handle_blocked(cptr, tls); - return IO_BLOCKED; - } - if (res != GNUTLS_E_SUCCESS) - return gnutls_error_is_fatal(res) ? IO_FAILURE : IO_BLOCKED; - } res = gnutls_record_recv(tls, buf, length); - if (res >= 0) + if (res > 0) { *count_out = res; return IO_SUCCESS; @@ -347,7 +362,7 @@ IOResult ircd_tls_recv(struct Client *cptr, char *buf, return IO_SUCCESS; } if (res == GNUTLS_E_INTERRUPTED || res == GNUTLS_E_AGAIN) - handle_blocked(cptr, tls); + return IO_BLOCKED; return gnutls_error_is_fatal(res) ? IO_FAILURE : IO_BLOCKED; } @@ -365,17 +380,6 @@ IOResult ircd_tls_sendv(struct Client *cptr, struct MsgQ *buf, tls = s_tls(&con_socket(con)); if (!tls) return IO_FAILURE; - if (IsNegotiatingTLS(cptr)) - { - res = gnutls_handshake(tls); - if (res == GNUTLS_E_INTERRUPTED || res == GNUTLS_E_AGAIN) - { - handle_blocked(cptr, tls); - return IO_BLOCKED; - } - if (res != GNUTLS_E_SUCCESS) - return gnutls_error_is_fatal(res) ? IO_FAILURE : IO_BLOCKED; - } /* TODO: Try to use gnutls_record_cork()/_uncork()/_check_corked(). * The exact semantics of check_corked()'s return value are not clear: @@ -387,18 +391,27 @@ IOResult ircd_tls_sendv(struct Client *cptr, struct MsgQ *buf, if (con->con_rexmit) { res = gnutls_record_send(tls, con->con_rexmit, con->con_rexmit_len); - if (res <= 0) - { + if (res <= 0) { if (res == GNUTLS_E_INTERRUPTED || res == GNUTLS_E_AGAIN) - handle_blocked(cptr, tls); + return IO_BLOCKED; return gnutls_error_is_fatal(res) ? IO_FAILURE : IO_BLOCKED; } - msgq_excise(buf, con->con_rexmit, con->con_rexmit_len); - con->con_rexmit_len = 0; - con->con_rexmit = NULL; - result = IO_SUCCESS; + + // Only excise the message if the full message was sent + if (res == (int)con->con_rexmit_len) { + msgq_excise(buf, con->con_rexmit, con->con_rexmit_len); + con->con_rexmit_len = 0; + con->con_rexmit = NULL; + result = IO_SUCCESS; + } else { + // Partial send, update pointer and length for next retry + con->con_rexmit = (char *)con->con_rexmit + res; + con->con_rexmit_len -= res; + return IO_BLOCKED; + } } + // Process remaining messages in the queue count = msgq_mapiov(buf, iov, sizeof(iov) / sizeof(iov[0]), count_in); for (ii = 0; ii < count; ++ii) { @@ -407,12 +420,18 @@ IOResult ircd_tls_sendv(struct Client *cptr, struct MsgQ *buf, { *count_out += res; result = IO_SUCCESS; + if (res < (int)iov[ii].iov_len) { + // Partial send, store for retransmission + cli_connect(cptr)->con_rexmit = (char *)iov[ii].iov_base + res; + cli_connect(cptr)->con_rexmit_len = iov[ii].iov_len - res; + return IO_BLOCKED; + } + // else, full message sent, continue to next continue; } - if (res == GNUTLS_E_INTERRUPTED || res == GNUTLS_E_AGAIN) - { - handle_blocked(cptr, tls); + /* We only reach this if the gnutls_record_send failed. */ + if (res == GNUTLS_E_INTERRUPTED || res == GNUTLS_E_AGAIN) { cli_connect(cptr)->con_rexmit = iov[ii].iov_base; cli_connect(cptr)->con_rexmit_len = iov[ii].iov_len; } From d9bab5600396e5e800b8075790a2e5d2e2e6d0de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20T=C3=B8rrissen?= Date: Sat, 16 Aug 2025 00:11:25 +0200 Subject: [PATCH 12/20] libtls implementation. --- acinclude.m4 | 17 ++-- ircd/tls_gnutls.c | 9 +- ircd/tls_libtls.c | 206 +++++++++++++++++++++++++++++++++++----------- 3 files changed, 177 insertions(+), 55 deletions(-) diff --git a/acinclude.m4 b/acinclude.m4 index d47458e8..e790611d 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -182,17 +182,20 @@ if test x"$unet_cv_with_tls" = xyes ; then fi dnl Try libtls next. if test x"$unet_cv_with_tls" = xyes ; then - dnl First try pkg-config (Linux/ports with libtls.pc). Do not modify LIBS/CFLAGS yet. - PKG_CHECK_MODULES([LIBTLS], [libtls], [ - unet_cv_with_tls=libtls - ], [ + dnl Temporarily disable pkg-config to force fallback path + dnl PKG_CHECK_MODULES([LIBTLS], [libtls], [ + dnl unet_cv_with_tls=libtls + dnl ], [ dnl Fallback for OpenBSD base (no .pc): header + symbol link test. AC_CHECK_HEADER([tls.h], [ AC_CHECK_LIB([tls], [tls_init], [ unet_cv_with_tls=libtls + LIBTLS_LIBS="-ltls" + LIBTLS_CFLAGS="" + LIBTLS_LDFLAGS="" ]) ]) - ]) + dnl ]) fi case x"$unet_cv_with_tls" in @@ -209,6 +212,10 @@ xgnutls) TLS_C="tls_gnutls.c" ;; xlibtls) + # Ensure LIBTLS_LIBS is set even when explicitly specified + if test x"$LIBTLS_LIBS" = x ; then + LIBTLS_LIBS="-ltls" + fi CFLAGS="$CFLAGS $LIBTLS_CFLAGS" LDFLAGS="$LDFLAGS $LIBTLS_LDFLAGS" LIBS="$LIBS $LIBTLS_LIBS" diff --git a/ircd/tls_gnutls.c b/ircd/tls_gnutls.c index 03631133..d3efbb17 100644 --- a/ircd/tls_gnutls.c +++ b/ircd/tls_gnutls.c @@ -38,6 +38,7 @@ #include #include #include +#include #if defined(GNUTLS_AUTO_REAUTH) /* 3.6.4 */ # define TLS_SESSION_FLAGS GNUTLS_NONBLOCK | GNUTLS_NO_SIGNAL \ @@ -334,7 +335,13 @@ int ircd_tls_negotiate(struct Client *cptr) default: Debug((DEBUG_DEBUG, " ... gnutls_handshake() failed -> %s (%d)", gnutls_strerror(res), res)); - return gnutls_error_is_fatal(res) ? -1 : 0; + if (gnutls_error_is_fatal(res)) { + const char* const error_tls = "ERROR :TLS connection error\r\n"; + Debug((DEBUG_DEBUG, "GnuTLS handshake failed for %s: %s", cli_name(cptr), gnutls_strerror(res))); + write(cli_fd(cptr), error_tls, strlen(error_tls)); + return -1; + } + return 0; } } diff --git a/ircd/tls_libtls.c b/ircd/tls_libtls.c index 945bb2d5..a32f2e36 100644 --- a/ircd/tls_libtls.c +++ b/ircd/tls_libtls.c @@ -23,19 +23,26 @@ #include "config.h" #include "client.h" #include "ircd_features.h" +#include "ircd.h" #include "ircd_log.h" #include "ircd_string.h" #include "ircd_tls.h" #include "listener.h" +#include "s_auth.h" +#include "send.h" #include "s_conf.h" #include "s_debug.h" #include +#include +#include #include +#include #include -#define CONCAT(A, B) A # B -const char *ircd_tls_version = CONCAT("libtls ", TLS_API); +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) +const char *ircd_tls_version = "libtls " TOSTRING(TLS_API); int ircd_tls_init(void) { @@ -70,13 +77,11 @@ static struct tls_config *make_tls_config(const char *ciphers) return NULL; } - if (tls_config_add_keypair_file(new_cfg, ircd_tls_certfile, ircd_tls_keyfile) < 0) + if (tls_config_set_keypair_file(new_cfg, ircd_tls_certfile, ircd_tls_keyfile) < 0) { log_write(LS_SYSTEM, L_ERROR, 0, "unable to load certificate and key: %s", tls_config_error(new_cfg)); - fail: - tls_config_free(new_cfg); - return NULL; + goto fail; } str = feature_str(FEAT_TLS_CACERTDIR); @@ -86,8 +91,8 @@ static struct tls_config *make_tls_config(const char *ciphers) { log_write(LS_SYSTEM, L_ERROR, 0, "unable to set CA path to %s: %s", str, tls_config_error(new_cfg)); + goto fail; } - goto fail; } str = feature_str(FEAT_TLS_CACERTFILE); @@ -97,21 +102,21 @@ static struct tls_config *make_tls_config(const char *ciphers) { log_write(LS_SYSTEM, L_ERROR, 0, "unable to set CA file to %s: %s", str, tls_config_error(new_cfg)); + goto fail; } - goto fail; + } + + /* Configure verification based on self-signed certificate feature */ + if (feature_bool(FEAT_TLS_ALLOW_SELFSIGNED)) { + tls_config_verify_client_optional(new_cfg); + tls_config_insecure_noverifycert(new_cfg); } protos = 0; - /* libtls does not support SSLv2 or SSLv3. */ - if (feature_bool(FEAT_TLS_V1P0)) - protos |= TLS_PROTOCOL_TLSv1_0; - if (feature_bool(FEAT_TLS_V1P1)) - protos |= TLS_PROTOCOL_TLSv1_1; - if (feature_bool(FEAT_TLS_V1P2)) - protos |= TLS_PROTOCOL_TLSv1_2; + /* Set minimum TLS version to 1.2 (like OpenSSL) and support 1.3 */ + protos |= TLS_PROTOCOL_TLSv1_2; #ifdef TLS_PROTOCOL_TLSv1_3 - if (feature_bool(FEAT_TLS_V1P3)) - protos |= TLS_PROTOCOL_TLSv1_3; + protos |= TLS_PROTOCOL_TLSv1_3; #endif if (tls_config_set_protocols(new_cfg, protos) < 0) { @@ -134,16 +139,30 @@ static struct tls_config *make_tls_config(const char *ciphers) } return new_cfg; + +fail: + tls_config_free(new_cfg); + return NULL; } void *ircd_tls_accept(struct Listener *listener, int fd) { struct tls *tls; - if (tls_accept_socket(listener->tls_ctx, &tls, fd) < 0) + if (!listener) { + log_write(LS_SYSTEM, L_ERROR, 0, "TLS accept called with NULL listener"); + return NULL; + } + + if (!listener->tls_ctx) { + log_write(LS_SYSTEM, L_ERROR, 0, "TLS accept called with NULL tls_ctx"); + return NULL; + } + + if (tls_accept_socket((struct tls *)listener->tls_ctx, &tls, fd) < 0) { log_write(LS_SYSTEM, L_ERROR, 0, "TLS accept failed: %s", - tls_error(listener->tls_ctx)); + tls_error((struct tls *)listener->tls_ctx)); return NULL; } @@ -194,49 +213,59 @@ void ircd_tls_close(void *ctx, const char *message) tls_free(ctx); } -static int tls_handle_error(struct Client *cptr, struct tls *tls, int err) +static IOResult tls_handle_error(struct Client *cptr, struct tls *tls, int err) { - if (err == TLS_WANT_POLLIN) - { - socket_events(&cli_socket(cptr), SOCK_EVENT_READABLE); - return 0; - } - if (err == TLS_WANT_POLLOUT) - { - socket_events(&cli_socket(cptr), SOCK_EVENT_WRITABLE); - return 0; + switch (err) { + case TLS_WANT_POLLIN: + case TLS_WANT_POLLOUT: + return IO_BLOCKED; + + default: + /* Fatal error */ + Debug((DEBUG_DEBUG, "tls fatal error for %s: %s", cli_name(cptr), tls_error(tls))); + break; } - Debug((DEBUG_DEBUG, "tls fatal error for %s: %s", cli_name(cptr), tls_error(tls))); tls_free(tls); s_tls(&cli_socket(cptr)) = NULL; - return -1; + return IO_FAILURE; } int ircd_tls_listen(struct Listener *listener) { struct tls_config *cfg; - struct tls *srv; + struct tls *server_ctx; cfg = make_tls_config(listener->tls_ciphers); if (!cfg) return 1; - tls_config_verify_client_optional(cfg); if (listener->tls_ctx) - tls_reset(listener->tls_ctx); - else if (!(listener->tls_ctx = tls_server())) + server_ctx = (struct tls *)listener->tls_ctx; + else { - log_write(LS_SYSTEM, L_ERROR, 0, "tls_server failed"); - return 2; + server_ctx = tls_server(); + if (!server_ctx) + { + log_write(LS_SYSTEM, L_ERROR, 0, "tls_server failed"); + tls_config_free(cfg); + return 2; + } + listener->tls_ctx = (void *)server_ctx; } - if (tls_configure(listener->tls_ctx, cfg) < 0) + if (tls_configure(server_ctx, cfg) < 0) { + const char *error = tls_error(server_ctx); + fprintf(stderr, "TLS configure failed: %s\n", error ? error : "unknown error"); log_write(LS_SYSTEM, L_ERROR, 0, "unable to configure TLS server context: %s", - tls_error(listener->tls_ctx)); - return 3; + error ? error : "unknown error"); + tls_free(server_ctx); + listener->tls_ctx = NULL; + tls_config_free(cfg); + return 3; /* Return error when configuration fails */ } + tls_config_free(cfg); return 0; } @@ -245,23 +274,61 @@ int ircd_tls_negotiate(struct Client *cptr) const char *hash; struct tls *tls; int res; + const char* const error_tls = "ERROR :TLS connection error\r\n"; tls = s_tls(&cli_socket(cptr)); if (!tls) return 1; + /* Check for handshake timeout */ + if (CurrentTime - cli_firsttime(cptr) > TLS_HANDSHAKE_TIMEOUT) { + Debug((DEBUG_DEBUG, "libtls handshake timeout for %s", cli_name(cptr))); + return -1; + } + + Debug((DEBUG_DEBUG, "libtls handshake for %s", cli_name(cptr))); + res = tls_handshake(tls); if (res == 0) { + ClearNegotiatingTLS(cptr); + hash = tls_peer_cert_hash(tls); if (hash && !ircd_strncmp(hash, "SHA256:", 7)) { - ircd_strncpy(cli_tls_fingerprint(cptr), hash+7, 64); + /* Convert the hash to our fingerprint format */ + if (strlen(hash + 7) <= 64) { + ircd_strncpy(cli_tls_fingerprint(cptr), hash + 7, 64); + Debug((DEBUG_DEBUG, "Fingerprint for %s: %s", cli_name(cptr), cli_tls_fingerprint(cptr))); + } else { + memset(cli_tls_fingerprint(cptr), 0, 65); + Debug((DEBUG_DEBUG, "Invalid fingerprint length: %zu", strlen(hash + 7))); + } + } else { + memset(cli_tls_fingerprint(cptr), 0, 65); + Debug((DEBUG_DEBUG, "Failed to get fingerprint for %s", cli_name(cptr))); } - ClearNegotiatingTLS(cptr); + + /* For incoming connections, start auth like OpenSSL/GnuTLS */ + if (!IsConnecting(cptr)) { + start_auth(cptr); + } + return 1; } - return tls_handle_error(cptr, tls, res); + + if (res == TLS_WANT_POLLIN || res == TLS_WANT_POLLOUT) { + return 0; /* Handshake in progress */ + } + + IOResult tls_result = tls_handle_error(cptr, tls, res); + if (tls_result == IO_FAILURE) { + Debug((DEBUG_DEBUG, "TLS handshake failed for %s", cli_name(cptr))); + write(cli_fd(cptr), error_tls, strlen(error_tls)); + return -1; + } + /* tls_result == IO_BLOCKED - handshake still in progress */ + return 0; } IOResult ircd_tls_recv(struct Client *cptr, char *buf, @@ -275,12 +342,13 @@ IOResult ircd_tls_recv(struct Client *cptr, char *buf, return IO_FAILURE; res = tls_read(tls, buf, length); - if (res >= 0) + if (res > 0) { *count_out = res; return IO_SUCCESS; } - return (tls_handle_error(cptr, tls, res) < 0) ? IO_FAILURE : IO_BLOCKED; + + return tls_handle_error(cptr, tls, res); } IOResult ircd_tls_sendv(struct Client *cptr, struct MsgQ *buf, @@ -289,7 +357,8 @@ IOResult ircd_tls_sendv(struct Client *cptr, struct MsgQ *buf, struct iovec iov[512]; struct tls *tls; struct Connection *con; - int ii, count, res, orig_errno; + IOResult result = IO_BLOCKED; + int ii, count, res; con = cli_connect(cptr); tls = s_tls(&con_socket(con)); @@ -298,6 +367,30 @@ IOResult ircd_tls_sendv(struct Client *cptr, struct MsgQ *buf, /* tls_write() does not document any restriction on retries. */ *count_out = 0; + if (con->con_rexmit) + { + res = tls_write(tls, con->con_rexmit, con->con_rexmit_len); + if (res <= 0) { + if (res == TLS_WANT_POLLIN || res == TLS_WANT_POLLOUT) + return IO_BLOCKED; + return tls_handle_error(cptr, tls, res); + } + + // Only excise the message if the full message was sent + if (res == (int)con->con_rexmit_len) { + msgq_excise(buf, con->con_rexmit, con->con_rexmit_len); + con->con_rexmit_len = 0; + con->con_rexmit = NULL; + result = IO_SUCCESS; + } else { + // Partial send, update pointer and length for next retry + con->con_rexmit = (char *)con->con_rexmit + res; + con->con_rexmit_len -= res; + return IO_BLOCKED; + } + } + + // Process remaining messages in the queue count = msgq_mapiov(buf, iov, sizeof(iov) / sizeof(iov[0]), count_in); for (ii = 0; ii < count; ++ii) { @@ -305,11 +398,26 @@ IOResult ircd_tls_sendv(struct Client *cptr, struct MsgQ *buf, if (res > 0) { *count_out += res; - break; + result = IO_SUCCESS; + if (res < (int)iov[ii].iov_len) { + // Partial send, store for retransmission + cli_connect(cptr)->con_rexmit = (char *)iov[ii].iov_base + res; + cli_connect(cptr)->con_rexmit_len = iov[ii].iov_len - res; + return IO_BLOCKED; + } + // else, full message sent, continue to next + continue; } - return (tls_handle_error(cptr, tls, res) < 0) ? IO_FAILURE : IO_BLOCKED; + /* We only reach this if the tls_write failed. */ + if (res == TLS_WANT_POLLIN || res == TLS_WANT_POLLOUT) { + cli_connect(cptr)->con_rexmit = iov[ii].iov_base; + cli_connect(cptr)->con_rexmit_len = iov[ii].iov_len; + return IO_BLOCKED; + } + result = tls_handle_error(cptr, tls, res); + break; } - return IO_SUCCESS; + return result; } From eeb73fd54585a1d0af1ec85a2b878fca719db120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20T=C3=B8rrissen?= Date: Sat, 16 Aug 2025 00:21:40 +0200 Subject: [PATCH 13/20] Added feature for bursting fingerprints for backwards compatability --- include/ircd_features.h | 1 + ircd/ircd_features.c | 1 + ircd/s_user.c | 7 ++++--- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/include/ircd_features.h b/include/ircd_features.h index 30966835..6d6bc6f5 100644 --- a/include/ircd_features.h +++ b/include/ircd_features.h @@ -104,6 +104,7 @@ enum Feature { FEAT_TLS_CACERTDIR, FEAT_TLS_CIPHERS, FEAT_TLS_ALLOW_SELFSIGNED, + FEAT_TLS_BURST_FINGERPRINT, /* features that affect all operators */ FEAT_CONFIG_OPERCMDS, diff --git a/ircd/ircd_features.c b/ircd/ircd_features.c index 19c0833a..2d77349f 100644 --- a/ircd/ircd_features.c +++ b/ircd/ircd_features.c @@ -369,6 +369,7 @@ static struct FeatureDesc { F_S(TLS_CACERTDIR, FEAT_NULL | FEAT_CASE | FEAT_OPER, 0, 0), F_S(TLS_CIPHERS, FEAT_NULL | FEAT_CASE | FEAT_OPER, 0, 0), F_B(TLS_ALLOW_SELFSIGNED, 0, 1, 0), + F_B(TLS_BURST_FINGERPRINT, 0, 1, 0), /* features that affect all operators */ F_B(CONFIG_OPERCMDS, 0, 0, 0), diff --git a/ircd/s_user.c b/ircd/s_user.c index 822bb25b..621d6394 100644 --- a/ircd/s_user.c +++ b/ircd/s_user.c @@ -1090,7 +1090,8 @@ int set_user_mode(struct Client *cptr, struct Client *sptr, int parc, case 'z': if (what == MODE_ADD) { SetTLS(sptr); - tls_fingerprint = *(++p); + if (feature_bool(FEAT_TLS_BURST_FINGERPRINT)) + tls_fingerprint = *(++p); } /* There is no -z */ break; @@ -1186,7 +1187,7 @@ int set_user_mode(struct Client *cptr, struct Client *sptr, int parc, if (!FlagHas(&setflags, FLAG_HIDDENHOST) && do_host_hiding && allow_modes != ALLOWMODES_DEFAULT) hide_hostmask(sptr, FLAG_HIDDENHOST); - if (tls_fingerprint && tls_fingerprint[0] != '_') { + if (feature_bool(FEAT_TLS_BURST_FINGERPRINT) && tls_fingerprint && tls_fingerprint[0] != '_') { ircd_strncpy(cli_tls_fingerprint(sptr), tls_fingerprint, 64); Debug((DEBUG_DEBUG, "Received TLS fingerprint in user mode; " "fingerprint \"%s\"", cli_tls_fingerprint(sptr))); @@ -1284,7 +1285,7 @@ char *umode_str(struct Client *cptr) * If the fingerprint is empty (client has not provided a certificate), * we return _ in the place of the fingerprint. */ - if (IsTLS(cptr)) + if (IsTLS(cptr) && feature_bool(FEAT_TLS_BURST_FINGERPRINT)) { char* t = cli_tls_fingerprint(cptr); From 09ef1ce3c2d7cc859c9315c59f3421be4eaaa2d9 Mon Sep 17 00:00:00 2001 From: MrIron Date: Tue, 19 Aug 2025 23:11:50 +0200 Subject: [PATCH 14/20] Pass on fingerprint to iauth --- ircd/s_auth.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ircd/s_auth.c b/ircd/s_auth.c index 413727dc..1ac98d38 100644 --- a/ircd/s_auth.c +++ b/ircd/s_auth.c @@ -1190,6 +1190,10 @@ void start_auth(struct Client* client) if (IsUserPort(client)) { /* Try to start iauth lookup. */ start_iauth_query(auth); + + /* Pass on fingerprint to iauth. */ + if (IsTLS(client) && cli_tls_fingerprint(client)) + sendto_iauth(auth->client, "Z %s", cli_tls_fingerprint(client)); } } From 66c1702b151c689a36c3c4687c32f80f1292607a Mon Sep 17 00:00:00 2001 From: MrIron Date: Tue, 26 Aug 2025 19:56:29 +0200 Subject: [PATCH 15/20] Added supported tls modes --- include/channel.h | 2 +- include/client.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/channel.h b/include/channel.h index f2f9be03..ab16b64c 100644 --- a/include/channel.h +++ b/include/channel.h @@ -123,7 +123,7 @@ struct Client; #define MODE_WPARAS (MODE_CHANOP|MODE_VOICE|MODE_BAN|MODE_KEY|MODE_LIMIT|MODE_APASS|MODE_UPASS) /** Available Channel modes */ -#define infochanmodes feature_bool(FEAT_OPLEVELS) ? "AbiklmnopstUvrDdRcCPM" : "biklmnopstvrDdRcCPM" +#define infochanmodes feature_bool(FEAT_OPLEVELS) ? "AbiklmnopstUvrDdRcCPMZ" : "biklmnopstvrDdRcCPMZ" /** Available Channel modes that take parameters */ #define infochanmodeswithparams feature_bool(FEAT_OPLEVELS) ? "AbkloUv" : "bklov" diff --git a/include/client.h b/include/client.h index e1ffd2fa..ce26e9af 100644 --- a/include/client.h +++ b/include/client.h @@ -93,7 +93,7 @@ typedef unsigned long flagpage_t; #define FlagClr(set,flag) ((set)->bits[FLAGSET_INDEX(flag)] &= ~FLAGSET_MASK(flag)) /** String containing valid user modes, in no particular order. */ -#define infousermodes "diOoswkgx" +#define infousermodes "diOoswkgxz" /** Operator privileges. */ enum Priv From b12b8e8648ca4cf292873b7c428b2788ca1ef608 Mon Sep 17 00:00:00 2001 From: MrIron Date: Thu, 23 Oct 2025 10:36:37 +0200 Subject: [PATCH 16/20] fix: Bug in tls implementation caused by clients immediately closing the connection. start_auth() is now called from ET_WRITE which immediately return --- ircd/ircd.c | 1 + ircd/s_auth.c | 2 +- ircd/s_bsd.c | 16 +++++++++------- ircd/tls_gnutls.c | 5 ----- ircd/tls_libtls.c | 5 ----- ircd/tls_openssl.c | 30 +++++++++++++----------------- 6 files changed, 24 insertions(+), 35 deletions(-) diff --git a/ircd/ircd.c b/ircd/ircd.c index 3ddee06f..97e77aa1 100644 --- a/ircd/ircd.c +++ b/ircd/ircd.c @@ -501,6 +501,7 @@ static void parse_command_line(int argc, char** argv) { #else printf("select()"); #endif + printf("\nTLS: %s", ircd_tls_version); printf("\nCompiled for a maximum of %d connections.\n", MAXCONNECTIONS); diff --git a/ircd/s_auth.c b/ircd/s_auth.c index 1ac98d38..d848a084 100644 --- a/ircd/s_auth.c +++ b/ircd/s_auth.c @@ -1192,7 +1192,7 @@ void start_auth(struct Client* client) start_iauth_query(auth); /* Pass on fingerprint to iauth. */ - if (IsTLS(client) && cli_tls_fingerprint(client)) + if (IsTLS(client) && *cli_tls_fingerprint(client)) sendto_iauth(auth->client, "Z %s", cli_tls_fingerprint(client)); } } diff --git a/ircd/s_bsd.c b/ircd/s_bsd.c index afde5974..7f2528d6 100644 --- a/ircd/s_bsd.c +++ b/ircd/s_bsd.c @@ -969,16 +969,17 @@ static void client_sock_callback(struct Event* ev) ircd_tls_close(s_tls(&cli_socket(cptr)), "TLS negotiation failed"); s_tls(&cli_socket(cptr)) = NULL; } - exit_client(cptr, cptr, &me, "TLS negotiation failed"); - return; + fmt = "TLS negotiation failed: %s"; + fallback = "TLS negotiation failed"; + break; } if (res == 0) { /* Still negotiating */ break; } - /* TLS negotiation succeeded */ - if (IsConnecting(cptr)) - completed_connection(cptr); + /* TLS negotiation succeeded */ + (IsConnecting(cptr) ? completed_connection : start_auth)(cptr); + return; } ClrFlag(cptr, FLAG_BLOCKED); if (cli_listing(cptr) && MsgQLength(&(cli_sendQ(cptr))) < 2048) @@ -1000,8 +1001,9 @@ static void client_sock_callback(struct Event* ev) ircd_tls_close(s_tls(&cli_socket(cptr)), "TLS negotiation failed"); s_tls(&cli_socket(cptr)) = NULL; } - exit_client(cptr, cptr, &me, "TLS negotiation failed"); - return; + fmt = "TLS negotiation failed: %s"; + fallback = "TLS negotiation failed"; + break; } if (res == 0) { /* Still negotiating */ diff --git a/ircd/tls_gnutls.c b/ircd/tls_gnutls.c index d3efbb17..d68586d4 100644 --- a/ircd/tls_gnutls.c +++ b/ircd/tls_gnutls.c @@ -324,11 +324,6 @@ int ircd_tls_negotiate(struct Client *cptr) memset(cli_tls_fingerprint(cptr), 0, 65); Debug((DEBUG_DEBUG, "Invalid fingerprint length: %zu", len)); } - - /* For incoming connections, start auth like OpenSSL version */ - if (!IsConnecting(cptr)) { - start_auth(cptr); - } return 1; diff --git a/ircd/tls_libtls.c b/ircd/tls_libtls.c index a32f2e36..79074a03 100644 --- a/ircd/tls_libtls.c +++ b/ircd/tls_libtls.c @@ -309,11 +309,6 @@ int ircd_tls_negotiate(struct Client *cptr) Debug((DEBUG_DEBUG, "Failed to get fingerprint for %s", cli_name(cptr))); } - /* For incoming connections, start auth like OpenSSL/GnuTLS */ - if (!IsConnecting(cptr)) { - start_auth(cptr); - } - return 1; } diff --git a/ircd/tls_openssl.c b/ircd/tls_openssl.c index 30acd07a..b56b6083 100644 --- a/ircd/tls_openssl.c +++ b/ircd/tls_openssl.c @@ -351,8 +351,8 @@ static IOResult ssl_handle_error(struct Client *cptr, SSL *tls, int res, int ori { int err = SSL_get_error(tls, res); - Debug((DEBUG_DEBUG, "ssl_handle_error: SSL_get_error=%d, res=%d, orig_errno=%d for %s", - err, res, orig_errno, cli_name(cptr))); + Debug((DEBUG_DEBUG, "ssl_handle_error: SSL_get_error=%d, res=%d, orig_errno=%d for %C", + err, res, orig_errno, cptr)); switch (err) { @@ -367,23 +367,24 @@ static IOResult ssl_handle_error(struct Client *cptr, SSL *tls, int res, int ori return IO_BLOCKED; break; case SSL_ERROR_ZERO_RETURN: - Debug((DEBUG_DEBUG, "SSL_ERROR_ZERO_RETURN: peer closed connection for %s", cli_name(cptr))); + Debug((DEBUG_DEBUG, "SSL_ERROR_ZERO_RETURN: peer closed connection for %C", cptr)); if (SSL_shutdown(tls) == 0) SSL_shutdown(tls); break; default: /* Fatal SSL error */ - Debug((DEBUG_ERROR, "SSL fatal error %d for %s", err, cli_name(cptr))); + Debug((DEBUG_ERROR, "SSL fatal error %d for %C", err, cptr)); unsigned long e; while ((e = ERR_get_error()) != 0) { - Debug((DEBUG_ERROR, "[SSL] %s", ERR_error_string(e, NULL))); + Debug((DEBUG_ERROR, "SSL ERROR: %s", ERR_error_string(e, NULL))); } break; } /* Fatal error - clean up SSL context */ if (tls && s_tls(&cli_socket(cptr)) == tls) { + Debug((DEBUG_ERROR, "SSL fall-through fatal error %d for %C", err, cptr)); s_tls(&cli_socket(cptr)) = NULL; /* Do not call SSL_shutdown() after fatal errors */ SSL_free(tls); @@ -407,7 +408,7 @@ int ircd_tls_negotiate(struct Client *cptr) /* Check for handshake timeout */ if (CurrentTime - cli_firsttime(cptr) > TLS_HANDSHAKE_TIMEOUT) { - Debug((DEBUG_DEBUG, "SSL handshake timeout for %s", cli_name(cptr))); + Debug((DEBUG_DEBUG, "SSL handshake timeout for fd=%d", cli_fd(cptr))); return -1; } @@ -420,18 +421,18 @@ int ircd_tls_negotiate(struct Client *cptr) if (res == 1) { - Debug((DEBUG_DEBUG, "SSL handshake success for %s", cli_name(cptr))); + Debug((DEBUG_DEBUG, "SSL handshake success for fd=%d", cli_fd(cptr))); cert = SSL_get_peer_certificate(tls); if (cert) { - Debug((DEBUG_DEBUG, "SSL_get_peer_certificate success for %s", cli_name(cptr))); + Debug((DEBUG_DEBUG, "SSL_get_peer_certificate success for fd=%d", cli_fd(cptr))); len = sizeof(buf); res = X509_digest(cert, fp_digest, buf, &len); X509_free(cert); if (res) { - log_write(LS_SYSTEM, L_ERROR, 0, "X509_digest failed for %s: %d", - cli_name(cptr), res); + log_write(LS_SYSTEM, L_ERROR, 0, "X509_digest failed for %C: %d", + cptr, res); } if (len == 32) { /* Convert fingerprint to lowercase hex */ @@ -440,7 +441,7 @@ int ircd_tls_negotiate(struct Client *cptr) sprintf(p + (i * 2), "%02x", buf[i]); } p[len * 2] = '\0'; - Debug((DEBUG_DEBUG, "Fingerprint for %s: %s", cli_name(cptr), cli_tls_fingerprint(cptr))); + Debug((DEBUG_DEBUG, "Fingerprint for %C: %s", cptr, cli_tls_fingerprint(cptr))); } else { memset(cli_tls_fingerprint(cptr), 0, 65); @@ -448,11 +449,6 @@ int ircd_tls_negotiate(struct Client *cptr) } } ClearNegotiatingTLS(cptr); - - /* For incoming connections, start auth */ - if (!IsConnecting(cptr)) { - start_auth(cptr); - } } else { @@ -460,7 +456,7 @@ int ircd_tls_negotiate(struct Client *cptr) /* Handshake in progress. */ IOResult ssl_result = ssl_handle_error(cptr, tls, res, orig_errno); if (ssl_result == IO_FAILURE) { - Debug((DEBUG_DEBUG, "SSL handshake failed for %s", cli_name(cptr))); + Debug((DEBUG_DEBUG, "SSL handshake failed for fd=%d", cli_fd(cptr))); write(cli_fd(cptr), error_ssl, strlen(error_ssl)); return -1; } From 58eef853b227e6d6ede4d6eb5daca5d653f7cb89 Mon Sep 17 00:00:00 2001 From: MrIron Date: Thu, 23 Oct 2025 11:27:38 +0200 Subject: [PATCH 17/20] fix: compiler error on FreeBSD due to different type --- ircd/s_bsd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ircd/s_bsd.c b/ircd/s_bsd.c index 7f2528d6..9d6e4745 100644 --- a/ircd/s_bsd.c +++ b/ircd/s_bsd.c @@ -978,7 +978,7 @@ static void client_sock_callback(struct Event* ev) break; } /* TLS negotiation succeeded */ - (IsConnecting(cptr) ? completed_connection : start_auth)(cptr); + IsConnecting(cptr) ? completed_connection(cptr) : start_auth(cptr); return; } ClrFlag(cptr, FLAG_BLOCKED); From 10139c5c0640782401c6ac8e790d1f06b7b6a907 Mon Sep 17 00:00:00 2001 From: MrIron Date: Wed, 25 Mar 2026 23:40:38 +0100 Subject: [PATCH 18/20] Updated Makefile --- configure.ac | 30 ++++++++++++++++++++++++++++++ ircd/Makefile.am | 13 +++++++++++++ 2 files changed, 43 insertions(+) diff --git a/configure.ac b/configure.ac index 5b25f2a7..ab9f636c 100644 --- a/configure.ac +++ b/configure.ac @@ -724,8 +724,38 @@ AC_MSG_RESULT([$unet_cv_with_maxcon]) AC_DEFINE_UNQUOTED(MAXCONNECTIONS, $unet_cv_with_maxcon, [Maximum number of network connections]) + +# Call unet_TLS macro to set unet_cv_with_tls and TLS_C unet_TLS +# Set Automake conditionals for each TLS backend +case x"$unet_cv_with_tls" in + xopenssl) + AM_CONDITIONAL([TLS_OPENSSL], [true]) + AM_CONDITIONAL([TLS_GNUTLS], [false]) + AM_CONDITIONAL([TLS_LIBTLS], [false]) + AM_CONDITIONAL([TLS_NONE], [false]) + ;; + xgnutls) + AM_CONDITIONAL([TLS_OPENSSL], [false]) + AM_CONDITIONAL([TLS_GNUTLS], [true]) + AM_CONDITIONAL([TLS_LIBTLS], [false]) + AM_CONDITIONAL([TLS_NONE], [false]) + ;; + xlibtls) + AM_CONDITIONAL([TLS_OPENSSL], [false]) + AM_CONDITIONAL([TLS_GNUTLS], [false]) + AM_CONDITIONAL([TLS_LIBTLS], [true]) + AM_CONDITIONAL([TLS_NONE], [false]) + ;; + *) + AM_CONDITIONAL([TLS_OPENSSL], [false]) + AM_CONDITIONAL([TLS_GNUTLS], [false]) + AM_CONDITIONAL([TLS_LIBTLS], [false]) + AM_CONDITIONAL([TLS_NONE], [true]) + ;; +esac + dnl Finally really generate all output files: AC_CONFIG_FILES([Makefile ircd/Makefile ircd/test/Makefile]) AC_OUTPUT diff --git a/ircd/Makefile.am b/ircd/Makefile.am index d30ff340..2350f6d5 100644 --- a/ircd/Makefile.am +++ b/ircd/Makefile.am @@ -147,6 +147,19 @@ ircd_SOURCES = \ ircd_crypt_smd5.c \ ircd_crypt_native.c +if TLS_OPENSSL +ircd_SOURCES += tls_openssl.c +endif +if TLS_GNUTLS +ircd_SOURCES += tls_gnutls.c +endif +if TLS_LIBTLS +ircd_SOURCES += tls_libtls.c +endif +if TLS_NONE +ircd_SOURCES += tls_none.c +endif + EXTRA_ircd_SOURCES = \ engine_devpoll.c \ engine_epoll.c \ From b57dfba2a11536fb2d1edd161e9ede272adf9ccc Mon Sep 17 00:00:00 2001 From: MrIron Date: Thu, 26 Mar 2026 00:01:22 +0100 Subject: [PATCH 19/20] Added m4 macro for openssl check --- m4/ax_check_openssl.m4 | 124 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 m4/ax_check_openssl.m4 diff --git a/m4/ax_check_openssl.m4 b/m4/ax_check_openssl.m4 new file mode 100644 index 00000000..ce1617c1 --- /dev/null +++ b/m4/ax_check_openssl.m4 @@ -0,0 +1,124 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_check_openssl.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_OPENSSL([action-if-found[, action-if-not-found]]) +# +# DESCRIPTION +# +# Look for OpenSSL in a number of default spots, or in a user-selected +# spot (via --with-openssl). Sets +# +# OPENSSL_INCLUDES to the include directives required +# OPENSSL_LIBS to the -l directives required +# OPENSSL_LDFLAGS to the -L or -R flags required +# +# and calls ACTION-IF-FOUND or ACTION-IF-NOT-FOUND appropriately +# +# This macro sets OPENSSL_INCLUDES such that source files should use the +# openssl/ directory in include directives: +# +# #include +# +# LICENSE +# +# Copyright (c) 2009,2010 Zmanda Inc. +# Copyright (c) 2009,2010 Dustin J. Mitchell +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 11 + +AU_ALIAS([CHECK_SSL], [AX_CHECK_OPENSSL]) +AC_DEFUN([AX_CHECK_OPENSSL], [ + found=false + AC_ARG_WITH([openssl], + [AS_HELP_STRING([--with-openssl=DIR], + [root of the OpenSSL directory])], + [ + case "$withval" in + "" | y | ye | yes | n | no) + AC_MSG_ERROR([Invalid --with-openssl value]) + ;; + *) ssldirs="$withval" + ;; + esac + ], [ + # if pkg-config is installed and openssl has installed a .pc file, + # then use that information and don't search ssldirs + AC_CHECK_TOOL([PKG_CONFIG], [pkg-config]) + if test x"$PKG_CONFIG" != x""; then + OPENSSL_LDFLAGS=`$PKG_CONFIG openssl --libs-only-L 2>/dev/null` + if test $? = 0; then + OPENSSL_LIBS=`$PKG_CONFIG openssl --libs-only-l 2>/dev/null` + OPENSSL_INCLUDES=`$PKG_CONFIG openssl --cflags-only-I 2>/dev/null` + found=true + fi + fi + + # no such luck; use some default ssldirs + if ! $found; then + ssldirs="/usr/local/ssl /usr/lib/ssl /usr/ssl /usr/pkg /usr/local /usr" + fi + ] + ) + + + # note that we #include , so the OpenSSL headers have to be in + # an 'openssl' subdirectory + + if ! $found; then + OPENSSL_INCLUDES= + for ssldir in $ssldirs; do + AC_MSG_CHECKING([for include/openssl/ssl.h in $ssldir]) + if test -f "$ssldir/include/openssl/ssl.h"; then + OPENSSL_INCLUDES="-I$ssldir/include" + OPENSSL_LDFLAGS="-L$ssldir/lib" + OPENSSL_LIBS="-lssl -lcrypto" + found=true + AC_MSG_RESULT([yes]) + break + else + AC_MSG_RESULT([no]) + fi + done + + # if the file wasn't found, well, go ahead and try the link anyway -- maybe + # it will just work! + fi + + # try the preprocessor and linker with our new flags, + # being careful not to pollute the global LIBS, LDFLAGS, and CPPFLAGS + + AC_MSG_CHECKING([whether compiling and linking against OpenSSL works]) + echo "Trying link with OPENSSL_LDFLAGS=$OPENSSL_LDFLAGS;" \ + "OPENSSL_LIBS=$OPENSSL_LIBS; OPENSSL_INCLUDES=$OPENSSL_INCLUDES" >&AS_MESSAGE_LOG_FD + + save_LIBS="$LIBS" + save_LDFLAGS="$LDFLAGS" + save_CPPFLAGS="$CPPFLAGS" + LDFLAGS="$LDFLAGS $OPENSSL_LDFLAGS" + LIBS="$OPENSSL_LIBS $LIBS" + CPPFLAGS="$OPENSSL_INCLUDES $CPPFLAGS" + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([#include ], [SSL_new(NULL)])], + [ + AC_MSG_RESULT([yes]) + $1 + ], [ + AC_MSG_RESULT([no]) + $2 + ]) + CPPFLAGS="$save_CPPFLAGS" + LDFLAGS="$save_LDFLAGS" + LIBS="$save_LIBS" + + AC_SUBST([OPENSSL_INCLUDES]) + AC_SUBST([OPENSSL_LIBS]) + AC_SUBST([OPENSSL_LDFLAGS]) +]) From 587923df9a2979dbe4db06deb7cc74833633bb7f Mon Sep 17 00:00:00 2001 From: MrIron Date: Fri, 22 May 2026 13:27:45 +0200 Subject: [PATCH 20/20] Reload TLS files on rehash --- ircd/s_conf.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ircd/s_conf.c b/ircd/s_conf.c index d9d3d25f..637c3bf0 100644 --- a/ircd/s_conf.c +++ b/ircd/s_conf.c @@ -40,6 +40,7 @@ #include "ircd_reply.h" #include "ircd_snprintf.h" #include "ircd_string.h" +#include "ircd_tls.h" #include "list.h" #include "listener.h" #include "match.h" @@ -1001,6 +1002,7 @@ int rehash(struct Client *cptr, int sig) restart_resolver(); log_reopen(); /* reopen log files */ + ircd_tls_init(); auth_close_unused(); close_listeners();