e5aabc6b4b65823b40b9664c119b14784fcd0cd6
[users/heiko/exim.git] / src / src / tls.c
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
10 based on a patch that was originally contributed by Steve Haslam. It was
11 adapted from stunnel, a GPL program by Michal Trojnara. The code for GNU TLS is
12 based on a patch contributed by Nikos Mavrogiannopoulos. Because these packages
13 are so very different, the functions for each are kept in separate files. The
14 relevant file is #included as required, after any any common functions.
15
16 No cryptographic code is included in Exim. All this module does is to call
17 functions 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
40 build-time configuration. However, some compilers don't like compiling empty
41 modules, so keep them happy with a dummy when skipping the rest. Make it
42 reference itself to stop picky compilers complaining that it is unused, and put
43 in a dummy argument to stop even pickier compilers complaining about infinite
44 loops. */
45
46 #ifdef DISABLE_TLS
47 static void dummy(int x) { dummy(x-1); }
48 #else
49
50 /* Static variables that are used for buffering data by both sets of
51 functions and the common functions below.
52
53 We're moving away from this; GnuTLS is already using a state, which
54 can switch, so we can do TLS callouts during ACLs. */
55
56 static const int ssl_xfer_buffer_size = 4096;
57 #ifdef USE_OPENSSL
58 static uschar *ssl_xfer_buffer = NULL;
59 static int ssl_xfer_buffer_lwm = 0;
60 static int ssl_xfer_buffer_hwm = 0;
61 static int ssl_xfer_eof = FALSE;
62 static 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.
71 Other failures return FALSE. For a server, an SMTP response is given.
72
73 Arguments:
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
78 Returns:    TRUE if OK; result may still be NULL after forced failure
79 */
80
81 static BOOL
82 expand_check(const uschar *s, const uschar *name, uschar **result, uschar ** errstr)
83 {
84 if (!s)
85   *result = NULL;
86 else 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   }
95 return TRUE;
96 }
97
98
99 /*************************************************
100 *        Timezone environment flipping           *
101 *************************************************/
102
103 static uschar *
104 to_tz(uschar * tz)
105 {
106 uschar * old = US getenv("TZ");
107 (void) setenv("TZ", CCS tz, 1);
108 tzset();
109 return old;
110 }
111
112 static void
113 restore_tz(uschar * tz)
114 {
115 if (tz)
116   (void) setenv("TZ", CCS tz, 1);
117 else
118   (void) os_unsetenv(US"TZ");
119 tzset();
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
148 called once.
149 Only used by the server-side TLS.
150
151 Arguments:
152   ch           the character
153
154 Returns:       the character
155 */
156
157 int
158 tls_ungetc(int ch)
159 {
160 ssl_xfer_buffer[--ssl_xfer_buffer_lwm] = ch;
161 return ch;
162 }
163
164
165
166 /*************************************************
167 *           TLS version of feof                  *
168 *************************************************/
169
170 /* Tests for a previous EOF
171 Only used by the server-side TLS.
172
173 Arguments:     none
174 Returns:       non-zero if the eof flag is set
175 */
176
177 int
178 tls_feof(void)
179 {
180 return (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
190 restored to what it was when the error was detected.
191 Only used by the server-side TLS.
192
193 >>>>> Hmm. Errno not handled yet. Where do we get it from?  >>>>>
194
195 Arguments:     none
196 Returns:       non-zero if the error flag is set
197 */
198
199 int
200 tls_ferror(void)
201 {
202 return (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.
211 Only used by the server-side TLS.
212
213 Arguments:     none
214 Returns:       TRUE/FALSE
215 */
216
217 BOOL
218 tls_smtp_buffered(void)
219 {
220 return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm;
221 }
222
223
224 #endif  /*DISABLE_TLS*/
225
226 void
227 tls_modify_variables(tls_support * dest_tsp)
228 {
229 modify_variable(US"tls_bits",                 &dest_tsp->bits);
230 modify_variable(US"tls_certificate_verified", &dest_tsp->certificate_verified);
231 modify_variable(US"tls_cipher",               &dest_tsp->cipher);
232 modify_variable(US"tls_peerdn",               &dest_tsp->peerdn);
233 #ifdef USE_OPENSSL
234 modify_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.
245 Backslashed commas need to be replaced by doublecomma
246 for Exim's list quoting.  We modify the given string
247 inplace.
248 */
249
250 static void
251 dn_to_list(uschar * dn)
252 {
253 for (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-
260 format Distinguished Name.  Return an Exim list.
261 NOTE: We modify the supplied dn string during operation.
262
263 Arguments:
264         dn      Distinguished Name string
265         mod     list containing optional output list-sep and
266                 field selector match, comma-separated
267 Return:
268         allocated string with list of matching fields,
269         field type stripped
270 */
271
272 uschar *
273 tls_field_from_dn(uschar * dn, const uschar * mod)
274 {
275 int insep = ',';
276 uschar outsep = '\n';
277 uschar * ele;
278 uschar * match = NULL;
279 int len;
280 gstring * list = NULL;
281
282 while ((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
288 dn_to_list(dn);
289 insep = ',';
290 len = match ? Ustrlen(match) : -1;
291 while ((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);
296 return string_from_gstring(list);
297 }
298
299
300 /* Compare a domain name with a possibly-wildcarded name. Wildcards
301 are restricted to a single one, as the first element of patterns
302 having at least three dot-separated elements.  Case-independent.
303 Return TRUE for a match
304 */
305 static BOOL
306 is_name_match(const uschar * name, const uschar * pat)
307 {
308 uschar * cp;
309 return *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
320 of the Subject Alternate Name, if any, and the
321 Subject otherwise.
322
323 Arguments:
324         namelist names to compare
325         cert     certificate
326
327 Returns:
328         TRUE/FALSE
329 */
330
331 BOOL
332 tls_is_name_for_cert(const uschar * namelist, void * cert)
333 {
334 uschar * altnames = tls_cert_subject_altname(cert, US"dns");
335 uschar * subjdn;
336 uschar * certname;
337 int cmp_sep = 0;
338 uschar * cmpname;
339
340 if ((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
352 else 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   }
369 return FALSE;
370 }
371
372
373 /* Environment cleanup: The GnuTLS library uses SSLKEYLOGFILE in the environment
374 and writes a file by that name.  Our OpenSSL code does the same, using keying
375 info from the library API.
376 The GnuTLS support only works if exim is run by root, not taking advantage of
377 the setuid bit.
378 You can use either the external environment (modulo the keep_environment config)
379 or the add_environment config option for SSLKEYLOGFILE; the latter takes
380 precedence.
381
382 If the path is absolute, require it starts with the spooldir; otherwise delete
383 the env variable.  If relative, prefix the spooldir.
384 */
385 void
386 tls_clean_env(void)
387 {
388 uschar * path = US getenv("SSLKEYLOGFILE");
389 if (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
411 root when we call into the TLS library, in case of library linkage errors
412 which cause segfaults; before this check, those were always done as the Exim
413 runtime user and it makes sense to continue with that.
414
415 Assumes:  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
419 Returns:  bool for "okay"; false will cause caller to immediately exit.
420 */
421
422 BOOL
423 tls_dropprivs_validate_require_cipher(BOOL nowarn)
424 {
425 const uschar *errmsg;
426 pid_t pid;
427 int rc, status;
428 void (*oldsignal)(int);
429
430 /* If TLS will never be used, no point checking ciphers */
431
432 if (  !tls_advertise_hosts
433    || !*tls_advertise_hosts
434    || Ustrcmp(tls_advertise_hosts, ":") == 0
435    )
436   return TRUE;
437 else 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
442 oldsignal = signal(SIGCHLD, SIG_DFL);
443
444 fflush(NULL);
445 if ((pid = exim_fork(US"cipher-validate")) < 0)
446   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "fork failed for TLS check");
447
448 if (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
462 do {
463   rc = waitpid(pid, &status, 0);
464 } while (rc < 0 && errno == EINTR);
465
466 DEBUG(D_tls)
467   debug_printf("tls_validate_require_cipher child %d ended: status=0x%x\n",
468       (int)pid, status);
469
470 signal(SIGCHLD, oldsignal);
471
472 return 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 */