Setting up a dgamelaunch game server

A Blast from the Past

© Photo by Kevin Borrill on Unsplash

© Photo by Kevin Borrill on Unsplash

Author(s):

If you are into retrogaming, dgamelaunch lets you set up a server to play Roguelike games and compete with friends, all while preserving a piece of gaming history.

In the early '80s, Rogue, a game that would overhaul the gaming world, was released. According to Roguelike Gallery, Rogue first ran on a PDP-11 machine running Unix v6 [1]. Primitive by today's standards, Rogue popularized the Roguelike genre despite not being the first game of its kind. Rogue's influence can be seen in modern commercial titles. Rogue's source code is still available, as are many games that pre-date Linux.

In this article, I show how to set up a server to host antique terminal games. Why would you want to do this? Besides preserving a piece of history, playing these games is fun despite their age. In addition, running games on a specific server allows you to keep scoreboards, letting you compete against friends on a shared server. Last, but not least, setting up a game server is an instructive exercise.

Service Architecture

This article assumes you are using Debian, which unlike early 16-bit Operating Systems, is capable of using modern connectivity protocols.

In the service architecture, openbsd-inetd takes an incoming Telnet connection from the user and passes it to a Telnet daemon (telnetd). The Telnet daemon calls dgamelaunch. Dgamelaunch chroots into /var/dgl and provides a limited shell that allows the user to play games (Figure 1).

Figure 1: The game server's service architecture.

The core of this game server is the game launcher, which is a limited shell that is used to authenticate the player and start games. The launcher I am using, dgamelaunch [2], is surprisingly capable for such a small C program. Dgamelaunch records games, allows users to watch other players, and relays messages from one person to another.

The game server will be available over Telnet, a ubiquitous protocol that most operating systems support out of the box. The idea is to let the player connect to the server using a Telnet client and offer him or her a dgamelaunch shell that will allow them to select which games to play. Beware: Telnet is not secure (see Upgrading from Telnet for a safer approach).

Upgrading from Telnet

While the setup described in this article is safe to deploy on a home LAN, it is unsafe to use for a game server accessible over the Internet.

Armies of bots roam the Internet, storming Telnet services and trying to gain access to them via brute force attacks. Bots can also automatically create massive numbers of accounts in the hope of using them later for nefarious purposes. Although the real damage a bot can cause after registering or gaining access into an already existing account is negligible, dgamelaunch is not well equipped for dealing with these attacks directly, and neither is telnetd in this configuration.

Another issue associated with Telnet is that it lacks secure authentication – at least the way it is demonstrated here. Usernames and passwords are sent unencrypted over the line. That may be acceptable for a home network, but not for the Internet.

Organizations who offer retrogame servers over the Internet favor SSH instead, which offers encrypted connections and better support for dealing with bot attacks. SSH also does not require inetd. Using SSH, however, is outside of the scope of this article.

In order to mitigate the damages that may be caused by malicious users, dgamelaunch is designed to chroot into an isolated part of the operating system and drop privileges.

Chroot Security

Dgamelaunch uses chroot() as a security layer. Roughly speaking, when you chroot into a directory, you are running a process inside that directory, with the process viewing the directory as the root (/) of the filesystem hierarchy. In essence, it means any process running within the chroot cannot access files placed outside of the chroot directory. This makes chroot() a poor man's isolation technique. A server running within a chroot won't be able to wreak havoc in the rest of the filesystem if the service is compromised.

However, chroots are breakable by design, making them less than ideal for isolating a service from the rest of the operating system. A root-owned process within a chroot can create block device nodes. If a chroot includes a hard link that points to an external resource, the resource can be abused from within the chroot. Also, a root-owned process can break out of the chroot directory.

Consequently, chroot() is no substitute for proper privilege separation. To avoid this problem, dgamelaunch drops privileges immediately after chrooting. Unprivileged processes can't escape from a chrooted environment so easily.

Installing dgamelaunch

Unfortunately, dgamelaunch is not packaged by any major Linux distribution. At the time of writing, the project has had no official release since 2011. However, dgamelauch is used by many current projects, including Roguelike Gallery, Dungeon Crawl Stone Soup [3], and the public NetHack server NAO [4]. As a result, there is a constant influx of patches from the community should you ever need them, despite the official project appearing to be dead.

The following command (which must be run as superuser) installs everything you need in order to compile dgamelaunch:

# apt-get install automake autoconf build-essential git bison sqlite3 libsqlite3-dev curl unzip groff libncurses-dev flex-old

You can get the source code from GitHub using the following command:

$ git clone https://github.com/paxed/dgamelaunch.git

Then, compile the program. If you intend to enable sqlite for managing your users and plan to use /var/dgl as a chroot directory, you may configure and compile dgamelaunch as follows:

$ cd dgamelaunch
$ ./autogen.sh --enable-sqlite --enable-shmem --with-config-file=/var/dgl/etc/dgamelaunch.conf
$ make

This prepares dgamelaunch to load its configuration from /var/dgl/etc/dgamelaunch.conf and to use sqlite as the user database.

Next, build the chroot directory that dgamelaunch will switch into when a user connects to the server. Dgamelaunch provides a script, dgl-create-chroot, to do this. Open dgl-create-chroot with any text editor and change the configuration variables to your liking. The script is NetHack-centric, because NetHack is the most popular Roguelike game in existence, but you can leave the NetHack-related variables unmodified if you don't plan to run it at all. See Listing 1 for the variables you can set.

Listing 1

Dgl-create-chroot Configuration

CHROOT="/var/dgl/"
USRGRP="games:games"
SQLITE_DBFILE="/dgldir/dgamelaunch.db"
# Leave this variable empty to skip installing gzip in the chroot
COMPRESSBIN=""
# The script is Nethack centric. Leave some variables blank since
# we have no use for them.
NETHACKBIN=""
NH_PLAYGROUND_FIXED=""
# There Nethack related variables must be set even if we have no
# use for them.
NHSUBDIR="/nh343/"
NH_VAR_PLAYGROUND="/nh343/var/"

Once the configuration is done, save the script and run it as root:

# bash dgl-create-chroot

If you use the example values from Listing 1, the /var/dgl directory will be created and populated with all the necessary libraries and configuration files to run a chroot.

Finally, install dgamelaunch in /usr/bin, with the setuid bit set.

# cp dgamelaunch /usr/bin/
# chmod 4755 /usr/bin/dgamelaunch

Installing Games

Installing the original Rogue seems appropriate for this article. Roguelike Gallery hosts builds for many early Roguelikes. John "Elwin" Edwards, Roguelike Gallery's creator, has done an amazing job of keeping and updating these antique games' source code to ensure they can run on modern operating systems. Roguelike Gallery also provides precompiled binaries [5].

I keep a convenient copy of Elwin's Roguelike collection on a personal server. You may download it with the following command:

$ curl -LO gopher://gopher.operationalsecurity.es/9/Software/Early Roguelikes/ElwinR-rl-74351bf23e5e.zip

Compile Rogue v3 (the earliest version of Rogue that was widely available) with:

$ unzip ElwinR-rl-74351bf23e5e.zip
$ cd ElwinR-rl-74351bf23e5e/rogue3
$ autoreconf
$ ./configure --enable-savedir=/var/games/rogue3/save--enable-scorefile=/var/games/rogue3/rogue.scr --enable-logfile=/var/games/rogue3/rogue.log
$ make

The enable-savedir, enable-scorefile, and enable-logfile switches are necessary to compile a game for systemwide installation. Keep in mind that Rogue will live in a chroot and won't be able to modify the rest of the operating system: For the game, the chroot directory will be all there is to the operating system.

Create the appropriate directories in the chroot and move the binary file to its final destination:

# cd /var/dgl
# mkdir -p var/games/rogue3/save
# mkdir -p usr/games
# mkdir -p dgldir/inprogress-rogue3
# cp $user_home/ElwinR-rl-74351bf23e5e/rogue3/rogue3 usr/games/
# chown -R games:games var/games dgldir

You can install additional games using similar steps. Keep in mind that you also must copy the libraries required by those games inside the chroot folder.

Rogue requires the appropriate ncurses library to live within the chroot.

# cp /lib/x86_64-linux-gnu/libncurses.so.6 /var/dgl/lib/x86_64-linux-gnu/

Configuring dgamelaunch

The game launcher's main configuration file resides in /var/dgl/etc/dgamelaunch.conf. Listing 2 shows an example to get you started. You may find more example configuration files in dgamelaunch's source code tarball, with the meaning of the variables properly explained.

Listing 2

dgamelaunch.conf

chroot_path = "/var/dgl"
dglroot = "/dgldir/"
banner = "/dgl-banner"
shed_uid = 5
shed_gid = 60
commands[register] = mkdir "%ruserdata/%n",
  mkdir "%ruserdata/%n/ttyrec",
  mkdir "%ruserdata/%n/ttyrec/rogue3"
commands[login] = mkdir "%ruserdata/%n",
  mkdir "%ruserdata/%n/ttyrec",
  mkdir "%ruserdata/%n/ttyrec/rogue3"
menu["mainmenu_anon"] {
  bannerfile = "/dgl_menu_main_anon.txt"
  commands["l"] = ask_login
  commands["r"] = ask_register
  commands["w"] = watch_menu
  commands["q"] = quit
}
menu["mainmenu_user"] {
  bannerfile = "/dgl_menu_main_user.txt"
  commands["c"] = chpasswd
  commands["e"] = chmail
  commands["w"] = watch_menu
  commands["3"] = play_game "RogueV3"
  commands["q"] = quit
}
menu["watchmenu_help"] {
  bannerfile = "/dgl_menu_watchmenu_help.txt"
  commands["qQ "] = return
}
DEFINE {
  game_path = "/usr/games/rogue3"
  game_name = "Rogue V3 (3.6)"
  short_name = "RogueV3"
  game_args = "rogue3", "-n", "%n"
  inprogressdir = "%rinprogress-rogue3/"
  ttyrecdir = "%ruserdata/%n/ttyrec/rogue3/"
  commands = cp "/var/games/rogue3/save/%u-%n.r3sav" "/var/games/rogue3/save/%u-%n.r3sav.bak"
}

The shed_uid and shed_gid variables define the user ID and group ID that dgamelaunch will drop privileges to after chrooting. In the example shown in Listing 2, this would be equivalent to games:games in a default Debian install. commands[login] and commands[register] tell dgamelaunch which actions to perform when a player logs in or registers, respectively. In Listing 2, they build a directory tree when the user registers and rebuild it if the user logs in and the tree does not exist.

The DEFINE clause provides a configuration for loading Rogue, game_path defines the location of the rogue3 binary within the chroot, while commands backs up each player's saved files each time the game is launched. ttyrecdir sets the directory in which the game session is recorded, just in case you want to watch your games later.

The final step is to configure the menus the users will see when logged into the game server. The default configuration suffices for testing, with the exception of the menu located at /var/dgl/dgl_menu_main_user.txt. By default, the menu is NetHack-centric. Listing 3 provides you with an appropriate alternative.

Listing 3

dgl_menu_main_user.txt

##
## $VERSION - network console game launcher
## Copyright (c) 2000-2009 The Dgamelaunch Team
## See http://nethack.wikia.com/wiki/dgamelaunch for more info
##
## Games on this server are recorded for in-progress viewing and playback!
Logged in as: $USERNAME
c) Change password                e) Change email address
w) Watch games in progress
3) Play Rogue V3
q) Quit
=>

Making the Service Available

You can test whether dgamelaunch works by invoking the dgamelaunch command as any regular user:

$ /usr/bin/dgamelaunch

If everything works as intended, dgamelaunch will chroot into /var/dgl, and you'll be presented with a menu (Figure 2), from which you can create a user account for the game service, play games, and watch other players.

Figure 2: The dgamelaunch menu.

In order to make the game available over Telnet, you must install the appropriate Telnet daemon and configure it. Dgamelaunch's README file offers instructions to do this. Begin by installing an inetd daemon and a Telnet server:

# apt-get install openbsd-inetd telnetd

OpenBSD's inetd is a superserver that takes incoming connections and passes them to the appropriate server, in this case telnetd. In order to make this configuration work, edit /etc/inetd.conf and ensure the line shown in Listing 4 is its only content.

Listing 4

inetd.conf

telnet stream tcp nowait root.root /usr/sbin/tcpd /usr/sbin/in.telnetd -h -L /var/dgl/dgamelaunch

This line instructs OpenBSD's inetd to call the Telnet daemon when a Telnet connection is received. In turn, the Telnet daemon is configured to use /var/dgl/dgamelaunch as a shell. Remember to reload the inetd daemon for the configuration to take effect:

# systemctl reload inetd

Recording Games

The server automatically records the games under /var/dgl/dgldir/userdata/$user/ttyrec/rogue3. The recordings can be watched using a ttyrec player, such as ttyplay. It is common for public dgamelaunch servers to offer the files for download via a web server.

Any user who connects while other people are playing may watch the ongoing games. If the game is patched for proper dgamelaunch integration, it is possible for the audience to send messages to the player. Games that support this include NetHack and Dungeon Crawl Stone Soup. Trying to deliver a message when a game has no spooldir variable set will crash dgamelaunch, but thankfully it will not affect the instances of the people who are running games.

Conclusions

Setting a server to play old terminal games is not easy, but it is certainly doable.

There are lots of games that can be run within a dgamelaunch service. Debian has the bsdgames package in its repository, with many terminal games from the pre-Linux era, including phantasia, battlestar, and trek. With a little tweaking, incorporating these games into the game server is possible.

You can see dgamelaunch in action using Roguelike Gallery's SSH service [6] (use rodney for the username and yendor for the password). Instructions for playing Rogue are in the source tarball. If you prefer to play a modern game with a retro feel, the Dungeon Crawl Stone Soup team keeps a list of servers for playing their games [7].

Infos

  1. History of Rogue: https://rlgallery.org/about/rogue3.html
  2. dgamelaunch's GitHub repository: https://github.com/paxed/dgamelaunch
  3. Dungeon Crawl Stone Soup: https://crawl.develz.org/
  4. NAO: https://alt.org/nethack/
  5. Roguelike Gallery: https://rlgallery.org
  6. Roguelike Gallery's dgamelaunch SSH service: https://rlgallery.org:8080
  7. Dungeon Crawl Stone Soup online: http://crawl.chaosforge.org/Playing_online

The Author

Rubén Llorente is a mechanical engineer, whose job is to ensure that the security measures of the IT infrastructure of a small clinic are both law compliant and safe. In addition, he is an OpenBSD enthusiast and a weapons collector.