Initial project commit

This commit is contained in:
2021-08-28 11:28:23 +12:00
commit af437cfae5
22 changed files with 3442 additions and 0 deletions

0
src/__init__.py Normal file
View File

8
src/config/app.yml Normal file
View File

@@ -0,0 +1,8 @@
environment: ''
proxy_support: true
log_level: debug
log_to: file
proxy_support_base: http://127.0.0.1
server_port: 2222
token_valid_for_days: 30
timezone: Pacific/Auckland

View File

@@ -0,0 +1,16 @@
---
# Port that the server will run on.
server_port: 8080
# Turn Proxy support on. True if behind Traefik/Nginx for example
proxy_support: True
# Proxy support Base URI
proxy_support_base: http://127.0.0.1
# Values accepted are '', production, staging
environment: ''
api_keys:
- key: ZU7f3NogDxIzhfW5tsv9
name: super # Name of user
expires: never # Expiry never for superuser
- key: DhCIZ5yKjVN7ReKVh6Tl
name: jaydeep
expires: 21/11/2020, 10:56:24

2590
src/config/directdns.log Normal file

File diff suppressed because it is too large Load Diff

67
src/db.py Normal file
View File

@@ -0,0 +1,67 @@
import re
from sqlalchemy import create_engine
def connect(hostname, sid, username, password, port=1521) -> object:
host = hostname
port = port
sid = sid
user = username
password = password
sid = cx_Oracle.makedsn(host, port, sid=sid)
connection_string = 'oracle://{user}:{password}@{sid}'.format(
user=user,
password=password,
sid=sid
)
engine = create_engine(
connection_string,
convert_unicode=False,
pool_recycle=10,
pool_size=50,
)
return engine
def make_address(flt_num: str, street_name: str, area: str, address_string: str) -> str:
"""
Take input parameters from GIS Data and returns Address as string
:rtype: str
"""
street_number = nv(flt_num).upper().strip() # Make the street number upper case and strip whitespace
street_name = nv(street_name).upper().strip() # Make the street name upper case and strip whitespace
town_name = nv(area)
if area != 'None' and \
area is not None and \
area != '':
town_name = nv(area).upper().strip() # Make the area upper case and strip whitespace
else:
town_name = nv(lookup_town_in_string(address_string)) # Get the town from address string
# Assemble the address string
full_address = street_number + " " + street_name + " " + town_name
return full_address
def lookup_town_in_string(address: str) -> str:
for p in street_type_lookup():
first_word = r"^(\w+)\s?((?!\\1)([\w]+)?)(?:\s+[\d]{4})" # Return First Words
try:
f = address.index(p)
size = len(p)
if f is not None:
m = re.search(first_word, address[f + size::].strip())
if m.group(1) is not None and m.group(2) is not None:
if m.group(1) != m.group(2):
return m.group(1) + ' ' + m.group(2)
else:
return m.group(1)
elif m.group(1) is not None and m.group(2) is None:
return m.group(1)
except ValueError:
pass

232
src/directdnsonly.py Normal file
View File

@@ -0,0 +1,232 @@
import mmap
import cherrypy
from cherrypy import request
from cherrypy._cpnative_server import CPHTTPServer
from pythonjsonlogger import jsonlogger
import logging
import os
import time
import sys
import yaml
import datetime
import lib.common
import lib.db
import lib.db.models
class DaDNS(object):
@cherrypy.expose
def CMD_API_LOGIN_TEST(self, **params):
return 'error=0&text=Login OK&details=none'
@cherrypy.expose
def CMD_API_DNS_ADMIN(self, **params):
applog.debug('Processing Method: ' + request.method)
if request.method == 'POST':
action = request.params.get('action')
decoded_params = None
if action is None:
decoded_params = decode_params(str(request.body.read(), 'utf-8'))
action = decoded_params['action']
zone_file = str(request.body.read(), 'utf-8')
applog.debug(zone_file)
if action == 'delete':
# Domain is being removed from the DNS
hostname = decoded_params['hostname']
domain = decoded_params['select0']
record = session.query(lib.db.models.Domain).filter_by(domain=domain).one()
if record.hostname == hostname:
applog.debug('Hostname matches the original host {}: Delete is allowed'.format('hostname'))
session.delete(record)
applog.info('{} deleted from database')
write_named_include()
if action == 'rawsave':
# DirectAdmin wants to add/update a domain
hostname = request.params.get('hostname')
username = request.params.get('username')
domain = request.params.get('domain')
applog.debug('Domain name to check: ' + domain)
applog.debug('Does zone exist? ' + str(check_zone_exists(str(domain))))
if not check_zone_exists(str(domain)):
applog.debug('Zone is not present in db')
put_zone_index(str(domain), str(hostname), str(username))
write_zone_file(str(domain), zone_file)
else:
# Domain already exists
applog.debug('Zone is present in db')
write_zone_file(str(domain), zone_file)
elif request.method == 'GET':
applog.debug('Action Type: ' + request.params.get('action'))
action = request.params.get('action')
if action == 'exists':
# DirectAdmin is checking whether the domain is in the cluster
if check_zone_exists(request.params.get('domain')):
return 'result: exists=1'
else:
return 'result: exists=0'
def create_zone_index():
# Create an index of all zones present from zone definitions
regex = r"(?<=\")(?P<domain>.*)(?=\"\s)"
with open(zone_index_file, 'w+') as f:
with open(named_conf, 'r') as named_file:
while True:
# read line
line = named_file.readline()
if not line:
# Reached end of file
break
print(line)
hosted_domain = re.search(regex, line).group(0)
f.write(hosted_domain + "\n")
def put_zone_index(zone_name, host_name, user_name):
# add a new zone to index
applog.debug('Placed zone into database.. {}'.format(str(zone_name)))
domain = lib.db.models.Domain(domain=zone_name, hostname=host_name, username=user_name)
session.add(domain)
session.commit()
def write_zone_file(zone_name, data):
# Write the zone to file
applog.debug('Zone Name for write: ' + zone_name)
applog.debug('Zone file to write: \n' + data)
with open(zones_dir + '/' + zone_name + '.db', 'w') as f:
f.write(data)
applog.debug('Zone written to {}'.format(zones_dir + '/' + zone_name + '.db'))
def write_named_include():
applog.debug('Rewrite named zone include...')
domains = session.query(lib.db.models.Domain).all()
with open(named_conf, 'w') as f:
for domain in domains:
applog.debug('Writing zone {} to named.config'.format(domain.domain))
f.write('zone "{}" { type master; file "/etc/pdns/zones/{}.db"; };'
.format(domain.domain,
domain.domain))
def check_parent_domain_owner(zone_name, owner):
applog.debug('Checking if {} is owner of parent in the DB'.format(zone_name))
# check try to find domain name
parent_domain = ".".join(zone_name.split('.')[1:])
domain_exists = session.query(session.query(lib.db.models.Domain).filter_by(domain=parent_domain).exists()).scalar()
if domain_exists:
# domain exists in the db
applog.debug('{} exists in db'.format(parent_domain))
domain_record = session.query(lib.db.models.Domain).filter_by(domain=parent_domain).one()
applog.debug(str(domain_record))
if domain_record.username == owner:
return True
else:
return False
def check_zone_exists(zone_name):
# Check if zone is present in the index
applog.debug('Checking if {} is present in the DB'.format(zone_name))
domain_exists = session.query(session.query(lib.db.models.Domain).filter_by(domain=zone_name).exists()).scalar()
if domain_exists:
return True
else:
return False
def decode_params(payload):
from urllib.parse import parse_qs
response = parse_qs(payload)
params = dict()
for key, val in response.items():
params[key] = val[0]
return params
@cherrypy.expose
@cherrypy.tools.json_out()
def health(self):
# Defaults to 200
return {"Message": "OK!"}
def setup_logging():
os.environ['TZ'] = config['timezone']
time.tzset()
applog = logging.getLogger()
applog.setLevel(level=getattr(logging, config['log_level'].upper()))
if config['log_to'] == 'stdout':
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(level=getattr(logging, config['log_level'].upper()))
formatter = jsonlogger.JsonFormatter(
fmt='%(asctime)s %(levelname)s %(message)s'
)
handler.setFormatter(formatter)
applog.addHandler(handler)
elif config['log_to'] == 'file':
handler = logging.FileHandler('./config/directdns.log')
handler.setLevel(level=getattr(logging, config['log_level'].upper()))
formatter = jsonlogger.JsonFormatter(
fmt='%(asctime)s %(levelname)s %(message)s'
)
handler.setFormatter(formatter)
applog.addHandler(handler)
return applog
if __name__ == '__main__':
app_version = "1.0.0"
if os.path.isfile("/lib/x86_64-linux-gnu/" + "libgcc_s.so.1"):
# Load local library
libgcc_s = ctypes.cdll.LoadLibrary("/lib/x86_64-linux-gnu/" + "libgcc_s.so.1")
# We are about to start our application
with open(r'config/app.yml') as config_file:
config = yaml.load(config_file, Loader=yaml.SafeLoader)
applog = setup_logging()
applog.info('DirectDNS Starting')
applog.info('Timezone is {}'.format(config['timezone']))
applog.info('Get Database Connection')
session = lib.db.connect()
applog.info('Database Connected!')
zones_dir = "/etc/pdns/zones"
named_conf = "/etc/pdns/named.conf"
cherrypy.__version__ = ''
cherrypy._cperror._HTTPErrorTemplate = cherrypy._cperror._HTTPErrorTemplate.replace(
'Powered by <a href="http://www.cherrypy.org">CherryPy %(version)s</a>\n', '%(version)s')
userpassdict = {'test': 'test'}
checkpassword = cherrypy.lib.auth_basic.checkpassword_dict(userpassdict)
cherrypy.config.update({
'server.socket_host': '0.0.0.0',
'server.socket_port': config['server_port'],
'tools.proxy.on': config['proxy_support'],
'tools.proxy.base': config['proxy_support_base'],
'tools.auth_basic.on': True,
'tools.auth_basic.realm': 'dadns',
'tools.auth_basic.checkpassword': checkpassword,
'tools.response_headers.on': True,
'tools.response_headers.headers': [('Server', 'DirectDNS v' + app_version)],
'environment': config['environment']
})
# cherrypy.log.error_log.propagate = False
# cherrypy.log.access_log.propagate = False
if not lib.common.check_if_super_user_exists(session):
password_str = lib.common.get_random_string(35)
applog.info('Creating superuser account: {}'.format('super'))
applog.info('Password: {}'.format(password_str))
superuser = lib.db.models.Key(key=password_str, name='super', service='*')
session.add(superuser)
session.commit()
else:
applog.info('Superuser account already exists: skipping creation')
cherrypy.quickstart(DaDNS())

0
src/lib/__init__.py Normal file
View File

View File

@@ -0,0 +1,18 @@
import random
import string
import lib.db.models
def check_if_super_user_exists(session):
exists = session.query(session.query(lib.db.models.Key).filter_by(name='super').exists()).scalar()
return exists
def check_if_domain_exists(session):
pass
def get_random_string(length):
letters_and_digits = string.ascii_letters + string.digits
result_str = ''.join(random.choice(letters_and_digits) for i in range(length))
return result_str

15
src/lib/db/__init__.py Normal file
View File

@@ -0,0 +1,15 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
import datetime
Base = declarative_base()
def connect():
# Start SQLite engine
engine = create_engine('sqlite:///./config/keys.db', connect_args={'check_same_thread': False})
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
return session

View File

@@ -0,0 +1,28 @@
from lib.db import Base
from sqlalchemy import Column, Integer, String, DateTime
class Key(Base):
__tablename__ = "keys"
id = Column(Integer, primary_key=True)
key = Column(String, unique=True)
name = Column(String)
expires = Column(DateTime)
service = Column(String)
def __repr__(self):
return "<Key(key='%s', name='%s', expires='%s', service='%s')>" % (
self.key, self.name, self.expires, self.service)
class Domain(Base):
__tablename__ = "domains"
id = Column(Integer, primary_key=True)
domain = Column(String, unique=True)
hostname = Column(String)
username = Column(String)
def __repr__(self):
return "<Domain(id='%s', domain='%s', hostname='%s', username='%s')>" % (
self.id, self.domain, self.hostname, self.username
)

6
src/test-file.py Normal file
View File

@@ -0,0 +1,6 @@
import yaml
data = {'ZU7f3NogDxIzhfW5tsv9' : {'name': 'super',
'expires': '11/21/2020, 10:56:24'}}
print(yaml.dump(data))

18
src/test.py Normal file
View File

@@ -0,0 +1,18 @@
import datetime
import lib.common
import lib.db
import lib.db.models
new_expiry_date = datetime.datetime.now() + datetime.timedelta(int(10))
session = lib.db.connect()
if not lib.common.check_if_super_user_exists(session):
password_str = lib.common.get_random_string(20)
print('Creating superuser account: {}'.format('super'))
print('Password: {}'.format(password_str))
super = lib.db.models.Key(key=password_str, name='super', service='*')
session.add(super)
session.commit()
else:
print('Superuser account already exists: skipping creation')