14192
|
1 /**
|
|
2 * @file ssl-nss.c Mozilla NSS SSL plugin.
|
|
3 *
|
|
4 * gaim
|
|
5 *
|
|
6 * Copyright (C) 2003 Christian Hammond <chipx86@gnupdate.org>
|
|
7 *
|
|
8 * This program is free software; you can redistribute it and/or modify
|
|
9 * it under the terms of the GNU General Public License as published by
|
|
10 * the Free Software Foundation; either version 2 of the License, or
|
|
11 * (at your option) any later version.
|
|
12 *
|
|
13 * This program is distributed in the hope that it will be useful,
|
|
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16 * GNU General Public License for more details.
|
|
17 *
|
|
18 * You should have received a copy of the GNU General Public License
|
|
19 * along with this program; if not, write to the Free Software
|
|
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
21 */
|
|
22 #include "internal.h"
|
|
23 #include "debug.h"
|
|
24 #include "plugin.h"
|
|
25 #include "sslconn.h"
|
|
26 #include "version.h"
|
|
27
|
|
28 #define SSL_NSS_PLUGIN_ID "ssl-nss"
|
|
29
|
|
30 #ifdef HAVE_NSS
|
|
31
|
|
32 #undef HAVE_LONG_LONG /* Make Mozilla less angry. If angry, Mozilla SMASH! */
|
|
33
|
|
34 #include <nspr.h>
|
|
35 #include <private/pprio.h>
|
|
36 #include <nss.h>
|
|
37 #include <pk11func.h>
|
|
38 #include <prio.h>
|
|
39 #include <secerr.h>
|
|
40 #include <secmod.h>
|
|
41 #include <ssl.h>
|
|
42 #include <sslerr.h>
|
|
43 #include <sslproto.h>
|
|
44
|
|
45 typedef struct
|
|
46 {
|
|
47 PRFileDesc *fd;
|
|
48 PRFileDesc *in;
|
|
49 guint handshake_handler;
|
|
50
|
|
51 } GaimSslNssData;
|
|
52
|
|
53 #define GAIM_SSL_NSS_DATA(gsc) ((GaimSslNssData *)gsc->private_data)
|
|
54
|
|
55 static const PRIOMethods *_nss_methods = NULL;
|
|
56 static PRDescIdentity _identity;
|
|
57
|
|
58 /* Thank you, Evolution */
|
|
59 static void
|
|
60 set_errno(int code)
|
|
61 {
|
|
62 /* FIXME: this should handle more. */
|
|
63 switch (code) {
|
|
64 case PR_INVALID_ARGUMENT_ERROR:
|
|
65 errno = EINVAL;
|
|
66 break;
|
|
67 case PR_PENDING_INTERRUPT_ERROR:
|
|
68 errno = EINTR;
|
|
69 break;
|
|
70 case PR_IO_PENDING_ERROR:
|
|
71 errno = EAGAIN;
|
|
72 break;
|
|
73 case PR_WOULD_BLOCK_ERROR:
|
|
74 errno = EAGAIN;
|
|
75 /*errno = EWOULDBLOCK; */
|
|
76 break;
|
|
77 case PR_IN_PROGRESS_ERROR:
|
|
78 errno = EINPROGRESS;
|
|
79 break;
|
|
80 case PR_ALREADY_INITIATED_ERROR:
|
|
81 errno = EALREADY;
|
|
82 break;
|
|
83 case PR_NETWORK_UNREACHABLE_ERROR:
|
|
84 errno = EHOSTUNREACH;
|
|
85 break;
|
|
86 case PR_CONNECT_REFUSED_ERROR:
|
|
87 errno = ECONNREFUSED;
|
|
88 break;
|
|
89 case PR_CONNECT_TIMEOUT_ERROR:
|
|
90 case PR_IO_TIMEOUT_ERROR:
|
|
91 errno = ETIMEDOUT;
|
|
92 break;
|
|
93 case PR_NOT_CONNECTED_ERROR:
|
|
94 errno = ENOTCONN;
|
|
95 break;
|
|
96 case PR_CONNECT_RESET_ERROR:
|
|
97 errno = ECONNRESET;
|
|
98 break;
|
|
99 case PR_IO_ERROR:
|
|
100 default:
|
|
101 errno = EIO;
|
|
102 break;
|
|
103 }
|
|
104 }
|
|
105
|
|
106 static void
|
|
107 ssl_nss_init_nss(void)
|
|
108 {
|
|
109 char *lib;
|
|
110 PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
|
|
111 NSS_NoDB_Init(NULL);
|
|
112
|
|
113 /* TODO: Fix this so autoconf does the work trying to find this lib. */
|
|
114 #ifndef _WIN32
|
|
115 lib = g_strdup(BR_LIBDIR("/libnssckbi.so"));
|
|
116 #else
|
|
117 lib = g_strdup("nssckbi.dll");
|
|
118 #endif
|
|
119 SECMOD_AddNewModule("Builtins", lib, 0, 0);
|
|
120 g_free(lib);
|
|
121 NSS_SetDomesticPolicy();
|
|
122
|
|
123 _identity = PR_GetUniqueIdentity("Gaim");
|
|
124 _nss_methods = PR_GetDefaultIOMethods();
|
|
125 }
|
|
126
|
|
127 static SECStatus
|
|
128 ssl_auth_cert(void *arg, PRFileDesc *socket, PRBool checksig,
|
|
129 PRBool is_server)
|
|
130 {
|
|
131 return SECSuccess;
|
|
132
|
|
133 #if 0
|
|
134 CERTCertificate *cert;
|
|
135 void *pinArg;
|
|
136 SECStatus status;
|
|
137
|
|
138 cert = SSL_PeerCertificate(socket);
|
|
139 pinArg = SSL_RevealPinArg(socket);
|
|
140
|
|
141 status = CERT_VerifyCertNow((CERTCertDBHandle *)arg, cert, checksig,
|
|
142 certUsageSSLClient, pinArg);
|
|
143
|
|
144 if (status != SECSuccess) {
|
|
145 gaim_debug_error("nss", "CERT_VerifyCertNow failed\n");
|
|
146 CERT_DestroyCertificate(cert);
|
|
147 return status;
|
|
148 }
|
|
149
|
|
150 CERT_DestroyCertificate(cert);
|
|
151 return SECSuccess;
|
|
152 #endif
|
|
153 }
|
|
154
|
|
155 static SECStatus
|
|
156 ssl_bad_cert(void *arg, PRFileDesc *socket)
|
|
157 {
|
|
158 SECStatus status = SECFailure;
|
|
159 PRErrorCode err;
|
|
160
|
|
161 if (arg == NULL)
|
|
162 return status;
|
|
163
|
|
164 *(PRErrorCode *)arg = err = PORT_GetError();
|
|
165
|
|
166 switch (err)
|
|
167 {
|
|
168 case SEC_ERROR_INVALID_AVA:
|
|
169 case SEC_ERROR_INVALID_TIME:
|
|
170 case SEC_ERROR_BAD_SIGNATURE:
|
|
171 case SEC_ERROR_EXPIRED_CERTIFICATE:
|
|
172 case SEC_ERROR_UNKNOWN_ISSUER:
|
|
173 case SEC_ERROR_UNTRUSTED_CERT:
|
|
174 case SEC_ERROR_CERT_VALID:
|
|
175 case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
|
|
176 case SEC_ERROR_CRL_EXPIRED:
|
|
177 case SEC_ERROR_CRL_BAD_SIGNATURE:
|
|
178 case SEC_ERROR_EXTENSION_VALUE_INVALID:
|
|
179 case SEC_ERROR_CA_CERT_INVALID:
|
|
180 case SEC_ERROR_CERT_USAGES_INVALID:
|
|
181 case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
|
|
182 status = SECSuccess;
|
|
183 break;
|
|
184
|
|
185 default:
|
|
186 status = SECFailure;
|
|
187 break;
|
|
188 }
|
|
189
|
|
190 gaim_debug_error("nss", "Bad certificate: %d\n", err);
|
|
191
|
|
192 return status;
|
|
193 }
|
|
194
|
|
195 static gboolean
|
|
196 ssl_nss_init(void)
|
|
197 {
|
|
198 return TRUE;
|
|
199 }
|
|
200
|
|
201 static void
|
|
202 ssl_nss_uninit(void)
|
|
203 {
|
|
204 PR_Cleanup();
|
|
205
|
|
206 _nss_methods = NULL;
|
|
207 }
|
|
208
|
|
209 static void
|
|
210 ssl_nss_handshake_cb(gpointer data, int fd, GaimInputCondition cond)
|
|
211 {
|
|
212 GaimSslConnection *gsc = (GaimSslConnection *)data;
|
|
213 GaimSslNssData *nss_data = gsc->private_data;
|
|
214
|
|
215 /* I don't think this the best way to do this...
|
|
216 * It seems to work because it'll eventually use the cached value
|
|
217 */
|
|
218 if(SSL_ForceHandshake(nss_data->in) != SECSuccess) {
|
|
219 set_errno(PR_GetError());
|
|
220 if (errno == EAGAIN || errno == EWOULDBLOCK)
|
|
221 return;
|
|
222
|
|
223 gaim_debug_error("nss", "Handshake failed %d\n", PR_GetError());
|
|
224
|
|
225 if (gsc->error_cb != NULL)
|
|
226 gsc->error_cb(gsc, GAIM_SSL_HANDSHAKE_FAILED, gsc->connect_cb_data);
|
|
227
|
|
228 gaim_ssl_close(gsc);
|
|
229
|
|
230 return;
|
|
231 }
|
|
232
|
|
233 gaim_input_remove(nss_data->handshake_handler);
|
|
234 nss_data->handshake_handler = 0;
|
|
235
|
|
236 gsc->connect_cb(gsc->connect_cb_data, gsc, cond);
|
|
237 }
|
|
238
|
|
239 static void
|
|
240 ssl_nss_connect(GaimSslConnection *gsc)
|
|
241 {
|
|
242 GaimSslNssData *nss_data = g_new0(GaimSslNssData, 1);
|
|
243 PRSocketOptionData socket_opt;
|
|
244
|
|
245 gsc->private_data = nss_data;
|
|
246
|
|
247 nss_data->fd = PR_ImportTCPSocket(gsc->fd);
|
|
248
|
|
249 if (nss_data->fd == NULL)
|
|
250 {
|
|
251 gaim_debug_error("nss", "nss_data->fd == NULL!\n");
|
|
252
|
|
253 if (gsc->error_cb != NULL)
|
|
254 gsc->error_cb(gsc, GAIM_SSL_CONNECT_FAILED, gsc->connect_cb_data);
|
|
255
|
|
256 gaim_ssl_close((GaimSslConnection *)gsc);
|
|
257
|
|
258 return;
|
|
259 }
|
|
260
|
|
261 socket_opt.option = PR_SockOpt_Nonblocking;
|
|
262 socket_opt.value.non_blocking = PR_TRUE;
|
|
263
|
|
264 if (PR_SetSocketOption(nss_data->fd, &socket_opt) != PR_SUCCESS)
|
|
265 gaim_debug_warning("nss", "unable to set socket into non-blocking mode: %d\n", PR_GetError());
|
|
266
|
|
267 nss_data->in = SSL_ImportFD(NULL, nss_data->fd);
|
|
268
|
|
269 if (nss_data->in == NULL)
|
|
270 {
|
|
271 gaim_debug_error("nss", "nss_data->in == NUL!\n");
|
|
272
|
|
273 if (gsc->error_cb != NULL)
|
|
274 gsc->error_cb(gsc, GAIM_SSL_CONNECT_FAILED, gsc->connect_cb_data);
|
|
275
|
|
276 gaim_ssl_close((GaimSslConnection *)gsc);
|
|
277
|
|
278 return;
|
|
279 }
|
|
280
|
|
281 SSL_OptionSet(nss_data->in, SSL_SECURITY, PR_TRUE);
|
|
282 SSL_OptionSet(nss_data->in, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
|
|
283
|
|
284 SSL_AuthCertificateHook(nss_data->in,
|
|
285 (SSLAuthCertificate)ssl_auth_cert,
|
|
286 (void *)CERT_GetDefaultCertDB());
|
|
287 SSL_BadCertHook(nss_data->in, (SSLBadCertHandler)ssl_bad_cert, NULL);
|
|
288
|
|
289 if(gsc->host)
|
|
290 SSL_SetURL(nss_data->in, gsc->host);
|
|
291
|
|
292 #if 0
|
|
293 /* This seems like it'd the be the correct way to implement the
|
|
294 nonblocking stuff, but it doesn't seem to work */
|
|
295 SSL_HandshakeCallback(nss_data->in,
|
|
296 (SSLHandshakeCallback) ssl_nss_handshake_cb, gsc);
|
|
297 #endif
|
|
298 SSL_ResetHandshake(nss_data->in, PR_FALSE);
|
|
299
|
|
300 nss_data->handshake_handler = gaim_input_add(gsc->fd,
|
|
301 GAIM_INPUT_READ, ssl_nss_handshake_cb, gsc);
|
|
302
|
|
303 ssl_nss_handshake_cb(gsc, gsc->fd, GAIM_INPUT_READ);
|
|
304 }
|
|
305
|
|
306 static void
|
|
307 ssl_nss_close(GaimSslConnection *gsc)
|
|
308 {
|
|
309 GaimSslNssData *nss_data = GAIM_SSL_NSS_DATA(gsc);
|
|
310
|
|
311 if(!nss_data)
|
|
312 return;
|
|
313
|
|
314 if (nss_data->in) PR_Close(nss_data->in);
|
|
315 /* if (nss_data->fd) PR_Close(nss_data->fd); */
|
|
316
|
|
317 if (nss_data->handshake_handler)
|
|
318 gaim_input_remove(nss_data->handshake_handler);
|
|
319
|
|
320 g_free(nss_data);
|
|
321 gsc->private_data = NULL;
|
|
322 }
|
|
323
|
|
324 static size_t
|
|
325 ssl_nss_read(GaimSslConnection *gsc, void *data, size_t len)
|
|
326 {
|
|
327 ssize_t ret;
|
|
328 GaimSslNssData *nss_data = GAIM_SSL_NSS_DATA(gsc);
|
|
329
|
|
330 ret = PR_Read(nss_data->in, data, len);
|
|
331
|
|
332 if (ret == -1)
|
|
333 set_errno(PR_GetError());
|
|
334
|
|
335 return ret;
|
|
336 }
|
|
337
|
|
338 static size_t
|
|
339 ssl_nss_write(GaimSslConnection *gsc, const void *data, size_t len)
|
|
340 {
|
|
341 ssize_t ret;
|
|
342 GaimSslNssData *nss_data = GAIM_SSL_NSS_DATA(gsc);
|
|
343
|
|
344 if(!nss_data)
|
|
345 return 0;
|
|
346
|
|
347 ret = PR_Write(nss_data->in, data, len);
|
|
348
|
|
349 if (ret == -1)
|
|
350 set_errno(PR_GetError());
|
|
351
|
|
352 return ret;
|
|
353 }
|
|
354
|
|
355 static GaimSslOps ssl_ops =
|
|
356 {
|
|
357 ssl_nss_init,
|
|
358 ssl_nss_uninit,
|
|
359 ssl_nss_connect,
|
|
360 ssl_nss_close,
|
|
361 ssl_nss_read,
|
|
362 ssl_nss_write
|
|
363 };
|
|
364
|
|
365 #endif /* HAVE_NSS */
|
|
366
|
|
367
|
|
368 static gboolean
|
|
369 plugin_load(GaimPlugin *plugin)
|
|
370 {
|
|
371 #ifdef HAVE_NSS
|
|
372 if (!gaim_ssl_get_ops()) {
|
|
373 gaim_ssl_set_ops(&ssl_ops);
|
|
374 }
|
|
375
|
|
376 /* Init NSS now, so others can use it even if sslconn never does */
|
|
377 ssl_nss_init_nss();
|
|
378
|
|
379 return TRUE;
|
|
380 #else
|
|
381 return FALSE;
|
|
382 #endif
|
|
383 }
|
|
384
|
|
385 static gboolean
|
|
386 plugin_unload(GaimPlugin *plugin)
|
|
387 {
|
|
388 #ifdef HAVE_NSS
|
|
389 if (gaim_ssl_get_ops() == &ssl_ops) {
|
|
390 gaim_ssl_set_ops(NULL);
|
|
391 }
|
|
392 #endif
|
|
393
|
|
394 return TRUE;
|
|
395 }
|
|
396
|
|
397 static GaimPluginInfo info =
|
|
398 {
|
|
399 GAIM_PLUGIN_MAGIC,
|
|
400 GAIM_MAJOR_VERSION,
|
|
401 GAIM_MINOR_VERSION,
|
|
402 GAIM_PLUGIN_STANDARD, /**< type */
|
|
403 NULL, /**< ui_requirement */
|
|
404 GAIM_PLUGIN_FLAG_INVISIBLE, /**< flags */
|
|
405 NULL, /**< dependencies */
|
|
406 GAIM_PRIORITY_DEFAULT, /**< priority */
|
|
407
|
|
408 SSL_NSS_PLUGIN_ID, /**< id */
|
|
409 N_("NSS"), /**< name */
|
|
410 VERSION, /**< version */
|
|
411 /** summary */
|
|
412 N_("Provides SSL support through Mozilla NSS."),
|
|
413 /** description */
|
|
414 N_("Provides SSL support through Mozilla NSS."),
|
|
415 "Christian Hammond <chipx86@gnupdate.org>",
|
|
416 GAIM_WEBSITE, /**< homepage */
|
|
417
|
|
418 plugin_load, /**< load */
|
|
419 plugin_unload, /**< unload */
|
|
420 NULL, /**< destroy */
|
|
421
|
|
422 NULL, /**< ui_info */
|
|
423 NULL, /**< extra_info */
|
|
424 NULL, /**< prefs_info */
|
|
425 NULL /**< actions */
|
|
426 };
|
|
427
|
|
428 static void
|
|
429 init_plugin(GaimPlugin *plugin)
|
|
430 {
|
|
431 }
|
|
432
|
|
433 GAIM_INIT_PLUGIN(ssl_nss, init_plugin, info)
|