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/lib-ui.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;
498 char dn_common_name[STRING];
499 char dn_email[STRING];
500 char dn_organization[STRING];
501 char dn_organizational_unit[STRING];
502 char dn_locality[STRING];
503 char dn_province[STRING];
504 char dn_country[STRING];
506 int done, row, i, ret;
509 const gnutls_datum *cert_list;
510 unsigned int cert_list_size = 0;
511 gnutls_certificate_status_t certstat;
513 gnutls_x509_crt cert;
514 gnutls_datum pemdata;
515 int certerr_expired = 0;
516 int certerr_notyetvalid = 0;
517 int certerr_hostname = 0;
518 int certerr_nottrusted = 0;
519 int certerr_revoked = 0;
520 int certerr_signernotca = 0;
522 if (gnutls_auth_get_type (state) != GNUTLS_CRD_CERTIFICATE) {
523 mutt_error (_("Unable to get certificate from peer"));
528 if (gnutls_certificate_verify_peers2(state, &certstat) < 0) {
529 mutt_error (_("Certificate verification error (%s)"),
530 gnutls_strerror(certstat));
535 /* We only support X.509 certificates (not OpenPGP) at the moment */
536 if (gnutls_certificate_type_get (state) != GNUTLS_CRT_X509) {
537 mutt_error (_("Certificate is not X.509"));
542 if (gnutls_x509_crt_init (&cert) < 0) {
543 mutt_error (_("Error initialising gnutls certificate data"));
548 cert_list = gnutls_certificate_get_peers (state, &cert_list_size);
550 mutt_error (_("Unable to get certificate from peer"));
555 /* FIXME: Currently only check first certificate in chain. */
556 if (gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) {
557 mutt_error (_("Error processing certificate data"));
562 if (gnutls_x509_crt_get_expiration_time (cert) < time (NULL)) {
566 if (gnutls_x509_crt_get_activation_time (cert) > time (NULL)) {
567 certerr_notyetvalid = 1;
570 if (!gnutls_x509_crt_check_hostname (cert, conn->account.host) &&
571 !tls_check_stored_hostname (&cert_list[0], conn->account.host)) {
572 certerr_hostname = 1;
575 /* see whether certificate is in our cache (certificates file) */
576 if (tls_compare_certificates (&cert_list[0])) {
577 if (certstat & GNUTLS_CERT_INVALID) {
578 /* doesn't matter - have decided is valid because server
579 certificate is in our trusted cache */
580 certstat ^= GNUTLS_CERT_INVALID;
583 if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) {
584 /* doesn't matter that we haven't found the signer, since
585 certificate is in our trusted cache */
586 certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND;
589 if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) {
590 /* Hmm. Not really sure how to handle this, but let's say
591 that we don't care if the CA certificate hasn't got the
592 correct X.509 basic constraints if server certificate is
594 certstat ^= GNUTLS_CERT_SIGNER_NOT_CA;
599 if (certstat & GNUTLS_CERT_REVOKED) {
601 certstat ^= GNUTLS_CERT_REVOKED;
604 if (certstat & GNUTLS_CERT_INVALID) {
605 certerr_nottrusted = 1;
606 certstat ^= GNUTLS_CERT_INVALID;
609 if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) {
610 /* NB: already cleared if cert in cache */
611 certerr_nottrusted = 1;
612 certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND;
615 if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) {
616 /* NB: already cleared if cert in cache */
617 certerr_signernotca = 1;
618 certstat ^= GNUTLS_CERT_SIGNER_NOT_CA;
621 /* OK if signed by (or is) a trusted certificate */
622 /* we've been zeroing the interesting bits in certstat -
623 don't return OK if there are any unhandled bits we don't
625 if (!(certerr_expired || certerr_notyetvalid ||
626 certerr_hostname || certerr_nottrusted) && certstat == 0) {
627 gnutls_x509_crt_deinit (cert);
632 /* interactive check from user */
633 menu = mutt_new_menu ();
635 menu->dialog = p_new(char*, menu->max);
636 for (i = 0; i < menu->max; i++)
637 menu->dialog[i] = p_new(char, STRING);
640 m_strcpy(menu->dialog[row], STRING,
641 _("This certificate belongs to:"));
644 buflen = sizeof (dn_common_name);
645 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0,
646 dn_common_name, (size_t *)&buflen) != 0)
647 dn_common_name[0] = '\0';
648 buflen = sizeof (dn_email);
649 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0,
650 dn_email, (size_t *)&buflen) != 0)
652 buflen = sizeof (dn_organization);
653 if (gnutls_x509_crt_get_dn_by_oid
654 (cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, dn_organization,
655 (size_t *)&buflen) != 0)
656 dn_organization[0] = '\0';
657 buflen = sizeof (dn_organizational_unit);
658 if (gnutls_x509_crt_get_dn_by_oid
659 (cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0,
660 dn_organizational_unit, (size_t *)&buflen) != 0)
661 dn_organizational_unit[0] = '\0';
662 buflen = sizeof (dn_locality);
663 if (gnutls_x509_crt_get_dn_by_oid
664 (cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, dn_locality, (size_t *)&buflen) != 0)
665 dn_locality[0] = '\0';
666 buflen = sizeof (dn_province);
667 if (gnutls_x509_crt_get_dn_by_oid
668 (cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, dn_province,
669 (size_t *)&buflen) != 0)
670 dn_province[0] = '\0';
671 buflen = sizeof (dn_country);
672 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0,
673 dn_country, (size_t *)&buflen) != 0)
674 dn_country[0] = '\0';
676 snprintf (menu->dialog[row++], STRING, " %s %s", dn_common_name,
678 snprintf (menu->dialog[row++], STRING, " %s", dn_organization);
679 snprintf (menu->dialog[row++], STRING, " %s",
680 dn_organizational_unit);
681 snprintf (menu->dialog[row++], STRING, " %s %s %s", dn_locality,
682 dn_province, dn_country);
685 m_strcpy(menu->dialog[row], STRING,
686 _("This certificate was issued by:"));
689 buflen = sizeof (dn_common_name);
690 if (gnutls_x509_crt_get_issuer_dn_by_oid
691 (cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, dn_common_name, (size_t *)&buflen) != 0)
692 dn_common_name[0] = '\0';
693 buflen = sizeof (dn_email);
694 if (gnutls_x509_crt_get_issuer_dn_by_oid
695 (cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0, dn_email, (size_t *)&buflen) != 0)
697 buflen = sizeof (dn_organization);
698 if (gnutls_x509_crt_get_issuer_dn_by_oid
699 (cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, dn_organization,
700 (size_t *)&buflen) != 0)
701 dn_organization[0] = '\0';
702 buflen = sizeof (dn_organizational_unit);
703 if (gnutls_x509_crt_get_issuer_dn_by_oid
704 (cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0,
705 dn_organizational_unit, (size_t *)&buflen) != 0)
706 dn_organizational_unit[0] = '\0';
707 buflen = sizeof (dn_locality);
708 if (gnutls_x509_crt_get_issuer_dn_by_oid
709 (cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, dn_locality, (size_t *)&buflen) != 0)
710 dn_locality[0] = '\0';
711 buflen = sizeof (dn_province);
712 if (gnutls_x509_crt_get_issuer_dn_by_oid
713 (cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, dn_province,
714 (size_t *)&buflen) != 0)
715 dn_province[0] = '\0';
716 buflen = sizeof (dn_country);
717 if (gnutls_x509_crt_get_issuer_dn_by_oid
718 (cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0, dn_country, (size_t *)&buflen) != 0)
719 dn_country[0] = '\0';
721 snprintf (menu->dialog[row++], STRING, " %s %s", dn_common_name,
723 snprintf (menu->dialog[row++], STRING, " %s", dn_organization);
724 snprintf (menu->dialog[row++], STRING, " %s",
725 dn_organizational_unit);
726 snprintf (menu->dialog[row++], STRING, " %s %s %s", dn_locality,
727 dn_province, dn_country);
730 snprintf (menu->dialog[row++], STRING,
731 _("This certificate is valid"));
733 t = gnutls_x509_crt_get_activation_time (cert);
734 snprintf (menu->dialog[row++], STRING, _(" from %s"),
735 tls_make_date (t, datestr, 30));
737 t = gnutls_x509_crt_get_expiration_time (cert);
738 snprintf (menu->dialog[row++], STRING, _(" to %s"),
739 tls_make_date (t, datestr, 30));
742 tls_fingerprint (GNUTLS_DIG_SHA, fpbuf, sizeof (fpbuf), &cert_list[0]);
743 snprintf (menu->dialog[row++], STRING, _("SHA1 Fingerprint: %s"),
746 tls_fingerprint (GNUTLS_DIG_MD5, fpbuf, sizeof (fpbuf), &cert_list[0]);
747 snprintf (menu->dialog[row++], STRING, _("MD5 Fingerprint: %s"),
750 if (certerr_notyetvalid) {
752 m_strcpy(menu->dialog[row], STRING,
753 _("WARNING: Server certificate is not yet valid"));
755 if (certerr_expired) {
757 m_strcpy(menu->dialog[row], STRING,
758 _("WARNING: Server certificate has expired"));
760 if (certerr_revoked) {
762 m_strcpy(menu->dialog[row], STRING,
763 _("WARNING: Server certificate has been revoked"));
765 if (certerr_hostname) {
767 m_strcpy(menu->dialog[row], STRING,
768 _("WARNING: Server hostname does not match certificate"));
770 if (certerr_signernotca) {
772 m_strcpy(menu->dialog[row], STRING,
773 _("WARNING: Signer of server certificate is not a CA"));
776 menu->title = _("TLS/SSL Certificate check");
777 /* certificates with bad dates, or that are revoked, must be
778 accepted manually each and every time */
779 if (mod_ssl.cert_file && !certerr_expired && !certerr_notyetvalid
780 && !certerr_revoked) {
781 menu->prompt = _("(r)eject, accept (o)nce, (a)ccept always");
782 menu->keys = _("roa");
785 menu->prompt = _("(r)eject, accept (o)nce");
786 menu->keys = _("ro");
790 set_option (OPTUNBUFFEREDINPUT);
792 switch (mutt_menuLoop (menu)) {
794 case OP_MAX + 1: /* reject */
798 case OP_MAX + 3: /* accept always */
800 if ((fp = fopen(mod_ssl.cert_file, "a"))) {
801 /* save hostname if necessary */
802 if (certerr_hostname) {
803 fprintf (fp, "#H %s %s\n", conn->account.host, fpbuf);
806 if (certerr_nottrusted) {
808 ret = gnutls_pem_base64_encode_alloc ("CERTIFICATE", &cert_list[0],
811 if (fwrite (pemdata.data, pemdata.size, 1, fp) == 1) {
814 gnutls_free (pemdata.data);
820 mutt_error (_("Warning: Couldn't save certificate"));
824 mutt_message (_("Certificate saved"));
828 case OP_MAX + 2: /* accept once */
833 unset_option (OPTUNBUFFEREDINPUT);
834 mutt_menuDestroy (&menu);
835 gnutls_x509_crt_deinit (cert);