]> gitweb.michael.orlitzky.com - untangle-https-backup.git/blob - src/untangle/untangle.py
src/untangle/untangle.py: use cleaner way of disabling verification.
[untangle-https-backup.git] / src / untangle / untangle.py
1 import configparser
2 import http.cookiejar
3 import ssl
4 import urllib.parse
5 import urllib.request
6
7 class Untangle:
8 """
9 This class wraps one instance of Untangle. It gets initialized with
10 some configuration information, and then provides the methods to
11 retreive a backup.
12 """
13 def __init__(self, s):
14 """
15 Initialize this Untangle object with a ConfigParser section.
16 """
17 self.name = s.name
18 self.host = s['host']
19 self.username = s.get('username', 'admin')
20 self.password = s['password']
21 self.timeout = s.get('timeout', 300)
22 self.base_url = 'https://' + self.host + '/' # This never changes
23
24 # Sanity check the numerical version.
25 self.version = s.get('version', '14.1')
26 if self.version not in ['9', '10', '11', '12', '13', '13.1',
27 '14', '14.1']:
28 msg = 'Invalid version "' + self.version + '" '
29 msg += 'in section "' + s.name + '"'
30 raise configparser.ParsingError(msg)
31
32 # Sanity check the boolean verify_cert parameter.
33 vc = s.get('verify_cert', 'False')
34 if vc == 'True':
35 self.verify_cert = True
36 elif vc == 'False':
37 self.verify_cert = False
38 else:
39 msg = 'Invalid value "' + vc + '" for verify_cert '
40 msg += 'in section "' + s.name + '"'
41 raise configparser.ParsingError(msg)
42
43 # Sanity check the integer "timeout" parameter. We want to
44 # bail if either the given parameter is not an integer, or if
45 # it's negative. To handle both at the same time, we try to
46 # parse an integer...
47 timeout = s.get('timeout', 300)
48 try:
49 self.timeout = int(timeout)
50 except:
51 # ...and set self.timeout to a negative value if we can't...
52 self.timeout = -1
53
54 # Now we check to see if the timeout value is negative. That
55 # will happen if it was either negative to begin with, or
56 # non-integer (and we set it negative).
57 if self.timeout < 0:
58 msg = 'Invalid value "' + timeout + '" for timeout '
59 msg += 'in section "' + s.name + '"'
60 raise configparser.ParsingError(msg)
61
62 # Finally, create a URL opener to make HTTPS requests.
63 #
64 # First, create a cookie jar that we'll attach to our URL
65 # opener thingy.
66 cj = http.cookiejar.CookieJar()
67 cookie_proc = urllib.request.HTTPCookieProcessor(cj)
68
69 # SSL mumbo jumbo to make it ignore the certificate's hostname
70 # when verify_cert = False.
71 ssl_ctx = ssl.create_default_context()
72 if not self.verify_cert:
73 ssl_ctx.check_hostname = False
74 ssl_ctx.verify_mode = ssl.CERT_NONE
75
76 https_handler = urllib.request.HTTPSHandler(context=ssl_ctx)
77
78 # Now Create a URL opener, and tell it to use our cookie jar
79 # and SSL context. We keep this around for future requests.
80 self.opener = urllib.request.build_opener(https_handler, cookie_proc)
81
82
83 def open(self, url, data=None):
84 """
85 A wrapper around ``self.opener.open()`` that uses our
86 configurable socket "timeout" value. Without the timeout
87 parameter, it seems we can wait forever in some corner cases.
88 """
89 return self.opener.open(url, data, self.timeout)
90
91
92 def login(self):
93 """
94 Perform the HTTPS request to log in to the Untangle web admin
95 UI. The resulting session cookie is stored by our ``self.opener``.
96 """
97 login_path = 'auth/login?url=/setup/welcome.do&realm=Administrator'
98 url = self.base_url + login_path
99 post_vars = {'username': self.username, 'password': self.password }
100 post_data = urllib.parse.urlencode(post_vars).encode('ascii')
101 self.open(url, post_data)
102
103
104 def get_backup(self):
105 """
106 Version-agnostic get-me-a-backup method. Dispatches to the
107 actual implementation based on ``self.version``.
108 """
109 if self.version == '9':
110 return self.get_backup_v9()
111 elif self.version in ['10', '11', '12', '13']:
112 # The procedure for v11, v12, or v13 is the same as for v10.
113 return self.get_backup_v10()
114 elif self.version in ['13.1', '14', '14.1']:
115 # But the minor update v13.1 moved the backup URL.
116 return self.get_backup_v13_1()
117 else:
118 raise ValueError('unknown version %s' % self.version)
119
120
121 def get_backup_v9(self):
122 """
123 Retrieve a backup from Untangle version 9. This requires two
124 requests; the first just hits the page, and the second actually
125 retrieves the backup file.
126
127 Returns the binary HTTPS response (i.e. the file).
128 """
129 url = self.base_url + '/webui/backup'
130 post_vars = {'action': 'requestBackup'}
131 post_data = urllib.parse.urlencode(post_vars).encode('ascii')
132 self.open(url, post_data)
133
134 url = self.base_url + 'webui/backup?action=initiateDownload'
135 with self.open(url) as response:
136 return response.read()
137
138
139 def get_backup_v10(self):
140 """
141 Retrieve a backup from Untangle version 10.
142
143 Returns the binary HTTPS response (i.e. the file).
144 """
145 url = self.base_url + '/webui/download'
146 post_vars = {'type': 'backup'}
147 post_data = urllib.parse.urlencode(post_vars).encode('ascii')
148 with self.open(url, post_data) as response:
149 return response.read()
150
151
152 def get_backup_v13_1(self):
153 """
154 Retrieve a backup from Untangle version 13.1. This
155 differs from v13 (and v12, and v11,...) by only one word
156 in the URL: "webui" becomes "admin".
157
158 Returns the binary HTTPS response (i.e. the file).
159 """
160 url = self.base_url + '/admin/download'
161 post_vars = {'type': 'backup'}
162 post_data = urllib.parse.urlencode(post_vars).encode('ascii')
163 with self.open(url, post_data) as response:
164 return response.read()