Extending test-client-custom-summary to try e_book_client_get_contacts_uids()
[platform/upstream/evolution-data-server.git] / camel / camel-tcp-stream-ssl.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  *  Authors: Jeffrey Stedfast <fejj@ximian.com>
4  *
5  *  Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of version 2 of the GNU Lesser General Public
9  * License as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  *
21  */
22
23 /* NOTE: This is the default implementation of CamelTcpStreamSSL,
24  * used when the Mozilla NSS libraries are used.
25  */
26
27 #ifdef HAVE_CONFIG_H
28 #include <config.h>
29 #endif
30
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <sys/stat.h>
36 #include <sys/types.h>
37
38 #include <nspr.h>
39 #include <prio.h>
40 #include <prerror.h>
41 #include <prerr.h>
42 #include <secerr.h>
43 #include <sslerr.h>
44 #include "nss.h"    /* Don't use <> here or it will include the system nss.h instead */
45 #include <ssl.h>
46 #include <cert.h>
47 #include <certdb.h>
48 #include <pk11func.h>
49
50 #include <glib/gi18n-lib.h>
51 #include <glib/gstdio.h>
52
53 #include "camel-certdb.h"
54 #include "camel-file-utils.h"
55 #include "camel-net-utils.h"
56 #include "camel-operation.h"
57 #include "camel-session.h"
58 #include "camel-stream-fs.h"
59 #include "camel-tcp-stream-ssl.h"
60
61 #ifdef G_OS_WIN32
62 #include <winsock2.h>
63 #include <ws2tcpip.h>
64 #endif
65
66 #define d(x)
67
68 #define IO_TIMEOUT (PR_TicksPerSecond() * 4 * 60)
69 #define CONNECT_TIMEOUT (PR_TicksPerSecond () * 4 * 60)
70
71 #define CAMEL_TCP_STREAM_SSL_GET_PRIVATE(obj) \
72         (G_TYPE_INSTANCE_GET_PRIVATE \
73         ((obj), CAMEL_TYPE_TCP_STREAM_SSL, CamelTcpStreamSSLPrivate))
74
75 struct _CamelTcpStreamSSLPrivate {
76         CamelSession *session;
77         gchar *expected_host;
78         gboolean ssl_mode;
79         CamelTcpStreamSSLFlags flags;
80 };
81
82 G_DEFINE_TYPE (CamelTcpStreamSSL, camel_tcp_stream_ssl, CAMEL_TYPE_TCP_STREAM_RAW)
83
84 static const gchar *
85 tcp_stream_ssl_get_cert_dir (void)
86 {
87         static gchar *cert_dir = NULL;
88
89         if (G_UNLIKELY (cert_dir == NULL)) {
90                 const gchar *data_dir;
91                 const gchar *home_dir;
92                 gchar *old_dir;
93
94                 home_dir = g_get_home_dir ();
95                 data_dir = g_get_user_data_dir ();
96
97                 cert_dir = g_build_filename (data_dir, "camel_certs", NULL);
98
99                 /* Move the old certificate directory if present. */
100                 old_dir = g_build_filename (home_dir, ".camel_certs", NULL);
101                 if (g_file_test (old_dir, G_FILE_TEST_IS_DIR))
102                         g_rename (old_dir, cert_dir);
103                 g_free (old_dir);
104
105                 g_mkdir_with_parents (cert_dir, 0700);
106         }
107
108         return cert_dir;
109 }
110
111 static void
112 tcp_stream_ssl_dispose (GObject *object)
113 {
114         CamelTcpStreamSSLPrivate *priv;
115
116         priv = CAMEL_TCP_STREAM_SSL_GET_PRIVATE (object);
117
118         if (priv->session != NULL) {
119                 g_object_unref (priv->session);
120                 priv->session = NULL;
121         }
122
123         /* Chain up to parent's dispose() method. */
124         G_OBJECT_CLASS (camel_tcp_stream_ssl_parent_class)->dispose (object);
125 }
126
127 static void
128 tcp_stream_ssl_finalize (GObject *object)
129 {
130         CamelTcpStreamSSLPrivate *priv;
131
132         priv = CAMEL_TCP_STREAM_SSL_GET_PRIVATE (object);
133
134         g_free (priv->expected_host);
135
136         /* Chain up to parent's finalize() method. */
137         G_OBJECT_CLASS (camel_tcp_stream_ssl_parent_class)->finalize (object);
138 }
139
140 #if 0
141 /* Since this is default implementation, let NSS handle it. */
142 static SECStatus
143 ssl_get_client_auth (gpointer data,
144                      PRFileDesc *sockfd,
145                      struct CERTDistNamesStr *caNames,
146                      struct CERTCertificateStr **pRetCert,
147                      struct SECKEYPrivateKeyStr **pRetKey)
148 {
149         SECStatus status = SECFailure;
150         SECKEYPrivateKey *privkey;
151         CERTCertificate *cert;
152         gpointer proto_win;
153
154         proto_win = SSL_RevealPinArg (sockfd);
155
156         if ((gchar *) data) {
157                 cert = PK11_FindCertFromNickname ((gchar *) data, proto_win);
158                 if (cert) {
159                         privKey = PK11_FindKeyByAnyCert (cert, proto_win);
160                         if (privkey) {
161                                 status = SECSuccess;
162                         } else {
163                                 CERT_DestroyCertificate (cert);
164                         }
165                 }
166         } else {
167                 /* no nickname given, automatically find the right cert */
168                 CERTCertNicknames *names;
169                 gint i;
170
171                 names = CERT_GetCertNicknames (
172                         CERT_GetDefaultCertDB (),
173                         SEC_CERT_NICKNAMES_USER,
174                         proto_win);
175
176                 if (names != NULL) {
177                         for (i = 0; i < names->numnicknames; i++) {
178                                 cert = PK11_FindCertFromNickname (
179                                         names->nicknames[i], proto_win);
180                                 if (!cert)
181                                         continue;
182
183                                 /* Only check unexpired certs */
184                                 if (CERT_CheckCertValidTimes (cert, PR_Now (), PR_FALSE) != secCertTimeValid) {
185                                         CERT_DestroyCertificate (cert);
186                                         continue;
187                                 }
188
189                                 status = NSS_CmpCertChainWCANames (cert, caNames);
190                                 if (status == SECSuccess) {
191                                         privkey = PK11_FindKeyByAnyCert (cert, proto_win);
192                                         if (privkey)
193                                                 break;
194
195                                         status = SECFailure;
196                                         break;
197                                 }
198
199                                 CERT_FreeNicknames (names);
200                         }
201                 }
202         }
203
204         if (status == SECSuccess) {
205                 *pRetCert = cert;
206                 *pRetKey  = privkey;
207         }
208
209         return status;
210 }
211 #endif
212
213 #if 0
214 /* Since this is the default NSS implementation, no need for us to use this. */
215 static SECStatus
216 ssl_auth_cert (gpointer data,
217                PRFileDesc *sockfd,
218                PRBool checksig,
219                PRBool is_server)
220 {
221         CERTCertificate *cert;
222         SECStatus status;
223         gpointer pinarg;
224         gchar *host;
225
226         cert = SSL_PeerCertificate (sockfd);
227         pinarg = SSL_RevealPinArg (sockfd);
228         status = CERT_VerifyCertNow (
229                 (CERTCertDBHandle *) data, cert,
230                 checksig, certUsageSSLClient, pinarg);
231
232         if (status != SECSuccess)
233                 return SECFailure;
234
235         /* Certificate is OK.  Since this is the client side of an SSL
236          * connection, we need to verify that the name field in the cert
237          * matches the desired hostname.  This is our defense against
238          * man-in-the-middle attacks.
239          */
240
241         /* SSL_RevealURL returns a hostname, not a URL. */
242         host = SSL_RevealURL (sockfd);
243
244         if (host && *host) {
245                 status = CERT_VerifyCertName (cert, host);
246         } else {
247                 PR_SetError (SSL_ERROR_BAD_CERT_DOMAIN, 0);
248                 status = SECFailure;
249         }
250
251         if (host)
252                 PR_Free (host);
253
254         return secStatus;
255 }
256 #endif
257
258 CamelCert *camel_certdb_nss_cert_get (CamelCertDB *certdb, CERTCertificate *cert, const gchar *hostname);
259 CamelCert *camel_certdb_nss_cert_convert (CamelCertDB *certdb, CERTCertificate *cert);
260 void camel_certdb_nss_cert_set (CamelCertDB *certdb, CamelCert *ccert, CERTCertificate *cert);
261
262 static gchar *
263 cert_fingerprint (CERTCertificate *cert)
264 {
265         GChecksum *checksum;
266         guint8 *digest;
267         gsize length;
268         guchar fingerprint[50], *f;
269         gint i;
270         const gchar tohex[16] = "0123456789abcdef";
271
272         length = g_checksum_type_get_length (G_CHECKSUM_MD5);
273         digest = g_alloca (length);
274
275         checksum = g_checksum_new (G_CHECKSUM_MD5);
276         g_checksum_update (checksum, cert->derCert.data, cert->derCert.len);
277         g_checksum_get_digest (checksum, digest, &length);
278         g_checksum_free (checksum);
279
280         for (i = 0,f = fingerprint; i < length; i++) {
281                 guint c = digest[i];
282
283                 *f++ = tohex[(c >> 4) & 0xf];
284                 *f++ = tohex[c & 0xf];
285 #ifndef G_OS_WIN32
286                 *f++ = ':';
287 #else
288                 /* The fingerprint is used as a file name, can't have
289                  * colons in file names. Use underscore instead.
290                  */
291                 *f++ = '_';
292 #endif
293         }
294
295         fingerprint[47] = 0;
296
297         return g_strdup ((gchar *) fingerprint);
298 }
299
300 /* lookup a cert uses fingerprint to index an on-disk file */
301 CamelCert *
302 camel_certdb_nss_cert_get (CamelCertDB *certdb,
303                            CERTCertificate *cert,
304                            const gchar *hostname)
305 {
306         gchar *fingerprint;
307         CamelCert *ccert;
308
309         fingerprint = cert_fingerprint (cert);
310
311         ccert = camel_certdb_get_host (certdb, hostname, fingerprint);
312         if (ccert == NULL) {
313                 g_free (fingerprint);
314                 return NULL;
315         }
316
317         if (ccert->rawcert == NULL) {
318                 GByteArray *array;
319                 gchar *filename;
320                 gchar *contents;
321                 gsize length;
322                 const gchar *cert_dir;
323                 GError *error = NULL;
324
325                 cert_dir = tcp_stream_ssl_get_cert_dir ();
326                 filename = g_build_filename (cert_dir, fingerprint, NULL);
327                 if (!g_file_get_contents (filename, &contents, &length, &error) ||
328                     error != NULL) {
329                         g_warning (
330                                 "Could not load cert %s: %s",
331                                 filename, error ? error->message : "Unknown error");
332                         g_clear_error (&error);
333
334                         /* failed to load the certificate, thus remove it from
335                          * the CertDB, thus it can be re-added and properly saved */
336                         camel_certdb_remove_host (certdb, hostname, fingerprint);
337                         camel_certdb_touch (certdb);
338                         g_free (fingerprint);
339                         g_free (filename);
340
341                         return ccert;
342                 }
343                 g_free (filename);
344
345                 array = g_byte_array_sized_new (length);
346                 g_byte_array_append (array, (guint8 *) contents, length);
347                 g_free (contents);
348
349                 ccert->rawcert = array;
350         }
351
352         g_free (fingerprint);
353         if (ccert->rawcert->len != cert->derCert.len
354             || memcmp (ccert->rawcert->data, cert->derCert.data, cert->derCert.len) != 0) {
355                 g_warning ("rawcert != derCer");
356                 camel_cert_set_trust (certdb, ccert, CAMEL_CERT_TRUST_UNKNOWN);
357                 camel_certdb_touch (certdb);
358         }
359
360         return ccert;
361 }
362
363 /* Create a CamelCert corresponding to the NSS cert, with unknown trust. */
364 CamelCert *
365 camel_certdb_nss_cert_convert (CamelCertDB *certdb,
366                                CERTCertificate *cert)
367 {
368         CamelCert *ccert;
369         gchar *fingerprint;
370
371         fingerprint = cert_fingerprint (cert);
372
373         ccert = camel_certdb_cert_new (certdb);
374         camel_cert_set_issuer (certdb, ccert, CERT_NameToAscii (&cert->issuer));
375         camel_cert_set_subject (certdb, ccert, CERT_NameToAscii (&cert->subject));
376         /* hostname is set in caller */
377         /*camel_cert_set_hostname(certdb, ccert, ssl->priv->expected_host);*/
378         camel_cert_set_fingerprint (certdb, ccert, fingerprint);
379         camel_cert_set_trust (certdb, ccert, CAMEL_CERT_TRUST_UNKNOWN);
380         g_free (fingerprint);
381
382         return ccert;
383 }
384
385 /* set the 'raw' cert (& save it) */
386 void
387 camel_certdb_nss_cert_set (CamelCertDB *certdb,
388                            CamelCert *ccert,
389                            CERTCertificate *cert)
390 {
391         gchar *filename, *fingerprint;
392         CamelStream *stream;
393         const gchar *cert_dir;
394
395         fingerprint = ccert->fingerprint;
396
397         if (ccert->rawcert == NULL)
398                 ccert->rawcert = g_byte_array_new ();
399
400         g_byte_array_set_size (ccert->rawcert, cert->derCert.len);
401         memcpy (ccert->rawcert->data, cert->derCert.data, cert->derCert.len);
402
403         cert_dir = tcp_stream_ssl_get_cert_dir ();
404         filename = g_build_filename (cert_dir, fingerprint, NULL);
405
406         stream = camel_stream_fs_new_with_name (
407                 filename, O_WRONLY | O_CREAT | O_TRUNC, 0600, NULL);
408         if (stream != NULL) {
409                 if (camel_stream_write (
410                         stream, (const gchar *) ccert->rawcert->data,
411                         ccert->rawcert->len, NULL, NULL) == -1) {
412                         g_warning (
413                                 "Could not save cert: %s: %s",
414                                 filename, g_strerror (errno));
415                         g_unlink (filename);
416                 }
417                 camel_stream_close (stream, NULL, NULL);
418                 g_object_unref (stream);
419         } else {
420                 g_warning (
421                         "Could not save cert: %s: %s",
422                         filename, g_strerror (errno));
423         }
424
425         g_free (filename);
426 }
427
428 #if 0
429 /* used by the mozilla-like code below */
430 static gchar *
431 get_nickname (CERTCertificate *cert)
432 {
433         gchar *server, *nick = NULL;
434         gint i;
435         PRBool status = PR_TRUE;
436
437         server = CERT_GetCommonName (&cert->subject);
438         if (server == NULL)
439                 return NULL;
440
441         for (i = 1; status == PR_TRUE; i++) {
442                 if (nick) {
443                         g_free (nick);
444                         nick = g_strdup_printf ("%s #%d", server, i);
445                 } else {
446                         nick = g_strdup (server);
447                 }
448                 status = SEC_CertNicknameConflict (server, &cert->derSubject, cert->dbhandle);
449         }
450
451         return nick;
452 }
453 #endif
454
455 static void
456 tcp_stream_cancelled (GCancellable *cancellable,
457                       PRThread *thread)
458 {
459         PR_Interrupt (thread);
460 }
461
462 static SECStatus
463 ssl_bad_cert (gpointer data,
464               PRFileDesc *sockfd)
465 {
466         gboolean accept;
467         CamelCertDB *certdb = NULL;
468         CamelCert *ccert = NULL;
469         gboolean ccert_is_new = FALSE;
470         gchar *prompt, *cert_str, *fingerprint;
471         CamelTcpStreamSSL *ssl;
472         CERTCertificate *cert;
473         SECStatus status = SECFailure;
474
475         g_return_val_if_fail (data != NULL, SECFailure);
476         g_return_val_if_fail (CAMEL_IS_TCP_STREAM_SSL (data), SECFailure);
477
478         ssl = data;
479
480         cert = SSL_PeerCertificate (sockfd);
481         if (cert == NULL)
482                 return SECFailure;
483
484         certdb = camel_certdb_get_default ();
485         ccert = camel_certdb_nss_cert_get (certdb, cert, ssl->priv->expected_host);
486         if (ccert == NULL) {
487                 ccert = camel_certdb_nss_cert_convert (certdb, cert);
488                 camel_cert_set_hostname (certdb, ccert, ssl->priv->expected_host);
489                 /* Don't put in the certdb yet.  Since we can only store one
490                  * entry per hostname, we'd rather not ruin any existing entry
491                  * for this hostname if the user rejects the new certificate. */
492                 ccert_is_new = TRUE;
493         }
494
495         if (ccert->trust == CAMEL_CERT_TRUST_UNKNOWN) {
496                 GSList *button_captions = NULL;
497                 gint button_id;
498
499                 status = CERT_VerifyCertNow (cert->dbhandle, cert, TRUE, certUsageSSLClient, NULL);
500                 fingerprint = cert_fingerprint (cert);
501                 cert_str = g_strdup_printf (_(
502                         "   Issuer:       %s\n"
503                         "   Subject:      %s\n"
504                         "   Fingerprint:  %s\n"
505                         "   Signature:    %s"),
506                         CERT_NameToAscii (&cert->issuer),
507                         CERT_NameToAscii (&cert->subject),
508                         fingerprint,
509                         status == SECSuccess ? _("GOOD") : _("BAD"));
510                 g_free (fingerprint);
511
512                 /* construct our user prompt */
513                 prompt = g_strdup_printf (
514                         _("SSL Certificate for '%s' is not trusted. "
515                         "Do you wish to accept it?\n\n"
516                         "Detailed information about the certificate:\n%s"),
517                         ssl->priv->expected_host, cert_str);
518                 g_free (cert_str);
519
520                 button_captions = g_slist_append (button_captions, _("_Reject"));
521                 button_captions = g_slist_append (button_captions, _("Accept _Temporarily"));
522                 button_captions = g_slist_append (button_captions, _("_Accept Permanently"));
523
524                 /* query the user to find out if we want to accept this certificate */
525                 button_id = camel_session_alert_user (ssl->priv->session, CAMEL_SESSION_ALERT_WARNING, prompt, button_captions);
526                 g_slist_free (button_captions);
527                 g_free (prompt);
528
529                 accept = button_id != 0;
530                 if (ccert_is_new) {
531                         camel_certdb_nss_cert_set (certdb, ccert, cert);
532                         camel_certdb_put (certdb, ccert);
533                 }
534
535                 switch (button_id) {
536                 case 0: /* Reject */
537                         camel_cert_set_trust (certdb, ccert, CAMEL_CERT_TRUST_NEVER);
538                         break;
539                 case 1: /* Accept temporarily */
540                         camel_cert_set_trust (certdb, ccert, CAMEL_CERT_TRUST_TEMPORARY);
541                         break;
542                 case 2: /* Accept permanently */
543                         camel_cert_set_trust (certdb, ccert, CAMEL_CERT_TRUST_FULLY);
544                         break;
545                 default: /* anything else means failure and will ask again */
546                         accept = FALSE;
547                         break;
548                 }
549                 camel_certdb_touch (certdb);
550         } else {
551                 accept = ccert->trust != CAMEL_CERT_TRUST_NEVER;
552         }
553
554         camel_certdb_cert_unref (certdb, ccert);
555         camel_certdb_save (certdb);
556         g_object_unref (certdb);
557
558         return accept ? SECSuccess : SECFailure;
559
560 #if 0
561         gint i, error;
562         CERTCertTrust trust;
563         SECItem *certs[1];
564         gint go = 1;
565         gchar *host, *nick;
566
567         error = PR_GetError ();
568
569         /* This code is basically what mozilla does - however it doesn't seem to work here
570          * very reliably :-/ */
571         while (go && status != SECSuccess) {
572                 gchar *prompt = NULL;
573
574                 printf ("looping, error '%d'\n", error);
575
576                 switch (error) {
577                 case SEC_ERROR_UNKNOWN_ISSUER:
578                 case SEC_ERROR_CA_CERT_INVALID:
579                 case SEC_ERROR_UNTRUSTED_ISSUER:
580                 case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
581                         /* add certificate */
582                         printf ("unknown issuer, adding ... \n");
583                         prompt = g_strdup_printf (_("Certificate problem: %s\nIssuer: %s"), cert->subjectName, cert->issuerName);
584
585                         if (camel_session_alert_user (ssl->priv->session, CAMEL_SESSION_ALERT_WARNING, prompt, TRUE)) {
586
587                                 nick = get_nickname (cert);
588                                 if (NULL == nick) {
589                                         g_free (prompt);
590                                         status = SECFailure;
591                                         break;
592                                 }
593
594                                 printf ("adding cert '%s'\n", nick);
595
596                                 if (!cert->trust) {
597                                         cert->trust = (CERTCertTrust *) PORT_ArenaZAlloc (cert->arena, sizeof (CERTCertTrust));
598                                         CERT_DecodeTrustString (cert->trust, "P");
599                                 }
600
601                                 certs[0] = &cert->derCert;
602                                 /*CERT_ImportCerts (cert->dbhandle, certUsageSSLServer, 1, certs, NULL, TRUE, FALSE, nick);*/
603                                 CERT_ImportCerts (cert->dbhandle, certUsageUserCertImport, 1, certs, NULL, TRUE, FALSE, nick);
604                                 g_free (nick);
605
606                                 printf (" cert type %08x\n", cert->nsCertType);
607
608                                 memset ((gpointer) &trust, 0, sizeof (trust));
609                                 if (CERT_GetCertTrust (cert, &trust) != SECSuccess) {
610                                         CERT_DecodeTrustString (&trust, "P");
611                                 }
612                                 trust.sslFlags |= CERTDB_VALID_PEER | CERTDB_TRUSTED;
613                                 if (CERT_ChangeCertTrust (cert->dbhandle, cert, &trust) != SECSuccess) {
614                                         printf ("couldn't change cert trust?\n");
615                                 }
616
617                                 /*status = SECSuccess;*/
618 #if 1
619                                 /* re-verify? */
620                                 status = CERT_VerifyCertNow (cert->dbhandle, cert, TRUE, certUsageSSLServer, NULL);
621                                 error = PR_GetError ();
622                                 printf ("re-verify status %d, error %d\n", status, error);
623 #endif
624
625                                 printf (" cert type %08x\n", cert->nsCertType);
626                         } else {
627                                 printf ("failed/cancelled\n");
628                                 go = 0;
629                         }
630
631                         break;
632                 case SSL_ERROR_BAD_CERT_DOMAIN:
633                         printf ("bad domain\n");
634
635                         prompt = g_strdup_printf (_("Bad certificate domain: %s\nIssuer: %s"), cert->subjectName, cert->issuerName);
636
637                         if (camel_session_alert_user (ssl->priv->session, CAMEL_SESSION_ALERT_WARNING, prompt, TRUE)) {
638                                 host = SSL_RevealURL (sockfd);
639                                 status = CERT_AddOKDomainName (cert, host);
640                                 printf ("add ok domain name : %s\n", status == SECFailure?"fail":"ok");
641                                 error = PR_GetError ();
642                                 if (status == SECFailure)
643                                         go = 0;
644                         } else {
645                                 go = 0;
646                         }
647
648                         break;
649
650                 case SEC_ERROR_EXPIRED_CERTIFICATE:
651                         printf ("expired\n");
652
653                         prompt = g_strdup_printf (_("Certificate expired: %s\nIssuer: %s"), cert->subjectName, cert->issuerName);
654
655                         if (camel_session_alert_user (ssl->priv->session, CAMEL_SESSION_ALERT_WARNING, prompt, TRUE)) {
656                                 cert->timeOK = PR_TRUE;
657                                 status = CERT_VerifyCertNow (cert->dbhandle, cert, TRUE, certUsageSSLClient, NULL);
658                                 error = PR_GetError ();
659                                 if (status == SECFailure)
660                                         go = 0;
661                         } else {
662                                 go = 0;
663                         }
664
665                         break;
666
667                 case SEC_ERROR_CRL_EXPIRED:
668                         printf ("crl expired\n");
669
670                         prompt = g_strdup_printf (_("Certificate revocation list expired: %s\nIssuer: %s"), cert->subjectName, cert->issuerName);
671
672                         if (camel_session_alert_user (ssl->priv->session, CAMEL_SESSION_ALERT_WARNING, prompt, TRUE)) {
673                                 host = SSL_RevealURL (sockfd);
674                                 status = CERT_AddOKDomainName (cert, host);
675                         }
676
677                         go = 0;
678                         break;
679
680                 default:
681                         printf ("generic error\n");
682                         go = 0;
683                         break;
684                 }
685
686                 g_free (prompt);
687         }
688
689         CERT_DestroyCertificate (cert);
690
691         return status;
692 #endif
693 }
694
695 static PRFileDesc *
696 enable_ssl (CamelTcpStreamSSL *ssl,
697             PRFileDesc *fd)
698 {
699         PRFileDesc *ssl_fd;
700
701         g_assert (fd != NULL);
702
703         ssl_fd = SSL_ImportFD (NULL, fd);
704         if (!ssl_fd)
705                 return NULL;
706
707         SSL_OptionSet (ssl_fd, SSL_SECURITY, PR_TRUE);
708
709         if (ssl->priv->flags & CAMEL_TCP_STREAM_SSL_ENABLE_SSL2) {
710                 SSL_OptionSet (ssl_fd, SSL_ENABLE_SSL2, PR_TRUE);
711                 SSL_OptionSet (ssl_fd, SSL_V2_COMPATIBLE_HELLO, PR_TRUE);
712         } else {
713                 SSL_OptionSet (ssl_fd, SSL_ENABLE_SSL2, PR_FALSE);
714                 SSL_OptionSet (ssl_fd, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
715         }
716
717         if (ssl->priv->flags & CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
718                 SSL_OptionSet (ssl_fd, SSL_ENABLE_SSL3, PR_TRUE);
719         else
720                 SSL_OptionSet (ssl_fd, SSL_ENABLE_SSL3, PR_FALSE);
721
722         if (ssl->priv->flags & CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
723                 SSL_OptionSet (ssl_fd, SSL_ENABLE_TLS, PR_TRUE);
724         else
725                 SSL_OptionSet (ssl_fd, SSL_ENABLE_TLS, PR_FALSE);
726
727         SSL_SetURL (ssl_fd, ssl->priv->expected_host);
728
729         /* NSS provides a default implementation for the SSL_GetClientAuthDataHook callback
730          * but does not enable it by default. It must be explicltly requested by the application.
731          * See: http://www.mozilla.org/projects/security/pki/nss/ref/ssl/sslfnc.html#1126622 */
732         SSL_GetClientAuthDataHook (ssl_fd, (SSLGetClientAuthData) &NSS_GetClientAuthData, NULL );
733
734         /* NSS provides _and_ installs a default implementation for the
735          * SSL_AuthCertificateHook callback so we _don't_ need to install one. */
736         SSL_BadCertHook (ssl_fd, ssl_bad_cert, ssl);
737
738         return ssl_fd;
739 }
740
741 static PRFileDesc *
742 enable_ssl_or_close_fd (CamelTcpStreamSSL *ssl,
743                         PRFileDesc *fd,
744                         GError **error)
745 {
746         PRFileDesc *ssl_fd;
747
748         ssl_fd = enable_ssl (ssl, fd);
749         if (ssl_fd == NULL) {
750                 gint errnosave;
751
752                 _set_errno_from_pr_error (PR_GetError ());
753                 errnosave = errno;
754                 PR_Shutdown (fd, PR_SHUTDOWN_BOTH);
755                 PR_Close (fd);
756                 errno = errnosave;
757                 _set_g_error_from_errno (error, FALSE);
758
759                 return NULL;
760         }
761
762         return ssl_fd;
763 }
764
765 static gboolean
766 rehandshake_ssl (PRFileDesc *fd,
767                  GCancellable *cancellable,
768                  GError **error)
769 {
770         SECStatus status = SECSuccess;
771         gulong cancel_id = 0;
772
773         if (g_cancellable_set_error_if_cancelled (cancellable, error))
774                 return FALSE;
775
776         if (G_IS_CANCELLABLE (cancellable))
777                 cancel_id = g_cancellable_connect (
778                         cancellable, G_CALLBACK (tcp_stream_cancelled),
779                         PR_GetCurrentThread (), (GDestroyNotify) NULL);
780
781         if (status == SECSuccess)
782                 status = SSL_ResetHandshake (fd, FALSE);
783
784         if (status == SECSuccess)
785                 status = SSL_ForceHandshake (fd);
786
787         if (cancel_id > 0)
788                 g_cancellable_disconnect (cancellable, cancel_id);
789
790         if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
791                 status = SECFailure;
792
793         } else if (status == SECFailure) {
794                 _set_error_from_pr_error (error);
795         }
796
797         return (status == SECSuccess);
798 }
799
800 static gint
801 tcp_stream_ssl_connect (CamelTcpStream *stream,
802                         const gchar *host,
803                         const gchar *service,
804                         gint fallback_port,
805                         GCancellable *cancellable,
806                         GError **error)
807 {
808         CamelTcpStreamSSL *ssl = CAMEL_TCP_STREAM_SSL (stream);
809         gint retval;
810
811         retval = CAMEL_TCP_STREAM_CLASS (
812                 camel_tcp_stream_ssl_parent_class)->connect (
813                 stream, host, service, fallback_port, cancellable, error);
814         if (retval != 0)
815                 return retval;
816
817         if (ssl->priv->ssl_mode) {
818                 PRFileDesc *fd;
819                 PRFileDesc *ssl_fd;
820
821                 d (g_print ("  enabling SSL\n"));
822
823                 fd = camel_tcp_stream_get_file_desc (stream);
824                 ssl_fd = enable_ssl_or_close_fd (ssl, fd, error);
825                 _camel_tcp_stream_raw_replace_file_desc (CAMEL_TCP_STREAM_RAW (stream), ssl_fd);
826
827                 if (!ssl_fd) {
828                         d (g_print ("  could not enable SSL\n"));
829                         return -1;
830                 } else {
831                         d (g_print ("  re-handshaking SSL\n"));
832
833                         if (!rehandshake_ssl (ssl_fd, cancellable, error)) {
834                                 d (g_print ("  failed\n"));
835                                 return -1;
836                         }
837                 }
838         }
839
840         return 0;
841 }
842
843 static void
844 camel_tcp_stream_ssl_class_init (CamelTcpStreamSSLClass *class)
845 {
846         GObjectClass *object_class;
847         CamelTcpStreamClass *tcp_stream_class;
848
849         g_type_class_add_private (class, sizeof (CamelTcpStreamSSLPrivate));
850
851         object_class = G_OBJECT_CLASS (class);
852         object_class->dispose = tcp_stream_ssl_dispose;
853         object_class->finalize = tcp_stream_ssl_finalize;
854
855         tcp_stream_class = CAMEL_TCP_STREAM_CLASS (class);
856         tcp_stream_class->connect = tcp_stream_ssl_connect;
857 }
858
859 static void
860 camel_tcp_stream_ssl_init (CamelTcpStreamSSL *stream)
861 {
862         stream->priv = CAMEL_TCP_STREAM_SSL_GET_PRIVATE (stream);
863 }
864
865 /**
866  * camel_tcp_stream_ssl_new:
867  * @session: an active #CamelSession object
868  * @expected_host: host that the stream is expected to connect with
869  * @flags: a bitwise combination of #CamelTcpStreamSSLFlags
870  *
871  * Since the SSL certificate authenticator may need to prompt the
872  * user, a #CamelSession is needed. @expected_host is needed as a
873  * protection against an MITM attack.
874  *
875  * Returns: a new #CamelTcpStreamSSL stream preset in SSL mode
876  **/
877 CamelStream *
878 camel_tcp_stream_ssl_new (CamelSession *session,
879                           const gchar *expected_host,
880                           CamelTcpStreamSSLFlags flags)
881 {
882         CamelTcpStreamSSL *stream;
883
884         g_assert (CAMEL_IS_SESSION (session));
885
886         stream = g_object_new (CAMEL_TYPE_TCP_STREAM_SSL, NULL);
887
888         stream->priv->session = g_object_ref (session);
889         stream->priv->expected_host = g_strdup (expected_host);
890         stream->priv->ssl_mode = TRUE;
891         stream->priv->flags = flags;
892
893         return CAMEL_STREAM (stream);
894 }
895
896 /**
897  * camel_tcp_stream_ssl_new_raw:
898  * @session: an active #CamelSession object
899  * @expected_host: host that the stream is expected to connect with
900  * @flags: a bitwise combination of #CamelTcpStreamSSLFlags
901  *
902  * Since the SSL certificate authenticator may need to prompt the
903  * user, a CamelSession is needed. @expected_host is needed as a
904  * protection against an MITM attack.
905  *
906  * Returns: a new #CamelTcpStreamSSL stream not yet toggled into SSL mode
907  **/
908 CamelStream *
909 camel_tcp_stream_ssl_new_raw (CamelSession *session,
910                               const gchar *expected_host,
911                               CamelTcpStreamSSLFlags flags)
912 {
913         CamelTcpStreamSSL *stream;
914
915         g_assert (CAMEL_IS_SESSION (session));
916
917         stream = g_object_new (CAMEL_TYPE_TCP_STREAM_SSL, NULL);
918
919         stream->priv->session = g_object_ref (session);
920         stream->priv->expected_host = g_strdup (expected_host);
921         stream->priv->ssl_mode = FALSE;
922         stream->priv->flags = flags;
923
924         return CAMEL_STREAM (stream);
925 }
926
927 /**
928  * camel_tcp_stream_ssl_enable_ssl:
929  * @ssl: a #CamelTcpStreamSSL object
930  * @cancellable: optional #GCancellable object, or %NULL
931  * @error: return location for a #GError, or %NULL
932  *
933  * Toggles an ssl-capable stream into ssl mode (if it isn't already).
934  *
935  * Returns: %0 on success or %-1 on fail
936  **/
937 gint
938 camel_tcp_stream_ssl_enable_ssl (CamelTcpStreamSSL *ssl,
939                                  GCancellable *cancellable,
940                                  GError **error)
941 {
942         PRFileDesc *fd, *ssl_fd;
943
944         g_return_val_if_fail (CAMEL_IS_TCP_STREAM_SSL (ssl), -1);
945
946         fd = camel_tcp_stream_get_file_desc (CAMEL_TCP_STREAM (ssl));
947
948         if (fd && !ssl->priv->ssl_mode) {
949                 if (!(ssl_fd = enable_ssl (ssl, fd))) {
950                         _set_error_from_pr_error (error);
951                         return -1;
952                 }
953
954                 _camel_tcp_stream_raw_replace_file_desc (CAMEL_TCP_STREAM_RAW (ssl), ssl_fd);
955                 ssl->priv->ssl_mode = TRUE;
956
957                 if (!rehandshake_ssl (ssl_fd, cancellable, error))
958                         return -1;
959         }
960
961         ssl->priv->ssl_mode = TRUE;
962
963         return 0;
964 }