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