CVE-2020-28024: Heap buffer underflow in smtp_ungetc()
[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 if (ssl_xfer_buffer_lwm <= 0)
161   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in tls_ungetc");
162
163 ssl_xfer_buffer[--ssl_xfer_buffer_lwm] = ch;
164 return ch;
165 }
166
167
168
169 /*************************************************
170 *           TLS version of feof                  *
171 *************************************************/
172
173 /* Tests for a previous EOF
174 Only used by the server-side TLS.
175
176 Arguments:     none
177 Returns:       non-zero if the eof flag is set
178 */
179
180 int
181 tls_feof(void)
182 {
183 return (int)ssl_xfer_eof;
184 }
185
186
187
188 /*************************************************
189 *              TLS version of ferror             *
190 *************************************************/
191
192 /* Tests for a previous read error, and returns with errno
193 restored to what it was when the error was detected.
194 Only used by the server-side TLS.
195
196 >>>>> Hmm. Errno not handled yet. Where do we get it from?  >>>>>
197
198 Arguments:     none
199 Returns:       non-zero if the error flag is set
200 */
201
202 int
203 tls_ferror(void)
204 {
205 return (int)ssl_xfer_error;
206 }
207
208
209 /*************************************************
210 *           TLS version of smtp_buffered         *
211 *************************************************/
212
213 /* Tests for unused chars in the TLS input buffer.
214 Only used by the server-side TLS.
215
216 Arguments:     none
217 Returns:       TRUE/FALSE
218 */
219
220 BOOL
221 tls_smtp_buffered(void)
222 {
223 return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm;
224 }
225
226
227 #endif  /*DISABLE_TLS*/
228
229 void
230 tls_modify_variables(tls_support * dest_tsp)
231 {
232 modify_variable(US"tls_bits",                 &dest_tsp->bits);
233 modify_variable(US"tls_certificate_verified", &dest_tsp->certificate_verified);
234 modify_variable(US"tls_cipher",               &dest_tsp->cipher);
235 modify_variable(US"tls_peerdn",               &dest_tsp->peerdn);
236 #ifdef USE_OPENSSL
237 modify_variable(US"tls_sni",                  &dest_tsp->sni);
238 #endif
239 }
240
241
242 #ifndef DISABLE_TLS
243 /************************************************
244 *       TLS certificate name operations         *
245 ************************************************/
246
247 /* Convert an rfc4514 DN to an exim comma-sep list.
248 Backslashed commas need to be replaced by doublecomma
249 for Exim's list quoting.  We modify the given string
250 inplace.
251 */
252
253 static void
254 dn_to_list(uschar * dn)
255 {
256 for (uschar * cp = dn; *cp; cp++)
257   if (cp[0] == '\\' && cp[1] == ',')
258     *cp++ = ',';
259 }
260
261
262 /* Extract fields of a given type from an RFC4514-
263 format Distinguished Name.  Return an Exim list.
264 NOTE: We modify the supplied dn string during operation.
265
266 Arguments:
267         dn      Distinguished Name string
268         mod     list containing optional output list-sep and
269                 field selector match, comma-separated
270 Return:
271         allocated string with list of matching fields,
272         field type stripped
273 */
274
275 uschar *
276 tls_field_from_dn(uschar * dn, const uschar * mod)
277 {
278 int insep = ',';
279 uschar outsep = '\n';
280 uschar * ele;
281 uschar * match = NULL;
282 int len;
283 gstring * list = NULL;
284
285 while ((ele = string_nextinlist(&mod, &insep, NULL, 0)))
286   if (ele[0] != '>')
287     match = ele;        /* field tag to match */
288   else if (ele[1])
289     outsep = ele[1];    /* nondefault output separator */
290
291 dn_to_list(dn);
292 insep = ',';
293 len = match ? Ustrlen(match) : -1;
294 while ((ele = string_nextinlist(CUSS &dn, &insep, NULL, 0)))
295   if (  !match
296      || Ustrncmp(ele, match, len) == 0 && ele[len] == '='
297      )
298     list = string_append_listele(list, outsep, ele+len+1);
299 return string_from_gstring(list);
300 }
301
302
303 /* Compare a domain name with a possibly-wildcarded name. Wildcards
304 are restricted to a single one, as the first element of patterns
305 having at least three dot-separated elements.  Case-independent.
306 Return TRUE for a match
307 */
308 static BOOL
309 is_name_match(const uschar * name, const uschar * pat)
310 {
311 uschar * cp;
312 return *pat == '*'              /* possible wildcard match */
313   ?    *++pat == '.'            /* starts star, dot              */
314     && !Ustrchr(++pat, '*')     /* has no more stars             */
315     && Ustrchr(pat, '.')        /* and has another dot.          */
316     && (cp = Ustrchr(name, '.'))/* The name has at least one dot */
317     && strcmpic(++cp, pat) == 0 /* and we only compare after it. */
318   :    !Ustrchr(pat+1, '*')
319     && strcmpic(name, pat) == 0;
320 }
321
322 /* Compare a list of names with the dnsname elements
323 of the Subject Alternate Name, if any, and the
324 Subject otherwise.
325
326 Arguments:
327         namelist names to compare
328         cert     certificate
329
330 Returns:
331         TRUE/FALSE
332 */
333
334 BOOL
335 tls_is_name_for_cert(const uschar * namelist, void * cert)
336 {
337 uschar * altnames = tls_cert_subject_altname(cert, US"dns");
338 uschar * subjdn;
339 uschar * certname;
340 int cmp_sep = 0;
341 uschar * cmpname;
342
343 if ((altnames = tls_cert_subject_altname(cert, US"dns")))
344   {
345   int alt_sep = '\n';
346   while ((cmpname = string_nextinlist(&namelist, &cmp_sep, NULL, 0)))
347     {
348     const uschar * an = altnames;
349     while ((certname = string_nextinlist(&an, &alt_sep, NULL, 0)))
350       if (is_name_match(cmpname, certname))
351         return TRUE;
352     }
353   }
354
355 else if ((subjdn = tls_cert_subject(cert, NULL)))
356   {
357   int sn_sep = ',';
358
359   dn_to_list(subjdn);
360   while ((cmpname = string_nextinlist(&namelist, &cmp_sep, NULL, 0)))
361     {
362     const uschar * sn = subjdn;
363     while ((certname = string_nextinlist(&sn, &sn_sep, NULL, 0)))
364       if (  *certname++ == 'C'
365          && *certname++ == 'N'
366          && *certname++ == '='
367          && is_name_match(cmpname, certname)
368          )
369         return TRUE;
370     }
371   }
372 return FALSE;
373 }
374
375
376 /* Environment cleanup: The GnuTLS library uses SSLKEYLOGFILE in the environment
377 and writes a file by that name.  Our OpenSSL code does the same, using keying
378 info from the library API.
379 The GnuTLS support only works if exim is run by root, not taking advantage of
380 the setuid bit.
381 You can use either the external environment (modulo the keep_environment config)
382 or the add_environment config option for SSLKEYLOGFILE; the latter takes
383 precedence.
384
385 If the path is absolute, require it starts with the spooldir; otherwise delete
386 the env variable.  If relative, prefix the spooldir.
387 */
388 void
389 tls_clean_env(void)
390 {
391 uschar * path = US getenv("SSLKEYLOGFILE");
392 if (path)
393   if (!*path)
394     unsetenv("SSLKEYLOGFILE");
395   else if (*path != '/')
396     {
397     DEBUG(D_tls)
398       debug_printf("prepending spooldir to  env SSLKEYLOGFILE\n");
399     setenv("SSLKEYLOGFILE", CCS string_sprintf("%s/%s", spool_directory, path), 1);
400     }
401   else if (Ustrncmp(path, spool_directory, Ustrlen(spool_directory)) != 0)
402     {
403     DEBUG(D_tls)
404       debug_printf("removing env SSLKEYLOGFILE=%s: not under spooldir\n", path);
405     unsetenv("SSLKEYLOGFILE");
406     }
407 }
408
409 /*************************************************
410 *       Drop privs for checking TLS config      *
411 *************************************************/
412
413 /* We want to validate TLS options during readconf, but do not want to be
414 root when we call into the TLS library, in case of library linkage errors
415 which cause segfaults; before this check, those were always done as the Exim
416 runtime user and it makes sense to continue with that.
417
418 Assumes:  tls_require_ciphers has been set, if it will be
419           exim_user has been set, if it will be
420           exim_group has been set, if it will be
421
422 Returns:  bool for "okay"; false will cause caller to immediately exit.
423 */
424
425 BOOL
426 tls_dropprivs_validate_require_cipher(BOOL nowarn)
427 {
428 const uschar *errmsg;
429 pid_t pid;
430 int rc, status;
431 void (*oldsignal)(int);
432
433 /* If TLS will never be used, no point checking ciphers */
434
435 if (  !tls_advertise_hosts
436    || !*tls_advertise_hosts
437    || Ustrcmp(tls_advertise_hosts, ":") == 0
438    )
439   return TRUE;
440 else if (!nowarn && !tls_certificate)
441   log_write(0, LOG_MAIN,
442     "Warning: No server certificate defined; will use a selfsigned one.\n"
443     " Suggested action: either install a certificate or change tls_advertise_hosts option");
444
445 oldsignal = signal(SIGCHLD, SIG_DFL);
446
447 fflush(NULL);
448 if ((pid = exim_fork(US"cipher-validate")) < 0)
449   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "fork failed for TLS check");
450
451 if (pid == 0)
452   {
453   /* in some modes, will have dropped privilege already */
454   if (!geteuid())
455     exim_setugid(exim_uid, exim_gid, FALSE,
456         US"calling tls_validate_require_cipher");
457
458   if ((errmsg = tls_validate_require_cipher()))
459     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
460         "tls_require_ciphers invalid: %s", errmsg);
461   fflush(NULL);
462   exim_underbar_exit(EXIT_SUCCESS);
463   }
464
465 do {
466   rc = waitpid(pid, &status, 0);
467 } while (rc < 0 && errno == EINTR);
468
469 DEBUG(D_tls)
470   debug_printf("tls_validate_require_cipher child %d ended: status=0x%x\n",
471       (int)pid, status);
472
473 signal(SIGCHLD, oldsignal);
474
475 return status == 0;
476 }
477
478
479
480
481 #endif  /*!DISABLE_TLS*/
482 #endif  /*!MACRO_PREDEF*/
483
484 /* vi: aw ai sw=2
485 */
486 /* End of tls.c */