]> code.citadel.org Git - citadel.git/blob - citadel/client_crypto.c
* Allow multiple simultaneous IPC connections. All changes necessary for
[citadel.git] / citadel / client_crypto.c
1 /* $Id$ */
2
3 #include "sysdep.h"
4 #ifdef HAVE_PTHREAD_H
5 #include <pthread.h>
6 #endif
7 #include <unistd.h>
8 #include <sys/types.h>
9 #include "citadel.h"
10 #include "citadel_ipc.h"
11
12 #ifdef HAVE_OPENSSL
13 static SSL_CTX *ssl_ctx;
14 char arg_encrypt;
15 char rc_encrypt;
16 #ifdef THREADED_CLIENT
17 pthread_mutex_t **Critters;                     /* Things that need locking */
18 #endif /* THREADED_CLIENT */
19
20 #endif /* HAVE_OPENSSL */
21
22
23 static void (*status_hook)(char *s) = NULL;
24
25 void setCryptoStatusHook(void (*hook)(char *s)) {
26         status_hook = hook;
27 }
28
29
30 #ifdef HAVE_OPENSSL
31 /*
32  * input binary data from encrypted connection
33  */
34 void serv_read_ssl(CtdlIPC* ipc, char *buf, int bytes)
35 {
36         int len, rlen;
37         char junk[1];
38
39         len = 0;
40         while (len < bytes) {
41                 if (SSL_want_read(ipc->ssl)) {
42                         if ((SSL_write(ipc->ssl, junk, 0)) < 1) {
43                                 error_printf("SSL_write in serv_read:\n");
44                                 ERR_print_errors_fp(stderr);
45                         }
46                 }
47                 rlen = SSL_read(ipc->ssl, &buf[len], bytes - len);
48                 if (rlen < 1) {
49                         long errval;
50
51                         errval = SSL_get_error(ipc->ssl, rlen);
52                         if (errval == SSL_ERROR_WANT_READ ||
53                                         errval == SSL_ERROR_WANT_WRITE) {
54                                 sleep(1);
55                                 continue;
56                         }
57                         if (errval == SSL_ERROR_ZERO_RETURN ||
58                                         errval == SSL_ERROR_SSL) {
59                                 serv_read(ipc, &buf[len], bytes - len);
60                                 return;
61                         }
62                         error_printf("SSL_read in serv_read:\n");
63                         ERR_print_errors_fp(stderr);
64                         connection_died();
65                         return;
66                 }
67                 len += rlen;
68         }
69 }
70
71
72 /*
73  * send binary to server encrypted
74  */
75 void serv_write_ssl(CtdlIPC *ipc, const char *buf, int nbytes)
76 {
77         int bytes_written = 0;
78         int retval;
79         char junk[1];
80
81         while (bytes_written < nbytes) {
82                 if (SSL_want_write(ipc->ssl)) {
83                         if ((SSL_read(ipc->ssl, junk, 0)) < 1) {
84                                 error_printf("SSL_read in serv_write:\n");
85                                 ERR_print_errors_fp(stderr);
86                         }
87                 }
88                 retval = SSL_write(ipc->ssl, &buf[bytes_written],
89                                 nbytes - bytes_written);
90                 if (retval < 1) {
91                         long errval;
92
93                         errval = SSL_get_error(ipc->ssl, retval);
94                         if (errval == SSL_ERROR_WANT_READ ||
95                                         errval == SSL_ERROR_WANT_WRITE) {
96                                 sleep(1);
97                                 continue;
98                         }
99                         if (errval == SSL_ERROR_ZERO_RETURN ||
100                                         errval == SSL_ERROR_SSL) {
101                                 serv_write(ipc, &buf[bytes_written],
102                                                 nbytes - bytes_written);
103                                 return;
104                         }
105                         error_printf("SSL_write in serv_write:\n");
106                         ERR_print_errors_fp(stderr);
107                         connection_died();
108                         return;
109                 }
110                 bytes_written += retval;
111         }
112 }
113
114
115 void ssl_lock(int mode, int n, const char *file, int line)
116 {
117 #ifdef THREADED_CLIENT
118         if (mode & CRYPTO_LOCK)
119                 pthread_mutex_lock(Critters[n]);
120         else
121                 pthread_mutex_unlock(Critters[n]);
122 #endif /* THREADED_CLIENT */
123 }
124 #endif /* HAVE_OPENSSL */
125
126 #if defined(THREADED_CLIENT) && defined(HAVE_OPENSSL)
127 static unsigned long id_callback(void) {
128         return (unsigned long)pthread_self();
129 }
130 #endif
131
132 /* FIXME: per application not per ipc */
133 /*
134  * starttls() starts SSL/TLS if possible
135  * Returns 1 if the session is encrypted, 0 otherwise
136  */
137 int starttls(CtdlIPC *ipc)
138 {
139 #ifdef HAVE_OPENSSL
140         int a;
141         int r;                          /* IPC response code */
142         char buf[SIZ];
143         SSL_METHOD *ssl_method;
144         DH *dh;
145         
146         /* Figure out whether to encrypt the session based on user options */
147         /* User request to disable encryption */
148         if (arg_encrypt == RC_NO || rc_encrypt == RC_NO) {
149                 return 0;
150         }
151         /* User expressed no preference */
152         else if (rc_encrypt == RC_DEFAULT && arg_encrypt == RC_DEFAULT &&
153                         ipc->isLocal) {
154                 return 0;
155         }
156
157         /* Get started */
158         ipc->ssl = NULL;
159         ssl_ctx = NULL;
160         dh = NULL;
161         SSL_load_error_strings();
162         SSLeay_add_ssl_algorithms();
163
164         /* Set up the SSL context in which we will oeprate */
165         ssl_method = SSLv23_client_method();
166         ssl_ctx = SSL_CTX_new(ssl_method);
167         if (!ssl_ctx) {
168                 error_printf("SSL_CTX_new failed: %s\n",
169                                 ERR_reason_error_string(ERR_get_error()));
170                 return 0;
171         }
172         /* Any reasonable cipher we can get */
173         if (!(SSL_CTX_set_cipher_list(ssl_ctx, CIT_CIPHERS))) {
174                 error_printf("No ciphers available for encryption\n");
175                 endtls(ipc);
176                 return 0;
177         }
178         SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_BOTH);
179         
180         /* Load DH parameters into the context */
181         dh = DH_new();
182         if (!dh) {
183                 error_printf("Can't allocate a DH object: %s\n",
184                                 ERR_reason_error_string(ERR_get_error()));
185                 endtls(ipc);
186                 return 0;
187         }
188         if (!(BN_hex2bn(&(dh->p), DH_P))) {
189                 error_printf("Can't assign DH_P: %s\n",
190                                 ERR_reason_error_string(ERR_get_error()));
191                 DH_free(dh);
192                 endtls(ipc);
193                 return 0;
194         }
195         if (!(BN_hex2bn(&(dh->g), DH_G))) {
196                 error_printf("Can't assign DH_G: %s\n",
197                                 ERR_reason_error_string(ERR_get_error()));
198                 DH_free(dh);
199                 endtls(ipc);
200                 return 0;
201         }
202         dh->length = DH_L;
203         SSL_CTX_set_tmp_dh(ssl_ctx, dh);
204         DH_free(dh);
205
206 #ifdef THREADED_CLIENT
207         /* OpenSSL requires callbacks for threaded clients */
208         CRYPTO_set_locking_callback(ssl_lock);
209         CRYPTO_set_id_callback(id_callback);
210
211         /* OpenSSL requires us to do semaphores for threaded clients */
212         Critters = malloc(CRYPTO_num_locks() * sizeof (pthread_mutex_t *));
213         if (!Critters) {
214                 perror("malloc failed");
215                 exit(1);
216         } else {
217                 for (a = 0; a < CRYPTO_num_locks(); a++) {
218                         Critters[a] = malloc(sizeof (pthread_mutex_t));
219                         if (!Critters[a]) {
220                                 perror("malloc failed");
221                                 exit(1);
222                         }
223                         pthread_mutex_init(Critters[a], NULL);
224                 }
225         }
226 #endif /* THREADED_CLIENT */
227
228         /* New SSL object */
229         ipc->ssl = SSL_new(ssl_ctx);
230         if (!ipc->ssl) {
231                 error_printf("SSL_new failed: %s\n",
232                                 ERR_reason_error_string(ERR_get_error()));
233                 endtls(ipc);
234                 return 0;
235         }
236         /* Pointless flag waving */
237 #if SSLEAY_VERSION_NUMBER >= 0x0922
238         SSL_set_session_id_context(ipc->ssl, "Citadel/UX SID", 14);
239 #endif
240
241         if (!access("/var/run/egd-pool", F_OK))
242                 RAND_egd("/var/run/egd-pool");
243
244         if (!RAND_status()) {
245                 error_printf("PRNG not properly seeded\n");
246                 endtls(ipc);
247                 return 0;
248         }
249
250         /* Associate network connection with SSL object */
251         if (SSL_set_fd(ipc->ssl, ipc->sock) < 1) {
252                 error_printf("SSL_set_fd failed: %s\n",
253                                 ERR_reason_error_string(ERR_get_error()));
254                 endtls(ipc);
255                 return 0;
256         }
257
258         if (status_hook != NULL)
259                 status_hook("Requesting encryption...\r");
260
261         /* Ready to start SSL/TLS */
262         /* Old code
263         CtdlIPC_putline(ipc, "STLS");
264         CtdlIPC_getline(ipc, buf);
265         if (buf[0] != '2') {
266                 error_printf("Server can't start TLS: %s\n", buf);
267                 return 0;
268         }
269         */
270         {
271                 /*
272                  * We can't have ipc->ssl set when we call StartEncryption()
273                  * because the connection isn't yet encrypted.  So we fake it.
274                  */
275                 SSL *temp_ssl;
276
277                 temp_ssl = ipc->ssl;
278                 ipc->ssl = NULL;
279                 r = CtdlIPCStartEncryption(ipc, buf);
280                 ipc->ssl = temp_ssl;
281                 if (r / 100 != 2) {
282                         error_printf("Server can't start TLS: %s\n", buf);
283                         return 0;
284                 }
285         }
286
287         /* Do SSL/TLS handshake */
288         if ((a = SSL_connect(ipc->ssl)) < 1) {
289                 error_printf("SSL_connect failed: %s\n",
290                                 ERR_reason_error_string(ERR_get_error()));
291                 endtls(ipc);
292                 return 0;
293         }
294         BIO_set_close(ipc->ssl->rbio, BIO_NOCLOSE);
295         {
296                 int bits, alg_bits;
297
298                 bits = SSL_CIPHER_get_bits(SSL_get_current_cipher(ipc->ssl), &alg_bits);
299                 error_printf("Encrypting with %s cipher %s (%d of %d bits)\n",
300                                 SSL_CIPHER_get_version(SSL_get_current_cipher(ipc->ssl)),
301                                 SSL_CIPHER_get_name(SSL_get_current_cipher(ipc->ssl)),
302                                 bits, alg_bits);
303         }
304         return 1;
305 #else
306         return 0;
307 #endif /* HAVE_OPENSSL */
308 }
309
310
311 /*
312  * void endtls() - end SSL/TLS session
313  */
314 void endtls(CtdlIPC *ipc)
315 {
316 #ifdef HAVE_OPENSSL
317         if (ipc->ssl) {
318                 SSL_shutdown(ipc->ssl);
319                 SSL_free(ipc->ssl);
320                 ipc->ssl = NULL;
321         }
322 #endif
323 }