Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

ptftpd

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ptftpd - npm Package Compare versions

Comparing version
1.2
to
1.3
+5
-5
METADATA

@@ -1,4 +0,4 @@

Metadata-Version: 2.0
Metadata-Version: 2.1
Name: ptftpd
Version: 1.2
Version: 1.3
Summary: pTFTPd, a pure-Python TFTP tool suite that works

@@ -26,6 +26,6 @@ Home-page: https://github.com/mpetazzoni/ptftpd

- ``dhcpd``: a simple, stripped-down DHCP server.
- ``ptftpd``: the TFTP server (RFC1350, 2347, 2348 and 2349 compliant)
- ``ptftpd``: the TFTP server (RFC1350, 2347, 2348, 2349 and 7440 compliant)
- ``pxed``: a one-call PXE server using dhcpd and ptftpd.
- ``ptftp``: a simple TFTP client (RFC1350, 2347, 2348 and 2349 compliant and
capable)
- ``ptftp``: a simple TFTP client (RFC1350, 2347, 2348, 2349 and 7440
compliant and capable)

@@ -32,0 +32,0 @@ They all support the ``--help`` option to present the usage summary to

#!/usr/bin/env python
# coding=utf-8

@@ -356,5 +357,5 @@ # Authors: David Anderson

server.serve_forever()
except socket.error, e:
except socket.error as e:
sys.stderr.write('Socket error (%s): %s!\n' %
(errno.errorcode[e[0]], e[1]))
(errno.errorcode[e.args[0]], e.args[1]))
return 1

@@ -361,0 +362,0 @@

#!/usr/bin/env python
# coding=utf-8

@@ -276,3 +277,3 @@ # Author: David Anderson

current = time.time()
old = [ip for ip, to in self.ips_allocated.iteritems()
old = [ip for ip, to in self.ips_allocated.items()
if to <= current]

@@ -400,3 +401,3 @@ for ip in old:

loglevel=options.loglevel,
format='%(levelname)s(%(name)s): %(message)s')
fmt='%(levelname)s(%(name)s): %(message)s')

@@ -407,5 +408,5 @@ try:

server.serve_forever()
except socket.error, e:
except socket.error as e:
sys.stderr.write('Socket error (%s): %s!\n' %
(errno.errorcode[e[0]], e[1]))
(errno.errorcode[e.args[0]], e.args[1]))
return 1

@@ -412,0 +413,0 @@

@@ -0,1 +1,2 @@

# coding=utf-8
# Author: Maxime Petazzoni

@@ -61,3 +62,3 @@ # maxime.petazzoni@bulix.org

def __init__(self, stream, loglevel, format):
def __init__(self, stream, loglevel, fmt):
"""Creates a new notification engine that simply logs to the given

@@ -70,3 +71,3 @@ stream.

outputted.
format (string format): default format string to apply
fmt (string format): default format string to apply
on log messages.

@@ -77,3 +78,3 @@ """

self.setFormatter(logging.Formatter(format))
self.setFormatter(logging.Formatter(fmt))
self.setLevel(loglevel)

@@ -83,4 +84,4 @@

def install(logger, stream=sys.stderr, loglevel=logging.WARNING,
format='%(message)s'):
handler = StreamEngine(stream, loglevel, format)
fmt='%(message)s'):
handler = StreamEngine(stream, loglevel, fmt)
logger.addHandler(handler)

@@ -90,3 +91,3 @@

class DetailFilter(logging.Filter):
"""This log filter filters log records that don't posess the extra
"""This log filter filters log records that don't possess the extra
information we require for advanced notifications (host, port, file name

@@ -97,3 +98,3 @@ and transfer state)."""

r = record.__dict__
return ('host' in r and 'port' in r and 'file' in r and 'state' in r)
return all(['host' in r, 'port' in r, 'file' in r, 'state' in r])

@@ -103,3 +104,3 @@

"""The DetailledStreamEngine is a extension of the StreamEngine define
above designed to log detailled notifications. Pertinent log records are
above designed to log detailed notifications. Pertinent log records are
filtered using the DetailFilter, as set up by the install() method."""

@@ -121,4 +122,4 @@

def install(logger, stream=sys.stderr, loglevel=logging.INFO,
format='%(message)s (%(host)s:%(port)d#%(file)s %(state)s)'):
handler = DetailledStreamEngine(stream, loglevel, format)
fmt='%(message)s (%(host)s:%(port)d#%(file)s %(state)s)'):
handler = DetailledStreamEngine(stream, loglevel, fmt)
handler.addFilter(DetailFilter())

@@ -153,4 +154,4 @@ logger.addHandler(handler)

callable = self.callbacks.get(record.state, self._nop)
callable(host=record.host,
callback = self.callbacks.get(record.state, self._nop)
callback(host=record.host,
port=record.port,

@@ -161,3 +162,5 @@ file=record.file,

@staticmethod
def install(logger, callbacks={}):
def install(logger, callbacks=None):
if callbacks is None:
callbacks = {}
handler = CallbackEngine(callbacks)

@@ -164,0 +167,0 @@ handler.addFilter(DetailFilter())

@@ -0,1 +1,2 @@

# coding=utf-8
# Author: Maxime Petazzoni

@@ -38,2 +39,3 @@ # maxime.petazzoni@bulix.org

# - RFC2349 - TFTP Timeout interval and Transfer size options
# - RFC7440 - TFTP Windowsize Option

@@ -47,2 +49,8 @@ # TFTP data packet size. A data packet with a length less than this

# TFTP default window size (equivalent to the option not being used).
TFTP_DEFAULT_WINDOW_SIZE = 1
# Enhanced window size for LAN networks
TFTP_LAN_WINDOW_SIZE = 8
# Maximum packet number (2^16). When reached, we may want to wraparound and

@@ -100,12 +108,18 @@ # reset to TFTP_PACKETNUM_RESET to continue transfer (some clients may not

# TFTP transfer modes (mail is deprecated as of RFC1350)
TFTP_MODES = ['netascii', 'octet']
TFTP_MODES = set(['netascii', 'octet'])
NETASCII_TO_OCTET = re.compile('\r\n')
OCTET_TO_NETASCII = re.compile('\r?\n')
# TFTP option names, as defined in RFC2348 and RFC2349
# TFTP option names, as defined in RFC2348, RFC2349 and RFC7440
TFTP_OPTION_BLKSIZE = 'blksize'
TFTP_OPTION_TIMEOUT = 'timeout'
TFTP_OPTION_TSIZE = 'tsize'
TFTP_OPTION_WINDOWSIZE = 'windowsize'
TFTP_OPTIONS = [TFTP_OPTION_BLKSIZE, TFTP_OPTION_TIMEOUT, TFTP_OPTION_TSIZE]
TFTP_OPTIONS = set([
TFTP_OPTION_BLKSIZE,
TFTP_OPTION_TIMEOUT,
TFTP_OPTION_TSIZE,
TFTP_OPTION_WINDOWSIZE
])

@@ -118,4 +132,8 @@ TFTP_BLKSIZE_MIN = 8

TFTP_WINDOWSIZE_MIN = 1
TFTP_WINDOWSIZE_MAX = 65535
class TFTPHelper:
# noinspection PyPep8Naming
class TFTPHelper(object):
"""

@@ -141,7 +159,10 @@ Static helper methods for the TFTP protocol.

packet = struct.pack('!H%dsc%dsc' % (len(filename), len(mode)),
OP_RRQ, filename, '\0', mode, '\0')
OP_RRQ,
filename.encode('ascii'), b'\0',
mode.encode('ascii'), b'\0')
for opt, val in opts.iteritems():
for opt, val in opts.items():
packet += struct.pack('!%dsc%dsc' % (len(opt), len(str(val))),
opt, '\0', str(val), '\0')
opt.encode('ascii'), b'\0',
str(val).encode('ascii'), b'\0')

@@ -166,7 +187,10 @@ return packet

packet = struct.pack('!H%dsc%dsc' % (len(filename), len(mode)),
OP_WRQ, filename, '\0', mode, '\0')
OP_WRQ,
filename.encode('ascii'), b'\0',
mode.encode('ascii'), b'\0')
for opt, val in opts.iteritems():
for opt, val in opts.items():
packet += struct.pack('!%dsc%dsc' % (len(opt), len(str(val))),
opt, '\0', str(val), '\0')
opt.encode('ascii'), b'\0',
str(val).encode('ascii'), b'\0')

@@ -211,3 +235,4 @@ return packet

return struct.pack('!HH%dsc' % len(error),
OP_ERROR, errno, error, '\0')
OP_ERROR, errno,
error.encode('ascii'), b'\0')

@@ -220,3 +245,3 @@ def createDATA(num, data):

num: the data packet number (int).
data: the data to be sent (string).
data: the data to be sent (bytes).
Returns:

@@ -243,6 +268,7 @@ The data packet as a string.

opts_str = ""
for opt, val in opts.iteritems():
for opt, val in opts.items():
opts_str += "%s%c%s%c" % (opt, '\0', val, '\0')
return struct.pack('!H%ds' % len(opts_str), OP_OACK, opts_str)
return struct.pack('!H%ds' % len(opts_str),
OP_OACK, opts_str.encode('ascii'))

@@ -261,3 +287,3 @@ def parseRRQ(request):

packet = request.split('\0')[:-1]
packet = request.split(b'\0')[:-1]

@@ -267,11 +293,16 @@ # If the length of the parsed list is not even, the packet is

if len(packet) % 2 != 0:
raise SyntaxError
raise SyntaxError('invalid request packet')
filename = packet[0]
mode = packet[1].lower()
filename = packet[0].decode('ascii')
if not filename:
raise SyntaxError('invalid filename')
mode = packet[1].decode('ascii').lower()
if mode not in TFTP_MODES:
raise SyntaxError('unknown mode %s' % mode)
opts = {}
for i in xrange(2, len(packet)-1, 2):
opt = packet[i].lower()
val = packet[i+1]
for i in range(2, len(packet)-1, 2):
opt = packet[i].decode('ascii').lower()
val = packet[i+1].decode('ascii')

@@ -281,10 +312,5 @@ if opt in TFTP_OPTIONS:

try:
TFTP_MODES.index(mode)
if filename != '':
l.debug(" < %s: %s (mode: %s, opts: %s)" %
(TFTP_OPS[OP_RRQ], filename, mode, opts))
return filename, mode, opts
except ValueError:
raise SyntaxError()
l.debug(" < %s: %s (mode: %s, opts: %s)" %
(TFTP_OPS[OP_RRQ], filename, mode, opts))
return filename, mode, opts

@@ -303,3 +329,3 @@ def parseWRQ(request):

packet = request.split('\0')[:-1]
packet = request.split(b'\0')[:-1]

@@ -309,11 +335,16 @@ # If the length of the parsed list is not even, the packet is

if len(packet) % 2 != 0:
raise SyntaxError()
raise SyntaxError('invalid request packet')
filename = packet[0]
mode = packet[1].lower()
filename = packet[0].decode('ascii')
if not filename:
raise SyntaxError('invalid filename')
mode = packet[1].decode('ascii').lower()
if mode not in TFTP_MODES:
raise SyntaxError('unknown mode %s' % mode)
opts = {}
for i in xrange(2, len(packet)-1, 2):
opt = packet[i].lower()
val = packet[i+1]
for i in range(2, len(packet)-1, 2):
opt = packet[i].decode('ascii').lower()
val = packet[i+1].decode('ascii')

@@ -323,10 +354,5 @@ if opt in TFTP_OPTIONS:

try:
TFTP_MODES.index(mode)
if filename != '':
l.debug(" < %s: %s (mode: %s, opts: %s)" %
(TFTP_OPS[OP_WRQ], filename, mode, opts))
return filename, mode, opts
except ValueError:
raise SyntaxError()
l.debug(" < %s: %s (mode: %s, opts: %s)" %
(TFTP_OPS[OP_WRQ], filename, mode, opts))
return filename, mode, opts

@@ -356,3 +382,3 @@ def parseACK(request):

except struct.error:
raise SyntaxError()
raise SyntaxError('invalid acknowledgment packet')

@@ -381,3 +407,3 @@ def parseDATA(request):

except struct.error:
raise SyntaxError()
raise SyntaxError('invalid data packet')

@@ -400,3 +426,3 @@ def parseERROR(request):

errno = packet[0]
errmsg = request[2:].split('\0')[0]
errmsg = request[2:].split(b'\0')[0].decode('ascii')

@@ -406,3 +432,3 @@ l.debug(" < %s: %s" % (TFTP_OPS[OP_ERROR], errmsg))

except (struct.error, IndexError):
raise SyntaxError()
raise SyntaxError('invalid error packet')

@@ -419,3 +445,3 @@ def parseOACK(request):

packet = request.split('\0')[:-1]
packet = request.split(b'\0')[:-1]

@@ -425,7 +451,9 @@ # If the length of the parsed list is not even, the packet is

if len(packet) % 2 != 0:
raise SyntaxError()
raise SyntaxError('invalid request packet')
opts = {}
for i in xrange(0, len(packet)-1, 2):
opts[packet[i]] = packet[i+1]
for i in range(0, len(packet)-1, 2):
opt = packet[i].decode('ascii').lower()
val = packet[i+1].decode('ascii')
opts[opt] = val

@@ -436,3 +464,3 @@ l.debug(" < %s: %s" % (TFTP_OPS[OP_OACK], opts))

def getOP(data):
def get_opcode(data):
if data:

@@ -442,3 +470,3 @@ try:

except (struct.error, KeyError):
raise SyntaxError()
raise SyntaxError('invalid packet')

@@ -461,3 +489,3 @@ return None

blksize = int(opts[TFTP_OPTION_BLKSIZE])
if blksize >= TFTP_BLKSIZE_MIN and blksize <= TFTP_BLKSIZE_MAX:
if TFTP_BLKSIZE_MIN <= blksize <= TFTP_BLKSIZE_MAX:
used[TFTP_OPTION_BLKSIZE] = blksize

@@ -471,3 +499,3 @@ else:

timeout = int(opts[TFTP_OPTION_TIMEOUT])
if timeout >= TFTP_TIMEOUT_MIN and timeout <= TFTP_TIMEOUT_MAX:
if TFTP_TIMEOUT_MIN <= timeout <= TFTP_TIMEOUT_MAX:
used[TFTP_OPTION_TIMEOUT] = timeout

@@ -480,4 +508,17 @@ else:

if TFTP_OPTION_WINDOWSIZE in opts:
windowsize = int(opts[TFTP_OPTION_WINDOWSIZE])
if TFTP_WINDOWSIZE_MIN <= windowsize <= TFTP_WINDOWSIZE_MAX:
used[TFTP_OPTION_WINDOWSIZE] = windowsize
else:
return None
else:
used[TFTP_OPTION_WINDOWSIZE] = TFTP_DEFAULT_WINDOW_SIZE
return used
def get_data_size(blksize):
"""Return the expected size of a DATA packet, in bytes."""
return TFTP_OPCODE_LEN + 2 + blksize
createRRQ = staticmethod(createRRQ)

@@ -497,3 +538,4 @@ createWRQ = staticmethod(createWRQ)

getOP = staticmethod(getOP)
get_opcode = staticmethod(get_opcode)
parse_options = staticmethod(parse_options)
get_data_size = staticmethod(get_data_size)
#!/usr/bin/env python
# coding=utf-8

@@ -36,5 +37,5 @@ # Author: David Anderson

import notify
import tftpserver
import dhcpserver
from . import notify
from . import tftpserver
from . import dhcpserver

@@ -82,9 +83,9 @@ l = notify.getLogger('pxed')

loglevel=options.loglevel,
format='%(levelname)s(%(name)s): %(message)s')
fmt='%(levelname)s(%(name)s): %(message)s')
notify.StreamEngine.install(dhcpserver.l, stream=sys.stdout,
loglevel=options.loglevel,
format='%(levelname)s(%(name)s): %(message)s')
fmt='%(levelname)s(%(name)s): %(message)s')
notify.StreamEngine.install(tftpserver.l, stream=sys.stdout,
loglevel=options.loglevel,
format='%(levelname)s(%(name)s): %(message)s')
fmt='%(levelname)s(%(name)s): %(message)s')

@@ -95,9 +96,9 @@ try:

strict_rfc1350=options.strict_rfc1350)
except tftpserver.TFTPServerConfigurationError, e:
except tftpserver.TFTPServerConfigurationError as e:
sys.stderr.write('TFTP server configuration error: %s!\n' %
e.message)
e.args)
return 1
except socket.error, e:
except socket.error as e:
sys.stderr.write('Socket error (%s): %s!\n' %
(errno.errorcode[e[0]], e[1]))
(errno.errorcode[e.args[0]], e.args[1]))
return 1

@@ -104,0 +105,0 @@

@@ -0,1 +1,2 @@

# coding=utf-8
# Author: Maxime Petazzoni

@@ -35,3 +36,3 @@ # maxime.petazzoni@bulix.org

class TFTPState:
class TFTPState(object):
"""

@@ -73,3 +74,4 @@ This class represents a peer's state. Because SocketServer is not

self.opts = {
proto.TFTP_OPTION_BLKSIZE: proto.TFTP_DEFAULT_PACKET_SIZE
proto.TFTP_OPTION_BLKSIZE: proto.TFTP_DEFAULT_PACKET_SIZE,
proto.TFTP_OPTION_WINDOWSIZE: proto.TFTP_DEFAULT_WINDOW_SIZE,
}

@@ -80,2 +82,4 @@

self.packetnum = None # Current data packet number
self.last_acked = 0 # Packet number of the last acked
# DATA packet
self.loop_packetnum = loop_packet # Packet number wraparound toggle

@@ -87,3 +91,3 @@ self.total_packets = 0 # Total number of data packets sent

self.data = None
self.tosend = ""
self.tosend = bytes()

@@ -196,6 +200,6 @@ def extra(self, state):

if self.mode == 'netascii':
fromfile = proto.OCTET_TO_NETASCII.sub('\r\n', fromfile)
fromfile = proto.OCTET_TO_NETASCII.sub(b'\r\n', fromfile)
self.data = self.tosend + fromfile
self.tosend = ""
self.tosend = bytes()

@@ -217,4 +221,15 @@ self.packetnum += 1

return proto.TFTPHelper.createDATA(self.packetnum, self.data)
packet = proto.TFTPHelper.createDATA(self.packetnum, self.data)
# If the window hasn't been completed yet, send the DATA packet
# and provide a continuation for the next DATA packet to send in the
# window.
next_window = self.last_acked + self.opts[proto.TFTP_OPTION_WINDOWSIZE]
if self.state == STATE_SEND and self.packetnum < next_window:
return packet, self.next
# Otherwise just send this DATA packet and we'll wait for the client to
# reply with a ACK.
return packet
def __next_send_oack(self):

@@ -240,3 +255,3 @@ self.state = STATE_SEND if self.op == proto.OP_RRQ else STATE_RECV

self.file.write(self.data)
except IOError, e:
except IOError as e:
self.file.close()

@@ -253,4 +268,5 @@ if e.errno == errno.ENOSPC:

ack = proto.TFTPHelper.createACK(self.packetnum)
packetnum = self.packetnum
# Compute next packet number
if not self.done:

@@ -264,5 +280,9 @@ self.packetnum += 1

return ack
# Only return a ACK if the window size has been reached, or when done
next_window = self.last_acked + self.opts[proto.TFTP_OPTION_WINDOWSIZE]
if self.done or packetnum >= next_window:
self.last_acked = packetnum
return proto.TFTPHelper.createACK(packetnum)
def __next_error(self):
return proto.TFTPHelper.createERROR(self.error)
#!/usr/bin/env python
# coding=utf-8

@@ -22,2 +23,10 @@ # Author: Maxime Petazzoni

from __future__ import print_function
try:
# noinspection PyShadowingBuiltins
input = raw_input # Py2
except NameError:
pass # Py3
"""Simple TFTP client.

@@ -50,3 +59,3 @@

# UDP datagram size
_UDP_TRANSFER_SIZE = 8192
_UDP_TRANSFER_SIZE = 2**16

@@ -59,2 +68,3 @@ _PTFTP_DEFAULT_PORT = 69

proto.TFTP_OPTION_BLKSIZE: proto.TFTP_LAN_PACKET_SIZE,
proto.TFTP_OPTION_WINDOWSIZE: proto.TFTP_LAN_WINDOW_SIZE,
}

@@ -67,4 +77,68 @@

class TFTPClient:
# noinspection PyPep8Naming
class UDPMessageSocket(object):
"""A wrapper around a UDP datagram socket that provides per-message
receival semantics.
This wrapper is very specific to the TFTP client use case. Messages are
expected to be received one at a time, expect for DATA messages which may
come streaming when a window size greater than one is negotiated between
the client and the server. When that's the case, reading from the socket
returns more than one message but the client (and its state machine) still
expects to process them one at a time.
By utilizing the knowledge of the expected size of those DATA messages
(based on the negotiated block size) this wrapper slices the received data
accordingly to expose the desired one-at-a-time semantics.
"""
def __init__(self):
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.reset()
@property
def rport(self):
return self._rport
def settimeout(self, timeout):
self._sock.settimeout(timeout)
def send(self, message, peer):
self._sock.sendto(message, peer)
def reset(self):
self._buffer = None
self._rport = None
def close(self):
self._sock.close()
def recv(self, blksize):
if not self._buffer:
self._recv()
if not self._buffer:
return None
opcode = proto.TFTPHelper.get_opcode(self._buffer)
if opcode == proto.OP_DATA:
size = proto.TFTPHelper.get_data_size(blksize)
data, self._buffer = self._buffer[:size], self._buffer[size:]
else:
data, self._buffer = self._buffer, None
return data
def _recv(self):
(data, (address, port)) = self._sock.recvfrom(_UDP_TRANSFER_SIZE)
if not self._rport:
self._rport = port
elif port != self._rport:
# Ignore packets from other peers
return
self._buffer = data
# noinspection PyPep8Naming
class TFTPClient(object):
"""
A small and simple TFTP client to pull/push files from a TFTP server.

@@ -76,3 +150,3 @@ """

def __init__(self, peer, opts=None, mode='octet', rfc1350=False,
notification_callbacks={}):
notification_callbacks=None):
"""

@@ -92,2 +166,5 @@ Initializes the TFTP client.

if notification_callbacks is None:
notification_callbacks = {}
self.peer = peer

@@ -124,3 +201,3 @@ self.transfer_mode = mode

self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock = UDPMessageSocket()
self.sock.settimeout(state.STATE_TIMEOUT_SECS)

@@ -150,7 +227,7 @@ print('Connected to {}:{}.'.format(self.peer[0], self.peer[1]))

while True:
print
print()
try:
command = raw_input('tftp> ')
command = input('tftp> ')
except EOFError:
print
print()
break

@@ -174,2 +251,4 @@

self.blksize(cmd_parts[1:])
elif cmd_parts[0] in ('w', 'windowsize'):
self.windowsize(cmd_parts[1:])
else:

@@ -188,13 +267,14 @@ print('Unrecognized command. Try help.')

print 'Available commands:'
print
print '? help Display help'
print 'q quit Quit the TFTP client'
print 'm mode [newmode] Display or change transfer mode'
print 'b blksize [newsize] Display or change the transfer block size'
print
print 'g get [-f] <filename> Get <filename> from server.'
print ' (use -f to overwrite local file)'
print 'p put <filename> Push <filename> to the server'
print
print('Available commands:')
print()
print('? help Display help')
print('q quit Quit the TFTP client')
print('m mode [newmode] Display or change transfer mode')
print('b blksize [newsize] Display or change the transfer block size')
print('w windowsize [newsize] Display or change the transfer window size')
print()
print('g get [-f] <filename> Get <filename> from server.')
print(' (use -f to overwrite local file)')
print('p put <filename> Push <filename> to the server')
print()

@@ -216,9 +296,4 @@ def handle(self):

self.error = False
self.sock.reset()
# UDP recv size is _UDP_TRANSFER_SIZE or more if required by
# the used block size.
recvsize = _UDP_TRANSFER_SIZE
if self.opts[proto.TFTP_OPTION_BLKSIZE] > recvsize:
recvsize = self.opts[proto.TFTP_OPTION_BLKSIZE] + 4
# Process incoming packet until the state is cleared by the

@@ -228,10 +303,6 @@ # end of a succesfull transmission or an error

try:
(request, (raddress, rport)) = self.sock.recvfrom(recvsize)
if not len(request):
(request, (raddress, rport)) = self.sock.recvfrom(recvsize)
# Still nothing?
if not len(request):
self.error = (True, 'Communication error.')
return
blksize = self.opts[proto.TFTP_OPTION_BLKSIZE]
request = self.sock.recv(blksize)
if not request:
continue
except socket.timeout:

@@ -242,13 +313,6 @@ self.error = (True, 'Connection timed out.')

if not self.PTFTP_STATE.tid:
self.PTFTP_STATE.tid = rport
self.PTFTP_STATE.tid = self.sock.rport
print('Communicating with {}:{}.'
.format(self.peer[0], self.PTFTP_STATE.tid))
if self.PTFTP_STATE.tid != rport:
l.debug(
'Ignoring packet from {}:{}, we are connected to {}:{}.'
.format(raddress, rport, raddress, self.peer[0],
self.PTFTP_STATE.tid))
continue
# Reset the response packet

@@ -258,3 +322,3 @@ response = None

# Get the packet opcode and dispatch
opcode = proto.TFTPHelper.getOP(request)
opcode = proto.TFTPHelper.get_opcode(request)
if not opcode:

@@ -267,6 +331,6 @@ self.error = (True, None)

response = proto.TFTPHelper.createERROR(proto.ERROR_ILLEGAL_OP)
return
else:
try:
handler = getattr(self, "serve%s" % proto.TFTP_OPS[opcode])
response = handler(opcode, request[2:])
except AttributeError:

@@ -277,9 +341,6 @@ self.error = (True, 'Operation not supported.')

if not response:
response = handler(opcode, request[2:])
# Finally, send the response if we have one
if response:
self.sock.sendto(response,
(self.peer[0], self.PTFTP_STATE.tid))
peer = (self.peer[0], self.PTFTP_STATE.tid)
self.sock.send(response, peer)

@@ -480,3 +541,3 @@ def serveOACK(self, op, request):

self.PTFTP_STATE.state = state.STATE_RECV
except IOError, e:
except IOError as e:
print('Error: {}'.format(os.strerror(e.errno)))

@@ -496,3 +557,3 @@ print('Can\'t write to temporary file {}!'

# Everything's OK, let's go
print "Retrieving '%s' from the remote host..." % filename
print("Retrieving '%s' from the remote host..." % filename)

@@ -502,3 +563,3 @@ packet = proto.TFTPHelper.createRRQ(filepath, self.transfer_mode, opts)

transfer_start = datetime.today()
self.sock.sendto(packet, self.peer)
self.sock.send(packet, self.peer)
self.handle()

@@ -520,3 +581,3 @@ transfer_time = datetime.today() - transfer_start

shutil.copy(self.PTFTP_STATE.file.name, filename)
except IOError, e:
except IOError as e:
print('Error: {}'.format(os.strerror(e.errno)))

@@ -560,3 +621,3 @@ print('Can\'t copy temporary file to local file {}!'

self.PTFTP_STATE.state = state.STATE_SEND
except IOError, e:
except IOError as e:
print('Error: {}'.format(os.strerror(e.errno)))

@@ -575,3 +636,3 @@ print('Can\'t read from local file {}!'.format(filepath))

# Everything's OK, let's go
print "Pushing '%s' to the remote host..." % filepath
print("Pushing '%s' to the remote host..." % filepath)

@@ -581,3 +642,3 @@ packet = proto.TFTPHelper.createWRQ(filepath, self.transfer_mode, opts)

transfer_start = datetime.today()
self.sock.sendto(packet, self.peer)
self.sock.send(packet, self.peer)
self.handle()

@@ -633,2 +694,19 @@ transfer_time = datetime.today() - transfer_start

def windowsize(self, args):
if len(args) > 1:
print('Usage: windowsize [newsize]')
return
if not len(args):
print('Current window size: {} packet(s).'
.format(self.opts[proto.TFTP_OPTION_WINDOWSIZE]))
return
try:
self.opts[proto.TFTP_OPTION_WINDOWSIZE] = int(args[0])
print('Window size set to {} packet(s).'
.format(self.opts[proto.TFTP_OPTION_WINDOWSIZE]))
except ValueError:
print('Window size must be a number!')
def __get_speed(self, filesize, time):

@@ -640,17 +718,20 @@ return (filesize / 1024.0 /

def usage():
print "usage: %s [options]" % sys.argv[0]
print
print " -? --help Get help"
print " -h --host <host> Set TFTP server (default: %s)" % _PTFTP_DEFAULT_HOST
print " -p --port <port> Define the port to connect to (default: %d)" % _PTFTP_DEFAULT_PORT
print " -m --mode <mode> Set transfer mode (default: %s)" % _PTFTP_DEFAULT_MODE
print " Must be one of:", ', '.join(proto.TFTP_MODES)
print
print "Available extra options (using the TFTP option extension protocol):"
print " -b --blksize <n> Set transfer block size (default: %d bytes)" % proto.TFTP_LAN_PACKET_SIZE
print
print "To disable the use of TFTP extensions:"
print " -r --rfc1350 Strictly comply to the RFC1350 only (no extensions)"
print " This will discard other TFTP option values."
print
print("usage: %s [options]" % sys.argv[0])
print()
print(" -? --help Get help")
print(" -h --host <host> Set TFTP server (default: %s)" % _PTFTP_DEFAULT_HOST)
print(" -p --port <port> Define the port to connect to (default: %d)" % _PTFTP_DEFAULT_PORT)
print(" -m --mode <mode> Set transfer mode (default: %s)" % _PTFTP_DEFAULT_MODE)
print(" Must be one of:", ', '.join(proto.TFTP_MODES))
print()
print("Available extra options (using the TFTP option extension protocol):")
print(" -b --blksize <n> Set transfer block size (default: %d bytes)" % proto.TFTP_LAN_PACKET_SIZE)
print(" Must be between %d and %d" % (proto.TFTP_BLKSIZE_MIN, proto.TFTP_BLKSIZE_MAX))
print(" -w --windowsize <n> Set streaming window size (default: %d)" % proto.TFTP_LAN_WINDOW_SIZE)
print(" Must be between %d and %d" % (proto.TFTP_WINDOWSIZE_MIN, proto.TFTP_WINDOWSIZE_MAX))
print()
print("To disable the use of TFTP extensions:")
print(" -r --rfc1350 Strictly comply to the RFC1350 only (no extensions)")
print(" This will discard other TFTP option values.")
print()

@@ -663,6 +744,7 @@

try:
opts, args = getopt.getopt(sys.argv[1:], '?h:p:b:m:r',
opts, args = getopt.getopt(sys.argv[1:], '?h:p:b:w:m:r',
['help', 'host=',
'port=', 'blksize=',
'mode=', 'rfc1350'])
'windowsize=', 'mode=',
'rfc1350'])
except getopt.GetoptError:

@@ -696,2 +778,8 @@ usage()

return 2
if opt in ('-w', '--windowsize'):
try:
exts[proto.TFTP_OPTION_WINDOWSIZE] = int(val)
except ValueError:
print('Window size must be a number!')
return 2
if opt in ('-m', '--mode'):

@@ -698,0 +786,0 @@ if val in proto.TFTP_MODES:

#!/usr/bin/env python
# coding=utf-8

@@ -39,3 +40,3 @@ # Author: Maxime Petazzoni

import socket
import SocketServer
import subprocess
import stat

@@ -50,2 +51,7 @@ import sys

try:
import SocketServer as socketserver # Py2
except ImportError:
import socketserver # Py3
l = notify.getLogger('tftpd')

@@ -59,5 +65,3 @@

def get_ip_config_for_iface(iface):
"""Retrieve and return the IP address/netmask and MAC address of the
given interface."""
"""Retrieve and return the IP address/netmask of the given interface."""
if iface not in netifaces.interfaces():

@@ -69,12 +73,18 @@ raise TFTPServerConfigurationError(

inet = details[netifaces.AF_INET][0]
link = details[netifaces.AF_LINK][0]
return inet['addr'], inet['netmask']
return inet['addr'], inet['netmask'], link['addr']
def get_max_udp_datagram_size():
"""Retrieve the maximum UDP datagram size allowed by the system."""
val = subprocess.check_output(['sysctl', '-n', 'net.inet.udp.maxdgram'])
return int(val)
class TFTPServerConfigurationError(Exception):
"""The configuration of the pTFTPd is incorrect."""
pass
class TFTPServerHandler(SocketServer.DatagramRequestHandler):
# noinspection PyPep8Naming
class TFTPServerHandler(socketserver.DatagramRequestHandler):
"""

@@ -89,8 +99,6 @@ The SocketServer UDP datagram handler for the TFTP protocol.

"""
request = self.rfile.read()
response = None
# Get the packet opcode and dispatch
opcode = proto.TFTPHelper.getOP(request)
opcode = proto.TFTPHelper.get_opcode(request)

@@ -104,4 +112,3 @@ if not opcode:

response = proto.TFTPHelper.createERROR(proto.ERROR_ILLEGAL_OP)
self.wfile.write(response)
self.wfile.flush()
self.send_response(response)
return

@@ -111,2 +118,3 @@

handler = getattr(self, "serve%s" % proto.TFTP_OPS[opcode])
response = handler(opcode, request[2:])
except AttributeError:

@@ -117,8 +125,31 @@ l.error("Unsupported operation %s" % opcode)

'Operation not supported by server.')
except:
response = proto.TFTPHelper.createERROR(
proto.ERROR_UNDEF,
'Server error.')
finally:
self.send_response(response)
response = handler(opcode, request[2:])
if response:
self.wfile.write(response)
self.wfile.flush()
def send_response(self, response):
"""
Send a response to the client.
Args:
response (bytes or tuple): the response packet sequence. If the
argument is a simple bytestring object, it is sent as-is. If it is
a tuple, it is expected to be a 2-uple containing first a
bytestring packet, and second a function that, when called, returns
the next packet sequence to send through this method (recursively).
"""
if not response:
return
if type(response) == tuple:
self.send_response(response[0])
self.send_response(response[1]())
return
self.wfile.write(response)
self.wfile.flush()
def finish_state(self, peer_state):

@@ -173,2 +204,12 @@ self.server.clients[self.client_address] = peer_state

if opts:
blksize = opts[proto.TFTP_OPTION_BLKSIZE]
windowsize = opts[proto.TFTP_OPTION_WINDOWSIZE]
max_window_size = int(
get_max_udp_datagram_size() /
proto.TFTPHelper.get_data_size(blksize))
if windowsize > max_window_size:
l.info('Restricting window size to %d to fit UDP.' %
max_window_size)
opts[proto.TFTP_OPTION_WINDOWSIZE] = max_window_size
# HOOK: this is where we should check that we accept

@@ -184,3 +225,3 @@ # the options requested by the client.

except IOError, e:
except IOError as e:
peer_state.state = state.STATE_ERROR

@@ -244,3 +285,3 @@

except IOError, e:
except IOError as e:
# Otherwise, if the open failed because the file did not

@@ -323,2 +364,3 @@ # exist, create it and go on

elif peer_state.packetnum != num:
# TODO: handle ACK recovery when operating in streaming mode.
peer_state.state = state.STATE_ERROR

@@ -334,2 +376,3 @@ peer_state.error = proto.ERROR_ILLEGAL_OP

peer_state.last_acked = num
return peer_state.next()

@@ -398,3 +441,3 @@

next = peer_state.next()
next_state = peer_state.next()

@@ -414,3 +457,3 @@ if peer_state.done:

return next
return next_state

@@ -473,3 +516,3 @@ l.error('Unexpected DATA!',

for peer, peer_state in self.clients.iteritems():
for peer, peer_state in self.clients.items():
delta = datetime.today() - peer_state.last_seen

@@ -491,5 +534,9 @@ if delta > timedelta(seconds=state.STATE_TIMEOUT_SECS):

def __init__(self, iface, root, port=_PTFTPD_DEFAULT_PORT,
strict_rfc1350=False, notification_callbacks={}):
strict_rfc1350=False, notification_callbacks=None):
if notification_callbacks is None:
notification_callbacks = {}
self.iface, self.root, self.port, self.strict_rfc1350 = \
iface, root, port, strict_rfc1350
iface, root, port, strict_rfc1350
self.client_registry = {}

@@ -501,4 +548,4 @@

self.ip, self.netmask, self.mac = get_ip_config_for_iface(self.iface)
self.server = SocketServer.UDPServer((self.ip, port),
self.ip, self.netmask = get_ip_config_for_iface(self.iface)
self.server = socketserver.UDPServer((self.ip, port),
TFTPServerHandler)

@@ -514,4 +561,4 @@ self.server.root = self.root

def serve_forever(self):
l.info("Serving TFTP requests on %s:%d in %s" %
(self.iface, self.port, self.root))
l.info("Serving TFTP requests on %s/%s:%d in %s" %
(self.iface, self.ip, self.port, self.root))
self.cleanup_thread.start()

@@ -550,3 +597,3 @@ self.server.serve_forever()

loglevel=options.loglevel,
format='%(levelname)s(%(name)s): %(message)s')
fmt='%(levelname)s(%(name)s): %(message)s')

@@ -556,10 +603,10 @@ try:

server.serve_forever()
except TFTPServerConfigurationError, e:
except TFTPServerConfigurationError as e:
sys.stderr.write('TFTP server configuration error: %s!' %
e.message)
e.args)
return 1
except socket.error, e:
except socket.error as e:
sys.stderr.write('Error creating a listening socket on port %d: '
'%s (%s).\n' % (options.port, e[1],
errno.errorcode[e[0]]))
'%s (%s).\n' % (options.port, e.args[1],
errno.errorcode[e.args[0]]))
return 1

@@ -566,0 +613,0 @@

@@ -0,2 +1,3 @@

# coding=utf-8
name = 'ptftpd'
version = '1.2'
version = '1.3'
+17
-19

@@ -1,19 +0,17 @@

ptftpd-1.2.dist-info/DESCRIPTION.rst,sha256=YwkcOW3iL0quNYbeZgI4-yIjBuGXfDagaEuo0oP9BhA,2913
ptftpd-1.2.dist-info/METADATA,sha256=y_KAwg86OXGSVZPk7GV_c0nbU7ommQQDqylIsK-dCR0,3306
ptftpd-1.2.dist-info/RECORD,,
ptftpd-1.2.dist-info/WHEEL,sha256=o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34,110
ptftpd-1.2.dist-info/entry_points.txt,sha256=GHbaAos5ilos__c34JO80WSFVMwOj35D7fJURauuNC8,185
ptftpd-1.2.dist-info/metadata.json,sha256=kbA4VaUGZXQkqfC_6Np_V0DiQ9s_9OVn9O-xxVIdUiY,1066
ptftpd-1.2.dist-info/pbr.json,sha256=o1sdx0XJ4AG-lMPzxvsraads41T9eKgkCLdVkfAb2zA,47
ptftpd-1.2.dist-info/top_level.txt,sha256=bT3CSdhcwUBOuDY05owwMyoebq1zulemqLL7roFudIQ,9
ptftpd-1.2.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
ptftplib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
ptftplib/bootpserver.py,sha256=SbJI30_TWjTjF4nb4T1oyY_pZJlXoVqhJKgcQJVzG54,12917
ptftplib/dhcpserver.py,sha256=ZVsjDUT7RwrZLhURo1kKIPLl0jW8DA7pLKR9q7_DSBg,15523
ptftplib/notify.py,sha256=KnFnnxWob3jZknjtdxQR0udm69UE1QucMf62IxE-Xwc,5816
ptftplib/proto.py,sha256=c8q2HBC9myi4UKV0xRl6rIX5v7yBrsdoA4LvGPyu23Q,13939
ptftplib/pxeserver.py,sha256=cUScHY3yQJM_5oAyM7rlWFgkF1olHT1IuLCKHNDMTAU,3956
ptftplib/state.py,sha256=7rtG685HiAS0_WqlBuZUj3MOtBuNkfQGEucxNNxF5jc,8156
ptftplib/tftpclient.py,sha256=fl1aSzpy4Xy7ci-V_Zix2pB6piWBgwMgWSbuLSl6tmk,22941
ptftplib/tftpserver.py,sha256=invFFNPWC61hMo_bBu7WCrg5bqUuojJ9l4064rXvhrY,20465
ptftplib/version.py,sha256=M2sBwgVbtV7NIfPkYB5s-8O4BEHSF2iIHWz8V0BLsPQ,32
ptftpd-1.3.dist-info/METADATA,sha256=BmU8ATgi0MPt3HOfJWc-KiTWRf7MPMorvXF42_bTNEw,3318
ptftpd-1.3.dist-info/RECORD,,
ptftpd-1.3.dist-info/WHEEL,sha256=gduuPyBvFJQSQ0zdyxF7k0zynDXbIbvg5ZBHoXum5uk,110
ptftpd-1.3.dist-info/entry_points.txt,sha256=GHbaAos5ilos__c34JO80WSFVMwOj35D7fJURauuNC8,185
ptftpd-1.3.dist-info/pbr.json,sha256=o1sdx0XJ4AG-lMPzxvsraads41T9eKgkCLdVkfAb2zA,47
ptftpd-1.3.dist-info/top_level.txt,sha256=bT3CSdhcwUBOuDY05owwMyoebq1zulemqLL7roFudIQ,9
ptftpd-1.3.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
ptftplib/__init__.py,sha256=eoZ6GfifbqhMLNzjlqRDVil-yyBkOmVN9ujSgJWNBlY,15
ptftplib/bootpserver.py,sha256=tBS0vKoj9I4P1SmwcBpVoviGBpxDWZlWh3NLtP7LcJ4,12944
ptftplib/dhcpserver.py,sha256=xwjkwvnT4geLKpTxn5Ke5D8p3ylNoYNsB9oBmih9dTk,15543
ptftplib/notify.py,sha256=gugA53Wnouk3I_Hb3QJzcGCTZEwdJLjU9imUdgIC6Rs,5865
ptftplib/proto.py,sha256=ushqeuuzi7rqD6teerjb-te3GnlbFfEUAdMytmz80Fs,15690
ptftplib/pxeserver.py,sha256=tO4YsReIEJfVTU01pMLBNIGjWsjZgRVza1-XO20rXPY,3994
ptftplib/state.py,sha256=He8iGLfOjXsr0FhWv1r-wlpH0D1IWSX3ze49Guhw_14,9192
ptftplib/tftpclient.py,sha256=fHOHObaD17O1RxDZw0IEzQkwGE9iAtSqM57pWbhf5fM,25947
ptftplib/tftpserver.py,sha256=Up_0r2lpvcoLn0jggh_YUnbHU3fbeMRmTbOl6T2UaaA,22347
ptftplib/version.py,sha256=7dEo520ne4sCV8iYzhvYOTrfQ4uNlv1uVCSk3cr6lj4,47
+1
-1
Wheel-Version: 1.0
Generator: bdist_wheel (0.29.0)
Generator: bdist_wheel (0.31.1)
Root-Is-Purelib: true

@@ -4,0 +4,0 @@ Tag: py2-none-any

pTFTPd - A pure-Python TFTP tool suite
======================================
pTFTPd is a collection of tools related to TFTP. It includes a TFTP
server, a TFTP client, and a complete PXE solution based on this TFTP
server and a micro-DHCP or BOOTP server. All these tools are written in
Python and designed to be fast, RFC compliant and easy to use.
Available tools include:
- ``bootpd``: a BOOTP server (RFC951 and RFC1497 compliant)
- ``dhcpd``: a simple, stripped-down DHCP server.
- ``ptftpd``: the TFTP server (RFC1350, 2347, 2348 and 2349 compliant)
- ``pxed``: a one-call PXE server using dhcpd and ptftpd.
- ``ptftp``: a simple TFTP client (RFC1350, 2347, 2348 and 2349 compliant and
capable)
They all support the ``--help`` option to present the usage summary to
the user.
All tools also understand the ``--rfc1350`` option, which forces them in
basic TFTP RFC1350 compliance mode, disabling all TFTP extensions for
increased compatibility would you encouter any problem with your target
system.
Installation
------------
pTFTPd is available on PyPI as the ``ptftpd`` distribution.
.. code::
$ pip install ptftpd
This will install the ``ptftplib`` Python package, as well as the scripts
listed above.
If you use the pTFTPd tool suite outside of a standard distribution
installation, you may need to specify the Python module search path with
``PYTHONPATH`` before executing the binaries:
.. code::
$ export PYTHONPATH=`pwd`
$ bin/ptftp
Connected to localhost:69.
tftp>
TFTP server and client
----------------------
The TFTP server, pTFTPd, fully supports the TFTP specification as
defined in RFC1350. It also supports the TFTP Option Extension protocol
(per RFC2347), the block size option as defined in RFC2348 and the
transfer size option from RFC2349.
For help on how to use pTFTPd, type:
.. code::
$ ptftpd --help
The port used can be changed using the ``-p`` option. The root path is
given as a simple argument. For example, to serve ``/var/lib/tftp`` on
port 6969 through the eth0 network interface:
.. code::
$ ptftpd -p 6969 eth0 /var/lib/tftp
The TFTP client is an interactive client, just launch it and type
``help`` to see the available commands:
.. code::
$ ptftp
tftp> help
...
PXE solution
------------
The PXE system is also very easy to use. It takes three arguments: the
network interface to listen on, the TFTP root path from which to serve
files, and the PXE boot filename. It will automatically start a TFTP
server and a DHCP server to serve hosts on the given interface. See
``--help`` for more details:
.. code::
$ pxed --help
Mechanics for using ``pxed.py`` with the BOOTP server are not yet in
place, but such a solution can easily be constructed manually by
starting the BOOTP server and the TFTP server manually:
.. code::
$ bootpd <interface> <PXE boot file> &
$ ptftpd <interface>
{"classifiers": ["Operating System :: OS Independent", "Programming Language :: Python"], "extensions": {"python.commands": {"wrap_console": {"bootpd": "ptftplib.bootpserver:main", "dhcpd": "ptftplib.dhcpserver:main", "ptftp": "ptftplib.tftpclient:main", "ptftpd": "ptftplib.tftpserver:main", "pxed": "ptftplib.pxeserver:main"}}, "python.details": {"contacts": [{"email": "maxime.petazzoni@bulix.org", "name": "Maxime Petazzoni", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/mpetazzoni/ptftpd"}}, "python.exports": {"console_scripts": {"bootpd": "ptftplib.bootpserver:main", "dhcpd": "ptftplib.dhcpserver:main", "ptftp": "ptftplib.tftpclient:main", "ptftpd": "ptftplib.tftpserver:main", "pxed": "ptftplib.pxeserver:main"}}}, "extras": [], "generator": "bdist_wheel (0.29.0)", "license": "GNU General Public License v2", "metadata_version": "2.0", "name": "ptftpd", "run_requires": [{"requires": ["netifaces"]}], "summary": "pTFTPd, a pure-Python TFTP tool suite that works", "version": "1.2"}