1#!/usr/bin/env perl 2#*************************************************************************** 3# _ _ ____ _ 4# Project ___| | | | _ \| | 5# / __| | | | |_) | | 6# | (__| |_| | _ <| |___ 7# \___|\___/|_| \_\_____| 8# 9# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. 10# 11# This software is licensed as described in the file COPYING, which 12# you should have received as part of this distribution. The terms 13# are also available at https://curl.se/docs/copyright.html. 14# 15# You may opt to use, copy, modify, merge, publish, distribute and/or sell 16# copies of the Software, and permit persons to whom the Software is 17# furnished to do so, under the terms of the COPYING file. 18# 19# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 20# KIND, either express or implied. 21# 22# SPDX-License-Identifier: curl 23# 24#*************************************************************************** 25 26# This is the HTTPS, FTPS, POP3S, IMAPS, SMTPS, server used for curl test 27# harness. Actually just a layer that runs stunnel properly using the 28# non-secure test harness servers. 29 30use strict; 31use warnings; 32 33BEGIN { 34 push(@INC, $ENV{'srcdir'}) if(defined $ENV{'srcdir'}); 35 push(@INC, "."); 36} 37 38use Cwd; 39use Cwd 'abs_path'; 40use File::Basename; 41 42use serverhelp qw( 43 server_pidfilename 44 server_logfilename 45 ); 46 47use pathhelp; 48 49my $stunnel = "stunnel"; 50 51my $verbose=0; # set to 1 for debugging 52 53my $accept_port = 8991; # just our default, weird enough 54my $target_port = 8999; # default test http-server port 55 56my $stuncert; 57 58my $ver_major; 59my $ver_minor; 60my $fips_support; 61my $stunnel_version; 62my $tstunnel_windows; 63my $socketopt; 64my $cmd; 65 66my $pidfile; # stunnel pid file 67my $logfile; # stunnel log file 68my $loglevel = 5; # stunnel log level 69my $ipvnum = 4; # default IP version of stunneled server 70my $idnum = 1; # default stunneled server instance number 71my $proto = 'https'; # default secure server protocol 72my $conffile; # stunnel configuration file 73my $capath; # certificate chain PEM folder 74my $certfile; # certificate chain PEM file 75 76#*************************************************************************** 77# stunnel requires full path specification for several files. 78# 79my $path = getcwd(); 80my $srcdir = $path; 81my $logdir = $path .'/log'; 82my $piddir; 83 84#*************************************************************************** 85# Signal handler to remove our stunnel 4.00 and newer configuration file. 86# 87sub exit_signal_handler { 88 my $signame = shift; 89 local $!; # preserve errno 90 local $?; # preserve exit status 91 unlink($conffile) if($conffile && (-f $conffile)); 92 exit; 93} 94 95#*************************************************************************** 96# Process command line options 97# 98while(@ARGV) { 99 if($ARGV[0] eq '--verbose') { 100 $verbose = 1; 101 } 102 elsif($ARGV[0] eq '--proto') { 103 if($ARGV[1]) { 104 $proto = $ARGV[1]; 105 shift @ARGV; 106 } 107 } 108 elsif($ARGV[0] eq '--accept') { 109 if($ARGV[1]) { 110 if($ARGV[1] =~ /^(\d+)$/) { 111 $accept_port = $1; 112 shift @ARGV; 113 } 114 } 115 } 116 elsif($ARGV[0] eq '--connect') { 117 if($ARGV[1]) { 118 if($ARGV[1] =~ /^(\d+)$/) { 119 $target_port = $1; 120 shift @ARGV; 121 } 122 } 123 } 124 elsif($ARGV[0] eq '--stunnel') { 125 if($ARGV[1]) { 126 $stunnel = $ARGV[1]; 127 shift @ARGV; 128 } 129 } 130 elsif($ARGV[0] eq '--srcdir') { 131 if($ARGV[1]) { 132 $srcdir = $ARGV[1]; 133 shift @ARGV; 134 } 135 } 136 elsif($ARGV[0] eq '--certfile') { 137 if($ARGV[1]) { 138 $stuncert = $ARGV[1]; 139 shift @ARGV; 140 } 141 } 142 elsif($ARGV[0] eq '--id') { 143 if($ARGV[1]) { 144 if($ARGV[1] =~ /^(\d+)$/) { 145 $idnum = $1 if($1 > 0); 146 shift @ARGV; 147 } 148 } 149 } 150 elsif($ARGV[0] eq '--ipv4') { 151 $ipvnum = 4; 152 } 153 elsif($ARGV[0] eq '--ipv6') { 154 $ipvnum = 6; 155 } 156 elsif($ARGV[0] eq '--pidfile') { 157 if($ARGV[1]) { 158 $pidfile = "$path/". $ARGV[1]; 159 shift @ARGV; 160 } 161 } 162 elsif($ARGV[0] eq '--logfile') { 163 if($ARGV[1]) { 164 $logfile = "$path/". $ARGV[1]; 165 shift @ARGV; 166 } 167 } 168 elsif($ARGV[0] eq '--logdir') { 169 if($ARGV[1]) { 170 $logdir = "$path/". $ARGV[1]; 171 shift @ARGV; 172 } 173 } 174 else { 175 print STDERR "\nWarning: secureserver.pl unknown parameter: $ARGV[0]\n"; 176 } 177 shift @ARGV; 178} 179 180#*************************************************************************** 181# Initialize command line option dependent variables 182# 183if($pidfile) { 184 # Use our pidfile directory to store the conf files 185 $piddir = dirname($pidfile); 186} 187else { 188 # Use the current directory to store the conf files 189 $piddir = $path; 190 $pidfile = server_pidfilename($piddir, $proto, $ipvnum, $idnum); 191} 192if(!$logfile) { 193 $logfile = server_logfilename($logdir, $proto, $ipvnum, $idnum); 194} 195 196$conffile = "$piddir/${proto}_stunnel.conf"; 197 198$capath = abs_path($path); 199$certfile = "$srcdir/". ($stuncert?"certs/$stuncert":"stunnel.pem"); 200$certfile = abs_path($certfile); 201 202my $ssltext = uc($proto) ." SSL/TLS:"; 203 204#*************************************************************************** 205# Find out version info for the given stunnel binary 206# 207foreach my $veropt (('-version', '-V')) { 208 foreach my $verstr (qx("$stunnel" $veropt 2>&1)) { 209 if($verstr =~ /^stunnel (\d+)\.(\d+) on /) { 210 $ver_major = $1; 211 $ver_minor = $2; 212 } 213 elsif($verstr =~ /^sslVersion.*fips *= *yes/) { 214 # the fips option causes an error if stunnel doesn't support it 215 $fips_support = 1; 216 last 217 } 218 } 219 last if($ver_major); 220} 221if((!$ver_major) || !defined($ver_minor)) { 222 if(-x "$stunnel" && ! -d "$stunnel") { 223 print "$ssltext Unknown stunnel version\n"; 224 } 225 else { 226 print "$ssltext No stunnel\n"; 227 } 228 exit 1; 229} 230$stunnel_version = (100*$ver_major) + $ver_minor; 231 232#*************************************************************************** 233# Verify minimum stunnel required version 234# 235if($stunnel_version < 310) { 236 print "$ssltext Unsupported stunnel version $ver_major.$ver_minor\n"; 237 exit 1; 238} 239 240#*************************************************************************** 241# Find out if we are running on Windows using the tstunnel binary 242# 243if($stunnel =~ /tstunnel(\.exe)?$/) { 244 $tstunnel_windows = 1; 245 246 # convert Cygwin/MinGW paths to Win32 format 247 $capath = pathhelp::sys_native_abs_path($capath); 248 $certfile = pathhelp::sys_native_abs_path($certfile); 249} 250 251#*************************************************************************** 252# Build command to execute for stunnel 3.X versions 253# 254if($stunnel_version < 400) { 255 if($stunnel_version >= 319) { 256 $socketopt = "-O a:SO_REUSEADDR=1"; 257 } 258 $cmd = "\"$stunnel\" -p $certfile -P $pidfile "; 259 $cmd .= "-d $accept_port -r $target_port -f -D $loglevel "; 260 $cmd .= ($socketopt) ? "$socketopt " : ""; 261 $cmd .= ">$logfile 2>&1"; 262 if($verbose) { 263 print uc($proto) ." server (stunnel $ver_major.$ver_minor)\n"; 264 print "cmd: $cmd\n"; 265 print "pem cert file: $certfile\n"; 266 print "pid file: $pidfile\n"; 267 print "log file: $logfile\n"; 268 print "log level: $loglevel\n"; 269 print "listen on port: $accept_port\n"; 270 print "connect to port: $target_port\n"; 271 } 272} 273 274#*************************************************************************** 275# Build command to execute for stunnel 4.00 and newer 276# 277if($stunnel_version >= 400) { 278 $socketopt = "a:SO_REUSEADDR=1"; 279 if(($stunnel_version >= 534) && $tstunnel_windows) { 280 # SO_EXCLUSIVEADDRUSE is on by default on Vista or newer, 281 # but does not work together with SO_REUSEADDR being on. 282 $socketopt .= "\nsocket = a:SO_EXCLUSIVEADDRUSE=0"; 283 } 284 $cmd = "\"$stunnel\" $conffile "; 285 $cmd .= ">$logfile 2>&1"; 286 # setup signal handler 287 $SIG{INT} = \&exit_signal_handler; 288 $SIG{TERM} = \&exit_signal_handler; 289 # stunnel configuration file 290 if(open(my $stunconf, ">", "$conffile")) { 291 print $stunconf "CApath = $capath\n"; 292 print $stunconf "cert = $certfile\n"; 293 print $stunconf "debug = $loglevel\n"; 294 print $stunconf "socket = $socketopt\n"; 295 if($fips_support) { 296 # disable fips in case OpenSSL doesn't support it 297 print $stunconf "fips = no\n"; 298 } 299 if(!$tstunnel_windows) { 300 # do not use Linux-specific options on Windows 301 print $stunconf "output = $logfile\n"; 302 print $stunconf "pid = $pidfile\n"; 303 print $stunconf "foreground = yes\n"; 304 } 305 print $stunconf "\n"; 306 print $stunconf "[curltest]\n"; 307 print $stunconf "accept = $accept_port\n"; 308 print $stunconf "connect = $target_port\n"; 309 if(!close($stunconf)) { 310 print "$ssltext Error closing file $conffile\n"; 311 exit 1; 312 } 313 } 314 else { 315 print "$ssltext Error writing file $conffile\n"; 316 exit 1; 317 } 318 if($verbose) { 319 print uc($proto) ." server (stunnel $ver_major.$ver_minor)\n"; 320 print "cmd: $cmd\n"; 321 print "CApath = $capath\n"; 322 print "cert = $certfile\n"; 323 print "debug = $loglevel\n"; 324 print "socket = $socketopt\n"; 325 if($fips_support) { 326 print "fips = no\n"; 327 } 328 if(!$tstunnel_windows) { 329 print "pid = $pidfile\n"; 330 print "output = $logfile\n"; 331 print "foreground = yes\n"; 332 } 333 print "\n"; 334 print "[curltest]\n"; 335 print "accept = $accept_port\n"; 336 print "connect = $target_port\n"; 337 } 338} 339 340#*************************************************************************** 341# Set file permissions on certificate pem file. 342# 343chmod(0600, $certfile) if(-f $certfile); 344print STDERR "RUN: $cmd\n" if($verbose); 345 346#*************************************************************************** 347# Run tstunnel on Windows. 348# 349if($tstunnel_windows) { 350 # Fake pidfile for tstunnel on Windows. 351 if(open(my $out, ">", "$pidfile")) { 352 print $out $$ . "\n"; 353 close($out); 354 } 355 356 # Flush output. 357 $| = 1; 358 359 # Put an "exec" in front of the command so that the child process 360 # keeps this child's process ID by being tied to the spawned shell. 361 exec("exec $cmd") || die "Can't exec() $cmd: $!"; 362 # exec() will create a new process, but ties the existence of the 363 # new process to the parent waiting perl.exe and sh.exe processes. 364 365 # exec() should never return back here to this process. We protect 366 # ourselves by calling die() just in case something goes really bad. 367 die "error: exec() has returned"; 368} 369 370#*************************************************************************** 371# Run stunnel. 372# 373my $rc = system($cmd); 374 375$rc >>= 8; 376 377unlink($conffile) if($conffile && -f $conffile); 378 379exit $rc; 380