2 * Copyright notice from original mutt:
3 * Copyright (C) 2001 Marco d'Itri <md@linux.it>
4 * Copyright (C) 2001-2004 Andrew McDonald <andrew@mcdonald.org.uk>
6 * This file is part of mutt-ng, see http://www.muttng.org/.
7 * It's licensed under the GNU General Public License,
8 * please see the file GPL in the top level source directory.
11 #include <lib-lib/lib-lib.h>
13 #include <gnutls/gnutls.h>
14 #include <gnutls/x509.h>
15 #ifdef HAVE_GNUTLS_OPENSSL_H
16 #include <gnutls/openssl.h>
19 #include <lib-ui/curses.h>
20 #include <lib-ui/menu.h>
23 #include "mutt_socket.h"
25 @import "../lib-lua/base.cpkg"
31 ** If this variable is \fIset\fP, Madmutt will require that all connections
32 ** to remote servers be encrypted. Furthermore it will attempt to
33 ** negotiate TLS even if the server does not advertise the capability,
34 ** since it would otherwise have to abort the connection anyway. This
35 ** option supersedes ``$$ssl_starttls''.
40 ** If \fIset\fP (the default), Madmutt will attempt to use STARTTLS on servers
41 ** advertising the capability. When \fIunset\fP, Madmutt will not attempt to
42 ** use STARTTLS regardless of the server's capabilities.
47 ** This variables specifies whether to attempt to use SSLv3 in the
48 ** SSL authentication process.
53 ** This variables specifies whether to attempt to use TLSv1 in the
54 ** SSL authentication process.
57 int min_dh_prime_bits = 0;
60 ** This variable specifies the minimum acceptable prime size (in bits)
61 ** for use in any Diffie-Hellman key exchange. A value of 0 will use
62 ** the default from the GNUTLS library.
65 path_t cert_file = m_strdup("~/.cache/madmutt/certificates");
68 ** This variable specifies the file where the certificates you trust
69 ** are saved. When an unknown certificate is encountered, you are asked
70 ** if you accept it or not. If you accept it, the certificate can also
71 ** be saved in this file and further connections are automatically
74 ** You can also manually add CA certificates in this file. Any server
75 ** certificate that is signed with one of these CA certificates are
76 ** also automatically accepted.
78 ** Example: \fTset certificate_file=~/.madmutt/certificates\fP
81 path_t ca_certificates_file = NULL;
84 ** This variable specifies a file containing trusted CA certificates.
85 ** Any server certificate that is signed with one of these CA
86 ** certificates are also automatically accepted.
88 ** Example: \fTset ssl_ca_certificates_file=/etc/ssl/certs/ca-certificates.crt\fP
92 typedef struct _tlssockdata {
94 gnutls_certificate_credentials xcred;
97 /* local prototypes */
98 static int tls_socket_read (CONNECTION * conn, char *buf, ssize_t len);
99 static int tls_socket_write (CONNECTION * conn, const char *buf, ssize_t len);
100 static int tls_socket_open (CONNECTION * conn);
101 static int tls_socket_close (CONNECTION * conn);
102 static int tls_starttls_close (CONNECTION * conn);
104 static int tls_init (void);
105 static int tls_negotiate (CONNECTION * conn);
106 static int tls_check_certificate (CONNECTION * conn);
109 static int tls_init (void)
111 static unsigned char init_complete = 0;
117 err = gnutls_global_init ();
119 mutt_error (_("gnutls_global_init: %s"), gnutls_strerror (err));
128 int mutt_ssl_socket_setup (CONNECTION * conn)
133 conn->conn_open = tls_socket_open;
134 conn->conn_read = tls_socket_read;
135 conn->conn_write = tls_socket_write;
136 conn->conn_close = tls_socket_close;
141 static int tls_socket_read (CONNECTION * conn, char *buf, ssize_t len)
143 tlssockdata *data = conn->sockdata;
147 mutt_error (_("Error: no TLS socket open"));
152 ret = gnutls_record_recv (data->state, buf, len);
153 if (gnutls_error_is_fatal (ret) == 1) {
154 mutt_error (_("tls_socket_read (%s)"), gnutls_strerror (ret));
161 static int tls_socket_write (CONNECTION * conn, const char *buf, ssize_t len)
163 tlssockdata *data = conn->sockdata;
167 mutt_error (_("Error: no TLS socket open"));
172 ret = gnutls_record_send (data->state, buf, len);
173 if (gnutls_error_is_fatal (ret) == 1) {
174 mutt_error (_("tls_socket_write (%s)"), gnutls_strerror (ret));
181 static int tls_socket_open (CONNECTION * conn)
183 if (raw_socket_open (conn) < 0)
186 if (tls_negotiate (conn) < 0) {
187 tls_socket_close (conn);
194 int mutt_ssl_starttls (CONNECTION * conn)
199 if (tls_negotiate (conn) < 0)
202 conn->conn_read = tls_socket_read;
203 conn->conn_write = tls_socket_write;
204 conn->conn_close = tls_starttls_close;
209 static int protocol_priority[] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 };
211 /* tls_negotiate: After TLS state has been initialised, attempt to negotiate
212 * TLS over the wire, including certificate checks. */
213 static int tls_negotiate (CONNECTION * conn)
218 data = p_new(tlssockdata, 1);
219 conn->sockdata = data;
220 err = gnutls_certificate_allocate_credentials (&data->xcred);
222 p_delete(&conn->sockdata);
223 mutt_error (_("gnutls_certificate_allocate_credentials: %s"),
224 gnutls_strerror (err));
229 gnutls_certificate_set_x509_trust_file (data->xcred, mod_ssl.cert_file,
230 GNUTLS_X509_FMT_PEM);
231 /* ignore errors, maybe file doesn't exist yet */
233 if (mod_ssl.ca_certificates_file) {
234 gnutls_certificate_set_x509_trust_file (data->xcred,
235 mod_ssl.ca_certificates_file,
236 GNUTLS_X509_FMT_PEM);
240 gnutls_set_x509_client_key (data->xcred, "", "");
241 gnutls_set_x509_cert_callback (data->xcred, cert_callback);
244 gnutls_init (&data->state, GNUTLS_CLIENT);
247 gnutls_transport_set_ptr (data->state, (gnutls_transport_ptr)(intptr_t)conn->fd);
249 /* disable TLS/SSL protocols as needed */
250 if (!mod_ssl.use_tlsv1 && !mod_ssl.use_sslv3) {
251 mutt_error (_("All available protocols for TLS/SSL connection disabled"));
254 else if (!mod_ssl.use_tlsv1) {
255 protocol_priority[0] = GNUTLS_SSL3;
256 protocol_priority[1] = 0;
258 else if (!mod_ssl.use_sslv3) {
259 protocol_priority[0] = GNUTLS_TLS1;
260 protocol_priority[1] = 0;
264 use the list set above
267 /* We use default priorities (see gnutls documentation),
268 except for protocol version */
269 gnutls_set_default_priority (data->state);
270 gnutls_protocol_set_priority (data->state, protocol_priority);
272 if (mod_ssl.min_dh_prime_bits > 0) {
273 gnutls_dh_set_prime_bits(data->state, mod_ssl.min_dh_prime_bits);
277 gnutls_set_cred (data->state, GNUTLS_ANON, NULL);
280 gnutls_credentials_set (data->state, GNUTLS_CRD_CERTIFICATE, data->xcred);
282 err = gnutls_handshake (data->state);
284 while (err == GNUTLS_E_AGAIN || err == GNUTLS_E_INTERRUPTED) {
285 err = gnutls_handshake (data->state);
288 if (err == GNUTLS_E_FATAL_ALERT_RECEIVED) {
289 mutt_error (_("gnutls_handshake: %s(%s)"), gnutls_strerror (err),
290 gnutls_alert_get_name (gnutls_alert_get (data->state)));
293 mutt_error (_("gnutls_handshake: %s"), gnutls_strerror (err));
299 if (!tls_check_certificate (conn))
302 /* set Security Strength Factor (SSF) for SASL */
303 /* NB: gnutls_cipher_get_key_size() returns key length in bytes */
305 gnutls_cipher_get_key_size (gnutls_cipher_get (data->state)) * 8;
307 mutt_message (_("SSL/TLS connection using %s (%s/%s/%s)"),
308 gnutls_protocol_get_name (gnutls_protocol_get_version
310 gnutls_kx_get_name (gnutls_kx_get (data->state)),
311 gnutls_cipher_get_name (gnutls_cipher_get (data->state)),
312 gnutls_mac_get_name (gnutls_mac_get (data->state)));
318 gnutls_certificate_free_credentials (data->xcred);
319 gnutls_deinit (data->state);
320 p_delete(&conn->sockdata);
324 static int tls_socket_close (CONNECTION * conn)
326 tlssockdata *data = conn->sockdata;
329 gnutls_bye (data->state, GNUTLS_SHUT_RDWR);
331 gnutls_certificate_free_credentials (data->xcred);
332 gnutls_deinit (data->state);
333 p_delete(&conn->sockdata);
336 return raw_socket_close (conn);
339 static int tls_starttls_close (CONNECTION * conn)
343 rc = tls_socket_close (conn);
344 conn->conn_read = raw_socket_read;
345 conn->conn_write = raw_socket_write;
346 conn->conn_close = raw_socket_close;
351 #define CERT_SEP "-----BEGIN"
353 /* this bit is based on read_ca_file() in gnutls */
354 static int tls_compare_certificates (const gnutls_datum * peercert)
360 gnutls_datum b64_data;
361 unsigned char *b64_data_data;
362 struct stat filestat;
364 if (stat(mod_ssl.cert_file, &filestat) == -1)
367 b64_data.size = filestat.st_size + 1;
368 b64_data_data = p_new(unsigned char, b64_data.size);
369 b64_data_data[b64_data.size - 1] = '\0';
370 b64_data.data = b64_data_data;
372 fd1 = fopen(mod_ssl.cert_file, "r");
377 b64_data.size = fread (b64_data.data, 1, b64_data.size, fd1);
381 ret = gnutls_pem_base64_decode_alloc (NULL, &b64_data, &cert);
383 p_delete(&b64_data_data);
387 ptr = (unsigned char *) strstr ((char*) b64_data.data, CERT_SEP) + 1;
388 ptr = (unsigned char *) strstr ((char*) ptr, CERT_SEP);
390 b64_data.size = b64_data.size - (ptr - b64_data.data);
393 if (cert.size == peercert->size) {
394 if (memcmp (cert.data, peercert->data, cert.size) == 0) {
396 gnutls_free (cert.data);
397 p_delete(&b64_data_data);
402 gnutls_free (cert.data);
403 } while (ptr != NULL);
406 p_delete(&b64_data_data);
410 static void tls_fingerprint (gnutls_digest_algorithm algo,
411 char *s, int l, const gnutls_datum * data)
419 if (gnutls_fingerprint(algo, data, md, (size_t *)&n) < 0) {
420 snprintf (s, l, _("[unable to calculate]"));
423 for (j = 0; j < (int) n; j++) {
426 snprintf (ch, 8, "%02X%s", md[j], (j % 2 ? " " : ""));
429 s[2 * n + n / 2 - 1] = '\0'; /* don't want trailing space */
433 static char *tls_make_date (time_t t, char *s, ssize_t len)
435 struct tm *l = gmtime (&t);
438 snprintf (s, len, "%s, %d %s %d %02d:%02d:%02d UTC",
439 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
440 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec);
442 m_strcpy(s, len, _("[invalid date]"));
447 static int tls_check_stored_hostname (const gnutls_datum * cert,
448 const char *hostname)
452 char *linestr = NULL;
456 regmatch_t pmatch[3];
458 /* try checking against names stored in stored certs file */
459 if ((fp = fopen(mod_ssl.cert_file, "r"))) {
462 "^#H ([a-zA-Z0-9_\\.-]+) ([0-9A-F]{4}( [0-9A-F]{4}){7})[ \t]*$",
463 REG_ICASE | REG_EXTENDED) != 0) {
469 tls_fingerprint (GNUTLS_DIG_MD5, buf, sizeof (buf), cert);
471 mutt_read_line (linestr, &linestrsize, fp, &linenum)) != NULL) {
472 if (linestr[0] == '#' && linestr[1] == 'H') {
473 if (regexec (&preg, linestr, 3, pmatch, 0) == 0) {
474 linestr[pmatch[1].rm_eo] = '\0';
475 linestr[pmatch[2].rm_eo] = '\0';
476 if (m_strcmp(linestr + pmatch[1].rm_so, hostname) == 0 &&
477 m_strcmp(linestr + pmatch[2].rm_so, buf) == 0) {
491 /* not found a matching name */
495 static int tls_check_certificate (CONNECTION * conn)
497 tlssockdata *data = conn->sockdata;
498 gnutls_session state = data->state;
499 char helpstr[STRING];
503 char dn_common_name[STRING];
504 char dn_email[STRING];
505 char dn_organization[STRING];
506 char dn_organizational_unit[STRING];
507 char dn_locality[STRING];
508 char dn_province[STRING];
509 char dn_country[STRING];
511 int done, row, i, ret;
514 const gnutls_datum *cert_list;
515 unsigned int cert_list_size = 0;
516 gnutls_certificate_status_t certstat;
518 gnutls_x509_crt cert;
519 gnutls_datum pemdata;
520 int certerr_expired = 0;
521 int certerr_notyetvalid = 0;
522 int certerr_hostname = 0;
523 int certerr_nottrusted = 0;
524 int certerr_revoked = 0;
525 int certerr_signernotca = 0;
527 if (gnutls_auth_get_type (state) != GNUTLS_CRD_CERTIFICATE) {
528 mutt_error (_("Unable to get certificate from peer"));
533 if (gnutls_certificate_verify_peers2(state, &certstat) < 0) {
534 mutt_error (_("Certificate verification error (%s)"),
535 gnutls_strerror(certstat));
540 /* We only support X.509 certificates (not OpenPGP) at the moment */
541 if (gnutls_certificate_type_get (state) != GNUTLS_CRT_X509) {
542 mutt_error (_("Certificate is not X.509"));
547 if (gnutls_x509_crt_init (&cert) < 0) {
548 mutt_error (_("Error initialising gnutls certificate data"));
553 cert_list = gnutls_certificate_get_peers (state, &cert_list_size);
555 mutt_error (_("Unable to get certificate from peer"));
560 /* FIXME: Currently only check first certificate in chain. */
561 if (gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) {
562 mutt_error (_("Error processing certificate data"));
567 if (gnutls_x509_crt_get_expiration_time (cert) < time (NULL)) {
571 if (gnutls_x509_crt_get_activation_time (cert) > time (NULL)) {
572 certerr_notyetvalid = 1;
575 if (!gnutls_x509_crt_check_hostname (cert, conn->account.host) &&
576 !tls_check_stored_hostname (&cert_list[0], conn->account.host)) {
577 certerr_hostname = 1;
580 /* see whether certificate is in our cache (certificates file) */
581 if (tls_compare_certificates (&cert_list[0])) {
582 if (certstat & GNUTLS_CERT_INVALID) {
583 /* doesn't matter - have decided is valid because server
584 certificate is in our trusted cache */
585 certstat ^= GNUTLS_CERT_INVALID;
588 if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) {
589 /* doesn't matter that we haven't found the signer, since
590 certificate is in our trusted cache */
591 certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND;
594 if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) {
595 /* Hmm. Not really sure how to handle this, but let's say
596 that we don't care if the CA certificate hasn't got the
597 correct X.509 basic constraints if server certificate is
599 certstat ^= GNUTLS_CERT_SIGNER_NOT_CA;
604 if (certstat & GNUTLS_CERT_REVOKED) {
606 certstat ^= GNUTLS_CERT_REVOKED;
609 if (certstat & GNUTLS_CERT_INVALID) {
610 certerr_nottrusted = 1;
611 certstat ^= GNUTLS_CERT_INVALID;
614 if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) {
615 /* NB: already cleared if cert in cache */
616 certerr_nottrusted = 1;
617 certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND;
620 if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) {
621 /* NB: already cleared if cert in cache */
622 certerr_signernotca = 1;
623 certstat ^= GNUTLS_CERT_SIGNER_NOT_CA;
626 /* OK if signed by (or is) a trusted certificate */
627 /* we've been zeroing the interesting bits in certstat -
628 don't return OK if there are any unhandled bits we don't
630 if (!(certerr_expired || certerr_notyetvalid ||
631 certerr_hostname || certerr_nottrusted) && certstat == 0) {
632 gnutls_x509_crt_deinit (cert);
637 /* interactive check from user */
638 menu = mutt_new_menu ();
640 menu->dialog = p_new(char*, menu->max);
641 for (i = 0; i < menu->max; i++)
642 menu->dialog[i] = p_new(char, STRING);
645 m_strcpy(menu->dialog[row], STRING,
646 _("This certificate belongs to:"));
649 buflen = sizeof (dn_common_name);
650 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0,
651 dn_common_name, (size_t *)&buflen) != 0)
652 dn_common_name[0] = '\0';
653 buflen = sizeof (dn_email);
654 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0,
655 dn_email, (size_t *)&buflen) != 0)
657 buflen = sizeof (dn_organization);
658 if (gnutls_x509_crt_get_dn_by_oid
659 (cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, dn_organization,
660 (size_t *)&buflen) != 0)
661 dn_organization[0] = '\0';
662 buflen = sizeof (dn_organizational_unit);
663 if (gnutls_x509_crt_get_dn_by_oid
664 (cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0,
665 dn_organizational_unit, (size_t *)&buflen) != 0)
666 dn_organizational_unit[0] = '\0';
667 buflen = sizeof (dn_locality);
668 if (gnutls_x509_crt_get_dn_by_oid
669 (cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, dn_locality, (size_t *)&buflen) != 0)
670 dn_locality[0] = '\0';
671 buflen = sizeof (dn_province);
672 if (gnutls_x509_crt_get_dn_by_oid
673 (cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, dn_province,
674 (size_t *)&buflen) != 0)
675 dn_province[0] = '\0';
676 buflen = sizeof (dn_country);
677 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0,
678 dn_country, (size_t *)&buflen) != 0)
679 dn_country[0] = '\0';
681 snprintf (menu->dialog[row++], STRING, " %s %s", dn_common_name,
683 snprintf (menu->dialog[row++], STRING, " %s", dn_organization);
684 snprintf (menu->dialog[row++], STRING, " %s",
685 dn_organizational_unit);
686 snprintf (menu->dialog[row++], STRING, " %s %s %s", dn_locality,
687 dn_province, dn_country);
690 m_strcpy(menu->dialog[row], STRING,
691 _("This certificate was issued by:"));
694 buflen = sizeof (dn_common_name);
695 if (gnutls_x509_crt_get_issuer_dn_by_oid
696 (cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, dn_common_name, (size_t *)&buflen) != 0)
697 dn_common_name[0] = '\0';
698 buflen = sizeof (dn_email);
699 if (gnutls_x509_crt_get_issuer_dn_by_oid
700 (cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0, dn_email, (size_t *)&buflen) != 0)
702 buflen = sizeof (dn_organization);
703 if (gnutls_x509_crt_get_issuer_dn_by_oid
704 (cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, dn_organization,
705 (size_t *)&buflen) != 0)
706 dn_organization[0] = '\0';
707 buflen = sizeof (dn_organizational_unit);
708 if (gnutls_x509_crt_get_issuer_dn_by_oid
709 (cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0,
710 dn_organizational_unit, (size_t *)&buflen) != 0)
711 dn_organizational_unit[0] = '\0';
712 buflen = sizeof (dn_locality);
713 if (gnutls_x509_crt_get_issuer_dn_by_oid
714 (cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, dn_locality, (size_t *)&buflen) != 0)
715 dn_locality[0] = '\0';
716 buflen = sizeof (dn_province);
717 if (gnutls_x509_crt_get_issuer_dn_by_oid
718 (cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, dn_province,
719 (size_t *)&buflen) != 0)
720 dn_province[0] = '\0';
721 buflen = sizeof (dn_country);
722 if (gnutls_x509_crt_get_issuer_dn_by_oid
723 (cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0, dn_country, (size_t *)&buflen) != 0)
724 dn_country[0] = '\0';
726 snprintf (menu->dialog[row++], STRING, " %s %s", dn_common_name,
728 snprintf (menu->dialog[row++], STRING, " %s", dn_organization);
729 snprintf (menu->dialog[row++], STRING, " %s",
730 dn_organizational_unit);
731 snprintf (menu->dialog[row++], STRING, " %s %s %s", dn_locality,
732 dn_province, dn_country);
735 snprintf (menu->dialog[row++], STRING,
736 _("This certificate is valid"));
738 t = gnutls_x509_crt_get_activation_time (cert);
739 snprintf (menu->dialog[row++], STRING, _(" from %s"),
740 tls_make_date (t, datestr, 30));
742 t = gnutls_x509_crt_get_expiration_time (cert);
743 snprintf (menu->dialog[row++], STRING, _(" to %s"),
744 tls_make_date (t, datestr, 30));
747 tls_fingerprint (GNUTLS_DIG_SHA, fpbuf, sizeof (fpbuf), &cert_list[0]);
748 snprintf (menu->dialog[row++], STRING, _("SHA1 Fingerprint: %s"),
751 tls_fingerprint (GNUTLS_DIG_MD5, fpbuf, sizeof (fpbuf), &cert_list[0]);
752 snprintf (menu->dialog[row++], STRING, _("MD5 Fingerprint: %s"),
755 if (certerr_notyetvalid) {
757 m_strcpy(menu->dialog[row], STRING,
758 _("WARNING: Server certificate is not yet valid"));
760 if (certerr_expired) {
762 m_strcpy(menu->dialog[row], STRING,
763 _("WARNING: Server certificate has expired"));
765 if (certerr_revoked) {
767 m_strcpy(menu->dialog[row], STRING,
768 _("WARNING: Server certificate has been revoked"));
770 if (certerr_hostname) {
772 m_strcpy(menu->dialog[row], STRING,
773 _("WARNING: Server hostname does not match certificate"));
775 if (certerr_signernotca) {
777 m_strcpy(menu->dialog[row], STRING,
778 _("WARNING: Signer of server certificate is not a CA"));
781 menu->title = _("TLS/SSL Certificate check");
782 /* certificates with bad dates, or that are revoked, must be
783 accepted manually each and every time */
784 if (mod_ssl.cert_file && !certerr_expired && !certerr_notyetvalid
785 && !certerr_revoked) {
786 menu->prompt = _("(r)eject, accept (o)nce, (a)ccept always");
787 menu->keys = _("roa");
790 menu->prompt = _("(r)eject, accept (o)nce");
791 menu->keys = _("ro");
795 mutt_make_help (buf, sizeof (buf), _("Exit "), MENU_GENERIC, OP_EXIT);
796 strncat (helpstr, buf, sizeof (helpstr));
797 mutt_make_help (buf, sizeof (buf), _("Help"), MENU_GENERIC, OP_HELP);
798 strncat (helpstr, buf, sizeof (helpstr));
799 menu->help = helpstr;
802 set_option (OPTUNBUFFEREDINPUT);
804 switch (mutt_menuLoop (menu)) {
806 case OP_MAX + 1: /* reject */
810 case OP_MAX + 3: /* accept always */
812 if ((fp = fopen(mod_ssl.cert_file, "a"))) {
813 /* save hostname if necessary */
814 if (certerr_hostname) {
815 fprintf (fp, "#H %s %s\n", conn->account.host, fpbuf);
818 if (certerr_nottrusted) {
820 ret = gnutls_pem_base64_encode_alloc ("CERTIFICATE", &cert_list[0],
823 if (fwrite (pemdata.data, pemdata.size, 1, fp) == 1) {
826 gnutls_free (pemdata.data);
832 mutt_error (_("Warning: Couldn't save certificate"));
836 mutt_message (_("Certificate saved"));
840 case OP_MAX + 2: /* accept once */
845 unset_option (OPTUNBUFFEREDINPUT);
846 mutt_menuDestroy (&menu);
847 gnutls_x509_crt_deinit (cert);