]> gitweb.michael.orlitzky.com - untangle-https-backup.git/blob - src/untangle/untangle.py
681135ab5ff302ce0df7aac1a9f669ea8742bb96
[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', '13.1')
26 if self.version not in ['9', '10', '11', '12', '13', '13.1']:
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 == '13.1':
114 # But the minor update v13.1 moved the backup URL.
115 return self.get_backup_v13_1()
116
117
118 def get_backup_v9(self):
119 """
120 Retrieve a backup from Untangle version 9. This requires two
121 requests; the first just hits the page, and the second actually
122 retrieves the backup file.
123
124 Returns the binary HTTPS response (i.e. the file).
125 """
126 url = self.base_url + '/webui/backup'
127 post_vars = {'action': 'requestBackup'}
128 post_data = urllib.parse.urlencode(post_vars).encode('ascii')
129 self.open(url, post_data)
130
131 url = self.base_url + 'webui/backup?action=initiateDownload'
132 with self.open(url) as response:
133 return response.read()
134
135
136 def get_backup_v10(self):
137 """
138 Retrieve a backup from Untangle version 10.
139
140 Returns the binary HTTPS response (i.e. the file).
141 """
142 url = self.base_url + '/webui/download'
143 post_vars = {'type': 'backup'}
144 post_data = urllib.parse.urlencode(post_vars).encode('ascii')
145 with self.open(url, post_data) as response:
146 return response.read()
147
148
149 def get_backup_v13_1(self):
150 """
151 Retrieve a backup from Untangle version 13.1. This
152 differs from v13 (and v12, and v11,...) by only one word
153 in the URL: "webui" becomes "admin".
154
155 Returns the binary HTTPS response (i.e. the file).
156 """
157 url = self.base_url + '/admin/download'
158 post_vars = {'type': 'backup'}
159 post_data = urllib.parse.urlencode(post_vars).encode('ascii')
160 with self.open(url, post_data) as response:
161 return response.read()