Files
psopeeps-newserv/PSODolphinConfig.py
T
Martin Michelsen 0d4b0b2279 fix a lot of issues on psogc; add proxy module
- $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
2020-02-16 21:42:29 -08:00

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))