You've already forked directdnsonly
Initial project commit
This commit is contained in:
0
src/__init__.py
Normal file
0
src/__init__.py
Normal file
8
src/config/app.yml
Normal file
8
src/config/app.yml
Normal 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
|
||||
16
src/config/app.yml.example
Normal file
16
src/config/app.yml.example
Normal 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
2590
src/config/directdns.log
Normal file
File diff suppressed because it is too large
Load Diff
67
src/db.py
Normal file
67
src/db.py
Normal 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
232
src/directdnsonly.py
Normal 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
0
src/lib/__init__.py
Normal file
18
src/lib/common/__init__.py
Normal file
18
src/lib/common/__init__.py
Normal 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
15
src/lib/db/__init__.py
Normal 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
|
||||
28
src/lib/db/models/__init__.py
Normal file
28
src/lib/db/models/__init__.py
Normal 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
6
src/test-file.py
Normal 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
18
src/test.py
Normal 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')
|
||||
Reference in New Issue
Block a user