mirror of
https://github.com/eblot/pybootd.git
synced 2024-09-11 22:17:44 +03:00
More cleanup and removing old useless stuff
This commit is contained in:
parent
d1231d55c0
commit
02c32b412c
@ -18,33 +18,8 @@
|
|||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
|
|
||||||
|
__version__ = '1.7.0'
|
||||||
def _get_package_name(default='', version='1.6.0'):
|
|
||||||
try:
|
|
||||||
from pkg_resources import WorkingSet
|
|
||||||
except ImportError:
|
|
||||||
ws = []
|
|
||||||
else:
|
|
||||||
ws = WorkingSet()
|
|
||||||
_path, _ = os.path.split(os.path.dirname(
|
|
||||||
sys.modules['pybootd'].__file__))
|
|
||||||
_path = os.path.normpath(_path)
|
|
||||||
if 'nt' not in os.name:
|
|
||||||
for dist in ws:
|
|
||||||
if os.path.samefile(os.path.normpath(dist.location), _path):
|
|
||||||
return dist.project_name, dist.version
|
|
||||||
else: # tweak for windows
|
|
||||||
_path = os.path.abspath(_path).lower()
|
|
||||||
for dist in ws:
|
|
||||||
if 'pybootd' in dist.location:
|
|
||||||
if _path == os.path.abspath(dist.location).lower():
|
|
||||||
return dist.project_name, dist.version
|
|
||||||
return default, version
|
|
||||||
|
|
||||||
|
|
||||||
PRODUCT_NAME, __version__ = _get_package_name('pybootd')
|
|
||||||
|
|
||||||
|
|
||||||
def pybootd_path(path):
|
def pybootd_path(path):
|
||||||
@ -61,9 +36,9 @@ def pybootd_path(path):
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
raise IOError('pkg_resources module not available')
|
raise IOError('pkg_resources module not available')
|
||||||
try:
|
try:
|
||||||
newpath = resource_filename(Requirement.parse(PRODUCT_NAME), path)
|
newpath = resource_filename(Requirement.parse('pybootd'), path)
|
||||||
if not newpath:
|
if not newpath:
|
||||||
localpath = get_distribution(PRODUCT_NAME).location
|
localpath = get_distribution('pybootd').location
|
||||||
newpath = os.path.join(localpath, path)
|
newpath = os.path.join(localpath, path)
|
||||||
except DistributionNotFound:
|
except DistributionNotFound:
|
||||||
newpath = path
|
newpath = path
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
from os.path import isfile
|
from os.path import isfile
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from sys import exit as sysexit, modules, stderr
|
from sys import exit as sysexit, modules, stderr
|
||||||
from . import pybootd_path, PRODUCT_NAME, __version__ as VERSION
|
from . import pybootd_path, __version__
|
||||||
from .pxed import BootpServer
|
from .pxed import BootpServer
|
||||||
from .tftpd import TftpServer
|
from .tftpd import TftpServer
|
||||||
from .util import logger_factory, EasyConfigParser
|
from .util import logger_factory, EasyConfigParser
|
||||||
@ -94,7 +94,7 @@ def main():
|
|||||||
logfile=cfgparser.get('logger', 'file'),
|
logfile=cfgparser.get('logger', 'file'),
|
||||||
level=cfgparser.get('logger', 'level',
|
level=cfgparser.get('logger', 'level',
|
||||||
'info'))
|
'info'))
|
||||||
logger.info('-'.join((PRODUCT_NAME, VERSION)))
|
logger.info('-'.join(('pybootd', __version__)))
|
||||||
|
|
||||||
daemon = None
|
daemon = None
|
||||||
if not args.tftp:
|
if not args.tftp:
|
||||||
|
@ -5,7 +5,8 @@ level = info
|
|||||||
[bootp]
|
[bootp]
|
||||||
address = 0.0.0.0
|
address = 0.0.0.0
|
||||||
; pool_start should be in a valid subnet
|
; pool_start should be in a valid subnet
|
||||||
pool_start = 192.168.25.100
|
; pool_start = 192.168.25.100
|
||||||
|
pool_start = 10.113.116.245
|
||||||
pool_count = 5
|
pool_count = 5
|
||||||
domain = localdomain
|
domain = localdomain
|
||||||
server_name = debug
|
server_name = debug
|
||||||
|
100
pybootd/pxed.py
100
pybootd/pxed.py
@ -17,15 +17,15 @@
|
|||||||
# License along with this library; if not, write to the Free Software
|
# License along with this library; if not, write to the Free Software
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
import re
|
|
||||||
import select
|
|
||||||
import socket
|
|
||||||
import string
|
|
||||||
import struct
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
from binascii import hexlify
|
from binascii import hexlify
|
||||||
from . import PRODUCT_NAME
|
from re import compile as recompile
|
||||||
|
from select import select
|
||||||
|
from socket import (inet_aton, inet_ntoa, socket,
|
||||||
|
AF_INET, SOCK_DGRAM, IPPROTO_UDP, SOL_SOCKET,
|
||||||
|
SO_BROADCAST, SO_REUSEADDR)
|
||||||
|
from struct import calcsize as scalc, pack as spack, unpack as sunpack
|
||||||
|
from time import sleep
|
||||||
|
from traceback import format_exc
|
||||||
from .util import hexline, to_bool, iptoint, inttoip, get_iface_config
|
from .util import hexline, to_bool, iptoint, inttoip, get_iface_config
|
||||||
|
|
||||||
BOOTP_PORT_REQUEST = 67
|
BOOTP_PORT_REQUEST = 67
|
||||||
@ -35,9 +35,9 @@ BOOTREQUEST = 1
|
|||||||
BOOTREPLY = 2
|
BOOTREPLY = 2
|
||||||
|
|
||||||
BOOTPFORMAT = '!4bIHH4s4s4s4s16s64s128s64s'
|
BOOTPFORMAT = '!4bIHH4s4s4s4s16s64s128s64s'
|
||||||
BOOTPFORMATSIZE = struct.calcsize(BOOTPFORMAT)
|
BOOTPFORMATSIZE = scalc(BOOTPFORMAT)
|
||||||
DHCPFORMAT = '!4bIHH4s4s4s4s16s64s128s4s'
|
DHCPFORMAT = '!4bIHH4s4s4s4s16s64s128s4s'
|
||||||
DHCPFORMATSIZE = struct.calcsize(DHCPFORMAT)
|
DHCPFORMATSIZE = scalc(DHCPFORMAT)
|
||||||
|
|
||||||
(BOOTP_OP, BOOTP_HTYPE, BOOTP_HLEN, BOOTP_HOPS, BOOTP_XID, BOOTP_SECS,
|
(BOOTP_OP, BOOTP_HTYPE, BOOTP_HLEN, BOOTP_HOPS, BOOTP_XID, BOOTP_SECS,
|
||||||
BOOTP_FLAGS, BOOTP_CIADDR, BOOTP_YIADDR, BOOTP_SIADDR, BOOTP_GIADDR,
|
BOOTP_FLAGS, BOOTP_CIADDR, BOOTP_YIADDR, BOOTP_SIADDR, BOOTP_GIADDR,
|
||||||
@ -177,9 +177,7 @@ class BootpServer:
|
|||||||
self.ippool = {} # key MAC address string, value assigned IP string
|
self.ippool = {} # key MAC address string, value assigned IP string
|
||||||
self.filepool = {} # key IP string, value pathname
|
self.filepool = {} # key IP string, value pathname
|
||||||
self.states = {} # key MAC address string, value client state
|
self.states = {} # key MAC address string, value client state
|
||||||
name_ = PRODUCT_NAME.split('-')
|
self.bootp_section = 'bootp'
|
||||||
name_[0] = 'bootp'
|
|
||||||
self.bootp_section = '_'.join(name_)
|
|
||||||
self.pool_start = self.config.get(self.bootp_section, 'pool_start')
|
self.pool_start = self.config.get(self.bootp_section, 'pool_start')
|
||||||
if not self.pool_start:
|
if not self.pool_start:
|
||||||
raise BootpError('Missing pool_start definition')
|
raise BootpError('Missing pool_start definition')
|
||||||
@ -236,7 +234,7 @@ class BootpServer:
|
|||||||
msg = ','.join([notice, uuid_str, mac_str, ip])
|
msg = ','.join([notice, uuid_str, mac_str, ip])
|
||||||
else:
|
else:
|
||||||
msg = ','.join([notice, mac_str, ip])
|
msg = ','.join([notice, mac_str, ip])
|
||||||
notify_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
notify_sock = socket(AF_INET, SOCK_DGRAM)
|
||||||
for n in self.notify:
|
for n in self.notify:
|
||||||
self.log.info('Notifying %s with %s' % (n, msg))
|
self.log.info('Notifying %s with %s' % (n, msg))
|
||||||
notify_sock.sendto(msg, n)
|
notify_sock.sendto(msg, n)
|
||||||
@ -249,10 +247,9 @@ class BootpServer:
|
|||||||
host = self.config.get(self.bootp_section, 'address', '0.0.0.0')
|
host = self.config.get(self.bootp_section, 'address', '0.0.0.0')
|
||||||
port = self.config.get(self.bootp_section, 'port',
|
port = self.config.get(self.bootp_section, 'port',
|
||||||
str(BOOTP_PORT_REQUEST))
|
str(BOOTP_PORT_REQUEST))
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
|
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
|
||||||
socket.IPPROTO_UDP)
|
sock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
|
||||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
|
||||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
||||||
self.sock.append(sock)
|
self.sock.append(sock)
|
||||||
self.log.info('Listening to %s:%s' % (host, port))
|
self.log.info('Listening to %s:%s' % (host, port))
|
||||||
sock.bind((host, int(port)))
|
sock.bind((host, int(port)))
|
||||||
@ -260,14 +257,13 @@ class BootpServer:
|
|||||||
def forever(self):
|
def forever(self):
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
r, w, e = select.select(self.sock, [], self.sock)
|
r, w, e = select(self.sock, [], self.sock)
|
||||||
for sock in r:
|
for sock in r:
|
||||||
data, addr = sock.recvfrom(556)
|
data, addr = sock.recvfrom(556)
|
||||||
self.handle(sock, addr, data)
|
self.handle(sock, addr, data)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
import traceback
|
self.log.critical('%s\n%s' % (exc, format_exc()))
|
||||||
self.log.critical('%s\n%s' % (exc, traceback.format_exc()))
|
sleep(1)
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
def parse_options(self, tail):
|
def parse_options(self, tail):
|
||||||
self.log.debug('Parsing DHCP options')
|
self.log.debug('Parsing DHCP options')
|
||||||
@ -280,7 +276,7 @@ class BootpServer:
|
|||||||
if tag == 0xff:
|
if tag == 0xff:
|
||||||
return dhcp_tags
|
return dhcp_tags
|
||||||
length = ord(tail[1])
|
length = ord(tail[1])
|
||||||
(value, ) = struct.unpack('!%ss' % length, tail[2:2+length])
|
(value, ) = sunpack('!%ss' % length, tail[2:2+length])
|
||||||
tail = tail[2+length:]
|
tail = tail[2+length:]
|
||||||
try:
|
try:
|
||||||
option = DHCP_OPTIONS[tag]
|
option = DHCP_OPTIONS[tag]
|
||||||
@ -296,25 +292,25 @@ class BootpServer:
|
|||||||
try:
|
try:
|
||||||
buf = ''
|
buf = ''
|
||||||
uuid = options[97]
|
uuid = options[97]
|
||||||
buf += struct.pack('!BB%ds' % len(uuid),
|
buf += spack('!BB%ds' % len(uuid),
|
||||||
97, len(uuid), uuid)
|
97, len(uuid), uuid)
|
||||||
clientclass = options[60]
|
clientclass = options[60]
|
||||||
clientclass = clientclass[:clientclass.find(':')]
|
clientclass = clientclass[:clientclass.find(':')]
|
||||||
buf += struct.pack('!BB%ds' % len(clientclass),
|
buf += spack('!BB%ds' % len(clientclass),
|
||||||
60, len(clientclass), clientclass)
|
60, len(clientclass), clientclass)
|
||||||
vendor = ''
|
vendor = ''
|
||||||
vendor += struct.pack('!BBB', PXE_DISCOVERY_CONTROL, 1, 0x0A)
|
vendor += spack('!BBB', PXE_DISCOVERY_CONTROL, 1, 0x0A)
|
||||||
vendor += struct.pack('!BBHB4s', PXE_BOOT_SERVERS, 2+1+4,
|
vendor += spack('!BBHB4s', PXE_BOOT_SERVERS, 2+1+4,
|
||||||
0, 1, server)
|
0, 1, server)
|
||||||
srvstr = 'Python'
|
srvstr = 'Python'
|
||||||
vendor += struct.pack('!BBHB%ds' % len(srvstr), PXE_BOOT_MENU,
|
vendor += spack('!BBHB%ds' % len(srvstr), PXE_BOOT_MENU,
|
||||||
2+1+len(srvstr), 0, len(srvstr), srvstr)
|
2+1+len(srvstr), 0, len(srvstr), srvstr)
|
||||||
prompt = 'Stupid PXE'
|
prompt = 'Stupid PXE'
|
||||||
vendor += struct.pack('!BBB%ds' % len(prompt), PXE_MENU_PROMPT,
|
vendor += spack('!BBB%ds' % len(prompt), PXE_MENU_PROMPT,
|
||||||
1+len(prompt), len(prompt), prompt)
|
1+len(prompt), len(prompt), prompt)
|
||||||
buf += struct.pack('!BB%ds' % len(vendor), 43,
|
buf += spack('!BB%ds' % len(vendor), 43,
|
||||||
len(vendor), vendor)
|
len(vendor), vendor)
|
||||||
buf += struct.pack('!BBB', 255, 0, 0)
|
buf += spack('!BBB', 255, 0, 0)
|
||||||
return buf
|
return buf
|
||||||
except KeyError as exc:
|
except KeyError as exc:
|
||||||
self.log.error('Missing options, cancelling: %s' % exc)
|
self.log.error('Missing options, cancelling: %s' % exc)
|
||||||
@ -324,7 +320,7 @@ class BootpServer:
|
|||||||
buf = ''
|
buf = ''
|
||||||
if not clientname:
|
if not clientname:
|
||||||
return buf
|
return buf
|
||||||
buf += struct.pack('!BB%ds' % len(clientname),
|
buf += spack('!BB%ds' % len(clientname),
|
||||||
12, len(clientname), clientname)
|
12, len(clientname), clientname)
|
||||||
return buf
|
return buf
|
||||||
|
|
||||||
@ -333,7 +329,7 @@ class BootpServer:
|
|||||||
if len(data) < DHCPFORMATSIZE:
|
if len(data) < DHCPFORMATSIZE:
|
||||||
self.log.error('Cannot be a DHCP or BOOTP request - too small!')
|
self.log.error('Cannot be a DHCP or BOOTP request - too small!')
|
||||||
tail = data[DHCPFORMATSIZE:]
|
tail = data[DHCPFORMATSIZE:]
|
||||||
buf = list(struct.unpack(DHCPFORMAT, data[:DHCPFORMATSIZE]))
|
buf = list(sunpack(DHCPFORMAT, data[:DHCPFORMATSIZE]))
|
||||||
if buf[BOOTP_OP] != BOOTREQUEST:
|
if buf[BOOTP_OP] != BOOTREQUEST:
|
||||||
self.log.warn('Not a BOOTREQUEST')
|
self.log.warn('Not a BOOTREQUEST')
|
||||||
return
|
return
|
||||||
@ -477,7 +473,7 @@ class BootpServer:
|
|||||||
# construct reply
|
# construct reply
|
||||||
buf[BOOTP_HOPS] = 0
|
buf[BOOTP_HOPS] = 0
|
||||||
buf[BOOTP_OP] = BOOTREPLY
|
buf[BOOTP_OP] = BOOTREPLY
|
||||||
self.log.info('Client IP: %s' % socket.inet_ntoa(buf[7]))
|
self.log.info('Client IP: %s' % inet_ntoa(buf[7]))
|
||||||
if buf[BOOTP_CIADDR] == '\x00\x00\x00\x00':
|
if buf[BOOTP_CIADDR] == '\x00\x00\x00\x00':
|
||||||
self.log.debug('Client needs its address')
|
self.log.debug('Client needs its address')
|
||||||
ipaddr = iptoint(self.pool_start)
|
ipaddr = iptoint(self.pool_start)
|
||||||
@ -501,20 +497,20 @@ class BootpServer:
|
|||||||
self.bootp_section, 'netmask', self.netconfig['mask']))
|
self.bootp_section, 'netmask', self.netconfig['mask']))
|
||||||
reply_broadcast = iptoint(ip) & mask
|
reply_broadcast = iptoint(ip) & mask
|
||||||
reply_broadcast |= (~mask) & ((1 << 32)-1)
|
reply_broadcast |= (~mask) & ((1 << 32)-1)
|
||||||
buf[BOOTP_YIADDR] = socket.inet_aton(ip)
|
buf[BOOTP_YIADDR] = inet_aton(ip)
|
||||||
buf[BOOTP_SECS] = 0
|
buf[BOOTP_SECS] = 0
|
||||||
buf[BOOTP_FLAGS] = BOOTP_FLAGS_BROADCAST
|
buf[BOOTP_FLAGS] = BOOTP_FLAGS_BROADCAST
|
||||||
|
|
||||||
relay = buf[BOOTP_GIADDR]
|
relay = buf[BOOTP_GIADDR]
|
||||||
if relay != b'\x00\x00\x00\x00':
|
if relay != b'\x00\x00\x00\x00':
|
||||||
addr = (socket.inet_ntoa(relay), addr[1])
|
addr = (inet_ntoa(relay), addr[1])
|
||||||
else:
|
else:
|
||||||
addr = (inttoip(reply_broadcast), addr[1])
|
addr = (inttoip(reply_broadcast), addr[1])
|
||||||
self.log.info('Reply to: %s:%s' % addr)
|
self.log.info('Reply to: %s:%s' % addr)
|
||||||
else:
|
else:
|
||||||
buf[BOOTP_YIADDR] = buf[BOOTP_CIADDR]
|
buf[BOOTP_YIADDR] = buf[BOOTP_CIADDR]
|
||||||
ip = socket.inet_ntoa(buf[BOOTP_YIADDR])
|
ip = inet_ntoa(buf[BOOTP_YIADDR])
|
||||||
buf[BOOTP_SIADDR] = socket.inet_aton(server_addr)
|
buf[BOOTP_SIADDR] = inet_aton(server_addr)
|
||||||
# sname
|
# sname
|
||||||
buf[BOOTP_SNAME] = \
|
buf[BOOTP_SNAME] = \
|
||||||
'.'.join([self.config.get(self.bootp_section,
|
'.'.join([self.config.get(self.bootp_section,
|
||||||
@ -567,38 +563,38 @@ class BootpServer:
|
|||||||
else:
|
else:
|
||||||
self.log.debug('No filename defined for IP %s' % ip)
|
self.log.debug('No filename defined for IP %s' % ip)
|
||||||
|
|
||||||
pkt = struct.pack(DHCPFORMAT, *buf)
|
pkt = spack(DHCPFORMAT, *buf)
|
||||||
pkt += struct.pack('!BBB', DHCP_MSG, 1, dhcp_reply)
|
pkt += spack('!BBB', DHCP_MSG, 1, dhcp_reply)
|
||||||
server = socket.inet_aton(server_addr)
|
server = inet_aton(server_addr)
|
||||||
pkt += struct.pack('!BB4s', DHCP_SERVER, 4, server)
|
pkt += spack('!BB4s', DHCP_SERVER, 4, server)
|
||||||
|
|
||||||
mask = socket.inet_aton(self.config.get(
|
mask = inet_aton(self.config.get(
|
||||||
self.bootp_section, 'netmask', self.netconfig['mask']))
|
self.bootp_section, 'netmask', self.netconfig['mask']))
|
||||||
|
|
||||||
pkt += struct.pack('!BB4s', DHCP_IP_MASK, 4, mask)
|
pkt += spack('!BB4s', DHCP_IP_MASK, 4, mask)
|
||||||
|
|
||||||
gateway_addr = self.config.get(self.bootp_section, 'gateway', '')
|
gateway_addr = self.config.get(self.bootp_section, 'gateway', '')
|
||||||
if gateway_addr:
|
if gateway_addr:
|
||||||
gateway = socket.inet_aton(gateway_addr)
|
gateway = inet_aton(gateway_addr)
|
||||||
else:
|
else:
|
||||||
gateway = server
|
gateway = server
|
||||||
pkt += struct.pack('!BB4s', DHCP_IP_GATEWAY, 4, gateway)
|
pkt += spack('!BB4s', DHCP_IP_GATEWAY, 4, gateway)
|
||||||
|
|
||||||
dns = self.config.get(self.bootp_section,
|
dns = self.config.get(self.bootp_section,
|
||||||
'dns', None)
|
'dns', None)
|
||||||
if dns:
|
if dns:
|
||||||
if dns.lower() == 'auto':
|
if dns.lower() == 'auto':
|
||||||
dns_list = self.get_dns_servers() or [socket.inet_ntoa(server)]
|
dns_list = self.get_dns_servers() or [inet_ntoa(server)]
|
||||||
else:
|
else:
|
||||||
dns_list = dns.split(';')
|
dns_list = dns.split(';')
|
||||||
for dns_str in dns_list:
|
for dns_str in dns_list:
|
||||||
dns_ip = socket.inet_aton(dns_str)
|
dns_ip = inet_aton(dns_str)
|
||||||
pkt += struct.pack('!BB4s', DHCP_IP_DNS, 4, dns_ip)
|
pkt += spack('!BB4s', DHCP_IP_DNS, 4, dns_ip)
|
||||||
pkt += struct.pack('!BBI', DHCP_LEASE_TIME, 4,
|
pkt += spack('!BBI', DHCP_LEASE_TIME, 4,
|
||||||
int(self.config.get(self.bootp_section,
|
int(self.config.get(self.bootp_section,
|
||||||
'lease_time',
|
'lease_time',
|
||||||
str(24*3600))))
|
str(24*3600))))
|
||||||
pkt += struct.pack('!BB', DHCP_END, 0)
|
pkt += spack('!BB', DHCP_END, 0)
|
||||||
|
|
||||||
# do not attempt to produce a PXE-augmented response for
|
# do not attempt to produce a PXE-augmented response for
|
||||||
# regular DHCP requests
|
# regular DHCP requests
|
||||||
@ -623,7 +619,7 @@ class BootpServer:
|
|||||||
self.states[mac_str] = newstate
|
self.states[mac_str] = newstate
|
||||||
|
|
||||||
def get_dns_servers(self):
|
def get_dns_servers(self):
|
||||||
nscre = re.compile(r'nameserver\s+(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})\s')
|
nscre = recompile(r'nameserver\s+(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})\s')
|
||||||
result = []
|
result = []
|
||||||
try:
|
try:
|
||||||
with open('/etc/resolv.conf', 'r') as resolv:
|
with open('/etc/resolv.conf', 'r') as resolv:
|
||||||
|
@ -18,22 +18,21 @@
|
|||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import select
|
|
||||||
import socket
|
|
||||||
import string
|
|
||||||
import struct
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
from configparser import NoSectionError
|
from configparser import NoSectionError
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
from re import compile as recompile, sub as resub
|
||||||
|
from select import select
|
||||||
|
from socket import socket, AF_INET, SOCK_DGRAM
|
||||||
|
from struct import pack as spack, unpack as sunpack
|
||||||
|
from sys import argv, exc_info
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
from time import time as now
|
||||||
|
from traceback import print_exc
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
from . import pybootd_path
|
from . import pybootd_path
|
||||||
from .util import hexline
|
from .util import hexline
|
||||||
|
|
||||||
__all__ = ['TftpServer']
|
|
||||||
|
|
||||||
TFTP_PORT = 69
|
TFTP_PORT = 69
|
||||||
|
|
||||||
@ -86,7 +85,7 @@ class TftpConnection(object):
|
|||||||
timeout = self.timeout
|
timeout = self.timeout
|
||||||
retry = self.server.retry
|
retry = self.server.retry
|
||||||
while retry:
|
while retry:
|
||||||
r, w, e = select.select([fno], [], [fno], timeout)
|
r, w, e = select([fno], [], [fno], timeout)
|
||||||
if not r:
|
if not r:
|
||||||
# We timed out -- retransmit
|
# We timed out -- retransmit
|
||||||
retry = retry - 1
|
retry = retry - 1
|
||||||
@ -121,13 +120,13 @@ class TftpConnection(object):
|
|||||||
path = self.server.bootpd.get_filename(client_ip)
|
path = self.server.bootpd.get_filename(client_ip)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
def parse(self, data, unpack=struct.unpack):
|
def parse(self, data, unpack=sunpack):
|
||||||
self.log.debug('parse')
|
self.log.debug('parse')
|
||||||
buf = buffer(data)
|
buf = buffer(data)
|
||||||
pkt = {}
|
pkt = {}
|
||||||
opcode = pkt['opcode'] = unpack('!h', buf[:2])[0]
|
opcode = pkt['opcode'] = unpack('!h', buf[:2])[0]
|
||||||
if (opcode == self.RRQ) or (opcode == self.WRQ):
|
if (opcode == self.RRQ) or (opcode == self.WRQ):
|
||||||
resource, mode, options = string.split(data[2:], '\000', 2)
|
resource, mode, options = data[2:].split('\000', 2)
|
||||||
resource = self.server.fcre.sub(self._filter_file, resource)
|
resource = self.server.fcre.sub(self._filter_file, resource)
|
||||||
if self.server.root and self.is_url(self.server.root):
|
if self.server.root and self.is_url(self.server.root):
|
||||||
resource = '%s/%s' % (self.server.root, resource)
|
resource = '%s/%s' % (self.server.root, resource)
|
||||||
@ -138,7 +137,7 @@ class TftpConnection(object):
|
|||||||
if not self.server.genfilecre.match(resource):
|
if not self.server.genfilecre.match(resource):
|
||||||
if resource.startswith('^%s' % os.sep):
|
if resource.startswith('^%s' % os.sep):
|
||||||
resource = os.path.join(
|
resource = os.path.join(
|
||||||
os.path.dirname(sys.argv[0]),
|
os.path.dirname(argv[0]),
|
||||||
resource.lstrip('^%s' % os.sep))
|
resource.lstrip('^%s' % os.sep))
|
||||||
elif self.server.root:
|
elif self.server.root:
|
||||||
if self.server.root.startswith(os.sep):
|
if self.server.root.startswith(os.sep):
|
||||||
@ -147,7 +146,7 @@ class TftpConnection(object):
|
|||||||
resource)
|
resource)
|
||||||
else:
|
else:
|
||||||
# Relative root directory, from the daemon path
|
# Relative root directory, from the daemon path
|
||||||
daemonpath = os.path.dirname(sys.argv[0])
|
daemonpath = os.path.dirname(argv[0])
|
||||||
if not daemonpath.startswith(os.sep):
|
if not daemonpath.startswith(os.sep):
|
||||||
daemonpath = os.path.normpath(
|
daemonpath = os.path.normpath(
|
||||||
os.path.join(os.getcwd(), daemonpath))
|
os.path.join(os.getcwd(), daemonpath))
|
||||||
@ -220,8 +219,7 @@ class TftpConnection(object):
|
|||||||
except TftpError as exc:
|
except TftpError as exc:
|
||||||
self.send_error(exc[0], exc[1])
|
self.send_error(exc[0], exc[1])
|
||||||
except:
|
except:
|
||||||
import traceback
|
self.log.error(format_exc())
|
||||||
self.log.error(traceback.format_exc())
|
|
||||||
self.log.debug('Ending connection %s:%s' % addr)
|
self.log.debug('Ending connection %s:%s' % addr)
|
||||||
|
|
||||||
def recv_ack(self, pkt):
|
def recv_ack(self, pkt):
|
||||||
@ -245,19 +243,19 @@ class TftpConnection(object):
|
|||||||
self.handle_err(pkt)
|
self.handle_err(pkt)
|
||||||
self.retransmit()
|
self.retransmit()
|
||||||
|
|
||||||
def send_data(self, data, pack=struct.pack):
|
def send_data(self, data, pack=spack):
|
||||||
self.log.debug('send_data')
|
self.log.debug('send_data')
|
||||||
if not self.time:
|
if not self.time:
|
||||||
self.time = time.time()
|
self.time = now()
|
||||||
blocksize = self.blocksize
|
blocksize = self.blocksize
|
||||||
block = self.blockNumber = self.blockNumber + 1
|
block = self.blockNumber = self.blockNumber + 1
|
||||||
lendata = len(data)
|
lendata = len(data)
|
||||||
format = '!hh%ds' % lendata
|
fmt = '!hh%ds' % lendata
|
||||||
pkt = pack(format, self.DATA, block, data)
|
pkt = pack(fmt, self.DATA, block, data)
|
||||||
self.send(pkt)
|
self.send(pkt)
|
||||||
self.active = (len(data) == blocksize)
|
self.active = (len(data) == blocksize)
|
||||||
if not self.active and self.time:
|
if not self.active and self.time:
|
||||||
total = time.time()-self.time
|
total = now()-self.time
|
||||||
self.time = 0
|
self.time = 0
|
||||||
try:
|
try:
|
||||||
name = self.file.name
|
name = self.file.name
|
||||||
@ -271,26 +269,24 @@ class TftpConnection(object):
|
|||||||
# StringIO does not have a 'name' attribute
|
# StringIO does not have a 'name' attribute
|
||||||
pass
|
pass
|
||||||
except Exception:
|
except Exception:
|
||||||
import traceback
|
print_exc()
|
||||||
traceback.print_exc()
|
|
||||||
pass
|
|
||||||
|
|
||||||
def send_ack(self, pack=struct.pack):
|
def send_ack(self, pack=spack):
|
||||||
self.log.debug('send_ack')
|
self.log.debug('send_ack')
|
||||||
block = self.blockNumber
|
block = self.blockNumber
|
||||||
self.blockNumber = self.blockNumber + 1
|
self.blockNumber = self.blockNumber + 1
|
||||||
format = '!hh'
|
fmt = '!hh'
|
||||||
pkt = pack(format, self.ACK, block)
|
pkt = pack(fmt, self.ACK, block)
|
||||||
self.send(pkt)
|
self.send(pkt)
|
||||||
|
|
||||||
def send_error(self, errnum, errtext, pack=struct.pack):
|
def send_error(self, errnum, errtext, pack=spack):
|
||||||
self.log.debug('send_error')
|
self.log.debug('send_error')
|
||||||
errtext = errtext + '\000'
|
errtext = errtext + '\000'
|
||||||
format = '!hh%ds' % len(errtext)
|
fmt = '!hh%ds' % len(errtext)
|
||||||
outdata = pack(format, self.ERR, errnum, errtext)
|
outdata = pack(fmt, self.ERR, errnum, errtext)
|
||||||
self.sock.sendto(outdata, self.client_addr)
|
self.sock.sendto(outdata, self.client_addr)
|
||||||
|
|
||||||
def send_oack(self, options, pack=struct.pack):
|
def send_oack(self, options, pack=spack):
|
||||||
self.log.debug('send_oack')
|
self.log.debug('send_oack')
|
||||||
pkt = pack('!h', self.OACK)
|
pkt = pack('!h', self.OACK)
|
||||||
for k, v in options:
|
for k, v in options:
|
||||||
@ -341,7 +337,7 @@ class TftpConnection(object):
|
|||||||
except Exception:
|
except Exception:
|
||||||
self.send_error(1, 'Cannot open resource')
|
self.send_error(1, 'Cannot open resource')
|
||||||
self.log.warn('Cannot open file for reading %s: %s' %
|
self.log.warn('Cannot open file for reading %s: %s' %
|
||||||
sys.exc_info()[:2])
|
exc_info()[:2])
|
||||||
return
|
return
|
||||||
if 'tsize' not in pkt:
|
if 'tsize' not in pkt:
|
||||||
self.send_data(self.file.read(self.blocksize))
|
self.send_data(self.file.read(self.blocksize))
|
||||||
@ -359,7 +355,7 @@ class TftpConnection(object):
|
|||||||
except:
|
except:
|
||||||
self.send_error(1, 'Cannot open file')
|
self.send_error(1, 'Cannot open file')
|
||||||
self.log.error('Cannot open file for writing %s: %s' %
|
self.log.error('Cannot open file for writing %s: %s' %
|
||||||
sys.exc_info()[:2])
|
exc_info()[:2])
|
||||||
return
|
return
|
||||||
self.send_ack()
|
self.send_ack()
|
||||||
|
|
||||||
@ -398,7 +394,7 @@ class TftpServer:
|
|||||||
self.retry = int(self.config.get('tftp', 'blocksize', '5'))
|
self.retry = int(self.config.get('tftp', 'blocksize', '5'))
|
||||||
self.root = self.config.get('tftp', 'root', os.getcwd())
|
self.root = self.config.get('tftp', 'root', os.getcwd())
|
||||||
self.fcre, self.filepatterns = self.get_file_filters()
|
self.fcre, self.filepatterns = self.get_file_filters()
|
||||||
self.genfilecre = re.compile(r'\[(?P<name>[\w\.\-]+)\]')
|
self.genfilecre = recompile(r'\[(?P<name>[\w\.\-]+)\]')
|
||||||
|
|
||||||
def bind(self):
|
def bind(self):
|
||||||
netconfig = self.bootpd and self.bootpd.get_netconfig()
|
netconfig = self.bootpd and self.bootpd.get_netconfig()
|
||||||
@ -407,7 +403,7 @@ class TftpServer:
|
|||||||
if not host:
|
if not host:
|
||||||
raise TftpError('TFTP address no defined')
|
raise TftpError('TFTP address no defined')
|
||||||
port = int(self.config.get('tftp', 'port', str(TFTP_PORT)))
|
port = int(self.config.get('tftp', 'port', str(TFTP_PORT)))
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
sock = socket(AF_INET, SOCK_DGRAM)
|
||||||
self.sock.append(sock)
|
self.sock.append(sock)
|
||||||
sock.bind((host, port))
|
sock.bind((host, port))
|
||||||
|
|
||||||
@ -417,7 +413,7 @@ class TftpServer:
|
|||||||
if not self.bootpd.is_alive():
|
if not self.bootpd.is_alive():
|
||||||
self.log.info('Bootp daemon is dead, exiting')
|
self.log.info('Bootp daemon is dead, exiting')
|
||||||
break
|
break
|
||||||
r, w, e = select.select(self.sock, [], self.sock)
|
r, w, e = select(self.sock, [], self.sock)
|
||||||
for sock in r:
|
for sock in r:
|
||||||
data, addr = sock.recvfrom(516)
|
data, addr = sock.recvfrom(516)
|
||||||
tc = TftpConnection(self)
|
tc = TftpConnection(self)
|
||||||
@ -433,7 +429,7 @@ class TftpServer:
|
|||||||
if not filename:
|
if not filename:
|
||||||
continue
|
continue
|
||||||
filepattern = self.filepatterns[group]
|
filepattern = self.filepatterns[group]
|
||||||
return re.sub(r'\{(\w+)\}', connexion._dynreplace, filepattern)
|
return resub(r'\{(\w+)\}', connexion._dynreplace, filepattern)
|
||||||
raise TftpError('Internal error, file matching pattern issue')
|
raise TftpError('Internal error, file matching pattern issue')
|
||||||
|
|
||||||
def get_file_filters(self):
|
def get_file_filters(self):
|
||||||
@ -451,4 +447,4 @@ class TftpServer:
|
|||||||
xre = r'^(?:\./)?(?:%s)$' % r'|'.join(patterns)
|
xre = r'^(?:\./)?(?:%s)$' % r'|'.join(patterns)
|
||||||
except NoSectionError:
|
except NoSectionError:
|
||||||
xre = r'^$'
|
xre = r'^$'
|
||||||
return (re.compile(xre), replacements)
|
return (recompile(xre), replacements)
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
# License along with this library; if not, write to the Free Software
|
# License along with this library; if not, write to the Free Software
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
from array import array
|
|
||||||
from configparser import SafeConfigParser, InterpolationSyntaxError
|
from configparser import SafeConfigParser, InterpolationSyntaxError
|
||||||
from logging import (DEBUG, INFO, ERROR, CRITICAL, WARNING,
|
from logging import (DEBUG, INFO, ERROR, CRITICAL, WARNING,
|
||||||
Formatter, FileHandler, StreamHandler, getLogger)
|
Formatter, FileHandler, StreamHandler, getLogger)
|
||||||
@ -29,7 +28,6 @@ from subprocess import run
|
|||||||
from struct import pack as spack, unpack as sunpack
|
from struct import pack as spack, unpack as sunpack
|
||||||
from sys import stderr
|
from sys import stderr
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import netifaces
|
import netifaces
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@ -112,7 +110,7 @@ def hexline(data, sep=' '):
|
|||||||
of the buffer data
|
of the buffer data
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if isinstance(data, (bytes, array)):
|
if isinstance(data, bytes):
|
||||||
src = bytearray(data)
|
src = bytearray(data)
|
||||||
elif isinstance(data, bytearray):
|
elif isinstance(data, bytearray):
|
||||||
src = data
|
src = data
|
||||||
|
Loading…
Reference in New Issue
Block a user