-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.py
More file actions
149 lines (124 loc) · 6.39 KB
/
Copy pathserver.py
File metadata and controls
149 lines (124 loc) · 6.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import asyncio
import websockets
import subprocess
import socket
import json
import time
import os
from datetime import datetime
# ─── Helpers ──────────────────────────────────────────────────────────────────
def ts() -> str:
"""Current time prefix for CLI output."""
return datetime.now().strftime("[%H:%M:%S]")
def get_local_ip() -> str:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
ip = s.getsockname()[0]
s.close()
return ip
except Exception:
return "127.0.0.1"
def make_response(status: str, output: str) -> str:
"""Build a JSON response string."""
return json.dumps({
"status": status,
"output": output,
"ts": int(time.time())
})
# ─── Security ─────────────────────────────────────────────────────────────────
FORBIDDEN = [
"format", "del /s", "del /f /s", "rd /s", "rmdir /s",
"rm -rf", "rm -fr", "mkfs", "dd if=",
":(){:|:&};:", "shutdown /f", # fork bomb, forced shutdown
"reg delete", "bcdedit", # registry / bootloader
"cipher /w", "sfc /scannow /offbootdir",
]
COMMAND_TIMEOUT = 10 # seconds
# ─── Special Commands ──────────────────────────────────────────────────────────
START_TIME = time.time()
SPECIAL: dict[str, str] = {
# Power
"shutdown": "shutdown /s /t 5",
"restart": "shutdown /r /t 5",
"sleep": "rundll32.exe powrprof.dll,SetSuspendState 0,1,0",
# Volume (requires nircmd — silently no-ops if not installed)
"vol_up": "nircmd changesysvolume 4000",
"vol_down": "nircmd changesysvolume -4000",
# Brightness (nircmd)
"bright_up": "nircmd changebrightness 10",
"bright_down": "nircmd changebrightness -10",
}
# ─── Client Handler ────────────────────────────────────────────────────────────
async def handle_client(websocket):
client_ip = websocket.remote_address[0]
print(f"{ts()} [*] New connection from {client_ip}")
await websocket.send(make_response("ok", "Connected to Laptop Control Server. Ready."))
try:
async for raw_message in websocket:
message = raw_message.strip()
print(f"{ts()} [>] {client_ip}: {message}")
# ── Heartbeat ──────────────────────────────────────────────────────
if message.lower() == "ping":
uptime = int(time.time() - START_TIME)
h, rem = divmod(uptime, 3600)
m, s = divmod(rem, 60)
await websocket.send(make_response("ok", f"pong · uptime {h:02d}:{m:02d}:{s:02d}"))
continue
# ── Security check ─────────────────────────────────────────────────
if any(cmd in message.lower() for cmd in FORBIDDEN):
print(f"{ts()} [!] Forbidden command blocked: {message}")
await websocket.send(make_response("error", "Forbidden command."))
continue
# ── Resolve special aliases ────────────────────────────────────────
resolved = SPECIAL.get(message.lower(), message)
# ── Execute ────────────────────────────────────────────────────────
try:
proc = await asyncio.create_subprocess_shell(
resolved,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
try:
stdout_b, stderr_b = await asyncio.wait_for(
proc.communicate(), timeout=COMMAND_TIMEOUT
)
stdout = stdout_b.decode(errors="replace").strip()
stderr = stderr_b.decode(errors="replace").strip()
output = stdout or stderr or f"'{message}' executed (no output)."
status = "error" if (not stdout and stderr) else "ok"
except asyncio.TimeoutError:
proc.kill()
output = f"Command timed out after {COMMAND_TIMEOUT}s."
status = "error"
print(f"{ts()} [<] Sending response to {client_ip}")
await websocket.send(make_response(status, output))
except Exception as exc:
msg = f"Execution error: {exc}"
print(f"{ts()} [!] {msg}")
await websocket.send(make_response("error", msg))
except websockets.exceptions.ConnectionClosed:
print(f"{ts()} [-] Connection closed by {client_ip}")
except Exception as exc:
print(f"{ts()} [!] Unexpected error ({client_ip}): {exc}")
# ─── Main ──────────────────────────────────────────────────────────────────────
async def main():
host = "0.0.0.0"
port = 8765
local_ip = get_local_ip()
print("=" * 44)
print(" Laptop Control System — Server v2")
print("=" * 44)
print(f"{ts()} [*] Starting server…")
print(f"{ts()} [*] Mobile app IP : {local_ip}")
print(f"{ts()} [*] Listening on : ws://{local_ip}:{port}")
print(f"{ts()} [*] Command timeout: {COMMAND_TIMEOUT}s")
print(f"{ts()} [*] Responses : JSON")
print("=" * 44)
async with websockets.serve(handle_client, host, port):
await asyncio.Future() # run forever
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print(f"\n{ts()} [*] Server stopped.")