diff --git a/.cvsignore b/.cvsignore
index a9b9051..b3235be 100644
--- a/.cvsignore
+++ b/.cvsignore
@@ -2,3 +2,5 @@ dgamelaunch
 tags
 cscope.out
 error.log
+y.tab.*
+lex.yy.*
diff --git a/Makefile b/Makefile
index abe6483..090e35d 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@ CC = gcc
 LDFLAGS = 
 CFLAGS = -g3 $(optimize) -Wall $(DEFS)
 DEFS = -DVERSION=\"$(VERSION)\"
-SRCS = virus.c ttyrec.c dgamelaunch.c io.c ttyplay.c stripgfx.c strlcpy.c strlcat.c
+SRCS = virus.c ttyrec.c dgamelaunch.c io.c ttyplay.c stripgfx.c strlcpy.c strlcat.c y.tab.o lex.yy.o
 OBJS = $(SRCS:.c=.o)
 LIBS = -lncurses -lcrypt -lutil
 
@@ -30,6 +30,15 @@ indent:
 	indent -nut -ts2 *.c *.h
 	rm -f *~
 
+lex.yy.c: config.l
+	flex $<
+
+y.tab.c: config.y
+	bison -d -y $<
+
+lex.yy.o: lex.yy.c
+y.tab.o: y.tab.c
+
 dist: clean indent
 	rm -rf $(NAME)-$(VERSION)
 	(cd .. && ln -sf $(CURDIR) $(NAME)-$(VERSION))
diff --git a/config.l b/config.l
new file mode 100644
index 0000000..1ffcbb6
--- /dev/null
+++ b/config.l
@@ -0,0 +1,105 @@
+/* Lexical analyzer for dgamelaunch's configuration file. */
+
+%option nounput
+%option noyywrap
+
+%{
+
+#include <stdio.h>
+#include <string.h>
+
+#include "y.tab.h"
+#include "dgamelaunch.h"
+
+unsigned int line = 1, col = 0;
+unsigned int comment_begin_line, comment_begin_col;
+
+static void ccomment(void);
+
+#define YY_USER_ACTION col += yyleng;
+
+%}
+
+VALUE		\".*\"
+MALSTRING	\"[^\"\n]*\n
+WHITE		[\t ]*
+COMMENT		^#.*
+LONGCOMMENT	"/*"
+
+%%             /* BEGIN RULES SECTION */
+
+{VALUE}	{
+  yytext[yyleng - 1] = '\0'; /* Kill the trailing quote */
+  yytext++; /* Kill the leading quote */
+  yylval.s = strdup(yytext);
+  return TYPE_VALUE;
+}
+
+{MALSTRING} {
+  /* yytext already contains a newline, no need for one here */
+  fprintf(stderr, "%s: unterminated string constant at line %d, start column %d: %s\n", config, line, col - yyleng + 1, yytext);
+}
+
+{WHITE}		{ }
+{COMMENT}	{ }
+{LONGCOMMENT}	{
+  comment_begin_line = line;
+  comment_begin_col = col - 1;
+  ccomment();
+}
+
+"="		{ return '='; }
+"shed_user"	{ return TYPE_SUSER; }
+"shed_group"	{ return TYPE_SGROUP; }
+"shed_uid"	{ return TYPE_SUID; }
+"shed_gid"	{ return TYPE_SGID; }
+"maxusers"	{ return TYPE_MAX; }
+
+"chroot_path"	{ return TYPE_PATH_CHROOT; }
+"nethack"	{ return TYPE_PATH_NETHACK; }
+"dglroot"	{ return TYPE_PATH_DGLDIR; }
+"spooldir"	{ return TYPE_PATH_SPOOL; }
+"banner"	{ return TYPE_PATH_BANNER; }
+"rc_template"	{ return TYPE_PATH_CANNED; }
+
+\n		{ line++; col = 0; }
+
+. {
+  fprintf(stderr, "%s: unrecognized token \"%s\" at line %d, column %d\n", config, yytext, line, col);
+}
+
+%%
+
+/* Ripped from ircd-hybrid/src/ircd_lexer.l */
+void ccomment(void)
+{
+  int c;
+
+  while (1)
+  {
+    while ((c = input()) != '*' && c != EOF)
+    {
+      if (c == '\n')
+      {
+        col = 0;
+	++line;
+      }
+      else
+        ++col;
+    }
+      
+    if (c == '*')
+    {
+      ++col;
+      while ((c = input()) == '*') ++col;
+      if (c == '/') { ++col; break; }
+    }
+    if (c == EOF)
+    {
+      fprintf(stderr, "%s: encountered end-of-file in comment starting on line %d, column %d\n", config, col, comment_begin_col);
+      exit(1);
+
+      break;
+    }
+  }
+}
diff --git a/config.y b/config.y
new file mode 100644
index 0000000..80f5ee7
--- /dev/null
+++ b/config.y
@@ -0,0 +1,171 @@
+%{
+
+#include <grp.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "dgamelaunch.h"
+
+extern int yylex(void);
+extern void yyerror(const char*);
+extern char *yytext;
+extern unsigned int line, col;
+
+static const char* lookup_token (int t);
+
+%}
+
+%union {
+	char* s;
+	int kt;
+	unsigned long i;
+}
+
+%token TYPE_SUSER TYPE_SGROUP TYPE_SGID TYPE_SUID TYPE_MAX
+%token TYPE_PATH_NETHACK TYPE_PATH_DGLDIR TYPE_PATH_SPOOL
+%token TYPE_PATH_BANNER TYPE_PATH_CANNED TYPE_PATH_CHROOT
+%token <s> TYPE_VALUE
+%token <i> TYPE_NUMBER
+%type  <kt> KeyType
+
+%%
+
+Configuration: KeyPairs
+	| { fprintf(stderr, "%s: no settings, proceeding with defaults\n", config); }
+	;
+
+KeyPairs: KeyPairs KeyPair
+	| KeyPair
+	;
+
+KeyPair: KeyType '=' TYPE_VALUE {
+  struct group* gr;
+  struct passwd* usr;
+  
+  if (!myconfig)
+    myconfig = calloc(1, sizeof(struct dg_config));
+
+  switch ($1)
+  {
+    case TYPE_SGROUP:
+      if ((gr = getgrnam($3)) != NULL)
+        myconfig->shed_gid = gr->gr_gid;
+      else
+        fprintf(stderr, "%s: no such group '%s'\n", config, $3);
+      
+      break;
+    case TYPE_SUSER:
+      if ((usr = getpwnam($3)) != NULL)
+        myconfig->shed_uid = usr->pw_uid;
+      else
+        fprintf(stderr, "%s: no such group '%s'\n", config, $3);
+     break;
+
+    case TYPE_PATH_CHROOT:
+      if (myconfig->chroot) free(myconfig->chroot);
+        myconfig->chroot = strdup ($3);
+      break;
+
+    case TYPE_PATH_NETHACK:
+      if (myconfig->nethack) free(myconfig->nethack);
+        myconfig->nethack = strdup ($3);
+      break;
+
+    case TYPE_PATH_DGLDIR:
+      if (myconfig->dglroot) free(myconfig->dglroot);
+        myconfig->dglroot = strdup ($3);
+      break;
+
+    case TYPE_PATH_BANNER:
+      if (myconfig->banner) free(myconfig->banner);
+        myconfig->banner = strdup($3);
+      break;
+
+    case TYPE_PATH_CANNED:
+      if (myconfig->rcfile) free(myconfig->rcfile);
+        myconfig->rcfile = strdup($3);
+      break;
+
+    case TYPE_PATH_SPOOL:
+      if (myconfig->spool) free (myconfig->spool);
+        myconfig->spool = strdup($3);
+      break;
+
+    default:
+      fprintf(stderr, "%s: token %s does not take a string, bailing out\n",
+        config, lookup_token($1));
+      exit(1);
+      
+  }
+
+  free($3);
+}
+	| KeyType '=' TYPE_NUMBER {
+  switch ($1)
+  {
+    case TYPE_SUID:
+      if (getpwuid($3) != NULL)
+        myconfig->shed_uid = $3;
+      else
+        fprintf(stderr, "%s: no such uid %lu\n", config, $3);
+	
+      break;
+
+    case TYPE_SGID:
+      if (getgrgid($3) != NULL)
+        myconfig->shed_gid = $3;
+      else
+        fprintf(stderr, "%s: no such gid %lu\n", config, $3);
+      break;
+
+    case TYPE_MAX:
+      myconfig->max = $3;
+      break;
+
+    default:
+      fprintf(stderr, "%s: token %s does not take a number, bailing out\n",
+        config, lookup_token($1)); 
+      exit(1);
+  }
+};
+
+KeyType : TYPE_SUSER	{ $$ = TYPE_SUSER; }
+	| TYPE_SGROUP	{ $$ = TYPE_SGROUP; }
+	| TYPE_SUID	{ $$ = TYPE_SUID; }
+	| TYPE_SGID	{ $$ = TYPE_SGID; }
+	| TYPE_MAX	{ $$ = TYPE_MAX; }
+	| TYPE_PATH_CHROOT	{ $$ = TYPE_PATH_CHROOT; }
+	| TYPE_PATH_NETHACK	{ $$ = TYPE_PATH_NETHACK; }
+	| TYPE_PATH_DGLDIR	{ $$ = TYPE_PATH_DGLDIR; }
+	| TYPE_PATH_SPOOL	{ $$ = TYPE_PATH_SPOOL; }
+	| TYPE_PATH_BANNER	{ $$ = TYPE_PATH_BANNER; }
+	| TYPE_PATH_CANNED	{ $$ = TYPE_PATH_CANNED; }
+	;
+
+%%
+
+const char* lookup_token (int t)
+{
+  switch (t)
+  {
+    case TYPE_SUSER: return "shed_user";
+    case TYPE_SGROUP: return "shed_group";
+    case TYPE_SUID: return "shed_uid";
+    case TYPE_SGID: return "shed_gid";
+    case TYPE_MAX: return "maxusers";
+    case TYPE_PATH_CHROOT: return "chroot_path";
+    case TYPE_PATH_NETHACK: return "nethack";
+    case TYPE_PATH_DGLDIR: return "dglroot";
+    case TYPE_PATH_SPOOL: return "spooldir";
+    case TYPE_PATH_BANNER: return "banner";
+    case TYPE_PATH_CANNED: return "rc_template";
+    default: abort();
+  }
+}
+
+void yyerror(char const* s)
+{
+  fprintf(stderr, "%s: couldn't parse \"%s\" at line %d, column %d: %s\n", config, yytext, line, col, s);
+}
diff --git a/dgamelaunch.c b/dgamelaunch.c
index 897aa74..9ec3313 100644
--- a/dgamelaunch.c
+++ b/dgamelaunch.c
@@ -68,6 +68,7 @@
 # define ARRAY_SIZE(x) sizeof(x) / sizeof(x[0])
 #endif
 
+#include <pwd.h>
 #include <grp.h>
 #include <time.h>
 #include <sys/resource.h>
@@ -82,6 +83,10 @@
 #include <unistd.h>
 #include <termios.h>
 
+#include "y.tab.h"
+extern FILE* yyin;
+extern int yyparse ();
+
 extern int vi_main (int argc, char **argv);
 extern int ttyplay_main (char *ttyfile, int mode, int rstripgfx);
 extern int ttyrec_main (char *);
@@ -92,6 +97,21 @@ extern struct winsize win;
 
 /* global variables */
 
+struct dg_config *myconfig = NULL;
+char* config = NULL;
+
+struct dg_config defconfig = {
+  "/var/lib/dgamelaunch/",
+  "/bin/nethack",
+  "/dgldir/",
+  "/dgl-banner",
+  "/dgl-default-rcfile",
+  "/var/mail/",
+  "games", "games",
+  5, 60, /* games:games in Debian */
+  64000 
+};
+
 int pid_game = 0;
 int loggedin = 0;
 char rcfilename[80];
@@ -103,9 +123,57 @@ struct dg_user **users = NULL;
 struct dg_user *me = NULL;
 struct dg_banner banner;
 
+void
+create_config ()
+{
+  FILE *config_file = NULL;
+
+  if (config)
+  {
+    if ((config_file = fopen(config, "r")) != NULL)
+    {
+      yyin = config_file;
+      yyparse();
+      fclose(config_file);
+      free (config);
+    }
+
+    /* Fill the rest with defaults */
+    if (!myconfig->shed_user && myconfig->shed_uid == 0)
+    {
+      struct passwd *pw;
+      if ((pw = getpwnam(defconfig.shed_user)))
+        myconfig->shed_uid = pw->pw_uid;
+      else
+	myconfig->shed_uid = defconfig.shed_uid;
+    }
+
+    if (!myconfig->shed_group && myconfig->shed_gid == 0)
+    {
+      struct group *gr;
+      if ((gr = getgrnam(defconfig.shed_group)))
+	myconfig->shed_gid = gr->gr_gid;
+      else
+	myconfig->shed_gid = defconfig.shed_gid;
+    }
+
+    if (myconfig->max == 0) myconfig->max = defconfig.max;
+    if (!myconfig->chroot) myconfig->chroot = strdup(defconfig.chroot);
+    if (!myconfig->nethack) myconfig->nethack = strdup(defconfig.nethack);
+    if (!myconfig->dglroot) myconfig->dglroot = strdup(defconfig.dglroot);
+    if (!myconfig->rcfile) myconfig->rcfile = strdup(defconfig.rcfile);
+    if (!myconfig->spool) myconfig->spool = strdup(defconfig.spool);
+  }
+  else
+  {
+    myconfig = &defconfig;
+  }
+}
+
 /* ************************************************************* */
 /* for ttyrec */
 
+
 void
 ttyrec_getmaster ()
 {
@@ -623,7 +691,10 @@ drawmenu ()
   /* for retarded clients */
   flood++;
   if (flood >= 20)
+  {
+    endwin();
     graceful_exit (119);
+  }
 }
 
 /* ************************************************************* */
@@ -791,7 +862,10 @@ newuser ()
         error = 1;
 
       if (strlen (buf) == 0)
+      {
+	free(me);
         return;
+      }
     }
 
   me->username = strdup (buf);
@@ -802,6 +876,7 @@ newuser ()
 
   if (!changepw ())                  /* Calling changepw instead to prompt twice. */
   {
+    free(me->username);
     free(me);
     me = NULL;
     return;
@@ -818,7 +893,7 @@ newuser ()
             "This is sent _nowhere_ but will be used if you ask the sysadmin for lost");
   mvaddstr (7, 1,
             "password help. Please use a correct one. It only benefits you.");
-  mvaddstr (8, 1, "80 character max. No ':' characters.");
+  mvaddstr (8, 1, "80 character max. No ':' characters. Blank line aborts.");
   mvaddstr (10, 1, "=> ");
 
   refresh ();
@@ -827,6 +902,15 @@ newuser ()
   if (strchr (buf, ':') != NULL)
     graceful_exit (113);
 
+  if (buf && *buf == '\0')
+  {
+    free (me->username);
+    free (me->password);
+    free (me);
+    me = NULL;
+    return;
+  }
+
   me->email = strdup (buf);
   me->env = calloc (1, 1);
 
diff --git a/dgamelaunch.conf b/dgamelaunch.conf
new file mode 100644
index 0000000..dfed64b
--- /dev/null
+++ b/dgamelaunch.conf
@@ -0,0 +1,41 @@
+/* This is a sample dgamelaunch configuration file. Comments like this as
+   well as bash-style comments are allowed in this configuration file. Each
+   configuration option will be explained along with its default value. */
+
+# shed_user: username to shed privileges to
+shed_user = "games"
+
+# shed_group: group name to shed privileges to
+shed_group = "games"
+
+# Alternatively, you may use the respective gids/uids. This is for Debian:
+shed_uid = 5
+shed_gid = 60
+
+# Note that shed_user and shed_group will always take precedence over
+# shed_uid and shed_gid.
+
+# Max amount of registered users to allow.
+maxusers = 64000
+
+# Path to a prepared chroot jail.
+chroot_path = "/var/lib/dgamelaunch/"
+
+# From inside the jail, the location of the nethack binary.
+nethack = "/bin/nethack"
+
+# From inside the jail, dgamelaunch's working directory for rcfiles/ttyrec/etc
+dglroot = "/dgldir/"
+
+# From inside the jail, where dgamelaunch should put mail - should match up with
+# NetHack settings.
+spooldir = "/var/mail/"
+
+# From inside the jail, location of a banner file that contains no more than
+# 14 lines of 80-column width text. Any more will be truncated.
+banner = "/dgl-banner"
+
+# From inside the jail, the default .nethackrc that is copied for new users.
+rc_template = "/dgl-default-rcfile"
+
+
diff --git a/dgamelaunch.h b/dgamelaunch.h
index b1eac56..60a0b4b 100644
--- a/dgamelaunch.h
+++ b/dgamelaunch.h
@@ -34,6 +34,25 @@ struct dg_game
   time_t idle_time;
 };
 
+struct dg_config
+{
+  char* chroot;
+  char* nethack;
+  char* dglroot;
+  char* banner;
+  char* rcfile;
+  char* spool;
+  char* shed_user;
+  char* shed_group;
+  uid_t shed_uid;
+  gid_t shed_gid;
+  unsigned long max;
+};
+
+extern char* config; /* file path */
+extern struct dg_config *myconfig;
+extern struct dg_config defconfig;
+
 #define SHED_UID 5              /* the uid to shed privs to */
 #define SHED_GID 60             /* the gid to shed privs to */
 #define MAXUSERS 64000          /* solves some preallocation issues. */
@@ -49,6 +68,7 @@ struct dg_game
 #define LOC_BANNER		"/dgl-banner"
 
 /* dgamelaunch.c function prototypes */
+extern void create_config (void);
 extern void ttyrec_getmaster (void);
 extern void gen_ttyrec_filename (void);
 extern void gen_inprogress_lock (pid_t pid);