diff --git a/acconfig.h b/acconfig.h index 33f02000..e87197fb 100644 --- a/acconfig.h +++ b/acconfig.h @@ -36,3 +36,6 @@ #undef HAVE_GETTEXT #undef HAVE_LC_MESSAGES #undef HAVE_STPCPY + +/* SSL */ +#undef HAVE_OPENSSL diff --git a/configure.in b/configure.in index a778c4bf..d612bf04 100644 --- a/configure.in +++ b/configure.in @@ -112,7 +112,7 @@ if test "x$prefix" != "xNONE"; then fi AC_ARG_WITH(tests, -[ --with-glib2 Use GLIB 2.0 instead of 1.2], +[ --with-glib2 Use GLIB 2.0 instead of 1.2], if test x$withval = xyes; then want_glib2=yes else @@ -126,7 +126,7 @@ AC_ARG_WITH(tests, AC_ARG_WITH(perl-staticlib, [ --with-perl-staticlib Specify that we want to link perl libraries - statically in irssi, default is no], + statically in irssi, default is no], if test x$withval = xyes; then want_staticperllib=yes else @@ -141,7 +141,7 @@ AC_ARG_WITH(perl-staticlib, AC_ARG_WITH(perl-lib, [ --with-perl-lib=[site|vendor|DIR] Specify where to install the - Perl libraries for irssi, default is site], + Perl libraries for irssi, default is site], if test "x$withval" = xyes; then want_perl=yes elif test "x$withval" = xno; then @@ -170,8 +170,8 @@ AC_ARG_WITH(perl-lib, AC_ARG_WITH(perl, [ --with-perl[=yes|no|module] Build with Perl support - also specifies - if it should be built into main irssi binary - (static, default) or as module], + if it should be built into main irssi binary + (static, default) or as module], if test x$withval = xyes; then want_perl=static elif test x$withval = xstatic; then @@ -196,6 +196,62 @@ AC_ARG_ENABLE(ipv6, fi, want_ipv6=no) +dnl ** +dnl ** SSL Library checks (OpenSSL) +dnl ** + +AC_ARG_ENABLE(ssl, + [ --disable-ssl Turn on Secure Sockets Layer support [default=yes]],, + enable_ssl=yes) + +AC_ARG_WITH(openssl-includes, + [ --with-openssl-includes Specify location of OpenSSL header files], + [openssl_inc_prefix=-I$withval]) + +AC_ARG_WITH(openssl-libs, + [ --with-openssl-libs Specify location of OpenSSL libs], + [openssl_prefix=$withval], + [openssl_prefix=/usr/lib]) + +if test "x$enable_ssl" = xyes; then + ### + ### Check for OpenSSL + ### + save_CFLAGS=$CFLAGS; + CFLAGS="-lcrypto"; + + enable_openssl="no"; + OPENSSL_LDFLAGS=""; + AC_CHECK_LIB(ssl, SSL_read, + AC_CHECK_LIB(crypto, X509_new, + AC_CHECK_HEADERS(openssl/ssl.h openssl/err.h, + [ + enable_openssl="yes"; + OPENSSL_LDFLAGS="-lssl -lcrypto" + ], + AC_ERROR([Cannot find OpenSSL includes !])), + AC_ERROR([Cannot find libCrypto !])), + AC_ERROR([Cannot find libSSL !])) + CFLAGS=$save_CFLAGS + + if test "x$enable_openssl" = xyes; then + AC_DEFINE(HAVE_OPENSSL) + OPENSSL_LIBS="-L$openssl_prefix $OPENSSL_LDFLAGS" + OPENSSL_CFLAGS="$openssl_inc_prefix" + else + OPENSSL_LIBS= + OPENSSL_CFLAGS= + fi + + AC_SUBST(OPENSSL_CFLAGS) + AC_SUBST(OPENSSL_LIBS) + LIBS="$LIBS $OPENSSL_LIBS" + CFLAGS="$CFLAGS $OPENSSL_CFLAGS" +else + enable_openssl="no" +fi + + dnl ** dnl ** just some generic stuff... dnl ** @@ -830,7 +886,6 @@ fi echo "Building text frontend ..... : $text" echo "Building irssi bot ......... : $want_irssibot" echo "Building irssi proxy ....... : $want_irssiproxy" -echo "Building with IPv6 support . : $want_ipv6" if test "x$have_gmodule" = "xyes"; then echo "Building with module support : yes" else @@ -877,8 +932,12 @@ if test "x$want_perl" != "xno"; then echo " Anyway, installing perl to this directory should work just as well." fi fi - echo "Install prefix ............. : $prefix" +echo + +echo "Building with IPv6 support . : $want_ipv6" +echo "Building with SSL support .. : ${enable_openssl}" + echo echo "If there was any problems, read the INSTALL file." diff --git a/src/core/Makefile.am b/src/core/Makefile.am index f543f361..beee1098 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -30,6 +30,7 @@ libcore_a_SOURCES = \ net-nonblock.c \ net-sendbuffer.c \ network.c \ + network-openssl.c \ nicklist.c \ nickmatch-cache.c \ pidwait.c \ diff --git a/src/core/chat-commands.c b/src/core/chat-commands.c index 4bc931dc..aff0d8b7 100644 --- a/src/core/chat-commands.c +++ b/src/core/chat-commands.c @@ -88,12 +88,16 @@ static SERVER_CONNECT_REC *get_server_connect(const char *data, int *plus_addr, else if (g_hash_table_lookup(optlist, "4") != NULL) conn->family = AF_INET; + if(g_hash_table_lookup(optlist, "ssl") != NULL) + conn->use_ssl = TRUE; + if (g_hash_table_lookup(optlist, "!") != NULL) conn->no_autojoin_channels = TRUE; if (g_hash_table_lookup(optlist, "noproxy") != NULL) g_free_and_null(conn->proxy); + *rawlog_file = g_strdup(g_hash_table_lookup(optlist, "rawlog")); host = g_hash_table_lookup(optlist, "host"); @@ -108,7 +112,7 @@ static SERVER_CONNECT_REC *get_server_connect(const char *data, int *plus_addr, return conn; } -/* SYNTAX: CONNECT [-4 | -6] [-ircnet ] [-host ] +/* SYNTAX: CONNECT [-4 | -6] [-ssl] [-ircnet ] [-host ]
| [ [ []]] */ static void cmd_connect(const char *data) { @@ -209,7 +213,7 @@ static void sig_default_command_server(const char *data, SERVER_REC *server, signal_emit("command server connect", 3, data, server, item); } -/* SYNTAX: SERVER [-4 | -6] [-ircnet ] [-host ] +/* SYNTAX: SERVER [-4 | -6] [-ssl] [-ircnet ] [-host ] [+]
| [ [ []]] */ static void cmd_server_connect(const char *data, SERVER_REC *server) { @@ -439,7 +443,7 @@ void chat_commands_init(void) signal_add("default command server", (SIGNAL_FUNC) sig_default_command_server); - command_set_options("connect", "4 6 !! +host noproxy -rawlog"); + command_set_options("connect", "4 6 !! ssl +host noproxy -rawlog"); command_set_options("join", "invite"); command_set_options("msg", "channel nick"); } diff --git a/src/core/network-openssl.c b/src/core/network-openssl.c new file mode 100644 index 00000000..7a4e974d --- /dev/null +++ b/src/core/network-openssl.c @@ -0,0 +1,291 @@ +/* + network-ssl.c : SSL support + + Copyright (C) 2002 vjt + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "network.h" + +#ifdef HAVE_OPENSSL + +#include +#include +#include +#include +#include + +/* ssl read */ +GIOError irssi_ssl_read(GIOChannel *, gchar *, guint, guint *); +/* ssl write */ +GIOError irssi_ssl_write(GIOChannel *, gchar *, guint, guint*); +/* ssl seek */ +GIOError irssi_ssl_seek(GIOChannel *, gint, GSeekType); +/* ssl close */ +void irssi_ssl_close(GIOChannel *); +/* ssl create watch */ +guint irssi_ssl_create_watch(GIOChannel *, gint, GIOCondition, GIOFunc, gpointer, GDestroyNotify); +/* ssl free */ +void irssi_ssl_free(GIOChannel *); + +/* ssl i/o channel object */ +typedef struct +{ + GIOChannel pad; + gint fd; + GIOChannel *giochan; + SSL *ssl; + X509 *cert; +} GIOSSLChannel; + +/* ssl function pointers */ +GIOFuncs irssi_ssl_channel_funcs = +{ + irssi_ssl_read, + irssi_ssl_write, + irssi_ssl_seek, + irssi_ssl_close, + irssi_ssl_create_watch, + irssi_ssl_free +}; + +SSL_CTX *ssl_ctx = NULL; + +#ifdef G_CAN_INLINE +G_INLINE_FUNC +#endif +gint ssl_errno(gint e) +{ + switch(e) + { + case EINVAL: + return G_IO_ERROR_INVAL; + case EINTR: + case EAGAIN: + return G_IO_ERROR_AGAIN; + default: + return G_IO_ERROR_INVAL; + } + /*UNREACH*/ + return -1; +} + +gboolean irssi_ssl_cert_step(GIOSSLChannel *chan) +{ + gint err; + switch(err = SSL_do_handshake(chan->ssl)) + { + case 1: + if(!(chan->cert = SSL_get_peer_certificate(chan->ssl))) + { + g_warning("SSL server supplied no certificate"); + return G_IO_ERROR_INVAL; + } + return G_IO_ERROR_NONE; + default: + if(SSL_get_error(chan->ssl, err) == SSL_ERROR_WANT_READ) + return G_IO_ERROR_AGAIN; + return ssl_errno(errno); + } + /*UNREACH*/ + return -1; +} + +GIOError irssi_ssl_read(GIOChannel *handle, gchar *buf, guint len, guint *ret) +{ + GIOSSLChannel *chan = (GIOSSLChannel *)handle; + gint err; + + if(chan->cert == NULL) + { + gint cert_err = irssi_ssl_cert_step(chan); + if(cert_err != G_IO_ERROR_NONE) + return cert_err; + } + + err = SSL_read(chan->ssl, buf, len); + if(err < 0) + { + *ret = 0; + if(SSL_get_error(chan->ssl, err) == SSL_ERROR_WANT_READ) + return G_IO_ERROR_AGAIN; + return ssl_errno(errno); + } + else + { + *ret = err; + return G_IO_ERROR_NONE; + } + /*UNREACH*/ + return -1; +} + +GIOError irssi_ssl_write(GIOChannel *handle, gchar *buf, guint len, guint *ret) +{ + GIOSSLChannel *chan = (GIOSSLChannel *)handle; + gint err; + + if(chan->cert == NULL) + { + gint cert_err = irssi_ssl_cert_step(chan); + if(cert_err != G_IO_ERROR_NONE) + return cert_err; + } + + + err = SSL_write(chan->ssl, (const char *)buf, len); + if(err < 0) + { + *ret = 0; + if(SSL_get_error(chan->ssl, err) == SSL_ERROR_WANT_READ) + return G_IO_ERROR_AGAIN; + return ssl_errno(errno); + } + else + { + *ret = err; + return G_IO_ERROR_NONE; + } + /*UNREACH*/ + return -1; +} + +GIOError irssi_ssl_seek(GIOChannel *handle, gint offset, GSeekType type) +{ + GIOSSLChannel *chan = (GIOSSLChannel *)handle; + GIOError e; + e = g_io_channel_seek(chan->giochan, offset, type); + return (e == G_IO_ERROR_NONE) ? G_IO_ERROR_NONE : G_IO_ERROR_INVAL; +} + +void irssi_ssl_close(GIOChannel *handle) +{ + GIOSSLChannel *chan = (GIOSSLChannel *)handle; + g_io_channel_close(chan->giochan); +} + +guint irssi_ssl_create_watch(GIOChannel *handle, gint priority, GIOCondition cond, + GIOFunc func, gpointer data, GDestroyNotify notify) +{ + GIOSSLChannel *chan = (GIOSSLChannel *)handle; + return chan->giochan->funcs->io_add_watch(handle, priority, cond, func, data, notify); +} + +void irssi_ssl_free(GIOChannel *handle) +{ + GIOSSLChannel *chan = (GIOSSLChannel *)handle; + g_io_channel_unref(chan->giochan); + SSL_free(chan->ssl); + g_free(chan); +} + +gboolean irssi_ssl_init(void) +{ + SSL_library_init(); + SSL_load_error_strings(); + + ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + if(!ssl_ctx) + { + g_error("Initialization of the SSL library failed"); + return FALSE; + } + + return TRUE; + +} + +GIOChannel *irssi_ssl_get_iochannel(GIOChannel *handle) +{ + GIOSSLChannel *chan; + GIOChannel *gchan; + int err, fd; + SSL *ssl; + X509 *cert = NULL; + + g_return_val_if_fail(handle != NULL, NULL); + + if(!ssl_ctx && !irssi_ssl_init()) + return NULL; + + if(!(fd = g_io_channel_unix_get_fd(handle))) + return NULL; + + if(!(ssl = SSL_new(ssl_ctx))) + { + g_warning("Failed to allocate SSL structure"); + return NULL; + } + + if(!(err = SSL_set_fd(ssl, fd))) + { + g_warning("Failed to associate socket to SSL stream"); + return NULL; + } + + if((err = SSL_connect(ssl)) <= 0) + { + switch(err = SSL_get_error(ssl, err)) + { + case SSL_ERROR_SYSCALL: + if(errno == EINTR || errno == EAGAIN) + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + break; + default: + return NULL; + } + } + else if(!(cert = SSL_get_peer_certificate(ssl))) + { + g_warning("SSL server supplied no certificate"); + return NULL; + } + else + X509_free(cert); + + chan = g_new0(GIOSSLChannel, 1); + chan->fd = fd; + chan->giochan = handle; + chan->ssl = ssl; + chan->cert = cert; + g_io_channel_ref(handle); + + gchan = (GIOChannel *)chan; + gchan->funcs = &irssi_ssl_channel_funcs; + g_io_channel_init(gchan); + + return gchan; +} + +GIOChannel *net_connect_ip_ssl(IPADDR *ip, int port, IPADDR *my_ip) +{ + GIOChannel *gret = net_connect_ip(ip, port, my_ip); + gret = irssi_ssl_get_iochannel(gret); + return gret; +} + +#else /* HAVE_OPENSSL */ + +GIOChannel *net_connect_ip_ssl(IPADDR *ip, int port, IPADDR *my_ip) +{ + g_warning("Connection failed: SSL support not enabled in this build."); + errno = ENOSYS; + return NULL; +} + +#endif /* ! HAVE_OPENSSL */ diff --git a/src/core/network.h b/src/core/network.h index 646bc05e..c6b08f9f 100644 --- a/src/core/network.h +++ b/src/core/network.h @@ -44,6 +44,8 @@ int net_ip_compare(IPADDR *ip1, IPADDR *ip2); /* Connect to socket */ GIOChannel *net_connect(const char *addr, int port, IPADDR *my_ip); +/* Connect to socket with ip address and SSL*/ +GIOChannel *net_connect_ip_ssl(IPADDR *ip, int port, IPADDR *my_ip); /* Connect to socket with ip address */ GIOChannel *net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip); /* Connect to named UNIX socket */ diff --git a/src/core/server-connect-rec.h b/src/core/server-connect-rec.h index 3613759e..ce1e48a4 100644 --- a/src/core/server-connect-rec.h +++ b/src/core/server-connect-rec.h @@ -29,5 +29,6 @@ GIOChannel *connect_handle; /* connect using this handle */ unsigned int reconnection:1; /* we're trying to reconnect */ unsigned int no_autojoin_channels:1; /* don't autojoin any channels */ unsigned int unix_socket:1; /* Connect using named unix socket */ +unsigned int use_ssl:1; /* this connection uses SSL */ char *channels; char *away_reason; diff --git a/src/core/server-setup-rec.h b/src/core/server-setup-rec.h index f94ec9ae..d04fa1fe 100644 --- a/src/core/server-setup-rec.h +++ b/src/core/server-setup-rec.h @@ -18,5 +18,6 @@ unsigned int no_proxy:1; unsigned int last_failed:1; /* if last connection attempt failed */ unsigned int banned:1; /* if we're banned from this server */ unsigned int dns_error:1; /* DNS said the host doesn't exist */ +unsigned int use_ssl:1; /* this connection uses SSL */ GHashTable *module_data; diff --git a/src/core/servers-reconnect.c b/src/core/servers-reconnect.c index 93067b2b..eee3e843 100644 --- a/src/core/servers-reconnect.c +++ b/src/core/servers-reconnect.c @@ -164,6 +164,8 @@ server_connect_copy_skeleton(SERVER_CONNECT_REC *src, int connect_info) dest->away_reason = g_strdup(src->away_reason); dest->no_autojoin_channels = src->no_autojoin_channels; + dest->use_ssl = src->use_ssl; + return dest; } diff --git a/src/core/servers-setup.c b/src/core/servers-setup.c index d16f93a7..8ccfcc27 100644 --- a/src/core/servers-setup.c +++ b/src/core/servers-setup.c @@ -108,6 +108,8 @@ void server_setup_fill_reconn(SERVER_CONNECT_REC *conn, if (sserver->password != NULL && conn->password == NULL) conn->password = g_strdup(sserver->password); + conn->use_ssl = sserver->use_ssl; + signal_emit("server setup fill reconn", 2, conn, sserver); } @@ -391,6 +393,7 @@ static SERVER_SETUP_REC *server_setup_read(CONFIG_NODE *node) (g_strcasecmp(family, "inet") == 0 ? AF_INET : 0); rec->address = g_strdup(server); rec->password = g_strdup(config_node_get_str(node, "password", NULL)); + rec->use_ssl = config_node_get_bool(node, "use_ssl", FALSE); rec->port = port; rec->autoconnect = config_node_get_bool(node, "autoconnect", FALSE); rec->no_proxy = config_node_get_bool(node, "no_proxy", FALSE); @@ -420,6 +423,7 @@ static void server_setup_save(SERVER_SETUP_REC *rec) iconfig_node_set_int(node, "port", rec->port); iconfig_node_set_str(node, "password", rec->password); + iconfig_node_set_bool(node, "use_ssl", rec->use_ssl); iconfig_node_set_str(node, "own_host", rec->own_host); iconfig_node_set_str(node, "family", diff --git a/src/core/servers.c b/src/core/servers.c index f26481d5..6bd843af 100644 --- a/src/core/servers.c +++ b/src/core/servers.c @@ -178,13 +178,18 @@ static void server_real_connect(SERVER_REC *server, IPADDR *ip, server->connrec->own_ip4); port = server->connrec->proxy != NULL ? server->connrec->proxy_port : server->connrec->port; - handle = net_connect_ip(ip, port, own_ip); + handle = server->connrec->use_ssl ? + net_connect_ip_ssl(ip, port, own_ip) : + net_connect_ip(ip, port, own_ip); } else { handle = net_connect_unix(unix_socket); } if (handle == NULL) { /* failed */ + if (server->connrec->use_ssl && errno == ENOSYS) + server->no_reconnect = TRUE; + server->connection_lost = TRUE; server_connect_failed(server, g_strerror(errno)); } else { diff --git a/src/core/session.c b/src/core/session.c index 0f3d089c..004cd4f7 100644 --- a/src/core/session.c +++ b/src/core/session.c @@ -182,6 +182,8 @@ static void session_save_server(SERVER_REC *server, CONFIG_REC *config, config_node_set_str(config, node, "password", server->connrec->password); config_node_set_str(config, node, "nick", server->nick); + config_node_set_bool(config, node, "use_ssl", server->connrec->use_ssl); + handle = g_io_channel_unix_get_fd(net_sendbuffer_handle(server->handle)); config_node_set_int(config, node, "handle", handle); diff --git a/src/fe-common/core/fe-server.c b/src/fe-common/core/fe-server.c index db068f3b..06f46777 100644 --- a/src/fe-common/core/fe-server.c +++ b/src/fe-common/core/fe-server.c @@ -144,6 +144,9 @@ static void cmd_server_add(const char *data) else if (g_hash_table_lookup(optlist, "4")) rec->family = AF_INET; + if (g_hash_table_lookup(optlist, "ssl")) + rec->use_ssl = TRUE; + if (g_hash_table_lookup(optlist, "auto")) rec->autoconnect = TRUE; if (g_hash_table_lookup(optlist, "noauto")) rec->autoconnect = FALSE; if (g_hash_table_lookup(optlist, "proxy")) rec->no_proxy = FALSE; @@ -327,7 +330,7 @@ void fe_server_init(void) command_bind("server connect", NULL, (SIGNAL_FUNC) cmd_server_connect); command_bind("server add", NULL, (SIGNAL_FUNC) cmd_server_add); command_bind("server remove", NULL, (SIGNAL_FUNC) cmd_server_remove); - command_set_options("server add", "4 6 auto noauto proxy noproxy -host -port"); + command_set_options("server add", "4 6 ssl auto noauto proxy noproxy -host -port"); signal_add("server looking", (SIGNAL_FUNC) sig_server_looking); signal_add("server connecting", (SIGNAL_FUNC) sig_server_connecting); diff --git a/src/fe-common/irc/fe-events-numeric.c b/src/fe-common/irc/fe-events-numeric.c index 742b4100..a7977c03 100644 --- a/src/fe-common/irc/fe-events-numeric.c +++ b/src/fe-common/irc/fe-events-numeric.c @@ -917,6 +917,7 @@ void fe_events_numeric_init(void) signal_add("event 378", (SIGNAL_FUNC) event_whois_realhost); signal_add("event 377", (SIGNAL_FUNC) event_whois_usermode); signal_add("event 320", (SIGNAL_FUNC) event_whois_special); + signal_add("event 275", (SIGNAL_FUNC) event_whois_special); signal_add("event 314", (SIGNAL_FUNC) event_whowas); signal_add("event 317", (SIGNAL_FUNC) event_whois_idle); signal_add("event 318", (SIGNAL_FUNC) event_end_of_whois); @@ -1002,6 +1003,7 @@ void fe_events_numeric_deinit(void) signal_remove("event 378", (SIGNAL_FUNC) event_whois_realhost); signal_remove("event 377", (SIGNAL_FUNC) event_whois_usermode); signal_remove("event 320", (SIGNAL_FUNC) event_whois_special); + signal_remove("event 275", (SIGNAL_FUNC) event_whois_special); signal_remove("event 314", (SIGNAL_FUNC) event_whowas); signal_remove("event 317", (SIGNAL_FUNC) event_whois_idle); signal_remove("event 318", (SIGNAL_FUNC) event_end_of_whois);