From 54b54a07b29cd57a19728f468e19cf887cda3873 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Sat, 11 Jul 1998 02:10:03 +0000 Subject: [PATCH] Initial revision --- citadel/COPYING.txt | 333 +++++++ citadel/IAFA-PACKAGE | 12 + citadel/Makefile.in | 189 ++++ citadel/README.txt | 183 ++++ citadel/aidepost.c | 66 ++ citadel/axdefs.h | 20 + citadel/citadel.c | 1340 ++++++++++++++++++++++++++ citadel/citadel.h | 252 +++++ citadel/citadel.lsm | 17 + citadel/citadel.rc | 207 ++++ citadel/citmail.c | 796 +++++++++++++++ citadel/citserver.c | 1027 ++++++++++++++++++++ citadel/client_chat.c | 190 ++++ citadel/commands.c | 885 +++++++++++++++++ citadel/config.c | 50 + citadel/control.c | 81 ++ citadel/copyright.txt | 30 + citadel/cux2ascii.c | 195 ++++ citadel/database.c | 257 +++++ citadel/dnetsetup | 178 ++++ citadel/file_ops.c | 712 ++++++++++++++ citadel/help/aide | 18 + citadel/help/floors | 43 + citadel/help/hours | 1 + citadel/help/intro | 139 +++ citadel/help/mail | 20 + citadel/help/network | 4 + citadel/help/nice | 128 +++ citadel/help/policy | 3 + citadel/help/software | 5 + citadel/help/summary | 48 + citadel/housekeeping.c | 65 ++ citadel/install.txt | 353 +++++++ citadel/internetmail.c | 104 ++ citadel/ipc_c_tcp.c | 335 +++++++ citadel/ipcdef.h | 72 ++ citadel/locate_host.c | 48 + citadel/mailinglist.c | 165 ++++ citadel/mailinglists.txt | 96 ++ citadel/messages.c | 1062 +++++++++++++++++++++ citadel/messages/aideopt | 12 + citadel/messages/changepw | 5 + citadel/messages/dotopt | 10 + citadel/messages/entermsg | 1 + citadel/messages/entopt | 13 + citadel/messages/goodbye | 7 + citadel/messages/hello | 5 + citadel/messages/help | 31 + citadel/messages/mainmenu | 11 + citadel/messages/newuser | 3 + citadel/messages/readopt | 14 + citadel/messages/register | 7 + citadel/messages/roomaccess | 17 + citadel/messages/saveopt | 7 + citadel/messages/unlisted | 3 + citadel/msgbase.c | 1249 ++++++++++++++++++++++++ citadel/msgform.c | 159 +++ citadel/netmailer.c | 292 ++++++ citadel/netpoll.c | 267 ++++++ citadel/netproc.c | 1341 ++++++++++++++++++++++++++ citadel/netsetup.c | 452 +++++++++ citadel/netsetup.txt | 53 + citadel/network.txt | 265 +++++ citadel/network/filterlist | 8 + citadel/network/internetmail.config | 47 + citadel/network/mail.aliases | 3 + citadel/network/mail.sysinfo | 217 +++++ citadel/network/mailinglists | 9 + citadel/network/rnews.xref | 2 + citadel/network/systems/uncnsrd | 1 + citadel/public_clients | 9 + citadel/rcit.c | 299 ++++++ citadel/readlog.c | 99 ++ citadel/room_ops.c | 1380 +++++++++++++++++++++++++++ citadel/rooms.c | 968 +++++++++++++++++++ citadel/routines.c | 668 +++++++++++++ citadel/routines2.c | 579 +++++++++++ citadel/serv_chat.c | 412 ++++++++ citadel/server.h | 129 +++ citadel/setup.c | 1273 ++++++++++++++++++++++++ citadel/stats.c | 357 +++++++ citadel/support.c | 243 +++++ citadel/sysconfig.h | 69 ++ citadel/sysdep.c | 726 ++++++++++++++ citadel/sysop.txt | 265 +++++ citadel/user_ops.c | 1067 +++++++++++++++++++++ citadel/utils.txt | 226 +++++ citadel/utilsmenu | 57 ++ citadel/whobbs.c | 142 +++ 89 files changed, 23208 insertions(+) create mode 100644 citadel/COPYING.txt create mode 100644 citadel/IAFA-PACKAGE create mode 100644 citadel/Makefile.in create mode 100644 citadel/README.txt create mode 100644 citadel/aidepost.c create mode 100644 citadel/axdefs.h create mode 100644 citadel/citadel.c create mode 100644 citadel/citadel.h create mode 100644 citadel/citadel.lsm create mode 100644 citadel/citadel.rc create mode 100644 citadel/citmail.c create mode 100644 citadel/citserver.c create mode 100644 citadel/client_chat.c create mode 100644 citadel/commands.c create mode 100644 citadel/config.c create mode 100644 citadel/control.c create mode 100644 citadel/copyright.txt create mode 100644 citadel/cux2ascii.c create mode 100644 citadel/database.c create mode 100755 citadel/dnetsetup create mode 100644 citadel/file_ops.c create mode 100644 citadel/help/aide create mode 100644 citadel/help/floors create mode 100644 citadel/help/hours create mode 100644 citadel/help/intro create mode 100644 citadel/help/mail create mode 100644 citadel/help/network create mode 100644 citadel/help/nice create mode 100644 citadel/help/policy create mode 100644 citadel/help/software create mode 100644 citadel/help/summary create mode 100644 citadel/housekeeping.c create mode 100644 citadel/install.txt create mode 100644 citadel/internetmail.c create mode 100644 citadel/ipc_c_tcp.c create mode 100644 citadel/ipcdef.h create mode 100644 citadel/locate_host.c create mode 100644 citadel/mailinglist.c create mode 100644 citadel/mailinglists.txt create mode 100644 citadel/messages.c create mode 100644 citadel/messages/aideopt create mode 100644 citadel/messages/changepw create mode 100644 citadel/messages/dotopt create mode 100644 citadel/messages/entermsg create mode 100644 citadel/messages/entopt create mode 100644 citadel/messages/goodbye create mode 100644 citadel/messages/hello create mode 100644 citadel/messages/help create mode 100644 citadel/messages/mainmenu create mode 100644 citadel/messages/newuser create mode 100644 citadel/messages/readopt create mode 100644 citadel/messages/register create mode 100644 citadel/messages/roomaccess create mode 100644 citadel/messages/saveopt create mode 100644 citadel/messages/unlisted create mode 100644 citadel/msgbase.c create mode 100644 citadel/msgform.c create mode 100644 citadel/netmailer.c create mode 100644 citadel/netpoll.c create mode 100644 citadel/netproc.c create mode 100644 citadel/netsetup.c create mode 100644 citadel/netsetup.txt create mode 100644 citadel/network.txt create mode 100644 citadel/network/filterlist create mode 100644 citadel/network/internetmail.config create mode 100644 citadel/network/mail.aliases create mode 100644 citadel/network/mail.sysinfo create mode 100644 citadel/network/mailinglists create mode 100644 citadel/network/rnews.xref create mode 100644 citadel/network/systems/uncnsrd create mode 100644 citadel/public_clients create mode 100644 citadel/rcit.c create mode 100644 citadel/readlog.c create mode 100644 citadel/room_ops.c create mode 100644 citadel/rooms.c create mode 100644 citadel/routines.c create mode 100644 citadel/routines2.c create mode 100644 citadel/serv_chat.c create mode 100644 citadel/server.h create mode 100644 citadel/setup.c create mode 100644 citadel/stats.c create mode 100644 citadel/support.c create mode 100644 citadel/sysconfig.h create mode 100644 citadel/sysdep.c create mode 100644 citadel/sysop.txt create mode 100644 citadel/user_ops.c create mode 100644 citadel/utils.txt create mode 100755 citadel/utilsmenu create mode 100644 citadel/whobbs.c diff --git a/citadel/COPYING.txt b/citadel/COPYING.txt new file mode 100644 index 000000000..2156ccdad --- /dev/null +++ b/citadel/COPYING.txt @@ -0,0 +1,333 @@ +GNU GENERAL PUBLIC LICENSE + +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to +share and change it. By contrast, the GNU General Public License is intended +to guarantee your freedom to share and change free software--to make sure +the software is free for all its users. This General Public License applies +to most of the Free Software Foundation's software and to any other program +whose authors commit to using it. (Some other Free Software Foundation +software is covered by the GNU Library General Public License instead.) You +can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom +to distribute copies of free software (and charge for this service if you +wish), that you receive source code or can get it if you want it, that you +can change the software or use pieces of it in new free programs; and that +you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These +restrictions translate to certain responsibilities for you if you distribute +copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or +for a fee, you must give the recipients all the rights that you have. You +must make sure that they, too, receive or can get the source code. And you +must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) +offer you this license which gives you legal permission to copy, distribute +and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If +the software is modified by someone else and passed on, we want its +recipients to know that what they have is not the original, so that any +problems introduced by others will not reflect on the original authors' +reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that redistributors of a free program will +individually obtain patent licenses, in effect making the program +proprietary. To prevent this, we have made it clear that any patent must be +licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms +of this General Public License. The "Program", below, refers to any such +program or work, and a "work based on the Program" means either the Program +or any derivative work under copyright law: that is to say, a work +containing the Program or a portion of it, either verbatim or with +modifications and/or translated into another language. (Hereinafter, +translation is included without limitation in the term "modification".) Each +licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running the Program +is not restricted, and the output from the Program is covered only if its +contents constitute a work based on the Program (independent of having been +made by running the Program). Whether that is true depends on what the +Program does. + +1. You may copy and distribute verbatim copies of the Program's source code +as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this +License and to the absence of any warranty; and give any other recipients of +the Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, +thus forming a work based on the Program, and copy and distribute such +modifications or work under the terms of Section 1 above, provided that you +also meet all of these conditions: + + * a) You must cause the modified files to carry prominent notices stating + that you changed the files and the date of any change. + + * b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any part + thereof, to be licensed as a whole at no charge to all third parties + under the terms of this License. + + * c) If the modified program normally reads commands interactively when + run, you must cause it, when started running for such interactive use + in the most ordinary way, to print or display an announcement including + an appropriate copyright notice and a notice that there is no warranty + (or else, saying that you provide a warranty) and that users may + redistribute the program under these conditions, and telling the user + how to view a copy of this License. (Exception: if the Program itself + is interactive but does not normally print such an announcement, your + work based on the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be +reasonably considered independent and separate works in themselves, then +this License, and its terms, do not apply to those sections when you +distribute them as separate works. But when you distribute the same sections +as part of a whole which is a work based on the Program, the distribution of +the whole must be on the terms of this License, whose permissions for other +licensees extend to the entire whole, and thus to each and every part +regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works +based on the Program. + +In addition, mere aggregation of another work not based on the Program with +the Program (or with a work based on the Program) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may copy and distribute the Program (or a work based on it, under +Section 2) in object code or executable form under the terms of Sections 1 +and 2 above provided that you also do one of the following: + + * a) Accompany it with the complete corresponding machine-readable source + code, which must be distributed under the terms of Sections 1 and 2 + above on a medium customarily used for software interchange; or, + + * b) Accompany it with a written offer, valid for at least three years, + to give any third party, for a charge no more than your cost of + physically performing source distribution, a complete machine-readable + copy of the corresponding source code, to be distributed under the + terms of Sections 1 and 2 above on a medium customarily used for + software interchange; or, + + * c) Accompany it with the information you received as to the offer to + distribute corresponding source code. (This alternative is allowed only + for noncommercial distribution and only if you received the program in + object code or executable form with such an offer, in accord with + Subsection b above.) + +The source code for a work means the preferred form of the work for making +modifications to it. For an executable work, complete source code means all +the source code for all modules it contains, plus any associated interface +definition files, plus the scripts used to control compilation and +installation of the executable. However, as a special exception, the source +code distributed need not include anything that is normally distributed (in +either source or binary form) with the major components (compiler, kernel, +and so on) of the operating system on which the executable runs, unless that +component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to +copy from a designated place, then offering equivalent access to copy the +source code from the same place counts as distribution of the source code, +even though third parties are not compelled to copy the source along with +the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as +expressly provided under this License. Any attempt otherwise to copy, +modify, sublicense or distribute the Program is void, and will automatically +terminate your rights under this License. However, parties who have received +copies, or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Program or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the +Program (or any work based on the Program), you indicate your acceptance of +this License to do so, and all its terms and conditions for copying, +distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the original +licensor to copy, distribute or modify the Program subject to these terms +and conditions. You may not impose any further restrictions on the +recipients' exercise of the rights granted herein. You are not responsible +for enforcing compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot distribute so +as to satisfy simultaneously your obligations under this License and any +other pertinent obligations, then as a consequence you may not distribute +the Program at all. For example, if a patent license would not permit +royalty-free redistribution of the Program by all those who receive copies +directly or indirectly through you, then the only way you could satisfy both +it and this License would be to refrain entirely from distribution of the +Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and +the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system, which is implemented by public license +practices. Many people have made generous contributions to the wide range of +software distributed through that system in reliance on consistent +application of that system; it is up to the author/donor to decide if he or +she is willing to distribute software through any other system and a +licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original +copyright holder who places the Program under this License may add an +explicit geographical distribution limitation excluding those countries, so +that distribution is permitted only in or among countries not thus excluded. +In such case, this License incorporates the limitation as if written in the +body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of +the General Public License from time to time. Such new versions will be +similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make +exceptions for this. Our decision will be guided by the two goals of +preserving the free status of all derivatives of our free software and of +promoting the sharing and reuse of software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO +THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM +PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO +LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR +THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible +use to the public, the best way to achieve this is to make it free software +which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively convey the +exclusion of warranty; and each file should have at least the "copyright" +line and a pointer to where the full notice is found. + +one line to give the program's name and an idea of what it does. +Copyright (C) 19yy name of author + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when +it starts in an interactive mode: + +Gnomovision version 69, Copyright (C) 19yy name of author +Gnomovision comes with ABSOLUTELY NO WARRANTY; for details +type `show w'. This is free software, and you are welcome +to redistribute it under certain conditions; type `show c' +for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may be +called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright +interest in the program `Gnomovision' +(which makes passes at compilers) written +by James Hacker. + +signature of Ty Coon, 1 April 1989 +Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General Public +License instead of this License. diff --git a/citadel/IAFA-PACKAGE b/citadel/IAFA-PACKAGE new file mode 100644 index 000000000..25a3d67c6 --- /dev/null +++ b/citadel/IAFA-PACKAGE @@ -0,0 +1,12 @@ +Title: Citadel/UX +Version: 5.01 +Description: An implementation of the Citadel BBS program for Unix systems. + This is the de facto standard Unix version of Citadel, and is + now an advanced client/server application. +Author: Art Cancro +Maintained-by: Art Cancro +Maintained-at: uncnsrd.mt-kisco.ny.us, ftp.tux.org +Platforms: Any reasonably compliant POSIX (Unix, Linux, etc.) type + system with a TCP/IP stack and POSIX Threads (pthreads). +Copying-Policy: Freely Redistributable +Keywords: Citadel Citadel/UX UNIX room-based BBS IGnet IGnet/Open diff --git a/citadel/Makefile.in b/citadel/Makefile.in new file mode 100644 index 000000000..3ac0bea4e --- /dev/null +++ b/citadel/Makefile.in @@ -0,0 +1,189 @@ +# Makefile for Citadel/UX +# +# NOTE: normally you should not have to modify the Makefile. All +# system-dependent configuration is in the "configure" script, which +# uses "Makefile.in" to generate a "Makefile". In the rare instance +# that you have to modify something here, please take note: +# 1. Edit Makefile.in, -not- Makefile. +# 2. Send e-mail to ajc@uncnsrd.mt-kisco.ny.us and let me know what you +# did, so any necessary changes can be put into the next release. +# +######################################################################## + +client: citadel whobbs + +server: citserver setup + +utils: aidepost netmailer netproc netsetup useradmin msgform \ +msgstats readlog rcit stats sysoputil citmail netpoll mailinglist + +# +# + +citadel: ipc_c_tcp.o citadel.o rooms.o routines.o routines2.o messages.o \ + commands.o client_chat.o + $(CC) $(CFLAGS) ipc_c_tcp.o citadel.o rooms.o routines.o routines2.o \ + messages.o commands.o client_chat.o $(LFLAGS) -o citadel + +netpoll: netpoll.c config.o ipc_c_tcp.o + $(CC) $(CFLAGS) netpoll.c config.o ipc_c_tcp.o $(LFLAGS) -o netpoll + +ipc_c_tcp.o: ipc_c_tcp.c sysdep.h + $(CC) $(CFLAGS) -c ipc_c_tcp.c + +ipc_c_socks4.o: ipc_c_socks4.c sysdep.h + $(CC) $(CFLAGS) -c ipc_c_socks4.c + +citadel.o: citadel.c axdefs.h citadel.h + $(CC) -O $(CFLAGS) -c citadel.c + +rooms.o: rooms.c citadel.h + $(CC) -O $(CFLAGS) -c rooms.c + +messages.o: messages.c citadel.h + $(CC) -O $(CFLAGS) -c messages.c + +commands.o: commands.c citadel.h + $(CC) -O $(CFLAGS) -c commands.c + +routines.o: routines.c citadel.h + $(CC) -O $(CFLAGS) -c routines.c + +routines2.o: routines2.c citadel.h + $(CC) -O $(CFLAGS) -c routines2.c + +client_chat.o: client_chat.c citadel.h + $(CC) -O $(CFLAGS) -c client_chat.c + + +# +# + +citserver: citserver.o user_ops.o support.o room_ops.o file_ops.o \ + msgbase.o config.o sysdep.o locate_host.o serv_chat.o \ + hooks.o housekeeping.o database.o control.o + $(CC) $(CFLAGS) citserver.o user_ops.o room_ops.o file_ops.o support.o \ + msgbase.o config.o sysdep.o locate_host.o serv_chat.o \ + hooks.o housekeeping.o database.o control.o \ + $(LFLAGS) $(SERVER_LFLAGS) -o citserver + +citserver.o: citserver.c citadel.h + $(CC) $(CFLAGS) -D_REENTRANT -c citserver.c + +user_ops.o: user_ops.c citadel.h + $(CC) $(CFLAGS) -D_REENTRANT -c user_ops.c + +room_ops.o: room_ops.c citadel.h + $(CC) $(CFLAGS) -D_REENTRANT -c room_ops.c + +file_ops.o: file_ops.c citadel.h + $(CC) $(CFLAGS) -D_REENTRANT -c file_ops.c + +support.o: support.c citadel.h + $(CC) $(CFLAGS) -D_REENTRANT -c support.c + +msgbase.o: msgbase.c citadel.h + $(CC) $(CFLAGS) -D_REENTRANT -c msgbase.c + +locate_host.o: locate_host.c citadel.h + $(CC) $(CFLAGS) -D_REENTRANT -c locate_host.c + +serv_chat.o: serv_chat.c citadel.h + $(CC) $(CFLAGS) -D_REENTRANT -c serv_chat.c + +hooks.o: hooks.c citadel.h + $(CC) $(CFLAGS) -D_REENTRANT -c hooks.c + +housekeeping.o: housekeeping.c citadel.h + $(CC) $(CFLAGS) -D_REENTRANT -c housekeeping.c + +database.o: database.c citadel.h + $(CC) $(CFLAGS) -D_REENTRANT -c database.c + +control.o: control.c citadel.h + $(CC) $(CFLAGS) -D_REENTRANT -c control.c + +config.o: config.c citadel.h axdefs.h + $(CC) -O $(CFLAGS) -D_REENTRANT -c config.c + +sysdep.o: sysdep.c citadel.h + $(CC) -O $(CFLAGS) -D_REENTRANT -c sysdep.c + +aidepost: aidepost.c config.o citadel.h + $(CC) -O $(CFLAGS) aidepost.c config.o $(LFLAGS) -o aidepost + +# +# 'netmailer' needs to run setuid because it generates headers for Internet +# mail. If it is not run setuid, all outgoing mail may always show as coming +# from your BBSUID rather than the actual sending user. +# +netmailer: netmailer.c internetmail.o config.o citadel.h + $(CC) -O $(CFLAGS) netmailer.c config.o internetmail.o $(LFLAGS) -o netmailer + chmod 4755 netmailer + +internetmail.o: internetmail.c + $(CC) -O $(CFLAGS) -c internetmail.c + +netproc: netproc.o config.o ipc_c_tcp.o citadel.h + $(CC) -O $(CFLAGS) netproc.o config.o ipc_c_tcp.o \ + $(LFLAGS) -o netproc + +netproc.o: netproc.c citadel.h + $(CC) -O $(CFLAGS) -c netproc.c + +citmail: citmail.c config.o internetmail.o citadel.h + # + # ###### IMPORTANT ###### + # To allow Citadel users to receive Internet mail, you must + # set this program to be your local mail delivery agent. + # + $(CC) -O $(CFLAGS) citmail.c config.o internetmail.o $(LFLAGS) -o citmail + chmod 4755 citmail + +mailinglist: mailinglist.c config.o internetmail.o citadel.h + $(CC) -O $(CFLAGS) mailinglist.c config.o internetmail.o \ + $(LFLAGS) -o mailinglist + +setup: setup.c citadel.h + $(CC) -O $(CFLAGS) setup.c $(CURSES) $(LFLAGS) -o setup + +netsetup: netsetup.c config.o citadel.h + $(CC) -O $(CFLAGS) netsetup.c config.o $(LFLAGS) -o netsetup + +whobbs: whobbs.c ipc_c_tcp.o + $(CC) -O $(CFLAGS) whobbs.c ipc_c_tcp.o $(LFLAGS) -o whobbs + +useradmin: useradmin.c config.o citadel.h axdefs.h + $(CC) -O $(CFLAGS) useradmin.c config.o $(CURSES) $(LFLAGS) -o useradmin + chmod 4750 useradmin + +msgform: msgform.c + $(CC) -O $(CFLAGS) msgform.c $(LFLAGS) -o msgform + +msgstats: msgstats.c config.o citadel.h + $(CC) -O $(CFLAGS) msgstats.c config.o $(LFLAGS) -o msgstats + +readlog: readlog.c config.o citadel.h + $(CC) -O $(CFLAGS) readlog.c config.o $(LFLAGS) -o readlog + +rcit: rcit.c config.o citadel.h + $(CC) -O $(CFLAGS) rcit.c config.o $(LFLAGS) -o rcit + # + # NOTE: TO RECEIVE CITADEL TRAFFIC VIA THE RCIT PROGRAM (ESPECIALLY + # IF YOU ARE GATEWAYING TO USENET) YOU MUST LINK RNEWS TO RCIT: + #ln -s rcit /usr/bin/rnews + # + +stats: stats.c config.o citadel.h + $(CC) -O $(CFLAGS) stats.c config.o $(LFLAGS) -o stats + +sysoputil: sysoputil.c config.o citadel.h + $(CC) -O $(CFLAGS) sysoputil.c config.o $(LFLAGS) -o sysoputil + chmod 4750 sysoputil + +citadel.h: sysdep.h sysconfig.h ipcdef.h server.h + touch citadel.h + +clean: + find . -name \*.[o] -print -exec rm -f {} \; + rm -f sysdep.h diff --git a/citadel/README.txt b/citadel/README.txt new file mode 100644 index 000000000..eacd00891 --- /dev/null +++ b/citadel/README.txt @@ -0,0 +1,183 @@ + Citadel/UX release notes -- version 5.01 ("Scooby") + + Major overhaul. The server is now multithreaded; you run one copy of it +when you bring up your system, rather than having inetd start a separate +server process for each session. + + Access level 0, which was formerly "marked for deletion," has been changed +to "deleted." When a user record is marked access level 0, it is then +considered a vacant space in the userlog rather than a user entry. When +new users are added to the system, the server first searches for these vacant +slots before appending to the end of the file. + + The setup program has been overhauled. It can now operate in three different +modes: a curses-based mode, a mode based on Savio Lam's "dialog" program, and +of course dumb-terminal mode. + + All system messages (hello, newuser, etc.) can now be edited from a client +program. We now also have support for graphics images, including graphics +tied to various system objects (such as pictures of each user). + + Much more support for server graphics is in place. This is currently used +in WebCit; expect other clients to do more with graphics in the future. + + There really is too much to list here. More documentation will be written +as time permits. + + + Citadel/UX release notes -- version 4.11 + + Smarter usage of external editors. EDITOR_EXIT is no longer needed. Instead, +the program examines temp files before and after they are edited, in order to +determine whether the user saved the file. Modified files are saved, unchanged +files are aborted. + + External editors work for Bio and Room Info files as well. + + The TCP/IP based client (citatcp, linked against ipc_c_tcp.o) now supports +connection to a Citadel server from behind a SOCKS v4 firewall without having +to "socksify" the program first. + + Added a few more commands to the chat server. + + + Citadel/UX release notes -- version 4.10 + + Floors now fully supported in both client and server. + + Now supports "bio" files for each user - free-form text files in which +users may record personal info for others to browse. + + <.G>oto and <.S>kip now have more advanced logic for partial matches. A +left-aligned match carries a heavier weight than a mid-string match. For +example, <.G> TECH would prefer "Tech Area>" over "Biotech/Ethics>". + + + Citadel/UX release notes -- version 4.03 + + There are now commands available for users with their own copy of the client +to upload and download directly to their local disk, without having to use +a protocol such as Zmodem. These commands are disabled by default, but can +easily be enabled by changing citadel.rc. + + Multiuser hat is now fully integrated into both the client and server. + + New

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 +in addition to SysV-style . 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 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 ile elete -- delete it + <.A>ide ile ove -- move it to another room + <.A>ide ile end -- send it over the network + + Changed the way the main Citadel program sends network mail, both to other +Citadels and through the RFC822 gateway. Everything now gets fed through +netproc, which is responsible for figuring out the routing and doing the +actual processing. It also gets rid of quite a bit of redundant code. + + + Citadel/UX release notes -- version 3.23 + + NOTE: if you are upgrading to 3.23 from another 3.2x release, you MUST run +setup to bring your data files up to date. Run setup and answer "no" when it +asks you if you want to create the various files. It will see your old files +and bring them up to date. (If you are doing a new installation, of course, +you'll be creating all new files anyway.) + + The built-in message editor has been completely rewritten, and is now the +preferable editor to use. It generates messages that will be formatted to +the reader's screen width, as in other implementations of Citadel (and as +Citadel/UX used to do until a few versions ago -- there was a need to put +that functionality back in). + + Users are now prompted for their screen width AND height. + + We can now talk to C86NET compliant networks. Get the "cit86net" package +if you wish to do this. + + Rooms can now optionally be "read only," which means that only Aides and +utilities such as the networker and aidepost can post to the room. + + Kernel-based file locking is now fully supported for the purpose of locking +the message base during writes. If your kernel supports the flock() system +call, you can tell the compiler to use it by setting a flag in the Makefile. +If your kernel supports file locking using fcntl(), this will automatically be +detected and utilized. If your kernel supports neither, the program will use +a fully portable (but less reliable) file locking scheme. + + An external editor is no longer required for the .AI command. + + + Citadel/UX release notes -- version 3.22 + + The "setup" program is now a curses-based, full-screen utility that's very +easy to use. Of course, if you have trouble compiling curses-based programs +on your system, you can compile it to run the old way. + + + Citadel/UX release notes -- version 3.21 + + I'm now running my system under Linux, since that seems to be what everyone +is using these days. As a result, you'll find that version 3.21 will compile +very cleanly under Linux. + + + Citadel/UX release notes -- version 3.20 + + Lots of improvements and new features are here. It seems that assorted +hacks and variations of Citadel/UX have percolated around the country -- +this 3.2 release should supersede them and get everyone running (hopefully) +off the same code. I've looked around at the various mods people have made +to Citadel/UX and tried to implement the most-often-added and most-requested +features to the stock distribution. If there's a feature you want/need that +still isn't here, drop me a line and I'll see what I can do about adding it +to the next release. I can be contacted at ajc@uncnsrd.mt-kisco.ny.us or +simply log on to my BBS at uncnsrd.mt-kisco.ny.us (internet) or 914-244-3252 +(dialup). diff --git a/citadel/aidepost.c b/citadel/aidepost.c new file mode 100644 index 000000000..7a9f96bbd --- /dev/null +++ b/citadel/aidepost.c @@ -0,0 +1,66 @@ +/* aidepost.c + * This is just a little hack to copy standard input to a message in Aide> + * v1.6 + */ + +#include +#include +#include +#include +#include "citadel.h" + +void get_config(); +struct config config; + +void make_message(filename) +char *filename; { + FILE *fp; + int a; + long bb,cc,now; + time(&now); + fp=fopen(filename,"wb"); if (fp==NULL) exit(22); + putc(255,fp); + putc(MES_NORMAL,fp); + putc(1,fp); + fprintf(fp,"Proom_aide"); putc(0,fp); + fprintf(fp,"T%ld",now); putc(0,fp); + fprintf(fp,"ACitadel"); putc(0,fp); + fprintf(fp,"OAide"); putc(0,fp); + fprintf(fp,"N%s",NODENAME); putc(0,fp); + putc('M',fp); + bb=ftell(fp); + while (a=getc(stdin), a>0) { + if (a!=8) putc(a,fp); + else { + cc=ftell(fp); + if (cc!=bb) fseek(fp,(-1L),1); + } + } + putc(0,fp); + putc(0,fp); + putc(0,fp); + fclose(fp); + } + +void main(argc,argv) +int argc; +char *argv[]; +{ + char tempbase[32]; + char temptmp[64]; + char tempspool[64]; + char movecmd[256]; + + get_config(); + sprintf(tempbase,"ap.%d",getpid()); + sprintf(temptmp,"/tmp/%s", tempbase); + sprintf(tempspool,"./network/spoolin/%s", tempbase); + make_message(temptmp); + + sprintf(movecmd, "/bin/mv %s %s", temptmp, tempspool); + system(movecmd); + + execlp("./netproc","netproc",NULL); + fprintf(stderr,"aidepost: could not run netproc\n"); + exit(1); + } diff --git a/citadel/axdefs.h b/citadel/axdefs.h new file mode 100644 index 000000000..a32254e79 --- /dev/null +++ b/citadel/axdefs.h @@ -0,0 +1,20 @@ +#ifndef AXDEFS + +char *axdefs[]={ + "Deleted", + "New User", + "Problem User", + "Local User", + "Network User", + "Preferred User", + "Aide", + "Sysop" + }; + +#define AXDEFS 1 + +#else + +extern char *axdefs[]; + +#endif diff --git a/citadel/citadel.c b/citadel/citadel.c new file mode 100644 index 000000000..73c507129 --- /dev/null +++ b/citadel/citadel.c @@ -0,0 +1,1340 @@ +/* + * Citadel/UX + * + * citadel.c - Main source file. + * + */ + +#include "sysdep.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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 bandon room cmd */ +long maxmsgnum; /* used for 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 oto doesn't make us + * walk around in circles + */ + remove_march(room_name,0); + } + + sort_march_list(); + + if (march!=NULL) { + strcpy(next_room,march->march_name); + } + else { + strcpy(next_room,"_BASEROOM_"); + } + remove_march(next_room,0); + dotgoto(next_room,1); + } + +/* + * forget all rooms on a given floor + */ +void forget_all_rooms_on(ffloor) +int ffloor; { + char buf[256]; + struct march *flist,*fptr; + + printf("Forgetting all rooms on %s...\r",&floorlist[ffloor][0]); + fflush(stdout); + sprintf(buf,"LKRA %d",ffloor); + serv_puts(buf); + serv_gets(buf); + if (buf[0]!='1') { + printf("%-72s\n",&buf[4]); + return; + } + flist = NULL; + while (serv_gets(buf), strcmp(buf,"000")) { + fptr = (struct march *) malloc(sizeof(struct march)); + fptr->next = flist; + flist = fptr; + extract(fptr->march_name,buf,0); + } + while (flist != NULL) { + sprintf(buf,"GOTO %s",flist->march_name); + serv_puts(buf); + serv_gets(buf); + if (buf[0]=='2') { + serv_puts("FORG"); + serv_gets(buf); + } + fptr = flist; + flist = flist->next; + free(fptr); + } + printf("%-72s\r",""); + } + + +/* + * routine called by gotofloor() to move to a new room on a new floor + */ +void gf_toroom(towhere,mode) +char *towhere; +int mode; { + int floor_being_left; + + floor_being_left = curr_floor; + + if (mode == GF_GOTO) { /* <;G>oto mode */ + updatels(); + dotgoto(towhere,1); + } + + if (mode == GF_SKIP) { /* <;S>kip mode */ + dotgoto(towhere,1); + remove_march("_FLOOR_",floor_being_left); + } + + if (mode == GF_ZAP) { /* <;Z>ap mode */ + dotgoto(towhere,1); + remove_march("_FLOOR_",floor_being_left); + forget_all_rooms_on(floor_being_left); + } + } + + +/* + * go to a new floor + */ +void gotofloor(towhere,mode) +char *towhere; +int mode; { + int a,tofloor; + struct march *mptr; + char buf[256],targ[256]; + + if (floorlist[0][0]==0) load_floorlist(); + tofloor = (-1); + for (a=0; a<128; ++a) if (!strucmp(&floorlist[a][0],towhere)) + tofloor = a; + + if (tofloor<0) { + for (a=0; a<128; ++a) { + if (!struncmp(&floorlist[a][0],towhere,strlen(towhere))) { + tofloor = a; + } + } + } + + if (tofloor<0) { + for (a=0; a<128; ++a) + if (pattern(towhere,&floorlist[a][0])>0) + tofloor = a; + } + + if (tofloor<0) { + printf("No floor '%s'.\n",towhere); + return; + } + + for (mptr = march; mptr != NULL; mptr = mptr->next) { + if ((mptr->march_floor) == tofloor) + gf_toroom(mptr->march_name,mode); + return; + } + + strcpy(targ,""); + sprintf(buf,"LKRA %d",tofloor); + serv_puts(buf); + serv_gets(buf); + if (buf[0]=='1') while (serv_gets(buf), strcmp(buf,"000")) { + if ((extract_int(buf,2)==tofloor)&&(strlen(targ)==0)) + extract(targ,buf,0); + } + if (strlen(targ)>0) { + gf_toroom(targ,mode); + } + else { + printf("There are no rooms on '%s'.\n",&floorlist[tofloor][0]); + } + } + + +/* + * forget all rooms on current floor + */ +void forget_this_floor() { + + if (curr_floor == 0) { + printf("Can't forget this floor.\n"); + return; + } + + if (floorlist[0][0]==0) load_floorlist(); + printf("Are you sure you want to forget all rooms on %s? ", + &floorlist[(int)curr_floor][0]); + if (yesno()==0) return; + + gf_toroom("_BASEROOM_",GF_ZAP); + } + + +/* + * Figure out the physical screen dimensions, if we can + */ +void check_screen_dims() { +#ifdef TIOCGWINSZ + struct { + unsigned short height; /* rows */ + unsigned short width; /* columns */ + unsigned short xpixels; + unsigned short ypixels; /* pixels */ + } xwinsz; + + if (have_xterm) { /* dynamically size screen if on an xterm */ + if ( ioctl(0, TIOCGWINSZ, &xwinsz) == 0 ) { + if (xwinsz.height) screenheight = (int) xwinsz.height; + if (xwinsz.width) screenwidth = (int) xwinsz.width; + } + } +#endif + } + + +/* + * set floor mode depending on client, server, and user settings + */ +void set_floor_mode() { + if (serv_info.serv_ok_floors == 0) { + floor_mode = 0; /* Don't use floors if the server */ + } /* doesn't support them! */ + else { + if (rc_floor_mode == RC_NO) { /* never use floors */ + floor_mode = 0; + } + if (rc_floor_mode == RC_YES) { /* always use floors */ + floor_mode = 1; + } + if (rc_floor_mode == RC_DEFAULT) { /* user choice */ + floor_mode = ((userflags & US_FLOORS) ? 1 : 0); + } + } + } + +/* + * Set or change the user's password + */ +int set_password() { + char pass1[20]; + char pass2[20]; + char buf[256]; + + if (strlen(rc_password) > 0) { + strcpy(pass1,rc_password); + strcpy(pass2,rc_password); + } + else { + IFNEXPERT formout("changepw"); + newprompt("Enter a new password: ", pass1, -19); + newprompt("Enter it again to confirm: ", pass2, -19); + } + if (!strucmp(pass1,pass2)) { + sprintf(buf,"SETP %s",pass1); + serv_puts(buf); + serv_gets(buf); + printf("%s\n",&buf[4]); + return(0); + } + else { + printf("*** They don't match... try again.\n"); + return(1); + } + } + + +/* + * Display list of users currently logged on to the server + */ +void who_is_online(int longlist) +{ + char buf[128], username[128], roomname[128], fromhost[128], flags[128]; + char tbuf[128], timestr[128], idlebuf[128]; + long timenow=0; + long idletime; + + printf("FLG ### User Name Room From host\n"); + printf("--- --- ------------------------- -------------------- ------------------------\n"); + if (longlist) + { + serv_puts("TIME"); + serv_gets(tbuf); + extract(timestr, tbuf, 1); + timenow = atol(timestr); + } + serv_puts("RWHO"); + serv_gets(buf); + if (buf[0]=='1') + { + while(serv_gets(buf), strcmp(buf,"000")) + { + extract(username,buf,1); + extract(roomname,buf,2); + extract(fromhost,buf,3); + extract(idlebuf, buf,5); + extract(flags,buf,7); + printf("%-3s %-3d %-25s %-20s %-24s\n", + flags, extract_int(buf,0), username, + roomname, fromhost); + if (longlist) + { + idletime = atol(idlebuf); + printf("Idle time: %ld seconds\n", timenow-idletime); + } + } + } +} + +void enternew(char *desc, char *buf, int maxlen) +{ + char bbb[128]; + sprintf(bbb, "Enter in your new %s: ", desc); + newprompt(bbb, buf, maxlen); +} + +/* + * main + */ +void main(argc,argv) +int argc; +char *argv[]; { +int a,b,mcmd; +int termn8 = 0; +char aaa[100],bbb[100],ccc[100],eee[100]; /* general purpose variables */ +char argbuf[32]; /* command line buf */ + + +load_command_set(); /* parse the citadel.rc file */ +sttybbs(SB_SAVE); /* Store the old terminal parameters */ +sttybbs(SB_NO_INTR); /* Install the new ones */ +signal(SIGINT,SIG_IGN); +signal(SIGQUIT,SIG_IGN); +signal(SIGHUP,dropcarr); /* Cleanup gracefully if carrier is dropped */ +signal(SIGTERM,dropcarr); /* Cleanup gracefully if terminated */ + +send_ansi_detect(); +printf("Attaching to server...\r"); +fflush(stdout); +attach_to_server(argc,argv); + +cls(); +color(7); +serv_gets(aaa); +if (aaa[0]!='2') { + printf("%s\n",&aaa[4]); + logoff(atoi(aaa)); + } +get_serv_info(); + +printf("%-22s\n%s\n%s\n",serv_info.serv_software,serv_info.serv_humannode, + serv_info.serv_bbs_city); +screenwidth = 80; /* default screen dimensions */ +screenheight = 24; + +printf(" pause next stop\n"); +printf(" ctrl-s ctrl-o ctrl-c\n\n"); +formout("hello"); /* print the opening greeting */ +printf("\n"); + + /* if we're not the login shell, try auto-login */ +if (getppid()!=1) { + serv_puts("AUTO"); + serv_gets(aaa); + if (aaa[0]=='2') { + load_user_info(&aaa[4]); + goto PWOK; + } + } + +look_for_ansi(); + +GSTA: termn8=0; newnow=0; + do { + if (strlen(rc_username) > 0) { + strcpy(fullname,rc_username); + } + else { + newprompt("Enter your name: ",fullname,29); + } + strproc(fullname); + if (!strucmp(fullname,"new")) { /* just in case */ + printf("Please enter the name you wish to log in with.\n"); + } + } while( + (!strucmp(fullname,"bbs")) + || (!strucmp(fullname,"new")) + || (strlen(fullname)==0) ); + + if (!strucmp(fullname,"off")) { + mcmd=29; + goto TERMN8; + } + + /* sign on to the server */ + sprintf(aaa,"USER %s",fullname); + serv_puts(aaa); + serv_gets(aaa); + if (aaa[0]!='3') goto NEWUSR; + + /* password authentication */ + if (strlen(rc_password)>0) { + strcpy(eee,rc_password); + } + else { + newprompt("\rPlease enter your password: ",eee,-19); + } + strproc(eee); + sprintf(aaa,"PASS %s",eee); + serv_puts(aaa); + serv_gets(aaa); + if (aaa[0]=='2') { + load_user_info(&aaa[4]); + goto PWOK; + } + + printf("<< wrong password >>\n"); + if (strlen(rc_password)>0) logoff(0); + goto GSTA; + +NEWUSR: if (strlen(rc_password)==0) { + printf("No record. Enter as new user? "); + if (yesno()==0) goto GSTA; + } + + sprintf(aaa,"NEWU %s",fullname); + serv_puts(aaa); + serv_gets(aaa); + if (aaa[0]!='2') { + printf("%s\n",aaa); + goto GSTA; + } + load_user_info(&aaa[4]); + + while (set_password() != 0) ;; + newnow=1; + + enter_config(1); + + +PWOK: printf("%s\nAccess level: %d (%s)\nUser #%ld / Call #%d\n", + fullname,axlevel,axdefs[(int)axlevel], + usernum,timescalled); + + serv_puts("CHEK"); + serv_gets(aaa); + if (aaa[0]=='2') { + b = extract_int(&aaa[4],0); + if (b==1) printf("*** You have a new private message in Mail>\n"); + if (b>1) printf("*** You have %d new private messages in Mail>\n",b); + + if ((axlevel>=6) && (extract_int(&aaa[4],2)>0)) { + printf("*** Users need validation\n"); + } + + if (extract_int(&aaa[4],1)>0) { + printf("*** Please register.\n"); + formout("register"); + entregis(); + } + } + + /* Make up some temporary filenames for use in various parts of the + * program. Don't mess with these once they've been set, because we + * will be unlinking them later on in the program and we don't + * want to delete something that we didn't create. */ + sprintf(temp,"/tmp/citA%d",getpid()); + sprintf(temp2,"/tmp/citB%d",getpid()); + sprintf(tempdir,"/tmp/citC%d",getpid()); + + /* Get screen dimensions. First we go to a default of 80x24. Then + * we try to get the user's actual screen dimensions off the server. + * However, if we're running on an xterm, all this stuff is + * irrelevant because we're going to dynamically size the screen + * during the session. + */ + screenwidth = 80; + screenheight = 24; + serv_puts("GETU"); + serv_gets(aaa); + if (aaa[0]=='2') { + screenwidth = extract_int(&aaa[4],0); + screenheight = extract_int(&aaa[4],1); + } + if (getenv("TERM")!=NULL) if (!strcmp(getenv("TERM"),"xterm")) { + have_xterm = 1; + } +#ifdef TIOCGWINSZ + check_screen_dims(); +#endif + + set_floor_mode(); + + + /* Enter the lobby */ + dotgoto("_BASEROOM_",1); + +/* Main loop for the system... user is logged in. */ + strcpy(ugname,""); + uglsn = 0L; + + if (newnow==1) readmsgs(3,1,5); + else readmsgs(1,1,0); + +do { /* MAIN LOOP OF PROGRAM */ + signal(SIGINT,SIG_IGN); + signal(SIGQUIT,SIG_IGN); + mcmd=getcmd(argbuf); + +#ifdef TIOCGWINSZ + check_screen_dims(); +#endif + + if (termn8==0) switch(mcmd) { + case 1: formout("help"); + break; + case 4: entmsg(0,0); + break; + case 36: entmsg(0,1); + break; + case 46: entmsg(0,2); + break; + case 78: newprompt("What do you want your username to be? ", aaa, 32); + sprintf(bbb, "ENT0 2|0|0|0|%s", aaa); + serv_puts(bbb); + serv_gets(aaa); + if (strncmp("200", aaa, 3)) + printf("\n%s\n", aaa); + else + entmsg(0, 0); + break; + case 5: updatels(); + gotonext(); + break; + case 47: updatelsa(); + gotonext(); + break; + case 58: updatelsa(); + dotgoto("_MAIL_",1); + break; + case 20: updatels(); + case 52: dotgoto(argbuf,0); + break; + case 10: readmsgs(0,1,0); + break; + case 9: readmsgs(3,1,5); + break; + case 13: readmsgs(1,1,0); + break; + case 11: readmsgs(0,(-1),0); + break; + case 12: readmsgs(2,(-1),0); + break; + case 71: readmsgs(3, 1, atoi(argbuf)); + break; + case 7: forget(); break; + case 18: subshell(); break; + case 38: updatels(); + entroom(); break; + case 22: killroom(); break; + case 32: userlist(); break; + case 27: invite(); break; + case 28: kickout(); break; + case 23: editthisroom(); break; + case 14: roomdir(); break; + case 33: download(0); break; + case 34: download(1); break; + case 31: download(2); break; + case 43: download(3); break; + case 45: download(4); break; + case 55: download(5); break; + case 39: upload(0); break; + case 40: upload(1); break; + case 42: upload(2); break; + case 44: upload(3); break; + case 57: cli_upload(); break; + case 16: ungoto(); break; + case 24: whoknows(); break; + case 26: validate(); break; + case 29: updatels(); + termn8=1; + break; + case 30: updatels(); + termn8=1; + break; + case 48: enterinfo(); + break; + case 49: readinfo(); + break; + case 72: cli_image_upload("_userpic_"); + break; + case 73: cli_image_upload("_roompic_"); + break; + +case 74: + sprintf(aaa, "_floorpic_|%d", curr_floor); + cli_image_upload(aaa); + break; + +case 75: + enternew("roomname", aaa, 20); + sprintf(bbb, "RCHG %s", aaa); + serv_puts(bbb); + serv_gets(aaa); + if (strncmp("200",aaa, 3)) + printf("\n%s\n", aaa); + break; +case 76: + enternew("hostname", aaa, 25); + sprintf(bbb, "HCHG %s", aaa); + serv_puts(bbb); + serv_gets(aaa); + if (strncmp("200",aaa, 3)) + printf("\n%s\n", aaa); + break; +case 77: + enternew("username", aaa, 32); + sprintf(bbb, "UCHG %s", aaa); + serv_puts(bbb); + serv_gets(aaa); + if (strncmp("200",aaa, 3)) + printf("\n%s\n", aaa); + break; + +case 35: + set_password(); + break; + +case 21: + if (argbuf[0]==0) strcpy(aaa,"?"); + display_help(argbuf); + break; + +case 41: + formout("register"); + entregis(); + break; + +case 15: + printf("Are you sure (y/n)? "); + if (yesno()==1) { + updatels(); + a=0; + termn8=1; + } + break; + +case 6: + gotonext(); + break; + +case 3: chatmode(); + break; + +case 2: if (server_is_local) { + sttybbs(SB_RESTORE); +sprintf(aaa,"USERNAME=\042%s\042; export USERNAME; exec ./subsystem %ld %d %d", + fullname, + usernum,screenwidth,axlevel); + ka_system(aaa); + sttybbs(SB_NO_INTR); + } + else { + printf("*** Can't run doors when server is not local.\n"); + } + break; + +case 17: + who_is_online(0); + break; + +case 79: + who_is_online(1); + break; + +case 50: + enter_config(2); + break; + +case 37: + enter_config(0); + set_floor_mode(); + break; + +case 59: + enter_config(3); + set_floor_mode(); + break; + +case 60: + gotofloor(argbuf,GF_GOTO); + break; + +case 61: + gotofloor(argbuf,GF_SKIP); + break; + +case 62: + forget_this_floor(); + break; + +case 63: + create_floor(); + break; + +case 64: + edit_floor(); + break; + +case 65: + kill_floor(); + break; + +case 66: + enter_bio(); + break; + +case 67: + read_bio(); + break; + +case 25: + edituser(); + break; + +case 8: + knrooms(floor_mode); + printf("\n"); + break; + +case 68: + knrooms(2); + printf("\n"); + break; + +case 69: + misc_server_cmd(argbuf); + break; + +case 70: + edit_system_message(argbuf); + break; + +case 19: + listzrooms(); + printf("\n"); + break; + +case 51: + deletefile(); + break; + +case 53: + netsendfile(); + break; + +case 54: + movefile(); + break; + +case 56: + if (last_paged[0]) + sprintf(bbb, "Page who [%s]? ", last_paged); + else + sprintf(bbb, "Page who? "); + newprompt(bbb,aaa,30); + if (!aaa[0]) + strcpy(aaa, last_paged); + strproc(aaa); + newprompt("Message: ",bbb,69); + sprintf(ccc,"SEXP %s|%s",aaa,bbb); + serv_puts(ccc); + serv_gets(ccc); + if (!strncmp("200", ccc, 3)) + strcpy(last_paged, aaa); + printf("%s\n",&ccc[4]); + break; + + } /* end switch */ + } while(termn8==0); + +TERMN8: printf("%s logged out.\n",fullname); + while (march!=NULL) remove_march(march->march_name,0); + if (mcmd==30) + printf("\n\nType 'off' to hang up, or next user...\n"); + sprintf(aaa,"LOUT"); + serv_puts(aaa); + serv_gets(aaa); + if ((mcmd==29)||(mcmd==15)) { + formout("goodbye"); + logoff(0); + } + goto GSTA; + +} /* end main() */ + diff --git a/citadel/citadel.h b/citadel/citadel.h new file mode 100644 index 000000000..2ed573329 --- /dev/null +++ b/citadel/citadel.h @@ -0,0 +1,252 @@ +/* citadel.h + * main Citadel/UX header file + * see copyright.doc for copyright information + */ + +/* system customizations are in sysconfig.h */ +#include "sysdep.h" +#include "sysconfig.h" +#include "ipcdef.h" +#define CITADEL "Citadel/UX 5.02" +#define REV_LEVEL 501 +#define SERVER_TYPE 0 /* zero for stock Citadel/UX; other developers please + obtain SERVER_TYPE codes for your implementations */ + +#undef tolower +#define tolower(x) ( ((x>='A')&&(x<='Z')) ? (x+'a'-'A') : x ) +#define strucmp(lstr,rstr) struncmp(lstr,rstr,32767) +#define NEW_CONFIG + +/* + * The only typedef we do is an 8-bit unsigned, for screen dimensions. + * All other defs are done using standard C types. The code assumes that + * 'int' 'unsigned' and 'short' are at least 16 bits, and that 'long' is at + * least 32 bits. There are no endian dependencies in any of the Citadel + * programs. + */ +typedef unsigned char CIT_UBYTE; + +struct config { + char c_nodename[16]; /* UUCP and Citadel nodename */ + char c_fqdn[64]; /* Fully Qualified Domain Name */ + char c_humannode[21]; /* Long name of system */ + char c_phonenum[16]; /* Dialup number of system */ + int c_bbsuid; /* UID of the bbs-only user */ + int c_pwcrypt; /* password encryption seed */ + char c_creataide; /* room creator = room aide flag */ + int c_sleeping; /* watchdog timer setting */ + char c_initax; /* initial access level */ + char c_regiscall; /* call number to register on */ + char c_twitdetect; /* twit detect flag */ + char c_twitroom[20]; /* twit detect msg move to room */ + int c_defent; /* command generated by key */ + char c_moreprompt[80]; /* paginator prompt */ + char c_restrict; /* restrict Internet mail flag */ + long c_msgbase; /* size of message base */ + char c_bbs_city[32]; /* city and state you are located in*/ + char c_sysadm[26]; /* name of system administrator */ + char c_bucket_dir[15]; /* bit bucket for files... */ + int c_setup_level; /* what rev level we've setup to */ + int c_maxsessions; /* maximum concurrent sessions */ + char c_net_password[20]; /* system net password */ + int c_port_number; /* TCP port to run the server on */ + int c_ipgm_secret; /* Internal program authentication */ + }; + +#define NODENAME config.c_nodename +#define FQDN config.c_fqdn +#define HUMANNODE config.c_humannode +#define PHONENUM config.c_phonenum +#define BBSUID config.c_bbsuid +#define PWCRYPT config.c_pwcrypt +#define CREATAIDE config.c_creataide +#define INITAX config.c_initax +#define REGISCALL config.c_regiscall +#define TWITDETECT config.c_twitdetect +#define TWITROOM config.c_twitroom +#define MORE_PROMPT config.c_moreprompt +#define RESTRICT_INTERNET config.c_restrict +#define MM_FILELEN config.c_msgbase + +struct usersupp { /* User record */ + int USuid; /* userid (==BBSUID for bbs only) */ + char password[20]; /* password (for BBS-only users) */ + long lastseen[MAXROOMS]; /* Last message seen in each room */ + char generation[MAXROOMS]; /* Generation # (for private rooms) */ + char forget[MAXROOMS]; /* Forgotten generation number */ + long mailnum[MAILSLOTS]; /* Message #'s of each mail message */ + long mailpos[MAILSLOTS]; /* Disk positions of each mail */ + unsigned flags; /* See US_ flags below */ + int timescalled; /* Total number of logins */ + int posted; /* Number of messages posted (ever) */ + char fullname[26]; /* Name for Citadel messages & mail */ + char axlevel; /* Access level */ + CIT_UBYTE USscreenwidth; /* Screen width */ + CIT_UBYTE USscreenheight; /* Screen height */ + char spare[1]; /* spare bytes in the user account */ + long usernum; /* Eternal (non-recycled) user num */ + long lastcall; /* Last time the user called */ + char USname[30]; /* Real name (i.e. not a handle) */ + char USaddr[25]; /* Street address */ + char UScity[15]; /* Municipality */ + char USstate[3]; /* State or province */ + char USzip[10]; /* ZIP code */ + char USphone[11]; /* Voice telephone number */ + char USemail[32]; /* E-mail address (elsewhere) */ + int logged_time; /* (Not yet implemented) */ + int time_limit; /* (Not yet implemented) */ + }; + + +/* this is a mask for all of the bits the user is allowed to change */ +#define US_USER_SET (US_LASTOLD | US_EXPERT | US_UNLISTED | \ + US_NOPROMPT | US_DISAPPEAR | US_PAGINATOR | US_FLOORS) + +/**************************************************************************** + * This is the control record for the message base... + */ +struct CitControl { + long MMhighest; /* highest message number in file */ + unsigned MMflags; /* Global system flags */ + long MMnextuser; /* highest user number on system */ + }; + +/* Bits which may appear in CitControl.MMflags. Note that these don't + * necessarily pertain to the message base -- it's just a good place to + * store any global flags. + */ +#define MM_VALID 4 /* New users need validating */ + +/**************************************************************************** + * Information returned when a message is written to the message base + */ +struct smreturn { /* Return from the send_message() */ + long smnumber; /* Message number (if sent) */ + long smpos; /* Position in file (if sent) */ + int smerror; /* Error code */ + }; + +/**************************************************************************** + * Room records + */ +struct quickroom { + char QRname[20]; /* Max. len is 19, plus null term */ + char QRpasswd[10]; /* Only valid if it's a private rm */ + long QRroomaide; /* User number of room aide */ + long QRhighest; /* Highest message NUMBER in room */ + char QRgen; /* Generation number of room */ + unsigned QRflags; /* See flag values below */ + char QRdirname[15]; /* Directory name, if applicable */ + long QRinfo; /* Info file update relative to msgs*/ + char QRfloor; /* Which floor this room is on */ + }; + + +/* Private rooms are always flagged with QR_PRIVATE. If neither QR_PASSWORDED + * or QR_GUESSNAME is set, then it is invitation-only. Passworded rooms are + * flagged with both QR_PRIVATE and QR_PASSWORDED while guess-name rooms are + * flagged with both QR_PRIVATE and QR_GUESSNAME. DO NOT set all three flags. + * + ****************************************************************************/ + +struct fullroom { + long FRnum[MSGSPERRM]; /* Message NUMBERS */ + long FRpos[MSGSPERRM]; /* Message POSITIONS in master file */ + }; + +/* This structure is not 'circular'. When scrolling, each message moves + * down a slot, and the oldest one falls off the bottom. A null message is + * represented by the value 0L in both fields. + */ + +struct calllog { + char CLfullname[30]; /* Name of user */ + long CLtime; /* Date/time of record */ + unsigned CLflags; /* Info on record */ + }; + +#define CL_CONNECT 8 /* Connect to server */ +#define CL_LOGIN 16 /* CLfullname logged in */ +#define CL_NEWUSER 32 /* CLfullname is a new user */ +#define CL_BADPW 64 /* Bad attempt at CLfullname's pw */ +#define CL_TERMINATE 128 /* Logout - proper termination */ +#define CL_DROPCARR 256 /* Logout - dropped carrier */ +#define CL_SLEEPING 512 /* Logout - sleeping */ +#define CL_PWCHANGE 1024 /* CLfullname changed passwords */ + +/* Miscellaneous */ + +#define MES_NORMAL 65 /* Normal message */ +#define MES_ANON 66 /* "****" header */ +#define MES_AN2 67 /* "Anonymous" header */ + +#define M_ERROR (-1) /* Can't send message due to bad address */ +#define M_LOCAL 0 /* Local message, do no network processing */ +#define M_INTERNET 1 /* Convert msg and send as Internet mail */ +#define M_BINARY 2 /* Process recipient and send via C/UX net */ + +/****************************************************************************/ + +struct recentmsg { + char RMnodename[10]; + long RMnum; /* Number or time of message */ + }; + + +/**************************************************************************** + * + * Floor record. The floor number is implicit in its location in the file. + */ +struct floor { + unsigned short f_flags; /* flags */ + char f_name[256]; /* name of floor */ + int f_ref_count; /* reference count */ + int f_reserved; /* for future use */ + }; + +#define F_INUSE 1 /* floor is in use */ + + +/**************************************************************************** + * Values used internally for function call returns, etc. + */ + +#define NEWREGISTER 0 /* new user to register */ +#define REREGISTER 1 /* existing user reregistering */ + +#define READ_HEADER 2 +#define READ_MSGBODY 3 + +/* commands we can send to the sttybbs() routine */ +#define SB_NO_INTR 0 /* set to bbs mode, i/q disabled */ +#define SB_YES_INTR 1 /* set to bbs mode, i/q enabled */ +#define SB_SAVE 2 /* save settings */ +#define SB_RESTORE 3 /* restore settings */ + +#define NEXT_KEY 15 +#define STOP_KEY 3 + +/* server exit codes */ +#define EXIT_NORMAL 0 /* server terminated normally */ + /* 1 through 63 reserved for signals */ +#define EXIT_NULL 64 /* EOF on server command input */ + +/* citadel.rc stuff */ +#define RC_NO 0 /* always no */ +#define RC_YES 1 /* always yes */ +#define RC_DEFAULT 2 /* setting depends on user config */ + +/* keepalives */ +#define KA_NO 0 /* no keepalives */ +#define KA_YES 1 /* full keepalives */ +#define KA_CHAT 2 /* half keepalives (for chat mode) */ + +/* for <;G>oto and <;S>kip commands */ +#define GF_GOTO 0 /* <;G>oto floor mode */ +#define GF_SKIP 1 /* <;S>kip floor mode */ +#define GF_ZAP 2 /* <;Z>ap floor mode */ + +/* message transfer formats */ +#define MT_CITADEL 0 /* Citadel proprietary */ +#define MT_RFC822 2 /* RFC822 */ +#define MT_RAW 3 /* IGnet raw format */ diff --git a/citadel/citadel.lsm b/citadel/citadel.lsm new file mode 100644 index 000000000..9dc8d86a0 --- /dev/null +++ b/citadel/citadel.lsm @@ -0,0 +1,17 @@ +Begin3 +Title: Citadel/UX +Version: 5.01 +Entered-date: Tue Jun 2 11:25:59 EDT 1998 +Description: An implementation of the Citadel BBS program for Unix systems. + This is the de facto standard Unix version of Citadel, and is + now an advanced client/server application. +Keywords: Citadel Citadel/UX UNIX POSIX room-based BBS IGnet IGnet/Open +Author: Art Cancro +Maintained-by: Art Cancro +Primary-site: uncnsrd.mt-kisco.ny.us +Alternate-site: ftp.tux.org +Original-site: uncnsrd.mt-kisco.ny.us +Platform: Any reasonably compliant POSIX (Unix, Linux, etc.) type + system with a TCP/IP stack and POSIX Threads (pthreads). +Copying-Policy: GPL +End diff --git a/citadel/citadel.rc b/citadel/citadel.rc new file mode 100644 index 000000000..750185315 --- /dev/null +++ b/citadel/citadel.rc @@ -0,0 +1,207 @@ +# +# citadel.rc +# +# This file allows full customization of the user interface. +# +# The standard client looks for this file in: +# 1. $HOME/.citadelrc +# 2. /usr/local/lib/citadel.rc +# 3. /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

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 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,&! +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 ump commands which do the same thing, in +# order to please a wider audience of users. Remove one if you want to. +# +cmd=20,0,&Jump: +cmd=20,0,&.,&Goto: +# +cmd=21,0,&.,&Help: +cmd=22,1,&.,&Aide,&Kill this room +cmd=23,1,&.,&Aide,&Edit this room +cmd=24,1,&.,&Aide,&Who knows room +cmd=25,2,&.,&Aide,edit &User +cmd=26,2,&.,&Aide,&Validate new users +cmd=48,1,&.,&Aide,enter &Info file +cmd=27,1,&.,&Aide,&Room,&Invite user +cmd=28,1,&.,&Aide,&Room,&Kick out user +cmd=51,1,&.,&Aide,&File,&Delete +cmd=53,1,&.,&Aide,&File,&Send over net +cmd=54,1,&.,&Aide,&File,&Move +cmd=70,2,&.,&Aide,&Message edit: +cmd=29,0,&.,&Terminate,and &Quit +cmd=30,0,&.,&Terminate,and &Stay online +cmd=32,0,&.,&Read,&User listing +cmd=33,0,&.,&Read,&Textfile formatted +# +# Command 55 allows the user to save a downloaded file directly to the +# computer running the client software. It is appropriate for a copy of +# this client running on the user's own computer. It is NOT appropriate for +# public copies of the client that people will be dialing into. +# +#cmd=55,0,&.,&Read,&File +# +# Commands 34, 43, and 45 are appropriate for public copies of the client for +# dialup use. They transfer downloaded files to a temporary file and then +# send them along to a dialup user using the popular protocols. +# +cmd=34,0,&.,&Read,file using &Xmodem +cmd=43,0,&.,&Read,file using &Ymodem +cmd=45,0,&.,&Read,file using &Zmodem +cmd=31,0,&.,&Read,&File unformatted +# +cmd=13,0,&.,&Read,&New messages +cmd=12,0,&.,&Read,&Old msgs reverse +cmd=71,0,&.,read &Last: +cmd=9,0,&.,&Read,&Last five msgs +cmd=14,0,&.,&Read,&Directory +cmd=49,0,&.,&Read,&Info file +cmd=35,0,&.,&Enter,&Password +cmd=36,0,&.,&Enter,&ASCII message +cmd=37,0,&.,&Enter,&Configuration +cmd=38,0,&.,&Enter,a new &Room +cmd=39,0,&.,&Enter,&Textfile +cmd=40,0,&.,&Enter,file using &Xmodem +cmd=42,0,&.,&Enter,file using &Ymodem +cmd=44,0,&.,&Enter,file using &Zmodem +# +# Command 57 is the local-file-upload command for users with their own +# copy of the clientware. Commands 72-74 are for image uploads. +# +#cmd=57,0,&.,&Enter,&File +#cmd=72,0,&.,&Enter,&Image,user &Picture +#cmd=73,0,&.,&Enter,&Image,&Room banner +#cmd=74,0,&.,&Enter,&Image,&Floor label +# +cmd=41,0,&.,&Enter,re&Gistration +cmd=4,0,&.,&Enter,&Message +cmd=46,0,&.,&Enter,message with &Editor +# +cmd=59,0,&;,&Configure floor mode +cmd=60,0,&;,&Goto floor: +cmd=61,0,&;,&Skip to floor: +cmd=62,0,&;,&Zap (forget) floor +cmd=63,2,&;,&Aide,&Create floor +cmd=64,2,&;,&Aide,&Edit floor +cmd=65,2,&;,&Aide,&Kill floor +cmd=68,0,&;,&Known rooms +cmd=66,0,&.,&Enter,&Bio +cmd=67,0,&.,&Read,&Bio +# +# Command 69 allows the user to enter a server command directly. It is +# primarily for testing and not intended for general use. Usually there +# is no need to enable it. +cmd=69,0,&@Server command: +# +# end of command set configuration +# diff --git a/citadel/citmail.c b/citadel/citmail.c new file mode 100644 index 000000000..4dec56b21 --- /dev/null +++ b/citadel/citmail.c @@ -0,0 +1,796 @@ +/* + * citmail.c v4.0 + * + * This program may be used as a local mail delivery agent, which will allow + * all Citadel users to receive Internet e-mail. To enable this functionality, + * you must tell sendmail, smail, or whatever mailer you are using, that this + * program is your local mail delivery agent. This program is a direct + * replacement for lmail, deliver, or whatever. + * + * Usage: + * + * citmail - Deliver a message + * citmail -t - Address test mode (will not deliver) + * citmail -i - Run as an SMTP daemon (typically from inetd) + * + */ + +#include "sysdep.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 (lcrc) 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=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= 0) { + tmbuf->tm_mon = tval; + strcpy(&dbuf[cpos],&dbuf[cpos+3]); + } + + /* now the year */ + + for (a=0; 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=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=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 */ + strcpy(name,rfc822); + for (a=0; a 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 0) && (name[0]!='(') ) { + strcpy(&name[0],&name[1]); + } + strcpy(&name[0],&name[1]); + for (a=0; a 0) && (name[0]!=34) ) { + strcpy(&name[0],&name[1]); + } + strcpy(&name[0],&name[1]); + for (a=0; a 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') 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 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') 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; a0) { + 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; a0) { + 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; a0) && (buf[strlen(buf)-1]>0) && (buf[strlen(buf)-1]<32) ) { + buf[strlen(buf)-1] = 0; + } + + /* null-pad to allow some lazy compares */ + buf[strlen(buf)+1] = 0; + buf[strlen(buf)+2] = 0; + buf[strlen(buf)+3] = 0; + + if (!struncmp(buf, "QUIT", 4)) { + printf("221 Later, dude.\n"); + } + else if (!struncmp(buf, "HELP", 4)) { + printf("214 You think _you_ need help?\n"); + } + else if (!struncmp(buf, "HELO", 4)) { + printf("250 Howdy ho, Mr. Hankey!\n"); + } + else if (!struncmp(buf, "MAIL", 4)) { + printf("250 Sure, whatever...\n"); + } + + + else if (!struncmp(buf, "RCPT To: ", 9)) { + if (strlen(recp) > 0) { + printf("571 Multiple recipients not supported.\n"); + } + else { + strcpy(recp, &buf[9]); + if (haschar(recp, ',')) { + printf("571 Multiple recipients not supported.\n"); + strcpy(recp, ""); + } + else { + syslog(LOG_NOTICE,"recp: %s",recp); + for (a=0; a<2; ++a) { + alias(recp); + } + + /* did we alias it back to a remote address? */ + if ( (haschar(recp,'%')) + || (haschar(recp,'@')) + || (haschar(recp,'!')) ) { + + process_rfc822_addr(recp, user, node, name); + host_alias(node); + + /* If there are dots, it's an Internet host, so feed it + * back to an external mail transport agent such as sendmail. + */ + if (haschar(node, '.')) { + printf("571 Away with thee, spammer!\n"); + strcpy(recp, ""); + } + + /* Otherwise, we're dealing with Citadel mail. */ + else { + sprintf(recp, "%s!%s", node, user); + deliver_to_ignet = 1; + printf("250 IGnet recipient.\n"); + } + } + else { + printf("250 Local recipient.\n"); + } + + } + + } + } + + + + else if (!struncmp(buf, "RCPT", 4)) { + printf("501 Only 'To:' commands are supported.\n"); + } + else if (!struncmp(buf, "DATA", 4)) { + if (strlen(recp) > 0) { + printf("354 Sock it to me, baby...\n"); + fflush(stdout); + deliver(recp, is_test, deliver_to_ignet); + printf("250 Cool beans!\n"); + } + else { + printf("503 No recipient has been specified.\n"); + } + } + else { + printf("500 Huh?\n"); + } + + } while (struncmp(buf,"QUIT",4)); + } + + else { + /*** Non-SMTP delivery mode ***/ + syslog(LOG_NOTICE,"recp: %s",recp); + for (a=0; a<2; ++a) { + alias(recp); + } + + /* did we alias it back to a remote address? */ + if ( (haschar(recp,'%')) + || (haschar(recp,'@')) + || (haschar(recp,'!')) ) { + + process_rfc822_addr(recp, user, node, name); + host_alias(node); + + /* If there are dots, it's an Internet host, so feed it + * back to an external mail transport agent such as sendmail. + */ + if (haschar(node, '.')) { + sprintf(buf, SENDMAIL, recp); + system(buf); + exit(0); + } + + /* Otherwise, we're dealing with Citadel mail. */ + else { + sprintf(recp, "%s!%s", node, user); + deliver_to_ignet = 1; + } + + } + + deliver(recp, is_test, deliver_to_ignet); + } + + closelog(); + if (RUN_NETPROC) execlp("./netproc","netproc",NULL); + exit(0); + } + diff --git a/citadel/citserver.c b/citadel/citserver.c new file mode 100644 index 000000000..94de0e6eb --- /dev/null +++ b/citadel/citserver.c @@ -0,0 +1,1027 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; ausersupp.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, ""); + CC->cs_flags = CC->cs_flags|CS_STEALTH; + cprintf("%d Authenticated as an internal program.\n",OK); + } + else { + cprintf("%d Authentication failed.\n",ERROR); + } + } + + +/* + * Shut down the server + */ +void cmd_down(void) { + if (!CC->logged_in) { + cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN); + return; + } + + if (CC->usersupp.axlevel < 6) { + cprintf("%d You must be an Aide to shut down the server.\n", + ERROR+HIGHER_ACCESS_REQUIRED); + return; + } + + cprintf("%d Shutting down server. Goodbye.\n", OK); + master_cleanup(); + } + +/* + * Shut down the server + */ +void cmd_scdn(char *argbuf) +{ + int new_state; + + if (!CC->logged_in) { + cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN); + return; + } + + if (CC->usersupp.axlevel < 6) { + cprintf("%d You must be an Aide to schedule a shutdown.\n", + ERROR+HIGHER_ACCESS_REQUIRED); + return; + } + + new_state = extract_int(argbuf, 0); + if ((new_state == 0) || (new_state == 1)) { + ScheduledShutdown = new_state; + } + cprintf("%d %d\n", OK, ScheduledShutdown); + } + +/* + * main context loop + */ +void *context_loop(struct CitContext *con) +{ + char cmdbuf[256]; + int session_num; + + /* + * Wedge our way into the context table. + */ + InitMyContext(con); + + /* + * Initialize some variables specific to our context. + */ + CC->curr_rm = (-1); + CC->logged_in = 0; + CC->internal_pgm = 0; + CC->download_fp = NULL; + CC->upload_fp = NULL; + CC->cs_pid = con->client_socket; /* not necessarily portable */ + CC->FirstExpressMessage = NULL; + time(&CC->lastcmd); + time(&CC->lastidle); + strcpy(CC->lastcmdname, " "); + strcpy(CC->cs_clientname, "(unknown)"); + strcpy(CC->curr_user,""); + strcpy(CC->net_node,""); + sprintf(CC->temp,"/tmp/CitServer.%d.%d", getpid(), CC->cs_pid); + strcpy(CC->cs_room, ""); + strcpy(CC->cs_host, config.c_fqdn); + locate_host(CC->cs_host); + CC->cs_flags = 0; + CC->upload_type = UPL_FILE; + CC->dl_is_net = 0; + + session_num = session_count(); + CC->nologin = 0; + if ((config.c_maxsessions > 0)&&(session_num >= config.c_maxsessions)) + CC->nologin = 1; + + if (CC->nologin==1) { + cprintf("%d %s: Too many users are already online (maximum is %d)\n", + ERROR+MAX_SESSIONS_EXCEEDED, + config.c_nodename,config.c_maxsessions); + } + else { + cprintf("%d %s Citadel/UX server ready.\n",OK,config.c_nodename); + } + + lprintf(3, "citserver[%3d]: started.\n", CC->cs_pid); + hook_start_session(CC->cs_pid); + + do { + time(&CC->lastcmd); + if (client_gets(cmdbuf) < 1) cleanup(EXIT_NULL); + lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf); + hook_command_received(CC->cs_pid, cmdbuf); + + /* + * Let other clients see the last command we executed, but + * exclude NOOP because that would be boring. + */ + if (struncmp(cmdbuf, "NOOP", 4)) { + strcpy(CC->lastcmdname, " "); + strncpy(CC->lastcmdname, cmdbuf, 4); + time(&CC->lastidle); + } + + if ((struncmp(cmdbuf, "ENT0", 4)) && (struncmp(cmdbuf, "MESG", 4)) && (struncmp(cmdbuf, "MSGS", 4))) + { + CC->cs_flags &= ~CS_POSTING; + } + +/* + * This loop recognizes all server commands. + */ + + if (!struncmp(cmdbuf,"NOOP",4)) { + cprintf("%d%cok\n",OK,check_express()); + } + + else if (!struncmp(cmdbuf,"QUIT",4)) { + cprintf("%d Goodbye.\n",OK); + } + + else if (!struncmp(cmdbuf,"LOUT",4)) { + if (CC->logged_in) logout(CC); + cprintf("%d logged out.\n",OK); + } + + else if (!struncmp(cmdbuf,"USER",4)) { + cmd_user(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"PASS",4)) { + cmd_pass(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"NEWU",4)) { + cmd_newu(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"SETP",4)) { + cmd_setp(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"LRMS",4)) { + cmd_lrms(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"LKRA",4)) { + cmd_lkra(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"LKRN",4)) { + cmd_lkrn(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"LKRO",4)) { + cmd_lkro(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"LZRM",4)) { + cmd_lzrm(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"GETU",4)) { + cmd_getu(); + } + + else if (!struncmp(cmdbuf,"SETU",4)) { + cmd_setu(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"GOTO",4)) { + cmd_goto(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"MSGS",4)) { + cmd_msgs(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"WHOK",4)) { + cmd_whok(); + } + + else if (!struncmp(cmdbuf,"RDIR",4)) { + cmd_rdir(); + } + + else if (!struncmp(cmdbuf,"MSG0",4)) { + cmd_msg0(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"MSG2",4)) { + cmd_msg2(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"MSG3",4)) { + cmd_msg3(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"INFO",4)) { + cmd_info(); + } + + else if (!struncmp(cmdbuf,"SLRP",4)) { + cmd_slrp(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"INVT",4)) { + cmd_invt_kick(&cmdbuf[5],1); + } + + else if (!struncmp(cmdbuf,"KICK",4)) { + cmd_invt_kick(&cmdbuf[5],0); + } + + else if (!struncmp(cmdbuf,"GETR",4)) { + cmd_getr(); + } + + else if (!struncmp(cmdbuf,"SETR",4)) { + cmd_setr(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"GETA",4)) { + cmd_geta(); + } + + else if (!struncmp(cmdbuf,"SETA",4)) { + cmd_seta(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"ENT0",4)) { + cmd_ent0(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"ENT3",4)) { + cmd_ent3(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"RINF",4)) { + cmd_rinf(); + } + + else if (!struncmp(cmdbuf,"DELE",4)) { + cmd_dele(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"KILL",4)) { + cmd_kill(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"CRE8",4)) { + cmd_cre8(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"MOVE",4)) { + cmd_move(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"FORG",4)) { + cmd_forg(); + } + + else if (!struncmp(cmdbuf,"MESG",4)) { + cmd_mesg(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"EMSG",4)) { + cmd_emsg(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"GNUR",4)) { + cmd_gnur(); + } + + else if (!struncmp(cmdbuf,"GREG",4)) { + cmd_greg(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"VALI",4)) { + cmd_vali(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"EINF",4)) { + cmd_einf(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"LIST",4)) { + cmd_list(); + } + + else if (!struncmp(cmdbuf,"REGI",4)) { + cmd_regi(); + } + + else if (!struncmp(cmdbuf,"CHEK",4)) { + cmd_chek(); + } + + else if (!struncmp(cmdbuf,"DELF",4)) { + cmd_delf(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"MOVF",4)) { + cmd_movf(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"NETF",4)) { + cmd_netf(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"RWHO",4)) { + cmd_rwho(); + } + + else if (!struncmp(cmdbuf,"OPEN",4)) { + cmd_open(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"CLOS",4)) { + cmd_clos(); + } + + else if (!struncmp(cmdbuf,"UOPN",4)) { + cmd_uopn(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"UCLS",4)) { + cmd_ucls(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"READ",4)) { + cmd_read(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"WRIT",4)) { + cmd_writ(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"QUSR",4)) { + cmd_qusr(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"ECHO",4)) { + cmd_echo(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"OIMG",4)) { + cmd_oimg(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"MORE",4)) { + cmd_more(); + } + + else if (!struncmp(cmdbuf,"NETP",4)) { + cmd_netp(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"NDOP",4)) { + cmd_ndop(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"NUOP",4)) { + cmd_nuop(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"LFLR",4)) { + cmd_lflr(); + } + + else if (!struncmp(cmdbuf,"CFLR",4)) { + cmd_cflr(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"KFLR",4)) { + cmd_kflr(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"EFLR",4)) { + cmd_eflr(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"IDEN",4)) { + cmd_iden(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"IPGM",4)) { + cmd_ipgm(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"CHAT",4)) { + cmd_chat(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"PEXP",4)) { + cmd_pexp(); + } + + else if (!struncmp(cmdbuf,"SEXP",4)) { + cmd_sexp(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"EBIO",4)) { + cmd_ebio(); + } + + else if (!struncmp(cmdbuf,"RBIO",4)) { + cmd_rbio(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"LBIO",4)) { + cmd_lbio(); + } + + else if (!struncmp(cmdbuf,"STEL",4)) { + cmd_stel(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"TERM",4)) { + cmd_term(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf,"DOWN",4)) { + cmd_down(); + } + + else if (!struncmp(cmdbuf,"SCDN",4)) { + cmd_scdn(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf, "NSET", 4)) { + cmd_nset(&cmdbuf[5]); + } + + else if (!struncmp(cmdbuf, "UIMG", 4)) { + cmd_uimg(&cmdbuf[5]); + } + else if (!struncmp(cmdbuf, "UCHG", 4)) { + cmd_uchg(&cmdbuf[5]); + } + else if (!struncmp(cmdbuf, "TIME", 4)) { + cmd_time(&cmdbuf[5]); + } + else if (!struncmp(cmdbuf, "HCHG", 4)) { + cmd_hchg(&cmdbuf[5]); + } + else if (!struncmp(cmdbuf, "RCHG", 4)) { + cmd_rchg(&cmdbuf[5]); + } + else { + cprintf("%d Unrecognized or unsupported command.\n", + ERROR); + } + + } while(struncmp(cmdbuf,"QUIT",4)); + + cleanup(EXIT_NORMAL); + return(NULL); + } diff --git a/citadel/client_chat.c b/citadel/client_chat.c new file mode 100644 index 000000000..6316a138e --- /dev/null +++ b/citadel/client_chat.c @@ -0,0 +1,190 @@ +/* + * Citadel/UX + * + * client_chat.c -- front end for chat mode + * (the "single process" version - no more fork() anymore) + * + */ + +#include "sysdep.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef NEED_SELECT_H +#include +#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=2) { + extract(c_user,buf,0); + extract(c_text,buf,1); + if (num_parms(buf)>2) + { + extract(c_room,buf,2); + printf("Got room %s\n", c_room); + } + + if (strucmp(c_text,"NOOP")) { + if (!strcmp(c_user, fullname)) { + color(3); + } + else if (!strcmp(c_user,":")) { + color(1); + } + else { + color(2); + } + if (strcmp(c_user,last_user)) { + sprintf(buf,"%s: %s",c_user,c_text); + } + else { + sprintf(buf,"%40s",""); + sprintf(&buf[strlen(c_user)+2], + "%s",c_text); + } + while (strlen(buf)<79) strcat(buf," "); + if (strcmp(c_user,last_user)) { + printf("\r%79s\n",""); + strcpy(last_user,c_user); + } + printf("\r%s\n",buf); + fflush(stdout); + } + } + color(3); + printf("> %s",wbuf); + recv_complete_line = 0; + strcpy(buf,""); + } + } + } + + diff --git a/citadel/commands.c b/citadel/commands.c new file mode 100644 index 000000000..77cb8308d --- /dev/null +++ b/citadel/commands.c @@ -0,0 +1,885 @@ +/* + * Citadel/UX + * + * commands.c - front end for Citadel + * + * This version is the traditional command parser for room prompts. + * + */ + +#include "sysdep.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef POSIX_TERMIO +#include +#else +#include +#endif + +#ifdef NEED_SELECT_H +#include +#endif + + +#include +#include +#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 (iimax) printf("*** Must be no more than %d.\n",imax); + } while((iimax)); + 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; aommand 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=6) cmdax = 2; + + for (a=0; ac_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 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; ac_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 +#include +#include +#include +#include +#include "citadel.h" + +extern void get_config(void); +struct config config; +char bbs_home_directory[256]; +int home_specified = 0; + +void get_config(void) { + FILE *cfp; + + if (chdir( home_specified ? bbs_home_directory : BBSDIR ) != 0) { + fprintf(stderr, "Cannot start.\nThere is no Citadel installation in %s\n%s\n", + (home_specified ? bbs_home_directory : BBSDIR), + strerror(errno)); + exit(errno); + } + cfp=fopen("citadel.config","r"); + if (cfp==NULL) { + fprintf(stderr, "Cannot start.\n"); + fprintf(stderr, "There is no citadel.config in %s\n%s\n", + (home_specified ? bbs_home_directory : BBSDIR), + strerror(errno)); + exit(errno); + } + fread((char *)&config,sizeof(struct config),1,cfp); + fclose(cfp); + if ( (config.c_setup_level / 10) != (REV_LEVEL/10) ) { + fprintf(stderr, "config: Your data files are out of date. "); + fprintf(stderr, "Run setup to update them.\n"); + fprintf(stderr, + " This program requires level %d.%02d\n", + (REV_LEVEL / 100), (REV_LEVEL % 100) ); + fprintf(stderr, + " Data files are currently at %d.%02d\n", + (config.c_setup_level / 100), + (config.c_setup_level % 100) ); + exit(1); + } + } diff --git a/citadel/control.c b/citadel/control.c new file mode 100644 index 000000000..4dec1f0fa --- /dev/null +++ b/citadel/control.c @@ -0,0 +1,81 @@ +/* + * control.c + * + * This module handles states which are global to the entire server. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "citadel.h" +#include "server.h" +#include "proto.h" + +struct CitControl CitControl; + +/* + * get_control - read the control record into memory. + */ +void get_control() { + FILE *fp; + + /* Zero it out. If the control record on disk is missing or short, + * the system functions with all control record fields initialized + * to zero. + */ + bzero(&CitControl, sizeof(struct CitControl)); + fp = fopen("citadel.control", "rb"); + if (fp == NULL) return; + + fread(&CitControl, sizeof(struct CitControl), 1, fp); + fclose(fp); + } + +/* + * put_control - write the control record to disk. + */ +void put_control() { + FILE *fp; + + fp = fopen("citadel.control", "wb"); + if (fp != NULL) { + fwrite(&CitControl, sizeof(struct CitControl), 1, fp); + } + } + + +/* + * get_new_message_number() - Obtain a new, unique ID to be used for a message. + */ +long get_new_message_number() { + + begin_critical_section(S_CONTROL); + get_control(); + ++CitControl.MMhighest; + put_control(); + end_critical_section(S_CONTROL); + return(CitControl.MMhighest); + } + + +/* + * get_new_user_number() - Obtain a new, unique ID to be used for a user. + */ +long get_new_user_number() { + + begin_critical_section(S_CONTROL); + get_control(); + ++CitControl.MMnextuser; + put_control(); + end_critical_section(S_CONTROL); + return(CitControl.MMhighest); + } diff --git a/citadel/copyright.txt b/citadel/copyright.txt new file mode 100644 index 000000000..3f08f6b25 --- /dev/null +++ b/citadel/copyright.txt @@ -0,0 +1,30 @@ + Citadel/UX release 5.01 + +Copyright (c) 1987-1998 by: + Art Cancro + +Portions contributed by: + Brian Costello + + ------------------------------------------------------------------------------ + + The entire package is free software; you can redistribute + and/or modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + Feedback concerning Citadel/UX goes to: ajc@uncnsrd.mt-kisco.ny.us + + The home of Citadel/UX is UNCENSORED! BBS: + telnet://uncnsrd.mt-kisco.ny.us + http://uncnsrd.mt-kisco.ny.us + 914-244-3252 (modem) diff --git a/citadel/cux2ascii.c b/citadel/cux2ascii.c new file mode 100644 index 000000000..ce4bd6a56 --- /dev/null +++ b/citadel/cux2ascii.c @@ -0,0 +1,195 @@ +/* + * cux2ascii v2.3 + * see copyright.doc for copyright information + * + * This program is a filter which converts Citadel/UX binary message format + * to standard UseNet news format. Useful for Citadel<->News gateways. + */ + +#include +#include +#include +#include +#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 (posrc) 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\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; a0)); + msglen=ftell(tfp); + fclose(tfp); + + if (cuunbatch) { + printf("#! cuunbatch %ld\n",msglen); + } + else { + printf("#! rnews %ld\n",msglen); + } + + tfp=fopen(tflnm,"r"); + while(msglen--) putc(getc(tfp),stdout); + fclose(tfp); + unlink(tflnm); + } +exit(0); +} + +fpgetfield(fp,string) /* level-2 break out next null-terminated string */ +FILE *fp; +char string[]; +{ +int a,b; +strcpy(string,""); +a=0; + do { + b=getc(fp); + if (b<1) { string[a]=0; return(0); } + string[a]=b; + ++a; + } while(b!=0); + return(0); +} + +fmout(width,fp,mout) +int width; +FILE *fp,*mout; + { + int a,b,c,real,old; + char aaa[140]; + + strcpy(aaa,""); old=255; + c=1; /* c is the current pos */ +FMTA: old=real; a=getc(fp); real=a; + if (a<=0) goto FMTEND; + + if ( ((a==13)||(a==10)) && (old!=13) && (old!=10) ) a=32; + if ( ((old==13)||(old==10)) && (isspace(real)) ) { + fprintf(mout,"\n"); c=1; } + if (a>126) goto FMTA; + + if (a>32) { + if ( ((strlen(aaa)+c)>(width-1)) && (strlen(aaa)>(width-1)) ) + { fprintf(mout,"\n%s",aaa); c=strlen(aaa); aaa[0]=0; } + b=strlen(aaa); aaa[b]=a; aaa[b+1]=0; } + if (a==32) { if ((strlen(aaa)+c)>(width-1)) { + fprintf(mout,"\n"); + c=1; + } + fprintf(mout,"%s ",aaa); ++c; c=c+strlen(aaa); + strcpy(aaa,""); goto FMTA; } + if ((a==13)||(a==10)) { + fprintf(mout,"%s\n",aaa); c=1; + strcpy(aaa,""); goto FMTA; } + goto FMTA; + +FMTEND: fprintf(mout,"\n"); + return(0); +} + +xref(roomname,newsgroup) +char *roomname,*newsgroup; { + char tbuf[128]; + FILE *fp; + int commapos,a; + + strcpy(newsgroup,roomname); + fp=fopen("./network/rnews.xref","r"); + if (fp==NULL) return(1); + while (fgets(tbuf,128,fp)!=NULL) { + tbuf[strlen(tbuf)-1]=0; /* strip off the newline */ + a=strlen(tbuf); + while (a--) if (tbuf[a]==',') commapos=a; + tbuf[commapos]=0; + if (!strucmp(&tbuf[commapos+1],roomname)) + strcpy(newsgroup,tbuf); + } + fclose(fp); + return(0); + } diff --git a/citadel/database.c b/citadel/database.c new file mode 100644 index 000000000..d81726aff --- /dev/null +++ b/citadel/database.c @@ -0,0 +1,257 @@ +/* + * This file contains a set of abstractions that allow Citadel to plug into any + * record manager or database system for its data store. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; alen = dret.dsize; + tempcdb->ptr = dret.dptr; + return(tempcdb); + } + + +/* + * Free a cdbdata item (ok, this is really no big deal, but we might need to do + * more complex stuff with other database managers in the future). + */ +void cdb_free(struct cdbdata *cdb) { + free(cdb->ptr); + free(cdb); + } + + +/* + * Prepare for a sequential search of an entire database. (In the gdbm model, + * we do this by keeping an array dtkey[] of "the next" key for each session + * that is open. There is guaranteed to be no more than one traversal in + * progress per session at any given time.) + */ +void cdb_rewind(int cdb) { + + if (dtkey[CC->cs_pid].dptr != NULL) { + free(dtkey[CC->cs_pid].dptr); + } + + dtkey[CC->cs_pid] = gdbm_firstkey(gdbms[cdb]); + } + + +/* + * Fetch the next item in a sequential search. Returns a pointer to a + * cdbdata structure, or NULL if we've hit the end. + */ +struct cdbdata *cdb_next_item(int cdb) { + datum dret; + struct cdbdata *cdbret; + + + if (dtkey[CC->cs_pid].dptr == NULL) { /* end of file */ + return NULL; + } + + dret = gdbm_fetch(gdbms[cdb], dtkey[CC->cs_pid]); + if (dret.dptr == NULL) { /* bad read */ + free(dtkey[CC->cs_pid].dptr); + return NULL; + } + + cdbret = (struct cdbdata *) malloc(sizeof(struct cdbdata)); + cdbret->len = dret.dsize; + cdbret->ptr = dret.dptr; + + dtkey[CC->cs_pid] = gdbm_nextkey(gdbms[cdb], dtkey[CC->cs_pid]); + return(cdbret); + } diff --git a/citadel/dnetsetup b/citadel/dnetsetup new file mode 100755 index 000000000..46b296208 --- /dev/null +++ b/citadel/dnetsetup @@ -0,0 +1,178 @@ +#!/bin/sh + +# dnetsetup version 1.0 +# Copyright (c)1998 by Art Cancro / All rights reserved. +# This program is distributed under the terms of the GNU General Public +# License. See copyright.txt for more information. +# +# dnetsetup requires Savio Lam's "dialog" program to perform its interactive +# dialogs. This program is standard on most Linux systems, and should be +# easy enough to get on other systems. + +cleanup() { + rm -f $tempfile $tempfile2 $menucmd 2>/dev/null + exit + } + + +roomlist() { + node=$1 + + ./netsetup roomlist $1 >$tempfile 2>&1 + $DIALOG --textbox $tempfile $menuheight $menuwidth + } + + +unshare() { + node=$1 + + ./netsetup roomlist $node >$tempfile + numrooms=`cat $tempfile |wc -l` + sed s/\ /\\\\\ /g <$tempfile | sed s/$/\ \"\"/g >$tempfile2 + echo $DIALOG --menu \"Select the room you wish to stop sharing\" \ + $menuheight $menuwidth $numrooms \ + `cat $tempfile2` >$menucmd + if sh $menucmd 2>$tempfile; then + room_to_delete=`cat $tempfile` + + if $DIALOG --yesno "Are you sure you wish to stop sharing $room_to_delete ?" 5 $menuwidth ; then + if ./netsetup unshare $node "$room_to_delete" 2>$tempfile; then + $DIALOG --msgbox "$room_to_delete has been unshared." 5 $menuwidth + else + $DIALOG --textbox $tempfile $menuheight $menuwidth + fi + fi + fi + + } + +do_share() { + node=$1 + + if $DIALOG --inputbox "Enter name of room to share:" \ + $menuheight $menuwidth 2>$tempfile ; then + + room_to_share=`cat $tempfile` + if ./netsetup share $node "$room_to_share" 2>$tempfile; then + $DIALOG --msgbox "$room_to_share has been added." 5 $menuwidth + else + $DIALOG --textbox $tempfile $menuheight $menuwidth + fi + + fi + } + + +edit_this_node() { + node=$1 + choice=nothing_yet + + while [ $choice != EXIT ] + do + if $DIALOG --menu "Editing $node" $menuheight $menuwidth 4 \ + LIST "List currently shared rooms" \ + SHARE "Add a shared room" \ + UNSHARE "Stop sharing a room" \ + EXIT "Return to main menu" 2>$tempfile; then + choice=`cat $tempfile` + else + choice="EXIT" + fi + + [ $choice = "LIST" ] && roomlist $node + [ $choice = "SHARE" ] && do_share $node + [ $choice = "UNSHARE" ] && unshare $node + + done + } + + +edit_a_node() { + + ./netsetup nodelist >$tempfile + numnodes=`cat $tempfile |wc -l` + sed s/$/\ \"\"/g <$tempfile >$tempfile2 + echo $DIALOG --menu \"Select the node you wish to edit\" \ + $menuheight $menuwidth $numnodes \ + `cat $tempfile2` >$menucmd + if sh $menucmd 2>$tempfile; then + system_to_edit=`cat $tempfile` + edit_this_node $system_to_edit + else + true + fi + } + + + + + + +delete_network_nodes() { + + ./netsetup nodelist >$tempfile + numnodes=`cat $tempfile |wc -l` + sed s/$/\ \"\"/g <$tempfile >$tempfile2 + echo $DIALOG --menu \"Select the node you wish to delete\" \ + $menuheight $menuwidth $numnodes \ + `cat $tempfile2` >$menucmd + if sh $menucmd 2>$tempfile; then + system_to_delete=`cat $tempfile` + + if $DIALOG --yesno "Are you sure you wish to delete $system_to_delete ?" 5 $menuwidth ; then + if ./netsetup deletenode $system_to_delete 2>$tempfile; then + $DIALOG --msgbox "$system_to_delete has been deleted." 5 $menuwidth + else + $DIALOG --textbox $tempfile $menuheight $menuwidth + fi + fi + fi + + } + + +add_a_node() { + node=$1 + + if $DIALOG --inputbox "Enter name of new node:" \ + $menuheight $menuwidth 2>$tempfile ; then + + new_node=`cat $tempfile` + if ./netsetup addnode $new_node 2>$tempfile; then + $DIALOG --msgbox "$new_node has been created." 5 $menuwidth + else + $DIALOG --textbox $tempfile $menuheight $menuwidth + fi + + fi + } + + +# Main loop... +clear +tempfile=/tmp/dnetsetup.$$.1 +tempfile2=/tmp/dnetsetup.$$.2 +menucmd=/tmp/dnetsetup.$$.3 +menuheight=20 +menuwidth=72 +DIALOG=dialog +while true +do + if $DIALOG --menu "Citadel/UX Network Setup" $menuheight $menuwidth 4 \ + EDIT "View or change a network node" \ + ADD "Add a new network node" \ + DELETE "Delete a network node" \ + EXIT "Exit from Network Setup" \ + 2>$tempfile; then + choice=`cat $tempfile` + else + choice=EXIT + fi + rm -f $tempfile + + [ $choice = "EXIT" ] && cleanup + [ $choice = "DELETE" ] && delete_network_nodes + [ $choice = "EDIT" ] && edit_a_node + [ $choice = "ADD" ] && add_a_node + + done diff --git a/citadel/file_ops.c b/citadel/file_ops.c new file mode 100644 index 000000000..9d99dbfaf --- /dev/null +++ b/citadel/file_ops.c @@ -0,0 +1,712 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; aquickroom.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; aquickroom.QRdirname,filename); + if (access(pathname,0)!=0) { + cprintf("%d File '%s' not found.\n", + ERROR+FILE_NOT_FOUND,pathname); + return; + } + + for (a=0; a>./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; aquickroom.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; aquickroom.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; adownload_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; aupl_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; ausersupp.axlevel >= 6) { + sprintf(CC->upl_path, "./images/%s", basenm); + } + + if (!strucmp(basenm, "_userpic_")) { + sprintf(CC->upl_path, "./userpics/%ld.gif", + CC->usersupp.usernum); + } + + if ( (!strucmp(basenm, "_floorpic_")) && (CC->usersupp.axlevel >= 6) ) { + which_floor = extract_int(cmdbuf, 2); + sprintf(CC->upl_path, "./images/floor.%d.gif", which_floor); + } + + if ( (!strucmp(basenm, "_roompic_")) && (is_room_aide()) ) { + sprintf(CC->upl_path, "./images/room.%d.gif", CC->curr_rm); + } + + if (strlen(CC->upl_path) == 0) { + cprintf("%d Higher access required.\n", + ERROR+HIGHER_ACCESS_REQUIRED); + return; + } + + if (is_this_for_real == 0) { + cprintf("%d Ok to send image\n", OK); + return; + } + + CC->upload_fp = fopen(CC->upl_path,"wb"); + if (CC->upload_fp == NULL) { + cprintf("%d Cannot open %s: %s\n", + ERROR,CC->upl_path,strerror(errno)); + return; + } + cprintf("%d Ok\n",OK); + CC->upload_type = UPL_IMAGE; + } + + +/* + * close the download file + */ +void cmd_clos(void) { + char buf[256]; + + if (CC->download_fp == NULL) { + cprintf("%d You don't have a download file open.\n",ERROR); + return; + } + + fclose(CC->download_fp); + CC->download_fp = NULL; + + if (CC->dl_is_net == 1) { + CC->dl_is_net = 0; + sprintf(buf,"%s/network/spoolout/%s",BBSDIR,CC->net_node); + unlink(buf); + } + + cprintf("%d Ok\n",OK); + } + + +/* + * abort and upload + */ +void abort_upl(struct CitContext *who) +{ + if (who->upload_fp != NULL) { + fclose(who->upload_fp); + who->upload_fp = NULL; + unlink(CC->upl_path); + } + } + + + +/* + * close the upload file + */ +void cmd_ucls(char *cmd) +{ + FILE *fp; + long now; + + if (CC->upload_fp == NULL) { + cprintf("%d You don't have an upload file open.\n",ERROR); + return; + } + + fclose(CC->upload_fp); + CC->upload_fp = NULL; + + if ((!strucmp(cmd,"1")) && (CC->upload_type != UPL_FILE)) { + CC->upload_type = UPL_FILE; + cprintf("%d Upload completed.\n", OK); + return; + } + + if (!strucmp(cmd,"1")) { + cprintf("%d File '%s' saved.\n",OK,CC->upl_path); + fp = fopen(CC->upl_filedir,"a"); + if (fp==NULL) fp=fopen(CC->upl_filedir,"w"); + if (fp!=NULL) { + fprintf(fp,"%s %s\n",CC->upl_file,CC->upl_comment); + fclose(fp); + } + + /* put together an upload notice */ + time(&now); + fp=fopen(CC->temp,"wb"); + putc(255,fp); + putc(MES_NORMAL,fp); + putc(0,fp); + fprintf(fp,"Pcit%ld",CC->usersupp.usernum); putc(0,fp); + fprintf(fp,"T%ld",now); putc(0,fp); + fprintf(fp,"A%s",CC->curr_user); putc(0,fp); + fprintf(fp,"O%s",CC->quickroom.QRname); putc(0,fp); + fprintf(fp,"N%s",NODENAME); putc(0,fp); putc('M',fp); + fprintf(fp,"NEW UPLOAD: '%s'\n %s\n",CC->upl_file,CC->upl_comment); + putc(0,fp); + fclose(fp); + save_message(CC->temp, "", 0, M_LOCAL, 1); + + } + else { + abort_upl(CC); + cprintf("%d File '%s' aborted.\n",OK,CC->upl_path); + } + } + +/* + * read from the download file + */ +void cmd_read(char *cmdbuf) +{ + long start_pos; + int bytes; + char buf[4096]; + + start_pos = extract_long(cmdbuf,0); + bytes = extract_int(cmdbuf,1); + + if (CC->download_fp == NULL) { + cprintf("%d You don't have a download file open.\n",ERROR); + return; + } + + if (bytes > 4096) { + cprintf("%d You may not read more than 4096 bytes.\n",ERROR); + return; + } + + if (CC->dl_is_net) if (bytes>64) bytes = 64; /**** FIX ****/ + + fseek(CC->download_fp,start_pos,0); + fread(buf,bytes,1,CC->download_fp); + cprintf("%d %d\n",BINARY_FOLLOWS,bytes); + client_write(buf, bytes); + } + + + +/* + * write to the upload file + */ +void cmd_writ(char *cmdbuf) +{ + int bytes; + char buf[4096]; + + bytes = extract_int(cmdbuf,0); + + if (CC->upload_fp == NULL) { + cprintf("%d You don't have an upload file open.\n",ERROR); + return; + } + + if (bytes > 4096) { + cprintf("%d You may not write more than 4096 bytes.\n",ERROR); + return; + } + + if (CC->upload_type==UPL_NET) if (bytes > 64) bytes = 64; /*** FIX ***/ + + cprintf("%d %d\n",SEND_BINARY,bytes); + client_read(buf, bytes); + fwrite(buf,bytes,1,CC->upload_fp); + } + + + +/* + * cmd_netp() - identify as network poll session + */ +void cmd_netp(char *cmdbuf) +{ + char buf[256]; + + extract(buf,cmdbuf,1); + if (strucmp(buf,config.c_net_password)) { + cprintf("%d authentication failed\n",ERROR); + return; + } + extract(CC->net_node,cmdbuf,0); + cprintf("%d authenticated as network node '%s'\n",OK,CC->net_node); + } + +/* + * cmd_ndop() - open a network spool file for downloading + */ +void cmd_ndop(char *cmdbuf) +{ + char pathname[256]; + struct stat statbuf; + + if (strlen(CC->net_node)==0) { + cprintf("%d Not authenticated as a network node.\n", + ERROR+NOT_LOGGED_IN); + return; + } + + if (CC->download_fp != NULL) { + cprintf("%d You already have a download file open.\n",ERROR); + return; + } + + sprintf(pathname,"%s/network/spoolout/%s",BBSDIR,CC->net_node); + + /* first open the file in append mode in order to create a + * zero-length file if it doesn't already exist + */ + CC->download_fp = fopen(pathname,"a"); + fclose(CC->download_fp); + + /* now open it */ + CC->download_fp = fopen(pathname,"r"); + if (CC->download_fp==NULL) { + cprintf("%d cannot open %s: %s\n", + ERROR,pathname,strerror(errno)); + return; + } + + + /* set this flag so other routines know that the download file + * currently open is a network spool file + */ + CC->dl_is_net = 1; + + stat(pathname,&statbuf); + cprintf("%d %ld\n",OK,statbuf.st_size); + } + +/* + * cmd_nuop() - open a network spool file for uploading + */ +void cmd_nuop(char *cmdbuf) +{ + if (strlen(CC->net_node)==0) { + cprintf("%d Not authenticated as a network node.\n", + ERROR+NOT_LOGGED_IN); + return; + } + + if (CC->upload_fp != NULL) { + cprintf("%d You already have an upload file open.\n",ERROR); + return; + } + + sprintf(CC->upl_path,"%s/network/spoolin/%s.%d", + BBSDIR,CC->net_node,getpid()); + + CC->upload_fp = fopen(CC->upl_path,"r"); + if (CC->upload_fp != NULL) { + fclose(CC->upload_fp); + CC->upload_fp = NULL; + cprintf("%d '%s' already exists\n", + ERROR+ALREADY_EXISTS,CC->upl_path); + return; + } + + CC->upload_fp = fopen(CC->upl_path,"w"); + if (CC->upload_fp == NULL) { + cprintf("%d Cannot open %s: %s\n", + ERROR,CC->upl_path,strerror(errno)); + return; + } + + CC->upload_type = UPL_NET; + cprintf("%d Ok\n",OK); + } diff --git a/citadel/help/aide b/citadel/help/aide new file mode 100644 index 000000000..4d5b5c177 --- /dev/null +++ b/citadel/help/aide @@ -0,0 +1,18 @@ +The following commands are available only to Aides. A subset of these +commands are available to room aides when they are currently in the room +they are room aide for. + + .ide dit Room - edit current room's parameters + .ide ile elete - delete a file from the directory + .ide ile ove - move a file to another room + .ide ile end over net - send a file across the network + .ide enter nfo file - create/change this room's info file + .ide ill Room - delete the current room + .ide oom nvite user - add user to invitation-only room + .ide oom ick out user - delete user from invitation-only room + .ide ser edit - change a user's access level + .ide alidate new users - process new user registration + .ide ho knows room - lists users with access to current room + + In addition, the ove and elete commands are available at the +message prompt. diff --git a/citadel/help/floors b/citadel/help/floors new file mode 100644 index 000000000..c7a393fdc --- /dev/null +++ b/citadel/help/floors @@ -0,0 +1,43 @@ + Floors + ------ + Floors in ^variantname are used to group rooms into related subject areas, +just as rooms are used to group messages into manageable groups. + + You, as a user, do NOT have to use floors. If you choose not to, you suffer +no penalty; you will not lose access to any rooms. You may use .EC or ;C (the +latter is easier to use) to decide if you want to use floors. Feel free to +experiment. + + Floor options are accessed two ways. First, if you are in floor mode, the +oto and kip commands take you to the next room with new messages on the +current floor; if there are none left, then the system will automatically +switch floors (and let you know) and put you in the first room with new messages +on that level. (Notice that your pattern of basic use of ^variantname therefore +doesn't really change.) + + Direct access to floor options is via the use of a ";" command. +The following commands are currently available (more can be +added if needed): + + <;C>onfigure + This command toggles your floor mode. + + <;G>oto FLOORNAME + This command causes the system to take you to the named floor. + + <;K>nown rooms on floors + List all rooms on all floors. This is a very readable way to get a list of +all rooms on the system. + + <;S>kip FLOORNAME + This command causes the system to mark all rooms on the current floor as +Skipped and takes you to the floor that you specify. + + <;Z>Forget floor + This command causes you to forget all the rooms currently on the current +floor. Unfortunately, it doesn't apply to rooms that are subsequently created +or moved to this floor. (Sorry.) + + Feel free to experiment, you can't hurt yourself or the system with the +floor stuff unless you ZForget a floor by accident. + diff --git a/citadel/help/hours b/citadel/help/hours new file mode 100644 index 000000000..5da3df329 --- /dev/null +++ b/citadel/help/hours @@ -0,0 +1 @@ +^humannode is up 24 hours a day, 7 days a week. diff --git a/citadel/help/intro b/citadel/help/intro new file mode 100644 index 000000000..bf3e3cd7f --- /dev/null +++ b/citadel/help/intro @@ -0,0 +1,139 @@ + Welcome to ^humannode! + New User's Introduction to the BBS + + This is an introduction to ^humannode and to the Citadel BBS concept. +It is intended for new users so that they can more easily become +aquainted with the bbs system. Of course, old users might learn +something new each time they read through it. + + Full help for the BBS commands can be obtained by typing <.H>elp SUMMARY + + The CITADEL BBS room concept + ---------------------------- + The term BBS stands for "Bulletin-Board System". The analogy is +appropriate: one posts messages so that others may read them. In +order to organize the posts, people can post in different areas of the +BBS, called rooms. + In order to post in a certain room, you need to be "in" that room. +Your current prompt is usually the room that you are in, followed the +greater-than-sign, such as: + + Lobby> + + The easiest way to traverse the room structure is with the "Goto" +command, on the "G" key. Pressing "G" will take you to the next room +in the "scanlist" (see below) that has new messages in it. You can +read these new messages with the "N" key. + Once you've "Gotoed" every room in the system (or all of the ones +you choose to read) you return to the "Lobby," the first and last room +in the system. If new messages get posted to rooms you've already +read during your session you will be brought BACK to those rooms so +you can read them. + + Scanlist + -------- + All the room names are stored in a scanlist, which is just a +string containing all the room names. When you oto or 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 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 +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 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 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-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 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 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 "". Along with this information is displayed the +name of the host computer the user is logged in from. + + (This file was shamelessly swiped from QuartzBBS. Thanks to its original +author for putting it together.) diff --git a/citadel/help/mail b/citadel/help/mail new file mode 100644 index 000000000..69225caee --- /dev/null +++ b/citadel/help/mail @@ -0,0 +1,20 @@ +To send mail on this system, go to the Mail> room (using the command .G Mail) +and press E to enter a message. You will be prompted with: + Enter Recipient: + At this point you may enter the name of another user on the system. Private +mail is only readable by the sender and recipient. There is no need to delete +mail after it is read; it will scroll out automatically. + + To send mail to another user on the Citadel network, simply type the +user's name, followed by @ and then the system name. For example, + + Enter Recipient: Joe Schmoe @ citadrool + + To send mail to a user on the Internet, type their network address +at the recipient prompt. Citadel/UX will send it over the network: + + Enter Recipient: ajc@herring.fishnet.com + + Your e-mail address on other Citadels is ^username @ ^nodename + Your e-mail address on the Internet is cit^usernum@^fqdn + diff --git a/citadel/help/network b/citadel/help/network new file mode 100644 index 000000000..4a2ce39ca --- /dev/null +++ b/citadel/help/network @@ -0,0 +1,4 @@ + Welcome to the network. Messages entered in a network room will appear in +that room on all other systems carrying it (The name of the room, however, +may be different on other systems). + diff --git a/citadel/help/nice b/citadel/help/nice new file mode 100644 index 000000000..2e5411442 --- /dev/null +++ b/citadel/help/nice @@ -0,0 +1,128 @@ + The following are a few points of general BBS etiquette. If you wish +to maintain your welcome on whatever system you happen to call, it +would be to your advantage to observe these few rules. Feel free to +download this and display spread it around. + + 1. Don't habitually hang up on a system. Every Sysop is aware that +accidental disconnections happen once in a while but we do tend to +get annoyed with people who hang up every single time they call +because they are either too lazy to terminate properly or they labor +under the mistaken assumption that the 10 seconds they save online is +going to significantly alter their phone bill. "Call Waiting" is not +an acceptable excuse for long. If you have it and intend to use the +line to call BBS's you should either have it disconnected or find +some other way to circumvent it. + + 2. Don't do dumb things like leave yourself a message that says +"Just testing to see if this thing works". Where do you think all +those other messages came from if it didn't work? Also, don't leave +whiney messages that say "Please leave me a message". If ever there +was a person to ignore, it is the one who begs someone to leave him a +message. If you want to get messages, start by reading the ones that +are already online and getting involved in the conversations that +exist. + + 3. Don't use the local equivalent of a chat command unless you +really have some clear cut notion of what you want to say and why. +Almost any Sysop is more than happy to answer questions or offer help +concerning his system. Unfortunately, because about 85% of the +people who call want to chat and about 99% of those people have +absolutely nothing to say besides "How old are you?" or something +equally irrelevant, fewer Sysops even bother answering their pagers +every day. + + 4. When you are offered a place to leave comments when exiting a +system, don't try to use this area to ask the Sysop questions. It is +very rude to the other callers to expect the Sysop to carry on a half +visible conversation with someone. If you have a question or +statement to make and expect the Sysop to respond to it, it should +always be made in the section where all the other messages are kept. +This allows the Sysop to help many people with the same problem with +the least amount of effort on his part. + + 5. Before you log on with your favorite pseudonym, make sure that +handles are allowed. Some Sysops do not want people using handles on +the system. The reasons vary, but everyone should still be willing to +take full responsibility for his actions or comments instead of +slinging mud from behind a phoney name. + + Also when signing on, why not sign on just like you would introduce +yourself in your own society? How many of you usually introduce +yourselves as Joe W Smutz the 3rd or 4th? + + 6. Take the time to log on properly. If the BBS asks you the city +where you are calling from, remember that there is no such place as +RIV, HB, ANA or any of a thousand other abbreviations people use +instead of their proper city. You may think that everyone knows what +RIV is supposed to mean, but every BBS has people calling from all +around the country and I assure you that someone from Podunk, Iowa +has no idea what you are talking about. + + 7. Don't go out of your way to make rude observations like "Gee, +this system is slow". Every BBS is a tradeoff of features. You can +generally assume that if someone is running a particular brand of +software, that he is either happy with it or he will decide to find +another system he likes better. It does nobody any good when you +make comments about something that you perceive to be a flaw when it +is running the way the Sysop wants it to. Constructive criticism is +somewhat more welcome. If you have an alternative method that seems +to make good sense then run it up the flagpole. + + 8. When leaving messages, stop and ask yourself whether it is +necessary to make it private. Unless there is some particular reason +that everyone should not know what you are saying, do not make it +private. We do not call them PUBLIC bulletin boards for nothing, +folks. It is very irritating to other callers when there are huge +blank spots in the messages that they can not read and it stifles +interaction between callers. + + 9. If your favorite BBS has a time limit, observe it. If it +doesn't, set a limit for yourself and abide by it instead. Do not +tie up a system as a new user and run right to the other numbers +list. There is probably very little that is more annoying to any +Sysop than to have his board completely passed over by you on your +way to another board. + + 10. Have the common courtesy to pay attention to what passes in +front of your face. When a BBS displays your name and asks "Is this +you?", don't say yes when you can see perfectly well that it is +misspelled. Also, do not start asking questions about simple +operation of a system until you have thoroughly read all of the +instructions that are available to you. I assure you that it is not +any fun to answer a question for the thousandth time when the answer +is prominently displayed in the system bulletins or instructions. Use +some common sense when you ask your questions. The person who said +"There is no such thing as a stupid question" obviously never +operated a BBS. + + 11. Don't be personally abusive. It does not matter whether you +like a Sysop or think he/she is a jerk. The fact remains that he/she +has a large investment in making his computer available, usually out +of the goodness of his/her heart. If you don't like a Sysop or +his/her system, just remember that you can change the channel any +time you want. Besides, whether you are aware of it or not, if you +make yourself enough of an annoyance to any Sysop, he/she can take +the time to trace you down and make your life, or that of your +parents, miserable. + + Along those lines, don't be abusive of other users on the system. +It doesn't matter what you think of him/her/them, but "If you don't +have something nice to say, don't say it." If you think someone is +being too abusive/whatever, let the Sysop know. It is his/her +system, and upon him/her lies the responsibilty of dealing with +problem users. If you think that he/she is not doing a good enough +job, do not call back. + + 12. Lastly and ****** MOST IMPORTANTLY ****** keep firmly in mind +that you are a *** GUEST *** on any BBS you happen to call. Do not +think of logging on as one of your basic human rights. Every person +that has ever put a computer system online for the use of other +people has spent a lot of time and money to do so. While he/she does +not expect nonstop pats on the back, it seems reasonable that he/she +should at least be able to expect fair treatment from his/her +callers. This includes following any of the rules for system use +he/she has laid out without grumping about it. Every Sysop has +his/her own idea of how he/she wants his/her system to be run. It is +really none of your business why he/she wants to run it the way +he/she does. Your business is to either abide by what he says, or +call some other BBS where you feel that you can obey the rules. diff --git a/citadel/help/policy b/citadel/help/policy new file mode 100644 index 000000000..6dfecc8c7 --- /dev/null +++ b/citadel/help/policy @@ -0,0 +1,3 @@ + + < this new user policy resides in ./messages/newuser > + diff --git a/citadel/help/software b/citadel/help/software new file mode 100644 index 000000000..e98bba752 --- /dev/null +++ b/citadel/help/software @@ -0,0 +1,5 @@ + Citadel/UX is the premier "online community" (i.e. Bulletin Board System) +software. It runs on all POSIX-compliant systems, including Linux. It is an +advanced client/server application, and is being actively maintained. + + For more info, visit UNCENSORED! BBS at uncnsrd.mt-kisco.ny.us diff --git a/citadel/help/summary b/citadel/help/summary new file mode 100644 index 000000000..207cc6c93 --- /dev/null +++ b/citadel/help/summary @@ -0,0 +1,48 @@ +Extended commands are available using the period ( . ) key. To use +a dot command, press the . key, and then enter the first letter of +each word in the command. The words will appear as you enter the keys. +You can also backspace over partially entered commands. The following +commands are available: + + .nter message using scii - raw mode message input + .nter io - tell everyone about yourself + .nter onfiguration - set up your account statistics + .nter message with ditor - (installation dependent) + .nter reistration - enter your name, address, etc. + .nter essage - enter message with the Citadel editor + .nter

assword - change your password + .nter oom - create a new room + .nter extfile - ASCII upload + .nter file using modem - protocol upload + .nter file using modem - protocol upload + .nter file using modem - protocol upload + + .ead io - read other users' bios + .ead irectory - directory of current room + .ead ile unformatted - raw ASCII download + .ead nfo file - read info file for this room + .ead ast five messages - same as + .ead ew messages - same as + .ead ld messages - same as + .ead everse - same as + .ead extfile paginated - prints a textfile with page prompts + .ead ser List - lists users with accounts on system + .ead file using modem - protocol download + .ead file using modem - protocol download + .ead file using modem - protocol download + + .oto room: (type roomname) - jump to a specific room + (you only need to type enough of the + room name to make it unique) + .kip, goto: (type roomname) - skip current room, jump to another + .elp file: (type filename) - read a help file + .erminate and uit - log out immediately without prompts + .erminate and tay online - log out and allow another user to log in + .apped room list - list all zapped (forgotten) rooms + + Floor commands (if using floor mode) + ;onfigure floor mode - turn floor mode on or off + ;oto floor: - jump to a specific floor + ;nown rooms - list all rooms on all floors + ;kip to floor: - skip current floor, jump to another + ;ap floor - zap (forget) all rooms on this floor diff --git a/citadel/housekeeping.c b/citadel/housekeeping.c new file mode 100644 index 000000000..159169e68 --- /dev/null +++ b/citadel/housekeeping.c @@ -0,0 +1,65 @@ +/* + * This file contains housekeeping tasks which periodically + * need to be executed. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "citadel.h" +#include "server.h" +#include "proto.h" + +/* + * Terminate idle sessions. This function pounds through the session table + * comparing the current time to each session's time-of-last-command. If an + * idle session is found it is terminated, then the search restarts at the + * beginning because the pointer to our place in the list becomes invalid. + */ +void terminate_idle_sessions(void) { + struct CitContext *ccptr; + time_t now; + + time(&now); + for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) { + if ( (ccptr!=CC) + && (config.c_sleeping > 0) + && (now - (ccptr->lastcmd) > config.c_sleeping) ) { + lprintf(3, "Session %d timed out\n", ccptr->cs_pid); + kill_session(ccptr->cs_pid); + ccptr = ContextList; + } + } + } + + +/* + * Main housekeeping function. + */ +void do_housekeeping() { + + begin_critical_section(S_HOUSEKEEPING); + /* + * Terminate idle sessions. + */ + lprintf(7, "Calling terminate_idle_sessions()\n"); + terminate_idle_sessions(); + + /* + * If the server is scheduled to shut down the next time all + * users are logged out, now's the time to do it. + */ + if ((ScheduledShutdown == 1) && (ContextList == NULL)) { + lprintf(3, "Scheduled shutdown initiating.\n"); + master_cleanup(); + } + end_critical_section(S_HOUSEKEEPING); + } + + diff --git a/citadel/install.txt b/citadel/install.txt new file mode 100644 index 000000000..b0d7ad1d9 --- /dev/null +++ b/citadel/install.txt @@ -0,0 +1,353 @@ + Citadel/UX Installation Procedure + See copyright.doc for copyright information + + + OVERVIEW + + Citadel/UX is an advanced, multiuser, client/server, room-based BBS +program. It is designed to handle the needs of both small dialup systems and +large-scale Internet-connected systems. It was originally developed on an +Altos system running Xenix, and has been installed and tested on various +Unix and Unix-like platforms. The author's current development environment +(and BBS) is a Linux/GNU system. The current distribution includes: + + - The Citadel/UX server (this is the back end that does all processing) + - A text-based client program designed with the traditional Citadel "look + and feel" (room prompts, dot commands, and the like) + - A networker that utilizes any file transfer mechanism (such as UUCP for + standalone systems, or ftp for Internet) and can share messages with other + Citadel/UX systems, as well as UseNet sites. Gateway software to talk + with C86NET (Citadel-86 and its deriviatives), HengeNet (StoneHenge), + and NYTI FordBoard is also available. + - Setup programs + - A rich set of utilities for system administration and maintenance + - Documentation + + Some knowledge of the Unix system is necessary to install and manage the +system. It is preferable that the sysop have superuser access to the operating +system. The following are required to install Citadel/UX: + + - Some sort of Unix (or Unix look-alike) operating system + - C compiler + - POSIX threads + - TCP/IP + - the "make" utility (you don't want to try compiling it manually!) + - Enough disk space to hold all of the programs and data + + + NOW AVAILABLE: + + - "WebCit", a gateway program to allow full access to Citadel/UX BBS's + via the World Wide Web. Interactive access through any Web browser. + + - A point-and-click client program for MS Windows. (The text-based client + is now available for Windows as well.) + + COMING SOON: + + - A client program written in Java that will be portable to all operating + systems. + + + + EVERYTHING IN ITS PLACE... + + Hopefully you've unpacked the distribution archive into its own directory. +This is the directory in which all Citadel files are located and in which all +BBS activity will take place. Several subdirectories have already been created +during the unpacking process, and others may be created by the software if +needed. + + + THE BBS LOGIN + + There will be one account in /etc/passwd which all BBS users will use to +login to the system. This account is typically called "bbs" or "citadel" or +something to that effect. You will tell Citadel what the user-id of that +account is, and when someone logs in under that account, Citadel will prompt +for a user name. + +The BBS login should have a unique uid. The home directory should be the +one your BBS resides in (in this example we will use /usr/bbs) and the shell +should be either "citadel" in that directory, or a script that will start up +citadel (you may wish to set up an external text editor; see below). Example: + + bbs::100:1:BBS Login:/usr/citadel:/usr/citadel/citadel + + When you run setup later, you will be required to tell it what the BBS +login's numeric user ID is, so it knows what user to run as. + + For all other users in /etc/passwd, Citadel will automatically set up an +account using the "full name" field. No password is required, since it +assumes that if a user is logged in, he/she has already entered a password. +Note that this does have to be enabled at compile time (see ENABLE_AUTOLOGIN +in the Makefile). If such an account needs to be accessed remotely (such as +from client software), these users can use *either* their Citadel login name +or their login name on the host computer, and their password on the host +computer. + + + EDITING STUFF BEFORE COMPILING... + + If you are upgrading an existing installation, be sure to check the +sysconfig.h header, to make sure the values there are the same as in your +previous installation. For a new system, it's best to leave these values +alone, so you won't have to worry about it the next time you upgrade. + + You might also want to check the "Configure" script for platform-specific +stuff. Any platforms which have been tested will be auto-detected by the +script and the compiler and linker directives set accordingly. If your +platform isn't autodetected, you'll have to figure out the flags yourself (but +please get in touch so your platform can be supported in the next release!). + + + COMPILING THE PROGRAMS + + + As with most Unix programs, you compile it using these two commands: + + ./Configure + make + + The 'Configure' script will generate a Makefile from the Makefile.in, and +it will also write the file "sysdep.h" to your Citadel directory. Please do +not edit sysdep.h or Makefile.in yourself. The Configure script will figure +out your system dependencies and set everything correctly. The ONLY places +you should be editing, if anywhere at all, are sysconfig.h and Configure. + + File permissions are always a bother to work with. You don't want the +board to crash because someone couldn't access a file, but you also don't +want shell users peeking into the binaries to do things like reading others' +mail, finding private rooms, etc. The Citadel server needs to be started +as root in order to bind to a privileged port, but as soon as its +initialization is finished, it changes its user ID to your BBS user ID in +order to avoid security holes. + + + THE CITADEL.RC FILE + + This is a change from the way things were done before. All client-side setup +is in a "citadel.rc" file. The standard client looks for this file in: + 1. $HOME/.citadelrc + 2. /usr/local/lib/citadel.rc + 3. /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 .nter ditor +command. You can also make it the default editor for the 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=; export USERNAME + ./subsystem + + ...so you can put whatever you want in there. I suggest putting in a menu +program to allow the users to pick one of a number of programs, etc. + + Do be aware that chat and door programs will only be available when two +conditions are met: + + 1. The client and server programs are running on the same computer + 2. The user is running the text-based Unix client. + + Because of these restrictions, Door programs are being utilized less and +less every day. + + + + SETTING UP CITADEL TO SEND AND RECEIVE INTERNET MAIL + + Mail programs are now elaborate enough that it is trivial to set up Citadel +to act as your system's local mail delivery agent. It couples easily with +either sendmail or smail, or with any other mail system that is capable of +invoking a separate program to deliver local mail. + + Unlike earlier versions of Citadel/UX, there is no longer a need to play +with rmail or to patch other pieces of your system's existing mailer. Simply +make a few quick configurations, compile the Citadel/UX package "as is, and +you're ready to go. Here's how to do it: + + 1. First, open up the config file "internetmail.config" in the "network" +directory, and edit the definitions in it to your local configuration. It's +very self-documented; just go through the file making any necessary changes. + + 2. Next, you must configure your system's main mail delivery agent to +use the "citmail" program to deliver mail to local addresses. This will +change from mailer to mailer, of course. I'm using sendmail, and although +I don't know enough about sendmail to provide really good instructions on +sendmail configuration, here's what worked for me: + + I added the following mailer definition: + + Mcitadel, P=/appl/citadel/citmail, F=lsDFMoqeu9, S=10/30, R=20/40, D=$z:/, + T=X-Unix, + A=/appl/citadel/citmail $u + + Then I replaced all instances of "#local" with "#citadel". This seems to +work on my system; your mileage may vary. + + 3. Some mailers will need to be killed and restarted for the changes to +take effect. Once this is done, all of your BBS users now have an Internet +e-mail address. They can use two forms of addresses: + + user_name@your.system.name + cit1234@your.system.name + + In the first form, the name portion of the user's Internet e-mail address +is the name they use on your Citadel system, with all spaces replaced by +underscores. In the second form, the name is the letters "cit" followed +by the user's user number. This is a nice shorthand that is sometimes +easier to use. Note that the help file <.H>elp MAIL is set up to tell users +what their address is. + + NOTE: I cannot and will not answer e-mails regarding the configuration of +sendmail or any other mailer. I am not a sendmail expert; what is written +above is everything I know about getting Citadel and sendmail to talk to each +other. Please refer these questions to your local sendmail wizard. + + + + THE PEANUT GALLERY + + That's just about all the information you need to install the system. If +you have any comments, suggestions, bomb threats, etc., send them to +ajc@uncnsrd.mt-kisco.ny.us or call Uncensored Communications Group BBS at +(914) 244-3252 (modem) or uncnsrd.mt-kisco.ny.us (Internet). diff --git a/citadel/internetmail.c b/citadel/internetmail.c new file mode 100644 index 000000000..351f63f11 --- /dev/null +++ b/citadel/internetmail.c @@ -0,0 +1,104 @@ +/* + * Internet mail configurator for Citadel/UX + * see copyright.doc for copyright information + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 "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 0) buf[strlen(buf)-1] = 0; + /* printf("> %s\n", buf); */ + } + + +/* + * send line to server - implemented in terms of serv_write() + */ +void serv_puts(buf) +char *buf; { + /* printf("< %s\n", buf); */ + serv_write(buf, strlen(buf)); + serv_write("\n", 1); + } + + +/* + * attach to server + */ +void attach_to_server(argc,argv) +int argc; +char *argv[]; { + int a; + char cithost[256]; int host_copied = 0; + char citport[256]; int port_copied = 0; + char socks4[256]; + unsigned char buf[256]; + struct passwd *p; + + strcpy(cithost,DEFAULT_HOST); /* default host */ + strcpy(citport,DEFAULT_PORT); /* default port */ + strcpy(socks4,""); /* SOCKS v4 server */ + + for (a = 0; a < argc; ++a) { + if (a == 0) { + /* do nothing */ + } + else if (!strcmp(argv[a],"-s")) { + strcpy(socks4,argv[++a]); + } + else if (host_copied == 0) { + host_copied = 1; + strcpy(cithost,argv[a]); + } + else if (port_copied == 0) { + port_copied = 1; + strcpy(citport,argv[a]); + } + +/* + else { + fprintf(stderr,"%s: usage: ",argv[0]); + fprintf(stderr,"%s [host] [port] ",argv[0]); + fprintf(stderr,"[-s socks_server]\n"); + logoff(2); + } +*/ + } + + server_is_local = 0; + if ( (!strcmp(cithost,"localhost")) + || (!strcmp(cithost,"127.0.0.1")) ) server_is_local = 1; + + /* if not using a SOCKS proxy server, make the connection directly */ + if (strlen(socks4)==0) { + serv_sock = connectsock(cithost,citport,"tcp"); + return; + } + + /* if using SOCKS, connect first to the proxy... */ + serv_sock = connectsock(socks4,"1080","tcp"); + printf("Connected to SOCKS proxy at %s.\n",socks4); + printf("Attaching to server...\r"); + fflush(stdout); + + sprintf(buf,"%c%c", + 4, /* version 4 */ + 1); /* method = connect */ + serv_write(buf,2); + + numericize(buf,cithost,citport,"tcp"); + serv_write(buf,6); /* port and address */ + + p = (struct passwd *) getpwuid(getuid()); + serv_write(p->pw_name, strlen(p->pw_name)+1); + /* user name */ + + serv_read(buf,8); /* get response */ + + if (buf[1] != 90) { + printf("SOCKS server denied this proxy request.\n"); + logoff(3); + } + + } + +/* + * return the file descriptor of the server socket so we can select() on it. + */ +int getsockfd() { + return serv_sock; + } + + +/* + * return one character + */ +char serv_getc() { + char buf[2]; + char ch; + + serv_read(buf, 1); + ch = (int) buf[0]; + + return(ch); + } diff --git a/citadel/ipcdef.h b/citadel/ipcdef.h new file mode 100644 index 000000000..68d9db02a --- /dev/null +++ b/citadel/ipcdef.h @@ -0,0 +1,72 @@ +#ifdef OK +#undef OK +#endif + +#define LISTING_FOLLOWS 100 +#define OK 200 +#define MORE_DATA 300 +#define SEND_LISTING 400 +#define ERROR 500 +#define BINARY_FOLLOWS 600 +#define SEND_BINARY 700 +#define START_CHAT_MODE 800 + +#define INTERNAL_ERROR 10 +#define NOT_LOGGED_IN 20 +#define CMD_NOT_SUPPORTED 30 +#define PASSWORD_REQUIRED 40 +#define HIGHER_ACCESS_REQUIRED 50 +#define MAX_SESSIONS_EXCEEDED 51 +#define NOT_HERE 60 +#define INVALID_FLOOR_OPERATION 61 +#define NO_SUCH_USER 70 +#define FILE_NOT_FOUND 71 +#define ROOM_NOT_FOUND 72 +#define NO_SUCH_SYSTEM 73 +#define ALREADY_EXISTS 74 + +struct serv_info { + int serv_pid; + char serv_nodename[32]; + char serv_humannode[64]; + char serv_fqdn[64]; + char serv_software[64]; + int serv_rev_level; + char serv_bbs_city[64]; + char serv_sysadm[64]; + char serv_moreprompt[256]; + int serv_ok_floors; + }; + +#define QR_PERMANENT 1 /* Room does not purge */ +#define QR_INUSE 2 /* Set if in use, clear if avail */ +#define QR_PRIVATE 4 /* Set for any type of private room */ +#define QR_PASSWORDED 8 /* Set if there's a password too */ +#define QR_GUESSNAME 16 /* Set if it's a guessname room */ +#define QR_DIRECTORY 32 /* Directory room */ +#define QR_UPLOAD 64 /* Allowed to upload */ +#define QR_DOWNLOAD 128 /* Allowed to download */ +#define QR_VISDIR 256 /* Visible directory */ +#define QR_ANONONLY 512 /* Anonymous-Only room */ +#define QR_ANON2 1024 /* Anonymous-Option room */ +#define QR_NETWORK 2048 /* Shared network room */ +#define QR_PREFONLY 4096 /* Preferred status needed to enter */ +#define QR_READONLY 8192 /* Aide status required to post */ + + +#define US_NEEDVALID 1 /* User needs to be validated */ +#define US_PERM 4 /* Permanent user */ +#define US_LASTOLD 16 /* Print last old message with new */ +#define US_EXPERT 32 /* Experienced user */ +#define US_UNLISTED 64 /* Unlisted userlog entry */ +#define US_NOPROMPT 128 /* Don't prompt after each message */ +#define US_DISAPPEAR 512 /* Use "disappearing msg prompts" */ +#define US_REGIS 1024 /* Registered user */ +#define US_PAGINATOR 2048 /* Pause after each screen of text */ +#define US_INTERNET 4096 /* UUCP/Internet mail privileges */ +#define US_FLOORS 8192 /* User wants to see floors */ +#define US_USER_SET (US_LASTOLD | US_EXPERT | US_UNLISTED | \ + US_NOPROMPT | US_DISAPPEAR | US_PAGINATOR | US_FLOORS) + +void serv_puts(); +void serv_gets(); diff --git a/citadel/locate_host.c b/citadel/locate_host.c new file mode 100644 index 000000000..d3c194adc --- /dev/null +++ b/citadel/locate_host.c @@ -0,0 +1,48 @@ +/* + * locate the originating host + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sysdep.h" +#include "citadel.h" +#include "server.h" + +struct config config; + +void locate_host(char *tbuf) +{ + struct sockaddr_in cs; + struct hostent * ch; + int len; + char *i; + int a1,a2,a3,a4; + + len = sizeof(cs); + if (getpeername(CC->client_socket, (struct sockaddr *)&cs,&len) < 0){ + strcpy(tbuf,config.c_fqdn); + return; + } + + if((ch = gethostbyaddr((char *) &cs.sin_addr, sizeof(cs.sin_addr), + AF_INET)) == NULL) { + i = (char *) &cs.sin_addr; + a1 = ((*i++)&0xff); + a2 = ((*i++)&0xff); + a3 = ((*i++)&0xff); + a4 = ((*i++)&0xff); + sprintf(tbuf,"%d.%d.%d.%d",a1,a2,a3,a4); + return; + } + + strncpy(tbuf,ch->h_name, 24); + tbuf[24] = 0; + } diff --git a/citadel/mailinglist.c b/citadel/mailinglist.c new file mode 100644 index 000000000..be2b04f06 --- /dev/null +++ b/citadel/mailinglist.c @@ -0,0 +1,165 @@ +/* + * mailinglist.c + * + * This program is designed to be a filter which allows two-way interaction + * between a traditional e-mail mailing list and a Citadel room. + * + * It only handles the outbound side. The inbound side is handled by the + * Citadel e-mail gateway. You should subscribe rooms to lists using the + * "room_roomname@node.dom" type address. + * + * Since some listprocs only accept postings from subscribed addresses, the + * messages which this program converts will all appear to be from the room + * address; however, the full name of the sender is set to the Citadel user + * name of the real message author. + * + * To prevent loops, this program only sends messages originating on the local + * system. Therefore it is not possible to carry a two-way mailing list room + * on a Citadel network. + */ + +#include +#include +#include +#include +#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 (posrc) 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 screenwidth) { + last_space = (-1); + for (b=0; b=0) { + for (b=0; b 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("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 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; ahighmsg) ++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=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("eply "); + if (strlen(printcmd)>0) printf("

rint "); + printf("ack gain uote eader ext top -> "); + do { + lines_printed = 2; + e=(inkey()&127); e=tolower(e); +/* return key same as */ if (e==13) e='n'; +/* del/move for aides only */ if (!is_room_aide) if ((e=='d')||(e=='m')) e=0; +/* print only if available */ if ((e=='p')&&(strlen(printcmd)==0)) e=0; +/* can't move from Mail> */ if ((e=='m')&&(is_mail==1)) e=0; +/* can't reply in public rms */ if ((e=='r')&&(is_mail!=1)) e=0; + } while((e!='a')&&(e!='n')&&(e!='s') + &&(e!='d')&&(e!='m')&&(e!='p') + &&(e!='q')&&(e!='b')&&(e!='h') + &&(e!='r')); + switch(e) { + case 's': printf("Stop\r"); break; + case 'a': printf("Again\r"); break; + case 'd': printf("Delete\r"); break; + case 'm': printf("Move\r"); break; + case 'n': printf("Next\r"); break; + case 'p': printf("Print\r"); break; + case 'q': printf("Quote\r"); break; + case 'b': printf("Back\r"); break; + case 'h': printf("Header\r"); break; + case 'r': printf("Reply\r"); break; + } + if (userflags & US_DISAPPEAR) + printf("%75s\r",""); + else + printf("\n"); + fflush(stdout); + } + switch(e) { + case 'p': fflush(stdout); + freopen(prtfile,"w",stdout); + arcflag = 1; + goto RAGAIN; + case 'q': fflush(stdout); + freopen(temp2,"w",stdout); + quotflag = 1; + goto RAGAIN; + case 's': return; + case 'a': goto RAGAIN; + case 'b': a=a-(rdir*2); + break; + case 'm': newprompt("Enter target room: ",targ,19); + if (strlen(targ)>0) { + sprintf(cmd,"MOVE %ld|%s", + msg_arr[a],targ); + serv_puts(cmd); + serv_gets(cmd); + printf("%s\n",&cmd[4]); + if (cmd[0]=='2') msg_arr[a]=0L; + } + else { + goto RMSGREAD; + } + if (cmd[0]!='2') goto RMSGREAD; + break; + case 'd': printf("*** Delete this message? "); + if (yesno()==1) { + sprintf(cmd,"DELE %ld",msg_arr[a]); + serv_puts(cmd); + serv_gets(cmd); + printf("%s\n",&cmd[4]); + if (cmd[0]=='2') msg_arr[a]=0L; + } + else { + goto RMSGREAD; + } + break; + case 'h': read_message(msg_arr[a],READ_HEADER); + goto RMSGREAD; + case 'r': entmsg(1,(DEFAULT_ENTRY==46 ? 2 : 0)); + goto RMSGREAD; + } + } /* end for loop */ + } /* end read routine */ + + + + +/* + * View and edit a system message + */ +void edit_system_message(char *which_message) +{ + char desc[64]; + char read_cmd[64]; + char write_cmd[64]; + + sprintf(desc, "system message '%s'", which_message); + sprintf(read_cmd, "MESG %s", which_message); + sprintf(write_cmd, "EMSG %s", which_message); + do_edit(desc, read_cmd, "NOOP", write_cmd); + } diff --git a/citadel/messages/aideopt b/citadel/messages/aideopt new file mode 100644 index 000000000..b006420d9 --- /dev/null +++ b/citadel/messages/aideopt @@ -0,0 +1,12 @@ +One of: + dit room + ile elete + ile end over net + ile ove + edit nfo file + ill room + oom nvite user + oom ick out user + ser edit + alidate new users + ho knows room diff --git a/citadel/messages/changepw b/citadel/messages/changepw new file mode 100644 index 000000000..e5726477d --- /dev/null +++ b/citadel/messages/changepw @@ -0,0 +1,5 @@ + You should choose a password that no one will ever be able to guess, +so no one can abuse your account. In addition, if you even suspect that +someone knows your password, change it immediately. Your password will +not be echoed to the screen when you type it, and unless you tell +someone, no one can find out what your password is. diff --git a/citadel/messages/dotopt b/citadel/messages/dotopt new file mode 100644 index 000000000..fc6a52e2d --- /dev/null +++ b/citadel/messages/dotopt @@ -0,0 +1,10 @@ +One of: + nter + ead + ide options (aides only) + oto: (type room name) + kip to: (type room name) + elp: (type name of help file) + apped rooms list + erminate + diff --git a/citadel/messages/entermsg b/citadel/messages/entermsg new file mode 100644 index 000000000..c9473abf7 --- /dev/null +++ b/citadel/messages/entermsg @@ -0,0 +1 @@ +Entering message -- end by hitting return twice. diff --git a/citadel/messages/entopt b/citadel/messages/entopt new file mode 100644 index 000000000..1477e1bcb --- /dev/null +++ b/citadel/messages/entopt @@ -0,0 +1,13 @@ +One of: + message using scii + io + onfiguration + message with ditor + reistration + essage +

assword + oom + extfile + file using modem + file using modem + file using modem diff --git a/citadel/messages/goodbye b/citadel/messages/goodbye new file mode 100644 index 000000000..5b36793b9 --- /dev/null +++ b/citadel/messages/goodbye @@ -0,0 +1,7 @@ + + < this logoff banner resides in ./messages/goodbye > + + Thanks for calling ^humannode - please call again soon! + + Also be sure to visit UNCENSORED! BBS at uncnsrd.mt-kisco.ny.us + diff --git a/citadel/messages/hello b/citadel/messages/hello new file mode 100644 index 000000000..f2ccad318 --- /dev/null +++ b/citadel/messages/hello @@ -0,0 +1,5 @@ + + < this logon banner resides in ./messages/hello > + + Welcome to ^humannode ! + diff --git a/citadel/messages/help b/citadel/messages/help new file mode 100644 index 000000000..9ff26da4d --- /dev/null +++ b/citadel/messages/help @@ -0,0 +1,31 @@ + ^variantname Help Menu + + ? Help. (Typing a '?' will give you a menu almost anywhere) + A Abandon this room where you stopped reading, goto next room. + C Chat (multiuser chat, where available) + D Prints directory, if there is one in the current room. + E Enter a message. + F Read all messages in the room, forward. + G Goto next room which has UNREAD messages. + H Help. Same as '?' + I Reads the Information file for this room. + K List of Known rooms. + L Reads the last five messages in the room. + N Reads all new messages in the room. + O Reads all old messages, backwards. + P Page another user (send an express message) + R Reads all messages in the room, in reverse order. + S Skips current room without making its messages old. + T Terminate (logout) + U Ungoto (returns to the last room you were in) + W Displays who is currently logged in. + X Toggle eXpert mode (menus and help blurbs on/off) + Z Zap (forget) room. (Removes the room from your list) + * Enter any locally installed 'doors'. + + In addition, there are dot commands. You hit the . (dot), then press the +first letter of each word of the command. As you hit the letters, the words +pop onto your screen. Exceptions: after you hit .Help or .Goto, the remainder +of the command is a help file name or room name. + + *** USE .elp ? or .elp SUMMARY for additional help *** diff --git a/citadel/messages/mainmenu b/citadel/messages/mainmenu new file mode 100644 index 000000000..03e7e2234 --- /dev/null +++ b/citadel/messages/mainmenu @@ -0,0 +1,11 @@ + ----------------------------------------------------------------------- + Room cmds: nown rooms, oto next room, <.G>oto a specific room, + kip this room, bandon this room, ap this room, + ngoto (move back) + Message cmds: ew msgs, orward read, everse read, ld msgs, + ast five msgs, nter a message + General cmds: help, erminate, hat, ho is online + Misc: toggle eXpert mode, irectory, <*> doorway + + (Type .Help SUMMARY for extended commands, to disable this menu) + ----------------------------------------------------------------------- diff --git a/citadel/messages/newuser b/citadel/messages/newuser new file mode 100644 index 000000000..6dfecc8c7 --- /dev/null +++ b/citadel/messages/newuser @@ -0,0 +1,3 @@ + + < this new user policy resides in ./messages/newuser > + diff --git a/citadel/messages/readopt b/citadel/messages/readopt new file mode 100644 index 000000000..6a5a4acfa --- /dev/null +++ b/citadel/messages/readopt @@ -0,0 +1,14 @@ +One of: + io + irectory + ile unformatted + nfo file + ast five messages + ew messages + ld messages + everse + extfile + serlist + file using modem + file using modem + file using modem diff --git a/citadel/messages/register b/citadel/messages/register new file mode 100644 index 000000000..3b70eed68 --- /dev/null +++ b/citadel/messages/register @@ -0,0 +1,7 @@ + Please enter the correct information here. In most cases we do not voice +validate, but we must have this information in order to discourage vandalism. + + If you skip this section, you will NOT get access! + + WE MEAN IT! + diff --git a/citadel/messages/roomaccess b/citadel/messages/roomaccess new file mode 100644 index 000000000..0b796a9e0 --- /dev/null +++ b/citadel/messages/roomaccess @@ -0,0 +1,17 @@ +Most rooms are public. Anyone on the system may get into a public room. +Private rooms may take several forms: + + Guess-name: to gain access to this type of room, a user need only know +the room's name. No hints are given by the system. + + Passworded: same as guess-name but the user must also know a password +to get access. + + Invitation-only: users may only gain access to this type of room if +an aide gives it to them. + + Once a user has access to a private room, it shows up on the known +rooms list and acts the same as any other room. Aides may kick users out +of any type of private room, and out of excludable rooms. + + Before making it private, think about it. Does it really NEED to be private? diff --git a/citadel/messages/saveopt b/citadel/messages/saveopt new file mode 100644 index 000000000..20c01bb51 --- /dev/null +++ b/citadel/messages/saveopt @@ -0,0 +1,7 @@ +One of: + bort + ontinue + old this message +

rint formatted + eplace string (edit) + ave message diff --git a/citadel/messages/unlisted b/citadel/messages/unlisted new file mode 100644 index 000000000..2ce76e7f8 --- /dev/null +++ b/citadel/messages/unlisted @@ -0,0 +1,3 @@ +The user list is provided as a service to the users of this system. Please +don't choose to be unlisted unless you really find it it necessary. By the +way, aides still see unlisted entries. diff --git a/citadel/msgbase.c b/citadel/msgbase.c new file mode 100644 index 000000000..7894541ba --- /dev/null +++ b/citadel/msgbase.c @@ -0,0 +1,1249 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "citadel.h" +#include "server.h" +#include +#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; a1) { + /* cprintf("Too many @'s in address\n"); */ + return(M_ERROR); + } + if (b==1) { + for (a=0; 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; alogged_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; afullroom.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)&&(afullroom.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; afullroom.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\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=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; ausernum,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) + */ +void cmd_ent0(char *entargs) +{ + int post = 0; + char recipient[256]; + int anon_flag = 0; + int format_type = 0; + char newusername[256]; /* */ + + 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) { /* */ + 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; afullroom.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; aquickroom,CC->curr_rm); + get_fullroom(&CC->fullroom,CC->curr_rm); + + foundit = 0; + for (a=0; afullroom.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 +#include +#include +#include +#include +#include +#include +#include + +long finduser(); +int fmout(); +int fpgetfield(); + +int qwk = 0; + + +#ifdef NO_STRERROR +/* + * replacement strerror() for systems that don't have it + */ +char *strerror(e) +int e; { + static char buf[32]; + + sprintf(buf,"errno = %d",e); + return(buf); + } +#endif + +void main(argc,argv) +int argc; +char *argv[]; { + struct tm *tm; + int a,b,e,mtype,aflag; + char bbb[1024]; + char subject[1024]; + FILE *fp; + long now; + + if (argc==2) if (!strcmp(argv[1],"-q")) qwk = 1; + fp=stdin; + if (argc==2) if (strcmp(argv[1],"-q")) { + fp = fopen(argv[1],"r"); + if (fp==NULL) { + fprintf(stderr,"%s: cannot open %s: %s\n", + argv[0],argv[1],strerror(errno)); + exit(errno); + } + } + +TOP: do { + e=getc(fp); + if (e<0) exit(0); + } while(e!=255); + strcpy(subject,""); + mtype=getc(fp); aflag=getc(fp); + if (qwk == 0) printf(" "); + + do { + b=getc(fp); + if (b=='M') { + if (qwk==0) { + printf("\n"); + if (strlen(subject)!=0) + printf("Subject: %s\n",subject); + } + if (aflag!=1) fmout(80,fp); + else while(a=getc(fp), a>0) { + if (a==13) putc(10,stdout); + else putc(a,stdout); + } + } + if ((b!='M')&&(b>0)) fpgetfield(fp,bbb); + if (b=='U') strcpy(subject,bbb); + if (qwk==0) { + if (b=='A') printf("from %s ",bbb); + if (b=='N') printf("@%s ",bbb); + if (b=='O') printf("in %s> ",bbb); + if (b=='R') printf("to %s ",bbb); + if (b=='T') { + now=atol(bbb); + tm=(struct tm *)localtime(&now); + strcpy(bbb,asctime(tm)); bbb[strlen(bbb)-1]=0; + printf("%s ",&bbb[4]); + } + } + } while ((b!='M')&&(b>0)); + if (qwk==0) printf("\n"); + if (qwk==1) exit(0); + goto TOP; +} + +int fpgetfield(fp,string) /* level-2 break out next null-terminated string */ +FILE *fp; +char string[]; +{ +int a,b; +strcpy(string,""); +a=0; + do { + b=getc(fp); + if (b<1) { string[a]=0; return(0); } + string[a]=b; + ++a; + } while(b!=0); + return(0); +} + +int fmout(width,fp) +int width; +FILE *fp; + { + int a,b,c; + int real = 0; + int old = 0; + char aaa[140]; + + strcpy(aaa,""); old=255; + c=1; /* c is the current pos */ +FMTA: old=real; a=getc(fp); real=a; + if (a<=0) goto FMTEND; + + if ( ((a==13)||(a==10)) && (old!=13) && (old!=10) ) a=32; + if ( ((old==13)||(old==10)) && (isspace(real)) ) { + printf("\n"); c=1; } + if (a>126) goto FMTA; + + if (a>32) { + if ( ((strlen(aaa)+c)>(width-5)) && (strlen(aaa)>(width-5)) ) + { printf("\n%s",aaa); c=strlen(aaa); aaa[0]=0; } + b=strlen(aaa); aaa[b]=a; aaa[b+1]=0; } + if (a==32) { if ((strlen(aaa)+c)>(width-5)) { + printf("\n"); + c=1; + } + printf("%s ",aaa); ++c; c=c+strlen(aaa); + strcpy(aaa,""); goto FMTA; } + if ((a==13)||(a==10)) { + printf("%s\n",aaa); c=1; + strcpy(aaa,""); goto FMTA; } + goto FMTA; + +FMTEND: printf("\n"); + return(0); +} diff --git a/citadel/netmailer.c b/citadel/netmailer.c new file mode 100644 index 000000000..e657f5a56 --- /dev/null +++ b/citadel/netmailer.c @@ -0,0 +1,292 @@ +/* + * netmailer for Citadel/UX + * see copyright.doc for copyright information + * + * netproc calls this to export Citadel mail to RFC822-compliant mailers. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#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; arc) 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\n", mid_buf, nbuf); + fprintf(rmail,"X-Mailer: %s\n", CITADEL); + fprintf(rmail,"Subject: %s\n", subject); + fprintf(rmail,"\n"); + fp=fopen(temp,"r"); + if (fp!=NULL) { + do { + a=getc(fp); + if (a>=0) putc(a,rmail); + } while(a>=0); + fclose(fp); + } + fprintf(rmail,"\n"); + pclose(rmail); + + unlink(temp); /* get rid of the ASCII file */ + exit(0); /* go back to the main program */ + } diff --git a/citadel/netpoll.c b/citadel/netpoll.c new file mode 100644 index 000000000..3e9b96c09 --- /dev/null +++ b/citadel/netpoll.c @@ -0,0 +1,267 @@ +#include +#include +#include +#include +#include +#include +#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= 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 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

\n", + argv[0],argv[0]); + exit(1); + } + + get_config(); + + attach_to_server(argc,argv); + serv_gets(buf); + printf("%s\n",buf); + if ((buf[0]!='2')&&(strncmp(buf,"551",3))) { + fprintf(stderr,"%s: %s\n",argv[0],&buf[4]); + logoff(atoi(buf)); + } + + serv_puts("INFO"); + serv_gets(buf); + if (buf[0]=='1') { + a = 0; + while (serv_gets(buf), strcmp(buf,"000")) { + if (a==1) strcpy(remote_nodename,buf); + if (a==1) printf("Connected to: %s ",buf); + if (a==2) printf("(%s) ",buf); + if (a==6) printf("%s\n",buf); + ++a; + } + } + + if (!strcmp(remote_nodename,config.c_nodename)) { + fprintf(stderr,"Connected to local system\n"); + } + else { + sprintf(buf,"NETP %s|%s",config.c_nodename,argv[3]); + serv_puts(buf); + serv_gets(buf); + printf("%s\n",buf); + + /* only do the transfers if we authenticated correctly! */ + if (buf[0]=='2') { + receive_spool(); + transmit_spool(remote_nodename); + } + } + + serv_puts("QUIT"); + serv_gets(buf); + exit(0); + } diff --git a/citadel/netproc.c b/citadel/netproc.c new file mode 100644 index 000000000..49b912d7a --- /dev/null +++ b/citadel/netproc.c @@ -0,0 +1,1341 @@ +/* + * Citadel/UX Intelligent Network Processor for IGnet/Open networks v3.6 + * Designed and written by Art Cancro @ Uncensored Communications Group + * See copyright.txt for copyright information + */ + +/* + * Specify where netproc should log to, and the mode for opening the file. + * If you are logging to a file, NPLOGMODE should be a; if you are logging to + * a device or fifo it should be w. + */ +#define NPLOGFILE "./netproc.log" +#define NPLOGMODE "a" + +/* How long it takes for an old node to drop off the network map */ +#define EXPIRY_TIME (2592000L) + +/* Where do we keep our lock file? */ +#define LOCKFILE "/var/lock/LCK.netproc" + +/* Path to the 'uudecode' utility (needed for network file transfers) */ +#define UUDECODE "/usr/bin/uudecode" + +/* Uncomment the DEBUG def to see noisy traces */ +/* #define DEBUG 1 */ + + +#include "sysdep.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 (posrc) 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; inext = 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; eO,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; enexthop,bbb); + } + if (b=='R') { + for (e=0; eR,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=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; a0) 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; anext) { + + /* 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 +#include +#include +#include +#include +#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 (posrc) 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 [arguments]\n\n"); + fprintf(stderr, "Commands: \n"); + fprintf(stderr, " nodelist (Lists all neighboring nodes\n"); + fprintf(stderr, " addnode [name] (Adds a new node to the list)\n"); + fprintf(stderr, " deletenode [name] (Deletes a node from the list)\n"); + fprintf(stderr, " roomlist [node] (List rooms being shared)\n"); + fprintf(stderr, " getcommand [node] (Show spool command)\n"); + fprintf(stderr, " setcommand [node] [cmd] (Set spool command)\n"); + fprintf(stderr, " share [node] [room] (Add a new shared room)\n"); + fprintf(stderr, " unshare [node] [room] (Stop sharing a room)\n"); + fprintf(stderr, " help (Display this message)\n"); + } + + +/* + * Display all neighboring nodes + * (This is inherently nonportable) + */ +void display_nodelist() { + FILE *ls; + char buf[256]; + + ls = (FILE *) popen("cd ./network/systems; ls", "r"); + if (ls == NULL) { + fprintf(stderr, "netsetup: Cannot open nodelist: %s\n", + strerror(errno)); + exit(errno); + } + + while (fgets(buf, 255, ls) != NULL) { + printf("%s", buf); + } + + pclose(ls); + } + + + +/* + */ +void add_node(NewNodeName) +char *NewNodeName; { + FILE *fp; + char sysfilename[256]; + + sprintf(sysfilename, "./network/systems/%s", NewNodeName); + + fp = fopen(sysfilename, "r"); + if (fp != NULL) { + fclose(fp); + fprintf(stderr, "A node named '%s' already exists.\n", + NewNodeName); + exit(2); + } + + fp = fopen(sysfilename, "w"); + if (fp == NULL) { + fprintf(stderr, "%s\n", strerror(errno)); + exit(errno); + } + + fprintf(fp, "cat %%s >>./network/spoolout/%s\n", NewNodeName); + fclose(fp); + } + + +/* + */ +void delete_node(NodeName) +char *NodeName; { + FILE *fp; + char sysfilename[256]; + char spooloutfilename[256]; + + sprintf(sysfilename, "./network/systems/%s", NodeName); + sprintf(spooloutfilename, "./network/spoolout/%s", NodeName); + + fp = fopen(sysfilename, "r"); + if (fp == NULL) { + fprintf(stderr, "'%s' does not exist.\n", + NodeName); + exit(3); + } + fclose(fp); + + unlink(spooloutfilename); + if (unlink(sysfilename)==0) { + return; + } + fprintf(stderr, "%s\n", strerror(errno)); + exit(errno); + } + + +/* + */ +void do_roomlist(NodeName) +char *NodeName; { + FILE *fp; + char sysfilename[256]; + char buf[256]; + + sprintf(sysfilename, "./network/systems/%s", NodeName); + + fp = fopen(sysfilename, "r"); + if (fp == NULL) { + fprintf(stderr, "'%s' does not exist.\n", + NodeName); + exit(3); + } + + fgets(buf, 255, fp); /* skip past spool cmd */ + while (fgets(buf, 255, fp) != NULL) { + printf("%s", buf); + fgets(buf, 255, fp); /* skip past last-sent pointer */ + } + + fclose(fp); + } + + + +/* + */ +void show_spool_cmd(NodeName) +char *NodeName; { + FILE *fp; + char sysfilename[256]; + char buf[256]; + + sprintf(sysfilename, "./network/systems/%s", NodeName); + + fp = fopen(sysfilename, "r"); + if (fp == NULL) { + fprintf(stderr, "'%s' does not exist.\n", + NodeName); + exit(3); + } + + fgets(buf, 255, fp); + printf("%s", buf); + fclose(fp); + } + + +/* + */ +void set_spool_cmd(nodename, spoolcmd) +char *nodename; +char *spoolcmd; { + struct netnode *nnptr; + + nnptr = load_node(nodename); + if (nnptr == NULL) { + fprintf(stderr, "No such node '%s'.\n", nodename); + exit(4); + } + + strncpy(nnptr->nn_spoolcmd, spoolcmd, 255); + save_node(nnptr); + } + + +/* + */ +void add_share(nodename, roomname) +char *nodename; +char *roomname; { + struct netnode *nnptr; + struct roomshare *rsptr; + long highest = 0L; + int foundit = 0; + + nnptr = load_node(nodename); + if (nnptr == NULL) { + fprintf(stderr, "No such node '%s'.\n", nodename); + exit(4); + } + + for (rsptr = nnptr->nn_first; rsptr != NULL; rsptr = rsptr->next) { + if (!strucmp(rsptr->rs_name, roomname)) { + foundit = 1; + } + if (rsptr->rs_lastsent > highest) { + highest = rsptr->rs_lastsent; + } + } + + if (foundit == 0) { + rsptr = (struct roomshare *) malloc(sizeof(struct roomshare)); + rsptr->next = nnptr->nn_first; + strcpy(rsptr->rs_name, roomname); + rsptr->rs_lastsent = highest; + nnptr->nn_first = rsptr; + } + + save_node(nnptr); + } + + +/* + */ +void remove_share(nodename, roomname) +char *nodename; +char *roomname; { + struct netnode *nnptr; + struct roomshare *rsptr, *rshold; + int foundit = 0; + + nnptr = load_node(nodename); + if (nnptr == NULL) { + fprintf(stderr, "No such node '%s'.\n", nodename); + exit(4); + } + + if (nnptr->nn_first != NULL) + if (!strucmp(nnptr->nn_first->rs_name, roomname)) { + rshold = nnptr->nn_first; + nnptr->nn_first = nnptr->nn_first->next; + free(rshold); + foundit = 1; + } + + if (nnptr->nn_first != NULL) + for (rsptr = nnptr->nn_first; rsptr->next != NULL; rsptr = rsptr->next) { + if (!strucmp(rsptr->next->rs_name, roomname)) { + rshold = rsptr->next; + rsptr->next = rsptr->next->next; + free(rshold); + foundit = 1; + rsptr = nnptr->nn_first; + } + } + + save_node(nnptr); + + if (foundit == 0) { + fprintf(stderr, "Not sharing '%s' with %s\n", + roomname, nodename); + exit(5); + } + } + + +int main(argc, argv) +int argc; +char *argv[]; { + + if (argc < 2) { + display_usage(); + exit(1); + } + + get_config(); + + if (!strcmp(argv[1], "help")) { + display_usage(); + exit(0); + } + + if (!strcmp(argv[1], "nodelist")) { + display_nodelist(); + exit(0); + } + + if (!strcmp(argv[1], "addnode")) { + if (argc < 3) { + display_usage(); + exit(1); + } + add_node(argv[2]); + exit(0); + } + + if (!strcmp(argv[1], "deletenode")) { + if (argc < 3) { + display_usage(); + exit(1); + } + delete_node(argv[2]); + exit(0); + } + + if (!strcmp(argv[1], "roomlist")) { + if (argc < 3) { + display_usage(); + exit(1); + } + do_roomlist(argv[2]); + exit(0); + } + + if (!strcmp(argv[1], "getcommand")) { + if (argc < 3) { + display_usage(); + exit(1); + } + show_spool_cmd(argv[2]); + exit(0); + } + + if (!strcmp(argv[1], "setcommand")) { + if (argc < 4) { + display_usage(); + exit(1); + } + set_spool_cmd(argv[2], argv[3]); + exit(0); + } + + if (!strcmp(argv[1], "share")) { + if (argc < 4) { + display_usage(); + exit(1); + } + add_share(argv[2], argv[3]); + exit(0); + } + + if (!strcmp(argv[1], "unshare")) { + if (argc < 4) { + display_usage(); + exit(1); + } + remove_share(argv[2], argv[3]); + exit(0); + } + + display_usage(); + exit(1); + } diff --git a/citadel/netsetup.txt b/citadel/netsetup.txt new file mode 100644 index 000000000..f954c8ddd --- /dev/null +++ b/citadel/netsetup.txt @@ -0,0 +1,53 @@ + NETWORK CONFIGURATION UTILITIES FOR CITADEL/UX + ---------------------------------------------- + + ABSTRACT + -------- + + The Citadel/UX system now comes with two utilities for managing room-sharing +with other systems: 'netsetup' (a command line utility) and 'dnetsetup' (a +curses-based front end to netsetup). Read on for more detail... + + + THE NETSETUP PROGRAM + -------------------- + + 'netsetup' is a program which eliminates the need to edit the files in your +network/systems directory in order to maintain the sharing of rooms on a +network. It allows you to make all changes from the command line. While the +program is quite usable and straightforward this way, the real objective is +for it to be controlled by any of several user-friendly front ends (such as +the 'dnetsetup' program, described below). The usage of 'netsetup' is as +follows: + + netsetup: usage: netsetup [arguments] + + Commands: + nodelist (Lists all neighboring nodes) + addnode [name] (Adds a new node to the list) + deletenode [name] (Deletes a node from the list) + roomlist [node] (List rooms being shared) + getcommand [node] (Show spool command) + setcommand [node] [cmd] (Set spool command) + share [node] [room] (Add a new shared room) + unshare [node] [room] (Stop sharing a room) + help (Display this message) + + The usage of each command should be quite straightforward from the +descriptions listed here. + + + THE DNETSETUP PROGRAM + --------------------- + + dnetsetup is a more user-friendly front end to netsetup. It's written in +shell-script and requires no compiling. Simply type 'dnetsetup' at the +command line and follow the menus to create, edit, and delete neighboring +network nodes, and to share and unshare rooms. + + + FEEDBACK + -------- + + By now you already know where I hang out. :) You can find me at +UNCENSORED! BBS at uncnsrd.mt-kisco.ny.us (telnet, www, citadel-client, etc.) diff --git a/citadel/network.txt b/citadel/network.txt new file mode 100644 index 000000000..74532e82b --- /dev/null +++ b/citadel/network.txt @@ -0,0 +1,265 @@ + Citadel/UX Network Manual + See copyright.doc for copyright information + + + OVERVIEW + + The fundamental structure of the networker is fairly simple, however, it +has enough features to make it a bit complicated. This is probably the most +difficult part of the entire Citadel/UX package. So before we dive in head +first, let's look at the various network files and directories. + + netproc.c Does all of the actual network processing. + rcit.c Feeds standard input into the networker, also + has the ability to translate UseNet news format + into Citadel/UX binary format. + netmailer.c Called by the main program when a user sends a + network mail message. + citmail.c A patch to allow Citadel/UX users to receive + mail through the normal UUCP mail facility. + cux2ascii.c A filter which translates Citadel/UX binary + format to UseNet news format. + network Directory in which all network files reside. + network/systems Contains network info for each neighboring system + network/systems/sysname Network file for a node called "sysname". + network/mail.aliases Aliases for the mailer. + network/rnews.xref Cross-references room names to newsgroup names. + network/mail.sysinfo Contains routing information for network mail. + network/filterlist The "kill file" for your system. + + + SETUP + + There are three options in setup which must be properly set. The +first is NODENAME, which must be the same as your uucp system name. The second +is HUMANNODE, which is the "long" full name of your system (for documentation). +The third is FQDN, which is your system's fully qualified domain name. If +you don't have a domain, just set this value to your NODENAME followed by the +string ".UUCP" (a traditional convention, even if you're not on a UUCP net). + + + SETTING UP SYSTEMS FILES + + For each of your neighboring Citadel/UX systems you must create a systems +file. The file is called network/systems/sysname, where sysname is the other +system's node name. The first line contains a command that transfers a spool +file to the network/spoolin directory on the remote system. The string "%s" +will be replaced by the name of the spool file by netproc. You may only use +%s ONCE in the command line. Usually, some sort of UUCP transfer will be used +to do the transfer, but you may use any facility you want, *** as long as the +file ends up in the network/spoolin directory on the remote system ***. In +a typical system, you will probably use uux to pipe the file through the +command "rcit -c" on the remote system. When the remote system receives this +command, it will copy standard input to the network/spoolin directory, and +then begin processing the file as soon as it is there. + After the command line you should enter the names of all the rooms you +intend to share with this system. Each room name should be followed by a +line containing a zero - this extra field is the "last message sent" (which +will be updated by netproc when it is run). Here is a sample systems file for +a node called uncnsrd: + +cat %s |uux - uncnsrd!rcit -c +Network Test +0 +Gateway +0 +The Room +0 + + The rooms "Network Test", "Gateway", and "The Room" will be spooled to +the remote system. These rooms should be designated as network rooms with +the .ide ditRoom command. + + + USING NETPROC + + Calling netproc with no arguments simply looks in the network/spoolin +directory for newly arrived messages, and posts them if it finds any. + To batch new messages and send them off to a remote system, the usage + + netproc sysname + + will do outbound network processing for system "sysname". It is recommended +that you use the cron program to handle your network processing on a routine +basis automatically. Arrange with your netneighbors for both of your systems +to batch new messages before actual polls take place, to guarantee that +messages travel across the network as quickly as possible. + + + NETWORKING WITH A USENET NEWS SITE + + Two filters are provided that will allow a room to be equivalent to a +UseNet newsgroup. rcit, when called without the -c argument, will assume +that standard input is in the news format, and convert it before processing. +Likewise, the filter cux2ascii.c converts Citadel format to news format, +allowing you to use command lines such as + + cat %s |cux2ascii |uux - uunet!rnews + + ...the remote system need not be a Citadel/UX. By default, room names are +the same as the newsgroup names. However, if you wish the room name to be +different, you may specify so in the network/rnews.xref file. Here is a +sample of this file: + +comp.unix.wizards,UNIX wizards +alt.drugs,Drugs and Narcotics +alt.music,Music + + It is rather simple, each line taking the form of newsgroupname,sysname. +There may not be any spaces in the newsgroup name, but there may be spaces +in the corresponding room name. + + + USING CITADEL/UX AS YOUR LOCAL E-MAIL SYSTEM + + To use Citadel/UX as your local mail system, simply define the "citmail" +program as your *local* mail delivery agent. You can plug citmail into any +popular mail routing system, including sendmail, smail, MMDF, etc. + + Once you are using citmail as your local mail delivery agent, all users +on your BBS may receive mail. They can use addresses like +"my_user_name@yoursite.com" (note the spaces in the user's name are replaced +by underscores) or "cit1234@yoursite.com" (where 1234 is the user's user +number). + + Messages may be posted by mail if you have this program installed. Simply +use the prefix "room_" -- for example, a message addressed to +"room_hot_pink_amoebas@uncnsrd.mt-kisco.ny.us would be posted in the room "Hot +Pink Amoebas" at the target system. + + PLEASE NOTE that for your BBS users to be able to send mail, you should +check the mailer command at the top of "netmailer.c" to be sure that it is +the correct mailer command for your system. You might need a command like +"sendmail %s" for SMTP or "rmail %s" for UUCP. + + + MAIL ALIASES + + The file network/mail.aliases is a simple list of aliases for the various +mailers to use. Each line takes the form + +alias,name + + Obviously, neither the alias nor the name can contain commas. The name +may also be the system name "sysop", where messages sent to sysop will +be posted in the Aide> room. + + + CITADEL/UX NETWORK MAIL + + Citadel/UX has the ability to transport mail in a simple and +transparent fashion not unlike the way public messages are sent. Users may +enter recipient names exactly as they appear on top of messages (i.e., +user name @ system name). In addition, mail routing is provided, allowing +users to send mail to systems which do not directly connect with their own. + + When entering a message in the Mail> room, a user may type a recipient +name on the local system, or on a remote system. If the recipient is not +local, citadel.c calls netmailer.c, which is a standalone program that handles +network mail. This runs in a multithreaded mode, allowing netmailer to run in +the background while the user goes on to do something else. + + + INTELLIGENT NETWORK PROCESSING AND THE MAIL.SYSINFO FILE + + There is (or soon will be) a file in your network directory called +"mail.sysinfo". In earlier releases of the network software, the system +administrator had to manually configure this file. Starting with netproc +version 2.1, the system should now create and configure the file automatically. +Note that all information may not appear in the file immediately. When a +message arrives from a system on the network, your system will attempt to +add that system to its network map. If the originating system is one of your +netneighbors, it will look for a systems file in the network/systems directory +to determine whether it is a valid neighbor. If the originating system is +not a neighbor, but the message arrived via a valid neighbor, your map will +be updated accordingly, with an entry for the new system showing the next hop +to get there. + + So, under normal circumstances you shouldn't have to configure this file at +all. But if you need to do something special, or if for some reason netproc +detects the topology wrong, here's how to configure mail.sysinfo. There +are three types of entries in this file. A "use" entry tells the system which +neighbor to route a message through to get to a particular non-neighboring +system. A "bin" entry tells the system that a particular neighbor supports +net mail. If there are systems that either do not have the netmailer or are +not running Citadel/UX, but can be reached by regular electronic mail, you +can use the "uum" command. Type "uum" followed by an address (for the user +name, use a %s which will be replaced by the user name at the remote +system. Here is a sample network map, where our system is called "myself" +and all systems have Citadel/UX EXCEPT for "gateway" and "mailsys": + + gateway---mailsys _____testbbs + / / + othersys ----- myself ----- thebox + / \_______theirsys + funboard + + In this example, our neighbors are "othersys" and "thebox". othersys +also connects to funboard, and thebox connects to testbbs and theirsys. If +everyone supports netmail, the network/mail.sysinfo file would look like this: + +funboard +use othersys + +testbbs +use thebox + +theirsys +use thebox + +othersys +bin Mail + +theirsys +bin Mail + +gateway +uum othersys!gateway!%s + +mailsys +uum othersys!gateway!mailsys!%s + + (Keep in mind that your file will contain additional system-generated +information.) + + The "bin" entries specify neighbors, the "use" entries specify routing, and +the "uum" entries specify regular UUCP mail. The method of delivery is totally +transparent to the user, who only needs to enter the recipient as user@sysname. +Note that netproc will probably stuff lots of other info into each entry. + + + THE KILL FILE + + Tired of idiots lowering the quality of the net? You can set up a "kill +file" in ./network/filterlist that can be used to filter out messages from +any user, room, or system (or any combination). The three fields should be +separated by commas, and the name "*" may be used as a wildcard in any field. +Examples: + + # Filter out user "The Idiot" in "Idiot Room" at "idiotbbs" + # + The Idiot,Idiot Room,idiotbbs + + # Filter out the same user, but in every room + # + The Idiot,*,idiotbbs + + # Filter out all messages from a system we don't like + # + *,*,idiotbbs + + # Filter out messages in a certain room from a certain system + # + *,The Room,idiotbbs + + You could also put a "*" wildcard in all three fields, essentially +disabling all incoming messages. Obviously you don't want to do this. + + + CONCLUSION + + That should cover everything you need to get running. By the way, gateway +software for StoneHenge and NYTI FordBoard systems is available upon special +request. And, a Cit86Net gateway is now available. For the latest version +of this program, or to leave comments/suggestions, call my board - +UNCENSORED! BBS at (914) 244-3252 (modem) or uncnsrd.mt-kisco.ny.us (Internet). diff --git a/citadel/network/filterlist b/citadel/network/filterlist new file mode 100644 index 000000000..dd0699dd7 --- /dev/null +++ b/citadel/network/filterlist @@ -0,0 +1,8 @@ +# This is a "kill file" for the networker. You can specify users, rooms, +# systems, or all three. The name "*" may be used as a wildcard for any +# field. See network.txt for more information. +# +# +The Banned User,*,* +The Other Banned User,This Room Only,* +*,*,The Banned System diff --git a/citadel/network/internetmail.config b/citadel/network/internetmail.config new file mode 100644 index 000000000..f14e47b71 --- /dev/null +++ b/citadel/network/internetmail.config @@ -0,0 +1,47 @@ +# +# internetmail.config +# +# This file contains a bunch of defs which you must edit to reflect the +# local environment on your system. +# +# The purpose of this file (and the programs that use it) is to enable +# the exchange of Internet e-mail to and from Citadel. + + +# Where to find the alias table. Usually there is no need to change this. +aliases = ./network/mail.aliases + +# If you are networking with Citadel-86 systems via Internet e-mail, they +# will be sending uuencoded network packets to "cit86net@yourhost.fqdn". +# Define CIT86NET to the name of the directory you wish these packets to be +# unpacked into. +cit86net spoolin = ./network/spoolin.cit86net + +# Command to use to transmit mail (usually it's sendmail) +sendmail = sendmail %s + +# Fallback mailer for delivery to non-Citadel local users (lmail, procmail...) +fallback = lmail %s + +# Other names which this host is known by (specify up to ten names) +# deliver local = uncnsrd.spaghetti.com +# deliver local = uncnsrd.local + +# Set to 0 to prevent the immediate processing of incoming Internet mail. +# This can be useful if mail tends to arrive in big batches. Setting this +# value to 1 will run netproc immediately after each message arrives. +run netproc = 0 + +# If you are participating in a network in which any Citadel may gateway +# Internet mail to any Citadel on the local network, define GW_DOMAIN to the +# base name of the domain containing all the Citadels. Otherwise, comment it +# out to disable this functionality. +gateway domain = citadelia.org + +# If you are providing access to Internet mailing lists via Citadel rooms +# in order to allow everyone to access a list without everyone having to +# subscribe to the list, TABLEFILE defines where to locate the information +# to process the lists. (This is NOT the same thing as using a networked room +# to *host* a mailing list, which you can also do.) See mailinglists.txt for +# more information. +table file = ./network/mailinglists diff --git a/citadel/network/mail.aliases b/citadel/network/mail.aliases new file mode 100644 index 000000000..46cb86329 --- /dev/null +++ b/citadel/network/mail.aliases @@ -0,0 +1,3 @@ +bbs,room_aide +root,room_aide +Auto,room_aide diff --git a/citadel/network/mail.sysinfo b/citadel/network/mail.sysinfo new file mode 100644 index 000000000..accc987d7 --- /dev/null +++ b/citadel/network/mail.sysinfo @@ -0,0 +1,217 @@ +clemsont +use uncnsrd +phonenum US (616) 323 7207 +gdom MI +humannode Clemson Tyde +lastcontact 899841267 Tue Jul 7 15:54:27 1998 + +imagesat +use uncnsrd +phonenum US (612) 884 7951 +gdom MN +humannode Images at Twilight +lastcontact 899841264 Tue Jul 7 15:54:24 1998 + +gorlocks +use uncnsrd +phonenum US (612) 5721236 +gdom MN +humannode Gorlock's Warehouse +lastcontact 899841256 Tue Jul 7 15:54:16 1998 + +houseofm +use uncnsrd +phonenum US (612)831-2312 +gdom MN +humannode House of Moo +lastcontact 899841256 Tue Jul 7 15:54:16 1998 + +fifthdim +use uncnsrd +phonenum CA (780) 432-1272 +gdom Alta +humannode Fifth Dimension +lastcontact 899841261 Tue Jul 7 15:54:21 1998 + +havenbbs +use uncnsrd +phonenum US (317)843-1288 +gdom IN +humannode Haven BBS +lastcontact 899841248 Tue Jul 7 15:54:08 1998 + +ivorytow +use uncnsrd +phonenum US 612-425-0554 +gdom MN +humannode Ivory Tower +lastcontact 899841250 Tue Jul 7 15:54:10 1998 + +kronospe +use uncnsrd +phonenum CA 403 886 4171 +gdom Alta +humannode Kronos, Penhold +lastcontact 899841245 Tue Jul 7 15:54:05 1998 + +doglink +use uncnsrd +phonenum US 612 460 6056 +gdom MN +humannode DogLink +lastcontact 899841267 Tue Jul 7 15:54:27 1998 + +cbbs +use uncnsrd +phonenum US (513) 939 1645 +gdom Cinci +humannode The CBBS +lastcontact 899841244 Tue Jul 7 15:54:04 1998 + +gateway +use uncnsrd +phonenum US (609) 931-3014 +gdom NJ +humannode Gateway +lastcontact 899841259 Tue Jul 7 15:54:19 1998 + +feathers +use uncnsrd +phonenum CA (604) 589-8539 +gdom BC +humannode Feathers & Furballs +lastcontact 899841270 Tue Jul 7 15:54:30 1998 + +eternalc +use uncnsrd +phonenum US 612 227 0215 +gdom MN +humannode Eternal City +lastcontact 899841247 Tue Jul 7 15:54:07 1998 + +peripher +use uncnsrd +phonenum US (612) 930 0055 +gdom MN +humannode Peripheral Visions +lastcontact 899840596 Tue Jul 7 15:43:16 1998 + +hub +use uncnsrd +phonenum US (612) 783-7160 +gdom MN +humannode The HUB +lastcontact 899841268 Tue Jul 7 15:54:28 1998 + +mnmensa +use uncnsrd +phonenum US (612) 730-0041 +gdom MN +humannode MN-Mensa +lastcontact 899840602 Tue Jul 7 15:43:22 1998 + +bistro +use uncnsrd +phonenum CA (250) 542-3617 +gdom BC +humannode Bistro +lastcontact 899841259 Tue Jul 7 15:54:19 1998 + +rundale +use uncnsrd +phonenum US 609 854 9135 +gdom NJ +humannode Rundale +lastcontact 899840411 Tue Jul 7 15:40:11 1998 + +jacs +use uncnsrd +phonenum US6093461224 +gdom NJ +humannode JACS +lastcontact 899841263 Tue Jul 7 15:54:23 1998 + +jdevil +use uncnsrd +humannode Jersey Devil Citadel +lastcontact 899840922 Tue Jul 7 15:48:42 1998 + +nyti +use uncnsrd +humannode New York Telephone I +lastcontact 899841262 Tue Jul 7 15:54:22 1998 + +flynnsin +use uncnsrd +phonenum CA 403 430-9193 +gdom Alta +humannode Flynn's Inn +lastcontact 899841260 Tue Jul 7 15:54:20 1998 + +ctestsys +use uncnsrd +phonenum US 612 470 9635 +gdom MN +humannode C-86 Test System +lastcontact 899841271 Tue Jul 7 15:54:31 1998 + +uncnsrd +bin Mail +phonenum US 914 244 3252 +humannode Uncensored +lastcontact 899841269 Tue Jul 7 15:54:29 1998 + +internet +uum %s +humannode Internet Gateway +lastcontact 899841231 Tue Jul 7 15:53:51 1998 + +scooby +bin Mail +phonenum US 800 555 1212 +humannode Scooby Test Server +lastcontact 899841279 Tue Jul 7 15:54:39 1998 + +gwnorth +use uncnsrd +phonenum US 317 290 8590 +gdom IGnet +humannode GWNorth +lastcontact 899841270 Tue Jul 7 15:54:30 1998 + +dogpound2 +use uncnsrd +phonenum US 215 946 1164 +gdom IGnet +humannode Dog Pound BBS II +lastcontact 899841262 Tue Jul 7 15:54:22 1998 + +fortress +use uncnsrd +humannode The Mighty Fortress +lastcontact 899841270 Tue Jul 7 15:54:30 1998 + +bearcave +use uncnsrd +humannode The Bearcave +lastcontact 899840869 Tue Jul 7 15:47:49 1998 + +emubbs +use uncnsrd +phonenum US 612 470 9635 +gdom IGnet +humannode EmuBBS +lastcontact 899841262 Tue Jul 7 15:54:22 1998 + +storm +use uncnsrd +humannode Bilskinir +lastcontact 899840870 Tue Jul 7 15:47:50 1998 + +amigazon +use uncnsrd +phonenum US (609) 953 8159 +gdom NJ +humannode The Amiga Zone +lastcontact 899841267 Tue Jul 7 15:54:27 1998 + diff --git a/citadel/network/mailinglists b/citadel/network/mailinglists new file mode 100644 index 000000000..63490d5a4 --- /dev/null +++ b/citadel/network/mailinglists @@ -0,0 +1,9 @@ +# Room name cross-reference for bidirectional mailing list gateway +# +# Each line is in the form: +# , +# +# (Yes, this implies that you can't cross-reference a room that has a +# comma in its name.) +# +gnu-win32@cygnus.com,Cygnus diff --git a/citadel/network/rnews.xref b/citadel/network/rnews.xref new file mode 100644 index 000000000..3e2073c9e --- /dev/null +++ b/citadel/network/rnews.xref @@ -0,0 +1,2 @@ +mega-net.gateway,Gateway +test.usenet,Network Test diff --git a/citadel/network/systems/uncnsrd b/citadel/network/systems/uncnsrd new file mode 100644 index 000000000..740b59ae6 --- /dev/null +++ b/citadel/network/systems/uncnsrd @@ -0,0 +1 @@ +cat %s >>./network/spoolout/uncnsrd diff --git a/citadel/public_clients b/citadel/public_clients new file mode 100644 index 000000000..504890c43 --- /dev/null +++ b/citadel/public_clients @@ -0,0 +1,9 @@ +# public_clients +# +# This file contains a list of hosts at which well-known public +# copies of client software exist. If the originating host +# for a client is one of these, then the server will use the +# location of the user that it is told by the client. + +localhost +127.0.0.1 diff --git a/citadel/rcit.c b/citadel/rcit.c new file mode 100644 index 000000000..5308bf3ed --- /dev/null +++ b/citadel/rcit.c @@ -0,0 +1,299 @@ +#define UNCOMPRESS "/usr/bin/gunzip" + +/* Citadel/UX rnews + * version 2.8 + * + * This program functions the same as the standard rnews program for + * UseNet. It accepts standard input, and looks for rooms to post messages + * (translated from UseNet format to the Citadel/UX binary message format) + * in that match the names of the newsgroups. network/rnews.xref is checked + * in case the sysop wants to cross-reference room names to newsgroup names. + * If standard input is already in binary, the -c flag should be used. + * The netproc program is then called to do the processing. + * + * If you have a separate newsreader and don't want to use Citadel for news, + * just call this program something else (rcit, for example) -- but make sure + * to tell your Citadel network neighbors the new name of the program to call. + * + * usage: + * rnews [-c] [-z] [-s] + * flags: + * -c Input is already in Citadel binary format + * (default is UseNet news format) + * -z Input is compressed, run uncompress on it before processing + * -s Don't run netproc now, just accept the input into spoolin + */ + +#include +#include +#include +#include +#include +#include +#include +#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; arc) 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=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=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') node[a]=0; + if (node[a]=='(') node[a-1]=0; + } + for (a=0; a') aaa[a]=0; + strcpy(node,aaa); + goto B; + } + goto B; + +C: if ((author[0]==0)||(room[0]==0)||(node[0]==0)) goto ABORT; + putc(255,mout); /* start of message */ + putc(MES_NORMAL,mout); /* not anonymous */ + putc(1,mout); /* not formatted */ + time(&now); + + fprintf(mout,"I%ld",mid); putc(0,mout); + fprintf(mout,"P%s",path); putc(0,mout); + fprintf(mout,"T%ld",now); putc(0,mout); + fprintf(mout,"A%s",author); putc(0,mout); + strcpy(ccc,room); + getroom(room,ccc); + fprintf(mout,"O%s",room); putc(0,mout); + fprintf(mout,"N%s",node); putc(0,mout); + if (orgname[0]!=0) { + fprintf(mout,"H%s",orgname); putc(0,mout); + } + if (recipient[0]!=0) { + fprintf(mout,"R%s",recipient); putc(0,mout); + } + if (subject[0]!=0) { + fprintf(mout,"U%s",subject); putc(0,mout); + } + fprintf(mout,"M"); + a=0; + aaa[0]=0; + + do { + a=getc(mtemp); + if (a>0) putc(a,mout); + } while (a>0); + putc(0,mout); +ABORT: fclose(mtemp); + unlink(tname); + goto A; + +END: putc(0,mout); + fclose(mout); + unlink(tname); + if (compressed_input) pclose(minput); + if (!spool_only) execlp("./netproc","netproc",NULL); + exit(0); +} + + diff --git a/citadel/readlog.c b/citadel/readlog.c new file mode 100644 index 000000000..91e196f04 --- /dev/null +++ b/citadel/readlog.c @@ -0,0 +1,99 @@ +/* + * readlog.c + * v1.4 + */ + +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#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; afullroom.FRnum[a]=0L; + } + for (a=0; afullroom.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; ausersupp.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; ausersupp)) + || (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; ausersupp)) + && ((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; ausersupp)) + && (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; ausersupp)) + && (!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; ausersupp)) + && ((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; ausersupp.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; afullroom.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(""); + } + 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; ainternal_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 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; acurr_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=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; ausersupp,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 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; alogged_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; alogged_in)) { + cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN); + return; + } + + if (CC->usersupp.axlevel<6) { + cprintf("%d You need higher access to delete floors.\n", + ERROR+HIGHER_ACCESS_REQUIRED); + return; + } + + lgetfloor(&flbuf,floor_to_delete); + + delete_ok = 1; + if ((flbuf.f_flags & F_INUSE) == 0) { + cprintf("%d Floor %d not in use.\n", + ERROR+INVALID_FLOOR_OPERATION,floor_to_delete); + delete_ok = 0; + } + + else { + if (flbuf.f_ref_count != 0) { + cprintf("%d Cannot delete; floor contains %d rooms.\n", + ERROR+INVALID_FLOOR_OPERATION, + flbuf.f_ref_count); + delete_ok = 0; + } + + else { + if (kflr_ok == 1) { + cprintf("%d Ok\n",OK); + } + else { + cprintf("%d Ok to delete...\n",OK); + } + + } + + } + + if ( (delete_ok == 1) && (kflr_ok == 1) ) flbuf.f_flags = 0; + lputfloor(&flbuf,floor_to_delete); + } + +/* + * edit a floor + */ +void cmd_eflr(char *argbuf) +{ + struct floor flbuf; + int floor_num; + int np; + + np = num_parms(argbuf); + if (np < 1) { + cprintf("%d Usage error.\n",ERROR); + return; + } + + if (!(CC->logged_in)) { + cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN); + return; + } + + if (CC->usersupp.axlevel<6) { + cprintf("%d You need higher access to edit floors.\n", + ERROR+HIGHER_ACCESS_REQUIRED); + return; + } + + floor_num = extract_int(argbuf,0); + lgetfloor(&flbuf,floor_num); + if ( (flbuf.f_flags & F_INUSE) == 0) { + lputfloor(&flbuf,floor_num); + cprintf("%d Floor %d is not in use.\n", + ERROR+INVALID_FLOOR_OPERATION,floor_num); + return; + } + if (np >= 2) extract(flbuf.f_name,argbuf,1); + lputfloor(&flbuf,floor_num); + + cprintf("%d Ok\n",OK); + } diff --git a/citadel/rooms.c b/citadel/rooms.c new file mode 100644 index 000000000..02f8dc4e2 --- /dev/null +++ b/citadel/rooms.c @@ -0,0 +1,968 @@ +/* Citadel/UX room-oriented routines */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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); + } + + + + +/* + * .ide 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 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; aHelp\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; aho knows room... + */ +void whoknows() { + char buf[256]; + serv_puts("WHOK"); + serv_gets(buf); + if (buf[0]!='1') { + printf("%s\n",&buf[5]); + return; + } + sigcaught = 0; + sttybbs(SB_YES_INTR); + while (serv_gets(buf), strncmp(buf,"000",3)) { + if (sigcaught==0) printf("%s\n",buf); + } + sttybbs(SB_NO_INTR); + } + + +void do_edit(desc,read_cmd,check_cmd,write_cmd) +char *desc; +char *read_cmd; +char *check_cmd; +char *write_cmd; + { + FILE *fp; + char cmd[256]; + int b,cksum,editor_exit; + + + if (strlen(editor_path)==0) { + printf("Do you wish to re-enter %s? ",desc); + if (yesno()==0) return; + } + + fp = fopen(temp,"w"); + fclose(fp); + + serv_puts(check_cmd); + serv_gets(cmd); + if (cmd[0]!='2') { + printf("%s\n",&cmd[4]); + return; + } + + if (strlen(editor_path)>0) { + serv_puts(read_cmd); + serv_gets(cmd); + if (cmd[0]=='1') { + fp = fopen(temp,"w"); + while (serv_gets(cmd), strcmp(cmd,"000")) { + fprintf(fp,"%s\n",cmd); + } + fclose(fp); + } + } + + cksum = file_checksum(temp); + + if (strlen(editor_path)>0) { + editor_pid=fork(); + if (editor_pid==0) { + chmod(temp,0600); + sttybbs(SB_RESTORE); + execlp(editor_path,editor_path,temp,NULL); + exit(1); + } + if (editor_pid>0) do { + editor_exit = 0; + b=wait(&editor_exit); + } while((b!=editor_pid)&&(b>=0)); + editor_pid = (-1); + printf("Executed %s\n", editor_path); + sttybbs(0); + } + else { + printf("Entering %s. ",desc); + printf("Press return twice when finished.\n"); + fp=fopen(temp,"r+"); + citedit(fp,0); + fclose(fp); + } + + if (file_checksum(temp) == cksum) { + printf("*** Aborted.\n"); + } + + else { + serv_puts(write_cmd); + serv_gets(cmd); + if (cmd[0]!='4') { + printf("%s\n",&cmd[4]); + return; + } + + fp=fopen(temp,"r"); + while (fgets(cmd,255,fp)!=NULL) { + cmd[strlen(cmd)-1] = 0; + serv_puts(cmd); + } + fclose(fp); + serv_puts("000"); + } + + unlink(temp); + } + + +void enterinfo() { /* edit info file for current room */ + do_edit("the Info file for this room","RINF","EINF 0","EINF 1"); + } + +void enter_bio() { + char cmd[256]; + sprintf(cmd,"RBIO %s",fullname); + do_edit("your Bio",cmd,"NOOP","EBIO"); + } + +/* + * create a new floor + */ +void create_floor() { + char buf[256]; + char newfloorname[256]; + + serv_puts("CFLR xx|0"); + serv_gets(buf); + if (buf[0]!='2') { + printf("%s\n",&buf[4]); + return; + } + + newprompt("Name for new floor: ",newfloorname,255); + sprintf(buf,"CFLR %s|1",newfloorname); + serv_puts(buf); + serv_gets(buf); + if (buf[0]=='2') { + printf("Floor has been created.\n"); + } + else { + printf("%s\n",&buf[4]); + } + } + +/* + * edit the current floor + */ +void edit_floor() { + char buf[256]; + + if (floorlist[(int)curr_floor][0]==0) load_floorlist(); + strprompt("New floor name",&floorlist[(int)curr_floor][0],255); + sprintf(buf,"EFLR %d|%s",curr_floor,&floorlist[(int)curr_floor][0]); + serv_puts(buf); + serv_gets(buf); + printf("%s\n",&buf[4]); + load_floorlist(); + } + + + + +/* + * kill the current floor + */ +void kill_floor() { + int floornum_to_delete,a; + char buf[256]; + + if (floorlist[(int)curr_floor][0]==0) load_floorlist(); + do { + floornum_to_delete = (-1); + printf("(Press return to abort)\n"); + newprompt("Delete which floor? ",buf,255); + if (strlen(buf)==0) return; + for (a=0; a<128; ++a) + if (!strucmp(&floorlist[a][0],buf)) + floornum_to_delete = a; + if (floornum_to_delete < 0) { + printf("No such floor. Select one of:\n"); + for (a=0; a<128; ++a) + if (floorlist[a][0]!=0) + printf("%s\n",&floorlist[a][0]); + } + } while (floornum_to_delete < 0); + sprintf(buf,"KFLR %d|1",floornum_to_delete); + serv_puts(buf); + serv_gets(buf); + printf("%s\n",&buf[4]); + } diff --git a/citadel/routines.c b/citadel/routines.c new file mode 100644 index 000000000..2d47c6b6d --- /dev/null +++ b/citadel/routines.c @@ -0,0 +1,668 @@ +/* Citadel/UX support routines */ + +#include "sysdep.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#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 (posrc) 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= 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=(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; a126) 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; a0) { + 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; a1) { + printf("Too many @'s in address\n"); + return(M_ERROR); + } + if (b==1) { + for (a=0; 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 ",buf,255); + serv_puts(buf); + } while(strcmp(buf,"000")); + return; + } + } + + +/* + * compute the checksum of a file + */ +int file_checksum(filename) +char *filename; { + int cksum = 0; + int ch; + FILE *fp; + + fp = fopen(filename,"r"); + if (fp == NULL) return(0); + + /* yes, this algorithm may allow cksum to overflow, but that's ok + * as long as it overflows consistently, which it will. + */ + while (ch=getc(fp), ch>=0) { + cksum = (cksum + ch); + } + + fclose(fp); + return(cksum); + } + +/* + * nuke a directory and its contents + */ +int nukedir(dirname) +char *dirname; { + DIR *dp; + struct dirent *d; + char filename[256]; + + dp = opendir(dirname); + if (dp == NULL) { + return(errno); + } + + while (d = readdir(dp), d != NULL) { + sprintf(filename, "%s/%s", dirname, d->d_name); + unlink(filename); + } + + closedir(dp); + return(rmdir(dirname)); + } diff --git a/citadel/routines2.c b/citadel/routines2.c new file mode 100644 index 000000000..9f7d89200 --- /dev/null +++ b/citadel/routines2.c @@ -0,0 +1,579 @@ +/* More Citadel/UX routines... + * unlike routines.c, some of these DO use global variables. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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') + || (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 ile 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 ile 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 ile ove command + */ +void movefile() { + char filename[64]; + char newroom[20]; + char cmd[256]; + + newprompt("Filename: ",filename,63); + if (strlen(filename)==0) return; + newprompt("Enter target room: ",newroom,19); + + sprintf(cmd,"MOVF %s|%s",filename,newroom); + serv_puts(cmd); + serv_gets(cmd); + printf("%s\n",&cmd[4]); + } + + +/* + * list of users who have filled out a bio + */ +void list_bio() { + char buf[256]; + int pos = 1; + + serv_puts("LBIO"); + serv_gets(buf); + if (buf[0]!='1') { + printf("%s\n",&buf[4]); + return; + } + while (serv_gets(buf), strcmp(buf,"000")) { + if ((pos+strlen(buf)+5)>screenwidth) { + printf("\n"); + pos = 1; + } + printf("%s, ",buf); + pos = pos + strlen(buf) + 2; + } + printf("%c%c \n\n",8,8); + } + + +/* + * read bio + */ +void read_bio() { + char who[256]; + char buf[256]; + + do { + newprompt("Read bio for who ('?' for list) : ",who,25); + printf("\n"); + if (!strcmp(who,"?")) list_bio(); + } while(!strcmp(who,"?")); + sprintf(buf,"RBIO %s",who); + serv_puts(buf); + serv_gets(buf); + if (buf[0]!='1') { + printf("%s\n",&buf[4]); + return; + } + while (serv_gets(buf), strcmp(buf,"000")) { + printf("%s\n",buf); + } + } diff --git a/citadel/serv_chat.c b/citadel/serv_chat.c new file mode 100644 index 000000000..a6b99cb6c --- /dev/null +++ b/citadel/serv_chat.c @@ -0,0 +1,412 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "citadel.h" +#include "server.h" +#include +#ifdef NEED_SELECT_H +#include +#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, "")) + && ((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, "")) + && ((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(""); + cprintf("%d Entering chat mode (type '/help' for available commands)\n", + START_CHAT_MODE); + + MyLastMsg = ChatLastMsg; + + if ((CC->cs_flags & CS_STEALTH) == 0) { + allwrite("",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("",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("",0, CC->chat_room, NULL); + if (!cmdbuf[6]) + strcpy(CC->chat_room, "Main room"); + else + { + strncpy(CC->chat_room, &cmdbuf[6], 20); + } + allwrite("",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 + */ +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; /* */ + + 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]) /* */ + un = ccptr->fake_username; + else + un = ccptr->usersupp.fullname; + + if ( (!strucmp(un, x_user)) + || (!strucmp(x_user, "broadcast")) ) { + strcpy(ccptr->last_pager, CC->curr_user); + emnew = (struct ExpressMessage *) + malloc(sizeof(struct ExpressMessage)); + emnew->next = NULL; + sprintf(emnew->em_text, "%s from %s:\n %s\n", + ( (!strucmp(x_user, "broadcast")) ? "Broadcast message" : "Message" ), + lun, x_msg); + + if (ccptr->FirstExpressMessage == NULL) { + ccptr->FirstExpressMessage = emnew; + } + else { + emptr = ccptr->FirstExpressMessage; + while (emptr->next != NULL) { + emptr = emptr->next; + } + emptr->next = emnew; + } + + ++message_sent; + } + } + end_critical_section(S_SESSION_TABLE); + + if (message_sent > 0) { + cprintf("%d Message sent.\n",OK); + } + else { + cprintf("%d No user '%s' logged in.\n",ERROR,x_user); + } + } diff --git a/citadel/server.h b/citadel/server.h new file mode 100644 index 000000000..c48cec936 --- /dev/null +++ b/citadel/server.h @@ -0,0 +1,129 @@ + +typedef pthread_t THREAD; + + +struct ExpressMessage { + struct ExpressMessage *next; + char em_text[300]; + }; + +/* + * Here's the big one... the Citadel context structure. + * + * This structure keeps track of all information relating to a running + * session on the server. We keep one of these for each session thread. + * + * Note that the first element is "*next" so that it may be used without + * modification in a linked list. + */ +struct CitContext { + struct CitContext *next; /* Link to next session in the list */ + + struct usersupp usersupp; /* Database record buffers */ + struct quickroom quickroom; + struct fullroom fullroom; + + char curr_user[32]; /* name of current user */ + int curr_rm; /* index of current room */ + int logged_in; /* logged in */ + int internal_pgm; /* authenticated as internal program */ + char temp[32]; /* temp file name */ + int nologin; /* not allowed to log in */ + + char net_node[32]; + THREAD mythread; + int client_socket; + struct ExpressMessage *FirstExpressMessage; + int cs_pid; /* session ID */ + char cs_room[20]; /* current room */ + long cs_lastupdt; /* time of last update */ + time_t lastcmd; /* time of last command executed */ + time_t lastidle; /* For computing idle time */ + char lastcmdname[5]; /* name of last command executed */ + unsigned cs_flags; /* miscellaneous flags */ + + /* feeping creaturisms... */ + int cs_clientdev; /* client developer ID */ + int cs_clienttyp; /* client type code */ + int cs_clientver; /* client version number */ + char cs_clientname[32]; /* name of client software */ + char cs_host[25]; /* host logged in from */ + + FILE *download_fp; /* Fields relating to file transfer */ + FILE *upload_fp; + char upl_file[256]; + char upl_path[256]; + char upl_comment[256]; + char upl_filedir[256]; + char chat_room[20]; /* The chat room */ + char dl_is_net; + char upload_type; + + char ucache_name[32]; /* For a performance boost, we cache */ + long ucache_pos; /* the position of the last user rec */ + char fake_username[32]; /* Fake username */ + char fake_postname[32]; /* Fake postname */ + char fake_hostname[25]; /* Name of the fake hostname */ + char fake_roomname[20]; /* Name of the fake room */ + char last_pager[32]; /* The username of the last pager */ + }; + +#define CS_STEALTH 1 /* stealth mode */ +#define CS_CHAT 2 /* chat mode */ +#define CS_POSTING 4 /* Posting */ + +struct CitContext *MyContext(void); +#define CC ((struct CitContext *)MyContext()) + +extern struct CitContext *ContextList; +extern int ScheduledShutdown; +extern struct CitControl CitControl; + +struct ChatLine { + struct ChatLine *next; + int chat_seq; + time_t chat_time; + char chat_text[256]; + char chat_room[20]; + char chat_username[32]; + }; + +/* + * Various things we need to lock and unlock + */ +#define S_USERSUPP 0 +#define S_USER_TRANS 1 +#define S_QUICKROOM 2 +#define S_MSGMAIN 3 +#define S_CALLLOG 4 +#define S_SESSION_TABLE 5 +#define S_FLOORTAB 6 +#define S_CHATQUEUE 7 +#define S_CONTROL 8 +#define S_HOUSEKEEPING 9 +#define MAX_SEMAPHORES 10 + + +/* + * Upload types + */ +#define UPL_FILE 0 +#define UPL_NET 1 +#define UPL_IMAGE 2 + + + +/* + * Citadel DataBases (define one for each cdb we need to open) + */ +#define CDB_MSGMAIN 0 /* message base */ +#define CDB_USERSUPP 1 /* user file */ +#define CDB_QUICKROOM 2 /* room index */ +#define CDB_FULLROOM 3 /* room message lists */ +#define CDB_FLOORTAB 4 /* floor index */ +#define MAXCDB 5 /* total number of CDB's defined */ + +struct cdbdata { + size_t len; + char *ptr; + }; diff --git a/citadel/setup.c b/citadel/setup.c new file mode 100644 index 000000000..ee16f00cf --- /dev/null +++ b/citadel/setup.c @@ -0,0 +1,1273 @@ +/* + * Citadel/UX setup program + * v3.3 / by Art Cancro + * see copyright.txt for copyright information + * + * *** YOU MUST EDIT sysconfig.h >BEFORE< COMPILING SETUP *** + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 '' 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\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 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%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&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"); + if (strlen(config.c_twitroom)==0) + strcpy(config.c_twitroom,"Trashcan"); + if (strlen(config.c_bucket_dir)==0) + strcpy(config.c_bucket_dir,"bitbucket"); + if (config.c_msgbase == 0L) + config.c_msgbase = 2000000; + if (strlen(config.c_net_password)==0) + strcpy(config.c_net_password,"netpassword"); + if (config.c_port_number == 0) { + config.c_port_number = 504; + } + if (config.c_ipgm_secret == 0) { + srand(getpid()); + config.c_ipgm_secret = rand(); + } + if (config.c_sleeping == 0) { + config.c_sleeping = 900; + } + + /* Go through a series of dialogs prompting for config info */ + for (curr = 1; curr <= MAXSETUP; ++curr) { + edit_value(curr); + } + + /* + if (setuid(config.c_bbsuid) != 0) { + important_message("Citadel/UX Setup", + "Failed to change the user ID to your BBS user."); + cleanup(errno); + } + */ + + /***** begin version update section ***** */ + /* take care of any updating that is necessary */ + + old_setup_level = config.c_setup_level; + + if (old_setup_level == 0) goto NEW_INST; + + if (old_setup_level < 323) { + important_message("Citadel/UX Setup", + "This Citadel/UX installation is too old to be upgraded."); + cleanup(1); + } + + write_config_to_disk(); + + if ((config.c_setup_level / 10) == 32) { + important_msgnum(31); + cleanup(0); + } + + if (config.c_setup_level < 400) { + config.c_setup_level = 400; + } + + /* end of 3.23 -> 4.00 update section */ + + /* end of 4.00 -> 4.02 update section */ + + old_setup_level = config.c_setup_level; + + /* end of version update section */ + +NEW_INST: + config.c_setup_level = REV_LEVEL; + write_config_to_disk(); + + system("mkdir info 2>/dev/null"); /* Create these */ + system("mkdir rooms 2>/dev/null"); + system("mkdir bio 2>/dev/null"); + system("mkdir userpics 2>/dev/null"); + system("mkdir messages 2>/dev/null"); + system("mkdir help 2>/dev/null"); + system("mkdir images 2>/dev/null"); + sprintf(aaa,"mkdir %s 2>/dev/null",config.c_bucket_dir); + system(aaa); + + + system("rm -fr ./chatpipes 2>/dev/null"); /* Don't need these */ + system("rm -fr ./expressmsgs 2>/dev/null"); + unlink("sessions"); + + important_msgnum(30); + + + a=0; + fp=fopen("calllog","r"); + if (fp==NULL) { + cre8clog(); + } + else { + fclose(fp); + if (yesno_s("Create call log?")==1) cre8clog(); + } + + check_ref_counts(); /* Check reference counts */ + check_services_entry(); /* Check /etc/services */ + check_inittab_entry(); /* Check /etc/inittab */ + + progress("Setting file permissions", 0, 3); + chown(".", config.c_bbsuid, getgid()); + progress("Setting file permissions", 1, 3); + chown("citadel.config", config.c_bbsuid, getgid()); + progress("Setting file permissions", 2, 3); + sprintf(aaa, "find . -exec chown %d {} \\; 2>/dev/null", + config.c_bbsuid); + system(aaa); + progress("Setting file permissions", 3, 3); + + important_message("Setup finished", + "Setup is finished. You may now start the Citadel server."); + + + cleanup(0); +} diff --git a/citadel/stats.c b/citadel/stats.c new file mode 100644 index 000000000..bf60e8789 --- /dev/null +++ b/citadel/stats.c @@ -0,0 +1,357 @@ +/* Citadel/UX call log stats program + * version 2.4 + */ +#include +#include +#include +#include +#include +#include "citadel.h" + +#define disply(x,y) printf("%20s %4.1f %4.1f %4d\n",x,((float)y)/calls,((float)y)/days,y) + +#define GRANULARITY 100 + +int batch_mode = 0; + +struct caller + { + struct caller *next; + char Cname[30]; + int Ctimescalled; + }; + +void get_config (); +struct config config; + + +void +prompt () +{ + char buf[16]; + if (batch_mode == 0) + { + printf ("Press return to continue..."); + fgets (buf, 16, stdin); + } + else + { + printf ("\n"); + } +} + +int +halfhour (time) /* Returns half-hour time period of time */ + long time; +{ + int a; + struct tm *tm; + tm = (struct tm *) localtime (&time); + a = (tm->tm_hour) * 3; + if ((tm->tm_min) > 19) + ++a; + if ((tm->tm_min) > 39) + ++a; + return (a); +} + + + +void +progress (curr, max) + long curr; + long max; +{ + static int dots; + int pos; + + if (curr == 0L) + { + printf ("--------------------------------------"); + printf ("--------------------------------------\r"); + fflush (stdout); + dots = 0; + } + + pos = (curr * 72) / max; + while (dots < pos) + { + putc ('*', stdout); + fflush (stdout); + ++dots; + } + + if (dots == 72) + { + printf (" "); + printf (" \r"); + fflush (stdout); + } + +} + + + + +void +main (argc, argv) + int argc; + char *argv[]; +{ + struct calllog calllog; + struct usersupp usersupp; + int file, pos, a, b, lowest; + float p, q; + long timeon[72]; + long timeup[72]; + char dname[30]; + int sess = 0; + long cftime, cttime, aa; + int calls, logins, newusers; + int badpws, terms, drops, sleeps; + long from, to, tottime; + int days, hours, minutes; + char aaa[100]; + struct tm *tm; + struct caller *callers = NULL; + struct caller *callptr = NULL; + FILE *fp, *sortpipe; + char thegraph[GRANULARITY][73]; + int pc_only = 0; + + for (a = 0; a < argc; ++a) + { + if (!strcmp (argv[a], "-b")) + batch_mode = 1; + if (!strcmp (argv[a], "-p")) + pc_only = 1; + } + + + for (a = 0; a < GRANULARITY; ++a) + strcpy (thegraph[a], + "........................................................................"); + + get_config (); + + if (pc_only) + goto PC_ONLY_HERE; + + if (!batch_mode) + printf ("Scanning call log, please wait...\n\n\n\n"); + + file = open ("calllog.pos", O_RDONLY); + read (file, &pos, 2); + close (file); + from = 0L; + to = 0L; + for (a = 0; a < 72; ++a) + { + timeon[a] = 0L; + timeup[a] = 0L; + } + cftime = 0L; + cttime = 0L; + + calls = 0; + logins = 0; + newusers = 0; + badpws = 0; + terms = 0; + drops = 0; + sleeps = 0; + file = open ("calllog", O_RDONLY); + lseek (file, (long) (pos * sizeof (struct calllog)), 0); + if (!batch_mode) + printf ("Scanning call log, please wait...\n"); + for (a = 0; a < CALLLOG; ++a) + { + if (!batch_mode) + progress ((long) a, (long) (CALLLOG - 1)); + if ((a + pos) == CALLLOG) + lseek (file, 0L, 0); + read (file, &calllog, sizeof (struct calllog)); + if (calllog.CLflags != 0) + { + if ((calllog.CLtime < from) || (from == 0L)) + from = calllog.CLtime; + if ((calllog.CLtime > to) || (to == 0L)) + to = calllog.CLtime; + strcpy (aaa, ""); + if (calllog.CLflags & CL_CONNECT) + { + ++calls; + ++sess; + if (sess == 1) + cftime = calllog.CLtime; + strcpy (dname, calllog.CLfullname); + } + if (calllog.CLflags & CL_LOGIN) + { + ++logins; + b = 0; + for (callptr = callers; callptr != NULL; callptr = callptr->next) + { + if (!strcmp (callptr->Cname, calllog.CLfullname)) + { + ++b; + ++callptr->Ctimescalled; + } + } + if (b == 0) + { + callptr = (struct caller *) malloc (sizeof (struct caller)); + callptr->next = callers; + callers = callptr; + strcpy (callers->Cname, calllog.CLfullname); + callers->Ctimescalled = 1; + } + } + if (calllog.CLflags & CL_NEWUSER) + ++newusers; + if (calllog.CLflags & CL_BADPW) + ++badpws; + if (calllog.CLflags & CL_TERMINATE) + { + ++terms; + --sess; + if (sess == 0) + { + cttime = calllog.CLtime; + for (aa = cftime; aa <= cttime; aa = aa + 300L) + timeon[halfhour (aa)] = timeon[halfhour (aa)] + 5L; + cftime = 0L; + cttime = 0L; + } + } + if (calllog.CLflags & CL_DROPCARR) + { + ++drops; + --sess; + if (sess == 0) + { + cttime = calllog.CLtime; + for (aa = cftime; aa <= cttime; aa = aa + 300L) + timeon[halfhour (aa)] = timeon[halfhour (aa)] + 5L; + cftime = 0L; + cttime = 0L; + } + } + if (calllog.CLflags & CL_SLEEPING) + { + ++sleeps; + --sess; + if (sess == 0) + { + cttime = calllog.CLtime; + for (aa = cftime; aa <= cttime; aa = aa + 300L) + timeon[halfhour (aa)] = timeon[halfhour (aa)] + 5L; + cftime = 0L; + cttime = 0L; + } + } + + if (sess < 0) + sess = 0; + + } + } + close (file); + tottime = to - from; + days = (int) (tottime / 86400L); + hours = (int) ((tottime % 86400L) / 3600L); + minutes = (int) ((tottime % 3600L) / 60L); + + printf (" Avg/Call Avg/Day Total\n"); + disply ("Calls:", calls); + disply ("Logins:", logins); + disply ("New users:", newusers); + disply ("Bad pw attempts:", badpws); + disply ("Proper logoffs:", terms); + disply ("Carrier drops:", drops); + disply ("Sleeping drops:", sleeps); + + printf ("\n"); + tm = (struct tm *) localtime (&from); + printf ("From: %s", (char *) asctime (localtime (&from))); + printf ("To: %s", (char *) asctime (localtime (&to))); + printf ("Total report time: "); + printf ("%d days, %d hours, %d minutes\n", + days, hours, minutes); + + for (aa = from; aa <= to; aa = aa + 1200L) + timeup[halfhour (aa)] = timeup[halfhour (aa)] + 20L; + prompt (); + + lowest = GRANULARITY - 1; + for (b = 0; b < 72; ++b) + { + for (a = 0; a <= GRANULARITY; ++a) + { + p = ((float) timeon[b]) / ((float) timeup[b]) * GRANULARITY; + q = (float) a; + if (p >= q) + { + thegraph[(GRANULARITY - 1) - a][b] = '#'; + if (lowest > (GRANULARITY - 1) - a) + lowest = (GRANULARITY - 1) - a; + } + } + } + + printf ("\n\n\n\n\n\n"); + + b = ((GRANULARITY - lowest) / 18); + if (b < 1) + b = 1; + for (a = lowest; a < GRANULARITY; a = a + b) + printf ("%2d%% |%s\n", + 100 - (a + 1), + thegraph[a]); + printf (" +------------------------------------------------------------------------\n"); + printf (" 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23\n"); + fflush (stdout); + prompt (); + + printf ("\n\n\n\n"); + + + printf ("Top 20 Callers (sorted by total number of logins)\n"); + printf ("Calls Avg/Day Username\n"); + printf ("----- ------- ------------------------------\n"); + fflush (stdout); + sortpipe = (FILE *) popen ("sort |tail -20 |sort -r", "w"); + for (callptr = callers; callptr != NULL; callptr = callptr->next) + { + fprintf (sortpipe, "%5d %7.2f %-30s\n", + callptr->Ctimescalled, + (((float) callptr->Ctimescalled) / ((float) days)), + callptr->Cname); + } + pclose (sortpipe); + while (callers != NULL) + { + callptr = callers->next; + free (callers); + callers = callptr; + } + prompt (); + +PC_ONLY_HERE: + printf ("Top 20 Contributing Users (post to call ratio)\n"); + printf ("P/C Ratio Username\n"); + printf ("--------- ------------------------------\n"); + fflush (stdout); + sortpipe = (FILE *) popen ("sort |tail -20 |sort -r", "w"); + fp = fopen ("usersupp", "r"); + while ((fp != NULL) + && (fread ((char *) &usersupp, sizeof (struct usersupp), 1, fp) > 0)) + { + fprintf (sortpipe, "%9.2f %-30s\n", + ((float) usersupp.posted / (float) usersupp.timescalled), + usersupp.fullname); + } + fclose (fp); + pclose (sortpipe); + exit (0); +} diff --git a/citadel/support.c b/citadel/support.c new file mode 100644 index 000000000..8ab6d1e75 --- /dev/null +++ b/citadel/support.c @@ -0,0 +1,243 @@ +#include +#include +#include +#include +#include +#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 (posrc) 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; a126) 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= n) { + strcpy(dest,""); + return; + } + strcpy(buf,source); + if ( (parmnum == 0) && (n == 1) ) { + strcpy(dest,buf); + for (n=0; n0) && (buf[0]!='|') ); + if (buf[0]=='|') strcpy(buf,&buf[1]); + for (count = 0; count=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; anter message: + * presses the nter message key. The possible values are: + * 46 - .nter message with ditor + * 4 - .nter essage + * 36 - .nter message with scii + * Normally, this value will be set to 4, to cause the nter message + * command to run Citadel's built-in editor. However, if you have an external + * editor installed, and you want to make it the default, set this to 46 + * to make it use your editor by default. + */ +#define DEFAULT_ENTRY 4 + + +/*** STRUCTURE SIZE VARIABLES ***/ + +/* You may NOT change these values once you set up your system. */ +#define MAXROOMS 128 /* Number of rooms in system */ +#define MAXFLOORS 16 /* Do not set higher than 127 */ +#define MAILSLOTS 35 /* Number of mail slots per user */ +#define MSGSPERRM 150 /* Messages per room */ +#define CALLLOG 1000 /* Number of entries in call log */ +/* Do not set MAILSLOTS higher than MSGSPERRM */ + +/* These may be changed at any time. */ +#define MAXUCACHE 10 /* Entries in server user cache */ + + +/*** END OF STRUCTURE SIZE VARIABLES ***/ diff --git a/citadel/sysdep.c b/citadel/sysdep.c new file mode 100644 index 000000000..8680e2209 --- /dev/null +++ b/citadel/sysdep.c @@ -0,0 +1,726 @@ +/* + * Citadel/UX "system dependent" stuff. + * See copyright.txt for copyright information. + * + * Here's where we (hopefully) have all the parts of the Citadel server that + * would need to be altered to run the server in a non-POSIX environment. + * Wherever possible, we use function wrappers and type definitions to create + * abstractions that are platform-independent from the calling side. + * + * Eventually we'll try porting to a different platform and either have + * multiple variants of this file or simply load it up with #ifdefs. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "citadel.h" +#include "server.h" +#include "proto.h" + +#ifdef NEED_SELECT_H +#include +#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; acs_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(lenclient_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&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; apw_gecos); + for (a=0; a 0) ? 0 : 1 ) ; + } + + /* -x specifies the desired logging level */ + else if (!strncmp(argv[a], "-x", 2)) { + strcpy(convbuf, argv[a]); + verbosity = atoi(&convbuf[2]); + } + + else if (!strncmp(argv[a], "-h", 2)) { + strcpy(convbuf, argv[a]); + strcpy(bbs_home_directory, &convbuf[2]); + home_specified = 1; + } + + /* any other parameter makes it crash and burn */ + else { + lprintf(1, "citserver: usage: "); + lprintf(1, "citserver [-tTraceFile]"); + lprintf(1, " [-d] [-xLogLevel] [-hHomeDir]\n"); + exit(1); + } + + } + + /* Tell 'em who's in da house */ + lprintf(1, "Multithreaded message server for %s\n", CITADEL); + lprintf(1, "Copyright (C) 1987-1998 by Art Cancro. "); + lprintf(1, "All rights reserved.\n\n"); + + /* Initialize... */ + init_sysdep(); + openlog("citserver",LOG_PID,LOG_USER); + + /* Load site-specific parameters */ + lprintf(7, "Loading citadel.config\n"); + get_config(); + hook_init(); + + /* Databases must be opened *after* config is loaded, otherwise we might + * end up working in the wrong directory. + */ + lprintf(7, "Opening databases\n"); + open_databases(); + + /* + * Bind the server to our favourite port. + * There is no need to check for errors, because ig_tcp_server() + * exits if it doesn't succeed. + */ + lprintf(7, "Attempting to bind to port %d...\n", config.c_port_number); + msock = ig_tcp_server(config.c_port_number, 5); + lprintf(7, "Listening on socket %d\n", msock); + + /* + * Now that we've bound the socket, change to the BBS user id + lprintf(7, "Changing uid to %d\n", BBSUID); + if (setuid(BBSUID) != 0) { + lprintf(3, "setuid() failed: %s", strerror(errno)); + } + */ + + /* + * Endless loop. Listen on the master socket. When a connection + * comes in, create a socket, a context, and a thread. + */ + while (1) { + ssock = accept(msock, (struct sockaddr *)&fsin, &alen); + if (ssock < 0) { + lprintf(2, "citserver: accept() failed: %s\n", + strerror(errno)); + } + else { + lprintf(7, "citserver: Client socket %d\n", ssock); + lprintf(9, "creating context\n"); + con = CreateNewContext(); + con->client_socket = ssock; + + /* Set the SO_REUSEADDR socket option */ + lprintf(9, "setting socket options\n"); + i = 1; + setsockopt(ssock, SOL_SOCKET, SO_REUSEADDR, + &i, sizeof(i)); + + /* set attributes for the new thread */ + lprintf(9, "setting thread attributes\n"); + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, + PTHREAD_CREATE_DETACHED); + + /* now create the thread */ + lprintf(9, "creating thread\n"); + if (pthread_create(&SessThread, &attr, (void *)sd_context_loop, + con) != 0) { + lprintf(1, + "citserver: can't create thread: %s\n", + strerror(errno)); + } + + /* detach the thread + * (defunct -- now done at thread creation time) + * if (pthread_detach(&SessThread) != 0) { + * lprintf(1, + * "citserver: can't detach thread: %s\n", + * strerror(errno)); + * } + */ + lprintf(9, "done!\n"); + } + } + } + diff --git a/citadel/sysop.txt b/citadel/sysop.txt new file mode 100644 index 000000000..d8f295a9f --- /dev/null +++ b/citadel/sysop.txt @@ -0,0 +1,265 @@ + Citadel/UX Sysop/Aide Manual + See copyright.doc for copyright information + + + OVERVIEW + + Citadel/UX, when installed properly, will do most of its maintenance by +itself. The message file loops upon itself forever, scrolling off old messages +to make space for new ones. The room files work in the same way. Other types +of maintenance can be done by cron. I have left my system unattended for long +periods of time without any software failures. + + The system has seven access levels. Most users are at the bottom and have no +special privileges. Aides are selected people who have special access within +the Citadel program. Room Aides only have this access in a certain room. +Preferred users can be selected by Aides for access to preferred only rooms. A +sysop is anyone who has access to the various sysop utilities - these are in +their own executable files, which should have their permissions set to allow +only sysops to run them. I recommend either creating a sysops group in +/etc/group, or using some other existing group for this purpose. + + Aides have access to EVERY room on the system, public and private (all +types). They also have access to commands starting with .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: + + .ide dit room Allows an aide to change certain parameters of + the current room. Lobby>, Mail>, and Aide> may + not be edited. + .ide ile elete If the current room has a directory, an Aide or + room Aide can delete files from the directory + using this command. + .ide ile 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. + .ide ile 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. + .ide edit 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 + .ead nfo file command is entered. + .ide ill room Deletes the current room. Lobby>, Mail>, and + Aide> may not be deleted. + .ide oom nvite Invites a user to the room if it is private. + .ide oom ickOut Kicks a user out of the room if it is private. + .ide serEdit Edits certain parameters of a user's account. + .ide alidate newusers Lists users who have recently registered and + prompts for new access levels. + .ide 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' nown rooms +listing, but if they .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 +.ide oom 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 /files/. + +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: + gain, ext message, top -> + Entering will delete the message. A (y/n) prompt will appear to confirm +that you really want to delete the message. + Entering will prompt for a room to move the message to. + + + SYSOP UTILITIES + + There are a number of utilities which may be accessed from the shell. It +is up to the system operator to decide which should be "sysop" utilities and +which should be accessible to all shell users. Please see utils.doc for a +description of these programs. + + + CUSTOMIZING THE HELP FILES + + The subdirectory called "help" contains your system's help files. There's +nothing hard-coded into the system that dictates what files should be there. +Whenever a user types the command "<.H>elp" followed by the name of a help +file, it displays the contents of that help file. + + The help files that come with the system, of course, are enough to guide +a user through its operation. But you can add, change, or remove help files +to suit whatever is appropriate for your system. + + Now for the fun part. There are several strings that you can put in help +files that will be automatically substituted with other strings. They are: + + ^nodename = The node name of your system on a Citadel/UX network + ^humannode = Human-readable node name (also your node name on C86Net) + ^fqdn = Your system's fully-qualified domain name + ^username = The name of the user reading the help file + ^usernum = The user number of the user reading the help file + ^sysadm = The name of the system administraor (i.e., you) + ^variantname = The name of the BBS software you're running + + So, for example, you could create a help file which looked like: + + "Lots of help, of course, is available right here on ^humannode. Of +course, if you still have trouble, you could always bug ^sysadm about it!" + + + CONCLUSION + + Comments from the Peanut Gallery should be directed to (at) my email +address ajc@uncnsrd.mt-kisco.ny.us, or call UNCENSORED! BBS at 914-244-3252 +(modem) or uncnsrd.mt-kisco.ny.us (Internet). diff --git a/citadel/user_ops.c b/citadel/user_ops.c new file mode 100644 index 000000000..9a71c3778 --- /dev/null +++ b/citadel/user_ops.c @@ -0,0 +1,1067 @@ +#define _XOPEN_SOURCE /* needed to properly enable crypt() stuff on some systems */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; aptr, 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; ausersupp,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); + /* */ + 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'; + /* */ + 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; ausersupp.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; ausersupp.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; ausersupp.lastseen[a]=0L; + CC->usersupp.generation[a]=(-1); + CC->usersupp.forget[a]=(-1); + } + for (a=0; ausersupp.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, 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='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='0')&&(buf[c]<='9')) { + b=strlen(tmpzip); + tmpzip[b]=buf[c]; + tmpzip[b+1]=0; + } + } + } + if (a==5) { + for (c=0; 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; ausersupp.mailnum[a])>(CC->usersupp.lastseen[1])) + ++mail; + + cprintf("%d %d|%d|%d\n",OK,mail,regis,vali); + } + + +/* + * check to see if a user exists + */ +void cmd_qusr(char *who) +{ + struct usersupp usbuf; + + if (getuser(&usbuf,who) == 0) { + cprintf("%d %s\n",OK,usbuf.fullname); + } + else { + cprintf("%d No such user.\n",ERROR+NO_SUCH_USER); + } + } + + +/* + * enter user bio + */ +void cmd_ebio(void) { + char buf[256]; + FILE *fp; + + if (!(CC->logged_in)) { + cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN); + return; + } + + sprintf(buf,"./bio/%ld",CC->usersupp.usernum); + fp = fopen(buf,"w"); + if (fp == NULL) { + cprintf("%d Cannot create file\n",ERROR); + return; + } + cprintf("%d \n",SEND_LISTING); + while(client_gets(buf), strcmp(buf,"000")) { + fprintf(fp,"%s\n",buf); + } + fclose(fp); + } + +/* + * read user bio + */ +void cmd_rbio(char *cmdbuf) +{ + struct usersupp ruser; + char buf[256]; + FILE *fp; + + extract(buf,cmdbuf,0); + if (getuser(&ruser,buf)!=0) { + cprintf("%d No such user.\n",ERROR+NO_SUCH_USER); + return; + } + sprintf(buf,"./bio/%ld",ruser.usernum); + + fp = fopen(buf,"r"); + if (fp == NULL) { + cprintf("%d %s has no bio on file.\n", + ERROR+FILE_NOT_FOUND,ruser.fullname); + return; + } + cprintf("%d \n",LISTING_FOLLOWS); + while (fgets(buf,256,fp)!=NULL) cprintf("%s",buf); + fclose(fp); + cprintf("000\n"); + } + +/* + * list of users who have entered bios + */ +void cmd_lbio(void) { + char buf[256]; + FILE *ls; + struct usersupp usbuf; + + ls=popen("cd ./bio; ls","r"); + if (ls==NULL) { + cprintf("%d Cannot open listing.\n",ERROR+FILE_NOT_FOUND); + return; + } + + cprintf("%d\n",LISTING_FOLLOWS); + while (fgets(buf,255,ls)!=NULL) + if (getuserbynumber(&usbuf,atol(buf))==0) + cprintf("%s\n",usbuf.fullname); + pclose(ls); + cprintf("000\n"); + } diff --git a/citadel/utils.txt b/citadel/utils.txt new file mode 100644 index 000000000..9ce507cee --- /dev/null +++ b/citadel/utils.txt @@ -0,0 +1,226 @@ + Citadel/UX Utilities Manual + See copyright.doc for copyright information + + + OVERVIEW + + The following utilities will be discussed in this document: + aidepost Post standard input to the Aide> room. NOTE: called by chat.c + whobbs Who is on the system (connected to the server, actually.) + msgstats Print lowest & highest message numbers, and current file position + stats Print the calling statistics & graph. + mailutil Mailbox utilities + msgform Format a binary message to the screen (stdin or in a file) + userlist Print the userlist. + readlog Read the caller log + useradmin Full-screen user account editor. NOTE: requires the "curses" lib + sysoputil Various and sundry sysop utilities. + + It is up to you to decide which utilities should be made accessible only +to sysops. It is important that you set the file permissions correctly. All +utilities should have access to the BBS data files, whether through ownership +or set-user-id. In addition, aidepost should be accessible by the chat program, +and userlist & whobbs should be accessible by citadel. I will attempt to +address each program individually. + + + AIDEPOST + + The nature of this program is rather simple. Standard input (stdin) is +converted into a message, filed in the main message file (msgmain), and posted +in the Aide> room. This is useful for keeping transcripts of system activity +that has to do with the BBS. You may also wish to have a really BIG msgmain +file, and have all of your normal system logs go in there also. + + + WHOBBS + + This program is similar to the "who" command. It lists all of the users +who are currently connected to your BBS server, either locally or across a +network. Unless you're running a standalone system, "who" and "whobbs" will +probably not have a one-to-one correspondence (unlike earlier versions of the +system). + + One thing to keep in mind is that the "whobbs" utility actually opens a +connection to the server. If the server is maxed out, whobbs will still be +able to provide a listing, because it doesn't need to log in to execute the +RWHO command. Note that whobbs does not list its own session. + + Running the 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 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 byte and ending after the final NULL, and print as many as it +finds until it encounters EOF. + You could use this utility along with netproc to provide printouts or +archives of certain rooms. + + + USERLIST + + This is a program to print the userlist. There are two flags that may be +set when running this program. When called without any arguments, userlist +will display all users (except those who have chosen to be unlisted), their +user numbers, times called, messages posted, screen width, and date of their +most recent call. + + Setting the -p option (only allowed by root as distributed; you may wish +to change this) also displays passwords, and lists all users regardless of +whether they are unlisted. + + Setting the -n option causes the next argument after -n to be a user +number to search for. + + You can also elect to sort the output by user name or user number by +specifying the -su or -sn flags. + + + READLOG + + Called without any arguments, readlog dumps the contents of the calllog +file. This file records all times the Citadel/UX program has been started, and +at what baud rate, as well as logins, proper logouts, loss of carrier (SIGHUP), +bad password attempts, and new user logins. + Readlog called with the -t argument displays a list of the names of the +last twenty users who have logged in. + + + USERADMIN + + Useradmin is a full-screen program to view and edit any user's account. +The program will prompt for a user name. After you enter the name, various +parameters of the user's account will appear on the screen. To change one +of the parameters, simply enter its number. + + 1. User name - you may change a user's name at any time. If you do this, +the hash table to the user file will be rewritten when you exit useradmin. + 2. User number - if you change a user's number, keep in mind that the user's +account will no longer be attached to any other records which are indexed by +user number, such as registration, room aide assignments, etc. These must be +changed as well. + 3. UID attachment - if you wish to attach a BBS account to an /etc/passwd +account, simply make the uid's the same. Likewise, if a user is to no longer +have an account in /etc/passwd, simply set this to the same as BBSUID. Note +that the user's "full name" in /etc/passwd MUST be the same as their BBS login +name, otherwise a new (and probably unwanted) account will be created. + 4. Password - these four fields are self explanatory. + 5. Screen width + 6. Times called + 7. Messages posted + 8. Last call - Date and time of last call. If you select this, +it will not prompt for a new time, but set it to the current time. + 9. Access Level - choose from the standard access levels: + 0 = Marked for deletion + 1 = New unvalidated user + 2 = Problem user + 3 = Validated user without network privileges + 4 = Validated user with network privileges + 5 = Preferred user + 6 = Aide + + The rest are boolean flags. Selecting them will toggle the options. + 10. Permanent user (do not scroll off) + 11. Print last old message on new message request + 12. Expert mode (supress automatic hints) + 13. Do not list in .RU command + 14. Prompting after each message + 15. Registered in the registration file + 16. Pause after each screenful of text + + In addition, if the user is registered, you can type -1 to make the +corresponding record in the registration file appear on the screen. + When you are finished examining or editing an account, type 0 to exit. It +will ask you if you wish to save the changes. + + + SYSOPUTIL + + This program handles some of the sysop functions of the system. It can +be called three ways: sysoputil by itself brings up the menu. Also: + sysoputil -u Execute option 7 (user purge) and exit + sysoputil -r Execute option 1 (room purge) and exit + sysoputil -g Execute option 2 (registration list) and exit + sysoputil -h Execute option 3 (rewrite hash table) and exit + + The menu choices are as follows: + + 1. Purge old rooms. This will delete any rooms which have not been written +to (posted in) in two weeks. Notification of purged rooms is posted in the +Aide> room (using the aidepost utility). + 2. Read registration file. This will partially list the registration file +to the screen (partially: street address is omitted). + 3. Rewrite hash table. Under normal conditions, the hash table of user +names is updated whenever usersupp is updated. If the system for some +reason doesn't know about any users, but they're right there for you to see +when you run userlist, chances are the hash table is out of sync. This will +hardly ever happen unless the disk was full, or you've been tinkering with +usersupp, or if you're upgrading from a non-hashing version of Citadel. This +option will rewrite the hash table from scratch. + 4. Unlock message file. The msgmain file is locked during message saves to +prevent two users from writing to the file at once. If there is some sort of +crash or bug and it remains locked, this will unlock it. + 5. Sort the userlog by user ID. + 6. Sort the userlog by user name. (Sorting the userlog is a purely cosmetic +option, and will not at all affect the performance of the system.) + 7. Purge old users. This will delete any users which have not called in +two months. Notification of purged users is posted in the Aide> room (using +the aidepost utility). + 9. Quit + + + That should cover all of the included utilities. Comments, suggestions, +etc. may be sent to ajc@uncnsrd.mt-kisco.ny.us or call UNCENSORED! BBS at +(914) 244-3252 (modem) or uncnsrd.mt-kisco.ny.us (Internet). diff --git a/citadel/utilsmenu b/citadel/utilsmenu new file mode 100755 index 000000000..631c57bae --- /dev/null +++ b/citadel/utilsmenu @@ -0,0 +1,57 @@ +# Citadel/UX Utilities Menu (shell script) +# version 1.00 - March 1989 +# see copyright.doc for copyright information + +while true +do + clear + echo + echo + echo " Citadel/UX Utilities Menu" + cat < +#include +#include +#include +#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= 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