Ultra-simplification of the build system for the text client.
authorArt Cancro <ajc@citadel.org>
Fri, 6 Apr 2018 18:38:11 +0000 (14:38 -0400)
committerArt Cancro <ajc@citadel.org>
Fri, 6 Apr 2018 18:38:11 +0000 (14:38 -0400)
Eliminated the use of GNU Autotools because Richard Stallman is
a communist who hates America.  Now we use "conf-IG-ure" , a new
build system which is patriotic and has screeching eagles and
American flags and Bibles and guns and can win at NASCAR without
even trying.

Seriously though ... autotools is just way too complex for the
21st Century.  I guess that means we're dropping support for the
extreme edge case obsolete systems so that we can make our own
software more maintainable.

I've also ripped out the billions of lines of experimental code
which does nothing and isn't being used anymore.

67 files changed:
ctdlsh/README [deleted file]
ctdlsh/README.txt [new file with mode: 0644]
textclient/.gitignore
textclient/Makefile [new file with mode: 0644]
textclient/Makefile.in [deleted file]
textclient/README.txt [new file with mode: 0644]
textclient/acinclude.m4 [deleted file]
textclient/aclocal.m4 [deleted file]
textclient/bootstrap
textclient/buildpackages [deleted file]
textclient/citadel.c [new file with mode: 0644]
textclient/citadel_ipc.c [new file with mode: 0644]
textclient/client_chat.c [new file with mode: 0644]
textclient/client_passwords.c [new file with mode: 0644]
textclient/commands.c [new file with mode: 0644]
textclient/config.guess [deleted file]
textclient/config.sub [deleted file]
textclient/configure [new file with mode: 0755]
textclient/configure.ac [deleted file]
textclient/install-sh [deleted file]
textclient/ipc_c_tcp.c [new file with mode: 0644]
textclient/ltmain.sh [deleted symlink]
textclient/m4/libtool.m4 [deleted symlink]
textclient/m4/ltoptions.m4 [deleted symlink]
textclient/m4/ltsugar.m4 [deleted symlink]
textclient/m4/ltversion.m4 [deleted symlink]
textclient/m4/lt~obsolete.m4 [deleted symlink]
textclient/md5.c [new file with mode: 0644]
textclient/messages.c [new file with mode: 0644]
textclient/missing [deleted file]
textclient/mkinstalldirs [deleted file]
textclient/rooms.c [new file with mode: 0644]
textclient/routines.c [new file with mode: 0644]
textclient/routines2.c [new file with mode: 0644]
textclient/screen.c [new file with mode: 0644]
textclient/scripts/mk_svn_revision.sh [deleted file]
textclient/src/citadel.c [deleted file]
textclient/src/citadel_ipc.c [deleted file]
textclient/src/client_chat.c [deleted file]
textclient/src/client_passwords.c [deleted file]
textclient/src/commands.c [deleted file]
textclient/src/ecrash.c [deleted file]
textclient/src/include/citadel_decls.h [deleted file]
textclient/src/include/citadel_ipc.h [deleted file]
textclient/src/include/client_chat.h [deleted file]
textclient/src/include/client_passwords.h [deleted file]
textclient/src/include/commands.h [deleted file]
textclient/src/include/ecrash.h [deleted file]
textclient/src/include/help.h [deleted file]
textclient/src/include/md5.h [deleted file]
textclient/src/include/messages.h [deleted file]
textclient/src/include/rooms.h [deleted file]
textclient/src/include/routines.h [deleted file]
textclient/src/include/routines2.h [deleted file]
textclient/src/include/screen.h [deleted file]
textclient/src/include/tuiconfig.h [deleted file]
textclient/src/include/typesize.h [deleted file]
textclient/src/ipc_c_tcp.c [deleted file]
textclient/src/md5.c [deleted file]
textclient/src/messages.c [deleted file]
textclient/src/rooms.c [deleted file]
textclient/src/routines.c [deleted file]
textclient/src/routines2.c [deleted file]
textclient/src/screen.c [deleted file]
textclient/src/tuiconfig.c [deleted file]
textclient/textclient.h [new file with mode: 0644]
textclient/tuiconfig.c [new file with mode: 0644]

diff --git a/ctdlsh/README b/ctdlsh/README
deleted file mode 100644 (file)
index 306490d..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-
-Someday this will be a Citadel server administration/maintenance shell.
-Right now it is unfinished.  You are welcome to tinker with it and
-submit patches, but if you attempt to use it in a production system or
-if you request support for the program, you will be told to die in a
-car fire.
-
-License: you are permitted to use this software under the condition that
-you refer to it as "open source" and not "free software," and that you
-refer to the operating system on which it runs as "Linux" and not
-"GNU/Linux."  By reading this notice you have already accepted the license,
-and you have irrevocably agreed that Richard Stallman is a communist.
-
diff --git a/ctdlsh/README.txt b/ctdlsh/README.txt
new file mode 100644 (file)
index 0000000..306490d
--- /dev/null
@@ -0,0 +1,13 @@
+
+Someday this will be a Citadel server administration/maintenance shell.
+Right now it is unfinished.  You are welcome to tinker with it and
+submit patches, but if you attempt to use it in a production system or
+if you request support for the program, you will be told to die in a
+car fire.
+
+License: you are permitted to use this software under the condition that
+you refer to it as "open source" and not "free software," and that you
+refer to the operating system on which it runs as "Linux" and not
+"GNU/Linux."  By reading this notice you have already accepted the license,
+and you have irrevocably agreed that Richard Stallman is a communist.
+
index 81bb5c3..dc9a574 100644 (file)
@@ -4,7 +4,6 @@
 *.gcda
 Make_modules
 Make_sources
-Makefile
 Makefile.in
 locale
 aclocal.m4
@@ -19,7 +18,6 @@ citmail
 citserver
 config.log
 config.status
-configure
 configure-stamp
 ctdlmigrate
 database_cleanup.sh
diff --git a/textclient/Makefile b/textclient/Makefile
new file mode 100644 (file)
index 0000000..d52a704
--- /dev/null
@@ -0,0 +1,35 @@
+# MAKEFILE FOR CITADEL TEXT CLIENT
+# Copyright (c) 2016-2018 by the citadel.org team
+
+# config.mk is generated by ./configure
+include config.mk
+
+OBJS := citadel.o citadel_ipc.o client_chat.o client_passwords.o commands.o ipc_c_tcp.o md5.o messages.o rooms.o routines2.o routines.o screen.o tuiconfig.o
+
+
+# link
+citadel: $(OBJS) config.mk
+       gcc $(OBJS) $(LDFLAGS) -lcitadel -o citadel
+
+# pull in dependency info for *existing* .o files
+-include $(OBJS:.o=.d)
+
+# compile and generate dependency info
+%.o: %.c
+       gcc -c $(CFLAGS) -DCTDLDIR=\"$(CTDLDIR)\" $*.c -o $*.o
+       gcc -MM $(CFLAGS) $*.c > $*.d
+
+config.mk:
+       ./configure
+
+clean:
+       rm -f citadel *.o *.d
+
+distclean: clean
+       rm -f config.mk
+
+install: citadel
+       install citadel $(BINDIR)/citadel
+
+uninstall:
+       rm -vf $(BINDIR)/citadel
diff --git a/textclient/Makefile.in b/textclient/Makefile.in
deleted file mode 100644 (file)
index 12a9372..0000000
+++ /dev/null
@@ -1,214 +0,0 @@
-# Makefile for Citadel
-#
-# NOTE: normally you should not have to modify the Makefile.  All
-# system-dependent configuration is in the "configure" script, which
-# uses "Makefile.in" to generate a "Makefile".  In the rare instance
-# that you have to modify something here, please take note:
-# 1. Edit Makefile.in, -not- Makefile.
-# 2. Send e-mail to ajc@uncensored.citadel.org and let me know what you
-#    did, so any necessary changes can be put into the next release.
-#
-########################################################################
-
-prefix=@prefix@
-srcdir=@srcdir@
-VPATH=$(srcdir)
-
-TARGETS=@TARGETS@
-RUN_DIR=@MAKE_RUN_DIR@
-SPOOL_DIR=@MAKE_SPOOL_DIR@
-ETC_DIR=@MAKE_ETC_DIR@
-DATA_DIR=@MAKE_DATA_DIR@
-STATICDATA_DIR=@MAKE_STATICDATA_DIR@
-HELP_DIR=@MAKE_HELP_DIR@
-DOC_DIR=@MAKE_DOC_DIR@
-UTILBIN_DIR=@MAKE_UTILBIN_DIR@
-DEPEND_FLAG=@DEPEND_FLAG@
-all: buildinfo $(TARGETS)
-
-.SUFFIXES: .o .d .c
-
-EXEEXT=@EXEEXT@
-
-CLIENT_TARGETS=citadel$(EXEEXT)
-
-ACLOCAL=@ACLOCAL@
-AUTOCONF=@AUTOCONF@
-chkpwd_LIBS=@chkpwd_LIBS@
-CC=@CC@
-CFLAGS=@CFLAGS@ -I ./include/
-CPPFLAGS=@CPPFLAGS@ -I. -I ./src/include/
-DATABASE=@DATABASE@
-DEFS=@DEFS@ -DDIFF=\"@DIFF@\" -DPATCH=\"@PATCH@\"
-LDFLAGS=@LDFLAGS@
-LIBS=@LIBS@
-LIBOBJS=@LIBOBJS@
-INSTALL=@INSTALL@
-INSTALL_DATA=@INSTALL_DATA@
-RESOLV=@RESOLV@
-SHELL=/bin/sh
-SERVER_LDFLAGS=@SERVER_LDFLAGS@
-SERVER_LIBS=@SERVER_LIBS@
-SETUP_LIBS=@SETUP_LIBS@
-YACC=@YACC@
-DIFF=@DIFF@
-PATCH=@PATCH@
-LOCALEDIR=@LOCALEDIR@
-
-# End configuration section
-
-.SILENT:
-
-
-SOURCES=src/client_chat.c \
-       src/client_passwords.c \
-       src/commands.c \
-       src/messages.c \
-       src/rooms.c \
-       src/routines.c \
-       src/routines2.c \
-       src/tuiconfig.c \
-       src/citadel.c \
-       src/citadel_ipc.c \
-       src/ecrash.c \
-       src/ipc_c_tcp.c \
-       src/getutline.c \
-       src/screen.c
-
-# for VPATH builds (invoked by configure)
-mkdir-init:
-       mkdir -p textclient
-       mkdir locale
-
-
-noinst: $(NOINST_TARGETS)
-client: $(CLIENT_TARGETS)
-
-citadel$(EXEEXT): src/citadel.o  \
-       src/client_chat.o \
-       src/client_passwords.o \
-       src/commands.o \
-       src/md5.o \
-       src/messages.o \
-       src/rooms.o \
-       src/routines.o \
-       src/routines2.o \
-       src/tuiconfig.o \
-       src/screen.o \
-       src/ecrash.o \
-       src/citadel_ipc.o \
-       src/ipc_c_tcp.o \
-       $(LIBOBJS)
-       $(CC) src/citadel.o \
-       src/client_chat.o \
-       src/client_passwords.o \
-       src/commands.o \
-       src/md5.o \
-       src/messages.o \
-       src/rooms.o \
-       src/routines.o \
-       src/routines2.o \
-       src/tuiconfig.o \
-       src/screen.o \
-       src/ecrash.o \
-       src/citadel_ipc.o \
-       src/ipc_c_tcp.o \
-       $(LIBOBJS) \
-       $(LDFLAGS) -o citadel $(LIBS)
-
-%.o: %.c ${HEADERS}
-       echo "CC $<"
-       $(CC) $(CFLAGS) $(CPPFLAGS) $(DEFS) -c $< -o $@
-
-.PHONY: install-data install-doc install-exec clean cleaner distclean
-
-install-locale:
-
-install: install-exec install-data install-doc install-locale
-       @echo 
-       @echo Installation is complete.
-       @echo 
-
-install-new: install-exec-new install-data-new install-doc-new install-locale
-       @echo 
-       @echo Installation is complete.
-       @echo 
-
-upgrade: install-exec install-doc
-       @echo
-       @echo Upgrade is complete.
-       @echo
-
-install-data:
-       @for i in citadel.rc \
-                `find $(srcdir)/help $(srcdir)/messages $(srcdir)/network -type f | grep -v .svn`; do \
-               echo $(INSTALL_DATA) $$i $(DESTDIR)$(prefix)/$$i; \
-               $(INSTALL_DATA) $$i $(DESTDIR)$(prefix)/$$i; \
-       done
-
-install-data-new:
-       $(srcdir)/mkinstalldirs $(DESTDIR)$(ETC_DIR)/
-       $(INSTALL_DATA) $(srcdir)/citadel.rc $(DESTDIR)$(ETC_DIR)/citadel.rc
-
-install-doc:
-       @$(srcdir)/mkinstalldirs $(DESTDIR)$(prefix)/docs
-
-install-doc-new:
-       @$(srcdir)/mkinstalldirs $(DESTDIR)$(DOC_DIR)/docs
-
-install-exec: all
-       @for i in $(CLIENT_TARGETS) ; do \
-               if test -f $$i; then \
-                       echo $(INSTALL) $$i $(DESTDIR)$(prefix)/$$i; \
-                       $(INSTALL) $$i $(DESTDIR)$(prefix)/$$i; \
-               fi \
-       done
-
-install-exec-new: all
-       $(srcdir)/mkinstalldirs $(DESTDIR)/usr/bin; 
-       @for i in $(CLIENT_TARGETS); do \
-               if test -f $$i; then \
-                       echo $(INSTALL) $$i $(DESTDIR)/usr/bin/$$i; \
-                       $(INSTALL) $$i $(DESTDIR)/usr/bin/$$i; \
-               fi \
-       done
-
-clean:
-       rm -fr locale/*
-       rm -f *.o 
-       rm -f $(CLIENT_TARGETS) 
-
-cleaner: clean
-       rm -rf $(CLIENT_TARGETS)
-
-distclean: cleaner
-       find . -name '*~' -o -name '.#*' | xargs rm -f
-       rm -f po/Makefile 
-       rm -f Makefile sysdep.h config.cache config.log config.status *.d 
-
-
-.c.d:
-       @echo Checking dependencies for $<
-       @$(CC) $(DEPEND_FLAG) $(CPPFLAGS) $< | sed -e 's!$*.o!$*.o $*/.o $@!' > $@
-       @test -s $@ || rm -f $@
-
-Makefile: $(srcdir)/Makefile.in config.status
-       CONFIG_FILES=Makefile CONFIG_HEADERS= $(SHELL) ./config.status
-
-config.status: $(srcdir)/configure
-       $(SHELL) ./config.status --recheck
-
-$(srcdir)/configure: $(srcdir)/configure.ac $(srcdir)/aclocal.m4
-       cd $(srcdir) && $(AUTOCONF)
-
-$(srcdir)/aclocal.m4: $(srcdir)/acinclude.m4
-       cd $(srcdir) && $(ACLOCAL)
-
-buildinfo:
-       echo
-       echo "Dependencies: $(CC) $(DEPEND_FLAG) $(CPPFLAGS) $< | sed -e 's!$*.o!$*.o $*/.o $@!' > $@"
-       echo "Complie: $(CC) $(CFLAGS) $(CPPFLAGS) $(DEFS) -c $< -o $@ "
-       echo "LDFLAGS: $(LDFLAGS)"
-       echo
-
--include $(DEP_FILES)
diff --git a/textclient/README.txt b/textclient/README.txt
new file mode 100644 (file)
index 0000000..a80489f
--- /dev/null
@@ -0,0 +1,10 @@
+
+This is a text mode user interface for the Citadel system.  It presents
+a Citadel site to users in the form of a traditional BBS.
+
+All code is Copyright (c) 1987-2018 by the citadel.org team, and is released
+under the terms of the GNU General Public License v3.  As a special exception,
+the Citadel team requires all users of this code to agree that our favorite
+software model is called "open source" and NOT "free software", and that our
+favorite operating system is called "Linux" and NOT "GNU/Linux".
+
diff --git a/textclient/acinclude.m4 b/textclient/acinclude.m4
deleted file mode 100644 (file)
index 0941acf..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-# CIT_STRUCT_TM
-# ------------------
-# Figure out how to get the current GMT offset.  If `struct tm' has a
-# `tm_gmtoff' member, define `HAVE_STRUCT_TM_TM_GMTOFF'.  Otherwise, if the
-# external variable `timezone' is found, define `HAVE_TIMEZONE'.
-AC_DEFUN([CIT_STRUCT_TM],
-[AC_REQUIRE([AC_STRUCT_TM])dnl
-AC_CHECK_MEMBERS([struct tm.tm_gmtoff],,,[#include <sys/types.h>
-#include <$ac_cv_struct_tm>
-])
-if test "$ac_cv_member_struct_tm_tm_gmtoff" != yes; then
-  AC_CACHE_CHECK(for timezone, ac_cv_var_timezone,
-[AC_TRY_LINK(
-[#include <time.h>],
-[printf("%ld", (long)timezone);], ac_cv_var_timezone=yes, ac_cv_var_timezone=no)])
-  if test $ac_cv_var_timezone = yes; then
-    AC_DEFINE(HAVE_TIMEZONE, 1,
-              [Define if you don't have `tm_gmtoff' but do have the external
-               variable `timezone'.])
-  fi
-fi
-])# CIT_STRUCT_TM
-
-AC_DEFUN([AC_CHECK_DB],[
-for lib in $1
-do
-   AS_VAR_PUSHDEF([ac_tr_db], [ac_cv_db_lib_${lib}])dnl
-   bogo_saved_LIBS="$LIBS"
-   LIBS="$LIBS -l$lib"
-   AC_CACHE_CHECK([for db_create in -l${lib}], ac_tr_db,
-      [AC_TRY_LINK([#include <db.h>], [int foo=db_create((void *)0, (void *) 0, 
-0 )],
-                   [AS_VAR_SET(ac_tr_db, yes)],
-                   [AS_VAR_SET(ac_tr_db, no)])
-      ])
-   AS_IF([test AS_VAR_GET(ac_tr_db) = yes],
-         [$2
-         LIBS="$bogo_saved_LIBS"
-         SERVER_LIBS="$SERVER_LIBS -l$lib"
-          db=yes],
-         [LIBS="$bogo_saved_LIBS"
-          db=no])
-   AS_VAR_POPDEF([ac_tr_db])dnl
-test "$db" = "yes" && break
-done
-if test "$db" = "no"; then
-$3
-fi
-])# AC_CHECK_DB
-
diff --git a/textclient/aclocal.m4 b/textclient/aclocal.m4
deleted file mode 100644 (file)
index 440da76..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-# generated automatically by aclocal 1.14.1 -*- Autoconf -*-
-
-# Copyright (C) 1996-2013 Free Software Foundation, Inc.
-
-# This file is free software; the Free Software Foundation
-# gives unlimited permission to copy and/or distribute it,
-# with or without modifications, as long as this notice is preserved.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
-# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
-# PARTICULAR PURPOSE.
-
-m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])])
-# AM_AUX_DIR_EXPAND                                         -*- Autoconf -*-
-
-# Copyright (C) 2001-2013 Free Software Foundation, Inc.
-#
-# This file is free software; the Free Software Foundation
-# gives unlimited permission to copy and/or distribute it,
-# with or without modifications, as long as this notice is preserved.
-
-# For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets
-# $ac_aux_dir to '$srcdir/foo'.  In other projects, it is set to
-# '$srcdir', '$srcdir/..', or '$srcdir/../..'.
-#
-# Of course, Automake must honor this variable whenever it calls a
-# tool from the auxiliary directory.  The problem is that $srcdir (and
-# therefore $ac_aux_dir as well) can be either absolute or relative,
-# depending on how configure is run.  This is pretty annoying, since
-# it makes $ac_aux_dir quite unusable in subdirectories: in the top
-# source directory, any form will work fine, but in subdirectories a
-# relative path needs to be adjusted first.
-#
-# $ac_aux_dir/missing
-#    fails when called from a subdirectory if $ac_aux_dir is relative
-# $top_srcdir/$ac_aux_dir/missing
-#    fails if $ac_aux_dir is absolute,
-#    fails when called from a subdirectory in a VPATH build with
-#          a relative $ac_aux_dir
-#
-# The reason of the latter failure is that $top_srcdir and $ac_aux_dir
-# are both prefixed by $srcdir.  In an in-source build this is usually
-# harmless because $srcdir is '.', but things will broke when you
-# start a VPATH build or use an absolute $srcdir.
-#
-# So we could use something similar to $top_srcdir/$ac_aux_dir/missing,
-# iff we strip the leading $srcdir from $ac_aux_dir.  That would be:
-#   am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"`
-# and then we would define $MISSING as
-#   MISSING="\${SHELL} $am_aux_dir/missing"
-# This will work as long as MISSING is not called from configure, because
-# unfortunately $(top_srcdir) has no meaning in configure.
-# However there are other variables, like CC, which are often used in
-# configure, and could therefore not use this "fixed" $ac_aux_dir.
-#
-# Another solution, used here, is to always expand $ac_aux_dir to an
-# absolute PATH.  The drawback is that using absolute paths prevent a
-# configured tree to be moved without reconfiguration.
-
-AC_DEFUN([AM_AUX_DIR_EXPAND],
-[AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl
-# Expand $ac_aux_dir to an absolute path.
-am_aux_dir=`cd "$ac_aux_dir" && pwd`
-])
-
-# Fake the existence of programs that GNU maintainers use.  -*- Autoconf -*-
-
-# Copyright (C) 1997-2013 Free Software Foundation, Inc.
-#
-# This file is free software; the Free Software Foundation
-# gives unlimited permission to copy and/or distribute it,
-# with or without modifications, as long as this notice is preserved.
-
-# AM_MISSING_PROG(NAME, PROGRAM)
-# ------------------------------
-AC_DEFUN([AM_MISSING_PROG],
-[AC_REQUIRE([AM_MISSING_HAS_RUN])
-$1=${$1-"${am_missing_run}$2"}
-AC_SUBST($1)])
-
-# AM_MISSING_HAS_RUN
-# ------------------
-# Define MISSING if not defined so far and test if it is modern enough.
-# If it is, set am_missing_run to use it, otherwise, to nothing.
-AC_DEFUN([AM_MISSING_HAS_RUN],
-[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
-AC_REQUIRE_AUX_FILE([missing])dnl
-if test x"${MISSING+set}" != xset; then
-  case $am_aux_dir in
-  *\ * | *\    *)
-    MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;;
-  *)
-    MISSING="\${SHELL} $am_aux_dir/missing" ;;
-  esac
-fi
-# Use eval to expand $SHELL
-if eval "$MISSING --is-lightweight"; then
-  am_missing_run="$MISSING "
-else
-  am_missing_run=
-  AC_MSG_WARN(['missing' script is too old or missing])
-fi
-])
-
-m4_include([acinclude.m4])
index d4c23fc..c52d3c2 100755 (executable)
@@ -1,34 +1,3 @@
 #!/bin/sh
-#
-# run me after checking Citadel out of the source code repository.
 
-# Remove any vestiges of pre-6.05 build environments
-rm -f .libs modules *.so *.lo *.la 2>/dev/null
-
-echo ... running aclocal ...
-aclocal  -I m4
-
-echo ... running autoconf ...
-autoconf
-
-# If your autoconf version changes, the autom4te.cache stuff will mess you up.
-# Get rid of it.
-echo ... removing autoheader cache files ...
-rm -rf autom4te*.cache
-
-echo ... running autoheader ...
-autoheader
-
-echo ... running mk_svn_revision.sh ...
-./scripts/mk_svn_revision.sh
-
-echo
-echo This script has been tested with autoconf 2.53 and
-echo automake 1.5. Other versions may work, but we recommend
-echo the latest echo compatible versions of these.
-echo
-echo Also note that autoconf and automake should be configured
-echo with the same prefix.
-echo
-
-grep '^#define CLIENT_VERSION' src/include/citadel_ipc.h | sed 's/[^0-9]*//g' >package-version.txt
+exit 0
diff --git a/textclient/buildpackages b/textclient/buildpackages
deleted file mode 100755 (executable)
index c784a48..0000000
+++ /dev/null
@@ -1,122 +0,0 @@
-#!/bin/bash
-# find out the package version from conf
-if test -f Makefile; then
-    make distclean
-fi
-
-#./bootstrap
-
-export `grep PACKAGE_VERSION= configure |sed -e "s;';;g" -e "s;PACKAGE;CITADEL;"`
-
-PACKAGE_VERSION=`cat packageversion`
-DATE=`date '+%a, %d %b %Y %H:%I:00 %z'`
-ACTUAL_DIR=`pwd`
-
-
-rm -rf debian/textclient debian/citadel-common debian/citadel-doc debian/citadel-mta debian/citadel-server debian/citadel-suite debian/tmp
-if echo "$ACTUAL_DIR" |grep -q "$CITADEL_VERSION"; then
-       echo "directory ($ACTUAL_DIR) naming scheme seems right. nothing done."
-else
-       done=false
-       if test -L "$ACTUAL_DIR"; then 
-               SYMLINK_=`pwd`
-               SYMLINK=`ls -l $SYMLINK_|sed "s;.*-> ;;"`
-               if ls -l $SYMLINK_|grep -q "$CITADEL_VERSION"; then
-                       done=true
-               fi
-       else
-               SYMLINK=`pwd|sed "s;.*/;;"`
-       fi
-       if test "$done" = "false"; then 
-               cd ..
-               if test ! -L "textclient-$CITADEL_VERSION"; then
-                       ln -sf textclient "textclient-$CITADEL_VERSION"
-               fi
-               cd "textclient-$CITADEL_VERSION"
-       else
-               cd "../textclient-$CITADEL_VERSION"
-       fi
-       
-fi
-
-
-case $1 in
-    debian)
-       if grep -q "($CITADEL_VERSION" debian/changelog; then
-           echo rebuilding package.
-       else
-           echo "Upstream Version higher than local."
-           
-       fi
-       if test "$2" == "src"; then
-           cd ..
-           rm -rf tmp
-           mkdir tmp
-           cp -rL textclient-$CITADEL_VERSION tmp
-           cd tmp/textclient-$CITADEL_VERSION
-           rm -rf `find -name .svn ` svn*tmp config.log config.status
-           find -type f -exec chmod a-x {} \;
-           chmod a+x configure *.sh *.sh.in debian/rules debian/*inst* debian/rules mkinstalldirs
-           cd ..
-           tar -chzf textclient_${CITADEL_VERSION}.orig.tar.gz textclient-${CITADEL_VERSION}/ --exclude "debian/*"
-           pwd
-           cd  textclient-${CITADEL_VERSION}; debuild -S -sa -kw.goesgens@outgesourced.org
-       else
-           fakeroot dpkg-buildpackage
-       fi
-       ;;
-    csw)
-       if !test -d ~/pkgs/; then
-           mkdir ~/pkgs
-       fi
-       echo "
-PKG=CSWcitadel-textclient
-NAME=citadel - The groupware server for Web 2.0
-VERSION=${PACKAGE_VERSION}
-CATEGORY=application
-VENDOR=http://www.citadel.org/  packaged for CSW by Wilfried Goesgens
-HOTLINE=https://uncensored.citadel.org/ Room citadel support
-EMAIL=citadel@outgesourced.org
-" >~/pkgs/citadel
-       export LDFLAGS='-L/opt/csw/lib -L /usr/local/lib'
-       export CFLAGS='-I/opt/csw/include -I/usr/local/include  -DDISABLE_CURSES'
-       ./configure \
-           --with-db=/opt/csw/bdb44 \
-           --with-ical=/usr/local/ \
-           --with-prefix=/opt/csw/ \
-           --with-datadir=/opt/csw/var/lib/citadel \
-           --with-sysconfdir=/opt/csw/etc/citadel \
-           --with-ssldir=/opt/csw/etc/ssl/citadel/ \
-           --with-spooldir=/opt/csw/var/spool/citadel \
-           --with-rundir=/opt/csw/var/run/citadel \
-           --with-docdir=/opt/csw/share/doc/citadel-doc/ \
-           --with-pam \
-           --with-zlib \
-           --with-ldap \
-           --with-libsieve
-           
-       gmake citserver aidepost msgform citmail userlist sendcommand base64 whobbs citadel
-       gmake DESTDIR=$ACTUAL_DIR/cswstage install-new
-       
-
-
-       ;;
-    sourcedist)
-       cd ..; tar \
-           --exclude ".gitignore" \
-           --exclude "*.lo" \
-           --exclude "*.o" \
-           --exclude "*.d" \
-           --exclude "autom4te.cache/*" \
-           --exclude "debian/*" \
-           --exclude "sysdep.h" \
-           \
-           -chvzf textclient-$CITADEL_VERSION.tar.gz textclient-$CITADEL_VERSION/
-       ;;
-       version)
-               echo This would build textclient-$CITADEL_VERSION
-               ;;
-       *)
-               echo "Not yet implemented. we have: debian "
-               ;;
-esac
diff --git a/textclient/citadel.c b/textclient/citadel.c
new file mode 100644 (file)
index 0000000..d01f6fe
--- /dev/null
@@ -0,0 +1,2152 @@
+/*
+ * Main source module for the client program.
+ *
+ * Copyright (c) 1987-2018 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "textclient.h"
+
+#define IFEXPERT if (userflags&US_EXPERT)
+#define IFNEXPERT if ((userflags&US_EXPERT)==0)
+#define IFAIDE if (axlevel>=AxAideU)
+#define IFNAIDE if (axlevel<AxAideU)
+
+int rordercmp(struct ctdlroomlisting *r1, struct ctdlroomlisting *r2);
+march *marchptr = NULL;
+extern char *moreprompt;
+
+/* globals associated with the client program */
+char temp[PATH_MAX];           /* Name of general-purpose temp file */
+char temp2[PATH_MAX];          /* Name of general-purpose temp file */
+char tempdir[PATH_MAX];                /* Name of general-purpose temp directory */
+char printcmd[SIZ];            /* print command */
+int editor_pid = (-1);
+char fullname[USERNAME_SIZE];
+unsigned room_flags;
+unsigned room_flags2;
+int entmsg_ok = 0;
+char room_name[ROOMNAMELEN];
+char *uglist[UGLISTLEN]; /* size of the ungoto list */
+long uglistlsn[UGLISTLEN]; /* current read position for all the ungoto's. Not going to make any friends with this one. */
+int uglistsize = 0;
+char is_mail = 0;              /* nonzero when we're in a mail room */
+char axlevel = AxDeleted;              /* access level */
+char is_room_aide = 0;         /* boolean flag, 1 if room admin */
+int timescalled;
+int posted;
+unsigned userflags;
+long usernum = 0L;             /* user number */
+time_t lastcall = 0L;          /* Date/time of previous login */
+char newnow;
+long highest_msg_read;         /* used for <A>bandon room cmd */
+long maxmsgnum;                        /* used for <G>oto */
+char sigcaught = 0;
+char rc_username[USERNAME_SIZE];
+char rc_password[32];
+char hostbuf[SIZ];
+char portbuf[SIZ];
+char rc_floor_mode;
+char floor_mode;
+char curr_floor = 0;           /* number of current floor */
+char floorlist[128][SIZ];      /* names of floors */
+int termn8 = 0;                        /* Set to nonzero to cause a logoff */
+int secure;                    /* Set to nonzero when wire is encrypted */
+
+extern char instant_msgs;      /* instant messages waiting! */
+extern int rc_ansi_color;      /* ansi color value from citadel.rc */
+extern int next_lazy_cmd;
+
+CtdlIPC *ipc_for_signal_handlers;      /* KLUDGE cover your eyes */
+int enable_syslog = 0;
+
+
+/*
+ * here is our 'clean up gracefully and exit' routine
+ */
+void ctdl_logoff(char *file, int line, CtdlIPC *ipc, int code)
+{
+       int lp;
+
+       if (editor_pid > 0) {   /* kill the editor if it's running */
+               kill(editor_pid, SIGHUP);
+       }
+
+       /* Free the ungoto list */
+       for (lp = 0; lp < uglistsize; lp++) {
+               free(uglist[lp]);
+       }
+
+/* Shut down the server connection ... but not if the logoff code is 3,
+ * because that means we're exiting because we already lost the server.
+ */
+       if (code != 3) {
+               CtdlIPCQuit(ipc);
+       }
+
+/*
+ * now clean up various things
+ */
+       unlink(temp);
+       unlink(temp2);
+       nukedir(tempdir);
+
+       /* Violently kill off any child processes if Citadel is
+        * the login shell. 
+        */
+       if (getppid() == 1) {
+               kill(0 - getpgrp(), SIGTERM);
+               sleep(1);
+               kill(0 - getpgrp(), SIGKILL);
+       }
+       color(ORIGINAL_PAIR);   /* Restore the old color settings */
+       stty_ctdl(SB_RESTORE);  /* return the old terminal settings */
+       /* 
+        * uncomment the following if you need to know why Citadel exited
+       printf("*** Exit code %d at %s:%d\n", code, file, line);
+       sleep(2);
+        */
+       exit(code);             /* exit with the proper exit code */
+}
+
+
+
+/*
+ * signal catching function for hangups...
+ */
+void dropcarr(int signum)
+{
+       logoff(NULL, 3);        /* No IPC when server's already gone! */
+}
+
+
+
+/*
+ * catch SIGCONT to reset terminal modes when were are put back into the
+ * foreground.
+ */
+void catch_sigcont(int signum)
+{
+       stty_ctdl(SB_LAST);
+       signal(SIGCONT, catch_sigcont);
+}
+
+
+/* general purpose routines */
+
+/* display a file */
+void formout(CtdlIPC *ipc, char *name)
+{
+       int r;                  /* IPC return code */
+       char buf[SIZ];
+       char *text = NULL;
+
+       r = CtdlIPCSystemMessage(ipc, name, &text, buf);
+       if (r / 100 != 1) {
+               scr_printf("%s\n", buf);
+               return;
+       }
+       if (text) {
+               fmout(screenwidth, NULL, text, NULL, 1);
+               free(text);
+       }
+}
+
+
+void userlist(CtdlIPC *ipc, char *patn)
+{
+       char buf[SIZ];
+       char fl[SIZ];
+       struct tm tmbuf;
+       time_t lc;
+       int r;                          /* IPC response code */
+       char *listing = NULL;
+
+       r = CtdlIPCUserListing(ipc, patn, &listing, buf);
+       if (r / 100 != 1) {
+               scr_printf("%s\n", buf);
+               return;
+       }
+
+       scr_printf("       User Name           Num  L Last Visit Logins Messages\n");
+       scr_printf("------------------------- ----- - ---------- ------ --------\n");
+       if (listing != NULL) while (!IsEmptyStr(listing)) {
+               extract_token(buf, listing, 0, '\n', sizeof buf);
+               remove_token(listing, 0, '\n');
+
+               if (sigcaught == 0) {
+                   extract_token(fl, buf, 0, '|', sizeof fl);
+                   if (pattern(fl, patn) >= 0) {
+                       scr_printf("%-25s ", fl);
+                       scr_printf("%5ld %d ", extract_long(buf, 2),
+                              extract_int(buf, 1));
+                       lc = extract_long(buf, 3);
+                       localtime_r(&lc, &tmbuf);
+                       scr_printf("%02d/%02d/%04d ",
+                              (tmbuf.tm_mon + 1),
+                              tmbuf.tm_mday,
+                              (tmbuf.tm_year + 1900));
+                       scr_printf("%6ld %8ld\n", extract_long(buf, 4), extract_long(buf, 5));
+                   }
+
+               }
+       }
+       free(listing);
+       scr_printf("\n");
+}
+
+
+/*
+ * grab assorted info about the user...
+ */
+void load_user_info(char *params)
+{
+       extract_token(fullname, params, 0, '|', sizeof fullname);
+       axlevel = extract_int(params, 1);
+       timescalled = extract_int(params, 2);
+       posted = extract_int(params, 3);
+       userflags = extract_int(params, 4);
+       usernum = extract_long(params, 5);
+       lastcall = extract_long(params, 6);
+}
+
+
+/*
+ * Remove a room from the march list.  'floornum' is ignored unless
+ * 'roomname' is set to _FLOOR_, in which case all rooms on the requested
+ * floor will be removed from the march list.
+ */
+void remove_march(char *roomname, int floornum)
+{
+       struct march *mptr, *mptr2;
+
+       if (marchptr == NULL)
+               return;
+
+       if ((!strcasecmp(marchptr->march_name, roomname))
+           || ((!strcasecmp(roomname, "_FLOOR_")) && (marchptr->march_floor == floornum))) {
+               mptr = marchptr->next;
+               free(marchptr);
+               marchptr = mptr;
+               return;
+       }
+       mptr2 = marchptr;
+       for (mptr = marchptr; mptr != NULL; mptr = mptr->next) {
+
+               if ((!strcasecmp(mptr->march_name, roomname))
+                   || ((!strcasecmp(roomname, "_FLOOR_"))
+                       && (mptr->march_floor == floornum))) {
+
+                       mptr2->next = mptr->next;
+                       free(mptr);
+                       mptr = mptr2;
+               } else {
+                       mptr2 = mptr;
+               }
+       }
+}
+
+
+/*
+ * Locate the room on the march list which we most want to go to.  Each room
+ * is measured given a "weight" of preference based on various factors.
+ */
+char *pop_march(int desired_floor, struct march *_march)
+{
+       static char TheRoom[ROOMNAMELEN];
+       int TheWeight = 0;
+       int weight;
+       struct march *mptr = NULL;
+
+       strcpy(TheRoom, "_BASEROOM_");
+       if (_march == NULL)
+               return (TheRoom);
+
+       for (mptr = _march; mptr != NULL; mptr = mptr->next) {
+               weight = 0;
+               if ((strcasecmp(mptr->march_name, "_BASEROOM_")))
+                       weight = weight + 10000;
+               if (mptr->march_floor == desired_floor)
+                       weight = weight + 5000;
+
+               weight = weight + ((128 - (mptr->march_floor)) * 128);
+               weight = weight + (128 - (mptr->march_order));
+
+               if (weight > TheWeight) {
+                       TheWeight = weight;
+                       strcpy(TheRoom, mptr->march_name);
+               }
+       }
+       return (TheRoom);
+}
+
+
+/*
+ * jump directly to a room
+ */
+void dotgoto(CtdlIPC *ipc, char *towhere, int display_name, int fromungoto)
+{
+       char aaa[SIZ], bbb[SIZ];
+       static long ls = 0L;
+       int newmailcount = 0;
+       int partial_match, best_match;
+       char from_floor;
+       int ugpos = uglistsize;
+       int r;                          /* IPC result code */
+       struct ctdlipcroom *room = NULL;
+       int rv = 0;
+
+       /* store ungoto information */
+       if (fromungoto == 0) {
+               /* sloppy slide them all down, hey it's the client, who cares. :-) */
+               if (uglistsize >= (UGLISTLEN-1)) {
+                       int lp;
+                       free (uglist[0]);
+                       for (lp = 0; lp < (UGLISTLEN-1); lp++) {
+                               uglist[lp] = uglist[lp+1];
+                               uglistlsn[lp] = uglistlsn[lp+1];
+                       }
+                       ugpos--;
+               } else {
+                       uglistsize++;
+               }
+        
+               uglist[ugpos] = malloc(strlen(room_name)+1);
+               strcpy(uglist[ugpos], room_name);
+               uglistlsn[ugpos] = ls;
+       }
+      
+       /* first try an exact match */
+       r = CtdlIPCGotoRoom(ipc, towhere, "", &room, aaa);
+       if (r / 10 == 54) {
+               newprompt("Enter room password: ", bbb, 9);
+               r = CtdlIPCGotoRoom(ipc, towhere, bbb, &room, aaa);
+               if (r / 10 == 54) {
+                       scr_printf("Wrong password.\n");
+                       return;
+               }
+       }       
+
+       /*
+        * If a match is not found, try a partial match.
+        * Partial matches anywhere in the string carry a weight of 1,
+        * left-aligned matches carry a weight of 2.  Pick the room that
+        * has the highest-weighted match.  Do not match on forgotten
+        * rooms.
+        */
+       if (r / 100 != 2) {
+               struct march *march = NULL;
+
+               best_match = 0;
+               strcpy(bbb, "");
+
+               r = CtdlIPCKnownRooms(ipc, SubscribedRooms, AllFloors, &march, aaa);
+               if (r / 100 == 1) {
+                       /* Run the roomlist; free the data as we go */
+                       struct march *mp = march;       /* Current */
+
+                       while (mp) {
+                               partial_match = 0;
+                               if (pattern(mp->march_name, towhere) >= 0) {
+                                       partial_match = 1;
+                               }
+                               if (!strncasecmp(mp->march_name, towhere, strlen(towhere))) {
+                                       partial_match = 2;
+                               }
+                               if (partial_match > best_match) {
+                                       strcpy(bbb, mp->march_name);
+                                       best_match = partial_match;
+                               }
+                               /* Both pointers are NULL at end of list */
+                               march = mp->next;
+                               free(mp);
+                               mp = march;
+                       }
+               }
+
+               if (IsEmptyStr(bbb)) {
+                       scr_printf("No room '%s'.\n", towhere);
+                       return;
+               }
+               r = CtdlIPCGotoRoom(ipc, bbb, "", &room, aaa);
+       }
+       if (r / 100 != 1 && r / 100 != 2) {
+               scr_printf("%s\n", aaa);
+               return;
+       }
+       safestrncpy(room_name, room->RRname, ROOMNAMELEN);
+       room_flags = room->RRflags;
+       room_flags2 = room->RRflags2;
+       from_floor = curr_floor;
+       curr_floor = room->RRfloor;
+
+       /* Determine, based on the room's default view, whether an <E>nter message
+        * command will be valid here.
+        */
+       switch(room->RRdefaultview) {
+               case VIEW_BBS:
+               case VIEW_MAILBOX:
+               case VIEW_BLOG:
+                                       entmsg_ok = 1;
+                                       break;
+               default:
+                                       entmsg_ok = 0;
+                                       break;
+       }
+
+       remove_march(room_name, 0);
+       if (!strcasecmp(towhere, "_BASEROOM_"))
+               remove_march(towhere, 0);
+       if (!room->RRunread)
+               next_lazy_cmd = 5;      /* Don't read new if no new msgs */
+       if ((from_floor != curr_floor) && (display_name > 0) && (floor_mode == 1)) {
+               if (floorlist[(int) curr_floor][0] == 0)
+                       load_floorlist(ipc);
+               scr_printf("(Entering floor: %s)\n", &floorlist[(int) curr_floor][0]);
+       }
+       if (display_name == 1) {
+               color(BRIGHT_WHITE);
+               scr_printf("%s ", room_name);
+               color(DIM_WHITE);
+               scr_printf("- ");
+       }
+       if (display_name != 2) {
+               color(BRIGHT_YELLOW);
+               scr_printf("%d ", room->RRunread);
+               color(DIM_WHITE);
+               scr_printf("new of ");
+               color(BRIGHT_YELLOW);
+               scr_printf("%d ", room->RRtotal);
+               color(DIM_WHITE);
+               scr_printf("messages.\n");
+       }
+       highest_msg_read = room->RRlastread;
+       maxmsgnum = room->RRhighest;
+       is_mail = room->RRismailbox;
+       is_room_aide = room->RRaide;
+       ls = room->RRlastread;
+
+       /* read info file if necessary */
+       if (room->RRinfoupdated > 0)
+               readinfo(ipc);
+
+       /* check for newly arrived mail if we can */
+       newmailcount = room->RRnewmail;
+       if (newmailcount > 0) {
+               color(BRIGHT_RED);
+               if (newmailcount == 1) {
+                       scr_printf("*** A new mail message has arrived.\n");
+               }
+               else {
+                       scr_printf("*** %d new mail messages have arrived.\n",
+                                       newmailcount);
+               }
+               color(DIM_WHITE);
+               if (!IsEmptyStr(rc_gotmail_cmd)) {
+                       rv = system(rc_gotmail_cmd);
+                       if (rv) 
+                               scr_printf("*** failed to check for mail calling %s Reason %d.\n",
+                                          rc_gotmail_cmd, rv);
+               }
+       }
+       free(room);
+
+       if (screenwidth>5) snprintf(&status_line[1], screenwidth-1, "%s  |  %s  |  %s  |  %s  |  %d new mail  |",
+               (secure ? "Encrypted" : "Unencrypted"),
+               ipc->ServInfo.humannode,
+               ipc->ServInfo.site_location,
+               room_name,
+               newmailcount
+       );
+}
+
+/* Goto next room having unread messages.
+ * We want to skip over rooms that the user has already been to, and take the
+ * user back to the lobby when done.  The room we end up in is placed in
+ * newroom - which is set to 0 (the lobby) initially.
+ */
+void gotonext(CtdlIPC *ipc)
+{
+       char buf[SIZ];
+       struct march *mptr, *mptr2;
+       char next_room[ROOMNAMELEN];
+
+       /* Check to see if the march-mode list is already allocated.
+        * If it is, pop the first room off the list and go there.
+        */
+       if (marchptr == NULL) {
+               CtdlIPCKnownRooms(ipc, SubscribedRoomsWithNewMessages,
+                                 AllFloors, &marchptr, buf);
+
+/* add _BASEROOM_ to the end of the march list, so the user will end up
+ * in the system base room (usually the Lobby>) at the end of the loop
+ */
+               mptr = (struct march *) malloc(sizeof(struct march));
+               mptr->next = NULL;
+               mptr->march_order = 0;
+               mptr->march_floor = 0;
+               strcpy(mptr->march_name, "_BASEROOM_");
+               if (marchptr == NULL) {
+                       marchptr = mptr;
+               } else {
+                       mptr2 = marchptr;
+                       while (mptr2->next != NULL)
+                               mptr2 = mptr2->next;
+                       mptr2->next = mptr;
+               }
+/*
+ * ...and remove the room we're currently in, so a <G>oto doesn't make us
+ * walk around in circles
+ */
+               remove_march(room_name, 0);
+       }
+       if (marchptr != NULL) {
+               strcpy(next_room, pop_march(curr_floor, marchptr));
+       } else {
+               strcpy(next_room, "_BASEROOM_");
+       }
+       remove_march(next_room, 0);
+       dotgoto(ipc, next_room, 1, 0);
+}
+
+/*
+ * forget all rooms on a given floor
+ */
+void forget_all_rooms_on(CtdlIPC *ipc, int ffloor)
+{
+       char buf[SIZ];
+       struct march *flist = NULL;
+       struct march *fptr = NULL;
+       struct ctdlipcroom *room = NULL;
+       int r;                          /* IPC response code */
+
+       scr_printf("Forgetting all rooms on %s...\n", &floorlist[ffloor][0]);
+       remove_march("_FLOOR_", ffloor);
+       r = CtdlIPCKnownRooms(ipc, AllAccessibleRooms, ffloor, &flist, buf);
+       if (r / 100 != 1) {
+               scr_printf("Error %d: %s\n", r, buf);
+               return;
+       }
+       while (flist) {
+               r = CtdlIPCGotoRoom(ipc, flist->march_name, "", &room, buf);
+               if (r / 100 == 2) {
+                       r = CtdlIPCForgetRoom(ipc, buf);
+                       if (r / 100 != 2) {
+                               scr_printf("Error %d: %s\n", r, buf);
+                       }
+
+               }
+               fptr = flist;
+               flist = flist->next;
+               free(fptr);
+       }
+       if (room) free(room);
+}
+
+
+/*
+ * routine called by gotofloor() to move to a new room on a new floor
+ */
+void gf_toroom(CtdlIPC *ipc, char *towhere, int mode)
+{
+       int floor_being_left;
+
+       floor_being_left = curr_floor;
+
+       if (mode == GF_GOTO) {          /* <;G>oto mode */
+               updatels(ipc);
+               dotgoto(ipc, towhere, 1, 0);
+       }
+       else if (mode == GF_SKIP) {     /* <;S>kip mode */
+               dotgoto(ipc, towhere, 1, 0);
+               remove_march("_FLOOR_", floor_being_left);
+       }
+       else if (mode == GF_ZAP) {      /* <;Z>ap mode */
+               dotgoto(ipc, towhere, 1, 0);
+               remove_march("_FLOOR_", floor_being_left);
+               forget_all_rooms_on(ipc, floor_being_left);
+       }
+}
+
+
+/*
+ * go to a new floor
+ */
+void gotofloor(CtdlIPC *ipc, char *towhere, int mode)
+{
+       int a, tofloor;
+       int r;          /* IPC response code */
+       struct march *mptr;
+       char buf[SIZ], targ[SIZ];
+
+       if (floorlist[0][0] == 0)
+               load_floorlist(ipc);
+       tofloor = (-1);
+       for (a = 0; a < 128; ++a)
+               if (!strcasecmp(&floorlist[a][0], towhere))
+                       tofloor = a;
+
+       if (tofloor < 0) {
+               for (a = 0; a < 128; ++a) {
+                       if (!strncasecmp(&floorlist[a][0], towhere, strlen(towhere))) {
+                               tofloor = a;
+                       }
+               }
+       }
+       if (tofloor < 0) {
+               for (a = 0; a < 128; ++a)
+                       if (pattern(towhere, &floorlist[a][0]) > 0)
+                               tofloor = a;
+       }
+       if (tofloor < 0) {
+               scr_printf("No floor '%s'.\n", towhere);
+               return;
+       }
+       for (mptr = marchptr; mptr != NULL; mptr = mptr->next) {
+               if ((mptr->march_floor) == tofloor) {
+                       gf_toroom(ipc, mptr->march_name, mode);
+                       return;
+               }
+       }
+
+       /* Find first known room on the floor */
+
+       strcpy(targ, "");
+       mptr = NULL;
+       r = CtdlIPCKnownRooms(ipc, SubscribedRooms, tofloor, &mptr, buf);
+       if (r / 100 == 1) {
+               struct march *tmp = mptr;
+
+               /*. . . according to room order */
+               if (mptr)
+           strcpy(targ, pop_march(tofloor, mptr));
+               while (mptr) {
+                       tmp = mptr->next;
+                       free(mptr);
+                       mptr = tmp;
+               }
+       }
+       if (!IsEmptyStr(targ)) {
+               gf_toroom(ipc, targ, mode);
+               return;
+       }
+
+       /* No known rooms on the floor; unzap the first one then */
+
+       strcpy(targ, "");
+       mptr = NULL;
+       r = CtdlIPCKnownRooms(ipc, AllAccessibleRooms, tofloor, &mptr, buf);
+       if (r / 100 == 1) {
+               struct march *tmp = mptr;
+               
+        /*. . . according to room order */
+               if (mptr)
+                       strcpy(targ, pop_march(tofloor, mptr));
+               while (mptr) {
+                       tmp = mptr->next;
+                       free(mptr);
+                       mptr = tmp;
+               }
+       }
+       if (!IsEmptyStr(targ)) {
+               gf_toroom(ipc, targ, mode);
+       } else {
+               scr_printf("There are no rooms on '%s'.\n", &floorlist[tofloor][0]);
+       }
+}
+
+/*
+ * Indexing mechanism for a room list, called by gotoroomstep()
+ */
+void room_tree_list_query(struct ctdlroomlisting *rp, char *findrmname, int findrmslot, char *rmname, int *rmslot, int *rmtotal)
+{
+       char roomname[ROOMNAMELEN];
+       static int cur_rmslot = 0;
+
+       if (rp == NULL) {
+               cur_rmslot = 0;
+               return;
+       }
+
+       if (rp->lnext != NULL) {
+               room_tree_list_query(rp->lnext, findrmname, findrmslot, rmname, rmslot, rmtotal);
+       }
+
+       if (sigcaught == 0) {
+               strcpy(roomname, rp->rlname);
+
+               if (rmname != NULL) {
+                       if (cur_rmslot == findrmslot) {
+                               strcpy(rmname, roomname);
+                       }
+               }
+               if (rmslot != NULL) {
+                       if (!strcmp(roomname, findrmname)) {
+                               *rmslot = cur_rmslot;
+                       }
+               }
+               cur_rmslot++;
+       }
+
+       if (rp->rnext != NULL) {
+               room_tree_list_query(rp->rnext, findrmname, findrmslot, rmname, rmslot, rmtotal);
+       }
+
+       if ((rmname == NULL) && (rmslot == NULL))
+               free(rp);
+
+       if (rmtotal != NULL) {
+               *rmtotal = cur_rmslot;
+       }
+}
+
+/*
+ * step through rooms on current floor
+ */
+void  gotoroomstep(CtdlIPC *ipc, int direction, int mode)
+{
+       struct march *listing = NULL;
+       struct march *mptr;
+       int r;          /* IPC response code */
+       char buf[SIZ];
+       struct ctdlroomlisting *rl = NULL;
+       struct ctdlroomlisting *rp;
+       struct ctdlroomlisting *rs;
+       int list_it;
+       char rmname[ROOMNAMELEN];
+       int rmslot = 0;
+       int rmtotal;
+
+       /* Ask the server for a room list */
+       r = CtdlIPCKnownRooms(ipc, SubscribedRooms, (-1), &listing, buf);
+       if (r / 100 != 1) {
+               listing = NULL;
+       }
+
+       load_floorlist(ipc);
+
+       for (mptr = listing; mptr != NULL; mptr = mptr->next) {
+               list_it = 1;
+
+               if ( floor_mode 
+                        && (mptr->march_floor != curr_floor))
+                       list_it = 0;
+
+               if (list_it) {
+                       rp = malloc(sizeof(struct ctdlroomlisting));
+                       strncpy(rp->rlname, mptr->march_name, ROOMNAMELEN);
+                       rp->rlflags = mptr->march_flags;
+                       rp->rlfloor = mptr->march_floor;
+                       rp->rlorder = mptr->march_order;
+                       rp->lnext = NULL;
+                       rp->rnext = NULL;
+
+                       rs = rl;
+                       if (rl == NULL) {
+                               rl = rp;
+                       } else {
+                               while (rp != NULL) {
+                                       if (rordercmp(rp, rs) < 0) {
+                                               if (rs->lnext == NULL) {
+                                                       rs->lnext = rp;
+                                                       rp = NULL;
+                                               } else {
+                                                       rs = rs->lnext;
+                                               }
+                                       } else {
+                                               if (rs->rnext == NULL) {
+                                                       rs->rnext = rp;
+                                                       rp = NULL;
+                                               } else {
+                                                       rs = rs->rnext;
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+
+       /* Find position of current room */
+       room_tree_list_query(NULL, NULL, 0, NULL, NULL, NULL);
+       room_tree_list_query(rl,  room_name, 0, NULL, &rmslot, &rmtotal);
+
+       if (direction == 0) { /* Previous room */
+               /* If we're at the first room, wrap to the last room */
+               if (rmslot == 0) {
+                       rmslot = rmtotal - 1;
+               } else {
+                       rmslot--;
+               }
+       } else {                 /* Next room */
+               /* If we're at the last room, wrap to the first room */
+               if (rmslot == rmtotal - 1) {
+                       rmslot = 0; 
+               } else {
+                       rmslot++;
+               }
+       }
+
+       /* Get name of next/previous room */
+       room_tree_list_query(NULL, NULL, 0, NULL, NULL, NULL);
+       room_tree_list_query(rl,  NULL, rmslot, rmname, NULL, NULL);
+
+       /* Free the tree */
+       room_tree_list_query(rl, NULL, 0, NULL, NULL, NULL);
+
+       if (mode == 0) { /* not skipping */
+           updatels(ipc);
+       }
+
+       /* Free the room list */
+       while (listing) {
+               mptr = listing->next;
+               free(listing);
+               listing = mptr;
+       };
+
+       dotgoto(ipc, rmname, 1, 0);
+}
+
+
+/*
+ * step through floors on system
+ */
+void  gotofloorstep(CtdlIPC *ipc, int direction, int mode)
+{
+       int  tofloor;
+
+       if (floorlist[0][0] == 0)
+               load_floorlist(ipc);
+
+       empty_keep_going:
+
+       if (direction == 0) { /* Previous floor */
+               if (curr_floor) tofloor = curr_floor - 1;
+               else tofloor = 127;
+
+               while (!floorlist[tofloor][0]) tofloor--;
+       } else {                   /* Next floor */
+               if (curr_floor < 127) tofloor = curr_floor + 1;
+               else tofloor = 0;
+
+               while (!floorlist[tofloor][0] && tofloor < 127) tofloor++;
+               if (!floorlist[tofloor][0])     tofloor = 0;
+       }
+       /* ;g works when not in floor mode so . . . */
+       if (!floor_mode) {
+               scr_printf("(%s)\n", floorlist[tofloor] );
+       }
+
+       gotofloor(ipc, floorlist[tofloor], mode);
+       if (curr_floor != tofloor) { /* gotofloor failed */
+            curr_floor = tofloor;
+            goto empty_keep_going;
+       }            
+}
+
+/* 
+ * Display user 'preferences'.
+ */
+extern int rc_prompt_control;
+void read_config(CtdlIPC *ipc)
+{
+       char buf[SIZ];
+       char *resp = NULL;
+       int r;                  /* IPC response code */
+    char _fullname[USERNAME_SIZE];
+       long _usernum;
+       int _axlevel, _timescalled, _posted;
+       time_t _lastcall;
+       struct ctdluser *user = NULL;
+
+       /* get misc user info */   
+       r = CtdlIPCGetBio(ipc, fullname, &resp, buf);
+       if (r / 100 != 1) {
+               scr_printf("%s\n", buf);
+               return;
+       }
+       extract_token(_fullname, buf, 1, '|', sizeof fullname);
+       _usernum = extract_long(buf, 2);
+       _axlevel = extract_int(buf, 3);
+       _lastcall = extract_long(buf, 4);
+    _timescalled = extract_int(buf, 5);
+       _posted = extract_int(buf, 6);
+       free(resp);
+       resp = NULL;
+   
+       /* get preferences */
+       r = CtdlIPCGetConfig(ipc, &user, buf);
+       if (r / 100 != 2) {
+               scr_printf("%s\n", buf);
+               free(user);
+               return;
+       }
+
+       /* show misc user info */
+       scr_printf("%s\nAccess level: %d (%s)\n"
+                  "User #%ld / %d Calls / %d Posts",
+                  _fullname, _axlevel, axdefs[(int) _axlevel],
+                  _usernum, _timescalled, _posted);
+       if (_lastcall > 0L) {
+               scr_printf(" / Curr login: %s",
+                          asctime(localtime(&_lastcall)));
+       }
+       scr_printf("\n");
+
+       /* show preferences */
+       scr_printf("Are you an experienced Citadel user: ");                   color(BRIGHT_CYAN); scr_printf("%s\n", (user->flags & US_EXPERT) ? "Yes" : "No");     color(DIM_WHITE);
+       scr_printf("Print last old message on New message request: ");         color(BRIGHT_CYAN); scr_printf("%s\n", (user->flags & US_LASTOLD)? "Yes" : "No");     color(DIM_WHITE);
+       scr_printf("Prompt after each message: ");                             color(BRIGHT_CYAN); scr_printf("%s\n", (!(user->flags & US_NOPROMPT))? "Yes" : "No"); color(DIM_WHITE);
+       if ((user->flags & US_NOPROMPT) == 0) {
+       scr_printf("Use 'disappearing' prompts: ");                        color(BRIGHT_CYAN); scr_printf("%s\n", (user->flags & US_DISAPPEAR)? "Yes" : "No");   color(DIM_WHITE);
+       }
+       scr_printf("Pause after each screenful of text: ");                    color(BRIGHT_CYAN); scr_printf("%s\n", (user->flags & US_PAGINATOR)? "Yes" : "No");   color(DIM_WHITE);
+    if (rc_prompt_control == 3 && (user->flags & US_PAGINATOR)) {
+       scr_printf("<N>ext and <S>top work at paginator prompt: ");        color(BRIGHT_CYAN); scr_printf("%s\n", (user->flags & US_PROMPTCTL)? "Yes" : "No");   color(DIM_WHITE);
+       }
+    if (rc_floor_mode == RC_DEFAULT) {
+       scr_printf("View rooms by floor: ");                               color(BRIGHT_CYAN); scr_printf("%s\n", (user->flags & US_FLOORS)? "Yes" : "No");          color(DIM_WHITE);
+       }
+       if (rc_ansi_color == 3) {
+           scr_printf("Enable color support: ");                              color(BRIGHT_CYAN); scr_printf("%s\n", (user->flags & US_COLOR)? "Yes" : "No");       color(DIM_WHITE);
+       }
+       scr_printf("Be unlisted in userlog: ");                                color(BRIGHT_CYAN); scr_printf("%s\n", (user->flags & US_UNLISTED)? "Yes" : "No");    color(DIM_WHITE);
+       if (!IsEmptyStr(editor_path)) {
+       scr_printf("Always enter messages with the full-screen editor: "); color(BRIGHT_CYAN); scr_printf("%s\n", (user->flags & US_EXTEDIT)? "Yes" : "No");     color(DIM_WHITE);
+       }
+       free(user);
+}
+
+/*
+ * Display system statistics.
+ */
+void system_info(CtdlIPC *ipc)
+{
+       char buf[SIZ];
+       char *resp = NULL;
+       size_t bytes;
+       int mrtg_users, mrtg_active_users; 
+       char mrtg_server_uptime[40];
+       long mrtg_himessage;
+
+       /* get #users, #active & server uptime */
+       CtdlIPCGenericCommand(ipc, "MRTG|users", NULL, 0, &resp, &bytes, buf);
+       mrtg_users = extract_int(resp, 0);
+       remove_token(resp, 0, '\n');
+       mrtg_active_users = extract_int(resp, 0);
+       remove_token(resp, 0, '\n');
+       extract_token(mrtg_server_uptime, resp, 0, '\n', sizeof mrtg_server_uptime);
+       free(resp);
+       resp = NULL;
+
+       /* get high message# */
+       CtdlIPCGenericCommand(ipc, "MRTG|messages", NULL, 0, &resp, &bytes, buf);
+       mrtg_himessage = extract_long(resp, 0);
+       free(resp);
+       resp = NULL;
+
+       /* refresh server info just in case */
+       CtdlIPCServerInfo(ipc, buf);
+
+       scr_printf("You are connected to %s (%s) @%s\n", ipc->ServInfo.nodename, ipc->ServInfo.humannode, ipc->ServInfo.fqdn);
+       scr_printf("running %s with text client v%.2f,\n", ipc->ServInfo.software, (float)CLIENT_VERSION/100);
+       scr_printf("server build %s,\n", ipc->ServInfo.svn_revision, (float)CLIENT_VERSION/100);
+       scr_printf("and located in %s.\n", ipc->ServInfo.site_location);
+       scr_printf("Connected users %d / Active users %d / Highest message #%ld\n", mrtg_users, mrtg_active_users, mrtg_himessage);
+       scr_printf("Server uptime: %s\n", mrtg_server_uptime);
+       scr_printf("Your system administrator is %s.\n", ipc->ServInfo.sysadm);
+}
+
+/*
+ * forget all rooms on current floor
+ */
+void forget_this_floor(CtdlIPC *ipc)
+{
+       if (curr_floor == 0) {
+               scr_printf("Can't forget this floor.\n");
+               return;
+       }
+       if (floorlist[0][0] == 0) {
+               load_floorlist(ipc);
+       }
+       scr_printf("Are you sure you want to forget all rooms on %s? ",
+              &floorlist[(int) curr_floor][0]);
+       if (yesno() == 0) {
+               return;
+       }
+
+       gf_toroom(ipc, "_BASEROOM_", GF_ZAP);
+}
+
+
+/*
+ * set floor mode depending on client, server, and user settings
+ */
+void set_floor_mode(CtdlIPC* ipc)
+{
+       if (ipc->ServInfo.ok_floors == 0) {
+               floor_mode = 0; /* Don't use floors if the server */
+       }
+       /* doesn't support them!          */
+       else {
+               if (rc_floor_mode == RC_NO) {   /* never use floors */
+                       floor_mode = 0;
+               }
+               if (rc_floor_mode == RC_YES) {  /* always use floors */
+                       floor_mode = 1;
+               }
+               if (rc_floor_mode == RC_DEFAULT) {      /* user choice */
+                       floor_mode = ((userflags & US_FLOORS) ? 1 : 0);
+               }
+       }
+}
+
+/*
+ * Set or change the user's password
+ */
+int set_password(CtdlIPC *ipc)
+{
+       char pass1[20];
+       char pass2[20];
+       char buf[SIZ];
+
+       if (!IsEmptyStr(rc_password)) {
+               strcpy(pass1, rc_password);
+               strcpy(pass2, rc_password);
+       } else {
+               IFNEXPERT formout(ipc, "changepw");
+               newprompt("Enter a new password: ", pass1, -19);
+               newprompt("Enter it again to confirm: ", pass2, -19);
+       }
+       strproc(pass1);
+       strproc(pass2);
+       if (!strcasecmp(pass1, pass2)) {
+               CtdlIPCChangePassword(ipc, pass1, buf);
+               scr_printf("%s\n", buf);
+               offer_to_remember_password(ipc, hostbuf, portbuf, fullname, pass1);
+               return (0);
+       } else {
+               scr_printf("*** They don't match... try again.\n");
+               return (1);
+       }
+}
+
+
+
+/*
+ * get info about the server we've connected to
+ */
+void get_serv_info(CtdlIPC *ipc, char *supplied_hostname)
+{
+       char buf[SIZ];
+
+       CtdlIPCServerInfo(ipc, buf);
+       moreprompt = ipc->ServInfo.moreprompt;
+
+       /* be nice and identify ourself to the server */
+       CtdlIPCIdentifySoftware(ipc, CLIENT_TYPE, 0, CLIENT_VERSION,
+                (ipc->isLocal ? "local" : "Citadel text mode client"),
+                (supplied_hostname) ? supplied_hostname : 
+                /* Look up the , in the bible if you're confused */
+                (locate_host(ipc, buf), buf), buf);
+
+       /* Indicate to the server that we prefer to decode Base64 and
+        * quoted-printable on the client side.
+        */
+       if ((CtdlIPCSpecifyPreferredFormats(ipc, buf, "dont_decode") / 100 ) != 2) {
+               scr_printf("ERROR: Extremely old server; MSG4 framework not supported.\n");
+               logoff(ipc, 0);
+       }
+
+       /*
+        * Tell the server what our preferred content formats are.
+        *
+        * Originally we preferred HTML over plain text because we can format
+        * it to the reader's screen width, but since our HTML-to-text parser
+        * isn't really all that great, it's probably better to just go with
+        * the plain text when we have it available.
+        */
+       if ((CtdlIPCSpecifyPreferredFormats(ipc, buf, "text/plain|text/html") / 100 ) != 2) {
+               scr_printf("ERROR: Extremely old server; MSG4 framework not supported.\n");
+               logoff(ipc, 0);
+       }
+}
+
+
+
+/*
+ * Session username compare function for SortOnlineUsers()
+ */
+int rwho_username_cmp(const void *rec1, const void *rec2) {
+       char *u1, *u2;
+
+       u1 = strchr(rec1, '|');
+       u2 = strchr(rec2, '|');
+
+       return strcasecmp( (u1?++u1:"") , (u2?++u2:"") );
+}
+
+
+/*
+ * Idle time compare function for SortOnlineUsers()
+ */
+int idlecmp(const void *rec1, const void *rec2) {
+       time_t i1, i2;
+
+       i1 = extract_long(rec1, 5);
+       i2 = extract_long(rec2, 5);
+
+       if (i1 < i2) return(1);
+       if (i1 > i2) return(-1);
+       return(0);
+}
+
+
+/*
+ * Sort the list of online users by idle time.
+ * This function frees the supplied buffer, and returns a new one
+ * to the caller.  The caller is responsible for freeing the returned buffer.
+ *
+ * If 'condense' is nonzero, multiple sessions for the same user will be
+ * combined into one for brevity.
+ */
+char *SortOnlineUsers(char *listing, int condense) {
+       int rows;
+       char *sortbuf;
+       char *retbuf;
+       char buf[SIZ];
+       int i;
+
+       rows = num_tokens(listing, '\n');
+       sortbuf = malloc(rows * SIZ);
+       if (sortbuf == NULL) return(listing);
+       retbuf = malloc(rows * SIZ);
+       if (retbuf == NULL) {
+               free(sortbuf);
+               return(listing);
+       }
+
+       /* Copy the list into a fixed-record-size array for sorting */
+       for (i=0; i<rows; ++i) {
+               memset(buf, 0, SIZ);
+               extract_token(buf, listing, i, '\n', sizeof buf);
+               memcpy(&sortbuf[i*SIZ], buf, (size_t)SIZ);
+       }
+
+       /* Sort by idle time */
+       qsort(sortbuf, rows, SIZ, idlecmp);
+
+       /* Combine multiple sessions for the same user */
+       if (condense) {
+               qsort(sortbuf, rows, SIZ, rwho_username_cmp);
+               if (rows > 1) for (i=1; i<rows; ++i) if (i>0) {
+                       char u1[USERNAME_SIZE];
+                       char u2[USERNAME_SIZE];
+                       extract_token(u1, &sortbuf[(i-1)*SIZ], 1, '|', sizeof u1);
+                       extract_token(u2, &sortbuf[i*SIZ], 1, '|', sizeof u2);
+                       if (!strcasecmp(u1, u2)) {
+                               memcpy(&sortbuf[i*SIZ], &sortbuf[(i+1)*SIZ], (rows-i-1)*SIZ);
+                               --rows;
+                               --i;
+                       }
+               }
+
+               qsort(sortbuf, rows, SIZ, idlecmp);     /* idle sort again */
+       }
+
+       /* Copy back to a \n delimited list */
+       strcpy(retbuf, "");
+       for (i=0; i<rows; ++i) {
+               if (!IsEmptyStr(&sortbuf[i*SIZ])) {
+                       strcat(retbuf, &sortbuf[i*SIZ]);
+                       if (i<(rows-1)) strcat(retbuf, "\n");
+               }
+       }
+       free(listing);
+       free(sortbuf);
+       return(retbuf);
+}
+
+
+
+/*
+ * Display list of users currently logged on to the server
+ */
+void who_is_online(CtdlIPC *ipc, int longlist)
+{
+       char buf[SIZ], username[SIZ], roomname[SIZ], fromhost[SIZ];
+       char flags[SIZ];
+       char actual_user[SIZ], actual_room[SIZ], actual_host[SIZ];
+       char clientsoft[SIZ];
+       time_t timenow = 0;
+       time_t idletime, idlehours, idlemins, idlesecs;
+       int last_session = (-1);
+       int skipidle = 0;
+       char *listing = NULL;
+       int r;                          /* IPC response code */
+    
+       if (longlist == 2) {
+               longlist = 0;
+               skipidle = 1;
+       }
+
+       if (!longlist) {
+               color(BRIGHT_WHITE);
+               scr_printf("           User Name               Room          ");
+               if (screenwidth >= 80) scr_printf(" Idle        From host");
+               scr_printf("\n");
+               color(DIM_WHITE);
+               scr_printf("   ------------------------- --------------------");
+               if (screenwidth >= 80) scr_printf(" ---- ------------------------");
+               scr_printf("\n");
+       }
+       r = CtdlIPCOnlineUsers(ipc, &listing, &timenow, buf);
+       listing = SortOnlineUsers(listing, (!longlist));
+       if (r / 100 == 1) {
+               while (!IsEmptyStr(listing)) {
+                       int isidle = 0;
+                       
+                       /* Get another line */
+                       extract_token(buf, listing, 0, '\n', sizeof buf);
+                       remove_token(listing, 0, '\n');
+
+                       extract_token(username, buf, 1, '|', sizeof username);
+                       extract_token(roomname, buf, 2, '|', sizeof roomname);
+                       extract_token(fromhost, buf, 3, '|', sizeof fromhost);
+                       extract_token(clientsoft, buf, 4, '|', sizeof clientsoft);
+                       extract_token(flags, buf, 7, '|', sizeof flags);
+
+                       idletime = timenow - extract_long(buf, 5);
+                       idlehours = idletime / 3600;
+                       idlemins = (idletime - (idlehours * 3600)) / 60;
+                       idlesecs = (idletime - (idlehours * 3600) - (idlemins * 60));
+
+                       if (idletime > rc_idle_threshold) {
+                               if (skipidle) {
+                                       isidle = 1;
+                               }
+                       }
+
+                       if (longlist) {
+                               extract_token(actual_user, buf, 8, '|', sizeof actual_user);
+                               extract_token(actual_room, buf, 9, '|', sizeof actual_room);
+                               extract_token(actual_host, buf, 10, '|', sizeof actual_host);
+
+                               scr_printf("  Flags: %s\n", flags);
+                               scr_printf("Session: %d\n", extract_int(buf, 0));
+                               scr_printf("   Name: %s\n", username);
+                               scr_printf("In room: %s\n", roomname);
+                               scr_printf("   Host: %s\n", fromhost);
+                               scr_printf(" Client: %s\n", clientsoft);
+                               scr_printf("   Idle: %ld:%02ld:%02ld\n",
+                                       (long) idlehours,
+                                       (long) idlemins,
+                                       (long) idlesecs);
+
+                               if ( (!IsEmptyStr(actual_user)&&
+                                     !IsEmptyStr(actual_room)&&
+                                     !IsEmptyStr(actual_host))) {
+                                       scr_printf("(really ");
+                                       if (!IsEmptyStr(actual_user)) scr_printf("<%s> ", actual_user);
+                                       if (!IsEmptyStr(actual_room)) scr_printf("in <%s> ", actual_room);
+                                       if (!IsEmptyStr(actual_host)) scr_printf("from <%s> ", actual_host);
+                                       scr_printf(")\n");
+                               }
+                               scr_printf("\n");
+
+                       } else {
+                               if (isidle == 0) {
+                                       if (extract_int(buf, 0) == last_session) {
+                                               scr_printf("        ");
+                                       }
+                                       else {
+                                               color(BRIGHT_MAGENTA);
+                                               scr_printf("%-3s", flags);
+                                       }
+                                       last_session = extract_int(buf, 0);
+                                       color(BRIGHT_CYAN);
+                                       scr_printf("%-25s ", username);
+                                       color(BRIGHT_MAGENTA);
+                                       roomname[20] = 0;
+                                       scr_printf("%-20s", roomname);
+
+                                       if (screenwidth >= 80) {
+                                               scr_printf(" ");
+                                               if (idletime > rc_idle_threshold) {
+                                                       /* over 1000d, must be gone fishing */
+                                                       if (idlehours > 23999) {
+                                                               scr_printf("fish");
+                                                       /* over 10 days */
+                                                       } else if (idlehours > 239) {
+                                                               scr_printf("%3ldd", idlehours / 24);
+                                                       /* over 10 hours */
+                                                       } else if (idlehours > 9) {
+                                                               scr_printf("%1ldd%02ld",
+                                                                       idlehours / 24,
+                                                                       idlehours % 24);
+                                                       /* less than 10 hours */
+                                                       }
+                                                       else {
+                                                               scr_printf("%1ld:%02ld", idlehours, idlemins);
+                                                       }
+                                               }
+                                               else {
+                                                       scr_printf("    ");
+                                               }
+                                               scr_printf(" ");
+                                               color(BRIGHT_CYAN);
+                                               fromhost[24] = '\0';
+                                               scr_printf("%-24s", fromhost);
+                                       }
+                                       scr_printf("\n");
+                                       color(DIM_WHITE);
+                               }
+                       }
+               }
+       }
+       free(listing);
+}
+
+void enternew(CtdlIPC *ipc, char *desc, char *buf, int maxlen)
+{
+       char bbb[128];
+       snprintf(bbb, sizeof bbb, "Enter in your new %s: ", desc);
+       newprompt(bbb, buf, maxlen);
+}
+
+
+
+int shift(int argc, char **argv, int start, int count) {
+       int i;
+
+       for (i=start; i<(argc-count); ++i) {
+               argv[i] = argv[i+count];
+       }
+       argc = argc - count;
+       return argc;
+}
+
+static void statusHook(char *s) {
+       scr_printf(s);
+}
+
+/*
+ * main
+ */
+int main(int argc, char **argv)
+{
+       int a, b, mcmd;
+       char aaa[100], bbb[100];/* general purpose variables */
+       char argbuf[64];        /* command line buf */
+       char nonce[NONCE_SIZE];
+       char *telnet_client_host = NULL;
+       char *sptr, *sptr2;     /* USed to extract the nonce */
+       char hexstring[MD5_HEXSTRING_SIZE];
+       char password[SIZ];
+       struct ctdlipcmisc chek;
+       struct ctdluser *myself = NULL;
+       CtdlIPC* ipc;                   /* Our server connection */
+       int r;                          /* IPC result code */
+       int rv = 0;                     /* fetch but ignore syscall return value to suppress warnings */
+
+       int relh=0;
+       int home=0;
+       char relhome[PATH_MAX]="";
+       char ctdldir[PATH_MAX]=CTDLDIR;
+    int lp; 
+       calc_dirs_n_files(relh, home, relhome, ctdldir, 0);
+
+#ifdef HAVE_BACKTRACE
+       bzero(&params, sizeof(params));
+       params.debugLevel = ECRASH_DEBUG_VERBOSE;
+       params.dumpAllThreads = TRUE;
+       params.useBacktraceSymbols = 1;
+       params.signals[0]=SIGSEGV;
+       params.signals[1]=SIGILL;
+       params.signals[2]=SIGBUS;
+       params.signals[3]=SIGABRT;
+#endif 
+       setIPCErrorPrintf(scr_printf);
+       setCryptoStatusHook(statusHook);
+       
+       stty_ctdl(SB_SAVE);             /* Store the old terminal parameters */
+       load_command_set();             /* parse the citadel.rc file */
+       stty_ctdl(SB_NO_INTR);          /* Install the new ones */
+       signal(SIGPIPE, dropcarr);      /* Cleanup gracefully if local conn. dropped */
+       signal(SIGTERM, dropcarr);      /* Cleanup gracefully if terminated */
+       signal(SIGCONT, catch_sigcont); /* Catch SIGCONT so we can reset terminal */
+#ifdef SIGWINCH
+       signal(SIGWINCH, scr_winch);    /* Window resize signal */
+#endif
+
+#ifdef HAVE_OPENSSL
+       arg_encrypt = RC_DEFAULT;
+#endif
+
+       /* 
+        * Handle command line options as if we were called like /bin/login
+        * (i.e. from in.telnetd)
+        */
+       for (a=0; a<argc; ++a) {
+               if ((argc > a+1) && (!strcmp(argv[a], "-h")) ) {
+                       telnet_client_host = argv[a+1];
+                       argc = shift(argc, argv, a, 2);
+               }
+               if (!strcmp(argv[a], "-x")) {
+#ifdef HAVE_OPENSSL
+                       arg_encrypt = RC_NO;
+#endif
+                       argc = shift(argc, argv, a, 1);
+               }
+               if (!strcmp(argv[a], "-X")) {
+#ifdef HAVE_OPENSSL
+                       arg_encrypt = RC_YES;
+                       argc = shift(argc, argv, a, 1);
+#else
+                       fprintf(stderr, "Not compiled with encryption support");
+                       return 1;
+#endif
+               }
+               if (!strcmp(argv[a], "-p")) {
+                       // ignore this argument when called from telnetd
+                       argc = shift(argc, argv, a, 1);
+               }
+       }
+       
+       screen_new();
+       /* Get screen dimensions.  First we go to a default of 80x24.
+        * Then attempt to read the actual screen size from the terminal.
+        */
+       check_screen_dims();
+
+
+#ifdef __CYGWIN__
+       newprompt("Connect to (return for local server): ", hostbuf, 64);
+#endif
+
+       scr_printf("Attaching to server...\n");
+       ipc = CtdlIPC_new(argc, argv, hostbuf, portbuf);
+       if (!ipc) {
+               error_printf("Can't connect: %s\n", strerror(errno));
+               logoff(NULL, 3);
+       }
+
+       CtdlIPC_SetNetworkStatusCallback(ipc, scr_wait_indicator);
+
+       if (!(ipc->isLocal)) {
+               scr_printf("Connected to %s [%s].\n", ipc->ip_hostname, ipc->ip_address);
+       }
+
+       ipc_for_signal_handlers = ipc;  /* KLUDGE cover your eyes */
+
+       CtdlIPC_chat_recv(ipc, aaa);
+       if (aaa[0] != '2') {
+               scr_printf("%s\n", &aaa[4]);
+               logoff(ipc, atoi(aaa));
+       }
+
+       /* If there is a [nonce] at the end, put the nonce in <nonce>, else nonce
+        * is zeroized.
+        */
+       
+       if ((sptr = strchr(aaa, '<')) == NULL)
+               {
+                       nonce[0] = '\0';
+               }
+       else
+               {
+                       if ((sptr2 = strchr(sptr, '>')) == NULL)
+                               {
+                                       nonce[0] = '\0';
+                               }
+                       else
+                               {
+                                       sptr2++;
+                                       *sptr2 = '\0';
+                                       strncpy(nonce, sptr, (size_t)NONCE_SIZE);
+                               }
+               }
+
+#ifdef HAVE_OPENSSL
+       /* Evaluate encryption preferences */
+       if (arg_encrypt != RC_NO && rc_encrypt != RC_NO) {
+               if (!ipc->isLocal || arg_encrypt == RC_YES || rc_encrypt == RC_YES) {
+                       secure = (CtdlIPCStartEncryption(ipc, aaa) / 100 == 2) ? 1 : 0;
+                       if (!secure)
+                               error_printf("Can't encrypt: %s\n", aaa);
+               }
+       }
+#endif
+
+       get_serv_info(ipc, telnet_client_host);
+       scr_printf("%-24s\n%s\n%s\n", ipc->ServInfo.software, ipc->ServInfo.humannode,
+                  ipc->ServInfo.site_location);
+
+       scr_printf(" pause    next    stop\n");
+       scr_printf(" ctrl-s  ctrl-o  ctrl-c\n\n");
+       formout(ipc, "hello");  /* print the opening greeting */
+       scr_printf("\n");
+
+ GSTA: /* See if we have a username and password on disk */
+       if (rc_remember_passwords) {
+               get_stored_password(hostbuf, portbuf, fullname, password);
+               if (!IsEmptyStr(fullname)) {
+                       r = CtdlIPCTryLogin(ipc, fullname, aaa);
+                       if (r / 100 == 3) {
+                               if (*nonce) {
+                                       r = CtdlIPCTryApopPassword(ipc, make_apop_string(password, nonce, hexstring, sizeof hexstring), aaa);
+                               } else {
+                                       r = CtdlIPCTryPassword(ipc, password, aaa);
+                               }
+                       }
+
+                       if (r / 100 == 2) {
+                               load_user_info(aaa);
+                               goto PWOK;
+                       } else {
+                               set_stored_password(hostbuf, portbuf, "", "");
+                       }
+               }
+       }
+
+       termn8 = 0;
+       newnow = 0;
+       do {
+               if (!IsEmptyStr(rc_username)) {
+                       strcpy(fullname, rc_username);
+               } else {
+                       newprompt("Enter your name: ", fullname, 29);
+               }
+               strproc(fullname);
+               if (!strcasecmp(fullname, "new")) {     /* just in case */
+                       scr_printf("Please enter the name you wish to log in with.\n");
+               }
+       } while (
+                (!strcasecmp(fullname, "bbs"))
+                || (!strcasecmp(fullname, "new"))
+                || (IsEmptyStr(fullname)));
+
+       if (!strcasecmp(fullname, "off")) {
+               mcmd = 29;
+               goto TERMN8;
+       }
+
+       /* FIXME this is a stupid way to do guest mode but it's a reasonable test harness FIXME */
+       if ( (ipc->ServInfo.guest_logins) && (!strcasecmp(fullname, "guest")) ) {
+               goto PWOK;
+       }
+
+       /* sign on to the server */
+       r = CtdlIPCTryLogin(ipc, fullname, aaa);
+       if (r / 100 != 3)
+               goto NEWUSR;
+
+       /* password authentication */
+       if (!IsEmptyStr(rc_password)) {
+               strcpy(password, rc_password);
+       } else {
+               newprompt("\rPlease enter your password: ", password, -(SIZ-1));
+       }
+
+       if (*nonce) {
+               r = CtdlIPCTryApopPassword(ipc, make_apop_string(password, nonce, hexstring, sizeof hexstring), aaa);
+               if (r / 100 != 2) {
+                       strproc(password);
+                       r = CtdlIPCTryApopPassword(ipc, make_apop_string(password, nonce, hexstring, sizeof hexstring), aaa);
+               }
+       } else {
+               r = CtdlIPCTryPassword(ipc, password, aaa);
+               if (r / 100 != 2) {
+                       strproc(password);
+                       r = CtdlIPCTryPassword(ipc, password, aaa);
+               }
+       }
+       
+       if (r / 100 == 2) {
+               load_user_info(aaa);
+               offer_to_remember_password(ipc, hostbuf, portbuf,
+                                          fullname, password);
+               goto PWOK;
+       }
+       scr_printf("<< wrong password >>\n");
+       if (!IsEmptyStr(rc_password))
+               logoff(ipc, 2);
+       goto GSTA;
+
+NEWUSR:        if (IsEmptyStr(rc_password)) {
+               scr_printf("'%s' not found.\n", fullname);
+               scr_printf("Type 'off' if you would like to exit.\n");
+               if (ipc->ServInfo.newuser_disabled == 1) {
+                       goto GSTA;
+               }
+               scr_printf("Do you want to create a new user account called '%s'? ",
+                       fullname);
+               if (yesno() == 0) {
+                       goto GSTA;
+               }
+       }
+
+       r = CtdlIPCCreateUser(ipc, fullname, 1, aaa);
+       if (r / 100 != 2) {
+               scr_printf("%s\n", aaa);
+               goto GSTA;
+       }
+       load_user_info(aaa);
+
+       while (set_password(ipc) != 0);
+       newnow = 1;
+
+       enter_config(ipc, 1);
+
+ PWOK:
+       /* Switch color support on or off if we're in user mode */
+       if (rc_ansi_color == 3) {
+               if (userflags & US_COLOR)
+                       enable_color = 1;
+               else
+                       enable_color = 0;
+       }
+
+       color(BRIGHT_WHITE);
+       scr_printf("\n%s\nAccess level: %d (%s)\n"
+                  "User #%ld / Login #%d",
+                  fullname, axlevel, axdefs[(int) axlevel],
+                  usernum, timescalled);
+       if (lastcall > 0L) {
+               scr_printf(" / Last login: %s",
+                          asctime(localtime(&lastcall)));
+       }
+       scr_printf("\n");
+
+       r = CtdlIPCMiscCheck(ipc, &chek, aaa);
+       if (r / 100 == 2) {
+               b = chek.newmail;
+               if (b > 0) {
+                       color(BRIGHT_RED);
+                       if (b == 1)
+                               scr_printf("*** You have a new private message in Mail>\n");
+                       if (b > 1)
+                               scr_printf("*** You have %d new private messages in Mail>\n", b);
+                       color(DIM_WHITE);
+                       if (!IsEmptyStr(rc_gotmail_cmd)) {
+                               rv = system(rc_gotmail_cmd);
+                               if (rv)
+                                       scr_printf("*** failed to check for mail calling %s Reason %d.\n",
+                                                  rc_gotmail_cmd, rv);
+
+                       }
+               }
+               if ((axlevel >= AxAideU) && (chek.needvalid > 0)) {
+                       scr_printf("*** Users need validation\n");
+               }
+               if (chek.needregis > 0) {
+                       scr_printf("*** Please register.\n");
+                       formout(ipc, "register");
+                       entregis(ipc);
+               }
+       }
+       /* Make up some temporary filenames for use in various parts of the
+        * program.  Don't mess with these once they've been set, because we
+        * will be unlinking them later on in the program and we don't
+        * want to delete something that we didn't create. */
+       CtdlMakeTempFileName(temp, sizeof temp);
+       CtdlMakeTempFileName(temp2, sizeof temp2);
+       CtdlMakeTempFileName(tempdir, sizeof tempdir);
+
+       r = CtdlIPCGetConfig(ipc, &myself, aaa);
+       set_floor_mode(ipc);
+
+       /* Enter the lobby */
+       dotgoto(ipc, "_BASEROOM_", 1, 0);
+
+       /* Main loop for the system... user is logged in. */
+    free(uglist[0]);
+       uglistsize = 0;
+
+       if (newnow == 1)
+               readmsgs(ipc, LastMessages, ReadForward, 5);
+       else
+               readmsgs(ipc, NewMessages, ReadForward, 0);
+
+       /* MAIN COMMAND LOOP */
+       do {
+               mcmd = getcmd(ipc, argbuf);     /* Get keyboard command */
+
+#ifdef TIOCGWINSZ
+               check_screen_dims();            /* get screen size */
+#endif
+
+               if (termn8 == 0)
+                       switch (mcmd) {
+                       case 1:
+                               display_help(ipc, "help");
+                               break;
+                       case 4:
+                               entmsg(ipc, 0, ((userflags & US_EXTEDIT) ? 2 : 0), 0);
+                               break;
+                       case 36:
+                               entmsg(ipc, 0, 1, 0);
+                               break;
+                       case 46:
+                               entmsg(ipc, 0, 2, 0);
+                               break;
+                       case 78:
+                               entmsg(ipc, 0, ((userflags & US_EXTEDIT) ? 2 : 0), 1);
+                               break;
+                       case 5:                         /* <G>oto */
+                               updatels(ipc);
+                               gotonext(ipc);
+                               break;
+                       case 47:                        /* <A>bandon */
+                               gotonext(ipc);
+                               break;
+                       case 90:                        /* <.A>bandon */
+                               dotgoto(ipc, argbuf, 0, 0);
+                               break;
+                       case 58:                        /* <M>ail */
+                               updatelsa(ipc);
+                               dotgoto(ipc, "_MAIL_", 1, 0);
+                               break;
+                       case 20:
+                               if (!IsEmptyStr(argbuf)) {
+                                       updatels(ipc);
+                                       dotgoto(ipc, argbuf, 0, 0);
+                               }
+                               break;
+                       case 52:
+                               if (!IsEmptyStr(argbuf)) {
+                                       dotgoto(ipc, argbuf, 0, 0);
+                               }
+                               break;
+                       case 95: /* what exactly is the numbering scheme supposed to be anyway? --Ford, there isn't one. -IO */
+                               dotungoto(ipc, argbuf);
+                               break;
+                       case 10:
+                               readmsgs(ipc, AllMessages, ReadForward, 0);
+                               break;
+                       case 9:
+                               readmsgs(ipc, LastMessages, ReadForward, 5);
+                               break;
+                       case 13:
+                               readmsgs(ipc, NewMessages, ReadForward, 0);
+                               break;
+                       case 11:
+                               readmsgs(ipc, AllMessages, ReadReverse, 0);
+                               break;
+                       case 12:
+                               readmsgs(ipc, OldMessages, ReadReverse, 0);
+                               break;
+                       case 71:
+                               readmsgs(ipc, LastMessages, ReadForward,
+                                               atoi(argbuf));
+                               break;
+                       case 7:
+                               forget(ipc);
+                               break;
+                       case 18:
+                               subshell();
+                               break;
+                       case 38:
+                               updatels(ipc);
+                               entroom(ipc);
+                               break;
+                       case 22:
+                               killroom(ipc);
+                               break;
+                       case 32:
+                               userlist(ipc, argbuf);
+                               break;
+                       case 27:
+                               invite(ipc);
+                               break;
+                       case 28:
+                               kickout(ipc);
+                               break;
+                       case 23:
+                               editthisroom(ipc);
+                               break;
+                       case 14:
+                               roomdir(ipc);
+                               break;
+                       case 33:
+                               download(ipc, 0);
+                               break;
+                       case 34:
+                               download(ipc, 1);
+                               break;
+                       case 31:
+                               download(ipc, 2);
+                               break;
+                       case 43:
+                               download(ipc, 3);
+                               break;
+                       case 45:
+                               download(ipc, 4);
+                               break;
+                       case 55:
+                               download(ipc, 5);
+                               break;
+                       case 39:
+                               upload(ipc, 0);
+                               break;
+                       case 40:
+                               upload(ipc, 1);
+                               break;
+                       case 42:
+                               upload(ipc, 2);
+                               break;
+                       case 44:
+                               upload(ipc, 3);
+                               break;
+                       case 57:
+                               cli_upload(ipc);
+                               break;
+                       case 16:
+                               ungoto(ipc);
+                               break;
+                       case 24:
+                               whoknows(ipc);
+                               break;
+                       case 26:
+                               validate(ipc);
+                               break;
+                       case 29:
+                       case 30:
+                               updatels(ipc);
+                               termn8 = 1;
+                               break;
+                       case 48:
+                               enterinfo(ipc);
+                               break;
+                       case 49:
+                               readinfo(ipc);
+                               break;
+                       case 72:
+                               cli_image_upload(ipc, "_userpic_");
+                               break;
+                       case 73:
+                               cli_image_upload(ipc, "_roompic_");
+                               break;
+                       case 75:
+                               enternew(ipc, "roomname", aaa, 20);
+                               r = CtdlIPCChangeRoomname(ipc, aaa, bbb);
+                               if (r / 100 != 2)
+                                       scr_printf("\n%s\n", bbb);
+                               break;
+                       case 76:
+                               enternew(ipc, "hostname", aaa, 25);
+                               r = CtdlIPCChangeHostname(ipc, aaa, bbb);
+                               if (r / 100 != 2)
+                                       scr_printf("\n%s\n", bbb);
+                               break;
+                       case 77:
+                               enternew(ipc, "username", aaa, 32);
+                               r = CtdlIPCChangeUsername(ipc, aaa, bbb);
+                               if (r / 100 != 2)
+                                       scr_printf("\n%s\n", bbb);
+                               break;
+
+                       case 35:
+                               set_password(ipc);
+                               break;
+
+                       case 21:
+                               if (argbuf[0] == 0) {
+                                       strcpy(argbuf, "?");
+                               }
+                               display_help(ipc, argbuf);
+                               break;
+
+                       case 41:
+                               formout(ipc, "register");
+                               entregis(ipc);
+                               break;
+
+                       case 15:
+                               scr_printf("Are you sure (y/n)? ");
+                               if (yesno() == 1) {
+                                       updatels(ipc);
+                                       a = 0;
+                                       termn8 = 1;
+                               }
+                               break;
+
+                       case 85:
+                               scr_printf("All users will be disconnected!  "
+                                          "Really terminate the server? ");
+                               if (yesno() == 1) {
+                                       updatels(ipc);
+                                       r = CtdlIPCTerminateServerNow(ipc, aaa);
+                                       scr_printf("%s\n", aaa);
+                                       if (r / 100 == 2) {
+                                               a = 0;
+                                               termn8 = 1;
+                                       }
+                               }
+                               break;
+
+                       case 86:
+                               scr_printf("Do you really want to schedule a "
+                                          "server shutdown? ");
+                               if (yesno() == 1) {
+                                       r = CtdlIPCTerminateServerScheduled(ipc, 1, aaa);
+                                       if (r / 100 == 2) {
+                                               if (atoi(aaa)) {
+                                                       scr_printf(
+                                                                  "The Citadel server will terminate when all users are logged off.\n"
+                                                                  );
+                                               } else {
+                                                       scr_printf(
+                                                                  "The Citadel server will not terminate.\n"
+                                                                  );
+                                               }
+                                       }
+                               }
+                               break;
+
+                       case 87:
+                               network_config_management(ipc, "listrecp", "Message-by-message mailing list recipients");
+                               break;
+
+                       case 94:
+                               network_config_management(ipc, "digestrecp", "Digest mailing list recipients");
+                               break;
+
+                       case 6:
+                               gotonext(ipc);
+                               break;
+
+                       case 3:
+                               chatmode(ipc);
+                               break;
+
+                       case 17:
+                               who_is_online(ipc, 0);
+                               break;
+
+                       case 79:
+                               who_is_online(ipc, 1);
+                               break;
+
+                       case 91:
+                               who_is_online(ipc, 2);
+                               break;
+                
+                       case 80:
+                               do_system_configuration(ipc);
+                               break;
+
+                       case 82:
+                               do_internet_configuration(ipc);
+                               break;
+
+                       case 84:
+                               quiet_mode(ipc);
+                               break;
+
+                       case 93:
+                               stealth_mode(ipc);
+                               break;
+
+                       case 50:
+                               enter_config(ipc, 2);
+                               break;
+
+                       case 37:
+                               enter_config(ipc, 0);
+                               set_floor_mode(ipc);
+                               break;
+
+                       case 59:
+                               enter_config(ipc, 3);
+                               set_floor_mode(ipc);
+                               break;
+
+                       case 60:
+                               gotofloor(ipc, argbuf, GF_GOTO);
+                               break;
+
+                       case 61:
+                               gotofloor(ipc, argbuf, GF_SKIP);
+                               break;
+
+                       case 62:
+                               forget_this_floor(ipc);
+                               break;
+
+                       case 63:
+                               create_floor(ipc);
+                               break;
+
+                       case 64:
+                               edit_floor(ipc);
+                               break;
+
+                       case 65:
+                               kill_floor(ipc);
+                               break;
+
+                       case 66:
+                               enter_bio(ipc);
+                               break;
+
+                       case 67:
+                               read_bio(ipc);
+                               break;
+
+                       case 25:
+                               edituser(ipc, 25);
+                               break;
+
+                       case 96:
+                               edituser(ipc, 96);
+                               break;
+
+                       case 8:
+                               knrooms(ipc, floor_mode);
+                               scr_printf("\n");
+                               break;
+
+                       case 68:
+                               knrooms(ipc, 2);
+                               scr_printf("\n");
+                               break;
+
+                       case 69:
+                               misc_server_cmd(ipc, argbuf);
+                               break;
+
+                       case 70:
+                               edit_system_message(ipc, argbuf);
+                               break;
+
+                       case 19:
+                               listzrooms(ipc);
+                               scr_printf("\n");
+                               break;
+
+                       case 51:
+                               deletefile(ipc);
+                               break;
+
+                       case 54:
+                               movefile(ipc);
+                               break;
+
+                       case 56:
+                               page_user(ipc);
+                               break;
+
+            case 110:           /* <+> Next room */
+                                gotoroomstep(ipc, 1, 0);
+                            break;
+
+            case 111:           /* <-> Previous room */
+                 gotoroomstep(ipc, 0, 0);
+                            break;
+
+                       case 112:           /* <>> Next floor */
+                 gotofloorstep(ipc, 1, GF_GOTO);
+                            break;
+
+                       case 113:           /* <<> Previous floor */
+                 gotofloorstep(ipc, 0, GF_GOTO);
+                                break;
+
+            case 116:           /* <.> skip to <+> Next room */
+                 gotoroomstep(ipc, 1, 1);
+                            break;
+
+            case 117:           /* <.> skip to <-> Previous room */
+                 gotoroomstep(ipc, 0, 1);
+                            break;
+
+                       case 118:           /* <.> skip to <>> Next floor */
+                 gotofloorstep(ipc, 1, GF_SKIP);
+                            break;
+
+                       case 119:           /* <.> skip to <<> Previous floor */
+                 gotofloorstep(ipc, 0, GF_SKIP);
+                                break;
+
+                       case 114:           
+                 read_config(ipc);
+                                break;
+
+                       case 115:           
+                 system_info(ipc);
+                                break;
+
+                       case 120:           /* .KAnonymous */
+                        dotknown(ipc, 0, NULL);
+                                break;
+
+                       case 121:           /* .KDirectory */
+                        dotknown(ipc, 1, NULL);
+                                break;
+
+                       case 122:           /* .KMatch */
+                        dotknown(ipc, 2, argbuf);
+                                break;
+
+                       case 123:           /* .KpreferredOnly */
+                        dotknown(ipc, 3, NULL);
+                                break;
+
+                       case 124:           /* .KPrivate */
+                        dotknown(ipc, 4, NULL);
+                                break;
+
+                       case 125:           /* .KRead only */
+                        dotknown(ipc, 5, NULL);
+                                break;
+
+                       case 126:           /* .KShared */
+                        dotknown(ipc, 6, NULL);
+                                break;
+
+                       case 127:           /* Configure POP3 aggregation */
+                               do_pop3client_configuration(ipc);
+                               break;
+
+                       case 128:           /* Configure XML/RSS feed retrieval */
+                               do_rssclient_configuration(ipc);
+                               break;
+
+                       default:
+                               break;
+                       }       /* end switch */
+       } while (termn8 == 0);
+
+TERMN8:        scr_printf("%s logged out.", fullname);
+       termn8 = 0;
+       color(ORIGINAL_PAIR);
+       scr_printf("\n");
+       while (marchptr != NULL) {
+               remove_march(marchptr->march_name, 0);
+       }
+       if (mcmd == 30) {
+               scr_printf("\n\nType 'off' to disconnect, or next user...\n");
+       }
+       CtdlIPCLogout(ipc);
+       if ((mcmd == 29) || (mcmd == 15)) {
+               stty_ctdl(SB_RESTORE);
+               formout(ipc, "goodbye");
+               logoff(ipc, 0);
+       }
+       /* Free the ungoto list */
+       for (lp = 0; lp < uglistsize; lp++) {
+               free(uglist[lp]);
+       }
+    uglistsize = 0;
+       goto GSTA;
+
+}      /* end main() */
+
diff --git a/textclient/citadel_ipc.c b/textclient/citadel_ipc.c
new file mode 100644 (file)
index 0000000..da0f7b9
--- /dev/null
@@ -0,0 +1,3351 @@
+/*
+ * Copyright (c) 1987-2018 by the citadel.org team
+ *
+ *  This program is open source software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 3.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#include "textclient.h"
+
+#ifdef HAVE_OPENSSL
+static SSL_CTX *ssl_ctx;
+char arg_encrypt;
+char rc_encrypt;
+
+#endif /* HAVE_OPENSSL */
+
+#ifndef INADDR_NONE
+#define INADDR_NONE 0xffffffff
+#endif
+
+static void (*status_hook)(char *s) = NULL;
+char ctdl_autoetc_dir[PATH_MAX]="";
+char file_citadel_rc[PATH_MAX]="";
+char ctdl_run_dir[PATH_MAX]="";
+char ctdl_etc_dir[PATH_MAX]="";
+char ctdl_home_directory[PATH_MAX] = "";
+char file_citadel_socket[PATH_MAX]="";
+
+char *viewdefs[]={
+        "Messages",
+        "Summary",
+        "Address book",
+        "Calendar",
+        "Tasks"
+};
+
+char *axdefs[]={
+        "Deleted",
+        "New User",
+        "Problem User",
+        "Local User",
+        "Network User",
+        "Preferred User",
+        "Admin",
+        "Admin"
+        };
+
+
+void CtdlIPC_lock(CtdlIPC *ipc)
+{
+       if (ipc->network_status_cb) ipc->network_status_cb(1);
+}
+
+
+void CtdlIPC_unlock(CtdlIPC *ipc)
+{
+       if (ipc->network_status_cb) ipc->network_status_cb(0);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+
+char *libcitadelclient_version_string(void) {
+        return "libcitadelclient(unnumbered)";
+}
+
+
+
+
+#define COMPUTE_DIRECTORY(SUBDIR) memcpy(dirbuffer,SUBDIR, sizeof dirbuffer);\
+       snprintf(SUBDIR,sizeof SUBDIR,  "%s%s%s%s%s%s%s", \
+                        (home&!relh)?ctdl_home_directory:basedir, \
+             ((basedir!=ctdldir)&(home&!relh))?basedir:"/", \
+             ((basedir!=ctdldir)&(home&!relh))?"/":"", \
+                        relhome, \
+             (relhome[0]!='\0')?"/":"",\
+                        dirbuffer,\
+                        (dirbuffer[0]!='\0')?"/":"");
+
+#define DBG_PRINT(A) if (dbg==1) fprintf (stderr,"%s : %s \n", #A, A)
+
+
+void calc_dirs_n_files(int relh, int home, const char *relhome, char  *ctdldir, int dbg)
+{
+       const char* basedir = "";
+       char dirbuffer[PATH_MAX] = "";
+
+       StripSlashes(ctdldir, 1);
+
+#ifndef HAVE_RUN_DIR
+       basedir=ctdldir;
+#else
+       basedir=RUN_DIR;
+#endif
+       COMPUTE_DIRECTORY(ctdl_run_dir);
+       StripSlashes(ctdl_run_dir, 1);
+
+
+#ifndef HAVE_AUTO_ETC_DIR
+       basedir=ctdldir;
+#else
+       basedir=AUTO_ETC_DIR;
+#endif
+       COMPUTE_DIRECTORY(ctdl_autoetc_dir);
+       StripSlashes(ctdl_autoetc_dir, 1);
+
+
+#ifndef HAVE_ETC_DIR
+       basedir=ctdldir;
+#else
+       basedir=ETC_DIR;
+#endif
+       COMPUTE_DIRECTORY(ctdl_etc_dir);
+       StripSlashes(ctdl_etc_dir, 1);
+
+
+
+       snprintf(file_citadel_rc, 
+                        sizeof file_citadel_rc,
+                        "%scitadel.rc",
+                        ctdl_etc_dir);
+       StripSlashes(file_citadel_rc, 0);
+
+       snprintf(file_citadel_socket, 
+                        sizeof file_citadel_socket,
+                               "%scitadel.socket",
+                        ctdl_run_dir);
+       StripSlashes(file_citadel_socket, 0);
+
+       DBG_PRINT(ctdl_run_dir);
+       DBG_PRINT(file_citadel_socket);
+       DBG_PRINT(ctdl_etc_dir);
+       DBG_PRINT(file_citadel_rc);
+}
+
+void setCryptoStatusHook(void (*hook)(char *s)) {
+       status_hook = hook;
+}
+
+void CtdlIPC_SetNetworkStatusCallback(CtdlIPC *ipc, void (*hook)(int state)) {
+       ipc->network_status_cb = hook;
+}
+
+
+char instant_msgs = 0;
+
+
+static void serv_read(CtdlIPC *ipc, char *buf, unsigned int bytes);
+static void serv_write(CtdlIPC *ipc, const char *buf, unsigned int nbytes);
+#ifdef HAVE_OPENSSL
+static void serv_read_ssl(CtdlIPC *ipc, char *buf, unsigned int bytes);
+static void serv_write_ssl(CtdlIPC *ipc, const char *buf, unsigned int nbytes);
+static void endtls(SSL *ssl);
+#endif /* HAVE_OPENSSL */
+static void CtdlIPC_getline(CtdlIPC* ipc, char *buf);
+static void CtdlIPC_putline(CtdlIPC *ipc, const char *buf);
+
+
+
+const char *svn_revision(void);
+
+/*
+ * Does nothing.  The server should always return 200.
+ */
+int CtdlIPCNoop(CtdlIPC *ipc)
+{
+       char aaa[128];
+
+       return CtdlIPCGenericCommand(ipc, "NOOP", NULL, 0, NULL, NULL, aaa);
+}
+
+
+/*
+ * Does nothing interesting.  The server should always return 200
+ * along with your string.
+ */
+int CtdlIPCEcho(CtdlIPC *ipc, const char *arg, char *cret)
+{
+       int ret;
+       char *aaa;
+       
+       if (!arg) return -2;
+       if (!cret) return -2;
+
+       aaa = (char *)malloc((size_t)(strlen(arg) + 6));
+       if (!aaa) return -1;
+
+       sprintf(aaa, "ECHO %s", arg);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/*
+ * Asks the server to close the connecction.
+ * Should always return 200.
+ */
+int CtdlIPCQuit(CtdlIPC *ipc)
+{
+       int ret = 221;          /* Default to successful quit */
+       char aaa[SIZ]; 
+
+       CtdlIPC_lock(ipc);
+       if (ipc->sock > -1) {
+               CtdlIPC_putline(ipc, "QUIT");
+               CtdlIPC_getline(ipc, aaa);
+               ret = atoi(aaa);
+       }
+#ifdef HAVE_OPENSSL
+       if (ipc->ssl)
+               SSL_shutdown(ipc->ssl);
+       ipc->ssl = NULL;
+#endif
+       if (ipc->sock)
+               shutdown(ipc->sock, 2); /* Close connection; we're dead */
+       ipc->sock = -1;
+       CtdlIPC_unlock(ipc);
+       return ret;
+}
+
+
+/*
+ * Asks the server to log out.  Should always return 200, even if no user
+ * was logged in.  The user will not be logged in after this!
+ */
+int CtdlIPCLogout(CtdlIPC *ipc)
+{
+       int ret;
+       char aaa[SIZ];
+
+       CtdlIPC_lock(ipc);
+       CtdlIPC_putline(ipc, "LOUT");
+       CtdlIPC_getline(ipc, aaa);
+       ret = atoi(aaa);
+       CtdlIPC_unlock(ipc);
+       return ret;
+}
+
+
+/*
+ * First stage of authentication - pass the username.  Returns 300 if the
+ * username is able to log in, with the username correctly spelled in cret.
+ * Returns various 500 error codes if the user doesn't exist, etc.
+ */
+int CtdlIPCTryLogin(CtdlIPC *ipc, const char *username, char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!username) return -2;
+       if (!cret) return -2;
+
+       aaa = (char *)malloc((size_t)(strlen(username) + 6));
+       if (!aaa) return -1;
+
+       sprintf(aaa, "USER %s", username);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/*
+ * Second stage of authentication - provide password.  The server returns
+ * 200 and several arguments in cret relating to the user's account.
+ */
+int CtdlIPCTryPassword(CtdlIPC *ipc, const char *passwd, char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!passwd) return -2;
+       if (!cret) return -2;
+
+       aaa = (char *)malloc((size_t)(strlen(passwd) + 6));
+       if (!aaa) return -1;
+
+       sprintf(aaa, "PASS %s", passwd);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/*
+ * Second stage of authentication - provide password.  The server returns
+ * 200 and several arguments in cret relating to the user's account.
+ */
+int CtdlIPCTryApopPassword(CtdlIPC *ipc, const char *response, char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!response) return -2;
+       if (!cret) return -2;
+
+       aaa = (char *)malloc((size_t)(strlen(response) + 6));
+       if (!aaa) return -1;
+
+       sprintf(aaa, "PAS2 %s", response);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/*
+ * Create a new user.  This returns 200 plus the same arguments as TryPassword
+ * if selfservice is nonzero, unless there was a problem creating the account.
+ * If selfservice is zero, creates a new user but does not log out the existing
+ * user - intended for use by system administrators to create accounts on
+ * behalf of other users.
+ */
+int CtdlIPCCreateUser(CtdlIPC *ipc, const char *username, int selfservice, char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!username) return -2;
+       if (!cret) return -2;
+
+       aaa = (char *)malloc((size_t)(strlen(username) + 6));
+       if (!aaa) return -1;
+
+       sprintf(aaa, "%s %s", selfservice ? "NEWU" : "CREU",  username);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/*
+ * Changes the user's password.  Returns 200 if changed, errors otherwise.
+ */
+int CtdlIPCChangePassword(CtdlIPC *ipc, const char *passwd, char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!passwd) return -2;
+       if (!cret) return -2;
+
+       aaa = (char *)malloc((size_t)(strlen(passwd) + 6));
+       if (!aaa) return -1;
+
+       sprintf(aaa, "SETP %s", passwd);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* LKRN */
+/* Caller must free the march list */
+/* Room types are defined in enum RoomList; keep these in sync! */
+/* floor is -1 for all, or floornum */
+int CtdlIPCKnownRooms(CtdlIPC *ipc, enum RoomList which, int floor, struct march **listing, char *cret)
+{
+       int ret;
+       struct march *march = NULL;
+       static char *proto[] =
+               {"LKRA", "LKRN", "LKRO", "LZRM", "LRMS", "LPRM" };
+       char aaa[SIZ];
+       char *bbb = NULL;
+       size_t bbb_len;
+
+       if (!listing) return -2;
+       if (*listing) return -2;        /* Free the listing first */
+       if (!cret) return -2;
+       /* if (which < 0 || which > 4) return -2; */
+       if (floor < -1) return -2;      /* Can't validate upper bound, sorry */
+
+       sprintf(aaa, "%s %d", proto[which], floor);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
+       if (ret / 100 == 1) {
+               struct march *mptr;
+
+               while (bbb && strlen(bbb)) {
+                       int a;
+
+                       extract_token(aaa, bbb, 0, '\n', sizeof aaa);
+                       a = strlen(aaa);
+                       memmove(bbb, bbb + a + 1, strlen(bbb) - a);
+                       mptr = (struct march *) malloc(sizeof (struct march));
+                       if (mptr) {
+                               mptr->next = NULL;
+                               extract_token(mptr->march_name, aaa, 0, '|', sizeof mptr->march_name);
+                               mptr->march_flags = (unsigned int) extract_int(aaa, 1);
+                               mptr->march_floor = (char) extract_int(aaa, 2);
+                               mptr->march_order = (char) extract_int(aaa, 3);
+                               mptr->march_flags2 = (unsigned int) extract_int(aaa, 4);
+                               mptr->march_access = (char) extract_int(aaa, 5);
+                               if (march == NULL)
+                                       march = mptr;
+                               else {
+                                       struct march *mptr2;
+
+                                       mptr2 = march;
+                                       while (mptr2->next != NULL)
+                                               mptr2 = mptr2->next;
+                                       mptr2->next = mptr;
+                               }
+                       }
+               }
+       }
+       *listing = march;
+       if (bbb) free(bbb);
+       return ret;
+}
+
+
+/* GETU */
+/* Caller must free the struct ctdluser; caller may pass an existing one */
+int CtdlIPCGetConfig(CtdlIPC *ipc, struct ctdluser **uret, char *cret)
+{
+       int ret;
+
+       if (!cret) return -2;
+       if (!uret) return -2;
+       if (!*uret) *uret = (struct ctdluser *)calloc(1, sizeof (struct ctdluser));
+       if (!*uret) return -1;
+
+       ret = CtdlIPCGenericCommand(ipc, "GETU", NULL, 0, NULL, NULL, cret);
+       if (ret / 100 == 2) {
+               uret[0]->flags = extract_int(cret, 2);
+       }
+       return ret;
+}
+
+
+/* SETU */
+int CtdlIPCSetConfig(CtdlIPC *ipc, struct ctdluser *uret, char *cret)
+{
+       char aaa[48];
+
+       if (!uret) return -2;
+       if (!cret) return -2;
+
+       sprintf(aaa,
+               "SETU 80|24|%d",
+               uret->flags
+       );
+       return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* RENU */
+int CtdlIPCRenameUser(CtdlIPC *ipc, char *oldname, char *newname, char *cret)
+{
+       int ret;
+       char cmd[256];
+
+       if (!oldname) return -2;
+       if (!newname) return -2;
+       if (!cret) return -2;
+
+       snprintf(cmd, sizeof cmd, "RENU %s|%s", oldname, newname);
+       ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
+       return ret;
+}
+
+
+/* GOTO */
+int CtdlIPCGotoRoom(CtdlIPC *ipc, const char *room, const char *passwd,
+               struct ctdlipcroom **rret, char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!rret) return -2;
+       if (!*rret) *rret = (struct ctdlipcroom *)calloc(1, sizeof (struct ctdlipcroom));
+       if (!*rret) return -1;
+
+       if (passwd) {
+               aaa = (char *)malloc(strlen(room) + strlen(passwd) + 7);
+               if (!aaa) {
+                       free(*rret);
+                       return -1;
+               }
+               sprintf(aaa, "GOTO %s|%s", room, passwd);
+       } else {
+               aaa = (char *)malloc(strlen(room) + 6);
+               if (!aaa) {
+                       free(*rret);
+                       return -1;
+               }
+               sprintf(aaa, "GOTO %s", room);
+       }
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       if (ret / 100 == 2) {
+               extract_token(rret[0]->RRname, cret, 0, '|', sizeof rret[0]->RRname);
+               rret[0]->RRunread = extract_long(cret, 1);
+               rret[0]->RRtotal = extract_long(cret, 2);
+               rret[0]->RRinfoupdated = extract_int(cret, 3);
+               rret[0]->RRflags = extract_int(cret, 4);
+               rret[0]->RRhighest = extract_long(cret, 5);
+               rret[0]->RRlastread = extract_long(cret, 6);
+               rret[0]->RRismailbox = extract_int(cret, 7);
+               rret[0]->RRaide = extract_int(cret, 8);
+               rret[0]->RRnewmail = extract_long(cret, 9);
+               rret[0]->RRfloor = extract_int(cret, 10);
+               rret[0]->RRcurrentview = extract_int(cret, 11);
+               rret[0]->RRdefaultview = extract_int(cret, 12);
+               /* position 13 is a trash folder flag ... irrelevant in this client */
+               rret[0]->RRflags2 = extract_int(cret, 14);
+       } else {
+               free(*rret);
+               *rret = NULL;
+       }
+       free(aaa);
+       return ret;
+}
+
+
+/* MSGS */
+/* which is 0 = all, 1 = old, 2 = new, 3 = last, 4 = first, 5 = gt, 6 = lt */
+/* whicharg is number of messages, applies to last, first, gt, lt */
+int CtdlIPCGetMessages(CtdlIPC *ipc, enum MessageList which, int whicharg,
+               const char *mtemplate, unsigned long **mret, char *cret)
+{
+       int ret;
+       unsigned long count = 0;
+       static char *proto[] =
+               { "ALL", "OLD", "NEW", "LAST", "FIRST", "GT", "LT" };
+       char aaa[33];
+       char *bbb = NULL;
+       size_t bbb_len;
+
+       if (!cret) return -2;
+       if (!mret) return -2;
+       if (*mret) return -2;
+       if (which < 0 || which > 6) return -2;
+
+       if (which <= 2)
+               sprintf(aaa, "MSGS %s||%d", proto[which],
+                               (mtemplate) ? 1 : 0);
+       else
+               sprintf(aaa, "MSGS %s|%d|%d", proto[which], whicharg,
+                               (mtemplate) ? 1 : 0);
+       if (mtemplate) count = strlen(mtemplate);
+       ret = CtdlIPCGenericCommand(ipc, aaa, mtemplate, count, &bbb, &bbb_len, cret);
+       if (ret / 100 != 1)
+               return ret;
+       count = 0;
+       *mret = (unsigned long *)calloc(1, sizeof(unsigned long));
+       if (!*mret)
+               return -1;
+       while (bbb && strlen(bbb)) {
+               extract_token(aaa, bbb, 0, '\n', sizeof aaa);
+               remove_token(bbb, 0, '\n');
+               *mret = (unsigned long *)realloc(*mret, (size_t)((count + 2) *
+                                       sizeof (unsigned long)));
+               if (*mret) {
+                       (*mret)[count++] = atol(aaa);
+                       (*mret)[count] = 0L;
+               } else {
+                       break;
+               }
+       }
+       if (bbb) free(bbb);
+       return ret;
+}
+
+
+/* MSG0, MSG2 */
+int CtdlIPCGetSingleMessage(CtdlIPC *ipc, long msgnum, int headers, int as_mime,
+               struct ctdlipcmessage **mret, char *cret)
+{
+       int ret;
+       char aaa[SIZ];
+       char *bbb = NULL;
+       size_t bbb_len;
+       int multipart_hunting = 0;
+       char multipart_prefix[128];
+       char encoding[256];
+
+       if (!cret) return -1;
+       if (!mret) return -1;
+       if (!*mret) *mret = (struct ctdlipcmessage *)calloc(1, sizeof (struct ctdlipcmessage));
+       if (!*mret) return -1;
+       if (!msgnum) return -1;
+
+       strcpy(encoding, "");
+       strcpy(mret[0]->content_type, "");
+       sprintf(aaa, "MSG%d %ld|%d", as_mime, msgnum, headers);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
+       if (ret / 100 == 1) {
+               if (as_mime != 2) {
+                       strcpy(mret[0]->mime_chosen, "1");      /* Default chosen-part is "1" */
+                       while (strlen(bbb) > 4 && bbb[4] == '=') {
+                               extract_token(aaa, bbb, 0, '\n', sizeof aaa);
+                               remove_token(bbb, 0, '\n');
+
+                               if (!strncasecmp(aaa, "nhdr=yes", 8))
+                                       mret[0]->nhdr = 1;
+                               else if (!strncasecmp(aaa, "from=", 5))
+                                       safestrncpy(mret[0]->author, &aaa[5], SIZ);
+                               else if (!strncasecmp(aaa, "type=", 5))
+                                       mret[0]->type = atoi(&aaa[5]);
+                               else if (!strncasecmp(aaa, "msgn=", 5))
+                                       safestrncpy(mret[0]->msgid, &aaa[5], SIZ);
+                               else if (!strncasecmp(aaa, "subj=", 5))
+                                       safestrncpy(mret[0]->subject, &aaa[5], SIZ);
+                               else if (!strncasecmp(aaa, "rfca=", 5))
+                                       safestrncpy(mret[0]->email, &aaa[5], SIZ);
+                               else if (!strncasecmp(aaa, "room=", 5))
+                                       safestrncpy(mret[0]->room, &aaa[5], SIZ);
+                               else if (!strncasecmp(aaa, "rcpt=", 5))
+                                       safestrncpy(mret[0]->recipient, &aaa[5], SIZ);
+                               else if (!strncasecmp(aaa, "wefw=", 5))
+                                       safestrncpy(mret[0]->references, &aaa[5], SIZ);
+                               else if (!strncasecmp(aaa, "time=", 5))
+                                       mret[0]->time = atol(&aaa[5]);
+
+                               /* Multipart/alternative prefix & suffix strings help
+                                * us to determine which part we want to download.
+                                */
+                               else if (!strncasecmp(aaa, "pref=", 5)) {
+                                       extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
+                                       if (!strcasecmp(multipart_prefix,
+                                          "multipart/alternative")) {
+                                               ++multipart_hunting;
+                                       }
+                               }
+                               else if (!strncasecmp(aaa, "suff=", 5)) {
+                                       extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
+                                       if (!strcasecmp(multipart_prefix,
+                                          "multipart/alternative")) {
+                                               ++multipart_hunting;
+                                       }
+                               }
+
+                               else if (!strncasecmp(aaa, "part=", 5)) {
+                                       struct parts *ptr, *chain;
+       
+                                       ptr = (struct parts *)calloc(1, sizeof (struct parts));
+                                       if (ptr) {
+
+                                               /* Fill the buffers for the caller */
+                                               extract_token(ptr->name, &aaa[5], 0, '|', sizeof ptr->name);
+                                               extract_token(ptr->filename, &aaa[5], 1, '|', sizeof ptr->filename);
+                                               extract_token(ptr->number, &aaa[5], 2, '|', sizeof ptr->number);
+                                               extract_token(ptr->disposition, &aaa[5], 3, '|', sizeof ptr->disposition);
+                                               extract_token(ptr->mimetype, &aaa[5], 4, '|', sizeof ptr->mimetype);
+                                               ptr->length = extract_long(&aaa[5], 5);
+                                               if (!mret[0]->attachments)
+                                                       mret[0]->attachments = ptr;
+                                               else {
+                                                       chain = mret[0]->attachments;
+                                                       while (chain->next)
+                                                               chain = chain->next;
+                                                       chain->next = ptr;
+                                               }
+
+                                               /* Now handle multipart/alternative */
+                                               if (multipart_hunting > 0) {
+                                                       if ( (!strcasecmp(ptr->mimetype,
+                                                            "text/plain"))
+                                                          || (!strcasecmp(ptr->mimetype,
+                                                             "text/html")) ) {
+                                                               strcpy(mret[0]->mime_chosen,
+                                                                       ptr->number);
+                                                       }
+                                               }
+
+                                       }
+                               }
+                       }
+                       /* Eliminate "text\n" */
+                       remove_token(bbb, 0, '\n');
+
+                       /* If doing a MIME thing, pull out the extra headers */
+                       if (as_mime == 4) {
+                               do {
+                                       if (!strncasecmp(bbb, "Content-type:", 13)) {
+                                               extract_token(mret[0]->content_type, bbb, 0, '\n', sizeof mret[0]->content_type);
+                                               strcpy(mret[0]->content_type, &mret[0]->content_type[13]);
+                                               striplt(mret[0]->content_type);
+
+                                               /* strip out ";charset=" portion.  FIXME do something with
+                                                * the charset (like... convert it) instead of just throwing
+                                                * it away
+                                                */
+                                               if (strstr(mret[0]->content_type, ";") != NULL) {
+                                                       strcpy(strstr(mret[0]->content_type, ";"), "");
+                                               }
+
+                                       }
+                                       if (!strncasecmp(bbb, "X-Citadel-MSG4-Partnum:", 23)) {
+                                               extract_token(mret[0]->mime_chosen, bbb, 0, '\n', sizeof mret[0]->mime_chosen);
+                                               strcpy(mret[0]->mime_chosen, &mret[0]->mime_chosen[23]);
+                                               striplt(mret[0]->mime_chosen);
+                                       }
+                                       if (!strncasecmp(bbb, "Content-transfer-encoding:", 26)) {
+                                               extract_token(encoding, bbb, 0, '\n', sizeof encoding);
+                                               strcpy(encoding, &encoding[26]);
+                                               striplt(encoding);
+                                       }
+                                       remove_token(bbb, 0, '\n');
+                               } while ((bbb[0] != 0) && (bbb[0] != '\n'));
+                               remove_token(bbb, 0, '\n');
+                       }
+
+
+               }
+               if (strlen(bbb)) {
+
+                       if ( (!strcasecmp(encoding, "base64")) || (!strcasecmp(encoding, "quoted-printable")) ) {
+                               char *ccc = NULL;
+                               int bytes_decoded = 0;
+                               ccc = malloc(strlen(bbb) + 32768);
+                               if (!strcasecmp(encoding, "base64")) {
+                                       bytes_decoded = CtdlDecodeBase64(ccc, bbb, strlen(bbb));
+                               }
+                               else if (!strcasecmp(encoding, "quoted-printable")) {
+                                       bytes_decoded = CtdlDecodeQuotedPrintable(ccc, bbb, strlen(bbb));
+                               }
+                               ccc[bytes_decoded] = 0;
+                               free(bbb);
+                               bbb = ccc;
+                       }
+
+                       /* FIXME: Strip trailing whitespace */
+                       bbb = (char *)realloc(bbb, (size_t)(strlen(bbb) + 1));
+
+               } else {
+                       bbb = (char *)realloc(bbb, 1);
+                       *bbb = '\0';
+               }
+               mret[0]->text = bbb;
+       }
+       return ret;
+}
+
+
+/* WHOK */
+int CtdlIPCWhoKnowsRoom(CtdlIPC *ipc, char **listing, char *cret)
+{
+       int ret;
+       size_t bytes;
+
+       if (!cret) return -2;
+       if (!listing) return -2;
+       if (*listing) return -2;
+
+       ret = CtdlIPCGenericCommand(ipc, "WHOK", NULL, 0, listing, &bytes, cret);
+       return ret;
+}
+
+
+/* INFO */
+int CtdlIPCServerInfo(CtdlIPC *ipc, char *cret)
+{
+       int ret;
+       size_t bytes;
+       char *listing = NULL;
+       char buf[SIZ];
+
+       if (!cret) return -2;
+
+       ret = CtdlIPCGenericCommand(ipc, "INFO", NULL, 0, &listing, &bytes, cret);
+       if (ret / 100 == 1) {
+               int line = 0;
+
+               while (*listing && strlen(listing)) {
+                       extract_token(buf, listing, 0, '\n', sizeof buf);
+                       remove_token(listing, 0, '\n');
+                       switch (line++) {
+                       case 0:         ipc->ServInfo.pid = atoi(buf);
+                                       break;
+                       case 1:         strcpy(ipc->ServInfo.nodename,buf);
+                                       break;
+                       case 2:         strcpy(ipc->ServInfo.humannode,buf);
+                                       break;
+                       case 3:         strcpy(ipc->ServInfo.fqdn,buf);
+                                       break;
+                       case 4:         strcpy(ipc->ServInfo.software,buf);
+                                       break;
+                       case 5:         ipc->ServInfo.rev_level = atoi(buf);
+                                       break;
+                       case 6:         strcpy(ipc->ServInfo.site_location,buf);
+                                       break;
+                       case 7:         strcpy(ipc->ServInfo.sysadm,buf);
+                                       break;
+                       case 9:         strcpy(ipc->ServInfo.moreprompt,buf);
+                                       break;
+                       case 10:        ipc->ServInfo.ok_floors = atoi(buf);
+                                       break;
+                       case 11:        ipc->ServInfo.paging_level = atoi(buf);
+                                       break;
+                       case 13:        ipc->ServInfo.supports_qnop = atoi(buf);
+                                       break;
+                       case 14:        ipc->ServInfo.supports_ldap = atoi(buf);
+                                       break;
+                       case 15:        ipc->ServInfo.newuser_disabled = atoi(buf);
+                                       break;
+                       case 16:        strcpy(ipc->ServInfo.default_cal_zone, buf);
+                                       break;
+                       case 17:        ipc->ServInfo.load_avg = atof(buf);
+                                       break;
+                       case 18:        ipc->ServInfo.worker_avg = atof(buf);
+                                       break;
+                       case 19:        ipc->ServInfo.thread_count = atoi(buf);
+                                       break;
+                       case 20:        ipc->ServInfo.has_sieve = atoi(buf);
+                                       break;
+                       case 21:        ipc->ServInfo.fulltext_enabled = atoi(buf);
+                                       break;
+                       case 22:        strcpy(ipc->ServInfo.svn_revision, buf);
+                                       break;
+                       case 24:        ipc->ServInfo.guest_logins = atoi(buf);
+                                       break;
+                       }
+               }
+
+       }
+       if (listing) free(listing);
+       return ret;
+}
+
+
+/* RDIR */
+int CtdlIPCReadDirectory(CtdlIPC *ipc, char **listing, char *cret)
+{
+       int ret;
+       size_t bytes;
+
+       if (!cret) return -2;
+       if (!listing) return -2;
+       if (*listing) return -2;
+
+       ret = CtdlIPCGenericCommand(ipc, "RDIR", NULL, 0, listing, &bytes, cret);
+       return ret;
+}
+
+
+/*
+ * Set last-read pointer in this room to msgnum, or 0 for HIGHEST.
+ */
+int CtdlIPCSetLastRead(CtdlIPC *ipc, long msgnum, char *cret)
+{
+       int ret;
+       char aaa[64];
+
+       if (!cret) return -2;
+
+       if (msgnum) {
+               sprintf(aaa, "SLRP %ld", msgnum);
+       }
+       else {
+               sprintf(aaa, "SLRP HIGHEST");
+       }
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       return ret;
+}
+
+
+/* INVT */
+int CtdlIPCInviteUserToRoom(CtdlIPC *ipc, const char *username, char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!username) return -2;
+
+       aaa = (char *)malloc(strlen(username) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "INVT %s", username);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* KICK */
+int CtdlIPCKickoutUserFromRoom(CtdlIPC *ipc, const char *username, char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!cret) return -1;
+       if (!username) return -1;
+
+       aaa = (char *)malloc(strlen(username) + 6);
+
+       sprintf(aaa, "KICK %s", username);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* GETR */
+int CtdlIPCGetRoomAttributes(CtdlIPC *ipc, struct ctdlroom **qret, char *cret)
+{
+       int ret;
+
+       if (!cret) return -2;
+       if (!qret) return -2;
+       if (!*qret) *qret = (struct ctdlroom *)calloc(1, sizeof (struct ctdlroom));
+       if (!*qret) return -1;
+
+       ret = CtdlIPCGenericCommand(ipc, "GETR", NULL, 0, NULL, NULL, cret);
+       if (ret / 100 == 2) {
+               extract_token(qret[0]->QRname, cret, 0, '|', sizeof qret[0]->QRname);
+               extract_token(qret[0]->QRpasswd, cret, 1, '|', sizeof qret[0]->QRpasswd);
+               extract_token(qret[0]->QRdirname, cret, 2, '|', sizeof qret[0]->QRdirname);
+               qret[0]->QRflags = extract_int(cret, 3);
+               qret[0]->QRfloor = extract_int(cret, 4);
+               qret[0]->QRorder = extract_int(cret, 5);
+               qret[0]->QRdefaultview = extract_int(cret, 6);
+               qret[0]->QRflags2 = extract_int(cret, 7);
+       }
+       return ret;
+}
+
+
+/* SETR */
+/* set forget to kick all users out of room */
+int CtdlIPCSetRoomAttributes(CtdlIPC *ipc, int forget, struct ctdlroom *qret, char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!qret) return -2;
+
+       aaa = (char *)malloc(strlen(qret->QRname) + strlen(qret->QRpasswd) +
+                       strlen(qret->QRdirname) + 64);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "SETR %s|%s|%s|%d|%d|%d|%d|%d|%d",
+                       qret->QRname, qret->QRpasswd, qret->QRdirname,
+                       qret->QRflags, forget, qret->QRfloor, qret->QRorder,
+                       qret->QRdefaultview, qret->QRflags2);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* GETA */
+int CtdlIPCGetRoomAide(CtdlIPC *ipc, char *cret)
+{
+       if (!cret) return -1;
+
+       return CtdlIPCGenericCommand(ipc, "GETA", NULL, 0, NULL, NULL, cret);
+}
+
+
+/* SETA */
+int CtdlIPCSetRoomAide(CtdlIPC *ipc, const char *username, char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!username) return -2;
+
+       aaa = (char *)malloc(strlen(username) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "SETA %s", username);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* ENT0 */
+int CtdlIPCPostMessage(CtdlIPC *ipc, int flag, int *subject_required,  struct ctdlipcmessage *mr, char *cret)
+{
+       int ret;
+       char cmd[SIZ];
+       char *ptr;
+
+       if (!cret) return -2;
+       if (!mr) return -2;
+
+       if (mr->references) {
+               for (ptr=mr->references; *ptr != 0; ++ptr) {
+                       if (*ptr == '|') *ptr = '!';
+               }
+       }
+
+       snprintf(cmd, sizeof cmd,
+                       "ENT0 %d|%s|%d|%d|%s|%s||||||%s|", flag, mr->recipient,
+                       mr->anonymous, mr->type, mr->subject, mr->author, mr->references);
+       ret = CtdlIPCGenericCommand(ipc, cmd, mr->text, strlen(mr->text), NULL,
+                       NULL, cret);
+       if ((flag == 0) && (subject_required != NULL)) {
+               /* Is the server strongly recommending that the user enter a message subject? */
+               if ((cret[3] != '\0') && (cret[4] != '\0')) {
+                       *subject_required = extract_int(&cret[4], 1);
+               }
+
+               
+       }
+       return ret;
+}
+
+
+/* RINF */
+int CtdlIPCRoomInfo(CtdlIPC *ipc, char **iret, char *cret)
+{
+       size_t bytes;
+
+       if (!cret) return -2;
+       if (!iret) return -2;
+       if (*iret) return -2;
+
+       return CtdlIPCGenericCommand(ipc, "RINF", NULL, 0, iret, &bytes, cret);
+}
+
+
+/* DELE */
+int CtdlIPCDeleteMessage(CtdlIPC *ipc, long msgnum, char *cret)
+{
+       char aaa[64];
+
+       if (!cret) return -2;
+       if (!msgnum) return -2;
+
+       sprintf(aaa, "DELE %ld", msgnum);
+       return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* MOVE */
+int CtdlIPCMoveMessage(CtdlIPC *ipc, int copy, long msgnum, const char *destroom, char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!destroom) return -2;
+       if (!msgnum) return -2;
+
+       aaa = (char *)malloc(strlen(destroom) + 28);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "MOVE %ld|%s|%d", msgnum, destroom, copy);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* KILL */
+int CtdlIPCDeleteRoom(CtdlIPC *ipc, int for_real, char *cret)
+{
+       char aaa[64];
+
+       if (!cret) return -2;
+
+       sprintf(aaa, "KILL %d", for_real);
+       return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* CRE8 */
+int CtdlIPCCreateRoom(CtdlIPC *ipc, int for_real, const char *roomname, int type,
+               const char *password, int floor, char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!roomname) return -2;
+
+       if (password) {
+               aaa = (char *)malloc(strlen(roomname) + strlen(password) + 40);
+               if (!aaa) return -1;
+               sprintf(aaa, "CRE8 %d|%s|%d|%s|%d", for_real, roomname, type,
+                               password, floor);
+       } else {
+               aaa = (char *)malloc(strlen(roomname) + 40);
+               if (!aaa) return -1;
+               sprintf(aaa, "CRE8 %d|%s|%d||%d", for_real, roomname, type,
+                               floor);
+       }
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* FORG */
+int CtdlIPCForgetRoom(CtdlIPC *ipc, char *cret)
+{
+       if (!cret) return -2;
+
+       return CtdlIPCGenericCommand(ipc, "FORG", NULL, 0, NULL, NULL, cret);
+}
+
+
+/* MESG */
+int CtdlIPCSystemMessage(CtdlIPC *ipc, const char *message, char **mret, char *cret)
+{
+       int ret;
+       char *aaa;
+       size_t bytes;
+
+       if (!cret) return -2;
+       if (!mret) return -2;
+       if (*mret) return -2;
+       if (!message) return -2;
+
+       aaa = (char *)malloc(strlen(message) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "MESG %s", message);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, mret, &bytes, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* GNUR */
+int CtdlIPCNextUnvalidatedUser(CtdlIPC *ipc, char *cret)
+{
+       if (!cret) return -2;
+
+       return CtdlIPCGenericCommand(ipc, "GNUR", NULL, 0, NULL, NULL, cret);
+}
+
+
+/* GREG */
+int CtdlIPCGetUserRegistration(CtdlIPC *ipc, const char *username, char **rret, char *cret)
+{
+       int ret;
+       char *aaa;
+       size_t bytes;
+
+       if (!cret) return -2;
+       if (!rret) return -2;
+       if (*rret) return -2;
+
+       if (username)
+               aaa = (char *)malloc(strlen(username) + 6);
+       else
+               aaa = (char *)malloc(12);
+       if (!aaa) return -1;
+
+       if (username)
+               sprintf(aaa, "GREG %s", username);
+       else
+               sprintf(aaa, "GREG _SELF_");
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, rret, &bytes, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* VALI */
+int CtdlIPCValidateUser(CtdlIPC *ipc, const char *username, int axlevel, char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!username) return -2;
+       if (axlevel < AxDeleted || axlevel > AxAideU) return -2;
+
+       aaa = (char *)malloc(strlen(username) + 17);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "VALI %s|%d", username, axlevel);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* EINF */
+int CtdlIPCSetRoomInfo(CtdlIPC *ipc, int for_real, const char *info, char *cret)
+{
+       char aaa[64];
+
+       if (!cret) return -1;
+       if (!info) return -1;
+
+       sprintf(aaa, "EINF %d", for_real);
+       return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* LIST */
+int CtdlIPCUserListing(CtdlIPC *ipc, char *searchstring, char **listing, char *cret)
+{
+       size_t bytes;
+       char *cmd;
+       int ret;
+
+       if (!cret) return -1;
+       if (!listing) return -1;
+       if (*listing) return -1;
+       if (!searchstring) return -1;
+
+       cmd = malloc(strlen(searchstring) + 10);
+       sprintf(cmd, "LIST %s", searchstring);
+
+       ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, listing, &bytes, cret);
+       free(cmd);
+       return(ret);
+}
+
+
+/* REGI */
+int CtdlIPCSetRegistration(CtdlIPC *ipc, const char *info, char *cret)
+{
+       if (!cret) return -1;
+       if (!info) return -1;
+
+       return CtdlIPCGenericCommand(ipc, "REGI", info, strlen(info),
+                       NULL, NULL, cret);
+}
+
+
+/* CHEK */
+int CtdlIPCMiscCheck(CtdlIPC *ipc, struct ctdlipcmisc *chek, char *cret)
+{
+       int ret;
+
+       if (!cret) return -1;
+       if (!chek) return -1;
+
+       ret = CtdlIPCGenericCommand(ipc, "CHEK", NULL, 0, NULL, NULL, cret);
+       if (ret / 100 == 2) {
+               chek->newmail = extract_long(cret, 0);
+               chek->needregis = extract_int(cret, 1);
+               chek->needvalid = extract_int(cret, 2);
+       }
+       return ret;
+}
+
+
+/* DELF */
+int CtdlIPCDeleteFile(CtdlIPC *ipc, const char *filename, char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!filename) return -2;
+       
+       aaa = (char *)malloc(strlen(filename) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "DELF %s", filename);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* MOVF */
+int CtdlIPCMoveFile(CtdlIPC *ipc, const char *filename, const char *destroom, char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!filename) return -2;
+       if (!destroom) return -2;
+
+       aaa = (char *)malloc(strlen(filename) + strlen(destroom) + 7);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "MOVF %s|%s", filename, destroom);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* RWHO */
+int CtdlIPCOnlineUsers(CtdlIPC *ipc, char **listing, time_t *stamp, char *cret)
+{
+       int ret;
+       size_t bytes;
+
+       if (!cret) return -1;
+       if (!listing) return -1;
+       if (*listing) return -1;
+
+       *stamp = CtdlIPCServerTime(ipc, cret);
+       if (!*stamp)
+               *stamp = time(NULL);
+       ret = CtdlIPCGenericCommand(ipc, "RWHO", NULL, 0, listing, &bytes, cret);
+       return ret;
+}
+
+
+/* OPEN */
+int CtdlIPCFileDownload(CtdlIPC *ipc, const char *filename, void **buf,
+               size_t resume,
+               void (*progress_gauge_callback)
+                       (CtdlIPC*, unsigned long, unsigned long),
+               char *cret)
+{
+       int ret;
+       size_t bytes;
+       time_t last_mod;
+       char mimetype[SIZ];
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!filename) return -2;
+       if (!buf) return -2;
+       if (*buf) return -2;
+       if (ipc->downloading) return -2;
+
+       aaa = (char *)malloc(strlen(filename) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "OPEN %s", filename);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       if (ret / 100 == 2) {
+               ipc->downloading = 1;
+               bytes = extract_long(cret, 0);
+               last_mod = extract_int(cret, 1);
+               extract_token(mimetype, cret, 2, '|', sizeof mimetype);
+
+               ret = CtdlIPCReadDownload(ipc, buf, bytes, resume,
+                                       progress_gauge_callback, cret);
+               /*
+               ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, resume,
+                                       progress_gauge_callback, cret);
+               */
+
+               ret = CtdlIPCEndDownload(ipc, cret);
+               if (ret / 100 == 2)
+                       sprintf(cret, "%d|%ld|%s|%s", (int)bytes, last_mod,
+                                       filename, mimetype);
+       }
+       return ret;
+}
+
+
+/* OPNA */
+int CtdlIPCAttachmentDownload(CtdlIPC *ipc, long msgnum, const char *part,
+               void **buf,
+               void (*progress_gauge_callback)
+                       (CtdlIPC*, unsigned long, unsigned long),
+               char *cret)
+{
+       int ret;
+       size_t bytes;
+       time_t last_mod;
+       char filename[SIZ];
+       char mimetype[SIZ];
+       char aaa[SIZ];
+
+       if (!cret) return -2;
+       if (!buf) return -2;
+       if (*buf) return -2;
+       if (!part) return -2;
+       if (!msgnum) return -2;
+       if (ipc->downloading) return -2;
+
+       sprintf(aaa, "OPNA %ld|%s", msgnum, part);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       if (ret / 100 == 2) {
+               ipc->downloading = 1;
+               bytes = extract_long(cret, 0);
+               last_mod = extract_int(cret, 1);
+               extract_token(filename, cret, 2, '|', sizeof filename);
+               extract_token(mimetype, cret, 3, '|', sizeof mimetype);
+               /* ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
+               ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
+               ret = CtdlIPCEndDownload(ipc, cret);
+               if (ret / 100 == 2)
+                       sprintf(cret, "%d|%ld|%s|%s", (int)bytes, last_mod,
+                                       filename, mimetype);
+       }
+       return ret;
+}
+
+
+/* OIMG */
+int CtdlIPCImageDownload(CtdlIPC *ipc, const char *filename, void **buf,
+               void (*progress_gauge_callback)
+                       (CtdlIPC*, unsigned long, unsigned long),
+               char *cret)
+{
+       int ret;
+       size_t bytes;
+       time_t last_mod;
+       char mimetype[SIZ];
+       char *aaa;
+
+       if (!cret) return -1;
+       if (!buf) return -1;
+       if (*buf) return -1;
+       if (!filename) return -1;
+       if (ipc->downloading) return -1;
+
+       aaa = (char *)malloc(strlen(filename) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "OIMG %s", filename);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       if (ret / 100 == 2) {
+               ipc->downloading = 1;
+               bytes = extract_long(cret, 0);
+               last_mod = extract_int(cret, 1);
+               extract_token(mimetype, cret, 2, '|', sizeof mimetype);
+/*             ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
+               ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
+               ret = CtdlIPCEndDownload(ipc, cret);
+               if (ret / 100 == 2)
+                       sprintf(cret, "%d|%ld|%s|%s", (int)bytes, last_mod,
+                                       filename, mimetype);
+       }
+       return ret;
+}
+
+
+/* UOPN */
+int CtdlIPCFileUpload(CtdlIPC *ipc, const char *save_as, const char *comment, 
+               const char *path, 
+               void (*progress_gauge_callback)
+                       (CtdlIPC*, unsigned long, unsigned long),
+               char *cret)
+{
+       int ret;
+       char *aaa;
+       FILE *uploadFP;
+       char MimeTestBuf[64];
+       const char *MimeType;
+       long len;
+
+       if (!cret) return -1;
+       if (!save_as) return -1;
+       if (!comment) return -1;
+       if (!path) return -1;
+       if (!*path) return -1;
+       if (ipc->uploading) return -1;
+
+       uploadFP = fopen(path, "r");
+       if (!uploadFP) return -2;
+
+       len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
+       rewind (uploadFP);
+       if (len < 0) 
+               return -3;
+
+       MimeType = GuessMimeType(&MimeTestBuf[0], len);
+       aaa = (char *)malloc(strlen(save_as) + strlen(MimeType) + strlen(comment) + 7);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "UOPN %s|%s|%s", save_as, MimeType,  comment);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       if (ret / 100 == 2) {
+               ipc->uploading = 1;
+               ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
+               ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
+               ipc->uploading = 0;
+       }
+       return ret;
+}
+
+
+/* UIMG */
+int CtdlIPCImageUpload(CtdlIPC *ipc, int for_real, const char *path,
+               const char *save_as,
+               void (*progress_gauge_callback)
+                       (CtdlIPC*, unsigned long, unsigned long),
+               char *cret)
+{
+       int ret;
+       FILE *uploadFP;
+       char *aaa;
+       char MimeTestBuf[64];
+       const char *MimeType;
+       long len;
+
+       if (!cret) return -1;
+       if (!save_as) return -1;
+       if (!path && for_real) return -1;
+       if (!*path && for_real) return -1;
+       if (ipc->uploading) return -1;
+
+       aaa = (char *)malloc(strlen(save_as) + 17);
+       if (!aaa) return -1;
+
+       uploadFP = fopen(path, "r");
+       if (!uploadFP) return -2;
+
+       len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
+       rewind (uploadFP);
+       if (len < 0) 
+               return -3;
+       MimeType = GuessMimeType(&MimeTestBuf[0], 64);
+
+       sprintf(aaa, "UIMG %d|%s|%s", for_real, MimeType, save_as);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       if (ret / 100 == 2 && for_real) {
+               ipc->uploading = 1;
+               ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
+               ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
+               ipc->uploading = 0;
+       }
+       return ret;
+}
+
+
+/* QUSR */
+int CtdlIPCQueryUsername(CtdlIPC *ipc, const char *username, char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!username) return -2;
+
+       aaa = (char *)malloc(strlen(username) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "QUSR %s", username);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* LFLR */
+int CtdlIPCFloorListing(CtdlIPC *ipc, char **listing, char *cret)
+{
+       size_t bytes;
+
+       if (!cret) return -2;
+       if (!listing) return -2;
+       if (*listing) return -2;
+
+       return CtdlIPCGenericCommand(ipc, "LFLR", NULL, 0, listing, &bytes, cret);
+}
+
+
+/* CFLR */
+int CtdlIPCCreateFloor(CtdlIPC *ipc, int for_real, const char *name, char *cret)
+{
+       int ret;
+       char aaa[SIZ];
+
+       if (!cret) return -2;
+       if (!name) return -2;
+
+       sprintf(aaa, "CFLR %s|%d", name, for_real);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       return ret;
+}
+
+
+/* KFLR */
+int CtdlIPCDeleteFloor(CtdlIPC *ipc, int for_real, int floornum, char *cret)
+{
+       char aaa[SIZ];
+
+       if (!cret) return -1;
+       if (floornum < 0) return -1;
+
+       sprintf(aaa, "KFLR %d|%d", floornum, for_real);
+       return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* EFLR */
+int CtdlIPCEditFloor(CtdlIPC *ipc, int floornum, const char *floorname, char *cret)
+{
+       int ret;
+       char aaa[SIZ];
+
+       if (!cret) return -2;
+       if (!floorname) return -2;
+       if (floornum < 0) return -2;
+
+       sprintf(aaa, "EFLR %d|%s", floornum, floorname);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       return ret;
+}
+
+
+/*
+ * IDEN 
+ *
+ * You only need to fill out hostname, the defaults will be used if any of the
+ * other fields are not set properly.
+ */
+int CtdlIPCIdentifySoftware(CtdlIPC *ipc, int developerid, int clientid,
+               int revision, const char *software_name, const char *hostname,
+               char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (developerid < 0 || clientid < 0 || revision < 0 ||
+           !software_name) {
+               developerid = 8;
+               clientid = 0;
+               revision = CLIENT_VERSION - 600;
+               software_name = "Citadel (libcitadel)";
+       }
+       if (!hostname) return -2;
+
+       aaa = (char *)malloc(strlen(software_name) + strlen(hostname) + 29);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "IDEN %d|%d|%d|%s|%s", developerid, clientid,
+                       revision, software_name, hostname);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* SEXP */
+int CtdlIPCSendInstantMessage(CtdlIPC *ipc, const char *username, const char *text,
+               char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!username) return -2;
+
+       aaa = (char *)malloc(strlen(username) + 8);
+       if (!aaa) return -1;
+
+       if (text) {
+               sprintf(aaa, "SEXP %s|-", username);
+               ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text),
+                               NULL, NULL, cret);
+       } else {
+               sprintf(aaa, "SEXP %s||", username);
+               ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       }
+       free(aaa);
+       return ret;
+}
+
+
+/* GEXP */
+int CtdlIPCGetInstantMessage(CtdlIPC *ipc, char **listing, char *cret)
+{
+       size_t bytes;
+
+       if (!cret) return -2;
+       if (!listing) return -2;
+       if (*listing) return -2;
+
+       return CtdlIPCGenericCommand(ipc, "GEXP", NULL, 0, listing, &bytes, cret);
+}
+
+
+/* DEXP */
+/* mode is 0 = enable, 1 = disable, 2 = status */
+int CtdlIPCEnableInstantMessageReceipt(CtdlIPC *ipc, int mode, char *cret)
+{
+       char aaa[64];
+
+       if (!cret) return -2;
+
+       sprintf(aaa, "DEXP %d", mode);
+       return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* EBIO */
+int CtdlIPCSetBio(CtdlIPC *ipc, char *bio, char *cret)
+{
+       if (!cret) return -2;
+       if (!bio) return -2;
+
+       return CtdlIPCGenericCommand(ipc, "EBIO", bio, strlen(bio),
+                       NULL, NULL, cret);
+}
+
+
+/* RBIO */
+int CtdlIPCGetBio(CtdlIPC *ipc, const char *username, char **listing, char *cret)
+{
+       int ret;
+       size_t bytes;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!username) return -2;
+       if (!listing) return -2;
+       if (*listing) return -2;
+
+       aaa = (char *)malloc(strlen(username) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "RBIO %s", username);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* LBIO */
+int CtdlIPCListUsersWithBios(CtdlIPC *ipc, char **listing, char *cret)
+{
+       size_t bytes;
+
+       if (!cret) return -2;
+       if (!listing) return -2;
+       if (*listing) return -2;
+
+       return CtdlIPCGenericCommand(ipc, "LBIO", NULL, 0, listing, &bytes, cret);
+}
+
+
+/* STEL */
+int CtdlIPCStealthMode(CtdlIPC *ipc, int mode, char *cret)
+{
+       char aaa[64];
+
+       if (!cret) return -1;
+
+       sprintf(aaa, "STEL %d", mode ? 1 : 0);
+       return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* TERM */
+int CtdlIPCTerminateSession(CtdlIPC *ipc, int sid, char *cret)
+{
+       char aaa[64];
+
+       if (!cret) return -1;
+
+       sprintf(aaa, "TERM %d", sid);
+       return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* DOWN */
+int CtdlIPCTerminateServerNow(CtdlIPC *ipc, char *cret)
+{
+       if (!cret) return -1;
+
+       return CtdlIPCGenericCommand(ipc, "DOWN", NULL, 0, NULL, NULL, cret);
+}
+
+
+/* SCDN */
+int CtdlIPCTerminateServerScheduled(CtdlIPC *ipc, int mode, char *cret)
+{
+       char aaa[16];
+
+       if (!cret) return -1;
+
+       sprintf(aaa, "SCDN %d", mode ? 1 : 0);
+       return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* EMSG */
+int CtdlIPCEnterSystemMessage(CtdlIPC *ipc, const char *filename, const char *text,
+               char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!text) return -2;
+       if (!filename) return -2;
+
+       aaa = (char *)malloc(strlen(filename) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "EMSG %s", filename);
+       ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text), NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* HCHG */
+int CtdlIPCChangeHostname(CtdlIPC *ipc, const char *hostname, char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!hostname) return -2;
+
+       aaa = (char *)malloc(strlen(hostname) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "HCHG %s", hostname);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* RCHG */
+int CtdlIPCChangeRoomname(CtdlIPC *ipc, const char *roomname, char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!roomname) return -2;
+
+       aaa = (char *)malloc(strlen(roomname) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "RCHG %s", roomname);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* UCHG */
+int CtdlIPCChangeUsername(CtdlIPC *ipc, const char *username, char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!username) return -2;
+
+       aaa = (char *)malloc(strlen(username) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "UCHG %s", username);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* TIME */
+/* This function returns the actual server time reported, or 0 if error */
+time_t CtdlIPCServerTime(CtdlIPC *ipc, char *cret)
+{
+       time_t tret;
+       int ret;
+
+       ret = CtdlIPCGenericCommand(ipc, "TIME", NULL, 0, NULL, NULL, cret);
+       if (ret / 100 == 2) {
+               tret = extract_long(cret, 0);
+       } else {
+               tret = 0L;
+       }
+       return tret;
+}
+
+
+/* AGUP */
+int CtdlIPCAideGetUserParameters(CtdlIPC *ipc, const char *who, struct ctdluser **uret, char *cret)
+{
+       int ret;
+       char aaa[SIZ];
+
+       if (!cret) return -2;
+       if (!uret) return -2;
+       if (!*uret) *uret = (struct ctdluser *)calloc(1, sizeof(struct ctdluser));
+       if (!*uret) return -1;
+
+       sprintf(aaa, "AGUP %s", who);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+
+       if (ret / 100 == 2) {
+               extract_token(uret[0]->fullname, cret, 0, '|', sizeof uret[0]->fullname);
+               extract_token(uret[0]->password, cret, 1, '|', sizeof uret[0]->password);
+               uret[0]->flags = extract_int(cret, 2);
+               uret[0]->timescalled = extract_long(cret, 3);
+               uret[0]->posted = extract_long(cret, 4);
+               uret[0]->axlevel = extract_int(cret, 5);
+               uret[0]->usernum = extract_long(cret, 6);
+               uret[0]->lastcall = extract_long(cret, 7);
+               uret[0]->USuserpurge = extract_int(cret, 8);
+       }
+       return ret;
+}
+
+
+/* ASUP */
+int CtdlIPCAideSetUserParameters(CtdlIPC *ipc, const struct ctdluser *uret, char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!uret) return -2;
+
+       aaa = (char *)malloc(strlen(uret->fullname) + strlen(uret->password) + 84);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "ASUP %s|%s|%d|%ld|%ld|%d|%ld|%ld|%d",
+               uret->fullname, uret->password, uret->flags, uret->timescalled,
+               uret->posted, uret->axlevel, uret->usernum, uret->lastcall, uret->USuserpurge
+       );
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* AGEA */
+int CtdlIPCAideGetEmailAddresses(CtdlIPC *ipc, const char *who, char *target_buf, char *cret)
+{
+       int ret;
+       char aaa[SIZ];
+       char *emailaddrs = NULL;
+       size_t emailaddrs_len = 0;
+
+       sprintf(aaa, "AGEA %s", who);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &emailaddrs, &emailaddrs_len, cret);
+
+       if (ret / 100 == 1) {
+               strcpy(target_buf, emailaddrs);
+       }
+
+       if (emailaddrs != NULL) {
+               free(emailaddrs);
+       }
+
+       return ret;
+}
+
+
+/* ASEA */
+int CtdlIPCAideSetEmailAddresses(CtdlIPC *ipc, const char *who, char *emailaddrs, char *cret)
+{
+       char aaa[SIZ];
+       int ret;
+
+       if (!who) return -2;
+       if (!emailaddrs) return -2;
+       if (!cret) return -2;
+
+       sprintf(aaa, "ASEA %s", who);
+       ret = CtdlIPCGenericCommand(ipc, aaa, emailaddrs, 0, NULL, NULL, cret);
+       return ret;
+}
+
+
+/* GPEX */
+/* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
+/* caller must free the struct ExpirePolicy */
+int CtdlIPCGetMessageExpirationPolicy(CtdlIPC *ipc, GPEXWhichPolicy which,
+               struct ExpirePolicy **policy, char *cret)
+{
+       static char *proto[] = {
+               strof(roompolicy),
+               strof(floorpolicy),
+               strof(sitepolicy),
+               strof(mailboxespolicy)
+       };
+       char cmd[256];
+       int ret;
+
+       if (!cret) return -2;
+       if (!policy) return -2;
+       if (!*policy) *policy = (struct ExpirePolicy *)calloc(1, sizeof(struct ExpirePolicy));
+       if (!*policy) return -1;
+       if (which < 0 || which > 3) return -2;
+       
+       sprintf(cmd, "GPEX %s", proto[which]);
+       ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
+       if (ret / 100 == 2) {
+               policy[0]->expire_mode = extract_int(cret, 0);
+               policy[0]->expire_value = extract_int(cret, 1);
+       }
+       return ret;
+}
+
+
+/* SPEX */
+/* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
+/* policy is 0 = inherit, 1 = no purge, 2 = by count, 3 = by age (days) */
+int CtdlIPCSetMessageExpirationPolicy(CtdlIPC *ipc, int which,
+               struct ExpirePolicy *policy, char *cret)
+{
+       char aaa[38];
+       char *whichvals[] = { "room", "floor", "site", "mailboxes" };
+
+       if (!cret) return -2;
+       if (which < 0 || which > 3) return -2;
+       if (!policy) return -2;
+       if (policy->expire_mode < 0 || policy->expire_mode > 3) return -2;
+       if (policy->expire_mode >= 2 && policy->expire_value < 1) return -2;
+
+       sprintf(aaa, "SPEX %s|%d|%d", whichvals[which],
+                       policy->expire_mode, policy->expire_value);
+       return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* CONF GET */
+int CtdlIPCGetSystemConfig(CtdlIPC *ipc, char **listing, char *cret)
+{
+       size_t bytes;
+
+       if (!cret) return -2;
+       if (!listing) return -2;
+       if (*listing) return -2;
+
+       return CtdlIPCGenericCommand(ipc, "CONF GET", NULL, 0,
+                       listing, &bytes, cret);
+}
+
+
+/* CONF SET */
+int CtdlIPCSetSystemConfig(CtdlIPC *ipc, const char *listing, char *cret)
+{
+       if (!cret) return -2;
+       if (!listing) return -2;
+
+       return CtdlIPCGenericCommand(ipc, "CONF SET", listing, strlen(listing),
+                       NULL, NULL, cret);
+}
+
+
+/* CONF GETSYS */
+int CtdlIPCGetSystemConfigByType(CtdlIPC *ipc, const char *mimetype, char **listing, char *cret)
+{
+       int ret;
+       char *aaa;
+       size_t bytes;
+
+       if (!cret) return -2;
+       if (!mimetype) return -2;
+       if (!listing) return -2;
+       if (*listing) return -2;
+
+       aaa = malloc(strlen(mimetype) + 13);
+       if (!aaa) return -1;
+       sprintf(aaa, "CONF GETSYS|%s", mimetype);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* CONF PUTSYS */
+int CtdlIPCSetSystemConfigByType(CtdlIPC *ipc, const char *mimetype, const char *listing, char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!cret) return -2;
+       if (!mimetype) return -2;
+       if (!listing) return -2;
+
+       aaa = malloc(strlen(mimetype) + 13);
+       if (!aaa) return -1;
+       sprintf(aaa, "CONF PUTSYS|%s", mimetype);
+       ret = CtdlIPCGenericCommand(ipc, aaa, listing, strlen(listing), NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* GNET */
+int CtdlIPCGetRoomNetworkConfig(CtdlIPC *ipc, char **listing, char *cret)
+{
+       size_t bytes;
+
+       if (!cret) return -2;
+       if (!listing) return -2;
+       if (*listing) return -2;
+
+       return CtdlIPCGenericCommand(ipc, "GNET", NULL, 0, listing, &bytes, cret);
+}
+
+
+/* SNET */
+int CtdlIPCSetRoomNetworkConfig(CtdlIPC *ipc, const char *listing, char *cret)
+{
+       if (!cret) return -2;
+       if (!listing) return -2;
+
+       return CtdlIPCGenericCommand(ipc, "SNET", listing, strlen(listing), NULL, NULL, cret);
+}
+
+
+/* REQT */
+int CtdlIPCRequestClientLogout(CtdlIPC *ipc, int session, char *cret)
+{
+       char aaa[64];
+
+       if (!cret) return -2;
+       if (session < 0) return -2;
+
+       sprintf(aaa, "REQT %d", session);
+       return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* SEEN */
+int CtdlIPCSetMessageSeen(CtdlIPC *ipc, long msgnum, int seen, char *cret)
+{
+       char aaa[27];
+
+       if (!cret) return -2;
+       if (msgnum < 0) return -2;
+
+       sprintf(aaa, "SEEN %ld|%d", msgnum, seen ? 1 : 0);
+       return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+/* STLS */
+int CtdlIPCStartEncryption(CtdlIPC *ipc, char *cret)
+{
+       int a;
+       int r;
+       char buf[SIZ];
+
+#ifdef HAVE_OPENSSL
+       SSL *temp_ssl;
+
+       /* New SSL object */
+       temp_ssl = SSL_new(ssl_ctx);
+       if (!temp_ssl) {
+               error_printf("SSL_new failed: %s\n", ERR_reason_error_string(ERR_get_error()));
+               return -2;
+       }
+       /* Pointless flag waving */
+#if SSLEAY_VERSION_NUMBER >= 0x0922
+       SSL_set_session_id_context(temp_ssl, (const unsigned char*) "Citadel SID", 14);
+#endif
+
+       /* Associate network connection with SSL object */
+       if (SSL_set_fd(temp_ssl, ipc->sock) < 1) {
+               error_printf("SSL_set_fd failed: %s\n", ERR_reason_error_string(ERR_get_error()));
+               return -2;
+       }
+
+       if (status_hook != NULL) {
+               status_hook("Requesting encryption...\r");
+       }
+
+       /* Ready to start SSL/TLS */
+       r = CtdlIPCGenericCommand(ipc, "STLS", NULL, 0, NULL, NULL, cret);
+       if (r / 100 != 2) {
+               error_printf("Server can't start TLS: %s\n", buf);
+               endtls(temp_ssl);
+               return r;
+       }
+
+       /* Do SSL/TLS handshake */
+       if ((a = SSL_connect(temp_ssl)) < 1) {
+               error_printf("SSL_connect failed: %s\n", ERR_reason_error_string(ERR_get_error()));
+               endtls(temp_ssl);
+               return -2;
+       }
+       ipc->ssl = temp_ssl;
+
+       error_printf("Encrypting with %s cipher %s\n",
+               SSL_CIPHER_get_version(SSL_get_current_cipher(ipc->ssl)),
+               SSL_CIPHER_get_name(SSL_get_current_cipher(ipc->ssl))
+       );
+       return r;
+#else
+       return 0;
+#endif /* HAVE_OPENSSL */
+}
+
+
+#ifdef HAVE_OPENSSL
+static void endtls(SSL *ssl)
+{
+       if (ssl) {
+               SSL_shutdown(ssl);
+               SSL_free(ssl);
+       }
+}
+#endif
+
+
+/* QDIR */
+int CtdlIPCDirectoryLookup(CtdlIPC *ipc, const char *address, char *cret)
+{
+       int ret;
+       char *aaa;
+
+       if (!address) return -2;
+       if (!cret) return -2;
+
+       aaa = (char *)malloc(strlen(address) + 6);
+       if (!aaa) return -1;
+
+       sprintf(aaa, "QDIR %s", address);
+       ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+       free(aaa);
+       return ret;
+}
+
+
+/* IPGM */
+int CtdlIPCInternalProgram(CtdlIPC *ipc, int secret, char *cret)
+{
+       char aaa[30];
+
+       if (!cret) return -2;
+       sprintf(aaa, "IPGM %d", secret);
+       return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
+}
+
+
+
+
+/* ************************************************************************** */
+/*          Stuff below this line is not for public consumption            */
+/* ************************************************************************** */
+
+
+/* Read a listing from the server up to 000.  Append to dest if it exists */
+char *CtdlIPCReadListing(CtdlIPC *ipc, char *dest)
+{
+       size_t length = 0;
+       size_t linelength;
+       char *ret = NULL;
+       char aaa[SIZ];
+
+       ret = dest;
+       if (ret != NULL) {
+               length = strlen(ret);
+       } else {
+               length = 0;
+       }
+
+       while (CtdlIPC_getline(ipc, aaa), strcmp(aaa, "000")) {
+               linelength = strlen(aaa);
+               ret = (char *)realloc(ret, (size_t)(length + linelength + 2));
+               if (ret) {
+                       strcpy(&ret[length], aaa);
+                       length += linelength;
+                       strcpy(&ret[length++], "\n");
+               }
+       }
+
+       return(ret);
+}
+
+
+/* Send a listing to the server; generate the ending 000. */
+int CtdlIPCSendListing(CtdlIPC *ipc, const char *listing)
+{
+       char *text;
+
+       text = (char *)malloc(strlen(listing) + 6);
+       if (text) {
+               strcpy(text, listing);
+               while (text[strlen(text) - 1] == '\n')
+                       text[strlen(text) - 1] = '\0';
+               strcat(text, "\n000");
+               CtdlIPC_putline(ipc, text);
+               free(text);
+               text = NULL;
+       } else {
+               /* Malloc failed but we are committed to send */
+               /* This may result in extra blanks at the bottom */
+               CtdlIPC_putline(ipc, text);
+               CtdlIPC_putline(ipc, "000");
+       }
+       return 0;
+}
+
+
+/* Partial read of file from server */
+size_t CtdlIPCPartialRead(CtdlIPC *ipc, void **buf, size_t offset, size_t bytes, char *cret)
+{
+       size_t len = 0;
+       char aaa[SIZ];
+
+       if (!buf) return 0;
+       if (!cret) return 0;
+       if (bytes < 1) return 0;
+
+       CtdlIPC_lock(ipc);
+       sprintf(aaa, "READ %d|%d", (int)offset, (int)bytes);
+       CtdlIPC_putline(ipc, aaa);
+       CtdlIPC_getline(ipc, aaa);
+       if (aaa[0] != '6')
+               strcpy(cret, &aaa[4]);
+       else {
+               len = extract_long(&aaa[4], 0);
+               *buf = (void *)realloc(*buf, (size_t)(offset + len));
+               if (*buf) {
+                       /* I know what I'm doing */
+                       serv_read(ipc, ((char *)(*buf) + offset), len);
+               } else {
+                       /* We have to read regardless */
+                       serv_read(ipc, aaa, len);
+                       len = 0;
+               }
+       }
+       CtdlIPC_unlock(ipc);
+       return len;
+}
+
+
+/* CLOS */
+int CtdlIPCEndDownload(CtdlIPC *ipc, char *cret)
+{
+       int ret;
+
+       if (!cret) return -2;
+       if (!ipc->downloading) return -2;
+
+       ret = CtdlIPCGenericCommand(ipc, "CLOS", NULL, 0, NULL, NULL, cret);
+       if (ret / 100 == 2)
+               ipc->downloading = 0;
+       return ret;
+}
+
+
+/* MSGP */
+int CtdlIPCSpecifyPreferredFormats(CtdlIPC *ipc, char *cret, char *formats) {
+       int ret;
+       char cmd[SIZ];
+       
+       snprintf(cmd, sizeof cmd, "MSGP %s", formats);
+       ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
+       return ret;
+}
+
+
+
+/* READ */
+int CtdlIPCReadDownload(CtdlIPC *ipc, void **buf, size_t bytes, size_t resume,
+               void (*progress_gauge_callback)
+                       (CtdlIPC*, unsigned long, unsigned long),
+              char *cret)
+{
+       size_t len;
+
+       if (!cret) return -1;
+       if (!buf) return -1;
+       if (*buf) return -1;
+       if (!ipc->downloading) return -1;
+
+       len = resume;
+       if (progress_gauge_callback)
+               progress_gauge_callback(ipc, len, bytes);
+       while (len < bytes) {
+               size_t block;
+
+               block = CtdlIPCPartialRead(ipc, buf, len, 4096, cret);
+               if (block == 0) {
+                       free(*buf);
+                       return 0;
+               }
+               len += block;
+               if (progress_gauge_callback)
+                       progress_gauge_callback(ipc, len, bytes);
+       }
+       return len;
+}
+
+/* READ - pipelined */
+int CtdlIPCHighSpeedReadDownload(CtdlIPC *ipc, void **buf, size_t bytes,
+              size_t resume,
+               void (*progress_gauge_callback)
+                       (CtdlIPC*, unsigned long, unsigned long),
+              char *cret)
+{
+       size_t len;
+       int calls;      /* How many calls in the pipeline */
+       int i;          /* iterator */
+       char aaa[4096];
+
+       if (!cret) return -1;
+       if (!buf) return -1;
+       if (*buf) return -1;
+       if (!ipc->downloading) return -1;
+
+       *buf = (void *)realloc(*buf, bytes - resume);
+       if (!*buf) return -1;
+
+       len = 0;
+       CtdlIPC_lock(ipc);
+       if (progress_gauge_callback)
+               progress_gauge_callback(ipc, len, bytes);
+
+       /* How many calls will be in the pipeline? */
+       calls = (bytes - resume) / 4096;
+       if ((bytes - resume) % 4096) calls++;
+
+       /* Send all requests at once */
+       for (i = 0; i < calls; i++) {
+               sprintf(aaa, "READ %d|4096", (int)(i * 4096 + resume) );
+               CtdlIPC_putline(ipc, aaa);
+       }
+
+       /* Receive all responses at once */
+       for (i = 0; i < calls; i++) {
+               CtdlIPC_getline(ipc, aaa);
+               if (aaa[0] != '6')
+                       strcpy(cret, &aaa[4]);
+               else {
+                       len = extract_long(&aaa[4], 0);
+                       /* I know what I'm doing */
+                       serv_read(ipc, ((char *)(*buf) + (i * 4096)), len);
+               }
+               if (progress_gauge_callback)
+                       progress_gauge_callback(ipc, i * 4096 + len, bytes);
+       }
+       CtdlIPC_unlock(ipc);
+       return len;
+}
+
+
+/* UCLS */
+int CtdlIPCEndUpload(CtdlIPC *ipc, int discard, char *cret)
+{
+       int ret;
+       char cmd[8];
+
+       if (!cret) return -1;
+       if (!ipc->uploading) return -1;
+
+       sprintf(cmd, "UCLS %d", discard ? 0 : 1);
+       ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
+       ipc->uploading = 0;
+       return ret;
+}
+
+
+/* WRIT */
+int CtdlIPCWriteUpload(CtdlIPC *ipc, FILE *uploadFP,
+               void (*progress_gauge_callback)
+                       (CtdlIPC*, unsigned long, unsigned long),
+               char *cret)
+{
+       int ret = -1;
+       size_t offset = 0;
+       size_t bytes;
+       char aaa[SIZ];
+       char buf[4096];
+       FILE *fd = uploadFP;
+       int ferr;
+
+       if (!cret) return -1;
+
+       fseek(fd, 0L, SEEK_END);
+       bytes = ftell(fd);
+       rewind(fd);
+
+       if (progress_gauge_callback)
+               progress_gauge_callback(ipc, 0, bytes);
+
+       while (offset < bytes) {
+               size_t to_write;
+
+               /* Read some data in */
+               to_write = fread(buf, 1, 4096, fd);
+               if (!to_write) {
+                       if (feof(fd) || ferror(fd)) break;
+               }
+               sprintf(aaa, "WRIT %d", (int)to_write);
+               CtdlIPC_putline(ipc, aaa);
+               CtdlIPC_getline(ipc, aaa);
+               strcpy(cret, &aaa[4]);
+               ret = atoi(aaa);
+               if (aaa[0] == '7') {
+                       to_write = extract_long(&aaa[4], 0);
+                       
+                       serv_write(ipc, buf, to_write);
+                       offset += to_write;
+                       if (progress_gauge_callback)
+                               progress_gauge_callback(ipc, offset, bytes);
+                       /* Detect short reads and back up if needed */
+                       /* offset will never be negative anyway */
+                       fseek(fd, (signed)offset, SEEK_SET);
+               } else {
+                       break;
+               }
+       }
+       if (progress_gauge_callback)
+               progress_gauge_callback(ipc, 1, 1);
+       ferr = ferror(fd);
+       fclose(fd);
+       return (!ferr ? ret : -2);
+}
+
+
+/*
+ * Generic command method.  This method should handle any server command
+ * except for CHAT.  It takes the following arguments:
+ *
+ * ipc                 The server to speak with
+ * command             Preformatted command to send to server
+ * to_send             A text or binary file to send to server
+ *                     (only sent if server requests it)
+ * bytes_to_send       The number of bytes in to_send (required if
+ *                     sending binary, optional if sending listing)
+ * to_receive          Pointer to a NULL pointer, if the server
+ *                     sends text or binary we will allocate memory
+ *                     for the file and stuff it here
+ * bytes_to_receive    If a file is received, we will store its
+ *                     byte count here
+ * proto_response      The protocol response.  Caller must provide
+ *                     this buffer and ensure that it is at least
+ *                     128 bytes in length.
+ *
+ * This function returns a number equal to the protocol response number,
+ * -1 if an internal error occurred, -2 if caller provided bad values,
+ * or 0 - the protocol response number if bad values were found during
+ * the protocol exchange.
+ * It stores the protocol response string (minus the number) in 
+ * protocol_response as described above.  Some commands send additional
+ * data in this string.
+ */
+int CtdlIPCGenericCommand(CtdlIPC *ipc,
+               const char *command, const char *to_send,
+               size_t bytes_to_send, char **to_receive, 
+               size_t *bytes_to_receive, char *proto_response)
+{
+       char buf[SIZ];
+       int ret;
+
+       if (!command) return -2;
+       if (!proto_response) return -2;
+
+       CtdlIPC_lock(ipc);
+       CtdlIPC_putline(ipc, command);
+       while (1) {
+               CtdlIPC_getline(ipc, proto_response);
+               if (proto_response[3] == '*')
+                       instant_msgs = 1;
+               ret = atoi(proto_response);
+               strcpy(proto_response, &proto_response[4]);
+               switch (ret / 100) {
+               default:                        /* Unknown, punt */
+               case 2:                         /* OK */
+               case 3:                         /* MORE_DATA */
+               case 5:                         /* ERROR */
+                       /* Don't need to do anything */
+                       break;
+               case 1:                         /* LISTING_FOLLOWS */
+                       if (to_receive && !*to_receive && bytes_to_receive) {
+                               *to_receive = CtdlIPCReadListing(ipc, NULL);
+                       } else { /* Drain */
+                               while (CtdlIPC_getline(ipc, buf), strcmp(buf, "000")) ;
+                               ret = -ret;
+                       }
+                       break;
+               case 4:                         /* SEND_LISTING */
+                       if (to_send) {
+                               CtdlIPCSendListing(ipc, to_send);
+                       } else {
+                               /* No listing given, fake it */
+                               CtdlIPC_putline(ipc, "000");
+                               ret = -ret;
+                       }
+                       break;
+               case 6:                         /* BINARY_FOLLOWS */
+                       if (to_receive && !*to_receive && bytes_to_receive) {
+                               *bytes_to_receive =
+                                       extract_long(proto_response, 0);
+                               *to_receive = (char *)
+                                       malloc((size_t)*bytes_to_receive);
+                               if (!*to_receive) {
+                                       ret = -1;
+                               } else {
+                                       serv_read(ipc, *to_receive,
+                                                       *bytes_to_receive);
+                               }
+                       } else {
+                               /* Drain */
+                               size_t drain;
+
+                               drain = extract_long(proto_response, 0);
+                               while (drain > SIZ) {
+                                       serv_read(ipc, buf, SIZ);
+                                       drain -= SIZ;
+                               }
+                               serv_read(ipc, buf, drain);
+                               ret = -ret;
+                       }
+                       break;
+               case 7:                         /* SEND_BINARY */
+                       if (to_send && bytes_to_send) {
+                               serv_write(ipc, to_send, bytes_to_send);
+                       } else if (bytes_to_send) {
+                               /* Fake it, send nulls */
+                               size_t fake;
+
+                               fake = bytes_to_send;
+                               memset(buf, '\0', SIZ);
+                               while (fake > SIZ) {
+                                       serv_write(ipc, buf, SIZ);
+                                       fake -= SIZ;
+                               }
+                               serv_write(ipc, buf, fake);
+                               ret = -ret;
+                       } /* else who knows?  DANGER WILL ROBINSON */
+                       break;
+               case 8:                         /* START_CHAT_MODE */
+                       if (!strncasecmp(command, "CHAT", 4)) {
+                               /* Don't call chatmode with generic! */
+                               CtdlIPC_putline(ipc, "/quit");
+                               ret = -ret;
+                       } else {
+                               /* In this mode we send then receive listing */
+                               if (to_send) {
+                                       CtdlIPCSendListing(ipc, to_send);
+                               } else {
+                                       /* No listing given, fake it */
+                                       CtdlIPC_putline(ipc, "000");
+                                       ret = -ret;
+                               }
+                               if (to_receive && !*to_receive
+                                               && bytes_to_receive) {
+                                       *to_receive = CtdlIPCReadListing(ipc, NULL);
+                               } else { /* Drain */
+                                       while (CtdlIPC_getline(ipc, buf),
+                                                       strcmp(buf, "000")) ;
+                                       ret = -ret;
+                               }
+                       }
+                       break;
+               case 9:                         /* ASYNC_MSG */
+                       /* CtdlIPCDoAsync(ret, proto_response); */
+                       free(CtdlIPCReadListing(ipc, NULL));    /* STUB FIXME */
+                       break;
+               }
+               if (ret / 100 != 9)
+                       break;
+       }
+       CtdlIPC_unlock(ipc);
+       return ret;
+}
+
+
+/*
+ * Connect to a Citadel on a remote host using a TCP/IP socket
+ */
+static int tcp_connectsock(char *host, char *service)
+{
+       struct in6_addr serveraddr;
+       struct addrinfo hints;
+       struct addrinfo *res = NULL;
+       struct addrinfo *ai = NULL;
+       int rc = (-1);
+       int sock = (-1);
+
+       if ((host == NULL) || IsEmptyStr(host)) {
+               service = DEFAULT_HOST ;
+       }
+       if ((service == NULL) || IsEmptyStr(service)) {
+               service = DEFAULT_PORT ;
+       }
+
+       memset(&hints, 0x00, sizeof(hints));
+       hints.ai_flags = AI_NUMERICSERV;
+       hints.ai_family = AF_UNSPEC;
+       hints.ai_socktype = SOCK_STREAM;
+
+       /*
+        * Handle numeric IPv4 and IPv6 addresses
+        */
+       rc = inet_pton(AF_INET, host, &serveraddr);
+       if (rc == 1) {                                          /* dotted quad */
+               hints.ai_family = AF_INET;
+               hints.ai_flags |= AI_NUMERICHOST;
+       }
+       else {
+               rc = inet_pton(AF_INET6, host, &serveraddr);
+               if (rc == 1) {                                  /* IPv6 address */
+                       hints.ai_family = AF_INET6;
+                       hints.ai_flags |= AI_NUMERICHOST;
+               }
+       }
+
+       /* Begin the connection process */
+
+       rc = getaddrinfo(host, service, &hints, &res);
+       if (rc != 0) {
+               return(-1);
+       }
+
+       /*
+        * Try all available addresses until we connect to one or until we run out.
+        */
+       for (ai = res; ai != NULL; ai = ai->ai_next) {
+               sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+               if (sock < 0) return(-1);
+
+               rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
+               if (rc >= 0) {
+                       return(sock);           /* Connected! */
+               }
+               else {
+                       close(sock);            /* Failed.  Close the socket to avoid fd leak! */
+               }
+       }
+
+       return(-1);
+}
+
+
+
+
+
+/*
+ * Connect to a Citadel on the local host using a unix domain socket
+ */
+static int uds_connectsock(int *isLocal, char *sockpath)
+{
+       struct sockaddr_un addr;
+       int s;
+
+       memset(&addr, 0, sizeof(addr));
+       addr.sun_family = AF_UNIX;
+       safestrncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
+
+       s = socket(AF_UNIX, SOCK_STREAM, 0);
+       if (s < 0) {
+               return -1;
+       }
+
+       if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+               close(s);
+               return -1;
+       }
+
+       *isLocal = 1;
+       return s;
+}
+
+
+/*
+ * input binary data from socket
+ */
+static void serv_read(CtdlIPC *ipc, char *buf, unsigned int bytes)
+{
+       unsigned int len, rlen;
+
+#if defined(HAVE_OPENSSL)
+       if (ipc->ssl) {
+               serv_read_ssl(ipc, buf, bytes);
+               return;
+       }
+#endif
+       len = 0;
+       while (len < bytes) {
+               rlen = read(ipc->sock, &buf[len], bytes - len);
+               if (rlen < 1) {
+                       connection_died(ipc, 0);
+                       return;
+               }
+               len += rlen;
+       }
+}
+
+
+/*
+ * send binary to server
+ */
+void serv_write(CtdlIPC *ipc, const char *buf, unsigned int nbytes)
+{
+       unsigned int bytes_written = 0;
+       int retval;
+
+#if defined(HAVE_OPENSSL)
+       if (ipc->ssl) {
+               serv_write_ssl(ipc, buf, nbytes);
+               return;
+       }
+#endif
+       while (bytes_written < nbytes) {
+               retval = write(ipc->sock, &buf[bytes_written],
+                              nbytes - bytes_written);
+               if (retval < 1) {
+                       connection_died(ipc, 0);
+                       return;
+               }
+               bytes_written += retval;
+       }
+}
+
+
+#ifdef HAVE_OPENSSL
+/*
+ * input binary data from encrypted connection
+ */
+static void serv_read_ssl(CtdlIPC* ipc, char *buf, unsigned int bytes)
+{
+       int len, rlen;
+       char junk[1];
+
+       len = 0;
+       while (len < bytes) {
+               if (SSL_want_read(ipc->ssl)) {
+                       if ((SSL_write(ipc->ssl, junk, 0)) < 1) {
+                               error_printf("SSL_write in serv_read:\n");
+                               ERR_print_errors_fp(stderr);
+                       }
+               }
+               rlen = SSL_read(ipc->ssl, &buf[len], bytes - len);
+               if (rlen < 1) {
+                       long errval;
+
+                       errval = SSL_get_error(ipc->ssl, rlen);
+                       if (errval == SSL_ERROR_WANT_READ ||
+                                       errval == SSL_ERROR_WANT_WRITE) {
+                               sleep(1);
+                               continue;
+                       }
+/***
+ Not sure why we'd want to handle these error codes any differently,
+ but this definitely isn't the way to handle them.  Someone must have
+ naively assumed that we could fall back to unencrypted communications,
+ but all it does is just recursively blow the stack.
+                       if (errval == SSL_ERROR_ZERO_RETURN ||
+                                       errval == SSL_ERROR_SSL) {
+                               serv_read(ipc, &buf[len], bytes - len);
+                               return;
+                       }
+ ***/
+                       error_printf("SSL_read in serv_read: %s\n",
+                                       ERR_reason_error_string(ERR_peek_error()));
+                       connection_died(ipc, 1);
+                       return;
+               }
+               len += rlen;
+       }
+}
+
+
+/*
+ * send binary to server encrypted
+ */
+static void serv_write_ssl(CtdlIPC *ipc, const char *buf, unsigned int nbytes)
+{
+       unsigned int bytes_written = 0;
+       int retval;
+       char junk[1];
+
+       while (bytes_written < nbytes) {
+               if (SSL_want_write(ipc->ssl)) {
+                       if ((SSL_read(ipc->ssl, junk, 0)) < 1) {
+                               error_printf("SSL_read in serv_write:\n");
+                               ERR_print_errors_fp(stderr);
+                       }
+               }
+               retval = SSL_write(ipc->ssl, &buf[bytes_written],
+                               nbytes - bytes_written);
+               if (retval < 1) {
+                       long errval;
+
+                       errval = SSL_get_error(ipc->ssl, retval);
+                       if (errval == SSL_ERROR_WANT_READ ||
+                                       errval == SSL_ERROR_WANT_WRITE) {
+                               sleep(1);
+                               continue;
+                       }
+                       if (errval == SSL_ERROR_ZERO_RETURN ||
+                                       errval == SSL_ERROR_SSL) {
+                               serv_write(ipc, &buf[bytes_written],
+                                               nbytes - bytes_written);
+                               return;
+                       }
+                       error_printf("SSL_write in serv_write: %s\n",
+                                       ERR_reason_error_string(ERR_peek_error()));
+                       connection_died(ipc, 1);
+                       return;
+               }
+               bytes_written += retval;
+       }
+}
+
+
+
+
+static void CtdlIPC_init_OpenSSL(void)
+{
+       int a;
+       const SSL_METHOD *ssl_method;
+       DH *dh;
+       
+       /* already done init */
+       if (ssl_ctx) {
+               return;
+       }
+
+       /* Get started */
+       a = 0;
+       ssl_ctx = NULL;
+       dh = NULL;
+       SSL_load_error_strings();
+       SSLeay_add_ssl_algorithms();
+
+       /* Set up the SSL context in which we will oeprate */
+       ssl_method = SSLv23_client_method();
+       ssl_ctx = SSL_CTX_new(ssl_method);
+       if (!ssl_ctx) {
+               error_printf("SSL_CTX_new failed: %s\n", ERR_reason_error_string(ERR_get_error()));
+               return;
+       }
+       /* Any reasonable cipher we can get */
+       if (!(SSL_CTX_set_cipher_list(ssl_ctx, CIT_CIPHERS))) {
+               error_printf("No ciphers available for encryption\n");
+               return;
+       }
+       SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_BOTH);
+
+       /* Load DH parameters into the context */
+       dh = DH_new();
+       if (!dh) {
+               error_printf("Can't allocate a DH object: %s\n", ERR_reason_error_string(ERR_get_error()));
+               return;
+       }
+
+       if (!(DH_generate_parameters_ex(dh, 128, DH_GENERATOR_2, 0))) {
+               error_printf("Can't generate DH parameters: %s\n", ERR_reason_error_string(ERR_get_error()));
+               DH_free(dh);
+               return;
+       }
+
+       SSL_CTX_set_tmp_dh(ssl_ctx, dh);
+       DH_free(dh);
+}
+
+#endif /* HAVE_OPENSSL */
+
+
+int
+ReadNetworkChunk(CtdlIPC* ipc)
+{
+       fd_set read_fd;
+       int ret = 0;
+       int err = 0;
+       struct timeval tv;
+       size_t n;
+
+       tv.tv_sec = 1;
+       tv.tv_usec = 1000;
+       /*tries = 0; */
+       n = 0;
+       while (1)
+       {
+               errno=0;
+               FD_ZERO(&read_fd);
+               FD_SET(ipc->sock, &read_fd);
+               ret = select(ipc->sock+1, &read_fd, NULL, NULL,  &tv);
+               
+               if (ret > 0) {
+                       
+                       *(ipc->BufPtr) = '\0';
+                       n = recv(ipc->sock, ipc->BufPtr, ipc->BufSize  - (ipc->BufPtr - ipc->Buf) - 1, 0);
+                       if (n > 0) {
+                               ipc->BufPtr[n]='\0';
+                               ipc->BufUsed += n;
+                               return n;
+                       }
+                       else 
+                               return n;
+               }
+               else if (ret < 0) {
+                       if (!(errno == EINTR || errno == EAGAIN))
+                               error_printf( "\nselect failed: %d %s\n", err, strerror(err));
+                       return -1;
+               }/*
+               else {
+                       tries ++;
+                       if (tries >= 10)
+                       n = read(ipc->sock, ipc->BufPtr, ipc->BufSize  - (ipc->BufPtr - ipc->Buf) - 1);
+                       if (n > 0) {
+                               ipc->BufPtr[n]='\0';
+                               ipc->BufUsed += n;
+                               return n;
+                       }
+                       else {
+                               connection_died(ipc, 0);
+                               return -1;
+                       }
+                       }*/
+       }
+}
+
+/*
+ * input string from socket - implemented in terms of serv_read()
+ */
+#ifdef CHUNKED_READ
+
+static void CtdlIPC_getline(CtdlIPC* ipc, char *buf)
+{
+       int i, ntries;
+       char *aptr, *bptr, *aeptr, *beptr;
+
+//     error_printf("---\n");
+
+       beptr = buf + SIZ;
+#if defined(HAVE_OPENSSL)
+       if (ipc->ssl) {
+               
+               /* Read one character at a time. */
+               for (i = 0;; i++) {
+                       serv_read(ipc, &buf[i], 1);
+                       if (buf[i] == '\n' || i == (SIZ-1))
+                               break;
+               }
+               
+               /* If we got a long line, discard characters until the newline. */
+               if (i == (SIZ-1))
+                       while (buf[i] != '\n')
+                               serv_read(ipc, &buf[i], 1);
+               
+               /* Strip the trailing newline (and carriage return, if present) */
+               if (i>=0 && buf[i] == 10) buf[i--] = 0;
+               if (i>=0 && buf[i] == 13) buf[i--] = 0;
+       }
+       else
+#endif
+       {
+               if (ipc->Buf == NULL)
+               {
+                       ipc->BufSize = SIZ;
+                       ipc->Buf = (char*) malloc(ipc->BufSize + 10);
+                       *(ipc->Buf) = '\0';
+                       ipc->BufPtr = ipc->Buf;
+               }
+
+               ntries = 0;
+//             while ((ipc->BufUsed == 0)||(ntries++ > 10))
+               if (ipc->BufUsed == 0)
+                       ReadNetworkChunk(ipc);
+
+////           if (ipc->BufUsed != 0) while (1)
+               bptr = buf;
+
+               while (1)
+               {
+                       aptr = ipc->BufPtr;
+                       aeptr = ipc->Buf + ipc->BufSize;
+                       while ((aptr < aeptr) && 
+                              (bptr < beptr) &&
+                              (*aptr != '\0') && 
+                              (*aptr != '\n'))
+                               *(bptr++) = *(aptr++);
+                       if ((*aptr == '\n') && (aptr < aeptr))
+                       {
+                               /* Terminate it right, remove the line breaks */
+                               while ((aptr < aeptr) && ((*aptr == '\n') || (*aptr == '\r')))
+                                       aptr ++;
+                               while ((aptr < aeptr ) && (*(aptr + 1) == '\0') )
+                                       aptr ++;
+                               *(bptr++) = '\0';
+//                             fprintf(stderr, "parsing %d %d %d - %d %d %d %s\n", ipc->BufPtr - ipc->Buf, aptr - ipc->BufPtr, ipc->BufUsed , *aptr, *(aptr-1), *(aptr+1), buf);
+                               if ((bptr > buf + 1) && (*(bptr-1) == '\r'))
+                                       *(--bptr) = '\0';
+                               
+                               /* is there more in the buffer we need to read later? */
+                               if (ipc->Buf + ipc->BufUsed > aptr)
+                               {
+                                       ipc->BufPtr = aptr;
+                               }
+                               else
+                               {
+                                       ipc->BufUsed = 0;
+                                       ipc->BufPtr = ipc->Buf;
+                               }
+//                             error_printf("----bla6\n");
+                               return;
+                               
+                       }/* should we move our read stuf to the bufferstart so we have more space at the end? */
+                       else if ((ipc->BufPtr != ipc->Buf) && 
+                                (ipc->BufUsed > (ipc->BufSize  - (ipc->BufSize / 4))))
+                       {
+                               size_t NewBufSize = ipc->BufSize * 2;
+                               int delta = (ipc->BufPtr - ipc->Buf);
+                               char *NewBuf;
+
+                               /* if the line would end after our buffer, we should use a bigger buffer. */
+                               NewBuf = (char *)malloc (NewBufSize + 10);
+                               memcpy (NewBuf, ipc->BufPtr, ipc->BufUsed - delta);
+                               free(ipc->Buf);
+                               ipc->Buf = ipc->BufPtr = NewBuf;
+                               ipc->BufUsed -= delta;
+                               ipc->BufSize = NewBufSize;
+                       }
+                       if (ReadNetworkChunk(ipc) <0)
+                       {
+//                             error_printf("----bla\n");
+                               return;
+                       }
+               }
+///            error_printf("----bl45761%s\nipc->BufUsed");
+       }
+//     error_printf("----bla1\n");
+}
+
+#else  /* CHUNKED_READ */
+
+static void CtdlIPC_getline(CtdlIPC* ipc, char *buf)
+{
+       int i;
+
+       /* Read one character at a time. */
+       for (i = 0;; i++) {
+               serv_read(ipc, &buf[i], 1);
+               if (buf[i] == '\n' || i == (SIZ-1))
+                       break;
+       }
+
+       /* If we got a long line, discard characters until the newline. */
+       if (i == (SIZ-1))
+               while (buf[i] != '\n')
+                       serv_read(ipc, &buf[i], 1);
+
+       /* Strip the trailing newline (and carriage return, if present) */
+       if (i>=0 && buf[i] == 10) buf[i--] = 0;
+       if (i>=0 && buf[i] == 13) buf[i--] = 0;
+}
+
+
+#endif /* CHUNKED_READ */
+
+
+void CtdlIPC_chat_recv(CtdlIPC* ipc, char* buf)
+{
+       CtdlIPC_getline(ipc, buf);
+}
+
+/*
+ * send line to server - implemented in terms of serv_write()
+ */
+static void CtdlIPC_putline(CtdlIPC *ipc, const char *buf)
+{
+       char *cmd = NULL;
+       int len;
+
+       len = strlen(buf);
+       cmd = malloc(len + 2);
+       if (!cmd) {
+               /* This requires no extra memory */
+               serv_write(ipc, buf, len);
+               serv_write(ipc, "\n", 1);
+       } else {
+               /* This is network-optimized */
+               strncpy(cmd, buf, len);
+               strcpy(cmd + len, "\n");
+               serv_write(ipc, cmd, len + 1);
+               free(cmd);
+       }
+
+       ipc->last_command_sent = time(NULL);
+}
+
+void CtdlIPC_chat_send(CtdlIPC* ipc, const char* buf)
+{
+       CtdlIPC_putline(ipc, buf);
+}
+
+
+/*
+ * attach to server
+ */
+CtdlIPC* CtdlIPC_new(int argc, char **argv, char *hostbuf, char *portbuf)
+{
+       int a;
+       char cithost[SIZ];
+       char citport[SIZ];
+       char sockpath[SIZ];
+       CtdlIPC* ipc;
+
+       ipc = malloc(sizeof(struct _CtdlIPC));
+       if (!ipc) {
+               return 0;
+       }
+#if defined(HAVE_OPENSSL)
+       ipc->ssl = NULL;
+       CtdlIPC_init_OpenSSL();
+#endif
+       ipc->sock = -1;                 /* Not connected */
+       ipc->isLocal = 0;               /* Not local, of course! */
+       ipc->downloading = 0;
+       ipc->uploading = 0;
+       ipc->last_command_sent = 0L;
+       ipc->network_status_cb = NULL;
+       ipc->Buf = NULL;
+       ipc->BufUsed = 0;
+       ipc->BufPtr = NULL;
+
+       strcpy(cithost, DEFAULT_HOST);  /* default host */
+       strcpy(citport, DEFAULT_PORT);  /* default port */
+
+       /* Allow caller to supply our values */
+       if (hostbuf && strlen(hostbuf) > 0) {
+               strcpy(cithost, hostbuf);
+       }
+       if (portbuf && strlen(portbuf) > 0) {
+               strcpy(citport, portbuf);
+       }
+
+       /* Read host/port from command line if present */
+       for (a = 0; a < argc; ++a) {
+               if (a == 0) {
+                       /* do nothing */
+               } else if (a == 1) {
+                       strcpy(cithost, argv[a]);
+               } else if (a == 2) {
+                       strcpy(citport, argv[a]);
+               } else {
+                       error_printf("%s: usage: ",argv[0]);
+                       error_printf("%s [host] [port] ",argv[0]);
+                       free(ipc);
+                       errno = EINVAL;
+                       return 0;
+               }
+       }
+
+       if ((!strcmp(cithost, "localhost")) || (!strcmp(cithost, "127.0.0.1"))) {
+               ipc->isLocal = 1;
+       }
+
+       /* If we're using a unix domain socket we can do a bunch of stuff */
+       if (!strcmp(cithost, UDS)) {
+               if (!strcasecmp(citport, DEFAULT_PORT)) {
+                       snprintf(sockpath, sizeof sockpath, "%s", file_citadel_socket);
+               }
+               else {
+                       snprintf(sockpath, sizeof sockpath, "%s/%s", citport, "citadel.socket");
+               }
+               printf("[%s]\n", sockpath);
+               ipc->sock = uds_connectsock(&(ipc->isLocal), sockpath);
+               if (ipc->sock == -1) {
+                       free(ipc);
+                       return 0;
+               }
+               if (hostbuf != NULL) strcpy(hostbuf, cithost);
+               if (portbuf != NULL) strcpy(portbuf, sockpath);
+               strcpy(ipc->ip_hostname, "");
+               strcpy(ipc->ip_address, "");
+               return ipc;
+       }
+
+       printf("[%s:%s]\n", cithost, citport);
+       ipc->sock = tcp_connectsock(cithost, citport);
+       if (ipc->sock == -1) {
+               free(ipc);
+               return 0;
+       }
+
+
+       /* Learn the actual network identity of the host to which we are connected */
+
+       struct sockaddr_in6 clientaddr;
+       unsigned int addrlen = sizeof(clientaddr);
+
+       ipc->ip_hostname[0] = 0;
+       ipc->ip_address[0] = 0;
+
+       getpeername(ipc->sock, (struct sockaddr *)&clientaddr, &addrlen);
+       getnameinfo((struct sockaddr *)&clientaddr, addrlen,
+               ipc->ip_hostname, sizeof ipc->ip_hostname, NULL, 0, 0
+       );
+       getnameinfo((struct sockaddr *)&clientaddr, addrlen,
+               ipc->ip_address, sizeof ipc->ip_address, NULL, 0, NI_NUMERICHOST
+       );
+
+       /* stuff other things elsewhere */
+
+       if (hostbuf != NULL) strcpy(hostbuf, cithost);
+       if (portbuf != NULL) strcpy(portbuf, citport);
+       return ipc;
+}
+
+
+/*
+ * Disconnect and delete the IPC class (destructor)
+ */
+void CtdlIPC_delete(CtdlIPC* ipc)
+{
+#ifdef HAVE_OPENSSL
+       if (ipc->ssl) {
+               SSL_shutdown(ipc->ssl);
+               SSL_free(ipc->ssl);
+               ipc->ssl = NULL;
+       }
+#endif
+       if (ipc->sock > -1) {
+               shutdown(ipc->sock, 2); /* Close it up */
+               ipc->sock = -1;
+       }
+       if (ipc->Buf != NULL)
+               free (ipc->Buf);
+       ipc->Buf = NULL;
+       ipc->BufPtr = NULL;
+       free(ipc);
+}
+
+
+/*
+ * Disconnect and delete the IPC class (destructor)
+ * Also NULLs out the pointer
+ */
+void CtdlIPC_delete_ptr(CtdlIPC** pipc)
+{
+       CtdlIPC_delete(*pipc);
+       *pipc = NULL;
+}
+
+
+/*
+ * return the file descriptor of the server socket so we can select() on it.
+ *
+ * FIXME: This is only used in chat mode; eliminate it when chat mode gets
+ * rewritten...
+ */
+int CtdlIPC_getsockfd(CtdlIPC* ipc)
+{
+       return ipc->sock;
+}
+
+
+/*
+ * return one character
+ *
+ * FIXME: This is only used in chat mode; eliminate it when chat mode gets
+ * rewritten...
+ */
+char CtdlIPC_get(CtdlIPC* ipc)
+{
+       char buf[2];
+       char ch;
+
+       serv_read(ipc, buf, 1);
+       ch = (int) buf[0];
+
+       return (ch);
+}
diff --git a/textclient/client_chat.c b/textclient/client_chat.c
new file mode 100644 (file)
index 0000000..e725f02
--- /dev/null
@@ -0,0 +1,270 @@
+/*
+ * front end for multiuser chat
+ *
+ * Copyright (c) 1987-2016 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "textclient.h"
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+extern char temp[];
+char last_paged[SIZ] = "";
+
+void chatmode(CtdlIPC *ipc)
+{
+       char wbuf[SIZ];
+       char buf[SIZ];
+       char response[SIZ];
+       char c_user[SIZ];
+       char c_text[SIZ];
+       char last_user[SIZ];
+       int send_complete_line;
+       char ch;
+       int a, pos;
+       int seq = 0;
+
+       fd_set rfds;
+       struct timeval tv;
+       int retval;
+
+       CtdlIPC_chat_send(ipc, "RCHT enter");
+       CtdlIPC_chat_recv(ipc, buf);
+       if (buf[0] != '2') {
+               scr_printf("%s\n", &buf[4]);
+               return;
+       }
+       scr_printf("Entering chat mode (type /quit to exit)\n");
+
+       strcpy(buf, "");
+       strcpy(wbuf, "");
+       strcpy(last_user, ""); 
+       color(BRIGHT_YELLOW);
+       scr_printf("\n");
+       scr_printf("> ");
+       send_complete_line = 0;
+
+       while (1) {
+               scr_flush();
+               FD_ZERO(&rfds);
+               FD_SET(0, &rfds);
+               tv.tv_sec = 1;
+               tv.tv_usec = 0;
+               retval = select(1, &rfds, NULL, NULL, &tv);
+
+               if (retval < 0) {
+                       color(BRIGHT_WHITE);
+                       scr_printf("Server gone Exiting chat mode\n");
+                       scr_flush();
+                       return;
+               }
+
+               /* If there's data from the keyboard... */
+               if (FD_ISSET(0, &rfds)) {
+                       ch = scr_getc(SCR_BLOCK);
+                       if ((ch == 10) || (ch == 13)) {
+                               send_complete_line = 1;
+                       } else if ((ch == 8) || (ch == 127)) {
+                               if (!IsEmptyStr(wbuf)) {
+                                       wbuf[strlen(wbuf) - 1] = 0;
+                                       scr_printf("%c %c", 8, 8);
+                               }
+                       } else {
+                               scr_putc(ch);
+                               wbuf[strlen(wbuf) + 1] = 0;
+                               wbuf[strlen(wbuf)] = ch;
+                       }
+               }
+
+               /* if the user hit return, send the line */
+               if (send_complete_line) {
+
+                       if (!strcasecmp(wbuf, "/quit")) {
+                               CtdlIPC_chat_send(ipc, "RCHT exit");
+                               CtdlIPC_chat_recv(ipc, response);       /* don't care about the result */
+                               color(BRIGHT_WHITE);
+                               scr_printf("\rExiting chat mode\n");
+                               scr_flush();
+                               return;
+                       }
+
+                       CtdlIPC_chat_send(ipc, "RCHT send");
+                       CtdlIPC_chat_recv(ipc, response);
+                       if (response[0] == '4') {
+                               CtdlIPC_chat_send(ipc, wbuf);
+                               CtdlIPC_chat_send(ipc, "000");
+                       }
+                       strcpy(wbuf, "");
+                       send_complete_line = 0;
+               }
+
+               /* if it's time to word wrap, send a partial line */
+               if (strlen(wbuf) >= (77 - strlen(fullname))) {
+                       pos = 0;
+                       for (a = 0; !IsEmptyStr(&wbuf[a]); ++a) {
+                               if (wbuf[a] == 32)
+                                       pos = a;
+                       }
+                       if (pos == 0) {
+                               CtdlIPC_chat_send(ipc, "RCHT send");
+                               CtdlIPC_chat_recv(ipc, response);
+                               if (response[0] == '4') {
+                                       CtdlIPC_chat_send(ipc, wbuf);
+                                       CtdlIPC_chat_send(ipc, "000");
+                               }
+                               strcpy(wbuf, "");
+                               send_complete_line = 0;
+                       } else {
+                               wbuf[pos] = 0;
+                               CtdlIPC_chat_send(ipc, "RCHT send");
+                               CtdlIPC_chat_recv(ipc, response);
+                               if (response[0] == '4') {
+                                       CtdlIPC_chat_send(ipc, wbuf);
+                                       CtdlIPC_chat_send(ipc, "000");
+                               }
+                               strcpy(wbuf, &wbuf[pos + 1]);
+                       }
+               }
+
+               /* poll for incoming chat messages */
+               snprintf(buf, sizeof buf, "RCHT poll|%d", seq);
+               CtdlIPC_chat_send(ipc, buf);
+               CtdlIPC_chat_recv(ipc, response);
+       
+               if (response[0] == '1') {
+                       seq = extract_int(&response[4], 0);
+                       extract_token(c_user, &response[4], 2, '|', sizeof c_user);
+                       while (CtdlIPC_chat_recv(ipc, c_text), strcmp(c_text, "000")) {
+                               scr_printf("\r%79s\r", "");
+                               if (!strcmp(c_user, fullname)) {
+                                       color(BRIGHT_YELLOW);
+                               } else if (!strcmp(c_user, ":")) {
+                                       color(BRIGHT_RED);
+                               } else {
+                                       color(BRIGHT_GREEN);
+                               }
+                               if (strcmp(c_user, last_user)) {
+                                       snprintf(buf, sizeof buf, "%s: %s", c_user, c_text);
+                               } else {
+                                       size_t i = MIN(sizeof buf - 1, strlen(c_user) + 2);
+                                       memset(buf, ' ', i);
+                                       safestrncpy(&buf[i], c_text, sizeof buf - i);
+                               }
+                               while (strlen(buf) < 79) {
+                                       strcat(buf, " ");
+                               }
+                               if (strcmp(c_user, last_user)) {
+                                       scr_printf("\r%79s\n", "");
+                                       strcpy(last_user, c_user);
+                               }
+                               scr_printf("\r%s\n", buf);
+                               scr_flush();
+                       }
+               }
+               color(BRIGHT_YELLOW);
+               scr_printf("\r> %s", wbuf);
+               scr_flush();
+               strcpy(buf, "");
+       }
+}
+
+
+/*
+ * send an instant message
+ */
+void page_user(CtdlIPC *ipc)
+{
+       char buf[SIZ], touser[SIZ], msg[SIZ];
+       FILE *pagefp;
+
+       strcpy(touser, last_paged);
+       strprompt("Page who", touser, 30);
+
+       /* old server -- use inline paging */
+       if (ipc->ServInfo.paging_level == 0) {
+               newprompt("Message: ", msg, 69);
+               snprintf(buf, sizeof buf, "SEXP %s|%s", touser, msg);
+               CtdlIPC_chat_send(ipc, buf);
+               CtdlIPC_chat_recv(ipc, buf);
+               if (!strncmp(buf, "200", 3)) {
+                       strcpy(last_paged, touser);
+               }
+               scr_printf("%s\n", &buf[4]);
+               return;
+       }
+       /* new server -- use extended paging */
+       else if (ipc->ServInfo.paging_level >= 1) {
+               snprintf(buf, sizeof buf, "SEXP %s||", touser);
+               CtdlIPC_chat_send(ipc, buf);
+               CtdlIPC_chat_recv(ipc, buf);
+               if (buf[0] != '2') {
+                       scr_printf("%s\n", &buf[4]);
+                       return;
+               }
+               if (client_make_message(ipc, temp, touser, 0, 0, 0, NULL, 0) != 0) {
+                       scr_printf("No message sent.\n");
+                       return;
+               }
+               pagefp = fopen(temp, "r");
+               unlink(temp);
+               snprintf(buf, sizeof buf, "SEXP %s|-", touser);
+               CtdlIPC_chat_send(ipc, buf);
+               CtdlIPC_chat_recv(ipc, buf);
+               if (buf[0] == '4') {
+                       strcpy(last_paged, touser);
+                       while (fgets(buf, sizeof buf, pagefp) != NULL) {
+                               buf[strlen(buf) - 1] = 0;
+                               CtdlIPC_chat_send(ipc, buf);
+                       }
+                       fclose(pagefp);
+                       CtdlIPC_chat_send(ipc, "000");
+                       scr_printf("Message sent.\n");
+               } else {
+                       scr_printf("%s\n", &buf[4]);
+               }
+       }
+}
+
+
+void quiet_mode(CtdlIPC *ipc)
+{
+       static int quiet = 0;
+       char cret[SIZ];
+       int r;
+
+       r = CtdlIPCEnableInstantMessageReceipt(ipc, !quiet, cret);
+       if (r / 100 == 2) {
+               quiet = !quiet;
+               scr_printf("Quiet mode %sabled (%sother users may page you)\n",
+                               (quiet) ? "en" : "dis",
+                               (quiet) ? "no " : "");
+       } else {
+               scr_printf("Unable to change quiet mode: %s\n", cret);
+       }
+}
+
+
+void stealth_mode(CtdlIPC *ipc)
+{
+       static int stealth = 0;
+       char cret[SIZ];
+       int r;
+
+       r = CtdlIPCStealthMode(ipc, !stealth, cret);
+       if (r / 100 == 2) {
+               stealth = !stealth;
+               scr_printf("Stealth mode %sabled (you are %s)\n",
+                               (stealth) ? "en" : "dis",
+                               (stealth) ? "invisible" : "listed as online");
+       } else {
+               scr_printf("Unable to change stealth mode: %s\n", cret);
+       }
+}
diff --git a/textclient/client_passwords.c b/textclient/client_passwords.c
new file mode 100644 (file)
index 0000000..2337736
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * Functions which allow the client to remember usernames and passwords for
+ * various sites.
+ *
+ * Copyright (c) 1987-2016 by the citadel.org team
+ *
+ *  This program is open source software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 3.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#include "textclient.h"
+
+#define PWFILENAME "%s/.citadel.passwords"
+
+void determine_pwfilename(char *pwfile, size_t n) {
+       struct passwd *p;
+
+       p = getpwuid(getuid());
+       if (p == NULL) strcpy(pwfile, "");
+       snprintf(pwfile, n, PWFILENAME, p->pw_dir);
+}
+
+
+/*
+ * Check the password file for a host/port match; if found, stuff the user
+ * name and password into the user/pass buffers
+ */
+void get_stored_password(
+               char *host,
+               char *port,
+               char *username,
+               char *password) {
+
+       char pwfile[PATH_MAX];
+       FILE *fp;
+       char buf[SIZ];
+       char buf64[SIZ];
+       char hostbuf[256], portbuf[256], ubuf[256], pbuf[256];
+
+       strcpy(username, "");
+       strcpy(password, "");
+
+       determine_pwfilename(pwfile, sizeof pwfile);
+       if (IsEmptyStr(pwfile)) return;
+
+       fp = fopen(pwfile, "r");
+       if (fp == NULL) return;
+       while (fgets(buf64, sizeof buf64, fp) != NULL) {
+               CtdlDecodeBase64(buf, buf64, sizeof(buf64));
+               extract_token(hostbuf, buf, 0, '|', sizeof hostbuf);
+               extract_token(portbuf, buf, 1, '|', sizeof portbuf);
+               extract_token(ubuf, buf, 2, '|', sizeof ubuf);
+               extract_token(pbuf, buf, 3, '|', sizeof pbuf);
+
+               if (!strcasecmp(hostbuf, host)) {
+                       if (!strcasecmp(portbuf, port)) {
+                               strcpy(username, ubuf);
+                               strcpy(password, pbuf);
+                       }
+               }
+       }
+       fclose(fp);
+}
+
+
+/*
+ * Set (or clear) stored passwords.
+ */
+void set_stored_password(
+               char *host,
+               char *port,
+               char *username,
+               char *password) {
+
+       char pwfile[PATH_MAX];
+       FILE *fp, *oldfp;
+       char buf[SIZ];
+       char buf64[SIZ];
+       char hostbuf[256], portbuf[256], ubuf[256], pbuf[256];
+
+       determine_pwfilename(pwfile, sizeof pwfile);
+       if (IsEmptyStr(pwfile)) return;
+
+       oldfp = fopen(pwfile, "r");
+       if (oldfp == NULL) oldfp = fopen("/dev/null", "r");
+       unlink(pwfile);
+       fp = fopen(pwfile, "w");
+       if (fp == NULL) fp = fopen("/dev/null", "w");
+       while (fgets(buf64, sizeof buf64, oldfp) != NULL) {
+               CtdlDecodeBase64(buf, buf64, sizeof(buf64));
+               extract_token(hostbuf, buf, 0, '|', sizeof hostbuf);
+               extract_token(portbuf, buf, 1, '|', sizeof portbuf);
+               extract_token(ubuf, buf, 2, '|', sizeof ubuf);
+               extract_token(pbuf, buf, 3, '|', sizeof pbuf);
+
+               if ( (strcasecmp(hostbuf, host)) 
+                  || (strcasecmp(portbuf, port)) ) {
+                       snprintf(buf, sizeof buf, "%s|%s|%s|%s|",
+                               hostbuf, portbuf, ubuf, pbuf);
+                       CtdlEncodeBase64(buf64, buf, strlen(buf), 0);
+                       fprintf(fp, "%s\n", buf64);
+               }
+       }
+       if (!IsEmptyStr(username)) {
+               snprintf(buf, sizeof buf, "%s|%s|%s|%s|",
+                       host, port, username, password);
+               CtdlEncodeBase64(buf64, buf, strlen(buf), 0);
+               fprintf(fp, "%s\n", buf64);
+       }
+       fclose(oldfp);
+       fclose(fp);
+       chmod(pwfile, 0600);
+}
+
+
+/*
+ * Set the password if the user wants to, clear it otherwise 
+ */
+void offer_to_remember_password(CtdlIPC *ipc,
+               char *host,
+               char *port,
+               char *username,
+               char *password) {
+
+       if (rc_remember_passwords) {
+               if (boolprompt("Remember username/password for this site", 0)) {
+                       set_stored_password(host, port, username, password);
+               }
+               else {
+                       set_stored_password(host, port, "", "");
+               }
+       }
+}
diff --git a/textclient/commands.c b/textclient/commands.c
new file mode 100644 (file)
index 0000000..9a68488
--- /dev/null
@@ -0,0 +1,1864 @@
+/*
+ * This file contains functions which implement parts of the
+ * text-mode user interface.
+ *
+ * Copyright (c) 1987-2018 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "textclient.h"
+
+char *helpnames[] = {
+       "help",
+       "admin",
+       "floors",
+       "intro",
+       "mail",
+       "network",
+       "software"
+};
+
+char *helptexts[] = {
+
+"                          Citadel Help Menu\n"
+"  \n"
+" ?         Help. (Typing a '?' will give you a menu almost anywhere)\n"
+" A         Abandon this room where you stopped reading, goto next room.\n"
+" C         Chat (multiuser chat, where available)\n"
+" D         Prints directory, if there is one in the current room.\n"
+" E         Enter a message.\n"
+" F         Read all messages in the room, forward.\n"
+" G         Goto next room which has UNREAD messages.\n"
+" H         Help. Same as '?'\n"
+" I         Reads the Information file for this room.\n"
+" K         List of Known rooms.\n"
+" L         Reads the last five messages in the room.\n"
+" N         Reads all new messages in the room.\n"
+" O         Reads all old messages, backwards.\n"
+" P         Page another user (send an instant message)\n"
+" R         Reads all messages in the room, in reverse order.\n"
+" S         Skips current room without making its messages old.\n"
+" T         Terminate (logout)\n"
+" U         Ungoto (returns to the last room you were in)\n"
+" W         Displays who is currently logged in.\n"
+" X         Toggle eXpert mode (menus and help blurbs on/off)\n"
+" Z         Zap (forget) room. (Removes the room from your list)\n"
+" + -       Goto next, previous room on current floor.\n"
+" > <       Goto next, previous floor.\n"
+" *         Enter any locally installed 'doors'.\n"
+"   \n"
+" In addition, there are dot commands. You hit the . (dot), then press the\n"
+"first letter of each word of the command. As you hit the letters, the words\n"
+"pop onto your screen. Exceptions: after you hit .Help or .Goto, the remainder\n"
+"of the command is a help file name or room name.\n"
+"    \n"
+"      *** USE  .<H>elp ?    for additional help *** \n"
+
+,
+
+"The following commands are available only to Admins.  A subset of these\n"
+"commands are available to room aides when they are currently in the room\n"
+"they are room aide for.\n"
+"\n"
+" <.> <A>dmin <K>ill this room   (Delete the current room)\n"
+" <.> <A>dmin <E>dit this room   (Edit the current room's parameters)\n"
+" <.> <A>dmin <W>ho knows room   (List users with access to this room)\n"
+" <.> <A>dmin edit <U>ser        (Change user's access level, password, etc.)\n"
+" <.> <A>dmin <V>alidate new users   (Process new user registrations)\n"
+" <.> <A>dmin enter <I>nfo file      (Create/change this room's banner)\n"
+" <.> <A>dmin <R>oom <I>nvite user   (Grant access to an invitation-only room)\n"
+" <.> <A>dmin <R>oom <K>ick out user (Revoke access to an invitation-only room)\n"
+" <.> <A>dmin <F>ile <D>elete        (Delete a file from the room's directory)\n"
+" <.> <A>dmin <F>ile <S>end over net (Transmit a file to another node)\n"
+" <.> <A>dmin <F>ile <M>ove          (Move a file to another room's directory)\n"
+" <.> <A>dmin <M>essage edit:        (Edit system banners)\n"
+" <.> <A>dmin <P>ost                 (Post a message on behalf of another user)\n"
+" <.> <A>dmin <S>ystem configuration <G>eneral   (Edit global site config)\n"
+" <.> <A>dmin <S>ystem configuration <I>nternet  (Edit Internet domains)\n"
+" <.> <A>dmin <S>ystem configuration check <M>essage base   (Internal checks)\n"
+" <.> <A>dmin <S>ystem configuration <N>etwork   (Netting with other Citadels)\n"
+" <.> <A>dmin <S>ystem configuration network <F>ilter list\n"
+" <.> <A>dmin <T>erminate server <N>ow          (Shut down Citadel server now)\n"
+" <.> <A>dmin <T>erminate server <S>cheduled    (Shut down Citadel server later)\n"
+" <.> <A>dmin mailing <L>ist recipients         (For mailing list rooms)\n"
+" <.> <A>dmin mailing list <D>igest recipients  (For mailing list rooms)\n"
+" <.> <A>dmin <N>etwork room sharing     (Replication with other Citadels)\n"
+" \n"
+" In addition, the <M>ove and <D>elete commands are available at the\n"
+"message prompt.\n"
+
+,
+
+" Floors\n"
+" ------\n"
+"   Floors in Citadel are used to group rooms into related subject areas,\n"
+"just as rooms are used to group messages into manageable groups.\n"
+" \n"
+"   You, as a user, do NOT have to use floors.  If you choose not to, you suffer\n"
+"no penalty; you will not lose access to any rooms.  You may use .EC or ;C (the\n"
+"latter is easier to use) to decide if you want to use floors.  Feel free to\n"
+"experiment.\n"
+" \n"
+"   Floor options are accessed two ways.  First, if you are in floor mode, the\n"
+"<G>oto and <S>kip commands take you to the next room with new messages on the\n"
+"current floor; if there are none left, then the system will automatically\n"
+"switch floors (and let you know) and put you in the first room with new messages\n"
+"on that level.  (Notice that your pattern of basic use of Citadel therefore\n"
+"doesn't really change.)\n"
+" \n"
+"   Direct access to floor options is via the use of a ';' command.\n"
+"The following commands are currently available (more can be\n"
+"added if needed):\n"
+" \n"
+" <;C>onfigure\n"
+" This command toggles your floor mode.\n"
+" \n"
+" <;G>oto FLOORNAME\n"
+" This command causes the system to take you to the named floor.\n"
+" \n"
+" <;K>nown rooms on floors\n"
+" List all rooms on all floors.  This is a very readable way to get a list of\n"
+"all rooms on the system.\n"
+" \n"
+" <;S>kip FLOORNAME\n"
+" This command causes the system to mark all rooms on the current floor as\n"
+"Skipped and takes you to the floor that you specify.\n"
+" \n"
+" <;Z>Forget floor\n"
+"   This command causes you to forget all the rooms currently on the current\n"
+"floor.  Unfortunately, it doesn't apply to rooms that are subsequently created\n"
+"or moved to this floor.  (Sorry.)\n"
+" \n"
+"   Feel free to experiment, you can't hurt yourself or the system with the\n"
+"floor stuff unless you ZForget a floor by accident.\n"
+
+,
+
+"                  New User's Introduction to the site\n"
+"  \n"
+" This is an introduction to the Citadel BBS concept.  It is intended\n"
+"for new users so that they can more easily become acquainted to using\n"
+"Citadel when accessing it in the form of a text-based BBS.  Of\n"
+"course, old users might learn something new each time they read\n"
+"through it.\n"
+" \n"
+" Full help for the BBS commands can be obtained by typing <.H>elp SUMMARY\n"
+"  \n"
+" The CITADEL BBS room concept\n"
+" ----------------------------\n"
+"   The term BBS stands for 'Bulletin Board System'.  The analogy is\n"
+"appropriate: one posts messages so that others may read them.  In\n"
+"order to organize the posts, people can post in different areas of the\n"
+"BBS, called rooms.\n"
+"   In order to post in a certain room, you need to be 'in' that room.\n"
+"Your current prompt is usually the room that you are in, followed the\n"
+"greater-than-sign, such as:\n"
+" \n"
+" Lobby>\n"
+" \n"
+" The easiest way to traverse the room structure is with the 'Goto'\n"
+"command, on the 'G' key.  Pressing 'G' will take you to the next room\n"
+"in the 'march list' (see below) that has new messages in it.  You can\n"
+"read these new messages with the 'N' key.\n"
+" Once you've 'Gotoed' every room in the system (or all of the ones\n"
+"you choose to read) you return to the 'Lobby,' the first and last room\n"
+"in the system.  If new messages get posted to rooms you've already\n"
+"read during your session you will be brought BACK to those rooms so\n"
+"you can read them.\n"
+" \n"
+" March List\n"
+" ----------\n"
+"   All the room names are stored in a march list, which is just a\n"
+"list containing all the room names.  When you <G>oto or <S>kip a\n"
+"room, you are placed in the next room in your march list THAT HAS NEW\n"
+"MESSAGES.  If you have no new messages in any of the rooms on your\n"
+"march list, you will keep going to the Lobby>.  You can choose not to\n"
+"read certain rooms (that don't interest you) by 'Z'apping them.  When\n"
+"you <Z>ap a room, you are merely deleting it from your march list (but\n"
+"not from anybody else's).\n"
+" \n"
+"   You can use the <.G>oto (note the period before the G.  You can also use\n"
+"<J>ump on some systems) to go to any room in the\n"
+"system.  You don't have to type in the complete name of a room to\n"
+"'jump' to it; you merely need to type in enough to distinguish it from\n"
+"the other rooms.  Left-aligned matches carry a heavier weight, so if you\n"
+"typed (for example) '.Goto TECH', you might be taken to a room called\n"
+"'Tech Area>' even if it found a room called 'Biotech/Ethics>' first.\n"
+" \n"
+"  To return to a room you have previously <Z>apped, use the <.G>oto command\n"
+"to enter it, and it will be re-inserted into your march list.  In the case\n"
+"of returning to Zapped rooms, you must type the room name in its entirety.\n"
+"REMEMBER, rooms with no new messages will not show on your\n"
+"march list!  You must <.G>oto to a room with no new messages.\n"
+"Incidentally, you cannot change the order of the rooms on your march list.\n"
+"It's the same for everybody.\n"
+" \n"
+" Special rooms\n"
+" -------------\n"
+"   There are two special rooms on a Citadel that you should know about.\n"
+"  \n"
+"   The first is the Lobby>.  It's used for system announcements and other\n"
+"such administrativia.  You cannot <Z>ap the Lobby>.  Each time you first\n"
+"login, you will be placed in the Lobby>.\n"
+" \n"
+"   The second is Mail>.  In Mail>, when you post a messages, you are\n"
+"prompted to enter the screen name of the person who you want to send the\n"
+"message to.  Only the person who you send the message to can read the\n"
+"message.  NO ONE else can read it, not even the admins.  Mail> is the\n"
+"first room on the march list, and is un-<Z>appable, so you can be sure\n"
+"that the person will get the message.\n"
+"   \n"
+" System admins\n"
+" -------------\n"
+"   These people, along with the room admins, keep the site running smoothly.\n"
+"\n"
+"   Among the many things that admins do are: create rooms, delete\n"
+"rooms, set access levels, invite users, check registration, grant\n"
+"room admin status, and countless other things.  They have access to the\n"
+"Aide> room, a special room only for admins.\n"
+" \n"
+"   If you enter a mail message to 'Sysop' it will be placed in the\n"
+"Aide> room so that the next admin online will read it and deal with it.\n"
+"Admins cannot <Z>ap rooms.  All the rooms are always on each admin's\n"
+"march list.  Admins can read *any* and *every* room, but they *CAN* *NOT*\n"
+"read other users' Mail!\n"
+"  \n"
+" Room admins\n"
+" -----------\n"
+"   Room admins are granted special privileges in specific rooms.\n"
+"They are *NOT* true system admins; their power extends only over the\n"
+"rooms that they control, and they answer to the system admins.\n"
+"  \n"
+"   A room admin's job is to keep the topic of the their room on track,\n"
+"with nudges in the right direction now and then.  A room admin can also\n"
+"move an off topic post to another room, or delete a post, if he/she\n"
+"feels it is necessary. \n"
+"  \n"
+"   Currently, very few rooms have room admins.  Most rooms do not need\n"
+"their own specific room admin.  Being a room admin requires a certain\n"
+"amount of trust, due to the additional privileges granted.\n"
+"  \n"
+" Citadel messages\n"
+" ----------------\n"
+"   Most of the time, the BBS code does not print a lot of messages\n"
+"to your screen.  This is a great benefit once you become familiar\n"
+"with the system, because you do not have endless menus and screens\n"
+"to navigate through.  nevertheless, there are some messages which you\n"
+"might see from time to time.\n"
+"  \n"
+"  'There were messages posted while you were entering.'\n"
+"  \n"
+"   This is also known as 'simulposting.'  When you start entering a \n"
+"message, the system knows where you last left off.  When you save\n"
+"your message, the system checks to see if any messages were entered\n"
+"while you were typing.  This is so that you know whether you need\n"
+"to go back and re-read the last few messages.  This message may appear\n"
+"in any room.\n"
+"   \n"
+" '*** You have new mail'\n"
+"  \n"
+"   This message is essentially the same as the above message, but can\n"
+"appear at any time.  It simply means that new mail has arrived for you while\n"
+"you are logged in.  Simply go to the Mail> room to read it.\n"
+"  \n"
+" Who list\n"
+" --------\n"
+"   The <W>ho command shows you the names of all users who are currently\n"
+"online.  It also shows you the name of the room they are currently in.  If\n"
+"they are in any type of private room, however, the room name will simply\n"
+"display as '<private room>'.  Along with this information is displayed the\n"
+"name of the host computer the user is logged in from.\n"
+
+,
+
+"To send mail on this system, go to the Mail> room (using the command .G Mail)\n"
+"and press E to enter a message.  You will be prompted with:\n"
+" \n"
+" Enter Recipient:\n"
+" \n"
+"   At this point you may enter the name of another user on the system.  Private\n"
+"mail is only readable by the sender and recipient.  There is no need to delete\n"
+"mail after it is read; it will scroll out automatically.\n"
+"  \n"
+"   To send mail to another user on the Citadel network, simply type the\n"
+"user's name, followed by @ and then the system name. For example,\n"
+"  \n"
+" Enter Recipient: Joe Schmoe @ citadrool\n"
+"  \n"
+"  If your account is enabled for Internet mail, you can also send email to\n"
+"anyone on the Internet here.  Simply enter their address at the prompt:\n"
+"  \n"
+" Enter Recipient: ajc@herring.fishnet.com\n"
+
+,
+
+"  Welcome to the network. Messages entered in a network room will appear in\n"
+"that room on all other systems carrying it (The name of the room, however,\n"
+"may be different on other systems).\n"
+
+,
+
+"   Citadel is the premier 'online community' (i.e. Bulletin Board System)\n"
+"software.  It runs on all POSIX-compliant systems, including Linux.  It is an\n"
+"advanced client/server application, and is being actively maintained.\n"
+" \n"
+"   For more info, visit UNCENSORED! BBS at uncensored.citadel.org\n"
+
+,
+
+"Extended commands are available using the period ( . ) key. To use\n"
+"a dot command, press the . key, and then enter the first letter of\n"
+"each word in the command. The words will appear as you enter the keys.\n"
+"You can also backspace over partially entered commands. The following\n"
+"commands are available:\n"
+"\n"
+" <.> <H>elp:    Displays help files.  Type .H followed by a help file\n"
+"                name.  You are now reading <.H>elp SUMMARY\n"
+" \n"
+" <.> <G>oto:    Jumps directly to the room you specify.  You can also\n"
+"                type a partial room name, just enough to make it unique,\n"
+"                and it'll find the room you're looking for.  As with the\n"
+"                regular <G>oto command, messages in the current room will\n"
+"                be marked as read.\n"
+" \n"
+" <.> <S>kip, goto:    This is similar to <.G>oto, except it doesn't mark\n"
+"                      messages in the current room as read.\n"
+" \n"
+" <.> list <Z>apped rooms      Shows all rooms you've <Z>apped (forgotten)\n"
+"\n"
+"  \n"
+" Terminate (logoff) commands:\n"
+" \n"
+" <.> <T>erminate and <Q>uit               Log off and disconnect.\n"
+" <.> <T>erminate and <S>tay online        Log in as a different user.\n"
+" \n"
+" \n"
+" Read commands:\n"
+"\n"
+" <.> <R>ead <N>ew messages                Same as <N>ew\n"
+" <.> <R>ead <O>ld msgs reverse            Same as <O>ld\n"
+" <.> <R>ead <L>ast five msgs              Same as <L>ast5\n"
+" <.> read <L>ast:                         Allows you to specify how many\n"
+"                                          messages you wish to read.\n"
+"\n"
+" <.> <R>ead <U>ser listing:               Lists all users on the system if\n"
+"                                          you just hit enter, otherwise\n"
+"                                          you can specify a partial match\n"
+"\n"
+" <.> <R>ead <T>extfile formatted          File 'download' commands.\n"
+" <.> <R>ead file using <X>modem   \n"
+" <.> <R>ead file using <Y>modem   \n"
+" <.> <R>ead file using <Z>modem   \n"
+" <.> <R>ead <F>ile unformatted   \n"
+" <.> <R>ead <D>irectory   \n"
+"\n"
+" <.> <R>ead <I>nfo file                   Read the room info file.\n"
+" <.> <R>ead <B>io                         Read other users' 'bio' files.\n"
+" <.> <R>ead <C>onfiguration               Display your 'preferences'.\n"
+" <.> <R>ead <S>ystem info                 Display system statistics.\n"
+"\n"
+" \n"
+" Enter commands:\n"
+"\n"
+" <.> <E>nter <M>essage                    Post a message in this room.\n"
+" <.> <E>nter message with <E>ditor        Post using a full-screen editor.\n"
+" <.> <E>nter <A>SCII message              Post 'raw' (use this when 'pasting'\n"
+"                                          a message from your clipboard).\n"
+"\n"
+" <.> <E>nter <P>assword                   Change your password.\n"
+" <.> <E>nter <C>onfiguration              Change your 'preferences'.\n"
+" <.> <E>nter a new <R>oom                 Create a new room.\n"
+" <.> <E>nter re<G>istration               Register (name, address, etc.)\n"
+" <.> <E>nter <B>io                        Enter/change your 'bio' file.\n"
+"\n"
+" <.> <E>nter <T>extfile                   File 'upload' commands.\n"
+" <.> <E>nter file using <X>modem   \n"
+" <.> <E>nter file using <Y>modem   \n"
+" <.> <E>nter file using <Z>modem   \n"
+"  \n"
+"  \n"
+"  Wholist commands:\n"
+" \n"
+" <.> <W>holist <L>ong             Same as <W>ho is online, but displays\n"
+"                                  more detailed information.\n"
+" <.> <W>holist <R>oomname         Masquerade your room name (other users\n"
+"                                  see the name you enter rather than the\n"
+"                                  actual name of the room you're in)\n"
+" <.> <W>holist <H>ostname         Masquerade your host name\n"
+" <.> <E>nter <U>sername           Masquerade your user name (Admins only)\n"
+" <.> <W>holist <S>tealth mode     Enter/exit 'stealth mode' (when in stealth\n"
+"                                  mode you are invisible on the wholist)\n"
+" \n"
+" \n"
+" Floor commands (if using floor mode)\n"
+" ;<C>onfigure floor mode            - turn floor mode on or off\n"
+" ;<G>oto floor:                     - jump to a specific floor\n"
+" ;<K>nown rooms                     - list all rooms on all floors\n"
+" ;<S>kip to floor:                  - skip current floor, jump to another\n"
+" ;<Z>ap floor                       - zap (forget) all rooms on this floor\n"
+" \n"
+" \n"
+" Administrative commands: \n"
+" \n"
+" <.> <A>dmin <K>ill this room   \n"
+" <.> <A>dmin <E>dit this room   \n"
+" <.> <A>dmin <W>ho knows room   \n"
+" <.> <A>dmin edit <U>ser   \n"
+" <.> <A>dmin <V>alidate new users   \n"
+" <.> <A>dmin enter <I>nfo file   \n"
+" <.> <A>dmin <R>oom <I>nvite user  \n"
+" <.> <A>dmin <R>oom <K>ick out user  \n"
+" <.> <A>dmin <F>ile <D>elete  \n"
+" <.> <A>dmin <F>ile <S>end over net  \n"
+" <.> <A>dmin <F>ile <M>ove  \n"
+" <.> <A>dmin <M>essage edit:   \n"
+" <.> <A>dmin <P>ost   \n"
+" <.> <A>dmin <S>ystem configuration   \n"
+" <.> <A>dmin <T>erminate server <N>ow\n"
+" <.> <A>dmin <T>erminate server <S>cheduled\n"
+
+};
+
+
+struct citcmd {
+       struct citcmd *next;
+       int c_cmdnum;
+       int c_axlevel;
+       char c_keys[5][64];
+};
+
+#define IFNEXPERT if ((userflags&US_EXPERT)==0)
+
+
+int rc_exp_beep;
+char rc_exp_cmd[1024];
+int rc_allow_attachments;
+int rc_display_message_numbers;
+int rc_force_mail_prompts;
+int rc_remember_passwords;
+int rc_ansi_color;
+int rc_color_use_bg;
+int rc_prompt_control = 0;
+time_t rc_idle_threshold = (time_t)900;
+char rc_url_cmd[SIZ];
+char rc_open_cmd[SIZ];
+char rc_gotmail_cmd[SIZ];
+
+int next_lazy_cmd = 5;
+
+extern int screenwidth, screenheight;
+extern int termn8;
+extern CtdlIPC *ipc_for_signal_handlers;       /* KLUDGE cover your eyes */
+
+struct citcmd *cmdlist = NULL;
+
+
+/* these variables are local to this module */
+char keepalives_enabled = KA_YES;      /* send NOOPs to server when idle */
+int ok_to_interrupt = 0;               /* print instant msgs asynchronously */
+time_t AnsiDetect;                     /* when did we send the detect code? */
+int enable_color = 0;                  /* nonzero for ANSI color */
+
+
+
+
+/*
+ * If an interesting key has been pressed, return its value, otherwise 0
+ */
+char was_a_key_pressed(void) {
+       fd_set rfds;
+       struct timeval tv;
+       int the_character;
+       int retval;
+
+       FD_ZERO(&rfds);
+       FD_SET(0, &rfds);
+       tv.tv_sec = 0;
+       tv.tv_usec = 0;
+       retval = select(1, &rfds, NULL, NULL, &tv); 
+
+       /* Careful!  Disable keepalives during keyboard polling; we're probably
+        * in the middle of a data transfer from the server, in which case
+        * sending a NOOP would throw the client protocol out of sync.
+        */
+       if ((retval > 0) && FD_ISSET(0, &rfds)) {
+               set_keepalives(KA_NO);
+               the_character = inkey();
+               set_keepalives(KA_YES);
+       }
+       else {
+               the_character = 0;
+       }
+       return(the_character);
+}
+
+
+
+
+
+/*
+ * print_instant()  -  print instant messages if there are any
+ */
+void print_instant(void)
+{
+       char buf[1024];
+       FILE *outpipe;
+       time_t timestamp;
+       struct tm stamp;
+       int flags = 0;
+       char sender[64];
+       char node[64];
+       char *listing = NULL;
+       int r;                  /* IPC result code */
+
+       if (instant_msgs == 0)
+               return;
+
+       if (rc_exp_beep) {
+               ctdl_beep();
+       }
+       if (IsEmptyStr(rc_exp_cmd)) {
+               color(BRIGHT_RED);
+               scr_printf("\r---");
+       }
+       
+       while (instant_msgs != 0) {
+               r = CtdlIPCGetInstantMessage(ipc_for_signal_handlers, &listing, buf);
+               if (r / 100 != 1)
+                       return;
+       
+               instant_msgs = extract_int(buf, 0);
+               timestamp = extract_long(buf, 1);
+               flags = extract_int(buf, 2);
+               extract_token(sender, buf, 3, '|', sizeof sender);
+               extract_token(node, buf, 4, '|', sizeof node);
+               strcpy(last_paged, sender);
+       
+               localtime_r(&timestamp, &stamp);
+
+               /* If the page is a Logoff Request, honor it. */
+               if (flags & 2) {
+                       termn8 = 1;
+                       return;
+               }
+       
+               if (!IsEmptyStr(rc_exp_cmd)) {
+                       outpipe = popen(rc_exp_cmd, "w");
+                       if (outpipe != NULL) {
+                               /* Header derived from flags */
+                               if (flags & 2)
+                                       fprintf(outpipe,
+                                              "Please log off now, as requested ");
+                               else if (flags & 1)
+                                       fprintf(outpipe, "Broadcast message ");
+                               else if (flags & 4)
+                                       fprintf(outpipe, "Chat request ");
+                               else
+                                       fprintf(outpipe, "Message ");
+                               /* Timestamp.  Can this be improved? */
+                               if (stamp.tm_hour == 0 || stamp.tm_hour == 12)
+                                       fprintf(outpipe, "at 12:%02d%cm",
+                                               stamp.tm_min, 
+                                               stamp.tm_hour ? 'p' : 'a');
+                               else if (stamp.tm_hour > 12)            /* pm */
+                                       fprintf(outpipe, "at %d:%02dpm",
+                                               stamp.tm_hour - 12,
+                                               stamp.tm_min);
+                               else                                    /* am */
+                                       fprintf(outpipe, "at %d:%02dam",
+                                               stamp.tm_hour, stamp.tm_min);
+                               fprintf(outpipe, " from %s", sender);
+                               if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
+                                       fprintf(outpipe, " @%s", node);
+                               fprintf(outpipe, ":\n%s\n", listing);
+                               pclose(outpipe);
+                               if (instant_msgs == 0)
+                                       return;
+                               continue;
+                       }
+               }
+               /* fall back to built-in instant message display */
+               scr_printf("\n");
+
+               /* Header derived from flags */
+               if (flags & 2)
+                       scr_printf("Please log off now, as requested ");
+               else if (flags & 1)
+                       scr_printf("Broadcast message ");
+               else if (flags & 4)
+                       scr_printf("Chat request ");
+               else
+                       scr_printf("Message ");
+       
+               /* Timestamp.  Can this be improved? */
+               if (stamp.tm_hour == 0 || stamp.tm_hour == 12)/* 12am/12pm */
+                       scr_printf("at 12:%02d%cm", stamp.tm_min, 
+                               stamp.tm_hour ? 'p' : 'a');
+               else if (stamp.tm_hour > 12)                    /* pm */
+                       scr_printf("at %d:%02dpm",
+                               stamp.tm_hour - 12, stamp.tm_min);
+               else                                            /* am */
+                       scr_printf("at %d:%02dam", stamp.tm_hour, stamp.tm_min);
+               
+               /* Sender */
+               scr_printf(" from %s", sender);
+       
+               /* Remote node, if any */
+               if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
+                       scr_printf(" @%s", node);
+       
+               scr_printf(":\n");
+               fmout(screenwidth, NULL, listing, NULL, 0);
+               free(listing);
+
+       }
+       scr_printf("\n---\n");
+       color(BRIGHT_WHITE);
+
+
+}
+
+
+void set_keepalives(int s)
+{
+       keepalives_enabled = (char) s;
+}
+
+/* 
+ * This loop handles the "keepalive" messages sent to the server when idling.
+ */
+
+static time_t idlet = 0;
+static void really_do_keepalive(void) {
+
+       time(&idlet);
+
+       /* This may sometimes get called before we are actually connected
+        * to the server.  Don't do anything if we aren't connected. -IO
+        */
+       if (!ipc_for_signal_handlers)
+               return;
+
+       /* If full keepalives are enabled, send a NOOP to the server and
+        * wait for a response.
+        */
+       if (keepalives_enabled == KA_YES) {
+               CtdlIPCNoop(ipc_for_signal_handlers);
+               if (instant_msgs > 0) {
+                       if (ok_to_interrupt == 1) {
+                               scr_printf("\r%64s\r", "");
+                               print_instant();
+                               scr_printf("%s%c ", room_name,
+                                      room_prompt(room_flags));
+                               scr_flush();
+                       }
+               }
+       }
+
+       /* If half keepalives are enabled, send a QNOP to the server (if the
+        * server supports it) and then do nothing.
+        */
+       if ( (keepalives_enabled == KA_HALF)
+          && (ipc_for_signal_handlers->ServInfo.supports_qnop > 0) ) {
+               CtdlIPC_chat_send(ipc_for_signal_handlers, "QNOP");
+       }
+}
+
+/* I changed this from static to not because I need to call it from
+ * screen.c, either that or make something in screen.c not static.
+ * Fix it how you like. Why all the staticness? stu
+ */
+void do_keepalive(void)
+{
+       time_t now;
+
+       time(&now);
+       if ((now - idlet) < ((long) S_KEEPALIVE))
+       {
+               return;
+       }
+
+       /* Do a space-backspace to keep terminal sessions from idling out */
+       scr_printf(" %c", 8);
+       scr_flush();
+
+       really_do_keepalive();
+}
+
+
+
+int inkey(void)
+{                              /* get a character from the keyboard, with   */
+       int a;                  /* the watchdog timer in effect if necessary */
+       fd_set rfds;
+       struct timeval tv;
+       time_t start_time;
+
+       scr_flush();
+       time(&start_time);
+
+       do {
+               /* This loop waits for keyboard input.  If the keepalive
+                * timer expires, it sends a keepalive to the server if
+                * necessary and then waits again.
+                */
+               do {
+                       do_keepalive();
+
+                       FD_ZERO(&rfds);
+                       FD_SET(0, &rfds);
+                       tv.tv_sec = S_KEEPALIVE;
+                       tv.tv_usec = 0;
+
+                       select(1, &rfds, NULL, NULL, &tv);
+               } while (!FD_ISSET(0, &rfds));
+
+               /* At this point, there's input, so fetch it.
+                * (There's a hole in the bucket...)
+                */
+               a = scr_getc(SCR_BLOCK);
+               if (a == 127) {
+                       a = 8;
+               }
+               if (a == 13) {
+                       a = 10;
+               }
+       } while (a == 0);
+       return (a);
+}
+
+
+int yesno(void)
+{                              /* Returns 1 for yes, 0 for no */
+       int a;
+       while (1) {
+               a = inkey();
+               a = tolower(a);
+               if (a == 'y') {
+                       scr_printf("Yes\n");
+                       return (1);
+               }
+               if (a == 'n') {
+                       scr_printf("No\n");
+                       return (0);
+               }
+       }
+}
+
+/* Returns 1 for yes, 0 for no, arg is default value */
+int yesno_d(int d)
+{
+       int a;
+       while (1) {
+               a = inkey();
+               a = tolower(a);
+               if (a == 10)
+                       a = (d ? 'y' : 'n');
+               if (a == 'y') {
+                       scr_printf("Yes\n");
+                       return (1);
+               }
+               if (a == 'n') {
+                       scr_printf("No\n");
+                       return (0);
+               }
+       }
+}
+
+
+
+
+/*
+ * Function to read a line of text from the terminal.
+ *
+ * string              Pointer to string buffer
+ * lim                 Maximum length
+ * noshow              Echo asterisks instead of keystrokes?
+ * bs                  Allow backspacing out of the prompt? (returns -1 if this happens)
+ *
+ * returns: string length
+ */
+int ctdl_getline(char *string, int lim, int noshow, int bs)
+{
+       int pos = strlen(string);
+       int ch;
+
+       if (noshow && !IsEmptyStr(string)) {
+               int num_stars = strlen(string);
+               while (num_stars--) {
+                       scr_putc('*');
+               }
+       }
+       else {
+               scr_printf("%s", string);
+       }
+
+       while(1) {
+               ch = inkey();
+
+               if ((ch == 8)  && (pos > 0)) {                          /* backspace */
+                       --pos;
+                       scr_putc(8); scr_putc(32); scr_putc(8);
+               }
+
+               else if ((ch == 8) && (pos == 0) && (bs)) {             /* backspace out of the prompt */
+                       return(-1);
+               }
+
+               else if ((ch == 23) && (pos > 0)) {                     /* Ctrl-W deletes a word */
+                       while ((pos > 0) && !isspace(string[pos])) {
+                               --pos;
+                               scr_putc(8); scr_putc(32); scr_putc(8);
+                       }
+                       while ((pos > 0) && !isspace(string[pos-1])) {
+                               --pos;
+                               scr_putc(8); scr_putc(32); scr_putc(8);
+                       }
+               }
+
+               else if (ch == 10) {                                    /* return */
+                       string[pos] = 0;
+                       scr_printf("\n");
+                       return(pos);
+               }
+
+               else if (isprint(ch)) {                                 /* payload characters */
+                       scr_putc((noshow ? '*' : ch));
+                       string[pos] = ch;
+                       ++pos;
+               }
+       }
+}
+
+
+/* 
+ * newprompt()         prompt for a string, print the existing value, and
+ *                     allow the user to press return to keep it...
+ *                     If len is negative, pass the "noshow" flag to ctdl_getline()
+ */
+void strprompt(char *prompt, char *str, int len)
+{
+       print_instant();
+       color(DIM_WHITE);
+       scr_printf("%s", prompt);
+       color(DIM_WHITE);
+       scr_printf(": ");
+       color(BRIGHT_CYAN);
+       ctdl_getline(str, abs(len), (len<0), 0);
+       color(DIM_WHITE);
+}
+
+/*
+ * boolprompt()  -  prompt for a yes/no, print the existing value and
+ *                  allow the user to press return to keep it...
+ */
+int boolprompt(char *prompt, int prev_val)
+{
+       int r;
+
+       color(DIM_WHITE);
+       scr_printf("%s ", prompt);
+       color(DIM_MAGENTA);
+       scr_printf("[");
+       color(BRIGHT_MAGENTA);
+       scr_printf("%s", (prev_val ? "Yes" : "No"));
+       color(DIM_MAGENTA);
+       scr_printf("]: ");
+       color(BRIGHT_CYAN);
+       r = (yesno_d(prev_val));
+       color(DIM_WHITE);
+       return r;
+}
+
+/* 
+ * intprompt()  -  like strprompt(), except for an integer
+ *                 (note that it RETURNS the new value!)
+ */
+int intprompt(char *prompt, int ival, int imin, int imax)
+{
+       char buf[16];
+       int i;
+       int p;
+
+       do {
+               i = ival;
+               snprintf(buf, sizeof buf, "%d", i);
+               strprompt(prompt, buf, 15);
+               i = atoi(buf);
+               for (p=0; !IsEmptyStr(&buf[p]); ++p) {
+                       if ( (!isdigit(buf[p]))
+                          && ( (buf[p]!='-') || (p!=0) )  )
+                               i = imin - 1;
+               }
+               if (i < imin)
+                       scr_printf("*** Must be no less than %d.\n", imin);
+               if (i > imax)
+                       scr_printf("*** Must be no more than %d.\n", imax);
+       } while ((i < imin) || (i > imax));
+       return (i);
+}
+
+/* 
+ * newprompt()         prompt for a string with no existing value
+ *                     (clears out string buffer first)
+ *                     If len is negative, pass the "noshow" flag to ctdl_getline()
+ */
+void newprompt(char *prompt, char *str, int len)
+{
+       str[0] = 0;
+       color(BRIGHT_MAGENTA);
+       scr_printf("%s", prompt);
+       color(DIM_MAGENTA);
+       ctdl_getline(str, abs(len), (len<0), 0);
+       color(DIM_WHITE);
+}
+
+
+int lkey(void)
+{                              /* returns a lower case value */
+       int a;
+       a = inkey();
+       if (isupper(a))
+               a = tolower(a);
+       return (a);
+}
+
+/*
+ * parse the citadel.rc file
+ */
+void load_command_set(void)
+{
+       FILE *ccfile;
+       char buf[1024];
+       struct citcmd *cptr;
+       struct citcmd *lastcmd = NULL;
+       int a, d;
+       int b = 0;
+
+       /* first, set up some defaults for non-required variables */
+
+       strcpy(editor_path, "");
+       strcpy(printcmd, "");
+       strcpy(imagecmd, "");
+       strcpy(rc_username, "");
+       strcpy(rc_password, "");
+       rc_floor_mode = 0;
+       rc_exp_beep = 1;
+       rc_allow_attachments = 0;
+       rc_remember_passwords = 0;
+       strcpy(rc_exp_cmd, "");
+       rc_display_message_numbers = 0;
+       rc_force_mail_prompts = 0;
+       rc_ansi_color = 0;
+       rc_color_use_bg = 0;
+       strcpy(rc_url_cmd, "");
+       strcpy(rc_open_cmd, "");
+       strcpy(rc_gotmail_cmd, "");
+#ifdef HAVE_OPENSSL
+       rc_encrypt = RC_DEFAULT;
+#endif
+
+       /* now try to open the citadel.rc file */
+
+       ccfile = NULL;
+       if (getenv("HOME") != NULL) {
+               snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
+               ccfile = fopen(buf, "r");
+       }
+       if (ccfile == NULL) {
+               ccfile = fopen(file_citadel_rc, "r");
+       }
+       if (ccfile == NULL) {
+               ccfile = fopen("/etc/citadel.rc", "r");
+       }
+       if (ccfile == NULL) {
+               ccfile = fopen("./citadel.rc", "r");
+       }
+       if (ccfile == NULL) {
+               perror("commands: cannot open citadel.rc");
+               logoff(NULL, 3);
+       }
+       while (fgets(buf, sizeof buf, ccfile) != NULL) {
+               while ((!IsEmptyStr(buf)) ? (isspace(buf[strlen(buf) - 1])) : 0)
+                       buf[strlen(buf) - 1] = 0;
+
+               if (!strncasecmp(buf, "encrypt=", 8)) {
+                       if (!strcasecmp(&buf[8], "yes")) {
+#ifdef HAVE_OPENSSL
+                               rc_encrypt = RC_YES;
+#else
+                               fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
+                               logoff(NULL, 3);
+#endif
+                       }
+#ifdef HAVE_OPENSSL
+                       else if (!strcasecmp(&buf[8], "no")) {
+                               rc_encrypt = RC_NO;
+                       }
+                       else if (!strcasecmp(&buf[8], "default")) {
+                               rc_encrypt = RC_DEFAULT;
+                       }
+#endif
+               }
+
+               if (!strncasecmp(buf, "editor=", 7)) {
+                       strcpy(editor_path, &buf[7]);
+               }
+
+               if (!strncasecmp(buf, "printcmd=", 9))
+                       strcpy(printcmd, &buf[9]);
+
+               if (!strncasecmp(buf, "imagecmd=", 9))
+                       strcpy(imagecmd, &buf[9]);
+
+               if (!strncasecmp(buf, "expcmd=", 7))
+                       strcpy(rc_exp_cmd, &buf[7]);
+
+               if (!strncasecmp(buf, "use_floors=", 11)) {
+                       if (!strcasecmp(&buf[11], "yes"))
+                               rc_floor_mode = RC_YES;
+                       if (!strcasecmp(&buf[11], "no"))
+                               rc_floor_mode = RC_NO;
+                       if (!strcasecmp(&buf[11], "default"))
+                               rc_floor_mode = RC_DEFAULT;
+               }
+               if (!strncasecmp(buf, "beep=", 5)) {
+                       rc_exp_beep = atoi(&buf[5]);
+               }
+               if (!strncasecmp(buf, "allow_attachments=", 18)) {
+                       rc_allow_attachments = atoi(&buf[18]);
+               }
+               if (!strncasecmp(buf, "idle_threshold=", 15)) {
+                       rc_idle_threshold = atol(&buf[15]);
+               }
+               if (!strncasecmp(buf, "remember_passwords=", 19)) {
+                       rc_remember_passwords = atoi(&buf[19]);
+               }
+               if (!strncasecmp(buf, "display_message_numbers=", 24)) {
+                       rc_display_message_numbers = atoi(&buf[24]);
+               }
+               if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
+                       rc_force_mail_prompts = atoi(&buf[19]);
+               }
+               if (!strncasecmp(buf, "ansi_color=", 11)) {
+                       if (!strncasecmp(&buf[11], "on", 2))
+                               rc_ansi_color = 1;
+                       if (!strncasecmp(&buf[11], "auto", 4))
+                               rc_ansi_color = 2;      /* autodetect */
+                       if (!strncasecmp(&buf[11], "user", 4))
+                               rc_ansi_color = 3;      /* user config */
+               }
+