]> gitweb.michael.orlitzky.com - untangle-https-backup.git/blobdiff - src/untangle/untangle.py
src/untangle/untangle.py: use cleaner way of disabling verification.
[untangle-https-backup.git] / src / untangle / untangle.py
index 09c5723c9c11a08a98dc250973716ec081ecdd3a..8ce4fb7f39b3017c49cfdd0295787ac047319862 100644 (file)
@@ -5,7 +5,11 @@ import urllib.parse
 import urllib.request
 
 class Untangle:
-
+    """
+    This class wraps one instance of Untangle. It gets initialized with
+    some configuration information, and then provides the methods to
+    retreive a backup.
+    """
     def __init__(self, s):
         """
         Initialize this Untangle object with a ConfigParser section.
@@ -14,12 +18,14 @@ class Untangle:
         self.host = s['host']
         self.username = s.get('username', 'admin')
         self.password = s['password']
-        self.version = int(s.get('version', '11'))
+        self.timeout = s.get('timeout', 300)
         self.base_url = 'https://' + self.host + '/' # This never changes
 
         # Sanity check the numerical version.
-        if self.version not in [9, 11]:
-            msg =  'Invalid version "' + str(self.version) + '" '
+        self.version = s.get('version', '14.1')
+        if self.version not in ['9', '10', '11', '12', '13', '13.1',
+                                '14', '14.1']:
+            msg =  'Invalid version "' + self.version + '" '
             msg += 'in section "' + s.name + '"'
             raise configparser.ParsingError(msg)
 
@@ -34,7 +40,25 @@ class Untangle:
             msg += 'in section "' + s.name + '"'
             raise configparser.ParsingError(msg)
 
-        #
+        # Sanity check the integer "timeout" parameter. We want to
+        # bail if either the given parameter is not an integer, or if
+        # it's negative. To handle both at the same time, we try to
+        # parse an integer...
+        timeout = s.get('timeout', 300)
+        try:
+            self.timeout = int(timeout)
+        except:
+            # ...and set self.timeout to a negative value if we can't...
+            self.timeout = -1
+
+        # Now we check to see if the timeout value is negative. That
+        # will happen if it was either negative to begin with, or
+        # non-integer (and we set it negative).
+        if self.timeout < 0:
+            msg =  'Invalid value "' + timeout + '" for timeout '
+            msg += 'in section "' + s.name + '"'
+            raise configparser.ParsingError(msg)
+
         # Finally, create a URL opener to make HTTPS requests.
         #
         # First, create a cookie jar that we'll attach to our URL
@@ -44,10 +68,10 @@ class Untangle:
 
         # SSL mumbo jumbo to make it ignore the certificate's hostname
         # when verify_cert = False.
-        if self.verify_cert:
-            ssl_ctx = ssl.create_default_context()
-        else:
-            ssl_ctx = ssl._create_unverified_context()
+        ssl_ctx = ssl.create_default_context()
+        if not self.verify_cert:
+            ssl_ctx.check_hostname = False
+            ssl_ctx.verify_mode = ssl.CERT_NONE
 
         https_handler = urllib.request.HTTPSHandler(context=ssl_ctx)
 
@@ -56,35 +80,85 @@ class Untangle:
         self.opener = urllib.request.build_opener(https_handler, cookie_proc)
 
 
+    def open(self, url, data=None):
+        """
+        A wrapper around ``self.opener.open()`` that uses our
+        configurable socket "timeout" value. Without the timeout
+        parameter, it seems we can wait forever in some corner cases.
+        """
+        return self.opener.open(url, data, self.timeout)
+
+
     def login(self):
+        """
+        Perform the HTTPS request to log in to the Untangle web admin
+        UI. The resulting session cookie is stored by our ``self.opener``.
+        """
         login_path = 'auth/login?url=/setup/welcome.do&realm=Administrator'
         url = self.base_url + login_path
         post_vars = {'username': self.username, 'password': self.password }
         post_data = urllib.parse.urlencode(post_vars).encode('ascii')
-        self.opener.open(url, post_data)
+        self.open(url, post_data)
 
 
     def get_backup(self):
-        if self.version == 9:
+        """
+        Version-agnostic get-me-a-backup method. Dispatches to the
+        actual implementation based on ``self.version``.
+        """
+        if self.version == '9':
             return self.get_backup_v9()
-        elif self.version == 11:
-            return self.get_backup_v11()
+        elif self.version in ['10', '11', '12', '13']:
+            # The procedure for v11, v12, or v13 is the same as for v10.
+            return self.get_backup_v10()
+        elif self.version in  ['13.1', '14', '14.1']:
+            # But the minor update v13.1 moved the backup URL.
+            return self.get_backup_v13_1()
+        else:
+            raise ValueError('unknown version %s' % self.version)
 
 
     def get_backup_v9(self):
+        """
+        Retrieve a backup from Untangle version 9. This requires two
+        requests; the first just hits the page, and the second actually
+        retrieves the backup file.
+
+        Returns the binary HTTPS response (i.e. the file).
+        """
         url = self.base_url + '/webui/backup'
         post_vars = {'action': 'requestBackup'}
         post_data = urllib.parse.urlencode(post_vars).encode('ascii')
-        self.opener.open(url, post_data)
+        self.open(url, post_data)
 
         url = self.base_url + 'webui/backup?action=initiateDownload'
-        with self.opener.open(url) as response:
+        with self.open(url) as response:
+            return response.read()
+
+
+    def get_backup_v10(self):
+        """
+        Retrieve a backup from Untangle version 10.
+
+        Returns the binary HTTPS response (i.e. the file).
+        """
+        url = self.base_url + '/webui/download'
+        post_vars = {'type': 'backup'}
+        post_data = urllib.parse.urlencode(post_vars).encode('ascii')
+        with self.open(url, post_data) as response:
             return response.read()
 
 
-    def get_backup_v11(self):
-        url = self.base_url + '/webui/download?type=backup'
+    def get_backup_v13_1(self):
+        """
+        Retrieve a backup from Untangle version 13.1. This
+        differs from v13 (and v12, and v11,...) by only one word
+        in the URL: "webui" becomes "admin".
+
+        Returns the binary HTTPS response (i.e. the file).
+        """
+        url = self.base_url + '/admin/download'
         post_vars = {'type': 'backup'}
         post_data = urllib.parse.urlencode(post_vars).encode('ascii')
-        with self.opener.open(url, post_data) as response:
+        with self.open(url, post_data) as response:
             return response.read()