From 268605560d104122a47089dfc2f6cd51645f9335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Old=C5=99ich=20Jedli=C4=8Dka?= Date: Fri, 18 May 2018 18:46:48 +0200 Subject: [PATCH] Initial commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oldřich Jedlička --- .gitignore | 8 + Dockerfile | 37 +++ Dockerfile.development | 45 +++ LICENSE.txt | 8 + MANIFEST.in | 5 + README.md | 114 ++++++++ requirements.txt | 5 + setup.cfg | 12 + setup.py | 82 ++++++ subregsim.conf.sample | 29 ++ subregsim/__init__.py | 0 subregsim/__main__.py | 647 +++++++++++++++++++++++++++++++++++++++++ 12 files changed, 992 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Dockerfile.development create mode 100644 LICENSE.txt create mode 100644 MANIFEST.in create mode 100644 README.md create mode 100644 requirements.txt create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 subregsim.conf.sample create mode 100644 subregsim/__init__.py create mode 100644 subregsim/__main__.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..daf1c38 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/.project +/.pydevproject +/SubregSimulator.egg-info/ +*.pyc +*.crt +*.pem +*.key +subregsim.conf diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9fcb825 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ +# Dockerfile for production (sources taken from Git) +# 1. Build as: docker build -t subregsim:latest . +# 2. Prepare configuration file and certificates (both optional) - see argument "-c" in next step +# 3. Run as: docker run --rm -it -v $PWD/server-certificate.crt:/config/server-certificate.crt -v $PWD/server-certificate.key:/config/server-certificate.key -v $PWD/subregsim.conf:/config/subregsim.conf subregsim:latest -c /config/subregsim.conf + +# Base image +FROM alpine:edge as base + +RUN mkdir /python +ENV PYTHONUSERBASE=/python +ENV PATH="$PYTHONUSERBASE/bin:$PATH" + +RUN apk add --no-cache python3~3.6 py3-openssl + +# Build phase +FROM base as build + +RUN mkdir /build +WORKDIR /build + +RUN apk add --no-cache git py3-setuptools && \ + pip3 install --upgrade pip + +RUN git clone -q https://github.com/oldium/subregsim.git && \ + cd subregsim && \ + pip3 install --user -r requirements.txt && \ + pip3 install --user . + +# Final phase +FROM base + +EXPOSE 80 443 + +COPY --from=build /python /python + +ENTRYPOINT ["/python/bin/subregsim"] +CMD ["--help"] \ No newline at end of file diff --git a/Dockerfile.development b/Dockerfile.development new file mode 100644 index 0000000..f9aa4b7 --- /dev/null +++ b/Dockerfile.development @@ -0,0 +1,45 @@ +# Docker file for development (sources taken from the current directory) +# 1. Build as: docker build -t subregsim:develop-latest -f Dockerfile.development . +# 2. Prepare configuration file and certificates (both optional) - see argument "-c" in next step +# 3. Run as: docker run --rm -it -v $PWD/server-certificate.crt:/config/server-certificate.crt -v $PWD/server-certificate.key:/config/server-certificate.key -v $PWD/subregsim.conf:/config/subregsim.conf -v $PWD/.:/source subregsim:develop-latest -c /config/subregsim.conf + +# Base image +FROM alpine:edge as base + +RUN mkdir /python +ENV PYTHONUSERBASE=/python +ENV PATH="$PYTHONUSERBASE/bin:$PATH" + +RUN apk add --no-cache python3~3.6 py3-openssl + +# Build phase +FROM base as build + +RUN mkdir /build +WORKDIR /build + +RUN apk add --no-cache git py3-setuptools && \ + pip3 install --upgrade pip + +RUN mkdir /source +WORKDIR /source + +COPY requirements.txt /source/ +RUN cd /source && pip3 install --user -r requirements.txt + +COPY setup.py setup.cfg README.md /source/ +COPY ./subregsim /source/subregsim +RUN pip3 install --user -e . + +# Final phase +FROM base + +EXPOSE 80 443 + +COPY --from=build /python /python + +RUN mkdir /source +VOLUME ["/source"] + +ENTRYPOINT ["/python/bin/subregsim"] +CMD ["--help"] diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..2b7e81d --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,8 @@ +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License version 3 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..9f98b43 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,5 @@ +# Include the README +include *.md + +# Include the license file +include LICENSE.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..bcb1c07 --- /dev/null +++ b/README.md @@ -0,0 +1,114 @@ +# Simple Subreg.cz API simulator + +The simulator of the Subreg.cz API implements just few methods fulfilling +needs of the Python [lexicon][lexicon] library. + +The home page of the project is [here][subregsim-home]. + +[lexicon]: https://github.com/AnalogJ/lexicon +[subregsim-home]: https://github.com/oldium/subregsim + +## Installation + +Fetch the sources: + +``` +git clone -q https://github.com/oldium/subregsim.git +cd subregsim +``` + +Install dependencies: + +``` +pip install -r requirements.txt +``` + +Install subregsim Python package, including the `subregsim` binary (like in +`Dockerfile`): + +``` +pip install . +``` + +or in editable mode (like in `Dockerfile.development`): + +``` +pip install -e . +``` + + +## Configuration + +### Basic Setup + +The configuration can be supplied in two ways: + +1. See `subregsim.conf.sample`, copy it to `subregsim.conf` and change it to + suite your needs. Use `-c subregsim.conf` argument to `subregsim`. + +2. All configuration options can be supplied on command-line. + +### SSL Setup + +#### Local Certificate Authority + +Generate self-signed certificate for your Certificate Authority (use your own `subj` string): + +``` +openssl req -x509 -newkey rsa:4096 -nodes -keyout test-ca.key -sha256 -days 1825 -subj "/C=GB/ST=London/L=London/O=Global Security/OU=IT Department/CN=Test System CA" -out test-ca.csr +``` + +Now you have file `test-ca.key` with private key of Certificate Authority and +`test-ca.crt` with certificate. You can now import the `test-ca.crt` +file into your test system. + +#### Domain Certificate + +Now generate domain certificate, replace `example.com` with your domain (and use your own `subj` string): + +``` +openssl req -newkey rsa:4096 -nodes -keyout server-certificate.key -subj "/C=GB/ST=London/L=London/O=Global Security/OU=IT Department/CN=example.com" -out server-certificate.csr +``` + +The file `server-certificate.key` is the private key, the file `server-certificate.csr` is certificate signing request. + +#### Signed Domain Certificate + +Now sign the request with your Certificate Authority: + +``` +openssl x509 -req -in server-certificate.csr -CA test-ca.pem -CAkey test-ca.key -CAcreateserial -out server-certificate.crt -days 1825 -sha256 +``` + +The file `server-certificate.crt` (together with `server-certificate.key`) can now be used by the test server. + +## Docker + +### Production image + +Build from sources (optional step - you can use official image): + +``` +docker build -t subregsim:latest . +``` + +Run as (uses `subregsim.conf` and generated certificates): + +``` +docker run --rm -it -v $PWD/server-certificate.crt:/config/server-certificate.crt -v $PWD/server-certificate.key:/config/server-certificate.key -v $PWD/subregsim.conf:/config/subregsim.conf subregsim:latest -c /config/subregsim.conf +``` + +### Development image + +Build from sources: + +``` +docker build -t subregsim:develop-latest -f Dockerfile.development . +``` + +Run as (uses `subregsim.conf` and generated certificates): + +``` +docker run --rm -it -v $PWD/server-certificate.crt:/config/server-certificate.crt -v $PWD/server-certificate.key:/config/server-certificate.key -v $PWD/subregsim.conf:/config/subregsim.conf -v $PWD/.:/source subregsim:develop-latest -c /config/subregsim.conf +``` + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4e47856 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +# Package requirements + +future +git+https://github.com/oldium/pysimplesoap.git@fix-typeerror#egg=PySimpleSOAP +configargparse diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..89f24c7 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,12 @@ +[metadata] +# This includes the license file in the wheel. +license_file = LICENSE.txt + +[bdist_wheel] +# This flag says to generate wheels that support both Python 2 and Python +# 3. If your code will not run unchanged on both Python 2 and 3, you will +# need to generate separate wheels for each Python version that you +# support. Removing this line (or setting universal to 0) will prevent +# bdist_wheel from trying to make a universal wheel. For more see: +# https://packaging.python.org/tutorials/distributing-packages/#wheels +universal=1 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..5c1d3d1 --- /dev/null +++ b/setup.py @@ -0,0 +1,82 @@ +"""A setuptools based setup module. + +See: +https://packaging.python.org/en/latest/distributing.html +https://github.com/pypa/sampleproject +""" + +# Always prefer setuptools over distutils +from setuptools import setup, find_packages +# To use a consistent encoding +import codecs +import os +import re + +here = os.path.abspath(os.path.dirname(__file__)) + +# Get the long description from the README file +with codecs.open(os.path.join(here, 'README.md'), encoding='utf-8') as f: + long_description = f.read() + +def read(*parts): + with codecs.open(os.path.join(here, *parts), 'r') as fp: + return fp.read() + +def find_version(*file_paths): + version_file = read(*file_paths) + version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", + version_file, re.M) + if version_match: + return version_match.group(1) + raise RuntimeError("Unable to find version string.") + +setup( + name='SubregSimulator', + version=find_version("subregsim", "__main__.py"), + description='Simple Subreg.cz API simulator', + long_description=long_description, + long_description_content_type='text/markdown', + + url='https://github.com/oldium/subregsim', + + author='Oldřich Jedlička', + author_email='oldium.pro@gmail.com', + + # See https://pypi.org/classifiers/ + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'Topic :: Software Development :: Testing', + 'Topic :: Utilities', + 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + ], + + keywords='subreg api simulator', + + packages=find_packages(exclude=['contrib', 'config', 'docs', 'tests']), + + # See https://packaging.python.org/en/latest/requirements.html + install_requires=['future', + 'PySimpleSOAP', + 'configargparse'], + extras_require={}, + + entry_points={ + 'console_scripts': [ + 'subregsim=subregsim.__main__:main', + ], + }, + + project_urls={ + 'Bug Reports': 'https://github.com/oldium/subregsim/issues', + 'Say Thanks!': 'https://saythanks.io/to/oldium', + 'Source': 'https://github.com/oldium/subregsim/', + }, +) diff --git a/subregsim.conf.sample b/subregsim.conf.sample new file mode 100644 index 0000000..617b365 --- /dev/null +++ b/subregsim.conf.sample @@ -0,0 +1,29 @@ +# Docker configuration, read README.md for details + +# Required simulator user name +username = username + +# Required simulator password +password = password + +# Required simulator domain +domain = example.com + +# Host name or IP address to listen on (use 127.0.0.1 for testing on localhost, +# or 0.0.0.0 to accept any address for testing with Docker) +host = 0.0.0.0 + +# Port to listen on when in HTTP mode +#port = 80 + +# If you want to switch to HTTPS (SSL), uncomment the following line +#ssl = true + +# Port to listen on when in HTTPS (SSL) mode +#ssl-port = 443 + +# SSL server certificate in PEM format (may contain private key) +#ssl-certificate = /config/server-certificate.crt + +# SSL server private key in PEM format (not necessary if present in the certificate file) +#ssl-private-key = /config/server-certificate.key diff --git a/subregsim/__init__.py b/subregsim/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/subregsim/__main__.py b/subregsim/__main__.py new file mode 100644 index 0000000..3bb9200 --- /dev/null +++ b/subregsim/__main__.py @@ -0,0 +1,647 @@ +''' +Subreg.cz API simulator suitable for Python lexicon module. +''' + +from __future__ import (absolute_import, print_function) + +__version__ = "0.1" + +from http.server import HTTPServer +import configargparse +import logging +import pysimplesoap.server as soapserver +import random +import signal +import string +import threading + +log = logging.getLogger(__name__) +logging.basicConfig(level=logging.INFO) + +try: + import ssl + has_ssl = True +except: + has_ssl = False + +class Handler(object): + def __init__(self, username, password, domain): + self.next_id = 1 + self.username = username + self.password = password + self.domain = domain + self.db = [] + self.ssid = None + + def login(self, login, password): + log.info("Login: {}/{}".format(login, password)) + + self.ssid = None + + if login != self.username or password != self.password: + return { + "response": { + "status": "error", + "error": { + "errormsg": "Incorrect login or password", + "errorcode": { + "major": 500, + "minor": 104 + } + } + } + } + + self.ssid = "".join(random.SystemRandom().choice(string.digits + string.ascii_lowercase) for _ in range(32)) + + return { + "response": { + "status": "ok", + "data": { + "ssid": self.ssid + } + } + } + + def domains_list(self, ssid): + if ssid != self.ssid: + return { + "response": { + "status": "error", + "error": { + "errormsg": "You are not logged", + "errorcode": { + "major": 500, + "minor": 101 + } + } + } + } + + return { + "response": { + "status": "ok", + "data": { + "count": "1", + "domains": [{ + "name": self.domain, + "expire": "2023-10-20", + "autorenew": 0 + }] + } + } + } + + def get_dns_zone(self, ssid, domain): + if ssid != self.ssid: + return { + "response": { + "status": "error", + "error": { + "errormsg": "You are not logged", + "errorcode": { + "major": 500, + "minor": 101 + } + } + } + } + + if domain != self.domain: + return { + "response": { + "status": "error", + "error": { + "errormsg": "Invalid domain", + "errorcode": { + "major": 524, + "minor": 1009 + } + } + } + } + + if len(self.db) > 0: + return { + "response": { + "status": "ok", + "data": { + "domain": domain, + "records": self.db + } + } + } + else: + return { + "response": { + "status": "ok", + "data": { + "domain": domain + } + } + } + + def add_dns_record(self, ssid, domain, record): + if ssid != self.ssid: + return { + "response": { + "status": "error", + "error": { + "errormsg": "You are not logged", + "errorcode": { + "major": 500, + "minor": 101 + } + } + } + } + + if domain != self.domain: + return { + "response": { + "status": "error", + "error": { + "errormsg": "Invalid domain", + "errorcode": { + "major": 524, + "minor": 1009 + } + } + } + } + + if record.get("type", None) not in ["A", "AAAA", "CNAME", "MX", "TXT", "SPF", "SRV", "NS", "TLSA", "CAA", "SSHFP"]: + return { + "response": { + "status": "error", + "error": { + "errormsg": "Unknown record type", + "errorcode": { + "major": 524, + "minor": 1007 + } + } + } + } + + if "name" not in record: + return { + "response": { + "status": "error", + "error": { + "errormsg": "Invalid domain name in record", + "errorcode": { + "major": 524, + "minor": 1006 + } + } + } + } + + if record["type"] == "CNAME" and any(found["name"] == record["name"] and + found["type"] == record["type"] and + found["content"] == record["content"] for found in self.db): + return { + "response": { + "status": "error", + "error": { + "errormsg": "Cannot create CNAME, where another record already exists", + "errorcode": { + "major": 524, + "minor": 1008 + } + } + } + } + + new_record = dict(record) + + new_record["id"] = self.next_id + self.next_id += 1 + + if 'ttl' not in new_record: + new_record['ttl'] = 600 + if 'prio' not in new_record: + new_record['prio'] = 0 + + self.db.append(new_record) + + return { + "response": { + "status": "ok" + } + } + + def modify_dns_record(self, ssid, domain, record): + if ssid != self.ssid: + return { + "response": { + "status": "error", + "error": { + "errormsg": "You are not logged", + "errorcode": { + "major": 500, + "minor": 101 + } + } + } + } + + if domain != self.domain: + return { + "response": { + "status": "error", + "error": { + "errormsg": "Invalid domain", + "errorcode": { + "major": 524, + "minor": 1009 + } + } + } + } + + if "id" not in record: + return { + "response": { + "status": "error", + "error": { + "errormsg": "Missing or empty value for record ID", + "errorcode": { + "major": 524, + "minor": 1002 + } + } + } + } + + if "type" in record and record["type"] not in ["A", "AAAA", "CNAME", "MX", "TXT", "SPF", "SRV", "NS", "TLSA", "CAA", "SSHFP"]: + return { + "response": { + "status": "error", + "error": { + "errormsg": "Unknown record type", + "errorcode": { + "major": 524, + "minor": 1007 + } + } + } + } + + found_items = [item for item in self.db if item["id"] == record["id"]] + if len(found_items) == 0: + return { + "response": { + "status": "error", + "error": { + "errormsg": "Record does not exist", + "errorcode": { + "major": 524, + "minor": 1003 + } + } + } + } + + found_items[0].update(record) + + return { + "response": { + "status": "ok" + } + } + + def delete_dns_record(self, ssid, domain, record): + if ssid != self.ssid: + return { + "response": { + "status": "error", + "error": { + "errormsg": "You are not logged", + "errorcode": { + "major": 500, + "minor": 101 + } + } + } + } + + if domain != self.domain: + return { + "response": { + "status": "error", + "error": { + "errormsg": "Invalid domain", + "errorcode": { + "major": 524, + "minor": 1009 + } + } + } + } + + if "id" not in record: + return { + "response": { + "status": "error", + "error": { + "errormsg": "Missing or empty value for record ID", + "errorcode": { + "major": 524, + "minor": 1002 + } + } + } + } + + if "type" in record and record["type"] not in ["A", "AAAA", "CNAME", "MX", "TXT", "SPF", "SRV", "NS", "TLSA", "CAA", "SSHFP"]: + return { + "response": { + "status": "error", + "error": { + "errormsg": "Unknown record type", + "errorcode": { + "major": 524, + "minor": 1007 + } + } + } + } + + before_length = len(self.db) + self.db = [item for item in self.db if item["id"] != record["id"]] + if before_length == len(self.db): + return { + "response": { + "status": "error", + "error": { + "errormsg": "Record does not exist", + "errorcode": { + "major": 524, + "minor": 1003 + } + } + } + } + + return { + "response": { + "status": "ok" + } + } + + ERROR_INFO_TYPES = { + "errormsg": str, + "errorcode": { + "major": int, + "minor": int + } + } + + LOGIN_REQUEST_TYPES = { + "login": str, + "password": str + } + + LOGIN_RESPONSE_TYPES = { + "response": { + "status": str, + "data": { + "ssid": str + }, + "error": ERROR_INFO_TYPES, + } + } + + GET_DNS_ZONE_REQUEST_TYPES = { + "ssid": str, + "domain": str + } + + GET_DNS_ZONE_RESPONSE_TYPES = { + "response": { + "status": str, + "data": { + "domain": str, + "records": [{ + "id": int, + "name": str, + "type": str, + "content": str, + "prio": int, + "ttl": int + }] + }, + "error": ERROR_INFO_TYPES, + } + } + + ADD_DNS_RECORD_REQUEST_TYPES = { + "ssid": str, + "domain": str, + "record": { + "name": str, + "type": str, + "content": str, + "prio": int, + "ttl": int + } + } + + ADD_DNS_RECORD_RESPONSE_TYPES = { + "response": { + "status": str, + "data": {}, + "error": ERROR_INFO_TYPES, + } + } + + MODIFY_DNS_RECORD_REQUEST_TYPES = { + "ssid": str, + "domain": str, + "record": { + "id": int, + "type": str, + "content": str, + "prio": int, + "ttl": int + } + } + + MODIFY_DNS_RECORD_RESPONSE_TYPES = { + "response": { + "status": str, + "data": {}, + "error": ERROR_INFO_TYPES, + } + } + + DELETE_DNS_RECORD_REQUEST_TYPES = { + "ssid": str, + "domain": str, + "record": { + "id": int + } + } + + DELETE_DNS_RECORD_RESPONSE_TYPES = { + "response": { + "status": str, + "data": {}, + "error": ERROR_INFO_TYPES, + } + } + + DOMAINS_LIST_REQUEST_TYPES = { + "ssid": str + } + + DOMAINS_LIST_RESPONSE_TYPES = { + "response": { + "status": str, + "data": { + "count": int, + "domains": [{ + "name": str, + "expire": str, + "autorenew": int + }] + }, + "error": ERROR_INFO_TYPES, + } + } + +def parse_command_line(): + parser = configargparse.ArgumentParser(description="Subreg.cz API simulator suitable for Python lexicon module.") + required_group = parser.add_argument_group("required arguments") + required_group.add_argument("--domain", required=True, env_var="SUBREGSIM_DOMAIN", help="simulated domain name") + required_group.add_argument("--username", required=True, env_var="SUBREGSIM_USERNAME", help="expected login user name by the server") + required_group.add_argument("--password", required=True, env_var="SUBREGSIM_PASSWORD", help="expected login password by the server") + + optional_group = parser.add_argument_group("optional arguments") + optional_group.add_argument("-c", "--config", metavar="FILE", is_config_file=True, help="configuration file for all options (can be specified only on command-line)") + optional_group.add_argument("--host", default="localhost", env_var="SUBREGSIM_HOST", help="server listening host name or IP address (defaults to localhost)") + optional_group.add_argument("--port", type=int, default=80, env_var="SUBREGSIM_PORT", help="server listening port (defaults to 80)") + optional_group.add_argument("--url", default="http://localhost:8008/", env_var="SUBREGSIM_URL", help="API root URL for WSDL generation (defaults to http://localhost:8008/)") + + if has_ssl: + ssl_group = parser.add_argument_group("optional SSL arguments") + ssl_group.add_argument("--ssl", dest="ssl", action="store_true", default=False, env_var="SUBREGSIM_SSL", help="enables SSL on server listening port") + ssl_group.add_argument("--ssl-port", dest="ssl_port", type=int, default=443, metavar="PORT", env_var="SUBREGSIM_SSL_PORT", help="server SSL listening port (defaults to 443)") + ssl_group.add_argument("--ssl-certificate", dest="ssl_certificate", metavar="PEM-FILE", default=None, env_var="SUBREGSIM_SSL_CERTIFICATE", help="specifies server certificate") + ssl_group.add_argument("--ssl-private-key", dest="ssl_private_key", metavar="PEM-FILE", default=None, env_var="SUBREGSIM_SSL_PRIVATE_KEY", help="specifies server privatey key (not necessary if private key is part of certificate file)") + + parsed = parser.parse_args() + + if has_ssl and parsed.ssl and ('ssl_certificate' not in parsed or not parsed.ssl_certificate): + parser.error("--ssl requires --ssl-certificate") + + return parsed + +class TerminationHandler(object): + def __init__(self, httpd): + self.httpd = httpd + signal.signal(signal.SIGINT, self.terminate) + signal.signal(signal.SIGTERM, self.terminate) + + def terminate(self, signum, frame): + if signum == signal.SIGTERM: + log.info("Shutting down due to termination request") + else: + log.info("Shutting down due to interrupt request") + self.httpd.shutdown() + +class SoapHttpServer(HTTPServer): + def __init__(self, server_address, is_ssl): + HTTPServer.__init__(self, server_address, soapserver.SOAPHandler) + self.is_ssl = is_ssl + + def run(self): + try: + self.serve_forever() + except: + if self.is_ssl: + log.exception("Error running HTTPS server") + else: + log.exception("Error running HTTP server") + finally: + try: + self.server_close() + except: + pass + + def handle_error(self, request, client_address): + if self.is_ssl: + log.exception("HTTPS request handling failed") + else: + log.exception("HTTP request handling failed") + +class TerminateException(Exception): + pass + +def main(): + + arguments = parse_command_line() + + handler = Handler(arguments.username, arguments.password, arguments.domain) + + dispatcher = soapserver.SoapDispatcher( + name = "SubregCz", + location = arguments.url, + action = arguments.url, + namespace = "http://subreg.cz/wsdl", + prefix = "nsl", + documentation = "Subreg.CZ domain name services", + ns = True + ) + + dispatcher.register_function("Login", handler.login, + args=handler.LOGIN_REQUEST_TYPES, + returns=handler.LOGIN_RESPONSE_TYPES) + dispatcher.register_function("Domains_List", handler.domains_list, + args=handler.DOMAINS_LIST_REQUEST_TYPES, + returns=handler.DOMAINS_LIST_RESPONSE_TYPES) + dispatcher.register_function("Get_DNS_Zone", handler.get_dns_zone, + args=handler.GET_DNS_ZONE_REQUEST_TYPES, + returns=handler.GET_DNS_ZONE_RESPONSE_TYPES) + dispatcher.register_function("Add_DNS_Record", handler.add_dns_record, + args=handler.ADD_DNS_RECORD_REQUEST_TYPES, + returns=handler.ADD_DNS_RECORD_RESPONSE_TYPES) + dispatcher.register_function("Modify_DNS_Record", handler.modify_dns_record, + args=handler.MODIFY_DNS_RECORD_REQUEST_TYPES, + returns=handler.MODIFY_DNS_RECORD_RESPONSE_TYPES) + dispatcher.register_function("Delete_DNS_Record", handler.delete_dns_record, + args=handler.DELETE_DNS_RECORD_REQUEST_TYPES, + returns=handler.DELETE_DNS_RECORD_RESPONSE_TYPES) + + use_ssl = has_ssl and arguments.ssl + + httpd = SoapHttpServer((arguments.host, arguments.port), use_ssl) + + if use_ssl: + log.info("Starting HTTPS server to listen on {}:{}...".format(arguments.host, arguments.ssl_port)) + httpd.socket = ssl.wrap_socket(httpd.socket, + keyfile=arguments.ssl_private_key, + certfile=arguments.ssl_certificate, + server_side=True) + else: + log.info("Starting HTTP server to listen on {}:{}...".format(arguments.host, arguments.port)) + + httpd.dispatcher = dispatcher + + TerminationHandler(httpd) + + httpd_thread = threading.Thread(target=httpd.run, name="SOAP Server") + httpd_thread.start() + + while httpd_thread.is_alive(): + httpd_thread.join(timeout=0.5) + +if __name__ == '__main__': + try: + main() + except ssl.SSLError: + log.exception("SSL setup failed, verify that both the private key and certificate are supplied") + except: + log.exception("Program terminated due to exception")