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