1 /* tlsComm.c - primitive routines to aid TLS communication
2 within wmbiff, without rewriting each mailbox access
3 scheme. These functions hide whether the underlying
4 transport is encrypted.
6 Neil Spring (nspring@cs.washington.edu) */
8 /* TODO: handle "* BYE" internally? */
15 #include <sys/types.h>
22 #ifdef HAVE_GNUTLS_GNUTLS_H
24 #include <gnutls/gnutls.h>
25 #include <gnutls/x509.h>
34 #include "Client.h" /* debugging messages */
36 /* if non-null, set to a file for certificate verification */
37 extern const char *certificate_filename
;
38 /* if set, don't fail when dealing with a bad certificate.
39 (continue to whine, though, as bad certs should be fixed) */
40 extern int SkipCertificateCheck
;
42 /* WARNING: implcitly uses scs to gain access to the mailbox
43 that holds the per-mailbox debug flag. */
44 #define TDM(lvl, args...) DM(scs->pc, lvl, "comm: " args)
46 /* how long to wait for the server to do its thing
47 when we issue it a command (in seconds) */
48 #define EXPECT_TIMEOUT 40
50 /* this is the per-connection state that is maintained for
51 each connection; BIG variables are for ssl (null if not
54 struct connection_state
{
58 gnutls_session tls_state
;
59 gnutls_certificate_credentials xcred
;
61 /*@null@ */ void *tls_state
;
62 /*@null@ */ void *xcred
;
64 char unprocessed
[BUF_SIZE
];
65 Pop3 pc
; /* mailbox handle for debugging messages */
68 /* gotta do our own line buffering, sigh */
69 int getline_from_buffer(char *readbuffer
, char *linebuffer
,
71 void handle_gnutls_read_error(int readbytes
, struct connection_state
*scs
);
73 void tlscomm_close(struct connection_state
*scs
)
75 TDM(DEBUG_INFO
, "%s: closing.\n",
76 (scs
->name
!= NULL
) ? scs
->name
: "null");
78 /* not ok to call this more than once */
81 /* this next line seems capable of hanging... */
82 /* gnutls_bye(scs->tls_state, GNUTLS_SHUT_RDWR); */
83 /* so we'll try just _bye'ing the WR direction, which
84 should send the alert but not wait for a response. */
85 gnutls_bye(scs
->tls_state
, GNUTLS_SHUT_WR
);
86 gnutls_certificate_free_credentials(scs
->xcred
);
87 gnutls_deinit(scs
->tls_state
);
91 (void) close(scs
->sd
);
94 scs
->tls_state
= NULL
;
101 extern int x_socket(void);
102 extern void ProcessPendingEvents(void);
104 /* this avoids blocking without using non-blocking i/o */
105 static int wait_for_it(int sd
, int timeoutseconds
)
109 int ready_descriptors
;
112 struct timeval time_now
;
113 struct timeval time_out
;
115 gettimeofday(&time_now
, NULL
);
116 memcpy(&time_out
, &time_now
, sizeof(struct timeval
));
117 time_out
.tv_sec
+= timeoutseconds
;
120 maxfd
= max(sd
, xfd
);
124 ProcessPendingEvents();
126 gettimeofday(&time_now
, NULL
);
127 tv
.tv_sec
= max(time_out
.tv_sec
- time_now
.tv_sec
+ 1, 0); /* sloppy, but bfd */
129 /* select will return if we have X stuff or we have comm stuff on sd */
131 FD_SET(sd
, &readfds
);
132 // FD_SET(xfd, &readfds);
133 ready_descriptors
= select(maxfd
+ 1, &readfds
, NULL
, NULL
, &tv
);
135 // "select %d/%d returned %d descriptor, %d\n",
136 // sd, timeoutseconds, ready_descriptors, FD_ISSET(sd, &readfds));
138 } while(tv
.tv_sec
> 0 && (!FD_ISSET(sd
, &readfds
) || (errno
== EINTR
&& ready_descriptors
== -1)));
141 FD_SET(sd
, &readfds
);
142 tv
.tv_sec
= 0; tv
.tv_usec
= 0;
143 ready_descriptors
= select(sd
+ 1, &readfds
, NULL
, NULL
, &tv
);
145 while (ready_descriptors
== -1 && errno
== EINTR
);
146 if (ready_descriptors
== 0) {
148 "select timed out after %d seconds on socket: %d\n",
151 } else if (ready_descriptors
== -1) {
153 "select failed on socket %d: %s\n", sd
, strerror(errno
));
156 return (FD_ISSET(sd
, &readfds
));
159 /* exported for testing */
161 getline_from_buffer(char *readbuffer
, char *linebuffer
, int linebuflen
)
165 /* find end of line (stopping if linebuflen is too small. */
166 for (p
= readbuffer
, i
= 0;
167 *p
!= '\n' && *p
!= '\0' && i
< linebuflen
- 1; p
++, i
++);
169 /* gobble \n if it starts the line. */
171 /* grab the end of line too! and then advance past
176 /* TODO -- perhaps we should return no line at all
177 here, as it might be incomplete. don't want to
178 break anything though. */
179 DMA(DEBUG_INFO
, "expected line doesn't end on its own.\n");
183 /* copy a line into the linebuffer */
184 strncpy(linebuffer
, readbuffer
, (size_t) i
);
185 /* sigh, null terminate */
186 linebuffer
[i
] = '\0';
187 /* shift the rest over; this could be done
188 instead with strcpy... I think. */
197 /* return the length of the line */
199 if (i
< 0 || i
> linebuflen
) {
200 DM((Pop3
) NULL
, DEBUG_ERROR
, "bork bork bork!: %d %d\n", i
,
206 /* eat lines, until one starting with prefix is found;
207 this skips 'informational' IMAP responses */
208 /* the correct response to a return value of 0 is almost
209 certainly tlscomm_close(scs): don't _expect() anything
210 unless anything else would represent failure */
212 tlscomm_expect(struct connection_state
*scs
,
213 const char *prefix
, char *linebuf
, int buflen
)
215 int prefixlen
= (int) strlen(prefix
);
216 int buffered_bytes
= 0;
217 memset(linebuf
, 0, buflen
);
218 TDM(DEBUG_INFO
, "%s: expecting: %s\n", scs
->name
, prefix
);
219 /* if(scs->unprocessed[0]) {
220 TDM(DEBUG_INFO, "%s: buffered: %s\n", scs->name, scs->unprocessed);
222 while (scs
->unprocessed
[0] != '\0'
223 || wait_for_it(scs
->sd
, EXPECT_TIMEOUT
)) {
224 if (scs
->unprocessed
[buffered_bytes
] == '\0') {
227 if (scs
->tls_state
) {
228 /* BUF_SIZE - 1 leaves room for trailing \0 */
230 gnutls_read(scs
->tls_state
,
231 &scs
->unprocessed
[buffered_bytes
],
232 BUF_SIZE
- 1 - buffered_bytes
);
233 if (thisreadbytes
< 0) {
234 handle_gnutls_read_error(thisreadbytes
, scs
);
241 read(scs
->sd
, &scs
->unprocessed
[buffered_bytes
],
242 BUF_SIZE
- 1 - buffered_bytes
);
243 if (thisreadbytes
< 0) {
244 TDM(DEBUG_ERROR
, "%s: error reading: %s\n",
245 scs
->name
, strerror(errno
));
249 buffered_bytes
+= thisreadbytes
;
250 /* force null termination */
251 scs
->unprocessed
[buffered_bytes
] = '\0';
252 if (buffered_bytes
== 0) {
253 return 0; /* bummer */
256 buffered_bytes
= strlen(scs
->unprocessed
);
258 while (buffered_bytes
>= prefixlen
) {
261 getline_from_buffer(scs
->unprocessed
, linebuf
, buflen
);
262 if (linebytes
== 0) {
265 buffered_bytes
-= linebytes
;
266 if (strncmp(linebuf
, prefix
, prefixlen
) == 0) {
267 TDM(DEBUG_INFO
, "%s: got: %*s", scs
->name
,
269 return 1; /* got it! */
271 TDM(DEBUG_INFO
, "%s: dumped(%d/%d): %.*s", scs
->name
,
272 linebytes
, buffered_bytes
, linebytes
, linebuf
);
276 if (buffered_bytes
== -1) {
277 TDM(DEBUG_INFO
, "%s: timed out while expecting '%s'\n",
280 TDM(DEBUG_ERROR
, "%s: expecting: '%s', saw (%d): %s%s",
281 scs
->name
, prefix
, buffered_bytes
, linebuf
,
282 /* only print the newline if the linebuf lacks it */
283 (linebuf
[strlen(linebuf
) - 1] == '\n') ? "\n" : "");
285 return 0; /* wait_for_it failed */
288 int tlscomm_gets(char *buf
, int buflen
, struct connection_state
*scs
)
290 return (tlscomm_expect(scs
, "", buf
, buflen
));
293 void tlscomm_printf(struct connection_state
*scs
, const char *format
, ...)
300 DMA(DEBUG_ERROR
, "null connection to tlscomm_printf\n");
303 va_start(args
, format
);
304 bytes
= vsnprintf(buf
, 1024, format
, args
);
309 if (scs
->tls_state
) {
310 int written
= gnutls_write(scs
->tls_state
, buf
, bytes
);
311 if (written
< bytes
) {
313 "Error %s prevented writing: %*s\n",
314 gnutls_strerror(written
), bytes
, buf
);
319 (void) write(scs
->sd
, buf
, bytes
);
322 ("warning: tlscomm_printf called with an invalid socket descriptor\n");
325 TDM(DEBUG_INFO
, "wrote %*s", bytes
, buf
);
328 /* most of this file only makes sense if using TLS. */
330 #include "gnutls-common.h"
333 bad_certificate(const struct connection_state
*scs
, const char *msg
)
335 TDM(DEBUG_ERROR
, "%s", msg
);
336 if (!SkipCertificateCheck
) {
337 TDM(DEBUG_ERROR
, "to ignore this error, run wmbiff "
338 "with the -skip-certificate-check option\n");
343 /* a start of a hack at verifying certificates. does not
344 provide any security at all. I'm waiting for either
345 gnutls to make this as easy as it should be, or someone
346 to port Andrew McDonald's gnutls-for-mutt patch.
349 #define CERT_SEP "-----BEGIN"
351 /* this bit is based on read_ca_file() in gnutls */
352 static int tls_compare_certificates(const gnutls_datum
* peercert
)
358 gnutls_datum b64_data
;
359 unsigned char *b64_data_data
;
360 struct stat filestat
;
362 if (stat(certificate_filename
, &filestat
) == -1)
365 b64_data
.size
= filestat
.st_size
+ 1;
366 b64_data_data
= (unsigned char *) malloc(b64_data
.size
);
367 b64_data_data
[b64_data
.size
- 1] = '\0';
368 b64_data
.data
= b64_data_data
;
370 fd1
= fopen(certificate_filename
, "r");
375 b64_data
.size
= fread(b64_data
.data
, 1, b64_data
.size
, fd1
);
379 ret
= gnutls_pem_base64_decode_alloc(NULL
, &b64_data
, &cert
);
385 ptr
= (unsigned char *) strstr(b64_data
.data
, CERT_SEP
) + 1;
386 ptr
= (unsigned char *) strstr(ptr
, CERT_SEP
);
388 b64_data
.size
= b64_data
.size
- (ptr
- b64_data
.data
);
391 if (cert
.size
== peercert
->size
) {
392 if (memcmp(cert
.data
, peercert
->data
, cert
.size
) == 0) {
394 gnutls_free(cert
.data
);
400 gnutls_free(cert
.data
);
401 } while (ptr
!= NULL
);
410 tls_check_certificate(struct connection_state
*scs
,
411 const char *remote_hostname
)
414 const gnutls_datum
*cert_list
;
415 int cert_list_size
= 0;
416 gnutls_x509_crt cert
;
418 if (gnutls_auth_get_type(scs
->tls_state
) != GNUTLS_CRD_CERTIFICATE
) {
419 bad_certificate(scs
, "Unable to get certificate from peer.\n");
420 return; /* bad_cert will exit if -skip-certificate-check was not given */
422 certstat
= gnutls_certificate_verify_peers(scs
->tls_state
);
423 if (certstat
== GNUTLS_E_NO_CERTIFICATE_FOUND
) {
424 bad_certificate(scs
, "server presented no certificate.\n");
425 #ifdef GNUTLS_CERT_CORRUPTED
426 } else if (certstat
& GNUTLS_CERT_CORRUPTED
) {
427 bad_certificate(scs
, "server's certificate is corrupt.\n");
429 } else if (certstat
& GNUTLS_CERT_REVOKED
) {
430 bad_certificate(scs
, "server's certificate has been revoked.\n");
431 } else if (certstat
& GNUTLS_CERT_INVALID
) {
432 if (gnutls_certificate_type_get(scs
->tls_state
) == GNUTLS_CRT_X509
) {
433 /* bad_certificate(scs, "server's certificate is not trusted.\n"
434 "there may be a problem with the certificate stored in your certfile\n"); */
437 "server's certificate is invalid or not X.509.\n"
438 "there may be a problem with the certificate stored in your certfile\n");
440 #if defined(GNUTLS_CERT_SIGNER_NOT_FOUND)
441 } else if (certstat
& GNUTLS_CERT_SIGNER_NOT_FOUND
) {
442 TDM(DEBUG_INFO
, "server's certificate is not signed.\n");
444 "to verify that a certificate is trusted, use the certfile option.\n");
447 #if defined(GNUTLS_CERT_NOT_TRUSTED)
448 } else if (certstat
& GNUTLS_CERT_NOT_TRUSTED
) {
449 TDM(DEBUG_INFO
, "server's certificate is not trusted.\n");
451 "to verify that a certificate is trusted, use the certfile option.\n");
455 if (gnutls_x509_crt_init(&cert
) < 0) {
457 "Unable to initialize certificate data structure");
461 /* not checking for not-yet-valid certs... this would make sense
462 if we weren't just comparing to stored ones */
464 gnutls_certificate_get_peers(scs
->tls_state
, &cert_list_size
);
466 if (gnutls_x509_crt_import(cert
, &cert_list
[0], GNUTLS_X509_FMT_DER
) <
468 bad_certificate(scs
, "Error processing certificate data");
471 if (gnutls_x509_crt_get_expiration_time(cert
) < time(NULL
)) {
472 bad_certificate(scs
, "server's certificate has expired.\n");
473 } else if (gnutls_x509_crt_get_activation_time(cert
)
475 bad_certificate(scs
, "server's certificate is not yet valid.\n");
477 TDM(DEBUG_INFO
, "certificate passed time check.\n");
480 if (gnutls_x509_crt_check_hostname(cert
, remote_hostname
) == 0) {
481 char certificate_hostname
[256];
483 gnutls_x509_crt_get_dn(cert
, certificate_hostname
, &buflen
);
484 /* gnutls_x509_extract_certificate_dn(&cert_list[0], &dn); */
486 "server's certificate (%s) does not match its hostname (%s).\n",
487 certificate_hostname
, remote_hostname
);
489 "server's certificate does not match its hostname.\n");
491 if ((scs
->pc
)->debug
>= DEBUG_INFO
) {
492 char certificate_hostname
[256];
494 gnutls_x509_crt_get_dn(cert
, certificate_hostname
, &buflen
);
495 /* gnutls_x509_extract_certificate_dn(&cert_list[0], &dn); */
497 "server's certificate (%s) matched its hostname (%s).\n",
498 certificate_hostname
, remote_hostname
);
502 if (certificate_filename
!= NULL
&&
503 tls_compare_certificates(&cert_list
[0]) == 0) {
505 "server's certificate was not found in the certificate file.\n");
508 gnutls_x509_crt_deinit(cert
);
510 TDM(DEBUG_INFO
, "certificate check ok.\n");
514 struct connection_state
*initialize_gnutls(int sd
, char *name
, Pop3 pc
,
515 const char *remote_hostname
)
517 static int gnutls_initialized
;
519 struct connection_state
*scs
= malloc(sizeof(struct connection_state
));
520 memset(scs
, 0, sizeof(struct connection_state
)); /* clears the unprocessed buffer */
526 if (gnutls_initialized
== 0) {
527 assert(gnutls_global_init() == 0);
528 gnutls_initialized
= 1;
531 assert(gnutls_init(&scs
->tls_state
, GNUTLS_CLIENT
) == 0);
533 const int protocols
[] = { GNUTLS_TLS1
, GNUTLS_SSL3
, 0 };
534 const int ciphers
[] =
535 { GNUTLS_CIPHER_RIJNDAEL_128_CBC
, GNUTLS_CIPHER_3DES_CBC
,
536 GNUTLS_CIPHER_RIJNDAEL_256_CBC
,
537 GNUTLS_CIPHER_ARCFOUR
, 0
539 const int compress
[] = { GNUTLS_COMP_ZLIB
, GNUTLS_COMP_NULL
, 0 };
540 const int key_exch
[] = { GNUTLS_KX_RSA
, GNUTLS_KX_DHE_DSS
,
543 /* mutt with gnutls doesn't use kx_srp or kx_anon_dh */
544 const int mac
[] = { GNUTLS_MAC_SHA
, GNUTLS_MAC_MD5
, 0 };
545 assert(gnutls_protocol_set_priority(scs
->tls_state
, protocols
) ==
547 assert(gnutls_cipher_set_priority(scs
->tls_state
, ciphers
) == 0);
548 assert(gnutls_compression_set_priority(scs
->tls_state
, compress
) ==
550 assert(gnutls_kx_set_priority(scs
->tls_state
, key_exch
) == 0);
551 assert(gnutls_mac_set_priority(scs
->tls_state
, mac
) == 0);
552 /* no client private key */
553 if (gnutls_certificate_allocate_credentials(&scs
->xcred
) < 0) {
554 DMA(DEBUG_ERROR
, "gnutls memory error\n");
558 /* certfile seems to work. */
559 if (certificate_filename
!= NULL
) {
560 if (!exists(certificate_filename
)) {
562 "Certificate file (certfile=) %s not found.\n",
563 certificate_filename
);
566 zok
= gnutls_certificate_set_x509_trust_file(scs
->xcred
,
568 certificate_filename
,
569 GNUTLS_X509_FMT_PEM
);
572 "GNUTLS did not like your certificate file %s (%d).\n",
573 certificate_filename
, zok
);
579 gnutls_cred_set(scs
->tls_state
, GNUTLS_CRD_CERTIFICATE
,
581 gnutls_transport_set_ptr(scs
->tls_state
,
582 (gnutls_transport_ptr
) sd
);
584 zok
= gnutls_handshake(scs
->tls_state
);
586 while (zok
== GNUTLS_E_INTERRUPTED
|| zok
== GNUTLS_E_AGAIN
);
588 tls_check_certificate(scs
, remote_hostname
);
592 TDM(DEBUG_ERROR
, "%s: Handshake failed\n", name
);
593 TDM(DEBUG_ERROR
, "%s: This may be a problem in gnutls, "
594 "which is under development\n", name
);
596 "%s: This copy of wmbiff was compiled with \n"
597 " gnutls version %s.\n", name
, LIBGNUTLS_VERSION
);
599 if (scs
->pc
->u
.pop_imap
.serverPort
!= 143 /* starttls */ ) {
601 "%s: Please run 'gnutls-cli-debug -p %d %s' to test ssl directly.\n"
602 " That tool provides a lower-level test of gnutls with your server.\n",
603 name
, scs
->pc
->u
.pop_imap
.serverPort
, remote_hostname
);
605 gnutls_deinit(scs
->tls_state
);
609 TDM(DEBUG_INFO
, "%s: Handshake was completed\n", name
);
610 if (scs
->pc
->debug
>= DEBUG_INFO
)
611 print_info(scs
->tls_state
, remote_hostname
);
618 /* moved down here, to keep from interrupting the flow with
619 verbose error crap */
620 void handle_gnutls_read_error(int readbytes
, struct connection_state
*scs
)
622 if (gnutls_error_is_fatal(readbytes
) == 1) {
624 "%s: Received corrupted data(%d) - server has terminated the connection abnormally\n",
625 scs
->name
, readbytes
);
627 if (readbytes
== GNUTLS_E_WARNING_ALERT_RECEIVED
628 || readbytes
== GNUTLS_E_FATAL_ALERT_RECEIVED
)
629 TDM(DEBUG_ERROR
, "* Received alert [%d]\n",
630 gnutls_alert_get(scs
->tls_state
));
631 if (readbytes
== GNUTLS_E_REHANDSHAKE
)
632 TDM(DEBUG_ERROR
, "* Received HelloRequest message\n");
635 "%s: gnutls error reading: %s\n",
636 scs
->name
, gnutls_strerror(readbytes
));
640 /* declare stubs when tls isn't compiled in */
641 struct connection_state
*initialize_gnutls(UNUSED(int sd
),
648 "FATAL: tried to initialize ssl when ssl wasn't compiled in.\n");
654 struct connection_state
*initialize_unencrypted(int sd
,
655 /*@only@ */ char *name
,
658 struct connection_state
*ret
= malloc(sizeof(struct connection_state
));
661 memset(ret
, 0, sizeof(struct connection_state
)); /* clears the unprocessed buffer */
664 ret
->tls_state
= NULL
;
670 /* bad seed connections that can't be setup */
672 struct connection_state
*initialize_blacklist( /*@only@ */ char *name
)
674 struct connection_state
*ret
= malloc(sizeof(struct connection_state
));
678 ret
->tls_state
= NULL
;
685 int tlscomm_is_blacklisted(const struct connection_state
*scs
)
687 return (scs
!= NULL
&& scs
->sd
== -1);