Initial revision
authorArt Cancro <ajc@citadel.org>
Sat, 11 Jul 1998 02:10:03 +0000 (02:10 +0000)
committerArt Cancro <ajc@citadel.org>
Sat, 11 Jul 1998 02:10:03 +0000 (02:10 +0000)
89 files changed:
citadel/COPYING.txt [new file with mode: 0644]
citadel/IAFA-PACKAGE [new file with mode: 0644]
citadel/Makefile.in [new file with mode: 0644]
citadel/README.txt [new file with mode: 0644]
citadel/aidepost.c [new file with mode: 0644]
citadel/axdefs.h [new file with mode: 0644]
citadel/citadel.c [new file with mode: 0644]
citadel/citadel.h [new file with mode: 0644]
citadel/citadel.lsm [new file with mode: 0644]
citadel/citadel.rc [new file with mode: 0644]
citadel/citmail.c [new file with mode: 0644]
citadel/citserver.c [new file with mode: 0644]
citadel/client_chat.c [new file with mode: 0644]
citadel/commands.c [new file with mode: 0644]
citadel/config.c [new file with mode: 0644]
citadel/control.c [new file with mode: 0644]
citadel/copyright.txt [new file with mode: 0644]
citadel/cux2ascii.c [new file with mode: 0644]
citadel/database.c [new file with mode: 0644]
citadel/dnetsetup [new file with mode: 0755]
citadel/file_ops.c [new file with mode: 0644]
citadel/help/aide [new file with mode: 0644]
citadel/help/floors [new file with mode: 0644]
citadel/help/hours [new file with mode: 0644]
citadel/help/intro [new file with mode: 0644]
citadel/help/mail [new file with mode: 0644]
citadel/help/network [new file with mode: 0644]
citadel/help/nice [new file with mode: 0644]
citadel/help/policy [new file with mode: 0644]
citadel/help/software [new file with mode: 0644]
citadel/help/summary [new file with mode: 0644]
citadel/housekeeping.c [new file with mode: 0644]
citadel/install.txt [new file with mode: 0644]
citadel/internetmail.c [new file with mode: 0644]
citadel/ipc_c_tcp.c [new file with mode: 0644]
citadel/ipcdef.h [new file with mode: 0644]
citadel/locate_host.c [new file with mode: 0644]
citadel/mailinglist.c [new file with mode: 0644]
citadel/mailinglists.txt [new file with mode: 0644]
citadel/messages.c [new file with mode: 0644]
citadel/messages/aideopt [new file with mode: 0644]
citadel/messages/changepw [new file with mode: 0644]
citadel/messages/dotopt [new file with mode: 0644]
citadel/messages/entermsg [new file with mode: 0644]
citadel/messages/entopt [new file with mode: 0644]
citadel/messages/goodbye [new file with mode: 0644]
citadel/messages/hello [new file with mode: 0644]
citadel/messages/help [new file with mode: 0644]
citadel/messages/mainmenu [new file with mode: 0644]
citadel/messages/newuser [new file with mode: 0644]
citadel/messages/readopt [new file with mode: 0644]
citadel/messages/register [new file with mode: 0644]
citadel/messages/roomaccess [new file with mode: 0644]
citadel/messages/saveopt [new file with mode: 0644]
citadel/messages/unlisted [new file with mode: 0644]
citadel/msgbase.c [new file with mode: 0644]
citadel/msgform.c [new file with mode: 0644]
citadel/netmailer.c [new file with mode: 0644]
citadel/netpoll.c [new file with mode: 0644]
citadel/netproc.c [new file with mode: 0644]
citadel/netsetup.c [new file with mode: 0644]
citadel/netsetup.txt [new file with mode: 0644]
citadel/network.txt [new file with mode: 0644]
citadel/network/filterlist [new file with mode: 0644]
citadel/network/internetmail.config [new file with mode: 0644]
citadel/network/mail.aliases [new file with mode: 0644]
citadel/network/mail.sysinfo [new file with mode: 0644]
citadel/network/mailinglists [new file with mode: 0644]
citadel/network/rnews.xref [new file with mode: 0644]
citadel/network/systems/uncnsrd [new file with mode: 0644]
citadel/public_clients [new file with mode: 0644]
citadel/rcit.c [new file with mode: 0644]
citadel/readlog.c [new file with mode: 0644]
citadel/room_ops.c [new file with mode: 0644]
citadel/rooms.c [new file with mode: 0644]
citadel/routines.c [new file with mode: 0644]
citadel/routines2.c [new file with mode: 0644]
citadel/serv_chat.c [new file with mode: 0644]
citadel/server.h [new file with mode: 0644]
citadel/setup.c [new file with mode: 0644]
citadel/stats.c [new file with mode: 0644]
citadel/support.c [new file with mode: 0644]
citadel/sysconfig.h [new file with mode: 0644]
citadel/sysdep.c [new file with mode: 0644]
citadel/sysop.txt [new file with mode: 0644]
citadel/user_ops.c [new file with mode: 0644]
citadel/utils.txt [new file with mode: 0644]
citadel/utilsmenu [new file with mode: 0755]
citadel/whobbs.c [new file with mode: 0644]

diff --git a/citadel/COPYING.txt b/citadel/COPYING.txt
new file mode 100644 (file)
index 0000000..2156ccd
--- /dev/null
@@ -0,0 +1,333 @@
+GNU GENERAL PUBLIC LICENSE
+
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to
+share and change it. By contrast, the GNU General Public License is intended
+to guarantee your freedom to share and change free software--to make sure
+the software is free for all its users. This General Public License applies
+to most of the Free Software Foundation's software and to any other program
+whose authors commit to using it. (Some other Free Software Foundation
+software is covered by the GNU Library General Public License instead.) You
+can apply it to your programs, too.
+
+When we speak of free software, we are referring to freedom, not price. Our
+General Public Licenses are designed to make sure that you have the freedom
+to distribute copies of free software (and charge for this service if you
+wish), that you receive source code or can get it if you want it, that you
+can change the software or use pieces of it in new free programs; and that
+you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to
+deny you these rights or to ask you to surrender the rights. These
+restrictions translate to certain responsibilities for you if you distribute
+copies of the software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis or
+for a fee, you must give the recipients all the rights that you have. You
+must make sure that they, too, receive or can get the source code. And you
+must show them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and (2)
+offer you this license which gives you legal permission to copy, distribute
+and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain that
+everyone understands that there is no warranty for this free software. If
+the software is modified by someone else and passed on, we want its
+recipients to know that what they have is not the original, so that any
+problems introduced by others will not reflect on the original authors'
+reputations.
+
+Finally, any free program is threatened constantly by software patents. We
+wish to avoid the danger that redistributors of a free program will
+individually obtain patent licenses, in effect making the program
+proprietary. To prevent this, we have made it clear that any patent must be
+licensed for everyone's free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and modification
+follow.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License applies to any program or other work which contains a notice
+placed by the copyright holder saying it may be distributed under the terms
+of this General Public License. The "Program", below, refers to any such
+program or work, and a "work based on the Program" means either the Program
+or any derivative work under copyright law: that is to say, a work
+containing the Program or a portion of it, either verbatim or with
+modifications and/or translated into another language. (Hereinafter,
+translation is included without limitation in the term "modification".) Each
+licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not covered
+by this License; they are outside its scope. The act of running the Program
+is not restricted, and the output from the Program is covered only if its
+contents constitute a work based on the Program (independent of having been
+made by running the Program). Whether that is true depends on what the
+Program does.
+
+1. You may copy and distribute verbatim copies of the Program's source code
+as you receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice and
+disclaimer of warranty; keep intact all the notices that refer to this
+License and to the absence of any warranty; and give any other recipients of
+the Program a copy of this License along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and you
+may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of it,
+thus forming a work based on the Program, and copy and distribute such
+modifications or work under the terms of Section 1 above, provided that you
+also meet all of these conditions:
+
+   * a) You must cause the modified files to carry prominent notices stating
+     that you changed the files and the date of any change.
+
+   * b) You must cause any work that you distribute or publish, that in
+     whole or in part contains or is derived from the Program or any part
+     thereof, to be licensed as a whole at no charge to all third parties
+     under the terms of this License.
+
+   * c) If the modified program normally reads commands interactively when
+     run, you must cause it, when started running for such interactive use
+     in the most ordinary way, to print or display an announcement including
+     an appropriate copyright notice and a notice that there is no warranty
+     (or else, saying that you provide a warranty) and that users may
+     redistribute the program under these conditions, and telling the user
+     how to view a copy of this License. (Exception: if the Program itself
+     is interactive but does not normally print such an announcement, your
+     work based on the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Program, and can be
+reasonably considered independent and separate works in themselves, then
+this License, and its terms, do not apply to those sections when you
+distribute them as separate works. But when you distribute the same sections
+as part of a whole which is a work based on the Program, the distribution of
+the whole must be on the terms of this License, whose permissions for other
+licensees extend to the entire whole, and thus to each and every part
+regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest your
+rights to work written entirely by you; rather, the intent is to exercise
+the right to control the distribution of derivative or collective works
+based on the Program.
+
+In addition, mere aggregation of another work not based on the Program with
+the Program (or with a work based on the Program) on a volume of a storage
+or distribution medium does not bring the other work under the scope of this
+License.
+
+3. You may copy and distribute the Program (or a work based on it, under
+Section 2) in object code or executable form under the terms of Sections 1
+and 2 above provided that you also do one of the following:
+
+   * a) Accompany it with the complete corresponding machine-readable source
+     code, which must be distributed under the terms of Sections 1 and 2
+     above on a medium customarily used for software interchange; or,
+
+   * b) Accompany it with a written offer, valid for at least three years,
+     to give any third party, for a charge no more than your cost of
+     physically performing source distribution, a complete machine-readable
+     copy of the corresponding source code, to be distributed under the
+     terms of Sections 1 and 2 above on a medium customarily used for
+     software interchange; or,
+
+   * c) Accompany it with the information you received as to the offer to
+     distribute corresponding source code. (This alternative is allowed only
+     for noncommercial distribution and only if you received the program in
+     object code or executable form with such an offer, in accord with
+     Subsection b above.)
+
+The source code for a work means the preferred form of the work for making
+modifications to it. For an executable work, complete source code means all
+the source code for all modules it contains, plus any associated interface
+definition files, plus the scripts used to control compilation and
+installation of the executable. However, as a special exception, the source
+code distributed need not include anything that is normally distributed (in
+either source or binary form) with the major components (compiler, kernel,
+and so on) of the operating system on which the executable runs, unless that
+component itself accompanies the executable.
+
+If distribution of executable or object code is made by offering access to
+copy from a designated place, then offering equivalent access to copy the
+source code from the same place counts as distribution of the source code,
+even though third parties are not compelled to copy the source along with
+the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program except as
+expressly provided under this License. Any attempt otherwise to copy,
+modify, sublicense or distribute the Program is void, and will automatically
+terminate your rights under this License. However, parties who have received
+copies, or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+5. You are not required to accept this License, since you have not signed
+it. However, nothing else grants you permission to modify or distribute the
+Program or its derivative works. These actions are prohibited by law if you
+do not accept this License. Therefore, by modifying or distributing the
+Program (or any work based on the Program), you indicate your acceptance of
+this License to do so, and all its terms and conditions for copying,
+distributing or modifying the Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the original
+licensor to copy, distribute or modify the Program subject to these terms
+and conditions. You may not impose any further restrictions on the
+recipients' exercise of the rights granted herein. You are not responsible
+for enforcing compliance by third parties to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot distribute so
+as to satisfy simultaneously your obligations under this License and any
+other pertinent obligations, then as a consequence you may not distribute
+the Program at all. For example, if a patent license would not permit
+royalty-free redistribution of the Program by all those who receive copies
+directly or indirectly through you, then the only way you could satisfy both
+it and this License would be to refrain entirely from distribution of the
+Program.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply and
+the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents
+or other property right claims or to contest validity of any such claims;
+this section has the sole purpose of protecting the integrity of the free
+software distribution system, which is implemented by public license
+practices. Many people have made generous contributions to the wide range of
+software distributed through that system in reliance on consistent
+application of that system; it is up to the author/donor to decide if he or
+she is willing to distribute software through any other system and a
+licensee cannot impose that choice.
+
+This section is intended to make thoroughly clear what is believed to be a
+consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in certain
+countries either by patents or by copyrighted interfaces, the original
+copyright holder who places the Program under this License may add an
+explicit geographical distribution limitation excluding those countries, so
+that distribution is permitted only in or among countries not thus excluded.
+In such case, this License incorporates the limitation as if written in the
+body of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions of
+the General Public License from time to time. Such new versions will be
+similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+10. If you wish to incorporate parts of the Program into other free programs
+whose distribution conditions are different, write to the author to ask for
+permission. For software which is copyrighted by the Free Software
+Foundation, write to the Free Software Foundation; we sometimes make
+exceptions for this. Our decision will be guided by the two goals of
+preserving the free status of all derivatives of our free software and of
+promoting the sharing and reuse of software generally.
+
+NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
+THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO
+THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM
+PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
+CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO
+LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR
+THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
+
+How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest possible
+use to the public, the best way to achieve this is to make it free software
+which everyone can redistribute and change under these terms.
+
+To do so, attach the following notices to the program. It is safest to
+attach them to the start of each source file to most effectively convey the
+exclusion of warranty; and each file should have at least the "copyright"
+line and a pointer to where the full notice is found.
+
+one line to give the program's name and an idea of what it does.
+Copyright (C) 19yy  name of author
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+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.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this when
+it starts in an interactive mode:
+
+Gnomovision version 69, Copyright (C) 19yy name of author
+Gnomovision comes with ABSOLUTELY NO WARRANTY; for details
+type `show w'.  This is free software, and you are welcome
+to redistribute it under certain conditions; type `show c'
+for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may be
+called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+Yoyodyne, Inc., hereby disclaims all copyright
+interest in the program `Gnomovision'
+(which makes passes at compilers) written
+by James Hacker.
+
+signature of Ty Coon, 1 April 1989
+Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General Public
+License instead of this License.
diff --git a/citadel/IAFA-PACKAGE b/citadel/IAFA-PACKAGE
new file mode 100644 (file)
index 0000000..25a3d67
--- /dev/null
@@ -0,0 +1,12 @@
+Title:          Citadel/UX
+Version:        5.01
+Description:    An implementation of the Citadel BBS program for Unix systems.
+               This is the de facto standard Unix version of Citadel, and is
+                now an advanced client/server application.
+Author:         Art Cancro <ajc@uncnsrd.mt-kisco.ny.us>
+Maintained-by:  Art Cancro <ajc@uncnsrd.mt-kisco.ny.us>
+Maintained-at:  uncnsrd.mt-kisco.ny.us, ftp.tux.org
+Platforms:      Any reasonably compliant POSIX (Unix, Linux, etc.) type
+                system with a TCP/IP stack and POSIX Threads (pthreads).
+Copying-Policy: Freely Redistributable
+Keywords:       Citadel Citadel/UX UNIX room-based BBS IGnet IGnet/Open
diff --git a/citadel/Makefile.in b/citadel/Makefile.in
new file mode 100644 (file)
index 0000000..3ac0bea
--- /dev/null
@@ -0,0 +1,189 @@
+# Makefile for Citadel/UX
+#
+# 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@uncnsrd.mt-kisco.ny.us and let me know what you
+#    did, so any necessary changes can be put into the next release.
+#
+########################################################################
+
+client: citadel whobbs
+
+server: citserver setup
+
+utils: aidepost netmailer netproc netsetup useradmin msgform \
+msgstats readlog rcit stats sysoputil citmail netpoll mailinglist
+
+#
+#
+
+citadel: ipc_c_tcp.o citadel.o rooms.o routines.o routines2.o messages.o \
+       commands.o client_chat.o
+       $(CC) $(CFLAGS) ipc_c_tcp.o citadel.o rooms.o routines.o routines2.o \
+       messages.o commands.o client_chat.o $(LFLAGS) -o citadel
+
+netpoll: netpoll.c config.o ipc_c_tcp.o
+       $(CC) $(CFLAGS) netpoll.c config.o ipc_c_tcp.o $(LFLAGS) -o netpoll
+
+ipc_c_tcp.o: ipc_c_tcp.c sysdep.h
+       $(CC) $(CFLAGS) -c ipc_c_tcp.c
+
+ipc_c_socks4.o: ipc_c_socks4.c sysdep.h
+       $(CC) $(CFLAGS) -c ipc_c_socks4.c
+
+citadel.o: citadel.c axdefs.h citadel.h
+       $(CC) -O $(CFLAGS) -c citadel.c
+
+rooms.o: rooms.c citadel.h
+       $(CC) -O $(CFLAGS) -c rooms.c
+
+messages.o: messages.c citadel.h
+       $(CC) -O $(CFLAGS) -c messages.c
+
+commands.o: commands.c citadel.h
+       $(CC) -O $(CFLAGS) -c commands.c
+
+routines.o: routines.c citadel.h
+       $(CC) -O $(CFLAGS) -c routines.c
+
+routines2.o: routines2.c citadel.h
+       $(CC) -O $(CFLAGS) -c routines2.c
+
+client_chat.o: client_chat.c citadel.h
+       $(CC) -O $(CFLAGS) -c client_chat.c
+
+
+#
+#
+
+citserver: citserver.o user_ops.o support.o room_ops.o file_ops.o \
+       msgbase.o config.o sysdep.o locate_host.o serv_chat.o \
+       hooks.o housekeeping.o database.o control.o
+       $(CC) $(CFLAGS) citserver.o user_ops.o room_ops.o file_ops.o support.o \
+               msgbase.o config.o sysdep.o locate_host.o serv_chat.o \
+               hooks.o housekeeping.o database.o control.o \
+               $(LFLAGS) $(SERVER_LFLAGS) -o citserver
+
+citserver.o: citserver.c citadel.h
+       $(CC) $(CFLAGS) -D_REENTRANT -c citserver.c
+
+user_ops.o: user_ops.c citadel.h
+       $(CC) $(CFLAGS) -D_REENTRANT -c user_ops.c
+
+room_ops.o: room_ops.c citadel.h
+       $(CC) $(CFLAGS) -D_REENTRANT -c room_ops.c
+
+file_ops.o: file_ops.c citadel.h
+       $(CC) $(CFLAGS) -D_REENTRANT -c file_ops.c
+
+support.o: support.c citadel.h
+       $(CC) $(CFLAGS) -D_REENTRANT -c support.c
+
+msgbase.o: msgbase.c citadel.h
+       $(CC) $(CFLAGS) -D_REENTRANT -c msgbase.c
+
+locate_host.o: locate_host.c citadel.h
+       $(CC) $(CFLAGS) -D_REENTRANT -c locate_host.c
+
+serv_chat.o: serv_chat.c citadel.h
+       $(CC) $(CFLAGS) -D_REENTRANT -c serv_chat.c
+
+hooks.o: hooks.c citadel.h
+       $(CC) $(CFLAGS) -D_REENTRANT -c hooks.c
+
+housekeeping.o: housekeeping.c citadel.h
+       $(CC) $(CFLAGS) -D_REENTRANT -c housekeeping.c
+
+database.o: database.c citadel.h
+       $(CC) $(CFLAGS) -D_REENTRANT -c database.c
+
+control.o: control.c citadel.h
+       $(CC) $(CFLAGS) -D_REENTRANT -c control.c
+
+config.o: config.c citadel.h axdefs.h
+       $(CC) -O $(CFLAGS) -D_REENTRANT -c config.c
+
+sysdep.o: sysdep.c citadel.h
+       $(CC) -O $(CFLAGS) -D_REENTRANT -c sysdep.c
+
+aidepost: aidepost.c config.o citadel.h
+       $(CC) -O $(CFLAGS) aidepost.c config.o $(LFLAGS) -o aidepost
+
+#
+# 'netmailer' needs to run setuid because it generates headers for Internet
+# mail.  If it is not run setuid, all outgoing mail may always show as coming
+# from your BBSUID rather than the actual sending user.
+#
+netmailer: netmailer.c internetmail.o config.o citadel.h
+       $(CC) -O $(CFLAGS) netmailer.c config.o internetmail.o $(LFLAGS) -o netmailer
+       chmod 4755 netmailer
+
+internetmail.o: internetmail.c
+       $(CC) -O $(CFLAGS) -c internetmail.c
+
+netproc: netproc.o config.o ipc_c_tcp.o citadel.h
+       $(CC) -O $(CFLAGS) netproc.o config.o ipc_c_tcp.o \
+               $(LFLAGS) -o netproc
+
+netproc.o: netproc.c citadel.h
+       $(CC) -O $(CFLAGS) -c netproc.c
+
+citmail: citmail.c config.o internetmail.o citadel.h
+       #
+       #                 ###### IMPORTANT ######
+       # To allow Citadel users to receive Internet mail, you must
+       # set this program to be your local mail delivery agent.
+       #
+       $(CC) -O $(CFLAGS) citmail.c config.o internetmail.o $(LFLAGS) -o citmail
+       chmod 4755 citmail
+
+mailinglist: mailinglist.c config.o internetmail.o citadel.h
+       $(CC) -O $(CFLAGS) mailinglist.c config.o internetmail.o \
+               $(LFLAGS) -o mailinglist
+
+setup: setup.c citadel.h
+       $(CC) -O $(CFLAGS) setup.c $(CURSES) $(LFLAGS) -o setup
+
+netsetup: netsetup.c config.o citadel.h
+       $(CC) -O $(CFLAGS) netsetup.c config.o $(LFLAGS) -o netsetup
+
+whobbs: whobbs.c ipc_c_tcp.o
+       $(CC) -O $(CFLAGS) whobbs.c ipc_c_tcp.o $(LFLAGS) -o whobbs
+
+useradmin: useradmin.c config.o citadel.h axdefs.h
+       $(CC) -O $(CFLAGS) useradmin.c config.o $(CURSES) $(LFLAGS) -o useradmin
+       chmod 4750 useradmin
+
+msgform: msgform.c
+       $(CC) -O $(CFLAGS) msgform.c $(LFLAGS) -o msgform
+
+msgstats: msgstats.c config.o citadel.h
+       $(CC) -O $(CFLAGS) msgstats.c config.o $(LFLAGS) -o msgstats
+
+readlog: readlog.c config.o citadel.h
+       $(CC) -O $(CFLAGS) readlog.c config.o $(LFLAGS) -o readlog
+
+rcit: rcit.c config.o citadel.h
+       $(CC) -O $(CFLAGS) rcit.c config.o $(LFLAGS) -o rcit
+       # 
+       # NOTE: TO RECEIVE CITADEL TRAFFIC VIA THE RCIT PROGRAM (ESPECIALLY
+       # IF YOU ARE GATEWAYING TO USENET) YOU MUST LINK RNEWS TO RCIT:
+       #ln -s rcit /usr/bin/rnews
+       # 
+
+stats: stats.c config.o citadel.h
+       $(CC) -O $(CFLAGS) stats.c config.o $(LFLAGS) -o stats
+
+sysoputil: sysoputil.c config.o citadel.h
+       $(CC) -O $(CFLAGS) sysoputil.c config.o $(LFLAGS) -o sysoputil
+       chmod 4750 sysoputil
+
+citadel.h: sysdep.h sysconfig.h ipcdef.h server.h
+       touch citadel.h
+
+clean:
+       find . -name \*.[o] -print -exec rm -f {} \;
+       rm -f sysdep.h
diff --git a/citadel/README.txt b/citadel/README.txt
new file mode 100644 (file)
index 0000000..eacd008
--- /dev/null
@@ -0,0 +1,183 @@
+ Citadel/UX release notes -- version 5.01 ("Scooby")
+  
+ Major overhaul.  The server is now multithreaded; you run one copy of it
+when you bring up your system, rather than having inetd start a separate
+server process for each session.
+  
+ Access level 0, which was formerly "marked for deletion," has been changed
+to "deleted."  When a user record is marked access level 0, it is then 
+considered a vacant space in the userlog rather than a user entry.  When
+new users are added to the system, the server first searches for these vacant
+slots before appending to the end of the file.
+ The setup program has been overhauled.  It can now operate in three different
+modes: a curses-based mode, a mode based on Savio Lam's "dialog" program, and
+of course dumb-terminal mode.
+ All system messages (hello, newuser, etc.) can now be edited from a client
+program.  We now also have support for graphics images, including graphics
+tied to various system objects (such as pictures of each user).
+ Much more support for server graphics is in place.  This is currently used
+in WebCit; expect other clients to do more with graphics in the future.
+ There really is too much to list here.  More documentation will be written
+as time permits.
+  
+ Citadel/UX release notes -- version 4.11
+  
+ Smarter usage of external editors.  EDITOR_EXIT is no longer needed.  Instead,
+the program examines temp files before and after they are edited, in order to
+determine whether the user saved the file.  Modified files are saved, unchanged
+files are aborted.
+  
+ External editors work for Bio and Room Info files as well.
+ The TCP/IP based client (citatcp, linked against ipc_c_tcp.o) now supports
+connection to a Citadel server from behind a SOCKS v4 firewall without having
+to "socksify" the program first.
+ Added a few more commands to the chat server.
+ Citadel/UX release notes -- version 4.10
+  
+ Floors now fully supported in both client and server.
+ Now supports "bio" files for each user - free-form text files in which 
+users may record personal info for others to browse.
+ <.G>oto and <.S>kip now have more advanced logic for partial matches.  A
+left-aligned match carries a heavier weight than a mid-string match.  For
+example, <.G> TECH would prefer "Tech Area>" over "Biotech/Ethics>".
+ Citadel/UX release notes -- version 4.03
+ There are now commands available for users with their own copy of the client
+to upload and download directly to their local disk, without having to use
+a protocol such as Zmodem.  These commands are disabled by default, but can
+easily be enabled by changing citadel.rc.
+ Multiuser <C>hat is now fully integrated into both the client and server.
+ New <P>aging functionality allows users to send each other near-real-time
+"express" messages.
+
+ Citadel/UX release notes -- version 4.02
+ The "rnews" program has been renamed to "rcit".  Its usage has not changed;
+it still accepts UseNet-style news by default.  Use the -c option to read in
+Citadel (IGnet) format data.
+  
+ The text client now compiles and works on systems using BSD-style <sgtty.h>
+in addition to SysV-style <termio.h>.  This should be a big help for many.
+ Citadel/UX release notes -- version 4.01
+ Remember to always run setup again when using a new version of the code, to
+bring your data files up to date!  No data will be lost.
+ This release is primarily to clean up loose ends and fix assorted small
+bug reports from the users of 4.00.  Also, the code is slowly being reworked
+to compile cleanly under ANSI C compilers even when warning messages are
+set to the highest level.
+  
+ -> The client now sends periodic "keep-alive" messages to the server during
+message entry, with both internal and external editors.  This should keep
+sessions from locking up when a user hits <S>ave after typing for a long time.
+ -> Stats now has a "-b" option for batch mode (see utils.doc)
+    It also has a "-p" option to only print the post-to-call ratios.
+ Citadel/UX release notes -- version 4.00
+ This is the long-awaited client/server version of Citadel.  Feedback is
+encouraged!
+ NOTE: if you are upgrading to 4.00 from a 3.2x release, you should run setup
+to bring your data files up to date.  Setup will prompt you to run the
+"conv_32_40" program; go ahead and do that.
+ If you are upgrading to 4.00 from a 3.1x release, setup will not tell you
+what to do!  Here is the correct procedure:
+  1. Run the "conv_31_32" program
+  2. Run setup
+  3. Run "conv_32_40"
+ If you are currently running a version earlier than 3.10, you must erase your
+data files and bring up a new system.
+ A new series of commands to manipluate files in a room's directory:
+   <.A>ide <F>ile <D>elete   -- delete it
+   <.A>ide <F>ile <M>ove     -- move it to another room
+   <.A>ide <F>ile <S>end     -- send it over the network
+  
+ Changed the way the main Citadel program sends network mail, both to other
+Citadels and through the RFC822 gateway.  Everything now gets fed through
+netproc, which is responsible for figuring out the routing and doing the
+actual processing.  It also gets rid of quite a bit of redundant code.
+  
+  
+ Citadel/UX release notes -- version 3.23
+ NOTE: if you are upgrading to 3.23 from another 3.2x release, you MUST run
+setup to bring your data files up to date.  Run setup and answer "no" when it
+asks you if you want to create the various files.  It will see your old files
+and bring them up to date.  (If you are doing a new installation, of course,
+you'll be creating all new files anyway.)
+ The built-in message editor has been completely rewritten, and is now the
+preferable editor to use.  It generates messages that will be formatted to
+the reader's screen width, as in other implementations of Citadel (and as
+Citadel/UX used to do until a few versions ago -- there was a need to put
+that functionality back in).
+ Users are now prompted for their screen width AND height.
+ We can now talk to C86NET compliant networks.  Get the "cit86net" package
+if you wish to do this.
+ Rooms can now optionally be "read only," which means that only Aides and
+utilities such as the networker and aidepost can post to the room.
+ Kernel-based file locking is now fully supported for the purpose of locking
+the message base during writes.  If your kernel supports the flock() system
+call, you can tell the compiler to use it by setting a flag in the Makefile.
+If your kernel supports file locking using fcntl(), this will automatically be
+detected and utilized.  If your kernel supports neither, the program will use
+a fully portable (but less reliable) file locking scheme.
+ An external editor is no longer required for the .AI command.
+  
+ Citadel/UX release notes -- version 3.22
+ The "setup" program is now a curses-based, full-screen utility that's very
+easy to use.  Of course, if you have trouble compiling curses-based programs
+on your system, you can compile it to run the old way.
+  
+   
+ Citadel/UX release notes -- version 3.21
+   
+ I'm now running my system under Linux, since that seems to be what everyone
+is using these days.  As a result, you'll find that version 3.21 will compile
+very cleanly under Linux.
+  
+    
+ Citadel/UX release notes -- version 3.20
+  
+ Lots of improvements and new features are here.  It seems that assorted
+hacks and variations of Citadel/UX have percolated around the country --
+this 3.2 release should supersede them and get everyone running (hopefully)
+off the same code.  I've looked around at the various mods people have made
+to Citadel/UX and tried to implement the most-often-added and most-requested
+features to the stock distribution.  If there's a feature you want/need that
+still isn't here, drop me a line and I'll see what I can do about adding it
+to the next release.  I can be contacted at ajc@uncnsrd.mt-kisco.ny.us or
+simply log on to my BBS at uncnsrd.mt-kisco.ny.us (internet) or 914-244-3252
+(dialup).
diff --git a/citadel/aidepost.c b/citadel/aidepost.c
new file mode 100644 (file)
index 0000000..7a9f96b
--- /dev/null
@@ -0,0 +1,66 @@
+/* aidepost.c
+ * This is just a little hack to copy standard input to a message in Aide>
+ * v1.6
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <time.h>
+#include "citadel.h"
+
+void get_config();
+struct config config;
+
+void make_message(filename)
+char *filename; {
+       FILE *fp;
+       int a;
+       long bb,cc,now;
+       time(&now);
+       fp=fopen(filename,"wb"); if (fp==NULL) exit(22);
+       putc(255,fp);
+       putc(MES_NORMAL,fp);
+       putc(1,fp);
+       fprintf(fp,"Proom_aide"); putc(0,fp);
+       fprintf(fp,"T%ld",now); putc(0,fp);
+       fprintf(fp,"ACitadel"); putc(0,fp);
+       fprintf(fp,"OAide"); putc(0,fp);
+       fprintf(fp,"N%s",NODENAME); putc(0,fp);
+       putc('M',fp);
+       bb=ftell(fp);
+       while (a=getc(stdin), a>0) {
+               if (a!=8) putc(a,fp);
+               else {
+                       cc=ftell(fp);
+                       if (cc!=bb) fseek(fp,(-1L),1);
+                       }
+               }
+       putc(0,fp);
+       putc(0,fp);
+       putc(0,fp);
+       fclose(fp);
+       }
+
+void main(argc,argv)
+int argc;
+char *argv[];
+{
+       char tempbase[32];
+       char temptmp[64];
+       char tempspool[64];
+       char movecmd[256];
+       
+       get_config();
+       sprintf(tempbase,"ap.%d",getpid());
+       sprintf(temptmp,"/tmp/%s", tempbase);
+       sprintf(tempspool,"./network/spoolin/%s", tempbase);
+       make_message(temptmp);
+
+       sprintf(movecmd, "/bin/mv %s %s", temptmp, tempspool);
+       system(movecmd);
+
+       execlp("./netproc","netproc",NULL);
+       fprintf(stderr,"aidepost: could not run netproc\n");
+       exit(1);
+       }
diff --git a/citadel/axdefs.h b/citadel/axdefs.h
new file mode 100644 (file)
index 0000000..a32254e
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef AXDEFS
+
+char *axdefs[]={
+       "Deleted",
+       "New User",
+       "Problem User",
+       "Local User",
+       "Network User",
+       "Preferred User",
+       "Aide",
+       "Sysop"
+       };
+
+#define AXDEFS 1
+
+#else
+
+extern char *axdefs[];
+
+#endif
diff --git a/citadel/citadel.c b/citadel/citadel.c
new file mode 100644 (file)
index 0000000..73c5071
--- /dev/null
@@ -0,0 +1,1340 @@
+/*
+ * Citadel/UX  
+ *
+ * citadel.c - Main source file.
+ *
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <setjmp.h>
+
+#include "citadel.h"
+#include "axdefs.h"
+
+struct march {
+       struct march *next;
+       char march_name[32];
+       char march_floor;
+       };
+
+#define IFEXPERT if (userflags&US_EXPERT)
+#define IFNEXPERT if ((userflags&US_EXPERT)==0)
+#define IFAIDE if (axlevel>=6)
+#define IFNAIDE if (axlevel<6)
+
+struct march *march = NULL;
+long finduser();
+void extract();
+long extract_long();
+int extract_int();
+void load_command_set();
+void updatelsa();
+void movefile();
+void deletefile();
+void netsendfile();
+void listzrooms();
+int ka_system();
+void interr();
+int struncmp();
+int yesno();
+void sttybbs();
+void newprompt();
+void strprompt();
+int fmout();
+int checkpagin();
+int pattern();
+int pattern2();
+void readinfo();
+int num_parms();
+void attach_to_server();
+void strproc();
+void enter_config();
+void entregis();
+int entmsg();
+void updatels();
+void forget();
+void readmsgs();
+int getcmd();
+void subshell();
+void entroom();
+void killroom();
+void invite();
+void kickout();
+void editthisroom();
+void roomdir();
+void download();
+void upload();
+void cli_upload();
+void ungoto();
+void whoknows();
+void validate();
+void enterinfo();
+void display_help();
+void edituser();
+void knrooms();
+void locate_host();
+void chatmode();
+void load_floorlist();
+void create_floor();
+void edit_floor();
+void kill_floor();
+void enter_bio();
+void read_bio();
+void misc_server_cmd();
+int nukedir();
+void color();
+void cls();
+void edit_system_message();
+void send_ansi_detect();
+void look_for_ansi();
+void cli_image_upload();
+
+
+/* globals associated with the client program */
+char temp[16];                         /* Name of general temp file */
+char temp2[16];                                /* Name of general temp file */
+char tempdir[16];                      /* Name of general temp dir */
+char editor_path[256];                 /* path to external editor */
+char printcmd[256];                    /* print command */
+int editor_pid = (-1);
+char fullname[32];
+jmp_buf nextbuf;
+struct serv_info serv_info;            /* Info on the server connected */
+int screenwidth;
+int screenheight;
+unsigned room_flags;
+char room_name[32];
+char ugname[20];
+long uglsn;                            /* holds <u>ngoto info */
+char is_mail = 0;                      /* nonzero when we're in a mail room */
+char axlevel = 0;                      /* access level */
+char is_room_aide = 0;                 /* boolean flag, 1 if room aide */
+int timescalled;
+int posted;
+unsigned userflags;
+long usernum = 0L;                     /* user number */
+char newnow;
+long highest_msg_read;                 /* used for <A>bandon room cmd */
+long maxmsgnum;                                /* used for <G>oto */
+char sigcaught = 0;
+char have_xterm = 0;                   /* are we running on an xterm? */
+char rc_username[32];
+char rc_password[32];
+char rc_floor_mode;
+char floor_mode;
+char curr_floor = 0;                   /* number of current floor */
+char floorlist[128][256];              /* names of floors */
+char express_msgs = 0;                 /* express messages waiting! */
+char last_paged[32]="";
+
+extern char server_is_local;           /* defined in ipc module */
+
+/*
+ * here is our 'clean up gracefully and exit' routine
+ */
+void logoff(code)
+int code; {
+       if (editor_pid>0) {             /* kill the editor if it's running */
+               kill(editor_pid,SIGHUP);
+               }
+
+/* shut down the server... but not if the logoff code is 3, because
+ * that means we're exiting because we already lost the server
+ */
+       if (code!=3) serv_puts("QUIT");
+
+/*
+ * 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);
+               }
+
+       sttybbs(SB_RESTORE);            /* return the old terminal settings */
+       exit(code);                     /* exit with the proper exit code */
+       }
+
+
+
+/*
+ * We handle "next" and "stop" much differently than in earlier versions.
+ * The signal catching routine simply sets a flag and returns.
+ */
+void sighandler(which_sig)
+int which_sig; {
+       signal(SIGINT,SIG_IGN);
+       signal(SIGQUIT,SIG_IGN);
+       sigcaught = which_sig;
+       return;
+       }
+
+
+/*
+ * signal catching function for hangups...
+ */
+void dropcarr() {
+       logoff(SIGHUP);
+       }
+
+
+
+/* general purpose routines */
+
+void formout(name) /* display a file */
+char name[];
+       {
+       char cmd[256];
+       sprintf(cmd,"MESG %s",name);
+       serv_puts(cmd);
+       serv_gets(cmd);
+       if (cmd[0]!='1') {
+               printf("%s\n",&cmd[4]);
+               return;
+               }
+       fmout(screenwidth,NULL,
+               ((userflags & US_PAGINATOR) ? 1 : 0),
+               screenheight,1,1);
+       }
+
+
+void userlist() { 
+       char buf[256];
+       char fl[256];
+       struct tm *tmbuf;
+       long lc;
+       int linecount = 2;
+
+       serv_puts("LIST");
+       serv_gets(buf);
+       if (buf[0]!='1') {
+               printf("%s\n",&buf[4]);
+               return;
+               }
+       sigcaught = 0;
+       sttybbs(SB_YES_INTR);
+       printf("       User Name           Num  L  LastCall  Calls Posts\n");
+       printf("------------------------- ----- - ---------- ----- -----\n");
+       while (serv_gets(buf), strcmp(buf,"000")) {
+               if (sigcaught == 0) {
+                       extract(fl,buf,0);
+                       printf("%-25s ",fl);
+                       printf("%5ld %d ",extract_long(buf,2),
+                               extract_int(buf,1));
+                       lc = extract_long(buf,3);
+                       tmbuf = (struct tm *)localtime(&lc);
+                       printf("%02d/%02d/%04d ",
+                               (tmbuf->tm_mon+1),
+                               tmbuf->tm_mday,
+                               (tmbuf->tm_year + 1900));
+                       printf("%5ld %5ld\n",extract_long(buf,4),extract_long(buf,5));
+
+                       ++linecount;
+                       linecount = checkpagin(linecount,
+                               ((userflags & US_PAGINATOR) ? 1 : 0),
+                               screenheight);
+
+                       }
+               }
+       sttybbs(SB_NO_INTR);
+       printf("\n");
+       }
+
+
+/*
+ * get info about the server we've connected to
+ */
+void get_serv_info() {
+       char buf[256];
+       int a;
+
+       /* fetch info */        
+       serv_puts("INFO");
+       serv_gets(buf);
+       if (buf[0]!='1') return;
+
+       a = 0;
+       while(serv_gets(buf), strcmp(buf,"000")) {
+           switch(a) {
+               case 0:         serv_info.serv_pid = atoi(buf);
+                               break;
+               case 1:         strcpy(serv_info.serv_nodename,buf);
+                               break;
+               case 2:         strcpy(serv_info.serv_humannode,buf);
+                               break;
+               case 3:         strcpy(serv_info.serv_fqdn,buf);
+                               break;
+               case 4:         strcpy(serv_info.serv_software,buf);
+                               break;
+               case 5:         serv_info.serv_rev_level = atoi(buf);
+                               break;
+               case 6:         strcpy(serv_info.serv_bbs_city,buf);
+                               break;
+               case 7:         strcpy(serv_info.serv_sysadm,buf);
+                               break;
+               case 9:         strcpy(serv_info.serv_moreprompt,buf);
+                               break;
+               case 10:        serv_info.serv_ok_floors = atoi(buf);
+                               break;
+               }
+           ++a;
+           }
+
+       /* be nice and identify ourself to the server */
+       sprintf(buf,"IDEN %d|%d|%d|%s%s|",
+               SERVER_TYPE,0,REV_LEVEL,CITADEL,
+               (server_is_local ? "(local)" : ""));
+       locate_host(&buf[strlen(buf)]); /* append to the end */
+       serv_puts(buf);
+       serv_gets(buf); /* we don't care about the result code */
+
+       }
+
+/*
+ * grab assorted info about the user...
+ */
+void load_user_info(params)
+char *params; {
+       extract(fullname,params,0);
+       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);
+       }
+
+
+/*
+ * 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(roomname,floornum)
+char *roomname;
+int floornum; {
+       struct march *mptr,*mptr2;
+
+       if (march==NULL) return;
+
+       if ( (!strucmp(march->march_name,roomname))
+        || ((!strucmp(roomname,"_FLOOR_"))&&(march->march_floor==floornum))) {
+               mptr = march->next;
+               free(march);
+               march = mptr;
+               return;
+               }
+
+       mptr2 = march;
+       for (mptr=march; mptr!=NULL; mptr=mptr->next) {
+
+               if ( (!strucmp(mptr->march_name,roomname))
+                  || ((!strucmp(roomname,"_FLOOR_"))
+                       &&(mptr->march_floor==floornum))) {
+
+                       mptr2->next = mptr->next;
+                       free(mptr);
+                       mptr=mptr2;
+                       }
+               else {
+                       mptr2=mptr;
+                       }
+               }
+       }
+
+/*
+ * sort the march list by floor
+ */
+void sort_march_list() {
+       struct march *mlist[129];
+       struct march *mptr = NULL;
+       int a;
+
+       if (march == NULL) return;
+
+       for (a=0; a<129; ++a) mlist[a] = NULL;
+
+       /* first, create 128 separate lists for each floor. */
+       while (march != NULL) {
+
+               a = (int)(march->march_floor);
+
+               /* assign an illegal floor number of 128 to _BASEROOM_
+                * in order to force it to show up last */      
+               if (!strucmp(march->march_name,"_BASEROOM_")) a = 128;
+
+               mptr = march;
+               march = march->next;
+               mptr->next = mlist[a];
+               mlist[a] = mptr;
+               }
+
+       /* now merge the lists, in order, into one big list, 
+        * except the current floor
+        */
+       for (a=128; a>=0; --a) if (a != curr_floor) {
+               while (mlist[a] != NULL) {
+                       mptr = mlist[a];
+                       mlist[a] = mlist[a]->next;
+                       mptr->next = march;
+                       march = mptr;
+                       }
+               }
+
+       /* now merge in rooms from the current floor */
+       while (mlist[(int)curr_floor] != NULL) {
+               mptr = mlist[(int)curr_floor];
+               mlist[(int)curr_floor] = mlist[(int)curr_floor]->next;
+               mptr->next = march;
+               march = mptr;
+               }
+
+       }
+
+
+
+/*
+ * jump directly to a room
+ */
+void dotgoto(towhere,display_name)
+char *towhere;
+int display_name; {
+       char aaa[256],bbb[256],psearch[256];
+       static long ls = 0L;
+       int newmailcount;
+       static int oldmailcount = (-1);
+       int partial_match,best_match;
+       char from_floor;
+
+       /* store ungoto information */
+       strcpy(ugname,room_name);
+       uglsn = ls;
+
+       /* first try an exact match */
+       sprintf(aaa,"GOTO %s",towhere);
+       serv_puts(aaa);
+       serv_gets(aaa);
+       if (aaa[3]=='*') express_msgs = 1;
+       if (!strncmp(aaa,"54",2)) {
+               newprompt("Enter room password: ",bbb,9);
+               sprintf(aaa,"GOTO %s|%s",towhere,bbb);
+               serv_puts(aaa);
+               serv_gets(aaa);
+               if (aaa[3]=='*') express_msgs = 1;
+               }
+       if (!strncmp(aaa,"54",2)) {
+               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.
+        */
+       if (aaa[0]!='2') {
+               best_match = 0;
+               strcpy(bbb,"");
+               serv_puts("LKRA");
+               serv_gets(aaa);
+               if (aaa[0]=='1') while (serv_gets(aaa), strcmp(aaa,"000")) {
+                       extract(psearch,aaa,0);
+                       partial_match = 0;
+                       if (pattern(psearch,towhere)>=0) {
+                               partial_match = 1;
+                               }
+                       if (!struncmp(towhere,psearch,strlen(towhere))) {
+                               partial_match = 2;
+                               }
+                       if (partial_match > best_match) {
+                               strcpy(bbb,psearch);
+                               best_match = partial_match;
+                               }
+                       }
+               if (strlen(bbb)==0) {
+                       printf("No room '%s'.\n",towhere);
+                       return;
+                       }
+               sprintf(aaa,"GOTO %s",bbb);
+               serv_puts(aaa);
+               serv_gets(aaa);
+               if (aaa[3]=='*') express_msgs = 1;
+               }
+
+       if (aaa[0]!='2') {
+               printf("%s\n",aaa);
+               return;
+               }
+
+       extract(room_name,&aaa[4],0);
+       room_flags = extract_int(&aaa[4],4);
+       from_floor = curr_floor;
+       curr_floor = extract_int(&aaa[4],10);
+
+       remove_march(room_name,0);
+       if (!strucmp(towhere,"_BASEROOM_")) remove_march(towhere,0);
+       if ((from_floor!=curr_floor) && (display_name>0) && (floor_mode==1)) {
+               if (floorlist[(int)curr_floor][0]==0) load_floorlist();
+               printf("(Entering floor: %s)\n",&floorlist[(int)curr_floor][0]);
+               }
+       if (display_name == 1) printf("%s - ",room_name);
+       if (display_name != 2) printf("%d new of %d messages.\n",
+               extract_int(&aaa[4],1),
+               extract_int(&aaa[4],2));
+       highest_msg_read = extract_int(&aaa[4],6);
+       maxmsgnum = extract_int(&aaa[4],5);
+       is_mail = (char) extract_int(&aaa[4],7);
+       is_room_aide = (char) extract_int(&aaa[4],8);
+       ls = extract_long(&aaa[4],6);
+
+       /* read info file if necessary */
+       if (extract_int(&aaa[4],3) > 0) readinfo();
+
+       /* check for newly arrived mail if we can */
+       if (num_parms(&aaa[4])>=10) {
+               newmailcount = extract_int(&aaa[4],9);
+               if ( (oldmailcount >= 0) && (newmailcount > oldmailcount) )
+                       printf("*** You have new mail\n");
+               oldmailcount = 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.
+ * We start the search in the current room rather than the beginning to prevent
+ * two or more concurrent users from dragging each other back to the same room.
+ */
+void gotonext() {
+       char buf[256];
+       struct march *mptr,*mptr2;
+       char next_room[32];
+
+       /* First 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 (march==NULL) {
+               serv_puts("LKRN");
+               serv_gets(buf);
+               if (buf[0]=='1')
+                   while (serv_gets(buf), strcmp(buf,"000")) {
+                       mptr = (struct march *) malloc(sizeof(struct march));
+                       mptr->next = NULL;
+                       extract(mptr->march_name,buf,0);
+                       mptr->march_floor = (char) (extract_int(buf,2) & 0x7F);
+                       if (march==NULL) {
+                               march = mptr;
+                               }
+                       else {
+                               mptr2 = march;
+                               while (mptr2->next != NULL)
+                                       mptr2 = mptr2->next;
+                               mptr2->next = mptr;
+                               }
+                       }
+
+/* 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;
+               strcpy(mptr->march_name,"_BASEROOM_");
+               if (march==NULL) {
+                       march = mptr;
+                       }
+               else {
+                       mptr2 = march;
+                       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);
+               }
+
+       sort_march_list();
+
+       if (march!=NULL) {
+               strcpy(next_room,march->march_name);
+               }
+       else {
+               strcpy(next_room,"_BASEROOM_");
+               }
+       remove_march(next_room,0);
+       dotgoto(next_room,1);
+   }
+
+/*
+ * forget all rooms on a given floor
+ */
+void forget_all_rooms_on(ffloor)
+int ffloor; {
+       char buf[256];
+       struct march *flist,*fptr;
+
+       printf("Forgetting all rooms on %s...\r",&floorlist[ffloor][0]);
+       fflush(stdout);
+       sprintf(buf,"LKRA %d",ffloor);
+       serv_puts(buf);
+       serv_gets(buf);
+       if (buf[0]!='1') {
+               printf("%-72s\n",&buf[4]);
+               return;
+               }
+       flist = NULL;
+       while (serv_gets(buf), strcmp(buf,"000")) {
+               fptr = (struct march *) malloc(sizeof(struct march));
+               fptr->next = flist;
+               flist = fptr;
+               extract(fptr->march_name,buf,0);
+               }
+       while (flist != NULL) {
+               sprintf(buf,"GOTO %s",flist->march_name);
+               serv_puts(buf);
+               serv_gets(buf);
+               if (buf[0]=='2') {
+                       serv_puts("FORG");
+                       serv_gets(buf);
+                       }
+               fptr = flist;
+               flist = flist->next;
+               free(fptr);
+               }
+       printf("%-72s\r","");
+       }
+
+
+/*
+ * routine called by gotofloor() to move to a new room on a new floor
+ */
+void gf_toroom(towhere,mode)
+char *towhere;
+int mode; {
+       int floor_being_left;
+
+       floor_being_left = curr_floor;
+
+       if (mode == GF_GOTO) {          /* <;G>oto mode */
+               updatels();
+               dotgoto(towhere,1);
+               }
+
+       if (mode == GF_SKIP) {          /* <;S>kip mode */
+               dotgoto(towhere,1);
+               remove_march("_FLOOR_",floor_being_left);
+               }
+
+       if (mode == GF_ZAP) {           /* <;Z>ap mode */
+               dotgoto(towhere,1);
+               remove_march("_FLOOR_",floor_being_left);
+               forget_all_rooms_on(floor_being_left);
+               }
+       }
+
+
+/*
+ * go to a new floor
+ */
+void gotofloor(towhere,mode)
+char *towhere;
+int mode; {
+       int a,tofloor;
+       struct march *mptr;
+       char buf[256],targ[256];
+
+       if (floorlist[0][0]==0) load_floorlist();
+       tofloor = (-1);
+       for (a=0; a<128; ++a) if (!strucmp(&floorlist[a][0],towhere))
+               tofloor = a;
+
+       if (tofloor<0) {
+               for (a=0; a<128; ++a) {
+                   if (!struncmp(&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) {
+               printf("No floor '%s'.\n",towhere);
+               return;
+               }
+
+       for (mptr = march; mptr != NULL; mptr = mptr->next) {
+               if ((mptr->march_floor) == tofloor) 
+                       gf_toroom(mptr->march_name,mode);
+                       return;
+               }
+
+       strcpy(targ,"");
+       sprintf(buf,"LKRA %d",tofloor);
+       serv_puts(buf);
+       serv_gets(buf);
+       if (buf[0]=='1') while (serv_gets(buf), strcmp(buf,"000")) {
+               if ((extract_int(buf,2)==tofloor)&&(strlen(targ)==0))
+                        extract(targ,buf,0);
+               }
+       if (strlen(targ)>0) {
+               gf_toroom(targ,mode);
+               }
+       else {
+               printf("There are no rooms on '%s'.\n",&floorlist[tofloor][0]);
+               }
+       }
+
+
+/*
+ * forget all rooms on current floor
+ */
+void forget_this_floor() {
+       
+       if (curr_floor == 0) {
+               printf("Can't forget this floor.\n");
+               return;
+               }
+
+       if (floorlist[0][0]==0) load_floorlist();
+       printf("Are you sure you want to forget all rooms on %s? ",
+               &floorlist[(int)curr_floor][0]);
+       if (yesno()==0) return;
+
+       gf_toroom("_BASEROOM_",GF_ZAP); 
+       }
+
+
+/* 
+ * Figure out the physical screen dimensions, if we can
+ */
+void check_screen_dims() {
+#ifdef TIOCGWINSZ
+       struct {
+               unsigned short height;          /* rows */
+               unsigned short width;           /* columns */
+               unsigned short xpixels;
+               unsigned short ypixels;         /* pixels */
+       } xwinsz;
+
+       if (have_xterm) {       /* dynamically size screen if on an xterm */
+               if ( ioctl(0, TIOCGWINSZ, &xwinsz) == 0 ) {
+                       if (xwinsz.height) screenheight = (int) xwinsz.height;
+                       if (xwinsz.width) screenwidth = (int) xwinsz.width;
+                       }
+               }
+#endif
+       }
+
+
+/*
+ * set floor mode depending on client, server, and user settings
+ */
+void set_floor_mode() {
+       if (serv_info.serv_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() {
+       char pass1[20];
+       char pass2[20];
+       char buf[256];
+
+       if (strlen(rc_password) > 0) {
+               strcpy(pass1,rc_password);
+               strcpy(pass2,rc_password);
+               }
+       else {
+               IFNEXPERT formout("changepw");
+               newprompt("Enter a new password: ", pass1, -19);
+               newprompt("Enter it again to confirm: ", pass2, -19);
+               }
+       if (!strucmp(pass1,pass2)) {
+               sprintf(buf,"SETP %s",pass1);
+               serv_puts(buf);
+               serv_gets(buf);
+               printf("%s\n",&buf[4]);
+               return(0);
+               }
+       else {
+               printf("*** They don't match... try again.\n");
+               return(1);
+               }
+       }
+
+
+/*
+ * Display list of users currently logged on to the server
+ */
+void who_is_online(int longlist) 
+{
+       char buf[128], username[128], roomname[128], fromhost[128], flags[128];
+       char tbuf[128], timestr[128], idlebuf[128];
+       long timenow=0;
+       long idletime;
+       
+       printf("FLG ###        User Name                 Room                 From host\n");
+       printf("--- --- ------------------------- -------------------- ------------------------\n");
+       if (longlist)
+       {
+          serv_puts("TIME");
+          serv_gets(tbuf);
+          extract(timestr, tbuf, 1);
+          timenow = atol(timestr);
+       }
+       serv_puts("RWHO");
+       serv_gets(buf);
+       if (buf[0]=='1') 
+       {
+               while(serv_gets(buf), strcmp(buf,"000")) 
+               {
+                       extract(username,buf,1);
+                       extract(roomname,buf,2);
+                       extract(fromhost,buf,3);
+                       extract(idlebuf, buf,5);
+                       extract(flags,buf,7);
+                       printf("%-3s %-3d %-25s %-20s %-24s\n",
+                               flags, extract_int(buf,0), username,
+                               roomname, fromhost);
+                       if (longlist)
+                       {
+                          idletime = atol(idlebuf);
+                          printf("Idle time: %ld seconds\n", timenow-idletime);
+                       }
+               }
+       }
+}
+
+void enternew(char *desc, char *buf, int maxlen)
+{
+   char bbb[128];
+   sprintf(bbb, "Enter in your new %s: ", desc);
+   newprompt(bbb, buf, maxlen);
+}
+
+/*
+ * main
+ */
+void main(argc,argv)
+int argc;
+char *argv[]; {
+int a,b,mcmd;
+int termn8 = 0;
+char aaa[100],bbb[100],ccc[100],eee[100];      /* general purpose variables */
+char argbuf[32];                               /* command line buf */
+
+
+load_command_set();            /* parse the citadel.rc file */
+sttybbs(SB_SAVE);              /* Store the old terminal parameters */
+sttybbs(SB_NO_INTR);           /* Install the new ones */
+signal(SIGINT,SIG_IGN);
+signal(SIGQUIT,SIG_IGN);
+signal(SIGHUP,dropcarr);       /* Cleanup gracefully if carrier is dropped */
+signal(SIGTERM,dropcarr);      /* Cleanup gracefully if terminated */
+
+send_ansi_detect();
+printf("Attaching to server...\r");
+fflush(stdout);
+attach_to_server(argc,argv);
+
+cls();
+color(7);
+serv_gets(aaa);
+if (aaa[0]!='2') {
+       printf("%s\n",&aaa[4]);
+       logoff(atoi(aaa));
+       }
+get_serv_info();
+
+printf("%-22s\n%s\n%s\n",serv_info.serv_software,serv_info.serv_humannode,
+       serv_info.serv_bbs_city);
+screenwidth = 80;              /* default screen dimensions */
+screenheight = 24;
+
+printf(" pause    next    stop\n");
+printf(" ctrl-s  ctrl-o  ctrl-c\n\n");
+formout("hello");              /* print the opening greeting */
+printf("\n");
+
+       /* if we're not the login shell, try auto-login */
+if (getppid()!=1) {
+       serv_puts("AUTO");
+       serv_gets(aaa);
+       if (aaa[0]=='2') {
+               load_user_info(&aaa[4]);
+               goto PWOK;
+               }
+       }
+
+look_for_ansi();
+
+GSTA:  termn8=0; newnow=0;
+       do {
+               if (strlen(rc_username) > 0) {
+                       strcpy(fullname,rc_username);
+                       }
+               else {
+                       newprompt("Enter your name: ",fullname,29);
+                       }
+               strproc(fullname); 
+               if (!strucmp(fullname,"new")) {         /* just in case */
+                  printf("Please enter the name you wish to log in with.\n");
+                  }
+               } while( 
+                       (!strucmp(fullname,"bbs"))
+                       || (!strucmp(fullname,"new"))
+                       || (strlen(fullname)==0) );
+
+       if (!strucmp(fullname,"off")) {
+               mcmd=29;
+               goto TERMN8;
+               }
+
+       /* sign on to the server */
+       sprintf(aaa,"USER %s",fullname);
+       serv_puts(aaa);
+       serv_gets(aaa);
+       if (aaa[0]!='3') goto NEWUSR;
+
+       /* password authentication */
+       if (strlen(rc_password)>0) {
+               strcpy(eee,rc_password);
+               }
+       else {
+               newprompt("\rPlease enter your password: ",eee,-19);
+               }
+       strproc(eee);
+       sprintf(aaa,"PASS %s",eee);
+       serv_puts(aaa);
+       serv_gets(aaa);
+       if (aaa[0]=='2') {
+               load_user_info(&aaa[4]);
+               goto PWOK;
+               }
+       
+       printf("<< wrong password >>\n");
+       if (strlen(rc_password)>0) logoff(0);
+       goto GSTA;
+
+NEWUSR:        if (strlen(rc_password)==0) {
+               printf("No record. Enter as new user? ");
+               if (yesno()==0) goto GSTA;
+               }
+
+       sprintf(aaa,"NEWU %s",fullname);
+       serv_puts(aaa);
+       serv_gets(aaa);
+       if (aaa[0]!='2') {
+               printf("%s\n",aaa);
+               goto GSTA;
+               }
+       load_user_info(&aaa[4]);
+
+       while (set_password() != 0) ;;
+       newnow=1;
+
+       enter_config(1);
+       
+
+PWOK:  printf("%s\nAccess level: %d (%s)\nUser #%ld / Call #%d\n",
+               fullname,axlevel,axdefs[(int)axlevel],
+               usernum,timescalled);
+
+       serv_puts("CHEK");
+       serv_gets(aaa);
+       if (aaa[0]=='2') {
+               b = extract_int(&aaa[4],0);
+               if (b==1) printf("*** You have a new private message in Mail>\n");
+               if (b>1)  printf("*** You have %d new private messages in Mail>\n",b);
+
+               if ((axlevel>=6) && (extract_int(&aaa[4],2)>0)) {
+                       printf("*** Users need validation\n");
+                       }
+
+               if (extract_int(&aaa[4],1)>0) {
+                       printf("*** Please register.\n");
+                       formout("register");
+                       entregis();
+                       }
+               }
+
+       /* 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. */
+       sprintf(temp,"/tmp/citA%d",getpid());
+       sprintf(temp2,"/tmp/citB%d",getpid());
+       sprintf(tempdir,"/tmp/citC%d",getpid());
+
+       /* Get screen dimensions.  First we go to a default of 80x24.  Then
+        * we try to get the user's actual screen dimensions off the server.
+        * However, if we're running on an xterm, all this stuff is
+        * irrelevant because we're going to dynamically size the screen
+        * during the session.
+        */
+       screenwidth = 80;
+       screenheight = 24;
+       serv_puts("GETU");
+       serv_gets(aaa);
+       if (aaa[0]=='2') {
+               screenwidth = extract_int(&aaa[4],0);
+               screenheight = extract_int(&aaa[4],1);
+               }
+       if (getenv("TERM")!=NULL) if (!strcmp(getenv("TERM"),"xterm")) {
+               have_xterm = 1;
+               }
+#ifdef TIOCGWINSZ
+       check_screen_dims();
+#endif
+
+       set_floor_mode();
+
+
+       /* Enter the lobby */
+       dotgoto("_BASEROOM_",1);
+
+/* Main loop for the system... user is logged in. */
+       strcpy(ugname,"");
+       uglsn = 0L;
+
+       if (newnow==1)  readmsgs(3,1,5);
+               else    readmsgs(1,1,0);
+
+do {   /* MAIN LOOP OF PROGRAM */
+       signal(SIGINT,SIG_IGN);
+       signal(SIGQUIT,SIG_IGN);
+       mcmd=getcmd(argbuf);
+
+#ifdef TIOCGWINSZ
+       check_screen_dims();
+#endif
+
+       if (termn8==0) switch(mcmd) {
+          case 1:      formout("help");
+                       break;
+          case 4:      entmsg(0,0);
+                       break;
+          case 36:     entmsg(0,1);
+                       break;
+          case 46:     entmsg(0,2);
+                       break;
+          case 78:     newprompt("What do you want your username to be? ", aaa, 32);
+                       sprintf(bbb, "ENT0 2|0|0|0|%s", aaa);
+                       serv_puts(bbb);
+                       serv_gets(aaa);
+                       if (strncmp("200", aaa, 3))
+                          printf("\n%s\n", aaa);
+                       else
+                          entmsg(0, 0);
+                       break;
+          case 5:      updatels();
+                       gotonext();
+                       break;
+          case 47:     updatelsa();
+                       gotonext();
+                       break;
+          case 58:     updatelsa();
+                       dotgoto("_MAIL_",1);
+                       break;
+          case 20:     updatels();
+          case 52:     dotgoto(argbuf,0);
+                       break;
+          case 10:     readmsgs(0,1,0);
+                       break;
+          case 9:      readmsgs(3,1,5);
+                       break;
+          case 13:     readmsgs(1,1,0);
+                       break;
+          case 11:     readmsgs(0,(-1),0);
+                       break;
+          case 12:     readmsgs(2,(-1),0);
+                       break;
+          case 71:     readmsgs(3, 1, atoi(argbuf));
+                       break;
+          case 7:      forget();       break;
+          case 18:     subshell();     break;
+          case 38:     updatels();
+                       entroom();      break;
+          case 22:     killroom();     break;
+          case 32:     userlist();     break;
+          case 27:     invite();       break;
+          case 28:     kickout();      break;
+          case 23:     editthisroom(); break;
+          case 14:     roomdir();      break;
+          case 33:     download(0);    break;
+          case 34:     download(1);    break;
+          case 31:     download(2);    break;
+          case 43:     download(3);    break;
+          case 45:     download(4);    break;
+          case 55:     download(5);    break;
+          case 39:     upload(0);      break;
+          case 40:     upload(1);      break;
+          case 42:     upload(2);      break;
+          case 44:     upload(3);      break;
+          case 57:     cli_upload();   break;
+          case 16:     ungoto();       break;
+          case 24:     whoknows();     break;
+          case 26:     validate();     break;
+          case 29:     updatels();
+                       termn8=1;
+                       break;
+          case 30:     updatels();
+                       termn8=1;
+                       break;
+          case 48:     enterinfo();
+                       break;
+          case 49:     readinfo();
+                       break;
+          case 72:     cli_image_upload("_userpic_");
+                       break;
+          case 73:     cli_image_upload("_roompic_");
+                       break;
+
+case 74:
+       sprintf(aaa, "_floorpic_|%d", curr_floor);
+       cli_image_upload(aaa);
+       break;
+       
+case 75:
+       enternew("roomname", aaa, 20);
+       sprintf(bbb, "RCHG %s", aaa);
+       serv_puts(bbb);
+       serv_gets(aaa);
+       if (strncmp("200",aaa, 3))
+          printf("\n%s\n", aaa);
+       break;
+case 76:
+       enternew("hostname", aaa, 25);
+       sprintf(bbb, "HCHG %s", aaa);
+       serv_puts(bbb);
+       serv_gets(aaa);
+       if (strncmp("200",aaa, 3))
+          printf("\n%s\n", aaa);
+       break;
+case 77:
+       enternew("username", aaa, 32);
+       sprintf(bbb, "UCHG %s", aaa);
+       serv_puts(bbb);
+       serv_gets(aaa);
+       if (strncmp("200",aaa, 3))
+          printf("\n%s\n", aaa);
+       break;
+
+case 35:
+       set_password();
+       break;
+
+case 21:
+       if (argbuf[0]==0) strcpy(aaa,"?");
+       display_help(argbuf);
+       break;
+
+case 41:
+       formout("register");
+       entregis();
+       break;
+
+case 15:
+       printf("Are you sure (y/n)? ");
+       if (yesno()==1) {
+               updatels();
+               a=0;
+               termn8=1;
+               }
+       break;
+
+case 6:
+       gotonext();
+       break;
+
+case 3:        chatmode();
+       break;
+
+case 2: if (server_is_local) {
+               sttybbs(SB_RESTORE);
+sprintf(aaa,"USERNAME=\042%s\042; export USERNAME; exec ./subsystem %ld %d %d",
+                       fullname,
+                       usernum,screenwidth,axlevel);
+               ka_system(aaa);
+               sttybbs(SB_NO_INTR);
+               }
+       else {
+               printf("*** Can't run doors when server is not local.\n");
+               }
+       break;
+
+case 17:
+       who_is_online(0);
+       break;
+
+case 79:
+       who_is_online(1);
+       break;
+
+case 50:
+       enter_config(2);
+       break;
+
+case 37:
+       enter_config(0);
+       set_floor_mode();
+       break;
+
+case 59:
+       enter_config(3);
+       set_floor_mode();
+       break;
+
+case 60:
+       gotofloor(argbuf,GF_GOTO);
+       break;
+       
+case 61:
+       gotofloor(argbuf,GF_SKIP);
+       break;
+       
+case 62:
+       forget_this_floor();
+       break;
+
+case 63:
+       create_floor();
+       break;
+
+case 64:
+       edit_floor();
+       break;
+
+case 65:
+       kill_floor();
+       break;
+
+case 66:
+       enter_bio();
+       break;
+
+case 67:
+       read_bio();
+       break;
+
+case 25:
+       edituser();
+       break;
+
+case 8:
+       knrooms(floor_mode);
+       printf("\n");
+       break;
+
+case 68:
+       knrooms(2);
+       printf("\n");
+       break;
+
+case 69:
+       misc_server_cmd(argbuf);
+       break;
+
+case 70:
+       edit_system_message(argbuf);
+       break;
+
+case 19:
+       listzrooms();
+       printf("\n");
+       break;
+
+case 51:
+       deletefile();
+       break;
+
+case 53:
+       netsendfile();
+       break;
+
+case 54:
+       movefile();
+       break;
+
+case 56:
+        if (last_paged[0])
+           sprintf(bbb, "Page who [%s]? ", last_paged);
+        else
+           sprintf(bbb, "Page who? ");
+       newprompt(bbb,aaa,30);
+       if (!aaa[0])
+          strcpy(aaa, last_paged);
+       strproc(aaa);
+       newprompt("Message:  ",bbb,69);
+       sprintf(ccc,"SEXP %s|%s",aaa,bbb);
+       serv_puts(ccc);
+       serv_gets(ccc);
+       if (!strncmp("200", ccc, 3))
+          strcpy(last_paged, aaa);
+       printf("%s\n",&ccc[4]);
+       break;
+
+       } /* end switch */
+    } while(termn8==0);
+
+TERMN8:        printf("%s logged out.\n",fullname);
+       while (march!=NULL) remove_march(march->march_name,0);
+       if (mcmd==30)
+               printf("\n\nType 'off' to hang up, or next user...\n");
+       sprintf(aaa,"LOUT");
+       serv_puts(aaa);
+       serv_gets(aaa);
+       if ((mcmd==29)||(mcmd==15)) {
+               formout("goodbye");
+               logoff(0);
+               }
+       goto GSTA;
+
+} /* end main() */
+
diff --git a/citadel/citadel.h b/citadel/citadel.h
new file mode 100644 (file)
index 0000000..2ed5733
--- /dev/null
@@ -0,0 +1,252 @@
+/* citadel.h
+ * main Citadel/UX header file
+ * see copyright.doc for copyright information
+ */
+
+/* system customizations are in sysconfig.h */
+#include "sysdep.h"
+#include "sysconfig.h"
+#include "ipcdef.h"
+#define CITADEL        "Citadel/UX 5.02"
+#define REV_LEVEL 501
+#define SERVER_TYPE 0  /* zero for stock Citadel/UX; other developers please
+                          obtain SERVER_TYPE codes for your implementations */
+
+#undef tolower
+#define tolower(x)     ( ((x>='A')&&(x<='Z')) ? (x+'a'-'A') : x )
+#define strucmp(lstr,rstr) struncmp(lstr,rstr,32767)
+#define NEW_CONFIG
+
+/* 
+ * The only typedef we do is an 8-bit unsigned, for screen dimensions.
+ * All other defs are done using standard C types.  The code assumes that
+ * 'int' 'unsigned' and 'short' are at least 16 bits, and that 'long' is at
+ * least 32 bits.  There are no endian dependencies in any of the Citadel
+ * programs.
+ */
+typedef unsigned char CIT_UBYTE;
+
+struct config {
+       char c_nodename[16];            /* UUCP and Citadel nodename        */
+       char c_fqdn[64];                /* Fully Qualified Domain Name      */
+       char c_humannode[21];           /* Long name of system              */
+       char c_phonenum[16];            /* Dialup number of system          */
+       int c_bbsuid;                   /* UID of the bbs-only user         */
+       int c_pwcrypt;                  /* password encryption seed         */
+       char c_creataide;               /* room creator = room aide  flag   */
+       int c_sleeping;                 /* watchdog timer setting           */
+       char c_initax;                  /* initial access level             */
+       char c_regiscall;               /* call number to register on       */
+       char c_twitdetect;              /* twit detect flag                 */
+       char c_twitroom[20];            /* twit detect msg move to room     */
+       int c_defent;                   /* command generated by <E> key     */
+       char c_moreprompt[80];          /* paginator prompt                 */
+       char c_restrict;                /* restrict Internet mail flag      */
+       long c_msgbase;                 /* size of message base             */
+       char c_bbs_city[32];            /* city and state you are located in*/
+       char c_sysadm[26];              /* name of system administrator     */
+       char c_bucket_dir[15];          /* bit bucket for files...          */
+       int c_setup_level;              /* what rev level we've setup to    */
+       int c_maxsessions;              /* maximum concurrent sessions      */
+       char c_net_password[20];        /* system net password              */
+       int c_port_number;              /* TCP port to run the server on    */
+       int c_ipgm_secret;              /* Internal program authentication  */
+       };
+
+#define NODENAME               config.c_nodename
+#define FQDN                   config.c_fqdn
+#define HUMANNODE              config.c_humannode
+#define PHONENUM               config.c_phonenum
+#define BBSUID                 config.c_bbsuid
+#define PWCRYPT                        config.c_pwcrypt
+#define CREATAIDE              config.c_creataide
+#define INITAX                 config.c_initax
+#define REGISCALL              config.c_regiscall
+#define TWITDETECT             config.c_twitdetect
+#define TWITROOM               config.c_twitroom
+#define MORE_PROMPT            config.c_moreprompt
+#define RESTRICT_INTERNET      config.c_restrict
+#define MM_FILELEN             config.c_msgbase
+
+struct usersupp {                      /* User record                      */
+       int USuid;                      /* userid (==BBSUID for bbs only)   */
+       char password[20];              /* password (for BBS-only users)    */
+       long lastseen[MAXROOMS];        /* Last message seen in each room   */
+       char generation[MAXROOMS];      /* Generation # (for private rooms) */
+       char forget[MAXROOMS];          /* Forgotten generation number      */
+       long mailnum[MAILSLOTS];        /* Message #'s of each mail message */
+       long mailpos[MAILSLOTS];        /* Disk positions of each mail      */
+       unsigned flags;                 /* See US_ flags below              */
+       int timescalled;                /* Total number of logins           */
+       int posted;                     /* Number of messages posted (ever) */
+       char fullname[26];              /* Name for Citadel messages & mail */
+       char axlevel;                   /* Access level                     */
+       CIT_UBYTE USscreenwidth;        /* Screen width                     */
+       CIT_UBYTE USscreenheight;       /* Screen height                    */
+       char spare[1];                  /* spare bytes in the user account  */
+       long usernum;                   /* Eternal (non-recycled) user num  */
+       long lastcall;                  /* Last time the user called        */
+       char USname[30];                /* Real name (i.e. not a handle)    */
+       char USaddr[25];                /* Street address                   */
+       char UScity[15];                /* Municipality                     */
+       char USstate[3];                /* State or province                */
+       char USzip[10];                 /* ZIP code                         */
+       char USphone[11];               /* Voice telephone number           */
+       char USemail[32];               /* E-mail address (elsewhere)       */
+       int logged_time;                /* (Not yet implemented)            */
+       int time_limit;                 /* (Not yet implemented)            */
+       };
+
+
+/* this is a mask for all of the bits the user is allowed to change */
+#define US_USER_SET    (US_LASTOLD | US_EXPERT | US_UNLISTED | \
+                       US_NOPROMPT | US_DISAPPEAR | US_PAGINATOR | US_FLOORS)
+
+/****************************************************************************
+ * This is the control record for the message base... 
+ */
+struct CitControl {
+       long MMhighest;                 /* highest message number in file   */
+       unsigned MMflags;               /* Global system flags              */
+       long MMnextuser;                /* highest user number on system    */
+       };
+
+/* Bits which may appear in CitControl.MMflags.  Note that these don't
+ * necessarily pertain to the message base -- it's just a good place to
+ * store any global flags.
+ */
+#define MM_VALID       4               /* New users need validating        */
+
+/****************************************************************************
+ * Information returned when a message is written to the message base
+ */
+struct smreturn {                      /* Return from the send_message()   */
+       long smnumber;                  /* Message number (if sent)         */
+       long smpos;                     /* Position in file (if sent)       */
+       int smerror;                    /* Error code                       */
+       };
+
+/****************************************************************************
+ * Room records
+ */
+struct quickroom {
+       char QRname[20];                /* Max. len is 19, plus null term   */
+       char QRpasswd[10];              /* Only valid if it's a private rm  */
+       long QRroomaide;                /* User number of room aide         */
+       long QRhighest;                 /* Highest message NUMBER in room   */
+       char QRgen;                     /* Generation number of room        */
+       unsigned QRflags;               /* See flag values below            */
+       char QRdirname[15];             /* Directory name, if applicable    */
+       long QRinfo;                    /* Info file update relative to msgs*/
+       char QRfloor;                   /* Which floor this room is on      */
+       };
+
+
+/* Private rooms are always flagged with QR_PRIVATE. If neither QR_PASSWORDED
+ * or QR_GUESSNAME is set, then it is invitation-only. Passworded rooms are
+ * flagged with both QR_PRIVATE and QR_PASSWORDED while guess-name rooms are
+ * flagged with both QR_PRIVATE and QR_GUESSNAME. DO NOT set all three flags.
+ *
+ ****************************************************************************/
+
+struct fullroom {
+       long FRnum[MSGSPERRM];          /* Message NUMBERS                  */
+       long FRpos[MSGSPERRM];          /* Message POSITIONS in master file */
+               };
+
+/* This structure is not 'circular'. When scrolling, each message moves
+ * down a slot, and the oldest one falls off the bottom. A null message is 
+ * represented by the value 0L in both fields.
+ */
+
+struct calllog {
+       char CLfullname[30];            /* Name of user                     */
+       long CLtime;                    /* Date/time of record              */
+       unsigned CLflags;               /* Info on record                   */
+       };
+
+#define CL_CONNECT     8               /* Connect to server                */
+#define CL_LOGIN       16              /* CLfullname logged in             */
+#define CL_NEWUSER     32              /* CLfullname is a new user         */
+#define CL_BADPW       64              /* Bad attempt at CLfullname's pw   */
+#define CL_TERMINATE   128             /* Logout - proper termination      */
+#define CL_DROPCARR    256             /* Logout - dropped carrier         */
+#define CL_SLEEPING    512             /* Logout - sleeping                */
+#define CL_PWCHANGE    1024            /* CLfullname changed passwords     */
+
+/* Miscellaneous                                                            */
+
+#define MES_NORMAL     65              /* Normal message                   */
+#define MES_ANON       66              /* "****" header                    */
+#define MES_AN2                67              /* "Anonymous" header               */
+
+#define M_ERROR                (-1)    /* Can't send message due to bad address   */
+#define M_LOCAL                0       /* Local message, do no network processing */
+#define M_INTERNET     1       /* Convert msg and send as Internet mail   */
+#define M_BINARY       2       /* Process recipient and send via C/UX net */
+
+/****************************************************************************/
+
+struct recentmsg {
+       char RMnodename[10];
+       long RMnum;                     /* Number or time of message        */
+       };
+
+
+/****************************************************************************
+ *
+ * Floor record.  The floor number is implicit in its location in the file.
+ */
+struct floor {
+       unsigned short f_flags;         /* flags */
+       char f_name[256];               /* name of floor */
+       int f_ref_count;                /* reference count */
+       int f_reserved;                 /* for future use */
+       };
+
+#define F_INUSE                1               /* floor is in use */
+
+
+/****************************************************************************
+ * Values used internally for function call returns, etc.
+ */
+
+#define NEWREGISTER    0               /* new user to register */
+#define REREGISTER     1               /* existing user reregistering */
+
+#define READ_HEADER    2
+#define READ_MSGBODY   3
+
+/* commands we can send to the sttybbs() routine */
+#define SB_NO_INTR     0               /* set to bbs mode, i/q disabled */
+#define SB_YES_INTR    1               /* set to bbs mode, i/q enabled */
+#define SB_SAVE                2               /* save settings */
+#define SB_RESTORE     3               /* restore settings */
+
+#define        NEXT_KEY        15
+#define STOP_KEY       3
+
+/* server exit codes */
+#define EXIT_NORMAL    0               /* server terminated normally */
+                                       /* 1 through 63 reserved for signals */
+#define EXIT_NULL      64              /* EOF on server command input */
+
+/* citadel.rc stuff */
+#define RC_NO          0               /* always no */
+#define RC_YES         1               /* always yes */
+#define RC_DEFAULT     2               /* setting depends on user config */
+
+/* keepalives */
+#define KA_NO          0               /* no keepalives */
+#define KA_YES         1               /* full keepalives */
+#define KA_CHAT                2               /* half keepalives (for chat mode) */
+
+/* for <;G>oto and <;S>kip commands */
+#define GF_GOTO                0               /* <;G>oto floor mode */
+#define GF_SKIP                1               /* <;S>kip floor mode */
+#define GF_ZAP         2               /* <;Z>ap floor mode */
+
+/* message transfer formats */
+#define MT_CITADEL     0               /* Citadel proprietary */
+#define MT_RFC822      2               /* RFC822 */
+#define MT_RAW         3               /* IGnet raw format */
diff --git a/citadel/citadel.lsm b/citadel/citadel.lsm
new file mode 100644 (file)
index 0000000..9dc8d86
--- /dev/null
@@ -0,0 +1,17 @@
+Begin3
+Title:          Citadel/UX
+Version:        5.01
+Entered-date:  Tue Jun  2 11:25:59 EDT 1998
+Description:    An implementation of the Citadel BBS program for Unix systems.
+               This is the de facto standard Unix version of Citadel, and is
+                now an advanced client/server application.
+Keywords:       Citadel Citadel/UX UNIX POSIX room-based BBS IGnet IGnet/Open
+Author:         Art Cancro <ajc@uncnsrd.mt-kisco.ny.us>
+Maintained-by:  Art Cancro <ajc@uncnsrd.mt-kisco.ny.us>
+Primary-site:  uncnsrd.mt-kisco.ny.us
+Alternate-site:        ftp.tux.org
+Original-site: uncnsrd.mt-kisco.ny.us
+Platform:      Any reasonably compliant POSIX (Unix, Linux, etc.) type
+               system with a TCP/IP stack and POSIX Threads (pthreads).
+Copying-Policy: GPL
+End
diff --git a/citadel/citadel.rc b/citadel/citadel.rc
new file mode 100644 (file)
index 0000000..7501853
--- /dev/null
@@ -0,0 +1,207 @@
+#
+# citadel.rc
+#
+# This file allows full customization of the user interface.
+#
+# The standard client looks for this file in:
+# 1. $HOME/.citadelrc
+# 2. /usr/local/lib/citadel.rc
+# 3. <compiled BBSDIR>/citadel.rc
+
+# Set EDITOR to the name of an external editor to be used for entering
+# messages.  If you want the external editor to be used by default, be sure
+# to reflect this in the command set below.
+#
+editor=/usr/local/bin/simped
+
+# If you define PRINTCMD, it will be a pipe through which messages are
+# printed when the user hits the <P>rint key after a message.
+#
+#printcmd=lpr
+
+# If you define EXPCMD, it will be a pipe through which any incoming
+# express messages will be printed.
+#expcmd=xmessage -title "Express Message" -center -buttons OK -file -
+
+# If LOCAL_SCREEN_DIMENSIONS is set to 1, then the screen dimensions will
+# be requested from the underlying operating system instead of asking the
+# user.  This works when the user has his/her own copy of the client, it
+# works for xterms, it sometimes works for telnet sessions, but it doesn't
+# work for dialup connections.  Generally you should set this to 1 for a
+# private copy of the client or 0 for a shared copy of the client.
+#
+local_screen_dimensions=0
+
+# USE_FLOORS determines whether the user sees floors, or a flat room space.
+# Set it to YES to always use floors, NO to never use floors, or DEFAULT
+# to use the setting in the user's configuration (which is normally the case).
+#
+use_floors=DEFAULT
+
+# BEEP should be set to 1 if you wish the terminal to beep when an express
+# message (page) comes in, otherwise set it to 0.
+#
+beep=1
+
+# If you set the USERNAME variable, the value you set here will automatically
+# be passed to the "Enter your name:" prompt.
+#
+#username=My User Name
+
+# If you set the PASSWORD variable, the value you set here will automatically
+# be passed to all prompts which request a password ... including the prompt
+# which asks for a password when creating a new user.
+#
+#password=mypassword
+
+
+# COMMAND SET CONFIGURATION
+#
+# All lines starting with "cmd=" are considered to be commands.  This allows
+# mapping of keytstrokes to various functions of the client.
+#
+# Format of each line:
+# cmd_num,access,keystrokes
+#
+# Keep a copy of the original version of this file around as a reference
+# for the command numbers.  They are not documented anywhere else.
+#
+# Access is:  0 (all users), 1 (aides or room aides), 2 (aides only).
+# Please be aware that it is futile to attempt to gain unauthorized access to
+# the administrative functions of the system by changing all the access levels
+# to 0.  If you do this, you'll simply be able to enter a lot of commands that
+# will fail at the server ... so don't bother trying. :-)
+#
+# The actual key to be pressed should be prefaced with an & (ampersand)
+# character.  Ampersands are interesting and useful characters and you should
+# use them as much as possible.  Commands requiring more than one keystroke
+# should be entered as multiple fields.
+#
+# If the last keystroke string ends with a : (colon), then the command
+# will finish by allowing the user to enter a string.
+#
+# In keystroke names, the string ^r will be replaced by the name of the
+# current room.  The string ^c will be replaced by a comma.
+#
+# Commands may contain no more than five keystrokes.
+#
+# Note that the following characters are illegal in commands:
+#  , (comma)    : (colon)     ^ (caret)     & (ampersand)
+#
+#
+cmd=1,0,&? (Help)
+cmd=1,0,&Help
+cmd=2,0,&*Doorway
+cmd=3,0,&Chat
+#
+# If you want to use an external editor by default, set <E>nter message
+# to command #46 (external editor) instead of #4 (built-in editor).
+cmd=4,0,&Enter message
+#
+cmd=5,0,&Goto
+cmd=6,0,&Skip ^r
+cmd=7,0,&Zap (forget) room
+cmd=8,0,&Known rooms
+cmd=9,0,&Last five msgs
+cmd=10,0,read &Forward
+cmd=11,0,read &Reverse
+cmd=12,0,read &Old
+cmd=13,0,read &New
+cmd=14,0,read &Directory
+cmd=15,0,&Terminate
+cmd=16,0,&Ungoto
+cmd=17,0,&Who is online
+cmd=47,0,&Abandon ^r^c goto...
+cmd=50,0,toggle e&Xpert mode
+cmd=49,0,read &Info file
+cmd=18,2,&! <shell>
+cmd=19,0,&.,list &Zapped rooms
+cmd=52,0,&.,&Skip ^r^c goto:
+cmd=56,0,&Page a user
+cmd=58,0,&Mail
+#
+# We implement both <.G>oto and <J>ump commands which do the same thing, in
+# order to please a wider audience of users.  Remove one if you want to.
+#
+cmd=20,0,&Jump:
+cmd=20,0,&.,&Goto:
+#
+cmd=21,0,&.,&Help:
+cmd=22,1,&.,&Aide,&Kill this room
+cmd=23,1,&.,&Aide,&Edit this room
+cmd=24,1,&.,&Aide,&Who knows room
+cmd=25,2,&.,&Aide,edit &User
+cmd=26,2,&.,&Aide,&Validate new users
+cmd=48,1,&.,&Aide,enter &Info file
+cmd=27,1,&.,&Aide,&Room,&Invite user
+cmd=28,1,&.,&Aide,&Room,&Kick out user
+cmd=51,1,&.,&Aide,&File,&Delete
+cmd=53,1,&.,&Aide,&File,&Send over net
+cmd=54,1,&.,&Aide,&File,&Move
+cmd=70,2,&.,&Aide,&Message edit:
+cmd=29,0,&.,&Terminate,and &Quit
+cmd=30,0,&.,&Terminate,and &Stay online
+cmd=32,0,&.,&Read,&User listing
+cmd=33,0,&.,&Read,&Textfile formatted
+#
+# Command 55 allows the user to save a downloaded file directly to the
+# computer running the client software.  It is appropriate for a copy of
+# this client running on the user's own computer.  It is NOT appropriate for
+# public copies of the client that people will be dialing into.
+#
+#cmd=55,0,&.,&Read,&File
+#
+# Commands 34, 43, and 45 are appropriate for public copies of the client for
+# dialup use.  They transfer downloaded files to a temporary file and then
+# send them along to a dialup user using the popular protocols.
+#
+cmd=34,0,&.,&Read,file using &Xmodem
+cmd=43,0,&.,&Read,file using &Ymodem
+cmd=45,0,&.,&Read,file using &Zmodem
+cmd=31,0,&.,&Read,&File unformatted
+#
+cmd=13,0,&.,&Read,&New messages
+cmd=12,0,&.,&Read,&Old msgs reverse
+cmd=71,0,&.,read &Last:
+cmd=9,0,&.,&Read,&Last five msgs
+cmd=14,0,&.,&Read,&Directory
+cmd=49,0,&.,&Read,&Info file
+cmd=35,0,&.,&Enter,&Password
+cmd=36,0,&.,&Enter,&ASCII message
+cmd=37,0,&.,&Enter,&Configuration
+cmd=38,0,&.,&Enter,a new &Room
+cmd=39,0,&.,&Enter,&Textfile
+cmd=40,0,&.,&Enter,file using &Xmodem
+cmd=42,0,&.,&Enter,file using &Ymodem
+cmd=44,0,&.,&Enter,file using &Zmodem
+#
+# Command 57 is the local-file-upload command for users with their own
+# copy of the clientware.  Commands 72-74 are for image uploads.
+#
+#cmd=57,0,&.,&Enter,&File
+#cmd=72,0,&.,&Enter,&Image,user &Picture
+#cmd=73,0,&.,&Enter,&Image,&Room banner
+#cmd=74,0,&.,&Enter,&Image,&Floor label
+#
+cmd=41,0,&.,&Enter,re&Gistration
+cmd=4,0,&.,&Enter,&Message
+cmd=46,0,&.,&Enter,message with &Editor
+#
+cmd=59,0,&;,&Configure floor mode
+cmd=60,0,&;,&Goto floor:
+cmd=61,0,&;,&Skip to floor:
+cmd=62,0,&;,&Zap (forget) floor
+cmd=63,2,&;,&Aide,&Create floor
+cmd=64,2,&;,&Aide,&Edit floor
+cmd=65,2,&;,&Aide,&Kill floor
+cmd=68,0,&;,&Known rooms
+cmd=66,0,&.,&Enter,&Bio
+cmd=67,0,&.,&Read,&Bio
+#
+# Command 69 allows the user to enter a server command directly.  It is
+# primarily for testing and not intended for general use.  Usually there
+# is no need to enable it.
+cmd=69,0,&@Server command:
+#
+# end of command set configuration
+#
diff --git a/citadel/citmail.c b/citadel/citmail.c
new file mode 100644 (file)
index 0000000..4dec56b
--- /dev/null
@@ -0,0 +1,796 @@
+/*
+ * citmail.c v4.0
+ *
+ * This program may be used as a local mail delivery agent, which will allow
+ * all Citadel users to receive Internet e-mail.  To enable this functionality,
+ * you must tell sendmail, smail, or whatever mailer you are using, that this
+ * program is your local mail delivery agent.  This program is a direct
+ * replacement for lmail, deliver, or whatever.
+ *
+ * Usage:
+ *
+ * citmail <recipient>       - Deliver a message
+ * citmail -t <recipient>    - Address test mode (will not deliver)
+ * citmail -i                - Run as an SMTP daemon (typically from inetd)
+ *
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <pwd.h>
+#include <errno.h>
+#include <syslog.h>
+#include "citadel.h"
+
+#define LOCAL 0
+#define REMOTE 1
+#define UUCP 2
+#define CCITADEL 3
+
+#undef tolower
+#define tolower(x) isupper(x) ? (x+'a'-'A') : x
+
+char *monthdesc[] = {  "Jan","Feb","Mar","Apr","May","Jun",
+                       "Jul","Aug","Sep","Oct","Nov","Dec" };
+
+
+
+void LoadInternetConfig();
+void get_config();
+int IsHostLocal();
+struct config config;
+
+char ALIASES[128];
+char CIT86NET[128];
+char SENDMAIL[128];
+char FALLBACK[128];
+char GW_DOMAIN[128];
+char TABLEFILE[128];
+char OUTGOING_FQDN[128];
+int RUN_NETPROC = 1;
+
+int struncmp(lstr,rstr,l)
+char lstr[],rstr[]; {
+       int pos = 0;
+       char lc,rc;
+       while (1) {
+               if (pos==l) return(0);
+               lc=tolower(lstr[pos]);
+               rc=tolower(rstr[pos]);
+               if ((lc==0)&&(rc==0)) return(0);
+               if (lc<rc) return(-1);
+               if (lc>rc) return(1);
+               pos=pos+1;
+               }
+       }
+
+long conv_date(sdbuf)
+char sdbuf[]; {
+       int a,b,cpos,tend,tval;
+       long now;
+       struct tm *tmbuf;
+       char dbuf[128];
+
+       strcpy(dbuf,sdbuf);
+       time(&now);
+       tmbuf = (struct tm *)localtime(&now);
+
+       /* get rid of + or - timezone mods */
+       for (a=0; a<strlen(dbuf); ++a) 
+               if ((dbuf[a]=='+')||(dbuf[a]=='-'))
+                       do {
+                               strcpy(&dbuf[a],&dbuf[a+1]);
+                               } while ((dbuf[a]!=32)&&(dbuf[a]!=0));
+
+       /* try and extract the time by looking for colons */
+       cpos = (-1);
+       for (a=strlen(dbuf); a>=0; --a)
+               if ((dbuf[a]==':')&&(atoi(&dbuf[a-1])!=0)) cpos=a;
+       if (cpos>=0) {
+               cpos = cpos - 2;
+               tend = strlen(dbuf);
+               for (a=tend; a>=cpos; --a) if (dbuf[a]==' ') tend=a;
+
+               tmbuf->tm_hour = atoi(&dbuf[cpos]);
+               tmbuf->tm_min = atoi(&dbuf[cpos+3]);
+               tmbuf->tm_sec = atoi(&dbuf[cpos+6]);
+
+               do {
+                       strcpy(&dbuf[cpos],&dbuf[cpos+1]);
+                       } while ((dbuf[cpos]!=32)&&(dbuf[cpos]!=0));
+               }
+
+       /* next try to extract a month */
+       
+       tval = (-1);
+       for (a=0; a<strlen(dbuf); ++a)
+               for (b=0; b<12; ++b)
+                       if (!strncmp(&dbuf[a],monthdesc[b],3)) {
+                               tval = b;
+                               cpos = a;
+                               }
+       if (tval >= 0) {
+               tmbuf->tm_mon = tval;
+               strcpy(&dbuf[cpos],&dbuf[cpos+3]);
+               }
+
+       /* now the year */
+
+       for (a=0; a<strlen(dbuf); ++a)
+               if ((atoi(&dbuf[a])>=1900) && (dbuf[a]!=32)) {
+                       tmbuf->tm_year = atoi(&dbuf[a]) - 1900;
+                       strcpy(&dbuf[a],&dbuf[a+4]);
+                       }
+
+       /* whatever's left is the mday (hopefully) */
+
+       for (a=0; a<strlen(dbuf); ++a)
+               if ((dbuf[a]!=32)&&(atoi(&dbuf[a])>=1)&&(atoi(&dbuf[a])<=31)
+                  && ( (a==0)||(dbuf[a-1]==' ') ) ) {
+                       tmbuf->tm_mday = atoi(&dbuf[a]);
+                       strcpy(&dbuf[a],&dbuf[a+2]);
+                       }
+
+       return((long)mktime(tmbuf));
+       }
+
+
+#ifdef NO_STRERROR
+/*
+ * replacement strerror() for systems that don't have it
+ */
+char *strerror(e)
+int e; {
+       static char buf[32];
+
+       sprintf(buf,"errno = %d",e);
+       return(buf);
+       }
+#endif
+
+int haschar(st,ch)
+char st[];
+int ch; {
+       int a,b;
+       b=0;
+       for (a=0; a<strlen(st); ++a) if (st[a]==ch) ++b;
+       return(b);
+       }
+
+void strip_trailing_whitespace(buf)
+char buf[]; {
+       while(isspace(buf[strlen(buf)-1]))
+               buf[strlen(buf)-1]=0;
+       }
+
+int islocalok(char recp[]) {
+
+       struct usersupp ust;
+       long lookfor;
+       char a_recp[128];
+       int found_closest_match = 0;
+       int a,us;
+       strcpy(a_recp,recp);
+       for (a=0; a<strlen(a_recp); ++a)
+               if (a_recp[a]=='_') a_recp[a]=32;
+       lookfor = (-1L); if (!struncmp(recp,"cit",3)) lookfor=atol(&recp[3]);
+       us=open("/appl/citadel/usersupp",O_RDONLY);
+       if (us>=0) {
+               while(read(us,&ust,sizeof(struct usersupp))>0) {
+                       if (lookfor == ust.usernum) {
+                               strcpy(recp,ust.fullname);
+                               close(us);
+                               return(2);
+                               }
+                       if (!strucmp(ust.fullname,a_recp)) {
+                               strcpy(recp,ust.fullname);
+                               close(us);
+                               return(3);
+                               }
+                       if (!struncmp(ust.fullname,a_recp,strlen(a_recp))) {
+                               strcpy(recp,ust.fullname);
+                               found_closest_match = 1;
+                               }
+                       }
+               close(us);
+               }
+       if (getpwnam(recp)!=NULL) return(1);
+       if (found_closest_match) return(3);
+       return(0);
+       }
+
+/* strip leading and trailing spaces */
+void striplt(buf)
+char buf[]; {
+       while ( (strlen(buf)>0) && (buf[0]==32) ) strcpy(buf,&buf[1]);
+       while (buf[strlen(buf)-1] == 32) buf[strlen(buf)-1] = 0;
+       }
+
+
+/*
+ * Check to see if a given FQDN really maps to a Citadel network node
+ */
+void host_alias(char host[]) {
+
+       int a;
+
+       /* What name is the local host known by? */
+       /* if (!strucmp(host, config.c_fqdn)) { */
+       if (IsHostLocal(host)) {
+               strcpy(host, config.c_nodename);
+               return;
+               }
+
+       /* Other hosts in the gateway domain? */
+       for (a=0; a<strlen(host); ++a) {
+               if ((host[a]=='.') && (!strucmp(&host[a+1], GW_DOMAIN))) {
+                       host[a] = 0;
+                       for (a=0; a<strlen(host); ++a) {
+                               if (host[a]=='.') host[a] = 0;
+                               }
+                       return;
+                       }
+               }
+
+       /* Otherwise, do nothing... */
+       }
+
+
+
+/*
+ * Split an RFC822-style address into userid, host, and full name
+ */
+void process_rfc822_addr(rfc822,user,node,name)
+char rfc822[];
+char user[];
+char node[];
+char name[];  {
+       int a;
+
+       /* extract full name - first, it's From minus <userid> */
+       strcpy(name,rfc822);
+       for (a=0; a<strlen(name); ++a) if (name[a]=='<') {
+               do {
+                       strcpy(&name[a],&name[a+1]);
+                       } while ( (strlen(name) > 0) && (name[a]!='>') );
+               strcpy(&name[a],&name[a+1]);
+               }
+
+       /* strip anything to the left of a bang */
+       while ( (strlen(name)>0) && (haschar(name,'!')>0) ) 
+               strcpy(name,&name[1]);
+
+       /* and anything to the right of a @ or % */
+       for (a=0; a<strlen(name); ++a) {
+               if (name[a]=='@') name[a]=0;
+               if (name[a]=='%') name[a]=0;
+               }
+
+       /* but if there are parentheses, that changes the rules... */
+       if ( (haschar(rfc822,'(') == 1) && (haschar(rfc822,')') == 1) ) {
+               strcpy(name,rfc822);
+               while ( (strlen(name) > 0) && (name[0]!='(') ) {
+                       strcpy(&name[0],&name[1]);
+                       }
+               strcpy(&name[0],&name[1]);
+               for (a=0; a<strlen(name); ++a)
+                        if (name[a]==')') name[a]=0;
+               }
+
+       /* but if there are a set of quotes, that supersedes everything */
+       if (haschar(rfc822,34)==2) {
+               strcpy(name,rfc822);
+               while ( (strlen(name) > 0) && (name[0]!=34) ) {
+                       strcpy(&name[0],&name[1]);
+                       }
+               strcpy(&name[0],&name[1]);
+               for (a=0; a<strlen(name); ++a)
+                       if (name[a]==34) name[a]=0;
+               }
+
+       /* extract user id */
+       strcpy(user,rfc822);
+
+       /* first get rid of anything in parens */
+       for (a=0; a<strlen(user); ++a) if (user[a]=='(') {
+               do {
+                       strcpy(&user[a],&user[a+1]);
+                       } while ( (strlen(user) > 0) && (user[a]!=')') );
+               strcpy(&user[a],&user[a+1]);
+               }
+
+       /* if there's a set of angle brackets, strip it down to that */
+       if ( (haschar(user,'<') == 1) && (haschar(user,'>') == 1) ) {
+               while ( (strlen(user) > 0) && (user[0]!='<') ) {
+                       strcpy(&user[0],&user[1]);
+                       }
+               strcpy(&user[0],&user[1]);
+               for (a=0; a<strlen(user); ++a)
+                        if (user[a]=='>') user[a]=0;
+               }
+
+       /* strip anything to the left of a bang */
+       while ( (strlen(user)>0) && (haschar(user,'!')>0) ) 
+               strcpy(user,&user[1]);
+
+       /* and anything to the right of a @ or % */
+       for (a=0; a<strlen(user); ++a) {
+               if (user[a]=='@') user[a]=0;
+               if (user[a]=='%') user[a]=0;
+               }
+
+
+       
+       /* extract node name */
+       strcpy(node, rfc822);
+
+       /* first get rid of anything in parens */
+       for (a=0; a<strlen(node); ++a) if (node[a]=='(') {
+               do {
+                       strcpy(&node[a],&node[a+1]);
+                       } while ( (strlen(node) > 0) && (node[a]!=')') );
+               strcpy(&node[a],&node[a+1]);
+               }
+
+       /* if there's a set of angle brackets, strip it down to that */
+       if ( (haschar(node,'<') == 1) && (haschar(node,'>') == 1) ) {
+               while ( (strlen(node) > 0) && (node[0]!='<') ) {
+                       strcpy(&node[0],&node[1]);
+                       }
+               strcpy(&node[0],&node[1]);
+               for (a=0; a<strlen(node); ++a)
+                        if (node[a]=='>') node[a]=0;
+               }
+
+       /* strip anything to the left of a @ */
+       while ( (strlen(node)>0) && (haschar(node,'@')>0) ) 
+               strcpy(node,&node[1]);
+
+       /* strip anything to the left of a % */
+       while ( (strlen(node)>0) && (haschar(node,'%')>0) ) 
+               strcpy(node,&node[1]);
+
+       /* reduce multiple system bang paths to node!user */
+       while ( (strlen(node)>0) && (haschar(node,'!')>1) ) 
+               strcpy(node,&node[1]);
+
+       /* now get rid of the user portion of a node!user string */
+       for (a=0; a<strlen(node); ++a) if (node[a]=='!') node[a]=0;
+
+
+
+       /* strip leading and trailing spaces in all strings */
+       striplt(user);
+       striplt(node);
+       striplt(name);
+       }
+
+/*
+ * Copy line by line, ending at EOF or a "." 
+ */
+void loopcopy(FILE *to, FILE *from) {
+       char buf[1024];
+       char *r;
+       
+       while (1) {
+               r = fgets(buf, sizeof(buf), from);
+               if (r == NULL) return;
+               strip_trailing_whitespace(buf);
+               if (!strcmp(buf, ".")) return;
+               fprintf(to, "%s\n", buf);
+               }
+       }
+
+
+/*
+ * pipe message through netproc
+ */
+void do_citmail(char recp[], int dtype) {
+
+       long now;
+       FILE *temp;
+       int a;
+       char buf[128];
+       char from[512];
+       char userbuf[256];
+       char frombuf[256];
+       char nodebuf[256];
+       char destsys[256];
+       char subject[256];
+
+
+       if (dtype==REMOTE) {
+
+               /* get the Citadel node name out of the path */
+               strncpy(destsys, recp, sizeof(destsys) );
+               for (a=0; a<strlen(destsys); ++a) {
+                       if ((destsys[a]=='!')||(destsys[a]=='.')) {
+                               destsys[a]=0;
+                               }
+                       }
+
+               /* chop the system name out, so we're left with a user */
+               while (haschar(recp,'!')) strcpy(recp,&recp[1]);
+
+               /* now convert underscores to spaces */
+               for (a=0; a<strlen(recp); ++a) if (recp[a]=='_') recp[a]=' ';
+
+               }
+
+       time(&now);
+       sprintf(from, "postmaster@%s", config.c_nodename);
+
+       sprintf(buf, "./network/spoolin/citmail.%d", getpid());
+       temp = fopen(buf,"w");
+
+       putc(255,temp); putc(MES_NORMAL,temp); putc(1,temp);
+       strcpy(subject,"");
+       strcpy(nodebuf, config.c_nodename);
+       do {
+               if (fgets(buf,128,stdin) == NULL) strcpy(buf, ".");
+               strip_trailing_whitespace(buf);
+
+               if (!strncmp(buf,"Subject: ",9)) strcpy(subject,&buf[9]);
+               if (!strncmp(buf,"Date: ",6)) now = conv_date(&buf[6]);
+               if (!strncmp(buf,"From: ",6)) strcpy(from, &buf[6]);
+               } while ( (strcmp(buf, ".")) && (strcmp(buf, "")) );
+
+       process_rfc822_addr(from, userbuf, nodebuf, frombuf);
+
+       /* now convert it to Citadel format */
+       fprintf(temp,"P%s@%s%c", userbuf, nodebuf, 0);
+       fprintf(temp,"E%s%c", userbuf, 0);
+       fprintf(temp,"T%ld%c", now, 0);
+       fprintf(temp,"A%s%c", frombuf, 0);
+       fprintf(temp,"OMail%c", 0);
+       fprintf(temp,"N%s%c", nodebuf, 0);
+       if (dtype==REMOTE) {
+               fprintf(temp,"D%s%c", destsys, 0);
+               }
+       fprintf(temp,"R%s%c", recp, 0);
+       if (strlen(subject)>0) {
+               fprintf(temp,"U%s%c", subject, 0);
+               }
+       putc('M',temp);
+       if (strcmp(buf, ".")) loopcopy(temp, stdin);
+       putc(0,temp);
+       fclose(temp);
+       }
+
+void do_roommail(recp)  /* pipe public message through netproc */
+char recp[]; {
+       long now;
+       FILE *temp;
+       int a;
+       char buf[128],userbuf[128],frombuf[128],nodebuf[128];
+       char subject[128], from[256];
+
+       strcpy(subject,"");
+       sprintf(from, "postmaster@%s", config.c_nodename);
+       strcpy(recp,&recp[5]);
+       for (a=0; a<strlen(recp); ++a) if (recp[a]=='_') recp[a]=32;
+       time(&now);
+
+
+       sprintf(buf,"./network/spoolin/citmail.%d",getpid());
+       temp = fopen(buf,"w");
+
+       putc(255,temp); putc(MES_NORMAL,temp); putc(1,temp);
+       strcpy(frombuf,"Internet Mail Gateway");
+       strcpy(nodebuf, config.c_nodename);
+       do {
+               if (fgets(buf,128,stdin) == NULL) strcpy(buf, ".");
+               strip_trailing_whitespace(buf);
+
+               if (!strncmp(buf,"Subject: ",9)) strcpy(subject,&buf[9]);
+               if (!strncmp(buf,"Date: ",6)) now = conv_date(&buf[6]);
+               if (!strncmp(buf,"From: ",6)) strcpy(from, &buf[6]);
+               } while ( (strcmp(buf, ".")) && (strcmp(buf, "")) );
+
+       process_rfc822_addr(from, userbuf, nodebuf, frombuf);
+
+       fprintf(temp,"P%s@%s",userbuf,nodebuf); putc(0,temp);
+       fprintf(temp,"T%ld",now); putc(0,temp);
+       fprintf(temp,"A%s",frombuf); putc(0,temp);
+       fprintf(temp,"O%s",recp); putc(0,temp);
+       fprintf(temp,"N%s",nodebuf); putc(0,temp);
+       if (strlen(subject)>0) {
+               fprintf(temp,"U%s",subject); putc(0,temp);
+               }
+       putc('M',temp);
+       if (strcmp(buf, ".")) loopcopy(temp, stdin);
+       putc(0,temp);
+       fclose(temp);
+       }
+
+void do_uudecode(target)
+char *target;  {
+       static char buf[1024];
+       FILE *fp;
+       
+       sprintf(buf,"cd %s; uudecode",target);
+
+       fp=popen(buf,"w");
+       if (fp==NULL) return;
+       while (fgets(buf,1024,stdin)!=NULL) {
+               fprintf(fp,"%s",buf);
+               }
+       pclose(fp);
+
+       }
+
+void do_fallback(recp)
+char recp[]; {
+       static char buf[1024];
+       FILE *fp;
+       
+       sprintf(buf, FALLBACK, recp);
+       fp=popen(buf,"w");
+       if (fp==NULL) fp = popen("cat >/dev/null", "w");
+       loopcopy(fp, stdin);
+       pclose(fp);
+       }
+
+int alias(name)
+char *name; {
+       FILE *fp;
+       int a;
+       char abuf[256];
+       
+       fp=fopen(ALIASES,"r");
+       if (fp==NULL) {
+               syslog(LOG_ERR,"cannot open %s: %s",ALIASES,strerror(errno));
+               return(2);
+               }
+
+       while (fgets(abuf,256,fp)!=NULL) {
+               strip_trailing_whitespace(abuf);
+               for (a=0; a<strlen(abuf); ++a) {
+                       if (abuf[a]==',') {
+                               abuf[a]=0;
+                               if (!strucmp(name,abuf)) {
+                                       strcpy(name,&abuf[a+1]);
+                                       }
+                               }
+                       }
+               }
+       fclose(fp);
+       return(0);
+       }
+
+
+void deliver(char recp[], int is_test, int deliver_to_ignet) {
+
+       int b;
+       b=islocalok(recp);
+
+       /* various ways we can deliver mail... */
+
+       if (deliver_to_ignet) {
+               syslog(LOG_NOTICE,"to Citadel network user %s",recp);
+               if (is_test == 0) do_citmail(recp, REMOTE);
+               }
+
+       else if (!strcmp(recp,"uudecode")) {
+               syslog(LOG_NOTICE,"uudecoding to bit bucket directory");
+               if (is_test == 0) do_uudecode(config.c_bucket_dir);
+               }
+
+       else if (!strcmp(recp,"cit86net")) {
+               syslog(LOG_NOTICE,"uudecoding to Cit86net spool");
+               if (is_test == 0) {
+                       do_uudecode(CIT86NET);
+                       system("exec ./BatchTranslate86");
+                       }
+               }
+
+       else if (!strcmp(recp,"null")) {
+               syslog(LOG_NOTICE,"zapping nulled message");
+               }
+
+       else if (!struncmp(recp,"room_",5)) {
+               syslog(LOG_NOTICE,"to room %s",recp);
+               if (is_test == 0) do_roommail(recp);
+               }
+
+       else if (b==1) {
+               syslog(LOG_NOTICE,"fallback mailer to user %s",recp);
+               if (is_test == 0) do_fallback(recp);
+               }
+
+       else {
+               /* Otherwise, the user is local (or an unknown name was specified, in
+                * which case we let netproc handle the bounce)
+                */
+               syslog(LOG_NOTICE,"to Citadel user %s",recp);
+               if (is_test == 0) do_citmail(recp, LOCAL);
+               }
+
+       }
+
+
+
+void main(argc,argv)
+int argc;
+char *argv[]; {
+       int is_test = 0;
+       int deliver_to_ignet = 0;
+       int smtp = 0;
+       static char recp[1024], buf[1024];
+       static char user[1024], node[1024], name[1024];
+       int a;
+
+       openlog("citmail", LOG_PID, LOG_USER);
+       get_config();
+       LoadInternetConfig();
+
+       if (!strcmp(argv[1],"-t")) {
+               is_test = 1;
+               syslog(LOG_NOTICE,"test mode - will not deliver");
+               }
+       if (!strcmp(argv[1], "-i")) {
+               smtp = 1;
+               syslog(LOG_NOTICE,"started as an SMTP daemon");
+               }
+
+
+       if (smtp) {
+               strcpy(recp, "");
+               }
+       else if (is_test == 0) {
+               strcpy(recp,argv[1]);
+               }
+       else {
+               strcpy(recp,argv[2]);
+               }
+
+
+       if (smtp) {
+               /*** SMTP delivery mode ***/
+
+               printf("200 Citadel/UX SMTP gateway ready.\n");
+       
+               do {
+                       fflush(stdout);
+                       fflush(stderr);
+                       fgets(buf, 1024, stdin);
+                       while ( (strlen(buf)>0) && (buf[strlen(buf)-1]>0) && (buf[strlen(buf)-1]<32) ) {
+                               buf[strlen(buf)-1] = 0;
+                               }
+
+                       /* null-pad to allow some lazy compares */
+                       buf[strlen(buf)+1] = 0;
+                       buf[strlen(buf)+2] = 0;
+                       buf[strlen(buf)+3] = 0;
+       
+                       if (!struncmp(buf, "QUIT", 4)) {
+                               printf("221 Later, dude.\n");
+                               }
+                       else if (!struncmp(buf, "HELP", 4)) {
+                               printf("214 You think _you_ need help?\n");
+                               }
+                       else if (!struncmp(buf, "HELO", 4)) {
+                               printf("250 Howdy ho, Mr. Hankey!\n");
+                               }
+                       else if (!struncmp(buf, "MAIL", 4)) {
+                               printf("250 Sure, whatever...\n");
+                               }
+
+
+                       else if (!struncmp(buf, "RCPT To: ", 9)) {
+                               if (strlen(recp) > 0) {
+                                       printf("571 Multiple recipients not supported.\n");
+                                       }
+                               else {
+                                       strcpy(recp, &buf[9]);
+                                       if (haschar(recp, ',')) {
+                                               printf("571 Multiple recipients not supported.\n");
+                                               strcpy(recp, "");
+                                               }
+                                       else {
+                                               syslog(LOG_NOTICE,"recp: %s",recp);
+                                               for (a=0; a<2; ++a) {
+                                                       alias(recp);
+                                                       }
+
+                                               /* did we alias it back to a remote address? */
+                                               if (    (haschar(recp,'%'))
+                                               ||      (haschar(recp,'@'))
+                                               ||      (haschar(recp,'!')) ) {
+       
+                                                       process_rfc822_addr(recp, user, node, name);
+                                                       host_alias(node);
+               
+                                                       /* If there are dots, it's an Internet host, so feed it
+                                                       * back to an external mail transport agent such as sendmail.
+                                                       */
+                                                       if (haschar(node, '.')) {
+                                                               printf("571 Away with thee, spammer!\n");
+                                                               strcpy(recp, "");
+                                                               }
+               
+                                                       /* Otherwise, we're dealing with Citadel mail. */
+                                                       else {
+                                                               sprintf(recp, "%s!%s", node, user);
+                                                               deliver_to_ignet = 1;
+                                                               printf("250 IGnet recipient.\n");
+                                                               }
+                                                       }
+                                               else {
+                                                       printf("250 Local recipient.\n");
+                                                       }
+       
+                                               }
+       
+                                       }
+                               }
+
+
+
+                       else if (!struncmp(buf, "RCPT", 4)) {
+                               printf("501 Only 'To:' commands are supported.\n");
+                               }
+                       else if (!struncmp(buf, "DATA", 4)) {
+                               if (strlen(recp) > 0) {
+                                       printf("354 Sock it to me, baby...\n");
+                                       fflush(stdout);
+                                       deliver(recp, is_test, deliver_to_ignet);
+                                       printf("250 Cool beans!\n");
+                                       }
+                               else {
+                                       printf("503 No recipient has been specified.\n");
+                                       }
+                               }
+                       else {
+                               printf("500 Huh?\n");
+                               }
+       
+                       } while (struncmp(buf,"QUIT",4));
+               }
+
+       else {
+               /*** Non-SMTP delivery mode ***/
+               syslog(LOG_NOTICE,"recp: %s",recp);
+               for (a=0; a<2; ++a) {
+                       alias(recp);
+                       }
+       
+               /* did we alias it back to a remote address? */
+               if (    (haschar(recp,'%'))
+               ||      (haschar(recp,'@'))
+               ||      (haschar(recp,'!')) ) {
+       
+                       process_rfc822_addr(recp, user, node, name);
+                       host_alias(node);
+               
+                       /* If there are dots, it's an Internet host, so feed it
+                       * back to an external mail transport agent such as sendmail.
+                       */
+                       if (haschar(node, '.')) {
+                               sprintf(buf, SENDMAIL, recp);
+                               system(buf);
+                               exit(0);
+                               }
+       
+                       /* Otherwise, we're dealing with Citadel mail. */
+                       else {
+                               sprintf(recp, "%s!%s", node, user);
+                               deliver_to_ignet = 1;
+                               }
+       
+                       }
+       
+               deliver(recp, is_test, deliver_to_ignet);
+               }
+       
+       closelog();
+       if (RUN_NETPROC) execlp("./netproc","netproc",NULL);
+       exit(0);
+       }
+
diff --git a/citadel/citserver.c b/citadel/citserver.c
new file mode 100644 (file)
index 0000000..94de0e6
--- /dev/null
@@ -0,0 +1,1027 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <time.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+#include <syslog.h>
+#include "citadel.h"
+#include "server.h"
+#include "proto.h"
+
+struct config config;
+
+struct CitContext *ContextList = NULL;
+int ScheduledShutdown = 0;
+
+       
+/*
+ * record an entry in the call log
+ */
+void rec_log(unsigned int lrtype, char *name)
+{
+       struct calllog record;
+       int file,file2,a,b;
+
+
+       time(&record.CLtime);
+       record.CLflags = lrtype;
+       strncpy(record.CLfullname,name,29);
+
+       begin_critical_section(S_CALLLOG);
+       file=open("calllog.pos",O_RDWR);
+
+       read(file,&a,sizeof(int));
+       b=a; ++a; if (a>=CALLLOG) a=0;
+       lseek(file,0L,0);
+       write(file,&a,sizeof(int));
+
+       file2=open("calllog",O_RDWR);
+       lseek(file2,(long)(b*sizeof(struct calllog)),0);
+       write(file2,(char *)&record,sizeof(struct calllog));
+       close(file2);
+
+       close(file);
+       end_critical_section(S_CALLLOG);
+       }
+
+/*
+ * Cleanup routine to be called when the server is shutting down.
+ */
+void master_cleanup(void) {
+
+       /* Cancel all running sessions */
+       lprintf(7, "Cancelling running sessions...\n");
+       while (ContextList != NULL) {
+               kill_session(ContextList->cs_pid);
+               }
+
+       /* Do system-dependent stuff */
+       sysdep_master_cleanup();
+
+       /* Now go away. */
+       hook_cleanup();
+       lprintf(3, "citserver: exiting.\n");
+       exit(0);
+       }
+
+
+/*
+ * Gracefully terminate the session and thread.
+ * (This is called as a cleanup handler by the thread library.)
+ */
+void cleanup_stuff()
+{
+       struct ExpressMessage *emptr;
+
+       lprintf(9, "cleanup_stuff() called\n");
+
+       lprintf(7, "Calling logout(%d)\n", CC->cs_pid);
+       logout(CC);
+
+       rec_log(CL_TERMINATE,CC->curr_user);
+       unlink(CC->temp);
+       lprintf(3, "citserver[%3d]: ended.\n",CC->cs_pid);
+       hook_end_session(CC->cs_pid);
+       syslog(LOG_NOTICE,"session %d ended", CC->cs_pid);
+       
+       /* Deallocate any unsent express messages */
+       begin_critical_section(S_SESSION_TABLE);
+       while (CC->FirstExpressMessage != NULL) {
+               emptr = CC->FirstExpressMessage;
+               CC->FirstExpressMessage = CC->FirstExpressMessage->next;
+               free(emptr);
+               }
+       end_critical_section(S_SESSION_TABLE);
+
+       /* Now get rid of the session and context */
+       lprintf(7, "cleanup_stuff() is calling RemoveContext(%d)\n", CC->cs_pid);
+       RemoveContext(CC);
+
+       /* While we still have an extra thread with no user attached to it,
+        * take the opportunity to do some housekeeping before exiting.
+        */
+       do_housekeeping();
+       }
+
+
+/*
+ * set_wtmpsupp()  -  alter the session listing
+ */
+void set_wtmpsupp(char *newtext)
+{
+       strncpy(CC->cs_room,newtext,19);
+       CC->cs_room[19] = 0;
+       time(&CC->cs_lastupdt);
+       hook_room_name(CC->cs_pid, CC->cs_room);
+       }
+
+
+/*
+ * cmd_info()  -  tell the client about this server
+ */
+void cmd_info(void) {
+       cprintf("%d Server info:\n",LISTING_FOLLOWS);
+       cprintf("%d\n",CC->cs_pid);
+       cprintf("%s\n",config.c_nodename);
+       cprintf("%s\n",config.c_humannode);
+       cprintf("%s\n",config.c_fqdn);
+       cprintf("%s\n",CITADEL);
+       cprintf("%d\n",REV_LEVEL);
+       cprintf("%s\n",config.c_bbs_city);
+       cprintf("%s\n",config.c_sysadm);
+       cprintf("%d\n",SERVER_TYPE);
+       cprintf("%s\n",config.c_moreprompt);
+       cprintf("1\n"); /* 1 = yes, this system supports floors */
+       cprintf("000\n");
+       }
+
+void cmd_rchg(char *newroomname)
+{
+   if ((newroomname) && (newroomname[0]))
+   {
+      bzero(CC->fake_roomname, 20);
+      strncpy(CC->fake_roomname, newroomname, 19);
+   }
+   else
+      CC->fake_roomname[0] = '\0';
+   cprintf("%d\n",OK);
+}
+
+void cmd_hchg(char *newhostname)
+{
+   if ((newhostname) && (newhostname[0]))
+   {
+      bzero(CC->fake_hostname, 25);
+      strncpy(CC->fake_hostname, newhostname, 24);
+   }
+   else
+      CC->fake_hostname[0] = '\0';
+   cprintf("%d\n",OK);
+}
+
+void cmd_uchg(char *newusername)
+{
+   if (CC->usersupp.axlevel < 6) 
+   {
+      cprintf("%d You must be an Aide to use UCHG.\n",
+               ERROR+HIGHER_ACCESS_REQUIRED);
+      return;
+   }
+   if ((newusername) && (newusername[0]))
+   {
+      CC->cs_flags &= ~CS_STEALTH;
+      bzero(CC->fake_username, 32);
+      if (struncmp(newusername, CC->curr_user, strlen(CC->curr_user)))
+         strncpy(CC->fake_username, newusername, 31);
+   }
+   else
+   {
+      CC->fake_username[0] = '\0';
+      CC->cs_flags |= CS_STEALTH;
+   }
+   cprintf("%d\n",OK);
+}
+
+void cmd_time()
+{
+   time_t tv;
+   
+   tv = time(NULL);
+   
+   cprintf("%d|%ld\n", OK, tv);
+}
+
+/*
+ * check a hostname against the public_clients file
+ */
+int is_public_client(char *where)
+{
+       char buf[256];
+       FILE *fp;
+
+       if (!strucmp(where,"localhost")) return(1);
+       if (!strucmp(where,config.c_fqdn)) return(1);
+
+       fp = fopen("public_clients","r");
+       if (fp == NULL) return(0);
+
+       while (fgets(buf,256,fp)!=NULL) {
+               while (isspace((buf[strlen(buf)-1]))) 
+                       buf[strlen(buf)-1] = 0;
+               if (!strucmp(buf,where)) {
+                       fclose(fp);
+                       return(1);
+                       }
+               }
+
+       fclose(fp);
+       return(0);
+       }
+
+
+/*
+ * the client is identifying itself to the server
+ */
+void cmd_iden(char *argbuf)
+{
+       int dev_code;
+       int cli_code;
+       int rev_level;
+       char desc[256];
+       char from_host[256];
+
+       if (num_parms(argbuf)<4) {
+               cprintf("%d usage error\n",ERROR);
+               return;
+               }
+
+       dev_code = extract_int(argbuf,0);
+       cli_code = extract_int(argbuf,1);
+       rev_level = extract_int(argbuf,2);
+       extract(desc,argbuf,3);
+
+       strcpy(from_host,config.c_fqdn);
+       if (num_parms(argbuf)>=5) extract(from_host,argbuf,4);
+
+       CC->cs_clientdev = dev_code;
+       CC->cs_clienttyp = cli_code;
+       CC->cs_clientver = rev_level;
+       strncpy(CC->cs_clientname,desc,31);
+       CC->cs_clientname[31] = 0;
+
+       if ((strlen(from_host)>0) && 
+          (is_public_client(CC->cs_host))) {
+               strncpy(CC->cs_host,from_host,24);
+               CC->cs_host[24] = 0;
+               }
+       set_wtmpsupp(CC->quickroom.QRname);
+
+       syslog(LOG_NOTICE,"client %d/%d/%01d.%02d (%s)\n",
+               dev_code,
+               cli_code,
+               (rev_level / 100),
+               (rev_level % 100),
+               desc);
+               cprintf("%d Ok\n",OK);
+       }
+
+
+/*
+ * enter or exit "stealth mode"
+ */
+void cmd_stel(char *cmdbuf)
+{
+       int requested_mode;
+
+       requested_mode = extract_int(cmdbuf,0);
+       if (requested_mode !=0) requested_mode = 1;
+
+       if (!CC->logged_in) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (CC->usersupp.axlevel < 6) {
+               cprintf("%d You must be an Aide to use stealth mode.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       if (CC->cs_flags & CS_STEALTH) {
+               if (requested_mode == 0)
+                       CC->cs_flags = CC->cs_flags-CS_STEALTH;
+               }
+       else {
+               if (requested_mode == 1)
+                       CC->cs_flags = CC->cs_flags|CS_STEALTH;
+               }
+
+       set_wtmpsupp(CC->quickroom.QRname);
+       cprintf("%d Ok\n",OK);
+       }
+
+
+
+
+/*
+ * display system messages or help
+ */
+void cmd_mesg(char *mname)
+{
+       FILE *mfp;
+       char targ[256];
+       char buf[256];
+       char *dirs[2];
+
+       extract(buf,mname,0);
+
+
+       dirs[0]=malloc(64);
+       dirs[1]=malloc(64);
+       strcpy(dirs[0],"messages");
+       strcpy(dirs[1],"help");
+       mesg_locate(targ,buf,2,dirs);
+       free(dirs[0]);
+       free(dirs[1]);
+
+
+       if (strlen(targ)==0) {
+               cprintf("%d '%s' not found.\n",ERROR,mname);
+               return;
+               }
+
+       mfp = fopen(targ,"r");
+       if (mfp==NULL) {
+               cprintf("%d Cannot open '%s': %s\n",
+                       ERROR,targ,strerror(errno));
+               return;
+               }
+       cprintf("%d %s\n",LISTING_FOLLOWS,buf);
+
+       while (fgets(buf,255,mfp)!=NULL) {
+               buf[strlen(buf)-1] = 0;
+               do_help_subst(buf);
+               cprintf("%s\n",buf);
+               }
+
+       fclose(mfp);
+       cprintf("000\n");
+       }
+
+
+/*
+ * enter system messages or help
+ */
+void cmd_emsg(char *mname)
+{
+       FILE *mfp;
+       char targ[256];
+       char buf[256];
+       char *dirs[2];
+       int a;
+
+       if (CC->usersupp.axlevel < 6) {
+               cprintf("%d You must be an Aide to edit system messages.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       extract(buf,mname,0);
+       for (a=0; a<strlen(buf); ++a) {         /* security measure */
+               if (buf[a] == '/') buf[a] = '.';
+               }
+
+       dirs[0]=malloc(64);
+       dirs[1]=malloc(64);
+       strcpy(dirs[0],"messages");
+       strcpy(dirs[1],"help");
+       mesg_locate(targ,buf,2,dirs);
+       free(dirs[0]);
+       free(dirs[1]);
+
+       if (strlen(targ)==0) {
+               sprintf(targ, "./help/%s", buf);
+               }
+
+       mfp = fopen(targ,"w");
+       if (mfp==NULL) {
+               cprintf("%d Cannot open '%s': %s\n",
+                       ERROR,targ,strerror(errno));
+               return;
+               }
+       cprintf("%d %s\n", SEND_LISTING, targ);
+
+       while (client_gets(buf), strcmp(buf, "000")) {
+               fprintf(mfp, "%s\n", buf);
+               }
+
+       fclose(mfp);
+       }
+
+
+/*
+ * who's online
+ */
+void cmd_rwho(void) {
+       struct CitContext *cptr;
+       int spoofed = 0;
+       int aide;
+       char un[40], room[40], host[40], flags[5];
+       
+       aide = CC->usersupp.axlevel >= 6;
+       cprintf("%d\n",LISTING_FOLLOWS);
+       
+       for (cptr = ContextList; cptr != NULL; cptr = cptr->next) 
+       {
+               flags[0] = '\0';
+               spoofed = 0;
+               
+               if (cptr->cs_flags & CS_POSTING)
+                  strcat(flags, "*");
+               else
+                  strcat(flags, ".");
+                  
+               if (cptr->fake_username[0])
+               {
+                  strcpy(un, cptr->fake_username);
+                  spoofed = 1;
+               }
+               else
+                  strcpy(un, cptr->curr_user);
+                  
+               if (cptr->fake_hostname[0])
+               {
+                  strcpy(host, cptr->fake_hostname);
+                  spoofed = 1;
+               }
+               else
+                  strcpy(host, cptr->cs_host);
+
+               if (cptr->fake_roomname[0])
+               {
+                  strcpy(room, cptr->fake_roomname);
+                  spoofed = 1;
+               }
+               else
+                  strcpy(room, cptr->cs_room);
+                  
+               
+                if ((aide) && (spoofed))
+                   strcat(flags, "+");
+               
+               if ((cptr->cs_flags & CS_STEALTH) && (aide))
+                  strcat(flags, "-");
+               
+               if (((cptr->cs_flags&CS_STEALTH)==0) || (aide))
+               {
+                       cprintf("%d|%s|%s|%s|%s|%ld|%s|%s\n",
+                               cptr->cs_pid, un, room,
+                               host, cptr->cs_clientname,
+                               (long)(cptr->lastidle),
+                               cptr->lastcmdname, flags);
+               }
+               if ((spoofed) && (aide))
+               {
+                       cprintf("%d|%s|%s|%s|%s|%ld|%s|%s\n",
+                               cptr->cs_pid, cptr->curr_user, cptr->cs_room,
+                               cptr->cs_host, cptr->cs_clientname,
+                               (long)(cptr->lastidle),
+                               cptr->lastcmdname, flags);
+               
+               }
+       }
+       cprintf("000\n");
+       }
+
+
+/*
+ * Terminate another running session
+ */
+void cmd_term(char *cmdbuf)
+{
+       int session_num;
+       struct CitContext *ccptr;
+
+       if (!CC->logged_in) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (CC->usersupp.axlevel < 6) {
+               cprintf("%d You must be an Aide to terminate sessions.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       session_num = extract_int(cmdbuf, 0);
+       if (session_num == CC->cs_pid) {
+               cprintf("%d You can't kill your own session.\n", ERROR);
+               return;
+               }
+
+       for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
+               if (session_num == ccptr->cs_pid) {
+                       kill_session(ccptr->cs_pid);
+                       cprintf("%d Session terminated.\n", OK);
+                       return;
+                       }
+               }
+
+       cprintf("%d No such session.\n", ERROR);
+       }
+
+
+
+
+
+/* 
+ * get the paginator prompt
+ */
+void cmd_more(void) {
+       cprintf("%d %s\n",OK,config.c_moreprompt);
+       }
+
+/*
+ * echo 
+ */
+void cmd_echo(char *etext)
+{
+       cprintf("%d %s\n",OK,etext);
+       }
+
+
+
+/* 
+ * identify as internal program
+ */
+void cmd_ipgm(char *argbuf)
+{
+       int secret;
+
+       secret = extract_int(argbuf, 0);
+       if (secret == config.c_ipgm_secret) {
+               CC->internal_pgm = 1;
+               strcpy(CC->curr_user, "<internal program>");
+               CC->cs_flags = CC->cs_flags|CS_STEALTH;
+               cprintf("%d Authenticated as an internal program.\n",OK);
+               }
+       else {
+               cprintf("%d Authentication failed.\n",ERROR);
+               }
+       }
+
+
+/*
+ * Shut down the server
+ */
+void cmd_down(void) {
+       if (!CC->logged_in) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (CC->usersupp.axlevel < 6) {
+               cprintf("%d You must be an Aide to shut down the server.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       cprintf("%d Shutting down server.  Goodbye.\n", OK);
+       master_cleanup();
+       }
+
+/*
+ * Shut down the server
+ */
+void cmd_scdn(char *argbuf)
+{
+       int new_state;
+
+       if (!CC->logged_in) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (CC->usersupp.axlevel < 6) {
+               cprintf("%d You must be an Aide to schedule a shutdown.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       new_state = extract_int(argbuf, 0);
+       if ((new_state == 0) || (new_state == 1)) {
+               ScheduledShutdown = new_state;
+               }
+       cprintf("%d %d\n", OK, ScheduledShutdown);
+       }
+
+/*
+ * main context loop
+ */
+void *context_loop(struct CitContext *con)
+{
+       char cmdbuf[256];
+       int session_num;
+
+       /*
+        * Wedge our way into the context table.
+        */
+       InitMyContext(con);
+
+       /* 
+        * Initialize some variables specific to our context.
+        */
+       CC->curr_rm = (-1);
+       CC->logged_in = 0;
+       CC->internal_pgm = 0;
+       CC->download_fp = NULL;
+       CC->upload_fp = NULL;
+       CC->cs_pid = con->client_socket;        /* not necessarily portable */
+       CC->FirstExpressMessage = NULL;
+       time(&CC->lastcmd);
+       time(&CC->lastidle);
+       strcpy(CC->lastcmdname, "    ");
+       strcpy(CC->cs_clientname, "(unknown)");
+       strcpy(CC->curr_user,"");
+       strcpy(CC->net_node,"");
+       sprintf(CC->temp,"/tmp/CitServer.%d.%d", getpid(), CC->cs_pid);
+       strcpy(CC->cs_room, "");
+       strcpy(CC->cs_host, config.c_fqdn);
+       locate_host(CC->cs_host);
+       CC->cs_flags = 0;
+       CC->upload_type = UPL_FILE;
+       CC->dl_is_net = 0;
+
+       session_num = session_count();
+       CC->nologin = 0;
+       if ((config.c_maxsessions > 0)&&(session_num >= config.c_maxsessions))
+               CC->nologin = 1;
+
+       if (CC->nologin==1) {
+          cprintf("%d %s: Too many users are already online (maximum is %d)\n",
+               ERROR+MAX_SESSIONS_EXCEEDED,
+               config.c_nodename,config.c_maxsessions);
+               }
+       else {
+          cprintf("%d %s Citadel/UX server ready.\n",OK,config.c_nodename);
+               }
+
+       lprintf(3, "citserver[%3d]: started.\n", CC->cs_pid);
+       hook_start_session(CC->cs_pid);
+
+       do {
+               time(&CC->lastcmd);
+               if (client_gets(cmdbuf) < 1) cleanup(EXIT_NULL);
+               lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
+               hook_command_received(CC->cs_pid, cmdbuf);
+
+               /*
+                * Let other clients see the last command we executed, but
+                * exclude NOOP because that would be boring.
+                */
+               if (struncmp(cmdbuf, "NOOP", 4)) {
+                       strcpy(CC->lastcmdname, "    ");
+                       strncpy(CC->lastcmdname, cmdbuf, 4);
+                       time(&CC->lastidle);
+                       }
+                       
+               if ((struncmp(cmdbuf, "ENT0", 4)) && (struncmp(cmdbuf, "MESG", 4)) && (struncmp(cmdbuf, "MSGS", 4)))
+               {
+                  CC->cs_flags &= ~CS_POSTING;
+               }
+                  
+/*
+ * This loop recognizes all server commands.
+ */
+
+               if (!struncmp(cmdbuf,"NOOP",4)) {
+                       cprintf("%d%cok\n",OK,check_express());
+                       }
+
+               else if (!struncmp(cmdbuf,"QUIT",4)) {
+                       cprintf("%d Goodbye.\n",OK);
+                       }
+
+               else if (!struncmp(cmdbuf,"LOUT",4)) {
+                       if (CC->logged_in) logout(CC);
+                       cprintf("%d logged out.\n",OK);
+                       }
+
+               else if (!struncmp(cmdbuf,"USER",4)) {
+                       cmd_user(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"PASS",4)) {
+                       cmd_pass(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"NEWU",4)) {
+                       cmd_newu(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"SETP",4)) {
+                       cmd_setp(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"LRMS",4)) {
+                       cmd_lrms(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"LKRA",4)) {
+                       cmd_lkra(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"LKRN",4)) {
+                       cmd_lkrn(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"LKRO",4)) {
+                       cmd_lkro(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"LZRM",4)) {
+                       cmd_lzrm(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"GETU",4)) {
+                       cmd_getu();
+                       }
+
+               else if (!struncmp(cmdbuf,"SETU",4)) {
+                       cmd_setu(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"GOTO",4)) {
+                       cmd_goto(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"MSGS",4)) {
+                       cmd_msgs(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"WHOK",4)) {
+                       cmd_whok();
+                       }
+
+               else if (!struncmp(cmdbuf,"RDIR",4)) {
+                       cmd_rdir();
+                       }
+
+               else if (!struncmp(cmdbuf,"MSG0",4)) {
+                       cmd_msg0(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"MSG2",4)) {
+                       cmd_msg2(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"MSG3",4)) {
+                       cmd_msg3(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"INFO",4)) {
+                       cmd_info();
+                       }
+
+               else if (!struncmp(cmdbuf,"SLRP",4)) {
+                       cmd_slrp(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"INVT",4)) {
+                       cmd_invt_kick(&cmdbuf[5],1);
+                       }
+
+               else if (!struncmp(cmdbuf,"KICK",4)) {
+                       cmd_invt_kick(&cmdbuf[5],0);
+                       }
+
+               else if (!struncmp(cmdbuf,"GETR",4)) {
+                       cmd_getr();
+                       }
+
+               else if (!struncmp(cmdbuf,"SETR",4)) {
+                       cmd_setr(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"GETA",4)) {
+                       cmd_geta();
+                       }
+
+               else if (!struncmp(cmdbuf,"SETA",4)) {
+                       cmd_seta(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"ENT0",4)) {
+                       cmd_ent0(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"ENT3",4)) {
+                       cmd_ent3(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"RINF",4)) {
+                       cmd_rinf();
+                       }
+
+               else if (!struncmp(cmdbuf,"DELE",4)) {
+                       cmd_dele(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"KILL",4)) {
+                       cmd_kill(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"CRE8",4)) {
+                       cmd_cre8(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"MOVE",4)) {
+                       cmd_move(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"FORG",4)) {
+                       cmd_forg();
+                       }
+
+               else if (!struncmp(cmdbuf,"MESG",4)) {
+                       cmd_mesg(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"EMSG",4)) {
+                       cmd_emsg(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"GNUR",4)) {
+                       cmd_gnur();
+                       }
+
+               else if (!struncmp(cmdbuf,"GREG",4)) {
+                       cmd_greg(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"VALI",4)) {
+                       cmd_vali(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"EINF",4)) {
+                       cmd_einf(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"LIST",4)) {
+                       cmd_list();
+                       }
+
+               else if (!struncmp(cmdbuf,"REGI",4)) {
+                       cmd_regi();
+                       }
+
+               else if (!struncmp(cmdbuf,"CHEK",4)) {
+                       cmd_chek();
+                       }
+
+               else if (!struncmp(cmdbuf,"DELF",4)) {
+                       cmd_delf(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"MOVF",4)) {
+                       cmd_movf(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"NETF",4)) {
+                       cmd_netf(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"RWHO",4)) {
+                       cmd_rwho();
+                       }
+
+               else if (!struncmp(cmdbuf,"OPEN",4)) {
+                       cmd_open(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"CLOS",4)) {
+                       cmd_clos();
+                       }
+
+               else if (!struncmp(cmdbuf,"UOPN",4)) {
+                       cmd_uopn(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"UCLS",4)) {
+                       cmd_ucls(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"READ",4)) {
+                       cmd_read(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"WRIT",4)) {
+                       cmd_writ(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"QUSR",4)) {
+                       cmd_qusr(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"ECHO",4)) {
+                       cmd_echo(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"OIMG",4)) {
+                       cmd_oimg(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"MORE",4)) {
+                       cmd_more();
+                       }
+
+               else if (!struncmp(cmdbuf,"NETP",4)) {
+                       cmd_netp(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"NDOP",4)) {
+                       cmd_ndop(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"NUOP",4)) {
+                       cmd_nuop(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"LFLR",4)) {
+                       cmd_lflr();
+                       }
+
+               else if (!struncmp(cmdbuf,"CFLR",4)) {
+                       cmd_cflr(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"KFLR",4)) {
+                       cmd_kflr(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"EFLR",4)) {
+                       cmd_eflr(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"IDEN",4)) {
+                       cmd_iden(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"IPGM",4)) {
+                       cmd_ipgm(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"CHAT",4)) {
+                       cmd_chat(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"PEXP",4)) {
+                       cmd_pexp();
+                       }
+
+               else if (!struncmp(cmdbuf,"SEXP",4)) {
+                       cmd_sexp(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"EBIO",4)) {
+                       cmd_ebio();
+                       }
+
+               else if (!struncmp(cmdbuf,"RBIO",4)) {
+                       cmd_rbio(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"LBIO",4)) {
+                       cmd_lbio();
+                       }
+
+               else if (!struncmp(cmdbuf,"STEL",4)) {
+                       cmd_stel(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"TERM",4)) {
+                       cmd_term(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf,"DOWN",4)) {
+                       cmd_down();
+                       }
+
+               else if (!struncmp(cmdbuf,"SCDN",4)) {
+                       cmd_scdn(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf, "NSET", 4)) {
+                       cmd_nset(&cmdbuf[5]);
+                       }
+
+               else if (!struncmp(cmdbuf, "UIMG", 4)) {
+                       cmd_uimg(&cmdbuf[5]);
+                       }
+               else if (!struncmp(cmdbuf, "UCHG", 4)) {
+                       cmd_uchg(&cmdbuf[5]);
+                       }
+               else if (!struncmp(cmdbuf, "TIME", 4)) {
+                       cmd_time(&cmdbuf[5]);
+                       }
+               else if (!struncmp(cmdbuf, "HCHG", 4)) {
+                       cmd_hchg(&cmdbuf[5]);
+                       }
+               else if (!struncmp(cmdbuf, "RCHG", 4)) {
+                       cmd_rchg(&cmdbuf[5]);
+                       }
+               else {
+                       cprintf("%d Unrecognized or unsupported command.\n",
+                               ERROR);
+                       }
+
+               } while(struncmp(cmdbuf,"QUIT",4));
+
+       cleanup(EXIT_NORMAL);
+       return(NULL);
+       }
diff --git a/citadel/client_chat.c b/citadel/client_chat.c
new file mode 100644 (file)
index 0000000..6316a13
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * Citadel/UX
+ *
+ * client_chat.c  --  front end for chat mode
+ *                    (the "single process" version - no more fork() anymore)
+ *
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <string.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#ifdef NEED_SELECT_H
+#include <sys/select.h>
+#endif
+#include "citadel.h"
+
+extern char fullname[];
+
+int inkey();
+void set_keepalives();
+int num_parms();
+void extract();
+int struncmp();
+int getsockfd();
+char serv_getc();
+void color();
+
+
+void chatmode() {
+       char wbuf[256];
+       char buf[256];
+       char c_user[256];
+       char c_text[256];
+       char c_room[256];
+       char last_user[256];
+       int send_complete_line;
+       int recv_complete_line;
+       char ch;
+       int a,pos;
+       
+       fd_set rfds;
+       struct timeval tv;
+       int retval;
+
+       serv_puts("CHAT");
+       serv_gets(buf);
+       if (buf[0]!='8') {
+               printf("%s\n",&buf[4]);
+               return;
+               }
+
+       printf("Entering chat mode (type /quit to exit, /help for other cmds)\n");
+       set_keepalives(KA_CHAT);
+
+       strcpy(buf,"");
+       strcpy(wbuf,"");
+       color(3);
+       printf("> ");
+       send_complete_line = 0;
+       recv_complete_line = 0;
+
+       while(1) {
+           fflush(stdout);
+           FD_ZERO(&rfds);
+           FD_SET(0,&rfds);
+           FD_SET(getsockfd(),&rfds);
+           tv.tv_sec = S_KEEPALIVE;
+           tv.tv_usec = 0;
+           retval = select(getsockfd()+1, &rfds, NULL, NULL, &tv);
+
+           if (FD_ISSET(getsockfd(), &rfds)) {
+               ch = serv_getc();
+               if (ch == 10) {
+                       recv_complete_line = 1;
+                       goto RCL; /* ugly, but we've gotta get out! */
+                       }
+               else {
+                       buf[strlen(buf) + 1] = 0;
+                       buf[strlen(buf)] = ch;
+                       }
+               goto RCL;
+               }
+
+           if (FD_ISSET(0, &rfds)) {
+               ch = inkey();
+               if ((ch == 10) || (ch == 13)) {
+                       send_complete_line = 1;
+                       }
+               else if ((ch == 8) || (ch == 127)) {
+                       if (strlen(wbuf) > 0) {
+                               wbuf[strlen(wbuf)-1] = 0;
+                               printf("%c %c",8,8);
+                               }
+                       }
+               else {
+                       putc(ch,stdout);
+                       wbuf[strlen(wbuf) + 1] = 0;
+                       wbuf[strlen(wbuf)] = ch;
+                       }
+               }
+
+
+       /* if the user hit return, send the line */
+RCL:       if (send_complete_line) {
+               serv_puts(wbuf);
+               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; a<strlen(wbuf); ++a) {
+                       if (wbuf[a] == 32) pos = a;
+                       }
+               if (pos == 0) {
+                       serv_puts(wbuf);
+                       strcpy(wbuf, "");
+                       send_complete_line = 0;
+                       }
+               else {
+                       wbuf[pos] = 0;
+                       serv_puts(wbuf);
+                       strcpy(wbuf,&wbuf[pos+1]);
+                       }
+               }
+
+           if (recv_complete_line) {   
+               printf("\r%79s\r","");
+               if (!strcmp(buf,"000")) {
+                       color(7);
+                       printf("Exiting chat mode\n");
+
+                       fflush(stdout);
+                       set_keepalives(KA_YES);
+                       return;
+                       }
+               if (num_parms(buf)>=2) {
+                       extract(c_user,buf,0);
+                       extract(c_text,buf,1);
+                       if (num_parms(buf)>2)
+                       {
+                          extract(c_room,buf,2);
+                          printf("Got room %s\n", c_room);
+                       }
+                          
+                       if (strucmp(c_text,"NOOP")) {
+                               if (!strcmp(c_user, fullname)) {
+                                       color(3);
+                                       }
+                               else if (!strcmp(c_user,":")) {
+                                       color(1);
+                                       }
+                               else {
+                                       color(2);
+                                       }
+                               if (strcmp(c_user,last_user)) {
+                                       sprintf(buf,"%s: %s",c_user,c_text);
+                                       }
+                               else {
+                                       sprintf(buf,"%40s","");
+                                       sprintf(&buf[strlen(c_user)+2],
+                                               "%s",c_text);
+                                       }
+                               while (strlen(buf)<79) strcat(buf," ");
+                               if (strcmp(c_user,last_user)) {
+                                       printf("\r%79s\n","");
+                                       strcpy(last_user,c_user);
+                                       }
+                               printf("\r%s\n",buf);
+                               fflush(stdout);
+                               }
+                       }
+               color(3);
+               printf("> %s",wbuf);
+               recv_complete_line = 0;
+               strcpy(buf,"");
+               }
+           }
+       }
+
+
diff --git a/citadel/commands.c b/citadel/commands.c
new file mode 100644 (file)
index 0000000..77cb830
--- /dev/null
@@ -0,0 +1,885 @@
+/*
+ * Citadel/UX
+ *
+ * commands.c - front end for Citadel
+ *
+ * This version is the traditional command parser for room prompts.
+ *
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/time.h>
+
+#ifdef POSIX_TERMIO
+#include <termios.h>
+#else
+#include <sgtty.h>
+#endif
+
+#ifdef NEED_SELECT_H
+#include <sys/select.h>
+#endif
+
+
+#include <signal.h>
+#include <errno.h>
+#include "citadel.h"
+
+struct citcmd {
+       struct citcmd *next;
+       int c_cmdnum;
+       int c_axlevel;
+       char c_keys[5][64];
+       };
+
+#define IFNEXPERT if ((userflags&US_EXPERT)==0)
+
+
+extern unsigned room_flags;
+extern char room_name[];
+extern struct serv_info serv_info;
+extern char axlevel;
+extern char is_room_aide;
+extern unsigned userflags;
+extern char sigcaught;
+extern char editor_path[];
+extern char printcmd[];
+extern char have_xterm;
+extern char rc_username[];
+extern char rc_password[];
+extern char rc_floor_mode;
+char rc_exp_beep;
+char rc_exp_cmd[256];
+extern int lines_printed;
+extern char express_msgs;
+
+char *gl_string;
+
+struct citcmd *cmdlist = NULL;
+
+void sighandler();
+void logoff();
+int struncmp();
+void formout();
+int room_prompt();
+void back();
+int checkpagin();
+void color();
+
+
+/* these variables are local to this module */
+char keepalives_enabled = KA_YES;      /* send NOOPs to server when idle */
+int ok_to_interrupt = 0;               /* print express msgs asynchronously */
+time_t AnsiDetect;                     /* when did we send the detect code? */
+int enable_color = 0;                  /* nonzero for ANSI color */
+
+
+/*
+ * print_express()  -  print express messages if there are any
+ */
+void print_express() {
+       char buf[256];
+       FILE *outpipe;
+
+       if (express_msgs == 0) return;
+       express_msgs = 0;
+       serv_puts("PEXP");
+       serv_gets(buf);
+       if (buf[0]!='1') return;
+
+       if (strlen(rc_exp_cmd) > 0) {
+               outpipe = popen(rc_exp_cmd, "w");
+               if (outpipe != NULL) {
+                       while (serv_gets(buf), strcmp(buf,"000")) {
+                               fprintf(outpipe, "%s\n", buf);
+                               }
+                       pclose(outpipe);
+                       return;
+                       }
+               }
+
+       /* fall back to built-in express message display */
+       if (rc_exp_beep) {
+               putc(7,stdout);
+               }
+       color(1);
+       printf("---\n");
+       while (serv_gets(buf), strcmp(buf,"000")) {
+               printf("%s\n",buf);
+               }
+       printf("---\n");
+       color(7);
+       }
+
+
+void set_keepalives(s)
+int s; {
+       keepalives_enabled = (char)s;
+       }
+
+/* 
+ * This loop handles the "keepalive" messages sent to the server when idling.
+ */
+void do_keepalive() {
+       char buf[256];
+       static long idlet = 0L;
+       long now;
+
+       time(&now);
+       if ((now - idlet) < ((long)S_KEEPALIVE)) return;
+       time(&idlet);
+
+       if (keepalives_enabled != KA_NO) {
+               serv_puts("NOOP");
+               if (keepalives_enabled == KA_YES) {
+                       serv_gets(buf);
+                       if (buf[3]=='*') {
+                               express_msgs = 1;
+                               if (ok_to_interrupt == 1) {
+                                       printf("\r                    \r");
+                                       print_express();
+                                       printf("%s%c ",room_name,
+                                               room_prompt(room_flags));
+                                       fflush(stdout); 
+                                       }
+                               }
+                       }
+               }
+       }
+
+
+int inkey() {          /* get a character from the keyboard, with   */
+       int a;          /* the watchdog timer in effect if necessary */
+        fd_set rfds;
+        struct timeval tv;
+       long start_time, now;
+       char inbuf[2];
+
+       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();
+                       fflush(stdout);
+                       FD_ZERO(&rfds);
+                       FD_SET(0,&rfds);
+                       tv.tv_sec = S_KEEPALIVE;
+                       tv.tv_usec = 0;
+
+                       time(&now);
+                       if (((now-start_time) > SLEEPING)
+                          && (SLEEPING != 0) && (getppid()==1)) {
+                               printf("Sleeping? Call again.\n");
+                               logoff(SIGALRM);
+                               }
+
+                       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...)
+                */
+               read(0, inbuf, 1);
+               a = inbuf[0];
+               if (a==127) a=8;
+               if (a>126) a=0;
+               if (a==10) a=13;
+               if (((a!=4)&&(a!=13)&&(a!=8)&&(a!=NEXT_KEY)&&(a!=STOP_KEY))
+                       && ((a<32)||(a>126))) a=0;
+               } while(a==0);
+       return(a);
+       }
+
+void getline(string,lim)       /* Gets a line from the terminal */
+char string[];                 /* Pointer to string buffer */
+int lim;               /* Maximum length - if negative, no-show */
+{
+       int a,b;
+       char flag = 0;
+
+       if (lim<0) { lim=(0-lim); flag=1; }
+       strcpy(string,"");
+       gl_string = string;
+GLA:   a=inkey(); a=(a&127);
+       if ((a==8)&&(strlen(string)==0)) goto GLA;
+       if ((a!=13)&&(a!=8)&&(strlen(string)==lim)) goto GLA;
+       if ((a==8)&&(string[0]!=0)) {
+               string[strlen(string)-1]=0;
+               putc(8,stdout); putc(32,stdout); putc(8,stdout); goto GLA; }
+       if ((a==13)||(a==10)) {
+               putc(13,stdout);
+               putc(10,stdout);
+               return;
+               }
+       if (a<32) a='.';
+       b=strlen(string);
+       string[b]=a;
+       string[b+1]=0;
+       if (flag==0) putc(a,stdout);
+       if (flag==1) putc('*',stdout);
+       goto GLA;
+       }
+
+
+/*
+ * strprompt()  -  prompt for a string, print the existing value and
+ *                 allow the user to press return to keep it...
+ */
+void strprompt(prompt,str,len)
+char *prompt;
+char *str;
+int len; {
+       char buf[128];
+       print_express();
+       color(3);
+       printf("%s [", prompt);
+       color(1);
+       printf("%s", str);
+       color(3);
+       printf("]: ");
+       color(2);
+       getline(buf,len);
+       color(7);
+       if (buf[0]!=0) strcpy(str,buf);
+       }
+
+/* 
+ * intprompt()  -  like strprompt(), except for an integer
+ *                 (note that it RETURNS the new value!)
+ */
+int intprompt(prompt,ival,imin,imax)
+char *prompt;
+int ival;
+int imin;
+int imax; {
+       char buf[16];
+       int i;
+       i = ival;
+       do {
+               sprintf(buf,"%d",i);
+               strprompt(prompt,buf,15);
+               i=atoi(buf);
+               if (i<imin) printf("*** Must be no less than %d.\n",imin);
+               if (i>imax) 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)
+ */
+void newprompt(prompt,str,len)
+char *prompt;
+char *str;
+int len; {
+       color(3);
+       printf("%s",prompt);
+       color(2);
+       getline(str,len);
+       color(7);
+       }
+
+
+int lkey() {   /* 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() {
+       FILE *ccfile;
+       char buf[256];
+       struct citcmd *cptr;
+       int a,d;
+       int b = 0;
+
+
+       /* first, set up some defaults for non-required variables */
+
+       strcpy(editor_path,"");
+       strcpy(printcmd,"");
+       strcpy(rc_username,"");
+       strcpy(rc_password,"");
+       rc_floor_mode = 0;
+       rc_exp_beep = 1;
+       strcpy(rc_exp_cmd, "");
+
+       /* now try to open the citadel.rc file */
+
+       ccfile = NULL;
+       if (getenv("HOME") != NULL) {
+               sprintf(buf,"%s/.citadelrc",getenv("HOME"));
+               ccfile = fopen(buf,"r");
+               }
+       if (ccfile==NULL) {
+               ccfile = fopen("/usr/local/lib/citadel.rc","r");
+               }
+       if (ccfile==NULL) {
+               sprintf(buf,"%s/citadel.rc",BBSDIR);
+               ccfile = fopen(buf,"r");
+               }
+       if (ccfile==NULL) {
+               perror("commands: cannot open citadel.rc");
+               logoff(errno);
+               }
+
+       while (fgets(buf, 256, ccfile) != NULL) {
+            while ( (strlen(buf)>0) ? (isspace(buf[strlen(buf)-1])) : 0 )
+               buf[strlen(buf)-1] = 0;
+
+           if (!struncmp(buf,"editor=",7))
+               strcpy(editor_path,&buf[7]);
+
+           if (!struncmp(buf,"printcmd=",9))
+               strcpy(printcmd,&buf[9]);
+
+           if (!struncmp(buf,"expcmd=",7))
+               strcpy(rc_exp_cmd,&buf[7]);
+
+           if (!struncmp(buf,"local_screen_dimensions=",24))
+               have_xterm = (char)atoi(&buf[24]);
+
+           if (!struncmp(buf,"use_floors=",11)) {
+               if (!strucmp(&buf[11],"yes")) rc_floor_mode = RC_YES;
+               if (!strucmp(&buf[11],"no")) rc_floor_mode = RC_NO;
+               if (!strucmp(&buf[11],"default")) rc_floor_mode = RC_DEFAULT;
+               }
+
+           if (!struncmp(buf,"beep=",5)) {
+               rc_exp_beep = atoi(&buf[5]);
+               }
+
+           if (!struncmp(buf,"username=",9))
+               strcpy(rc_username,&buf[9]);
+
+           if (!struncmp(buf,"password=",9))
+               strcpy(rc_password,&buf[9]);
+
+           if (!struncmp(buf,"cmd=",4)) {
+               strcpy(buf,&buf[4]);
+
+               cptr = (struct citcmd *) malloc (sizeof(struct citcmd));
+               
+               cptr->c_cmdnum = atoi (buf);
+               for (d=strlen(buf); d>=0; --d)
+                       if (buf[d]==',') b=d;
+               strcpy (buf, &buf[b+1]);
+
+               cptr->c_axlevel = atoi (buf);
+               for (d=strlen(buf); d>=0; --d)
+                       if (buf[d]==',') b=d;
+               strcpy (buf, &buf[b+1]);
+
+               for (a=0; a<5; ++a) cptr->c_keys[a][0] = 0;
+
+               a = 0;  b = 0;
+               buf[strlen(buf)+1] = 0;
+               while (strlen(buf) > 0) {
+                       b = strlen(buf);
+                       for (d=strlen(buf); d>=0; --d)
+                               if (buf[d]==',') b=d;
+                       strncpy(cptr->c_keys[a],buf,b);
+                       cptr->c_keys[a][b] = 0;
+                       if (buf[b]==',')
+                               strcpy(buf,&buf[b+1]);
+                       else
+                               strcpy(buf,"");
+                       ++a;
+                       }
+
+               cptr->next = cmdlist;
+               cmdlist = cptr;
+
+               }
+           }
+       fclose(ccfile);
+       }
+
+
+
+/*
+ * return the key associated with a command
+ */
+char keycmd(cmdstr)
+char cmdstr[]; {
+       int a;
+
+       for (a=0; a<strlen(cmdstr); ++a)
+               if (cmdstr[a]=='&')
+                       return(tolower(cmdstr[a+1]));
+       return(0);
+       }
+
+
+/*
+ * Output the string from a key command without the ampersand
+ * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
+ */
+char *cmd_expand(strbuf,mode)
+char strbuf[];
+int mode; {
+       int a;
+       static char exp[64];
+       char buf[256];
+               
+       strcpy(exp,strbuf);
+       
+       for (a=0; a<strlen(exp); ++a) {
+               if (strbuf[a] == '&') {
+
+                   if (mode == 0) {
+                       strcpy(&exp[a],&exp[a+1]);
+                       }
+
+                   if (mode == 1) {
+                       exp[a] = '<';
+                       strcpy(buf,&exp[a+2]);
+                       exp[a+2] = '>';
+                       exp[a+3] = 0;
+                       strcat(exp,buf);
+                       }
+
+                   }
+
+               if (!strncmp(&exp[a],"^r",2)) {
+                       strcpy(buf,exp);
+                       strcpy(&exp[a],room_name);
+                       strcat(exp,&buf[a+2]);
+                       }
+
+               if (!strncmp(&exp[a],"^c",2)) {
+                       exp[a] = ',';
+                       strcpy(&exp[a+1],&exp[a+2]);
+                       }
+
+               }
+
+       return(exp);
+       }
+
+
+
+/*
+ * Comparison function to determine if entered commands match a
+ * command loaded from the config file.
+ */
+int cmdmatch(cmdbuf,cptr,ncomp)
+char cmdbuf[];
+struct citcmd *cptr;
+int ncomp; {
+       int a;
+       int cmdax;
+
+       cmdax = 0;
+       if (is_room_aide) cmdax = 1;
+       if (axlevel >=6) cmdax = 2;
+
+       for (a=0; a<ncomp; ++a) {
+               if ( (tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
+                  || (cptr->c_axlevel > cmdax) )
+                       return(0);
+               }
+       return(1);
+       }
+
+
+/*
+ * This function returns 1 if a given command requires a string input
+ */
+int requires_string(cptr,ncomp)
+struct citcmd *cptr;
+int ncomp; {
+       int a;
+       char buf[64];
+       
+       strcpy(buf,cptr->c_keys[ncomp-1]);
+       for (a=0; a<strlen(buf); ++a) {
+               if (buf[a]==':') return(1);
+               }
+       return(0);
+       }
+
+
+/*
+ * Input a command at the main prompt.
+ * This function returns an integer command number.  If the command prompts
+ * for a string then it is placed in the supplied buffer.
+ */
+int getcmd(argbuf)
+char *argbuf;  {
+       char cmdbuf[5];
+       int cmdspaces[5];
+       int cmdpos;
+       int ch;
+       int a;
+       int got;
+       struct citcmd *cptr;
+
+       /* if we're running in idiot mode, display a cute little menu */
+       IFNEXPERT formout("mainmenu");
+
+       print_express(); /* print express messages if there are any */
+       strcpy(argbuf, "");
+       cmdpos = 0;
+       for (a=0; a<5; ++a) cmdbuf[a]=0;
+       /* now the room prompt... */
+       ok_to_interrupt = 1;
+       printf("\n%s%c ",room_name,room_prompt(room_flags));
+       fflush(stdout);
+       
+       while(1) {
+               ch = inkey();
+               ok_to_interrupt = 0;
+
+               if ( (ch == 8) && (cmdpos > 0) ) {
+                       back(cmdspaces[cmdpos-1] + 1);
+                       cmdbuf[cmdpos] = 0;
+                       --cmdpos;
+                       }
+
+               cmdbuf[cmdpos] = tolower(ch);
+
+               for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
+                       if (cmdmatch(cmdbuf,cptr,cmdpos+1)) {
+                               
+                               printf("%s",cmd_expand(cptr->c_keys[cmdpos],0));
+                               cmdspaces[cmdpos] = strlen(
+                                       cmd_expand(cptr->c_keys[cmdpos],0) );
+                               if (cmdpos<4) 
+                                       if ((cptr->c_keys[cmdpos+1]) != 0)
+                                               putc(' ',stdout);
+                               ++cmdpos;
+                               }
+                       }
+
+               for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
+                       if (cmdmatch(cmdbuf,cptr,5)) {
+                               if (requires_string(cptr,cmdpos)) {
+                                       getline(argbuf,32);
+                                       }
+                               else {
+                                       printf("\n");
+                                       }
+                               return(cptr->c_cmdnum);
+                               }
+                       }
+
+               if (ch == '?') {
+                       printf("\rOne of ...                         \n");
+                       for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
+                           if (cmdmatch(cmdbuf,cptr,cmdpos)) {
+                               for (a=0; a<5; ++a) {
+                                   printf("%s ",cmd_expand(cptr->c_keys[a],1));
+                                   }
+                               printf("\n");
+                               }
+                           }
+
+                       printf("\n%s%c ",room_name,room_prompt(room_flags));
+                       got = 0;
+                       for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
+                           if ((got==0)&&(cmdmatch(cmdbuf,cptr,cmdpos))) {
+                               for (a=0; a<cmdpos; ++a) {
+                                   printf("%s ",
+                                       cmd_expand(cptr->c_keys[a],0));
+                                   }
+                               got = 1;
+                               }
+                           }
+                       }
+
+               }
+
+       }
+
+
+
+
+
+/*
+ * set tty modes.  commands are:
+ * 
+ * 0 - set to bbs mode, intr/quit disabled
+ * 1 - set to bbs mode, intr/quit enabled
+ * 2 - save current settings for later restoral
+ * 3 - restore saved settings
+ */
+#ifdef POSIX_TERMIO
+void sttybbs(cmd)              /* SysV version of sttybbs() */
+int cmd; {
+       struct termios live;
+       static struct termios saved_settings;
+
+       if ( (cmd == 0) || (cmd == 1) ) {
+               tcgetattr(0,&live);
+               live.c_iflag=ISTRIP|IXON|IXANY;
+               live.c_oflag=OPOST|ONLCR;
+               live.c_lflag=NOFLSH;
+               if (cmd==1) live.c_lflag=ISIG|NOFLSH;
+
+               if (cmd==SB_YES_INTR) {
+                       live.c_cc[VINTR]=NEXT_KEY;
+                       live.c_cc[VQUIT]=STOP_KEY;
+                       signal(SIGINT,*sighandler);
+                       signal(SIGQUIT,*sighandler);
+                       }
+               else {
+                       signal(SIGINT,SIG_IGN);
+                       signal(SIGQUIT,SIG_IGN);
+                       live.c_cc[VINTR]=(-1);
+                       live.c_cc[VQUIT]=(-1);
+                       }
+
+               /* do we even need this stuff anymore? */
+               /* live.c_line=0; */
+               live.c_cc[VERASE]=8;
+               live.c_cc[VKILL]=24;
+               live.c_cc[VEOF]=1;
+               live.c_cc[VEOL]=255;
+               live.c_cc[VEOL2]=0;
+               live.c_cc[VSTART]=0;
+               tcsetattr(0,TCSADRAIN,&live);
+               }
+       if (cmd == 2) {
+               tcgetattr(0,&saved_settings);
+               }
+       if (cmd == 3) {
+               tcsetattr(0,TCSADRAIN,&saved_settings);
+               }
+       }
+#else
+void sttybbs(cmd)              /* BSD version of sttybbs() */
+int cmd; {
+       struct sgttyb live;
+       static struct sgttyb saved_settings;
+
+       if ( (cmd == 0) || (cmd == 1) ) {
+               gtty(0,&live);
+               live.sg_flags |= CBREAK;
+               live.sg_flags |= CRMOD;
+               live.sg_flags |= NL1;
+               live.sg_flags &= ~ECHO;
+               if (cmd==1) live.sg_flags |= NOFLSH;
+               stty(0,&live);
+               }
+       if (cmd == 2) {
+               gtty(0,&saved_settings);
+               }
+       if (cmd == 3) {
+               stty(0,&saved_settings);
+               }
+       }
+#endif
+
+
+/*
+ * display_help()  -  help file viewer
+ */
+void display_help(name)
+char name[]; {
+       formout(name);
+       }
+
+
+/*
+ * fmout()  -  Citadel text formatter and paginator
+ */
+int fmout(width,fp,pagin,height,starting_lp,subst)
+int width;             /* screen width to use */
+FILE *fp;              /* file to read from, or NULL to read from server */
+char pagin;            /* nonzero if we should use the paginator */
+int height;            /* screen height to use */
+int starting_lp;       /* starting value for lines_printed, -1 for global */
+char subst;            /* nonzero if we should use hypertext mode */
+       {
+       int a,b,c,d,old;
+       int real = (-1);
+       char aaa[140];
+       char buffer[512];
+       int eof_flag = 0;
+
+       if (starting_lp >= 0) { 
+               lines_printed = starting_lp;
+               }
+       strcpy(aaa,""); old=255;
+       strcpy(buffer,"");
+       c=1; /* c is the current pos */
+
+       sigcaught = 0;
+       sttybbs(1);
+
+FMTA:  while ( (eof_flag==0) && (strlen(buffer)<126) ) {
+               if (sigcaught) goto OOPS;
+               if (fp!=NULL) { /* read from file */
+                       if (feof(fp)) eof_flag = 1;
+                       if (eof_flag==0) {
+                               a=getc(fp);
+                               buffer[strlen(buffer)+1] = 0;
+                               buffer[strlen(buffer)] = a;
+                               }
+                       }
+               else {          /* read from server */
+                       d=strlen(buffer);
+                       serv_gets(&buffer[d]);
+while ( (!isspace(buffer[d])) && (isspace(buffer[strlen(buffer)-1])) )
+       buffer[strlen(buffer)-1]=0;
+                       if (!strcmp(&buffer[d],"000")) {
+                               buffer[d] = 0;
+                               eof_flag = 1;
+                               while(isspace(buffer[strlen(buffer)-1]))
+                                       buffer[strlen(buffer)-1] = 0;
+                               }
+                       d=strlen(buffer);
+                       buffer[d] = 10;
+                       buffer[d+1] = 0;
+                       }
+               }
+
+       buffer[strlen(buffer)+1] = 0;
+       a=buffer[0];
+       strcpy(buffer,&buffer[1]);
+       
+       old=real;
+       real=a;
+       if (a<=0) goto FMTEND;
+       
+       if ( ((a==13)||(a==10)) && (old!=13) && (old!=10) ) a=32;
+       if ( ((old==13)||(old==10)) && (isspace(real)) ) {
+               printf("\n");
+               ++lines_printed;
+               lines_printed = checkpagin(lines_printed,pagin,height);
+               c=1;
+               }
+       if (a>126) goto FMTA;
+
+       if (a>32) {
+       if ( ((strlen(aaa)+c)>(width-5)) && (strlen(aaa)>(width-5)) ) {
+               printf("\n%s",aaa); c=strlen(aaa); aaa[0]=0;
+               ++lines_printed;
+               lines_printed = checkpagin(lines_printed,pagin,height);
+               }
+           b=strlen(aaa); aaa[b]=a; aaa[b+1]=0;
+           }
+       if (a==32) {
+               if ((strlen(aaa)+c)>(width-5)) { 
+                       c=1;
+                       printf("\n");
+                       ++lines_printed;
+                       lines_printed = checkpagin(lines_printed,pagin,height);
+                       }
+               printf("%s ",aaa); ++c; c=c+strlen(aaa);
+               strcpy(aaa,"");
+               goto FMTA;
+               }
+       if ((a==13)||(a==10)) {
+               printf("%s\n",aaa);
+               c=1;
+               ++lines_printed;
+               lines_printed = checkpagin(lines_printed,pagin,height);
+               strcpy(aaa,"");
+               goto FMTA;
+               }
+       goto FMTA;
+
+       /* signal caught; drain the server */
+OOPS:  do {
+               serv_gets(aaa);
+               } while(strcmp(aaa,"000"));
+
+FMTEND:        printf("\n");
+       ++lines_printed;
+       lines_printed = checkpagin(lines_printed,pagin,height);
+       return(sigcaught);
+}
+
+
+/*
+ * support ANSI color if defined
+ */
+void color(colornum)
+int colornum;  {
+
+#ifdef ANSI_COLOR
+       if (enable_color) {
+               fflush(stdout);
+               printf("%c[3%dm%c[1m", 27, colornum, 27);
+               fflush(stdout);
+               }
+#endif
+       }
+
+void cls() {
+#ifdef ANSI_COLOR
+       fflush(stdout);
+       printf("%c[2J%c[H", 27, 27);
+       fflush(stdout);
+#endif
+       }
+
+
+/*
+ * Detect whether ANSI color is available (answerback)
+ */
+void send_ansi_detect() {
+#ifdef ANSI_COLOR
+       printf("%c[c", 27);
+       fflush(stdout);
+       time(&AnsiDetect);
+#endif
+       }
+
+void look_for_ansi() {
+#ifdef ANSI_COLOR
+        fd_set rfds;
+        struct timeval tv;
+       char abuf[512];
+       time_t now;
+       int a;
+
+       strcpy(abuf, "");
+
+       time(&now);
+       if ( (now - AnsiDetect) < 2 ) sleep(1);
+
+       do {
+               FD_ZERO(&rfds);
+               FD_SET(0,&rfds);
+               tv.tv_sec = 0;
+               tv.tv_usec = 1;
+
+               select(1, &rfds, NULL, NULL, &tv);
+               if (FD_ISSET(0, &rfds)) {
+                       abuf[strlen(abuf)+1] = 0;
+                       read(0, &abuf[strlen(abuf)], 1);
+                       }
+
+               } while (FD_ISSET(0, &rfds));
+
+       for (a=0; a<strlen(abuf); ++a) {
+               if ( (abuf[a] == 27) && (abuf[a+1] == '[')
+                  && (abuf[a+2] == '?') ) {
+                       enable_color = 1;
+                       }
+               }
+#endif
+       }
diff --git a/citadel/config.c b/citadel/config.c
new file mode 100644 (file)
index 0000000..fde50fe
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * This function reads the citadel.config file.  It should be called at
+ * the beginning of EVERY Citadel program.
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include "citadel.h"
+
+extern void get_config(void);
+struct config config;
+char bbs_home_directory[256];
+int home_specified = 0;
+
+void get_config(void) {
+       FILE *cfp;
+
+       if (chdir( home_specified ? bbs_home_directory : BBSDIR ) != 0) {
+               fprintf(stderr, "Cannot start.\nThere is no Citadel installation in %s\n%s\n",
+                       (home_specified ? bbs_home_directory : BBSDIR),
+                       strerror(errno));
+               exit(errno);
+               }
+       cfp=fopen("citadel.config","r");
+       if (cfp==NULL) {
+               fprintf(stderr, "Cannot start.\n");
+               fprintf(stderr, "There is no citadel.config in %s\n%s\n",
+                       (home_specified ? bbs_home_directory : BBSDIR),
+                       strerror(errno));
+               exit(errno);
+               }
+       fread((char *)&config,sizeof(struct config),1,cfp);
+       fclose(cfp);
+       if ( (config.c_setup_level / 10) != (REV_LEVEL/10) ) {
+               fprintf(stderr, "config: Your data files are out of date.  ");
+               fprintf(stderr, "Run setup to update them.\n");
+               fprintf(stderr,
+                       "        This program requires level %d.%02d\n",
+                               (REV_LEVEL / 100), (REV_LEVEL % 100) );
+               fprintf(stderr,
+                       "        Data files are currently at %d.%02d\n",
+                               (config.c_setup_level / 100),
+                               (config.c_setup_level % 100) );
+               exit(1);
+               }
+       }
diff --git a/citadel/control.c b/citadel/control.c
new file mode 100644 (file)
index 0000000..4dec1f0
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * control.c
+ *
+ * This module handles states which are global to the entire server.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <time.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+#include <syslog.h>
+#include "citadel.h"
+#include "server.h"
+#include "proto.h"
+
+struct CitControl CitControl;
+
+/*
+ * get_control  -  read the control record into memory.
+ */
+void get_control() {
+       FILE *fp;
+
+       /* Zero it out.  If the control record on disk is missing or short,
+        * the system functions with all control record fields initialized
+        * to zero.
+        */
+       bzero(&CitControl, sizeof(struct CitControl));
+       fp = fopen("citadel.control", "rb");
+       if (fp == NULL) return;
+
+       fread(&CitControl, sizeof(struct CitControl), 1, fp);
+       fclose(fp);
+       }
+
+/*
+ * put_control  -  write the control record to disk.
+ */
+void put_control() {
+       FILE *fp;
+
+       fp = fopen("citadel.control", "wb");
+       if (fp != NULL) {
+               fwrite(&CitControl, sizeof(struct CitControl), 1, fp);
+               }
+       }
+
+
+/*
+ * get_new_message_number()  -  Obtain a new, unique ID to be used for a message.
+ */
+long get_new_message_number() {
+
+       begin_critical_section(S_CONTROL);
+       get_control();
+       ++CitControl.MMhighest;
+       put_control();
+       end_critical_section(S_CONTROL);
+       return(CitControl.MMhighest);
+       }
+
+
+/*
+ * get_new_user_number()  -  Obtain a new, unique ID to be used for a user.
+ */
+long get_new_user_number() {
+
+       begin_critical_section(S_CONTROL);
+       get_control();
+       ++CitControl.MMnextuser;
+       put_control();
+       end_critical_section(S_CONTROL);
+       return(CitControl.MMhighest);
+       }
diff --git a/citadel/copyright.txt b/citadel/copyright.txt
new file mode 100644 (file)
index 0000000..3f08f6b
--- /dev/null
@@ -0,0 +1,30 @@
+                         Citadel/UX release 5.01
+
+Copyright (c) 1987-1998 by:
+        Art Cancro <ajc@uncnsrd.mt-kisco.ny.us>
+
+Portions contributed by:
+       Brian Costello <btx@calyx.net>
+
+ ------------------------------------------------------------------------------
+    The entire package is free software; you can redistribute
+    and/or modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the 
+    License, or (at your option) any later version.
+
+    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.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  
+    Feedback concerning Citadel/UX goes to: ajc@uncnsrd.mt-kisco.ny.us 
+    The home of Citadel/UX is UNCENSORED! BBS:
+                    telnet://uncnsrd.mt-kisco.ny.us
+                    http://uncnsrd.mt-kisco.ny.us
+                    914-244-3252 (modem)
diff --git a/citadel/cux2ascii.c b/citadel/cux2ascii.c
new file mode 100644 (file)
index 0000000..ce4bd6a
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+ * cux2ascii v2.3
+ * see copyright.doc for copyright information
+ *
+ * This program is a filter which converts Citadel/UX binary message format
+ * to standard UseNet news format.  Useful for Citadel<->News gateways.
+ */
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <time.h>
+#include "citadel.h"
+
+long finduser();
+
+void get_config();     struct config config;
+
+int struncmp(lstr,rstr,len)
+char lstr[],rstr[];
+int len; {
+       int pos = 0;
+       char lc,rc;
+       while (pos<len) {
+               lc=tolower(lstr[pos]);
+               rc=tolower(rstr[pos]);
+               if ((lc==0)&&(rc==0)) return(0);
+               if (lc<rc) return(-1);
+               if (lc>rc) return(1);
+               pos=pos+1;
+               }
+       return(0);
+       }
+
+main(argc,argv)
+int argc;
+char *argv[]; {
+       struct tm *tm;
+       int a,b,e,mtype,aflag;
+       char bbb[100],ngn[100];
+       long tmid;
+       char tsid[64];
+       char tuid[64];
+       FILE *fp,*tfp;
+       long now,msglen;
+       char tflnm[16];
+
+       int cuunbatch = 0;
+
+       /* experimental cuunbatch header generation */
+       for (a=0; a<argc; ++a) {
+               if (!strcmp(argv[a],"-c")) cuunbatch = 1;
+               }
+
+       get_config();
+
+       sprintf(tflnm,"/tmp/c2a%d",getpid());
+
+       fp=stdin;
+   while (1) {
+       do {
+               e=getc(fp);
+               if (e<0) exit(0);
+               } while(e!=255);
+       mtype=getc(fp); aflag=getc(fp);
+
+       tmid = 0L;
+       strcpy(tsid,FQDN);
+       strcpy(tuid,"postmaster");
+
+       tfp=fopen(tflnm,"w");
+   do {
+       b=getc(fp);
+       if (b=='M') {
+               fprintf(tfp,"Message-ID: <%ld@%s>\n",tmid,tsid);
+               fprintf(tfp,"\n");
+               if (aflag!=1) fmout(80,fp,tfp);
+                  else while(a=getc(fp), a>0) {
+                       putc(a,tfp); if (a==13) putc(10,tfp);
+                       }
+               }
+       if ((b!='M')&&(b>0)) fpgetfield(fp,bbb);
+       if (b=='I') tmid=atol(bbb);
+       if (b=='N') {
+               strcpy(tsid,bbb);
+               if (!strcmp(tsid,NODENAME)) strcpy(tsid,FQDN);
+               for (a=0; a<strlen(tuid); ++a) if (tuid[a]==' ') tuid[a]='_';
+               fprintf(tfp,"From: %s@%s ",tuid,tsid);
+               for (a=0; a<strlen(tuid); ++a) if (tuid[a]=='_') tuid[a]=' ';
+               fprintf(tfp,"(%s)\n",tuid);
+               }
+       if (b=='P') fprintf(tfp,"Path: %s\n",bbb);
+       if (b=='A') strcpy(tuid,bbb);
+       if (b=='O') {
+               xref(bbb,ngn);
+               fprintf(tfp,"Newsgroups: %s\n",ngn);
+               }
+       if (b=='R') fprintf(tfp,"To: %s\n",bbb);
+       if (b=='U') fprintf(tfp,"Subject: %s\n",bbb);
+       if (b=='T') {
+               now=atol(bbb);
+               tm=(struct tm *)localtime(&now);
+               fprintf(tfp,"Date: %s",asctime(tm));
+               }
+          } while ((b!='M')&&(b>0));
+       msglen=ftell(tfp);
+       fclose(tfp);
+
+       if (cuunbatch) {
+               printf("#! cuunbatch %ld\n",msglen);
+               }
+       else {
+               printf("#! rnews %ld\n",msglen);
+               }
+
+       tfp=fopen(tflnm,"r");
+       while(msglen--) putc(getc(tfp),stdout);
+       fclose(tfp);
+       unlink(tflnm);
+   }
+exit(0);
+}
+
+fpgetfield(fp,string)  /* level-2 break out next null-terminated string */
+FILE *fp;
+char string[];
+{
+int a,b;
+strcpy(string,"");
+a=0;
+       do {
+               b=getc(fp);
+               if (b<1) { string[a]=0; return(0); }
+               string[a]=b;
+               ++a;
+               } while(b!=0);
+       return(0);
+}
+
+fmout(width,fp,mout)
+int width;
+FILE *fp,*mout;
+       {
+       int a,b,c,real,old;
+       char aaa[140];
+       
+       strcpy(aaa,""); old=255;
+       c=1; /* c is the current pos */
+FMTA:  old=real; a=getc(fp); real=a;
+       if (a<=0) goto FMTEND;
+       
+       if ( ((a==13)||(a==10)) && (old!=13) && (old!=10) ) a=32;
+       if ( ((old==13)||(old==10)) && (isspace(real)) ) {
+                                               fprintf(mout,"\n"); c=1; }
+       if (a>126) goto FMTA;
+
+       if (a>32) {
+       if ( ((strlen(aaa)+c)>(width-1)) && (strlen(aaa)>(width-1)) )
+               { fprintf(mout,"\n%s",aaa); c=strlen(aaa); aaa[0]=0; }
+        b=strlen(aaa); aaa[b]=a; aaa[b+1]=0; }
+       if (a==32) {    if ((strlen(aaa)+c)>(width-1)) { 
+                                                       fprintf(mout,"\n");
+                                                       c=1;
+                                                       }
+                       fprintf(mout,"%s ",aaa); ++c; c=c+strlen(aaa);
+                       strcpy(aaa,""); goto FMTA; }
+       if ((a==13)||(a==10)) {
+                               fprintf(mout,"%s\n",aaa); c=1;
+                               strcpy(aaa,""); goto FMTA; }
+       goto FMTA;
+
+FMTEND:        fprintf(mout,"\n");
+       return(0);
+}
+
+xref(roomname,newsgroup)
+char *roomname,*newsgroup; {
+       char tbuf[128];
+       FILE *fp;
+       int commapos,a;
+
+       strcpy(newsgroup,roomname);
+       fp=fopen("./network/rnews.xref","r");
+       if (fp==NULL) return(1);
+       while (fgets(tbuf,128,fp)!=NULL) {
+               tbuf[strlen(tbuf)-1]=0;         /* strip off the newline */
+               a=strlen(tbuf);
+               while (a--) if (tbuf[a]==',') commapos=a;
+               tbuf[commapos]=0;
+               if (!strucmp(&tbuf[commapos+1],roomname))
+                       strcpy(newsgroup,tbuf);
+               }
+       fclose(fp);
+       return(0);
+       }
diff --git a/citadel/database.c b/citadel/database.c
new file mode 100644 (file)
index 0000000..d81726a
--- /dev/null
@@ -0,0 +1,257 @@
+/*
+ * This file contains a set of abstractions that allow Citadel to plug into any
+ * record manager or database system for its data store.
+ */
+
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <time.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+#include <gdbm.h>
+#include "citadel.h"
+#include "server.h"
+#include "proto.h"
+
+
+/*
+ * This array holds one gdbm handle for each Citadel database.
+ */
+GDBM_FILE gdbms[MAXCDB];
+
+/*
+ * We also keep these around, for sequential searches... (one per 
+ * session.  Maybe there's a better way?)
+ */
+#define MAXKEYS 256
+datum dtkey[MAXKEYS];
+
+
+/*
+ * Reclaim unused space in the databases.  We need to do each one of
+ * these discretely, rather than in a loop.
+ */
+void defrag_databases() {
+
+       /* defrag the message base */
+       begin_critical_section(S_MSGMAIN);
+       gdbm_reorganize(gdbms[CDB_MSGMAIN]);
+       end_critical_section(S_MSGMAIN);
+
+       /* defrag the user file */
+       begin_critical_section(S_USERSUPP);
+       gdbm_reorganize(gdbms[CDB_USERSUPP]);
+       end_critical_section(S_USERSUPP);
+
+       /* defrag the room files */
+       begin_critical_section(S_QUICKROOM);
+       gdbm_reorganize(gdbms[CDB_QUICKROOM]);
+       gdbm_reorganize(gdbms[CDB_FULLROOM]);
+       end_critical_section(S_QUICKROOM);
+
+       /* defrag the floor table */
+       begin_critical_section(S_FLOORTAB);
+       gdbm_reorganize(gdbms[CDB_FLOORTAB]);
+       end_critical_section(S_FLOORTAB);
+       }
+
+
+/*
+ * Open the various gdbm databases we'll be using.  Any database which
+ * does not exist should be created.
+ */
+void open_databases() {
+       int a;
+
+       gdbms[CDB_MSGMAIN] = gdbm_open("msgmain.gdbm", 8192,
+               GDBM_WRCREAT, 0700, NULL);
+       if (gdbms[CDB_MSGMAIN] == NULL) {
+               lprintf(2, "Cannot open msgmain: %s\n",
+                       gdbm_strerror(gdbm_errno));
+               }
+
+       gdbms[CDB_USERSUPP] = gdbm_open("usersupp.gdbm", 0,
+               GDBM_WRCREAT, 0700, NULL);
+       if (gdbms[CDB_USERSUPP] == NULL) {
+               lprintf(2, "Cannot open usersupp: %s\n",
+                       gdbm_strerror(gdbm_errno));
+               }
+
+       gdbms[CDB_QUICKROOM] = gdbm_open("quickroom.gdbm", 0,
+               GDBM_WRCREAT, 0700, NULL);
+       if (gdbms[CDB_QUICKROOM] == NULL) {
+               lprintf(2, "Cannot open quickroom: %s\n",
+                       gdbm_strerror(gdbm_errno));
+               }
+
+       gdbms[CDB_FULLROOM] = gdbm_open("fullroom.gdbm", 0,
+               GDBM_WRCREAT, 0700, NULL);
+       if (gdbms[CDB_FULLROOM] == NULL) {
+               lprintf(2, "Cannot open fullroom: %s\n",
+                       gdbm_strerror(gdbm_errno));
+               }
+
+       gdbms[CDB_FLOORTAB] = gdbm_open("floortab.gdbm", 0,
+               GDBM_WRCREAT, 0700, NULL);
+       if (gdbms[CDB_FLOORTAB] == NULL) {
+               lprintf(2, "Cannot open floortab: %s\n",
+                       gdbm_strerror(gdbm_errno));
+               }
+
+       for (a=0; a<MAXKEYS; ++a) {
+               dtkey[a].dsize = 0;
+               dtkey[a].dptr = NULL;
+               }
+
+
+       }
+
+
+/*
+ * Close all of the gdbm database files we've opened.  This can be done
+ * in a loop, since it's just a bunch of closes.
+ */
+void close_databases() {
+       int a;
+
+       defrag_databases();
+       for (a=0; a<MAXCDB; ++a) {
+               lprintf(7, "Closing database %d\n", a);
+               gdbm_close(gdbms[a]);
+               }
+
+       for (a=0; a<MAXKEYS; ++a) {
+               if (dtkey[a].dptr != NULL) {
+                       free(dtkey[a].dptr);
+                       }
+               }
+
+       }
+
+
+/*
+ * Store a piece of data.  Returns 0 if the operation was successful.  If a
+ * datum already exists it should be overwritten.
+ */
+int cdb_store(int cdb,
+               char *key, int keylen,
+               char *data, int datalen) {
+
+       datum dkey, ddata;
+
+       dkey.dsize = keylen;
+       dkey.dptr = key;
+       ddata.dsize = datalen;
+       ddata.dptr = data;
+
+       if ( gdbm_store(gdbms[cdb], dkey, ddata, GDBM_REPLACE) < 0 ) {
+                lprintf(2, "gdbm error: %s\n", gdbm_strerror(gdbm_errno));
+                return(-1);
+               }
+
+       return(0);
+       }
+
+
+/*
+ * Delete a piece of data.  Returns 0 if the operation was successful.
+ */
+int cdb_delete(int cdb, char *key, int keylen) {
+
+       datum dkey;
+
+       dkey.dsize = keylen;
+       dkey.dptr = key;
+
+       return(gdbm_delete(gdbms[cdb], dkey));
+
+       }
+
+
+
+
+/*
+ * Fetch a piece of data.  If not found, returns NULL.  Otherwise, it returns
+ * a struct cdbdata which it is the caller's responsibility to free later on
+ * using the cdb_free() routine.
+ */
+struct cdbdata *cdb_fetch(int cdb, char *key, int keylen) {
+       
+       struct cdbdata *tempcdb;
+       datum dkey, dret;
+       
+       dkey.dsize = keylen;
+       dkey.dptr = key;
+
+       dret = gdbm_fetch(gdbms[cdb], dkey);
+       if (dret.dptr == NULL) {
+               return NULL;
+               }
+
+       tempcdb = (struct cdbdata *) malloc(sizeof(struct cdbdata));
+       if (tempcdb == NULL) {
+               lprintf(2, "Cannot allocate memory!\n");
+               }
+
+       tempcdb->len = dret.dsize;
+       tempcdb->ptr = dret.dptr;
+       return(tempcdb);
+       }
+
+
+/*
+ * Free a cdbdata item (ok, this is really no big deal, but we might need to do
+ * more complex stuff with other database managers in the future).
+ */
+void cdb_free(struct cdbdata *cdb) {
+       free(cdb->ptr);
+       free(cdb);
+       }
+
+
+/* 
+ * Prepare for a sequential search of an entire database.  (In the gdbm model,
+ * we do this by keeping an array dtkey[] of "the next" key for each session
+ * that is open.  There is guaranteed to be no more than one traversal in
+ * progress per session at any given time.)
+ */
+void cdb_rewind(int cdb) {
+
+       if (dtkey[CC->cs_pid].dptr != NULL) {
+               free(dtkey[CC->cs_pid].dptr);
+               }
+
+       dtkey[CC->cs_pid] = gdbm_firstkey(gdbms[cdb]);
+       }
+
+
+/*
+ * Fetch the next item in a sequential search.  Returns a pointer to a 
+ * cdbdata structure, or NULL if we've hit the end.
+ */
+struct cdbdata *cdb_next_item(int cdb) {
+       datum dret;
+       struct cdbdata *cdbret;
+
+       
+       if (dtkey[CC->cs_pid].dptr == NULL) {   /* end of file */
+               return NULL;
+               }
+
+       dret = gdbm_fetch(gdbms[cdb], dtkey[CC->cs_pid]);
+       if (dret.dptr == NULL) {        /* bad read */
+               free(dtkey[CC->cs_pid].dptr);
+               return NULL;
+               }
+
+       cdbret = (struct cdbdata *) malloc(sizeof(struct cdbdata));
+       cdbret->len = dret.dsize;
+       cdbret->ptr = dret.dptr;
+
+       dtkey[CC->cs_pid] = gdbm_nextkey(gdbms[cdb], dtkey[CC->cs_pid]);
+       return(cdbret);
+       }
diff --git a/citadel/dnetsetup b/citadel/dnetsetup
new file mode 100755 (executable)
index 0000000..46b2962
--- /dev/null
@@ -0,0 +1,178 @@
+#!/bin/sh
+
+# dnetsetup version 1.0
+# Copyright (c)1998 by Art Cancro / All rights reserved.
+# This program is distributed under the terms of the GNU General Public
+# License.  See copyright.txt for more information.
+#
+# dnetsetup requires Savio Lam's "dialog" program to perform its interactive
+# dialogs.  This program is standard on most Linux systems, and should be
+# easy enough to get on other systems.
+
+cleanup() {
+       rm -f $tempfile $tempfile2 $menucmd 2>/dev/null
+       exit
+       }
+
+
+roomlist() {
+       node=$1
+       
+       ./netsetup roomlist $1 >$tempfile 2>&1
+       $DIALOG --textbox $tempfile $menuheight $menuwidth
+       }
+
+
+unshare() {
+       node=$1
+
+       ./netsetup roomlist $node >$tempfile
+       numrooms=`cat $tempfile |wc -l`
+       sed s/\ /\\\\\ /g <$tempfile | sed s/$/\ \"\"/g >$tempfile2
+       echo $DIALOG --menu \"Select the room you wish to stop sharing\" \
+               $menuheight $menuwidth $numrooms \
+               `cat $tempfile2` >$menucmd
+       if sh $menucmd 2>$tempfile; then
+               room_to_delete=`cat $tempfile`
+
+               if $DIALOG --yesno "Are you sure you wish to stop sharing $room_to_delete ?" 5 $menuwidth ; then
+                       if ./netsetup unshare $node "$room_to_delete" 2>$tempfile; then
+                               $DIALOG --msgbox "$room_to_delete has been unshared." 5 $menuwidth
+                       else
+                               $DIALOG --textbox $tempfile $menuheight $menuwidth
+                               fi
+                       fi
+               fi
+
+       }
+
+do_share() {
+       node=$1
+
+       if $DIALOG --inputbox "Enter name of room to share:" \
+               $menuheight $menuwidth 2>$tempfile ; then
+       
+               room_to_share=`cat $tempfile`   
+               if ./netsetup share $node "$room_to_share" 2>$tempfile; then
+                       $DIALOG --msgbox "$room_to_share has been added." 5 $menuwidth
+               else
+                       $DIALOG --textbox $tempfile $menuheight $menuwidth
+                       fi
+
+               fi
+       }
+
+
+edit_this_node() {
+       node=$1
+       choice=nothing_yet
+
+       while [ $choice != EXIT ]
+       do
+               if $DIALOG --menu "Editing $node" $menuheight $menuwidth 4 \
+                       LIST    "List currently shared rooms" \
+                       SHARE   "Add a shared room" \
+                       UNSHARE "Stop sharing a room" \
+                       EXIT    "Return to main menu" 2>$tempfile; then
+                               choice=`cat $tempfile`
+                       else
+                               choice="EXIT"
+                               fi
+
+               [ $choice = "LIST" ] && roomlist $node
+               [ $choice = "SHARE" ] && do_share $node
+               [ $choice = "UNSHARE" ] && unshare $node
+
+               done
+       }
+
+
+edit_a_node() {
+
+       ./netsetup nodelist >$tempfile
+       numnodes=`cat $tempfile |wc -l`
+       sed s/$/\ \"\"/g <$tempfile >$tempfile2
+       echo $DIALOG --menu \"Select the node you wish to edit\" \
+               $menuheight $menuwidth $numnodes \
+               `cat $tempfile2` >$menucmd
+       if sh $menucmd 2>$tempfile; then
+               system_to_edit=`cat $tempfile`
+               edit_this_node $system_to_edit
+       else
+               true
+               fi
+       }
+
+
+
+
+
+
+delete_network_nodes() {
+
+       ./netsetup nodelist >$tempfile
+       numnodes=`cat $tempfile |wc -l`
+       sed s/$/\ \"\"/g <$tempfile >$tempfile2
+       echo $DIALOG --menu \"Select the node you wish to delete\" \
+               $menuheight $menuwidth $numnodes \
+               `cat $tempfile2` >$menucmd
+       if sh $menucmd 2>$tempfile; then
+               system_to_delete=`cat $tempfile`
+
+               if $DIALOG --yesno "Are you sure you wish to delete $system_to_delete ?" 5 $menuwidth ; then
+                       if ./netsetup deletenode $system_to_delete 2>$tempfile; then
+                               $DIALOG --msgbox "$system_to_delete has been deleted." 5 $menuwidth
+                       else
+                               $DIALOG --textbox $tempfile $menuheight $menuwidth
+                               fi
+                       fi
+               fi
+
+       }
+
+
+add_a_node() {
+       node=$1
+
+       if $DIALOG --inputbox "Enter name of new node:" \
+               $menuheight $menuwidth 2>$tempfile ; then
+       
+               new_node=`cat $tempfile`        
+               if ./netsetup addnode $new_node 2>$tempfile; then
+                       $DIALOG --msgbox "$new_node has been created." 5 $menuwidth
+               else
+                       $DIALOG --textbox $tempfile $menuheight $menuwidth
+                       fi
+
+               fi
+       }
+
+
+# Main loop...
+clear
+tempfile=/tmp/dnetsetup.$$.1
+tempfile2=/tmp/dnetsetup.$$.2
+menucmd=/tmp/dnetsetup.$$.3
+menuheight=20
+menuwidth=72
+DIALOG=dialog
+while true
+do
+       if $DIALOG --menu "Citadel/UX Network Setup" $menuheight $menuwidth 4 \
+               EDIT    "View or change a network node" \
+               ADD     "Add a new network node" \
+               DELETE  "Delete a network node" \
+               EXIT    "Exit from Network Setup" \
+               2>$tempfile; then
+                       choice=`cat $tempfile`
+               else
+                       choice=EXIT
+               fi
+       rm -f $tempfile
+
+       [ $choice = "EXIT" ] && cleanup
+       [ $choice = "DELETE" ] && delete_network_nodes
+       [ $choice = "EDIT" ] && edit_a_node
+       [ $choice = "ADD" ] && add_a_node
+
+       done
diff --git a/citadel/file_ops.c b/citadel/file_ops.c
new file mode 100644 (file)
index 0000000..9d99dbf
--- /dev/null
@@ -0,0 +1,712 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <pthread.h>
+#include "citadel.h"
+#include "server.h"
+#include "proto.h"
+
+void cmd_delf(char *filename)
+{
+       char pathname[64];
+       int a;
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (!is_room_aide()) {
+               cprintf("%d Higher access required.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+
+       if ((CC->quickroom.QRflags & QR_DIRECTORY) == 0) {
+               cprintf("%d No directory in this room.\n",ERROR+NOT_HERE);
+               return;
+               }
+
+       if (strlen(filename)==0) {
+               cprintf("%d You must specify a file name.\n",
+                       ERROR+FILE_NOT_FOUND);
+               return;
+               }
+       for (a=0; a<strlen(filename); ++a)
+               if (filename[a]=='/') filename[a] = '_';
+       sprintf(pathname,"./files/%s/%s",CC->quickroom.QRdirname,filename);
+       a=unlink(pathname);
+       if (a==0) cprintf("%d File '%s' deleted.\n",OK,pathname);
+       else cprintf("%d File '%s' not found.\n",ERROR+FILE_NOT_FOUND,pathname);
+       }
+
+
+
+
+/*
+ * move a file from one room directory to another
+ */
+void cmd_movf(char *cmdbuf)
+{
+       char filename[256];
+       char pathname[256];
+       char newpath[256];
+       char newroom[256];
+       char buf[256];
+       FILE *fp;
+       int a;
+       int target_room = (-1);
+       struct quickroom qrbuf;
+
+       extract(filename,cmdbuf,0);
+       extract(newroom,cmdbuf,1);
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (!is_room_aide()) {
+               cprintf("%d Higher access required.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       if ((CC->quickroom.QRflags & QR_DIRECTORY) == 0) {
+               cprintf("%d No directory in this room.\n",ERROR+NOT_HERE);
+               return;
+               }
+
+       if (strlen(filename)==0) {
+               cprintf("%d You must specify a file name.\n",
+                       ERROR+FILE_NOT_FOUND);
+               return;
+               }
+
+       for (a=0; a<strlen(filename); ++a)
+               if (filename[a]=='/') filename[a] = '_';
+       sprintf(pathname,"./files/%s/%s",CC->quickroom.QRdirname,filename);
+       if (access(pathname,0)!=0) {
+               cprintf("%d File '%s' not found.\n",
+                       ERROR+FILE_NOT_FOUND,pathname);
+               return;
+               }
+
+       for (a=0; a<MAXROOMS; ++a) {
+               getroom(&qrbuf,a);
+               if (!strucmp(qrbuf.QRname,newroom)) target_room = a;
+               }
+       if (target_room < 0) {
+               cprintf("%d Room '%s' not found.\n",
+                       ERROR+ROOM_NOT_FOUND,newroom);
+               return;
+               }
+       getroom(&qrbuf, target_room);
+       if ((qrbuf.QRflags & QR_DIRECTORY) == 0) {
+               cprintf("%d '%s' is not a directory room.\n",
+                       ERROR+NOT_HERE,qrbuf.QRname);
+               return;
+               }
+       sprintf(newpath,"./files/%s/%s",qrbuf.QRdirname,filename);
+       if (link(pathname,newpath)!=0) {
+               cprintf("%d Couldn't move file: %s\n",ERROR,strerror(errno));
+               return;
+               }
+       unlink(pathname);
+
+       /* this is a crude method of copying the file description */
+       sprintf(buf,"cat ./files/%s/filedir |grep %s >>./files/%s/filedir",
+               CC->quickroom.QRdirname,
+               filename,
+               qrbuf.QRdirname);
+       system(buf);
+       cprintf("%d File '%s' has been moved.\n",OK,filename);
+       }
+
+
+/*
+ * send a file over the net
+ */
+void cmd_netf(char *cmdbuf)
+{
+       char pathname[256],filename[256],destsys[256],buf[256],outfile[256];
+       int a,e;
+       long now;
+       FILE *ofp;
+
+       extract(filename,cmdbuf,0);
+       extract(destsys,cmdbuf,1);
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (!is_room_aide()) {
+               cprintf("%d Higher access required.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       if ((CC->quickroom.QRflags & QR_DIRECTORY) == 0) {
+               cprintf("%d No directory in this room.\n",ERROR+NOT_HERE);
+               return;
+               }
+
+       if (strlen(filename)==0) {
+               cprintf("%d You must specify a file name.\n",
+                       ERROR+FILE_NOT_FOUND);
+               return;
+               }
+
+       for (a=0; a<strlen(filename); ++a)
+               if (filename[a]=='/') filename[a] = '_';
+       sprintf(pathname,"./files/%s/%s",CC->quickroom.QRdirname,filename);
+       if (access(pathname,0)!=0) {
+               cprintf("%d File '%s' not found.\n",
+                       ERROR+FILE_NOT_FOUND,pathname);
+               return;
+               }
+       sprintf(buf,"sysop@%s",destsys);
+       e=alias(buf);
+       if (e!=M_BINARY) {
+               cprintf("%d No such system: '%s'\n",
+                       ERROR+NO_SUCH_SYSTEM,destsys);
+               return;
+               }
+       sprintf(outfile,"%s/network/spoolin/nsf.%d",BBSDIR,getpid());
+       ofp=fopen(outfile,"a");
+       if (ofp==NULL) {
+               cprintf("%d internal error\n",ERROR);
+               return;
+               }
+
+       putc(255,ofp);
+       putc(MES_NORMAL,ofp);
+       putc(0,ofp);
+       fprintf(ofp,"Pcit%ld",CC->usersupp.usernum); putc(0,ofp);
+       time(&now);
+       fprintf(ofp,"T%ld",now); putc(0,ofp);
+       fprintf(ofp,"A%s",CC->usersupp.fullname); putc(0,ofp);
+       fprintf(ofp,"O%s",CC->quickroom.QRname); putc(0,ofp);
+       fprintf(ofp,"N%s",NODENAME); putc(0,ofp);
+       fprintf(ofp,"D%s",destsys); putc(0,ofp);
+       fprintf(ofp,"SFILE"); putc(0,ofp);
+       putc('M',ofp);
+       fclose(ofp);
+
+       sprintf(buf,"cd ./files/%s; uuencode %s <%s 2>/dev/null >>%s",
+               CC->quickroom.QRdirname,filename,filename,outfile);
+       system(buf);
+
+       ofp = fopen(outfile,"a");
+       putc(0,ofp);
+       fclose(ofp);
+
+       cprintf("%d File '%s' has been sent to %s.\n",OK,filename,destsys);
+       system("nohup ./netproc >/dev/null 2>&1 &");
+       return;
+       }
+
+/*
+ * This code is common to all commands which open a file for downloading.
+ * It examines the file and displays the OK result code and some information
+ * about the file.  NOTE: this stuff is Unix dependent.
+ */
+void OpenCmdResult() {
+       struct stat statbuf;
+
+       fstat(fileno(CC->download_fp), &statbuf);
+       cprintf("%d %ld|%ld\n", OK, statbuf.st_size, statbuf.st_mtime);
+       }
+
+
+/*
+ * open a file for downloading
+ */
+void cmd_open(char *cmdbuf)
+{
+       char filename[256];
+       char pathname[256];
+       int a;
+
+       extract(filename,cmdbuf,0);
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if ((CC->quickroom.QRflags & QR_DIRECTORY) == 0) {
+               cprintf("%d No directory in this room.\n",ERROR+NOT_HERE);
+               return;
+               }
+
+       if (strlen(filename)==0) {
+               cprintf("%d You must specify a file name.\n",
+                       ERROR+FILE_NOT_FOUND);
+               return;
+               }
+
+       if (CC->download_fp != NULL) {
+               cprintf("%d You already have a download file open.\n",ERROR);
+               return;
+               }
+
+       for (a=0; a<strlen(filename); ++a)
+               if (filename[a]=='/') filename[a] = '_';
+
+       sprintf(pathname,"./files/%s/%s",CC->quickroom.QRdirname,filename);
+       CC->download_fp = fopen(pathname,"r");
+
+       if (CC->download_fp==NULL) {
+               cprintf("%d cannot open %s: %s\n",
+                       ERROR,pathname,strerror(errno));
+               return;
+               }
+
+       OpenCmdResult();
+       }
+
+/*
+ * open an image file
+ */
+void cmd_oimg(char *cmdbuf)
+{
+       char filename[256];
+       char pathname[256];
+       struct usersupp usbuf;
+       char which_user[32];
+       int which_floor;
+       int a;
+
+       extract(filename,cmdbuf,0);
+
+       if (strlen(filename)==0) {
+               cprintf("%d You must specify a file name.\n",
+                       ERROR+FILE_NOT_FOUND);
+               return;
+               }
+
+       if (CC->download_fp != NULL) {
+               cprintf("%d You already have a download file open.\n",ERROR);
+               return;
+               }
+
+       if (!strucmp(filename, "_userpic_")) {
+               extract(which_user, cmdbuf, 1);
+               if (getuser(&usbuf, which_user) != 0) {
+                       cprintf("%d No such user.\n", ERROR+NO_SUCH_USER);
+                       return;
+                       }
+               sprintf(pathname, "./userpics/%ld.gif", usbuf.usernum);
+               }
+       else if (!strucmp(filename, "_floorpic_")) {
+               which_floor = extract_int(cmdbuf, 1);
+               sprintf(pathname, "./images/floor.%d.gif", which_floor);
+               }
+       else if (!strucmp(filename, "_roompic_")) {
+               sprintf(pathname, "./images/room.%d.gif", CC->curr_rm);
+               }
+       else {
+               for (a=0; a<strlen(filename); ++a) {
+                       filename[a] = tolower(filename[a]);
+                       if (filename[a]=='/') filename[a] = '_';
+                       }
+               sprintf(pathname,"./images/%s.gif",filename);
+               }
+       
+       CC->download_fp = fopen(pathname,"r");
+       if (CC->download_fp == NULL) {
+               cprintf("%d Cannot open %s: %s\n",
+                       ERROR+FILE_NOT_FOUND,pathname,strerror(errno));
+               return;
+               }
+       
+       OpenCmdResult();
+       }
+
+/*
+ * open a file for uploading
+ */
+void cmd_uopn(char *cmdbuf)
+{
+       int a;
+
+       extract(CC->upl_file,cmdbuf,0);
+       extract(CC->upl_comment,cmdbuf,1);
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if ((CC->quickroom.QRflags & QR_DIRECTORY) == 0) {
+               cprintf("%d No directory in this room.\n",ERROR+NOT_HERE);
+               return;
+               }
+
+       if (strlen(CC->upl_file)==0) {
+               cprintf("%d You must specify a file name.\n",
+                       ERROR+FILE_NOT_FOUND);
+               return;
+               }
+
+       if (CC->upload_fp != NULL) {
+               cprintf("%d You already have a upload file open.\n",ERROR);
+               return;
+               }
+
+       for (a=0; a<strlen(CC->upl_file); ++a)
+               if (CC->upl_file[a]=='/') CC->upl_file[a] = '_';
+       sprintf(CC->upl_path,"./files/%s/%s",CC->quickroom.QRdirname,CC->upl_file);
+       sprintf(CC->upl_filedir,"./files/%s/filedir",CC->quickroom.QRdirname);
+       
+       CC->upload_fp = fopen(CC->upl_path,"r");
+       if (CC->upload_fp != NULL) {
+               fclose(CC->upload_fp);
+               CC->upload_fp = NULL;
+               cprintf("%d '%s' already exists\n",
+                       ERROR+ALREADY_EXISTS,CC->upl_path);
+               return;
+               }
+
+       CC->upload_fp = fopen(CC->upl_path,"wb");
+       if (CC->upload_fp == NULL) {
+               cprintf("%d Cannot open %s: %s\n",
+                       ERROR,CC->upl_path,strerror(errno));
+               return;
+               }
+       cprintf("%d Ok\n",OK);
+       }
+
+
+
+/*
+ * open an image file for uploading
+ */
+void cmd_uimg(char *cmdbuf)
+{
+       int is_this_for_real;
+       char basenm[256];
+       int which_floor;
+       int a;
+
+       if (num_parms(cmdbuf) < 2) {
+               cprintf("%d Usage error.\n", ERROR);
+               return;
+               }
+
+       is_this_for_real = extract_int(cmdbuf,0);       
+       extract(basenm, cmdbuf, 1);
+       if (CC->upload_fp != NULL) {
+               cprintf("%d You already have an upload file open.\n", ERROR);
+               return;
+               }
+
+       strcpy(CC->upl_path, "");
+
+       for (a=0; a<strlen(basenm); ++a) {
+               basenm[a] = tolower(basenm[a]);
+               if (basenm[a]=='/') basenm[a] = '_';
+               }
+
+       if (CC->usersupp.axlevel >= 6) {
+               sprintf(CC->upl_path, "./images/%s", basenm);
+               }
+
+       if (!strucmp(basenm, "_userpic_")) {
+               sprintf(CC->upl_path, "./userpics/%ld.gif",
+                       CC->usersupp.usernum);
+               }
+
+       if ( (!strucmp(basenm, "_floorpic_")) && (CC->usersupp.axlevel >= 6) ) {
+               which_floor = extract_int(cmdbuf, 2);
+               sprintf(CC->upl_path, "./images/floor.%d.gif", which_floor);
+               }
+
+       if ( (!strucmp(basenm, "_roompic_")) && (is_room_aide()) ) {
+               sprintf(CC->upl_path, "./images/room.%d.gif", CC->curr_rm);
+               }
+
+       if (strlen(CC->upl_path) == 0) {
+               cprintf("%d Higher access required.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       if (is_this_for_real == 0) {
+               cprintf("%d Ok to send image\n", OK);
+               return;
+               }
+
+       CC->upload_fp = fopen(CC->upl_path,"wb");
+       if (CC->upload_fp == NULL) {
+               cprintf("%d Cannot open %s: %s\n",
+                       ERROR,CC->upl_path,strerror(errno));
+               return;
+               }
+       cprintf("%d Ok\n",OK);
+       CC->upload_type = UPL_IMAGE;
+       }
+
+
+/*
+ * close the download file
+ */
+void cmd_clos(void) {
+       char buf[256];
+       
+       if (CC->download_fp == NULL) {
+               cprintf("%d You don't have a download file open.\n",ERROR);
+               return;
+               }
+
+       fclose(CC->download_fp);
+       CC->download_fp = NULL;
+
+       if (CC->dl_is_net == 1) {
+               CC->dl_is_net = 0;
+               sprintf(buf,"%s/network/spoolout/%s",BBSDIR,CC->net_node);
+               unlink(buf);
+               }
+
+       cprintf("%d Ok\n",OK);
+       }
+
+
+/*
+ * abort and upload
+ */
+void abort_upl(struct CitContext *who)
+{
+       if (who->upload_fp != NULL) {
+               fclose(who->upload_fp);
+               who->upload_fp = NULL;
+               unlink(CC->upl_path);
+               }
+       }
+
+
+
+/*
+ * close the upload file
+ */
+void cmd_ucls(char *cmd)
+{
+       FILE *fp;
+       long now;
+       
+       if (CC->upload_fp == NULL) {
+               cprintf("%d You don't have an upload file open.\n",ERROR);
+               return;
+               }
+
+       fclose(CC->upload_fp);
+       CC->upload_fp = NULL;
+
+       if ((!strucmp(cmd,"1")) && (CC->upload_type != UPL_FILE)) {
+               CC->upload_type = UPL_FILE;
+               cprintf("%d Upload completed.\n", OK);
+               return;
+               }
+
+       if (!strucmp(cmd,"1")) {
+               cprintf("%d File '%s' saved.\n",OK,CC->upl_path);
+               fp = fopen(CC->upl_filedir,"a");
+               if (fp==NULL) fp=fopen(CC->upl_filedir,"w");
+               if (fp!=NULL) {
+                       fprintf(fp,"%s %s\n",CC->upl_file,CC->upl_comment);
+                       fclose(fp);
+                       }
+
+               /* put together an upload notice */
+               time(&now);
+               fp=fopen(CC->temp,"wb");
+               putc(255,fp);
+               putc(MES_NORMAL,fp);
+               putc(0,fp);
+               fprintf(fp,"Pcit%ld",CC->usersupp.usernum); putc(0,fp);
+               fprintf(fp,"T%ld",now); putc(0,fp);
+               fprintf(fp,"A%s",CC->curr_user); putc(0,fp);
+               fprintf(fp,"O%s",CC->quickroom.QRname); putc(0,fp);
+               fprintf(fp,"N%s",NODENAME); putc(0,fp); putc('M',fp);
+               fprintf(fp,"NEW UPLOAD: '%s'\n %s\n",CC->upl_file,CC->upl_comment);
+               putc(0,fp);
+               fclose(fp);
+               save_message(CC->temp, "", 0, M_LOCAL, 1);
+
+               }
+       else {
+               abort_upl(CC);
+               cprintf("%d File '%s' aborted.\n",OK,CC->upl_path);
+               }
+       }
+
+/*
+ * read from the download file
+ */
+void cmd_read(char *cmdbuf)
+{
+       long start_pos;
+       int bytes;
+       char buf[4096];
+
+       start_pos = extract_long(cmdbuf,0);
+       bytes = extract_int(cmdbuf,1);
+       
+       if (CC->download_fp == NULL) {
+               cprintf("%d You don't have a download file open.\n",ERROR);
+               return;
+               }
+
+       if (bytes > 4096) {
+               cprintf("%d You may not read more than 4096 bytes.\n",ERROR);
+               return;
+               }
+
+       if (CC->dl_is_net) if (bytes>64) bytes = 64; /**** FIX ****/
+
+       fseek(CC->download_fp,start_pos,0);
+       fread(buf,bytes,1,CC->download_fp);
+       cprintf("%d %d\n",BINARY_FOLLOWS,bytes);
+       client_write(buf, bytes);
+       }
+
+
+
+/*
+ * write to the upload file
+ */
+void cmd_writ(char *cmdbuf)
+{
+       int bytes;
+       char buf[4096];
+
+       bytes = extract_int(cmdbuf,0);
+       
+       if (CC->upload_fp == NULL) {
+               cprintf("%d You don't have an upload file open.\n",ERROR);
+               return;
+               }
+
+       if (bytes > 4096) {
+               cprintf("%d You may not write more than 4096 bytes.\n",ERROR);
+               return;
+               }
+
+       if (CC->upload_type==UPL_NET) if (bytes > 64) bytes = 64; /*** FIX ***/
+
+       cprintf("%d %d\n",SEND_BINARY,bytes);
+       client_read(buf, bytes);
+       fwrite(buf,bytes,1,CC->upload_fp);
+       }
+
+
+
+/*
+ * cmd_netp() - identify as network poll session
+ */
+void cmd_netp(char *cmdbuf)
+{
+       char buf[256];
+       
+       extract(buf,cmdbuf,1);
+       if (strucmp(buf,config.c_net_password)) {
+               cprintf("%d authentication failed\n",ERROR);
+               return;
+               }
+       extract(CC->net_node,cmdbuf,0);
+       cprintf("%d authenticated as network node '%s'\n",OK,CC->net_node);
+       }
+
+/*
+ * cmd_ndop() - open a network spool file for downloading
+ */
+void cmd_ndop(char *cmdbuf)
+{
+       char pathname[256];
+       struct stat statbuf;
+
+       if (strlen(CC->net_node)==0) {
+               cprintf("%d Not authenticated as a network node.\n",
+                       ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (CC->download_fp != NULL) {
+               cprintf("%d You already have a download file open.\n",ERROR);
+               return;
+               }
+
+       sprintf(pathname,"%s/network/spoolout/%s",BBSDIR,CC->net_node);
+
+       /* first open the file in append mode in order to create a
+        * zero-length file if it doesn't already exist 
+        */
+       CC->download_fp = fopen(pathname,"a");
+       fclose(CC->download_fp);
+
+       /* now open it */
+       CC->download_fp = fopen(pathname,"r");
+       if (CC->download_fp==NULL) {
+               cprintf("%d cannot open %s: %s\n",
+                       ERROR,pathname,strerror(errno));
+               return;
+               }
+
+
+       /* set this flag so other routines know that the download file
+        * currently open is a network spool file 
+        */
+       CC->dl_is_net = 1;
+
+       stat(pathname,&statbuf);
+       cprintf("%d %ld\n",OK,statbuf.st_size);
+       }
+
+/*
+ * cmd_nuop() - open a network spool file for uploading
+ */
+void cmd_nuop(char *cmdbuf)
+{
+       if (strlen(CC->net_node)==0) {
+               cprintf("%d Not authenticated as a network node.\n",
+                       ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (CC->upload_fp != NULL) {
+               cprintf("%d You already have an upload file open.\n",ERROR);
+               return;
+               }
+
+       sprintf(CC->upl_path,"%s/network/spoolin/%s.%d",
+               BBSDIR,CC->net_node,getpid());
+
+       CC->upload_fp = fopen(CC->upl_path,"r");
+       if (CC->upload_fp != NULL) {
+               fclose(CC->upload_fp);
+               CC->upload_fp = NULL;
+               cprintf("%d '%s' already exists\n",
+                       ERROR+ALREADY_EXISTS,CC->upl_path);
+               return;
+               }
+
+       CC->upload_fp = fopen(CC->upl_path,"w");
+       if (CC->upload_fp == NULL) {
+               cprintf("%d Cannot open %s: %s\n",
+                       ERROR,CC->upl_path,strerror(errno));
+               return;
+               }
+
+       CC->upload_type = UPL_NET;
+       cprintf("%d Ok\n",OK);
+       }
diff --git a/citadel/help/aide b/citadel/help/aide
new file mode 100644 (file)
index 0000000..4d5b5c1
--- /dev/null
@@ -0,0 +1,18 @@
+The following commands are available only to Aides.  A subset of these
+commands are available to room aides when they are currently in the room
+they are room aide for.
+   
+ .<A>ide <E>dit Room                - edit current room's parameters
+ .<A>ide <F>ile <D>elete            - delete a file from the directory
+ .<A>ide <F>ile <M>ove              - move a file to another room
+ .<A>ide <F>ile <S>end over net     - send a file across the network
+ .<A>ide enter <I>nfo file          - create/change this room's info file
+ .<A>ide <K>ill Room                - delete the current room
+ .<A>ide <R>oom <I>nvite user       - add user to invitation-only room
+ .<A>ide <R>oom <K>ick out user     - delete user from invitation-only room
+ .<A>ide <U>ser edit                - change a user's access level
+ .<A>ide <V>alidate new users       - process new user registration
+ .<A>ide <W>ho knows room           - lists users with access to current room
+ In addition, the <M>ove and <D>elete commands are available at the
+message prompt.
diff --git a/citadel/help/floors b/citadel/help/floors
new file mode 100644 (file)
index 0000000..c7a393f
--- /dev/null
@@ -0,0 +1,43 @@
+ Floors
+ ------
+   Floors in ^variantname are used to group rooms into related subject areas,
+just as rooms are used to group messages into manageable groups.
+   You, as a user, do NOT have to use floors.  If you choose not to, you suffer
+no penalty; you will not lose access to any rooms.  You may use .EC or ;C (the
+latter is easier to use) to decide if you want to use floors.  Feel free to
+experiment.
+   Floor options are accessed two ways.  First, if you are in floor mode, the
+<G>oto and <S>kip commands take you to the next room with new messages on the
+current floor; if there are none left, then the system will automatically
+switch floors (and let you know) and put you in the first room with new messages
+on that level.  (Notice that your pattern of basic use of ^variantname therefore
+doesn't really change.)
+   Direct access to floor options is via the use of a ";" command.
+The following commands are currently available (more can be
+added if needed):
+ <;C>onfigure
+ This command toggles your floor mode.
+ <;G>oto FLOORNAME
+ This command causes the system to take you to the named floor.
+ <;K>nown rooms on floors
+ List all rooms on all floors.  This is a very readable way to get a list of
+all rooms on the system.
+ <;S>kip FLOORNAME
+ This command causes the system to mark all rooms on the current floor as
+Skipped and takes you to the floor that you specify.
+ <;Z>Forget floor
+   This command causes you to forget all the rooms currently on the current
+floor.  Unfortunately, it doesn't apply to rooms that are subsequently created
+or moved to this floor.  (Sorry.)
+   Feel free to experiment, you can't hurt yourself or the system with the
+floor stuff unless you ZForget a floor by accident.
diff --git a/citadel/help/hours b/citadel/help/hours
new file mode 100644 (file)
index 0000000..5da3df3
--- /dev/null
@@ -0,0 +1 @@
+^humannode is up 24 hours a day, 7 days a week.
diff --git a/citadel/help/intro b/citadel/help/intro
new file mode 100644 (file)
index 0000000..bf3e3cd
--- /dev/null
@@ -0,0 +1,139 @@
+                     Welcome to ^humannode!
+                  New User's Introduction to the BBS
+  
+ This is an introduction to ^humannode and to the Citadel BBS concept.
+It is intended for new users so that they can more easily become
+aquainted with the bbs system.  Of course, old users might learn
+something new each time they read through it.
+ Full help for the BBS commands can be obtained by typing <.H>elp SUMMARY
+  
+ The CITADEL BBS room concept
+ ----------------------------
+   The term BBS stands for "Bulletin-Board System".  The analogy is
+appropriate: one posts messages so that others may read them.  In
+order to organize the posts, people can post in different areas of the
+BBS, called rooms.
+   In order to post in a certain room, you need to be "in" that room.
+Your current prompt is usually the room that you are in, followed the
+greater-than-sign, such as:
+ Lobby>
+ The easiest way to traverse the room structure is with the "Goto"
+command, on the "G" key.  Pressing "G" will take you to the next room
+in the "scanlist" (see below) that has new messages in it.  You can
+read these new messages with the "N" key.
+ Once you've "Gotoed" every room in the system (or all of the ones
+you choose to read) you return to the "Lobby," the first and last room
+in the system.  If new messages get posted to rooms you've already
+read during your session you will be brought BACK to those rooms so
+you can read them.
+ Scanlist
+ --------
+   All the room names are stored in a scanlist, which is just a
+string containing all the room names.  When you <G>oto or <S>kip a
+room, you are placed in the next room in your scanlist THAT HAS NEW
+MESSAGES.  If you have no new messages in any of the rooms on your
+scanlist, you will keep going to the Lobby>.  You can choose not to
+read certain rooms (that don't interest you) by "Z"apping them.  When
+you <Z>ap a room, you are merely deleting it from your scanlist (but
+not from anybody else's).
+   You can use the <.G>oto (note the period before the G.  You can also use
+<J>ump on some systems) to go to any room in the
+system.  You don't have to type in the complete name of a room to
+"jump" to it; you merely need to type in enough to distinguish it from
+the other rooms.  Left-aligned matches carry a heavier weight, so if you
+typed (for example) ".Goto TECH", you might be taken to a room called
+"Tech Area>" even if it found a room called "Biotech/Ethics>" first.
+  To return to a room you have previously <Z>apped, use the <.G>oto command
+to enter it, and it will be re-inserted into your scanlist.  In the case
+of returning to Zapped rooms, you must type the room name in its entirety.
+REMEMBER, rooms with no new messages will not show on your
+scanlist!  You must <.G>oto to a room with no new messages.
+Incidentally, you cannot change the order of the rooms on your scanlist.
+It's the same for everybody.
+ Special rooms
+ -------------
+   There are two special rooms on ^humannode that you should
+know about.
+  
+   The first is the Lobby>.  It's used for system announcements and other
+such administrativia.  You cannot <Z>ap the Lobby>.  Each time you first
+login, you will be placed in the Lobby>.
+   The second is Mail>.  In Mail>, when you post a messages, you are
+prompted to enter the person (handle) who you want to send the message
+to.  Only the person who you send the message to can read the message.
+NO ONE else can read it, not even the aides.  Mail> is the first room
+on the scanlist, and is un-<Z>appable, so you can be sure that the
+person will get the message.
+   
+ System aides
+ ------------
+   These people, along with the room aides, keep the BBS running
+smoothly.
+
+   Among the many things that aides do are: create rooms, delete
+rooms, set access levels, invite users, check registration, grant
+room aide status, and countless other things.  They have access to the
+Aide> room, a special room only for aides.
+   If you enter a mail message to "Sysop" it will be placed in the
+Aide> room so that the next aide online will read it and deal with it.
+Aides cannot <Z>ap rooms.  All the rooms are always on each aide's
+scanlist.  Aides can read *any* and *every* room, but they *CAN* *NOT*
+read other user's Mail!
+ Room aides
+ ----------
+   Room aides are granted special privileges in rooms that they aide.
+They are *NOT* true system aides; their power extends only over the
+rooms that they control, and they answer to the system aides.
+   A room aide's job is to keep the topic of the their room on track,
+with nudges in the right direction now and then.  A room aide can also
+move an off topic post to another room, or delete a post, if he/she
+feels it is necessary. 
+   Currently, very few rooms have room aides.  Most rooms do not need
+their own specific room aide.  Being a room aide requires a certain
+amount of trust, due to the additional privileges granted.
+ Citadel messages
+ ----------------
+   Most of the time, the bbs code does not print a lot of messages
+to your screen.  This is a great benefit once you become familiar
+with the system, because you do not have endless menus and screens
+to navigate through.  nevertheless, there are some messages which you
+might see from time to time.
+  "There were messages posted while you were entering."
+   This is also known as "simulposting."  When you start entering a 
+message, the system knows where you last left off.  When you save
+your message, the system checks to see if any messages were entered
+while you were typing.  This is so that you know whether you need
+to go back and re-read the last few messages.  This message may appear
+in any room.
+   
+ "*** You have new mail"
+   This message is essentially the same as the above message, but can
+appear at any time.  It simply means that new mail has arrived for you while
+you are logged in.  Simply go to the Mail> room to read it.
+ Who list
+ --------
+   The <W>ho command shows you the names of all users who are currently
+online.  It also shows you the name of the room they are currently in.  If
+they are in any type of private room, however, the room name will simply
+display as "<private room>".  Along with this information is displayed the
+name of the host computer the user is logged in from.
+ (This file was shamelessly swiped from QuartzBBS.  Thanks to its original
+author for putting it together.)
diff --git a/citadel/help/mail b/citadel/help/mail
new file mode 100644 (file)
index 0000000..69225ca
--- /dev/null
@@ -0,0 +1,20 @@
+To send mail on this system, go to the Mail> room (using the command .G Mail)
+and press E to enter a message.  You will be prompted with:
+ Enter Recipient:
+   At this point you may enter the name of another user on the system.  Private
+mail is only readable by the sender and recipient.  There is no need to delete
+mail after it is read; it will scroll out automatically.
+  
+   To send mail to another user on the Citadel network, simply type the
+user's name, followed by @ and then the system name. For example,
+  
+ Enter Recipient: Joe Schmoe @ citadrool
+  
+   To send mail to a user on the Internet, type their network address
+at the recipient prompt.  Citadel/UX will send it over the network:
+  
+ Enter Recipient: ajc@herring.fishnet.com
+   
+ Your e-mail address on other Citadels is ^username @ ^nodename
+ Your e-mail address on the Internet is cit^usernum@^fqdn
diff --git a/citadel/help/network b/citadel/help/network
new file mode 100644 (file)
index 0000000..4a2ce39
--- /dev/null
@@ -0,0 +1,4 @@
+  Welcome to the network. Messages entered in a network room will appear in
+that room on all other systems carrying it (The name of the room, however,
+may be different on other systems).
diff --git a/citadel/help/nice b/citadel/help/nice
new file mode 100644 (file)
index 0000000..2e54114
--- /dev/null
@@ -0,0 +1,128 @@
+ The following are a few points of general BBS etiquette. If you wish
+to maintain your welcome on whatever system you happen to call, it
+would be to your advantage to observe these few rules.  Feel free to
+download this and display spread it around.
+  1. Don't habitually hang up on a system.  Every Sysop is aware that
+accidental disconnections happen once in a while but we do tend to
+get annoyed with people who hang up every single time they call
+because they are either too lazy to terminate properly or they labor
+under the mistaken assumption that the 10 seconds they save online is
+going to significantly alter their phone bill.  "Call Waiting" is not
+an acceptable excuse for long.  If you have it and intend to use the
+line to call BBS's you should either have it disconnected or find
+some other way to circumvent it.
+  2. Don't do dumb things like leave yourself a message that says
+"Just testing to see if this thing works".  Where do you think all
+those other messages came from if it didn't work?  Also, don't leave
+whiney messages that say "Please leave me a message".  If ever there
+was a person to ignore, it is the one who begs someone to leave him a
+message.  If you want to get messages, start by reading the ones that
+are already online and getting involved in the conversations that
+exist.
+  3. Don't use the local equivalent of a chat command unless you
+really have some clear cut notion of what you want to say and why.
+Almost any Sysop is more than happy to answer questions or offer help
+concerning his system.  Unfortunately, because about 85% of the
+people who call want to chat and about 99% of those people have
+absolutely nothing to say besides "How old are you?" or something
+equally irrelevant, fewer Sysops even bother answering their pagers
+every day.
+  4. When you are offered a place to leave comments when exiting a
+system, don't try to use this area to ask the Sysop questions.  It is
+very rude to the other callers to expect the Sysop to carry on a half
+visible conversation with someone.  If you have a question or
+statement to make and expect the Sysop to respond to it, it should
+always be made in the section where all the other messages are kept.
+This allows the Sysop to help many people with the same problem with
+the least amount of effort on his part.
+  5. Before you log on with your favorite pseudonym, make sure that
+handles are allowed.  Some Sysops do not want people using handles on
+the system.  The reasons vary, but everyone should still be willing to
+take full responsibility for his actions or comments instead of
+slinging mud from behind a phoney name.
+  Also when signing on, why not sign on just like you would introduce
+yourself in your own society?  How many of you usually introduce
+yourselves as Joe W Smutz the 3rd or 4th?
+  6. Take the time to log on properly.  If the BBS asks you the city
+where you are calling from, remember that there is no such place as
+RIV, HB, ANA or any of a thousand other abbreviations people use
+instead of their proper city.  You may think that everyone knows what
+RIV is supposed to mean, but every BBS has people calling from all
+around the country and I assure you that someone from Podunk, Iowa
+has no idea what you are talking about.
+  7. Don't go out of your way to make rude observations like "Gee,
+this system is slow".  Every BBS is a tradeoff of features.  You can
+generally assume that if someone is running a particular brand of
+software, that he is either happy with it or he will decide to find
+another system he likes better.  It does nobody any good when you
+make comments about something that you perceive to be a flaw when it
+is running the way the Sysop wants it to.  Constructive criticism is
+somewhat more welcome.  If you have an alternative method that seems
+to make good sense then run it up the flagpole.
+  8. When leaving messages, stop and ask yourself whether it is
+necessary to make it private.  Unless there is some particular reason
+that everyone should not know what you are saying, do not make it
+private.  We do not call them PUBLIC bulletin boards for nothing,
+folks.  It is very irritating to other callers when there are huge
+blank spots in the messages that they can not read and it stifles
+interaction between callers.
+  9. If your favorite BBS has a time limit, observe it.  If it
+doesn't, set a limit for yourself and abide by it instead.  Do not
+tie up a system as a new user and run right to the other numbers
+list.  There is probably very little that is more annoying to any
+Sysop than to have his board completely passed over by you on your
+way to another board.
+  10. Have the common courtesy to pay attention to what passes in
+front of your face.  When a BBS displays your name and asks "Is this
+you?", don't say yes when you can see perfectly well that it is
+misspelled.  Also, do not start asking questions about simple
+operation of a system until you have thoroughly read all of the
+instructions that are available to you.  I assure you that it is not
+any fun to answer a question for the thousandth time when the answer
+is prominently displayed in the system bulletins or instructions. Use
+some common sense when you ask your questions.  The person who said
+"There is no such thing as a stupid question" obviously never
+operated a BBS.
+  11. Don't be personally abusive.  It does not matter whether you
+like a Sysop or think he/she is a jerk.  The fact remains that he/she
+has a large investment in making his computer available, usually out
+of the goodness of his/her heart.  If you don't like a Sysop or
+his/her system, just remember that you can change the channel any
+time you want.  Besides, whether you are aware of it or not, if you
+make yourself enough of an annoyance to any Sysop, he/she can take
+the time to trace you down and make your life, or that of your
+parents, miserable.
+  Along those lines, don't be abusive of other users on the system.
+It doesn't matter what you think of him/her/them, but "If you don't
+have something nice to say, don't say it."  If you think someone is
+being too abusive/whatever, let the Sysop know.  It is his/her
+system, and upon him/her lies the responsibilty of dealing with
+problem users.  If you think that he/she is not doing a good enough
+job, do not call back.
+  12. Lastly and ****** MOST IMPORTANTLY ****** keep firmly in mind
+that you are a *** GUEST *** on any BBS you happen to call.  Do not
+think of logging on as one of your basic human rights.  Every person
+that has ever put a computer system online for the use of other
+people has spent a lot of time and money to do so.  While he/she does
+not expect nonstop pats on the back, it seems reasonable that he/she
+should at least be able to expect fair treatment from his/her
+callers.  This includes following any of the rules for system use
+he/she has laid out without grumping about it.  Every Sysop has
+his/her own idea of how he/she wants his/her system to be run.  It is
+really none of your business why he/she wants to run it the way
+he/she does.  Your business is to either abide by what he says, or
+call some other BBS where you feel that you can obey the rules.
diff --git a/citadel/help/policy b/citadel/help/policy
new file mode 100644 (file)
index 0000000..6dfecc8
--- /dev/null
@@ -0,0 +1,3 @@
+ < this new user policy resides in ./messages/newuser > 
diff --git a/citadel/help/software b/citadel/help/software
new file mode 100644 (file)
index 0000000..e98bba7
--- /dev/null
@@ -0,0 +1,5 @@
+   Citadel/UX is the premier "online community" (i.e. Bulletin Board System)
+software.  It runs on all POSIX-compliant systems, including Linux.  It is an
+advanced client/server application, and is being actively maintained.
+   For more info, visit UNCENSORED! BBS at uncnsrd.mt-kisco.ny.us
diff --git a/citadel/help/summary b/citadel/help/summary
new file mode 100644 (file)
index 0000000..207cc6c
--- /dev/null
@@ -0,0 +1,48 @@
+Extended commands are available using the period ( . ) key. To use
+a dot command, press the . key, and then enter the first letter of
+each word in the command. The words will appear as you enter the keys.
+You can also backspace over partially entered commands. The following
+commands are available:
+ .<E>nter message using <A>scii     - raw mode message input
+ .<E>nter <B>io                     - tell everyone about yourself
+ .<E>nter <C>onfiguration           - set up your account statistics
+ .<E>nter message with <E>ditor     - (installation dependent)
+ .<E>nter re<G>istration            - enter your name, address, etc.
+ .<E>nter <M>essage                 - enter message with the Citadel editor
+ .<E>nter <P>assword                - change your password
+ .<E>nter <R>oom                    - create a new room
+ .<E>nter <T>extfile                - ASCII upload
+ .<E>nter file using <X>modem       - protocol upload
+ .<E>nter file using <Y>modem       - protocol upload
+ .<E>nter file using <Z>modem       - protocol upload
+    
+ .<R>ead <B>io                      - read other users' bios
+ .<R>ead <D>irectory                - directory of current room
+ .<R>ead <F>ile unformatted         - raw ASCII download
+ .<R>ead <I>nfo file                - read info file for this room
+ .<R>ead <L>ast five messages       - same as <L>
+ .<R>ead <N>ew messages             - same as <N>
+ .<R>ead <O>ld messages             - same as <O>
+ .<R>ead <R>everse                  - same as <R>
+ .<R>ead <T>extfile paginated       - prints a textfile with page prompts
+ .<R>ead <U>ser List                - lists users with accounts on system
+ .<R>ead file using <X>modem        - protocol download
+ .<R>ead file using <Y>modem        - protocol download
+ .<R>ead file using <Z>modem        - protocol download
+    
+ .<G>oto room: (type roomname)      - jump to a specific room
+                                      (you only need to type enough of the
+                                      room name to make it unique)
+ .<S>kip, goto: (type roomname)     - skip current room, jump to another
+ .<H>elp file: (type filename)      - read a help file
+ .<T>erminate and <Q>uit            - log out immediately without prompts
+ .<T>erminate and <S>tay online     - log out and allow another user to log in
+ .<Z>apped room list                - list all zapped (forgotten) rooms
+
+ Floor commands (if using floor mode)
+ ;<C>onfigure floor mode            - turn floor mode on or off
+ ;<G>oto floor:                     - jump to a specific floor
+ ;<K>nown rooms                     - list all rooms on all floors
+ ;<S>kip to floor:                  - skip current floor, jump to another
+ ;<Z>ap floor                       - zap (forget) all rooms on this floor
diff --git a/citadel/housekeeping.c b/citadel/housekeeping.c
new file mode 100644 (file)
index 0000000..159169e
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * This file contains housekeeping tasks which periodically
+ * need to be executed.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <time.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+#include "citadel.h"
+#include "server.h"
+#include "proto.h"
+
+/*
+ * Terminate idle sessions.  This function pounds through the session table
+ * comparing the current time to each session's time-of-last-command.  If an
+ * idle session is found it is terminated, then the search restarts at the
+ * beginning because the pointer to our place in the list becomes invalid.
+ */
+void terminate_idle_sessions(void) {
+       struct CitContext *ccptr;
+       time_t now;
+       
+       time(&now);
+       for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
+               if (  (ccptr!=CC)
+                  && (config.c_sleeping > 0)
+                  && (now - (ccptr->lastcmd) > config.c_sleeping) ) {
+                       lprintf(3, "Session %d timed out\n", ccptr->cs_pid);
+                       kill_session(ccptr->cs_pid);
+                       ccptr = ContextList;
+                       }
+               }
+       }
+
+
+/*
+ * Main housekeeping function.
+ */
+void do_housekeeping() {
+
+       begin_critical_section(S_HOUSEKEEPING);
+       /*
+        * Terminate idle sessions.
+        */
+       lprintf(7, "Calling terminate_idle_sessions()\n");
+       terminate_idle_sessions();
+
+       /*
+        * If the server is scheduled to shut down the next time all
+        * users are logged out, now's the time to do it.
+        */
+       if ((ScheduledShutdown == 1) && (ContextList == NULL)) {
+               lprintf(3, "Scheduled shutdown initiating.\n");
+               master_cleanup();
+               }
+       end_critical_section(S_HOUSEKEEPING);
+       }
+
+
diff --git a/citadel/install.txt b/citadel/install.txt
new file mode 100644 (file)
index 0000000..b0d7ad1
--- /dev/null
@@ -0,0 +1,353 @@
+                      Citadel/UX Installation Procedure
+                 See copyright.doc for copyright information
+ OVERVIEW
+   Citadel/UX is an advanced, multiuser, client/server, room-based BBS
+program.  It is designed to handle the needs of both small dialup systems and
+large-scale Internet-connected systems.  It was originally developed on an
+Altos system running Xenix, and has been installed and tested on various
+Unix and Unix-like platforms.  The author's current development environment
+(and BBS) is a Linux/GNU system.  The current distribution includes:
+  
+    - The Citadel/UX server (this is the back end that does all processing)
+    - A text-based client program designed with the traditional Citadel "look
+      and feel" (room prompts, dot commands, and the like)
+    - A networker that utilizes any file transfer mechanism (such as UUCP for
+      standalone systems, or ftp for Internet) and can share messages with other
+      Citadel/UX systems, as well as UseNet sites.  Gateway software to talk
+      with C86NET (Citadel-86 and its deriviatives), HengeNet (StoneHenge),
+      and NYTI FordBoard is also available.
+    - Setup programs
+    - A rich set of utilities for system administration and maintenance
+    - Documentation
+ Some knowledge of the Unix system is necessary to install and manage the
+system. It is preferable that the sysop have superuser access to the operating
+system. The following are required to install Citadel/UX:
+
+    - Some sort of Unix (or Unix look-alike) operating system
+    - C compiler
+    - POSIX threads
+    - TCP/IP
+    - the "make" utility (you don't want to try compiling it manually!)
+    - Enough disk space to hold all of the programs and data
+   
+ NOW AVAILABLE:
+    - "WebCit", a gateway program to allow full access to Citadel/UX BBS's
+      via the World Wide Web.  Interactive access through any Web browser.
+    - A point-and-click client program for MS Windows.  (The text-based client
+      is now available for Windows as well.)
+ COMING SOON: 
+  
+    - A client program written in Java that will be portable to all operating
+      systems.
+   
+  
+ EVERYTHING IN ITS PLACE...
+
+   Hopefully you've unpacked the distribution archive into its own directory.
+This is the directory in which all Citadel files are located and in which all
+BBS activity will take place. Several subdirectories have already been created
+during the unpacking process, and others may be created by the software if
+needed.
+   
+  
+ THE BBS LOGIN
+  
+   There will be one account in /etc/passwd which all BBS users will use to
+login to the system. This account is typically called "bbs" or "citadel" or
+something to that effect. You will tell Citadel what the user-id of that
+account is, and when someone logs in under that account, Citadel will prompt
+for a user name.
+
+The BBS login should have a unique uid. The home directory should be the
+one your BBS resides in (in this example we will use /usr/bbs) and the shell
+should be either "citadel" in that directory, or a script that will start up
+citadel (you may wish to set up an external text editor; see below).  Example:
+ bbs::100:1:BBS Login:/usr/citadel:/usr/citadel/citadel
+  
+ When you run setup later, you will be required to tell it what the BBS
+login's numeric user ID is, so it knows what user to run as.
+
+  For all other users in /etc/passwd, Citadel will automatically set up an
+account using the "full name" field. No password is required, since it
+assumes that if a user is logged in, he/she has already entered a password.
+Note that this does have to be enabled at compile time (see ENABLE_AUTOLOGIN
+in the Makefile).  If such an account needs to be accessed remotely (such as
+from client software), these users can use *either* their Citadel login name
+or their login name on the host computer, and their password on the host
+computer.
+   
+  
+ EDITING STUFF BEFORE COMPILING...
+
+   If you are upgrading an existing installation, be sure to check the
+sysconfig.h header, to make sure the values there are the same as in your
+previous installation.  For a new system, it's best to leave these values
+alone, so you won't have to worry about it the next time you upgrade.
+   You might also want to check the "Configure" script for platform-specific
+stuff.  Any platforms which have been tested will be auto-detected by the
+script and the compiler and linker directives set accordingly.  If your
+platform isn't autodetected, you'll have to figure out the flags yourself (but
+please get in touch so your platform can be supported in the next release!).
+ COMPILING THE PROGRAMS
+   
+   As with most Unix programs, you compile it using these two commands:
+   ./Configure
+   make
+   The 'Configure' script will generate a Makefile from the Makefile.in, and
+it will also write the file "sysdep.h" to your Citadel directory.  Please do
+not edit sysdep.h or Makefile.in yourself.  The Configure script will figure
+out your system dependencies and set everything correctly.  The ONLY places
+you should be editing, if anywhere at all, are sysconfig.h and Configure.
+   File permissions are always a bother to work with. You don't want the
+board to crash because someone couldn't access a file, but you also don't
+want shell users peeking into the binaries to do things like reading others'
+mail, finding private rooms, etc.   The Citadel server needs to be started
+as root in order to bind to a privileged port, but as soon as its
+initialization is finished, it changes its user ID to your BBS user ID in
+order to avoid security holes.
+  
+    
+ THE CITADEL.RC FILE
+ This is a change from the way things were done before.  All client-side setup
+is in a "citadel.rc" file.  The standard client looks for this file in:
+ 1. $HOME/.citadelrc
+ 2. /usr/local/lib/citadel.rc
+ 3. <compiled BBSDIR>/citadel.rc
+ The next couple of sections deal with client-side configuration.
+ USING AN EXTERNAL EDITOR FOR MESSAGES
+ Citadel/UX has a built-in message editor.  However, you can also use your
+favorite text editor to write messages.  To do this you simply put a line in
+your citadel.rc file like:
+
+editor=/usr/bin/vi
+ ...would make Citadel call the vi editor when using the .<E>nter <E>ditor
+command.  You can also make it the default editor for the <E>nter command by
+editing the citadel.rc file.  (WARNING: external editors on public systems
+can create a security hole.  Make sure there is absolutely no way for users
+to drop into the shell from the editor, or save files other than the temp file
+they are editing!)
+
+ Using this mechanism, shell users can pick their favorite editor for Citadel.
+BBS users can use external editors too; just have the bbs login call a script
+that sets the variables and then calls citadel.  I used to recommend using
+an external editor as the default, but the built-in editor is now a bit more
+robust, so the use of an external editor is definitely optional.  By the
+way, be VERY careful what editor you choose and how you set up its options.
+Giving the general public to an editor like vi or emacs can open up lots of
+security holes.
+
+ Citadel runs the external editor using the REAL uid and gid of the user, if
+you are running it in setuid mode.
+
+
+ PRINTING MESSAGES
+ Citadel/UX can send messages to a printer, or just about anywhere else in
+your system.  The variable PRINTCMD in citadel.rc specifies what command you
+use to print.  Text is sent to the standard input (stdin) of the print command.
+ So if you did this:
+printcmd="nl|pr|lpr -dlocal"
+ ...that would add line numbers, then paginate, then print on the printer
+named "local".  There's tons of stuff you can do with this feature.  For
+example, you could use a command like "cat >>$HOME/archive" to save copies
+of important messages in a textfile.
+
+
+ SETUP AND LOGIN
+  
+   Before logging in for the first time, you must run the setup program.  Type
+"./setup" to begin this procedure.  (Be sure to use the "./" because some
+systems, most notably the Slackware distribution of Linux, will run the
+operating system's own setup program if you just type the word "setup".)
+Sit back and relax; there are a slew of options here that will allow you to
+customize the BBS to your liking.
+   The setup program will ask you what directory to place your data files in.
+You can use this functionality to keep your programs and data in different
+directories, or to run more than one BBS on the same computer.  If you don't
+use the default directory (the one specified in the Makefile), remember to
+specify the directory name again when you start up the server later on.
+   If this is a new installation, the setup program will automatically
+create all of your data files.  Otherwise, it will ask you if you want to
+re-initialize them.  Normally you will answer "no" unless you want to wipe
+out your system for some reason.
+  
+      
+ PREPARING TO START THE SERVER
+   
+   Before you can use Citadel, you must define the "citadel" service to your 
+system.  This is accomplished by adding a line to your /etc/services file that
+looks something like this:
+ citadel               504/tcp                 # Citadel/UX Server
+   
+ 504 is the port number officially designated by the IANA for use by Citadel. 
+There should not be any need to use a different port number, unless you are 
+running multiple BBS's on the same computer and therefore need a different
+port for each one.
+  The next step is to arrange for the server to start.  The "citserver"
+program is the main Citadel server.  Before we cover the recommended method of
+starting the server, let's examine its usage options:
+ citserver [-hHomeDir] [-xDebugLevel] [-tTraceFile] [-d]
+ The options are as follows:
+ -hHomeDir     - the directory your BBS data files live in.  This should, of
+course, be a directory that you've run the "setup" program against to set up
+some data files.  If a directory is not specified, the directory name which
+was specified in the Makefile will be used.
+ -xDebugLevel  - Set the verbosity of trace messages printed.  The available 
+debugging levels are:
+    1 - Internal errors (failed thread creation, malloc problems, etc.)
+    2 - Network errors (broken sockets, failed socket creation)
+    3 - Begin and end of sessions, startup/shutdown of server
+    5 - Server commands being sent from clients
+    7 - Entry and exit of various functions 
+    8 - Entry and exit of critical sections
+    9 - Various debugging checkpoints (insanely verbose)
+  
+ -tTraceFile   - Tell the server where to send its debug/trace output.  
+Normally it is sent to stdout.
+ -d            - Run as a daemon.  This switch would be necessary if you were 
+starting the Citadel server, for example, from an rc.local script (which is 
+not recommended).
+   The preferred method of starting the Citadel server is to place an entry in
+your /etc/inittab file.  This will conveniently bring the server up when your 
+system is up, and terminate it gracefully when your system is shutting down.  
+The exact syntax for your system may vary, but here's the entry that I use on 
+my Linux system:
+  
+ cit:2345:respawn:/appl/citadel/citserver -h/appl/citadel -t/dev/tty6 -x3
+ What I've done here is to set debugging level 3, and have the trace stuff
+output to one of my virtual consoles.  It's important to remember to turn off
+any getty that is set up on that virtual console, if you do this.  After
+making this change, the command "init q" works on most systems to tell init
+to re-read the file.  If in doubt, just reboot your computer.
+   
+   
+ LOGGING IN FOR THE FIRST TIME
+   
+   At this point, your system is ready to run. Run the citadel program from
+the shell and it will automatically create your account.  NOTE: the first
+user account to be created will automatically be set to access level 6
+(Aide).  This overcomes some obvious logistical problems - normally, Aide
+access is given by another Aide, but since there aren't any on your system
+yet, this isn't possible.  You could also use the useradmin utility to
+accomplish the same thing, but this makes it a bit easier.
+    
+   
+ SPACE FOR ADDING YOUR OWN FEATURES (doors)
+   The "doorway" feature is just a generic way to add features to the system.
+I called it "Doorway" to make it resemble the doors on non-Unix boards, but as
+we all know, us Unix types don't have to write special code to access the
+modem. :-)  Anyway, when a user hits the <*> (doorway) command, Citadel does...
+   USERNAME=<username>; export USERNAME
+   ./subsystem <user number> <screen width> <access level>
+   ...so you can put whatever you want in there.  I suggest putting in a menu
+program to allow the users to pick one of a number of programs, etc.
+   Do be aware that chat and door programs will only be available when two
+conditions are met:
+    1. The client and server programs are running on the same computer
+    2. The user is running the text-based Unix client.
+   Because of these restrictions, Door programs are being utilized less and
+less every day.
+
+  
+ SETTING UP CITADEL TO SEND AND RECEIVE INTERNET MAIL
+ Mail programs are now elaborate enough that it is trivial to set up Citadel
+to act as your system's local mail delivery agent.  It couples easily with
+either sendmail or smail, or with any other mail system that is capable of
+invoking a separate program to deliver local mail.
+ Unlike earlier versions of Citadel/UX, there is no longer a need to play
+with rmail or to patch other pieces of your system's existing mailer.  Simply
+make a few quick configurations, compile the Citadel/UX package "as is, and
+you're ready to go.  Here's how to do it:
+ 1. First, open up the config file "internetmail.config" in the "network"
+directory, and edit the definitions in it to your local configuration.  It's
+very self-documented; just go through the file making any necessary changes.
+
+ 2. Next, you must configure your system's main mail delivery agent to
+use the "citmail" program to deliver mail to local addresses.  This will
+change from mailer to mailer, of course.  I'm using sendmail, and although
+I don't know enough about sendmail to provide really good instructions on
+sendmail configuration, here's what worked for me:
+    
+  I added the following mailer definition:
+  
+ Mcitadel,  P=/appl/citadel/citmail, F=lsDFMoqeu9, S=10/30, R=20/40, D=$z:/,
+            T=X-Unix,
+            A=/appl/citadel/citmail $u
+  Then I replaced all instances of "#local" with "#citadel".  This seems to
+work on my system; your mileage may vary.
+ 3. Some mailers will need to be killed and restarted for the changes to
+take effect.  Once this is done, all of your BBS users now have an Internet
+e-mail address.  They can use two forms of addresses:
+  user_name@your.system.name
+  cit1234@your.system.name
+ In the first form, the name portion of the user's Internet e-mail address
+is the name they use on your Citadel system, with all spaces replaced by
+underscores.  In the second form, the name is the letters "cit" followed
+by the user's user number.  This is a nice shorthand that is sometimes
+easier to use.  Note that the help file <.H>elp MAIL is set up to tell users
+what their address is.
+ NOTE: I cannot and will not answer e-mails regarding the configuration of
+sendmail or any other mailer.  I am not a sendmail expert; what is written
+above is everything I know about getting Citadel and sendmail to talk to each
+other.  Please refer these questions to your local sendmail wizard. 
+ THE PEANUT GALLERY
+  
+   That's just about all the information you need to install the system. If
+you have any comments, suggestions, bomb threats, etc., send them to
+ajc@uncnsrd.mt-kisco.ny.us or call Uncensored Communications Group BBS at
+(914) 244-3252 (modem) or uncnsrd.mt-kisco.ny.us (Internet).
diff --git a/citadel/internetmail.c b/citadel/internetmail.c
new file mode 100644 (file)
index 0000000..351f63f
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Internet mail configurator for Citadel/UX
+ * see copyright.doc for copyright information
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <time.h>
+#include <string.h>
+#include <ctype.h>
+#include <syslog.h>
+#include "citadel.h"
+
+extern struct config config;
+
+int struncmp();
+char metoo[10][128];
+int mecount = 0;
+
+extern char ALIASES[128];
+extern char CIT86NET[128];
+extern char SENDMAIL[128];
+extern char FALLBACK[128];
+extern char GW_DOMAIN[128];
+extern char TABLEFILE[128];
+extern int RUN_NETPROC;
+
+void StripLeadingAndTrailingWhitespace(char *str) {
+       if (strlen(str) == 0) return;
+       while (isspace(str[0])) strcpy(str, &str[1]);
+       while (isspace(str[strlen(str)-1])) str[strlen(str)-1] = 0;
+       }
+
+void LoadInternetConfig() {
+       char ParamName[256], ParamValue[256], buf[256];
+       FILE *conf;
+       int a, eqpos;
+
+
+       conf = fopen("network/internetmail.config", "r");
+       if (conf == NULL) {
+               syslog(LOG_NOTICE, "Couldn't load internetmail.config");
+               exit(1);
+               }
+
+       while (fgets(buf, 256, conf) != NULL) {
+               if (strlen(buf) > 0) buf[strlen(buf) - 1] = 0;
+               strcpy(ParamName, "");
+               strcpy(ParamValue, "");
+               if (buf[0] != '#') {
+                       eqpos = (-1);
+                       for (a=strlen(buf); a>=0; --a) {
+                               if (buf[a] == '=') eqpos = a;
+                               }
+                       if (eqpos >= 0) {
+                               strcpy(ParamName, buf);
+                               ParamName[eqpos] = 0;
+                               strcpy(ParamValue, &buf[eqpos+1]);
+                               }
+
+                       StripLeadingAndTrailingWhitespace(ParamName);
+                       StripLeadingAndTrailingWhitespace(ParamValue);
+
+                       if (!strucmp(ParamName, "aliases"))
+                               strcpy(ALIASES, ParamValue);
+                       if (!strucmp(ParamName, "cit86net spoolin"))
+                               strcpy(CIT86NET, ParamValue);
+                       if (!strucmp(ParamName, "sendmail"))
+                               strcpy(SENDMAIL, ParamValue);
+                       if (!strucmp(ParamName, "fallback"))
+                               strcpy(FALLBACK, ParamValue);
+                       if (!strucmp(ParamName, "gateway domain"))
+                               strcpy(GW_DOMAIN, ParamValue);
+                       if (!strucmp(ParamName, "table file"))
+                               strcpy(TABLEFILE, ParamValue);
+                       if (!strucmp(ParamName, "deliver local"))
+                               strcpy(metoo[mecount++], ParamValue);
+                       if (!strucmp(ParamName, "run netproc")) 
+                               RUN_NETPROC = atoi(ParamValue);
+                       }
+               }
+       fclose(conf);
+       }
+
+
+/* 
+ * returns nonzero if the specified host is listed as local
+ */
+int IsHostLocal(char *WhichHost) {
+       int a;
+
+       if (!strucmp(WhichHost, FQDN)) return(1);
+
+       if (mecount > 0) {
+               for (a=0; a<mecount; ++a) {
+                       if (!strucmp(WhichHost, metoo[a])) return(1);
+                       }
+               }
+       
+       return(0);
+       }
diff --git a/citadel/ipc_c_tcp.c b/citadel/ipc_c_tcp.c
new file mode 100644 (file)
index 0000000..c408136
--- /dev/null
@@ -0,0 +1,335 @@
+/*
+ * ipc_c_std.c
+ * 
+ * Citadel/UX client/server IPC - client module using TCP/IP
+ *
+ * version 1.3
+ *
+ */
+
+#define DEFAULT_HOST   "127.0.0.1"
+#define DEFAULT_PORT   "citadel"
+
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <string.h>
+#include <pwd.h>
+#include <errno.h>
+
+void logoff();
+
+/*
+ * If server_is_local is set to nonzero, the client assumes that it is running
+ * on the same computer as the server.  Several things happen when this is
+ * the case, including the ability to map a specific tty to a particular login
+ * session in the "<W>ho is online" listing, the ability to run external
+ * programs, and the ability to download files directly off the disk without
+ * having to first fetch them from the server.
+ * Set the flag to 1 if this IPC is local (as is the case with pipes, or a
+ * network session to the local machine) or 0 if the server is executing on
+ * a remote computer.
+ */
+char server_is_local = 0;
+
+#ifndef INADDR_NONE
+#define INADDR_NONE 0xffffffff
+#endif
+
+int serv_sock;
+
+u_long inet_addr();
+
+void timeout() {
+       printf("\rConnection timed out.\n");
+       logoff(3);
+       }
+
+int connectsock(host,service,protocol)
+char *host;
+char *service;
+char *protocol; {
+       struct hostent *phe;
+       struct servent *pse;
+       struct protoent *ppe;
+       struct sockaddr_in sin;
+       int s,type;
+
+       bzero((char *)&sin,sizeof(sin));
+       sin.sin_family = AF_INET;
+
+       pse=getservbyname(service,protocol);
+       if (pse) {
+               sin.sin_port = pse->s_port;
+               }
+       else if ((sin.sin_port = htons((u_short)atoi(service))) == 0) {
+               fprintf(stderr,"Can't get %s service entry: %s\n",
+                       service,strerror(errno));
+               logoff(3);
+               }
+       
+       phe=gethostbyname(host);
+       if (phe) {
+               bcopy(phe->h_addr,(char *)&sin.sin_addr,phe->h_length);
+               }
+       else if ((sin.sin_addr.s_addr = inet_addr(host))==INADDR_NONE) {
+               fprintf(stderr,"Can't get %s host entry: %s\n",
+                       host,strerror(errno));
+               logoff(3);
+               }
+
+       if ((ppe=getprotobyname(protocol))==0) {
+               fprintf(stderr,"Can't get %s protocol entry: %s\n",
+                       protocol,strerror(errno));
+               logoff(3);
+               }
+
+       if (!strcmp(protocol,"udp")) {
+               type = SOCK_DGRAM;
+               }
+       else {
+               type = SOCK_STREAM;
+               }
+
+       s = socket(PF_INET,type,ppe->p_proto);
+       if (s<0) {
+               fprintf(stderr,"Can't create socket: %s\n",strerror(errno));
+               logoff(3);
+               }
+
+
+       signal(SIGALRM,timeout);
+       alarm(30);
+
+       if (connect(s,(struct sockaddr *)&sin,sizeof(sin))<0) {
+               fprintf(stderr,"can't connect to %s.%s: %s\n",
+                       host,service,strerror(errno));
+               logoff(3);
+               }
+
+       alarm(0);
+       signal(SIGALRM,SIG_IGN);
+
+       return(s);
+       }
+
+/*
+ * convert service and host entries into a six-byte numeric in the format
+ * expected by a SOCKS v4 server
+ */
+void numericize(buf,host,service,protocol)
+unsigned char buf[];
+char *host;
+char *service;
+char *protocol; {
+       struct hostent *phe;
+       struct servent *pse;
+       struct sockaddr_in sin;
+
+       bzero((char *)&sin,sizeof(sin));
+       sin.sin_family = AF_INET;
+
+       pse=getservbyname(service,protocol);
+       if (pse) {
+               sin.sin_port = pse->s_port;
+               }
+       else if ((sin.sin_port = htons((u_short)atoi(service))) == 0) {
+               fprintf(stderr,"Can't get %s service entry: %s\n",
+                       service,strerror(errno));
+               logoff(3);
+               }
+
+       buf[1] = (((sin.sin_port) & 0xFF00) >> 8);
+       buf[0] = ((sin.sin_port) & 0x00FF);
+       
+       phe=gethostbyname(host);
+       if (phe) {
+               bcopy(phe->h_addr,(char *)&sin.sin_addr,phe->h_length);
+               }
+       else if ((sin.sin_addr.s_addr = inet_addr(host))==INADDR_NONE) {
+               fprintf(stderr,"Can't get %s host entry: %s\n",
+                       host,strerror(errno));
+               logoff(3);
+               }
+       buf[5] = ((sin.sin_addr.s_addr) & 0xFF000000) >> 24;
+       buf[4] = ((sin.sin_addr.s_addr) & 0x00FF0000) >> 16;
+       buf[3] = ((sin.sin_addr.s_addr) & 0x0000FF00) >> 8;
+       buf[2] = ((sin.sin_addr.s_addr) & 0x000000FF) ;
+       }
+
+/*
+ * input binary data from socket
+ */
+void serv_read(buf,bytes)
+char buf[];
+int bytes; {
+       int len,rlen;
+
+       len = 0;
+       while(len<bytes) {
+               rlen = read(serv_sock,&buf[len],bytes-len);
+               if (rlen<1) {
+                       printf("\rNetwork error - connection terminated.\n");
+                       printf("%s\n", strerror(errno));
+                       logoff(3);
+                       }
+               len = len + rlen;
+               }
+       }
+
+
+/*
+ * send binary to server
+ */
+void serv_write(buf, nbytes)
+char buf[];
+int nbytes; {
+       int bytes_written = 0;
+       int retval;
+       while (bytes_written < nbytes) {
+               retval = write(serv_sock, &buf[bytes_written],
+                       nbytes - bytes_written);
+               if (retval < 1) {
+                       printf("\rNetwork error - connection terminated.\n");
+                       printf("%s\n", strerror(errno));
+                       logoff(3);
+                       }
+               bytes_written = bytes_written + retval;
+               }
+       }
+
+
+
+/*
+ * input string from socket - implemented in terms of serv_read()
+ */
+void serv_gets(buf)
+char buf[]; {
+       buf[0] = 0;
+       do {
+               buf[strlen(buf) + 1] = 0;
+               if (strlen(buf) < 255) serv_read(&buf[strlen(buf)], 1);
+               } while (buf[strlen(buf)-1] != 10);
+       if (strlen(buf) > 0) buf[strlen(buf)-1] = 0;
+       /* printf("> %s\n", buf); */
+       }
+
+
+/*
+ * send line to server - implemented in terms of serv_write()
+ */
+void serv_puts(buf)
+char *buf; {
+       /* printf("< %s\n", buf); */
+       serv_write(buf, strlen(buf));
+       serv_write("\n", 1);
+       }
+
+
+/*
+ * attach to server
+ */
+void attach_to_server(argc,argv)
+int argc;
+char *argv[]; {
+       int a;
+       char cithost[256];      int host_copied = 0;
+       char citport[256];      int port_copied = 0;
+       char socks4[256];
+       unsigned char buf[256];
+       struct passwd *p;
+
+       strcpy(cithost,DEFAULT_HOST);   /* default host */
+       strcpy(citport,DEFAULT_PORT);   /* default port */
+       strcpy(socks4,"");              /* SOCKS v4 server */
+
+       for (a = 0; a < argc; ++a) {
+               if (a == 0) {
+                       /* do nothing */
+                       }
+               else if (!strcmp(argv[a],"-s")) {
+                       strcpy(socks4,argv[++a]);
+                       }
+               else if (host_copied == 0) {
+                       host_copied = 1;
+                       strcpy(cithost,argv[a]);
+                       }
+               else if (port_copied == 0) {
+                       port_copied = 1;
+                       strcpy(citport,argv[a]);
+                       }
+
+/*
+               else {
+                       fprintf(stderr,"%s: usage: ",argv[0]);
+                       fprintf(stderr,"%s [host] [port] ",argv[0]);
+                       fprintf(stderr,"[-s socks_server]\n");
+                       logoff(2);
+                       }
+*/
+               }
+
+       server_is_local = 0;
+       if ( (!strcmp(cithost,"localhost"))
+          || (!strcmp(cithost,"127.0.0.1")) ) server_is_local = 1;
+
+       /* if not using a SOCKS proxy server, make the connection directly */
+       if (strlen(socks4)==0) {
+               serv_sock = connectsock(cithost,citport,"tcp");
+               return;
+               }
+
+       /* if using SOCKS, connect first to the proxy... */
+       serv_sock = connectsock(socks4,"1080","tcp");
+       printf("Connected to SOCKS proxy at %s.\n",socks4);
+       printf("Attaching to server...\r");
+       fflush(stdout);
+
+       sprintf(buf,"%c%c",
+               4,                      /* version 4 */
+               1);                     /* method = connect */
+       serv_write(buf,2);
+
+       numericize(buf,cithost,citport,"tcp");
+       serv_write(buf,6);              /* port and address */
+
+       p = (struct passwd *) getpwuid(getuid());
+       serv_write(p->pw_name, strlen(p->pw_name)+1);
+                                       /* user name */
+
+       serv_read(buf,8);               /* get response */
+
+       if (buf[1] != 90) {
+               printf("SOCKS server denied this proxy request.\n");
+               logoff(3);
+               }
+
+       }
+
+/*
+ * return the file descriptor of the server socket so we can select() on it.
+ */
+int getsockfd() {
+       return serv_sock;
+       }
+
+
+/*
+ * return one character
+ */
+char serv_getc() {
+       char buf[2];
+       char ch;
+
+       serv_read(buf, 1);
+       ch = (int) buf[0];
+
+       return(ch);
+       }
diff --git a/citadel/ipcdef.h b/citadel/ipcdef.h
new file mode 100644 (file)
index 0000000..68d9db0
--- /dev/null
@@ -0,0 +1,72 @@
+#ifdef OK
+#undef OK
+#endif
+
+#define LISTING_FOLLOWS                100
+#define OK                     200
+#define MORE_DATA              300
+#define SEND_LISTING           400
+#define ERROR                  500
+#define BINARY_FOLLOWS         600
+#define SEND_BINARY            700
+#define START_CHAT_MODE                800
+
+#define INTERNAL_ERROR         10
+#define NOT_LOGGED_IN          20
+#define CMD_NOT_SUPPORTED      30
+#define PASSWORD_REQUIRED      40
+#define HIGHER_ACCESS_REQUIRED 50
+#define MAX_SESSIONS_EXCEEDED  51
+#define NOT_HERE               60
+#define INVALID_FLOOR_OPERATION        61
+#define NO_SUCH_USER           70
+#define FILE_NOT_FOUND         71
+#define ROOM_NOT_FOUND         72
+#define NO_SUCH_SYSTEM         73
+#define ALREADY_EXISTS         74
+
+struct serv_info {
+       int serv_pid;
+       char serv_nodename[32];
+       char serv_humannode[64];
+       char serv_fqdn[64];
+       char serv_software[64];
+       int serv_rev_level;
+       char serv_bbs_city[64];
+       char serv_sysadm[64];
+       char serv_moreprompt[256];
+       int serv_ok_floors;
+       };
+
+#define QR_PERMANENT   1               /* Room does not purge              */
+#define QR_INUSE       2               /* Set if in use, clear if avail    */
+#define QR_PRIVATE     4               /* Set for any type of private room */
+#define QR_PASSWORDED  8               /* Set if there's a password too    */
+#define QR_GUESSNAME   16              /* Set if it's a guessname room     */
+#define QR_DIRECTORY   32              /* Directory room                   */
+#define QR_UPLOAD      64              /* Allowed to upload                */
+#define QR_DOWNLOAD    128             /* Allowed to download              */
+#define QR_VISDIR      256             /* Visible directory                */
+#define QR_ANONONLY    512             /* Anonymous-Only room              */
+#define QR_ANON2       1024            /* Anonymous-Option room            */
+#define QR_NETWORK     2048            /* Shared network room              */
+#define QR_PREFONLY    4096            /* Preferred status needed to enter */
+#define QR_READONLY    8192            /* Aide status required to post     */
+
+
+#define US_NEEDVALID   1               /* User needs to be validated       */
+#define US_PERM                4               /* Permanent user                   */
+#define US_LASTOLD     16              /* Print last old message with new  */
+#define US_EXPERT      32              /* Experienced user                 */
+#define US_UNLISTED    64              /* Unlisted userlog entry           */
+#define US_NOPROMPT    128             /* Don't prompt after each message  */
+#define US_DISAPPEAR   512             /* Use "disappearing msg prompts"   */
+#define US_REGIS       1024            /* Registered user                  */
+#define US_PAGINATOR   2048            /* Pause after each screen of text  */
+#define US_INTERNET    4096            /* UUCP/Internet mail privileges    */
+#define US_FLOORS      8192            /* User wants to see floors         */
+#define US_USER_SET    (US_LASTOLD | US_EXPERT | US_UNLISTED | \
+                       US_NOPROMPT | US_DISAPPEAR | US_PAGINATOR | US_FLOORS)
+
+void serv_puts();
+void serv_gets();
diff --git a/citadel/locate_host.c b/citadel/locate_host.c
new file mode 100644 (file)
index 0000000..d3c194a
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * locate the originating host
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <string.h>
+#include <pthread.h>
+#include "sysdep.h"
+#include "citadel.h"
+#include "server.h"
+
+struct config config;
+
+void locate_host(char *tbuf)
+{
+       struct sockaddr_in      cs;     
+       struct hostent      *   ch;        
+       int                     len;     
+       char *i;
+       int a1,a2,a3,a4;
+       
+    len = sizeof(cs);   
+    if (getpeername(CC->client_socket, (struct sockaddr *)&cs,&len) < 0){   
+       strcpy(tbuf,config.c_fqdn);
+       return;
+       }
+     
+    if((ch = gethostbyaddr((char *) &cs.sin_addr, sizeof(cs.sin_addr),  
+         AF_INET)) == NULL) { 
+               i = (char *) &cs.sin_addr;
+               a1 = ((*i++)&0xff);
+               a2 = ((*i++)&0xff);
+               a3 = ((*i++)&0xff);
+               a4 = ((*i++)&0xff);
+               sprintf(tbuf,"%d.%d.%d.%d",a1,a2,a3,a4);
+               return;
+               }
+
+       strncpy(tbuf,ch->h_name, 24);
+       tbuf[24] = 0;
+       }
diff --git a/citadel/mailinglist.c b/citadel/mailinglist.c
new file mode 100644 (file)
index 0000000..be2b04f
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * mailinglist.c
+ *
+ * This program is designed to be a filter which allows two-way interaction
+ * between a traditional e-mail mailing list and a Citadel room.
+ *
+ * It only handles the outbound side.  The inbound side is handled by the
+ * Citadel e-mail gateway.  You should subscribe rooms to lists using the
+ * "room_roomname@node.dom" type address.
+ * 
+ * Since some listprocs only accept postings from subscribed addresses, the
+ * messages which this program converts will all appear to be from the room
+ * address; however, the full name of the sender is set to the Citadel user
+ * name of the real message author.
+ * 
+ * To prevent loops, this program only sends messages originating on the local
+ * system.  Therefore it is not possible to carry a two-way mailing list room
+ * on a Citadel network.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <syslog.h>
+#include "citadel.h"
+
+void LoadInternetConfig();
+void get_config();
+struct config config;
+
+char ALIASES[128];
+char CIT86NET[128];
+char SENDMAIL[128];
+char FALLBACK[128];
+char GW_DOMAIN[128];
+char TABLEFILE[128];
+char OUTGOING_FQDN[128];
+int RUN_NETPROC = 1;
+
+int struncmp(lstr,rstr,len)
+char lstr[],rstr[];
+int len; {
+        int pos = 0;
+        char lc,rc;
+        while (pos<len) {
+                lc=tolower(lstr[pos]);
+                rc=tolower(rstr[pos]);
+                if ((lc==0)&&(rc==0)) return(0);
+                if (lc<rc) return(-1);
+                if (lc>rc) return(1);
+                pos=pos+1;
+                }
+        return(0);
+        }
+
+/* 
+ * Consult the mailinglists table to find out where we should send the
+ * mailing list postings to.
+ */
+void xref(room,listaddr)               /* xref table */
+char room[]; 
+char listaddr[]; {
+       FILE *fp;
+       int a;
+       char buf[128];
+       
+       strcpy(listaddr, "");
+       fp=fopen(TABLEFILE, "r");
+       while (fgets(buf,128,fp)!=NULL) {
+               buf[strlen(buf)-1]=0;
+               for (a=0; a<strlen(buf); ++a) {
+                       if ( (buf[a]==',') && (!strucmp(&buf[a+1],room)) ) {
+                               strcpy(listaddr,buf);
+                               listaddr[a] = 0;
+                               }
+                       }
+               }
+       fclose(fp);
+       return;
+       }
+
+
+/*
+ * The main loop.  We don't need any command-line parameters to this program.
+ */
+void main() {
+
+       char header[3];
+       char fields[32][1024];
+       int num_fields;
+       int ch, p, a;
+       int in_header;
+       int is_good;
+       char listaddr[512];
+       char mailcmd[256];
+       FILE *nm;
+       char tempfile[64];
+
+       get_config();
+       LoadInternetConfig();
+       sprintf(tempfile, "/tmp/mlist.%d", getpid() );
+
+       while(1) {
+
+               /* seek to the next message */
+               is_good = 0;
+               do {
+                       if (feof(stdin)) {
+                               unlink(tempfile);
+                               exit(0);
+                               }
+                       } while (getc(stdin) != 255);
+
+               header[0] = 255;
+               header[1] = getc(stdin);
+               header[2] = getc(stdin);
+               in_header = 1;
+               num_fields = 0;
+
+               do {
+                       fields[num_fields][0] = getc(stdin);
+                       if (fields[num_fields][0] != 'M') {
+                               p = 1;
+                               do {
+                                       ch = getc(stdin);
+                                       fields[num_fields][p++] = ch;
+                                       } while ((ch!=0) && (!feof(stdin)));
+                               if (fields[num_fields][0] == 'N') {
+                                       if (!strcmp(&fields[num_fields][1],
+                                               NODENAME)) {
+                                                       is_good = 1;
+                                                       }
+                                       }
+                               if (fields[num_fields][0] == 'O') {
+                                       xref(&fields[num_fields][1], listaddr);
+                                       }
+                               if (fields[num_fields][0]!='R') ++num_fields;
+                               }
+                       else {
+                               /* flush the message out to the next program */
+
+                               nm =  fopen(tempfile, "wb");
+                               fprintf(nm, "%c%c%c",
+                                       header[0], header[1], header[2]);
+                               for (a=0; a<num_fields; ++a) {
+                                       fprintf(nm, "%s%c", &fields[a][0], 0);
+                                       }
+                               fprintf(nm, "R%s%c", listaddr, 0);
+                       
+                               putc('M', nm);
+                               do {
+                                       ch = getc(stdin);
+                                       putc(ch, nm);
+                                       } while ((ch!=0) && (!feof(stdin)));
+                               in_header = 0;
+                               fclose(nm);
+                               if (is_good) {
+                                       sprintf(mailcmd, "exec netmailer %s mlist", tempfile);
+                                       system(mailcmd);
+                                       is_good = 0;
+                                       }
+                               }
+                       } while (in_header);
+               }
+       }
diff --git a/citadel/mailinglists.txt b/citadel/mailinglists.txt
new file mode 100644 (file)
index 0000000..9b5e5a9
--- /dev/null
@@ -0,0 +1,96 @@
+                BIDIRECTIONAL MAILING LIST GATEWAY INSTRUCTIONS
+   Citadel/UX now has the ability to host a room on the BBS as a
+bidirectional gateway to an Internet mailing list.  This makes it convenient
+for the entire BBS community to have reading and posting access to a mailing
+list without each member having to subscribe to the list.  This document
+describes the procedure for setting up such functionality.
+   Here's the prerequisite: you must be using Citadel as your system's local
+mail delivery agent.  Please refer to network.doc for information on how to
+do this.  Before attempting a mailing list, make sure that you can send and
+receive regular Internet e-mail from your Citadel system.
+   As you may or may not know, once Citadel is your e-mail system, each room
+on the system has an e-mail address that may be used to post to the room
+from anywhere on the Internet.  That address is of the form
+"room_roomname@yourhost.dom", where "roomname" is the name of the room
+(spaces replaced by underscores) and "yourhost.dom" is your fully-qualified
+domain name.
+   The first step is to create a room for the list and then subscribe that
+room to the list.  The procedure for subscribing to a list depends on what
+type of software the list server is running, and is beyond the scope of this
+document.  For the purpose of example, let us suppose that you wish to
+subscribe to the "Broccoli Advocates" mailing list, and you've discovered
+that the list administrator is at broccoli-request@stalks.com and messages
+to be posted to the list should be mailed to broccoli@stalks.com.  Let us
+further suppose that your BBS is at mybbs.org.
+   So you create a room called "Broccoli Advocates".  Then, because you're a
+conscientous system administrator, you give the room an Info file which
+warns users that all messages posted to the room will be posted to the list.
+   You then send e-mail to the list administrator at
+broccoli-request@stalks.com requesting that the address
+"room_broccoli_advocates@mybbs.org" be added to the list.  Once the
+administrator sets this up, the receiving end of the list is set up.  Note
+that if a receive-only setup is all you want, you can stop here and you're
+done.
+   Now you have to set things up for sending.   The first thing you have to
+do is set up the file "mailinglists" which resides in the "network"
+subdirectory, which holds a table of list addresses associated with each
+room.  Each line is of the form
+ list-address,Room Name
+ ...one room per line.  You'll use this same file to set up all mailing list
+references.  So for our example, the line
+ broccoli@stalks.com,Broccoli Advocates
+ should be added.  In other words, messages originating from the "Broccoli
+Advocates" room will be mailed to broccoli@stalks.com.
+ The next thing to do is create a file in the "network/systems" subdirectory
+which spools out new messages.  Create a file "network/systems/mailinglists"
+which looks like this:
+ mailinglist <%s
+ Broccoli Advocates
+ 0
+ Note that one systems entry may be used for all mailing lists to which you
+subscribe.  Just keep adding any rooms (refer to network.doc for the
+procedure) that are mapped to mailing lists.
+ Now you're done!  Just do a "netproc mailinglists" or "netproc all" on a
+regular basis (probably using cron or a similar scheduling utility) to batch
+up new messages and send them out on a regular basis.
+  
+ NOTES ABOUT MAILING LISTS:
+   Since many listservers will only accept postings from e-mail addresses
+which are subscribed to the list, the mailing list gateway software sets the
+sending address of each message to the address of the room.  The "full name"
+portion of the address will remain intact.  So the posted messages will have
+headers which look like this:
+ From: room_broccoli_advocates@mybbs.org (Joe User)
+ To: broccoli@stalks.org
+   This ensures that messages are properly posted, but it makes it difficult
+for other members of the list to learn the correct e-mail addresses of the
+users on your system.  However, since there's so much spam around these
+days, that's probably just as well anyway.
+   To prevent loops, the mailing list gateway software only spools out
+messages which originated on _your_ system.  This implies that it is not
+possible to carry a gatewayed room on any type of Citadel network.
+   Well, that's just about it.  If you have any comments, suggestions, or
+questions, please visit UNCENSORED! BBS, either on the Internet at
+uncnsrd.mt-kisco.ny.us or via dialup at 914-244-3252.
diff --git a/citadel/messages.c b/citadel/messages.c
new file mode 100644 (file)
index 0000000..051f218
--- /dev/null
@@ -0,0 +1,1062 @@
+/*
+ * Citadel/UX message support routines
+ * see copyright.doc for copyright information
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include "citadel.h"
+
+#define MAXWORDBUF 256
+#define MAXMSGS 512
+
+struct cittext {
+       struct cittext *next;
+       char text[MAXWORDBUF];
+       };
+
+long finduser();
+char inkey();
+void sttybbs();
+int struncmp();
+int fmout();
+int haschar();
+int checkpagin();
+void getline();
+void formout();
+int yesno();
+void newprompt();
+int file_checksum();
+void color();
+void do_edit();
+
+char reply_to[512];
+long msg_arr[MAXMSGS];
+int num_msgs;
+extern char room_name[];
+extern unsigned room_flags;
+extern long highest_msg_read;
+extern struct serv_info serv_info;
+extern char temp[];
+extern char temp2[];
+extern int screenwidth;
+extern int screenheight;
+extern long maxmsgnum;
+extern char is_mail;
+extern char is_aide;
+extern char is_room_aide;
+extern char fullname[];
+extern char axlevel;
+extern unsigned userflags;
+extern char sigcaught;
+extern char editor_path[];
+extern char printcmd[];
+
+extern int editor_pid;
+
+int lines_printed;
+
+void ka_sigcatch(void) {
+       char buf[256];
+       alarm(S_KEEPALIVE);
+       signal(SIGALRM, (void *)ka_sigcatch);
+       serv_puts("NOOP");
+       serv_gets(buf);
+       }
+
+
+/*
+ * server keep-alive version of wait() (needed for external editor)
+ */
+pid_t ka_wait(pid_t *kstatus)
+{
+       pid_t p;
+
+       alarm(S_KEEPALIVE);
+       signal(SIGALRM, (void *)ka_sigcatch);
+       do {
+               errno = 0;
+               p = wait(kstatus);
+               } while (errno==EINTR);
+       signal(SIGALRM,SIG_IGN);
+       alarm(0);
+       return(p);
+       }
+
+
+/*
+ * version of system() that uses ka_wait()
+ */
+int ka_system(char *shc)
+{
+       pid_t childpid;
+       pid_t waitpid;
+       int retcode;
+
+       childpid = fork();
+       if (childpid < 0) {
+               color(1);
+               perror("Cannot fork");
+               color(7);
+               return((pid_t)childpid);
+               }
+
+       if (childpid == 0) {
+               execlp("/bin/sh","sh","-c",shc,NULL);
+               exit(127);
+               }
+
+       if (childpid > 0) {
+               do {
+                       waitpid = ka_wait(&retcode);
+                       } while (waitpid != childpid);
+               return(retcode);
+               }
+
+       return(-1);
+       }
+
+
+
+/*
+ * add a newline to the buffer...
+ */
+void add_newline(struct cittext *textlist)
+{
+       struct cittext *ptr;
+
+       ptr=textlist;
+       while (ptr->next != NULL) ptr = ptr->next;
+
+       while (ptr->text[strlen(ptr->text)-1]==32)
+               ptr->text[strlen(ptr->text)-1] = 0;
+       /* strcat(ptr->text,"\n"); */
+
+       ptr->next = (struct cittext *)
+               malloc(sizeof(struct cittext));
+       ptr = ptr->next;
+       ptr->next = NULL;
+       strcpy(ptr->text,"");
+       }
+
+
+/*
+ * add a word to the buffer...
+ */
+void add_word(struct cittext *textlist, char *wordbuf)
+{
+       struct cittext *ptr;
+
+
+       ptr=textlist;
+       while (ptr->next != NULL) ptr = ptr->next;
+       
+       if (3+strlen(ptr->text)+strlen(wordbuf) > screenwidth) {
+               ptr->next = (struct cittext *)
+                       malloc(sizeof(struct cittext));
+               ptr = ptr->next;
+               ptr->next = NULL;
+               strcpy(ptr->text,"");
+               }
+       
+       strcat(ptr->text,wordbuf);
+       strcat(ptr->text," ");
+       }
+
+
+/*
+ * begin editing of an opened file pointed to by fp, beginning at position pos.
+ */
+void citedit(FILE *fp, long int base_pos)
+{
+       int a,prev,finished,b,last_space;
+       int appending = 0;
+       struct cittext *textlist = NULL;
+       struct cittext *ptr;
+       char wordbuf[MAXWORDBUF];
+       char buf[256];
+       long last_server_msg,now;
+
+       /*
+        * we're going to keep track of the last time we talked to
+        * the server, so we can periodically send keep-alive messages
+        * out so it doesn't time out.
+        */
+       time(&last_server_msg);
+
+       /* first, load the text into the buffer */
+       fseek(fp,base_pos,0);
+       textlist = (struct cittext *)malloc(sizeof(struct cittext));
+       textlist->next = NULL;
+       strcpy(textlist->text,"");
+
+       strcpy(wordbuf,"");
+       prev = (-1);
+       while (a=getc(fp), a>=0) {
+               appending = 1;
+               if ((a==32)||(a==9)||(a==13)||(a==10)) {
+                       add_word(textlist,wordbuf);
+                       strcpy(wordbuf,"");
+                       if ((prev==13)||(prev==10)) {
+                               add_word(textlist,"\n");
+                               add_newline(textlist);
+                               add_word(textlist,"");
+                               }
+                       }
+               else {
+                       wordbuf[strlen(wordbuf)+1] = 0;
+                       wordbuf[strlen(wordbuf)] = a;
+                       }
+               if (strlen(wordbuf)+3 > screenwidth) {
+                       add_word(textlist,wordbuf);
+                       strcpy(wordbuf,"");
+                       }
+               prev = a;
+               }
+
+       /* get text */
+       finished = 0;
+       prev = (appending ? 13 : (-1));
+       strcpy(wordbuf,"");
+       do {
+               a=inkey();
+               if (a==10) a=13;
+               if (a==9) a=32;
+               if (a==127) a=8;
+               if ((a==32)&&(prev==13)) {
+                       add_word(textlist,"\n");
+                       add_newline(textlist);
+                       }
+               if (a==8) {
+                       if (strlen(wordbuf)>0) {
+                               wordbuf[strlen(wordbuf)-1] = 0;
+                               putc(8,stdout);
+                               putc(32,stdout);
+                               putc(8,stdout);
+                               }
+                       }
+               else if (a==13) {
+                       printf("\n");
+                       if (strlen(wordbuf)==0) finished = 1;
+                       else {
+                               for (b=0; b<strlen(wordbuf); ++b)
+                                  if (wordbuf[b]==32) {
+                                       wordbuf[b]=0;
+                                       add_word(textlist,wordbuf);
+                                       strcpy(wordbuf,&wordbuf[b+1]);
+                                       b=0;
+                                       }
+                               add_word(textlist,wordbuf);
+                               strcpy(wordbuf,"");
+                               }
+                       }
+               else {
+                       putc(a,stdout);
+                       wordbuf[strlen(wordbuf)+1] = 0;
+                       wordbuf[strlen(wordbuf)] = a;
+                       }
+               if ((strlen(wordbuf)+3) > screenwidth) {
+                       last_space = (-1);
+                       for (b=0; b<strlen(wordbuf); ++b)
+                               if (wordbuf[b]==32) last_space = b;
+                       if (last_space>=0) {
+                               for (b=0; b<strlen(wordbuf); ++b)
+                                  if (wordbuf[b]==32) {
+                                       wordbuf[b]=0;
+                                       add_word(textlist,wordbuf);
+                                       strcpy(wordbuf,&wordbuf[b+1]);
+                                       b=0;
+                                       }
+                               for (b=0; b<strlen(wordbuf); ++b) {
+                                       putc(8,stdout);
+                                       putc(32,stdout);
+                                       putc(8,stdout);
+                                       }
+                               printf("\n%s",wordbuf);
+                               }
+                       else {
+                               add_word(textlist,wordbuf);
+                               strcpy(wordbuf,"");
+                               printf("\n");
+                               }
+                       }
+               prev = a;
+
+               /* this check implements the server keep-alive */
+               time(&now);
+               if ( (now - last_server_msg) > S_KEEPALIVE ) {
+                       serv_puts("NOOP");
+                       serv_gets(buf);
+                       last_server_msg = now;
+                       }
+
+               } while (finished==0);
+
+       /* write the buffer back to disk */
+       fseek(fp,base_pos,0);
+       for (ptr=textlist; ptr!=NULL; ptr=ptr->next) {
+               fprintf(fp,"%s",ptr->text);
+               }
+       putc(10,fp);
+       putc(0,fp);
+
+       /* and deallocate the memory we used */
+       while (textlist!=NULL) {
+               ptr=textlist->next;
+               free(textlist);
+               textlist=ptr;
+               }
+       }
+
+
+int read_message(long int num, char pagin)     /* Read a message from the server */
+                               /* message number */
+               /* 0 = normal read, 1 = read with pagination, 2 = header */
+{
+       char buf[256];
+       char m_subject[256];
+       char from[256];
+       long now;
+       struct tm *tm;
+       int format_type = 0;
+       int fr = 0;
+       int nhdr = 0;
+
+       sigcaught = 0;
+       sttybbs(1);
+
+       sprintf(buf,"MSG0 %ld|%d",num,(pagin==READ_HEADER ? 1 : 0));
+       serv_puts(buf);
+       serv_gets(buf);
+       if (buf[0]!='1') {
+               printf("*** msg #%ld: %s\n",num,buf);
+               ++lines_printed;
+               lines_printed = checkpagin(lines_printed,pagin,screenheight);
+               sttybbs(0);
+               return(0);
+               }
+
+       strcpy(m_subject,"");
+       printf("\n");
+       ++lines_printed;
+       lines_printed = checkpagin(lines_printed,pagin,screenheight);
+       printf(" ");
+       if (pagin == 1) color(3);
+
+       if (pagin==2) {
+               while(serv_gets(buf), strcmp(buf,"000")) {
+                       if (buf[4]=='=') {
+                               printf("%s\n",buf);
+                               ++lines_printed;
+                               lines_printed = 
+                                       checkpagin(lines_printed,
+                                               pagin,screenheight);
+                               }
+                       }
+               sttybbs(0);
+               return(0);
+               }
+
+       strcpy(reply_to,"nobody...xxxxx");
+       while(serv_gets(buf), struncmp(buf,"text",4)) {
+               if (!struncmp(buf,"nhdr=yes",8)) nhdr=1;
+               if (!struncmp(buf,"from=",5)) {
+                       strcpy(from,&buf[5]);
+                       }
+               if (nhdr==1) buf[0]='_';
+               if (!struncmp(buf,"type=",5))
+                       format_type=atoi(&buf[5]);
+               if (!struncmp(buf,"from=",5)) {
+                       printf("from %s ",&buf[5]);
+                       }
+               if (!struncmp(buf,"path=",5))
+                       strcpy(reply_to,&buf[5]);
+               if (!struncmp(buf,"subj=",5))
+                       strcpy(m_subject,&buf[5]);
+               if ((!struncmp(buf,"hnod=",5)) 
+                  && (strucmp(&buf[5],serv_info.serv_humannode)))
+                       printf("(%s) ",&buf[5]);
+               if ((!struncmp(buf,"room=",5))
+                  && (strucmp(&buf[5],room_name)))
+                       printf("in %s> ",&buf[5]);
+
+               if (!struncmp(buf,"node=",5)) {
+                       if ( (room_flags&QR_NETWORK)
+                          || ((strucmp(&buf[5],serv_info.serv_nodename)
+                          &&(strucmp(&buf[5],serv_info.serv_fqdn)))))
+                               {
+                               printf("@%s ",&buf[5]);
+                               }
+                       if ((!strucmp(&buf[5],serv_info.serv_nodename))
+                          ||(!strucmp(&buf[5],serv_info.serv_fqdn)))
+                               {
+                               strcpy(reply_to,from);
+                               }
+                       else if (haschar(&buf[5],'.')==0) {
+                               sprintf(reply_to,"%s @ %s",from,&buf[5]);
+                               }
+                       }
+
+               if (!struncmp(buf,"rcpt=",5))
+                       printf("to %s ",&buf[5]);
+               if (!struncmp(buf,"time=",5)) {
+                       now=atol(&buf[5]);
+                       tm=(struct tm *)localtime(&now);
+                       strcpy(buf,asctime(tm)); buf[strlen(buf)-1]=0;
+                       strcpy(&buf[16],&buf[19]);
+                       printf("%s ",&buf[4]);
+                       }
+               }
+
+       if (nhdr==1) {
+               if (!is_room_aide) {
+                       printf(" ****");
+                       }
+               else {
+                       printf(" %s",from);
+                       }
+               }
+       printf("\n");
+       if (pagin == 1 ) color(2);
+       ++lines_printed;
+       lines_printed = checkpagin(lines_printed,pagin,screenheight);
+
+       if (strlen(m_subject)>0) {
+               printf("Subject: %s\n",m_subject);
+               ++lines_printed;
+               lines_printed = checkpagin(lines_printed,pagin,screenheight);
+               }
+
+       if (format_type == 0) {
+               fr=fmout(screenwidth,NULL,
+                       ((pagin==1) ? 1 : 0),
+                       screenheight,(-1),1);
+               }
+       else {
+               while(serv_gets(buf), strcmp(buf,"000")) {
+                       if (sigcaught==0) {
+                               printf("%s\n",buf);
+                               lines_printed = lines_printed + 1 +
+                                       (strlen(buf)/screenwidth);
+                               lines_printed =
+                                       checkpagin(lines_printed,pagin,screenheight);
+                               }
+                       }
+               fr = sigcaught;
+               }
+       printf("\n");
+       ++lines_printed;
+       lines_printed = checkpagin(lines_printed,pagin,screenheight);
+
+       if (pagin == 1) color(7);
+       sttybbs(0);
+       return(fr);
+       }
+
+/*
+ * replace string function for the built-in editor
+ */
+void replace_string(char *filename, long int startpos)
+{
+       char buf[512];
+       char srch_str[128];
+       char rplc_str[128];
+       FILE *fp;
+       int a;
+       long rpos,wpos;
+       char *ptr;
+       int substitutions = 0;
+       long msglen = 0L;
+
+       printf("Enter text to be replaced:\n: ");
+       getline(srch_str,128);
+       if (strlen(srch_str)==0) return;
+       
+       printf("Enter text to replace it with:\n: ");
+       getline(rplc_str,128);
+
+       fp=fopen(filename,"r+");
+       if (fp==NULL) return;
+
+       wpos=startpos;
+       fseek(fp,startpos,0);
+       strcpy(buf,"");
+       while (a=getc(fp), a>0) {
+               ++msglen;
+               buf[strlen(buf)+1] = 0;
+               buf[strlen(buf)] = a;
+               if ( strlen(buf) >= strlen(srch_str) ) {
+                       ptr=&buf[strlen(buf)-strlen(srch_str)];
+                       if (!struncmp(ptr,srch_str,strlen(srch_str))) {
+                               strcpy(ptr,rplc_str);
+                               ++substitutions;
+                               }
+                       }
+               if (strlen(buf)>384) {
+                       rpos=ftell(fp);
+                       fseek(fp,wpos,0);
+                       fwrite((char *)buf,128,1,fp);
+                       strcpy(buf,&buf[128]);
+                       wpos = ftell(fp);
+                       fseek(fp,rpos,0);
+                       }
+               }
+       fseek(fp,wpos,0);
+       if (strlen(buf)>0) fwrite((char *)buf,strlen(buf),1,fp);
+       putc(0,fp);
+       fclose(fp);
+       printf("<R>eplace made %d substitution(s).\n\n",substitutions);
+       }
+
+
+int make_message(char *filename, char *recipient, int anon_type, int format_type, int mode)
+                       /* temporary file name */
+                       /* NULL if it's not mail */
+                       /* see MES_ types in header file */
+                
+         
+{ 
+       FILE *fp;
+       int a,b,e_ex_code;
+       long now,beg;
+       char datestr[64];
+       int cksum = 0;
+
+       if (mode==2) if (strlen(editor_path)==0) {
+               printf("*** No editor available, using built-in editor\n");
+               mode=0;
+               }
+
+       time(&now);
+       strcpy(datestr,asctime(localtime(&now)));
+       datestr[strlen(datestr)-1] = 0;
+
+       if (room_flags & QR_ANONONLY) {
+               printf(" ****");
+               }
+       else {
+               printf(" %s from %s",datestr,fullname);
+               if (strlen(recipient)>0) printf(" to %s",recipient);
+               }
+       printf("\n");
+
+       beg = 0L;
+
+       if (mode==1) printf("(Press ctrl-d when finished)\n");
+       if (mode==0) {
+               fp=fopen(filename,"r");
+               if (fp!=NULL) {
+                       fmout(screenwidth,fp,0,screenheight,0,0);
+                       beg = ftell(fp);
+                       fclose(fp);
+                       }
+               else {
+                       fp=fopen(filename,"w");
+                       fclose(fp);
+                       }
+               }
+
+ME1:   switch(mode) {
+
+          case 0:
+               fp=fopen(filename,"r+");
+               citedit(fp,beg);
+               fclose(fp);
+               goto MECR;
+               break;
+
+          case 1:
+               fp=fopen(filename,"w");
+               do {
+                       a=inkey(); if (a==255) a=32;
+                       if (a==13) a=10;
+                       if (a!=4) {
+                               putc(a,fp);
+                               putc(a,stdout);
+                               }
+                       if (a==10) putc(13,stdout);
+                       } while(a!=4);
+               fclose(fp);
+               break;
+
+          case 2:
+               e_ex_code = 1;  /* start with a failed exit code */
+               editor_pid=fork();
+               cksum = file_checksum(filename);
+               if (editor_pid==0) {
+                       chmod(filename,0600);
+                       sttybbs(SB_RESTORE);
+                       execlp(editor_path,editor_path,filename,NULL);
+                       exit(1);
+                       }
+               if (editor_pid>0) do {
+                       e_ex_code = 0;
+                       b=ka_wait(&e_ex_code);
+                       } while((b!=editor_pid)&&(b>=0));
+               editor_pid = (-1);
+               sttybbs(0);
+               break;
+               }
+
+MECR:  if (mode==2) {
+               if (file_checksum(filename) == cksum) {
+                       printf("*** Aborted message.\n");
+                       e_ex_code = 1;
+                       }
+               if (e_ex_code==0) goto MEFIN;
+               goto MEABT2;
+               }
+MECR1: printf("Entry cmd (? for options) -> ");
+MECR2: b=inkey();
+       if (b==NEXT_KEY) b='n';
+       if (b==STOP_KEY) b='s';
+       b=(b&127); b=tolower(b);
+       if (b=='?') {
+               printf("Help\n");
+               formout("saveopt");
+               goto MECR1;
+               }
+       if (b=='a') { printf("Abort\n");        goto MEABT;     }
+       if (b=='c') { printf("Continue\n");     goto ME1;       }
+       if (b=='s') { printf("Save message\n"); goto MEFIN;     } 
+       if (b=='p') {
+               printf("Print formatted\n");
+               printf(" %s from %s",datestr,fullname);
+               if (strlen(recipient)>0) printf(" to %s",recipient);
+               printf("\n");
+               fp=fopen(filename,"r");
+               if (fp!=NULL) {
+                       fmout(screenwidth,fp,
+                               ((userflags & US_PAGINATOR) ? 1 : 0),
+                               screenheight,0,0);
+                       beg = ftell(fp);
+                       fclose(fp);
+                       }
+               goto MECR;
+               }
+       if (b=='r') {
+               printf("Replace string\n");
+               replace_string(filename,0L);
+               goto MECR;
+               }
+       if (b=='h') {
+               printf("Hold message\n");
+               return(2);
+               }
+       goto MECR2;
+
+MEFIN: return(0);
+
+MEABT: printf("Are you sure? ");
+       if (yesno()==0) goto ME1;
+MEABT2:        unlink(filename);
+       return(2);
+       }
+
+/*
+ * transmit message text to the server
+ */
+void transmit_message(FILE *fp)
+{
+       char buf[256];
+       int ch,a;
+       
+       strcpy(buf,"");
+       while (ch=getc(fp), (ch>=0)) {
+               if (ch==10) {
+                       if (!strcmp(buf,"000")) strcpy(buf,">000");
+                       serv_puts(buf);
+                       strcpy(buf,"");
+                       }
+               else {
+                       a = strlen(buf);
+                       buf[a+1] = 0;
+                       buf[a] = ch;
+                       if ((ch==32)&&(strlen(buf)>200)) {
+                               buf[a]=0;
+                               if (!strcmp(buf,"000")) strcpy(buf,">000");
+                               serv_puts(buf);
+                               strcpy(buf,"");
+                               }
+                       if (strlen(buf)>250) {
+                               if (!strcmp(buf,"000")) strcpy(buf,">000");
+                               serv_puts(buf);
+                               strcpy(buf,"");
+                               }
+                       }
+               }
+       serv_puts(buf);
+       }
+
+
+
+/*
+ * entmsg()  -  edit and create a message
+ *              returns 0 if message was saved
+ */
+int entmsg(int is_reply, int c)
+                       /* nonzero if this was a <R>eply command */
+       {               /* */
+       char buf[300];
+       char cmd[256];
+       int a,b;
+       int need_recp = 0;
+       int mode;
+       long highmsg;
+       FILE *fp;
+
+       if (c>0) mode=1;
+       else mode=0;
+
+       sprintf(cmd,"ENT0 0||0|%d",mode);
+       serv_puts(cmd);
+       serv_gets(cmd);
+
+       if ((strncmp(cmd,"570",3)) && (strncmp(cmd,"200",3))) {
+               printf("%s\n",&cmd[4]);
+               return(1);
+               }
+       need_recp = 0;
+       if (!strncmp(cmd,"570",3)) need_recp = 1;
+
+       if ((userflags & US_EXPERT) == 0) formout("entermsg");
+               
+       strcpy(buf,"");
+       if (need_recp==1) {
+               if (axlevel>=2) {
+                       if (is_reply) {
+                               strcpy(buf,reply_to);
+                               }
+                       else {
+                               printf("Enter recipient: ");
+                               getline(buf,299);
+                               if (strlen(buf)==0) return(1);
+                               }
+                       }
+               else strcpy(buf,"sysop");
+               }
+
+       b=0;
+       if (room_flags&QR_ANON2) {
+               printf("Anonymous (Y/N)? ");
+               if (yesno()==1) b=1;
+               }
+
+/* if it's mail, we've got to check the validity of the recipient... */
+       if (strlen(buf)>0) {
+               sprintf(cmd,"ENT0 0|%s|%d|%d",buf,b,mode);
+               serv_puts(cmd);
+               serv_gets(cmd);
+               if (cmd[0]!='2') {
+                       printf("%s\n",&cmd[4]);
+                       return(1);
+                       }
+               }
+
+/* learn the number of the newest message in in the room, so we can tell
+ * upon saving whether someone else has posted too
+ */
+       num_msgs = 0;
+       serv_puts("MSGS LAST|1");
+       serv_gets(cmd);
+       if (cmd[0]!='1') {
+               printf("%s\n",&cmd[5]);
+               }
+       else {
+               while (serv_gets(cmd), strcmp(cmd,"000")) {
+                       msg_arr[num_msgs++] = atol(cmd);
+                       }
+               }
+
+/* now put together the message */
+       a=make_message(temp,buf,b,0,c);
+       if (a!=0)
+       {
+          return(2);
+       }
+
+/* and send it to the server */
+       sprintf(cmd,"ENT0 1|%s|%d|%d",buf,b,mode);
+       serv_puts(cmd);
+       serv_gets(cmd);
+       if (cmd[0]!='4') {
+               printf("%s\n",&cmd[4]);
+               return(1);
+               }
+       fp=fopen(temp,"r");
+       if (fp!=NULL) {
+               transmit_message(fp);
+               fclose(fp);
+               }
+       serv_puts("000");
+       unlink(temp);
+       
+       highmsg = msg_arr[num_msgs - 1];
+       num_msgs = 0;
+       serv_puts("MSGS NEW");
+       serv_gets(cmd);
+       if (cmd[0]!='1') {
+               printf("%s\n",&cmd[5]);
+               }
+       else {
+               while (serv_gets(cmd), strcmp(cmd,"000")) {
+                       msg_arr[num_msgs++] = atol(cmd);
+                       }
+               }
+
+       /* get new highest message number in room to set lrp for goto... */
+       maxmsgnum = msg_arr[num_msgs - 1];
+
+       /* now see if anyone else has posted in here */
+       b=(-1);
+       for (a=0; a<num_msgs; ++a) if (msg_arr[a]>highmsg) ++b;
+
+       /* in the Mail> room, this algorithm always counts one message
+        * higher than in public rooms, so we decrement it by one */
+       if (need_recp) --b;
+
+       if (b==1) printf(
+"*** 1 additional message has been entered in this room by another user.\n");
+       if (b>1) printf(
+"*** %d additional messages have been entered in this room by other users.\n",b);
+
+       return(0);
+       }
+
+void process_quote(void) {     /* do editing on quoted file */
+FILE *qfile,*tfile;
+char buf[128];
+int line,qstart,qend;
+
+       qfile = fopen(temp2,"r");
+       line = 0;
+       fgets(buf,128,qfile);
+       while (fgets(buf,128,qfile)!=NULL) {
+               printf("%2d %s",++line,buf);
+               }
+       printf("Begin quoting at [ 1] : ");
+       getline(buf,3);
+       qstart = (buf[0]==0) ? (1) : atoi(buf);
+       printf("  End quoting at [%d] : ",line);
+       getline(buf,3);
+       qend = (buf[0]==0) ? (line) : atoi(buf);
+       rewind(qfile);
+       line=0;
+       fgets(buf,128,qfile);
+       tfile=fopen(temp,"w");
+       while(fgets(buf,128,qfile)!=NULL) {
+               if ((++line>=qstart)&&(line<=qend)) fprintf(tfile," >%s",buf);
+               }
+       fprintf(tfile," \n");
+       fclose(qfile);
+       fclose(tfile);
+       unlink(temp2);
+       chmod(temp,0666);
+       }
+
+
+void readmsgs(int c, int rdir, int q)  /* read contents of a room */
+               /* 0=Read all  1=Read new  2=Read old 3=Read last q */
+               /* 1=Forward (-1)=Reverse */
+               /* Number of msgs to read (if c==3) */
+       {
+       int a,b,e,f,g,start;
+       int hold_sw = 0;
+       char arcflag = 0;
+       char quotflag = 0;
+       char prtfile[16];
+       char pagin;
+       char cmd[256];
+       char targ[20];
+
+       signal(SIGINT,SIG_IGN);
+       signal(SIGQUIT,SIG_IGN);
+
+       if (c<0) b=(MSGSPERRM-1);
+       else b=0;
+
+       sprintf(prtfile,"/tmp/CPrt%d",getpid());
+
+       num_msgs = 0;
+       strcpy(cmd,"MSGS ");
+       switch(c) {
+               case 0: strcat(cmd,"ALL");
+                       break;
+               case 1: strcat(cmd,"NEW");
+                       break;
+               case 2: strcat(cmd,"OLD");
+                       break;
+               case 3: sprintf(&cmd[strlen(cmd)], "LAST|%d", q);
+                       break;
+               }
+       serv_puts(cmd);
+       serv_gets(cmd);
+       if (cmd[0]!='1') {
+               printf("%s\n",&cmd[5]);
+               }
+       else {
+               while (serv_gets(cmd), strcmp(cmd,"000")) {
+                       msg_arr[num_msgs++] = atol(cmd);
+                       }
+               }
+
+       lines_printed = 0;
+
+       /* this loop cycles through each message... */
+       start = ( (rdir==1) ? 0 : (num_msgs-1) );
+       for (a=start; ((a<num_msgs)&&(a>=0)); a=a+rdir) {
+               while (msg_arr[a]==0L) {
+                       a=a+rdir; if ((a==MSGSPERRM)||(a==(-1))) return;
+                       }
+
+RAGAIN:                pagin=((arcflag==0)&&(quotflag==0)&&
+                       (userflags & US_PAGINATOR)) ? 1 : 0;
+
+       /* if we're doing a quote, set the screenwidth to 72 temporarily */
+               if (quotflag) {
+                       hold_sw = screenwidth;
+                       screenwidth = 72;
+                       }
+
+       /* now read the message... */
+               e=read_message(msg_arr[a],pagin);
+
+       /* ...and set the screenwidth back if we have to */
+               if (quotflag) {
+                       screenwidth = hold_sw;
+                       }
+RMSGREAD:      fflush(stdout);
+               highest_msg_read = msg_arr[a];
+               if (quotflag) {
+                       freopen("/dev/tty","r+",stdout);
+                       quotflag=0;
+                       process_quote();
+                       }
+               if (arcflag) {
+                       freopen("/dev/tty","r+",stdout);
+                       arcflag=0;
+                       f=fork();
+                       if (f==0) {
+                               freopen(prtfile,"r",stdin);
+                               sttybbs(SB_RESTORE);
+                               ka_system(printcmd);
+                               sttybbs(SB_NO_INTR);
+                               unlink(prtfile);
+                               exit(0);
+                               }
+                       if (f>0) do {
+                               g=wait(NULL);
+                               } while((g!=f)&&(g>=0));
+                       printf("Message printed.\n");
+                       }
+               if (e==3) return;
+               if ((userflags&US_NOPROMPT)||(e==2)) e='n';
+               else {
+                       printf("(%d) ",num_msgs-a-1);
+                       if (is_mail==1) printf("<R>eply ");
+                       if (strlen(printcmd)>0) printf("<P>rint ");
+               printf("<B>ack <A>gain <Q>uote <H>eader <N>ext <S>top -> ");
+                       do {
+                               lines_printed = 2;
+                               e=(inkey()&127); e=tolower(e);
+/* return key same as <N> */   if (e==13) e='n';
+/* del/move for aides only */  if (!is_room_aide) if ((e=='d')||(e=='m')) e=0;
+/* print only if available */  if ((e=='p')&&(strlen(printcmd)==0)) e=0;
+/* can't move from Mail> */    if ((e=='m')&&(is_mail==1)) e=0;
+/* can't reply in public rms */        if ((e=='r')&&(is_mail!=1)) e=0;
+                               } while((e!='a')&&(e!='n')&&(e!='s')
+                                       &&(e!='d')&&(e!='m')&&(e!='p')
+                                       &&(e!='q')&&(e!='b')&&(e!='h')
+                                       &&(e!='r'));
+                       switch(e) {
+                               case 's':       printf("Stop\r");       break;
+                               case 'a':       printf("Again\r");      break;
+                               case 'd':       printf("Delete\r");     break;
+                               case 'm':       printf("Move\r");       break;
+                               case 'n':       printf("Next\r");       break;
+                               case 'p':       printf("Print\r");      break;
+                               case 'q':       printf("Quote\r");      break;
+                               case 'b':       printf("Back\r");       break;
+                               case 'h':       printf("Header\r");     break;
+                               case 'r':       printf("Reply\r");      break;
+                               }
+                       if (userflags & US_DISAPPEAR)
+                               printf("%75s\r","");
+                       else
+                               printf("\n");
+                       fflush(stdout);
+                       }
+               switch(e) {
+                  case 'p':    fflush(stdout);
+                               freopen(prtfile,"w",stdout);
+                               arcflag = 1;
+                               goto RAGAIN;
+                  case 'q':    fflush(stdout);
+                               freopen(temp2,"w",stdout);
+                               quotflag = 1;
+                               goto RAGAIN;
+                  case 's':    return;
+                  case 'a':    goto RAGAIN;
+                  case 'b':    a=a-(rdir*2);
+                               break;
+                  case 'm':    newprompt("Enter target room: ",targ,19);
+                               if (strlen(targ)>0) {
+                                       sprintf(cmd,"MOVE %ld|%s",
+                                               msg_arr[a],targ);
+                                       serv_puts(cmd);
+                                       serv_gets(cmd);
+                                       printf("%s\n",&cmd[4]);
+                                       if (cmd[0]=='2') msg_arr[a]=0L;
+                                       }
+                               else {
+                                       goto RMSGREAD;
+                                       }
+                               if (cmd[0]!='2') goto RMSGREAD;
+                               break;
+                  case 'd':    printf("*** Delete this message? ");
+                               if (yesno()==1) {
+                                       sprintf(cmd,"DELE %ld",msg_arr[a]);
+                                       serv_puts(cmd);
+                                       serv_gets(cmd);
+                                       printf("%s\n",&cmd[4]);
+                                       if (cmd[0]=='2') msg_arr[a]=0L;
+                                       }
+                               else {
+                                       goto RMSGREAD;
+                                       }
+                               break;
+                  case 'h':    read_message(msg_arr[a],READ_HEADER);
+                               goto RMSGREAD;
+                  case 'r':    entmsg(1,(DEFAULT_ENTRY==46 ? 2 : 0));
+                               goto RMSGREAD;
+                       }
+               } /* end for loop */
+       } /* end read routine */
+
+
+
+
+/*
+ * View and edit a system message
+ */
+void edit_system_message(char *which_message)
+{
+       char desc[64];
+       char read_cmd[64];
+       char write_cmd[64];
+
+       sprintf(desc, "system message '%s'", which_message);
+       sprintf(read_cmd, "MESG %s", which_message);
+       sprintf(write_cmd, "EMSG %s", which_message);
+       do_edit(desc, read_cmd, "NOOP", write_cmd);
+       }
diff --git a/citadel/messages/aideopt b/citadel/messages/aideopt
new file mode 100644 (file)
index 0000000..b006420
--- /dev/null
@@ -0,0 +1,12 @@
+One of:
+ <E>dit room
+ <F>ile <D>elete
+ <F>ile <S>end over net
+ <F>ile <M>ove
+ edit <I>nfo file
+ <K>ill room
+ <R>oom <I>nvite user
+ <R>oom <K>ick out user
+ <U>ser edit
+ <V>alidate new users
+ <W>ho knows room
diff --git a/citadel/messages/changepw b/citadel/messages/changepw
new file mode 100644 (file)
index 0000000..e572647
--- /dev/null
@@ -0,0 +1,5 @@
+   You should choose a password that no one will ever be able to guess,
+so no one can abuse your account. In addition, if you even suspect that
+someone knows your password, change it immediately. Your password will
+not be echoed to the screen when you type it, and unless you tell
+someone, no one can find out what your password is.
diff --git a/citadel/messages/dotopt b/citadel/messages/dotopt
new file mode 100644 (file)
index 0000000..fc6a52e
--- /dev/null
@@ -0,0 +1,10 @@
+One of:
+ <E>nter
+ <R>ead
+ <A>ide options (aides only)
+ <G>oto: (type room name)
+ <S>kip to: (type room name)
+ <H>elp: (type name of help file)
+ <Z>apped rooms list
+ <T>erminate
+
diff --git a/citadel/messages/entermsg b/citadel/messages/entermsg
new file mode 100644 (file)
index 0000000..c9473ab
--- /dev/null
@@ -0,0 +1 @@
+Entering message -- end by hitting return twice.
diff --git a/citadel/messages/entopt b/citadel/messages/entopt
new file mode 100644 (file)
index 0000000..1477e1b
--- /dev/null
@@ -0,0 +1,13 @@
+One of:
+ message using <A>scii
+ <B>io
+ <C>onfiguration
+ message with <E>ditor
+ re<G>istration
+ <M>essage
+ <P>assword
+ <R>oom
+ <T>extfile
+ file using <X>modem
+ file using <Y>modem
+ file using <Z>modem
diff --git a/citadel/messages/goodbye b/citadel/messages/goodbye
new file mode 100644 (file)
index 0000000..5b36793
--- /dev/null
@@ -0,0 +1,7 @@
+  < this logoff banner resides in ./messages/goodbye >
+ Thanks for calling ^humannode - please call again soon!
+ Also be sure to visit UNCENSORED! BBS at uncnsrd.mt-kisco.ny.us
+
diff --git a/citadel/messages/hello b/citadel/messages/hello
new file mode 100644 (file)
index 0000000..f2ccad3
--- /dev/null
@@ -0,0 +1,5 @@
+ < this logon banner resides in ./messages/hello >
+  Welcome to ^humannode !
diff --git a/citadel/messages/help b/citadel/messages/help
new file mode 100644 (file)
index 0000000..9ff26da
--- /dev/null
@@ -0,0 +1,31 @@
+                          ^variantname Help Menu
+  
+ ?         Help. (Typing a '?' will give you a menu almost anywhere)
+ A         Abandon this room where you stopped reading, goto next room.
+ C         Chat (multiuser chat, where available)
+ D         Prints directory, if there is one in the current room.
+ E         Enter a message.
+ F         Read all messages in the room, forward.
+ G         Goto next room which has UNREAD messages.
+ H         Help. Same as '?'
+ I         Reads the Information file for this room.
+ K         List of Known rooms.
+ L         Reads the last five messages in the room.
+ N         Reads all new messages in the room.
+ O         Reads all old messages, backwards.
+ P         Page another user (send an express message)
+ R         Reads all messages in the room, in reverse order.
+ S         Skips current room without making its messages old.
+ T         Terminate (logout)
+ U         Ungoto (returns to the last room you were in)
+ W         Displays who is currently logged in.
+ X         Toggle eXpert mode (menus and help blurbs on/off)
+ Z         Zap (forget) room. (Removes the room from your list)
+ *         Enter any locally installed 'doors'.
+   
+ In addition, there are dot commands. You hit the . (dot), then press the
+first letter of each word of the command. As you hit the letters, the words
+pop onto your screen. Exceptions: after you hit .Help or .Goto, the remainder
+of the command is a help file name or room name.
+    
+      *** USE  .<H>elp ?  or  .<H>elp SUMMARY  for additional help *** 
diff --git a/citadel/messages/mainmenu b/citadel/messages/mainmenu
new file mode 100644 (file)
index 0000000..03e7e22
--- /dev/null
@@ -0,0 +1,11 @@
+ -----------------------------------------------------------------------
+ Room cmds:    <K>nown rooms, <G>oto next room, <.G>oto a specific room,
+               <S>kip this room, <A>bandon this room, <Z>ap this room,
+               <U>ngoto (move back)
+ Message cmds: <N>ew msgs, <F>orward read, <R>everse read, <O>ld msgs,
+               <L>ast five msgs, <E>nter a message
+ General cmds: <?> help, <T>erminate, <C>hat, <W>ho is online
+ Misc:         <X> toggle eXpert mode, <D>irectory, <*> doorway
+
+  (Type .Help SUMMARY for extended commands, <X> to disable this menu)
+ -----------------------------------------------------------------------
diff --git a/citadel/messages/newuser b/citadel/messages/newuser
new file mode 100644 (file)
index 0000000..6dfecc8
--- /dev/null
@@ -0,0 +1,3 @@
+ < this new user policy resides in ./messages/newuser > 
diff --git a/citadel/messages/readopt b/citadel/messages/readopt
new file mode 100644 (file)
index 0000000..6a5a4ac
--- /dev/null
@@ -0,0 +1,14 @@
+One of:
+ <B>io
+ <D>irectory
+ <F>ile unformatted
+ <I>nfo file
+ <L>ast five messages
+ <N>ew messages
+ <O>ld messages
+ <R>everse
+ <T>extfile
+ <U>serlist
+ file using <X>modem
+ file using <Y>modem
+ file using <Z>modem
diff --git a/citadel/messages/register b/citadel/messages/register
new file mode 100644 (file)
index 0000000..3b70eed
--- /dev/null
@@ -0,0 +1,7 @@
+  Please enter the correct information here. In most cases we do not voice
+validate, but we must have this information in order to discourage vandalism.
+        If you skip this section, you will NOT get access!
+                          WE MEAN IT!
+    
diff --git a/citadel/messages/roomaccess b/citadel/messages/roomaccess
new file mode 100644 (file)
index 0000000..0b796a9
--- /dev/null
@@ -0,0 +1,17 @@
+Most rooms are public. Anyone on the system may get into a public room.
+Private rooms may take several forms:
+ Guess-name: to gain access to this type of room, a user need only know
+the room's name. No hints are given by the system.
+ Passworded: same as guess-name but the user must also know a password
+to get access.
+ Invitation-only: users may only gain access to this type of room if
+an aide gives it to them. 
+   Once a user has access to a private room, it shows up on the known
+rooms list and acts the same as any other room. Aides may kick users out
+of any type of private room, and out of excludable rooms.
+ Before making it private, think about it. Does it really NEED to be private?
diff --git a/citadel/messages/saveopt b/citadel/messages/saveopt
new file mode 100644 (file)
index 0000000..20c01bb
--- /dev/null
@@ -0,0 +1,7 @@
+One of:
+ <A>bort
+ <C>ontinue
+ <H>old this message
+ <P>rint formatted
+ <R>eplace string (edit)
+ <S>ave message
diff --git a/citadel/messages/unlisted b/citadel/messages/unlisted
new file mode 100644 (file)
index 0000000..2ce76e7
--- /dev/null
@@ -0,0 +1,3 @@
+The user list is provided as a service to the users of this system. Please
+don't choose to be unlisted unless you really find it it necessary. By the
+way, aides still see unlisted entries.
diff --git a/citadel/msgbase.c b/citadel/msgbase.c
new file mode 100644 (file)
index 0000000..7894541
--- /dev/null
@@ -0,0 +1,1249 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <time.h>
+#include <ctype.h>
+#include <string.h>
+#include <syslog.h>
+#include <pthread.h>
+#include "citadel.h"
+#include "server.h"
+#include <errno.h>
+#include "proto.h"
+
+#define MSGS_ALL       0
+#define MSGS_OLD       1
+#define MSGS_NEW       2
+#define MSGS_FIRST     3
+#define MSGS_LAST      4
+#define MSGS_GT                5
+
+extern struct config config;
+int twitroom=-1;
+
+
+/*
+ * Aliasing for network mail.
+ * (Error messages have been commented out, because this is a server.)
+ */
+int alias(char *name)          /* process alias and routing info for mail */
+             {
+       FILE *fp;
+       int a,b;
+       char aaa[300],bbb[300];
+       
+       fp=fopen("network/mail.aliases","r");
+       if (fp==NULL) fp=fopen("/dev/null","r");
+       if (fp==NULL) return(M_ERROR);
+GNA:   strcpy(aaa,""); strcpy(bbb,"");
+       do {
+               a=getc(fp);
+               if (a==',') a=0;
+               if (a>0) {
+                       b=strlen(aaa);
+                       aaa[b]=a;
+                       aaa[b+1]=0;
+                       }
+               } while(a>0);
+       do {
+               a=getc(fp);
+               if (a==10) a=0;
+               if (a>0) {
+                       b=strlen(bbb);
+                       bbb[b]=a;
+                       bbb[b+1]=0;
+                       }
+               } while(a>0);
+       if (a<0) {
+               fclose(fp);
+               goto DETYPE;
+               }
+       if (strucmp(name,aaa)) goto GNA;
+       fclose(fp);
+       strcpy(name,bbb);
+       /* cprintf("*** Mail is being forwarded to %s\n",name); */
+
+DETYPE:        /* determine local or remote type, see citadel.h */
+       for (a=0; a<strlen(name); ++a) if (name[a]=='!') return(M_INTERNET);
+       for (a=0; a<strlen(name); ++a)
+               if (name[a]=='@')
+                       for (b=a; b<strlen(name); ++b)
+                               if (name[b]=='.') return(M_INTERNET);
+       b=0; for (a=0; a<strlen(name); ++a) if (name[a]=='@') ++b;
+       if (b>1) {
+               /* cprintf("Too many @'s in address\n"); */
+               return(M_ERROR);
+               }
+       if (b==1) {
+               for (a=0; a<strlen(name); ++a)
+                       if (name[a]=='@') strcpy(bbb,&name[a+1]);
+               while (bbb[0]==32) strcpy(bbb,&bbb[1]);
+               fp = fopen("network/mail.sysinfo","r");
+               if (fp==NULL) return(M_ERROR);
+GETSN:         do {
+                       a=getstring(fp,aaa);
+                       } while ((a>=0)&&(strucmp(aaa,bbb)));
+               a=getstring(fp,aaa);
+               if (!strncmp(aaa,"use ",4)) {
+                       strcpy(bbb,&aaa[4]);
+                       fseek(fp,0L,0);
+                       goto GETSN;
+                       }
+               fclose(fp);
+               if (!strncmp(aaa,"uum",3)) {
+                       strcpy(bbb,name);
+                       for (a=0; a<strlen(bbb); ++a) {
+                               if (bbb[a]=='@') bbb[a]=0;
+                               if (bbb[a]==' ') bbb[a]='_';
+                               }
+                       while(bbb[strlen(bbb)-1]=='_') bbb[strlen(bbb)-1]=0;
+                       sprintf(name,&aaa[4],bbb);
+                       return(M_INTERNET);
+                       }
+               if (!strncmp(aaa,"bin",3)) {
+                       strcpy(aaa,name); strcpy(bbb,name);
+                       while (aaa[strlen(aaa)-1]!='@') aaa[strlen(aaa)-1]=0;
+                       aaa[strlen(aaa)-1]=0;
+                       while (aaa[strlen(aaa)-1]==' ') aaa[strlen(aaa)-1]=0;
+                       while (bbb[0]!='@') strcpy(bbb,&bbb[1]);
+                       strcpy(bbb,&bbb[1]);
+                       while (bbb[0]==' ') strcpy(bbb,&bbb[1]);
+                       sprintf(name,"%s @%s",aaa,bbb);
+                       return(M_BINARY);
+                       }
+               return(M_ERROR);
+               }
+       return(M_LOCAL);
+       }
+
+
+void get_mm(void) {
+       FILE *fp;
+
+       fp=fopen("citadel.control","r");
+       fread((char *)&CitControl,sizeof(struct CitControl),1,fp);
+       fclose(fp);
+       }
+
+/*
+ * cmd_msgs()  -  get list of message #'s in this room
+ */
+void cmd_msgs(char *cmdbuf)
+{
+       int a;
+       int mode;
+       char which[256];
+       int cm_howmany;
+       long cm_gt;
+
+       extract(which,cmdbuf,0);
+
+       mode = MSGS_ALL;
+       strcat(which,"   ");
+       if (!struncmp(which,"OLD",3))   mode = MSGS_OLD;
+       if (!struncmp(which,"NEW",3))   mode = MSGS_NEW;
+       if (!struncmp(which,"FIRST",5)) {
+               mode = MSGS_FIRST;
+               cm_howmany = extract_int(cmdbuf,1);
+               }
+       if (!struncmp(which,"LAST",4))  {
+               mode = MSGS_LAST;
+               cm_howmany = extract_int(cmdbuf,1);
+               }
+       if (!struncmp(which,"GT",2))    {
+               mode = MSGS_GT;
+               cm_gt = extract_long(cmdbuf,1);
+               }
+
+       if ((!(CC->logged_in))&&(!(CC->internal_pgm))) {
+               cprintf("%d not logged in\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+       if (CC->curr_rm < 0) {
+               cprintf("%d no room\n",ERROR);
+               return;
+               }
+       get_mm();
+       get_fullroom(&CC->fullroom,CC->curr_rm);
+       getuser(&CC->usersupp,CC->curr_user);
+       cprintf("%d messages...\n",LISTING_FOLLOWS);
+       for (a=0; a<MSGSPERRM; ++a) 
+              if ((CC->fullroom.FRnum[a] >=0)
+              && ( 
+
+(mode==MSGS_ALL)
+|| ((mode==MSGS_OLD) && (CC->fullroom.FRnum[a] <= CC->usersupp.lastseen[CC->curr_rm]))
+|| ((mode==MSGS_NEW) && (CC->fullroom.FRnum[a] > CC->usersupp.lastseen[CC->curr_rm]))
+|| ((mode==MSGS_NEW) && (CC->fullroom.FRnum[a] >= CC->usersupp.lastseen[CC->curr_rm])
+                    && (CC->usersupp.flags & US_LASTOLD))
+|| ((mode==MSGS_LAST)&& (a>=(MSGSPERRM-cm_howmany)))
+|| ((mode==MSGS_FIRST)&&(a<cm_howmany))
+|| ((mode==MSGS_GT) && (CC->fullroom.FRnum[a] > cm_gt))
+
+                       )
+               )
+               cprintf("%ld\n",CC->fullroom.FRnum[a]);
+       cprintf("000\n");
+       }
+
+
+
+/* 
+ * help_subst()  -  support routine for help file viewer
+ */
+void help_subst(char *strbuf, char *source, char *dest)
+{
+       char workbuf[256];
+       int p;
+
+       while (p=pattern2(strbuf,source), (p>=0)) {
+               strcpy(workbuf,&strbuf[p+strlen(source)]);
+               strcpy(&strbuf[p],dest);
+               strcat(strbuf,workbuf);
+               }
+       }
+
+
+void do_help_subst(char *buffer)
+{
+       char buf2[16];
+
+       help_subst(buffer,"^nodename",config.c_nodename);
+       help_subst(buffer,"^humannode",config.c_humannode);
+       help_subst(buffer,"^fqdn",config.c_fqdn);
+       help_subst(buffer,"^username",CC->usersupp.fullname);
+       sprintf(buf2,"%ld",CC->usersupp.usernum);
+       help_subst(buffer,"^usernum",buf2);
+       help_subst(buffer,"^sysadm",config.c_sysadm);
+       help_subst(buffer,"^variantname",CITADEL);
+       sprintf(buf2,"%d",config.c_maxsessions);
+       help_subst(buffer,"^maxsessions",buf2);
+       }
+
+
+
+/*
+ * memfmout()  -  Citadel text formatter and paginator.
+ *             Although the original purpose of this routine was to format
+ *             text to the reader's screen width, all we're really using it
+ *             for here is to format text out to 80 columns before sending it
+ *             to the client.  The client software may reformat it again.
+ */
+void memfmout(int width, char *mptr, char subst)
+                       /* screen width to use */
+                       /* where are we going to get our text from? */
+                       /* nonzero if we should use hypertext mode */
+       {
+       int a,b,c,real,old;
+       CIT_UBYTE ch;
+       char aaa[140];
+       char buffer[256];
+       
+       strcpy(aaa,""); old=255;
+       strcpy(buffer,"");
+       c=1; /* c is the current pos */
+
+FMTA:  if (subst) {
+               while (ch=*mptr, ((ch!=0) && (strlen(buffer)<126) )) {
+                       ch=*mptr++;
+                       buffer[strlen(buffer)+1] = 0;
+                       buffer[strlen(buffer)] = ch;
+                       }
+
+               if (buffer[0]=='^') do_help_subst(buffer);
+
+               buffer[strlen(buffer)+1] = 0;
+               a=buffer[0];
+               strcpy(buffer,&buffer[1]);
+               }
+       
+       else ch=*mptr++;
+
+       old=real;
+       real=ch;
+       if (ch<=0) goto FMTEND;
+       
+       if ( ((ch==13)||(ch==10)) && (old!=13) && (old!=10) ) ch=32;
+       if ( ((old==13)||(old==10)) && (isspace(real)) ) {
+               cprintf("\n");
+               c=1;
+               }
+       if (ch>126) goto FMTA;
+
+       if (ch>32) {
+       if ( ((strlen(aaa)+c)>(width-5)) && (strlen(aaa)>(width-5)) )
+               { cprintf("\n%s",aaa); c=strlen(aaa); aaa[0]=0;
+               }
+        b=strlen(aaa); aaa[b]=ch; aaa[b+1]=0; }
+       if (ch==32) {
+               if ((strlen(aaa)+c)>(width-5)) { 
+                       cprintf("\n");
+                       c=1;
+                       }
+               cprintf("%s ",aaa); ++c; c=c+strlen(aaa);
+               strcpy(aaa,"");
+               goto FMTA;
+               }
+       if ((ch==13)||(ch==10)) {
+               cprintf("%s\n",aaa);
+               c=1;
+               strcpy(aaa,"");
+               goto FMTA;
+               }
+       goto FMTA;
+
+FMTEND:        cprintf("\n");
+       }
+
+
+/*
+ * get a message off disk.
+ * 
+ */
+void output_message(char *msgid, int mode, int headers_only)
+{
+       long msg_num;
+       int a,och,len;
+       CIT_UBYTE ch, rch;
+       FILE *msg;
+       CIT_UBYTE format_type,anon_flag;
+       char buf[1024];
+       long msg_len;
+       int msg_ok = 0;
+
+       struct cdbdata *dmsgtext;
+       char *mptr;
+
+       /* buffers needed for RFC822 translation */
+       char suser[256];
+       char luser[256];
+       char snode[256];
+       char lnode[256];
+       char mid[256];
+       long xtime;
+       /* */
+
+       msg_num = atol(msgid);
+
+
+       if ((!(CC->logged_in))&&(!(CC->internal_pgm))) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+       if (CC->curr_rm < 0) {
+               cprintf("%d No room selected.\n",ERROR);
+               return;
+               }
+
+       /* We used to need to check in the current room's fullroom table
+        * to determine where the message's disk position.  We no longer need
+        * to do this, but we do it anyway as a security measure, in order to
+        * prevent rogue clients from reading messages not in the current room.
+        */
+       for (a=0; a<MSGSPERRM; ++a) if (CC->fullroom.FRnum[a] == msg_num) {
+               msg_ok = 1;
+               }
+       if (!msg_ok) {
+               cprintf("%d Message %ld is not in this room.\n",
+                       ERROR, msg_num);
+               return;
+               }
+       
+
+       dmsgtext = cdb_fetch(CDB_MSGMAIN, &msg_num, sizeof(long));
+       
+       if (dmsgtext == NULL) {
+               cprintf("%d Can't find message %ld\n", ERROR+INTERNAL_ERROR);
+               return;
+               }
+
+       msg_len = (long) dmsgtext->len;
+       mptr = dmsgtext->ptr;
+       lprintf(9, "Returned message length is %ld\n", msg_len);
+
+       /* this loop spews out the whole message if we're doing raw format */
+       if (mode == MT_RAW) {
+               cprintf("%d %ld\n", BINARY_FOLLOWS, msg_len);
+               client_write(dmsgtext->ptr, (int) msg_len);
+               cdb_free(dmsgtext);
+               return;
+               }
+
+       /* Otherwise, we'll start parsing it field by field... */
+       ch = *mptr++;
+       if (ch != 255) {
+               cprintf("%d Illegal message format on disk\n",
+                       ERROR+INTERNAL_ERROR);
+               cdb_free(dmsgtext);
+               return;
+               }
+
+       anon_flag = *mptr++;
+       format_type = *mptr++;
+
+       /* now for the user-mode message reading loops */
+       cprintf("%d Message %ld:\n",LISTING_FOLLOWS,msg_num);
+
+       if (mode == MT_CITADEL) cprintf("type=%d\n",format_type);
+
+       if ( (anon_flag == MES_ANON) && (mode == MT_CITADEL) ) {
+               cprintf("nhdr=yes\n");
+               }
+
+       /* begin header processing loop for Citadel message format */
+
+       if (mode == MT_CITADEL) while(ch = *mptr++, (ch!='M' && ch!=0)) {
+               buf[0] = 0;
+               do {
+                       buf[strlen(buf)+1] = 0;
+                       rch = *mptr++;
+                       buf[strlen(buf)] = rch;
+                       } while (rch > 0);
+
+               if (ch=='A') {
+                       if (anon_flag==MES_ANON) cprintf("from=****");
+                       else if (anon_flag==MES_AN2) cprintf("from=anonymous");
+                       else cprintf("from=%s",buf);
+                       if ((is_room_aide()) && ((anon_flag == MES_ANON)
+                          || (anon_flag == MES_AN2)))
+                               cprintf(" [%s]",buf);
+                       cprintf("\n");
+                       }
+               else if (ch=='P') cprintf("path=%s\n",buf);
+               else if (ch=='U') cprintf("subj=%s\n",buf);
+               else if (ch=='I') cprintf("msgn=%s\n",buf);
+               else if (ch=='H') cprintf("hnod=%s\n",buf);
+               else if (ch=='O') cprintf("room=%s\n",buf);
+               else if (ch=='N') cprintf("node=%s\n",buf);
+               else if (ch=='R') cprintf("rcpt=%s\n",buf);
+               else if (ch=='T') cprintf("time=%s\n",buf);
+               /* else cprintf("fld%c=%s\n",ch,buf); */
+               }
+
+       /* begin header processing loop for RFC822 transfer format */
+
+       strcpy(suser, "");
+       strcpy(luser, "");
+       strcpy(snode, NODENAME);
+       strcpy(lnode, HUMANNODE);
+       if (mode == MT_RFC822) while(ch = *mptr++, (ch!='M' && ch!=0)) {
+               buf[0] = 0;
+               do {
+                       buf[strlen(buf)+1] = 0;
+                       rch = *mptr++;
+                       buf[strlen(buf)] = rch;
+                       } while (rch > 0);
+
+               if (ch=='A') strcpy(luser, buf);
+               else if (ch=='P') {
+                       cprintf("Path: %s\n",buf);
+                       for (a=0; a<strlen(buf); ++a) {
+                               if (buf[a] == '!') {
+                                       strcpy(buf,&buf[a+1]);
+                                       a=0;
+                                       }
+                               }
+                       strcpy(suser, buf);
+                       }
+               else if (ch=='U') cprintf("Subject: %s\n",buf);
+               else if (ch=='I') strcpy(mid, buf);
+               else if (ch=='H') strcpy(lnode, buf);
+               else if (ch=='O') cprintf("X-Citadel-Room: %s\n",buf);
+               else if (ch=='N') strcpy(snode, buf);
+               else if (ch=='R') cprintf("To: %s\n",buf);
+               else if (ch=='T')  {
+                       xtime = atol(buf);
+                       cprintf("Date: %s", asctime(localtime(&xtime)));
+                       }
+               }
+
+       if (mode == MT_RFC822) {
+               if (!strucmp(snode, NODENAME)) {
+                       strcpy(snode, FQDN);
+                       }
+               cprintf("Message-ID: <%s@%s>\n", mid, snode);
+               cprintf("From: %s@%s (%s)\n",
+                       suser, snode, luser);
+               cprintf("Organization: %s\n", lnode);
+               }
+
+       /* end header processing loop ... at this point, we're in the text */
+
+       if (ch==0) {
+               cprintf("text\n*** ?Message truncated\n000\n");
+               fclose(msg);
+               cdb_free(dmsgtext);
+               return;
+               }
+
+       if (headers_only) {
+               /* give 'em a length */
+               msg_len = 0L;
+               while(och=ch, ch = *mptr++, ch>0) {
+                       ++msg_len;
+                       }
+               cprintf("mlen=%ld\n", msg_len);
+               cprintf("000\n");
+               fclose(msg);
+               cdb_free(dmsgtext);
+               return;
+               }
+
+       /* signify start of msg text */
+       if (mode == MT_CITADEL) cprintf("text\n");
+       if (mode == MT_RFC822) cprintf("\n");
+
+       /* If the format type on disk is 1 (fixed-format), then we want
+        * everything to be output completely literally ... regardless of
+        * what message transfer format is in use.
+        */
+       if (format_type == 1) {
+               och = 0;
+               len = 0;
+               while(och=ch, ch = *mptr++, ch>0) {
+                       if (ch == 13) ch = 10;
+                       ++len;
+                       if ((ch!=10)||(och!=10)) {
+                               cprintf("%c", ch);
+                               if (ch==10) len = 0;
+                               }
+                       if (len>=250) {
+                               len = 0;
+                               /* cprintf("%c", ch); */
+                               cprintf("%c", 10);
+                               }
+                       }
+               if (len!=0) cprintf("%c", 10);
+               }
+       /* If the message on disk is format 0 (Citadel vari-format), we
+        * output using the formatter at 80 columns.  This is the final output
+        * form if the transfer format is RFC822, but if the transfer format
+        * is Citadel proprietary, it'll still work, because the indentation
+        * for new paragraphs is correct and the client will reformat the
+        * message to the reader's screen width.
+        */
+       if (format_type == 0) {
+               memfmout(80,mptr,0);
+               }
+
+
+       /* now we're done */
+       cprintf("000\n");
+       cdb_free(dmsgtext);
+       }
+
+
+/*
+ * display a message (mode 0 - Citadel proprietary)
+ */
+void cmd_msg0(char *cmdbuf)
+{
+       char msgid[256];
+       int headers_only = 0;
+
+       extract(msgid,cmdbuf,0);
+       headers_only = extract_int(cmdbuf,1);
+
+       output_message(msgid,MT_CITADEL,headers_only);
+       }
+
+
+/*
+ * display a message (mode 2 - RFC822)
+ */
+void cmd_msg2(char *cmdbuf)
+{
+       char msgid[256];
+       int headers_only = 0;
+
+       extract(msgid,cmdbuf,0);
+       headers_only = extract_int(cmdbuf,1);
+
+       output_message(msgid,MT_RFC822,headers_only);
+       }
+
+/* 
+ * display a message (mode 3 - IGnet raw format - internal programs only)
+ */
+void cmd_msg3(char *cmdbuf)
+{
+       char msgid[256];
+       int headers_only = 0;
+
+       if (CC->internal_pgm == 0) {
+               cprintf("%d This command is for internal programs only.\n",
+                       ERROR);
+               return;
+               }
+
+       extract(msgid,cmdbuf,0);
+       headers_only = extract_int(cmdbuf,1);
+
+       output_message(msgid,MT_RAW,headers_only);
+       }
+
+
+
+/*
+ * message base operation to send a message to the master file
+ */
+void send_message(char *filename, struct smreturn *retbuf, int generate_id)
+                               /* tempfilename of proper message */
+                               /* return information */
+                               /* set to 1 to generate an 'I' field */
+{
+
+       int file,a;
+       FILE *fp;
+       long newmsgid;
+
+       char *message_in_memory;
+       size_t templen;
+
+       fp = fopen(filename, "rb");
+
+       /* Measure the message */
+       lprintf(9, "Measuring the message\n");
+       fseek(fp, 0L, SEEK_END);
+       templen = ftell(fp);
+
+       /* Now read it into memory */
+       lprintf(9, "Allocating %ld bytes\n", templen);
+       message_in_memory = (char *) malloc(templen);
+       if (message_in_memory == NULL) {
+               lprintf(2, "Can't allocate memory to save message!\n");
+               fclose(fp);     
+               retbuf->smnumber = 0L;
+               retbuf->smpos = 0L;
+               return;
+               }
+
+       lprintf(9, "Reading it into memory\n"); 
+       fseek(fp, 0L, SEEK_SET);
+       fread(message_in_memory, templen, 1, fp);
+       fclose(fp);
+
+       /* Get a new message number */
+       newmsgid = get_new_message_number();
+
+       /* Write our little bundle of joy into the message base */
+
+       lprintf(9, "Storing message %ld\n", newmsgid);
+       begin_critical_section(S_MSGMAIN);
+       if ( cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
+                       message_in_memory, templen) < 0 ) {
+               lprintf(2, "Can't store message\n");
+               retbuf->smnumber = 0L;
+               retbuf->smpos = 0L;
+               return;
+               }
+       end_critical_section(S_MSGMAIN);
+       free(message_in_memory);
+
+
+       /* Finally, return the pointers */
+       retbuf->smnumber = newmsgid;
+       retbuf->smpos= 1L; /* FIX we really won't be needing this */
+       }
+
+
+
+
+
+
+void loadtroom(void) {
+       struct quickroom qrbuf;
+       int a;
+       unsigned newflags;
+
+       /* first try to locate the twit room */
+       for (a=0; a<MAXROOMS; ++a) {
+               getroom(&qrbuf,a);
+               if (!strucmp(qrbuf.QRname,config.c_twitroom)) {
+                       twitroom = a;
+                       return;
+                       }
+               }
+
+       /* if not found, try to create it  -  put it in the last slot */
+       twitroom = get_free_room_slot(-1);
+       if (twitroom>=0) {
+               newflags = create_room(twitroom,config.c_twitroom,0,"",0);
+               return;
+               }
+
+       /* as a last resort, point to Aide> */
+       twitroom = 2;
+       }
+
+
+/*
+ * this is a simple file copy routine.
+ */
+void copy_file(char *from, char *to)
+{
+       FILE *ffp,*tfp;
+       int a;
+
+       ffp=fopen(from,"r");
+       if (ffp==NULL) return;
+       tfp=fopen(to,"w");
+       if (tfp==NULL) {
+               fclose(ffp);
+               return;
+               }
+       while (a=getc(ffp), a>=0) {
+               putc(a,tfp);
+               }
+       fclose(ffp);
+       fclose(tfp);
+       return;
+       }
+
+
+
+/*
+ * message base operation to save a message and install its pointers
+ */
+void save_message(char *mtmp,  /* file containing proper message */
+               char *rec,      /* Recipient (if mail) */
+               char mtsflag,   /* 0 for normal, 1 to force Aide> room */
+               int mailtype,   /* local or remote type, see citadel.h */
+               int generate_id) /* set to 1 to generate an 'I' field */
+{
+       int a,e;
+       struct usersupp tempUS;
+       char aaa[100];
+       struct smreturn smreturn;
+       int hold_rm;
+
+       send_message(mtmp,&smreturn,generate_id);
+       hold_rm=(-1);
+
+       /* if the user is a twit, move to the twit room for posting */
+       if (TWITDETECT) if (CC->usersupp.axlevel==2) {
+               if (twitroom<0) loadtroom();
+               hold_rm=CC->curr_rm;
+               CC->curr_rm=twitroom;
+               }
+
+       /* and if this message is destined for Aide> then go there */
+       if (mtsflag) {
+               hold_rm=CC->curr_rm;
+               CC->curr_rm=2;
+               }
+
+       /* this call to usergoto() changes rooms if necessary.  It also
+          causes the latest fullroom structure to be read into memory */
+       usergoto(CC->curr_rm,0);
+
+       /* Store the message pointer, but NOT for sent mail! */
+       if (CC->curr_rm != 1) {
+
+               /* read in the quickroom record, obtaining a lock... */
+               lgetroom(&CC->quickroom,CC->curr_rm);
+               get_fullroom(&CC->fullroom,CC->curr_rm);
+
+               /* Delete the oldest message if there is one */
+               if (CC->fullroom.FRnum[0] != 0L) {
+                       cdb_delete(CDB_MSGMAIN,
+                               &CC->fullroom.FRnum[0], sizeof(long));
+                       }
+
+               /* Now scroll... */
+               for (a=0; a<(MSGSPERRM-1); ++a) {
+                       CC->fullroom.FRnum[a]=CC->fullroom.FRnum[a+1];
+                       }
+       
+               /* ...and add the new message */
+               CC->fullroom.FRnum[MSGSPERRM-1]=smreturn.smnumber;
+       
+               /* Write it back to disk. */
+               put_fullroom(&CC->fullroom,CC->curr_rm);
+       
+               /* update quickroom */
+               CC->quickroom.QRhighest=CC->fullroom.FRnum[MSGSPERRM-1];
+               lputroom(&CC->quickroom,CC->curr_rm);
+               }
+
+       /* Bump this user's messages posted counter.  Also, if the user is a
+          twit, give them access to the twit room. */
+       lgetuser(&CC->usersupp,CC->curr_user);
+       CC->usersupp.posted = CC->usersupp.posted + 1;
+       if (CC->curr_rm==twitroom) CC->usersupp.generation[twitroom]=CC->quickroom.QRgen;
+       lputuser(&CC->usersupp,CC->curr_user);
+
+       /* if mail, there's still more to do, if not, skip it */
+       if ((CC->curr_rm!=1)||(mtsflag)) goto ENTFIN;
+
+       /* network mail - send a copy to the network program */
+       if (mailtype!=M_LOCAL) {
+               sprintf(aaa,"./network/spoolin/nm.%d",getpid());
+               copy_file(mtmp,aaa);
+               system("exec nohup ./netproc >/dev/null 2>&1 &");
+               }
+
+       /* local mail - put a copy in the recipient's mailbox */
+       if (mailtype==M_LOCAL) {
+               if (lgetuser(&tempUS,rec)==0) {
+                       if (tempUS.mailnum[0] != 0L) {
+                               cdb_delete(CDB_MSGMAIN, &tempUS.mailnum[0],
+                                       sizeof(long));
+                               }
+                       for (e=0; e<(MAILSLOTS-1); ++e) {
+                               tempUS.mailnum[e]=tempUS.mailnum[e+1];
+                               }
+                       tempUS.mailnum[MAILSLOTS-1]=smreturn.smnumber;
+                       lputuser(&tempUS,rec);
+                       }
+               }
+
+       /* if we've posted in a room other than the current room, then we
+          have to now go back to the current room... */
+ENTFIN:        if (hold_rm!=(-1)) {
+               usergoto(hold_rm,0);
+               }
+       unlink(mtmp);           /* delete the temporary file */
+       }
+
+
+/*
+ * generate an administrative message and post it in the Aide> room
+ */
+void aide_message(char *text)
+{
+       long now;
+       FILE *fp;
+
+       time(&now);
+       fp=fopen(CC->temp,"wb");
+       fprintf(fp,"%c%c%c",255,MES_NORMAL,0);
+       fprintf(fp,"Psysop%c",0);
+       fprintf(fp,"T%ld%c",now,0);
+       fprintf(fp,"ACitadel%c",0);
+       fprintf(fp,"OAide%c",0);
+       fprintf(fp,"N%s%c",NODENAME,0);
+       fprintf(fp,"M%s\n%c",text,0);
+       fclose(fp);
+       save_message(CC->temp,"",1,M_LOCAL,1);
+       syslog(LOG_NOTICE,text);
+       }
+
+
+
+/*
+ * build a binary message to be saved on disk
+ */
+void make_message(char *filename, struct usersupp *author, char *recipient, char *room, int type, int net_type, int format_type, char *fake_name)
+                       /* temporary file name */
+                          /* author's usersupp structure */
+                       /* NULL if it's not mail */
+                       /* room where it's going */
+                       /* see MES_ types in header file */
+                       /* local or remote type, see citadel.h */
+                       /* format type (see citadel.h) */
+{ 
+       FILE *fp;
+       int a;
+       long now;
+       char dest_node[32];
+       char buf[256];
+
+       /* don't confuse the poor folks if it's not routed mail. */
+       strcpy(dest_node,"");
+
+       /* if net_type is M_BINARY, split out the destination node */
+       if (net_type == M_BINARY) {
+               strcpy(dest_node,NODENAME);
+               for (a=0; a<strlen(recipient); ++a) {
+                       if (recipient[a]=='@') {
+                               recipient[a]=0;
+                               strcpy(dest_node,&recipient[a+1]);
+                               }
+                       }
+               }
+
+       /* if net_type is M_INTERNET, set the dest node to 'internet' */
+       if (net_type == M_INTERNET) {
+               strcpy(dest_node,"internet");
+               }
+
+       while (isspace(recipient[strlen(recipient)-1]))
+               recipient[strlen(recipient)-1] = 0;
+
+       time(&now);
+       fp=fopen(filename,"w");
+       putc(255,fp);
+       putc(type,fp);  /* Normal or anonymous, see MES_ flags */
+       putc(format_type,fp);   /* Formatted or unformatted */
+       fprintf(fp,"Pcit%ld%c",author->usernum,0);      /* path */
+       fprintf(fp,"T%ld%c",now,0);                     /* date/time */
+       if (fake_name[0])
+          fprintf(fp,"A%s%c",fake_name,0);
+       else
+          fprintf(fp,"A%s%c",author->fullname,0);      /* author */
+       fprintf(fp,"O%s%c",CC->quickroom.QRname,0);     /* room */
+       fprintf(fp,"N%s%c",NODENAME,0);                 /* nodename */
+       fprintf(fp,"H%s%c",HUMANNODE,0);                /* human nodename */
+
+       if (recipient[0]!=0) fprintf(fp,"R%s%c",recipient,0);
+       if (dest_node[0]!=0) fprintf(fp,"D%s%c",dest_node,0);
+
+       putc('M',fp);
+
+       while (client_gets(buf), strcmp(buf,"000"))
+       {
+          fprintf(fp,"%s\n",buf);
+        }
+        syslog(LOG_INFO, "Closing message");
+       putc(0,fp);
+       fclose(fp);
+       }
+
+
+
+
+
+/*
+ * message entry  -  mode 0 (normal) <bc>
+ */
+void cmd_ent0(char *entargs)
+{
+       int post = 0;
+       char recipient[256];
+       int anon_flag = 0;
+       int format_type = 0;
+       char newusername[256];          /* <bc> */
+
+       int a,b,e;
+       int mtsflag = 0;
+       struct usersupp tempUS;
+       char buf[256];
+
+       post = extract_int(entargs,0);
+       extract(recipient,entargs,1);
+       anon_flag = extract_int(entargs,2);
+       format_type = extract_int(entargs,3);
+
+       /* first check to make sure the request is valid. */
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+       if (CC->curr_rm < 0) {
+               cprintf("%d No room selected.\n",ERROR);
+               return;
+               }
+       if ((CC->usersupp.axlevel<2)&&(CC->curr_rm!=1)) {
+               cprintf("%d Need to be validated to enter (except in Mail> to sysop)\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+       if ((CC->usersupp.axlevel<4)&&(CC->quickroom.QRflags&QR_NETWORK)) {
+               cprintf("%d Need net privileges to enter here.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+       if ((CC->usersupp.axlevel<6)&&(CC->quickroom.QRflags&QR_READONLY)) {
+               cprintf("%d Sorry, this is a read-only room.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       mtsflag=0;
+       
+               
+        if (post==2) {                 /* <bc> */
+           if (CC->usersupp.axlevel<6)
+           {
+              cprintf("%d\nYou don't have sufficient permission to do an aide post.\n", ERROR+HIGHER_ACCESS_REQUIRED);
+              return;
+           }
+          extract(newusername,entargs,4);
+          bzero(CC->fake_postname, 32);
+           strcpy(CC->fake_postname, newusername);
+           cprintf("%d Ok\n",OK);
+           return;
+        }
+        
+       CC->cs_flags |= CS_POSTING;
+       
+       buf[0]=0;
+       if (CC->curr_rm==1) {
+               if (CC->usersupp.axlevel>=2) {
+                       strcpy(buf,recipient);
+                       }
+               else strcpy(buf,"sysop");
+               lprintf(9, "aliasing...\n");
+               e=alias(buf);                   /* alias and mail type */
+               lprintf(9,"...type is %d\n", e);
+               if ((buf[0]==0) || (e==M_ERROR)) {
+                       cprintf("%d Unknown address - cannot send message.\n",
+                               ERROR+NO_SUCH_USER);
+                       return;
+                       }
+               if ((e!=M_LOCAL)&&(CC->usersupp.axlevel<4)) {
+                       cprintf("%d Net privileges required for network mail.\n",
+                               ERROR+HIGHER_ACCESS_REQUIRED);
+                       return;
+                       }
+               if ((RESTRICT_INTERNET==1)&&(e==M_INTERNET)
+                  &&((CC->usersupp.flags&US_INTERNET)==0)
+                  &&(!CC->internal_pgm) ) {
+                       cprintf("%d You don't have access to Internet mail.\n",
+                               ERROR+HIGHER_ACCESS_REQUIRED);
+                       return;
+                       }
+               if (!strucmp(buf,"sysop")) {
+                       mtsflag=1;
+                       goto SKFALL;
+                       }
+               if (e!=M_LOCAL) goto SKFALL;    /* don't search local file  */
+               if (!strucmp(buf,CC->usersupp.fullname)) {
+                       cprintf("%d Can't send mail to yourself!\n",
+                               ERROR+NO_SUCH_USER);
+                       return;
+                       }
+
+               /* Check to make sure the user exists; also get the correct
+               * upper/lower casing of the name. 
+               */
+               lprintf(9, "checking validity of %s\n", buf);
+               a = getuser(&tempUS,buf);
+               lprintf(9, "getuser() returned %d\n", a);
+               if (a != 0) {
+                       cprintf("%d No such user.\n",ERROR+NO_SUCH_USER);
+                       return;
+                       }
+               strcpy(buf,tempUS.fullname);
+               }
+       
+SKFALL: b=MES_NORMAL;
+       if (CC->quickroom.QRflags&QR_ANONONLY) b=MES_ANON;
+       if (CC->quickroom.QRflags&QR_ANON2) {
+               if (anon_flag==1) b=MES_AN2;
+               }
+       if (CC->curr_rm!=1) buf[0]=0;
+
+       /* If we're only checking the validity of the request, return
+        * success without creating the message.
+        */
+       if (post==0) {
+               cprintf("%d %s\n",OK,buf);
+               return;
+               }
+       
+       cprintf("%d send message\n",SEND_LISTING);
+       if (CC->fake_postname[0])
+          make_message(CC->temp,&CC->usersupp,buf,CC->quickroom.QRname,b,e,format_type, CC->fake_postname);
+       else
+          if (CC->fake_username[0])
+             make_message(CC->temp,&CC->usersupp,buf,CC->quickroom.QRname,b,e,format_type, CC->fake_username);
+          else
+             make_message(CC->temp,&CC->usersupp,buf,CC->quickroom.QRname,b,e,format_type, "");
+       save_message(CC->temp,buf,mtsflag,e,1);
+        CC->fake_postname[0]='\0';
+       return;
+       }
+
+
+
+/* 
+ * message entry - mode 3 (raw)
+ */
+void cmd_ent3(char *entargs)
+{
+       char recp[256];
+       char buf[256];
+       int a, e;
+       struct usersupp tempUS;
+       long msglen;
+       long bloklen;
+       FILE *fp;
+
+       if (CC->internal_pgm == 0) {
+               cprintf("%d This command is for internal programs only.\n",
+                       ERROR);
+               return;
+               }
+
+       if (CC->curr_rm < 0) {
+               cprintf("%d No room selected.\n",ERROR);
+               return;
+               }
+
+       if (CC->curr_rm == 1) { /* If we're in Mail, check the recipient */
+               extract(recp, entargs, 1);
+               lprintf(9, "aliasing...\n");
+               e=alias(recp);                  /* alias and mail type */
+               lprintf(9,"...type is %d\n", e);
+               if ((buf[0]==0) || (e==M_ERROR)) {
+                       cprintf("%d Unknown address - cannot send message.\n",
+                               ERROR+NO_SUCH_USER);
+                       return;
+                       }
+               if (e == M_LOCAL) {
+                       a = getuser(&tempUS,recp);
+                       if (a!=0) {
+                               cprintf("%d No such user.\n", ERROR+NO_SUCH_USER);
+                               return;
+                               }
+                       }
+               }
+
+       /* At this point, message has been approved. */
+       if (extract_int(entargs, 0) == 0) {
+               cprintf("%d OK to send\n", OK);
+               return;
+               }
+
+       /* open a temp file to hold the message */
+       fp = fopen(CC->temp, "wb");
+       if (fp == NULL) {
+               cprintf("%d Cannot open %s: %s\n", 
+                       ERROR + INTERNAL_ERROR,
+                       CC->temp, strerror(errno) );
+               return;
+               }
+
+       msglen = extract_long(entargs, 2);
+       cprintf("%d %ld\n", SEND_BINARY, msglen);
+       while(msglen > 0L) {
+               bloklen = ((msglen >= 255L) ? 255 : msglen);
+               client_read(buf, (int)bloklen );
+               fwrite(buf, (int)bloklen, 1, fp);
+               msglen = msglen - bloklen;
+               }
+       fclose(fp);
+
+       save_message(CC->temp, recp, 0, e, 0);
+       }
+
+
+/*
+ * Delete message from current room
+ */
+void cmd_dele(char *delstr)
+{
+       long delnum;
+       int a,ok;
+
+       getuser(&CC->usersupp,CC->curr_user);
+       if ((CC->usersupp.axlevel < 6)
+          && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
+               cprintf("%d Higher access required.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       delnum = atol(delstr);
+       if (CC->curr_rm==1) {
+               cprintf("%d Can't delete mail.\n",ERROR);
+               return;
+               }
+       
+       /* get room records, obtaining a lock... */
+       lgetroom(&CC->quickroom,CC->curr_rm);
+       get_fullroom(&CC->fullroom,CC->curr_rm);
+
+       ok = 0;
+       for (a=0; a<MSGSPERRM; ++a)
+               if (CC->fullroom.FRnum[a]==delnum) {
+                       CC->fullroom.FRnum[a]=0L;
+                       ok = 1;
+                       }
+
+       sort_fullroom(&CC->fullroom);
+       CC->quickroom.QRhighest = CC->fullroom.FRnum[MSGSPERRM-1];
+
+       put_fullroom(&CC->fullroom,CC->curr_rm);
+       lputroom(&CC->quickroom,CC->curr_rm);
+       if (ok==1) {
+               cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
+               cprintf("%d Message deleted.\n",OK);
+               }
+       else cprintf("%d No message %ld.\n",ERROR,delnum);
+       } 
+
+
+/*
+ * move a message to another room
+ */
+void cmd_move(char *args)
+{
+       long num;
+       char targ[32];
+       int a;
+       int targ_slot;
+       struct quickroom qtemp;
+       struct fullroom ftemp;
+       int foundit;
+
+       num = extract_long(args,0);
+       extract(targ,args,1);
+       
+       if (CC->curr_rm < 0) {
+               cprintf("%d no room\n",ERROR);
+               return;
+               }
+
+       getuser(&CC->usersupp,CC->curr_user);
+       if ((CC->usersupp.axlevel < 6)
+          && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
+               cprintf("%d Higher access required.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       targ_slot = (-1);
+       for (a=0; a<MAXROOMS; ++a) {
+               getroom(&qtemp,a);
+               if (!strucmp(qtemp.QRname,targ)) {
+                       targ_slot = a;
+                       a = MAXROOMS;
+                       }
+               }
+       if (targ_slot < 0) {
+               cprintf("%d '%s' does not exist.\n",ERROR,targ);
+               return;
+               }
+
+       /* yank the message out of the current room... */
+       lgetroom(&CC->quickroom,CC->curr_rm);
+       get_fullroom(&CC->fullroom,CC->curr_rm);
+
+       foundit = 0;
+       for (a=0; a<MSGSPERRM; ++a) {
+               if (CC->fullroom.FRnum[a] == num) {
+                       foundit = 1;
+                       CC->fullroom.FRnum[a] = 0L;
+                       }
+               }
+       if (foundit) {
+               sort_fullroom(&CC->fullroom);
+               put_fullroom(&CC->fullroom,CC->curr_rm);
+               CC->quickroom.QRhighest = CC->fullroom.FRnum[MSGSPERRM-1];
+               }
+       lputroom(&CC->quickroom,CC->curr_rm);
+       if (!foundit) {
+               cprintf("%d msg %ld does not exist.\n",ERROR,num);
+               return;
+               }
+
+       lgetroom(&qtemp,targ_slot);
+       get_fullroom(&ftemp,targ_slot);
+       if (CC->fullroom.FRnum[0] != 0L) {
+               cdb_delete(CDB_MSGMAIN, &CC->fullroom.FRnum[0], sizeof(long));
+               }
+       for (a=0; a<MSGSPERRM-1; ++a) {
+               ftemp.FRnum[a]=ftemp.FRnum[a+1];
+               }
+       ftemp.FRnum[MSGSPERRM-1] = num;
+       sort_fullroom(&ftemp);
+       qtemp.QRhighest = ftemp.FRnum[MSGSPERRM-1];
+       put_fullroom(&ftemp,targ_slot);
+       lputroom(&qtemp,targ_slot);
+       cprintf("%d ok\n",OK);
+       }
diff --git a/citadel/msgform.c b/citadel/msgform.c
new file mode 100644 (file)
index 0000000..6b47b83
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * msgform.c v2.1
+ * see copyright.doc for copyright information
+ * 
+ * This is simply a filter that converts Citadel binary message format
+ * to readable, formatted output.
+ * 
+ * If the -q (quiet or qwk) flag is used, only the message text prints, and
+ * then it stops at the end of the first message it prints.
+ * This is used by the QWK reader for Citadel/UX during message format
+ * translation.
+ */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+
+long finduser();
+int fmout();
+int fpgetfield();
+
+int qwk = 0;
+
+
+#ifdef NO_STRERROR
+/*
+ * replacement strerror() for systems that don't have it
+ */
+char *strerror(e)
+int e; {
+       static char buf[32];
+
+       sprintf(buf,"errno = %d",e);
+       return(buf);
+       }
+#endif
+
+void main(argc,argv)
+int argc;
+char *argv[]; {
+       struct tm *tm;
+       int a,b,e,mtype,aflag;
+       char bbb[1024];
+       char subject[1024];
+       FILE *fp;
+       long now;
+
+       if (argc==2) if (!strcmp(argv[1],"-q")) qwk = 1;
+       fp=stdin;
+       if (argc==2) if (strcmp(argv[1],"-q")) {
+               fp = fopen(argv[1],"r");
+               if (fp==NULL) {
+                       fprintf(stderr,"%s: cannot open %s: %s\n",
+                               argv[0],argv[1],strerror(errno));
+                       exit(errno);
+                       }
+               }
+
+TOP:   do {
+               e=getc(fp);
+               if (e<0) exit(0);
+               } while(e!=255);
+       strcpy(subject,"");
+       mtype=getc(fp); aflag=getc(fp);
+       if (qwk == 0) printf(" ");
+
+   do {
+       b=getc(fp);
+       if (b=='M') {
+               if (qwk==0) {
+                               printf("\n");
+                               if (strlen(subject)!=0)
+                                       printf("Subject: %s\n",subject);
+                               }
+               if (aflag!=1) fmout(80,fp);
+                  else while(a=getc(fp), a>0) {
+                       if (a==13) putc(10,stdout);
+                       else putc(a,stdout);
+                       }
+               }
+       if ((b!='M')&&(b>0)) fpgetfield(fp,bbb);
+       if (b=='U') strcpy(subject,bbb);
+       if (qwk==0) {
+               if (b=='A') printf("from %s ",bbb);
+               if (b=='N') printf("@%s ",bbb);
+               if (b=='O') printf("in %s> ",bbb);
+               if (b=='R') printf("to %s ",bbb);
+               if (b=='T') {
+                       now=atol(bbb);
+                       tm=(struct tm *)localtime(&now);
+                       strcpy(bbb,asctime(tm)); bbb[strlen(bbb)-1]=0;
+                       printf("%s ",&bbb[4]);
+                       }
+               }
+          } while ((b!='M')&&(b>0));
+       if (qwk==0) printf("\n"); 
+       if (qwk==1) exit(0);
+       goto TOP;
+}
+
+int fpgetfield(fp,string) /* level-2 break out next null-terminated string */
+FILE *fp;
+char string[];
+{
+int a,b;
+strcpy(string,"");
+a=0;
+       do {
+               b=getc(fp);
+               if (b<1) { string[a]=0; return(0); }
+               string[a]=b;
+               ++a;
+               } while(b!=0);
+       return(0);
+}
+
+int fmout(width,fp)
+int width;
+FILE *fp;
+       {
+       int a,b,c;
+       int real = 0;
+       int old = 0;
+       char aaa[140];
+       
+       strcpy(aaa,""); old=255;
+       c=1; /* c is the current pos */
+FMTA:  old=real; a=getc(fp); real=a;
+       if (a<=0) goto FMTEND;
+       
+       if ( ((a==13)||(a==10)) && (old!=13) && (old!=10) ) a=32;
+       if ( ((old==13)||(old==10)) && (isspace(real)) ) {
+                                               printf("\n"); c=1; }
+       if (a>126) goto FMTA;
+
+       if (a>32) {
+       if ( ((strlen(aaa)+c)>(width-5)) && (strlen(aaa)>(width-5)) )
+               { printf("\n%s",aaa); c=strlen(aaa); aaa[0]=0; }
+        b=strlen(aaa); aaa[b]=a; aaa[b+1]=0; }
+       if (a==32) {    if ((strlen(aaa)+c)>(width-5)) { 
+                                                       printf("\n");
+                                                       c=1;
+                                                       }
+                       printf("%s ",aaa); ++c; c=c+strlen(aaa);
+                       strcpy(aaa,""); goto FMTA; }
+       if ((a==13)||(a==10)) {
+                               printf("%s\n",aaa); c=1;
+                               strcpy(aaa,""); goto FMTA; }
+       goto FMTA;
+
+FMTEND:        printf("\n");
+       return(0);
+}
diff --git a/citadel/netmailer.c b/citadel/netmailer.c
new file mode 100644 (file)
index 0000000..e657f5a
--- /dev/null
@@ -0,0 +1,292 @@
+/*
+ * netmailer for Citadel/UX
+ * see copyright.doc for copyright information
+ *
+ * netproc calls this to export Citadel mail to RFC822-compliant mailers.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <time.h>
+#include <string.h>
+#include <ctype.h>
+#include <syslog.h>
+#include "citadel.h"
+
+int struncmp();
+void LoadInternetConfig();
+void get_config();
+struct config config;
+
+char temp[20];
+
+char ALIASES[128];
+char CIT86NET[128];
+char SENDMAIL[128];
+char FALLBACK[128];
+char GW_DOMAIN[128];
+char TABLEFILE[128];
+int RUN_NETPROC = 1;
+
+int haschar(st,ch)
+char st[];
+int ch; {
+       int a,b;
+       b=0;
+       for (a=0; a<strlen(st); ++a) if (st[a]==ch) ++b;
+       return(b);
+       }
+
+
+int struncmp(lstr,rstr,len)
+char lstr[],rstr[];
+int len; {
+       int pos = 0;
+       char lc,rc;
+       while (pos<len) {
+               lc=tolower(lstr[pos]);
+               rc=tolower(rstr[pos]);
+               if ((lc==0)&&(rc==0)) return(0);
+               if (lc<rc) return(-1);
+               if (lc>rc) return(1);
+               pos=pos+1;
+               }
+       return(0);
+       }
+
+
+void fpgetfield(fp,string)
+FILE *fp;
+char string[];
+{
+       int a,b;
+       strcpy(string,"");
+       a=0;
+       do {
+               b=getc(fp);
+               if (b<1) {
+                       string[a]=0;
+                       return;
+                       }
+               string[a]=b;
+               ++a;
+               } while (b!=0);
+       }
+
+
+/* This is NOT the same msgform() found in the main program. It has been
+ * modified to format 80 columns into a temporary file, and extract the
+ * sender and recipient names for use within the main() loop.
+ */
+void msgform(msgfile,mfout,sbuf,rbuf,nbuf,pbuf,mid_buf,rmname,subj)
+char *msgfile;
+FILE *mfout;
+char *sbuf;            /* sender */
+char *rbuf;            /* recipient (in this case, an Internet address) */
+char *nbuf;            /* source node */
+char *pbuf;            /* path */
+long *mid_buf;         /* message ID */
+char *rmname;          /* room name */
+char *subj;            /* subject */
+       {
+       int a,b,c,e,old,mtype,aflag;
+       int real = 0;
+       char aaa[128],bbb[128];
+       FILE *fp;
+       int width;
+       int generate_subject = 1;
+
+       strcpy(subj, "");
+       strcpy(pbuf, "");       
+       strcpy(nbuf, NODENAME); 
+       strcpy(rmname,"");
+       time(mid_buf);
+       width=80;
+       fp=fopen(msgfile, "rb");
+       if (fp==NULL) {
+               fprintf(stderr, "netmailer: can't open message file\n");
+               return;
+               }
+       strcpy(aaa,""); old=255;
+       c=1; /* c is the current pos */
+       e=getc(fp);
+       if (e!=255) {
+               fprintf(stderr,"netmailer: This is not a Citadel message.\n");
+               goto END;
+               }
+       mtype=getc(fp); aflag=getc(fp);
+       goto BONFGM;
+A:     if (aflag==1) goto AFLAG;
+       old=real; a=getc(fp); real=a;
+       if (a==0) goto END;
+       if (a<0) goto END;
+
+       /* generate subject... */
+       if ( (generate_subject == 1) && (strlen(subj) < 60)) {
+               subj[strlen(subj)+1] = 0;
+               subj[strlen(subj)] = (((a>31)&&(a<127)) ? a : 32);
+               }
+       
+       if ( ((a==13)||(a==10)) && (old!=13) && (old!=10) ) a=32;
+       if ( ((old==13)||(old==10)) && ((real==32)||(real==13)||(real==10))) {
+                                               fprintf(mfout,"\n"); c=1; }
+
+       if (a!=32) {
+       if ( ((strlen(aaa)+c)>(width-5)) && (strlen(aaa)>(width-5)) )
+               { fprintf(mfout,"\n%s",aaa); c=strlen(aaa); aaa[0]=0; }
+        b=strlen(aaa); aaa[b]=a; aaa[b+1]=0; }
+       if (a==32) {    if ((strlen(aaa)+c)>(width-5)) { 
+                                                       fprintf(mfout,"\n");
+                                                       c=1;
+                                                       }
+                       fprintf(mfout,"%s ",aaa); ++c; c=c+strlen(aaa);
+                       strcpy(aaa,""); goto A; }
+       if ((a==13)||(a==10)) {
+                               fprintf(mfout,"%s\n",aaa); c=1;
+                               strcpy(aaa,""); goto A; }
+       goto A;
+
+AFLAG: a=getc(fp);
+       if (a==0) goto END;
+       if (a==13) {
+               putc(10, mfout);
+               }
+       else {
+               putc(a, mfout);
+               }
+       goto AFLAG;
+
+END:   fclose(fp);
+       return;
+
+BONFGM:        b=getc(fp); if (b<0) goto END;
+       if (b=='M') goto A;
+       fpgetfield(fp, bbb);
+       if (b=='A') strcpy(sbuf, bbb);
+       if (b=='R') strcpy(rbuf, bbb);
+       if (b=='N') strcpy(nbuf, bbb);
+       if (b=='P') strcpy(pbuf, bbb);
+       if (b=='I') *mid_buf = atol(bbb);
+       if (b=='O') strcpy(rmname, bbb);
+       if (b=='U') {
+               strcpy(subj, bbb);
+               generate_subject = 0; /* have a real subj so don't gen one */
+               }
+       goto BONFGM;
+       }
+
+void main(argc,argv)
+int argc;
+char *argv[]; {
+       int a;
+       FILE *fp,*rmail;
+       char sbuf[200],rbuf[200],cstr[100],fstr[128];
+       char nbuf[64],pbuf[128],rmname[128],buf[128];
+       char subject[200];
+       long mid_buf;
+       long now;
+       int mlist = 0;
+
+       fprintf(stderr, "netmailer: started...  sending mail to %s\n", argv[1]);
+       openlog("netmailer", LOG_PID, LOG_USER);
+       get_config();   
+       LoadInternetConfig();
+       sprintf(temp, "/tmp/netmailer.%d", getpid()); /* temp file name */
+
+       if ( (argc < 2) || (argc > 3) ) {
+               fprintf(stderr, "netmailer: usage: netmailer recipient@node.dom [mlist]\n");
+               exit(1);
+               }
+
+       /*
+        * If we are running in mailing list mode, the room is being two-way
+        * gatewayed to an Internet mailing list.  Since many listprocs only
+        * accept postings from subscribed addresses, we must always use the
+        * room's address as the originating user.
+        */
+       if ( (argc == 3) && (!strucmp(argv[2], "mlist")) ) {
+               mlist = 1;
+               }
+
+       /* convert to ASCII & get info */
+       fp=fopen(temp,"w");
+       msgform(argv[1], fp, sbuf, rbuf, nbuf, pbuf, &mid_buf, rmname, subject);
+       fclose(fp);
+
+       strcpy(buf, rmname);
+       strcpy(rmname, "room_");
+       strcat(rmname, buf);
+       for (a=0; a<strlen(rmname); ++a) {
+               if (rmname[a] == ' ') rmname[a] = '_';
+               rmname[a] = tolower(rmname[a]);
+               }
+
+       sprintf(cstr, SENDMAIL, rbuf);
+       rmail=(FILE *)popen(cstr,"w");
+
+       strcpy(fstr,sbuf);
+       for (a=0; a<strlen(sbuf); ++a) if (sbuf[a]==32) sbuf[a]='_';
+       for (a=0; a<strlen(rbuf); ++a) if (rbuf[a]==32) rbuf[a]='_';
+
+       /*
+        * This logic attempts to compose From and From: lines that are
+        * as RFC822-compliant as possible.  The return addresses are correct
+        * if you're using Citadel's 'citmail' delivery agent to allow BBS
+        * users to receive Internet mail.
+        */
+       fprintf(rmail,"From ");
+       if (strucmp(nbuf,NODENAME)) fprintf(rmail,"%s!",nbuf);
+
+       if (!strucmp(nbuf,NODENAME)) strcpy(nbuf, FQDN);
+       
+       if (mlist) {
+               fprintf(rmail,"%s\n", rmname);
+               fprintf(rmail,"From: %s@%s (%s)\n", rmname, FQDN, fstr);
+               }
+       else {
+
+               if (!strucmp(nbuf, NODENAME)) {         /* from this system */
+                       fprintf(rmail,"%s\n",pbuf);
+                       fprintf(rmail,"From: %s@%s (%s)\n",
+                               sbuf, FQDN, fstr);
+                       }
+               else if (haschar(nbuf, '.')) {          /* from an FQDN */
+                       fprintf(rmail,"%s\n",sbuf);
+                       fprintf(rmail,"From: %s@%s (%s)\n",
+                               sbuf, nbuf, fstr);
+                       }
+               else {                                  /* from another Cit */
+                       fprintf(rmail,"%s\n",sbuf);
+                       fprintf(rmail,"From: %s@%s.%s (%s)\n",
+                               sbuf, nbuf, GW_DOMAIN, fstr);
+                       }
+
+               }
+
+       /*
+        * Everything else is pretty straightforward.
+        */
+       fprintf(rmail,"To: %s\n", rbuf);
+       time(&now);
+       fprintf(rmail,"Date: %s", asctime(localtime(&now)));
+       fprintf(rmail,"Message-Id: <%ld@%s>\n", mid_buf, nbuf);
+       fprintf(rmail,"X-Mailer: %s\n", CITADEL);
+       fprintf(rmail,"Subject: %s\n", subject);
+       fprintf(rmail,"\n");
+       fp=fopen(temp,"r");
+       if (fp!=NULL) {
+               do {
+                       a=getc(fp);
+                       if (a>=0) putc(a,rmail);
+                       } while(a>=0);
+               fclose(fp);
+               }
+       fprintf(rmail,"\n");
+       pclose(rmail);
+  
+       unlink(temp);           /* get rid of the ASCII file */
+       exit(0);                /* go back to the main program */
+       }
diff --git a/citadel/netpoll.c b/citadel/netpoll.c
new file mode 100644 (file)
index 0000000..3e9b96c
--- /dev/null
@@ -0,0 +1,267 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include "citadel.h"
+
+/*
+ * This variable defines the amount of network spool data that may be carried
+ * in one server transfer command.  For some reason, some networks get hung
+ * up on larger packet sizes.  We don't know why.  In any case, never set the
+ * packet size higher than 4096 or your server sessions will crash.
+ */
+#define IGNET_PACKET_SIZE 64
+
+long atol();
+
+void attach_to_server();
+void serv_read();
+void serv_write();
+void get_config();
+struct config config;
+
+
+/*
+ * num_parms()  -  discover number of parameters...
+ */
+int num_parms(source)
+char source[]; {
+       int a;
+       int count = 1;
+
+       for (a=0; a<strlen(source); ++a) 
+               if (source[a]=='|') ++count;
+       return(count);
+       }
+
+
+/*
+ * extract()  -  extract a parameter from a series of "|" separated...
+ */
+void extract(dest,source,parmnum)
+char dest[];
+char source[];
+int parmnum; {
+       char buf[256];
+       int count = 0;
+       int n;
+
+       n = num_parms(source);
+
+       if (parmnum >= n) {
+               strcpy(dest,"");
+               return;
+               }
+       strcpy(buf,source);
+       if ( (parmnum == 0) && (n == 1) ) {
+               strcpy(dest,buf);
+               return;
+               }
+
+       while (count++ < parmnum) do {
+               strcpy(buf,&buf[1]);
+               } while( (strlen(buf)>0) && (buf[0]!='|') );
+       if (buf[0]=='|') strcpy(buf,&buf[1]);
+       for (count = 0; count<strlen(buf); ++count)
+               if (buf[count] == '|') buf[count] = 0;
+       strcpy(dest,buf);
+       }
+
+/*
+ * extract_int()  -  extract an int parm w/o supplying a buffer
+ */
+int extract_int(source,parmnum)
+char *source;
+int parmnum; {
+       char buf[256];
+       
+       extract(buf,source,parmnum);
+       return(atoi(buf));
+       }
+
+/*
+ * extract_long()  -  extract a long parm w/o supplying a buffer
+ */
+long extract_long(source,parmnum)
+char *source;
+int parmnum; {
+       char buf[256];
+       
+       extract(buf,source,parmnum);
+       return(atol(buf));
+       }
+
+
+void logoff(code)
+int code; {
+       exit(code);
+       }
+
+
+/*
+ * receive network spool from the remote system
+ */
+void receive_spool() {
+       long download_len;
+       long bytes_received;
+       char buf[256];
+       static char pbuf[IGNET_PACKET_SIZE];
+       char tempfilename[64];
+       long plen;
+       FILE *fp;
+
+       sprintf(tempfilename,"/tmp/netpoll.%d",getpid());
+       serv_puts("NDOP");
+       serv_gets(buf);
+       printf("%s\n",buf);
+       if (buf[0]!='2') return;
+       download_len = extract_long(&buf[4],0);
+
+       bytes_received = 0L;
+       fp = fopen(tempfilename,"w");
+       if (fp==NULL) {
+               perror("cannot open download file locally");
+               return;
+               }
+
+       while (bytes_received < download_len) {
+               sprintf(buf,"READ %ld|%ld",
+                       bytes_received,
+                       ( (download_len - bytes_received > IGNET_PACKET_SIZE)
+                         ? IGNET_PACKET_SIZE : (download_len - bytes_received) ) );
+               serv_puts(buf);
+               serv_gets(buf);
+               if (buf[0]=='6') {
+                       plen = extract_long(&buf[4],0);
+                       serv_read(pbuf,plen);
+                       fwrite((char *)pbuf,plen,1,fp);
+                       bytes_received = bytes_received + plen;
+                       }
+               }
+
+       fclose(fp);
+       serv_puts("CLOS");
+       serv_gets(buf);
+       printf("%s\n",buf);
+       sprintf(buf,"mv %s %s/network/spoolin/netpoll.%d",
+               tempfilename,BBSDIR,getpid());
+       system(buf);
+       system("exec nohup ./netproc >/dev/null 2>&1 &");
+       }
+
+/*
+ * transmit network spool to the remote system
+ */
+void transmit_spool(remote_nodename)
+char *remote_nodename; {
+       char buf[256];
+       char pbuf[4096];
+       long plen;
+       long bytes_to_write, thisblock;
+       int fd;
+       char sfname[128];
+
+       serv_puts("NUOP");
+       serv_gets(buf);
+       printf("%s\n",buf);
+       if (buf[0]!='2') return;
+
+       sprintf(sfname,"%s/network/spoolout/%s",BBSDIR,remote_nodename);
+       fd = open(sfname,O_RDONLY);
+       if (fd<0) {
+               if (errno == ENOENT) {
+                       printf("Nothing to send.\n");
+                       }
+               else {
+                       perror("cannot open upload file locally");
+                       }
+               return;
+               }
+
+       while ( plen=(long)read(fd,pbuf,IGNET_PACKET_SIZE), plen>0L) {
+               bytes_to_write = plen; 
+               while (bytes_to_write > 0L) {
+                       sprintf(buf,"WRIT %ld", bytes_to_write);
+                       serv_puts(buf);
+                       serv_gets(buf);
+                       thisblock = atol(&buf[4]);
+                       if (buf[0]=='7') {
+                               serv_write(pbuf, (int)thisblock);
+                               bytes_to_write = bytes_to_write - thisblock;
+                               }
+                       else {
+                               goto ABORTUPL;
+                               }
+                       }
+               }
+
+ABORTUPL:
+       close(fd);
+       serv_puts("UCLS 1");
+       serv_gets(buf);
+       printf("%s\n",buf);
+       if (buf[0]=='2') unlink(sfname);
+       }
+
+
+
+
+void main(argc,argv)
+int argc;
+char *argv[]; {
+       char buf[256];
+       char remote_nodename[32];
+       int a;
+
+       if (argc != 4) {
+               fprintf(stderr,
+               "%s: usage: %s <address> <port number> <remote netpassword>\n",
+                       argv[0],argv[0]);
+               exit(1);
+               }
+
+       get_config();
+
+       attach_to_server(argc,argv);
+       serv_gets(buf);
+       printf("%s\n",buf);
+       if ((buf[0]!='2')&&(strncmp(buf,"551",3))) {
+               fprintf(stderr,"%s: %s\n",argv[0],&buf[4]);
+               logoff(atoi(buf));
+               }
+
+       serv_puts("INFO");
+       serv_gets(buf);
+       if (buf[0]=='1') {
+               a = 0;
+               while (serv_gets(buf), strcmp(buf,"000")) {
+                       if (a==1) strcpy(remote_nodename,buf);
+                       if (a==1) printf("Connected to: %s ",buf);
+                       if (a==2) printf("(%s) ",buf);
+                       if (a==6) printf("%s\n",buf);
+                       ++a;
+                       }
+               }
+
+       if (!strcmp(remote_nodename,config.c_nodename)) {
+               fprintf(stderr,"Connected to local system\n");
+               }
+       else {
+               sprintf(buf,"NETP %s|%s",config.c_nodename,argv[3]);
+               serv_puts(buf);
+               serv_gets(buf);
+               printf("%s\n",buf);
+       
+               /* only do the transfers if we authenticated correctly! */
+               if (buf[0]=='2') {
+                       receive_spool();
+                       transmit_spool(remote_nodename);
+                       }
+               }
+
+       serv_puts("QUIT");
+       serv_gets(buf);
+       exit(0);
+       }
diff --git a/citadel/netproc.c b/citadel/netproc.c
new file mode 100644 (file)
index 0000000..49b912d
--- /dev/null
@@ -0,0 +1,1341 @@
+/*
+ * Citadel/UX Intelligent Network Processor for IGnet/Open networks v3.6
+ * Designed and written by Art Cancro @ Uncensored Communications Group
+ * See copyright.txt for copyright information
+ */
+
+/*
+ * Specify where netproc should log to, and the mode for opening the file.
+ * If you are logging to a file, NPLOGMODE should be a; if you are logging to
+ * a device or fifo it should be w.
+ */
+#define NPLOGFILE      "./netproc.log" 
+#define NPLOGMODE      "a"
+
+/* How long it takes for an old node to drop off the network map */
+#define EXPIRY_TIME    (2592000L)
+
+/* Where do we keep our lock file? */
+#define LOCKFILE       "/var/lock/LCK.netproc"
+
+/* Path to the 'uudecode' utility (needed for network file transfers) */
+#define UUDECODE       "/usr/bin/uudecode"
+
+/* Uncomment the DEBUG def to see noisy traces */
+/* #define DEBUG 1 */
+
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <time.h>
+#include <signal.h>
+#include <errno.h>
+#include "citadel.h"
+
+/* A list of users you wish to filter out of incoming traffic can be kept
+ * in ./network/filterlist -- messages from these users will be automatically
+ * moved to FILTERROOM.  Normally this will be the same as TWITROOM (the
+ * room problem user messages are moved to) but you can override this by
+ * specifying a different room name here.
+ */
+#ifndef FILTERROOM
+#define FILTERROOM TWITROOM
+#endif
+
+struct msglist {
+       struct msglist *next;
+       long m_num;
+       char m_rmname[20];
+       };
+
+struct rmlist {
+       struct rmlist *next;
+       char rm_name[20];
+       long rm_lastsent;
+       };
+
+struct filterlist {
+       struct filterlist *next;
+       char f_person[64];
+       char f_room[64];
+       char f_system[64];
+       };
+
+struct syslist {
+       struct syslist *next;
+       char s_name[16];
+       char s_type[4];
+       char s_nexthop[128];
+       long s_lastcontact;
+       char s_humannode[64];
+       char s_phonenum[32];
+       char s_gdom[64];
+       };
+
+struct minfo {
+       char A[512];
+       char E[512];
+       long I;
+       char N[512];
+       char O[512];
+       char R[512];
+       long T;
+       char D[512];
+       char C[512];
+       char nexthop[32];
+       char H[512];
+       char S[512];
+       char B[512];
+       char G[512];
+       };
+
+
+void attach_to_server();
+void serv_read();
+void serv_write();
+void get_config();
+
+struct filterlist *filter = NULL;
+char roomnames[MAXROOMS][20];
+char roomdirs[MAXROOMS][15];
+struct syslist *slist = NULL;
+
+struct config config;
+extern char bbs_home_directory[];
+extern int home_specified;
+
+int struncmp(lstr,rstr,len)
+char lstr[],rstr[];
+int len; {
+       int pos = 0;
+       char lc,rc;
+       while (pos<len) {
+               lc=tolower(lstr[pos]);
+               rc=tolower(rstr[pos]);
+               if ((lc==0)&&(rc==0)) return(0);
+               if (lc<rc) return(-1);
+               if (lc>rc) return(1);
+               pos=pos+1;
+               }
+       return(0);
+       }
+
+/* redefine strucmp, just in case we're using an old version of citadel.h
+ * that has it as a separate routine
+ */
+#ifndef strucmp
+#undef strucmp
+#endif
+#define strucmp(lstr,rstr) struncmp(lstr,rstr,32767)
+
+
+#ifdef NO_STRERROR
+/*
+ * replacement strerror() for systems that don't have it
+ */
+char *strerror(e)
+int e; {
+       static char buf[32];
+
+       sprintf(buf,"errno = %d",e);
+       return(buf);
+       }
+#endif
+
+
+void strip_trailing_whitespace(buf)
+char buf[]; {
+       while(isspace(buf[strlen(buf)-1]))
+               buf[strlen(buf)-1]=0;
+       }
+
+
+/*
+ * for performance optimization, netproc loads the list of room names (and
+ * their corresponding directory names, if applicable) into a table in memory.
+ */
+int load_roomnames() {
+       FILE *fp;
+       struct quickroom qbuf;
+       int i;
+
+       fp=fopen("./quickroom","rb");
+       if (fp==NULL) return(1);
+       for (i=0; i<MAXROOMS; ++i) {
+               if (fread((char *)&qbuf,sizeof(struct quickroom),1,fp)!=1)
+                       return(1);
+               strcpy(roomnames[i],qbuf.QRname);
+               if (qbuf.QRflags & QR_DIRECTORY)
+                       strcpy(roomdirs[i],qbuf.QRdirname);
+               else
+                       strcpy(roomdirs[i],config.c_bucket_dir);
+               }
+       fclose(fp);
+       return(0);
+       }
+
+/*
+ * we also load the network/mail.sysinfo table into memory, make changes
+ * as we learn more about the network from incoming messages, and write
+ * the table back to disk when we're done.
+ */
+int load_syslist() {
+       FILE *fp;
+       struct syslist *stemp;
+       char insys = 0;
+       char buf[128];
+
+       fp=fopen("network/mail.sysinfo","rb");
+       if (fp==NULL) return(1);
+
+       while(1) {
+               if (fgets(buf,128,fp)==NULL) {
+                       fclose(fp);
+                       return(0);
+                       }
+               buf[strlen(buf)-1] = 0;
+               while (isspace(buf[0])) strcpy(buf,&buf[1]);
+               if (buf[0]=='#') buf[0]=0;
+               if ( (insys==0) && (strlen(buf)!=0) ) {
+                       insys = 1;
+                       stemp =(struct syslist *)malloc(sizeof(struct syslist));
+                       stemp->next = slist;
+                       slist = stemp;
+                       strcpy(slist->s_name,buf);
+                       strcpy(slist->s_type,"bin");
+                       strcpy(slist->s_nexthop,"Mail");
+                       slist->s_lastcontact = 0L;
+                       strcpy(slist->s_humannode,"");
+                       strcpy(slist->s_phonenum,"");
+                       strcpy(slist->s_gdom,"");
+                       }
+               else if ( (insys==1) && (strlen(buf)==0) ) {
+                       insys = 0;
+                       }
+               else if ( (insys==1) && (!strncmp(buf,"bin",3)) ) {
+                       strcpy(slist->s_type,"bin");
+                       strcpy(slist->s_nexthop,&buf[4]);
+                       }
+               else if ( (insys==1) && (!strncmp(buf,"use",3)) ) {
+                       strcpy(slist->s_type,"use");
+                       strcpy(slist->s_nexthop,&buf[4]);
+                       }
+               else if ( (insys==1) && (!strncmp(buf,"uum",3)) ) {
+                       strcpy(slist->s_type,"uum");
+                       strcpy(slist->s_nexthop,&buf[4]);
+                       }
+               else if ( (insys==1) && (!strncmp(buf,"lastcontact",11)) ) {
+                       sscanf(&buf[12],"%ld",&slist->s_lastcontact);
+                       }
+               else if ( (insys==1) && (!strncmp(buf,"humannode",9)) ) {
+                       strcpy(slist->s_humannode,&buf[10]);
+                       }
+               else if ( (insys==1) && (!strncmp(buf,"phonenum",8)) ) {
+                       strcpy(slist->s_phonenum,&buf[9]);
+                       }
+               else if ( (insys==1) && (!strncmp(buf,"gdom",4)) ) {
+                       strcpy(slist->s_gdom,&buf[5]);
+                       }
+               }
+       }
+
+/* now we have to set up two "special" nodes on the list: one
+ * for the local node, and one for an Internet gateway
+ */
+void setup_special_nodes() {
+       struct syslist *stemp,*slocal;
+
+       slocal = NULL;
+       for (stemp=slist; stemp!=NULL; stemp=stemp->next) {
+               if (!strucmp(stemp->s_name,config.c_nodename)) slocal=stemp;
+               }
+       if (slocal==NULL) {
+               slocal =(struct syslist *)malloc(sizeof(struct syslist));
+               slocal->next = slist;
+               slist = slocal;
+               }
+       strcpy(slocal->s_name,config.c_nodename);
+       strcpy(slocal->s_type,"bin");
+       strcpy(slocal->s_nexthop,"Mail");
+       time(&slocal->s_lastcontact);
+       strcpy(slocal->s_humannode,config.c_humannode);
+       strcpy(slocal->s_phonenum,config.c_phonenum);
+
+       slocal = NULL;
+       for (stemp=slist; stemp!=NULL; stemp=stemp->next) {
+               if (!strucmp(stemp->s_name,"internet")) slocal=stemp;
+               }
+       if (slocal==NULL) {
+               slocal =(struct syslist *)malloc(sizeof(struct syslist));
+               slocal->next = slist;
+               slist = slocal;
+               }
+       strcpy(slocal->s_name,"internet");
+       strcpy(slocal->s_type,"uum");
+       strcpy(slocal->s_nexthop,"%s");
+       time(&slocal->s_lastcontact);
+       strcpy(slocal->s_humannode,"Internet Gateway");
+       strcpy(slocal->s_phonenum,"");
+       strcpy(slocal->s_gdom,"");
+
+       }
+
+/*
+ * here's the routine to write the table back to disk.
+ */
+void rewrite_syslist() {
+       struct syslist *stemp;
+       FILE *newfp;
+       long now;
+
+       time(&now);
+       newfp=fopen("network/mail.sysinfo","w");
+       for (stemp=slist; stemp!=NULL; stemp=stemp->next) {
+               if (!strucmp(stemp->s_name,config.c_nodename)) {
+                       time(&stemp->s_lastcontact);
+                       strcpy(stemp->s_type,"bin");
+                       strcpy(stemp->s_humannode,config.c_humannode);
+                       strcpy(stemp->s_phonenum,config.c_phonenum);
+                       }
+           /* remove systems we haven't heard from in a while */
+           if ( (stemp->s_lastcontact == 0L) 
+                || (now - stemp->s_lastcontact < EXPIRY_TIME) ) {
+               fprintf(newfp,"%s\n%s %s\n",
+                       stemp->s_name,stemp->s_type,stemp->s_nexthop);
+               if (strlen(stemp->s_phonenum) > 0) 
+                       fprintf(newfp,"phonenum %s\n",stemp->s_phonenum);
+               if (strlen(stemp->s_gdom) > 0) 
+                       fprintf(newfp,"gdom %s\n",stemp->s_gdom);
+               if (strlen(stemp->s_humannode) > 0) 
+                       fprintf(newfp,"humannode %s\n",stemp->s_humannode);
+               if (stemp->s_lastcontact > 0L)
+                       fprintf(newfp,"lastcontact %ld %s",
+                               stemp->s_lastcontact,
+                               asctime(localtime(&stemp->s_lastcontact)));
+               fprintf(newfp,"\n");
+               }
+           }
+       fclose(newfp);
+       /* now free the list */
+       while (slist!=NULL) {
+               stemp = slist;
+               slist = slist->next;
+               free(stemp);
+               }
+       }
+
+
+/* call this function with the node name of a system and it returns a pointer
+ * to its syslist structure.
+ */
+struct syslist *get_sys_ptr(sysname)
+char *sysname; {
+       static char sysnambuf[16];
+       static struct syslist *sysptrbuf = NULL;
+       struct syslist *stemp;
+
+       if ( (!strcmp(sysname,sysnambuf))
+               && (sysptrbuf!=NULL) )  return(sysptrbuf);
+
+       strcpy(sysnambuf,sysname);
+       for (stemp=slist; stemp!=NULL; stemp=stemp->next) {
+               if (!strcmp(sysname,stemp->s_name)) {
+                       sysptrbuf = stemp;
+                       return(stemp);
+                       }
+               }
+       sysptrbuf = NULL;
+       return(NULL);
+       }
+
+
+/*
+ * make sure only one copy of netproc runs at a time, using lock files
+ */
+int set_lockfile() {
+       FILE *lfp;
+       int ok = 1;
+       int onppid;
+       char buf[64];
+
+       if (access(LOCKFILE,0)==0) {
+
+       /* 
+        * if the /proc filesystem is available, we can further check to
+        * make sure that the process that wrote the lock file is actually
+        * running, and didn't simply terminate and not clean up after itself
+        */
+#ifdef HAVE_PROC_FS
+               lfp = fopen(LOCKFILE,"r");
+               fscanf(lfp,"%d",&onppid);
+               fclose(lfp);
+               sprintf(buf,"/proc/%d/cmdline",onppid);
+               if (access(buf,0)==0) ok = 0;
+#else
+               ok = 0;
+#endif
+               }
+
+       if (ok == 0) return(1);
+       lfp=fopen(LOCKFILE,"w");
+       fprintf(lfp,"%d\n",getpid());
+       fclose(lfp);
+       return(0);
+       }
+
+void remove_lockfile() {
+       unlink(LOCKFILE);
+       }
+
+/*
+ * Why both cleanup() and nq_cleanup() ?  Notice the alarm() call in
+ * cleanup() .  If for some reason netproc hangs waiting for the server
+ * to clean up, the alarm clock goes off and the program exits anyway.
+ * The cleanup() routine makes a check to ensure it's not reentering, in
+ * case the ipc module looped it somehow.
+ */
+void nq_cleanup(e)
+int e; {
+       remove_lockfile();
+       exit(e);
+       }
+
+void cleanup(e)
+int e; {
+       static int nested = 0;
+
+       alarm(30);
+       signal(SIGALRM,nq_cleanup);
+       if (nested++ < 1) serv_puts("QUIT");
+       nq_cleanup(e);
+       }
+
+/*
+ * This is implemented as a function rather than as a macro because the
+ * client-side IPC modules expect logoff() to be defined.  They call logoff()
+ * when a problem connecting or staying connected to the server occurs.
+ */
+void logoff(e)
+int e; {
+       cleanup(e);
+       }
+
+/*
+ * If there is a kill file in place, this function will process it.
+ */
+void load_filterlist() {
+       FILE *fp;
+       struct filterlist *fbuf;
+       char sbuf[256];
+       int a,p;
+       fp=fopen("./network/filterlist","r");
+       if (fp==NULL) return;
+       while (fgets(sbuf,256,fp)!=NULL) {
+               if (sbuf[0]!='#') {
+                       sbuf[strlen(sbuf)-1]=0;
+                       fbuf=(struct filterlist *)
+                               malloc((long)sizeof(struct filterlist));
+                       fbuf->next = filter;
+                       filter = fbuf;
+                       strcpy(fbuf->f_person,"*");
+                       strcpy(fbuf->f_room,"*");
+                       strcpy(fbuf->f_system,"*");
+                       p = (-1);
+                       for (a=strlen(sbuf); a>=0; --a) if (sbuf[a]==',') p=a;
+                       if (p>=0) {
+                               sbuf[p] = 0;
+                               strcpy(fbuf->f_person,sbuf);
+                               strcpy(sbuf,&sbuf[p+1]);
+                               }
+                       for (a=strlen(sbuf); a>=0; --a) if (sbuf[a]==',') p=a;
+                       if (p>=0) {
+                               sbuf[p] = 0;
+                               strcpy(fbuf->f_room,sbuf);
+                               strcpy(sbuf,&sbuf[p+1]);
+                               }
+                       strcpy(fbuf->f_system,sbuf);
+                       }
+               }
+       fclose(fp);
+       }
+
+/* returns 1 if user/message/room combination is in the kill file */
+int is_banned(k_person,k_room,k_system)
+char *k_person,*k_room,*k_system; {
+       struct filterlist *fptr;
+
+       for (fptr=filter; fptr!=NULL; fptr=fptr->next) if (
+        ((!strucmp(fptr->f_person,k_person))||(!strcmp(fptr->f_person,"*")))
+       &&
+        ((!strucmp(fptr->f_room,k_room))||(!strcmp(fptr->f_room,"*")))
+       &&
+        ((!strucmp(fptr->f_system,k_system))||(!strcmp(fptr->f_system,"*")))
+       ) return(1);
+
+       return(0);
+       }
+
+int get_sysinfo_type(name)     /* determine routing from sysinfo file */
+char name[]; {
+       struct syslist *stemp;
+GETSN: for (stemp=slist; stemp!=NULL; stemp=stemp->next) {
+           if (!strucmp(stemp->s_name,name)) {
+               if (!strucmp(stemp->s_type,"use")) {
+                       strcpy(name,stemp->s_nexthop);
+                       goto GETSN;
+                       }
+               if (!strucmp(stemp->s_type,"bin")) {
+                       return(M_BINARY);
+                       }
+               if (!strucmp(stemp->s_type,"uum")) {
+                       return(M_INTERNET);
+                       }
+               }
+           }
+       printf("netproc: cannot find system '%s' in mail.sysinfo\n",name);
+       return(-1);
+       }
+
+
+void fpgetfield(fp,string)
+FILE *fp;
+char string[]; {
+       int a,b;
+
+       strcpy(string,"");
+       a=0;
+       do {
+               b=getc(fp);
+               if (b<1) {
+                       string[a]=0;
+                       return;
+                       }
+               string[a]=b;
+               ++a;
+               } while (b!=0);
+       }
+
+
+
+/*
+ * Load all of the fields of a message, except the actual text, into a
+ * table in memory (so we know how to process the message).
+ */
+void msgfind(msgfile,buffer)
+char *msgfile;
+struct minfo *buffer; {
+       int b,e,mtype,aflag;
+       char bbb[1024];
+       char userid[1024];
+       FILE *fp;
+               
+       strcpy(userid,"");
+       fp=fopen(msgfile,"rb");
+       if (fp==NULL) {
+               fprintf(stderr,"Can't open message file: %s\n",strerror(errno));
+               return;
+               }
+       e=getc(fp);
+       if (e!=255) {
+               fprintf(stdout,"Incorrect message format\n");
+               goto END;
+               }
+       mtype=getc(fp); aflag=getc(fp);
+       buffer->I=0L;
+       buffer->R[0]=0;
+       buffer->E[0]=0;
+       buffer->H[0]=0;
+       buffer->S[0]=0;
+       buffer->B[0]=0;
+       buffer->G[0]=0;
+
+BONFGM:        b=getc(fp); if (b<0) goto END;
+       if (b=='M') goto END;
+       fpgetfield(fp,bbb);
+       while ((bbb[0]==' ')&&(strlen(bbb)>1)) strcpy(bbb,&bbb[1]);
+       if (b=='A') {
+               strcpy(buffer->A,bbb);
+               if (strlen(userid)==0) {
+                       strcpy(userid,bbb);
+                       for (e=0; e<strlen(userid); ++e)
+                               if (userid[e]==' ') userid[e]='_';
+                       }
+               }
+       if (b=='O') strcpy(buffer->O,bbb);
+       if (b=='C') strcpy(buffer->C,bbb);
+       if (b=='N') strcpy(buffer->N,bbb);
+       if (b=='S') strcpy(buffer->S,bbb);
+       if (b=='P') {
+               /* extract the user id from the path */
+               for (e=0; e<strlen(bbb); ++e)
+                       if (bbb[e]=='!') strcpy(userid,&bbb[e+1]);
+
+               /* now find the next hop */
+               for (e=0; e<strlen(bbb); ++e) if (bbb[e]=='!') bbb[e]=0;
+               strcpy(buffer->nexthop,bbb);
+               }
+       if (b=='R') {
+               for (e=0; e<strlen(bbb); ++e) if (bbb[e]=='_') bbb[e]=' ';
+               strcpy(buffer->R,bbb);
+               }
+       if (b=='D') strcpy(buffer->D,bbb);
+       if (b=='T') buffer->T=atol(bbb);
+       if (b=='I') buffer->I=atol(bbb);
+       if (b=='H') strcpy(buffer->H,bbb);
+       if (b=='B') strcpy(buffer->B,bbb);
+       if (b=='G') strcpy(buffer->G,bbb);
+       if (b=='E') strcpy(buffer->E,bbb);
+       goto BONFGM;
+
+END:   if (buffer->I==0L) buffer->I=buffer->T;
+       fclose(fp);
+       }
+
+void ship_to(filenm,sysnm)     /* send spool file filenm to system sysnm */
+char *filenm;
+char *sysnm; {
+       char sysflnm[100];
+       char commbuf1[100];
+       char commbuf2[100];
+       FILE *sysflfd;
+
+#ifdef DEBUG
+       fprintf(stdout,"netproc: shipping %s to %s\n",filenm,sysnm);
+#endif
+       sprintf(sysflnm,"./network/systems/%s",sysnm);
+       sysflfd=fopen(sysflnm,"r");
+       if (sysflfd==NULL) fprintf(stdout,"netproc: cannot open %s\n",sysflnm);
+       fgets(commbuf1,99,sysflfd);
+       commbuf1[strlen(commbuf1)-1] = 0;
+       fclose(sysflfd);
+       sprintf(commbuf2,commbuf1,filenm);
+       system(commbuf2);
+       }
+
+/*
+ * proc_file_transfer()  -  handle a simple file transfer packet
+ */
+void proc_file_transfer(tname)
+char *tname; { /* name of temp file containing the whole message */
+       char buf[128];
+       char dest_dir[32];
+       FILE *tfp,*uud;
+       int a,b;
+
+       printf("netproc: processing network file transfer...\n");
+       strcpy(dest_dir,config.c_bucket_dir);
+
+       tfp=fopen(tname,"rb");
+       if (tfp==NULL) printf("netproc: cannot open %s\n",tname);
+       getc(tfp); getc(tfp); getc(tfp);
+       do {
+               a=getc(tfp);
+               if (a!='M') {
+                       fpgetfield(tfp,buf);
+                       if (a=='O') for (b=0; b<MAXROOMS; ++b) {
+                               if (!strucmp(buf,roomnames[b]))
+                                       strcpy(dest_dir,roomdirs[b]);
+                               }
+                       }
+               } while ((a!='M')&&(a>=0));
+       if (a!='M') {
+               fclose(tfp);
+               printf("netproc: no message text for file transfer\n");
+               return;
+               }
+
+       sprintf(buf,"cd %s/files/%s; exec %s",bbs_home_directory,dest_dir,UUDECODE);
+       uud=(FILE *)popen(buf,"w");
+       if (uud==NULL) {
+               printf("netproc: cannot open uudecode pipe\n");
+               fclose(tfp);
+               return;
+               }
+
+       fgets(buf,128,tfp);
+       buf[strlen(buf)-1] = 0;
+       for (a=0; a<strlen(buf); ++a) if (buf[a]=='/') buf[a]='_';
+       fprintf(uud,"%s\n",buf);
+       printf("netproc: %s\n",buf);
+       while(a=getc(tfp), a>0) putc(a,uud);
+       fclose(tfp);
+       pclose(uud);
+       return;
+       }
+
+
+/* send a bounce message */
+void bounce(bminfo)
+struct minfo *bminfo; {
+
+       FILE *bounce;
+       char bfilename[64];
+       static int bseq = 1;
+       long now;
+
+       sprintf(bfilename,"./network/spoolin/bounce.%d.%d",getpid(),bseq++);
+       bounce = fopen(bfilename,"wb");
+       time(&now);
+               
+       fprintf(bounce,"%c%c%c",0xFF,MES_NORMAL,0);
+       fprintf(bounce,"Ppostmaster%c",0);
+       fprintf(bounce,"T%ld%c",now,0);
+       fprintf(bounce,"APostmaster%c",0);
+       fprintf(bounce,"OMail%c",0);
+       fprintf(bounce,"N%s%c",config.c_nodename,0);
+       fprintf(bounce,"H%s%c",config.c_humannode,0);
+
+       if (strlen(bminfo->E) > 0) {
+               fprintf(bounce,"R%s%c",bminfo->E,0);
+               }
+       else {
+               fprintf(bounce,"R%s%c",bminfo->A,0);
+               }
+
+       fprintf(bounce,"D%s%c",bminfo->N,0);
+       fprintf(bounce,"M%s could not deliver your mail to:\n",
+               config.c_humannode);
+       fprintf(bounce," \n %s\n \n",bminfo->R);
+       fprintf(bounce," because there is no such user on this system.\n");
+       fprintf(bounce," (Unsent message does *not* follow.  ");
+       fprintf(bounce,"Help to conserve bandwidth.)\n%c",0);
+       fclose(bounce);
+       }
+
+
+
+/*
+ * process incoming files in ./network/spoolin
+ */
+void inprocess() {
+       FILE *fp,*message,*testfp,*ls;
+       static struct minfo minfo;
+       struct recentmsg recentmsg;
+       char tname[128],aaa[1024],iname[256],sfilename[256],pfilename[256];
+       int a,b;
+       struct syslist *stemp;
+       char *ptr = NULL;
+       char buf[256];
+       long msglen;
+       int bloklen;
+
+       sprintf(tname,"/tmp/net.t%d",getpid()); /* temp file name */
+       sprintf(iname,"/tmp/net.i%d",getpid()); /* temp file name */
+
+       load_filterlist();
+
+       chdir(bbs_home_directory);
+       
+       /* Let the shell do the dirty work. Get all data from spoolin */
+    do {
+       sprintf(aaa,"cd %s/network/spoolin; ls",bbs_home_directory);
+       ls=popen(aaa,"r");
+       if (ls==NULL) {
+               fprintf(stderr,"netproc: could not open dir cmd: %s\n",
+                       strerror(errno));
+               }
+       if (ls!=NULL) {
+               do {
+                       ptr=fgets(sfilename,256,ls);
+                       if (ptr!=NULL) sfilename[strlen(sfilename)-1] = 0;
+                       } while( (ptr!=NULL)&&((!strcmp(sfilename,"."))
+                                ||(!strcmp(sfilename,".."))));
+               if (ptr!=NULL) printf("netproc: processing %s\n",sfilename);
+               pclose(ls);
+               }
+
+      if (ptr!=NULL) {
+       sprintf(pfilename,"%s/network/spoolin/%s",bbs_home_directory,sfilename);
+       fprintf(stderr,"netproc: processing <%s>\n", pfilename);
+       fflush(stderr);
+       
+       fp=fopen(pfilename,"r");
+       if(fp == NULL) {
+           fprintf(stderr, "netproc: cannot open <%s>: %s\n",
+                       pfilename,strerror(errno));
+           fflush(stderr);
+           fp = fopen("/dev/null","r");
+           }
+               
+NXMSG: /* Seek to the beginning of the next message */
+       do {
+               a=getc(fp);
+               } while((a!=255)&&(a>=0));
+       if (a<0) goto ENDSTR;
+       message=fopen(tname,"wb");
+       putc(255,message);
+       do {
+               do {
+                       a=getc(fp);
+                       putc(a,message);
+                       } while(a>0);
+               a=getc(fp);
+               putc(a,message);
+               } while ((a!='M') && (a>0));
+       do {
+               a=getc(fp);
+               putc(a,message);
+               } while(a>0);
+       msglen = ftell(fp);
+       fclose(message);
+
+       /* process the individual mesage */
+       minfo.D[0]=0;
+       minfo.C[0]=0;
+       minfo.B[0]=0;
+       minfo.G[0]=0;
+       minfo.R[0]=0;
+       msgfind(tname,&minfo);
+       strncpy(recentmsg.RMnodename,minfo.N,9);
+       recentmsg.RMnodename[9]=0;
+       recentmsg.RMnum=minfo.I;
+       printf("netproc: #%ld fm <%s> in <%s> @ <%s>\n",
+               minfo.I,minfo.A,minfo.O,minfo.N);
+       if (strlen(minfo.R)>0) {
+               printf("         to <%s>",minfo.R);
+               if (strlen(minfo.D)>0) {
+                       printf(" @ <%s>",minfo.D);
+                       }
+               printf("\n");
+               }
+       fflush(stdout);
+       if (!strucmp(minfo.D,FQDN)) strcpy(minfo.D,NODENAME);
+
+       /* this routine updates our info on the system that sent the message */
+       stemp = get_sys_ptr(minfo.N);
+       if ((stemp == NULL) && (get_sys_ptr(minfo.nexthop) != NULL)) {
+               /* add non-neighbor system to map */
+               printf("Adding non-neighbor system <%s> to map\n", slist->s_name);
+               stemp = (struct syslist *)malloc((long)sizeof(struct syslist));
+               stemp->next = slist;
+               slist = stemp;
+               strcpy(slist->s_name,minfo.N);
+               strcpy(slist->s_type,"use");
+               strcpy(slist->s_nexthop,minfo.nexthop);
+               time(&slist->s_lastcontact);
+               }
+       else if ((stemp == NULL) && (!strucmp(minfo.N,minfo.nexthop))) {
+               /* add neighbor system to map */
+               printf("Adding neighbor system <%s> to map\n", slist->s_name);
+               sprintf(aaa,"%s/network/systems/%s",bbs_home_directory,minfo.N);
+               testfp=fopen(aaa,"r");
+               if (testfp!=NULL) {
+                       fclose(testfp);
+                       stemp = (struct syslist *)
+                               malloc((long)sizeof(struct syslist));
+                       stemp->next = slist;
+                       slist = stemp;
+                       strcpy(slist->s_name,minfo.N);
+                       strcpy(slist->s_type,"bin");
+                       strcpy(slist->s_nexthop,"Mail");
+                       time(&slist->s_lastcontact);
+                       }
+               }
+       /* now update last contact and long node name if we can */
+       if (stemp!=NULL) {
+               time(&stemp->s_lastcontact);
+               if (strlen(minfo.H) > 0) strcpy(stemp->s_humannode,minfo.H);
+               if (strlen(minfo.B) > 0) strcpy(stemp->s_phonenum,minfo.B);
+               if (strlen(minfo.G) > 0) strcpy(stemp->s_gdom,minfo.G);
+               }
+
+       /* route the message if necessary */
+       if ((strucmp(minfo.D,NODENAME))&&(minfo.D[0]!=0)) { 
+               a = get_sysinfo_type(minfo.D);
+               printf("netproc: routing message to system <%s>\n",minfo.D);
+               fflush(stdout);
+               if (a==M_INTERNET) {
+                       if (fork()==0) {
+                               printf("netproc: netmailer %s\n", tname);
+                               fflush(stdout);
+                               execlp("./netmailer","netmailer",
+                                       tname,NULL);
+                               printf("netproc: error running netmailer: %s\n",
+                                       strerror(errno));
+                               fflush(stdout);
+                               exit(errno);
+                               }
+                       else while (wait(&b)!=(-1));
+                       }
+               else if (a==M_BINARY) {
+                       ship_to(tname,minfo.D);
+                       }
+               else {
+                       /* message falls into the bit bucket? */
+                       }
+               }
+
+       /* check to see if it's a file transfer */
+       else if (!struncmp(minfo.S,"FILE",4)) {
+               proc_file_transfer(tname);
+               }
+
+       /* otherwise process it as a normal message */
+       else {
+
+               if (!strucmp(minfo.R, "postmaster")) {
+                       strcpy(minfo.R, "");
+                       strcpy(minfo.C, "Aide");
+                       }
+
+               if (strlen(minfo.R) > 0) {
+                       sprintf(buf,"GOTO _MAIL_");
+                       }
+               if (is_banned(minfo.A,minfo.C,minfo.N)) {
+                       sprintf(buf,"GOTO %s", FILTERROOM);
+                       }
+               else {
+                       if (strlen(minfo.C) > 0) {
+                               sprintf(buf,"GOTO %s", minfo.C);
+                               }
+                       else {
+                               sprintf(buf,"GOTO %s", minfo.O);
+                               }
+                       }
+               serv_puts(buf);
+               serv_gets(buf);
+               if (buf[0] != '2') {
+                       puts(buf); fflush(stdout);
+                       sprintf(buf,"GOTO _BITBUCKET_");
+                       serv_puts(buf);
+                       serv_gets(buf);
+                       }
+
+               /* Open the temporary file containing the message */
+               message = fopen(tname, "rb");
+               if (message == NULL) {
+                       fprintf(stderr, "netproc: cannot open %s: %s\n",
+                               tname, strerror(errno));
+                       unlink(tname);
+                       goto NXMSG;
+                       }
+
+               /* Measure the message */
+               fseek(message, 0L, 2);
+               msglen = ftell(fp);
+               fseek(message, 0L, 0);
+
+               /* Transmit the message to the server */
+               sprintf(buf, "ENT3 1|%s|%ld", minfo.R, msglen);
+               printf("< %s\n", buf);
+               serv_puts(buf);
+               serv_gets(buf);
+               printf("> %s\n", buf);
+               if (!strncmp(buf, "570", 3)) {
+                       /* no such user, do a bounce */
+                       bounce(&minfo);
+                       }
+               if (buf[0] == '7') {
+                       /* Always use the server's idea of the message length,
+                        * even though they should both be identical */
+                       msglen = atol(&buf[4]);
+                       while (msglen > 0L) {
+                               bloklen = ((msglen >= 255L) ? 255 : ((int)msglen));
+                               if (fread(buf, bloklen, 1, message) < 1) {
+                                       fprintf(stderr, "netproc: error trying to read %d bytes: %s\n",
+                                               bloklen, strerror(errno));
+                                       fflush(stderr);
+                                       }
+                               serv_write(buf, bloklen);
+                               msglen = msglen - (long)bloklen;
+                               }
+                       serv_puts("NOOP");
+                       serv_gets(buf);
+                       }
+               else {
+                       puts(buf);
+                       fflush(stdout); 
+                       }
+
+               fclose(message);
+               }
+
+       unlink(tname);
+       goto NXMSG;
+
+ENDSTR:        fclose(fp);
+       unlink(pfilename);
+       }
+      } while(ptr!=NULL);
+    unlink(iname);
+    }
+
+
+int checkpath(path,sys)        /* Checks to see whether its ok to send */
+char path[];           /* Returns 1 for ok, send message       */
+char sys[]; {          /* Returns 0 if message already there   */
+       int a;
+       char sys2[512];
+       strcpy(sys2,sys);
+       strcat(sys2,"!");
+
+#ifdef DEBUG
+       printf("netproc: checkpath <%s> <%s> ... ",path,sys);
+#endif
+       for (a=0; a<strlen(path); ++a) {
+               if (!strncmp(&path[a],sys2,strlen(sys2))) return(0);
+               }
+       return(1);
+       }
+
+/*
+ * implement split horizon algorithm
+ */
+int ismsgok(mpos,mmfp,sysname)
+long mpos;
+FILE *mmfp;
+char *sysname; {
+       int a;
+       int ok = 0;             /* fail safe - no path, don't send it */
+       char fbuf[256];
+
+       fseek(mmfp,mpos,0);
+       if (getc(mmfp)!=255) return(0);
+       getc(mmfp); getc(mmfp);
+
+       while (a=getc(mmfp),((a!='M')&&(a!=0))) {
+               fpgetfield(mmfp,fbuf);
+               if (a=='P') {
+                       ok = checkpath(fbuf,sysname);
+                       }
+               }
+#ifdef DEBUG
+       printf("%s\n", ((ok)?"SEND":"(no)") );
+#endif
+       return(ok);
+       }
+
+int spool_out(cmlist,destfp,sysname)   /* spool list of messages to a file */
+struct msglist *cmlist;                        /* returns # of msgs spooled */
+FILE *destfp; 
+char *sysname;
+{
+       struct msglist *cmptr;
+       FILE *mmfp;
+       char mmtemp[128];
+       char fbuf[128];
+       int a;
+       int msgs_spooled = 0;
+       long msg_len;
+       int blok_len;
+
+       char buf[256];
+       char curr_rm[256];
+
+       strcpy(curr_rm, "");
+       sprintf(mmtemp, "/tmp/net.m%d", getpid());
+
+       /* for each message in the list... */
+       for (cmptr=cmlist; cmptr!=NULL; cmptr=cmptr->next) {
+
+               /* make sure we're in the correct room... */
+               if (strucmp(curr_rm, cmptr->m_rmname)) {
+                       sprintf(buf, "GOTO %s", cmptr->m_rmname);
+                       serv_puts(buf);
+                       serv_gets(buf);
+                       if (buf[0] == '2') {
+                               strcpy(curr_rm, cmptr->m_rmname);
+                               }
+                       else {
+                               fprintf(stderr,"%s\n", buf);
+                               }
+                       }
+
+               /* download the message from the server... */
+               mmfp = fopen(mmtemp, "wb");
+               sprintf(buf, "MSG3 %ld", cmptr->m_num);
+               serv_puts(buf);
+               serv_gets(buf);
+               if (buf[0]=='6') {                      /* read the msg */
+                       msg_len = atol(&buf[4]);
+                       while (msg_len > 0L) {
+                               blok_len = ((msg_len >= 256L) ? 256 : (int)msg_len);
+                               serv_read(buf, blok_len);
+                               fwrite(buf, blok_len, 1, mmfp);
+                               msg_len = msg_len - (long)blok_len;
+                               }
+                       }
+               else {                                  /* or print the err */
+                       fprintf(stderr, "%s\n", buf);
+                       }
+               fclose(mmfp);
+       
+               mmfp = fopen(mmtemp,"rb");
+
+               if (ismsgok(0L,mmfp,sysname)) {
+                       ++msgs_spooled;
+                       fflush(stdout);
+                       fseek(mmfp,0L,0);
+                       fread(fbuf,3,1,mmfp);
+                       fwrite(fbuf,3,1,destfp);
+                       while (a=getc(mmfp),((a!=0)&&(a!='M'))) {
+                               if (a!='C') putc(a,destfp);
+                               fpgetfield(mmfp,fbuf);
+                               if (a=='P') fprintf(destfp,"%s!",NODENAME);
+                               if (a!='C')
+                                       fwrite(fbuf,strlen(fbuf)+1,1,destfp);
+                               }
+                       if (a=='M') {
+                               fprintf(destfp, "C%s%c",
+                                       cmptr->m_rmname, 0);
+                               putc('M',destfp);
+                               do {
+                                       a=getc(mmfp);
+                                       putc(a,destfp);
+                                       } while(a>0);
+                               }
+                       }
+               fclose(mmfp);
+               }
+
+       unlink(mmtemp);
+       return(msgs_spooled);
+       }
+
+void outprocess(sysname) /* send new room messages to sysname */
+char *sysname; {
+       char sysflnm[64];
+       char srmname[32];
+       char shiptocmd[128];
+       char lbuf[64];
+       char tempflnm[64];
+       char buf[256];
+       struct msglist *cmlist = NULL;
+       struct rmlist *crmlist = NULL;
+       struct rmlist *rmptr,*rmptr2;
+       struct msglist *cmptr,*cmptr2;
+       FILE *sysflfp,*tempflfp;
+       int outgoing_msgs;
+       long thismsg;
+
+       sprintf(tempflnm,"/tmp/%s.%d",NODENAME,getpid());
+       tempflfp=fopen(tempflnm,"w");
+       if (tempflfp==NULL) return;
+
+
+/*
+ * Read system file for node in question and put together room list
+ */
+       sprintf(sysflnm,"%s/network/systems/%s",bbs_home_directory,sysname);
+       sysflfp=fopen(sysflnm,"r");
+       if (sysflfp==NULL) return;
+       fgets(shiptocmd,128,sysflfp); shiptocmd[strlen(shiptocmd)-1]=0;
+       while(!feof(sysflfp)) {
+               if (fgets(srmname,32,sysflfp)==NULL) break;
+               srmname[strlen(srmname)-1]=0;
+               fgets(lbuf,32,sysflfp);
+               rmptr=(struct rmlist *)malloc(sizeof(struct rmlist));
+               rmptr->next = NULL;
+               strcpy(rmptr->rm_name,srmname);
+               strip_trailing_whitespace(rmptr->rm_name);
+               rmptr->rm_lastsent = atol(lbuf);
+               if (crmlist==NULL) crmlist=rmptr;
+               else if (!strucmp(rmptr->rm_name,"control")) {
+                       /* control has to be first in room list */
+                       rmptr->next = crmlist;
+                       crmlist = rmptr;
+                       }
+               else {
+                       rmptr2=crmlist;
+                       while (rmptr2->next != NULL) rmptr2=rmptr2->next;
+                       rmptr2->next=rmptr;
+                       }
+               }
+       fclose(sysflfp);
+
+/*
+ * Assemble list of messages to be spooled
+ */
+       for (rmptr=crmlist; rmptr!=NULL; rmptr=rmptr->next) {
+
+               sprintf(buf,"GOTO %s",rmptr->rm_name);
+               serv_puts(buf);
+               serv_gets(buf);
+               if (buf[0]!='2') {
+                       fprintf(stderr, "%s\n", buf);
+                       }
+               else {
+                       sprintf(buf, "MSGS GT|%ld", rmptr->rm_lastsent);
+                       serv_puts(buf);
+                       serv_gets(buf);
+                       if (buf[0]=='1') while (serv_gets(buf), strcmp(buf,"000")) {
+                               thismsg = atol(buf);
+                               if ( thismsg > (rmptr->rm_lastsent) ) {
+                                       rmptr->rm_lastsent = thismsg;
+                                       
+                               cmptr=(struct msglist *)
+                                     malloc(sizeof(struct msglist));
+                                       cmptr->next = NULL;
+                                       cmptr->m_num = thismsg;
+                                       strcpy(cmptr->m_rmname, rmptr->rm_name);
+       
+                                       if (cmlist == NULL) cmlist = cmptr;
+                                       else {
+                                               cmptr2 = cmlist;
+                                               while (cmptr2->next != NULL)
+                                               cmptr2 = cmptr2->next;
+                                               cmptr2->next = cmptr;
+                                               }
+                                       }
+                               }
+                       else {          /* print error from "msgs all" */
+                               fprintf(stderr, "%s\n", buf);
+                               }
+                       }
+               }
+
+       outgoing_msgs=0; cmptr2=cmlist; /* this loop counts the messages */
+       while (cmptr2!=NULL) {
+               ++outgoing_msgs;
+               cmptr2 = cmptr2->next;
+               }
+       printf("netproc: %d messages to be spooled to %s\n",
+               outgoing_msgs,sysname);
+       fflush(stdout);
+
+/*
+ * Spool out the messages, but only if there are any.
+ */
+       fflush(stdout);
+       if (outgoing_msgs!=0) outgoing_msgs=spool_out(cmlist,tempflfp,sysname);
+       printf("netproc: %d messages actually spooled\n",
+               outgoing_msgs);
+       fflush(stdout);
+
+/*
+ * Deallocate list of spooled messages.
+ */
+       while(cmlist!=NULL) {
+               cmptr=cmlist->next;
+               free(cmlist);
+               cmlist=cmptr;
+               }
+
+/*
+ * Rewrite system file and deallocate room list.
+ */
+       printf("Spooling...\n");
+       fflush(stdout);
+       sysflfp=fopen(sysflnm,"w");
+       fprintf(sysflfp,"%s\n",shiptocmd);
+       for (rmptr=crmlist; rmptr!=NULL; rmptr=rmptr->next)  
+               fprintf(sysflfp,"%s\n%ld\n",rmptr->rm_name,rmptr->rm_lastsent);
+       fclose(sysflfp);
+       while(crmlist!=NULL) {
+               rmptr=crmlist->next;
+               free(crmlist);
+               crmlist=rmptr;
+               }
+
+/* 
+ * Close temporary file, ship it out, and return
+ */
+       fclose(tempflfp);
+       if (outgoing_msgs!=0) ship_to(tempflnm,sysname);
+       unlink(tempflnm);
+       }
+
+
+/*
+ * Connect netproc to the Citadel server running on this computer.
+ */
+void np_attach_to_server() {
+       char buf[256];
+       char portname[8];
+       char *args[] = { "netproc", "localhost", NULL, NULL } ;
+
+       printf("Attaching to server...\n");
+       sprintf(portname, "%d", config.c_port_number);
+       args[2] = portname;
+       attach_to_server(3, args);
+       serv_gets(buf);
+       printf("%s\n",&buf[4]);
+       sprintf(buf,"IPGM %d", config.c_ipgm_secret);
+       serv_puts(buf);
+       serv_gets(buf);
+       printf("%s\n",&buf[4]);
+       if (buf[0]!='2') {
+               cleanup(2);
+               }
+       }
+
+
+
+/*
+ * main
+ */
+void main(argc,argv)
+int argc;
+char *argv[];
+{
+       char allst[32];
+       FILE *allfp;
+       int a;
+
+
+       strcpy(bbs_home_directory, BBSDIR);
+
+       /*
+        * Change directories if specified
+        */
+       if (argv > 0) for (a=1; a<argc; ++a) {
+               if (!strncmp(argv[a], "-h", 2)) {
+                       strcpy(bbs_home_directory, argv[a]);
+                       strcpy(bbs_home_directory, &bbs_home_directory[2]);
+                       home_specified = 1;
+                       }
+               else {
+                       fprintf(stderr, "netproc: usage: netproc [-hHomeDir]\n");
+                       exit(1);
+                       }
+               }
+
+       get_config();
+
+       /* write all messages to the log from this point onward */
+       freopen(NPLOGFILE,NPLOGMODE,stdout);
+       freopen(NPLOGFILE,NPLOGMODE,stderr);
+
+       if (set_lockfile()!=0) {
+               fprintf(stderr,"netproc: lock file exists: already running\n");
+               cleanup(1);
+               }
+
+       signal(SIGINT,cleanup);
+       signal(SIGQUIT,cleanup);
+       signal(SIGHUP,cleanup);
+       signal(SIGTERM,cleanup);
+
+       printf("netproc: started.  pid=%d\n",getpid());
+       fflush(stdout);
+       np_attach_to_server();
+       fflush(stdout);
+
+       if (load_roomnames()!=0) fprintf(stdout,"netproc: cannot load rooms\n");
+       if (load_syslist()!=0) fprintf(stdout,"netproc: cannot load sysinfo\n");
+       setup_special_nodes();
+
+       inprocess();    /* first collect incoming stuff */
+
+       allfp=(FILE *)popen("cd ./network/systems; ls","r");
+       if (allfp!=NULL) {
+               while (fgets(allst,32,allfp)!=NULL) {
+                       allst[strlen(allst)-1] = 0;
+                       outprocess(allst);
+                       }
+               pclose(allfp);
+               }
+
+       inprocess();    /* incoming again in case anything new was generated */
+       rewrite_syslist();
+       printf("netproc: processing ended.\n");
+       cleanup(0);
+       }
+
diff --git a/citadel/netsetup.c b/citadel/netsetup.c
new file mode 100644 (file)
index 0000000..e64c9d0
--- /dev/null
@@ -0,0 +1,452 @@
+/*
+ * netsetup.c
+ *
+ * Copyright (c) 1998  Art Cancro
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include "citadel.h"
+
+struct roomshare {
+       struct roomshare *next;
+       char rs_name[30];
+       long rs_lastsent;
+       };
+
+struct netnode {
+       char nn_nodename[32];
+       char nn_spoolcmd[256];
+       struct roomshare *nn_first;
+       };
+
+
+void get_config();
+struct config config;
+
+/*
+ * struncmp()  -  case-insensitive version of strncmp()
+ *                citadel.h will #define a strucmp() based on this
+ */
+int struncmp(lstr,rstr,len)
+char lstr[],rstr[];
+int len; {
+       int pos = 0;
+       char lc,rc;
+       while (pos<len) {
+               lc=tolower(lstr[pos]);
+               rc=tolower(rstr[pos]);
+               if ((lc==0)&&(rc==0)) return(0);
+               if (lc<rc) return(-1);
+               if (lc>rc) return(1);
+               pos=pos+1;
+               }
+       return(0);
+       }
+
+
+
+
+struct netnode *load_node(nodename)
+char *nodename; {
+       FILE *fp;
+       char buf[256];
+       char filename[256];
+       struct netnode *newnn;
+       struct roomshare *newrs;
+
+       sprintf(filename, "./network/systems/%s", nodename);
+       fp = fopen(filename, "r");
+       if (fp == NULL) {
+               return NULL;
+               }
+
+       newnn = (struct netnode *) malloc(sizeof(struct netnode));
+       strcpy(newnn->nn_nodename, nodename);
+       newnn->nn_first = NULL;
+
+       fgets(buf, 255, fp);
+       buf[strlen(buf)-1] = 0;
+       strcpy(newnn->nn_spoolcmd, buf);
+
+       while (fgets(buf, 255, fp) != NULL) {
+               newrs = (struct roomshare *) malloc(sizeof(struct roomshare));
+               newrs->next = newnn->nn_first;
+               newnn->nn_first = newrs;
+               buf[strlen(buf)-1] = 0;
+               strcpy(newrs->rs_name, buf);
+               fgets(buf, 255, fp);
+               buf[strlen(buf)-1] = 0;
+               newrs->rs_lastsent = atol(buf);
+               }
+
+       fclose(fp);
+       return(newnn);
+       }
+
+
+
+void save_node(nnptr)
+struct netnode *nnptr; {
+
+       FILE *fp;
+       char filename[256];
+       struct roomshare *rsptr = NULL;
+       
+       sprintf(filename, "./network/systems/%s", nnptr->nn_nodename);
+       fp = fopen(filename, "w");
+       if (fp == NULL) {
+               fprintf(stderr, "%s\n", strerror(errno));
+               return;
+               }
+       fprintf(fp, "%s\n", nnptr->nn_spoolcmd);
+       while (nnptr->nn_first != NULL) {
+               fprintf(fp, "%s\n%ld\n", nnptr->nn_first->rs_name,
+                                       nnptr->nn_first->rs_lastsent);
+               rsptr = nnptr->nn_first->next;
+               free(nnptr->nn_first);
+               nnptr->nn_first = rsptr;
+               }
+       fclose(fp);
+       free(rsptr);
+       }
+
+
+
+void display_usage() {
+       fprintf(stderr, "netsetup for %s\n", CITADEL);
+       fprintf(stderr, "usage: netsetup <command> [arguments]\n\n");
+       fprintf(stderr, "Commands: \n");
+       fprintf(stderr, "   nodelist                  (Lists all neighboring nodes\n");
+       fprintf(stderr, "   addnode [name]            (Adds a new node to the list)\n");
+       fprintf(stderr, "   deletenode [name]         (Deletes a node from the list)\n");
+       fprintf(stderr, "   roomlist [node]           (List rooms being shared)\n");
+       fprintf(stderr, "   getcommand [node]         (Show spool command)\n");
+       fprintf(stderr, "   setcommand [node] [cmd]   (Set spool command)\n");
+       fprintf(stderr, "   share [node] [room]       (Add a new shared room)\n");
+       fprintf(stderr, "   unshare [node] [room]     (Stop sharing a room)\n");
+       fprintf(stderr, "   help                      (Display this message)\n");
+       }
+
+
+/*
+ * Display all neighboring nodes
+ * (This is inherently nonportable)
+ */
+void display_nodelist() {
+       FILE *ls;
+       char buf[256];
+
+       ls = (FILE *) popen("cd ./network/systems; ls", "r");
+       if (ls == NULL) {
+               fprintf(stderr, "netsetup: Cannot open nodelist: %s\n",
+                       strerror(errno));
+               exit(errno);
+               }
+
+       while (fgets(buf, 255, ls) != NULL) {
+               printf("%s", buf);
+               }
+
+       pclose(ls);
+       }
+
+
+
+/*
+ */
+void add_node(NewNodeName)
+char *NewNodeName; {
+       FILE *fp;
+       char sysfilename[256];
+
+       sprintf(sysfilename, "./network/systems/%s", NewNodeName);
+
+       fp = fopen(sysfilename, "r");
+       if (fp != NULL) {
+               fclose(fp);
+               fprintf(stderr, "A node named '%s' already exists.\n",
+                       NewNodeName);
+               exit(2);
+               }
+
+       fp = fopen(sysfilename, "w");
+       if (fp == NULL) {
+               fprintf(stderr, "%s\n", strerror(errno));
+               exit(errno);
+               }
+
+       fprintf(fp, "cat %%s >>./network/spoolout/%s\n", NewNodeName);
+       fclose(fp);
+       }
+
+
+/*
+ */
+void delete_node(NodeName)
+char *NodeName; {
+       FILE *fp;
+       char sysfilename[256];
+       char spooloutfilename[256];
+
+       sprintf(sysfilename, "./network/systems/%s", NodeName);
+       sprintf(spooloutfilename, "./network/spoolout/%s", NodeName);
+
+       fp = fopen(sysfilename, "r");
+       if (fp == NULL) {
+               fprintf(stderr, "'%s' does not exist.\n",
+                       NodeName);
+               exit(3);
+               }
+       fclose(fp);
+
+       unlink(spooloutfilename);
+       if (unlink(sysfilename)==0) {
+               return;
+               }
+       fprintf(stderr, "%s\n", strerror(errno));
+       exit(errno);
+       }
+
+
+/*
+ */
+void do_roomlist(NodeName)
+char *NodeName; {
+       FILE *fp;
+       char sysfilename[256];
+       char buf[256];
+
+       sprintf(sysfilename, "./network/systems/%s", NodeName);
+
+       fp = fopen(sysfilename, "r");
+       if (fp == NULL) {
+               fprintf(stderr, "'%s' does not exist.\n",
+                       NodeName);
+               exit(3);
+               }
+
+       fgets(buf, 255, fp);    /* skip past spool cmd */
+       while (fgets(buf, 255, fp) != NULL) {
+               printf("%s", buf);
+               fgets(buf, 255, fp);    /* skip past last-sent pointer */
+               }
+
+       fclose(fp);
+       }
+
+
+
+/*
+ */
+void show_spool_cmd(NodeName)
+char *NodeName; {
+       FILE *fp;
+       char sysfilename[256];
+       char buf[256];
+
+       sprintf(sysfilename, "./network/systems/%s", NodeName);
+
+       fp = fopen(sysfilename, "r");
+       if (fp == NULL) {
+               fprintf(stderr, "'%s' does not exist.\n",
+                       NodeName);
+               exit(3);
+               }
+
+       fgets(buf, 255, fp);
+       printf("%s", buf);
+       fclose(fp);
+       }
+
+
+/*
+ */
+void set_spool_cmd(nodename, spoolcmd)
+char *nodename;
+char *spoolcmd; {
+       struct netnode *nnptr;
+
+       nnptr = load_node(nodename);
+       if (nnptr == NULL) {
+               fprintf(stderr, "No such node '%s'.\n", nodename);
+               exit(4);
+               }
+
+       strncpy(nnptr->nn_spoolcmd, spoolcmd, 255);
+       save_node(nnptr);
+       }
+
+
+/*
+ */
+void add_share(nodename, roomname)
+char *nodename;
+char *roomname; {
+       struct netnode *nnptr;
+       struct roomshare *rsptr;
+       long highest = 0L;
+       int foundit = 0;
+
+       nnptr = load_node(nodename);
+       if (nnptr == NULL) {
+               fprintf(stderr, "No such node '%s'.\n", nodename);
+               exit(4);
+               }
+
+       for (rsptr = nnptr->nn_first; rsptr != NULL; rsptr = rsptr->next) {
+               if (!strucmp(rsptr->rs_name, roomname)) {
+                       foundit = 1;
+                       }
+               if (rsptr->rs_lastsent > highest) {
+                       highest = rsptr->rs_lastsent;
+                       }
+               }
+
+       if (foundit == 0) {
+               rsptr = (struct roomshare *) malloc(sizeof(struct roomshare));
+               rsptr->next = nnptr->nn_first;
+               strcpy(rsptr->rs_name, roomname);
+               rsptr->rs_lastsent = highest;
+               nnptr->nn_first = rsptr;
+               }
+
+       save_node(nnptr);
+       }
+
+
+/*
+ */
+void remove_share(nodename, roomname)
+char *nodename;
+char *roomname; {
+       struct netnode *nnptr;
+       struct roomshare *rsptr, *rshold;
+       int foundit = 0;
+
+       nnptr = load_node(nodename);
+       if (nnptr == NULL) {
+               fprintf(stderr, "No such node '%s'.\n", nodename);
+               exit(4);
+               }
+
+       if (nnptr->nn_first != NULL)
+          if (!strucmp(nnptr->nn_first->rs_name, roomname)) {
+               rshold = nnptr->nn_first;
+               nnptr->nn_first = nnptr->nn_first->next;
+               free(rshold);
+               foundit = 1;
+               }
+
+       if (nnptr->nn_first != NULL)
+          for (rsptr = nnptr->nn_first; rsptr->next != NULL; rsptr = rsptr->next) {
+               if (!strucmp(rsptr->next->rs_name, roomname)) {
+                       rshold = rsptr->next;
+                       rsptr->next = rsptr->next->next;
+                       free(rshold);
+                       foundit = 1;
+                       rsptr = nnptr->nn_first;
+                       }
+               }
+
+       save_node(nnptr);
+
+       if (foundit == 0) {
+               fprintf(stderr, "Not sharing '%s' with %s\n",
+                       roomname, nodename);
+               exit(5);
+               }
+       }
+
+
+int main(argc, argv)
+int argc;
+char *argv[]; {
+
+       if (argc < 2) {
+               display_usage();
+               exit(1);
+               }
+
+       get_config();
+
+       if (!strcmp(argv[1], "help")) {
+               display_usage();
+               exit(0);
+               }
+
+       if (!strcmp(argv[1], "nodelist")) {
+               display_nodelist();
+               exit(0);
+               }
+
+       if (!strcmp(argv[1], "addnode")) {
+               if (argc < 3) {
+                       display_usage();
+                       exit(1);
+                       }
+               add_node(argv[2]);
+               exit(0);
+               }
+
+       if (!strcmp(argv[1], "deletenode")) {
+               if (argc < 3) {
+                       display_usage();
+                       exit(1);
+                       }
+               delete_node(argv[2]);
+               exit(0);
+               }
+
+       if (!strcmp(argv[1], "roomlist")) {
+               if (argc < 3) {
+                       display_usage();
+                       exit(1);
+                       }
+               do_roomlist(argv[2]);
+               exit(0);
+               }
+
+       if (!strcmp(argv[1], "getcommand")) {
+               if (argc < 3) {
+                       display_usage();
+                       exit(1);
+                       }
+               show_spool_cmd(argv[2]);
+               exit(0);
+               }
+
+       if (!strcmp(argv[1], "setcommand")) {
+               if (argc < 4) {
+                       display_usage();
+                       exit(1);
+                       }
+               set_spool_cmd(argv[2], argv[3]);
+               exit(0);
+               }
+
+       if (!strcmp(argv[1], "share")) {
+               if (argc < 4) {
+                       display_usage();
+                       exit(1);
+                       }
+               add_share(argv[2], argv[3]);
+               exit(0);
+               }
+
+       if (!strcmp(argv[1], "unshare")) {
+               if (argc < 4) {
+                       display_usage();
+                       exit(1);
+                       }
+               remove_share(argv[2], argv[3]);
+               exit(0);
+               }
+
+       display_usage();
+       exit(1);
+       }
diff --git a/citadel/netsetup.txt b/citadel/netsetup.txt
new file mode 100644 (file)
index 0000000..f954c8d
--- /dev/null
@@ -0,0 +1,53 @@
+              NETWORK CONFIGURATION UTILITIES FOR CITADEL/UX
+              ----------------------------------------------
+ ABSTRACT
+ --------
+ The Citadel/UX system now comes with two utilities for managing room-sharing
+with other systems: 'netsetup' (a command line utility) and 'dnetsetup' (a
+curses-based front end to netsetup).  Read on for more detail...
+  
+  
+ THE NETSETUP PROGRAM
+ --------------------
+ 'netsetup' is a program which eliminates the need to edit the files in your
+network/systems directory in order to maintain the sharing of rooms on a
+network.  It allows you to make all changes from the command line.  While the
+program is quite usable and straightforward this way, the real objective is
+for it to be controlled by any of several user-friendly front ends (such as
+the 'dnetsetup' program, described below).  The usage of 'netsetup' is as
+follows:
+  
+ netsetup: usage: netsetup <command> [arguments]
+ Commands: 
+   nodelist                  (Lists all neighboring nodes)
+   addnode [name]            (Adds a new node to the list)
+   deletenode [name]         (Deletes a node from the list)
+   roomlist [node]           (List rooms being shared)
+   getcommand [node]         (Show spool command)
+   setcommand [node] [cmd]   (Set spool command)
+   share [node] [room]       (Add a new shared room)
+   unshare [node] [room]     (Stop sharing a room)
+   help                      (Display this message)
+  
+  The usage of each command should be quite straightforward from the
+descriptions listed here.
+ THE DNETSETUP PROGRAM
+ ---------------------
+ dnetsetup is a more user-friendly front end to netsetup.  It's written in
+shell-script and requires no compiling.  Simply type 'dnetsetup' at the
+command line and follow the menus to create, edit, and delete neighboring
+network nodes, and to share and unshare rooms.
+ FEEDBACK
+ --------
+  
+ By now you already know where I hang out.  :)  You can find me at
+UNCENSORED! BBS at uncnsrd.mt-kisco.ny.us (telnet, www, citadel-client, etc.)
diff --git a/citadel/network.txt b/citadel/network.txt
new file mode 100644 (file)
index 0000000..74532e8
--- /dev/null
@@ -0,0 +1,265 @@
+                          Citadel/UX Network Manual
+                 See copyright.doc for copyright information
+  
+     
+  OVERVIEW
+   
+   The fundamental structure of the networker is fairly simple, however, it
+has enough features to make it a bit complicated. This is probably the most
+difficult part of the entire Citadel/UX package. So before we dive in head
+first, let's look at the various network files and directories.
+  
+ netproc.c                    Does all of the actual network processing.
+ rcit.c                       Feeds standard input into the networker, also
+                              has the ability to translate UseNet news format
+                              into Citadel/UX binary format.
+ netmailer.c                  Called by the main program when a user sends a
+                              network mail message.
+ citmail.c                    A patch to allow Citadel/UX users to receive
+                              mail through the normal UUCP mail facility.
+ cux2ascii.c                  A filter which translates Citadel/UX binary
+                              format to UseNet news format.
+ network                      Directory in which all network files reside.
+ network/systems              Contains network info for each neighboring system
+ network/systems/sysname      Network file for a node called "sysname".
+ network/mail.aliases         Aliases for the mailer.
+ network/rnews.xref           Cross-references room names to newsgroup names.
+ network/mail.sysinfo         Contains routing information for network mail.
+ network/filterlist           The "kill file" for your system.
+   
+  
+  SETUP
+  
+  There are three options in setup which must be properly set. The
+first is NODENAME, which must be the same as your uucp system name. The second
+is HUMANNODE, which is the "long" full name of your system (for documentation).
+The third is FQDN, which is your system's fully qualified domain name.  If
+you don't have a domain, just set this value to your NODENAME followed by the
+string ".UUCP" (a traditional convention, even if you're not on a UUCP net).
+     
+   SETTING UP SYSTEMS FILES
+   
+   For each of your neighboring Citadel/UX systems you must create a systems
+file. The file is called network/systems/sysname, where sysname is the other
+system's node name. The first line contains a command that transfers a spool
+file to the network/spoolin directory on the remote system. The string "%s"
+will be replaced by the name of the spool file by netproc. You may only use
+%s ONCE in the command line. Usually, some sort of UUCP transfer will be used
+to do the transfer, but you may use any facility you want, *** as long as the
+file ends up in the network/spoolin directory on the remote system ***. In
+a typical system, you will probably use uux to pipe the file through the
+command "rcit -c" on the remote system. When the remote system receives this
+command, it will copy standard input to the network/spoolin directory, and
+then begin processing the file as soon as it is there.
+   After the command line you should enter the names of all the rooms you
+intend to share with this system. Each room name should be followed by a
+line containing a zero - this extra field is the "last message sent" (which
+will be updated by netproc when it is run). Here is a sample systems file for
+a node called uncnsrd:
+  
+cat %s |uux - uncnsrd!rcit -c
+Network Test
+0
+Gateway
+0
+The Room
+0
+   
+  The rooms "Network Test", "Gateway", and "The Room" will be spooled to
+the remote system. These rooms should be designated as network rooms with
+the .<A>ide <E>ditRoom command.
+   
+  
+  USING NETPROC
+  
+   Calling netproc with no arguments simply looks in the network/spoolin
+directory for newly arrived messages, and posts them if it finds any.
+   To batch new messages and send them off to a remote system, the usage
+  
+ netproc sysname
+  
+ will do outbound network processing for system "sysname". It is recommended
+that you use the cron program to handle your network processing on a routine
+basis automatically.  Arrange with your netneighbors for both of your systems
+to batch new messages before actual polls take place, to guarantee that
+messages travel across the network as quickly as possible.
+   
+   
+  NETWORKING WITH A USENET NEWS SITE
+   
+   Two filters are provided that will allow a room to be equivalent to a
+UseNet newsgroup. rcit, when called without the -c argument, will assume
+that standard input is in the news format, and convert it before processing.
+Likewise, the filter cux2ascii.c converts Citadel format to news format,
+allowing you to use command lines such as
+   
+ cat %s |cux2ascii |uux - uunet!rnews
+   
+  ...the remote system need not be a Citadel/UX. By default, room names are
+the same as the newsgroup names. However, if you wish the room name to be
+different, you may specify so in the network/rnews.xref file. Here is a
+sample of this file:
+       
+comp.unix.wizards,UNIX wizards
+alt.drugs,Drugs and Narcotics
+alt.music,Music
+   
+  It is rather simple, each line taking the form of newsgroupname,sysname.
+There may not be any spaces in the newsgroup name, but there may be spaces
+in the corresponding room name.
+  USING CITADEL/UX AS YOUR LOCAL E-MAIL SYSTEM
+   
+   To use Citadel/UX as your local mail system, simply define the "citmail"
+program as your *local* mail delivery agent.  You can plug citmail into any
+popular mail routing system, including sendmail, smail, MMDF, etc.
+
+   Once you are using citmail as your local mail delivery agent, all users
+on your BBS may receive mail.  They can use addresses like
+"my_user_name@yoursite.com" (note the spaces in the user's name are replaced
+by underscores) or "cit1234@yoursite.com" (where 1234 is the user's user
+number).
+  
+   Messages may be posted by mail if you have this program installed.  Simply
+use the prefix "room_" -- for example, a message addressed to
+"room_hot_pink_amoebas@uncnsrd.mt-kisco.ny.us would be posted in the room "Hot
+Pink Amoebas" at the target system.
+   PLEASE NOTE that for your BBS users to be able to send mail, you should
+check the mailer command at the top of "netmailer.c" to be sure that it is
+the correct mailer command for your system.  You might need a command like
+"sendmail %s" for SMTP or "rmail %s" for UUCP.
+   
+     
+  MAIL ALIASES
+   
+   The file network/mail.aliases is a simple list of aliases for the various
+mailers to use. Each line takes the form
+  
+alias,name
+  
+   Obviously, neither the alias nor the name can contain commas. The name
+may also be the system name "sysop", where messages sent to sysop will
+be posted in the Aide> room. 
+   
+   
+  CITADEL/UX NETWORK MAIL
+   
+   Citadel/UX has the ability to transport mail in a simple and
+transparent fashion not unlike the way public messages are sent. Users may
+enter recipient names exactly as they appear on top of messages (i.e.,
+user name @ system name). In addition, mail routing is provided, allowing
+users to send mail to systems which do not directly connect with their own.
+   
+  When entering a message in the Mail> room, a user may type a recipient
+name on the local system, or on a remote system. If the recipient is not
+local, citadel.c calls netmailer.c, which is a standalone program that handles
+network mail.  This runs in a multithreaded mode, allowing netmailer to run in
+the background while the user goes on to do something else.
+  INTELLIGENT NETWORK PROCESSING AND THE MAIL.SYSINFO FILE
+  There is (or soon will be) a file in your network directory called
+"mail.sysinfo".  In earlier releases of the network software, the system
+administrator had to manually configure this file.  Starting with netproc
+version 2.1, the system should now create and configure the file automatically.
+Note that all information may not appear in the file immediately.  When a
+message arrives from a system on the network, your system will attempt to
+add that system to its network map.  If the originating system is one of your
+netneighbors, it will look for a systems file in the network/systems directory
+to determine whether it is a valid neighbor.  If the originating system is
+not a neighbor, but the message arrived via a valid neighbor, your map will
+be updated accordingly, with an entry for the new system showing the next hop
+to get there.
+  So, under normal circumstances you shouldn't have to configure this file at
+all.  But if you need to do something special, or if for some reason netproc
+detects the topology wrong, here's how to configure mail.sysinfo.  There
+are three types of entries in this file. A "use" entry tells the system which
+neighbor to route a message through to get to a particular non-neighboring
+system. A "bin" entry tells the system that a particular neighbor supports
+net mail. If there are systems that either do not have the netmailer or are
+not running Citadel/UX, but can be reached by regular electronic mail, you
+can use the "uum" command. Type "uum" followed by an address (for the user
+name, use a %s which will be replaced by the user name at the remote
+system. Here is a sample network map, where our system is called "myself"
+and all systems have Citadel/UX EXCEPT for "gateway" and "mailsys":
+     
+                gateway---mailsys          _____testbbs
+                /                         / 
+           othersys ----- myself ----- thebox
+              /                          \_______theirsys
+          funboard
+   
+   In this example, our neighbors are "othersys" and "thebox". othersys
+also connects to funboard, and thebox connects to testbbs and theirsys. If
+everyone supports netmail, the network/mail.sysinfo file would look like this:
+   
+funboard
+use othersys
+
+testbbs
+use thebox
+
+theirsys
+use thebox
+
+othersys
+bin Mail
+
+theirsys
+bin Mail
+
+gateway
+uum othersys!gateway!%s
+
+mailsys
+uum othersys!gateway!mailsys!%s
+   
+  (Keep in mind that your file will contain additional system-generated
+information.)
+  The "bin" entries specify neighbors, the "use" entries specify routing, and
+the "uum" entries specify regular UUCP mail. The method of delivery is totally
+transparent to the user, who only needs to enter the recipient as user@sysname.
+Note that netproc will probably stuff lots of other info into each entry.
+  
+    
+  THE KILL FILE
+   
+   Tired of idiots lowering the quality of the net?  You can set up a "kill
+file" in ./network/filterlist that can be used to filter out messages from
+any user, room, or system (or any combination).  The three fields should be
+separated by commas, and the name "*" may be used as a wildcard in any field.
+Examples:
+ # Filter out user "The Idiot" in "Idiot Room" at "idiotbbs"
+ #
+ The Idiot,Idiot Room,idiotbbs
+ # Filter out the same user, but in every room
+ # 
+ The Idiot,*,idiotbbs
+ # Filter out all messages from a system we don't like
+ #
+ *,*,idiotbbs
+ # Filter out messages in a certain room from a certain system
+ #
+ *,The Room,idiotbbs
+   You could also put a "*" wildcard in all three fields, essentially
+disabling all incoming messages.  Obviously you don't want to do this.
+  
+  CONCLUSION
+   
+   That should cover everything you need to get running. By the way, gateway
+software for StoneHenge and NYTI FordBoard systems is available upon special
+request.  And, a Cit86Net gateway is now available.  For the latest version
+of this program, or to leave comments/suggestions, call my board  -  
+UNCENSORED! BBS at (914) 244-3252 (modem) or uncnsrd.mt-kisco.ny.us (Internet).
diff --git a/citadel/network/filterlist b/citadel/network/filterlist
new file mode 100644 (file)
index 0000000..dd0699d
--- /dev/null
@@ -0,0 +1,8 @@
+# This is a "kill file" for the networker.  You can specify users, rooms,
+# systems, or all three.  The name "*" may be used as a wildcard for any
+# field.  See network.txt for more information.
+#
+#
+The Banned User,*,*
+The Other Banned User,This Room Only,*
+*,*,The Banned System
diff --git a/citadel/network/internetmail.config b/citadel/network/internetmail.config
new file mode 100644 (file)
index 0000000..f14e47b
--- /dev/null
@@ -0,0 +1,47 @@
+#
+# internetmail.config
+# 
+# This file contains a bunch of defs which you must edit to reflect the
+# local environment on your system.
+#
+# The purpose of this file (and the programs that use it) is to enable
+# the exchange of Internet e-mail to and from Citadel.
+
+
+# Where to find the alias table.  Usually there is no need to change this.
+aliases = ./network/mail.aliases
+
+# If you are networking with Citadel-86 systems via Internet e-mail, they
+# will be sending uuencoded network packets to "cit86net@yourhost.fqdn".
+# Define CIT86NET to the name of the directory you wish these packets to be
+# unpacked into.
+cit86net spoolin = ./network/spoolin.cit86net
+
+# Command to use to transmit mail (usually it's sendmail)
+sendmail = sendmail %s
+
+# Fallback mailer for delivery to non-Citadel local users (lmail, procmail...)
+fallback = lmail %s
+
+# Other names which this host is known by (specify up to ten names)
+# deliver local = uncnsrd.spaghetti.com
+# deliver local = uncnsrd.local
+
+# Set to 0 to prevent the immediate processing of incoming Internet mail.
+# This can be useful if mail tends to arrive in big batches.  Setting this
+# value to 1 will run netproc immediately after each message arrives.
+run netproc = 0
+
+# If you are participating in a network in which any Citadel may gateway
+# Internet mail to any Citadel on the local network, define GW_DOMAIN to the
+# base name of the domain containing all the Citadels.  Otherwise, comment it
+# out to disable this functionality.
+gateway domain = citadelia.org
+
+# If you are providing access to Internet mailing lists via Citadel rooms
+# in order to allow everyone to access a list without everyone having to
+# subscribe to the list, TABLEFILE defines where to locate the information
+# to process the lists.  (This is NOT the same thing as using a networked room
+# to *host* a mailing list, which you can also do.)  See mailinglists.txt for
+# more information.
+table file = ./network/mailinglists
diff --git a/citadel/network/mail.aliases b/citadel/network/mail.aliases
new file mode 100644 (file)
index 0000000..46cb863
--- /dev/null
@@ -0,0 +1,3 @@
+bbs,room_aide
+root,room_aide
+Auto,room_aide
diff --git a/citadel/network/mail.sysinfo b/citadel/network/mail.sysinfo
new file mode 100644 (file)
index 0000000..accc987
--- /dev/null
@@ -0,0 +1,217 @@
+clemsont
+use uncnsrd
+phonenum US (616) 323 7207
+gdom MI
+humannode Clemson Tyde
+lastcontact 899841267 Tue Jul  7 15:54:27 1998
+
+imagesat
+use uncnsrd
+phonenum US (612) 884 7951
+gdom MN
+humannode Images at Twilight
+lastcontact 899841264 Tue Jul  7 15:54:24 1998
+
+gorlocks
+use uncnsrd
+phonenum US (612) 5721236
+gdom MN
+humannode Gorlock's Warehouse
+lastcontact 899841256 Tue Jul  7 15:54:16 1998
+
+houseofm
+use uncnsrd
+phonenum US (612)831-2312
+gdom MN
+humannode House of Moo
+lastcontact 899841256 Tue Jul  7 15:54:16 1998
+
+fifthdim
+use uncnsrd
+phonenum CA (780) 432-1272
+gdom Alta
+humannode Fifth Dimension
+lastcontact 899841261 Tue Jul  7 15:54:21 1998
+
+havenbbs
+use uncnsrd
+phonenum US (317)843-1288
+gdom IN
+humannode Haven BBS
+lastcontact 899841248 Tue Jul  7 15:54:08 1998
+
+ivorytow
+use uncnsrd
+phonenum US 612-425-0554
+gdom MN
+humannode Ivory Tower
+lastcontact 899841250 Tue Jul  7 15:54:10 1998
+
+kronospe
+use uncnsrd
+phonenum CA 403 886 4171
+gdom Alta
+humannode Kronos, Penhold
+lastcontact 899841245 Tue Jul  7 15:54:05 1998
+
+doglink
+use uncnsrd
+phonenum US 612 460 6056
+gdom MN
+humannode DogLink
+lastcontact 899841267 Tue Jul  7 15:54:27 1998
+
+cbbs
+use uncnsrd
+phonenum US (513) 939 1645
+gdom Cinci
+humannode The CBBS
+lastcontact 899841244 Tue Jul  7 15:54:04 1998
+
+gateway
+use uncnsrd
+phonenum US (609) 931-3014
+gdom NJ
+humannode Gateway
+lastcontact 899841259 Tue Jul  7 15:54:19 1998
+
+feathers
+use uncnsrd
+phonenum CA (604) 589-8539
+gdom BC
+humannode Feathers & Furballs
+lastcontact 899841270 Tue Jul  7 15:54:30 1998
+
+eternalc
+use uncnsrd
+phonenum US 612 227 0215
+gdom MN
+humannode Eternal City
+lastcontact 899841247 Tue Jul  7 15:54:07 1998
+
+peripher
+use uncnsrd
+phonenum US (612) 930 0055
+gdom MN
+humannode Peripheral Visions
+lastcontact 899840596 Tue Jul  7 15:43:16 1998
+
+hub
+use uncnsrd
+phonenum US (612) 783-7160
+gdom MN
+humannode The HUB
+lastcontact 899841268 Tue Jul  7 15:54:28 1998
+
+mnmensa
+use uncnsrd
+phonenum US (612) 730-0041
+gdom MN
+humannode MN-Mensa
+lastcontact 899840602 Tue Jul  7 15:43:22 1998
+
+bistro
+use uncnsrd
+phonenum CA (250) 542-3617
+gdom BC
+humannode Bistro
+lastcontact 899841259 Tue Jul  7 15:54:19 1998
+
+rundale
+use uncnsrd
+phonenum US 609 854 9135
+gdom NJ
+humannode Rundale
+lastcontact 899840411 Tue Jul  7 15:40:11 1998
+
+jacs
+use uncnsrd
+phonenum US6093461224
+gdom NJ
+humannode JACS
+lastcontact 899841263 Tue Jul  7 15:54:23 1998
+
+jdevil
+use uncnsrd
+humannode Jersey Devil Citadel
+lastcontact 899840922 Tue Jul  7 15:48:42 1998
+
+nyti
+use uncnsrd
+humannode New York Telephone I
+lastcontact 899841262 Tue Jul  7 15:54:22 1998
+
+flynnsin
+use uncnsrd
+phonenum CA 403 430-9193
+gdom Alta
+humannode Flynn's Inn
+lastcontact 899841260 Tue Jul  7 15:54:20 1998
+
+ctestsys
+use uncnsrd
+phonenum US 612 470 9635
+gdom MN
+humannode C-86 Test System
+lastcontact 899841271 Tue Jul  7 15:54:31 1998
+
+uncnsrd
+bin Mail
+phonenum US 914 244 3252
+humannode Uncensored
+lastcontact 899841269 Tue Jul  7 15:54:29 1998
+
+internet
+uum %s
+humannode Internet Gateway
+lastcontact 899841231 Tue Jul  7 15:53:51 1998
+
+scooby
+bin Mail
+phonenum US 800 555 1212
+humannode Scooby Test Server
+lastcontact 899841279 Tue Jul  7 15:54:39 1998
+
+gwnorth
+use uncnsrd
+phonenum US 317 290 8590
+gdom IGnet
+humannode GWNorth
+lastcontact 899841270 Tue Jul  7 15:54:30 1998
+
+dogpound2
+use uncnsrd
+phonenum US 215 946 1164
+gdom IGnet
+humannode Dog Pound BBS II
+lastcontact 899841262 Tue Jul  7 15:54:22 1998
+
+fortress
+use uncnsrd
+humannode The Mighty Fortress
+lastcontact 899841270 Tue Jul  7 15:54:30 1998
+
+bearcave
+use uncnsrd
+humannode The Bearcave
+lastcontact 899840869 Tue Jul  7 15:47:49 1998
+
+emubbs
+use uncnsrd
+phonenum US 612 470 9635
+gdom IGnet
+humannode EmuBBS
+lastcontact 899841262 Tue Jul  7 15:54:22 1998
+
+storm
+use uncnsrd
+humannode Bilskinir
+lastcontact 899840870 Tue Jul  7 15:47:50 1998
+
+amigazon
+use uncnsrd
+phonenum US (609) 953 8159
+gdom NJ
+humannode The Amiga Zone
+lastcontact 899841267 Tue Jul  7 15:54:27 1998
+
diff --git a/citadel/network/mailinglists b/citadel/network/mailinglists
new file mode 100644 (file)
index 0000000..63490d5
--- /dev/null
@@ -0,0 +1,9 @@
+# Room name cross-reference for bidirectional mailing list gateway
+#
+# Each line is in the form:
+# <mailing list posting address>,<Cit/UX room name>
+#
+# (Yes, this implies that you can't cross-reference a room that has a
+# comma in its name.)
+#
+gnu-win32@cygnus.com,Cygnus
diff --git a/citadel/network/rnews.xref b/citadel/network/rnews.xref
new file mode 100644 (file)
index 0000000..3e2073c
--- /dev/null
@@ -0,0 +1,2 @@
+mega-net.gateway,Gateway
+test.usenet,Network Test
diff --git a/citadel/network/systems/uncnsrd b/citadel/network/systems/uncnsrd
new file mode 100644 (file)
index 0000000..740b59a
--- /dev/null
@@ -0,0 +1 @@
+cat %s >>./network/spoolout/uncnsrd
diff --git a/citadel/public_clients b/citadel/public_clients
new file mode 100644 (file)
index 0000000..504890c
--- /dev/null
@@ -0,0 +1,9 @@
+# public_clients
+#
+# This file contains a list of hosts at which well-known public
+# copies of client software exist.  If the originating host
+# for a client is one of these, then the server will use the
+# location of the user that it is told by the client.
+
+localhost
+127.0.0.1
diff --git a/citadel/rcit.c b/citadel/rcit.c
new file mode 100644 (file)
index 0000000..5308bf3
--- /dev/null
@@ -0,0 +1,299 @@
+#define UNCOMPRESS "/usr/bin/gunzip"
+
+/* Citadel/UX rnews
+ * version 2.8
+ *
+ * This program functions the same as the standard rnews program for
+ * UseNet. It accepts standard input, and looks for rooms to post messages
+ * (translated from UseNet format to the Citadel/UX binary message format)
+ * in that match the names of the newsgroups. network/rnews.xref is checked
+ * in case the sysop wants to cross-reference room names to newsgroup names.
+ * If standard input is already in binary, the -c flag should be used.
+ *   The netproc program is then called to do the processing.
+ *
+ * If you have a separate newsreader and don't want to use Citadel for news,
+ * just call this program something else (rcit, for example) -- but make sure
+ * to tell your Citadel network neighbors the new name of the program to call.
+ * 
+ * usage:
+ *     rnews [-c] [-z] [-s]
+ * flags:
+ *     -c      Input is already in Citadel binary format
+ *             (default is UseNet news format)
+ *     -z      Input is compressed, run uncompress on it before processing
+ *     -s      Don't run netproc now, just accept the input into spoolin
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include "citadel.h"
+
+void get_config();
+struct config config;
+
+char roomlist[MAXROOMS][20];
+
+void load_roomlist() {
+       FILE *fp;
+       struct quickroom QRtemp;
+       int a;
+
+       for (a=0; a<MAXROOMS; ++a) strcpy(roomlist[a],TWITROOM);
+       fp=fopen("quickroom","r");
+       if (fp==NULL) return;
+       for (a=0; a<MAXROOMS; ++a) {
+               fread((char *)&QRtemp,sizeof(struct quickroom),1,fp);
+               if (QRtemp.QRflags & QR_INUSE)
+                       strcpy(roomlist[a],QRtemp.QRname);
+               }
+       fclose(fp);
+       }
+
+int struncmp(lstr,rstr,len)
+char lstr[],rstr[];
+int len; {
+       int pos = 0;
+       char lc,rc;
+       while (pos<len) {
+               lc=tolower(lstr[pos]);
+               rc=tolower(rstr[pos]);
+               if ((lc==0)&&(rc==0)) return(0);
+               if (lc<rc) return(-1);
+               if (lc>rc) return(1);
+               pos=pos+1;
+               }
+       return(0);
+       }
+
+
+int rnewsxref(room,ngroup)             /* xref table */
+char room[]; 
+char ngroup[]; {
+       FILE *fp;
+       int a,b;
+       char aaa[50],bbb[50];
+       
+       strcpy(room,ngroup);
+       fp=fopen("network/rnews.xref","r");
+GNA:   strcpy(aaa,""); strcpy(bbb,"");
+       do {
+               a=getc(fp);
+               if (a==',') a=0;
+               if (a>0) { b=strlen(aaa); aaa[b]=a; aaa[b+1]=0; }
+               } while(a>0);
+       do {
+               a=getc(fp);
+               if (a==10) a=0;
+               if (a>0) { b=strlen(bbb); bbb[b]=a; bbb[b+1]=0; }
+               } while(a>0);
+       if (a<0) {
+               fclose(fp);
+               return(1);
+               }
+       if (strucmp(ngroup,aaa)) goto GNA;
+       fclose(fp);
+       strcpy(room,bbb);
+       return(0);
+       }
+
+
+void getroom(room,ngroup)
+char room[];
+char ngroup[]; {
+       char ngbuf[256];
+       char tryme[256];
+       int a,b;
+       struct quickroom qbuf;
+
+       strcpy(ngbuf,ngroup);
+       strcat(ngbuf,",");
+       for (a=0; a<strlen(ngbuf); ++a) {
+               if ((ngbuf[a]==',')||(ngbuf[a]==0)) {
+                       strcpy(tryme,ngbuf);    
+                       tryme[a]=0;
+                       if (!rnewsxref(room,tryme)) return;
+                       room[sizeof(qbuf.QRname)-1]=0;
+                       for (b=0; b<MAXROOMS; ++b) {
+                               if (!strucmp(roomlist[b],room)) return;
+                               }
+                       strcpy(ngbuf,&ngbuf[a+1]);
+                       a=(-1);
+                       }
+               }
+       }
+
+void main(argc,argv)
+int argc;
+char *argv[]; {
+       char aaa[128],bbb[128],ccc[128];
+       char author[128],recipient[128],room[128],node[128],path[512];
+       char subject[128];
+       char orgname[128];
+       long mid = 0L;
+       long now,bcount,aa;
+       int a;
+       char flnm[128],tname[128];
+       FILE *minput,*mout,*mtemp;
+       char binary_input = 0;
+       char compressed_input = 0;
+       char spool_only = 0;
+
+       get_config();
+       sprintf(flnm,"./network/spoolin/rnews.%d",getpid());
+       sprintf(tname,"/tmp/rnews.%d",getpid());
+
+       for (a=1; a<argc; ++a) {
+               if (!strcmp(argv[a],"-c")) binary_input = 1;
+               if (!strcmp(argv[a],"-z")) compressed_input = 1;
+               if (!strcmp(argv[a],"-s")) spool_only = 1;
+               }
+
+       minput=stdin;
+       if (compressed_input) minput=popen(UNCOMPRESS,"r");
+       if (minput==NULL) fprintf(stderr,"rnews: can't open input!!!!\n");
+
+       mout=fopen(flnm,"w");
+
+       /* process Citadel/UX binary format input */
+       if (binary_input) {
+               while ((a=getc(minput))>=0) putc(a,mout);
+               goto END;
+               }
+
+       /* process UseNet news input */
+       load_roomlist();
+A:     if (fgets(aaa,128,minput)==NULL) goto END;
+       aaa[strlen(aaa)-1]=0;
+       if (strncmp(aaa,"#! rnews ",9)) goto A;
+       bcount=atol(&aaa[9]);
+       mtemp=fopen(tname,"w");
+       for (aa=0L; aa<bcount; ++aa) {
+               a=getc(minput);
+               if (a<0) goto NMA;
+               if (a>=0) putc(a,mtemp);
+               }
+NMA:   fclose(mtemp);
+       if (a<0) {
+               fprintf(stderr,"rnews: EOF unexpected\n");
+               goto END;
+               }
+
+       mtemp=fopen(tname,"r");
+       strcpy(author,"");
+       strcpy(recipient,"");
+       strcpy(room,"");
+       strcpy(node,"");
+       strcpy(path,"");
+       strcpy(orgname,"");
+       strcpy(subject,"");
+
+B:     if (fgets(aaa,128,mtemp)==NULL) goto ABORT;
+       aaa[strlen(aaa)-1]=0;
+       if (strlen(aaa)==0) goto C;
+
+
+       if (!strncmp(aaa,"From: ",6)) {
+               strcpy(author,&aaa[6]);
+               while((author[0]==' ')&&(strlen(author)>0))
+                       strcpy(author,&author[1]);
+               for (a=0; a<strlen(author); ++a) {
+                       if (author[a]=='<') author[a-1]=0;
+                       if (author[a]==')') author[a]=0;
+                       if (author[a]=='(') {
+                               strcpy(author,&author[a+1]);
+                               a=0;
+                               }
+                       }
+               if (!strcmp(author,")")) {
+                       strcpy(author,&aaa[6]);
+                       for (a=0; a<strlen(author); ++a)
+                               if (author[a]=='@') author[a]=0;
+                       }
+               strcpy(node,&aaa[6]);
+               for (a=0; a<strlen(node); ++a) {
+                       if ((node[a]=='<')||(node[a]=='@')) {
+                               strcpy(node,&node[a+1]);
+                               a=0;
+                               }
+                       if (node[a]=='>') node[a]=0;
+                       if (node[a]=='(') node[a-1]=0;
+                       }
+               for (a=0; a<strlen(author); ++a)
+                       if (author[a]=='@') author[a]=0;
+               }
+
+       if (!strncmp(aaa,"Path: ",6)) strcpy(path,&aaa[6]);
+       if (!strncmp(aaa,"To: ",4)) strcpy(recipient,&aaa[4]);
+       if (!strncmp(aaa,"Subject: ",9)) strcpy(subject,&aaa[9]);
+       if (!strncmp(aaa,"Organization: ",14)) strcpy(orgname,&aaa[14]);
+
+       if (!strncmp(aaa,"Newsgroups: ",11)) {
+               strcpy(room,&aaa[12]);
+               for (a=0; a<strlen(aaa); ++a) if (aaa[a]==',') aaa[a]=0;
+               goto B;
+               }
+
+       if (!strncmp(aaa,"Message-ID: ",11)) {
+               strcpy(bbb,&aaa[13]);
+               for (a=0; a<strlen(bbb); ++a) if (bbb[a]=='@') bbb[a]=0;
+               mid=atol(bbb);
+               while((aaa[0]!='@')&&(aaa[0]!=0)) {
+                       strcpy(&aaa[0],&aaa[1]);
+                       }
+               strcpy(&aaa[0],&aaa[1]);
+               for (a=0; a<strlen(aaa); ++a) if (aaa[a]=='>') aaa[a]=0;
+               strcpy(node,aaa);
+               goto B;
+               }
+               goto B;
+
+C:     if ((author[0]==0)||(room[0]==0)||(node[0]==0)) goto ABORT;
+       putc(255,mout);                 /* start of message */
+       putc(MES_NORMAL,mout);          /* not anonymous */
+       putc(1,mout);                   /* not formatted */
+       time(&now);
+
+       fprintf(mout,"I%ld",mid); putc(0,mout);
+       fprintf(mout,"P%s",path); putc(0,mout);
+       fprintf(mout,"T%ld",now); putc(0,mout);
+       fprintf(mout,"A%s",author); putc(0,mout);
+       strcpy(ccc,room);
+       getroom(room,ccc);
+       fprintf(mout,"O%s",room); putc(0,mout);
+       fprintf(mout,"N%s",node); putc(0,mout);
+       if (orgname[0]!=0) {
+               fprintf(mout,"H%s",orgname); putc(0,mout);
+               }
+       if (recipient[0]!=0) {
+               fprintf(mout,"R%s",recipient); putc(0,mout);
+               }
+       if (subject[0]!=0) {
+               fprintf(mout,"U%s",subject); putc(0,mout);
+               }
+       fprintf(mout,"M");
+       a=0;
+       aaa[0]=0;
+
+       do {
+               a=getc(mtemp);
+               if (a>0) putc(a,mout);
+               } while (a>0);
+       putc(0,mout);
+ABORT: fclose(mtemp);
+       unlink(tname);
+       goto A;
+
+END:   putc(0,mout);
+       fclose(mout);
+       unlink(tname);
+       if (compressed_input) pclose(minput);
+       if (!spool_only) execlp("./netproc","netproc",NULL);
+       exit(0);
+}
+
+
diff --git a/citadel/readlog.c b/citadel/readlog.c
new file mode 100644 (file)
index 0000000..91e196f
--- /dev/null
@@ -0,0 +1,99 @@
+/* 
+ * readlog.c
+ * v1.4
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <time.h>
+#include "citadel.h"
+
+void get_config();
+struct config config;
+
+void last20(file,pos)
+int file;
+long pos;
+       {
+       int a,count;
+       long aa;
+       struct calllog calllog;
+       struct calllog listing[20];
+       struct tm *tm;
+       char *tstring;
+       
+       count=0;
+       for (a=0; a<20; ++a) {
+               listing[a].CLfullname[0]=0;
+               listing[a].CLtime=0L;
+               listing[a].CLflags=0;
+               }
+       aa=pos-1;
+       while(count<20) {
+               if (aa<0L) aa=CALLLOG;
+               lseek(file,(aa*sizeof(struct calllog)),0);
+               a=read(file,(char *)&calllog,sizeof(struct calllog));
+               if (calllog.CLflags==CL_LOGIN) {
+                       strcpy(listing[count].CLfullname,calllog.CLfullname);
+                       listing[count].CLtime=calllog.CLtime;
+                       listing[count].CLflags=calllog.CLflags;
+                       ++count;
+                       }
+               if (aa==pos) break;
+               aa=aa-1;
+               }
+       for (a=19; a>=0; --a) {
+               tm=(struct tm *)localtime(&listing[a].CLtime);
+               tstring=(char *)asctime(tm);
+               printf("%30s %s",listing[a].CLfullname,tstring);
+               }
+       }
+
+void main(argc,argv)
+int argc;
+char *argv[]; {
+       struct calllog calllog;
+       int file,pos,a,b;
+       char aaa[100];
+       struct tm *tm;
+       char *tstring;
+
+       get_config();
+       file=open("calllog.pos",O_RDONLY);
+       a=read(file,(char *)&pos,sizeof(int));
+       close(file);
+
+       file=open("calllog",O_RDONLY);
+       if (argc>=2) {
+               if (!strcmp(argv[1],"-t")) last20(file,(long)pos);
+               else fprintf(stderr,"%s: usage: %s [-t]\n",argv[0],argv[0]);
+               close(file);
+               exit(0);
+               }
+else {
+       lseek(file,(long)(pos*sizeof(struct calllog)),0);
+       for (a=0; a<CALLLOG; ++a) {
+               if ((a+pos)==CALLLOG) lseek(file,0L,0);
+               b=read(file,(char *)&calllog,sizeof(struct calllog));
+       if (calllog.CLflags!=0) {
+               strcpy(aaa,"");
+               if (calllog.CLflags&CL_CONNECT) strcpy(aaa,"Connect");
+               if (calllog.CLflags&CL_LOGIN)   strcpy(aaa,"Login");
+               if (calllog.CLflags&CL_NEWUSER) strcpy(aaa,"New User");
+               if (calllog.CLflags&CL_BADPW)   strcpy(aaa,"Bad PW Attempt");
+               if (calllog.CLflags&CL_TERMINATE) strcpy(aaa,"Terminate");
+               if (calllog.CLflags&CL_DROPCARR) strcpy(aaa,"Dropped Carrier");
+               if (calllog.CLflags&CL_SLEEPING) strcpy(aaa,"Sleeping");
+               if (calllog.CLflags&CL_PWCHANGE) strcpy(aaa,"Changed Passwd");
+               tm=(struct tm *)localtime(&calllog.CLtime);
+               tstring=(char *)asctime(tm);
+               printf("%30s %20s %s",calllog.CLfullname,aaa,tstring);
+               }
+               }
+       }
+       close(file);
+       exit(0);
+}
+       
diff --git a/citadel/room_ops.c b/citadel/room_ops.c
new file mode 100644 (file)
index 0000000..240b0ed
--- /dev/null
@@ -0,0 +1,1380 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <pthread.h>
+#include "citadel.h"
+#include "server.h"
+#include "proto.h"
+
+extern struct config config;
+
+FILE *popen(const char *, const char *);
+
+/*
+ * is_known()  -  returns nonzero if room is in user's known room list
+ */
+int is_known(struct quickroom *roombuf, int roomnum, struct usersupp *userbuf)
+{
+
+       /* for internal programs, always succeed */
+       if (((CC->internal_pgm))&&(roombuf->QRflags & QR_INUSE)) return(1);
+
+       /* mail */
+       if (roomnum==1) roombuf->QRhighest=userbuf->mailnum[MAILSLOTS-1];
+
+       /* for regular rooms, check the permissions */
+       if ((roombuf->QRflags & QR_INUSE)
+               && ( (roomnum!=2) || (userbuf->axlevel>=6))
+               && (roombuf->QRgen != (userbuf->forget[roomnum]) )
+
+               && (    ((roombuf->QRflags&QR_PREFONLY)==0)
+               ||      ((userbuf->axlevel)>=5)
+               )
+
+               && (    ((roombuf->QRflags&QR_PRIVATE)==0)
+               ||      ((userbuf->axlevel)>=6)
+               ||      (roombuf->QRgen==(userbuf->generation[roomnum]))
+               )
+
+               ) return(1);
+       else return(0);
+       }
+
+
+/*
+ * has_newmsgs()  -  returns nonzero if room has new messages
+ */
+int has_newmsgs(struct quickroom *roombuf, int roomnum, struct usersupp *userbuf)
+{
+       if (roombuf->QRhighest > (userbuf->lastseen[roomnum]) )
+               return(1);
+       else return(0);
+       }
+
+/*
+ * is_zapped()  -  returns nonzero if room is on forgotten rooms list
+ */
+int is_zapped(struct quickroom *roombuf, int roomnum, struct usersupp *userbuf)
+{
+       if (roomnum==1) roombuf->QRhighest=userbuf->mailnum[MAILSLOTS-1];
+       if ((roombuf->QRflags & QR_INUSE)
+               && (roombuf->QRgen == (userbuf->forget[roomnum]) )
+               && ( (roomnum!=2) || ((userbuf->axlevel)>=6))
+               && (    ((roombuf->QRflags&QR_PRIVATE)==0)
+               ||      ((userbuf->axlevel)>=6)
+               ||      (roombuf->QRgen==(userbuf->generation[roomnum]))
+               )
+               ) return(1);
+       else return(0);
+       }
+
+/*
+ * getroom()  -  retrieve room data from disk
+ */
+void getroom(struct quickroom *qrbuf, int room_num)
+{
+       struct cdbdata *cdbqr;
+       int a;
+
+       bzero(qrbuf, sizeof(struct quickroom));
+       cdbqr = cdb_fetch(CDB_QUICKROOM, &room_num, sizeof(int));
+       if (cdbqr != NULL) {
+               memcpy(qrbuf, cdbqr->ptr, cdbqr->len);
+               cdb_free(cdbqr);
+               }
+       else {
+               if (room_num < 3) {
+                       qrbuf->QRflags = QR_INUSE;
+                       qrbuf->QRgen = 1;
+                       switch(room_num) {
+                               case 0: strcpy(qrbuf->QRname, "Lobby");
+                                       break;
+                               case 1: strcpy(qrbuf->QRname, "Mail");
+                                       break;
+                               case 2: strcpy(qrbuf->QRname, "Aide");
+                                       break;
+                               }
+                       }
+               }
+
+
+       /** FIX **   VILE SLEAZY HACK ALERT!!  
+        * This is a temporary fix until I can track down where room names
+        * are getting corrupted on some systems.
+        */
+       for (a=0; a<20; ++a) if (qrbuf->QRname[a] < 32) qrbuf->QRname[a] = 0;
+       qrbuf->QRname[19] = 0;
+       }
+
+/*
+ * lgetroom()  -  same as getroom() but locks the record (if supported)
+ */
+void lgetroom(struct quickroom *qrbuf, int room_num)
+{
+       begin_critical_section(S_QUICKROOM);
+       getroom(qrbuf,room_num);
+       }
+
+
+/*
+ * putroom()  -  store room data on disk
+ */
+void putroom(struct quickroom *qrbuf, int room_num)
+{
+
+       cdb_store(CDB_QUICKROOM, &room_num, sizeof(int),
+               qrbuf, sizeof(struct quickroom));
+       }
+
+
+/*
+ * lputroom()  -  same as putroom() but unlocks the record (if supported)
+ */
+void lputroom(struct quickroom *qrbuf, int room_num)
+{
+
+       putroom(qrbuf,room_num);
+       end_critical_section(S_QUICKROOM);
+
+       }
+
+
+/*
+ * getfloor()  -  retrieve floor data from disk
+ */
+void getfloor(struct floor *flbuf, int floor_num)
+{
+       struct cdbdata *cdbfl;
+
+       bzero(flbuf, sizeof(struct floor));
+       cdbfl = cdb_fetch(CDB_FLOORTAB, &floor_num, sizeof(int));
+       if (cdbfl != NULL) {
+               memcpy(flbuf, cdbfl->ptr, cdbfl->len);
+               cdb_free(cdbfl);
+               }
+       else {
+               if (floor_num == 0) {
+                       strcpy(flbuf->f_name, "Main Floor");
+                       flbuf->f_flags = F_INUSE;
+                       flbuf->f_ref_count = 3;
+                       }
+               }
+
+       }
+
+/*
+ * lgetfloor()  -  same as getfloor() but locks the record (if supported)
+ */
+void lgetfloor(struct floor *flbuf, int floor_num)
+{
+
+       begin_critical_section(S_FLOORTAB);
+       getfloor(flbuf,floor_num);
+       }
+
+
+/*
+ * putfloor()  -  store floor data on disk
+ */
+void putfloor(struct floor *flbuf, int floor_num)
+{
+       cdb_store(CDB_FLOORTAB, &floor_num, sizeof(int),
+               flbuf, sizeof(struct floor));
+       }
+
+
+/*
+ * lputfloor()  -  same as putfloor() but unlocks the record (if supported)
+ */
+void lputfloor(struct floor *flbuf, int floor_num)
+{
+
+       putfloor(flbuf,floor_num);
+       end_critical_section(S_FLOORTAB);
+
+       }
+
+
+
+void readmail(void) {
+       int a;
+       for (a=0; a<MSGSPERRM; ++a) {
+               CC->fullroom.FRnum[a]=0L;
+               }
+       for (a=0; a<MAILSLOTS; ++a) {
+               CC->fullroom.FRnum[a+(MSGSPERRM-MAILSLOTS)] =
+                       CC->usersupp.mailnum[a];
+               }
+       CC->quickroom.QRhighest = CC->usersupp.mailnum[MAILSLOTS-1];
+       }
+
+
+void writemail(void) {
+       int a;
+       lgetuser(&CC->usersupp,CC->curr_user);
+       for (a=0; a<MAILSLOTS; ++a) {
+               CC->usersupp.mailnum[a] =
+                       CC->fullroom.FRnum[a+(MSGSPERRM-MAILSLOTS)];
+               }
+       lputuser(&CC->usersupp,CC->curr_user);
+       }
+
+
+
+
+/*
+ * get_fullroom()  -  retrieve room message pointers
+ */
+void get_fullroom(struct fullroom *frbuf, int room_num)
+{
+       struct cdbdata *cdbfr;
+
+       if (room_num != 1) {
+               bzero(frbuf, sizeof(struct fullroom));
+               cdbfr = cdb_fetch(CDB_FULLROOM, &room_num, sizeof(int));
+               if (cdbfr != NULL) {
+                       memcpy(frbuf, cdbfr->ptr, cdbfr->len);
+                       cdb_free(cdbfr);
+                       }
+
+               }
+       else {
+               readmail();
+               }
+       }
+
+
+/*
+ * put_fullroom()  -  retrieve room message pointers
+ */
+void put_fullroom(struct fullroom *frbuf, int room_num)
+{
+
+       if (room_num != 1) {
+               cdb_store(CDB_FULLROOM, &room_num, sizeof(int),
+                       frbuf, sizeof(struct fullroom));
+               }
+       else {
+               writemail();    
+               }
+       }
+
+
+
+/* 
+ * cmd_lrms()   -  List all accessible rooms, known or forgotten
+ */
+void cmd_lrms(char *argbuf)
+{
+       int a;
+       int target_floor = (-1);
+       struct quickroom qrbuf;
+
+       if (strlen(argbuf)>0) target_floor = extract_int(argbuf,0);
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (getuser(&CC->usersupp,CC->curr_user)) {
+               cprintf("%d Can't locate user!\n",ERROR+INTERNAL_ERROR);
+               return;
+               }
+
+       cprintf("%d Accessible rooms:\n",LISTING_FOLLOWS);
+       
+       for (a=0; a<MAXROOMS; ++a) {
+               getroom(&qrbuf,a);
+               if ( ( (is_known(&qrbuf,a,&CC->usersupp))
+                  ||   (is_zapped(&qrbuf,a,&CC->usersupp)) )
+               && ((qrbuf.QRfloor == target_floor)||(target_floor<0)) )
+                       cprintf("%s|%u|%d\n",
+                               qrbuf.QRname,qrbuf.QRflags,qrbuf.QRfloor);
+               }
+       cprintf("000\n");
+       }
+
+/* 
+ * cmd_lkra()   -  List all known rooms
+ */
+void cmd_lkra(char *argbuf)
+{
+       int a;
+       struct quickroom qrbuf;
+       int target_floor = (-1);
+
+       if (strlen(argbuf)>0) target_floor = extract_int(argbuf,0);
+
+       if ((!(CC->logged_in))&&(!(CC->internal_pgm))) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (!(CC->internal_pgm)) if (getuser(&CC->usersupp,CC->curr_user)) {
+               cprintf("%d Can't locate user!\n",ERROR+INTERNAL_ERROR);
+               return;
+               }
+
+       cprintf("%d Known rooms:\n",LISTING_FOLLOWS);
+       
+       for (a=0; a<MAXROOMS; ++a) {
+               getroom(&qrbuf,a);
+               if ((is_known(&qrbuf,a,&CC->usersupp))
+                  && ((qrbuf.QRfloor == target_floor)||(target_floor<0)) )
+                       cprintf("%s|%u|%d\n",
+                               qrbuf.QRname,qrbuf.QRflags,qrbuf.QRfloor);
+               }
+       cprintf("000\n");
+       }
+
+/* 
+ * cmd_lkrn()   -  List Known Rooms with New messages
+ */
+void cmd_lkrn(char *argbuf)
+{
+       int a;
+       struct quickroom qrbuf;
+       int target_floor = (-1);
+
+       if (strlen(argbuf)>0) target_floor = extract_int(argbuf,0);
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (getuser(&CC->usersupp,CC->curr_user)) {
+               cprintf("%d can't locate user\n",ERROR+INTERNAL_ERROR);
+               return;
+               }
+
+       cprintf("%d list of rms w/ new msgs\n",LISTING_FOLLOWS);
+       
+       for (a=0; a<MAXROOMS; ++a) {
+               getroom(&qrbuf,a);
+               if ( ( (is_known(&qrbuf,a,&CC->usersupp))
+                  &&   (has_newmsgs(&qrbuf,a,&CC->usersupp)) )
+                  && ((qrbuf.QRfloor == target_floor)||(target_floor<0)) )
+                       cprintf("%s|%u|%d\n",
+                               qrbuf.QRname,qrbuf.QRflags,qrbuf.QRfloor);
+               }
+       cprintf("000\n");
+       }
+
+/* 
+ * cmd_lkro()   -  List Known Rooms with Old (no new) messages
+ */
+void cmd_lkro(char *argbuf)
+{
+       int a;
+       struct quickroom qrbuf;
+       int target_floor = (-1);
+
+       if (strlen(argbuf)>0) target_floor = extract_int(argbuf,0);
+
+       if (!(CC->logged_in)) {
+               cprintf("%d not logged in\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (getuser(&CC->usersupp,CC->curr_user)) {
+               cprintf("%d can't locate user\n",ERROR+INTERNAL_ERROR);
+               return;
+               }
+
+       cprintf("%d list of rms w/o new msgs\n",LISTING_FOLLOWS);
+       
+       for (a=0; a<MAXROOMS; ++a) {
+               getroom(&qrbuf,a);
+               if ( ( (is_known(&qrbuf,a,&CC->usersupp))
+                  &&   (!has_newmsgs(&qrbuf,a,&CC->usersupp)) ) 
+                  && ((qrbuf.QRfloor == target_floor)||(target_floor<0)) ) {
+                       if (!strcmp(qrbuf.QRname,"000")) cprintf(">");
+                       cprintf("%s|%u|%d\n",
+                               qrbuf.QRname,qrbuf.QRflags,qrbuf.QRfloor);
+                       }
+               }
+       cprintf("000\n");
+       }
+
+/* 
+ * cmd_lzrm()   -  List Zapped RooMs
+ */
+void cmd_lzrm(char *argbuf)
+{
+       int a;
+       struct quickroom qrbuf;
+       int target_floor = (-1);
+
+       if (strlen(argbuf)>0) target_floor = extract_int(argbuf,0);
+
+       if (!(CC->logged_in)) {
+               cprintf("%d not logged in\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (getuser(&CC->usersupp,CC->curr_user)) {
+               cprintf("%d can't locate user\n",ERROR+INTERNAL_ERROR);
+               return;
+               }
+
+       cprintf("%d list of forgotten rms\n",LISTING_FOLLOWS);
+       
+       for (a=0; a<MAXROOMS; ++a) {
+               getroom(&qrbuf,a);
+               if ( (is_zapped(&qrbuf,a,&CC->usersupp))
+                  && ((qrbuf.QRfloor == target_floor)||(target_floor<0)) ) {
+                       if (!strcmp(qrbuf.QRname,"000")) cprintf(">");
+                       cprintf("%s|%u|%d\n",
+                               qrbuf.QRname,qrbuf.QRflags,qrbuf.QRfloor);
+                       }
+               }
+       cprintf("000\n");
+       }
+
+
+
+void usergoto(int where, int display_result)
+{
+       int a,b,c;
+       int info = 0;
+       int rmailflag;
+       int raideflag;
+       int newmailcount = 0;
+
+       CC->curr_rm=where;
+       getroom(&CC->quickroom,CC->curr_rm);
+       lgetuser(&CC->usersupp,CC->curr_user);
+       CC->usersupp.forget[CC->curr_rm]=(-1);
+       CC->usersupp.generation[CC->curr_rm]=CC->quickroom.QRgen;
+       lputuser(&CC->usersupp,CC->curr_user);
+
+       for (a=0; a<MAILSLOTS; ++a)
+               if (CC->usersupp.mailnum[a] > CC->usersupp.lastseen[1]) ++newmailcount;
+
+       /* set info to 1 if the user needs to read the room's info file */
+       if (CC->quickroom.QRinfo > CC->usersupp.lastseen[CC->curr_rm]) info = 1;
+
+       b=0; c=0;
+       get_mm();
+       get_fullroom(&CC->fullroom,CC->curr_rm);
+       for (a=0; a<MSGSPERRM; ++a) {
+               if (CC->fullroom.FRnum[a]>0L) {
+                       ++b;
+                       if (CC->fullroom.FRnum[a]>CC->usersupp.lastseen[CC->curr_rm]) ++c;
+                       }
+               }
+
+
+       if (CC->curr_rm == 1) rmailflag = 1;
+       else rmailflag = 0;
+
+       if ( (CC->quickroom.QRroomaide == CC->usersupp.usernum)
+          || (CC->usersupp.axlevel>=6) )  raideflag = 1;
+       else raideflag = 0;
+
+       if (display_result) cprintf("%d%c%s|%d|%d|%d|%d|%ld|%ld|%d|%d|%d|%d\n",
+               OK,check_express(),
+               CC->quickroom.QRname,c,b,info,CC->quickroom.QRflags,
+               CC->quickroom.QRhighest,CC->usersupp.lastseen[CC->curr_rm],
+               rmailflag,raideflag,newmailcount,CC->quickroom.QRfloor);
+       if (CC->quickroom.QRflags & QR_PRIVATE) {
+               set_wtmpsupp("<private room>");
+               }
+       else {
+               set_wtmpsupp(CC->quickroom.QRname);
+               }
+       }
+
+
+/* 
+ * cmd_goto()  -  goto a new room
+ */
+void cmd_goto(char *gargs)
+{
+       struct quickroom QRscratch;
+       int a,c;
+       int ok;
+       char bbb[20],towhere[32],password[20];
+
+       if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
+               cprintf("%d not logged in\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       extract(towhere,gargs,0);
+       extract(password,gargs,1);
+
+       c=0;
+       getuser(&CC->usersupp,CC->curr_user);
+       for (a=0; a<MAXROOMS; ++a) {
+               getroom(&QRscratch,a);
+               if ((a==0)&&(!strucmp(towhere,"_BASEROOM_"))) {
+                       strncpy(towhere,QRscratch.QRname,31);
+                       }
+               if ((a==1)&&(!strucmp(towhere,"_MAIL_"))) {
+                       strncpy(towhere,QRscratch.QRname,31);
+                       }
+               if ((!strucmp(QRscratch.QRname,config.c_twitroom))
+                  &&(!strucmp(towhere,"_BITBUCKET_"))) {
+                       strncpy(towhere,QRscratch.QRname,31);
+                       }
+               strcpy(bbb,QRscratch.QRname);
+               ok = 0;
+
+               /* let internal programs go directly to any room */
+               if (((CC->internal_pgm))&&(!strucmp(bbb,towhere))) {
+                       usergoto(a,1);
+                       return;
+                       }
+
+               /* normal clients have to pass through security */
+               if ( 
+                       (strucmp(bbb,towhere)==0)
+                       &&      ((QRscratch.QRflags&QR_INUSE)!=0)
+
+                       && (    ((QRscratch.QRflags&QR_PREFONLY)==0)
+                       ||      (CC->usersupp.axlevel>=5)
+                       )
+
+                       && (    (a!=2) || (CC->usersupp.axlevel>=6) )
+
+                       && (    ((QRscratch.QRflags&QR_PRIVATE)==0)
+                       || (QRscratch.QRflags&QR_GUESSNAME)
+                       || (CC->usersupp.axlevel>=6)
+                       || (QRscratch.QRflags&QR_PASSWORDED)
+                       ||      (QRscratch.QRgen==CC->usersupp.generation[a])
+                       )
+       
+                       ) ok = 1;
+
+
+               if (ok==1) {
+
+                       if (  (QRscratch.QRflags&QR_PASSWORDED) &&
+                               (CC->usersupp.axlevel<6) &&
+                               (QRscratch.QRgen!=CC->usersupp.generation[a]) &&
+                               (strucmp(QRscratch.QRpasswd,password))
+                               ) {
+                                       cprintf("%d wrong or missing passwd\n",
+                                               ERROR+PASSWORD_REQUIRED);
+                                       return;
+                                       }
+
+                       usergoto(a,1);
+                       return;
+                       }
+
+               }
+       cprintf("%d room '%s' not found\n",ERROR+ROOM_NOT_FOUND,towhere);
+       }
+
+
+void cmd_whok(void) {
+       struct usersupp temp;
+       struct cdbdata *cdbus;
+
+       if ((!(CC->logged_in))&&(!(CC->internal_pgm))) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+       getuser(&CC->usersupp,CC->curr_user);
+
+       if ((!is_room_aide()) && (!(CC->internal_pgm)) ) {
+               cprintf("%d Higher access required.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       cprintf("%d Who knows room:\n",LISTING_FOLLOWS);
+       cdb_rewind(CDB_USERSUPP);
+       while(cdbus = cdb_next_item(CDB_USERSUPP), cdbus != NULL) {
+               bzero(&temp, sizeof(struct usersupp));
+               memcpy(&temp, cdbus->ptr, cdbus->len);
+               cdb_free(cdbus);
+               if ((CC->quickroom.QRflags & QR_INUSE)
+                       && ( (CC->curr_rm!=2) || (temp.axlevel>=6) )
+                       && (CC->quickroom.QRgen != (temp.forget[CC->curr_rm]) )
+
+                       && (    ((CC->quickroom.QRflags&QR_PREFONLY)==0)
+                       ||      (temp.axlevel>=5)
+                       )
+
+                       && (    ((CC->quickroom.QRflags&QR_PRIVATE)==0)
+                       ||      (temp.axlevel>=6)
+                       ||      (CC->quickroom.QRgen==(temp.generation[CC->curr_rm]))
+                       )
+
+                       && (strncmp(temp.fullname,"000",3))
+
+               ) cprintf("%s\n",temp.fullname);
+               }
+       cprintf("000\n");
+       }
+
+
+/*
+ * RDIR command for room directory
+ */
+void cmd_rdir(void) {
+       char buf[256];
+       char flnm[256];
+       char comment[256];
+       FILE *ls,*fd;
+       struct stat statbuf;
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       getroom(&CC->quickroom,CC->curr_rm);
+       getuser(&CC->usersupp,CC->curr_user);
+
+       if ((CC->quickroom.QRflags & QR_DIRECTORY) == 0) {
+               cprintf("%d not here.\n",ERROR+NOT_HERE);
+               return;
+               }
+
+       if (((CC->quickroom.QRflags & QR_VISDIR) == 0)
+          && (CC->usersupp.axlevel<6)
+          && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
+               cprintf("%d not here.\n",ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       cprintf("%d %s|%s/files/%s\n",
+               LISTING_FOLLOWS,config.c_fqdn,BBSDIR,CC->quickroom.QRdirname);
+
+       sprintf(buf,"cd %s/files/%s; ls >%s 2>/dev/null",
+               BBSDIR,CC->quickroom.QRdirname,CC->temp);
+       system(buf);
+
+       sprintf(buf,"%s/files/%s/filedir",BBSDIR,CC->quickroom.QRdirname);
+       fd = fopen(buf,"r");
+       if (fd==NULL) fd=fopen("/dev/null","r");
+
+       ls = fopen(CC->temp,"r");
+       while (fgets(flnm,256,ls)!=NULL) {
+               flnm[strlen(flnm)-1]=0;
+               if (strucmp(flnm,"filedir")) {
+                       sprintf(buf,"%s/files/%s/%s",
+                               BBSDIR,CC->quickroom.QRdirname,flnm);
+                       stat(buf,&statbuf);
+                       strcpy(comment,"");
+                       fseek(fd,0L,0);
+                       while ((fgets(buf,256,fd)!=NULL)
+                           &&(strlen(comment)==0)) {
+                               buf[strlen(buf)-1] = 0;
+                               if ((!struncmp(buf,flnm,strlen(flnm)))
+                                  && (buf[strlen(flnm)]==' ')) 
+                                       strncpy(comment,
+                                               &buf[strlen(flnm)+1],255);
+                               }
+                       cprintf("%s|%ld|%s\n",flnm,statbuf.st_size,comment);
+                       }
+               }
+       fclose(ls);
+       fclose(fd);
+       unlink(CC->temp);
+
+       cprintf("000\n");
+       }
+
+/*
+ * get room parameters (aide or room aide command)
+ */
+void cmd_getr(void) {
+       if ((!(CC->logged_in))&&(!(CC->internal_pgm))) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if ( (!is_room_aide()) && (!(CC->internal_pgm)) ) {
+               cprintf("%d Higher access required.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       if (CC->curr_rm < 3) {
+               cprintf("%d Can't edit this room.\n",ERROR+NOT_HERE);
+               return;
+               }
+
+       getroom(&CC->quickroom,CC->curr_rm);
+       cprintf("%d%c%s|%s|%s|%d|%d\n",
+               OK,check_express(),
+               CC->quickroom.QRname,
+               ((CC->quickroom.QRflags & QR_PASSWORDED) ? CC->quickroom.QRpasswd : ""),
+               ((CC->quickroom.QRflags & QR_DIRECTORY) ? CC->quickroom.QRdirname : ""),
+               CC->quickroom.QRflags,
+               (int)CC->quickroom.QRfloor);
+       }
+
+
+/*
+ * set room parameters (aide or room aide command)
+ */
+void cmd_setr(char *args) {
+       char buf[256];
+       struct floor flbuf;
+       int old_floor;
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (!is_room_aide()) {
+               cprintf("%d Higher access required.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       if (CC->curr_rm < 3) {
+               cprintf("%d Can't edit this room.\n",ERROR+NOT_HERE);
+               return;
+               }
+
+       if (num_parms(args)>=6) {
+               getfloor(&flbuf,extract_int(args,5));
+               if ((flbuf.f_flags & F_INUSE) == 0) {
+                       cprintf("%d Invalid floor number.\n",
+                               ERROR+INVALID_FLOOR_OPERATION);
+                       return;
+                       }
+               }
+
+       lgetroom(&CC->quickroom,CC->curr_rm);
+       extract(buf,args,0); buf[20]=0;
+       strncpy(CC->quickroom.QRname,buf,19);
+       extract(buf,args,1); buf[10]=0;
+       strncpy(CC->quickroom.QRpasswd,buf,9);
+       extract(buf,args,2); buf[15]=0;
+       strncpy(CC->quickroom.QRdirname,buf,19);
+       CC->quickroom.QRflags = ( extract_int(args,3) | QR_INUSE);
+
+       /* Clean up a client boo-boo: if the client set the room to
+        * guess-name or passworded, ensure that the private flag is
+        * also set.
+        */
+       if ((CC->quickroom.QRflags & QR_GUESSNAME)
+          ||(CC->quickroom.QRflags & QR_PASSWORDED))
+               CC->quickroom.QRflags |= QR_PRIVATE;
+
+       /* Kick everyone out if the client requested it */
+       if (extract_int(args,4)) {
+               ++CC->quickroom.QRgen;
+               if (CC->quickroom.QRgen==100) CC->quickroom.QRgen=1;
+               }
+
+       old_floor = CC->quickroom.QRfloor;
+       if (num_parms(args)>=6) {
+               CC->quickroom.QRfloor = extract_int(args,5);
+               }
+
+       lputroom(&CC->quickroom,CC->curr_rm);
+
+       /* adjust the floor reference counts */
+       lgetfloor(&flbuf,old_floor);
+       --flbuf.f_ref_count;
+       lputfloor(&flbuf,old_floor);
+       lgetfloor(&flbuf,CC->quickroom.QRfloor);
+       ++flbuf.f_ref_count;
+       lputfloor(&flbuf,CC->quickroom.QRfloor);
+
+       /* create a room directory if necessary */
+       if (CC->quickroom.QRflags & QR_DIRECTORY) {
+               sprintf(buf,
+                       "mkdir ./files/%s </dev/null >/dev/null 2>/dev/null",
+               CC->quickroom.QRdirname);
+               system(buf);
+               }
+
+       sprintf(buf,"%s> edited by %s",CC->quickroom.QRname,CC->curr_user);
+       aide_message(buf);
+       cprintf("%d Ok\n",OK);
+       }
+
+
+
+/* 
+ * get the name of the room aide for this room
+ */
+void cmd_geta(void) {
+       struct usersupp usbuf;
+
+       if ((!(CC->logged_in))&&(!(CC->internal_pgm))) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (CC->curr_rm < 0) {
+               cprintf("%d No current room.\n",ERROR);
+               return;
+               }
+
+       if (getuserbynumber(&usbuf,CC->quickroom.QRroomaide)==0) {
+               cprintf("%d %s\n",OK,usbuf.fullname);
+               }
+       else {
+               cprintf("%d \n",OK);
+               }
+       }
+
+
+/* 
+ * set the room aide for this room
+ */
+void cmd_seta(char *new_ra)
+{
+       struct usersupp usbuf;
+       long newu;
+       char buf[256];
+       int post_notice;
+       
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (!is_room_aide()) {
+               cprintf("%d Higher access required.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       if (CC->curr_rm < 3) {
+               cprintf("%d Can't edit this room.\n",ERROR+NOT_HERE);
+               return;
+               }
+
+       if (getuser(&usbuf,new_ra)!=0) {
+               newu = (-1L);
+               }
+       else {
+               newu = usbuf.usernum;
+               }
+
+       lgetroom(&CC->quickroom,CC->curr_rm);
+       post_notice = 0;
+       if (CC->quickroom.QRroomaide != newu) {
+               post_notice = 1;
+               }
+       CC->quickroom.QRroomaide = newu;
+       lputroom(&CC->quickroom,CC->curr_rm);
+
+       /*
+        * We have to post the change notice _after_ writing changes to 
+        * the room table, otherwise it would deadlock!
+        */
+       if (post_notice == 1) {
+               sprintf(buf,"%s is now room aide for %s>",
+                       usbuf.fullname,CC->quickroom.QRname);
+               aide_message(buf);
+               }
+       cprintf("%d Ok\n",OK);
+       }
+
+
+/* 
+ * retrieve info file for this room
+ */
+void cmd_rinf(void) {
+       char filename[64];
+       char buf[256];
+       FILE *info_fp;
+       
+       sprintf(filename,"./info/%d",CC->curr_rm);
+       info_fp = fopen(filename,"r");
+
+       if (info_fp==NULL) {
+               cprintf("%d No info file.\n",ERROR);
+               return;
+               }
+
+       cprintf("%d Info:\n",LISTING_FOLLOWS);  
+       while (fgets(buf, 256, info_fp) != NULL) {
+               if (strlen(buf) > 0) buf[strlen(buf)-1] = 0;
+               cprintf("%s\n", buf);
+               }
+       cprintf("000\n");
+       fclose(info_fp);
+       }
+
+/*
+ * aide command: kill the current room
+ */
+void cmd_kill(char *argbuf)
+{
+       char aaa[100];
+       int a;
+       int kill_ok;
+       struct floor flbuf;
+       struct fullroom frbuf;
+       
+       kill_ok = extract_int(argbuf,0);
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (!is_room_aide()) {
+               cprintf("%d Higher access required.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       if (CC->curr_rm < 3) {
+               cprintf("%d Can't kill this room.\n",ERROR+NOT_HERE);
+               return;
+               }
+
+       if (kill_ok) {
+
+               /* first flag the room record as not in use */
+               lgetroom(&CC->quickroom,CC->curr_rm);
+               CC->quickroom.QRflags=0;
+
+               /* then delete the messages in the room */
+               get_fullroom(&frbuf, CC->curr_rm);
+               for (a=0; a<MSGSPERRM; ++a) {
+                       cdb_delete(CDB_MSGMAIN, &frbuf.FRnum[a], sizeof(long));
+                       }
+               put_fullroom(&frbuf, CC->curr_rm);
+
+               lputroom(&CC->quickroom,CC->curr_rm);
+
+
+               /* then decrement the reference count for the floor */
+               lgetfloor(&flbuf,(int)CC->quickroom.QRfloor);
+               flbuf.f_ref_count = flbuf.f_ref_count - 1;
+               lputfloor(&flbuf,(int)CC->quickroom.QRfloor);
+
+               /* tell the world what we did */
+               sprintf(aaa,"%s> killed by %s",CC->quickroom.QRname,CC->curr_user);
+               aide_message(aaa);
+               CC->curr_rm=(-1);
+               cprintf("%d '%s' deleted.\n",OK,CC->quickroom.QRname);
+               }
+       else {
+               cprintf("%d ok to delete.\n",OK);
+               }
+       }
+
+
+/*
+ * Find a free slot to create a new room in, or return -1 for error.
+ * search_dir is the direction to search in.  1 causes this function to
+ * return the first available slot, -1 gets the last available slot.
+ */
+int get_free_room_slot(int search_dir)
+{
+       int a,st;
+       struct quickroom qrbuf;
+
+       st = ((search_dir>0) ? 3 : (MAXROOMS-1));
+
+       for (a=st; ((a<MAXROOMS)&&(a>=3)); a=a+search_dir) {
+               getroom(&qrbuf,a);
+               if ((qrbuf.QRflags & QR_INUSE)==0) return(a);
+               }
+       return(-1);
+       }
+
+
+/*
+ * internal code to create a new room (returns room flags)
+ */
+unsigned create_room(int free_slot, char *new_room_name, int new_room_type, char *new_room_pass, int new_room_floor)
+{
+       struct quickroom qrbuf;
+       struct fullroom frbuf;
+       struct floor flbuf;
+       int a;
+
+       lgetroom(&qrbuf,free_slot);
+       strncpy(qrbuf.QRname,new_room_name,19);
+       strncpy(qrbuf.QRpasswd,new_room_pass,9);
+       qrbuf.QRflags = QR_INUSE;
+       if (new_room_type > 0) qrbuf.QRflags=(qrbuf.QRflags|QR_PRIVATE);
+       if (new_room_type == 1) qrbuf.QRflags=(qrbuf.QRflags|QR_GUESSNAME);
+       if (new_room_type == 2) qrbuf.QRflags=(qrbuf.QRflags|QR_PASSWORDED);
+       qrbuf.QRroomaide = (-1L);
+       if ((new_room_type > 0)&&(CREATAIDE==1))
+               qrbuf.QRroomaide=CC->usersupp.usernum;
+       qrbuf.QRhighest = 0L;
+       ++qrbuf.QRgen; if (qrbuf.QRgen>=126) qrbuf.QRgen=10;
+       qrbuf.QRfloor = new_room_floor;
+
+       /* Initialize a blank fullroom structure */
+       get_fullroom(&frbuf,free_slot);
+       for (a=0; a<MSGSPERRM; ++a) {
+               frbuf.FRnum[a]=0L;
+               }
+       put_fullroom(&frbuf,free_slot);
+
+       /* save what we just did... */
+       lputroom(&qrbuf,free_slot);
+
+       /* bump the reference count on whatever floor the room is on */
+       lgetfloor(&flbuf,(int)qrbuf.QRfloor);
+       flbuf.f_ref_count = flbuf.f_ref_count + 1;
+       lputfloor(&flbuf,(int)qrbuf.QRfloor);
+
+       /* be sure not to kick the creator out of the room! */
+       lgetuser(&CC->usersupp,CC->curr_user);
+       CC->usersupp.generation[free_slot] = qrbuf.QRgen;
+       CC->usersupp.forget[free_slot] = (-1);
+       lputuser(&CC->usersupp,CC->curr_user);
+
+       /* resume our happy day */
+       return(qrbuf.QRflags);
+       }
+
+
+/*
+ * create a new room
+ */
+void cmd_cre8(char *args)
+{
+       int cre8_ok;
+       int free_slot;
+       int a;
+       char new_room_name[256];
+       int new_room_type;
+       char new_room_pass[256];
+       int new_room_floor;
+       char aaa[256];
+       unsigned newflags;
+       struct quickroom qrbuf;
+       struct floor flbuf;
+
+       cre8_ok = extract_int(args,0);
+       extract(new_room_name,args,1);
+       new_room_name[19] = 0;
+       new_room_type = extract_int(args,2);
+       extract(new_room_pass,args,3);
+       new_room_pass[9] = 0;
+       new_room_floor = 0;
+
+       if ((strlen(new_room_name)==0) && (cre8_ok==1)) {
+               cprintf("%d Invalid room name.\n",ERROR);
+               return;
+               }
+
+       if (num_parms(args)>=5) {
+               getfloor(&flbuf,extract_int(args,4));
+               if ((flbuf.f_flags & F_INUSE) == 0) {
+                       cprintf("%d Invalid floor number.\n",
+                               ERROR+INVALID_FLOOR_OPERATION);
+                       return;
+                       }
+               else {
+                       new_room_floor = extract_int(args,4);
+                       }
+               }
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (CC->usersupp.axlevel<3) {
+               cprintf("%d You need higher access to create rooms.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       free_slot = get_free_room_slot(1);
+       if (free_slot<0) {
+               cprintf("%d There is no space available for a new room.\n",
+                       ERROR);
+               return;
+               }
+
+       if (cre8_ok==0) {
+               cprintf("%d ok to create...\n",OK);
+               return;
+               }
+
+       for (a=0; a<MAXROOMS; ++a) {
+               getroom(&qrbuf,a);
+               if ( (!strucmp(qrbuf.QRname,new_room_name))
+                  && (qrbuf.QRflags & QR_INUSE) ) {
+                       cprintf("%d '%s' already exists.\n",
+                               ERROR,qrbuf.QRname);
+                       return;
+                       }
+               }
+
+       if ((new_room_type < 0) || (new_room_type > 3)) {
+               cprintf("%d Invalid room type.\n",ERROR);
+               return;
+               }
+
+       newflags = create_room(free_slot,new_room_name,
+                       new_room_type,new_room_pass,new_room_floor);
+
+       /* post a message in Aide> describing the new room */
+       strncpy(aaa,new_room_name,255);
+       strcat(aaa,"> created by ");
+       strcat(aaa,CC->usersupp.fullname);
+       if (newflags&QR_PRIVATE) strcat(aaa," [private]");
+       if (newflags&QR_GUESSNAME) strcat(aaa,"[guessname] ");
+       if (newflags&QR_PASSWORDED) {
+               strcat(aaa,"\n Password: ");
+               strcat(aaa,new_room_pass);
+               }
+       aide_message(aaa); 
+
+       sprintf(aaa,"./info/%d",free_slot);     /* delete old info file */
+       unlink(aaa);    
+       sprintf(aaa,"./images/room.%d.gif",free_slot);  /* and picture */
+       unlink(aaa);    
+
+       cprintf("%d '%s' has been created.\n",OK,qrbuf.QRname);
+       }
+
+
+
+void cmd_einf(char *ok)
+{      /* enter info file for current room */
+       FILE *fp;
+       char infofilename[32];
+       char buf[256];
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (!is_room_aide()) {
+               cprintf("%d Higher access required.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       if (atoi(ok)==0) {
+               cprintf("%d Ok.\n",OK);
+               return;
+               }
+
+       cprintf("%d Send info...\n",SEND_LISTING);
+
+       sprintf(infofilename,"./info/%d",CC->curr_rm);
+
+       fp=fopen(infofilename,"w");
+       do {
+               client_gets(buf);
+               if (strcmp(buf,"000")) fprintf(fp,"%s\n",buf);
+               } while(strcmp(buf,"000"));
+       fclose(fp);
+
+       /* now update the room index so people will see our new info */
+       lgetroom(&CC->quickroom,CC->curr_rm);   /* lock so no one steps on us */
+       CC->quickroom.QRinfo = CC->quickroom.QRhighest + 1L;
+       lputroom(&CC->quickroom,CC->curr_rm);
+       }
+
+
+/* 
+ * cmd_lflr()   -  List all known floors
+ */
+void cmd_lflr(void) {
+       int a;
+       struct floor flbuf;
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       /* if (getuser(&CC->usersupp,CC->curr_user)) {
+               cprintf("%d Can't locate user!\n",ERROR+INTERNAL_ERROR);
+               return;
+               }
+       */
+
+       cprintf("%d Known floors:\n",LISTING_FOLLOWS);
+       
+       for (a=0; a<MAXFLOORS; ++a) {
+               getfloor(&flbuf,a);
+               if (flbuf.f_flags & F_INUSE) {
+                       cprintf("%d|%s|%d\n",
+                               a,
+                               flbuf.f_name,
+                               flbuf.f_ref_count);
+                       }
+               }
+       cprintf("000\n");
+       }
+
+
+
+/*
+ * create a new floor
+ */
+void cmd_cflr(char *argbuf)
+{
+       char new_floor_name[256];
+       struct floor flbuf;
+       int cflr_ok;
+       int free_slot = (-1);
+       int a;
+
+       extract(new_floor_name,argbuf,0);
+       cflr_ok = extract_int(argbuf,1);
+
+       
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (CC->usersupp.axlevel<6) {
+               cprintf("%d You need higher access to create rooms.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       for (a=0; a<MAXFLOORS; ++a) {
+               getfloor(&flbuf,a);
+
+               /* note any free slots while we're scanning... */
+               if ( ((flbuf.f_flags & F_INUSE)==0) 
+                    && (free_slot < 0) )  free_slot = a;
+
+               /* check to see if it already exists */
+               if ( (!strucmp(flbuf.f_name,new_floor_name))
+                    && (flbuf.f_flags & F_INUSE) ) {
+                       cprintf("%d Floor '%s' already exists.\n",
+                               ERROR+ALREADY_EXISTS,
+                               flbuf.f_name);
+                       return;
+                       }
+
+               }
+
+       if (free_slot<0) {
+               cprintf("%d There is no space available for a new floor.\n",
+                       ERROR+INVALID_FLOOR_OPERATION);
+               return;
+               }
+
+       if (cflr_ok==0) {
+               cprintf("%d ok to create...\n",OK);
+               return;
+               }
+
+       lgetfloor(&flbuf,free_slot);
+       flbuf.f_flags = F_INUSE;
+       flbuf.f_ref_count = 0;
+       strncpy(flbuf.f_name,new_floor_name,255);
+       lputfloor(&flbuf,free_slot);
+       cprintf("%d %d\n",OK,free_slot);
+       }
+
+
+
+/*
+ * delete a floor
+ */
+void cmd_kflr(char *argbuf)
+{
+       struct floor flbuf;
+       int floor_to_delete;
+       int kflr_ok;
+       int delete_ok;
+
+       floor_to_delete = extract_int(argbuf,0);
+       kflr_ok = extract_int(argbuf,1);
+
+       
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (CC->usersupp.axlevel<6) {
+               cprintf("%d You need higher access to delete floors.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       lgetfloor(&flbuf,floor_to_delete);
+
+       delete_ok = 1;  
+       if ((flbuf.f_flags & F_INUSE) == 0) {
+               cprintf("%d Floor %d not in use.\n",
+                       ERROR+INVALID_FLOOR_OPERATION,floor_to_delete);
+               delete_ok = 0;
+               }
+
+       else {
+               if (flbuf.f_ref_count != 0) {
+                       cprintf("%d Cannot delete; floor contains %d rooms.\n",
+                               ERROR+INVALID_FLOOR_OPERATION,
+                               flbuf.f_ref_count);
+                       delete_ok = 0;
+                       }
+
+               else {
+                       if (kflr_ok == 1) {
+                               cprintf("%d Ok\n",OK);
+                               }
+                       else {
+                               cprintf("%d Ok to delete...\n",OK);
+                               }
+
+                       }
+
+               }
+
+       if ( (delete_ok == 1) && (kflr_ok == 1) ) flbuf.f_flags = 0;
+       lputfloor(&flbuf,floor_to_delete);
+       }
+
+/*
+ * edit a floor
+ */
+void cmd_eflr(char *argbuf)
+{
+       struct floor flbuf;
+       int floor_num;
+       int np;
+
+       np = num_parms(argbuf);
+       if (np < 1) {
+               cprintf("%d Usage error.\n",ERROR);
+               return;
+               }
+       
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (CC->usersupp.axlevel<6) {
+               cprintf("%d You need higher access to edit floors.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       floor_num = extract_int(argbuf,0);
+       lgetfloor(&flbuf,floor_num);
+       if ( (flbuf.f_flags & F_INUSE) == 0) {
+               lputfloor(&flbuf,floor_num);
+               cprintf("%d Floor %d is not in use.\n",
+                       ERROR+INVALID_FLOOR_OPERATION,floor_num);
+               return;
+               }
+       if (np >= 2) extract(flbuf.f_name,argbuf,1);
+       lputfloor(&flbuf,floor_num);
+       
+       cprintf("%d Ok\n",OK);
+       }
diff --git a/citadel/rooms.c b/citadel/rooms.c
new file mode 100644 (file)
index 0000000..02f8dc4
--- /dev/null
@@ -0,0 +1,968 @@
+/* Citadel/UX room-oriented routines */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include "citadel.h"
+
+#define IFEXPERT if (userflags&US_EXPERT)
+#define IFNEXPERT if ((userflags&US_EXPERT)==0)
+#define IFAIDE if (axlevel>=6)
+#define IFNAIDE if (axlevel<6)
+
+
+long finduser();
+void sttybbs();
+void extract();
+int extract_int();
+void hit_any_key();
+int yesno();
+int yesno_d();
+void strprompt();
+void newprompt();
+int struncmp();
+void dotgoto();
+long extract_long();
+void serv_read();
+void formout();
+int inkey();
+int fmout();
+void citedit();
+void progress();
+int pattern();
+int file_checksum();
+int nukedir();
+void color();
+
+extern unsigned room_flags;
+extern char room_name[];
+extern char temp[];
+extern char tempdir[];
+extern int editor_pid;
+extern char editor_path[];
+extern int screenwidth;
+extern int screenheight;
+extern char fullname[];
+extern int userflags;
+extern char sigcaught;
+extern char floor_mode;
+extern char curr_floor;
+
+
+extern int ugnum;
+extern long uglsn;
+extern char ugname[];
+
+extern char floorlist[128][256];
+
+
+void load_floorlist() {
+       int a;
+       char buf[256];
+
+       for (a=0; a<128; ++a) floorlist[a][0] = 0;
+
+       serv_puts("LFLR");
+       serv_gets(buf);
+       if (buf[0]!='1') {
+               strcpy(floorlist[0],"Main Floor");
+               return;
+               }
+       while (serv_gets(buf), strcmp(buf,"000")) {
+               extract(floorlist[extract_int(buf,0)],buf,1);
+               }
+       }
+
+void listrms(variety)
+char *variety; {
+       char buf[256];
+       char rmname[32];
+       int f,c;
+
+       serv_puts(variety);
+       serv_gets(buf);
+       if (buf[0]!='1') return;
+       c = 1;
+       sigcaught = 0;
+       sttybbs(SB_YES_INTR);
+       while (serv_gets(buf), strcmp(buf,"000")) if (sigcaught==0) {
+               extract(rmname,buf,0);
+               if ((c + strlen(rmname) + 4) > screenwidth) {
+                       printf("\n");
+                       c = 1;
+                       }
+               f = extract_int(buf,1);
+               if (f & QR_PRIVATE) {
+                       color(1);
+                       }
+               else {
+                       color(2);
+                       }
+               printf("%s",rmname);
+               if ((f & QR_DIRECTORY) && (f & QR_NETWORK)) printf("}  ");
+               else if (f & QR_DIRECTORY) printf("]  ");
+               else if (f & QR_NETWORK) printf(")  ");
+               else printf(">  ");
+               c = c + strlen(rmname) + 3;
+               }
+       color(7);
+       sttybbs(SB_NO_INTR);
+       }
+
+
+void list_other_floors() {
+       int a,c;
+
+       c = 1;
+       for (a=0; a<128; ++a) if ((strlen(floorlist[a])>0)&&(a!=curr_floor)) {
+               if ((c + strlen(floorlist[a]) + 4) > screenwidth) {
+                       printf("\n");
+                       c = 1;
+                       }
+               printf("%s:  ",floorlist[a]);
+               c = c + strlen(floorlist[a]) + 3;
+               }
+       }
+
+
+/*
+ * List known rooms.  kn_floor_mode should be set to 0 for a 'flat' listing,
+ * 1 to list rooms on the current floor, or 1 to list rooms on all floors.
+ */
+void knrooms(kn_floor_mode)
+int kn_floor_mode; {
+       char buf[256];
+       int a;
+
+       load_floorlist();
+
+       if (kn_floor_mode == 0) {
+               color(3);
+               printf("\n   Rooms with unread messages:\n");
+               listrms("LKRN");
+               color(3);
+               printf("\n\n   No unseen messages in:\n");
+               listrms("LKRO");
+               printf("\n");
+               }
+
+       if (kn_floor_mode == 1) {
+               color(3);
+               printf("\n   Rooms with unread messages on %s:\n",
+                       floorlist[(int)curr_floor]);
+               sprintf(buf,"LKRN %d",curr_floor);
+               listrms(buf);
+               color(3);
+               printf("\n\n   Rooms with no new messages on %s:\n",
+                       floorlist[(int)curr_floor]);
+               sprintf(buf,"LKRO %d",curr_floor);
+               listrms(buf);
+               color(3);
+               printf("\n\n   Other floors:\n");
+               list_other_floors();
+               printf("\n");
+               }
+
+       if (kn_floor_mode == 2) {
+               for (a=0; a<128; ++a) if (floorlist[a][0]!=0) {
+                       color(3);
+                       printf("\n   Rooms on %s:\n",floorlist[a]);
+                       sprintf(buf,"LKRA %d",a);
+                       listrms(buf);
+                       printf("\n");
+                       }
+               }
+       
+       color(7);
+       IFNEXPERT hit_any_key();
+       }
+
+
+void listzrooms() {            /* list public forgotten rooms */
+       color(3);
+       printf("\n   Forgotten public rooms:\n");
+       listrms("LZRM");
+       printf("\n");
+       color(7);
+       IFNEXPERT hit_any_key();
+       }
+
+
+int set_room_attr(ibuf,prompt,sbit)
+int ibuf;
+char *prompt;
+unsigned sbit; {
+       int a;
+
+       printf("%s [%s]? ",prompt,((ibuf&sbit) ? "Yes":"No"));
+       a=yesno_d(ibuf&sbit);
+       ibuf=(ibuf|sbit);
+       if (!a) ibuf=(ibuf^sbit);
+       return(ibuf);
+       }
+
+
+
+/*
+ * Select a floor (used in several commands)
+ * The supplied argument is the 'default' floor number.
+ * This function returns the selected floor number.
+ */
+int select_floor(rfloor)
+int rfloor; {
+       int a, newfloor;
+       char floorstr[256];
+
+       if (floor_mode == 1) {
+               if (floorlist[(int)curr_floor][0]==0) load_floorlist();
+
+               do {
+                       newfloor = (-1);
+                       strcpy(floorstr,&floorlist[rfloor][0]);
+                       strprompt("Which floor",floorstr,256);
+                       for (a=0; a<128; ++a) {
+                               if (!strucmp(floorstr,&floorlist[a][0]))
+                                       newfloor = a;
+                               if ((newfloor<0)&&(!struncmp(floorstr,
+                                       &floorlist[a][0],strlen(floorstr))))
+                                               newfloor = a;
+                               if ((newfloor<0)&&(pattern(&floorlist[a][0],
+                                       floorstr)>=0)) newfloor = a;
+                               }
+                       if (newfloor<0) {
+                               printf("\n One of:\n");
+                               for (a=0; a<128; ++a)
+                                       if (floorlist[a][0]!=0)
+                                               printf("%s\n",
+                                                       &floorlist[a][0]);
+                               }
+                       } while(newfloor < 0);
+               return(newfloor);
+               }
+       return(rfloor);
+       }
+
+
+
+
+/*
+ * .<A>ide <E>dit room
+ */
+void editthisroom() {
+       char rname[20];
+       char rpass[10];
+       char rdir[15];
+       unsigned rflags;
+       int rbump;
+       char raide[32];
+       char buf[256];
+       int rfloor;
+
+       serv_puts("GETR");
+       serv_gets(buf);
+       if (buf[0]!='2') {
+               printf("%s\n",&buf[4]);
+               return;
+               }
+
+       extract(rname,&buf[4],0);
+       extract(rpass,&buf[4],1);
+       extract(rdir, &buf[4],2);
+       rflags = extract_int(&buf[4],3);
+       rfloor = extract_int(&buf[4],4);
+       rbump = 0;
+
+       serv_puts("GETA");
+       serv_gets(buf);
+       if (buf[0]=='2') strcpy(raide,&buf[4]);
+       else strcpy(raide,"");
+       if (strlen(raide)==0) strcpy(raide,"none");
+
+       strprompt("Room name",rname,19);
+
+       rfloor = select_floor(rfloor);
+       rflags = set_room_attr(rflags,"Private room",QR_PRIVATE);
+       if (rflags & QR_PRIVATE)
+               rflags = set_room_attr(rflags,
+                       "Accessible by guessing room name",QR_GUESSNAME);
+
+       /* if it's public, clear the privacy classes */
+       if ((rflags & QR_PRIVATE)==0) {
+               if (rflags & QR_GUESSNAME)  rflags = rflags - QR_GUESSNAME;
+               if (rflags & QR_PASSWORDED) rflags = rflags - QR_PASSWORDED;
+               }
+
+       /* if it's private, choose the privacy classes */
+       if ( (rflags & QR_PRIVATE)
+          && ( (rflags & QR_GUESSNAME) == 0) ) {
+               rflags = set_room_attr(rflags,
+                       "Accessible by entering a password",QR_PASSWORDED);
+               }
+       if ( (rflags & QR_PRIVATE)
+          && ((rflags&QR_PASSWORDED)==QR_PASSWORDED) ) {
+               strprompt("Room password",rpass,9);
+               }
+
+       if ((rflags&QR_PRIVATE)==QR_PRIVATE) {
+               printf("Cause current users to forget room [No] ? ");
+               if (yesno_d(0)==1) rbump = 1;
+               }
+
+       rflags = set_room_attr(rflags,"Preferred users only",QR_PREFONLY);
+       rflags = set_room_attr(rflags,"Read-only room",QR_READONLY);
+       rflags = set_room_attr(rflags,"Directory room",QR_DIRECTORY);
+       rflags = set_room_attr(rflags,"Permanent room",QR_PERMANENT);
+       if (rflags & QR_DIRECTORY) {
+               strprompt("Directory name",rdir,14);
+               rflags = set_room_attr(rflags,"Uploading allowed",QR_UPLOAD);
+               rflags = set_room_attr(rflags,"Downloading allowed",
+                                                               QR_DOWNLOAD);
+               rflags = set_room_attr(rflags,"Visible directory",QR_VISDIR);
+               }
+       rflags = set_room_attr(rflags,"Network shared room",QR_NETWORK);
+       rflags = set_room_attr(rflags,
+               "Automatically make all messages anonymous",QR_ANONONLY);
+       if ( (rflags & QR_ANONONLY) == 0) {
+               rflags = set_room_attr(rflags,
+                       "Ask users whether to make messages anonymous",
+                       QR_ANON2);
+               }
+
+       do {
+               strprompt("Room aide (or 'none')",raide,29);
+               if (!strucmp(raide,"none")) {
+                       strcpy(raide,"");
+                       strcpy(buf,"200");
+                       }
+               else {
+                       sprintf(buf,"QUSR %s",raide);
+                       serv_puts(buf);
+                       serv_gets(buf);
+                       if (buf[0]!='2') printf("%s\n",&buf[4]);
+                       }
+               } while(buf[0]!='2');
+
+       if (!strucmp(raide,"none")) strcpy(raide,"");
+
+       printf("Save changes (y/n)? ");
+       if (yesno()==1) {
+               sprintf(buf,"SETR %s|%s|%s|%d|%d|%d",
+                       rname,rpass,rdir,rflags,rbump,rfloor);
+               serv_puts(buf);
+               serv_gets(buf);
+               printf("%s\n",&buf[4]);
+               sprintf(buf,"SETA %s",raide);
+               serv_puts(buf);
+               serv_gets(buf);
+               if (buf[0]=='2') dotgoto(rname,2);
+               }
+       }
+
+
+/*
+ * un-goto the previous room
+ */
+void ungoto() { 
+       char buf[256];
+       
+       if (!strcmp(ugname,"")) return;
+       sprintf(buf,"GOTO %s",ugname);
+       serv_puts(buf);
+       serv_gets(buf);
+       if (buf[0]!='2') {
+               printf("%s\n",&buf[4]);
+               return;
+               }
+       sprintf(buf,"SLRP %ld",uglsn);
+       serv_puts(buf);
+       serv_gets(buf);
+       if (buf[0]!='2') printf("%s\n",&buf[4]);
+       strcpy(buf,ugname);
+       strcpy(ugname,"");
+       dotgoto(buf,0);
+       }
+
+
+/*
+ * download()  -  download a file or files.  The argument passed to this
+ *                function determines which protocol to use.
+ */
+void download(proto)
+int proto; {
+
+/*
+  - 0 = paginate, 1 = xmodem, 2 = raw, 3 = ymodem, 4 = zmodem, 5 = save
+*/
+
+
+       char buf[256];
+       char dbuf[4096];
+       char filename[256];
+       long total_bytes = 0L;
+       long transmitted_bytes = 0L;
+       long aa,bb;
+       int a,b;
+       int packet;
+       FILE *tpipe = NULL;
+       FILE *savefp = NULL;
+       int proto_pid;
+       int broken = 0;
+
+       if ((room_flags & QR_DOWNLOAD) == 0) {
+               printf("*** You cannot download from this room.\n");
+               return;
+               }
+       
+       newprompt("Enter filename: ",filename,255);
+
+       sprintf(buf,"OPEN %s",filename);
+       serv_puts(buf);
+       serv_gets(buf);
+       if (buf[0]!='2') {
+               printf("%s\n",&buf[4]);
+               return;
+               }
+       total_bytes = extract_long(&buf[4],0);
+
+
+       /* Here's the code for simply transferring the file to the client,
+        * for folks who have their own clientware.  It's a lot simpler than
+        * the [XYZ]modem code below...
+        */
+       if (proto == 5) {
+               printf("Enter the name of the directory to save '%s'\n",
+                       filename);
+               printf("to, or press return for the current directory.\n");
+               newprompt("Directory: ",dbuf,256);
+               if (strlen(dbuf)==0) strcpy(dbuf,".");
+               strcat(dbuf,"/");
+               strcat(dbuf,filename);
+               
+               savefp = fopen(dbuf,"w");
+               if (savefp == NULL) {
+                       printf("Cannot open '%s': %s\n",dbuf,strerror(errno));
+                       /* close the download file at the server */
+                       serv_puts("CLOS");
+                       serv_gets(buf);
+                       if (buf[0]!='2') {
+                               printf("%s\n",&buf[4]);
+                               }
+                       return;
+                       }
+               progress(0,total_bytes);
+               while ( (transmitted_bytes < total_bytes) && (broken == 0) ) {
+                       bb = total_bytes - transmitted_bytes;
+                       aa = ((bb < 4096) ? bb : 4096);
+                       sprintf(buf,"READ %ld|%ld",transmitted_bytes,aa);
+                       serv_puts(buf);
+                       serv_gets(buf);
+                       if (buf[0]!='6') {
+                               printf("%s\n",&buf[4]);
+                               return;
+                               }
+                       packet = extract_int(&buf[4],0);
+                       serv_read(dbuf,packet);
+                       if (fwrite(dbuf,packet,1,savefp) < 1) broken = 1;
+                       transmitted_bytes = transmitted_bytes + (long)packet;
+                       progress(transmitted_bytes,total_bytes);
+                       }
+               fclose(savefp);
+               /* close the download file at the server */
+               serv_puts("CLOS");
+               serv_gets(buf);
+               if (buf[0]!='2') {
+                       printf("%s\n",&buf[4]);
+                       }
+               return;
+               }
+
+
+       mkdir(tempdir,0700);
+#ifdef USE_MKFIFO
+       sprintf(buf,"mkfifo %s/%s",tempdir,filename);
+#else
+       sprintf(buf,"mknod %s/%s p",tempdir,filename);
+#endif
+       system(buf);
+
+       /* We do the remainder of this function as a separate process in
+        * order to allow recovery if the transfer is aborted.  If the
+        * file transfer program aborts, the first child process receives a
+        * "broken pipe" signal and aborts.  We *should* be able to catch
+        * this condition with signal(), but it doesn't seem to work on all
+        * systems.
+        */
+       a = fork();
+       if (a!=0) {
+               /* wait for the download to finish */
+               while (wait(&b)!=a) ;;
+               sttybbs(0);
+               /* close the download file at the server */
+               serv_puts("CLOS");
+               serv_gets(buf);
+               if (buf[0]!='2') {
+                       printf("%s\n",&buf[4]);
+                       }
+               /* clean up the temporary directory */
+               nukedir(tempdir);
+               return;
+               }
+
+       sprintf(buf,"%s/%s",tempdir,filename);  /* full pathname */
+
+       /* The next fork() creates a second child process that is used for
+        * the actual file transfer program (usually sz).
+        */
+       proto_pid = fork();
+       if (proto_pid == 0) {
+               if (proto==0)  {
+                       sttybbs(0);
+                       signal(SIGINT,SIG_DFL);
+                       signal(SIGQUIT,SIG_DFL);
+                       sprintf(dbuf,"SHELL=/dev/null; export SHELL; TERM=dumb; export TERM; exec more -d <%s",buf);
+                       system(dbuf);
+                       sttybbs(SB_NO_INTR);
+                       exit(0);
+                       }
+               sttybbs(3);
+               signal(SIGINT,SIG_DFL);
+               signal(SIGQUIT,SIG_DFL);
+               if (proto==1) execlp("sx","sx",buf,NULL);
+               if (proto==2) execlp("cat","cat",buf,NULL);
+               if (proto==3) execlp("sb","sb",buf,NULL);
+               if (proto==4) execlp("sz","sz",buf,NULL);
+               execlp("cat","cat",buf,NULL);
+               exit(1);
+               }
+
+       tpipe = fopen(buf,"w");
+
+       while ( (transmitted_bytes < total_bytes) && (broken == 0) ) {
+               bb = total_bytes - transmitted_bytes;
+               aa = ((bb < 4096) ? bb : 4096);
+               sprintf(buf,"READ %ld|%ld",transmitted_bytes,aa);
+               serv_puts(buf);
+               serv_gets(buf);
+               if (buf[0]!='6') {
+                       printf("%s\n",&buf[4]);
+                       return;
+                       }
+               packet = extract_int(&buf[4],0);
+               serv_read(dbuf,packet);
+               if (fwrite(dbuf,packet,1,tpipe) < 1) broken = 1;
+               transmitted_bytes = transmitted_bytes + (long)packet;
+               }
+       if (tpipe!=NULL) fclose(tpipe);
+
+       /* Hang out and wait for the file transfer program to finish */
+       while (wait(&a) != proto_pid) ;;
+
+
+       putc(7,stdout);
+       exit(0);        /* transfer control back to the main program */
+       }
+
+
+/*
+ * read directory of this room
+ */
+void roomdir() {
+       char flnm[256];
+       char flsz[32];
+       char comment[256];
+       char buf[256];
+
+       serv_puts("RDIR");
+       serv_gets(buf);
+       if (buf[0]!='1') {
+               printf("%s\n",&buf[4]);
+               return;
+               }
+
+       extract(comment,&buf[4],0);
+       extract(flnm,&buf[4],1);
+       printf("\nDirectory of %s on %s\n",flnm,comment);
+       printf("-----------------------\n");
+       while (serv_gets(buf), strcmp(buf,"000")) {
+               extract(flnm,buf,0);
+               extract(flsz,buf,1);
+               extract(comment,buf,2);
+               if (strlen(flnm)<=14)
+                       printf("%-14s %8s %s\n",flnm,flsz,comment);
+               else
+                       printf("%s\n%14s %8s %s\n",flnm,"",flsz,comment);
+               }
+       }
+
+
+/*
+ * add a user to a private room
+ */
+void invite() {
+       char aaa[31],bbb[256];
+
+       if ((room_flags & QR_PRIVATE)==0) {
+               printf("This is not a private room.\n");
+               return;
+               }
+
+       newprompt("Name of user? ",aaa,30);
+       if (aaa[0]==0) return;
+
+       sprintf(bbb,"INVT %s",aaa);
+       serv_puts(bbb);
+       serv_gets(bbb);
+       printf("%s\n",&bbb[4]);
+       }
+
+
+/*
+ * kick a user out of a room
+ */
+void kickout() {
+       char aaa[31],bbb[256];
+
+       if ((room_flags & QR_PRIVATE)==0) {
+               printf("Note: this is not a private room.  Kicking a user ");
+               printf("out of this room will only\nhave the same effect ");
+               printf("as if they <Z>apped the room.\n\n");
+               }
+
+       newprompt("Name of user? ",aaa,30);
+       if (aaa[0]==0) return;
+
+       sprintf(bbb,"KICK %s",aaa);
+       serv_puts(bbb);
+       serv_gets(bbb);
+       printf("%s\n",&bbb[4]);
+       }
+
+
+/*
+ * aide command: kill the current room
+ */
+void killroom() {
+       char aaa[100];
+
+       serv_puts("KILL 0");
+       serv_gets(aaa);
+       if (aaa[0]!='2') {
+               printf("%s\n",&aaa[4]);
+               return;
+               }
+
+       printf("Are you sure you want to kill this room? ");
+       if (yesno()==0) return;
+
+       serv_puts("KILL 1");
+       serv_gets(aaa);
+       printf("%s\n",&aaa[4]);
+       if (aaa[0]!='2') return;
+       dotgoto("_BASEROOM_",0);
+       }
+
+void forget() {        /* forget the current room */
+       char cmd[256];
+
+       printf("Are you sure you want to forget this room? ");
+       if (yesno()==0) return;
+
+       serv_puts("FORG");
+       serv_gets(cmd);
+       if (cmd[0]!='2') {
+               printf("%s\n",&cmd[4]);
+               return;
+               }
+
+       /* now return to the lobby */
+       dotgoto("_BASEROOM_",0);
+       }
+
+
+/*
+ * create a new room
+ */
+void entroom() {
+       char cmd[256];
+       char new_room_name[20];
+       int new_room_type;
+       char new_room_pass[10];
+       int new_room_floor;
+       int a,b;
+
+       serv_puts("CRE8 0");
+       serv_gets(cmd);
+       
+       if (cmd[0]!='2') {
+               printf("%s\n",&cmd[4]);
+               return;
+               }
+       
+       newprompt("Name for new room? ",new_room_name,19);
+       if (strlen(new_room_name)==0) return;
+       for (a=0; a<strlen(new_room_name); ++a)
+               if (new_room_name[a] == '|') new_room_name[a]='_';
+
+       new_room_floor = select_floor((int)curr_floor);
+
+       IFNEXPERT formout("roomaccess");
+       do {
+               printf("<?>Help\n<1>Public room\n<2>Guess-name room\n");
+               printf("<3>Passworded room\n<4>Invitation-only room\n");
+               printf("Enter room type: ");
+               do {
+                       b=inkey();
+                       } while (((b<'1')||(b>'4')) && (b!='?'));
+               if (b=='?') {
+                       printf("?\n");
+                       formout("roomaccess");
+                       }
+               } while ((b<'1')||(b>'4'));
+       b=b-48;
+       printf("%d\n",b);
+       new_room_type = b - 1;
+       if (new_room_type==2) {
+               newprompt("Enter a room password: ",new_room_pass,9);
+               for (a=0; a<strlen(new_room_pass); ++a)
+                       if (new_room_pass[a] == '|') new_room_pass[a]='_';
+               }
+       else strcpy(new_room_pass,"");
+
+       printf("\042%s\042, a",new_room_name);
+       if (b==1) printf(" public room.");
+       if (b==2) printf(" guess-name room.");
+       if (b==3) printf(" passworded room, password: %s",new_room_pass);
+       if (b==4) printf("n invitation-only room.");
+       printf("\nInstall it? (y/n) : ");
+       a=yesno();
+       if (a==0) return;
+
+       sprintf(cmd, "CRE8 1|%s|%d|%s|%d", new_room_name,
+               new_room_type, new_room_pass, new_room_floor);
+       serv_puts(cmd);
+       serv_gets(cmd);
+       if (cmd[0]!='2') {
+               printf("%s\n",&cmd[4]);
+               return;
+               }
+
+       /* command succeeded... now GO to the new room! */
+       dotgoto(new_room_name,0);
+       }
+
+
+
+void readinfo() {      /* read info file for current room */
+       char cmd[256];
+       
+       sprintf(cmd,"RINF");
+       serv_puts(cmd);
+       serv_gets(cmd);
+
+       if (cmd[0]!='1') return;
+
+       fmout(screenwidth,NULL,
+               ((userflags & US_PAGINATOR) ? 1 : 0),
+               screenheight,0,1);
+       }
+
+
+/*
+ * <W>ho knows room...
+ */
+void whoknows() {
+       char buf[256];
+       serv_puts("WHOK");
+       serv_gets(buf);
+       if (buf[0]!='1') {
+               printf("%s\n",&buf[5]);
+               return;
+               }
+       sigcaught = 0;
+       sttybbs(SB_YES_INTR);
+       while (serv_gets(buf), strncmp(buf,"000",3)) {
+               if (sigcaught==0) printf("%s\n",buf);
+               }
+       sttybbs(SB_NO_INTR);
+       }
+
+
+void do_edit(desc,read_cmd,check_cmd,write_cmd)
+char *desc;
+char *read_cmd;
+char *check_cmd;
+char *write_cmd;
+       {
+       FILE *fp;
+       char cmd[256];
+       int b,cksum,editor_exit;
+
+
+       if (strlen(editor_path)==0) {
+               printf("Do you wish to re-enter %s? ",desc);
+               if (yesno()==0) return;
+               }
+
+       fp = fopen(temp,"w");
+       fclose(fp);
+
+       serv_puts(check_cmd);
+       serv_gets(cmd);
+       if (cmd[0]!='2') {
+               printf("%s\n",&cmd[4]);
+               return;
+               }
+
+       if (strlen(editor_path)>0) {
+               serv_puts(read_cmd);
+               serv_gets(cmd);
+               if (cmd[0]=='1') {
+                       fp = fopen(temp,"w");
+                       while (serv_gets(cmd), strcmp(cmd,"000")) {
+                               fprintf(fp,"%s\n",cmd);
+                               }
+                       fclose(fp);
+                       }
+               }
+
+       cksum = file_checksum(temp);
+
+       if (strlen(editor_path)>0) {
+               editor_pid=fork();
+               if (editor_pid==0) {
+                        chmod(temp,0600);
+                       sttybbs(SB_RESTORE);
+                       execlp(editor_path,editor_path,temp,NULL);
+                       exit(1);
+                       }
+               if (editor_pid>0) do {
+                       editor_exit = 0;
+                       b=wait(&editor_exit);
+                       } while((b!=editor_pid)&&(b>=0));
+               editor_pid = (-1);
+               printf("Executed %s\n", editor_path);
+               sttybbs(0);
+               }
+       else {
+               printf("Entering %s.  ",desc);
+               printf("Press return twice when finished.\n");
+               fp=fopen(temp,"r+");
+               citedit(fp,0);
+               fclose(fp);
+               }
+
+       if (file_checksum(temp) == cksum) {
+               printf("*** Aborted.\n");
+               }
+
+       else {
+               serv_puts(write_cmd);
+               serv_gets(cmd);
+               if (cmd[0]!='4') {
+                       printf("%s\n",&cmd[4]);
+                       return;
+                       }
+
+               fp=fopen(temp,"r");
+               while (fgets(cmd,255,fp)!=NULL) {
+                       cmd[strlen(cmd)-1] = 0;
+                       serv_puts(cmd);
+                       }
+               fclose(fp);
+               serv_puts("000");
+               }
+
+       unlink(temp);
+       }
+
+
+void enterinfo() {             /* edit info file for current room */
+       do_edit("the Info file for this room","RINF","EINF 0","EINF 1");
+       }
+
+void enter_bio() {
+       char cmd[256];
+       sprintf(cmd,"RBIO %s",fullname);
+       do_edit("your Bio",cmd,"NOOP","EBIO");
+       }
+
+/*
+ * create a new floor
+ */
+void create_floor() {
+       char buf[256];
+       char newfloorname[256];
+
+       serv_puts("CFLR xx|0");
+       serv_gets(buf);
+       if (buf[0]!='2') {
+               printf("%s\n",&buf[4]);
+               return;
+               }
+
+       newprompt("Name for new floor: ",newfloorname,255);
+       sprintf(buf,"CFLR %s|1",newfloorname);
+       serv_puts(buf);
+       serv_gets(buf);
+       if (buf[0]=='2') {
+               printf("Floor has been created.\n");
+               }
+       else {
+               printf("%s\n",&buf[4]);
+               }
+       }
+
+/*
+ * edit the current floor
+ */
+void edit_floor() {
+       char buf[256];
+
+       if (floorlist[(int)curr_floor][0]==0) load_floorlist();
+       strprompt("New floor name",&floorlist[(int)curr_floor][0],255);
+       sprintf(buf,"EFLR %d|%s",curr_floor,&floorlist[(int)curr_floor][0]);
+       serv_puts(buf);
+       serv_gets(buf);
+       printf("%s\n",&buf[4]);
+       load_floorlist();
+       }
+
+
+
+
+/*
+ * kill the current floor 
+ */
+void kill_floor() {
+       int floornum_to_delete,a;
+       char buf[256];
+
+       if (floorlist[(int)curr_floor][0]==0) load_floorlist();
+       do {
+               floornum_to_delete = (-1);
+               printf("(Press return to abort)\n");
+               newprompt("Delete which floor? ",buf,255);
+               if (strlen(buf)==0) return;
+               for (a=0; a<128; ++a)
+                       if (!strucmp(&floorlist[a][0],buf))
+                               floornum_to_delete = a;
+               if (floornum_to_delete < 0) {
+                       printf("No such floor.  Select one of:\n");
+                       for (a=0; a<128; ++a)
+                               if (floorlist[a][0]!=0)
+                                       printf("%s\n",&floorlist[a][0]);
+                       }
+               } while (floornum_to_delete < 0);
+       sprintf(buf,"KFLR %d|1",floornum_to_delete);
+       serv_puts(buf);
+       serv_gets(buf);
+       printf("%s\n",&buf[4]);
+       }
diff --git a/citadel/routines.c b/citadel/routines.c
new file mode 100644 (file)
index 0000000..2d47c6b
--- /dev/null
@@ -0,0 +1,668 @@
+/* Citadel/UX support routines */
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <pwd.h>
+#include <signal.h>
+#include <dirent.h>
+#include <errno.h>
+
+
+#define ROUTINES_C
+
+#include "citadel.h"
+
+char inkey();
+void sttybbs();
+void newprompt();
+void val_user();
+int intprompt();
+void formout();
+void logoff();
+void set_keepalives();
+void strprompt();
+void newprompt();
+void color();
+
+#define IFAIDE if(axlevel>=6)
+#define IFNAIDE if (axlevel<6)
+
+extern unsigned userflags;
+extern char *axdefs[7];
+extern char sigcaught;
+extern struct serv_info serv_info;
+extern char rc_floor_mode;
+
+int struncmp(lstr,rstr,len)
+char lstr[],rstr[];
+int len; {
+       int pos = 0;
+       char lc,rc;
+       while (pos<len) {
+               lc=tolower(lstr[pos]);
+               rc=tolower(rstr[pos]);
+               if ((lc==0)&&(rc==0)) return(0);
+               if (lc<rc) return(-1);
+               if (lc>rc) return(1);
+               pos=pos+1;
+               }
+       return(0);
+       }
+
+
+/* 
+ * check for the presence of a character within a string (returns count)
+ */
+int haschar(st,ch)
+char st[];
+int ch; {
+       int a,b;
+       b=0;
+       for (a=0; a<strlen(st); ++a) if (st[a]==ch) ++b;
+       return(b);
+       }
+
+
+/*
+ * num_parms()  -  discover number of parameters...
+ */
+int num_parms(source)
+char source[]; {
+       int a;
+       int count = 1;
+
+       for (a=0; a<strlen(source); ++a) 
+               if (source[a]=='|') ++count;
+       return(count);
+       }
+
+/*
+ * extract()  -  extract a parameter from a series of "|" separated...
+ */
+void extract(dest,source,parmnum)
+char dest[];
+char source[];
+int parmnum; {
+       char buf[256];
+       int count = 0;
+       int n;
+
+       n = num_parms(source);
+
+       if (parmnum >= n) {
+               strcpy(dest,"");
+               return;
+               }
+       strcpy(buf,source);
+       if ( (parmnum == 0) && (n == 1) ) {
+               strcpy(dest,buf);
+               return;
+               }
+
+       while (count++ < parmnum) do {
+               strcpy(buf,&buf[1]);
+               } while( (strlen(buf)>0) && (buf[0]!='|') );
+       if (buf[0]=='|') strcpy(buf,&buf[1]);
+       for (count = 0; count<strlen(buf); ++count)
+               if (buf[count] == '|') buf[count] = 0;
+       strcpy(dest,buf);
+       }
+
+/*
+ * extract_int()  -  extract an int parm w/o supplying a buffer
+ */
+int extract_int(source,parmnum)
+char *source;
+int parmnum; {
+       char buf[256];
+       
+       extract(buf,source,parmnum);
+       return(atoi(buf));
+       }
+
+/*
+ * extract_long()  -  extract a long parm w/o supplying a buffer
+ */
+long extract_long(source,parmnum)
+char *source;
+int parmnum; {
+       char buf[256];
+       
+       extract(buf,source,parmnum);
+       return(atol(buf));
+       }
+
+void back(spaces) /* Destructive backspace */
+int spaces; {
+int a;
+       for (a=1; a<=spaces; ++a) {
+               putc(8,stdout); putc(32,stdout); putc(8,stdout);
+               }
+       }
+
+int yesno() { /* Returns 1 for yes, 0 for no */
+int a;
+       while (1) {
+               a=inkey(); a=tolower(a);
+               if (a=='y') { printf("Yes\n"); return(1); }
+               if (a=='n') { printf("No\n");  return(0); }
+               }
+       }
+
+int yesno_d(d) /* Returns 1 for yes, 0 for no, arg is default value */
+int d; {
+int a;
+       while (1) {
+               a=inkey(); a=tolower(a);
+               if (a==13) a=(d ? 'y' : 'n');
+               if (a=='y') { printf("Yes\n"); return(1); }
+               if (a=='n') { printf("No\n");  return(0); }
+               }
+       }
+
+
+void hit_any_key() {           /* hit any key to continue */
+       int a,b;
+
+       printf("%s\r",serv_info.serv_moreprompt);
+       sttybbs(0);
+       b=inkey();
+       for (a=0; a<strlen(serv_info.serv_moreprompt); ++a)
+               putc(' ',stdout);
+       putc(13,stdout);
+       sttybbs(1);
+       if (b==NEXT_KEY) sigcaught = SIGINT;
+       if (b==STOP_KEY) sigcaught = SIGQUIT;
+       }
+
+/*
+ * change a user's access level
+ */
+void edituser(userbuf)
+struct usersupp *userbuf; {
+       char who[256];
+       char buf[256];
+
+       newprompt("User name: ",who,25);
+       sprintf(buf,"QUSR %s",who);
+       serv_puts(buf);
+       serv_gets(buf);
+       if (buf[0]!='2') {
+               printf("%s\n",&buf[4]);
+               return;
+               }
+       
+       val_user(who);
+       }
+
+
+int set_attr(sval,prompt,sbit)
+int sval;
+char *prompt;
+unsigned sbit; {
+       int a;
+       int temp;
+
+       temp = sval;
+       color(3);
+       printf("%45s [", prompt);
+       color(1);
+       printf("%3s", ((temp&sbit) ? "Yes":"No"));
+       color(3);
+       printf("]? ");
+       color(2);
+       a=yesno_d(temp&sbit);
+       color(7);
+       temp=(temp|sbit);
+       if (!a) temp=(temp^sbit);
+       return(temp);
+       }
+
+/*
+ * modes are:  0 - .EC command, 1 - .EC for new user,
+ *             2 - toggle Xpert mode  3 - toggle floor mode
+ */
+void enter_config(mode)
+int mode; {
+       int width,height,flags;
+       char buf[128];
+
+       sprintf(buf,"GETU");
+       serv_puts(buf);
+       serv_gets(buf);
+       if (buf[0]!='2') {
+               printf("%s\n",&buf[4]);
+               return;
+               }
+
+       width = extract_int(&buf[4],0);
+       height = extract_int(&buf[4],1);
+       flags = extract_int(&buf[4],2);
+
+       if ((mode==0)||(mode==1)) {
+
+        width = intprompt("Enter your screen width",width,20,255);
+        height = intprompt("Enter your screen height",height,3,255);
+        flags = set_attr(flags,
+               "Are you an experienced Citadel user",US_EXPERT);
+        if ( ((flags&US_EXPERT)==0) && (mode==1))
+               return;
+        flags = set_attr(flags,
+               "Print last old message on New message request",US_LASTOLD);
+        if ((flags&US_EXPERT)==0) formout("unlisted");
+        flags = set_attr(flags,"Be unlisted in userlog",US_UNLISTED);
+        flags = set_attr(flags,"Suppress message prompts",US_NOPROMPT);
+        if ((flags & US_NOPROMPT)==0)
+           flags = set_attr(flags,"Use 'disappearing' prompts",US_DISAPPEAR);
+        flags = set_attr(flags,
+               "Pause after each screenful of text",US_PAGINATOR);
+        if (rc_floor_mode == RC_DEFAULT) {
+         flags = set_attr(flags,
+               "View rooms by floor",US_FLOORS);
+         }
+        }
+
+       if (mode==2) {
+        if (flags & US_EXPERT) {
+               flags = (flags ^ US_EXPERT);
+               printf("Expert mode now OFF\n");
+               }
+        else {
+               flags = (flags | US_EXPERT);
+               printf("Expert mode now ON\n");
+               }
+        }
+
+       if (mode==3) {
+        if (flags & US_FLOORS) {
+               flags = (flags ^ US_FLOORS);
+               printf("Floor mode now OFF\n");
+               }
+        else {
+               flags = (flags | US_FLOORS);
+               printf("Floor mode now ON\n");
+               }
+        }
+
+       sprintf(buf,"SETU %d|%d|%d",width,height,flags);
+       serv_puts(buf);
+       serv_gets(buf);
+       if (buf[0]!='2') printf("%s\n",&buf[4]);
+       userflags = flags;
+}
+
+/*
+ * getstring()  -  get a line of text from a file
+ *                ignores lines beginning with "#"
+ */
+int getstring(fp,string)
+FILE *fp;              
+char string[]; {
+       int a,c;
+       do {
+               strcpy(string,"");
+               a=0;
+               do {
+                       c=getc(fp);
+                       if (c<0) {
+                               string[a]=0;
+                               return(-1);
+                               }
+                       string[a++]=c;
+                       } while(c!=10);
+                       string[a-1]=0;
+               } while(string[0]=='#');
+       return(strlen(string));
+       }
+
+int pattern(search,patn)       /* Searches for patn in search string */
+char search[];
+char patn[];
+{
+       int a,b;
+       for (a=0; a<strlen(search); ++a)
+       {       b=struncmp(&search[a],patn,strlen(patn));
+               if (b==0) return(b);
+               }
+       return(-1);
+}
+
+void interr(errnum)    /* display internal error as defined in errmsgs */
+int errnum; {
+       printf("*** INTERNAL ERROR %d\n",errnum);
+       printf("(Press any key to continue)\n");
+       inkey();
+       logoff(errnum);
+}
+
+
+
+/*
+ * Check to see if we need to pause at the end of a screen.
+ * If we do, we have to disable server keepalives during the pause because
+ * we are probably in the middle of a server operation and the NOOP command
+ * would confuse everything.
+ */
+int checkpagin(lp,pagin,height)
+int lp;
+int pagin; {
+       if (pagin!=1) return(0);
+       if (lp>=(height-1)) {
+               set_keepalives(KA_NO);
+               hit_any_key();
+               set_keepalives(KA_YES);
+               return(0);
+               }
+       return(lp);
+       }
+
+
+void strproc(string)
+char string[];
+{
+       int a;
+
+       if (strlen(string)==0) return;
+
+       /* Convert non-printable characters to blanks */
+       for (a=0; a<strlen(string); ++a) {
+               if (string[a]<32) string[a]=32;
+               if (string[a]>126) string[a]=32;
+               }
+
+       /* Remove leading and trailing blanks */
+       while(string[0]<33) strcpy(string,&string[1]);
+       while(string[strlen(string)-1]<33) string[strlen(string)-1]=0;
+
+       /* Remove double blanks */
+       for (a=0; a<strlen(string); ++a) {
+               if ((string[a]==32)&&(string[a+1]==32)) {
+                       strcpy(&string[a],&string[a+1]);
+                       a=0;
+                       }
+               }
+
+       /* remove characters which would interfere with the network */
+       for (a=0; a<strlen(string); ++a) {
+               if (string[a]=='!') strcpy(&string[a],&string[a+1]);
+               if (string[a]=='@') strcpy(&string[a],&string[a+1]);
+               if (string[a]=='_') strcpy(&string[a],&string[a+1]);
+               if (string[a]==',') strcpy(&string[a],&string[a+1]);
+               if (string[a]=='%') strcpy(&string[a],&string[a+1]);
+               if (string[a]=='|') strcpy(&string[a],&string[a+1]);
+               }
+
+       }
+
+
+int hash(str)
+char str[]; {
+       int h = 0;
+       int i;
+
+       for (i=0; i<strlen(str); ++i) h=h+((i+1)*tolower(str[i]));
+       return(h);
+       }
+
+long finduser(file,name)
+int file;
+char *name; {
+       FILE *fp;
+       int uh,fh;
+       long pp=0L;
+       
+       uh=hash(name);
+       fp=fopen("hashtab","r");
+       while(fread((char *)&fh,sizeof(int),1,fp)>0) {
+               if (uh==fh) {
+                       lseek(file,pp,0);
+                       return(pp);
+                       }
+               pp = pp + (long)sizeof(struct usersupp);
+               }
+       fclose(fp);
+       return(-1L);
+       }
+
+
+int alias(name)                /* process alias and routing info for mail */
+char name[]; {
+       FILE *fp;
+       int a,b;
+       char aaa[300],bbb[300];
+       
+       fp=fopen("network/mail.aliases","r");
+       if (fp==NULL) return(2);
+GNA:   strcpy(aaa,""); strcpy(bbb,"");
+       do {
+               a=getc(fp);
+               if (a==',') a=0;
+               if (a>0) { b=strlen(aaa); aaa[b]=a; aaa[b+1]=0; }
+               } while(a>0);
+       do {
+               a=getc(fp);
+               if (a==10) a=0;
+               if (a>0) { b=strlen(bbb); bbb[b]=a; bbb[b+1]=0; }
+               } while(a>0);
+       if (a<0) {
+               fclose(fp);
+               goto DETYPE;
+               }
+       if (strucmp(name,aaa)) goto GNA;
+       fclose(fp);
+       strcpy(name,bbb);
+       printf("*** Mail is being forwarded to %s\n",name);
+
+DETYPE:        /* determine local or remote type, see citadel.h */
+       for (a=0; a<strlen(name); ++a) if (name[a]=='!') return(M_INTERNET);
+       for (a=0; a<strlen(name); ++a)
+               if (name[a]=='@')
+                       for (b=a; b<strlen(name); ++b)
+                               if (name[b]=='.') return(M_INTERNET);
+       b=0; for (a=0; a<strlen(name); ++a) if (name[a]=='@') ++b;
+       if (b>1) {
+               printf("Too many @'s in address\n");
+               return(M_ERROR);
+               }
+       if (b==1) {
+               for (a=0; a<strlen(name); ++a)
+                       if (name[a]=='@') strcpy(bbb,&name[a+1]);
+               while (bbb[0]==32) strcpy(bbb,&bbb[1]);
+               fp = fopen("network/mail.sysinfo","r");
+               if (fp==NULL) return(M_ERROR);
+GETSN:         do {
+                       a=getstring(fp,aaa);
+                       } while ((a>=0)&&(strucmp(aaa,bbb)));
+               a=getstring(fp,aaa);
+               if (!strncmp(aaa,"use ",4)) {
+                       strcpy(bbb,&aaa[4]);
+                       fseek(fp,0L,0);
+                       goto GETSN;
+                       }
+               fclose(fp);
+               if (!strncmp(aaa,"uum",3)) {
+                       strcpy(bbb,name);
+                       for (a=0; a<strlen(bbb); ++a) {
+                               if (bbb[a]=='@') bbb[a]=0;
+                               if (bbb[a]==' ') bbb[a]='_';
+                               }
+                       while(bbb[strlen(bbb)-1]=='_') bbb[strlen(bbb)-1]=0;
+                       sprintf(name,&aaa[4],bbb);
+                       return(M_INTERNET);
+                       }
+               if (!strncmp(aaa,"bin",3)) {
+                       strcpy(aaa,name); strcpy(bbb,name);
+                       while (aaa[strlen(aaa)-1]!='@') aaa[strlen(aaa)-1]=0;
+                       aaa[strlen(aaa)-1]=0;
+                       while (aaa[strlen(aaa)-1]==' ') aaa[strlen(aaa)-1]=0;
+                       while (bbb[0]!='@') strcpy(bbb,&bbb[1]);
+                       strcpy(bbb,&bbb[1]);
+                       while (bbb[0]==' ') strcpy(bbb,&bbb[1]);
+                       sprintf(name,"%s @%s",aaa,bbb);
+                       return(M_BINARY);
+                       }
+               return(M_ERROR);
+               }
+       return(M_LOCAL);
+       }
+
+
+#ifdef NO_STRERROR
+/*
+ * replacement strerror() for systems that don't have it
+ */
+char *strerror(e)
+int e; {
+       static char buf[32];
+
+       sprintf(buf,"errno = %d",e);
+       return(buf);
+       }
+#endif
+
+
+void progress(curr,cmax)
+long curr;
+long cmax; {
+       static long dots_printed;
+       long a;
+
+       if (curr==0) {
+               printf(".......................................");
+               printf(".......................................\r");
+               fflush(stdout);
+               dots_printed = 0;
+               }
+       else if (curr==cmax) {
+               printf("\r%79s\n","");
+               }
+       else {
+               a=(curr * 100) / cmax;
+               a=a*78; a=a/100;
+               while (dots_printed < a) {
+                       printf("*");
+                       ++dots_printed;
+                       fflush(stdout);
+                       }
+               }
+       }
+
+
+/*
+ * NOT the same locate_host() in locate_host.c.  This one just does a
+ * 'who am i' to try to discover where the user is...
+ */
+void locate_host(hbuf)
+char hbuf[]; {
+       char buf[256];
+       FILE *who;
+       int a,b;
+
+       who = (FILE *)popen("who am i","r");
+       if (who==NULL) {
+               strcpy(hbuf,serv_info.serv_fqdn);
+               return; 
+               }
+       fgets(buf,256,who);
+       pclose(who);
+
+       b = 0;
+       for (a=0; a<strlen(buf); ++a) {
+               if ((buf[a]=='(')||(buf[a]==')')) ++b;
+               }
+       if (b<2) {
+               strcpy(hbuf,serv_info.serv_fqdn);
+               return;
+               }
+
+       for (a=0; a<strlen(buf); ++a) {
+               if (buf[a]=='(') {
+                       strcpy(buf,&buf[a+1]);
+                       }
+               }
+       for (a=0; a<strlen(buf); ++a) {
+               if (buf[a]==')') buf[a] = 0;
+               }
+
+       if (strlen(buf)==0) strcpy(hbuf,serv_info.serv_fqdn);
+       else strncpy(hbuf,buf,24);
+       }
+
+/*
+ * miscellaneous server commands (testing, etc.)
+ */
+void misc_server_cmd(char *cmd) {
+       char buf[256];
+
+       serv_puts(cmd);
+       serv_gets(buf);
+       printf("%s\n",buf);
+       if (buf[0]=='1') {
+               while (serv_gets(buf), strcmp(buf,"000")) {
+                       printf("%s\n",buf);
+                       }
+               return;
+               }
+       if (buf[0]=='4') {
+               do {
+                       newprompt("> ",buf,255);
+                       serv_puts(buf);
+                       } while(strcmp(buf,"000"));
+               return;
+               }
+       }
+
+
+/*
+ * compute the checksum of a file
+ */
+int file_checksum(filename)
+char *filename; {
+       int cksum = 0;
+       int ch;
+       FILE *fp;
+
+       fp = fopen(filename,"r");
+       if (fp == NULL) return(0);
+
+       /* yes, this algorithm may allow cksum to overflow, but that's ok
+        * as long as it overflows consistently, which it will.
+        */
+       while (ch=getc(fp), ch>=0) {
+               cksum = (cksum + ch);
+               }
+
+       fclose(fp);
+       return(cksum);
+       }
+
+/*
+ * nuke a directory and its contents
+ */
+int nukedir(dirname)
+char *dirname; {
+       DIR *dp;
+       struct dirent *d;
+       char filename[256];
+
+       dp = opendir(dirname);
+       if (dp == NULL) {
+               return(errno);
+               }
+
+       while (d = readdir(dp), d != NULL) {
+               sprintf(filename, "%s/%s", dirname, d->d_name);
+               unlink(filename);
+               }
+
+       closedir(dp);
+       return(rmdir(dirname));
+       }
diff --git a/citadel/routines2.c b/citadel/routines2.c
new file mode 100644 (file)
index 0000000..9f7d892
--- /dev/null
@@ -0,0 +1,579 @@
+/* More Citadel/UX routines...
+ * unlike routines.c, some of these DO use global variables.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <signal.h>
+#include <pwd.h>
+#include <setjmp.h>
+#include <errno.h>
+#include "citadel.h"
+
+void interr();
+void strprompt();
+void newprompt();
+void sttybbs();
+int inkey();
+int ka_wait();
+void serv_write();
+void extract();
+long finduser();
+int haschar();
+void progress();
+void citedit();
+int yesno();
+void nukedir();
+
+extern char temp[];
+extern char tempdir[];
+extern char *axdefs[7];
+extern long highest_msg_read;
+extern long maxmsgnum;
+extern unsigned room_flags;
+extern int screenwidth;
+
+
+int eopen(name,mode)
+char *name;
+int mode; {
+       int ret;
+       ret = open(name,mode);
+       if (ret<0) {
+               fprintf(stderr,"Cannot open file '%s', mode=%d, errno=%d\n",
+                       name,mode,errno);
+               interr(errno);
+               }
+       return(ret);
+       }
+
+
+int room_prompt(qrflags)       /* return proper room prompt character */
+int qrflags; {
+       int a;
+       a='>';
+       if (qrflags&QR_DIRECTORY) a=']';
+       if ((a==']')&&(qrflags&QR_NETWORK)) a='}';
+       if ((a=='>')&&(qrflags&QR_NETWORK)) a=')';
+       return(a);
+       }
+
+void entregis()        /* register with name and address */
+       {
+
+       char buf[256];
+       char tmpname[256];
+       char tmpaddr[256];
+       char tmpcity[256];
+       char tmpstate[256];
+       char tmpzip[256];
+       char tmpphone[256];
+       char tmpemail[256];
+       int a;
+
+       strcpy(tmpname,"");
+       strcpy(tmpaddr,"");
+       strcpy(tmpcity,"");
+       strcpy(tmpstate,"");
+       strcpy(tmpzip,"");
+       strcpy(tmpphone,"");
+       strcpy(tmpemail,"");
+
+       serv_puts("GREG _SELF_");
+       serv_gets(buf);
+       if (buf[0]=='1') {
+               a = 0;
+               while (serv_gets(buf), strcmp(buf,"000")) {
+                       if (a==2) strcpy(tmpname,buf);
+                       if (a==3) strcpy(tmpaddr,buf);
+                       if (a==4) strcpy(tmpcity,buf);
+                       if (a==5) strcpy(tmpstate,buf);
+                       if (a==6) strcpy(tmpzip,buf);
+                       if (a==7) strcpy(tmpphone,buf);
+                       if (a==9) strcpy(tmpemail,buf);
+                       ++a;
+                       }
+               }
+
+       strprompt("REAL name",tmpname,29);
+       strprompt("Address",tmpaddr,24);
+       strprompt("City/town",tmpcity,14);
+       strprompt("State",tmpstate,2);
+       strprompt("ZIP Code",tmpzip,10);
+       strprompt("Telephone number",tmpphone,14);
+       strprompt("Email address",tmpemail,31);
+
+       /* now send the registration info back to the server */
+       serv_puts("REGI");
+       serv_gets(buf);
+       if (buf[0]!='4') {
+               printf("%s\n",&buf[4]);
+               return;
+               }
+       serv_puts(tmpname);
+       serv_puts(tmpaddr);
+       serv_puts(tmpcity);
+       serv_puts(tmpstate);
+       serv_puts(tmpzip);
+       serv_puts(tmpphone);
+       serv_puts(tmpemail);
+       serv_puts("000");
+       printf("\n");
+       }
+
+void updatels() {      /* make all messages old in current room */
+       char buf[256];
+       serv_puts("SLRP HIGHEST");
+       serv_gets(buf);
+       if (buf[0]!='2') printf("%s\n",&buf[4]);
+       }
+
+void updatelsa() {   /* only make messages old in this room that have been read */
+       char buf[256];
+       sprintf(buf,"SLRP %ld",highest_msg_read);
+       serv_puts(buf);
+       serv_gets(buf);
+       if (buf[0]!='2') printf("%s\n",&buf[4]);
+       }
+
+
+/*
+ * This routine completes a client upload
+ */
+void do_upload(int fd) {
+       char buf[256];
+       char tbuf[4096];
+       long transmitted_bytes, total_bytes;
+       int bytes_to_send;
+       int bytes_expected;
+
+       /* learn the size of the file */
+       total_bytes = lseek(fd,0L,2);
+       lseek(fd,0L,0);
+
+       transmitted_bytes = 0L;
+       progress(transmitted_bytes,total_bytes);
+       do {
+               bytes_to_send = read(fd,tbuf,4096);
+               if (bytes_to_send>0) {
+                       sprintf(buf,"WRIT %d",bytes_to_send);
+                       serv_puts(buf);
+                       serv_gets(buf);
+                       if (buf[0]=='7') {
+                               bytes_expected = atoi(&buf[4]);
+                               serv_write(tbuf,bytes_expected);
+                               }
+                       else {
+                               printf("%s\n",&buf[4]);
+                               }
+                       }
+               transmitted_bytes = transmitted_bytes + (long)bytes_to_send;
+               progress(transmitted_bytes, total_bytes);
+               } while (bytes_to_send > 0);
+
+       /* close the upload file, locally and at the server */
+       close(fd);
+       serv_puts("UCLS 1");
+       serv_gets(buf);
+       printf("%s\n",&buf[4]);
+       }
+
+
+/*
+ * client-based uploads (for users with their own clientware)
+ */
+void cli_upload() {
+       char flnm[256];
+       char desc[151];
+       char buf[256];
+       char tbuf[256];
+       int a;
+       int fd;
+
+       if ((room_flags & QR_UPLOAD) == 0) {
+               printf("*** You cannot upload to this room.\n");
+               return;
+               }
+
+       newprompt("File to be uploaded: ",flnm,55);
+       fd = open(flnm,O_RDONLY);
+       if (fd<0) {
+               printf("Cannot open '%s': %s\n",flnm,strerror(errno));
+               return;
+               }
+       printf("Enter a description of this file:\n");
+       newprompt(": ",desc,75);
+
+       /* keep generating filenames in hope of finding a unique one */
+       a = 0;
+       do {
+               if (a==10) return; /* fail if tried 10 times */
+               strcpy(buf,flnm);
+               while ((strlen(buf)>0)&&(haschar(buf,'/')))
+                       strcpy(buf,&buf[1]);
+               if (a>0) sprintf(&buf[strlen(buf)],"%d",a);
+               sprintf(tbuf,"UOPN %s|%s",buf,desc);
+               serv_puts(tbuf);
+               serv_gets(buf);
+               if (buf[0]!='2') printf("%s\n",&buf[4]);
+               ++a;
+               } while (buf[0]!='2');
+
+       /* at this point we have an open upload file at the server */
+       do_upload(fd);
+       }
+
+
+/*
+ * Function used for various image upload commands
+ */
+void cli_image_upload(char *keyname) {
+       char flnm[256];
+       char buf[256];
+       int fd;
+
+       sprintf(buf, "UIMG 0|%s", keyname);
+       serv_puts(buf);
+       serv_gets(buf);
+       if (buf[0] != '2') {
+               printf("%s\n", &buf[4]);
+               return;
+               }
+
+       newprompt("Image file to be uploaded: ",flnm,55);
+       fd = open(flnm,O_RDONLY);
+       if (fd<0) {
+               printf("Cannot open '%s': %s\n",flnm,strerror(errno));
+               return;
+               }
+
+       sprintf(buf, "UIMG 1|%s", keyname);
+       serv_puts(buf);
+       serv_gets(buf);
+       if (buf[0] != '2') {
+               printf("%s\n", &buf[4]);
+               return;
+               }
+
+       do_upload(fd);
+       }
+
+
+/*
+ * protocol-based uploads (Xmodem, Ymodem, Zmodem)
+ */
+void upload(c) /* c = upload mode */
+int c; {
+       char flnm[256];
+       char desc[151];
+       char buf[256];
+       char tbuf[4096];
+       int xfer_pid;
+       int a,b;
+       FILE *fp,*lsfp;
+       int fd;
+
+       if ((room_flags & QR_UPLOAD) == 0) {
+               printf("*** You cannot upload to this room.\n");
+               return;
+               }
+
+       /* we don't need a filename when receiving batch y/z modem */
+       if ((c==2)||(c==3)) strcpy(flnm,"x");
+       else newprompt("Enter filename: ",flnm,15);
+
+       for (a=0; a<strlen(flnm); ++a)
+               if ( (flnm[a]=='/') || (flnm[a]=='\\') || (flnm[a]=='>')
+                    || (flnm[a]=='?') || (flnm[a]=='*')
+                    || (flnm[a]==';') || (flnm[a]=='&') ) flnm[a]='_';
+
+       newprompt("Enter a short description of the file:\n: ",desc,150);
+
+       /* create a temporary directory... */
+       if (mkdir(tempdir,0700) != 0) {
+               printf("*** Could not create temporary directory %s: %s\n",
+                       tempdir,strerror(errno));
+               return;
+               }
+
+       /* now do the transfer ... in a separate process */
+       xfer_pid = fork();
+       if (xfer_pid == 0) {
+           chdir(tempdir);
+           switch(c) {
+               case 0:
+                       sttybbs(0);
+                       printf("Receiving %s - press Ctrl-D to end.\n",flnm);
+                       fp = fopen(flnm,"w");
+                       do {
+                               b=inkey(); 
+                               if (b==13) {
+                                       b=10; printf("\r");
+                                       }
+                               if (b!=4) {
+                                       printf("%c",b);
+                                       putc(b,fp);
+                                       }
+                               } while(b!=4);
+                       fclose(fp);
+                       exit(0);
+               case 1:
+                       sttybbs(3);
+                       execlp("rx","rx",flnm,NULL);
+                       exit(1);
+               case 2:
+                       sttybbs(3);
+                       execlp("rb","rb",NULL);
+                       exit(1);
+               case 3:
+                       sttybbs(3);
+                       execlp("rz","rz",NULL);
+                       exit(1);
+                       }
+               }
+       else do {
+               b=ka_wait(&a);
+               } while ((b!=xfer_pid)&&(b!=(-1)));
+       sttybbs(0);
+
+       if (a != 0) {
+               printf("\r*** Transfer unsuccessful.\n");
+               nukedir(tempdir);
+               return;
+               }
+
+       printf("\r*** Transfer successful.  Sending file(s) to server...\n");
+       sprintf(buf,"cd %s; ls",tempdir);
+       lsfp = popen(buf,"r");
+       if (lsfp!=NULL) {
+               while (fgets(flnm,256,lsfp)!=NULL) {
+                       flnm[strlen(flnm)-1] = 0;
+                       sprintf(buf,"%s/%s",tempdir,flnm);
+                       fd = open(buf,O_RDONLY);
+                       if (fd>=0) {
+                               a = 0;
+                               do {
+                                       sprintf(buf,"UOPN %s|%s",flnm,desc);
+                                       if (a>0) sprintf(&buf[strlen(buf)],
+                                               ".%d",a);
+                                       ++a;
+                                       serv_puts(buf);
+                                       serv_gets(buf);
+                                       } while((buf[0]!='2')&&(a<100));
+                               if (buf[0]=='2') do {
+                                       a=read(fd,tbuf,4096);
+                                       if (a>0) {
+                                               sprintf(buf,"WRIT %d",a);
+                                               serv_puts(buf);
+                                               serv_gets(buf);
+                                               if (buf[0]=='7')
+                                                       serv_write(tbuf,a);
+                                               }
+                                       } while (a>0);
+                               close(fd);
+                               serv_puts("UCLS 1");
+                               serv_gets(buf);
+                               printf("%s\n",&buf[4]);
+                               }
+                       }
+               pclose(lsfp);
+               }
+
+       nukedir(tempdir);
+       }
+
+/* 
+ * validate a user
+ */
+void val_user(user)
+char *user; {
+       int a,b;
+       char cmd[256];
+       char buf[256];
+       int ax = 0;
+
+       sprintf(cmd,"GREG %s",user);
+       serv_puts(cmd);
+       serv_gets(cmd);
+       if (cmd[0]=='1') {
+               a = 0;
+               do {
+                       serv_gets(buf);
+                       ++a;
+                       if (a==1) printf("User #%s - %s  ",
+                               buf,&cmd[4]);
+                       if (a==2) printf("PW: %s\n",buf);
+                       if (a==3) printf("%s\n",buf);
+                       if (a==4) printf("%s\n",buf);
+                       if (a==5) printf("%s, ",buf);
+                       if (a==6) printf("%s ",buf);
+                       if (a==7) printf("%s\n",buf);
+                       if (a==8) printf("%s\n",buf);
+                       if (a==9) ax=atoi(buf);
+                       if (a==10) printf("%s\n",buf);
+                       } while(strcmp(buf,"000"));
+               printf("Current access level: %d (%s)\n",ax,axdefs[ax]);
+               }
+       else {
+               printf("%-30s\n%s\n",user,&cmd[4]);
+               }
+
+       /* now set the access level */
+       do {
+               printf("Access level (? for list): ");
+               a=inkey();
+               if (a=='?') {
+                       printf("list\n");
+                       for (b=0; b<7; ++b)
+                               printf("%d %s\n",b,axdefs[b]);
+                       }
+               a=a-48;
+               } while((a<0)||(a>6));
+       printf("%d\n\n",a);
+       sprintf(cmd,"VALI %s|%d",user,a);
+       serv_puts(cmd);
+       serv_gets(cmd);
+       if (cmd[0]!='2') printf("%s\n",&cmd[4]);
+       printf("\n");
+       }
+
+
+void validate() {      /* validate new users */
+       char cmd[256];
+       char buf[256];
+       int finished = 0;
+
+       do {
+               serv_puts("GNUR");
+               serv_gets(cmd);
+               if (cmd[0]!='3') finished = 1;
+               if (cmd[0]=='2') printf("%s\n",&cmd[4]);
+               if (cmd[0]=='3') {
+                       extract(buf,cmd,0);
+                       val_user(&buf[4]);
+                       }
+               } while(finished==0);
+       }
+
+void subshell() {
+       int a,b;
+       a=fork();
+       if (a==0) {
+               sttybbs(SB_RESTORE);
+               signal(SIGINT,SIG_DFL);
+               signal(SIGQUIT,SIG_DFL);
+               execlp(getenv("SHELL"),getenv("SHELL"),NULL);
+               printf("Could not open a shell: %s\n", strerror(errno));
+               exit(errno);
+               }
+       do {
+               b=ka_wait(NULL);
+               } while ((a!=b)&&(a!=(-1)));
+       sttybbs(0);
+       }
+
+/*
+ * <.A>ide <F>ile <D>elete command
+ */
+void deletefile() {
+       char filename[32];
+       char cmd[256];
+
+       newprompt("Filename: ",filename,31);
+       if (strlen(filename)==0) return;
+       sprintf(cmd,"DELF %s",filename);
+       serv_puts(cmd);
+       serv_gets(cmd);
+       printf("%s\n",&cmd[4]);
+       }
+
+/*
+ * <.A>ide <F>ile <S>end command
+ */
+void netsendfile() {
+       char filename[32],destsys[20],cmd[256];
+
+       newprompt("Filename: ",filename,31);
+       if (strlen(filename)==0) return;
+       newprompt("System to send to: ",destsys,19);
+       sprintf(cmd,"NETF %s|%s",filename,destsys);
+       serv_puts(cmd);
+       serv_gets(cmd);
+       printf("%s\n",&cmd[4]);
+       return;
+       }
+
+/*
+ * <.A>ide <F>ile <M>ove command
+ */
+void movefile() {
+       char filename[64];
+       char newroom[20];
+       char cmd[256];
+
+       newprompt("Filename: ",filename,63);
+       if (strlen(filename)==0) return;
+       newprompt("Enter target room: ",newroom,19);
+
+       sprintf(cmd,"MOVF %s|%s",filename,newroom);
+       serv_puts(cmd);
+       serv_gets(cmd);
+       printf("%s\n",&cmd[4]);
+       }
+
+
+/* 
+ * list of users who have filled out a bio
+ */
+void list_bio() {
+       char buf[256];
+       int pos = 1;
+
+       serv_puts("LBIO");
+       serv_gets(buf);
+       if (buf[0]!='1') {
+               printf("%s\n",&buf[4]);
+               return;
+               }
+       while (serv_gets(buf), strcmp(buf,"000")) {
+               if ((pos+strlen(buf)+5)>screenwidth) {
+                       printf("\n");
+                       pos = 1;
+                       }
+               printf("%s, ",buf);
+               pos = pos + strlen(buf) + 2;
+               }
+       printf("%c%c  \n\n",8,8);
+       }
+
+
+/*
+ * read bio
+ */
+void read_bio() {
+       char who[256];
+       char buf[256];
+
+       do {
+               newprompt("Read bio for who ('?' for list) : ",who,25);
+               printf("\n");
+               if (!strcmp(who,"?")) list_bio();
+               } while(!strcmp(who,"?"));
+       sprintf(buf,"RBIO %s",who);
+       serv_puts(buf);
+       serv_gets(buf);
+       if (buf[0]!='1') {
+               printf("%s\n",&buf[4]);
+               return;
+               }
+       while (serv_gets(buf), strcmp(buf,"000")) {
+               printf("%s\n",buf);
+               }
+       }
diff --git a/citadel/serv_chat.c b/citadel/serv_chat.c
new file mode 100644 (file)
index 0000000..a6b99cb
--- /dev/null
@@ -0,0 +1,412 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <pthread.h>
+#include "citadel.h"
+#include "server.h"
+#include <syslog.h>
+#ifdef NEED_SELECT_H
+#include <sys/select.h>
+#endif
+#include "proto.h"
+
+extern struct config config;
+extern struct CitContext *ContextList;
+
+struct ChatLine *ChatQueue = NULL;
+int ChatLastMsg = 0;
+
+
+void allwrite(char *cmdbuf, int flag, char *roomname, char *username)
+{      
+       FILE *fp;
+       char bcast[256];
+       char *un;
+       struct ChatLine *clptr, *clnew;
+       time_t now;
+
+       if (CC->fake_username[0])
+          un = CC->fake_username;
+       else
+          un = CC->usersupp.fullname;
+       if (flag == 1) 
+       {
+               sprintf(bcast,":|<%s %s>",un,cmdbuf);
+       }
+       else
+       if (flag == 0)
+       {
+               sprintf(bcast,"%s|%s",un,cmdbuf);
+       }
+       else
+       if (flag == 2)
+       {
+               sprintf(bcast,":|<%s whispers %s>", un, cmdbuf);
+       }
+       if (strucmp(cmdbuf,"NOOP")) {
+               fp = fopen(CHATLOG,"a");
+               fprintf(fp,"%s\n",bcast);
+               fclose(fp);
+               }
+
+       clnew = (struct ChatLine *) malloc(sizeof(struct ChatLine));
+       bzero(clnew, sizeof(struct ChatLine));
+       if (clnew == NULL) {
+               fprintf(stderr, "citserver: cannot alloc chat line: %s\n",
+                       strerror(errno));
+               return;
+               }
+
+       time(&now);
+       clnew->next = NULL;
+       clnew->chat_time = now;
+       strncpy(clnew->chat_room, roomname, 19);
+       if (username)
+          strncpy(clnew->chat_username, username, 31); 
+       else
+          clnew->chat_username[0] = '\0';
+       strcpy(clnew->chat_text, bcast);
+
+       /* Here's the critical section.
+        * First, add the new message to the queue...
+        */
+       begin_critical_section(S_CHATQUEUE);
+       ++ChatLastMsg;
+       clnew->chat_seq = ChatLastMsg;
+       if (ChatQueue == NULL) {
+               ChatQueue = clnew;
+               }
+       else {
+               for (clptr=ChatQueue; clptr->next != NULL; clptr=clptr->next) ;;
+               clptr->next = clnew;
+               }
+
+       /* Then, before releasing the lock, free the expired messages */
+       while(1) {
+               if (ChatQueue == NULL) goto DONE_FREEING;
+               if ( (now - ChatQueue->chat_time) < 120L ) goto DONE_FREEING;
+               clptr = ChatQueue;
+               ChatQueue = ChatQueue->next;
+               free(clptr);
+               }
+DONE_FREEING:  end_critical_section(S_CHATQUEUE);
+       }
+
+/*
+ * List users in chat.  Setting allflag to 1 also lists users elsewhere.
+ */
+void do_chat_listing(int allflag)
+{
+       struct CitContext *ccptr;
+
+       cprintf(":|\n:| Users currently in chat:\n");
+       for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
+               if ( (!strucmp(ccptr->cs_room, "<chat>"))
+                  && ((ccptr->cs_flags & CS_STEALTH) == 0)) {
+                       cprintf(":| %-25s <%s>\n", (ccptr->fake_username[0]) ? ccptr->fake_username : ccptr->curr_user, ccptr->chat_room);
+                       }
+               }
+
+       if (allflag == 1) 
+       {
+               cprintf(":|\n:| Users not in chat:\n");
+               for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) 
+               {
+                       if ( (strucmp(ccptr->cs_room, "<chat>"))
+                          && ((ccptr->cs_flags & CS_STEALTH) == 0)) 
+                       {
+                               cprintf(":| %-25s <%s>:\n", (ccptr->fake_username[0]) ? ccptr->fake_username : ccptr->curr_user, ccptr->cs_room);
+                       }
+               }
+       }
+       
+       cprintf(":|\n");
+       }
+
+
+void cmd_chat(char *argbuf)
+{
+       char cmdbuf[256];
+       char *un;
+       char *strptr1;
+       char hold_cs_room[20];
+       int MyLastMsg, ThisLastMsg;
+       struct ChatLine *clptr;
+       int retval;
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       strcpy(CC->chat_room, "Main room");
+
+       strcpy(hold_cs_room,CC->cs_room);
+       CC->cs_flags = CC->cs_flags | CS_CHAT;
+       set_wtmpsupp("<chat>");
+       cprintf("%d Entering chat mode (type '/help' for available commands)\n",
+               START_CHAT_MODE);
+
+       MyLastMsg = ChatLastMsg;
+
+       if ((CC->cs_flags & CS_STEALTH) == 0) {
+               allwrite("<entering chat>",0, CC->chat_room, NULL);
+               }
+
+       strcpy(cmdbuf, "");
+
+       while(1) {
+               cmdbuf[strlen(cmdbuf) + 1] = 0;
+               retval = client_read_to(&cmdbuf[strlen(cmdbuf)], 1, 2);
+
+               /* if we have a complete line, do send processing */
+               if (strlen(cmdbuf) > 0) if (cmdbuf[strlen(cmdbuf)-1] == 10) {
+                       cmdbuf[strlen(cmdbuf) - 1] = 0;
+                       time(&CC->lastcmd);
+                       time(&CC->lastidle);
+
+                       if ( (!strucmp(cmdbuf,"exit"))
+                       ||(!strucmp(cmdbuf,"/exit"))
+                       ||(!strucmp(cmdbuf,"quit"))
+                       ||(!strucmp(cmdbuf,"logout"))
+                       ||(!strucmp(cmdbuf,"logoff"))
+                       ||(!strucmp(cmdbuf,"/q"))
+                       ||(!strucmp(cmdbuf,".q"))
+                       ||(!strucmp(cmdbuf,"/quit"))
+                               ) strcpy(cmdbuf,"000");
+       
+                       if (!strcmp(cmdbuf,"000")) {
+                               if ((CC->cs_flags & CS_STEALTH) == 0) {
+                                       allwrite("<exiting chat>",0, CC->chat_room, NULL);
+                                       }
+                               sleep(1);
+                               cprintf("000\n");
+                               CC->cs_flags = CC->cs_flags - CS_CHAT;
+                               set_wtmpsupp(hold_cs_room);
+                               return;
+                               }
+       
+                       if ((!strucmp(cmdbuf,"/help"))
+                       ||(!strucmp(cmdbuf,"help"))
+                       ||(!strucmp(cmdbuf,"/?"))
+                       ||(!strucmp(cmdbuf,"?"))) {
+                               cprintf(":|\n");
+                               cprintf(":|Available commands: \n");
+                               cprintf(":|/help   (prints this message) \n");
+                               cprintf(":|/who    (list users currently in chat) \n");
+                               cprintf(":|/whobbs (list users in chat -and- elsewhere) \n");
+                               cprintf(":|/me     ('action' line, ala irc) \n");
+                               cprintf(":|/msg    (send private message, ala irc) \n");
+                               cprintf(":|/join   (join new room) \n"); 
+                               cprintf(":|/quit   (return to the BBS) \n");
+                               cprintf(":|\n");
+                               }
+                       if (!strucmp(cmdbuf,"/who")) {
+                               do_chat_listing(0);
+                               }
+                       if (!strucmp(cmdbuf,"/whobbs")) {
+                               do_chat_listing(1);
+                               }
+                       if (!struncmp(cmdbuf,"/me ",4)) {
+                               allwrite(&cmdbuf[4],1, CC->chat_room, NULL);
+                               }
+                               
+                       if (!struncmp(cmdbuf,"/msg ", 5))
+                       {
+                          strptr1 = strtok(cmdbuf, " ");
+                          if (strptr1)
+                          {
+                             strptr1 = strtok(NULL, " ");
+                             if (strptr1)
+                             {
+                                allwrite(&strptr1[strlen(strptr1)+1], 2, CC->chat_room, CC->curr_user);
+                                if (struncmp(CC->curr_user, strptr1, strlen(CC->curr_user)))
+                                   allwrite(&strptr1[strlen(strptr1)+1], 2, CC->chat_room, strptr1);
+                             }
+                          }
+                          cprintf("\n");
+                       }
+
+                       if (!struncmp(cmdbuf,"/join ", 6))
+                       {
+                          allwrite("<changing rooms>",0, CC->chat_room, NULL);
+                          if (!cmdbuf[6])
+                             strcpy(CC->chat_room, "Main room");
+                          else
+                          {
+                             strncpy(CC->chat_room, &cmdbuf[6], 20);
+                          }
+                          allwrite("<joining room>",0, CC->chat_room, NULL);
+                          cprintf("\n");
+                          
+                       }
+                       if ((cmdbuf[0]!='/')&&(strlen(cmdbuf)>0)) {
+                               allwrite(cmdbuf,0, CC->chat_room, NULL);
+                               }
+
+                       strcpy(cmdbuf, "");
+
+                       }
+       
+               /* now check the queue for new incoming stuff */
+               
+               if (CC->fake_username[0])
+                  un = CC->fake_username;
+               else
+                  un = CC->curr_user;
+               if (ChatLastMsg > MyLastMsg) {
+                       ThisLastMsg = ChatLastMsg;
+                       for (clptr=ChatQueue; clptr!=NULL; clptr=clptr->next) 
+                       {
+                               if (
+                               (clptr->chat_seq > MyLastMsg) && 
+                               (!struncmp(CC->chat_room, clptr->chat_room, 20)) &&
+                               ((!clptr->chat_username[0]) || (!struncmp(un, clptr->chat_username, 32)))
+                               )
+                               {
+                                       cprintf("%s\n", clptr->chat_text);
+                               }
+                       }
+                       MyLastMsg = ThisLastMsg;
+                       }
+
+               }
+       }
+
+
+/*
+ * poll for express messages
+ */
+void cmd_pexp(void) {
+       struct ExpressMessage *emptr;
+
+       if (CC->FirstExpressMessage == NULL) {
+               cprintf("%d No express messages waiting.\n",ERROR);
+               return;
+               }
+
+       cprintf("%d Express msgs:\n",LISTING_FOLLOWS);
+
+       while (CC->FirstExpressMessage != NULL) {
+               cprintf("%s", CC->FirstExpressMessage->em_text);
+               begin_critical_section(S_SESSION_TABLE);
+               emptr = CC->FirstExpressMessage;
+               CC->FirstExpressMessage = CC->FirstExpressMessage->next;
+               free(emptr);
+               end_critical_section(S_SESSION_TABLE);
+               }
+       cprintf("000\n");
+       }
+
+/*
+ * returns an asterisk if there are any express messages waiting,
+ * space otherwise.
+ */
+char check_express(void) {
+       if (CC->FirstExpressMessage == NULL) {
+               return(' ');
+               }
+       else {
+               return('*');
+               }
+       }
+
+
+/*
+ * send express messages  <bc>
+ */
+void cmd_sexp(char *argbuf)
+{
+       char x_user[256];
+       char x_msg[256];
+       int message_sent = 0;
+       struct CitContext *ccptr;
+       struct ExpressMessage *emptr, *emnew;
+       char *lun;              /* <bc> */
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (num_parms(argbuf)!=2) {
+               cprintf("%d usage error\n",ERROR);
+               return; 
+               }
+
+       if (CC->fake_username[0])
+          lun = CC->fake_username;
+       else
+          lun = CC->usersupp.fullname;
+
+       extract(x_user,argbuf,0);
+
+       if (!strcmp(x_user, "."))
+       {
+          strcpy(x_user, CC->last_pager);
+       }
+       extract(x_msg,argbuf,1);
+       
+       if (!x_user[0])
+       {
+          cprintf("%d You were not previously paged.\n", ERROR);
+          return;
+       }
+
+       if ( (!strucmp(x_user, "broadcast")) && (CC->usersupp.axlevel < 6) ) {
+               cprintf("%d Higher access required to send a broadcast.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       /* find the target user's context and append the message */
+       begin_critical_section(S_SESSION_TABLE);
+       for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
+               char *un;
+               
+               if (ccptr->fake_username[0])            /* <bc> */
+                  un = ccptr->fake_username;
+               else
+                  un = ccptr->usersupp.fullname;
+                  
+               if ( (!strucmp(un, x_user))
+                  || (!strucmp(x_user, "broadcast")) ) {
+                       strcpy(ccptr->last_pager, CC->curr_user);
+                       emnew = (struct ExpressMessage *)
+                               malloc(sizeof(struct ExpressMessage));
+                       emnew->next = NULL;
+                       sprintf(emnew->em_text, "%s from %s:\n %s\n",
+                               ( (!strucmp(x_user, "broadcast")) ? "Broadcast message" : "Message" ),
+                               lun, x_msg);
+
+                       if (ccptr->FirstExpressMessage == NULL) {
+                               ccptr->FirstExpressMessage = emnew;
+                               }
+                       else {
+                               emptr = ccptr->FirstExpressMessage;
+                               while (emptr->next != NULL) {
+                                       emptr = emptr->next;
+                                       }
+                               emptr->next = emnew;
+                               }
+
+                       ++message_sent;
+                       }
+               }
+       end_critical_section(S_SESSION_TABLE);
+
+       if (message_sent > 0) {
+               cprintf("%d Message sent.\n",OK);
+               }
+       else {
+               cprintf("%d No user '%s' logged in.\n",ERROR,x_user);
+               }
+       }
diff --git a/citadel/server.h b/citadel/server.h
new file mode 100644 (file)
index 0000000..c48cec9
--- /dev/null
@@ -0,0 +1,129 @@
+
+typedef pthread_t THREAD;
+
+
+struct ExpressMessage {
+       struct ExpressMessage *next;
+       char em_text[300];
+       };
+
+/*
+ * Here's the big one... the Citadel context structure.
+ *
+ * This structure keeps track of all information relating to a running 
+ * session on the server.  We keep one of these for each session thread.
+ *
+ * Note that the first element is "*next" so that it may be used without
+ * modification in a linked list.
+ */
+struct CitContext {
+       struct CitContext *next;        /* Link to next session in the list */
+
+        struct usersupp usersupp;      /* Database record buffers */
+        struct quickroom quickroom;
+        struct fullroom fullroom;
+
+        char curr_user[32];            /* name of current user */
+        int curr_rm;                   /* index of current room */
+        int logged_in;                 /* logged in */
+        int internal_pgm;              /* authenticated as internal program */
+        char temp[32];                 /* temp file name */
+        int nologin;                   /* not allowed to log in */
+
+        char net_node[32];
+       THREAD mythread;
+       int client_socket;
+       struct ExpressMessage *FirstExpressMessage;
+       int cs_pid;                     /* session ID */
+       char cs_room[20];               /* current room */
+       long cs_lastupdt;               /* time of last update */
+       time_t lastcmd;                 /* time of last command executed */
+       time_t lastidle;                /* For computing idle time */
+       char lastcmdname[5];            /* name of last command executed */
+       unsigned cs_flags;              /* miscellaneous flags */
+
+                                       /* feeping creaturisms... */
+       int cs_clientdev;               /* client developer ID */
+       int cs_clienttyp;               /* client type code */
+       int cs_clientver;               /* client version number */
+       char cs_clientname[32];         /* name of client software */
+       char cs_host[25];               /* host logged in from */
+
+        FILE *download_fp;             /* Fields relating to file transfer */
+        FILE *upload_fp;
+       char upl_file[256];
+       char upl_path[256];
+       char upl_comment[256];
+       char upl_filedir[256];
+       char chat_room[20];             /* The chat room */
+       char dl_is_net;
+       char upload_type;
+
+       char ucache_name[32];           /* For a performance boost, we cache */
+       long ucache_pos;                /* the position of the last user rec */
+       char fake_username[32];         /* Fake username <bc>                */
+       char fake_postname[32];         /* Fake postname <bc>                */
+       char fake_hostname[25];         /* Name of the fake hostname <bc>    */
+       char fake_roomname[20];         /* Name of the fake room <bc>        */
+       char last_pager[32];            /* The username of the last pager    */
+       };
+
+#define CS_STEALTH     1               /* stealth mode */
+#define CS_CHAT                2               /* chat mode */
+#define CS_POSTING     4               /* Posting */
+
+struct CitContext *MyContext(void);
+#define CC ((struct CitContext *)MyContext())
+
+extern struct CitContext *ContextList;
+extern int ScheduledShutdown;
+extern struct CitControl CitControl;
+
+struct ChatLine {
+       struct ChatLine *next;
+       int chat_seq;
+       time_t chat_time;
+       char chat_text[256];
+       char chat_room[20];
+       char chat_username[32];
+       };
+
+/*
+ * Various things we need to lock and unlock
+ */
+#define S_USERSUPP     0
+#define S_USER_TRANS   1
+#define S_QUICKROOM    2
+#define S_MSGMAIN      3
+#define S_CALLLOG      4
+#define S_SESSION_TABLE        5
+#define S_FLOORTAB     6
+#define S_CHATQUEUE    7
+#define S_CONTROL      8
+#define S_HOUSEKEEPING 9
+#define MAX_SEMAPHORES 10
+
+
+/*
+ * Upload types
+ */
+#define UPL_FILE       0
+#define UPL_NET                1
+#define UPL_IMAGE      2
+
+
+
+/*
+ * Citadel DataBases (define one for each cdb we need to open)
+ */
+#define CDB_MSGMAIN    0       /* message base */
+#define CDB_USERSUPP   1       /* user file */
+#define CDB_QUICKROOM  2       /* room index */
+#define CDB_FULLROOM   3       /* room message lists */
+#define CDB_FLOORTAB   4       /* floor index */
+#define MAXCDB         5       /* total number of CDB's defined */
+
+struct cdbdata {
+       size_t len;
+       char *ptr;
+       };
diff --git a/citadel/setup.c b/citadel/setup.c
new file mode 100644 (file)
index 0000000..ee16f00
--- /dev/null
@@ -0,0 +1,1273 @@
+/*
+ * Citadel/UX setup program
+ * v3.3 / by Art Cancro
+ * see copyright.txt for copyright information
+ *
+ * *** YOU MUST EDIT sysconfig.h >BEFORE< COMPILING SETUP ***
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <netdb.h>
+#include <errno.h>
+
+#include "citadel.h"
+#include "axdefs.h"
+#include "sysdep.h"
+
+#ifdef CURSES_INC
+# ifdef OK
+# undef OK
+# endif
+#include CURSES_INC
+#endif
+
+#define MAXSETUP 21
+
+#define UI_TEXT                0       /* Default setup type -- text only */
+#define UI_DIALOG      1       /* Use the 'dialog' program */
+#define UI_CURSES      2       /* Use curses */
+
+#define SERVICE_NAME   "citadel"
+#define PROTO_NAME     "tcp"
+
+int setup_type;
+char setup_directory[128];
+int need_init_q = 0;
+
+char *setup_titles[] = {
+       "BBS Home Directory",
+       "Citadel node name",
+       "Fully Qualified Domain Name (FQDN)",
+       "Human-readable node name",
+       "Phone number",
+       "BBS City and State",
+       "System Administrator",
+       "BBS User ID",
+       "Password encryption seed",
+       "'Room Creator = Room Aide' flag",
+       "Server timeout period",
+       "Initial access level",
+       "Registration requirements",
+       "Twit Detect!",
+       "Twit Detect target room",
+       "Maximum concurrent sessions",
+       "Paginator prompt",
+       "Restrict Internet mail flag",
+       "Nothing",
+       "Name of bit bucket subdirectory",
+       "System net password",
+       "Server port number",
+       };
+
+
+char *setup_text[] = {
+
+"0",
+"Enter the full pathname of the directory in which the BBS you are",
+"creating or updating resides.  If you specify a directory other than the",
+"default, you will need to specify the -h flag to the server when you start",
+"it up.",
+
+"1",
+"This is the name your system is known by on a Citadel/UX network.  It",
+"should be 8 characters or less, and should generally be comprised only of",
+"letters.  You can also use numbers and hyphens if necessary.",
+
+"2",
+"This is the name your system is known by on the Internet.",
+"If you're not on the Internet, simply set this to your",
+"node name followed by '.UUCP'.",
+
+"3",
+"This is a longer description of your system, readable by",
+"us mere humans.  It can be up to 20 characters long and it",
+"can have spaces in it.  Note that if you are part of a",
+"Cit86Net, this is the name your system will be known by on",
+"that network.",
+
+"4",
+"This is the main dialup number for your system.  If yours",
+"can not be dialed into, then make one up!  It should be in",
+"the format 'US 000 000 0000' - the US is your country code",
+"(look it up if you're not in the United States) and the",
+"rest is, of course, your area code and phone number.",
+"This doesn't have any use in Citadel/UX, but gateways to",
+"other networks may require it, and someday we may use this",
+"to have the networker automatically build a BBS list.",
+
+"5",
+"Enter the city and state your system is located in.",
+
+"6",
+"Enter the name of the system administrator (which is probably you).",
+"When an account is created with this name, it will automatically be",
+"assigned the highest access level.",
+
+"7",
+"You should create a user called 'bbs', 'guest', 'citadel', or something",
+"similar, that will allow users a way into your BBS.  The server will run",
+"under this user ID.  Please specify that (numeric) user ID here.",
+
+"8",
+"Citadel uses a (very) simple password encryption scheme",
+"to thwart breakins that could occur if someone snatched",
+"a copy of your userlog.  This parameter is part of the",
+"algorithm, so that the code can be different on each",
+"system.  Once it has been set, DO NOT change it --",
+"otherwise no one will be able to log in!",
+
+"9",
+"This is a boolean value.  If you set it to 1, anyone who",
+"creates a class 3 (passworded) or class 4 (invitation",
+"only) room will automatically become the Room Aide for",
+"that room, allowing them to edit it, delete/move messages,",
+"etc.  This is an administrative decision: it works well on",
+"some systems, and not so well on others.  Set this to 0 to",
+"disable this function.",
+
+"10",
+"This setting specifies how long a server session may sit idle before it is",
+"automatically terminated.  The recommended value is 900 seconds (15",
+"minutes).  Note that this has *nothing* to do with any watchdog timer that",
+"is presented to the user.  The server's timeout is intended to kill idle or",
+"zombie sessions running on a network, etc.  ",
+"You MUST set this to a reasonable value.  Setting it to zero will cause",
+"the server to malfunction.",
+
+"11",
+"This is the access level new users are assigned.",
+"",
+"The most common settings for this will be either 1, for",
+"systems which require new user validation by the system",
+"administrator ('sysop' is a word for people who run DOS",
+"boards!), or 4, for systems which give instant access.",
+"The current access levels available are:",
+
+"12",
+"'Registration' refers to the boring part of logging into a BBS for the first",
+"time: typing your name, address, and telephone number.  Set this value to 1",
+"to automatically do registration for new users, or 0 to not auto-register.",
+"Optionally, you could set it to, say, 2, to auto-register on a user's second",
+"call, but there really isn't much point to doing this.  The recommended",
+"value is 1 if you've set your inital access level to 1, or 0 if you've set",
+"your initial access level to something higher.",
+
+"13",
+"Every BBS has its share of problem users.  This is one",
+"good way to deal with them: if you enable this option,",
+"anyone you flag as a 'problem user' (access level 2) can",
+"post anywhere they want, but their messages will all be",
+"automatically moved to a room of your choosing.  Set this",
+"value to 1 to enable Twit Detect, or 0 to disable it.",
+
+"14",
+"This is the name of the room that problem user messages",
+"get moved to if you have Twit Detect enabled.",
+"(Note: don't forget to *create* this room!)",
+
+"15",
+"This is the maximum number of concurrent Citadel sessions which may be",
+"running at any given time.  Use this to keep very busy systems from being",
+"overloaded.",
+"  Set this value to 0 to allow an unlimited number of sessions.",
+
+"16",
+"This is the prompt that appears after each screenful of",
+"text - for users that have chosen that option.  Usually",
+"a simple '<more>' will do, but some folks like to be",
+"creative...",
+
+"17",
+"If you have a gateway set up to allow Citadel users to",
+"send Internet mail, with sendmail, qmail, or whatever, and",
+"you wish to restrict this to only users to whom you have",
+"given this privilege, set this flag to 1.  Otherwise, set",
+"it to 0 to allow everyone to send Internet mail.",
+"(Obviously, if your system doesn't have the ability to",
+"send mail to the outside world, this is all irrelevant.)",
+
+"18",
+"This parameter is meaningless and should be removed.",
+
+"19",
+"Select the name of a subdirectory (relative to the main",
+"Citadel directory - do not type an absolute pathname!) in",
+"which to place arriving file transfers that otherwise",
+"don't have a home.",
+
+"20",
+"If you use Citadel client/server sessions to transport network spool data",
+"between systems, this is the password other systems will use to authenticate",
+"themselves as network nodes rather than regular callers.",
+
+"21",
+"Specify the TCP port number on which your server will run.  Normally, this",
+"will be port 504, which is the official port assigned by the IANA for",
+"Citadel servers.  You'll only need to specify a different port number if",
+"you run multiple BBS's on the same computer and there's something else",
+"already using port 504.",
+
+"22",
+"23",
+"24",
+"25",
+"26",
+"27",
+"28",
+"29",
+"30",
+"DO NOT re-create files that you wish to keep intact!",
+"They will be permanently ERASED if you do so!",
+"(Obviously, if this is the first time you are setting up the BBS,",
+"then you will want to create all of the files.)",
+
+"31",
+"Setup has detected that you currently have data files from a Citadel/UX",
+"version 3.2x installation.  The program 'conv_32_40' can upgrade your",
+"files to version 4.0x format.",
+" Setup will now exit.  Please either run 'conv_32_40' or delete your data",
+"files, and run setup again.",
+
+"32",
+
+};
+
+
+long atol();
+void get_config();
+struct config config;
+int direction;
+
+void cleanup(int exitcode) {
+#ifdef CURSES_INC
+       if (setup_type == UI_CURSES) {
+               clear();
+               refresh();
+               endwin();
+               }
+#endif
+
+       /* Do an 'init q' if we need to.  When we hit the right one, init
+        * will take over and setup won't come back because we didn't do a
+        * fork().  If init isn't found, we fall through the bottom of the
+        * loop and setup exits quietly.
+        */
+       if (need_init_q) {
+               execlp("/sbin/init", "init", "q", NULL);
+               execlp("/usr/sbin/init", "init", "q", NULL);
+               execlp("/bin/init", "init", "q", NULL);
+               execlp("/usr/bin/init", "init", "q", NULL);
+               execlp("init", "init", "q", NULL);
+               }
+
+       exit(exitcode);
+       }
+
+
+#ifdef CURSES_INC
+void getlin(yp,xp,string,lim)  /* Gets a line from the terminal */
+int yp,xp;                     /* Where on the screen to start */
+char string[];                 /* Pointer to string buffer */
+int lim;                       /* Maximum length - if negative, no-show */
+{
+int a,b; char flag;
+
+       flag=0;
+       if (lim<0) { lim=(0-lim); flag=1; }
+       move(yp,xp);
+       standout();
+       for (a=0; a<lim; ++a) addch('-');
+       refresh();
+       move(yp,xp);
+       for (a=0; a<lim; ++a) addch(' ');
+       move(yp,xp);
+       printw("%s", string);
+GLA:   move(yp,xp+strlen(string));
+       refresh();
+       a=getch();
+       if (a==127) a=8;
+       a=(a&127);
+       if (a==10) a=13;
+       if ((a==8)&&(strlen(string)==0)) goto GLA;
+       if ((a!=13)&&(a!=8)&&(strlen(string)==lim)) goto GLA;
+       if ((a==8)&&(string[0]!=0)) {
+               string[strlen(string)-1]=0;
+               move(yp,xp+strlen(string));
+               addch(' ');
+               goto GLA;
+               }
+       if ((a==13)||(a==10)) {
+               standend();
+               move(yp,xp);
+               for (a=0; a<lim; ++a) addch(' ');
+               mvprintw(yp,xp,"%s",string);
+               refresh();
+               return;
+               }
+       b=strlen(string);
+       string[b]=a;
+       string[b+1]=0;
+       if (flag==0) addch(a);
+       if (flag==1) addch('*');
+       goto GLA;
+}
+#endif
+
+
+
+void title(text)
+char *text; {
+       if (setup_type == UI_TEXT) {
+               printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<%s>\n",text);
+               }
+       }
+
+
+void hit_any_key() {
+       char junk[5];
+
+#ifdef CURSES_INC
+       if (setup_type == UI_CURSES) {
+               mvprintw(20, 0, "Press any key to continue... ");
+               refresh();
+               getch();
+               return;
+               }
+#endif
+       printf("Press return to continue...");
+       fgets(junk, 5, stdin);
+       }
+
+int yesno(question)
+char *question; {
+       int answer = 0;
+       char buf[4096];
+
+       switch(setup_type) {
+
+               case UI_TEXT:
+                       do {
+                               printf("%s\nYes/No --> ",question);
+                               fgets(buf, 4096, stdin);
+                               answer=tolower(buf[0]);
+                               if (answer=='y') answer=1;
+                               else if (answer=='n') answer=0;
+                               } while ((answer<0)||(answer>1));
+                       break;
+
+               case UI_DIALOG:
+                       sprintf(buf, "dialog --yesno \"%s\" 7 80", question);
+                       answer = ( (system(buf)==0) ? 1 : 0);
+                       break;
+#ifdef CURSES_INC
+               case UI_CURSES:
+                       do {
+                               clear();
+                               standout();
+                               mvprintw(1, 20, "Question");
+                               standend();
+                               mvprintw(10, 0, "%-80s", question);
+                               mvprintw(20, 0, "%80s", "");
+                               mvprintw(20, 0, "Yes/No -> ");
+                               refresh();
+                               answer = getch();
+                               answer=tolower(answer);
+                               if (answer=='y') answer=1;
+                               else if (answer=='n') answer=0;
+                               } while ((answer<0)||(answer>1));
+                       break;
+#endif
+
+               }
+       return(answer);
+       }
+
+
+
+void dump_access_levels() {
+       int a;
+       for (a=0; a<=6; ++a) printf("%d %s\n",a,axdefs[a]);
+       }
+
+void get_setup_msg(char *dispbuf, int msgnum) {
+       int a,b;
+
+       a=0;
+       b=0;
+       while (atol(setup_text[a]) != msgnum) ++a;
+       ++a;
+       strcpy(dispbuf, "");
+       do {
+               strcat(dispbuf, setup_text[a++]);
+               strcat(dispbuf, "\n");
+               } while(atol(setup_text[a])!=(msgnum+1));
+       }
+
+void print_setup(msgnum) {
+       char dispbuf[4096];
+
+       get_setup_msg(dispbuf, msgnum);
+       printf("\n\n%s\n\n", dispbuf);
+       }
+
+
+void important_message(char *title, char *msgtext) {
+       char buf[4096];
+
+       switch(setup_type) {
+               
+               case UI_TEXT:
+                       printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
+                       printf("       %s \n\n%s\n\n", title, msgtext);
+                       hit_any_key();
+                       break;
+
+               case UI_DIALOG:
+                       sprintf(buf, "dialog --title \"%s\" --msgbox \"\n%s\" 20 80",
+                               title, msgtext);
+                       system(buf);
+                       break;
+#ifdef CURSES_INC
+               case UI_CURSES:
+                       clear();
+                       move(1, 20);
+                       standout();
+                       printw("  Important Message  ");
+                       standend();
+                       move(3, 0);
+                       printw("%s", msgtext);
+                       refresh();
+                       hit_any_key();
+                       break;
+#endif
+
+               }
+       }
+
+void important_msgnum(int msgnum) {
+       char dispbuf[4096];
+       
+       get_setup_msg(dispbuf, msgnum);
+       important_message("Important Message", dispbuf);
+       }
+
+void display_error(char *error_message) {
+       important_message("Error", error_message);
+       }
+
+void progress(text,curr,cmax)
+char *text;
+long curr;
+long cmax; {
+       static long dots_printed;
+       long a;
+       static long prev;
+       static FILE *gauge = NULL;
+       char gcmd[256];
+
+       switch(setup_type) {
+
+               case UI_TEXT:
+                       if (curr==0) {
+                               printf("%s\n",text);
+                               printf("..........................");
+                               printf("..........................");
+                               printf("..........................\r");
+                               fflush(stdout);
+                               dots_printed = 0;
+                               }
+                       else if (curr==cmax) {
+                               printf("\r%79s\n","");
+                               }
+                       else {
+                               a=(curr * 100) / cmax;
+                               a=a*78; a=a/100;
+                               while (dots_printed < a) {
+                                       printf("*");
+                                       ++dots_printed;
+                                       fflush(stdout);
+                                       }
+                               }
+                       break;
+
+#ifdef CURSES_INC
+               case UI_CURSES:
+                       if (curr==0) {
+                               clear();
+                               move(5, 20);
+                               printw("%s\n",text);
+                               move(10, 1);
+                               printf("..........................");
+                               printf("..........................");
+                               printf("..........................\r");
+                               refresh();
+                               dots_printed = 0;
+                               }
+                       else if (curr==cmax) {
+                               clear();
+                               refresh();
+                               }
+                       else {
+                               a=(curr * 100) / cmax;
+                               a=a*78; a=a/100;
+                               move(10,1);
+                               dots_printed = 0;
+                               while (dots_printed < a) {
+                                       printw("*");
+                                       ++dots_printed;
+                                       }
+                               refresh();
+                               }
+                       break;
+#endif
+                       
+               case UI_DIALOG:
+                       if ( (curr == 0) && (gauge == NULL) ) {
+                               sprintf(gcmd, "dialog --guage \"%s\" 7 80 0",
+                                       text);
+                               gauge = (FILE *) popen(gcmd, "w");
+                               prev = 0;
+                               }
+                       else if (curr==cmax) {
+                               fprintf(gauge, "100\n");
+                               pclose(gauge);
+                               gauge = NULL;
+                               }
+                       else {
+                               a=(curr * 100) / cmax;
+                               if (a != prev) {
+                                       fprintf(gauge, "%ld\n", a);
+                                       fflush(gauge);
+                                       }
+                               prev = a;
+                               }
+                       break;
+               }
+       }
+
+
+void cre8floors() {
+       int a,main_ref_count;
+       FILE *fp;
+       struct quickroom quickroom;
+       struct floor floor_rec;
+
+       /*
+        * first, put all existing rooms on floor 0 (the main floor) and
+        * count the room records that are in use so we can set the main
+        * floor's reference count
+        */
+       main_ref_count = 0;
+       fp = fopen("quickroom", "rb+");
+       for (a=0; a<MAXROOMS; ++a) {
+               progress("Preparing room files for addition of floors",
+                       (long)a,
+                       (long)MAXROOMS-1);
+               fseek(fp, (a*((long)sizeof(struct quickroom))), 0);
+               fread((char *)&quickroom,sizeof(struct quickroom),1,fp);
+               if (quickroom.QRflags & QR_INUSE) ++main_ref_count;
+               quickroom.QRfloor = 0;
+               fseek(fp, (a*((long)sizeof(struct quickroom))), 0);
+               fwrite((char *)&quickroom,sizeof(struct quickroom),1,fp);
+               }
+       fclose(fp);
+
+       /* Open a new floortab file */
+       fp=fopen("floortab","wb");
+
+       /* Create the main floor */
+       floor_rec.f_flags = (F_INUSE);
+       strcpy(floor_rec.f_name, "Main Floor");
+       floor_rec.f_ref_count = main_ref_count;
+       floor_rec.f_reserved = 0;
+       fwrite((char *)&floor_rec,sizeof(struct floor),1,fp);
+
+
+       /* make the remaining floors blanks */
+       floor_rec.f_flags = 0;
+       strcpy(floor_rec.f_name, "");
+       floor_rec.f_ref_count = 0;
+       floor_rec.f_reserved = 0;
+
+       for (a=1; a<MAXFLOORS; ++a) {
+               progress("Creating floor table (floortab)",
+                       (long)a,
+                       (long)MAXFLOORS-1
+                       );
+               fwrite((char *)&floor_rec,sizeof(struct floor),1,fp);
+               }
+       fclose(fp);
+
+       }
+
+
+/*
+ * check (and fix) floor reference counts
+ */
+void check_ref_counts() {
+       int ref[MAXFLOORS];
+       int a;
+       FILE *fp;
+       struct quickroom qrbuf;
+       struct floor flbuf;
+
+       for (a=0; a<MAXFLOORS; ++a) ref[a] = 0;
+
+       fp = fopen("quickroom","rb");
+       for (a=0; a<MAXROOMS; ++a) {
+               progress("Checking reference counts - phase 1 of 2",
+                       a,MAXROOMS-1);
+               fread((char *)&qrbuf,sizeof(struct quickroom),1,fp);
+               if (qrbuf.QRflags & QR_INUSE) {
+                       ++ref[(int)qrbuf.QRfloor];
+                       }
+               }
+       fclose(fp);
+
+       fp = fopen("floortab","rb+");
+       for (a=0; a<MAXFLOORS; ++a) {
+               progress("Checking reference counts - phase 2 of 2",
+                       a,MAXFLOORS-1);
+               fseek(fp,(long)a*(long)sizeof(struct floor),0);
+               fread((char *)&flbuf,sizeof(struct floor),1,fp);
+               flbuf.f_ref_count = ref[a];
+               fseek(fp,(long)a*(long)sizeof(struct floor),0);
+               fwrite((char *)&flbuf,sizeof(struct floor),1,fp);
+               }
+       fclose(fp);
+       }       
+
+
+/*
+ * check_services_entry()  -- Make sure "citadel" is in /etc/services
+ *
+ */
+void check_services_entry() {
+       char question[128];
+       FILE *sfp;
+
+       sprintf(question,
+"There is no '%s' entry in /etc/services.  Would you like to add one?",
+               SERVICE_NAME);
+
+       if (getservbyname(SERVICE_NAME, PROTO_NAME) == NULL) {
+               if (yesno(question)==1) {
+                       sfp = fopen("/etc/services", "a");
+                       if (sfp == NULL) {
+                               display_error(strerror(errno));
+                               }
+                       else {
+                               fprintf(sfp, "%s                504/tcp\n",
+                                       SERVICE_NAME);
+                               fclose(sfp);
+                               }
+                       }
+               }
+
+       }
+
+
+/*
+ * check_inittab_entry()  -- Make sure "citadel" is in /etc/inittab
+ *
+ */
+void check_inittab_entry() {
+       FILE *infp;
+       char buf[256];
+       char looking_for[256];
+       char question[128];
+       char *ptr;
+       int have_entry = 0;
+       char entryname[3];
+
+       /* Determine the fully qualified path name of citserver */
+       sprintf(looking_for, "%s/citserver ", BBSDIR);
+
+       /* Pound through /etc/inittab line by line.  Set have_entry to 1 if
+        * an entry is found which we believe starts citserver.
+        */
+       infp = fopen("/etc/inittab", "r");
+       if (infp == NULL) {
+               display_error(strerror(errno));
+               }
+       else {
+               while (fgets(buf, 256, infp) != NULL) {
+                       buf[strlen(buf) - 1] = 0;
+                       ptr = strtok(buf, ":");
+                       ptr = strtok(NULL, ":");
+                       ptr = strtok(NULL, ":");
+                       ptr = strtok(NULL, ":");
+                       if (ptr != NULL) {
+                        if (!strncmp(ptr, looking_for, strlen(looking_for))) {
+                                       ++have_entry;
+                                       }
+                               }
+                       }
+               fclose(infp);
+               }
+
+       /* If there's already an entry, then we have nothing left to do. */
+       if (have_entry > 0) return;
+
+       /* Otherwise, prompt the user to create an entry. */
+       sprintf(question,
+"There is no '%s' entry in /etc/inittab.\nWould you like to add one?",
+               looking_for);
+       if (yesno(question)==0) return;
+
+       /* Generate a unique entry name for /etc/inittab */
+       sprintf(entryname, "c0");
+       do {
+               ++entryname[1];
+               if (entryname[1] > '9') {
+                       entryname[1] = 0;
+                       ++entryname[0];
+                       if (entryname[0] > 'z') {
+                               display_error(
+                                       "Can't generate a unique entry name");
+                               return;
+                               }
+                       }
+               sprintf(buf,
+                       "grep %s: /etc/inittab >/dev/null 2>&1", entryname);
+               } while(system(buf)==0);
+
+       /* Now write it out to /etc/inittab */
+       infp = fopen("/etc/inittab", "a");
+       if (infp == NULL) {
+               display_error(strerror(errno));
+               }
+       else {
+               fprintf(infp, "# Start the Citadel/UX server...\n");
+               fprintf(infp,"%s:2345:respawn:%s -h%s\n",
+                       entryname, looking_for, setup_directory);
+               fclose(infp);
+               need_init_q = 1;
+               }
+       }
+
+
+
+/*
+ * Create a blank call log
+ */
+void cre8clog() {
+       int file,a;
+       struct calllog calllog;
+
+       calllog.CLfullname[0]=0;
+       calllog.CLtime=0L;
+       calllog.CLflags=0;
+       a=0;
+
+       file=creat("calllog.pos",0666);
+       chmod("calllog.pos",0666);
+       write(file,&a,sizeof(int));
+       close(file);
+
+       file=creat("calllog",0666);
+       chmod("calllog",0666);
+       for (a=0; a<CALLLOG; ++a) {
+               progress("Creating call log file",
+                       (long)a,
+                       (long)CALLLOG-1
+                       );
+               write(file,&calllog,sizeof(struct calllog));
+               }
+       close(file);
+       }
+
+
+void set_str_val(int msgpos, char str[]) {
+       char buf[4096];
+       char setupmsg[4096];
+       char tempfile[64];
+       FILE *fp;
+
+       sprintf(tempfile, "/tmp/setup.%d", getpid());
+
+       switch (setup_type) {
+               case UI_TEXT:
+                       title(setup_titles[msgpos]);
+                       print_setup(msgpos);
+                       if (msgpos==11) dump_access_levels();
+                       printf("This is currently set to:\n%s\n",str);
+                       printf("Enter new value or press return to leave unchanged:\n");
+                       fgets(buf, 4096, stdin);
+                       buf[strlen(buf)-1] = 0;
+                       if (strlen(buf)!=0) strcpy(str,buf);
+                       break;
+               case UI_DIALOG:
+                       get_setup_msg(setupmsg, msgpos);
+                       sprintf(buf,
+                               "dialog --title \"%s\" --inputbox \"\n%s\n\" 20 80 \"%s\" 2>%s",
+                               setup_titles[msgpos],
+                               setupmsg,
+                               str, tempfile);
+                       if (system(buf)==0) {
+                               fp = fopen(tempfile, "rb");
+                               fgets(str, 4095, fp);
+                               fclose(fp);
+                               if (strlen(str)>0) 
+                                       if (str[strlen(str)-1]==10)
+                                               str[strlen(str)-1]=0;
+                               }
+                       break;
+#ifdef CURSES_INC
+               case UI_CURSES:
+                       clear();
+                       move(1, ((80-strlen(setup_titles[msgpos]))/2) );
+                       standout();
+                       printw("%s", setup_titles[msgpos]);
+                       standend();
+                       move(3, 0);
+                       get_setup_msg(setupmsg, msgpos);
+                       printw("%s", setupmsg);
+                       refresh();
+                       getlin(20, 0, str, 80);
+                       break;
+#endif
+               }
+       }
+
+void set_int_val(msgpos, ip)
+int msgpos;
+int *ip; {
+       char buf[16];
+       sprintf(buf,"%d",(int)*ip);
+       set_str_val(msgpos, buf);
+       *ip = atoi(buf);
+       }
+
+
+void set_char_val(msgpos, ip)
+int msgpos;
+char *ip; {
+       char buf[16];
+       sprintf(buf,"%d",(int)*ip);
+       set_str_val(msgpos, buf);
+       *ip = (char)atoi(buf);
+       }
+
+
+void set_long_val(msgpos, ip)
+int msgpos;
+long *ip; {
+       char buf[16];
+       sprintf(buf,"%ld",*ip);
+       set_str_val(msgpos, buf);
+       *ip = atol(buf);
+       }
+
+
+int yesno_s(question) {
+       int a;
+       char buf[4096];
+       char tempfile[64];
+       FILE *fp;
+
+       sprintf(tempfile, "/tmp/setup.%d", getpid());
+       switch (setup_type) {
+       
+               case UI_TEXT:
+                       a=yesno(question);
+                       if (a==1) a=yesno("Are you SURE you want to reinitialize this file? ");
+                       return(a);
+                       break;
+
+               case UI_CURSES:
+                       a=yesno(question);
+                       if (a==1) a=yesno("Are you SURE you want to reinitialize this file? ");
+                       return(a);
+                       break;
+
+               case UI_DIALOG:
+                       a = yesno(question);
+                       if (a==0) return(a);
+                       sprintf(buf, "dialog --title \"Confirm file overwrite\" --menu \"\nAre you SURE you want to reinitialize this file?\n\" 13 80 2 NO \"No, don't overwrite\" YES \"Yes, overwrite the existing file\" 2>%s", tempfile);
+                       a = system(buf);
+                       if (a != 0) return(0);
+                       fp = fopen(tempfile, "rb");
+                       fgets(buf, 4095, fp);
+                       fclose(fp);
+                       if (strlen(buf)>0) 
+                               if (buf[strlen(buf)-1]==10)
+                                       buf[strlen(buf)-1]=0;
+                       return( (!strcmp(buf, "YES")) ? 1 : 0 );
+                       break;
+
+               }
+
+       return(0); /* just in case */
+       }
+
+
+void edit_value(curr)
+int curr; {
+ int a;
+ switch(curr) {
+
+case 1:
+       set_str_val(curr, config.c_nodename);
+       break;
+
+case 2:
+       set_str_val(curr, config.c_fqdn);
+       break;
+
+case 3:
+       set_str_val(curr, config.c_humannode);
+       break;
+
+case 4:
+       set_str_val(curr, config.c_phonenum);
+       break;
+
+case 5:
+       set_str_val(curr, config.c_bbs_city);
+       break;
+
+case 6:
+       set_str_val(curr, config.c_sysadm);
+       break;
+
+case 7:
+       set_int_val(curr, &config.c_bbsuid);
+       break;
+
+case 8:
+       set_int_val(curr, &config.c_pwcrypt);
+       break;
+
+case 9:
+       set_char_val(curr, &config.c_creataide);
+       break;
+
+case 10:
+       set_int_val(curr, &config.c_sleeping);
+       break;
+
+case 11:
+       set_char_val(curr, &config.c_initax);
+       break;
+
+case 12:
+       set_char_val(curr, &config.c_regiscall);
+       break;
+
+case 13:
+       set_char_val(curr, &config.c_twitdetect);
+       break;
+
+case 14:
+       set_str_val(curr, config.c_twitroom);
+       break;
+
+case 15:
+       set_int_val(curr, &config.c_maxsessions);
+       break;
+
+case 16:
+       set_str_val(curr, config.c_moreprompt);
+       break;
+
+case 17:
+       set_char_val(curr, &config.c_restrict);
+       break;
+
+case 18:
+       set_long_val(curr, &config.c_msgbase);
+       break;
+
+case 19:
+       set_str_val(curr, config.c_bucket_dir);
+       config.c_bucket_dir[14] = 0;
+       for (a=0; a<strlen(config.c_bucket_dir); ++a)
+               if (!isalpha(config.c_bucket_dir[a]))
+                       strcpy(&config.c_bucket_dir[a],
+                               &config.c_bucket_dir[a+1]);
+       break;
+
+case 20:
+       set_str_val(curr, config.c_net_password);
+       break;
+
+case 21:
+       set_int_val(curr, &config.c_port_number);
+       break;
+
+
+ }
+}
+
+/*
+ * (re-)write the config data to disk
+ */
+void write_config_to_disk() {
+       FILE *fp;
+
+       fp=fopen("citadel.config","wb");
+       if (fp==NULL) {
+               display_error("setup: cannot open citadel.config");
+               cleanup(1);
+               }
+       fwrite((char *)&config,sizeof(struct config),1,fp);
+       fclose(fp);
+       }
+
+
+
+
+/*
+ * Figure out what type of user interface we're going to use
+ */
+int discover_ui() {
+
+#ifdef CURSES_INC
+       return UI_CURSES;
+#endif
+
+       if (system("dialog -h </dev/null 2>&1 |grep Savio")==0) {
+               return UI_DIALOG;
+               }
+
+       return UI_TEXT;
+       }
+
+
+
+
+
+void main(int argc, char *argv[]) {
+       int a;
+       int curr;
+       char aaa[128];
+       FILE *fp;
+       int old_setup_level = 0;
+       int info_only = 0;
+
+       /* set an invalid setup type */
+       setup_type = (-1);
+
+       /* parse command line args */
+       for (a=0; a<argc; ++a) {
+               if (!strncmp(argv[a], "-u", 2)) {
+                       strcpy(aaa, argv[a]);
+                       strcpy(aaa, &aaa[2]);
+                       setup_type = atoi(aaa);
+                       }
+               if (!strcmp(argv[a], "-i")) {
+                       info_only = 1;
+                       }
+               }
+
+
+       /* If a setup type was not specified, try to determine automatically
+        * the best one to use out of all available types.
+        */
+       if (setup_type < 0) {
+               setup_type = discover_ui();
+               }
+
+#ifdef CURSES_INC
+       if (setup_type == UI_CURSES) {
+               initscr();
+               raw();
+               noecho();
+               }
+#endif
+
+       if (info_only == 1) {
+               important_message("Citadel/UX Setup", CITADEL);
+               cleanup(0);
+               }
+
+       strcpy(setup_directory, BBSDIR);
+       set_str_val(0, setup_directory);
+       if (chdir(setup_directory) != 0) {
+               important_message("Citadel/UX Setup",
+                       "The directory you specified does not exist.");
+               cleanup(errno);
+               }
+
+
+       switch(setup_type) {
+               
+               case UI_TEXT:
+                       printf("\n\n\n               *** Citadel/UX setup program ***\n\n");
+                       break;
+               
+               case UI_DIALOG:
+                       system("exec clear");
+                       break;
+                       
+               }
+
+       /*
+        * What we're going to try to do here is append a whole bunch of
+        * nulls to the citadel.config file, so we can keep the old config
+        * values if they exist, but if the file is missing or from an
+        * earlier version with a shorter config structure, when setup tries
+        * to read the old config parameters, they'll all come up zero.
+        * The length of the config file will be set to what it's supposed
+        * to be when we rewrite it, because we replace the old file with a
+        * completely new copy.  (Neat, eh?)
+        */
+
+       fp=fopen("citadel.config","ab");
+       if (fp==NULL) {
+               display_error("setup: cannot append citadel.config");
+               cleanup(errno);
+               }
+       for (a=0; a<sizeof(struct config); ++a) putc(0,fp);
+       fclose(fp);
+
+       /* now we re-open it, and read the old or blank configuration */
+       fp=fopen("citadel.config","rb");
+       if (fp==NULL) {
+               display_error("setup: cannot open citadel.config");
+               cleanup(errno);
+               }
+       fread((char *)&config,sizeof(struct config),1,fp);
+       fclose(fp);
+
+
+       /* set some sample/default values in place of blanks... */
+       if (strlen(config.c_nodename)==0)
+               strcpy(config.c_nodename,"mysystem");
+       if (strlen(config.c_fqdn)==0)
+               sprintf(config.c_fqdn,"%s.UUCP",config.c_nodename);
+       if (strlen(config.c_humannode)==0)
+               strcpy(config.c_humannode,"My System");
+       if (strlen(config.c_phonenum)==0)
+               strcpy(config.c_phonenum,"US 800 555 1212");
+       if (config.c_initax == 0)
+               config.c_initax = 1;
+       /* if (config.c_regiscall == 0)
+               config.c_regiscall = 1; */
+       if (strlen(config.c_moreprompt)==0)
+               strcpy(config.c_moreprompt,"<more>");
+       if (strlen(config.c_twitroom)==0)
+               strcpy(config.c_twitroom,"Trashcan");
+       if (strlen(config.c_bucket_dir)==0)
+               strcpy(config.c_bucket_dir,"bitbucket");
+       if (config.c_msgbase == 0L)
+               config.c_msgbase = 2000000;
+       if (strlen(config.c_net_password)==0)
+               strcpy(config.c_net_password,"netpassword");
+       if (config.c_port_number == 0) {
+               config.c_port_number = 504;
+               }
+       if (config.c_ipgm_secret == 0) {
+               srand(getpid());
+               config.c_ipgm_secret = rand();
+               }
+       if (config.c_sleeping == 0) {
+               config.c_sleeping = 900;
+               }
+
+       /* Go through a series of dialogs prompting for config info */
+       for (curr = 1; curr <= MAXSETUP; ++curr) {
+               edit_value(curr);
+               }
+
+       /*
+       if (setuid(config.c_bbsuid) != 0) {
+               important_message("Citadel/UX Setup",
+                       "Failed to change the user ID to your BBS user.");
+               cleanup(errno);
+               }
+       */
+
+       /***** begin version update section ***** */
+       /* take care of any updating that is necessary */
+
+       old_setup_level = config.c_setup_level;
+
+       if (old_setup_level == 0) goto NEW_INST;
+       
+       if (old_setup_level < 323) {
+               important_message("Citadel/UX Setup",
+                       "This Citadel/UX installation is too old to be upgraded.");
+               cleanup(1);
+               }
+
+       write_config_to_disk();
+
+       if ((config.c_setup_level / 10) == 32) {
+               important_msgnum(31);
+               cleanup(0);
+               }
+
+       if (config.c_setup_level < 400) {
+               config.c_setup_level = 400;
+               }
+
+       /* end of 3.23 -> 4.00 update section */
+
+       /* end of 4.00 -> 4.02 update section */
+
+       old_setup_level = config.c_setup_level;
+
+       /* end of version update section */
+
+NEW_INST:
+       config.c_setup_level = REV_LEVEL;
+       write_config_to_disk();
+
+       system("mkdir info 2>/dev/null");               /* Create these */
+       system("mkdir rooms 2>/dev/null");
+       system("mkdir bio 2>/dev/null");
+       system("mkdir userpics 2>/dev/null");
+       system("mkdir messages 2>/dev/null");
+       system("mkdir help 2>/dev/null");
+       system("mkdir images 2>/dev/null");
+       sprintf(aaa,"mkdir %s 2>/dev/null",config.c_bucket_dir);
+       system(aaa);
+
+
+       system("rm -fr ./chatpipes 2>/dev/null");       /* Don't need these */
+       system("rm -fr ./expressmsgs 2>/dev/null");
+       unlink("sessions");
+
+       important_msgnum(30);
+
+
+       a=0;
+       fp=fopen("calllog","r");
+       if (fp==NULL) {
+               cre8clog();
+               }
+       else {
+               fclose(fp);
+               if (yesno_s("Create call log?")==1) cre8clog();
+               }
+
+       check_ref_counts();             /* Check reference counts */
+       check_services_entry();         /* Check /etc/services */
+       check_inittab_entry();          /* Check /etc/inittab */
+
+       progress("Setting file permissions", 0, 3);
+       chown(".", config.c_bbsuid, getgid());
+       progress("Setting file permissions", 1, 3);
+       chown("citadel.config", config.c_bbsuid, getgid());
+       progress("Setting file permissions", 2, 3);
+       sprintf(aaa, "find . -exec chown %d {} \\; 2>/dev/null",
+               config.c_bbsuid);
+       system(aaa);
+       progress("Setting file permissions", 3, 3);
+
+       important_message("Setup finished", 
+               "Setup is finished.  You may now start the Citadel server.");
+
+
+       cleanup(0);
+}
diff --git a/citadel/stats.c b/citadel/stats.c
new file mode 100644 (file)
index 0000000..bf60e87
--- /dev/null
@@ -0,0 +1,357 @@
+/* Citadel/UX call log stats program
+ * version 2.4
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <stdio.h>
+#include "citadel.h"
+
+#define disply(x,y) printf("%20s            %4.1f    %4.1f    %4d\n",x,((float)y)/calls,((float)y)/days,y)
+
+#define GRANULARITY 100
+
+int batch_mode = 0;
+
+struct caller
+  {
+    struct caller *next;
+    char Cname[30];
+    int Ctimescalled;
+  };
+
+void get_config ();
+struct config config;
+
+
+void 
+prompt ()
+{
+  char buf[16];
+  if (batch_mode == 0)
+    {
+      printf ("Press return to continue...");
+      fgets (buf, 16, stdin);
+    }
+  else
+    {
+      printf ("\n");
+    }
+}
+
+int 
+halfhour (time)                        /* Returns half-hour time period of time */
+     long time;
+{
+  int a;
+  struct tm *tm;
+  tm = (struct tm *) localtime (&time);
+  a = (tm->tm_hour) * 3;
+  if ((tm->tm_min) > 19)
+    ++a;
+  if ((tm->tm_min) > 39)
+    ++a;
+  return (a);
+}
+
+
+
+void 
+progress (curr, max)
+     long curr;
+     long max;
+{
+  static int dots;
+  int pos;
+
+  if (curr == 0L)
+    {
+      printf ("--------------------------------------");
+      printf ("--------------------------------------\r");
+      fflush (stdout);
+      dots = 0;
+    }
+
+  pos = (curr * 72) / max;
+  while (dots < pos)
+    {
+      putc ('*', stdout);
+      fflush (stdout);
+      ++dots;
+    }
+
+  if (dots == 72)
+    {
+      printf ("                                      ");
+      printf ("                                      \r");
+      fflush (stdout);
+    }
+
+}
+
+
+
+
+void 
+main (argc, argv)
+     int argc;
+     char *argv[];
+{
+  struct calllog calllog;
+  struct usersupp usersupp;
+  int file, pos, a, b, lowest;
+  float p, q;
+  long timeon[72];
+  long timeup[72];
+  char dname[30];
+  int sess = 0;
+  long cftime, cttime, aa;
+  int calls, logins, newusers;
+  int badpws, terms, drops, sleeps;
+  long from, to, tottime;
+  int days, hours, minutes;
+  char aaa[100];
+  struct tm *tm;
+  struct caller *callers = NULL;
+  struct caller *callptr = NULL;
+  FILE *fp, *sortpipe;
+  char thegraph[GRANULARITY][73];
+  int pc_only = 0;
+
+  for (a = 0; a < argc; ++a)
+    {
+      if (!strcmp (argv[a], "-b"))
+       batch_mode = 1;
+      if (!strcmp (argv[a], "-p"))
+       pc_only = 1;
+    }
+
+
+  for (a = 0; a < GRANULARITY; ++a)
+    strcpy (thegraph[a],
+           "........................................................................");
+
+  get_config ();
+
+  if (pc_only)
+    goto PC_ONLY_HERE;
+
+  if (!batch_mode)
+    printf ("Scanning call log, please wait...\n\n\n\n");
+
+  file = open ("calllog.pos", O_RDONLY);
+  read (file, &pos, 2);
+  close (file);
+  from = 0L;
+  to = 0L;
+  for (a = 0; a < 72; ++a)
+    {
+      timeon[a] = 0L;
+      timeup[a] = 0L;
+    }
+  cftime = 0L;
+  cttime = 0L;
+
+  calls = 0;
+  logins = 0;
+  newusers = 0;
+  badpws = 0;
+  terms = 0;
+  drops = 0;
+  sleeps = 0;
+  file = open ("calllog", O_RDONLY);
+  lseek (file, (long) (pos * sizeof (struct calllog)), 0);
+  if (!batch_mode)
+    printf ("Scanning call log, please wait...\n");
+  for (a = 0; a < CALLLOG; ++a)
+    {
+      if (!batch_mode)
+       progress ((long) a, (long) (CALLLOG - 1));
+      if ((a + pos) == CALLLOG)
+       lseek (file, 0L, 0);
+      read (file, &calllog, sizeof (struct calllog));
+      if (calllog.CLflags != 0)
+       {
+         if ((calllog.CLtime < from) || (from == 0L))
+           from = calllog.CLtime;
+         if ((calllog.CLtime > to) || (to == 0L))
+           to = calllog.CLtime;
+         strcpy (aaa, "");
+         if (calllog.CLflags & CL_CONNECT)
+           {
+             ++calls;
+             ++sess;
+             if (sess == 1)
+               cftime = calllog.CLtime;
+             strcpy (dname, calllog.CLfullname);
+           }
+         if (calllog.CLflags & CL_LOGIN)
+           {
+             ++logins;
+             b = 0;
+             for (callptr = callers; callptr != NULL; callptr = callptr->next)
+               {
+                 if (!strcmp (callptr->Cname, calllog.CLfullname))
+                   {
+                     ++b;
+                     ++callptr->Ctimescalled;
+                   }
+               }
+             if (b == 0)
+               {
+                 callptr = (struct caller *) malloc (sizeof (struct caller));
+                 callptr->next = callers;
+                 callers = callptr;
+                 strcpy (callers->Cname, calllog.CLfullname);
+                 callers->Ctimescalled = 1;
+               }
+           }
+         if (calllog.CLflags & CL_NEWUSER)
+           ++newusers;
+         if (calllog.CLflags & CL_BADPW)
+           ++badpws;
+         if (calllog.CLflags & CL_TERMINATE)
+           {
+             ++terms;
+             --sess;
+             if (sess == 0)
+               {
+                 cttime = calllog.CLtime;
+                 for (aa = cftime; aa <= cttime; aa = aa + 300L)
+                   timeon[halfhour (aa)] = timeon[halfhour (aa)] + 5L;
+                 cftime = 0L;
+                 cttime = 0L;
+               }
+           }
+         if (calllog.CLflags & CL_DROPCARR)
+           {
+             ++drops;
+             --sess;
+             if (sess == 0)
+               {
+                 cttime = calllog.CLtime;
+                 for (aa = cftime; aa <= cttime; aa = aa + 300L)
+                   timeon[halfhour (aa)] = timeon[halfhour (aa)] + 5L;
+                 cftime = 0L;
+                 cttime = 0L;
+               }
+           }
+         if (calllog.CLflags & CL_SLEEPING)
+           {
+             ++sleeps;
+             --sess;
+             if (sess == 0)
+               {
+                 cttime = calllog.CLtime;
+                 for (aa = cftime; aa <= cttime; aa = aa + 300L)
+                   timeon[halfhour (aa)] = timeon[halfhour (aa)] + 5L;
+                 cftime = 0L;
+                 cttime = 0L;
+               }
+           }
+
+         if (sess < 0)
+           sess = 0;
+
+       }
+    }
+  close (file);
+  tottime = to - from;
+  days = (int) (tottime / 86400L);
+  hours = (int) ((tottime % 86400L) / 3600L);
+  minutes = (int) ((tottime % 3600L) / 60L);
+
+  printf ("                              Avg/Call Avg/Day  Total\n");
+  disply ("Calls:", calls);
+  disply ("Logins:", logins);
+  disply ("New users:", newusers);
+  disply ("Bad pw attempts:", badpws);
+  disply ("Proper logoffs:", terms);
+  disply ("Carrier drops:", drops);
+  disply ("Sleeping drops:", sleeps);
+
+  printf ("\n");
+  tm = (struct tm *) localtime (&from);
+  printf ("From:              %s", (char *) asctime (localtime (&from)));
+  printf ("To:                %s", (char *) asctime (localtime (&to)));
+  printf ("Total report time: ");
+  printf ("%d days, %d hours, %d minutes\n",
+         days, hours, minutes);
+
+  for (aa = from; aa <= to; aa = aa + 1200L)
+    timeup[halfhour (aa)] = timeup[halfhour (aa)] + 20L;
+  prompt ();
+
+  lowest = GRANULARITY - 1;
+  for (b = 0; b < 72; ++b)
+    {
+      for (a = 0; a <= GRANULARITY; ++a)
+       {
+         p = ((float) timeon[b]) / ((float) timeup[b]) * GRANULARITY;
+         q = (float) a;
+         if (p >= q)
+           {
+             thegraph[(GRANULARITY - 1) - a][b] = '#';
+             if (lowest > (GRANULARITY - 1) - a)
+               lowest = (GRANULARITY - 1) - a;
+           }
+       }
+    }
+
+  printf ("\n\n\n\n\n\n");
+
+  b = ((GRANULARITY - lowest) / 18);
+  if (b < 1)
+    b = 1;
+  for (a = lowest; a < GRANULARITY; a = a + b)
+    printf ("%2d%% |%s\n",
+           100 - (a + 1),
+           thegraph[a]);
+  printf ("    +------------------------------------------------------------------------\n");
+  printf ("     0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19 20 21 22 23\n");
+  fflush (stdout);
+  prompt ();
+
+  printf ("\n\n\n\n");
+
+
+  printf ("Top 20 Callers (sorted by total number of logins)\n");
+  printf ("Calls Avg/Day Username\n");
+  printf ("----- ------- ------------------------------\n");
+  fflush (stdout);
+  sortpipe = (FILE *) popen ("sort |tail -20 |sort -r", "w");
+  for (callptr = callers; callptr != NULL; callptr = callptr->next)
+    {
+      fprintf (sortpipe, "%5d %7.2f %-30s\n",
+              callptr->Ctimescalled,
+              (((float) callptr->Ctimescalled) / ((float) days)),
+              callptr->Cname);
+    }
+  pclose (sortpipe);
+  while (callers != NULL)
+    {
+      callptr = callers->next;
+      free (callers);
+      callers = callptr;
+    }
+  prompt ();
+
+PC_ONLY_HERE:
+  printf ("Top 20 Contributing Users (post to call ratio)\n");
+  printf ("P/C Ratio Username\n");
+  printf ("--------- ------------------------------\n");
+  fflush (stdout);
+  sortpipe = (FILE *) popen ("sort |tail -20 |sort -r", "w");
+  fp = fopen ("usersupp", "r");
+  while ((fp != NULL)
+       && (fread ((char *) &usersupp, sizeof (struct usersupp), 1, fp) > 0))
+    {
+      fprintf (sortpipe, "%9.2f %-30s\n",
+              ((float) usersupp.posted / (float) usersupp.timescalled),
+              usersupp.fullname);
+    }
+  fclose (fp);
+  pclose (sortpipe);
+  exit (0);
+}
diff --git a/citadel/support.c b/citadel/support.c
new file mode 100644 (file)
index 0000000..8ab6d1e
--- /dev/null
@@ -0,0 +1,243 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <pthread.h>
+#include "citadel.h"
+#include "server.h"
+#include "proto.h"
+
+/*
+ * struncmp()  -  case-insensitive version of strncmp()
+ *                citadel.h will #define a strucmp() based on this
+ */
+int struncmp(char *lstr, char *rstr, int len)
+{
+       int pos = 0;
+       char lc,rc;
+       while (pos<len) {
+               lc=tolower(lstr[pos]);
+               rc=tolower(rstr[pos]);
+               if ((lc==0)&&(rc==0)) return(0);
+               if (lc<rc) return(-1);
+               if (lc>rc) return(1);
+               pos=pos+1;
+               }
+       return(0);
+       }
+
+
+/*
+ * strproc()  -  make a string 'nice'
+ */
+void strproc(char *string)
+{
+       int a;
+
+       if (strlen(string)==0) return;
+
+       /* Convert non-printable characters to blanks */
+       for (a=0; a<strlen(string); ++a) {
+               if (string[a]<32) string[a]=32;
+               if (string[a]>126) string[a]=32;
+               }
+
+       /* Remove leading and trailing blanks */
+       while( (string[0]<33) && (strlen(string)>0) )
+               strcpy(string,&string[1]);
+       while( (string[strlen(string)-1]<33) && (strlen(string)>0) )
+               string[strlen(string)-1]=0;
+
+       /* Remove double blanks */
+       for (a=0; a<strlen(string); ++a) {
+               if ((string[a]==32)&&(string[a+1]==32)) {
+                       strcpy(&string[a],&string[a+1]);
+                       a=0;
+                       }
+               }
+
+       /* remove characters which would interfere with the network */
+       for (a=0; a<strlen(string); ++a) {
+               while (string[a]=='!') strcpy(&string[a],&string[a+1]);
+               while (string[a]=='@') strcpy(&string[a],&string[a+1]);
+               while (string[a]=='_') strcpy(&string[a],&string[a+1]);
+               while (string[a]==',') strcpy(&string[a],&string[a+1]);
+               while (string[a]=='%') strcpy(&string[a],&string[a+1]);
+               while (string[a]=='|') strcpy(&string[a],&string[a+1]);
+               }
+
+       }
+
+
+
+/*
+ * num_parms()  -  discover number of parameters...
+ */
+int num_parms(char *source)
+{
+       int a;
+       int count = 1;
+
+       for (a=0; a<strlen(source); ++a) 
+               if (source[a]=='|') ++count;
+       return(count);
+       }
+
+/*
+ * extract()  -  extract a parameter from a series of "|" separated...
+ */
+void extract(char *dest, char *source, int parmnum)
+{
+       char buf[256];
+       int count = 0;
+       int n;
+
+       if (strlen(source)==0) {
+               strcpy(dest,"");
+               return;
+               }
+
+       n = num_parms(source);
+
+       if (parmnum >= n) {
+               strcpy(dest,"");
+               return;
+               }
+       strcpy(buf,source);
+       if ( (parmnum == 0) && (n == 1) ) {
+               strcpy(dest,buf);
+               for (n=0; n<strlen(dest); ++n)
+                       if (dest[n]=='|') dest[n] = 0;
+               return;
+               }
+
+       while (count++ < parmnum) do {
+               strcpy(buf,&buf[1]);
+               } while( (strlen(buf)>0) && (buf[0]!='|') );
+       if (buf[0]=='|') strcpy(buf,&buf[1]);
+       for (count = 0; count<strlen(buf); ++count)
+               if (buf[count] == '|') buf[count] = 0;
+       strcpy(dest,buf);
+       }
+
+/*
+ * extract_int()  -  extract an int parm w/o supplying a buffer
+ */
+int extract_int(char *source, int parmnum)
+{
+       char buf[256];
+       
+       extract(buf,source,parmnum);
+       return(atoi(buf));
+       }
+
+/*
+ * extract_long()  -  extract an long parm w/o supplying a buffer
+ */
+long extract_long(char *source, long int parmnum)
+{
+       char buf[256];
+       
+       extract(buf,source,parmnum);
+       return(atol(buf));
+       }
+
+
+
+/*
+ * get a line of text from a file
+ * ignores lines starting with #
+ */
+int getstring(FILE *fp, char *string)
+{
+       int a,c;
+       do {
+               strcpy(string,"");
+               a=0;
+               do {
+                       c=getc(fp);
+                       if (c<0) {
+                               string[a]=0;
+                               return(-1);
+                               }
+                       string[a++]=c;
+                       } while(c!=10);
+                       string[a-1]=0;
+               } while(string[0]=='#');
+       return(strlen(string));
+       }
+
+
+/*
+ * sort message pointers
+ */
+void sort_fullroom(struct fullroom *frptr)
+{
+       int a,b;
+       long hold;
+       for (a=MSGSPERRM-2; a>=0; --a) {
+               for (b=0; b<=a; ++b) {
+                       if ((frptr->FRnum[b]) > (frptr->FRnum[b+1])) {
+                               hold = frptr->FRnum[b];
+                               frptr->FRnum[b] = frptr->FRnum[b+1];
+                               frptr->FRnum[b+1] = hold;
+                               }
+                       }
+               }
+       }
+
+/*
+ * pattern2()  -  searches for patn within search string, returns pos 
+ */ 
+int pattern2(char *search, char *patn)
+{
+       int a;
+       for (a=0; a<strlen(search); ++a) {
+               if (!struncmp(&search[a],patn,strlen(patn))) return(a);
+               }
+       return(-1);
+       }
+
+
+/*
+ * mesg_locate()  -  locate a message or help file, case insensitive
+ */
+void mesg_locate(char *targ, char *searchfor, int numdirs, char **dirs)
+{
+       int a;
+       char buf[256];
+       FILE *ls;
+
+       for (a=0; a<numdirs; ++a) {
+               sprintf(buf,"cd %s; exec ls",dirs[a]);
+               ls = (FILE *) popen(buf,"r");
+               if (ls != NULL) {
+                       while(fgets(buf,255,ls)!=NULL) {
+                               while (isspace(buf[strlen(buf)-1]))
+                                       buf[strlen(buf)-1] = 0;
+                               if (!strucmp(buf,searchfor)) {
+                                       pclose(ls);
+                                       sprintf(targ,"%s/%s",dirs[a],buf);
+                                       return;
+                                       }
+                               }
+                       pclose(ls);
+                       }
+               }
+       strcpy(targ,"");
+       }
+
+
+#ifdef NO_STRERROR
+/*
+ * replacement strerror() for systems that don't have it
+ */
+char *strerror(e)
+int e; {
+       static char buf[32];
+
+       sprintf(buf,"errno = %d",e);
+       return(buf);
+       }
+#endif
+
diff --git a/citadel/sysconfig.h b/citadel/sysconfig.h
new file mode 100644 (file)
index 0000000..49eb103
--- /dev/null
@@ -0,0 +1,69 @@
+/****************************************************************************/
+/*                  YOUR SYSTEM CONFIGURATION                               */
+/* Set all the values in this file appropriately BEFORE compiling any of the*/
+/* C programs. If you are upgrading from an older version of Citadel/UX, it */
+/* is vitally important that the #defines which are labelled "structure size*/
+/* variables" are EXACTLY the same as they were in your old system,         */
+/* otherwise your files will be munged beyond repair.                       */
+/****************************************************************************/
+
+/* NOTE THAT THIS FILE IS MUCH, MUCH SMALLER THAN IT USED TO BE.
+ * That's because the setup program now creates a citadel.config file with
+ * all of the settings that don't really need to be in a header file.
+ * You can now run setup whenever you want, and change lots of parameters
+ * without having to recompile the whole system!
+ */
+
+/*
+ * If you want to keep a transcript of all multiuser chats that go across
+ * your system, define CHATLOG to the filename to be saved to.  Otherwise,
+ * set CHATLOG to "/dev/null".
+ */
+#define CHATLOG                "./chat.log"
+
+/*
+ * SLEEPING refers to the watchdog timer.  If a user sits idle without typing
+ * anything for this number of seconds, the session will automatically be
+ * logged out.  Set it to zero to disable this feature.
+ * Note: the watchdog timer only functions when the parent is 1 (init) - in
+ * other words, only if Citadel is the login shell. 
+ */
+#define SLEEPING       180
+
+/* 
+ * S_KEEPALIVE is also a watchdog timer, except it is used to send "keep
+ * alive" messages to the server to prevent the server from assuming the
+ * client is dead and terminating the session.  30 seconds is the recommended
+ * value; I can't think of any good reason to change it.
+ */
+#define S_KEEPALIVE    30
+
+/*
+ * This is the command that gets executed when a user hits <E>nter message:
+ * presses the <E>nter message key.  The possible values are:
+ *   46 - .<E>nter message with <E>ditor
+ *   4  - .<E>nter <M>essage
+ *   36 - .<E>nter message with <A>scii
+ * Normally, this value will be set to 4, to cause the <E>nter message
+ * command to run Citadel's built-in editor.  However, if you have an external
+ * editor installed, and you want to make it the default, set this to 46
+ * to make it use your editor by default.
+ */
+#define DEFAULT_ENTRY  4
+
+
+/*** STRUCTURE SIZE VARIABLES ***/
+
+/* You may NOT change these values once you set up your system.            */
+#define MAXROOMS       128             /* Number of rooms in system        */
+#define MAXFLOORS      16              /* Do not set higher than 127       */
+#define MAILSLOTS      35              /* Number of mail slots per user    */
+#define MSGSPERRM      150             /* Messages per room                */
+#define CALLLOG                1000            /* Number of entries in call log    */
+/* Do not set MAILSLOTS higher than MSGSPERRM                              */
+
+/* These may be changed at any time. */
+#define MAXUCACHE      10              /* Entries in server user cache     */
+
+
+/*** END OF STRUCTURE SIZE VARIABLES ***/
diff --git a/citadel/sysdep.c b/citadel/sysdep.c
new file mode 100644 (file)
index 0000000..8680e22
--- /dev/null
@@ -0,0 +1,726 @@
+/*
+ * Citadel/UX "system dependent" stuff.
+ * See copyright.txt for copyright information.
+ *
+ * Here's where we (hopefully) have all the parts of the Citadel server that
+ * would need to be altered to run the server in a non-POSIX environment.
+ * Wherever possible, we use function wrappers and type definitions to create
+ * abstractions that are platform-independent from the calling side.
+ * 
+ * Eventually we'll try porting to a different platform and either have
+ * multiple variants of this file or simply load it up with #ifdefs.
+ */
+
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <string.h>
+#include <pwd.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <syslog.h>
+#include <pthread.h>
+#include "citadel.h"
+#include "server.h"
+#include "proto.h"
+
+#ifdef NEED_SELECT_H
+#include <sys/select.h>
+#endif
+
+extern struct CitContext *ContextList;
+extern struct config config;
+extern char bbs_home_directory[];
+extern int home_specified;
+
+pthread_mutex_t Critters[MAX_SEMAPHORES];      /* Things needing locking */
+pthread_key_t MyConKey;                                /* TSD key for MyContext() */
+
+int msock;                                     /* master listening socket */
+int verbosity = 3;                             /* Logging level */
+
+
+/*
+ * lprintf()  ...   Write logging information
+ */
+void lprintf(int loglevel, const char *format, ...) {   
+        va_list arg_ptr;   
+        char buf[256];   
+        int rc;   
+  
+       if (loglevel <= verbosity) { 
+               va_start(arg_ptr, format);   
+               rc = vsprintf(buf, format, arg_ptr);   
+               va_end(arg_ptr);   
+               
+               fprintf(stderr, "%s", buf);
+               fflush(stderr);
+               }
+  
+       }   
+
+
+/*
+ * Some initialization stuff...
+ */
+void init_sysdep(void) {
+       int a;
+
+       /* Set up a bunch of semaphores to be used for critical sections */
+       for (a=0; a<MAX_SEMAPHORES; ++a) {
+               pthread_mutex_init(&Critters[a], NULL);
+               }
+
+       /*
+        * Set up a place to put thread-specific data.
+        * We only need a single pointer per thread - it points to the
+        * thread's CitContext structure in the ContextList linked list.
+        */
+       if (pthread_key_create(&MyConKey, NULL) != 0) {
+               lprintf(1, "Can't create TSD key!!  %s\n", strerror(errno));
+               }
+
+       /*
+        * The action for unexpected signals and exceptions should be to
+        * call master_cleanup() to gracefully shut down the server.
+        */
+       signal(SIGINT, master_cleanup);
+       signal(SIGQUIT, master_cleanup);
+       signal(SIGHUP, master_cleanup);
+       signal(SIGTERM, master_cleanup);
+       }
+
+
+/*
+ * Obtain a semaphore lock to begin a critical section.
+ */
+void begin_critical_section(int which_one)
+{
+       int oldval;
+
+       lprintf(8, "begin_critical_section(%d)\n", which_one);
+       if (CC != NULL) hook_crit_get(CC->cs_pid, which_one);
+
+       /* Don't get interrupted during the critical section */
+       pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldval);
+
+       /* Obtain a semaphore */
+       pthread_mutex_lock(&Critters[which_one]);
+
+       if (CC != NULL) hook_crit_got(CC->cs_pid, which_one);
+       }
+
+/*
+ * Release a semaphore lock to end a critical section.
+ */
+void end_critical_section(int which_one)
+{
+       int oldval;
+
+       lprintf(8, "  end_critical_section(%d)\n", which_one);
+       if (CC != NULL) hook_crit_end(CC->cs_pid, which_one);
+
+       /* Let go of the semaphore */
+       pthread_mutex_unlock(&Critters[which_one]);
+
+       /* If a cancel was sent during the critical section, do it now.
+        * Then re-enable thread cancellation.
+        */
+       pthread_testcancel();
+       pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldval);
+       pthread_testcancel();
+
+       }
+
+
+
+/*
+ * This is a generic function to set up a master socket for listening on
+ * a TCP port.  The server shuts down if the bind fails.
+ */
+int ig_tcp_server(int port_number, int queue_len)
+{
+       struct sockaddr_in sin;
+       int s, i;
+
+       bzero((char *)&sin, sizeof(sin));
+       sin.sin_family = AF_INET;
+       sin.sin_addr.s_addr = INADDR_ANY;
+
+       if (port_number == 0) {
+               lprintf(1, "citserver: No port number specified.  Run setup again.\n");
+               exit(1);
+               }
+       
+       sin.sin_port = htons((u_short)port_number);
+
+       s = socket(PF_INET, SOCK_STREAM, (getprotobyname("tcp")->p_proto));
+       if (s < 0) {
+               lprintf(1, "citserver: Can't create a socket: %s\n",
+                       strerror(errno));
+               exit(errno);
+               }
+
+       /* Set the SO_REUSEADDR socket option, because it makes sense. */
+       i = 1;
+       setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
+
+       if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
+               lprintf(1, "citserver: Can't bind: %s\n", strerror(errno));
+               exit(errno);
+               }
+
+       if (listen(s, queue_len) < 0) {
+               lprintf(1, "citserver: Can't listen: %s\n", strerror(errno));
+               exit(errno);
+               }
+
+       return(s);
+       }
+
+
+/*
+ * Return a pointer to a thread's own CitContext structure (old)
+ * NOTE: this version of MyContext() is commented out because it is no longer
+ * in use.  It was written before I discovered TSD keys.  This
+ * version pounds through the context list until it finds the one matching
+ * the currently running thread.  It remains here, commented out, in case it
+ * is needed for future ports to threading libraries which have the equivalent
+ * of pthread_self() but not pthread_key_create() and its ilk.
+ *
+ * struct CitContext *MyContext() {
+ *     struct CitContext *ptr;
+ *     THREAD me;
+ *
+ *     me = pthread_self();
+ *     for (ptr=ContextList; ptr!=NULL; ptr=ptr->next) {
+ *             if (ptr->mythread == me) return(ptr);
+ *             }
+ *     return(NULL);
+ *     }
+ */
+
+/*
+ * Return a pointer to a thread's own CitContext structure (new)
+ */
+struct CitContext *MyContext(void) {
+       return (struct CitContext *) pthread_getspecific(MyConKey);
+       }
+
+
+/*
+ * Wedge our way into the context list.
+ */
+struct CitContext *CreateNewContext(void) {
+       struct CitContext *me;
+
+       lprintf(9, "CreateNewContext: calling malloc()\n");
+       me = (struct CitContext *) malloc(sizeof(struct CitContext));
+       if (me == NULL) {
+               lprintf(1, "citserver: can't allocate memory!!\n");
+               pthread_exit(NULL);
+               }
+
+       begin_critical_section(S_SESSION_TABLE);
+       me->next = ContextList;
+       ContextList = me;
+       end_critical_section(S_SESSION_TABLE);
+       return(me);
+       }
+
+/*
+ * Add a thread's thread ID to the context
+ */
+void InitMyContext(struct CitContext *con)
+{
+       int oldval;
+
+       con->mythread = pthread_self();
+       pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldval);
+       pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldval);
+       if (pthread_setspecific(MyConKey, (void *)con) != 0) {
+               lprintf(1, "ERROR!  pthread_setspecific() failed: %s\n",
+                       strerror(errno));
+               }
+       }
+
+/*
+ * Remove a context from the context list.
+ */
+void RemoveContext(struct CitContext *con)
+{
+       struct CitContext *ptr;
+
+       lprintf(7, "Starting RemoveContext()\n");
+       lprintf(9, "session count before RemoveContext is %d\n", session_count());
+       if (con==NULL) {
+               lprintf(7, "WARNING: RemoveContext() called with null!\n");
+               return;
+               }
+
+       begin_critical_section(S_SESSION_TABLE);
+       lprintf(7, "Closing socket %d\n", con->client_socket);
+       close(con->client_socket);
+
+       if (ContextList==con) {
+               ContextList = ContextList->next;
+               }
+       else {
+               for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
+                       if (ptr->next == con) {
+                               ptr->next = ptr->next->next;
+                               }
+                       }
+               }
+       
+       free(con);
+
+       lprintf(9, "session count after RemoveContext is %d\n", session_count());
+
+       lprintf(7, "Done with RemoveContext\n");
+       end_critical_section(S_SESSION_TABLE);
+       }
+
+
+/*
+ * Return the number of sessions currently running.
+ * (This should probably be moved out of sysdep.c)
+ */
+int session_count(void) {
+       struct CitContext *ptr;
+       int TheCount = 0;
+
+       lprintf(9, "session_count() starting\n");
+       for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
+               ++TheCount;
+               lprintf(9, "Counted session %3d (%d)\n", ptr->cs_pid, TheCount);
+               }
+
+       lprintf(9, "session_count() finishing\n");
+       return(TheCount);
+       }
+
+
+/*
+ * client_write()   ...    Send binary data to the client.
+ */
+void client_write(char *buf, int nbytes)
+{
+       int bytes_written = 0;
+       int retval;
+       while (bytes_written < nbytes) {
+               retval = write(CC->client_socket, &buf[bytes_written],
+                       nbytes - bytes_written);
+               if (retval < 1) {
+                       lprintf(2, "client_write() failed: %s\n",
+                               strerror(errno));
+                       cleanup(errno);
+                       }
+               bytes_written = bytes_written + retval;
+               }
+       }
+
+
+/*
+ * cprintf()  ...   Send formatted printable data to the client.   It is
+ *                  implemented in terms of client_write() but remains in
+ *                  sysdep.c in case we port to somewhere without va_args...
+ */
+void cprintf(const char *format, ...) {   
+        va_list arg_ptr;   
+        char buf[256];   
+        int rc;   
+   
+        va_start(arg_ptr, format);   
+        rc = vsprintf(buf, format, arg_ptr);   
+        va_end(arg_ptr);   
+  
+       client_write(buf, strlen(buf)); 
+       }   
+
+
+/*
+ * Read data from the client socket.
+ * Return values are:
+ *     1       Requested number of bytes has been read.
+ *     0       Request timed out.
+ * If the socket breaks, the session is immediately terminated.
+ */
+int client_read_to(char *buf, int bytes, int timeout)
+{
+       int len,rlen;
+       fd_set rfds;
+       struct timeval tv;
+       int retval;
+
+       len = 0;
+       while(len<bytes) {
+               FD_ZERO(&rfds);
+               FD_SET(CC->client_socket, &rfds);
+               tv.tv_sec = timeout;
+               tv.tv_usec = 0;
+
+               retval = select( (CC->client_socket)+1, 
+                                       &rfds, NULL, NULL, &tv);
+               if (FD_ISSET(CC->client_socket, &rfds) == 0) {
+                       return(0);
+                       }
+
+               rlen = read(CC->client_socket, &buf[len], bytes-len);
+               if (rlen<1) {
+                       lprintf(2, "client_read() failed: %s\n",
+                               strerror(errno));
+                       cleanup(errno);
+                       }
+               len = len + rlen;
+               }
+       return(1);
+       }
+
+/*
+ * Read data from the client socket with default timeout.
+ * (This is implemented in terms of client_read_to() and could be
+ * justifiably moved out of sysdep.c)
+ */
+int client_read(char *buf, int bytes)
+{
+       return(client_read_to(buf, bytes, config.c_sleeping));
+       }
+
+
+/*
+ * client_gets()   ...   Get a LF-terminated line of text from the client.
+ * (This is implemented in terms of client_read() and could be
+ * justifiably moved out of sysdep.c)
+ */
+int client_gets(char *buf)
+{
+       int retval = 0;
+
+       /* Clear the buffer, and read one character at a time.
+        */
+       buf[0] = 0;
+       do {
+               if (strlen(buf)<255) {
+                       buf[strlen(buf) + 1] = 0;
+                       retval = client_read(&buf[strlen(buf)], 1);
+                       }
+               } while ( (buf[strlen(buf)-1] != 10) && (retval==1) );
+
+       /* Strip the trailing newline.
+        */
+       if (strlen(buf) > 0) buf[strlen(buf)-1] = 0;
+       return(retval);
+       }
+
+
+
+/*
+ * The system-dependent part of master_cleanup() - close the master socket.
+ */
+void sysdep_master_cleanup(void) {
+       lprintf(3, "Closing master socket %d\n", msock);
+       close(msock);
+       lprintf(7, "Closing databases\n");
+       close_databases();
+       }
+
+/*
+ * Cleanup routine to be called when one thread is shutting down.
+ */
+void cleanup(int exit_code)
+{
+       /* Terminate the thread.
+        * Its cleanup handler will call cleanup_stuff()
+        */
+       lprintf(7, "Calling pthread_exit()\n");
+       pthread_exit(NULL);
+       }
+
+/*
+ * Terminate another session.
+ */
+void kill_session(int session_to_kill) {
+       struct CitContext *ptr;
+
+       for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
+               if (ptr->cs_pid == session_to_kill) {
+                       pthread_cancel(ptr->mythread);
+                       }
+               }
+       }
+
+
+/*
+ * The system-dependent wrapper around the main context loop.
+ */
+void sd_context_loop(struct CitContext *con) {
+       pthread_cleanup_push(*cleanup_stuff, NULL);
+       context_loop(con);
+       pthread_cleanup_pop(0);
+       }
+
+
+/*
+ * Start running as a daemon.  Only close stdio if do_close_stdio is set.
+ */
+void start_daemon(int do_close_stdio) {
+       if (do_close_stdio) {
+               /* close(0); */
+               close(1);
+               close(2);
+               }
+       signal(SIGHUP,SIG_IGN);
+       signal(SIGINT,SIG_IGN);
+       signal(SIGQUIT,SIG_IGN);
+       if (fork()!=0) exit(0);
+       }
+
+
+
+/*
+ * Tie in to the 'netsetup' program.
+ *
+ * (We're going to hope that netsetup never feeds more than 4096 bytes back.)
+ */
+void cmd_nset(char *cmdbuf)
+{
+       int retcode;
+       char fbuf[4096];
+       FILE *netsetup;
+       int ch;
+       int a, b;
+       char netsetup_args[3][256];
+
+       if (CC->usersupp.axlevel < 6) {
+               cprintf("%d Higher access required.\n", 
+                       ERROR + HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       for (a=1; a<=3; ++a) {
+               if (num_parms(cmdbuf) >= a) {
+                       extract(netsetup_args[a-1], cmdbuf, a-1);
+                       for (b=0; b<strlen(netsetup_args[a-1]); ++b) {
+                               if (netsetup_args[a-1][b] == 34) {
+                                       netsetup_args[a-1][b] = '_';
+                                       }
+                               }
+                       }
+               else {
+                       netsetup_args[a-1][0] = 0;
+                       }
+               }
+
+       sprintf(fbuf, "./netsetup \"%s\" \"%s\" \"%s\" </dev/null 2>&1",
+               netsetup_args[0], netsetup_args[1], netsetup_args[2]);
+       netsetup = popen(fbuf, "r");
+       if (netsetup == NULL) {
+               cprintf("%d %s\n", ERROR, strerror(errno));
+               return;
+               }
+
+       fbuf[0] = 0;
+       while (ch = getc(netsetup), (ch > 0)) {
+               fbuf[strlen(fbuf)+1] = 0;
+               fbuf[strlen(fbuf)] = ch;
+               }
+
+       retcode = pclose(netsetup);
+
+       if (retcode != 0) {
+               for (a=0; a<strlen(fbuf); ++a) {
+                       if (fbuf[a] < 32) fbuf[a] = 32;
+                       }
+               fbuf[245] = 0;
+               cprintf("%d %s\n", ERROR, fbuf);
+               return;
+               }
+
+       cprintf("%d Command succeeded.  Output follows:\n", LISTING_FOLLOWS);
+       cprintf("%s", fbuf);
+       if (fbuf[strlen(fbuf)-1] != 10) cprintf("\n");
+       cprintf("000\n");
+       }
+
+
+
+/*
+ * Generic routine to convert a login name to a full name (gecos)
+ * Returns nonzero if a conversion took place
+ */
+int convert_login(char NameToConvert[]) {
+       struct passwd *pw;
+       int a;
+
+       pw = getpwnam(NameToConvert);
+       if (pw == NULL) {
+               return(0);
+               }
+       else {
+               strcpy(NameToConvert, pw->pw_gecos);
+               for (a=0; a<strlen(NameToConvert); ++a) {
+                       if (NameToConvert[a] == ',') NameToConvert[a] = 0;
+                       }
+               return(1);
+               }
+       }
+
+
+
+
+       
+
+/*
+ * Here's where it all begins.
+ */
+int main(int argc, char **argv)
+{
+       struct sockaddr_in fsin;        /* Data for master socket */
+       int alen;                       /* Data for master socket */
+       int ssock;                      /* Descriptor for master socket */
+       THREAD SessThread;              /* Thread descriptor */
+        pthread_attr_t attr;           /* Thread attributes */
+       struct CitContext *con;         /* Temporary context pointer */
+       char tracefile[128];            /* Name of file to log traces to */
+       int a, i;                       /* General-purpose variables */
+       char convbuf[128];
+
+       /* specify default port name and trace file */
+       strcpy(tracefile, "");
+
+       /* parse command-line arguments */
+       for (a=1; a<argc; ++a) {
+
+               /* -t specifies where to log trace messages to */
+               if (!strncmp(argv[a], "-t", 2)) {
+                       strcpy(tracefile, argv[a]);
+                       strcpy(tracefile, &tracefile[2]);
+                       freopen(tracefile, "r", stdin);
+                       freopen(tracefile, "w", stdout);
+                       freopen(tracefile, "w", stderr);
+                       }
+
+               /* run in the background if -d was specified */
+               else if (!strcmp(argv[a], "-d")) {
+                       start_daemon( (strlen(tracefile) > 0) ? 0 : 1 ) ;
+                       }
+
+               /* -x specifies the desired logging level */
+               else if (!strncmp(argv[a], "-x", 2)) {
+                       strcpy(convbuf, argv[a]);
+                       verbosity = atoi(&convbuf[2]);
+                       }
+
+               else if (!strncmp(argv[a], "-h", 2)) {
+                       strcpy(convbuf, argv[a]);
+                       strcpy(bbs_home_directory, &convbuf[2]);
+                       home_specified = 1;
+                       }
+
+               /* any other parameter makes it crash and burn */
+               else {
+                       lprintf(1, "citserver: usage: ");
+                       lprintf(1, "citserver [-tTraceFile]");
+                       lprintf(1, " [-d] [-xLogLevel] [-hHomeDir]\n");
+                       exit(1);
+                       }
+
+               }
+
+       /* Tell 'em who's in da house */
+       lprintf(1, "Multithreaded message server for %s\n", CITADEL);
+       lprintf(1, "Copyright (C) 1987-1998 by Art Cancro.  ");
+       lprintf(1, "All rights reserved.\n\n");
+
+       /* Initialize... */
+       init_sysdep();
+       openlog("citserver",LOG_PID,LOG_USER);
+
+       /* Load site-specific parameters */
+       lprintf(7, "Loading citadel.config\n");
+       get_config();
+       hook_init();
+
+       /* Databases must be opened *after* config is loaded, otherwise we might
+        * end up working in the wrong directory.
+        */
+       lprintf(7, "Opening databases\n");
+       open_databases();
+
+       /*
+        * Bind the server to our favourite port.
+        * There is no need to check for errors, because ig_tcp_server()
+        * exits if it doesn't succeed.
+        */
+       lprintf(7, "Attempting to bind to port %d...\n", config.c_port_number);
+       msock = ig_tcp_server(config.c_port_number, 5);
+       lprintf(7, "Listening on socket %d\n", msock);
+
+       /*
+        * Now that we've bound the socket, change to the BBS user id
+       lprintf(7, "Changing uid to %d\n", BBSUID);
+       if (setuid(BBSUID) != 0) {
+               lprintf(3, "setuid() failed: %s", strerror(errno));
+               }
+        */
+
+       /* 
+        * Endless loop.  Listen on the master socket.  When a connection
+        * comes in, create a socket, a context, and a thread.
+        */     
+       while (1) {
+               ssock = accept(msock, (struct sockaddr *)&fsin, &alen);
+               if (ssock < 0) {
+                       lprintf(2, "citserver: accept() failed: %s\n",
+                               strerror(errno));
+                       }
+               else {
+                       lprintf(7, "citserver: Client socket %d\n", ssock);
+                       lprintf(9, "creating context\n");
+                       con = CreateNewContext();
+                       con->client_socket = ssock;
+
+                       /* Set the SO_REUSEADDR socket option */
+                       lprintf(9, "setting socket options\n");
+                       i = 1;
+                       setsockopt(ssock, SOL_SOCKET, SO_REUSEADDR,
+                               &i, sizeof(i));
+
+                       /* set attributes for the new thread */
+                       lprintf(9, "setting thread attributes\n");
+                       pthread_attr_init(&attr);
+                       pthread_attr_setdetachstate(&attr,
+                               PTHREAD_CREATE_DETACHED);
+
+                       /* now create the thread */
+                       lprintf(9, "creating thread\n");
+                       if (pthread_create(&SessThread, &attr, (void *)sd_context_loop,
+                          con) != 0) {
+                               lprintf(1,
+                                       "citserver: can't create thread: %s\n",
+                                       strerror(errno));
+                               }
+
+                       /* detach the thread 
+                        * (defunct -- now done at thread creation time)
+                        * if (pthread_detach(&SessThread) != 0) {
+                        *      lprintf(1,
+                        *              "citserver: can't detach thread: %s\n",
+                        *              strerror(errno));
+                        *      }
+                        */
+                       lprintf(9, "done!\n");
+                       }
+               }
+       }
+
diff --git a/citadel/sysop.txt b/citadel/sysop.txt
new file mode 100644 (file)
index 0000000..d8f295a
--- /dev/null
@@ -0,0 +1,265 @@
+                        Citadel/UX Sysop/Aide Manual
+                 See copyright.doc for copyright information
+  
+   
+ OVERVIEW
+   Citadel/UX, when installed properly, will do most of its maintenance by
+itself. The message file loops upon itself forever, scrolling off old messages
+to make space for new ones. The room files work in the same way. Other types
+of maintenance can be done by cron. I have left my system unattended for long
+periods of time without any software failures. 
+   
+  The system has seven access levels. Most users are at the bottom and have no
+special privileges. Aides are selected people who have special access within
+the Citadel program. Room Aides only have this access in a certain room. 
+Preferred users can be selected by Aides for access to preferred only rooms. A
+sysop is anyone who has access to the various sysop utilities - these are in
+their own executable files, which should have their permissions set to allow
+only sysops to run them. I recommend either creating a sysops group in
+/etc/group, or using some other existing group for this purpose. 
+   
+   Aides have access to EVERY room on the system, public and private (all
+types). They also have access to commands starting with .<A>ide in addition
+to being able to delete and move messages. The system room, Aide>, is
+accessible only by those designated by aides.
+   
+   
+ AIDE COMMANDS
+   Aides have the following commands available to them that are not available
+to normal users. They are:
+  
+ .<A>ide <E>dit room         Allows an aide to change certain parameters of
+                             the current room. Lobby>, Mail>, and Aide> may
+                             not be edited.
+ .<A>ide <F>ile <D>elete     If the current room has a directory, an Aide or
+                             room Aide can delete files from the directory
+                             using this command.
+ .<A>ide <F>ile <M>ove       Moves a file from the directory of the current
+                             room to the directory of another room.  If there
+                             is a file description attached, it is moved also.
+ .<A>ide <F>ile <S>end...    This will send a copy of a file in the current
+                             room's directory to the directory of the same
+                             room on another system on the network.  The other
+                             system must be running Citadel/UX or another
+                             program supporting IGnet/Open file transfers.
+ .<A>ide edit <I>nfo file    Creates an info file for the current room, which
+                             will be displayed to the user when any of three
+                             conditions exist: the first time the user enters
+                             the room, the next time the user enters the room
+                             after the file has been changed, and when the
+                             .<R>ead <I>nfo file command is entered.
+ .<A>ide <K>ill room         Deletes the current room. Lobby>, Mail>, and
+                             Aide> may not be deleted.
+ .<A>ide <R>oom <I>nvite     Invites a user to the room if it is private.
+ .<A>ide <R>oom <K>ickOut    Kicks a user out of the room if it is private.
+ .<A>ide <U>serEdit          Edits certain parameters of a user's account.
+ .<A>ide <V>alidate newusers Lists users who have recently registered and
+                             prompts for new access levels.
+ .<A>ide <W>hoKnowsRoom      Lists all users who have access to, and who have
+                             not chosen to zap, the current room.
+   
+   
+   EDITING ROOMS
+   
+   This command allows any aide to change the parameters of a room.  Go to
+the room you wish to edit and enter the .AE command.  A series of prompts will
+be displayed.  The existing parameters will be displayed in brackets; simply
+press return if you want to leave any or all of them unchanged.
+Room name [IG's Fun Room]:
+   ...the name of the room.
+
+Private room [Yes]? 
+   ...enter Yes if you wish to restrict access to the room, or no if the room
+is to be accessible by all users.  Note that Citadel doesn't bother users
+about access to rooms every time they need to access the room.  Once a user
+gains access to a private room, it then behaves like a public room to them.
+The following four questions will only be asked if you selected Private...
+
+Accessible by guessing room name [No]?
+   ...if you enter Yes, the room will not show up in users' <K>nown rooms
+listing, but if they .<G>oto the room (typing the room's full name), they
+will gain access to the room.
+
+Accessible by entering a password [No]?
+Room password [mypasswd]:  
+   ...this adds an additional layer of security to the room, prompting users
+for a password before they can gain access to the room.
+If you did not select guessname or passworded, then the only way users can
+access the room is if an Aide explicitly invites them to the room using the
+.<A>ide <R>oom <I>nvite user command.
+
+Cause current users to forget room [No] ? No
+   Enter Yes if you wish to kick out anyone who currently has access to
+the room.
+Preferred users only [No]? No
+   Enter Yes if you wish to restrict the room to only users who have level 5
+(Preferred User) status (and Aides too, of course).  You should make the room
+public if you intend to do this, otherwise the two restrictions will be
+COMBINED.
+Read-only room [No]? No
+   If you set a room to Read-Only, then normal users will not be allowed to
+post messages in it.  Messages may only be posted by Aides, and by utility
+programs such as the networker and the "aidepost" utility.  This is useful
+in situations where a room is used exclusively for important announcements,
+or if you've set up a room to receive an Internet mailing list and posting
+wouldn't make sense.  Other uses will, of course, become apparent as the
+need arises.
+
+Now for a few other attributes...
+
+Directory room [Yes]? Yes
+   ...enter Yes if you wish to associate a directory with this room.  If you
+enter Yes, you will also be prompted with the following four questions:
+
+Directory name [mydirname]: 
+   ...the name of the subdirectory to put this room's files in.  The name of
+the directory created will be <your BBS directory>/files/<room dir name>.
+    
+Uploading allowed [Yes]? Yes
+   ...enter Yes if users are allowed to upload to this room.
+
+Downloading allowed [Yes]? Yes
+  
+   ...enter Yes if users are allowed to download from this room.
+
+Visible directory [Yes]? Yes
+  
+   ...enter Yes if users can read the directory of this room.
+
+
+Network shared room [No]? No
+  
+   ...you can share a room over a network without setting this flag, and
+vice versa, but what this flag does is twofold:
+   1. It prevents people with no network access from entering messages here
+   2. Messages are displayed with the name of their originating system in
+      the header.
+
+Permanent room [No]? No
+   ...the sysop utilities have an option to purge rooms which have not been posted
+in for two weeks.  If you wish to keep this from happening to a particular room, you
+can set this option.  (Keep in mind that Lobby>, Mail>, Aide>, any network rooms, and
+any directory rooms are automatically permanent.)
+
+
+Anonymous messages [No]? No
+Ask users whether to make messages anonymous [No]? No
+  
+   ...you can have rooms in which all messages are automatically anonymous,
+and you can have rooms in which users are prompted whether to make a message
+anonymous when they enter it.
+
+Room aide [Joe Responsible]: 
+   ...on larger systems, it helps to designate a person to be responsible for
+a room.  Room Aides have access to a restricted set of Aide commands, ONLY
+when they are in the room in which they have this privilege.  They can edit
+the room, delete the room, delete and move messages, and invite or kick out
+users (if it is a private room), but they cannot perform aide commands that
+are not room-related (such as changing users access levels).
+
+Save changes (y/n)? Yes
+  
+    ...this gives you an opportunity to back out, if you feel you really
+messed things up while editing.
+
+   FILE DIRECTORIES 
+ If you have created any directory rooms, you can attach file descriptions to
+the filenames through a special file called "filedir".  Each line contains
+the name of a file in the directory, followed by a space and then a description
+of the file, such as:
+ myfile.txt This is a description of my file.
+ phluff A phile phull of phluff!
+ ...this would create file descriptions for the files 'myfile.txt' and 'phluff'
+which would be displayed along with the directory.  It should also be noted
+that when users upload files to your system, they will be prompted for file
+descriptions, which will be added to the 'filedir' file.  If one does not
+exist, it will be created.
+   EDITING USERS
+   This command allows any aide to change certain parameters of any user's
+account. Entering this command will ask for the name of a user to edit, and
+then prompt with the user's current access level, then ask for the new one.
+  0 - Marked for deletion
+  1 - New unvalidated user
+  2 - Problem user
+  3 - User with no network privileges
+  4 - Normal user
+  5 - Preferred user
+  6 - Aide
+   
+   DELETING AND MOVING MESSAGES
+   
+   Aides have the ability to delete and move messages; however, they must have
+message prompting turned on in order to do this. After each message, the
+normal prompt appears:
+   <A>gain, <N>ext message, <S>top ->
+  Entering <D> will delete the message. A (y/n) prompt will appear to confirm
+that you really want to delete the message.
+  Entering <M> will prompt for a room to move the message to.
+     
+   
+   SYSOP UTILITIES
+   
+   There are a number of utilities which may be accessed from the shell. It
+is up to the system operator to decide which should be "sysop" utilities and
+which should be accessible to all shell users. Please see utils.doc for a
+description of these programs.
+    
+   
+   CUSTOMIZING THE HELP FILES
+   The subdirectory called "help" contains your system's help files.  There's
+nothing hard-coded into the system that dictates what files should be there.
+Whenever a user types the command "<.H>elp" followed by the name of a help
+file, it displays the contents of that help file.
+   The help files that come with the system, of course, are enough to guide
+a user through its operation.  But you can add, change, or remove help files
+to suit whatever is appropriate for your system. 
+   Now for the fun part.  There are several strings that you can put in help
+files that will be automatically substituted with other strings.  They are:
+ ^nodename    = The node name of your system on a Citadel/UX network
+ ^humannode   = Human-readable node name (also your node name on C86Net)
+ ^fqdn        = Your system's fully-qualified domain name
+ ^username    = The name of the user reading the help file
+ ^usernum     = The user number of the user reading the help file
+ ^sysadm      = The name of the system administraor (i.e., you)
+ ^variantname = The name of the BBS software you're running
+   So, for example, you could create a help file which looked like:
+  "Lots of help, of course, is available right here on ^humannode.  Of
+course, if you still have trouble, you could always bug ^sysadm about it!"
+   
+        
+   CONCLUSION
+  
+   Comments from the Peanut Gallery should be directed to (at) my email
+address ajc@uncnsrd.mt-kisco.ny.us, or call UNCENSORED! BBS at 914-244-3252
+(modem) or uncnsrd.mt-kisco.ny.us (Internet).
diff --git a/citadel/user_ops.c b/citadel/user_ops.c
new file mode 100644 (file)
index 0000000..9a71c37
--- /dev/null
@@ -0,0 +1,1067 @@
+#define _XOPEN_SOURCE  /* needed to properly enable crypt() stuff on some systems */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <string.h>
+#include <syslog.h>
+#include <pthread.h>
+#include "citadel.h"
+#include "server.h"
+#include "proto.h"
+
+extern struct config config;
+
+
+/*
+ * pwcrypt()  -  simple password encryption
+ */
+void pwcrypt(char *text, int code)
+{
+       int a;
+       for (a=0; a<strlen(text); ++a) text[a]=(text[a]^(((code|128)^a)&0xFF));
+       }
+
+
+/*
+ * hash()  -  hash table function for user lookup
+ */
+int hash(char *str)
+{
+       int h = 0;
+       int i;
+
+       for (i=0; i<strlen(str); ++i) h=h+((i+1)*tolower(str[i]));
+       return(h);
+       }
+
+
+/*
+ * getuser()  -  retrieve named user into supplied buffer.
+ *               returns 0 on success
+ */
+int getuser(struct usersupp *usbuf, char name[]) {
+
+       char lowercase_name[32];
+       int a;
+       struct cdbdata *cdbus;
+
+       bzero(usbuf, sizeof(struct usersupp));
+       for (a=0; a<=strlen(name); ++a) {
+               lowercase_name[a] = tolower(name[a]);
+               }
+
+       cdbus = cdb_fetch(CDB_USERSUPP, lowercase_name, strlen(lowercase_name));
+       if (cdbus == NULL) {    /* not found */
+               return(1);
+               }
+
+       memcpy(usbuf, cdbus->ptr, cdbus->len);
+       cdb_free(cdbus);
+       return(0);
+       }
+
+
+/*
+ * lgetuser()  -  same as getuser() but locks the record
+ */
+int lgetuser(struct usersupp *usbuf, char *name)
+{
+       int retcode;
+
+       retcode = getuser(usbuf,name);
+       if (retcode == 0) {
+               begin_critical_section(S_USERSUPP);
+               }
+       return(retcode);
+       }
+
+
+/*
+ * putuser()  -  write user buffer into the correct place on disk
+ */
+void putuser(struct usersupp *usbuf, char *name)
+{
+       char lowercase_name[32];
+       int a;
+
+       for (a=0; a<=strlen(name); ++a) {
+               lowercase_name[a] = tolower(name[a]);
+               }
+
+       cdb_store(CDB_USERSUPP, lowercase_name, strlen(lowercase_name),
+               usbuf, sizeof(struct usersupp));
+
+       }
+
+
+/*
+ * lputuser()  -  same as putuser() but locks the record
+ */
+void lputuser(struct usersupp *usbuf, char *name)
+{
+       putuser(usbuf,name);
+       end_critical_section(S_USERSUPP);
+       }
+
+
+/*
+ * Is the user currently logged in an Aide?
+ */
+int is_aide(void) {
+       if (CC->usersupp.axlevel >= 6) return(1);
+       else return(0);
+       }
+
+
+/*
+ * Is the user currently logged in an Aide *or* the room aide for this room?
+ */
+int is_room_aide(void) {
+       if ( (CC->usersupp.axlevel >= 6)
+               || (CC->quickroom.QRroomaide == CC->usersupp.usernum) ) return(1);
+       else return(0);
+       }
+
+/*
+ * getuserbynumber()  -  get user by number
+ *                      returns 0 if user was found
+ */
+int getuserbynumber(struct usersupp *usbuf, long int number)
+{
+       struct cdbdata *cdbus;
+
+       cdb_rewind(CDB_USERSUPP);
+
+       while(cdbus = cdb_next_item(CDB_USERSUPP), cdbus != NULL) {
+               bzero(usbuf, sizeof(struct usersupp));
+               memcpy(usbuf, cdbus->ptr, cdbus->len);
+               cdb_free(cdbus);
+               if (usbuf->usernum == number) {
+                       return(0);
+                       }
+               }
+       return(-1);
+       }
+
+
+/*
+ * USER cmd
+ */
+void cmd_user(char *cmdbuf)
+{
+       char username[256];
+       char autoname[256];
+       int found_user = 0;
+       struct passwd *p;
+       int a;
+
+       extract(username,cmdbuf,0);
+       username[25] = 0;
+       strproc(username);
+
+       if ((CC->logged_in)) {
+               cprintf("%d Already logged in.\n",ERROR);
+               return;
+               }
+
+       found_user = getuser(&CC->usersupp,username);
+       if (found_user != 0) {
+               p = (struct passwd *)getpwnam(username);
+               if (p!=NULL) {
+                       strcpy(autoname,p->pw_gecos);
+                       for (a=0; a<strlen(autoname); ++a)
+                               if (autoname[a]==',') autoname[a]=0;
+                       found_user = getuser(&CC->usersupp,autoname);
+                       }
+               }
+       if (found_user == 0) {
+               if (((CC->nologin)) && (CC->usersupp.axlevel < 6)) {
+                       cprintf("%d %s: Too many users are already online (maximum is %d)\n",
+                       ERROR+MAX_SESSIONS_EXCEEDED,
+                       config.c_nodename,config.c_maxsessions);
+                       }
+               else {
+                       strcpy(CC->curr_user,CC->usersupp.fullname);
+                       cprintf("%d Password required for %s\n",
+                               MORE_DATA,CC->curr_user);
+                       }
+               }
+       else {
+               cprintf("%d %s not found.\n",ERROR,username);
+               }
+       }
+
+
+
+/*
+ * session startup code which is common to both cmd_pass() and cmd_newu()
+ */
+void session_startup(void) {
+       int a;
+       struct quickroom qr;
+
+       syslog(LOG_NOTICE,"user <%s> logged in",CC->curr_user);
+       hook_user_login(CC->cs_pid, CC->curr_user);
+       lgetuser(&CC->usersupp,CC->curr_user);
+       ++(CC->usersupp.timescalled);
+       /* <bc> */
+       CC->fake_username[0] = '\0';
+       CC->fake_postname[0] = '\0';
+       CC->fake_hostname[0] = '\0';
+       CC->fake_roomname[0] = '\0';
+       CC->last_pager[0] = '\0';
+       /* <bc> */
+       time(&CC->usersupp.lastcall);
+
+       /* If this user's name is the name of the system administrator
+        * (as specified in setup), automatically assign access level 6.
+        */
+       if (!strucmp(CC->usersupp.fullname, config.c_sysadm)) {
+               CC->usersupp.axlevel = 6;
+               }
+
+/* A room's generation number changes each time it is recycled. Users are kept
+ * out of private rooms or forget rooms by matching the generation numbers. To
+ * avoid an accidental matchup, unmatched numbers are set to -1 here.
+ */
+       for (a=0; a<MAXROOMS; ++a) {
+               getroom(&qr,a);
+               if (CC->usersupp.generation[a] != qr.QRgen)
+                                       CC->usersupp.generation[a]=(-1);
+               if (CC->usersupp.forget[a] != qr.QRgen)
+                                       CC->usersupp.forget[a]=(-1);
+               }
+
+       lputuser(&CC->usersupp,CC->curr_user);
+
+       cprintf("%d %s|%d|%d|%d|%u|%ld\n",OK,CC->usersupp.fullname,CC->usersupp.axlevel,
+               CC->usersupp.timescalled,CC->usersupp.posted,CC->usersupp.flags,
+               CC->usersupp.usernum);
+       usergoto(0,0);          /* Enter the lobby */   
+       rec_log(CL_LOGIN,CC->curr_user);
+       }
+
+
+
+/* 
+ * misc things to be taken care of when a user is logged out
+ */
+void logout(struct CitContext *who)
+{
+       who->logged_in = 0;
+       if (who->download_fp != NULL) {
+               fclose(who->download_fp);
+               who->download_fp = NULL;
+               }
+       if (who->upload_fp != NULL) {
+               abort_upl(who);
+               }
+       }
+
+
+void cmd_pass(char *buf)
+{
+       char password[256];
+       int code;
+       struct passwd *p;
+
+       extract(password,buf,0);
+
+       if ((CC->logged_in)) {
+               cprintf("%d Already logged in.\n",ERROR);
+               return;
+               }
+       if (!strcmp(CC->curr_user,"")) {
+               cprintf("%d You must send a name with USER first.\n",ERROR);
+               return;
+               }
+       if (getuser(&CC->usersupp,CC->curr_user)) {
+               cprintf("%d Can't find user record!\n",ERROR+INTERNAL_ERROR);
+               return;
+               }
+
+       code = (-1);
+       if (CC->usersupp.USuid == BBSUID) {
+               strproc(password);
+               pwcrypt(CC->usersupp.password,config.c_pwcrypt);
+               strproc(CC->usersupp.password);
+               code = strucmp(CC->usersupp.password,password);
+               pwcrypt(CC->usersupp.password,config.c_pwcrypt);
+               }
+       else {
+               p = (struct passwd *)getpwuid(CC->usersupp.USuid);
+#ifdef ENABLE_AUTOLOGIN
+               if (p!=NULL) {
+                       if (!strcmp(p->pw_passwd,
+                          (char *)crypt(password,p->pw_passwd))) {
+                               code = 0;
+                               lgetuser(&CC->usersupp, CC->curr_user);
+                               strcpy(CC->usersupp.password, password);
+                               pwcrypt(CC->usersupp.password, config.c_pwcrypt);
+                               lputuser(&CC->usersupp, CC->curr_user);
+                               }
+                       }
+#endif
+               }
+
+       if (!code) {
+               (CC->logged_in) = 1;
+               session_startup();
+               }
+       else {
+               cprintf("%d Wrong password.\n",ERROR);
+               rec_log(CL_BADPW,CC->curr_user);
+               }
+       }
+
+
+/*
+ * purge related files when removing or overwriting a user record
+ */
+void purge_user(pnum)
+long pnum; {
+       char filename[64];
+
+       /* remove the user's bio file */        
+       sprintf(filename, "./bio/%ld", pnum);
+       unlink(filename);
+
+       /* remove the user's picture */
+       sprintf(filename, "./userpics/%ld.gif", pnum);
+       unlink(filename);
+       
+       }
+
+
+/*
+ * create_user()  -  back end processing to create a new user
+ */
+int create_user(char *newusername)
+{
+       struct usersupp usbuf;
+       int a,file;
+       long aa;
+       struct passwd *p = NULL;
+       char username[64];
+
+       strcpy(username, newusername);
+       strproc(username);
+
+#ifdef ENABLE_AUTOLOGIN
+       p = (struct passwd *)getpwnam(username);
+#endif
+       if (p != NULL) {
+               strcpy(username, p->pw_gecos);
+               for (a=0; a<strlen(username); ++a) {
+                       if (username[a] == ',') username[a] = 0;
+                       }
+               CC->usersupp.USuid = p->pw_uid;
+               }
+       else {
+               CC->usersupp.USuid = BBSUID;
+               }
+
+       if (!getuser(&usbuf,username)) {
+               return(ERROR+ALREADY_EXISTS);
+               }
+
+       strcpy(CC->curr_user,username);
+       strcpy(CC->usersupp.fullname,username);
+       (CC->logged_in) = 1;
+
+       for (a=0; a<MAXROOMS; ++a) {
+               CC->usersupp.lastseen[a]=0L;
+               CC->usersupp.generation[a]=(-1);
+               CC->usersupp.forget[a]=(-1);
+               }
+       for (a=0; a<MAILSLOTS; ++a) {
+               CC->usersupp.mailnum[a]=0L;
+               }
+       strcpy(CC->usersupp.password,"");
+
+       /* These are the default flags on new accounts */
+       CC->usersupp.flags =
+               US_NEEDVALID|US_LASTOLD|US_DISAPPEAR|US_PAGINATOR|US_FLOORS;
+
+       CC->usersupp.timescalled = 0;
+       CC->usersupp.posted = 0;
+       CC->usersupp.axlevel = INITAX;
+       CC->usersupp.USscreenwidth = 80;
+       CC->usersupp.USscreenheight = 24;
+       time(&CC->usersupp.lastcall);
+       strcpy(CC->usersupp.USname, "");
+       strcpy(CC->usersupp.USaddr, "");
+       strcpy(CC->usersupp.UScity, "");
+       strcpy(CC->usersupp.USstate, "");
+       strcpy(CC->usersupp.USzip, "");
+       strcpy(CC->usersupp.USphone, "");
+
+       /* fetch a new user number */
+       CC->usersupp.usernum = get_new_user_number();
+
+       if (CC->usersupp.usernum == 1L) {
+               CC->usersupp.axlevel = 6;
+               }
+
+       /* add user to userlog */
+       putuser(&CC->usersupp,CC->curr_user);
+       if (getuser(&CC->usersupp,CC->curr_user)) {
+               return(ERROR+INTERNAL_ERROR);
+               }
+       rec_log(CL_NEWUSER,CC->curr_user);
+       return(0);
+       }
+
+
+
+
+/*
+ * cmd_newu()  -  create a new user account
+ */
+void cmd_newu(char *cmdbuf)
+{
+       int a;
+       char username[256];
+
+       if ((CC->logged_in)) {
+               cprintf("%d Already logged in.\n",ERROR);
+               return;
+               }
+
+       if ((CC->nologin)) {
+               cprintf("%d %s: Too many users are already online (maximum is %d)\n",
+               ERROR+MAX_SESSIONS_EXCEEDED,
+               config.c_nodename,config.c_maxsessions);
+               }
+
+       extract(username,cmdbuf,0);
+       username[25] = 0;
+       strproc(username);
+
+       if (strlen(username)==0) {
+               cprintf("%d You must supply a user name.\n",ERROR);
+               return;
+               }
+
+       a = create_user(username);
+       if ((!strucmp(username, "bbs")) ||
+           (!strucmp(username, "new")) ||
+           (!strucmp(username, ".")))
+       {
+          cprintf("%d '%s' is an invalid login name.\n", ERROR);
+          return;
+       }
+       if (a==ERROR+ALREADY_EXISTS) {
+               cprintf("%d '%s' already exists.\n",
+                       ERROR+ALREADY_EXISTS,username);
+               return;
+               }
+       else if (a==ERROR+INTERNAL_ERROR) {
+               cprintf("%d Internal error - user record disappeared?\n",
+                       ERROR+INTERNAL_ERROR);
+               return;
+               }
+       else if (a==0) {
+               session_startup();
+               }
+       else {
+               cprintf("%d unknown error\n",ERROR);
+               }
+       rec_log(CL_NEWUSER,CC->curr_user);
+       }
+
+
+
+/*
+ * set password
+ */
+void cmd_setp(char *new_pw)
+{
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+       if (CC->usersupp.USuid != BBSUID) {
+               cprintf("%d Not allowed.  Use the 'passwd' command.\n",ERROR);
+               return;
+               }
+       strproc(new_pw);
+       if (strlen(new_pw)==0) {
+               cprintf("%d Password unchanged.\n",OK);
+               return;
+               }
+       lgetuser(&CC->usersupp,CC->curr_user);
+       strcpy(CC->usersupp.password,new_pw);
+       pwcrypt(CC->usersupp.password,config.c_pwcrypt);
+       lputuser(&CC->usersupp,CC->curr_user);
+       cprintf("%d Password changed.\n",OK);
+       rec_log(CL_PWCHANGE,CC->curr_user);
+       }
+
+/*
+ * get user parameters
+ */
+void cmd_getu(void) {
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+       getuser(&CC->usersupp,CC->curr_user);
+       cprintf("%d %d|%d|%d\n",OK,CC->usersupp.USscreenwidth,
+               CC->usersupp.USscreenheight,(CC->usersupp.flags & US_USER_SET));
+       }
+
+/*
+ * set user parameters
+ */
+void cmd_setu(char *new_parms)
+{
+
+       if (num_parms(new_parms)!=3) {
+               cprintf("%d Usage error.\n",ERROR);
+               return;
+               }       
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+       lgetuser(&CC->usersupp,CC->curr_user);
+       CC->usersupp.USscreenwidth = extract_int(new_parms,0);
+       CC->usersupp.USscreenheight = extract_int(new_parms,1);
+       CC->usersupp.flags = CC->usersupp.flags & (~US_USER_SET);
+       CC->usersupp.flags = CC->usersupp.flags | 
+               (extract_int(new_parms,2) & US_USER_SET);
+       lputuser(&CC->usersupp,CC->curr_user);
+       cprintf("%d Ok\n",OK);
+       }
+
+/*
+ * set last read pointer
+ */
+void cmd_slrp(char *new_ptr)
+{
+       long newlr;
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (CC->curr_rm < 0) {
+               cprintf("%d No current room.\n",ERROR);
+               return;
+               }
+
+       if (!struncmp(new_ptr,"highest",7)) {
+               newlr = CC->quickroom.QRhighest;
+               }
+       else {
+               newlr = atol(new_ptr);
+               }
+
+       lgetuser(&CC->usersupp, CC->curr_user);
+       CC->usersupp.lastseen[CC->curr_rm] = newlr;
+       lputuser(&CC->usersupp, CC->curr_user);
+       cprintf("%d %ld\n",OK,newlr);
+       }
+
+
+/*
+ * INVT and KICK commands
+ */
+void cmd_invt_kick(char *iuser, int op)
+                       /* user name */
+        {              /* 1 = invite, 0 = kick out */
+       struct usersupp USscratch;
+       char bbb[256];
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (CC->curr_rm < 0) {
+               cprintf("%d No current room.\n",ERROR);
+               return;
+               }
+
+       if (is_room_aide()==0) {
+               cprintf("%d Higher access required.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       if ( (op==1) && ((CC->quickroom.QRflags&QR_PRIVATE)==0) ) {
+               cprintf("%d Not a private room.\n",ERROR+NOT_HERE);
+               return;
+               }
+
+       if (lgetuser(&USscratch,iuser)!=0) {
+               cprintf("%d No such user.\n",ERROR);
+               return;
+               }
+
+       if (op==1) {
+               USscratch.generation[CC->curr_rm]=CC->quickroom.QRgen;
+               USscratch.forget[CC->curr_rm]=(-1);
+               }
+
+       if (op==0) {
+               USscratch.generation[CC->curr_rm]=(-1);
+               USscratch.forget[CC->curr_rm]=CC->quickroom.QRgen;
+               }
+
+       lputuser(&USscratch,iuser);
+
+       /* post a message in Aide> saying what we just did */
+       sprintf(bbb,"%s %s %s> by %s",
+               iuser,
+               ((op == 1) ? "invited to" : "kicked out of"),
+               CC->quickroom.QRname,
+               CC->usersupp.fullname);
+       aide_message(bbb);
+
+       if ((op==0)&&((CC->quickroom.QRflags&QR_PRIVATE)==0)) {
+               cprintf("%d Ok. (Not a private room, <Z>ap effect only)\n",OK);
+               }
+       else {
+               cprintf("%d Ok.\n",OK);
+               }
+       return;
+       }
+
+
+/*
+ * forget (Zap) the current room
+ */
+void cmd_forg(void) {
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (CC->curr_rm < 0) {
+               cprintf("%d No current room.\n",ERROR);
+               return;
+               }
+
+       if (CC->curr_rm < 3) {
+               cprintf("%d You cannot forget this room.\n",ERROR+NOT_HERE);
+               return;
+               }
+
+       if (is_aide()) {
+               cprintf("%d Aides cannot forget rooms.\n",ERROR);
+               return;
+               }
+
+       lgetuser(&CC->usersupp,CC->curr_user);
+       CC->usersupp.forget[CC->curr_rm] = CC->quickroom.QRgen;
+       CC->usersupp.generation[CC->curr_rm] = (-1);
+       lputuser(&CC->usersupp,CC->curr_user);
+       cprintf("%d Ok\n",OK);
+       CC->curr_rm = (-1);
+       }
+
+/*
+ * Get Next Unregistered User
+ */
+void cmd_gnur(void) {
+       struct cdbdata *cdbus;
+       struct usersupp usbuf;
+       FILE *fp;
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (CC->usersupp.axlevel < 6) {
+               cprintf("%d Higher access required.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       if ((CitControl.MMflags&MM_VALID)==0) {
+               cprintf("%d There are no unvalidated users.\n",OK);
+               return;
+               }
+
+       /* There are unvalidated users.  Traverse the usersupp database,
+        * and return the first user we find that needs validation.
+        */
+       cdb_rewind(CDB_USERSUPP);
+       while (cdbus = cdb_next_item(CDB_USERSUPP), cdbus != NULL) {
+               bzero(&usbuf, sizeof(struct usersupp));
+               memcpy(&usbuf, cdbus->ptr, cdbus->len);
+               cdb_free(cdbus);
+               if ((usbuf.flags & US_NEEDVALID)
+                  &&(usbuf.axlevel > 0)) {
+                       cprintf("%d %s\n",MORE_DATA,usbuf.fullname);
+                       return;
+                       }
+               } 
+
+       /* If we get to this point, there are no more unvalidated users.
+        * Therefore we clear the "users need validation" flag.
+        */
+
+       begin_critical_section(S_CONTROL);
+       get_control();
+       CitControl.MMflags = CitControl.MMflags&(~MM_VALID);
+       put_control();
+       end_critical_section(S_CONTROL);
+       cprintf("%d *** End of registration.\n",OK);
+
+
+       }
+
+
+/*
+ * get registration info for a user
+ */
+void cmd_greg(char *who)
+{
+       struct usersupp usbuf;
+       int a,b;
+       char pbuf[32];
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (!strucmp(who,"_SELF_")) strcpy(who,CC->curr_user);
+
+       if ((CC->usersupp.axlevel < 6) && (strucmp(who,CC->curr_user))) {
+               cprintf("%d Higher access required.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       if (getuser(&usbuf,who) != 0) {
+               cprintf("%d '%s' not found.\n",ERROR+NO_SUCH_USER,who);
+               return;
+               }
+
+       cprintf("%d %s\n",LISTING_FOLLOWS,usbuf.fullname);
+       cprintf("%ld\n",usbuf.usernum);
+       pwcrypt(usbuf.password,PWCRYPT);
+       cprintf("%s\n",usbuf.password);
+       cprintf("%s\n",usbuf.USname);
+       cprintf("%s\n",usbuf.USaddr);
+       cprintf("%s\n%s\n%s\n",
+               usbuf.UScity,usbuf.USstate,usbuf.USzip);
+       strcpy(pbuf,usbuf.USphone);
+       usbuf.USphone[0]=0;
+       for (a=0; a<strlen(pbuf); ++a) {
+               if ((pbuf[a]>='0')&&(pbuf[a]<='9')) {
+                       b=strlen(usbuf.USphone);
+                       usbuf.USphone[b]=pbuf[a];
+                       usbuf.USphone[b+1]=0;
+                       }
+               }
+       while(strlen(usbuf.USphone)<10) {
+               strcpy(pbuf,usbuf.USphone);
+               strcpy(usbuf.USphone," ");
+               strcat(usbuf.USphone,pbuf);
+               }
+
+       cprintf("(%c%c%c) %c%c%c-%c%c%c%c\n",
+               usbuf.USphone[0],usbuf.USphone[1],
+               usbuf.USphone[2],usbuf.USphone[3],
+               usbuf.USphone[4],usbuf.USphone[5],
+               usbuf.USphone[6],usbuf.USphone[7],
+               usbuf.USphone[8],usbuf.USphone[9]);
+
+       cprintf("%d\n",usbuf.axlevel);
+       cprintf("%s\n",usbuf.USemail);
+       cprintf("000\n");
+       }
+
+/*
+ * validate a user
+ */
+void cmd_vali(char *v_args)
+{
+       char user[256];
+       int newax;
+       struct usersupp userbuf;
+
+       extract(user,v_args,0);
+       newax = extract_int(v_args,1);
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       if (CC->usersupp.axlevel < 6) {
+               cprintf("%d Higher access required.\n",
+                       ERROR+HIGHER_ACCESS_REQUIRED);
+               return;
+               }
+
+       if (lgetuser(&userbuf,user)!=0) {
+               cprintf("%d '%s' not found.\n",ERROR+NO_SUCH_USER,user);
+               return;
+               }
+
+       userbuf.axlevel = newax;
+       userbuf.flags = (userbuf.flags & ~US_NEEDVALID);
+
+       lputuser(&userbuf,user);
+       cprintf("%d ok\n",OK);
+       }
+
+
+
+/* 
+ *  List users
+ */
+void cmd_list(void) {
+       struct usersupp usbuf;
+       struct cdbdata *cdbus;
+
+       cdb_rewind(CDB_USERSUPP);
+       cprintf("%d \n",LISTING_FOLLOWS);
+
+       while(cdbus = cdb_next_item(CDB_USERSUPP), cdbus != NULL) {
+               bzero(&usbuf, sizeof(struct usersupp));
+               memcpy(&usbuf, cdbus->ptr, cdbus->len);
+               cdb_free(cdbus);
+
+           if (usbuf.axlevel > 0) {
+               if ((CC->usersupp.axlevel>=6)
+                  ||((usbuf.flags&US_UNLISTED)==0)
+                  ||((CC->internal_pgm))) {
+                       cprintf("%s|%d|%ld|%ld|%d|%d|",
+                               usbuf.fullname,
+                               usbuf.axlevel,
+                               usbuf.usernum,
+                               usbuf.lastcall,
+                               usbuf.timescalled,
+                               usbuf.posted);
+                       pwcrypt(usbuf.password,config.c_pwcrypt);
+                       if (CC->usersupp.axlevel >= 6) cprintf("%s",usbuf.password);
+                       cprintf("\n");
+                       }
+                   }
+               }
+       cprintf("000\n");
+       }
+
+/*
+ * enter registration info
+ */
+void cmd_regi(void) {
+       int a,b,c;
+       FILE *fp;
+       char buf[256];
+
+       char tmpname[256];
+       char tmpaddr[256];
+       char tmpcity[256];
+       char tmpstate[256];
+       char tmpzip[256];
+       char tmpphone[256];
+       char tmpemail[256];
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       strcpy(tmpname,"");
+       strcpy(tmpaddr,"");
+       strcpy(tmpcity,"");
+       strcpy(tmpstate,"");
+       strcpy(tmpzip,"");
+       strcpy(tmpphone,"");
+       strcpy(tmpemail,"");
+
+       cprintf("%d Send registration...\n",SEND_LISTING);
+       a=0;
+       while (client_gets(buf), strcmp(buf,"000")) {
+               if (a==0) strcpy(tmpname,buf);
+               if (a==1) strcpy(tmpaddr,buf);
+               if (a==2) strcpy(tmpcity,buf);
+               if (a==3) strcpy(tmpstate,buf);
+               if (a==4) {
+                       for (c=0; c<strlen(buf); ++c) {
+                               if ((buf[c]>='0')&&(buf[c]<='9')) {
+                                       b=strlen(tmpzip);
+                                       tmpzip[b]=buf[c];
+                                       tmpzip[b+1]=0;
+                                       }
+                               }
+                       }
+               if (a==5) {
+                       for (c=0; c<strlen(buf); ++c) {
+                               if ((buf[c]>='0')&&(buf[c]<='9')) {
+                                       b=strlen(tmpphone);
+                                       tmpphone[b]=buf[c];
+                                       tmpphone[b+1]=0;
+                                       }
+                               }
+                       }
+               if (a==6) strncpy(tmpemail,buf,31);
+               ++a;
+               }
+
+       tmpname[29]=0;
+       tmpaddr[24]=0;
+       tmpcity[14]=0;
+       tmpstate[2]=0;
+       tmpzip[9]=0;
+       tmpphone[10]=0;
+       tmpemail[31]=0;
+
+       lgetuser(&CC->usersupp,CC->curr_user);
+       strcpy(CC->usersupp.USname,tmpname);
+       strcpy(CC->usersupp.USaddr,tmpaddr);
+       strcpy(CC->usersupp.UScity,tmpcity);
+       strcpy(CC->usersupp.USstate,tmpstate);
+       strcpy(CC->usersupp.USzip,tmpzip);
+       strcpy(CC->usersupp.USphone,tmpphone);
+       strcpy(CC->usersupp.USemail,tmpemail);
+       CC->usersupp.flags=(CC->usersupp.flags|US_REGIS|US_NEEDVALID);
+       lputuser(&CC->usersupp,CC->curr_user);
+
+       /* set global flag calling for validation */
+       begin_critical_section(S_CONTROL);
+       get_control();
+       CitControl.MMflags = CitControl.MMflags | MM_VALID ;
+       put_control();
+       end_critical_section(S_CONTROL);
+       cprintf("%d *** End of registration.\n",OK);
+       }
+
+
+/*
+ * assorted info we need to check at login
+ */
+void cmd_chek(void) {
+       int mail = 0;
+       int regis = 0;
+       int vali = 0;
+       int a,file;
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       getuser(&CC->usersupp,CC->curr_user); /* no lock is needed here */
+       if ((REGISCALL!=0)&&((CC->usersupp.flags&US_REGIS)==0)) regis = 1;
+
+       if (CC->usersupp.axlevel >= 6) {
+               get_control();
+               if (CitControl.MMflags&MM_VALID) vali = 1;
+               }
+
+       mail=0;                         /* check for mail */
+       for (a=0; a<MAILSLOTS; ++a)
+               if ((CC->usersupp.mailnum[a])>(CC->usersupp.lastseen[1]))
+                       ++mail;
+
+       cprintf("%d %d|%d|%d\n",OK,mail,regis,vali);
+       }
+
+
+/*
+ * check to see if a user exists
+ */
+void cmd_qusr(char *who)
+{
+       struct usersupp usbuf;
+
+       if (getuser(&usbuf,who) == 0) {
+               cprintf("%d %s\n",OK,usbuf.fullname);
+               }
+       else {
+               cprintf("%d No such user.\n",ERROR+NO_SUCH_USER);
+               }
+       }
+
+
+/*
+ * enter user bio
+ */
+void cmd_ebio(void) {
+       char buf[256];
+       FILE *fp;
+
+       if (!(CC->logged_in)) {
+               cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
+               return;
+               }
+
+       sprintf(buf,"./bio/%ld",CC->usersupp.usernum);
+       fp = fopen(buf,"w");
+       if (fp == NULL) {
+               cprintf("%d Cannot create file\n",ERROR);
+               return;
+               }
+       cprintf("%d  \n",SEND_LISTING);
+       while(client_gets(buf), strcmp(buf,"000")) {
+               fprintf(fp,"%s\n",buf);
+               }
+       fclose(fp);
+       }
+
+/*
+ * read user bio
+ */
+void cmd_rbio(char *cmdbuf)
+{
+       struct usersupp ruser;
+       char buf[256];
+       FILE *fp;
+
+       extract(buf,cmdbuf,0);
+       if (getuser(&ruser,buf)!=0) {
+               cprintf("%d No such user.\n",ERROR+NO_SUCH_USER);
+               return;
+               }
+       sprintf(buf,"./bio/%ld",ruser.usernum);
+       
+       fp = fopen(buf,"r");
+       if (fp == NULL) {
+               cprintf("%d %s has no bio on file.\n",
+                       ERROR+FILE_NOT_FOUND,ruser.fullname);
+               return;
+               }
+       cprintf("%d  \n",LISTING_FOLLOWS);
+       while (fgets(buf,256,fp)!=NULL) cprintf("%s",buf);
+       fclose(fp);
+       cprintf("000\n");
+       }
+
+/*
+ * list of users who have entered bios
+ */
+void cmd_lbio(void) {
+       char buf[256];
+       FILE *ls;
+       struct usersupp usbuf;
+
+       ls=popen("cd ./bio; ls","r");
+       if (ls==NULL) {
+               cprintf("%d Cannot open listing.\n",ERROR+FILE_NOT_FOUND);
+               return;
+               }
+
+       cprintf("%d\n",LISTING_FOLLOWS);
+       while (fgets(buf,255,ls)!=NULL)
+               if (getuserbynumber(&usbuf,atol(buf))==0)
+                       cprintf("%s\n",usbuf.fullname);
+       pclose(ls);
+       cprintf("000\n");
+       }
diff --git a/citadel/utils.txt b/citadel/utils.txt
new file mode 100644 (file)
index 0000000..9ce507c
--- /dev/null
@@ -0,0 +1,226 @@
+                        Citadel/UX Utilities Manual 
+                 See copyright.doc for copyright information
+   
+   
+ OVERVIEW
+    
+   The following utilities will be discussed in this document:
+ aidepost   Post standard input to the Aide> room. NOTE: called by chat.c
+ whobbs     Who is on the system (connected to the server, actually.)
+ msgstats   Print lowest & highest message numbers, and current file position
+ stats      Print the calling statistics & graph.
+ mailutil   Mailbox utilities
+ msgform    Format a binary message to the screen (stdin or in a file)
+ userlist   Print the userlist.
+ readlog    Read the caller log
+ useradmin  Full-screen user account editor. NOTE: requires the "curses" lib
+ sysoputil  Various and sundry sysop utilities.
+   
+   It is up to you to decide which utilities should be made accessible only
+to sysops. It is important that you set the file permissions correctly. All
+utilities should have access to the BBS data files, whether through ownership
+or set-user-id. In addition, aidepost should be accessible by the chat program,
+and userlist & whobbs should be accessible by citadel. I will attempt to
+address each program individually.
+   
+   
+  AIDEPOST
+  
+   The nature of this program is rather simple. Standard input (stdin) is
+converted into a message, filed in the main message file (msgmain), and posted
+in the Aide> room. This is useful for keeping transcripts of system activity
+that has to do with the BBS. You may also wish to have a really BIG msgmain
+file, and have all of your normal system logs go in there also. 
+   
+    
+  WHOBBS
+   
+   This program is similar to the "who" command.  It lists all of the users
+who are currently connected to your BBS server, either locally or across a
+network.  Unless you're running a standalone system, "who" and "whobbs" will
+probably not have a one-to-one correspondence (unlike earlier versions of the
+system).
+   One thing to keep in mind is that the "whobbs" utility actually opens a
+connection to the server.  If the server is maxed out, whobbs will still be
+able to provide a listing, because it doesn't need to log in to execute the
+RWHO command.  Note that whobbs does not list its own session.
+   Running the <W>ho is online command from the client does *not* call this
+utility.  It has this functionality built in.
+   
+   
+  MSGSTATS
+   
+   This program displays the lowest and highest message numbers which currently
+exist in the master file, the current file pointer (what byte in msgmain the
+next message entered will start at), and whether the file is locked or
+unlocked. Normally the file will only be locked for a second or two during a
+message <S>ave, or network processing, but if there is a glitch somewhere and
+the file remains locked, this program will tell you so and you can use the
+unlock option in sysoputil.
+   As of release 3.23, message base locking is a lot more reliable, because
+it uses file-locking system calls if they are available.  But, if you're
+running on a brain-damaged kernel that doesn't have file locking, and your
+message base appears to be locked up, this will tell you.
+   This utility now also provides information on the total number of
+messages currently on the system, and the average message length.
+   
+   
+  STATS
+  
+   (NOTE: this program no longer uses the "curses" library.)   It prints
+various statistics on the screen based on the calllog file, such as number
+of connects, number of logins, number of logouts, number of disconnects,
+bad password attempts, etc. All statistics are provided in three figures:
+times per call, times per day, and total times.
+   After this screen appears, you may press return for the next screen. This
+is a graphic representation of system usage, broken down into 20 minute
+segments of the day.  This will show you when your peak usage is.
+   Press return again and the stats program will list your system's top
+20 callers.
+   The final screen lists the twenty users with the highest post per call
+ratios.  Unline the other screens, this statistic is extracted from the user
+file itself rather than the call log.
+ stats may be called with the "-b" option to run in batch mode.  In this case,
+it skips all the prompts and just prints everything out in one bulk output.
+Or it may be called with the "-p" option to only print the Post-to-Call
+ratios and nothing else.
+  
+  
+  MAILUTIL
+   
+    This utility allows you to perform various operations on a user's
+mailbox.  Note that reading people's mail is, of course, unethical, and
+possibly illegal.
+   
+  MSGFORM
+  
+   On occasion, I have had messages in Citadel/UX binary format that I have
+wanted to view. Or needed a way to view a network spool file. Or wanted to
+directly examine parts of msgmain. This program is a simple message formatter.
+   msgform reads standard input, scanning for binary messages, starting
+with an <FF> byte and ending after the final NULL, and print as many as it
+finds until it encounters EOF.
+   You could use this utility along with netproc to provide printouts or
+archives of certain rooms.
+    
+   
+  USERLIST
+  
+   This is a program to print the userlist. There are two flags that may be
+set when running this program. When called without any arguments, userlist
+will display all users (except those who have chosen to be unlisted), their
+user numbers, times called, messages posted, screen width, and date of their
+most recent call.
+
+   Setting the -p option (only allowed by root as distributed; you may wish
+to change this) also displays passwords, and lists all users regardless of
+whether they are unlisted.
+
+   Setting the -n option causes the next argument after -n to be a user
+number to search for.
+   You can also elect to sort the output by user name or user number by
+specifying the -su or -sn flags.
+   
+   
+  READLOG
+  
+   Called without any arguments, readlog dumps the contents of the calllog
+file. This file records all times the Citadel/UX program has been started, and
+at what baud rate, as well as logins, proper logouts, loss of carrier (SIGHUP),
+bad password attempts, and new user logins.
+   Readlog called with the -t argument displays a list of the names of the
+last twenty users who have logged in.
+   
+    
+  USERADMIN
+  
+   Useradmin is a full-screen program to view and edit any user's account.
+The program will prompt for a user name. After you enter the name, various
+parameters of the user's account will appear on the screen. To change one
+of the parameters, simply enter its number.
+  1. User name - you may change a user's name at any time.  If you do this,
+the hash table to the user file will be rewritten when you exit useradmin.
+  2. User number - if you change a user's number, keep in mind that the user's
+account will no longer be attached to any other records which are indexed by
+user number, such as registration, room aide assignments, etc.  These must be
+changed as well.
+  3. UID attachment - if you wish to attach a BBS account to an /etc/passwd
+account, simply make the uid's the same. Likewise, if a user is to no longer
+have an account in /etc/passwd, simply set this to the same as BBSUID. Note
+that the user's "full name" in /etc/passwd MUST be the same as their BBS login
+name, otherwise a new (and probably unwanted) account will be created.
+  4. Password          - these four fields are self explanatory.
+  5. Screen width
+  6. Times called
+  7. Messages posted
+  8. Last call - Date and time of last call. If you select this,
+it will not prompt for a new time, but set it to the current time.
+  9. Access Level - choose from the standard access levels:
+       0 = Marked for deletion
+       1 = New unvalidated user
+       2 = Problem user
+       3 = Validated user without network privileges
+       4 = Validated user with network privileges
+       5 = Preferred user
+       6 = Aide
+   
+  The rest are boolean flags. Selecting them will toggle the options.
+ 10. Permanent user (do not scroll off)
+ 11. Print last old message on new message request
+ 12. Expert mode (supress automatic hints)
+ 13. Do not list in .RU command
+ 14. Prompting after each message
+ 15. Registered in the registration file
+ 16. Pause after each screenful of text
+   
+  In addition, if the user is registered, you can type -1 to make the
+corresponding record in the registration file appear on the screen. 
+   When you are finished examining or editing an account, type 0 to exit. It
+will ask you if you wish to save the changes.
+   
+   
+  SYSOPUTIL
+   
+   This program handles some of the sysop functions of the system. It can
+be called three ways: sysoputil by itself brings up the menu. Also:
+ sysoputil -u      Execute option 7 (user purge) and exit
+ sysoputil -r      Execute option 1 (room purge) and exit
+ sysoputil -g      Execute option 2 (registration list) and exit
+ sysoputil -h     Execute option 3 (rewrite hash table) and exit
+  
+  The menu choices are as follows:
+  1. Purge old rooms. This will delete any rooms which have not been written
+to (posted in) in two weeks. Notification of purged rooms is posted in the
+Aide> room (using the aidepost utility).
+ 2. Read registration file. This will partially list the registration file
+to the screen (partially: street address is omitted).
+ 3. Rewrite hash table.  Under normal conditions, the hash table of user
+names is updated whenever usersupp is updated.  If the system for some
+reason doesn't know about any users, but they're right there for you to see
+when you run userlist, chances are the hash table is out of sync.  This will
+hardly ever happen unless the disk was full, or you've been tinkering with
+usersupp, or if you're upgrading from a non-hashing version of Citadel.  This
+option will rewrite the hash table from scratch.
+ 4. Unlock message file. The msgmain file is locked during message saves to
+prevent two users from writing to the file at once. If there is some sort of
+crash or bug and it remains locked, this will unlock it.
+ 5. Sort the userlog by user ID.
+ 6. Sort the userlog by user name.  (Sorting the userlog is a purely cosmetic
+option, and will not at all affect the performance of the system.)
+ 7. Purge old users. This will delete any users which have not called in
+two months. Notification of purged users is posted in the Aide> room (using
+the aidepost utility).
+ 9. Quit
+   
+   
+   That should cover all of the included utilities. Comments, suggestions,
+etc. may be sent to ajc@uncnsrd.mt-kisco.ny.us or call UNCENSORED! BBS at
+(914) 244-3252 (modem) or uncnsrd.mt-kisco.ny.us (Internet).
diff --git a/citadel/utilsmenu b/citadel/utilsmenu
new file mode 100755 (executable)
index 0000000..631c57b
--- /dev/null
@@ -0,0 +1,57 @@
+# Citadel/UX Utilities Menu (shell script)
+# version 1.00 - March 1989
+# see copyright.doc for copyright information
+
+while true
+do
+       clear
+       echo
+       echo
+       echo "                    Citadel/UX Utilities Menu"
+       cat <<!!
+
+ a. Citadel/UX login                     g. Mail utilities
+ b. Sysop utilities                      h. Read call log
+ c. Calling statistics                   i. List last 20 users
+ d. Message file statistics              j. Who is logged in
+ e. User administration                  k. Setup and configuration
+ f. User list with passwords             q. Quit
+
+!!
+       echo "Please enter your selection:"
+       read x
+       case $x in
+               [a,A])  citadel
+                       ;;
+               [b,B])  sysoputil
+                       ;;
+               [c,C])  stats 
+                       read y
+                       ;;
+               [d,D])  msgstats |more -cw
+                       ;;
+               [e,E])  useradmin
+                       ;;
+               [f,F])  clear
+                       userlist -p |more -cw
+                       ;;
+               [g,G])  mailutil
+                       ;;
+               [h,H])  readlog |more -cw
+                       ;;
+               [i,I])  readlog -t |more -cw
+                       ;;
+               [j,J])  whobbs |more -cw
+                       ;;
+               [k,K])  clear
+                       setup
+                       ;;
+               [q,Q])  clear
+                       break;;
+               !)      sh
+                       ;;
+               *)      echo "Selection $x is not available"
+                       sleep 1
+                       ;;
+               esac
+       done
diff --git a/citadel/whobbs.c b/citadel/whobbs.c
new file mode 100644 (file)
index 0000000..29d942f
--- /dev/null
@@ -0,0 +1,142 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include "citadel.h"
+
+void attach_to_server();
+
+/*
+ * num_parms()  -  discover number of parameters...
+ */
+int num_parms(source)
+char source[]; {
+       int a;
+       int count = 1;
+
+       for (a=0; a<strlen(source); ++a) 
+               if (source[a]=='|') ++count;
+       return(count);
+       }
+
+
+/*
+ * extract()  -  extract a parameter from a series of "|" separated...
+ */
+void extract(dest,source,parmnum)
+char dest[];
+char source[];
+int parmnum; {
+       char buf[256];
+       int count = 0;
+       int n;
+
+       n = num_parms(source);
+
+       if (parmnum >= n) {
+               strcpy(dest,"");
+               return;
+               }
+       strcpy(buf,source);
+       if ( (parmnum == 0) && (n == 1) ) {
+               strcpy(dest,buf);
+               return;
+               }
+
+       while (count++ < parmnum) do {
+               strcpy(buf,&buf[1]);
+               } while( (strlen(buf)>0) && (buf[0]!='|') );
+       if (buf[0]=='|') strcpy(buf,&buf[1]);
+       for (count = 0; count<strlen(buf); ++count)
+               if (buf[count] == '|') buf[count] = 0;
+       strcpy(dest,buf);
+       }
+
+/*
+ * extract_int()  -  extract an int parm w/o supplying a buffer
+ */
+int extract_int(source,parmnum)
+char *source;
+int parmnum; {
+       char buf[256];
+       
+       extract(buf,source,parmnum);
+       return(atoi(buf));
+       }
+
+
+void logoff(code)
+int code; {
+       exit(code);
+       }
+
+void main(argc,argv)
+int argc;
+char *argv[]; {
+       char buf[256];
+       char nodetitle[256];
+       int a;
+       int s_pid = 0;
+       int my_pid = 0;
+       char s_user[256];
+       char s_room[256];
+       char s_host[256];
+
+       attach_to_server(argc,argv);
+       serv_gets(buf);
+       if ((buf[0]!='2')&&(strncmp(buf,"551",3))) {
+               fprintf(stderr,"%s: %s\n",argv[0],&buf[4]);
+               logoff(atoi(buf));
+               }
+       strcpy(nodetitle, "this BBS");
+       serv_puts("INFO");
+       serv_gets(buf);
+       if (buf[0]=='1') {
+               a = 0;
+               while (serv_gets(buf), strcmp(buf,"000")) {
+                       if (a==0) my_pid = atoi(buf);
+                       if (a==2) strcpy(nodetitle, buf);
+                       ++a;
+                       }
+               }
+       printf("            Users currently logged on to %s\n", nodetitle);
+       serv_puts("RWHO");
+       serv_gets(buf);
+       if (buf[0]!='1') {
+               fprintf(stderr,"%s: %s\n",argv[0],&buf[4]);
+               logoff(atoi(buf));
+               }
+
+       printf("Session         User name               Room                  From host\n");
+       printf("------- ------------------------- -------------------- ------------------------\n");
+       while (serv_gets(buf), strcmp(buf,"000")) {
+               s_pid = extract_int(buf,0);
+               extract(s_user,buf,1);
+               extract(s_room,buf,2);
+               extract(s_host,buf,3);
+               if (s_pid != my_pid) {
+                       printf("%-7d%c%-25s %-20s %-24s\n",
+                               s_pid,
+                               ((s_pid == my_pid) ? '*' : ' '),
+                               s_user,s_room,s_host);
+                       }
+               }
+
+       serv_puts("QUIT");
+       serv_gets(buf);
+       exit(0);
+       }
+
+
+#ifdef NO_STRERROR
+/*
+ * replacement strerror() for systems that don't have it
+ */
+char *strerror(e)
+int e; {
+       static char buf[32];
+
+       sprintf(buf,"errno = %d",e);
+       return(buf);
+       }
+#endif