Home | History | Annotate | Line # | Download | only in TLSProxy
      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