~ubuntu-branches/ubuntu/trusty/libesmtp/trusty-proposed

« back to all changes in this revision

Viewing changes to smtp-tls.c

  • Committer: Bazaar Package Importer
  • Author(s): Jeremy T. Bouse
  • Date: 2002-03-06 08:37:48 UTC
  • Revision ID: james.westby@ubuntu.com-20020306083748-ihmt32mddsslvg5i
Tags: upstream-0.8.11
ImportĀ upstreamĀ versionĀ 0.8.11

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *  This file is part of libESMTP, a library for submission of RFC 2822
 
3
 *  formatted electronic mail messages using the SMTP protocol described
 
4
 *  in RFC 2821.
 
5
 *
 
6
 *  Copyright (C) 2001,2002  Brian Stafford  <brian@stafford.uklinux.net>
 
7
 *
 
8
 *  This library is free software; you can redistribute it and/or
 
9
 *  modify it under the terms of the GNU Lesser General Public
 
10
 *  License as published by the Free Software Foundation; either
 
11
 *  version 2.1 of the License, or (at your option) any later version.
 
12
 *
 
13
 *  This library is distributed in the hope that it will be useful,
 
14
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
15
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
16
 *  Lesser General Public License for more details.
 
17
 *
 
18
 *  You should have received a copy of the GNU Lesser General Public
 
19
 *  License along with this library; if not, write to the Free Software
 
20
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
21
 */
 
22
 
 
23
#ifdef HAVE_CONFIG_H
 
24
#include <config.h>
 
25
#endif
 
26
 
 
27
/* Support for the SMTP STARTTLS verb.
 
28
 */
 
29
 
 
30
 
 
31
#ifdef USE_TLS
 
32
 
 
33
/* This stuff doesn't belong here */
 
34
/* vvvvvvvvvvv */
 
35
#include <sys/types.h>
 
36
#include <sys/stat.h>
 
37
#include <unistd.h>
 
38
#include <string.h>
 
39
#include <missing.h> /* declarations for missing library functions */
 
40
/* ^^^^^^^^^^^ */
 
41
 
 
42
#include "libesmtp-private.h"
 
43
#include "siobuf.h"
 
44
#include "protocol.h"
 
45
 
 
46
 
 
47
static int tls_init;
 
48
static SSL_CTX *starttls_ctx;
 
49
static smtp_starttls_passwordcb_t ctx_password_cb;
 
50
static void *ctx_password_cb_arg;
 
51
 
 
52
 
 
53
#ifdef USE_PTHREADS
 
54
#include <pthread.h>
 
55
static pthread_mutex_t starttls_mutex = PTHREAD_MUTEX_INITIALIZER;
 
56
static pthread_mutex_t *openssl_mutex;
 
57
 
 
58
static void
 
59
openssl_mutexcb (int mode, int n,
 
60
                 const char *file __attribute__ ((unused)),
 
61
                 int line __attribute__ ((unused)))
 
62
{
 
63
  if (mode & CRYPTO_LOCK)
 
64
    pthread_mutex_lock (&openssl_mutex[n]);
 
65
  else
 
66
    pthread_mutex_unlock (&openssl_mutex[n]);
 
67
}
 
68
#endif
 
69
 
 
70
static int
 
71
starttls_init (void)
 
72
{
 
73
  if (tls_init)
 
74
    return 1;
 
75
 
 
76
#ifdef USE_PTHREADS
 
77
  /* Set up mutexes for the OpenSSL library */
 
78
  if (openssl_mutex == NULL)
 
79
    {
 
80
      pthread_mutexattr_t attr;
 
81
      int n;
 
82
 
 
83
      openssl_mutex = malloc (sizeof (pthread_mutex_t) * CRYPTO_num_locks ());
 
84
      if (openssl_mutex == NULL)
 
85
        return 0;
 
86
      pthread_mutexattr_init (&attr);
 
87
      for (n = 0; n < CRYPTO_num_locks (); n++)
 
88
        pthread_mutex_init (&openssl_mutex[n], &attr);
 
89
      pthread_mutexattr_destroy (&attr);
 
90
      CRYPTO_set_locking_callback (openssl_mutexcb);
 
91
    }
 
92
#endif
 
93
  tls_init = 1;
 
94
  SSL_load_error_strings ();
 
95
  SSL_library_init ();
 
96
  return 1;
 
97
}
 
98
 
 
99
/* This stuff is crude and doesn't belong here */
 
100
/* vvvvvvvvvvv */
 
101
 
 
102
static const char *
 
103
get_home (void)
 
104
{
 
105
  return getenv ("HOME");
 
106
}
 
107
 
 
108
static char *
 
109
user_pathname (char buf[], size_t buflen, const char *tail)
 
110
{
 
111
  const char *home;
 
112
 
 
113
  home = get_home ();
 
114
  snprintf (buf, buflen, "%s/.authenticate/%s", home, tail);
 
115
  return buf;
 
116
}
 
117
 
 
118
/* Check file exists, is a regular file and contains something */
 
119
static int
 
120
check_file (const char *file)
 
121
{
 
122
  struct stat st;
 
123
 
 
124
  if (stat (file, &st) < 0)
 
125
    return 0;
 
126
  /* File must be regular and contain something */
 
127
  if (!S_ISREG (st.st_mode) && st.st_size > 0)
 
128
    return 0;
 
129
  /* For now this check is paranoid.  The way I figure it, the passwords
 
130
     on the private keys will be intensely annoying, so people will
 
131
     remove them.  Therefore make them protect the files using very
 
132
     restrictive file permissions.  Only the owner should be able to
 
133
     read or write the certificates and the user the app is running as
 
134
     should own the files. */
 
135
  if ((st.st_mode & (S_IXUSR | S_IRWXG | S_IRWXO)) || st.st_uid != getuid ())
 
136
    return 0;
 
137
  return 1;
 
138
}
 
139
 
 
140
/* Check directory exists */
 
141
static int
 
142
check_directory (const char *file)
 
143
{
 
144
  struct stat st;
 
145
 
 
146
  if (stat (file, &st) < 0)
 
147
    return 0;
 
148
  /* File must be a directory */
 
149
  if (!S_ISDIR (st.st_mode))
 
150
    return 0;
 
151
  /* Paranoia - only permit owner rwx permissions */
 
152
  if ((st.st_mode & (S_IRWXG | S_IRWXO)) || st.st_uid != getuid ())
 
153
    return 0;
 
154
  return 1;
 
155
}
 
156
 
 
157
/* ^^^^^^^^^^^ */
 
158
 
 
159
/* Unusually this API does not require a smtp_session_t.  The data
 
160
   it sets is global.
 
161
 
 
162
   N.B.  If this API is not called and OpenSSL requires a password, it
 
163
         will supply a default callback which prompts on the user's tty.
 
164
         This is likely to be undesired behaviour, so the app should
 
165
         supply a callback using this function.
 
166
 */
 
167
int
 
168
smtp_starttls_set_password_cb (smtp_starttls_passwordcb_t cb, void *arg)
 
169
{
 
170
  SMTPAPI_CHECK_ARGS (cb != NULL, 0);
 
171
 
 
172
#ifdef USE_PTHREADS
 
173
  pthread_mutex_lock (&starttls_mutex);
 
174
#endif
 
175
  ctx_password_cb = cb;
 
176
  ctx_password_cb_arg = arg;
 
177
#ifdef USE_PTHREADS
 
178
  pthread_mutex_unlock (&starttls_mutex);
 
179
#endif
 
180
  return 1;
 
181
}
 
182
 
 
183
static SSL_CTX *
 
184
starttls_create_ctx (void)
 
185
{
 
186
  SSL_CTX *ctx;
 
187
  char buf[2048];
 
188
  char buf2[2048];
 
189
  char *keyfile, *cafile, *capath;
 
190
 
 
191
  /* The decision not to support SSL v2 and v3 but instead to use only
 
192
     TLSv1 is deliberate.  RFC 2487 is not clear on what versions of
 
193
     SSL and TLS are to be supported but the implication is that
 
194
     TLS and not SSL is to be used.  Servers typically support SSL as
 
195
     well as TLS because some versions of Netscape do not support TLS.
 
196
     I am assuming that all deployed servers support the full set of
 
197
     protocols including TLSv1. */
 
198
  ctx = SSL_CTX_new (TLSv1_client_method ());
 
199
 
 
200
  /* Load our keys and certificates.  To avoid messing with configuration
 
201
     variables etc, use fixed paths for the certificate store.  These are
 
202
     as follows :-
 
203
 
 
204
     ~/.authenticate/private/smtp-starttls.pem
 
205
                                the user's certificate and private key
 
206
     ~/.authenticate/ca.pem
 
207
                                the user's trusted CA list
 
208
     ~/.authenticate/ca
 
209
                                the user's hashed CA directory
 
210
 
 
211
     Host specific stuff follows the same structure except that its
 
212
     below ~/.authenticate/host.name
 
213
 
 
214
     It probably makes sense to check that the directories and/or files
 
215
     are readable only by the user who owns them.
 
216
 
 
217
     This structure will certainly change.  I'm on a voyage of discovery
 
218
     here!  Eventually, this code and the SASL stuff will become a
 
219
     separate library used by libESMTP (libAUTH?).  The general idea is
 
220
     that ~/.authenticate will be used to store authentication
 
221
     information for the user (eventually there might be a
 
222
     ({/usr{/local}}/etc/authenticate for system wide stuff - CA lists
 
223
     and CRLs for example).  The "private" subdirectory is just to
 
224
     separate out private info from others.  There might be a "public"
 
225
     directory too.  Since the CA list is global (I think) I've put them
 
226
     below .authenticate for now.  Within the "private" and "public"
 
227
     directories, certificates and other authentication data are named
 
228
     according to their purpose (hence smtp-starttls.pem).  Symlinks can
 
229
     be used to avoid duplication where authentication tokens are shared
 
230
     for several purposes.  My reasoning here is that libESMTP (or any
 
231
     client layered over the hypothetical libAUTH) will always want the
 
232
     same authentication behaviour for a given service, regardless of
 
233
     the application using it.
 
234
 
 
235
     XXX - The above isn't quite enough.  Per-host directories are
 
236
     required, e.g. a different smtp-starttls.pem might be needed for
 
237
     different servers.  This will not affect the trusted CAs though.
 
238
 
 
239
     XXX - (this comment belongs in auth-client.c) Ideally, the
 
240
     ~/.authenticate hierarchy will be able to store SASL passwords
 
241
     if required, preferably encrypted.  Then the application would
 
242
     not necessarily have to supply usernames and passwords via the
 
243
     libESMTP API to be able to authenticate to a server.
 
244
   */
 
245
 
 
246
  /* Client certificate policy: if a client certificate is found at
 
247
     ~/.authenticate/private/smtp-starttls.pem, it is presented to the
 
248
     server if it requests it.  The server may use the certificate to
 
249
     perform authentication at its own discretion. */
 
250
  if (ctx_password_cb != NULL)
 
251
    {
 
252
      SSL_CTX_set_default_passwd_cb (ctx, ctx_password_cb);
 
253
      SSL_CTX_set_default_passwd_cb_userdata (ctx, ctx_password_cb_arg); 
 
254
    }
 
255
  keyfile = user_pathname (buf, sizeof buf, "private/smtp-starttls.pem");
 
256
  if (check_file (keyfile))
 
257
    {
 
258
      if (!SSL_CTX_use_certificate_file (ctx, keyfile, SSL_FILETYPE_PEM))
 
259
        {
 
260
          /* FIXME: set an error code */
 
261
          return NULL;
 
262
        }
 
263
      if (!SSL_CTX_use_PrivateKey_file (ctx, keyfile, SSL_FILETYPE_PEM))
 
264
        {
 
265
          /* FIXME: set an error code */
 
266
          return NULL;
 
267
        }
 
268
    }
 
269
 
 
270
  /* Server certificate policy: check the server certificate against the
 
271
     trusted CA list to a depth of 1. */
 
272
  cafile = user_pathname (buf, sizeof buf, "ca.pem");
 
273
  if (!check_file (cafile))
 
274
    cafile = NULL;
 
275
  capath = user_pathname (buf2, sizeof buf2, "ca");
 
276
  if (!check_directory (capath))
 
277
    capath = NULL;
 
278
  /* Load the CAs we trust */
 
279
  if (cafile != NULL || capath != NULL)
 
280
    SSL_CTX_load_verify_locations (ctx, cafile, capath);
 
281
  else
 
282
    SSL_CTX_set_default_verify_paths (ctx);
 
283
 
 
284
  /* FIXME: load a source of randomness */
 
285
 
 
286
  return ctx;
 
287
}
 
288
 
 
289
static SSL *
 
290
starttls_create_ssl (smtp_session_t session)
 
291
{
 
292
  char buf[2048];
 
293
  char buf2[2048];
 
294
  char *keyfile;
 
295
  SSL *ssl;
 
296
 
 
297
  ssl = SSL_new (session->starttls_ctx);
 
298
 
 
299
  /* Client certificate policy: if a host specific client certificate
 
300
     is found at ~/.authenticate/host.name/private/smtp-starttls.pem,
 
301
     it is presented to the server if it requests it. */
 
302
 
 
303
  /* FIXME: when the default client certificate is loaded a passowrd may be
 
304
            required.  Then the code below might ask for one too.  It
 
305
            will be annoying when two passwords are needed and only one
 
306
            is necessary.  Also, the default certificate will only need
 
307
            the password when the SSL_CTX is created.  A host specific
 
308
            certificate's password will be needed for every SMTP session
 
309
            within an application.  This needs a solution.  */
 
310
 
 
311
  /* FIXME: in protocol.c, record the canonic name of the host returned
 
312
            by getaddrinfo.  Use that instead of session->host. */
 
313
  snprintf (buf2, sizeof buf2, "%s/private/smtp-starttls.pem", session->host);
 
314
  keyfile = user_pathname (buf, sizeof buf, buf2);
 
315
  if (check_file (keyfile))
 
316
    {
 
317
      if (!SSL_use_certificate_file (ssl, keyfile, SSL_FILETYPE_PEM))
 
318
        {
 
319
          /* FIXME: set an error code */
 
320
          return NULL;
 
321
        }
 
322
      if (!SSL_use_PrivateKey_file (ssl, keyfile, SSL_FILETYPE_PEM))
 
323
        {
 
324
          /* FIXME: set an error code */
 
325
          return NULL;
 
326
        }
 
327
    }
 
328
 
 
329
  return ssl;
 
330
}
 
331
 
 
332
/* App calls this to allow libESMTP to use an SSL_CTX it has already
 
333
   initialised.  NULL means use a default created by libESMTP.
 
334
   If called at all, libESMTP assumes the application has initialised
 
335
   openssl.  Otherwise, libESMTP will initialise OpenSSL before calling
 
336
   any of the SSL APIs. */
 
337
int
 
338
smtp_starttls_set_ctx (smtp_session_t session, SSL_CTX *ctx)
 
339
{
 
340
  SMTPAPI_CHECK_ARGS (session != NULL, 0);
 
341
 
 
342
  tls_init = 1;                 /* Assume app has set up openssl */
 
343
  session->starttls_ctx = ctx;
 
344
  return 1;
 
345
}
 
346
 
 
347
/* how == 0: disabled, 1: if possible, 2: required */
 
348
int
 
349
smtp_starttls_enable (smtp_session_t session, enum starttls_option how)
 
350
{
 
351
  SMTPAPI_CHECK_ARGS (session != NULL, 0);
 
352
 
 
353
  session->starttls_enabled = how;
 
354
  if (how == Starttls_REQUIRED)
 
355
    session->required_extensions |= EXT_STARTTLS;
 
356
  else
 
357
    session->required_extensions &= ~EXT_STARTTLS;
 
358
  return 1;
 
359
}
 
360
 
 
361
int
 
362
select_starttls (smtp_session_t session)
 
363
{
 
364
  if (session->using_tls || session->authenticated)
 
365
    return 0;
 
366
  /* FIXME: if the server has reported the TLS extension in a previous
 
367
            session promote Starttls_ENABLED to Starttls_REQUIRED.
 
368
            If this session does not offer STARTTLS, this will force
 
369
            protocol.c to report the extension as not available and QUIT
 
370
            as reccommended in RFC 2487.  */
 
371
  /* if (...)
 
372
      session->starttls_enabled = Starttls_REQUIRED; */
 
373
  if (!(session->extensions & EXT_STARTTLS))
 
374
    return 0;
 
375
  if (!session->starttls_enabled)
 
376
    return 0;
 
377
#ifdef USE_PTHREADS
 
378
  pthread_mutex_lock (&starttls_mutex);
 
379
#endif
 
380
  if (starttls_ctx == NULL && starttls_init ())
 
381
    starttls_ctx = starttls_create_ctx ();
 
382
#ifdef USE_PTHREADS
 
383
  pthread_mutex_unlock (&starttls_mutex);
 
384
#endif
 
385
  session->starttls_ctx = starttls_ctx;
 
386
  return session->starttls_ctx != NULL;
 
387
}
 
388
 
 
389
static int
 
390
check_acceptable_security (smtp_session_t session, SSL *ssl)
 
391
{
 
392
  X509 *cert;
 
393
  char buf[256];
 
394
  int bits;
 
395
  long vfy_result;
 
396
  int ok;
 
397
 
 
398
  /* Check certificate validity.
 
399
   */
 
400
  vfy_result = SSL_get_verify_result (ssl);
 
401
  if (vfy_result != X509_V_OK)
 
402
    {
 
403
      ok = 0;
 
404
      if (session->event_cb != NULL)
 
405
        (*session->event_cb) (session, SMTP_EV_INVALID_PEER_CERTIFICATE,
 
406
                              session->event_cb_arg, vfy_result, &ok);
 
407
      if (!ok)
 
408
        return 0;
 
409
    }
 
410
 
 
411
  /* Check cipher strength.  Since we use only TLSv1 the cipher should
 
412
     never be this weak.  */
 
413
  bits = SSL_get_cipher_bits (ssl, NULL);
 
414
  if (bits <= 40)
 
415
    {
 
416
      ok = 0;
 
417
      if (session->event_cb != NULL)
 
418
        (*session->event_cb) (session, SMTP_EV_WEAK_CIPHER,
 
419
                              session->event_cb_arg, bits, &ok);
 
420
      if (!ok)
 
421
        return 0;
 
422
    }
 
423
 
 
424
  /* Check server credentials stored in the certificate.
 
425
   */
 
426
  cert = SSL_get_peer_certificate (ssl);
 
427
  if (cert != NULL)
 
428
    {
 
429
      /* FIXME: should retrieve each subjectAltName of type dNSName
 
430
                and compare them to the remote host name.  If any one
 
431
                of them matches, accept the certificate. */
 
432
      X509_NAME_get_text_by_NID (X509_get_subject_name (cert),
 
433
                                 NID_commonName, buf, sizeof buf);
 
434
      X509_free (cert);
 
435
 
 
436
      /* FIXME: in protocol.c, record the canonic name of the host returned
 
437
                by getaddrinfo.  Use that instead of session->host. */
 
438
 
 
439
      /* FIXME: the host name in the certificate may have wild cards
 
440
                present.  Use something better than strcasecmp!  */
 
441
      if (strcasecmp (session->host, buf) != 0)
 
442
        {
 
443
          ok = 0;
 
444
          if (session->event_cb != NULL)
 
445
            (*session->event_cb) (session, SMTP_EV_WRONG_PEER_CERTIFICATE,
 
446
                                  session->event_cb_arg, &ok);
 
447
          if (!ok)
 
448
            return 0;
 
449
        }
 
450
    }
 
451
  else
 
452
    {
 
453
      ok = 0;
 
454
      if (session->event_cb != NULL)
 
455
        (*session->event_cb) (session, SMTP_EV_NO_PEER_CERTIFICATE,
 
456
                              session->event_cb_arg, &ok);
 
457
      if (!ok)
 
458
        return 0;
 
459
    }
 
460
 
 
461
  /* Since the criteria for rejecting a certificate may be at the
 
462
     user's discretion, a config file might be needed to configure
 
463
     the client TLS (and SASL) options.
 
464
 
 
465
     Yuck!  libESMTP has got on without bloody config files until now :(
 
466
   */
 
467
 
 
468
  return 1;
 
469
}
 
470
 
 
471
void
 
472
cmd_starttls (siobuf_t conn, smtp_session_t session)
 
473
{
 
474
  sio_write (conn, "STARTTLS\r\n", -1);
 
475
  session->cmd_state = -1;
 
476
}
 
477
 
 
478
void
 
479
rsp_starttls (siobuf_t conn, smtp_session_t session)
 
480
{
 
481
  int code;
 
482
  SSL *ssl;
 
483
  X509 *cert;
 
484
  char buf[256];
 
485
 
 
486
  code = read_smtp_response (conn, session, &session->mta_status, NULL);
 
487
  if (code == 2
 
488
      && sio_set_tlsclient_ssl (conn, (ssl = starttls_create_ssl (session))))
 
489
    {
 
490
      session->using_tls = 1;
 
491
 
 
492
      /* Forget what we know about the server and reset protocol state.
 
493
       */
 
494
      session->extensions = 0;
 
495
      destroy_auth_mechanisms (session);
 
496
 
 
497
      if (!check_acceptable_security (session, ssl))
 
498
        session->rsp_state = S_quit;
 
499
      else
 
500
        {
 
501
          if (session->event_cb != NULL)
 
502
            (*session->event_cb) (session, SMTP_EV_STARTTLS_OK,
 
503
                                  session->event_cb_arg,
 
504
                                  ssl, SSL_get_cipher (ssl),
 
505
                                  SSL_get_cipher_bits (ssl, NULL));
 
506
          cert = SSL_get_certificate (ssl);
 
507
          if (cert != NULL)
 
508
            {
 
509
              /* Copy the common name [typically email address] from the
 
510
                 client certificate and use it to prime the SASL EXTERNAL
 
511
                 mechanism */
 
512
              X509_NAME_get_text_by_NID (X509_get_subject_name (cert),
 
513
                                         NID_commonName, buf, sizeof buf);
 
514
              X509_free (cert);
 
515
              if (session->auth_context != NULL)
 
516
                auth_set_external_id (session->auth_context, buf);
 
517
            }
 
518
 
 
519
          /* Next state is EHLO */
 
520
          session->rsp_state = S_ehlo;
 
521
        }
 
522
    }
 
523
  else
 
524
    session->rsp_state = S_quit;
 
525
}
 
526
 
 
527
#else
 
528
 
 
529
#define SSL_CTX void
 
530
#include "libesmtp-private.h"
 
531
 
 
532
/* Fudge the declaration.  The idea is that all builds of the library
 
533
   export the same API, but that the unsupported features always fail.
 
534
   This prototype is declared only if <openssl/ssl.h> is included.
 
535
   Strict ANSI compiles require prototypes, so here it is! */
 
536
int smtp_starttls_set_ctx (smtp_session_t session, SSL_CTX *ctx);
 
537
 
 
538
int
 
539
smtp_starttls_set_ctx (smtp_session_t session,
 
540
                       SSL_CTX *ctx __attribute__ ((unused)))
 
541
{
 
542
  SMTPAPI_CHECK_ARGS (session != (smtp_session_t) 0, 0);
 
543
 
 
544
  return 0;
 
545
}
 
546
 
 
547
int
 
548
smtp_starttls_enable (smtp_session_t session,
 
549
                      enum starttls_option how __attribute__ ((unused)))
 
550
{
 
551
  SMTPAPI_CHECK_ARGS (session != (smtp_session_t) 0, 0);
 
552
 
 
553
  return 0;
 
554
}
 
555
 
 
556
int
 
557
smtp_starttls_set_password_cb (smtp_starttls_passwordcb_t cb
 
558
                                                        __attribute__ ((unused)),
 
559
                               void *arg __attribute__ ((unused)))
 
560
{
 
561
  return 0;
 
562
}
 
563
 
 
564
#endif