On Jan 26, Thomas Roessler <roessler@xxxxxxxxxxxxxxxxxx> wrote: > If there are any patches you want to see included with 1.5.7, please > re-submit them to mutt-dev by Thursday evening. 1.5.7 due on Friday. Here I'm attaching two patches. The first adds support for gnutls. I have been using it for the debian package since november 2001 and it has been solid for a long time. Please, please please apply it, because it's the only way for a distribution to support SSL, due to the well known OpenSSL/GPL licensing incompatibility. Also, gnutls has a much nicer interface... :-) The other patch is a subset of the ja patch, it adds the the config options assumed_charset, file_charset and strict_mime. I can't see how people using mutt in the real world and having to deal with Outlook users can live without it. -- ciao, Marco
# vi: ft=diff This is the gnutls patch by Andrew McDonald <andrew@xxxxxxxxxxxxxxx>. The home page for this patch is: http://www.mcdonald.org.uk/andrew/mutt-gnutls/ * Patch last synced with upstream: - Date: unknown (mutt_1.5.6-1) - File: http://www.mcdonald.org.uk/andrew/mutt-gnutls/002_patch-1.5.5.1.admcd.gnutls.59 * Changes made: - 2004-07-22: updated globals.h diff to get it to apply cleanly to current CVS. - 2004-09-16: updated globals.h and mutt_sasl.c to get it to apply cleanly to current CVS. == END PATCH diff -Nru mutt-1.5.5.1.orig/PATCHES mutt-1.5.5.1/PATCHES --- mutt-1.5.5.1.orig/PATCHES 2004-04-04 21:07:55.000000000 +0100 +++ mutt-1.5.5.1/PATCHES 2004-04-04 21:08:34.000000000 +0100 @@ -1,0 +1 @@ +patch-1.4.admcd.gnutls.59d diff -Nru mutt-1.5.5.1.orig/globals.h mutt-1.5.5.1/globals.h --- mutt-1.5.5.1.orig/globals.h 2003-11-05 09:41:31.000000000 +0000 +++ mutt-1.5.5.1/globals.h 2004-04-04 21:08:34.000000000 +0100 @@ -102,12 +102,18 @@ WHERE char *Signature; WHERE char *SimpleSearch; WHERE char *Spoolfile; WHERE char *SpamSep; -#if defined(USE_SSL) || defined(USE_NSS) +#if defined(USE_SSL) || defined(USE_NSS) || defined(USE_GNUTLS) WHERE char *SslCertFile INITVAL (NULL); +#endif +#if defined(USE_SSL) || defined(USE_NSS) WHERE char *SslEntropyFile INITVAL (NULL); WHERE char *SslClientCert INITVAL (NULL); #endif +#ifdef USE_GNUTLS +WHERE short SslDHPrimeBits; +WHERE char *SslCACertFile INITVAL (NULL); +#endif WHERE char *StChars; WHERE char *Status; WHERE char *Tempdir; diff -Nru mutt-1.5.5.1.orig/imap/imap.c mutt-1.5.5.1/imap/imap.c --- mutt-1.5.5.1.orig/imap/imap.c 2004-04-04 21:07:54.000000000 +0100 +++ mutt-1.5.5.1/imap/imap.c 2004-04-04 21:08:34.000000000 +0100 @@ -29,7 +29,7 @@ #include "browser.h" #include "message.h" #include "imap_private.h" -#ifdef USE_SSL +#if defined(USE_SSL) || defined(USE_GNUTLS) # include "mutt_ssl.h" #endif @@ -408,7 +408,7 @@ /* TODO: Parse new tagged CAPABILITY data (* OK [CAPABILITY...]) */ if (imap_check_capabilities (idata)) goto bail; -#if defined(USE_SSL) && !defined(USE_NSS) +#if defined(USE_SSL) || defined(USE_GNUTLS) /* Attempt STARTTLS if available and desired. */ if (mutt_bit_isset (idata->capabilities, STARTTLS) && !idata->conn->ssf) { @@ -422,7 +422,11 @@ goto bail; if (rc != -2) { +#ifdef USE_SSL if (mutt_ssl_starttls (idata->conn)) +#elif USE_GNUTLS + if (mutt_gnutls_starttls (idata->conn)) +#endif { mutt_error (_("Could not negotiate TLS connection")); mutt_sleep (1); diff -Nru mutt-1.5.5.1.orig/imap/util.c mutt-1.5.5.1/imap/util.c --- mutt-1.5.5.1.orig/imap/util.c 2003-11-05 09:41:36.000000000 +0000 +++ mutt-1.5.5.1/imap/util.c 2004-04-04 21:08:34.000000000 +0100 @@ -164,7 +164,7 @@ } } -#ifdef USE_SSL +#if defined(USE_SSL) || defined(USE_GNUTLS) if (option (OPTIMAPFORCESSL)) mx->account.flags |= M_ACCT_SSL; #endif diff -Nru mutt-1.5.5.1.orig/init.c mutt-1.5.5.1/init.c --- mutt-1.5.5.1.orig/init.c 2003-11-05 09:41:31.000000000 +0000 +++ mutt-1.5.5.1/init.c 2004-04-04 21:08:34.000000000 +0100 @@ -27,7 +27,7 @@ #include "mutt_crypt.h" #include "mutt_idna.h" -#if defined(USE_SSL) || defined(USE_NSS) +#if defined(USE_SSL) || defined(USE_NSS) || defined(USE_GNUTLS) #include "mutt_ssl.h" #endif diff -Nru mutt-1.5.5.1.orig/init.h mutt-1.5.5.1/init.h --- mutt-1.5.5.1.orig/init.h 2004-04-04 21:07:55.000000000 +0100 +++ mutt-1.5.5.1/init.h 2004-04-04 21:08:34.000000000 +0100 @@ -782,7 +782,7 @@ ** as folder separators for displaying IMAP paths. In particular it ** helps in using the '=' shortcut for your \fIfolder\fP variable. */ -# ifdef USE_SSL +# if defined(USE_SSL) || defined(USE_GNUTLS) { "imap_force_ssl", DT_BOOL, R_NONE, OPTIMAPFORCESSL, 0 }, /* ** .pp @@ -1737,8 +1737,8 @@ ** (S/MIME only) */ -#if defined(USE_SSL)||defined(USE_NSS) -# ifndef USE_NSS +#if defined(USE_SSL)||defined(USE_NSS)||defined(USE_GNUTLS) +# if defined(USE_SSL)||defined(USE_GNUTLS) { "ssl_starttls", DT_QUAD, R_NONE, OPT_SSLSTARTTLS, M_YES }, /* ** .pp @@ -1762,6 +1762,7 @@ ** .pp ** Example: set certificate_file=~/.mutt/certificates */ +# ifndef USE_GNUTLS { "ssl_usesystemcerts", DT_BOOL, R_NONE, OPTSSLSYSTEMCERTS, 1 }, /* ** .pp @@ -1781,6 +1782,7 @@ ** This variables specifies whether to attempt to use SSLv2 in the ** SSL authentication process. */ +# endif { "ssl_use_sslv3", DT_BOOL, R_NONE, OPTSSLV3, 1 }, /* ** .pp @@ -1793,6 +1795,24 @@ ** This variables specifies whether to attempt to use TLSv1 in the ** SSL authentication process. */ +#ifdef USE_GNUTLS + { "ssl_min_dh_prime_bits", DT_NUM, R_NONE, UL &SslDHPrimeBits, 0 }, + /* + ** .pp + ** This variable specifies the minimum acceptable prime size (in bits) + ** for use in any Diffie-Hellman key exchange. A value of 0 will use + ** the default from the GNUTLS library. + */ + { "ssl_ca_certificates_file", DT_PATH, R_NONE, UL &SslCACertFile, 0 }, + /* + ** .pp + ** This variable specifies a file containing trusted CA certificates. + ** Any server certificate that is signed with one of these CA + ** certificates are also automatically accepted. + ** .pp + ** Example: set ssl_ca_certificates_file=/etc/ssl/certs/ca-certificates.crt + */ +#endif #endif { "pipe_split", DT_BOOL, R_NONE, OPTPIPESPLIT, 0 }, diff -Nru mutt-1.5.5.1.orig/main.c mutt-1.5.5.1/main.c --- mutt-1.5.5.1.orig/main.c 2004-04-04 21:07:55.000000000 +0100 +++ mutt-1.5.5.1/main.c 2004-04-04 21:08:34.000000000 +0100 @@ -241,6 +241,12 @@ "-USE_SSL " #endif +#ifdef USE_GNUTLS + "+USE_GNUTLS " +#else + "-USE_GNUTLS " +#endif + #ifdef USE_SASL "+USE_SASL " #else diff -Nru mutt-1.5.5.1.orig/mutt.h mutt-1.5.5.1/mutt.h --- mutt-1.5.5.1.orig/mutt.h 2004-04-04 21:07:55.000000000 +0100 +++ mutt-1.5.5.1/mutt.h 2004-04-04 21:08:34.000000000 +0100 @@ -289,7 +289,7 @@ OPT_QUIT, OPT_REPLYTO, OPT_RECALL, -#ifdef USE_SSL +#if defined(USE_SSL) || defined(USE_GNUTLS) OPT_SSLSTARTTLS, #endif OPT_SUBJECT, @@ -362,15 +362,19 @@ OPTIMAPPASSIVE, OPTIMAPPEEK, OPTIMAPSERVERNOISE, -# ifdef USE_SSL +# if defined(USE_SSL) || defined(USE_GNUTLS) OPTIMAPFORCESSL, # endif #endif -#if defined(USE_SSL) || defined(USE_NSS) +#if defined(USE_SSL) || defined(USE_NSS) || defined(USE_GNUTLS) +# ifndef USE_GNUTLS OPTSSLV2, +# endif OPTSSLV3, OPTTLSV1, +# ifndef USE_GNUTLS OPTSSLSYSTEMCERTS, +# endif #endif OPTIMPLICITAUTOVIEW, OPTKEEPFLAGGED, diff -Nru mutt-1.5.5.1.orig/mutt_sasl.c mutt-1.5.5.1/mutt_sasl.c --- mutt-1.5.5.1.orig/mutt_sasl.c 2004-04-04 21:07:54.000000000 +0100 +++ mutt-1.5.5.1/mutt_sasl.c 2004-04-04 21:08:34.000000000 +0100 @@ -293,7 +293,7 @@ * If someone does it'd probably be trivial to write mutt_nss_get_ssf(). * I have a feeling more SSL code could be shared between those two files, * but I haven't looked into it yet, since I still don't know the APIs. */ -#if defined(USE_SSL) +#if defined(USE_SSL) || defined(USE_GNUTLS) if (conn->ssf) { #ifdef USE_SASL2 /* I'm not sure this actually has an effect, at least with SASLv2 */ diff -Nru mutt-1.5.5.1.orig/mutt_socket.c mutt-1.5.5.1/mutt_socket.c --- mutt-1.5.5.1.orig/mutt_socket.c 2004-04-04 21:07:54.000000000 +0100 +++ mutt-1.5.5.1/mutt_socket.c 2004-04-04 21:08:34.000000000 +0100 @@ -22,7 +22,7 @@ #include "globals.h" #include "mutt_socket.h" #include "mutt_tunnel.h" -#ifdef USE_SSL +#if defined(USE_SSL) || defined(USE_GNUTLS) || defined(USE_NSS) # include "mutt_ssl.h" #endif @@ -257,6 +257,12 @@ ssl_socket_setup (conn); #elif USE_NSS mutt_nss_socket_setup (conn); +#elif USE_GNUTLS + if (mutt_gnutls_socket_setup (conn) < 0) + { + mutt_socket_free (conn); + return NULL; + } #else mutt_error _("SSL is unavailable."); mutt_sleep (2); diff -Nru mutt-1.5.5.1.orig/mutt_ssl.h mutt-1.5.5.1/mutt_ssl.h --- mutt-1.5.5.1.orig/mutt_ssl.h 2002-01-24 12:10:50.000000000 +0000 +++ mutt-1.5.5.1/mutt_ssl.h 2004-04-04 21:08:34.000000000 +0100 @@ -21,8 +21,17 @@ #include "mutt_socket.h" +#ifdef USE_SSL int mutt_ssl_starttls (CONNECTION* conn); extern int ssl_socket_setup (CONNECTION *conn); +#endif +#ifdef USE_GNUTLS +int mutt_gnutls_starttls (CONNECTION* conn); +extern int mutt_gnutls_socket_setup (CONNECTION *conn); +#endif +#ifdef USE_NSS +int mutt_nss_socket_setup (CONNECTION* conn); +#endif #endif /* _MUTT_SSL_H_ */ diff -Nru mutt-1.5.5.1.orig/mutt_ssl_gnutls.c mutt-1.5.5.1/mutt_ssl_gnutls.c --- mutt-1.5.5.1.orig/mutt_ssl_gnutls.c 1970-01-01 01:00:00.000000000 +0100 +++ mutt-1.5.5.1/mutt_ssl_gnutls.c 2004-04-04 21:08:41.000000000 +0100 @@ -0,0 +1,825 @@ +/* Copyright (C) 2001 Marco d'Itri <md@xxxxxxxx> + * Copyright (C) 2001-2004 Andrew McDonald <andrew@xxxxxxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> + +#include "mutt.h" +#include "mutt_socket.h" +#include "mutt_curses.h" +#include "mutt_menu.h" +#include "mutt_ssl.h" +#include "mutt_regex.h" + +typedef struct _tlssockdata +{ + gnutls_session state; + gnutls_certificate_credentials xcred; +} +tlssockdata; + +/* local prototypes */ +static int tls_socket_read (CONNECTION* conn, char* buf, size_t len); +static int tls_socket_write (CONNECTION* conn, const char* buf, size_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_gnutls_socket_setup (CONNECTION* conn) +{ + if (tls_init() < 0) + return -1; + + conn->open = tls_socket_open; + conn->read = tls_socket_read; + conn->write = tls_socket_write; + conn->close = tls_socket_close; + + return 0; +} + +static int tls_socket_read (CONNECTION* conn, char* buf, size_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, size_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_gnutls_starttls (CONNECTION* conn) +{ + if (tls_init() < 0) + return -1; + + if (tls_negotiate (conn) < 0) + return -1; + + conn->read = tls_socket_read; + conn->write = tls_socket_write; + 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 = (tlssockdata *) safe_calloc (1, sizeof (tlssockdata)); + conn->sockdata = data; + err = gnutls_certificate_allocate_credentials (&data->xcred); + if (err < 0) + { + FREE(&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)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); + FREE(&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); + safe_free ((void **) &conn->sockdata); + } + + return raw_socket_close (conn); +} + +static int tls_starttls_close (CONNECTION* conn) +{ + int rc; + + rc = tls_socket_close (conn); + conn->read = raw_socket_read; + conn->write = raw_socket_write; + 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 = (unsigned char *) safe_calloc (1, 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); + fclose(fd1); + + do { + ret = gnutls_pem_base64_decode_alloc(NULL, &b64_data, &cert); + if (ret != 0) + { + FREE (&b64_data_data); + return 0; + } + + ptr = (unsigned char *)strstr(b64_data.data, CERT_SEP) + 1; + ptr = (unsigned char *)strstr(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); + FREE (&b64_data_data); + return 1; + } + } + + gnutls_free(cert.data); + } while (ptr != NULL); + + /* no match found */ + FREE (&b64_data_data); + return 0; +} + +static void tls_fingerprint (gnutls_digest_algorithm algo, + char* s, int l, const gnutls_datum* data) +{ + unsigned char md[36]; + size_t n; + int j; + + n = 36; + + if (gnutls_fingerprint (algo, data, (char *)md, &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, size_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 + strfcpy (s, _("[invalid date]"), len); + + return (s); +} + +static int tls_check_stored_hostname (const gnutls_datum *cert, + const char *hostname) +{ + char buf[80]; + FILE *fp; + char *linestr = NULL; + size_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 (strcmp(linestr + pmatch[1].rm_so, hostname) == 0 && + strcmp(linestr + pmatch[2].rm_so, buf) == 0) + { + regfree(&preg); + safe_free((void**)&linestr); + fclose(fp); + return 1; + } + } + } + } + + regfree(&preg); + 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[SHORT_STRING]; + char buf[SHORT_STRING]; + char fpbuf[SHORT_STRING]; + int buflen; + char dn_common_name[SHORT_STRING]; + char dn_email[SHORT_STRING]; + char dn_organization[SHORT_STRING]; + char dn_organizational_unit[SHORT_STRING]; + char dn_locality[SHORT_STRING]; + char dn_province[SHORT_STRING]; + char dn_country[SHORT_STRING]; + MUTTMENU *menu; + int done, row, i, ret; + FILE *fp; + gnutls_x509_dn dn; + time_t t; + const gnutls_datum *cert_list; + int cert_list_size = 0; + gnutls_certificate_status 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; + } + + certstat = gnutls_certificate_verify_peers(state); + + if (certstat == GNUTLS_E_NO_CERTIFICATE_FOUND) + { + mutt_error (_("Unable to get certificate from peer")); + mutt_sleep (2); + return 0; + } + if (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 (_("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 = (char **) safe_calloc (1, menu->max * sizeof (char *)); + for (i = 0; i < menu->max; i++) + menu->dialog[i] = (char *) safe_calloc (1, SHORT_STRING * sizeof (char)); + + row = 0; + strfcpy (menu->dialog[row], _("This certificate belongs to:"), SHORT_STRING); + 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, &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, &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, &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, &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, &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, &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, &buflen) != 0) + dn_country[0] = '\0'; + + snprintf (menu->dialog[row++], SHORT_STRING, " %s %s", dn_common_name, dn_email); + snprintf (menu->dialog[row++], SHORT_STRING, " %s", dn_organization); + snprintf (menu->dialog[row++], SHORT_STRING, " %s", dn_organizational_unit); + snprintf (menu->dialog[row++], SHORT_STRING, " %s %s %s", + dn_locality, dn_province, dn_country); + row++; + + strfcpy (menu->dialog[row], _("This certificate was issued by:"), SHORT_STRING); + 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, &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, &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, &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, &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, &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, &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, &buflen) != 0) + dn_country[0] = '\0'; + + snprintf (menu->dialog[row++], SHORT_STRING, " %s %s", dn_common_name, dn_email); + snprintf (menu->dialog[row++], SHORT_STRING, " %s", dn_organization); + snprintf (menu->dialog[row++], SHORT_STRING, " %s", dn_organizational_unit); + snprintf (menu->dialog[row++], SHORT_STRING, " %s %s %s", + dn_locality, dn_province, dn_country); + row++; + + snprintf (menu->dialog[row++], SHORT_STRING, _("This certificate is valid")); + + t = gnutls_x509_crt_get_activation_time(cert); + snprintf (menu->dialog[row++], SHORT_STRING, _(" from %s"), + tls_make_date(t, datestr, 30)); + + t = gnutls_x509_crt_get_expiration_time(cert); + snprintf (menu->dialog[row++], SHORT_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++], SHORT_STRING, _("SHA1 Fingerprint: %s"), fpbuf); + fpbuf[0] = '\0'; + tls_fingerprint (GNUTLS_DIG_MD5, fpbuf, sizeof (fpbuf), &cert_list[0]); + snprintf (menu->dialog[row++], SHORT_STRING, _("MD5 Fingerprint: %s"), fpbuf); + + if (certerr_notyetvalid) + { + row++; + strfcpy (menu->dialog[row], _("WARNING: Server certificate is not yet valid"), SHORT_STRING); + } + if (certerr_expired) + { + row++; + strfcpy (menu->dialog[row], _("WARNING: Server certificate has expired"), SHORT_STRING); + } + if (certerr_revoked) + { + row++; + strfcpy (menu->dialog[row], _("WARNING: Server certificate has been revoked"), SHORT_STRING); + } + if (certerr_hostname) + { + row++; + strfcpy (menu->dialog[row], _("WARNING: Server hostname does not match certificate"), SHORT_STRING); + } + if (certerr_signernotca) + { + row++; + strfcpy (menu->dialog[row], _("WARNING: Signer of server certificate is not a CA"), SHORT_STRING); + } + + 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; + 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); + } + } + 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; + } + } + mutt_menuDestroy (&menu); + gnutls_x509_crt_deinit(cert); + return (done == 2); +}
# vi: ft=diff This patch by Alexander Neumann <alexander@xxxxxxxxxx> adds POP TLS support to the gnutls patch. It was received at Debian's BTS, Bug#260638. * Patch last synced with upstream: - Date: 2004-08-05 - File: http://bugs.debian.org/cgi-bin/bugreport.cgi/mutt-gnutls-pop.patch?bug=260638&msg=3&att=1 * Changes made: - 2004-08-05: removed the correct-typo-in-comment part. == END PATCH --- mutt/pop_lib.c.orig 2004-07-21 15:18:49.000000000 +0200 +++ mutt/pop_lib.c 2004-07-21 15:58:58.000000000 +0200 @@ -270,7 +270,7 @@ return -2; } -#if defined(USE_SSL) && !defined(USE_NSS) +#if (defined(USE_SSL) || defined(USE_GNUTLS)) && !defined(USE_NSS) /* Attempt STLS if available and desired. */ if (pop_data->cmd_stls && !pop_data->conn->ssf) { @@ -295,7 +295,11 @@ mutt_error ("%s", pop_data->err_msg); mutt_sleep (2); } +#ifdef USE_SSL else if (mutt_ssl_starttls (pop_data->conn)) +#elif USE_GNUTLS + else if (mutt_gnutls_starttls (pop_data->conn)) +#endif { mutt_error (_("Could not negotiate TLS connection")); mutt_sleep (2);
# vi: ft=diff This is the gnutlsbuild patch by Andrew McDonald <andrew@xxxxxxxxxxxxxxx>. The home page for this patch is: http://www.mcdonald.org.uk/andrew/mutt-gnutls/ * Patch last synced with upstream: - Date: unknown. - File: http://www.mcdonald.org.uk/andrew/mutt-gnutls/012_patch-1.4.admcd.gnutlsbuild.53 * Changes made: - leave only the init.h diff. == END PATCH diff -ruN mutt-1.5.5.1.orig/init.h mutt-1.5.5.1/init.h --- mutt-1.5.5.1.orig/init.h 2003-12-22 02:01:14.000000000 +0100 +++ mutt-1.5.5.1/init.h 2003-12-22 01:49:37.000000000 +0100 @@ -1746,7 +1746,7 @@ ** use STARTTLS regardless of the server's capabilities. */ # endif - { "certificate_file", DT_PATH, R_NONE, UL &SslCertFile, 0 }, + { "certificate_file", DT_PATH, R_NONE, UL &SslCertFile, UL "~/.mutt_certificates" }, /* ** .pp ** This variable specifies the file where the certificates you trust
# vi: ft=diff This is the compat patch by Takashi TAKIZAWA <taki@xxxxxxxxxxxxxxxxx>. The home page for this patch is: http://www.emaillab.org/mutt/download15.html.en * Patch last synced with upstream: - Date: 2004-07-22 - File: http://www.emaillab.org/mutt/1.5/patch-1.5.6.tt.assumed_charset.1.gz - File: http://www.emaillab.org/mutt/1.5/patch-1.5.6.tt.adjust_edited_file.1.gz - File: http://www.emaillab.org/mutt/1.5/patch-1.5.6.tt.adjust_edited_file.1.gz - File: http://www.emaillab.org/mutt/1.5/patch-1.5.6.tt.adjust_line.3.gz * Changes made: - 2004-07-22: merge all PATCHES entries into a single one. == END PATCH diff -uNr mutt-1.5.6.orig/PATCHES mutt-1.5.6/PATCHES --- mutt-1.5.6.orig/PATCHES Mon Feb 2 02:42:47 2004 +++ mutt-1.5.6/PATCHES Sun Feb 15 18:12:21 2004 @@ -0,0 +1 @@ +patch-1.5.6.tt.compat.1 diff -uNr mutt-1.5.6.orig/curs_lib.c mutt-1.5.6/curs_lib.c --- mutt-1.5.6.orig/curs_lib.c Wed Nov 5 18:41:31 2003 +++ mutt-1.5.6/curs_lib.c Sun Feb 15 18:12:11 2004 @@ -43,6 +43,19 @@ static size_t UngetBufLen = 0; static event_t *KeyEvent; +static void adjust_edited_file (const char *data) +{ + FILE *fp; + int c; + + if ((fp = safe_fopen (data, "a+")) == NULL) + return; + fseek (fp,-1,SEEK_END); + if ((c = fgetc(fp)) != '\n') + fputc ('\n', fp); + safe_fclose (&fp); +} + void mutt_refresh (void) { /* don't refresh when we are waiting for a child. */ @@ -147,6 +160,7 @@ mutt_expand_file_fmt (cmd, sizeof (cmd), editor, data); if (mutt_system (cmd) == -1) mutt_error (_("Error running \"%s\"!"), cmd); + adjust_edited_file (data); keypad (stdscr, TRUE); clearok (stdscr, TRUE); } diff -ur mutt-1.5.6.orig/curs_lib.c mutt-1.5.6/curs_lib.c --- mutt-1.5.6.orig/curs_lib.c Wed Nov 5 18:41:31 2003 +++ mutt-1.5.6/curs_lib.c Tue May 25 23:37:27 2004 @@ -575,7 +575,7 @@ wc = replacement_char (); } if (arboreal && wc < M_TREE_MAX) - w = 1; /* hack */ + w = mutt_mbswidth ((char *)s,1); else { if (!IsWPrint (wc)) diff -ur mutt-1.5.6.orig/muttlib.c mutt-1.5.6/muttlib.c --- mutt-1.5.6.orig/muttlib.c Mon Feb 2 02:15:17 2004 +++ mutt-1.5.6/muttlib.c Tue May 25 23:47:27 2004 @@ -23,6 +23,7 @@ #include "mailbox.h" #include "mx.h" #include "url.h" +#include "mbyte.h" #include "reldate.h" @@ -913,6 +914,43 @@ } +/* return col of multibyte character string */ +size_t mutt_mbswidth (char *s, size_t n) +{ + wchar_t wc; + size_t k,l,m; + mbstate_t mbstate; + unsigned int i; + + l = 0; + if (!s) + return 0; + memset (&mbstate, 0, sizeof (mbstate)); + while (*s && n) + { + i = (unsigned int)*s; + if (i < M_TREE_MAX) + { + if (Charset_is_utf8) + l += 1; /* FIXME: width of WACS character is not always 1 */ + else + l++; + s++, n--; + } + else + { + k = mbrtowc (&wc, s, n, &mbstate); + if (k == (size_t)(-1) || k == (size_t)(-2)) + break; + m = wcwidth (wc); + l += (m > 0) ? m : 0; + s += k, n -= k; + } + } + return l; +} + + void mutt_FormatString (char *dest, /* output buffer */ size_t destlen, /* output buffer len */ const char *src, /* template string */ @@ -922,11 +960,12 @@ { char prefix[SHORT_STRING], buf[LONG_STRING], *cp, *wptr = dest, ch; char ifstring[SHORT_STRING], elsestring[SHORT_STRING]; - size_t wlen, count, len; + size_t wlen, count, len, col, wid; prefix[0] = '\0'; destlen--; /* save room for the terminal \0 */ wlen = (flags & M_FORMAT_ARROWCURSOR && option (OPTARROWCURSOR)) ? 3 : 0; + col = wlen; while (*src && wlen < destlen) { @@ -936,6 +975,7 @@ { *wptr++ = '%'; wlen++; + col++; src++; continue; } @@ -1008,23 +1048,26 @@ /* calculate space left on line. if we've already written more data than will fit on the line, ignore the rest of the line */ count = (COLS < destlen ? COLS : destlen); - if (count > wlen) + if (count > col) { - count -= wlen; /* how many chars left on this line */ + count -= col; /* how many columns left on this line */ mutt_FormatString (buf, sizeof (buf), src, callback, data, flags); len = mutt_strlen (buf); - if (count > len) + wid = mutt_mbswidth (buf, len); + if (count > wid) { - count -= len; /* how many chars to pad */ + count -= wid; /* how many chars to pad */ memset (wptr, ch, count); wptr += count; wlen += count; + col += count; } if (len + wlen > destlen) len = destlen - wlen; memcpy (wptr, buf, len); wptr += len; wlen += len; + col += mutt_mbswidth (buf, len); } break; /* skip rest of input */ } @@ -1076,6 +1119,7 @@ memcpy (wptr, buf, len); wptr += len; wlen += len; + col += mutt_mbswidth (buf, len); } } else if (*src == '\\') @@ -1106,11 +1150,13 @@ src++; wptr++; wlen++; + col++; } else { *wptr++ = *src++; wlen++; + col++; } } *wptr = 0; diff -ur mutt-1.5.6.orig/pager.c mutt-1.5.6/pager.c --- mutt-1.5.6.orig/pager.c Mon Feb 2 02:10:57 2004 +++ mutt-1.5.6/pager.c Tue May 25 23:37:27 2004 @@ -1706,15 +1706,17 @@ CLEARLINE (statusoffset); if (IsHeader (extra)) { - _mutt_make_string (buffer, - COLS-9 < sizeof (buffer) ? COLS-9 : sizeof (buffer), - NONULL (PagerFmt), Context, extra->hdr, M_FORMAT_MAKEPRINT); + size_t l1 = (COLS - 9) * MB_LEN_MAX; + size_t l2 = sizeof (buffer); + _mutt_make_string (buffer, l1 < l2 ? l1 : l2, NONULL (PagerFmt), + Context, extra->hdr, M_FORMAT_MAKEPRINT); } else if (IsMsgAttach (extra)) { - _mutt_make_string (buffer, - COLS - 9 < sizeof (buffer) ? COLS - 9: sizeof (buffer), - NONULL (PagerFmt), Context, extra->bdy->hdr, M_FORMAT_MAKEPRINT); + size_t l1 = (COLS - 9) * MB_LEN_MAX; + size_t l2 = sizeof (buffer); + _mutt_make_string (buffer, l1 < l2 ? l1 : l2, NONULL (PagerFmt), + Context, extra->bdy->hdr, M_FORMAT_MAKEPRINT); } mutt_paddstr (COLS-10, IsHeader (extra) || IsMsgAttach (extra) ? buffer : banner); diff -ur mutt-1.5.6.orig/protos.h mutt-1.5.6/protos.h --- mutt-1.5.6.orig/protos.h Mon Feb 2 02:15:17 2004 +++ mutt-1.5.6/protos.h Tue May 25 23:37:27 2004 @@ -350,6 +350,7 @@ /* utf8.c */ int mutt_wctoutf8 (char *s, unsigned int c); +size_t mutt_mbswidth (char *s, size_t n); #ifdef LOCALES_HACK #define IsPrint(c) (isprint((unsigned char)(c)) || \ diff -uNr mutt-1.5.6.orig/charset.c mutt-1.5.6/charset.c --- mutt-1.5.6.orig/charset.c Tue Jan 21 21:25:21 2003 +++ mutt-1.5.6/charset.c Sun Feb 15 15:13:26 2004 @@ -581,3 +581,86 @@ iconv_close (fc->cd); FREE (_fc); } + +char *mutt_get_first_charset (const char *charset) +{ + static char fcharset[SHORT_STRING]; + const char *c, *c1; + + c = charset; + if (!mutt_strlen(c)) + return "us-ascii"; + if (!(c1 = strchr (c, ':'))) + return charset; + strfcpy (fcharset, c, c1 - c + 1); + return fcharset; +} + +static size_t convert_string (ICONV_CONST char *f, size_t flen, + const char *from, const char *to, + char **t, size_t *tlen) +{ + iconv_t cd; + char *buf, *ob; + size_t obl, n; + int e; + + cd = mutt_iconv_open (to, from, 0); + if (cd == (iconv_t)(-1)) + return (size_t)(-1); + obl = 4 * flen + 1; + ob = buf = safe_malloc (obl); + n = iconv (cd, &f, &flen, &ob, &obl); + if (n == (size_t)(-1) || iconv (cd, 0, 0, &ob, &obl) == (size_t)(-1)) + { + e = errno; + FREE (&buf); + iconv_close (cd); + errno = e; + return (size_t)(-1); + } + *ob = '\0'; + + *tlen = ob - buf; + + safe_realloc ((void **) &buf, ob - buf + 1); + *t = buf; + iconv_close (cd); + + return n; +} + +int mutt_convert_nonmime_string (char **ps) +{ + const char *c, *c1; + + for (c = AssumedCharset; c; c = c1 ? c1 + 1 : 0) + { + char *u = *ps; + char *s; + char *fromcode; + size_t m, n; + size_t ulen = mutt_strlen (*ps); + size_t slen; + + if (!u || !*u) + return 0; + + c1 = strchr (c, ':'); + n = c1 ? c1 - c : mutt_strlen (c); + if (!n) + continue; + fromcode = safe_malloc (n + 1); + strfcpy (fromcode, c, n + 1); + m = convert_string (u, ulen, fromcode, Charset, &s, &slen); + FREE (&fromcode); + if (m != (size_t)(-1)) + { + FREE (ps); + *ps = s; + return 0; + } + } + return -1; +} + diff -uNr mutt-1.5.6.orig/charset.h mutt-1.5.6/charset.h --- mutt-1.5.6.orig/charset.h Tue Mar 4 16:49:43 2003 +++ mutt-1.5.6/charset.h Sun Feb 15 15:06:19 2004 @@ -35,6 +35,8 @@ #endif int mutt_convert_string (char **, const char *, const char *, int); +char *mutt_get_first_charset (const char *); +int mutt_convert_nonmime_string (char **); iconv_t mutt_iconv_open (const char *, const char *, int); size_t mutt_iconv (iconv_t, ICONV_CONST char **, size_t *, char **, size_t *, ICONV_CONST char **, const char *); diff -uNr mutt-1.5.6.orig/globals.h mutt-1.5.6/globals.h --- mutt-1.5.6.orig/globals.h Mon Feb 2 02:15:17 2004 +++ mutt-1.5.6/globals.h Sun Feb 15 15:06:19 2004 @@ -32,6 +32,7 @@ WHERE char *AliasFile; WHERE char *AliasFmt; +WHERE char *AssumedCharset; WHERE char *AttachSep; WHERE char *Attribution; WHERE char *AttachFormat; @@ -45,6 +46,7 @@ WHERE char *DsnReturn; WHERE char *Editor; WHERE char *EscChar; +WHERE char *FileCharset; WHERE char *FolderFormat; WHERE char *ForwFmt; WHERE char *Fqdn; diff -uNr mutt-1.5.6.orig/handler.c mutt-1.5.6/handler.c --- mutt-1.5.6.orig/handler.c Wed Nov 5 18:41:31 2003 +++ mutt-1.5.6/handler.c Sun Feb 15 15:06:19 2004 @@ -1718,11 +1718,21 @@ int istext = mutt_is_text_part (b); iconv_t cd = (iconv_t)(-1); - if (istext && s->flags & M_CHARCONV) + if (istext) { - char *charset = mutt_get_parameter ("charset", b->parameter); - if (charset && Charset) - cd = mutt_iconv_open (Charset, charset, M_ICONV_HOOK_FROM); + if(s->flags & M_CHARCONV) + { + char *charset = mutt_get_parameter ("charset", b->parameter); + if (!option (OPTSTRICTMIME) && !charset) + charset = mutt_get_first_charset (AssumedCharset); + if (charset && Charset) + cd = mutt_iconv_open (Charset, charset, M_ICONV_HOOK_FROM); + } + else + { + if (b->file_charset) + cd = mutt_iconv_open (Charset, b->file_charset, M_ICONV_HOOK_FROM); + } } fseek (s->fpin, b->offset, 0); diff -uNr mutt-1.5.6.orig/init.h mutt-1.5.6/init.h --- mutt-1.5.6.orig/init.h Mon Feb 2 02:15:17 2004 +++ mutt-1.5.6/init.h Sun Feb 15 15:07:40 2004 @@ -184,6 +184,23 @@ ** If set, Mutt will prompt you for carbon-copy (Cc) recipients before ** editing the body of an outgoing message. */ + { "assumed_charset", DT_STR, R_NONE, UL &AssumedCharset, UL "us-ascii"}, + /* + ** .pp + ** This variable is a colon-separated list of character encoding + ** schemes for messages without character encoding indication. + ** Header field values and message body content without character encoding + ** indication would be assumed that they are written in one of this list. + ** By default, all the header fields and message body without any charset + ** indication are assumed to be in "us-ascii". + ** .pp + ** For example, Japanese users might prefer this: + ** .pp + ** set assumed_charset="iso-2022-jp:euc-jp:shift_jis:utf-8" + ** .pp + ** However, only the first content is valid for the message body. + ** This variable is valid only if $$strict_mime is unset. + */ { "attach_format", DT_STR, R_NONE, UL &AttachFormat, UL "%u%D%I %t%4n %T%.40d%> [%.7m/%.10M, %.6e%?C?, %C?, %s] " }, /* ** .pp @@ -532,6 +549,20 @@ ** signed. ** (PGP only) */ + { "file_charset", DT_STR, R_NONE, UL &FileCharset, UL 0 }, + /* + ** .pp + ** This variable is a colon-separated list of character encoding + ** schemes for text file attatchments. + ** If unset, $$charset value will be used instead. + ** For example, the following configuration would work for Japanese + ** text handling: + ** .pp + ** set file_charset="iso-2022-jp:euc-jp:shift_jis:utf-8" + ** .pp + ** Note: "iso-2022-*" must be put at the head of the value as shown above + ** if included. + */ { "folder", DT_PATH, R_NONE, UL &Maildir, UL "~/Mail" }, /* ** .pp @@ -2476,6 +2507,19 @@ ** Setting this variable causes the ``status bar'' to be displayed on ** the first line of the screen rather than near the bottom. */ + { "strict_mime", DT_BOOL, R_NONE, OPTSTRICTMIME, 1 }, + /* + ** .pp + ** When unset, non MIME-compliant messages that doesn't have any + ** charset indication in ``Content-Type'' field can be displayed + ** (non MIME-compliant messages are often generated by old mailers + ** or buggy mailers like MS Outlook Express). + ** See also $$assumed_charset. + ** .pp + ** This option also replaces linear-white-space between encoded-word + ** and *text to a single space to prevent the display of MIME-encoded + ** ``Subject'' field from being devided into multiple lines. + */ { "strict_threads", DT_BOOL, R_RESORT|R_RESORT_INIT|R_INDEX, OPTSTRICTTHREADS, 0 }, /* ** .pp diff -uNr mutt-1.5.6.orig/mutt.h mutt-1.5.6/mutt.h --- mutt-1.5.6.orig/mutt.h Mon Feb 2 02:15:17 2004 +++ mutt-1.5.6/mutt.h Sun Feb 15 15:06:19 2004 @@ -406,6 +406,7 @@ OPTSIGONTOP, OPTSORTRE, OPTSTATUSONTOP, + OPTSTRICTMIME, OPTSTRICTTHREADS, OPTSUSPEND, OPTTEXTFLOWED, @@ -599,6 +600,7 @@ * If NULL, filename is used * instead. */ + char *file_charset; /* charset of attached file */ CONTENT *content; /* structure used to store detailed info about * the content of the attachment. this is used * to determine what content-transfer-encoding diff -uNr mutt-1.5.6.orig/parse.c mutt-1.5.6/parse.c --- mutt-1.5.6.orig/parse.c Wed Nov 5 18:41:33 2003 +++ mutt-1.5.6/parse.c Sun Feb 15 15:06:19 2004 @@ -208,9 +208,23 @@ if (*s == '"') { + int state_ascii = 1; s++; - for (i=0; *s && *s != '"' && i < sizeof (buffer) - 1; i++, s++) + for (i=0; *s && i < sizeof (buffer) - 1; i++, s++) { + if (!option (OPTSTRICTMIME)) { + /* As iso-2022-* has a characer of '"' with non-ascii state, + * ignore it. */ + if (*s == 0x1b && i < sizeof (buffer) - 2) + { + if (s[1] == '(' && (s[2] == 'B' || s[2] == 'J')) + state_ascii = 1; + else + state_ascii = 0; + } + } + if (state_ascii && *s == '"') + break; if (*s == '\\') { /* Quote the next character */ @@ -379,7 +393,9 @@ if (ct->type == TYPETEXT) { if (!(pc = mutt_get_parameter ("charset", ct->parameter))) - mutt_set_parameter ("charset", "us-ascii", &ct->parameter); + mutt_set_parameter ("charset", option (OPTSTRICTMIME) ? "us-ascii" : + (const char *) mutt_get_first_charset (AssumedCharset), + &ct->parameter); } } diff -uNr mutt-1.5.6.orig/rfc2047.c mutt-1.5.6/rfc2047.c --- mutt-1.5.6.orig/rfc2047.c Wed Nov 5 18:41:33 2003 +++ mutt-1.5.6/rfc2047.c Sun Feb 15 15:13:58 2004 @@ -706,13 +706,54 @@ return 0; } +/* return length of linear white space */ +static size_t lwslen (const char *s, size_t n) +{ + const char *p = s; + size_t len = n; + + if (n <= 0) + return 0; + + for (; p < s + n; p++) + if (!strchr (" \t\r\n", *p)) + { + len = (size_t)(p - s); + break; + } + if (strchr ("\r\n", *(p-1))) /* LWS doesn't end with CRLF */ + len = (size_t)0; + return len; +} + +/* return length of linear white space : reverse */ +static size_t lwsrlen (const char *s, size_t n) +{ + const char *p = s + n - 1; + size_t len = n; + + if (n <= 0) + return 0; + + if (strchr ("\r\n", *p)) /* LWS doesn't end with CRLF */ + return (size_t)0; + + for (; p >= s; p--) + if (!strchr (" \t\r\n", *p)) + { + len = (size_t)(s + n - 1 - p); + break; + } + return len; +} + /* try to decode anything that looks like a valid RFC2047 encoded * header field, ignoring RFC822 parsing rules */ void rfc2047_decode (char **pd) { const char *p, *q; - size_t n; + size_t m, n; int found_encoded = 0; char *d0, *d; const char *s = *pd; @@ -729,6 +770,37 @@ if (!(p = find_encoded_word (s, &q))) { /* no encoded words */ + if (!option (OPTSTRICTMIME)) + { + n = mutt_strlen (s); + if (found_encoded && (m = lwslen (s, n)) != 0) + { + if (m != n) + *d = ' ', d++, dlen--; + n -= m, s += m; + } + if (ascii_strcasecmp (AssumedCharset, "us-ascii")) + { + char *t; + size_t tlen; + + t = safe_malloc (n + 1); + strfcpy (t, s, n + 1); + if (mutt_convert_nonmime_string (&t) == 0) + { + tlen = mutt_strlen (t); + strncpy (d, t, tlen); + d += tlen; + } + else + { + strncpy (d, s, n); + d += n; + } + FREE (&t); + break; + } + } strncpy (d, s, dlen); d += dlen; break; @@ -737,15 +809,37 @@ if (p != s) { n = (size_t) (p - s); - /* ignore spaces between encoded words */ - if (!found_encoded || strspn (s, " \t\r\n") != n) + /* ignore spaces between encoded words + * and linear white spaces between encoded word and *text */ + if (!option (OPTSTRICTMIME)) { - if (n > dlen) - n = dlen; - memcpy (d, s, n); - d += n; - dlen -= n; + if (found_encoded && (m = lwslen (s, n)) != 0) + { + if (m != n) + *d = ' ', d++, dlen--; + n -= m, s += m; + } + + if ((m = n - lwsrlen (s, n)) != 0) + { + if (m > dlen) + m = dlen; + memcpy (d, s, m); + d += m; + dlen -= m; + if (m != n) + *d = ' ', d++, dlen--; + } } + else if (!found_encoded || strspn (s, " \t\r\n") != n) + { + if (n > dlen) + n = dlen; + memcpy (d, s, n); + d += n; + dlen -= n; + } + } rfc2047_decode_word (d, p, dlen); @@ -766,7 +860,7 @@ { while (a) { - if (a->personal && strstr (a->personal, "=?") != NULL) + if (a->personal) rfc2047_decode (&a->personal); #ifdef EXACT_ADDRESS if (a->val && strstr (a->val, "=?") != NULL) diff -uNr mutt-1.5.6.orig/rfc2231.c mutt-1.5.6/rfc2231.c --- mutt-1.5.6.orig/rfc2231.c Wed Nov 5 18:41:33 2003 +++ mutt-1.5.6/rfc2231.c Sun Feb 15 15:06:19 2004 @@ -113,6 +113,11 @@ if (option (OPTRFC2047PARAMS) && p->value && strstr (p->value, "=?")) rfc2047_decode (&p->value); + else if (!option (OPTSTRICTMIME)) + { + if (ascii_strcasecmp (AssumedCharset, "us-ascii")) + mutt_convert_nonmime_string (&p->value); + } *last = p; last = &p->next; diff -uNr mutt-1.5.6.orig/sendlib.c mutt-1.5.6/sendlib.c --- mutt-1.5.6.orig/sendlib.c Wed Nov 5 18:41:33 2003 +++ mutt-1.5.6/sendlib.c Sun Feb 15 15:11:33 2004 @@ -496,7 +496,7 @@ } if (a->type == TYPETEXT && (!a->noconv)) - fc = fgetconv_open (fpin, Charset, + fc = fgetconv_open (fpin, a->file_charset, mutt_get_body_charset (send_charset, sizeof (send_charset), a), 0); else @@ -896,6 +896,7 @@ CONTENT *info; CONTENT_STATE state; FILE *fp = NULL; + char *fromcode; char *tocode; char buffer[100]; char chsbuf[STRING]; @@ -930,15 +931,18 @@ if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) { char *chs = mutt_get_parameter ("charset", b->parameter); + char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ? + FileCharset : Charset) : Charset; if (Charset && (chs || SendCharset) && - convert_file_from_to (fp, Charset, chs ? chs : SendCharset, - 0, &tocode, info) != (size_t)(-1)) + convert_file_from_to (fp, fchs, chs ? chs : SendCharset, + &fromcode, &tocode, info) != (size_t)(-1)) { if (!chs) { mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode); mutt_set_parameter ("charset", chsbuf, &b->parameter); } + b->file_charset = fromcode; FREE (&tocode); safe_fclose (&fp); return info; @@ -1318,6 +1322,7 @@ body->unlink = 1; body->use_disp = 0; body->disposition = DISPINLINE; + body->noconv = 1; mutt_parse_mime_message (ctx, hdr);
Attachment:
signature.asc
Description: Digital signature