"Fossies" - the Fresh Open Source Software Archive

Member "bind-9.11.23/bin/python/isc/rndc.py.in" (7 Sep 2020, 6698 Bytes) of package /linux/misc/dns/bind9/9.11.23/bind-9.11.23.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file.

    1 ############################################################################
    2 # Copyright (C) Internet Systems Consortium, Inc. ("ISC")
    3 #
    4 # This Source Code Form is subject to the terms of the Mozilla Public
    5 # License, v. 2.0. If a copy of the MPL was not distributed with this
    6 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
    7 #
    8 # See the COPYRIGHT file distributed with this work for additional
    9 # information regarding copyright ownership.
   10 ############################################################################
   11 
   12 ############################################################################
   13 # rndc.py
   14 # This module implements the RNDC control protocol.
   15 ############################################################################
   16 
   17 from collections import OrderedDict
   18 import time
   19 import struct
   20 import hashlib
   21 import hmac
   22 import base64
   23 import random
   24 import socket
   25 
   26 
   27 class rndc(object):
   28     """RNDC protocol client library"""
   29     __algos = {'md5':    157,
   30                'sha1':   161,
   31                'sha224': 162,
   32                'sha256': 163,
   33                'sha384': 164,
   34                'sha512': 165}
   35 
   36     def __init__(self, host, algo, secret):
   37         """Creates a persistent connection to RNDC and logs in
   38         host - (ip, port) tuple
   39         algo - HMAC algorithm: one of md5, sha1, sha224, sha256, sha384, sha512
   40                (with optional prefix 'hmac-')
   41         secret - HMAC secret, base64 encoded"""
   42         self.host = host
   43         algo = algo.lower()
   44         if algo.startswith('hmac-'):
   45             algo = algo[5:]
   46         self.algo = algo
   47         self.hlalgo = getattr(hashlib, algo)
   48         self.secret = base64.b64decode(secret)
   49         self.ser = random.randint(0, 1 << 24)
   50         self.nonce = None
   51         self.__connect_login()
   52 
   53     def call(self, cmd):
   54         """Call a RNDC command, all parsing is done on the server side
   55         cmd - a complete string with a command (eg 'reload zone example.com')
   56         """
   57         return dict(self.__command(type=cmd)['_data'])
   58 
   59     def __serialize_dict(self, data, ignore_auth=False):
   60         rv = bytearray()
   61         for k, v in data.items():
   62             if ignore_auth and k == '_auth':
   63                 continue
   64             rv += struct.pack('B', len(k)) + k.encode('ascii')
   65             if type(v) == str:
   66                 rv += struct.pack('>BI', 1, len(v)) + v.encode('ascii')
   67             elif type(v) == bytes:
   68                 rv += struct.pack('>BI', 1, len(v)) + v
   69             elif type(v) == bytearray:
   70                 rv += struct.pack('>BI', 1, len(v)) + v
   71             elif type(v) == OrderedDict:
   72                 sd = self.__serialize_dict(v)
   73                 rv += struct.pack('>BI', 2, len(sd)) + sd
   74             else:
   75                 raise NotImplementedError('Cannot serialize element of type %s'
   76                                           % type(v))
   77         return rv
   78 
   79     def __prep_message(self, *args, **kwargs):
   80         self.ser += 1
   81         now = int(time.time())
   82         data = OrderedDict(*args, **kwargs)
   83 
   84         d = OrderedDict()
   85         d['_auth'] = OrderedDict()
   86         d['_ctrl'] = OrderedDict()
   87         d['_ctrl']['_ser'] = str(self.ser)
   88         d['_ctrl']['_tim'] = str(now)
   89         d['_ctrl']['_exp'] = str(now+60)
   90         if self.nonce is not None:
   91             d['_ctrl']['_nonce'] = self.nonce
   92         d['_data'] = data
   93 
   94         msg = self.__serialize_dict(d, ignore_auth=True)
   95         hash = hmac.new(self.secret, msg, self.hlalgo).digest()
   96         bhash = base64.b64encode(hash)
   97         if self.algo == 'md5':
   98             d['_auth']['hmd5'] = struct.pack('22s', bhash)
   99         else:
  100             d['_auth']['hsha'] = bytearray(struct.pack('B88s',
  101                                              self.__algos[self.algo], bhash))
  102         msg = self.__serialize_dict(d)
  103         msg = struct.pack('>II', len(msg) + 4, 1) + msg
  104         return msg
  105 
  106     def __verify_msg(self, msg):
  107         if self.nonce is not None and msg['_ctrl']['_nonce'] != self.nonce:
  108             return False
  109         if self.algo == 'md5':
  110             bhash = msg['_auth']['hmd5']
  111         else:
  112             bhash = msg['_auth']['hsha'][1:]
  113         if type(bhash) == bytes:
  114             bhash = bhash.decode('ascii')
  115         bhash += '=' * (4 - (len(bhash) % 4))
  116         remote_hash = base64.b64decode(bhash)
  117         my_msg = self.__serialize_dict(msg, ignore_auth=True)
  118         my_hash = hmac.new(self.secret, my_msg, self.hlalgo).digest()
  119         return (my_hash == remote_hash)
  120 
  121     def __command(self, *args, **kwargs):
  122         msg = self.__prep_message(*args, **kwargs)
  123         sent = self.socket.send(msg)
  124         if sent != len(msg):
  125             raise IOError("Cannot send the message")
  126 
  127         header = self.socket.recv(8)
  128         if len(header) != 8:
  129             # What should we throw here? Bad auth can cause this...
  130             raise IOError("Can't read response header")
  131 
  132         length, version = struct.unpack('>II', header)
  133         if version != 1:
  134             raise NotImplementedError('Wrong message version %d' % version)
  135 
  136         # it includes the header
  137         length -= 4
  138         data = self.socket.recv(length, socket.MSG_WAITALL)
  139         if len(data) != length:
  140             raise IOError("Can't read response data")
  141 
  142         if type(data) == str:
  143             data = bytearray(data)
  144         msg = self.__parse_message(data)
  145         if not self.__verify_msg(msg):
  146             raise IOError("Authentication failure")
  147 
  148         return msg
  149 
  150     def __connect_login(self):
  151         self.socket = socket.create_connection(self.host)
  152         self.nonce = None
  153         msg = self.__command(type='null')
  154         self.nonce = msg['_ctrl']['_nonce']
  155 
  156     def __parse_element(self, input):
  157         pos = 0
  158         labellen = input[pos]
  159         pos += 1
  160         label = input[pos:pos+labellen].decode('ascii')
  161         pos += labellen
  162         type = input[pos]
  163         pos += 1
  164         datalen = struct.unpack('>I', input[pos:pos+4])[0]
  165         pos += 4
  166         data = input[pos:pos+datalen]
  167         pos += datalen
  168         rest = input[pos:]
  169 
  170         if type == 1:         # raw binary value
  171             return label, data, rest
  172         elif type == 2:       # dictionary
  173             d = OrderedDict()
  174             while len(data) > 0:
  175                 ilabel, value, data = self.__parse_element(data)
  176                 d[ilabel] = value
  177             return label, d, rest
  178         # TODO type 3 - list
  179         else:
  180             raise NotImplementedError('Unknown element type %d' % type)
  181 
  182     def __parse_message(self, input):
  183         rv = OrderedDict()
  184         hdata = None
  185         while len(input) > 0:
  186             label, value, input = self.__parse_element(input)
  187             rv[label] = value
  188         return rv