X-Git-Url: http://git.madism.org/?p=apps%2Fmadmutt.git;a=blobdiff_plain;f=lib-sys%2Fmutt_ssl.cpkg;fp=lib-sys%2Fmutt_ssl.cpkg;h=d2663581e1e51bd96553a69dfd7611c8fcb7fcf7;hp=0000000000000000000000000000000000000000;hb=3bb9d26e7eb111656d716e34d81225dfb16b6c4b;hpb=55de28a5bb96c3edfbb2a3b080356acbb44a3cc4 diff --git a/lib-sys/mutt_ssl.cpkg b/lib-sys/mutt_ssl.cpkg new file mode 100644 index 0000000..d266358 --- /dev/null +++ b/lib-sys/mutt_ssl.cpkg @@ -0,0 +1,781 @@ +/* + * Copyright notice from original mutt: + * Copyright (C) 2001 Marco d'Itri + * Copyright (C) 2001-2004 Andrew McDonald + * + * This file is part of mutt-ng, see http://www.muttng.org/. + * It's licensed under the GNU General Public License, + * please see the file GPL in the top level source directory. + */ + +#include + +#include +#include +#ifdef HAVE_GNUTLS_OPENSSL_H +#include +#endif + +#include +#include + +#include "mutt.h" +#include "mutt_socket.h" + +typedef struct _tlssockdata { + gnutls_session state; + gnutls_certificate_credentials xcred; +} tlssockdata; + +/* local prototypes */ +static int tls_socket_read (CONNECTION * conn, char *buf, ssize_t len); +static int tls_socket_write (CONNECTION * conn, const char *buf, ssize_t len); +static int tls_socket_open (CONNECTION * conn); +static int tls_socket_close (CONNECTION * conn); +static int tls_starttls_close (CONNECTION * conn); + +static int tls_init (void); +static int tls_negotiate (CONNECTION * conn); +static int tls_check_certificate (CONNECTION * conn); + + +static int tls_init (void) +{ + static unsigned char init_complete = 0; + int err; + + if (init_complete) + return 0; + + err = gnutls_global_init (); + if (err < 0) { + mutt_error (_("gnutls_global_init: %s"), gnutls_strerror (err)); + mutt_sleep (2); + return -1; + } + + init_complete = 1; + return 0; +} + +int mutt_ssl_socket_setup (CONNECTION * conn) +{ + if (tls_init () < 0) + return -1; + + conn->conn_open = tls_socket_open; + conn->conn_read = tls_socket_read; + conn->conn_write = tls_socket_write; + conn->conn_close = tls_socket_close; + + return 0; +} + +static int tls_socket_read (CONNECTION * conn, char *buf, ssize_t len) +{ + tlssockdata *data = conn->sockdata; + int ret; + + if (!data) { + mutt_error (_("Error: no TLS socket open")); + mutt_sleep (2); + return -1; + } + + ret = gnutls_record_recv (data->state, buf, len); + if (gnutls_error_is_fatal (ret) == 1) { + mutt_error (_("tls_socket_read (%s)"), gnutls_strerror (ret)); + mutt_sleep (4); + return -1; + } + return ret; +} + +static int tls_socket_write (CONNECTION * conn, const char *buf, ssize_t len) +{ + tlssockdata *data = conn->sockdata; + int ret; + + if (!data) { + mutt_error (_("Error: no TLS socket open")); + mutt_sleep (2); + return -1; + } + + ret = gnutls_record_send (data->state, buf, len); + if (gnutls_error_is_fatal (ret) == 1) { + mutt_error (_("tls_socket_write (%s)"), gnutls_strerror (ret)); + mutt_sleep (4); + return -1; + } + return ret; +} + +static int tls_socket_open (CONNECTION * conn) +{ + if (raw_socket_open (conn) < 0) + return -1; + + if (tls_negotiate (conn) < 0) { + tls_socket_close (conn); + return -1; + } + + return 0; +} + +int mutt_ssl_starttls (CONNECTION * conn) +{ + if (tls_init () < 0) + return -1; + + if (tls_negotiate (conn) < 0) + return -1; + + conn->conn_read = tls_socket_read; + conn->conn_write = tls_socket_write; + conn->conn_close = tls_starttls_close; + + return 0; +} + +static int protocol_priority[] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 }; + +/* tls_negotiate: After TLS state has been initialised, attempt to negotiate + * TLS over the wire, including certificate checks. */ +static int tls_negotiate (CONNECTION * conn) +{ + tlssockdata *data; + int err; + + data = p_new(tlssockdata, 1); + conn->sockdata = data; + err = gnutls_certificate_allocate_credentials (&data->xcred); + if (err < 0) { + p_delete(&conn->sockdata); + mutt_error (_("gnutls_certificate_allocate_credentials: %s"), + gnutls_strerror (err)); + mutt_sleep (2); + return -1; + } + + gnutls_certificate_set_x509_trust_file (data->xcred, SslCertFile, + GNUTLS_X509_FMT_PEM); + /* ignore errors, maybe file doesn't exist yet */ + + if (SslCACertFile) { + gnutls_certificate_set_x509_trust_file (data->xcred, SslCACertFile, + GNUTLS_X509_FMT_PEM); + } + +/* + gnutls_set_x509_client_key (data->xcred, "", ""); + gnutls_set_x509_cert_callback (data->xcred, cert_callback); +*/ + + gnutls_init (&data->state, GNUTLS_CLIENT); + + /* set socket */ + gnutls_transport_set_ptr (data->state, (gnutls_transport_ptr)(intptr_t)conn->fd); + + /* disable TLS/SSL protocols as needed */ + if (!option (OPTTLSV1) && !option (OPTSSLV3)) { + mutt_error (_("All available protocols for TLS/SSL connection disabled")); + goto fail; + } + else if (!option (OPTTLSV1)) { + protocol_priority[0] = GNUTLS_SSL3; + protocol_priority[1] = 0; + } + else if (!option (OPTSSLV3)) { + protocol_priority[0] = GNUTLS_TLS1; + protocol_priority[1] = 0; + } + /* + else + use the list set above + */ + + /* We use default priorities (see gnutls documentation), + except for protocol version */ + gnutls_set_default_priority (data->state); + gnutls_protocol_set_priority (data->state, protocol_priority); + + if (SslDHPrimeBits > 0) { + gnutls_dh_set_prime_bits (data->state, SslDHPrimeBits); + } + +/* + gnutls_set_cred (data->state, GNUTLS_ANON, NULL); +*/ + + gnutls_credentials_set (data->state, GNUTLS_CRD_CERTIFICATE, data->xcred); + + err = gnutls_handshake (data->state); + + while (err == GNUTLS_E_AGAIN || err == GNUTLS_E_INTERRUPTED) { + err = gnutls_handshake (data->state); + } + if (err < 0) { + if (err == GNUTLS_E_FATAL_ALERT_RECEIVED) { + mutt_error (_("gnutls_handshake: %s(%s)"), gnutls_strerror (err), + gnutls_alert_get_name (gnutls_alert_get (data->state))); + } + else { + mutt_error (_("gnutls_handshake: %s"), gnutls_strerror (err)); + } + mutt_sleep (2); + goto fail; + } + + if (!tls_check_certificate (conn)) + goto fail; + + /* set Security Strength Factor (SSF) for SASL */ + /* NB: gnutls_cipher_get_key_size() returns key length in bytes */ + conn->ssf = + gnutls_cipher_get_key_size (gnutls_cipher_get (data->state)) * 8; + + mutt_message (_("SSL/TLS connection using %s (%s/%s/%s)"), + gnutls_protocol_get_name (gnutls_protocol_get_version + (data->state)), + gnutls_kx_get_name (gnutls_kx_get (data->state)), + gnutls_cipher_get_name (gnutls_cipher_get (data->state)), + gnutls_mac_get_name (gnutls_mac_get (data->state))); + mutt_sleep (0); + + return 0; + +fail: + gnutls_certificate_free_credentials (data->xcred); + gnutls_deinit (data->state); + p_delete(&conn->sockdata); + return -1; +} + +static int tls_socket_close (CONNECTION * conn) +{ + tlssockdata *data = conn->sockdata; + + if (data) { + gnutls_bye (data->state, GNUTLS_SHUT_RDWR); + + gnutls_certificate_free_credentials (data->xcred); + gnutls_deinit (data->state); + p_delete(&conn->sockdata); + } + + return raw_socket_close (conn); +} + +static int tls_starttls_close (CONNECTION * conn) +{ + int rc; + + rc = tls_socket_close (conn); + conn->conn_read = raw_socket_read; + conn->conn_write = raw_socket_write; + conn->conn_close = raw_socket_close; + + return rc; +} + +#define CERT_SEP "-----BEGIN" + +/* this bit is based on read_ca_file() in gnutls */ +static int tls_compare_certificates (const gnutls_datum * peercert) +{ + gnutls_datum cert; + unsigned char *ptr; + FILE *fd1; + int ret; + gnutls_datum b64_data; + unsigned char *b64_data_data; + struct stat filestat; + + if (stat (SslCertFile, &filestat) == -1) + return 0; + + b64_data.size = filestat.st_size + 1; + b64_data_data = p_new(unsigned char, b64_data.size); + b64_data_data[b64_data.size - 1] = '\0'; + b64_data.data = b64_data_data; + + fd1 = fopen (SslCertFile, "r"); + if (fd1 == NULL) { + return 0; + } + + b64_data.size = fread (b64_data.data, 1, b64_data.size, fd1); + m_fclose(&fd1); + + do { + ret = gnutls_pem_base64_decode_alloc (NULL, &b64_data, &cert); + if (ret != 0) { + p_delete(&b64_data_data); + return 0; + } + + ptr = (unsigned char *) strstr ((char*) b64_data.data, CERT_SEP) + 1; + ptr = (unsigned char *) strstr ((char*) ptr, CERT_SEP); + + b64_data.size = b64_data.size - (ptr - b64_data.data); + b64_data.data = ptr; + + if (cert.size == peercert->size) { + if (memcmp (cert.data, peercert->data, cert.size) == 0) { + /* match found */ + gnutls_free (cert.data); + p_delete(&b64_data_data); + return 1; + } + } + + gnutls_free (cert.data); + } while (ptr != NULL); + + /* no match found */ + p_delete(&b64_data_data); + return 0; +} + +static void tls_fingerprint (gnutls_digest_algorithm algo, + char *s, int l, const gnutls_datum * data) +{ + char md[36]; + ssize_t n; + int j; + + n = 36; + + if (gnutls_fingerprint(algo, data, md, (size_t *)&n) < 0) { + snprintf (s, l, _("[unable to calculate]")); + } + else { + for (j = 0; j < (int) n; j++) { + char ch[8]; + + snprintf (ch, 8, "%02X%s", md[j], (j % 2 ? " " : "")); + strncat (s, ch, l); + } + s[2 * n + n / 2 - 1] = '\0'; /* don't want trailing space */ + } +} + +static char *tls_make_date (time_t t, char *s, ssize_t len) +{ + struct tm *l = gmtime (&t); + + if (l) + snprintf (s, len, "%s, %d %s %d %02d:%02d:%02d UTC", + Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon], + l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec); + else + m_strcpy(s, len, _("[invalid date]")); + + return (s); +} + +static int tls_check_stored_hostname (const gnutls_datum * cert, + const char *hostname) +{ + char buf[80]; + FILE *fp; + char *linestr = NULL; + ssize_t linestrsize; + int linenum = 0; + regex_t preg; + regmatch_t pmatch[3]; + + /* try checking against names stored in stored certs file */ + if ((fp = fopen (SslCertFile, "r"))) { + if (regcomp + (&preg, + "^#H ([a-zA-Z0-9_\\.-]+) ([0-9A-F]{4}( [0-9A-F]{4}){7})[ \t]*$", + REG_ICASE | REG_EXTENDED) != 0) { + regfree (&preg); + return 0; + } + + buf[0] = '\0'; + tls_fingerprint (GNUTLS_DIG_MD5, buf, sizeof (buf), cert); + while ((linestr = + mutt_read_line (linestr, &linestrsize, fp, &linenum)) != NULL) { + if (linestr[0] == '#' && linestr[1] == 'H') { + if (regexec (&preg, linestr, 3, pmatch, 0) == 0) { + linestr[pmatch[1].rm_eo] = '\0'; + linestr[pmatch[2].rm_eo] = '\0'; + if (m_strcmp(linestr + pmatch[1].rm_so, hostname) == 0 && + m_strcmp(linestr + pmatch[2].rm_so, buf) == 0) { + regfree (&preg); + p_delete(&linestr); + m_fclose(&fp); + return 1; + } + } + } + } + + regfree (&preg); + m_fclose(&fp); + } + + /* not found a matching name */ + return 0; +} + +static int tls_check_certificate (CONNECTION * conn) +{ + tlssockdata *data = conn->sockdata; + gnutls_session state = data->state; + char helpstr[STRING]; + char buf[STRING]; + char fpbuf[STRING]; + ssize_t buflen; + char dn_common_name[STRING]; + char dn_email[STRING]; + char dn_organization[STRING]; + char dn_organizational_unit[STRING]; + char dn_locality[STRING]; + char dn_province[STRING]; + char dn_country[STRING]; + MUTTMENU *menu; + int done, row, i, ret; + FILE *fp; + time_t t; + const gnutls_datum *cert_list; + unsigned int cert_list_size = 0; + gnutls_certificate_status_t certstat; + char datestr[30]; + gnutls_x509_crt cert; + gnutls_datum pemdata; + int certerr_expired = 0; + int certerr_notyetvalid = 0; + int certerr_hostname = 0; + int certerr_nottrusted = 0; + int certerr_revoked = 0; + int certerr_signernotca = 0; + + if (gnutls_auth_get_type (state) != GNUTLS_CRD_CERTIFICATE) { + mutt_error (_("Unable to get certificate from peer")); + mutt_sleep (2); + return 0; + } + + if (gnutls_certificate_verify_peers2(state, &certstat) < 0) { + mutt_error (_("Certificate verification error (%s)"), + gnutls_strerror(certstat)); + mutt_sleep (2); + return 0; + } + + /* We only support X.509 certificates (not OpenPGP) at the moment */ + if (gnutls_certificate_type_get (state) != GNUTLS_CRT_X509) { + mutt_error (_("Certificate is not X.509")); + mutt_sleep (2); + return 0; + } + + if (gnutls_x509_crt_init (&cert) < 0) { + mutt_error (_("Error initialising gnutls certificate data")); + mutt_sleep (2); + return 0; + } + + cert_list = gnutls_certificate_get_peers (state, &cert_list_size); + if (!cert_list) { + mutt_error (_("Unable to get certificate from peer")); + mutt_sleep (2); + return 0; + } + + /* FIXME: Currently only check first certificate in chain. */ + if (gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) { + mutt_error (_("Error processing certificate data")); + mutt_sleep (2); + return 0; + } + + if (gnutls_x509_crt_get_expiration_time (cert) < time (NULL)) { + certerr_expired = 1; + } + + if (gnutls_x509_crt_get_activation_time (cert) > time (NULL)) { + certerr_notyetvalid = 1; + } + + if (!gnutls_x509_crt_check_hostname (cert, conn->account.host) && + !tls_check_stored_hostname (&cert_list[0], conn->account.host)) { + certerr_hostname = 1; + } + + /* see whether certificate is in our cache (certificates file) */ + if (tls_compare_certificates (&cert_list[0])) { + if (certstat & GNUTLS_CERT_INVALID) { + /* doesn't matter - have decided is valid because server + certificate is in our trusted cache */ + certstat ^= GNUTLS_CERT_INVALID; + } + + if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) { + /* doesn't matter that we haven't found the signer, since + certificate is in our trusted cache */ + certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND; + } + + if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) { + /* Hmm. Not really sure how to handle this, but let's say + that we don't care if the CA certificate hasn't got the + correct X.509 basic constraints if server certificate is + in our cache. */ + certstat ^= GNUTLS_CERT_SIGNER_NOT_CA; + } + + } + + if (certstat & GNUTLS_CERT_REVOKED) { + certerr_revoked = 1; + certstat ^= GNUTLS_CERT_REVOKED; + } + + if (certstat & GNUTLS_CERT_INVALID) { + certerr_nottrusted = 1; + certstat ^= GNUTLS_CERT_INVALID; + } + + if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) { + /* NB: already cleared if cert in cache */ + certerr_nottrusted = 1; + certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND; + } + + if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) { + /* NB: already cleared if cert in cache */ + certerr_signernotca = 1; + certstat ^= GNUTLS_CERT_SIGNER_NOT_CA; + } + + /* OK if signed by (or is) a trusted certificate */ + /* we've been zeroing the interesting bits in certstat - + don't return OK if there are any unhandled bits we don't + understand */ + if (!(certerr_expired || certerr_notyetvalid || + certerr_hostname || certerr_nottrusted) && certstat == 0) { + gnutls_x509_crt_deinit (cert); + return 1; + } + + + /* interactive check from user */ + menu = mutt_new_menu (); + menu->max = 25; + menu->dialog = p_new(char*, menu->max); + for (i = 0; i < menu->max; i++) + menu->dialog[i] = p_new(char, STRING); + + row = 0; + m_strcpy(menu->dialog[row], STRING, + _("This certificate belongs to:")); + row++; + + buflen = sizeof (dn_common_name); + if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, + dn_common_name, (size_t *)&buflen) != 0) + dn_common_name[0] = '\0'; + buflen = sizeof (dn_email); + if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0, + dn_email, (size_t *)&buflen) != 0) + dn_email[0] = '\0'; + buflen = sizeof (dn_organization); + if (gnutls_x509_crt_get_dn_by_oid + (cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, dn_organization, + (size_t *)&buflen) != 0) + dn_organization[0] = '\0'; + buflen = sizeof (dn_organizational_unit); + if (gnutls_x509_crt_get_dn_by_oid + (cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0, + dn_organizational_unit, (size_t *)&buflen) != 0) + dn_organizational_unit[0] = '\0'; + buflen = sizeof (dn_locality); + if (gnutls_x509_crt_get_dn_by_oid + (cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, dn_locality, (size_t *)&buflen) != 0) + dn_locality[0] = '\0'; + buflen = sizeof (dn_province); + if (gnutls_x509_crt_get_dn_by_oid + (cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, dn_province, + (size_t *)&buflen) != 0) + dn_province[0] = '\0'; + buflen = sizeof (dn_country); + if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0, + dn_country, (size_t *)&buflen) != 0) + dn_country[0] = '\0'; + + snprintf (menu->dialog[row++], STRING, " %s %s", dn_common_name, + dn_email); + snprintf (menu->dialog[row++], STRING, " %s", dn_organization); + snprintf (menu->dialog[row++], STRING, " %s", + dn_organizational_unit); + snprintf (menu->dialog[row++], STRING, " %s %s %s", dn_locality, + dn_province, dn_country); + row++; + + m_strcpy(menu->dialog[row], STRING, + _("This certificate was issued by:")); + row++; + + buflen = sizeof (dn_common_name); + if (gnutls_x509_crt_get_issuer_dn_by_oid + (cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, dn_common_name, (size_t *)&buflen) != 0) + dn_common_name[0] = '\0'; + buflen = sizeof (dn_email); + if (gnutls_x509_crt_get_issuer_dn_by_oid + (cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0, dn_email, (size_t *)&buflen) != 0) + dn_email[0] = '\0'; + buflen = sizeof (dn_organization); + if (gnutls_x509_crt_get_issuer_dn_by_oid + (cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, dn_organization, + (size_t *)&buflen) != 0) + dn_organization[0] = '\0'; + buflen = sizeof (dn_organizational_unit); + if (gnutls_x509_crt_get_issuer_dn_by_oid + (cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0, + dn_organizational_unit, (size_t *)&buflen) != 0) + dn_organizational_unit[0] = '\0'; + buflen = sizeof (dn_locality); + if (gnutls_x509_crt_get_issuer_dn_by_oid + (cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, dn_locality, (size_t *)&buflen) != 0) + dn_locality[0] = '\0'; + buflen = sizeof (dn_province); + if (gnutls_x509_crt_get_issuer_dn_by_oid + (cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, dn_province, + (size_t *)&buflen) != 0) + dn_province[0] = '\0'; + buflen = sizeof (dn_country); + if (gnutls_x509_crt_get_issuer_dn_by_oid + (cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0, dn_country, (size_t *)&buflen) != 0) + dn_country[0] = '\0'; + + snprintf (menu->dialog[row++], STRING, " %s %s", dn_common_name, + dn_email); + snprintf (menu->dialog[row++], STRING, " %s", dn_organization); + snprintf (menu->dialog[row++], STRING, " %s", + dn_organizational_unit); + snprintf (menu->dialog[row++], STRING, " %s %s %s", dn_locality, + dn_province, dn_country); + row++; + + snprintf (menu->dialog[row++], STRING, + _("This certificate is valid")); + + t = gnutls_x509_crt_get_activation_time (cert); + snprintf (menu->dialog[row++], STRING, _(" from %s"), + tls_make_date (t, datestr, 30)); + + t = gnutls_x509_crt_get_expiration_time (cert); + snprintf (menu->dialog[row++], STRING, _(" to %s"), + tls_make_date (t, datestr, 30)); + + fpbuf[0] = '\0'; + tls_fingerprint (GNUTLS_DIG_SHA, fpbuf, sizeof (fpbuf), &cert_list[0]); + snprintf (menu->dialog[row++], STRING, _("SHA1 Fingerprint: %s"), + fpbuf); + fpbuf[0] = '\0'; + tls_fingerprint (GNUTLS_DIG_MD5, fpbuf, sizeof (fpbuf), &cert_list[0]); + snprintf (menu->dialog[row++], STRING, _("MD5 Fingerprint: %s"), + fpbuf); + + if (certerr_notyetvalid) { + row++; + m_strcpy(menu->dialog[row], STRING, + _("WARNING: Server certificate is not yet valid")); + } + if (certerr_expired) { + row++; + m_strcpy(menu->dialog[row], STRING, + _("WARNING: Server certificate has expired")); + } + if (certerr_revoked) { + row++; + m_strcpy(menu->dialog[row], STRING, + _("WARNING: Server certificate has been revoked")); + } + if (certerr_hostname) { + row++; + m_strcpy(menu->dialog[row], STRING, + _("WARNING: Server hostname does not match certificate")); + } + if (certerr_signernotca) { + row++; + m_strcpy(menu->dialog[row], STRING, + _("WARNING: Signer of server certificate is not a CA")); + } + + menu->title = _("TLS/SSL Certificate check"); + /* certificates with bad dates, or that are revoked, must be + accepted manually each and every time */ + if (SslCertFile && !certerr_expired && !certerr_notyetvalid + && !certerr_revoked) { + menu->prompt = _("(r)eject, accept (o)nce, (a)ccept always"); + menu->keys = _("roa"); + } + else { + menu->prompt = _("(r)eject, accept (o)nce"); + menu->keys = _("ro"); + } + + helpstr[0] = '\0'; + mutt_make_help (buf, sizeof (buf), _("Exit "), MENU_GENERIC, OP_EXIT); + strncat (helpstr, buf, sizeof (helpstr)); + mutt_make_help (buf, sizeof (buf), _("Help"), MENU_GENERIC, OP_HELP); + strncat (helpstr, buf, sizeof (helpstr)); + menu->help = helpstr; + + done = 0; + set_option (OPTUNBUFFEREDINPUT); + while (!done) { + switch (mutt_menuLoop (menu)) { + case -1: /* abort */ + case OP_MAX + 1: /* reject */ + case OP_EXIT: + done = 1; + break; + case OP_MAX + 3: /* accept always */ + done = 0; + if ((fp = fopen (SslCertFile, "a"))) { + /* save hostname if necessary */ + if (certerr_hostname) { + fprintf (fp, "#H %s %s\n", conn->account.host, fpbuf); + done = 1; + } + if (certerr_nottrusted) { + done = 0; + ret = gnutls_pem_base64_encode_alloc ("CERTIFICATE", &cert_list[0], + &pemdata); + if (ret == 0) { + if (fwrite (pemdata.data, pemdata.size, 1, fp) == 1) { + done = 1; + } + gnutls_free (pemdata.data); + } + } + m_fclose(&fp); + } + if (!done) { + mutt_error (_("Warning: Couldn't save certificate")); + mutt_sleep (2); + } + else { + mutt_message (_("Certificate saved")); + mutt_sleep (0); + } + /* fall through */ + case OP_MAX + 2: /* accept once */ + done = 2; + break; + } + } + unset_option (OPTUNBUFFEREDINPUT); + mutt_menuDestroy (&menu); + gnutls_x509_crt_deinit (cert); + return (done == 2); +}