+++ /dev/null
-
-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.
-
--- /dev/null
+
+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.
+
*.gcda
Make_modules
Make_sources
-Makefile
Makefile.in
locale
aclocal.m4
citserver
config.log
config.status
-configure
configure-stamp
ctdlmigrate
database_cleanup.sh
--- /dev/null
+# 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
+++ /dev/null
-# 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)
--- /dev/null
+
+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".
+
+++ /dev/null
-# 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
-
+++ /dev/null
-# 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])
#!/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
+++ /dev/null
-#!/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
--- /dev/null
+/*
+ * 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(¶ms, 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() */
+
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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, "", "");
+ }
+ }
+}
--- /dev/null
+/*
+ * 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(×tamp, &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 */
+ }
+ if (!strncasecmp(buf, "status_line=", 12)) {
+ if (!strncasecmp(&buf[12], "on", 2))
+ &n