]> granicus.if.org Git - mutt/commitdiff
Adding support for Netscape's (pardon, Mozilla's) SSL
authorThomas Roessler <roessler@does-not-exist.org>
Wed, 4 Oct 2000 18:33:27 +0000 (18:33 +0000)
committerThomas Roessler <roessler@does-not-exist.org>
Wed, 4 Oct 2000 18:33:27 +0000 (18:33 +0000)
implementation.  From Michael Elkins.

Makefile.am
acconfig.h
configure.in
init.c
init.h
mutt.h
mutt_socket.c
mutt_ssl_nss.c [new file with mode: 0644]
pop.c

index c674929ee0079e4f910de7217aa1e0dc3751dbd0..652e60f659f0d1d487f76aaed43de31f4852a743 100644 (file)
@@ -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 \
index 1f50cc6fc69e14a0447d9b8eb386d88a9ec875b7..3dad2c55f13abec349f10b500a7a96cb2cb2517f 100644 (file)
@@ -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
 
index 78e78f664bbde2c72b7b4d8d622d3a0037af6f35..e5bd60b4d73beec81697c6edd020e800c11df010 100644 (file)
@@ -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 8390441ab08d4205663f706bb21360f8a5d9c66e..9c1cfd325a23798f1e66447748f10d47398294a8 100644 (file)
--- 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 e7ee8cf85ccb5fee35d3698b2baab1eaf5653d90..fbb5902ab1e8e934848511731711bd48920c57c5 100644 (file)
--- 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 c07f4d5936be57a709318fc4afc09961e4da330c..44a462a943db0f3b015c0a7f2d07ff9b04da5b93 100644 (file)
--- 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,
index d513081e08b490ef941683db2b7d3b14b476716b..052a7579108c63ab321e6b4dd18aae9af63525bd 100644 (file)
@@ -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 (file)
index 0000000..d30a153
--- /dev/null
@@ -0,0 +1,390 @@
+/* Copyright (C) 2000 Michael R. Elkins <me@mutt.org>
+ *
+ *     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 <prinit.h>
+#include <pk11func.h>
+#include <prtypes.h>
+#include <prio.h>
+#include <prnetdb.h>
+#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, &not_before, &not_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 6e640202de9020498d87a86cf9211363c1088cd0..0080fd80554082e826bbaa9cbafaf88db35f6e2e 100644 (file)
--- 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;