summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThe Doctor <drwho@virtadpt.net>2013-01-29 13:35:56 -0500
committerThe Doctor <drwho@virtadpt.net>2013-01-29 13:35:56 -0500
commitb141543e501215b4b6cafa3b32c5ff0a65b95e4a (patch)
tree70d215aed601d1bf8ad5d3b9105b37a0d519c42c
Initial commit of the eBBS v3.1.1 repository.HEADmaster
Signed-off-by: The Doctor <drwho@virtadpt.net>
-rw-r--r--ChangeLog340
-rw-r--r--EBBS-Guide1440
-rwxr-xr-xInstall.sh92
-rw-r--r--Makefile219
-rw-r--r--README143
-rw-r--r--ReleaseNotes184
-rw-r--r--acct.c629
-rw-r--r--addacct.c132
-rw-r--r--bbfinger.c78
-rw-r--r--bbslog.c65
-rw-r--r--bbsmaild.c296
-rw-r--r--board.c404
-rw-r--r--c_boards.c436
-rw-r--r--c_chat.c568
-rw-r--r--c_files.c402
-rw-r--r--c_lists.c188
-rw-r--r--c_mail.c447
-rw-r--r--c_post.c494
-rw-r--r--c_talk.c442
-rw-r--r--c_users.c1063
-rw-r--r--chat.c241
-rw-r--r--chatconf.c93
-rw-r--r--chatserv.c1495
-rw-r--r--client.c449
-rw-r--r--client.h220
-rw-r--r--clientui.h129
-rw-r--r--clntcmds.h128
-rw-r--r--common.h287
-rw-r--r--complete.c406
-rw-r--r--config/COPYING339
-rw-r--r--config/access250
-rw-r--r--config/bbconfig62
-rw-r--r--config/boardlist5
-rw-r--r--config/charset-alt256
-rw-r--r--config/charset-cp866256
-rw-r--r--config/charset-koi0
-rw-r--r--config/charset-koi80
-rw-r--r--config/chatconfig16
-rw-r--r--config/chathlp.txt25
-rw-r--r--config/chatxhlp.txt19
-rw-r--r--config/editors19
-rw-r--r--config/ftplist14
-rw-r--r--config/info11
-rw-r--r--config/issue3
-rw-r--r--config/logons11
-rw-r--r--config/mailers16
-rw-r--r--config/menu.desc326
-rw-r--r--config/modes27
-rw-r--r--config/passwds12
-rw-r--r--config/permstrs26
-rw-r--r--config/protos19
-rw-r--r--config/welcome22
-rw-r--r--conv.c149
-rw-r--r--delacct.c192
-rw-r--r--edit.c94
-rw-r--r--env.c117
-rw-r--r--exec.c138
-rw-r--r--files.c1051
-rw-r--r--gram.y374
-rw-r--r--headers.c189
-rw-r--r--home.c48
-rw-r--r--init.c348
-rw-r--r--lex.yy.c1387
-rw-r--r--log.c109
-rw-r--r--login.c632
-rw-r--r--menu.l34
-rw-r--r--menus.c176
-rw-r--r--misc.c129
-rw-r--r--modes.c54
-rw-r--r--modes.h39
-rw-r--r--name.c174
-rw-r--r--netmail.c165
-rw-r--r--nmenus.c747
-rw-r--r--osdeps.h176
-rw-r--r--passwd.c71
-rw-r--r--pbbs/edit.h33
-rw-r--r--pbbs/io.c314
-rw-r--r--pbbs/io.h58
-rw-r--r--pbbs/more.c176
-rw-r--r--pbbs/screen.c694
-rw-r--r--pbbs/screen.h48
-rw-r--r--pbbs/stuff.c361
-rw-r--r--pbbs/term.c272
-rw-r--r--pbbs/vedit.c757
-rw-r--r--perms.h38
-rw-r--r--readbits.c323
-rw-r--r--readmenu.c458
-rw-r--r--readnew.c271
-rw-r--r--record.c345
-rw-r--r--retval.h99
-rw-r--r--server.h163
-rw-r--r--system.c331
-rw-r--r--talk.c397
-rw-r--r--uldl.c299
-rw-r--r--utable.c500
-rw-r--r--util.c283
-rw-r--r--y.tab.c1233
-rw-r--r--y.tab.h26
98 files changed, 27316 insertions, 0 deletions
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..d993229
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,340 @@
+----------------------------------------------------------------------------
+
+Version 3.BETA-1
+22-MAY-1994
+
+** Initial public release
+
+-----------------------------------------------------------------------------
+
+Version 3.BETA-2
+5-JUN-1994
+
+** NEW PORTS:
+** SunOS 4.x (SUNOS) and SunOS 5.x (SOLARIS)
+
+** NEW FEATURES:
+** Added lastlogin and fromhost field to LOGININFO struct, fixed bbs_login
+ to fill them in, client displays lastlogin at bottom of welcome.
+** Fixed builtin pager (more) to handle tabs and to support backward
+ scrolling with the 'b' key.
+** Added file implementation of the user table.
+** Added and fixed up the Configurable Menus. Left old code in for now
+ as a safety measure for now. Added menu description file, menu.desc,
+ into the config file directory. Also, changed for format of the access
+ file somewhat.
+** Added CTRL-C as an exit key from Chat.
+** Added key to symbols to Monitor display.
+** Added CTRL-U hotkey to Talk to get online user list.
+** Fixed main read menu so 'E' key works for Edit Post
+
+** BUG FIXES:
+** Some fixes to Makefile.
+** Fixed bug to cure garbage in USEREC fromhost field.
+** Fixed NULL-pointer bug in MailRead.
+** Fixed typecast bug in DoTalk.
+** Added missing lines to config/access.
+** Fixed bug causing bbs_check_mail to return true after unread mail
+ has been deleted.
+** Fixed chatd to disallow the empty string as a room name.
+** Minor fixes to /query in Chat.
+** Now highlighting the pointer in chat.
+** Fix so chat can be used after a system crash.
+** Fixed log function to seek to EOF before writing.
+** Fix to chat_process_incoming to end crashing with /w.
+
+-----------------------------------------------------------------------------
+
+Version 3.BETA-3
+20-JUN-1994
+
+** NEW PORTS:
+** IBM AIX 3.2.x (AIX), DEC OSF/1 (OSF), NeXTStep 3.2 (NEXTSTEP)
+
+** NEW FEATURES:
+** Added fromhost to possible environment in add-on programs, denoted by %H.
+** New bbslog utility for add-on scripts to use for writing to the bbs log.
+** New mkftplist.pl script for auto-generation of etc/ftplist.
+** Added mail/post signatures.
+** Increased length of Chat input line quite a bit (experimental)
+** Added escape keys (CTRL-C/CTRL-D) to getdata function to allow selected
+ prompts to be cancelled (login, chat entry, change username, etc).
+** Moved chat help text out of program into config files.
+** Added a "follow thread" option to Sequential Read.
+** Added M_PAGE mode to show users who are Paging
+** Added chat daemon configuration file, etc/chatconfig.
+ Chat ops and restricted accounts may be listed here; they are granted
+ denied certain privileges in chat.
+ The name of the main chat room (default 'main') by also be set here.
+** Changed /op command in chat to relinquish Op status of the sender.
+** Added a welcome message to chat telling how many users/rooms are present.
+
+** BUG FIXES:
+** Fix to set mode to "Mail" when reading/sending mail.
+** Fix for pager to not insert a blank line after 80-character lines.
+** Fixed Talk/Chat to be aware of cloak and adjust it accordingly.
+** Fixed setpgrp call in chatserv to take parameters if needed.
+** Fixed bug in menu access determination.
+
+---------------
+
+Version 3.BETA-4
+5-JUL-1994
+
+** NEW PORTS:
+** Apple A/UX (AUX)
+
+** NEW FEATURES:
+** Added ability to see real names in Query, limited by showreal entry
+ in bbconfig and C_SEEREALNAME in access.
+** Added somewhat more configurable default menu actions.
+** Moved some of the hardcoded privileged functions (see cloak, etc.)
+ into the access file for flexibility.
+** Added C_PUBLICACCT access code to identify public (shared) accounts.
+ For public accounts, the read bits are never set, and the menus in the
+ local client always default to Help.
+** Added parameter to bbs_login to allow non-public accounts to kick
+ stranded logins if their logon limit is exceeded.
+** Added a idle timeout to the local client. The length of idle time
+ before disconnection is configurable in the bbconfig file.
+** Added File Forward function so files may be forwarded from the
+ File Menu. Binary files are first uuencoded.
+
+** BUG FIXES:
+** Fixed bug making post #(BBS_MAX_FILES) inaccessible.
+** Added a new field to the read menu items so those functions could be
+ granted/denied en masse instead of just through Read/Post/Manage flags.
+** Added missing newline in bbs log entry for Kick.
+** Fixed bug in Monitor causing Cloak field (#) to not update.
+
+----------------
+
+Version 3.BETA-5
+19-AUG-1994
+
+** NEW FEATURES:
+** "Post now?" prompt added to Read New at the end of each board.
+** Status messages displayed during Read New to inform reader of progress
+ through each nonzapped board.
+** bbfinger utility added for remote user listing through an unused port.
+** Ability to prevent boards from being zapped has been added.
+** Install script and make install target added to distribution.
+
+** BUG FIXES:
+** Fixed some bugs with 80-character lines in chat.
+** Fixed bug causing marks on posts to not show up.
+** Upped idle timeout during login to 5 minutes if user is creating a
+ new account.
+** Replaced calls to access(2) since they work on real uid and we are
+ probably running setuid.
+** Added display of current pager setting to the Set Pager function.
+** Now passing fatal signals on to client-side child processes to
+ clean them up.
+** Now clearing out the global user list used by Monitor so the "holes"
+ caused by users logging out are filled upon Monitor re-entry.
+
+----------------
+
+Version 3.0
+20-SEP-1994
+
+** NEW FEATURES:
+** EBBS-Guide added to distribution.
+** New chat commands: /whois, /whoin, /long.
+** Chat command /u changed back to short format.
+** Notification of new mail added to chat.
+** New FLG_SHARED account flag replaces the C_SHAREDACCT access code.
+** Environment field added to records in etc/editors.
+
+** BUG FIXES:
+** Fix to back off to file-based user table if shmat(2) fails.
+** Fixes to prevent premature chatd death: ignore SIGPIPE and set sockets
+ to nonblocking.
+** New checks to ensure only one chat daemon runs at a time.
+** Small fixes to /i, /m and /n chat commands.
+** Rearrangement of available options after reading posts.
+** Fixed delete range to reset the message pointers correctly.
+** Changed -e tests in Install.sh to the more portable -f.
+** Display of plan no longer interprets %-escapes a la printf.
+** Temporary file now gets deleted when you abort a post.
+** Fix to addacct utility to correctly add 12-letter userids.
+** Fix to delacct utility to make -c (user clean) flag work.
+** Now preventing mail from being delivered to accounts which cannot
+ read mail and preventing paging users who can't answer.
+** Fixed menu.l and gram.y to work with lex and yacc.
+** Fixed formatting of "To:" header in group mail.
+
+----------------
+
+Version 3.0.1
+29-JAN-1995
+
+** NEW PORTS:
+** Makefile and osdeps.h entries for UNIXWARE and ULTRIX.
+** Additional Makefile notes for Solaris.
+
+** NEW FEATURES:
+** Added etc/modes configuration file, bbs_get_modestrings and
+ bbs_get_modechars functions, and removed hardcoding from modes.c.
+** Added capability to set the mode in an "exec" or "exec.pause" menu action.
+** Added usertablesize parameter in bbconfig to allow changing of the
+ user limit on the fly, without having to remove the shared memory segment.
+
+** NEW FIXES:
+** bzero changed to memset in gram.y.
+** /wall command added to chat daemon (chat manager only).
+** Fixed get_remote_host to ignore stale DEAD_PROCESS entries.
+** Changed reference to 'newbbs' in Install.sh to 'bbs'.
+** Fixed non-initialization bug in delacct utility.
+** Fixed Talk function to beep every 20 seconds for the paging user also.
+** When editing using an external editor, paging is now disabled.
+** Moved include of <sys/types.h> to common.h.
+** Fixed chat bug causing long lines to not be fully broadcast.
+** Fixed off-by-one error in chatd causing text to get lost in heavy lag.
+** Added some extra checking in the chatd to better detect users who have
+ logged out of the bbs, but somehow not out of chat.
+** Fixed bug allowing duplicate chatids.
+** Relaxed validity checking of email addresses to allow +, [, ].
+** Fixed certain toupper calls to be macro-safe.
+
+----------------
+
+Version 3.1-BETA
+12-MAY-1995
+
+** NEW PORTS:
+** Makefile and osdeps.h entries for MACHTEN and HPUX
+
+** NEW FEATURES:
+** Added ability to mail send & reply operations to send mail outside the
+ bbs by prefixing the recipient userid. The external mailer to be invoked
+ is looked up in the etc/mailers file based on the prefix.
+** Added bbsmaild utility to distribution. This may be used from the command
+ line (as bbsmail) or called by sendmail(8) to route mail into the bbs
+ mail system.
+** Added "reservedslots" entry to bbconfig. This allows any number of user
+ table slots to be reserved for sysops.
+** Readmenus are now somewhat configurable and are defined in menu.desc.
+** Added VT key support to readmenus (up/down arrows only).
+** Added a new menu navigation method that keeps the menu choices on the
+ screen at all times. This is the "Novice" menu style. The old style
+ where one selects (H)elp to get a list is the "Expert" menu style.
+ Novice is the default; there is a new API and client function that
+ allow the user to switch back and forth on the fly.
+** Added VT key support to menus (up/down arrows). More useful in the
+ Novice menu style but also works in Expert style.
+** Added a Sequential Read function to the Mail Read menu. It reads all
+ mail, not new mail. Enhanced board Sequential Read to have a "read all
+ messages" option.
+** Added "nopunct" option to chatconfig file. If set to yes, all punctuation
+ is disallowed in chatids. '*', ':', '+' are always disallowed.
+** Added message-of-the-day feature to the chat daemon.
+** Updated EBBS-Guide to include new features listed above.
+
+** NEW FIXES:
+** Modified name completion to be able to complete dynamically by calling
+ a function, in addition to completing statically from a supplied list.
+ (required for the 2-way Internet mail)
+** Fixed chat daemon to strip out "+++" in everything passing through it.
+ Chatid and roomnames may no longer contain the '+' character.
+** Fixed a bug in PostDisplay that allowed posting on post-restricted boards.
+** Added in ifdef around some utmp stuff so SunOS et al. can stomach it.
+** Couple of fixes to Install.sh.
+
+----------------
+
+Version 3.1-BETA-2
+23-OCT-1995
+
+** NEW PORTS:
+** Makefile and osdeps.h entries for FREEBSD
+
+** NEW FEATURES:
+** 8-bit character support and ability to save and change character set
+ mappings on the fly. Contributed by Alexis Yushin (alexis@ww.net).
+** New chat commands /ignore and /unignore. These need testing before
+ the final 3.1 release.
+
+** NEW FIXES:
+** One-line fixes to bbsmaild.c, board.c, c_users.c, delacct.c.
+** Fixed Makefile and Install.sh to install bbsmaild with proper
+ permissions (setuid and setgid bit on).
+** Fixed bug causing access file entries with more than one permission
+ string to not be parsed properly.
+** Bracketing characters in usernames no longer cause forwarded mail
+ and posts to bounce.
+** Uploads using ymodem and zmodem protocols work now.
+** New mail notification in chat works more than once per user session.
+
+----------------
+
+Version 3.1-BETA-3
+11-AUG-1996
+
+** NEW FEATURES:
+** Subdirectory support for file boards.
+** Added optional color support for ansi terminal type only.
+** Made the chat /ignore list persistent through chat (and login) sessions.
+** Added /date command in chat.
+** Optional feature to disallow users from ignoring chat operators.
+** Optional extra commands /roll and /think.
+
+** NEW FIXES:
+** Fix for chat /ignore to properly reset ignore list on exit.
+** Small fix for partial chat id matching.
+** Removed mkftplist.pl from distribution; no longer needed with
+ subdirectory support.
+
+----------------
+
+Version 3.1
+16-MAR-1997
+
+** NEW PORTS:
+** Makefile and osdeps.h entries for IRIX added
+
+** NEW FIXES:
+** Security fix to disallow potentially dangerous e-mail addresses
+ from being passed to sendmail. Thanks to Martin Straka
+ (straka@a1fel.feld.cvut.cz) for reporting this.
+** Some preprocessor directives rewritten to make them digestible
+ by more compilers. Thanks to Cameron Bowes for the tip.
+** Makefile and osdeps.h entries for FREEBSD fixed. Thanks to
+ Pedro Giffuni and Anthony Kehoe for assistance.
+** Fixed uninitialized variable bug causing segfaults when
+ running external programs without a mode number in menu.desc.
+** Fixed bug causing chat ignores read in from file to not be
+ properly set up. Thanks to Randy Austin for the fix.
+** Fixed an OLD bug in construction of the To: mail header
+ in long group mail lists.
+
+----------------
+
+Version 3.1.1
+17-AUG-1997
+
+** NEW FEATURES:
+** New PostMove command. See ReleaseNotes for what you have to fix in
+ your access and menu.desc files to make this work.
+** Menus now key on the first capital letter in the command name, if
+ present, instead of the first letter. This allows two commands to
+ begin with the same letter but bind to different keys, for example
+ "Snork" and "snorQue". Thanks Rodolfo Gonzales for the reminder.
+** Chat /kick command now accepts a message for display to the chat room,
+ coded by SirSyko.
+** Support added for cases where the remote host can be read from an
+ environment variable instead of utmp/utmpx. (I may post my patches
+ for linux login and sshd to make this work, sometime.)
+** Couple of benign mods for the still-under-development client/server
+ version of EBBS.
+** Additional notes for MachTen in Makefile, from Bill Schwartz.
+
+** NEW FIXES:
+** Fixed a couple of hostname-related buffer overflows in chatserv.c.
+** Chat ignore lists no longer saved between sessions for users listed
+ as 'restricted' in chatconfig.
+** Hopefully corrected the problem in the 3.1 binary distribution which
+ caused addition of new accounts to fail. I could not find an error in
+ the packaging, so have to assume it was something I did in the build.
+** config/info file updated to show Version 3.1.1, something I forgot
+ to do in the 3.1 release.
diff --git a/EBBS-Guide b/EBBS-Guide
new file mode 100644
index 0000000..fa434b2
--- /dev/null
+++ b/EBBS-Guide
@@ -0,0 +1,1440 @@
+
+Eagles BBS 3.1 Administrator's Guide
+by Ray Rocker (rocker@datasync.com)
+
+This guide is designed to help the EBBS administrator install and
+configure the bbs system. Some basic system administration knowledge
+is assumed. It is not intended as a general user's guide.
+
+TABLE OF CONTENTS
+I. Installation
+ 1. Choosing a Home Directory
+ 2. Setting the Owner and Group
+ 3. Building the Source
+ 4. Installing the Software
+II. General Configuration
+III. How Permissions Work
+ 1. Account Permissions and the permstrs file
+ 2. Operations and the access File
+IV. Configuring the Menus
+ 1. Menus
+ 2. Menu Commands
+ 3. Examples
+ 4. Security Considerations
+ 5. Readmenus
+V. Account Management
+ 1. Overview of the Account System
+ 2. Adding Accounts
+ 3. Deleting Accounts
+ 4. Modifying Account Attributes
+ 5. Limiting Logons
+VI. Board Management
+ 1. Overview of the Board System
+ 2. Adding Boards
+ 3. Deleting Boards
+ 4. Modifying Board Attributes
+ 5. Implementation Details
+VII. BBS Mail
+ 1. Local Mail Storage
+ 2. Internet Mail Support
+VIII. Setting up the File Area
+ 1. Setting Up Download and Upload Directories
+ 2. Setting Up Protocols
+ 3. File Transfer via Internet
+IX. Chat System Configuration
+ 1. Chat Overview
+ 2. Technical Information
+X. Miscellaneous
+ 1. Issue and Welcome Files
+ 2. Copyright Notice and License
+ 3. Configuring Alternate Editors
+ 4. Logging
+ 5. Mail, Post, and File Forwarding
+ 6. Modes
+ 7. 8-Bit Character Support
+XI. External Utilities
+ 1. addacct
+ 2. delacct
+ 3. bbslog
+ 4. bbfinger
+ 5. bbsmail
+Appendix A. How To Set Up a Guest Account
+
+
+I. Installation
+
+1. Choosing a Home Directory
+
+The first thing you need is a home directory for your bbs. The bbs program
+and utilities make no assumptions about where the bbs home directory is.
+This allows you maximum flexibility in placing the bbs into a suitable
+location in your file system. It also requires that the bbs program and
+utilities be able to find the bbs home directory, which I will address later.
+
+You should be sure to pick a place with plenty of room to grow. The bbs
+executables and configuration files total about 500K initially. As you add
+users and boards, the disk usage will naturally go up. Accounts and boards
+per se take very little space; posts and mail will occupy the majority of
+the space under the bbs home directory. You should also make sure you have
+plenty of inodes available in your filesystem. Each account will consume
+up to 8 inodes plus one for each mail message. Each board will consume up
+to 2 inodes plus one for each post. The total inode usage can get large in
+a hurry, so placing the bbs in a separate filesystem from other high inode
+usage software (such as a Usenet news spool) would be a good idea.
+
+The bbs home directory will be referred to as /home/bbs (where the full path
+is called for) or ~bbs (where shorthand is ok) in the rest of this document.
+Remember to mentally substitute in your selected home directory!
+
+
+2. Setting the Owner and Group
+
+Next, you need to decide on an owner and group for the bbs. The bbs runs as
+a single user on your system -- individual accounts do not have their own
+uids in /etc/passwd. ~bbs and everything beneath it must be owned by the
+same uid and gid.
+
+The simplest solution is to create a 'bbs' account in /etc/passwd. If you
+will be allowing remote access to your bbs (via telnet or rlogin) you will
+need this passwd entry anyway. It should look like this:
+
+bbs::9999:99:Bulletin Board System:/home/bbs:/home/bbs/bin/lbbs
+
+Remember to substitute your bbs home directory for "/home/bbs" in the above.
+
+The uid (9999) can be any unused value. The gid (99) can be an existing
+gid defined in /etc/group, or a good idea would be to create a bbs group:
+
+bbs::99:bbs
+
+While we're editing /etc/passwd, I've found that a bbsadm account, with
+the same uid/gid as bbs but a real login shell, is very handy.
+
+bbsadm::9999:99:BBS Administrator:/home/bbs:/bin/sh
+
+Be sure to put a secure password on this account though! Now, if you do
+the rest of the installation as bbsadm, you won't have to worry about
+file ownerships being wrong.
+
+Now you can create /home/bbs (or whatever), chown it to bbs (or whoever),
+chgrp it to bbs (or whichever), and chmod it to 770. Directory permissions
+of 770 will allow full access to the bbs owner and group and no access to
+anyone else. You might use 700 if only bbs and bbsadm are to be allowed into
+the bbs area.
+
+
+3. Building the Source
+
+If you are using the binary distribution of EBBS 3.1, this section does
+not apply. Skip ahead to "Installing the Software".
+
+Unpack the source distribution if you haven't already.
+
+First, edit osdeps.h to set the ifdefs correctly for your operating system.
+It is already set up for LINUX, SUNOS, SOLARIS, AIX, OSF/1, NEXTSTEP, and AUX.
+Any other operating systems may require more files to be edited.
+
+Second, edit the Makefile, filling in the top part as necessary. If you
+chose not to create a bbs account in /etc/passwd, you must set INSTALLDIR
+to point to the bbs home directory. There are a couple of #defines not
+covered in osdeps.h you can put in the CCFLAGS line to enable/disable
+optional or experimental features. Some of these are:
+
+-DCOLOR, which will allow ANSI escape sequences in config files such
+as the welcome screen or menu.desc to be passed through. If COLOR is
+not defined, these escapes will be stripped out. Also, the chat area
+will be somewhat colorized. -DCOLOR is present in the distribution
+Makefile.
+
+-DMENU_STANDOUT, which will cause the verbose ("newbie") menus to use
+standout instead of the little arrows
+
+-DEXTRA_CHAT_STUFF, which will enable some extra chat commands
+that aren't listed in the chat help files
+
+-DNO_IGNORE_CHATOPS, which will make it impossible to /ignore users
+designated as chat operators.
+
+There are others that probably aren't worthy of your attention.
+
+The y.tab.h, y.tab.c, and lex.yy.c files were generated by flex 2.4.6 and
+GNU Bison 1.22 for Linux. They are included so that you don't need flex
+and bison to build EBBS, but may not work on all non-Linux systems. If you
+have a scanner (flex, lex, etc.) and parser generator (bison, yacc, etc.)
+installed on your system, you may delete y.tab.h, y.tab.c, and lex.yy.c
+and use the Makefile to build them. Set YACC, LEX, and YFLAGS appropriately
+in the Makefile.
+
+"make" with the supplied Makefile just builds the lbbs and chatd programs.
+
+"make install" will create all the directories you need and copy the
+executable and configuration files under ~bbs. NOTE: You should do the
+install from the bbsadm account to get all the ownerships and permissions
+right! If not you should do a "chown -R bbs ~bbs" to set them straight.
+"make install" may also be used to copy new executables to the bbs if
+you do any source modifications. It will not overwrite existing config files.
+
+Alternatively, you can do a "make bindist" and then follow the directions
+for installing the binary distribution in the following section of this Guide.
+If your /bin/sh doesn't like the install script, this may come in very handy.
+
+Other useful Makefile targets:
+"make clean" deletes all of the object files.
+"make clear" deletes all of the executables.
+"make clobber" does both a clean and clear.
+"make srcdist" makes the source distribution just like this one.
+
+
+4. Installing the Software
+
+If you successfully did a "make install" with the source distribution,
+you're done! Skip to Chapter II (General Configuration).
+
+If you have a binary distribution (either to start with, or the result of
+"make bindist" in the above section) just copy the tar archive into ~bbs
+and unbundle it with "tar -xvf". If you do this as someone other than the
+bbs owner, you must set the owner and group of all extracted files to the
+bbs owner and group, and restore the setuid/setgid bits on ~bbs/bin/lbbs.
+
+And that's that. You're ready to configure and run.
+
+
+II. General Configuration
+
+Every file used and created by the bbs will be under ~bbs. Initially,
+you have a bin subdirectory containing the local bbs shell (lbbs), the
+chat daemon (chatd), and some other utilities; an etc subdirectory
+containing all the bbs configuration files; a home subdirectory under
+which information and mail for each bbs account will go (the SYSOP account
+is already there); a boards subdirectory under which information and
+posts for each board will go; and a tmp directory where work files will
+be temporarily placed.
+
+The rest of this Guide is primarily concerned with the bbs configuration
+files under ~bbs/etc. Most of these files are record-oriented, and in
+these any line beginning with # is a comment. You must edit all of these
+by hand EXCEPT ~bbs/etc/passwds and ~bbs/etc/boardlist, which lbbs
+manipulates via commands in the (A)dmin menu. You should use caution
+when editing these two files by hand, especially ~bbs/etc/passwds, because
+the records in it must all be exactly the same length.
+
+The first one we'll look at is ~bbs/etc/bbconfig. This is the startup
+configuration and MUST be present for lbbs or any of the utilities to run.
+Recall that I pointed out in Chapter I of this Guide that these programs
+do not automatically know where the bbs home directory is (and thus where
+bbconfig is). So, to run any of these from the command line you must either
+be currently in ~bbs, OR have the environment variable BBSHOME pointing to
+/home/bbs (or whatever). So, take this time to define BBSHOME in your
+.profile or .login -- or better yet in /etc/profile or /etc/csh.login.
+
+That out of the way, look at bbconfig. Read the comments carefully.
+You set things like the name of your bbs, how many users may log in
+at one time, the location of the bbs log file and how much stuff gets
+logged, and whether new users may create their own accounts.
+Change the default values to your liking.
+
+Now is probably a good time to log in and set the SYSOP password.
+Execute ~bbs/bin/lbbs (you did set BBSHOME, right?). Type SYSOP at the
+"Enter userid" prompt, and 'password' (without the quotes) as the password.
+If all is well, you're in and can start navigating the menus. You should
+change that SYSOP password first thing -- enter X to go to the Xyz Menu
+and then P to change the password.
+
+If anything went wrong, you should reread Chapters I and II of this Guide
+to make sure you didn't miss anything. If something REALLY went wrong
+(like a fatal signal telling you to Run! Flee!) you may need to fix the
+source and rebuild lbbs. The rest of this Guide is a reference for configuring
+the menus and everything else.
+
+
+III. How Permissions Work
+
+1. Account Permissions and the permstrs File
+
+The bbs uses a very flexible scheme for allowing and disallowing access
+to each menu function. With flexibility comes complexity, however, and
+that is compounded by the fact that (with luck) someday in the future
+this bbs will be distributed, with a client composed of the user interface
+and menus on a remote host and a much smaller server portion to run on
+the bbs host.
+
+Each bbs account has a 32-bit permission mask -- the third field in each
+~bbs/etc/passwds record. It is stored as an 8-digit hexadecimal number.
+Each bit in that field corresponds to a permission name in ~bbs/etc/permstrs.
+Ten of the thirty-two permstrs are already defined. The first five of these,
+Basic-1, Basic-2, Basic-3, Basic-4, and Sysop, corresponding to the 5 least
+significant bits in the 32-bit mask, are used by the system. You can change
+the names on these but NOT the function. Five others are used not by the
+system but only in the default access and menu.desc files.
+
+Basic-1 through Basic-4 are automatically granted to every account upon
+creation. So, when a new account is created, the third field in the passwds
+file will be 0000000f. The ready-made SYSOP account has all 10 predefined
+permissions, so its third field is 000003ff.
+
+The strings in the permstrs file are what you see when setting the permissions
+on an account or board via the (P)ermissions, (N)ewBoard, or (C)hangeBoard
+functions on the Admin Menu. Any strings you add to ~bbs/etc/permstrs will
+replace the "(unused)" entries.
+
+The position of the bits in the 32-bit mask are what is important -- the
+strings are just to make the Admin Menu functions friendlier. This is why
+you must ONLY add or change lines in the permstrs file, never delete them.
+For instance, if you delete the Basic-4 line, then the string "Sysop"
+becomes associated with the 4th bit in the permission setting screen.
+But the 5th bit is the one that the system uses to determine if a user
+has full sysop permission!
+
+
+2. Operations and the access File
+
+I hope you're not lost yet, because there's more. The ~bbs/etc/access file
+lists 95 operations that the bbs can perform. Entries in this file are in
+the following format:
+
+<operation-name>={ALL | NONE | <permstr-name>[,<permstr-name>,...]}
+
+For now, just remember this: do NOT edit the <operation-name> or reorder
+the first 95 of these. In a nutshell, these serve to limit access to each
+operation to those accounts with the bits corresponding to the permstr-names
+turned on, or to nobody (NONE), or to everybody (ALL). For example:
+
+Login=ALL
+Everyone can do the login operation.
+
+AddAccount=AccountMgr
+Only accounts with the AccountMgr bit (as shown in (A)dmin/(P)ermissions)
+can do the AddAccount operation.
+
+GetPermStrings=AccountMgr,BoardMgr
+Accounts with either the AccountMgr or BoardMgr bit can do the
+GetPermStrings operation.
+
+Upload=NONE
+No one can do the Upload operation (until you change the NONE).
+
+Most of these really don't mean anything at present -- they are set up
+for the planned future client/server split -- EXCEPT to the menu system.
+The operation-names are referenced in the ~bbs/etc/menu.desc file, and
+in conjunction with that you probably want to modify some of the records
+in ~bbs/etc/access. That is the subject of the next chapter.
+
+
+IV. Configuring the Menus
+
+1. Menus
+
+The bbs menus are completely configurable via the ~bbs/etc/menu.desc file.
+There are comments in that file explaining the syntax. Here, I will try to
+explain briefly what it all means as well:
+
+A menu is defined as follows:
+menu <menu-name>, <menu-title>, <menu-prompt>, <default-action> {
+<command-entry>,
+...
+}
+
+The <menu-name> string is used in the command-entries for jumping between
+menus (more on this below).
+
+The <menu-title> is displayed in the upper left corner of the screen when
+the menu is active. A "*" here means to display the name of the bbs, as
+given in ~bbs/etc/bbconfig.
+
+The <menu-prompt> string is the prompt displayed when asking the user
+for a command.
+
+The <default-action> string specifies the first letter of the default
+command when the menu is first entered. If this string begins with $,
+then it should have two letters following: the first is the default
+command if no new mail is waiting, and the second is the default command
+if new mail is waiting.
+
+
+2. Menu Commands
+
+Each menu is made of one of more command entries in the following form:
+
+(<command-name>, <next-command>, <error-command>, <operation-name>,
+ <action>, <help-string>)
+
+The <command-name> string is the command to be typed by the user.
+Each command name is bound to a single letter key which identifies
+it in the menu. Usually this key is the first letter of the <command-name>,
+but starting in version 3.1.1, the first capital letter is used as the
+key if one is present. There is an example in the next section.
+
+Each <command-name> must have a unique key within its menu. If you
+use the same key twice in a menu, you will get an "attempt to use
+filled slot" error on startup and the second instance will not show up.
+
+The <next-command> string is the default next command after completion
+of this command. If an error occurs during the command, then the
+<error-command> is the default next command.
+
+The <operation-name> is where the ~bbs/etc/access file comes into play.
+For this menu command to be available to the user, their account must
+have permission to perform the operation, as defined in ~bbs/etc/access.
+Here is where all the business with 32-bit permission values, permstrs,
+and operations comes together. Pay close attention to this example:
+
+("Welcome", "Exit", "Exit", EditWelcome, $EditWelcome,
+"(W)elcome Edit the Welcome Screen")
+
+This is the menu command on the Xyz Menu through which the Welcome screen
+may be edited. The operation associated with this menu command is
+EditWelcome. EditWelcome MUST be listed in ~bbs/etc/access, or you'll
+see a "does not grok" error when the bbs starts up. It's there though:
+
+EditWelcome=WelcomeMgr
+
+This means that a user must have the WelcomeMgr permission (bit 10 in
+the mask since it's the 10th string in ~bbs/etc/permstrs) in order to
+do the EditWelcome operation, and hence to access the (X)yz/(W)elcome
+menu command. I'll give more examples of how to use this later.
+^L
+
+The <action> string is the action to be taken by the menu command.
+These all begin with $ and refer to functions built into lbbs.
+Two special cases take parameters:
+
+$Menu takes the name of another menu defined in menu.desc and jumps to
+that menu. For example: $Menu "Xyz" means this command should jump to
+the Xyz menu.
+
+$exec, $exec.pause, $exec.more are available for you to hook in any
+external programs into the bbs menu system. $exec executes an arbitrary
+program. $exec.pause does the same and prompts the user to press RETURN
+before returning to the menu prompt. $exec.more pipes the program's
+output through the builtin 'more'-like pager.
+
+The format for the $exec actions is $exec "program[:environment[:mode]]"
+For example:
+
+$exec "/bin/sh"
+Executes /bin/sh, then returns to the bbs menu prompt.
+
+$exec "/bin/sh:TERM=ansi:11"
+Same as above but adds TERM=ansi to the environment passed to /bin/sh,
+and sets the mode to 11 while the exec is in effect. By adding an entry
+for mode 11 in ~bbs/etc/modes, you can cause an arbitary string to display
+in the (U)sers list. See Chapter X under "Modes" for details.
+
+The $exec actions provide a default environment, which is defined at the
+top of the menu.desc file in the "Environment" entry. You may edit this
+line as you like. Any environment specified in the $exec action is added
+to, and possibly overrides, this default environment.
+
+Information associated with the bbs user may be passed in the environment.
+%T, %I, %E, %U, and %H are escape sequences which are replaced by the
+bbs user's termtype, userid, editor, username, and remote host, respectively,
+in the constructed environment. Example:
+
+$exec "/usr/local/bin/irc:IRCNICK=%U,USER=%I,TERM=%T"
+
+Finally, the <help-string> in a menu command is the string displayed for
+the command by the builtin $Help command.
+
+
+3. Examples
+
+Now for some practical examples of how all of this mumbo-jumbo works.
+
+Example #1. How to restrict a guest account from posting.
+You'll notice an entry in the Main menu to posting messages:
+
+("Post", "Read", "Read", Post, $Post,
+"(P)ost Post a message on current board")
+
+The operation associated with this menu command is, suprisingly enough,
+Post. Looking in ~bbs/etc/access you'll find the 47th operation:
+
+Post=ALL
+
+You want users to be able to post by default, just not the guest account.
+So, you want to restrict the Post operation to one of the four Basic
+permissions, which all accounts get by default. Let's change it to:
+
+Post=Basic-1
+
+There. Now all you have to do is go to (A)dmin/(P)ermissions, bring up
+the screen for 'guest', and change Basic-1 from ON to OFF. Voila.
+'guest' cannot use (P)ost on the main menu. In addition, since the builtin
+reply functions also test the Post operation, guests won't get the option
+to post followups when reading either.
+
+Example #2. How to hook an IRC client into the Talk Menu.
+In menu.desc you first must add a command into menu "Talk". Fortunately
+there is no command in that menu beginning with I already, so this will
+work:
+
+("IRC", "IRC", "Exit", EnterIRC,
+$exec "/usr/local/bin/irc:IRCNICK=%U,USER=%I,TERM=%T"
+"(I)RC Enter IRC Chat")
+
+Now, where did I get the EnterIRC operation? It's not in the access file!
+Well, you can add it. Just put it at the END of ~bbs/etc/access after the
+85 predefined operations. You can have as many of your own operations as
+you want there. What about the right-hand side? It can be ALL if you want
+everyone to be able to use IRC. Or, use one of the predefined permissions
+like Basic-1. Or, add a permission string to ~bbs/etc/permstrs and turn
+on that bit for those users you want to be able to use IRC.
+
+Example #3. How to put two commands starting with the same letter into
+a menu by using different keys.
+
+("icB", "icB", "Exit", EnterICB,
+$exec "/usr/local/bin/icb:USER=%I,TERM=%T"
+"ic(B) Enter ICB Chat")
+
+Suppose you want to add a gateway to ICB just like you did for IRC in
+the previous example. But, the letters I and C are already used on
+this menu. Get around it by forcing the "IC" in ICB to lower case in
+the command name and leaving a B upper case. This tells lbbs that you
+want to use the letter B for this menu selection. Reflect this in the
+help string too, and voila.
+
+Hopefully the flexibility of this complex permission system is apparent
+by now, and you'll forgive me for making it so hard to understand :-)
+
+
+4. Security Considerations
+
+An important note about security. Remember that all files under ~bbs are
+owned by the bbs owner, and bbs users are running as the bbs owner. Hence
+you should take EXTREME CARE that any external program you hook in via
+$exec does not have the ability to read and write arbitrary files on your
+system, or even worse spawn a shell! (PLEASE, do NOT make root the bbs
+owner under any circumstances!)
+
+In the IRC client example above, you should know that IRC clients have
+CD and EXEC commands, which are probably not things you want users of
+your bbs to be able to do. You should compile them, and anything else
+that looks suspicious, out of the IRC client you make available. This
+holds true not only for IRC but any external program you choose to make
+available, of course.
+
+
+5. Readmenus
+
+As of version 3.1, the readmenus are no longer hardcoded and are defined
+in the menu.desc file as well. These are the screens which allow the user
+to navigate through their mailbox, a board, or a file board. So, you may
+want to rearrange, add some new keystrokes, or change the help text or the
+general look of the readmenus. You cannot, however, add readmenus (there
+are three and only three) or invoke arbitrary commands via them like you
+can in the primary bbs menus.
+
+The syntax is similar to that of the primary bbs menus described in the
+earlier sections of this chapter, and there's probably no urgent need for
+you to mess with the readmenu configuration, so I won't go into detail here.
+
+A rundown of the syntax of the readmenus is given in ~bbs/etc/menu.desc,
+right before the first readmenu is defined.
+
+
+V. Account Management
+
+1. Account System Overview
+
+Basic information about each bbs account is recorded in the ~bbs/etc/passwds
+file. This consists of the userid, encrypted password, permission mask,
+account flags, and username.
+
+SYSOP :AA6tQYSfGxd/A :000003ff:0000:System Operator :
+
+IMPORTANT: Each record must be the same length, or this file may be
+corrupted when the bbs writes out a modified record (when any of the
+fields are changed through Xyz or Admin menu commands). Therefore manual
+editing must be done with great care, and you should make a backup
+copy of this file before doing any manual editing. Keep this in mind
+even as I tell you a few paragraphs from now that you *have* to edit
+it by hand to set certain account flags.
+
+Also, each account has two directories associated with it: ~bbs/home/<userid>
+and ~bbs/home/<userid>/mail. Naturally, the user's mail is stored under
+the latter of these. Files stored in the former include:
+
+lastlogin: The user's remote host name is written here at login time.
+overrides: User-configured list used to control Talk requests.
+plan: User-created plan file, shown by the $Query action.
+profile: Extra configuration information not stored in the passwds file.
+ Real name, address, Internet email path, editor, terminal type, and
+ upload/download protocol may be here. Certain default values (such as
+ vt100 for the terminal type) are not stored.
+readbits: Used for saving information about which posts and mail have
+ been read. It consists of one record for each board which has been
+ accessed, and one for mail (" $MAIL "). This is also where the new
+ message scan configuration (currently just the $Zap action) is stored.
+signature: User-created signature file for posts and mail.
+
+
+2. Adding Accounts
+
+Accounts may be added by the $AddAccount action (Admin Menu/Add User).
+You will be prompted to enter all the pertinent information. This will
+create a record in the passwds file and create the user's directories
+and profile. You may also use the addacct utility program for adding
+accounts from the shell (see Chapter XI of this guide for details).
+
+NOTE: Just adding lines to the passwds file is NOT sufficient!
+You must add the directories too. Adding accounts in this way is not
+recommended.
+
+You can allow users to create accounts on demand by setting "new=yes" in
+~bbs/etc/bbconfig. The users can then enter "new" at the bbs login prompt
+and enter their userid, password, username, termtype, and email path.
+Real name and address are not prompted for here like they are in the
+Admin Menu (verifying them would be quite difficult anyway).
+
+
+3. Deleting Accounts
+
+Accounts may be deleted with the $DeleteAccount action (Admin Menu/
+Delete User). If you built lbbs with FULL_USER_DELETE, this can be very
+slow because the bbs will delete ALL references to the account in every
+other user's override files and all the board manager files. You can also
+use the delacct utility to delete accounts from the shell, perhaps as a
+background job.
+
+The delacct utility also has a useful "user clean" feature, which allows
+accounts to be cleaned en masse based on the elapsed time since the last
+login. The exempt flag (see next paragraph) is used to mark accounts for
+exemption from user clean. See Chapter XI for instruction on use of the
+delacct utility.
+
+
+4. Modifiying Account Attributes
+
+Account information may be modified with the $SetUserData, $SetUserPerms,
+and $ToggleExempt actions (Info, Permissions, and Xempt on the Admin Menu).
+The Info command lets you change a user's userid, password, username,
+terminal type, real name, address, or email path. If the userid is changed
+and you built lbbs with FULL_USER_DELETE defined, there will be a delay
+while the bbs goes through all override and board manager files to attempt
+to change the userid. Otherwise it's fast, but up to you to fix the board
+manager files. The Permissions command allows the 32 permission bits to be
+set. The strings from the permstrs file are displayed here to make things
+clear. The Xempt command toggles a flag in the account's flags field. This
+flag is used by the delacct utility in its user clean mode -- any accounts
+with this flag set will be spared deletion even if they are otherwise aged
+beyond the cutoff point.
+
+Account flags other than the exempt flag cannot be set except some by
+the user themself. The cloak flag and two talk request control flags may
+be set by the user -- or by the sysop by careful editing of the passwds
+record. Two other flags can only be toggled by editing the passwds record.
+Recall the above warning about careful editing of this file!
+
+The flags are positioned as follows, in the 4th field of the passwds record.
+This is a 16-bit hexadecimal number, and you'll have to do a little
+hex-to-binary conversion to set the individual flags:
+
+0000 hex ---> 0000 0000 0000 0000 binary
+ UUUU UUUU UEOP SDXC are the flags
+
+U denotes unused.
+E is the expert flag: if set, the user wants the old, non-verbose menus.
+O is the override flag: if set, users on the override list cannot Page.
+P is the pager flag: if set, users NOT on the override list cannot Page.
+S is the shared flag: if set, the account is considered "shared" and
+ cannot receive mail, the readbits are not set, and the default menu
+ action is always "H" regardless of the menu.desc settings. This is
+ something you'll want to set for a guest-type account.
+D is the disabled flag: if set, the user cannot log in.
+X is the xempt flag described above.
+C is the cloak flag: is set, the user's presence is hidden from all users
+ except those with permission to SeeCloaked in ~bbs/etc/access.
+
+So, for example, if the flags on an account are 0000 and you want to turn
+on the shared (S) flag, you would change the flags field in their passwds
+record to 0008, which is hex for 0000 0000 0000 1000 binary.
+
+
+5. Limiting Logons
+
+The number of simultaneous logins an account may have is governed by
+the "logons" entry in the bbconfig file. By default this is set to 0,
+which means no limit. Changing it to 1 limits each user to one login
+at a time. This value can be greater than 1 as well. If a login attempt
+is made to an account that has reached its limit and the account does not
+have the shared flag, the user is given the option to kill a previous
+login to make room for a new one. This is useful when connections get
+broken abnormally and do not terminate properly.
+
+The ~bbs/etc/logons file may be used to let certain accounts override
+the system logon limit. For example, "guest=5" in the logons file allows
+for 5 simultaneous logons to the guest account regardless of the limit
+imposed in bbconfig.
+
+
+VI. Board Management
+
+1. Overview of the Board System
+
+Information about each board (public forum) is recorded in the
+~bbs/etc/boardlist file. This consists of the board name, 32-bit permission
+mask for read access, 32-bit permission mask for post access, and board
+description. Permission masks are in hex like account permissions.
+
+General:00000000:00000000:general board:
+
+Each board has one directory associated with it: ~bbs/boards/<boardname>.
+The board's posts are stored in this directory along with a couple of
+optional configuration files, which are:
+
+managers: A list of userids who have manager access on the board, one
+ userid per line. This list may be edited by the $SetBoardMgrs action
+ (Managers on the Admin Menu).
+nozap: If this file exists in the board directory, then the board may not
+ be zapped via the $Zap action. This is so boards with important system
+ information will always be seen in a new message scan. The nozap file's
+ contents are not important; it may be zero length.
+
+
+2. Adding Boards
+
+Boards may be added by the $AddBoard action (Admin Menu/New Board).
+You will be prompted to enter the board name and description, and whether
+you would like to restrict access to the board. Read access and post
+access are separate: if an account lacks read access to a board, the user
+will not even be able to see or select it (hence, post access is irrelevant).
+If you choose to restrict either one, you get a screen exactly like the
+account permission editing function. By turning any permission bit ON,
+you require a user to have that bit in order to read or post on the board.
+If more than one bit is set to ON, the user must have at least one of those
+permission bits to access the board. If all of the permissions are turned
+OFF, access is granted to all accounts.
+
+NOTE: As with adding accounts, just adding lines to the boardlist file is
+NOT sufficient to create a board. The board's directory must be created.
+Adding boards in this way is not recommended.
+
+
+3. Deleting Boards
+
+Boards may be deleted with the $DeleteBoard action (Admin Menu/Board Delete).
+This can be a very slow action because the bbs erases the readbits entry
+for that board from each user's readbits file, as well as deleting all of
+the posts. There is no external utility (a la delacct) to remove boards from
+outside the bbs at this time.
+
+
+4. Modifying Board Attributes
+
+Board information may be modified with the $ChangeBoard and $SetBoardMgrs
+actions (Change Board and Managers on the Admin Menu). The Change Board
+menu command lets you change a board's name, description, and both
+permission masks. If the board's name is changed, this will be very slow
+because the bbs will have to change the board name reference in each user's
+readbits file. As mentioned above, the Managers menu command provides a
+way to edit the board managers list within the bbs.
+
+A user who is on the managers list for a board, or who has global board
+manager capability as defined by the ManageAllBoards entry in ~bbs/etc/access
+(by default, accounts with the BoardMgr permission bit), has certain extra
+privileges within the board. These extra privileges are reflected by extra
+options being given when a post is read, and extra commands being available
+in the $MainRead menu (Main Menu/Read). The extra privileges are:
+
+-- Ability to delete any post regardless of ownership.
+-- Ability to move posts to another board regardless of ownership.
+-- Ability to mark posts. A marked post is spared by the Delete Range
+ function on the read-menu. Marks are not visible to non-managers, and
+ are permanent until unmarked.
+-- Ability to clean ranges of posts from the read-menu. Marks may be used
+ to spare individual posts inside the range.
+
+In addition, ability to edit posts may be granted to board managers by
+lowering the access for ReplaceMessage in ~bbs/etc/access. By default
+the permission required for this operation is Sysop.
+
+
+5. Implementation Details
+
+In the $ReadNew (Main Menu/New) action, which is used to scan all
+unzapped boards, the boards are presented in the order in which they
+are listed in the boardlist file. If you would like to re-order this
+file, it can be done with an editor.
+
+The filenames used for posts are 4-digit hexadecimal numbers. The numbers
+are not significant -- in particular, post 0001 need not be older than
+post 0002. When a board is accessed, the posts are ordered by the last
+modification time of the file, which is stored by the operating system.
+One ramification of this is that if you edit a post outside of the bbs,
+it will start appearing later in the list of posts for the board, and
+will seem out of order because the Date header still reflects the old
+date and time. The builtin post editing facility compensates for this
+by "touching" the file back to its prior time of modification.
+
+As of version 3.1.1, there is a $MainRead menu function for moving a
+post from one board to another. You can also do this from the shell
+by simply moving the file from one board directory to another. The
+Posted-By header will still reflect the original board it was posted on
+unless you edit it, regardless of how you move it. Be sure that you move
+the file to a new filename not already in use -- for example don't move
+~bbs/boards/board1/0001 to ~bbs/boards/board2/0001 without ensuring that
+~bbs/boards/board2/0001 does not already exist. Moving a file does not
+change the modification time in most filesystems.
+
+There is a limit of 2048 posts per board. This limit is not configurable
+at the present time. When 2048 posts accumulate on a board, any attempts
+to post will result in an error. You will probably want to keep fewer
+posts than that around due to space requirements anyway.
+
+There is a utility, delivered separately from the bbs, that cleans up
+old posts based on expiration instructions given in a config file you
+add to ~bbs/etc. It is quite helpful in conjunction with cron(8) in
+making board management painless. Look for cleanpkg.tar.gz on the ftp
+site at ftp.datasync.com in /pub/ebbs.
+
+
+VII. BBS Mail
+
+1. Local Mail Storage
+
+Mail for each account is stored in ~bbs/home/<userid>/mail. The file
+naming scheme is exactly the same as for posts -- 4 digit hex numbers.
+Record of which mail is read and unread is kept in the user's readbits
+file (~bbs/home/readbits) in the " $MAIL " record.
+
+Mail is subject to the same nonconfigurable limit of 2048 messages per
+directory as are boards. There is no Mail Clean function like earlier
+versions of EBBS had. Another script/program that I plan to write soon
+is something to run through each user's mail directory and tally up
+their messages, for purposes of sending out reminders that mailboxes
+need to be kept clean. A script/program to do cleaning for uncooperative
+users would be nice too.
+
+Mail may not be Marked for immunity to Delete Range, because of the way
+Group Mail is implemented. A mail message sent to multiple users is only
+stored once on disk, and the other "copies" are hard links. This makes it
+impossible to use chmod to mark individual copies, as can be done on boards.
+
+
+2. Internet Mail Support
+
+Mail both to and from EBBS to the Internet, and potentially any other
+network, is now supported. Both are controlled by the ExternalMail
+setting in the ~bbs/etc/access file. Anyone without ExternalMail access
+will neither be able to send mail outside the bbs nor will the incoming
+mailer accept mail for them.
+
+Outgoing mail (EBBS to outside) is the easier of the two to set up.
+The file ~bbs/etc/mailers contains information the bbs needs to send
+mail to the outside world. Records in this file are in this format:
+
+prefix:mailer-path
+
+Where "prefix" is the sequence, followed by a colon, that the bbs
+uses to recognize outside-bound mail, and mailer-path is the executable
+(with flags if necessary) that is to handle the outgoing mail. For example,
+suppose the mailers file contains
+
+INTERNET:/usr/lib/sendmail -bm
+
+Then if a user tries to send mail to INTERNET:user@foo.bar.edu, sendmail
+will be invoked: "/usr/lib/sendmail -bm user@foo.bar.edu".
+
+The "INTERNET" prefix has special meaning -- it is prepended to the user's
+email address as set in the system, if no prefix is there already, when
+the forwarding functions in the read menus are invoked. Note that forwarding
+access is separate from incoming/outgoing mail access -- they are controlled
+by the ForwardMessage and ForwardFile access file entries rather than the
+ExternalMail entry. See Chapter X (Mail, Post, and File Forwarding) for
+more details.
+
+Incoming mail is also possible, but requires sendmail(8) or a similarly
+configurable mail transfer agent, and knowledge of configuration.
+The ~bbs/bin/bbsmaild program is a local mailer suitable for use in
+sendmail.cf which delivers mail to the bbs mail system.
+
+It is recommended that you configure your sendmail.cf to direct addresses
+in the form <userid>.bbs or <userid>.bbs@your.host.name to bbsmaild,
+to be consistent.
+
+The following are additions to sendmail.cf that work for me. I use the
+stock sendmail.cf that comes with sendmail version 8.6.12. You must both
+define the bbs mailer and add rewrite rules that recognize the <userid>.bbs
+format and direct it properly.
+
+# Handle mail for EBBS. Ruleset 98 is reserved for local stuff in 8.8.x.
+# If you don't have such a ruleset, put it somewhere in ruleset 0.
+# Please note that there are TABS instead of spaces in here.
+S98
+R$+ . bbs $#ebbs $: $1 <userid>.bbs
+R$+ . bbs < @ $=w . > $#ebbs $: $1 <userid>.bbs@local
+
+# This goes near the Local and Program Mailer Specifications
+# EBBS Mailer -- be sure the paths are right, and adjust the S= and R=
+# rulesets to match whatever your local mailer uses.
+Mebbs, P=/home/bbs/bin/bbsmaild, F=DFPlmnsu, S=10, R=20/40,
+ A=bbsmaild -d /home/bbs $u
+
+I also tried it out with a sendmail.cf from sendmail version 5.67b.
+Here are the additions you need for that version:
+
+# First add this to the section where the mailers are defined
+# Substitute the appropriate path for "/home/bbs".
+Mebbs, P=/home/bbs/bin/bbsmaild, F=DFPlmnsu, S=10, R=25/10,
+ A=bbsmaild -d /home/bbs $u
+
+# Then add this to the top of ruleset 26.
+# EBBS: Check for bbs mail
+R<@$=w>,$+.bbs $#ebbs $@$w $:$2 local bbs user
+R<>,@$+$=Y$+.bbs $#ebbs $@$w $:$3 local bbs user
+
+In addition, if bbsmaild is linked to bbsmail and invoked that way, it
+may be used locally to send mail into the bbs, e.g. "bbsmail SYSOP".
+A subject or title may be added by adding -s <subject> to the command line.
+
+
+VIII. Setting up the File Area
+
+1. Setting Up Download and Upload Directories
+
+You may allow access to selected directories anywhere on your system by
+adding to the ~bbs/etc/ftplist file. For each directory containing files
+you wish bbs users to be able to view and download, place an entry in the
+ftplist file in the following format:
+
+<file-board-name>:<directory-path>:<description>
+
+The <file-board-name> is a 2-12 character board name consisting of letters,
+digits, dashes, and underscores -- the same restrictions as on account and
+board (forum) names.
+
+The <directory-path> is the full path to the directory.
+
+The <description> is an optional string describing the contents of the
+directory. It is displayed by the $FileBoards (File Menu/List) action.
+
+All subdirectories beneath <directory-path> will also be navigable
+by users and the files therein may be accessed. Users will not be
+able to go above <directory-path> in the directory structure, however.
+
+At this time, file boards do not feature the access control available
+in the forums. All users with access to the $FileDownload action
+(File Menu/Download) can view and download from the file boards.
+See Chapter X for notes regarding file forwarding via e-mail.
+
+The special file-board-name UPLOAD may be used to specify a directory
+for uploading instead of downloading. The same directory may be given
+another file-board-name if download access is desired as well. Any user
+with permission to use the $Upload function -- by default the Upload
+access is set to NONE -- may upload files to the UPLOAD directory.
+Files may not be uploaded to directories other than the UPLOAD directory.
+
+
+2. Setting Up Protocols
+
+Text files may be viewed in the same manner as posts and mail through the
+$FileDownload action. For transferring files to and from the user's
+machine, serial-line protocols such as kermit and zmodem may be used.
+To do this, the user must select a protocol from the list in ~bbs/etc/protos.
+This file is already configured for Kermit, Xmodem, Ymodem, Zmodem, and ASCII,
+but you should make sure the given paths are correct for your system.
+
+Records in the protos file are formatted like this:
+<proto-name>:<download-command>:<upload-command>
+
+For example:
+Zmodem:/usr/bin/sz -be:/usr/bin/rz -bpe:
+
+Then, if a user selects the Zmodem protocol and downloads the file foo.tar,
+the bbs would change directory to the current file board directory and invoke
+"/usr/bin/sz -be foo.tar". To upload the file bar.c, the bbs would change
+directory to the UPLOAD directory and execute "/usr/bin/rz -bpe bar.c".
+
+If the <upload-command> begins with "|", then the destination file is
+opened as standard output of the transfer program instead of being
+specified on the command line. The ASCII proto entry takes advantage of
+this feature to use the "cat" utility for uploading.
+
+
+3. File Transfer via Internet
+
+If users will be accessing your bbs through telnet, they should be aware
+that uploading and downloading of binary files will usually not work
+because the escape characters used by telnet may be present in a binary
+file and hence trigger telnet escapes. In general, use of serial-line
+protocols such as Zmodem across the Internet is iffy at best. You probably
+will want to set up anonymous ftp access to your file area if you expect
+to serve files to Internet users.
+
+
+IX. Chat System Configuration
+
+1. Chat Overview
+
+The bbs chat system is for interactive chat between users of the bbs.
+At this time no facility for linking chat daemons between bbses is
+available. Separate rooms may be created on demand within the chat system
+a la Internet Relay Chat, as well as several other special commands
+that integrate the chat system with the rest of the bbs, such as commands
+for getting user lists and querying users.
+
+The files ~bbs/etc/chathlp.txt and ~bbs/etc/chatxhlp.txt are displayed
+by the /help and /xhelp commands, respectively. These may be edited to
+alter the output of the /help and /xhelp commands if you wish.
+
+The file ~bbs/etc/chat.motd, if it exists, is displayed along with the
+welcome message when users enter the chat system.
+
+A separate config file is used by the chat daemon: ~bbs/etc/chatconfig.
+At this time, most aspects of the chat system, such as the special chat
+commands, are only configurable by modifying the source. The configuration
+available through chatconfig is as follows:
+
+mainroom=<main-room-name>
+The main chat room, where all users start out when entering Chat, is named
+"main" by default. You can change the name of this room by specifying it
+in the mainroom entry.
+
+operators=<userid>[,<userid>,...]
+Chat operator privileges are given by the chat daemon to any userids
+specified on this line. These users can see and enter Locked and Secret
+rooms and use /kick in all rooms including the main room. This definition
+of "operator" is different from the room operator privilege given to a
+user who creates a new room with /join. Room operators can set the Locked
+and Secret flags, use /kick, give keys (to open Locks), and transfer the
+operator privilege to other users in their room ONLY.
+
+restricted=<userid>[,<userid>,...]
+A special "restricted" mode is available for use by guest-type accounts
+if you see fit. Any accounts listed on this line cannot join other rooms
+or obtain information about the other rooms or the users in them. They
+also do not retain their ignore lists between chat sessions.
+
+nopunct=<yes or no>
+Three punctuation characters, '*', '+', and ':' are always disallowed
+from chatids. You can disallow all the rest too (except '-' and '_')
+if you like by setting nopunct=yes in chatconfig.
+
+At this time it is not possible to restrict accounts to read-only in
+chat. The TalkInChat and ExitChat operations are usable by anyone who
+can use the Chat operation, regardless of how they are restricted in the
+~bbs/etc/access file.
+
+
+2. Technical Information
+
+The chat daemon process (~bbs/bin/chatd) starts up when the first user
+enters Chat, and dies when the last user exits. Its function is to
+relay lines input by one chat user to the appropriate other users.
+It also handles some of the special chat commands. Upon startup, it writes
+its process-id to the file ~bbs/etc/.chatpid and its TCP port number
+to the file ~bbs/etc/.chatport. Both numbers are written as 4-digit hex
+values. These serve to synchronize the multiple lbbs processes connecting
+to the chat daemon. If problems arise for some reason, a good start at
+correcting then would be to remove the .chatpid and .chatport files and
+then kill any running bbs.chatd processes.
+
+The chat daemon uses a special handshake with the bbs program, wherein
+the daemon uses the user table (in shared memory or a temporary file)
+to verify that the connecting process is in fact a user logged into the
+bbs. For this reason, connections to the chat port by non-bbs processes
+will not see any output, and will be closed as soon as they try to send
+input to the daemon.
+
+
+X. Miscellaneous
+
+1. Issue and Welcome Files
+
+The ~bbs/etc/issue file is a plain text file which is displayed by the
+lbbs program prior to the bbs login prompt. It can be used, for example,
+to show first-time users how to log in.
+
+The ~bbs/etc/welcome file is a plain text file which is shown immediately
+after a successful login and before the main menu prompt. It will be paged
+if it contains more than one screenful. Many sites like this to be exactly
+one page long and put some sort of ASCII picture there which serves to
+personify the bbs. This file may be edited from within the bbs via the
+$EditWelcome action (Xyz Menu/Welcome).
+
+
+2. Copyright Notice and License
+
+The ~bbs/etc/COPYING file is the Free Software Foundation's GNU General
+Public License, Version 2. This is the license under which this software
+is distributed. You should read this document to understand your rights
+regarding use and distribution of this software. The $GnuInfo action
+(Xyz Menu/Gnu) allows users to view this document.
+
+The ~bbs/etc/info file is another plain text file that is used to inform
+users about the copyright and warranty associated with the bbs software.
+Per the GNU General Public License, you must make this information
+available to your users. You may add information pertaining to your
+bbs or its administration below the copyright information. The $BoardInfo
+(Main Menu/Info) action displays this file.
+
+
+3. Configuring Alternate Editors
+
+The ~bbs/etc/editors file allows you to configure editors besides the
+builtin editor for use by your bbs users. No such external editors are
+supplied with this distribution, but can probably be found near this
+package on your favorite ftp site.
+
+Before offering external editors for use in the bbs, be SURE to heed the
+warnings outlined in the editors file. Most popular editors (vi and emacs
+included) allow functions which would be detrimental to the security of
+your bbs if bbs users were allowed to use them. These include shell
+escapes, and the ability to load/view/write arbitrary files. You must
+be sure these features are disabled in any alternate editor you offer.
+
+The format of records in the editors file is as follows:
+<editor-name>:<full-path>:<environment>:<comment>
+
+The editor name is one which users can choose with the $SelectEditor
+action (Xyz Menu/FileEditor). It is saved permanently in the user's
+profile file. The <full-path> and optional <environment> field follow
+the same format as in the $exec actions in ~bbs/etc/menu.desc (see
+Chapter IV of this Guide).
+
+In addition to add entries to this file, you'll need to modify the
+~bbs/etc/access to allow ALL (or restricted) access to the SetEditor,
+GetEditor, and EnumEditors operations, as their access is NONE by default.
+
+You should be sure to uncomment the entry for the builtin editor if
+other editors are offered.
+
+
+4. Logging
+
+The lbbs program writes a record of certain events to a log file.
+The log file may be anywhere on your system; just specify the full path
+in the "logfile" entry in ~bbs/etc/bbconfig. The default log file is
+~bbs/log.
+
+You may also control the volume of logging information with the "loglevel"
+entry in ~bbs/etc/bbconfig. This value may be zero or any positive integer.
+Higher values mean more events are recorded in the log -- for any given
+log level, log events associated with a lower log level are also included.
+The default log level is 1. Here is a brief summary of the events logged
+at each level (and above):
+
+Level 0: System errors are always logged.
+Level 1: Logins, failed login attempts, logouts, and account creations.
+Level 2: Administrative actions -- account deletions and modifications;
+ board creations, deletions, and modifications; manager actions
+ on boards; cloak; kick.
+Level 3: Posts, uploads, downloads, forwards.
+Level 4: Entries to Chat; Talk requests, accepts, and refusals.
+
+The log entries are designed to be easily parsed by Perl or shell scripts,
+so that statistics may be tallied and saved. Such scripts are currently
+under development and may be made available in the future.
+
+External scripts invoked from the bbs through the configurable menus may also
+write to the log file by using the bbslog utility. See Chapter XI, section 3
+for details.
+
+
+5. Mail, Post and File Forwarding
+
+Mail messages, posts, and files may be forwarded to the user's email address
+(as stored in their profile file -- see Chapter V) from the Mail Read, Main
+Read, and Download read-menus, respectively. Two operations in the access
+file govern permission to do this:
+
+ForwardMessage, the 73rd operation, controls mail and post forwarding;
+ForwardFile, the 74th operation, controls file forwarding.
+Both are set to ALL by default.
+
+For the forwarding to work, the "INTERNET" prefix must be listed in the
+~bbs/etc/mailers file with a suitable mailer. "/usr/lib/sendmail" is the
+default. The name of the file to be forwarded is given as the first and
+only parameter to the mailer program. You may include flags in the mailer
+line; for instance "/usr/lib/sendmail -odq" is recommended if you have heavy
+forwarding traffic, since this flag tells sendmail to just queue the mail
+and run it in the background later.
+
+
+6. Modes
+
+One of the fields returned for each user by the Users operation is a mode.
+This is a number which gives some idea of what the user is currently doing,
+or that the bbs doesn't know what the user is currently doing (e.g., moving
+around menus). Eleven modes are preconfigured and used by the system.
+These include modes to indicate the user is reading, posting, in mail,
+talking, or chatting.
+
+The Talk Menu (U)sers command converts the mode into a string for display;
+the (L)ist and (M)onitor commands convert the mode into a single character
+for display. You can set these strings and characters to anything you wish
+by editing the ~bbs/etc/modes file. Of course, since the mode values from
+0 to 10 are used by the system, it is not wise to change their meanings,
+but, for example, you may want to use a language other than English.
+
+You also may add modes of your own to the modes file, using mode values
+11 through 31. These may be referenced in the ~bbs/etc/menu.desc file so
+that you can define modes for any external programs you hook in. See
+Chapter IV, "Configuring The Menus" and the delivered modes file for
+more information.
+
+
+7. 8-Bit Character Support
+
+EBBS 3.1 is able to handle 8-bit character sets cleanly. The "locale"
+entry in ~bbs/etc/bbconfig is passed to the C library setlocale(3)
+routine on systems that support it, to set the locale-dependent
+C library routines for the primary locale of your system. See the
+setlocale(3) man pages for more information.
+
+Also, each user may select an alternate character mapping from those
+installed in the ~bbs/etc directory. A character mapping file consists
+of 256 pairs of values, one per line, in the form
+
+<input-mapping>=<output-mapping>
+
+The nth line of this file describes the input and output mappings for
+each 8-bit character n. The <input-mapping> gives the translation
+applied to the character on input, and the <output-mapping> gives the
+translation applied on output.
+
+The SetCharset method allows the user to select a mapping. The name of
+the mapping to be entered by the user is the last part of the mapping
+file name, i.e. to select the mapping in ~bbs/etc/charset-alt, the user
+would enter "alt". No name completion or listing of available mapping
+files is available to the user; it is up to you to make this information
+available. "ascii" is a special value which sets the default mapping
+(no translation at all).
+
+
+XI. External Utilities
+
+1. addacct
+
+Syntax: addacct -i <userid> -p <password> [-u <username>] [-t <termtype>]
+ [-e <email>] [-r <realname>] [-a <address>] [-d <bbs-dir>]
+
+userid: The logon name of the new account. Must be specified.
+ This is an alphanumeric string between 2 and 12 characters.
+ It must begin with a letter, end with a letter or number,
+ and may have - or _ provided they are surrounded by alphanumerics.
+
+password: The password of the new account. Must be specified.
+ Must be at least 1 character, at most 8 are significant.
+
+username: Up to 24 characters, free form. Defaults to userid if not specified.
+
+termtype: The terminal type for this account. Defaults to vt100.
+
+email: Email address for this account. Optional.
+
+realname: Real name of the account's owner. Optional. Not accessable
+ through the bbs except to sysops and account managers unless
+ showreal=yes in bbconfig, and then can be controlled with
+ ShowRealName in the access file.
+
+address: Address of the account's owner. Optional. Same access restrictions
+ as the realname field.
+
+bbs-dir: Specifies the bbs home directory. Only needed if BBSHOME is
+ not set in the environment.
+
+Addacct adds a new account to ~bbs/etc/passwds and creates a home directory
+and mail directory for them. The profile information (termtype, email,
+realname, address) is placed in ~bbs/home/<userid>/profile if given.
+
+Addacct writes an error message to stderr if it encounters an error.
+No output is written to the terminal if successful. A line is written to
+the bbs log file if one is configured and the log level is 1 or greater.
+
+Return values: 0 == success, 1 == error, 2 == usage error.
+
+
+2. delacct
+
+Syntax: delacct [-a age] [-c] [-d bbs-dir] [-n] [-t] userid ...
+
+age: Only meaningful with the -c flag. Specifies a time period in
+ days which, if an account has not been used in that many days,
+ it is deleted. Default is 30 days. Any value below 7 days is
+ illegal to prevent accidental deletion of too many accounts.
+
+-c: Directs delacct to do a user clean. No userids may be given
+ if -c is specified. All accounts not used within the period
+ specified by the age parameter are deleted, except account
+ that have been Exempted by the bbs (A)dmin/(X)empt function.
+ Sysop accounts are always spared by user clean.
+
+bbs-dir: Specifies the bbs home directory. Only needed if BBSHOME is
+ not set in the environment.
+
+-n: Directs delacct to only delete accounts which have never been
+ used. This is so you can apply a different grace period to
+ never-used accounts. Not meaningful unless -c is given also.
+
+-t: Test mode: delacct tells which accounts would be deleted,
+ but does not delete them.
+
+userid: Specifies the userids to delete. These may only be given if the
+ -c flag is not, and vice versa. By not specifying -c and giving
+ userids instead, delacct will delete the specified accounts.
+ Age checks and exemption flags do not apply.
+
+Delacct deletes accounts from ~bbs/etc/passwds and removes their home and
+mail directories.
+
+Delacct writes a line to stdout for each account it deletes, or would have
+deleted if the -t flag was given. If an error occurs a message is written
+to stdout. In addition, for every deleted account a line is written to
+the bbs log file if it is configured and the log level is at least 1.
+
+Return values: 0 == success, 1 == error, 2 == usage error.
+
+
+3. bbslog
+
+Syntax: bbslog [-d bbs-dir] [-h log-header] log-level message
+
+bbs-dir: Specifies the bbs home directory. Only needed if BBSHOME is
+ not set in the environment and the current directory is not
+ the bbs home directory.
+
+log-header: An optional 1-16 character tag which is saved as the first
+ field of the log message. The default is BBSLOG.
+
+log-level: A numeric value specifying the importance of the message.
+ Lower numbers indicate greater importance (0 is always logged).
+ If the bbs logging level in bbconfig is less than the log-level,
+ the message is not logged.
+
+message: The freeform text log message.
+
+bbslog provides a way for external scripts to write messages into the bbs
+log file. The location of the file and log level are specified in the
+~bbs/etc/bbconfig file; see that file for more information.
+
+Return values: 0 == success, 1 == error, 2 == usage error.
+
+
+4. bbfinger
+
+Syntax: bbfinger [-d bbs-dir]
+
+bbfinger is a utility for displaying the list of logged-in users from
+outside the bbs. The -d flag may be used to specify the bbs home directory.
+If this flag is not given, bbfinger will try to use $BBSHOME, or finally
+the current directory.
+
+The most obvious use for this program is as an Internet server to allow
+bbs users to get the user list remotely without logging in. Here's how to
+set this up on most systems:
+
+1. Pick an unused tcp port -- 1992, the year EBBS was born, is common --
+ and assign the 'bfinger' service to it. In /etc/services put this line:
+
+bfinger 1992/tcp # EBBS 3.0 finger
+
+2. Tell your inetd daemon to run bbfinger for connections to the bfinger
+ port. Add a line like so to your /etc/inetd.conf following the example of
+ the other servers like fingerd. This example is from Slackware Linux:
+
+bfinger stream tcp nowait bbs /usr/sbin/tcpd /home/bbs/bin/bbfinger -d /home/bbs
+
+3. Tell inetd to reread inetd.conf by sending it SIGHUP, e.g.
+
+kill -HUP <pid of inetd process>
+
+Now, anyone can "telnet <your host> <bfinger port>" and get the online
+user list.
+
+Notes: With a little work, this can be a full-fledged finger daemon for
+a system where the bbs is the focus of attention.
+
+Exit values: 0 on successful completion, nonzero on error.
+
+
+5. bbsmail
+
+Syntax: bbsmail [-d bbs-dir] [-s subject] user ...
+
+bbs-dir: Specifies the bbs home directory. Only needed if BBSHOME is
+ not set in the environment and the current directory is not
+ the bbs home directory.
+
+subject: Specifies the subject of the mail message. Be sure to use
+ quotes if there is more than one word.
+
+user: The bbs user or users the mail is to be delivered to.
+
+bbsmail must be a hard or symbolic link to the ~bbs/bin/bbsmaild program.
+bbsmaild is not designed to be invoked from the command line unless invoked
+as bbsmail. (bbsmaild expects headers to be present, bbsmail generates them.)
+
+bbsmail collects a message on standard input and places it in the bbs mailbox
+of the user or users specified, up to a limit of 32 users. Only users who
+have the ExternalMail privilege, defined in the access file, may be specified.
+Otherwise bbsmail will display an error message and the mail will not be
+delivered.
+
+Return values: 0 == success, 1 == error, 2 == usage error.
+
+
+Appendix A
+How To Set Up a Guest Account
+
+You should probably read the rest of this Guide before this example will
+make much sense. References to more detailed discussions in the main body
+will be used. This Appendix serves as a useful summary.
+
+We shall start by assuming that you have a bbs up and running with regular
+accounts, boards, and perhaps a file area.
+
+Step 1. Create the guest account (Chapter V, section 2). Use the Add User
+ command on the Admin Menu or use addacct from the command line.
+ Pick a password that's easy to guess, like 'guest'. Note: it is not
+ possible to create passwordless accounts at this time.
+
+Step 2. Restrict permissions (Chapter V, section 4). Use the Permissions
+ command on the Admin Menu. Since Basic-1 through Basic-4 are available
+ to all other users, you want to turn one or more of these OFF for guest.
+ Let's assume you turn off Basic-1 (or this one plus others).
+
+Step 3. This is the fun part. Go through ~bbs/etc/menu.desc, and make a
+ list of the menu commands you want to make off limits to guests.
+ Don't edit menu.desc, just note the operation names, which are the
+ 4th fields in the menu commands (Chapter IV, section 2). Here are
+ some you might consider: Post, ZapBoard, VisitBoard, AdminMenu,
+ Cloak, SetAddress, SetUsername, SetPassword, EditWelcome, SetPlan,
+ SetSignature, SetEditor, Page, SetPager, SetOverrides, Kick.
+
+ Now go through the ~bbs/etc/access file (Chapter III, section 2)
+ and change the access from ALL to Basic-1 for each of these. Some of
+ them, like AdminMenu, Cloak, and Kick, are already restricted to
+ higher permission bits -- you don't want to alter them. Keep an eye
+ open for other things in the access file you want to restrict that
+ aren't listed in menu.desc (the Read and Download menus are not yet
+ configurable). ForwardMessage and ForwardFile are good examples.
+
+ You can make your own access file entries and deny access to whole
+ menus like the Mail and Files menus by using the same trick as for
+ the Admin Menu.
+
+Step 4. Make the account shared (Chapter V, section 4). This keeps the
+ guest account from receiving mail, keeping records of read posts,
+ and makes all menus default to (H)elp. You must edit the 'guest'
+ record at the bottom of ~bbs/etc/passwds to do this. While you're
+ here, you can turn off guest's pager so users cannot page them if
+ you wish. Change the "0000" in the 4th field of the passwds record
+ to "0008", or "0038" to restrict paging too.
+
+Step 5. Set a logon limit for guests (Chapter V, section 5). It may be
+ a good idea to limit the guest account's simultaneous logons if you
+ expect high usage. Add a line like "guest:5" in ~bbs/etc/logons.
+
+Step 6. Go through your list of boards and restrict Read access to any
+ that you don't want guests seeing (Chapter VI, section 4). Use
+ the Change Board command on the Admin Menu.
+
+Step 7. If you are allowing guests to use Chat, you may want to use the
+ "restricted chat" feature to only let them in the main room
+ (Chapter IX, section 1). Edit ~bbs/etc/chatconfig.
+
+And that should do it. Login as guest to make sure the menus look like
+you want them to for guests. Congratulations, you're on your way to
+becoming a Master of EBBS Configuration. :-)
diff --git a/Install.sh b/Install.sh
new file mode 100755
index 0000000..a30580c
--- /dev/null
+++ b/Install.sh
@@ -0,0 +1,92 @@
+#!/bin/sh
+#
+# Eagles BBS 3.1 install script
+#
+
+homedir=$1
+if [ "$homedir" = "" ]
+then
+ homedir=$BBSHOME;
+ if [ "$homedir" = "" ]
+ then
+ homedir=`grep \^bbs: /etc/passwd | cut -d: -f6`
+ if [ "$homedir" = "" ]
+ then
+ echo Cannot determine bbs home directory\!
+ echo You must do one of the following\:
+ echo \ \ \ \ \-\- define INSTALLDIR in the Makefile
+ echo \ \ \ \ \-\- define BBSHOME in the environment
+ echo \ \ \ \ \-\- add an entry for 'bbs' into /etc/passwd.
+ exit 1
+ fi
+ fi
+fi
+
+echo Checking directories
+if [ ! -d "$homedir" ]
+then
+ echo Creating "$homedir"
+ mkdir $homedir
+ chmod 770 $homedir
+fi
+
+if [ ! -d "$homedir/bin" ]
+then
+ echo Creating $homedir/bin
+ mkdir $homedir/bin
+fi
+
+if [ -f "$homedir/bin/lbbs" ]
+then
+ echo Moving existing lbbs to lbbs.old
+ mv $homedir/bin/lbbs $homedir/bin/lbbs.old
+fi
+
+if [ -f "$homedir/bin/chatd" ]
+then
+ echo Moving existing chatd to chatd.old
+ mv $homedir/bin/chatd $homedir/bin/chatd.old
+fi
+
+echo Copying new executables
+cp lbbs chatd addacct delacct bbslog bbfinger bbsmaild $homedir/bin
+(cd $homedir/bin; strip lbbs chatd addacct delacct bbslog bbfinger bbsmaild)
+chmod 6755 $homedir/bin/lbbs $homedir/bin/bbsmaild
+
+if [ ! -d "$homedir/etc" ]
+then
+ echo Creating $homedir/etc
+ mkdir $homedir/etc
+ echo Copying configuration files
+ cp config/* $homedir/etc
+fi
+
+if [ ! -d "$homedir/tmp" ]
+then
+ echo Creating $homedir/tmp
+ mkdir $homedir/tmp
+fi
+
+if [ ! -d "$homedir/home" ]
+then
+ echo Creating $homedir/home
+ mkdir $homedir/home
+fi
+
+if [ ! -d "$homedir/home/SYSOP" ]
+then
+ echo Creating $homedir/home/SYSOP
+ mkdir $homedir/home/SYSOP
+fi
+
+if [ ! -d "$homedir/home/SYSOP/mail" ]
+then
+ echo Creating $homedir/home/SYSOP/mail
+ mkdir $homedir/home/SYSOP/mail
+fi
+
+if [ ! -d "$homedir/boards" ]
+then
+ echo Creating $homedir/boards
+ mkdir $homedir/boards
+fi
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ad186bb
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,219 @@
+
+#############################################################################
+#
+# Makefile for Eagles BBS version 3.1
+#
+#############################################################################
+
+# This is the configurable part. Put anything OS-dependent here.
+# You do not need to worry with LEX, YACC, or YFLAGS unless you want to
+# modify the menu parser.
+
+# your C compiler of choice
+CC=gcc
+
+# your C linker of choice
+LD=gcc
+
+# archiver of choice
+AR=ar
+
+# ranlib, if applicable.
+# SunOS, OSF/1, NeXT, A/UX, MachTen, FreeBSD use RANLIB=ranlib.
+RANLIB=echo Not using ranlib:
+
+# special include paths?
+INCLUDES=
+
+# special libs?
+# Solaris needs -lsocket -lnsl -L/usr/ccs/lib
+# MachTen needs -lcompat
+# Unixware needs -lsocket -lnsl (and maybe -L/usr/ucblib -lucb)
+# FreeBSD needs -lcrypt -lcompat -lipc
+LIBS=
+
+# flags for the compiler
+# NeXT wants -pipe -s -O4 -arch m68040; FreeBSD wants -O2 -m486 -pipe
+# might want to try -DMENU_STANDOUT too if you're a dialup bbs
+# might want -DEXTRA_CHAT_STUFF or -DNO_IGNORE_CHATOPS, see docs
+CCFLAGS=-O -DCOLOR
+
+# flags for the linker -- NeXT wants -s -O4; FreeBSD wants -s
+LDFLAGS=-O
+
+# flags for the archiver -- remove s if using ranlib, add c for NeXT, FreeBSD
+ARFLAGS=rs
+
+# zipper for srcdist and bindist targets, if desired
+ZIP=gzip
+
+# scanner generator: lex or flex
+LEX=flex
+
+# parser: yacc or bison
+YACC=bison
+
+# flags for parser
+YFLAGS=-d -o y.tab.c
+
+# default install directory for the install script
+INSTALLDIR=
+
+# The rest shouldn't need messing with.
+
+##############################################################################
+
+CFLAGS=$(CCFLAGS) $(INCLUDES)
+
+BINS=lbbs chatd addacct delacct bbslog bbfinger bbsmaild
+
+DOCS=README ChangeLog EBBS-Guide ReleaseNotes
+
+all: $(BINS)
+
+##############################################################################
+# BBS Library make
+##############################################################################
+
+LIBOBJS=record.o util.o name.o log.o utable.o login.o passwd.o home.o init.o \
+acct.o misc.o headers.o readbits.o board.o files.o exec.o uldl.o chat.o \
+talk.o netmail.o edit.o conv.o
+
+libbbs.a: $(LIBOBJS)
+ -rm -f libbbs.a
+ $(AR) $(ARFLAGS) libbbs.a $(LIBOBJS)
+ $(RANLIB) libbbs.a
+
+# Here are the makes for the external utility programs.
+
+addacct.o: addacct.c server.h common.h
+ $(CC) -c $(CFLAGS) addacct.c
+
+addacct: addacct.o libbbs.a
+ $(CC) $(LDFLAGS) -o addacct addacct.o libbbs.a $(LIBS)
+
+delacct.o: delacct.c server.h common.h
+ $(CC) -c $(CFLAGS) delacct.c
+
+delacct: delacct.o libbbs.a
+ $(CC) $(LDFLAGS) -o delacct delacct.o libbbs.a $(LIBS)
+
+bbslog.o: bbslog.c server.h common.h
+ $(CC) -c $(CFLAGS) bbslog.c
+
+bbslog: bbslog.o libbbs.a
+ $(CC) $(LDFLAGS) -o bbslog bbslog.o libbbs.a $(LIBS)
+
+bbfinger.o: bbfinger.c server.h common.h
+ $(CC) -c $(CFLAGS) bbfinger.c
+
+bbfinger: bbfinger.o modes.o libbbs.a
+ $(CC) $(LDFLAGS) -o bbfinger bbfinger.o modes.o libbbs.a $(LIBS)
+
+bbsmaild.o: bbsmaild.c server.h common.h
+ $(CC) -c $(CFLAGS) bbsmaild.c
+
+bbsmaild: bbsmaild.o libbbs.a
+ $(CC) $(LDFLAGS) -o bbsmaild bbsmaild.o libbbs.a $(LIBS)
+
+############################################################################
+# BBS daemon make (not quite finished)
+############################################################################
+
+#bbsd:
+# cd server; make CC="$(CC)" CFLAGS="$(CFLAGS)" LD="$(LD)" LDFLAGS="$(LDFLAGS)" LIBS="$(LIBS)" bbsd
+
+############################################################################
+# Chat daemon make
+############################################################################
+
+chatserv.o: chatserv.c
+ $(CC) -c $(CFLAGS) chatserv.c
+
+chatconf.o: chatconf.c
+ $(CC) -c $(CFLAGS) chatconf.c
+
+chatd: chatserv.o chatconf.o libbbs.a
+ $(LD) $(LDFLAGS) -o chatd chatserv.o chatconf.o libbbs.a $(LIBS)
+
+############################################################################
+# BBS client make (not quite finished)
+############################################################################
+
+#bbs:
+# cd client; make CC="$(CC)" CFLAGS="$(CFLAGS)" LD="$(LD)" LDFLAGS="$(LDFLAGS)" AR="$(AR)" ARFLAGS="$(ARFLAGS)" RANLIB="$(RANLIB)" LIBS="$(LIBS)" bbs
+
+############################################################################
+# Local client make
+############################################################################
+
+LLIBS=libbbs.a pbbs/libpbbs.a
+
+LOBJS=client.o menus.o complete.o system.o c_users.o readmenu.o c_mail.o \
+modes.o c_boards.o c_post.o readnew.o c_files.o c_chat.o c_talk.o c_lists.o \
+nmenus.o env.o y.tab.o lex.yy.o
+
+PLIBOBJS=pbbs/term.o pbbs/screen.o pbbs/io.o pbbs/stuff.o \
+pbbs/more.o pbbs/vedit.o
+
+pbbs/term.o: pbbs/term.c
+ cd pbbs; $(CC) -c -I.. $(CFLAGS) term.c
+
+pbbs/screen.o: pbbs/screen.c
+ cd pbbs; $(CC) -c -I.. $(CFLAGS) screen.c
+
+pbbs/io.o: pbbs/io.c
+ cd pbbs; $(CC) -c -I.. $(CFLAGS) io.c
+
+pbbs/stuff.o: pbbs/stuff.c
+ cd pbbs; $(CC) -c -I.. $(CFLAGS) stuff.c
+
+pbbs/more.o: pbbs/more.c
+ cd pbbs; $(CC) -c -I.. $(CFLAGS) more.c
+
+pbbs/vedit.o: pbbs/vedit.c
+ cd pbbs; $(CC) -c -I.. $(CFLAGS) vedit.c
+
+pbbs/libpbbs.a: $(PLIBOBJS)
+ -rm -f pbbs/libpbbs.a
+ $(AR) $(ARFLAGS) pbbs/libpbbs.a $(PLIBOBJS)
+ $(RANLIB) pbbs/libpbbs.a
+
+lex.yy.c: menu.l
+ $(LEX) menu.l
+
+lex.yy.o: lex.yy.c y.tab.o
+
+y.tab.c: gram.y
+ $(YACC) $(YFLAGS) gram.y
+
+lbbs: $(LOBJS) $(LLIBS)
+ $(LD) $(LDFLAGS) -o lbbs $(LOBJS) $(LLIBS) $(LIBS) -ltermcap
+
+srcdist:
+ rm -f ebbssrc.tar ebbssrc.tar.gz
+ tar -cvf ebbssrc.tar [A-Z]* *.[chly] pbbs/*.c pbbs/*.h config/*
+ $(ZIP) ebbssrc.tar
+
+bindist: all
+ rm -f ebbsbin.tar ebbsbin.tar.gz
+ if [ ! -d deliv ]; then \
+ mkdir deliv deliv/bin deliv/boards deliv/etc deliv/tmp; \
+ mkdir deliv/home deliv/home/SYSOP deliv/home/SYSOP/mail; \
+ fi
+ cp $(DOCS) deliv
+ cp $(BINS) deliv/bin
+ chmod 6755 deliv/bin/lbbs deliv/bin/bbsmaild
+ cp config/* deliv/etc
+ (cd deliv/bin; strip $(BINS))
+ (cd deliv; tar -cvf ../ebbsbin.tar .)
+ $(ZIP) ebbsbin.tar
+
+install: all
+ ./Install.sh $(INSTALLDIR)
+
+clean:
+ rm -f *.o pbbs/*.o *~ pbbs/*~ config/*~ *# y.output
+
+clobber: clean
+ rm -f $(BINS) libbbs.a pbbs/libpbbs.a
diff --git a/README b/README
new file mode 100644
index 0000000..596f2a6
--- /dev/null
+++ b/README
@@ -0,0 +1,143 @@
+
+Welcome to Eagles BBS 3.1.1
+
+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, or (at your option)
+any later version. See the COPYING file for further information.
+
+Eagles BBS 3 is a standalone, screen-oriented bulletin board package.
+It is the successor to Eagles BBS version 2, which was built upon Ed Luke's
+Pirates BBS package. The user interface from those programs has been kept
+intact for the most part, and some code has been reused. For the most part
+this is a complete rewrite though, to correct some basic limitations and
+inefficiencies present in Pirates and Eagles 2.x.
+
+This software has a fairly full-featured message posting and private mail
+system, its own IRC-like chat system, and private talk facility. It has
+configurable menus allowing custom arrangement of the builtin functions
+and any external programs you care to hook in.
+
+One thing Eagles BBS is not suited for quite yet is as a fancy shell.
+All BBS users run as a single user, so things like shell access and
+news do not integrate very well. You should probably look elsewhere if
+BBS users are to be anything more than guests on your system.
+
+One improvement over Pirates and old Eagles is much greater runtime
+configuration. A lot of stuff you used to have to recompile to change is
+now in config files read in at runtime. All of these files are ASCII so
+they may be patched with an editor if needed. The stock config files are
+in the config directory and reside in ~bbs/etc at runtime.
+
+IMPORTANT:
+If you are already using an earlier version of EBBS 3, be SURE to read the
+ReleaseNotes and ChangeLog to see what has changed before continuing!!!
+
+All documentation has been moved into the EBBS-Guide. Please consult it
+for installation and configuration instructions, examples, and technical
+information.
+
+The Linux ELF binary distribution, ebbs-3.1.1.bin.tar.gz, was built
+with gcc 2.7.2, libc 5.4.33, and binutils 2.8.1.0.1. You should have
+Linux 2.0 or later and the GNU shared C library version 5.4.x or later.
+Earlier versions of the kernel and shared C library may or may not work.
+
+The source distribution, ebbs-3.1.1.tar.gz, should build under recent
+versions of Linux, SunOS, Solaris, AIX, Digital Unix, NextStep, A/UX,
+Unixware, Ultrix, MachTen, HPUX, FreeBSD, and IRIX. You should go
+through the Makefile and osdeps.h to set up the conditional defines
+for your operating system -- see EBBS-Guide under "Building the Source"
+for details.
+
+If you have an operational EBBS 2.x that you want to convert to version 3,
+you'll need the source distribution and the conversion package, cvtpkg.tar.gz.
+It should be available at ftp://ftp.datasync.com/pub/ebbs or other sites.
+Get it and see the enclosed README for the scoop on getting your existing
+BBS converted to version 3.1.
+
+Add-on packages (useful scripts, safe editors, etc.) are under development.
+Check the ftp site mentioned above to see what's there.
+
+
+EBBS Mailing List
+-----------------
+
+I have set up a mailing list for EBBS. There is no one on it but myself
+as I write this, but I wanted it there before distributing this code,
+so everyone who uses EBBS may be informed of security issues, bugs,
+patches, and so forth as they happen. The list is not moderated at this
+time so feel free to post for help with things. If it starts getting
+a lot of traffic I'll make a separate moderated ebbs-announce list
+for the important stuff.
+
+To subscribe: send a message to majordomo@datasync.com with
+
+subscribe ebbs
+end
+
+in the body (not Subject) of the message. If that doesn't work
+mail me at the address at the bottom of this file.
+
+
+The TODO list
+-------------
+
+These features aren't there, but might be one day:
+
+-- Mail clean program (will be released as an add-on package)
+-- Tagging of messages in mail, for deleting or forwarding.
+-- Reading lists that allow each user to specify the order to scan the
+ boards during a new message scan.
+-- The big TODO: A split into client and server, with which the client
+ can run on a different machine and do things like editing, menu movements,
+ shell escapes, job control, etc. locally.
+ (Update: I have a client/server pair that has all the regular features
+ except talk working. It's really slow though. As soon as I can
+ write some functional documentation for it, I'll throw it out
+ for anyone who wants to experiment with it.)
+
+
+Known Bugs
+----------
+
+-- On the File Menu, the "Current Board" is shown is not the currently
+ selected File board. It is correct on the Download read-menu.
+ (The fix isn't worth the trouble)
+-- The file-based user table is reported to have some problems. This is
+ only an issue if you do not have a shared memory facility.
+
+
+Credits
+-------
+
+Thanks to these people for helping code, test, debug, and port EBBS 3!
+Ed Luke
+Ray Seyfarth
+Bill Schwartz
+Xabier Vazquez Gallardo
+John Salmi
+Paul Snow
+Mark Roe
+Andre Schmidt
+Jon Stevens
+Thanh Ma
+Alex Schubert
+Jiri Randus
+Rob Woodard
+Mike McCallum
+Randy Austin
+Alexis Yushin
+J. Douglas Armstrong
+Everyone at the Eagle's Nest and Auggie BBS
+
+Additional tip of the hat for assistance with previous versions of EBBS:
+Guy Vega, Dominic Tynes, Jim Morton, Carey Chou
+
+------------------------------------------------------------------------------
+
+Good luck, and happy BBSing!
+
+TheRock
+In real life: Ray Rocker
+On the air: WQ5L
+Internet: rocker@datasync.com
diff --git a/ReleaseNotes b/ReleaseNotes
new file mode 100644
index 0000000..b7889d3
--- /dev/null
+++ b/ReleaseNotes
@@ -0,0 +1,184 @@
+This is Eagles BBS 3.1.1. This release was done mainly to merge
+in a few neat things I've written for the Eagle's Nest BBS, and
+that have been passed along by other sysops. See the ChangeLog
+for more details.
+
+The only semi-serious bug I've found in EBBS 3.1 is a buffer in
+the chat daemon that could get overflowed by a long hostname. Now,
+I doubt anyone can encode the typical buffer overflow nastiness into
+a hostname and not have name servers choke on it, but who knows.
+
+UPGRADING FROM EBBS-3.1 TO EBBS-3.1.1:
+
+The new MoveMessage feature for moving posts from one board to
+another requires that a couple of lines be added to your existing
+~bbs/etc/access and ~bbs/etc/menu.desc files.
+
+To you access file, add these lines right after the FileChdir entry:
+
+# C_MOVEMESSAGE 98 Move a post from one board to another
+MoveMessage=ALL
+
+You may leave the access set to ALL to allow any user to move their
+own posts. Board managers can move them off of their boards also.
+
+To your menu.desc file, add this to the readmenu "Main":
+
+('t', $PostMove, $$P, MoveMessage,
+"t Toss (move) post to another board")
+
+I chose 't' for Toss since it seemed like the best available fit.
+As always, you may assign your own keys and help text.
+
+The ReleaseNotes for version 3.1 follow. If you are upgrading
+from something EARLIER then 3.1, please follow the instructions
+below FIRST, then complete the above for the 3.1-3.1.1 move.
+
+---------------------------------------------------------------------
+
+This is the official release of Eagles BBS 3.1. It's been over
+two years since the EBBS 3.0.1 release. There have been quite
+a few changes over those years, and it's never been really unstable,
+but I've been too busy with other things to put together a release.
+
+I wanted to set up this package to use GNU autoconf. It didn't
+get done. You still have to edit the Makefile and osdeps.h.
+
+Two new features were added in 3.1-BETA2. First are the /ignore
+and /unignore commands in chat. Also, 8-bit character set support
+and ability to change and save character set mappings on the fly.
+
+Two new features were added in 3.1-BETA3. The file areas now allow
+navigation of directories below the one listed in the ftplist.
+Also, the PBBS library was patched to allow ANSI color to be used
+the the code and in strings read from the config files. Be sure
+to add -DCOLOR to the Makefile CCFLAGS is you want this. The color
+support is very crude, but it's a starting point for hackers to
+do something better with if desired.
+
+The only changes since 3.1-BETA3 are bug fixes.
+
+--- If you are upgrading from EBBS 3.1-BETA3 ---
+
+No config file changes are necessary; just build and install the
+binaries.
+
+--- If you are upgrading from EBBS 3.1-BETA2 ---
+
+No config file changes are necessary; just build and install the
+binaries. If you are using the file boards, you may want to clean
+up ~bbs/etc/ftplist a bit, as only the root directory of a directory
+tree you want to be available need be listed now, as opposed to
+every directory having to be there as in the past.
+
+--- If you are upgrading from EBBS 3.1-BETA ---
+
+Five bbs config files were changed between 3.1-BETA and 3.1-BETA-2.
+You should copy chathlp.txt and protos to your ~bbs/etc directory.
+The access, bbconfig, and menu.desc also have additions. Add these
+entries:
+
+To your access file, after C_XYZMENU (entry 95):
+# C_SETCHARSET 96 Set charset (if 8-bit support not needed, use NONE)
+SetCharset=ALL
+
+To your bbconfig file:
+locale=
+
+To your menu.desc file, in the (X)yz Menu description:
+("Language", "Language", "Language", SetCharset, $SetCharset,
+"(L)anguage Set language-dependent character mapping")
+
+Finally, copy the charset files from the config directory to ~bbs/etc
+if you wish to use the 8-bit support.
+
+--- If you are upgrading from EBBS 3.0 or 3.01 ---
+
+There are some cool new features in this version. Most noticable is the
+new menus. That was the #1 gripe about EBBS up until now from what I can
+tell, that there wasn't a friendly menu system like you see on dialup bbses.
+Well it's there now, but still not in color. Maybe somebody will try to
+rip out the old PBBS stuff and link with ncurses instead.
+
+Note the new (M)enus function in the Xyz Menu. Be sure you get that entry
+into your existing menu.desc is you are already running EBBS. That lets
+you revert to the old tried-and-true (and faster) menus.
+
+Also we have two-way Internet mail capability now. I have written about it
+in the EBBS-Guide, Chapter VII Section 2, "Internet Mail Support".
+A couple of new files in this distribution are part of this new feature:
+~bbs/bin/bbsmaild and ~bbs/etc/mailers. Be sure you copy those to your
+bbs directories.
+
+Also, the readmenus are now configurable (somewhat) and are defined in
+~bbs/etc/menu.desc. If you already have a bbs, be SURE to cut the bottom
+part with the readmenus out of the menu.desc in this distribution and
+paste them into yours. Also don't to add forget (X)yz/(M)enus and
+(X)yz/(L)anguage, as these are new in EBBS 3.1.
+
+The chat daemon will display a message-of-the-day upon entry now, if
+you put said message in ~bbs/etc/chat.motd.
+
+In ~bbs/etc/bbconfig, the "mailer" entry is no longer used since there
+is a mailers files now. You can remove it. "reservedslots" is a new
+option which allows you to reserve any number of slots for sysops,
+allowing you access when others can't get in. "locale" is a new
+option for setting the default locale for character typing and
+collating, as in setlocale(3).
+
+Finally, there are a few things new in the ~bbs/etc/access file.
+You MUST put these new entries in the right place or things are not
+going to work properly! In fact you may consider just using the new
+access file and setting up the permissions the same way you had them
+before, to be safe. Here are the new entries:
+
+# C_SETCLIOPTS 82 Set client options (i.e. expert/novice menus)
+SetCliOpts=ALL
+
+(C_SEEALLINFO through C_NOTIMEOUT are now 83-88. They must be AFTER
+ C_SETCLIOPTS in the access file.)
+
+# C_USERESERVED 89 Can use specially reserved user table slots.
+UseReserved=Sysop
+# C_EXTERNMAIL 90 May send/receive external e-mail.
+ExternalMail=Sysop
+# C_ADMINMENU 91 Admin Menu access
+AdminMenu=Sysop,AccountMgr,BoardMgr
+# C_FILEMENU 92 File Menu access
+FileMenu=ALL
+# C_MAILMENU 93 Mail Menu access
+MailMenu=ALL
+# C_TALKMENU 94 Talk Menu access
+TalkMenu=ALL
+# C_XYZMENU 95 Xyz Menu access
+XyzMenu=ALL
+# C_SETCHARSET 96 Set charset (if 8-bit support not needed, use NONE)
+SetCharset=ALL
+
+SUMMARY of things to do to upgrade from 3.0 or 3.01 to 3.1:
+
+-- Update ~bbs/etc/access, ~bbs/etc/menu.desc, ~bbs/etc/bbconfig,
+ ~bbs/etc/chathlp.txt, ~bbs/etc/protos as directed above or replace them
+
+-- Copy new config files (mailers, modes if you don't have 3.01) to
+ ~bbs/etc
+
+-- Copy all executables (lbbs, chatd, addacct, delacct, bbslog, bbfinger,
+ bbsmaild) to ~bbs/bin, and make bbsmail a link to bbsmaild if you like
+
+-- If 2-way mail is desired, fix your sendmail.cf file to route bbs mail
+ to bbsmaild (helpful hints in EBBS-Guide)
+
+-- If you're using boardclean, get the new cleanpkg.tar.gz from datasync
+ and put the new executable into ~bbs/bin
+
+Lotd of changes and stuff. If you find any bugs, tell me about them.
+Especially if you have trouble compiling. I only have Linux and limited,
+sporadic access to other operating systems so I count on you folks
+to let me know when things break on non-Linux systems.
+
+See the blurb in the README about the EBBS Mailing List if you haven't
+already seen it.
+
+Ray Rocker
+rocker@datasync.com
diff --git a/acct.c b/acct.c
new file mode 100644
index 0000000..e64b0b6
--- /dev/null
+++ b/acct.c
@@ -0,0 +1,629 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "server.h"
+#include <sys/stat.h>
+
+extern SERVERDATA server;
+
+/* lastlogin file stuff */
+
+#define LASTLOGIN "lastlogin"
+
+get_lastlog_file(userid, buf)
+char *userid;
+char *buf;
+{
+ get_home_directory(userid, buf);
+ strcat(buf, "/");
+ strcat(buf, LASTLOGIN);
+}
+
+get_lastlog_time(userid, tbuf)
+char *userid;
+LONG *tbuf;
+{
+ PATH lastlog;
+ struct stat stbuf;
+ *tbuf = 0;
+ get_lastlog_file(userid, lastlog);
+ if (stat(lastlog, &stbuf) == 0) {
+ *tbuf = (LONG)stbuf.st_mtime;
+ }
+}
+
+get_lastlog_host(userid, host)
+char *userid;
+char *host;
+{
+ PATH lastlog;
+ FILE *fp;
+ host[0] = '\0';
+ get_lastlog_file(userid, lastlog);
+ if (fp = fopen(lastlog, "r")) {
+ fgets(host, HOSTLEN, fp);
+ fclose(fp);
+ }
+}
+
+set_lastlog(userid, host)
+char *userid;
+char *host;
+{
+ PATH lastlog;
+ FILE *fp;
+ get_lastlog_file(userid, lastlog);
+ if (fp = fopen(lastlog, "w")) {
+ fputs(host, fp);
+ fclose(fp);
+ }
+}
+
+/*
+ Profile. Bunch of optional information stored one per line,
+ XXXX=data, where XXXX is TERM, ADDR, NAME, MAIL, PROT.
+*/
+
+#define PROFILE "profile"
+#define PROFILE_TERM "TERM"
+#define PROFILE_CSET "CSET"
+#define PROFILE_NAME "NAME"
+#define PROFILE_ADDR "ADDR"
+#define PROFILE_MAIL "MAIL"
+#define PROFILE_PROT "PROT"
+#define PROFILE_EDIT "EDIT"
+
+#define DEFAULT_TERMINAL "vt100"
+#define DEFAULT_EDITOR "builtin"
+#define DEFAULT_CHARSET "ascii"
+
+struct profile_data {
+ char *key;
+ char *data;
+};
+
+get_profile_file(userid, buf)
+char *userid;
+char *buf;
+{
+ get_home_directory(userid, buf);
+ strcat(buf, "/");
+ strcat(buf, PROFILE);
+}
+
+_match_profile_key(rec, key)
+char *rec;
+char *key;
+{
+ if (!strncmp(rec, key, 4) && rec[4] == '=') return S_RECEXISTS;
+ return S_OK;
+}
+
+_profile_upd(newrec, oldrec, data)
+char *newrec;
+char *oldrec;
+char *data;
+{
+ strncpy(newrec, oldrec, 5);
+ strcpy(newrec+5, data);
+ strcat(newrec, "\n");
+ return S_OK;
+}
+
+_profile_format(rec, pd)
+char *rec;
+struct profile_data *pd;
+{
+ sprintf(rec, "%s=%s\n", pd->key, pd->data);
+ return S_OK;
+}
+
+set_profile_data(userid, key, data)
+char *userid;
+char *key;
+char *data;
+{
+ int rc;
+ PATH profile;
+ struct stat stbuf;
+ get_profile_file(userid, profile);
+
+ if (data[0] == '\0' ||
+ (!strcmp(key, PROFILE_TERM) && !strcmp(data, DEFAULT_TERMINAL)) ||
+ (!strcmp(key, PROFILE_CSET) && !strcmp(data, DEFAULT_CHARSET)) ||
+ (!strcmp(key, PROFILE_EDIT) && !strcmp(data, DEFAULT_EDITOR))) {
+ _record_delete(profile, _match_profile_key, key);
+ return S_OK;
+ }
+
+ rc = _record_replace(profile, _match_profile_key, key, _profile_upd, data);
+ if (rc != S_OK) {
+ struct profile_data pd;
+ pd.key = key;
+ pd.data = data;
+ rc = _record_add(profile, _match_profile_key, key, _profile_format, &pd);
+ }
+ return rc;
+}
+
+/*ARGSUSED*/
+_profile_fill(indx, rec, acct)
+int indx;
+char *rec;
+ACCOUNT *acct;
+{
+ strip_trailing_space(rec);
+ if (rec[4] != '=') return S_OK;
+ rec[4] = '\0';
+ if (!strcmp(rec, PROFILE_TERM))
+ strncpy(acct->terminal, rec+5, TERMLEN);
+ else if (!strcmp(rec, PROFILE_CSET))
+ strncpy(acct->charset, rec+5, CSETLEN);
+ else if (!strcmp(rec, PROFILE_NAME))
+ strncpy(acct->realname, rec+5, RNAMELEN);
+ else if (!strcmp(rec, PROFILE_ADDR))
+ strncpy(acct->address, rec+5, ADDRLEN);
+ else if (!strcmp(rec, PROFILE_MAIL))
+ strncpy(acct->email, rec+5, MAILLEN);
+ else if (!strcmp(rec, PROFILE_PROT))
+ strncpy(acct->protocol, rec+5, NAMELEN);
+ else if (!strcmp(rec, PROFILE_EDIT))
+ strncpy(acct->editor, rec+5, NAMELEN);
+ return S_OK;
+}
+
+enum_profile_data(userid, acct)
+char *userid;
+ACCOUNT *acct;
+{
+ PATH profile;
+ get_profile_file(userid, profile);
+ _record_enumerate(profile, 0, _profile_fill, acct);
+ if (acct->terminal[0] == '\0')
+ strcpy(acct->terminal, DEFAULT_TERMINAL);
+ if (acct->charset[0] == '\0')
+ strcpy(acct->charset, DEFAULT_CHARSET);
+
+ return S_OK;
+}
+
+_fill_profile_name(rec, buf)
+char *rec;
+char *buf;
+{
+ strip_trailing_space(rec);
+ strncpy(buf, rec+5, RNAMELEN);
+ return S_OK;
+}
+
+lookup_profile_name(userid, namebuf)
+char *userid;
+char *namebuf;
+{
+ PATH profile;
+ int rc;
+ get_profile_file(userid, profile);
+ rc = _record_find(profile, _match_profile_key, PROFILE_NAME,
+ _fill_profile_name, namebuf);
+ return rc;
+}
+
+/* Now for the passfile itself. */
+
+#define PASSFILE "etc/passwds"
+
+#define PF_USERID_OFFSET 0
+#define PF_PASSWD_OFFSET (PF_USERID_OFFSET+NAMELEN+1)
+#define PF_PERMS_OFFSET (PF_PASSWD_OFFSET+PASSLEN+1)
+#define PF_FLAGS_OFFSET (PF_PERMS_OFFSET+9)
+#define PF_USERNAME_OFFSET (PF_FLAGS_OFFSET+5)
+#define PF_END_OF_RECORD (PF_USERNAME_OFFSET+UNAMELEN+1)
+
+format_passent(rec, acct)
+char *rec;
+ACCOUNT *acct;
+{
+ memset(rec, ' ', PF_USERNAME_OFFSET+UNAMELEN);
+ memcpy(rec+PF_USERID_OFFSET, acct->userid, strlen(acct->userid));
+ *(rec+PF_PASSWD_OFFSET-1) = ':';
+ memcpy(rec+PF_PASSWD_OFFSET, acct->passwd, strlen(acct->passwd));
+ *(rec+PF_PERMS_OFFSET-1) = ':';
+ LONGcpy(rec+PF_PERMS_OFFSET, acct->perms);
+ *(rec+PF_FLAGS_OFFSET-1) = ':';
+ SHORTcpy(rec+PF_FLAGS_OFFSET, acct->flags);
+ *(rec+PF_USERNAME_OFFSET-1) = ':';
+ memcpy(rec+PF_USERNAME_OFFSET, acct->username, strlen(acct->username));
+ memcpy(rec+PF_END_OF_RECORD-1, ":\n", 3);
+ return S_OK;
+}
+
+passent_to_account(rec, acct)
+char *rec;
+ACCOUNT *acct;
+{
+ acct->userid[NAMELEN] = '\0';
+ acct->passwd[PASSLEN] = '\0';
+ acct->username[UNAMELEN] = '\0';
+ strncpy(acct->userid, rec+PF_USERID_OFFSET, NAMELEN);
+ strip_trailing_space(acct->userid);
+ strncpy(acct->passwd, rec+PF_PASSWD_OFFSET, PASSLEN);
+ strip_trailing_space(acct->passwd);
+ strncpy(acct->username, rec+PF_USERNAME_OFFSET, UNAMELEN);
+ strip_trailing_space(acct->username);
+ acct->perms = hex2LONG(rec+PF_PERMS_OFFSET);
+ acct->flags = hex2SHORT(rec+PF_FLAGS_OFFSET);
+ return S_OK;
+}
+
+hide_priv_acct_fields(acct)
+ACCOUNT *acct;
+{
+ int myself = is_me(acct->userid);
+ if (!myself) memset(acct->passwd, '\0', PASSLEN);
+ if (!_has_perms(PERM_SYSOP)) acct->perms = 0;
+ if (!_has_access(C_SEEALLAINFO)) {
+ if (myself) acct->flags &= ~FLG_EXEMPT; /* hide only this flag */
+ else acct->flags = 0; /* hide all from others */
+ }
+}
+
+_lookup_account(userid, acct)
+char *userid;
+ACCOUNT *acct;
+{
+ int rc;
+ rc = _record_find(PASSFILE, _match_first, userid, passent_to_account, acct);
+ return rc;
+}
+
+local_bbs_add_account(newacct, is_encrypted)
+ACCOUNT *newacct;
+SHORT is_encrypted;
+{
+
+ int rc;
+ ACCOUNT acct;
+ PATH homedir;
+ PATH maildir;
+ char *adder;
+
+ memcpy(&acct, newacct, sizeof acct);
+ if (!is_valid_userid(acct.userid)) return S_BADUSERID;
+ if (!is_valid_password(acct.passwd)) return S_BADPASSWD;
+
+ if (!is_encrypted) {
+ encrypt_passwd(acct.passwd, newacct->passwd);
+ }
+
+ acct.flags = 0;
+ acct.perms = PERM_DEFAULT;
+
+ rc = _record_add(PASSFILE, _match_first, acct.userid, format_passent, &acct);
+ if (rc != S_OK) return S_USEREXISTS;
+
+ get_home_directory(acct.userid, homedir);
+ get_mail_directory(acct.userid, maildir);
+ recursive_rmdir (homedir);
+ if (mkdir(homedir, 0700) || mkdir(maildir, 0700)) {
+ _record_delete(PASSFILE, _match_first, acct.userid);
+ bbslog(0, "ERROR local_bbs_add_account: mkdir failed: %s or %s\n",
+ homedir, maildir);
+ return S_SYSERR;
+ }
+
+ if (acct.terminal[0])
+ set_profile_data(acct.userid, PROFILE_TERM, acct.terminal);
+ if (acct.charset[0])
+ set_profile_data(acct.userid, PROFILE_CSET, acct.charset);
+ if (acct.realname[0])
+ set_profile_data(acct.userid, PROFILE_NAME, acct.realname);
+ if (acct.address[0])
+ set_profile_data(acct.userid, PROFILE_ADDR, acct.address);
+ if (acct.email[0])
+ set_profile_data(acct.userid, PROFILE_MAIL, acct.email);
+ if (acct.protocol[0])
+ set_profile_data(acct.userid, PROFILE_PROT, acct.protocol);
+ if (acct.editor[0])
+ set_profile_data(acct.userid, PROFILE_EDIT, acct.editor);
+
+ adder = my_userid();
+ if (*adder == '\0')
+ bbslog(1, "NEWACCT %s\n", acct.userid);
+ else bbslog(1, "ADDACCT %s by %s\n", acct.userid, adder);
+
+ return S_OK;
+}
+
+local_bbs_delete_account(userid)
+char *userid;
+{
+ int rc;
+ ACCOUNT acct;
+ PATH homedir;
+
+ rc = _record_find(PASSFILE, _match_first, userid, passent_to_account, &acct);
+ if (rc != S_OK) return S_NOSUCHUSER;
+
+ if ((acct.perms & PERM_SYSOP) && !_has_perms(PERM_SYSOP)) {
+ return S_DENIED;
+ }
+
+ rc = _record_delete(PASSFILE, _match_first, acct.userid);
+ if (rc != S_OK) return S_SYSERR;
+
+ get_home_directory(acct.userid, homedir);
+ recursive_rmdir(homedir);
+#if FULL_USER_DELETE
+ _board_enum_fix_managers(acct.userid, NULL);
+ _acct_enum_fix_overrides(acct.userid, NULL);
+#endif
+
+ bbslog(2, "DELETEACCT %s by %s\n", acct.userid, my_userid());
+ return S_OK;
+}
+
+/*ARGSUSED*/
+update_passent(newrec, oldrec, acct)
+char *newrec;
+char *oldrec;
+ACCOUNT *acct;
+{
+ format_passent(newrec, acct);
+ return S_OK;
+}
+
+_set_account(userid, newacct, flags)
+char *userid;
+ACCOUNT *newacct;
+SHORT flags;
+{
+ int rc;
+ ACCOUNT acct;
+ NAME saveid;
+
+ rc = _record_find(PASSFILE, _match_first, userid, passent_to_account, &acct);
+ if (rc != S_OK) return S_NOSUCHUSER;
+
+ if ((acct.perms & PERM_SYSOP) && !_has_perms(PERM_SYSOP)) {
+ return S_DENIED;
+ }
+
+ if (flags & MOD_USERID) {
+ if (!is_valid_userid(newacct->userid)) return S_BADUSERID;
+ rc = _record_find(PASSFILE, _match_first, newacct->userid, NULL, NULL);
+ if (rc != S_NOSUCHREC) return S_USEREXISTS;
+ strcpy(saveid, acct.userid);
+ strncpy(acct.userid, newacct->userid, NAMELEN);
+ }
+
+ if (flags & MOD_PASSWD) {
+ if (!is_valid_password(newacct->passwd)) return S_BADPASSWD;
+ encrypt_passwd(acct.passwd, newacct->passwd);
+ }
+
+ if (flags & MOD_USERNAME)
+ strncpy(acct.username, newacct->username, UNAMELEN);
+
+ if (flags & _MOD_PERMS)
+ acct.perms = newacct->perms;
+
+ if (flags & _TOGGLE_FLAGS)
+ acct.flags ^= newacct->flags;
+
+ rc = _record_replace(PASSFILE, _match_first, userid, update_passent, &acct);
+
+ if (rc != S_OK) return S_SYSERR;
+
+ if (flags & MOD_USERID) {
+ PATH oldhome, newhome;
+ get_home_directory(saveid, oldhome);
+ get_home_directory(acct.userid, newhome);
+ recursive_rmdir(newhome);
+ rename(oldhome, newhome);
+#if FULL_USER_DELETE
+ _board_enum_fix_managers(saveid, acct.userid);
+ _acct_enum_fix_overrides(saveid, acct.userid);
+#endif
+ }
+
+ if (flags & MOD_TERMINAL)
+ set_profile_data(acct.userid, PROFILE_TERM, newacct->terminal);
+ if (flags & MOD_CHARSET)
+ set_profile_data(acct.userid, PROFILE_CSET, newacct->charset);
+ if (flags & MOD_REALNAME)
+ set_profile_data(acct.userid, PROFILE_NAME, newacct->realname);
+ if (flags & MOD_ADDRESS)
+ set_profile_data(acct.userid, PROFILE_ADDR, newacct->address);
+ if (flags & MOD_EMAIL)
+ set_profile_data(acct.userid, PROFILE_MAIL, newacct->email);
+ if (flags & MOD_PROTOCOL)
+ set_profile_data(acct.userid, PROFILE_PROT, newacct->protocol);
+ if (flags & MOD_EDITOR)
+ set_profile_data(acct.userid, PROFILE_EDIT, newacct->editor);
+
+ return S_OK;
+}
+
+local_bbs_modify_account(userid, acct, flags)
+char *userid;
+ACCOUNT *acct;
+SHORT flags;
+{
+ /* Clear the internal-only flags */
+ flags &= MOD_ACCOUNT_MASK;
+
+ if (flags & MOD_USERID)
+ bbslog(2, "MODIFYACCT %s to %s by %s\n",
+ userid, acct->userid, my_userid());
+ else bbslog(2, "MODIFYACCT %s by %s\n", userid, my_userid());
+
+ return(_set_account(userid, acct, flags));
+}
+
+local_bbs_modify_perms(userid, perms)
+char *userid;
+LONG perms;
+{
+ ACCOUNT acct;
+ acct.perms = perms;
+ bbslog(2, "SETPERMS %s to %08x by %s\n", userid, perms, my_userid());
+ return (_set_account(userid, &acct, _MOD_PERMS));
+}
+
+local_bbs_toggle_exempt(userid)
+char *userid;
+{
+ ACCOUNT acct;
+ acct.flags = FLG_EXEMPT;
+ bbslog(2, "EXEMPT %s by %s\n", userid, my_userid());
+ return (_set_account(userid, &acct, _TOGGLE_FLAGS));
+}
+
+local_bbs_query(userid, acct)
+char *userid;
+ACCOUNT *acct;
+{
+ int rc;
+ memset(acct, 0, sizeof(*acct));
+ rc = _record_find(PASSFILE, _match_first, userid, passent_to_account, acct);
+ if (rc != S_OK) return S_NOSUCHUSER;
+ if (my_utable_slot() == -1) {
+ /* Not logged in -- only allow test of userid's existence */
+ memset(acct, 0, sizeof(*acct));
+ return S_OK;
+ }
+ hide_priv_acct_fields(acct);
+ get_lastlog_time(acct->userid, &acct->lastlogin);
+ get_lastlog_host(acct->userid, acct->fromhost);
+ if (server.queryreal && _has_access(C_SEEREALNAME)) {
+ lookup_profile_name(acct->userid, acct->realname);
+ }
+ return S_OK;
+}
+
+local_bbs_get_userinfo(userid, acct)
+char *userid;
+ACCOUNT *acct;
+{
+ int rc;
+ memset(acct, 0, sizeof(*acct));
+ rc = _record_find(PASSFILE, _match_first, userid, passent_to_account, acct);
+ if (rc != S_OK) return S_NOSUCHUSER;
+ hide_priv_acct_fields(acct);
+ get_lastlog_time(acct->userid, &acct->lastlogin);
+ get_lastlog_host(acct->userid, acct->fromhost);
+ enum_profile_data(acct->userid, acct);
+ return S_OK;
+}
+
+_enum_accounts(indx, rec, en)
+int indx;
+char *rec;
+struct enumstruct *en;
+{
+ ACCOUNT acct;
+ memset(&acct, 0, sizeof acct);
+ passent_to_account(rec, &acct);
+ get_lastlog_time(acct.userid, &acct.lastlogin);
+ hide_priv_acct_fields(&acct);
+ if (en->fn(indx, &acct, en->arg) == ENUM_QUIT) return ENUM_QUIT;
+ return S_OK;
+}
+
+/*ARGSUSED*/
+local_bbs_enum_accounts(chunk, startrec, enumfn, arg)
+SHORT chunk;
+SHORT startrec;
+int (*enumfn)();
+void *arg;
+{
+ struct enumstruct en;
+ en.fn = enumfn;
+ en.arg = arg;
+ _record_enumerate(PASSFILE, startrec, _enum_accounts, &en);
+ return S_OK;
+}
+
+_fill_acctnames(indx, rec, lc)
+int indx;
+char *rec;
+struct listcomplete *lc;
+{
+ NAME userid;
+ memcpy(userid, rec+PF_USERID_OFFSET, NAMELEN);
+ userid[NAMELEN] = '\0';
+ strip_trailing_space(userid);
+ if (!strncasecmp(userid, lc->str, strlen(lc->str)))
+ add_namelist(lc->listp, userid, NULL);
+
+ return S_OK;
+}
+
+local_bbs_acctnames(list, complete)
+NAMELIST *list;
+char *complete;
+{
+ struct listcomplete lc;
+ create_namelist(list);
+ lc.listp = list;
+ lc.str = complete == NULL ? "" : complete;
+ _record_enumerate(PASSFILE, 0, _fill_acctnames, &lc);
+ return S_OK;
+}
+
+/*
+ Called from bbs_delete_board and bbs_modify_board:
+ loop through each account and fix the readbits file.
+ (jumping thru lots of hoops in the name of modularity!)
+*/
+
+extern int fix_readbit_entry();
+
+_acct_enum_fix_readbits(oldbname, newbname)
+char *oldbname;
+char *newbname;
+{
+ struct namechange ncs;
+ ncs.oldname = oldbname;
+ ncs.newname = newbname;
+ _record_enumerate(PASSFILE, 0, fix_readbit_entry, &ncs);
+ return S_OK;
+}
+
+#if FULL_USER_DELETE
+/*
+ Called from bbs_delete_account and bbs_modify_account;
+ loop through all each user's override file and fix it for them.
+ Aren't we nice?
+*/
+
+extern int fix_override_entry();
+
+_acct_enum_fix_overrides(oldname, newname)
+char *oldname;
+char *newname;
+{
+ struct namechange ncs;
+ ncs.oldname = oldname;
+ ncs.newname = newname;
+ _record_enumerate(PASSFILE, 0, fix_override_entry, &ncs);
+ return S_OK;
+}
+#endif
diff --git a/addacct.c b/addacct.c
new file mode 100644
index 0000000..def119e
--- /dev/null
+++ b/addacct.c
@@ -0,0 +1,132 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "server.h"
+
+extern USERDATA user_params;
+
+extern char *optarg;
+
+usage(prog)
+char *prog;
+{
+ fprintf(stderr,
+ "Usage: %s -i userid -p passwd [-u username] [-t termtype]\n", prog);
+ fprintf(stderr,
+ " [-e email] [-r realname] [-a address] [-d bbs-dir]\n");
+}
+
+main(argc, argv)
+int argc;
+char *argv[];
+{
+ char *bbshome = NULL;
+ int iflg, pflg, uflg;
+ int c;
+ ACCOUNT acct;
+ int rc;
+
+ memset(&acct, 0, sizeof(acct));
+ iflg = pflg = uflg = 0;
+
+ while ((c = getopt(argc, argv, "a:d:e:i:p:r:t:u:?")) != -1)
+ {
+ switch (c)
+ {
+ case 'a':
+ strncpy(acct.address, optarg, sizeof acct.address-1);
+ break;
+ case 'd':
+ bbshome = optarg;
+ break;
+ case 'e':
+ strncpy(acct.email, optarg, sizeof acct.email-1);
+ break;
+ case 'i':
+ iflg++;
+ strncpy(acct.userid, optarg, sizeof acct.userid-1);
+ break;
+ case 'p':
+ pflg++;
+ strncpy(acct.passwd, optarg, sizeof acct.passwd-1);
+ break;
+ case 'r':
+ strncpy(acct.realname, optarg, sizeof acct.realname-1);
+ break;
+ case 't':
+ strncpy(acct.terminal, optarg, sizeof acct.terminal-1);
+ break;
+ case 'u':
+ uflg++;
+ strncpy(acct.username, optarg, sizeof acct.username-1);
+ break;
+ case '?':
+ usage(argv[0]);
+ return 2;
+ }
+ }
+
+ if (!iflg || !pflg) {
+ fprintf(stderr, "%s: userid and passwd args must be given\n", argv[0]);
+ return 1;
+ }
+
+ if (home_bbs(bbshome) == -1) {
+ fprintf(stderr, "%s: Cannot chdir to %s\n", argv[0], bbshome);
+ return 1;
+ }
+
+ if (local_bbs_initialize(NULL) != S_OK) {
+ fprintf(stderr, "%s: local_bbs_initialize failed\n", argv[0]);
+ return 1;
+ }
+
+ /* Identify ourself for the log file */
+ strcpy(user_params.u.userid, "[addacct]");
+ user_params.perms = ~0;
+
+ if (!uflg) strncpy(acct.username, acct.userid, sizeof acct.username-1);
+
+ rc = local_bbs_add_account(&acct, 0);
+ switch (rc) {
+ case S_BADUSERID:
+ case S_BADPASSWD:
+ fprintf(stderr, "%s: invalid userid or password\n", acct.userid);
+ break;
+ case S_USEREXISTS:
+ fprintf(stderr, "%s: userid already exists\n", acct.userid);
+ break;
+ case S_SYSERR:
+ fprintf(stderr, "%s: error creating account\n", acct.userid);
+ fprintf(stderr, "Wrong -d argument or $BBSHOME?\n");
+ }
+
+ local_bbs_disconnect();
+
+ return (rc == S_OK ? 0 : 1);
+}
+
+
+
+
+
+
+
+
diff --git a/bbfinger.c b/bbfinger.c
new file mode 100644
index 0000000..23a663d
--- /dev/null
+++ b/bbfinger.c
@@ -0,0 +1,78 @@
+#include "server.h"
+
+extern char *optarg;
+extern int optind;
+
+extern USERDATA user_params;
+extern SERVERDATA server;
+
+extern char *ModeToString __P((SHORT));
+
+usage(prog)
+char *prog;
+{
+ fprintf(stderr,
+ "Usage: %s [-d bbs-dir]\n", prog);
+}
+
+/*ARGSUSED*/
+one_line_display(indx, urec, count)
+int indx;
+USEREC *urec;
+int *count;
+{
+ printf("%-12s %-25s %-25s %s\n", urec->userid,
+ urec->username, urec->fromhost,
+ ModeToString(urec->mode));
+
+ (*count)++;
+ return S_OK;
+}
+
+main(argc, argv)
+int argc;
+char *argv[];
+{
+ char *bbshome = NULL;
+ int c, count = 0;
+
+ while ((c = getopt(argc, argv, "d:?")) != -1)
+ {
+ switch (c)
+ {
+ case 'd':
+ bbshome = optarg;
+ break;
+ case '?':
+ usage(argv[0]);
+ return 2;
+ }
+ }
+
+ if (home_bbs(bbshome) == -1) {
+ fprintf(stderr, "%s: Cannot chdir to %s\n", argv[0], bbshome);
+ return 1;
+ }
+
+ if (local_bbs_initialize(NULL) != S_OK) {
+ fprintf(stderr, "%s: local_bbs_initialize failed\n", argv[0]);
+ return 1;
+ }
+
+ /* Identify ourself for the log file, just in case */
+ strcpy(user_params.u.userid, "[bbfinger]");
+ /* assume lowest possible priviliges */
+ user_params.perms = 0;
+
+ /* Do it! */
+ printf("[%s]\n\n", server.name);
+ printf("%-12s %-25s %-25s %s\n",
+ "User Id", "User Name", "From", "Mode");
+ local_bbs_enum_users(50, 0, NULL, one_line_display, &count);
+ printf("\n%d %s displayed\n\n", count, count==1?"user":"users");
+
+ local_bbs_disconnect();
+
+ return 0;
+}
+
diff --git a/bbslog.c b/bbslog.c
new file mode 100644
index 0000000..19e7068
--- /dev/null
+++ b/bbslog.c
@@ -0,0 +1,65 @@
+#include "server.h"
+
+extern char *optarg;
+extern int optind;
+
+usage(prog)
+char *prog;
+{
+ fprintf(stderr,
+ "Usage: %s [-d bbs-dir] [-h log-header] log-level message\n", prog);
+}
+
+main(argc, argv)
+int argc;
+char *argv[];
+{
+ char *bbshome = NULL;
+ char *logheader = NULL;
+ char *msg;
+ int c;
+ int loglevel, rc;
+
+ while ((c = getopt(argc, argv, "d:h:?")) != -1)
+ {
+ switch (c)
+ {
+ case 'd':
+ bbshome = optarg;
+ break;
+ case 'h':
+ logheader = optarg;
+ break;
+ case '?':
+ usage(argv[0]);
+ return 2;
+ }
+ }
+
+ if (optind+2 > argc) {
+ usage(argv[0]);
+ return 2;
+ }
+
+ if (logheader == NULL) logheader = "BBSLOG";
+ loglevel = atoi(argv[optind++]);
+ msg = argv[optind];
+
+ if (home_bbs(bbshome) == -1) {
+ fprintf(stderr, "%s: Cannot chdir to %s\n", argv[0], bbshome);
+ return 1;
+ }
+
+ if (local_bbs_initialize(NULL) != S_OK) {
+ fprintf(stderr, "%s: local_bbs_initialize failed\n", argv[0]);
+ return 1;
+ }
+
+ set_log_header(logheader);
+ bbslog(loglevel, "%s\n", msg);
+
+ local_bbs_disconnect();
+
+ return 0;
+}
+
diff --git a/bbsmaild.c b/bbsmaild.c
new file mode 100644
index 0000000..8069ee5
--- /dev/null
+++ b/bbsmaild.c
@@ -0,0 +1,296 @@
+#include "server.h"
+#include <pwd.h>
+
+/* Header types, so we can deal with continuations */
+#define HEADER_NONE 0
+#define HEADER_OTHER 1
+#define HEADER_SUBJECT 2
+#define HEADER_FROM 3
+#define HEADER_REPLYTO 4
+
+extern USERDATA user_params;
+extern SERVERDATA server;
+
+extern char *optarg;
+extern int optind;
+
+/* Are we bbsmail or bbsmaild? Assume bbsmaild */
+int daemon = 1;
+
+/* Information we need from the headers */
+ADDR sender;
+TITLE subject;
+RNAME sendername;
+
+/* sender, without the prefix */
+char *sptr;
+int ssize;
+
+char *
+identify_header(str, hdr_type)
+char *str;
+int *hdr_type;
+{
+ char *cp, *colon;
+ colon = strchr(str, ':');
+ if (colon == NULL) {
+ *hdr_type = HEADER_NONE;
+ return str;
+ }
+ *colon = '\0';
+ for (cp = str; cp != colon; cp++) {
+ if (*cp == ' ' || *cp == '\t') {
+ *hdr_type = HEADER_NONE;
+ return str;
+ }
+ }
+
+ if (!strcasecmp(str, "Subject")) *hdr_type = HEADER_SUBJECT;
+ else if (!strcasecmp(str, "From")) *hdr_type = HEADER_FROM;
+ else if (!strcasecmp(str, "Reply-To")) *hdr_type = HEADER_REPLYTO;
+ else *hdr_type = HEADER_OTHER;
+
+ for (cp = colon+1; *cp == ' ' || *cp == '\t'; cp++) ;
+ return cp;
+}
+
+split_sender(type, str)
+int type;
+char *str;
+{
+ /*
+ This splits a From: or Reply-To: header into the address and
+ full name parts, according to RFC 822. Give the Reply-To precedence.
+ */
+ char *cp, *sp;
+ cp = strchr(str, '<');
+ if (cp == NULL) {
+ /* Format should be "address (Real Name)" */
+ cp = strchr(str, ' ');
+ if (cp != NULL) *cp++ = '\0';
+ if (type == HEADER_REPLYTO || *sptr == '\0')
+ strncpy(sptr, str, ssize);
+ if (cp != NULL && *cp == '(') {
+ sp = strchr(cp, ')');
+ if (sp != NULL) {
+ *sp = '\0';
+ if (type == HEADER_REPLYTO || sendername[0] == '\0')
+ strncpy(sendername, cp, sizeof sendername);
+ }
+ }
+ }
+ else {
+ /* Format should be "Real Name <address>" */
+ if (cp > str) {
+ *(cp-1) = '\0';
+ if (type == HEADER_REPLYTO || sendername[0] == '\0')
+ strncpy(sendername, str, sizeof sendername);
+ }
+ sp = strchr(cp, '>');
+ if (sp != NULL) {
+ *sp = '\0';
+ if (type == HEADER_REPLYTO || *sptr == '\0')
+ strncpy(sptr, cp+1, ssize);
+ }
+ }
+
+ sender[strlen(sender)] = '\0';
+ sendername[strlen(sendername)] = '\0';
+ return 0;
+}
+
+eat_header(str)
+char *str;
+{
+ /*
+ This attempts to determine if we are reading headers. It is
+ not foolproof -- if something looks like a header but really
+ isn't, it won't get into the delivered message. As long as the
+ message is in the standard format (headers, blank line, text)
+ it'll be ok.
+ */
+ static int lastheader = HEADER_NONE;
+ int header, is_cont;
+ char *nl, *htext;
+
+ if (str[0] == '\n' || str[0] == '\0') return 0;
+
+ /* Chop newline */
+ nl = strchr(str, '\n');
+ if (nl != NULL) *nl = '\0';
+
+ if (str[0] == ' ' || str[0] == '\t') {
+ header = lastheader;
+ for (htext = str; *htext == ' ' || *htext == '\t'; htext++) ;
+ is_cont = 1;
+ }
+ else {
+ htext = identify_header(str, &header);
+ is_cont = 0;
+ }
+ lastheader = header;
+
+ switch (header) {
+ case HEADER_NONE: return 0;
+ case HEADER_OTHER: break;
+ case HEADER_SUBJECT:
+ if (!is_cont) strncpy(subject, htext, sizeof subject - 1);
+ break;
+ case HEADER_FROM:
+ case HEADER_REPLYTO:
+ if (!is_cont) split_sender(header, htext);
+ break;
+ }
+ return 1;
+}
+
+show_failures(indx, userid, mask)
+int indx;
+char *userid;
+LONG *mask;
+{
+ if ((*mask) & (LONG)(1<<indx)) {
+ fprintf(stderr, "%s: bbs_mail failed\n", userid);
+ }
+ return S_OK;
+}
+
+usage(prog)
+char *prog;
+{
+ if (daemon)
+ fprintf(stderr, "Usage: %s [-d bbs-dir] user ...\n", prog);
+ else
+ fprintf(stderr, "Usage: %s [-d bbs-dir] [-s subject] user ...\n",
+ prog);
+}
+
+main(argc, argv)
+int argc;
+char *argv[];
+{
+ char *bbshome = NULL, *prog;
+ char copybuf[256];
+ int c, rc;
+ int in_header = 1;
+ FILE *fp;
+ NAMELIST userid_list;
+ LONG failmask;
+
+ /* Prefix sender with INTERNET: */
+ strcpy(sender, MAILER_PREFIX);
+ strcat(sender, ":");
+ sptr = sender + strlen(sender);
+ ssize = sizeof(sender) - strlen(sender);
+
+ /* Find out if we are bbsmail or bbsmaild */
+ prog = strrchr(argv[0], '/');
+ if (prog == NULL) prog = argv[0];
+ else prog++;
+ if (!strcmp(prog, "bbsmail")) {
+ /* We do not expect headers. Get sender info from passwd file. */
+ struct passwd *pw = getpwuid(getuid());
+ if (pw != NULL) {
+ char *comma;
+ strncpy(sptr, pw->pw_name, ssize);
+ strncpy(sendername, pw->pw_gecos, sizeof(sendername)+1);
+ if ((comma = strchr(sendername, ',')) != NULL) *comma = '\0';
+ }
+ daemon = in_header = 0;
+ }
+
+ /* Scan argv for option flags */
+ while ((c = getopt(argc, argv, "d:s:?")) != -1)
+ {
+ switch (c)
+ {
+ case 'd':
+ bbshome = optarg;
+ break;
+ case 's':
+ if (daemon) {
+ usage(prog);
+ return 2;
+ }
+ strncpy(subject, optarg, sizeof(subject)-1);
+ break;
+ case '?':
+ usage(prog);
+ return 2;
+ }
+ }
+
+ /* Must have at least one recipient */
+ if (optind >= argc) {
+ usage(prog);
+ return 2;
+ }
+
+ /* Form recipients into namelist */
+ userid_list = NULL;
+ while (optind < argc) {
+ if (!is_in_namelist(userid_list, argv[optind]))
+ add_namelist(&userid_list, argv[optind], NULL);
+ optind++;
+ }
+
+ /* Home and initialize bbs library */
+ if (home_bbs(bbshome) == -1) {
+ fprintf(stderr, "%s: Cannot chdir to %s\n", prog, bbshome);
+ return 1;
+ }
+
+ if (local_bbs_initialize(NULL) != S_OK) {
+ fprintf(stderr, "%s: local_bbs_initialize failed\n", prog);
+ return 1;
+ }
+
+ /* Identify ourself for the log file */
+ strcpy(user_params.u.userid, "[bbsmail]");
+ user_params.perms = ~0;
+
+ /* Copy message to temporary file, eating headers first */
+ fp = fopen(server.tempfile, "w");
+ if (fp == NULL) {
+ free_namelist(&userid_list);
+ fprintf(stderr, "%s: ", prog);
+ perror("spool file open failed");
+ return 1;
+ }
+ while (fgets(copybuf, sizeof copybuf, stdin) != NULL) {
+ if (in_header) {
+ in_header = eat_header(copybuf);
+ }
+ if (!in_header) fputs(copybuf, fp);
+ }
+ fclose(fp);
+
+ /* Make sure we know who the sender is */
+ if (*sptr == '\0') {
+ free_namelist(&userid_list);
+ remove(server.tempfile);
+ fprintf(stderr, "%s: cannot determine sender\n", prog);
+ return 1;
+ }
+
+ /* Now mail it and let our caller know what happened */
+ rc = local_bbs_mail(sender, sendername, userid_list, subject,
+ server.tempfile, &failmask);
+
+ if (rc != S_OK) {
+ apply_namelist(userid_list, show_failures, &failmask);
+ if (rc == S_NOSUCHUSER)
+ fprintf(stderr, "bbs_mail: No such user\n");
+ else if (rc == S_CANNOTMAIL)
+ fprintf(stderr, "bbs_mail: User cannot receive Internet mail\n");
+ }
+
+ /* That's all, folks */
+ free_namelist(&userid_list);
+ remove(server.tempfile);
+ local_bbs_disconnect();
+
+ return (rc == S_OK ? 0 : 1);
+}
+
+
diff --git a/board.c b/board.c
new file mode 100644
index 0000000..9be00ba
--- /dev/null
+++ b/board.c
@@ -0,0 +1,404 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "server.h"
+#include <time.h>
+#ifdef NeXT
+# include <sys/stat.h>
+#endif
+
+#define BOARDFILE "etc/boardlist"
+
+#define MGR_FILE "managers"
+
+get_managers_file(bname, buf)
+char *bname;
+char *buf;
+{
+ get_board_directory(bname, buf);
+ strcat(buf, "/");
+ strcat(buf, MGR_FILE);
+}
+
+local_bbs_set_boardmgrs(bname, list)
+char *bname;
+NAMELIST list;
+{
+ BOARD board;
+ PATH buf;
+ if (_lookup_board(bname, &board) != S_OK) return S_NOSUCHBOARD;
+ get_managers_file(board.name, buf);
+ return (write_namelist(buf, list));
+}
+
+local_bbs_get_boardmgrs(bname, list)
+char *bname;
+NAMELIST *list;
+{
+ BOARD board;
+ PATH buf;
+ if (_lookup_board(bname, &board) != S_OK) return S_NOSUCHBOARD;
+ get_managers_file(board.name, buf);
+ return (read_namelist(buf, list));
+}
+
+_has_read_access(board)
+BOARD *board;
+{
+ if (board->readmask == 0) return 1;
+ return (_has_perms(board->readmask));
+}
+
+_has_post_access(board)
+BOARD *board;
+{
+ if (!_has_access(C_POST)) return 0;
+ if (board->postmask == 0) return 1;
+ return (_has_perms(board->postmask));
+}
+
+_has_manager_access(board)
+BOARD *board;
+{
+ PATH mgrfile;
+ if (_has_access(C_ALLBOARDMGR)) return 1;
+ get_managers_file(board->name, mgrfile);
+ if (_record_find(mgrfile, _match_full, my_userid(), NULL, NULL) == S_OK)
+ return 1;
+ else return 0;
+}
+
+format_boardent(rec, board)
+char *rec;
+BOARD *board;
+{
+ rec[0] = '\0';
+ rec = _append_quoted(rec, board->name);
+ rec = LONGcpy(rec, board->readmask);
+ *rec++ = ':';
+ rec = LONGcpy(rec, board->postmask);
+ *rec++ = ':';
+ rec = _append_quoted(rec, board->description);
+ strcat(rec, "\n");
+ return S_OK;
+}
+
+boardent_to_board(rec, board)
+char *rec;
+BOARD *board;
+{
+ rec = _extract_quoted(rec, board->name, sizeof(board->name));
+ board->readmask = hex2LONG(rec);
+ rec+=9;
+ board->postmask = hex2LONG(rec);
+ rec+=9;
+ rec = _extract_quoted(rec, board->description, sizeof(board->description));
+ return S_OK;
+}
+
+hide_priv_board_fields(board)
+BOARD *board;
+{
+ if (!_has_access(C_SEEALLBINFO)) {
+ board->readmask = 0;
+ board->postmask = 0;
+ }
+}
+
+_lookup_board(bname, board)
+char *bname;
+BOARD *board;
+{
+ int rc;
+ memset(board, 0, sizeof *board);
+ rc = _record_find(BOARDFILE, _match_first, bname, boardent_to_board, board);
+ return rc;
+}
+
+local_bbs_add_board(newboard)
+BOARD *newboard;
+{
+ int rc;
+ BOARD board;
+ PATH homedir;
+
+ memcpy(&board, newboard, sizeof board);
+ if (!is_valid_boardname(board.name)) return S_BADBOARDNAME;
+
+ rc = _record_add(BOARDFILE,_match_first,board.name,format_boardent,&board);
+ if (rc != S_OK) return S_BOARDEXISTS;
+
+ get_board_directory(board.name, homedir);
+ recursive_rmdir (homedir);
+ if (mkdir(homedir, 0700)) {
+ _record_delete(BOARDFILE, _match_first, board.name);
+ bbslog(0, "ERROR local_bbs_add_board: mkdir failed: %s\n", homedir);
+ return S_SYSERR;
+ }
+
+ bbslog(2, "ADDBOARD %s by %s\n", board.name, my_userid());
+ return S_OK;
+}
+
+local_bbs_delete_board(bname)
+char *bname;
+{
+ int rc;
+ BOARD board;
+ PATH homedir;
+
+ rc = _lookup_board(bname, &board);
+ if (rc != S_OK) return S_NOSUCHBOARD;
+
+ rc = _record_delete(BOARDFILE, _match_first, board.name);
+ if (rc != S_OK) return S_SYSERR;
+
+ get_board_directory(board.name, homedir);
+ recursive_rmdir(homedir);
+ _acct_enum_fix_readbits(board.name, NULL);
+
+ bbslog(2, "DELETEBOARD %s by %s\n", board.name, my_userid());
+ return S_OK;
+}
+
+local_bbs_get_board(bname, board)
+char *bname;
+BOARD *board;
+{
+ int rc;
+ BOARD myboard;
+ SHORT order;
+ rc = _lookup_board(bname, &myboard);
+ if (rc != S_OK) return S_NOSUCHBOARD;
+ if (!_has_read_access(&myboard)) return S_NOSUCHBOARD;
+ get_read_order(myboard.name, &order);
+ if (order & READ_ORDER_ZAPPED) myboard.flags |= BOARD_ZAPPED;
+ hide_priv_board_fields(&myboard);
+ memcpy(board, &myboard, sizeof(*board));
+ return S_OK;
+}
+
+/*ARGSUSED*/
+update_boardent(newrec, oldrec, board)
+char *newrec;
+char *oldrec;
+BOARD *board;
+{
+ format_boardent(newrec, board);
+ return S_OK;
+}
+
+_set_board(bname, newboard, flags)
+char *bname;
+BOARD *newboard;
+SHORT flags;
+{
+ int rc;
+ BOARD board;
+ NAME saveid;
+
+ rc = _lookup_board(bname, &board);
+ if (rc != S_OK) return S_NOSUCHBOARD;
+
+ strcpy(saveid, board.name);
+
+ if (flags & MOD_BNAME) {
+ if (!is_valid_boardname(newboard->name)) return S_BADBOARDNAME;
+ rc = _record_find(BOARDFILE, _match_first, newboard->name, NULL, NULL);
+ if (rc != S_NOSUCHREC) return S_BOARDEXISTS;
+ strncpy(board.name, newboard->name, NAMELEN);
+ }
+
+ if (flags & MOD_READMASK)
+ board.readmask = newboard->readmask;
+
+ if (flags & MOD_POSTMASK)
+ board.postmask = newboard->postmask;
+
+ if (flags & MOD_BOARDDESC)
+ strncpy(board.description, newboard->description, TITLELEN);
+
+ rc = _record_replace(BOARDFILE,_match_first,saveid,update_boardent,&board);
+
+ if (rc != S_OK) return S_SYSERR;
+
+ if (flags & MOD_BNAME) {
+ PATH oldhome, newhome;
+ get_board_directory(saveid, oldhome);
+ get_board_directory(board.name, newhome);
+ recursive_rmdir(newhome);
+ rename(oldhome, newhome);
+ _acct_enum_fix_readbits(saveid, board.name);
+ }
+
+ return S_OK;
+}
+
+local_bbs_modify_board(bname, board, flags)
+char *bname;
+BOARD *board;
+SHORT flags;
+{
+ if (flags & MOD_BNAME)
+ bbslog(2, "MODIFYBOARD %s to %s by %s\n", bname, board->name, my_userid());
+ else bbslog(2, "MODIFYBOARD %s by %s\n", bname, my_userid());
+
+ return(_set_board(bname, board, flags));
+}
+
+_enum_boards(indx, rec, en)
+int indx;
+char *rec;
+struct enumstruct *en;
+{
+ BOARD board;
+ SHORT order;
+ int zapped;
+ memset(&board, '\0', sizeof board);
+ boardent_to_board(rec, &board);
+ if (!_has_read_access(&board)) return S_OK;
+
+ get_read_order(board.name, &order);
+
+ if (order == READ_ORDER_ZAPPED) {
+ if (!(en->flags & BE_ZAPPED)) return S_OK;
+ board.flags |= BOARD_ZAPPED;
+ }
+ else if (!(en->flags & BE_UNZAPPED)) return S_OK;
+
+ if (en->flags & BE_DO_COUNTS) {
+ READINFO rinfo;
+ get_bitfile_ent(board.name, &rinfo);
+ _board_count(&board, &rinfo);
+ }
+
+ hide_priv_board_fields(&board);
+ if (en->fn(indx, &board, en->arg) == ENUM_QUIT) return ENUM_QUIT;
+ return S_OK;
+}
+
+/*ARGSUSED*/
+local_bbs_enum_boards(chunk, startrec, flags, enumfn, arg)
+SHORT chunk;
+SHORT startrec;
+SHORT flags;
+int (*enumfn)();
+void *arg;
+{
+ struct enumstruct en;
+ en.fn = enumfn;
+ en.arg = arg;
+ en.flags = flags;
+ _record_enumerate(BOARDFILE, startrec, _enum_boards, &en);
+ return S_OK;
+}
+
+_fill_boardnames(indx, rec, lc)
+int indx;
+char *rec;
+struct listcomplete *lc;
+{
+ BOARD board;
+ boardent_to_board(rec, &board);
+ if (_has_read_access(&board)) {
+ if (!strncasecmp(board.name, lc->str, strlen(lc->str)))
+ add_namelist(lc->listp, board.name, NULL);
+ }
+ return S_OK;
+}
+
+local_bbs_boardnames(list, complete)
+NAMELIST *list;
+char *complete;
+{
+ struct listcomplete lc;
+ create_namelist(list);
+ lc.listp = list;
+ lc.str = complete == NULL ? "" : complete;
+ _record_enumerate(BOARDFILE, 0, _fill_boardnames, &lc);
+ return S_OK;
+}
+
+/*ARGSUSED*/
+_visit_all_boards(indx, rec, arg)
+int indx;
+char *rec;
+void *arg;
+{
+ BOARD board;
+ boardent_to_board(rec, &board);
+ if (_has_read_access(&board))
+ _mark_all_as_read(board.name);
+
+ return S_OK;
+}
+
+local_bbs_visit_board(bname)
+char *bname;
+{
+ int rc;
+ BOARD board;
+ if (!strcmp(bname, "*")) {
+ _record_enumerate(BOARDFILE, 0, _visit_all_boards, NULL);
+ }
+ else {
+ rc = _lookup_board(bname, &board);
+ if (rc != S_OK) return S_NOSUCHBOARD;
+ if (!_has_read_access(&board)) return S_NOSUCHBOARD;
+ _mark_all_as_read(board.name);
+ }
+ return S_OK;
+}
+
+#if FULL_USER_DELETE
+/*
+ For changing the board manager files when a user changes id
+ or gets deleted.
+*/
+
+/*ARGSUSED*/
+_do_fix_manager(indx, rec, ncs)
+int indx;
+char *rec;
+struct namechange *ncs;
+{
+ BOARD board;
+ PATH mgrfile;
+ boardent_to_board(rec, &board);
+ get_managers_file(board.name, mgrfile);
+
+ if (ncs->newname == NULL)
+ _record_delete(mgrfile, _match_full, ncs->oldname);
+ else _record_replace(mgrfile, _match_full, ncs->oldname,
+ _change_name, ncs->newname);
+ return S_OK;
+}
+
+_board_enum_fix_managers(oldname, newname)
+char *oldname;
+char *newname;
+{
+ struct namechange ncs;
+ ncs.oldname = oldname;
+ ncs.newname = newname;
+ _record_enumerate(BOARDFILE, 0, _do_fix_manager, &ncs);
+ return S_OK;
+}
+#endif
diff --git a/c_boards.c b/c_boards.c
new file mode 100644
index 0000000..13c1363
--- /dev/null
+++ b/c_boards.c
@@ -0,0 +1,436 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "client.h"
+#include <time.h>
+#include <ctype.h>
+
+/* The currently selected board */
+NAME currboard;
+NAMELIST boardlist;
+
+/* stuff in c_users.c for the permission mask setting */
+extern LONG SetPermMenu __P((LONG));
+
+CheckBoardName(bname)
+char *bname;
+{
+ BOARD buf;
+ if (bname[0] == '\0') return 1;
+ if (!is_valid_boardname(bname)) return 3;
+ if (bbs_get_board(bname, &buf) != S_NOSUCHBOARD) return 2;
+ return 0;
+}
+
+/*ARGSUSED*/
+AllBoardsFunc(indx, board, info)
+int indx;
+BOARD *board;
+struct enum_info *info;
+{
+ if (info->topline == info->currline) {
+ move(info->topline-1, 0);
+ prints(" %-16s %-58s\n", "Name", "Description");
+ }
+
+ prints("%c%-15s %c%c %s\n",
+ BITISSET(board->flags, BOARD_ZAPPED) ? '*' : ' ',
+ board->name,
+ board->readmask ? 'R' : ' ',
+ board->postmask ? 'P' : ' ',
+ board->description);
+
+ info->currline++;
+ info->count++;
+
+ if (info->currline > info->bottomline) {
+ int ch;
+ standout();
+ prints("--MORE--");
+ standend();
+ clrtoeol();
+ while((ch = igetch()) != EOF) {
+ if(ch == '\n' || ch == '\r' || ch == ' ')
+ break;
+ if(toupper(ch) == 'Q') {
+ move(info->currline, 0);
+ clrtoeol();
+ return ENUM_QUIT;
+ }
+ else bell();
+ }
+ info->currline = info->topline;
+ }
+ return S_OK;
+}
+
+Boards()
+{
+ struct enum_info info;
+ info.count = 0;
+ info.topline = info.currline = 4;
+ info.bottomline = t_lines-2;
+ move(3,0);
+ clrtobot();
+ bbs_enum_boards(t_lines-5, 0, BE_ALL, AllBoardsFunc, &info);
+ clrtobot();
+ move(t_lines-1, 0);
+ prints("%d %s displayed\n", info.count, info.count==1?"board":"boards");
+ return PARTUPDATE;
+}
+
+/*ARGSUSED*/
+BoardCountsFunc(indx, board, info)
+int indx;
+BOARD *board;
+struct enum_info *info;
+{
+ if (info->topline == info->currline) {
+ move(info->topline-1, 0);
+ prints(" %-16s %6s %6s %-30s\n", "Board",
+ "Total", "Unread", "Last Post");
+ }
+
+ prints("%c%-16s %6d %6d %s",
+ BITISSET(board->flags, BOARD_ZAPPED) ? '*' : ' ',
+ board->name,
+ board->totalposts,
+ board->newposts,
+ (board->lastpost == 0 ? "\n" : ctime((time_t *)&board->lastpost)));
+
+ info->currline++;
+ info->count++;
+ info->totals[0] += board->totalposts;
+ info->totals[1] += board->newposts;
+
+ if (info->currline > info->bottomline) {
+ int ch;
+ standout();
+ prints("--MORE--");
+ standend();
+ clrtoeol();
+ while((ch = igetch()) != EOF) {
+ if(ch == '\n' || ch == '\r' || ch == ' ')
+ break;
+ if(toupper(ch) == 'Q') {
+ move(info->currline, 0);
+ clrtoeol();
+ return ENUM_QUIT;
+ }
+ else bell();
+ }
+ info->currline = info->topline;
+ }
+ return S_OK;
+}
+
+BoardCounts()
+{
+ struct enum_info info;
+ SHORT flags;
+ char ans[9];
+ info.count = 0;
+ info.topline = info.currline = 4;
+ info.bottomline = t_lines-2;
+ info.totals[0] = info.totals[1] = 0;
+
+ move(3,0);
+ clrtobot();
+ if (getdata(3, 0, "(U)nzapped only, (Z)apped only, or (A)ll? [U]: ",
+ ans, sizeof ans, DOECHO, 1) == -1) return FULLUPDATE;
+
+ if (*ans == 'Z' || *ans == 'z') flags = BE_ZAPPED;
+ else if (*ans == 'A' || *ans == 'a') flags = BE_ALL;
+ else flags = BE_UNZAPPED;
+
+ flags |= BE_DO_COUNTS;
+ bbs_enum_boards(t_lines-5, 0, flags, BoardCountsFunc, &info);
+
+ clrtobot();
+ move(info.currline, 0);
+
+ prints(" %-16s %6d %6d\n", "*** TOTALS ***",
+ info.totals[0], info.totals[1]);
+
+ return PARTUPDATE;
+}
+
+SelectBoard()
+{
+ NAME newboard;
+ move(2,0);
+ clrtobot();
+ bbs_boardnames(&boardlist, NULL);
+ if (boardlist == NULL) {
+ prints("No boards to select.\n");
+ pressreturn();
+ return FULLUPDATE;
+ }
+ namecomplete(NULL, boardlist, "Enter board name: ", newboard, sizeof(NAME));
+ if (newboard[0] == '\0') return FULLUPDATE;
+ else if (!is_in_namelist(boardlist, newboard)) {
+ move(4,0);
+ bbperror(S_NOSUCHBOARD, NULL);
+ pressreturn();
+ return FULLUPDATE;
+ }
+ strcpy(currboard, newboard);
+ return (FULLUPDATE | NEWDIRECT);
+}
+
+Zap()
+{
+ NAME zapboard;
+ BOARD brec;
+ SHORT notzapped;
+ int rc;
+ move(2,0);
+ bbs_boardnames(&boardlist, NULL);
+ namecomplete(NULL, boardlist, "Board to zap/unzap: ",
+ zapboard, sizeof(NAME));
+ if (zapboard[0] == '\0') return PARTUPDATE;
+ if (!is_in_namelist(boardlist, zapboard) ||
+ bbs_get_board(zapboard, &brec) != S_OK) {
+ move(4,0);
+ bbperror(S_NOSUCHBOARD, NULL);
+ pressreturn();
+ return FULLUPDATE;
+ }
+ notzapped = !BITISSET(brec.flags, BOARD_ZAPPED);
+ if ((rc = bbs_zap_board(brec.name, notzapped)) != S_OK) {
+ move(3,0);
+ bbperror(rc, "Zap failed");
+ }
+ return PARTUPDATE;
+}
+
+Visit()
+{
+ NAME visitboard;
+ char ans[4];
+ int code;
+ move(2,0);
+ clrtobot();
+ bbs_boardnames(&boardlist, NULL);
+ add_namelist(&boardlist, "*", NULL);
+ namecomplete(NULL, boardlist, "Visit board (* = all unzapped boards): ",
+ visitboard, sizeof(NAME));
+ remove_namelist(&boardlist, "*");
+ if (visitboard[0] == '\0') return FULLUPDATE;
+ else if (visitboard[0] != '*' && !is_in_namelist(boardlist, visitboard)) {
+ move(4,0);
+ bbperror(S_NOSUCHBOARD, NULL);
+ pressreturn();
+ return FULLUPDATE;
+ }
+ getdata(3,0,"Are you sure (Y/N)? [N]: ", ans, sizeof(ans), DOECHO, 0);
+ move(4,0);
+ if (*ans != 'Y' && *ans != 'y') {
+ prints("Visit NOT done.\n");
+ pressreturn();
+ return FULLUPDATE;
+ }
+ /* this can take a while, so...*/
+ prints("Visit in progress...");
+ refresh();
+ code = bbs_visit_board(visitboard);
+ if (code == S_OK) prints("done!\n");
+ else bbperror(code, "visit failed");
+ pressreturn();
+ return FULLUPDATE;
+}
+
+SetBoardPerms(brec, postp)
+BOARD *brec;
+int postp;
+{
+ LONG newperms;
+ move(2,0);
+ clrtobot();
+ prints("Set the %s permissions for board '%s'\n",
+ (postp ? "post" : "read"), brec->name);
+ newperms = SetPermMenu(postp ? brec->postmask : brec->readmask);
+ clear();
+ if (newperms != (postp ? brec->postmask : brec->readmask)) {
+ if (postp) brec->postmask = newperms;
+ else brec->readmask = newperms;
+ return 0;
+ }
+ else return -1;
+}
+
+AddBoard()
+{
+ BOARD board;
+ int y, grok;
+ int rc;
+ char ans[4];
+ move(3,0);
+ clrtobot();
+ memset(&board, 0, sizeof(BOARD));
+ do {
+ if (getdata(3,0,"New board name: ",board.name,NAMELEN+1,DOECHO,1)==-1)
+ return FULLUPDATE;
+
+ grok = CheckBoardName(board.name);
+ switch (grok) {
+ case 1: return FULLUPDATE;
+ case 2: bbperror(S_BOARDEXISTS, NULL);
+ break;
+ case 3: bbperror(S_BADBOARDNAME, NULL);
+ }
+ } while (grok);
+ getdata(4,0,"Board description: ", board.description, TITLELEN+1, DOECHO, 0);
+ getdata(5,0,"Set read restrictions (Y/N)? [N]: ",ans,sizeof(ans),DOECHO,0);
+ if (*ans == 'Y' || *ans == 'y') {
+ SetBoardPerms(&board, 0);
+ y = 0;
+ }
+ else y = 6;
+ getdata(y,0,"Set post restrictions (Y/N)? [N]: ",ans,sizeof(ans),DOECHO,0);
+ if (*ans == 'Y' || *ans == 'y') {
+ SetBoardPerms(&board, 1);
+ y = 0;
+ }
+ else y = 7;
+ getdata(y,0,"Add board: are you sure (Y/N)? [N]: ",ans,sizeof(ans),
+ DOECHO, 0);
+ if (*ans != 'Y' && *ans != 'y') {
+ prints("Board not added.\n");
+ }
+ else {
+ if ((rc = bbs_add_board(&board)) == S_OK) {
+ prints("Board '%s' was added.\n", board.name);
+ }
+ else bbperror(rc, "Board add failed");
+ }
+ pressreturn();
+ return FULLUPDATE;
+}
+
+DeleteBoard()
+{
+ NAME namebuf;
+ char ans[4];
+ int rc;
+ move(2,0);
+ clrtobot();
+ bbs_boardnames(&boardlist, NULL);
+ namecomplete(NULL, boardlist, "Delete which board: ", namebuf, sizeof(NAME));
+ if (namebuf[0] == '\0') return FULLUPDATE;
+ else if (!is_in_namelist(boardlist, namebuf)) {
+ bbperror(S_NOSUCHBOARD, NULL);
+ pressreturn();
+ return FULLUPDATE;
+ }
+ prints("Deleting board '%s'.\n", namebuf);
+ getdata(5,0,"Are you sure (Y/N)? [N]: ",ans,sizeof(ans), DOECHO, 0);
+ if (ans[0] != 'Y' && ans[0] != 'y') {
+ prints("Board not deleted.\n");
+ pressreturn();
+ return FULLUPDATE;
+ }
+ if ((rc = bbs_delete_board(namebuf)) == S_OK)
+ prints("Board has been deleted.\n");
+ else
+ bbperror(rc, "Board delete failed");
+ pressreturn();
+ return FULLUPDATE;
+}
+
+ChangeBoard()
+{
+ NAME namebuf, oldname;
+ BOARD brec;
+ int rc;
+ int y, grok;
+ SHORT flags = 0;
+ char ans[4];
+ move(2,0);
+ clrtobot();
+ bbs_boardnames(&boardlist, NULL);
+ namecomplete(NULL, boardlist, "Change which board: ", namebuf, sizeof(NAME));
+ if (namebuf[0] == '\0') return FULLUPDATE;
+ else if (!is_in_namelist(boardlist, namebuf)) {
+ bbperror(S_NOSUCHBOARD, NULL);
+ pressreturn();
+ return FULLUPDATE;
+ }
+ if ((rc = bbs_get_board(namebuf, &brec)) != S_OK) {
+ bbperror(rc, NULL);
+ pressreturn();
+ return FULLUPDATE;
+ }
+ strncpy(oldname, brec.name, NAMELEN);
+ move(3,0);
+ clrtobot();
+ move(4,0);
+ prints("Board name: %s\n", brec.name);
+ prints("Board description: %s\n", brec.description);
+ prints("Use (M)anagers to view and set board managers.\n");
+ if (brec.readmask) prints("Read restrictions are in effect.\n");
+ if (brec.postmask) prints("Post restrictions are in effect.\n");
+ do {
+ getdata(9,0,"New board name (ENTER to leave unchanged): ",
+ namebuf, NAMELEN+1, DOECHO, 0);
+ grok = CheckBoardName(namebuf);
+ switch (grok) {
+ case 0: BITSET(flags, MOD_BNAME);
+ strncpy(brec.name, namebuf, sizeof(brec.name));
+ break;
+ case 1: grok = 0;
+ break;
+ case 2: bbperror(S_BOARDEXISTS, NULL);
+ break;
+ case 3: bbperror(S_BADBOARDNAME, NULL);
+ }
+ } while (grok);
+ getdata(10,0,"New description if any: ", brec.description, TITLELEN,
+ DOECHO, 0);
+ if (brec.description[0] != '\0') BITSET(flags, MOD_BOARDDESC);
+ getdata(11,0,"Alter read restrictions (Y/N)? [N]: ",
+ ans,sizeof(ans),DOECHO,0);
+ if (*ans == 'Y' || *ans == 'y') {
+ if (SetBoardPerms(&brec, 0) != -1)
+ BITSET(flags, MOD_READMASK);
+ y = 0;
+ }
+ else y = 12;
+ getdata(y,0,"Alter post restrictions (Y/N)? [N]: ",
+ ans,sizeof(ans),DOECHO,0);
+ if (*ans == 'Y' || *ans == 'y') {
+ if (SetBoardPerms(&brec, 1) != -1)
+ BITSET(flags, MOD_POSTMASK);
+ y = 0;
+ }
+ else y = 14;
+ getdata(y,0,"Change board: are you sure (Y/N)? [N]: ",ans,sizeof(ans),
+ DOECHO,0);
+ if (*ans != 'Y' && *ans != 'y') {
+ prints("Board not changed.\n");
+ }
+ else {
+ rc = bbs_modify_board(oldname, &brec, flags);
+ if (rc == S_OK) prints("Board '%s' was changed.\n", oldname);
+ else bbperror(rc, "Board change failed");
+ }
+ pressreturn();
+ return FULLUPDATE;
+}
diff --git a/c_chat.c b/c_chat.c
new file mode 100644
index 0000000..8df55e7
--- /dev/null
+++ b/c_chat.c
@@ -0,0 +1,568 @@
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "client.h"
+#include <ctype.h>
+#include <varargs.h>
+
+#define CHAT_PROMPT "-->"
+#define CHAT_HELP_FILE "etc/chathlp.txt"
+#define CHAT_XTRA_HELP_FILE "etc/chatxhlp.txt"
+
+extern BBSINFO serverinfo;
+extern char *Ctime __P((time_t *)); /* for query */
+
+int g_currline;
+int g_echatwin;
+int g_you_have_mail;
+
+print_chatid(chatid)
+char *chatid;
+{
+ char buf[CHATID_MAX+2];
+ int i;
+ memset(buf, 0, sizeof buf);
+ strncpy(buf, chatid, CHATID_MAX);
+ i = strlen(buf);
+ buf[i] = ':';
+ for (i++; i<=CHATID_MAX; i++) buf[i] = ' ';
+ buf[CHATID_MAX+1] = '\0';
+ move(g_echatwin+1, 0);
+ prints("%s", buf);
+ return 0;
+}
+
+printchatline(str)
+char *str;
+{
+ int linelen = t_columns-1, len = strlen(str);
+#if COLOR
+ int color = 0;
+#endif
+ if (!g_you_have_mail && bbs_check_mail()) {
+ int i;
+ move(g_echatwin, 0);
+ prints("---(You have mail.)");
+ for (i=19; i<t_columns; i++) prints("-");
+ g_you_have_mail++;
+ }
+#if COLOR
+ if (*str == '*')
+ color = (strncmp(str, "***", 3) ? COLOR_GREEN : COLOR_LIGHTBLUE);
+#endif
+ while (len) {
+ move(g_currline, 0);
+ clrtoeol();
+ if (linelen < t_columns-1) move(g_currline, CHATID_MAX+2);
+ if (len >= linelen) {
+ int eoln;
+ char c;
+ for (eoln = linelen-1; eoln >= t_columns/4; eoln--)
+ if (isspace(str[eoln])) break;
+ if (!isspace(str[eoln])) eoln = linelen-1;
+ c = str[eoln];
+ str[eoln] = '\0';
+#if COLOR
+ if (color) colorstart(color);
+#endif
+ prints("%s", str);
+#if COLOR
+ if (color) colorend();
+#endif
+ str[eoln] = c;
+ if (isspace(c)) eoln++;
+ len -= eoln;
+ str += eoln;
+ if (linelen == t_columns-1) linelen -= (CHATID_MAX+2);
+ }
+ else {
+#if COLOR
+ if (color) colorstart(color);
+#endif
+ prints("%s", str);
+#if COLOR
+ if (color) colorend();
+#endif
+ len = 0;
+ }
+ if (++g_currline == g_echatwin) g_currline = 0;
+ move(g_currline, 0);
+ standout();
+ prints(CHAT_PROMPT);
+ standend();
+ clrtoeol();
+ }
+}
+
+chat_help(helpfile)
+char *helpfile;
+{
+ FILE *fp;
+ char buf[84];
+ fp = fopen(helpfile, "r");
+ if (fp == NULL) {
+ printchatline("*** Cannot open help file\n");
+ }
+ else {
+ strcpy(buf, "*** ");
+ while (fgets(buf+4, sizeof(buf)-4, fp))
+ if (buf[4] != '#') printchatline(buf);
+ fclose(fp);
+ }
+ return 0;
+}
+
+chat_resetscreen(chatid)
+char *chatid;
+{
+ int i;
+ char buf[80];
+ clear();
+ g_currline = 0;
+ g_echatwin = t_lines - 2;
+ move(g_echatwin, 0);
+ for (i=0; i<t_columns; i++) prints("-");
+ sprintf(buf, "%s Chat System -- type /h for Help", serverinfo.boardname);
+ printchatline(buf);
+ print_chatid(chatid);
+ return 0;
+}
+
+struct _chatlist {
+ int start;
+ int stop;
+ int col;
+ int verbose;
+ char buf[80];
+};
+
+chat_list_users_func(indx, urec, cl)
+int indx;
+USEREC *urec;
+struct _chatlist *cl;
+{
+ indx++; /* Start counting at one, not zero. */
+ if (indx < cl->start) return S_OK;
+ else if (cl->stop && (indx > cl->stop)) return ENUM_QUIT;
+ if (cl->verbose) {
+ char fromhost[24];
+ memset(fromhost, '\0', sizeof fromhost);
+ strncpy(fromhost, urec->fromhost, sizeof(fromhost)-1);
+ sprintf(cl->buf, "*** %-14s %-25s %c%-8s %-23s",
+ urec->userid, urec->username,
+ BITISSET(urec->flags, FLG_CLOAK) ? '#' : ' ',
+ ModeToString(urec->mode), fromhost);
+ printchatline(cl->buf);
+ }
+ else {
+ if (cl->col == 0) {
+ strcpy(cl->buf, "*** ");
+ cl->col = 4;
+ }
+ sprintf(cl->buf+cl->col, "[%c]%s%-13s ", ModeToChar(urec->mode),
+ BITISSET(urec->flags, FLG_CLOAK) ? " #" : " ", urec->userid);
+ if ((cl->col += 18) > 70) {
+ printchatline(cl->buf);
+ cl->col = 0;
+ }
+ }
+ return S_OK;
+}
+
+chat_list_users(cbuf, verbose)
+char *cbuf;
+int verbose;
+{
+ struct _chatlist cl;
+ extern char global_modechar_key[];
+ if (!HasPerm(C_USERS)) {
+ printchatline("*** You do not have permission to list users");
+ return 0;
+ }
+ while (*cbuf && !isspace(*cbuf)) cbuf++;
+ while (*cbuf && isspace(*cbuf)) cbuf++;
+ cl.start = atoi(cbuf);
+ while (*cbuf && isdigit(*cbuf)) cbuf++;
+ while (*cbuf && !isdigit(*cbuf)) cbuf++;
+ cl.stop = atoi(cbuf);
+ cl.col = 0;
+ cl.verbose = verbose;
+ printchatline("***");
+ if (verbose) {
+ sprintf(cl.buf, "*** %-14s %-25s %-8s %s", "Userid", "Username",
+ "Mode", "From");
+ printchatline(cl.buf);
+ sprintf(cl.buf, "*** %-14s %-25s %-8s %s", "------", "--------",
+ "----", "----");
+ printchatline(cl.buf);
+ }
+ else {
+ if (global_modechar_key[0] == '\0') form_modechar_key();
+ sprintf(cl.buf, "*** %s", global_modechar_key);
+ printchatline(cl.buf);
+ printchatline("*** ------------------------------------------------------------------------");
+ }
+ bbs_enum_users(t_lines-5, 0, NULL, chat_list_users_func, &cl);
+ if (!verbose && cl.col > 0) printchatline(cl.buf);
+ return 0;
+}
+
+extern int _query_if_logged_in __P((int, USEREC *, int *));
+
+chat_query_user(cbuf)
+char *cbuf;
+{
+ ACCOUNT acct;
+ char buf[80];
+ int in_now = 0;
+ while (*cbuf && !isspace(*cbuf)) cbuf++;
+ while (*cbuf && isspace(*cbuf)) cbuf++;
+ strip_trailing_space(cbuf);
+ if (!HasPerm(C_QUERY)) {
+ printchatline("*** You do not have permission to query");
+ return 0;
+ }
+ if (*cbuf == '\0') {
+ printchatline("*** You must specify a userid to query");
+ return 0;
+ }
+ if (bbs_query(cbuf, &acct) != S_OK) {
+ sprintf(buf, "*** Userid '%s' not found", cbuf);
+ printchatline(buf);
+ return 0;
+ }
+ bbs_enum_users(20, 0, acct.userid, _query_if_logged_in, &in_now);
+ printchatline("***");
+ sprintf(buf, "*** %s (%s):", acct.userid, acct.username);
+ printchatline(buf);
+ if (acct.lastlogin == 0)
+ strcpy(buf, "*** Never logged in.");
+ else sprintf(buf, "*** %s from %s %s %s",
+ in_now ? "Logged in" : "Last login",
+ acct.fromhost, in_now ? "since" : "at",
+ Ctime((time_t *)&acct.lastlogin));
+ printchatline(buf);
+ if (acct.realname[0] != '\0') {
+ sprintf(buf, "*** Real name: %s", acct.realname);
+ printchatline(buf);
+ }
+ return 0;
+}
+
+chat_show_page_request()
+{
+ USEREC urec;
+ char buf[80];
+ if (bbs_get_talk_request(&urec, NULL, NULL) != S_OK) {
+ /* Whoever was paging stopped. */
+ return 0;
+ }
+ sprintf(buf, "*** Being paged by %s (%s)", urec.userid, urec.username);
+ printchatline(buf);
+ return 0;
+}
+
+chat_process_incoming(fd, chatid)
+int fd;
+char *chatid;
+{
+ static char buf[CHATLINE_MAX*2+1];
+ static int bufstart = 0;
+ int c, len;
+ char *bptr;
+
+ len = sizeof(buf) - bufstart - 1;
+ if ((c = recv(fd, buf+bufstart, len, 0)) <= 0) return -1;
+ c += bufstart;
+
+ bptr = buf;
+ while (c > 0) {
+ len = strlen(bptr)+1;
+ if (len > c && len < (sizeof buf / 2)) break;
+ if (!strncmp(bptr, CHAT_CTRL_CHATID, 3)) {
+ memset(chatid, 0, CHATID_MAX+1);
+ strncpy(chatid, bptr+4, CHATID_MAX);
+ print_chatid(chatid);
+ }
+ else printchatline(bptr);
+ c -= len;
+ bptr += len;
+ }
+
+ if (c > 0) {
+ char temp[CHATLINE_MAX*2+1];
+ strcpy(temp, bptr);
+ strcpy(buf, temp);
+ bufstart = len-1;
+ }
+ else bufstart = 0;
+ return 0;
+}
+
+chat_exit(buf)
+char *buf;
+{
+ /* Send the line, then return 1 so we exit. */
+ bbs_chat_send(buf);
+ return 1;
+}
+
+chat_cmd_match(buf, str)
+char *buf;
+char *str;
+{
+ if (*buf != '/') return 0;
+ for (buf++; *str && *buf && !isspace(*buf); buf++, str++) {
+ if (tolower(*buf) != *str) return 0;
+ }
+ return 1;
+}
+
+chat_process_local(cbuf, chatid)
+char *cbuf;
+char *chatid;
+{
+ /*
+ See if the typed line should be handled locally. If not, return -1.
+ If so, return 1 if we should exit, 0 if we keep going.
+ */
+ if (!strncmp(cbuf, "Goodbye!", 8)) {
+ chat_exit("/e\n");
+ return 1;
+ }
+
+ if (chat_cmd_match(cbuf, "clear"))
+ return (chat_resetscreen(chatid));
+ else if (chat_cmd_match(cbuf, "exit"))
+ return (chat_exit(cbuf));
+ else if (chat_cmd_match(cbuf, "help"))
+ return (chat_help(CHAT_HELP_FILE));
+ else if (chat_cmd_match(cbuf, "long"))
+ return (chat_list_users(cbuf, 1));
+ else if (chat_cmd_match(cbuf, "query"))
+ return (chat_query_user(cbuf));
+ else if (chat_cmd_match(cbuf, "users"))
+ return (chat_list_users(cbuf, 0));
+ else if (chat_cmd_match(cbuf, "xhelp"))
+ return (chat_help(CHAT_XTRA_HELP_FILE));
+
+ return -1;
+}
+
+Chat()
+{
+ CHATID chatid;
+ char cbuf[CHATLINE_TEXT_MAX+1];
+ int cfd;
+ int rc;
+ int done_chatting = 0;
+ int margin, curspos, maxpos, endpos;
+ int ch, i;
+ int metakey = 0;
+ int shift, shiftdelta;
+
+ move(2, 0);
+ clrtobot();
+
+ do {
+ if (getdata(2,0,"Enter chatid: ",chatid,sizeof chatid,DOECHO,1)==-1)
+ return FULLUPDATE;
+
+ if (*chatid == '\0') {
+ strncpy(chatid, myinfo.userid, CHATID_MAX);
+ chatid[CHATID_MAX] = '\0';
+ }
+ rc = bbs_chat(chatid, (LONG *)&cfd);
+ switch (rc) {
+ case S_OK: break;
+ case S_BADCHATID:
+ case S_CHATIDINUSE:
+ bbperror(rc, NULL);
+ break;
+ default:
+ bbperror(rc, "Error entering chat");
+ return PARTUPDATE;
+ }
+ } while (rc != S_OK);
+
+ add_io(cfd, 0);
+ chat_resetscreen(chatid);
+
+ memset(cbuf, '\0', sizeof cbuf);
+ margin = CHATID_MAX+2;
+ maxpos = sizeof(cbuf) - margin - 1;
+ curspos = 0;
+ endpos = 0;
+ shift = 0;
+ shiftdelta = (t_columns - margin) / 2;
+
+ while (!done_chatting) {
+ move(g_echatwin+1, margin+(curspos-shift));
+
+ ch = igetch();
+ if (NewPagePending()) {
+ chat_show_page_request();
+ }
+ if (ch == -1) done_chatting = 1;
+ else if (ch == I_OTHERDATA) {
+ /* Incoming! */
+ if (chat_process_incoming(cfd, chatid) == -1)
+ done_chatting = 1;
+ }
+ else if (metakey) {
+ switch (ch) {
+ case 'b': case 'B':
+ if (curspos == 0) bell();
+ else {
+ while (curspos && isspace(cbuf[curspos-1])) curspos--;
+ while (curspos && !isspace(cbuf[curspos-1])) curspos--;
+ }
+ break;
+ case 'f': case 'F':
+ if (curspos == endpos) bell();
+ else {
+ while (curspos < endpos && isspace(cbuf[curspos])) curspos++;
+ while (curspos < endpos && !isspace(cbuf[curspos])) curspos++;
+ }
+ break;
+ default:
+ bell();
+ }
+ metakey = 0;
+ }
+ else switch (ch) {
+ case '\r':
+ case '\n':
+ if (endpos > 0) {
+ cbuf[endpos] = '\n';
+ done_chatting = chat_process_local(cbuf, chatid);
+ if (done_chatting == -1) {
+ done_chatting = (bbs_chat_send(cbuf) != S_OK);
+ }
+ }
+ memset(cbuf, '\0', sizeof cbuf);
+ endpos = curspos = 0;
+ move(g_echatwin+1, margin);
+ clrtoeol();
+ break;
+ case CTRL('A'):
+ if (curspos == 0) bell();
+ else curspos = 0;
+ break;
+ case CTRL('B'):
+ if (curspos == 0) bell();
+ else curspos--;
+ break;
+ case CTRL('C'):
+ bbs_chat_send("/e\n");
+ done_chatting = 1;
+ break;
+ case CTRL('D'):
+ if (curspos == endpos) bell();
+ else {
+ for (i=curspos; i<endpos; i++) cbuf[i] = cbuf[i+1];
+ endpos--;
+ move(g_echatwin+1, margin+(curspos-shift));
+ prints("%s", cbuf+curspos);
+ clrtoeol();
+ }
+ break;
+ case CTRL('E'):
+ if (curspos == endpos) bell();
+ else curspos = endpos;
+ break;
+ case CTRL('F'):
+ if (curspos == endpos) bell();
+ else curspos++;
+ break;
+ case CTRL('H'):
+ case 127:
+ if (curspos == 0) bell();
+ else {
+ for (i=curspos; i<=endpos; i++) cbuf[i-1] = cbuf[i];
+ endpos--;
+ curspos--;
+ move(g_echatwin+1, margin+(curspos-shift));
+ prints("%s", cbuf+curspos);
+ clrtoeol();
+ }
+ break;
+ case CTRL('U'):
+ if (endpos == 0) bell();
+ else {
+ memset(cbuf, '\0', sizeof cbuf);
+ curspos = endpos = 0;
+ move(g_echatwin+1, margin);
+ clrtoeol();
+ }
+ break;
+ case CTRL('W'):
+ if (curspos == 0) bell();
+ else {
+ while (curspos && isspace(cbuf[curspos-1])) {
+ for (i=curspos; i<=endpos; i++) cbuf[i-1] = cbuf[i];
+ curspos--, endpos--;
+ }
+ while (curspos && !isspace(cbuf[curspos-1])) {
+ for (i=curspos; i<=endpos; i++) cbuf[i-1] = cbuf[i];
+ curspos--, endpos--;
+ }
+ move(g_echatwin+1, margin+(curspos-shift));
+ prints("%s", cbuf+curspos);
+ clrtoeol();
+ }
+ break;
+ case 27: /* ESC */
+ metakey = 1;
+ break;
+ default:
+ if (isprint(ch) && endpos < maxpos) {
+ for (i=endpos; i>curspos; i--) cbuf[i] = cbuf[i-1];
+ cbuf[curspos] = ch;
+ move(g_echatwin+1, margin+(curspos-shift));
+ prints("%s", cbuf+curspos);
+ curspos++;
+ endpos++;
+ }
+ else bell();
+ }
+ if (curspos-shift >= (t_columns-margin)) {
+ while (curspos-shift >= (t_columns-margin)) shift += shiftdelta;
+ move(g_echatwin+1, margin);
+ prints("%s", cbuf+shift);
+ clrtoeol();
+ }
+ else if (curspos < shift) {
+ while (curspos < shift) shift -= shiftdelta;
+ move(g_echatwin+1, margin);
+ prints("%s", cbuf+shift);
+ clrtoeol();
+ }
+ }
+
+ bbs_exit_chat();
+ g_you_have_mail = 0;
+ add_io(0, 0);
+ return FULLUPDATE;
+}
+
+
+
+
+
diff --git a/c_files.c b/c_files.c
new file mode 100644
index 0000000..d75e3ca
--- /dev/null
+++ b/c_files.c
@@ -0,0 +1,402 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "client.h"
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <time.h>
+#include <ctype.h>
+#if LACKS_MALLOC_H
+#else
+# if !defined(NeXT)
+# include <malloc.h>
+# endif
+#endif
+
+/* The currently selected file board */
+NAME currfileboard;
+NAMELIST fileboardlist;
+
+/* The currently selected protocol */
+NAME currprotocol;
+#ifndef REMOTE_CLIENT
+NAMELIST protolist;
+#endif
+
+/*ARGSUSED*/
+AllFileBoardsFunc(indx, board, info)
+int indx;
+BOARD *board;
+struct enum_info *info;
+{
+ if (info->topline == info->currline) {
+ move(info->topline-1, 0);
+ prints(" %-16s %-58s\n", "Name", "Description");
+ }
+
+ prints(" %-15s %s\n", board->name, board->description);
+
+ info->currline++;
+ info->count++;
+
+ if (info->currline > info->bottomline) {
+ int ch;
+ standout();
+ prints("--MORE--");
+ standend();
+ clrtoeol();
+ while((ch = igetch()) != EOF) {
+ if(ch == '\n' || ch == '\r' || ch == ' ')
+ break;
+ if(toupper(ch) == 'Q') {
+ move(info->currline, 0);
+ clrtoeol();
+ return ENUM_QUIT;
+ }
+ else bell();
+ }
+ info->currline = info->topline;
+ }
+ return S_OK;
+}
+
+FileBoards()
+{
+ struct enum_info info;
+ info.count = 0;
+ info.topline = info.currline = 4;
+ info.bottomline = t_lines-2;
+ move(3,0);
+ clrtobot();
+ bbs_enum_fileboards(t_lines-5, 0, AllFileBoardsFunc, &info);
+ clrtobot();
+ move(t_lines-1, 0);
+ prints("%d %s displayed\n", info.count, info.count==1?"board":"boards");
+ return PARTUPDATE;
+}
+
+FileSelect()
+{
+ NAME newboard;
+ move(2,0);
+ clrtobot();
+ bbs_fileboardnames(&fileboardlist, NULL);
+ if (fileboardlist == NULL) {
+ prints("No file boards to select.\n");
+ pressreturn();
+ return FULLUPDATE;
+ }
+ namecomplete(NULL, fileboardlist, "Enter board name: ",
+ newboard, sizeof(NAME));
+ if (newboard[0] == '\0') return FULLUPDATE;
+ else if (!is_in_namelist(fileboardlist, newboard)) {
+ move(4,0);
+ bbperror(S_NOSUCHBOARD, NULL);
+ pressreturn();
+ return FULLUPDATE;
+ }
+ strcpy(currfileboard, newboard);
+ return (FULLUPDATE | NEWDIRECT);
+}
+
+OpenFileBoard(openflags, newonly, resp)
+int *openflags;
+int newonly;
+int *resp;
+{
+ int code;
+ OPENINFO openinfo;
+ code = bbs_open_fileboard(currfileboard, &openinfo);
+ if (resp) *resp = code;
+ if (code != S_OK) return -1;
+ if (openflags) *openflags = openinfo.flags;
+ return (newonly ? openinfo.newmsgs : openinfo.totalmsgs);
+}
+
+CloseFileBoard()
+{
+ int code;
+ code = bbs_close_board();
+ return (code == S_OK ? 0 : -1);
+}
+
+#ifndef REMOTE_CLIENT
+
+SelectProtocol()
+{
+ NAME protoname;
+ move(2,0);
+ clrtobot();
+ bbs_protonames(&protolist, NULL);
+ if (protolist == NULL) {
+ prints("No protocols to select!\n");
+ pressreturn();
+ return FULLUPDATE;
+ }
+ namecomplete(NULL, protolist, "Select a protocol: ",
+ protoname, sizeof(NAME));
+ if (protoname[0] == '\0') return FULLUPDATE;
+ else if (!is_in_namelist(protolist, protoname)) {
+ move(4,0);
+ bbperror(S_NOSUCHPROTO, NULL);
+ pressreturn();
+ return FULLUPDATE;
+ }
+ strcpy(currprotocol, protoname);
+ bbs_set_protocol(protoname);
+ return FULLUPDATE;
+}
+
+FileView(hptr, currmsg, numrecs, openflags)
+HEADER *hptr;
+int currmsg, numrecs, openflags;
+{
+ int rc;
+ PATH fname;
+
+ if (BITISSET(hptr->flags, FILE_DIRECTORY)) {
+ bell();
+ return DONOTHING;
+ }
+
+ clear();
+ if (BITISSET(hptr->flags, FILE_BINARY)) {
+ char ans[4];
+ getdata(0, 0, "This may be a binary file. Continue (Y/N)? [N]: ",
+ ans, sizeof ans, DOECHO, 0);
+ if (*ans != 'Y' && *ans != 'y')
+ return FULLUPDATE;
+ }
+
+ rc = bbs_download(hptr->title, NULL, fname);
+ if (rc != S_OK) {
+ bbperror(rc, "Error reading file");
+ pressreturn();
+ return FULLUPDATE;
+ }
+
+ More(fname, 1);
+ return FULLUPDATE;
+}
+
+GetSavedProtocol()
+{
+ ACCOUNT acct;
+ if (bbs_owninfo(&acct) == S_OK) {
+ strncpy(currprotocol, acct.protocol, sizeof currprotocol-1);
+ }
+}
+
+#endif
+
+FileUpload()
+{
+ PATH fname;
+ char *base, *slash;
+ char ans[4];
+#ifdef REMOTE_CLIENT
+ strcpy(currprotocol, "builtin");
+#else
+ if (currprotocol[0] == '\0') {
+ GetSavedProtocol();
+ if (currprotocol[0] == '\0') {
+ clear();
+ prints("You must select an upload protocol first.\n");
+ pressreturn();
+ return FULLUPDATE;
+ }
+ }
+#endif
+ clear();
+ if (getdata(0,0,"File to upload: ",fname,sizeof fname,DOECHO,1)==-1) {
+ return FULLUPDATE;
+ }
+
+ if ((slash = strrchr(fname, '/')) != NULL) base = slash+1;
+ else base = fname;
+
+ prints("Uploading '%s'.\n", base);
+ prints("Using %s protocol.\n", currprotocol);
+ getdata(2, 0, "Are you sure (Y/N)? [Y]: ", ans, sizeof(ans), DOECHO, 0);
+ if (*ans == 'N' || *ans == 'n') {
+ prints("File NOT uploaded.\n");
+ }
+ else {
+ int rc;
+#ifdef REMOTE_CLIENT
+ prints("Uploading...please stand by.\n");
+ rc = bbs_upload(fname, base, currprotocol);
+#else
+ prints("Setup modem program and press any key to upload.\n");
+ refresh();
+ fgetc(stdin);
+ reset_tty();
+ rc = bbs_upload(NULL, base, currprotocol);
+ printf("\nDone. Press any key.\n");
+ fgetc(stdin);
+ restore_tty();
+ clear();
+#endif
+ prints("\n");
+ if (rc == S_OK) {
+ prints("File uploaded!\n");
+ }
+ else bbperror(rc, "Upload error");
+ }
+ pressreturn();
+ return FULLUPDATE;
+}
+
+FileReceive(hptr, currmsg, numrecs, openflags)
+HEADER *hptr;
+LONG currmsg, numrecs, openflags;
+{
+ char ans[4];
+#ifdef REMOTE_CLIENT
+ PATH fname;
+#endif
+
+ if (BITISSET(hptr->flags, FILE_DIRECTORY)) {
+ bell();
+ return DONOTHING;
+ }
+
+#ifdef REMOTE_CLIENT
+ strcpy(currprotocol, "builtin");
+#else
+ if (currprotocol[0] == '\0') {
+ GetSavedProtocol();
+ if (currprotocol[0] == '\0') {
+ clear();
+ prints("You must select a download protocol first.\n");
+ pressreturn();
+ return FULLUPDATE;
+ }
+ }
+#endif
+ clear();
+ prints("Downloading '%s'.\n", hptr->title);
+ prints("Using %s protocol.\n", currprotocol);
+ getdata(2, 0, "Are you sure (Y/N)? [Y]: ", ans, sizeof(ans), DOECHO, 0);
+ if (*ans == 'N' || *ans == 'n') {
+ prints("File NOT downloaded.\n");
+ }
+ else {
+ int rc;
+#ifdef REMOTE_CLIENT
+ if (getdata(3, 0, "Download to (RETURN = current directory): ",
+ fname, sizeof fname, DOECHO, 1) == -1) {
+ return FULLUPDATE;
+ }
+ if (fname[0] == '\0') strncpy(fname, hptr->title, sizeof fname);
+ prints("Receiving %d bytes. Please stand by.\n", hptr->size);
+ rc = bbs_download(hptr->title, currprotocol, fname);
+#else
+ prints("Setup modem program and press any key to download.\n");
+ refresh();
+ fgetc(stdin);
+ reset_tty();
+ rc = bbs_download(hptr->title, currprotocol, NULL);
+ printf("\nDone. Press any key.\n");
+ fgetc(stdin);
+ restore_tty();
+ clear();
+#endif
+ if (rc == S_OK) {
+ prints("\nFile downloaded!\n");
+ }
+ else bbperror(rc, "Download error");
+ }
+ pressreturn();
+ return FULLUPDATE;
+}
+
+/*ARGSUSED*/
+FileForward(hptr, currmsg, numrecs, openflags)
+HEADER *hptr;
+int currmsg, numrecs, openflags;
+{
+ int code, rc = FULLUPDATE;
+ ACCOUNT acct;
+ char ans[4];
+
+ if (BITISSET(hptr->flags, FILE_DIRECTORY)) {
+ bell();
+ return DONOTHING;
+ }
+ clear();
+ if ((code = bbs_owninfo(&acct)) != S_OK) {
+ bbperror(code, "Can't fetch address");
+ pressreturn();
+ return rc;
+ }
+ prints("Mailing '%s' to:\n", hptr->title);
+ prints("%s\n", acct.email);
+ getdata(2,0,"Are you sure (Y/N)? [Y]: ",ans,sizeof(ans),DOECHO,0);
+ move(4,0);
+ if (*ans == 'N' || *ans == 'n') {
+ prints("Message not forwarded.\n");
+ }
+ else {
+ if ((code = bbs_forward_file(hptr->title)) == S_OK) {
+ prints("Mail sent!\n");
+ }
+ else bbperror(code, "Forward failed");
+ }
+ pressreturn();
+ return rc;
+}
+
+/*ARGSUSED*/
+FileChdir(hptr, currmsg, numrecs, openflags)
+HEADER *hptr;
+int currmsg, numrecs, openflags;
+{
+ OPENINFO oinfo;
+ if (!BITISSET(hptr->flags, FILE_DIRECTORY)) {
+ bell();
+ return DONOTHING;
+ }
+ /* Ostensibly, this should never fail */
+ bbs_change_fileboard_dir(hptr->title, &oinfo);
+ /* If oinfo->name were modified, we'd have to update currfileboard */
+ return (FULLUPDATE | NEWDIRECT | NOCLOSE);
+}
+
+/*ARGSUSED*/
+FileReadMenuSelect(hptr, currmsg, numrecs, openflags)
+HEADER *hptr;
+int currmsg, numrecs, openflags;
+{
+ return (FileSelect());
+}
+
+#ifndef REMOTE_CLIENT
+
+/*ARGSUSED*/
+FileReadMenuProto(hptr, currmsg, numrecs, openflags)
+HEADER *hptr;
+int currmsg, numrecs, openflags;
+{
+ return (SelectProtocol());
+}
+
+#endif
diff --git a/c_lists.c b/c_lists.c
new file mode 100644
index 0000000..a229fe6
--- /dev/null
+++ b/c_lists.c
@@ -0,0 +1,188 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "client.h"
+
+extern NAMELIST acctlist;
+extern NAMELIST boardlist;
+
+struct disp_list {
+ int x;
+ int y;
+};
+
+/*ARGSUSED*/
+display_list_element(indx, name, arg)
+int indx;
+char *name;
+void *arg;
+{
+ struct disp_list *info = arg;
+ move(info->y, info->x);
+ prints(name);
+ if (++(info->y) == t_lines) {
+ info->y = 4;
+ info->x+=(NAMELEN + 2);
+ }
+}
+
+display_list(header, list)
+char *header;
+NAMELIST list;
+{
+ struct disp_list info;
+ info.x = 0;
+ info.y = 4;
+ move(3,0);
+ clrtobot();
+ prints("%s\n", header);
+ apply_namelist(list, display_list_element, &info);
+}
+
+SetOverrides()
+{
+ int rc, done = 0, change = 0;
+ char ans[7];
+ NAME userid;
+ NAMELIST overlist = NULL;
+ clear();
+ bbs_acctnames(&acctlist, NULL);
+ if ((rc = bbs_get_overrides(&overlist)) != S_OK) {
+ bbperror(rc, "Error fetching override list");
+ pressreturn();
+ return FULLUPDATE;
+ }
+ prints("Edit Talk Override List\n");
+ while (!done) {
+ display_list("*** Users on Override List ***", overlist);
+ getdata(1,0,"Add name, Remove name, or Quit? [Q]: ",
+ ans, sizeof(ans), DOECHO, 0);
+ switch (ans[0]) {
+ case 'A': case 'a':
+ move(2,0);
+ namecomplete(NULL, acctlist, "Add which user: ", userid, sizeof(NAME));
+ if (is_in_namelist(acctlist, userid) &&
+ !is_in_namelist(overlist, userid)) {
+ add_namelist(&overlist, userid, NULL);
+ change++;
+ }
+ break;
+ case 'R': case 'r':
+ if (overlist == NULL) {
+ prints("No one to remove!\n");
+ clrtobot();
+ pressreturn();
+ }
+ else {
+ namecomplete(NULL, overlist, "Remove which user: ",
+ userid, sizeof(NAME));
+ if (remove_namelist(&overlist, userid) == S_OK) change++;
+ }
+ break;
+ default:
+ done = 1;
+ }
+ move(2,0);
+ clrtoeol();
+ }
+ if (change) {
+ if ((rc = bbs_set_overrides(overlist)) == S_OK)
+ prints("Override list was updated.\n");
+ else bbperror(rc, "Error setting override list");
+ }
+ else prints("No changes to override list.\n");
+ free_namelist(&overlist);
+ pressreturn();
+ return FULLUPDATE;
+}
+
+SetBoardMgrs()
+{
+ int rc, done = 0, change = 0;
+ char ans[7];
+ NAME namebuf;
+ BOARD board;
+ NAMELIST mgrlist = NULL;
+ clear();
+ bbs_boardnames(&boardlist, NULL);
+ bbs_acctnames(&acctlist, NULL);
+
+ namecomplete(NULL, boardlist, "Set manager for which board: ",
+ namebuf, sizeof(NAME));
+ if (namebuf[0] == '\0') return FULLUPDATE;
+ else if (!is_in_namelist(boardlist, namebuf)) {
+ bbperror(S_NOSUCHBOARD, NULL);
+ pressreturn();
+ return FULLUPDATE;
+ }
+ if ((rc = bbs_get_board(namebuf, &board)) != S_OK) {
+ bbperror(rc, NULL);
+ pressreturn();
+ return FULLUPDATE;
+ }
+ if ((rc = bbs_get_boardmgrs(board.name, &mgrlist)) != S_OK) {
+ bbperror(rc, "Error retrieving manager list");
+ pressreturn();
+ return FULLUPDATE;
+ }
+
+ prints("Edit Board Manager List\n");
+ while (!done) {
+ display_list("*** Board Managers ***", mgrlist);
+ getdata(1,0,"Add manager, Remove manager, or Quit? [Q]: ",
+ ans, sizeof(ans), DOECHO, 0);
+ switch (ans[0]) {
+ case 'A': case 'a':
+ move(2,0);
+ namecomplete(NULL, acctlist, "Add which user: ", namebuf, sizeof(NAME));
+ if (is_in_namelist(acctlist, namebuf) &&
+ !is_in_namelist(mgrlist, namebuf)) {
+ add_namelist(&mgrlist, namebuf, NULL);
+ change++;
+ }
+ break;
+ case 'R': case 'r':
+ if (mgrlist == NULL) {
+ prints("No one to remove!\n");
+ clrtobot();
+ pressreturn();
+ }
+ else {
+ namecomplete(NULL, mgrlist, "Remove which user: ",
+ namebuf, sizeof(NAME));
+ if (remove_namelist(&mgrlist, namebuf) == S_OK) change++;
+ }
+ break;
+ default:
+ done = 1;
+ }
+ move(2,0);
+ clrtoeol();
+ }
+ if (change) {
+ if ((rc = bbs_set_boardmgrs(board.name, mgrlist)) == S_OK)
+ prints("Manager list for '%s' was updated.\n", board.name);
+ else bbperror(rc, "Error setting manager list");
+ }
+ else prints("No changes to manager list.\n");
+ free_namelist(&mgrlist);
+ pressreturn();
+ return FULLUPDATE;
+}
diff --git a/c_mail.c b/c_mail.c
new file mode 100644
index 0000000..95e205e
--- /dev/null
+++ b/c_mail.c
@@ -0,0 +1,447 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "client.h"
+#include <stdlib.h>
+#include <sys/stat.h>
+
+OpenMailbox(openflags, newonly, resp)
+int *openflags;
+int newonly;
+int *resp;
+{
+ int code;
+ OPENINFO openinfo;
+ code = bbs_open_mailbox(&openinfo);
+ if (resp) *resp = code;
+ if (code != S_OK) return -1;
+ if (openflags) *openflags = (int)openinfo.flags;
+ return (newonly ? (int)openinfo.newmsgs : (int)openinfo.totalmsgs);
+}
+
+CloseMailbox()
+{
+ int code;
+ code = bbs_close_board();
+ return (code == S_OK ? 0 : -1);
+}
+
+/*
+ If mail to ANY recipients fails, return error with retcode holding the mask.
+ Abort mail, return -2.
+ Zero length file, return -3.
+ Otherwise return 0.
+*/
+
+DoMailSend(recips, subject, textfile, doedit, retcode)
+NAMELIST recips; /* list of intended recipients */
+char *subject; /* subject string */
+char *textfile; /* file to send: if NULL invoke editor */
+int doedit; /* edit even if textfile != NULL */
+LONG *retcode; /* bit mask of failed destinations */
+{
+ int rc = 0;
+ struct stat stbuf;
+
+ if (textfile == NULL || doedit) {
+ if (textfile == NULL) textfile = c_tempfile;
+ if (Edit(textfile)) {
+ rc = -2;
+ }
+ }
+ if (rc == 0) {
+ if (stat(textfile, &stbuf) != 0 || stbuf.st_size == 0) {
+ rc = -3;
+ }
+ else rc = bbs_mail(NULL, NULL, recips, subject, textfile, retcode);
+ }
+ if (textfile == c_tempfile) unlink(c_tempfile);
+ return rc;
+}
+
+show_names(indx, userid, mask)
+int indx;
+char *userid;
+LONG *mask;
+{
+ int x, y;
+ if ((*mask) & (LONG)(1<<indx)) {
+ getyx(&y, &x);
+ if (strlen(userid)+3 > (t_columns-x)) prints("\n");
+ else if (x > 0) prints(", ");
+ prints("%s", userid);
+ }
+ return S_OK;
+}
+
+GenericMailSend(group)
+int group;
+{
+ int rc;
+ ADDR nbuf;
+ NAMELIST recips = NULL;
+ TITLE subject;
+ int max, count = 0;
+ char *prompt;
+ LONG failmask = ~0;
+ ACCOUNT acct;
+
+ bbs_set_mode(M_MAIL);
+ if (group) {
+ max = BBS_MAX_MAILRECIPS;
+ prompt = "Enter recipient (RETURN when done): ";
+ }
+ else {
+ max = 1;
+ prompt = "Enter recipient: ";
+ }
+
+ move(2,0);
+ clrtobot();
+ for (count = 0; count < max; count++) {
+ move(2,0);
+ namecomplete(bbs_acctnames, NULL, prompt, nbuf, sizeof nbuf);
+ if (nbuf[0] == '\0') break;
+ if (strchr(nbuf, ':') != NULL) {
+ if (!HasPerm(C_EXTERNMAIL)) {
+ move(3,0);
+ bbperror(S_NOSUCHUSER, NULL);
+ continue;
+ }
+ }
+ else {
+ if ((rc = bbs_query(nbuf, &acct)) != S_OK) {
+ move(3,0);
+ bbperror(rc, NULL);
+ continue;
+ }
+ strcpy(nbuf, acct.userid);
+ }
+ if (!is_in_namelist(recips, nbuf))
+ add_namelist(&recips, nbuf, NULL);
+ if (group) {
+ move(3,0);
+ prints("\nSending to:\n");
+ apply_namelist(recips, show_names, &failmask);
+ }
+ }
+
+ if (recips != NULL) {
+ getdata(3,0,"Subject: ", subject, sizeof subject, DOECHO, 0);
+ rc = DoMailSend(recips, subject, (char *)NULL, 0, &failmask);
+ clear();
+ move(0,0);
+ switch (rc) {
+ case -3:
+ prints("Zero length file NOT sent.\n");
+ break;
+ case -2:
+ prints("Mail NOT sent.\n");
+ break;
+ case 0:
+ prints("Mail sent!\n");
+ break;
+ default:
+ if (group) {
+ bbperror(rc, "Mail failed to the following");
+ apply_namelist(recips, show_names, &failmask);
+ }
+ else bbperror(rc, "Mail failed");
+ break;
+ }
+ }
+ pressreturn();
+ bbs_set_mode(M_UNDEFINED);
+ return FULLUPDATE;
+}
+
+GroupSend()
+{
+ return (GenericMailSend(1));
+}
+
+MailSend()
+{
+ return (GenericMailSend(0));
+}
+
+/*ARGSUSED*/
+MailDelete(hptr, currmsg, numrecs, openflags)
+HEADER *hptr;
+int currmsg, numrecs, openflags;
+{
+ int code, rc = FULLUPDATE;
+ char ans[4];
+ clear();
+ prints("Delete message from %s\n", hptr->owner);
+ prints("entitled '%s'\n", hptr->title);
+ getdata(2,0,"Are you sure (Y/N)? [N]: ",ans,sizeof(ans),DOECHO, 0);
+ move(4,0);
+ if (*ans != 'Y' && *ans != 'y') {
+ prints("Message not deleted.\n");
+ }
+ else {
+ if ((code = bbs_delete_message(hptr->fileid)) == S_OK) {
+ prints("Message deleted.\n");
+ rc |= FETCHNEW;
+ }
+ else {
+ bbperror(code, "Deletion failed");
+ }
+ }
+ pressreturn();
+ return rc;
+}
+
+/*ARGSUSED*/
+MailDelRange(hptr, currmsg, numinbox, openflags)
+HEADER *hptr;
+int currmsg, numinbox, openflags;
+{
+ int code, rc = FULLUPDATE;
+ char ans[5];
+ SHORT n1 = 0, n2 = 0, deleted;
+ clear();
+ if (numinbox <= 0) {
+ prints("No messages to delete!\n");
+ pressreturn();
+ return rc;
+ }
+ prints("Delete Range\n");
+ prints("[NOTE: Unread messages will NOT be deleted]\n");
+ do {
+ if (getdata(2,0,"Enter number of first message to delete: ",
+ ans,sizeof(ans), DOECHO, 1) == -1) return rc;
+ n1 = atoi(ans);
+ if (n1 <= 0 || n1 > numinbox) {
+ bell();
+ move(3,0);
+ prints("Not valid! Must be in range 1-%d.\n", numinbox);
+ }
+ } while (n1 <= 0 || n1 > numinbox);
+ do {
+ if (getdata(3,0,"Enter number of last message to delete: ",
+ ans,sizeof(ans), DOECHO, 1) == -1) return rc;
+ n2 = atoi(ans);
+ if (n2 < n1 || n2 > numinbox) {
+ bell();
+ move(4,0);
+ prints("Not valid! Must be in range %d-%d.\n", n1, numinbox);
+ }
+ } while (n2 < n1 || n2 > numinbox);
+
+ move(4,0);
+ clrtobot();
+ getdata(4,0,"Are you sure (Y/N)? [N]: ",ans,sizeof(ans),DOECHO,0);
+ move(6,0);
+ if (*ans != 'Y' && *ans != 'y') {
+ prints("No messages deleted.\n");
+ }
+ else {
+ if ((code = bbs_delete_range(n1, n2, &deleted)) == S_OK) {
+ prints("%d messages deleted.\n", deleted);
+ rc |= FETCHNEW;
+ }
+ else bbperror(code, "Delete range failed\n");
+ }
+ pressreturn();
+ return rc;
+}
+
+/*ARGSUSED*/
+Forward(hptr, currmsg, numrecs, openflags)
+HEADER *hptr;
+int currmsg, numrecs, openflags;
+{
+ int code, rc = FULLUPDATE;
+ ACCOUNT acct;
+ char ans[4];
+ clear();
+ if ((code = bbs_owninfo(&acct)) != S_OK) {
+ bbperror(code, "Can't fetch address");
+ pressreturn();
+ return rc;
+ }
+ prints("Mailing '%s' to:\n", hptr->title);
+ prints("%s\n", acct.email);
+ getdata(2,0,"Are you sure (Y/N)? [Y]: ",ans,sizeof(ans),DOECHO,0);
+ move(4,0);
+ if (*ans == 'N' || *ans == 'n') {
+ prints("Message not forwarded.\n");
+ }
+ else {
+ if ((code = bbs_forward_message(hptr->fileid)) == S_OK) {
+ prints("Mail sent!\n");
+ }
+ else bbperror(code, "Forward failed");
+ }
+ pressreturn();
+ return rc;
+}
+
+GenericMailReply(hptr, group, msgsrc)
+HEADER *hptr;
+int group;
+char *msgsrc;
+{
+ NAMELIST recips = NULL;
+ TITLE subject;
+ char ans[4];
+ int rc, retcode = FULLUPDATE;
+ LONG failmask;
+ PATH msgfile;
+
+ clear();
+
+ if (msgsrc == NULL) {
+ if ((rc = bbs_read_message(hptr->fileid, msgfile)) != S_OK) {
+ bbperror(rc, "Can't read message");
+ return retcode;
+ }
+ }
+ else strcpy(msgfile, msgsrc);
+
+ add_namelist(&recips, hptr->owner, NULL);
+ if (group) parse_to_list(&recips, msgfile, myinfo.userid);
+
+ if (strncasecmp(hptr->title, "Re:", 3)) {
+ strcpy(subject, "Re: ");
+ strncat(subject, hptr->title, TITLELEN-5);
+ }
+ else strncpy(subject, hptr->title, TITLELEN-1);
+
+ getdata(0,0,"Include message text in reply (Y/N)? [N]: ",
+ ans, sizeof(ans), DOECHO, 0);
+ if (*ans == 'Y' || *ans == 'y') {
+ AnnotateMessageBody(c_tempfile, msgfile);
+ }
+
+ rc = DoMailSend(recips, subject, c_tempfile, 1, &failmask);
+ clear();
+ move(0,0);
+ switch (rc) {
+ case -3:
+ prints("Zero length reply not sent.\n");
+ break;
+ case -2:
+ prints("Reply NOT sent.\n");
+ break;
+ case 0:
+ prints("Reply sent!\n");
+ if (is_in_namelist(recips, myinfo.userid)) BITSET(retcode, FETCHNEW);
+ break;
+ default:
+ if (group) {
+ bbperror(rc, "Reply not delivered to following");
+ apply_namelist(recips, show_names, &failmask);
+ }
+ else bbperror(rc, "Reply could not be delivered.\n");
+ break;
+ }
+ unlink(c_tempfile);
+ pressreturn();
+ return retcode;
+}
+
+/*ARGSUSED*/
+MailReply(hptr, currmsg, numrecs, openflags)
+HEADER *hptr;
+int currmsg, numrecs, openflags;
+{
+ return (GenericMailReply(hptr, 0, NULL));
+}
+
+/*ARGSUSED*/
+GroupReply(hptr, currmsg, numrecs, openflags)
+HEADER *hptr;
+int currmsg, numrecs, openflags;
+{
+ return (GenericMailReply(hptr, 1, NULL));
+}
+
+/*ARGSUSED*/
+MailDisplay(hptr, currmsg, numrecs, openflags)
+HEADER *hptr;
+int currmsg, numrecs, openflags;
+{
+ char ans[9], promptstr[80];
+ PATH msgfile;
+ int code, rc = FULLUPDATE;
+ int done = 0, replied = 0;
+#ifdef REMOTE_CLIENT
+ int saved = 0;
+#else
+ int forwarded = 0;
+#endif
+ if ((code = bbs_read_message(hptr->fileid, msgfile)) != S_OK) {
+ bbperror(code, "Error retrieving file");
+ pressreturn();
+ return rc;
+ }
+ BITCLR(hptr->flags, FILE_UNREAD);
+ clear();
+ More(msgfile, 0);
+ while (!done) {
+ promptstr[0] = '\0';
+ if (!replied) strcat(promptstr, "Reply, Group reply, ");
+#ifdef REMOTE_CLIENT
+ if (!saved) strcat(promptstr, "Save, ");
+#else
+ if (!forwarded) strcat(promptstr, "Forward, ");
+#endif
+ strcat(promptstr, "Delete, or Continue? [C]: ");
+ getdata(t_lines-1, 0, promptstr, ans, sizeof ans, DOECHO, 0);
+ switch (*ans) {
+ case 'r': case 'R':
+ case 'g': case 'G':
+ if (replied) done = 1;
+ else {
+ rc |= GenericMailReply(hptr, toupper(*ans)=='G'?1:0, msgfile);
+ replied = 1;
+ }
+ break;
+#ifdef REMOTE_CLIENT
+ case 's': case 'S':
+ if (saved) done = 1;
+ else {
+ rc |= SaveFile(msgfile);
+ saved = 1;
+ }
+ break;
+#else
+ case 'f': case 'F':
+ if (forwarded) done = 1;
+ else {
+ rc |= Forward(hptr, currmsg, numrecs, openflags);
+ forwarded = 1;
+ }
+ break;
+#endif
+ case 'd': case 'D':
+ rc |= MailDelete(hptr, currmsg, numrecs, openflags);
+ /* fall through */
+ default:
+ done = 1;
+ break;
+ }
+ }
+ return rc;
+}
+
diff --git a/c_post.c b/c_post.c
new file mode 100644
index 0000000..947868f
--- /dev/null
+++ b/c_post.c
@@ -0,0 +1,494 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "client.h"
+#include <stdlib.h>
+#include <sys/stat.h>
+
+int
+OpenBoard(openflags, newonly, resp)
+int *openflags;
+int newonly;
+int *resp;
+{
+ int code;
+ OPENINFO openinfo;
+ code = bbs_open_board(currboard, &openinfo);
+ if (resp) *resp = code;
+ if (code != S_OK) return -1;
+ if (openflags) *openflags = openinfo.flags;
+ return (newonly ? openinfo.newmsgs : openinfo.totalmsgs);
+}
+
+CloseBoard()
+{
+ int code;
+ code = bbs_close_board();
+ return (code == S_OK ? 0 : -1);
+}
+
+/*
+ Return codes for DoPostSend:
+ 0 == success
+ -2 == post was manually aborted
+ -3 == post is zero length -- aborted
+ other == error code from bbs_post
+*/
+
+DoPostSend(subject, textfile, doedit)
+char *subject; /* subject string */
+char *textfile; /* file to send: if NULL invoke editor */
+int doedit; /* edit even if textfile != NULL */
+{
+ int rc = 0;
+ struct stat stbuf;
+
+ if (textfile == NULL || doedit) {
+ if (textfile == NULL) textfile = c_tempfile;
+ if (Edit(textfile)) {
+ if (textfile == c_tempfile) unlink(c_tempfile);
+ return -2;
+ }
+ }
+ if (stat(textfile, &stbuf) != 0 || stbuf.st_size == 0) {
+ rc = -3;
+ }
+ else rc = bbs_post(currboard, subject, textfile);
+
+ if (textfile == c_tempfile) unlink(c_tempfile);
+ return rc;
+}
+
+GenericPost(docheck)
+int docheck;
+{
+ int rc;
+ LONG retcode = FULLUPDATE;
+ TITLE subject;
+ clear();
+ if (docheck) {
+ SHORT flags;
+ if (currboard[0] == '\0') {
+ prints("Use (S)elect to select a board first.\n");
+ pressreturn();
+ return FULLUPDATE;
+ }
+ if ((rc = bbs_test_board(currboard, &flags)) != S_OK) {
+ bbperror(rc, "Can't access board");
+ pressreturn();
+ return FULLUPDATE;
+ }
+ if (!BITISSET(flags, OPEN_POST)) {
+ prints("Posting on the '%s' board is restricted.\n", currboard);
+ pressreturn();
+ return FULLUPDATE;
+ }
+ }
+ bbs_set_mode(M_POSTING);
+ prints("Posting message on board '%s'\n", currboard);
+ getdata(1, 0, "Subject: ", subject, sizeof(subject), DOECHO, 0);
+ rc = DoPostSend(subject, (char *)NULL, 0);
+ clear();
+ move(0,0);
+ switch (rc) {
+ case -3:
+ prints("Zero length file NOT sent.\n");
+ break;
+ case -2:
+ prints("File NOT posted.\n");
+ break;
+ case 0:
+ prints("File posted!\n");
+ retcode |= FETCHNEW;
+ break;
+ default:
+ bbperror(rc, "Error saving post");
+ break;
+ }
+ pressreturn();
+ /* If docheck is true, we came in from the main menu -- else, read menu */
+ bbs_set_mode(docheck ? M_UNDEFINED : M_READING);
+ return retcode;
+}
+
+Post()
+{
+ return (GenericPost(1));
+}
+
+/*ARGSUSED*/
+PostMessage(hptr, currmsg, numrecs, openflags)
+HEADER *hptr;
+int currmsg, numrecs, openflags;
+{
+ if (!BITISSET(openflags, OPEN_POST)) {
+ bell();
+ return DONOTHING;
+ }
+ return (GenericPost(0)); /* no need to check post privilege -- we do */
+}
+
+/*ARGSUSED*/
+ReadMenuSelect(hptr, currmsg, numrecs, openflags)
+HEADER *hptr;
+int currmsg, numrecs, openflags;
+{
+ return (SelectBoard());
+}
+
+GenericPostReply(hptr, msgsrc)
+HEADER *hptr;
+char *msgsrc;
+{
+ TITLE subject;
+ char ans[4];
+ int rc, retcode = FULLUPDATE;
+ PATH msgfile;
+
+ clear();
+
+ if (msgsrc == NULL) {
+ if ((rc = bbs_read_message(hptr->fileid, msgfile)) != S_OK) {
+ bbperror(rc, "Error reading post");
+ return retcode;
+ }
+ }
+ else strcpy(msgfile, msgsrc);
+
+ move(1,0);
+ bbs_set_mode(M_POSTING);
+ prints("[RETURN to Re: current title]\n");
+ getdata(0, 0, "Title: ", subject, sizeof(subject), DOECHO, 0);
+ if (*subject == '\0') {
+ if (strncasecmp(hptr->title, "Re:", 3)) {
+ strcpy(subject, "Re: ");
+ strncat(subject, hptr->title, TITLELEN-5);
+ }
+ else strncpy(subject, hptr->title, TITLELEN-1);
+ move(0,0);
+ prints("Title: %s\n", subject);
+ }
+
+ getdata(1, 0, "Include message text in followup (Y/N)? [N]: ",
+ ans, sizeof(ans), DOECHO, 0);
+ if (*ans == 'Y' || *ans == 'y') {
+ AnnotateMessageBody(c_tempfile, msgfile);
+ }
+
+ rc = DoPostSend(subject, c_tempfile, 1);
+ clear();
+ move(0,0);
+ switch (rc) {
+ case -3:
+ prints("Zero length file NOT sent.\n");
+ break;
+ case -2:
+ prints("File NOT posted.\n");
+ break;
+ case 0:
+ prints("File posted!\n");
+ retcode |= FETCHNEW;
+ break;
+ default:
+ bbperror(rc, "Post failed");
+ break;
+ }
+ pressreturn();
+ bbs_set_mode(M_READING);
+ return retcode;
+}
+
+/*ARGSUSED*/
+PostReply(hptr, currmsg, numrecs, openflags)
+HEADER *hptr;
+LONG currmsg, numrecs, openflags;
+{
+ return (GenericPostReply(hptr, NULL));
+}
+
+/*ARGSUSED*/
+PostDelete(hptr, currmsg, numrecs, openflags)
+HEADER *hptr;
+int currmsg, numrecs, openflags;
+{
+ int code, rc = FULLUPDATE;
+ char ans[4];
+ if (strcmp(myinfo.userid,hptr->owner) && !BITISSET(openflags,OPEN_MANAGE)) {
+ bell();
+ return DONOTHING;
+ }
+ clear();
+ prints("Delete message '%s'\n", hptr->title);
+ getdata(2,0,"Are you sure (Y/N)? [N]: ",ans,sizeof(ans),DOECHO, 0);
+ move(4,0);
+ if (*ans != 'Y' && *ans != 'y') {
+ prints("Message not deleted.\n");
+ }
+ else {
+ if ((code = bbs_delete_message(hptr->fileid)) == S_OK) {
+ prints("Message deleted.\n");
+ rc |= FETCHNEW;
+ }
+ else {
+ bbperror(code, "Deletion failed");
+ }
+ }
+ pressreturn();
+ return rc;
+}
+
+/*ARGSUSED*/
+PostDelRange(hptr, currmsg, numinbox, openflags)
+HEADER *hptr;
+int currmsg, numinbox, openflags;
+{
+ int code, rc = FULLUPDATE;
+ char ans[5];
+ SHORT n1 = 0, n2 = 0, deleted;
+ if (!BITISSET(openflags, OPEN_MANAGE)) {
+ bell();
+ return DONOTHING;
+ }
+ clear();
+ if (numinbox <= 0) {
+ prints("No messages to delete!\n");
+ pressreturn();
+ return rc;
+ }
+ prints("Delete Range\n");
+ prints("[NOTE: Marked messages will NOT be deleted]\n");
+ do {
+ if (getdata(2,0,"Enter number of first message to delete: ",
+ ans,sizeof(ans),DOECHO,1) == -1) return rc;
+ n1 = atoi(ans);
+ if (n1 <= 0 || n1 > numinbox) {
+ bell();
+ move(3,0);
+ prints("Not valid! Must be in range 1-%d.\n", numinbox);
+ }
+ } while (n1 <= 0 || n1 > numinbox);
+ do {
+ if (getdata(3,0,"Enter number of last message to delete: ",
+ ans,sizeof(ans),DOECHO,1) == -1) return rc;
+ n2 = atoi(ans);
+ if (n2 < n1 || n2 > numinbox) {
+ bell();
+ move(4,0);
+ prints("Not valid! Must be in range %d-%d.\n", n1, numinbox);
+ }
+ } while (n2 < n1 || n2 > numinbox);
+
+ move(4,0);
+ clrtobot();
+ getdata(4,0,"Are you sure (Y/N)? [N]: ",ans,sizeof(ans),DOECHO,0);
+ move(6,0);
+ if (*ans != 'Y' && *ans != 'y') {
+ prints("No messages deleted.\n");
+ }
+ else {
+ if ((code = bbs_delete_range(n1, n2, &deleted)) == S_OK) {
+ prints("%d unmarked messages deleted.\n", deleted);
+ rc |= FETCHNEW;
+ }
+ else bbperror(code, "Delete range failed");
+ }
+ pressreturn();
+ return rc;
+}
+
+/*ARGSUSED*/
+PostMark(hptr, currmsg, numrecs, openflags)
+HEADER *hptr;
+int currmsg, numrecs, openflags;
+{
+ SHORT ismarked = BITISSET(hptr->flags, FILE_MARKED);
+ if (bbs_mark_message(hptr->fileid, !ismarked) != S_OK) return DONOTHING;
+ if (ismarked) BITCLR(hptr->flags, FILE_MARKED);
+ else BITSET(hptr->flags, FILE_MARKED);
+ return PARTUPDATE;
+}
+
+/*ARGSUSED*/
+PostMove(hptr, currmsg, numrecs, openflags)
+HEADER *hptr;
+int currmsg, numrecs, openflags;
+{
+ int code, rc = FULLUPDATE;
+ NAME bname;
+ extern NAMELIST boardlist;
+ char ans[4];
+ if (strcmp(myinfo.userid,hptr->owner) && !BITISSET(openflags,OPEN_MANAGE)) {
+ bell();
+ return DONOTHING;
+ }
+ clear();
+ prints("Moving post '%s' to another board\n", hptr->title);
+ getdata(2,0,"Are you sure (Y/N)? [N]: ",ans,sizeof(ans),DOECHO, 0);
+ move(4,0);
+ if (*ans != 'Y' && *ans != 'y') {
+ prints("Message not moved.\n");
+ }
+ else {
+ bbs_boardnames(&boardlist, NULL);
+ if (boardlist == NULL) {
+ prints("No boards to select.\n");
+ pressreturn();
+ return FULLUPDATE;
+ }
+ move(2,0);
+ namecomplete(NULL, boardlist, "Move to board: ", bname, sizeof(NAME));
+ if (bname[0] == '\0') return FULLUPDATE;
+ else if (!is_in_namelist(boardlist, bname)) {
+ bbperror(S_NOSUCHBOARD, NULL);
+ }
+ else if ((code = bbs_move_message(hptr->fileid, bname)) == S_OK) {
+ prints("Message moved to '%s'.\n", bname);
+ rc |= FETCHNEW;
+ }
+ else {
+ bbperror(code, "Move failed");
+ }
+ }
+ pressreturn();
+ return rc;
+}
+
+/*ARGSUSED*/
+PostEdit(hptr, currmsg, numrecs, openflags)
+HEADER *hptr;
+int currmsg, numrecs, openflags;
+{
+ struct stat stbuf;
+ PATH msgfile;
+ int rc;
+ if ((rc = bbs_read_message(hptr->fileid, msgfile)) != S_OK) {
+ bbperror(rc, "Error reading post");
+ pressreturn();
+ return FULLUPDATE;
+ }
+ if (Edit(msgfile)) {
+ prints("File NOT edited.\n");
+ return FULLUPDATE;
+ }
+ if (stat(msgfile, &stbuf) != 0 || stbuf.st_size == 0) {
+ prints("New file is zero length -- not updated.\n");
+ return FULLUPDATE;
+ }
+ if ((rc = bbs_update_message(hptr->fileid, msgfile)) == S_OK) {
+ prints("File was edited!\n");
+ }
+ else bbperror(rc, "File edit failed");
+ return FULLUPDATE;
+}
+
+/*ARGSUSED*/
+PostDisplay(hptr, currmsg, numrecs, openflags)
+HEADER *hptr;
+int currmsg, numrecs, openflags;
+{
+ char ans[9], promptstr[80];
+ PATH msgfile;
+ int code, rc = FULLUPDATE;
+ int done = 0, replied = 0, marked = 0, can_delete = 0;
+#ifndef NO_POST_FOLLOWUP
+ int posted = 0;
+#endif
+#ifdef REMOTE_CLIENT
+ int saved = 0;
+#endif
+ if ((code = bbs_read_message(hptr->fileid, msgfile)) != S_OK) {
+ bbperror(code, "Error reading file");
+ pressreturn();
+ return rc;
+ }
+ BITCLR(hptr->flags, FILE_UNREAD);
+
+ if (!HasPerm(C_MAIL)) replied = 1;
+#ifndef NO_POST_FOLLOWUP
+ if (!BITISSET(openflags, OPEN_POST)) posted = 1;
+#endif
+ if (!BITISSET(openflags, OPEN_MANAGE)) marked = 1;
+ if (BITISSET(openflags, OPEN_MANAGE) || !strcmp(hptr->owner, myinfo.userid))
+ can_delete = 1;
+
+ clear();
+ More(msgfile, 0);
+ while (!done) {
+ promptstr[0] = '\0';
+ if (!replied) strcat(promptstr, "Mail reply, ");
+#ifndef NO_POST_FOLLOWUP
+ if (!posted) strcat(promptstr, "Post followup, ");
+#endif
+#ifdef REMOTE_CLIENT
+ if (!saved) strcat(promptstr, "Save, ");
+#endif
+ if (!marked) strcat(promptstr,
+ BITISSET(hptr->flags, FILE_MARKED) ? "unmarK, " : "marK, ");
+ if (can_delete) strcat(promptstr, "Delete, ");
+
+ if (promptstr[0] == '\0') {
+ pressreturn();
+ break;
+ }
+ else strcat(promptstr, "or Continue? [C]: ");
+ getdata(t_lines-1, 0, promptstr, ans, sizeof(ans), DOECHO, 0);
+ switch (*ans) {
+ case 'm': case 'M':
+ if (replied) done = 1;
+ else {
+ rc |= MailReply(hptr, currmsg, numrecs, openflags);
+ replied = 1;
+ }
+ break;
+#ifndef NO_POST_FOLLOWUP
+ case 'p': case 'P':
+ if (posted) done = 1;
+ else {
+ rc |= GenericPostReply(hptr, msgfile);
+ posted = 1;
+ }
+ break;
+#endif
+#ifdef REMOTE_CLIENT
+ case 's': case 'S':
+ if (saved) done = 1;
+ else {
+ SaveFile(msgfile);
+ saved = 1;
+ }
+ break;
+#endif
+ case 'k': case 'K':
+ if (marked) done = 1;
+ else {
+ PostMark(hptr, currmsg, numrecs, openflags);
+ marked = 1;
+ }
+ break;
+ case 'd': case 'D':
+ if (can_delete) rc |= PostDelete(hptr, currmsg, numrecs, openflags);
+ /* fall through */
+ default:
+ done = 1;
+ break;
+ }
+ }
+ return rc;
+}
diff --git a/c_talk.c b/c_talk.c
new file mode 100644
index 0000000..7d6b9b4
--- /dev/null
+++ b/c_talk.c
@@ -0,0 +1,442 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "client.h"
+#include <signal.h>
+
+#define TALK_MAX_COLS 128
+
+NAMELIST userlist;
+int g_page_pending;
+int g_page_need_notify;
+
+struct talkwin {
+ int firstln;
+ int lastln;
+ int currln;
+ int firstcol;
+ int lastcol;
+ int currcol;
+ char line[TALK_MAX_COLS];
+};
+
+#define MAX_HANDLED_LOGINS 20
+
+struct usertopid {
+ USEREC saved_urec;
+ LONG pids[MAX_HANDLED_LOGINS];
+ int count;
+};
+
+void
+page_handler(sig)
+int sig;
+{
+ bell();
+ bell();
+ g_page_pending = 1;
+ g_page_need_notify = 1;
+ signal(sig, page_handler);
+}
+
+PagePending()
+{
+ return g_page_pending;
+}
+
+/* We need this function to handle page requests in talk and chat mode.
+ We only want to notify once, but need to keep the page request
+ around in case we want to go off and answer it. */
+
+NewPagePending()
+{
+ if (g_page_need_notify) {
+ g_page_need_notify = 0;
+ return 1;
+ }
+ return 0;
+}
+
+PrintLoginEntry(num, urec)
+int num;
+USEREC *urec;
+{
+ prints("%2d %-12s %-24s %-30s %s\n", num, urec->userid,
+ urec->username, urec->fromhost, ModeToString(urec->mode));
+}
+
+/*ARGSUSED*/
+PickLogin(indx, urec, info)
+int indx;
+USEREC *urec;
+struct usertopid *info;
+{
+ if (info->count >= MAX_HANDLED_LOGINS) return ENUM_QUIT;
+ info->pids[info->count++] = urec->pid;
+ if (info->count == 1) {
+ memcpy(&info->saved_urec, urec, sizeof(info->saved_urec));
+ return S_OK;
+ }
+ if (info->count == 2) {
+ clear();
+ PrintLoginEntry(1, &info->saved_urec);
+ }
+ PrintLoginEntry(info->count, urec);
+ return S_OK;
+}
+
+LONG
+UseridToPid(userid)
+char *userid;
+{
+ struct usertopid u2p;
+ int x, y, indx;
+ char ans[3];
+ memset(&u2p, 0, sizeof u2p);
+ bbs_enum_users(MAX_HANDLED_LOGINS, 0, userid, PickLogin, &u2p);
+ if (u2p.count == 0) return (LONG)-1;
+ else if (u2p.count == 1) return u2p.pids[0];
+ /* Else, more than one. We must decide. */
+ getyx(&y, &x);
+ getdata(y, x, "Which login [1]: ", ans, sizeof ans, DOECHO, 0);
+ indx = atoi(ans);
+ if (indx < 1 || indx > u2p.count) indx = 1;
+ return (u2p.pids[indx-1]);
+}
+
+Kick()
+{
+ NAME namebuf;
+ LONG pid;
+ char ans[4];
+ int rc;
+ move(2, 0);
+ clrtobot();
+ bbs_usernames(&userlist, NULL);
+ namecomplete(NULL, userlist, "Kick whom: ", namebuf, sizeof(NAME));
+ if (namebuf[0] == '\0') {
+ return FULLUPDATE;
+ }
+ else if (!is_in_namelist(userlist, namebuf)) {
+ bbperror(S_NOSUCHUSER, NULL);
+ pressreturn();
+ return FULLUPDATE;
+ }
+ pid = UseridToPid(namebuf);
+ if (pid == (LONG)-1) {
+ bbperror(S_NOSUCHUSER, NULL);
+ pressreturn();
+ return FULLUPDATE;
+ }
+ getdata(3, 0, "Are you sure (Y/N)? [N]: ", ans, sizeof ans, DOECHO, 0);
+ if (*ans != 'Y' && *ans != 'y') {
+ prints("User NOT kicked.\n");
+ pressreturn();
+ return FULLUPDATE;
+ }
+
+ if ((rc = bbs_kick_user(pid)) == S_OK) {
+ prints("User has been kicked.\n");
+ }
+ else bbperror(rc, "Kick failed");
+
+ pressreturn();
+ return FULLUPDATE;
+}
+
+void
+TalkAdvanceLine(ts)
+struct talkwin *ts;
+{
+ if (++ts->currln > ts->lastln) ts->currln = ts->firstln;
+ move((ts->currln == ts->lastln ? ts->firstln : ts->currln+1), 0);
+ clrtoeol();
+ move(ts->currln, 0);
+ clrtoeol();
+}
+
+DoTalkChar(ch, ts)
+char ch;
+struct talkwin *ts;
+{
+ /* This function handles backspaces, newlines, and printables. */
+ move(ts->currln, ts->currcol);
+ if (ch == CTRL('H') || ch == 127) {
+ if (ts->currcol == 0) return -1;
+ move(ts->currln, --ts->currcol);
+ addch(' ');
+ ts->line[ts->currcol] = '\0';
+ move(ts->currln, ts->currcol);
+ }
+ else if (ch == '\n' || ch == '\r') {
+ ts->currcol = 0;
+ TalkAdvanceLine(ts);
+ memset(ts->line, 0, sizeof ts->line);
+ }
+ else if (isprint(ch)) {
+ if (ts->currcol < ts->lastcol) {
+ ts->line[ts->currcol++] = ch;
+ addch(ch);
+ }
+ else {
+ char save[TALK_MAX_COLS];
+ int i = ts->currcol;
+ ts->line[i] = ch;
+ memcpy(save, ts->line, sizeof save);
+ while (i && save[i] != ' ') i--;
+ if (i == 0) {
+ memset(ts->line, 0, sizeof(ts->line));
+ ts->currcol = 1;
+ TalkAdvanceLine(ts);
+ addch(ch);
+ }
+ else {
+ move(ts->currln, i);
+ clrtoeol();
+ ts->currcol -= i;
+ memset(ts->line, 0, sizeof(ts->line));
+ memcpy(ts->line, &save[i+1], ts->currcol);
+ TalkAdvanceLine(ts);
+ prints(ts->line);
+ }
+ }
+ }
+ else return -1;
+
+ refresh();
+ return 0;
+}
+
+DoTalkString(s, tw)
+char *s;
+struct talkwin *tw;
+{
+ for (; s && *s; s++) DoTalkChar(*s, tw);
+ return 0;
+}
+
+talk_show_page_request(ln)
+int ln;
+{
+ USEREC urec;
+ char buf[80];
+ if (bbs_get_talk_request(&urec, NULL, NULL) != S_OK) {
+ /* Whoever was paging stopped. */
+ return 0;
+ }
+ sprintf(buf, " Being paged by %s (%s)", urec.userid, urec.username);
+ move(ln, 3);
+ prints(buf);
+ move(ln, t_columns-20);
+ prints("[CTRL-R to erase]");
+ return 0;
+}
+
+_talk_enum_users(count, urec, tw)
+int count;
+USEREC *urec;
+struct talkwin *tw;
+{
+ char buf[NAMELEN+10];
+ sprintf(buf, ",%s%s [%c]", BITISSET(urec->flags, FLG_CLOAK) ? " #" : " ",
+ urec->userid, ModeToChar(urec->mode));
+ DoTalkString((tw->currcol == 0 ? buf+2 : buf), tw);
+ return S_OK;
+}
+
+talk_user_list(tw)
+struct talkwin *tw;
+{
+ DoTalkString("\n*** Users currently online ***\n", tw);
+ bbs_enum_users(10, 0, NULL, _talk_enum_users, tw);
+ DoTalkChar('\n', tw);
+ return 0;
+}
+
+DrawDivider(ln)
+int ln;
+{
+ int i;
+ move(ln, 0);
+ for (i=0; i<t_columns; i++) {
+ addch('-');
+ }
+ refresh();
+}
+
+DoTalk(sock)
+int sock;
+{
+ int i, ch, cc;
+ int divider;
+ char c;
+ char incoming[80];
+ struct talkwin me, them;
+
+ divider = (t_lines-1) / 2;
+ me.currln = me.firstln = 0;
+ me.lastln = divider - 1;
+ them.currln = them.firstln = divider + 1;
+ them.lastln = t_lines - 1;
+ me.currcol = me.firstcol = them.currcol = them.firstcol = 0;
+ me.lastcol = them.lastcol =
+ (t_columns > TALK_MAX_COLS ? TALK_MAX_COLS : t_columns - 1);
+ memset(me.line, 0, sizeof me.line);
+ memset(them.line, 0, sizeof them.line);
+
+ clear();
+ DrawDivider(divider);
+ move(0, 0);
+ add_io(sock, 0);
+ while (1) {
+ ch = igetch();
+ if (NewPagePending()) {
+ talk_show_page_request(divider);
+ move(me.currln, me.currcol);
+ }
+ if (ch == -1) {
+ /* error in igetch! */
+ break;
+ }
+ if (ch == I_OTHERDATA) {
+ /* pending input on sock */
+ cc = recv(sock, incoming, sizeof incoming, 0);
+ if (cc <= 0) {
+ /* other side closed connection */
+ break;
+ }
+ for (i=0; i<cc; i++) {
+ DoTalkChar(incoming[i], &them);
+ }
+ }
+ else {
+ /* something we typed */
+ c = (char)ch;
+ if (DoTalkChar(c, &me) == 0) {
+ if (send(sock, &c, 1, 0) != 1) {
+ /* connection broken */
+ break;
+ }
+ }
+ else if (c == CTRL('C') || c == CTRL('D')) {
+ /* we're ending the talk */
+ break;
+ }
+ else if (c == CTRL('R')) {
+ /* clear message on divider line */
+ DrawDivider(divider);
+ move(me.currln, me.currcol);
+ }
+ else if (c == CTRL('U')) {
+ /* show a brief user list */
+ talk_user_list(&me);
+ }
+ else {
+ /* a character we don't like, at this time */
+ bell();
+ }
+ }
+ }
+ add_io(0,0);
+
+ bbs_exit_talk();
+ close(sock);
+ return 0;
+}
+
+Talk()
+{
+ NAME namebuf;
+ LONG pid;
+ LONG sock;
+ int rc;
+ move(2, 0);
+ clrtobot();
+ bbs_usernames(&userlist, NULL);
+ namecomplete(NULL, userlist, "Page whom: ", namebuf, sizeof(NAME));
+ if (namebuf[0] == '\0') {
+ return FULLUPDATE;
+ }
+ else if (!is_in_namelist(userlist, namebuf)) {
+ bbperror(S_NOSUCHUSER, NULL);
+ pressreturn();
+ return FULLUPDATE;
+ }
+ pid = UseridToPid(namebuf);
+ if (pid == (LONG)-1) {
+ bbperror(S_NOSUCHUSER, NULL);
+ pressreturn();
+ return FULLUPDATE;
+ }
+ clear();
+ prints("Paging %s. Press CTRL-D to abort.\n", namebuf);
+ refresh();
+ do {
+ bell();
+ switch (rc = bbs_talk(pid, 0, &sock)) {
+ case S_OK:
+ DoTalk((int)sock);
+ break;
+ case S_CMDTIMEOUT:
+ prints("No answer. Ringing party again.\n");
+ refresh();
+ break;
+ case S_INTERRUPT:
+ return FULLUPDATE;
+ default:
+ bbperror(rc, "Page failed");
+ }
+ } while (rc == S_CMDTIMEOUT);
+ if (rc != S_OK && rc != S_CMDTIMEOUT) pressreturn();
+ return FULLUPDATE;
+}
+
+Answer()
+{
+ USEREC urec;
+ LONG addr, sock;
+ SHORT port;
+ char ans[4];
+ int rc;
+
+ g_page_pending = g_page_need_notify = 0;
+
+ if (bbs_get_talk_request(&urec, &addr, &port) != S_OK) {
+ /* Whoever was paging stopped. */
+ return 0;
+ }
+
+ clear();
+ prints("Would you like to talk to %s (%s)?", urec.userid, urec.username);
+ getdata(1, 0, "Yes or No [Y]: ", ans, sizeof ans, DOECHO, 0);
+ if (*ans == 'n' || *ans == 'N') {
+ bbs_refuse_page(addr, port);
+ return 1;
+ }
+
+ if ((rc = bbs_accept_page(addr, port, &sock)) != S_OK) {
+ bbperror(rc, "Answer failed");
+ pressreturn();
+ return 1;
+ }
+
+ DoTalk((int)sock);
+ return 1;
+}
diff --git a/c_users.c b/c_users.c
new file mode 100644
index 0000000..c9206be
--- /dev/null
+++ b/c_users.c
@@ -0,0 +1,1063 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "client.h"
+#if LACKS_MALLOC_H
+# include <stdlib.h>
+#else
+# include <malloc.h>
+#endif
+#include <signal.h>
+#include <time.h>
+
+NAMELIST acctlist;
+extern LOGININFO myinfo; /* for idle timeout in Monitor */
+
+CheckUserid(userid)
+char *userid;
+{
+ ACCOUNT acct;
+ if (userid[0] == '\0') return 1;
+ if (!is_valid_userid(userid)) return 3;
+ if (bbs_query(userid, &acct) != S_NOSUCHUSER) return 2;
+ return 0;
+}
+
+PromptForAccountInfo(acct, for_self)
+ACCOUNT *acct;
+int for_self;
+{
+ PASSWD passcfm;
+ char ans[4];
+ int lineno, userok = 0, passok, cfm = 1;
+ memset(acct, 0, sizeof(*acct));
+ if (for_self) {
+ prints("Welcome, new user! Enter a userid, 1-%d characters, no spaces.\n",
+ NAMELEN);
+ lineno = 0;
+ }
+ else lineno = 3;
+ while (!userok) {
+ if (getdata(lineno,0,"Userid: ",acct->userid,NAMELEN+1,DOECHO,1) == -1)
+ return -1;
+
+ switch (CheckUserid(acct->userid)) {
+ case 0: userok = 1;
+ break;
+ case 1: if (!for_self) return -1; /* bail out */
+ break;
+ case 2: bbperror(S_USEREXISTS, NULL);
+ break;
+ case 3: bbperror(S_BADUSERID, NULL);
+ break;
+ }
+ }
+ if (lineno) lineno++;
+ while (cfm) {
+ do {
+ getdata(lineno,0,"Enter Passwd: ", acct->passwd, PASSLEN+1, NOECHO, 0);
+ passok = is_valid_password(acct->passwd);
+ if (!passok) bbperror(S_BADPASSWD, NULL);
+ } while (!passok);
+ getdata(lineno+1,0,"Confirm Passwd: ", passcfm, PASSLEN+1, NOECHO, 0);
+ if (cfm = strcmp(acct->passwd, passcfm))
+ prints("\nPasswords did not match! Try again.\n");
+ }
+ if (lineno) lineno++;
+ getdata(lineno,0,"User Name: ", acct->username, UNAMELEN+1, DOECHO, 0);
+ if (lineno) lineno++;
+ getdata(lineno,0,"Terminal type (default=vt100): ",
+ acct->terminal, TERMLEN+1, DOECHO, 0);
+ getdata(lineno,0,"Charset (default=ascii): ",
+ acct->charset, CSETLEN+1, DOECHO, 0);
+ if (!for_self) {
+ if (lineno) lineno++;
+ getdata(lineno,0,"Real Name: ", acct->realname, RNAMELEN+1,DOECHO,0);
+ if (lineno) lineno++;
+ getdata(lineno,0,"Postal Address: ", acct->address, ADDRLEN+1,DOECHO,0);
+ }
+ if (lineno) lineno++;
+ getdata(lineno,0,"E-mail address, if any: ",acct->email,MAILLEN+1,DOECHO,0);
+ return 0;
+}
+
+/*ARGSUSED*/
+AllUsersFunc(indx, acct, info)
+int indx;
+ACCOUNT *acct;
+struct enum_info *info;
+{
+ if (info->topline == info->currline) {
+ move(info->topline-1, 0);
+ prints("%-14s %-30s %s\n","User Id", "User Name", "Last Login");
+ }
+
+ prints("%-14s %-30s %c %s", acct->userid, acct->username,
+ BITISSET(acct->flags, FLG_EXEMPT) ? 'X': ' ',
+ (acct->lastlogin == 0) ? "\n":ctime((time_t *)&acct->lastlogin));
+
+ info->currline++;
+ info->count++;
+
+ if (info->currline > info->bottomline) {
+ int ch;
+ standout();
+ prints("--MORE--");
+ standend();
+ clrtoeol();
+ while((ch = igetch()) != EOF) {
+ if(ch == '\n' || ch == '\r' || ch == ' ')
+ break;
+ if(toupper(ch) == 'Q') {
+ move(info->currline, 0);
+ clrtoeol();
+ return ENUM_QUIT;
+ }
+ else bell();
+ }
+ info->currline = info->topline;
+ }
+ return S_OK;
+}
+
+AllUsers()
+{
+ struct enum_info info;
+ info.count = 0;
+ info.topline = info.currline = 4;
+ info.bottomline = t_lines-2;
+ move(3,0);
+ clrtobot();
+ bbs_enum_accounts(t_lines-5, 0, AllUsersFunc, &info);
+ clrtobot();
+ move(t_lines-1, 0);
+ prints("%d %s displayed\n", info.count, info.count==1?"user":"users");
+ return PARTUPDATE;
+}
+
+/*ARGSUSED*/
+OnlineUsersFunc(indx, urec, info)
+int indx;
+USEREC *urec;
+struct enum_info *info;
+{
+ if (info->topline == info->currline) {
+ move(info->topline-1, 0);
+ prints("%-12s %-25s %-25s %s %s\n",
+ "User Id", "User Name", "From", "P", "Mode");
+ }
+
+ prints("%-12s %c %-25s %-25s %c %s\n", urec->userid,
+ BITISSET(urec->flags, FLG_CLOAK) ? '#': ' ',
+ urec->username, urec->fromhost,
+ BITISSET(urec->flags, FLG_NOPAGE) ? 'N': ' ',
+ ModeToString(urec->mode));
+
+ info->currline++;
+ info->count++;
+
+ if (info->currline > info->bottomline) {
+ int ch;
+ standout();
+ prints("--MORE--");
+ standend();
+ clrtoeol();
+ while((ch = igetch()) != EOF) {
+ if(ch == '\n' || ch == '\r' || ch == ' ')
+ break;
+ if(toupper(ch) == 'Q') {
+ move(info->currline, 0);
+ clrtoeol();
+ return ENUM_QUIT;
+ }
+ else bell();
+ }
+ info->currline = info->topline;
+ }
+ return S_OK;
+}
+
+OnlineUsers()
+{
+ struct enum_info info;
+ info.count = 0;
+ info.topline = info.currline = 4;
+ info.bottomline = t_lines-2;
+ move(3,0);
+ clrtobot();
+ bbs_enum_users(t_lines-5, 0, NULL, OnlineUsersFunc, &info);
+ clrtobot();
+ move(t_lines-1, 0);
+ prints("%d %s displayed\n", info.count, info.count==1?"user":"users");
+ return PARTUPDATE;
+}
+
+struct shorturec {
+ NAME userid;
+ LONG pid;
+ SHORT flags;
+ SHORT mode;
+ short wasfound;
+ short found;
+ short active;
+} *global_ulist;
+
+int global_ulist_sz;
+
+SetupGlobalList()
+{
+ global_ulist_sz = (t_lines - 4) * 4;
+ global_ulist =
+ (struct shorturec *)calloc(global_ulist_sz, sizeof(struct shorturec));
+ if (global_ulist == NULL)
+ {
+ move(3,0);
+ prints("Not enough memory to fetch user list!\n");
+ return -1;
+ }
+ return 0;
+}
+
+/*ARGSUSED*/
+FillShortUserList(indx, urec, arg)
+int indx;
+USEREC *urec;
+void *arg;
+{
+ int i;
+ for (i=0; i<global_ulist_sz; i++)
+ if (global_ulist[i].pid == urec->pid) {
+ global_ulist[i].flags = urec->flags;
+ global_ulist[i].mode = urec->mode;
+ global_ulist[i].found = 1;
+ break;
+ }
+ if (i >= global_ulist_sz)
+ for (i=0; i<global_ulist_sz; i++)
+ if (!global_ulist[i].active)
+ {
+ strcpy(global_ulist[i].userid, urec->userid);
+ global_ulist[i].pid = urec->pid;
+ global_ulist[i].flags = urec->flags;
+ global_ulist[i].mode = urec->mode;
+ global_ulist[i].active = global_ulist[i].found = 1;
+ break;
+ }
+
+ return S_OK;
+}
+
+DoShortUserList()
+{
+ int i, y = 3, x = 0, ucount = 0;
+ time_t now;
+
+ for (i=0; i<global_ulist_sz; i++) {
+ global_ulist[i].wasfound = global_ulist[i].found;
+ global_ulist[i].found = 0;
+ }
+ move(y, x);
+ clrtobot();
+ time(&now);
+ bbs_enum_users(global_ulist_sz, 0, NULL, FillShortUserList, NULL);
+ for (i=0; i<global_ulist_sz; i++)
+ {
+ if (global_ulist[i].found)
+ {
+ prints("[%c]%s%-14s", ModeToChar(global_ulist[i].mode),
+ BITISSET(global_ulist[i].flags, FLG_CLOAK) ? " #" : " ",
+ global_ulist[i].userid);
+ ucount++;
+ }
+ else if (global_ulist[i].wasfound) prints("%18s", " ");
+
+ x+=18;
+ if ((x+18) > t_columns)
+ {
+ x=0; y++;
+ }
+ move(y, x);
+ }
+ move(t_lines-1, 0);
+ prints("%d user%s online at %s", ucount, (ucount==1?"":"s"), ctime(&now));
+ refresh();
+ return ucount;
+}
+
+ShortList()
+{
+ int i;
+ if (global_ulist == NULL) {
+ if (SetupGlobalList() == -1) return PARTUPDATE;
+ }
+ DoShortUserList();
+ memset(global_ulist, 0, global_ulist_sz * sizeof(*global_ulist));
+ return PARTUPDATE;
+}
+
+struct shorturec *monitor_data;
+int monitor_max;
+int monitor_idle;
+char global_modechar_key[75];
+
+form_modechar_key()
+{
+ SHORT i;
+ int left;
+ char c, *s, buf[20];
+ strcpy(global_modechar_key, "Key:");
+ left = sizeof(global_modechar_key) - 5;
+ for (i=0; i<=BBS_MAX_MODE; i++) {
+ c = ModeToChar(i);
+ if (c == ' ') continue;
+ s = ModeToString(i);
+ sprintf(buf, " [%c]", c);
+ if (toupper(c) == toupper(*s)) strncat(buf+4, s+1, 10);
+ else strncat(buf+4, s, 10);
+ if (left > strlen(buf)) {
+ strcat(global_modechar_key, buf);
+ left -= strlen(buf);
+ }
+ }
+ return 0;
+}
+
+void
+monitor_refresh(sig)
+{
+ int i, boottime;
+ if (sig) signal(sig, SIG_IGN);
+ boottime = myinfo.idletimeout*120;
+ if (boottime && ((monitor_idle += MONITOR_REFRESH) > boottime)) {
+ disconnect(EXIT_TIMEDOUT);
+ }
+ if (bbs_check_mail()) {
+ move(0, t_columns/3);
+ prints("(You have mail.)");
+ }
+ DoShortUserList();
+ for (i=0; i<global_ulist_sz; i++)
+ if (!global_ulist[i].found) global_ulist[i].active = 0;
+
+ signal(SIGALRM, monitor_refresh);
+ alarm(MONITOR_REFRESH);
+}
+
+Monitor()
+{
+ void (*asig)();
+ char ch;
+ int saved_alarm;
+ if (global_modechar_key[0] == '\0') form_modechar_key();
+ clear();
+ prints("Monitor Mode Press CTRL-C \
+or CTRL-D to exit.\n");
+ prints("%s\n", global_modechar_key);
+ prints("-----------------------------------------------------------\
+------------------\n");
+ if (global_ulist == NULL) {
+ if (SetupGlobalList() == -1) return PARTUPDATE;
+ }
+ monitor_idle = 0;
+ bbs_set_mode(M_MONITOR);
+ saved_alarm = alarm(0);
+ asig = signal(SIGALRM, SIG_IGN);
+ monitor_refresh(0);
+ while (1)
+ {
+ ch = igetch();
+ monitor_idle = 0;
+ if (ch == CTRL('C') || ch == CTRL('D')) break;
+ }
+ alarm(0);
+ signal(SIGALRM, asig);
+ if (saved_alarm) alarm(saved_alarm);
+ bbs_set_mode(M_UNDEFINED);
+ memset(global_ulist, 0, global_ulist_sz * sizeof(*global_ulist));
+ return FULLUPDATE;
+}
+
+SetPasswd()
+{
+ ACCOUNT acct;
+ int rc;
+ PASSWD passbuf, passcfm;
+ move(3,0);
+ clrtobot();
+ if ((rc = bbs_owninfo(&acct)) != S_OK) {
+ move(4,0);
+ prints("\n");
+ bbperror(rc, "Cannot fetch old password");
+ return PARTUPDATE;
+ }
+ if (getdata(3,0,"Old password: ",passbuf,sizeof passbuf,NOECHO,1) == -1)
+ return FULLUPDATE;
+
+ if (passbuf[0] == '\0' || !is_passwd_good(acct.passwd, passbuf)) {
+ move(4,0);
+ prints("\nSorry.\n");
+ return PARTUPDATE;
+ }
+ getdata(4,0,"New Password: ",passbuf,sizeof passbuf,NOECHO,0);
+ if(!is_valid_password(passbuf)) {
+ move(5,0);
+ prints("\nBad Password\n");
+ return PARTUPDATE;
+ }
+ getdata(5,0,"Confirm Password: ",passcfm,sizeof passbuf,NOECHO,0);
+ move(6,0);
+ if(strcmp(passbuf,passcfm)) {
+ prints("Error entering password.\n");
+ return PARTUPDATE;
+ }
+ rc = bbs_set_passwd(passbuf);
+ if (rc == S_OK)
+ prints("Password was changed.\n");
+ else
+ bbperror(rc, "Password change failed");
+
+ return PARTUPDATE;
+}
+
+SetUsername()
+{
+ UNAME username;
+ int rc;
+ move(3,0);
+ clrtobot();
+ if (getdata(3,0,"Enter your name: ",username,sizeof username,DOECHO,1)==-1)
+ return FULLUPDATE;
+ rc = bbs_set_username(username);
+ if (rc == S_OK)
+ prints("Name was changed.\n");
+ else
+ bbperror(rc, "Name change failed");
+
+ return PARTUPDATE;
+}
+
+SetAddress()
+{
+ MAIL email;
+ int rc;
+ move(3,0);
+ clrtobot();
+ if (getdata(3, 0, "Enter your e-mail address: ", email,
+ sizeof email,DOECHO,1) == -1) return FULLUPDATE;
+ rc = bbs_set_email(email);
+ if (rc == S_OK)
+ prints("Address was changed.\n");
+ else
+ bbperror(rc, "Address change failed");
+
+ return PARTUPDATE;
+}
+
+SetTermtype()
+{
+ TERM terminal;
+ int rc;
+ move(3,0);
+ clrtobot();
+ if (getdata(3,0, "Enter new terminal type: ", terminal,
+ sizeof terminal, DOECHO, 1) == -1) return FULLUPDATE;
+ if(terminal[0] == '\0')
+ return PARTUPDATE;
+ if(term_init(terminal) == -1) {
+ prints("Invalid terminal type.\n");
+#ifndef REMOTE_CLIENT
+ return PARTUPDATE;
+#endif
+ }
+ else {
+ initscr();
+ clear();
+ }
+ rc = bbs_set_terminal(terminal);
+ if (rc == S_OK)
+ prints("New terminal type was saved.\n");
+ else
+ bbperror(rc, "New terminal type not saved.");
+
+ pressreturn();
+ return FULLUPDATE;
+}
+
+SetCharset()
+{
+ CSET charset;
+ int rc;
+ move(3,0);
+ clrtobot();
+ if (getdata(3,0, "Enter new charset: ", charset,
+ sizeof charset, DOECHO, 1) == -1) return FULLUPDATE;
+ if(charset[0] == '\0')
+ return PARTUPDATE;
+ if(conv_init(charset) == -1) {
+ prints("Invalid character set.\n");
+#ifndef REMOTE_CLIENT
+ return PARTUPDATE;
+#endif
+ }
+ else {
+ initscr();
+ clear();
+ }
+ rc = bbs_set_charset(charset);
+ if (rc == S_OK)
+ prints("New character set was saved.\n");
+ else
+ prints("New character set NOT saved.\n");
+ pressreturn();
+ return FULLUPDATE;
+}
+
+UserDisplay(acct)
+ACCOUNT *acct;
+{
+ prints("[%s]\n", acct->userid);
+ prints("User name: %s\n", acct->username);
+ if (acct->lastlogin) {
+ prints("Last login time: %s", ctime((time_t *)&acct->lastlogin));
+ prints("Last login from: %s\n", acct->fromhost);
+ }
+ else prints("Never logged in.\n");
+ if (*acct->terminal) prints("Terminal type: %s\n", acct->terminal);
+ if (*acct->charset) prints("Charset: %s\n", acct->charset);
+ if (*acct->realname) prints("Real name: %s\n", acct->realname);
+ if (*acct->address) prints("Address: %s\n", acct->address);
+ if (*acct->email) prints("E-mail address: %s\n", acct->email);
+ clrtobot();
+}
+
+ShowOwnInfo()
+{
+ ACCOUNT acct;
+ int rc;
+ move(3,0);
+ clrtobot();
+ rc = bbs_owninfo(&acct);
+ if (rc != S_OK) {
+ bbperror(rc, "Can't get user info:\n");
+ }
+ else UserDisplay(&acct);
+ return PARTUPDATE;
+}
+
+AddAccount()
+{
+ int rc;
+ ACCOUNT acct;
+ char ans[4];
+ move(3,0);
+ clrtobot();
+ if (PromptForAccountInfo(&acct, 0) == -1) {
+ return PARTUPDATE;
+ }
+ getdata(12, 0, "Are you sure (Y/N)? [N]: ", ans, sizeof ans, DOECHO, 0);
+ move(13,0);
+ if (ans[0] != 'Y' && ans[0] != 'y') {
+ prints("Account not added.\n");
+ return PARTUPDATE;
+ }
+ rc = bbs_add_account(&acct, 0);
+ switch (rc) {
+ case S_OK:
+ prints("New account added.\n");
+ break;
+ default:
+ bbperror(rc, "Account add failed");
+ }
+ return PARTUPDATE;
+}
+
+DeleteAccount()
+{
+ NAME namebuf;
+ int rc;
+ char ans[4];
+ move(2,0);
+ clrtobot();
+ bbs_acctnames(&acctlist, NULL);
+ namecomplete(NULL, acctlist, "Userid to delete: ", namebuf, sizeof(NAME));
+ if (namebuf[0] == '\0' || !is_in_namelist(acctlist, namebuf)) {
+ bbperror(S_NOSUCHUSER, NULL);
+ pressreturn();
+ return FULLUPDATE;
+ }
+ prints("Deleting user '%s'.\n", namebuf);
+ getdata(5,0,"Are you sure (Y/N)? [N]: ",ans,sizeof(ans),DOECHO,0);
+ if (ans[0] != 'Y' && ans[0] != 'y') {
+ prints("Account not deleted.\n");
+ pressreturn();
+ return FULLUPDATE;
+ }
+ rc = bbs_delete_account(namebuf);
+ if (rc == S_OK)
+ prints("Account deleted.\n");
+ else
+ bbperror(rc, "Account deletion failed");
+
+ pressreturn();
+ return FULLUPDATE;
+}
+
+SetUserData()
+{
+ NAME userid;
+ ACCOUNT acct, nr;
+ int rc;
+ SHORT flags = 0;
+ PASSWD passcfm;
+ int x, y, grok;
+ char genbuf[256], ans[4];
+ move(2,0);
+ memset(&nr, 0, sizeof nr);
+ bbs_acctnames(&acctlist, NULL);
+ namecomplete(NULL, acctlist, "Userid to set: ", userid, sizeof(NAME));
+ if (userid[0] == '\0' || !is_in_namelist(acctlist, userid)) {
+ bbperror(S_NOSUCHUSER, NULL);
+ pressreturn();
+ return FULLUPDATE;
+ }
+ if ((rc = bbs_get_userinfo(userid, &acct)) != S_OK) {
+ bbperror(rc, NULL);
+ pressreturn();
+ return FULLUPDATE;
+ }
+ move(3,0);
+ UserDisplay(&acct);
+ getyx(&y, &x);
+ getdata(++y,0,"Change any user information (Y/N)? [N]: ",ans,sizeof(ans),
+ DOECHO, 0);
+ if (ans[0] != 'Y' && ans[0] != 'y') {
+ prints("Record not changed.\n");
+ pressreturn();
+ return FULLUPDATE;
+ }
+
+ sprintf(genbuf, "New userid [%s]: ", acct.userid);
+ do {
+ getdata(y+1, 0, genbuf, nr.userid, sizeof(nr.userid), DOECHO, 0);
+ if (!nr.userid[0])
+ break;
+ if (grok = CheckUserid(nr.userid)) {
+ move(y+2, 0);
+ prints("Invalid or taken userid. Try again.\n");
+ }
+ else BITSET(flags, MOD_USERID);
+ } while (grok);
+ y++;
+
+ do {
+ getdata(y+1,0,"New Password: ",nr.passwd,sizeof(nr.passwd),NOECHO, 0);
+ if (!nr.passwd[0]) break;
+ getdata(y+2,0,"Confirm Pass: ", passcfm,sizeof(passcfm),NOECHO, 0);
+ if (grok = strcmp(nr.passwd, passcfm)) {
+ move(y+3, 0);
+ prints("Passwords don't match. Try again.\n");
+ }
+ else BITSET(flags, MOD_PASSWD);
+ } while (grok);
+ move(y+2, 0);
+ clrtobot();
+ y+=2;
+
+ sprintf(genbuf, "New username [%s]: ", acct.username);
+ getdata(y++, 0, genbuf, nr.username, sizeof(nr.username), DOECHO, 0);
+ if (nr.username[0]) BITSET(flags, MOD_USERNAME);
+
+ sprintf(genbuf, "New terminal type [%s]: ", acct.terminal);
+ getdata(y++, 0, genbuf, nr.terminal, sizeof(nr.terminal), DOECHO, 0);
+ if (nr.terminal[0]) BITSET(flags, MOD_TERMINAL);
+
+ sprintf(genbuf, "New charset [%s]: ", acct.charset);
+ getdata(y++, 0, genbuf, nr.charset, sizeof(nr.charset), DOECHO, 0);
+ if (nr.charset[0]) BITSET(flags, MOD_CHARSET);
+
+ sprintf(genbuf, "New real name [%s]: ", acct.realname);
+ getdata(y++, 0, genbuf, nr.realname, sizeof(nr.realname), DOECHO, 0);
+ if (nr.realname[0]) BITSET(flags, MOD_REALNAME);
+
+ sprintf(genbuf, "New address: ");
+ getdata(y++, 0, genbuf, nr.address, sizeof(nr.address), DOECHO, 0);
+ if (nr.address[0]) BITSET(flags, MOD_ADDRESS);
+
+ sprintf(genbuf, "New mail address: ");
+ getdata(y++, 0, genbuf, nr.email, sizeof(nr.email), DOECHO, 0);
+ if (nr.email[0]) BITSET(flags, MOD_EMAIL);
+
+ getdata(y, 0, "Are you sure (Y/N)? [N]: ", ans, sizeof(ans), DOECHO, 0);
+ if (ans[0] == 'Y' || ans[0] == 'y') {
+ rc = bbs_modify_account(acct.userid, &nr, flags);
+ if (rc == S_OK)
+ prints("User data was changed.\n");
+ else
+ bbperror(rc, "User modify failed");
+ }
+ else prints("User data not changed.\n");
+
+ pressreturn();
+ return FULLUPDATE;
+}
+
+char *global_permstrs[32];
+
+#define PERMMENULETTER(i) ((i)<26?('A'+(i)):('1'+(i)-26))
+#define PERMMENUNUMBER(c) ((c)>='A'?((c)-'A'):((c)-'1'+26))
+#define PBITSET(m,i) (((m)>>(i))&1)
+
+LONG
+SetPermMenu(pbits)
+LONG pbits;
+{
+ int i, rc, done = 0;
+ char buf[80], choice[2];
+ move(4,0);
+
+ if (global_permstrs[0] == NULL)
+ if ((rc = bbs_get_permstrings(global_permstrs)) != S_OK) {
+ move(3,0);
+ bbperror(rc, "Can't get permission strings");
+ pressreturn();
+ return pbits;
+ }
+
+ prints("Enter the letter/number to toggle, RETURN when done.\n");
+ move(6,0);
+ for (i=0; i<16; i++) {
+ sprintf(buf, "%c. %-20s %3s %c. %-20s %3s\n",
+ PERMMENULETTER(i), global_permstrs[i],
+ PBITSET(pbits,i) ? "YES" : "NO",
+ PERMMENULETTER(i+16), global_permstrs[i+16],
+ PBITSET(pbits,i+16) ? "YES" : "NO");
+ prints(buf);
+ }
+ clrtobot();
+ while (!done) {
+ getdata(t_lines-1, 0, "Choice (ENTER to quit): ",choice,2,DOECHO,0);
+ *choice = toupper(*choice);
+ if (*choice == '\n' || *choice == '\0') done = 1;
+ else if (!isalnum(*choice) || (*choice>='7' && *choice<='9') ||
+ *choice == '0') bell();
+ else {
+ i = PERMMENUNUMBER(*choice);
+ if (PBITSET(pbits,i))
+ BITCLR(pbits,1<<i);
+ else BITSET(pbits,1<<i);
+ i%=16;
+ sprintf(buf, "%c. %-20s %3s %c. %-20s %3s\n",
+ PERMMENULETTER(i), global_permstrs[i],
+ PBITSET(pbits,i) ? "YES" : "NO",
+ PERMMENULETTER(i+16), global_permstrs[i+16],
+ PBITSET(pbits,i+16) ? "YES" : "NO");
+ move(i+6,0);
+ prints(buf);
+ }
+ }
+ return (pbits);
+}
+
+SetUserPerms()
+{
+ int rc;
+ NAME namebuf;
+ LONG newperms;
+ ACCOUNT acct;
+ move(2,0);
+ bbs_acctnames(&acctlist, NULL);
+ namecomplete(NULL, acctlist, "Userid to set: ", namebuf, sizeof(NAME));
+ if (namebuf[0] == '\0' || !is_in_namelist(acctlist, namebuf)) {
+ bbperror(S_NOSUCHUSER, NULL);
+ pressreturn();
+ return FULLUPDATE;
+ }
+ if ((rc = bbs_get_userinfo(namebuf, &acct)) != S_OK) {
+ bbperror(rc, NULL);
+ pressreturn();
+ return FULLUPDATE;
+ }
+ move(2,0);
+ clrtobot();
+ prints("Set the permissions for user '%s'\n", acct.userid);
+ newperms = SetPermMenu(acct.perms);
+ move(2,0);
+ if (newperms == acct.perms)
+ prints("User '%s' permissions not changed.\n", acct.userid);
+ else {
+ rc = bbs_modify_perms(acct.userid, newperms);
+ if (rc == S_OK) {
+ prints("User '%s' permissions changed.\n", acct.userid);
+ }
+ else {
+ bbperror(rc, "Permission change failed");
+ }
+ }
+ pressreturn();
+ return FULLUPDATE;
+}
+
+QueryEdit()
+{
+ PATH planfile;
+ char ans[7];
+ int rc;
+ move(3,0);
+ clrtobot();
+ if (getdata(4,0,"Edit or Delete plan? [E]: ",ans,sizeof(ans),DOECHO,1)==-1)
+ return FULLUPDATE;
+
+ if (*ans == 'D' || *ans == 'd') {
+ bbs_set_plan(NULL);
+ move(6,0);
+ prints("Plan deleted.\n");
+ return PARTUPDATE;
+ }
+ bbs_get_plan(myinfo.userid, planfile);
+ if (Edit(planfile)) {
+ clear();
+ prints("Plan NOT updated.\n");
+ }
+ else {
+ clear();
+ if ((rc = bbs_set_plan(planfile)) == S_OK) prints("Plan updated.\n");
+ else bbperror(rc, "Plan update failed");
+ }
+ pressreturn();
+ return FULLUPDATE;
+}
+
+/*ARGSUSED*/
+_query_if_logged_in(indx, urec, loggedin)
+int indx;
+USEREC *urec;
+int *loggedin;
+{
+ (*loggedin)++;
+ return ENUM_QUIT;
+}
+
+Query()
+{
+ NAME namebuf;
+ ACCOUNT acct;
+ PATH planfile;
+ char buf[80];
+ FILE *fp;
+ int i, rc, in_now = 0, firstsig = 6;
+ bbs_acctnames(&acctlist, NULL);
+ move(3,0);
+ clrtobot();
+ prints("<Enter Userid>\n");
+ move(2,0);
+ namecomplete(NULL, acctlist, "Query who: ", namebuf, sizeof(NAME));
+ move(2,0);
+ clrtoeol();
+ move(3,0);
+ if (namebuf[0] == '\0' || !is_in_namelist(acctlist, namebuf)) {
+ if (namebuf[0]) {
+ bbperror(S_NOSUCHUSER, NULL);
+ }
+ return PARTUPDATE;
+ }
+
+ if ((rc = bbs_query(namebuf, &acct)) != S_OK) {
+ bbperror(rc, NULL);
+ return PARTUPDATE;
+ }
+
+ bbs_enum_users(20, 0, acct.userid, _query_if_logged_in, &in_now);
+
+ prints("%s (%s):\n", acct.userid, acct.username);
+ if (acct.lastlogin == 0)
+ prints("Never logged in.\n");
+ else prints("%s from %s %s %s", in_now ? "On" : "Last login", acct.fromhost,
+ in_now ? "since" : "at", ctime((time_t *)&acct.lastlogin));
+
+ if (acct.realname[0] != '\0') {
+ prints("Real name: %s\n", acct.realname);
+ firstsig++;
+ }
+
+ if (bbs_get_plan(acct.userid, planfile) != S_OK) {
+ prints("No plan.\n");
+ }
+ else {
+ /* For now, just print one screen of the plan. In the future maybe
+ prompt to ask if they want to page thru the whole plan, since
+ we have it. */
+ if (fp = fopen(planfile, "r")) {
+ prints("Plan:\n");
+ for (i=firstsig; i<t_lines; i++) {
+ if (!fgets(buf, sizeof buf, fp)) break;
+ prints("%s", buf);
+ }
+ fclose(fp);
+ }
+ else prints("No plan.\n");
+ }
+ return PARTUPDATE;
+}
+
+ToggleCloak()
+{
+ int rc;
+ move(3,0);
+ clrtobot();
+ rc = bbs_toggle_cloak();
+ if (rc != S_OK) {
+ bbperror(rc, "Cloak toggle failed");
+ return PARTUPDATE;
+ }
+ prints("Cloak has been toggled.\n");
+ return PARTUPDATE;
+}
+
+ToggleExempt()
+{
+ int rc;
+ NAME namebuf;
+ ACCOUNT acct;
+ move(2,0);
+ bbs_acctnames(&acctlist, NULL);
+ namecomplete(NULL, acctlist, "Userid to exempt/unexempt: ",
+ namebuf, sizeof(NAME));
+ if (namebuf[0] == '\0' || !is_in_namelist(acctlist, namebuf)) {
+ bbperror(S_NOSUCHUSER, NULL);
+ pressreturn();
+ return FULLUPDATE;
+ }
+ if ((rc = bbs_get_userinfo(namebuf, &acct)) != S_OK) {
+ bbperror(rc, NULL);
+ pressreturn();
+ return FULLUPDATE;
+ }
+ rc = bbs_toggle_exempt(acct.userid);
+ if (rc == S_OK) {
+ if (BITISSET(acct.flags, FLG_EXEMPT))
+ prints("User '%s' is now subject to user clean.\n", acct.userid);
+ else
+ prints("User '%s' is now exempt from user clean.\n", acct.userid);
+ }
+ else {
+ bbperror(rc, "Exempt toggle failed");
+ }
+ pressreturn();
+ return FULLUPDATE;
+}
+
+SetPager()
+{
+ int rc;
+ char ans[3], buf[40];
+ ACCOUNT acct;
+ SHORT setting = 1, pageroff = 0, overrideoff = 0;
+ move(3,0);
+ clrtobot();
+ if ((rc = bbs_owninfo(&acct)) != S_OK) {
+ bbperror(rc, NULL);
+ return PARTUPDATE;
+ }
+ if (acct.flags & FLG_NOPAGE) setting += 1;
+ if (acct.flags & FLG_NOOVERRIDE) setting += 2;
+ prints("Current pager setting is %d. Select new setting:\n", setting);
+ prints("1) Anyone can page\n");
+ prints("2) Only users on override list can page\n");
+ prints("3) Only users NOT on override list can page\n");
+ prints("4) Nobody can page\n");
+ sprintf(buf, "Your choice (1-4)? [%d]: ", setting);
+ if (getdata(8, 0, buf, ans, sizeof ans, DOECHO, 1)==-1)
+ return FULLUPDATE;
+ if (*ans != '1' && *ans != '2' && *ans != '3' && *ans != '4') {
+ prints("Pager setting not changed.\n");
+ return PARTUPDATE;
+ }
+ if (*ans == '2' || *ans == '4') pageroff = 1;
+ if (*ans == '3' || *ans == '4') overrideoff = 1;
+ rc = bbs_set_pager(pageroff, overrideoff);
+ if (rc != S_OK) {
+ bbperror(rc, "Pager setting failed");
+ return PARTUPDATE;
+ }
+ prints("Pager has been set.\n");
+ return PARTUPDATE;
+}
+
+SignatureEdit()
+{
+ PATH sigfile;
+ char ans[7];
+ int rc;
+ move(3,0);
+ clrtobot();
+ if (getdata(4,0,"Edit or Delete signature? [E]: ",
+ ans, sizeof(ans), DOECHO, 1) == -1) return FULLUPDATE;
+
+ if (*ans == 'D' || *ans == 'd') {
+ bbs_set_signature(NULL);
+ move(6,0);
+ prints("Signature deleted.\n");
+ return PARTUPDATE;
+ }
+ bbs_get_signature(sigfile);
+ if (Edit(sigfile)) {
+ clear();
+ prints("Signature NOT updated.\n");
+ }
+ else {
+ clear();
+ if ((rc = bbs_set_signature(sigfile)) == S_OK)
+ prints("Signature updated.\n");
+ else bbperror(rc, "Signature update failed");
+ }
+ pressreturn();
+ return FULLUPDATE;
+}
+
+MenuConfig()
+{
+ int rc;
+ SHORT expert = (myinfo.flags & FLG_EXPERT);
+ SHORT newsetting;
+ char ans[3], buf[40];
+ move(3,0);
+ clrtobot();
+ prints("Current menu level is: %s\n", expert ? "Expert" : "Novice");
+ sprintf(buf, "Use (N)ovice or (E)xpert menus? [%c]: ", expert ? 'N' : 'E');
+ if (getdata(5, 0, buf, ans, sizeof ans, DOECHO, 1) == -1)
+ return FULLUPDATE;
+ if (*ans == 'N' || *ans == 'n') newsetting = 0;
+ else if (*ans == 'E' || *ans == 'e') newsetting = 1;
+ else newsetting = (expert ? 0 : 1);
+ if (expert == newsetting) {
+ prints("Menu setting not changed.\n");
+ return PARTUPDATE;
+ }
+ rc = bbs_set_cliopts(newsetting);
+ if (rc != S_OK) {
+ bbperror(rc, "Menu setting failed");
+ return PARTUPDATE;
+ }
+ prints("Changing to %s menus...\n", newsetting ? "Expert" : "Novice");
+ if (newsetting) myinfo.flags |= FLG_EXPERT;
+ else myinfo.flags &= ~FLG_EXPERT;
+ pressreturn();
+ return FULLUPDATE;
+}
diff --git a/chat.c b/chat.c
new file mode 100644
index 0000000..b3314f6
--- /dev/null
+++ b/chat.c
@@ -0,0 +1,241 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "server.h"
+#include <signal.h>
+#include <errno.h> /* this may be just temporary */
+#include <sys/socket.h>
+#include <netinet/in.h>
+#ifdef NeXT
+# include <sys/wait.h>
+# include <sys/time.h>
+# include <sys/resource.h>
+# define waitpid wait3
+#endif
+
+#ifndef INADDR_LOOPBACK
+# define INADDR_LOOPBACK 0x7f000001
+#endif
+
+extern SERVERDATA server;
+
+int chatfd = -1;
+int inchat = 0;
+
+unsigned short
+_read_daemoninfo(fname)
+char *fname;
+{
+ FILE *fp;
+ char buf[5];
+ unsigned short number;
+ if ((fp = fopen(fname, "r")) == NULL) {
+ return 0;
+ }
+ fgets(buf, sizeof buf, fp);
+ buf[4] = '\0';
+ number = (unsigned short)hex2SHORT(buf);
+ fclose(fp);
+ return number;
+}
+
+_start_chat_daemon()
+{
+ int pid;
+ char *argv0 = "bbs.chatd";
+ char argv1[8];
+ sprintf(argv1, "%d", server.maxutable);
+
+ switch (pid = fork()) {
+ case -1:
+ return -1;
+ case 0:
+ execl(PATH_CHATD, argv0, argv1, NULL);
+ bbslog(0, "ERROR _start_chat_daemon: execl failed: %s\n", PATH_CHATD);
+ exit(1);
+ default:
+ /* The chat daemon forks so we can wait on it here. */
+ waitpid(pid, NULL, 0);
+ }
+ return 0;
+}
+
+local_bbs_chat(chatid, pfd)
+char *chatid;
+LONG *pfd;
+{
+ CHATLINE sendbuf;
+ int rc, daemon_started = 0;
+ unsigned short port;
+ if (inchat) return S_OK;
+
+ if (my_real_mode() == M_TALK) return S_MODEVIOLATION;
+
+ if (chatfd == -1) {
+ int s;
+ struct sockaddr_in sin;
+setupchat:
+ memset(&sin, 0, sizeof sin);
+ sin.sin_family = PF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sin.sin_port = port = ntohs(_read_daemoninfo(PATH_CHATPORT));
+ if (sin.sin_port == 0) {
+ _start_chat_daemon();
+ daemon_started++;
+ sin.sin_port = port = ntohs(_read_daemoninfo(PATH_CHATPORT));
+ if (sin.sin_port == 0) {
+ return S_SYSERR;
+ }
+ }
+ if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
+ bbslog(0, "ERROR local_bbs_chat: socket failed\n");
+ return S_SYSERR;
+ }
+ rc = connect(s, (struct sockaddr *)&sin, sizeof sin);
+ if (rc == -1) {
+ if (errno == SIGINT) goto setupchat;
+ bbslog(0, "ERROR local_bbs_chat: connect failed: errno %d\n", errno);
+ if (!daemon_started) {
+ unsigned long currport;
+ close(s);
+ currport = ntohs(_read_daemoninfo(PATH_CHATPORT));
+ if (currport == port) {
+ /* We may have crashed and left the chatport file there.
+ Try to kill the running daemon if it's hung. */
+ pid_t pid = (pid_t)_read_daemoninfo(PATH_CHATPID);
+ if (pid > 0) kill(pid, SIGQUIT);
+ _start_chat_daemon();
+ daemon_started++;
+ }
+ goto setupchat;
+ }
+ close(s);
+ return S_SYSERR;
+ }
+ chatfd = s;
+ }
+
+ set_real_mode(M_CHAT);
+ sprintf(sendbuf, "/! %d %s\n", my_utable_slot(), chatid);
+ if (send(chatfd, sendbuf, strlen(sendbuf), 0) != strlen(sendbuf)) {
+ local_bbs_exit_chat();
+ bbslog(0, "ERROR local_bbs_chat: send failed\n");
+ return S_SYSERR;
+ }
+ if (recv(chatfd, sendbuf, 3, 0) != 3) {
+ local_bbs_exit_chat();
+ bbslog(0, "ERROR local_bbs_chat: recv failed\n");
+ return S_SYSERR;
+ }
+ if (!strcmp(sendbuf, CHAT_LOGIN_OK)) {
+ inchat = 1;
+ *pfd = (LONG)chatfd;
+ bbslog(4, "CHAT ENTER %s as %s\n", my_userid(), chatid);
+ return S_OK;
+ }
+ else if (!strcmp(sendbuf, CHAT_LOGIN_EXISTS)) {
+ set_real_mode(M_UNDEFINED);
+ return S_CHATIDINUSE;
+ }
+ else if (!strcmp(sendbuf, CHAT_LOGIN_INVALID)) {
+ set_real_mode(M_UNDEFINED);
+ return S_BADCHATID;
+ }
+
+ /* else, server really didn't like us */
+ local_bbs_exit_chat();
+ bbslog(0, "ERROR local_bbs_chat: daemon sent back '%s'\n", sendbuf);
+ return S_CHATDERROR;
+}
+
+local_bbs_exit_chat()
+{
+ if (chatfd != -1) {
+ close(chatfd);
+ chatfd = -1;
+ inchat = 0;
+ set_real_mode(M_UNDEFINED);
+ bbslog(4, "CHAT EXIT %s\n", my_userid());
+ }
+ return S_OK;
+}
+
+local_bbs_chat_send(buf)
+char *buf;
+{
+ int len = strlen(buf);
+ if (chatfd == -1 || inchat == 0) return S_DENIED;
+ if (send(chatfd, buf, len, 0) != len) {
+ local_bbs_exit_chat();
+ bbslog(0, "ERROR local_bbs_chat_send: send failed\n");
+ return S_SYSERR;
+ }
+ return S_OK;
+}
+
+remote_bbs_chat(chatid, pport, magicstr)
+char *chatid;
+unsigned short *pport;
+char *magicstr;
+{
+ /* Assumes magicstr is big enough (should be 256 chars) */
+ int daemon_started = 0;
+ unsigned short port;
+ pid_t pid;
+
+ if (inchat) return S_OK;
+
+ if (my_real_mode() == M_TALK) return S_MODEVIOLATION;
+
+ pid = (pid_t)_read_daemoninfo(PATH_CHATPID);
+ if (pid == 0 || kill(pid, 0) == -1) {
+ _start_chat_daemon();
+ daemon_started++;
+ }
+
+ port = ntohs(_read_daemoninfo(PATH_CHATPORT));
+ if (port == 0) {
+ if (daemon_started == 0) {
+ _start_chat_daemon();
+ daemon_started++;
+ port = ntohs(_read_daemoninfo(PATH_CHATPORT));
+ }
+ if (port == 0) {
+ return S_SYSERR;
+ }
+ }
+
+ sprintf(magicstr, "/! %d %s\n", my_utable_slot(), chatid);
+ bbslog(4, "CHAT ENTER %s as %s\n", my_userid(), chatid);
+ *pport = port;
+ inchat = 1;
+ set_real_mode(M_CHAT);
+ return S_OK;
+}
+
+remote_bbs_exit_chat()
+{
+ if (inchat) {
+ inchat = 0;
+ set_real_mode(M_UNDEFINED);
+ bbslog(4, "CHAT EXIT %s\n", my_userid());
+ }
+ return S_OK;
+}
diff --git a/chatconf.c b/chatconf.c
new file mode 100644
index 0000000..37656ea
--- /dev/null
+++ b/chatconf.c
@@ -0,0 +1,93 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "server.h"
+
+#define CHATCONFIGFILE "etc/chatconfig"
+
+/*
+ This is a piece of the chat server; separated because chatserv.c
+ is too damn big already.
+*/
+
+extern NAMELIST manager_list;
+extern NAMELIST restricted_list;
+extern NAME mainroom;
+extern int no_punct_in_chatids;
+
+_chat_config_form_list(list, str)
+NAMELIST *list;
+char *str;
+{
+ char *userid;
+ while ((userid = strtok(str, ", \t")) != NULL) {
+ str = NULL;
+ add_namelist(list, userid, NULL);
+ }
+ return 0;
+}
+
+/*ARGSUSED*/
+_chat_init_config_func(indx, rec, arg)
+int indx;
+char *rec;
+void *arg;
+{
+ char *equals;
+ int i;
+ strip_trailing_space(rec);
+
+ if ((equals = strchr(rec, '=')) == NULL) return S_OK;
+ *equals++ = '\0';
+ strip_trailing_space(rec);
+ strip_trailing_space(equals);
+ while (*rec && isspace(*rec)) rec++;
+ while (*equals && isspace(*equals)) equals++;
+
+ if (!strcasecmp(rec, "mainroom")) {
+ if (*equals != '\0') strncpy(mainroom, equals, NAMELEN);
+ }
+ else if (!strcasecmp(rec, "operators")) {
+ _chat_config_form_list(&manager_list, equals);
+ }
+ else if (!strcasecmp(rec, "restricted")) {
+ _chat_config_form_list(&restricted_list, equals);
+ }
+ else if (!strcasecmp(rec, "nopunct")) {
+ no_punct_in_chatids = (*equals == 'Y' || *equals == 'y');
+ }
+ return S_OK;
+}
+
+chat_init_config()
+{
+ _record_enumerate(CHATCONFIGFILE, 0, _chat_init_config_func, NULL);
+ return S_OK;
+}
+
+chat_get_ignore_file(userid, buf)
+char *userid;
+char *buf;
+{
+ get_home_directory(userid, buf);
+ strcat(buf, "/");
+ strcat(buf, "chatignores");
+}
+
diff --git a/chatserv.c b/chatserv.c
new file mode 100644
index 0000000..92322be
--- /dev/null
+++ b/chatserv.c
@@ -0,0 +1,1495 @@
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+/*-------------------------------------------------------------------------*/
+
+#include "server.h"
+#include "stdlib.h"
+#include <ctype.h>
+#if LACKS_MALLOC_H
+# include <stdlib.h>
+#else
+# include <malloc.h>
+#endif
+#include <errno.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/time.h>
+
+#ifndef RELIABLE_SELECT_FOR_WRITE
+# include <fcntl.h>
+#endif
+
+#if USES_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+
+#if NO_SETPGID
+# define setpgid setpgrp
+#endif
+
+#ifndef SOMAXCONN
+# define SOMAXCONN 5
+#endif
+
+#define FLG_CHATOP 0x0100
+#define FLG_CHATMGR 0x0200
+#define FLG_CHATGUEST 0x0400
+#define FLG_RESTORECLOAK 0x1000
+
+#define RESTRICTED(u) (users[(u)].flags & FLG_CHATGUEST)
+#define OPERATOR(u) (users[(u)].flags & FLG_CHATOP)
+#define MANAGER(u) (users[(u)].flags & FLG_CHATMGR)
+
+#define ROOM_LOCKED 0x1
+#define ROOM_SECRET 0x2
+
+#define LOCKED(r) (rooms[(r)].flags & ROOM_LOCKED)
+#define SECRET(r) (rooms[(r)].flags & ROOM_SECRET)
+
+#define ROOM_ALL (-2)
+
+#define CHAT_MOTD_FILE "etc/chat.motd"
+
+struct chatuser {
+ int sockfd; /* socket to bbs server */
+ int utent; /* utable entry for this user */
+ int client_pid; /* client pid */
+ int room; /* room: -1 means none, 0 means main */
+ int flags; /* FLG_CHATOP, FLG_CHATMGR, FLG_CHATGUEST, etc. */
+ NAME userid; /* real userid */
+ NAME chatid; /* chat id */
+ CHATLINE ibuf; /* buffer for sending/receiving */
+ int ibufsize; /* current size of ibuf */
+ NAMELIST ignorelist; /* list of userids being ignored */
+ char *ignoring; /* list of chat user numbers ignoring me */
+} *users;
+
+struct chatroom {
+ NAME name; /* name of room; room 0 is "main" */
+ short occupants; /* number of users in room */
+ short flags; /* ROOM_LOCKED, ROOM_SECRET */
+ char *invites; /* Keep track of invites to rooms */
+} *rooms;
+
+struct chatcmd {
+ char *cmdstr;
+ int (*cmdfunc)();
+ int exact;
+};
+
+int bbs_max_users; /* Passed in at exec time as argv[1] */
+int sock = -1; /* the socket for listening */
+int nfds; /* number of sockets to select on */
+int num_conns; /* current number of connections */
+fd_set allfds; /* fd set for selecting */
+struct timeval zerotv; /* timeval for selecting */
+CHATLINE genbuf; /* general purpose buffer */
+
+/* These are set up in the chatconfig file. */
+NAME mainroom; /* name of the main room (always exists) */
+NAMELIST manager_list; /* list of chat super-operators */
+NAMELIST restricted_list; /* list of restricted access accounts */
+int no_punct_in_chatids; /* are punctuation characters allowed? */
+
+is_valid_chatid(id)
+char *id;
+{
+ int i;
+ if (*id == '\0') return 0;
+
+ for (i=0; i<CHATID_MAX && *id; i++, id++) {
+ if (!isprint(*id) || *id == '*' || *id == '+' || *id == ':') return 0;
+ if (no_punct_in_chatids && ispunct(*id) && *id != '-' && *id != '_')
+ return 0;
+ }
+
+ return 1;
+}
+
+is_valid_roomname(roomid)
+char *roomid;
+{
+ int i;
+ if (*roomid == '\0') return 0;
+
+ for (i=0; i<NAMELEN && *roomid; i++, roomid++)
+ if (!isprint(*roomid) || *roomid == '+') return 0;
+
+ return 1;
+}
+
+char *
+nextword(str)
+char **str;
+{
+ char *p;
+ while (isspace(**str)) (*str)++;
+ p = *str;
+ while (**str && !isspace(**str)) (*str)++;
+ if (**str) {
+ **str = '\0';
+ (*str)++;
+ }
+ return p;
+}
+
+chatid_to_indx(chatid)
+char *chatid;
+{
+ register int i;
+ for (i=0; i<bbs_max_users; i++) {
+ if (users[i].sockfd == -1) continue;
+ if (!strcasecmp(chatid, users[i].chatid)) return i;
+ }
+ return -1;
+}
+
+fuzzy_chatid_to_indx(chatid)
+char *chatid;
+{
+ register int i, indx = -1;
+ int len = strlen(chatid);
+ for (i=0; i<bbs_max_users; i++) {
+ if (users[i].sockfd == -1) continue;
+ if (!strncasecmp(chatid, users[i].chatid, len)) {
+ if (len == strlen(users[i].chatid)) return i;
+ if (indx == -1) indx = i;
+ else indx = -2;
+ }
+ }
+ return indx;
+}
+
+userid_to_indx(userid)
+char *userid;
+{
+ register int i;
+ for (i=0; i<bbs_max_users; i++) {
+ if (users[i].sockfd == -1) continue;
+ if (!strcasecmp(userid, users[i].userid)) return i;
+ }
+ return -1;
+}
+
+roomid_to_indx(roomid)
+char *roomid;
+{
+ register int i;
+ for (i=0; i<bbs_max_users; i++) {
+ if (i && rooms[i].occupants == 0) continue;
+ if (!strcasecmp(roomid, rooms[i].name)) return i;
+ }
+ return -1;
+}
+
+do_send(writefds, str)
+fd_set *writefds;
+char *str;
+{
+ register int i;
+ int len = strlen(str);
+ /* Remove the string +++ anywhere we see it, for modem users */
+ for (i=0; i<len-2; i++) {
+ if (str[i] == '+' && str[i+1] == '+' && str[i+2] == '+')
+ str[i] = str[i+1] = str[i+2] = ' ';
+ }
+ if (select(nfds, NULL, writefds, NULL, &zerotv) > 0) {
+ for (i=0; i<nfds; i++)
+ if (FD_ISSET(i, writefds)) send(i, str, len+1, 0);
+ }
+}
+
+send_to_room(room, str, fromunum)
+int room;
+char *str;
+int fromunum;
+{
+ int i, checkign, rc = 0;
+ fd_set writefds;
+ checkign = (fromunum != -1 && users[fromunum].ignoring != NULL);
+ FD_ZERO(&writefds);
+ for (i=0; i<bbs_max_users; i++) {
+ if (users[i].sockfd == -1) continue;
+ if (room == ROOM_ALL || room == users[i].room)
+ if (!checkign || !users[fromunum].ignoring[i]) {
+ FD_SET(users[i].sockfd, &writefds);
+ rc++;
+ }
+ }
+ do_send(&writefds, str);
+ return rc;
+}
+
+send_to_unum(unum, str, fromunum)
+int unum;
+char *str;
+int fromunum;
+{
+ fd_set writefds;
+ if (fromunum == -1 || users[fromunum].ignoring == NULL ||
+ users[fromunum].ignoring[unum] == 0) {
+ FD_ZERO(&writefds);
+ FD_SET(users[unum].sockfd, &writefds);
+ do_send(&writefds, str);
+ return 1;
+ }
+ else return 0;
+}
+
+exit_room(unum, disp, msg)
+int unum;
+int disp;
+char *msg;
+{
+ int oldrnum = users[unum].room;
+ if (oldrnum != -1) {
+ if (--rooms[oldrnum].occupants) {
+ switch (disp) {
+ case EXIT_LOGOUT:
+ sprintf(genbuf, "*** %s has left the room", users[unum].chatid);
+ if (msg && *msg) {
+ strcat(genbuf, ": ");
+ strncat(genbuf, msg, CHATLINE_TEXT_MAX);
+ strcat(genbuf, "\n");
+ }
+ break;
+ case EXIT_LOSTCONN:
+ sprintf(genbuf, "*** %s has been disconnected\n", users[unum].chatid);
+ break;
+ case EXIT_KICK:
+ sprintf(genbuf, "*** %s has been kicked out ", users[unum].chatid);
+ if (msg && *msg) {
+ strcat(genbuf, "(");
+ strncat(genbuf, msg, CHATLINE_TEXT_MAX-(strlen(genbuf)+3));
+ strcat(genbuf, ")");
+ }
+ break;
+ }
+ send_to_room(oldrnum, genbuf, unum);
+ }
+ }
+ users[unum].flags &= ~FLG_CHATOP;
+ users[unum].room = -1;
+}
+
+enter_room(unum, room, msg)
+int unum;
+char *room;
+char *msg;
+{
+ int rnum = roomid_to_indx(room);
+ int op = 0;
+ register int i;
+ if (rnum == -1) {
+ /* new room */
+ for (i=1; i<bbs_max_users; i++) {
+ if (rooms[i].occupants == 0) {
+ rnum = i;
+ if (rooms[rnum].invites == NULL) {
+ rooms[rnum].invites = (char *)malloc(bbs_max_users);
+ if (rooms[rnum].invites == NULL) {
+ send_to_unum(unum, "*** Not enough memory\n", -1);
+ return 0;
+ }
+ }
+ memset(rooms[rnum].invites, 0, bbs_max_users);
+ strncpy(rooms[rnum].name, room, NAMELEN);
+ rooms[rnum].name[NAMELEN] = '\0';
+ rooms[rnum].flags = 0;
+ op++;
+ break;
+ }
+ }
+ if (rnum == -1) {
+ send_to_unum(unum, "*** No more rooms available\n", -1);
+ return 0;
+ }
+ }
+ if (!MANAGER(unum))
+ if (LOCKED(rnum) && rooms[rnum].invites[unum] == 0) {
+ send_to_unum(unum, "*** Cannot enter locked room without a key\n", -1);
+ return 0;
+ }
+
+ exit_room(unum, EXIT_LOGOUT, msg);
+ users[unum].room = rnum;
+ if (op) users[unum].flags |= FLG_CHATOP;
+ rooms[rnum].occupants++;
+ rooms[rnum].invites[unum] = 0;
+ sprintf(genbuf, "*** %s has entered room '%s'\n",
+ users[unum].chatid, rooms[rnum].name);
+ send_to_room(rnum, genbuf, unum);
+ return 0;
+}
+
+logout_user(unum)
+{
+ USERDATA udata;
+ int i, sockfd = users[unum].sockfd;
+ PATH ignorefile;
+#ifdef DEBUG
+ printf("Logout on slot %2d, fd %d\n", unum, sockfd);
+#endif
+ close(sockfd);
+ FD_CLR(sockfd, &allfds);
+
+ utable_get_record(users[unum].utent, &udata);
+ if (users[unum].flags & FLG_RESTORECLOAK) {
+ udata.u.flags |= FLG_CLOAK;
+ }
+ utable_set_record(users[unum].utent, &udata);
+
+ if (!RESTRICTED(unum)) {
+ chat_get_ignore_file(users[unum].userid, ignorefile);
+ write_namelist(ignorefile, users[unum].ignorelist);
+ }
+ free_namelist(&users[unum].ignorelist);
+ memset(&users[unum], 0, sizeof(users[unum]));
+ users[unum].sockfd = users[unum].utent = users[unum].room = -1;
+ users[unum].ignorelist = NULL;
+ if (users[unum].ignoring != NULL) {
+ free(users[unum].ignoring);
+ users[unum].ignoring = NULL;
+ }
+ for (i=0; i<bbs_max_users; i++) {
+ if (rooms[i].invites != NULL) rooms[i].invites[unum] = 0;
+ if (users[i].sockfd != -1 && users[i].ignoring != NULL) {
+ users[i].ignoring[unum] = 0;
+ }
+ }
+ num_conns--;
+}
+
+display_motd(unum)
+int unum;
+{
+ FILE *fp = fopen(CHAT_MOTD_FILE, "r");
+ if (fp != NULL) {
+ char buf[80];
+ strcpy(buf, "*** ");
+ while (fgets(buf+4, sizeof(buf)-4, fp) != NULL) {
+ buf[sizeof(buf)-1] = '\0';
+ send_to_unum(unum, buf, -1);
+ }
+ fclose(fp);
+ }
+ return 0;
+}
+
+print_user_counts(unum)
+int unum;
+{
+ int i, c, userc = 0, suserc = 0, roomc = 0;
+ for (i=0; i<bbs_max_users; i++) {
+ c = rooms[i].occupants;
+ if (i > 0 && c > 0) {
+ if (!SECRET(i) || MANAGER(unum)) roomc++;
+ }
+ c = users[i].room;
+ if (users[i].sockfd != -1 && c != -1) {
+ if (SECRET(c) && !MANAGER(unum)) suserc++;
+ else userc++;
+ }
+ }
+ sprintf(genbuf, "*** %d other user(s) present", userc);
+ if (suserc)
+ sprintf(genbuf+strlen(genbuf), " (+ %d in secret rooms)", suserc);
+ strcat(genbuf, "\n");
+ send_to_unum(unum, genbuf, -1);
+ sprintf(genbuf, "*** %d other visible room(s) in use\n", roomc);
+ send_to_unum(unum, genbuf, -1);
+ return 0;
+}
+
+login_user(unum, msg)
+int unum;
+char *msg;
+{
+ int i, utent, fd = users[unum].sockfd;
+ char accessbytes[MAX_CLNTCMDS];
+ char *utentstr = nextword(&msg);
+ char *chatid = nextword(&msg);
+ USERDATA udata;
+ PATH ignorefile;
+
+ utent = atoi(utentstr);
+ if (utable_get_record(utent, &udata) != S_OK || udata.u.mode != M_CHAT) {
+ send_to_unum(unum, CHAT_LOGIN_BOGUS, -1);
+ return -1;
+ }
+ for (i=0; i<bbs_max_users; i++) {
+ if (users[i].sockfd != -1 && users[i].utent == utent) {
+ /* Either a "ghost" or a hacker */
+ if (kill(users[i].client_pid, 0) == 0) {
+ send_to_unum(unum, CHAT_LOGIN_BOGUS, -1);
+ return -1;
+ }
+ else {
+ exit_room(i, EXIT_LOSTCONN, (char *)NULL);
+ logout_user(i);
+ }
+ }
+ }
+ if (strlen(chatid) > CHATID_MAX) chatid[CHATID_MAX] = '\0';
+ if (!is_valid_chatid(chatid)) {
+ send_to_unum(unum, CHAT_LOGIN_INVALID, -1);
+ return 0; /* TODO change to -1 when possible */
+ }
+ if (chatid_to_indx(chatid) != -1) {
+ /* userid in use */
+ send_to_unum(unum, CHAT_LOGIN_EXISTS, -1);
+ return 0; /* TODO: change to -1 when possible */
+ }
+
+ /* Login is ok. Set flags and fill in user record. */
+ utable_get_record(utent, &udata);
+ if (udata.u.flags & FLG_CLOAK) {
+ udata.u.flags &= ~FLG_CLOAK;
+ users[unum].flags |= FLG_RESTORECLOAK;
+ }
+ utable_set_record(utent, &udata);
+
+ if (is_in_namelist(manager_list, udata.u.userid))
+ users[unum].flags |= FLG_CHATMGR;
+ else if (is_in_namelist(restricted_list, udata.u.userid))
+ users[unum].flags |= FLG_CHATGUEST;
+
+ users[unum].utent = utent;
+ users[unum].client_pid = udata.u.pid;
+ strncpy(users[unum].userid, udata.u.userid, NAMELEN);
+ if (!RESTRICTED(unum)) {
+ chat_get_ignore_file(users[unum].userid, ignorefile);
+ read_namelist(ignorefile, &users[unum].ignorelist);
+ }
+ users[unum].ignoring = NULL;
+ for (i = 0; i < bbs_max_users; i++) {
+ if (users[i].sockfd == -1) continue;
+ if (is_in_namelist(users[i].ignorelist, udata.u.userid)) {
+ if (users[unum].ignoring == NULL)
+ users[unum].ignoring = (char *)calloc(bbs_max_users, sizeof(char));
+ if (users[unum].ignoring != NULL)
+ users[unum].ignoring[i] = 1;
+ }
+ if (is_in_namelist(users[unum].ignorelist, users[i].userid)) {
+ if (users[i].ignoring == NULL)
+ users[i].ignoring = (char *)calloc(bbs_max_users, sizeof(char));
+ if (users[i].ignoring != NULL)
+ users[i].ignoring[unum] = 1;
+ }
+ }
+ strcpy(users[unum].chatid, chatid);
+ send_to_unum(unum, CHAT_LOGIN_OK, -1);
+ sprintf(genbuf, "*** Welcome to Chat, %s\n", users[unum].chatid);
+ send_to_unum(unum, genbuf, -1);
+ display_motd(unum);
+ print_user_counts(unum);
+ enter_room(unum, mainroom, (char *)NULL);
+ return 0;
+}
+
+/*ARGSUSED*/
+chat_list_rooms(unum, msg)
+int unum;
+char *msg;
+{
+ int i, occupants;
+ if (RESTRICTED(unum)) {
+ send_to_unum(unum, "*** You do not have permission to list rooms\n", -1);
+ return 0;
+ }
+ send_to_unum(unum, "***\n", -1);
+ send_to_unum(unum, "*** Room Users\n", -1);
+ send_to_unum(unum, "*** ---- -----\n", -1);
+ for (i=0; i<bbs_max_users; i++) {
+ occupants = rooms[i].occupants;
+ if (occupants > 0) {
+ if (!MANAGER(unum))
+ if ((rooms[i].flags & ROOM_SECRET) && (users[unum].room != i))
+ continue;
+ sprintf(genbuf, "*** %-15s %3d", rooms[i].name, occupants);
+ if (rooms[i].flags & ROOM_LOCKED) strcat(genbuf, " (LOCKED)");
+ if (rooms[i].flags & ROOM_SECRET) strcat(genbuf, " (SECRET)");
+ strcat(genbuf, "\n");
+ send_to_unum(unum, genbuf, -1);
+ }
+ }
+ return 0;
+}
+
+chat_do_user_list(unum, msg, whichroom)
+int unum;
+char *msg;
+int whichroom;
+{
+ int start, stop, curr = 0;
+ int i, rnum, myroom = users[unum].room;
+ while (*msg && isspace(*msg)) msg++;
+ start = atoi(msg);
+ while (*msg && isdigit(*msg)) msg++;
+ while (*msg && !isdigit(*msg)) msg++;
+ stop = atoi(msg);
+ send_to_unum(unum, "***\n", -1);
+ send_to_unum(unum, "*** Nick Userid Room\n", -1);
+ send_to_unum(unum, "*** ---- ------ ----\n", -1);
+ for (i=0; i<bbs_max_users; i++) {
+ rnum = users[i].room;
+ if (users[i].sockfd != -1 && rnum != -1) {
+ if (whichroom != -1 && whichroom != rnum) continue;
+ if (myroom != rnum) {
+ if (RESTRICTED(unum)) continue;
+ if ((rooms[rnum].flags & ROOM_SECRET) && !MANAGER(unum)) continue;
+ }
+ curr++;
+ if (curr < start) continue;
+ else if (stop && (curr > stop)) break;
+ sprintf(genbuf, "*** %-9s %-15s %s", users[i].chatid,
+ users[i].userid, rooms[rnum].name);
+ if (OPERATOR(i)) strcat(genbuf, " (Op)");
+ strcat(genbuf, "\n");
+ send_to_unum(unum, genbuf, -1);
+ }
+ }
+ return 0;
+}
+
+chat_list_by_room(unum, msg)
+int unum;
+char *msg;
+{
+ int whichroom;
+ char *roomstr = nextword(&msg);
+ if (*roomstr == '\0') whichroom = users[unum].room;
+ else {
+ if ((whichroom = roomid_to_indx(roomstr)) == -1) {
+ sprintf(genbuf, "*** No such room '%s'\n", roomstr);
+ send_to_unum(unum, genbuf, -1);
+ return 0;
+ }
+ if ((rooms[whichroom].flags & ROOM_SECRET) && !MANAGER(unum)) {
+ send_to_unum(unum, "*** Cannot list users in secret rooms\n", -1);
+ return 0;
+ }
+ }
+ return (chat_do_user_list(unum, msg, whichroom));
+}
+
+chat_list_users(unum, msg)
+int unum;
+char *msg;
+{
+ return (chat_do_user_list(unum, msg, -1));
+}
+
+chat_map_chatids(unum)
+int unum;
+{
+ int i, c, myroom, rnum;
+ myroom = users[unum].room;
+ send_to_unum(unum,
+ "*** Chatid Userid Chatid Userid Chatid Userid\n", -1);
+ send_to_unum(unum,
+ "*** ------ ------ ------ ------ ------ ------\n", -1);
+ strcpy(genbuf, "*** ");
+ for (i=0, c=0; i<bbs_max_users; i++) {
+ rnum = users[i].room;
+ if (users[i].sockfd != -1 && rnum != -1) {
+ if (myroom != rnum) {
+ if (RESTRICTED(unum)) continue;
+ if ((rooms[rnum].flags & ROOM_SECRET) && !MANAGER(unum)) continue;
+ }
+ sprintf(genbuf+(c*24)+4, "%-8s %-12s \n",
+ users[i].chatid, users[i].userid);
+ if (++c == 3) {
+ send_to_unum(unum, genbuf, -1);
+ c = 0;
+ }
+ }
+ }
+ if (c > 0) send_to_unum(unum, genbuf, -1);
+ return 0;
+}
+
+chat_query_chatid(unum, msg)
+int unum;
+char *msg;
+{
+ USERDATA udata;
+ int recunum;
+ char *recipient = nextword(&msg);
+ send_to_unum(unum, "***\n", -1);
+ if (*recipient == '\0') {
+ /* map all chatids to userids */
+ return (chat_map_chatids(unum));
+ }
+ recunum = fuzzy_chatid_to_indx(recipient);
+ if (recunum < 0) {
+ /* no such user, or ambiguous */
+ if (recunum == -1) sprintf(genbuf, "*** No such chatid '%s'\n", recipient);
+ else sprintf(genbuf, "*** '%s' is ambiguous: try more letters", recipient);
+ send_to_unum(unum, genbuf, -1);
+ return 0;
+ }
+ if (utable_get_record(users[recunum].utent, &udata) != S_OK ||
+ strcmp(users[recunum].userid, udata.u.userid) ||
+ udata.u.mode != M_CHAT) {
+ sprintf(genbuf, "*** '%s' is apparently no longer here\n",
+ users[recunum].userid);
+ send_to_unum(unum, genbuf, -1);
+ exit_room(recunum, EXIT_LOSTCONN, (char *)NULL);
+ logout_user(recunum);
+ return 0;
+ }
+ sprintf(genbuf, "*** %s is %s (%s)\n", users[recunum].chatid,
+ udata.u.userid, udata.u.username);
+ send_to_unum(unum, genbuf, -1);
+ sprintf(genbuf, "*** Logged in from %s\n", udata.u.fromhost);
+ send_to_unum(unum, genbuf, -1);
+ return 0;
+}
+
+chat_setroom(unum, msg)
+int unum;
+char *msg;
+{
+ char *modestr = nextword(&msg);
+ int rnum = users[unum].room;
+ int sign = 1;
+ int flag;
+ char *fstr;
+ if (!OPERATOR(unum)) {
+ send_to_unum(unum, "*** You're not operator\n", -1);
+ return 0;
+ }
+ if (*modestr == '+') modestr++;
+ else if (*modestr == '-') {
+ modestr++;
+ sign = 0;
+ }
+ if (*modestr == '\0') {
+ send_to_unum(unum, "*** No flags specified\n", -1);
+ return 0;
+ }
+ while (*modestr) {
+ flag = 0;
+ switch (*modestr) {
+ case 'l': case 'L':
+ flag = ROOM_LOCKED;
+ fstr = "Locked";
+ break;
+ case 's': case 'S':
+ flag = ROOM_SECRET;
+ fstr = "Secret";
+ break;
+ default:
+ sprintf(genbuf, "*** Unknown flag '%c'\n", *modestr);
+ send_to_unum(unum, genbuf, -1);
+ }
+ if (flag && ((rooms[rnum].flags & flag) != sign*flag)) {
+ rooms[rnum].flags ^= flag;
+ sprintf(genbuf, "*** Mode change by %s to%s%s\n",
+ users[unum].chatid, sign ? " " : " NOT ", fstr);
+ send_to_room(rnum, genbuf, -1);
+ }
+ modestr++;
+ }
+ return 0;
+}
+
+chat_nick(unum, msg)
+int unum;
+char *msg;
+{
+ char *chatid = nextword(&msg);
+ int othernum;
+ if (!is_valid_chatid(chatid)) {
+ send_to_unum(unum, "*** Invalid chatid\n", -1);
+ return 0;
+ }
+ if (strlen(chatid) > CHATID_MAX) chatid[CHATID_MAX] = '\0';
+ othernum = chatid_to_indx(chatid);
+ if (othernum != -1 && othernum != unum) {
+ send_to_unum(unum, "*** Chatid is in use\n", -1);
+ return 0;
+ }
+ sprintf(genbuf, "*** %s is now known as ", users[unum].chatid);
+ strcpy(users[unum].chatid, chatid);
+ strcat(genbuf, users[unum].chatid);
+ strcat(genbuf, "\n");
+ send_to_room(users[unum].room, genbuf, unum);
+ sprintf(genbuf, "**C %s", users[unum].chatid);
+ send_to_unum(unum, genbuf, -1);
+ return 0;
+}
+
+chat_private(unum, msg)
+int unum;
+char *msg;
+{
+ char *recipient = nextword(&msg);
+ int recunum = fuzzy_chatid_to_indx(recipient);
+ if (recunum < 0) {
+ /* no such user, or ambiguous */
+ if (recunum == -1) sprintf(genbuf, "*** No such chatid '%s'\n", recipient);
+ else sprintf(genbuf, "*** '%s' is ambiguous: try more letters", recipient);
+ send_to_unum(unum, genbuf, -1);
+ return 0;
+ }
+ if (*msg) {
+ sprintf(genbuf, "*%s* ", users[unum].chatid);
+ strncat(genbuf, msg, CHATLINE_TEXT_MAX);
+ strcat(genbuf, "\n");
+ if (send_to_unum(recunum, genbuf, unum)) {
+ sprintf(genbuf, "%s> ", users[recunum].chatid);
+ strncat(genbuf, msg, CHATLINE_TEXT_MAX);
+ strcat(genbuf, "\n");
+ }
+ else sprintf(genbuf, "*** You are being ignored by '%s'\n",
+ users[recunum].chatid);
+ send_to_unum(unum, genbuf, -1);
+ }
+ return 0;
+}
+
+put_chatid(unum, str)
+int unum;
+char *str;
+{
+ int i;
+ char *chatid = users[unum].chatid;
+ memset(str, ' ', 10);
+ for (i=0; chatid[i]; i++) str[i] = chatid[i];
+ str[i] = ':';
+ str[10] = '\0';
+}
+
+chat_allmsg(unum, msg)
+int unum;
+char *msg;
+{
+ if (*msg) {
+ put_chatid(unum, genbuf);
+ strncat(genbuf, msg, CHATLINE_TEXT_MAX);
+ strcat(genbuf, "\n");
+ send_to_room(users[unum].room, genbuf, unum);
+ }
+ return 0;
+}
+
+chat_emote(unum, msg)
+int unum;
+char *msg;
+{
+ if (*msg) {
+ strcpy(genbuf, users[unum].chatid);
+ strcat(genbuf, " ");
+ strncat(genbuf, msg, CHATLINE_TEXT_MAX);
+ strcat(genbuf, "\n");
+ send_to_room(users[unum].room, genbuf, unum);
+ }
+ return 0;
+}
+
+/*ARGSUSED*/
+chat_time(unum, msg)
+int unum;
+char *msg;
+{
+ time_t now;
+ time(&now);
+ sprintf(genbuf, "*** Date and time on this server: %s", ctime(&now));
+ send_to_unum(unum, genbuf, -1);
+}
+
+#if EXTRA_CHAT_STUFF
+
+diceroll(die)
+int die;
+{
+ int r;
+ int max = (RAND_MAX / die) * die - 1;
+ while ((r = rand()) > max) ;
+ return (r % die + 1);
+}
+
+chat_roll_dice(unum, msg)
+int unum;
+char *msg;
+{
+ static int seeded;
+ int ndice = 2;
+ int die = 6;
+ int total = 0;
+ int i, roll;
+ char diebuf[8];
+ char *rollspec = nextword(&msg);
+ char *d;
+ if (!seeded) {
+ srand((int)time(NULL));
+ seeded++;
+ }
+ if ((d = strchr(rollspec, 'd')) != NULL) {
+ *d = '\0';
+ die = atoi(d+1);
+ }
+ if (*rollspec != '\0') {
+ ndice = atoi(rollspec);
+ }
+ if (ndice < 0 || ndice > 10) {
+ send_to_unum(unum, "*** May only roll from 1 to 10 dice\n", -1);
+ return 0;
+ }
+ if (die < 2 || die > 100) {
+ send_to_unum(unum, "*** Die must have from 2 to 100 sides\n", -1);
+ return 0;
+ }
+ strcpy(genbuf, "*** ");
+ strcat(genbuf, users[unum].chatid);
+ strcat(genbuf, " rolled (");
+ for (i=0; i<ndice; i++) {
+ roll = diceroll(die);
+ if (i > 0) strcat(genbuf, ", ");
+ sprintf(diebuf, "%d", roll);
+ strcat(genbuf, diebuf);
+ total += roll;
+ }
+ strcat(genbuf, ") = ");
+ sprintf(diebuf, "%d", total);
+ strcat(genbuf, diebuf);
+ strcat(genbuf, " on ");
+ sprintf(diebuf, "%dd%d", ndice, die);
+ strcat(genbuf, diebuf);
+ strcat(genbuf, "\n");
+ send_to_room(users[unum].room, genbuf, unum);
+ return 0;
+}
+
+chat_think(unum, msg)
+int unum;
+char *msg;
+{
+ if (*msg) {
+ strcpy(genbuf, users[unum].chatid);
+ strcat(genbuf, " . o O ( ");
+ strncat(genbuf, msg, CHATLINE_TEXT_MAX);
+ strcat(genbuf, " )\n");
+ send_to_room(users[unum].room, genbuf, unum);
+ }
+ return 0;
+}
+
+#endif /* EXTRA_CHAT_STUFF */
+
+chat_join(unum, msg)
+int unum;
+char *msg;
+{
+ char *roomid = nextword(&msg);
+ if (RESTRICTED(unum)) {
+ send_to_unum(unum,
+ "*** You do not have permission to join other rooms\n", -1);
+ return 0;
+ }
+ if (*roomid == '\0') {
+ send_to_unum(unum, "*** You must specify a room\n", -1);
+ return 0;
+ }
+ if (!is_valid_roomname(roomid)) {
+ send_to_unum(unum, "*** Invalid room name\n", -1);
+ return 0;
+ }
+ enter_room(unum, roomid, msg);
+ return 0;
+}
+
+chat_kick(unum, msg)
+int unum;
+char *msg;
+{
+ char *twit = nextword(&msg);
+ int rnum = users[unum].room;
+ int recunum;
+ if (!OPERATOR(unum) && !MANAGER(unum)) {
+ send_to_unum(unum, "*** You're not operator\n", -1);
+ return 0;
+ }
+ if ((recunum = chatid_to_indx(twit)) == -1) {
+ /* no such user */
+ sprintf(genbuf, "*** No such chatid '%s'\n", twit);
+ send_to_unum(unum, genbuf, -1);
+ return 0;
+ }
+ if (rnum != users[recunum].room) {
+ sprintf(genbuf, "*** '%s' is not in this room\n", users[recunum].chatid);
+ send_to_unum(unum, genbuf, -1);
+ return 0;
+ }
+ exit_room(recunum, EXIT_KICK, msg);
+
+ if (rnum == 0) logout_user(recunum);
+ else enter_room(recunum, mainroom, (char *)NULL);
+
+ return 0;
+}
+
+chat_makeop(unum, msg)
+int unum;
+char *msg;
+{
+ char *newop = nextword(&msg);
+ int rnum = users[unum].room;
+ int recunum;
+ if (!OPERATOR(unum)) {
+ send_to_unum(unum, "*** You're not operator\n", -1);
+ return 0;
+ }
+ if ((recunum = chatid_to_indx(newop)) == -1) {
+ /* no such user */
+ sprintf(genbuf, "*** No such chatid '%s'\n", newop);
+ send_to_unum(unum, genbuf, -1);
+ return 0;
+ }
+ if (unum == recunum) {
+ sprintf(genbuf, "*** You're already op\n");
+ send_to_unum(unum, genbuf, -1);
+ return 0;
+ }
+ if (rnum != users[recunum].room) {
+ sprintf(genbuf, "*** '%s' is not in this room\n", users[recunum].chatid);
+ send_to_unum(unum, genbuf, -1);
+ return 0;
+ }
+ users[unum].flags &= ~FLG_CHATOP;
+ users[recunum].flags |= FLG_CHATOP;
+ sprintf(genbuf, "*** %s gave Op to %s\n", users[unum].chatid,
+ users[recunum].chatid);
+ send_to_room(rnum, genbuf, -1);
+ return 0;
+}
+
+chat_invite(unum, msg)
+int unum;
+char *msg;
+{
+ char *invitee = nextword(&msg);
+ int rnum = users[unum].room;
+ int recunum;
+ if (!OPERATOR(unum)) {
+ send_to_unum(unum, "*** You're not operator\n", -1);
+ return 0;
+ }
+ if ((recunum = chatid_to_indx(invitee)) == -1) {
+ /* no such user */
+ sprintf(genbuf, "*** No such chatid '%s'\n", invitee);
+ send_to_unum(unum, genbuf, -1);
+ return 0;
+ }
+ if (rooms[rnum].invites[recunum] == 1) {
+ sprintf(genbuf, "*** %s already has a key\n", users[recunum].chatid);
+ send_to_unum(unum, genbuf, -1);
+ return 0;
+ }
+ rooms[rnum].invites[recunum] = 1;
+ sprintf(genbuf, "*** %s gave you a key to room '%s'\n",
+ users[unum].chatid, rooms[rnum].name);
+ send_to_unum(recunum, genbuf, unum);
+ sprintf(genbuf, "*** Key given to %s\n", users[recunum].chatid);
+ send_to_unum(unum, genbuf, -1);
+ return 0;
+}
+
+chat_broadcast(unum, msg)
+int unum;
+char *msg;
+{
+ if (!MANAGER(unum)) {
+ send_to_unum(unum, "*** You can't do that\n", -1);
+ return 0;
+ }
+ if (*msg == '\0') {
+ send_to_unum(unum, "*** No message given\n", -1);
+ return 0;
+ }
+ sprintf(genbuf, "*** Broadcast message from %s:\n", users[unum].chatid);
+ send_to_room(ROOM_ALL, genbuf, -1);
+ strcpy(genbuf, "*** ");
+ strncat(genbuf, msg, CHATLINE_TEXT_MAX);
+ send_to_room(ROOM_ALL, genbuf, -1);
+ return 0;
+}
+
+/*ARGSUSED*/
+_show_ignores(indx, ignoree, unum)
+int indx;
+char *ignoree;
+int *unum;
+{
+ sprintf(genbuf, "*** You are ignoring %s\n", ignoree);
+ send_to_unum(*unum, genbuf, -1);
+ return 0;
+}
+
+_set_ignores(ignoree, unum, status)
+char *ignoree;
+int unum;
+int status;
+{
+ int inum;
+ for (inum = 0; inum < bbs_max_users; inum++) {
+ if (users[inum].sockfd == -1) continue;
+ if (!strcasecmp(ignoree, users[inum].userid)) {
+ if (users[inum].ignoring == NULL)
+ users[inum].ignoring=(char *)calloc(bbs_max_users, sizeof(char));
+ if (users[inum].ignoring != NULL)
+ users[inum].ignoring[unum] = status;
+ }
+ }
+}
+
+chat_ignore(unum, msg)
+int unum;
+char *msg;
+{
+ if (*msg == '\0') {
+ if (users[unum].ignorelist == NULL)
+ send_to_unum(unum, "*** You're not ignoring anyone\n", -1);
+ else {
+ apply_namelist(users[unum].ignorelist, _show_ignores, &unum);
+ }
+ }
+ else {
+ int inum;
+ char *ignoree = nextword(&msg);
+ inum = userid_to_indx(ignoree);
+ if (inum == -1) {
+ sprintf(genbuf, "*** User '%s' is not in chat\n", ignoree);
+ send_to_unum(unum, genbuf, -1);
+ }
+#if NO_IGNORE_CHATOPS
+ else if (MANAGER(inum)) {
+ sprintf(genbuf, "*** You can't ignore '%s'\n", ignoree);
+ send_to_unum(unum, genbuf, -1);
+ }
+#endif
+ else {
+ int ignoring = is_in_namelist(users[unum].ignorelist, ignoree);
+ if (ignoring) {
+ sprintf(genbuf, "*** Already ignoring user '%s'\n", ignoree);
+ }
+ else {
+ add_namelist(&users[unum].ignorelist, ignoree, NULL);
+ sprintf(genbuf, "*** Ignoring user '%s'\n", ignoree);
+ _set_ignores(ignoree, unum, 1);
+ }
+ send_to_unum(unum, genbuf, -1);
+ }
+ }
+ return 0;
+}
+
+chat_unignore(unum, msg)
+int unum;
+char *msg;
+{
+ if (*msg == '\0') {
+ send_to_unum(unum, "*** Must specify a userid\n", -1);
+ }
+ else {
+ char *ignoree = nextword(&msg);
+ int removing = is_in_namelist(users[unum].ignorelist, ignoree);
+ if (removing) {
+ remove_namelist(&users[unum].ignorelist, ignoree);
+ sprintf(genbuf, "*** No longer ignoring user '%s'\n", ignoree);
+ _set_ignores(ignoree, unum, 0);
+ }
+ else {
+ sprintf(genbuf, "*** You aren't ignoring '%s'\n", ignoree);
+ }
+ send_to_unum(unum, genbuf, -1);
+ }
+ return 0;
+}
+
+chat_goodbye(unum, msg)
+int unum;
+char *msg;
+{
+ exit_room(unum, EXIT_LOGOUT, msg);
+ return 0;
+}
+
+struct chatcmd cmdlist[] =
+{ "action", chat_emote, 0,
+ "date", chat_time, 0,
+ "exit", chat_goodbye, 0,
+ "flags", chat_setroom, 0,
+ "ignore", chat_ignore, 1,
+ "invite", chat_invite, 0,
+ "join", chat_join, 0,
+ "kick", chat_kick, 0,
+ "me", chat_emote, 1,
+ "msg", chat_private, 0,
+ "nick", chat_nick, 0,
+ "operator", chat_makeop, 0,
+#if EXTRA_CHAT_STUFF
+ "roll", chat_roll_dice, 1,
+#endif
+ "rooms", chat_list_rooms, 0,
+#if EXTRA_CHAT_STUFF
+ "think", chat_think, 0,
+#endif
+ "unignore", chat_unignore, 1,
+ "whoin", chat_list_by_room, 1,
+ "whois", chat_query_chatid, 1,
+ "wall", chat_broadcast, 1,
+ "who", chat_list_users, 0,
+ NULL, NULL, 0
+};
+
+command_execute(unum)
+int unum;
+{
+ char *msg = users[unum].ibuf;
+ char *cmd;
+ struct chatcmd *cmdrec;
+ int match = 0;
+
+#ifdef DEBUG
+ printf("command_execute: %s\n", msg);
+#endif
+
+ /* Validation routine */
+ if (users[unum].room == -1) {
+ /* MUST give special /! command if not in the room yet */
+ if (msg[0] != '/' || msg[1] != '!') return -1;
+ else return (login_user(unum, msg+2));
+ }
+
+ /* If not a /-command, it goes to the room. */
+ if (msg[0] != '/') {
+ chat_allmsg(unum, msg);
+ return 0;
+ }
+
+ msg++;
+ cmd = nextword(&msg);
+
+ /* Look up the command in the chat command table. */
+ for (cmdrec = cmdlist; !match && cmdrec->cmdstr != NULL; cmdrec++) {
+ if (cmdrec->exact) match = !strcasecmp(cmd, cmdrec->cmdstr);
+ else match = !strncasecmp(cmd, cmdrec->cmdstr, strlen(cmd));
+ if (match) cmdrec->cmdfunc(unum, msg);
+ }
+
+ if (!match) {
+ /* invalid input */
+ sprintf(genbuf, "*** Unknown command '/%s'\n", cmd);
+ send_to_unum(unum, genbuf, -1);
+ }
+
+ memset(users[unum].ibuf, 0, sizeof users[unum].ibuf);
+ return 0;
+}
+
+process_chat_command(unum)
+int unum;
+{
+ register int i;
+ int sd, rc, ibufsize;
+ CHATLINE recbuf;
+
+ sd = users[unum].sockfd;
+#ifdef DEBUG
+ printf("Incoming on fd %d: ", sd);
+#endif
+ if ((rc = recv(sd, recbuf, sizeof recbuf, 0)) <= 0) {
+ /* disconnected */
+ exit_room(unum, EXIT_LOSTCONN, (char *)NULL);
+ return -1;
+ }
+ ibufsize = users[unum].ibufsize;
+ for (i=0; i<rc; i++) {
+ /* if newline is two characters, throw out the first */
+ if (recbuf[i] == '\r') continue;
+
+ /* carriage return signals end of line */
+ else if (recbuf[i] == '\n') {
+ users[unum].ibuf[ibufsize] = '\0';
+ if (command_execute(unum) == -1) return -1;
+ ibufsize = 0;
+ }
+
+ /* add other chars to input buffer unless size limit exceeded */
+ else {
+ if (ibufsize < CHATLINE_TEXT_MAX)
+ users[unum].ibuf[ibufsize++] = recbuf[i];
+ }
+ }
+ users[unum].ibufsize = ibufsize;
+ return 0;
+}
+
+/*ARGSUSED*/
+exit_chatd(rc, str)
+int rc;
+char *str;
+{
+ int i;
+#ifdef DEBUG
+ printf("Server exit: %s\n", str);
+#endif
+ close(sock);
+ unlink(PATH_CHATPORT);
+ unlink(PATH_CHATPID);
+ utable_detach(0);
+ if (rc) {
+ for (i=0; i<bbs_max_users; i++)
+ if (users[i].sockfd != -1) close(users[i].sockfd);
+ }
+ free(users);
+ free(rooms);
+ exit(rc);
+}
+
+void
+sig_catcher(sig)
+{
+ char msg[80];
+ sprintf(msg, "shutting down due to signal %d\n", sig);
+ signal(sig, SIG_DFL);
+ exit_chatd(1, msg);
+}
+
+_write_daemoninfo(fname, number)
+char *fname;
+unsigned short number;
+{
+ FILE *fp;
+ if ((fp = fopen(fname, "w")) == NULL) {
+ fprintf(stderr, "cannot open %s\n", fname);
+ perror("fopen");
+ exit_chatd(1, "cannot write chatport or chatpid file");
+ }
+ fprintf(fp, "%04x\n", number);
+ fclose(fp);
+}
+
+main(argc, argv)
+int argc;
+char *argv[];
+{
+ struct sockaddr_in sin;
+ register int i;
+ int pid, sr, newsock, sinsize;
+ fd_set readfds;
+ struct timeval *tvptr = NULL;
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s numusers\n" ,argv[0]);
+ return 1;
+ }
+
+ bbs_max_users = atoi(argv[1]);
+
+ if (bbs_max_users == 0) {
+ fprintf(stderr, "maxusers cannot be zero\n");
+ return 1;
+ }
+
+ manager_list = restricted_list = NULL;
+ strcpy(mainroom, "main");
+ chat_init_config();
+
+ users = (struct chatuser *)calloc(bbs_max_users, sizeof(*users));
+ if (users == NULL) {
+ perror("calloc");
+ return 1;
+ }
+ for (i=0; i<bbs_max_users; i++) users[i].sockfd = users[i].utent = -1;
+
+ rooms = (struct chatroom *)calloc(bbs_max_users, sizeof(*rooms));
+ if (rooms == NULL ||
+ (rooms[0].invites = (char *)malloc(bbs_max_users)) == NULL) {
+ perror("calloc");
+ return 1;
+ }
+ strcpy(rooms[0].name, mainroom);
+
+ if (utable_attach(bbs_max_users) != S_OK) {
+ fprintf(stderr, "cannot attach to utable\n");
+ return 1;
+ }
+
+ if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
+ perror("socket");
+ exit_chatd(1, "socket failed");
+ }
+
+ sin.sin_family = AF_INET;
+ sin.sin_port = 0;
+ sin.sin_addr.s_addr = INADDR_ANY;
+
+ if (bind(sock, (struct sockaddr *)&sin, sizeof sin) == -1) {
+ perror("bind");
+ exit_chatd(1, "bind failed");
+ }
+
+ sinsize = sizeof sin;
+ if (getsockname(sock, (struct sockaddr *)&sin, &sinsize) == -1) {
+ perror("getsockname");
+ exit_chatd(1, "getsockname failed");
+ }
+
+ if (listen(sock, SOMAXCONN) == -1) {
+ perror("listen");
+ exit_chatd(1, "listen failed");
+ }
+
+ /* race condition here if two daemons start up at once? */
+ _write_daemoninfo(PATH_CHATPORT, htons(sin.sin_port));
+
+#ifndef DEBUG
+ switch (pid = fork()) {
+ case -1:
+ perror("fork");
+ exit_chatd(1, "fork failed");
+ case 0:
+ setpgid(0, 0);
+ break;
+ default:
+ /* Assuming that a pid fits into a ushort may be a bad idea. */
+ _write_daemoninfo(PATH_CHATPID, (unsigned short)pid);
+ return 0;
+ }
+#else
+ printf("Server bound to port %d\n", htons(sin.sin_port));
+#endif
+
+ signal(SIGHUP, sig_catcher);
+ signal(SIGINT, sig_catcher);
+#ifndef DEBUG
+ signal(SIGQUIT, sig_catcher);
+#endif
+ signal(SIGILL, sig_catcher);
+ signal(SIGIOT, sig_catcher);
+#ifdef SIGEMT
+ signal(SIGEMT, sig_catcher);
+#endif
+ signal(SIGFPE, sig_catcher);
+#ifdef SIGBUS
+ signal(SIGBUS, sig_catcher);
+#endif
+ signal(SIGSEGV, sig_catcher);
+#ifdef SIGSYS
+ signal(SIGSYS, sig_catcher);
+#endif
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGTERM, sig_catcher);
+ signal(SIGALRM, SIG_IGN);
+#ifdef SIGIO
+ signal(SIGIO, SIG_IGN);
+#endif
+#ifdef SIGURG
+ signal(SIGURG, SIG_IGN);
+#endif
+#ifdef SIGPWR
+ signal(SIGPWR, sig_catcher);
+#endif
+
+ FD_ZERO(&allfds);
+ FD_SET(sock, &allfds);
+ nfds = sock+1;
+
+ while (1) {
+ memcpy(&readfds, &allfds, sizeof readfds);
+#ifdef DEBUG
+ printf("Selecting...");
+#endif
+ sr = select(nfds, &readfds, NULL, NULL, tvptr);
+#ifdef DEBUG
+ printf("done: returned %d\n", sr);
+#endif
+ if (sr == 0) {
+ exit_chatd(0, "normal chat server shutdown");
+ }
+
+ if (sr == -1) {
+ if (errno == EINTR) continue;
+ else {
+ exit_chatd(1, "select failed");
+ }
+ }
+
+ if (tvptr) tvptr = NULL;
+
+ if (FD_ISSET(sock, &readfds)) {
+ sinsize = sizeof sin;
+ newsock = accept(sock, (struct sockaddr *)&sin, &sinsize);
+ if (newsock == -1) {
+#ifdef DEBUG
+ printf("accept failed: errno %d\n", errno);
+#endif
+ continue;
+ }
+ for (i=0; i<bbs_max_users; i++) {
+ if (users[i].sockfd == -1) {
+ users[i].sockfd = newsock;
+ users[i].room = -1;
+ users[i].ibufsize = 0;
+#ifdef DEBUG
+ printf("Connection to slot %2d, fd %d\n", i, newsock);
+#endif
+ break;
+ }
+ }
+ if (i >= bbs_max_users) {
+ /* full -- no more chat users */
+ close(newsock);
+ }
+ else {
+#ifndef RELIABLE_SELECT_FOR_WRITE
+ int flags = fcntl(newsock, F_GETFL, 0);
+ flags |= O_NDELAY;
+ fcntl(newsock, F_SETFL, flags);
+#endif
+ FD_SET(newsock, &allfds);
+ if (newsock >= nfds) nfds = newsock+1;
+ num_conns++;
+ }
+ }
+
+ for (i=0; i<bbs_max_users; i++) {
+ /* we are done with newsock, so re-use the variable */
+ newsock = users[i].sockfd;
+ if (newsock != -1 && FD_ISSET(newsock, &readfds)) {
+ if (process_chat_command(i) == -1) {
+ logout_user(i); /* this clear the bit in allfds & dec. num_conns */
+ }
+ }
+ }
+
+ if (num_conns <= 0) {
+ /* one more pass at select, then we go bye-bye */
+ tvptr = &zerotv;
+ }
+ }
+ /*NOTREACHED*/
+ return 0;
+}
+
diff --git a/client.c b/client.c
new file mode 100644
index 0000000..3eedd30
--- /dev/null
+++ b/client.c
@@ -0,0 +1,449 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "client.h"
+#include <signal.h>
+#include <time.h>
+#ifdef REMOTE_CLIENT
+# include <pwd.h>
+# include <stdlib.h>
+#endif
+
+BBSINFO serverinfo; /* server's info structure */
+LOGININFO myinfo; /* user's info structure */
+
+PATH c_tempfile; /* work file for the client side */
+int input_active; /* for the idle timer */
+
+#ifdef REMOTE_CLIENT
+char *tmpdir;
+char *shell;
+char *pager;
+char *editor;
+char *termtype;
+char *charset;
+
+extern char *optarg;
+extern int optind;
+#endif
+
+extern PromptForAccountInfo();
+extern SetPermTable();
+extern char *_menudesc_file;
+extern NDoMenu();
+extern void page_handler __P((int));
+extern char *Ctime __P((time_t *));
+
+bbperror(code, str)
+LONG code;
+char *str;
+{
+ char *errstr;
+ if (code > S_MAXERROR) errstr = "unknown error";
+ else errstr = bb_errlist[code];
+ if (str == NULL) prints("%s\n", errstr);
+ else prints("%s: %s\n", str, errstr);
+ return 0;
+}
+
+disconnect(status)
+int status;
+{
+ unlink(c_tempfile);
+ bbs_disconnect();
+ if (fullscreen()) {
+ clear();
+ refresh();
+ }
+ else oflush();
+ reset_tty();
+ printf("\n");
+ switch (status) {
+ case EXIT_LOGOUT:
+ printf("Goodbye!\n");
+#ifdef AUX
+ clear();
+#endif
+ break;
+ case EXIT_CLIERROR:
+ printf("Something's wrong. Exiting.\n");
+ break;
+ case EXIT_LOSTCONN:
+ printf("Server closed connection. Exiting.\n");
+ break;
+ case EXIT_TIMEDOUT:
+ printf("Idle timeout exceeded. Exiting.\n");
+ break;
+ default: /* assumed to be a signal */
+ if (g_child_pid != -1) {
+ kill(g_child_pid, status);
+ }
+ switch (status) {
+ case SIGHUP: break;
+ case SIGINT:
+ printf("Interrupt signal received. Goodbye!\n");
+ break;
+ case SIGQUIT:
+ printf("Quit signal received. Goodbye!\n");
+ break;
+ case SIGTERM:
+ printf("Terminate signal received. Goodbye!\n");
+ break;
+ default:
+ printf("Bad news: signal %d received. Run! Flee!\n", status);
+ break;
+ }
+ break;
+ }
+ exit(status);
+}
+
+generic_abort() /* general "bail and exit" */
+{
+ disconnect(EXIT_CLIERROR);
+}
+
+#ifdef REMOTE_CLIENT
+bbslib_abort()
+{
+ disconnect(EXIT_LOSTCONN);
+}
+#endif
+
+void
+sig_handler(sig)
+int sig;
+{
+ disconnect(sig == SIGALRM ? EXIT_TIMEDOUT : sig);
+}
+
+void
+hit_alarm_clock()
+{
+ if (!input_active) {
+ disconnect(EXIT_TIMEDOUT);
+ }
+ input_active = 0;
+ signal(SIGALRM, hit_alarm_clock);
+ if (myinfo.idletimeout) alarm((int)myinfo.idletimeout*60);
+}
+
+set_idle_alarm()
+{
+ if (myinfo.idletimeout != 0) {
+ signal(SIGALRM, hit_alarm_clock);
+ alarm((int)myinfo.idletimeout*60);
+ }
+}
+
+cancel_idle_alarm()
+{
+ alarm(0);
+}
+
+void
+InitializeTerminal()
+{
+ TERM t;
+#ifdef REMOTE_CLIENT
+ if (termtype == NULL) {
+#else /* !REMOTE_CLIENT */
+ ACCOUNT acct;
+ if (bbs_owninfo(&acct) != S_OK) {
+#endif
+ prints("Can't determine your terminal type!\n");
+ prints("Defaulting to 'dumb'.\n");
+ term_init("dumb");
+ return;
+ }
+#ifdef REMOTE_CLIENT
+ strncpy(t, termtype, TERMLEN);
+#else
+ strncpy(t, acct.terminal, TERMLEN);
+#endif
+ if (term_init(t) == -1) {
+ prints("I don't know about '%s' terminals!\n", t);
+ prints("Defaulting terminal type to 'dumb'.\n");
+ term_init("dumb");
+ }
+}
+
+void
+InitializeCharset()
+{
+ CSET c;
+#ifdef REMOTE_CLIENT
+ if (charset == NULL) {
+#else /* !REMOTE_CLIENT */
+ ACCOUNT acct;
+ if (bbs_owninfo(&acct) != S_OK) {
+#endif
+ prints("Can't determine your character set!\n");
+ prints("Defaulting to 'ascii'.\n");
+ conv_init("ascii");
+ return;
+ }
+#ifdef REMOTE_CLIENT
+ strncpy(c, charset, TERMLEN);
+#else
+ strncpy(c, acct.charset, TERMLEN);
+#endif
+
+ if (conv_init(c) == -1) {
+ prints("I don't know about '%s' charset!\n", c);
+ prints("Defaulting character set to 'ascii'.\n");
+ conv_init("ascii");
+ }
+}
+
+Login(newok)
+SHORT newok;
+{
+ ACCOUNT acct;
+ int rc;
+
+ char *ustr = (newok ? "userid ('new' for new user): " : "userid: ");
+ alarm(60); /* You get one minute to log in succesfully */
+ do {
+ if (getdata(0, 0, ustr, acct.userid, sizeof(acct.userid), DOECHO, 1) == -1)
+ disconnect(EXIT_LOGOUT);
+
+ if (newok && !strcmp(acct.userid, "new")) {
+ /* Give them five minutes to enter new user data */
+ alarm(300);
+ PromptForAccountInfo(&acct, 1);
+ rc = bbs_newlogin(&acct, &myinfo);
+ }
+ else {
+ getdata(0, 0, "Password: ", acct.passwd, sizeof(acct.passwd), NOECHO, 0);
+ rc = bbs_login(acct.userid, acct.passwd, 0, &myinfo);
+ }
+login_switch:
+ if (rc != S_OK) {
+ prints("\n");
+ bbperror(rc, NULL);
+ prints("\n");
+ switch (rc) {
+ case S_FULL:
+ case S_ACCTDISABLED:
+ case S_LOGINLIMIT:
+ disconnect(EXIT_LOGOUT);
+ break;
+ case S_LOGINMUSTBOOT:
+ {
+ char ans[4];
+ getdata(0,0,"Kill other login (Y/N)? [Y]: ",ans,sizeof ans,DOECHO,0);
+ if (*ans == 'N' || *ans == 'n') disconnect(EXIT_LOGOUT);
+ rc = bbs_login(acct.userid, acct.passwd, 1, &myinfo);
+ goto login_switch;
+ }
+ }
+ }
+ } while (rc != S_OK);
+ alarm(0);
+ set_idle_alarm();
+ return 0;
+}
+
+#ifdef REMOTE_CLIENT
+usage(prog)
+char *prog;
+{
+ fprintf(stderr,
+ "Usage: %s [-c charset] [-d tmpdir] [-e editor] [-m menu-file]\n", prog);
+ fprintf(stderr,
+ " [-p pager] [-s shell] [-t terminal-type] [bbs-server] [port]\n");
+}
+#endif
+
+main(argc, argv)
+int argc;
+char *argv[];
+{
+ int rc;
+ PATH fname;
+ char *bbshomedir = NULL;
+ char *servername = "localhost";
+ SHORT port = EBBS_PORT;
+ INITINFO initinfo;
+
+#ifdef REMOTE_CLIENT
+ int c;
+ struct passwd *pw;
+
+ while ((c = getopt(argc, argv, "c:d:e:m:p:s:t:?")) != -1) {
+ switch (c) {
+ case 'c':
+ charset = optarg;
+ break;
+ case 'd':
+ tmpdir = optarg;
+ break;
+ case 'e':
+ editor = optarg;
+ break;
+ case 'm':
+ _menudesc_file = optarg;
+ break;
+ case 'p':
+ pager = optarg;
+ break;
+ case 's':
+ shell = optarg;
+ break;
+ case 't':
+ termtype = optarg;
+ break;
+ case '?':
+ usage(argv[0]);
+ return 2;
+ }
+ }
+
+ if (optind < argc) servername = argv[optind];
+ if (optind+1 < argc) port = (SHORT)atoi(argv[optind+1]);
+
+ if (shell == NULL) shell = getenv("SHELL");
+ if (shell == NULL) shell = "/bin/sh";
+
+ if (tmpdir == NULL) tmpdir = getenv("TMPDIR");
+ if (tmpdir == NULL) tmpdir = "/tmp";
+
+ if (pager == NULL) pager = getenv("BBS_PAGER");
+ if (editor == NULL) editor = getenv("BBS_EDITOR");
+ if (termtype == NULL) termtype = getenv("TERM");
+ if (_menudesc_file == NULL) _menudesc_file = ".ebbsmenu";
+
+ if ((pw = getpwuid(getuid())) != NULL)
+ bbshomedir = pw->pw_dir;
+
+ initinfo.abortfn = bbslib_abort;
+ initinfo.tmpdir = tmpdir;
+#else
+ _menudesc_file = "etc/menu.desc";
+ initinfo.abortfn = NULL;
+ initinfo.tmpdir = NULL;
+#endif
+
+ if (get_tty() == -1) {
+ perror("tty");
+ fprintf(stderr, "Can't get terminal settings!\n");
+ return 1;
+ }
+
+ home_bbs(bbshomedir);
+ if ((rc = bbs_initialize(&initinfo)) != S_OK) {
+ bbperror(rc, "bbs_initialize");
+ oflush();
+ return 1;
+ }
+
+ if ((rc = bbs_connect(servername, port, &serverinfo)) != S_OK) {
+ bbperror(rc, "bbs_connect");
+ oflush();
+ return 1;
+ }
+
+#ifdef REMOTE_CLIENT
+ sprintf(c_tempfile, "%s/bbs%05d", tmpdir, getpid());
+#else
+ sprintf(c_tempfile, "tmp/bbl%05d", getpid());
+#endif
+
+ signal(SIGHUP, sig_handler);
+ signal(SIGINT, sig_handler);
+ signal(SIGQUIT, sig_handler);
+ signal(SIGILL, sig_handler);
+ signal(SIGIOT, sig_handler);
+#ifdef SIGEMT
+ signal(SIGEMT, sig_handler);
+#endif
+ signal(SIGFPE, sig_handler);
+#ifdef SIGBUS
+ signal(SIGBUS, sig_handler);
+#endif
+ signal(SIGSEGV, sig_handler);
+#ifdef SIGSYS
+ signal(SIGSYS, sig_handler);
+#endif
+ signal(SIGPIPE, sig_handler);
+ signal(SIGTERM, sig_handler);
+ signal(SIGALRM, sig_handler);
+ signal(SIGCHLD, SIG_DFL);
+#ifdef SIGIO
+ signal(SIGIO, SIG_IGN);
+#endif
+#ifdef SIGURG
+ signal(SIGURG, SIG_IGN);
+#endif
+#ifdef SIGPWR
+ signal(SIGPWR, sig_handler);
+#endif
+
+#ifndef REMOTE_CLIENT
+ /* This is how the local client gets paged -- via SIGUSR1. */
+ signal(SIGUSR1, page_handler);
+#endif
+
+ init_tty();
+
+ prints("\nWelcome to %s\n", serverinfo.boardname);
+ if (bbs_get_issue(fname) == S_OK) {
+ FILE *fp = fopen(fname, "r");
+ char buf[80];
+ if (fp) {
+ while (fgets(buf, sizeof(buf), fp))
+ prints(buf);
+ fclose(fp);
+ }
+ }
+
+ Login(serverinfo.newok);
+ SetPermTable();
+
+ if (ParseMenu() == -1) {
+ disconnect(EXIT_LOGOUT);
+ }
+
+ InitializeCharset();
+
+ InitializeTerminal();
+ initscr(); /* initscr() HAS to follow InitializeTerminal */
+
+ /* use this if time is not being set -- putenv("TZ=CST6CDT"); tzset(); */
+
+ clear();
+ if (bbs_get_welcome(fname) == S_OK) {
+ if (myinfo.lastlogin == 0) More(fname, 1);
+ else {
+ char buf[80], host[21], ans;
+ memset(host, '\0', sizeof host);
+ strncpy(host, myinfo.fromhost, sizeof(host)-1);
+ More(fname, 0);
+ sprintf(buf, "Last login %s from %s [RETURN]: ",
+ Ctime((time_t *)&myinfo.lastlogin), host);
+ getdata(t_lines-1, 0, buf, &ans, 1, NOECHO, 0);
+ }
+ }
+
+ NDoMenu("Main") ; /* Start at the Main Menu */
+
+ disconnect(EXIT_LOGOUT);
+}
diff --git a/client.h b/client.h
new file mode 100644
index 0000000..8181e6c
--- /dev/null
+++ b/client.h
@@ -0,0 +1,220 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "common.h"
+#include "perms.h"
+#include <stdio.h>
+
+#define CLIENT_VERSION_MAJ 0
+#define CLIENT_VERSION_MIN 1
+
+/*
+ One day there will be a different libbbs.a that will be the client side
+ of a remote bbs service. The entry points will just pack the parameters
+ into network form and ship them across to the server, who will unpack
+ them and call the local_bbs_* functions to do the work.
+
+ For the local libbbs.a, we can just #define all the entry points to the
+ library to be the local_bbs_* functions.
+*/
+
+#ifndef REMOTE_CLIENT
+
+#define bbs_initialize local_bbs_initialize
+#define bbs_connect local_bbs_connect
+#define bbs_disconnect local_bbs_disconnect
+#define bbs_get_issue local_bbs_get_issue
+#define bbs_get_welcome local_bbs_get_welcome
+#define bbs_newlogin local_bbs_newlogin
+#define bbs_login local_bbs_login
+#define bbs_add_account local_bbs_add_account
+#define bbs_delete_account local_bbs_delete_account
+#define bbs_query local_bbs_query
+#define bbs_owninfo local_bbs_owninfo
+#define bbs_get_userinfo local_bbs_get_userinfo
+#define bbs_set_mode local_bbs_set_mode
+#define bbs_set_passwd local_bbs_set_passwd
+#define bbs_set_username local_bbs_set_username
+#define bbs_set_terminal local_bbs_set_terminal
+#define bbs_set_charset local_bbs_set_charset
+#define bbs_set_email local_bbs_set_email
+#define bbs_modify_perms local_bbs_modify_perms
+#define bbs_modify_account local_bbs_modify_account
+#define bbs_enum_accounts local_bbs_enum_accounts
+#define bbs_enum_users local_bbs_enum_users
+#define bbs_acctnames local_bbs_acctnames
+#define bbs_usernames local_bbs_usernames
+#define bbs_get_info local_bbs_get_info
+#define bbs_get_license local_bbs_get_license
+#define bbs_get_plan local_bbs_get_plan
+#define bbs_set_plan local_bbs_set_plan
+#define bbs_toggle_cloak local_bbs_toggle_cloak
+#define bbs_toggle_exempt local_bbs_toggle_exempt
+#define bbs_get_permstrings local_bbs_get_permstrings
+#define bbs_check_mail local_bbs_check_mail
+#define bbs_mail local_bbs_mail
+#define bbs_open_mailbox local_bbs_open_mailbox
+#define bbs_close_board local_bbs_close_board
+#define bbs_enum_headers local_bbs_enum_headers
+#define bbs_read_message local_bbs_read_message
+#define bbs_delete_message local_bbs_delete_message
+#define bbs_delete_range local_bbs_delete_range
+#define bbs_mark_message local_bbs_mark_message
+#define bbs_set_welcome local_bbs_set_welcome
+#define bbs_add_board local_bbs_add_board
+#define bbs_delete_board local_bbs_delete_board
+#define bbs_get_board local_bbs_get_board
+#define bbs_modify_board local_bbs_modify_board
+#define bbs_enum_boards local_bbs_enum_boards
+#define bbs_boardnames local_bbs_boardnames
+#define bbs_visit_board local_bbs_visit_board
+#define bbs_post local_bbs_post
+#define bbs_open_board local_bbs_open_board
+#define bbs_test_board local_bbs_test_board
+#define bbs_update_message local_bbs_update_message
+#define bbs_zap_board local_bbs_zap_board
+#define bbs_get_boardmgrs local_bbs_get_boardmgrs
+#define bbs_set_boardmgrs local_bbs_set_boardmgrs
+#define bbs_enum_fileboards local_bbs_enum_fileboards
+#define bbs_fileboardnames local_bbs_fileboardnames
+#define bbs_open_fileboard local_bbs_open_fileboard
+#define bbs_protonames local_bbs_protonames
+#define bbs_set_protocol local_bbs_set_protocol
+#define bbs_upload local_bbs_upload
+#define bbs_download local_bbs_download
+#define bbs_chat local_bbs_chat
+#define bbs_exit_chat local_bbs_exit_chat
+#define bbs_chat_send local_bbs_chat_send
+#define bbs_kick_user local_bbs_kick_user
+#define bbs_talk local_bbs_talk
+#define bbs_exit_talk local_bbs_exit_talk
+#define bbs_get_talk_request local_bbs_get_talk_request
+#define bbs_accept_page local_bbs_accept_page
+#define bbs_refuse_page local_bbs_refuse_page
+#define bbs_set_pager local_bbs_set_pager
+#define bbs_get_overrides local_bbs_get_overrides
+#define bbs_set_overrides local_bbs_set_overrides
+#define bbs_forward_message local_bbs_forward_message
+#define bbs_forward_file local_bbs_forward_file
+#define bbs_set_editor local_bbs_set_editor
+#define bbs_get_editor local_bbs_get_editor
+#define bbs_enum_editors local_bbs_enum_editors
+#define bbs_get_signature local_bbs_get_signature
+#define bbs_set_signature local_bbs_set_signature
+#define bbs_get_modestrings local_bbs_get_modestrings
+#define bbs_get_modechars local_bbs_get_modechars
+#define bbs_set_cliopts local_bbs_set_cliopts
+#define bbs_change_fileboard_dir local_bbs_change_fileboard_dir
+#define bbs_move_message local_bbs_move_message
+
+#endif /* !REMOTE_CLIENT */
+
+/* Function prototypes for libbbs entry points. */
+
+int bbs_initialize __P((INITINFO *));
+int bbs_connect __P((char *, SHORT, BBSINFO *));
+int bbs_disconnect __P((void));
+int bbs_get_issue __P((char *));
+int bbs_get_welcome __P((char *));
+int bbs_newlogin __P((ACCOUNT *, LOGININFO *));
+int bbs_login __P((char *, char *, SHORT, LOGININFO *));
+int bbs_add_account __P((ACCOUNT *, SHORT));
+int bbs_delete_account __P((char *));
+int bbs_query __P((char *, ACCOUNT *));
+int bbs_owninfo __P((ACCOUNT *));
+int bbs_get_userinfo __P((char *, ACCOUNT *));
+int bbs_set_mode __P((SHORT));
+int bbs_set_passwd __P((char *));
+int bbs_set_username __P((char *));
+int bbs_set_terminal __P((char *));
+int bbs_set_charset __P((char *));
+int bbs_set_email __P((char *));
+int bbs_modify_perms __P((char *, LONG));
+int bbs_modify_account __P((char *, ACCOUNT *, SHORT));
+int bbs_enum_accounts __P((SHORT, SHORT, int(), void *));
+int bbs_enum_users __P((SHORT, SHORT, char *, int(), void *));
+int bbs_acctnames __P((NAMELIST *, char *));
+int bbs_usernames __P((NAMELIST *, char *));
+int bbs_get_info __P((char *));
+int bbs_get_license __P((char *));
+int bbs_get_plan __P((char *, char *));
+int bbs_set_plan __P((char *));
+int bbs_toggle_cloak __P((void));
+int bbs_toggle_exempt __P((char *));
+int bbs_get_permstrings __P((char **));
+int bbs_check_mail __P((void));
+int bbs_mail __P((char *, char *, NAMELIST, char *, char *, LONG *));
+int bbs_open_mailbox __P((OPENINFO *));
+int bbs_close_board __P((void));
+int bbs_enum_headers __P((SHORT, SHORT, SHORT, int(), void *));
+int bbs_read_message __P((SHORT, char *));
+int bbs_delete_message __P((SHORT));
+int bbs_delete_range __P((SHORT, SHORT, SHORT *));
+int bbs_mark_message __P((SHORT, SHORT));
+int bbs_set_welcome __P((char *));
+int bbs_add_board __P((BOARD *));
+int bbs_delete_board __P((char *));
+int bbs_get_board __P((char *, BOARD *));
+int bbs_modify_board __P((char *, BOARD *, SHORT));
+int bbs_enum_boards __P((SHORT, SHORT, SHORT, int(), void *));
+int bbs_boardnames __P((NAMELIST *, char *));
+int bbs_visit_board __P((char *));
+int bbs_post __P((char *, char *, char *));
+int bbs_open_board __P((char *, OPENINFO *));
+int bbs_test_board __P((char *, SHORT *));
+int bbs_update_message __P((SHORT, char *));
+int bbs_zap_board __P((char *, SHORT));
+int bbs_get_boardmgrs __P((char *, NAMELIST *));
+int bbs_set_boardmgrs __P((char *, NAMELIST));
+int bbs_enum_fileboards __P((SHORT, SHORT, int(), void *));
+int bbs_fileboardnames __P((NAMELIST *, char *));
+int bbs_open_fileboard __P((char *, OPENINFO *));
+int bbs_protonames __P((NAMELIST *, char *));
+int bbs_set_protocol __P((char *));
+int bbs_upload __P((char *, char *, char *));
+int bbs_download __P((char *, char *, char *));
+int bbs_chat __P((char *, LONG *));
+int bbs_exit_chat __P((void));
+int bbs_chat_send __P((char *));
+int bbs_kick_user __P((LONG));
+int bbs_talk __P((LONG, LONG, LONG *));
+int bbs_exit_talk __P((void));
+int bbs_get_talk_request __P((USEREC *, LONG *, SHORT *));
+int bbs_accept_page __P((LONG, SHORT, LONG *));
+int bbs_refuse_page __P((LONG, SHORT));
+int bbs_set_pager __P((SHORT, SHORT));
+int bbs_get_overrides __P((NAMELIST *));
+int bbs_set_overrides __P((NAMELIST));
+int bbs_forward_message __P((SHORT));
+int bbs_forward_file __P((char *));
+int bbs_set_editor __P((char *));
+int bbs_get_editor __P((char *, char *, char *));
+int bbs_enum_editors __P((NAMELIST *, char *));
+int bbs_get_signature __P((char *));
+int bbs_set_signature __P((char *));
+int bbs_get_modestrings __P((char **));
+int bbs_get_modechars __P((char *));
+int bbs_set_cliopts __P((SHORT));
+int bbs_change_fileboard_dir __P((char *, OPENINFO *));
+int bbs_move_message __P((SHORT, char *));
+
+/* Now for the UI-dependent stuff. */
+
+#include "clientui.h"
diff --git a/clientui.h b/clientui.h
new file mode 100644
index 0000000..5ee5bc5
--- /dev/null
+++ b/clientui.h
@@ -0,0 +1,129 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "pbbs/io.h"
+
+/*
+ Header file for the user interface. This plus the remote libbbs will
+ make up the client in a future version.
+*/
+
+#if COLOR
+/* ANSI Color constants */
+# define COLOR_BLACK 0
+# define COLOR_RED 1
+# define COLOR_GREEN 2
+# define COLOR_ORANGE 3
+# define COLOR_DARKBLUE 4
+# define COLOR_PURPLE 5
+# define COLOR_LIGHTBLUE 6
+# define COLOR_WHITE 7
+#endif
+
+/* Flags for the menu commands */
+
+#define DONOTHING 0
+#define FULLUPDATE 0001
+#define PARTUPDATE 0002
+#define FETCHNEW 0004
+#define NEWDIRECT 0010
+#define EXITMENU 0020
+#define MENUERROR 0040
+#define NOCLOSE 0100
+
+struct enum_info {
+ int count;
+ int topline;
+ int bottomline;
+ int currline;
+ int totals[2]; /* general purpose counters */
+};
+
+/* sleep time between Monitor refreshes */
+#define MONITOR_REFRESH 10
+
+/* Additions for new_menu items */
+
+#define MAXMENUSZ (26)
+#define MAXMENUDEPTH (5)
+
+#define MENU_UP (16)
+#define MENU_DOWN (14)
+
+typedef struct _NMENUITEM {
+ char *name ;
+ int enabled ;
+ char *default_action ;
+ char *error_action ;
+ int (*action_func)() ;
+ char *action_arg ;
+ char *help ;
+ struct _NMENUITEM *prev ;
+ struct _NMENUITEM *next ;
+} NMENUITEM ;
+
+typedef struct _NMENU {
+ char *menu_id ;
+ char *menu_title ;
+ char *menu_default ;
+ char *menu_prompt ;
+ NMENUITEM *menucommands[MAXMENUSZ] ;
+ NMENUITEM *commlist ;
+ struct _NMENU *next ;
+} NMENU ;
+
+typedef struct _NREADMENUITEM {
+ int key ;
+ int mainprivs ;
+ int boardprivs ;
+ int (*action_func)() ;
+ char *help ;
+ struct _NREADMENUITEM *next ;
+} NREADMENUITEM ;
+
+typedef struct _NREADMENU {
+ char *menu_helptitle ;
+ char *menu_title ;
+ char *menu_message ;
+ char *menu_line2 ;
+ char *menu_line3 ;
+ char *menu_field1 ;
+ char *menu_field2 ;
+ char *menu_field3 ;
+ char *menu_field4 ;
+ NREADMENUITEM *commlist ;
+} NREADMENU ;
+
+/* A few global variables */
+extern LOGININFO myinfo;
+extern char c_tempfile[];
+extern NAME currboard;
+extern NAME currfileboard;
+extern NREADMENU *PostReadMenu, *MailReadMenu, *FileReadMenu;
+extern char *bb_errlist[];
+
+/* Functions to check for page requests */
+extern int PagePending __P((void));
+extern int NewPagePending __P((void));
+
+/* Other common functions */
+extern char ModeToChar __P((SHORT));
+extern char *ModeToString __P((SHORT));
+extern int bbperror __P((LONG, char *));
diff --git a/clntcmds.h b/clntcmds.h
new file mode 100644
index 0000000..c5b17d5
--- /dev/null
+++ b/clntcmds.h
@@ -0,0 +1,128 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+
+/*
+ All of the interface points between the bbs client and server parts
+ are listed here. These must not be changed, but new ones can be added
+ up to MAX_CLNTCMDS.
+*/
+
+#define C_INIT 0
+#define C_CONNECT 1
+#define C_DISCONNECT 2
+#define C_ISSUE 3
+#define C_WELCOME 4
+#define C_NEWLOGIN 5
+#define C_LOGIN 6
+#define C_ADDACCT 7
+#define C_DELACCT 8
+#define C_QUERY 9
+#define C_OWNACCT 10
+#define C_GETACCT 11
+#define C_SETMODE 12
+#define C_SETPASSWD 13
+#define C_SETUSERNAME 14
+#define C_SETTERMINAL 15
+#define C_SETEMAIL 16
+#define C_SETPERMS 17
+#define C_SETACCT 18
+#define C_ALLUSERS 19
+#define C_USERS 20
+#define C_ALLNAMES 21
+#define C_NAMES 22
+#define C_INFOFILE 23
+#define C_LICENSE 24
+#define C_GETPLAN 25
+#define C_SETPLAN 26
+#define C_CLOAK 27
+#define C_EXEMPT 28
+#define C_GETPERMSTRS 29
+#define C_CHKMAIL 30
+#define C_MAIL 31
+#define C_OPENMAIL 32
+#define C_CLOSEBRD 33
+#define C_ENUMHDRS 34
+#define C_READMSG 35
+#define C_DELMSG 36
+#define C_DELRANGE 37
+#define C_MARKMSG 38
+#define C_SETWELCOME 39
+#define C_ADDBOARD 40
+#define C_DELBOARD 41
+#define C_GETBOARD 42
+#define C_SETBOARD 43
+#define C_ENUMBRDS 44
+#define C_BNAMES 45
+#define C_VISITBRD 46
+#define C_POST 47
+#define C_OPENBRD 48
+#define C_TESTBRD 49
+#define C_REPLACEMSG 50
+#define C_ZAPBOARD 51
+#define C_GETBMGRLIST 52
+#define C_SETBMGRLIST 53
+#define C_ENUMFBRDS 54
+#define C_FBNAMES 55
+#define C_OPENFBRD 56
+#define C_PROTONAMES 57
+#define C_SETPROTO 58
+#define C_UPLOAD 59
+#define C_DOWNLOAD 60
+#define C_CHAT 61
+#define C_EXITCHAT 62
+#define C_CHATLINE 63
+#define C_KICK 64
+#define C_TALK 65
+#define C_EXITTALK 66
+#define C_GETTALKREQ 67
+#define C_TALKACCEPT 68
+#define C_TALKREFUSE 69
+#define C_SETPAGER 70
+#define C_GETOVERLIST 71
+#define C_SETOVERLIST 72
+#define C_FORWARDMSG 73
+#define C_FORWARDFILE 74
+#define C_SETEDITOR 75
+#define C_GETEDITOR 76
+#define C_ENUMEDITORS 77
+#define C_GETSIGFILE 78
+#define C_SETSIGFILE 79
+#define C_GETMODESTRS 80
+#define C_GETMODECHRS 81
+#define C_SETCLIOPTS 82
+#define C_SEEALLAINFO 83
+#define C_SEEALLBINFO 84
+#define C_ALLBOARDMGR 85
+#define C_SEECLOAK 86
+#define C_SEEREALNAME 87
+#define C_NOTIMEOUT 88
+#define C_USERESERVED 89
+#define C_EXTERNMAIL 90
+#define C_ADMINMENU 91
+#define C_FILEMENU 92
+#define C_MAILMENU 93
+#define C_TALKMENU 94
+#define C_XYZMENU 95
+#define C_SETCHARSET 96
+#define C_FILECHDIR 97
+#define C_MOVEMESSAGE 98
+
+#define MAX_CLNTCMDS 256
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..6c25bab
--- /dev/null
+++ b/common.h
@@ -0,0 +1,287 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include "osdeps.h"
+#include "modes.h"
+#include "retval.h"
+#include "clntcmds.h"
+
+/*
+ Common header file for all pieces of the bbs, client and server.
+*/
+
+/* The port bbsd listens on. */
+#define EBBS_PORT 5150
+
+/* Exit values for lbbs. */
+
+#define EXIT_LOGOUT 0
+#define EXIT_LOSTCONN -1
+#define EXIT_CLIERROR -2
+#define EXIT_TIMEDOUT -3
+#define EXIT_KICK -4
+
+/* Lengths for string data items. */
+
+#define BBSNAMELEN 39
+#define NAMELEN 12
+#define PASSLEN 14
+#define UNAMELEN 25
+#define HOSTLEN 39
+#define TERMLEN 11
+#define CSETLEN 11
+#define RNAMELEN 29
+#define ADDRLEN 99
+#define MAILLEN 79
+#define PATHLEN 255
+#define TITLELEN 63
+
+/* I like typedefs. */
+
+typedef char BBSNAME[BBSNAMELEN+1];
+typedef char NAME[NAMELEN+1];
+typedef char PASSWD[PASSLEN+1];
+typedef char UNAME[UNAMELEN+1];
+typedef char HOST[HOSTLEN+1];
+typedef char TERM[TERMLEN+1];
+typedef char CSET[CSETLEN+1];
+typedef char RNAME[RNAMELEN+1];
+typedef char ADDR[ADDRLEN+1];
+typedef char MAIL[MAILLEN+1];
+typedef char PATH[PATHLEN+1];
+typedef char TITLE[TITLELEN+1];
+typedef char ACCESSCODES[MAX_CLNTCMDS];
+
+/* Structures used in the bbs library functions. */
+
+typedef struct _INITINFO {
+ int (*abortfn)();
+ char *tmpdir;
+} INITINFO;
+
+typedef struct _BBSINFO {
+ BBSNAME boardname;
+ SHORT majver;
+ SHORT minver;
+ SHORT newok;
+} BBSINFO;
+
+typedef struct _LOGININFO {
+ NAME userid;
+ SHORT flags;
+ LONG idletimeout;
+ LONG lastlogin;
+ HOST fromhost;
+ ACCESSCODES access;
+} LOGININFO;
+
+typedef struct _ACCOUNT {
+ /* in the passwds file */
+ NAME userid;
+ PASSWD passwd;
+ UNAME username;
+ LONG perms;
+ SHORT flags;
+ /* in home/<userid>/lastlogin */
+ LONG lastlogin;
+ HOST fromhost;
+ /* in home/<userid>/profile */
+ TERM terminal;
+ CSET charset;
+ RNAME realname;
+ ADDR address;
+ MAIL email;
+ NAME protocol;
+ NAME editor;
+} ACCOUNT;
+
+typedef struct _USEREC {
+ NAME userid;
+ UNAME username;
+ HOST fromhost;
+ LONG pid;
+ SHORT flags;
+ SHORT mode;
+} USEREC;
+
+/* Account and userec flags. */
+#define FLG_CLOAK 0x001 /* Invisibility */
+#define FLG_EXEMPT 0x002 /* For use by user clean utilities */
+#define FLG_DISABLED 0x004 /* Account is temporarily disabled */
+#define FLG_SHARED 0x008 /* Account does not belong to one user */
+#define FLG_NOPAGE 0x010 /* User not accepting page requests... */
+#define FLG_NOOVERRIDE 0x020 /* ...not even friends! */
+#define FLG_EXPERT 0x040 /* Verbose menus or not? */
+
+#ifdef FOR_FUTURE_USE
+/* More userec flags */
+#define FLG_IN_BBS 0x100 /* User is connected to bbs */
+#define FLG_IN_CHAT 0x200 /* User is connected to chat daemon */
+#endif
+
+/* Flags for bbs_modify_account */
+#define MOD_USERID 0x001
+#define MOD_PASSWD 0x002
+#define MOD_USERNAME 0x004
+#define MOD_TERMINAL 0x008
+#define MOD_REALNAME 0x010
+#define MOD_ADDRESS 0x020
+#define MOD_EMAIL 0x040
+#define MOD_PROTOCOL 0x080
+#define MOD_EDITOR 0x100
+#define MOD_CHARSET 0x200
+
+/* Mode constants */
+#define BBS_MAX_MODE 31 /* not including flags */
+#define MODE_FLG_NOPAGE 0x0100 /* must be greater than BBS_MAX_MODE */
+
+/* Max number of recipients for a mail message */
+#define BBS_MAX_MAILRECIPS 32
+
+typedef struct _OPENINFO {
+ NAME name;
+ SHORT flags;
+ LONG totalmsgs;
+ LONG newmsgs;
+} OPENINFO;
+
+/* Flags for OPENINFO */
+#define OPEN_POST 0x001
+#define OPEN_MANAGE 0x002
+#define OPEN_REOPEN 0x004
+
+typedef struct _HEADER {
+ SHORT fileid;
+ ADDR owner;
+ TITLE title;
+ SHORT flags;
+ LONG size;
+ LONG mtime;
+} HEADER;
+
+/* flags for HEADER structures */
+#define FILE_UNREAD 0x1
+#define FILE_MARKED 0x2
+#define FILE_BINARY 0x4
+#define FILE_DIRECTORY 0x8
+
+/* General-purpose board type constants */
+#define BOARD_NONE 0
+#define BOARD_MAIL 1
+#define BOARD_POST 2
+#define BOARD_FILE 3
+
+typedef struct _BOARD {
+ NAME name;
+ TITLE description;
+ LONG readmask;
+ LONG postmask;
+ SHORT flags;
+ LONG totalposts;
+ LONG newposts;
+ LONG ownedposts; /* not supported at the moment! */
+ LONG lastpost;
+} BOARD;
+
+/* Flags for BOARD structs */
+#define BOARD_ZAPPED 0x001
+#define BOARD_NOZAP 0x002
+
+/* Flags for bbs_modify_board. */
+#define MOD_BNAME 0x001
+#define MOD_BOARDDESC 0x002
+#define MOD_READMASK 0x004
+#define MOD_POSTMASK 0x008
+
+/* Flags for bbs_enum_boards. */
+#define BE_UNZAPPED 0x001
+#define BE_ZAPPED 0x002
+#define BE_ALL (BE_ZAPPED | BE_UNZAPPED)
+#define BE_UNDEFINED 0x004
+#define BE_DO_COUNTS 0x008
+
+/* Standard mailer-prefix for Internet mail messages. */
+#define MAILER_PREFIX "INTERNET"
+
+/* Common stuff for chat. */
+#define CHATID_MAX 8
+typedef char CHATID[CHATID_MAX+1];
+
+#define CHATLINE_MAX 255 /* Text plus /cmd at beginning */
+#define CHATLINE_TEXT_MAX 200 /* Max size of test only */
+typedef char CHATLINE[CHATLINE_MAX+1];
+
+#define CHAT_CTRL_CHATID "**C" /* Chatd-->client control message */
+
+/* Initial responses sent by the chat daemon */
+#define CHAT_LOGIN_OK "OK"
+#define CHAT_LOGIN_EXISTS "EX"
+#define CHAT_LOGIN_INVALID "IN"
+#define CHAT_LOGIN_BOGUS "BG"
+
+/* Namelists are very useful. */
+
+typedef struct _NAMENODE {
+ char *word;
+ struct _NAMENODE *next;
+} NAMENODE;
+
+typedef NAMENODE *NAMELIST;
+
+/* Useful bit-manipulation macros. */
+
+#define BITISSET(mask,bit) ((mask)&(bit))
+#define BITSET(mask,bit) ((mask)|=(bit))
+#define BITCLR(mask,bit) ((mask)&=~(bit))
+#define BITTOGGLE(mask,bit) ((mask)^=(bit))
+
+/* Prototypes. */
+
+void strip_trailing_space __P((char *));
+int recursive_rmdir __P((char *));
+int is_valid_userid __P((char *));
+int is_valid_password __P((char *));
+int is_valid_boardname __P((char *));
+LONG hex2LONG __P((char *));
+SHORT hex2SHORT __P((char *));
+char *LONGcpy __P((char *, LONG));
+char *SHORTcpy __P((char *, SHORT));
+int is_text_file __P((char *));
+int is_directory __P((char *));
+
+int is_passwd_good __P((char *, char *));
+void encrypt_passwd __P((char *, char *));
+
+void free_namelist __P((NAMELIST *));
+void create_namelist __P((NAMELIST *));
+int add_namelist __P((NAMELIST *, char *, char *));
+int remove_namelist __P((NAMELIST *, char *));
+int is_in_namelist __P((NAMELIST, char *));
+int apply_namelist __P((NAMELIST, int(), void *));
+int read_namelist __P((char *, NAMELIST *));
+int write_namelist __P((char *, NAMELIST));
+
+int read_headers __P((char *, HEADER *));
+int write_mail_headers __P((int, HEADER *, char *, NAMELIST));
+int write_post_headers __P((int, HEADER *, char *, char *));
+int parse_to_list __P((NAMELIST *, char *, char *));
diff --git a/complete.c b/complete.c
new file mode 100644
index 0000000..bdb460c
--- /dev/null
+++ b/complete.c
@@ -0,0 +1,406 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+/*
+ Adapted from Pirates BBS 1.8, namecomplete.c
+ Copyright (C) 1990, Edward Luke, lush@Athena.EE.MsState.EDU
+*/
+
+#include "client.h"
+#include <ctype.h>
+#if LACKS_MALLOC_H
+# include <stdlib.h>
+#else
+# include <malloc.h>
+#endif
+
+NumInList(list)
+NAMELIST list;
+{
+ int num = 0;
+ for (;list; list=list->next, num++);
+ return num;
+}
+
+ListMaxLen(list, lines)
+NAMELIST list;
+int lines;
+{
+ int len, max = 0;
+ for (;list && lines; list=list->next, lines--) {
+ len = strlen(list->word);
+ if (len > max) max = len;
+ }
+ return max;
+}
+
+chkstr(tag,name)
+char *tag, *name;
+{
+ register char c1, c2;
+ while(*tag != '\0') {
+ c1 = *tag++;
+ c2 = *name++;
+ if(toupper(c1) != toupper(c2))
+ return 0;
+ }
+ return 1;
+}
+
+NAMELIST
+GetSubList(tag, list)
+register char *tag ;
+register NAMELIST list;
+{
+ NAMELIST wlist, wcurr;
+
+ wlist = NULL ;
+ wcurr = NULL ;
+ while(list != NULL) {
+ if(chkstr(tag,list->word)) {
+ register NAMENODE *node ;
+ node = (NAMENODE *)malloc(sizeof(NAMENODE)) ;
+ node->word = list->word ;
+ node->next = NULL ;
+ if(wlist)
+ wcurr->next = node ;
+ else {
+ wlist = node ;
+ }
+ wcurr = node ;
+ }
+ list = list->next ;
+ }
+ return wlist ;
+}
+
+void
+ClearSubList(list)
+NAMELIST *list;
+{
+ NAMENODE *trav = *list, *next;
+ while (trav) {
+ next = trav->next;
+ free(trav);
+ trav = next;
+ }
+ *list = NULL;
+}
+
+#define NUMLINES (t_lines-5)
+
+/*
+ namecomplete can use two methods for completion.
+ Primarily, namecomplete uses the completefn to obtain the completion
+ list given the partial string. If completefn is NULL, the complist
+ is used to find the possible completions.
+*/
+
+namecomplete(completefn, complist, prompt, data, datasize)
+int (*completefn)();
+NAMELIST complist;
+char *prompt, *data ;
+int datasize;
+{
+ char *temp;
+ int ch ;
+ int count = 0 ;
+ int clearbot = 0;
+
+ if(scrint) {
+ NAMELIST cwlist, morelist ;
+ int x,y ;
+ int origx, origy;
+
+ if(prompt != NULL) {
+ prints("%s",prompt) ;
+ clrtoeol() ;
+ }
+ temp = data ;
+ cwlist = NULL;
+ morelist = NULL;
+ getyx(&y,&x) ;
+ getyx(&origy, &origx);
+
+ while((ch = igetch()) != EOF)
+ {
+ if(ch == '\n' || ch == '\r') {
+ *temp = '\0' ;
+ prints("\n") ;
+ if (temp != data) {
+ ClearSubList(&cwlist);
+ if (completefn) completefn(&cwlist, data) ;
+ else cwlist = GetSubList(data, complist) ;
+ if (NumInList(cwlist) == 1) {
+ strncpy(data,cwlist->word,datasize) ;
+ }
+ }
+ break ;
+ }
+ if(ch == ' ') {
+ int col,len ;
+ if (!morelist) {
+ ClearSubList(&cwlist);
+ if (completefn) completefn(&cwlist, data) ;
+ else cwlist = GetSubList(data, complist) ;
+ }
+ if(NumInList(cwlist) == 0) {
+ bell();
+ continue;
+ }
+ if(NumInList(cwlist) == 1) {
+ strncpy(data,cwlist->word,datasize) ;
+ ClearSubList(&cwlist);
+ move(origy,origx) ;
+ prints("%s",data) ;
+ count = strlen(data) ;
+ temp = data + count ;
+ getyx(&y,&x) ;
+ continue ;
+ }
+ clearbot = 1 ;
+ col = 0 ;
+ if(!morelist) morelist = cwlist ;
+ len = ListMaxLen(morelist,NUMLINES) ;
+ move(3,0) ;
+ clrtobot() ;
+ standout() ;
+ prints(
+ "------------------------------- Completion List -------------------------------") ;
+ standend() ;
+ while(len+col < 80) {
+ int i ;
+ for(i=NUMLINES;(morelist)&&(i>0);i--,morelist=morelist->next) {
+ move(4+(NUMLINES - i),col) ;
+ prints("%s",morelist->word) ;
+ }
+ col += len+2 ;
+ if(!morelist)
+ break ;
+ len = ListMaxLen(morelist,NUMLINES) ;
+ }
+ if(morelist) {
+ move(23,0) ;
+ standout() ;
+ prints("-- More --") ;
+ standend() ;
+ }
+ move(y,x) ;
+ continue ;
+ }
+ if(ch == '\177' || ch == '\010') {
+ if(temp == data)
+ continue ;
+ morelist = NULL;
+ temp-- ;
+ count-- ;
+ *temp = '\0' ;
+ x-- ;
+ move(y,x) ;
+ addch(' ') ;
+ move(y,x) ;
+ continue ;
+ }
+ if(count < datasize) {
+ morelist = NULL;
+ *temp++ = ch ;
+ count++ ;
+ *temp = '\0' ;
+ move(y,x) ;
+ addch(ch) ;
+ x++ ;
+ }
+ }
+ if(ch == EOF)
+ generic_abort() ;
+ if (*data) {
+ move(origy,origx);
+ prints("%s", data);
+ }
+ prints("\n") ;
+ refresh() ;
+ if(clearbot) {
+ move(3,0) ;
+ clrtobot() ;
+ }
+ ClearSubList(&cwlist);
+ return 0 ;
+ }
+ if(prompt != NULL) {
+ printf("%s",prompt) ;
+ fflush(stdout) ;
+ }
+ if(!fgets(data,datasize,stdin))
+ generic_abort() ;
+ data[datasize] = '\0';
+ if(temp = strchr(data,'\n'))
+ *temp = '\0' ;
+ return 0 ;
+}
+
+#if 0
+
+namecomplete(list, prompt, data)
+NAMELIST list;
+char *prompt, *data ;
+{
+ NAMENODE dummynode;
+ char *temp;
+ int ch ;
+ int count = 0 ;
+ int clearbot = 0;
+
+ if(scrint) {
+ NAMELIST cwlist, morelist ;
+ int x,y ;
+ int origx, origy;
+
+ if(prompt != NULL) {
+ prints("%s",prompt) ;
+ clrtoeol() ;
+ }
+ temp = data ;
+
+ if (list == NULL) {
+ dummynode.word = "";
+ dummynode.next = NULL;
+ list = &dummynode;
+ }
+ cwlist = GetSubList("", list) ;
+ morelist = NULL ;
+ getyx(&y,&x) ;
+ getyx(&origy, &origx);
+
+ while((ch = igetch()) != EOF)
+ {
+ if(ch == '\n' || ch == '\r') {
+ *temp = '\0' ;
+ prints("\n") ;
+ if(NumInList(cwlist) == 1 && temp != data)
+ strncpy(data,cwlist->word,WORDSIZE) ;
+ ClearSubList(cwlist) ;
+ break ;
+ }
+ if(ch == ' ') {
+ int col,len ;
+ if(NumInList(cwlist) == 1) {
+ strncpy(data,cwlist->word,WORDSIZE) ;
+ move(origy,origx) ;
+ prints("%s",data) ;
+ count = strlen(data) ;
+ temp = data + count ;
+ getyx(&y,&x) ;
+ continue ;
+ }
+ clearbot = 1 ;
+ col = 0 ;
+ if(!morelist)
+ morelist = cwlist ;
+ len = ListMaxLen(morelist,NUMLINES) ;
+ move(3,0) ;
+ clrtobot() ;
+ standout() ;
+ prints(
+ "------------------------------- Completion List -------------------------------") ;
+ standend() ;
+ while(len+col < 80) {
+ int i ;
+ for(i=NUMLINES;(morelist)&&(i>0);i--,morelist=morelist->next) {
+ move(4+(NUMLINES - i),col) ;
+ prints("%s",morelist->word) ;
+ }
+ col += len+2 ;
+ if(!morelist)
+ break ;
+ len = ListMaxLen(morelist,NUMLINES) ;
+ }
+ if(morelist) {
+ move(23,0) ;
+ standout() ;
+ prints("-- More --") ;
+ standend() ;
+ }
+ move(y,x) ;
+ continue ;
+ }
+ if(ch == '\177' || ch == '\010') {
+ if(temp == data)
+ continue ;
+ temp-- ;
+ count-- ;
+ *temp = '\0' ;
+ ClearSubList(cwlist) ;
+ cwlist = GetSubList(data,list) ;
+ morelist = NULL ;
+ x-- ;
+ move(y,x) ;
+ addch(' ') ;
+ move(y,x) ;
+ continue ;
+ }
+ if(count < WORDSIZE) {
+ NAMENODE *node ;
+ *temp++ = ch ;
+ count++ ;
+ *temp = '\0' ;
+ node = GetSubList(data,cwlist) ;
+ if(node == NULL) {
+ bell() ;
+ temp-- ;
+ *temp = '\0' ;
+ count-- ;
+ continue ;
+ }
+ ClearSubList(cwlist) ;
+ cwlist = node ;
+ morelist = NULL ;
+ move(y,x) ;
+ addch(ch) ;
+ x++ ;
+ }
+ }
+ if(ch == EOF)
+ generic_abort() ;
+ if (*data) {
+ move(origy,origx);
+ prints("%s", data);
+ }
+ prints("\n") ;
+ refresh() ;
+ if(clearbot) {
+ move(3,0) ;
+ clrtobot() ;
+ }
+ return 0 ;
+ }
+ if(prompt != NULL) {
+ printf("%s",prompt) ;
+ fflush(stdout) ;
+ }
+ if(!fgets(data,WORDSIZE,stdin))
+ generic_abort() ;
+ data[WORDSIZE] = '\0';
+ if(temp = strchr(data,'\n'))
+ *temp = '\0' ;
+ return 0 ;
+}
+
+#endif
+
diff --git a/config/COPYING b/config/COPYING
new file mode 100644
index 0000000..3bb93ef
--- /dev/null
+++ b/config/COPYING
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 675 Mass Ave, Cambridge, MA 02139, 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.
+
+ GNU GENERAL PUBLIC LICENSE
+ 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
+
+ Appendix: 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 a brief 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., 675 Mass Ave, Cambridge, MA 02139, 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/config/access b/config/access
new file mode 100644
index 0000000..e38dbae
--- /dev/null
+++ b/config/access
@@ -0,0 +1,250 @@
+# This is the bbs access file. For each function in libbbs.a, and a few
+# more, a line here is required to tell the bbs what permissions are
+# necessary to access each function. Be sure not to delete any of the
+# non-comments or no one will have access to some functions.
+#
+# For each function:
+# ALL means all users can do this, regardless of permission bits.
+# NONE means no users can do this. Not even Sysops.
+# Strings from the permstrs file (see it for more info) are used to
+# restrict access to users with those permissions. For example the sysop
+# functions have "Sysop". You can list more than one, separated by commas.
+#
+#
+# C_INIT 0 Initialize the bbs (not used)
+Initialize=ALL
+# C_CONNECT 1 Connect to the bbs
+Connect=ALL
+# C_DISCONNECT 2 Disconnect from the bbs (not used)
+Disconnect=ALL
+# C_ISSUE 3 Read the issue file
+GetIssue=ALL
+# C_WELCOME 4 Read the welcome screen
+GetWelcome=ALL
+#
+# Note: NEWLOGIN should stay ALL here. This is actually controlled in
+# bbconfig file; if "new=yes" is not in it the NEWLOGIN command will be
+# denied anyway.
+#
+# C_NEWLOGIN 5 Create a new account at the login prompt
+NewLogin=ALL
+# C_LOGIN 6 Log into the bbs
+Login=ALL
+# C_ADDACCT 7 Add an account from the Admin Menu
+AddAccount=AccountMgr
+# C_DELACCT 8 Delete an account
+DeleteAccount=AccountMgr
+# C_QUERY 9 Query an account
+Query=ALL
+# C_OWNINFO 10 Get complete info for your own account
+GetOwnInfo=ALL
+# C_GETACCT 11 Get complete info on any account
+GetAnyInfo=AccountMgr
+# C_SETMODE 12 Set your mode (Reading, Posting, Chat, etc.)
+SetMode=ALL
+# C_SETPASSWD 13 Change your password
+SetPassword=ALL
+# C_SETUSERNAME 14 Change your username
+SetUsername=ALL
+# C_SETTERMINAL 15 Change your terminal type
+SetTerminal=ALL
+# C_SETEMAIL 16 Change your email address
+SetAddress=ALL
+# C_SETPERMS 17 Change permissions for any account
+SetPermissions=Sysop
+# C_SETACCT 18 Set information for any account
+SetAccountInfo=AccountMgr
+# C_ALLUSERS 19 List all accounts
+AllUsers=ALL
+# C_USERS 20 List all logged-in users
+Users=ALL
+# C_ALLNAMES 21 Get list of all account userids
+AllNames=ALL
+# C_NAMES 22 Get list of all logged-in userids
+Names=ALL
+# C_INFOFILE 23 Read the bbs information file
+GetInfoFile=ALL
+# C_GNUFILE 24 Read the GNU General Public License
+GetGNUFile=ALL
+# C_GETPLAN 25 Read any user's plan
+GetPlan=ALL
+# C_SETPLAN 26 Set your own plan
+SetPlan=ALL
+# C_CLOAK 27 Hide from other users
+Cloak=Cloak
+# C_EXEMPT 28 Mark an account as exempt from user cleans
+ExemptUser=AccountMgr
+# C_GETPERMSTRS 29 Get the list of permission names
+GetPermStrings=AccountMgr,BoardMgr
+# C_CHKMAIL 30 Check to see if you have unread mail
+CheckMail=ALL
+# C_MAIL 31 Send mail to other bbs users
+SendMail=ALL
+# C_OPENMAIL 32 Open for own mailbox for reading
+OpenMail=ALL
+# C_CLOSEBRD 33 Close a mailbox, board, or file board
+CloseBoard=ALL
+# C_ENUMHDRS 34 Get mail or post header information
+EnumHeaders=ALL
+# C_READMSG 35 Read a mail message or post
+ReadMessage=ALL
+#
+# Note: DELMSG, DELRANGE, MARKMSG, and REPLACEMSG should remain accessible
+# to ALL in this file, unless you want these functions to be inaccessible
+# to the per-board Managers. A user must have Manager permission when opening
+# a board for these rights to be granted.
+#
+# C_DELMSG 36 Delete a mail message or post
+DeleteMessage=ALL
+# C_DELRANGE 37 Delete a range of mail messages or posts
+DeleteRange=ALL
+# C_MARKMSG 38 Mark a post for non-deletion
+MarkMessage=ALL
+# C_SETWELCOME 39 Modify or delete the welcome screen
+EditWelcome=WelcomeMgr
+# C_ADDBOARD 40 Create a new board for posting
+AddBoard=BoardMgr
+# C_DELBOARD 41 Delete a board and all posts on it
+DeleteBoard=BoardMgr
+# C_GETBOARD 42 Get information about a board
+GetBoardInfo=ALL
+# C_SETBOARD 43 Set information about a board
+SetBoardInfo=BoardMgr
+# C_ENUMBRDS 44 Return information about all boards
+EnumBoards=ALL
+# C_BNAMES 45 Get a list of all board names
+BoardNames=ALL
+# C_VISITBRD 46 Mark all posts on a board or boards as read
+VisitBoard=ALL
+# C_POST 47 Post a message on a board
+Post=ALL
+# Note: Since each board has its own access masks in the boardlist,
+# things like OPENBRD, GETBOARD, ENUMBRDS, etc. will only reveal the
+# boards to which the user has access. OPENBRD and TESTBRD return to
+# the user flags telling if they have Post or Manager access.
+#
+# C_OPENBRD 48 Open a public board for reading
+OpenBoard=ALL
+# C_TESTBRD 49 Find out whether you can post on or manage a board
+TestBoard=ALL
+# C_REPLACEMSG 50 Edit a message on a board (see note above)
+ReplaceMessage=Sysop
+# C_ZAPBOARD 51 Mark a board to not be scanned for new messages
+ZapBoard=ALL
+# C_GETBMGRLIST 52 Get list of managers for a board
+GetBoardMgrs=BoardMgr
+# C_SETBMGRLIST 53 Set list of managers for a board
+SetBoardMgrs=BoardMgr
+# C_ENUMFBRDS 54 Return information about all file boards
+EnumFileBoards=ALL
+# C_FBNAMES 55 Get a list of all file board names
+FileBoardNames=ALL
+# C_OPENFBRD 56 Open a file board for viewing or downloading
+OpenFileBoard=ALL
+# C_PROTONAMES 57 Get a list of all protocol names for UL/DL
+ProtocolNames=ALL
+# C_SETPROTO 58 Set your protocol for UL/DL
+SetProtocol=ALL
+# C_UPLOAD 59 Upload a file, if an UPLOAD board is configured
+Upload=NONE
+# C_DOWNLOAD 60 Download a file from the open file board
+Download=ALL
+# NOTE: The chat client doesn't support read-only chat, nor honor the
+# value of C_EXITCHAT for obvious reasons. C_EXITCHAT and C_CHATLINE
+# are available to whoever has C_CHAT.
+#
+# C_CHAT 61 Enter the Chat system
+Chat=ALL
+# C_EXITCHAT 62 Exit the Chat system (not used)
+ExitChat=ALL
+# C_CHATLINE 63 Send a line to the chat daemon (not used)
+TalkInChat=ALL
+# C_KICK 64 Kick a logged-in user off of the system
+Kick=Sysop
+# C_TALK 65 Page another user for private talk
+Page=ALL
+# C_EXITTALK 66 Exit a private talk (not used)
+EndTalk=ALL
+# C_GETTALKREQ 67 Find out who is paging you
+GetPageRequest=ALL
+# C_TALKACCEPT 68 Accept a page
+AcceptPage=ALL
+# C_TALKREFUSE 69 Refuse a page
+RefusePage=ALL
+# C_SETPAGER 70 Set your paging status
+SetPager=ALL
+# C_GETOVERLIST 71 Get your override/deny list for paging
+GetOverrides=ALL
+# C_SETOVERLIST 72 Set your override/deny list for paging
+SetOverrides=ALL
+# C_FORWARDMSG 73 Forward a message to your email address
+ForwardMessage=ALL
+# C_FORWARDFILE 74 Forward a file to your email address
+ForwardFile=ALL
+#
+# These next three should be NONE if you aren't offering any editors
+# except the builtin one to local users (see editors file).
+#
+# C_SETEDITOR 75 Set your editor for mailing/posting
+SetEditor=NONE
+# C_GETEDITOR 76 Get the name of your last selected editor
+GetEditor=NONE
+# C_ENUMEDITORS 77 List all editors available through the bbs
+EnumEditors=NONE
+# C_GETSIGFILE 78 Signature file for mail/posts
+GetSignature=ALL
+# C_SETSIGFILE 79 Signature file
+SetSignature=ALL
+# C_GETMODESTRS 80 Get mode strings (for Users list)
+GetModeStrs=ALL
+# C_GETMODECHRS 81 Get mode characters (for short List, Monitor)
+GetModeChars=ALL
+# C_SETCLIOPTS 82 Set client options (i.e. expert/novice menus)
+SetCliOpts=ALL
+#
+# These do not correspond to commands, but are used by the server for
+# filtering the information returned by some commands.
+#
+# C_SEEALLAINFO 83 See account flags in bbs_query, etc.
+SeeAllAccountInfo=AccountMgr
+# C_SEEALLBINFO 84 See board permission masks in bbs_get_board, etc.
+SeeAllBoardInfo=BoardMgr
+# C_ALLBOARDMGR 85 Have manager permission on all boards automatically.
+ManageAllBoards=BoardMgr
+# C_SEECLOAK 86 See cloaked users in bbs_enum_users, etc.
+SeeCloaked=SeeCloak
+# C_SEEREALNAME 87 See real names in bbs_query IF showreal is enabled.
+SeeRealName=Basic-1
+# C_NOTIMEOUT 88 Immunity to the local client's idle timer.
+NoIdleTimeout=Sysop
+# C_USERESERVED 89 Can use specially reserved user table slots.
+UseReserved=Sysop
+# C_EXTERNMAIL 90 May send/receive external e-mail.
+ExternalMail=Sysop
+#
+# These aren't used in the code but do allow the standard menus to be
+# visible or hidden to different users. For menus you add yourself, add
+# access codes below these.
+#
+# C_ADMINMENU 91 Admin Menu access
+AdminMenu=Sysop,AccountMgr,BoardMgr
+# C_FILEMENU 92 File Menu access
+FileMenu=ALL
+# C_MAILMENU 93 Mail Menu access
+MailMenu=ALL
+# C_TALKMENU 94 Talk Menu access
+TalkMenu=ALL
+# C_XYZMENU 95 Xyz Menu access
+XyzMenu=ALL
+# C_SETCHARSET 96 Change your character set
+SetCharset=ALL
+# C_FILECHDIR 97 Change working directory in file board
+FileChdir=ALL
+# C_MOVEMESSAGE 98 Move a post from one board to another
+MoveMessage=ALL
+#
+# More can go here, for use in configurable menus.
+# ONLY NEW ENTRIES ONLY BELOW THIS POINT!
+#
+ShellEscape=Sysop
+# etc.
diff --git a/config/bbconfig b/config/bbconfig
new file mode 100644
index 0000000..5843773
--- /dev/null
+++ b/config/bbconfig
@@ -0,0 +1,62 @@
+# This is the main bbs config file. It MUST exist or the bbs will not run.
+#
+# Startup information is given here in name=value form. The things you
+# specify here are:
+#
+# name: The name of your bbs. Up to 40 chars. Default=The Unknown BBS.
+# new: If yes, users can enter 'new' at the login prompt and create
+# their own accounts. Otherwise they can't. Default=no.
+# users: How many users can log in at once. Base this on the capacity
+# of your system, mainly on RAM. You can change this at any time,
+# perhaps automatically based on time of day, but it MUST be less
+# than or equal to the usertablesize parameter. Default=80.
+# reservedslots: Of the number of user slots given by the 'users' value,
+# reserve this many for Sysops or other privileged users. The
+# UseReserved parameter in the access file determines who may use
+# these slots. Default=0 (no reserved slots).
+# usertablesize: The size of the shared memory user table, in users.
+# Set this to the absolute maximum number of logins you expect to
+# allow on at once in the foreseeable future. IF YOU CHANGE THIS
+# PARAMETER, you MUST shut down the bbs and chatd and remove the
+# shared memory segment with the ipcrm utility. If you fail to do
+# this, bad things will happen.
+# logfile: The location of the bbs log file. Default=~/log.
+# loglevel: Controls what events get written to the logfile. Default=1.
+# Higher numbers mean more logging.
+# System errors are always logged.
+# Level 1: Logins, logouts, and account creations
+# Level 2: Administrative actions
+# Level 3: Posts, uploads, downloads, forwards.
+# Level 4: Entries to chat/talk.
+# logons: The number of times users can log onto the bbs at once.
+# Zero means unlimited. Use entries in the ~/etc/logons file to
+# override this default value.
+# siglines: The maximum number of lines from a signature file to be appended
+# to posts and mail. If zero, signature is not appended.
+# Default=4.
+# showreal: If yes, the real name of a user is returned with a Query.
+# Default=no. If yes, can be further restricted with the SeeRealName
+# setting in the access file.
+# timeout: After this many minutes of no input, the local client may time
+# out and boot a user. This is imprecise and the actual time before
+# boot may range from 1 to 2 times this value. In Monitor mode, the
+# timeout is exactly 2 times this value. Default is 0, meaning no idle
+# timeout. May be overridden with the NoIdleTimeout setting in the
+# access file.
+# encoder: Needed if you have binary files in your file section and want
+# to allow forwarding of them. Put the path to uuencode here.
+# locale: Locale name (pathname). Default=<null>, the default locale.
+#
+name=The Unknown BBS
+new=no
+users=80
+reservedslots=0
+usertablesize=80
+logfile=log
+loglevel=1
+logons=0
+siglines=4
+showreal=no
+timeout=0
+encoder=/usr/bin/uuencode
+locale=
diff --git a/config/boardlist b/config/boardlist
new file mode 100644
index 0000000..37b14b3
--- /dev/null
+++ b/config/boardlist
@@ -0,0 +1,5 @@
+# This is the list of public message boards on the bbs. DO NOT edit this
+# file by hand unless you know what you are doing! The Admin Menu has
+# functions for managing this file safely.
+#
+# To add a board, select (N)ew Board on the Admin menu.
diff --git a/config/charset-alt b/config/charset-alt
new file mode 100644
index 0000000..8491f5b
--- /dev/null
+++ b/config/charset-alt
@@ -0,0 +1,256 @@
+0=0
+1=1
+2=2
+3=3
+4=4
+5=5
+6=6
+7=7
+8=8
+9=9
+10=10
+11=11
+12=12
+13=13
+14=14
+15=15
+16=16
+17=17
+18=18
+19=19
+20=20
+21=21
+22=22
+23=23
+24=24
+25=25
+26=26
+27=27
+28=28
+29=29
+30=30
+31=31
+32=32
+33=33
+34=34
+35=35
+36=36
+37=37
+38=38
+39=39
+40=40
+41=41
+42=42
+43=43
+44=44
+45=45
+46=46
+47=47
+48=48
+49=49
+50=50
+51=51
+52=52
+53=53
+54=54
+55=55
+56=56
+57=57
+58=58
+59=59
+60=60
+61=61
+62=62
+63=63
+64=64
+65=65
+66=66
+67=67
+68=68
+69=69
+70=70
+71=71
+72=72
+73=73
+74=74
+75=75
+76=76
+77=77
+78=78
+79=79
+80=80
+81=81
+82=82
+83=83
+84=84
+85=85
+86=86
+87=87
+88=88
+89=89
+90=90
+91=91
+92=92
+93=93
+94=94
+95=95
+96=96
+97=97
+98=98
+99=99
+100=100
+101=101
+102=102
+103=103
+104=104
+105=105
+106=106
+107=107
+108=108
+109=109
+110=110
+111=111
+112=112
+113=113
+114=114
+115=115
+116=116
+117=117
+118=118
+119=119
+120=120
+121=121
+122=122
+123=123
+124=124
+125=125
+126=126
+127=127
+128=225
+129=226
+130=247
+131=231
+132=228
+133=229
+134=246
+135=250
+136=233
+137=234
+138=235
+139=236
+140=237
+141=238
+142=239
+143=240
+144=242
+145=243
+146=244
+147=245
+148=230
+149=232
+150=227
+151=254
+152=251
+153=253
+154=255
+155=249
+156=248
+157=252
+158=224
+159=241
+160=193
+161=194
+162=215
+163=199
+164=196
+165=197
+166=214
+167=218
+168=201
+169=202
+170=203
+171=204
+172=205
+173=206
+174=207
+175=208
+176=128
+177=129
+178=130
+179=131
+180=132
+181=133
+182=134
+183=135
+184=136
+185=137
+186=186
+187=139
+188=140
+189=141
+190=142
+191=143
+192=144
+193=145
+194=146
+195=147
+196=148
+197=149
+198=150
+199=151
+200=152
+201=153
+202=154
+203=191
+204=156
+205=157
+206=158
+207=159
+208=185
+209=161
+210=162
+211=176
+212=164
+213=165
+214=166
+215=167
+216=168
+217=169
+218=170
+219=171
+220=172
+221=173
+222=174
+223=175
+224=210
+225=211
+226=212
+227=213
+228=198
+229=200
+230=195
+231=222
+232=219
+233=221
+234=223
+235=217
+236=216
+237=220
+238=192
+239=209
+240=179
+241=163
+242=178
+243=177
+244=180
+245=181
+246=182
+247=183
+248=184
+249=160
+250=138
+251=187
+252=188
+253=189
+254=190
+255=155
diff --git a/config/charset-cp866 b/config/charset-cp866
new file mode 100644
index 0000000..8491f5b
--- /dev/null
+++ b/config/charset-cp866
@@ -0,0 +1,256 @@
+0=0
+1=1
+2=2
+3=3
+4=4
+5=5
+6=6
+7=7
+8=8
+9=9
+10=10
+11=11
+12=12
+13=13
+14=14
+15=15
+16=16
+17=17
+18=18
+19=19
+20=20
+21=21
+22=22
+23=23
+24=24
+25=25
+26=26
+27=27
+28=28
+29=29
+30=30
+31=31
+32=32
+33=33
+34=34
+35=35
+36=36
+37=37
+38=38
+39=39
+40=40
+41=41
+42=42
+43=43
+44=44
+45=45
+46=46
+47=47
+48=48
+49=49
+50=50
+51=51
+52=52
+53=53
+54=54
+55=55
+56=56
+57=57
+58=58
+59=59
+60=60
+61=61
+62=62
+63=63
+64=64
+65=65
+66=66
+67=67
+68=68
+69=69
+70=70
+71=71
+72=72
+73=73
+74=74
+75=75
+76=76
+77=77
+78=78
+79=79
+80=80
+81=81
+82=82
+83=83
+84=84
+85=85
+86=86
+87=87
+88=88
+89=89
+90=90
+91=91
+92=92
+93=93
+94=94
+95=95
+96=96
+97=97
+98=98
+99=99
+100=100
+101=101
+102=102
+103=103
+104=104
+105=105
+106=106
+107=107
+108=108
+109=109
+110=110
+111=111
+112=112
+113=113
+114=114
+115=115
+116=116
+117=117
+118=118
+119=119
+120=120
+121=121
+122=122
+123=123
+124=124
+125=125
+126=126
+127=127
+128=225
+129=226
+130=247
+131=231
+132=228
+133=229
+134=246
+135=250
+136=233
+137=234
+138=235
+139=236
+140=237
+141=238
+142=239
+143=240
+144=242
+145=243
+146=244
+147=245
+148=230
+149=232
+150=227
+151=254
+152=251
+153=253
+154=255
+155=249
+156=248
+157=252
+158=224
+159=241
+160=193
+161=194
+162=215
+163=199
+164=196
+165=197
+166=214
+167=218
+168=201
+169=202
+170=203
+171=204
+172=205
+173=206
+174=207
+175=208
+176=128
+177=129
+178=130
+179=131
+180=132
+181=133
+182=134
+183=135
+184=136
+185=137
+186=186
+187=139
+188=140
+189=141
+190=142
+191=143
+192=144
+193=145
+194=146
+195=147
+196=148
+197=149
+198=150
+199=151
+200=152
+201=153
+202=154
+203=191
+204=156
+205=157
+206=158
+207=159
+208=185
+209=161
+210=162
+211=176
+212=164
+213=165
+214=166
+215=167
+216=168
+217=169
+218=170
+219=171
+220=172
+221=173
+222=174
+223=175
+224=210
+225=211
+226=212
+227=213
+228=198
+229=200
+230=195
+231=222
+232=219
+233=221
+234=223
+235=217
+236=216
+237=220
+238=192
+239=209
+240=179
+241=163
+242=178
+243=177
+244=180
+245=181
+246=182
+247=183
+248=184
+249=160
+250=138
+251=187
+252=188
+253=189
+254=190
+255=155
diff --git a/config/charset-koi b/config/charset-koi
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/config/charset-koi
diff --git a/config/charset-koi8 b/config/charset-koi8
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/config/charset-koi8
diff --git a/config/chatconfig b/config/chatconfig
new file mode 100644
index 0000000..e8c20c9
--- /dev/null
+++ b/config/chatconfig
@@ -0,0 +1,16 @@
+# This is an optional configuration file used by the chat daemon.
+#
+# mainroom: allows the name of the main chat room to be set. Default=main.
+# operators: list of chat "super-ops", separated by commas or spaces.
+# Operators can kick out troublemakers from any room, and see/enter all
+# locked and secret rooms.
+# restricted: list of restricted accounts that cannot use /join, /rooms,
+# or see users in rooms besides the main room.
+# nopunct: if set to yes, disallows all punctuation characters in chatids
+# except those allowable in userids ('_', '-').
+# '*', '+', ':' are always disallowed.
+#
+#mainroom=main
+#operators=SYSOP,anotherop
+#restricted=guest
+#nopunct=y \ No newline at end of file
diff --git a/config/chathlp.txt b/config/chathlp.txt
new file mode 100644
index 0000000..43116b8
--- /dev/null
+++ b/config/chathlp.txt
@@ -0,0 +1,25 @@
+# This is the main Chat client help file, used by the /h command.
+# Blank lines do get displayed. Don't use tabs in here.
+#
+
+/a <text> Action: display <text> as an action (also /me)
+/c Clear: reset screen to initial state
+/e <text> Exit: leave chat (also CTRL-C)
+/f [+,-][ls] Flags: set or unset room flags (Op only)
+/h Help: get this help message
+/i <nick> Invite: give <nick> a key to the room (Op only)
+/ignore <userid> Ignore: squelch all messages from <userid>
+/j <room> Join: join a new or existing room
+/k <nick> Kick: boot <nick> back to main room (Op only)
+/l <range> Long list: more detailed user list than /u
+/m <nick> <text> Message: send <text> privately to <nick>
+/n <nick> Nick: change your chatid to <nick>
+/o <nick> Op: make <nick> an Op in this room (Op only)
+/q <userid> Query: query an account on the bbs
+/r Rooms: list all active rooms, except secret rooms
+/u <range> Users: list all users logged into the bbs
+/unignore <userid> Unignore: stop ignoring <userid>
+/w <range> Who: list all users in chat, excluding secret rooms
+/whoin <room> WhoIn: same as /w but only list one room
+/whois <nick> WhoIs: identify a nick or all nicks by chatid
+/x Xtra Help: yet another help screen
diff --git a/config/chatxhlp.txt b/config/chatxhlp.txt
new file mode 100644
index 0000000..2faae95
--- /dev/null
+++ b/config/chatxhlp.txt
@@ -0,0 +1,19 @@
+# This is the extra Chat client help file, used by the /x command.
+# Blank lines do get displayed. Don't use tabs in here.
+#
+
+INPUT LINE EDITING CONTROLS:
+Ctrl-A Beginning of line
+Ctrl-B Backward one character
+Ctrl-D Delete character under cursor
+Ctrl-E End of line
+Ctrl-F Forward one character
+Ctrl-H Delete character before cursor
+Ctrl-U Clear entire line
+Ctrl-W Delete word under or prior to cursor
+ESC B Backward one word
+ESC F Forward one word
+
+ROOM FLAGS used with /f command:
+l Locked: Only users invited with /i can enter
+s Secret: Room and users invisible to /u and /w
diff --git a/config/editors b/config/editors
new file mode 100644
index 0000000..482753e
--- /dev/null
+++ b/config/editors
@@ -0,0 +1,19 @@
+# This is the optional editor config file. You can offer alternate editors
+# to the builtin one here if you like.
+#
+# WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+# DO NOT put your favorite shell editors here. Most editors can do lots
+# of things you do NOT want bbs users to do! Like: escape to a shell,
+# pipe shell commands, write or bring up files other than the one they're
+# editing. Your bbs can be BLOWN AWAY quite easily. You should only install
+# editors here that you have modified to be safe. You have been warned.
+#
+# The builtin editor need only be uncommented if there are other choices too.
+# The builtin editor is always used as the default.
+#
+# The environment field uses the same format and substitution rules for
+# %I, %U, %T, %E, %H as described in the menu.desc file.
+#
+# Format: <editor-name>:<full-path-with-flags>:<environment>:<comment>
+#
+# builtin:::Builtin editor (no path or environment needed)
diff --git a/config/ftplist b/config/ftplist
new file mode 100644
index 0000000..8ac8431
--- /dev/null
+++ b/config/ftplist
@@ -0,0 +1,14 @@
+# This is the list of directories that contain files for uploading and
+# downloading. All directories beneath the directories listed here
+# will also be navigable. The format of records in this file is:
+#
+# board-name:full-directory-path:optional-description-of-files
+#
+# The special board-name UPLOAD designates the directory uploads go to.
+# You can point a download directory to it too if you like.
+#
+# Examples:
+#
+# EaglesBBS:/usr/src/eagles-bbs.3.0:Eagles BBS 3.0 source
+# UPLOAD:/usr/bbs/files/incoming:
+# Source:/usr/bbs/files/source:You can quote colons like this\: see.
diff --git a/config/info b/config/info
new file mode 100644
index 0000000..d295127
--- /dev/null
+++ b/config/info
@@ -0,0 +1,11 @@
+Eagles Bulletin Board System Version: 3.1.1
+Copyright (C) 1992-1997 Raymond R. Rocker
+
+Based in part on:
+Pirate Bulletin Board System Version: 1.00
+Copyright (C) 1990 Edward A. Luke
+
+This BBS comes with ABSOLUTELY NO WARRANTY.
+This is free software, and you are welcome to distribute it under
+certain conditions as described in the GNU GENERAL PUBLIC LICENSE.
+Use the (G)nu command in the (X)yz menu to find out more information.
diff --git a/config/issue b/config/issue
new file mode 100644
index 0000000..0794790
--- /dev/null
+++ b/config/issue
@@ -0,0 +1,3 @@
+
+This is the issue file from the etc directory. It contains information
+you want users to see before the bbs login prompt. Edit it to your needs.
diff --git a/config/logons b/config/logons
new file mode 100644
index 0000000..1344573
--- /dev/null
+++ b/config/logons
@@ -0,0 +1,11 @@
+# The logons file specifies the number of times each user may logon to
+# the bbs at once. It is here so that the default "logons" value in
+# the bbconfig file may be overridden for certain users. For instance,
+# you may allow unlimited logons by default, but set a guest account
+# to a limited number in this file. Or allow only one logon for normal
+# users with a higher logon limit for sysops given here.
+#
+# Format: userid:logon-limit
+#
+# guest:5
+# SYSOP:0
diff --git a/config/mailers b/config/mailers
new file mode 100644
index 0000000..6db32a1
--- /dev/null
+++ b/config/mailers
@@ -0,0 +1,16 @@
+# EBBS external mailers file
+#
+# Format: prefix:mailer-path
+# Prefix is the prefix to the address that identifies addresses for this
+# mailer to process. Must be no more than 12 characters.
+# Mailer-path, the path to the mailer, may include flags.
+#
+# Be sure to set the "encoder" entry in bbconfig if binary file forwarding
+# is to work.
+#
+# The INTERNET mailer must be included for mail, post, and file forwarding
+# from the read menus to work.
+#
+INTERNET:/usr/lib/sendmail
+# Here we let IN be an abbreviation for INTERNET.
+IN:/usr/lib/sendmail
diff --git a/config/menu.desc b/config/menu.desc
new file mode 100644
index 0000000..8067cd8
--- /dev/null
+++ b/config/menu.desc
@@ -0,0 +1,326 @@
+# This file contains a description of the menu hierarchy.
+
+# Set Up the Default Environment
+
+Environment("TERM=%T,BBS_USERID=%I,BBS_USERNAME=%U,SHELL=/bin/false")
+
+# The Environment Variables can be changed upon execution of a command with
+# $exec, $exec.pause, or $exec.more by including the environment string
+# in the execution command separated by a colon, for example to have
+# a menu entry execute the vi editor and reset the terminal type to
+# a specific type you would use:
+# $exec "/usr/ucb/vi:TERM=ansi"
+#
+# Percent characters in all strings of this menu indicate substitution
+# directives. %T substitutes the Terminal Type, %I the BBS User Id,
+# %U the BBS User Name, %E the selected editor, and %H the host from
+# which the user is logged in.
+#
+# In addition, you may specify a mode number, corresponding to an entry
+# in the ~bbs/etc/modes file, in each $exec, $exec.pause, or $exec.more
+# command. Setting up a mode in which the user cannot be paged is usually
+# a very good idea for any $exec function (see the modes files for details).
+# The mode is specified by appending a colon and the mode number after the
+# environment, or two colons and the mode number if there is no environment
+# specified. Like: $exec "/usr/ucb/vi:TERM=ansi:11" or $exec "/usr/ucb/vi::11"
+#
+# menu "menu name", "titlestring", "menu prompt", "default action" {
+# [command list]
+# }
+# A "*" in the titlestring field will be substituted by the bbs name.
+
+#
+# For every action in this list is a string. Normally the string is the
+# command name that you want to execute for that particular case, for example
+# a default action of "Help" means that the Help command will be the default
+# command when you enter this menu. You can also make the action string
+# be a function of the "new mail waiting" status. This is indicated by
+# action strings that start with a '$' character. When the action string
+# begins with a '$' the first character following the $ indicates the default
+# action for when no mail is waiting, and the second character indicates
+# the command for when mail is waiting. For example in the following
+# menu code the default action of "$NM" means that when you first enter
+# the bbs you get a "New" action when no mail is waiting, and a
+# "Mail" action for when there is mail waiting.
+#
+menu "Main", "*", "Enter Command: ", "$NM" {
+# Each Command entry is enclosed in parens and is a list of
+# the following:
+# Command Name, Default Next Command, Next Command on Error,
+# Permission required to use this command, Function to execute this
+# command (may include one text string as argument), and
+# finally the help string for the command.
+#
+# Uncomment this one if you get the account scripts (separate).
+#("Join", "Goodbye", "Goodbye", JoinTheBBS, $exec.pause "bin/bbsapply.pl:BBS_USERID=%I,BBS_FROMHOST=%H",
+#"(J)oin Apply for an account on this BBS")
+("Select", "Read", "Select", BoardNames, $SelectBoard,
+"(S)elect Set current board")
+("Boards", "Select", "Boards", EnumBoards, $Boards,
+"(B)oards List boards on system")
+("Count", "Select", "Boards", EnumBoards, $BoardCounts,
+"(C)ount Count posts by board")
+("Post", "Read", "Read", Post, $Post,
+"(P)ost Post a message on current board")
+("Read", "Read", "Post", OpenBoard, $MainRead,
+"(R)ead Enter multifunction Read Menu")
+("New", "Talk", "Select", EnumBoards, $ReadNew,
+"(N)ew Scan for new messages")
+("Zap", "Zap", "Zap", ZapBoard, $Zap,
+"(Z)ap Zap boards from (N)ew search")
+("Visit", "Visit", "Visit", VisitBoard, $Visit,
+"(V)isit Mark messages as read")
+("Mail", "$NM", "Mail", 0, $Menu "Mail",
+"(M)ail Enter Mail Menu")
+("Files", "$FM", "Files", 0, $Menu "Files",
+"(F)iles Enter File Transfer Menu")
+("Talk", "$NM", "Talk", 0, $Menu "Talk",
+"(T)alk Enter Talk Menu")
+("Xyz", "$NM", "Xyz", 0, $Menu "Xyz",
+"(X)yz Utilities (Change passwd, term type)")
+("Admin", "$AM", "Admin", AdminMenu, $Menu "Admin",
+"(A)dmin Enter Admin Menu")
+("Info", "Info", "Info", GetInfoFile, $BoardInfo,
+"(I)nfo Get Version and Copyright Information")
+("Welcome", "Welcome", "Welcome", GetWelcome, $Welcome,
+"(W)elcome Look at the Welcome Screen")
+("Users", "Talk", "Users", AllUsers, $AllUsers,
+"(U)sers List ALL accounts on this BBS")
+("Help", "Help", "Help", 0, $Help,
+"(H)elp Get this Help Screen")
+("Goodbye", "Goodbye", "Goodbye", 0, $EndMenu,
+"(G)oodbye Leave This BBS")
+}
+
+menu "Mail", "Personal Mail Menu", "Enter Mail Command: ", "$SN" {
+("Send", "Send", "Exit", SendMail, $MailSend,
+"(S)end Send a mail message")
+("GroupSend", "GroupSend", "Exit", SendMail, $GroupSend,
+"(G)roupSend Send mail to more than one user")
+("New", "Exit", "Exit", OpenMail, $ReadNewMail,
+"(N)ew Read new mail messages")
+("Read", "Exit", "Exit", OpenMail, $MailRead,
+"(R)ead Enter multipurpose mail read menu")
+("Help", "Help", "Help", 0, $Help,
+"(H)elp Get this help screen")
+("Exit", "Exit", "Exit", 0, $EndMenu,
+"(E)xit Exit to main menu")
+}
+
+menu "Files", "File Transfer Menu", "Enter File Transfer Command: ", "$HE" {
+("Select", "Download", "Exit", FileBoardNames, $FileSelect,
+"(S)elect Select a File Sub-board")
+("List", "Select", "Exit", EnumFileBoards, $FileBoards,
+"(L)ist List File sub-boards")
+("Protocol", "Download", "Select", SetProtocol, $SelectProtocol,
+"(P)rotocol Select protocol for transfers")
+("Upload", "Exit", "Select", Upload, $FileUpload,
+"(U)pload Upload a file")
+("Download", "Exit", "Select", Download, $FileDownload,
+"(D)ownload Enter multifunction download menu")
+("Help", "Help", "Help", 0, $Help,
+"(H)elp Get this help screen")
+("Exit", "Exit", "Exit", 0, $EndMenu,
+"(E)xit Exit to main menu")
+}
+
+menu "Xyz", "Misc. Utilities Menu", "Enter Xyz Menu Command: ", "$HE" {
+("Cloak", "Exit", "Exit", Cloak, $ToggleCloak,
+"(C)loak Hide from other users")
+("Passwd", "Exit", "Passwd", SetPassword, $SetPasswd,
+"(P)asswd Change your password")
+("Name", "Exit", "Name", SetUsername, $SetUsername,
+"(N)ame Change your username")
+("Address", "Exit", "Address", SetAddress, $SetAddress,
+"(A)ddress Set your e-mail address")
+("Users", "Help", "Help", Users, $OnlineUsers,
+"(U)sers Show users currently online")
+("Terminal", "Terminal", "Terminal", SetTerminal, $SetTermtype,
+"(T)erminal Set your terminal type")
+("Language", "Language", "Language", SetCharset, $SetCharset,
+"(L)anguage Set language-dependent character mapping")
+("Info", "Exit", "Exit", GetOwnInfo, $ShowOwnInfo,
+"(I)nfo Show your passfile info")
+("Welcome", "Exit", "Exit", EditWelcome, $EditWelcome,
+"(W)elcome Edit the Welcome Screen")
+("QueryEdit", "Exit", "Exit", SetPlan, $QueryEdit,
+"(Q)ueryEdit Edit your plan file")
+("Gnu", "Gnu", "Gnu", GetGNUFile, $GnuInfo,
+"(G)nu Gnu General Public License")
+("Signature", "Exit", "Exit", SetSignature, $SignatureEdit,
+"(S)ignature Edit your signature file")
+("FileEditor", "Exit", "Exit", SetEditor, $SelectEditor,
+"(F)ileEditor Select editor for posts, mail, etc.")
+("Menus", "Exit", "Exit", SetCliOpts, $MenuConfig,
+"(M)enus Novice/Expert menu toggle")
+("Help", "Help", "Exit", 0, $Help,
+"(H)elp Get this help screen")
+("Exit", "Help", "Exit", 0, $EndMenu,
+"(E)xit Return to Main Menu")
+}
+
+menu "Admin", "BBS Administration Menu", "Enter Admin Command: ", "$HE" {
+("Add User", "Add User", "Exit", AddAccount, $AddAccount,
+"(A)dd User Add an account")
+# Uncomment this one if you get the account scripts (available separately).
+#("Requests", "Requests", "Exit", AddAccount, $exec.pause "bin/bbsapprove.pl:BBS_USERID=%I",
+#"(R)equests Process requests submitted via (J)oin")
+("Delete User", "Delete", "Exit", DeleteAccount, $DeleteAccount,
+"(D)elete User Delete an account")
+("Info", "Info", "Info", SetAccountInfo, $SetUserData,
+"(I)nfo View/change a user's passfile record")
+("Permissions", "Permissions", "Exit", SetPermissions, $SetUserPerms,
+"(P)ermissions View/change a user's permissions")
+("Xempt", "Xempt", "Exit", ExemptUser, $ToggleExempt,
+"(X)empt Mark/unmark account to be immune to user clean")
+("New Board", "New Board", "New", AddBoard, $AddBoard,
+"(N)ew Board Add a board")
+("Board Delete", "Board Delete", "Board ", DeleteBoard, $DeleteBoard,
+"(B)oard Delete Delete a board")
+("Change Board", "Change", "Change", SetBoardInfo, $ChangeBoard,
+"(C)hange Board Set permissions/attributes of a board")
+("Managers", "Managers", "Managers", SetBoardMgrs, $SetBoardMgrs,
+"(M)anagers Set the list of managers for a board")
+# Use this at your own risk! Note the mode of 11 -- see also ~bbs/etc/modes
+#("Shell", "Exit", "Exit", ShellEscape, $exec "/bin/sh:TERM=%T:11",
+#"(S)hell Escape to a UNIX shell")
+("Help", "Help", "Help", 0, $Help,
+"(H)elp Get this help screen")
+("Exit", "Exit", "Exit", 0, $EndMenu,
+"(E)xit Exit to Main Menu")
+}
+
+menu "Talk", "Interactive Talk Menu", "Enter Talk Command: ", "$UE" {
+("Users", "Users", "Exit", Users, $OnlineUsers,
+"(U)sers Show currently connected users")
+("List", "List", "Exit", Users, $ShortList,
+"(L)ist Condensed version of (U)sers")
+("Monitor", "Users", "Exit", Users, $Monitor,
+"(M)onitor (L)ist with automatic updates")
+("Query", "Query", "Query", GetPlan, $Query,
+"(Q)uery Read a user's profile")
+("Chat", "Chat", "Chat", Chat, $Chat,
+"(C)hat Enter the Chat system")
+("Talk", "Talk", "Exit", Page, $Talk,
+"(T)alk Talk to another user")
+("Pager", "Users", "Exit", SetPager, $SetPager,
+"(P)ager Set your talk pager status")
+("Override", "Users", "Exit", SetOverrides, $SetOverrides,
+"(O)verride Allow only certain users to page")
+("Kick", "Users", "Exit", Kick, $Kick,
+"(K)ick Kick user off of system")
+("Date", "Date", "Date", 0, $ShowDate,
+"(D)ate Show local date and time")
+("Help", "Help", "Help", 0, $Help,
+"(H)elp Get this help screen")
+("Exit", "Exit", "Exit", 0, $EndMenu,
+"(E)xit Exit to main menu")
+}
+
+# Below is the configuration for the read menus (one for posts, one for
+# mail, one for files). You probably don't want to mess with these, but if
+# you must, here's a quick tour:
+#
+# The first string after "readmenu" is "Mail", "Main", or "File". DO NOT
+# alter these. The next nine strings compose the stuff displayed at the
+# top of the read menu screen. Change as you wish, but make sure you do
+# trial run to see how it looks.
+#
+# Following that are the read menu commands, each enclosed in parentheses.
+# The five fields in each one are:
+# 1. The key which invokes the command. This must be a single character
+# optionally preceded by ^ which indicates a CTRL-key sequence. Note
+# you may have a single command listed more than once as long as this
+# first field is different.
+# 2. The bbs function to invoke. You may NOT use "exec" or "more" here
+# in the main bbs menus. What you see below is all you get, there are
+# no others.
+# 3. The per-board access required for this command. This must be one of
+# the following: 0 (everyone), $$P (user must have post permission),
+# or $$M (user must have board manager permission).
+# 4. The system access required for this command. These are exactly the
+# same as in the main bbs menus -- they come from the ~bbs/etc/access
+# file. These have priority over the third field (per-board), that is
+# the per-board access is not checked if the system access check fails.
+# 5. A help string to be displayed by the MailReadHelp, MainReadHelp, or
+# FileReadHelp commands. If this string is empty (""), no line for the
+# command will be displayed by the Help commands.
+
+readmenu "Mail",
+"Interactive Read Menu (FOR Mail)", "",
+"(n)ext Message (p)revious Message (r)ead Message (e)xit Read Menu",
+"(##<cr>) go to message ## ($) go to last message (h) Get Help Screen",
+"ENT", "Size", "From", "Subject", "Mail Read Menu Help Screen" {
+('r', $MailDisplay, 0, ReadMessage,
+"r Read current message")
+#('^M', $MailDisplay, 0, ReadMessage,
+#"")
+('S', $SequentialReadMail,0, EnumHeaders,
+"S Sequentially read ALL mail from cursor")
+('R', $MailReply, $$P, SendMail,
+"R Reply to sender of current message")
+('G', $GroupReply, $$P, SendMail,
+"G Reply to sender and all other recipients")
+('d', $MailDelete, $$M, DeleteMessage,
+"d Delete current message")
+('D', $MailDelRange, $$M, DeleteRange,
+"D Delete range of mail messages")
+('F', $Forward, 0, ForwardMessage,
+"F Forward mail to your home mailbox")
+('h', $MailReadHelp, 0, 0,
+"h Get this help screen")
+}
+
+readmenu "Main",
+"Interactive Read Menu", "Current Board",
+"(n)ext Message (p)revious Message (r)ead Message (e)xit Read Menu",
+"##<cr> go to message ## <CTRL-P> post a message (h) Get a HELP screen",
+"ENT", "Size", "Owner", "Title", "Read Menu Help Screen" {
+('r', $PostDisplay, 0, ReadMessage,
+"r Read current message")
+#('^M', $PostDisplay, 0, ReadMessage,
+#"")
+('^P', $PostMessage, $$P, Post,
+"CTRL-P Post a message on this board")
+('s', $ReadMenuSelect, 0, BoardNames,
+"s Select a new board")
+('S', $SequentialRead, 0, EnumHeaders,
+"S Sequentially read new messages from cursor")
+('d', $PostDelete, 0, DeleteMessage,
+"d Delete current message (if owned)")
+('D', $PostDelRange, $$M, DeleteRange,
+"D Delete range of messages")
+('m', $PostMark, $$M, MarkMessage,
+"m Mark message for non-deletion")
+('t', $PostMove, $$P, MoveMessage,
+"t Toss (move) post to another board")
+('E', $PostEdit, $$M, ReplaceMessage,
+"E Edit a post")
+('F', $Forward, 0, ForwardMessage,
+"F Forward post to your home mailbox")
+('h', $MainReadHelp, 0, 0,
+"h Get this help screen")
+}
+
+readmenu "File",
+"Interactive Download Menu", "Current File Board",
+"(n)ext File (p)revious File (v)iew File (e)xit Download Menu",
+"##<cr> go to file ## (r)ecieve file (h) Get a HELP screen",
+"ENT", "T", "Size", "Filename", "Download Menu Help Screen" {
+('^M', $FileChdir, 0, FileChdir,
+"RETURN Change directory")
+('^J', $FileChdir, 0, FileChdir,
+"")
+('t', $FileReadMenuProto, 0, SetProtocol,
+"t Set download protocol")
+('v', $FileView, 0, Download,
+"v View a text file")
+('r', $FileReceive, 0, Download,
+"r Recieve (download) selected file")
+('F', $FileForward, 0, ForwardFile,
+"F Mail file to yourself -- uuencodes if binary")
+('s', $FileReadMenuSelect,0, FileBoardNames,
+"s Select a new file board")
+('h', $FileReadHelp, 0, 0,
+"h Get this help screen")
+}
diff --git a/config/modes b/config/modes
new file mode 100644
index 0000000..4b73f1c
--- /dev/null
+++ b/config/modes
@@ -0,0 +1,27 @@
+# EBBS Modes file
+#
+# The first 11 modes are used by the system. Users may get confused if
+# you change the meaning of these. 0, 1, and 2 should not be set.
+#
+# From mode 11 up, they are defined locally to be used in the menu.desc
+# file as an argument in the "exec" and "exec.pause" menu commands.
+#
+# Format: <mode-number>:<mode-char>:<mode-string>:<pageable>
+#
+# The maximum allowed mode number is 31.
+# Mode-string may be up to 9 characters. Mode-char is a single character.
+# These are what shows up in the (U)sers, (L)ist, and (M)onitor commands.
+# Place an 'N' in the <pageable> field to denote that a user in
+# this mode cannot be paged under any circumstances. This is a GOOD idea
+# for any mode you add!
+#
+3:M:Mail:
+4:R:Reading:
+5:P:Posting:
+6:U:UL/DL:N
+7:C:Chat:
+8:m:Monitor:
+9:T:Talk:
+10:p:Page:
+# The following might go with the (A)dmin/(S)hell example in menu.desc.
+#11:S:Shell:N
diff --git a/config/passwds b/config/passwds
new file mode 100644
index 0000000..1fa4ecd
--- /dev/null
+++ b/config/passwds
@@ -0,0 +1,12 @@
+# This is the bbs passwd file. DO NOT (!) edit it by hand unless you
+# know EXACTLY what you're doing! The records in this file MUST be all
+# the right length and formatted correctly.
+#
+# All data in this file may be manipulated by bbs functions.
+#
+# The following entry is for the SYSOP account with all privileges and
+# password as the initial password. I highly recommend you change it.
+# Note that ~bbs/home/SYSOP and ~bbs/home/SYSOP/mail must exist for this
+# account to be functional. Admin/(A)dd User and addacct do that for you.
+#
+SYSOP :AA6tQYSfGxd/A :000003ff:0000:System Operator :
diff --git a/config/permstrs b/config/permstrs
new file mode 100644
index 0000000..830202e
--- /dev/null
+++ b/config/permstrs
@@ -0,0 +1,26 @@
+# This file lists the names of the permission bits.
+#
+# They are referenced in the access file to determine what permissions
+# are needed to access each bbs function.
+#
+# The following are reserved and should NOT be altered.
+# Basic-1 through Basic-4 are given to each account automatically at creation.
+# Sysop permission is needed to alter a user's permissions.
+#
+Basic-1
+Basic-2
+Basic-3
+Basic-4
+Sysop
+#
+# The rest (up to 27 more, or 32 total) are only referenced in the
+# access and menu.desc files. You can name them as you please.
+# IMPORTANT! Never alter the order of permission names once you start
+# using them. That is, only add to the END of the list and if you want
+# to delete a permission name, put something else in its place.
+#
+AccountMgr
+BoardMgr
+SeeCloak
+Cloak
+WelcomeMgr
diff --git a/config/protos b/config/protos
new file mode 100644
index 0000000..72d9ec1
--- /dev/null
+++ b/config/protos
@@ -0,0 +1,19 @@
+# This file lists the protocols available for uploads/downloads.
+# You have to tailor this for your own site.
+#
+# Record format:
+# proto-name:send-command:receive-command
+#
+# If the receive-command is prefixed by '|', then the destination is
+# opened as standard out of the receive-command. For example to do an
+# ASCII upload we just "cat > filename".
+#
+# If the receive-command is prefixed by '-'. then the receive program
+# does not want filenames in the command. The default is to append the
+# filename.
+
+Kermit:/usr/bin/kermit -si:/usr/bin/kermit -ri:
+Xmodem:/usr/bin/sx:/usr/bin/rx:
+Ymodem:/usr/bin/sb:-/usr/bin/rb:
+Zmodem:/usr/bin/sz:-/usr/bin/rz:
+ASCII:cat:|cat:
diff --git a/config/welcome b/config/welcome
new file mode 100644
index 0000000..f3b21e1
--- /dev/null
+++ b/config/welcome
@@ -0,0 +1,22 @@
+ Eagles
+ ... ....... .o----------o. ...---o Bulletin
+ .-^" "^-..#^##oo##" o##o#^"".....---. Board
+ .o" ........:" """ " "o Service
+ #--^ "o .---. .---. "---o #
+ # # :-: # # :-: # "o #
+ "# "---" "---" # #
+ .^ .##. #
+ o^ .###^-o----^
+ o-"""-. --"""-o .- .####" "o
+ # ### ### #^"#.#####" "o
+ #^o... ...-#" o^o#### ^.
+ # # #"---o.........-o--""# # # ###^ ""-.
+ o# # # # # # # # o" ##" ""-
+ o" #^# # # # # # ##".#^
+ .^ ^.""---..# # #...-^-"".o""
+.^ """--...:"""""""""" ..o-^"
+ """#""#""""""
+ ^----^
+
+
+
diff --git a/conv.c b/conv.c
new file mode 100644
index 0000000..9b222e1
--- /dev/null
+++ b/conv.c
@@ -0,0 +1,149 @@
+/*
+Eagles Bulletin Board System
+Copyright (C) 1994, Ray Rocker, rocker@datasync.com
+
+Changes Copyright (C)1995, Alexis Yushin, alexis@ww.net
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "client.h"
+#if LACKS_MALLOC_H
+# include <stdlib.h>
+#else
+# include <malloc.h>
+#endif
+
+unsigned int conv_table[256][2] = {
+ {0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4},
+ {5, 5}, {6, 6}, {7, 7}, {8, 8}, {9, 9},
+ {10, 10}, {11, 11}, {12, 12}, {13, 13}, {14, 14},
+ {15, 15}, {16, 16}, {17, 17}, {18, 18}, {19, 19},
+ {20, 20}, {21, 21}, {22, 22}, {23, 23}, {24, 24},
+ {25, 25}, {26, 26}, {27, 27}, {28, 28}, {29, 29},
+ {30, 30}, {31, 31}, {32, 32}, {33, 33}, {34, 34},
+ {35, 35}, {36, 36}, {37, 37}, {38, 38}, {39, 39},
+ {40, 40}, {41, 41}, {42, 42}, {43, 43}, {44, 44},
+ {45, 45}, {46, 46}, {47, 47}, {48, 48}, {49, 49},
+ {50, 50}, {51, 51}, {52, 52}, {53, 53}, {54, 54},
+ {55, 55}, {56, 56}, {57, 57}, {58, 58}, {59, 59},
+ {60, 60}, {61, 61}, {62, 62}, {63, 63}, {64, 64},
+ {65, 65}, {66, 66}, {67, 67}, {68, 68}, {69, 69},
+ {70, 70}, {71, 71}, {72, 72}, {73, 73}, {74, 74},
+ {75, 75}, {76, 76}, {77, 77}, {78, 78}, {79, 79},
+ {80, 80}, {81, 81}, {82, 82}, {83, 83}, {84, 84},
+ {85, 85}, {86, 86}, {87, 87}, {88, 88}, {89, 89},
+ {90, 90}, {91, 91}, {92, 92}, {93, 93}, {94, 94},
+ {95, 95}, {96, 96}, {97, 97}, {98, 98}, {99, 99},
+ {100, 100}, {101, 101}, {102, 102}, {103, 103}, {104, 104},
+ {105, 105}, {106, 106}, {107, 107}, {108, 108}, {109, 109},
+ {110, 110}, {111, 111}, {112, 112}, {113, 113}, {114, 114},
+ {115, 115}, {116, 116}, {117, 117}, {118, 118}, {119, 119},
+ {120, 120}, {121, 121}, {122, 122}, {123, 123}, {124, 124},
+ {125, 125}, {126, 126}, {127, 127}, {128, 128}, {129, 129},
+ {130, 130}, {131, 131}, {132, 132}, {133, 133}, {134, 134},
+ {135, 135}, {136, 136}, {137, 137}, {138, 138}, {139, 139},
+ {140, 140}, {141, 141}, {142, 142}, {143, 143}, {144, 144},
+ {145, 145}, {146, 146}, {147, 147}, {148, 148}, {149, 149},
+ {150, 150}, {151, 151}, {152, 152}, {153, 153}, {154, 154},
+ {155, 155}, {156, 156}, {157, 157}, {158, 158}, {159, 159},
+ {160, 160}, {161, 161}, {162, 162}, {163, 163}, {164, 164},
+ {165, 165}, {166, 166}, {167, 167}, {168, 168}, {169, 169},
+ {170, 170}, {171, 171}, {172, 172}, {173, 173}, {174, 174},
+ {175, 175}, {176, 176}, {177, 177}, {178, 178}, {179, 179},
+ {180, 180}, {181, 181}, {182, 182}, {183, 183}, {184, 184},
+ {185, 185}, {186, 186}, {187, 187}, {188, 188}, {189, 189},
+ {190, 190}, {191, 191}, {192, 192}, {193, 193}, {194, 194},
+ {195, 195}, {196, 196}, {197, 197}, {198, 198}, {199, 199},
+ {200, 200}, {201, 201}, {202, 202}, {203, 203}, {204, 204},
+ {205, 205}, {206, 206}, {207, 207}, {208, 208}, {209, 209},
+ {210, 210}, {211, 211}, {212, 212}, {213, 213}, {214, 214},
+ {215, 215}, {216, 216}, {217, 217}, {218, 218}, {219, 219},
+ {220, 220}, {221, 221}, {222, 222}, {223, 223}, {224, 224},
+ {225, 225}, {226, 226}, {227, 227}, {228, 228}, {229, 229},
+ {230, 230}, {231, 231}, {232, 232}, {233, 233}, {234, 234},
+ {235, 235}, {236, 236}, {237, 237}, {238, 238}, {239, 239},
+ {240, 240}, {241, 241}, {242, 242}, {243, 243}, {244, 244},
+ {245, 245}, {246, 246}, {247, 247}, {248, 248}, {249, 249},
+ {250, 250}, {251, 251}, {252, 252}, {253, 253}, {254, 254},
+ {255, 255}
+};
+
+/*ARGSUSED*/
+_conv_init_func(indx, rec, arg)
+int indx;
+char *rec;
+void *arg;
+{
+ char *equals;
+ unsigned int r;
+ unsigned int e;
+ strip_trailing_space(rec);
+
+ if ((equals = strchr(rec, '=')) == NULL) return S_OK;
+ *equals++ = '\0';
+ strip_trailing_space(rec);
+ strip_trailing_space(equals);
+ while (*rec && isspace(*rec)) rec++;
+ while (*equals && isspace(*equals)) equals++;
+
+ r = atoi(rec);
+ e = atoi(equals);
+
+ if((r > 255) || (e > 255)) {
+ return S_OK;
+ };
+
+ conv_table[r][0] = e;
+ conv_table[e][1] = r;
+
+ return S_OK;
+}
+
+int conv_init(charset)
+char *charset;
+{
+ FILE *fp;
+ unsigned int i;
+#if REMOTE_CLIENT
+ char charsetfile[PATHLEN+1] = "";
+#else
+ char charsetfile[PATHLEN+1] = "etc/charset-";
+#endif
+
+ if(!charset || !*charset) {
+ return -1;
+ }
+
+ if(!strcmp(charset, "ascii")) {
+ for(i = 0; i < 256; i++) {
+ conv_table[i][0] = conv_table[i][1] = i;
+ };
+ return 0;
+ };
+
+ strncat(charsetfile, charset, PATHLEN - 12);
+
+ if ((fp = fopen(charsetfile, "r")) == NULL) {
+ return -1;
+ }
+ fclose(fp);
+
+ for(i = 0; i < 256; i++) {
+ conv_table[i][0] = conv_table[i][1] = i;
+ };
+ _record_enumerate(charsetfile, 0, _conv_init_func, NULL);
+ return 0;
+}
diff --git a/delacct.c b/delacct.c
new file mode 100644
index 0000000..d977006
--- /dev/null
+++ b/delacct.c
@@ -0,0 +1,192 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "server.h"
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <sys/stat.h>
+
+extern char *optarg;
+extern int optind;
+
+extern USERDATA user_params;
+
+struct userclean {
+ time_t cutoff;
+ time_t age;
+ int inactives;
+ int nologins;
+ int for_grins;
+};
+
+do_delete(userid, really)
+char *userid;
+int really;
+{
+ int rc;
+ if (really) {
+ rc = local_bbs_delete_account(userid);
+ if (rc == S_OK) printf("DELETED %s\n", userid);
+ else printf("ERROR %d deleting %s\n", rc, userid);
+ }
+ else printf("would have DELETED %s\n", userid);
+}
+
+user_clean_func(indx, userid, info)
+int indx;
+char *userid;
+struct userclean *info;
+{
+ ACCOUNT acct;
+ time_t lastlog;
+
+ if (local_bbs_get_userinfo(userid, &acct) != S_OK) return 0;
+
+ if (info->inactives) {
+ if (acct.flags & FLG_EXEMPT) {
+ return 0;
+ }
+ if (acct.perms & PERM_SYSOP) {
+ return 0;
+ }
+ }
+
+ if (acct.lastlogin == 0) {
+ /* Never logged in, so go by creation time of home directory */
+ /* I wish I was saving creation time somewhere besides the log */
+ PATH buf;
+ struct stat stbuf;
+ get_home_directory(acct.userid, buf);
+ if (stat(buf, &stbuf) == 0) acct.lastlogin = (LONG)stbuf.st_mtime;
+ }
+ else {
+ if (info->nologins) return 0;
+ }
+
+ if (!info->inactives || acct.lastlogin + info->age < info->cutoff) {
+ do_delete(acct.userid, !info->for_grins);
+ }
+
+ return 0;
+}
+
+usage(prog)
+char *prog;
+{
+ fprintf(stderr,
+ "Usage: %s [-a age] [-c] [-d bbsdir] [-n] [-t] userid ...\n", prog);
+ fprintf(stderr,
+ " -c means clean all inactive accounts (subject to age)\n");
+ fprintf(stderr,
+ " -n means only clean accounts that have never been used\n");
+ fprintf(stderr,
+ " -t is test mode: only show what would happen\n");
+}
+
+main(argc, argv)
+int argc;
+char *argv[];
+{
+ struct userclean info;
+ char *homedir = NULL;
+ NAMELIST names;
+ int c, cflg = 0;
+
+ info.age = 30; /* 30 days default */
+ info.for_grins = 0;
+ info.nologins = 0;
+ info.inactives = 0;
+
+ /* I should copy the passfile somewhere for safety */
+
+ while ((c = getopt(argc, argv, "a:cd:nt?")) != -1)
+ {
+ switch (c)
+ {
+ case 'a':
+ info.age = atoi(optarg);
+ break;
+ case 'c':
+ cflg++;
+ break;
+ case 'd':
+ homedir = optarg;
+ break;
+ case 'n':
+ info.nologins++;
+ break;
+ case 't':
+ info.for_grins++;
+ break;
+ case '?':
+ usage(argv[0]);
+ return 2;
+ }
+ }
+
+ /* Just a safety feature, delete it if you wish */
+ if (info.age < 7) {
+ fprintf(stderr, "%s: age parameter must be at least 7\n", argv[0]);
+ return 1;
+ }
+
+ if (home_bbs(homedir) == -1) {
+ fprintf(stderr, "%s: Cannot chdir to %s\n", argv[0], homedir);
+ return -1;
+ }
+
+ if (local_bbs_initialize(NULL) != S_OK) {
+ fprintf(stderr, "%s: local_bbs_initialize failed\n", argv[0]);
+ return 1;
+ }
+
+ /* Identify ourself for the log file */
+ strcpy(user_params.u.userid, "[delacct]");
+ user_params.perms = ~0;
+ user_params.access[C_SEEALLAINFO] = '1';
+
+ names = NULL;
+ while (optind < argc) {
+ add_namelist(&names, argv[optind], NULL);
+ optind++;
+ }
+
+ if (names == NULL) {
+ if (!cflg) {
+ fprintf(stderr, "%s: -c not specified and no names given\n", argv[0]);
+ local_bbs_disconnect();
+ return 2;
+ }
+ /* Delete all inactive accounts */
+ info.inactives++;
+ local_bbs_acctnames(&names, NULL);
+ }
+
+ time(&info.cutoff);
+ info.age *= 86400; /* convert to seconds */
+
+ apply_namelist(names, user_clean_func, &info);
+
+ free_namelist(&names);
+ local_bbs_disconnect();
+ return 0;
+}
+
diff --git a/edit.c b/edit.c
new file mode 100644
index 0000000..1e2183b
--- /dev/null
+++ b/edit.c
@@ -0,0 +1,94 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "server.h"
+
+extern char *ExpandString __P((char *));
+
+/*
+ List of available editors for the local client.
+ This stuff is completely moot for the remote client.
+ bbs_set_editor is in login.c.
+*/
+
+#define EDITORFILE "etc/editors"
+
+/* Struct for getting entries from the etc/editors file. */
+
+typedef struct _EDITSTRUCT {
+ NAME name;
+ PATH bin;
+ PATH envp;
+} EDITSTRUCT;
+
+editent_to_es(rec, es)
+char *rec;
+EDITSTRUCT *es;
+{
+ rec = _extract_quoted(rec, es->name, sizeof(es->name));
+ rec = _extract_quoted(rec, es->bin, sizeof(es->bin));
+ rec = _extract_quoted(rec, es->envp, sizeof(es->envp));
+ return S_OK;
+}
+
+local_bbs_get_editor(name, path, envp)
+char *name;
+char *path;
+char *envp;
+{
+ int rc;
+ EDITSTRUCT es;
+ memset(&es, 0, sizeof es);
+ rc = _record_find(EDITORFILE, _match_first, name, editent_to_es, &es);
+ if (rc == S_OK) {
+ strncpy(path, es.bin, PATHLEN);
+ strncpy(envp, ExpandString(es.envp), PATHLEN);
+ }
+ else rc = S_NOSUCHEDITOR;
+ return rc;
+}
+
+/*ARGSUSED*/
+_fill_editornames(indx, rec, lc)
+int indx;
+char *rec;
+struct listcomplete *lc;
+{
+ EDITSTRUCT es;
+ editent_to_es(rec, &es);
+ if (!strncasecmp(es.name, lc->str, strlen(lc->str)))
+ add_namelist(lc->listp, es.name, NULL);
+
+ return S_OK;
+}
+
+local_bbs_enum_editors(list, complete)
+NAMELIST *list;
+char *complete;
+{
+ struct listcomplete lc;
+ create_namelist(list);
+ lc.listp = list;
+ lc.str = complete == NULL ? "" : complete;
+ _record_enumerate(EDITORFILE, 0, _fill_editornames, &lc);
+ return S_OK;
+}
+
+
diff --git a/env.c b/env.c
new file mode 100644
index 0000000..a294e76
--- /dev/null
+++ b/env.c
@@ -0,0 +1,117 @@
+
+#include "client.h"
+
+char *default_env = "TERM=dumb,SHELL=/bin/false" ;
+
+
+char *ExpandString(s)
+char *s ;
+{
+ static char buf[1024] ;
+ char *sr = buf ;
+ int cnt = 0 ;
+ static ACCOUNT acct ;
+ static int acct_filled = 0 ;
+
+ for(cnt=0;cnt<sizeof(buf);cnt++,sr++) {
+ *sr = *s ;
+ if(*s == '\0') break ;
+ if(*s == '%') {
+ if(!acct_filled) {
+ bbs_owninfo(&acct) ;
+ acct_filled = 1 ;
+ }
+ switch(*(s+1)) {
+ int len ;
+ case 'T':
+ case 't':
+ len =strlen(acct.terminal) ;
+ if(len+cnt<sizeof(buf)) {
+ strcpy(sr,acct.terminal) ;
+ sr += len-1 ;
+ cnt += len-1 ;
+ }
+ s++ ;
+ break ;
+ case 'I':
+ case 'i':
+ len = strlen(acct.userid) ;
+ if(len+cnt<sizeof(buf)) {
+ strcpy(sr,acct.userid) ;
+ sr += len-1 ;
+ cnt += len-1 ;
+ }
+ s++ ;
+ break ;
+ case 'U':
+ case 'u':
+ len = strlen(acct.username) ;
+ if(len+cnt<sizeof(buf)) {
+ strcpy(sr,acct.username) ;
+ sr += len-1 ;
+ cnt += len-1 ;
+ }
+ s++ ;
+ break ;
+ case 'E':
+ case 'e':
+ len = strlen(acct.editor) ;
+ if(len+cnt<sizeof(buf)) {
+ strcpy(sr,acct.editor) ;
+ sr += len-1 ;
+ cnt += len-1 ;
+ }
+ s++ ;
+ break ;
+ case 'H':
+ case 'h':
+ len = strlen(acct.fromhost) ;
+ if(len+cnt<sizeof(buf)) {
+ strcpy(sr,acct.fromhost) ;
+ sr += len-1 ;
+ cnt += len-1 ;
+ }
+ s++ ;
+ break ;
+ default:
+ sr-- ;
+ break ;
+ }
+ }
+ s++ ;
+ }
+
+ return buf ;
+}
+
+
+parse_environment(s)
+char *s ;
+{
+ char buf[4096] ;
+ char *p1, *p2, *p3 ;
+ strncpy(buf,s,sizeof(buf)) ;
+ buf[sizeof(buf)-1] = '\0' ;
+ p1 = buf ;
+ while(p1!=NULL) {
+ p2 = strchr(p1,'=') ;
+ p3 = strchr(p1,',') ;
+ if(p2 == NULL)
+ break ;
+ *p2 = '\0' ;
+ if(p3!=NULL)
+ *p3 = '\0' ;
+ bbssetenv(p1,p2+1) ;
+ if(p3== NULL)
+ break ;
+ p1 = p3 + 1 ;
+
+ }
+}
+
+
+parse_default()
+{
+ parse_environment(default_env) ;
+}
+
diff --git a/exec.c b/exec.c
new file mode 100644
index 0000000..9fa8716
--- /dev/null
+++ b/exec.c
@@ -0,0 +1,138 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "server.h"
+#include <fcntl.h>
+#include <signal.h>
+
+#define MAXCOMSZ (1024) /* Maximum length of do_exec command */
+#define MAXFILELEN (80) /* Maximum length of the executable file name */
+#define MAXARGS (40) /* Maximum number of args to a do_exec command */
+
+#define LOOKFIRST (0)
+#define LOOKLAST (1)
+#define QUOTEMODE (2)
+
+_set_io(fname, fd, append)
+char *fname;
+int fd;
+int append;
+{
+ int newfd;
+ int mode;
+ if (fd == 0) mode = O_RDONLY;
+ else {
+ mode = O_WRONLY | O_CREAT;
+ if (append) mode |= O_APPEND;
+ }
+ close(fd);
+ newfd = open(fname, mode, 0600);
+ if (newfd == -1) {
+ bbslog(0, "ERROR _set_io: open failed: %s mode %d\n", fname, mode);
+ return -1;
+ }
+ if (newfd != fd && dup2(newfd, fd) == -1) {
+ bbslog(0, "ERROR _set_io: dup2 failed\n");
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ Adapted from Pirates BBS 1.6, do_exec in stuff.c
+ Copyright (C) 1990, Edward Luke, lush@Athena.EE.MsState.EDU
+ This one is suitable for the server, doesn't try to set the screen
+*/
+
+execute(com, wd, infile, outfile, errfile, envp, append)
+char *com, *wd, *infile, *outfile, *errfile;
+char **envp;
+int append;
+{
+ char path[MAXFILELEN] ;
+ char pcom[MAXCOMSZ] ;
+ char *arglist[MAXARGS] ;
+ register int i,len ;
+ register int argptr ;
+ int status, pid, w ;
+ int pmode ;
+ int saved_alarm ;
+ void (*isig)(), (*qsig)();
+
+ strncpy(pcom,com,MAXCOMSZ) ;
+ len = strlen(com)+1;
+ if (len > MAXCOMSZ) len = MAXCOMSZ;
+ pmode = LOOKFIRST ;
+ for(i=0,argptr=0;i<len;i++) {
+ if(pcom[i] == '\0')
+ break ;
+ if(pmode == QUOTEMODE) {
+ if(pcom[i] == '\"') {
+ pmode = LOOKFIRST ;
+ pcom[i] = '\0' ;
+ continue ;
+ }
+ continue ;
+ }
+ if(pcom[i] == '\"') {
+ pmode = QUOTEMODE ;
+ arglist[argptr++] = &pcom[i+1] ;
+ if(argptr+1 == MAXARGS)
+ break ;
+ continue ;
+ }
+ if(pmode == LOOKFIRST)
+ if(pcom[i] != ' ') {
+ arglist[argptr++] = &pcom[i] ;
+ if(argptr+1 == MAXARGS)
+ break ;
+ pmode = LOOKLAST ;
+ } else continue ;
+ if(pcom[i] == ' ') {
+ pmode = LOOKFIRST ;
+ pcom[i] = '\0' ;
+ }
+ }
+ arglist[argptr] = NULL ;
+ if(argptr == 0)
+ return -1 ;
+ strncpy(path,arglist[0],MAXFILELEN) ;
+ saved_alarm = alarm(0);
+ if((pid = fork()) == 0) {
+ if(wd)
+ if(chdir(wd)) {
+ bbslog(0, "ERROR execute: chdir failed: %s\n", wd);
+ exit(-1) ;
+ }
+ if (infile) _set_io(infile, 0, append);
+ if (outfile) _set_io(outfile, 1, append);
+ if (errfile) _set_io(errfile, 2, append);
+ execve(path, arglist, envp);
+ bbslog(0, "ERROR execute: execve failed: %s\n", path);
+ exit(-1) ;
+ }
+ isig = signal(SIGINT, SIG_IGN);
+ qsig = signal(SIGQUIT, SIG_IGN);
+ while ((w = wait(&status)) != pid && w != -1);
+ if (saved_alarm) alarm(saved_alarm);
+ signal(SIGINT, isig) ;
+ signal(SIGQUIT, qsig) ;
+ return((w == -1)? w: status) ;
+}
diff --git a/files.c b/files.c
new file mode 100644
index 0000000..9be8227
--- /dev/null
+++ b/files.c
@@ -0,0 +1,1051 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "server.h"
+#include <sys/stat.h>
+#include <fcntl.h>
+#if LACKS_MALLOC_H
+# include <stdlib.h>
+#else
+# include <malloc.h>
+#endif
+#include <time.h>
+#include <utime.h>
+#ifdef NeXT
+#include <sys/dir.h>
+#include <sys/dirent.h>
+#include <sys/stat.h>
+#else
+#include <dirent.h>
+#endif
+#include <ctype.h>
+
+#ifdef NeXT
+typedef unsigned short mode_t;
+struct utimbuf {
+ time_t actime;
+ time_t modtime;
+};
+#endif
+
+extern LONG mail_file_to_outside __P((char *, char *, char *, int, int));
+extern LONG forward_file_to_outside __P((char *, char *, int));
+
+extern SERVERDATA server; /* for append_sig */
+
+#define MAIL_READBITS_NAME "$MAIL$"
+
+typedef struct _FILENODE {
+ SHORT fileid;
+ SHORT flags;
+ LONG size;
+ time_t mtime;
+ struct _FILENODE *next;
+} FILENODE;
+
+typedef struct _FILENODE *FILELIST;
+
+struct _openboard {
+ int btype;
+ OPENINFO oi;
+ READINFO ri;
+ PATH bdir;
+ PATH wdir;
+ FILELIST bcache;
+ time_t bsync;
+} _currbrd;
+
+
+#define UNMARKED_FILE_MODE 0660
+#define MARKED_FILE_MODE 0640
+
+free_filelist(list)
+FILELIST *list;
+{
+ FILENODE *curr = *list, *next;
+ while (curr != NULL) {
+ next = curr->next;
+ free(curr);
+ curr = next;
+ }
+ *list = NULL;
+}
+
+insert_filelist(list, node)
+FILELIST *list;
+FILENODE *node;
+{
+ FILENODE *curr = *list, *prev = NULL;
+
+ if (*list == NULL || (*list)->mtime > node->mtime ||
+ ((*list)->mtime == node->mtime && (*list)->fileid > node->fileid)) {
+ node->next = *list;
+ *list = node;
+ return S_OK;
+ }
+
+ do {
+ prev = curr;
+ curr = curr->next;
+ } while (curr && (curr->mtime < node->mtime ||
+ (curr->mtime == node->mtime && curr->fileid < node->fileid)));
+
+ node->next = curr;
+ prev->next = node;
+ return S_OK;
+}
+
+build_filelist(dir, list, fn, arg)
+char *dir;
+FILELIST *list;
+int (*fn)();
+void *arg;
+{
+ DIR *dp;
+ struct dirent *dent;
+ PATH fname;
+ char *fnamebase;
+ struct stat stbuf;
+ FILENODE *node;
+ SHORT fileid;
+
+ free_filelist(list);
+
+ strcpy(fname, dir);
+ strcat(fname, "/");
+ fnamebase = fname+strlen(fname);
+
+ if ((dp = opendir(dir)) == NULL) {
+ return S_SYSERR;
+ }
+
+ while ((dent = readdir(dp)) != NULL) {
+ strcpy(fnamebase, dent->d_name);
+ fileid = hex2SHORT(dent->d_name);
+ if (fileid > 0 && fileid <= BBS_MAX_FILES && stat(fname, &stbuf) == 0) {
+ if (stbuf.st_size == 0) continue;
+ else if ((node = (FILENODE *)malloc(sizeof(*node))) != NULL) {
+ node->fileid = fileid;
+ node->flags = FILE_UNREAD;
+ if ((stbuf.st_mode & 0777) == MARKED_FILE_MODE)
+ node->flags |= FILE_MARKED;
+ node->size = stbuf.st_size;
+ node->mtime = stbuf.st_mtime;
+ insert_filelist(list, node);
+ if (fn) fn(node, arg);
+ }
+ }
+ }
+
+ closedir(dp);
+ return S_OK;
+}
+
+_enum_files(dir, fn, arg)
+char *dir;
+int (*fn)();
+void *arg;
+{
+ PATH fname;
+ DIR *dp;
+ struct dirent *dent;
+ int indx = 0;
+ int isdir;
+
+ if ((dp = opendir(dir)) == NULL) {
+ return S_SYSERR;
+ }
+
+ while ((dent = readdir(dp)) != NULL) {
+ isdir = 0;
+ if (dent->d_name[0] == '.') {
+ if (strcmp(dent->d_name, "..") || !strcmp(dir, _currbrd.bdir))
+ continue;
+ }
+ strcpy(fname, dir);
+ strcat(fname, "/");
+ strcat(fname, dent->d_name);
+ if (is_directory(fname)) isdir++;
+ if (fn(indx++, dir, dent->d_name, isdir, arg) == ENUM_QUIT) break;
+ }
+
+ closedir(dp);
+ return S_OK;
+}
+
+FILENODE *
+_bcache_find(fileid)
+SHORT fileid;
+{
+ FILENODE *trav = _currbrd.bcache;
+ while (trav) {
+ if (trav->fileid == fileid) break;
+ trav = trav->next;
+ }
+ return trav;
+}
+
+_msg_tally(node, newinfo)
+FILENODE *node;
+READINFO *newinfo;
+{
+ _currbrd.oi.totalmsgs++;
+ if ((node->mtime < (time_t)_currbrd.ri.stamp) &&
+ test_readbit(&_currbrd.ri, node->fileid)) {
+ set_readbit(newinfo, node->fileid);
+ node->flags &= ~FILE_UNREAD;
+ }
+ else _currbrd.oi.newmsgs++;
+ return S_OK;
+}
+
+/*ARGSUSED*/
+_file_tally(indx, dir, fname, isdir, arg)
+int indx;
+char *dir;
+char *fname;
+int isdir;
+void *arg;
+{
+ _currbrd.oi.totalmsgs++;
+ return S_OK;
+}
+
+_sync_currbrd()
+{
+ struct stat stbuf;
+ READINFO newinfo;
+ if (stat(_currbrd.wdir, &stbuf)) {
+ _currbrd.oi.totalmsgs = _currbrd.oi.newmsgs = 0;
+ return S_SYSERR;
+ }
+ if (stbuf.st_mtime < _currbrd.bsync) {
+ /* No files created or deleted, so we don't re-read */
+ return S_OK;
+ }
+ _currbrd.oi.totalmsgs = _currbrd.oi.newmsgs = 0;
+ switch (_currbrd.btype) {
+ case BOARD_MAIL:
+ case BOARD_POST:
+ clear_all_readbits(&newinfo);
+ build_filelist(_currbrd.bdir, &_currbrd.bcache, _msg_tally, &newinfo);
+ time((time_t *)&newinfo.stamp);
+ memcpy(&_currbrd.ri, &newinfo, sizeof(_currbrd.ri));
+ break;
+ case BOARD_FILE:
+ _enum_files(_currbrd.wdir, _file_tally, NULL);
+ break;
+ default:
+ return S_NOTOPEN;
+ }
+ time(&_currbrd.bsync);
+ return S_OK;
+}
+
+get_filelist_ids(dir, rinfo)
+char *dir;
+READINFO *rinfo;
+{
+ DIR *dp;
+ struct dirent *dent;
+ SHORT fileid;
+
+ clear_all_readbits(rinfo);
+
+ if ((dp = opendir(dir)) == NULL) {
+ return S_SYSERR;
+ }
+
+ while ((dent = readdir(dp)) != NULL) {
+ fileid = hex2SHORT(dent->d_name);
+ if (fileid > 0 && fileid <= BBS_MAX_FILES)
+ set_readbit(rinfo, fileid);
+ }
+
+ closedir(dp);
+ return S_OK;
+}
+
+fileid_to_fname(dir, fileid, fname)
+char *dir;
+SHORT fileid;
+char *fname;
+{
+ char *eodir;
+ strcpy(fname, dir);
+ strcat(fname, "/");
+ eodir = fname+strlen(fname);
+ SHORTcpy(eodir, fileid);
+ eodir[4] = '\0';
+}
+
+struct _sendmsgstruct {
+ int btype;
+ HEADER hdr;
+ RNAME username;
+ NAMELIST to_list;
+ PATH destfile;
+ char *srcfile;
+ LONG errcode;
+};
+
+append_sig(fd)
+int fd;
+{
+ FILE *fp;
+ PATH sigfile;
+ char buf[81];
+ int i;
+ /*
+ Append current user's sig to open file fd.
+ Honor maxsiglines defined in the global SERVERDATA struct.
+ */
+
+ if (server.maxsiglines == 0) {
+ return S_OK;
+ }
+
+ local_bbs_get_signature(sigfile);
+ if ((fp = fopen(sigfile, "r")) != NULL) {
+ for (i=0; i<server.maxsiglines; i++) {
+ if (fgets(buf, sizeof(buf), fp)) {
+ write(fd, buf, strlen(buf));
+ }
+ }
+ fclose(fp);
+ }
+
+ return S_OK;
+}
+
+_do_message(indx, bname, sm)
+int indx;
+char *bname;
+struct _sendmsgstruct *sm;
+{
+ PATH msgdir;
+ PATH msgfile;
+ READINFO readinfo;
+ SHORT fileid;
+ NAME goodbname;
+ struct stat stbuf;
+ int fd, ok = 0;
+
+ if (indx >= BBS_MAX_MAILRECIPS) return S_OK;
+
+ if (sm->btype == BOARD_MAIL) {
+ ACCOUNT acct;
+ /* If address has a : in it, assume it is going to an external mailer. */
+ if (strchr(bname, ':') != NULL) {
+ LONG rc;
+ if (!_has_access(C_EXTERNMAIL)) rc = S_DENIED;
+ else rc = mail_file_to_outside(sm->srcfile, sm->hdr.title, bname, 0, 0);
+ if (rc != S_OK) {
+ sm->hdr.size |= (1 << indx);
+ sm->errcode = rc;
+ }
+ return S_OK;
+ }
+ if (_lookup_account(bname, &acct) != S_OK) {
+ sm->hdr.size |= (1 << indx);
+ sm->errcode = S_NOSUCHUSER;
+ return S_OK;
+ }
+ /* Deny if: account is marked shared, account has no mail permission,
+ or mail is from outside and account has no external mail permission. */
+ if (acct.flags & FLG_SHARED ||
+ !_they_have_access(C_OPENMAIL, acct.perms) ||
+ (strchr(sm->hdr.owner, ':') &&
+ !_they_have_access(C_EXTERNMAIL, acct.perms))) {
+ sm->hdr.size |= (1 << indx);
+ sm->errcode = S_CANNOTMAIL;
+ return S_OK;
+ }
+ strncpy(goodbname, acct.userid, sizeof(goodbname));
+ get_mail_directory(goodbname, msgdir);
+ }
+ else {
+ BOARD board;
+ if (_lookup_board(bname, &board) != S_OK) {
+ sm->hdr.size |= (1 << indx);
+ sm->errcode = S_NOSUCHBOARD;
+ return S_OK;
+ }
+ strncpy(goodbname, board.name, sizeof(goodbname));
+ get_board_directory(goodbname, msgdir);
+ }
+
+ if (stat(msgdir, &stbuf) == -1 || !S_ISDIR(stbuf.st_mode)) {
+ /* A directory is missing! */
+ bbslog(0, "ERROR _do_message: cannot access %s\n", msgdir);
+ sm->hdr.size |= (1 << indx);
+ sm->errcode = S_SYSERR;
+ return S_OK;
+ }
+ get_filelist_ids(msgdir, &readinfo);
+
+ for (fileid = 1; fileid <= BBS_MAX_FILES; fileid++) {
+ if (test_readbit(&readinfo, fileid)) continue;
+ fileid_to_fname(msgdir, fileid, msgfile);
+ if (sm->destfile[0]) {
+ /* Carbon copy! Hopefully we can just link. */
+ if (link(sm->destfile, msgfile) == 0) {
+ ok++;
+ break;
+ }
+ /* Damn! Copy the stupid thing. */
+ fd = open(msgfile, O_WRONLY|O_CREAT|O_EXCL, UNMARKED_FILE_MODE);
+ if (fd != -1) {
+ append_file(fd, sm->destfile);
+ append_sig(fd);
+ close(fd);
+ ok++;
+ break;
+ }
+ }
+ else {
+ /* First copy. We have to write the file with headers. */
+ fd = open(msgfile, O_WRONLY|O_CREAT|O_EXCL, UNMARKED_FILE_MODE);
+ if (fd != -1) {
+ if (sm->btype == BOARD_MAIL)
+ write_mail_headers(fd, &sm->hdr, sm->username, sm->to_list);
+ else
+ write_post_headers(fd, &sm->hdr, sm->username, goodbname);
+
+ append_file(fd, sm->srcfile);
+ append_sig(fd);
+ close(fd);
+ strncpy(sm->destfile, msgfile, PATHLEN);
+ ok++;
+ break;
+ }
+ }
+ }
+
+ if (ok) {
+ if (sm->btype == BOARD_MAIL) notify_new_mail(bname, 1);
+ }
+ else {
+ /* Too many files, I guess! */
+ bbslog(0, "ERROR _do_message: cannot create message in %s\n", msgdir);
+ sm->hdr.size |= (1 << indx);
+ sm->errcode = S_BOARDFULL;
+ }
+ return S_OK;
+}
+
+local_bbs_mail(from, fromname, to_list, subject, fname, success)
+char *from;
+char *fromname;
+NAMELIST to_list;
+char *subject;
+char *fname;
+LONG *success;
+{
+ struct _sendmsgstruct sm;
+
+ sm.btype = BOARD_MAIL;
+ memset(&sm.hdr, 0, sizeof sm.hdr);
+
+ if (from == NULL) strncpy(sm.hdr.owner, my_userid(), sizeof sm.hdr.owner);
+ else strncpy(sm.hdr.owner, from, sizeof sm.hdr.owner);
+ if (fromname == NULL) strncpy(sm.username,my_username(),sizeof sm.username);
+ else strncpy(sm.username, fromname, sizeof sm.username);
+ strncpy(sm.hdr.title, subject, TITLELEN);
+ sm.to_list = to_list;
+ sm.srcfile = fname;
+ sm.destfile[0] = '\0';
+
+ apply_namelist(to_list, _do_message, &sm);
+
+ /* how's this for a kludge? */
+ *success = sm.hdr.size;
+ return (*success == 0 ? S_OK : sm.errcode);
+}
+
+local_bbs_post(bname, subject, fname)
+char *bname;
+char *subject;
+char *fname;
+{
+ struct _sendmsgstruct sm;
+
+ sm.btype = BOARD_POST;
+ memset(&sm.hdr, 0, sizeof sm.hdr);
+ strncpy(sm.hdr.owner, my_userid(), sizeof sm.hdr.owner);
+ strncpy(sm.username, my_username(), sizeof sm.username);
+ strncpy(sm.hdr.title, subject, TITLELEN);
+ sm.to_list = NULL;
+ sm.srcfile = fname;
+ sm.destfile[0] = '\0';
+ _do_message(0, bname, &sm);
+ bbslog(3, "POST '%s' in %s by %s\n", subject, bname, my_userid());
+ return (sm.hdr.size == 0 ? S_OK : sm.errcode);
+}
+
+local_bbs_open_mailbox(oinfo)
+OPENINFO *oinfo;
+{
+ if (_currbrd.btype != BOARD_NONE && _currbrd.btype != BOARD_MAIL) {
+ return S_ALREADYOPEN;
+ }
+ if (_currbrd.btype == BOARD_MAIL) {
+ _currbrd.oi.flags |= OPEN_REOPEN;
+ }
+ else {
+ _currbrd.btype = BOARD_MAIL;
+ strncpy(_currbrd.oi.name, my_userid(), sizeof _currbrd.oi.name);
+ get_mail_directory(_currbrd.oi.name, _currbrd.bdir);
+ strcpy(_currbrd.wdir, _currbrd.bdir);
+ _currbrd.oi.flags = OPEN_POST | OPEN_MANAGE;
+ get_bitfile_ent(MAIL_READBITS_NAME, &_currbrd.ri);
+ _currbrd.bsync = 0;
+ }
+ _sync_currbrd();
+ memcpy(oinfo, &_currbrd.oi, sizeof(*oinfo));
+ return S_OK;
+}
+
+local_bbs_open_board(bname, oinfo)
+char *bname;
+OPENINFO *oinfo;
+{
+ BOARD board;
+ if (_currbrd.btype != BOARD_NONE && _currbrd.btype != BOARD_POST) {
+ return S_ALREADYOPEN;
+ }
+ if (_currbrd.btype == BOARD_POST) {
+ if (strcasecmp(_currbrd.oi.name, bname)) return S_ALREADYOPEN;
+ _currbrd.oi.flags |= OPEN_REOPEN;
+ }
+ else {
+ if (_lookup_board(bname, &board) != S_OK) return S_NOSUCHBOARD;
+ if (!_has_read_access(&board)) return S_NOSUCHBOARD;
+ _currbrd.btype = BOARD_POST;
+ strcpy(_currbrd.oi.name, board.name);
+ get_board_directory(_currbrd.oi.name, _currbrd.bdir);
+ strcpy(_currbrd.wdir, _currbrd.bdir);
+ _currbrd.oi.flags = 0;
+ if (_has_post_access(&board)) _currbrd.oi.flags |= OPEN_POST;
+ if (_has_manager_access(&board)) _currbrd.oi.flags |= OPEN_MANAGE;
+ get_bitfile_ent(board.name, &_currbrd.ri);
+ _currbrd.bsync = 0;
+ }
+ _sync_currbrd();
+ memcpy(oinfo, &_currbrd.oi, sizeof(*oinfo));
+ return S_OK;
+}
+
+local_bbs_open_fileboard(bname, oinfo)
+char *bname;
+OPENINFO *oinfo;
+{
+ BOARD board;
+ if (_currbrd.btype != BOARD_NONE && _currbrd.btype != BOARD_FILE) {
+ return S_ALREADYOPEN;
+ }
+ if (_currbrd.btype == BOARD_FILE) {
+ if (strcasecmp(_currbrd.oi.name, bname)) return S_ALREADYOPEN;
+ _currbrd.oi.flags |= OPEN_REOPEN;
+ }
+ else {
+ if (_lookup_ftpent(bname, &board) != S_OK) return S_NOSUCHBOARD;
+ _currbrd.btype = BOARD_FILE;
+ strcpy(_currbrd.oi.name, board.name);
+ get_fileboard_directory(_currbrd.oi.name, _currbrd.bdir);
+ strcpy(_currbrd.wdir, _currbrd.bdir);
+ _currbrd.oi.flags = 0;
+ _currbrd.bsync = 0;
+ }
+ _sync_currbrd();
+ memcpy(oinfo, &_currbrd.oi, sizeof(*oinfo));
+ return S_OK;
+}
+
+fname_to_pathname(fname, buf)
+char *fname;
+char *buf;
+{
+ char *slash;
+ strcpy(buf, _currbrd.wdir);
+ if (!strcmp(fname, "..")) {
+ slash = strrchr(buf, '/');
+ if (slash != NULL && slash != buf) *slash = '\0';
+ }
+ else {
+ strcat(buf, "/");
+ strcat(buf, fname);
+ }
+}
+
+local_bbs_change_fileboard_dir(fname, oinfo)
+char *fname;
+OPENINFO *oinfo;
+{
+ BOARD board;
+ PATH fullname;
+ if (_currbrd.btype != BOARD_FILE) {
+ return S_WRONGTYPE;
+ }
+ fname_to_pathname(fname, fullname);
+ if (!is_directory(fullname)) return S_WRONGTYPE; /* should be S_NOTDIR */
+ /* Should we change _currbrd.oi.name? Can't since it's a NAME */
+ strcpy(_currbrd.wdir, fullname);
+ _currbrd.oi.flags = 0;
+ _currbrd.bsync = 0;
+ _sync_currbrd();
+ memcpy(oinfo, &_currbrd.oi, sizeof(*oinfo));
+ return S_OK;
+}
+
+local_bbs_test_board(bname, pflags)
+char *bname;
+SHORT *pflags;
+{
+ BOARD board;
+ if (_lookup_board(bname, &board) != S_OK) return S_NOSUCHBOARD;
+ if (!_has_read_access(&board)) return S_NOSUCHBOARD;
+ *pflags = 0;
+ if (_has_post_access(&board)) (*pflags) |= OPEN_POST;
+ if (_has_manager_access(&board)) (*pflags) |= OPEN_MANAGE;
+ return S_OK;
+}
+
+local_bbs_close_board()
+{
+ free_filelist(&_currbrd.bcache);
+
+ if (_currbrd.btype == BOARD_POST)
+ set_bitfile_ent(_currbrd.oi.name, &_currbrd.ri);
+ else if (_currbrd.btype == BOARD_MAIL)
+ set_bitfile_ent(MAIL_READBITS_NAME, &_currbrd.ri);
+
+ _currbrd.btype = BOARD_NONE;
+ return S_OK;
+}
+
+struct fileenum {
+ SHORT chunk;
+ SHORT start;
+ int (*fn)();
+ void *arg;
+};
+
+_get_file_headers(indx, dir, fname, isdir, info)
+int indx;
+char *dir;
+char *fname;
+struct fileenum *info;
+{
+ struct stat stbuf;
+ HEADER hdr;
+ PATH path;
+ strcpy(path, dir);
+ strcat(path, "/");
+ strcat(path, fname);
+ if (indx < info->start) return S_OK;
+ if (stat(path, &stbuf) || stbuf.st_size == 0) return S_OK;
+ memset(&hdr, 0, sizeof hdr);
+ strncpy(hdr.title, fname, sizeof(hdr.title));
+ hdr.size = stbuf.st_size;
+ hdr.flags = isdir ? FILE_DIRECTORY : (is_text_file(path) ? 0 : FILE_BINARY);
+ return (info->fn(indx, &hdr, info->arg));
+}
+
+/*ARGSUSED*/
+local_bbs_enum_headers(chunk, start, newonly, enumfn, arg)
+SHORT chunk;
+SHORT start;
+SHORT newonly;
+int (*enumfn)();
+void *arg;
+{
+ FILENODE *node;
+ PATH fname;
+ HEADER hdr;
+ SHORT indx;
+
+ if (_currbrd.btype == BOARD_NONE)
+ return S_NOTOPEN;
+
+ if (_currbrd.btype == BOARD_FILE) {
+ /* this case is completely different -- blech */
+ struct fileenum fe;
+ fe.chunk = chunk;
+ fe.start = start;
+ fe.fn = enumfn;
+ fe.arg = arg;
+ return (_enum_files(_currbrd.wdir, _get_file_headers, &fe));
+ }
+
+ _sync_currbrd();
+
+ for (node=_currbrd.bcache, indx=0; node; node=node->next, indx++) {
+ if (indx < start) continue;
+ if (newonly && !(node->flags & FILE_UNREAD)) continue;
+ fileid_to_fname(_currbrd.bdir, node->fileid, fname);
+ hdr.fileid = node->fileid;
+ hdr.flags = node->flags;
+ if (!(_currbrd.oi.flags & OPEN_MANAGE)) hdr.flags &= ~FILE_MARKED;
+ hdr.size = node->size;
+ hdr.mtime = (LONG)node->mtime;
+ read_headers(fname, &hdr);
+ if (hdr.owner[0] == '\0') strcpy(hdr.owner, SYSOP_ACCOUNT);
+ if (enumfn(indx, &hdr, arg) == ENUM_QUIT)
+ break;
+ }
+
+ return S_OK;
+}
+
+local_bbs_read_message(fileid, fname)
+SHORT fileid;
+char *fname;
+{
+ FILENODE *node;
+ PATH myfname;
+ struct stat stbuf;
+ mode_t mode;
+
+ if (_currbrd.btype != BOARD_MAIL &&_currbrd.btype != BOARD_POST)
+ return S_WRONGTYPE;
+
+ node = _bcache_find(fileid);
+ fileid_to_fname(_currbrd.bdir, fileid, myfname);
+ if (node == NULL || stat(myfname, &stbuf))
+ return S_NOSUCHFILE;
+ else node->flags &= ~FILE_UNREAD;
+
+ if (!test_readbit(&_currbrd.ri, fileid)) {
+ set_readbit(&_currbrd.ri, fileid);
+ if (_currbrd.btype == BOARD_MAIL)
+ notify_new_mail(_currbrd.oi.name, -1);
+ }
+
+ strcpy(fname, myfname);
+ return S_OK;
+}
+
+local_bbs_mark_message(fileid, mflag)
+SHORT fileid;
+SHORT mflag;
+{
+ FILENODE *node;
+ PATH fname;
+ struct stat stbuf;
+ mode_t mode;
+
+ if (_currbrd.btype != BOARD_POST)
+ return S_WRONGTYPE;
+
+ if (!(_currbrd.oi.flags & OPEN_MANAGE))
+ return S_DENIED;
+
+ node = _bcache_find(fileid);
+ fileid_to_fname(_currbrd.bdir, fileid, fname);
+ if (node == NULL || stat(fname, &stbuf))
+ return S_NOSUCHFILE;
+
+ if (mflag) {
+ mode = MARKED_FILE_MODE;
+ node->flags |= FILE_MARKED;
+ }
+ else {
+ mode = UNMARKED_FILE_MODE;
+ node->flags &= ~FILE_MARKED;
+ }
+
+ bbslog(2, "MARKMSG %s %d on %s by %s\n", mflag ? "ON" : "OFF",
+ fileid, _currbrd.oi.name, my_userid());
+
+ return(chmod(fname, mode) ? S_SYSERR : S_OK);
+}
+
+local_bbs_delete_message(fileid)
+SHORT fileid;
+{
+ FILENODE *node;
+ PATH fname;
+ HEADER hdr;
+ int is_my_post;
+
+ if (_currbrd.btype != BOARD_MAIL && _currbrd.btype != BOARD_POST)
+ return S_WRONGTYPE;
+
+ node = _bcache_find(fileid);
+ fileid_to_fname(_currbrd.bdir, fileid, fname);
+ if (node == NULL) return S_NOSUCHFILE;
+
+ read_headers(fname, &hdr);
+ is_my_post = is_me(hdr.owner);
+
+ if (!(_currbrd.oi.flags & OPEN_MANAGE)) {
+ if (!is_my_post) return S_DENIED;
+ }
+
+ if (unlink(fname)) return S_SYSERR;
+
+ if (_currbrd.btype == BOARD_MAIL && !test_readbit(&_currbrd.ri, fileid)) {
+ notify_new_mail(_currbrd.oi.name, -1);
+ }
+
+ if (_currbrd.btype == BOARD_POST && !is_my_post)
+ bbslog(2, "DELETEPOST '%s' (%s) on %s by %s\n", hdr.title, hdr.owner,
+ _currbrd.oi.name, my_userid());
+
+ return S_OK;
+}
+
+local_bbs_move_message(fileid, bname)
+SHORT fileid;
+char *bname;
+{
+ FILENODE *node;
+ PATH oldmsgfile;
+ HEADER hdr;
+ int is_my_post;
+ BOARD board;
+ PATH newmsgdir, newmsgfile;
+ SHORT newfileid;
+ READINFO readinfo;
+ struct stat stbuf;
+
+ if (_currbrd.btype != BOARD_POST)
+ return S_WRONGTYPE;
+
+ /* See if the specified fileid exists and if so get the filename. */
+ node = _bcache_find(fileid);
+ fileid_to_fname(_currbrd.bdir, fileid, oldmsgfile);
+ if (node == NULL) return S_NOSUCHFILE;
+
+ read_headers(oldmsgfile, &hdr);
+ is_my_post = is_me(hdr.owner);
+
+ if (!(_currbrd.oi.flags & OPEN_MANAGE)) {
+ if (!is_my_post) return S_DENIED;
+ }
+
+ if (_lookup_board(bname, &board) != S_OK) return S_NOSUCHBOARD;
+ if (!_has_read_access(&board)) return S_NOSUCHBOARD;
+ if (!_has_post_access(&board)) return S_DENIED;
+
+ get_board_directory(board.name, newmsgdir);
+ if (stat(newmsgdir, &stbuf) == -1 || !S_ISDIR(stbuf.st_mode)) {
+ bbslog(0, "ERROR bbs_move_message: cannot access %s\n", newmsgdir);
+ return S_SYSERR;
+ }
+ get_filelist_ids(newmsgdir, &readinfo);
+
+ for (newfileid = 1; newfileid <= BBS_MAX_FILES; newfileid++) {
+ if (test_readbit(&readinfo, newfileid)) continue;
+ fileid_to_fname(newmsgdir, newfileid, newmsgfile);
+ if (rename(oldmsgfile, newmsgfile)) return S_SYSERR;
+ break;
+ }
+
+ bbslog(2, "MOVEPOST '%s' (%s) on %s to %s by %s\n", hdr.title, hdr.owner,
+ _currbrd.oi.name, board.name, my_userid());
+
+ return S_OK;
+}
+
+local_bbs_update_message(fileid, newfile)
+SHORT fileid;
+char *newfile;
+{
+ FILENODE *node;
+ PATH fname;
+ struct utimbuf utbuf;
+
+ if (_currbrd.btype != BOARD_POST)
+ return S_WRONGTYPE;
+
+ if (!(_currbrd.oi.flags & OPEN_MANAGE))
+ return S_DENIED;
+
+ node = _bcache_find(fileid);
+ fileid_to_fname(_currbrd.bdir, fileid, fname);
+ if (node == NULL) return S_NOSUCHFILE;
+
+ /* Touch the message back to its original posting time */
+ utbuf.actime = 0;
+ utbuf.modtime = node->mtime;
+ if (utime(fname, &utbuf)) return S_SYSERR;
+
+ bbslog(2, "EDITMSG %d on %s by %s\n", fileid, _currbrd.oi.name, my_userid());
+ return S_OK;
+}
+
+local_bbs_delete_range(start, finis, count)
+SHORT start;
+SHORT finis;
+SHORT *count;
+{
+ FILENODE *node;
+ PATH fname;
+ SHORT indx = 0;
+
+ if (_currbrd.btype != BOARD_MAIL && _currbrd.btype != BOARD_POST)
+ return S_WRONGTYPE;
+
+ if (!(_currbrd.oi.flags & OPEN_MANAGE))
+ return S_DENIED;
+
+ *count = 0;
+ for (node = _currbrd.bcache; node && (indx++ < finis); node = node->next) {
+ if (indx < start) continue;
+ if (node->flags & FILE_MARKED) continue;
+ if (_currbrd.btype == BOARD_MAIL && (node->flags & FILE_UNREAD)) continue;
+ fileid_to_fname(_currbrd.bdir, node->fileid, fname);
+ if (unlink(fname) == 0) (*count)++;
+ }
+
+ if (_currbrd.btype == BOARD_POST)
+ bbslog(2, "DELETERANGE %d-%d on %s by %s\n", start, finis,
+ _currbrd.oi.name, my_userid());
+
+ return S_OK;
+}
+
+local_bbs_forward_message(fileid)
+SHORT fileid;
+{
+ FILENODE *node;
+ PATH fname;
+ HEADER hdr;
+
+ if (_currbrd.btype != BOARD_MAIL && _currbrd.btype != BOARD_POST)
+ return S_WRONGTYPE;
+
+ node = _bcache_find(fileid);
+ fileid_to_fname(_currbrd.bdir, fileid, fname);
+ if (node == NULL) return S_NOSUCHFILE;
+ read_headers(fname, &hdr);
+
+ return (forward_file_to_outside(fname, hdr.title, 0));
+}
+
+local_bbs_download(fname, protoname, path)
+char *fname;
+char *protoname;
+char *path;
+{
+ PATH fullname;
+ FILE *fp;
+
+ if (_currbrd.btype != BOARD_FILE) return S_WRONGTYPE;
+ fname_to_pathname(fname, fullname);
+ if (is_directory(fullname)) return S_WRONGTYPE; /* should be S_ISDIR */
+
+ if ((fp = fopen(fullname, "r")) == NULL) return S_NOSUCHFILE;
+ fclose(fp);
+
+ if (protoname == NULL && path != NULL) {
+ /* Special case -- just return the filename */
+ strcpy(path, fullname);
+ return S_OK;
+ }
+ else return (do_download(_currbrd.bdir, fname, protoname));
+}
+
+local_bbs_forward_file(fname)
+char *fname;
+{
+ PATH fullname;
+ TITLE title;
+
+ if (_currbrd.btype != BOARD_FILE) return S_WRONGTYPE;
+ fname_to_pathname(fname, fullname);
+ if (is_directory(fullname)) return S_WRONGTYPE; /* should be S_ISDIR */
+ sprintf(title, "%s: %s", _currbrd.oi.name, fname);
+ return (forward_file_to_outside(fullname, title, !is_text_file(fullname)));
+}
+
+/* This is used by bbs_visit_board */
+
+_mark_all_as_read(bname)
+char *bname;
+{
+ if (_currbrd.btype == BOARD_POST && !strcmp(_currbrd.oi.name, bname)) {
+ FILENODE *node;
+ get_filelist_ids(_currbrd.bdir, &_currbrd.ri);
+ time((time_t *)&_currbrd.ri.stamp);
+ for (node = _currbrd.bcache; node != NULL; node = node->next) {
+ node->flags &= ~FILE_UNREAD;
+ }
+ time(&_currbrd.bsync);
+ }
+ else {
+ PATH bdir;
+ READINFO ri;
+ get_board_directory(bname, bdir);
+ get_filelist_ids(bdir, &ri);
+ time((time_t *)&ri.stamp);
+ set_bitfile_ent(bname, &ri);
+ }
+ return S_OK;
+}
+
+/* This is used by bbs_enum_boards to do the counts */
+
+_board_count(board, rinfo)
+BOARD *board;
+READINFO *rinfo;
+{
+ PATH bdir, fname;
+ char *fnamebase;
+ DIR *dp;
+ struct dirent *dent;
+ struct stat stbuf;
+ SHORT fileid;
+
+ board->totalposts = board->newposts = board->ownedposts = 0;
+ board->lastpost = 0;
+
+ get_board_directory(board->name, bdir);
+ if ((dp = opendir(bdir)) == NULL) {
+ return S_SYSERR;
+ }
+
+ strcpy(fname, bdir);
+ strcat(fname, "/");
+ fnamebase = fname+strlen(fname);
+
+ while ((dent = readdir(dp)) != NULL) {
+ strcpy(fnamebase, dent->d_name);
+ fileid = hex2SHORT(dent->d_name);
+ if (fileid > 0 && fileid <= BBS_MAX_FILES && stat(fname, &stbuf) == 0) {
+ if (stbuf.st_size > 0) {
+ (board->totalposts)++;
+ if (!test_readbit(rinfo, fileid)) (board->newposts)++;
+ if ((LONG)stbuf.st_mtime > board->lastpost)
+ board->lastpost = (LONG)stbuf.st_mtime;
+ }
+ /*
+ To test and increment board->ownedposts, we'd have to read the
+ headers and compare the owner to our userid. In the interest of
+ speed, I'm not doing that, for now.
+ */
+ }
+ }
+
+ closedir(dp);
+ return S_OK;
+}
diff --git a/gram.y b/gram.y
new file mode 100644
index 0000000..9ad0819
--- /dev/null
+++ b/gram.y
@@ -0,0 +1,374 @@
+%token ENVIRONMENT
+%token MENU
+%token READMENU
+%token STRING
+%token COMMAND
+%token OBRACE
+%token CBRACE
+%token COMMA
+%token OPAREN
+%token CPAREN
+%token ID_NAME
+%token INTEGER
+%token KEY
+%token OPENFLAG
+%token ERROR
+
+%{
+#include <stdio.h>
+#include "client.h"
+
+extern char *get_yytext() ;
+extern int yyleng ;
+%}
+
+%start commands
+
+%union {
+ int ival ;
+ char * sval ;
+ NMENUITEM *mival ;
+ NMENU *mval ;
+ NREADMENUITEM *rmival ;
+ NREADMENU *rmval ;
+}
+
+%type <ival> integer name ident key openflag
+%type <sval> string
+%type <mival> menulist menuitem command commandentry
+%type <rmival> rmenulist rmenuitem rcommand
+
+%%
+
+commands :
+ | commands menudef
+ | commands rmenudef
+ | error
+
+menudef : MENU string COMMA string COMMA string COMMA string OBRACE menulist CBRACE
+{
+ NMENU *mkmenu(), *mp ;
+
+ if(mp = mkmenu($10)) {
+ mp->menu_id = $2 ;
+ mp->menu_title = $4 ;
+ mp->menu_prompt = $6 ;
+ mp->menu_default = $8 ;
+ pushmenu(mp) ;
+ }
+}
+| ENVIRONMENT OPAREN string CPAREN
+{
+ extern char *default_env ;
+ default_env = $3 ;
+ parse_default() ;
+}
+
+menulist : menuitem
+{
+ $$ = $1 ;
+}
+ | menulist menuitem
+{
+ if($1)
+ $2->prev = $1 ;
+ $$ = $2 ;
+}
+
+menuitem : OPAREN string COMMA string COMMA string COMMA ident COMMA commandentry COMMA string CPAREN
+{
+ register NMENUITEM *mip ;
+
+ if(mip = $10) {
+ mip->name = $2 ;
+ mip->enabled = $8 ;
+ mip->default_action = $4 ;
+ mip->error_action = $6 ;
+ mip->help = $12 ;
+ }
+ $$ = mip ;
+}
+
+rmenudef : READMENU string COMMA string COMMA string COMMA string COMMA string COMMA string COMMA string COMMA string COMMA string COMMA string OBRACE rmenulist CBRACE
+{
+ NREADMENU *mkreadmenu(), *mp ;
+
+ if(mp = mkreadmenu($2, $22)) {
+ mp->menu_helptitle = $20;
+ mp->menu_title = $4;
+ mp->menu_message = $6;
+ mp->menu_line2 = $8;
+ mp->menu_line3 = $10;
+ mp->menu_field1 = $12;
+ mp->menu_field2 = $14;
+ mp->menu_field3 = $16;
+ mp->menu_field4 = $18;
+ }
+}
+
+rmenulist : rmenuitem
+{
+ $$ = $1 ;
+}
+ | rmenulist rmenuitem
+{
+ if($1)
+ $2->next = $1 ;
+ $$ = $2 ;
+}
+
+rmenuitem : OPAREN key COMMA rcommand COMMA openflag COMMA ident COMMA string CPAREN
+{
+ register NREADMENUITEM *mip ;
+
+ if(mip = $4) {
+ mip->key = $2 ;
+ mip->mainprivs = $8 ;
+ mip->boardprivs = $6 ;
+ mip->help = $10 ;
+ }
+ $$ = mip ;
+}
+
+ident: integer
+{ $$ = $1 ; }
+| name
+{ $$ = $1 ; }
+
+command : COMMAND
+{
+ NMENUITEM *mkmenuitem() ;
+ char buf[512] ;
+ strncpy(buf,get_yytext(),yyleng) ;
+ buf[yyleng] = '\0' ;
+
+ $$ = mkmenuitem(buf+1) ;
+}
+
+commandentry: command
+{ $$ = $1; }
+ | command string
+{
+ NMENUITEM *mip = $1 ;
+
+ if(mip)
+ mip->action_arg = $2 ;
+ $$ = mip ;
+}
+
+rcommand : COMMAND
+{
+ NREADMENUITEM *mkreadmenuitem() ;
+ char buf[512] ;
+ strncpy(buf,get_yytext(),yyleng) ;
+ buf[yyleng] = '\0' ;
+
+ $$ = mkreadmenuitem(buf+1) ;
+}
+
+string : STRING
+{
+ char *mkstring() ;
+ char *ExpandString() ;
+ char buf[512] ;
+
+ strncpy(buf,get_yytext(),yyleng) ;
+ buf[yyleng-1] = '\0' ;
+ $$ = mkstring(ExpandString(buf+1)) ;
+}
+
+integer : INTEGER
+{
+ int atoi() ;
+
+ $$ = atoi(get_yytext()) ;
+}
+
+openflag : OPENFLAG
+{
+ int convert_openflag_to_int() ;
+ char buf[512] ;
+
+ strncpy(buf,get_yytext(),yyleng) ;
+ buf[yyleng] = '\0' ;
+ $$ = convert_openflag_to_int(buf+2) ;
+} | integer { $$ = $1; }
+
+name : ID_NAME
+{
+ int convert_cmd_to_int() ;
+ char buf[512] ;
+
+ strncpy(buf,get_yytext(),yyleng) ;
+ buf[yyleng] = '\0' ;
+ $$ = convert_cmd_to_int(buf) ;
+}
+
+key : KEY
+{
+ int convert_key_to_int() ;
+ char buf[512] ;
+
+ strncpy(buf,get_yytext(),yyleng) ;
+ buf[yyleng] = '\0' ;
+ $$ = convert_key_to_int(buf) ;
+}
+%%
+
+char *mkstring(s)
+register char *s ;
+{
+ register char *p ;
+
+ p = (char *) malloc(strlen(s)+1) ;
+ strcpy(p,s) ;
+ return p ;
+}
+
+menuerror()
+{
+ do_echo("WARNING, invalid function in Menu definition\n") ;
+ return 0 ;
+}
+
+rmenuerror()
+{
+ do_echo("WARNING, invalid function in ReadMenu definition\n") ;
+ return 0 ;
+}
+
+NMENUITEM *
+mkmenuitem(s)
+char *s ;
+{
+ NMENUITEM *mip ;
+ extern int line_num ;
+ extern int (*findfunc())() ;
+ if(!(mip = (NMENUITEM *) malloc(sizeof(NMENUITEM))))
+ return NULL ;
+ memset(mip,0,sizeof(NMENUITEM)) ;
+ if(!(mip->action_func = findfunc(s))) {
+ char buf[512] ;
+ sprintf(buf,"WARNING, invalid function in menu file\nfunction = '%s' LINE %d\n",s,line_num) ;
+ do_echo(buf) ;
+ mip->action_func = menuerror ;
+ }
+ return mip ;
+}
+
+char
+getmenuletter(s)
+char *s ;
+{
+ register char firstch;
+ for (firstch = *s; *s; s++)
+ if (*s >= 'A' && *s <= 'Z') return *s;
+
+ return firstch;
+}
+
+int
+getmenuindex(t)
+unsigned int t ;
+{
+ t = (t | 0x20) - 'a' ;
+ return t % MAXMENUSZ ;
+}
+
+NMENU *
+mkmenu(mip)
+NMENUITEM *mip ;
+{
+ NMENU *mp ;
+ NMENUITEM *tp ;
+
+ if(!(mp = (NMENU *)malloc(sizeof(NMENU))))
+ return NULL ;
+ memset(mp,0,sizeof(NMENU)) ;
+ mp->commlist = mip ;
+ for(tp = mip;tp;tp = tp->prev) {
+ register char menuletter = getmenuletter(tp->name) ;
+ register int menuindex = getmenuindex(menuletter) ;
+ if(mp->menucommands[menuindex]) {
+ char buf[512] ;
+ extern int line_num ;
+ sprintf(buf,
+ "Warning Menu command %s attempting to use filled slot!\nLINE %d\n",
+ tp->name,line_num) ;
+ do_echo(buf) ;
+ continue ;
+ }
+ mp->menucommands[menuindex] = tp ;
+ }
+ return mp ;
+}
+
+NMENU *bigMenuList = NULL ;
+
+pushmenu(mp)
+NMENU *mp ;
+{
+ NMENUITEM *mip = NULL ;
+ NMENUITEM *tmp, *next ;
+
+ for(tmp=mp->commlist;tmp;tmp = next) {
+ next = tmp->prev ;
+ tmp->next = mip ;
+ mip = tmp ;
+ }
+ mp->commlist = mip ;
+ mp->next = bigMenuList ;
+ bigMenuList = mp ;
+}
+
+NREADMENU *PostReadMenu = NULL;
+NREADMENU *MailReadMenu = NULL;
+NREADMENU *FileReadMenu = NULL;
+
+NREADMENU *
+mkreadmenu(name, mip)
+char *name ;
+NREADMENUITEM *mip ;
+{
+ NREADMENU *mp ;
+
+ if(!(mp = (NREADMENU *)malloc(sizeof(NREADMENU))))
+ return NULL ;
+ if (!strcasecmp(name, "Main")) PostReadMenu = mp;
+ else if (!strcasecmp(name, "Mail")) MailReadMenu = mp;
+ else if (!strcasecmp(name, "File")) FileReadMenu = mp;
+ else {
+ char buf[512] ;
+ extern int line_num ;
+ sprintf(buf,
+ "Warning Invalid ReadMenu '%s'!\nLINE %d\n", name, line_num) ;
+ do_echo(buf) ;
+ free(mp);
+ return NULL;
+ }
+ memset(mp,0,sizeof(NREADMENU)) ;
+ mp->commlist = mip ;
+ return mp ;
+}
+
+NREADMENUITEM *
+mkreadmenuitem(s)
+char *s ;
+{
+ NREADMENUITEM *mip ;
+ extern int line_num ;
+ extern int (*findrfunc())() ;
+ if(!(mip = (NREADMENUITEM *) malloc(sizeof(NREADMENUITEM))))
+ return NULL ;
+ memset(mip,0,sizeof(NREADMENUITEM)) ;
+ if(!(mip->action_func = findrfunc(s))) {
+ char buf[512] ;
+ sprintf(buf,"WARNING, invalid function in menu file\nfunction = '%s' LINE %d\n",s,line_num) ;
+ do_echo(buf) ;
+ mip->action_func = rmenuerror ;
+ }
+ return mip ;
+}
+
+
+
diff --git a/headers.c b/headers.c
new file mode 100644
index 0000000..21c6a43
--- /dev/null
+++ b/headers.c
@@ -0,0 +1,189 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "server.h"
+#include <time.h>
+
+#define HEADER_TAG_SIZE 11
+#define HEADERSIZE 128 /* must be greater than HEADER_TAG_SIZE+ADDRLEN */
+
+read_headers(fname, hdr)
+char *fname;
+HEADER *hdr;
+{
+ char buf[HEADERSIZE];
+ FILE *fp;
+ char *hdrdata, *space;
+
+ memset(hdr->owner, '\0', sizeof(hdr->owner));
+ memset(hdr->title, '\0', sizeof(hdr->title));
+
+ fp = fopen(fname, "r");
+ if (fp == NULL) return S_SYSERR;
+
+ hdrdata = buf+HEADER_TAG_SIZE;
+ while (fgets(buf, sizeof buf, fp)) {
+ strip_trailing_space(buf);
+ if (buf[0] == '\0') break;
+ else if (!strncmp(buf, "From: ", 6)) {
+ if (space = strchr(hdrdata, ' ')) *space = '\0';
+ strncpy(hdr->owner, hdrdata, sizeof(hdr->owner)-1);
+ }
+ else if (!strncmp(buf, "Posted By: ", 11)) {
+ if (space = strchr(hdrdata, ' ')) *space = '\0';
+ strncpy(hdr->owner, hdrdata, sizeof(hdr->owner)-1);
+ }
+ else if (!strncmp(buf, "Title: ", 7))
+ strncpy(hdr->title, hdrdata, sizeof(hdr->title)-1);
+ else if (!strncmp(buf, "Subject: ", 9))
+ strncpy(hdr->title, hdrdata, sizeof(hdr->title)-1);
+ }
+
+ fclose(fp);
+ return S_OK;
+}
+
+struct writetostruct {
+ int fd;
+ char *buf;
+};
+
+write_to_header(indx, userid, info)
+int indx;
+char *userid;
+struct writetostruct *info;
+{
+ int i;
+ int len = strlen(info->buf);
+ int needed = strlen(userid)+3;
+ if ((80 - len) < needed) {
+ strcat(info->buf, ",\n");
+ write(info->fd, info->buf, len+2);
+ for (i=0; i<HEADER_TAG_SIZE; i++) info->buf[i] = ' ';
+ info->buf[HEADER_TAG_SIZE] = '\0';
+ }
+ else if (len > HEADER_TAG_SIZE) strcat(info->buf, ", ");
+ strcat(info->buf, userid);
+ return S_OK;
+}
+
+/* What a mess. I should use stdio. */
+
+write_mail_headers(fd, hdr, username, list)
+int fd;
+HEADER *hdr;
+char *username;
+NAMELIST list;
+{
+ char fmt[40];
+ char hdrline[HEADERSIZE];
+ time_t now = time(NULL);
+ struct writetostruct tostruct;
+
+ sprintf(fmt, "%%-%ds", HEADER_TAG_SIZE);
+
+ sprintf(hdrline, fmt, "From:");
+ strcat(hdrline, hdr->owner);
+ strcat(hdrline, " (");
+ strcat(hdrline, username); /* hdrline is big enough */
+ strcat(hdrline, ")\n");
+ write(fd, hdrline, strlen(hdrline));
+
+ sprintf(hdrline, fmt, "Subject:");
+ strcat(hdrline, hdr->title); /* again, hdrline is big enough */
+ strcat(hdrline, "\n");
+ write(fd, hdrline, strlen(hdrline));
+
+ sprintf(hdrline, fmt, "Date:");
+ strcat(hdrline, ctime(&now));
+ write(fd, hdrline, strlen(hdrline));
+
+ sprintf(hdrline, fmt, "To:");
+ tostruct.fd = fd;
+ tostruct.buf = hdrline;
+ apply_namelist(list, write_to_header, &tostruct);
+ if (strlen(hdrline) > HEADER_TAG_SIZE) {
+ strcat(hdrline, "\n");
+ write(fd, hdrline, strlen(hdrline));
+ }
+ write(fd, "\n", 1);
+}
+
+write_post_headers(fd, hdr, username, bname)
+int fd;
+HEADER *hdr;
+char *username;
+char *bname;
+{
+ char fmt[40];
+ char hdrline[HEADERSIZE];
+ time_t now = time(NULL);
+ struct writetostruct tostruct;
+
+ sprintf(fmt, "%%-%ds", HEADER_TAG_SIZE);
+
+ sprintf(hdrline, fmt, "Posted By:");
+ strcat(hdrline, hdr->owner);
+ strcat(hdrline, " (");
+ strcat(hdrline, username); /* hdrline is big enough */
+ strcat(hdrline, ") on '");
+ strcat(hdrline, bname);
+ strcat(hdrline, "'\n");
+ write(fd, hdrline, strlen(hdrline));
+
+ sprintf(hdrline, fmt, "Title:");
+ strcat(hdrline, hdr->title); /* again, hdrline is big enough */
+ strcat(hdrline, "\n");
+ write(fd, hdrline, strlen(hdrline));
+
+ sprintf(hdrline, fmt, "Date:");
+ strcat(hdrline, ctime(&now));
+ write(fd, hdrline, strlen(hdrline));
+
+ write(fd, "\n", 1);
+}
+
+/* This one is used by the client side */
+
+parse_to_list(list, fname, myname)
+NAMELIST *list;
+char *fname;
+char *myname;
+{
+ FILE *fp;
+ char header[HEADERSIZE];
+ char *ptr, *id;
+ int gotit = 0;
+
+ if ((fp = fopen(fname, "r")) == NULL) return;
+ while (fgets(header, sizeof header, fp) && header[0] != '\n') {
+ if (!gotit && strncmp(header, "To: ", 4)) continue;
+ if (gotit && strncmp(header, " ", 4)) break;
+ gotit = 1;
+ ptr = header+4;
+ while (id = strtok(ptr, " \t\n,")) {
+ ptr = NULL;
+ if (strcmp(id, myname) && !is_in_namelist(*list, id))
+ add_namelist(list, id, NULL);
+ }
+ }
+ fclose(fp);
+}
+
diff --git a/home.c b/home.c
new file mode 100644
index 0000000..e1b27d5
--- /dev/null
+++ b/home.c
@@ -0,0 +1,48 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "osdeps.h"
+
+#define BBSHOMEENV "BBSHOME"
+
+/*
+ This function is used by the local bbs client and all utilities
+ to chdir to the bbs home directory.
+*/
+
+home_bbs(dir)
+char *dir;
+{
+ char *bbshome;
+ if (dir != NULL) bbshome = dir;
+ else {
+ bbshome = getenv(BBSHOMEENV);
+ if (bbshome == NULL) {
+ /* We must be there already. */
+ return 0;
+ }
+ }
+ return (chdir(bbshome));
+}
+
+
+
diff --git a/init.c b/init.c
new file mode 100644
index 0000000..c06fa08
--- /dev/null
+++ b/init.c
@@ -0,0 +1,348 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "server.h"
+#ifndef NO_LOCALE
+# include <locale.h>
+#endif
+#if LACKS_MALLOC_H
+# include <stdlib.h>
+#else
+# include <malloc.h>
+#endif
+
+int bbslib_user = BBSLIB_DEFAULT;
+
+SERVERDATA server;
+extern USERDATA user_params;
+
+char *perm_strs[PERMBIT_MAX+1];
+LONG perm_table[MAX_CLNTCMDS];
+
+char *mode_strs[BBS_MAX_MODE+1];
+char mode_chars[BBS_MAX_MODE+1];
+char mode_pageable[BBS_MAX_MODE+1];
+
+#define PERMSTRFILE "etc/permstrs"
+#define ACCESSFILE "etc/access"
+#define CONFIGFILE "etc/bbconfig"
+#define MODEFILE "etc/modes"
+
+/*ARGSUSED*/
+_init_modes_func(indx, rec, arg)
+int indx;
+char *rec;
+void *arg;
+{
+ /*
+ Modes file format:
+ mode-val:mode-char:mode-string:allow-page
+ */
+ char buf[10];
+ int mode, len;
+ strip_trailing_space(rec);
+ rec = _extract_quoted(rec, buf, sizeof(buf));
+ mode = atoi(buf);
+ if (mode >= 0 && mode <= BBS_MAX_MODE && mode_strs[mode] == NULL) {
+ rec = _extract_quoted(rec, buf, sizeof(buf));
+ mode_chars[mode] = *buf;
+ rec = _extract_quoted(rec, buf, sizeof(buf));
+ len = strlen(buf)+1;
+ if ((mode_strs[mode] = (char *)malloc(len)) == NULL) return ENUM_QUIT;
+ strcpy(mode_strs[mode], buf);
+ if (*rec == 'N' || *rec == 'n')
+ mode_pageable[mode] = 'N';
+ }
+ return S_OK;
+}
+
+init_mode_strs_chars()
+{
+ int count;
+ _record_enumerate(MODEFILE, 0, _init_modes_func, NULL);
+ return S_OK;
+}
+
+/*ARGSUSED*/
+_init_strs_func(indx, rec, arg)
+int indx;
+char *rec;
+void *arg;
+{
+ int len;
+ strip_trailing_space(rec);
+ len = strlen(rec)+1;
+ if ((perm_strs[indx] = (char *)malloc(len)) == NULL) return ENUM_QUIT;
+ strcpy(perm_strs[indx], rec);
+ return (indx == PERMBIT_MAX ? ENUM_QUIT : S_OK);
+}
+
+init_perm_strs()
+{
+ int count;
+ count = _record_enumerate(PERMSTRFILE, 0, _init_strs_func, NULL);
+ for (; count<=PERMBIT_MAX; count++) {
+ if ((perm_strs[count] = (char *)malloc(9)) != NULL)
+ strcpy(perm_strs[count], "(unused)");
+ }
+ return S_OK;
+}
+
+/*ARGSUSED*/
+_init_perms_func(indx, rec, arg)
+int indx;
+char *rec;
+void *arg;
+{
+ char *str, *equals;
+ int i;
+ perm_table[indx] = 0;
+ strip_trailing_space(rec);
+ if ((equals = strchr(rec, '=')) != NULL) {
+ equals++;
+ while (str = strtok(equals, " \t,")) {
+ if (!strcmp(str, "ALL")) {
+ perm_table[indx] = PERM_ALL;
+ break;
+ }
+ for (i=0; i<=PERMBIT_MAX; i++) {
+ if (perm_strs[i] && !strcmp(perm_strs[i], str))
+ perm_table[indx] |= PERMBIT(i);
+ }
+ equals = NULL;
+ }
+ }
+ return (indx == MAX_CLNTCMDS-1 ? ENUM_QUIT : S_OK);
+}
+
+init_perms()
+{
+ int count;
+ count = _record_enumerate(ACCESSFILE, 0, _init_perms_func, NULL);
+ for (; count<MAX_CLNTCMDS; count++) {
+ perm_table[count] = 0;
+ }
+ return S_OK;
+}
+
+/*ARGSUSED*/
+_init_config_func(indx, rec, arg)
+int indx;
+char *rec;
+void *arg;
+{
+ char *equals;
+ int i;
+ strip_trailing_space(rec);
+
+ if ((equals = strchr(rec, '=')) == NULL) return S_OK;
+ *equals++ = '\0';
+ strip_trailing_space(rec);
+ strip_trailing_space(equals);
+ while (*rec && isspace(*rec)) rec++;
+ while (*equals && isspace(*equals)) equals++;
+
+ if (!strcasecmp(rec, "new")) {
+ if (toupper(*equals) == 'Y') server.newok = 1;
+ }
+ else if (!strcasecmp(rec, "users")) {
+ i = atoi(equals);
+ if (i > 0) server.maxusers = i;
+ }
+ else if (!strcasecmp(rec, "reservedslots")) {
+ i = atoi(equals);
+ if (i > 0) server.reservedslots = i;
+ }
+ else if (!strcasecmp(rec, "usertablesize")) {
+ i = atoi(equals);
+ if (i > 0) server.maxutable = i;
+ }
+ else if (!strcasecmp(rec, "name")) {
+ strncpy(server.name, equals, BBSNAMELEN);
+ }
+ else if (!strcasecmp(rec, "logfile")) {
+ strncpy(server.logfile, equals, PATHLEN);
+ }
+ else if (!strcasecmp(rec, "loglevel")) {
+ i = atoi(equals);
+ if (i >= 0 && i < LOG_LEVEL_MAX) server.loglevel = i;
+ }
+ else if (!strcasecmp(rec, "encoder")) {
+ strncpy(server.encodebin, equals, PATHLEN);
+ }
+ else if (!strcasecmp(rec, "locale")) {
+ strncpy(server.locale, equals, PATHLEN);
+ }
+ else if (!strcasecmp(rec, "logons")) {
+ i = atoi(equals);
+ if (i >= 0) server.maxlogons = i;
+ }
+ else if (!strcasecmp(rec, "siglines")) {
+ i = atoi(equals);
+ if (i >= 0) server.maxsiglines = i;
+ }
+ else if (!strcasecmp(rec, "showreal")) {
+ if (toupper(*equals) == 'Y') server.queryreal = 1;
+ }
+ else if (!strcasecmp(rec, "timeout")) {
+ i = atoi(equals);
+ if (i >= 0) server.idletimeout = i;
+ }
+ return S_OK;
+}
+
+init_config()
+{
+ _record_enumerate(CONFIGFILE, 0, _init_config_func, NULL);
+ return S_OK;
+}
+
+_determine_access(mask, access)
+LONG mask;
+char *access;
+{
+ int i;
+ memset(access, '0', MAX_CLNTCMDS);
+ for (i=0; i<MAX_CLNTCMDS; i++) {
+ if (mask == 0) access[i] = (perm_table[i] == PERM_ALL ? '1' : '0');
+ else access[i] = (perm_table[i] & mask ? '1' : '0');
+ }
+ return S_OK;
+}
+
+_has_access(cmd)
+LONG cmd;
+{
+ if (cmd < 0 || cmd >= MAX_CLNTCMDS) return 0;
+ return(user_params.access[cmd] == '1');
+}
+
+_they_have_access(cmd, mask)
+LONG cmd;
+LONG mask;
+{
+ if (cmd < 0 || cmd >= MAX_CLNTCMDS) return 0;
+ if (mask == 0) return(perm_table[cmd] == PERM_ALL);
+ else return(perm_table[cmd] & mask);
+}
+
+local_bbs_initialize(initinfo)
+INITINFO *initinfo;
+{
+ char loghdr[16];
+ char *ident;
+ char *tmpdir;
+ FILE *fp;
+ if ((fp = fopen(CONFIGFILE, "r")) == NULL) {
+ /* We're in the wrong directory, probably. */
+ return S_NOCONFIGFILE;
+ }
+ fclose(fp);
+ if (initinfo != NULL && initinfo->tmpdir != NULL)
+ tmpdir = initinfo->tmpdir;
+ else tmpdir = "tmp";
+ umask(0007);
+ init_mode_strs_chars();
+ init_perm_strs();
+ init_perms();
+ server.newok = 0;
+ server.loglevel = LOG_LEVEL_DEFAULT;
+ server.maxusers = 80;
+ server.reservedslots = 0;
+ server.maxutable = 80;
+ server.maxlogons = 0;
+ server.maxsiglines = 4;
+ server.queryreal = 0;
+ server.idletimeout = 0;
+ strcpy(server.name, "The Unknown BBS");
+ strcpy(server.logfile, "log");
+ sprintf(server.tempfile, "%s/bbs%05d", tmpdir, getpid());
+ strcpy(server.encodebin, "/usr/bin/uuencode");
+ strcpy(server.locale, "");
+ init_config();
+ if (server.maxutable < server.maxusers)
+ server.maxutable = server.maxusers;
+ open_bbslog(server.logfile, server.loglevel);
+ ident = (bbslib_user == BBSLIB_BBSD ? "BBSD" : "LOCAL");
+ sprintf(loghdr, "%s(%05d)", ident, getpid());
+ set_log_header(loghdr);
+ if (utable_attach(server.maxusers) != S_OK) {
+ close_bbslog();
+ return S_SYSERR;
+ }
+ return S_OK;
+}
+
+local_bbs_disconnect()
+{
+ local_logout();
+ close_bbslog();
+ unlink(server.tempfile);
+ utable_detach(0);
+ return S_OK;
+}
+
+/*ARGSUSED*/
+local_bbs_connect(host, port, bbsinfo)
+char *host;
+SHORT port;
+BBSINFO *bbsinfo;
+{
+ strcpy(bbsinfo->boardname, server.name);
+ bbsinfo->majver = SERVER_VERSION_MAJ;
+ bbsinfo->minver = SERVER_VERSION_MIN;
+ bbsinfo->newok = server.newok;
+#ifndef NO_LOCALE
+ setlocale(LC_COLLATE, server.locale); /* XXX a error message upon fail? */
+ setlocale(LC_CTYPE, server.locale); /* XXX a error message upon fail? */
+#endif
+
+ return S_OK;
+}
+
+local_bbs_get_permstrings(ppstrs)
+char **ppstrs;
+{
+ int i;
+ for (i=0; i<=PERMBIT_MAX; i++) {
+ ppstrs[i] = perm_strs[i];
+ }
+ return S_OK;
+}
+
+local_bbs_get_modestrings(pmstrs)
+char **pmstrs;
+{
+ int i;
+ for (i=0; i<=BBS_MAX_MODE; i++) {
+ pmstrs[i] = (mode_strs[i] == NULL ? "" : mode_strs[i]);
+ }
+ return S_OK;
+}
+
+local_bbs_get_modechars(mchars)
+char *mchars;
+{
+ int i;
+ for (i=0; i<=BBS_MAX_MODE; i++) {
+ mchars[i] = (mode_chars[i] == '\0' ? ' ': mode_chars[i]);
+ }
+ return S_OK;
+}
diff --git a/lex.yy.c b/lex.yy.c
new file mode 100644
index 0000000..7e8dff1
--- /dev/null
+++ b/lex.yy.c
@@ -0,0 +1,1387 @@
+/* A lexical scanner generated by flex */
+
+/* Scanner skeleton version:
+ * $Header: flex.skl,v 1.2 94/01/04 14:33:15 vern Exp $
+ */
+
+#define FLEX_SCANNER
+
+#include <stdio.h>
+
+
+/* cfront 1.2 defines "c_plusplus" instead of "__cplusplus" */
+#ifdef c_plusplus
+#ifndef __cplusplus
+#define __cplusplus
+#endif
+#endif
+
+
+#ifdef __cplusplus
+
+#include <stdlib.h>
+#include <unistd.h>
+
+/* Use prototypes in function declarations. */
+#define YY_USE_PROTOS
+
+/* The "const" storage-class-modifier is valid. */
+#define YY_USE_CONST
+
+#else /* ! __cplusplus */
+
+#ifdef __STDC__
+
+#define YY_USE_PROTOS
+#define YY_USE_CONST
+
+#endif /* __STDC__ */
+#endif /* ! __cplusplus */
+
+
+#ifdef __TURBOC__
+#define YY_USE_CONST
+#endif
+
+
+#ifndef YY_USE_CONST
+#ifndef const
+#define const
+#endif
+#endif
+
+
+#ifdef YY_USE_PROTOS
+#define YY_PROTO(proto) proto
+#else
+#define YY_PROTO(proto) ()
+#endif
+
+/* Returned upon end-of-file. */
+#define YY_NULL 0
+
+/* Promotes a possibly negative, possibly signed char to an unsigned
+ * integer for use as an array index. If the signed char is negative,
+ * we want to instead treat it as an 8-bit unsigned char, hence the
+ * double cast.
+ */
+#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c)
+
+/* Enter a start condition. This macro really ought to take a parameter,
+ * but we do it the disgusting crufty way forced on us by the ()-less
+ * definition of BEGIN.
+ */
+#define BEGIN yy_start = 1 + 2 *
+
+/* Translate the current start state into a value that can be later handed
+ * to BEGIN to return to the state.
+ */
+#define YY_START ((yy_start - 1) / 2)
+
+/* Action number for EOF rule of a given start state. */
+#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1)
+
+/* Special action meaning "start processing a new file". Now included
+ * only for backward compatibility with previous versions of flex.
+ */
+#define YY_NEW_FILE yyrestart( yyin )
+
+#define YY_END_OF_BUFFER_CHAR 0
+
+/* Size of default input buffer. */
+#define YY_BUF_SIZE 16384
+
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+
+extern int yyleng;
+extern FILE *yyin, *yyout;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ extern int yywrap YY_PROTO(( void ));
+#ifdef __cplusplus
+ }
+#endif
+
+#define EOB_ACT_CONTINUE_SCAN 0
+#define EOB_ACT_END_OF_FILE 1
+#define EOB_ACT_LAST_MATCH 2
+
+/* The funky do-while in the following #define is used to turn the definition
+ * int a single C statement (which needs a semi-colon terminator). This
+ * avoids problems with code like:
+ *
+ * if ( condition_holds )
+ * yyless( 5 );
+ * else
+ * do_something_else();
+ *
+ * Prior to using the do-while the compiler would get upset at the
+ * "else" because it interpreted the "if" statement as being all
+ * done when it reached the ';' after the yyless() call.
+ */
+
+/* Return all but the first 'n' matched characters back to the input stream. */
+
+#define yyless(n) \
+ do \
+ { \
+ /* Undo effects of setting up yytext. */ \
+ *yy_cp = yy_hold_char; \
+ yy_c_buf_p = yy_cp = yy_bp + n - YY_MORE_ADJ; \
+ YY_DO_BEFORE_ACTION; /* set up yytext again */ \
+ } \
+ while ( 0 )
+
+#define unput(c) yyunput( c, yytext_ptr )
+
+
+struct yy_buffer_state
+ {
+ FILE *yy_input_file;
+
+ char *yy_ch_buf; /* input buffer */
+ char *yy_buf_pos; /* current position in input buffer */
+
+ /* Size of input buffer in bytes, not including room for EOB
+ * characters.
+ */
+ int yy_buf_size;
+
+ /* Number of characters read into yy_ch_buf, not including EOB
+ * characters.
+ */
+ int yy_n_chars;
+
+ /* Whether this is an "interactive" input source; if so, and
+ * if we're using stdio for input, then we want to use getc()
+ * instead of fread(), to make sure we stop fetching input after
+ * each newline.
+ */
+ int yy_is_interactive;
+
+ /* Whether to try to fill the input buffer when we reach the
+ * end of it.
+ */
+ int yy_fill_buffer;
+
+ /* Whether we've seen an EOF on this buffer. */
+ int yy_eof_status;
+#define EOF_NOT_SEEN 0
+ /* "Pending" happens when the EOF has been seen but there's still
+ * some text to process. Note that when we actually see the EOF,
+ * we switch the status back to "not seen" (via yyrestart()), so
+ * that the user can continue scanning by just pointing yyin at
+ * a new input file.
+ */
+#define EOF_PENDING 1
+ };
+
+static YY_BUFFER_STATE yy_current_buffer = 0;
+
+/* We provide macros for accessing buffer states in case in the
+ * future we want to put the buffer states in a more general
+ * "scanner state".
+ */
+#define YY_CURRENT_BUFFER yy_current_buffer
+
+
+/* yy_hold_char holds the character lost when yytext is formed. */
+static char yy_hold_char;
+
+static int yy_n_chars; /* number of characters read into yy_ch_buf */
+
+
+int yyleng;
+
+/* Points to current character in buffer. */
+static char *yy_c_buf_p = (char *) 0;
+static int yy_init = 1; /* whether we need to initialize */
+static int yy_start = 0; /* start state number */
+
+/* Flag which is used to allow yywrap()'s to do buffer switches
+ * instead of setting up a fresh yyin. A bit of a hack ...
+ */
+static int yy_did_buffer_switch_on_eof;
+
+static void yyunput YY_PROTO(( int c, char *buf_ptr ));
+void yyrestart YY_PROTO(( FILE *input_file ));
+void yy_switch_to_buffer YY_PROTO(( YY_BUFFER_STATE new_buffer ));
+void yy_load_buffer_state YY_PROTO(( void ));
+YY_BUFFER_STATE yy_create_buffer YY_PROTO(( FILE *file, int size ));
+void yy_delete_buffer YY_PROTO(( YY_BUFFER_STATE b ));
+void yy_init_buffer YY_PROTO(( YY_BUFFER_STATE b, FILE *file ));
+
+static int yy_start_stack_ptr = 0;
+static int yy_start_stack_depth = 0;
+static int *yy_start_stack = 0;
+static void yy_push_state YY_PROTO(( int new_state ));
+static void yy_pop_state YY_PROTO(( void ));
+static int yy_top_state YY_PROTO(( void ));
+
+#ifndef yytext_ptr
+static void yy_flex_strcpy YY_PROTO(( char *, const char * ));
+#endif
+
+static void *yy_flex_alloc YY_PROTO(( unsigned int ));
+static void *yy_flex_realloc YY_PROTO(( void *ptr, unsigned int ));
+static void yy_flex_free YY_PROTO(( void * ));
+
+#define yy_new_buffer yy_create_buffer
+
+#define INITIAL 0
+typedef unsigned char YY_CHAR;
+typedef int yy_state_type;
+FILE *yyin = (FILE *) 0, *yyout = (FILE *) 0;
+extern char *yytext;
+#define yytext_ptr yytext
+
+#ifdef __cplusplus
+static int yyinput YY_PROTO(( void ));
+#else
+static int input YY_PROTO(( void ));
+#endif
+
+static yy_state_type yy_get_previous_state YY_PROTO(( void ));
+static yy_state_type yy_try_NUL_trans YY_PROTO(( yy_state_type current_state ));
+static int yy_get_next_buffer YY_PROTO(( void ));
+static void yy_fatal_error YY_PROTO(( const char msg[] ));
+
+/* Done after the current pattern has been matched and before the
+ * corresponding action - sets up yytext.
+ */
+#define YY_DO_BEFORE_ACTION \
+ yytext_ptr = yy_bp; \
+ yyleng = yy_cp - yy_bp; \
+ yy_hold_char = *yy_cp; \
+ *yy_cp = '\0'; \
+ yy_c_buf_p = yy_cp;
+
+#define YY_END_OF_BUFFER 25
+static const short int yy_accept[81] =
+ { 0,
+ 0, 0, 25, 23, 19, 20, 23, 22, 23, 23,
+ 10, 11, 7, 23, 18, 17, 17, 17, 17, 17,
+ 17, 17, 8, 9, 0, 12, 22, 0, 15, 0,
+ 0, 21, 18, 17, 17, 17, 17, 17, 17, 17,
+ 16, 13, 0, 21, 17, 17, 17, 17, 17, 17,
+ 14, 17, 17, 4, 3, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 6, 5, 17, 17, 17, 17, 2, 1, 0
+ } ;
+
+static const int yy_ec[256] =
+ { 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 2, 3,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 2, 1, 4, 5, 6, 1, 1, 7, 8,
+ 9, 1, 1, 10, 1, 11, 12, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 1, 1, 1,
+ 1, 1, 1, 1, 14, 15, 15, 16, 17, 15,
+ 15, 15, 18, 15, 15, 15, 19, 20, 21, 22,
+ 15, 23, 15, 24, 25, 26, 15, 15, 15, 15,
+ 1, 1, 1, 27, 28, 1, 29, 15, 15, 30,
+
+ 31, 15, 15, 15, 32, 15, 15, 15, 33, 34,
+ 35, 15, 15, 36, 15, 37, 38, 39, 15, 15,
+ 15, 15, 40, 1, 41, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1
+ } ;
+
+static const int yy_meta[42] =
+ { 0,
+ 1, 1, 2, 1, 1, 3, 1, 1, 1, 1,
+ 3, 1, 1, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 5, 6, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 1,
+ 1
+ } ;
+
+static const short int yy_base[88] =
+ { 0,
+ 0, 0, 119, 120, 120, 120, 114, 0, 111, 89,
+ 120, 120, 120, 103, 101, 0, 22, 26, 27, 79,
+ 81, 80, 120, 120, 106, 120, 0, 26, 103, 101,
+ 0, 0, 94, 0, 80, 66, 84, 69, 88, 72,
+ 120, 120, 93, 0, 81, 66, 72, 58, 79, 64,
+ 120, 70, 56, 0, 0, 72, 57, 68, 53, 70,
+ 55, 65, 50, 59, 42, 47, 27, 30, 16, 36,
+ 21, 0, 0, 31, 16, 25, 10, 0, 0, 120,
+ 58, 64, 68, 69, 71, 42, 77
+ } ;
+
+static const short int yy_def[88] =
+ { 0,
+ 80, 1, 80, 80, 80, 80, 81, 82, 83, 84,
+ 80, 80, 80, 80, 80, 85, 85, 85, 85, 85,
+ 85, 85, 80, 80, 81, 80, 82, 80, 83, 80,
+ 86, 87, 80, 85, 85, 85, 85, 85, 85, 85,
+ 80, 80, 80, 87, 85, 85, 85, 85, 85, 85,
+ 80, 85, 85, 85, 85, 85, 85, 85, 85, 85,
+ 85, 85, 85, 85, 85, 85, 85, 85, 85, 85,
+ 85, 85, 85, 85, 85, 85, 85, 85, 85, 0,
+ 80, 80, 80, 80, 80, 80, 80
+ } ;
+
+static const short int yy_nxt[162] =
+ { 0,
+ 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+ 4, 14, 15, 16, 16, 16, 17, 16, 18, 16,
+ 16, 16, 19, 16, 16, 16, 4, 16, 16, 16,
+ 20, 16, 21, 16, 16, 22, 16, 16, 16, 23,
+ 24, 35, 37, 39, 41, 43, 79, 41, 78, 77,
+ 76, 75, 74, 73, 72, 36, 38, 40, 25, 71,
+ 25, 25, 25, 25, 27, 70, 27, 27, 27, 27,
+ 29, 29, 30, 30, 34, 69, 34, 44, 68, 44,
+ 44, 44, 44, 67, 66, 65, 64, 63, 62, 61,
+ 60, 59, 58, 57, 56, 55, 54, 53, 52, 51,
+
+ 50, 49, 48, 47, 46, 45, 33, 42, 80, 26,
+ 40, 38, 36, 33, 32, 31, 28, 26, 80, 3,
+ 80, 80, 80, 80, 80, 80, 80, 80, 80, 80,
+ 80, 80, 80, 80, 80, 80, 80, 80, 80, 80,
+ 80, 80, 80, 80, 80, 80, 80, 80, 80, 80,
+ 80, 80, 80, 80, 80, 80, 80, 80, 80, 80,
+ 80
+ } ;
+
+static const short int yy_chk[162] =
+ { 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 17, 18, 19, 28, 86, 77, 28, 76, 75,
+ 74, 71, 70, 69, 68, 17, 18, 19, 81, 67,
+ 81, 81, 81, 81, 82, 66, 82, 82, 82, 82,
+ 83, 83, 84, 84, 85, 65, 85, 87, 64, 87,
+ 87, 87, 87, 63, 62, 61, 60, 59, 58, 57,
+ 56, 53, 52, 50, 49, 48, 47, 46, 45, 43,
+
+ 40, 39, 38, 37, 36, 35, 33, 30, 29, 25,
+ 22, 21, 20, 15, 14, 10, 9, 7, 3, 80,
+ 80, 80, 80, 80, 80, 80, 80, 80, 80, 80,
+ 80, 80, 80, 80, 80, 80, 80, 80, 80, 80,
+ 80, 80, 80, 80, 80, 80, 80, 80, 80, 80,
+ 80, 80, 80, 80, 80, 80, 80, 80, 80, 80,
+ 80
+ } ;
+
+static yy_state_type yy_last_accepting_state;
+static char *yy_last_accepting_cpos;
+
+/* The intent behind this definition is that it'll catch
+ * any uses of REJECT which flex missed.
+ */
+#define REJECT reject_used_but_not_detected
+#define yymore() yymore_used_but_not_detected
+#define YY_MORE_ADJ 0
+char *yytext;
+# line 1 "menu.l"
+# line 2 "menu.l"
+#include "client.h"
+#include "y.tab.h"
+char *get_yytext() { return yytext ; }
+extern int line_num ;
+
+/* Macros after this point can all be overridden by user definitions in
+ * section 1.
+ */
+
+#ifdef YY_MALLOC_DECL
+YY_MALLOC_DECL
+#else
+#if __STDC__
+#ifndef __cplusplus
+#include <stdlib.h>
+#endif
+#else
+/* Just try to get by without declaring the routines. This will fail
+ * miserably on non-ANSI systems for which sizeof(size_t) != sizeof(int)
+ * or sizeof(void*) != sizeof(int).
+ */
+#endif
+#endif
+
+/* Amount of stuff to slurp up with each read. */
+#ifndef YY_READ_BUF_SIZE
+#define YY_READ_BUF_SIZE 8192
+#endif
+
+/* Copy whatever the last rule matched to the standard output. */
+
+#ifndef ECHO
+/* This used to be an fputs(), but since the string might contain NUL's,
+ * we now use fwrite().
+ */
+#define ECHO (void) fwrite( yytext, yyleng, 1, yyout )
+#endif
+
+/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL,
+ * is returned in "result".
+ */
+#ifndef YY_INPUT
+#define YY_INPUT(buf,result,max_size) \
+ if ( yy_current_buffer->yy_is_interactive ) \
+ { \
+ int c = getc( yyin ); \
+ result = c == EOF ? 0 : 1; \
+ buf[0] = (char) c; \
+ } \
+ else if ( ((result = fread( buf, 1, max_size, yyin )) == 0) \
+ && ferror( yyin ) ) \
+ YY_FATAL_ERROR( "input in flex scanner failed" );
+#endif
+
+/* No semi-colon after return; correct usage is to write "yyterminate();" -
+ * we don't want an extra ';' after the "return" because that will cause
+ * some compilers to complain about unreachable statements.
+ */
+#ifndef yyterminate
+#define yyterminate() return YY_NULL
+#endif
+
+/* Number of entries by which start-condition stack grows. */
+#ifndef YY_START_STACK_INCR
+#define YY_START_STACK_INCR 25
+#endif
+
+/* Report a fatal error. */
+#ifndef YY_FATAL_ERROR
+#define YY_FATAL_ERROR(msg) yy_fatal_error( msg )
+#endif
+
+/* Default declaration of generated scanner - a define so the user can
+ * easily add parameters.
+ */
+#ifndef YY_DECL
+#define YY_DECL int yylex YY_PROTO(( void ))
+#endif
+
+/* Code executed at the beginning of each rule, after yytext and yyleng
+ * have been set up.
+ */
+#ifndef YY_USER_ACTION
+#define YY_USER_ACTION
+#endif
+
+/* Code executed at the end of each rule. */
+#ifndef YY_BREAK
+#define YY_BREAK break;
+#endif
+
+YY_DECL
+ {
+ register yy_state_type yy_current_state;
+ register char *yy_cp, *yy_bp;
+ register int yy_act;
+
+# line 8 "menu.l"
+
+
+ if ( yy_init )
+ {
+#ifdef YY_USER_INIT
+ YY_USER_INIT;
+#endif
+
+ if ( ! yy_start )
+ yy_start = 1; /* first start state */
+
+ if ( ! yyin )
+ yyin = stdin;
+
+ if ( ! yyout )
+ yyout = stdout;
+
+ if ( yy_current_buffer )
+ yy_init_buffer( yy_current_buffer, yyin );
+ else
+ yy_current_buffer =
+ yy_create_buffer( yyin, YY_BUF_SIZE );
+
+ yy_load_buffer_state();
+
+ yy_init = 0;
+ }
+
+ while ( 1 ) /* loops until end-of-file is reached */
+ {
+ yy_cp = yy_c_buf_p;
+
+ /* Support of yytext. */
+ *yy_cp = yy_hold_char;
+
+ /* yy_bp points to the position in yy_ch_buf of the start of
+ * the current run.
+ */
+ yy_bp = yy_cp;
+
+ yy_current_state = yy_start;
+yy_match:
+ do
+ {
+ register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)];
+ if ( yy_accept[yy_current_state] )
+ {
+ yy_last_accepting_state = yy_current_state;
+ yy_last_accepting_cpos = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 81 )
+ yy_c = yy_meta[(unsigned int) yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+ ++yy_cp;
+ }
+ while ( yy_base[yy_current_state] != 120 );
+
+yy_find_action:
+ yy_act = yy_accept[yy_current_state];
+
+ YY_DO_BEFORE_ACTION;
+
+
+do_action: /* This label is used only to access EOF actions. */
+
+
+ switch ( yy_act )
+ { /* beginning of action switch */
+ case 0: /* must back up */
+ /* undo the effects of YY_DO_BEFORE_ACTION */
+ *yy_cp = yy_hold_char;
+ yy_cp = yy_last_accepting_cpos;
+ yy_current_state = yy_last_accepting_state;
+ goto yy_find_action;
+
+case 1:
+YY_USER_ACTION
+# line 10 "menu.l"
+case 2:
+YY_USER_ACTION
+# line 10 "menu.l"
+{return ENVIRONMENT;}
+ YY_BREAK
+case 3:
+YY_USER_ACTION
+# line 12 "menu.l"
+case 4:
+YY_USER_ACTION
+# line 12 "menu.l"
+{return MENU;}
+ YY_BREAK
+case 5:
+YY_USER_ACTION
+# line 14 "menu.l"
+case 6:
+YY_USER_ACTION
+# line 14 "menu.l"
+{return READMENU;}
+ YY_BREAK
+case 7:
+YY_USER_ACTION
+# line 15 "menu.l"
+{return COMMA;}
+ YY_BREAK
+case 8:
+YY_USER_ACTION
+# line 16 "menu.l"
+{return OBRACE;}
+ YY_BREAK
+case 9:
+YY_USER_ACTION
+# line 17 "menu.l"
+{return CBRACE;}
+ YY_BREAK
+case 10:
+YY_USER_ACTION
+# line 18 "menu.l"
+{return OPAREN;}
+ YY_BREAK
+case 11:
+YY_USER_ACTION
+# line 19 "menu.l"
+{return CPAREN;}
+ YY_BREAK
+case 12:
+YY_USER_ACTION
+# line 20 "menu.l"
+{return STRING;}
+ YY_BREAK
+case 13:
+YY_USER_ACTION
+# line 21 "menu.l"
+{return KEY;}
+ YY_BREAK
+case 14:
+YY_USER_ACTION
+# line 22 "menu.l"
+{return KEY;}
+ YY_BREAK
+case 15:
+YY_USER_ACTION
+# line 23 "menu.l"
+{return COMMAND;}
+ YY_BREAK
+case 16:
+YY_USER_ACTION
+# line 24 "menu.l"
+{return OPENFLAG;}
+ YY_BREAK
+case 17:
+YY_USER_ACTION
+# line 25 "menu.l"
+{return ID_NAME;}
+ YY_BREAK
+case 18:
+YY_USER_ACTION
+# line 26 "menu.l"
+{return INTEGER;}
+ YY_BREAK
+case 19:
+YY_USER_ACTION
+# line 27 "menu.l"
+;
+ YY_BREAK
+case 20:
+YY_USER_ACTION
+# line 28 "menu.l"
+{line_num++; }
+ YY_BREAK
+case 21:
+YY_USER_ACTION
+# line 29 "menu.l"
+;
+ YY_BREAK
+case 22:
+YY_USER_ACTION
+# line 30 "menu.l"
+;
+ YY_BREAK
+case 23:
+YY_USER_ACTION
+# line 31 "menu.l"
+{return ERROR ; }
+ YY_BREAK
+case 24:
+YY_USER_ACTION
+# line 32 "menu.l"
+ECHO;
+ YY_BREAK
+case YY_STATE_EOF(INITIAL):
+ yyterminate();
+
+ case YY_END_OF_BUFFER:
+ {
+ /* Amount of text matched not including the EOB char. */
+ int yy_amount_of_matched_text = yy_cp - yytext_ptr - 1;
+
+ /* Undo the effects of YY_DO_BEFORE_ACTION. */
+ *yy_cp = yy_hold_char;
+
+ if ( yy_current_buffer->yy_input_file != yyin )
+ {
+ /* This can happen if we scan a file, yywrap() returns
+ * 1, and then later the user points yyin at a new
+ * file to resume scanning. We have to assure
+ * consistency between yy_current_buffer and our
+ * globals. Here is the right place to do so, because
+ * this is the first action (other than possibly a
+ * back-up) that will match for the new input file.
+ */
+ yy_current_buffer->yy_input_file = yyin;
+ yy_n_chars = yy_current_buffer->yy_n_chars;
+ }
+
+ /* Note that here we test for yy_c_buf_p "<=" to the position
+ * of the first EOB in the buffer, since yy_c_buf_p will
+ * already have been incremented past the NUL character
+ * (since all states make transitions on EOB to the
+ * end-of-buffer state). Contrast this with the test
+ * in input().
+ */
+ if ( yy_c_buf_p <= &yy_current_buffer->yy_ch_buf[yy_n_chars] )
+ { /* This was really a NUL. */
+ yy_state_type yy_next_state;
+
+ yy_c_buf_p = yytext_ptr + yy_amount_of_matched_text;
+
+ yy_current_state = yy_get_previous_state();
+
+ /* Okay, we're now positioned to make the NUL
+ * transition. We couldn't have
+ * yy_get_previous_state() go ahead and do it
+ * for us because it doesn't know how to deal
+ * with the possibility of jamming (and we don't
+ * want to build jamming into it because then it
+ * will run more slowly).
+ */
+
+ yy_next_state = yy_try_NUL_trans( yy_current_state );
+
+ yy_bp = yytext_ptr + YY_MORE_ADJ;
+
+ if ( yy_next_state )
+ {
+ /* Consume the NUL. */
+ yy_cp = ++yy_c_buf_p;
+ yy_current_state = yy_next_state;
+ goto yy_match;
+ }
+
+ else
+ {
+ goto yy_find_action;
+ }
+ }
+
+ else switch ( yy_get_next_buffer() )
+ {
+ case EOB_ACT_END_OF_FILE:
+ {
+ yy_did_buffer_switch_on_eof = 0;
+
+ if ( yywrap() )
+ {
+ /* Note: because we've taken care in
+ * yy_get_next_buffer() to have set up
+ * yytext, we can now set up
+ * yy_c_buf_p so that if some total
+ * hoser (like flex itself) wants to
+ * call the scanner after we return the
+ * YY_NULL, it'll still work - another
+ * YY_NULL will get returned.
+ */
+ yy_c_buf_p = yytext_ptr + YY_MORE_ADJ;
+
+ yy_act = YY_STATE_EOF(YY_START);
+ goto do_action;
+ }
+
+ else
+ {
+ if ( ! yy_did_buffer_switch_on_eof )
+ YY_NEW_FILE;
+ }
+ break;
+ }
+
+ case EOB_ACT_CONTINUE_SCAN:
+ yy_c_buf_p =
+ yytext_ptr + yy_amount_of_matched_text;
+
+ yy_current_state = yy_get_previous_state();
+
+ yy_cp = yy_c_buf_p;
+ yy_bp = yytext_ptr + YY_MORE_ADJ;
+ goto yy_match;
+
+ case EOB_ACT_LAST_MATCH:
+ yy_c_buf_p =
+ &yy_current_buffer->yy_ch_buf[yy_n_chars];
+
+ yy_current_state = yy_get_previous_state();
+
+ yy_cp = yy_c_buf_p;
+ yy_bp = yytext_ptr + YY_MORE_ADJ;
+ goto yy_find_action;
+ }
+ break;
+ }
+
+ default:
+ YY_FATAL_ERROR(
+ "fatal flex scanner internal error--no action found" );
+ } /* end of action switch */
+ } /* end of scanning one token */
+ } /* end of yylex */
+
+
+/* yy_get_next_buffer - try to read in a new buffer
+ *
+ * Returns a code representing an action:
+ * EOB_ACT_LAST_MATCH -
+ * EOB_ACT_CONTINUE_SCAN - continue scanning from current position
+ * EOB_ACT_END_OF_FILE - end of file
+ */
+
+static int yy_get_next_buffer()
+ {
+ register char *dest = yy_current_buffer->yy_ch_buf;
+ register char *source = yytext_ptr - 1; /* copy prev. char, too */
+ register int number_to_move, i;
+ int ret_val;
+
+ if ( yy_c_buf_p > &yy_current_buffer->yy_ch_buf[yy_n_chars + 1] )
+ YY_FATAL_ERROR(
+ "fatal flex scanner internal error--end of buffer missed" );
+
+ if ( yy_current_buffer->yy_fill_buffer == 0 )
+ { /* Don't try to fill the buffer, so this is an EOF. */
+ if ( yy_c_buf_p - yytext_ptr - YY_MORE_ADJ == 1 )
+ {
+ /* We matched a singled characater, the EOB, so
+ * treat this as a final EOF.
+ */
+ return EOB_ACT_END_OF_FILE;
+ }
+
+ else
+ {
+ /* We matched some text prior to the EOB, first
+ * process it.
+ */
+ return EOB_ACT_LAST_MATCH;
+ }
+ }
+
+ /* Try to read more data. */
+
+ /* First move last chars to start of buffer. */
+ number_to_move = yy_c_buf_p - yytext_ptr;
+
+ for ( i = 0; i < number_to_move; ++i )
+ *(dest++) = *(source++);
+
+ if ( yy_current_buffer->yy_eof_status != EOF_NOT_SEEN )
+ /* don't do the read, it's not guaranteed to return an EOF,
+ * just force an EOF
+ */
+ yy_n_chars = 0;
+
+ else
+ {
+ int num_to_read =
+ yy_current_buffer->yy_buf_size - number_to_move - 1;
+
+ while ( num_to_read <= 0 )
+ { /* Not enough room in the buffer - grow it. */
+#ifdef YY_USES_REJECT
+ YY_FATAL_ERROR(
+"input buffer overflow, can't enlarge buffer because scanner uses REJECT" );
+#else
+
+ /* just a shorter name for the current buffer */
+ YY_BUFFER_STATE b = yy_current_buffer;
+
+ int yy_c_buf_p_offset = yy_c_buf_p - b->yy_ch_buf;
+
+ b->yy_buf_size *= 2;
+ b->yy_ch_buf = (char *)
+ yy_flex_realloc( (void *) b->yy_ch_buf,
+ b->yy_buf_size );
+
+ if ( ! b->yy_ch_buf )
+ YY_FATAL_ERROR(
+ "fatal error - scanner input buffer overflow" );
+
+ yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset];
+
+ num_to_read = yy_current_buffer->yy_buf_size -
+ number_to_move - 1;
+#endif
+ }
+
+ if ( num_to_read > YY_READ_BUF_SIZE )
+ num_to_read = YY_READ_BUF_SIZE;
+
+ /* Read in more data. */
+ YY_INPUT( (&yy_current_buffer->yy_ch_buf[number_to_move]),
+ yy_n_chars, num_to_read );
+ }
+
+ if ( yy_n_chars == 0 )
+ {
+ if ( number_to_move - YY_MORE_ADJ == 1 )
+ {
+ ret_val = EOB_ACT_END_OF_FILE;
+ yyrestart( yyin );
+ }
+
+ else
+ {
+ ret_val = EOB_ACT_LAST_MATCH;
+ yy_current_buffer->yy_eof_status = EOF_PENDING;
+ }
+ }
+
+ else
+ ret_val = EOB_ACT_CONTINUE_SCAN;
+
+ yy_n_chars += number_to_move;
+ yy_current_buffer->yy_ch_buf[yy_n_chars] = YY_END_OF_BUFFER_CHAR;
+ yy_current_buffer->yy_ch_buf[yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR;
+
+ /* yytext begins at the second character in yy_ch_buf; the first
+ * character is the one which preceded it before reading in the latest
+ * buffer; it needs to be kept around in case it's a newline, so
+ * yy_get_previous_state() will have with '^' rules active.
+ */
+
+ yytext_ptr = &yy_current_buffer->yy_ch_buf[1];
+
+ return ret_val;
+ }
+
+
+/* yy_get_previous_state - get the state just before the EOB char was reached */
+
+static yy_state_type yy_get_previous_state()
+ {
+ register yy_state_type yy_current_state;
+ register char *yy_cp;
+
+ yy_current_state = yy_start;
+
+ for ( yy_cp = yytext_ptr + YY_MORE_ADJ; yy_cp < yy_c_buf_p; ++yy_cp )
+ {
+ register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1);
+ if ( yy_accept[yy_current_state] )
+ {
+ yy_last_accepting_state = yy_current_state;
+ yy_last_accepting_cpos = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 81 )
+ yy_c = yy_meta[(unsigned int) yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+ }
+
+ return yy_current_state;
+ }
+
+
+/* yy_try_NUL_trans - try to make a transition on the NUL character
+ *
+ * synopsis
+ * next_state = yy_try_NUL_trans( current_state );
+ */
+
+#ifdef YY_USE_PROTOS
+static yy_state_type yy_try_NUL_trans( yy_state_type yy_current_state )
+#else
+static yy_state_type yy_try_NUL_trans( yy_current_state )
+yy_state_type yy_current_state;
+#endif
+ {
+ register int yy_is_jam;
+ register char *yy_cp = yy_c_buf_p;
+
+ register YY_CHAR yy_c = 1;
+ if ( yy_accept[yy_current_state] )
+ {
+ yy_last_accepting_state = yy_current_state;
+ yy_last_accepting_cpos = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 81 )
+ yy_c = yy_meta[(unsigned int) yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+ yy_is_jam = (yy_current_state == 80);
+
+ return yy_is_jam ? 0 : yy_current_state;
+ }
+
+
+#ifdef YY_USE_PROTOS
+static void yyunput( int c, register char *yy_bp )
+#else
+static void yyunput( c, yy_bp )
+int c;
+register char *yy_bp;
+#endif
+ {
+ register char *yy_cp = yy_c_buf_p;
+
+ /* undo effects of setting up yytext */
+ *yy_cp = yy_hold_char;
+
+ if ( yy_cp < yy_current_buffer->yy_ch_buf + 2 )
+ { /* need to shift things up to make room */
+ /* +2 for EOB chars. */
+ register int number_to_move = yy_n_chars + 2;
+ register char *dest = &yy_current_buffer->yy_ch_buf[
+ yy_current_buffer->yy_buf_size + 2];
+ register char *source =
+ &yy_current_buffer->yy_ch_buf[number_to_move];
+
+ while ( source > yy_current_buffer->yy_ch_buf )
+ *--dest = *--source;
+
+ yy_cp += dest - source;
+ yy_bp += dest - source;
+ yy_n_chars = yy_current_buffer->yy_buf_size;
+
+ if ( yy_cp < yy_current_buffer->yy_ch_buf + 2 )
+ YY_FATAL_ERROR( "flex scanner push-back overflow" );
+ }
+
+ if ( yy_cp > yy_bp && yy_cp[-1] == '\n' )
+ yy_cp[-2] = '\n';
+
+ *--yy_cp = (char) c;
+
+
+ /* Note: the formal parameter *must* be called "yy_bp" for this
+ * macro to now work correctly.
+ */
+ YY_DO_BEFORE_ACTION; /* set up yytext again */
+ }
+
+
+#ifdef __cplusplus
+static int yyinput()
+#else
+static int input()
+#endif
+ {
+ int c;
+
+ *yy_c_buf_p = yy_hold_char;
+
+ if ( *yy_c_buf_p == YY_END_OF_BUFFER_CHAR )
+ {
+ /* yy_c_buf_p now points to the character we want to return.
+ * If this occurs *before* the EOB characters, then it's a
+ * valid NUL; if not, then we've hit the end of the buffer.
+ */
+ if ( yy_c_buf_p < &yy_current_buffer->yy_ch_buf[yy_n_chars] )
+ /* This was really a NUL. */
+ *yy_c_buf_p = '\0';
+
+ else
+ { /* need more input */
+ yytext_ptr = yy_c_buf_p;
+ ++yy_c_buf_p;
+
+ switch ( yy_get_next_buffer() )
+ {
+ case EOB_ACT_END_OF_FILE:
+ {
+ if ( yywrap() )
+ {
+ yy_c_buf_p =
+ yytext_ptr + YY_MORE_ADJ;
+ return EOF;
+ }
+
+ YY_NEW_FILE;
+#ifdef __cplusplus
+ return yyinput();
+#else
+ return input();
+#endif
+ }
+
+ case EOB_ACT_CONTINUE_SCAN:
+ yy_c_buf_p = yytext_ptr + YY_MORE_ADJ;
+ break;
+
+ case EOB_ACT_LAST_MATCH:
+#ifdef __cplusplus
+ YY_FATAL_ERROR(
+ "unexpected last match in yyinput()" );
+#else
+ YY_FATAL_ERROR(
+ "unexpected last match in input()" );
+#endif
+ }
+ }
+ }
+
+ c = *yy_c_buf_p;
+ *yy_c_buf_p = '\0'; /* preserve yytext */
+ yy_hold_char = *++yy_c_buf_p;
+
+ return c;
+ }
+
+
+#ifdef YY_USE_PROTOS
+void yyrestart( FILE *input_file )
+#else
+void yyrestart( input_file )
+FILE *input_file;
+#endif
+ {
+ if ( ! yy_current_buffer )
+ yy_current_buffer = yy_create_buffer( yyin, YY_BUF_SIZE );
+
+ yy_init_buffer( yy_current_buffer, input_file );
+ yy_load_buffer_state();
+ }
+
+
+#ifdef YY_USE_PROTOS
+void yy_switch_to_buffer( YY_BUFFER_STATE new_buffer )
+#else
+void yy_switch_to_buffer( new_buffer )
+YY_BUFFER_STATE new_buffer;
+#endif
+ {
+ if ( yy_current_buffer == new_buffer )
+ return;
+
+ if ( yy_current_buffer )
+ {
+ /* Flush out information for old buffer. */
+ *yy_c_buf_p = yy_hold_char;
+ yy_current_buffer->yy_buf_pos = yy_c_buf_p;
+ yy_current_buffer->yy_n_chars = yy_n_chars;
+ }
+
+ yy_current_buffer = new_buffer;
+ yy_load_buffer_state();
+
+ /* We don't actually know whether we did this switch during
+ * EOF (yywrap()) processing, but the only time this flag
+ * is looked at is after yywrap() is called, so it's safe
+ * to go ahead and always set it.
+ */
+ yy_did_buffer_switch_on_eof = 1;
+ }
+
+
+#ifdef YY_USE_PROTOS
+void yy_load_buffer_state( void )
+#else
+void yy_load_buffer_state()
+#endif
+ {
+ yy_n_chars = yy_current_buffer->yy_n_chars;
+ yytext_ptr = yy_c_buf_p = yy_current_buffer->yy_buf_pos;
+ yyin = yy_current_buffer->yy_input_file;
+ yy_hold_char = *yy_c_buf_p;
+ }
+
+
+#ifdef YY_USE_PROTOS
+YY_BUFFER_STATE yy_create_buffer( FILE *file, int size )
+#else
+YY_BUFFER_STATE yy_create_buffer( file, size )
+FILE *file;
+int size;
+#endif
+ {
+ YY_BUFFER_STATE b;
+
+ b = (YY_BUFFER_STATE) yy_flex_alloc( sizeof( struct yy_buffer_state ) );
+
+ if ( ! b )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+ b->yy_buf_size = size;
+
+ /* yy_ch_buf has to be 2 characters longer than the size given because
+ * we need to put in 2 end-of-buffer characters.
+ */
+ b->yy_ch_buf = (char *) yy_flex_alloc( b->yy_buf_size + 2 );
+
+ if ( ! b->yy_ch_buf )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+ yy_init_buffer( b, file );
+
+ return b;
+ }
+
+
+#ifdef YY_USE_PROTOS
+void yy_delete_buffer( YY_BUFFER_STATE b )
+#else
+void yy_delete_buffer( b )
+YY_BUFFER_STATE b;
+#endif
+ {
+ if ( b == yy_current_buffer )
+ yy_current_buffer = (YY_BUFFER_STATE) 0;
+
+ yy_flex_free( (void *) b->yy_ch_buf );
+ yy_flex_free( (void *) b );
+ }
+
+
+#ifdef YY_USE_PROTOS
+void yy_init_buffer( YY_BUFFER_STATE b, FILE *file )
+#else
+void yy_init_buffer( b, file )
+YY_BUFFER_STATE b;
+FILE *file;
+#endif
+ {
+ b->yy_input_file = file;
+
+ /* We put in the '\n' and start reading from [1] so that an
+ * initial match-at-newline will be true.
+ */
+
+ b->yy_ch_buf[0] = '\n';
+ b->yy_n_chars = 1;
+
+ /* We always need two end-of-buffer characters. The first causes
+ * a transition to the end-of-buffer state. The second causes
+ * a jam in that state.
+ */
+ b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR;
+ b->yy_ch_buf[2] = YY_END_OF_BUFFER_CHAR;
+
+ b->yy_buf_pos = &b->yy_ch_buf[1];
+
+ b->yy_is_interactive = file ? isatty( fileno(file) ) : 0;
+
+ b->yy_fill_buffer = 1;
+
+ b->yy_eof_status = EOF_NOT_SEEN;
+ }
+
+
+#ifdef YY_USE_PROTOS
+static void yy_push_state( int new_state )
+#else
+static void yy_push_state( new_state )
+int new_state;
+#endif
+ {
+ if ( yy_start_stack_ptr >= yy_start_stack_depth )
+ {
+ int new_size;
+
+ yy_start_stack_depth += YY_START_STACK_INCR;
+ new_size = yy_start_stack_depth * sizeof( int );
+
+ if ( ! yy_start_stack )
+ yy_start_stack = (int *) yy_flex_alloc( new_size );
+
+ else
+ yy_start_stack = (int *) yy_flex_realloc(
+ (void *) yy_start_stack, new_size );
+
+ if ( ! yy_start_stack )
+ YY_FATAL_ERROR(
+ "out of memory expanding start-condition stack" );
+ }
+
+ yy_start_stack[yy_start_stack_ptr++] = YY_START;
+
+ BEGIN(new_state);
+ }
+
+
+static void yy_pop_state()
+ {
+ if ( --yy_start_stack_ptr < 0 )
+ YY_FATAL_ERROR( "start-condition stack underflow" );
+
+ BEGIN(yy_start_stack[yy_start_stack_ptr]);
+ }
+
+
+static int yy_top_state()
+ {
+ return yy_start_stack[yy_start_stack_ptr - 1];
+ }
+
+
+#ifdef YY_USE_PROTOS
+static void yy_fatal_error( const char msg[] )
+#else
+static void yy_fatal_error( msg )
+char msg[];
+#endif
+ {
+ (void) fprintf( stderr, "%s\n", msg );
+ exit( 1 );
+ }
+
+
+
+/* Redefine yyless() so it works in section 3 code. */
+
+#undef yyless
+#define yyless(n) \
+ do \
+ { \
+ /* Undo effects of setting up yytext. */ \
+ yytext[yyleng] = yy_hold_char; \
+ yy_c_buf_p = yytext + n - YY_MORE_ADJ; \
+ yy_hold_char = *yy_c_buf_p; \
+ *yy_c_buf_p = '\0'; \
+ yyleng = n; \
+ } \
+ while ( 0 )
+
+
+/* Internal utility routines. */
+
+#ifndef yytext_ptr
+#ifdef YY_USE_PROTOS
+static void yy_flex_strcpy( char *s1, const char *s2 )
+#else
+static void yy_flex_strcpy( s1, s2 )
+char *s1;
+const char *s2;
+#endif
+ {
+ while ( (*(s1++) = *(s2++)) )
+ ;
+ }
+#endif
+
+
+#ifdef YY_USE_PROTOS
+static void *yy_flex_alloc( unsigned int size )
+#else
+static void *yy_flex_alloc( size )
+unsigned int size;
+#endif
+ {
+ return (void *) malloc( size );
+ }
+
+#ifdef YY_USE_PROTOS
+static void *yy_flex_realloc( void *ptr, unsigned int size )
+#else
+static void *yy_flex_realloc( ptr, size )
+void *ptr;
+unsigned int size;
+#endif
+ {
+ return (void *) realloc( ptr, size );
+ }
+
+#ifdef YY_USE_PROTOS
+static void yy_flex_free( void *ptr )
+#else
+static void yy_flex_free( ptr )
+void *ptr;
+#endif
+ {
+ free( ptr );
+ }
+# line 32 "menu.l"
+
+
+
diff --git a/log.c b/log.c
new file mode 100644
index 0000000..4a47ba9
--- /dev/null
+++ b/log.c
@@ -0,0 +1,109 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "server.h"
+#include <time.h>
+#include <unistd.h>
+#if WANTS_VARARGS_H
+# include <varargs.h>
+#else
+# include <stdarg.h>
+#endif
+
+PATH bbs_logfile;
+int bbs_loglevel;
+char bbs_logsource[16];
+FILE *bbs_log;
+
+open_bbslog(logfile, loglevel)
+char *logfile;
+int loglevel;
+{
+ if (logfile == NULL) return S_BADFILENAME;
+ strncpy(bbs_logfile, logfile, PATHLEN-1);
+ bbs_log = fopen(bbs_logfile, "a");
+ if (bbs_log == NULL) {
+ return S_SYSERR;
+ }
+ bbs_loglevel = loglevel;
+ return S_OK;
+}
+
+close_bbslog()
+{
+ fclose(bbs_log);
+ return S_OK;
+}
+
+void
+set_log_header(str)
+char *str;
+{
+ char *p = str;
+ int indx = 0;
+ memset(bbs_logsource, ' ', 15);
+ while (p && *p && indx < 16) {
+ bbs_logsource[indx++] = *p++;
+ }
+ bbs_logsource[15] = '\0';
+}
+
+void
+#if WANTS_VARARGS_H
+bbslog(va_alist)
+va_dcl
+#else
+bbslog(int level, char *fmt, ...)
+#endif
+{
+ va_list args;
+ time_t now;
+ char timestr[40];
+#if WANTS_VARARGS_H
+ int level;
+ char *fmt;
+#endif
+
+#if WANTS_VARARGS_H
+ va_start(args);
+ level = va_arg(args, int);
+ fmt = va_arg(args, char *);
+#else
+ va_start(args, fmt);
+#endif
+
+ if (level > bbs_loglevel) {
+ va_end(args);
+ return;
+ }
+
+ fseek(bbs_log, 0, SEEK_END);
+ if (fprintf(bbs_log, "%-16s ", bbs_logsource) == EOF) {
+ close_bbslog();
+ open_bbslog(bbs_logfile, bbs_loglevel);
+ fprintf(bbs_log, "%-16s ", bbs_logsource);
+ }
+ time(&now);
+ strftime(timestr, sizeof timestr, "%x %X", localtime(&now));
+ fprintf(bbs_log, "%s ", timestr);
+ vfprintf(bbs_log, fmt, args);
+ fflush(bbs_log);
+ va_end(args);
+}
diff --git a/login.c b/login.c
new file mode 100644
index 0000000..9fd70e2
--- /dev/null
+++ b/login.c
@@ -0,0 +1,632 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "server.h"
+#include <fcntl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#ifdef NeXT
+# include <sys/stat.h>
+#endif
+
+#ifdef HOST_ENV_VAR
+# include <stdlib.h>
+#else
+# if USES_UTMPX
+# include <utmpx.h>
+# undef UTMP_FILE
+# define UTMP_FILE UTMPX_FILE
+# else
+# include <utmp.h>
+# ifndef UTMP_FILE
+# define UTMP_FILE "/etc/utmp"
+# endif
+# endif
+#endif
+
+#define LOGONFILE "etc/logons"
+
+typedef struct _LOGONDATA {
+ NAME userid;
+ SHORT logonlimit;
+} LOGONDATA;
+
+USERDATA user_params;
+int user_number = -1;
+
+extern SERVERDATA server;
+extern char mode_pageable[];
+
+SHORT
+my_real_mode()
+{
+ return (user_params.u.mode & ~MODE_FLG_NOPAGE);
+}
+
+set_real_mode(mode)
+SHORT mode;
+{
+ utable_get_record(user_number, &user_params);
+ switch (mode) {
+ case 0:
+ /* Magic value: clear the non-pageable flag */
+ user_params.u.mode &= ~MODE_FLG_NOPAGE;
+ break;
+ case 1:
+ /* Magic value: set the non-pageable flag */
+ user_params.u.mode |= MODE_FLG_NOPAGE;
+ break;
+ default:
+ user_params.u.mode = mode;
+ if (mode_pageable[mode] == 'N') user_params.u.mode |= MODE_FLG_NOPAGE;
+ }
+ utable_set_record(user_number, &user_params);
+ return S_OK;
+}
+
+my_utable_slot()
+{
+ return user_number;
+}
+
+_has_perms(mask)
+LONG mask;
+{
+ return(user_params.perms & mask);
+}
+
+is_me(userid)
+char *userid;
+{
+ return(!strcasecmp(userid, user_params.u.userid));
+}
+
+char *
+my_userid()
+{
+ return user_params.u.userid;
+}
+
+char *
+my_username()
+{
+ return user_params.u.username;
+}
+
+char *
+my_host()
+{
+ return user_params.u.fromhost;
+}
+
+int
+my_flag(flag)
+SHORT flag;
+{
+ return (user_params.u.flags & flag);
+}
+
+get_remote_host(host)
+char *host;
+{
+#ifdef HOST_ENV_VAR
+ char *ep = getenv(HOST_ENV_VAR);
+ if (ep != NULL && *ep != '\0') {
+ strncpy(host, ep, sizeof(HOST)-1);
+ return S_OK;
+ }
+#else
+ int fd;
+#if USES_UTMPX
+ struct utmpx ut;
+#else
+ struct utmp ut;
+#endif /* USES_UTMPX */
+ int uthostlen = sizeof ut.ut_host;
+ char *tt = ttyname(0);
+ if (uthostlen > HOSTLEN) uthostlen = HOSTLEN;
+ if (tt == NULL) tt = "???";
+ if (!strncmp(tt, "/dev/", 5)) tt+=5;
+ if ((fd = open(UTMP_FILE, O_RDONLY)) != -1) {
+ while (read(fd, &ut, sizeof ut) == sizeof ut) {
+#ifdef DEAD_PROCESS
+ if (!strcmp(tt, ut.ut_line) && ut.ut_type != DEAD_PROCESS) {
+#else
+ if (!strcmp(tt, ut.ut_line)) {
+#endif /* DEAD_PROCESS */
+ memset(host, 0, HOSTLEN);
+ if (ut.ut_host[0] == '\0') strcpy(host, "localhost");
+ else strncpy(host, ut.ut_host, uthostlen);
+ close(fd);
+ return S_OK;
+ }
+ }
+ close(fd);
+ strcpy(host, "localhost");
+ return S_OK;
+ }
+#endif /* HOST_ENV_VAR */
+ strcpy(host, "localhost");
+ return S_SYSERR;
+}
+
+get_client_host(host)
+char *host;
+{
+ struct sockaddr_in sin;
+ int sinsize = sizeof(sin);
+ struct hostent *hent;
+ if (getsockname(0, (struct sockaddr *)&sin, &sinsize) == -1) {
+ strcpy(host, "unknown");
+ return S_SYSERR;
+ }
+ hent = gethostbyaddr((char *)&sin.sin_addr.s_addr,
+ sizeof(sin.sin_addr.s_addr), AF_INET);
+ if (hent == NULL) {
+ char *netaddr = inet_ntoa(sin.sin_addr);
+ if (netaddr == NULL) strcpy(host, "unknown");
+ else strncpy(host, netaddr, HOSTLEN);
+ }
+ else {
+ strncpy(host, hent->h_name, HOSTLEN);
+ }
+ return S_OK;
+}
+
+_logent_to_data(rec, ldata)
+char *rec;
+LOGONDATA *ldata;
+{
+ rec =_extract_quoted(rec, ldata->userid, sizeof(ldata->userid));
+ ldata->logonlimit = atoi(rec);
+ return S_OK;
+}
+
+/*ARGSUSED*/
+_count_logons(indx, udata, ctr)
+int indx;
+USERDATA *udata;
+int *ctr;
+{
+ (*ctr)++;
+ return S_OK;
+}
+
+logon_limit_exceeded(userid)
+char *userid;
+{
+ LOGONDATA ldata;
+ int rc, online = 0;
+ rc = _record_find(LOGONFILE, _match_first, userid, _logent_to_data, &ldata);
+ if (rc != S_OK) ldata.logonlimit = server.maxlogons;
+ if (ldata.logonlimit == 0) return 0; /* 0 means unlimited */
+ utable_enumerate(0, userid, _count_logons, &online);
+ if (online >= ldata.logonlimit) return 1;
+ return 0;
+}
+
+/*ARGSUSED*/
+_userid_to_pid(indx, udata, pid)
+int indx;
+USERDATA *udata;
+LONG *pid;
+{
+ *pid = udata->u.pid;
+ return ENUM_QUIT;
+}
+
+kick_previous_logon(userid)
+char *userid;
+{
+ LONG pid = 0;
+ utable_enumerate(0, userid, _userid_to_pid, &pid);
+ if (pid > 0) {
+ if (kill(pid, SIGTERM) == 0) return 0;
+ }
+ return -1;
+}
+
+local_bbs_login(userid, passwd, kick, loginfo)
+char *userid;
+char *passwd;
+SHORT kick;
+LOGININFO *loginfo;
+{
+ ACCOUNT acct;
+ OPENINFO oinfo;
+ int usernum;
+
+ if (user_number != -1) {
+ /* Already logged in! */
+ return S_LOGGEDIN;
+ }
+
+ if (user_params.u.fromhost[0] == '\0') {
+ if (bbslib_user == BBSLIB_BBSD)
+ get_client_host(user_params.u.fromhost);
+ else
+ get_remote_host(user_params.u.fromhost);
+ }
+
+ if (_lookup_account(userid, &acct) != S_OK) {
+ bbslog(1, "BADUSERID %s from %s\n", userid, user_params.u.fromhost);
+ return S_LOGINFAIL;
+ }
+
+ if (!is_passwd_good(acct.passwd, passwd)) {
+ bbslog(1, "BADPASSWORD %s from %s\n", userid, user_params.u.fromhost);
+ return S_LOGINFAIL;
+ }
+
+ if (acct.flags & FLG_DISABLED) {
+ return S_ACCTDISABLED;
+ }
+
+ _determine_access(acct.perms, user_params.access);
+
+ if (logon_limit_exceeded(acct.userid)) {
+ if (acct.flags & FLG_SHARED) return S_LOGINLIMIT;
+ if (kick) kick_previous_logon(acct.userid);
+ else return S_LOGINMUSTBOOT;
+ }
+
+ if (utable_lock_record(&user_number) != S_OK) {
+ return S_FULL;
+ }
+
+ strcpy(loginfo->userid, acct.userid);
+ loginfo->flags = acct.flags & ~(FLG_EXEMPT | FLG_DISABLED);
+ loginfo->idletimeout = _has_access(C_NOTIMEOUT) ? 0 : server.idletimeout;
+ memcpy(loginfo->access, user_params.access, sizeof(loginfo->access));
+ get_lastlog_time(acct.userid, &loginfo->lastlogin);
+ get_lastlog_host(acct.userid, loginfo->fromhost);
+
+ strcpy(user_params.u.userid, acct.userid);
+ strcpy(user_params.u.username, acct.username);
+ user_params.u.pid = getpid();
+ user_params.u.flags = acct.flags;
+ user_params.u.mode = M_UNDEFINED;
+ user_params.usermode = M_UNDEFINED;
+ user_params.perms = acct.perms;
+
+ if (local_bbs_open_mailbox(&oinfo) == S_OK) {
+ user_params.newmailmsgs = oinfo.newmsgs;
+ local_bbs_close_board();
+ }
+
+ utable_set_record(user_number, &user_params);
+
+ set_lastlog(user_params.u.userid, user_params.u.fromhost);
+
+ bbslog(1, "LOGIN %s %s\n", user_params.u.userid, user_params.u.fromhost);
+ return S_OK;
+}
+
+local_logout()
+{
+ if (user_number != -1) {
+ utable_get_record(user_number, &user_params);
+
+ if (bbslib_user == BBSLIB_DEFAULT) {
+ if (user_params.u.mode == M_CHAT) {
+ local_bbs_exit_chat();
+ }
+ else if (user_params.u.mode == M_TALK) {
+ local_bbs_exit_talk();
+ }
+ }
+
+ utable_free_record(user_number);
+ bbslog(1, "LOGOUT %s\n", user_params.u.userid);
+ }
+ return S_OK;
+}
+
+local_bbs_newlogin(acct, loginfo)
+ACCOUNT *acct;
+LOGININFO *loginfo;
+{
+ int rc;
+
+ if (server.newok == 0) return S_DENIED;
+
+ rc = local_bbs_add_account(acct, 0);
+ if (rc != S_OK) return rc;
+
+ rc = local_bbs_login(acct->userid, acct->passwd, 0, loginfo);
+ return rc;
+}
+
+local_bbs_check_mail()
+{
+ utable_get_record(user_number, &user_params);
+ return (user_params.newmailmsgs > 0 ? 1 : 0);
+}
+
+local_bbs_set_mode(mode)
+SHORT mode;
+{
+ if (mode > BBS_MAX_MODE) return S_MODEVIOLATION;
+ utable_get_record(user_number, &user_params);
+ switch (mode) {
+ case 0:
+ /* Magic value: clear the non-pageable flag */
+ user_params.usermode &= ~MODE_FLG_NOPAGE;
+ break;
+ case 1:
+ /* Magic value: set the non-pageable flag */
+ user_params.usermode |= MODE_FLG_NOPAGE;
+ break;
+ default:
+ user_params.usermode = mode;
+ if (mode_pageable[mode] == 'N') user_params.usermode |= MODE_FLG_NOPAGE;
+ }
+ utable_set_record(user_number, &user_params);
+ return S_OK;
+}
+
+local_bbs_set_passwd(passwd)
+char *passwd;
+{
+ ACCOUNT acct;
+ strncpy(acct.passwd, passwd, PASSLEN);
+ return (_set_account(user_params.u.userid, &acct, MOD_PASSWD));
+}
+
+local_bbs_set_username(uname)
+char *uname;
+{
+ ACCOUNT acct;
+ utable_get_record(user_number, &user_params);
+ strncpy(user_params.u.username, uname, UNAMELEN);
+ utable_set_record(user_number, &user_params);
+ strncpy(acct.username, uname, UNAMELEN);
+ return(_set_account(user_params.u.userid, &acct, MOD_USERNAME));
+}
+
+local_bbs_set_terminal(term)
+char *term;
+{
+ ACCOUNT acct;
+ strncpy(acct.terminal, term, UNAMELEN);
+ return (_set_account(user_params.u.userid, &acct, MOD_TERMINAL));
+}
+
+local_bbs_set_charset(charset)
+char *charset;
+{
+ ACCOUNT acct;
+ strncpy(acct.charset, charset, CSETLEN);
+ return (_set_account(user_params.u.userid, &acct, MOD_CHARSET));
+}
+
+local_bbs_set_email(email)
+char *email;
+{
+ ACCOUNT acct;
+ if (!is_valid_address(email)) return S_BADADDRESS;
+ strncpy(acct.email, email, MAILLEN);
+ return (_set_account(user_params.u.userid, &acct, MOD_EMAIL));
+}
+
+local_bbs_set_protocol(protoname)
+char *protoname;
+{
+ ACCOUNT acct;
+ strncpy(acct.protocol, protoname, NAMELEN);
+ return (_set_account(user_params.u.userid, &acct, MOD_PROTOCOL));
+}
+
+local_bbs_set_editor(editor)
+char *editor;
+{
+ ACCOUNT acct;
+ strncpy(acct.editor, editor, NAMELEN);
+ return (_set_account(user_params.u.userid, &acct, MOD_EDITOR));
+}
+
+local_bbs_owninfo(acct)
+ACCOUNT *acct;
+{
+ return (local_bbs_get_userinfo(user_params.u.userid, acct));
+}
+
+local_bbs_toggle_cloak()
+{
+ ACCOUNT acct;
+ int mode = my_real_mode();
+ if (mode == M_TALK || mode == M_PAGE || mode == M_CHAT) {
+ /* We don't allow cloak to be toggled in these modes */
+ return(S_MODEVIOLATION);
+ }
+ utable_get_record(user_number, &user_params);
+ user_params.u.flags ^= FLG_CLOAK;
+ bbslog(2, "CLOAK %s %s\n", user_params.u.flags & FLG_CLOAK ? "ON" : "OFF",
+ user_params.u.userid);
+ utable_set_record(user_number, &user_params);
+ acct.flags = FLG_CLOAK;
+ return(_set_account(user_params.u.userid, &acct, _TOGGLE_FLAGS));
+}
+
+local_bbs_set_pager(pager, override)
+SHORT pager;
+SHORT override;
+{
+ ACCOUNT acct;
+ acct.flags = 0;
+ pager = (pager ? 1 : 0);
+ override = (override ? 1 : 0);
+ utable_get_record(user_number, &user_params);
+ if ((user_params.u.flags & FLG_NOPAGE ? 1 : 0) != pager)
+ acct.flags |= FLG_NOPAGE;
+ if ((user_params.u.flags & FLG_NOOVERRIDE ? 1 : 0) != override)
+ acct.flags |= FLG_NOOVERRIDE;
+ if (acct.flags == 0) return S_OK;
+ user_params.u.flags ^= acct.flags;
+ utable_set_record(user_number, &user_params);
+ return(_set_account(user_params.u.userid, &acct, _TOGGLE_FLAGS));
+}
+
+local_bbs_set_cliopts(menulevel)
+SHORT menulevel;
+{
+ /* At this time, only thing to set here is the local bbs menu style */
+ ACCOUNT acct;
+ int oldlevel;
+ /* Shared accounts cannot save these settings. */
+ if (my_flag(FLG_SHARED)) return S_OK;
+ utable_get_record(user_number, &user_params);
+ oldlevel = user_params.u.flags & FLG_EXPERT;
+ if (oldlevel == menulevel) return S_OK;
+ if (menulevel) user_params.u.flags |= FLG_EXPERT;
+ else user_params.u.flags &= ~FLG_EXPERT;
+ utable_set_record(user_number, &user_params);
+ acct.flags = FLG_EXPERT;
+ return(_set_account(user_params.u.userid, &acct, _TOGGLE_FLAGS));
+}
+
+local_bbs_set_plan(fname)
+char *fname;
+{
+ PATH planfile;
+ int rc = 0;
+ local_bbs_get_plan(user_params.u.userid, planfile);
+ if (fname == NULL) {
+ unlink(planfile);
+ }
+ else if (strcmp(planfile, fname)) {
+ rc = copy_file(fname, planfile, 0600, 0);
+ }
+ return (rc == 0 ? S_OK : S_SYSERR);
+}
+
+local_bbs_set_signature(fname)
+char *fname;
+{
+ PATH sigfile;
+ int rc = 0;
+ local_bbs_get_signature(sigfile);
+ if (fname == NULL) {
+ unlink(sigfile);
+ }
+ else if (strcmp(sigfile, fname)) {
+ rc = copy_file(fname, sigfile, 0600, 0);
+ }
+ return (rc == 0 ? S_OK : S_SYSERR);
+}
+
+_enum_users(indx, info, en)
+int indx;
+USERDATA *info;
+struct enumstruct *en;
+{
+ int pageok = 1;
+ if ((info->u.flags & FLG_CLOAK) && !_has_access(C_SEECLOAK))
+ return S_OK;
+
+ /* Assumption: real mode will never be M_UNDEFINED and unpageable */
+ if (info->u.mode == M_UNDEFINED) info->u.mode = info->usermode;
+ if (info->u.mode & MODE_FLG_NOPAGE) {
+ info->u.mode &= ~MODE_FLG_NOPAGE;
+ pageok = 0;
+ }
+
+ if (pageok) pageok = has_page_permission(info);
+ info->u.flags &= ~(FLG_EXEMPT);
+ info->u.flags &= ~(FLG_NOPAGE | FLG_NOOVERRIDE);
+ if (!pageok) info->u.flags |= FLG_NOPAGE;
+ if (en->fn(indx, &info->u, en->arg) == ENUM_QUIT) return ENUM_QUIT;
+ return S_OK;
+}
+
+local_bbs_enum_users(chunk, startrec, userid, enumfn, arg)
+SHORT chunk;
+SHORT startrec;
+char *userid;
+int (*enumfn)();
+void *arg;
+{
+ struct enumstruct en;
+ en.fn = enumfn;
+ en.arg = arg;
+ utable_enumerate(startrec, userid, _enum_users, &en);
+ return S_OK;
+}
+
+_enum_user_names(indx, info, lc)
+int indx;
+USERDATA *info;
+struct listcomplete *lc;
+{
+ if ((info->u.flags & FLG_CLOAK) && !_has_access(C_SEECLOAK))
+ return S_OK;
+
+ if (!strncasecmp(info->u.userid, lc->str, strlen(lc->str)))
+ if (!is_in_namelist(*(lc->listp), info->u.userid))
+ add_namelist(lc->listp, info->u.userid, NULL);
+
+ return S_OK;
+}
+
+local_bbs_usernames(list, complete)
+NAMELIST *list;
+char *complete;
+{
+ struct listcomplete lc;
+ create_namelist(list);
+ lc.listp = list;
+ lc.str = complete == NULL ? "" : complete;
+ utable_enumerate(0, NULL, _enum_user_names, &lc);
+ return S_OK;
+}
+
+local_bbs_kick_user(pid)
+LONG pid;
+{
+ USERDATA udata;
+ if (utable_find_record(pid, &udata) != S_OK) return S_NOSUCHUSER;
+ if ((udata.perms & PERM_SYSOP) && !_has_perms(PERM_SYSOP)) {
+ return S_DENIED;
+ }
+ bbslog(2, "KICK %s by %s\n", udata.u.userid, user_params.u.userid);
+ if (kill(pid, SIGTERM) == -1) return S_SYSERR;
+ else return S_OK;
+}
+
+_mail_adjust(indx, info, adjval)
+int indx;
+USERDATA *info;
+int *adjval;
+{
+ info->newmailmsgs += *adjval;
+ utable_set_record(indx, info);
+ return S_OK;
+}
+
+notify_new_mail(userid, adjust)
+char *userid;
+int adjust;
+{
+ utable_enumerate(0, userid, _mail_adjust, &adjust);
+}
diff --git a/menu.l b/menu.l
new file mode 100644
index 0000000..fb4df54
--- /dev/null
+++ b/menu.l
@@ -0,0 +1,34 @@
+%{
+#include "client.h"
+#include "y.tab.h"
+char *get_yytext() { return yytext ; }
+extern int line_num ;
+%}
+
+%%
+[Ee]nvironment |
+ENVIRONMENT {return ENVIRONMENT;}
+[Mm]enu |
+MENU {return MENU;}
+[Rr]eadmenu |
+READMENU {return READMENU;}
+\, {return COMMA;}
+\{ {return OBRACE;}
+\} {return CBRACE;}
+\( {return OPAREN;}
+\) {return CPAREN;}
+\"[^"\n]*\" {return STRING;}
+\'[a-zA-Z]' {return KEY;}
+\'^[a-zA-Z]' {return KEY;}
+\$[a-zA-Z.]+ {return COMMAND;}
+\$$[MP] {return OPENFLAG;}
+[a-zA-Z_]+ {return ID_NAME;}
+[0-9]+ {return INTEGER;}
+[ \t] ;
+\n {line_num++; }
+\/\/.* ;
+\#.* ;
+. {return ERROR ; }
+%%
+
+
diff --git a/menus.c b/menus.c
new file mode 100644
index 0000000..1534f1a
--- /dev/null
+++ b/menus.c
@@ -0,0 +1,176 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "client.h"
+#include <ctype.h>
+
+char permtab[MAX_CLNTCMDS];
+char *currentboard = currboard;
+
+DoReadHelp(m, openflags)
+NREADMENU *m;
+int openflags;
+{
+ NREADMENUITEM *mi;
+ for (mi = m->commlist; mi != NULL; mi = mi->next) {
+ if (HasReadMenuPerm(mi->boardprivs, openflags) &&
+ HasPerm(mi->mainprivs) && mi->help[0] != '\0')
+ prints("%s\n", mi->help);
+ }
+}
+
+MovementReadHelp()
+{
+ standout();
+ prints("Movement Commands\n");
+ standend();
+ prints("p Previous Message\n");
+ prints("n Next Message\n");
+ prints("P Previous Page\n");
+ prints("N Next Page\n");
+ prints("## <cr> Goto Message ##\n");
+ prints("$ Goto Last Message\n");
+}
+
+MiscReadHelp()
+{
+ prints("CTRL-L Redraw Screen\n");
+ prints("e EXIT Read Menu\n");
+}
+
+ReadMenuHelp(menu, flags)
+NREADMENU *menu;
+int flags;
+{
+ clear();
+ standout();
+ prints("%s\n", menu->menu_helptitle);
+ standend();
+ move(2,0);
+ MovementReadHelp();
+ prints("\n");
+ standout();
+ prints("Miscellaneous Commands\n");
+ standend();
+ DoReadHelp(menu, flags);
+ MiscReadHelp();
+ pressreturn();
+ clear();
+ return FULLUPDATE;
+}
+
+/*ARGSUSED*/
+MailReadHelp(hptr, curr, num, flags)
+void *hptr;
+LONG curr, num, flags;
+{
+ return (ReadMenuHelp(MailReadMenu, flags));
+}
+
+/*ARGSUSED*/
+MainReadHelp(hptr, curr, num, flags)
+void *hptr;
+LONG curr, num, flags;
+{
+ return (ReadMenuHelp(PostReadMenu, flags));
+}
+
+/*ARGSUSED*/
+FileReadHelp(hptr, curr, num, flags)
+void *hptr;
+LONG curr, num, flags;
+{
+ return (ReadMenuHelp(FileReadMenu, flags));
+}
+
+NotImpl()
+{
+ clear();
+ prints("This function is not yet implemented.\n");
+ pressreturn();
+ return FULLUPDATE;
+}
+
+EndMenu()
+{
+ return EXITMENU;
+}
+
+SetPermTable()
+{
+ memcpy(permtab, myinfo.access, MAX_CLNTCMDS);
+}
+
+HasPerm(perm)
+int perm;
+{
+ if (perm == 0) return 1;
+ else if (perm < 0 || perm >= MAX_CLNTCMDS) return 0;
+ else return (permtab[perm]-'0');
+}
+
+HasReadMenuPerm(perm, flags)
+int perm, flags;
+{
+ if (perm == 0) return 1;
+ else return (perm & flags);
+}
+
+#define KEY_NORMAL (0)
+#define KEY_ESCAPE (1)
+#define KEY_VTKEYS (2)
+
+int
+MenuGetch()
+{
+ char c;
+ int keymode = KEY_NORMAL;
+ while (1) {
+ c = igetch();
+ switch (keymode) {
+ case KEY_NORMAL:
+ if (isprint(c)) return c;
+ if (c == '\r' || c == '\n' || c == '\010' || c == '\177') {
+ c = '\n';
+ return c;
+ }
+ if (c == CTRL('L')) {
+ redoscr() ;
+ }
+ if (c == 27) keymode = KEY_ESCAPE; /* ESC */
+ break;
+ case KEY_ESCAPE:
+ if (c == '[') keymode = KEY_VTKEYS;
+ else {
+ bell();
+ keymode = KEY_NORMAL;
+ }
+ break;
+ case KEY_VTKEYS:
+ if (c == 'A') return MENU_UP;
+ else if (c == 'B') return MENU_DOWN;
+ else {
+ bell();
+ keymode = KEY_NORMAL;
+ }
+ }
+ }
+}
+
diff --git a/misc.c b/misc.c
new file mode 100644
index 0000000..fec847f
--- /dev/null
+++ b/misc.c
@@ -0,0 +1,129 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "server.h"
+
+/* Miscellaneous server functions. */
+
+#define HOME "home"
+
+get_home_directory(userid, buf)
+char *userid;
+char *buf;
+{
+ strcpy(buf, HOME);
+ strcat(buf, "/");
+ strcat(buf, userid);
+}
+
+#define MAILDIR "mail"
+
+get_mail_directory(userid, buf)
+char *userid;
+char *buf;
+{
+ get_home_directory(userid, buf);
+ strcat(buf, "/");
+ strcat(buf, MAILDIR);
+}
+
+#define PLANFILE "plan"
+
+local_bbs_get_plan(userid, buf)
+char *userid;
+char *buf;
+{
+ get_home_directory(userid, buf);
+ strcat(buf, "/");
+ strcat(buf, PLANFILE);
+ return S_OK;
+}
+
+#define SIGFILE "signature"
+
+local_bbs_get_signature(buf)
+char *buf;
+{
+ get_home_directory(my_userid(), buf);
+ strcat(buf, "/");
+ strcat(buf, SIGFILE);
+ return S_OK;
+}
+
+#define BOARDHOME "boards"
+
+get_board_directory(bname, buf)
+char *bname;
+char *buf;
+{
+ strcpy(buf, BOARDHOME);
+ strcat(buf, "/");
+ strcat(buf, bname);
+ return S_OK;
+}
+
+#define ISSUEFILE "etc/issue"
+
+local_bbs_get_issue(buf)
+char *buf;
+{
+ strcpy(buf, ISSUEFILE);
+ return S_OK;
+}
+
+#define INFOFILE "etc/info"
+
+local_bbs_get_info(buf)
+char *buf;
+{
+ strcpy(buf, INFOFILE);
+ return S_OK;
+}
+
+#define GNUFILE "etc/COPYING"
+
+local_bbs_get_license(buf)
+char *buf;
+{
+ strcpy(buf, GNUFILE);
+ return S_OK;
+}
+
+#define WELCFILE "etc/welcome"
+
+local_bbs_get_welcome(buf)
+char *buf;
+{
+ strcpy(buf, WELCFILE);
+ return S_OK;
+}
+
+local_bbs_set_welcome(buf)
+char *buf;
+{
+ int rc = 0;
+ if (buf == NULL) {
+ unlink(WELCFILE);
+ }
+ else if (strcmp(WELCFILE, buf)) {
+ rc = copy_file(buf, WELCFILE, 0660, 0);
+ }
+ return (rc == 0 ? S_OK : S_SYSERR);
+}
diff --git a/modes.c b/modes.c
new file mode 100644
index 0000000..b92201f
--- /dev/null
+++ b/modes.c
@@ -0,0 +1,54 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "client.h"
+
+char *global_modestrs[BBS_MAX_MODE+1];
+char global_modechars[BBS_MAX_MODE+1];
+
+char *
+ModeToString(mode)
+SHORT mode;
+{
+ if (global_modestrs[0] == NULL) {
+ bbs_get_modestrings(global_modestrs);
+ }
+
+ if (mode <= BBS_MAX_MODE) {
+ return(global_modestrs[mode]);
+ }
+
+ return("");
+}
+
+char
+ModeToChar(mode)
+SHORT mode;
+{
+ if (global_modechars[0] == '\0') {
+ bbs_get_modechars(global_modechars);
+ }
+
+ if (mode <= BBS_MAX_MODE) {
+ return(global_modechars[mode]);
+ }
+
+ return(' ');
+}
diff --git a/modes.h b/modes.h
new file mode 100644
index 0000000..7adac63
--- /dev/null
+++ b/modes.h
@@ -0,0 +1,39 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+
+/*
+ Legal values for the mode field in a userec structure, to tell what
+ a user is doing. Some of these are set by the server but by the client
+ because this is a transaction-based system for the most part.
+*/
+
+#define M_EMPTY 0 /* Empty slot in utable */
+#define M_CONNECTING 1
+#define M_UNDEFINED 2
+
+#define M_MAIL 3
+#define M_READING 4
+#define M_POSTING 5
+#define M_ULDL 6
+#define M_CHAT 7
+#define M_MONITOR 8
+#define M_TALK 9
+#define M_PAGE 10
diff --git a/name.c b/name.c
new file mode 100644
index 0000000..89027a8
--- /dev/null
+++ b/name.c
@@ -0,0 +1,174 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "common.h"
+#if LACKS_MALLOC_H
+# include <stdlib.h>
+#else
+# include <malloc.h>
+#endif
+
+void
+free_namelist(list)
+NAMELIST *list;
+{
+ NAMENODE *trav = *list, *next;
+ while (trav) {
+ free(trav->word);
+ next = trav->next;
+ free(trav);
+ trav = next;
+ }
+}
+
+void
+create_namelist(list)
+NAMELIST *list;
+{
+ free_namelist(list);
+ *list = NULL;
+}
+
+add_namelist(list, name, name_before)
+NAMELIST *list;
+char *name;
+char *name_before;
+{
+ NAMENODE *node;
+ NAMENODE *trav = *list;
+ node = (NAMENODE *)malloc(sizeof(NAMENODE));
+ if (!node) return S_SYSERR;
+ node->word = (char *)malloc(strlen(name)+1);
+ node->next = NULL;
+ if (!node->word) return S_SYSERR;
+ strcpy(node->word, name);
+ if (*list == NULL ||
+ (name_before && !strcasecmp(name_before, trav->word))) {
+ node->next = *list;
+ *list = node;
+ }
+ else {
+ while (trav->next) {
+ if (name_before && !strcasecmp(name_before, trav->next->word)) {
+ node->next = trav->next;
+ break;
+ }
+ trav = trav->next;
+ }
+ trav->next = node;
+ }
+ return 0;
+}
+
+remove_namelist(list, name)
+NAMELIST *list;
+char *name;
+{
+ NAMENODE *prev = NULL;
+ NAMENODE *curr = *list;
+ while (curr) {
+ if (!strcasecmp(curr->word, name)) {
+ if (prev == NULL) *list = curr->next;
+ else prev->next = curr->next;
+ curr->next = NULL;
+ free(curr->word);
+ free(curr);
+ return S_OK;
+ }
+ prev = curr;
+ curr = curr->next;
+ }
+ return S_NOSUCHREC;
+}
+
+is_in_namelist(list, name)
+NAMELIST list;
+char *name;
+{
+ while (list) {
+ if (!strcasecmp(list->word, name))
+ return 1;
+ list = list->next;
+ }
+ return 0;
+}
+
+apply_namelist(list, fptr, arg)
+NAMELIST list;
+int (*fptr)();
+void *arg;
+{
+ int indx = 0;
+ for(; list!=NULL; list=list->next)
+ (*fptr)(indx++, list->word, arg);
+ return indx;
+}
+
+/*ARGSUSED*/
+_write_list_element(indx, name, fp)
+int indx;
+char *name;
+FILE *fp;
+{
+ fprintf(fp, "%s\n", name);
+ return S_OK;
+}
+
+write_namelist(fname, list)
+char *fname;
+NAMELIST list;
+{
+ FILE *fp;
+ if (list == NULL) unlink(fname);
+ else {
+ if ((fp = fopen(fname, "w")) == NULL) {
+ return S_SYSERR;
+ }
+ apply_namelist(list, _write_list_element, fp);
+ fclose(fp);
+ }
+ return S_OK;
+}
+
+/*ARGSUSED*/
+_read_list_element(indx, rec, list)
+int indx;
+char *rec;
+NAMELIST *list;
+{
+ strip_trailing_space(rec);
+ rec[NAMELEN] = '\0';
+ if (!isspace(*rec) && !is_in_namelist(*list, rec)) {
+ add_namelist(list, rec, NULL);
+ }
+ return S_OK;
+}
+
+read_namelist(fname, list)
+char *fname;
+NAMELIST *list;
+{
+ create_namelist(list);
+ _record_enumerate(fname, 0, _read_list_element, list);
+ return S_OK;
+}
+
+
+
diff --git a/netmail.c b/netmail.c
new file mode 100644
index 0000000..258ea1b
--- /dev/null
+++ b/netmail.c
@@ -0,0 +1,165 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "server.h"
+#include <ctype.h>
+
+#define MAILERFILE "etc/mailers"
+
+/* For the bbs name and tempfile */
+extern SERVERDATA server;
+
+is_valid_address(addr)
+char *addr;
+{
+ if (*addr == '\0') return 0; /* blank */
+ if (*addr == '-') return 0; /* cannot begin with '-' */
+ while (*addr) {
+ if (!isalnum(*addr) && strchr(".%!@:;-_+[]", *addr) == NULL)
+ return 0;
+ addr++;
+ }
+ return 1;
+}
+
+spec_to_mailer(rec, mailer)
+char *rec;
+char *mailer;
+{
+ NAME prefix;
+ rec = _extract_quoted(rec, prefix, sizeof(prefix));
+ rec = _extract_quoted(rec, mailer, sizeof(ADDR));
+ return S_OK;
+}
+
+char *
+lookup_mailer(addr, mailer)
+char *addr;
+char *mailer;
+{
+ char *colon;
+ int rc;
+
+ if ((colon = strchr(addr, ':')) == NULL) return NULL;
+ *colon = '\0';
+ rc = _record_find(MAILERFILE, _match_first, addr, spec_to_mailer, mailer);
+ if (rc != S_OK) return NULL;
+ return colon+1;
+}
+
+ok_for_from_header(str)
+char *str;
+{
+ for (; str && *str; str++)
+ if (strchr("<>;\"'\\|[]{}()%@!", *str)) return 0;
+
+ return 1;
+}
+
+LONG
+mail_file_to_outside(fname, subject, addrspec, is_forward, is_binary)
+char *fname;
+char *subject;
+char *addrspec;
+int is_forward;
+int is_binary;
+{
+ FILE *fp;
+ ACCOUNT acct;
+ ADDR addrbuf;
+ PATH execbuf;
+ PATH mailer;
+ int rc;
+ char *address;
+ char *base;
+
+ if ((fp = fopen(fname, "r")) == NULL) return S_NOSUCHFILE;
+ fclose(fp);
+
+ if (local_bbs_owninfo(&acct) != S_OK) return S_SYSERR;
+
+ if (addrspec == NULL) {
+ sprintf(addrbuf, "%s:%s", MAILER_PREFIX, acct.email);
+ }
+ else {
+ strncpy(addrbuf, addrspec, sizeof addrbuf);
+ }
+
+ if ((address = lookup_mailer(addrbuf, mailer)) == NULL) return S_BADADDRESS;
+
+ if ((fp = fopen(server.tempfile, "w")) == NULL) return S_SYSERR;
+
+ /* Write headers! */
+ if (ok_for_from_header(acct.username))
+ fprintf(fp, "From: %s.bbs (%s)\n", acct.userid, acct.username);
+ else fprintf(fp, "From: %s.bbs (%s)\n", acct.userid, acct.userid);
+ if (is_forward) fprintf(fp, "Subject: %s (fwd)\n", subject);
+ else fprintf(fp, "Subject: %s\n", subject);
+ fprintf(fp, "To: %s\n", address);
+ fprintf(fp, "X-Disclaimer: %s is not responsible for the contents of this message.\n", server.name);
+
+ fprintf(fp, "\n");
+ if (is_forward) fprintf(fp, "*** Forwarded file follows ***\n\n");
+
+ fflush(fp);
+ if (is_binary) {
+ fclose(fp);
+ base = strrchr(fname, '/');
+ if (base) base++;
+ else base = fname;
+ strcpy(execbuf, server.encodebin);
+ strcat(execbuf, " ");
+ strcat(execbuf, base);
+ rc = execute(execbuf, NULL, fname, server.tempfile, "/dev/null", NULL, 1);
+ }
+ else {
+ rc = append_file(fileno(fp), fname);
+ fclose(fp);
+ }
+
+ if (rc != 0) {
+ unlink(server.tempfile);
+ return S_SYSERR;
+ }
+
+ sprintf(execbuf, "%s %s", mailer, address);
+
+ bbslog(3, "FORWARD '%s' to %s by %s\n", subject, address, acct.userid);
+
+ rc = execute(execbuf, NULL, server.tempfile, "/dev/null", "/dev/null", NULL);
+
+ unlink(server.tempfile);
+ return (rc == 0 ? S_OK : S_SYSERR);
+}
+
+LONG
+forward_file_to_outside(fname, title, is_binary)
+char *fname;
+char *title;
+int is_binary;
+{
+ LONG rc;
+
+ rc = mail_file_to_outside(fname, title, NULL, 1, is_binary);
+
+ return rc;
+}
+
+
diff --git a/nmenus.c b/nmenus.c
new file mode 100644
index 0000000..234983e
--- /dev/null
+++ b/nmenus.c
@@ -0,0 +1,747 @@
+
+/*
+Eagles Bulletin Board System
+Copyright (C) 1995, Ray Rocker, rocker@datasync.com
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "client.h"
+#include <ctype.h>
+#if LACKS_MALLOC_H
+# include <stdlib.h>
+#else
+# include <malloc.h>
+#endif
+
+extern LOGININFO myinfo;
+extern char *currentboard;
+char *_menudesc_file;
+
+/* New Menu Function */
+
+int GetMenuLetter(s)
+char *s;
+{
+ char c;
+ for (c = *s; *s; s++) if (*s >= 'A' && *s <= 'Z') return *s;
+ return c;
+}
+
+int GetMenuIndex(t)
+unsigned int t ;
+{
+ t = (t | 0x20) - 'a' ;
+ return t % MAXMENUSZ ;
+}
+
+char InterpretMenuAction(action)
+char *action;
+{
+ if(action[0] != '$')
+ return GetMenuLetter(action) ;
+ if(bbs_check_mail())
+ return action[2] ;
+ return action[1] ;
+}
+
+extern NMENU *bigMenuList ;
+
+NMENU *menuEnt[MAXMENUDEPTH] ;
+int currMenuEnt = -1 ;
+
+#if 0
+/*ARGSUSED*/
+int
+NDoMenu(menu_name)
+char *menu_name ;
+{
+ int found, update = FULLUPDATE;
+ int fieldsz = t_columns/3;
+ char oldcmd = 'h', cmd ;
+ int comm_loc ;
+ NMENU *msp ;
+ NMENUITEM *item = NULL ;
+
+ for(msp = bigMenuList;msp;msp=msp->next)
+ if(!strcmp(menu_name,msp->menu_id))
+ break ;
+ if(!msp || currMenuEnt == MAXMENUDEPTH)
+ return 0 ;
+ currMenuEnt++ ;
+ menuEnt[currMenuEnt] = msp ;
+ if (myinfo.lastlogin == 0 || BITISSET(myinfo.flags, FLG_SHARED)) cmd = 'H';
+ else cmd = InterpretMenuAction(msp->menu_default) ;
+ while (update != EXITMENU) {
+ if (PagePending()) {
+ if (Answer()) update = FULLUPDATE;
+ }
+ if (update == FULLUPDATE || update == PARTUPDATE) {
+ update == FULLUPDATE ? clear() : move(0,0);
+ if(!strcmp(msp->menu_title,"*")) {
+ extern BBSINFO serverinfo ;
+ prints("%s",serverinfo.boardname) ;
+ } else
+ prints("%s",msp->menu_title);
+ clrtoeol();
+ move(0, fieldsz*2);
+ if (currentboard[0] == '\0')
+ prints("No Board Currently Selected");
+ else
+ prints("Current Board: %s", currentboard);
+ move(2,0);
+ clrtoeol();
+ move(1,0);
+ prints("%s",msp->menu_prompt);
+ comm_loc = strlen(msp->menu_prompt) ;
+ clrtoeol();
+ }
+ if (bbs_check_mail()) {
+ move(0, fieldsz);
+ prints("(You have mail.)");
+ }
+ while(cmd != '\n') {
+ item = msp->menucommands[GetMenuIndex(cmd)] ;
+ if(item != NULL && HasPerm(item->enabled)) {
+ move(1,comm_loc);
+ standout();
+ prints("%s", item->name) ;
+ standend();
+ clrtoeol();
+ } else {
+ bell();
+ cmd = oldcmd;
+ }
+ oldcmd = cmd;
+ cmd = MenuGetch();
+ if (!isalpha(cmd) && cmd != '\n') {
+ bell();
+ cmd = oldcmd;
+ }
+ }
+ item = msp->menucommands[GetMenuIndex(oldcmd)] ;
+ if (item != NULL) {
+ update = (item->action_func)(item->action_arg);
+ BITCLR(update, FETCHNEW | NEWDIRECT | NOCLOSE);
+ if (update & MENUERROR)
+ cmd = InterpretMenuAction(item->error_action);
+ else
+ cmd = InterpretMenuAction(item->default_action) ;
+ }
+ }
+ currMenuEnt-- ;
+ return FULLUPDATE;
+}
+#endif
+
+int
+GetMenuLine(msp, cmd)
+NMENU *msp;
+char cmd;
+{
+ int lineno = 4;
+ char mcmd;
+ NMENUITEM *item;
+ cmd = tolower(cmd);
+ for (item=msp->commlist; item; item=item->next)
+ if(HasPerm(item->enabled)) {
+ mcmd = tolower(GetMenuLetter(item->name));
+ if (cmd == mcmd) return lineno;
+ lineno++;
+ }
+ return 0;
+}
+
+/*ARGSUSED*/
+int
+NDoMenu(menu_name)
+char *menu_name ;
+{
+ int found, update = FULLUPDATE;
+ int fieldsz = t_columns/3;
+ char oldcmd, cmd ;
+ int comm_loc ;
+ NMENU *msp ;
+ NMENUITEM *item = NULL;
+ NMENUITEM *selitem, *oldselitem;
+ int oldselline = 0, selline = 0;
+
+ for(msp = bigMenuList;msp;msp=msp->next)
+ if(!strcmp(menu_name,msp->menu_id))
+ break ;
+ if(!msp || currMenuEnt == MAXMENUDEPTH)
+ return 0 ;
+ currMenuEnt++ ;
+ menuEnt[currMenuEnt] = msp ;
+ cmd = InterpretMenuAction(msp->menu_default) ;
+ oldcmd = GetMenuIndex(GetMenuLetter(msp->commlist->name));
+ while (update != EXITMENU) {
+ if (PagePending()) {
+ if (Answer()) update = FULLUPDATE;
+ }
+ if (update == FULLUPDATE || update == PARTUPDATE) {
+ if (update == PARTUPDATE) {
+ if (myinfo.flags & FLG_EXPERT) move(0,0);
+ else {
+ pressreturn();
+ clear();
+ }
+ }
+ else clear();
+ if(!strcmp(msp->menu_title,"*")) {
+ extern BBSINFO serverinfo ;
+ prints("%s",serverinfo.boardname) ;
+ } else
+ prints("%s",msp->menu_title);
+ clrtoeol();
+ move(0, fieldsz*2);
+ if (currentboard[0] == '\0')
+ prints("No Board Currently Selected");
+ else
+ prints("Current Board: %s", currentboard);
+
+ if (!(myinfo.flags & FLG_EXPERT)) {
+ move(4,0) ;
+ clrtoeol() ;
+ for(item=msp->commlist;item;item=item->next)
+ if(HasPerm(item->enabled)) {
+#if MENU_STANDOUT
+ if (item == oldselitem) standout();
+ prints("%s\n",item->help) ;
+ if (item == oldselitem) standend();
+#else
+ if (item == oldselitem) prints("--> %s\n", item->help);
+ else prints(" %s\n",item->help) ;
+#endif
+ }
+ clrtobot() ;
+ }
+
+ move(2,0);
+ clrtoeol();
+ move(1,0);
+ prints("%s",msp->menu_prompt);
+ comm_loc = strlen(msp->menu_prompt) ;
+ clrtoeol();
+ }
+ if (bbs_check_mail()) {
+ move(0, fieldsz);
+ prints("(You have mail.)");
+ }
+ while(cmd != '\n') {
+ selitem = msp->menucommands[GetMenuIndex(cmd)] ;
+ if(selitem != NULL && HasPerm(selitem->enabled)) {
+ if (!(myinfo.flags & FLG_EXPERT)) {
+ selline = GetMenuLine(msp, cmd);
+ if (selline != oldselline) {
+ if (oldselline) {
+ move(oldselline, 0);
+#if MENU_STANDOUT
+ clrtoeol();
+ prints("%s\n", oldselitem->help);
+#else
+ prints(" ");
+#endif
+ }
+ move(selline, 0);
+#if MENU_STANDOUT
+ clrtoeol();
+ standout();
+ prints("%s\n", selitem->help);
+ standend();
+#else
+ prints("-->");
+#endif
+ oldselitem = selitem;
+ oldselline = selline;
+ }
+ }
+ move(1,comm_loc);
+ standout();
+ prints("%s", selitem->name) ;
+ standend();
+ clrtoeol();
+ } else {
+ bell();
+ cmd = oldcmd;
+ }
+ oldcmd = cmd;
+ cmd = MenuGetch();
+ if (cmd == MENU_UP) {
+ if (selitem != NULL)
+ for (item=selitem->prev; item && cmd==MENU_UP; item=item->prev)
+ if (HasPerm(item->enabled)) cmd = GetMenuLetter(item->name);
+ }
+ else if (cmd == MENU_DOWN) {
+ if (selitem != NULL)
+ for (item=selitem->next;item && cmd==MENU_DOWN;item=item->next)
+ if (HasPerm(item->enabled)) cmd = GetMenuLetter(item->name);
+ }
+ if (!isalpha(cmd) && cmd != '\n') {
+ bell();
+ cmd = oldcmd;
+ }
+ }
+ item = msp->menucommands[GetMenuIndex(oldcmd)] ;
+ if (item != NULL) {
+ update = (item->action_func)(item->action_arg);
+ BITCLR(update, FETCHNEW | NEWDIRECT);
+ if (update & MENUERROR)
+ cmd = InterpretMenuAction(item->error_action);
+ else
+ cmd = InterpretMenuAction(item->default_action) ;
+ }
+ }
+ currMenuEnt-- ;
+ return FULLUPDATE;
+}
+
+do_help()
+{
+ NMENU *mp ;
+ NMENUITEM *mip ;
+
+ if(currMenuEnt < 0)
+ return ;
+ mp = menuEnt[currMenuEnt] ;
+ move(3,0) ;
+ clrtoeol() ;
+ standout() ;
+ prints("%s Menu Help Screen\n", mp->menu_id) ;
+ standend() ;
+ for(mip=mp->commlist;mip;mip=mip->next)
+ if(HasPerm(mip->enabled))
+ prints("%s\n",mip->help) ;
+ clrtobot() ;
+ return PARTUPDATE;
+}
+
+do_echo(s)
+char *s ;
+{
+ clear() ;
+ prints("%s",s) ;
+ pressreturn() ;
+ return FULLUPDATE ;
+}
+
+exec_func(s)
+char *s ;
+{
+ int i ;
+ char buf[4096] ;
+ char *p, *q ;
+ SHORT mode ;
+
+ parse_default() ;
+ strncpy(buf,s,sizeof(buf)) ;
+ p=strchr(buf,':') ;
+ if(p) {
+ *p='\0' ;
+ q=strchr(p+1,':') ;
+ if(q) {
+ *q='\0';
+ mode = atoi(q+1);
+ }
+ parse_environment(p+1) ;
+ }
+
+ if (mode > 1) bbs_set_mode(mode);
+ clear() ;
+ refresh() ;
+ i = do_exec(buf,NULL) ;
+ clear() ;
+ if (mode > 1) bbs_set_mode(M_UNDEFINED);
+ return FULLUPDATE ;
+}
+
+exec_func_w_pause(s)
+char *s ;
+{
+ int i ;
+
+ char buf[4096] ;
+ char *p, *q ;
+ SHORT mode = 0;
+
+ parse_default() ;
+ strncpy(buf,s,sizeof(buf)) ;
+ p=strchr(buf,':') ;
+ if(p) {
+ *p='\0' ;
+ q=strchr(p+1,':') ;
+ if(q) {
+ *q='\0';
+ mode = atoi(q+1);
+ }
+ parse_environment(p+1) ;
+ }
+
+ if (mode > 1) bbs_set_mode(mode) ;
+ clear() ;
+ refresh() ;
+ i = do_exec(buf,NULL) ;
+ pressreturn() ;
+ clear() ;
+ if (mode > 1) bbs_set_mode(M_UNDEFINED) ;
+ return FULLUPDATE ;
+}
+
+int do_pipe_more() ;
+
+int revised_pipe_more(s)
+char *s ;
+{
+ char buf[4096] ;
+ char *p ;
+
+ parse_default() ;
+ strncpy(buf,s,sizeof(buf)) ;
+ p=strchr(buf,':') ;
+ if(p) {
+ *p='\0' ;
+ parse_environment(p+1) ;
+ }
+
+ do_pipe_more(buf) ;
+}
+
+struct funcs {
+ char *funcname ;
+ int (*funcptr)() ;
+} ;
+
+int NotImpl(), EndMenu(), XyzMenu(), AdminMenu(), MailMenu(), TalkMenu();
+int MainHelp(), XyzHelp(), AdminHelp(), MailHelp(), TalkHelp();
+int ShowDate(), Welcome(), BoardInfo(), GnuInfo(), EditWelcome();
+int MainReadHelp(), MailReadHelp();
+int FileMenu(), FileHelp(), FileReadHelp();
+int AllUsers(), OnlineUsers(), SetPasswd(), SetUsername(), SetAddress();
+int ShortList(), Monitor();
+int SetTermtype(), ShowOwnInfo(), AddAccount(), DeleteAccount();
+int SetCharset();
+int SetUserData(), SetUserPerms(), ToggleCloak(), ToggleExempt();
+int Query(), QueryEdit();
+int MailSend(), GroupSend(), ReadNewMail(), MailRead();
+int MailDisplay(), MailDelete(), MailDelRange();
+int MailReply(), GroupReply(), Forward();
+int Visit(), BoardCounts(), Zap(), ReadNew(), SequentialRead();
+int Boards(), SelectBoard(), AddBoard(), DeleteBoard(), ChangeBoard();
+int SetBoardMgrs();
+int Post(), MainRead(), ReadMenuSelect();
+int PostDisplay(), PostDelete(), PostMessage(), PostDelRange(), PostMark();
+int PostEdit();
+int FileBoards(), FileSelect(), FileDownload();
+int FileUpload(), FileReceive(), FileReadMenuSelect();
+int Chat(), Kick(), Talk(), SetPager(), SetOverrides();
+int SignatureEdit(), MenuConfig();
+#ifndef REMOTE_CLIENT
+int SelectEditor(), SelectProtocol(), FileReadMenuProto(), FileView();
+#endif
+
+struct funcs funclist[] = {
+ "exec", exec_func,
+ "exec.pause", exec_func_w_pause,
+ "exec.more", revised_pipe_more,
+ "echo", do_echo,
+ "NotImpl",NotImpl,
+ "EndMenu",EndMenu,
+ "Help",do_help,
+ "Menu",NDoMenu,
+ "ShowDate",ShowDate,
+ "Welcome", Welcome,
+ "BoardInfo",BoardInfo,
+ "GnuInfo",GnuInfo,
+ "EditWelcome",EditWelcome,
+ "AllUsers",AllUsers,
+ "OnlineUsers",OnlineUsers,
+ "SetPasswd",SetPasswd,
+ "SetUsername",SetUsername,
+ "SetAddress",SetAddress,
+ "ShortList",ShortList,
+ "Monitor",Monitor,
+ "SetTermtype",SetTermtype,
+ "SetCharset",SetCharset,
+ "ShowOwnInfo",ShowOwnInfo,
+ "AddAccount",AddAccount,
+ "DeleteAccount",DeleteAccount,
+ "SetUserData",SetUserData,
+ "SetUserPerms",SetUserPerms,
+ "ToggleCloak",ToggleCloak,
+ "ToggleExempt",ToggleExempt,
+ "Query",Query,
+ "QueryEdit",QueryEdit,
+ "MailSend",MailSend,
+ "GroupSend",GroupSend,
+ "ReadNewMail",ReadNewMail,
+ "MailRead",MailRead,
+ "Visit",Visit,
+ "BoardCounts",BoardCounts,
+ "Zap",Zap,
+ "ReadNew",ReadNew,
+ "Boards",Boards,
+ "SelectBoard",SelectBoard,
+ "AddBoard",AddBoard,
+ "DeleteBoard",DeleteBoard,
+ "ChangeBoard",ChangeBoard,
+ "SetBoardMgrs",SetBoardMgrs,
+ "Post",Post,
+ "MainRead",MainRead,
+ "FileBoards",FileBoards,
+ "FileSelect",FileSelect,
+ "FileDownload",FileDownload,
+#ifndef REMOTE_CLIENT
+ "SelectProtocol",SelectProtocol,
+#endif
+ "FileUpload",FileUpload,
+ "Chat",Chat,
+ "Kick",Kick,
+ "Talk",Talk,
+ "SetPager",SetPager,
+ "SetOverrides",SetOverrides,
+#ifndef REMOTE_CLIENT
+ "SelectEditor",SelectEditor,
+#endif
+ "SignatureEdit",SignatureEdit,
+ "MenuConfig",MenuConfig,
+ NULL,NULL
+} ;
+
+int (*findfunc(s))()
+char *s ;
+{
+ int i ;
+
+ for(i=0;funclist[i].funcname;i++)
+ if(!strcmp(funclist[i].funcname,s))
+ return funclist[i].funcptr ;
+ return NULL ;
+}
+
+#ifndef REMOTE_CLIENT
+
+/* NOTE: This MUST agree with ACCESSFILE location in init.c */
+#define ACCESSFILE "etc/access"
+
+char *funcstrings[MAX_CLNTCMDS];
+
+form_function_list()
+{
+ FILE *fp;
+ int i;
+ char buf[1024], *equals;
+ for (i=0; i<MAX_CLNTCMDS; i++) funcstrings[i] = NULL;
+ i = 0;
+ if ((fp = fopen(ACCESSFILE, "r")) != NULL) {
+ while (i<MAX_CLNTCMDS && fgets(buf, sizeof buf, fp) != NULL) {
+ if (*buf == '#' || isspace(*buf)) continue;
+ if ((equals = strchr(buf, '=')) != NULL) {
+ *equals = '\0';
+ if ((funcstrings[i] = (char *)malloc(strlen(buf)+1)) != NULL)
+ strcpy(funcstrings[i], buf);
+ }
+ i++;
+ }
+ fclose(fp);
+ }
+}
+
+free_function_list()
+{
+ int i;
+ for (i=0; i<MAX_CLNTCMDS; i++)
+ if (funcstrings[i] != NULL) free(funcstrings[i]);
+}
+