comparison libpurple/plugins/ssl/ssl-nss.c @ 15374:5fe8042783c1

Rename gtk/ and libgaim/ to pidgin/ and libpurple/
author Sean Egan <seanegan@gmail.com>
date Sat, 20 Jan 2007 02:32:10 +0000
parents
children 32c366eeeb99
comparison
equal deleted inserted replaced
15373:f79e0f4df793 15374:5fe8042783c1
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)