1 /***************************************
2 WWWOFFLE - World Wide Web Offline Explorer - Version 2.9j.
3
4 Functions for file input and output using gnutls.
5 ******************/ /******************
6 Written by Andrew M. Bishop
7
8 This file Copyright 2005-2016 Andrew M. Bishop
9 It may be distributed under the GNU Public License, version 2, or
10 any higher version. See section COPYING of the GNU Public license
11 for conditions under which this file may be redistributed.
12 ***************************************/
13
14
15 #include "autoconfig.h"
16
17 #include <stdlib.h>
18 #include <stdio.h>
19 #include <string.h>
20
21 #if TIME_WITH_SYS_TIME
22 # include <sys/time.h>
23 # include <time.h>
24 #else
25 # if HAVE_SYS_TIME_H
26 # include <sys/time.h>
27 # else
28 # include <time.h>
29 # endif
30 #endif
31
32 #include <errno.h>
33
34 #include <setjmp.h>
35 #include <signal.h>
36
37 #if USE_GNUTLS
38 #include <gnutls/gnutls.h>
39 #include <gnutls/x509.h>
40 #endif
41
42 #include "io.h"
43 #include "iopriv.h"
44 #include "errors.h"
45
46
47 #if USE_GNUTLS
48
49 #include "certificates.h"
50
51
52 /*+ A longjump context for write timeouts. +*/
53 static jmp_buf write_jmp_env;
54
55
56 /* Local functions */
57
58 static int pull_with_timeout(gnutls_transport_ptr_t ptr, void* data, size_t size);
59 static int push_with_timeout(gnutls_transport_ptr_t ptr, const void* data, size_t size);
60 static int handshake_sni_callback(gnutls_session_t session);
61
62 static int set_credentials(io_gnutls *context);
63
64 static void sigalarm(int signum);
65
66 static ssize_t write_all(gnutls_session_t session,const char *data,size_t n);
67
68 static void set_gnutls_error(int err,gnutls_session_t session);
69
70
71 /*++++++++++++++++++++++++++++++++++++++
72 Initialise the gnutls context information.
73
74 io_gnutls *io_init_gnutls Returns a new gnutls io context.
75
76 int fd The file descriptor for the session.
77
78 const char *host The name of the server to serve as or NULL for a client.
79
80 int type A flag set to 0 for client connection, 1 for built-in server or 2 for a fake server.
81
82 unsigned timeout_r The read timeout or 0 for none.
83
84 unsigned timeout_w The write timeout or 0 for none.
85 ++++++++++++++++++++++++++++++++++++++*/
86
io_init_gnutls(int fd,const char * host,int type,unsigned timeout_r,unsigned timeout_w)87 io_gnutls *io_init_gnutls(int fd,const char *host,int type,unsigned timeout_r,unsigned timeout_w)
88 {
89 io_gnutls *context=(io_gnutls*)calloc(1,sizeof(io_gnutls));
90
91 /* Initialise the gnutls session. */
92
93 if(type)
94 io_errno=gnutls_init(&context->session,GNUTLS_SERVER);
95 else
96 io_errno=gnutls_init(&context->session,GNUTLS_CLIENT);
97
98 if(io_errno!=GNUTLS_E_SUCCESS)
99 {
100 set_gnutls_error(io_errno,context->session);
101
102 PrintMessage(Warning,"GNUTLS Failed to initialise session [%s].",io_strerror);
103
104 free(context);
105 return(NULL);
106 }
107
108 io_errno=gnutls_set_default_priority(context->session);
109
110 if(io_errno!=GNUTLS_E_SUCCESS)
111 {
112 set_gnutls_error(io_errno,context->session);
113 gnutls_deinit(context->session);
114
115 PrintMessage(Warning,"GNUTLS Failed to set session priority [%s].",io_strerror);
116
117 free(context);
118 return(NULL);
119 }
120
121 /* Set the server credentials (in callback for server mode, now for client) */
122
123 if(type)
124 {
125 context->type=type;
126 context->host=host;
127
128 gnutls_handshake_set_post_client_hello_function(context->session,
129 (gnutls_handshake_post_client_hello_func)handshake_sni_callback);
130 }
131 else /* if(type==0) */
132 {
133 gnutls_server_name_set(context->session,GNUTLS_NAME_DNS,host,strlen(host));
134
135 context->cred=GetClientCredentials();
136
137 if(set_credentials(context))
138 {
139 free(context);
140 return(NULL);
141 }
142 }
143
144 /* Store the file descriptor and timeout and set the push and pull functions. */
145
146 context->fd=fd;
147
148 context->r_timeout=timeout_r;
149 context->w_timeout=timeout_w;
150
151 gnutls_transport_set_ptr(context->session,context);
152
153 gnutls_transport_set_pull_function(context->session,(gnutls_pull_func)pull_with_timeout);
154 gnutls_transport_set_push_function(context->session,(gnutls_push_func)push_with_timeout);
155
156 /* Handshake the session on the socket */
157
158 do
159 {
160 io_errno=gnutls_handshake(context->session);
161 }
162 while(io_errno!=GNUTLS_E_SUCCESS && !gnutls_error_is_fatal(io_errno));
163
164 if(io_errno!=GNUTLS_E_SUCCESS)
165 {
166 set_gnutls_error(io_errno,context->session);
167 gnutls_bye(context->session,GNUTLS_SHUT_WR);
168 gnutls_deinit(context->session);
169
170 PrintMessage(Warning,"GNUTLS handshake has failed [%s].",io_strerror);
171
172 free(context);
173 return(NULL);
174 }
175
176 /* Save the server credentials */
177
178 if(type==0)
179 PutRealCertificate(context->session,host);
180
181 return(context);
182 }
183
184
185 /*++++++++++++++++++++++++++++++++++++++
186 A function to be called by gnutles to read data from the socket.
187
188 int pull_with_timeout Return the number of bytes read or a negative number for an error.
189
190 gnutls_transport_ptr_t ptr The gnutls per-session transport pointer.
191
192 void* data The buffer to fill with data read from the sockeet.
193
194 size_t size The amount of data to read.
195
196 This is necessary because the SNI callback only passes the gnutls session but we
197 need the WWWOFFLE IO context so we have to put that into the gnutls_transport_ptr.
198 Therefore the timeout function is moved into here so that it applies to all gnutls
199 data transfers.
200 ++++++++++++++++++++++++++++++++++++++*/
201
pull_with_timeout(gnutls_transport_ptr_t ptr,void * data,size_t size)202 static int pull_with_timeout(gnutls_transport_ptr_t ptr, void* data, size_t size)
203 {
204 io_gnutls *context=(io_gnutls*)ptr;
205 int n;
206
207 if(context->r_timeout)
208 {
209 fd_set readfd;
210 struct timeval tv;
211
212 while(1)
213 {
214 FD_ZERO(&readfd);
215
216 FD_SET(context->fd,&readfd);
217
218 tv.tv_sec=context->r_timeout;
219 tv.tv_usec=0;
220
221 n=select(context->fd+1,&readfd,NULL,NULL,&tv);
222
223 if(n>0)
224 break;
225 else if(n==0)
226 return(0);
227 else if(errno!=EINTR)
228 return(-1);
229 }
230 }
231
232 n=read(context->fd,data,size);
233
234 return(n);
235 }
236
237
238 /*++++++++++++++++++++++++++++++++++++++
239 The signal handler for the alarm signal to timeout the socket write.
240
241 int signum The signal number.
242 ++++++++++++++++++++++++++++++++++++++*/
243
sigalarm(int signum)244 static void sigalarm(/*@unused@*/ int signum)
245 {
246 longjmp(write_jmp_env,1);
247 }
248
249
250 /*++++++++++++++++++++++++++++++++++++++
251 A function to be called by gnutls to write data to the socket.
252
253 int push_with_timeout Return the number of bytes written or a negative number for an error.
254
255 gnutls_transport_ptr_t ptr The gnutls per-session transport pointer.
256
257 const void* data The buffer of data to write to the sockeet.
258
259 size_t size The amount of data to write.
260
261 This is necessary because the SNI callback only passes the gnutls session but we
262 need the WWWOFFLE IO context so we have to put that into the gnutls_transport_ptr.
263 Therefore the timeout function is moved into here so that it applies to all gnutls
264 data transfers.
265 ++++++++++++++++++++++++++++++++++++++*/
266
push_with_timeout(gnutls_transport_ptr_t ptr,const void * data,size_t size)267 static int push_with_timeout(gnutls_transport_ptr_t ptr, const void* data, size_t size)
268 {
269 io_gnutls *context=(io_gnutls*)ptr;
270 struct sigaction action;
271 int n;
272
273 start:
274
275 if(!context->w_timeout)
276 {
277 n=write(context->fd,data,size);
278
279 return(n);
280 }
281
282 action.sa_handler = sigalarm;
283 sigemptyset(&action.sa_mask);
284 action.sa_flags = 0;
285 if(sigaction(SIGALRM, &action, NULL) != 0)
286 {
287 PrintMessage(Warning, "Failed to set SIGALRM; cancelling timeout for writing.");
288 context->w_timeout=0;
289 goto start;
290 }
291
292 alarm(context->w_timeout);
293
294 if(setjmp(write_jmp_env))
295 {
296 n=-1;
297 errno=ETIMEDOUT;
298 }
299 else
300 n=write(context->fd,data,size);
301
302 alarm(0);
303 action.sa_handler = SIG_IGN;
304 sigemptyset(&action.sa_mask);
305 action.sa_flags = 0;
306 if(sigaction(SIGALRM, &action, NULL) != 0)
307 PrintMessage(Warning, "Failed to clear SIGALRM.");
308
309 return(n);
310 }
311
312
313 /*++++++++++++++++++++++++++++++++++++++
314 A callback to handle the TLS SNI extension.
315
316 int handshake_sni_callback Returns 0 if OK, something else otherwise.
317
318 gnutls_session_t session The GNUTLS session information.
319 ++++++++++++++++++++++++++++++++++++++*/
320
handshake_sni_callback(gnutls_session_t session)321 static int handshake_sni_callback(gnutls_session_t session)
322 {
323 int err;
324 size_t sni_len=40;
325 unsigned int sni_type;
326 char *sni_name=NULL;
327 io_gnutls *context;
328
329 context=gnutls_transport_get_ptr(session);
330
331 /* Work out the server requested (using SNI) */
332
333 sni_name=(char*)malloc(sni_len);
334
335 err=gnutls_server_name_get(context->session,sni_name,&sni_len,&sni_type,0);
336
337 if(err==GNUTLS_E_SHORT_MEMORY_BUFFER)
338 {
339 sni_name=(char*)realloc((void*)sni_name,sni_len);
340
341 err=gnutls_server_name_get(context->session,sni_name,&sni_len,&sni_type,0);
342 }
343
344 if(err==GNUTLS_E_SUCCESS && sni_type==GNUTLS_NAME_DNS)
345 PrintMessage(Inform,"GNUTLS SNI server name was '%s'.",sni_name);
346 else
347 {
348 if(err==GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
349 PrintMessage(Inform,"GNUTLS No SNI server name found, using '%s'.",context->host);
350 else if(err==GNUTLS_E_SUCCESS && sni_type!=GNUTLS_NAME_DNS)
351 PrintMessage(Warning,"GNUTLS Requested SNI server name was not a DNS name [type=%d], using '%s'.",sni_type,context->host);
352 else
353 PrintMessage(Warning,"GNUTLS Request for SNI server name returned an error [%s], using '%s'.",gnutls_strerror(err),context->host);
354
355 free(sni_name);
356 sni_name=NULL;
357 }
358
359 /* Set the server credentials */
360
361 if(context->type==1)
362 {
363 if(sni_name)
364 {
365 context->cred=GetServerCredentials(sni_name);
366 free(sni_name);
367 }
368 else
369 context->cred=GetServerCredentials(context->host);
370 }
371 else /* if(context->type==2) */
372 {
373 if(sni_name)
374 {
375 context->cred=GetFakeCredentials(sni_name);
376 free(sni_name);
377 }
378 else
379 context->cred=GetFakeCredentials(context->host);
380 }
381
382 return(set_credentials(context));
383 }
384
385
386 /*++++++++++++++++++++++++++++++++++++++
387 Set the GNUTLS credentials.
388
389 int set_credentials Return 0 if OK or something else otherwise.
390
391 io_gnutls *context The gnutls context information.
392
393 This is only in a separate function because it needs to be called both
394 from the SNI callback function and from the main io_init_gnutls function.
395 ++++++++++++++++++++++++++++++++++++++*/
396
set_credentials(io_gnutls * context)397 static int set_credentials(io_gnutls *context)
398 {
399 if(!context->cred)
400 {
401 if(io_strerror)
402 free(io_strerror);
403 io_strerror=(char*)malloc(40);
404
405 strcpy(io_strerror,"IO(gnutls): Failed to get credentials");
406
407 PrintMessage(Warning,"GNUTLS Failed to get server credentials [%s].",io_strerror);
408
409 return(-1);
410 }
411
412 io_errno=gnutls_credentials_set(context->session,GNUTLS_CRD_CERTIFICATE,context->cred);
413
414 if(io_errno!=GNUTLS_E_SUCCESS)
415 {
416 set_gnutls_error(io_errno,context->session);
417
418 PrintMessage(Warning,"GNUTLS Failed to set session credentials [%s].",io_strerror);
419
420 return(-1);
421 }
422
423 return(0);
424 }
425
426
427 /*++++++++++++++++++++++++++++++++++++++
428 Finalise the gnutls data stream.
429
430 int io_finish_gnutls Returns 0 on completion, negative if error.
431
432 io_gnutls *context The gnutls context information.
433 ++++++++++++++++++++++++++++++++++++++*/
434
io_finish_gnutls(io_gnutls * context)435 int io_finish_gnutls(io_gnutls *context)
436 {
437 gnutls_bye(context->session,GNUTLS_SHUT_WR);
438
439 gnutls_deinit(context->session);
440
441 FreeCredentials(context->cred);
442
443 free(context);
444
445 return(0);
446 }
447
448
449 /*++++++++++++++++++++++++++++++++++++++
450 Read data from a gnutls session and buffer it with a timeout.
451
452 ssize_t io_gnutls_read_with_timeout Returns the number of bytes read.
453
454 io_gnutls *context The gnutls context information.
455
456 io_buffer *out The IO buffer to output the data.
457
458 unsigned timeout The maximum time to wait for data to be read (or 0 for no timeout).
459 ++++++++++++++++++++++++++++++++++++++*/
460
io_gnutls_read_with_timeout(io_gnutls * context,io_buffer * out,unsigned timeout)461 ssize_t io_gnutls_read_with_timeout(io_gnutls *context,io_buffer *out,unsigned timeout)
462 {
463 int n;
464
465 context->r_timeout=timeout;
466
467 do
468 {
469 n=gnutls_record_recv(context->session,out->data+out->length,out->size-out->length);
470 }
471 while(n==GNUTLS_E_INTERRUPTED || n==GNUTLS_E_AGAIN);
472
473 if(n==GNUTLS_E_REHANDSHAKE)
474 gnutls_alert_send(context->session,GNUTLS_AL_WARNING,GNUTLS_A_NO_RENEGOTIATION);
475
476 if(n==GNUTLS_E_UNEXPECTED_PACKET_LENGTH ||
477 n==GNUTLS_E_PREMATURE_TERMINATION ||
478 n==GNUTLS_E_INVALID_SESSION) /* Seems to happen at the end of the data. */
479 n=0;
480
481 if(n>0)
482 out->length+=n;
483
484 if(n<0)
485 set_gnutls_error(n,context->session);
486
487 return(n);
488 }
489
490
491 /*++++++++++++++++++++++++++++++++++++++
492 Write some data to a gnutls session from a buffer with a timeout.
493
494 ssize_t io_gnutls_write_with_timeout Returns the number of bytes written or negative on error.
495
496 io_gnutls *context The gnutls context information.
497
498 io_buffer *in The IO buffer with the input data.
499
500 unsigned timeout The maximum time to wait for data to be written (or 0 for no timeout).
501 ++++++++++++++++++++++++++++++++++++++*/
502
io_gnutls_write_with_timeout(io_gnutls * context,io_buffer * in,unsigned timeout)503 ssize_t io_gnutls_write_with_timeout(io_gnutls *context,io_buffer *in,unsigned timeout)
504 {
505 int n;
506
507 if(in->length==0)
508 return(0);
509
510 context->w_timeout=timeout;
511
512 if(in->length>(4*IO_BUFFER_SIZE))
513 {
514 size_t offset;
515 io_buffer temp;
516
517 temp.size=in->size;
518
519 for(offset=0;offset<in->length;offset+=IO_BUFFER_SIZE)
520 {
521 temp.data=in->data+offset;
522
523 temp.length=in->length-offset;
524 if(temp.length>IO_BUFFER_SIZE)
525 temp.length=IO_BUFFER_SIZE;
526
527 n=io_gnutls_write_with_timeout(context,&temp,timeout);
528
529 if(n<0)
530 {
531 in->length=0;
532 return(n);
533 }
534 }
535
536 in->length=0;
537 return(in->length);
538 }
539
540 n=write_all(context->session,in->data,in->length);
541
542 in->length=0;
543 return(n);
544 }
545
546
547 /*++++++++++++++++++++++++++++++++++++++
548 A function to write all of a buffer of data to a gnutls session.
549
550 ssize_t write_all Returns the number of bytes written.
551
552 gnutls_session_t session The gnutls session.
553
554 const char *data The data buffer to write.
555
556 size_t n The number of bytes to write.
557 ++++++++++++++++++++++++++++++++++++++*/
558
write_all(gnutls_session_t session,const char * data,size_t n)559 static ssize_t write_all(gnutls_session_t session,const char *data,size_t n)
560 {
561 int nn=0;
562
563 /* Unroll the first loop to optimise the obvious case. */
564
565 do
566 {
567 nn=gnutls_record_send(session,data,n);
568 }
569 while(nn==GNUTLS_E_INTERRUPTED || nn==GNUTLS_E_AGAIN);
570
571 if(nn<0 || nn==n)
572 return(nn);
573
574 /* Loop around until the data is finished. */
575
576 do
577 {
578 int m;
579
580 do
581 {
582 m=gnutls_record_send(session,data+nn,n-nn);
583 }
584 while(m==GNUTLS_E_INTERRUPTED || m==GNUTLS_E_AGAIN);
585
586 if(m<0)
587 {n=m;break;}
588 else
589 nn+=m;
590 }
591 while(nn<n);
592
593 return(n);
594 }
595
596
597 /*++++++++++++++++++++++++++++++++++++++
598 Set the error status when there is a gnutls error.
599
600 int err The error number.
601
602 gnutls_session_t session The session information if one is active.
603 ++++++++++++++++++++++++++++++++++++++*/
604
set_gnutls_error(int err,gnutls_session_t session)605 static void set_gnutls_error(int err,gnutls_session_t session)
606 {
607 const char *type,*msg;
608
609 if(err==GNUTLS_E_WARNING_ALERT_RECEIVED)
610 type="Warning Alert:";
611 else if(err==GNUTLS_E_FATAL_ALERT_RECEIVED)
612 type="Fatal Alert:";
613 else
614 type="Error:";
615
616 if(err==GNUTLS_E_WARNING_ALERT_RECEIVED || err==GNUTLS_E_FATAL_ALERT_RECEIVED)
617 {
618 if(session)
619 msg=gnutls_alert_get_name(gnutls_alert_get(session));
620 else
621 msg="No session info";
622 }
623 else
624 msg=gnutls_strerror(err);
625
626 errno=ERRNO_USE_IO_ERRNO;
627
628 if(io_strerror)
629 free(io_strerror);
630 io_strerror=(char*)malloc(16+strlen(type)+strlen(msg)+1);
631
632 sprintf(io_strerror,"IO(gnutls): %s %s",type,msg);
633 }
634
635 #endif /* USE_GNUTLS */
636