From: Thomas Roessler Date: Wed, 4 Oct 2000 18:33:27 +0000 (+0000) Subject: Adding support for Netscape's (pardon, Mozilla's) SSL X-Git-Tag: mutt-1-3-10-rel~9 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=47bb2c52234acfdc4ccc066baed8541df8acc3d0;p=mutt Adding support for Netscape's (pardon, Mozilla's) SSL implementation. From Michael Elkins. --- diff --git a/Makefile.am b/Makefile.am index c674929e..652e60f6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -69,11 +69,12 @@ non_us_sources = pgp.c pgpinvoke.c pgpkey.c pgplib.c sha1.c \ doc/language.txt doc/language50.txt OPS.PGP doc/PGP-Notes.txt \ OPS.MIX remailer.c remailer.h pgpewrap \ contrib/pgp2.rc contrib/pgp5.rc contrib/gpg.rc \ - mutt_ssl.c mutt_ssl.h README.SSL + mutt_ssl.c mutt_ssl.h README.SSL mutt_ssl_nss.c EXTRA_mutt_SOURCES = account.c md5c.c mutt_sasl.c mutt_socket.c mutt_ssl.c \ pop.c pgp.c pgpinvoke.c pgpkey.c pgplib.c sha1.c gnupgparse.c \ - resize.c dotlock.c remailer.c browser.h mbyte.h remailer.h url.h + resize.c dotlock.c remailer.c browser.h mbyte.h remailer.h url.h \ + mutt_ssl_nss.c EXTRA_DIST = COPYRIGHT GPL OPS OPS.PGP TODO configure acconfig.h account.h \ attach.h buffy.h charset.h copy.h dotlock.h functions.h gen_defs \ diff --git a/acconfig.h b/acconfig.h index 1f50cc6f..3dad2c55 100644 --- a/acconfig.h +++ b/acconfig.h @@ -59,6 +59,9 @@ /* Do you want support for SSL? (--with-ssl) */ #undef USE_SSL +/* Do you want support for SSL via the NSS library? (--with-nss) */ +#undef USE_NSS + /* Avoid SSL routines which used patent-encumbered RC5 algorithms */ #undef NO_RC5 diff --git a/configure.in b/configure.in index 78e78f66..e5bd60b4 100644 --- a/configure.in +++ b/configure.in @@ -554,8 +554,8 @@ dnl -- end socket dependencies -- if test "$need_socket" = "yes" then - AC_CHECK_FUNC(setsockopt, , AC_CHECK_LIB(socket, setsockopt)) AC_CHECK_FUNC(gethostent, , AC_CHECK_LIB(nsl, gethostent)) + AC_CHECK_FUNC(setsockopt, , AC_CHECK_LIB(socket, setsockopt)) AC_CHECK_FUNCS(getaddrinfo) AC_DEFINE(USE_SOCKET) MUTT_LIB_OBJECTS="$MUTT_LIB_OBJECTS account.o mutt_socket.o" @@ -659,6 +659,30 @@ AC_ARG_WITH(ssl, [ --with-ssl[=PFX] Compile in SSL socket support for ]) AM_CONDITIONAL(USE_SSL, test x$need_ssl = xyes) +dnl SSL support via NSS +AC_ARG_WITH(nss, [ --with-nss[=PFX] Compile in SSL socket support for POP/IMAP via NSS], +[ if test "$with_nss" != no + then + if test "$need_socket" != "yes"; then + AC_MSG_ERROR([SSL support is only useful with POP or IMAP support]) + fi + + if test "$with_nss" != "yes" + then + LDFLAGS="$LDFLAGS -L$withval/lib" + CPPFLAGS="$CPPFLAGS -I$withval/include -I$withval/public/security" + fi + + AC_DEFINE(USE_NSS) + MUTTLIBS="$MUTTLIBS -lssl -lnss -lcertdb -lcerthi -lcryptohi" + MUTTLIBS="$MUTTLIBS -lpk11wrap -lsoftoken -lsecutil -ldbm -lplds4 -lplc4 -lfreebl" + MUTTLIBS="$MUTTLIBS -lnspr4" + + MUTT_LIB_OBJECTS="$MUTT_LIB_OBJECTS mutt_ssl_nss.o" + need_ssl=yes + fi +]) + dnl -- end imap dependencies -- AC_ARG_WITH(sasl, [ --with-sasl[=DIR] Use Cyrus SASL library for POP/IMAP authentication], diff --git a/init.c b/init.c index 8390441a..9c1cfd32 100644 --- a/init.c +++ b/init.c @@ -31,7 +31,7 @@ #endif -#ifdef USE_SSL +#if defined(USE_SSL) || defined(USE_NSS) #include "mutt_ssl.h" #endif diff --git a/init.h b/init.h index e7ee8cf8..fbb5902a 100644 --- a/init.h +++ b/init.h @@ -1326,7 +1326,7 @@ struct option_t MuttVars[] = { */ #endif /* HAVE_PGP */ -#ifdef USE_SSL +#if defined(USE_SSL)||defined(USE_NSS) { "certificate_file", DT_PATH, R_NONE, UL &SslCertFile, 0 }, /* ** .pp @@ -1412,10 +1412,14 @@ struct option_t MuttVars[] = { ** .pp ** The name or address of your POP3 server. */ - { "pop_port", DT_NUM, R_NONE, UL &PopPort, 110 }, + { "pop_port", DT_NUM, R_NONE, UL &PopPort, -1 }, /* ** .pp ** This variable specifies which port your POP server is listening on. + ** If you specify a value less than zero, Mutt will auto-select the port + ** based upon whether your are doing normal pop3 (110) or pop3s (995). + ** If your POP3 server listens on a non-standard port, you will have to + ** set this to the correct value for your environment. */ { "pop_last", DT_BOOL, R_NONE, OPTPOPLAST, 0 }, /* diff --git a/mutt.h b/mutt.h index c07f4d59..44a462a9 100644 --- a/mutt.h +++ b/mutt.h @@ -318,7 +318,7 @@ enum OPTIMAPFORCESSL, # endif #endif -#ifdef USE_SSL +#if defined(USE_SSL) || defined(USE_NSS) OPTSSLV2, OPTSSLV3, OPTTLSV1, diff --git a/mutt_socket.c b/mutt_socket.c index d513081e..052a7579 100644 --- a/mutt_socket.c +++ b/mutt_socket.c @@ -195,6 +195,8 @@ CONNECTION* mutt_conn_find (const CONNECTION* start, const ACCOUNT* account) { #ifdef USE_SSL ssl_socket_setup (conn); +#elif USE_NSS + mutt_nss_socket_setup (conn); #else mutt_error _("SSL is unavailable."); sleep (2); diff --git a/mutt_ssl_nss.c b/mutt_ssl_nss.c new file mode 100644 index 00000000..d30a1535 --- /dev/null +++ b/mutt_ssl_nss.c @@ -0,0 +1,390 @@ +/* Copyright (C) 2000 Michael R. Elkins + * + * 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 +#include +#include +#include +#include +#include "nss.h" +#include "ssl.h" +#include "sechash.h" +#include "cert.h" +#include "cdbhdl.h" +#include "mutt.h" +#include "mutt_socket.h" +#include "mutt_curses.h" + +static int MuttNssInitialized = 0; + +char *SslCertFile = 0; +char *SslEntropyFile = 0; /* unused, required to link */ + +/* internal data struct we use with the CONNECTION. this is where NSS-specific + * data gets stuffed so that the main mutt_socket.h doesn't have to be + * modified. + */ +typedef struct +{ + PRFileDesc *fd; + CERTCertDBHandle *db; +} +mutt_nss_t; + +/* nss callback to grab the user's password. */ +static char * +mutt_nss_password_func (PK11SlotInfo * slot, PRBool retry, void *arg) +{ + return NULL; +} + +static int +mutt_nss_error (const char *call) +{ + mutt_error ("%s failed (error %d)", call, PR_GetError ()); + return -1; +} + +/* initialize the NSS library for use. must be called prior to any other + * functions in this module. + */ +static int +mutt_nss_init (void) +{ + if (!MuttNssInitialized) + { + PK11_SetPasswordFunc (mutt_nss_password_func); + if (NSS_Init (SslCertFile) == SECFailure) + return mutt_nss_error ("NSS_Init"); + + /* always use strong crypto. */ + if (NSS_SetDomesticPolicy () == SECFailure) + return mutt_nss_error ("NSS_SetDomesticPolicy"); + + /* intialize the session cache */ + SSL_ClearSessionCache (); + + MuttNssInitialized = 1; + } + return 0; +} + +/* convert from int64 to a readable string and print on the screen */ +static void +mutt_nss_pretty_time (int64 usecs) +{ + struct tm t; + PRExplodedTime ex; + char timebuf[128]; + + PR_ExplodeTime (usecs, PR_LocalTimeParameters, &ex); + + t.tm_sec = ex.tm_sec; + t.tm_min = ex.tm_min; + t.tm_hour = ex.tm_hour; + t.tm_mday = ex.tm_mday; + t.tm_mon = ex.tm_month; + t.tm_year = ex.tm_year - 1900; /* PRExplodedTime uses the absolute year */ + t.tm_wday = ex.tm_wday; + t.tm_yday = ex.tm_yday; + + strfcpy (timebuf, asctime (&t), sizeof (timebuf)); + timebuf[strlen (timebuf) - 1] = 0; + + addstr (timebuf); +} + +/* this function is called by the default hook CERT_AuthCertificate when it + * can't verify a cert based upon the contents of the user's certificate + * database. we are given the option to override the decision and accept + * it anyway. + */ +static SECStatus +mutt_nss_bad_cert (void *arg, PRFileDesc * fd) +{ + PRErrorCode err; + CERTCertificate *cert, *issuer; + unsigned char hash[16]; + int i; + CERTCertTrust trust; + int64 not_before, not_after; + event_t ch; + char status[256]; + + /* first lets see why this certificate failed. we only want to override + * in the case where the cert was not found. + */ + err = PR_GetError (); + mutt_error (_("SSL_AuthCertificate failed (error %d)"), err); + + /* fetch the cert in question */ + cert = SSL_PeerCertificate (fd); + + move (LINES - 8, 0); + clrtoeol (); + move (LINES - 7, 0); + clrtoeol (); + addstr ("Issuer: "); + addstr (CERT_NameToAscii (&cert->issuer)); + move (LINES - 6, 0); + clrtoeol (); + addstr ("Subject: "); + addstr (CERT_NameToAscii (&cert->subject)); + + move (LINES - 5, 0); + clrtoeol (); + addstr ("Valid: "); + CERT_GetCertTimes (cert, ¬_before, ¬_after); + mutt_nss_pretty_time (not_before); + addstr (" to "); + mutt_nss_pretty_time (not_after); + + move (LINES - 4, 0); + clrtoeol (); + addstr ("Fingerprint: "); + + /* calculate the MD5 hash of the raw certificate */ + HASH_HashBuf (HASH_AlgMD5, hash, cert->derCert.data, cert->derCert.len); + for (i = 0; i < 16; i++) + { + printw ("%0x", hash[i]); + if (i != 15) + addch (':'); + } + + mvaddstr (LINES - 3, 0, "Signature: "); + clrtoeol (); + + /* find the cert which signed this cert */ + issuer = CERT_FindCertByName ((CERTCertDBHandle *) arg, &cert->derIssuer); + + /* verify the sig (only) if we have the issuer cert handy */ + if (issuer && + CERT_VerifySignedData (&cert->signatureWrap, issuer, PR_Now (), NULL) + == SECSuccess) + addstr ("GOOD"); + else + addstr ("BAD"); + + move (LINES - 2, 0); + SETCOLOR (MT_COLOR_STATUS); + memset (status, ' ', sizeof (status) - 1); + if (COLS < sizeof (status)) + status[COLS - 1] = 0; + else + status[sizeof (status) - 1] = 0; + memcpy (status, "--- SSL Certificate Check", + sizeof ("--- SSL Certificate Check") - 1); + addstr (status); + clrtoeol (); + SETCOLOR (MT_COLOR_NORMAL); + + for (;;) + { + mvaddstr (LINES - 1, 0, "(r)eject, accept (o)nce, (a)lways accept?"); + clrtoeol (); + ch = mutt_getch (); + if (ch.ch == -1) + { + i = SECFailure; + break; + } + else if (tolower (ch.ch) == 'r') + { + i = SECFailure; + break; + } + else if (tolower (ch.ch) == 'o') + { + i = SECSuccess; + break; + } + else if (tolower (ch.ch) == 'a') + { + /* push this certificate onto the user's certificate store so it + * automatically becomes valid next time we see it + */ + + /* set this certificate as a valid peer for SSL-auth ONLY. */ + CERT_DecodeTrustString (&trust, "P,,"); + + CERT_AddTempCertToPerm (cert, NULL, &trust); + i = SECSuccess; + break; + } + BEEP (); + } + + /* SSL_PeerCertificate() returns a copy with an updated ref count, so + * we have to destroy our copy here. + */ + CERT_DestroyCertificate (cert); + + return i; +} + +/* TODO: this currently doesn't support the `Preconnect', it should be + * moved out of raw_socket_open() into a generic function instead. + */ +static int +mutt_nss_socket_open (CONNECTION * con) +{ + mutt_nss_t *sockdata; + PRNetAddr addr; + struct hostent *he; + + memset (&addr, 0, sizeof (addr)); + addr.inet.family = AF_INET; + addr.inet.port = PR_htons (con->account.port); + he = gethostbyname (con->account.host); + if (!he) + { + mutt_error (_("Unable to find ip for host %s"), con->account.host); + return -1; + } + addr.inet.ip = *((int *) he->h_addr_list[0]); + + sockdata = safe_calloc (1, sizeof (mutt_nss_t)); + + do + { + sockdata->fd = PR_NewTCPSocket (); + if (sockdata->fd == NULL) + { + mutt_error (_("PR_NewTCPSocket failed.")); + break; + } + /* make this a SSL socket */ + sockdata->fd = SSL_ImportFD (NULL, sockdata->fd); + + /* set SSL version options based upon user's preferences */ + if (!option (OPTTLSV1)) + { + SSL_OptionSet (sockdata->fd, SSL_ENABLE_TLS, PR_FALSE); + } + if (!option (OPTSSLV2)) + { + SSL_OptionSet (sockdata->fd, SSL_ENABLE_SSL2, PR_FALSE); + } + if (!option (OPTSSLV3)) + { + SSL_OptionSet (sockdata->fd, SSL_ENABLE_SSL3, PR_FALSE); + } + + /* set the host we were attempting to connect to in order to verify + * the name in the certificate we get back. + */ + if (SSL_SetURL (sockdata->fd, con->account.host)) + { + mutt_nss_error ("SSL_SetURL"); + break; + } + + /* we don't need no stinking pin. we don't authenticate ourself + * via SSL. + */ + SSL_SetPKCS11PinArg (sockdata->fd, 0); + + sockdata->db = CERT_GetDefaultCertDB (); + + /* use the default supplied hook. it takes an argument to our + * certificate database. the manual lies, you can't really specify + * NULL for the callback to get the default! + */ + SSL_AuthCertificateHook (sockdata->fd, SSL_AuthCertificate, + sockdata->db); + /* set the callback to be used when SSL_AuthCertificate() fails. this + * allows us to override and insert the cert back into the db + */ + SSL_BadCertHook (sockdata->fd, mutt_nss_bad_cert, sockdata->db); + + if (PR_Connect (sockdata->fd, &addr, PR_INTERVAL_NO_TIMEOUT) == + PR_FAILURE) + { + mutt_error (_("Unable to connect to host %s"), con->account.host); + break; + } + + /* store the extra info in the CONNECTION struct for later use. */ + con->sockdata = sockdata; + + /* HACK. some of the higher level calls in mutt_socket.c depend on this + * being >0 when we are in the connected state. we just set this to + * an arbitrary value to avoid hitting that bug, since we neve have the + * real fd. + */ + con->fd = 42; + + /* success */ + return 0; + } + while (0); + + /* we get here when we had an oops. clean up the mess. */ + + if (sockdata) + { + if (sockdata->fd) + PR_Close (sockdata->fd); + if (sockdata->db) + CERT_ClosePermCertDB (sockdata->db); + safe_free ((void **) &sockdata); + } + return -1; +} + +static int +mutt_nss_socket_close (CONNECTION * con) +{ + mutt_nss_t *sockdata = (mutt_nss_t *) con->sockdata; + + if (PR_Close (sockdata->fd) == PR_FAILURE) + { + return -1; + } + if (sockdata->db) + CERT_ClosePermCertDB (sockdata->db); + /* free up the memory we used for this connection specific to NSS. */ + safe_free ((void **) &con->sockdata); + return 0; +} + +static int +mutt_nss_socket_read (CONNECTION * con) +{ + return PR_Read (((mutt_nss_t *) con->sockdata)->fd, con->inbuf, + LONG_STRING); +} + +static int +mutt_nss_socket_write (CONNECTION * con, const char *buf, size_t count) +{ + return PR_Write (((mutt_nss_t *) con->sockdata)->fd, buf, count); +} + +/* initialize a new connection for use with NSS */ +int +mutt_nss_socket_setup (CONNECTION * con) +{ + if (mutt_nss_init ()) + return -1; + con->open = mutt_nss_socket_open; + con->read = mutt_nss_socket_read; + con->write = mutt_nss_socket_write; + con->close = mutt_nss_socket_close; + return 0; +} diff --git a/pop.c b/pop.c index 6e640202..0080fd80 100644 --- a/pop.c +++ b/pop.c @@ -120,7 +120,7 @@ int pop_authenticate (CONNECTION *conn) void mutt_fetchPopMail (void) { char buffer[LONG_STRING]; - char msgbuf[SHORT_STRING]; + char msgbuf[SHORT_STRING], *p; int i, delanswer, last = 0, msgs, bytes, err = 0; CONTEXT ctx; MESSAGE *msg = NULL; @@ -133,10 +133,30 @@ void mutt_fetchPopMail (void) return; } + /* if the host is specified as `my.com/ssl', use ssl to connect to the + * given port. this is kinda silly (looks like nobody was using this + * as of 9-29-2000), but it's easier to test pop+ssl than imap+ssl for + * the new NSS support. -me + */ + account.flags = 0; strfcpy (account.host, PopHost, sizeof (account.host)); - account.port = PopPort; + p = strchr(account.host, '/'); + if (p) { + if (!strcmp (p+1, "ssl")) { + *p = 0; + account.flags |= M_ACCT_SSL; + } + } account.type = M_ACCT_TYPE_POP; - account.flags = 0; + if (PopPort < 0) { + /* auto-select the port based upon what we are attempting to do */ + if (account.flags & M_ACCT_SSL) + account.port = 995; /* pop3s (pop3 + ssl) */ + else + account.port = 110; /* pop3 */ + } + else + account.port = PopPort; conn = mutt_conn_find (NULL, &account); if (mutt_socket_open (conn) < 0) return;