Copyright updates:
[exim.git] / src / src / tls.c
... / ...
CommitLineData
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
5/* Copyright (c) University of Cambridge 1995 - 2018 */
6/* Copyright (c) The Exim Maintainers 2020 */
7/* See the file NOTICE for conditions of use and distribution. */
8
9/* This module provides TLS (aka SSL) support for Exim. The code for OpenSSL is
10based on a patch that was originally contributed by Steve Haslam. It was
11adapted from stunnel, a GPL program by Michal Trojnara. The code for GNU TLS is
12based on a patch contributed by Nikos Mavrogiannopoulos. Because these packages
13are so very different, the functions for each are kept in separate files. The
14relevant file is #included as required, after any any common functions.
15
16No cryptographic code is included in Exim. All this module does is to call
17functions from the OpenSSL or GNU TLS libraries. */
18
19
20#include "exim.h"
21#include "transports/smtp.h"
22
23#if !defined(DISABLE_TLS) && !defined(USE_OPENSSL) && !defined(USE_GNUTLS)
24# error One of USE_OPENSSL or USE_GNUTLS must be defined for a TLS build
25#endif
26
27
28#if defined(MACRO_PREDEF) && !defined(DISABLE_TLS)
29# include "macro_predef.h"
30# ifdef USE_GNUTLS
31# include "tls-gnu.c"
32# else
33# include "tls-openssl.c"
34# endif
35#endif
36
37#ifndef MACRO_PREDEF
38
39/* This module is compiled only when it is specifically requested in the
40build-time configuration. However, some compilers don't like compiling empty
41modules, so keep them happy with a dummy when skipping the rest. Make it
42reference itself to stop picky compilers complaining that it is unused, and put
43in a dummy argument to stop even pickier compilers complaining about infinite
44loops. */
45
46#ifdef DISABLE_TLS
47static void dummy(int x) { dummy(x-1); }
48#else
49
50/* Static variables that are used for buffering data by both sets of
51functions and the common functions below.
52
53We're moving away from this; GnuTLS is already using a state, which
54can switch, so we can do TLS callouts during ACLs. */
55
56static const int ssl_xfer_buffer_size = 4096;
57#ifdef USE_OPENSSL
58static uschar *ssl_xfer_buffer = NULL;
59static int ssl_xfer_buffer_lwm = 0;
60static int ssl_xfer_buffer_hwm = 0;
61static int ssl_xfer_eof = FALSE;
62static BOOL ssl_xfer_error = FALSE;
63#endif
64
65
66/*************************************************
67* Expand string; give error on failure *
68*************************************************/
69
70/* If expansion is forced to fail, set the result NULL and return TRUE.
71Other failures return FALSE. For a server, an SMTP response is given.
72
73Arguments:
74 s the string to expand; if NULL just return TRUE
75 name name of string being expanded (for error)
76 result where to put the result
77
78Returns: TRUE if OK; result may still be NULL after forced failure
79*/
80
81static BOOL
82expand_check(const uschar *s, const uschar *name, uschar **result, uschar ** errstr)
83{
84if (!s)
85 *result = NULL;
86else if ( !(*result = expand_string(US s)) /* need to clean up const more */
87 && !f.expand_string_forcedfail
88 )
89 {
90 *errstr = US"Internal error";
91 log_write(0, LOG_MAIN|LOG_PANIC, "expansion of %s failed: %s", name,
92 expand_string_message);
93 return FALSE;
94 }
95return TRUE;
96}
97
98
99/*************************************************
100* Timezone environment flipping *
101*************************************************/
102
103static uschar *
104to_tz(uschar * tz)
105{
106uschar * old = US getenv("TZ");
107(void) setenv("TZ", CCS tz, 1);
108tzset();
109return old;
110}
111
112static void
113restore_tz(uschar * tz)
114{
115if (tz)
116 (void) setenv("TZ", CCS tz, 1);
117else
118 (void) os_unsetenv(US"TZ");
119tzset();
120}
121
122/*************************************************
123* Many functions are package-specific *
124*************************************************/
125
126#ifdef USE_GNUTLS
127# include "tls-gnu.c"
128# include "tlscert-gnu.c"
129# define ssl_xfer_buffer (state_server.xfer_buffer)
130# define ssl_xfer_buffer_lwm (state_server.xfer_buffer_lwm)
131# define ssl_xfer_buffer_hwm (state_server.xfer_buffer_hwm)
132# define ssl_xfer_eof (state_server.xfer_eof)
133# define ssl_xfer_error (state_server.xfer_error)
134#endif
135
136#ifdef USE_OPENSSL
137# include "tls-openssl.c"
138# include "tlscert-openssl.c"
139#endif
140
141
142
143/*************************************************
144* TLS version of ungetc *
145*************************************************/
146
147/* Puts a character back in the input buffer. Only ever
148called once.
149Only used by the server-side TLS.
150
151Arguments:
152 ch the character
153
154Returns: the character
155*/
156
157int
158tls_ungetc(int ch)
159{
160ssl_xfer_buffer[--ssl_xfer_buffer_lwm] = ch;
161return ch;
162}
163
164
165
166/*************************************************
167* TLS version of feof *
168*************************************************/
169
170/* Tests for a previous EOF
171Only used by the server-side TLS.
172
173Arguments: none
174Returns: non-zero if the eof flag is set
175*/
176
177int
178tls_feof(void)
179{
180return (int)ssl_xfer_eof;
181}
182
183
184
185/*************************************************
186* TLS version of ferror *
187*************************************************/
188
189/* Tests for a previous read error, and returns with errno
190restored to what it was when the error was detected.
191Only used by the server-side TLS.
192
193>>>>> Hmm. Errno not handled yet. Where do we get it from? >>>>>
194
195Arguments: none
196Returns: non-zero if the error flag is set
197*/
198
199int
200tls_ferror(void)
201{
202return (int)ssl_xfer_error;
203}
204
205
206/*************************************************
207* TLS version of smtp_buffered *
208*************************************************/
209
210/* Tests for unused chars in the TLS input buffer.
211Only used by the server-side TLS.
212
213Arguments: none
214Returns: TRUE/FALSE
215*/
216
217BOOL
218tls_smtp_buffered(void)
219{
220return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm;
221}
222
223
224#endif /*DISABLE_TLS*/
225
226void
227tls_modify_variables(tls_support * dest_tsp)
228{
229modify_variable(US"tls_bits", &dest_tsp->bits);
230modify_variable(US"tls_certificate_verified", &dest_tsp->certificate_verified);
231modify_variable(US"tls_cipher", &dest_tsp->cipher);
232modify_variable(US"tls_peerdn", &dest_tsp->peerdn);
233#ifdef USE_OPENSSL
234modify_variable(US"tls_sni", &dest_tsp->sni);
235#endif
236}
237
238
239#ifndef DISABLE_TLS
240/************************************************
241* TLS certificate name operations *
242************************************************/
243
244/* Convert an rfc4514 DN to an exim comma-sep list.
245Backslashed commas need to be replaced by doublecomma
246for Exim's list quoting. We modify the given string
247inplace.
248*/
249
250static void
251dn_to_list(uschar * dn)
252{
253for (uschar * cp = dn; *cp; cp++)
254 if (cp[0] == '\\' && cp[1] == ',')
255 *cp++ = ',';
256}
257
258
259/* Extract fields of a given type from an RFC4514-
260format Distinguished Name. Return an Exim list.
261NOTE: We modify the supplied dn string during operation.
262
263Arguments:
264 dn Distinguished Name string
265 mod list containing optional output list-sep and
266 field selector match, comma-separated
267Return:
268 allocated string with list of matching fields,
269 field type stripped
270*/
271
272uschar *
273tls_field_from_dn(uschar * dn, const uschar * mod)
274{
275int insep = ',';
276uschar outsep = '\n';
277uschar * ele;
278uschar * match = NULL;
279int len;
280gstring * list = NULL;
281
282while ((ele = string_nextinlist(&mod, &insep, NULL, 0)))
283 if (ele[0] != '>')
284 match = ele; /* field tag to match */
285 else if (ele[1])
286 outsep = ele[1]; /* nondefault output separator */
287
288dn_to_list(dn);
289insep = ',';
290len = match ? Ustrlen(match) : -1;
291while ((ele = string_nextinlist(CUSS &dn, &insep, NULL, 0)))
292 if ( !match
293 || Ustrncmp(ele, match, len) == 0 && ele[len] == '='
294 )
295 list = string_append_listele(list, outsep, ele+len+1);
296return string_from_gstring(list);
297}
298
299
300/* Compare a domain name with a possibly-wildcarded name. Wildcards
301are restricted to a single one, as the first element of patterns
302having at least three dot-separated elements. Case-independent.
303Return TRUE for a match
304*/
305static BOOL
306is_name_match(const uschar * name, const uschar * pat)
307{
308uschar * cp;
309return *pat == '*' /* possible wildcard match */
310 ? *++pat == '.' /* starts star, dot */
311 && !Ustrchr(++pat, '*') /* has no more stars */
312 && Ustrchr(pat, '.') /* and has another dot. */
313 && (cp = Ustrchr(name, '.'))/* The name has at least one dot */
314 && strcmpic(++cp, pat) == 0 /* and we only compare after it. */
315 : !Ustrchr(pat+1, '*')
316 && strcmpic(name, pat) == 0;
317}
318
319/* Compare a list of names with the dnsname elements
320of the Subject Alternate Name, if any, and the
321Subject otherwise.
322
323Arguments:
324 namelist names to compare
325 cert certificate
326
327Returns:
328 TRUE/FALSE
329*/
330
331BOOL
332tls_is_name_for_cert(const uschar * namelist, void * cert)
333{
334uschar * altnames = tls_cert_subject_altname(cert, US"dns");
335uschar * subjdn;
336uschar * certname;
337int cmp_sep = 0;
338uschar * cmpname;
339
340if ((altnames = tls_cert_subject_altname(cert, US"dns")))
341 {
342 int alt_sep = '\n';
343 while ((cmpname = string_nextinlist(&namelist, &cmp_sep, NULL, 0)))
344 {
345 const uschar * an = altnames;
346 while ((certname = string_nextinlist(&an, &alt_sep, NULL, 0)))
347 if (is_name_match(cmpname, certname))
348 return TRUE;
349 }
350 }
351
352else if ((subjdn = tls_cert_subject(cert, NULL)))
353 {
354 int sn_sep = ',';
355
356 dn_to_list(subjdn);
357 while ((cmpname = string_nextinlist(&namelist, &cmp_sep, NULL, 0)))
358 {
359 const uschar * sn = subjdn;
360 while ((certname = string_nextinlist(&sn, &sn_sep, NULL, 0)))
361 if ( *certname++ == 'C'
362 && *certname++ == 'N'
363 && *certname++ == '='
364 && is_name_match(cmpname, certname)
365 )
366 return TRUE;
367 }
368 }
369return FALSE;
370}
371
372
373/* Environment cleanup: The GnuTLS library uses SSLKEYLOGFILE in the environment
374and writes a file by that name. Our OpenSSL code does the same, using keying
375info from the library API.
376The GnuTLS support only works if exim is run by root, not taking advantage of
377the setuid bit.
378You can use either the external environment (modulo the keep_environment config)
379or the add_environment config option for SSLKEYLOGFILE; the latter takes
380precedence.
381
382If the path is absolute, require it starts with the spooldir; otherwise delete
383the env variable. If relative, prefix the spooldir.
384*/
385void
386tls_clean_env(void)
387{
388uschar * path = US getenv("SSLKEYLOGFILE");
389if (path)
390 if (!*path)
391 unsetenv("SSLKEYLOGFILE");
392 else if (*path != '/')
393 {
394 DEBUG(D_tls)
395 debug_printf("prepending spooldir to env SSLKEYLOGFILE\n");
396 setenv("SSLKEYLOGFILE", CCS string_sprintf("%s/%s", spool_directory, path), 1);
397 }
398 else if (Ustrncmp(path, spool_directory, Ustrlen(spool_directory)) != 0)
399 {
400 DEBUG(D_tls)
401 debug_printf("removing env SSLKEYLOGFILE=%s: not under spooldir\n", path);
402 unsetenv("SSLKEYLOGFILE");
403 }
404}
405
406/*************************************************
407* Drop privs for checking TLS config *
408*************************************************/
409
410/* We want to validate TLS options during readconf, but do not want to be
411root when we call into the TLS library, in case of library linkage errors
412which cause segfaults; before this check, those were always done as the Exim
413runtime user and it makes sense to continue with that.
414
415Assumes: tls_require_ciphers has been set, if it will be
416 exim_user has been set, if it will be
417 exim_group has been set, if it will be
418
419Returns: bool for "okay"; false will cause caller to immediately exit.
420*/
421
422BOOL
423tls_dropprivs_validate_require_cipher(BOOL nowarn)
424{
425const uschar *errmsg;
426pid_t pid;
427int rc, status;
428void (*oldsignal)(int);
429
430/* If TLS will never be used, no point checking ciphers */
431
432if ( !tls_advertise_hosts
433 || !*tls_advertise_hosts
434 || Ustrcmp(tls_advertise_hosts, ":") == 0
435 )
436 return TRUE;
437else if (!nowarn && !tls_certificate)
438 log_write(0, LOG_MAIN,
439 "Warning: No server certificate defined; will use a selfsigned one.\n"
440 " Suggested action: either install a certificate or change tls_advertise_hosts option");
441
442oldsignal = signal(SIGCHLD, SIG_DFL);
443
444fflush(NULL);
445if ((pid = exim_fork(US"cipher-validate")) < 0)
446 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "fork failed for TLS check");
447
448if (pid == 0)
449 {
450 /* in some modes, will have dropped privilege already */
451 if (!geteuid())
452 exim_setugid(exim_uid, exim_gid, FALSE,
453 US"calling tls_validate_require_cipher");
454
455 if ((errmsg = tls_validate_require_cipher()))
456 log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
457 "tls_require_ciphers invalid: %s", errmsg);
458 fflush(NULL);
459 exim_underbar_exit(EXIT_SUCCESS);
460 }
461
462do {
463 rc = waitpid(pid, &status, 0);
464} while (rc < 0 && errno == EINTR);
465
466DEBUG(D_tls)
467 debug_printf("tls_validate_require_cipher child %d ended: status=0x%x\n",
468 (int)pid, status);
469
470signal(SIGCHLD, oldsignal);
471
472return status == 0;
473}
474
475
476
477
478#endif /*!DISABLE_TLS*/
479#endif /*!MACRO_PREDEF*/
480
481/* vi: aw ai sw=2
482*/
483/* End of tls.c */