0d4b0b2279
- $ann implemented - concurrency removed; server is now single-threaded, event-driven and much more stable - rare seed is no longer the game id; ids are sequential from server startup so they weren't random at all before - supports dropping privileges; now you can run it as root so it can open a sockets on low ports, then it will switch to the given user before serving any traffic - newserv now behaves like a proxy if you run it with the --proxy-destination=<IP_OR_HOSTNAME> argument; there's also an (invisible) shell in this mode where you can inject commands to the server or client. e.g. it can always be christmas in the lobby if you do `sc DA 01 00 00` - increased the mtu on PSODolphinConfig's tap0 configuration; this seems to make the connection more stable - fixed some uninitialized memory bugs - the shell is now event-driven and now uses libevent too; unfortunately this means readline doesn't work anymore (no history and vim-like shortcuts) - made network command display consistent for input vs. output (the header appears in both cases now) - fixed bugs in some subcommand handling (the BB logic was being applied to non-BB clients erroneously, causing most item drops not to work at all) - fixed player tags in the short lobby data struct. unclear if this was actually a problem but it was inconsistent with other servers - fixed "unused" field in game join command (actually it appears to be disable_udp and should be 1, not 0) - cleaned up Server abstraction a bit - rewrote some text functions; asan was complaining about the built-in ones for some reason - added an optional welcome message
138 lines
5.1 KiB
Python
138 lines
5.1 KiB
Python
#!/bin/env python3
|
|
|
|
import argparse
|
|
import os
|
|
import pwd
|
|
import re
|
|
import stat
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
|
|
def change_user(username):
|
|
try:
|
|
user_info = pwd.getpwnam(username)
|
|
except KeyError:
|
|
print("user %s does not exist" % (username,))
|
|
raise
|
|
os.setregid(user_info.pw_gid, user_info.pw_gid)
|
|
os.setreuid(user_info.pw_uid, user_info.pw_uid)
|
|
|
|
|
|
def main(argv):
|
|
parser = argparse.ArgumentParser(description="Runs Dolphin as a non-privileged user with access to the privileged tap0 network interface. Run this script with sudo; it will run Dolphin as the user you sudo'ed from and provide it access to the tap interface. It will also automatically configure the tap interface when Dolphin first opens it.")
|
|
parser.add_argument(
|
|
"--dolphin-path", "-d",
|
|
action="store",
|
|
dest="dolphin_path",
|
|
default="./Dolphin.app/Contents/MacOS/Dolphin",
|
|
help="Path to Dolphin executable. Defaults to Dolphin.app/Contents/MacOS/Dolphin in current directory.",
|
|
)
|
|
parser.add_argument(
|
|
"--memwatch-path", "-m",
|
|
action="store",
|
|
dest="memwatch_path",
|
|
default="memwatch",
|
|
help="Path to memwatch executable. Defaults to memwatch (assumes it's installed somewhere on $PATH).",
|
|
)
|
|
parser.add_argument(
|
|
"--tap-interface", "-I",
|
|
action="store",
|
|
dest="tap_interface",
|
|
default="/dev/tap0",
|
|
help="Tap interface to use. Defaults to /dev/tap0.",
|
|
)
|
|
parser.add_argument(
|
|
"--tap-ip", "-i",
|
|
action="store",
|
|
dest="tap_ip",
|
|
default="192.168.0.5/24",
|
|
help="IP address and subnet bits to assign to tap interface. Defaults to 192.168.0.5/24. This should match the gateway and DNS server addresses configured in PSO's network settings.",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
try:
|
|
username = os.environ['SUDO_USER']
|
|
except KeyError:
|
|
print('$SUDO_USER not set; use `sudo -E`')
|
|
return 1
|
|
|
|
tap_match = re.match(r'^/dev/tap([0-9]+)$', args.tap_interface)
|
|
if tap_match is None:
|
|
print('tap interface name must begin with /dev/tap')
|
|
return 1
|
|
tap_interface_number = int(tap_match.group(1))
|
|
tap_name = 'tap%d' % (tap_interface_number,)
|
|
|
|
# 1. open tap and configure it
|
|
print("starting and configuring " + tap_name)
|
|
tap_fd = os.open(args.tap_interface, os.O_RDWR)
|
|
os.set_inheritable(tap_fd, True)
|
|
subprocess.check_call(['ifconfig', tap_name, args.tap_ip], stderr=subprocess.DEVNULL)
|
|
subprocess.check_call(['ifconfig', tap_name, 'up'], stderr=subprocess.DEVNULL)
|
|
subprocess.check_call(['ifconfig', tap_name, 'mtu', '9000'], stderr=subprocess.DEVNULL)
|
|
|
|
# 2. fork a Dolphin process, dropping privileges first
|
|
print("starting dolphin")
|
|
dolphin_proc = subprocess.Popen([args.dolphin_path],
|
|
preexec_fn=lambda: change_user(username), pass_fds=(tap_fd,))
|
|
time.sleep(1)
|
|
|
|
# 3. create a temp file for dolphin to open instead of /dev/tapN
|
|
print("creating temp file")
|
|
tmpfile_fd = os.open("/tmp/dnet", os.O_CREAT | os.O_RDWR, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
|
|
os.close(tmpfile_fd)
|
|
os.chmod("/tmp/dnet", stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
|
|
|
|
# 4. modify dolphin's memory to make it upen the temp file
|
|
print("redirecting /dev/tap0 for dolphin")
|
|
addresses_str = subprocess.check_output([args.memwatch_path, str(dolphin_proc.pid), 'find', '\"/dev/tap0\"'], stderr=subprocess.DEVNULL)
|
|
for line in addresses_str.splitlines():
|
|
tokens = line.split()
|
|
if len(tokens) != 3:
|
|
continue
|
|
print("redirecting /dev/tap0 to /tmp/dnet at %s in dolphin" % (tokens[1],))
|
|
subprocess.check_call([args.memwatch_path, str(dolphin_proc.pid), "access", tokens[1], "rwx"], stderr=subprocess.DEVNULL)
|
|
subprocess.check_call([args.memwatch_path, str(dolphin_proc.pid), "write", tokens[1], "\"/tmp/dnet\""], stderr=subprocess.DEVNULL)
|
|
subprocess.check_call([args.memwatch_path, str(dolphin_proc.pid), "access", tokens[1], "r-x"], stderr=subprocess.DEVNULL)
|
|
|
|
# step 5: use lsof to find out when dolphin opens /tmp/dnet
|
|
print("waiting for temp file to open")
|
|
dolphin_tap0_fd = -1
|
|
while dolphin_tap0_fd < 0:
|
|
time.sleep(1)
|
|
result = subprocess.check_output(["lsof", "-p", str(dolphin_proc.pid)], stderr=subprocess.DEVNULL)
|
|
for line in result.splitlines():
|
|
if b'/tmp/dnet' not in line:
|
|
continue
|
|
|
|
fd_str = line.split()[3]
|
|
dolphin_tap0_fd = int(fd_str[0:-len(fd_str.lstrip(b'0123456789'))])
|
|
print("found open tap fd %d in dolphin" % (dolphin_tap0_fd,))
|
|
|
|
# step 6: use memwatch to move the tap fd into place
|
|
print("replacing temp fd %d with tap fd %d in dolphin" % (dolphin_tap0_fd,
|
|
tap_fd))
|
|
assembly_contents = b"""start:
|
|
mov rax, 0x000000000200005A # dup2(from_fd, to_fd)
|
|
mov rdi, %d
|
|
mov rsi, %d
|
|
syscall
|
|
|
|
# close the original fd. note that rdi is preserved during the syscall so we
|
|
# don't need to reload it
|
|
mov rax, 0x0000000002000006 # close(fd)
|
|
syscall
|
|
|
|
ret
|
|
""" % (tap_fd, dolphin_tap0_fd)
|
|
subprocess.run([args.memwatch_path, str(dolphin_proc.pid), "--", "run", "-"], input=assembly_contents, stderr=subprocess.DEVNULL)
|
|
|
|
# ok we're done; dolphin is running as a non-privileged user with the tap open
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main(sys.argv))
|