6703
|
1 /**
|
|
2 * @file sslconn.c SSL API
|
|
3 * @ingroup core
|
|
4 *
|
|
5 * gaim
|
|
6 *
|
|
7 * Copyright (C) 2003 Christian Hammond <chipx86@gnupdate.org>
|
|
8 *
|
|
9 * This program is free software; you can redistribute it and/or modify
|
|
10 * it under the terms of the GNU General Public License as published by
|
|
11 * the Free Software Foundation; either version 2 of the License, or
|
|
12 * (at your option) any later version.
|
|
13 *
|
|
14 * This program is distributed in the hope that it will be useful,
|
|
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
17 * GNU General Public License for more details.
|
|
18 *
|
|
19 * You should have received a copy of the GNU General Public License
|
|
20 * along with this program; if not, write to the Free Software
|
|
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
22 */
|
|
23 #include "internal.h"
|
|
24
|
|
25 #include "debug.h"
|
|
26 #include "sslconn.h"
|
|
27
|
|
28 #ifdef HAVE_NSS
|
|
29 # include <nspr.h>
|
|
30 # include <nss.h>
|
|
31 # include <pk11func.h>
|
|
32 # include <prio.h>
|
|
33 # include <secerr.h>
|
|
34 # include <secmod.h>
|
|
35 # include <ssl.h>
|
|
36 # include <sslerr.h>
|
|
37 # include <sslproto.h>
|
|
38
|
|
39 typedef struct
|
|
40 {
|
|
41 char *host;
|
|
42 int port;
|
|
43 void *user_data;
|
|
44 GaimSslInputFunction input_func;
|
|
45
|
|
46 int fd;
|
|
47 int inpa;
|
|
48
|
|
49 PRFileDesc *nss_fd;
|
|
50 PRFileDesc *nss_in;
|
|
51
|
|
52 } GaimSslData;
|
|
53
|
|
54 static gboolean _nss_initialized = FALSE;
|
|
55 static const PRIOMethods *_nss_methods = NULL;
|
|
56 static PRDescIdentity _identity;
|
|
57
|
|
58 static void
|
|
59 destroy_ssl_data(GaimSslData *data)
|
|
60 {
|
|
61 if (data->inpa) gaim_input_remove(data->inpa);
|
|
62 if (data->nss_in) PR_Close(data->nss_in);
|
|
63 if (data->nss_fd) PR_Close(data->nss_fd);
|
|
64 if (data->fd) close(data->fd);
|
|
65
|
|
66 if (data->host != NULL)
|
|
67 g_free(data->host);
|
|
68
|
|
69 g_free(data);
|
|
70 }
|
|
71
|
|
72 static void
|
|
73 init_nss(void)
|
|
74 {
|
|
75 if (_nss_initialized)
|
|
76 return;
|
|
77
|
|
78 PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
|
|
79 NSS_NoDB_Init(NULL);
|
|
80
|
|
81 /* TODO: Fix this so autoconf does the work trying to find this lib. */
|
|
82 SECMOD_AddNewModule("Builtins", LIBDIR "/libnssckbi.so", 0, 0);
|
|
83 NSS_SetDomesticPolicy();
|
|
84
|
|
85 _identity = PR_GetUniqueIdentity("Gaim");
|
|
86 _nss_methods = PR_GetDefaultIOMethods();
|
|
87
|
|
88 _nss_initialized = TRUE;
|
|
89 }
|
|
90
|
|
91 static SECStatus
|
|
92 ssl_auth_cert(void *arg, PRFileDesc *socket, PRBool checksig, PRBool is_server)
|
|
93 {
|
|
94 return SECSuccess;
|
|
95
|
|
96 #if 0
|
|
97 CERTCertificate *cert;
|
|
98 void *pinArg;
|
|
99 SECStatus status;
|
|
100
|
|
101 cert = SSL_PeerCertificate(socket);
|
|
102 pinArg = SSL_RevealPinArg(socket);
|
|
103
|
|
104 status = CERT_VerifyCertNow((CERTCertDBHandle *)arg, cert, checksig,
|
|
105 certUsageSSLClient, pinArg);
|
|
106
|
|
107 if (status != SECSuccess) {
|
|
108 gaim_debug(GAIM_DEBUG_ERROR, "msn", "CERT_VerifyCertNow failed\n");
|
|
109 CERT_DestroyCertificate(cert);
|
|
110 return status;
|
|
111 }
|
|
112
|
|
113 CERT_DestroyCertificate(cert);
|
|
114 return SECSuccess;
|
|
115 #endif
|
|
116 }
|
|
117
|
|
118 SECStatus
|
|
119 ssl_bad_cert(void *arg, PRFileDesc *socket)
|
|
120 {
|
|
121 SECStatus status = SECFailure;
|
|
122 PRErrorCode err;
|
|
123
|
|
124 if (arg == NULL)
|
|
125 return status;
|
|
126
|
|
127 *(PRErrorCode *)arg = err = PORT_GetError();
|
|
128
|
|
129 switch (err)
|
|
130 {
|
|
131 case SEC_ERROR_INVALID_AVA:
|
|
132 case SEC_ERROR_INVALID_TIME:
|
|
133 case SEC_ERROR_BAD_SIGNATURE:
|
|
134 case SEC_ERROR_EXPIRED_CERTIFICATE:
|
|
135 case SEC_ERROR_UNKNOWN_ISSUER:
|
|
136 case SEC_ERROR_UNTRUSTED_CERT:
|
|
137 case SEC_ERROR_CERT_VALID:
|
|
138 case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
|
|
139 case SEC_ERROR_CRL_EXPIRED:
|
|
140 case SEC_ERROR_CRL_BAD_SIGNATURE:
|
|
141 case SEC_ERROR_EXTENSION_VALUE_INVALID:
|
|
142 case SEC_ERROR_CA_CERT_INVALID:
|
|
143 case SEC_ERROR_CERT_USAGES_INVALID:
|
|
144 case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
|
|
145 status = SECSuccess;
|
|
146 break;
|
|
147
|
|
148 default:
|
|
149 status = SECFailure;
|
|
150 break;
|
|
151 }
|
|
152
|
|
153 gaim_debug(GAIM_DEBUG_ERROR, "msn",
|
|
154 "Bad certificate: %d\n");
|
|
155
|
|
156 return status;
|
|
157 }
|
|
158
|
|
159 static void
|
|
160 input_func(gpointer data, gint source, GaimInputCondition cond)
|
|
161 {
|
|
162 GaimSslData *ssl_data = (GaimSslData *)data;
|
|
163 char *cp, *ip, *sp;
|
|
164 int op, kp0, kp1;
|
|
165 int result;
|
|
166
|
|
167 result = SSL_SecurityStatus(ssl_data->nss_in, &op, &cp, &kp0,
|
|
168 &kp1, &ip, &sp);
|
|
169
|
|
170 gaim_debug(GAIM_DEBUG_MISC, "msn",
|
|
171 "bulk cipher %s, %d secret key bits, %d key bits, status: %d\n"
|
|
172 "subject DN: %s\n"
|
|
173 "issuer DN: %s\n",
|
|
174 cp, kp1, kp0, op, sp, ip);
|
|
175
|
|
176 PR_Free(cp);
|
|
177 PR_Free(ip);
|
|
178 PR_Free(sp);
|
|
179
|
|
180 ssl_data->input_func(ssl_data->user_data, (GaimSslConnection *)ssl_data,
|
|
181 cond);
|
|
182 }
|
|
183
|
|
184 static void
|
|
185 ssl_connect_cb(gpointer data, gint source, GaimInputCondition cond)
|
|
186 {
|
|
187 PRSocketOptionData socket_opt;
|
|
188 GaimSslData *ssl_data = (GaimSslData *)data;
|
|
189
|
|
190 if (!_nss_initialized)
|
|
191 init_nss();
|
|
192
|
|
193 ssl_data->fd = source;
|
|
194
|
|
195 ssl_data->nss_fd = PR_ImportTCPSocket(ssl_data->fd);
|
|
196
|
|
197 if (ssl_data->nss_fd == NULL)
|
|
198 {
|
|
199 gaim_debug(GAIM_DEBUG_ERROR, "ssl", "nss_fd == NULL!\n");
|
|
200
|
|
201 destroy_ssl_data(ssl_data);
|
|
202
|
|
203 return;
|
|
204 }
|
|
205
|
|
206 socket_opt.option = PR_SockOpt_Nonblocking;
|
|
207 socket_opt.value.non_blocking = PR_FALSE;
|
|
208
|
|
209 PR_SetSocketOption(ssl_data->nss_fd, &socket_opt);
|
|
210
|
|
211 ssl_data->nss_in = SSL_ImportFD(NULL, ssl_data->nss_fd);
|
|
212
|
|
213 if (ssl_data->nss_in == NULL)
|
|
214 {
|
|
215 gaim_debug(GAIM_DEBUG_ERROR, "ssl", "nss_in == NUL!\n");
|
|
216
|
|
217 destroy_ssl_data(ssl_data);
|
|
218
|
|
219 return;
|
|
220 }
|
|
221
|
|
222 SSL_OptionSet(ssl_data->nss_in, SSL_SECURITY, PR_TRUE);
|
|
223 SSL_OptionSet(ssl_data->nss_in, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
|
|
224
|
|
225 SSL_AuthCertificateHook(ssl_data->nss_in,
|
|
226 (SSLAuthCertificate)ssl_auth_cert,
|
|
227 (void *)CERT_GetDefaultCertDB());
|
|
228 SSL_BadCertHook(ssl_data->nss_in, (SSLBadCertHandler)ssl_bad_cert, NULL);
|
|
229
|
|
230 SSL_SetURL(ssl_data->nss_in, ssl_data->host);
|
|
231
|
|
232 SSL_ResetHandshake(ssl_data->nss_in, PR_FALSE);
|
|
233
|
|
234 if (SSL_ForceHandshake(ssl_data->nss_in))
|
|
235 {
|
|
236 gaim_debug(GAIM_DEBUG_ERROR, "ssl", "Handshake failed\n");
|
|
237
|
|
238 destroy_ssl_data(ssl_data);
|
|
239
|
|
240 return;
|
|
241 }
|
|
242
|
|
243 #if 0
|
|
244 ssl_data->input_func(ssl_data->user_data, (GaimSslConnection *)ssl_data,
|
|
245 cond);
|
|
246 #endif
|
|
247
|
|
248 input_func(ssl_data, source, cond);
|
|
249 }
|
|
250 #endif /* HAVE_NSS */
|
|
251
|
|
252 gboolean
|
|
253 gaim_ssl_is_supported(void)
|
|
254 {
|
|
255 #ifdef HAVE_NSS
|
|
256 return TRUE;
|
|
257 #else
|
|
258 return FALSE;
|
|
259 #endif
|
|
260 }
|
|
261
|
|
262 GaimSslConnection *
|
|
263 gaim_ssl_connect(GaimAccount *account, const char *host, int port,
|
|
264 GaimSslInputFunction func, void *data)
|
|
265 {
|
|
266 #ifdef HAVE_NSS
|
|
267 int i;
|
|
268 GaimSslData *ssl_data;
|
|
269
|
|
270 g_return_val_if_fail(host != NULL, NULL);
|
|
271 g_return_val_if_fail(port != 0 && port != -1, NULL);
|
|
272 g_return_val_if_fail(func != NULL, NULL);
|
|
273 g_return_val_if_fail(gaim_ssl_is_supported(), NULL);
|
|
274
|
|
275 ssl_data = g_new0(GaimSslData, 1);
|
|
276
|
|
277 ssl_data->host = g_strdup(host);
|
|
278 ssl_data->port = port;
|
|
279 ssl_data->user_data = data;
|
|
280 ssl_data->input_func = func;
|
|
281
|
|
282 i = gaim_proxy_connect(account, host, port, ssl_connect_cb, ssl_data);
|
|
283
|
|
284 if (i < 0)
|
|
285 {
|
|
286 g_free(ssl_data->host);
|
|
287 g_free(ssl_data);
|
|
288
|
|
289 return NULL;
|
|
290 }
|
|
291
|
|
292 return (GaimSslConnection)ssl_data;
|
|
293 #else
|
|
294 g_return_val_if_fail(gaim_ssl_is_supported(), -1);
|
|
295 #endif
|
|
296 }
|
|
297
|
|
298 void
|
|
299 gaim_ssl_close(GaimSslConnection *gsc)
|
|
300 {
|
|
301 g_return_if_fail(gsc != NULL);
|
|
302
|
|
303 #ifdef HAVE_NSS
|
|
304 destroy_ssl_data((GaimSslData *)gsc);
|
|
305 #endif
|
|
306 }
|
|
307
|
|
308 size_t
|
|
309 gaim_ssl_read(GaimSslConnection *gsc, void *data, size_t len)
|
|
310 {
|
|
311 #ifdef HAVE_NSS
|
|
312 GaimSslData *ssl_data = (GaimSslData *)gsc;
|
|
313
|
|
314 g_return_val_if_fail(gsc != NULL, 0);
|
|
315 g_return_val_if_fail(data != NULL, 0);
|
|
316 g_return_val_if_fail(len > 0, 0);
|
|
317
|
|
318 return PR_Read(ssl_data->nss_in, data, len);
|
|
319 #else
|
|
320 return 0;
|
|
321 #endif
|
|
322 }
|
|
323
|
|
324 size_t
|
|
325 gaim_ssl_write(GaimSslConnection *gsc, const void *data, size_t len)
|
|
326 {
|
|
327 #ifdef HAVE_NSS
|
|
328 GaimSslData *ssl_data = (GaimSslData *)gsc;
|
|
329
|
|
330 g_return_val_if_fail(gsc != NULL, 0);
|
|
331 g_return_val_if_fail(data != NULL, 0);
|
|
332 g_return_val_if_fail(len > 0, 0);
|
|
333
|
|
334 return PR_Write(ssl_data->nss_in, data, len);
|
|
335 #else
|
|
336 return 0;
|
|
337 #endif
|
|
338 }
|
|
339
|
|
340 void
|
|
341 gaim_ssl_init(void)
|
|
342 {
|
|
343 }
|
|
344
|
|
345 void
|
|
346 gaim_ssl_uninit(void)
|
|
347 {
|
|
348 if (!_nss_initialized)
|
|
349 return;
|
|
350
|
|
351 #ifdef HAVE_NSS
|
|
352 PR_Cleanup();
|
|
353 #endif
|
|
354
|
|
355 _nss_initialized = FALSE;
|
|
356 _nss_methods = NULL;
|
|
357 }
|