mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-09-04 00:25:29 +00:00
4357. [func] Add the python RNDC module. [RT #42093]
This commit is contained in:
2
CHANGES
2
CHANGES
@@ -1,3 +1,5 @@
|
|||||||
|
4357. [func] Add the python RNDC module. [RT #42093]
|
||||||
|
|
||||||
4356. [func] Add the ability to specify whether to wait for
|
4356. [func] Add the ability to specify whether to wait for
|
||||||
nameserver addresses to be looked up or not to
|
nameserver addresses to be looked up or not to
|
||||||
rpz with a new modifying directive 'nsip-wait-recurse'.
|
rpz with a new modifying directive 'nsip-wait-recurse'.
|
||||||
|
@@ -23,7 +23,7 @@ SUBDIRS = tests
|
|||||||
PYTHON = @PYTHON@
|
PYTHON = @PYTHON@
|
||||||
|
|
||||||
PYSRCS = __init__.py dnskey.py eventlist.py keydict.py \
|
PYSRCS = __init__.py dnskey.py eventlist.py keydict.py \
|
||||||
keyevent.py keyzone.py policy.py
|
keyevent.py keyzone.py policy.py rndc.py
|
||||||
TARGETS = parsetab.py
|
TARGETS = parsetab.py
|
||||||
|
|
||||||
@BIND9_MAKE_RULES@
|
@BIND9_MAKE_RULES@
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
__all__ = ['checkds', 'coverage', 'keymgr', 'dnskey', 'eventlist',
|
__all__ = ['checkds', 'coverage', 'keymgr', 'dnskey', 'eventlist',
|
||||||
'keydict', 'keyevent', 'keyseries', 'keyzone', 'policy',
|
'keydict', 'keyevent', 'keyseries', 'keyzone', 'policy',
|
||||||
'parsetab', 'utils']
|
'parsetab', 'rndc', 'utils']
|
||||||
|
|
||||||
from isc.dnskey import *
|
from isc.dnskey import *
|
||||||
from isc.eventlist import *
|
from isc.eventlist import *
|
||||||
@@ -25,4 +25,5 @@ from isc.keyevent import *
|
|||||||
from isc.keyseries import *
|
from isc.keyseries import *
|
||||||
from isc.keyzone import *
|
from isc.keyzone import *
|
||||||
from isc.policy import *
|
from isc.policy import *
|
||||||
|
from isc.rndc import *
|
||||||
from isc.utils import *
|
from isc.utils import *
|
||||||
|
184
bin/python/isc/rndc.py
Normal file
184
bin/python/isc/rndc.py
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
############################################################################
|
||||||
|
# Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
# purpose with or without fee is hereby granted, provided that the above
|
||||||
|
# copyright notice and this permission notice appear in all copies.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||||
|
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
############################################################################
|
||||||
|
|
||||||
|
############################################################################
|
||||||
|
# rndc.py
|
||||||
|
# This module implements the RNDC control protocol.
|
||||||
|
############################################################################
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
from exceptions import TypeError
|
||||||
|
import time
|
||||||
|
import struct
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
import base64
|
||||||
|
import random
|
||||||
|
import socket
|
||||||
|
|
||||||
|
|
||||||
|
class rndc(object):
|
||||||
|
"""RNDC protocol client library"""
|
||||||
|
__algos = {'md5': 157,
|
||||||
|
'sha1': 161,
|
||||||
|
'sha224': 162,
|
||||||
|
'sha256': 163,
|
||||||
|
'sha384': 164,
|
||||||
|
'sha512': 165}
|
||||||
|
|
||||||
|
def __init__(self, host, algo, secret):
|
||||||
|
"""Creates a persistent connection to RNDC and logs in
|
||||||
|
host - (ip, port) tuple
|
||||||
|
algo - HMAC algorithm: one of md5, sha1, sha224, sha256, sha384, sha512
|
||||||
|
(with optional prefix 'hmac-')
|
||||||
|
secret - HMAC secret, base64 encoded"""
|
||||||
|
self.host = host
|
||||||
|
algo = algo.lower()
|
||||||
|
if algo.startswith('hmac-'):
|
||||||
|
algo = algo[5:]
|
||||||
|
self.algo = algo
|
||||||
|
self.hlalgo = getattr(hashlib, algo)
|
||||||
|
self.secret = base64.b64decode(secret)
|
||||||
|
self.ser = random.randint(0, 1 << 24)
|
||||||
|
self.nonce = None
|
||||||
|
self.__connect_login()
|
||||||
|
|
||||||
|
def call(self, cmd):
|
||||||
|
"""Call a RNDC command, all parsing is done on the server side
|
||||||
|
cmd - a complete string with a command (eg 'reload zone example.com')
|
||||||
|
"""
|
||||||
|
return dict(self.__command(type=cmd)['_data'])
|
||||||
|
|
||||||
|
def __serialize_dict(self, data, ignore_auth=False):
|
||||||
|
rv = ''
|
||||||
|
for k, v in data.iteritems():
|
||||||
|
if ignore_auth and k == '_auth':
|
||||||
|
continue
|
||||||
|
rv += chr(len(k))
|
||||||
|
rv += k
|
||||||
|
if type(v) == str:
|
||||||
|
rv += struct.pack('>BI', 1, len(v)) + v
|
||||||
|
elif type(v) == OrderedDict:
|
||||||
|
sd = self.__serialize_dict(v)
|
||||||
|
rv += struct.pack('>BI', 2, len(sd)) + sd
|
||||||
|
else:
|
||||||
|
raise NotImplementedError('Cannot serialize element of type %s'
|
||||||
|
% type(v))
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def __prep_message(self, *args, **kwargs):
|
||||||
|
self.ser += 1
|
||||||
|
now = int(time.time())
|
||||||
|
data = OrderedDict(*args, **kwargs)
|
||||||
|
|
||||||
|
d = OrderedDict()
|
||||||
|
d['_auth'] = OrderedDict()
|
||||||
|
d['_ctrl'] = OrderedDict()
|
||||||
|
d['_ctrl']['_ser'] = str(self.ser)
|
||||||
|
d['_ctrl']['_tim'] = str(now)
|
||||||
|
d['_ctrl']['_exp'] = str(now+60)
|
||||||
|
if self.nonce is not None:
|
||||||
|
d['_ctrl']['_nonce'] = self.nonce
|
||||||
|
d['_data'] = data
|
||||||
|
|
||||||
|
msg = self.__serialize_dict(d, ignore_auth=True)
|
||||||
|
hash = hmac.new(self.secret, msg, self.hlalgo).digest()
|
||||||
|
bhash = base64.b64encode(hash)
|
||||||
|
if self.algo == 'md5':
|
||||||
|
d['_auth']['hmd5'] = struct.pack('22s', bhash)
|
||||||
|
else:
|
||||||
|
d['_auth']['hsha'] = struct.pack('B88s',
|
||||||
|
self.__algos[self.algo], bhash)
|
||||||
|
msg = self.__serialize_dict(d)
|
||||||
|
msg = struct.pack('>II', len(msg) + 4, 1) + msg
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def __verify_msg(self, msg):
|
||||||
|
if self.nonce is not None and msg['_ctrl']['_nonce'] != self.nonce:
|
||||||
|
return False
|
||||||
|
bhash = msg['_auth']['hmd5' if self.algo == 'md5' else 'hsha']
|
||||||
|
bhash += '=' * (4 - (len(bhash) % 4))
|
||||||
|
remote_hash = base64.b64decode(bhash)
|
||||||
|
my_msg = self.__serialize_dict(msg, ignore_auth=True)
|
||||||
|
my_hash = hmac.new(self.secret, my_msg, self.hlalgo).digest()
|
||||||
|
return (my_hash == remote_hash)
|
||||||
|
|
||||||
|
def __command(self, *args, **kwargs):
|
||||||
|
msg = self.__prep_message(*args, **kwargs)
|
||||||
|
sent = self.socket.send(msg)
|
||||||
|
if sent != len(msg):
|
||||||
|
raise IOError("Cannot send the message")
|
||||||
|
|
||||||
|
header = self.socket.recv(8)
|
||||||
|
if len(header) != 8:
|
||||||
|
# What should we throw here? Bad auth can cause this...
|
||||||
|
raise IOError("Can't read response header")
|
||||||
|
|
||||||
|
length, version = struct.unpack('>II', header)
|
||||||
|
if version != 1:
|
||||||
|
raise NotImplementedError('Wrong message version %d' % version)
|
||||||
|
|
||||||
|
# it includes the header
|
||||||
|
length -= 4
|
||||||
|
data = self.socket.recv(length, socket.MSG_WAITALL)
|
||||||
|
if len(data) != length:
|
||||||
|
raise IOError("Can't read response data")
|
||||||
|
|
||||||
|
msg = self.__parse_message(data)
|
||||||
|
if not self.__verify_msg(msg):
|
||||||
|
raise IOError("Authentication failure")
|
||||||
|
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def __connect_login(self):
|
||||||
|
self.socket = socket.create_connection(self.host)
|
||||||
|
self.nonce = None
|
||||||
|
msg = self.__command(type='null')
|
||||||
|
self.nonce = msg['_ctrl']['_nonce']
|
||||||
|
|
||||||
|
def __parse_element(self, input):
|
||||||
|
pos = 0
|
||||||
|
labellen = ord(input[pos])
|
||||||
|
pos += 1
|
||||||
|
label = input[pos:pos+labellen]
|
||||||
|
pos += labellen
|
||||||
|
type = ord(input[pos])
|
||||||
|
pos += 1
|
||||||
|
datalen = struct.unpack('>I', input[pos:pos+4])[0]
|
||||||
|
pos += 4
|
||||||
|
data = input[pos:pos+datalen]
|
||||||
|
pos += datalen
|
||||||
|
rest = input[pos:]
|
||||||
|
|
||||||
|
if type == 1: # raw binary value
|
||||||
|
return label, data, rest
|
||||||
|
elif type == 2: # dictionary
|
||||||
|
d = OrderedDict()
|
||||||
|
while len(data) > 0:
|
||||||
|
ilabel, value, data = self.__parse_element(data)
|
||||||
|
d[ilabel] = value
|
||||||
|
return label, d, rest
|
||||||
|
# TODO type 3 - list
|
||||||
|
else:
|
||||||
|
raise NotImplementedError('Unknown element type %d' % type)
|
||||||
|
|
||||||
|
def __parse_message(self, input):
|
||||||
|
rv = OrderedDict()
|
||||||
|
hdata = None
|
||||||
|
while len(input) > 0:
|
||||||
|
label, value, input = self.__parse_element(input)
|
||||||
|
rv[label] = value
|
||||||
|
return rv
|
@@ -448,4 +448,20 @@ grep "^running on " rndc.output > /dev/null || ret=1
|
|||||||
if [ $ret != 0 ]; then echo "I:failed"; fi
|
if [ $ret != 0 ]; then echo "I:failed"; fi
|
||||||
status=`expr $status + $ret`
|
status=`expr $status + $ret`
|
||||||
|
|
||||||
|
if [ -x "$PYTHON" ]; then
|
||||||
|
echo "I:test rndc python bindings"
|
||||||
|
ret=0
|
||||||
|
$PYTHON > rndc.output << EOF
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, '../../../../bin/python')
|
||||||
|
from isc import *
|
||||||
|
r = rndc(('10.53.0.5', 9953), 'hmac-sha256', '1234abcd8765')
|
||||||
|
result = r.call('status')
|
||||||
|
print(result['text'])
|
||||||
|
EOF
|
||||||
|
grep 'server is up and running' rndc.output > /dev/null 2>&1 || ret=1
|
||||||
|
if [ $ret != 0 ]; then echo "I:failed"; fi
|
||||||
|
status=`expr $status + $ret`
|
||||||
|
fi
|
||||||
|
|
||||||
exit $status
|
exit $status
|
||||||
|
Reference in New Issue
Block a user