]> git.ipfire.org Git - people/ms/dma.git/blobdiff - crypto.c
Merge pull request #34 from mtremer/better-authentication
[people/ms/dma.git] / crypto.c
index e509c9475568b9e8101271a7c6b15ec48d81830d..0ebcf78c86d8babcd89d71c00f4cd959e6f7c2b7 100644 (file)
--- a/crypto.c
+++ b/crypto.c
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
- *
- * $DragonFly: src/libexec/dma/crypto.c,v 1.1 2008/02/02 18:20:51 matthias Exp $
  */
 
-#ifdef HAVE_CRYPTO
-
 #include <openssl/x509.h>
+#include <openssl/md5.h>
 #include <openssl/ssl.h>
 #include <openssl/err.h>
 #include <openssl/pem.h>
 
 #include "dma.h"
 
-extern struct config *config;
-
 static int
-init_cert_file(struct qitem *it, SSL_CTX *ctx, const char *path)
+init_cert_file(SSL_CTX *ctx, const char *path)
 {
        int error;
 
        /* Load certificate into ctx */
        error = SSL_CTX_use_certificate_chain_file(ctx, path);
        if (error < 1) {
-               syslog(LOG_ERR, "%s: SSL: Cannot load certificate: %s",
-                       it->queueid, path);
+               syslog(LOG_ERR, "SSL: Cannot load certificate `%s': %s", path, ssl_errstr());
                return (-1);
        }
 
        /* Add private key to ctx */
        error = SSL_CTX_use_PrivateKey_file(ctx, path, SSL_FILETYPE_PEM);
        if (error < 1) {
-               syslog(LOG_ERR, "%s: SSL: Cannot load private key: %s",
-                       it->queueid, path);
+               syslog(LOG_ERR, "SSL: Cannot load private key `%s': %s", path, ssl_errstr());
                return (-1);
        }
 
@@ -76,8 +69,7 @@ init_cert_file(struct qitem *it, SSL_CTX *ctx, const char *path)
         */
        error = SSL_CTX_check_private_key(ctx);
        if (error < 1) {
-               syslog(LOG_ERR, "%s: SSL: Cannot check private key: %s",
-                       it->queueid, path);
+               syslog(LOG_ERR, "SSL: Cannot check private key: %s", ssl_errstr());
                return (-1);
        }
 
@@ -85,29 +77,43 @@ init_cert_file(struct qitem *it, SSL_CTX *ctx, const char *path)
 }
 
 int
-smtp_init_crypto(struct qitem *it, int fd, int feature)
+smtp_init_crypto(int fd, int feature, struct smtp_features* features)
 {
        SSL_CTX *ctx = NULL;
+#if (OPENSSL_VERSION_NUMBER >= 0x00909000L)
+       const SSL_METHOD *meth = NULL;
+#else
        SSL_METHOD *meth = NULL;
+#endif
        X509 *cert;
-       char buf[2048];
        int error;
 
+       /* XXX clean up on error/close */
        /* Init SSL library */
        SSL_library_init();
+       SSL_load_error_strings();
 
-       meth = TLSv1_client_method();
+       // Allow any possible version
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+       meth = TLS_client_method();
+#else
+       meth = SSLv23_client_method();
+#endif
 
        ctx = SSL_CTX_new(meth);
        if (ctx == NULL) {
-               syslog(LOG_ERR, "%s: remote delivery deferred:"
-                      " SSL init failed: %m", it->queueid);
-               return (2);
+               syslog(LOG_WARNING, "remote delivery deferred: SSL init failed: %s", ssl_errstr());
+               return (1);
        }
 
        /* User supplied a certificate */
-       if (config->certfile != NULL)
-               init_cert_file(it, ctx, config->certfile);
+       if (config.certfile != NULL) {
+               error = init_cert_file(ctx, config.certfile);
+               if (error) {
+                       syslog(LOG_WARNING, "remote delivery deferred");
+                       return (1);
+               }
+       }
 
        /*
         * If the user wants STARTTLS, we have to send EHLO here
@@ -115,70 +121,197 @@ smtp_init_crypto(struct qitem *it, int fd, int feature)
        if (((feature & SECURETRANS) != 0) &&
             (feature & STARTTLS) != 0) {
                /* TLS init phase, disable SSL_write */
-               config->features |= TLSINIT;
+               config.features |= NOSSL;
 
-               send_remote_command(fd, "EHLO %s", hostname());
-               if (check_for_smtp_error(fd, buf) == 0) {
+               if (perform_server_greeting(fd, features) == 0) {
                        send_remote_command(fd, "STARTTLS");
-                       if (check_for_smtp_error(fd, buf) < 0) {
-                               syslog(LOG_ERR, "%s: remote delivery failed:"
-                                 " STARTTLS not available: %m", it->queueid);
-                               config->features &= ~TLSINIT;
-                               return (-1);
+                       if (read_remote(fd, 0, NULL) != 2) {
+                               if ((feature & TLS_OPP) == 0) {
+                                       syslog(LOG_ERR, "remote delivery deferred: STARTTLS not available: %s", neterr);
+                                       return (1);
+                               } else {
+                                       syslog(LOG_INFO, "in opportunistic TLS mode, STARTTLS not available: %s", neterr);
+                                       return (0);
+                               }
                        }
                }
+
                /* End of TLS init phase, enable SSL_write/read */
-               config->features &= ~TLSINIT;
+               config.features &= ~NOSSL;
        }
 
-       config->ssl = SSL_new(ctx);
-       if (config->ssl == NULL) {
-               syslog(LOG_ERR, "%s: remote delivery deferred:"
-                      " SSL struct creation failed:", it->queueid);
-               return (2);
+       config.ssl = SSL_new(ctx);
+       if (config.ssl == NULL) {
+               syslog(LOG_NOTICE, "remote delivery deferred: SSL struct creation failed: %s",
+                      ssl_errstr());
+               return (1);
        }
 
        /* Set ssl to work in client mode */
-       SSL_set_connect_state(config->ssl);
+       SSL_set_connect_state(config.ssl);
 
        /* Set fd for SSL in/output */
-       error = SSL_set_fd(config->ssl, fd);
+       error = SSL_set_fd(config.ssl, fd);
        if (error == 0) {
-               error = SSL_get_error(config->ssl, error);
-               syslog(LOG_ERR, "%s: remote delivery deferred:"
-                      " SSL set fd failed (%d): %m", it->queueid, error);
-               return (2);
+               syslog(LOG_NOTICE, "remote delivery deferred: SSL set fd failed: %s",
+                      ssl_errstr());
+               return (1);
        }
 
        /* Open SSL connection */
-       error = SSL_connect(config->ssl);
+       error = SSL_connect(config.ssl);
        if (error < 0) {
-               syslog(LOG_ERR, "%s: remote delivery failed:"
-                      " SSL handshake fataly failed: %m", it->queueid);
-               return (-1);
+               syslog(LOG_ERR, "remote delivery deferred: SSL handshake failed fatally: %s",
+                      ssl_errstr());
+               return (1);
        }
 
        /* Get peer certificate */
-       cert = SSL_get_peer_certificate(config->ssl);
+       cert = SSL_get_peer_certificate(config.ssl);
        if (cert == NULL) {
-               syslog(LOG_ERR, "%s: remote delivery deferred:"
-                      " Peer did not provied certificate: %m", it->queueid);
+               syslog(LOG_WARNING, "remote delivery deferred: Peer did not provide certificate: %s",
+                      ssl_errstr());
        }
        X509_free(cert);
 
        return (0);
 }
 
-#if 0
 /*
- * CRAM-MD5 authentication
+ * hmac_md5() taken out of RFC 2104.  This RFC was written by H. Krawczyk,
+ * M. Bellare and R. Canetti.
  *
- * XXX TODO implement me, I don't have a mail server with CRAM-MD5 available
+ * text      pointer to data stream
+ * text_len  length of data stream
+ * key       pointer to authentication key
+ * key_len   length of authentication key
+ * digest    caller digest to be filled int
+ */
+void
+hmac_md5(unsigned char *text, int text_len, unsigned char *key, int key_len,
+    unsigned char* digest)
+{
+        MD5_CTX context;
+        unsigned char k_ipad[65];    /* inner padding -
+                                      * key XORd with ipad
+                                      */
+        unsigned char k_opad[65];    /* outer padding -
+                                      * key XORd with opad
+                                      */
+        unsigned char tk[16];
+        int i;
+        /* if key is longer than 64 bytes reset it to key=MD5(key) */
+        if (key_len > 64) {
+
+                MD5_CTX      tctx;
+
+                MD5_Init(&tctx);
+                MD5_Update(&tctx, key, key_len);
+                MD5_Final(tk, &tctx);
+
+                key = tk;
+                key_len = 16;
+        }
+
+        /*
+         * the HMAC_MD5 transform looks like:
+         *
+         * MD5(K XOR opad, MD5(K XOR ipad, text))
+         *
+         * where K is an n byte key
+         * ipad is the byte 0x36 repeated 64 times
+        *
+         * opad is the byte 0x5c repeated 64 times
+         * and text is the data being protected
+         */
+
+        /* start out by storing key in pads */
+        bzero( k_ipad, sizeof k_ipad);
+        bzero( k_opad, sizeof k_opad);
+        bcopy( key, k_ipad, key_len);
+        bcopy( key, k_opad, key_len);
+
+        /* XOR key with ipad and opad values */
+        for (i=0; i<64; i++) {
+                k_ipad[i] ^= 0x36;
+                k_opad[i] ^= 0x5c;
+        }
+        /*
+         * perform inner MD5
+         */
+        MD5_Init(&context);                   /* init context for 1st
+                                              * pass */
+        MD5_Update(&context, k_ipad, 64);     /* start with inner pad */
+        MD5_Update(&context, text, text_len); /* then text of datagram */
+        MD5_Final(digest, &context);          /* finish up 1st pass */
+        /*
+         * perform outer MD5
+         */
+        MD5_Init(&context);                   /* init context for 2nd
+                                              * pass */
+        MD5_Update(&context, k_opad, 64);     /* start with outer pad */
+        MD5_Update(&context, digest, 16);     /* then results of 1st
+                                              * hash */
+        MD5_Final(digest, &context);          /* finish up 2nd pass */
+}
+
+/*
+ * CRAM-MD5 authentication
  */
 int
 smtp_auth_md5(int fd, char *login, char *password)
 {
-}
-#endif /* 0 */
+       unsigned char digest[BUF_SIZE];
+       char buffer[BUF_SIZE], ascii_digest[33];
+       char *temp;
+       int len, i;
+       static char hextab[] = "0123456789abcdef";
+
+       temp = calloc(BUF_SIZE, 1);
+       memset(buffer, 0, sizeof(buffer));
+       memset(digest, 0, sizeof(digest));
+       memset(ascii_digest, 0, sizeof(ascii_digest));
+
+       /* Send AUTH command according to RFC 2554 */
+       send_remote_command(fd, "AUTH CRAM-MD5");
+       if (read_remote(fd, sizeof(buffer), buffer) != 3) {
+               syslog(LOG_DEBUG, "smarthost authentication:"
+                      " AUTH cram-md5 not available: %s", neterr);
+               /* if cram-md5 is not available */
+               free(temp);
+               return (-1);
+       }
+
+       /* skip 3 char status + 1 char space */
+       base64_decode(buffer + 4, temp);
+       hmac_md5((unsigned char *)temp, strlen(temp),
+                (unsigned char *)password, strlen(password), digest);
+       free(temp);
+
+       ascii_digest[32] = 0;
+       for (i = 0; i < 16; i++) {
+               ascii_digest[2*i] = hextab[digest[i] >> 4];
+               ascii_digest[2*i+1] = hextab[digest[i] & 15];
+       }
+
+       /* prepare answer */
+       snprintf(buffer, BUF_SIZE, "%s %s", login, ascii_digest);
+
+       /* encode answer */
+       len = base64_encode(buffer, strlen(buffer), &temp);
+       if (len < 0) {
+               syslog(LOG_ERR, "can not encode auth reply: %m");
+               return (-1);
+       }
 
-#endif /* HAVE_CRYPTO */
+       /* send answer */
+       send_remote_command(fd, "%s", temp);
+       free(temp);
+       if (read_remote(fd, 0, NULL) != 2) {
+               syslog(LOG_WARNING, "remote delivery deferred:"
+                               " AUTH cram-md5 failed: %s", neterr);
+               return (-2);
+       }
+
+       return (0);
+}