1 # Copyright 2016-2025 The OpenSSL Project Authors. All Rights Reserved. 2 # 3 # Licensed under the Apache License 2.0 (the "License"). You may not use 4 # this file except in compliance with the License. You can obtain a copy 5 # in the file LICENSE in the source distribution or at 6 # https://www.openssl.org/source/license.html 7 8 use strict; 9 use POSIX ":sys_wait_h"; 10 11 package TLSProxy::Proxy; 12 13 use File::Spec; 14 use IO::Socket; 15 use IO::Select; 16 use TLSProxy::Record; 17 use TLSProxy::Message; 18 use TLSProxy::ClientHello; 19 use TLSProxy::ServerHello; 20 use TLSProxy::HelloVerifyRequest; 21 use TLSProxy::EncryptedExtensions; 22 use TLSProxy::Certificate; 23 use TLSProxy::CertificateRequest; 24 use TLSProxy::CertificateVerify; 25 use TLSProxy::ServerKeyExchange; 26 use TLSProxy::NewSessionTicket; 27 use TLSProxy::NextProto; 28 29 my $have_IPv6; 30 my $useINET6; 31 my $IP_factory; 32 33 BEGIN 34 { 35 # IO::Socket::IP is on the core module list, IO::Socket::INET6 isn't. 36 # However, IO::Socket::INET6 is older and is said to be more widely 37 # deployed for the moment, and may have less bugs, so we try the latter 38 # first, then fall back on the core modules. Worst case scenario, we 39 # fall back to IO::Socket::INET, only supports IPv4. 40 eval { 41 require IO::Socket::INET6; 42 my $s = IO::Socket::INET6->new( 43 LocalAddr => "::1", 44 LocalPort => 0, 45 Listen=>1, 46 ); 47 $s or die "\n"; 48 $s->close(); 49 }; 50 if ($@ eq "") { 51 $IP_factory = sub { IO::Socket::INET6->new(Domain => AF_INET6, @_); }; 52 $have_IPv6 = 1; 53 $useINET6 = 1; 54 } else { 55 eval { 56 require IO::Socket::IP; 57 my $s = IO::Socket::IP->new( 58 LocalAddr => "::1", 59 LocalPort => 0, 60 Listen=>1, 61 ); 62 $s or die "\n"; 63 $s->close(); 64 }; 65 if ($@ eq "") { 66 $IP_factory = sub { IO::Socket::IP->new(@_); }; 67 $have_IPv6 = 1; 68 $useINET6 = 0; 69 } else { 70 $IP_factory = sub { IO::Socket::INET->new(@_); }; 71 $have_IPv6 = 0; 72 $useINET6 = 0; 73 } 74 } 75 } 76 77 my $is_tls13 = 0; 78 my $ciphersuite = undef; 79 80 sub new { 81 my $class = shift; 82 my ($filter, 83 $execute, 84 $cert, 85 $debug) = @_; 86 return init($class, $filter, $execute, $cert, $debug, 0); 87 } 88 89 sub new_dtls { 90 my $class = shift; 91 my ($filter, 92 $execute, 93 $cert, 94 $debug) = @_; 95 return init($class, $filter, $execute, $cert, $debug, 1); 96 } 97 98 sub init 99 { 100 my $useSockInet = 0; 101 eval { 102 require IO::Socket::IP; 103 my $s = IO::Socket::IP->new( 104 LocalAddr => "::1", 105 LocalPort => 0, 106 Listen=>1, 107 ); 108 $s or die "\n"; 109 $s->close(); 110 }; 111 if ($@ eq "") { 112 require IO::Socket::IP; 113 } else { 114 $useSockInet = 1; 115 } 116 117 my $class = shift; 118 my ($filter, 119 $execute, 120 $cert, 121 $debug, 122 $isdtls) = @_; 123 124 my $test_client_port; 125 126 # Sometimes, our random selection of client ports gets unlucky 127 # And we randomly select a port that's already in use. This causes 128 # this test to fail, so lets harden ourselves against that by doing 129 # a test bind to the randomly selected port, and only continue once we 130 # find a port that's available. 131 my $test_client_addr = $have_IPv6 ? "[::1]" : "127.0.0.1"; 132 my $found_port = 0; 133 for (my $i = 0; $i <= 10; $i++) { 134 $test_client_port = 49152 + int(rand(65535 - 49152)); 135 my $test_sock; 136 if ($useINET6 == 0) { 137 if ($useSockInet == 0) { 138 $test_sock = IO::Socket::IP->new(LocalPort => $test_client_port, 139 LocalAddr => $test_client_addr); 140 } else { 141 $test_sock = IO::Socket::INET->new(LocalAddr => $test_client_addr, 142 LocalPort => $test_client_port); 143 } 144 } else { 145 $test_sock = IO::Socket::INET6->new(LocalAddr => $test_client_addr, 146 LocalPort => $test_client_port, 147 Domain => AF_INET6); 148 } 149 if ($test_sock) { 150 $found_port = 1; 151 $test_sock->close(); 152 print "Found available client port ${test_client_port}\n"; 153 last; 154 } 155 print "Port ${test_client_port} in use - $@\n"; 156 } 157 158 if ($found_port == 0) { 159 die "Unable to find usable port for TLSProxy"; 160 } 161 162 my $self = { 163 #Public read/write 164 proxy_addr => $test_client_addr, 165 client_addr => $test_client_addr, 166 filter => $filter, 167 serverflags => "", 168 clientflags => "", 169 serverconnects => 1, 170 reneg => 0, 171 sessionfile => undef, 172 173 #Public read 174 isdtls => $isdtls, 175 proxy_port => 0, 176 client_port => $test_client_port, 177 server_port => 0, 178 serverpid => 0, 179 clientpid => 0, 180 execute => $execute, 181 cert => $cert, 182 debug => $debug, 183 cipherc => "", 184 ciphersuitesc => "", 185 ciphers => "AES128-SHA", 186 ciphersuitess => "TLS_AES_128_GCM_SHA256", 187 flight => -1, 188 direction => -1, 189 partial => ["", ""], 190 record_list => [], 191 message_list => [], 192 }; 193 194 return bless $self, $class; 195 } 196 197 sub DESTROY 198 { 199 my $self = shift; 200 201 $self->{proxy_sock}->close() if $self->{proxy_sock}; 202 } 203 204 sub clearClient 205 { 206 my $self = shift; 207 208 $self->{cipherc} = ""; 209 $self->{ciphersuitec} = ""; 210 $self->{flight} = -1; 211 $self->{direction} = -1; 212 $self->{partial} = ["", ""]; 213 $self->{record_list} = []; 214 $self->{message_list} = []; 215 $self->{clientflags} = ""; 216 $self->{sessionfile} = undef; 217 $self->{clientpid} = 0; 218 $is_tls13 = 0; 219 $ciphersuite = undef; 220 221 TLSProxy::Message->clear(); 222 TLSProxy::Record->clear(); 223 } 224 225 sub clear 226 { 227 my $self = shift; 228 229 $self->clearClient; 230 $self->{ciphers} = "AES128-SHA"; 231 $self->{ciphersuitess} = "TLS_AES_128_GCM_SHA256"; 232 $self->{serverflags} = ""; 233 $self->{serverconnects} = 1; 234 $self->{serverpid} = 0; 235 $self->{reneg} = 0; 236 } 237 238 sub restart 239 { 240 my $self = shift; 241 242 $self->clear; 243 $self->start; 244 } 245 246 sub clientrestart 247 { 248 my $self = shift; 249 250 $self->clear; 251 $self->clientstart; 252 } 253 254 sub connect_to_server 255 { 256 my $self = shift; 257 my $servaddr = $self->{server_addr}; 258 259 $servaddr =~ s/[\[\]]//g; # Remove [ and ] 260 261 my $sock = $IP_factory->(PeerAddr => $servaddr, 262 PeerPort => $self->{server_port}, 263 Proto => $self->{isdtls} ? 'udp' : 'tcp'); 264 if (!defined($sock)) { 265 my $err = $!; 266 kill(3, $self->{real_serverpid}); 267 die "unable to connect: $err\n"; 268 } 269 270 $self->{server_sock} = $sock; 271 } 272 273 sub start 274 { 275 my ($self) = shift; 276 my $pid; 277 278 # Create the Proxy socket 279 my $proxaddr = $self->{proxy_addr}; 280 $proxaddr =~ s/[\[\]]//g; # Remove [ and ] 281 my $clientaddr = $self->{client_addr}; 282 $clientaddr =~ s/[\[\]]//g; # Remove [ and ] 283 284 my @proxyargs; 285 286 if ($self->{isdtls}) { 287 @proxyargs = ( 288 LocalHost => $proxaddr, 289 LocalPort => 0, 290 PeerHost => $clientaddr, 291 PeerPort => $self->{client_port}, 292 Proto => "udp", 293 ); 294 } else { 295 @proxyargs = ( 296 LocalHost => $proxaddr, 297 LocalPort => 0, 298 Proto => "tcp", 299 Listen => SOMAXCONN, 300 ); 301 } 302 303 if (my $sock = $IP_factory->(@proxyargs)) { 304 $self->{proxy_sock} = $sock; 305 $self->{proxy_port} = $sock->sockport(); 306 $self->{proxy_addr} = $sock->sockhost(); 307 $self->{proxy_addr} =~ s/(.*:.*)/[$1]/; 308 print "Proxy started on port ", 309 "$self->{proxy_addr}:$self->{proxy_port}\n"; 310 # use same address for s_server 311 $self->{server_addr} = $self->{proxy_addr}; 312 } else { 313 warn "Failed creating proxy socket (".$proxaddr.",0): $!\n"; 314 } 315 316 if ($self->{proxy_sock} == 0) { 317 return 0; 318 } 319 320 my $execcmd = $self->execute 321 ." s_server -no_comp -engine ossltest -state" 322 #In TLSv1.3 we issue two session tickets. The default session id 323 #callback gets confused because the ossltest engine causes the same 324 #session id to be created twice due to the changed random number 325 #generation. Using "-ext_cache" replaces the default callback with a 326 #different one that doesn't get confused. 327 ." -ext_cache" 328 ." -accept $self->{server_addr}:0" 329 ." -cert ".$self->cert." -cert2 ".$self->cert 330 ." -naccept ".$self->serverconnects; 331 if ($self->{isdtls}) { 332 $execcmd .= " -dtls -max_protocol DTLSv1.2" 333 # TLSProxy does not support message fragmentation. So 334 # set a high mtu and fingers crossed. 335 ." -mtu 1500"; 336 } else { 337 $execcmd .= " -rev -max_protocol TLSv1.3"; 338 } 339 if ($self->ciphers ne "") { 340 $execcmd .= " -cipher ".$self->ciphers; 341 } 342 if ($self->ciphersuitess ne "") { 343 $execcmd .= " -ciphersuites ".$self->ciphersuitess; 344 } 345 if ($self->serverflags ne "") { 346 $execcmd .= " ".$self->serverflags; 347 } 348 if ($self->debug) { 349 print STDERR "Server command: $execcmd\n"; 350 } 351 352 open(my $savedin, "<&STDIN"); 353 354 # Temporarily replace STDIN so that sink process can inherit it... 355 open(STDIN, "$^X -e 'sleep(10)' |") if $self->{isdtls}; 356 $pid = open(STDIN, "$execcmd 2>&1 |") or die "Failed to $execcmd: $!\n"; 357 $self->{real_serverpid} = $pid; 358 359 # Process the output from s_server until we find the ACCEPT line, which 360 # tells us what the accepting address and port are. 361 while (<>) { 362 print; 363 s/\R$//; # Better chomp 364 next unless (/^ACCEPT\s.*:(\d+)$/); 365 $self->{server_port} = $1; 366 last; 367 } 368 369 if ($self->{server_port} == 0) { 370 # This actually means that s_server exited, because otherwise 371 # we would still searching for ACCEPT... 372 waitpid($pid, 0); 373 die "no ACCEPT detected in '$execcmd' output: $?\n"; 374 } 375 376 # Just make sure everything else is simply printed [as separate lines]. 377 # The sub process simply inherits our STD* and will keep consuming 378 # server's output and printing it as long as there is anything there, 379 # out of our way. 380 my $error; 381 $pid = undef; 382 if (eval { require Win32::Process; 1; }) { 383 if (Win32::Process::Create(my $h, $^X, "perl -ne print", 0, 0, ".")) { 384 $pid = $h->GetProcessID(); 385 $self->{proc_handle} = $h; # hold handle till next round [or exit] 386 } else { 387 $error = Win32::FormatMessage(Win32::GetLastError()); 388 } 389 } else { 390 if (defined($pid = fork)) { 391 $pid or exec("$^X -ne print") or exit($!); 392 } else { 393 $error = $!; 394 } 395 } 396 397 # Change back to original stdin 398 open(STDIN, "<&", $savedin); 399 close($savedin); 400 401 if (!defined($pid)) { 402 kill(3, $self->{real_serverpid}); 403 die "Failed to capture s_server's output: $error\n"; 404 } 405 406 $self->{serverpid} = $pid; 407 408 print STDERR "Server responds on ", 409 "$self->{server_addr}:$self->{server_port}\n"; 410 411 # Connect right away... 412 $self->connect_to_server(); 413 414 return $self->clientstart; 415 } 416 417 sub clientstart 418 { 419 my ($self) = shift; 420 421 my $success = 1; 422 423 if ($self->execute) { 424 my $pid; 425 my $execcmd = $self->execute 426 ." s_client -engine ossltest" 427 ." -connect $self->{proxy_addr}:$self->{proxy_port}"; 428 if ($self->{isdtls}) { 429 $execcmd .= " -dtls -max_protocol DTLSv1.2" 430 # TLSProxy does not support message fragmentation. So 431 # set a high mtu and fingers crossed. 432 ." -mtu 1500" 433 # UDP has no "accept" for sockets which means we need to 434 # know were to send data back to. 435 ." -bind $self->{client_addr}:$self->{client_port}"; 436 } else { 437 $execcmd .= " -max_protocol TLSv1.3"; 438 } 439 if ($self->cipherc ne "") { 440 $execcmd .= " -cipher ".$self->cipherc; 441 } 442 if ($self->ciphersuitesc ne "") { 443 $execcmd .= " -ciphersuites ".$self->ciphersuitesc; 444 } 445 if ($self->clientflags ne "") { 446 $execcmd .= " ".$self->clientflags; 447 } 448 if ($self->clientflags !~ m/-(no)?servername/) { 449 $execcmd .= " -servername localhost"; 450 } 451 if (defined $self->sessionfile) { 452 $execcmd .= " -ign_eof"; 453 } 454 if ($self->debug) { 455 print STDERR "Client command: $execcmd\n"; 456 } 457 458 open(my $savedout, ">&STDOUT"); 459 # If we open pipe with new descriptor, attempt to close it, 460 # explicitly or implicitly, would incur waitpid and effectively 461 # dead-lock... 462 if (!($pid = open(STDOUT, "| $execcmd"))) { 463 my $err = $!; 464 kill(3, $self->{real_serverpid}); 465 die "Failed to $execcmd: $err\n"; 466 } 467 $self->{clientpid} = $pid; 468 469 # queue [magic] input 470 print $self->reneg ? "R" : "test"; 471 472 # this closes client's stdin without waiting for its pid 473 open(STDOUT, ">&", $savedout); 474 close($savedout); 475 } 476 477 # Wait for incoming connection from client 478 my $fdset = IO::Select->new($self->{proxy_sock}); 479 if (!$fdset->can_read(60)) { 480 kill(3, $self->{real_serverpid}); 481 die "s_client didn't try to connect\n"; 482 } 483 484 my $client_sock; 485 if($self->{isdtls}) { 486 $client_sock = $self->{proxy_sock} 487 } elsif (!($client_sock = $self->{proxy_sock}->accept())) { 488 warn "Failed accepting incoming connection: $!\n"; 489 return 0; 490 } 491 492 print "Connection opened\n"; 493 494 my $server_sock = $self->{server_sock}; 495 my $indata; 496 497 #Wait for either the server socket or the client socket to become readable 498 $fdset = IO::Select->new($server_sock, $client_sock); 499 my @ready; 500 my $ctr = 0; 501 local $SIG{PIPE} = "IGNORE"; 502 $self->{saw_session_ticket} = undef; 503 while($fdset->count && $ctr < 10) { 504 if (defined($self->{sessionfile})) { 505 # s_client got -ign_eof and won't be exiting voluntarily, so we 506 # look for data *and* session ticket... 507 last if TLSProxy::Message->success() 508 && $self->{saw_session_ticket}; 509 } 510 if (!(@ready = $fdset->can_read(1))) { 511 last if TLSProxy::Message->success() 512 && $self->{saw_session_ticket}; 513 514 $ctr++; 515 next; 516 } 517 foreach my $hand (@ready) { 518 if ($hand == $server_sock) { 519 if ($server_sock->sysread($indata, 16384)) { 520 if ($indata = $self->process_packet(1, $indata)) { 521 $client_sock->syswrite($indata) or goto END; 522 } 523 $ctr = 0; 524 } else { 525 $fdset->remove($server_sock); 526 $client_sock->shutdown(SHUT_WR); 527 } 528 } elsif ($hand == $client_sock) { 529 if ($client_sock->sysread($indata, 16384)) { 530 if ($indata = $self->process_packet(0, $indata)) { 531 $server_sock->syswrite($indata) or goto END; 532 } 533 $ctr = 0; 534 } else { 535 $fdset->remove($client_sock); 536 $server_sock->shutdown(SHUT_WR); 537 } 538 } else { 539 kill(3, $self->{real_serverpid}); 540 die "Unexpected handle"; 541 } 542 } 543 } 544 545 if ($ctr >= 10) { 546 kill(3, $self->{real_serverpid}); 547 print "No progress made\n"; 548 $success = 0; 549 } 550 551 END: 552 print "Connection closed\n"; 553 if($server_sock) { 554 $server_sock->close(); 555 $self->{server_sock} = undef; 556 } 557 if($client_sock) { 558 #Closing this also kills the child process 559 $client_sock->close(); 560 } 561 562 my $pid; 563 if (--$self->{serverconnects} == 0) { 564 $pid = $self->{serverpid}; 565 print "Waiting for 'perl -ne print' process to close: $pid...\n"; 566 $pid = waitpid($pid, 0); 567 if ($pid > 0) { 568 die "exit code $? from 'perl -ne print' process\n" if $? != 0; 569 } elsif ($pid == 0) { 570 kill(3, $self->{real_serverpid}); 571 die "lost control over $self->{serverpid}?"; 572 } 573 $pid = $self->{real_serverpid}; 574 print "Waiting for s_server process to close: $pid...\n"; 575 # it's done already, just collect the exit code [and reap]... 576 waitpid($pid, 0); 577 die "exit code $? from s_server process\n" if $? != 0; 578 } else { 579 # It's a bit counter-intuitive spot to make next connection to 580 # the s_server. Rationale is that established connection works 581 # as synchronization point, in sense that this way we know that 582 # s_server is actually done with current session... 583 $self->connect_to_server(); 584 } 585 $pid = $self->{clientpid}; 586 print "Waiting for s_client process to close: $pid...\n"; 587 waitpid($pid, 0); 588 589 return $success; 590 } 591 592 sub process_packet 593 { 594 my ($self, $server, $packet) = @_; 595 my $len_real; 596 my $decrypt_len; 597 my $data; 598 my $recnum; 599 600 if ($server) { 601 print "Received server packet\n"; 602 } else { 603 print "Received client packet\n"; 604 } 605 606 if ($self->{direction} != $server) { 607 $self->{flight} = $self->{flight} + 1; 608 $self->{direction} = $server; 609 } 610 611 print "Packet length = ".length($packet)."\n"; 612 print "Processing flight ".$self->flight."\n"; 613 614 #Return contains the list of record found in the packet followed by the 615 #list of messages in those records and any partial message 616 my @ret = TLSProxy::Record->get_records($server, $self->flight, 617 $self->{partial}[$server].$packet, 618 $self->{isdtls}); 619 620 $self->{partial}[$server] = $ret[2]; 621 push @{$self->{record_list}}, @{$ret[0]}; 622 push @{$self->{message_list}}, @{$ret[1]}; 623 624 print "\n"; 625 626 if (scalar(@{$ret[0]}) == 0 or length($ret[2]) != 0) { 627 return ""; 628 } 629 630 #Finished parsing. Call user provided filter here 631 if (defined $self->filter) { 632 $self->filter->($self); 633 } 634 635 #Take a note on NewSessionTicket 636 foreach my $message (reverse @{$self->{message_list}}) { 637 if ($message->{mt} == TLSProxy::Message::MT_NEW_SESSION_TICKET) { 638 $self->{saw_session_ticket} = 1; 639 last; 640 } 641 } 642 643 #Reconstruct the packet 644 $packet = ""; 645 foreach my $record (@{$self->record_list}) { 646 $packet .= $record->reconstruct_record($server); 647 } 648 649 print "Forwarded packet length = ".length($packet)."\n\n"; 650 651 return $packet; 652 } 653 654 #Read accessors 655 sub execute 656 { 657 my $self = shift; 658 return $self->{execute}; 659 } 660 sub cert 661 { 662 my $self = shift; 663 return $self->{cert}; 664 } 665 sub debug 666 { 667 my $self = shift; 668 return $self->{debug}; 669 } 670 sub flight 671 { 672 my $self = shift; 673 return $self->{flight}; 674 } 675 sub record_list 676 { 677 my $self = shift; 678 return $self->{record_list}; 679 } 680 sub success 681 { 682 my $self = shift; 683 return $self->{success}; 684 } 685 sub end 686 { 687 my $self = shift; 688 return $self->{end}; 689 } 690 sub supports_IPv6 691 { 692 my $self = shift; 693 return $have_IPv6; 694 } 695 sub proxy_addr 696 { 697 my $self = shift; 698 return $self->{proxy_addr}; 699 } 700 sub proxy_port 701 { 702 my $self = shift; 703 return $self->{proxy_port}; 704 } 705 sub server_addr 706 { 707 my $self = shift; 708 return $self->{server_addr}; 709 } 710 sub server_port 711 { 712 my $self = shift; 713 return $self->{server_port}; 714 } 715 sub serverpid 716 { 717 my $self = shift; 718 return $self->{serverpid}; 719 } 720 sub clientpid 721 { 722 my $self = shift; 723 return $self->{clientpid}; 724 } 725 726 #Read/write accessors 727 sub filter 728 { 729 my $self = shift; 730 if (@_) { 731 $self->{filter} = shift; 732 } 733 return $self->{filter}; 734 } 735 sub cipherc 736 { 737 my $self = shift; 738 if (@_) { 739 $self->{cipherc} = shift; 740 } 741 return $self->{cipherc}; 742 } 743 sub ciphersuitesc 744 { 745 my $self = shift; 746 if (@_) { 747 $self->{ciphersuitesc} = shift; 748 } 749 return $self->{ciphersuitesc}; 750 } 751 sub ciphers 752 { 753 my $self = shift; 754 if (@_) { 755 $self->{ciphers} = shift; 756 } 757 return $self->{ciphers}; 758 } 759 sub ciphersuitess 760 { 761 my $self = shift; 762 if (@_) { 763 $self->{ciphersuitess} = shift; 764 } 765 return $self->{ciphersuitess}; 766 } 767 sub serverflags 768 { 769 my $self = shift; 770 if (@_) { 771 $self->{serverflags} = shift; 772 } 773 return $self->{serverflags}; 774 } 775 sub clientflags 776 { 777 my $self = shift; 778 if (@_) { 779 $self->{clientflags} = shift; 780 } 781 return $self->{clientflags}; 782 } 783 sub serverconnects 784 { 785 my $self = shift; 786 if (@_) { 787 $self->{serverconnects} = shift; 788 } 789 return $self->{serverconnects}; 790 } 791 # This is a bit ugly because the caller is responsible for keeping the records 792 # in sync with the updated message list; simply updating the message list isn't 793 # sufficient to get the proxy to forward the new message. 794 # But it does the trick for the one test (test_sslsessiontick) that needs it. 795 sub message_list 796 { 797 my $self = shift; 798 if (@_) { 799 $self->{message_list} = shift; 800 } 801 return $self->{message_list}; 802 } 803 804 sub fill_known_data 805 { 806 my $length = shift; 807 my $ret = ""; 808 for (my $i = 0; $i < $length; $i++) { 809 $ret .= chr($i); 810 } 811 return $ret; 812 } 813 814 sub is_tls13 815 { 816 my $class = shift; 817 if (@_) { 818 $is_tls13 = shift; 819 } 820 return $is_tls13; 821 } 822 823 sub reneg 824 { 825 my $self = shift; 826 if (@_) { 827 $self->{reneg} = shift; 828 } 829 return $self->{reneg}; 830 } 831 832 #Setting a sessionfile means that the client will not close until the given 833 #file exists. This is useful in TLSv1.3 where otherwise s_client will close 834 #immediately at the end of the handshake, but before the session has been 835 #received from the server. A side effect of this is that s_client never sends 836 #a close_notify, so instead we consider success to be when it sends application 837 #data over the connection. 838 sub sessionfile 839 { 840 my $self = shift; 841 if (@_) { 842 $self->{sessionfile} = shift; 843 TLSProxy::Message->successondata(1); 844 } 845 return $self->{sessionfile}; 846 } 847 848 sub ciphersuite 849 { 850 my $class = shift; 851 if (@_) { 852 $ciphersuite = shift; 853 } 854 return $ciphersuite; 855 } 856 857 sub isdtls 858 { 859 my $self = shift; 860 return $self->{isdtls}; #read-only 861 } 862 863 1; 864