* SSL/TLS support for the Citadel/UX wire protocol
authorMichael Hampton <io_error@uncensored.citadel.org>
Sun, 6 Jan 2002 10:33:11 +0000 (10:33 +0000)
committerMichael Hampton <io_error@uncensored.citadel.org>
Sun, 6 Jan 2002 10:33:11 +0000 (10:33 +0000)
14 files changed:
citadel/ChangeLog
citadel/Makefile.in
citadel/client_crypto.c [new file with mode: 0644]
citadel/client_crypto.h [new file with mode: 0644]
citadel/configure.ac
citadel/ipc_c_tcp.c
citadel/ipcdef.h
citadel/md5.h
citadel/serv_crypto.c [new file with mode: 0644]
citadel/serv_crypto.h [new file with mode: 0644]
citadel/server.h
citadel/sysdep.c
citadel/techdoc/session.txt
citadel/user_ops.c

index 2882b925d0baf82f6ebd7e6c104ed5fbe9704124..ade92145c1d0fe6acf665cfaa39bd59c97429786 100644 (file)
@@ -1,4 +1,7 @@
  $Log$
+ Revision 590.42  2002/01/06 10:33:10  error
+ * SSL/TLS support for the Citadel/UX wire protocol
+
  Revision 590.41  2002/01/06 08:54:58  error
  * user_ops.c: fixed become_session() when calling EVT_LOGOUT session hooks
 
@@ -3086,4 +3089,3 @@ Sat Jul 11 00:20:48 EDT 1998 Nathan Bryant <bryant@cs.usm.maine.edu>
 
 Fri Jul 10 1998 Art Cancro <ajc@uncensored.citadel.org>
        * Initial CVS import 
-
index a75f5579574a33bcd34df97b118d5c08d86a7f60..1967e840f857b0463f2f92ee6711237abe13f76e 100644 (file)
@@ -63,6 +63,7 @@ LIBOBJS=@LIBOBJS@
 CL_LIBOBJS=@CL_LIBOBJS@
 PTHREAD_DEFS=@PTHREAD_DEFS@
 PTHREAD_LIBS=@PTHREAD_LIBS@
+SSL_LIBS=@SSL_LIBS@
 CLIENT_PTLIBS=@CLIENT_PTLIBS@
 INSTALL=@INSTALL@
 INSTALL_DATA=@INSTALL_DATA@
@@ -81,12 +82,12 @@ VPATH=$(srcdir)
 
 DOMAIN=@DOMAIN@
 
-SOURCES=aidepost.c citadel.c citmail.c citserver.c client_chat.c commands.c \
-       config.c control.c $(DATABASE) dynloader.c file_ops.c \
-       housekeeping.c ipc_c_tcp.c locate_host.c \
+SOURCES=aidepost.c citadel.c citmail.c citserver.c client_chat.c \
+       client_crypto.c commands.c config.c control.c $(DATABASE) \
+       dynloader.c file_ops.c housekeeping.c ipc_c_tcp.c locate_host.c \
        logging.c messages.c msgbase.c msgform.c \
        netsetup.c policy.c readlog.c migratenet.c \
-       room_ops.c rooms.c routines.c routines2.c serv_chat.c \
+       room_ops.c rooms.c routines.c routines2.c serv_chat.c serv_crypto.c \
        serv_info.c serv_test.c setup.c snprintf.c stats.c serv_vcard.c \
        support.c sysdep.c tools.c user_ops.c userlist.c serv_expire.c \
        whobbs.c sendcommand.c mime_parser.c base64.c getutline.c \
@@ -114,13 +115,13 @@ serv_modules: $(SERV_MODULES)
 
 citadel$(EXEEXT): ipc_c_tcp$(CX) citadel$(CX) rooms$(CX) routines$(CX) \
        routines2$(CX) messages$(CX)  \
-       client_passwords$(CX) md5$(CX) \
+       client_passwords$(CX) md5$(CX) client_crypto$(CX) \
        commands$(CX) client_chat$(CX) serv_info$(CX) tools$(CX) $(LIBOBJS)
        $(CC) ipc_c_tcp$(CX) citadel$(CX) rooms$(CX) routines$(CX) \
        routines2$(CX) messages$(CX) \
        commands$(CX) client_chat$(CX) serv_info$(CX) tools$(CX) \
-       client_passwords$(CX) md5$(CX) \
-       $(LIBOBJS) $(LDFLAGS) -o citadel $(NETLIBS) $(CLIENT_PTLIBS)
+       client_passwords$(CX) md5$(CX) client_crypto$(CX) \
+       $(LIBOBJS) $(LDFLAGS) -o citadel $(NETLIBS) $(SSL_LIBS) $(CLIENT_PTLIBS)
 
 .c.o:
        $(CC) $(CFLAGS) $(CPPFLAGS) $(DEFS) -c $<
@@ -139,11 +140,11 @@ parsedate.ro: parsedate.c
 LIBSERV_OBJS = user_ops.lo citserver.lo sysdep.lo dynloader.lo tools.lo $(DATABASE:.c=.lo) \
        control.lo policy.lo config.lo support.lo room_ops.lo file_ops.lo msgbase.lo \
        locate_host.lo housekeeping.lo logging.lo mime_parser.lo html.lo internet_addressing.lo \
-       parsedate.lo genstamp.lo clientsocket.lo $(AUTH) $(LIBOBJS:.o=.lo)
+       serv_crypto.lo parsedate.lo genstamp.lo clientsocket.lo $(AUTH) $(LIBOBJS:.o=.lo)
 
 libcitserver.la: $(LIBSERV_OBJS)
        $(LIBTOOL) $(CC) $(LDFLAGS) -rpath $(prefix) -no-undefined -avoid-version \
-       -o libcitserver.la $(LIBSERV_OBJS) $(GDBM) $(LIBS) $(PTHREAD_LIBS) $(NETLIBS) $(RESOLV)
+       -o libcitserver.la $(LIBSERV_OBJS) $(GDBM) $(LIBS) $(PTHREAD_LIBS) $(NETLIBS) $(SSL_LIBS) $(RESOLV)
 
 citserver: $(SERV_OBJS) $(LIBTOOL) libcitserver.la
        $(LIBTOOL) --mode=link $(CC) $(SERV_OBJS) $(LDFLAGS) libcitserver.la -o \
diff --git a/citadel/client_crypto.c b/citadel/client_crypto.c
new file mode 100644 (file)
index 0000000..5fdb3b6
--- /dev/null
@@ -0,0 +1,297 @@
+/* $Id$ */
+
+#include "sysdep.h"
+#ifdef HAVE_OPENSSL
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#endif
+#ifdef HAVE_PTHREAD_H
+#include <pthread.h>
+#endif
+#include <unistd.h>
+#include "citadel.h"
+#include "client_crypto.h"
+
+#ifdef HAVE_OPENSSL
+SSL *ssl;
+SSL_CTX *ssl_ctx;
+int ssl_is_connected = 0;
+#ifdef THREADED_CLIENT
+pthread_mutex_t **Critters;                    /* Things that need locking */
+#endif /* THREADED_CLIENT */
+
+extern int serv_sock;
+extern int server_is_local;
+#endif /* HAVE_OPENSSL */
+
+
+#ifdef HAVE_OPENSSL
+/*
+ * input binary data from encrypted connection
+ */
+void serv_read_ssl(char *buf, int bytes)
+{
+       int len, rlen;
+       char junk[1];
+
+       len = 0;
+       while (len < bytes) {
+               if (SSL_want_read(ssl)) {
+                       if ((SSL_write(ssl, junk, 0)) < 1) {
+                               fprintf(stderr, "SSL_write in serv_read:\n");
+                               ERR_print_errors_fp(stderr);
+                       }
+               }
+               rlen = SSL_read(ssl, &buf[len], bytes - len);
+               if (rlen < 1) {
+                       long errval;
+
+                       errval = SSL_get_error(ssl, rlen);
+                       if (errval == SSL_ERROR_WANT_READ ||
+                                       errval == SSL_ERROR_WANT_WRITE) {
+                               sleep(1);
+                               continue;
+                       }
+                       if (errval == SSL_ERROR_ZERO_RETURN ||
+                                       errval == SSL_ERROR_SSL) {
+                               endtls();
+                               serv_read(&buf[len], bytes - len);
+                               return;
+                       }
+                       fprintf(stderr, "SSL_read in serv_read:\n");
+                       ERR_print_errors_fp(stderr);
+                       connection_died();
+                       return;
+               }
+               len += rlen;
+       }
+}
+
+
+/*
+ * send binary to server encrypted
+ */
+void serv_write_ssl(char *buf, int nbytes)
+{
+       int bytes_written = 0;
+       int retval;
+       char junk[1];
+
+       while (bytes_written < nbytes) {
+               if (SSL_want_write(ssl)) {
+                       if ((SSL_read(ssl, junk, 0)) < 1) {
+                               fprintf(stderr, "SSL_read in serv_write:\n");
+                               ERR_print_errors_fp(stderr);
+                       }
+               }
+               retval = SSL_write(ssl, &buf[bytes_written],
+                               nbytes - bytes_written);
+               if (retval < 1) {
+                       long errval;
+
+                       errval = SSL_get_error(ssl, retval);
+                       if (errval == SSL_ERROR_WANT_READ ||
+                                       errval == SSL_ERROR_WANT_WRITE) {
+                               sleep(1);
+                               continue;
+                       }
+                       if (errval == SSL_ERROR_ZERO_RETURN ||
+                                       errval == SSL_ERROR_SSL) {
+                               endtls();
+                               serv_write(&buf[bytes_written],
+                                               nbytes - bytes_written);
+                               return;
+                       }
+                       fprintf(stderr, "SSL_write in serv_write:\n");
+                       ERR_print_errors_fp(stderr);
+                       connection_died();
+                       return;
+               }
+               bytes_written += retval;
+       }
+}
+
+
+void ssl_lock(int mode, int n, const char *file, int line)
+{
+#ifdef THREADED_CLIENT
+       if (mode & CRYPTO_LOCK)
+               pthread_mutex_lock(Critters[n]);
+       else
+               pthread_mutex_unlock(Critters[n]);
+#endif /* THREADED_CLIENT */
+}
+#endif /* HAVE_OPENSSL */
+
+
+/*
+ * starttls() starts SSL/TLS if possible
+ * Returns 1 if the session is encrypted, 0 otherwise
+ */
+int starttls(void)
+{
+#ifdef HAVE_OPENSSL
+       int a;
+       char buf[SIZ];
+       SSL_METHOD *ssl_method;
+       DH *dh;
+       
+       /* TLS is pointless when server is local */
+       if (server_is_local) {
+               return 0;
+       }
+
+       /* Get started */
+       ssl = NULL;
+       ssl_ctx = NULL;
+       dh = NULL;
+       SSL_load_error_strings();
+       SSLeay_add_ssl_algorithms();
+
+       /* Set up the SSL context in which we will oeprate */
+       ssl_method = SSLv23_client_method();
+       ssl_ctx = SSL_CTX_new(ssl_method);
+       if (!ssl_ctx) {
+               fprintf(stderr, "SSL_CTX_new failed: %s\n",
+                               ERR_reason_error_string(ERR_get_error()));
+               return 0;
+       }
+       /* Any reasonable cipher we can get */
+       if (!(SSL_CTX_set_cipher_list(ssl_ctx, CIT_CIPHERS))) {
+               fprintf(stderr, "No ciphers available for encryption\n");
+               SSL_CTX_free(ssl_ctx);
+               ssl_ctx = NULL;
+               return 0;
+       }
+       SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_BOTH);
+       
+       /* Load DH parameters into the context */
+       dh = DH_new();
+       if (!dh) {
+               fprintf(stderr, "Can't allocate a DH object: %s\n",
+                               ERR_reason_error_string(ERR_get_error()));
+               return 0;
+       }
+       if (!(BN_hex2bn(&(dh->p), DH_P))) {
+               fprintf(stderr, "Can't assign DH_P: %s\n",
+                               ERR_reason_error_string(ERR_get_error()));
+               return 0;
+       }
+       if (!(BN_hex2bn(&(dh->g), DH_G))) {
+               fprintf(stderr, "Can't assign DH_G: %s\n",
+                               ERR_reason_error_string(ERR_get_error()));
+               return 0;
+       }
+       dh->length = DH_L;
+       SSL_CTX_set_tmp_dh(ssl_ctx, dh);
+       DH_free(dh);
+
+#ifdef THREADED_CLIENT
+       /* OpenSSL requires callbacks for threaded clients */
+       CRYPTO_set_locking_callback(ssl_lock);
+       CRYPTO_set_id_callback(pthread_self);
+
+       /* OpenSSL requires us to do semaphores for threaded clients */
+       Critters = malloc(CRYPTO_num_locks() * sizeof (pthread_mutex_t *));
+       if (!Critters) {
+               perror("malloc failed");
+               exit(1);
+       } else {
+               for (a = 0; a < CRYPTO_num_locks(); a++) {
+                       Critters[a] = malloc(sizeof (pthread_mutex_t));
+                       if (!Critters[a]) {
+                               perror("malloc failed");
+                               exit(1);
+                       }
+                       pthread_mutex_init(Critters[a], NULL);
+               }
+       }
+#endif /* THREADED_CLIENT */
+
+       /* New SSL object */
+       ssl = SSL_new(ssl_ctx);
+       if (!ssl) {
+               fprintf(stderr, "SSL_new failed: %s\n",
+                               ERR_reason_error_string(ERR_get_error()));
+               SSL_CTX_free(ssl_ctx);
+               ssl_ctx = NULL;
+               return 0;
+       }
+       /* Pointless flag waving */
+#if SSLEAY_VERSION_NUMBER >= 0x0922
+       SSL_set_session_id_context(ssl, "Citadel/UX SID", 14);
+#endif
+       if (!RAND_status()) {
+               fprintf(stderr, "PRNG not properly seeded\n");
+               return 0;
+       }
+
+       /* Associate network connection with SSL object */
+       if (SSL_set_fd(ssl, serv_sock) < 1) {
+               fprintf(stderr, "SSL_set_fd failed: %s\n",
+                               ERR_reason_error_string(ERR_get_error()));
+               SSL_CTX_free(ssl_ctx);
+               ssl_ctx = NULL;
+               SSL_free(ssl);
+               ssl = NULL;
+               return 0;
+       }
+
+       printf("Requesting encryption...\r");
+       fflush(stdout);
+
+       /* Ready to start SSL/TLS */
+       serv_puts("STLS");
+       serv_gets(buf);
+       if (buf[0] != '2') {
+               fprintf(stderr, "Server can't start TLS: %s\n", &buf[4]);
+               return 0;
+       }
+
+       /* Do SSL/TLS handshake */
+       if ((a = SSL_connect(ssl)) < 1) {
+               fprintf(stderr, "SSL_connect failed: %s\n",
+                               ERR_reason_error_string(ERR_get_error()));
+               SSL_CTX_free(ssl_ctx);
+               ssl_ctx = NULL;
+               SSL_free(ssl);
+               ssl = NULL;
+               return 0;
+       }
+       BIO_set_close(ssl->rbio, BIO_NOCLOSE);
+       {
+               int bits, alg_bits;
+
+               bits = SSL_CIPHER_get_bits(SSL_get_current_cipher(ssl), &alg_bits);
+               printf("Encrypting with %s cipher %s (%d of %d bits)\n",
+                               SSL_CIPHER_get_version(SSL_get_current_cipher(ssl)),
+                               SSL_CIPHER_get_name(SSL_get_current_cipher(ssl)),
+                               bits, alg_bits);
+       }
+       ssl_is_connected = 1;
+       return 1;
+#else
+       return 0;
+#endif /* HAVE_OPENSSL */
+}
+
+
+/*
+ * void endtls() - end SSL/TLS session
+ */
+void endtls(void)
+{
+#ifdef HAVE_OPENSSL
+       if (ssl) {
+               SSL_shutdown(ssl);
+               SSL_free(ssl);
+               ssl = NULL;
+       }
+       ssl_is_connected = 0;
+       if (ssl_ctx) {
+               SSL_CTX_free(ssl_ctx);
+               ssl_ctx = NULL;
+       }
+#endif
+}
diff --git a/citadel/client_crypto.h b/citadel/client_crypto.h
new file mode 100644 (file)
index 0000000..0fed709
--- /dev/null
@@ -0,0 +1,17 @@
+/* $Id$ */
+
+/* Shared Diffie-Hellman parameters */
+#define DH_P           "1A74527AEE4EE2568E85D4FB2E65E18C9394B9C80C42507D7A6A0DBE9A9A54B05A9A96800C34C7AA5297095B69C88901EEFD127F969DCA26A54C0E0B5C5473EBAEB00957D2633ECAE3835775425DE66C0DE6D024DBB17445E06E6B0C78415E589B8814F08531D02FD43778451E7685541079CFFB79EF0D26EFEEBBB69D1E80383"
+#define DH_G           "2"
+#define DH_L           1024
+#define CIT_CIPHERS    "ALL:RC4+RSA:+SSLv2:@STRENGTH"  /* see ciphers(1) */
+
+int starttls(void);
+void endtls(void);
+void serv_read(char *buf, int bytes);
+void serv_write(char *buf, int nbytes);
+#ifdef HAVE_OPENSSL
+void serv_read_ssl(char *buf, int bytes);
+void serv_write_ssl(char *buf, int nbytes);
+void ssl_lock(int mode, int n, const char *file, int line);
+#endif /* HAVE_OPENSSL */
index df039663cd1fd6b02145b28d65a6cc8962a16cd0..59326f42754387b8b24a919c4b6d03297cf31b9f 100644 (file)
@@ -316,7 +316,8 @@ if test "$with_ssl" != "no"; then
        
        if test "x$ac_cv_openssldir" != "xno" ; then
                AC_DEFINE(HAVE_OPENSSL)
-               LIBS="$saved_LIBS -lssl -lcrypto"
+               LIBS="$saved_LIBS"
+               SSL_LIBS="-lssl -lcrypto"
                dnl Need to recover ssldir - test above runs in subshell
                ssldir=$ac_cv_openssldir
                if test ! -z "$ssldir" -a "x$ssldir" != "x/usr" -a "x$ssldir" != "x(system)"; then
@@ -472,6 +473,7 @@ AC_SUBST(CLIENT_PTLIBS)
 AC_SUBST(CL_LIBOBJS)
 AC_SUBST(CX)
 AC_SUBST(DATABASE)
+AC_SUBST(SSL_LIBS)
 AC_CONFIG_HEADER(sysdep.h)
 AC_CONFIG_FILES([Makefile weekly])
 AC_OUTPUT
index 28217ed57d5999e3287cb46cc6b957dce997ae6d..a5a086daaa0a35835a632b3f3c8204517a013aff 100644 (file)
@@ -30,6 +30,9 @@
 #include "citadel_decls.h"
 #include "ipc.h"
 #include "tools.h"
+#if defined(HAVE_OPENSSL) && defined(CIT_CLIENT)
+#include "client_crypto.h"
+#endif
 #ifndef HAVE_SNPRINTF
 #include "snprintf.h"
 #endif
@@ -53,6 +56,11 @@ int server_is_local = 0;
 
 int serv_sock;
 
+#if defined(HAVE_OPENSSL) && defined(CIT_CLIENT)
+extern int ssl_is_connected;
+#endif
+
+
 void connection_died(void) {
        fprintf(stderr, "\r"
                        "Your connection to this Citadel server is broken.\n"
@@ -161,6 +169,12 @@ void serv_read(char *buf, int bytes)
 {
        int len, rlen;
 
+#if defined(HAVE_OPENSSL) && defined(CIT_CLIENT)
+       if (ssl_is_connected) {
+               serv_read_ssl(buf, bytes);
+               return;
+       }
+#endif
        len = 0;
        while (len < bytes) {
                rlen = read(serv_sock, &buf[len], bytes - len);
@@ -168,7 +182,7 @@ void serv_read(char *buf, int bytes)
                        connection_died();
                        return;
                }
-               len = len + rlen;
+               len += rlen;
        }
 }
 
@@ -180,6 +194,13 @@ void serv_write(char *buf, int nbytes)
 {
        int bytes_written = 0;
        int retval;
+
+#if defined(HAVE_OPENSSL) && defined(CIT_CLIENT)
+       if (ssl_is_connected) {
+               serv_write_ssl(buf, nbytes);
+               return;
+       }
+#endif
        while (bytes_written < nbytes) {
                retval = write(serv_sock, &buf[bytes_written],
                               nbytes - bytes_written);
@@ -187,7 +208,7 @@ void serv_write(char *buf, int nbytes)
                        connection_died();
                        return;
                }
-               bytes_written = bytes_written + retval;
+               bytes_written += retval;
        }
 }
 
@@ -301,4 +322,3 @@ char serv_getc(void)
 
        return (ch);
 }
-
index 203f4454d2901c254a3f55c3bfddbb662725c18c..4e27f6e6b529e1b82744d48a841417f03f8aae80 100644 (file)
@@ -81,3 +81,4 @@ struct CtdlServInfo {
 
 void serv_puts(char *buf);
 void serv_gets(char *buf);
+void connection_died(void);
index 64beb61881ae93ceb69e267fc7adb0871aef7c88..ff8d0c835ed0c05cd2329288ac7aa202d06cb53b 100644 (file)
@@ -19,7 +19,9 @@ char *make_apop_string(char *realpass, char *nonce, char *buffer);
 /*
  * This is needed to make RSAREF happy on some MS-DOS compilers.
  */
+#ifndef HAVE_OPENSSL
 typedef struct MD5Context MD5_CTX;
+#endif
 
 #define MD5_DIGEST_LEN         16
 #define MD5_HEXSTRING_SIZE     33
diff --git a/citadel/serv_crypto.c b/citadel/serv_crypto.c
new file mode 100644 (file)
index 0000000..6f913e0
--- /dev/null
@@ -0,0 +1,330 @@
+/* $Id$ */
+
+#include <unistd.h>
+#include <sys/types.h>
+#include "sysdep.h"
+#ifdef HAVE_OPENSSL
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#endif
+#ifdef HAVE_PTHREAD_H
+#include <pthread.h>
+#endif
+#include "server.h"
+#include "serv_crypto.h"
+#include "sysdep_decls.h"
+#include "dynloader.h"
+
+
+#ifdef HAVE_OPENSSL
+SSL_CTX *ssl_ctx;                              /* SSL context */
+pthread_mutex_t **SSLCritters;                 /* Things needing locking */
+
+
+void init_ssl(void)
+{
+       SSL_METHOD *ssl_method;
+       DH *dh;
+       
+       if (!RAND_status()) {
+               lprintf(2, "PRNG not adequately seeded, won't do SSL/TLS\n");
+               return;
+       }
+       SSLCritters = mallok(CRYPTO_num_locks() * sizeof (pthread_mutex_t *));
+       if (!SSLCritters) {
+               lprintf(1, "citserver: can't allocate memory!!\n");
+               /* Nothing's been initialized, just die */
+               exit(1);
+       } else {
+               int a;
+
+               for (a=0; a<CRYPTO_num_locks(); a++) {
+                       SSLCritters[a] = mallok(sizeof (pthread_mutex_t));
+                       if (!SSLCritters[a]) {
+                               lprintf(1, "citserver: can't allocate memory!!\n");
+                               /* Nothing's been initialized, just die */
+                               exit(1);
+                       }
+                       pthread_mutex_init(SSLCritters[a], NULL);
+               }
+       }
+
+       /*
+        * Initialize SSL transport layer
+        */
+       SSL_library_init();
+       SSL_load_error_strings();
+       ssl_method = SSLv23_server_method();
+       if (!(ssl_ctx = SSL_CTX_new(ssl_method))) {
+               lprintf(2, "SSL_CTX_new failed: %s\n",
+                               ERR_reason_error_string(ERR_get_error()));
+               return;
+       }
+       if (!(SSL_CTX_set_cipher_list(ssl_ctx, CIT_CIPHERS))) {
+               lprintf(2, "SSL: No ciphers available\n");
+               SSL_CTX_free(ssl_ctx);
+               ssl_ctx = NULL;
+               return;
+       }
+#if 0
+#if SSLEAY_VERSION_NUMBER >= 0x00906000L
+       SSL_CTX_set_mode(ssl_ctx, SSL_CTX_get_mode(ssl_ctx) |
+                       SSL_MODE_AUTO_RETRY);
+#endif
+#endif
+       CRYPTO_set_locking_callback(ssl_lock);
+       CRYPTO_set_id_callback(pthread_self);
+
+       /* Load DH parameters into the context */
+       dh = DH_new();
+       if (!dh) {
+               lprintf(2, "init_ssl() can't allocate a DH object: %s\n",
+                               ERR_reason_error_string(ERR_get_error()));
+               SSL_CTX_free(ssl_ctx);
+               ssl_ctx = NULL;
+               return;
+       }
+       if (!(BN_hex2bn(&(dh->p), DH_P))) {
+               lprintf(2, "init_ssl() can't assign DH_P: %s\n",
+                               ERR_reason_error_string(ERR_get_error()));
+               SSL_CTX_free(ssl_ctx);
+               ssl_ctx = NULL;
+               return;
+       }
+       if (!(BN_hex2bn(&(dh->g), DH_G))) {
+               lprintf(2, "init_ssl() can't assign DH_G: %s\n",
+                               ERR_reason_error_string(ERR_get_error()));
+               SSL_CTX_free(ssl_ctx);
+               ssl_ctx = NULL;
+               return;
+       }
+       dh->length = DH_L;
+       SSL_CTX_set_tmp_dh(ssl_ctx, dh);
+       DH_free(dh);
+
+       /* Finally let the server know we're here */
+       CtdlRegisterProtoHook(cmd_stls, "STLS", "Start SSL/TLS session");
+       CtdlRegisterProtoHook(cmd_gtls, "GTLS", "Get SSL/TLS session status");
+       CtdlRegisterProtoHook(cmd_etls, "ETLS", "End SSL/TLS session");
+       CtdlRegisterSessionHook(endtls_atlogout, EVT_STOP);
+}
+
+
+/*
+ * client_write_ssl() Send binary data to the client encrypted.
+ */
+void client_write_ssl(char *buf, int nbytes)
+{
+       int retval;
+       int nremain;
+       char junk[1];
+
+       nremain = nbytes;
+
+       while (nremain > 0) {
+               if (SSL_want_write(CC->ssl)) {
+                       if ((SSL_read(CC->ssl, junk, 0)) < 1) {
+                               lprintf(9, "SSL_read in client_write:\n");
+                               ERR_print_errors_fp(stderr);
+                       }
+               }
+               retval = SSL_write(CC->ssl, &buf[nbytes - nremain], nremain);
+               if (retval < 1) {
+                       long errval;
+
+                       errval = SSL_get_error(CC->ssl, retval);
+                       if (errval == SSL_ERROR_WANT_READ ||
+                                       errval == SSL_ERROR_WANT_WRITE) {
+                               sleep(1);
+                               continue;
+                       }
+                       lprintf(9, "SSL_write got error %ld\n", errval);
+                       endtls(1);
+                       client_write(&buf[nbytes - nremain], nremain);
+                       return;
+               }
+               nremain -= retval;
+       }
+}
+
+
+/*
+ * client_read_ssl() - read data from the encrypted layer.
+ */
+int client_read_ssl(char *buf, int bytes, int timeout)
+{
+       int len,rlen;
+       fd_set rfds;
+       struct timeval tv;
+       int retval;
+       int s;
+       char junk[1];
+
+       len = 0;
+       while(len<bytes) {
+               FD_ZERO(&rfds);
+               s = BIO_get_fd(CC->ssl->rbio, NULL);
+               FD_SET(s, &rfds);
+               tv.tv_sec = timeout;
+               tv.tv_usec = 0;
+
+               retval = select(s+1, &rfds, NULL, NULL, &tv);
+
+               if (FD_ISSET(s, &rfds) == 0) {
+                       return(0);
+               }
+
+               if (SSL_want_read(CC->ssl)) {
+                       if ((SSL_write(CC->ssl, junk, 0)) < 1) {
+                               lprintf(9, "SSL_write in client_read:\n");
+                               ERR_print_errors_fp(stderr);
+                       }
+               }
+               rlen = SSL_read(CC->ssl, &buf[len], bytes-len);
+               if (rlen<1) {
+                       long errval;
+
+                       errval = SSL_get_error(CC->ssl, rlen);
+                       if (errval == SSL_ERROR_WANT_READ ||
+                                       errval == SSL_ERROR_WANT_WRITE) {
+                               sleep(1);
+                               continue;
+                       }
+                       lprintf(9, "SSL_read got error %ld\n", errval);
+                       endtls(1);
+                       return (client_read_to(&buf[len], bytes - len, timeout));
+               }
+               len += rlen;
+       }
+       return(1);
+}
+
+
+/*
+ * cmd_stls() starts SSL/TLS encryption for the current session
+ */
+void cmd_stls(char *params)
+{
+       int retval, bits, alg_bits;
+
+       if (!ssl_ctx) {
+               cprintf("%d No SSL_CTX available\n", ERROR);
+               return;
+       }
+       if (!(CC->ssl = SSL_new(ssl_ctx))) {
+               lprintf(2, "SSL_new failed: %s\n",
+                               ERR_reason_error_string(ERR_peek_error()));
+               cprintf("%d SSL_new: %s\n", ERROR,
+                               ERR_reason_error_string(ERR_get_error()));
+               return;
+       }
+       if (!(SSL_set_fd(CC->ssl, CC->client_socket))) {
+               lprintf(2, "SSL_set_fd failed: %s\n",
+                               ERR_reason_error_string(ERR_peek_error()));
+               SSL_free(CC->ssl);
+               CC->ssl = NULL;
+               cprintf("%d SSL_set_fd: %s\n", ERROR,
+                               ERR_reason_error_string(ERR_get_error()));
+               return;
+       }
+       cprintf("%d \n", OK);
+       retval = SSL_accept(CC->ssl);
+       if (retval < 1) {
+               /*
+                * Can't notify the client of an error here; they will
+                * discover the problem at the SSL layer and should
+                * revert to unencrypted communications.
+                */
+               long errval;
+
+               errval = SSL_get_error(CC->ssl, retval);
+               lprintf(2, "SSL_accept failed: %s\n",
+                               ERR_reason_error_string(ERR_get_error()));
+               SSL_free(CC->ssl);
+               CC->ssl = NULL;
+               return;
+       }
+       BIO_set_close(CC->ssl->rbio, BIO_NOCLOSE);
+       bits = SSL_CIPHER_get_bits(SSL_get_current_cipher(CC->ssl), &alg_bits);
+       lprintf(3, "Session %d using %s on %s (%d of %d bits)\n", CC->cs_pid,
+                       SSL_CIPHER_get_name(SSL_get_current_cipher(CC->ssl)),
+                       SSL_CIPHER_get_version(SSL_get_current_cipher(CC->ssl)),
+                       bits, alg_bits);
+       CC->redirect_ssl = 1;
+}
+
+
+/*
+ * cmd_gtls() returns status info about the TLS connection
+ */
+void cmd_gtls(char *params)
+{
+       int bits, alg_bits;
+       
+       if (!CC->ssl || !CC->redirect_ssl) {
+               cprintf("%d Session is not encrypted.\n", ERROR);
+               return;
+       }
+       bits = SSL_CIPHER_get_bits(SSL_get_current_cipher(CC->ssl), &alg_bits);
+       cprintf("%d %s|%s|%d|%d\n", OK,
+               SSL_CIPHER_get_version(SSL_get_current_cipher(CC->ssl)),
+               SSL_CIPHER_get_name(SSL_get_current_cipher(CC->ssl)),
+               alg_bits, bits);
+}
+
+
+/* Logout function hook */
+void endtls_atlogout(void)
+{
+       endtls(1);
+}
+
+
+/* Command function hook */
+void cmd_etls(char *params)
+{
+       endtls(0);
+}
+
+
+/*
+ * endtls() shuts down the TLS connection
+ * Parameter is NULL for client request, CitContext * for server request
+ *
+ * WARNING:  This may make your session vulnerable to a known plaintext
+ * attack in the current implmentation.
+ */
+void endtls(int who)
+{
+       lprintf(7, "Session %d ending SSL/TLS%s\n", CC->cs_pid,
+                       (who) ? "" : " at client request");
+
+       if (!who) {
+               if (!CC->ssl) {
+                       cprintf("%d Connection is not encrypted.\n", ERROR);
+                       return;
+               }
+               cprintf("%d Now stop encryption.\n", OK);
+       } else if (!CC->ssl) {
+               return;
+       }
+       
+       SSL_shutdown(CC->ssl);
+       SSL_free(CC->ssl);
+       CC->ssl = NULL;
+       CC->redirect_ssl = 0;
+}
+
+
+/*
+ * ssl_lock() callback for OpenSSL mutex locks
+ */
+void ssl_lock(int mode, int n, const char *file, int line)
+{
+       if (mode & CRYPTO_LOCK)
+               pthread_mutex_lock(SSLCritters[n]);
+       else
+               pthread_mutex_unlock(SSLCritters[n]);
+}
+#endif /* HAVE_OPENSSL */
diff --git a/citadel/serv_crypto.h b/citadel/serv_crypto.h
new file mode 100644 (file)
index 0000000..45e1a5d
--- /dev/null
@@ -0,0 +1,19 @@
+/* $Id$ */
+
+/* Shared Diffie-Hellman parameters */
+#define DH_P           "1A74527AEE4EE2568E85D4FB2E65E18C9394B9C80C42507D7A6A0DBE9A9A54B05A9A96800C34C7AA5297095B69C88901EEFD127F969DCA26A54C0E0B5C5473EBAEB00957D2633ECAE3835775425DE66C0DE6D024DBB17445E06E6B0C78415E589B8814F08531D02FD43778451E7685541079CFFB79EF0D26EFEEBBB69D1E80383"
+#define DH_G           "2"
+#define DH_L           1024
+#define CIT_CIPHERS    "ALL:RC4+RSA:+SSLv2:@STRENGTH"  /* see ciphers(1) */
+
+#ifdef HAVE_OPENSSL
+void init_ssl(void);
+void client_write_ssl (char *buf, int nbytes);
+int client_read_ssl (char *buf, int bytes, int timeout);
+void cmd_stls(char *params);
+void cmd_gtls(char *params);
+void cmd_etls(char *params);
+void endtls_atlogout(void);
+void endtls(int who);
+void ssl_lock(int mode, int n, const char *file, int line);
+#endif
index 5dbc22b70868e35588b85c2c1dbb288b8e3320cb..1a1655ae684718e32de04089affd8cc1bb20c197 100644 (file)
@@ -32,6 +32,9 @@
 #endif /* __CYGWIN__ */
 
 #include "citadel.h"
+#ifdef HAVE_OPENSSL
+#include <openssl/ssl.h>
+#endif
 
 #define        CTDLMESSAGE_MAGIC               0x159d
 struct CtdlMessage {
@@ -123,6 +126,10 @@ struct CitContext {
        /* Redirect this session's output to somewhere else? */
        FILE *redirect_fp;
        int redirect_sock;
+#ifdef HAVE_OPENSSL
+       SSL *ssl;
+       int redirect_ssl;
+#endif
 
        /* A linked list of all express messages sent to us. */
        struct ExpressMessage *FirstExpressMessage;
index c45e2f77681e44fd4fe424f5978e0334e6f521d2..fdb38cc277675fc5e1f11bc8aa5f1a8fd65892a7 100644 (file)
@@ -62,6 +62,7 @@
 #include "database.h"
 #include "housekeeping.h"
 #include "tools.h"
+#include "serv_crypto.h"
 
 #ifdef HAVE_SYS_SELECT_H
 #include <sys/select.h>
@@ -234,6 +235,10 @@ static RETSIGTYPE signal_cleanup(int signum) {
 void init_sysdep(void) {
        int a;
 
+#ifdef HAVE_OPENSSL
+       init_ssl();
+#endif
+
        /* Set up a bunch of semaphores to be used for critical sections */
        for (a=0; a<MAX_SEMAPHORES; ++a) {
                pthread_mutex_init(&Critters[a], NULL);
@@ -464,7 +469,6 @@ DONE:       ++num_sessions;
 }
 
 
-
 /*
  * client_write()   ...    Send binary data to the client.
  */
@@ -475,6 +479,13 @@ void client_write(char *buf, int nbytes)
        int sock;
 
 
+#ifdef HAVE_OPENSSL
+       if (CC->redirect_ssl) {
+               client_write_ssl(buf, nbytes);
+               return;
+       }
+#endif
+
        if (CC->redirect_fp != NULL) {
                fwrite(buf, nbytes, 1, CC->redirect_fp);
                return;
@@ -533,6 +544,11 @@ int client_read_to(char *buf, int bytes, int timeout)
        struct timeval tv;
        int retval;
 
+#ifdef HAVE_OPENSSL
+       if (CC->redirect_ssl) {
+               return (client_read_ssl(buf, bytes, timeout));
+       }
+#endif
        len = 0;
        while(len<bytes) {
                FD_ZERO(&rfds);
@@ -1074,6 +1090,3 @@ SETUP_FD: memcpy(&readfds, &masterfds, sizeof masterfds);
        --num_threads;
        return NULL;
 }
-
-
-
index 0c1f0538e64cd431b1df3fb99eda30b5d5da634b..7403611c72699f7bbd6a38db2cd3038c2524e268 100644 (file)
@@ -1828,8 +1828,57 @@ supported:
       SMTP runqueue                (attempt immediate delivery of all messages
                                     in the outbound SMTP queue, ignoring any
                                     retry times stored there)
+
+
+ STLS   (Start Transport Layer Security)
+
+ This command starts TLS on the current connection.  The current
+implementation uses OpenSSL on both the client and server end.  For future
+compatibility all clients must support at least TLSv1, and servers are
+guaranteed to support TLSv1.  During TLS negotiation (see below) the server
+and client may agree to use a different protocol.
+
+ The server returns ERROR if it does not support SSL or SSL initialization
+failed on the server; otherwise it returns OK.  Once the server returns OK and
+the client has read the response, the server and client immediately negotiate
+TLS (in OpenSSL, using SSL_connect() on the client and SSL_accept() on the
+server).  If negotiation fails, the server and client should attempt to resume
+the session unencrypted.  If either end is unable to resume the session, the
+connection should be closed.
+
+ This command may be run at any time.
   
   
+ GTLS   (Get Transport Layer Security Status)
+
+ This command returns information about the current connection.  The server
+returns OK plus several parameters if the connection is encrypted, and ERROR
+if the connection is not encrypted.  It is primarily used for debugging.  The
+command may be run at any time.
+ 0 - Protocol name, e.g. "SSLv3"
+ 1 - Cipher suite name, e.g. "ADH-RC4-MD5"
+ 2 - Cipher strength bits, e.g. 128
+ 3 - Cipher strength bits actually in use, e.g. 128
+
+
+ ETLS   (End Transport Layer Security)
+
+ This command shuts down TLS and resumes an unencrypted communications
+channel.  This command returns OK and then negotiates TLS shutdown.  The
+client should read the response before negotiating TLS shutdown (e.g. with
+SSL_shutdown() in OpenSSL).  The connection is then unencrypted.  The
+server may return ERROR if it does not support SSL or if the connection was
+not encrypted to begin with.  If either end is unable to resume the connection
+unencrypted, the connection should be closed.
+ This command is primarily for debugging.  Clients may end TLS at any time
+without calling this command.  If a client needs to end TLS and resume
+unencrypted communications, it should do so through the SSL/TLS layer (e.g.
+with SSL_shutdown() in OpenSSL) rather than using this command.  This command
+may be removed from future versions of Citadel/UX.
+
+
  ASYN   (ASYNchronous message support)
  
  Negotiate the use of asynchronous, or unsolicited, protocol messages.  The
index 0db8d1e7f6cfd9aa92f09f77ba9185bd0ff7060c..f8aefce0d3426c8df61d5e3f7fd6cdb694618d5f 100644 (file)
@@ -430,8 +430,6 @@ void logged_in_response(void)
  */
 void logout(struct CitContext *who)
 {
-       struct CitContext *saved_cc = NULL;
-
        who->logged_in = 0;
 
        /*
@@ -457,11 +455,7 @@ void logout(struct CitContext *who)
        }
 
        /* Do modular stuff... */
-       if (who != CC)
-               saved_cc = CC;
-       become_session(who);
        PerformSessionHooks(EVT_LOGOUT);
-       become_session(saved_cc);
 }
 
 #ifdef ENABLE_CHKPWD