mirror of
https://github.com/moparisthebest/curl
synced 2024-12-21 15:48:49 -05:00
test1451: add SMB support to the testbed
Add test 1451 which does some very basic SMB testing using the impacket SMB server. Closes #1630
This commit is contained in:
parent
f1609155d5
commit
a6f8d27efc
56
tests/curl_test_data.py
Executable file
56
tests/curl_test_data.py
Executable file
@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Project ___| | | | _ \| |
|
||||||
|
# / __| | | | |_) | |
|
||||||
|
# | (__| |_| | _ <| |___
|
||||||
|
# \___|\___/|_| \_\_____|
|
||||||
|
#
|
||||||
|
# Copyright (C) 2017, Daniel Stenberg, <daniel@haxx.se>, et al.
|
||||||
|
#
|
||||||
|
# This software is licensed as described in the file COPYING, which
|
||||||
|
# you should have received as part of this distribution. The terms
|
||||||
|
# are also available at https://curl.haxx.se/docs/copyright.html.
|
||||||
|
#
|
||||||
|
# You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
||||||
|
# copies of the Software, and permit persons to whom the Software is
|
||||||
|
# furnished to do so, under the terms of the COPYING file.
|
||||||
|
#
|
||||||
|
# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||||
|
# KIND, either express or implied.
|
||||||
|
#
|
||||||
|
"""Module for extracting test data from the test data folder"""
|
||||||
|
|
||||||
|
from __future__ import (absolute_import, division, print_function,
|
||||||
|
unicode_literals)
|
||||||
|
import os
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import logging
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TestData(object):
|
||||||
|
def __init__(self, data_folder):
|
||||||
|
self.data_folder = data_folder
|
||||||
|
|
||||||
|
def get_test_data(self, test_number):
|
||||||
|
# Create the test file name
|
||||||
|
filename = os.path.join(self.data_folder,
|
||||||
|
"test{0}".format(test_number))
|
||||||
|
|
||||||
|
# The user should handle the exception from failing to find the file.
|
||||||
|
tree = ET.parse(filename)
|
||||||
|
|
||||||
|
# We need the <reply><data> text.
|
||||||
|
reply = tree.find("reply")
|
||||||
|
data = reply.find("data")
|
||||||
|
|
||||||
|
# Return the text contents of the data
|
||||||
|
return data.text
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
td = TestData("./data")
|
||||||
|
data = td.get_test_data(1)
|
||||||
|
print(data)
|
@ -154,7 +154,7 @@ test1416 test1417 test1418 test1419 test1420 test1421 test1422 test1423 \
|
|||||||
test1424 test1425 test1426 \
|
test1424 test1425 test1426 \
|
||||||
test1428 test1429 test1430 test1431 test1432 test1433 test1434 test1435 \
|
test1428 test1429 test1430 test1431 test1432 test1433 test1434 test1435 \
|
||||||
test1436 test1437 test1438 test1439 test1440 test1441 test1442 test1443 \
|
test1436 test1437 test1438 test1439 test1440 test1441 test1442 test1443 \
|
||||||
test1444 test1445 test1446 test1450 \
|
test1444 test1445 test1446 test1450 test1451 \
|
||||||
\
|
\
|
||||||
test1500 test1501 test1502 test1503 test1504 test1505 test1506 test1507 \
|
test1500 test1501 test1502 test1503 test1504 test1505 test1506 test1507 \
|
||||||
test1508 test1509 test1510 test1511 test1512 test1513 test1514 test1515 \
|
test1508 test1509 test1510 test1511 test1512 test1513 test1514 test1515 \
|
||||||
|
36
tests/data/test1451
Normal file
36
tests/data/test1451
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<testcase>
|
||||||
|
<info>
|
||||||
|
<keywords>
|
||||||
|
SMB
|
||||||
|
</keywords>
|
||||||
|
</info>
|
||||||
|
|
||||||
|
#
|
||||||
|
# Server-side
|
||||||
|
<reply>
|
||||||
|
<data>Basic SMB test complete</data>
|
||||||
|
</reply>
|
||||||
|
|
||||||
|
#
|
||||||
|
# Client-side
|
||||||
|
<client>
|
||||||
|
<server>
|
||||||
|
smb
|
||||||
|
</server>
|
||||||
|
<features>
|
||||||
|
smb
|
||||||
|
</features>
|
||||||
|
<name>
|
||||||
|
Basic SMB request
|
||||||
|
</name>
|
||||||
|
<command>
|
||||||
|
-u 'curltest:curltest' smb://%HOSTIP:%SMBPORT/TESTS/1451
|
||||||
|
</command>
|
||||||
|
</client>
|
||||||
|
|
||||||
|
#
|
||||||
|
# Verify data after the test has been "shot"
|
||||||
|
<verify>
|
||||||
|
<stdout>Basic SMB test complete</stdout>
|
||||||
|
</verify>
|
||||||
|
</testcase>
|
File diff suppressed because it is too large
Load Diff
@ -146,6 +146,8 @@ my $HTTPPIPEPORT; # HTTP pipelining port
|
|||||||
my $HTTPUNIXPATH; # HTTP server Unix domain socket path
|
my $HTTPUNIXPATH; # HTTP server Unix domain socket path
|
||||||
my $HTTP2PORT; # HTTP/2 server port
|
my $HTTP2PORT; # HTTP/2 server port
|
||||||
my $DICTPORT; # DICT server port
|
my $DICTPORT; # DICT server port
|
||||||
|
my $SMBPORT; # SMB server port
|
||||||
|
my $SMBSPORT; # SMBS server port
|
||||||
|
|
||||||
my $srcdir = $ENV{'srcdir'} || '.';
|
my $srcdir = $ENV{'srcdir'} || '.';
|
||||||
my $CURL="../src/curl".exe_ext(); # what curl executable to run on the tests
|
my $CURL="../src/curl".exe_ext(); # what curl executable to run on the tests
|
||||||
@ -380,7 +382,7 @@ sub init_serverpidfile_hash {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for my $proto (('tftp', 'sftp', 'socks', 'ssh', 'rtsp', 'gopher', 'httptls',
|
for my $proto (('tftp', 'sftp', 'socks', 'ssh', 'rtsp', 'gopher', 'httptls',
|
||||||
'dict')) {
|
'dict', 'smb', 'smbs')) {
|
||||||
for my $ipvnum ((4, 6)) {
|
for my $ipvnum ((4, 6)) {
|
||||||
for my $idnum ((1, 2)) {
|
for my $idnum ((1, 2)) {
|
||||||
my $serv = servername_id($proto, $ipvnum, $idnum);
|
my $serv = servername_id($proto, $ipvnum, $idnum);
|
||||||
@ -1120,6 +1122,67 @@ sub verifysocks {
|
|||||||
return $pid;
|
return $pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#######################################################################
|
||||||
|
# Verify that the server that runs on $ip, $port is our server. This also
|
||||||
|
# implies that we can speak with it, as there might be occasions when the
|
||||||
|
# server runs fine but we cannot talk to it ("Failed to connect to ::1: Can't
|
||||||
|
# assign requested address")
|
||||||
|
#
|
||||||
|
sub verifysmb {
|
||||||
|
my ($proto, $ipvnum, $idnum, $ip, $port) = @_;
|
||||||
|
my $server = servername_id($proto, $ipvnum, $idnum);
|
||||||
|
my $pid = 0;
|
||||||
|
my $time=time();
|
||||||
|
my $extra="";
|
||||||
|
|
||||||
|
my $verifylog = "$LOGDIR/".
|
||||||
|
servername_canon($proto, $ipvnum, $idnum) .'_verify.log';
|
||||||
|
unlink($verifylog) if(-f $verifylog);
|
||||||
|
|
||||||
|
my $flags = "--max-time $server_response_maxtime ";
|
||||||
|
$flags .= "--silent ";
|
||||||
|
$flags .= "--verbose ";
|
||||||
|
$flags .= "--globoff ";
|
||||||
|
$flags .= "-u 'curltest:curltest' ";
|
||||||
|
$flags .= $extra;
|
||||||
|
$flags .= "\"$proto://$ip:$port/SERVER/verifiedserver\"";
|
||||||
|
|
||||||
|
my $cmd = "$VCURL $flags 2>$verifylog";
|
||||||
|
|
||||||
|
# check if this is our server running on this port:
|
||||||
|
logmsg "RUN: $cmd\n" if($verbose);
|
||||||
|
my @data = runclientoutput($cmd);
|
||||||
|
|
||||||
|
my $res = $? >> 8; # rotate the result
|
||||||
|
if($res & 128) {
|
||||||
|
logmsg "RUN: curl command died with a coredump\n";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach my $line (@data) {
|
||||||
|
if($line =~ /WE ROOLZ: (\d+)/) {
|
||||||
|
# this is our test server with a known pid!
|
||||||
|
$pid = 0+$1;
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if($pid <= 0 && @data && $data[0]) {
|
||||||
|
# this is not a known server
|
||||||
|
logmsg "RUN: Unknown server on our $server port: $port\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
# we can/should use the time it took to verify the server as a measure
|
||||||
|
# on how fast/slow this host is.
|
||||||
|
my $took = int(0.5+time()-$time);
|
||||||
|
|
||||||
|
if($verbose) {
|
||||||
|
logmsg "RUN: Verifying our test $server server took $took seconds\n";
|
||||||
|
}
|
||||||
|
$ftpchecktime = $took>=1?$took:1; # make sure it never is below 1
|
||||||
|
|
||||||
|
return $pid;
|
||||||
|
}
|
||||||
|
|
||||||
#######################################################################
|
#######################################################################
|
||||||
# Verify that the server that runs on $ip, $port is our server.
|
# Verify that the server that runs on $ip, $port is our server.
|
||||||
# Retry over several seconds before giving up. The ssh server in
|
# Retry over several seconds before giving up. The ssh server in
|
||||||
@ -1146,7 +1209,8 @@ my %protofunc = ('http' => \&verifyhttp,
|
|||||||
'socks' => \&verifysocks,
|
'socks' => \&verifysocks,
|
||||||
'gopher' => \&verifyhttp,
|
'gopher' => \&verifyhttp,
|
||||||
'httptls' => \&verifyhttptls,
|
'httptls' => \&verifyhttptls,
|
||||||
'dict' => \&verifyftp);
|
'dict' => \&verifyftp,
|
||||||
|
'smb' => \&verifysmb);
|
||||||
|
|
||||||
sub verifyserver {
|
sub verifyserver {
|
||||||
my ($proto, $ipvnum, $idnum, $ip, $port) = @_;
|
my ($proto, $ipvnum, $idnum, $ip, $port) = @_;
|
||||||
@ -2243,6 +2307,83 @@ sub rundictserver {
|
|||||||
return ($dictpid, $pid2);
|
return ($dictpid, $pid2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#######################################################################
|
||||||
|
# start the SMB server
|
||||||
|
#
|
||||||
|
sub runsmbserver {
|
||||||
|
my ($verbose, $alt, $port) = @_;
|
||||||
|
my $proto = "smb";
|
||||||
|
my $ip = $HOSTIP;
|
||||||
|
my $ipvnum = 4;
|
||||||
|
my $idnum = 1;
|
||||||
|
my $server;
|
||||||
|
my $srvrname;
|
||||||
|
my $pidfile;
|
||||||
|
my $logfile;
|
||||||
|
my $flags = "";
|
||||||
|
|
||||||
|
if($alt eq "ipv6") {
|
||||||
|
# No IPv6
|
||||||
|
}
|
||||||
|
|
||||||
|
$server = servername_id($proto, $ipvnum, $idnum);
|
||||||
|
|
||||||
|
$pidfile = $serverpidfile{$server};
|
||||||
|
|
||||||
|
# don't retry if the server doesn't work
|
||||||
|
if ($doesntrun{$pidfile}) {
|
||||||
|
return (0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $pid = processexists($pidfile);
|
||||||
|
if($pid > 0) {
|
||||||
|
stopserver($server, "$pid");
|
||||||
|
}
|
||||||
|
unlink($pidfile) if(-f $pidfile);
|
||||||
|
|
||||||
|
$srvrname = servername_str($proto, $ipvnum, $idnum);
|
||||||
|
|
||||||
|
$logfile = server_logfilename($LOGDIR, $proto, $ipvnum, $idnum);
|
||||||
|
|
||||||
|
$flags .= "--verbose 1 " if($debugprotocol);
|
||||||
|
$flags .= "--pidfile \"$pidfile\" --logfile \"$logfile\" ";
|
||||||
|
$flags .= "--id $idnum " if($idnum > 1);
|
||||||
|
$flags .= "--port $port --srcdir \"$srcdir\"";
|
||||||
|
|
||||||
|
my $cmd = "$srcdir/smbserver.py $flags";
|
||||||
|
my ($smbpid, $pid2) = startnew($cmd, $pidfile, 15, 0);
|
||||||
|
|
||||||
|
if($smbpid <= 0 || !pidexists($smbpid)) {
|
||||||
|
# it is NOT alive
|
||||||
|
logmsg "RUN: failed to start the $srvrname server\n";
|
||||||
|
stopserver($server, "$pid2");
|
||||||
|
displaylogs($testnumcheck);
|
||||||
|
$doesntrun{$pidfile} = 1;
|
||||||
|
return (0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Server is up. Verify that we can speak to it.
|
||||||
|
my $pid3 = verifyserver($proto, $ipvnum, $idnum, $ip, $port);
|
||||||
|
if(!$pid3) {
|
||||||
|
logmsg "RUN: $srvrname server failed verification\n";
|
||||||
|
# failed to talk to it properly. Kill the server and return failure
|
||||||
|
stopserver($server, "$smbpid $pid2");
|
||||||
|
displaylogs($testnumcheck);
|
||||||
|
$doesntrun{$pidfile} = 1;
|
||||||
|
return (0,0);
|
||||||
|
}
|
||||||
|
$pid2 = $pid3;
|
||||||
|
|
||||||
|
if($verbose) {
|
||||||
|
logmsg "RUN: $srvrname server is now running PID $smbpid\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(1);
|
||||||
|
|
||||||
|
return ($smbpid, $pid2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#######################################################################
|
#######################################################################
|
||||||
# Single shot http and gopher server responsiveness test. This should only
|
# Single shot http and gopher server responsiveness test. This should only
|
||||||
# be used to verify that a server present in %run hash is still functional
|
# be used to verify that a server present in %run hash is still functional
|
||||||
@ -2843,6 +2984,9 @@ sub subVariables {
|
|||||||
|
|
||||||
$$thing =~ s/%DICTPORT/$DICTPORT/g;
|
$$thing =~ s/%DICTPORT/$DICTPORT/g;
|
||||||
|
|
||||||
|
$$thing =~ s/%SMBPORT/$SMBPORT/g;
|
||||||
|
$$thing =~ s/%SMBSPORT/$SMBSPORT/g;
|
||||||
|
|
||||||
# server Unix domain socket paths
|
# server Unix domain socket paths
|
||||||
|
|
||||||
$$thing =~ s/%HTTPUNIXPATH/$HTTPUNIXPATH/g;
|
$$thing =~ s/%HTTPUNIXPATH/$HTTPUNIXPATH/g;
|
||||||
@ -4695,6 +4839,17 @@ sub startservers {
|
|||||||
$run{'dict'}="$pid $pid2";
|
$run{'dict'}="$pid $pid2";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
elsif($what eq "smb") {
|
||||||
|
if(!$run{'smb'}) {
|
||||||
|
($pid, $pid2) = runsmbserver($verbose, "", $SMBPORT);
|
||||||
|
if($pid <= 0) {
|
||||||
|
return "failed starting SMB server";
|
||||||
|
}
|
||||||
|
logmsg sprintf ("* pid SMB => %d %d\n", $pid, $pid2)
|
||||||
|
if($verbose);
|
||||||
|
$run{'dict'}="$pid $pid2";
|
||||||
|
}
|
||||||
|
}
|
||||||
elsif($what eq "none") {
|
elsif($what eq "none") {
|
||||||
logmsg "* starts no server\n" if ($verbose);
|
logmsg "* starts no server\n" if ($verbose);
|
||||||
}
|
}
|
||||||
@ -5155,6 +5310,8 @@ $HTTPPROXYPORT = $base++; # HTTP proxy port, when using CONNECT
|
|||||||
$HTTPPIPEPORT = $base++; # HTTP pipelining port
|
$HTTPPIPEPORT = $base++; # HTTP pipelining port
|
||||||
$HTTP2PORT = $base++; # HTTP/2 port
|
$HTTP2PORT = $base++; # HTTP/2 port
|
||||||
$DICTPORT = $base++; # DICT port
|
$DICTPORT = $base++; # DICT port
|
||||||
|
$SMBPORT = $base++; # SMB port
|
||||||
|
$SMBSPORT = $base++; # SMBS port
|
||||||
$HTTPUNIXPATH = 'http.sock'; # HTTP server Unix domain socket path
|
$HTTPUNIXPATH = 'http.sock'; # HTTP server Unix domain socket path
|
||||||
|
|
||||||
#######################################################################
|
#######################################################################
|
||||||
|
@ -105,7 +105,7 @@ sub servername_str {
|
|||||||
|
|
||||||
$proto = uc($proto) if($proto);
|
$proto = uc($proto) if($proto);
|
||||||
die "unsupported protocol: '$proto'" unless($proto &&
|
die "unsupported protocol: '$proto'" unless($proto &&
|
||||||
($proto =~ /^(((FTP|HTTP|HTTP\/2|IMAP|POP3|SMTP|HTTP-PIPE)S?)|(TFTP|SFTP|SOCKS|SSH|RTSP|GOPHER|HTTPTLS|DICT))$/));
|
($proto =~ /^(((FTP|HTTP|HTTP\/2|IMAP|POP3|SMTP|HTTP-PIPE)S?)|(TFTP|SFTP|SOCKS|SSH|RTSP|GOPHER|HTTPTLS|DICT|SMB|SMBS))$/));
|
||||||
|
|
||||||
$ipver = (not $ipver) ? 'ipv4' : lc($ipver);
|
$ipver = (not $ipver) ? 'ipv4' : lc($ipver);
|
||||||
die "unsupported IP version: '$ipver'" unless($ipver &&
|
die "unsupported IP version: '$ipver'" unless($ipver &&
|
||||||
|
377
tests/smbserver.py
Executable file
377
tests/smbserver.py
Executable file
@ -0,0 +1,377 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Project ___| | | | _ \| |
|
||||||
|
# / __| | | | |_) | |
|
||||||
|
# | (__| |_| | _ <| |___
|
||||||
|
# \___|\___/|_| \_\_____|
|
||||||
|
#
|
||||||
|
# Copyright (C) 2017, Daniel Stenberg, <daniel@haxx.se>, et al.
|
||||||
|
#
|
||||||
|
# This software is licensed as described in the file COPYING, which
|
||||||
|
# you should have received as part of this distribution. The terms
|
||||||
|
# are also available at https://curl.haxx.se/docs/copyright.html.
|
||||||
|
#
|
||||||
|
# You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
||||||
|
# copies of the Software, and permit persons to whom the Software is
|
||||||
|
# furnished to do so, under the terms of the COPYING file.
|
||||||
|
#
|
||||||
|
# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||||
|
# KIND, either express or implied.
|
||||||
|
#
|
||||||
|
"""Server for testing SMB"""
|
||||||
|
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
# unicode_literals)
|
||||||
|
import argparse
|
||||||
|
import ConfigParser
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
# Import our curl test data helper
|
||||||
|
import curl_test_data
|
||||||
|
|
||||||
|
# This saves us having to set up the PYTHONPATH explicitly
|
||||||
|
deps_dir = os.path.join(os.path.dirname(__file__), "python_dependencies")
|
||||||
|
sys.path.append(deps_dir)
|
||||||
|
from impacket import smbserver as imp_smbserver
|
||||||
|
from impacket import smb as imp_smb
|
||||||
|
from impacket.nt_errors import (STATUS_ACCESS_DENIED, STATUS_SUCCESS,
|
||||||
|
STATUS_NO_SUCH_FILE)
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
SERVER_MAGIC = "SERVER_MAGIC"
|
||||||
|
TESTS_MAGIC = "TESTS_MAGIC"
|
||||||
|
VERIFIED_REQ = "verifiedserver"
|
||||||
|
VERIFIED_RSP = b"WE ROOLZ: {pid}\n"
|
||||||
|
|
||||||
|
|
||||||
|
def smbserver(options):
|
||||||
|
"""Start up a TCP SMB server that serves forever
|
||||||
|
|
||||||
|
"""
|
||||||
|
if options.pidfile:
|
||||||
|
pid = os.getpid()
|
||||||
|
with open(options.pidfile, "w") as f:
|
||||||
|
f.write("{0}".format(pid))
|
||||||
|
|
||||||
|
# Here we write a mini config for the server
|
||||||
|
smb_config = ConfigParser.ConfigParser()
|
||||||
|
smb_config.add_section("global")
|
||||||
|
smb_config.set("global", "server_name", "SERVICE")
|
||||||
|
smb_config.set("global", "server_os", "UNIX")
|
||||||
|
smb_config.set("global", "server_domain", "WORKGROUP")
|
||||||
|
smb_config.set("global", "log_file", "")
|
||||||
|
smb_config.set("global", "credentials_file", "")
|
||||||
|
|
||||||
|
# We need a share which allows us to test that the server is running
|
||||||
|
smb_config.add_section("SERVER")
|
||||||
|
smb_config.set("SERVER", "comment", "server function")
|
||||||
|
smb_config.set("SERVER", "read only", "yes")
|
||||||
|
smb_config.set("SERVER", "share type", "0")
|
||||||
|
smb_config.set("SERVER", "path", SERVER_MAGIC)
|
||||||
|
|
||||||
|
# Have a share for tests. These files will be autogenerated from the
|
||||||
|
# test input.
|
||||||
|
smb_config.add_section("TESTS")
|
||||||
|
smb_config.set("TESTS", "comment", "tests")
|
||||||
|
smb_config.set("TESTS", "read only", "yes")
|
||||||
|
smb_config.set("TESTS", "share type", "0")
|
||||||
|
smb_config.set("TESTS", "path", TESTS_MAGIC)
|
||||||
|
|
||||||
|
if not options.srcdir or not os.path.isdir(options.srcdir):
|
||||||
|
raise ScriptException("--srcdir is mandatory")
|
||||||
|
|
||||||
|
test_data_dir = os.path.join(options.srcdir, "data")
|
||||||
|
|
||||||
|
smb_server = TestSmbServer(("127.0.0.1", options.port),
|
||||||
|
config_parser=smb_config,
|
||||||
|
test_data_directory=test_data_dir)
|
||||||
|
log.info("[SMB] setting up SMB server on port %s", options.port)
|
||||||
|
smb_server.processConfigFile()
|
||||||
|
smb_server.serve_forever()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
class TestSmbServer(imp_smbserver.SMBSERVER):
|
||||||
|
"""
|
||||||
|
Test server for SMB which subclasses the impacket SMBSERVER and provides
|
||||||
|
test functionality.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
address,
|
||||||
|
config_parser=None,
|
||||||
|
test_data_directory=None):
|
||||||
|
imp_smbserver.SMBSERVER.__init__(self,
|
||||||
|
address,
|
||||||
|
config_parser=config_parser)
|
||||||
|
|
||||||
|
# Set up a test data object so we can get test data later.
|
||||||
|
self.ctd = curl_test_data.TestData(test_data_directory)
|
||||||
|
|
||||||
|
# Override smbComNtCreateAndX so we can pretend to have files which
|
||||||
|
# don't exist.
|
||||||
|
self.hookSmbCommand(imp_smb.SMB.SMB_COM_NT_CREATE_ANDX,
|
||||||
|
self.create_and_x)
|
||||||
|
|
||||||
|
def create_and_x(self, conn_id, smb_server, smb_command, recv_packet):
|
||||||
|
"""
|
||||||
|
Our version of smbComNtCreateAndX looks for special test files and
|
||||||
|
fools the rest of the framework into opening them as if they were
|
||||||
|
normal files.
|
||||||
|
"""
|
||||||
|
conn_data = smb_server.getConnectionData(conn_id)
|
||||||
|
|
||||||
|
# Wrap processing in a try block which allows us to throw SmbException
|
||||||
|
# to control the flow.
|
||||||
|
try:
|
||||||
|
ncax_parms = imp_smb.SMBNtCreateAndX_Parameters(
|
||||||
|
smb_command["Parameters"])
|
||||||
|
|
||||||
|
path = self.get_share_path(conn_data,
|
||||||
|
ncax_parms["RootFid"],
|
||||||
|
recv_packet["Tid"])
|
||||||
|
log.info("[SMB] Requested share path: %s", path)
|
||||||
|
|
||||||
|
disposition = ncax_parms["Disposition"]
|
||||||
|
log.debug("[SMB] Requested disposition: %s", disposition)
|
||||||
|
|
||||||
|
# Currently we only support reading files.
|
||||||
|
if disposition != imp_smb.FILE_OPEN:
|
||||||
|
raise SmbException(STATUS_ACCESS_DENIED,
|
||||||
|
"Only support reading files")
|
||||||
|
|
||||||
|
# Check to see if the path we were given is actually a
|
||||||
|
# magic path which needs generating on the fly.
|
||||||
|
if path not in [SERVER_MAGIC, TESTS_MAGIC]:
|
||||||
|
# Pass the command onto the original handler.
|
||||||
|
return imp_smbserver.SMBCommands.smbComNtCreateAndX(conn_id,
|
||||||
|
smb_server,
|
||||||
|
smb_command,
|
||||||
|
recv_packet)
|
||||||
|
|
||||||
|
flags2 = recv_packet["Flags2"]
|
||||||
|
ncax_data = imp_smb.SMBNtCreateAndX_Data(flags=flags2,
|
||||||
|
data=smb_command[
|
||||||
|
"Data"])
|
||||||
|
requested_file = imp_smbserver.decodeSMBString(
|
||||||
|
flags2,
|
||||||
|
ncax_data["FileName"])
|
||||||
|
log.debug("[SMB] User requested file '%s'", requested_file)
|
||||||
|
|
||||||
|
if path == SERVER_MAGIC:
|
||||||
|
fid, full_path = self.get_server_path(requested_file)
|
||||||
|
else:
|
||||||
|
assert (path == TESTS_MAGIC)
|
||||||
|
fid, full_path = self.get_test_path(requested_file)
|
||||||
|
|
||||||
|
resp_parms = imp_smb.SMBNtCreateAndXResponse_Parameters()
|
||||||
|
resp_data = ""
|
||||||
|
|
||||||
|
# Simple way to generate a fid
|
||||||
|
if len(conn_data["OpenedFiles"]) == 0:
|
||||||
|
fakefid = 1
|
||||||
|
else:
|
||||||
|
fakefid = conn_data["OpenedFiles"].keys()[-1] + 1
|
||||||
|
resp_parms["Fid"] = fakefid
|
||||||
|
resp_parms["CreateAction"] = disposition
|
||||||
|
|
||||||
|
if os.path.isdir(path):
|
||||||
|
resp_parms[
|
||||||
|
"FileAttributes"] = imp_smb.SMB_FILE_ATTRIBUTE_DIRECTORY
|
||||||
|
resp_parms["IsDirectory"] = 1
|
||||||
|
else:
|
||||||
|
resp_parms["IsDirectory"] = 0
|
||||||
|
resp_parms["FileAttributes"] = ncax_parms["FileAttributes"]
|
||||||
|
|
||||||
|
# Get this file's information
|
||||||
|
resp_info, error_code = imp_smbserver.queryPathInformation(
|
||||||
|
"", full_path, level=imp_smb.SMB_QUERY_FILE_ALL_INFO)
|
||||||
|
|
||||||
|
if error_code != STATUS_SUCCESS:
|
||||||
|
raise SmbException(error_code, "Failed to query path info")
|
||||||
|
|
||||||
|
resp_parms["CreateTime"] = resp_info["CreationTime"]
|
||||||
|
resp_parms["LastAccessTime"] = resp_info[
|
||||||
|
"LastAccessTime"]
|
||||||
|
resp_parms["LastWriteTime"] = resp_info["LastWriteTime"]
|
||||||
|
resp_parms["LastChangeTime"] = resp_info[
|
||||||
|
"LastChangeTime"]
|
||||||
|
resp_parms["FileAttributes"] = resp_info[
|
||||||
|
"ExtFileAttributes"]
|
||||||
|
resp_parms["AllocationSize"] = resp_info[
|
||||||
|
"AllocationSize"]
|
||||||
|
resp_parms["EndOfFile"] = resp_info["EndOfFile"]
|
||||||
|
|
||||||
|
# Let's store the fid for the connection
|
||||||
|
# smbServer.log("Create file %s, mode:0x%x" % (pathName, mode))
|
||||||
|
conn_data["OpenedFiles"][fakefid] = {}
|
||||||
|
conn_data["OpenedFiles"][fakefid]["FileHandle"] = fid
|
||||||
|
conn_data["OpenedFiles"][fakefid]["FileName"] = path
|
||||||
|
conn_data["OpenedFiles"][fakefid]["DeleteOnClose"] = False
|
||||||
|
|
||||||
|
except SmbException as s:
|
||||||
|
log.debug("[SMB] SmbException hit: %s", s)
|
||||||
|
error_code = s.error_code
|
||||||
|
resp_parms = ""
|
||||||
|
resp_data = ""
|
||||||
|
|
||||||
|
resp_cmd = imp_smb.SMBCommand(imp_smb.SMB.SMB_COM_NT_CREATE_ANDX)
|
||||||
|
resp_cmd["Parameters"] = resp_parms
|
||||||
|
resp_cmd["Data"] = resp_data
|
||||||
|
smb_server.setConnectionData(conn_id, conn_data)
|
||||||
|
|
||||||
|
return [resp_cmd], None, error_code
|
||||||
|
|
||||||
|
def get_share_path(self, conn_data, root_fid, tid):
|
||||||
|
conn_shares = conn_data["ConnectedShares"]
|
||||||
|
|
||||||
|
if tid in conn_shares:
|
||||||
|
if root_fid > 0:
|
||||||
|
# If we have a rootFid, the path is relative to that fid
|
||||||
|
path = conn_data["OpenedFiles"][root_fid]["FileName"]
|
||||||
|
log.debug("RootFid present %s!" % path)
|
||||||
|
else:
|
||||||
|
if "path" in conn_shares[tid]:
|
||||||
|
path = conn_shares[tid]["path"]
|
||||||
|
else:
|
||||||
|
raise SmbException(STATUS_ACCESS_DENIED,
|
||||||
|
"Connection share had no path")
|
||||||
|
else:
|
||||||
|
raise SmbException(imp_smbserver.STATUS_SMB_BAD_TID,
|
||||||
|
"TID was invalid")
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
||||||
|
def get_server_path(self, requested_filename):
|
||||||
|
log.debug("[SMB] Get server path '%s'", requested_filename)
|
||||||
|
|
||||||
|
if requested_filename not in [VERIFIED_REQ]:
|
||||||
|
raise SmbException(STATUS_NO_SUCH_FILE, "Couldn't find the file")
|
||||||
|
|
||||||
|
fid, filename = tempfile.mkstemp()
|
||||||
|
log.debug("[SMB] Created %s (%d) for storing '%s'",
|
||||||
|
filename, fid, requested_filename)
|
||||||
|
|
||||||
|
contents = ""
|
||||||
|
|
||||||
|
if requested_filename == VERIFIED_REQ:
|
||||||
|
log.debug("[SMB] Verifying server is alive")
|
||||||
|
contents = VERIFIED_RSP.format(pid=os.getpid())
|
||||||
|
|
||||||
|
self.write_to_fid(fid, contents)
|
||||||
|
return fid, filename
|
||||||
|
|
||||||
|
def write_to_fid(self, fid, contents):
|
||||||
|
# Write the contents to file descriptor
|
||||||
|
os.write(fid, contents)
|
||||||
|
os.fsync(fid)
|
||||||
|
|
||||||
|
# Rewind the file to the beginning so a read gets us the contents
|
||||||
|
os.lseek(fid, 0, os.SEEK_SET)
|
||||||
|
|
||||||
|
def get_test_path(self, requested_filename):
|
||||||
|
log.info("[SMB] Get reply data from 'test%s'", requested_filename)
|
||||||
|
|
||||||
|
fid, filename = tempfile.mkstemp()
|
||||||
|
log.debug("[SMB] Created %s (%d) for storing test '%s'",
|
||||||
|
filename, fid, requested_filename)
|
||||||
|
|
||||||
|
try:
|
||||||
|
contents = self.ctd.get_test_data(requested_filename)
|
||||||
|
self.write_to_fid(fid, contents)
|
||||||
|
return fid, filename
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
log.exception("Failed to make test file")
|
||||||
|
raise SmbException(STATUS_NO_SUCH_FILE, "Failed to make test file")
|
||||||
|
|
||||||
|
|
||||||
|
class SmbException(Exception):
|
||||||
|
def __init__(self, error_code, error_message):
|
||||||
|
super(SmbException, self).__init__(error_message)
|
||||||
|
self.error_code = error_code
|
||||||
|
|
||||||
|
|
||||||
|
class ScriptRC(object):
|
||||||
|
"""Enum for script return codes"""
|
||||||
|
SUCCESS = 0
|
||||||
|
FAILURE = 1
|
||||||
|
EXCEPTION = 2
|
||||||
|
|
||||||
|
|
||||||
|
class ScriptException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_options():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument("--port", action="store", default=9017,
|
||||||
|
type=int, help="port to listen on")
|
||||||
|
parser.add_argument("--verbose", action="store", type=int, default=0,
|
||||||
|
help="verbose output")
|
||||||
|
parser.add_argument("--pidfile", action="store",
|
||||||
|
help="file name for the PID")
|
||||||
|
parser.add_argument("--logfile", action="store",
|
||||||
|
help="file name for the log")
|
||||||
|
parser.add_argument("--srcdir", action="store", help="test directory")
|
||||||
|
parser.add_argument("--id", action="store", help="server ID")
|
||||||
|
parser.add_argument("--ipv4", action="store_true", default=0,
|
||||||
|
help="IPv4 flag")
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logging(options):
|
||||||
|
"""
|
||||||
|
Set up logging from the command line options
|
||||||
|
"""
|
||||||
|
root_logger = logging.getLogger()
|
||||||
|
add_stdout = False
|
||||||
|
|
||||||
|
formatter = logging.Formatter("%(asctime)s %(levelname)-5.5s %(message)s")
|
||||||
|
|
||||||
|
# Write out to a logfile
|
||||||
|
if options.logfile:
|
||||||
|
handler = logging.FileHandler(options.logfile, mode="w")
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
handler.setLevel(logging.DEBUG)
|
||||||
|
root_logger.addHandler(handler)
|
||||||
|
else:
|
||||||
|
# The logfile wasn't specified. Add a stdout logger.
|
||||||
|
add_stdout = True
|
||||||
|
|
||||||
|
if options.verbose:
|
||||||
|
# Add a stdout logger as well in verbose mode
|
||||||
|
root_logger.setLevel(logging.DEBUG)
|
||||||
|
add_stdout = True
|
||||||
|
else:
|
||||||
|
root_logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
if add_stdout:
|
||||||
|
stdout_handler = logging.StreamHandler(sys.stdout)
|
||||||
|
stdout_handler.setFormatter(formatter)
|
||||||
|
stdout_handler.setLevel(logging.DEBUG)
|
||||||
|
root_logger.addHandler(stdout_handler)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Get the options from the user.
|
||||||
|
options = get_options()
|
||||||
|
|
||||||
|
# Setup logging using the user options
|
||||||
|
setup_logging(options)
|
||||||
|
|
||||||
|
# Run main script.
|
||||||
|
try:
|
||||||
|
rc = smbserver(options)
|
||||||
|
except Exception as e:
|
||||||
|
log.exception(e)
|
||||||
|
rc = ScriptRC.EXCEPTION
|
||||||
|
|
||||||
|
log.info("[SMB] Returning %d", rc)
|
||||||
|
sys.exit(rc)
|
Loading…
Reference in New Issue
Block a user