use strict;
use warnings;
use Cwd;
use sshhelp qw(
$sshdexe
$sshexe
$sftpsrvexe
$sftpexe
$sshkeygenexe
$sshdconfig
$sshconfig
$sftpconfig
$knownhosts
$sshdlog
$sshlog
$sftplog
$sftpcmds
$hstprvkeyf
$hstpubkeyf
$cliprvkeyf
$clipubkeyf
display_sshdconfig
display_sshconfig
display_sftpconfig
display_sshdlog
display_sshlog
display_sftplog
dump_array
find_sshd
find_ssh
find_sftpsrv
find_sftp
find_sshkeygen
logmsg
sshversioninfo
);
use serverhelp qw(
server_pidfilename
server_logfilename
);
my $verbose = 0; my $debugprotocol = 0; my $port = 8999; my $socksport = $port + 1; my $listenaddr = '127.0.0.1'; my $ipvnum = 4; my $idnum = 1; my $proto = 'ssh'; my $path = getcwd(); my $logdir = $path .'/log'; my $username = $ENV{USER}; my $pidfile;
my $error;
my @cfgarr;
while(@ARGV) {
if($ARGV[0] eq '--verbose') {
$verbose = 1;
}
elsif($ARGV[0] eq '--debugprotocol') {
$verbose = 1;
$debugprotocol = 1;
}
elsif($ARGV[0] eq '--user') {
if($ARGV[1]) {
$username = $ARGV[1];
shift @ARGV;
}
}
elsif($ARGV[0] eq '--id') {
if($ARGV[1]) {
if($ARGV[1] =~ /^(\d+)$/) {
$idnum = $1 if($1 > 0);
shift @ARGV;
}
}
}
elsif($ARGV[0] eq '--ipv4') {
$ipvnum = 4;
$listenaddr = '127.0.0.1' if($listenaddr eq '::1');
}
elsif($ARGV[0] eq '--ipv6') {
$ipvnum = 6;
$listenaddr = '::1' if($listenaddr eq '127.0.0.1');
}
elsif($ARGV[0] eq '--addr') {
if($ARGV[1]) {
my $tmpstr = $ARGV[1];
if($tmpstr =~ /^(\d\d?\d?)\.(\d\d?\d?)\.(\d\d?\d?)\.(\d\d?\d?)$/) {
$listenaddr = "$1.$2.$3.$4" if($ipvnum == 4);
shift @ARGV;
}
elsif($ipvnum == 6) {
$listenaddr = $tmpstr;
$listenaddr =~ s/^\[(.*)\]$/$1/;
shift @ARGV;
}
}
}
elsif($ARGV[0] eq '--pidfile') {
if($ARGV[1]) {
$pidfile = "$path/". $ARGV[1];
shift @ARGV;
}
}
elsif($ARGV[0] eq '--sshport') {
if($ARGV[1]) {
if($ARGV[1] =~ /^(\d+)$/) {
$port = $1;
shift @ARGV;
}
}
}
elsif($ARGV[0] eq '--socksport') {
if($ARGV[1]) {
if($ARGV[1] =~ /^(\d+)$/) {
$socksport = $1;
shift @ARGV;
}
}
}
else {
print STDERR "\nWarning: sshserver.pl unknown parameter: $ARGV[0]\n";
}
shift @ARGV;
}
if(!$pidfile) {
$pidfile = "$path/". server_pidfilename($proto, $ipvnum, $idnum);
}
$sshdlog = server_logfilename($logdir, 'ssh', $ipvnum, $idnum);
$sftplog = server_logfilename($logdir, 'sftp', $ipvnum, $idnum);
$sshlog = server_logfilename($logdir, 'socks', $ipvnum, $idnum);
my $loglevel = $debugprotocol?'DEBUG3':'DEBUG2';
if(!$username) {
$error = 'Will not run ssh server without a user name';
}
elsif($username eq 'root') {
$error = 'Will not run ssh server as root to mitigate security risks';
}
if($error) {
logmsg $error;
exit 1;
}
my $sshd = find_sshd();
if(!$sshd) {
logmsg "cannot find $sshdexe";
exit 1;
}
my ($sshdid, $sshdvernum, $sshdverstr, $sshderror) = sshversioninfo($sshd);
if(!$sshdid) {
logmsg $sshderror if($verbose);
logmsg 'SCP, SFTP and SOCKS tests require OpenSSH 2.9.9 or later';
exit 1;
}
logmsg "ssh server found $sshd is $sshdverstr" if($verbose);
if((($sshdid =~ /OpenSSH/) && ($sshdvernum < 299)) ||
(($sshdid =~ /SunSSH/) && ($sshdvernum < 100))) {
logmsg 'SCP, SFTP and SOCKS tests require OpenSSH 2.9.9 or later';
exit 1;
}
my $sftpsrv = find_sftpsrv();
if(!$sftpsrv) {
logmsg "cannot find $sftpsrvexe";
exit 1;
}
logmsg "sftp server plugin found $sftpsrv" if($verbose);
my $sftp = find_sftp();
if(!$sftp) {
logmsg "cannot find $sftpexe";
exit 1;
}
logmsg "sftp client found $sftp" if($verbose);
my $sshkeygen = find_sshkeygen();
if(!$sshkeygen) {
logmsg "cannot find $sshkeygenexe";
exit 1;
}
logmsg "ssh keygen found $sshkeygen" if($verbose);
my $ssh = find_ssh();
if(!$ssh) {
logmsg "cannot find $sshexe";
exit 1;
}
my ($sshid, $sshvernum, $sshverstr, $ssherror) = sshversioninfo($ssh);
if(!$sshid) {
logmsg $ssherror if($verbose);
logmsg 'SCP, SFTP and SOCKS tests require OpenSSH 2.9.9 or later';
exit 1;
}
logmsg "ssh client found $ssh is $sshverstr" if($verbose);
if((($sshid =~ /OpenSSH/) && ($sshvernum < 299)) ||
(($sshid =~ /SunSSH/) && ($sshvernum < 100))) {
logmsg 'SCP, SFTP and SOCKS tests require OpenSSH 2.9.9 or later';
exit 1;
}
if((! -e $hstprvkeyf) || (! -s $hstprvkeyf) ||
(! -e $hstpubkeyf) || (! -s $hstpubkeyf) ||
(! -e $cliprvkeyf) || (! -s $cliprvkeyf) ||
(! -e $clipubkeyf) || (! -s $clipubkeyf)) {
unlink($hstprvkeyf, $hstpubkeyf, $cliprvkeyf, $clipubkeyf);
logmsg 'generating host keys...' if($verbose);
if(system "$sshkeygen -q -t dsa -f $hstprvkeyf -C 'curl test server' -N ''") {
logmsg 'Could not generate host key';
exit 1;
}
logmsg 'generating client keys...' if($verbose);
if(system "$sshkeygen -q -t dsa -f $cliprvkeyf -C 'curl test client' -N ''") {
logmsg 'Could not generate client key';
exit 1;
}
}
logmsg 'generating ssh server config file...' if($verbose);
@cfgarr = ();
push @cfgarr, '# This is a generated file. Do not edit.';
push @cfgarr, "# $sshdverstr sshd configuration file for curl testing";
push @cfgarr, '#';
push @cfgarr, "DenyUsers !$username";
push @cfgarr, "AllowUsers $username";
push @cfgarr, 'DenyGroups';
push @cfgarr, 'AllowGroups';
push @cfgarr, '#';
push @cfgarr, "AuthorizedKeysFile $path/$clipubkeyf";
push @cfgarr, "AuthorizedKeysFile2 $path/$clipubkeyf";
push @cfgarr, "HostKey $path/$hstprvkeyf";
push @cfgarr, "PidFile $pidfile";
push @cfgarr, '#';
push @cfgarr, "Port $port";
push @cfgarr, "ListenAddress $listenaddr";
push @cfgarr, 'Protocol 2';
push @cfgarr, '#';
push @cfgarr, 'AllowTcpForwarding yes';
push @cfgarr, 'Banner none';
push @cfgarr, 'ChallengeResponseAuthentication no';
push @cfgarr, 'ClientAliveCountMax 3';
push @cfgarr, 'ClientAliveInterval 0';
push @cfgarr, 'GatewayPorts no';
push @cfgarr, 'HostbasedAuthentication no';
push @cfgarr, 'HostbasedUsesNameFromPacketOnly no';
push @cfgarr, 'IgnoreRhosts yes';
push @cfgarr, 'IgnoreUserKnownHosts yes';
push @cfgarr, 'KeyRegenerationInterval 0';
push @cfgarr, 'LoginGraceTime 30';
push @cfgarr, "LogLevel $loglevel";
push @cfgarr, 'MaxStartups 5';
push @cfgarr, 'PasswordAuthentication no';
push @cfgarr, 'PermitEmptyPasswords no';
push @cfgarr, 'PermitRootLogin no';
push @cfgarr, 'PrintLastLog no';
push @cfgarr, 'PrintMotd no';
push @cfgarr, 'PubkeyAuthentication yes';
push @cfgarr, 'RhostsRSAAuthentication no';
push @cfgarr, 'RSAAuthentication no';
push @cfgarr, 'ServerKeyBits 768';
push @cfgarr, 'StrictModes no';
push @cfgarr, "Subsystem sftp $sftpsrv";
push @cfgarr, 'SyslogFacility AUTH';
push @cfgarr, 'UseLogin no';
push @cfgarr, 'X11Forwarding no';
push @cfgarr, '#';
$error = dump_array($sshdconfig, @cfgarr);
if($error) {
logmsg $error;
exit 1;
}
sub sshd_supports_opt {
my ($option, $value) = @_;
my $err;
if((($sshdid =~ /OpenSSH/) && ($sshdvernum >= 310)) ||
($sshdid =~ /SunSSH/)) {
$err = grep /((Unsupported)|(Bad configuration)|(Deprecated)) option.*$option/,
qx($sshd -t -f $sshdconfig -o $option=$value 2>&1);
return !$err;
}
if(($sshdid =~ /OpenSSH/) && ($sshdvernum >= 299)) {
$err = dump_array($sshdconfig, (@cfgarr, "$option $value"));
if($err) {
logmsg $err;
return 0;
}
$err = grep /((Unsupported)|(Bad configuration)|(Deprecated)) option.*$option/,
qx($sshd -t -f $sshdconfig 2>&1);
unlink $sshdconfig;
return !$err;
}
return 0;
}
if(sshd_supports_opt('KerberosAuthentication','no')) {
push @cfgarr, 'KerberosAuthentication no';
}
if(sshd_supports_opt('KerberosGetAFSToken','no')) {
push @cfgarr, 'KerberosGetAFSToken no';
}
if(sshd_supports_opt('KerberosOrLocalPasswd','no')) {
push @cfgarr, 'KerberosOrLocalPasswd no';
}
if(sshd_supports_opt('KerberosTgtPassing','no')) {
push @cfgarr, 'KerberosTgtPassing no';
}
if(sshd_supports_opt('KerberosTicketCleanup','yes')) {
push @cfgarr, 'KerberosTicketCleanup yes';
}
if(sshd_supports_opt('AFSTokenPassing','no')) {
push @cfgarr, 'AFSTokenPassing no';
}
if(sshd_supports_opt('SkeyAuthentication','no')) {
push @cfgarr, 'SkeyAuthentication no';
}
my $sshd_builtwith_GSSAPI;
if(sshd_supports_opt('GSSAPIAuthentication','no')) {
push @cfgarr, 'GSSAPIAuthentication no';
$sshd_builtwith_GSSAPI = 1;
}
if(sshd_supports_opt('GSSAPICleanupCredentials','yes')) {
push @cfgarr, 'GSSAPICleanupCredentials yes';
}
if(sshd_supports_opt('GSSAPIKeyExchange','no')) {
push @cfgarr, 'GSSAPIKeyExchange no';
}
if(sshd_supports_opt('GSSAPIStoreDelegatedCredentials','no')) {
push @cfgarr, 'GSSAPIStoreDelegatedCredentials no';
}
if(sshd_supports_opt('GSSCleanupCreds','yes')) {
push @cfgarr, 'GSSCleanupCreds yes';
}
if(sshd_supports_opt('GSSUseSessionCredCache','no')) {
push @cfgarr, 'GSSUseSessionCredCache no';
}
push @cfgarr, '#';
if(sshd_supports_opt('AcceptEnv','')) {
push @cfgarr, 'AcceptEnv';
}
if(sshd_supports_opt('AddressFamily','any')) {
splice @cfgarr, 14, 0, 'AddressFamily any';
}
if(sshd_supports_opt('Compression','no')) {
push @cfgarr, 'Compression no';
}
if(sshd_supports_opt('KbdInteractiveAuthentication','no')) {
push @cfgarr, 'KbdInteractiveAuthentication no';
}
if(sshd_supports_opt('KeepAlive','no')) {
push @cfgarr, 'KeepAlive no';
}
if(sshd_supports_opt('LookupClientHostnames','no')) {
push @cfgarr, 'LookupClientHostnames no';
}
if(sshd_supports_opt('MaxAuthTries','10')) {
push @cfgarr, 'MaxAuthTries 10';
}
if(sshd_supports_opt('PAMAuthenticationViaKbdInt','no')) {
push @cfgarr, 'PAMAuthenticationViaKbdInt no';
}
if(sshd_supports_opt('PermitTunnel','no')) {
push @cfgarr, 'PermitTunnel no';
}
if(sshd_supports_opt('PermitUserEnvironment','no')) {
push @cfgarr, 'PermitUserEnvironment no';
}
if(sshd_supports_opt('RhostsAuthentication','no')) {
push @cfgarr, 'RhostsAuthentication no';
}
if(sshd_supports_opt('TCPKeepAlive','no')) {
push @cfgarr, 'TCPKeepAlive no';
}
if(sshd_supports_opt('UseDNS','no')) {
push @cfgarr, 'UseDNS no';
}
if(sshd_supports_opt('UsePAM','no')) {
push @cfgarr, 'UsePAM no';
}
if($sshdid =~ /OpenSSH/) {
if(sshd_supports_opt('UsePrivilegeSeparation','no')) {
push @cfgarr, 'UsePrivilegeSeparation no';
}
}
if(sshd_supports_opt('VerifyReverseMapping','no')) {
push @cfgarr, 'VerifyReverseMapping no';
}
if(sshd_supports_opt('X11UseLocalhost','yes')) {
push @cfgarr, 'X11UseLocalhost yes';
}
push @cfgarr, '#';
$error = dump_array($sshdconfig, @cfgarr);
if($error) {
logmsg $error;
exit 1;
}
if(system "$sshd -t -f $sshdconfig > $sshdlog 2>&1") {
logmsg "sshd configuration file $sshdconfig failed verification";
display_sshdlog();
display_sshdconfig();
exit 1;
}
if((! -e $knownhosts) || (! -s $knownhosts)) {
logmsg 'generating ssh client known hosts file...' if($verbose);
unlink($knownhosts);
if(open(DSAKEYFILE, "<$hstpubkeyf")) {
my @dsahostkey = do { local $/ = ' '; <DSAKEYFILE> };
if(close(DSAKEYFILE)) {
if(open(KNOWNHOSTS, ">$knownhosts")) {
print KNOWNHOSTS "$listenaddr ssh-dss $dsahostkey[1]\n";
if(!close(KNOWNHOSTS)) {
$error = "Error: cannot close file $knownhosts";
}
}
else {
$error = "Error: cannot write file $knownhosts";
}
}
else {
$error = "Error: cannot close file $hstpubkeyf";
}
}
else {
$error = "Error: cannot read file $hstpubkeyf";
}
if($error) {
logmsg $error;
exit 1;
}
}
logmsg 'generating ssh client config file...' if($verbose);
@cfgarr = ();
push @cfgarr, '# This is a generated file. Do not edit.';
push @cfgarr, "# $sshverstr ssh client configuration file for curl testing";
push @cfgarr, '#';
push @cfgarr, 'Host *';
push @cfgarr, '#';
push @cfgarr, "Port $port";
push @cfgarr, "HostName $listenaddr";
push @cfgarr, "User $username";
push @cfgarr, 'Protocol 2';
push @cfgarr, '#';
push @cfgarr, "BindAddress $listenaddr";
push @cfgarr, "DynamicForward $socksport";
push @cfgarr, '#';
push @cfgarr, "IdentityFile $path/curl_client_key";
push @cfgarr, "UserKnownHostsFile $path/$knownhosts";
push @cfgarr, '#';
push @cfgarr, 'BatchMode yes';
push @cfgarr, 'ChallengeResponseAuthentication no';
push @cfgarr, 'CheckHostIP no';
push @cfgarr, 'ClearAllForwardings no';
push @cfgarr, 'Compression no';
push @cfgarr, 'ConnectionAttempts 3';
push @cfgarr, 'ForwardAgent no';
push @cfgarr, 'ForwardX11 no';
push @cfgarr, 'GatewayPorts no';
push @cfgarr, 'GlobalKnownHostsFile /dev/null';
push @cfgarr, 'HostbasedAuthentication no';
push @cfgarr, 'KbdInteractiveAuthentication no';
push @cfgarr, "LogLevel $loglevel";
push @cfgarr, 'NumberOfPasswordPrompts 0';
push @cfgarr, 'PasswordAuthentication no';
push @cfgarr, 'PreferredAuthentications publickey';
push @cfgarr, 'PubkeyAuthentication yes';
push @cfgarr, 'RhostsRSAAuthentication no';
push @cfgarr, 'RSAAuthentication no';
push @cfgarr, 'StrictHostKeyChecking yes';
push @cfgarr, 'UsePrivilegedPort no';
push @cfgarr, '#';
if(($sshid =~ /OpenSSH/) && ($sshvernum >= 370)) {
push @cfgarr, 'AddressFamily any';
}
if((($sshid =~ /OpenSSH/) && ($sshvernum >= 370)) ||
(($sshid =~ /SunSSH/) && ($sshvernum >= 120))) {
push @cfgarr, 'ConnectTimeout 30';
}
if(($sshid =~ /OpenSSH/) && ($sshvernum >= 390)) {
push @cfgarr, 'ControlMaster no';
}
if(($sshid =~ /OpenSSH/) && ($sshvernum >= 420)) {
push @cfgarr, 'ControlPath none';
}
if(($sshid =~ /SunSSH/) && ($sshvernum >= 120)) {
push @cfgarr, 'DisableBanner yes';
}
if(($sshid =~ /OpenSSH/) && ($sshvernum >= 360)) {
push @cfgarr, 'EnableSSHKeysign no';
}
if(($sshid =~ /OpenSSH/) && ($sshvernum >= 440)) {
push @cfgarr, 'ExitOnForwardFailure yes';
}
if((($sshid =~ /OpenSSH/) && ($sshvernum >= 380)) ||
(($sshid =~ /SunSSH/) && ($sshvernum >= 120))) {
push @cfgarr, 'ForwardX11Trusted no';
}
if(($sshd_builtwith_GSSAPI) && ($sshdid eq $sshid) &&
($sshdvernum == $sshvernum)) {
push @cfgarr, 'GSSAPIAuthentication no';
push @cfgarr, 'GSSAPIDelegateCredentials no';
if($sshid =~ /SunSSH/) {
push @cfgarr, 'GSSAPIKeyExchange no';
}
}
if((($sshid =~ /OpenSSH/) && ($sshvernum >= 400)) ||
(($sshid =~ /SunSSH/) && ($sshvernum >= 120))) {
push @cfgarr, 'HashKnownHosts no';
}
if(($sshid =~ /OpenSSH/) && ($sshvernum >= 390)) {
push @cfgarr, 'IdentitiesOnly yes';
}
if(($sshid =~ /SunSSH/) && ($sshvernum >= 120)) {
push @cfgarr, 'IgnoreIfUnknown no';
}
if((($sshid =~ /OpenSSH/) && ($sshvernum < 380)) ||
($sshid =~ /SunSSH/)) {
push @cfgarr, 'KeepAlive no';
}
if((($sshid =~ /OpenSSH/) && ($sshvernum >= 300)) ||
($sshid =~ /SunSSH/)) {
push @cfgarr, 'NoHostAuthenticationForLocalhost no';
}
if(($sshid =~ /OpenSSH/) && ($sshvernum >= 430)) {
push @cfgarr, 'PermitLocalCommand no';
}
if((($sshid =~ /OpenSSH/) && ($sshvernum >= 370)) ||
(($sshid =~ /SunSSH/) && ($sshvernum >= 120))) {
push @cfgarr, 'RekeyLimit 1G';
}
if(($sshid =~ /OpenSSH/) && ($sshvernum >= 390)) {
push @cfgarr, 'SendEnv';
}
if((($sshid =~ /OpenSSH/) && ($sshvernum >= 380)) ||
(($sshid =~ /SunSSH/) && ($sshvernum >= 120))) {
push @cfgarr, 'ServerAliveCountMax 3';
push @cfgarr, 'ServerAliveInterval 0';
}
if(($sshid =~ /OpenSSH/) && ($sshvernum >= 380)) {
push @cfgarr, 'TCPKeepAlive no';
}
if(($sshid =~ /OpenSSH/) && ($sshvernum >= 430)) {
push @cfgarr, 'Tunnel no';
}
if(($sshid =~ /OpenSSH/) && ($sshvernum >= 380)) {
push @cfgarr, 'VerifyHostKeyDNS no';
}
push @cfgarr, '#';
$error = dump_array($sshconfig, @cfgarr);
if($error) {
logmsg $error;
exit 1;
}
logmsg 'generating sftp client config file...' if($verbose);
splice @cfgarr, 1, 1, "# $sshverstr sftp client configuration file for curl testing";
for(my $i = scalar(@cfgarr) - 1; $i > 0; $i--) {
if($cfgarr[$i] =~ /^DynamicForward/) {
splice @cfgarr, $i, 1;
next;
}
if($cfgarr[$i] =~ /^ClearAllForwardings/) {
splice @cfgarr, $i, 1, "ClearAllForwardings yes";
next;
}
}
$error = dump_array($sftpconfig, @cfgarr);
if($error) {
logmsg $error;
exit 1;
}
@cfgarr = ();
logmsg 'generating sftp client commands file...' if($verbose);
push @cfgarr, 'pwd';
push @cfgarr, 'quit';
$error = dump_array($sftpcmds, @cfgarr);
if($error) {
logmsg $error;
exit 1;
}
@cfgarr = ();
logmsg "SCP/SFTP server listening on port $port" if($verbose);
my $rc = system "$sshd -e -D -f $sshdconfig > $sshdlog 2>&1";
if($rc == -1) {
logmsg "$sshd failed with: $!";
}
elsif($rc & 127) {
logmsg sprintf("$sshd died with signal %d, and %s coredump",
($rc & 127), ($rc & 128)?'a':'no');
}
elsif($verbose && ($rc >> 8)) {
logmsg sprintf("$sshd exited with %d", $rc >> 8);
}
unlink($hstprvkeyf, $hstpubkeyf, $cliprvkeyf, $clipubkeyf, $knownhosts);
unlink($sshdconfig, $sshconfig, $sftpconfig);
exit 0;