irssi-import / bugs.irssi.org

bugs.irssi.org archive
https://github.com/irssi/irssi/issues
0 stars 0 forks source link

[PATCH] irssiproxy SSL support #645

Open irssibot opened 15 years ago

irssibot commented 15 years ago

Hi,

irssiproxy seems to lack support for SSL encryption (that'd be server-side). I've checked google for it, but as the only messages I've found about the topic were quite some years old; I assumed no one was currently working on it.

I've created this patch (on SVN/r5021) which adds SSL encryption to irssiproxy. (The proxy then acts as a SSL server, the client used to connect to it will need to support SSL.) It's been tested on Linux and Mac OS X. Unless someone stumbles upon bugs I've missed, maybe the patch could be merged. I know that adding a small wrapper might not be the most elegant solution, but taking a look at the existing irssi SSL code makes it appear pretty client centric; I assumed it might be more adequate to add this by changing the proxy code alone, not the basic network code.

Usage: (Apply by using patch < proxysslserver.diff in src/irc/proxy) It's enabled by compiling irssi SSL enabled; encryption then needs a server certificate and can be enabled network-wise. One can switch SSL on by adding :/path/to/certkey.pem to the irssiproxy_ports setting. e.g. /set irssiproxy_ports QuakeNet=7777:/home/user/.irssi/servercert.pem The certificate/key file must contain both cert&key, and be in PEM format. If one is familiar with PKI, obviously a proper cert can be used. For basic security, a simple cert/key can be made using the following command: openssl req -new -x509 -nodes -newkey rsa:2048 -keyout yourcert.pem -out yourcert.pem -days 365 (Obviously checking the cert for trustability must be disabled in this case on the client side.)

Regards,

Christian Sachs

irssibot commented 15 years ago

proxysslserver.diff

--- dump.orig.c 2009-02-21 19:18:15.000000000 +0100
+++ dump.c  2009-02-24 04:25:46.000000000 +0100
@@ -29,6 +29,42 @@
 #include "irc-channels.h"
 #include "irc-nicklist.h"
 #include "modes.h"
+#include "line-split.h"
+
+void proxy_send(CLIENT_REC *client, char *d, int l)
+{
+#ifdef HAVE_OPENSSL
+   if(client->listen->use_ssl) {
+       SSL_write(client->ssl, d, l);
+   } else 
+#endif
+       net_sendbuffer_send(client->handle, d, l);
+}
+
+int proxy_readline(CLIENT_REC *client, char **str)
+{
+#ifdef HAVE_OPENSSL
+   if(client->listen->use_ssl) {
+       char tmpbuf[2048];
+       int recvlen = 0;
+        
+       recvlen = SSL_read(client->ssl, tmpbuf, sizeof(tmpbuf));
+       if(recvlen > 0) {
+           return line_split(tmpbuf, recvlen, str, &client->handle->readbuffer);
+       } else {
+           int err;
+           err = SSL_get_error(client->ssl, recvlen);
+           /* READ/WRITE are not really errors, they just indicate that atm 
+              OpenSSL is waiting for more data */
+           if(err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
+               return line_split(tmpbuf, 0, str, &client->handle->readbuffer);
+           }
+           return recvlen; /* if any other error occurs, this will quit the connection */
+       }
+   } else 
+#endif
+       return net_sendbuffer_receive_line(client->handle, str, 1);
+}

 void proxy_outdata(CLIENT_REC *client, const char *data, ...)
 {
@@ -41,7 +77,7 @@
    va_start(args, data);

    str = g_strdup_vprintf(data, args);
-   net_sendbuffer_send(client->handle, str, strlen(str));
+   proxy_send(client, str, strlen(str));
    g_free(str);

    va_end(args);
@@ -65,7 +101,7 @@
        CLIENT_REC *rec = tmp->data;

        if (rec->connected && rec->server == server)
-           net_sendbuffer_send(rec->handle, str, len);
+           proxy_send(rec, str, len);
    }
    g_free(str);

--- listen.orig.c   2009-02-21 19:18:15.000000000 +0100
+++ listen.c    2009-02-25 02:17:19.000000000 +0100
@@ -47,6 +47,11 @@
    printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
          "Proxy: Client disconnected from %s", rec->host);

+#ifdef HAVE_OPENSSL
+   if(rec->listen->use_ssl) {
+       SSL_free(rec->ssl); 
+   }
+#endif
    g_free(rec->proxy_address);
    net_sendbuffer_destroy(rec->handle, TRUE);
    g_source_remove(rec->recv_tag);
@@ -126,6 +131,13 @@
            remove_client(client);
        } else {
            client->connected = TRUE;
+#ifdef HAVE_OPENSSL
+   if(client->listen->use_ssl) {
+       printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+             "Proxy: Client connected from %s using encryption %s and logged in!", client->host, SSL_get_cipher(client->ssl));
+   }
+#endif
+
            proxy_dump_data(client);
        }
    }
@@ -303,7 +315,7 @@
    g_return_if_fail(client != NULL);

    while (g_slist_find(proxy_clients, client) != NULL) {
-       ret = net_sendbuffer_receive_line(client->handle, &str, 1);
+       ret = proxy_readline(client, &str);
        if (ret == -1) {
            /* connection lost */
            remove_client(client);
@@ -343,6 +355,26 @@
    net_ip2host(&ip, host);
    sendbuf = net_sendbuffer_create(handle, 0);
    rec = g_new0(CLIENT_REC, 1);
+   
+#ifdef HAVE_OPENSSL
+   if(listen->use_ssl) {
+       rec->ssl = SSL_new(listen->ssl_ctx);
+       SSL_set_fd(rec->ssl, g_io_channel_unix_get_fd(handle));
+       int sslerror = SSL_accept(rec->ssl); /* handle error! */
+       if(sslerror <= 0) {
+           /* The Handshake might take longer and the client might not be ready yet
+              so if such an error occurs, we just ignore it, SSL_read and SSL_write
+              should continue with the handshake. */
+           if(SSL_get_error(rec->ssl, sslerror) != SSL_ERROR_WANT_READ) {
+               printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+                   "Proxy: An error occured while accepting SSL connection!");
+               g_free(rec);
+               return;  
+           }
+       }
+   }
+#endif
+       
    rec->listen = listen;
    rec->handle = sendbuf;
         rec->host = g_strdup(host);
@@ -361,8 +393,20 @@
    rec->listen->clients = g_slist_prepend(rec->listen->clients, rec);

         signal_emit("proxy client connected", 1, rec);
+#ifdef HAVE_OPENSSL
+   if(listen->use_ssl) {
+       printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+             "Proxy: Client securely connecting from %s...", rec->host);
+       /* it would be nice to know about the ciphers, but most likely the handshake
+          is currently engaged. So we'll show cipher infos after the first user data passes */
+   } else {    
+       printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+             "Proxy: Client connected from %s", rec->host);
+   }
+#else
    printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
          "Proxy: Client connected from %s", rec->host);
+#endif
 }

 static void sig_incoming(IRC_SERVER_REC *server, const char *line)
@@ -407,7 +451,7 @@
        if (sscanf(signal+6, "%p", &client) == 1) {
            /* send it to specific client only */
            if (g_slist_find(proxy_clients, client) != NULL)
-               net_sendbuffer_send(((CLIENT_REC *) client)->handle, next_line->str, next_line->len);
+               proxy_send((CLIENT_REC *) client, next_line->str, next_line->len);
            g_free(event);
                         signal_stop();
            return;
@@ -424,7 +468,7 @@
            if (rec->want_ctcp == 1) {
                            /* only CTCP for the chatnet where client is connected to will be forwarded */
                            if (strstr(rec->proxy_address, server->connrec->chatnet) != NULL) {
-                   net_sendbuffer_send(rec->handle,
+                   proxy_send(rec,
                                next_line->str, next_line->len);
                    signal_stop();
                }
@@ -573,7 +617,7 @@
    return NULL;
 }

-static void add_listen(const char *ircnet, int port)
+static void add_listen(const char *ircnet, int port, char *sslcert)
 {
    LISTEN_REC *rec;
    IPADDR ip4, ip6, *my_ip;
@@ -611,6 +655,51 @@
        return;
    }

+   if(sslcert != NULL) {
+#ifdef HAVE_OPENSSL
+       rec->use_ssl = TRUE;
+       rec->ssl_method = SSLv3_server_method(); /* let's start with 3 */
+       rec->ssl_ctx = SSL_CTX_new(rec->ssl_method);
+       if(rec->ssl_ctx == NULL) {
+           printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+             "Proxy: Error setting up SSL Context for port %d failed.",
+             rec->port);
+           g_free(rec->ircnet);
+            g_free(rec);
+            return;
+       }
+       
+       if(SSL_CTX_use_certificate_file(rec->ssl_ctx, sslcert, SSL_FILETYPE_PEM) <= 0) {
+           printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Proxy: Error loading certificate.");
+           SSL_CTX_free(rec->ssl_ctx);
+           g_free(rec->ircnet);
+            g_free(rec);
+            return;
+       }
+       
+       if(SSL_CTX_use_PrivateKey_file(rec->ssl_ctx, sslcert, SSL_FILETYPE_PEM) <= 0) {
+           printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Proxy: Error loading private key.");
+           SSL_CTX_free(rec->ssl_ctx); 
+           g_free(rec->ircnet);
+            g_free(rec);
+            return;
+           }
+       
+       if(!SSL_CTX_check_private_key(rec->ssl_ctx)) {
+           printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Proxy: Error loading checking certificate agains private key.");
+           SSL_CTX_free(rec->ssl_ctx); 
+           g_free(rec->ircnet);
+            g_free(rec);
+            return;
+       }
+
+#else
+       printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+           "Proxy: Specified SSL certificate/private key but irssi compiled WITHOUT OpenSSL!");
+#endif
+
+   }
+
    rec->tag = g_input_add(rec->handle, G_INPUT_READ,
                   (GInputFunction) sig_listen, rec);

@@ -625,6 +714,11 @@
        remove_client(rec->clients->data);

    net_disconnect(rec->handle);
+#ifdef HAVE_OPENSSL
+   if(rec->use_ssl) {
+       SSL_CTX_free(rec->ssl_ctx); 
+   }
+#endif
    g_source_remove(rec->tag);
    g_free(rec->ircnet);
    g_free(rec);
@@ -634,7 +728,7 @@
 {
    LISTEN_REC *rec;
    GSList *remove_listens;
-   char **ports, **tmp, *ircnet, *port;
+   char **ports, **tmp, *ircnet, *port, *sslfile;
    int portnum;

    remove_listens = g_slist_copy(proxy_listens);
@@ -647,13 +741,20 @@
            continue;

        *port++ = '\0';
+       
+       sslfile = strchr(port, ':');
+       
+       if (sslfile != NULL) {
+           *sslfile++ = '\0';  
+       }
+       
        portnum = atoi(port);
        if (portnum <=  0)
            continue;

        rec = find_listen(ircnet, portnum);
        if (rec == NULL)
-           add_listen(ircnet, portnum);
+           add_listen(ircnet, portnum, sslfile);
        else
            remove_listens = g_slist_remove(remove_listens, rec);
    }
--- module.orig.h   2009-02-21 19:18:15.000000000 +0100
+++ module.h    2009-02-24 04:26:58.000000000 +0100
@@ -24,3 +24,6 @@
 void proxy_outserver(CLIENT_REC *client, const char *data, ...);
 void proxy_outserver_all(IRC_SERVER_REC *server, const char *data, ...);
 void proxy_outserver_all_except(CLIENT_REC *client, const char *data, ...);
+
+void proxy_send(CLIENT_REC *client, char *d, int l);
+int proxy_readline(CLIENT_REC *client, char **str);
--- proxy.orig.c    2009-02-21 19:18:15.000000000 +0100
+++ proxy.c 2009-02-25 01:07:44.000000000 +0100
@@ -29,6 +29,11 @@
    settings_add_str("irssiproxy", "irssiproxy_password", "");
    settings_add_str("irssiproxy", "irssiproxy_bind", "");

+#ifdef HAVE_OPENSSL
+   SSL_load_error_strings();
+   OpenSSL_add_ssl_algorithms();
+#endif
+
    if (*settings_get_str("irssiproxy_password") == '\0') {
        /* no password - bad idea! */
        signal_emit("gui dialog", 2, "warning",
@@ -38,14 +43,21 @@
    }
    if (*settings_get_str("irssiproxy_ports") == '\0') {
        signal_emit("gui dialog", 2, "warning",
-               "No proxy ports specified. Use /SET "
+               "No proxy ports specified. Use /set "
+#ifdef HAVE_OPENSSL
+               "irssiproxy_ports <ircnet>=<port> <ircnet2>=<port2>:<sslcert> "
+               "... to set them. You can add :filename.pem to secure the proxy with SSL."
+               " (Should contain a cert and key in PEM format)");
+#else
                "irssiproxy_ports <ircnet>=<port> <ircnet2>=<port2> "
                "... to set them.");
+#endif
+
    }

    proxy_listen_init();
    settings_check();
-        module_register("proxy", "irc");
+   module_register("proxy", "irc");
 }

 void irc_proxy_deinit(void)
--- proxy.orig.h    2009-02-21 19:18:15.000000000 +0100
+++ proxy.h 2009-02-25 01:01:13.000000000 +0100
@@ -7,6 +7,15 @@
 #include "irc.h"
 #include "irc-servers.h"

+#ifdef HAVE_OPENSSL
+#include <openssl/rsa.h>
+#include <openssl/crypto.h>
+#include <openssl/x509.h>
+#include <openssl/pem.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#endif
+
 typedef struct {
    int port;
    char *ircnet;
@@ -15,6 +24,11 @@
    GIOChannel *handle;

    GSList *clients;
+#ifdef HAVE_OPENSSL
+   unsigned int use_ssl;
+   SSL_CTX *ssl_ctx;
+   SSL_METHOD *ssl_method;
+#endif
 } LISTEN_REC;

 typedef struct {
@@ -28,6 +42,9 @@
    unsigned int user_sent:1;
    unsigned int connected:1;
    unsigned int want_ctcp:1;
+#ifdef HAVE_OPENSSL
+   SSL *ssl;
+#endif
 } CLIENT_REC;

 #endif
irssibot commented 15 years ago

This would indeed be a nice feature...

It seems strange to me that the path to the certificate is per network, and not just one global setting though. Is there really a use for having different certificates for different proxy ports; or only having ssl enabled for some of them?

irssibot commented 12 years ago

Any reason this is not included?

irssibot commented 10 years ago

Registering interest in this feature in 2014...