diff options
| author | Игорь Толмачёв <igorechek06@noreply.codeberg.org> | 2024-04-22 12:21:07 +0000 |
|---|---|---|
| committer | Igor Tolmachev <me@igorek.dev> | 2024-04-23 00:28:49 +0900 |
| commit | 1cb2acd647efa5fb36b84aff246cabc9aaad9140 (patch) | |
| tree | 0a88e66f939e942aadc5cfcef2927d678d7817e2 /server.py | |
| download | minecraft_http_whitelist-1cb2acd647efa5fb36b84aff246cabc9aaad9140.tar.gz minecraft_http_whitelist-1cb2acd647efa5fb36b84aff246cabc9aaad9140.zip | |
Diffstat (limited to 'server.py')
| -rw-r--r-- | server.py | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/server.py b/server.py new file mode 100644 index 0000000..7edd081 --- /dev/null +++ b/server.py | |||
| @@ -0,0 +1,144 @@ | |||
| 1 | import socket | ||
| 2 | import struct | ||
| 3 | from http.server import BaseHTTPRequestHandler, HTTPServer | ||
| 4 | from json import load | ||
| 5 | from os.path import expanduser | ||
| 6 | from re import compile | ||
| 7 | from traceback import print_exc | ||
| 8 | |||
| 9 | import config | ||
| 10 | |||
| 11 | |||
| 12 | def rcon_packet(id: int, type: int, body: str) -> bytes: | ||
| 13 | return ( | ||
| 14 | struct.pack( | ||
| 15 | "<iii", | ||
| 16 | len(body) + 10, | ||
| 17 | id, | ||
| 18 | type, | ||
| 19 | ) | ||
| 20 | + body.encode("utf-8") | ||
| 21 | + bytes((0, 0)) | ||
| 22 | ) | ||
| 23 | |||
| 24 | |||
| 25 | def rcon_login() -> socket.socket: | ||
| 26 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
| 27 | s.connect(("127.0.0.1", config.rcon_port)) | ||
| 28 | s.send(rcon_packet(0, 3, config.rcon_password)) | ||
| 29 | return s | ||
| 30 | |||
| 31 | |||
| 32 | def rcon(command: str) -> None: | ||
| 33 | rcon_socket.send(rcon_packet(0, 2, command)) | ||
| 34 | |||
| 35 | |||
| 36 | def pipe(command: str) -> None: | ||
| 37 | with open(expanduser(config.pipe_path), "w") as pipe: | ||
| 38 | pipe.write(f"{command}\n") | ||
| 39 | |||
| 40 | |||
| 41 | class HttpHandler(BaseHTTPRequestHandler): | ||
| 42 | def disabled(self) -> None: | ||
| 43 | self.send_response(503) | ||
| 44 | self.send_header("Content-Type", "text/plain") | ||
| 45 | self.end_headers() | ||
| 46 | self.wfile.write("Endpoint disabled".encode("utf-8")) | ||
| 47 | |||
| 48 | def do_GET(self): | ||
| 49 | if "GET" not in config.enabled_endpoints: | ||
| 50 | self.disabled() | ||
| 51 | return | ||
| 52 | |||
| 53 | try: | ||
| 54 | with open(expanduser(config.whitelist_file_path)) as file: | ||
| 55 | status = 200 | ||
| 56 | response = file.read() | ||
| 57 | except Exception: | ||
| 58 | status = 500 | ||
| 59 | print_exc() | ||
| 60 | |||
| 61 | self.send_response(status) | ||
| 62 | self.send_header("Content-Type", "application/json") | ||
| 63 | self.end_headers() | ||
| 64 | self.wfile.write(response.encode("utf-8")) | ||
| 65 | |||
| 66 | def do_POST(self) -> None: | ||
| 67 | if "POST" not in config.enabled_endpoints: | ||
| 68 | self.disabled() | ||
| 69 | return | ||
| 70 | |||
| 71 | try: | ||
| 72 | length = int(self.headers.get("Content-Length", 0)) | ||
| 73 | username = self.rfile.read(length).decode("utf-8") | ||
| 74 | |||
| 75 | if allowed_usernames_regex.match(username) is None: | ||
| 76 | status = 400 | ||
| 77 | response = "Incorrect username" | ||
| 78 | elif username in whitelist: | ||
| 79 | status = 409 | ||
| 80 | response = "Player is already whitelisted" | ||
| 81 | else: | ||
| 82 | status = 201 | ||
| 83 | response = f"Added {username} to the whitelist" | ||
| 84 | |||
| 85 | whitelist.add(username) | ||
| 86 | console(f"whitelist add {username}") | ||
| 87 | except Exception: | ||
| 88 | status = 500 | ||
| 89 | print_exc() | ||
| 90 | |||
| 91 | self.send_response(status) | ||
| 92 | self.send_header("Content-Type", "text/plain") | ||
| 93 | self.end_headers() | ||
| 94 | self.wfile.write(response.encode("utf-8")) | ||
| 95 | |||
| 96 | def do_DELETE(self) -> None: | ||
| 97 | if "DELETE" not in config.enabled_endpoints: | ||
| 98 | self.disabled() | ||
| 99 | return | ||
| 100 | |||
| 101 | try: | ||
| 102 | length = int(self.headers.get("Content-Length", 0)) | ||
| 103 | username = self.rfile.read(length).decode("utf-8") | ||
| 104 | |||
| 105 | if allowed_usernames_regex.match(username) is None: | ||
| 106 | status = 400 | ||
| 107 | response = "Incorrect username" | ||
| 108 | elif username not in whitelist: | ||
| 109 | status = 409 | ||
| 110 | response = "Player is not whitelisted" | ||
| 111 | else: | ||
| 112 | status = 200 | ||
| 113 | response = f"Removed {username} from the whitelist" | ||
| 114 | |||
| 115 | whitelist.remove(username) | ||
| 116 | console(f"whitelist remove {username}") | ||
| 117 | except Exception: | ||
| 118 | status = 500 | ||
| 119 | print_exc() | ||
| 120 | |||
| 121 | self.send_response(status) | ||
| 122 | self.send_header("Content-Type", "text/plain") | ||
| 123 | self.end_headers() | ||
| 124 | self.wfile.write(response.encode("utf-8")) | ||
| 125 | |||
| 126 | |||
| 127 | server = HTTPServer(("0.0.0.0", config.http_port), HttpHandler) | ||
| 128 | allowed_usernames_regex = compile(r"^[a-zA-Z0-9_]+$") | ||
| 129 | |||
| 130 | with open(expanduser(config.whitelist_file_path)) as file: | ||
| 131 | whitelist = {e["name"] for e in load(file)} | ||
| 132 | |||
| 133 | if config.whitelist_access_type == "pipe": | ||
| 134 | console = pipe | ||
| 135 | elif config.whitelist_access_type == "rcon": | ||
| 136 | rcon_socket = rcon_login() | ||
| 137 | console = rcon | ||
| 138 | |||
| 139 | |||
| 140 | try: | ||
| 141 | server.serve_forever() | ||
| 142 | except KeyboardInterrupt: | ||
| 143 | rcon_socket.close() | ||
| 144 | server.server_close() | ||
