diff --git a/pybootd/__init__.py b/pybootd/__init__.py index 726491b..1e81d7e 100644 --- a/pybootd/__init__.py +++ b/pybootd/__init__.py @@ -18,33 +18,8 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os -import sys - -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') +__version__ = '1.7.0' def pybootd_path(path): @@ -61,9 +36,9 @@ def pybootd_path(path): except ImportError: raise IOError('pkg_resources module not available') try: - newpath = resource_filename(Requirement.parse(PRODUCT_NAME), path) + newpath = resource_filename(Requirement.parse('pybootd'), path) if not newpath: - localpath = get_distribution(PRODUCT_NAME).location + localpath = get_distribution('pybootd').location newpath = os.path.join(localpath, path) except DistributionNotFound: newpath = path diff --git a/pybootd/daemons.py b/pybootd/daemons.py index af0e60e..058c042 100755 --- a/pybootd/daemons.py +++ b/pybootd/daemons.py @@ -24,7 +24,7 @@ from os.path import isfile from threading import Thread 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 .tftpd import TftpServer from .util import logger_factory, EasyConfigParser @@ -94,7 +94,7 @@ def main(): logfile=cfgparser.get('logger', 'file'), level=cfgparser.get('logger', 'level', 'info')) - logger.info('-'.join((PRODUCT_NAME, VERSION))) + logger.info('-'.join(('pybootd', __version__))) daemon = None if not args.tftp: diff --git a/pybootd/etc/pybootd.ini b/pybootd/etc/pybootd.ini index af8331a..912f1e5 100644 --- a/pybootd/etc/pybootd.ini +++ b/pybootd/etc/pybootd.ini @@ -5,7 +5,8 @@ level = info [bootp] address = 0.0.0.0 ; 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 domain = localdomain server_name = debug diff --git a/pybootd/pxed.py b/pybootd/pxed.py index 877bc48..76ed398 100644 --- a/pybootd/pxed.py +++ b/pybootd/pxed.py @@ -17,15 +17,15 @@ # License along with this library; if not, write to the Free Software # 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 . 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 BOOTP_PORT_REQUEST = 67 @@ -35,9 +35,9 @@ BOOTREQUEST = 1 BOOTREPLY = 2 BOOTPFORMAT = '!4bIHH4s4s4s4s16s64s128s64s' -BOOTPFORMATSIZE = struct.calcsize(BOOTPFORMAT) +BOOTPFORMATSIZE = scalc(BOOTPFORMAT) DHCPFORMAT = '!4bIHH4s4s4s4s16s64s128s4s' -DHCPFORMATSIZE = struct.calcsize(DHCPFORMAT) +DHCPFORMATSIZE = scalc(DHCPFORMAT) (BOOTP_OP, BOOTP_HTYPE, BOOTP_HLEN, BOOTP_HOPS, BOOTP_XID, BOOTP_SECS, 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.filepool = {} # key IP string, value pathname self.states = {} # key MAC address string, value client state - name_ = PRODUCT_NAME.split('-') - name_[0] = 'bootp' - self.bootp_section = '_'.join(name_) + self.bootp_section = 'bootp' self.pool_start = self.config.get(self.bootp_section, 'pool_start') if not self.pool_start: raise BootpError('Missing pool_start definition') @@ -236,7 +234,7 @@ class BootpServer: msg = ','.join([notice, uuid_str, mac_str, ip]) else: 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: self.log.info('Notifying %s with %s' % (n, msg)) notify_sock.sendto(msg, n) @@ -249,10 +247,9 @@ class BootpServer: host = self.config.get(self.bootp_section, 'address', '0.0.0.0') port = self.config.get(self.bootp_section, 'port', str(BOOTP_PORT_REQUEST)) - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, - socket.IPPROTO_UDP) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) + sock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) + sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) self.sock.append(sock) self.log.info('Listening to %s:%s' % (host, port)) sock.bind((host, int(port))) @@ -260,14 +257,13 @@ class BootpServer: def forever(self): while True: try: - r, w, e = select.select(self.sock, [], self.sock) + r, w, e = select(self.sock, [], self.sock) for sock in r: data, addr = sock.recvfrom(556) self.handle(sock, addr, data) except Exception as exc: - import traceback - self.log.critical('%s\n%s' % (exc, traceback.format_exc())) - time.sleep(1) + self.log.critical('%s\n%s' % (exc, format_exc())) + sleep(1) def parse_options(self, tail): self.log.debug('Parsing DHCP options') @@ -280,7 +276,7 @@ class BootpServer: if tag == 0xff: return dhcp_tags 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:] try: option = DHCP_OPTIONS[tag] @@ -296,25 +292,25 @@ class BootpServer: try: buf = '' uuid = options[97] - buf += struct.pack('!BB%ds' % len(uuid), + buf += spack('!BB%ds' % len(uuid), 97, len(uuid), uuid) clientclass = options[60] clientclass = clientclass[:clientclass.find(':')] - buf += struct.pack('!BB%ds' % len(clientclass), + buf += spack('!BB%ds' % len(clientclass), 60, len(clientclass), clientclass) vendor = '' - vendor += struct.pack('!BBB', PXE_DISCOVERY_CONTROL, 1, 0x0A) - vendor += struct.pack('!BBHB4s', PXE_BOOT_SERVERS, 2+1+4, + vendor += spack('!BBB', PXE_DISCOVERY_CONTROL, 1, 0x0A) + vendor += spack('!BBHB4s', PXE_BOOT_SERVERS, 2+1+4, 0, 1, server) 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) 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) - buf += struct.pack('!BB%ds' % len(vendor), 43, + buf += spack('!BB%ds' % len(vendor), 43, len(vendor), vendor) - buf += struct.pack('!BBB', 255, 0, 0) + buf += spack('!BBB', 255, 0, 0) return buf except KeyError as exc: self.log.error('Missing options, cancelling: %s' % exc) @@ -324,7 +320,7 @@ class BootpServer: buf = '' if not clientname: return buf - buf += struct.pack('!BB%ds' % len(clientname), + buf += spack('!BB%ds' % len(clientname), 12, len(clientname), clientname) return buf @@ -333,7 +329,7 @@ class BootpServer: if len(data) < DHCPFORMATSIZE: self.log.error('Cannot be a DHCP or BOOTP request - too small!') tail = data[DHCPFORMATSIZE:] - buf = list(struct.unpack(DHCPFORMAT, data[:DHCPFORMATSIZE])) + buf = list(sunpack(DHCPFORMAT, data[:DHCPFORMATSIZE])) if buf[BOOTP_OP] != BOOTREQUEST: self.log.warn('Not a BOOTREQUEST') return @@ -477,7 +473,7 @@ class BootpServer: # construct reply buf[BOOTP_HOPS] = 0 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': self.log.debug('Client needs its address') ipaddr = iptoint(self.pool_start) @@ -501,20 +497,20 @@ class BootpServer: self.bootp_section, 'netmask', self.netconfig['mask'])) reply_broadcast = iptoint(ip) & mask 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_FLAGS] = BOOTP_FLAGS_BROADCAST relay = buf[BOOTP_GIADDR] if relay != b'\x00\x00\x00\x00': - addr = (socket.inet_ntoa(relay), addr[1]) + addr = (inet_ntoa(relay), addr[1]) else: addr = (inttoip(reply_broadcast), addr[1]) self.log.info('Reply to: %s:%s' % addr) else: buf[BOOTP_YIADDR] = buf[BOOTP_CIADDR] - ip = socket.inet_ntoa(buf[BOOTP_YIADDR]) - buf[BOOTP_SIADDR] = socket.inet_aton(server_addr) + ip = inet_ntoa(buf[BOOTP_YIADDR]) + buf[BOOTP_SIADDR] = inet_aton(server_addr) # sname buf[BOOTP_SNAME] = \ '.'.join([self.config.get(self.bootp_section, @@ -567,38 +563,38 @@ class BootpServer: else: self.log.debug('No filename defined for IP %s' % ip) - pkt = struct.pack(DHCPFORMAT, *buf) - pkt += struct.pack('!BBB', DHCP_MSG, 1, dhcp_reply) - server = socket.inet_aton(server_addr) - pkt += struct.pack('!BB4s', DHCP_SERVER, 4, server) + pkt = spack(DHCPFORMAT, *buf) + pkt += spack('!BBB', DHCP_MSG, 1, dhcp_reply) + server = inet_aton(server_addr) + 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'])) - 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', '') if gateway_addr: - gateway = socket.inet_aton(gateway_addr) + gateway = inet_aton(gateway_addr) else: 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', None) if dns: 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: dns_list = dns.split(';') for dns_str in dns_list: - dns_ip = socket.inet_aton(dns_str) - pkt += struct.pack('!BB4s', DHCP_IP_DNS, 4, dns_ip) - pkt += struct.pack('!BBI', DHCP_LEASE_TIME, 4, + dns_ip = inet_aton(dns_str) + pkt += spack('!BB4s', DHCP_IP_DNS, 4, dns_ip) + pkt += spack('!BBI', DHCP_LEASE_TIME, 4, int(self.config.get(self.bootp_section, 'lease_time', 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 # regular DHCP requests @@ -623,7 +619,7 @@ class BootpServer: self.states[mac_str] = newstate 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 = [] try: with open('/etc/resolv.conf', 'r') as resolv: diff --git a/pybootd/tftpd.py b/pybootd/tftpd.py index 64d0d46..47ac0ac 100644 --- a/pybootd/tftpd.py +++ b/pybootd/tftpd.py @@ -18,22 +18,21 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os -import re -import select -import socket -import string -import struct -import sys -import time from configparser import NoSectionError 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 time import time as now +from traceback import print_exc from urllib.parse import urlparse from urllib.request import urlopen from . import pybootd_path from .util import hexline -__all__ = ['TftpServer'] TFTP_PORT = 69 @@ -86,7 +85,7 @@ class TftpConnection(object): timeout = self.timeout retry = self.server.retry while retry: - r, w, e = select.select([fno], [], [fno], timeout) + r, w, e = select([fno], [], [fno], timeout) if not r: # We timed out -- retransmit retry = retry - 1 @@ -121,13 +120,13 @@ class TftpConnection(object): path = self.server.bootpd.get_filename(client_ip) return path - def parse(self, data, unpack=struct.unpack): + def parse(self, data, unpack=sunpack): self.log.debug('parse') buf = buffer(data) pkt = {} opcode = pkt['opcode'] = unpack('!h', buf[:2])[0] 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) if self.server.root and self.is_url(self.server.root): resource = '%s/%s' % (self.server.root, resource) @@ -138,7 +137,7 @@ class TftpConnection(object): if not self.server.genfilecre.match(resource): if resource.startswith('^%s' % os.sep): resource = os.path.join( - os.path.dirname(sys.argv[0]), + os.path.dirname(argv[0]), resource.lstrip('^%s' % os.sep)) elif self.server.root: if self.server.root.startswith(os.sep): @@ -147,7 +146,7 @@ class TftpConnection(object): resource) else: # 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): daemonpath = os.path.normpath( os.path.join(os.getcwd(), daemonpath)) @@ -220,8 +219,7 @@ class TftpConnection(object): except TftpError as exc: self.send_error(exc[0], exc[1]) except: - import traceback - self.log.error(traceback.format_exc()) + self.log.error(format_exc()) self.log.debug('Ending connection %s:%s' % addr) def recv_ack(self, pkt): @@ -245,19 +243,19 @@ class TftpConnection(object): self.handle_err(pkt) self.retransmit() - def send_data(self, data, pack=struct.pack): + def send_data(self, data, pack=spack): self.log.debug('send_data') if not self.time: - self.time = time.time() + self.time = now() blocksize = self.blocksize block = self.blockNumber = self.blockNumber + 1 lendata = len(data) - format = '!hh%ds' % lendata - pkt = pack(format, self.DATA, block, data) + fmt = '!hh%ds' % lendata + pkt = pack(fmt, self.DATA, block, data) self.send(pkt) self.active = (len(data) == blocksize) if not self.active and self.time: - total = time.time()-self.time + total = now()-self.time self.time = 0 try: name = self.file.name @@ -271,26 +269,24 @@ class TftpConnection(object): # StringIO does not have a 'name' attribute pass except Exception: - import traceback - traceback.print_exc() - pass + print_exc() - def send_ack(self, pack=struct.pack): + def send_ack(self, pack=spack): self.log.debug('send_ack') block = self.blockNumber self.blockNumber = self.blockNumber + 1 - format = '!hh' - pkt = pack(format, self.ACK, block) + fmt = '!hh' + pkt = pack(fmt, self.ACK, block) 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') errtext = errtext + '\000' - format = '!hh%ds' % len(errtext) - outdata = pack(format, self.ERR, errnum, errtext) + fmt = '!hh%ds' % len(errtext) + outdata = pack(fmt, self.ERR, errnum, errtext) 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') pkt = pack('!h', self.OACK) for k, v in options: @@ -341,7 +337,7 @@ class TftpConnection(object): except Exception: self.send_error(1, 'Cannot open resource') self.log.warn('Cannot open file for reading %s: %s' % - sys.exc_info()[:2]) + exc_info()[:2]) return if 'tsize' not in pkt: self.send_data(self.file.read(self.blocksize)) @@ -359,7 +355,7 @@ class TftpConnection(object): except: self.send_error(1, 'Cannot open file') self.log.error('Cannot open file for writing %s: %s' % - sys.exc_info()[:2]) + exc_info()[:2]) return self.send_ack() @@ -398,7 +394,7 @@ class TftpServer: self.retry = int(self.config.get('tftp', 'blocksize', '5')) self.root = self.config.get('tftp', 'root', os.getcwd()) self.fcre, self.filepatterns = self.get_file_filters() - self.genfilecre = re.compile(r'\[(?P[\w\.\-]+)\]') + self.genfilecre = recompile(r'\[(?P[\w\.\-]+)\]') def bind(self): netconfig = self.bootpd and self.bootpd.get_netconfig() @@ -407,7 +403,7 @@ class TftpServer: if not host: raise TftpError('TFTP address no defined') 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) sock.bind((host, port)) @@ -417,7 +413,7 @@ class TftpServer: if not self.bootpd.is_alive(): self.log.info('Bootp daemon is dead, exiting') break - r, w, e = select.select(self.sock, [], self.sock) + r, w, e = select(self.sock, [], self.sock) for sock in r: data, addr = sock.recvfrom(516) tc = TftpConnection(self) @@ -433,7 +429,7 @@ class TftpServer: if not filename: continue 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') def get_file_filters(self): @@ -451,4 +447,4 @@ class TftpServer: xre = r'^(?:\./)?(?:%s)$' % r'|'.join(patterns) except NoSectionError: xre = r'^$' - return (re.compile(xre), replacements) + return (recompile(xre), replacements) diff --git a/pybootd/util.py b/pybootd/util.py index 70d6ad0..e7665cd 100644 --- a/pybootd/util.py +++ b/pybootd/util.py @@ -17,7 +17,6 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -from array import array from configparser import SafeConfigParser, InterpolationSyntaxError from logging import (DEBUG, INFO, ERROR, CRITICAL, WARNING, Formatter, FileHandler, StreamHandler, getLogger) @@ -29,7 +28,6 @@ from subprocess import run from struct import pack as spack, unpack as sunpack from sys import stderr - try: import netifaces except ImportError: @@ -112,7 +110,7 @@ def hexline(data, sep=' '): of the buffer data """ try: - if isinstance(data, (bytes, array)): + if isinstance(data, bytes): src = bytearray(data) elif isinstance(data, bytearray): src = data