wesnoth_addon_manager: added SSL/TLS support
This commit is contained in:
parent
6fe2627048
commit
7e67bc5d08
3 changed files with 93 additions and 20 deletions
|
@ -15,6 +15,7 @@
|
|||
### User interface
|
||||
### WML Engine
|
||||
### Miscellaneous and Bug Fixes
|
||||
* wesnoth_addon_manager now supports SSL/TLS connection (using the `--secure` flag)
|
||||
|
||||
## Version 1.17.13
|
||||
### Add-ons client
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import gzip, zlib, io
|
||||
import socket, struct, glob, sys, shutil, threading, os, fnmatch
|
||||
import socket, ssl, struct, glob, sys, shutil, threading, os, fnmatch
|
||||
import wesnoth.wmlparser3 as wmlparser
|
||||
|
||||
# See the following files (among others):
|
||||
|
@ -46,7 +46,7 @@ class CampaignClient:
|
|||
("15005", "1.4.x"),
|
||||
)
|
||||
|
||||
def __init__(self, address = None, quiet=False):
|
||||
def __init__(self, address = None, quiet=False, secure=False):
|
||||
"""
|
||||
Return a new connection to the campaign server at the given address.
|
||||
"""
|
||||
|
@ -63,9 +63,18 @@ class CampaignClient:
|
|||
self.cs = None
|
||||
self.verbose = False
|
||||
self.quiet = quiet
|
||||
self.secure = secure
|
||||
|
||||
if self.secure:
|
||||
print("Attempting to connect to the server using SSL/TLS",
|
||||
file=sys.stderr)
|
||||
self.context = ssl.create_default_context()
|
||||
else:
|
||||
self.context = None
|
||||
|
||||
if address is not None:
|
||||
self.canceled = False
|
||||
self.ssl_unsupported = False
|
||||
self.error = False
|
||||
s = address.split(":")
|
||||
if len(s) == 2:
|
||||
|
@ -86,15 +95,55 @@ class CampaignClient:
|
|||
sys.stderr.write("\n")
|
||||
self.sock = socket.socket(addr[0], addr[1], addr[2])
|
||||
self.sock.connect(addr[4])
|
||||
self.sock.send(struct.pack("!I", 0))
|
||||
# the first part of the connection is unencrypted
|
||||
# the client must send a packet of 4 bytes
|
||||
# with a value of 1 if requesting an encrypted connection
|
||||
# and with a value of 0 otherwise
|
||||
if self.secure:
|
||||
self.sock.send(struct.pack("!I", 1))
|
||||
else:
|
||||
self.sock.send(struct.pack("!I", 0))
|
||||
|
||||
try:
|
||||
connection_num = self.sock.recv(4)
|
||||
if self.secure:
|
||||
result = self.sock.recv(4)
|
||||
# the server then replies with one of these two values
|
||||
# 0x00000000 if it supports encrypted connections
|
||||
# 0xFFFFFFFF if it doesn't support them
|
||||
# in this last case we exit the script
|
||||
if result == b'\x00\x00\x00\x00':
|
||||
# now we can encrypt the socket
|
||||
self.ssl_sock = self.context.wrap_socket(self.sock,
|
||||
server_hostname=self.host)
|
||||
sys.stderr.write("Connected with SSL/TLS\n")
|
||||
elif result == b'\xff\xff\xff\xff':
|
||||
self.ssl_unsupported = True
|
||||
else:
|
||||
# just in case, but the server should never return anything else
|
||||
sys.stderr.write("Handshake returned unsupported value\n")
|
||||
self.error = True
|
||||
else:
|
||||
# for unencrypted connections, the server returns an arbitrary
|
||||
# 32-bit number without any specific meaning
|
||||
# but at the moment this value is always 42
|
||||
result = self.sock.recv(4)
|
||||
except socket.error:
|
||||
connection_num = struct.pack("!I", -1)
|
||||
result = struct.pack("!I", -1)
|
||||
self.error = True
|
||||
except ssl.SSLError:
|
||||
sys.stderr.write("Failed to connect with SSL/TLS\n")
|
||||
self.error = True
|
||||
if not self.quiet:
|
||||
sys.stderr.write("Connected as %d.\n" % struct.unpack(
|
||||
"!I", connection_num))
|
||||
result_num = struct.unpack("!I", result)[0]
|
||||
# here we have an arcane incantation of Python's formatting mini-language
|
||||
# 0: refers to the only argument of the format method
|
||||
# # means alternate formatting, which adds the "0x" prefix to hex numbers
|
||||
# 0 is the padding character
|
||||
# 10 is the width of the field (instead of the usual 8 for 32-bit numbers,
|
||||
# because this includes the prefix)
|
||||
# x requires formatting as a hex number
|
||||
sys.stderr.write("Server returned value {0:d} \
|
||||
(hex: {0:#010x}).\n".format(result_num))
|
||||
|
||||
def async_cancel(self):
|
||||
"""
|
||||
|
@ -106,12 +155,17 @@ class CampaignClient:
|
|||
if not self.quiet:
|
||||
if self.canceled:
|
||||
sys.stderr.write("Canceled socket.\n")
|
||||
elif self.ssl_unsupported:
|
||||
sys.stderr.write("Server does not support SSL/TLS.\n")
|
||||
elif self.error:
|
||||
sys.stderr.write("Unexpected disconnection.\n")
|
||||
else:
|
||||
sys.stderr.write("Closing socket.\n")
|
||||
try:
|
||||
self.sock.shutdown(2)
|
||||
if self.secure:
|
||||
self.ssl_sock.shutdown(2)
|
||||
else:
|
||||
self.sock.shutdown(2)
|
||||
except socket.error:
|
||||
pass # Well, what can we do?
|
||||
except AttributeError:
|
||||
|
@ -135,6 +189,9 @@ class CampaignClient:
|
|||
"""
|
||||
Send binary data to the server.
|
||||
"""
|
||||
if self.error or self.ssl_unsupported:
|
||||
return None
|
||||
|
||||
# Compress the packet before we send it
|
||||
fio = io.BytesIO()
|
||||
z = gzip.GzipFile(mode = "wb", fileobj = fio)
|
||||
|
@ -143,15 +200,24 @@ class CampaignClient:
|
|||
zdata = fio.getvalue()
|
||||
|
||||
zpacket = struct.pack("!I", len(zdata)) + zdata
|
||||
self.sock.sendall(zpacket)
|
||||
if self.secure:
|
||||
self.ssl_sock.sendall(zpacket)
|
||||
else:
|
||||
self.sock.sendall(zpacket)
|
||||
|
||||
def read_packet(self):
|
||||
"""
|
||||
Read binary data from the server.
|
||||
"""
|
||||
if self.error or self.ssl_unsupported:
|
||||
return None
|
||||
|
||||
packet = b""
|
||||
while len(packet) < 4 and not self.canceled:
|
||||
r = self.sock.recv(4 - len(packet))
|
||||
if self.secure:
|
||||
r = self.ssl_sock.recv(4 - len(packet))
|
||||
else:
|
||||
r = self.sock.recv(4 - len(packet))
|
||||
if not r:
|
||||
return None
|
||||
packet += r
|
||||
|
@ -165,7 +231,10 @@ class CampaignClient:
|
|||
|
||||
packet = b""
|
||||
while len(packet) < l and not self.canceled:
|
||||
r = self.sock.recv(l - len(packet))
|
||||
if self.secure:
|
||||
r = self.ssl_sock.recv(l - len(packet))
|
||||
else:
|
||||
r = self.sock.recv(l - len(packet))
|
||||
if not r:
|
||||
return None
|
||||
packet += r
|
||||
|
@ -234,7 +303,7 @@ class CampaignClient:
|
|||
"""
|
||||
Returns a WML object containing all available info from the server.
|
||||
"""
|
||||
if self.error:
|
||||
if self.error or self.ssl_unsupported:
|
||||
return None
|
||||
request = append_tag(None, "request_campaign_list")
|
||||
if addon:
|
||||
|
|
|
@ -94,6 +94,9 @@ if __name__ == "__main__":
|
|||
argumentparser.add_argument("--change-passphrase", nargs=3,
|
||||
metavar=("ADD-ON","OLD","NEW"),
|
||||
help="Change the passphrase for ADD-ON from OLD to NEW")
|
||||
argumentparser.add_argument("-S", "--secure",
|
||||
action="store_true",
|
||||
help="Connect to the add-ons server using SSL/TLS encryption.")
|
||||
args = argumentparser.parse_args()
|
||||
|
||||
port = args.port
|
||||
|
@ -214,7 +217,7 @@ if __name__ == "__main__":
|
|||
campaign_list = None
|
||||
|
||||
if args.list:
|
||||
cs = CampaignClient(address)
|
||||
cs = CampaignClient(address, secure=args.secure)
|
||||
campaign_list = data = cs.list_campaigns()
|
||||
if data:
|
||||
campaigns = data.get_all(tag = "campaigns")[0]
|
||||
|
@ -245,7 +248,7 @@ if __name__ == "__main__":
|
|||
sys.stderr.write("Could not connect.\n")
|
||||
|
||||
elif args.download:
|
||||
cs = CampaignClient(address)
|
||||
cs = CampaignClient(address, secure=args.secure)
|
||||
fetchlist = []
|
||||
campaign_list = data = cs.list_campaigns()
|
||||
if data:
|
||||
|
@ -281,24 +284,24 @@ if __name__ == "__main__":
|
|||
"because it is already up-to-date.")
|
||||
|
||||
elif args.unpack:
|
||||
cs = CampaignClient(address)
|
||||
cs = CampaignClient(address, secure=args.secure)
|
||||
with open(args.unpack, "rb") as f:
|
||||
data = f.read()
|
||||
decoded = cs.decode(data)
|
||||
print("Unpacking %s..." % args.unpack)
|
||||
cs.unpackdir(decoded, args.campaigns_dir, verbose=True)
|
||||
elif args.remove:
|
||||
cs = CampaignClient(address)
|
||||
cs = CampaignClient(address, secure=args.secure)
|
||||
data = cs.delete_campaign(*args.remove)
|
||||
print_messages(data)
|
||||
|
||||
elif args.change_passphrase:
|
||||
cs = CampaignClient(address)
|
||||
cs = CampaignClient(address, secure=args.secure)
|
||||
data = cs.change_passphrase(*args.change_passphrase)
|
||||
print_messages(data)
|
||||
|
||||
elif args.upload:
|
||||
cs = CampaignClient(address)
|
||||
cs = CampaignClient(address, secure=args.secure)
|
||||
if os.path.isdir(args.upload):
|
||||
# else basename returns an empty string
|
||||
args.upload = args.upload.rstrip("/")
|
||||
|
@ -373,7 +376,7 @@ if __name__ == "__main__":
|
|||
cdir = args.update
|
||||
dirs = glob.glob(os.path.join(cdir, "*"))
|
||||
dirs = [x for x in dirs if os.path.isdir(x)]
|
||||
cs = CampaignClient(address)
|
||||
cs = CampaignClient(address, secure=args.secure)
|
||||
campaign_list = data = cs.list_campaigns()
|
||||
if not data:
|
||||
sys.stderr.write("Could not connect to the add-on server.\n")
|
||||
|
@ -427,7 +430,7 @@ if __name__ == "__main__":
|
|||
|
||||
if args.html:
|
||||
if not campaign_list:
|
||||
cs = CampaignClient(address)
|
||||
cs = CampaignClient(address, secure=args.secure)
|
||||
campaign_list = cs.list_campaigns()
|
||||
del cs
|
||||
if campaign_list:
|
||||
|
|
Loading…
Add table
Reference in a new issue