mirror of
https://github.com/eblot/pybootd.git
synced 2024-08-17 09:51:07 +03:00
Start porting to Py3 only and fix old syntax
This commit is contained in:
parent
efe3ff3b40
commit
d1231d55c0
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2010-2016 Emmanuel Blot <emmanuel.blot@free.fr>
|
# Copyright (c) 2010-2019 Emmanuel Blot <emmanuel.blot@free.fr>
|
||||||
# Copyright (c) 2010-2011 Neotion
|
# Copyright (c) 2010-2011 Neotion
|
||||||
#
|
#
|
||||||
# This library is free software; you can redistribute it and/or
|
# This library is free software; you can redistribute it and/or
|
||||||
|
@ -21,7 +21,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
def _get_package_name(default='', version='1.5.0'):
|
def _get_package_name(default='', version='1.6.0'):
|
||||||
try:
|
try:
|
||||||
from pkg_resources import WorkingSet
|
from pkg_resources import WorkingSet
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2010-2016 Emmanuel Blot <emmanuel.blot@free.fr>
|
# Copyright (c) 2010-2019 Emmanuel Blot <emmanuel.blot@free.fr>
|
||||||
# Copyright (c) 2010-2011 Neotion
|
# Copyright (c) 2010-2011 Neotion
|
||||||
#
|
#
|
||||||
# This library is free software; you can redistribute it and/or
|
# This library is free software; you can redistribute it and/or
|
||||||
@ -21,14 +21,13 @@
|
|||||||
"""Boot up server, a tiny BOOTP/DHCP/TFTP/PXE server"""
|
"""Boot up server, a tiny BOOTP/DHCP/TFTP/PXE server"""
|
||||||
|
|
||||||
|
|
||||||
import os
|
from os.path import isfile
|
||||||
import sys
|
|
||||||
from pxed import BootpServer
|
|
||||||
from pybootd import pybootd_path, PRODUCT_NAME, __version__ as VERSION
|
|
||||||
from six import print_
|
|
||||||
from tftpd import TftpServer
|
|
||||||
from util import logger_factory, EasyConfigParser
|
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
from sys import exit as sysexit, modules, stderr
|
||||||
|
from . import pybootd_path, PRODUCT_NAME, __version__ as VERSION
|
||||||
|
from .pxed import BootpServer
|
||||||
|
from .tftpd import TftpServer
|
||||||
|
from .util import logger_factory, EasyConfigParser
|
||||||
|
|
||||||
|
|
||||||
class BootpDaemon(Thread):
|
class BootpDaemon(Thread):
|
||||||
@ -65,7 +64,7 @@ def main():
|
|||||||
debug = False
|
debug = False
|
||||||
try:
|
try:
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
argparser = ArgumentParser(description=sys.modules[__name__].__doc__)
|
argparser = ArgumentParser(description=modules[__name__].__doc__)
|
||||||
argparser.add_argument('-c', '--config', dest='config',
|
argparser.add_argument('-c', '--config', dest='config',
|
||||||
default='pybootd/etc/pybootd.ini',
|
default='pybootd/etc/pybootd.ini',
|
||||||
help='configuration file')
|
help='configuration file')
|
||||||
@ -80,7 +79,7 @@ def main():
|
|||||||
args = argparser.parse_args()
|
args = argparser.parse_args()
|
||||||
debug = args.debug
|
debug = args.debug
|
||||||
|
|
||||||
if not os.path.isfile(args.config):
|
if not isfile(args.config):
|
||||||
argparser.error('Invalid configuration file')
|
argparser.error('Invalid configuration file')
|
||||||
|
|
||||||
if args.pxe and args.tftp:
|
if args.pxe and args.tftp:
|
||||||
@ -110,10 +109,10 @@ def main():
|
|||||||
if not daemon.is_alive():
|
if not daemon.is_alive():
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print_('\nError: %s' % e, file=sys.stderr)
|
print('\nError: %s' % e, file=stderr)
|
||||||
if debug:
|
if debug:
|
||||||
import traceback
|
import traceback
|
||||||
print_(traceback.format_exc(), file=sys.stderr)
|
print(traceback.format_exc(), file=stderr)
|
||||||
sys.exit(1)
|
sysexit(1)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print_("Aborting...")
|
print("Aborting...")
|
||||||
|
@ -14,7 +14,6 @@ lease_time = 86400
|
|||||||
access = mac
|
access = mac
|
||||||
allow_simple_dhcp = enable
|
allow_simple_dhcp = enable
|
||||||
dns = 10.130.0.2
|
dns = 10.130.0.2
|
||||||
boot_file = pxelinux.0
|
|
||||||
set_gateway = true
|
set_gateway = true
|
||||||
; use "nc -l -u 127.0.0.1 -p 12345" to debug
|
; use "nc -l -u 127.0.0.1 -p 12345" to debug
|
||||||
; notify = 192.168.26.201:12345;192.168.26.200:12345
|
; notify = 192.168.26.201:12345;192.168.26.200:12345
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2010-2016 Emmanuel Blot <emmanuel.blot@free.fr>
|
# Copyright (c) 2010-2019 Emmanuel Blot <emmanuel.blot@free.fr>
|
||||||
# Copyright (c) 2010-2011 Neotion
|
# Copyright (c) 2010-2011 Neotion
|
||||||
#
|
#
|
||||||
# This library is free software; you can redistribute it and/or
|
# This library is free software; you can redistribute it and/or
|
||||||
@ -25,7 +25,7 @@ import struct
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from binascii import hexlify
|
from binascii import hexlify
|
||||||
from pybootd import PRODUCT_NAME
|
from . import PRODUCT_NAME
|
||||||
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
|
||||||
@ -34,10 +34,10 @@ BOOTP_PORT_REPLY = 68
|
|||||||
BOOTREQUEST = 1
|
BOOTREQUEST = 1
|
||||||
BOOTREPLY = 2
|
BOOTREPLY = 2
|
||||||
|
|
||||||
BOOTPFormat = '!4bIHH4s4s4s4s16s64s128s64s'
|
BOOTPFORMAT = '!4bIHH4s4s4s4s16s64s128s64s'
|
||||||
BOOTPFormatSize = struct.calcsize(BOOTPFormat)
|
BOOTPFORMATSIZE = struct.calcsize(BOOTPFORMAT)
|
||||||
DHCPFormat = '!4bIHH4s4s4s4s16s64s128s4s'
|
DHCPFORMAT = '!4bIHH4s4s4s4s16s64s128s4s'
|
||||||
DHCPFormatSize = struct.calcsize(DHCPFormat)
|
DHCPFORMATSIZE = struct.calcsize(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,
|
||||||
@ -46,7 +46,7 @@ DHCPFormatSize = struct.calcsize(DHCPFormat)
|
|||||||
BOOTP_FLAGS_NONE = 0
|
BOOTP_FLAGS_NONE = 0
|
||||||
BOOTP_FLAGS_BROADCAST = 1<<15
|
BOOTP_FLAGS_BROADCAST = 1<<15
|
||||||
|
|
||||||
COOKIE='\0x63\0x82\0x53\0x63'
|
COOKIE = r'\0x63\0x82\0x53\0x63'
|
||||||
|
|
||||||
DHCP_OPTIONS = {0: 'Byte padding',
|
DHCP_OPTIONS = {0: 'Byte padding',
|
||||||
1: 'Subnet mask',
|
1: 'Subnet mask',
|
||||||
@ -191,7 +191,8 @@ 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')
|
||||||
self.netconfig = get_iface_config(host)
|
self.netconfig = get_iface_config(host)
|
||||||
if not self.netconfig:
|
if not self.netconfig:
|
||||||
raise BootpError('Unable to detect network configuration')
|
# the available networks on the host may not match the config...
|
||||||
|
raise BootpError('Unable to detect a matching network config')
|
||||||
|
|
||||||
keys = sorted(self.netconfig.keys())
|
keys = sorted(self.netconfig.keys())
|
||||||
self.log.info('Using %s' % ', '.join(map(
|
self.log.info('Using %s' % ', '.join(map(
|
||||||
@ -204,8 +205,8 @@ class BootpServer:
|
|||||||
for n in nlist:
|
for n in nlist:
|
||||||
n = n.strip().split(':')
|
n = n.strip().split(':')
|
||||||
self.notify.append((n[0], int(n[1])))
|
self.notify.append((n[0], int(n[1])))
|
||||||
except Exception, e:
|
except Exception as exc:
|
||||||
raise BootpError('Invalid notification URL: %s' % str(e))
|
raise BootpError('Invalid notification URL: %s' % exc)
|
||||||
access = self.config.get(self.bootp_section, 'access')
|
access = self.config.get(self.bootp_section, 'access')
|
||||||
if not access:
|
if not access:
|
||||||
self.acl = None
|
self.acl = None
|
||||||
@ -263,9 +264,9 @@ class BootpServer:
|
|||||||
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, e:
|
except Exception as exc:
|
||||||
import traceback
|
import traceback
|
||||||
self.log.critical('%s\n%s' % (str(e), traceback.format_exc()))
|
self.log.critical('%s\n%s' % (exc, traceback.format_exc()))
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
def parse_options(self, tail):
|
def parse_options(self, tail):
|
||||||
@ -315,8 +316,8 @@ class BootpServer:
|
|||||||
len(vendor), vendor)
|
len(vendor), vendor)
|
||||||
buf += struct.pack('!BBB', 255, 0, 0)
|
buf += struct.pack('!BBB', 255, 0, 0)
|
||||||
return buf
|
return buf
|
||||||
except KeyError, e:
|
except KeyError as exc:
|
||||||
self.log.error('Missing options, cancelling: ' + str(e))
|
self.log.error('Missing options, cancelling: %s' % exc)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def build_dhcp_options(self, clientname):
|
def build_dhcp_options(self, clientname):
|
||||||
@ -329,10 +330,10 @@ class BootpServer:
|
|||||||
|
|
||||||
def handle(self, sock, addr, data):
|
def handle(self, sock, addr, data):
|
||||||
self.log.info('Sender: %s on socket %s' % (addr, sock.getsockname()))
|
self.log.info('Sender: %s on socket %s' % (addr, sock.getsockname()))
|
||||||
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(struct.unpack(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
|
||||||
@ -360,9 +361,9 @@ class BootpServer:
|
|||||||
uuid = self.uuidpool.get(mac_addr, None)
|
uuid = self.uuidpool.get(mac_addr, None)
|
||||||
pxe = False
|
pxe = False
|
||||||
self.log.info('PXE UUID not present in request')
|
self.log.info('PXE UUID not present in request')
|
||||||
uuid_str = uuid and ('%s-%s-%s-%s-%s' % tuple(
|
uuid_str = uuid and ('%s-%s-%s-%s-%s' % tuple([hexlify(x)
|
||||||
[hexlify(x) for x in uuid[0:4], uuid[4:6], uuid[6:8],
|
for x in (uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:16])
|
||||||
uuid[8:10], uuid[10:16]])).upper()
|
])).upper()
|
||||||
if uuid_str:
|
if uuid_str:
|
||||||
self.log.info('UUID is %s for MAC %s' % (uuid_str, mac_str))
|
self.log.info('UUID is %s for MAC %s' % (uuid_str, mac_str))
|
||||||
|
|
||||||
@ -441,16 +442,16 @@ class BootpServer:
|
|||||||
filename = v
|
filename = v
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
except urllib2.HTTPError, e:
|
except urllib2.HTTPError as exc:
|
||||||
self.log.error('HTTP Error: %s' % str(e))
|
self.log.error('HTTP Error: %s' % exc)
|
||||||
self.states[mac_str] = self.ST_IDLE
|
self.states[mac_str] = self.ST_IDLE
|
||||||
return
|
return
|
||||||
except urllib2.URLError, e:
|
except urllib2.URLError as exc:
|
||||||
self.log.critical('Internal error: %s' % str(e))
|
self.log.critical('Internal error: %s' % exc)
|
||||||
self.states[mac_str] = self.ST_IDLE
|
self.states[mac_str] = self.ST_IDLE
|
||||||
return
|
return
|
||||||
except httplib.HTTPException, e:
|
except httplib.HTTPException as exc:
|
||||||
self.log.error('Server error: %s' % type(e))
|
self.log.error('Server error: %s' % type(exc))
|
||||||
self.states[mac_str] = self.ST_IDLE
|
self.states[mac_str] = self.ST_IDLE
|
||||||
return
|
return
|
||||||
# local access is only validated if mac address is not yet known
|
# local access is only validated if mac address is not yet known
|
||||||
@ -566,7 +567,7 @@ 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 = struct.pack(DHCPFORMAT, *buf)
|
||||||
pkt += struct.pack('!BBB', DHCP_MSG, 1, dhcp_reply)
|
pkt += struct.pack('!BBB', DHCP_MSG, 1, dhcp_reply)
|
||||||
server = socket.inet_aton(server_addr)
|
server = socket.inet_aton(server_addr)
|
||||||
pkt += struct.pack('!BB4s', DHCP_SERVER, 4, server)
|
pkt += struct.pack('!BB4s', DHCP_SERVER, 4, server)
|
||||||
@ -622,7 +623,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('nameserver\s+(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})\s')
|
nscre = re.compile(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:
|
||||||
@ -632,7 +633,7 @@ class BootpServer:
|
|||||||
dns = mo.group(1)
|
dns = mo.group(1)
|
||||||
self.log.info('Found nameserver: %s' % dns)
|
self.log.info('Found nameserver: %s' % dns)
|
||||||
result.append(dns)
|
result.append(dns)
|
||||||
except Exception, e:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
if not result:
|
if not result:
|
||||||
self.log.info('No nameserver found')
|
self.log.info('No nameserver found')
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2010-2016 Emmanuel Blot <emmanuel.blot@free.fr>
|
# Copyright (c) 2010-2019 Emmanuel Blot <emmanuel.blot@free.fr>
|
||||||
# Copyright (c) 2010-2011 Neotion
|
# Copyright (c) 2010-2011 Neotion
|
||||||
#
|
#
|
||||||
# This library is free software; you can redistribute it and/or
|
# This library is free software; you can redistribute it and/or
|
||||||
@ -25,13 +25,13 @@ import string
|
|||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import thread
|
from configparser import NoSectionError
|
||||||
import urllib2
|
from io import StringIO
|
||||||
import urlparse
|
from threading import Thread
|
||||||
from ConfigParser import NoSectionError
|
from urllib.parse import urlparse
|
||||||
from cStringIO import StringIO
|
from urllib.request import urlopen
|
||||||
from pybootd import pybootd_path
|
from . import pybootd_path
|
||||||
from util import hexline
|
from .util import hexline
|
||||||
|
|
||||||
__all__ = ['TftpServer']
|
__all__ = ['TftpServer']
|
||||||
|
|
||||||
@ -217,8 +217,8 @@ class TftpConnection(object):
|
|||||||
else:
|
else:
|
||||||
raise TftpError(5, 'Invalid opcode')
|
raise TftpError(5, 'Invalid opcode')
|
||||||
self.log.debug('End of active: %s:%s' % addr)
|
self.log.debug('End of active: %s:%s' % addr)
|
||||||
except TftpError, detail:
|
except TftpError as exc:
|
||||||
self.send_error(detail[0], detail[1])
|
self.send_error(exc[0], exc[1])
|
||||||
except:
|
except:
|
||||||
import traceback
|
import traceback
|
||||||
self.log.error(traceback.format_exc())
|
self.log.error(traceback.format_exc())
|
||||||
@ -310,7 +310,7 @@ class TftpConnection(object):
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
if self.is_url(resource):
|
if self.is_url(resource):
|
||||||
rp = urllib2.urlopen(resource)
|
rp = urlopen(resource)
|
||||||
meta = rp.info()
|
meta = rp.info()
|
||||||
filesize = int(meta.getheaders('Content-Length')[0])
|
filesize = int(meta.getheaders('Content-Length')[0])
|
||||||
else:
|
else:
|
||||||
@ -333,7 +333,7 @@ class TftpConnection(object):
|
|||||||
try:
|
try:
|
||||||
if self.is_url(resource):
|
if self.is_url(resource):
|
||||||
self.log.info("Sending resource '%s'" % resource)
|
self.log.info("Sending resource '%s'" % resource)
|
||||||
self.file = urllib2.urlopen(resource)
|
self.file = urlopen(resource)
|
||||||
else:
|
else:
|
||||||
resource = os.path.realpath(resource)
|
resource = os.path.realpath(resource)
|
||||||
self.log.info("Sending file '%s'" % resource)
|
self.log.info("Sending file '%s'" % resource)
|
||||||
@ -420,8 +420,9 @@ class TftpServer:
|
|||||||
r, w, e = select.select(self.sock, [], self.sock)
|
r, w, e = select.select(self.sock, [], self.sock)
|
||||||
for sock in r:
|
for sock in r:
|
||||||
data, addr = sock.recvfrom(516)
|
data, addr = sock.recvfrom(516)
|
||||||
t = TftpConnection(self)
|
tc = TftpConnection(self)
|
||||||
thread.start_new_thread(t.connect, (addr, data))
|
thread = Thread(tc.connect, (addr, data))
|
||||||
|
thread.start()
|
||||||
|
|
||||||
def filter_file(self, connexion, mo):
|
def filter_file(self, connexion, mo):
|
||||||
# extract the position of the matching pattern, then extract the
|
# extract the position of the matching pattern, then extract the
|
||||||
@ -442,12 +443,12 @@ class TftpServer:
|
|||||||
for pos, pattern in enumerate(self.config.options('filters'), 1):
|
for pos, pattern in enumerate(self.config.options('filters'), 1):
|
||||||
value = self.config.get('filters', pattern).strip()
|
value = self.config.get('filters', pattern).strip()
|
||||||
pattern = pattern.strip('\r\n \t')
|
pattern = pattern.strip('\r\n \t')
|
||||||
pattern = pattern.replace('.', '\.')
|
pattern = pattern.replace(r'.', r'\.')
|
||||||
pattern = pattern.replace('*', '.*').replace('?', '.')
|
pattern = pattern.replace(r'*', r'.*').replace(r'?', r'.')
|
||||||
pname = 'p%d' % pos
|
pname = 'p%d' % pos
|
||||||
replacements[pname] = value
|
replacements[pname] = value
|
||||||
patterns.append('(?P<%s>%s)' % (pname, pattern))
|
patterns.append(r'(?P<%s>%s)' % (pname, pattern))
|
||||||
xre = '^(?:\./)?(?:%s)$' % '|'.join(patterns)
|
xre = r'^(?:\./)?(?:%s)$' % r'|'.join(patterns)
|
||||||
except NoSectionError:
|
except NoSectionError:
|
||||||
xre = '^$'
|
xre = r'^$'
|
||||||
return (re.compile(xre), replacements)
|
return (re.compile(xre), replacements)
|
||||||
|
100
pybootd/util.py
100
pybootd/util.py
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2010-2016 Emmanuel Blot <emmanuel.blot@free.fr>
|
# Copyright (c) 2010-2019 Emmanuel Blot <emmanuel.blot@free.fr>
|
||||||
# Copyright (c) 2010-2011 Neotion
|
# Copyright (c) 2010-2011 Neotion
|
||||||
#
|
#
|
||||||
# This library is free software; you can redistribute it and/or
|
# This library is free software; you can redistribute it and/or
|
||||||
@ -18,14 +18,17 @@
|
|||||||
# 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 array import array
|
||||||
import commands
|
from configparser import SafeConfigParser, InterpolationSyntaxError
|
||||||
import logging
|
from logging import (DEBUG, INFO, ERROR, CRITICAL, WARNING,
|
||||||
import re
|
Formatter, FileHandler, StreamHandler, getLogger)
|
||||||
import socket
|
from logging.handlers import (BufferingHandler, NTEventLogHandler,
|
||||||
import struct
|
SysLogHandler)
|
||||||
import sys
|
from re import match
|
||||||
from ConfigParser import SafeConfigParser
|
from socket import inet_aton, inet_ntoa
|
||||||
from six import PY3, integer_types, binary_type
|
from subprocess import run
|
||||||
|
from struct import pack as spack, unpack as sunpack
|
||||||
|
from sys import stderr
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import netifaces
|
import netifaces
|
||||||
@ -35,6 +38,7 @@ except ImportError:
|
|||||||
raise ImportError('netifaces package is not installed')
|
raise ImportError('netifaces package is not installed')
|
||||||
netifaces = None
|
netifaces = None
|
||||||
|
|
||||||
|
|
||||||
# String values evaluated as true boolean values
|
# String values evaluated as true boolean values
|
||||||
TRUE_BOOLEANS = ['on', 'high', 'true', 'enable', 'enabled', 'yes', '1']
|
TRUE_BOOLEANS = ['on', 'high', 'true', 'enable', 'enabled', 'yes', '1']
|
||||||
# String values evaluated as false boolean values
|
# String values evaluated as false boolean values
|
||||||
@ -56,9 +60,9 @@ def to_int(value):
|
|||||||
"""
|
"""
|
||||||
if not value:
|
if not value:
|
||||||
return 0
|
return 0
|
||||||
if isinstance(value, integer_types):
|
if isinstance(value, int):
|
||||||
return int(value)
|
return int(value)
|
||||||
mo = re.match('^\s*(\d+)\s*(?:([KMkm]i?)?B?)?\s*$', value)
|
mo = match(r'^\s*(\d+)\s*(?:([KMkm]i?)?B?)?\s*$', value)
|
||||||
if mo:
|
if mo:
|
||||||
mult = {'K': (1000),
|
mult = {'K': (1000),
|
||||||
'KI': (1 << 10),
|
'KI': (1 << 10),
|
||||||
@ -108,7 +112,7 @@ def hexline(data, sep=' '):
|
|||||||
of the buffer data
|
of the buffer data
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if isinstance(data, (binary_type, array)):
|
if isinstance(data, (bytes, array)):
|
||||||
src = bytearray(data)
|
src = bytearray(data)
|
||||||
elif isinstance(data, bytearray):
|
elif isinstance(data, bytearray):
|
||||||
src = data
|
src = data
|
||||||
@ -128,20 +132,20 @@ def hexline(data, sep=' '):
|
|||||||
def logger_factory(logtype='syslog', logfile=None, level='WARNING',
|
def logger_factory(logtype='syslog', logfile=None, level='WARNING',
|
||||||
logid='PXEd', format=None):
|
logid='PXEd', format=None):
|
||||||
# this code has been copied from Trac (MIT modified license)
|
# this code has been copied from Trac (MIT modified license)
|
||||||
logger = logging.getLogger(logid)
|
logger = getLogger(logid)
|
||||||
logtype = logtype.lower()
|
logtype = logtype.lower()
|
||||||
if logtype == 'file':
|
if logtype == 'file':
|
||||||
hdlr = logging.FileHandler(logfile)
|
hdlr = FileHandler(logfile)
|
||||||
elif logtype in ('winlog', 'eventlog', 'nteventlog'):
|
elif logtype in ('winlog', 'eventlog', 'nteventlog'):
|
||||||
# Requires win32 extensions
|
# Requires win32 extensions
|
||||||
hdlr = logging.handlers.NTEventLogHandler(logid,
|
hdlr = NTEventLogHandler(logid,
|
||||||
logtype='Application')
|
logtype='Application')
|
||||||
elif logtype in ('syslog', 'unix'):
|
elif logtype in ('syslog', 'unix'):
|
||||||
hdlr = logging.handlers.SysLogHandler('/dev/log')
|
hdlr = SysLogHandler('/dev/log')
|
||||||
elif logtype in ('stderr'):
|
elif logtype in ('stderr'):
|
||||||
hdlr = logging.StreamHandler(sys.stderr)
|
hdlr = StreamHandler(stderr)
|
||||||
else:
|
else:
|
||||||
hdlr = logging.handlers.BufferingHandler(0)
|
hdlr = BufferingHandler(0)
|
||||||
|
|
||||||
if not format:
|
if not format:
|
||||||
format = 'PXEd[%(module)s] %(levelname)s: %(message)s'
|
format = 'PXEd[%(module)s] %(levelname)s: %(message)s'
|
||||||
@ -152,23 +156,23 @@ def logger_factory(logtype='syslog', logfile=None, level='WARNING',
|
|||||||
datefmt = '%X'
|
datefmt = '%X'
|
||||||
level = level.upper()
|
level = level.upper()
|
||||||
if level in ('DEBUG', 'ALL'):
|
if level in ('DEBUG', 'ALL'):
|
||||||
logger.setLevel(logging.DEBUG)
|
logger.setLevel(DEBUG)
|
||||||
elif level == 'INFO':
|
elif level == 'INFO':
|
||||||
logger.setLevel(logging.INFO)
|
logger.setLevel(INFO)
|
||||||
elif level == 'ERROR':
|
elif level == 'ERROR':
|
||||||
logger.setLevel(logging.ERROR)
|
logger.setLevel(ERROR)
|
||||||
elif level == 'CRITICAL':
|
elif level == 'CRITICAL':
|
||||||
logger.setLevel(logging.CRITICAL)
|
logger.setLevel(CRITICAL)
|
||||||
else:
|
else:
|
||||||
logger.setLevel(logging.WARNING)
|
logger.setLevel(WARNING)
|
||||||
formatter = logging.Formatter(format, datefmt)
|
formatter = Formatter(format, datefmt)
|
||||||
hdlr.setFormatter(formatter)
|
hdlr.setFormatter(formatter)
|
||||||
logger.addHandler(hdlr)
|
logger.addHandler(hdlr)
|
||||||
|
|
||||||
def logerror(record):
|
def logerror(record):
|
||||||
import traceback
|
import traceback
|
||||||
print_(record.msg)
|
print(record.msg)
|
||||||
print_(record.args)
|
print(record.args)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
# uncomment the following line to show logger formatting error
|
# uncomment the following line to show logger formatting error
|
||||||
#hdlr.handleError = logerror
|
#hdlr.handleError = logerror
|
||||||
@ -177,11 +181,11 @@ def logger_factory(logtype='syslog', logfile=None, level='WARNING',
|
|||||||
|
|
||||||
|
|
||||||
def iptoint(ipstr):
|
def iptoint(ipstr):
|
||||||
return struct.unpack('!I', socket.inet_aton(ipstr))[0]
|
return sunpack('!I', inet_aton(ipstr))[0]
|
||||||
|
|
||||||
|
|
||||||
def inttoip(ipval):
|
def inttoip(ipval):
|
||||||
return socket.inet_ntoa(struct.pack('!I', ipval))
|
return inet_ntoa(spack('!I', ipval))
|
||||||
|
|
||||||
|
|
||||||
def _netifaces_get_iface_config(address):
|
def _netifaces_get_iface_config(address):
|
||||||
@ -213,7 +217,7 @@ def _netifaces_get_iface_config(address):
|
|||||||
def _iproute_get_iface_config(address):
|
def _iproute_get_iface_config(address):
|
||||||
pool = iptoint(address)
|
pool = iptoint(address)
|
||||||
iplines = (line.strip()
|
iplines = (line.strip()
|
||||||
for line in commands.getoutput("ip address show").split('\n'))
|
for line in run("ip address show").stdout.split('\n'))
|
||||||
iface = None
|
iface = None
|
||||||
for l in iplines:
|
for l in iplines:
|
||||||
items = l.split()
|
items = l.split()
|
||||||
@ -246,11 +250,43 @@ def get_iface_config(address):
|
|||||||
|
|
||||||
|
|
||||||
class EasyConfigParser(SafeConfigParser):
|
class EasyConfigParser(SafeConfigParser):
|
||||||
"ConfigParser extension to support default config values"
|
"""ConfigParser extension to support default config values and do not
|
||||||
|
mess with multi-line option strings"""
|
||||||
|
|
||||||
def get(self, section, option, default=None):
|
INDENT_SIZE = 8
|
||||||
|
|
||||||
|
InterpolationSyntaxError = InterpolationSyntaxError
|
||||||
|
|
||||||
|
def get(self, section, option, default=None, raw=True, vars=None,
|
||||||
|
fallback=None):
|
||||||
|
"""Return the section:option value if it exists, or the default value
|
||||||
|
if either the section or the option is missing"""
|
||||||
if not self.has_section(section):
|
if not self.has_section(section):
|
||||||
return default
|
return default
|
||||||
if not self.has_option(section, option):
|
if not self.has_option(section, option):
|
||||||
return default
|
return default
|
||||||
return SafeConfigParser.get(self, section, option)
|
return SafeConfigParser.get(self, section, option, raw=raw, vars=vars,
|
||||||
|
fallback=fallback)
|
||||||
|
|
||||||
|
def write(self, filep):
|
||||||
|
"""Write an .ini-format representation of the configuration state,
|
||||||
|
with automatic line wrapping, using improved multi-line
|
||||||
|
representation.
|
||||||
|
"""
|
||||||
|
for section in self._sections:
|
||||||
|
filep.write("[%s]\n" % section)
|
||||||
|
for (key, value) in self._sections[section].items():
|
||||||
|
if key != "__name__":
|
||||||
|
filep.write("%s = %s\n" %
|
||||||
|
(key, str(value).replace('\n', '\n' +
|
||||||
|
' ' * self.INDENT_SIZE)))
|
||||||
|
filep.write("\n")
|
||||||
|
|
||||||
|
def _interpolate(self, section, option, rawval, vars):
|
||||||
|
# special overloading of SafeConfigParser._interpolate:
|
||||||
|
# do not attempt to interpolate if the string is (double-)quoted
|
||||||
|
if is_quoted(rawval):
|
||||||
|
return rawval
|
||||||
|
# cannot use 'super' here as ConfigParser is outdated
|
||||||
|
return SafeConfigParser._interpolate(self, section, option,
|
||||||
|
rawval, vars)
|
||||||
|
6
setup.py
6
setup.py
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2010-2016 Emmanuel Blot <emmanuel.blot@free.fr>
|
# Copyright (c) 2010-2019 Emmanuel Blot <emmanuel.blot@free.fr>
|
||||||
# Copyright (c) 2010-2011 Neotion
|
# Copyright (c) 2010-2011 Neotion
|
||||||
#
|
#
|
||||||
# This library is free software; you can redistribute it and/or
|
# This library is free software; you can redistribute it and/or
|
||||||
@ -26,7 +26,7 @@ def _read(fname):
|
|||||||
return open(os.path.join(os.path.dirname(__file__), fname)).read()
|
return open(os.path.join(os.path.dirname(__file__), fname)).read()
|
||||||
|
|
||||||
|
|
||||||
requirements = ['six']
|
requirements = []
|
||||||
if os.uname()[0].lower() == 'darwin':
|
if os.uname()[0].lower() == 'darwin':
|
||||||
requirements.append('netifaces (>= 0.5)')
|
requirements.append('netifaces (>= 0.5)')
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ setup(
|
|||||||
'Lesser General Public License (LGPL)',
|
'Lesser General Public License (LGPL)',
|
||||||
'Operating System :: MacOS :: MacOS X',
|
'Operating System :: MacOS :: MacOS X',
|
||||||
'Operating System :: POSIX',
|
'Operating System :: POSIX',
|
||||||
'Programming Language :: Python :: 2.7',
|
'Programming Language :: Python :: 3.5',
|
||||||
'Topic :: Internet',
|
'Topic :: Internet',
|
||||||
'Topic :: System :: Installation/Setup',
|
'Topic :: System :: Installation/Setup',
|
||||||
'Topic :: System :: Networking',
|
'Topic :: System :: Networking',
|
||||||
|
Loading…
Reference in New Issue
Block a user