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>
16 #include <lib-ui/curses.h>
17 #include <lib-ui/menu.h>
20 #include "mutt_socket.h"
22 @import "../lib-lua/base.cpkg"
28 ** If this variable is \fIset\fP, Madmutt will require that all connections
29 ** to remote servers be encrypted. Furthermore it will attempt to
30 ** negotiate TLS even if the server does not advertise the capability,
31 ** since it would otherwise have to abort the connection anyway. This
32 ** option supersedes ``$$ssl_starttls''.
37 ** If \fIset\fP (the default), Madmutt will attempt to use STARTTLS on servers
38 ** advertising the capability. When \fIunset\fP, Madmutt will not attempt to
39 ** use STARTTLS regardless of the server's capabilities.
44 ** This variables specifies whether to attempt to use SSLv3 in the
45 ** SSL authentication process.
50 ** This variables specifies whether to attempt to use TLSv1 in the
51 ** SSL authentication process.
54 int min_dh_prime_bits = 0;
57 ** This variable specifies the minimum acceptable prime size (in bits)
58 ** for use in any Diffie-Hellman key exchange. A value of 0 will use
59 ** the default from the GNUTLS library.
62 path_t cert_file = luaM_pathnew("~/.cache/madmutt/certificates");
65 ** This variable specifies the file where the certificates you trust
66 ** are saved. When an unknown certificate is encountered, you are asked
67 ** if you accept it or not. If you accept it, the certificate can also
68 ** be saved in this file and further connections are automatically
71 ** You can also manually add CA certificates in this file. Any server
72 ** certificate that is signed with one of these CA certificates are
73 ** also automatically accepted.
75 ** Example: \fTset certificate_file=~/.madmutt/certificates\fP
78 path_t ca_certificates_file = NULL;
81 ** This variable specifies a file containing trusted CA certificates.
82 ** Any server certificate that is signed with one of these CA
83 ** certificates are also automatically accepted.
85 ** Example: \fTset ssl_ca_certificates_file=/etc/ssl/certs/ca-certificates.crt\fP
89 typedef struct _tlssockdata {
91 gnutls_certificate_credentials xcred;
94 /* local prototypes */
95 static int tls_socket_read (CONNECTION * conn, char *buf, ssize_t len);
96 static int tls_socket_write (CONNECTION * conn, const char *buf, ssize_t len);
97 static int tls_socket_open (CONNECTION * conn);
98 static int tls_socket_close (CONNECTION * conn);
99 static int tls_starttls_close (CONNECTION * conn);
101 static int tls_init (void);
102 static int tls_negotiate (CONNECTION * conn);
103 static int tls_check_certificate (CONNECTION * conn);
106 static int tls_init (void)
108 static unsigned char init_complete = 0;
114 err = gnutls_global_init ();
116 mutt_error (_("gnutls_global_init: %s"), gnutls_strerror (err));
125 int mutt_ssl_socket_setup (CONNECTION * conn)
130 conn->conn_open = tls_socket_open;
131 conn->conn_read = tls_socket_read;
132 conn->conn_write = tls_socket_write;
133 conn->conn_close = tls_socket_close;
138 static int tls_socket_read (CONNECTION * conn, char *buf, ssize_t len)
140 tlssockdata *data = conn->sockdata;
144 mutt_error (_("Error: no TLS socket open"));
149 ret = gnutls_record_recv (data->state, buf, len);
150 if (gnutls_error_is_fatal (ret) == 1) {
151 mutt_error (_("tls_socket_read (%s)"), gnutls_strerror (ret));
158 static int tls_socket_write (CONNECTION * conn, const char *buf, ssize_t len)
160 tlssockdata *data = conn->sockdata;
164 mutt_error (_("Error: no TLS socket open"));
169 ret = gnutls_record_send (data->state, buf, len);
170 if (gnutls_error_is_fatal (ret) == 1) {
171 mutt_error (_("tls_socket_write (%s)"), gnutls_strerror (ret));
178 static int tls_socket_open (CONNECTION * conn)
180 if (raw_socket_open (conn) < 0)
183 if (tls_negotiate (conn) < 0) {
184 tls_socket_close (conn);
191 int mutt_ssl_starttls (CONNECTION * conn)
196 if (tls_negotiate (conn) < 0)
199 conn->conn_read = tls_socket_read;
200 conn->conn_write = tls_socket_write;
201 conn->conn_close = tls_starttls_close;
206 static int protocol_priority[] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 };
208 /* tls_negotiate: After TLS state has been initialised, attempt to negotiate
209 * TLS over the wire, including certificate checks. */
210 static int tls_negotiate (CONNECTION * conn)
215 data = p_new(tlssockdata, 1);
216 conn->sockdata = data;
217 err = gnutls_certificate_allocate_credentials (&data->xcred);
219 p_delete(&conn->sockdata);
220 mutt_error (_("gnutls_certificate_allocate_credentials: %s"),
221 gnutls_strerror (err));
226 gnutls_certificate_set_x509_trust_file (data->xcred, mod_ssl.cert_file,
227 GNUTLS_X509_FMT_PEM);
228 /* ignore errors, maybe file doesn't exist yet */
230 if (mod_ssl.ca_certificates_file) {
231 gnutls_certificate_set_x509_trust_file (data->xcred,
232 mod_ssl.ca_certificates_file,
233 GNUTLS_X509_FMT_PEM);
237 gnutls_set_x509_client_key (data->xcred, "", "");
238 gnutls_set_x509_cert_callback (data->xcred, cert_callback);
241 gnutls_init (&data->state, GNUTLS_CLIENT);
244 gnutls_transport_set_ptr (data->state, (gnutls_transport_ptr)(intptr_t)conn->fd);
246 /* disable TLS/SSL protocols as needed */
247 if (!mod_ssl.use_tlsv1 && !mod_ssl.use_sslv3) {
248 mutt_error (_("All available protocols for TLS/SSL connection disabled"));
251 else if (!mod_ssl.use_tlsv1) {
252 protocol_priority[0] = GNUTLS_SSL3;
253 protocol_priority[1] = 0;
255 else if (!mod_ssl.use_sslv3) {
256 protocol_priority[0] = GNUTLS_TLS1;
257 protocol_priority[1] = 0;
261 use the list set above
264 /* We use default priorities (see gnutls documentation),
265 except for protocol version */
266 gnutls_set_default_priority (data->state);
267 gnutls_protocol_set_priority (data->state, protocol_priority);
269 if (mod_ssl.min_dh_prime_bits > 0) {
270 gnutls_dh_set_prime_bits(data->state, mod_ssl.min_dh_prime_bits);
274 gnutls_set_cred (data->state, GNUTLS_ANON, NULL);
277 gnutls_credentials_set (data->state, GNUTLS_CRD_CERTIFICATE, data->xcred);
279 err = gnutls_handshake (data->state);
281 while (err == GNUTLS_E_AGAIN || err == GNUTLS_E_INTERRUPTED) {
282 err = gnutls_handshake (data->state);
285 if (err == GNUTLS_E_FATAL_ALERT_RECEIVED) {
286 mutt_error (_("gnutls_handshake: %s(%s)"), gnutls_strerror (err),
287 gnutls_alert_get_name (gnutls_alert_get (data->state)));
290 mutt_error (_("gnutls_handshake: %s"), gnutls_strerror (err));
296 if (!tls_check_certificate (conn))
299 /* set Security Strength Factor (SSF) for SASL */
300 /* NB: gnutls_cipher_get_key_size() returns key length in bytes */
302 gnutls_cipher_get_key_size (gnutls_cipher_get (data->state)) * 8;
304 mutt_message (_("SSL/TLS connection using %s (%s/%s/%s)"),
305 gnutls_protocol_get_name (gnutls_protocol_get_version
307 gnutls_kx_get_name (gnutls_kx_get (data->state)),
308 gnutls_cipher_get_name (gnutls_cipher_get (data->state)),
309 gnutls_mac_get_name (gnutls_mac_get (data->state)));
315 gnutls_certificate_free_credentials (data->xcred);
316 gnutls_deinit (data->state);
317 p_delete(&conn->sockdata);
321 static int tls_socket_close (CONNECTION * conn)
323 tlssockdata *data = conn->sockdata;
326 gnutls_bye (data->state, GNUTLS_SHUT_RDWR);
328 gnutls_certificate_free_credentials (data->xcred);
329 gnutls_deinit (data->state);
330 p_delete(&conn->sockdata);
333 return raw_socket_close (conn);
336 static int tls_starttls_close (CONNECTION * conn)
340 rc = tls_socket_close (conn);
341 conn->conn_read = raw_socket_read;
342 conn->conn_write = raw_socket_write;
343 conn->conn_close = raw_socket_close;
348 #define CERT_SEP "-----BEGIN"
350 /* this bit is based on read_ca_file() in gnutls */
351 static int tls_compare_certificates (const gnutls_datum * peercert)
357 gnutls_datum b64_data;
358 unsigned char *b64_data_data;
359 struct stat filestat;
361 if (stat(mod_ssl.cert_file, &filestat) == -1)
364 b64_data.size = filestat.st_size + 1;
365 b64_data_data = p_new(unsigned char, b64_data.size);
366 b64_data_data[b64_data.size - 1] = '\0';
367 b64_data.data = b64_data_data;
369 fd1 = fopen(mod_ssl.cert_file, "r");
374 b64_data.size = fread (b64_data.data, 1, b64_data.size, fd1);
378 ret = gnutls_pem_base64_decode_alloc (NULL, &b64_data, &cert);
380 p_delete(&b64_data_data);
384 ptr = (unsigned char *) strstr ((char*) b64_data.data, CERT_SEP) + 1;
385 ptr = (unsigned char *) strstr ((char*) ptr, CERT_SEP);
387 b64_data.size = b64_data.size - (ptr - b64_data.data);
390 if (cert.size == peercert->size) {
391 if (memcmp (cert.data, peercert->data, cert.size) == 0) {
393 gnutls_free (cert.data);
394 p_delete(&b64_data_data);
399 gnutls_free (cert.data);
400 } while (ptr != NULL);
403 p_delete(&b64_data_data);
407 static void tls_fingerprint (gnutls_digest_algorithm algo,
408 char *s, int l, const gnutls_datum * data)
416 if (gnutls_fingerprint(algo, data, md, (size_t *)&n) < 0) {
417 snprintf (s, l, _("[unable to calculate]"));
420 for (j = 0; j < (int) n; j++) {
423 snprintf (ch, 8, "%02X%s", md[j], (j % 2 ? " " : ""));
426 s[2 * n + n / 2 - 1] = '\0'; /* don't want trailing space */
430 static char *tls_make_date (time_t t, char *s, ssize_t len)
432 struct tm *l = gmtime (&t);
435 snprintf (s, len, "%s, %d %s %d %02d:%02d:%02d UTC",
436 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
437 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec);
439 m_strcpy(s, len, _("[invalid date]"));
444 static int tls_check_stored_hostname (const gnutls_datum * cert,
445 const char *hostname)
449 char *linestr = NULL;
453 regmatch_t pmatch[3];
455 /* try checking against names stored in stored certs file */
456 if ((fp = fopen(mod_ssl.cert_file, "r"))) {
459 "^#H ([a-zA-Z0-9_\\.-]+) ([0-9A-F]{4}( [0-9A-F]{4}){7})[ \t]*$",
460 REG_ICASE | REG_EXTENDED) != 0) {
466 tls_fingerprint (GNUTLS_DIG_MD5, buf, sizeof (buf), cert);
468 mutt_read_line (linestr, &linestrsize, fp, &linenum)) != NULL) {
469 if (linestr[0] == '#' && linestr[1] == 'H') {
470 if (regexec (&preg, linestr, 3, pmatch, 0) == 0) {
471 linestr[pmatch[1].rm_eo] = '\0';
472 linestr[pmatch[2].rm_eo] = '\0';
473 if (m_strcmp(linestr + pmatch[1].rm_so, hostname) == 0 &&
474 m_strcmp(linestr + pmatch[2].rm_so, buf) == 0) {
488 /* not found a matching name */
492 static int tls_check_certificate (CONNECTION * conn)
494 tlssockdata *data = conn->sockdata;
495 gnutls_session state = data->state;
496 char helpstr[STRING];
500 char dn_common_name[STRING];
501 char dn_email[STRING];
502 char dn_organization[STRING];
503 char dn_organizational_unit[STRING];
504 char dn_locality[STRING];
505 char dn_province[STRING];
506 char dn_country[STRING];
508 int done, row, i, ret;
511 const gnutls_datum *cert_list;
512 unsigned int cert_list_size = 0;
513 gnutls_certificate_status_t certstat;
515 gnutls_x509_crt cert;
516 gnutls_datum pemdata;
517 int certerr_expired = 0;
518 int certerr_notyetvalid = 0;
519 int certerr_hostname = 0;
520 int certerr_nottrusted = 0;
521 int certerr_revoked = 0;
522 int certerr_signernotca = 0;
524 if (gnutls_auth_get_type (state) != GNUTLS_CRD_CERTIFICATE) {
525 mutt_error (_("Unable to get certificate from peer"));
530 if (gnutls_certificate_verify_peers2(state, &certstat) < 0) {
531 mutt_error (_("Certificate verification error (%s)"),
532 gnutls_strerror(certstat));
537 /* We only support X.509 certificates (not OpenPGP) at the moment */
538 if (gnutls_certificate_type_get (state) != GNUTLS_CRT_X509) {
539 mutt_error (_("Certificate is not X.509"));
544 if (gnutls_x509_crt_init (&cert) < 0) {
545 mutt_error (_("Error initialising gnutls certificate data"));
550 cert_list = gnutls_certificate_get_peers (state, &cert_list_size);
552 mutt_error (_("Unable to get certificate from peer"));
557 /* FIXME: Currently only check first certificate in chain. */
558 if (gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) {
559 mutt_error (_("Error processing certificate data"));
564 if (gnutls_x509_crt_get_expiration_time (cert) < time (NULL)) {
568 if (gnutls_x509_crt_get_activation_time (cert) > time (NULL)) {
569 certerr_notyetvalid = 1;
572 if (!gnutls_x509_crt_check_hostname (cert, conn->account.host) &&
573 !tls_check_stored_hostname (&cert_list[0], conn->account.host)) {
574 certerr_hostname = 1;
577 /* see whether certificate is in our cache (certificates file) */
578 if (tls_compare_certificates (&cert_list[0])) {
579 if (certstat & GNUTLS_CERT_INVALID) {
580 /* doesn't matter - have decided is valid because server
581 certificate is in our trusted cache */
582 certstat ^= GNUTLS_CERT_INVALID;
585 if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) {
586 /* doesn't matter that we haven't found the signer, since
587 certificate is in our trusted cache */
588 certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND;
591 if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) {
592 /* Hmm. Not really sure how to handle this, but let's say
593 that we don't care if the CA certificate hasn't got the
594 correct X.509 basic constraints if server certificate is
596 certstat ^= GNUTLS_CERT_SIGNER_NOT_CA;
601 if (certstat & GNUTLS_CERT_REVOKED) {
603 certstat ^= GNUTLS_CERT_REVOKED;
606 if (certstat & GNUTLS_CERT_INVALID) {
607 certerr_nottrusted = 1;
608 certstat ^= GNUTLS_CERT_INVALID;
611 if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) {
612 /* NB: already cleared if cert in cache */
613 certerr_nottrusted = 1;
614 certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND;
617 if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) {
618 /* NB: already cleared if cert in cache */
619 certerr_signernotca = 1;
620 certstat ^= GNUTLS_CERT_SIGNER_NOT_CA;
623 /* OK if signed by (or is) a trusted certificate */
624 /* we've been zeroing the interesting bits in certstat -
625 don't return OK if there are any unhandled bits we don't
627 if (!(certerr_expired || certerr_notyetvalid ||
628 certerr_hostname || certerr_nottrusted) && certstat == 0) {
629 gnutls_x509_crt_deinit (cert);
634 /* interactive check from user */
635 menu = mutt_new_menu ();
637 menu->dialog = p_new(char*, menu->max);
638 for (i = 0; i < menu->max; i++)
639 menu->dialog[i] = p_new(char, STRING);
642 m_strcpy(menu->dialog[row], STRING,
643 _("This certificate belongs to:"));
646 buflen = sizeof (dn_common_name);
647 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0,
648 dn_common_name, (size_t *)&buflen) != 0)
649 dn_common_name[0] = '\0';
650 buflen = sizeof (dn_email);
651 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0,
652 dn_email, (size_t *)&buflen) != 0)
654 buflen = sizeof (dn_organization);
655 if (gnutls_x509_crt_get_dn_by_oid
656 (cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, dn_organization,
657 (size_t *)&buflen) != 0)
658 dn_organization[0] = '\0';
659 buflen = sizeof (dn_organizational_unit);
660 if (gnutls_x509_crt_get_dn_by_oid
661 (cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0,
662 dn_organizational_unit, (size_t *)&buflen) != 0)
663 dn_organizational_unit[0] = '\0';
664 buflen = sizeof (dn_locality);
665 if (gnutls_x509_crt_get_dn_by_oid
666 (cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, dn_locality, (size_t *)&buflen) != 0)
667 dn_locality[0] = '\0';
668 buflen = sizeof (dn_province);
669 if (gnutls_x509_crt_get_dn_by_oid
670 (cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, dn_province,
671 (size_t *)&buflen) != 0)
672 dn_province[0] = '\0';
673 buflen = sizeof (dn_country);
674 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0,
675 dn_country, (size_t *)&buflen) != 0)
676 dn_country[0] = '\0';
678 snprintf (menu->dialog[row++], STRING, " %s %s", dn_common_name,
680 snprintf (menu->dialog[row++], STRING, " %s", dn_organization);
681 snprintf (menu->dialog[row++], STRING, " %s",
682 dn_organizational_unit);
683 snprintf (menu->dialog[row++], STRING, " %s %s %s", dn_locality,
684 dn_province, dn_country);
687 m_strcpy(menu->dialog[row], STRING,
688 _("This certificate was issued by:"));
691 buflen = sizeof (dn_common_name);
692 if (gnutls_x509_crt_get_issuer_dn_by_oid
693 (cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, dn_common_name, (size_t *)&buflen) != 0)
694 dn_common_name[0] = '\0';
695 buflen = sizeof (dn_email);
696 if (gnutls_x509_crt_get_issuer_dn_by_oid
697 (cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0, dn_email, (size_t *)&buflen) != 0)
699 buflen = sizeof (dn_organization);
700 if (gnutls_x509_crt_get_issuer_dn_by_oid
701 (cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, dn_organization,
702 (size_t *)&buflen) != 0)
703 dn_organization[0] = '\0';
704 buflen = sizeof (dn_organizational_unit);
705 if (gnutls_x509_crt_get_issuer_dn_by_oid
706 (cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0,
707 dn_organizational_unit, (size_t *)&buflen) != 0)
708 dn_organizational_unit[0] = '\0';
709 buflen = sizeof (dn_locality);
710 if (gnutls_x509_crt_get_issuer_dn_by_oid
711 (cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, dn_locality, (size_t *)&buflen) != 0)
712 dn_locality[0] = '\0';
713 buflen = sizeof (dn_province);
714 if (gnutls_x509_crt_get_issuer_dn_by_oid
715 (cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, dn_province,
716 (size_t *)&buflen) != 0)
717 dn_province[0] = '\0';
718 buflen = sizeof (dn_country);
719 if (gnutls_x509_crt_get_issuer_dn_by_oid
720 (cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0, dn_country, (size_t *)&buflen) != 0)
721 dn_country[0] = '\0';
723 snprintf (menu->dialog[row++], STRING, " %s %s", dn_common_name,
725 snprintf (menu->dialog[row++], STRING, " %s", dn_organization);
726 snprintf (menu->dialog[row++], STRING, " %s",
727 dn_organizational_unit);
728 snprintf (menu->dialog[row++], STRING, " %s %s %s", dn_locality,
729 dn_province, dn_country);
732 snprintf (menu->dialog[row++], STRING,
733 _("This certificate is valid"));
735 t = gnutls_x509_crt_get_activation_time (cert);
736 snprintf (menu->dialog[row++], STRING, _(" from %s"),
737 tls_make_date (t, datestr, 30));
739 t = gnutls_x509_crt_get_expiration_time (cert);
740 snprintf (menu->dialog[row++], STRING, _(" to %s"),
741 tls_make_date (t, datestr, 30));
744 tls_fingerprint (GNUTLS_DIG_SHA, fpbuf, sizeof (fpbuf), &cert_list[0]);
745 snprintf (menu->dialog[row++], STRING, _("SHA1 Fingerprint: %s"),
748 tls_fingerprint (GNUTLS_DIG_MD5, fpbuf, sizeof (fpbuf), &cert_list[0]);
749 snprintf (menu->dialog[row++], STRING, _("MD5 Fingerprint: %s"),
752 if (certerr_notyetvalid) {
754 m_strcpy(menu->dialog[row], STRING,
755 _("WARNING: Server certificate is not yet valid"));
757 if (certerr_expired) {
759 m_strcpy(menu->dialog[row], STRING,
760 _("WARNING: Server certificate has expired"));
762 if (certerr_revoked) {
764 m_strcpy(menu->dialog[row], STRING,
765 _("WARNING: Server certificate has been revoked"));
767 if (certerr_hostname) {
769 m_strcpy(menu->dialog[row], STRING,
770 _("WARNING: Server hostname does not match certificate"));
772 if (certerr_signernotca) {
774 m_strcpy(menu->dialog[row], STRING,
775 _("WARNING: Signer of server certificate is not a CA"));
778 menu->title = _("TLS/SSL Certificate check");
779 /* certificates with bad dates, or that are revoked, must be
780 accepted manually each and every time */
781 if (mod_ssl.cert_file && !certerr_expired && !certerr_notyetvalid
782 && !certerr_revoked) {
783 menu->prompt = _("(r)eject, accept (o)nce, (a)ccept always");
784 menu->keys = _("roa");
787 menu->prompt = _("(r)eject, accept (o)nce");
788 menu->keys = _("ro");
792 mutt_make_help (buf, sizeof (buf), _("Exit "), MENU_GENERIC, OP_EXIT);
793 strncat (helpstr, buf, sizeof (helpstr));
794 mutt_make_help (buf, sizeof (buf), _("Help"), MENU_GENERIC, OP_HELP);
795 strncat (helpstr, buf, sizeof (helpstr));
796 menu->help = helpstr;
799 set_option (OPTUNBUFFEREDINPUT);
801 switch (mutt_menuLoop (menu)) {
803 case OP_MAX + 1: /* reject */
807 case OP_MAX + 3: /* accept always */
809 if ((fp = fopen(mod_ssl.cert_file, "a"))) {
810 /* save hostname if necessary */
811 if (certerr_hostname) {
812 fprintf (fp, "#H %s %s\n", conn->account.host, fpbuf);
815 if (certerr_nottrusted) {
817 ret = gnutls_pem_base64_encode_alloc ("CERTIFICATE", &cert_list[0],
820 if (fwrite (pemdata.data, pemdata.size, 1, fp) == 1) {
823 gnutls_free (pemdata.data);
829 mutt_error (_("Warning: Couldn't save certificate"));
833 mutt_message (_("Certificate saved"));
837 case OP_MAX + 2: /* accept once */
842 unset_option (OPTUNBUFFEREDINPUT);
843 mutt_menuDestroy (&menu);
844 gnutls_x509_crt_deinit (cert);