1/*
2 * Copyright (c) 2008 The DragonFly Project.  All rights reserved.
3 *
4 * This code is derived from software contributed to The DragonFly Project
5 * by Matthias Schmidt <matthias@dragonflybsd.org>, University of Marburg,
6 * Germany.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in
16 *    the documentation and/or other materials provided with the
17 *    distribution.
18 * 3. Neither the name of The DragonFly Project nor the names of its
19 *    contributors may be used to endorse or promote products derived
20 *    from this software without specific, prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
26 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
28 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
30 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36#include <openssl/x509.h>
37#include <openssl/md5.h>
38#include <openssl/ssl.h>
39#include <openssl/err.h>
40#include <openssl/pem.h>
41#include <openssl/rand.h>
42
43#include <string.h>
44#include <syslog.h>
45
46#include "dma.h"
47
48static int
49init_cert_file(SSL_CTX *ctx, const char *path)
50{
51	int error;
52
53	/* Load certificate into ctx */
54	error = SSL_CTX_use_certificate_chain_file(ctx, path);
55	if (error < 1) {
56		syslog(LOG_ERR, "SSL: Cannot load certificate `%s': %s", path, ssl_errstr());
57		return (-1);
58	}
59
60	/* Add private key to ctx */
61	error = SSL_CTX_use_PrivateKey_file(ctx, path, SSL_FILETYPE_PEM);
62	if (error < 1) {
63		syslog(LOG_ERR, "SSL: Cannot load private key `%s': %s", path, ssl_errstr());
64		return (-1);
65	}
66
67	/*
68	 * Check the consistency of a private key with the corresponding
69         * certificate
70	 */
71	error = SSL_CTX_check_private_key(ctx);
72	if (error < 1) {
73		syslog(LOG_ERR, "SSL: Cannot check private key: %s", ssl_errstr());
74		return (-1);
75	}
76
77	return (0);
78}
79
80int
81smtp_init_crypto(int fd, int feature)
82{
83	SSL_CTX *ctx = NULL;
84#if (OPENSSL_VERSION_NUMBER >= 0x00909000L)
85	const SSL_METHOD *meth = NULL;
86#else
87	SSL_METHOD *meth = NULL;
88#endif
89	X509 *cert;
90	int error;
91
92	/* XXX clean up on error/close */
93	/* Init SSL library */
94	SSL_library_init();
95	SSL_load_error_strings();
96
97	// Allow any possible version
98#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
99	meth = TLS_client_method();
100#else
101	meth = SSLv23_client_method();
102#endif
103
104	ctx = SSL_CTX_new(meth);
105	if (ctx == NULL) {
106		syslog(LOG_WARNING, "remote delivery deferred: SSL init failed: %s", ssl_errstr());
107		return (1);
108	}
109
110	/* User supplied a certificate */
111	if (config.certfile != NULL) {
112		error = init_cert_file(ctx, config.certfile);
113		if (error) {
114			syslog(LOG_WARNING, "remote delivery deferred");
115			return (1);
116		}
117	}
118
119	/*
120	 * If the user wants STARTTLS, we have to send EHLO here
121	 */
122	if (((feature & SECURETRANS) != 0) &&
123	     (feature & STARTTLS) != 0) {
124		/* TLS init phase, disable SSL_write */
125		config.features |= NOSSL;
126
127		send_remote_command(fd, "EHLO %s", hostname());
128		if (read_remote(fd, 0, NULL) == 2) {
129			send_remote_command(fd, "STARTTLS");
130			if (read_remote(fd, 0, NULL) != 2) {
131				if ((feature & TLS_OPP) == 0) {
132					syslog(LOG_ERR, "remote delivery deferred: STARTTLS not available: %s", neterr);
133					return (1);
134				} else {
135					syslog(LOG_INFO, "in opportunistic TLS mode, STARTTLS not available: %s", neterr);
136					return (0);
137				}
138			}
139		}
140		/* End of TLS init phase, enable SSL_write/read */
141		config.features &= ~NOSSL;
142	}
143
144	config.ssl = SSL_new(ctx);
145	if (config.ssl == NULL) {
146		syslog(LOG_NOTICE, "remote delivery deferred: SSL struct creation failed: %s",
147		       ssl_errstr());
148		return (1);
149	}
150
151	/* Set ssl to work in client mode */
152	SSL_set_connect_state(config.ssl);
153
154	/* Set fd for SSL in/output */
155	error = SSL_set_fd(config.ssl, fd);
156	if (error == 0) {
157		syslog(LOG_NOTICE, "remote delivery deferred: SSL set fd failed: %s",
158		       ssl_errstr());
159		return (1);
160	}
161
162	/* Open SSL connection */
163	error = SSL_connect(config.ssl);
164	if (error < 0) {
165		syslog(LOG_ERR, "remote delivery deferred: SSL handshake failed fatally: %s",
166		       ssl_errstr());
167		return (1);
168	}
169
170	/* Get peer certificate */
171	cert = SSL_get_peer_certificate(config.ssl);
172	if (cert == NULL) {
173		syslog(LOG_WARNING, "remote delivery deferred: Peer did not provide certificate: %s",
174		       ssl_errstr());
175	}
176	X509_free(cert);
177
178	return (0);
179}
180
181/*
182 * hmac_md5() taken out of RFC 2104.  This RFC was written by H. Krawczyk,
183 * M. Bellare and R. Canetti.
184 *
185 * text      pointer to data stream
186 * text_len  length of data stream
187 * key       pointer to authentication key
188 * key_len   length of authentication key
189 * digest    caller digest to be filled int
190 */
191void
192hmac_md5(unsigned char *text, int text_len, unsigned char *key, int key_len,
193    unsigned char* digest)
194{
195        MD5_CTX context;
196        unsigned char k_ipad[65];    /* inner padding -
197                                      * key XORd with ipad
198                                      */
199        unsigned char k_opad[65];    /* outer padding -
200                                      * key XORd with opad
201                                      */
202        unsigned char tk[16];
203        int i;
204        /* if key is longer than 64 bytes reset it to key=MD5(key) */
205        if (key_len > 64) {
206
207                MD5_CTX      tctx;
208
209                MD5_Init(&tctx);
210                MD5_Update(&tctx, key, key_len);
211                MD5_Final(tk, &tctx);
212
213                key = tk;
214                key_len = 16;
215        }
216
217        /*
218         * the HMAC_MD5 transform looks like:
219         *
220         * MD5(K XOR opad, MD5(K XOR ipad, text))
221         *
222         * where K is an n byte key
223         * ipad is the byte 0x36 repeated 64 times
224	 *
225         * opad is the byte 0x5c repeated 64 times
226         * and text is the data being protected
227         */
228
229        /* start out by storing key in pads */
230        bzero( k_ipad, sizeof k_ipad);
231        bzero( k_opad, sizeof k_opad);
232        bcopy( key, k_ipad, key_len);
233        bcopy( key, k_opad, key_len);
234
235        /* XOR key with ipad and opad values */
236        for (i=0; i<64; i++) {
237                k_ipad[i] ^= 0x36;
238                k_opad[i] ^= 0x5c;
239        }
240        /*
241         * perform inner MD5
242         */
243        MD5_Init(&context);                   /* init context for 1st
244                                              * pass */
245        MD5_Update(&context, k_ipad, 64);     /* start with inner pad */
246        MD5_Update(&context, text, text_len); /* then text of datagram */
247        MD5_Final(digest, &context);          /* finish up 1st pass */
248        /*
249         * perform outer MD5
250         */
251        MD5_Init(&context);                   /* init context for 2nd
252                                              * pass */
253        MD5_Update(&context, k_opad, 64);     /* start with outer pad */
254        MD5_Update(&context, digest, 16);     /* then results of 1st
255                                              * hash */
256        MD5_Final(digest, &context);          /* finish up 2nd pass */
257}
258
259/*
260 * CRAM-MD5 authentication
261 */
262int
263smtp_auth_md5(int fd, char *login, char *password)
264{
265	unsigned char digest[BUF_SIZE];
266	char buffer[BUF_SIZE], ascii_digest[33];
267	char *temp;
268	int len, i;
269	static char hextab[] = "0123456789abcdef";
270
271	temp = calloc(BUF_SIZE, 1);
272	memset(buffer, 0, sizeof(buffer));
273	memset(digest, 0, sizeof(digest));
274	memset(ascii_digest, 0, sizeof(ascii_digest));
275
276	/* Send AUTH command according to RFC 2554 */
277	send_remote_command(fd, "AUTH CRAM-MD5");
278	if (read_remote(fd, sizeof(buffer), buffer) != 3) {
279		syslog(LOG_DEBUG, "smarthost authentication:"
280		       " AUTH cram-md5 not available: %s", neterr);
281		/* if cram-md5 is not available */
282		free(temp);
283		return (-1);
284	}
285
286	/* skip 3 char status + 1 char space */
287	base64_decode(buffer + 4, temp);
288	hmac_md5((unsigned char *)temp, strlen(temp),
289		 (unsigned char *)password, strlen(password), digest);
290	free(temp);
291
292	ascii_digest[32] = 0;
293	for (i = 0; i < 16; i++) {
294		ascii_digest[2*i] = hextab[digest[i] >> 4];
295		ascii_digest[2*i+1] = hextab[digest[i] & 15];
296	}
297
298	/* prepare answer */
299	snprintf(buffer, BUF_SIZE, "%s %s", login, ascii_digest);
300
301	/* encode answer */
302	len = base64_encode(buffer, strlen(buffer), &temp);
303	if (len < 0) {
304		syslog(LOG_ERR, "can not encode auth reply: %m");
305		return (-1);
306	}
307
308	/* send answer */
309	send_remote_command(fd, "%s", temp);
310	free(temp);
311	if (read_remote(fd, 0, NULL) != 2) {
312		syslog(LOG_WARNING, "remote delivery deferred:"
313				" AUTH cram-md5 failed: %s", neterr);
314		return (-2);
315	}
316
317	return (0);
318}
319