From 7fbfc390d32f9acc3192d011b813f66f35370faa Mon Sep 17 00:00:00 2001 From: pk910 Date: Sun, 8 Nov 2015 19:11:56 +0100 Subject: [PATCH] added basic ssl support to ircu --- configure.in | 18 ++ include/client.h | 12 ++ include/listener.h | 5 + include/s_bsd.h | 1 + include/s_conf.h | 5 + include/ssl.h | 97 +++++++++++ ircd/Makefile.in | 3 +- ircd/ircd_lexer.l | 4 + ircd/ircd_parser.y | 51 +++++- ircd/listener.c | 18 +- ircd/s_auth.c | 5 +- ircd/s_bsd.c | 67 +++++++- ircd/s_user.c | 18 +- ircd/send.c | 4 + ircd/ssl.c | 416 +++++++++++++++++++++++++++++++++++++++++++++ 15 files changed, 711 insertions(+), 13 deletions(-) create mode 100644 include/ssl.h create mode 100644 ircd/ssl.c diff --git a/configure.in b/configure.in index e07e2b3..d4205cf 100644 --- a/configure.in +++ b/configure.in @@ -226,6 +226,24 @@ else fi AC_SUBST(ENGINE_C) +have_gnutls="no" +AC_CHECK_LIB(gnutls, gnutls_init, [ + AC_CHECK_HEADERS(gnutls/gnutls.h, [ + LIBS="$LIBS -lgnutls" + have_gnutls="yes" + ]) +]) +if test x"$have_gnutls" = xno; then + if test x$is_win32 = xyes ; then + openssl_deps="-lcrypto -lgdi32" + else + openssl_deps="-lcrypto" + fi + AC_CHECK_LIB([ssl],[SSL_library_init], [ + LIBS="$LIBS -lssl $openssl_deps" + ], [], $openssl_deps) +fi + dnl Now look for --enable-debug AC_MSG_CHECKING([whether to enable debug mode]) AC_ARG_ENABLE([debug], diff --git a/include/client.h b/include/client.h index f93a3ee..a071383 100644 --- a/include/client.h +++ b/include/client.h @@ -56,6 +56,7 @@ struct Whowas; struct hostent; struct Privs; struct AuthRequest; +struct SSLConnection; /* * Structures @@ -166,6 +167,7 @@ enum Flag FLAG_DEBUG, /**< send global debug/anti-hack info */ FLAG_ACCOUNT, /**< account name has been set */ FLAG_HIDDENHOST, /**< user's host is hidden */ + FLAG_SSLCONN, /**< SSL Connection */ FLAG_LAST_FLAG, /**< number of flags */ FLAG_LOCAL_UMODES = FLAG_LOCOP, /**< First local mode flag */ FLAG_GLOBAL_UMODES = FLAG_OPER /**< First global mode flag */ @@ -231,6 +233,7 @@ struct Connection struct CapSet con_capab; /**< Client capabilities (from us) */ struct CapSet con_active; /**< Active capabilities (to us) */ struct AuthRequest* con_auth; /**< Auth request for client */ + struct SSLConnection* con_ssl; /**< SSL connection for client */ }; /** Magic constant to identify valid Connection structures. */ @@ -457,6 +460,8 @@ struct Client { #define con_active(con) (&(con)->con_active) /** Get the auth request for the connection. */ #define con_auth(con) ((con)->con_auth) +/** Get the ssl connection for the connection. */ +#define con_ssl(con) ((con)->con_ssl) #define STAT_CONNECTING 0x001 /**< connecting to another server */ #define STAT_HANDSHAKE 0x002 /**< pass - server sent */ @@ -581,6 +586,8 @@ struct Client { #define IsAccount(x) HasFlag(x, FLAG_ACCOUNT) /** Return non-zero if the client has set mode +x (hidden host). */ #define IsHiddenHost(x) HasFlag(x, FLAG_HIDDENHOST) +/** Return non-zero if the client has set mode +S (SSL Connection). */ +#define IsSSLConn(x) HasFlag(x, FLAG_SSLCONN) /** Return non-zero if the client has an active PING request. */ #define IsPingSent(x) HasFlag(x, FLAG_PINGSENT) @@ -627,6 +634,9 @@ struct Client { #define SetAccount(x) SetFlag(x, FLAG_ACCOUNT) /** Mark a client as having mode +x (hidden host). */ #define SetHiddenHost(x) SetFlag(x, FLAG_HIDDENHOST) +/** Mark a client as having mode +S (SSL Connection). */ +#define SetSSLConn(x) SetFlag(x, FLAG_SSLCONN) + /** Mark a client as having a pending PING. */ #define SetPingSent(x) SetFlag(x, FLAG_PINGSENT) @@ -660,6 +670,8 @@ struct Client { #define ClearServNotice(x) ClrFlag(x, FLAG_SERVNOTICE) /** Remove mode +x (hidden host) from the client. */ #define ClearHiddenHost(x) ClrFlag(x, FLAG_HIDDENHOST) +/** Remove mode +S (SSL Connection) from the client. */ +#define ClearSSLConn(x) ClrFlag(x, FLAG_SSLCONN) /** Clear the client's pending PING flag. */ #define ClearPingSent(x) ClrFlag(x, FLAG_PINGSENT) /** Clear the client's HUB flag. */ diff --git a/include/listener.h b/include/listener.h index 47dfff1..f2fcd00 100644 --- a/include/listener.h +++ b/include/listener.h @@ -40,6 +40,7 @@ struct Client; struct StatDesc; +struct SSLListener; enum ListenerFlag { /** Port is currently accepting connections. */ @@ -52,6 +53,8 @@ enum ListenerFlag { LISTEN_IPV4, /** Port listens for IPv6 connections. */ LISTEN_IPV6, + /** Port listens for SSL connections. */ + LISTEN_SSL, /** Sentinel for counting listener flags. */ LISTEN_LAST_FLAG }; @@ -72,10 +75,12 @@ struct Listener { struct irc_in_addr mask; /**< listener hostmask */ struct Socket socket_v4; /**< describe IPv4 socket to event system */ struct Socket socket_v6; /**< describe IPv6 socket to event system */ + struct SSLListener* ssl_listener; /**< ssl listener if listening for ssl connections */ }; #define listener_server(LISTENER) FlagHas(&(LISTENER)->flags, LISTEN_SERVER) #define listener_active(LISTENER) FlagHas(&(LISTENER)->flags, LISTEN_ACTIVE) +#define listener_ssl(LISTENER) FlagHas(&(LISTENER)->flags, LISTEN_SSL) extern void add_listener(int port, const char* vaddr_ip, const char* mask, diff --git a/include/s_bsd.h b/include/s_bsd.h index 8b2231d..acb24b7 100644 --- a/include/s_bsd.h +++ b/include/s_bsd.h @@ -59,6 +59,7 @@ extern struct irc_sockaddr VirtualHost_dns_v6; /* * Proto types */ +extern int completed_connection(struct Client* cptr); extern unsigned int deliver_it(struct Client *cptr, struct MsgQ *buf); extern int connect_server(struct ConfItem* aconf, struct Client* by); extern int net_close_unregistered_connections(struct Client* source); diff --git a/include/s_conf.h b/include/s_conf.h index 301e9ab..35ba119 100644 --- a/include/s_conf.h +++ b/include/s_conf.h @@ -65,6 +65,7 @@ struct ConfItem time_t hold; /**< Earliest time to attempt an outbound connect on this ConfItem. */ int dns_pending; /**< A dns request is pending. */ + int usessl; /**< establish SSL connection */ int flags; /**< Additional modifiers for item. */ int addrbits; /**< Number of bits valid in ConfItem::address. */ struct Privs privs; /**< Privileges for opers. */ @@ -102,6 +103,10 @@ struct LocalConf { char* location1; /**< First line of location information. */ char* location2; /**< Second line of location information. */ char* contact; /**< Admin contact information. */ + + char* sslcertfile; /**< SSL certificate file. */ + char* sslkeyfile; /**< SSL private key file. */ + char* sslcafile; /**< SSL CA file. */ }; enum { diff --git a/include/ssl.h b/include/ssl.h new file mode 100644 index 0000000..dc910b1 --- /dev/null +++ b/include/ssl.h @@ -0,0 +1,97 @@ +/** @file ssl.h + * @brief Declarations for ssl handler. + * @version $Id$ + */ +#ifndef INCLUDED_ssl_h +#define INCLUDED_ssl_h +#include "ircd_osdep.h" + +enum SSLFlag { + SSLFLAG_INCOMING, + SSLFLAG_OUTGOING, + SSLFLAG_READY, + SSLFLAG_HANDSHAKE, + SSLFLAG_HANDSHAKE_R, + SSLFLAG_HANDSHAKE_W, + + SSLFLAG_LAST +}; + +/** Declare flagset type for ssl flags. */ +DECLARE_FLAGSET(SSLFlags, SSLFLAG_LAST); + +enum SSLDataType { + SSLData_Client +}; + +#if defined(HAVE_GNUTLS_GNUTLS_H) +#include + +struct SSLConnection { + struct SSLFlags flags; + gnutls_session_t session; + gnutls_certificate_client_credentials credentials; +}; + +struct SSLListener { + struct SSLFlags flags; + gnutls_priority_t priority; + gnutls_certificate_credentials_t credentials; +}; + +#elif defined(HAVE_OPENSSL_SSL_H) +#include +#include +#include + +struct SSLConnection { + struct SSLFlags flags; + SSL *session; +}; + +struct SSLOutConnection { + struct SSLFlags flags; + SSL *session; + SSL_CTX *context; +}; + +struct SSLListener { + struct SSLFlags flags; + + SSL *listener; + SSL_CTX *context; +}; + +#else + +struct SSLConnection { + struct SSLFlags flags; + //just unused +}; + +struct SSLListener { + struct SSLFlags flags; + //just unused +}; +#endif + +#define ssl_handshake(x) (FlagHas(&(x)->flags, SSLFLAG_HANDSHAKE)) +#define ssl_wantwrite(x) (FlagHas(&(x)->flags, SSLFLAG_HANDSHAKE_W)) +#define ssl_wantread(x) (FlagHas(&(x)->flags, SSLFLAG_HANDSHAKE_R)) + + +extern void ssl_free_connection(struct SSLConnection *connection); +extern void ssl_free_listener(struct SSLListener *listener); + +extern struct SSLListener *ssl_create_listener(); +extern struct SSLConnection *ssl_create_connect(int fd, void *data, enum SSLDataType datatype); + +extern struct SSLConnection *ssl_start_handshake_listener(struct SSLListener *listener, int fd, void *data, enum SSLDataType datatype); +extern void ssl_start_handshake_connect(struct SSLConnection *connection); + +IOResult ssl_recv_decrypt(struct SSLConnection *connection, char *buf, unsigned int buflen, unsigned int *len); +IOResult ssl_send_encrypt(struct SSLConnection *connection, struct MsgQ* buf, unsigned int *count_in, unsigned int *count_out); +IOResult ssl_send_encrypt_plain(struct SSLConnection *connection, char *buf, int len); +extern int ssl_connection_flush(struct SSLConnection *connection); + +#endif /* INCLUDED_parse_h */ diff --git a/ircd/Makefile.in b/ircd/Makefile.in index 01b69ca..f8867cd 100644 --- a/ircd/Makefile.in +++ b/ircd/Makefile.in @@ -210,7 +210,8 @@ IRCD_SRC = \ s_stats.c \ s_user.c \ send.c \ - uping.c \ + ssl.c \ + uping.c \ userload.c \ whocmds.c \ whowas.c \ diff --git a/ircd/ircd_lexer.l b/ircd/ircd_lexer.l index 78818e8..c149a63 100644 --- a/ircd/ircd_lexer.l +++ b/ircd/ircd_lexer.l @@ -105,6 +105,10 @@ static struct lexer_token { TOKEN(AUTOCONNECT), TOKEN(PROGRAM), TOKEN(DNS), + TOKEN(SSL), + TOKEN(CERTFILE), + TOKEN(KEYFILE), + TOKEN(CAFILE), #undef TOKEN { "administrator", ADMIN }, { "apass_opmode", TPRIV_APASS_OPMODE }, diff --git a/ircd/ircd_parser.y b/ircd/ircd_parser.y index 64a112c..38d725a 100644 --- a/ircd/ircd_parser.y +++ b/ircd/ircd_parser.y @@ -175,6 +175,10 @@ static void free_slist(struct SLink **link) { %token PROGRAM %token TOK_IPV4 TOK_IPV6 %token DNS +%token SSL +%token CERTFILE +%token KEYFILE +%token CAFILE /* 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 @@ -199,7 +203,7 @@ static void free_slist(struct SLink **link) { %% /* Blocks in the config file... */ blocks: blocks block | block; -block: adminblock | generalblock | classblock | connectblock | +block: adminblock | generalblock | classblock | connectblock | sslblock | uworldblock | operblock | portblock | jupeblock | clientblock | killblock | cruleblock | motdblock | featuresblock | quarantineblock | pseudoblock | iauthblock | error ';'; @@ -406,6 +410,40 @@ admincontact: CONTACT '=' QSTRING ';' localConf.contact = $3; }; +sslblock: SSL +{ + MyFree(localConf.sslcertfile); + MyFree(localConf.sslkeyfile); + MyFree(localConf.sslcafile); + localConf.sslcertfile = localConf.sslkeyfile = localConf.sslcafile = NULL; +} +'{' sslitems '}' ';' +{ + if (localConf.sslcertfile == NULL) + DupString(localConf.sslcertfile, ""); + if (localConf.sslkeyfile == NULL) + DupString(localConf.sslkeyfile, ""); + if (localConf.sslcafile == NULL) + DupString(localConf.sslcafile, ""); +}; +sslitems: sslitems sslitem | sslitem; +sslitem: sslcertfile | sslkeyfile | sslcafile; +sslcertfile: CERTFILE '=' QSTRING ';' +{ + MyFree(localConf.sslcertfile); + localConf.sslcertfile = $3; +}; +sslkeyfile: KEYFILE '=' QSTRING ';' +{ + MyFree(localConf.sslkeyfile); + localConf.sslkeyfile = $3; +}; +sslcafile: CAFILE '=' QSTRING ';' +{ + MyFree(localConf.sslcafile); + localConf.sslcafile = $3; +}; + classblock: CLASS { tping = 90; } '{' classitems '}' ';' @@ -722,6 +760,7 @@ portblock: PORT '{' portitems '}' ';' { FlagSet(&flags_here, LISTEN_IPV6); break; } + if (link->flags & 65535) port = link->flags & 65535; add_listener(port, link->value.cp, pass, &flags_here); @@ -733,7 +772,7 @@ portblock: PORT '{' portitems '}' ';' { port = 0; }; portitems: portitem portitems | portitem; -portitem: portnumber | portvhost | portvhostnumber | portmask | portserver | porthidden; +portitem: portnumber | portvhost | portvhostnumber | portmask | portserver | portssl | porthidden; portnumber: PORT '=' address_family NUMBER ';' { if ($4 < 1 || $4 > 65535) { @@ -783,6 +822,14 @@ portserver: SERVER '=' YES ';' FlagClr(&listen_flags, LISTEN_SERVER); }; +portssl: SSL '=' YES ';' +{ + FlagSet(&listen_flags, LISTEN_SSL); +} | SSL '=' NO ';' +{ + FlagClr(&listen_flags, LISTEN_SSL); +}; + porthidden: HIDDEN '=' YES ';' { FlagSet(&listen_flags, LISTEN_HIDDEN); diff --git a/ircd/listener.c b/ircd/listener.c index a3c9e93..5339fd9 100644 --- a/ircd/listener.c +++ b/ircd/listener.c @@ -40,6 +40,7 @@ #include "s_misc.h" #include "s_stats.h" #include "send.h" +#include "ssl.h" #include "sys.h" /* MAXCLIENTS */ /* #include -- Now using assert in ircd_log.h */ @@ -131,7 +132,7 @@ void show_ports(struct Client* sptr, const struct StatDesc* sd, char* param) { struct Listener *listener = 0; - char flags[8]; + char flags[9]; int show_hidden = IsOper(sptr); int count = (IsOper(sptr) || MyUser(sptr)) ? 100 : 8; int port = 0; @@ -147,6 +148,11 @@ void show_ports(struct Client* sptr, const struct StatDesc* sd, continue; len = 0; flags[len++] = listener_server(listener) ? 'S' : 'C'; + + if (FlagHas(&listener->flags, LISTEN_SSL)) + { + flags[len++] = 'E'; + } if (FlagHas(&listener->flags, LISTEN_HIDDEN)) { if (!show_hidden) @@ -310,8 +316,18 @@ void add_listener(int port, const char* vhost_ip, const char* mask, { new_listener = 1; listener = make_listener(port, &vaddr); + + } memcpy(&listener->flags, flags, sizeof(listener->flags)); + + if(FlagHas(&listener->flags, LISTEN_SSL) && !listener->ssl_listener) { + listener->ssl_listener = ssl_create_listener(); + } else if(!FlagHas(&listener->flags, LISTEN_SSL) && listener->ssl_listener) { + ssl_free_listener(listener->ssl_listener); + listener->ssl_listener = NULL; + } + FlagSet(&listener->flags, LISTEN_ACTIVE); if (mask) ipmask_parse(mask, &listener->mask, &listener->mask_bits); diff --git a/ircd/s_auth.c b/ircd/s_auth.c index b3e993a..b2102ad 100644 --- a/ircd/s_auth.c +++ b/ircd/s_auth.c @@ -141,7 +141,10 @@ typedef enum { /** 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) + if(cli_connect(c)->con_ssl) \ + ssl_send_encrypt_plain(cli_connect(c)->con_ssl, HeaderMessages[(r)].message, HeaderMessages[(r)].length); \ + else \ + send(cli_fd(c), HeaderMessages[(r)].message, HeaderMessages[(r)].length, 0) /** Enumeration of IAuth connection flags. */ enum IAuthFlag diff --git a/ircd/s_bsd.c b/ircd/s_bsd.c index 7e3205a..5fd6d91 100644 --- a/ircd/s_bsd.c +++ b/ircd/s_bsd.c @@ -53,6 +53,7 @@ #include "s_misc.h" #include "s_user.h" #include "send.h" +#include "ssl.h" #include "struct.h" #include "sys.h" #include "uping.h" @@ -248,6 +249,7 @@ static int connect_inet(struct ConfItem* aconf, struct Client* cptr) cli_fd(cptr) = -1; return 0; } + if (!socket_add(&(cli_socket(cptr)), client_sock_callback, (void*) cli_connect(cptr), (result == IO_SUCCESS) ? SS_CONNECTED : SS_CONNECTING, @@ -258,6 +260,21 @@ static int connect_inet(struct ConfItem* aconf, struct Client* cptr) cli_fd(cptr) = -1; return 0; } + + if(aconf->usessl) { + struct SSLConnection *ssl = ssl_create_connect(cli_fd(cptr), cptr, SSLData_Client); + cli_connect(cptr)->con_ssl = ssl; + if(ssl_handshake(ssl)) { + unsigned int events = 0; + if(ssl_wantread(ssl)) + events |= SOCK_EVENT_READABLE; + if(ssl_wantwrite(ssl)) + events |= SOCK_EVENT_WRITABLE; + socket_events(&(cli_socket(cptr)), SOCK_ACTION_SET | events); + result = IO_BLOCKED; + } + } + cli_freeflag(cptr) |= FREEFLAG_SOCKET; return 1; } @@ -274,9 +291,16 @@ unsigned int deliver_it(struct Client *cptr, struct MsgQ *buf) { unsigned int bytes_written = 0; unsigned int bytes_count = 0; + IOResult result; assert(0 != cptr); - switch (os_sendv_nonb(cli_fd(cptr), buf, &bytes_count, &bytes_written)) { + if(cli_connect(cptr)->con_ssl) { + result = ssl_send_encrypt(cli_connect(cptr)->con_ssl, buf, &bytes_count, &bytes_written); + } else { + result = os_sendv_nonb(cli_fd(cptr), buf, &bytes_count, &bytes_written); + } + + switch (result) { case IO_SUCCESS: ClrFlag(cptr, FLAG_BLOCKED); @@ -302,7 +326,7 @@ unsigned int deliver_it(struct Client *cptr, struct MsgQ *buf) * @param cptr Client to which we have connected, with all ConfItem structs attached. * @return Zero on failure (caller should exit_client()), non-zero on success. */ -static int completed_connection(struct Client* cptr) +int completed_connection(struct Client* cptr) { struct ConfItem *aconf; time_t newts; @@ -403,6 +427,11 @@ void close_connection(struct Client *cptr) else ServerStats->is_ni++; + if(cli_connect(cptr)->con_ssl) { + ssl_free_connection(cli_connect(cptr)->con_ssl); + cli_connect(cptr)->con_ssl = NULL; + } + if (-1 < cli_fd(cptr)) { flush_connections(cptr); LocalClientArray[cli_fd(cptr)] = 0; @@ -540,8 +569,20 @@ void add_connection(struct Listener* listener, int fd) { ++listener->ref_count; 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(listener_ssl(listener)) { + struct Connection* con = cli_connect(new_client); + con->con_ssl = ssl_start_handshake_listener(listener->ssl_listener, fd, new_client, SSLData_Client); + unsigned int events = 0; + if(ssl_wantread(con->con_ssl)) + events |= SOCK_EVENT_READABLE; + if(ssl_wantwrite(con->con_ssl)) + events |= SOCK_EVENT_WRITABLE; + socket_events(&(cli_socket(new_client)), SOCK_ACTION_SET | events); + } else { + /* if we've made it this far we can put the client on the auth query pile */ + start_auth(new_client); + } } /** Determines whether to tell the events engine we're interested in @@ -577,7 +618,16 @@ static int read_packet(struct Client *cptr, int socket_ready) if (socket_ready && !(IsUser(cptr) && DBufLength(&(cli_recvQ(cptr))) > feature_int(FEAT_CLIENT_FLOOD))) { - switch (os_recv_nonb(cli_fd(cptr), readbuf, sizeof(readbuf), &length)) { + + /* Handle SSL Sockets + */ + int recvret; + if(cli_connect(cptr)->con_ssl) { + recvret = ssl_recv_decrypt(cli_connect(cptr)->con_ssl, readbuf, sizeof(readbuf), &length); + } else { + recvret = os_recv_nonb(cli_fd(cptr), readbuf, sizeof(readbuf), &length); + } + switch (recvret) { case IO_SUCCESS: if (length) { @@ -596,7 +646,7 @@ static int read_packet(struct Client *cptr, int socket_ready) return 0; } } - + /* * For server connections, we process as many as we can without * worrying about the time of day or anything :) @@ -855,7 +905,10 @@ static void client_sock_callback(struct Event* ev) break; case ET_CONNECT: /* socket connection completed */ - if (!completed_connection(cptr) || IsDead(cptr)) + if(cli_connect(cptr)->con_ssl) { + ssl_start_handshake_connect(cli_connect(cptr)->con_ssl); + } + else if (!completed_connection(cptr) || IsDead(cptr)) fallback = cli_info(cptr); break; diff --git a/ircd/s_user.c b/ircd/s_user.c index 1f0e0d9..632e185 100644 --- a/ircd/s_user.c +++ b/ircd/s_user.c @@ -57,6 +57,7 @@ #include "s_misc.h" #include "s_serv.h" /* max_client_count */ #include "send.h" +#include "ssl.h" #include "struct.h" #include "supported.h" #include "sys.h" @@ -395,6 +396,10 @@ int register_user(struct Client *cptr, struct Client *sptr) cli_info(sptr), NumNick(cptr) /* two %s's */); IPcheck_connect_succeeded(sptr); + + if(cli_connect(sptr)->con_ssl) { + SetSSLConn(sptr); + } } else { struct Client *acptr = user->server; @@ -499,7 +504,8 @@ static const struct UserMode { { FLAG_CHSERV, 'k' }, { FLAG_DEBUG, 'g' }, { FLAG_ACCOUNT, 'r' }, - { FLAG_HIDDENHOST, 'x' } + { FLAG_HIDDENHOST, 'x' }, + { FLAG_SSLCONN, 'S' } }; /** Length of #userModeList. */ @@ -1076,6 +1082,12 @@ int set_user_mode(struct Client *cptr, struct Client *sptr, int parc, case 'x': if (what == MODE_ADD) do_host_hiding = 1; + case 'S': + if (what == MODE_ADD) + SetSSLConn(sptr); + else + ClearSSLConn(sptr); + break; break; case 'r': if (*(p + 1) && (what == MODE_ADD)) { @@ -1102,6 +1114,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_SSLCONN) && IsSSLConn(sptr)) + ClrFlag(sptr, FLAG_SSLCONN); + else if (FlagHas(&setflags, FLAG_SSLCONN) && !IsSSLConn(sptr)) + SetFlag(sptr, FLAG_SSLCONN); /* * new umode; servers can set it, local users cannot; * prevents users from /kick'ing or /mode -o'ing diff --git a/ircd/send.c b/ircd/send.c index 1afbaec..b9309b8 100644 --- a/ircd/send.c +++ b/ircd/send.c @@ -152,7 +152,10 @@ kill_highest_sendq(int servers_too) */ void flush_connections(struct Client* cptr) { + struct SSLConnection *ssl = cli_connect(cptr)->con_ssl; if (cptr) { + if(ssl) + ssl_connection_flush(ssl); send_queued(cptr); } else { @@ -161,6 +164,7 @@ void flush_connections(struct Client* cptr) assert(0 < MsgQLength(&(con_sendQ(con)))); send_queued(con_client(con)); } + ssl_connection_flush(NULL); } } diff --git a/ircd/ssl.c b/ircd/ssl.c new file mode 100644 index 0000000..e407bd4 --- /dev/null +++ b/ircd/ssl.c @@ -0,0 +1,416 @@ +/* + * IRC - Internet Relay Chat, ircd/ssl.c + * Copyright (C) 2015 pk910 (Philipp Kreil) + * + * 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 Implementation of functions for handling local clients. + * @version $Id$ + */ +#include "config.h" + +#include "client.h" +#include "ssl.h" +#include "class.h" +#include "ircd.h" +#include "ircd_features.h" +#include "ircd_log.h" +#include "ircd_reply.h" +#include "list.h" +#include "msgq.h" +#include "numeric.h" +#include "s_conf.h" +#include "s_debug.h" +#include "send.h" +#include "struct.h" + +/* #include -- Now using assert in ircd_log.h */ +#include + +#ifndef IOV_MAX +#define IOV_MAX 16 /**< minimum required length of an iovec array */ +#endif + +#if defined(HAVE_OPENSSL_SSL_H) + +static struct SSLPendingConections { + struct SSLConnection *connection; + struct SSLPendingConections *next; + + void *data; + enum SSLDataType datatype; +}; + +struct SSLPendingConections *firstPendingConection = NULL; +int ssl_is_initialized = 0; + +static void ssl_init() { + if(ssl_is_initialized) + return; + ssl_is_initialized = 1; + SSL_library_init(); + OpenSSL_add_all_algorithms(); /* load & register all cryptos, etc. */ + SSL_load_error_strings(); +} + +void ssl_free_connection(struct SSLConnection *connection) { + SSL_CTX *context = NULL; + if(FlagHas(&connection->flags, SSLFLAG_OUTGOING)) { + struct SSLOutConnection *outconn = (struct SSLOutConnection *)connection; + context = outconn->context; + } + SSL_shutdown(connection->session); + SSL_free(connection->session); + if(context) + SSL_CTX_free(context); + free(connection); +} + +void ssl_free_listener(struct SSLListener *listener) { + SSL_CTX_free(listener->context); + free(listener); +} + +static void ssl_handshake_completed(struct SSLConnection *connection, int success) { + struct SSLPendingConections *pending, *lastPending = NULL; + for(pending = firstPendingConection; pending; pending = pending->next) { + if(pending->connection == connection) { + if(lastPending) + lastPending->next = pending->next; + else + firstPendingConection = pending->next; + switch(pending->datatype) { + case SSLData_Client: { + struct Client *cptr = (struct Client *) pending->data; + if(success) { + if(FlagHas(&connection->flags, SSLFLAG_INCOMING)) + start_auth(cptr); + else if(!completed_connection(cptr)) + exit_client_msg(cptr, cptr, &me, "Registration failed."); + } else + exit_client_msg(cptr, cptr, &me, "SSL Handshake failed."); + } + break; + } + free(pending); + } + lastPending = pending; + } +} + +static int ssl_handshake_outgoing(struct SSLConnection *connection) { + int ret = SSL_do_handshake(connection->session); + FlagClr(&connection->flags, SSLFLAG_HANDSHAKE_R); + FlagClr(&connection->flags, SSLFLAG_HANDSHAKE_W); + + switch(SSL_get_error(connection->session, ret)) { + case SSL_ERROR_NONE: + FlagClr(&connection->flags, SSLFLAG_HANDSHAKE); + FlagSet(&connection->flags, SSLFLAG_READY); + + ssl_handshake_completed(connection, 1); + break; + case SSL_ERROR_WANT_READ: + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_R); + break; + case SSL_ERROR_WANT_WRITE: + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_W); + break; + default: + + break; + } +} + +struct SSLConnection *ssl_create_connect(int fd, void *data, enum SSLDataType datatype) { + struct SSLOutConnection *connection = malloc(sizeof(*connection)); + struct SSLConnection *sslconn = (struct SSLConnection *)connection; + struct SSLPendingConections *pending = NULL; + + if(!connection) + return NULL; + + if(!ssl_is_initialized) + ssl_init(); + + connection->context = SSL_CTX_new(SSLv23_client_method()); + if(!connection->context) { + goto ssl_create_connect_failed; + } + connection->session = SSL_new(connection->context); + if(!connection->session) { + goto ssl_create_connect_failed; + } + if(!SSL_set_fd(connection->session, fd)) { + goto ssl_create_connect_failed; + } + SSL_set_connect_state(connection->session); + FlagSet(&connection->flags, SSLFLAG_OUTGOING); + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE); + + pending = malloc(sizeof(*pending)); + if(!pending) { + goto ssl_create_connect_failed; + } + pending->connection = connection; + pending->next = firstPendingConection; + firstPendingConection = pending; + + pending->data = data; + pending->datatype = datatype; + + return sslconn; +ssl_create_connect_failed: + free(connection); + return NULL; +} + +void ssl_start_handshake_connect(struct SSLConnection *connection) { + ssl_handshake_outgoing(connection); +} + +struct SSLListener *ssl_create_listener() { + if(!ssl_is_initialized) + ssl_init(); + + struct SSLListener *listener = calloc(1, sizeof(*listener)); + listener->context = SSL_CTX_new(SSLv23_server_method()); + if(!listener->context) { + goto ssl_create_listener_failed; + } + + char *certfile = conf_get_local()->sslcertfile; + char *keyfile = conf_get_local()->sslkeyfile; + char *cafile = conf_get_local()->sslcafile; + + if(!certfile) { + goto ssl_create_listener_failed; + } + if(!keyfile) { + keyfile = certfile; + } + + /* load certificate */ + if(SSL_CTX_use_certificate_file(listener->context, certfile, SSL_FILETYPE_PEM) <= 0) { + goto ssl_create_listener_failed; + } + /* load keyfile */ + if(SSL_CTX_use_PrivateKey_file(listener->context, keyfile, SSL_FILETYPE_PEM) <= 0) { + goto ssl_create_listener_failed; + } + /* check certificate and keyfile */ + if(!SSL_CTX_check_private_key(listener->context)) { + goto ssl_create_listener_failed; + } + /* load cafile */ + if(cafile && cafile[0] && SSL_CTX_load_verify_locations(listener->context, cafile, NULL) <= 0) { + goto ssl_create_listener_failed; + } + FlagSet(&listener->flags, SSLFLAG_READY); + return listener; +ssl_create_listener_failed: + free(listener); + return NULL; +} + +static int ssl_handshake_incoming(struct SSLConnection *connection) { + int result = SSL_accept(connection->session); + FlagClr(&connection->flags, SSLFLAG_HANDSHAKE_R); + FlagClr(&connection->flags, SSLFLAG_HANDSHAKE_W); + switch(SSL_get_error(connection->session, result)) { + case SSL_ERROR_NONE: + FlagClr(&connection->flags, SSLFLAG_HANDSHAKE); + FlagSet(&connection->flags, SSLFLAG_READY); + + ssl_handshake_completed(connection, 1); + return 0; + case SSL_ERROR_WANT_READ: + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_R); + return 1; + case SSL_ERROR_WANT_WRITE: + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_W); + return 1; + default: + //unset connection! + //Handshake error! + ssl_handshake_completed(connection, 0); + return 0; + } + return 0; +} + +struct SSLConnection *ssl_start_handshake_listener(struct SSLListener *listener, int fd, void *data, enum SSLDataType datatype) { + if(!listener) + return NULL; + struct SSLPendingConections *pending = NULL; + struct SSLConnection *connection = malloc(sizeof(*connection)); + connection->session = SSL_new(listener->context); + if(!connection->session) { + goto ssl_start_handshake_listener_failed; + } + if(!SSL_set_fd(connection->session, fd)) { + goto ssl_start_handshake_listener_failed; + } + FlagSet(&connection->flags, SSLFLAG_INCOMING); + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE); + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_R); + + pending = malloc(sizeof(*pending)); + if(!pending) { + goto ssl_start_handshake_listener_failed; + } + pending->connection = connection; + pending->next = firstPendingConection; + firstPendingConection = pending; + + pending->data = data; + pending->datatype = datatype; + + ssl_handshake_incoming(connection); + return connection; +ssl_start_handshake_listener_failed: + free(connection); + return NULL; +} + +IOResult ssl_recv_decrypt(struct SSLConnection *connection, char *buf, unsigned int buflen, unsigned int *len) { + if(FlagHas(&connection->flags, SSLFLAG_HANDSHAKE)) { + if(FlagHas(&connection->flags, SSLFLAG_INCOMING)) { + ssl_handshake_incoming(connection); + return IO_BLOCKED; + } + if(FlagHas(&connection->flags, SSLFLAG_OUTGOING)) { + ssl_handshake_outgoing(connection); + return IO_BLOCKED; + } + } + + *len = SSL_read(connection->session, buf, buflen); + FlagClr(&connection->flags, SSLFLAG_HANDSHAKE_R); + int err = SSL_get_error(connection->session, *len); + switch(err) { + case SSL_ERROR_NONE: + return IO_SUCCESS; + case SSL_ERROR_ZERO_RETURN: + return IO_FAILURE; + case SSL_ERROR_WANT_READ: + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_R); + return IO_BLOCKED; + case SSL_ERROR_WANT_WRITE: + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_W); + return IO_BLOCKED; + case SSL_ERROR_SYSCALL: + return IO_FAILURE; + default: + return IO_FAILURE; + } +} + +static ssize_t ssl_writev(SSL *ssl, const struct iovec *vector, int count) { + char *buffer; + register char *bp; + size_t bytes, to_copy; + int i; + + /* Find the total number of bytes to be written. */ + bytes = 0; + for (i = 0; i < count; ++i) + bytes += vector[i].iov_len; + + /* Allocate a temporary buffer to hold the data. */ + buffer = (char *) alloca (bytes); + + /* Copy the data into BUFFER. */ + to_copy = bytes; + bp = buffer; + for (i = 0; i < count; ++i) { + size_t copy = ((vector[i].iov_len) > (to_copy) ? (to_copy) : (vector[i].iov_len)); + memcpy ((void *) bp, (void *) vector[i].iov_base, copy); + bp += copy; + to_copy -= copy; + if (to_copy == 0) + break; + } + return SSL_write(ssl, buffer, bytes); +} + +IOResult ssl_send_encrypt_plain(struct SSLConnection *connection, char* buf, int len) { + return SSL_write(connection->session, buf, len); +} + +IOResult ssl_send_encrypt(struct SSLConnection *connection, struct MsgQ* buf, unsigned int *count_in, unsigned int *count_out) { + int res; + int count; + struct iovec iov[IOV_MAX]; + + assert(0 != buf); + assert(0 != count_in); + assert(0 != count_out); + + *count_in = 0; + count = msgq_mapiov(buf, iov, IOV_MAX, count_in); + res = ssl_writev(connection->session, iov, count); + + switch(SSL_get_error(connection->session, res)) { + case SSL_ERROR_NONE: + case SSL_ERROR_ZERO_RETURN: + *count_out = (unsigned) res; + return IO_SUCCESS; + case SSL_ERROR_WANT_READ: + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_R); + return IO_BLOCKED; + case SSL_ERROR_WANT_WRITE: + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_W); + return IO_BLOCKED; + default: + *count_out = 0; + return IO_FAILURE; + } +} + +int ssl_connection_flush(struct SSLConnection *connection) { + if(connection) { + if(ssl_handshake(connection)) { + if(FlagHas(&connection->flags, SSLFLAG_INCOMING)) { + return ssl_handshake_incoming(connection); + } + if(FlagHas(&connection->flags, SSLFLAG_OUTGOING)) { + return ssl_handshake_outgoing(connection); + } + } + } else { + struct SSLPendingConections *curr, *last = NULL, *next; + for(curr = firstPendingConection; curr; curr = next) { + next = curr->next; + if(!ssl_connection_flush(curr->connection)) { + // connection is already in auth process here, curr is freed! + continue; + } + last = curr; + } + } + return 0; +} + +#else +void ssl_free_connection(struct SSLConnection *connection) {} +void ssl_free_listener(struct SSLConnection *listener) {} +struct SSLListener *ssl_create_listener() { return NULL; } +struct SSLConnection *ssl_start_handshake_listener(struct SSLListener *listener, int fd, void *data, enum SSLDataType datatype) { return NULL; } +IOResult ssl_recv_decrypt(struct SSLConnection *connection, char *buf, int *len) { return IO_FAILURE; } +int ssl_connection_flush(struct SSLConnection *connection) { return 0; }; +#endif + -- 2.20.1