1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3
* Authors: Jeffrey Stedfast <fejj@ximian.com>
5
* Copyright 2001 Ximian, Inc. (www.ximian.com)
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.
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.
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., 59 Temple Place - Suite 330,
19
* Boston, MA 02111-1307, USA.
23
/* NOTE: This is the default implementation of CamelTcpStreamSSL,
24
* used when the Mozilla NSS libraries are used. If you configured
25
* OpenSSL support instead, then this file won't be compiled and
26
* the CamelTcpStreamSSL implementation in camel-tcp-stream-openssl.c
27
* will be used instead.
41
#include <sys/types.h>
49
#include "nss.h" /* Don't use <> here or it will include the system nss.h instead */
56
#include <glib/gi18n-lib.h>
57
#include <glib/gstdio.h>
59
#include <libedataserver/md5-utils.h>
61
#include "camel-certdb.h"
62
#include "camel-file-utils.h"
63
#include "camel-operation.h"
64
#include "camel-private.h"
65
#include "camel-session.h"
66
#include "camel-stream-fs.h"
67
#include "camel-tcp-stream-ssl.h"
69
static CamelTcpStreamClass *parent_class = NULL;
71
/* Returns the class for a CamelTcpStreamSSL */
72
#define CTSS_CLASS(so) CAMEL_TCP_STREAM_SSL_CLASS (CAMEL_OBJECT_GET_CLASS (so))
74
static ssize_t stream_read (CamelStream *stream, char *buffer, size_t n);
75
static ssize_t stream_write (CamelStream *stream, const char *buffer, size_t n);
76
static int stream_flush (CamelStream *stream);
77
static int stream_close (CamelStream *stream);
79
static PRFileDesc *enable_ssl (CamelTcpStreamSSL *ssl, PRFileDesc *fd);
81
static int stream_connect (CamelTcpStream *stream, struct addrinfo *host);
82
static int stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data);
83
static int stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data);
84
static struct sockaddr *stream_get_local_address (CamelTcpStream *stream, socklen_t *len);
85
static struct sockaddr *stream_get_remote_address (CamelTcpStream *stream, socklen_t *len);
86
static ssize_t stream_read_nb (CamelTcpStream *stream, char *buffer, size_t n);
87
static ssize_t stream_read_idle (CamelStream *stream, char *buffer, size_t n);
89
struct _CamelTcpStreamSSLPrivate {
92
struct _CamelSession *session;
97
CamelService *service;
101
camel_tcp_stream_ssl_class_init (CamelTcpStreamSSLClass *camel_tcp_stream_ssl_class)
103
CamelTcpStreamClass *camel_tcp_stream_class =
104
CAMEL_TCP_STREAM_CLASS (camel_tcp_stream_ssl_class);
105
CamelStreamClass *camel_stream_class =
106
CAMEL_STREAM_CLASS (camel_tcp_stream_ssl_class);
108
parent_class = CAMEL_TCP_STREAM_CLASS (camel_type_get_global_classfuncs (camel_tcp_stream_get_type ()));
110
/* virtual method overload */
111
camel_stream_class->read_idle = stream_read_idle;
113
camel_stream_class->read = stream_read;
114
camel_stream_class->write = stream_write;
115
camel_stream_class->flush = stream_flush;
116
camel_stream_class->close = stream_close;
118
camel_tcp_stream_class->read_nb = stream_read_nb;
119
camel_tcp_stream_class->connect = stream_connect;
120
camel_tcp_stream_class->getsockopt = stream_getsockopt;
121
camel_tcp_stream_class->setsockopt = stream_setsockopt;
122
camel_tcp_stream_class->get_local_address = stream_get_local_address;
123
camel_tcp_stream_class->get_remote_address = stream_get_remote_address;
127
camel_tcp_stream_ssl_init (gpointer object, gpointer klass)
129
CamelTcpStreamSSL *stream = CAMEL_TCP_STREAM_SSL (object);
131
stream->priv = g_new0 (struct _CamelTcpStreamSSLPrivate, 1);
135
camel_tcp_stream_ssl_finalize (CamelObject *object)
137
CamelTcpStreamSSL *stream = CAMEL_TCP_STREAM_SSL (object);
139
if (stream->priv->sockfd != NULL)
140
PR_Close (stream->priv->sockfd);
142
if (stream->priv->session)
143
camel_object_unref(stream->priv->session);
145
g_free (stream->priv->expected_host);
147
g_free (stream->priv);
152
camel_tcp_stream_ssl_get_type (void)
154
static CamelType type = CAMEL_INVALID_TYPE;
156
if (type == CAMEL_INVALID_TYPE) {
157
type = camel_type_register (camel_tcp_stream_get_type (),
159
sizeof (CamelTcpStreamSSL),
160
sizeof (CamelTcpStreamSSLClass),
161
(CamelObjectClassInitFunc) camel_tcp_stream_ssl_class_init,
163
(CamelObjectInitFunc) camel_tcp_stream_ssl_init,
164
(CamelObjectFinalizeFunc) camel_tcp_stream_ssl_finalize);
172
* camel_tcp_stream_ssl_new:
173
* @session: an active #CamelSession object
174
* @expected_host: host that the stream is expected to connect with
175
* @flags: a bitwise combination of any of
176
* #CAMEL_TCP_STREAM_SSL_ENABLE_SSL2,
177
* #CAMEL_TCP_STREAM_SSL_ENABLE_SSL3 or
178
* #CAMEL_TCP_STREAM_SSL_ENABLE_TLS
180
* Since the SSL certificate authenticator may need to prompt the
181
* user, a #CamelSession is needed. @expected_host is needed as a
182
* protection against an MITM attack.
184
* Returns a new #CamelTcpStreamSSL stream preset in SSL mode
187
camel_tcp_stream_ssl_new (CamelService *service, const char *expected_host, guint32 flags)
189
CamelTcpStreamSSL *stream;
191
g_assert(CAMEL_IS_SESSION(service->session));
193
stream = CAMEL_TCP_STREAM_SSL (camel_object_new (camel_tcp_stream_ssl_get_type ()));
195
stream->priv->session = service->session;
196
camel_object_ref(service->session);
197
stream->priv->expected_host = g_strdup (expected_host);
198
stream->priv->ssl_mode = TRUE;
199
stream->priv->flags = flags;
200
stream->priv->service = service;
202
return CAMEL_STREAM (stream);
207
* camel_tcp_stream_ssl_new_raw:
208
* @service: an active #CamelService object
209
* @expected_host: host that the stream is expected to connect with
210
* @flags: a bitwise combination of any of
211
* #CAMEL_TCP_STREAM_SSL_ENABLE_SSL2,
212
* #CAMEL_TCP_STREAM_SSL_ENABLE_SSL3 or
213
* #CAMEL_TCP_STREAM_SSL_ENABLE_TLS
215
* Since the SSL certificate authenticator may need to prompt the
216
* user, a CamelSession is needed. @expected_host is needed as a
217
* protection against an MITM attack.
219
* Returns a new #CamelTcpStreamSSL stream not yet toggled into SSL mode
222
camel_tcp_stream_ssl_new_raw (CamelService *service, const char *expected_host, guint32 flags)
224
CamelTcpStreamSSL *stream;
226
g_assert(CAMEL_IS_SESSION(service->session));
228
stream = CAMEL_TCP_STREAM_SSL (camel_object_new (camel_tcp_stream_ssl_get_type ()));
230
stream->priv->session = service->session;
231
camel_object_ref(service->session);
232
stream->priv->expected_host = g_strdup (expected_host);
233
stream->priv->ssl_mode = FALSE;
234
stream->priv->flags = flags;
235
stream->priv->service = service;
237
return CAMEL_STREAM (stream);
244
/* FIXME: this should handle more. */
246
case PR_INVALID_ARGUMENT_ERROR:
249
case PR_PENDING_INTERRUPT_ERROR:
252
case PR_IO_PENDING_ERROR:
256
case PR_WOULD_BLOCK_ERROR:
261
case PR_IN_PROGRESS_ERROR:
266
case PR_ALREADY_INITIATED_ERROR:
271
case PR_NETWORK_UNREACHABLE_ERROR:
272
errno = EHOSTUNREACH;
276
case PR_CONNECT_REFUSED_ERROR:
277
errno = ECONNREFUSED;
281
case PR_CONNECT_TIMEOUT_ERROR:
282
case PR_IO_TIMEOUT_ERROR:
287
case PR_NOT_CONNECTED_ERROR:
292
case PR_CONNECT_RESET_ERROR:
305
* camel_tcp_stream_ssl_enable_ssl:
306
* @ssl: a #CamelTcpStreamSSL object
308
* Toggles an ssl-capable stream into ssl mode (if it isn't already).
310
* Returns %0 on success or %-1 on fail
313
camel_tcp_stream_ssl_enable_ssl (CamelTcpStreamSSL *ssl)
317
PRSocketOptionData sockopts;
319
/* get O_NONBLOCK options */
320
sockopts.option = PR_SockOpt_Nonblocking;
321
PR_GetSocketOption (ssl->priv->sockfd, &sockopts);
322
sockopts.option = PR_SockOpt_Nonblocking;
323
nonblock = sockopts.value.non_blocking;
324
sockopts.value.non_blocking = FALSE;
325
PR_SetSocketOption (ssl->priv->sockfd, &sockopts);
327
g_return_val_if_fail (CAMEL_IS_TCP_STREAM_SSL (ssl), -1);
329
if (ssl->priv->sockfd && !ssl->priv->ssl_mode)
332
if (!(fd = enable_ssl (ssl, NULL)))
334
set_errno (PR_GetError ());
338
ssl->priv->sockfd = fd;
340
if (SSL_ResetHandshake (fd, FALSE) == SECFailure)
342
set_errno (PR_GetError ());
346
if (SSL_ForceHandshake (fd) == SECFailure)
348
set_errno (PR_GetError ());
353
/* restore O_NONBLOCK options */
354
sockopts.option = PR_SockOpt_Nonblocking;
355
sockopts.value.non_blocking = nonblock;
356
PR_SetSocketOption (ssl->priv->sockfd, &sockopts);
358
ssl->priv->ssl_mode = TRUE;
365
stream_read_nb (CamelTcpStream *stream, char *buffer, size_t n)
367
CamelTcpStreamSSL *tcp_stream_ssl = CAMEL_TCP_STREAM_SSL (stream);
369
PRSocketOptionData sockopts;
370
PRPollDesc pollfds[2];
373
/* get O_NONBLOCK options */
374
sockopts.option = PR_SockOpt_Nonblocking;
375
PR_GetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
376
sockopts.option = PR_SockOpt_Nonblocking;
377
nonblock = sockopts.value.non_blocking;
378
sockopts.value.non_blocking = TRUE;
379
PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
381
pollfds[0].fd = tcp_stream_ssl->priv->sockfd;
382
pollfds[0].in_flags = PR_POLL_READ;
387
pollfds[0].out_flags = 0;
388
pollfds[1].out_flags = 0;
391
res = PR_Poll(pollfds, 1, NONBLOCKING_READ_TIMEOUT);
393
set_errno(PR_GetError());
403
if (PR_Available (tcp_stream_ssl->priv->sockfd) != 0)
404
nread = PR_Read (tcp_stream_ssl->priv->sockfd, buffer, n);
406
set_errno (PR_GetError ());
407
} while (0 && (nread == -1 && PR_GetError () == PR_PENDING_INTERRUPT_ERROR));
409
} while (0 && (nread == -1 && (PR_GetError () == PR_PENDING_INTERRUPT_ERROR ||
410
PR_GetError () == PR_IO_PENDING_ERROR ||
411
PR_GetError () == PR_WOULD_BLOCK_ERROR)));
413
/* restore O_NONBLOCK options */
414
sockopts.option = PR_SockOpt_Nonblocking;
415
sockopts.value.non_blocking = nonblock;
416
PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
422
stream_read (CamelStream *stream, char *buffer, size_t n)
424
CamelTcpStreamSSL *tcp_stream_ssl = CAMEL_TCP_STREAM_SSL (stream);
425
PRFileDesc *cancel_fd;
428
if (camel_operation_cancel_check (NULL)) {
433
cancel_fd = camel_operation_cancel_prfd (NULL);
434
if (cancel_fd == NULL) {
436
PRSocketOptionData sockopts;
437
PRPollDesc pollfds[1];
441
/* get O_NONBLOCK options */
442
sockopts.option = PR_SockOpt_Nonblocking;
443
PR_GetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
444
sockopts.option = PR_SockOpt_Nonblocking;
445
nonblock = sockopts.value.non_blocking;
446
sockopts.value.non_blocking = TRUE;
447
PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
449
pollfds[0].fd = tcp_stream_ssl->priv->sockfd;
450
pollfds[0].in_flags = PR_POLL_READ;
455
pollfds[0].out_flags = 0;
458
res = PR_Poll(pollfds, 1, PR_TicksPerSecond () * BLOCKING_READ_TIMEOUT);
461
set_errno(PR_GetError());
470
nread = PR_Read (tcp_stream_ssl->priv->sockfd, buffer, n);
472
set_errno (PR_GetError ());
473
} while (nread == -1 && PR_GetError () == PR_PENDING_INTERRUPT_ERROR);
475
} while (nread == -1 && (PR_GetError () == PR_PENDING_INTERRUPT_ERROR ||
476
PR_GetError () == PR_IO_PENDING_ERROR ||
477
PR_GetError () == PR_WOULD_BLOCK_ERROR));
479
/* restore O_NONBLOCK options */
481
sockopts.option = PR_SockOpt_Nonblocking;
482
sockopts.value.non_blocking = nonblock;
483
PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
487
PRSocketOptionData sockopts;
488
PRPollDesc pollfds[2];
492
/* get O_NONBLOCK options */
493
sockopts.option = PR_SockOpt_Nonblocking;
494
PR_GetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
495
sockopts.option = PR_SockOpt_Nonblocking;
496
nonblock = sockopts.value.non_blocking;
497
sockopts.value.non_blocking = TRUE;
498
PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
500
pollfds[0].fd = tcp_stream_ssl->priv->sockfd;
501
pollfds[0].in_flags = PR_POLL_READ;
502
pollfds[1].fd = cancel_fd;
503
pollfds[1].in_flags = PR_POLL_READ;
508
pollfds[0].out_flags = 0;
509
pollfds[1].out_flags = 0;
512
res = PR_Poll(pollfds, 2, PR_TicksPerSecond () * BLOCKING_READ_TIMEOUT);
515
set_errno(PR_GetError());
522
} else if (pollfds[1].out_flags == PR_POLL_READ) {
527
nread = PR_Read (tcp_stream_ssl->priv->sockfd, buffer, n);
529
set_errno (PR_GetError ());
530
} while (nread == -1 && PR_GetError () == PR_PENDING_INTERRUPT_ERROR);
532
} while (nread == -1 && (PR_GetError () == PR_PENDING_INTERRUPT_ERROR ||
533
PR_GetError () == PR_IO_PENDING_ERROR ||
534
PR_GetError () == PR_WOULD_BLOCK_ERROR));
536
/* restore O_NONBLOCK options */
539
sockopts.option = PR_SockOpt_Nonblocking;
540
sockopts.value.non_blocking = nonblock;
541
PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
551
stream_read_idle (CamelStream *stream, char *buffer, size_t n)
553
CamelTcpStreamSSL *tcp_stream_ssl = CAMEL_TCP_STREAM_SSL (stream);
554
PRFileDesc *cancel_fd;
557
if (camel_operation_cancel_check (NULL)) {
562
cancel_fd = camel_operation_cancel_prfd (NULL);
563
if (cancel_fd == NULL) {
565
PRSocketOptionData sockopts;
566
PRPollDesc pollfds[1];
570
/* get O_NONBLOCK options */
571
sockopts.option = PR_SockOpt_Nonblocking;
572
PR_GetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
573
sockopts.option = PR_SockOpt_Nonblocking;
574
nonblock = sockopts.value.non_blocking;
575
sockopts.value.non_blocking = TRUE;
576
PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
578
pollfds[0].fd = tcp_stream_ssl->priv->sockfd;
579
pollfds[0].in_flags = PR_POLL_READ;
584
pollfds[0].out_flags = 0;
587
res = PR_Poll(pollfds, 1, PR_TicksPerSecond () * IDLE_READ_TIMEOUT);
590
set_errno(PR_GetError());
599
nread = PR_Read (tcp_stream_ssl->priv->sockfd, buffer, n);
601
set_errno (PR_GetError ());
602
} while (nread == -1 && PR_GetError () == PR_PENDING_INTERRUPT_ERROR);
604
} while (nread == -1 && (PR_GetError () == PR_PENDING_INTERRUPT_ERROR ||
605
PR_GetError () == PR_IO_PENDING_ERROR ||
606
PR_GetError () == PR_WOULD_BLOCK_ERROR));
608
/* restore O_NONBLOCK options */
610
sockopts.option = PR_SockOpt_Nonblocking;
611
sockopts.value.non_blocking = nonblock;
612
PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
617
PRSocketOptionData sockopts;
618
PRPollDesc pollfds[2];
622
/* get O_NONBLOCK options */
623
sockopts.option = PR_SockOpt_Nonblocking;
624
PR_GetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
625
sockopts.option = PR_SockOpt_Nonblocking;
626
nonblock = sockopts.value.non_blocking;
627
sockopts.value.non_blocking = TRUE;
628
PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
630
pollfds[0].fd = tcp_stream_ssl->priv->sockfd;
631
pollfds[0].in_flags = PR_POLL_READ;
632
pollfds[1].fd = cancel_fd;
633
pollfds[1].in_flags = PR_POLL_READ;
638
pollfds[0].out_flags = 0;
639
pollfds[1].out_flags = 0;
642
res = PR_Poll(pollfds, 2, PR_TicksPerSecond () * IDLE_READ_TIMEOUT);
645
set_errno(PR_GetError());
652
} else if (pollfds[1].out_flags == PR_POLL_READ) {
657
nread = PR_Read (tcp_stream_ssl->priv->sockfd, buffer, n);
659
set_errno (PR_GetError ());
660
} while (nread == -1 && PR_GetError () == PR_PENDING_INTERRUPT_ERROR);
662
} while (nread == -1 && (PR_GetError () == PR_PENDING_INTERRUPT_ERROR ||
663
PR_GetError () == PR_IO_PENDING_ERROR ||
664
PR_GetError () == PR_WOULD_BLOCK_ERROR));
666
/* restore O_NONBLOCK options */
669
sockopts.option = PR_SockOpt_Nonblocking;
670
sockopts.value.non_blocking = nonblock;
671
PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
680
stream_write (CamelStream *stream, const char *buffer, size_t n)
682
CamelTcpStreamSSL *tcp_stream_ssl = CAMEL_TCP_STREAM_SSL (stream);
683
ssize_t w, written = 0;
684
PRFileDesc *cancel_fd;
686
if (camel_operation_cancel_check (NULL)) {
691
cancel_fd = camel_operation_cancel_prfd (NULL);
692
if (cancel_fd == NULL) {
694
PRSocketOptionData sockopts;
695
PRPollDesc pollfds[1];
699
/* get O_NONBLOCK options */
700
sockopts.option = PR_SockOpt_Nonblocking;
701
PR_GetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
702
sockopts.option = PR_SockOpt_Nonblocking;
703
nonblock = sockopts.value.non_blocking;
704
sockopts.value.non_blocking = TRUE;
705
PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
707
pollfds[0].fd = tcp_stream_ssl->priv->sockfd;
708
pollfds[0].in_flags = PR_POLL_WRITE;
713
pollfds[0].out_flags = 0;
716
res = PR_Poll (pollfds, 1, PR_TicksPerSecond () * BLOCKING_WRITE_TIMEOUT);
718
set_errno(PR_GetError());
719
if (PR_GetError () == PR_PENDING_INTERRUPT_ERROR)
721
} else if (res == 0) {
729
w = PR_Write (tcp_stream_ssl->priv->sockfd, buffer + written, n - written);
731
set_errno (PR_GetError ());
732
} while (w == -1 && PR_GetError () == PR_PENDING_INTERRUPT_ERROR);
735
if (PR_GetError () == PR_IO_PENDING_ERROR ||
736
PR_GetError () == PR_WOULD_BLOCK_ERROR)
741
} while (w != -1 && written < n);
743
/* restore O_NONBLOCK options */
745
sockopts.option = PR_SockOpt_Nonblocking;
746
sockopts.value.non_blocking = nonblock;
747
PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
751
PRSocketOptionData sockopts;
752
PRPollDesc pollfds[2];
756
/* get O_NONBLOCK options */
757
sockopts.option = PR_SockOpt_Nonblocking;
758
PR_GetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
759
sockopts.option = PR_SockOpt_Nonblocking;
760
nonblock = sockopts.value.non_blocking;
761
sockopts.value.non_blocking = TRUE;
762
PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
764
pollfds[0].fd = tcp_stream_ssl->priv->sockfd;
765
pollfds[0].in_flags = PR_POLL_WRITE;
766
pollfds[1].fd = cancel_fd;
767
pollfds[1].in_flags = PR_POLL_READ;
772
pollfds[0].out_flags = 0;
773
pollfds[1].out_flags = 0;
776
res = PR_Poll (pollfds, 2, PR_TicksPerSecond () * BLOCKING_WRITE_TIMEOUT);
778
set_errno(PR_GetError());
779
if (PR_GetError () == PR_PENDING_INTERRUPT_ERROR)
781
} else if (res == 0) {
787
} else if (pollfds[1].out_flags == PR_POLL_READ) {
791
w = PR_Write (tcp_stream_ssl->priv->sockfd, buffer + written, n - written);
793
set_errno (PR_GetError ());
794
} while (w == -1 && PR_GetError () == PR_PENDING_INTERRUPT_ERROR);
797
if (PR_GetError () == PR_IO_PENDING_ERROR ||
798
PR_GetError () == PR_WOULD_BLOCK_ERROR)
803
} while (w != -1 && written < n);
805
/* restore O_NONBLOCK options */
807
sockopts.option = PR_SockOpt_Nonblocking;
808
sockopts.value.non_blocking = nonblock;
809
PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
820
stream_flush (CamelStream *stream)
822
/*return PR_Sync (((CamelTcpStreamSSL *)stream)->priv->sockfd);*/
827
stream_close (CamelStream *stream)
829
if (((CamelTcpStreamSSL *)stream)->priv->sockfd == NULL) {
834
if (PR_Close (((CamelTcpStreamSSL *)stream)->priv->sockfd) == PR_FAILURE)
837
((CamelTcpStreamSSL *)stream)->priv->sockfd = NULL;
843
/* Since this is default implementation, let NSS handle it. */
845
ssl_get_client_auth (void *data, PRFileDesc *sockfd,
846
struct CERTDistNamesStr *caNames,
847
struct CERTCertificateStr **pRetCert,
848
struct SECKEYPrivateKeyStr **pRetKey)
850
SECStatus status = SECFailure;
851
SECKEYPrivateKey *privkey;
852
CERTCertificate *cert;
855
proto_win = SSL_RevealPinArg (sockfd);
858
cert = PK11_FindCertFromNickname ((char *) data, proto_win);
860
privKey = PK11_FindKeyByAnyCert (cert, proto_win);
864
CERT_DestroyCertificate (cert);
868
/* no nickname given, automatically find the right cert */
869
CERTCertNicknames *names;
872
names = CERT_GetCertNicknames (CERT_GetDefaultCertDB (),
873
SEC_CERT_NICKNAMES_USER,
877
for (i = 0; i < names->numnicknames; i++) {
878
cert = PK11_FindCertFromNickname (names->nicknames[i],
883
/* Only check unexpired certs */
884
if (CERT_CheckCertValidTimes (cert, PR_Now (), PR_FALSE) != secCertTimeValid) {
885
CERT_DestroyCertificate (cert);
889
status = NSS_CmpCertChainWCANames (cert, caNames);
890
if (status == SECSuccess) {
891
privkey = PK11_FindKeyByAnyCert (cert, proto_win);
899
CERT_FreeNicknames (names);
904
if (status == SECSuccess) {
914
/* Since this is the default NSS implementation, no need for us to use this. */
916
ssl_auth_cert (void *data, PRFileDesc *sockfd, PRBool checksig, PRBool is_server)
918
CERTCertificate *cert;
923
cert = SSL_PeerCertificate (sockfd);
924
pinarg = SSL_RevealPinArg (sockfd);
925
status = CERT_VerifyCertNow ((CERTCertDBHandle *)data, cert,
926
checksig, certUsageSSLClient, pinarg);
928
if (status != SECSuccess)
931
/* Certificate is OK. Since this is the client side of an SSL
932
* connection, we need to verify that the name field in the cert
933
* matches the desired hostname. This is our defense against
934
* man-in-the-middle attacks.
937
/* SSL_RevealURL returns a hostname, not a URL. */
938
host = SSL_RevealURL (sockfd);
941
status = CERT_VerifyCertName (cert, host);
943
PR_SetError (SSL_ERROR_BAD_CERT_DOMAIN, 0);
954
CamelCert *camel_certdb_nss_cert_get(CamelCertDB *certdb, CERTCertificate *cert);
955
CamelCert *camel_certdb_nss_cert_add(CamelCertDB *certdb, CERTCertificate *cert);
956
void camel_certdb_nss_cert_set(CamelCertDB *certdb, CamelCert *ccert, CERTCertificate *cert);
959
cert_fingerprint(CERTCertificate *cert)
961
unsigned char md5sum[16], fingerprint[50], *f;
963
const char tohex[16] = "0123456789abcdef";
965
md5_get_digest ((const gchar *) cert->derCert.data, cert->derCert.len, md5sum);
966
for (i=0,f = fingerprint; i<16; i++) {
967
unsigned int c = md5sum[i];
969
*f++ = tohex[(c >> 4) & 0xf];
970
*f++ = tohex[c & 0xf];
974
/* The fingerprint is used as a file name, can't have
975
* colons in file names. Use underscore instead.
983
return g_strdup((gchar*) fingerprint);
986
/* lookup a cert uses fingerprint to index an on-disk file */
988
camel_certdb_nss_cert_get(CamelCertDB *certdb, CERTCertificate *cert)
990
char *fingerprint, *path;
997
fingerprint = cert_fingerprint (cert);
998
ccert = camel_certdb_get_cert (certdb, fingerprint);
1000
g_free (fingerprint);
1004
if (ccert->rawcert == NULL) {
1006
path = g_strdup_printf ("%s/.camel_certs/%s", getenv ("HOME"), fingerprint);
1008
path = g_build_filename (g_get_home_dir (), ".camel_certs", fingerprint, NULL);
1010
if (g_stat (path, &st) == -1
1011
|| (fd = g_open (path, O_RDONLY | O_BINARY, 0)) == -1) {
1012
g_warning ("could not load cert %s: %s", path, strerror (errno));
1013
g_free (fingerprint);
1015
camel_cert_set_trust (certdb, ccert, CAMEL_CERT_TRUST_UNKNOWN);
1016
camel_certdb_touch (certdb);
1022
ccert->rawcert = g_byte_array_new ();
1023
g_byte_array_set_size (ccert->rawcert, st.st_size);
1028
n = read (fd, ccert->rawcert->data + nread, st.st_size - nread);
1029
} while (n == -1 && errno == EINTR);
1033
} while (nread < st.st_size && n != -1);
1037
if (nread != st.st_size) {
1038
/* g_warning ("cert size read truncated %s: %u != %ld", path, nread, st.st_size); */
1039
g_byte_array_free(ccert->rawcert, TRUE);
1040
ccert->rawcert = NULL;
1041
g_free(fingerprint);
1042
camel_cert_set_trust(certdb, ccert, CAMEL_CERT_TRUST_UNKNOWN);
1043
camel_certdb_touch(certdb);
1049
g_free(fingerprint);
1050
if (ccert->rawcert->len != cert->derCert.len
1051
|| memcmp(ccert->rawcert->data, cert->derCert.data, cert->derCert.len) != 0) {
1052
g_warning("rawcert != derCer");
1053
camel_cert_set_trust(certdb, ccert, CAMEL_CERT_TRUST_UNKNOWN);
1054
camel_certdb_touch(certdb);
1060
/* add a cert to the certdb */
1062
camel_certdb_nss_cert_add(CamelCertDB *certdb, CERTCertificate *cert)
1067
fingerprint = cert_fingerprint(cert);
1069
ccert = camel_certdb_cert_new(certdb);
1070
camel_cert_set_issuer(certdb, ccert, CERT_NameToAscii(&cert->issuer));
1071
camel_cert_set_subject(certdb, ccert, CERT_NameToAscii(&cert->subject));
1072
/* hostname is set in caller */
1073
/*camel_cert_set_hostname(certdb, ccert, ssl->priv->expected_host);*/
1074
camel_cert_set_fingerprint(certdb, ccert, fingerprint);
1075
camel_cert_set_trust(certdb, ccert, CAMEL_CERT_TRUST_UNKNOWN);
1076
g_free(fingerprint);
1078
camel_certdb_nss_cert_set(certdb, ccert, cert);
1080
camel_certdb_add(certdb, ccert);
1085
/* set the 'raw' cert (& save it) */
1087
camel_certdb_nss_cert_set(CamelCertDB *certdb, CamelCert *ccert, CERTCertificate *cert)
1089
char *dir, *path, *fingerprint;
1090
CamelStream *stream;
1093
fingerprint = ccert->fingerprint;
1095
if (ccert->rawcert == NULL)
1096
ccert->rawcert = g_byte_array_new ();
1098
g_byte_array_set_size (ccert->rawcert, cert->derCert.len);
1099
memcpy (ccert->rawcert->data, cert->derCert.data, cert->derCert.len);
1102
dir = g_strdup_printf ("%s/.camel_certs", getenv ("HOME"));
1104
dir = g_build_filename (g_get_home_dir (), ".camel_certs", NULL);
1106
if (g_stat (dir, &st) == -1 && g_mkdir (dir, 0700) == -1) {
1107
g_warning ("Could not create cert directory '%s': %s", dir, strerror (errno));
1112
path = g_strdup_printf ("%s/%s", dir, fingerprint);
1115
stream = camel_stream_fs_new_with_name (path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
1116
if (stream != NULL) {
1117
if (camel_stream_write (stream, (const char *) ccert->rawcert->data, ccert->rawcert->len) == -1) {
1118
g_warning ("Could not save cert: %s: %s", path, strerror (errno));
1121
camel_stream_close (stream);
1122
camel_object_unref (stream);
1124
g_warning ("Could not save cert: %s: %s", path, strerror (errno));
1132
/* used by the mozilla-like code below */
1134
get_nickname(CERTCertificate *cert)
1136
char *server, *nick = NULL;
1138
PRBool status = PR_TRUE;
1140
server = CERT_GetCommonName(&cert->subject);
1144
for (i=1;status == PR_TRUE;i++) {
1147
nick = g_strdup_printf("%s #%d", server, i);
1149
nick = g_strdup(server);
1151
status = SEC_CertNicknameConflict(server, &cert->derSubject, cert->dbhandle);
1159
ssl_bad_cert (void *data, PRFileDesc *sockfd)
1162
CamelCertDB *certdb = NULL;
1163
CamelCert *ccert = NULL;
1164
char *prompt, *cert_str, *fingerprint;
1165
CamelTcpStreamSSL *ssl;
1166
CERTCertificate *cert;
1167
SECStatus status = SECFailure;
1168
struct _CamelTcpStreamSSLPrivate *priv;
1170
g_return_val_if_fail (data != NULL, SECFailure);
1171
g_return_val_if_fail (CAMEL_IS_TCP_STREAM_SSL (data), SECFailure);
1176
cert = SSL_PeerCertificate (sockfd);
1180
certdb = camel_certdb_get_default();
1181
ccert = camel_certdb_nss_cert_get(certdb, cert);
1182
if (ccert == NULL) {
1183
ccert = camel_certdb_nss_cert_add(certdb, cert);
1184
camel_cert_set_hostname(certdb, ccert, ssl->priv->expected_host);
1187
if (ccert->trust == CAMEL_CERT_TRUST_UNKNOWN) {
1188
status = CERT_VerifyCertNow(cert->dbhandle, cert, TRUE, certUsageSSLClient, NULL);
1189
fingerprint = cert_fingerprint(cert);
1190
cert_str = g_strdup_printf (_("Issuer: %s\n"
1194
CERT_NameToAscii (&cert->issuer),
1195
CERT_NameToAscii (&cert->subject),
1196
fingerprint, status == SECSuccess?_("GOOD"):_("BAD"));
1197
g_free(fingerprint);
1199
/* construct our user prompt */
1200
prompt = g_strdup_printf (_("SSL Certificate check for %s:\n\n%s\n\nDo you wish to accept?"),
1201
ssl->priv->expected_host, cert_str);
1204
/* query the user to find out if we want to accept this certificate */
1205
accept = camel_session_alert_user_with_id (ssl->priv->session, CAMEL_SESSION_ALERT_WARNING, CAMEL_EXCEPTION_SERVICE_CERTIFICATE, prompt, TRUE, priv->service);
1208
camel_certdb_nss_cert_set(certdb, ccert, cert);
1209
camel_cert_set_trust(certdb, ccert, CAMEL_CERT_TRUST_FULLY);
1210
camel_certdb_touch(certdb);
1213
accept = ccert->trust != CAMEL_CERT_TRUST_NEVER;
1216
camel_certdb_cert_unref(certdb, ccert);
1217
camel_object_unref(certdb);
1219
priv->accepted = accept;
1221
return accept ? SECSuccess : SECFailure;
1225
CERTCertTrust trust;
1230
error = PR_GetError();
1232
/* This code is basically what mozilla does - however it doesn't seem to work here
1233
very reliably :-/ */
1234
while (go && status != SECSuccess) {
1235
char *prompt = NULL;
1237
printf("looping, error '%d'\n", error);
1240
case SEC_ERROR_UNKNOWN_ISSUER:
1241
case SEC_ERROR_CA_CERT_INVALID:
1242
case SEC_ERROR_UNTRUSTED_ISSUER:
1243
case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
1244
/* add certificate */
1245
printf("unknown issuer, adding ... \n");
1246
prompt = g_strdup_printf(_("Certificate problem: %s\nIssuer: %s"), cert->subjectName, cert->issuerName);
1248
CamelService *service = NULL; /* TODO: Is there a CamelService that we can use? */
1249
if (camel_session_alert_user_with_id(ssl->priv->session, CAMEL_SESSION_ALERT_WARNING, CAMEL_EXCEPTION_SERVICE_CERTIFICATE, prompt, TRUE, service)) {
1251
nick = get_nickname(cert);
1254
status = SECFailure;
1258
printf("adding cert '%s'\n", nick);
1261
cert->trust = (CERTCertTrust*)PORT_ArenaZAlloc(cert->arena, sizeof(CERTCertTrust));
1262
CERT_DecodeTrustString(cert->trust, "P");
1265
certs[0] = &cert->derCert;
1266
/*CERT_ImportCerts (cert->dbhandle, certUsageSSLServer, 1, certs, NULL, TRUE, FALSE, nick);*/
1267
CERT_ImportCerts(cert->dbhandle, certUsageUserCertImport, 1, certs, NULL, TRUE, FALSE, nick);
1270
printf(" cert type %08x\n", cert->nsCertType);
1272
memset((void*)&trust, 0, sizeof(trust));
1273
if (CERT_GetCertTrust(cert, &trust) != SECSuccess) {
1274
CERT_DecodeTrustString(&trust, "P");
1276
trust.sslFlags |= CERTDB_VALID_PEER | CERTDB_TRUSTED;
1277
if (CERT_ChangeCertTrust(cert->dbhandle, cert, &trust) != SECSuccess) {
1278
printf("couldn't change cert trust?\n");
1281
/*status = SECSuccess;*/
1284
status = CERT_VerifyCertNow(cert->dbhandle, cert, TRUE, certUsageSSLServer, NULL);
1285
error = PR_GetError();
1286
printf("re-verify status %d, error %d\n", status, error);
1289
printf(" cert type %08x\n", cert->nsCertType);
1291
printf("failed/cancelled\n");
1296
case SSL_ERROR_BAD_CERT_DOMAIN:
1297
printf("bad domain\n");
1299
prompt = g_strdup_printf(_("Bad certificate domain: %s\nIssuer: %s"), cert->subjectName, cert->issuerName);
1301
CamelService *service = NULL; /* TODO: Is there a CamelService that we can use? */
1302
if (camel_session_alert_user_with_id (ssl->priv->session, CAMEL_SESSION_ALERT_WARNING, CAMEL_EXCEPTION_SERVICE_CERTIFICATE, prompt, TRUE, service)) {
1303
host = SSL_RevealURL(sockfd);
1304
status = CERT_AddOKDomainName(cert, host);
1305
printf("add ok domain name : %s\n", status == SECFailure?"fail":"ok");
1306
error = PR_GetError();
1307
if (status == SECFailure)
1315
case SEC_ERROR_EXPIRED_CERTIFICATE:
1316
printf("expired\n");
1318
prompt = g_strdup_printf(_("Certificate expired: %s\nIssuer: %s"), cert->subjectName, cert->issuerName);
1320
CamelService *service = NULL; /* TODO: Is there a CamelService that we can use? */
1321
if (camel_session_alert_user_with_id(ssl->priv->session, CAMEL_SESSION_ALERT_WARNING, CAMEL_EXCEPTION_SERVICE_CERTIFICATE, prompt, TRUE, service)) {
1322
cert->timeOK = PR_TRUE;
1323
status = CERT_VerifyCertNow(cert->dbhandle, cert, TRUE, certUsageSSLClient, NULL);
1324
error = PR_GetError();
1325
if (status == SECFailure)
1333
case SEC_ERROR_CRL_EXPIRED:
1334
printf("crl expired\n");
1336
prompt = g_strdup_printf(_("Certificate revocation list expired: %s\nIssuer: %s"), cert->subjectName, cert->issuerName);
1338
CamelService *service = NULL; /* TODO: Is there a CamelService that we can use? */
1339
if (camel_session_alert_user_with_id(ssl->priv->session, CAMEL_SESSION_ALERT_WARNING, CAMEL_EXCEPTION_SERVICE_CERTIFICATE, prompt, TRUE, service)) {
1340
host = SSL_RevealURL(sockfd);
1341
status = CERT_AddOKDomainName(cert, host);
1348
printf("generic error\n");
1356
CERT_DestroyCertificate(cert);
1363
enable_ssl (CamelTcpStreamSSL *ssl, PRFileDesc *fd)
1367
ssl_fd = SSL_ImportFD (NULL, fd ? fd : ssl->priv->sockfd);
1371
SSL_OptionSet (ssl_fd, SSL_SECURITY, PR_TRUE);
1373
if (ssl->priv->flags & CAMEL_TCP_STREAM_SSL_ENABLE_SSL2)
1374
SSL_OptionSet (ssl_fd, SSL_ENABLE_SSL2, PR_TRUE);
1376
SSL_OptionSet (ssl_fd, SSL_ENABLE_SSL2, PR_FALSE);
1378
if (ssl->priv->flags & CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
1379
SSL_OptionSet (ssl_fd, SSL_ENABLE_SSL3, PR_TRUE);
1381
SSL_OptionSet (ssl_fd, SSL_ENABLE_SSL3, PR_FALSE);
1383
if (ssl->priv->flags & CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
1384
SSL_OptionSet (ssl_fd, SSL_ENABLE_TLS, PR_TRUE);
1386
SSL_OptionSet (ssl_fd, SSL_ENABLE_TLS, PR_FALSE);
1388
SSL_SetURL (ssl_fd, ssl->priv->expected_host);
1390
/*SSL_GetClientAuthDataHook (sslSocket, ssl_get_client_auth, (void *) certNickname);*/
1391
/*SSL_AuthCertificateHook (ssl_fd, ssl_auth_cert, (void *) CERT_GetDefaultCertDB ());*/
1392
SSL_BadCertHook (ssl_fd, ssl_bad_cert, ssl);
1394
ssl->priv->ssl_mode = TRUE;
1400
sockaddr_to_praddr(struct sockaddr *s, int len, PRNetAddr *addr)
1402
/* We assume the ip addresses are the same size - they have to be anyway.
1403
We could probably just use memcpy *shrug* */
1405
memset(addr, 0, sizeof(*addr));
1407
if (s->sa_family == AF_INET) {
1408
struct sockaddr_in *sin = (struct sockaddr_in *)s;
1410
if (len < sizeof(*sin))
1413
addr->inet.family = PR_AF_INET;
1414
addr->inet.port = sin->sin_port;
1415
memcpy(&addr->inet.ip, &sin->sin_addr, sizeof(addr->inet.ip));
1420
else if (s->sa_family == PR_AF_INET6) {
1421
struct sockaddr_in6 *sin = (struct sockaddr_in6 *)s;
1423
if (len < sizeof(*sin))
1426
addr->ipv6.family = PR_AF_INET6;
1427
addr->ipv6.port = sin->sin6_port;
1428
addr->ipv6.flowinfo = sin->sin6_flowinfo;
1429
memcpy(&addr->ipv6.ip, &sin->sin6_addr, sizeof(addr->ipv6.ip));
1430
addr->ipv6.scope_id = sin->sin6_scope_id;
1440
socket_connect(CamelTcpStream *stream, struct addrinfo *host)
1442
CamelTcpStreamSSL *ssl = CAMEL_TCP_STREAM_SSL (stream);
1444
PRFileDesc *fd, *cancel_fd;
1446
if (sockaddr_to_praddr(host->ai_addr, host->ai_addrlen, &netaddr) != 0) {
1451
fd = PR_OpenTCPSocket(netaddr.raw.family);
1453
set_errno (PR_GetError ());
1457
if (ssl->priv->ssl_mode) {
1460
ssl_fd = enable_ssl (ssl, fd);
1461
if (ssl_fd == NULL) {
1464
set_errno (PR_GetError ());
1475
cancel_fd = camel_operation_cancel_prfd(NULL);
1477
if (PR_Connect (fd, &netaddr, cancel_fd?0:(CONNECT_TIMEOUT*1000)) == PR_FAILURE) {
1480
set_errno (PR_GetError ());
1481
if (PR_GetError () == PR_IN_PROGRESS_ERROR ||
1482
(cancel_fd && (PR_GetError () == PR_CONNECT_TIMEOUT_ERROR ||
1483
PR_GetError () == PR_IO_TIMEOUT_ERROR))) {
1484
gboolean connected = FALSE;
1488
poll[0].in_flags = PR_POLL_WRITE | PR_POLL_EXCEPT;
1489
poll[1].fd = cancel_fd;
1490
poll[1].in_flags = PR_POLL_READ;
1493
poll[0].out_flags = 0;
1494
poll[1].out_flags = 0;
1496
if (PR_Poll (poll, cancel_fd?2:1, (CONNECT_TIMEOUT*1000)) == PR_FAILURE) {
1497
set_errno (PR_GetError ());
1501
if (poll[1].out_flags == PR_POLL_READ) {
1506
if (PR_ConnectContinue(fd, poll[0].out_flags) == PR_FAILURE) {
1507
set_errno (PR_GetError ());
1508
if (PR_GetError () != PR_IN_PROGRESS_ERROR)
1513
} while (!connected);
1518
ssl->priv->sockfd = NULL;
1527
ssl->priv->sockfd = fd;
1533
stream_connect(CamelTcpStream *stream, struct addrinfo *host)
1536
if (socket_connect(stream, host) == 0)
1538
host = host->ai_next;
1545
stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data)
1547
PRSocketOptionData sodata;
1549
memset ((void *) &sodata, 0, sizeof (sodata));
1550
memcpy ((void *) &sodata, (void *) data, sizeof (CamelSockOptData));
1552
if (PR_GetSocketOption (((CamelTcpStreamSSL *)stream)->priv->sockfd, &sodata) == PR_FAILURE)
1555
memcpy ((void *) data, (void *) &sodata, sizeof (CamelSockOptData));
1561
stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data)
1563
PRSocketOptionData sodata;
1565
memset ((void *) &sodata, 0, sizeof (sodata));
1566
memcpy ((void *) &sodata, (void *) data, sizeof (CamelSockOptData));
1568
if (PR_SetSocketOption (((CamelTcpStreamSSL *)stream)->priv->sockfd, &sodata) == PR_FAILURE)
1574
static struct sockaddr *
1575
sockaddr_from_praddr(PRNetAddr *addr, socklen_t *len)
1577
/* We assume the ip addresses are the same size - they have to be anyway */
1579
if (addr->raw.family == PR_AF_INET) {
1580
struct sockaddr_in *sin = g_malloc0(sizeof(*sin));
1582
sin->sin_family = AF_INET;
1583
sin->sin_port = addr->inet.port;
1584
memcpy(&sin->sin_addr, &addr->inet.ip, sizeof(sin->sin_addr));
1585
*len = sizeof(*sin);
1587
return (struct sockaddr *)sin;
1590
else if (addr->raw.family == PR_AF_INET6) {
1591
struct sockaddr_in6 *sin = g_malloc0(sizeof(*sin));
1593
sin->sin6_family = AF_INET6;
1594
sin->sin6_port = addr->ipv6.port;
1595
sin->sin6_flowinfo = addr->ipv6.flowinfo;
1596
memcpy(&sin->sin6_addr, &addr->ipv6.ip, sizeof(sin->sin6_addr));
1597
sin->sin6_scope_id = addr->ipv6.scope_id;
1598
*len = sizeof(*sin);
1600
return (struct sockaddr *)sin;
1607
static struct sockaddr *
1608
stream_get_local_address(CamelTcpStream *stream, socklen_t *len)
1610
PRFileDesc *sockfd = CAMEL_TCP_STREAM_SSL (stream)->priv->sockfd;
1613
if (PR_GetSockName(sockfd, &addr) != PR_SUCCESS)
1616
return sockaddr_from_praddr(&addr, len);
1619
static struct sockaddr *
1620
stream_get_remote_address (CamelTcpStream *stream, socklen_t *len)
1622
PRFileDesc *sockfd = CAMEL_TCP_STREAM_SSL (stream)->priv->sockfd;
1625
if (PR_GetPeerName(sockfd, &addr) != PR_SUCCESS)
1628
return sockaddr_from_praddr(&addr, len);
1631
#endif /* HAVE_NSS */