--- /dev/null
+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.
--- /dev/null
+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
--- /dev/null
+# 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
--- /dev/null
+ 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).
--- /dev/null
+/* 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);
+ }
--- /dev/null
+#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
--- /dev/null
+/*
+ * 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() */
+
--- /dev/null
+/* 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 */
--- /dev/null
+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
--- /dev/null
+#
+# 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
+#
--- /dev/null
+/*
+ * 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);
+ }
+
--- /dev/null
+#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);
+ }
--- /dev/null
+/*
+ * 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,"");
+ }
+ }
+ }
+
+
--- /dev/null
+/*
+ * 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
+ }
--- /dev/null
+/*
+ * 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);
+ }
+ }
--- /dev/null
+/*
+ * 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);
+ }
--- /dev/null
+ 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)
--- /dev/null
+/*
+ * 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);
+ }
--- /dev/null
+/*
+ * 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);
+ }
--- /dev/null
+#!/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
--- /dev/null
+#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);
+ }
--- /dev/null
+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.
--- /dev/null
+ 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.
+
--- /dev/null
+^humannode is up 24 hours a day, 7 days a week.
--- /dev/null
+ 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.)
--- /dev/null
+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
+
--- /dev/null
+ 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).
+
--- /dev/null
+ 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.
--- /dev/null
+
+ < this new user policy resides in ./messages/newuser >
+
--- /dev/null
+ 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
--- /dev/null
+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
--- /dev/null
+/*
+ * 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);
+ }
+
+
--- /dev/null
+ 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).
--- /dev/null
+/*
+ * 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);
+ }
--- /dev/null
+/*
+ * 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);
+ }
--- /dev/null
+#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();
--- /dev/null
+/*
+ * 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;
+ }
--- /dev/null
+/*
+ * 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);
+ }
+ }
--- /dev/null
+ 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.
--- /dev/null
+/*
+ * 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);
+ }
--- /dev/null
+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
--- /dev/null
+ 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.
--- /dev/null
+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
+
--- /dev/null
+Entering message -- end by hitting return twice.
--- /dev/null
+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
--- /dev/null
+
+ < 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
+
--- /dev/null
+
+ < this logon banner resides in ./messages/hello >
+
+ Welcome to ^humannode !
+
--- /dev/null
+ ^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 ***
--- /dev/null
+ -----------------------------------------------------------------------
+ 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)
+ -----------------------------------------------------------------------
--- /dev/null
+
+ < this new user policy resides in ./messages/newuser >
+
--- /dev/null
+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
--- /dev/null
+ 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!
+
--- /dev/null
+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?
--- /dev/null
+One of:
+ <A>bort
+ <C>ontinue
+ <H>old this message
+ <P>rint formatted
+ <R>eplace string (edit)
+ <S>ave message
--- /dev/null
+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.
--- /dev/null
+#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);
+ }
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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 */
+ }
--- /dev/null
+#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);
+ }
--- /dev/null
+/*
+ * 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);
+ }
+
--- /dev/null
+/*
+ * 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);
+ }
--- /dev/null
+ 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.)
--- /dev/null
+ 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).
--- /dev/null
+# 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
--- /dev/null
+#
+# 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
--- /dev/null
+bbs,room_aide
+root,room_aide
+Auto,room_aide
--- /dev/null
+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
+
--- /dev/null
+# 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
--- /dev/null
+mega-net.gateway,Gateway
+test.usenet,Network Test
--- /dev/null
+cat %s >>./network/spoolout/uncnsrd
--- /dev/null
+# 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
--- /dev/null
+#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);
+}
+
+
--- /dev/null
+/*
+ * 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);
+}
+
--- /dev/null
+#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);
+ }
--- /dev/null
+/* 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]);
+ }
--- /dev/null
+/* 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));
+ }
--- /dev/null
+/* 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);
+ }
+ }
--- /dev/null
+#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);
+ }
+ }
--- /dev/null
+
+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;
+ };
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+#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
+
--- /dev/null
+/****************************************************************************/
+/* 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 ***/
--- /dev/null
+/*
+ * 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");
+ }
+ }
+ }
+
--- /dev/null
+ 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).
--- /dev/null
+#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");
+ }
--- /dev/null
+ 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).
--- /dev/null
+# 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
--- /dev/null
+#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