Rocco Rutte:
[apps/madmutt.git] / mutt_ssl_nss.c
1 /* Copyright (C) 2000 Michael R. Elkins <me@mutt.org>
2  *
3  *     This program is free software; you can redistribute it and/or modify
4  *     it under the terms of the GNU General Public License as published by
5  *     the Free Software Foundation; either version 2 of the License, or
6  *     (at your option) any later version.
7  * 
8  *     This program is distributed in the hope that it will be useful,
9  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
10  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  *     GNU General Public License for more details.
12  * 
13  *     You should have received a copy of the GNU General Public License
14  *     along with this program; if not, write to the Free Software
15  *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
16  */
17
18 #if HAVE_CONFIG_H
19 # include "config.h"
20 #endif
21
22 #include <prinit.h>
23 #include <pk11func.h>
24 #include <prtypes.h>
25 #include <prio.h>
26 #include <prnetdb.h>
27 #include "nss.h"
28 #include "ssl.h"
29 #include "sechash.h"
30 #include "cert.h"
31 #include "cdbhdl.h"
32 #include "mutt.h"
33 #include "mutt_socket.h"
34 #include "mutt_curses.h"
35
36 static int MuttNssInitialized = 0;
37
38 /* internal data struct we use with the CONNECTION.  this is where NSS-specific
39  * data gets stuffed so that the main mutt_socket.h doesn't have to be
40  * modified.
41  */
42 typedef struct
43 {
44   PRFileDesc *fd;
45   CERTCertDBHandle *db;
46 }
47 mutt_nss_t;
48
49 /* nss callback to grab the user's password. */
50 static char *
51 mutt_nss_password_func (PK11SlotInfo * slot, PRBool retry, void *arg)
52 {
53   return NULL;
54 }
55
56 static int
57 mutt_nss_error (const char *call)
58 {
59   mutt_error (_("%s failed (error %d)"), call, PR_GetError ());
60   return -1;
61 }
62
63 /* initialize the NSS library for use.  must be called prior to any other
64  * functions in this module.
65  */
66 static int
67 mutt_nss_init (void)
68 {
69   if (!MuttNssInitialized)
70   {
71     PK11_SetPasswordFunc (mutt_nss_password_func);
72     if (NSS_Init (SslCertFile) == SECFailure)
73       return mutt_nss_error ("NSS_Init");
74     
75     /* always use strong crypto. */
76     if (NSS_SetDomesticPolicy () == SECFailure)
77       return mutt_nss_error ("NSS_SetDomesticPolicy");
78     
79     /* intialize the session cache */
80     SSL_ClearSessionCache ();
81     
82     MuttNssInitialized = 1;
83   }
84   return 0;
85 }
86
87 /* convert from int64 to a readable string and print on the screen */
88 static void
89 mutt_nss_pretty_time (int64 usecs)
90 {
91   struct tm t;
92   PRExplodedTime ex;
93   char timebuf[128];
94   
95   PR_ExplodeTime (usecs, PR_LocalTimeParameters, &ex);
96   
97   t.tm_sec = ex.tm_sec;
98   t.tm_min = ex.tm_min;
99   t.tm_hour = ex.tm_hour;
100   t.tm_mday = ex.tm_mday;
101   t.tm_mon = ex.tm_month;
102   t.tm_year = ex.tm_year - 1900;        /* PRExplodedTime uses the absolute year */
103   t.tm_wday = ex.tm_wday;
104   t.tm_yday = ex.tm_yday;
105   
106   strfcpy (timebuf, asctime (&t), sizeof (timebuf));
107   timebuf[strlen (timebuf) - 1] = 0;
108   
109   addstr (timebuf);
110 }
111
112 /* this function is called by the default hook CERT_AuthCertificate when it
113  * can't verify a cert based upon the contents of the user's certificate
114  * database.  we are given the option to override the decision and accept
115  * it anyway.
116  */
117 static SECStatus
118 mutt_nss_bad_cert (void *arg, PRFileDesc * fd)
119 {
120   PRErrorCode err;
121   CERTCertificate *cert, *issuer;
122   unsigned char hash[16];
123   int i;
124   CERTCertTrust trust;
125   int64 not_before, not_after;
126   event_t ch;
127   char status[256];
128
129   /* first lets see why this certificate failed.  we only want to override
130    * in the case where the cert was not found.
131    */
132   err = PR_GetError ();
133   mutt_error (_("SSL_AuthCertificate failed (error %d)"), err);
134
135   /* fetch the cert in question */
136   cert = SSL_PeerCertificate (fd);
137
138   move (LINES - 8, 0);
139   clrtoeol ();
140   move (LINES - 7, 0);
141   clrtoeol ();
142   addstr ("Issuer:      ");
143   addstr (CERT_NameToAscii (&cert->issuer));
144   move (LINES - 6, 0);
145   clrtoeol ();
146   addstr ("Subject:     ");
147   addstr (CERT_NameToAscii (&cert->subject));
148
149   move (LINES - 5, 0);
150   clrtoeol ();
151   addstr ("Valid:       ");
152   CERT_GetCertTimes (cert, &not_before, &not_after);
153   mutt_nss_pretty_time (not_before);
154   addstr (" to ");
155   mutt_nss_pretty_time (not_after);
156
157   move (LINES - 4, 0);
158   clrtoeol ();
159   addstr ("Fingerprint: ");
160
161   /* calculate the MD5 hash of the raw certificate */
162   HASH_HashBuf (HASH_AlgMD5, hash, cert->derCert.data, cert->derCert.len);
163   for (i = 0; i < 16; i++)
164   {
165     printw ("%0x", hash[i]);
166     if (i != 15)
167       addch (':');
168   }
169   
170   mvaddstr (LINES - 3, 0, "Signature:   ");
171   clrtoeol ();
172
173   /* find the cert which signed this cert */
174   issuer = CERT_FindCertByName ((CERTCertDBHandle *) arg, &cert->derIssuer);
175
176   /* verify the sig (only) if we have the issuer cert handy */
177   if (issuer &&
178       CERT_VerifySignedData (&cert->signatureWrap, issuer, PR_Now (), NULL)
179       == SECSuccess)
180     addstr ("GOOD");
181   else
182     addstr ("BAD");
183
184   move (LINES - 2, 0);
185   SETCOLOR (MT_COLOR_STATUS);
186   memset (status, ' ', sizeof (status) - 1);
187   if (COLS < sizeof (status))
188     status[COLS - 1] = 0;
189   else
190     status[sizeof (status) - 1] = 0;
191   memcpy (status, "--- SSL Certificate Check",
192           sizeof ("--- SSL Certificate Check") - 1);
193   addstr (status);
194   clrtoeol ();
195   SETCOLOR (MT_COLOR_NORMAL);
196
197   for (;;)
198   {
199     mvaddstr (LINES - 1, 0, "(r)eject, accept (o)nce, (a)lways accept?");
200     clrtoeol ();
201     ch = mutt_getch ();
202     if (ch.ch == -1)
203     {
204       i = SECFailure;
205       break;
206     }
207     else if (ascii_tolower (ch.ch) == 'r')
208     {
209       i = SECFailure;
210       break;
211     }
212     else if (ascii_tolower (ch.ch) == 'o')
213     {
214       i = SECSuccess;
215       break;
216     }
217     else if (ascii_tolower (ch.ch) == 'a')
218     {
219       /* push this certificate onto the user's certificate store so it
220        * automatically becomes valid next time we see it
221        */
222       
223       /* set this certificate as a valid peer for SSL-auth ONLY. */
224       CERT_DecodeTrustString (&trust, "P,,");
225       
226       CERT_AddTempCertToPerm (cert, NULL, &trust);
227       i = SECSuccess;
228       break;
229     }
230     BEEP ();
231   }
232   
233   /* SSL_PeerCertificate() returns a copy with an updated ref count, so
234    * we have to destroy our copy here.
235    */
236   CERT_DestroyCertificate (cert);
237   
238   return i;
239 }
240
241 static int
242 mutt_nss_socket_open (CONNECTION * con)
243 {
244   mutt_nss_t *sockdata;
245   PRNetAddr addr;
246   struct hostent *he;
247
248   memset (&addr, 0, sizeof (addr));
249   addr.inet.family = AF_INET;
250   addr.inet.port = PR_htons (con->account.port);
251   he = gethostbyname (con->account.host);
252   if (!he)
253   {
254     mutt_error (_("Unable to find ip for host %s"), con->account.host);
255     return -1;
256   }
257   addr.inet.ip = *((int *) he->h_addr_list[0]);
258
259   sockdata = safe_calloc (1, sizeof (mutt_nss_t));
260
261   do
262   {
263     sockdata->fd = PR_NewTCPSocket ();
264     if (sockdata->fd == NULL)
265     {
266       mutt_error (_("PR_NewTCPSocket failed."));
267       break;
268     }
269     /* make this a SSL socket */
270     sockdata->fd = SSL_ImportFD (NULL, sockdata->fd);
271     
272     /* set SSL version options based upon user's preferences */
273     if (!option (OPTTLSV1))
274       SSL_OptionSet (sockdata->fd, SSL_ENABLE_TLS, PR_FALSE);
275     
276     if (!option (OPTSSLV2))
277       SSL_OptionSet (sockdata->fd, SSL_ENABLE_SSL2, PR_FALSE);
278
279     if (!option (OPTSSLV3))
280       SSL_OptionSet (sockdata->fd, SSL_ENABLE_SSL3, PR_FALSE);
281     
282     /* set the host we were attempting to connect to in order to verify
283      * the name in the certificate we get back.
284      */
285     if (SSL_SetURL (sockdata->fd, con->account.host))
286     {
287       mutt_nss_error ("SSL_SetURL");
288       break;
289     }
290
291     /* we don't need no stinking pin.  we don't authenticate ourself
292      * via SSL.
293      */
294     SSL_SetPKCS11PinArg (sockdata->fd, 0);
295     
296     sockdata->db = CERT_GetDefaultCertDB ();
297     
298     /* use the default supplied hook.  it takes an argument to our
299      * certificate database.  the manual lies, you can't really specify
300      * NULL for the callback to get the default!
301      */
302     SSL_AuthCertificateHook (sockdata->fd, SSL_AuthCertificate,
303                              sockdata->db);
304     /* set the callback to be used when SSL_AuthCertificate() fails.  this
305      * allows us to override and insert the cert back into the db
306      */
307     SSL_BadCertHook (sockdata->fd, mutt_nss_bad_cert, sockdata->db);
308     
309     if (PR_Connect (sockdata->fd, &addr, PR_INTERVAL_NO_TIMEOUT) ==
310         PR_FAILURE)
311     {
312       mutt_error (_("Unable to connect to host %s"), con->account.host);
313       break;
314     }
315     
316     /* store the extra info in the CONNECTION struct for later use. */
317     con->sockdata = sockdata;
318     
319     /* HACK.  some of the higher level calls in mutt_socket.c depend on this
320      * being >0 when we are in the connected state.  we just set this to
321      * an arbitrary value to avoid hitting that bug, since we neve have the
322      * real fd.
323      */
324     con->fd = 42;
325     
326     /* success */
327     return 0;
328   }
329   while (0);
330   
331   /* we get here when we had an oops.  clean up the mess. */
332
333   if (sockdata)
334   {
335     if (sockdata->fd)
336       PR_Close (sockdata->fd);
337     if (sockdata->db)
338       CERT_ClosePermCertDB (sockdata->db);
339     FREE (&sockdata);
340   }
341   return -1;
342 }
343
344 static int
345 mutt_nss_socket_close (CONNECTION * con)
346 {
347   mutt_nss_t *sockdata = (mutt_nss_t *) con->sockdata;
348
349   if (PR_Close (sockdata->fd) == PR_FAILURE)
350     return -1;
351
352   if (sockdata->db)
353     CERT_ClosePermCertDB (sockdata->db);
354   /* free up the memory we used for this connection specific to NSS. */
355   FREE (&con->sockdata);
356   return 0;
357 }
358
359 static int
360 mutt_nss_socket_read (CONNECTION* conn, char* buf, size_t len)
361 {
362   return PR_Read (((mutt_nss_t*) conn->sockdata)->fd, buf, len);
363 }
364
365 static int
366 mutt_nss_socket_write (CONNECTION * con, const char *buf, size_t count)
367 {
368   return PR_Write (((mutt_nss_t *) con->sockdata)->fd, buf, count);
369 }
370
371 /* initialize a new connection for use with NSS */
372 int
373 mutt_nss_socket_setup (CONNECTION * con)
374 {
375   if (mutt_nss_init ())
376     return -1;
377   con->open = mutt_nss_socket_open;
378   con->read = mutt_nss_socket_read;
379   con->write = mutt_nss_socket_write;
380   con->close = mutt_nss_socket_close;
381   return 0;
382 }