aboutsummaryrefslogtreecommitdiffstats
path: root/FICS
diff options
context:
space:
mode:
authorMarkus Uhlin <markus@nifty-networks.net>2025-09-15 18:50:32 +0200
committerMarkus Uhlin <markus@nifty-networks.net>2025-09-15 18:50:32 +0200
commitc3eee8e333866d92e5fd94ae83cef618758c11bb (patch)
tree234a06fd90bd61a6668490a0cbf8870e6c674b81 /FICS
FICS RPBLC v1.4.61.4.6
Diffstat (limited to 'FICS')
-rw-r--r--FICS/GNU_LICENSE339
-rw-r--r--FICS/HISTORY171
-rw-r--r--FICS/ISC_LICENSE13
-rw-r--r--FICS/README103
-rw-r--r--FICS/README.timeseal16
-rw-r--r--FICS/README_LEGAL23
-rw-r--r--FICS/README_Nash37
-rw-r--r--FICS/adminproc.c2090
-rw-r--r--FICS/adminproc.h72
-rw-r--r--FICS/algcheck.c567
-rw-r--r--FICS/algcheck.h40
-rw-r--r--FICS/assert_error.c12
-rw-r--r--FICS/assert_error.h10
-rw-r--r--FICS/board.c1385
-rw-r--r--FICS/board.h158
-rw-r--r--FICS/build.mk95
-rw-r--r--FICS/command.c1382
-rw-r--r--FICS/command.h148
-rw-r--r--FICS/command_list.h285
-rw-r--r--FICS/common.h73
-rw-r--r--FICS/comproc.c2104
-rw-r--r--FICS/comproc.h85
-rw-r--r--FICS/config.h81
-rw-r--r--FICS/eco.c535
-rw-r--r--FICS/eco.h37
-rw-r--r--FICS/fics_addplayer.c202
-rw-r--r--FICS/fics_getsalt.cpp46
-rw-r--r--FICS/fics_getsalt.h13
-rw-r--r--FICS/ficslim.cpp9
-rw-r--r--FICS/ficslim.h13
-rw-r--r--FICS/ficsmain.c262
-rw-r--r--FICS/ficsmain.h77
-rw-r--r--FICS/formula.c705
-rw-r--r--FICS/formula.h57
-rw-r--r--FICS/gamedb.c2256
-rw-r--r--FICS/gamedb.h205
-rw-r--r--FICS/gameproc.c2164
-rw-r--r--FICS/gameproc.h64
-rw-r--r--FICS/iset.cpp23
-rw-r--r--FICS/iset.h11
-rw-r--r--FICS/legal.c39
-rw-r--r--FICS/legal.h31
-rw-r--r--FICS/legal2.c16
-rw-r--r--FICS/legal2.h10
-rw-r--r--FICS/lists.c567
-rw-r--r--FICS/lists.h76
-rw-r--r--FICS/makerank.c344
-rw-r--r--FICS/makerank.h20
-rw-r--r--FICS/matchproc.c1430
-rw-r--r--FICS/matchproc.h39
-rw-r--r--FICS/maxxes-utils.c46
-rw-r--r--FICS/maxxes-utils.h22
-rw-r--r--FICS/movecheck.c1608
-rw-r--r--FICS/movecheck.h61
-rw-r--r--FICS/multicol.c172
-rw-r--r--FICS/multicol.h41
-rw-r--r--FICS/network.c688
-rw-r--r--FICS/network.h96
-rw-r--r--FICS/obsproc.c2052
-rw-r--r--FICS/obsproc.h59
-rw-r--r--FICS/playerdb.c3278
-rw-r--r--FICS/playerdb.h281
-rw-r--r--FICS/rating_conv.c86
-rw-r--r--FICS/rating_conv.h10
-rw-r--r--FICS/ratings.c1964
-rw-r--r--FICS/ratings.h81
-rw-r--r--FICS/rmalloc.c97
-rw-r--r--FICS/rmalloc.h38
-rw-r--r--FICS/shutdown.c311
-rw-r--r--FICS/shutdown.h19
-rw-r--r--FICS/sought.cpp41
-rw-r--r--FICS/sought.h11
-rw-r--r--FICS/talkproc.c1086
-rw-r--r--FICS/talkproc.h56
-rw-r--r--FICS/utils.c1214
-rw-r--r--FICS/utils.h115
-rw-r--r--FICS/variable.c1066
-rw-r--r--FICS/variable.h55
-rw-r--r--FICS/vers.c5
-rw-r--r--FICS/vers.h32
80 files changed, 33231 insertions, 0 deletions
diff --git a/FICS/GNU_LICENSE b/FICS/GNU_LICENSE
new file mode 100644
index 0000000..a43ea21
--- /dev/null
+++ b/FICS/GNU_LICENSE
@@ -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/FICS/HISTORY b/FICS/HISTORY
new file mode 100644
index 0000000..e302ec3
--- /dev/null
+++ b/FICS/HISTORY
@@ -0,0 +1,171 @@
+
+Versions up to 1.2.16 and between 1.2.23 and 1.3.2 are approximate.
+Information on changes may not be exact or complete.
+
+1.6.2 News bug fixed (Marsalis), caused crash and corrupted index file.
+ ECO brought back (Sparky), bug fixed for use with scratch games
+ (uninitialized moveList caused crash). Examine can now
+ be used to load board positions (Sparky). Works with
+ scratch games also.
+
+1.6.1 News rewritten (Marsalis), Much faster code. Some crash
+ protection code (writing player files after every 'set'
+ command for example) has been supressed for speed.
+ Bug in new playerfile code fixed (sanity checks added to
+ avoid crashes).
+
+1.6 Playerfile formats completely recoded (DAV) new and old
+ formats recognized. New format is written always. The
+ new format has expanded functionallity, more compact form,
+ and much faster processing.
+
+1.5.2 Ghosts hopefully killed; no more timeseal sitting;
+ Oldmoves implementation changes; game structure shrunk.
+ fics_hostname now takes it's contents from SERVER_NAME,
+ which should nwo be a DNS resolvable hostname.
+ fics_addplayer new account message modernized to include
+ brief instructions on how to connect to the server and
+ and warn about the use of anauthorzied duplicate accounts.
+ both adminproc.c and fics_addplayer modified.
+
+1.5.1 Fixes in journal commands, some bughouse bugs.
+
+1.5.0 Journals allowed. Some fixes for NULL v. NULL games.
+
+1.4.0 Initial bughouse version.
+
+1.3.3 clearing and showing individual messages.
+
+1.3.2 fixes to open file bugs;
+ clearing and showing messages from a particular player.
+
+1.3.1 kibitz level; tell/whisper bugs and similar fixed;
+ noplay and gnotify lists.
+
+1.3.0 new admin structure for coordination, responding to suggestions, etc;
+ got rid of annoyingly large version numbers :-).
+
+1.2.23-1.2.24 ???
+
+1.2.22 Examining of history games supported.
+
+1.2.21 Language variable; can't refresh private games;
+ Got rid of "You are not observing" message when you are observing.
+ New news interface; cnewsi, cnewsf, and news show on startup.
+ Percent of life stats.
+
+1.2.20 New help interface; index command.
+
+1.2.19 Examine mode (without history game support)
+ ECO and NIC codes now supported for history games and for
+ interactive reporting on games in process.
+ PGN format now supported for email of game scores.
+ Direct sendmail support provided with the SENDMAILPROG
+ definition in config.h, which overrides any MAILPROGRAM
+ definition. Provides for more reliable and efficient operation
+ under heavy load.
+
+1.2.18 variable mailmess: mails messages to you.
+ messages give the user feedback on what was sent.
+ simul commands simallabort, simadjourn, simalladjourn
+ (aliases saa sadj saadj)
+ simabort given alias sa
+ cshout and it prompts fixed
+ it gives better feedback
+ can restart simuls (or untimed games in simul form) with simmatch
+ game format reduced in size
+1.2.17 asetpasswd now sends mail to the player telling him the new
+ password.
+ extra info in up for admins
+ annunreg seen by all admins
+ hostinfo and lock removed as was fics_delplayer, fics_mailproc
+ and mail_proc.
+ see messages from one person, clear messages from a person
+ More simul bug fixes.
+ simprev (alias sp), goboard (alias go, goto), gonum commands.
+ allow admin to reset RD; ratings may be set with 0 games played.
+1.2.16 Many simul bugs fixed, one fatal bug corrected which caused
+ a crash during a simul.
+ unobserve command.
+ MAILPROGRAM replaced by "sendmail -t" and appropriate "To:"
+ and subject headers. This should help with blank mail
+ message problem. mailhelp and mailsource still depend on MAILPROGRAM
+ stuff added for SUN5 compilation
+ best info shown in who
+1.2.15 Simul bug fixed - losing connection led to a crash
+ adjudicate written (forcedraw, forcewin, forceres commands removed)
+ hostinfo removed from addplayer
+1.2.14 Simul code made operational.
+ games list ordered.
+ tally code disabled
+1.2.13 forcedraw, forcewin, forceres commands
+ asetv command replaces pose
+1.2.12 bug fixes.
+ pose weakened.
+1.2.11 First stable version of FICS. unnotify and unalias had big problems
+ that led to crashes and corrupt player files.
+ siteinfo removed - caused infinite lag
+ mailmess
+1.2.10 bug fixes, max length enforced in alias to stop a crash.
+1.2.9 autoboot out code added.
+1.2.8 aliases fixed, @ added, can alias system commands
+ flagging without material claims a draw, not a win.
+1.2.7 filter list operational.
+ multicol.c used to drop 2048 bytes
+1.2.6 rank and best, makerank
+ time for next shout shown.
+ bug fix to timelog
+ login/logouts logged
+ addcomment and showcomment
+1.2.5 Summon written.
+ ban list
+ (TD) in who
+ board styles fixed
+ shout quotas, it and shout modified
+ admin command to toggle (*)
+ fixes to up
+ admins can observe anything
+1.2.4 td list
+ qtell, getpi, commands for TD
+ asetblitz, asetstd
+ news,anews
+ wild chess made rated, best shows wild, new file
+ asetwild
+ next
+ SGI compilation possible.
+1.2.3 lists
+ (FM) (GM) (IM) (C) shown in who
+ raisedead given a second argument to rename a player
+ ahelp, info written
+ help files split into help and info
+ shutdown fixed
+1.2.2 internal version
+1.2.1 bug fixes.
+ admin gods can only use pose.
+ siteinfo shown
+1.2 notifiedby flag
+ remplayer, asethandle, raisedead written
+ nuke improved
+ query disabled
+1.1 who bug fixed - caused a crash if more than 10 players on.
+ ++++ shown in who for unreg players
+ early version of nuke.
+ (*) shown if level 10 admin
+ level 60 given title Assistant superuser.
+ who A, who l, who U, who R
+ Look of announce and other stuff improved.
+1.0 Original Nash code.
+-----------------------------------------------------------------------------
+Here is a more stable, hopefully more portable release of the
+FICS source code with many features added.
+
+People who have helped improve this from previous releases
+(March 1st 1995 onward) include (FICS names):
+DAV, foxbat, grimm, Hawk, hersco, loon, mann, Shane, Sparky, vek
+SORRY if you feel you should have been on this list but were forgotten.
+
+And Most thanks to red (Richard Nash) for the original code!
+ And Micheal Moore for the original idea of an ICS.
+
+-- Uploaded by Shane Hudson (Shane on FICS).
+Thanks to everyone for their support of FICS.
diff --git a/FICS/ISC_LICENSE b/FICS/ISC_LICENSE
new file mode 100644
index 0000000..915b183
--- /dev/null
+++ b/FICS/ISC_LICENSE
@@ -0,0 +1,13 @@
+Copyright (c) 2023 - 2025 Markus Uhlin <maxxe@rpblc.net>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/FICS/README b/FICS/README
new file mode 100644
index 0000000..5a76069
--- /dev/null
+++ b/FICS/README
@@ -0,0 +1,103 @@
+README file for FICS - Free Internet Chess Server
+
+Please report any changes back to chess@ics.onenet.net. The code is
+very volatile right now, so keep gratuitous changes to a minimum. DO NOT
+send mail about the code to Richard Nash. He is no longer working on the
+FICS project. (We thank him for all his past work!)
+
+HOW TO START YOUR CHESS SERVER
+
+A. INTRODUCTION
+ First off, you should be familiar with programming in C and with the
+nuances of the system on which you will be running the server. The
+documentation is sparse, so often the code is the best place to go to find
+the answers.
+
+B. Compiling the code
+ 1. cd FICS
+ 2. Edit the file config.h to set the directories where the program's
+ data can be found.
+ 3. Copy the Makefile for your system to Makefile. At the time of writing
+ this there are only a few machines represented, so you may have to edit.
+ If you create a new Makefile that works on your machine, please mail
+ it back to the above address so that we can include it in the distribution.
+ NB. You may have to # out dfree.c and dfree.o in the makefile - they are
+ for debug perposes only (sun4).
+ 4. type 'makedepend' type 'make'
+ 5. If it doesn't compile, well...do your best. If you modify the code to
+ get it to compile on your machine, use #ifdef's wisely and send the
+ changes back to us so that we can include them in the distribution.
+ Also check the code for already defined ifdefs.
+ 6. Once code is built, start the server with "fics", the default port
+ is 5000. To specify another, use "fics -p #". login as AdminGuy
+ with no password and set his password. :-) Good luck.
+
+
+C. Registering with the ratings server.
+*** THERE IS NO RATINGS SERVER IN OPERATION AT PRESENT ***
+** The procedures below were for FICS 1.0 and are now obsolete **
+** Distributed ratings may come in a later version **
+
+ If you want to be hooked in with the distributed ratings server, and I
+hope everyone does, send mail to XXXXXXXXXXX to get your password and to
+confirm to and from email addresses.
+
+D. Configuring for the ratings server.
+ Once you have your email address, the servers email address and your secret
+code, copy the file config/hostinfo.client.format to config/hostinfo.client.
+Then edit that file and replace the appropriate entry with the correct
+information. Here is what an example should look like.
+
+client fics_client@foobar.com
+fics_server@XXXXXXX.XXX MyPassword 0 0
+
+ The next part is a little tricky, and you may need root privileges on your
+machine to do it. The goal here is this... To get the contents of the mail
+sent to fics_client piped into the fics_mailproc program. If there is no way
+you are getting root privileges, go right to way #4.
+
+1. One way is to create a mail alias that does it.
+
+ Here is the pertanent line from my aliases file.
+
+fics_client: "|/Users/nash/Source/FICS/FICS-distrib/fics_mailproc client"
+
+The '|' character causes mail to fics_client to get sent to the program that
+follows. The 'client' parameter to the fics_mailproc program is important
+because the fics_mailproc program is two-faced. It can run as a client or as
+the server.
+
+ Once you are set up, the ratings server should send you accounts for all
+of the registered network players.
+
+2. Another way is to create a user called fics_client with the same UID as
+ the user you will be running FICS as. This is to allow fics and
+ fics_mailproc to have the same uid. Then, in /etc/crontab put an entry
+ that periodically calls the 'mailproc' program. Here is my entry:
+0,15,30,45 * * * * nash /Users/nash/Source/FICS/FICS-distrib/mailproc /usr/spool/mail/fics_client "/Users/nash/Source/FICS/FICS-distrib/fics_mailproc client"
+
+3. Yet another way is to simply run mailproc by hand when you want mail to
+ be picked up. In a C-shell this works:
+while (1)
+ /Users/nash/Source/FICS/FICS-distrib/mailproc /usr/spool/mail/fics_client "/Users/nash/Source/FICS/FICS-distrib/fics_mailproc client"
+ sleep 900
+end
+
+4. There is yet one more way to set up a connection to the rating's server
+ if there is no way to get the mail client set up. It may be lest reliable
+ than the mail way of doing things because it doesn't have store and
+ forward capability if network errors occur during transmission. But it
+ will probably work. The mailproc program can be a daemon wait for
+ connections on a port and then send what it gets to a program. For
+ example:
+mailproc 5001 "/Users/nash/Source/FICS/FICS-distrib/fics_mailproc client"
+ Will wait for the server to connect and send the mail directly. Of course
+ you will need to set this up with the server administrator so that he
+ knows to send directly rather than through mail.
+
+E. Adding local players.
+ Of course this is your server and you have the write to include or exclude
+players as you like. To add a player user the fics_addplayer function or
+use addplayer when logged in as an ADMIN. To remove a player, just put *
+in the password field of their file. They won't be able to log in.
+
diff --git a/FICS/README.timeseal b/FICS/README.timeseal
new file mode 100644
index 0000000..3696fdf
--- /dev/null
+++ b/FICS/README.timeseal
@@ -0,0 +1,16 @@
+/*
+
+ The timeseal package must NEVER be distributed without
+ permission from Henrik Gram <u940456@daimi.aau.dk>
+
+ It's security is based SOLELY on keeping the crypt alg.
+ secret.
+
+ Both the timeseal server and client code was written
+ entirely by Henrik Gram.
+
+ Only the FICS sites at chess.onenet.net and *.daimi.aau.dk
+ are allowed to have it and use it - and NEVER charge for
+ its use nor for the distribution of the timeseal client.
+
+*/
diff --git a/FICS/README_LEGAL b/FICS/README_LEGAL
new file mode 100644
index 0000000..655d207
--- /dev/null
+++ b/FICS/README_LEGAL
@@ -0,0 +1,23 @@
+fics - An internet chess server.
+
+ Copyright (C) 1993 Richard V. Nash
+
+ 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
+ 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.
+
+
+You may also contact the author(s) at
+ Richard Nash: email - nash@visus.com
+ phone - (412)-488-3600
+
diff --git a/FICS/README_Nash b/FICS/README_Nash
new file mode 100644
index 0000000..9f6fa1b
--- /dev/null
+++ b/FICS/README_Nash
@@ -0,0 +1,37 @@
+From nash@nomos.com Tue May 30 07:56:37 1995
+Content-Type: text/plain
+Mime-Version: 1.0 (NeXT Mail 3.3 v118.2)
+From: "Richard V. Nash" <nash@nomos.com>
+Date: Tue, 30 May 95 08:52:10 -0400
+To: danke%daimi.aau.dk.kris%dgate.org.chess%onenet.net.romeo@dcs.warwick.ac.uk
+Subject: Please remove nash@nomos.com and visus.com from help files
+Content-Length: 1130
+Status: RO
+X-Lines: 25
+
+Hello Chess Server Administrators,
+
+ I, nash@nomos.com, am getting dozens of emails everyday inquiring about
+chess servers. I would like to stop this. Could you please remove any
+reference to nash@nomos.com or nash@visus.com from the help files associated
+with FICS. This is in both your running systems and versions of the code that
+is being passed around or placed in ftp'able directories.
+
+ There are also references to ftp.visus.com in help files. These should also
+be removed.
+
+ Could you also pass this message along to anybody that you know who may
+have a copy of FICS but who I've missed on my search for FICS's.
+
+ That said, thanks to all of you for taking the FICS code and making it a
+success! I'm so happy that my work to keep chess servers available and free
+did not fail.
+
+Rich
+-----------------------+---------------------------+
+| Richard V. Nash | NOMOS Corporation |
+| nash@nomos.com | Suite 400 |
+| Tel. (412)-934-5477 | 2591 Wexford-Bayne Road |
+| Fax. (412)-934-5488 | Sewickley, PA 15143 |
++----------------------+---------------------------+
+
diff --git a/FICS/adminproc.c b/FICS/adminproc.c
new file mode 100644
index 0000000..9f192bc
--- /dev/null
+++ b/FICS/adminproc.c
@@ -0,0 +1,2090 @@
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include <sys/param.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+
+#include "adminproc.h"
+#include "command.h"
+#include "comproc.h"
+#include "fics_getsalt.h"
+#include "ficsmain.h"
+#include "gamedb.h"
+#include "gameproc.h"
+#include "maxxes-utils.h"
+#include "multicol.h"
+#include "network.h"
+#include "obsproc.h"
+#include "playerdb.h"
+#include "ratings.h"
+#include "rmalloc.h"
+#include "talkproc.h"
+#include "utils.h"
+
+#define PASSLEN 8
+
+PUBLIC int num_anews = -1;
+
+/*
+ * adjudicate
+ *
+ * Usage: adjudicate white_player black_player result
+ *
+ * Adjudicates a saved (stored) game between white_player and black_player.
+ * The result is one of: abort, draw, white, black. "Abort" cancels the game
+ * (no win, loss or draw), "white" gives white_player the win, "black" gives
+ * black_player the win, and "draw" gives a draw.
+ */
+PUBLIC int
+com_adjudicate(int p, param_list param)
+{
+ int wp, wconnected, bp, bconnected, g, inprogress, confused = 0;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (!FindPlayer(p, param[0].val.word, &wp, &wconnected))
+ return COM_OK;
+ if (!FindPlayer(p, param[1].val.word, &bp, &bconnected)) {
+ if (!wconnected)
+ player_remove(wp);
+ return COM_OK;
+ }
+
+ inprogress = (parray[wp].game >= 0 && parray[wp].opponent == bp);
+
+ if (inprogress) {
+ g = parray[wp].game;
+ } else {
+ g = game_new();
+
+ if (game_read(g, wp, bp) < 0) {
+ confused = 1;
+ pprintf(p, "There is no stored game %s vs. %s\n",
+ parray[wp].name,
+ parray[bp].name);
+ } else {
+ garray[g].white = wp;
+ garray[g].black = bp;
+ }
+ }
+
+ if (!confused) {
+ if (strstr("abort", param[2].val.word) != NULL) {
+ game_ended(g, WHITE, END_ADJABORT);
+
+ pcommand(p, "message %s Your game \"%s vs. %s\" has "
+ "been aborted.",
+ parray[wp].name,
+ parray[wp].name,
+ parray[bp].name);
+ pcommand(p, "message %s Your game \"%s vs. %s\" has "
+ "been aborted.",
+ parray[bp].name,
+ parray[wp].name,
+ parray[bp].name);
+ } else if (strstr("draw", param[2].val.word) != NULL) {
+ game_ended(g, WHITE, END_ADJDRAW);
+
+ pcommand(p, "message %s Your game \"%s vs. %s\" has "
+ "been adjudicated as a draw",
+ parray[wp].name,
+ parray[wp].name,
+ parray[bp].name);
+ pcommand(p, "message %s Your game \"%s vs. %s\" has "
+ "been adjudicated as a draw",
+ parray[bp].name,
+ parray[wp].name,
+ parray[bp].name);
+ } else if (strstr("white", param[2].val.word) != NULL) {
+ game_ended(g, WHITE, END_ADJWIN);
+
+ pcommand(p, "message %s Your game \"%s vs. %s\" has "
+ "been adjudicated as a win",
+ parray[wp].name,
+ parray[wp].name,
+ parray[bp].name);
+ pcommand(p, "message %s Your game \"%s vs. %s\" has "
+ "been adjudicated as a loss",
+ parray[bp].name,
+ parray[wp].name,
+ parray[bp].name);
+ } else if (strstr("black", param[2].val.word) != NULL) {
+ game_ended(g, BLACK, END_ADJWIN);
+
+ pcommand(p, "message %s Your game \"%s vs. %s\" has "
+ "been adjudicated as a loss",
+ parray[wp].name,
+ parray[wp].name,
+ parray[bp].name);
+ pcommand(p, "message %s Your game \"%s vs. %s\" has "
+ "been adjudicated as a win",
+ parray[bp].name,
+ parray[wp].name,
+ parray[bp].name);
+ } else {
+ confused = 1;
+ pprintf(p, "Result must be one of: abort draw white "
+ "black\n");
+ }
+ } /* !confused */
+
+ if (!confused) {
+ pprintf(p, "Game adjudicated.\n");
+
+ if (!inprogress) {
+ game_delete(wp, bp);
+ } else {
+ return COM_OK;
+ }
+ }
+
+ game_remove(g);
+
+ if (!wconnected)
+ player_remove(wp);
+ if (!bconnected)
+ player_remove(bp);
+ return COM_OK;
+}
+
+/*
+ * create_news_file: Creates either a general or and admin news
+ * file, depending upon the admin switch.
+ */
+PRIVATE int
+create_news_file(int p, param_list param, int admin)
+{
+ FILE *fp;
+ char filename[MAX_FILENAME_SIZE] = { '\0' };
+ int fd;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (admin) {
+ if (param[0].val.integer > num_anews) {
+ pprintf(p, "There must be an admin news index #%d "
+ "before you can create the file.",
+ param[0].val.integer);
+ } else {
+ msnprintf(filename, sizeof filename, "%s/adminnews.%d",
+ news_dir,
+ param[0].val.integer);
+ fd = open(filename, g_open_flags[1], g_open_modes);
+ if (fd < 0)
+ return COM_FAILED;
+ else if ((fp = fdopen(fd, "w")) != NULL) {
+ fprintf(fp, "%s\n", param[1].val.string);
+ fclose(fp);
+ } else
+ close(fd);
+ }
+ } else {
+ if (param[0].val.integer > num_news) {
+ pprintf(p, "There must be a news index #%d before "
+ "you can create the file.", param[0].val.integer);
+ } else {
+ msnprintf(filename, sizeof filename, "%s/news.%d",
+ news_dir,
+ param[0].val.integer);
+ fd = open(filename, g_open_flags[1], g_open_modes);
+ if (fd < 0)
+ return COM_FAILED;
+ else if ((fp = fdopen(fd, "w")) != NULL) {
+ fprintf(fp, "%s\n", param[1].val.string);
+ fclose(fp);
+ } else
+ close(fd);
+ }
+ }
+
+ return COM_OK;
+}
+
+PRIVATE int
+add_item(char *new_item, char *filename)
+{
+ FILE *new_fp, *old_fp;
+ char junk[MAX_LINE_SIZE] = { '\0' };
+ char tmp_file[MAX_FILENAME_SIZE] = { '\0' };
+ int fd;
+
+ msnprintf(tmp_file, sizeof tmp_file, "%s/.tmp.idx", news_dir);
+
+ fd = open(tmp_file, g_open_flags[1], g_open_modes);
+
+ if (fd < 0)
+ return 0;
+ else if ((new_fp = fdopen(fd, "w")) == NULL) {
+ close(fd);
+ return 0;
+ }
+
+ fprintf(new_fp, "%s", new_item);
+
+ if ((old_fp = fopen(filename, "r")) == NULL)
+ goto end;
+
+ while (1) {
+ if (fgets(junk, sizeof junk, old_fp) == NULL ||
+ feof(old_fp))
+ break;
+ fprintf(new_fp, "%s", junk);
+ }
+
+ end:
+ (void) fclose(new_fp);
+
+ if (old_fp) {
+ (void) fclose(old_fp);
+ (void) remove(filename);
+ }
+
+ (void) rename(tmp_file, filename);
+ return 1;
+}
+
+/*
+ * Adds a new item to either the general or admin news index file,
+ * depending upon the admin switch.
+ */
+PRIVATE int
+create_news_index(int p, param_list param, int admin)
+{
+ char filename[MAX_FILENAME_SIZE] = { '\0' };
+ char new_item[MAX_LINE_SIZE] = { '\0' };
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (admin) {
+ if (strlen(param[0].val.string) > 50) {
+ pprintf(p, "Sorry, you must limit an index to 50 "
+ "charaters! Admin news index not created.\n");
+ } else {
+ num_anews++;
+
+ snprintf(new_item, sizeof new_item, "%ld %d %s\n",
+ (long int)time(NULL),
+ num_anews,
+ param[0].val.string);
+ snprintf(filename, sizeof filename,
+ "%s/newadminnews.index", news_dir);
+
+ if (add_item(new_item, filename)) {
+ pprintf(p, "Index for admin news item #%d "
+ "created.\n", num_anews);
+ pprintf(p, "Please use 'canewsf' to include "
+ "more info.\n");
+ } else {
+ pprintf(p, "Something went wrong creating item."
+ "\nNotify Marsalis.\n");
+ }
+ }
+ } else {
+ if (strlen(param[0].val.string) > 50) {
+ pprintf(p, "Sorry, you must limit an index to 50 "
+ "charaters! News index not created.\n");
+ } else {
+ num_news++;
+
+ snprintf(filename, sizeof filename, "%s/newnews.index",
+ news_dir);
+ snprintf(new_item, sizeof new_item, "%ld %d %s\n",
+ (long int)time(NULL),
+ num_news,
+ param[0].val.string);
+
+ if (add_item(new_item, filename)) {
+ pprintf(p, "Index for news item #%d created.\n",
+ num_news);
+ pprintf(p, "Please use 'cnewsf' to include "
+ "more info.\n");
+ } else {
+ pprintf(p, "Something went wrong creating item."
+ "\nNotify Marsalis.\n");
+ }
+ }
+ }
+
+ return COM_OK;
+}
+
+/* cnewsi
+ *
+ * Usage: cnewsi message
+ *
+ *
+ * This command adds a new item to the news index. The message is limited to
+ * 45 characters for formating purposes. In essence, the news index works
+ * like a newspaper headline, giving the user enough information to know
+ * whether they should read the entire news file for that item. After
+ * creating the news item, the command reports the news item number along
+ * with a reminder to create a news file if necessary.
+ */
+PUBLIC int
+com_cnewsi(int p, param_list param)
+{
+ return create_news_index(p, param, 0);
+}
+
+/* cnewsf
+ *
+ * Usage: cnewsf # message
+ *
+ * This command allows you to add additional information about a news item
+ * that had previously been created using 'cnewsi'. The '#' is the number
+ * of the news index and 'message' is the additional text. You can also
+ * modify a previous news item description and thus update the news item
+ * easily.
+ */
+PUBLIC int
+com_cnewsf(int p, param_list param)
+{
+ return create_news_file(p, param, 0);
+}
+
+PUBLIC int
+com_canewsi(int p, param_list param)
+{
+ return create_news_index(p, param, 1);
+}
+
+PUBLIC int
+com_canewsf(int p, param_list param)
+{
+ return create_news_file(p, param, 1);
+}
+
+/*
+ * anews
+ *
+ *
+ * Usage: anews [#, all]
+ *
+ * This command is used to display anews (admin news) entries. The
+ * entries are numbered. "Anews #" displays that anews item. "Anews
+ * all" will display all items.
+ *
+ */
+PUBLIC int
+com_anews(int p, param_list param)
+{
+ FILE *fp = NULL;
+ char count[10] = { '\0' };
+ char filename[MAX_FILENAME_SIZE] = { '\0' };
+ char junk[MAX_LINE_SIZE] = { '\0' };
+ char *junkp = NULL;
+ const char *v_scan_junk = "%" SCNd64 " " "%9s";
+ int found = 0;
+ int64_t lval = 0;
+ time_t crtime = 0;
+
+ msnprintf(filename, sizeof filename, "%s/newadminnews.index", news_dir);
+
+ if ((fp = fopen(filename, "r")) == NULL) {
+ fprintf(stderr, "Cant find news index.\n");
+ return COM_OK;
+ }
+
+ _Static_assert(9 < ARRAY_SIZE(count), "Array too small");
+
+ if (param[0].type == 0) {
+ /*
+ * No params - then just display index over news.
+ */
+
+ msnprintf(filename, sizeof filename, "%s/newadminnews.index",
+ news_dir);
+
+ pprintf(p, "Index of recent admin news items:\n");
+
+ if (fgets(junk, sizeof junk, fp) == NULL) {
+ warnx("%s: fgets() error", __func__);
+ fclose(fp);
+ return COM_FAILED;
+ }
+ if (sscanf(junk, v_scan_junk, &lval, count) != 2) {
+ warnx("%s: sscanf() error: too few items", __func__);
+ fclose(fp);
+ return COM_FAILED;
+ }
+
+ rscan_news2(fp, p, 9);
+
+ junkp = junk;
+ junkp = nextword(junkp);
+ junkp = nextword(junkp);
+
+ crtime = lval;
+ pprintf(p, "%3s (%s) %s", count, fix_time(strltime(&crtime)),
+ junkp);
+ fclose(fp);
+ } else if (param[0].type == TYPE_WORD &&
+ !strcmp(param[0].val.word, "all")) {
+ /*
+ * Param all - displays all news items.
+ */
+
+ pprintf(p, "Index of all admin news items:\n");
+
+ if (fgets(junk, sizeof junk, fp) == NULL) {
+ warnx("%s: fgets() error", __func__);
+ fclose(fp);
+ return COM_FAILED;
+ }
+ if (sscanf(junk, v_scan_junk, &lval, count) != 2) {
+ warnx("%s: sscanf() error: too few items", __func__);
+ fclose(fp);
+ return COM_FAILED;
+ }
+
+ rscan_news(fp, p, 0);
+
+ junkp = junk;
+ junkp = nextword(junkp);
+ junkp = nextword(junkp);
+
+ crtime = lval;
+ pprintf(p, "%3s (%s) %s", count, fix_time(strltime(&crtime)),
+ junkp);
+ fclose(fp);
+ } else {
+ while (!feof(fp) && !found) {
+ junkp = junk;
+
+ if (fgets(junk, sizeof junk, fp) == NULL)
+ break;
+
+ if (strlen(junk) > 1) {
+ if (sscanf(junkp, v_scan_junk, &lval,
+ count) != 2) {
+ warnx("%s: sscanf() error...",
+ __func__);
+ }
+
+ crtime = lval;
+
+ if (!strcmp(count, param[0].val.word)) {
+ found = 1;
+
+ junkp = nextword(junkp);
+ junkp = nextword(junkp);
+
+ pprintf(p, "ANEWS %3s (%s) %s\n", count,
+ fix_time(strltime(&crtime)), junkp);
+ }
+ }
+ }
+
+ fclose(fp);
+
+ if (!found) {
+ pprintf(p, "Bad index number!\n");
+ return COM_OK;
+ }
+
+ msnprintf(filename, sizeof filename, "%s/adminnews.%s",
+ news_dir,
+ param[0].val.word);
+
+ if ((fp = fopen(filename, "r")) == NULL) {
+ pprintf(p, "No more info.\n");
+ return COM_OK;
+ }
+
+ fclose(fp);
+ msnprintf(filename, sizeof filename, "adminnews.%s",
+ param[0].val.word);
+
+ if (psend_file(p, news_dir, filename) < 0) {
+ pprintf(p, "Internal error - couldn't send news file!"
+ "\n");
+ }
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+strcmpwild(char *mainstr, char *searchstr)
+{
+ size_t len[2];
+
+ len[0] = strlen(mainstr);
+ len[1] = strlen(searchstr);
+
+ if (len[0] < len[1])
+ return 1;
+
+ for (size_t i = 0; i < len[0]; i++) {
+ if (searchstr[i] == '*')
+ return 0;
+ if (mainstr[i] != searchstr[i])
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * chkip
+ *
+ * Usage: chkip ip_address
+ *
+ * This command returns the names of all users currently logged on
+ * from a given IP address.
+ */
+PUBLIC int
+com_checkIP(int p, param_list param)
+{
+ char *ipstr = param[0].val.word;
+ int p1;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ pprintf(p, "Matches the following player(s):\n\n");
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (!strcmpwild(dotQuad(parray[p1].thisHost), ipstr) &&
+ (parray[p1].status != PLAYER_EMPTY)) {
+ pprintf(p, "%16.16s %s\n", parray[p1].name,
+ dotQuad(parray[p1].thisHost));
+ }
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_checkSOCKET(int p, param_list param)
+{
+ int fd = param[0].val.integer;
+ int p1, flag;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ flag = 0;
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].socket == fd) {
+ flag = 1;
+ pprintf(p, "Socket %d is used by %s\n", fd,
+ parray[p1].name);
+ }
+ }
+
+ if (!flag)
+ pprintf(p, "Socket %d is unused!\n", fd);
+ return COM_OK;
+}
+
+/*
+ * chkpl
+ *
+ * Usage: chkpl handle
+ *
+ * This command displays server information about a given user. Items
+ * displayed are:
+ *
+ * number X in parray of size Y
+ * name
+ * login
+ * fullName
+ * emailAddress
+ * socket
+ * registered
+ * last_tell
+ * last_channel
+ * logon_time
+ * adminLevel
+ * thisHost
+ * lastHost
+ * num_comments
+ */
+PUBLIC int
+com_checkPLAYER(int p, param_list param)
+{
+ char *v_player = param[0].val.word;
+ int p1;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ p1 = player_search(p, param[0].val.word);
+
+ if (!p1)
+ return COM_OK;
+
+ if (p1 < 0) {
+ p1 = (-p1) - 1;
+ pprintf(p, "%s is not logged in.\n", v_player);
+ stolower(v_player);
+
+ pprintf(p, "name = %s\n", parray[p1].name);
+ pprintf(p, "login = %s\n", parray[p1].login);
+ pprintf(p, "fullName = %s\n",
+ (parray[p1].fullName
+ ? parray[p1].fullName
+ : "(none)"));
+ pprintf(p, "emailAddress = %s\n",
+ (parray[p1].emailAddress
+ ? parray[p1].emailAddress
+ : "(none)"));
+ pprintf(p, "adminLevel = %d\n", parray[p1].adminLevel);
+#if 0
+ pprintf(p, "network_player = %d\n", parray[p1].network_player);
+#endif
+ pprintf(p, "lastHost = %s\n", dotQuad(parray[p1].lastHost));
+ pprintf(p, "num_comments = %d\n", parray[p1].num_comments);
+
+ player_remove(p1);
+ return COM_OK;
+ } else {
+ char tbuf[30] = { '\0' };
+
+ p1 = p1 - 1;
+
+ pprintf(p, "%s is number %d in parray of size %d\n", v_player, p1,
+ (p_num + 1));
+ pprintf(p, "name = %s\n", parray[p1].name);
+ pprintf(p, "login = %s\n", parray[p1].login);
+ pprintf(p, "fullName = %s\n",
+ (parray[p1].fullName
+ ? parray[p1].fullName
+ : "(none)"));
+ pprintf(p, "emailAddress = %s\n",
+ (parray[p1].emailAddress
+ ? parray[p1].emailAddress
+ : "(none)"));
+ pprintf(p, "socket = %d\n", parray[p1].socket);
+ pprintf(p, "registered = %d\n", parray[p1].registered);
+ pprintf(p, "last_tell = %d\n", parray[p1].last_tell);
+ pprintf(p, "last_channel = %d\n", parray[p1].last_channel);
+ pprintf(p, "logon_time = %s",
+ (ctime_r(&parray[p1].logon_time, tbuf) != NULL
+ ? &tbuf[0]
+ : "n/a"));
+ pprintf(p, "adminLevel = %d\n", parray[p1].adminLevel);
+#if 0
+ pprintf(p, "network_player = %d\n", parray[p1].network_player);
+#endif
+ pprintf(p, "thisHost = %s\n", dotQuad(parray[p1].thisHost));
+ pprintf(p, "lastHost = %s\n", dotQuad(parray[p1].lastHost));
+ pprintf(p, "num_comments = %d\n", parray[p1].num_comments);
+ }
+
+ return COM_OK;
+}
+
+/*
+ * chkts
+ *
+ * Usage: chkts
+ *
+ * This command displays all current users who are using timeseal.
+ */
+PUBLIC int
+com_checkTIMESEAL(int p, param_list param)
+{
+ int p1, count = 0;
+
+ /* XXX: maybe unused */
+ (void) p1;
+ (void) count;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ pprintf(p, "The following player(s) are using timeseal:\n\n");
+
+#ifdef TIMESEAL
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].status != PLAYER_EMPTY &&
+ con[parray[p1].socket].timeseal) {
+ pprintf(p, "%s\n", parray[p1].name);
+ count++;
+ }
+ }
+ pprintf(p, "\nNumber of people using timeseal: %d\n", count);
+#endif
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_checkGAME(int p, param_list param)
+{
+ char tmp[10 + 1 + 7]; // enough to store number
+ // 'black: ' and '\0'
+ int found = 0;
+ int p1, g, link;
+ multicol *m;
+ time_t startTime;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (g_num == 0) {
+ pprintf(p, "No games are currently linked into the 'garray' "
+ "structure.\n");
+ return COM_OK;
+ }
+
+ if (param[0].type == TYPE_WORD) { // a player name
+ if ((p1 = player_find_part_login(param[0].val.word)) < 0) {
+ pprintf(p, "%s doesn't appear to be logged in.\n",
+ param[0].val.word);
+ pprintf(p, "Searching through garray to find matching "
+ "game numbers.\n");
+ pprintf(p, "Use chkgame <number> to view the results."
+ "\n");
+
+ m = multicol_start(g_num * 2); // Obviously no more
+ // than that
+
+ for (g = 0; g < g_num; g++) {
+ memset(tmp, 0, sizeof tmp); // XXX
+ multicol_store(m, tmp); // is this call right?
+
+ if (!strcasecmp(garray[g].white_name,
+ param[0].val.word)) {
+ msnprintf(tmp, sizeof tmp, "White: %d",
+ g);
+ multicol_store(m, tmp);
+ found = 1;
+ }
+ if (!strcasecmp(garray[g].black_name,
+ param[0].val.word)) {
+ msnprintf(tmp, sizeof tmp, "Black: %d",
+ g);
+ multicol_store(m, tmp);
+ found = 1;
+ }
+ }
+
+ if (found)
+ multicol_pprint(m, p, parray[p].d_width, 2);
+ else
+ pprintf(p, "No matching games were found.\n");
+ multicol_end(m);
+
+ return COM_OK;
+ }
+
+ if ((g = parray[p1].game) < 0) {
+ pprintf(p, "%s doesn't appear to be playing a game.\n",
+ parray[p1].name);
+ pprintf(p, "Searching through garray to find matching "
+ "game numbers.\n");
+ pprintf(p, "Use chkgame <number> to view the results."
+ "\n");
+
+ m = multicol_start(g_num * 2); // Obviously no more
+ // than that
+
+ for (g = 0; g < g_num; g++) {
+ if (garray[g].white == p1) {
+ msnprintf(tmp, sizeof tmp, "White: %d",
+ g);
+ multicol_store(m, tmp);
+ found = 1;
+ }
+
+ if (garray[g].black == p1) {
+ msnprintf(tmp, sizeof tmp, "Black: %d",
+ g);
+ multicol_store(m, tmp);
+ found = 1;
+ }
+ }
+
+ if (found)
+ multicol_pprint(m, p, parray[p].d_width, 2);
+ else
+ pprintf(p, "No matching games were found.\n");
+ multicol_end(m);
+
+ return COM_OK;
+ }
+ } else {
+ if ((g = param[0].val.integer - 1) < 0 || g >= g_num) {
+ pprintf(p, "The current range of game numbers is 1 to "
+ "%d.\n", g_num);
+ return COM_OK;
+ }
+ }
+
+ startTime = untenths(garray[g].timeOfStart);
+
+ pprintf(p, "Current stored info for game %d (garray[%d]):\n", (g + 1),
+ g);
+ pprintf(p, "Initial white time: %d Initial white increment %d\n",
+ (garray[g].wInitTime / 600),
+ (garray[g].wIncrement / 10));
+ pprintf(p, "Initial black time: %d Initial black increment %d\n",
+ (garray[g].bInitTime / 600),
+ (garray[g].bIncrement / 10));
+ pprintf(p, "Time of starting: %s\n", strltime(&startTime));
+ pprintf(p, "Game is: %s (%d) vs. %s (%d)\n",
+ garray[g].white_name,
+ garray[g].white_rating,
+ garray[g].black_name,
+ garray[g].black_rating);
+ pprintf(p, "White parray entry: %d Black parray entry %d\n",
+ garray[g].white,
+ garray[g].black);
+
+ if ((link = garray[g].link) >= 0) {
+ pprintf(p, "Bughouse linked to game: %d\n",
+ (garray[g].link + 1));
+ pprintf(p, "Partner is playing game: %s (%d) vs. %s (%d)\n",
+ garray[link].white_name,
+ garray[link].white_rating,
+ garray[link].black_name,
+ garray[link].black_rating);
+ } else {
+ pprintf(p, "Game is not bughouse or link to partner's game not "
+ "found.\n");
+ }
+
+ pprintf(p, "Game is %s\n", (garray[g].rated ? "rated" : "unrated"));
+ pprintf(p, "Game is %s\n", (garray[g].private ? "private" :
+ "not private"));
+
+ if (garray[g].type == TYPE_UNTIMED)
+ pprintf(p, "Games is of type: untimed\n");
+ else if (garray[g].type == TYPE_BLITZ)
+ pprintf(p, "Games is of type: blitz\n");
+ else if (garray[g].type == TYPE_STAND)
+ pprintf(p, "Games is of type: standard\n");
+ else if (garray[g].type == TYPE_NONSTANDARD)
+ pprintf(p, "Games is of type: non-standard\n");
+ else if (garray[g].type == TYPE_WILD)
+ pprintf(p, "Games is of type: wild\n");
+ else if (garray[g].type == TYPE_LIGHT)
+ pprintf(p, "Games is of type: lightning\n");
+ else if (garray[g].type == TYPE_BUGHOUSE)
+ pprintf(p, "Games is of type: bughouse\n");
+ else
+ pprintf(p, "Games is of type: Unknown - Error!\n");
+
+ pprintf(p, "%d halfmove(s) have been made\n", garray[g].numHalfMoves);
+
+ if (garray[g].status == GAME_ACTIVE)
+ game_update_time(g);
+
+ pprintf(p, "White's time %s Black's time ",
+ tenth_str((garray[g].wTime > 0 ? garray[g].wTime : 0), 0));
+ pprintf(p, "%s\n",
+ tenth_str((garray[g].bTime > 0 ? garray[g].bTime : 0), 0));
+ pprintf(p, "The clock is%sticking\n", ((garray[g].clockStopped ||
+ garray[g].status != GAME_ACTIVE) ? " not " : " "));
+
+ if (garray[g].status == GAME_EMPTY)
+ pprintf(p, "Game status: GAME_EMPTY\n");
+ else if (garray[g].status == GAME_NEW)
+ pprintf(p, "Game status: GAME_NEW\n");
+ else if (garray[g].status == GAME_ACTIVE)
+ pprintf(p, "Game status: GAME_ACTIVE\n");
+ else if (garray[g].status == GAME_EXAMINE)
+ pprintf(p, "Game status: GAME_EXAMINE\n");
+ else
+ pprintf(p, "Game status: Unknown - Error!\n");
+ return COM_OK;
+}
+
+/*
+ * remplayer
+ *
+ * Usage: remplayer name
+ *
+ * Removes an account. A copy of its files are saved under .rem.* which can
+ * be found in the appropriate directory (useful in case of an accident).
+ *
+ * The account's details, messages, games and logons are all saved as
+ * 'zombie' files. These zombie accounts are not listed in handles or
+ * totals.
+ */
+PUBLIC int
+com_remplayer(int p, param_list param)
+{
+ char *v_player = param[0].val.word;
+ char playerlower[MAX_LOGIN_NAME] = { '\0' };
+ int p1, lookup;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ mstrlcpy(playerlower, v_player, sizeof(playerlower));
+ stolower(playerlower);
+ p1 = player_new();
+ lookup = player_read(p1, playerlower);
+
+ if (!lookup) {
+ if (parray[p].adminLevel <= parray[p1].adminLevel &&
+ !player_ishead(p)) {
+ pprintf(p, "You can't remove an admin with a level "
+ "higher than or equal to yourself.\n");
+ player_remove(p1);
+ return COM_OK;
+ }
+ }
+
+ player_remove(p1);
+
+ if (lookup) {
+ pprintf(p, "No player by the name %s is registered.\n", v_player);
+ return COM_OK;
+ }
+
+ if (player_find_bylogin(playerlower) >= 0) {
+ pprintf(p, "A player by that name is logged in.\n");
+ return COM_OK;
+ }
+
+ if (!player_kill(playerlower)) {
+ pprintf(p, "Player %s removed.\n", v_player);
+ UpdateRank(TYPE_BLITZ, NULL, NULL, v_player);
+ UpdateRank(TYPE_STAND, NULL, NULL, v_player);
+ UpdateRank(TYPE_WILD, NULL, NULL, v_player);
+ } else {
+ pprintf(p, "Remplayer failed.\n");
+ }
+ return COM_OK;
+}
+
+/*
+ * raisedead
+ *
+ * Usage: raisedead oldname [newname]
+ *
+ * Restores an account that has been previously removed using "remplayer".
+ * The zombie files from which it came are removed. Under most
+ * circumstances, you restore the account to the same handle it had
+ * before (oldname). However, in some circumstances you may need to
+ * restore the account to a different handle, in which case you include
+ * "newname" as the new handle. After "raisedead", you may need to use the
+ * "asetpasswd" command to get the player started again as a registered
+ * user, especially if the account had been locked
+ * by setting the password to *.
+ */
+PUBLIC int
+com_raisedead(int p, param_list param)
+{
+ char *v_player = param[0].val.word;
+ char newplayerlower[MAX_LOGIN_NAME] = { '\0' };
+ char playerlower[MAX_LOGIN_NAME] = { '\0' };
+ int p1, p2, lookup;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ mstrlcpy(playerlower, v_player, sizeof playerlower);
+ stolower(playerlower);
+
+ if (player_find_bylogin(playerlower) >= 0) {
+ pprintf(p, "A player by that name is logged in.\n");
+ pprintf(p, "Can't raise until they leave.\n");
+ return COM_OK;
+ }
+
+ p1 = player_new();
+ lookup = player_read(p1, playerlower);
+ player_remove(p1);
+
+ if (!lookup) {
+ pprintf(p, "A player by the name %s is already registered.\n",
+ v_player);
+ pprintf(p, "Obtain a new handle for the dead person.\n");
+ pprintf(p, "Then use raisedead [oldname] [newname].\n");
+ return COM_OK;
+ }
+
+ if (param[1].type == TYPE_NULL) {
+ if (!player_raise(playerlower)) {
+ pprintf(p, "Player %s raised from dead.\n", v_player);
+
+ p1 = player_new();
+
+ if (!player_read(p1, playerlower)) {
+ if (parray[p1].s_stats.rating > 0) {
+ UpdateRank(TYPE_STAND, v_player,
+ &parray[p1].s_stats, v_player);
+ }
+ if (parray[p1].b_stats.rating > 0) {
+ UpdateRank(TYPE_BLITZ, v_player,
+ &parray[p1].b_stats, v_player);
+ }
+ if (parray[p1].w_stats.rating > 0) {
+ UpdateRank(TYPE_WILD, v_player,
+ &parray[p1].w_stats, v_player);
+ }
+ }
+
+ player_remove(p1);
+ } else {
+ pprintf(p, "Raisedead failed.\n");
+ }
+
+ return COM_OK;
+ } else {
+ char *newplayer = param[1].val.word;
+
+ mstrlcpy(newplayerlower, newplayer, sizeof newplayerlower);
+ stolower(newplayerlower);
+
+ if (player_find_bylogin(newplayerlower) >= 0) {
+ pprintf(p, "A player by the requested name is "
+ "logged in.\n");
+ pprintf(p, "Can't reincarnate until they leave.\n");
+ return COM_OK;
+ }
+
+ p2 = player_new();
+ lookup = player_read(p2, newplayerlower);
+ player_remove(p2);
+
+ if (!lookup) {
+ pprintf(p, "A player by the name %s is already "
+ "registered.\n", v_player);
+ pprintf(p, "Obtain another new handle for the dead "
+ "person.\n");
+ return COM_OK;
+ }
+
+ if (!player_reincarn(playerlower, newplayerlower)) {
+ pprintf(p, "Player %s reincarnated to %s.\n", v_player,
+ newplayer);
+
+ p2 = player_new();
+
+ if (!player_read(p2, newplayerlower)) {
+ strfree(parray[p2].name);
+ parray[p2].name = xstrdup(newplayer);
+ player_save(p2);
+
+ if (parray[p2].s_stats.rating > 0) {
+ UpdateRank(TYPE_STAND, newplayer,
+ &parray[p2].s_stats, newplayer);
+ }
+ if (parray[p2].b_stats.rating > 0) {
+ UpdateRank(TYPE_BLITZ, newplayer,
+ &parray[p2].b_stats, newplayer);
+ }
+ if (parray[p2].w_stats.rating > 0) {
+ UpdateRank(TYPE_WILD, newplayer,
+ &parray[p2].w_stats, newplayer);
+ }
+ }
+
+ player_remove(p2);
+ } else {
+ pprintf(p, "Raisedead failed.\n");
+ }
+ }
+
+ return COM_OK;
+}
+
+/*
+ * addplayer
+ *
+ * Usage: addplayer playername emailaddress realname
+ *
+ * Adds a local player to the server with the handle of "playername". For
+ * example:
+ *
+ * addplayer Hawk u940456@daimi.aau.dk Henrik Gram
+ */
+PUBLIC int
+com_addplayer(int p, param_list param)
+{
+ char *newemail = param[1].val.word;
+ char *newname = param[2].val.string;
+ char *newplayer = param[0].val.word;
+ char newplayerlower[MAX_LOGIN_NAME];
+ char password[PASSLEN + 1];
+ char salt[FICS_SALT_SIZE];
+ char text[2048];
+ int i;
+ int p1;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (strlen(newplayer) >= MAX_LOGIN_NAME) {
+ pprintf(p, "Player name is too long\n");
+ return COM_OK;
+ }
+
+ if (strlen(newplayer) < 3) {
+ pprintf(p, "Player name is too short\n");
+ return COM_OK;
+ }
+
+ if (!alphastring(newplayer)) {
+ pprintf(p, "Illegal characters in player name. "
+ "Only A-Za-z allowed.\n");
+ return COM_OK;
+ }
+
+ mstrlcpy(newplayerlower, newplayer, sizeof newplayerlower);
+ stolower(newplayerlower);
+
+ p1 = player_new();
+
+ if (!player_read(p1, newplayerlower)) {
+ pprintf(p, "A player by the name %s is already registered.\n",
+ newplayerlower);
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ parray[p1].name = xstrdup(newplayer);
+ parray[p1].login = xstrdup(newplayerlower);
+ parray[p1].fullName = xstrdup(newname);
+ parray[p1].emailAddress = xstrdup(newemail);
+
+ if (strcmp(newemail, "none")) {
+ for (i = 0; i < PASSLEN; i++)
+ password[i] = ('a' + arc4random_uniform(26));
+ password[i] = '\0';
+
+ mstrlcpy(salt, fics_getsalt(), sizeof salt);
+
+ parray[p1].passwd = xstrdup(crypt(password, salt));
+ } else {
+ password[0] = '\0';
+ parray[p1].passwd = xstrdup(password);
+ }
+
+ parray[p1].registered = 1;
+ parray[p1].rated = 1;
+
+ player_add_comment(p, p1, "Player added by addplayer.");
+ player_save(p1);
+ player_remove(p1);
+
+ pprintf(p, "Added: >%s< >%s< >%s< >%s<\n", newplayer, newname, newemail,
+ password);
+
+ if (strcmp(newemail, "none")) {
+ msnprintf(text, sizeof text,
+ "\nYour player account has been created.\n\n"
+ "Login Name: %s\n"
+ "Full Name: %s\n"
+ "Email Address: %s\n"
+ "Initial Password: %s\n\n"
+
+ "If any of this information is incorrect, please\n"
+ "contact the administrator to get it corrected.\n\n"
+
+ "You may change your password with the password\n"
+ "command on the server.\n\n"
+
+ "Please be advised that if this is an unauthorized\n"
+ "duplicate account for you, by using it you take\n"
+ "the risk of being banned from accessing this chess\n"
+ "server.\n\n"
+
+ "To connect to the server and use this account:\n\n"
+ "\ttelnet %s 5000\n\n"
+ "and enter your handle name and password.\n\n"
+
+ "Regards,\n\nThe FICS admins\n", newplayer, newname,
+ newemail, password, fics_hostname);
+
+ mail_string_to_address(newemail, "FICS Account Created", text);
+
+ if ((p1 = player_find_part_login(newplayer)) >= 0) {
+ pprintf_prompt(p1, "\n\nYou are now registered! "
+ "Confirmation together with\npassword is sent to "
+ "your email address.\n\n");
+ return COM_OK;
+ }
+
+ return COM_OK;
+ } else {
+ if ((p1 = player_find_part_login(newplayer)) >= 0) {
+ pprintf_prompt(p1, "\n\nYou are now registered! "
+ "You have NO password!\n\n");
+ return COM_OK;
+ }
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_pose(int p, param_list param)
+{
+ int p1;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if ((p1 = player_find_part_login(param[0].val.word)) < 0) {
+ pprintf(p, "%s is not logged in.\n", param[0].val.word);
+ return COM_OK;
+ }
+
+ if ((parray[p].adminLevel <= parray[p1].adminLevel) &&
+ !player_ishead(p)) {
+ pprintf(p, "You can only pose as players below your adminlevel."
+ "\n");
+ return COM_OK;
+ }
+
+ pprintf(p, "Command issued as %s\n", parray[p1].name);
+ pcommand(p1, "%s\n", param[1].val.string);
+ return COM_OK;
+}
+
+/*
+ * asetv
+ *
+ * Usage: asetv user instructions
+ *
+ * This command executes "set" instructions as if they had been made by the
+ * user indicated. For example, "asetv DAV shout 0" would set DAV's shout
+ * variable to 0.
+ */
+PUBLIC int
+com_asetv(int p, param_list param)
+{
+ int p1;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if ((p1 = player_find_part_login(param[0].val.word)) < 0) {
+ pprintf(p, "%s is not logged in.\n", param[0].val.word);
+ return COM_OK;
+ }
+
+ if ((parray[p].adminLevel <= parray[p1].adminLevel) &&
+ !player_ishead(p)) {
+ pprintf(p, "You can only aset players below your adminlevel."
+ "\n");
+ return COM_OK;
+ }
+
+ pprintf(p, "Command issued as %s\n", parray[p1].name);
+ pcommand(p1, "set %s\n", param[1].val.string);
+ return COM_OK;
+}
+
+/*
+ * announce
+ *
+ * Usage: announce message
+ *
+ * Broadcasts your message to all logged on users. Announcements reach all
+ * users and cannot be censored in any way (such as by "set shout 0").
+ */
+PUBLIC int
+com_announce(int p, param_list param)
+{
+ int count = 0;
+ int p1;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (!printablestring(param[0].val.string)) {
+ pprintf(p, "Your message contains some unprintable "
+ "character(s).\n");
+ return COM_OK;
+ }
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (p1 == p)
+ continue;
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ count++;
+ pprintf_prompt(p1, "\n\n **ANNOUNCEMENT** from %s: %s\n\n",
+ parray[p].name, param[0].val.string);
+ }
+
+ pprintf(p, "\n(%d) **ANNOUNCEMENT** from %s: %s\n\n", count,
+ parray[p].name, param[0].val.string);
+ return COM_OK;
+}
+
+/*
+ * annunreg
+ *
+ * Usage: annunreg message
+ *
+ * Broadcasts your message to all logged on unregistered users, and admins,
+ * too. Announcements reach all unregistered users and admins and cannot be
+ * censored in any way (such as by "set shout 0").
+ */
+PUBLIC int
+com_annunreg(int p, param_list param)
+{
+ int count = 0;
+ int p1;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (!printablestring(param[0].val.string)) {
+ pprintf(p, "Your message contains some unprintable "
+ "character(s).\n");
+ return COM_OK;
+ }
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (p1 == p)
+ continue;
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ if ((parray[p1].registered) &&
+ (parray[p1].adminLevel < ADMIN_ADMIN))
+ continue;
+ count++;
+ pprintf_prompt(p1, "\n\n **UNREG ANNOUNCEMENT** from %s: %s"
+ "\n\n", parray[p].name, param[0].val.string);
+ }
+
+ pprintf(p, "\n(%d) **UNREG ANNOUNCEMENT** from %s: %s\n\n", count,
+ parray[p].name, param[0].val.string);
+ return COM_OK;
+}
+
+PUBLIC int
+com_muzzle(int p, param_list param)
+{
+ pprintf(p, "Obsolete command: Please use +muzzle and -muzzle.\n");
+ return COM_OK;
+}
+
+PUBLIC int
+com_cmuzzle(int p, param_list param)
+{
+ pprintf(p, "Obsolete command: Please use +cmuzzle and -cmuzzle.\n");
+ return COM_OK;
+}
+
+/*
+ * asetpasswd
+ *
+ * Usage: asetpasswd player {password,*}
+ *
+ * This command sets the password of the player to the password given.
+ * If '*' is specified then the player's account is locked, and no password
+ * will work until a new one is set by asetpasswd.
+ *
+ * If the player is connected, he is told of the new password and the name
+ * of the admin who changed it, or likewise of his account status. An
+ * email message is mailed to the player's email address as well.
+ */
+PUBLIC int
+com_asetpasswd(int p, param_list param)
+{
+ char salt[FICS_SALT_SIZE];
+ char subject[400];
+ char text[10100];
+ int p1, connected;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+
+ if ((parray[p].adminLevel <= parray[p1].adminLevel) && !player_ishead(p)) {
+ pprintf(p, "You can only set password for players below your "
+ "adminlevel.\n");
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ if (!parray[p1].registered) {
+ pprintf(p, "You cannot set the password of an unregistered "
+ "player!\n");
+ return COM_OK;
+ }
+
+ if (parray[p1].passwd)
+ rfree(parray[p1].passwd);
+
+ if (param[1].val.word[0] == '*') {
+ parray[p1].passwd = xstrdup(param[1].val.word);
+ pprintf(p, "Account %s locked!\n", parray[p1].name);
+ msnprintf(text, sizeof text, "Password of %s is now useless. "
+ "Your account at our FICS has been locked.\n",
+ parray[p1].name);
+ } else {
+ mstrlcpy(salt, fics_getsalt(), sizeof salt);
+
+ parray[p1].passwd = xstrdup(crypt(param[1].val.word, salt));
+
+ msnprintf(text, sizeof text, "Password of %s changed to "
+ "\"%s\".\n",
+ parray[p1].name, param[1].val.word);
+ pprintf(p, "%s", text);
+ }
+
+ if (param[1].val.word[0] == '*') {
+ msnprintf(subject, sizeof subject, "FICS: %s has locked your "
+ "account.", parray[p].name);
+ if (connected)
+ pprintf_prompt(p1, "\n%s\n", subject);
+ } else {
+ msnprintf(subject, sizeof subject, "FICS: %s has changed your "
+ "password.", parray[p].name);
+ if (connected)
+ pprintf_prompt(p1, "\n%s\n", subject);
+ }
+
+ mail_string_to_address(parray[p1].emailAddress, subject, text);
+ player_save(p1);
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+}
+
+/*
+ * asetemail
+ *
+ * Usage: asetemail player [address]
+ *
+ * Sets the email address of the player to the address given. If the
+ * address is omited, then the player's email address is cleared. The
+ * person's email address is revealed to them when they use the "finger"
+ * command, but no other users -- except admins -- will have another
+ * player's email address displayed.
+ */
+PUBLIC int
+com_asetemail(int p, param_list param)
+{
+ int p1, connected;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+
+ if ((parray[p].adminLevel <= parray[p1].adminLevel) &&
+ !player_ishead(p)) {
+ pprintf(p, "You can only set email addr for players below "
+ "your adminlevel.\n");
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ if (parray[p1].emailAddress)
+ rfree(parray[p1].emailAddress);
+
+ if (param[1].type == TYPE_NULL) {
+ parray[p1].emailAddress = NULL;
+ pprintf(p, "Email address for %s removed\n", parray[p1].name);
+ } else {
+ parray[p1].emailAddress = xstrdup(param[1].val.word);
+ pprintf(p, "Email address of %s changed to \"%s\".\n",
+ parray[p1].name,
+ param[1].val.word);
+ }
+
+ player_save(p1);
+
+ if (connected) {
+ if (param[1].type == TYPE_NULL) {
+ pprintf_prompt(p1, "\n\n%s has removed your email "
+ "address.\n\n", parray[p].name);
+ } else {
+ pprintf_prompt(p1, "\n\n%s has changed your email "
+ "address.\n\n", parray[p].name);
+ }
+ } else {
+ player_remove(p1);
+ }
+
+ return COM_OK;
+}
+
+/*
+ * asetrealname
+ *
+ * Usage: asetrealname user newname
+ *
+ * This command sets the user's real name (as displayed to admins on finger
+ * notes) to "newname".
+ */
+PUBLIC int
+com_asetrealname(int p, param_list param)
+{
+ int p1, connected;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+
+ if ((parray[p].adminLevel <= parray[p1].adminLevel) &&
+ !player_ishead(p)) {
+ pprintf(p, "You can only set real names for players below your "
+ "adminlevel.\n");
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ if (parray[p1].fullName)
+ rfree(parray[p1].fullName);
+
+ if (param[1].type == TYPE_NULL) {
+ parray[p1].fullName = NULL;
+ pprintf(p, "Real name for %s removed\n", parray[p1].name);
+ } else {
+ parray[p1].fullName = xstrdup(param[1].val.word);
+ pprintf(p, "Real name of %s changed to \"%s\".\n",
+ parray[p1].name,
+ param[1].val.word);
+ }
+
+ player_save(p1);
+
+ if (connected) {
+ if (param[1].type == TYPE_NULL) {
+ pprintf_prompt(p1, "\n\n%s has removed your real name."
+ "\n\n", parray[p].name);
+ } else {
+ pprintf_prompt(p1, "\n\n%s has changed your real name."
+ "\n\n", parray[p].name);
+ }
+ } else {
+ player_remove(p1);
+ }
+
+ return COM_OK;
+}
+
+/*
+ * asethandle
+ *
+ * Usage: asethandle oldname newname
+ *
+ * This command changes the handle of the player from oldname to
+ * newname. The various player information, messages, logins, comments
+ * and games should be automatically transferred to the new account.
+ */
+PUBLIC int
+com_asethandle(int p, param_list param)
+{
+ char *newplayer = param[1].val.word;
+ char *v_player = param[0].val.word;
+ char newplayerlower[MAX_LOGIN_NAME] = { '\0' };
+ char playerlower[MAX_LOGIN_NAME] = { '\0' };
+ int p1;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ mstrlcpy(playerlower, v_player, sizeof playerlower);
+ stolower(playerlower);
+
+ mstrlcpy(newplayerlower, newplayer, sizeof newplayerlower);
+ stolower(newplayerlower);
+
+ if (player_find_bylogin(playerlower) >= 0) {
+ pprintf(p, "A player by that name is logged in.\n");
+ return COM_OK;
+ }
+
+ if (player_find_bylogin(newplayerlower) >= 0) {
+ pprintf(p, "A player by that new name is logged in.\n");
+ return COM_OK;
+ }
+
+ p1 = player_new();
+
+ if (player_read(p1, playerlower)) {
+ pprintf(p, "No player by the name %s is registered.\n", v_player);
+ player_remove(p1);
+ return COM_OK;
+ } else {
+ if ((parray[p].adminLevel <= parray[p1].adminLevel) &&
+ !player_ishead(p)) {
+ pprintf(p, "You can't set handles for an admin with "
+ "a level higher than or equal to yourself.\n");
+ player_remove(p1);
+ return COM_OK;
+ }
+ }
+
+ player_remove(p1);
+ p1 = player_new();
+
+ if ((!player_read(p1, newplayerlower)) &&
+ (strcmp(playerlower, newplayerlower))) {
+ pprintf(p, "Sorry that handle is already taken.\n");
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ player_remove(p1);
+
+ if ((!player_rename(playerlower, newplayerlower)) &&
+ (!player_read(p1, newplayerlower))) {
+ pprintf(p, "Player %s renamed to %s.\n", v_player, newplayer);
+ strfree(parray[p1].name);
+ parray[p1].name = xstrdup(newplayer);
+ player_save(p1);
+
+ if (parray[p1].s_stats.rating > 0) {
+ UpdateRank(TYPE_STAND, newplayer, &parray[p1].s_stats,
+ v_player);
+ }
+ if (parray[p1].b_stats.rating > 0) {
+ UpdateRank(TYPE_BLITZ, newplayer, &parray[p1].b_stats,
+ v_player);
+ }
+ if (parray[p1].w_stats.rating > 0) {
+ UpdateRank(TYPE_WILD, newplayer, &parray[p1].w_stats,
+ v_player);
+ }
+ } else {
+ pprintf(p, "Asethandle failed.\n");
+ }
+
+ player_remove(p1);
+ return COM_OK;
+}
+
+/*
+ * asetadmin
+ *
+ * Usage: asetadmin player AdminLevel
+ *
+ * Sets the admin level of the player with the following restrictions.
+ * 1. You can only set the admin level of players lower than yourself.
+ * 2. You can only set the admin level to a level that is lower than
+ * yourself.
+ */
+PUBLIC int
+com_asetadmin(int p, param_list param)
+{
+ int p1, connected;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_GOD);
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+
+ if ((parray[p].adminLevel <= parray[p1].adminLevel) &&
+ !player_ishead(p)) {
+ pprintf(p, "You can only set adminlevel for players below your "
+ "adminlevel.\n");
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ if ((parray[p1].login) == (parray[p].login)) {
+ pprintf(p, "You can't change your own adminlevel.\n");
+ return COM_OK;
+ }
+
+ if ((param[1].val.integer >= parray[p].adminLevel) &&
+ !player_ishead(p)) {
+ pprintf(p, "You can't promote someone to or above your "
+ "adminlevel.\n");
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ parray[p1].adminLevel = param[1].val.integer;
+ pprintf(p, "Admin level of %s set to %d.\n",
+ parray[p1].name,
+ parray[p1].adminLevel);
+ player_save(p1);
+
+ if (connected) {
+ pprintf_prompt(p1, "\n\n%s has set your admin level to %d.\n\n",
+ parray[p].name,
+ parray[p1].adminLevel);
+ } else {
+ player_remove(p1);
+ }
+
+ return COM_OK;
+}
+
+PRIVATE void
+SetRating(int p1, param_list param, statistics *s)
+{
+ s->rating = param[1].val.integer;
+
+ if (s->ltime == 0L)
+ s->sterr = 70.0;
+
+ if (param[2].type == TYPE_INT) {
+ s->win = param[2].val.integer;
+ if (param[3].type == TYPE_INT) {
+ s->los = param[3].val.integer;
+ if (param[4].type == TYPE_INT) {
+ s->dra = param[4].val.integer;
+ if (param[5].type == TYPE_INT) {
+ s->sterr = (double) param[5].val.integer;
+ }
+ }
+ }
+ }
+
+ s->num = s->win + s->los + s->dra;
+
+ if (s->num == 0) {
+ s->ltime = 0L;
+ } else {
+ s->ltime = time(NULL);
+ }
+}
+
+/*
+ * asetblitz
+ *
+ * Usage: asetblitz handle rating won lost drew RD
+ *
+ * This command allows admins to set a user's statistics for Blitz games.
+ * The parameters are self-explanatory: rating, # of wins, # of losses,
+ * # of draws, and ratings deviation.
+ */
+PUBLIC int
+com_asetblitz(int p, param_list param)
+{
+ int p1, connected;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+
+ SetRating(p1, param, &parray[p1].b_stats);
+ player_save(p1);
+ UpdateRank(TYPE_BLITZ, parray[p1].name, &parray[p1].b_stats,
+ parray[p1].name);
+ pprintf(p, "Blitz rating for %s modified.\n", parray[p1].name);
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+}
+
+/*
+ * asetwild
+ *
+ * Usage: asetwild handle rating won lost drew RD
+ *
+ * This command allows admins to set a user's statistics for Wild games.
+ * The parameters are self-explanatory: rating, # of wins, # of losses,
+ * # of draws, and ratings deviation.
+ */
+PUBLIC int
+com_asetwild(int p, param_list param)
+{
+ int p1, connected;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+
+ SetRating(p1, param, &parray[p1].w_stats);
+ player_save(p1);
+ UpdateRank(TYPE_WILD, parray[p1].name, &parray[p1].w_stats,
+ parray[p1].name);
+ pprintf(p, "Wild rating for %s modified.\n", parray[p1].name);
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+}
+
+/*
+ * asetstd
+ *
+ * Usage: asetstd handle rating won lost drew RD
+ *
+ * This command allows admins to set a user's statistics for Standard games.
+ * The parameters are self-explanatory: rating, # of wins, # of losses, # of
+ * draws, and ratings deviation.
+ */
+PUBLIC int
+com_asetstd(int p, param_list param)
+{
+ int p1, connected;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+
+ SetRating(p1, param, &parray[p1].s_stats);
+ player_save(p1);
+ UpdateRank(TYPE_STAND, parray[p1].name, &parray[p1].s_stats,
+ parray[p1].name);
+ pprintf(p, "Standard rating for %s modified.\n", parray[p1].name);
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+}
+
+/*
+ * asetlight
+ *
+ * Usage: asetlight handle rating won lost drew RD
+ *
+ * This command allows admins to set a user's statistics for Lightning games.
+ * The parameters are self-explanatory: rating, # of wins, # of losses, # of
+ * draws, and ratings deviation.
+ */
+PUBLIC int
+com_asetlight(int p, param_list param)
+{
+ int p1, connected;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+
+ SetRating(p1, param, &parray[p1].l_stats);
+ player_save(p1);
+ pprintf(p, "Lightning rating for %s modified.\n", parray[p1].name);
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+}
+
+/*
+ * nuke
+ *
+ * Usage: nuke user
+ *
+ * This command disconnects the user from the server. The user is informed
+ * that she/he has been nuked by the admin named and a comment is
+ * automatically placed in the user's files (if she/he is a registered
+ * user, of course).
+ */
+PUBLIC int
+com_nuke(int p, param_list param)
+{
+ int p1, fd;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if ((p1 = player_find_part_login(param[0].val.word)) < 0) {
+ pprintf(p, "%s isn't logged in.\n", param[0].val.word);
+ } else {
+ if ((parray[p].adminLevel > parray[p1].adminLevel) ||
+ player_ishead(p)) {
+ pprintf(p, "Nuking: %s\n", param[0].val.word);
+ pprintf(p, "Please leave a comment explaining why %s "
+ "was nuked.\n", parray[p1].name);
+ pprintf(p1, "\n\n**** You have been kicked out by %s! "
+ "****\n\n", parray[p].name);
+ pcommand(p, "addcomment %s Nuked\n", parray[p1].name);
+ fd = parray[p1].socket;
+ process_disconnection(fd);
+ net_close_connection(fd);
+ return COM_OK;
+ } else {
+ pprintf(p, "You need a higher adminlevel to nuke %s!\n",
+ param[0].val.word);
+ }
+ }
+ return COM_OK;
+}
+
+/*
+ * summon
+ *
+ * Usage: summon player
+ *
+ * This command gives a beep and a message to the player indicating that you
+ * want to talk with him/her. The command is useful for waking someone up,
+ * for example a sleepy admin or an ignorant player.
+ */
+PUBLIC int
+com_summon(int p, param_list param)
+{
+ int p1;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if ((p1 = player_find_part_login(param[0].val.word)) < 0) {
+ pprintf(p, "%s isn't logged in.\n", param[0].val.word);
+ return COM_OK;
+ } else {
+ pprintf(p1, "\a\n");
+ pprintf_highlight(p1, "%s", parray[p].name);
+ pprintf_prompt(p1, " needs to talk with you. Use tell %s "
+ "<message> to reply.\a\n", parray[p].name);
+ pprintf(p, "Summoning sent to %s.\n", parray[p1].name);
+ return COM_OK;
+ }
+}
+
+/*
+ * addcomment
+ *
+ * Usage: addcomment user comment
+ *
+ * Places "comment" in the user's comments. If a user has comments, the
+ * number of comments is indicated to admins using the "finger" command.
+ * The comments themselves are displayed by the "showcomments" command.
+ */
+PUBLIC int
+com_addcomment(int p, param_list param)
+{
+ int p1, connected;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+ if (player_add_comment(p, p1, param[1].val.string)) {
+ pprintf(p, "Error adding comment!\n");
+ } else {
+ pprintf(p, "Comment added for %s.\n", parray[p1].name);
+ player_save(p1);
+ }
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+}
+
+/*
+ * showcomment
+ *
+ * Usage: showcomment user
+ *
+ * This command will display all of the comments added to the user's account.
+ */
+PUBLIC int
+com_showcomment(int p, param_list param)
+{
+ int p1, connected;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+ ASSERT(param[0].type == TYPE_WORD);
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+
+ player_show_comments(p, p1);
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+}
+
+/*
+ * admin
+ *
+ * Usage: admin
+ *
+ * This command toggles your admin symbol (*) on/off. This symbol appears
+ * in who listings.
+ */
+PUBLIC int
+com_admin(int p, param_list param)
+{
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ parray[p].i_admin = !(parray[p].i_admin);
+
+ if (parray[p].i_admin) {
+ pprintf(p, "Admin mode (*) is now shown\n");
+ } else {
+ pprintf(p, "Admin mode (*) is now not shown\n");
+ }
+
+ return COM_OK;
+}
+
+/*
+ * quota
+ *
+ * Usage: quota [n]
+ *
+ * The command sets the number of seconds (n) for the shout quota, which
+ * affects only those persons on the shout quota list. If no parameter
+ * (n) is given, the current setting is displayed.
+ */
+PUBLIC int
+com_quota(int p, param_list param)
+{
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (param[0].type == TYPE_NULL) {
+ pprintf(p, "The current shout quota is 2 shouts per %d "
+ "seconds.\n", quota_time);
+ return COM_OK;
+ }
+
+ quota_time = param[0].val.integer;
+
+ pprintf(p, "The shout quota is now 2 shouts per %d seconds.\n",
+ quota_time);
+ return COM_OK;
+}
+
+/*
+ * asetmaxplayer
+ *
+ * Usage: asetmaxplayer [n]
+ *
+ * The command sets the maximum number of players (n) who can connect.
+ */
+PUBLIC int
+com_asetmaxplayer(int p, param_list param)
+{
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (param[0].type != TYPE_NULL) {
+ pprintf(p, "Previously %d total conenctions allowed...\n",
+ max_connections);
+ max_connections = param[0].val.integer;
+
+ if ((max_connections > MAX_PLAYER) ||
+ (max_connections > getdtablesize() - 4)) {
+ max_connections = MIN(MAX_PLAYER, getdtablesize() - 4);
+ pprintf(p, "Value too high. System OS limits us to "
+ "%d.\n", max_connections);
+ pprintf(p, "For saftey's sake, it should not be "
+ "higher than %d.\n", max_connections - 2);
+ }
+ }
+
+ pprintf(p, "There are currently %d regular and %d admin connections "
+ "available,\n", max_connections - 10, 10);
+ pprintf(p, "with %d maximum logins before unregistered login "
+ "restrictions begin.\n", MAX(max_connections - 50, 200));
+
+ pprintf(p, "Total allowed connections: %d.\n", max_connections);
+ return COM_OK;
+}
diff --git a/FICS/adminproc.h b/FICS/adminproc.h
new file mode 100644
index 0000000..0d30b6d
--- /dev/null
+++ b/FICS/adminproc.h
@@ -0,0 +1,72 @@
+/* adminproc.h
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 23/12/09 Sorted the declarations
+ Markus Uhlin 24/01/01 Added argument lists
+*/
+
+#ifndef _ADMINPROC_H
+#define _ADMINPROC_H
+
+#include "command.h"
+
+extern int num_anews;
+
+extern int com_addcomment(int, param_list);
+extern int com_addplayer(int, param_list);
+extern int com_adjudicate(int, param_list);
+extern int com_admin(int, param_list);
+extern int com_anews(int, param_list);
+extern int com_announce(int, param_list);
+extern int com_annunreg(int, param_list);
+extern int com_asetadmin(int, param_list);
+extern int com_asetblitz(int, param_list);
+extern int com_asetemail(int, param_list);
+extern int com_asethandle(int, param_list);
+extern int com_asetlight(int, param_list);
+extern int com_asetmaxplayer(int, param_list);
+extern int com_asetpasswd(int, param_list);
+extern int com_asetrealname(int, param_list);
+extern int com_asetstd(int, param_list);
+extern int com_asetv(int, param_list);
+extern int com_asetwild(int, param_list);
+extern int com_canewsf(int, param_list);
+extern int com_canewsi(int, param_list);
+extern int com_checkGAME(int, param_list);
+extern int com_checkIP(int, param_list);
+extern int com_checkPLAYER(int, param_list);
+extern int com_checkSOCKET(int, param_list);
+extern int com_checkTIMESEAL(int, param_list);
+extern int com_cmuzzle(int, param_list);
+extern int com_cnewsf(int, param_list);
+extern int com_cnewsi(int, param_list);
+extern int com_muzzle(int, param_list);
+extern int com_nuke(int, param_list);
+extern int com_pose(int, param_list);
+extern int com_quota(int, param_list);
+extern int com_raisedead(int, param_list);
+extern int com_remplayer(int, param_list);
+extern int com_showcomment(int, param_list);
+extern int com_summon(int, param_list);
+extern int strcmpwild(char *, char *);
+
+#endif /* _ADMINPROC_H */
diff --git a/FICS/algcheck.c b/FICS/algcheck.c
new file mode 100644
index 0000000..15642fd
--- /dev/null
+++ b/FICS/algcheck.c
@@ -0,0 +1,567 @@
+/* algcheck.c
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 24/05/05 Revised
+ Markus Uhlin 25/04/05 alg_parse_move:
+ return ambiguous move on
+ out-of-bounds array read/write.
+*/
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include <err.h>
+
+#include "algcheck.h"
+#include "board.h"
+#include "maxxes-utils.h"
+#include "movecheck.h"
+#include "utils.h"
+
+#define ALG_UNKNOWN -1
+
+/*
+ * Well, lets see if I can list the possibilities:
+ *
+ * Piece moves:
+ * Ne4
+ * Nxe4
+ * Nce4
+ * Ncxe4
+ * R2f3
+ * R2xf3
+ *
+ * Special pawn moves:
+ * e4
+ * ed
+ * exd
+ * exd5
+ * ed5
+ *
+ * Drop moves (bughouse, board edit):
+ * P@f7 P*f7
+ * #f7 #Nf7
+ * (o-o, o-o-o)
+ * Castling is handled earlier, so don't worry about that. Of course
+ * any of these can have a + or ++ or = string on the end, just cut
+ * that off.
+ */
+
+/*
+ * f - file
+ * r - rank
+ * p - piece
+ * x - x
+ * @ - drop character (bughouse)
+ */
+PRIVATE char *alg_list[] = {
+ "fxfr", "pxfr", // These two get confused in case of bishop
+ "ffr", "pfr", // These two get confused in case of bishop
+ "pffr",
+ "pfxfr",
+ "prfr",
+ "prxfr",
+ "fr",
+ "ff",
+ "fxf",
+ "p@fr",
+ "#fr",
+ "#pfr",
+ NULL
+};
+
+PRIVATE int
+get_move_info(char *str, int *piece, int *ff, int *fr, int *tf, int *tr,
+ int *bishconfusion)
+{
+ char *s;
+ char c;
+ char tmp[1024] = { '\0' };
+ int i, j, len;
+ int lpiece, lff, lfr, ltf, ltr;
+ int matchVal = -1;
+
+ *bishconfusion = 0;
+ mstrlcpy(tmp, str, sizeof tmp);
+
+ if ((s = strchr(tmp, '+'))) // Cut off any check marks
+ *s = '\0';
+ if ((s = strchr(tmp, '='))) // Cut off any promotion marks
+ *s = '\0';
+
+ *piece = *ff = *fr = *tf = *tr = ALG_UNKNOWN;
+ len = strlen(tmp);
+
+ for (i = 0; alg_list[i]; i++) {
+ lpiece = lff = lfr = ltf = ltr = ALG_UNKNOWN;
+
+ if (strlen(alg_list[i]) != (size_t)len)
+ continue;
+
+ for (j = len - 1; j >= 0; j--) {
+ switch (alg_list[i][j]) {
+ case 'f':
+ if ((tmp[j] < 'a') || (tmp[j] > 'h'))
+ goto nomatch;
+ if (ltf == ALG_UNKNOWN)
+ ltf = tmp[j] - 'a';
+ else
+ lff = tmp[j] - 'a';
+ break;
+ case 'r':
+ if ((tmp[j] < '1') || (tmp[j] > '8'))
+ goto nomatch;
+ if (ltr == ALG_UNKNOWN)
+ ltr = tmp[j] - '1';
+ else
+ lfr = tmp[j] - '1';
+ break;
+ case 'p':
+ if (isupper(tmp[j]))
+ c = tolower(tmp[j]);
+ else
+ c = tmp[j];
+ if (c == 'k')
+ lpiece = KING;
+ else if (c == 'q')
+ lpiece = QUEEN;
+ else if (c == 'r')
+ lpiece = ROOK;
+ else if (c == 'b')
+ lpiece = BISHOP;
+ else if (c == 'n')
+ lpiece = KNIGHT;
+ else if (c == 'p')
+ lpiece = PAWN;
+ else
+ goto nomatch;
+ break;
+ case 'x':
+ if ((tmp[j] != 'x') && (tmp[j] != 'X'))
+ goto nomatch;
+ break;
+ case '@':
+ if (tmp[j] != '@' && tmp[j] != '*')
+ goto nomatch;
+ lff = lfr = ALG_DROP;
+ break;
+ case '#':
+ if (tmp[j] != '#')
+ goto nomatch;
+ lff = lfr = ALG_DROP;
+ break;
+ default:
+ fprintf(stderr, "Unknown character in "
+ "algebraic parsing\n");
+ break;
+ }
+ }
+
+ if (lpiece == ALG_UNKNOWN)
+ lpiece = PAWN;
+ if (lpiece == PAWN && (lfr == ALG_UNKNOWN)) { // ffr or ff
+ if (lff != ALG_UNKNOWN) {
+ if (lff == ltf)
+ goto nomatch;
+ if ((lff - ltf != 1) && (ltf - lff != 1))
+ goto nomatch;
+ }
+ }
+
+ *piece = lpiece; // We have a match
+ *tf = ltf;
+ *tr = ltr;
+ *ff = lff;
+ *fr = lfr;
+
+ if (matchVal != -1) {
+ /*
+ * We have two matches, it must be that Bxc4
+ * vs. bxc4 problem. Or it could be the Bc4 vs
+ * bc4 problem.
+ */
+ *bishconfusion = 1;
+ }
+
+ matchVal = i;
+
+ nomatch:;
+ } /* for */
+
+ if (matchVal != -1)
+ return MS_ALG;
+ else
+ return MS_NOTMOVE;
+}
+
+PUBLIC int
+alg_is_move(char *mstr)
+{
+ int piece, ff, fr, tf, tr, bc;
+
+ return get_move_info(mstr, &piece, &ff, &fr, &tf, &tr, &bc);
+}
+
+/*
+ * We already know it is algebraic - get the move squares.
+ */
+PUBLIC int
+alg_parse_move(char *mstr, game_state_t *gs, move_t *mt)
+{
+ int f, r, tmpr, posf, posr, posr2;
+ int piece, ff, fr, tf, tr, bc;
+
+ if (get_move_info(mstr, &piece, &ff, &fr, &tf, &tr, &bc) != MS_ALG) {
+ fprintf(stderr, "FICS: Shouldn't try to algebraically parse "
+ "non-algebraic move string.\n");
+ return MOVE_ILLEGAL;
+ }
+
+ // Resolve ambiguities in to-ness
+ if (tf == ALG_UNKNOWN)
+ return MOVE_AMBIGUOUS; // Must always know to file
+ if (tr == ALG_UNKNOWN) {
+ posr = posr2 = ALG_UNKNOWN;
+
+ if (piece != PAWN)
+ return MOVE_AMBIGUOUS;
+ if (ff == ALG_UNKNOWN)
+ return MOVE_AMBIGUOUS;
+
+ /*
+ * Need to find pawn on ff that can take to tf and
+ * fill in ranks.
+ */
+ for (InitPieceLoop(gs->board, &f, &r, gs->onMove);
+ NextPieceLoop(gs->board, &f, &r, gs->onMove);) {
+ if ((ff != ALG_UNKNOWN) && (ff != f))
+ continue;
+ if (r < 0 || r >= 8) {
+ warnx("%s: out-of-bounds array read/write: "
+ "r=%d", __func__, r);
+ return MOVE_AMBIGUOUS;
+ }
+ if (piecetype(gs->board[f][r]) != piece)
+ continue;
+ if (gs->onMove == WHITE) {
+ tmpr = r + 1;
+ } else {
+ tmpr = r - 1;
+ }
+ if (tmpr < 0 || tmpr >= 8) {
+ warnx("%s: out-of-bounds array read/write: "
+ "tmpr=%d", __func__, tmpr);
+ return MOVE_AMBIGUOUS;
+ }
+
+ if (gs->board[tf][tmpr] == NOPIECE) {
+ if ((gs->ep_possible[((gs->onMove == WHITE) ?
+ 0 : 1)][ff]) != (tf - ff))
+ continue;
+ } else {
+ if (iscolor(gs->board[tf][tmpr], gs->onMove))
+ continue;
+ }
+
+ if (legal_andcheck_move(gs, f, r, tf, tmpr)) {
+ if ((posr != ALG_UNKNOWN) &&
+ (posr2 != ALG_UNKNOWN))
+ return MOVE_AMBIGUOUS;
+ posr = tmpr;
+ posr2 = r;
+ }
+ }
+
+ tr = posr;
+ fr = posr2;
+ } else if (bc) { // Could be bxc4 or Bxc4, tr is known.
+ ff = ALG_UNKNOWN;
+ fr = ALG_UNKNOWN;
+
+ for (InitPieceLoop(gs->board, &f, &r, gs->onMove);
+ NextPieceLoop(gs->board, &f, &r, gs->onMove);) {
+ if ((piecetype(gs->board[f][r]) != PAWN) &&
+ (piecetype(gs->board[f][r]) != BISHOP))
+ continue;
+ if (legal_andcheck_move(gs, f, r, tf, tr)) {
+ if ((piecetype(gs->board[f][r]) == PAWN) &&
+ (f != 1))
+ continue;
+ if ((ff != ALG_UNKNOWN) && (fr != ALG_UNKNOWN))
+ return (MOVE_AMBIGUOUS);
+ ff = f;
+ fr = r;
+ }
+ }
+ } else { // The from position is unknown
+ posf = ALG_UNKNOWN;
+ posr = ALG_UNKNOWN;
+
+ if ((ff == ALG_UNKNOWN) || (fr == ALG_UNKNOWN)) {
+ /*
+ * Need to find a piece that can go to tf, tr.
+ */
+ for (InitPieceLoop(gs->board, &f, &r, gs->onMove);
+ NextPieceLoop(gs->board, &f, &r, gs->onMove);) {
+ if ((ff != ALG_UNKNOWN) && (ff != f))
+ continue;
+ if ((fr != ALG_UNKNOWN) && (fr != r))
+ continue;
+ if (piecetype(gs->board[f][r]) != piece)
+ continue;
+ if (legal_andcheck_move(gs, f, r, tf, tr)) {
+ if ((posf != ALG_UNKNOWN) &&
+ (posr != ALG_UNKNOWN))
+ return MOVE_AMBIGUOUS;
+ posf = f;
+ posr = r;
+ }
+ }
+ } else if (ff == ALG_DROP) {
+ if (legal_andcheck_move(gs, ALG_DROP, piece, tf, tr)) {
+ posf = ALG_DROP;
+ posr = piece;
+ }
+ }
+
+ ff = posf;
+ fr = posr;
+ }
+
+ if ((tf == ALG_UNKNOWN) || (tr == ALG_UNKNOWN) ||
+ (ff == ALG_UNKNOWN) || (fr == ALG_UNKNOWN))
+ return MOVE_ILLEGAL;
+
+ mt->fromFile = ff;
+ mt->fromRank = fr;
+ mt->toFile = tf;
+ mt->toRank = tr;
+
+ return MOVE_OK;
+}
+
+/*
+ * A assumes the move has yet to be made on the board. (This is the
+ * old stupid function, we are testing one from soso...)
+ */
+#if 0
+PUBLIC char *
+alg_unparse(game_state_t *gs, move_t *mt)
+{
+ static char mStr[20] = { '\0' };
+
+ if ((piecetype(gs->board[mt->fromFile][mt->fromRank]) == KING) &&
+ ((mt->fromFile == 4) && (mt->toFile == 6)))
+ return "o-o";
+ if ((piecetype(gs->board[mt->fromFile][mt->fromRank]) == KING) &&
+ ((mt->fromFile == 4) && (mt->toFile == 2)))
+ return "o-o-o";
+
+ msnprintf(mStr, sizeof mStr, "%c%d%c%d",
+ mt->fromFile + 'a',
+ mt->fromRank + 1,
+ mt->toFile + 'a',
+ mt->toRank + 1);
+ return mStr;
+}
+#endif
+
+/*
+ * Soso: Rewrote the alg_unparse() function.
+ *
+ * Algebraic deparser - sets the 'mStr' variable with move description
+ * in short notation. Used in last move report and in 'moves' command.
+ */
+PUBLIC char *
+alg_unparse(game_state_t *gs, move_t *mt)
+{
+ char tmp[20] = { '\0' };
+ game_state_t fakeMove;
+ int ambig, r_ambig, f_ambig;
+ int piece, f, r;
+ static char mStr[20] = { '\0' };
+
+ if (mt->fromFile == ALG_DROP) {
+ piece = mt->fromRank;
+ } else {
+ piece = piecetype(gs->board[mt->fromFile][mt->fromRank]);
+ }
+
+ if ((piece == KING) && ((mt->fromFile == 4) && (mt->toFile == 6))) {
+ mstrlcpy(mStr, "O-O", sizeof mStr);
+ goto check;
+ }
+ if ((piece == KING) && ((mt->fromFile == 4) && (mt->toFile == 2))) {
+ mstrlcpy(mStr, "O-O-O", sizeof mStr);
+ goto check;
+ }
+
+ mstrlcpy(mStr, "", sizeof mStr);
+
+ switch (piece) {
+ case PAWN:
+ if (mt->fromFile == ALG_DROP) {
+ mstrlcpy(mStr, "P", sizeof mStr);
+ } else if (mt->fromFile != mt->toFile) {
+ msnprintf(tmp, sizeof tmp, "%c", mt->fromFile + 'a');
+ mstrlcpy(mStr, tmp, sizeof mStr);
+ }
+ break;
+ case KNIGHT:
+ mstrlcpy(mStr, "N", sizeof mStr);
+ break;
+ case BISHOP:
+ mstrlcpy(mStr, "B", sizeof mStr);
+ break;
+ case ROOK:
+ mstrlcpy(mStr, "R", sizeof mStr);
+ break;
+ case QUEEN:
+ mstrlcpy(mStr, "Q", sizeof mStr);
+ break;
+ case KING:
+ mstrlcpy(mStr, "K", sizeof mStr);
+ break;
+ default:
+ mstrlcpy(mStr, "", sizeof mStr);
+ break;
+ }
+
+ if (mt->fromFile == ALG_DROP) {
+ mstrlcat(mStr, DROP_STR, sizeof mStr);
+ } else {
+ /*
+ * Checks for ambiguity in short notation (Ncb3, R8e8
+ * or so).
+ */
+ if (piece != PAWN) {
+ ambig = r_ambig = f_ambig = 0;
+
+ for (r = 0; r < 8; r++) {
+ for (f = 0; f < 8; f++) {
+ if ((gs->board[f][r] != NOPIECE) &&
+ iscolor(gs->board[f][r], gs->onMove) &&
+ (piecetype(gs->board[f][r]) == piece) &&
+ ((f != mt->fromFile) ||
+ (r != mt->fromRank))) {
+ if (legal_move(gs, f, r,
+ mt->toFile, mt->toRank)) {
+ fakeMove = *gs;
+ fakeMove.board[f][r] =
+ NOPIECE;
+ fakeMove.onMove =
+ CToggle(fakeMove.onMove);
+ gs->onMove =
+ CToggle(gs->onMove);
+
+ /*
+ * New
+ * bracketing
+ * below to
+ * try to fix
+ * 'ambiguous
+ * move' bug.
+ */
+ if ((in_check(gs)) ||
+ !in_check(&fakeMove))
+ ambig++;
+
+ if (f == mt->fromFile) {
+ f_ambig++;
+ ambig++;
+ }
+ if (r == mt->fromRank) {
+ r_ambig++;
+ ambig++;
+ }
+
+ gs->onMove =
+ CToggle(gs->onMove);
+ }
+ }
+ } /* for */
+ } /* for */
+
+ if (ambig > 0) {
+ /*
+ * Ambiguity in short notation, need
+ * to add file, rank or _both_ in
+ * notation.
+ */
+
+ if (f_ambig == 0) {
+ msnprintf(tmp, sizeof tmp, "%c",
+ mt->fromFile + 'a');
+ mstrlcat(mStr, tmp, sizeof mStr);
+ } else if (r_ambig == 0) {
+ msnprintf(tmp, sizeof tmp, "%d",
+ mt->fromRank + 1);
+ mstrlcat(mStr, tmp, sizeof mStr);
+ } else {
+ msnprintf(tmp, sizeof tmp, "%c%d",
+ mt->fromFile + 'a',
+ mt->fromRank + 1);
+ mstrlcat(mStr, tmp, sizeof mStr);
+ }
+ }
+ }
+
+ if ((gs->board[mt->toFile][mt->toRank] != NOPIECE) ||
+ ((piece == PAWN) && (mt->fromFile != mt->toFile))) {
+ mstrlcat(mStr, "x", sizeof mStr);
+ }
+ }
+
+ msnprintf(tmp, sizeof tmp, "%c%d", mt->toFile + 'a', mt->toRank + 1);
+ mstrlcat(mStr, tmp, sizeof mStr);
+
+ if ((piece == PAWN) && (mt->piecePromotionTo != NOPIECE)) {
+ mstrlcat(mStr, "=", sizeof mStr); // = before promoting piece
+
+ switch (piecetype(mt->piecePromotionTo)) {
+ case KNIGHT:
+ mstrlcat(mStr, "N", sizeof mStr);
+ break;
+ case BISHOP:
+ mstrlcat(mStr, "B", sizeof mStr);
+ break;
+ case ROOK:
+ mstrlcat(mStr, "R", sizeof mStr);
+ break;
+ case QUEEN:
+ mstrlcat(mStr, "Q", sizeof mStr);
+ break;
+ default:
+ break;
+ }
+ }
+
+ check:;
+
+ fakeMove = *gs;
+ execute_move(&fakeMove, mt, 0);
+ fakeMove.onMove = CToggle(fakeMove.onMove);
+
+ if (in_check(&fakeMove))
+ mstrlcat(mStr, "+", sizeof mStr);
+ return mStr;
+}
diff --git a/FICS/algcheck.h b/FICS/algcheck.h
new file mode 100644
index 0000000..5978c89
--- /dev/null
+++ b/FICS/algcheck.h
@@ -0,0 +1,40 @@
+/* algcheck.h
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 24/05/05 Revised
+*/
+
+#ifndef _ALGCHECK_H
+#define _ALGCHECK_H
+
+#ifndef _BOARD_H
+#include "board.h"
+#endif
+
+#define DROP_CHAR '@' // used by algcheck.c and movecheck.c
+#define DROP_STR "@"
+
+extern char *alg_unparse(game_state_t *, move_t *);
+extern int alg_is_move(char *);
+extern int alg_parse_move(char *, game_state_t *, move_t *);
+
+#endif /* _ALGCHECK_H */
diff --git a/FICS/assert_error.c b/FICS/assert_error.c
new file mode 100644
index 0000000..2d26491
--- /dev/null
+++ b/FICS/assert_error.c
@@ -0,0 +1,12 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "assert_error.h"
+
+__dead void
+_assert_error(const char *file, const long int line)
+{
+ (void) fprintf(stderr, "Assertion failed: file %s, line %ld.\n",
+ file, line);
+ abort();
+}
diff --git a/FICS/assert_error.h b/FICS/assert_error.h
new file mode 100644
index 0000000..2de6438
--- /dev/null
+++ b/FICS/assert_error.h
@@ -0,0 +1,10 @@
+#ifndef ASSERT_ERROR_H
+#define ASSERT_ERROR_H
+
+#ifndef __dead
+#define __dead __attribute__((__noreturn__))
+#endif
+
+__dead void _assert_error(const char *, const long int);
+
+#endif
diff --git a/FICS/board.c b/FICS/board.c
new file mode 100644
index 0000000..2423024
--- /dev/null
+++ b/FICS/board.c
@@ -0,0 +1,1385 @@
+/* board.c
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 23/12/17 Fixed compiler warnings
+ Markus Uhlin 23/12/23 Reformatted all functions
+ Markus Uhlin 24/04/13 Added usage of the functions
+ from 'maxxes-utils.h'.
+ Markus Uhlin 24/06/01 Added and made use of brand().
+ Markus Uhlin 25/04/06 Fixed Clang Tidy warnings.
+ Markus Uhlin 25/09/02 wild_update: fixed file created
+ without restricting permissions.
+*/
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include <err.h>
+#include <limits.h>
+
+#include "board.h"
+#include "ficsmain.h"
+#include "gamedb.h"
+#include "maxxes-utils.h"
+#include "playerdb.h"
+#include "utils.h"
+
+#define WHITE_SQUARE 1
+#define BLACK_SQUARE 0
+#define ANY_SQUARE -1
+
+#define SquareColor(f, r) ((f ^ r) & 1)
+
+#define IsMachineStyle(n) (((1 << (n)) & mach_type) != 0)
+
+PUBLIC char *wpstring[] = {" ", "P", "N", "B", "R", "Q", "K"};
+PUBLIC char *bpstring[] = {" ", "p", "n", "b", "r", "q", "k"};
+
+PUBLIC int pieceValues[7] = {0, 1, 3, 3, 5, 9, 0};
+
+PRIVATE int (*styleFuncs[MAX_STYLES])(game_state_t *, move_t *) = {
+ style1,
+ style2,
+ style3,
+ style4,
+ style5,
+ style6,
+ style7,
+ style8,
+ style9,
+ style10,
+ style11,
+ style12,
+ style13
+};
+
+PRIVATE char bstring[MAX_BOARD_STRING_LEGTH];
+
+PRIVATE const int mach_type = ((1 << 7) |
+ (1 << 8) |
+ (1 << 9) |
+ (1 << 10) |
+ (1 << 11));
+
+/*
+ * Globals used for each board
+ */
+PRIVATE char *wName, *bName;
+PRIVATE int wTime, bTime;
+PRIVATE int orient;
+PRIVATE int forPlayer;
+PRIVATE int myTurn;
+
+PRIVATE int
+brand(void)
+{
+#if RAND_MAX < 32767 || RAND_MAX > INT_MAX
+#error RAND_MAX unacceptable
+#endif
+ return ((int) arc4random_uniform(RAND_MAX));
+}
+
+PUBLIC int
+board_init(game_state_t *b, char *category, char *board)
+{
+ int retval;
+ int wval;
+
+ if (category == NULL || board == NULL ||
+ !category[0] || !board[0]) {
+ retval = board_read_file("standard", "standard", b);
+ } else {
+ if (!strcmp(category, "wild")) {
+ if (sscanf(board, "%d", &wval) == 1 &&
+ wval >= 1 &&
+ wval <= 4)
+ wild_update(wval);
+ }
+
+ retval = board_read_file(category, board, b);
+ }
+
+ b->gameNum = -1;
+ return retval;
+}
+
+PUBLIC void
+board_calc_strength(game_state_t *b, int *ws, int *bs)
+{
+ int *p;
+ int r, f;
+
+ *ws = *bs = 0;
+
+ for (f = 0; f < 8; f++) {
+ for (r = 0; r < 8; r++) {
+ if (colorval(b->board[r][f]) == WHITE)
+ p = ws;
+ else
+ p = bs;
+ *p += pieceValues[piecetype(b->board[r][f])];
+ }
+ }
+
+ for (r = PAWN; r <= QUEEN; r++) {
+ *ws += (b->holding[0][r - 1] * pieceValues[r]);
+ *bs += (b->holding[1][r - 1] * pieceValues[r]);
+ }
+}
+
+PRIVATE char *
+holding_str(int *holding)
+{
+ int p, i, j;
+ static char tmp[30];
+
+ i = 0;
+
+ for (p = PAWN; p <= QUEEN; p++) {
+ for (j = 0; j < holding[p - 1]; j++)
+ tmp[i++] = wpstring[p][0];
+ }
+
+ tmp[i] = '\0';
+ return tmp;
+}
+
+PRIVATE char *
+append_holding_machine(char *buf, const size_t bufsize, int g, int c, int p)
+{
+ char tmp[50];
+ game_state_t *gs = &garray[g].game_state;
+
+ msnprintf(tmp, sizeof tmp, "<b1> game %d white [%s] black [", (g + 1),
+ holding_str(gs->holding[0]));
+ mstrlcat(tmp, holding_str(gs->holding[1]), sizeof tmp);
+ mstrlcat(buf, tmp, bufsize);
+
+ if (p) {
+ msnprintf(tmp, sizeof tmp, "] <- %c%s\n", "WB"[c], wpstring[p]);
+ mstrlcat(buf, tmp, bufsize);
+ } else
+ mstrlcat(buf, "]\n", bufsize);
+ return buf;
+}
+
+PRIVATE char *
+append_holding_display(char *buf, const size_t bufsize, game_state_t *gs,
+ int white)
+{
+ if (white)
+ mstrlcat(buf, "White holding: [", bufsize);
+ else
+ mstrlcat(buf, "Black holding: [", bufsize);
+ mstrlcat(buf, holding_str(gs->holding[white ? 0 : 1]), bufsize);
+ mstrlcat(buf, "]\n", bufsize);
+ return buf;
+}
+
+PUBLIC void
+update_holding(int g, int pieceCaptured)
+{
+ char tmp1[80];
+ char tmp2[80];
+ game_state_t *gs = &garray[g].game_state;
+ int c = colorval(pieceCaptured);
+ int p = piecetype(pieceCaptured);
+ int pp, pl;
+
+ if (c == WHITE) {
+ c = 0;
+ pp = garray[g].white;
+ } else {
+ c = 1;
+ pp = garray[g].black;
+ }
+
+ gs->holding[c][p - 1]++;
+
+ tmp1[0] = '\0';
+ append_holding_machine(tmp1, sizeof tmp1, g, c, p);
+
+ msnprintf(tmp2, sizeof tmp2, "Game %d %s received: %s -> [%s]\n",
+ (g + 1),
+ parray[pp].name,
+ wpstring[p],
+ holding_str(gs->holding[c]));
+
+ for (pl = 0; pl < p_num; pl++) {
+ if (parray[pl].status == PLAYER_EMPTY)
+ continue;
+
+ if (player_is_observe(pl, g) || parray[pl].game == g) {
+ pprintf_prompt(pl, "%s",
+ (IsMachineStyle(parray[pl].style) ? &tmp1[0] :
+ &tmp2[0]));
+ }
+ }
+}
+
+PUBLIC char *
+board_to_string(char *wn, char *bn, int wt, int bt, game_state_t *b, move_t *ml,
+ int style, int orientation, int relation, int p)
+{
+ int bh = (b->gameNum >= 0 && garray[b->gameNum].link >= 0);
+
+ wName = wn;
+ bName = bn;
+ wTime = wt;
+ bTime = bt;
+ orient = orientation;
+ myTurn = relation;
+ forPlayer = p;
+
+ if (style < 0 || style >= MAX_STYLES)
+ return NULL;
+
+ if (style != 11) { // game header
+ msnprintf(bstring, sizeof bstring, "Game %d (%s vs. %s)\n\n",
+ (b->gameNum + 1),
+ garray[b->gameNum].white_name,
+ garray[b->gameNum].black_name);
+ } else
+ bstring[0] = '\0';
+
+ if (bh && !IsMachineStyle(style)) {
+ append_holding_display(bstring, sizeof bstring, b,
+ (orientation == BLACK));
+ }
+ if (styleFuncs[style](b, ml))
+ return NULL;
+ if (bh) {
+ if (IsMachineStyle(style)) {
+ append_holding_machine(bstring, sizeof bstring,
+ b->gameNum, 0, 0);
+ } else {
+ append_holding_display(bstring, sizeof bstring, b,
+ (orientation == WHITE));
+ }
+ }
+
+ return bstring;
+}
+
+PUBLIC char *
+move_and_time(move_t *m)
+{
+ static char tmp[20];
+
+ msnprintf(tmp, sizeof tmp, "%-7s (%s)", m->algString,
+ tenth_str(m->tookTime, 0));
+ return &tmp[0];
+}
+
+PRIVATE int
+genstyle(game_state_t *b, move_t *ml, char *wp[], char *bp[],
+ char *wsqr, char *bsqr, char *top, char *mid, char *start, char *end,
+ char *label, char *blabel)
+{
+ char tmp[80];
+ int f, r, count;
+ int first, last, inc;
+ int ws, bs;
+
+ board_calc_strength(b, &ws, &bs);
+
+ if (orient == WHITE) {
+ first = 7;
+ last = 0;
+ inc = -1;
+ } else {
+ first = 0;
+ last = 7;
+ inc = 1;
+ }
+
+ mstrlcat(bstring, top, sizeof bstring);
+
+ for (f = first, count = 7; f != last + inc; f += inc, count--) {
+ msnprintf(tmp, sizeof tmp, " %d %s", f + 1, start);
+ mstrlcat(bstring, tmp, sizeof bstring);
+
+ for (r = last; r != first - inc; r = r - inc) {
+ if (square_color(r, f) == WHITE)
+ mstrlcat(bstring, wsqr, sizeof bstring);
+ else
+ mstrlcat(bstring, bsqr, sizeof bstring);
+
+ if (piecetype(b->board[r][f]) == NOPIECE) {
+ if (square_color(r, f) == WHITE) {
+ mstrlcat(bstring, bp[0],
+ sizeof bstring);
+ } else {
+ mstrlcat(bstring, wp[0],
+ sizeof bstring);
+ }
+ } else {
+ if (colorval(b->board[r][f]) == WHITE) {
+ mstrlcat(bstring,
+ wp[piecetype(b->board[r][f])],
+ sizeof bstring);
+ } else {
+ mstrlcat(bstring,
+ bp[piecetype(b->board[r][f])],
+ sizeof bstring);
+ }
+ }
+ }
+
+ msnprintf(tmp, sizeof tmp, "%s", end);
+ mstrlcat(bstring, tmp, sizeof bstring);
+
+ switch (count) {
+ case 7:
+ msnprintf(tmp, sizeof tmp, " Move # : %d (%s)",
+ b->moveNum,
+ CString(b->onMove));
+ mstrlcat(bstring, tmp, sizeof bstring);
+ break;
+ case 6:
+ if (garray[b->gameNum].numHalfMoves > 0) {
+ // loon: think this fixes the crashing ascii
+ // board on takeback bug
+ msnprintf(tmp, sizeof tmp, " %s Moves : "
+ "'%s'",
+ CString(CToggle(b->onMove)), move_and_time
+ (&ml[garray[b->gameNum].numHalfMoves - 1]));
+ mstrlcat(bstring, tmp, sizeof bstring);
+ }
+ break;
+ case 5:
+ break;
+ case 4:
+ msnprintf(tmp, sizeof tmp, " Black Clock : %s",
+ tenth_str((bTime > 0 ? bTime : 0), 1));
+ mstrlcat(bstring, tmp, sizeof bstring);
+ break;
+ case 3:
+ msnprintf(tmp, sizeof tmp, " White Clock : %s",
+ tenth_str((wTime > 0 ? wTime : 0), 1));
+ mstrlcat(bstring, tmp, sizeof bstring);
+ break;
+ case 2:
+ msnprintf(tmp, sizeof tmp, " Black Strength : %d",
+ bs);
+ mstrlcat(bstring, tmp, sizeof bstring);
+ break;
+ case 1:
+ msnprintf(tmp, sizeof tmp, " White Strength : %d",
+ ws);
+ mstrlcat(bstring, tmp, sizeof bstring);
+ break;
+ case 0:
+ break;
+ } // switch
+
+ mstrlcat(bstring, "\n", sizeof bstring);
+
+ if (count != 0)
+ mstrlcat(bstring, mid, sizeof bstring);
+ else
+ mstrlcat(bstring, top, sizeof bstring);
+ } // for
+
+ if (orient == WHITE)
+ mstrlcat(bstring, label, sizeof bstring);
+ else
+ mstrlcat(bstring, blabel, sizeof bstring);
+ return 0;
+}
+
+/*
+ * Experimental ANSI board for colour representation
+ */
+PUBLIC int
+style13(game_state_t *b, move_t *ml)
+{
+ static char *wp[] = {
+ " ",
+ "\033[37m\033[1m P ",
+ "\033[37m\033[1m N ",
+ "\033[37m\033[1m B ",
+ "\033[37m\033[1m R ",
+ "\033[37m\033[1m Q ",
+ "\033[37m\033[1m K "
+ };
+ static char *bp[] = {
+ " ",
+ "\033[21m\033[37m P ",
+ "\033[21m\033[37m N ",
+ "\033[21m\033[37m B ",
+ "\033[21m\033[37m R ",
+ "\033[21m\033[37m Q ",
+ "\033[21m\033[37m K "
+ };
+ static char *wsqr = "\033[40m";
+ static char *bsqr = "\033[45m";
+ static char *top = "\t+------------------------+\n";
+ static char *mid = "";
+ static char *start = "|";
+ static char *end = "\033[0m|";
+ static char *label = "\t a b c d e f g h\n";
+ static char *blabel = "\t h g f e d c b a\n";
+
+ return genstyle(b, ml, wp, bp, wsqr, bsqr, top, mid, start, end, label,
+ blabel);
+}
+
+/*
+ * Standard ICS
+ */
+PUBLIC int
+style1(game_state_t *b, move_t *ml)
+{
+ static char *wp[] = {
+ " |",
+ " P |",
+ " N |",
+ " B |",
+ " R |",
+ " Q |",
+ " K |"
+ };
+ static char *bp[] = {
+ " |",
+ " *P|",
+ " *N|",
+ " *B|",
+ " *R|",
+ " *Q|",
+ " *K|"
+ };
+ static char *wsqr = "";
+ static char *bsqr = "";
+ static char *top = "\t---------------------------------\n";
+ static char *mid = "\t|---+---+---+---+---+---+---+---|\n";
+ static char *start = "|";
+ static char *end = "";
+ static char *label = "\t a b c d e f g h\n";
+ static char *blabel = "\t h g f e d c b a\n";
+
+ return genstyle(b, ml, wp, bp, wsqr, bsqr, top, mid, start, end, label,
+ blabel);
+}
+
+/*
+ * USA-Today Sports Center-style board
+ */
+PUBLIC int
+style2(game_state_t *b, move_t *ml)
+{
+ static char *wp[] = {
+ "+ ",
+ "P ",
+ "N ",
+ "B ",
+ "R ",
+ "Q ",
+ "K "
+ };
+ static char *bp[] = {
+ "- ",
+ "p' ",
+ "n' ",
+ "b' ",
+ "r' ",
+ "q' ",
+ "k' "
+ };
+ static char *wsqr = "";
+ static char *bsqr = "";
+ static char *top = "";
+ static char *mid = "";
+ static char *start = "";
+ static char *end = "";
+ static char *label = "\ta b c d e f g h\n";
+ static char *blabel = "\th g f e d c b a\n";
+
+ return genstyle(b, ml, wp, bp, wsqr, bsqr, top, mid, start, end, label,
+ blabel);
+}
+
+/*
+ * Experimental vt-100 ANSI board for dark backgrounds
+ */
+PUBLIC int
+style3(game_state_t *b, move_t *ml)
+{
+ static char *wp[] = {
+ " ",
+ " P ",
+ " N ",
+ " B ",
+ " R ",
+ " Q ",
+ " K "
+ };
+ static char *bp[] = {
+ " ",
+ " *P",
+ " *N",
+ " *B",
+ " *R",
+ " *Q",
+ " *K"
+ };
+ static char *wsqr = "\033[0m";
+ static char *bsqr = "\033[7m";
+ static char *top = "\t+------------------------+\n";
+ static char *mid = "";
+ static char *start = "|";
+ static char *end = "\033[0m|";
+ static char *label = "\t a b c d e f g h\n";
+ static char *blabel = "\t h g f e d c b a\n";
+
+ return genstyle(b, ml, wp, bp, wsqr, bsqr, top, mid, start, end, label,
+ blabel);
+}
+
+/*
+ * Experimental vt-100 ANSI board for light backgrounds
+ */
+PUBLIC int
+style4(game_state_t *b, move_t *ml)
+{
+ static char *wp[] = {
+ " ",
+ " P ",
+ " N ",
+ " B ",
+ " R ",
+ " Q ",
+ " K "
+ };
+ static char *bp[] = {
+ " ",
+ " *P",
+ " *N",
+ " *B",
+ " *R",
+ " *Q",
+ " *K"
+ };
+ static char *wsqr = "\033[7m";
+ static char *bsqr = "\033[0m";
+ static char *top = "\t+------------------------+\n";
+ static char *mid = "";
+ static char *start = "|";
+ static char *end = "\033[0m|";
+ static char *label = "\t a b c d e f g h\n";
+ static char *blabel = "\t h g f e d c b a\n";
+
+ return genstyle(b, ml, wp, bp, wsqr, bsqr, top, mid, start, end, label,
+ blabel);
+}
+
+/*
+ * Style suggested by ajpierce@med.unc.edu
+ */
+PUBLIC int style5(game_state_t *b, move_t *ml)
+{
+ static char *wp[] = {
+ " ",
+ " o ",
+ " :N:",
+ " <B>",
+ " |R|",
+ " {Q}",
+ " =K="
+ };
+ static char *bp[] = {
+ " ",
+ " p ",
+ " :n:",
+ " <b>",
+ " |r|",
+ " {q}",
+ " =k="
+ };
+ static char *wsqr = "";
+ static char *bsqr = "";
+ static char *top = " . . . . . . . . .\n";
+ static char *mid = " . . . . . . . . .\n";
+ static char *start = "";
+ static char *end = "";
+ static char *label = "\t a b c d e f g h\n";
+ static char *blabel = "\t h g f e d c b a\n";
+
+ return genstyle(b, ml, wp, bp, wsqr, bsqr, top, mid, start, end, label,
+ blabel);
+}
+
+/*
+ * Email Board suggested by Thomas Fought (tlf@rsch.oclc.org)
+ */
+PUBLIC int
+style6(game_state_t *b, move_t *ml)
+{
+ static char *wp[] = {
+ " |",
+ " wp |",
+ " WN |",
+ " WB |",
+ " WR |",
+ " WQ |",
+ " WK |"
+ };
+ static char *bp[] = {
+ " |",
+ " bp |",
+ " BN |",
+ " BB |",
+ " BR |",
+ " BQ |",
+ " BK |"};
+ static char *wsqr = "";
+ static char *bsqr = "";
+ static char *top = "\t-----------------------------------------\n";
+ static char *mid = "\t-----------------------------------------\n";
+ static char *start = "|";
+ static char *end = "";
+ static char *label = "\t A B C D E F G H\n";
+ static char *blabel = "\t H G F E D C B A\n";
+
+ return genstyle(b, ml, wp, bp, wsqr, bsqr, top, mid, start, end, label,
+ blabel);
+}
+
+/*
+ * Miniature board
+ */
+PUBLIC int
+style7(game_state_t *b, move_t *ml)
+{
+ static char *wp[] = {" ", " P", " N", " B", " R", " Q", " K"};
+ static char *bp[] = {" -", " p", " n", " b", " r", " q", " k"};
+ static char *wsqr = "";
+ static char *bsqr = "";
+ static char *top = "\t:::::::::::::::::::::\n";
+ static char *mid = "";
+ static char *start = "..";
+ static char *end = " ..";
+ static char *label = "\t a b c d e f g h\n";
+ static char *blabel = "\t h g f e d c b a\n";
+
+ return genstyle(b, ml, wp, bp, wsqr, bsqr, top, mid, start, end, label,
+ blabel);
+}
+
+/*
+ * ICS interface maker board -- raw data dump.
+ */
+PUBLIC int
+style8(game_state_t *b, move_t *ml)
+{
+ char tmp[80];
+ int f, r;
+ int ws, bs;
+
+ board_calc_strength(b, &ws, &bs);
+
+ msnprintf(tmp, sizeof tmp, "#@#%03d%-16s%s%-16s%s",
+ (b->gameNum + 1),
+
+ garray[b->gameNum].white_name,
+ (orient == WHITE ? "*" : ":"),
+
+ garray[b->gameNum].black_name,
+ (orient == WHITE ? ":" : "*"));
+
+ mstrlcat(bstring, tmp, sizeof bstring);
+
+ for (r = 0; r < 8; r++) {
+ for (f = 0; f < 8; f++) {
+ if (b->board[f][r] == NOPIECE) {
+ mstrlcat(bstring, " ", sizeof bstring);
+ } else {
+ if (colorval(b->board[f][r]) == WHITE) {
+ mstrlcat(bstring, wpstring
+ [piecetype(b->board[f][r])],
+ sizeof bstring);
+ } else {
+ mstrlcat(bstring, bpstring
+ [piecetype(b->board[f][r])],
+ sizeof bstring);
+ }
+ }
+ }
+ }
+
+ msnprintf(tmp, sizeof tmp, "%03d%s%02d%02d%05d%05d%-7s(%s)@#@\n",
+ (garray[b->gameNum].numHalfMoves / 2 + 1),
+ (b->onMove == WHITE ? "W" : "B"),
+ ws,
+ bs,
+ ((wTime + 5) / 10),
+ ((bTime + 5) / 10),
+
+ (garray[b->gameNum].numHalfMoves
+ ? ml[garray[b->gameNum].numHalfMoves - 1].algString
+ : "none"),
+
+ (garray[b->gameNum].numHalfMoves
+ ? tenth_str(ml[garray[b->gameNum].numHalfMoves - 1].tookTime, 0)
+ : "0:00"));
+
+ mstrlcat(bstring, tmp, sizeof bstring);
+ return 0;
+}
+
+/*
+ * Last 2 moves only (previous non-verbose mode).
+ */
+PUBLIC int
+style9(game_state_t *b, move_t *ml)
+{
+ char tmp[80];
+ int i, count;
+ int startmove;
+
+ msnprintf(tmp, sizeof tmp, "\nMove %-23s%s\n",
+ garray[b->gameNum].white_name,
+ garray[b->gameNum].black_name);
+ mstrlcat(bstring, tmp, sizeof bstring);
+
+ msnprintf(tmp, sizeof tmp, "---- -------------- "
+ "--------------\n");
+ mstrlcat(bstring, tmp, sizeof bstring);
+
+ startmove = ((garray[b->gameNum].numHalfMoves - 3) / 2) * 2;
+
+ if (startmove < 0)
+ startmove = 0;
+
+ i = startmove;
+ count = 0;
+
+ while (i < garray[b->gameNum].numHalfMoves && count < 4) {
+ if (!(i & 0x01)) {
+ msnprintf(tmp, sizeof tmp, " %2d ", (i / 2 + 1));
+ mstrlcat(bstring, tmp, sizeof bstring);
+ }
+
+ msnprintf(tmp, sizeof tmp, "%-23s", move_and_time(&ml[i]));
+ mstrlcat(bstring, tmp, sizeof bstring);
+
+ if (i & 0x01)
+ mstrlcat(bstring, "\n", sizeof bstring);
+ i++;
+ count++;
+ }
+
+ if (i & 0x01)
+ mstrlcat(bstring, "\n", sizeof bstring);
+ return 0;
+}
+
+/*
+ * Sleator's 'new and improved' raw dump format...
+ */
+PUBLIC int
+style10(game_state_t *b, move_t *ml)
+{
+ char tmp[80];
+ int f, r;
+ int ret, too_long;
+ int ws, bs;
+
+ board_calc_strength(b, &ws, &bs);
+ msnprintf(tmp, sizeof tmp, "<10>\n");
+ mstrlcat(bstring, tmp, sizeof bstring);
+
+ for (r = 7; r >= 0; r--) {
+ mstrlcat(bstring, "|", sizeof bstring);
+
+ for (f = 0; f < 8; f++) {
+ if (b->board[f][r] == NOPIECE) {
+ mstrlcat(bstring, " ", sizeof bstring);
+ } else {
+ if (colorval(b->board[f][r]) == WHITE) {
+ mstrlcat(bstring, wpstring
+ [piecetype(b->board[f][r])],
+ sizeof bstring);
+ } else {
+ mstrlcat(bstring, bpstring
+ [piecetype(b->board[f][r])],
+ sizeof bstring);
+ }
+ }
+ }
+
+ mstrlcat(bstring, "|\n", sizeof bstring);
+ }
+
+ mstrlcat(bstring, (b->onMove == WHITE ? "W " : "B "), sizeof bstring);
+
+ if (garray[b->gameNum].numHalfMoves) {
+ msnprintf(tmp, sizeof tmp, "%d ",
+ ml[garray[b->gameNum].numHalfMoves - 1].doublePawn);
+ } else {
+ msnprintf(tmp, sizeof tmp, "-1 ");
+ }
+
+ mstrlcat(bstring, tmp, sizeof bstring);
+
+ msnprintf(tmp, sizeof tmp, "%d %d %d %d %d\n",
+ !(b->wkmoved || b->wkrmoved),
+ !(b->wkmoved || b->wqrmoved),
+ !(b->bkmoved || b->bkrmoved),
+ !(b->bkmoved || b->bqrmoved),
+
+ (garray[b->gameNum].numHalfMoves -
+ (b->lastIrreversable == -1 ? 0 : b->lastIrreversable)));
+
+ mstrlcat(bstring, tmp, sizeof bstring);
+
+ ret = snprintf(tmp, sizeof tmp, "%d %s %s %d %d %d %d %d %d %d %d %s "
+ "(%s) %s %d\n",
+ b->gameNum,
+ garray[b->gameNum].white_name,
+ garray[b->gameNum].black_name,
+ myTurn,
+ (garray[b->gameNum].wInitTime / 600),
+ (garray[b->gameNum].wIncrement / 10),
+ ws,
+ bs,
+ ((wTime + 5) / 10),
+ ((bTime + 5) / 10),
+ (garray[b->gameNum].numHalfMoves / 2 + 1),
+
+ (garray[b->gameNum].numHalfMoves
+ ? ml[garray[b->gameNum].numHalfMoves - 1].moveString
+ : "none"),
+
+ (garray[b->gameNum].numHalfMoves
+ ? tenth_str(ml[garray[b->gameNum].numHalfMoves - 1].tookTime, 0)
+ : "0:00"),
+
+ (garray[b->gameNum].numHalfMoves
+ ? ml[garray[b->gameNum].numHalfMoves - 1].algString
+ : "none"),
+
+ (orient == WHITE ? 0 : 1));
+
+ too_long = (ret < 0 || (size_t)ret >= sizeof tmp);
+
+ if (too_long) {
+ fprintf(stderr, "FICS: %s: warning: snprintf truncated\n",
+ __func__);
+ }
+
+ mstrlcat(bstring, tmp, sizeof bstring);
+
+ msnprintf(tmp, sizeof tmp, ">10<\n");
+ mstrlcat(bstring, tmp, sizeof bstring);
+
+ return 0;
+}
+
+/*
+ * Same as 8, but with verbose moves ("P/e3-e4", instead of "e4").
+ */
+PUBLIC int
+style11(game_state_t *b, move_t *ml)
+{
+ char tmp[80];
+ int f, r;
+ int ws, bs;
+
+ board_calc_strength(b, &ws, &bs);
+
+ msnprintf(tmp, sizeof tmp, "#@#%03d%-16s%s%-16s%s",
+ b->gameNum,
+
+ garray[b->gameNum].white_name,
+ (orient == WHITE ? "*" : ":"),
+
+ garray[b->gameNum].black_name,
+ (orient == WHITE ? ":" : "*"));
+
+ mstrlcat(bstring, tmp, sizeof bstring);
+
+ for (r = 0; r < 8; r++) {
+ for (f = 0; f < 8; f++) {
+ if (b->board[f][r] == NOPIECE) {
+ mstrlcat(bstring, " ", sizeof bstring);
+ } else {
+ if (colorval(b->board[f][r]) == WHITE) {
+ mstrlcat(bstring, wpstring
+ [piecetype(b->board[f][r])],
+ sizeof bstring);
+ } else {
+ mstrlcat(bstring, bpstring
+ [piecetype(b->board[f][r])],
+ sizeof bstring);
+ }
+ }
+ }
+ }
+
+ msnprintf(tmp, sizeof tmp, "%03d%s%02d%02d%05d%05d%-7s(%s)@#@\n",
+ (garray[b->gameNum].numHalfMoves / 2 + 1),
+ (b->onMove == WHITE ? "W" : "B"),
+ ws,
+ bs,
+ ((wTime + 5) / 10),
+ ((bTime + 5) / 10),
+
+ (garray[b->gameNum].numHalfMoves
+ ? ml[garray[b->gameNum].numHalfMoves - 1].moveString
+ : "none"),
+
+ (garray[b->gameNum].numHalfMoves
+ ? tenth_str(ml[garray[b->gameNum].numHalfMoves - 1].tookTime, 0)
+ : "0:00"));
+
+ mstrlcat(bstring, tmp, sizeof bstring);
+ return 0;
+}
+
+/*
+ * Similar to style 10. See the "style12" help file for information.
+ */
+PUBLIC int
+style12(game_state_t *b, move_t *ml)
+{
+ char tmp[80];
+ int f, r;
+ int ret, too_long;
+ int ws, bs;
+
+ board_calc_strength(b, &ws, &bs);
+ msnprintf(bstring, sizeof bstring, "<12> ");
+
+ for (r = 7; r >= 0; r--) {
+ for (f = 0; f < 8; f++) {
+ if (b->board[f][r] == NOPIECE) {
+ mstrlcat(bstring, "-", sizeof bstring);
+ } else {
+ if (colorval(b->board[f][r]) == WHITE) {
+ mstrlcat(bstring, wpstring
+ [piecetype(b->board[f][r])],
+ sizeof bstring);
+ } else {
+ mstrlcat(bstring, bpstring
+ [piecetype(b->board[f][r])],
+ sizeof bstring);
+ }
+ }
+ }
+
+ mstrlcat(bstring, " ", sizeof bstring);
+ }
+
+ mstrlcat(bstring, (b->onMove == WHITE ? "W " : "B "), sizeof bstring);
+
+ if (garray[b->gameNum].numHalfMoves) {
+ msnprintf(tmp, sizeof tmp, "%d ",
+ ml[garray[b->gameNum].numHalfMoves - 1].doublePawn);
+ } else {
+ msnprintf(tmp, sizeof tmp, "-1 ");
+ }
+
+ mstrlcat(bstring, tmp, sizeof bstring);
+
+ msnprintf(tmp, sizeof tmp, "%d %d %d %d %d ",
+ !(b->wkmoved || b->wkrmoved),
+ !(b->wkmoved || b->wqrmoved),
+ !(b->bkmoved || b->bkrmoved),
+ !(b->bkmoved || b->bqrmoved),
+
+ (garray[b->gameNum].numHalfMoves -
+ (b->lastIrreversable == -1 ? 0 : b->lastIrreversable)));
+ mstrlcat(bstring, tmp, sizeof bstring);
+
+ ret = snprintf(tmp, sizeof tmp, "%d %s %s %d %d %d %d %d %d %d %d %s "
+ "(%s) %s %d\n",
+ (b->gameNum + 1),
+ garray[b->gameNum].white_name,
+ garray[b->gameNum].black_name,
+ myTurn,
+ (garray[b->gameNum].wInitTime / 600),
+ (garray[b->gameNum].wIncrement / 10),
+ ws,
+ bs,
+ ((wTime + 5) / 10),
+ ((bTime + 5) / 10),
+ (garray[b->gameNum].numHalfMoves / 2 + 1),
+
+ (garray[b->gameNum].numHalfMoves
+ ? ml[garray[b->gameNum].numHalfMoves - 1].moveString
+ : "none"),
+
+ (garray[b->gameNum].numHalfMoves
+ ? tenth_str(ml[garray[b->gameNum].numHalfMoves - 1].tookTime, 0)
+ : "0:00"),
+
+ (garray[b->gameNum].numHalfMoves
+ ? ml[garray[b->gameNum].numHalfMoves - 1].algString
+ : "none"),
+
+ (orient == WHITE ? 0 : 1));
+
+ too_long = (ret < 0 || (size_t)ret >= sizeof tmp);
+
+ if (too_long) {
+ fprintf(stderr, "FICS: %s: warning: snprintf truncated\n",
+ __func__);
+ }
+
+ mstrlcat(bstring, tmp, sizeof bstring);
+
+ return 0;
+}
+
+PUBLIC int
+board_read_file(char *category, char *gname, game_state_t *gs)
+{
+ FILE *fp;
+ char fname[MAX_FILENAME_SIZE + 1];
+ int c;
+ int f, r;
+ int onColor = -1;
+ int onFile = -1;
+ int onNewLine = 1;
+ int onPiece = -1;
+ int onRank = -1;
+
+ msnprintf(fname, sizeof fname, "%s/%s/%s", board_dir, category, gname);
+
+ if ((fp = fopen(fname, "r")) == NULL)
+ return 1;
+ for (f = 0; f < 8; f++)
+ for (r = 0; r < 8; r++)
+ gs->board[f][r] = NOPIECE;
+ for (f = 0; f < 2; f++) {
+ for (r = 0; r < 8; r++)
+ gs->ep_possible[f][r] = 0;
+ for (r = PAWN; r <= QUEEN; r++)
+ gs->holding[f][r - 1] = 0;
+ }
+
+ gs->wkmoved = gs->wqrmoved = gs->wkrmoved = 0;
+ gs->bkmoved = gs->bqrmoved = gs->bkrmoved = 0;
+
+ gs->onMove = -1;
+ gs->moveNum = 1;
+ gs->lastIrreversable = -1;
+
+ while (!feof(fp)) {
+ c = fgetc(fp);
+
+ if (onNewLine) {
+ if (c == 'W') {
+ onColor = WHITE;
+ if (gs->onMove < 0)
+ gs->onMove = WHITE;
+ } else if (c == 'B') {
+ onColor = BLACK;
+ if (gs->onMove < 0)
+ gs->onMove = BLACK;
+ } else if (c == '#') {
+ while (!feof(fp) && c != '\n')
+ c = fgetc(fp); // Comment line
+ continue;
+ } else { // Skip any line we don't understand
+ while (!feof(fp) && c != '\n')
+ c = fgetc(fp);
+ continue;
+ }
+
+ onNewLine = 0;
+ } else {
+ switch (c) {
+ case 'P':
+ onPiece = PAWN;
+ break;
+ case 'R':
+ onPiece = ROOK;
+ break;
+ case 'N':
+ onPiece = KNIGHT;
+ break;
+ case 'B':
+ onPiece = BISHOP;
+ break;
+ case 'Q':
+ onPiece = QUEEN;
+ break;
+ case 'K':
+ onPiece = KING;
+ break;
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ case 'g':
+ case 'h':
+ onFile = (c - 'a');
+ onRank = -1; // NOLINT: dead store
+ break;
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ onRank = (c - '1');
+
+ if (onFile >= 0 &&
+ onColor >= 0 &&
+ onPiece >= 0) {
+ gs->board[onFile][onRank] =
+ (onPiece | onColor);
+ }
+
+ break;
+ case '#':
+ while (!feof(fp) && c != '\n')
+ c = fgetc(fp); // Comment line
+ case '\n':
+ onNewLine = 1;
+ onColor = -1;
+ onPiece = -1;
+ onFile = -1;
+ onRank = -1; // NOLINT: dead store
+ break;
+ default:
+ break;
+ } // switch
+ }
+ } // while
+
+ fclose(fp);
+ return 0;
+}
+
+PRIVATE void
+place_piece(board_t b, int piece, int squareColor)
+{
+ int placed = 0;
+ int r, f;
+
+ if (iscolor(piece, BLACK))
+ r = 7;
+ else
+ r = 0;
+
+ while (!placed) {
+ if (squareColor == ANY_SQUARE) {
+ f = (brand() % 8);
+ } else {
+ f = (brand() % 4) * 2;
+
+ if (SquareColor(f, r) != squareColor)
+ f++;
+ }
+
+ if ((b)[f][r] == NOPIECE) {
+ (b)[f][r] = piece;
+ placed = 1;
+ }
+ }
+}
+
+PUBLIC void
+wild_update(int style)
+{
+ board_t b;
+ int f, r, i;
+
+ for (f = 0; f < 8; f++) {
+ for (r = 0; r < 8; r++)
+ b[f][r] = NOPIECE;
+ }
+
+ for (f = 0; f < 8; f++) {
+ b[f][1] = W_PAWN;
+ b[f][6] = B_PAWN;
+ }
+
+ switch (style) {
+ case 1:
+ if (brand() & 0x01) {
+ b[4][0] = W_KING;
+ b[3][0] = W_QUEEN;
+ } else {
+ b[3][0] = W_KING;
+ b[4][0] = W_QUEEN;
+ }
+ if (brand() & 0x01) {
+ b[4][7] = B_KING;
+ b[3][7] = B_QUEEN;
+ } else {
+ b[3][7] = B_KING;
+ b[4][7] = B_QUEEN;
+ }
+
+ b[0][0] = b[7][0] = W_ROOK;
+ b[0][7] = b[7][7] = B_ROOK;
+
+ /*
+ * Must do bishops before knights to be sure opposite
+ * colored squares are available.
+ */
+ place_piece(b, W_BISHOP, WHITE_SQUARE);
+ place_piece(b, W_BISHOP, BLACK_SQUARE);
+ place_piece(b, W_KNIGHT, ANY_SQUARE);
+ place_piece(b, W_KNIGHT, ANY_SQUARE);
+ place_piece(b, B_BISHOP, WHITE_SQUARE);
+ place_piece(b, B_BISHOP, BLACK_SQUARE);
+ place_piece(b, B_KNIGHT, ANY_SQUARE);
+ place_piece(b, B_KNIGHT, ANY_SQUARE);
+ break;
+ case 2:
+ place_piece(b, W_KING, ANY_SQUARE);
+ place_piece(b, W_QUEEN, ANY_SQUARE);
+ place_piece(b, W_ROOK, ANY_SQUARE);
+ place_piece(b, W_ROOK, ANY_SQUARE);
+ place_piece(b, W_BISHOP, ANY_SQUARE);
+ place_piece(b, W_BISHOP, ANY_SQUARE);
+ place_piece(b, W_KNIGHT, ANY_SQUARE);
+ place_piece(b, W_KNIGHT, ANY_SQUARE);
+
+ /* Black mirrors White */
+ for (i = 0; i < 8; i++)
+ b[i][7] = (b[i][0] | BLACK);
+ break;
+ case 3:
+ /*
+ * Generate White king on random square plus random
+ * set of pieces.
+ */
+ place_piece(b, W_KING, ANY_SQUARE);
+
+ for (i = 0; i < 8; i++) {
+ if (b[i][0] != W_KING) {
+ b[i][0] = (brand() % 4) + 2;
+ }
+ }
+
+ /* Black mirrors White */
+ for (i = 0; i < 8; i++)
+ b[i][7] = (b[i][0] | BLACK);
+ break;
+ case 4:
+ /*
+ * Generate White king on random square plus random
+ * set of pieces.
+ */
+ place_piece(b, W_KING, ANY_SQUARE);
+
+ for (i = 0; i < 8; i++) {
+ if (b[i][0] != W_KING)
+ b[i][0] = (brand() % 4) + 2;
+ }
+
+ /*
+ * Black has the same set of pieces, but randomly
+ * permuted, except that Black must have the same
+ * number of bishops on white squares as White has on
+ * black squares, and vice versa. So we must place
+ * Black's bishops first to be sure there are enough
+ * squares left of the correct color.
+ */
+
+ for (i = 0; i < 8; i++) {
+ if (b[i][0] == W_BISHOP)
+ place_piece(b, B_BISHOP, !SquareColor(i, 0));
+ }
+
+ for (i = 0; i < 8; i++) {
+ if (b[i][0] != W_BISHOP)
+ place_piece(b, (b[i][0] | BLACK), ANY_SQUARE);
+ }
+ break;
+ default:
+ return;
+ break;
+ }
+
+ {
+ FILE *fp;
+ char fname[MAX_FILENAME_SIZE + 1];
+ int fd;
+ int onPiece;
+
+ msnprintf(fname, sizeof fname, "%s/wild/%d", board_dir, style);
+
+ if ((fd = open(fname, g_open_flags[1], g_open_modes)) < 0) {
+ warn("%s: can't write file name: %s", __func__, fname);
+ return;
+ } else if ((fp = fdopen(fd, "w")) == NULL) {
+ warn("%s: can't write file name: %s", __func__, fname);
+ close(fd);
+ return;
+ }
+
+ fprintf(fp, "W:");
+ onPiece = -1;
+
+ for (r = 1; r >= 0; r--) {
+ for (f = 0; f < 8; f++) {
+ if (onPiece < 0 || b[f][r] != onPiece) {
+ onPiece = b[f][r];
+
+ fprintf(fp, " %s", wpstring
+ [piecetype(b[f][r])]);
+ }
+
+ fprintf(fp, " %c%c", (f + 'a'), (r + '1'));
+ }
+ }
+
+ fprintf(fp, "\nB:");
+ onPiece = -1;
+
+ for (r = 6; r < 8; r++) {
+ for (f = 0; f < 8; f++) {
+ if (onPiece < 0 || b[f][r] != onPiece) {
+ onPiece = b[f][r];
+
+ fprintf(fp, " %s", wpstring
+ [piecetype(b[f][r])]);
+ }
+
+ fprintf(fp, " %c%c", (f + 'a'), (r + '1'));
+ }
+ }
+
+ fprintf(fp, "\n");
+ fclose(fp);
+ }
+}
+
+PUBLIC void
+wild_init(void)
+{
+ wild_update(1);
+ wild_update(2);
+ wild_update(3);
+ wild_update(4);
+}
diff --git a/FICS/board.h b/FICS/board.h
new file mode 100644
index 0000000..cc283ad
--- /dev/null
+++ b/FICS/board.h
@@ -0,0 +1,158 @@
+/* board.h
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 23/12/24 Revised
+*/
+
+#ifndef _BOARD_H
+#define _BOARD_H
+
+#define WHITE 0x00
+#define BLACK 0x80
+
+#define CString(c) (((c) == WHITE) ? "White" : "Black")
+#define CToggle(c) (((c) == BLACK) ? WHITE : BLACK)
+
+/*
+ * These are indexes into an array so their values are not arbitrary.
+ */
+#define NOPIECE 0x00
+#define PAWN 0x01
+#define KNIGHT 0x02
+#define BISHOP 0x03
+#define ROOK 0x04
+#define QUEEN 0x05
+#define KING 0x06
+
+#define MAX_BOARD_STRING_LEGTH 1280 /* Arbitrarily 80 * 16 */
+#define MAX_STYLES 13
+
+#define MoveToHalfMove(gs) \
+ ((((gs)->moveNum - 1) * 2) + (((gs)->onMove == WHITE) ? 0 : 1))
+
+#define W_PAWN (PAWN | WHITE)
+#define W_KNIGHT (KNIGHT | WHITE)
+#define W_BISHOP (BISHOP | WHITE)
+#define W_ROOK (ROOK | WHITE)
+#define W_QUEEN (QUEEN | WHITE)
+#define W_KING (KING | WHITE)
+
+#define B_PAWN (PAWN | BLACK)
+#define B_KNIGHT (KNIGHT | BLACK)
+#define B_BISHOP (BISHOP | BLACK)
+#define B_ROOK (ROOK | BLACK)
+#define B_QUEEN (QUEEN | BLACK)
+#define B_KING (KING | BLACK)
+
+#define isblack(p) ((p) & BLACK)
+#define iswhite(p) (!isblack(p))
+#define iscolor(p, color) (((p) & BLACK) == (color))
+
+#define colorval(p) ((p) & 0x80)
+#define piecetype(p) ((p) & 0x7f)
+#define square_color(r, f) ((((r) + (f)) & 0x01) ? BLACK : WHITE)
+
+/* Treated as [file][rank] */
+typedef int board_t[8][8];
+
+typedef struct _game_state_t {
+ board_t board;
+
+ /*
+ * For bughouse
+ */
+ int holding[2][5];
+
+ /*
+ * For castling
+ */
+ unsigned char wkmoved, wqrmoved, wkrmoved;
+ unsigned char bkmoved, bqrmoved, bkrmoved;
+
+ /*
+ * For ep
+ */
+ int ep_possible[2][8];
+
+ /*
+ * For draws
+ */
+ int lastIrreversable;
+ int onMove;
+ int moveNum;
+
+ int gameNum;
+} game_state_t;
+
+/*
+ * If a drop move, then 'fromFile' is ALG_DROP and 'fromRank' is
+ * piece.
+ */
+#define ALG_DROP -2
+
+typedef struct _move_t {
+ int color;
+ int fromFile, fromRank;
+ int toFile, toRank;
+ int pieceCaptured;
+ int piecePromotionTo;
+ int enPassant; // 0 = No, 1 = Higher, -1 = Lower
+ int doublePawn; // Only used for board display
+
+ char moveString[8];
+ char algString[8];
+
+ unsigned char FENpos[74];
+ unsigned int atTime;
+ unsigned int tookTime;
+} move_t;
+
+extern char *wpstring[];
+extern char *bpstring[];
+
+extern int pieceValues[7];
+
+extern int style1(game_state_t *, move_t *);
+extern int style2(game_state_t *, move_t *);
+extern int style3(game_state_t *, move_t *);
+extern int style4(game_state_t *, move_t *);
+extern int style5(game_state_t *, move_t *);
+extern int style6(game_state_t *, move_t *);
+extern int style7(game_state_t *, move_t *);
+extern int style8(game_state_t *, move_t *);
+extern int style9(game_state_t *, move_t *);
+extern int style10(game_state_t *, move_t *);
+extern int style11(game_state_t *, move_t *);
+extern int style12(game_state_t *, move_t *);
+extern int style13(game_state_t *, move_t *);
+
+extern char *board_to_string(char *, char *, int, int, game_state_t *,
+ move_t *, int, int, int, int);
+extern char *move_and_time(move_t *);
+extern int board_init(game_state_t *, char *, char *);
+extern int board_read_file(char *, char *, game_state_t *);
+extern void board_calc_strength(game_state_t *, int *, int *);
+extern void update_holding(int, int);
+extern void wild_init(void);
+extern void wild_update(int);
+
+#endif
diff --git a/FICS/build.mk b/FICS/build.mk
new file mode 100644
index 0000000..5d3d9b2
--- /dev/null
+++ b/FICS/build.mk
@@ -0,0 +1,95 @@
+# SPDX-FileCopyrightText: 2023 Markus Uhlin <maxxe@rpblc.net>
+# SPDX-License-Identifier: ISC
+
+SRC_DIR := FICS/
+
+OBJS = $(SRC_DIR)adminproc.o\
+ $(SRC_DIR)algcheck.o\
+ $(SRC_DIR)assert_error.o\
+ $(SRC_DIR)board.o\
+ $(SRC_DIR)command.o\
+ $(SRC_DIR)comproc.o\
+ $(SRC_DIR)eco.o\
+ $(SRC_DIR)fics_getsalt.o\
+ $(SRC_DIR)ficslim.o\
+ $(SRC_DIR)ficsmain.o\
+ $(SRC_DIR)formula.o\
+ $(SRC_DIR)gamedb.o\
+ $(SRC_DIR)gameproc.o\
+ $(SRC_DIR)iset.o\
+ $(SRC_DIR)legal.o\
+ $(SRC_DIR)legal2.o\
+ $(SRC_DIR)lists.o\
+ $(SRC_DIR)matchproc.o\
+ $(SRC_DIR)maxxes-utils.o\
+ $(SRC_DIR)movecheck.o\
+ $(SRC_DIR)multicol.o\
+ $(SRC_DIR)network.o\
+ $(SRC_DIR)obsproc.o\
+ $(SRC_DIR)playerdb.o\
+ $(SRC_DIR)rating_conv.o\
+ $(SRC_DIR)ratings.o\
+ $(SRC_DIR)rmalloc.o\
+ $(SRC_DIR)shutdown.o\
+ $(SRC_DIR)sought.o\
+ $(SRC_DIR)talkproc.o\
+ $(SRC_DIR)utils.o\
+ $(SRC_DIR)variable.o\
+ $(SRC_DIR)vers.o
+
+SRCS = $(SRC_DIR)adminproc.c\
+ $(SRC_DIR)algcheck.c\
+ $(SRC_DIR)assert_error.c\
+ $(SRC_DIR)board.c\
+ $(SRC_DIR)command.c\
+ $(SRC_DIR)comproc.c\
+ $(SRC_DIR)eco.c\
+ $(SRC_DIR)fics_getsalt.cpp\
+ $(SRC_DIR)ficslim.cpp\
+ $(SRC_DIR)ficsmain.c\
+ $(SRC_DIR)formula.c\
+ $(SRC_DIR)gamedb.c\
+ $(SRC_DIR)gameproc.c\
+ $(SRC_DIR)iset.cpp\
+ $(SRC_DIR)legal.c\
+ $(SRC_DIR)legal2.c\
+ $(SRC_DIR)lists.c\
+ $(SRC_DIR)matchproc.c\
+ $(SRC_DIR)maxxes-utils.c\
+ $(SRC_DIR)movecheck.c\
+ $(SRC_DIR)multicol.c\
+ $(SRC_DIR)network.c\
+ $(SRC_DIR)obsproc.c\
+ $(SRC_DIR)playerdb.c\
+ $(SRC_DIR)rating_conv.c\
+ $(SRC_DIR)ratings.c\
+ $(SRC_DIR)rmalloc.c\
+ $(SRC_DIR)shutdown.c\
+ $(SRC_DIR)sought.cpp\
+ $(SRC_DIR)talkproc.c\
+ $(SRC_DIR)utils.c\
+ $(SRC_DIR)variable.c\
+ $(SRC_DIR)vers.c
+
+AP_OBJS = $(SRC_DIR)fics_addplayer.o
+
+MR_OBJS = $(SRC_DIR)makerank.o
+# dfree
+# memmove
+
+$(INCLUDE_DIR)ficspaths.h:
+ $(ROOT)ficspaths.sh "$(FICS_HOME)"
+fics: $(INCLUDE_DIR)ficspaths.h $(OBJS)
+ $(E) " LINK " $@
+ $(Q) $(CXX) $(CXXFLAGS) -o $@ $(OBJS) $(LDFLAGS) $(LDLIBS)
+fics_addplayer: $(INCLUDE_DIR)ficspaths.h $(OBJS) $(AP_OBJS)
+ strip --strip-symbol=main $(SRC_DIR)ficsmain.o
+ $(E) " LINK " $@
+ $(Q) $(CXX) $(CXXFLAGS) -o $@ $(OBJS) \
+ $(AP_OBJS) $(AP_LDFLAGS) $(AP_LDLIBS)
+makerank: $(INCLUDE_DIR)ficspaths.h $(OBJS) $(MR_OBJS)
+ strip --strip-symbol=main $(SRC_DIR)ficsmain.o
+ $(E) " LINK " $@
+ $(Q) $(CXX) $(CXXFLAGS) -o $@ $(OBJS) \
+ $(MR_OBJS) $(MR_LDFLAGS) $(MR_LDLIBS)
+# EOF
diff --git a/FICS/command.c b/FICS/command.c
new file mode 100644
index 0000000..df5d4c2
--- /dev/null
+++ b/FICS/command.c
@@ -0,0 +1,1382 @@
+/* command.c
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 23/12/17 Fixed compiler warnings
+ Markus Uhlin 23/12/19 Usage of 'time_t'
+ Markus Uhlin 23/12/23 Fixed crypt()
+ Markus Uhlin 24/05/01 Refactored and reformatted all
+ functions.
+ Markus Uhlin 24/05/01 Replaced unbounded string
+ handling functions.
+ Markus Uhlin 24/11/17 process_prompt:
+ handle 'COM_FAILED' differently
+ Markus Uhlin 24/11/23 Improved:
+ check_news() and
+ rscan_news().
+ Markus Uhlin 24/11/25 Null checks
+ Markus Uhlin 25/03/09 Fixed double free()
+ Markus Uhlin 25/03/11 Fixed memleak
+ Markus Uhlin 25/03/16 Fixed use of 32-bit 'time_t'
+ Markus Uhlin 25/07/28 Usage of 'int64_t'
+ Markus Uhlin 25/08/23 Fixed file created without
+ restricting permissions.
+*/
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include <sys/param.h>
+
+#include <err.h>
+#include <inttypes.h>
+#include <stdint.h>
+
+#include "command.h"
+#include "command_list.h"
+#include "config.h"
+#include "fics_getsalt.h"
+#include "ficsmain.h"
+#include "gamedb.h"
+#include "gameproc.h"
+#include "maxxes-utils.h"
+#include "movecheck.h"
+#include "network.h"
+#include "obsproc.h"
+#include "playerdb.h"
+#include "ratings.h"
+#include "rmalloc.h"
+#include "shutdown.h"
+#include "utils.h"
+#include "vers.h"
+
+PUBLIC char *adhelp_dir = DEFAULT_ADHELP;
+PUBLIC char *adj_dir = DEFAULT_ADJOURNED;
+PUBLIC char *board_dir = DEFAULT_BOARDS;
+PUBLIC char *comhelp_dir = DEFAULT_COMHELP;
+PUBLIC char *def_prompt = DEFAULT_PROMPT;
+PUBLIC char *help_dir[NUM_LANGS] = {
+ DEFAULT_HELP,
+ HELP_SPANISH,
+ HELP_FRENCH,
+ HELP_DANISH
+};
+PUBLIC char *hist_dir = DEFAULT_HISTORY;
+PUBLIC char *index_dir = DEFAULT_INDEX;
+PUBLIC char *info_dir = DEFAULT_INFO;
+PUBLIC char *journal_dir = DEFAULT_JOURNAL;
+PUBLIC char *lists_dir = DEFAULT_LISTS;
+PUBLIC char *mess_dir = DEFAULT_MESS;
+PUBLIC char *news_dir = DEFAULT_NEWS;
+PUBLIC char *player_dir = DEFAULT_PLAYERS;
+PUBLIC char *source_dir = DEFAULT_SOURCE;
+PUBLIC char *stats_dir = DEFAULT_STATS;
+PUBLIC char *usage_dir[NUM_LANGS] = {
+ DEFAULT_USAGE,
+ USAGE_SPANISH,
+ USAGE_FRENCH,
+ USAGE_DANISH
+};
+PUBLIC char *uscf_dir = DEFAULT_USCF;
+
+PUBLIC char *hadmin_handle = HADMINHANDLE;
+PRIVATE char *hadmin_email = HADMINEMAIL;
+PRIVATE char *reg_addr = REGMAIL;
+
+PUBLIC char fics_hostname[81];
+PUBLIC int MailGameResult;
+PUBLIC int game_high;
+PUBLIC int player_high;
+PUBLIC time_t startuptime;
+
+/*
+ * The player whose command you're in
+ */
+PUBLIC int commanding_player = -1;
+
+PRIVATE int lastCommandFound = -1;
+
+/*
+ * Copies command into 'comm'.
+ */
+PRIVATE int
+parse_command(char *com_string, char **comm, char **parameters)
+{
+ *comm = com_string;
+ *parameters = eatword(com_string);
+
+ if (**parameters != '\0') {
+ **parameters = '\0';
+ (*parameters)++;
+ *parameters = eatwhite(*parameters);
+ }
+
+ if (strlen(*comm) >= MAX_COM_LENGTH)
+ return COM_BADCOMMAND;
+ return COM_OK;
+}
+
+PUBLIC int
+alias_lookup(char *tmp, alias_type *alias_list, int numalias)
+{
+ if (numalias >= MAX_ALIASES)
+ return -1;
+ for (int i = 0;
+ (i < numalias && alias_list[i].comm_name != NULL);
+ i++) {
+ if (!strcmp(tmp, alias_list[i].comm_name))
+ return i;
+ }
+ return -1; /* not found */
+}
+
+#if 0
+PRIVATE int
+alias_count(alias_type *alias_list)
+{
+ int i;
+
+ for (i = 0; alias_list[i].comm_name; i++) {
+ /* null */;
+ }
+ return i;
+}
+#endif
+
+/*
+ * Puts alias substitution into alias_string
+ */
+PRIVATE void
+alias_substitute(alias_type *alias_list, int num_alias, char *com_str,
+ char outalias[], const size_t size)
+{
+ char *atpos, *aliasval;
+ char *s = com_str;
+ char name[MAX_COM_LENGTH] = { '\0' };
+ char *t = name;
+ int i = 0;
+
+ /*
+ * Get first word of command, terminated by whitespace or by
+ * containing punctuation.
+ */
+ while (*s && !iswhitespace(*s)) {
+ if (i++ >= MAX_COM_LENGTH) {
+ mstrlcpy(outalias, com_str, size);
+ return;
+ }
+
+ if (ispunct(*t++ = *s++))
+ break;
+ }
+
+ *t = '\0';
+
+ if (*s && iswhitespace(*s))
+ s++;
+
+ i = alias_lookup(name, alias_list, num_alias);
+
+ if (i < 0) {
+ mstrlcpy(outalias, com_str, size);
+ return;
+ }
+
+ aliasval = alias_list[i].alias;
+
+ // See if alias contains an @
+ atpos = strchr(aliasval, '@');
+
+ if (atpos != NULL) {
+ const size_t diff = atpos - aliasval;
+
+ if (diff >= size) { // XXX
+ outalias[0] = '\0';
+ warnx("%s: diff out of bounds!", __func__);
+ return;
+ }
+
+ strncpy(outalias, aliasval, diff);
+ outalias[diff] = '\0';
+
+ mstrlcat(outalias, s, size);
+ mstrlcat(outalias, atpos + 1, size);
+ } else {
+ mstrlcpy(outalias, aliasval, size);
+
+ if (*s) {
+ mstrlcat(outalias, " ", size);
+ mstrlcat(outalias, s, size);
+ }
+ }
+}
+
+/*
+ * Returns pointer to command that matches
+ */
+PRIVATE int
+match_command(char *comm, int p)
+{
+ int gotIt = -1;
+ int i = 0;
+ int len = strlen(comm);
+
+ while (command_list[i].comm_name) {
+ if (!strncmp(command_list[i].comm_name, comm, len) &&
+ parray[p].adminLevel >= command_list[i].adminLevel) {
+ if (gotIt >= 0)
+ return -COM_AMBIGUOUS;
+ gotIt = i;
+ }
+
+ i++;
+ }
+
+ if (gotIt >= 0 &&
+ in_list(p, L_REMOVEDCOM, command_list[gotIt].comm_name)) {
+ pprintf(p, "Due to a bug - this command has been temporarily "
+ "removed.\n");
+ return -COM_FAILED;
+ }
+
+ if (gotIt >= 0) {
+ lastCommandFound = gotIt;
+ return gotIt;
+ }
+
+ return -COM_FAILED;
+}
+
+/*
+ * Gets the parameters for this command
+ */
+PRIVATE int
+get_parameters(int command, char *parameters, param_list params)
+{
+ char c;
+ int i, parlen;
+ int paramLower;
+ static char punc[2];
+
+ punc[1] = '\0'; // Holds punc parameters
+
+ for (i = 0; i < MAXNUMPARAMS; i++)
+ (params)[i].type = TYPE_NULL; // Set all parameters to NULL
+
+ parlen = strlen(command_list[command].param_string);
+
+ for (i = 0; i < parlen; i++) {
+ c = command_list[command].param_string[i];
+
+ if (isupper(c)) {
+ paramLower = 0;
+ c = tolower(c);
+ } else {
+ paramLower = 1;
+ }
+
+ switch (c) {
+ case 'w':
+ case 'o': // word or optional word
+ parameters = eatwhite(parameters);
+
+ if (!*parameters)
+ return (c == 'o' ? COM_OK : COM_BADPARAMETERS);
+
+ (params)[i].val.word = parameters;
+ (params)[i].type = TYPE_WORD;
+
+ if (ispunct(*parameters)) {
+ punc[0] = *parameters;
+
+ (params)[i].val.word = punc;
+
+ parameters++;
+
+ if (*parameters && iswhitespace(*parameters))
+ parameters++;
+ } else {
+ parameters = eatword(parameters);
+
+ if (*parameters != '\0') {
+ *parameters = '\0';
+ parameters++;
+ }
+ }
+
+ if (paramLower)
+ stolower((params)[i].val.word);
+
+ break;
+ case 'd':
+ case 'p': // optional or required integer
+ parameters = eatwhite(parameters);
+
+ if (!*parameters)
+ return (c == 'p' ? COM_OK : COM_BADPARAMETERS);
+
+ if (sscanf(parameters, "%d", &(params)[i].val.integer)
+ != 1)
+ return COM_BADPARAMETERS;
+
+ (params)[i].type = TYPE_INT;
+
+ parameters = eatword(parameters);
+
+ if (*parameters != '\0') {
+ *parameters = '\0';
+ parameters++;
+ }
+
+ break;
+ case 'i':
+ case 'n': // optional or required word or integer
+ parameters = eatwhite(parameters);
+
+ if (!*parameters)
+ return (c == 'n' ? COM_OK : COM_BADPARAMETERS);
+
+ if (sscanf(parameters, "%d", &(params)[i].val.integer)
+ != 1) {
+ (params)[i].val.word = parameters;
+ (params)[i].type = TYPE_WORD;
+ } else {
+ (params)[i].type = TYPE_INT;
+ }
+
+ if (ispunct(*parameters)) {
+ punc[0] = *parameters;
+
+ (params)[i].val.word = punc;
+ (params)[i].type = TYPE_WORD;
+
+ parameters++;
+
+ if (*parameters && iswhitespace(*parameters))
+ parameters++;
+ } else {
+ parameters = eatword(parameters);
+
+ if (*parameters != '\0') {
+ *parameters = '\0';
+ parameters++;
+ }
+ }
+
+ if ((params)[i].type == TYPE_WORD)
+ if (paramLower)
+ stolower((params)[i].val.word);
+ break;
+ case 's':
+ case 't': // optional or required string to end
+ if (!*parameters)
+ return (c == 't' ? COM_OK : COM_BADPARAMETERS);
+
+ (params)[i].val.string = parameters;
+ (params)[i].type = TYPE_STRING;
+
+ while (*parameters)
+ parameters = nextword(parameters);
+ if (paramLower)
+ stolower((params)[i].val.string);
+ break;
+ } /* switch */
+ } /* for */
+
+ if (*parameters)
+ return COM_BADPARAMETERS;
+ else
+ return COM_OK;
+}
+
+PRIVATE void
+printusage(int p, char *command_str)
+{
+ char *filenames[1000]; // enough for all usage names
+ char c;
+ int command;
+ int i, parlen, UseLang = parray[p].language;
+
+ if ((command = match_command(command_str, p)) < 0) {
+ pprintf(p, " UNKNOWN COMMAND\n");
+ return;
+ }
+
+ /*
+ * Usage added by DAV 11/19/95.
+ * First lets check if we have a text usage file for it.
+ */
+
+ i = search_directory(usage_dir[UseLang], command_str, filenames,
+ ARRAY_SIZE(filenames));
+
+ if (i == 0) {
+ if (UseLang != LANG_DEFAULT) {
+ i += search_directory(usage_dir[LANG_DEFAULT],
+ command_str, filenames, ARRAY_SIZE(filenames));
+
+ if (i > 0) {
+ pprintf(p, "No usage available in %s; "
+ "using %s instead.\n",
+ Language(UseLang),
+ Language(LANG_DEFAULT));
+ UseLang = LANG_DEFAULT;
+ }
+ }
+ }
+
+ if (i != 0) {
+ if (i == 1 ||
+ !strcmp(*filenames, command_str)) { // found it?
+ if (psend_file(p, usage_dir[UseLang], *filenames)) {
+ /*
+ * We should never reach this unless
+ * the file was just deleted.
+ */
+ pprintf(p, "Usage file %s could not be found! ",
+ *filenames);
+ pprintf(p, "Please inform an admin of this. "
+ "Thank you.\n");
+
+ /*
+ * No need to print 'system' usage -
+ * should never happen.
+ */
+ }
+
+ return;
+ }
+ }
+
+ /*
+ * Print the default 'system' usage files (which aren't much
+ * help!)
+ */
+ pprintf(p, "Usage: %s", command_list[lastCommandFound].comm_name);
+ parlen = strlen(command_list[command].param_string);
+
+ for (i = 0; i < parlen; i++) {
+ c = command_list[command].param_string[i];
+
+ if (isupper(c))
+ c = tolower(c);
+
+ switch (c) {
+ case 'w': // word
+ pprintf(p, " word");
+ break;
+ case 'o': // optional word
+ pprintf(p, " [word]");
+ break;
+ case 'd': // integer
+ pprintf(p, " integer");
+ break;
+ case 'p': // optional integer
+ pprintf(p, " [integer]");
+ break;
+ case 'i': // word or integer
+ pprintf(p, " {word, integer}");
+ break;
+ case 'n': // optional word or integer
+ pprintf(p, " [{word, integer}]");
+ break;
+ case 's': // string to end
+ pprintf(p, " string");
+ break;
+ case 't': // optional string to end
+ pprintf(p, " [string]");
+ break;
+ }
+ }
+
+ pprintf(p, "\nSee 'help %s' for a complete description.\n",
+ command_list[lastCommandFound].comm_name);
+}
+
+PUBLIC int
+process_command(int p, char *com_string, char **cmd)
+{
+ char *comm, *parameters;
+ int which_command, retval;
+ param_list params;
+ static char alias_string1[MAX_STRING_LENGTH * 4] = { '\0' };
+ static char alias_string2[MAX_STRING_LENGTH * 4] = { '\0' };
+
+#ifdef DEBUG
+ if (strcasecmp(parray[p].name, parray[p].login)) {
+ fprintf(stderr, "FICS: PROBLEM Name=%s, Login=%s\n",
+ parray[p].name,
+ parray[p].login);
+ }
+#endif
+
+ if (!com_string)
+ return COM_FAILED;
+
+#ifdef DEBUG
+ fprintf(stderr, "%s, %s, %d: >%s<\n", parray[p].name, parray[p].login,
+ parray[p].socket, com_string);
+#endif
+
+ alias_substitute(parray[p].alias_list, parray[p].numAlias, com_string,
+ alias_string1, sizeof(alias_string1));
+ alias_substitute(g_alias_list, 999, alias_string1, alias_string2,
+ sizeof(alias_string2));
+
+#ifdef DEBUG
+ if (strcmp(com_string, alias_string2) != 0) {
+ fprintf(stderr, "%s -alias-: >%s<\n", parray[p].name,
+ alias_string2);
+ }
+#endif
+
+ if ((retval = parse_command(alias_string2, &comm, &parameters)))
+ return retval;
+ if (is_move(comm))
+ return COM_ISMOVE;
+
+ stolower(comm); // All commands are case-insensitive
+ *cmd = comm;
+
+ if ((which_command = match_command(comm, p)) < 0)
+ return -which_command;
+ if (parray[p].adminLevel < command_list[which_command].adminLevel)
+ return COM_RIGHTS;
+
+ if ((retval = get_parameters(which_command, parameters, params)))
+ return retval;
+ return command_list[which_command].comm_func(p, params);
+}
+
+PRIVATE int
+process_login(int p, char *loginname)
+{
+ int problem = 1;
+
+ loginname = eatwhite(loginname);
+
+ if (!*loginname) {
+ /* do something in here? */;
+ } else {
+ char *loginnameii = xstrdup(loginname);
+
+ stolower(loginname);
+
+ if (!alphastring(loginname)) {
+ pprintf(p, "\nSorry, names can only consist of lower "
+ "and upper case letters. Try again.\n");
+ rfree(loginnameii);
+ loginnameii = NULL;
+ } else if (strlen(loginname) < 3) {
+ pprintf(p, "\nA name should be at least three "
+ "characters long! Try again.\n");
+ rfree(loginnameii);
+ loginnameii = NULL;
+ } else if (strlen(loginname) > 17) {
+ pprintf(p, "\nSorry, names may be at most 17 "
+ "characters long. Try again.\n");
+ rfree(loginnameii);
+ loginnameii = NULL;
+ } else if (in_list(p, L_BAN, loginnameii)) {
+ pprintf(p, "\nPlayer \"%s\" is banned.\n", loginname);
+ rfree(loginnameii);
+ return COM_LOGOUT;
+ } else if ((!in_list(p, L_ADMIN, loginnameii)) &&
+ (player_count(0) >= max_connections - 10)) {
+ psend_raw_file(p, mess_dir, MESS_FULL);
+ rfree(loginnameii);
+ return COM_LOGOUT;
+ } else {
+ problem = 0;
+
+ if (player_read(p, loginname)) {
+ rfree(parray[p].name);
+ parray[p].name = xstrdup(loginnameii);
+
+ if (in_list(p, L_FILTER,
+ dotQuad(parray[p].thisHost))) {
+ pprintf(p, "\nDue to abusive behavior, "
+ "nobody from your site may login.\n");
+ pprintf(p, "If you wish to use this "
+ "server please email %s\n",
+ reg_addr);
+ pprintf(p, "Include details of a "
+ "nick-name to be called here, "
+ "e-mail address and your real name."
+ "\n");
+ pprintf(p, "We will send a password "
+ "to you. Thanks.\n");
+ rfree(loginnameii);
+ return COM_LOGOUT;
+ }
+
+ if ((player_count(0)) >=
+ MAX(max_connections - 60, 200)) {
+ psend_raw_file(p, mess_dir,
+ MESS_FULL_UNREG);
+ rfree(loginnameii);
+ return COM_LOGOUT;
+ }
+
+ pprintf_noformat(p, "\n\"%s\" is not a "
+ "registered name. You may use this name "
+ "to play unrated games.\n(After logging in,"
+ "do \"help register\" for more info on "
+ "how to register.)\n\nPress return to "
+ "enter the FICS as \"%s\":",
+ parray[p].name,
+ parray[p].name);
+ } else {
+ pprintf_noformat(p, "\n\"%s\" is a registered "
+ "name. If it is yours, type the password.\n"
+ "If not, just hit return to try another "
+ "name.\n\npassword: ", parray[p].name);
+ }
+
+ parray[p].status = PLAYER_PASSWORD;
+ turn_echo_off(parray[p].socket);
+ rfree(loginnameii);
+ loginnameii = NULL; // XXX
+
+ if (strcasecmp(loginname, parray[p].name)) {
+ pprintf(p, "\nYou've got a bad name field in "
+ "your playerfile -- please report this to "
+ "an admin!\n");
+ return COM_LOGOUT;
+ }
+
+ if (parray[p].adminLevel != 0 &&
+ !in_list(p, L_ADMIN, parray[p].name)) {
+ pprintf(p, "\nYou've got a bad playerfile -- "
+ "please report this to an admin!\n");
+ pprintf(p, "Your handle is missing!");
+ pprintf(p, "Please log on as an unreg until "
+ "an admin can correct this.\n");
+ return COM_LOGOUT;
+ }
+
+ if (parray[p].registered &&
+ parray[p].fullName == NULL) {
+ pprintf(p, "\nYou've got a bad playerfile -- "
+ "please report this to an admin!\n");
+ pprintf(p, "Your FullName is missing!");
+ pprintf(p, "Please log on as an unreg until "
+ "an admin can correct this.\n");
+ return COM_LOGOUT;
+ }
+
+ if (parray[p].registered &&
+ parray[p].emailAddress == NULL) {
+ pprintf(p, "\nYou've got a bad playerfile -- "
+ "please report this to an admin!\n");
+ pprintf(p, "Your Email address is missing\n");
+ pprintf(p, "Please log on as an unreg until "
+ "an admin can correct this.\n");
+ return COM_LOGOUT;
+ }
+ }
+ }
+
+ if (problem) {
+ psend_raw_file(p, mess_dir, MESS_LOGIN);
+ pprintf(p, "login: ");
+ }
+
+ return 0;
+}
+
+PRIVATE void
+boot_out(int p, int p1)
+{
+ int fd;
+
+ pprintf(p, "\n **** %s is already logged in - kicking them out. ****\n",
+ parray[p].name);
+ pprintf(p1, "**** %s has arrived - you can't both be logged in. ****\n",
+ parray[p].name);
+
+ fd = parray[p1].socket;
+ process_disconnection(fd);
+ net_close_connection(fd);
+}
+
+PUBLIC void
+rscan_news(FILE *fp, int p, time_t lc)
+{
+ char count[10] = { '\0' };
+ char junk[MAX_LINE_SIZE] = { '\0' };
+ char *junkp = NULL;
+ const char *scan_fmt = "%" SCNd64 " " "%9s";
+ int64_t lval = 0;
+ time_t crtime = 0;
+
+ if (fgets(junk, sizeof junk, fp) == NULL ||
+ feof(fp))
+ return;
+
+ _Static_assert(ARRAY_SIZE(count) > 9, "Unexpected array size");
+
+ if (sscanf(junk, scan_fmt, &lval, count) != 2) {
+ warnx("%s: sscanf() error: too few items", __func__);
+ return;
+ }
+
+ crtime = lval;
+
+ if ((crtime - lc) < 0)
+ return;
+ else {
+ rscan_news(fp, p, lc);
+
+ junkp = junk;
+ junkp = nextword(junkp);
+ junkp = nextword(junkp);
+
+ pprintf(p, "%3s (%s) %s", count, fix_time(strltime(&crtime)),
+ junkp);
+ }
+}
+
+PRIVATE void
+check_news(int p, int admin)
+{
+ FILE *fp = NULL;
+ char count[10] = { '\0' };
+ char filename[MAX_FILENAME_SIZE] = { '\0' };
+ char junk[MAX_LINE_SIZE] = { '\0' };
+ char *junkp = NULL;
+ const char *v_scan_fmt = "%" SCNd64 " " "%9s";
+ int64_t lval = 0;
+ time_t crtime = 0;
+ time_t lc = player_lastconnect(p);
+
+ _Static_assert(ARRAY_SIZE(count) > 9, "Unexpected array size");
+
+ if (admin) {
+ msnprintf(filename, sizeof filename, "%s/newadminnews.index",
+ news_dir);
+
+ if ((fp = fopen(filename, "r")) == NULL) {
+ warn("%s: can't find admin news index (%s)", __func__,
+ filename);
+ return;
+ }
+
+ if (num_anews == -1) {
+ num_anews = count_lines(fp);
+ fclose(fp);
+ if ((fp = fopen(filename, "r")) == NULL) {
+ warn("%s: can't find admin news index (%s)",
+ __func__, filename);
+ return;
+ }
+ }
+
+ if (fgets(junk, sizeof junk, fp) == NULL) {
+ warnx("%s: fgets() error", __func__);
+ fclose(fp);
+ return;
+ } else if (sscanf(junk, v_scan_fmt, &lval, count) != 2) {
+ warnx("%s: sscanf() error", __func__);
+ fclose(fp);
+ return;
+ }
+
+ crtime = lval;
+
+ if ((crtime - lc) < 0) {
+ pprintf(p, "There are no new admin news items since "
+ "your last login.\n\n");
+ fclose(fp);
+ return;
+ } else {
+ pprintf(p, "Index of new admin news items:\n");
+ rscan_news(fp, p, lc);
+
+ junkp = junk;
+ junkp = nextword(junkp);
+ junkp = nextword(junkp);
+
+ pprintf(p, "%3s (%s) %s", count,
+ fix_time(strltime(&crtime)), junkp);
+ pprintf(p, "(\"anews %d\" will display the most recent "
+ "admin news file)\n", num_anews);
+ }
+ } else {
+ msnprintf(filename, sizeof filename, "%s/newnews.index",
+ news_dir);
+
+ if ((fp = fopen(filename, "r")) == NULL) {
+ warn("%s: can't find news index (%s)", __func__,
+ filename);
+ return;
+ }
+
+ if (num_news == -1) {
+ num_news = count_lines(fp);
+ fclose(fp);
+ if ((fp = fopen(filename, "r")) == NULL) {
+ warn("%s: can't find news index (%s)", __func__,
+ filename);
+ return;
+ }
+ }
+
+ if (fgets(junk, sizeof junk, fp) == NULL) {
+ warnx("%s: fgets() error", __func__);
+ fclose(fp);
+ return;
+ } else if (sscanf(junk, v_scan_fmt, &lval, count) != 2) {
+ warnx("%s: sscanf() error", __func__);
+ fclose(fp);
+ return;
+ }
+
+ crtime = lval;
+
+ if ((crtime - lc) < 0) {
+ pprintf(p, "There are no new news items since your "
+ "last login (%s).\n", strltime(&lc));
+ fclose(fp);
+ return;
+ } else {
+ pprintf(p, "Index of new news items:\n");
+ rscan_news(fp, p, lc);
+
+ junkp = junk;
+ junkp = nextword(junkp);
+ junkp = nextword(junkp);
+
+ pprintf(p, "%3s (%s) %s", count,
+ fix_time(strltime(&crtime)), junkp);
+ pprintf(p, "(\"news %d\" will display the most recent "
+ "admin news file)\n", num_news);
+ }
+ }
+
+ fclose(fp);
+}
+
+PRIVATE int
+process_password(int p, char *password)
+{
+ char salt[FICS_SALT_SIZE];
+ int dummy; // to hold a return value
+ int fd;
+ int messnum;
+ int p1;
+ unsigned int fromHost;
+
+ turn_echo_on(parray[p].socket);
+
+ if (parray[p].passwd && parray[p].registered) {
+ strncpy(salt, &(parray[p].passwd[0]), sizeof salt - 1);
+ salt[sizeof salt - 1] = '\0';
+
+ if (strcmp(crypt(password, salt), parray[p].passwd)) {
+ fd = parray[p].socket;
+ fromHost = parray[p].thisHost;
+
+ player_clear(p);
+ parray[p].logon_time = parray[p].last_command_time =
+ time(NULL);
+ parray[p].status = PLAYER_LOGIN;
+ parray[p].socket = fd;
+ parray[p].thisHost = fromHost;
+
+ if (*password) {
+ pprintf(p, "\n\n**** Invalid password! ****"
+ "\n\n");
+ }
+ return COM_LOGOUT;
+ }
+ }
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].name != NULL) {
+ if (!strcasecmp(parray[p].name, parray[p1].name) &&
+ p != p1) {
+ if (parray[p].registered == 0) {
+ pprintf(p, "\n*** Sorry %s is already "
+ "logged in ***\n", parray[p].name);
+ return COM_LOGOUT;
+ }
+ boot_out(p, p1);
+ }
+ }
+ }
+
+ if (parray[p].adminLevel > 0) {
+ psend_raw_file(p, mess_dir, MESS_ADMOTD);
+ } else {
+ psend_raw_file(p, mess_dir, MESS_MOTD);
+ }
+
+ if (!parray[p].passwd && parray[p].registered) {
+ pprintf(p, "\n*** You have no password. Please set one with "
+ "the password command.");
+ }
+ if (!parray[p].registered)
+ psend_raw_file(p, mess_dir, MESS_UNREGISTERED);
+
+ parray[p].status = PLAYER_PROMPT;
+ player_write_login(p);
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (p1 == p)
+ continue;
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ if (!parray[p1].i_login)
+ continue;
+
+ if (parray[p1].adminLevel > 0) {
+ pprintf_prompt(p1, "\n[%s (%s: %s) has connected.]\n",
+ parray[p].name,
+ (parray[p].registered ? "R" : "U"),
+ dotQuad(parray[p].thisHost));
+ } else {
+ pprintf_prompt(p1, "\n[%s has connected.]\n",
+ parray[p].name);
+ }
+ }
+
+ parray[p].num_comments = player_num_comments(p);
+ messnum = player_num_messages(p);
+
+ /*
+ * Don't send unreg any news. When you change this, feel free
+ * to put all the news junk in one source file. No reason for
+ * identical code in 'command.c' and 'comproc.c'.
+ */
+
+ if (parray[p].registered) {
+ check_news(p, 0);
+
+ if (parray[p].adminLevel > 0) {
+ pprintf(p, "\n");
+ check_news(p, 1);
+ }
+ }
+
+ if (messnum) {
+ pprintf(p, "\nYou have %d messages.\nUse \"messages\" to "
+ "display them, or \"clearmessages\" to remove them.\n",
+ messnum);
+ }
+
+ player_notify_present(p);
+ player_notify(p, "arrived", "arrival");
+ showstored(p);
+
+ if (parray[p].registered &&
+ parray[p].lastHost != 0 &&
+ parray[p].lastHost != parray[p].thisHost) {
+ pprintf(p, "\nPlayer %s: Last login: %s ", parray[p].name,
+ dotQuad(parray[p].lastHost));
+ pprintf(p, "This login: %s", dotQuad(parray[p].thisHost));
+ }
+
+ parray[p].lastHost = parray[p].thisHost;
+
+ if (parray[p].registered && !parray[p].timeOfReg)
+ parray[p].timeOfReg = time(NULL);
+
+ parray[p].logon_time = parray[p].last_command_time = time(NULL);
+
+ dummy = check_and_print_shutdown(p); // Tells the user if we are
+ // going to shutdown
+
+ // XXX: unused
+ (void) dummy;
+
+ pprintf(p, "\n%s", parray[p].prompt);
+ return 0;
+}
+
+PRIVATE int
+process_prompt(int p, char *command)
+{
+ char *cmd = "";
+ int i, len;
+ int retval;
+
+ command = eattailwhite(eatwhite(command));
+
+ if (!*command) {
+ pprintf(p, "%s", parray[p].prompt);
+ return COM_OK;
+ }
+
+ retval = process_command(p, command, &cmd);
+
+ switch (retval) {
+ case COM_OK:
+ retval = COM_OK;
+ pprintf(p, "%s", parray[p].prompt);
+ break;
+ case COM_OK_NOPROMPT:
+ retval = COM_OK;
+ break;
+ case COM_ISMOVE:
+ retval = COM_OK;
+
+#ifdef TIMESEAL
+ if (parray[p].game >= 0 &&
+ garray[parray[p].game].status == GAME_ACTIVE &&
+ parray[p].side == garray[parray[p].game].game_state.onMove &&
+ garray[parray[p].game].flag_pending != FLAG_NONE) {
+ ExecuteFlagCmd(parray[p].game,
+ con[parray[p].socket].time);
+ }
+#endif
+
+ process_move(p, command);
+ pprintf(p, "%s", parray[p].prompt);
+ break;
+ case COM_RIGHTS:
+ pprintf(p, "%s: Insufficient rights.\n", cmd);
+ pprintf(p, "%s", parray[p].prompt);
+ retval = COM_OK;
+ break;
+ case COM_AMBIGUOUS:
+ i = 0;
+ len = strlen(cmd);
+
+ pprintf(p, "Ambiguous command. Matches:");
+
+ while (command_list[i].comm_name) {
+ if (!strncmp(command_list[i].comm_name, cmd, len) &&
+ parray[p].adminLevel >= command_list[i].adminLevel)
+ pprintf(p, " %s", command_list[i].comm_name);
+ i++;
+ }
+
+ pprintf(p, "\n%s", parray[p].prompt);
+ retval = COM_OK;
+ break;
+ case COM_BADPARAMETERS:
+ printusage(p, command_list[lastCommandFound].comm_name);
+ pprintf(p, "%s", parray[p].prompt);
+ retval = COM_OK;
+ break;
+ case COM_FAILED:
+ pprintf(p, "%s: Command has failed.\n", cmd);
+ retval = COM_OK;
+ pprintf(p, "%s", parray[p].prompt);
+ break;
+ case COM_BADCOMMAND:
+ pprintf(p, "%s: Command not found.\n", cmd);
+ retval = COM_OK;
+ pprintf(p, "%s", parray[p].prompt);
+ break;
+ case COM_LOGOUT:
+ retval = COM_LOGOUT;
+ break;
+ }
+
+ return retval;
+}
+
+/* Return 1 to disconnect */
+PUBLIC int
+process_input(int fd, char *com_string)
+{
+ int p = player_find(fd);
+ int retval = 0;
+
+ if (p < 0) {
+ fprintf(stderr, "FICS: Input from a player not in array!\n");
+ return -1;
+ } else if (com_string == NULL) {
+ fprintf(stderr, "FICS: Command string is NULL!\n");
+ return -1;
+ }
+
+ commanding_player = p;
+ parray[p].last_command_time = time(NULL);
+
+ switch (parray[p].status) {
+ case PLAYER_EMPTY:
+ fprintf(stderr, "FICS: Command from an empty player!\n");
+ break;
+ case PLAYER_NEW:
+ fprintf(stderr, "FICS: Command from a new player!\n");
+ break;
+ case PLAYER_INQUEUE:
+ // Ignore input from player in queue
+ break;
+ case PLAYER_LOGIN:
+ retval = process_login(p, com_string);
+
+ if (retval == COM_LOGOUT) { // && com_string != NULL
+ fprintf(stderr, "%s tried to log in and failed.\n",
+ com_string);
+ }
+
+ break;
+ case PLAYER_PASSWORD:
+ retval = process_password(p, com_string);
+ break;
+ case PLAYER_PROMPT:
+ parray[p].busy[0] = '\0';
+
+ // added this to stop buggy admin levels; shane
+ if (parray[p].adminLevel < 10)
+ parray[p].adminLevel = 0;
+
+ retval = process_prompt(p, com_string);
+ break;
+ }
+
+ commanding_player = -1;
+ return retval;
+}
+
+PUBLIC int
+process_new_connection(int fd, unsigned int fromHost)
+{
+ int p = player_new();
+
+ parray[p].status = PLAYER_LOGIN;
+ parray[p].socket = fd;
+ parray[p].thisHost = fromHost;
+ parray[p].logon_time = time(NULL);
+
+ psend_raw_file(p, mess_dir, MESS_WELCOME);
+ pprintf(p, "Head admin : %s Complaints to : %s\n",
+ hadmin_handle,
+ hadmin_email);
+ pprintf(p, "Server location: %s Server version : %s\n", fics_hostname,
+ VERS_NUM);
+ psend_raw_file(p, mess_dir, MESS_LOGIN);
+ pprintf(p, "login: ");
+ return 0;
+}
+
+PUBLIC int
+process_disconnection(int fd)
+{
+ int p = player_find(fd);
+
+ if (p < 0) {
+ fprintf(stderr, "FICS: Disconnect from a player not in array!"
+ "\n");
+ return -1;
+ }
+
+ if (parray[p].game >= 0 && garray[parray[p].game].status ==
+ GAME_EXAMINE)
+ pcommand(p, "unexamine");
+ if (parray[p].game >= 0 && in_list(p, L_ABUSER, parray[p].name))
+ pcommand(p, "resign");
+
+ if (parray[p].status == PLAYER_PROMPT) {
+ for (int p1 = 0; p1 < p_num; p1++) {
+ if (p1 == p)
+ continue;
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ if (!parray[p1].i_login)
+ continue;
+ pprintf_prompt(p1, "\n[%s has disconnected.]\n",
+ parray[p].name);
+ }
+
+ player_notify(p, "departed", "departure");
+ player_notify_departure(p);
+ player_write_logout(p);
+
+ if (parray[p].registered) {
+ parray[p].totalTime += (time(NULL) -
+ parray[p].logon_time);
+ player_save(p);
+ } else { // delete unreg history file
+ char fname[MAX_FILENAME_SIZE] = { '\0' };
+
+ (void) snprintf(fname, sizeof fname,
+ "%s/player_data/%c/%s.games",
+ stats_dir,
+ parray[p].login[0],
+ parray[p].login);
+ unlink(fname);
+ }
+ }
+
+ player_remove(p);
+ return 0;
+}
+
+/* Called every few seconds */
+PUBLIC int
+process_heartbeat(int *fd)
+{
+ time_t now = time(NULL);
+ time_t time_since_last;
+ static time_t last_comfile = 0;
+ static time_t last_space = 0;
+ static time_t lastcalled = 0;
+
+ if (lastcalled == 0)
+ time_since_last = 0;
+ else
+ time_since_last = now - lastcalled;
+ lastcalled = now;
+
+ // XXX: unused
+ (void) time_since_last;
+
+ /*
+ * Check for timed out connections
+ */
+
+ for (int p = 0; p < p_num; p++) {
+ if ((parray[p].status == PLAYER_LOGIN ||
+ parray[p].status == PLAYER_PASSWORD) &&
+ player_idle(p) > MAX_LOGIN_IDLE) {
+ pprintf(p, "\n**** LOGIN TIMEOUT ****\n");
+ *fd = parray[p].socket;
+ return COM_LOGOUT;
+ }
+
+ if (parray[p].status == PLAYER_PROMPT &&
+ player_idle(p) > MAX_IDLE_TIME &&
+ parray[p].adminLevel == 0 &&
+ !in_list(p, L_TD, parray[p].name)) {
+ pprintf(p, "\n**** Auto-logout because you were idle "
+ "more than one hour. ****\n");
+ *fd = parray[p].socket;
+ return COM_LOGOUT;
+ }
+ }
+
+ /*
+ * Check for the communication file from mail updates every 10
+ * minutes. (That is probably too often, but who cares.)
+ */
+
+ if (MailGameResult) {
+ if (last_comfile == 0) {
+ last_comfile = now;
+ } else {
+ if ((last_comfile + 10 * 60) < now)
+ last_comfile = now;
+ }
+ }
+
+ if (last_space == 0) {
+ last_space = now;
+ } else {
+ if ((last_space + 60) < now) { // Check the disk space every
+ // minute
+ last_space = now;
+
+ if (available_space() < 1000000) {
+ server_shutdown(60, " **** Disk space is "
+ "dangerously low!!! ****\n");
+ }
+ }
+ }
+
+ ShutHeartBeat();
+ return COM_OK;
+}
+
+PUBLIC void
+commands_init(void)
+{
+ FILE *fp, *afp;
+ char fname[MAX_FILENAME_SIZE];
+ int fd[2];
+ int i = 0;
+
+ fp = afp = NULL;
+ snprintf(fname, sizeof fname, "%s/commands", comhelp_dir);
+
+ if ((fd[0] = open(fname, g_open_flags[1], g_open_modes)) < 0) {
+ warn("%s: open: %s", __func__, fname);
+ return;
+ } else if ((fp = fdopen(fd[0], "w")) == NULL) {
+ warn("%s: could not write commands help file (%s)", __func__,
+ fname);
+ close(fd[0]);
+ return;
+ }
+
+ snprintf(fname, sizeof fname, "%s/admin_commands", adhelp_dir);
+
+ if ((fd[1] = open(fname, g_open_flags[1], g_open_modes)) < 0) {
+ warn("%s: open: %s", __func__, fname);
+ goto clean_up;
+ } else if ((afp = fdopen(fd[1], "w")) == NULL) {
+ warn("%s: could not write admin_commands help file (%s)",
+ __func__, fname);
+ close(fd[1]);
+ goto clean_up;
+ }
+
+ while (command_list[i].comm_name) {
+ if (command_list[i].adminLevel >= ADMIN_ADMIN)
+ fprintf(afp, "%s\n", command_list[i].comm_name);
+ else
+ fprintf(fp, "%s\n", command_list[i].comm_name);
+ i++;
+ }
+
+ clean_up:
+ if (fp)
+ fclose(fp);
+ if (afp)
+ fclose(afp);
+}
+
+/* Need to save rated games */
+PUBLIC void
+TerminateCleanup(void)
+{
+ for (int g = 0; g < g_num; g++) {
+ if (garray[g].status != GAME_ACTIVE)
+ continue;
+ if (garray[g].rated)
+ game_ended(g, WHITE, END_ADJOURN);
+ }
+
+ for (int p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].status == PLAYER_EMPTY)
+ continue;
+
+ pprintf(p1, "\n **** Server shutting down immediately. "
+ "****\n\n");
+
+ if (parray[p1].status != PLAYER_PROMPT) {
+ close(parray[p1].socket);
+ } else {
+ pprintf(p1, "Logging you out.\n");
+ psend_raw_file(p1, mess_dir, MESS_LOGOUT);
+ player_write_logout(p1);
+ if (parray[p1].registered) {
+ parray[p1].totalTime +=
+ (time(NULL) - parray[p1].logon_time);
+ }
+ player_save(p1);
+ }
+ }
+}
diff --git a/FICS/command.h b/FICS/command.h
new file mode 100644
index 0000000..f7ca75f
--- /dev/null
+++ b/FICS/command.h
@@ -0,0 +1,148 @@
+/* command.h
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 23/12/19 Cleaned up the file
+ Markus Uhlin 24/04/07 Added missing parameter lists
+*/
+
+#ifndef _COMMAND_H
+#define _COMMAND_H
+
+#include "variable.h"
+#include "stdinclude.h"
+
+/*
+ * Maximum length of a login name
+ */
+#define MAX_LOGIN_NAME 20
+
+/*
+ * Maximum number of parameters per command
+ */
+#define MAXNUMPARAMS 10
+
+/*
+ * Maximum string length of a single command word
+ */
+#define MAX_COM_LENGTH 50
+
+/*
+ * Maximum string length of the whole command line
+ */
+#define MAX_STRING_LENGTH 1024
+
+#define COM_OK 0
+#define COM_FAILED 1
+#define COM_ISMOVE 2
+#define COM_AMBIGUOUS 3
+#define COM_BADPARAMETERS 4
+#define COM_BADCOMMAND 5
+#define COM_LOGOUT 6
+#define COM_FLUSHINPUT 7
+#define COM_RIGHTS 8
+#define COM_OK_NOPROMPT 9
+
+#define ADMIN_USER 0
+#define ADMIN_ADMIN 10
+#define ADMIN_MASTER 20
+#define ADMIN_DEMIGOD 60
+#define ADMIN_GOD 100
+
+#define TYPE_NULL 0
+#define TYPE_WORD 1
+#define TYPE_STRING 2
+#define TYPE_INT 3
+typedef struct u_parameter {
+ int type;
+
+ union {
+ char *word;
+ char *string;
+ int integer;
+ } val;
+} parameter;
+
+typedef parameter param_list[MAXNUMPARAMS];
+
+typedef struct s_command_type {
+ char *comm_name;
+ char *param_string;
+ int (*comm_func)(int, param_list);
+ int adminLevel;
+} command_type;
+
+typedef struct s_alias_type {
+ char *comm_name;
+ char *alias;
+} alias_type;
+
+typedef struct {
+ char com[MAX_STRING_LENGTH];
+} comstr_t;
+
+extern char *adhelp_dir;
+extern char *adj_dir;
+extern char *board_dir;
+extern char *comhelp_dir;
+extern char *config_dir;
+extern char *def_prompt;
+extern char *help_dir[NUM_LANGS];
+extern char *hist_dir;
+extern char *index_dir;
+extern char *info_dir;
+extern char *journal_dir;
+extern char *lists_dir;
+extern char *mess_dir;
+extern char *news_dir;
+extern char *player_dir;
+extern char *source_dir;
+extern char *stats_dir;
+extern char *usage_dir[NUM_LANGS];
+extern char *uscf_dir;
+
+extern char *hadmin_handle;
+
+extern char fics_hostname[81];
+extern int MailGameResult;
+extern int game_high;
+extern int player_high;
+extern time_t startuptime;
+
+/*
+ * The player whose command you're in
+ */
+extern int commanding_player;
+
+extern int alias_lookup(char *, alias_type *, int);
+extern int process_command(int, char *, char **);
+extern int process_disconnection(int);
+extern int process_heartbeat(int *);
+extern int process_input(int, char *);
+extern int process_new_connection(int, unsigned int);
+extern void TerminateCleanup(void);
+extern void commands_init(void);
+
+/* From variable.c */
+extern int com_partner(int, param_list);
+extern int com_variables(int, param_list);
+
+#endif /* _COMMAND_H */
diff --git a/FICS/command_list.h b/FICS/command_list.h
new file mode 100644
index 0000000..9d64e76
--- /dev/null
+++ b/FICS/command_list.h
@@ -0,0 +1,285 @@
+/* command_list.h
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 23/12/10 Cleaned up the file a bit
+ Markus Uhlin 24/07/08 Reformatted and sorted the lists
+ Markus Uhlin 24/07/13 Added 'iset' to the command list
+ Markus Uhlin 24/11/24 Added 'sought' to the command list
+*/
+
+#ifndef _COMMAND_LIST_H
+#define _COMMAND_LIST_H
+
+#include "adminproc.h"
+#include "comproc.h"
+#include "eco.h"
+#include "gameproc.h"
+#include "iset.h"
+#include "lists.h"
+#include "matchproc.h"
+#include "obsproc.h"
+#include "playerdb.h"
+#include "rating_conv.h"
+#include "ratings.h"
+#include "shutdown.h"
+#include "sought.h"
+#include "talkproc.h"
+
+extern command_type command_list[];
+extern alias_type g_alias_list[];
+
+/*
+ Parameter string format
+ w - a word
+ o - an optional word
+ d - integer
+ p - optional integer
+ i - word or integer
+ n - optional word or integer
+ s - string to end
+ t - optional string to end
+
+ If the parameter option is given in lower case then the parameter is
+ converted to lower case before being passsed to the function. If it is
+ in upper case, then the parameter is passed as typed.
+ */
+/* Try to keep this list in alpha order, that is the way it is shown to
+ * the 'help commands' command.
+ */
+ /* Name Options Functions Security */
+PUBLIC command_type command_list[] = {
+
+ {"abort", "", com_abort, ADMIN_USER},
+ {"accept", "n", com_accept, ADMIN_USER},
+ {"addlist", "ww", com_addlist, ADMIN_USER},
+ {"adjourn", "", com_adjourn, ADMIN_USER},
+ {"alias", "oT", com_alias, ADMIN_USER},
+ {"allobservers", "n", com_allobservers, ADMIN_USER},
+ {"assess", "oo", com_assess, ADMIN_USER},
+ {"backward", "p", com_backward, ADMIN_USER},
+ {"bell", "", com_bell, ADMIN_USER},
+ {"best", "o", com_best, ADMIN_USER},
+ {"boards", "o", com_boards, ADMIN_USER},
+ {"clearmessages", "n", com_clearmessages, ADMIN_USER},
+ {"convert_bcf", "d", com_CONVERT_BCF, ADMIN_USER},
+ {"convert_elo", "d", com_CONVERT_ELO, ADMIN_USER},
+ {"convert_uscf", "d", com_CONVERT_USCF, ADMIN_USER},
+ {"cshout", "S", com_cshout, ADMIN_USER},
+ {"date", "", com_date, ADMIN_USER},
+ {"decline", "n", com_decline, ADMIN_USER},
+ {"draw", "", com_draw, ADMIN_USER},
+ {"eco", "n", com_eco, ADMIN_USER},
+ {"examine", "on", com_examine, ADMIN_USER},
+ {"finger", "o", com_stats, ADMIN_USER},
+ {"flag", "", com_flag, ADMIN_USER},
+ {"flip", "", com_flip, ADMIN_USER},
+ {"forward", "p", com_forward, ADMIN_USER},
+ {"games", "o", com_games, ADMIN_USER},
+ {"getpi", "w", com_getpi, ADMIN_USER},
+ {"goboard", "w", com_goboard, ADMIN_USER},
+ {"gonum", "d", com_gonum, ADMIN_USER},
+ {"handles", "w", com_handles, ADMIN_USER},
+ {"hbest", "o", com_hbest, ADMIN_USER},
+ {"help", "o", com_help, ADMIN_USER},
+ {"history", "o", com_history, ADMIN_USER},
+ {"hrank", "oo", com_hrank, ADMIN_USER},
+ {"inchannel", "n", com_inchannel, ADMIN_USER},
+ {"index", "o", com_index, ADMIN_USER},
+ {"info", "", com_info, ADMIN_USER},
+ {"iset", "ww", com_iset, ADMIN_USER},
+ {"it", "T", com_it, ADMIN_USER},
+ {"journal", "o", com_journal, ADMIN_USER},
+ {"jsave", "wwi", com_jsave, ADMIN_USER},
+ {"kibitz", "S", com_kibitz, ADMIN_USER},
+ {"limits", "", com_limits, ADMIN_USER},
+ {"llogons", "", com_llogons, ADMIN_USER},
+ {"logons", "o", com_logons, ADMIN_USER},
+ {"mailhelp", "o", com_mailhelp, ADMIN_USER},
+ {"mailmess", "", com_mailmess, ADMIN_USER},
+ {"mailmoves", "n", com_mailmoves, ADMIN_USER},
+ {"mailoldmoves", "o", com_mailoldmoves, ADMIN_USER},
+ {"mailsource", "o", com_mailsource, ADMIN_USER},
+ {"mailstored", "wi", com_mailstored, ADMIN_USER},
+ {"match", "wt", com_match, ADMIN_USER},
+ {"messages", "nT", com_messages, ADMIN_USER},
+ {"mexamine", "w", com_mexamine, ADMIN_USER},
+ {"moretime", "d", com_moretime, ADMIN_USER},
+ {"moves", "n", com_moves, ADMIN_USER},
+ {"news", "o", com_news, ADMIN_USER},
+ {"next", "", com_more, ADMIN_USER},
+ {"observe", "n", com_observe, ADMIN_USER},
+ {"oldmoves", "o", com_oldmoves, ADMIN_USER},
+ {"open", "", com_open, ADMIN_USER},
+ {"partner", "o", com_partner, ADMIN_USER},
+ {"password", "WW", com_password, ADMIN_USER},
+ {"pause", "", com_pause, ADMIN_USER},
+ {"pending", "", com_pending, ADMIN_USER},
+ {"prefresh", "", com_prefresh, ADMIN_USER},
+ {"promote", "w", com_promote, ADMIN_USER},
+ {"ptell", "S", com_ptell, ADMIN_USER},
+ {"qtell", "iS", com_qtell, ADMIN_USER},
+ {"quit", "", com_quit, ADMIN_USER},
+ {"rank", "oo", com_rank, ADMIN_USER},
+ {"refresh", "n", com_refresh, ADMIN_USER},
+ {"resign", "o", com_resign, ADMIN_USER},
+ {"revert", "", com_revert, ADMIN_USER},
+ {"say", "S", com_say, ADMIN_USER},
+ {"servers", "", com_servers, ADMIN_USER},
+ {"set", "wT", com_set, ADMIN_USER},
+ {"shout", "T", com_shout, ADMIN_USER},
+ {"showlist", "o", com_showlist, ADMIN_USER},
+ {"simabort", "", com_simabort, ADMIN_USER},
+ {"simadjourn", "", com_simadjourn, ADMIN_USER},
+ {"simallabort", "", com_simallabort, ADMIN_USER},
+ {"simalladjourn", "", com_simalladjourn, ADMIN_USER},
+ {"simgames", "o", com_simgames, ADMIN_USER},
+ {"simmatch", "w", com_simmatch, ADMIN_USER},
+ {"simnext", "", com_simnext, ADMIN_USER},
+ {"simopen", "", com_simopen, ADMIN_USER},
+ {"simpass", "", com_simpass, ADMIN_USER},
+ {"simprev", "", com_simprev, ADMIN_USER},
+ {"smoves", "wi", com_smoves, ADMIN_USER},
+ {"sought", "o", com_sought, ADMIN_USER},
+ {"sposition", "ww", com_sposition, ADMIN_USER},
+ {"statistics", "", com_statistics, ADMIN_USER},
+ {"stored", "o", com_stored, ADMIN_USER},
+ {"style", "d", com_style, ADMIN_USER},
+ {"sublist", "ww", com_sublist, ADMIN_USER},
+ {"switch", "", com_switch, ADMIN_USER},
+ {"takeback", "p", com_takeback, ADMIN_USER},
+ {"tell", "nS", com_tell, ADMIN_USER},
+ {"time", "n", com_time, ADMIN_USER},
+ {"unalias", "w", com_unalias, ADMIN_USER},
+ {"unexamine", "", com_unexamine, ADMIN_USER},
+ {"unobserve", "n", com_unobserve, ADMIN_USER},
+ {"unpause", "", com_unpause, ADMIN_USER},
+ {"uptime", "", com_uptime, ADMIN_USER},
+ {"uscf", "o", com_uscf, ADMIN_USER},
+ {"variables", "o", com_variables, ADMIN_USER},
+ {"whenshut", "", com_whenshut, ADMIN_USER},
+ {"whisper", "S", com_whisper, ADMIN_USER},
+ {"who", "T", com_who, ADMIN_USER},
+ {"withdraw", "n", com_withdraw, ADMIN_USER},
+ {"xtell", "wS", com_xtell, ADMIN_USER},
+ {"znotify", "", com_znotify, ADMIN_USER},
+
+ {"addcomment", "wS", com_addcomment, ADMIN_ADMIN},
+ {"addplayer", "WWS", com_addplayer, ADMIN_ADMIN},
+ {"adjudicate", "www", com_adjudicate, ADMIN_ADMIN},
+ {"admin", "", com_admin, ADMIN_ADMIN},
+ {"ahelp", "o", com_adhelp, ADMIN_ADMIN},
+ {"anews", "o", com_anews, ADMIN_ADMIN},
+ {"announce", "S", com_announce, ADMIN_ADMIN},
+ {"annunreg", "S", com_annunreg, ADMIN_ADMIN},
+ {"asetadmin", "wd", com_asetadmin, ADMIN_ADMIN},
+ {"asetblitz", "wdpppp", com_asetblitz, ADMIN_ADMIN},
+ {"asetemail", "wO", com_asetemail, ADMIN_ADMIN},
+ {"asethandle", "WW", com_asethandle, ADMIN_ADMIN},
+ {"asetlight", "wdpppp", com_asetlight, ADMIN_ADMIN},
+ {"asetmaxplayers", "p", com_asetmaxplayer, ADMIN_ADMIN},
+ {"asetpasswd", "wW", com_asetpasswd, ADMIN_ADMIN},
+ {"asetrealname", "wT", com_asetrealname, ADMIN_ADMIN},
+ {"asetstd", "wdpppp", com_asetstd, ADMIN_ADMIN},
+ {"asetv", "wS", com_asetv, ADMIN_ADMIN},
+ {"asetwild", "wdpppp", com_asetwild, ADMIN_ADMIN},
+ {"canewsf", "dS", com_canewsf, ADMIN_ADMIN},
+ {"canewsi", "S", com_canewsi, ADMIN_ADMIN},
+ {"chkgame", "i", com_checkGAME, ADMIN_ADMIN},
+ {"chkip", "w", com_checkIP, ADMIN_ADMIN},
+ {"chkpl", "w", com_checkPLAYER, ADMIN_ADMIN},
+ {"chksc", "d", com_checkSOCKET, ADMIN_ADMIN},
+ {"chkts", "", com_checkTIMESEAL, ADMIN_ADMIN},
+ {"cmuzzle", "o", com_cmuzzle, ADMIN_ADMIN},
+ {"cnewsf", "dS", com_cnewsf, ADMIN_ADMIN},
+ {"cnewsi", "S", com_cnewsi, ADMIN_ADMIN},
+ {"muzzle", "o", com_muzzle, ADMIN_ADMIN},
+ {"nuke", "w", com_nuke, ADMIN_ADMIN},
+ {"pose", "wS", com_pose, ADMIN_GOD},
+ {"quota", "p", com_quota, ADMIN_ADMIN},
+ {"raisedead", "WO", com_raisedead, ADMIN_ADMIN},
+ {"remplayer", "w", com_remplayer, ADMIN_ADMIN},
+ {"rerank", "w", com_fixrank, ADMIN_ADMIN},
+ {"showcomment", "w", com_showcomment, ADMIN_ADMIN},
+ {"shutdown", "oT", com_shutdown, ADMIN_ADMIN},
+ {"summon", "w", com_summon, ADMIN_ADMIN},
+
+ {NULL, NULL, NULL, ADMIN_USER}
+};
+
+PUBLIC alias_type g_alias_list[] = {
+ {"a", "accept"},
+ {"adhelp", "ahelp"},
+ {"bye", "quit"},
+ {"ch", "channel"},
+ {"cls", "help cls"},
+ {"comment", "addcomment"},
+ {"exit", "quit"},
+ {"f", "finger"},
+ {"g", "games"},
+ {"go", "goboard"},
+ {"goto", "goboard"},
+ {"h", "help"},
+ {"i", "it"},
+ {"in", "inchannel"},
+ {"logout", "quit"},
+ {"m", "match"},
+ {"ma", "match"},
+ {"more", "next"},
+ {"n", "next"},
+ {"o", "observe"},
+ {"p", "who a"},
+ {"pl", "who a"},
+ {"players", "who a"},
+ {"r", "refresh"},
+ {"re", "refresh"}, // So r/re doesn't resign!
+ {"saa", "simallabort"},
+ {"saab", "simallaabort"},
+ {"saadj", "simalladjourn"},
+ {"sab", "simabort"},
+ {"sadj", "simadjourn"},
+ {"sh", "shout"},
+ {"sn", "simnext"},
+ {"sp", "simprev"},
+ {"t", "tell"},
+ {"vars", "variables"},
+ {"w", "who"},
+ {"znotl", "znotify"},
+
+ {",", "tell ,"},
+ {".", "tell ."},
+ {"`", "tell ."},
+
+ {":", "it"},
+ {"I", "it"},
+
+ {"!", "shout"},
+ {"#", "whisper"},
+ {"*", "kibitz"},
+ {"+", "addlist"},
+ {"-", "sublist"},
+ {"=", "showlist"},
+ {"?", "help"},
+
+ {NULL, NULL}
+};
+
+#endif /* _COMMAND_LIST_H */
diff --git a/FICS/common.h b/FICS/common.h
new file mode 100644
index 0000000..66823ea
--- /dev/null
+++ b/FICS/common.h
@@ -0,0 +1,73 @@
+/*
+ * File: common.h
+ * Copyright 1993, Richard V. Nash
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 24/01/03 Added/deleted macros and
+ redefined ASSERT().
+ Markus Uhlin 24/01/04 Added PRINTFLIKE().
+ Markus Uhlin 24/04/02 Defined:
+ UNUSED_PARAM() and
+ UNUSED_VAR().
+ Markus Uhlin 24/11/20 Added FICS begin/end macros.
+*/
+
+#ifndef _COMMON_H
+#define _COMMON_H
+
+#include "assert_error.h"
+#include "legal.h"
+#include "vers.h"
+
+#define PUBLIC
+#define PRIVATE static
+
+#ifndef NULL
+#define NULL ((void *)0)
+#endif
+
+#define ARRAY_SIZE(_a) (sizeof((_a)) / sizeof((_a)[0]))
+
+#ifdef __GNUC__
+#define PRINTFLIKE(arg_no) __attribute__((format(printf, arg_no, arg_no + 1)))
+#else
+#define PRINTFLIKE(arg_no)
+#endif
+
+#define UNUSED_PARAM(p) ((void) (p))
+#define UNUSED_VAR(v) ((void) (v))
+
+#ifdef DEBUG
+#define ASSERT(expression) \
+ ((void) ((expression)||_assert_error(__FILE__, __LINE__)))
+#else
+#define ASSERT(expression) ((void) 0)
+#endif
+
+#ifdef __cplusplus
+#define __FICS_BEGIN_DECLS extern "C" {
+#define __FICS_END_DECLS }
+#else
+#define __FICS_BEGIN_DECLS
+#define __FICS_END_DECLS
+#endif
+
+#endif /* _COMMON_H */
diff --git a/FICS/comproc.c b/FICS/comproc.c
new file mode 100644
index 0000000..b2504fd
--- /dev/null
+++ b/FICS/comproc.c
@@ -0,0 +1,2104 @@
+/* comproc.c
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ foxbat 95/03/11 Added filters in cmatch
+ Markus Uhlin 23/12/09 Fixed implicit ints
+ Markus Uhlin 23/12/17 Fixed compiler warnings
+ Markus Uhlin 23/12/17 Usage of 'time_t'
+ Markus Uhlin 23/12/17 Reformatted com_stats()
+ Markus Uhlin 23/12/19 Usage of 'time_t'
+ Markus Uhlin 23/12/23 Fixed crypt()
+ Markus Uhlin 24/01/03 Added usage of ARRAY_SIZE() and
+ reformatted functions.
+ Markus Uhlin 24/01/06 Fixed potentially insecure
+ format strings.
+ Markus Uhlin 24/03/23 Size-bounded string handling
+ plus truncation checks.
+ Markus Uhlin 24/11/19 Improved com_news().
+ plogins: fscanf:
+ added width specification
+ Markus Uhlin 25/03/08 Calc string length once
+ Markus Uhlin 25/03/11 Fixed possibly uninitialized
+ value 'rat' in who_terse().
+ Markus Uhlin 25/03/16 Fixed use of 32-bit 'time_t'.
+ Markus Uhlin 25/03/16 Fixed untrusted array index.
+ Markus Uhlin 25/03/25 com_unalias: fixed overflowed
+ array index read/write.
+ Markus Uhlin 25/07/21 com_who: fixed multiplication
+ result converted to larger type.
+ Markus Uhlin 25/07/24 Fixed use of potentially
+ dangerous functions.
+ Markus Uhlin 25/07/29 Usage of 'int64_t'.
+*/
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include <sys/resource.h>
+
+#include <err.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdint.h>
+
+#include "board.h"
+#include "command.h"
+#include "comproc.h"
+#include "config.h"
+#include "eco.h"
+#include "fics_getsalt.h"
+#include "ficsmain.h"
+#include "formula.h"
+#include "gamedb.h"
+#include "gameproc.h"
+#include "lists.h"
+#include "multicol.h"
+#include "network.h"
+#include "obsproc.h"
+#include "playerdb.h"
+#include "ratings.h"
+#include "rmalloc.h"
+#include "talkproc.h"
+#include "utils.h"
+#include "variable.h"
+
+#if __linux__
+#include <bsd/string.h>
+#endif
+
+#define WHO_OPEN 0x01
+#define WHO_CLOSED 0x02
+#define WHO_RATED 0x04
+#define WHO_UNRATED 0x08
+#define WHO_FREE 0x10
+#define WHO_PLAYING 0x20
+#define WHO_REGISTERED 0x40
+#define WHO_UNREGISTERED 0x80
+#define WHO_BUGTEAM 0x100
+
+#define WHO_ALL 0xff
+
+typedef int ((*who_cmp_t)(const void *, const void *));
+
+PUBLIC const int none = 0;
+PUBLIC const int blitz_rat = 1;
+PUBLIC const int std_rat = 2;
+PUBLIC const int wild_rat = 3;
+PUBLIC const int light_rat = 4;
+
+PUBLIC int num_news = -1;
+
+PRIVATE char *inout_string[] = { "login", "logout" };
+
+PUBLIC int
+com_rating_recalc(int p, param_list param)
+{
+ (void) p; // maybe not referenced
+ (void) param; // not referenced
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+ rating_recalc();
+ return COM_OK;
+}
+
+PUBLIC int
+com_more(int p, param_list param)
+{
+ // not referenced
+ (void) param;
+
+ pmore_file(p);
+ return COM_OK;
+}
+
+PUBLIC void
+rscan_news2(FILE *fp, int p, int num)
+{
+ char count[10] = { '\0' };
+ char junk[MAX_LINE_SIZE] = { '\0' };
+ char *junkp;
+ const char *v_scan_fmt = "%" SCNd64 " " "%9s";
+ int64_t lval;
+ time_t crtime;
+
+ if (num == 0)
+ return;
+
+ if (fgets(junk, sizeof junk, fp) == NULL || feof(fp) ||
+ sscanf(junk, v_scan_fmt, &lval, count) != 2)
+ return;
+
+ rscan_news2(fp, p, num - 1);
+
+ junkp = junk;
+ junkp = nextword(junkp);
+ junkp = nextword(junkp);
+
+ crtime = lval;
+ pprintf(p, "%3s (%s) %s", count, fix_time(strltime(&crtime)), junkp);
+}
+
+PUBLIC int
+com_news(int p, param_list param)
+{
+ FILE *fp = NULL;
+ char count[10] = { '\0' };
+ char filename[MAX_FILENAME_SIZE] = { '\0' };
+ char junk[MAX_LINE_SIZE] = { '\0' };
+ char *junkp = NULL;
+ const char *v_scan_fmt = "%" SCNd64 " " "%9s";
+ int found = 0;
+ int64_t lval = 0;
+ time_t crtime = 0;
+
+ snprintf(filename, sizeof filename, "%s/newnews.index", news_dir);
+
+ if ((fp = fopen(filename, "r")) == NULL) {
+ fprintf(stderr, "Can\'t find news index.\n");
+ return COM_OK;
+ }
+
+ _Static_assert(9 < ARRAY_SIZE(count), "'count' too small");
+
+ if (param[0].type == 0) {
+ /*
+ * No params - then just display index of last ten
+ * news items.
+ */
+
+ pprintf(p, "Index of recent news items:\n");
+
+ if (fgets(junk, sizeof junk, fp) == NULL ||
+ sscanf(junk, v_scan_fmt, &lval, count) != 2) {
+ warnx("%s: error: fgets() or sscanf()", __func__);
+ fclose(fp);
+ return COM_FAILED;
+ }
+
+ rscan_news2(fp, p, 9);
+
+ junkp = junk;
+ junkp = nextword(junkp);
+ junkp = nextword(junkp);
+
+ crtime = lval;
+ pprintf(p, "%3s (%s) %s", count, fix_time(strltime(&crtime)),
+ junkp);
+ fclose(fp);
+ } else if (param[0].type == TYPE_WORD &&
+ !strcmp(param[0].val.word, "all")) {
+ /*
+ * Param all - displays index all news items.
+ */
+
+ pprintf(p, "Index of all news items:\n");
+
+ if (fgets(junk, sizeof junk, fp) == NULL ||
+ sscanf(junk, v_scan_fmt, &lval, count) != 2) {
+ warnx("%s: error: fgets() or sscanf()", __func__);
+ fclose(fp);
+ return COM_FAILED;
+ }
+
+ rscan_news(fp, p, 0);
+
+ junkp = junk;
+ junkp = nextword(junkp);
+ junkp = nextword(junkp);
+
+ crtime = lval;
+ pprintf(p, "%3s (%s) %s", count, fix_time(strltime(&crtime)),
+ junkp);
+ fclose(fp);
+ } else {
+ /*
+ * Check if the specific news file exist in index.
+ */
+
+ while (!feof(fp) && !found) {
+ junkp = junk;
+
+ if (fgets(junk, sizeof junk, fp) == NULL || feof(fp))
+ break;
+ if (sscanf(junkp, v_scan_fmt, &lval, count) != 2)
+ warnx("%s: sscanf() error...", __func__);
+ crtime = lval;
+
+ if (!strcmp(count, param[0].val.word)) {
+ found = 1;
+
+ junkp = nextword(junkp);
+ junkp = nextword(junkp);
+
+ pprintf(p, "NEWS %3s (%s) %s\n", count,
+ fix_time(strltime(&crtime)), junkp);
+ }
+ }
+
+ fclose(fp);
+
+ if (!found) {
+ pprintf(p, "Bad index number!\n");
+ return COM_OK;
+ }
+
+ /*
+ * File exists - show it
+ */
+
+ snprintf(filename, sizeof filename, "%s/news.%s", news_dir,
+ param[0].val.word);
+
+ if ((fp = fopen(filename, "r")) == NULL) {
+ pprintf(p, "No more info.\n");
+ return COM_OK;
+ }
+
+ fclose(fp);
+ snprintf(filename, sizeof filename, "news.%s",
+ param[0].val.word);
+
+ if (psend_file(p, news_dir, filename) < 0) {
+ pprintf(p, "Internal error - couldn't send news file!"
+ "\n");
+ }
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_quit(int p, param_list param)
+{
+ // not referenced
+ (void) param;
+
+ if (parray[p].game >= 0 && garray[parray[p].game].status ==
+ GAME_EXAMINE)
+ pcommand(p, "unexamine");
+ if (parray[p].game >= 0) {
+ pprintf(p, "You can't quit while you are playing a game.\n"
+ "Type 'resign' to resign the game, or you can request an "
+ "abort with 'abort'.\n");
+ return COM_OK;
+ }
+
+ psend_logoutfile(p, mess_dir, MESS_LOGOUT);
+ return COM_LOGOUT;
+}
+
+PUBLIC int
+com_set(int p, param_list param)
+{
+ char *val;
+ int result;
+ int which;
+
+ if (param[1].type == TYPE_NULL)
+ val = NULL;
+ else
+ val = param[1].val.string;
+
+ result = var_set(p, param[0].val.word, val, &which);
+
+ switch (result) {
+ case VAR_OK:
+ break;
+ case VAR_BADVAL:
+ pprintf(p, "Bad value given for variable %s.\n",
+ param[0].val.word);
+ break;
+ case VAR_NOSUCH:
+ pprintf(p, "No such variable name %s.\n", param[0].val.word);
+ break;
+ case VAR_AMBIGUOUS:
+ pprintf(p, "Ambiguous variable name %s.\n", param[0].val.word);
+ break;
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+FindPlayer(int p, char *name, int *p1, int *connected)
+{
+ *p1 = player_search(p, name);
+
+ if (*p1 == 0)
+ return 0;
+ if (*p1 < 0) { // The player had to be connected and will be removed
+ // later.
+ *connected = 0;
+ *p1 = (-*p1) - 1;
+ } else {
+ *connected = 1;
+ *p1 = *p1 - 1;
+ }
+
+ return 1;
+}
+
+PRIVATE void
+com_stats_andify(int *numbers, int howmany, char *dest, size_t dsize)
+{
+ char tmp[10] = { '\0' };
+
+ *dest = '\0';
+
+ while (howmany--) {
+ snprintf(tmp, sizeof tmp, "%d", numbers[howmany]);
+ strlcat(dest, tmp, dsize);
+
+ if (howmany > 1)
+ strlcpy(tmp, ", ", sizeof tmp);
+ else if (howmany == 1)
+ strlcpy(tmp, " and ", sizeof tmp);
+ else
+ strlcpy(tmp, ".\n", sizeof tmp);
+
+ if (strlcat(dest, tmp, dsize) >= dsize) {
+ (void) fprintf(stderr, "FICS: %s: warning: strlcat() "
+ "truncated\n", __func__);
+ }
+ }
+}
+
+PRIVATE void
+com_stats_rating(char *hdr, statistics *stats, char *dest, const size_t dsize)
+{
+ char tmp[100] = { '\0' };
+
+ snprintf(dest, dsize, "%-10s%4s %5.1f %4d %4d %4d %4d", hdr,
+ ratstr(stats->rating),
+ stats->sterr,
+ stats->win,
+ stats->los,
+ stats->dra,
+ stats->num);
+
+ if (stats->whenbest) {
+ struct tm res = {0};
+
+ snprintf(tmp, sizeof tmp, " %d", stats->best);
+ strlcat(dest, tmp, dsize);
+
+ errno = 0;
+
+ if (localtime_r(&stats->whenbest, &res) != NULL) {
+ if (strftime(tmp, sizeof tmp, " (%d-%b-%y)", &res) != 0)
+ strlcat(dest, tmp, dsize);
+ } else
+ warn("%s: localtime_r", __func__);
+ }
+
+ if (strlcat(dest, "\n", dsize) >= dsize) {
+ (void) fprintf(stderr, "FICS: %s (line %d): warning: strlcat() "
+ "truncated\n", __func__, __LINE__);
+ }
+}
+
+PUBLIC int
+com_stats(int p, param_list param)
+{
+#define NUMBERS_SIZE \
+ (MAX_OBSERVE > MAX_SIMUL ? MAX_OBSERVE : MAX_SIMUL)
+ char line[255] = { '\0' };
+ char tmp[255] = { '\0' };
+ int g, i, t;
+ int numbers[NUMBERS_SIZE];
+ int onTime;
+ int p1, connected;
+
+ if (param[0].type == TYPE_WORD) {
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+ } else {
+ p1 = p;
+ connected = 1;
+ }
+
+ snprintf(line, sizeof line, "\nStatistics for %-11s ", parray[p1].name);
+
+ if (connected && parray[p1].status == PLAYER_PROMPT) {
+ snprintf(tmp, sizeof tmp, "On for: %s",
+ hms_desc(player_ontime(p1)));
+ strlcat(line, tmp, sizeof line);
+ snprintf(tmp, sizeof tmp, " Idle: %s\n",
+ hms_desc(player_idle(p1)));
+ } else {
+ time_t last;
+
+ if ((last = player_lastdisconnect(p1)) != 0) {
+ snprintf(tmp, sizeof tmp, "(Last disconnected %s):\n",
+ strltime(&last));
+ } else
+ strlcpy(tmp, "(Never connected.)\n", sizeof tmp);
+ }
+
+ strlcat(line, tmp, sizeof line);
+ pprintf(p, "%s", line);
+
+ if (parray[p1].simul_info.numBoards) {
+ t = 0;
+ i = 0;
+
+ while (i < parray[p1].simul_info.numBoards) {
+ if ((numbers[t] = parray[p1].simul_info.boards[i] + 1) != 0)
+ t++;
+ i++;
+ }
+ pprintf(p, "%s is giving a simul: game%s ", parray[p1].name,
+ (t > 1 ? "s" : ""));
+ com_stats_andify(numbers, t, tmp, sizeof tmp);
+ pprintf(p, "%s", tmp);
+ } else if (parray[p1].game >= 0) {
+ g = parray[p1].game;
+
+ if (garray[g].status == GAME_EXAMINE) {
+ pprintf(p, "(Examining game %d: %s vs. %s)\n", (g + 1),
+ garray[g].white_name,
+ garray[g].black_name);
+ } else {
+ pprintf(p, "(playing game %d: %s vs. %s)\n", (g + 1),
+ parray[garray[g].white].name,
+ parray[garray[g].black].name);
+
+ if (garray[g].link >= 0) {
+ pprintf(p, "(partner is playing game %d: "
+ "%s vs. %s)\n",
+ (garray[g].link + 1),
+ parray[garray[garray[g].link].white].name,
+ parray[garray[garray[g].link].black].name);
+ }
+ }
+ }
+
+ if (parray[p1].num_observe) {
+ t = 0;
+ i = 0;
+
+ while (i < parray[p1].num_observe) {
+ if ((g = parray[p1].observe_list[i]) != -1 &&
+ (parray[p].adminLevel >= ADMIN_ADMIN ||
+ garray[g].private == 0))
+ numbers[t++] = (g + 1);
+ i++;
+ }
+
+ if (t) {
+ pprintf(p, "%s is observing game%s ", parray[p1].name,
+ (t > 1 ? "s" : ""));
+ com_stats_andify(numbers, t, tmp, sizeof tmp);
+ pprintf(p, "%s", tmp);
+ }
+ }
+
+ if (parray[p1].busy[0])
+ pprintf(p, "(%s %s)\n", parray[p1].name, parray[p1].busy);
+
+ if (!parray[p1].registered) {
+ pprintf(p, "%s is NOT a registered player.\n\n",
+ parray[p1].name);
+ } else {
+ pprintf(p, "\n rating RD win loss draw "
+ "total best\n");
+
+ com_stats_rating("Blitz", &parray[p1].b_stats, tmp, sizeof tmp);
+ pprintf(p, "%s", tmp);
+ com_stats_rating("Standard", &parray[p1].s_stats, tmp,
+ sizeof tmp);
+ pprintf(p, "%s", tmp);
+ com_stats_rating("Lightning", &parray[p1].l_stats, tmp,
+ sizeof tmp);
+ pprintf(p, "%s", tmp);
+ com_stats_rating("Wild", &parray[p1].w_stats, tmp, sizeof tmp);
+ pprintf(p, "%s", tmp);
+ }
+
+ pprintf(p, "\n");
+
+ if (parray[p1].adminLevel > 0) {
+ pprintf(p, "Admin Level: ");
+
+ switch (parray[p1].adminLevel) {
+ case 5:
+ pprintf(p, "Authorized Helper Person\n");
+ break;
+ case 10:
+ pprintf(p, "Administrator\n");
+ break;
+ case 15:
+ pprintf(p, "Help File Librarian/Administrator\n");
+ break;
+ case 20:
+ pprintf(p, "Master Administrator\n");
+ break;
+ case 50:
+ pprintf(p, "Master Help File Librarian/Administrator"
+ "\n");
+ break;
+ case 60:
+ pprintf(p, "Assistant Super User\n");
+ break;
+ case 100:
+ pprintf(p, "Super User\n");
+ break;
+ default:
+ pprintf(p, "%d\n", parray[p1].adminLevel);
+ break;
+ }
+ }
+
+ // Full Name
+ if (parray[p].adminLevel > 0) {
+ pprintf(p, "Full Name : %s\n", (parray[p1].fullName ?
+ parray[p1].fullName : "(none)"));
+ }
+
+ // Address
+ if ((p1 == p && parray[p1].registered) || parray[p].adminLevel > 0) {
+ pprintf(p, "Address : %s\n", (parray[p1].emailAddress ?
+ parray[p1].emailAddress : "(none)"));
+ }
+
+ // Host
+ if (parray[p].adminLevel > 0) {
+ pprintf(p, "Host : %s\n", dotQuad(connected ?
+ parray[p1].thisHost : parray[p1].lastHost));
+ }
+
+ // Comments
+ if (parray[p].adminLevel > 0 && parray[p1].registered) {
+ if (parray[p1].num_comments) {
+ pprintf(p, "Comments : %d\n",
+ parray[p1].num_comments);
+ }
+ }
+
+ if (connected &&
+ parray[p1].registered &&
+ (p == p1 || parray[p].adminLevel > 0)) {
+ char timeToStr[30] = { '\0' };
+
+ errno = 0;
+
+ if (ctime_r(&parray[p1].timeOfReg, timeToStr) == NULL)
+ warn("%s: ctime_r", __func__);
+ timeToStr[strcspn(timeToStr, "\n")] = '\0';
+
+ pprintf(p, "\n");
+
+ onTime = ((time(NULL) - parray[p1].logon_time) +
+ parray[p1].totalTime);
+
+ pprintf(p, "Total time on-line: %s\n", hms_desc(onTime));
+ pprintf(p, "%% of life on-line: %3.1f (since %s)\n", // XXX
+ (double) ((onTime * 100) / (double) (time(NULL) -
+ parray[p1].timeOfReg)),
+ timeToStr);
+ }
+
+#ifdef TIMESEAL
+ if (connected) {
+ pprintf(p, "\nTimeseal : %s\n",
+ (con[parray[p1].socket].timeseal ? "On" : "Off"));
+ }
+ if (parray[p].adminLevel > 0 && connected) {
+ if (findConnection(parray[p1].socket) &&
+ con[parray[p1].socket].timeseal) {
+ pprintf(p, "Unix acc : %s\nSystem/OS : %s\n",
+ con[parray[p1].socket].user,
+ con[parray[p1].socket].sys);
+ }
+ }
+#endif
+
+ if (parray[p1].num_plan) {
+ pprintf(p, "\n");
+
+ for (i = 0; i < parray[p1].num_plan; i++) {
+ pprintf(p, "%2d: %s\n", (i + 1),
+ (parray[p1].planLines[i] != NULL
+ ? parray[p1].planLines[i]
+ : ""));
+ }
+ }
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+}
+
+PUBLIC int
+com_password(int p, param_list param)
+{
+ char *oldpassword = param[0].val.word;
+ char *newpassword = param[1].val.word;
+ char salt[FICS_SALT_SIZE];
+
+ if (!parray[p].registered) {
+ pprintf(p, "Setting a password is only for registered players."
+ "\n");
+ return COM_OK;
+ }
+
+ if (parray[p].passwd) {
+ strncpy(salt, &(parray[p].passwd[0]), sizeof salt - 1);
+ salt[sizeof salt - 1] = '\0';
+
+ if (strcmp(crypt(oldpassword, salt), parray[p].passwd)) {
+ pprintf(p, "Incorrect password, password not changed!"
+ "\n");
+ return COM_OK;
+ }
+
+ rfree(parray[p].passwd);
+ parray[p].passwd = NULL;
+ }
+
+ strlcpy(salt, fics_getsalt(), sizeof salt);
+ parray[p].passwd = xstrdup(crypt(newpassword, salt));
+
+ pprintf(p, "Password changed to \"%s\".\n", newpassword);
+ return COM_OK;
+}
+
+PUBLIC int
+com_uptime(int p, param_list param)
+{
+ int days, hours, mins, secs;
+ struct rusage ru;
+ unsigned long int uptime = (time(NULL) - startuptime);
+
+ (void) param; // XXX: unused
+
+ days = (uptime / (60*60*24));
+ hours = ((uptime % (60*60*24)) / (60*60));
+ mins = (((uptime % (60*60*24)) % (60*60)) / 60);
+ secs = (((uptime % (60*60*24)) % (60*60)) % 60);
+
+ pprintf(p, "Server location: %s Server version : %s\n", fics_hostname,
+ VERS_NUM);
+ pprintf(p, "The server has been up since %s.\n",
+ strltime(&startuptime));
+
+ if (days == 0 && hours == 0 && mins == 0) {
+ pprintf(p, "(Up for %d second%s)\n",
+ secs, (secs == 1 ? "" : "s"));
+ } else if (days == 0 && hours == 0) {
+ pprintf(p, "(Up for %d minute%s and %d second%s)\n",
+ mins, (mins == 1 ? "" : "s"),
+ secs, (secs == 1 ? "" : "s"));
+ } else if (days == 0) {
+ pprintf(p, "(Up for %d hour%s, %d minute%s and %d second%s)\n",
+ hours, (hours == 1 ? "" : "s"),
+ mins, (mins == 1 ? "" : "s"),
+ secs, (secs == 1 ? "" : "s"));
+ } else {
+ pprintf(p, "(Up for %d day%s, %d hour%s, %d minute%s and %d "
+ "second%s)\n",
+ days, (days == 1 ? "" : "s"),
+ hours, (hours == 1 ? "" : "s"),
+ mins, (mins == 1 ? "" : "s"),
+ secs, (secs == 1 ? "" : "s"));
+ }
+
+ pprintf(p, "\nAllocs: %u Frees: %u Allocs In Use: %u\n",
+ malloc_count, free_count, (malloc_count - free_count));
+ if (parray[p].adminLevel >= ADMIN_ADMIN) {
+ pprintf(p, "\nplayer size:%zu, game size:%zu, con size:%d, "
+ "g_num:%d\n", sizeof(player), sizeof(game), net_consize(),
+ g_num);
+
+ getrusage(RUSAGE_SELF, &ru);
+
+ pprintf(p, "pagesize = %d, maxrss = %ld, total = %ld\n",
+ getpagesize(),
+ ru.ru_maxrss,
+ (getpagesize() * ru.ru_maxrss));
+ }
+
+ pprintf(p, "\nPlayer limit: %d\n", (max_connections - 10));
+ pprintf(p, "\nThere are currently %d players, with a high of %d "
+ "since last restart.\n", player_count(1), player_high);
+ pprintf(p, "There are currently %d games, with a high of %d "
+ "since last restart.\n", game_count(), game_high);
+ pprintf(p, "\nCompiled on %s\n", COMP_DATE);
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_date(int p, param_list param)
+{
+ time_t t = time(NULL);
+
+ (void) param; // XXX: unused
+
+ pprintf(p, "Local time - %s\n", strltime(&t));
+ pprintf(p, "Greenwich time - %s\n", strgtime(&t));
+
+ return COM_OK;
+}
+
+PRIVATE int
+plogins(int p, char *fname)
+{
+ FILE *fp = NULL;
+ char ipstr[20] = { '\0' };
+ char loginName[MAX_LOGIN_NAME + 1] = { '\0' };
+ const char *v_scan_fmt = "%" SCNu16 " %19s " "%" SCNd64 " "
+ "%d %19s\n";
+ int registered = 0;
+ int64_t lval = 0;
+ time_t tval = 0;
+ uint16_t inout = 0;
+
+ if ((fp = fopen(fname, "r")) == NULL) {
+ pprintf(p, "Sorry, no login information available.\n");
+ return COM_OK;
+ }
+
+ _Static_assert(19 < ARRAY_SIZE(ipstr), "'ipstr' too small");
+ _Static_assert(19 < ARRAY_SIZE(loginName), "'loginName' too small");
+
+ while (!feof(fp)) {
+ if (fscanf(fp, v_scan_fmt, &inout, loginName, &lval,
+ &registered, ipstr) != 5) {
+ fprintf(stderr, "FICS: Error in login info format. "
+ "%s\n", fname);
+ fclose(fp);
+ return COM_OK;
+ }
+
+ tval = lval;
+
+ if (inout >= ARRAY_SIZE(inout_string)) {
+ warnx("%s: %s: 'inout' too large (%u)", __func__, fname,
+ inout);
+ } else {
+ pprintf(p, "%s: %-17s %-6s", strltime(&tval), loginName,
+ inout_string[inout]);
+ }
+
+ if (parray[p].adminLevel > 0)
+ pprintf(p, " from %s\n", ipstr);
+ else
+ pprintf(p, "\n");
+ }
+
+ fclose(fp);
+ return COM_OK;
+}
+
+PUBLIC int
+com_llogons(int p, param_list param)
+{
+ char fname[MAX_FILENAME_SIZE] = { '\0' };
+
+ (void) param; // XXX: unused
+
+ snprintf(fname, sizeof fname, "%s/%s", stats_dir, STATS_LOGONS);
+ return plogins(p, fname);
+}
+
+PUBLIC int
+com_logons(int p, param_list param)
+{
+ char fname[MAX_FILENAME_SIZE] = { '\0' };
+
+ if (param[0].type == TYPE_WORD) {
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s",
+ stats_dir, param[0].val.word[0], param[0].val.word,
+ STATS_LOGONS);
+ } else {
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s",
+ stats_dir, parray[p].login[0], parray[p].login,
+ STATS_LOGONS);
+ }
+ return plogins(p, fname);
+}
+
+PRIVATE void
+AddPlayerLists(int p1, char *ptmp, const size_t size)
+{
+ if (parray[p1].adminLevel >= 10 && parray[p1].i_admin)
+ strlcat(ptmp, "(*)", size);
+ if (in_list(p1, L_COMPUTER, parray[p1].name))
+ strlcat(ptmp, "(C)", size);
+ if (in_list(p1, L_FM, parray[p1].name))
+ strlcat(ptmp, "(FM)", size);
+ if (in_list(p1, L_IM, parray[p1].name))
+ strlcat(ptmp, "(IM)", size);
+ if (in_list(p1, L_GM, parray[p1].name))
+ strlcat(ptmp, "(GM)", size);
+ if (in_list(p1, L_TD, parray[p1].name))
+ strlcat(ptmp, "(TD)", size);
+ if (in_list(p1, L_TEAMS, parray[p1].name))
+ strlcat(ptmp, "(T)", size);
+ if (in_list(p1, L_BLIND, parray[p1].name))
+ strlcat(ptmp, "(B)", size);
+}
+
+PRIVATE void
+who_terse(int p, int num, int *plist, int type)
+{
+ char ptmp[80 + 20] = { '\0' }; // for highlight
+ int i;
+ int p1;
+ int rat;
+ multicol *m = multicol_start(PARRAY_SIZE);
+
+ for (i = 0; i < num; i++) {
+ p1 = plist[i];
+
+ if (type == blitz_rat)
+ rat = parray[p1].b_stats.rating;
+ else if (type == wild_rat)
+ rat = parray[p1].w_stats.rating;
+ else if (type == std_rat)
+ rat = parray[p1].s_stats.rating;
+ else if (type == light_rat)
+ rat = parray[p1].l_stats.rating;
+ else // Fallback to std...
+ rat = parray[p1].s_stats.rating;
+
+ if (type == none) {
+ strlcpy(ptmp, " ", sizeof ptmp);
+ } else {
+ snprintf(ptmp, sizeof ptmp, "%-4s", ratstrii(rat,
+ parray[p1].registered));
+
+ if (parray[p1].simul_info.numBoards) {
+ strlcat(ptmp, "~", sizeof ptmp);
+ } else if (parray[p1].game >= 0 &&
+ garray[parray[p1].game].status == GAME_EXAMINE) {
+ strlcat(ptmp, "#", sizeof ptmp);
+ } else if (parray[p1].game >= 0) {
+ strlcat(ptmp, "^", sizeof ptmp);
+ } else if (!parray[p1].open) {
+ strlcat(ptmp, ":", sizeof ptmp);
+ } else if (player_idle(p1) > 300) {
+ strlcat(ptmp, ".", sizeof ptmp);
+ } else {
+ strlcat(ptmp, " ", sizeof ptmp);
+ }
+ }
+
+ if (p == p1) {
+ const size_t len = strlen(ptmp);
+
+ psprintf_highlight(p, ptmp + len,
+ sizeof ptmp - len, "%s", parray[p1].name);
+ } else {
+ strlcat(ptmp, parray[p1].name, sizeof ptmp);
+ }
+
+ AddPlayerLists(p1, ptmp, sizeof ptmp);
+ multicol_store(m, ptmp);
+ }
+
+ multicol_pprint(m, p, 80, 2);
+ multicol_end(m);
+
+ pprintf(p, "\n %d players displayed (of %d). (*) indicates system "
+ "administrator.\n", num, player_count(1));
+}
+
+PRIVATE void
+who_verbose(int p, int num, int plist[])
+{
+ char p1WithAttrs[255] = { '\0' };
+ char playerLine[255] = { '\0' };
+ char tmp[255] = { '\0' };
+ int p1;
+ int ret, too_long;
+
+ pprintf(p, " +---------------------------------------------------------------+\n");
+ pprintf(p, " | User Standard Blitz On for Idle |\n");
+ pprintf(p, " +---------------------------------------------------------------+\n");
+
+ for (int i = 0; i < num; i++) {
+ p1 = plist[i];
+ strlcpy(playerLine, " |", sizeof playerLine);
+
+ if (parray[p1].game >= 0)
+ snprintf(tmp, sizeof tmp, "%3d", parray[p1].game + 1);
+ else
+ strlcpy(tmp, " ", sizeof tmp);
+
+ strlcat(playerLine, tmp, sizeof playerLine);
+
+ if (!parray[p1].open)
+ strlcpy(tmp, "X", sizeof tmp);
+ else
+ strlcpy(tmp, " ", sizeof tmp);
+
+ strlcat(playerLine, tmp, sizeof playerLine);
+
+ if (parray[p1].registered) {
+ if (parray[p1].rated)
+ strlcpy(tmp, " ", sizeof tmp);
+ else
+ strlcpy(tmp, "u", sizeof tmp);
+ } else {
+ strlcpy(tmp, "U", sizeof tmp);
+ }
+
+ strlcat(playerLine, tmp, sizeof playerLine);
+ strlcpy(p1WithAttrs, parray[p1].name, sizeof p1WithAttrs);
+ AddPlayerLists(p1, p1WithAttrs, sizeof p1WithAttrs);
+ p1WithAttrs[17] = '\0';
+
+ if (p == p1) {
+ size_t len;
+
+ strlcpy(tmp, " ", sizeof tmp);
+ len = strlen(tmp);
+ psprintf_highlight(p, tmp + len, sizeof tmp - len,
+ "%-17s", p1WithAttrs);
+ } else {
+ ret = snprintf(tmp, sizeof tmp, " %-17s", p1WithAttrs);
+
+ too_long = (ret < 0 || (size_t)ret >= sizeof tmp);
+
+ if (too_long) {
+ fprintf(stderr, "FICS: %s: warning: "
+ "snprintf truncated\n", __func__);
+ }
+ }
+
+ strlcat(playerLine, tmp, sizeof playerLine);
+ snprintf(tmp, sizeof tmp, " %4s %-4s %5s ",
+ ratstrii(parray[p1].s_stats.rating,
+ parray[p1].registered),
+ ratstrii(parray[p1].b_stats.rating,
+ parray[p1].registered),
+ hms(player_ontime(p1), 0, 0, 0));
+ strlcat(playerLine, tmp, sizeof playerLine);
+
+ if (player_idle(p1) >= 60) {
+ snprintf(tmp, sizeof tmp, "%5s |\n",
+ hms(player_idle(p1), 0, 0, 0));
+ } else {
+ strlcpy(tmp, " |\n", sizeof tmp);
+ }
+
+ strlcat(playerLine, tmp, sizeof playerLine);
+ pprintf(p, "%s", playerLine);
+ }
+
+ pprintf(p, " | |\n");
+ pprintf(p, " | %3d Players Displayed |\n", num);
+ pprintf(p, " +---------------------------------------------------------------+\n");
+}
+
+PRIVATE void
+who_winloss(int p, int num, int plist[])
+{
+ char p1WithAttrs[255] = { '\0' };
+ char playerLine[255] = { '\0' };
+ char tmp[255] = { '\0' };
+ int p1;
+
+ pprintf(p, "Name Stand win loss draw Blitz win loss draw idle\n");
+ pprintf(p, "---------------- ----- ------------- ----- ------------- ----\n");
+
+ for (int i = 0; i < num; i++) {
+ playerLine[0] = '\0';
+ p1 = plist[i];
+
+ /* Modified by hersco to include lists in 'who n.' */
+ strlcpy(p1WithAttrs, parray[p1].name, sizeof p1WithAttrs);
+ AddPlayerLists(p1, p1WithAttrs, sizeof p1WithAttrs);
+ p1WithAttrs[17] = '\0';
+
+ if (p1 == p) {
+ psprintf_highlight(p, playerLine, sizeof playerLine,
+ "%-17s", p1WithAttrs);
+ } else {
+ snprintf(playerLine, sizeof playerLine, "%-17s",
+ p1WithAttrs);
+ }
+
+ snprintf(tmp, sizeof tmp, " %4s %4d %4d %4d ",
+ ratstrii(parray[p1].s_stats.rating, parray[p1].registered),
+ parray[p1].s_stats.win,
+ parray[p1].s_stats.los,
+ parray[p1].s_stats.dra);
+ strlcat(playerLine, tmp, sizeof playerLine);
+
+ snprintf(tmp, sizeof tmp, "%4s %4d %4d %4d ",
+ ratstrii(parray[p1].b_stats.rating, parray[p1].registered),
+ parray[p1].b_stats.win,
+ parray[p1].b_stats.los,
+ parray[p1].b_stats.dra);
+ strlcat(playerLine, tmp, sizeof playerLine);
+
+ if (player_idle(p1) >= 60) {
+ snprintf(tmp, sizeof tmp, "%5s\n", hms(player_idle(p1),
+ 0, 0, 0));
+ } else {
+ strlcpy(tmp, "\n", sizeof tmp);
+ }
+
+ strlcat(playerLine, tmp, sizeof playerLine);
+ pprintf(p, "%s", playerLine);
+ }
+
+ pprintf(p, " %3d Players Displayed.\n", num);
+}
+
+PRIVATE int
+who_ok(int p, unsigned int sel_bits)
+{
+ int p2;
+
+ if (parray[p].status != PLAYER_PROMPT)
+ return 0;
+ if (sel_bits == WHO_ALL)
+ return 1;
+
+ if (sel_bits & WHO_OPEN) {
+ if (!parray[p].open)
+ return 0;
+ }
+ if (sel_bits & WHO_CLOSED) {
+ if (parray[p].open)
+ return 0;
+ }
+ if (sel_bits & WHO_RATED) {
+ if (!parray[p].rated)
+ return 0;
+ }
+ if (sel_bits & WHO_UNRATED) {
+ if (parray[p].rated)
+ return 0;
+ }
+ if (sel_bits & WHO_FREE) {
+ if (parray[p].game >= 0)
+ return 0;
+ }
+ if (sel_bits & WHO_PLAYING) {
+ if (parray[p].game < 0)
+ return 0;
+ }
+ if (sel_bits & WHO_REGISTERED) {
+ if (!parray[p].registered)
+ return 0;
+ }
+ if (sel_bits & WHO_UNREGISTERED) {
+ if (parray[p].registered)
+ return 0;
+ }
+ if (sel_bits & WHO_BUGTEAM) {
+ if ((p2 = parray[p].partner) < 0 || parray[p2].partner != p)
+ return 0;
+ }
+
+ return 1;
+}
+
+PRIVATE int
+blitz_cmp(const void *pp1, const void *pp2)
+{
+ register int p1 = *(int *) pp1;
+ register int p2 = *(int *) pp2;
+
+ if (parray[p1].status != PLAYER_PROMPT) {
+ if (parray[p2].status != PLAYER_PROMPT)
+ return 0;
+ else
+ return -1;
+ }
+ if (parray[p2].status != PLAYER_PROMPT)
+ return 1;
+ if (parray[p1].b_stats.rating > parray[p2].b_stats.rating)
+ return -1;
+ if (parray[p1].b_stats.rating < parray[p2].b_stats.rating)
+ return 1;
+ if (parray[p1].registered > parray[p2].registered)
+ return -1;
+ if (parray[p1].registered < parray[p2].registered)
+ return 1;
+ return strcmp(parray[p1].login, parray[p2].login);
+}
+
+PRIVATE int
+light_cmp(const void *pp1, const void *pp2)
+{
+ register int p1 = *(int *) pp1;
+ register int p2 = *(int *) pp2;
+
+ if (parray[p1].status != PLAYER_PROMPT) {
+ if (parray[p2].status != PLAYER_PROMPT)
+ return 0;
+ else
+ return -1;
+ }
+ if (parray[p2].status != PLAYER_PROMPT)
+ return 1;
+ if (parray[p1].l_stats.rating > parray[p2].l_stats.rating)
+ return -1;
+ if (parray[p1].l_stats.rating < parray[p2].l_stats.rating)
+ return 1;
+ if (parray[p1].registered > parray[p2].registered)
+ return -1;
+ if (parray[p1].registered < parray[p2].registered)
+ return 1;
+ return strcmp(parray[p1].login, parray[p2].login);
+}
+
+PRIVATE int
+stand_cmp(const void *pp1, const void *pp2)
+{
+ register int p1 = *(int *) pp1;
+ register int p2 = *(int *) pp2;
+
+ if (parray[p1].status != PLAYER_PROMPT) {
+ if (parray[p2].status != PLAYER_PROMPT)
+ return 0;
+ else
+ return -1;
+ }
+ if (parray[p2].status != PLAYER_PROMPT)
+ return 1;
+ if (parray[p1].s_stats.rating > parray[p2].s_stats.rating)
+ return -1;
+ if (parray[p1].s_stats.rating < parray[p2].s_stats.rating)
+ return 1;
+ if (parray[p1].registered > parray[p2].registered)
+ return -1;
+ if (parray[p1].registered < parray[p2].registered)
+ return 1;
+ return strcmp(parray[p1].login, parray[p2].login);
+}
+
+PRIVATE int
+wild_cmp(const void *pp1, const void *pp2)
+{
+ register int p1 = *(int *) pp1;
+ register int p2 = *(int *) pp2;
+
+ if (parray[p1].status != PLAYER_PROMPT) {
+ if (parray[p2].status != PLAYER_PROMPT)
+ return 0;
+ else
+ return -1;
+ }
+ if (parray[p2].status != PLAYER_PROMPT)
+ return 1;
+ if (parray[p1].w_stats.rating > parray[p2].w_stats.rating)
+ return -1;
+ if (parray[p1].w_stats.rating < parray[p2].w_stats.rating)
+ return 1;
+ if (parray[p1].registered > parray[p2].registered)
+ return -1;
+ if (parray[p1].registered < parray[p2].registered)
+ return 1;
+ return strcmp(parray[p1].login, parray[p2].login);
+}
+
+PRIVATE int
+alpha_cmp(const void *pp1, const void *pp2)
+{
+ register int p1 = *(int *) pp1;
+ register int p2 = *(int *) pp2;
+
+ if (parray[p1].status != PLAYER_PROMPT) {
+ if (parray[p2].status != PLAYER_PROMPT)
+ return 0;
+ else
+ return -1;
+ }
+ if (parray[p2].status != PLAYER_PROMPT)
+ return 1;
+ return strcmp(parray[p1].login, parray[p2].login);
+}
+
+PRIVATE void
+sort_players(int players[PARRAY_SIZE], who_cmp_t cmp_func)
+{
+ if (p_num <= 0) {
+ warnx("%s: p_num <= 0", __func__);
+ return;
+ }
+ for (int i = 0; i < p_num; i++)
+ players[i] = i;
+ qsort(players, p_num, sizeof(int), cmp_func);
+}
+
+/*
+ * This is the of the most compliclicated commands in terms of
+ * parameters.
+ */
+PUBLIC int
+com_who(int p, param_list param)
+{
+ char c;
+ float start_perc = 0;
+ float stop_perc = 1.0;
+ int i, len;
+ int p1, count, num_who;
+ int plist[PARRAY_SIZE];
+ int sort_type = blitz_rat;
+ int sortlist[PARRAY_SIZE];
+ int startpoint;
+ int stoppoint;
+ int style = 0;
+ int tmpI, tmpJ;
+ unsigned int sel_bits = WHO_ALL;
+ who_cmp_t cmp_func = blitz_cmp;
+
+ if (param[0].type != TYPE_NULL) {
+ len = strlen(param[0].val.string);
+
+ for (i = 0; i < len; i++) {
+ c = param[0].val.string[i];
+
+ if (isdigit(c)) {
+ if (i == 0 || !isdigit(param[0].val.string[i - 1])) {
+ tmpI = (c - '0');
+
+ if (tmpI == 1) {
+ start_perc = 0.0;
+ stop_perc = 0.333333;
+ } else if (tmpI == 2) {
+ start_perc = 0.333333;
+ stop_perc = 0.6666667;
+ } else if (tmpI == 3) {
+ start_perc = 0.6666667;
+ stop_perc = 1.0;
+ } else if (i == (len - 1) ||
+ !isdigit(param[0].val.string[i + 1]))
+ return COM_BADPARAMETERS;
+ } else {
+ tmpI = c - '0';
+ tmpJ = (param[0].val.string[i - 1] -
+ '0');
+
+ if (tmpI == 0)
+ return COM_BADPARAMETERS;
+ if (tmpJ > tmpI)
+ return COM_BADPARAMETERS;
+
+ start_perc = ((float) tmpJ - 1.0) /
+ (float) tmpI;
+ stop_perc = ((float) tmpJ) /
+ (float) tmpI;
+ }
+ } else {
+ switch (c) {
+ case ' ':
+ case '\n':
+ case '\t':
+ break;
+ case 'o':
+ if (sel_bits == WHO_ALL)
+ sel_bits = WHO_OPEN;
+ else
+ sel_bits |= WHO_OPEN;
+ break;
+ case 'r':
+ if (sel_bits == WHO_ALL)
+ sel_bits = WHO_RATED;
+ else
+ sel_bits |= WHO_RATED;
+ break;
+ case 'f':
+ if (sel_bits == WHO_ALL)
+ sel_bits = WHO_FREE;
+ else
+ sel_bits |= WHO_FREE;
+ break;
+ case 'a':
+ if (sel_bits == WHO_ALL)
+ sel_bits = WHO_FREE | WHO_OPEN;
+ else
+ sel_bits |= (WHO_FREE | WHO_OPEN);
+ break;
+ case 'R':
+ if (sel_bits == WHO_ALL)
+ sel_bits = WHO_REGISTERED;
+ else
+ sel_bits |= WHO_REGISTERED;
+ break;
+ case 'l': // Sort order
+ case 'A':
+ cmp_func = alpha_cmp;
+ sort_type = none;
+ break;
+ case 'w': // Sort order
+ cmp_func = wild_cmp;
+ sort_type = wild_rat;
+ break;
+ case 's': // Sort order
+ cmp_func = stand_cmp;
+ sort_type = std_rat;
+ break;
+ case 'b': // Sort order
+ cmp_func = blitz_cmp;
+ sort_type = blitz_rat;
+ break;
+ case 'L': // Sort order
+ cmp_func = light_cmp;
+ sort_type = light_rat;
+ break;
+ case 't': // format
+ style = 0;
+ break;
+ case 'v': // format
+ style = 1;
+ break;
+ case 'n': // format
+ style = 2;
+ break;
+ case 'U':
+ if (sel_bits == WHO_ALL)
+ sel_bits = WHO_UNREGISTERED;
+ else
+ sel_bits |= WHO_UNREGISTERED;
+ break;
+ case 'B':
+ if (sel_bits == WHO_ALL)
+ sel_bits = WHO_BUGTEAM;
+ else
+ sel_bits |= WHO_BUGTEAM;
+ break;
+ default:
+ return COM_BADPARAMETERS;
+ }
+ }
+ }
+ }
+
+ sort_players(sortlist, cmp_func);
+ count = 0;
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (!who_ok(sortlist[p1], sel_bits))
+ continue;
+ count++;
+ }
+
+ startpoint = floorf((float) count * start_perc);
+ stoppoint = ceilf((float) count * stop_perc) - 1;
+ num_who = 0;
+ count = 0;
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (!who_ok(sortlist[p1], sel_bits))
+ continue;
+ if (count >= startpoint && count <= stoppoint)
+ plist[num_who++] = sortlist[p1];
+
+ count++;
+ }
+
+ if (num_who == 0) {
+ pprintf(p, "No logged in players match the flags in your who "
+ "request.\n");
+ return COM_OK;
+ }
+
+ switch (style) {
+ case 0: // terse
+ who_terse(p, num_who, plist, sort_type);
+ break;
+ case 1: // verbose
+ who_verbose(p, num_who, plist);
+ break;
+ case 2: // win-loss
+ who_winloss(p, num_who, plist);
+ break;
+ default:
+ return COM_BADPARAMETERS;
+ break;
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_refresh(int p, param_list param)
+{
+ int g, p1;
+
+ if (param[0].type == TYPE_NULL) {
+ if (parray[p].game >= 0) {
+ send_board_to(parray[p].game, p);
+ } else { /* Do observing in here */
+ if (parray[p].num_observe) {
+ for (g = 0; g < parray[p].num_observe; g++) {
+ send_board_to(parray[p].observe_list[g],
+ p);
+ }
+ } else {
+ pprintf(p, "You are neither playing, observing "
+ "nor examining a game.\n");
+ return COM_OK;
+ }
+ }
+ } else {
+ if ((g = GameNumFromParam(p, &p1, &param[0])) < 0)
+ return COM_OK;
+ if (g >= g_num ||
+ (garray[g].status != GAME_ACTIVE &&
+ garray[g].status != GAME_EXAMINE)) {
+ pprintf(p, "No such game.\n");
+ } else if (garray[g].private && parray[p].adminLevel ==
+ ADMIN_USER) {
+ pprintf(p, "Sorry, game %d is a private game.\n",
+ (g + 1));
+ } else {
+ if (garray[g].private) {
+ pprintf(p, "Refreshing PRIVATE game %d\n",
+ (g + 1));
+ }
+ send_board_to(g, p);
+ }
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_prefresh(int p, param_list param)
+{
+ int retval, part;
+
+ (void) param;
+
+ if ((part = parray[p].partner) < 0) {
+ pprintf(p, "You do not have a partner.\n");
+ return COM_OK;
+ } else if ((retval = pcommand(p, "refresh %s", parray[part].name)) ==
+ COM_OK)
+ return COM_OK_NOPROMPT;
+ return retval;
+}
+
+PUBLIC int
+com_open(int p, param_list param)
+{
+ int retval;
+
+ (void) param;
+ ASSERT(param[0].type == TYPE_NULL);
+
+ if ((retval = pcommand(p, "set open")) != COM_OK)
+ return retval;
+ return COM_OK_NOPROMPT;
+}
+
+PUBLIC int
+com_simopen(int p, param_list param)
+{
+ int retval;
+
+ (void) param;
+ ASSERT(param[0].type == TYPE_NULL);
+
+ if ((retval = pcommand(p, "set simopen")) != COM_OK)
+ return retval;
+ return COM_OK_NOPROMPT;
+}
+
+PUBLIC int
+com_bell(int p, param_list param)
+{
+ int retval;
+
+ (void) param;
+ ASSERT(param[0].type == TYPE_NULL);
+
+ if ((retval = pcommand(p, "set bell")) != COM_OK)
+ return retval;
+ return COM_OK_NOPROMPT;
+}
+
+PUBLIC int
+com_flip(int p, param_list param)
+{
+ int retval;
+
+ (void) param;
+ ASSERT(param[0].type == TYPE_NULL);
+
+ if ((retval = pcommand(p, "set flip")) != COM_OK)
+ return retval;
+ return COM_OK_NOPROMPT;
+}
+
+PUBLIC int
+com_highlight(int p, param_list param)
+{
+ // XXX: unused
+ (void) param;
+
+ pprintf(p, "Obsolete command. Please do set highlight <0-15>.\n");
+ return COM_OK;
+}
+
+PUBLIC int
+com_style(int p, param_list param)
+{
+ int retval;
+
+ ASSERT(param[0].type == TYPE_INT);
+
+ if ((retval = pcommand(p, "set style %d", param[0].val.integer)) !=
+ COM_OK)
+ return retval;
+ return COM_OK_NOPROMPT;
+}
+
+PUBLIC int
+com_promote(int p, param_list param)
+{
+ int retval;
+
+ ASSERT(param[0].type == TYPE_WORD);
+
+ if ((retval = pcommand(p, "set promote %s", param[0].val.word)) !=
+ COM_OK)
+ return retval;
+ return COM_OK_NOPROMPT;
+}
+
+PUBLIC int
+com_alias(int p, param_list param)
+{
+ int al;
+
+ if (param[0].type == TYPE_NULL) {
+ for (al = 0; al < parray[p].numAlias; al++) {
+ pprintf(p, "%s -> %s\n",
+ parray[p].alias_list[al].comm_name,
+ parray[p].alias_list[al].alias);
+ }
+
+ return COM_OK;
+ }
+
+ al = alias_lookup(param[0].val.word, parray[p].alias_list,
+ parray[p].numAlias);
+
+ if (param[1].type == TYPE_NULL) {
+ if (al < 0) {
+ pprintf(p, "You have no alias named '%s'.\n",
+ param[0].val.word);
+ } else {
+ pprintf(p, "%s -> %s\n",
+ parray[p].alias_list[al].comm_name,
+ parray[p].alias_list[al].alias);
+ }
+ } else {
+ if (al < 0) {
+ if (parray[p].numAlias >= MAX_ALIASES - 1) {
+ pprintf(p, "You have your maximum of %d "
+ "aliases.\n", (MAX_ALIASES - 1));
+ } else {
+ if (!strcmp(param[0].val.string, "quit")) {
+ pprintf(p, "You can't alias this "
+ "command.\n");
+ } else if (!strcmp(param[0].val.string,
+ "unalias")) {
+ pprintf(p, "You can't alias this "
+ "command.\n");
+ } else {
+ parray[p].alias_list[parray[p].numAlias].comm_name =
+ xstrdup(param[0].val.word);
+ parray[p].alias_list[parray[p].numAlias].alias =
+ xstrdup(param[1].val.string);
+ parray[p].numAlias++;
+ pprintf(p, "Alias set.\n");
+ }
+ }
+ } else {
+ rfree(parray[p].alias_list[al].alias);
+ parray[p].alias_list[al].alias =
+ xstrdup(param[1].val.string);
+ pprintf(p, "Alias replaced.\n");
+ }
+
+ parray[p].alias_list[parray[p].numAlias].comm_name = NULL;
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_unalias(int p, param_list param)
+{
+ int al;
+
+ ASSERT(param[0].type == TYPE_WORD);
+
+ al = alias_lookup(param[0].val.word, parray[p].alias_list,
+ parray[p].numAlias);
+
+ if (al < 0) {
+ pprintf(p, "You have no alias named '%s'.\n",
+ param[0].val.word);
+ } else {
+ bool removed = false;
+ const int sz = (int) ARRAY_SIZE(parray[0].alias_list);
+
+ rfree(parray[p].alias_list[al].comm_name);
+ rfree(parray[p].alias_list[al].alias);
+
+ parray[p].alias_list[al].comm_name = NULL;
+ parray[p].alias_list[al].alias = NULL;
+
+ for (int i = al; i < parray[p].numAlias; i++) {
+ if (i >= sz || i + 1 >= sz) {
+ warnx("%s: overflowed array index read/write",
+ __func__);
+ break;
+ }
+
+ parray[p].alias_list[i].comm_name =
+ parray[p].alias_list[i + 1].comm_name;
+ parray[p].alias_list[i].alias =
+ parray[p].alias_list[i + 1].alias;
+ removed = true;
+ }
+
+ if (!removed) {
+ pprintf(p, "Remove error.\n");
+ return COM_FAILED;
+ }
+
+ parray[p].numAlias--;
+ parray[p].alias_list[parray[p].numAlias].comm_name = NULL;
+ pprintf(p, "Alias removed.\n");
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_servers(int p, param_list param)
+{
+ pprintf(p, "There are no other servers known to this server.\n");
+ (void) param;
+ return COM_OK;
+}
+
+PUBLIC int
+com_index(int p, param_list param)
+{
+ char *iwant, *filenames[1000];
+ char index_default[] = "_index";
+ int i;
+
+ if (param[0].type == TYPE_NULL) {
+ iwant = index_default;
+ } else {
+ iwant = param[0].val.word;
+
+ if (!safestring(iwant)) {
+ pprintf(p, "Illegal character in category %s.\n",
+ iwant);
+ return COM_OK;
+ }
+ }
+
+ i = search_directory(index_dir, iwant, filenames,
+ ARRAY_SIZE(filenames));
+
+ if (i == 0) {
+ pprintf(p, "No index entry for \"%s\".\n", iwant);
+ } else if (i == 1 || !strcmp(*filenames, iwant)) {
+ if (psend_file(p, index_dir, *filenames)) {
+ /*
+ * We should never reach this unless the file
+ * was just deleted.
+ */
+ pprintf(p, "Index file %s could not be found! ",
+ *filenames);
+ pprintf(p, "Please inform an admin of this. "
+ "Thank you.\n");
+ }
+ } else {
+ pprintf(p, "Matches:");
+ display_directory(p, filenames, i);
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_help(int p, param_list param)
+{
+ char *iwant, *filenames[1000];
+ char help_default[] = "_help";
+ int i, UseLang = parray[p].language;
+
+ if (param[0].type == TYPE_NULL) {
+ iwant = help_default;
+ } else {
+ iwant = param[0].val.word;
+
+ if (!safestring(iwant)) {
+ pprintf(p, "Illegal character in command %s.\n", iwant);
+ return COM_OK;
+ }
+ }
+
+ i = search_directory(help_dir[UseLang], iwant, filenames,
+ ARRAY_SIZE(filenames));
+
+ if (i == 0) {
+ if (UseLang != LANG_DEFAULT) {
+ i += search_directory(help_dir[LANG_DEFAULT], iwant,
+ filenames, ARRAY_SIZE(filenames));
+
+ if (i > 0) {
+ pprintf(p, "No help available in %s; using %s "
+ "instead.\n",
+ Language(UseLang),
+ Language(LANG_DEFAULT));
+ UseLang = LANG_DEFAULT;
+ }
+ }
+
+ if (i == 0) {
+ pprintf(p, "No help available on \"%s\".\n", iwant);
+ return COM_OK;
+ }
+ }
+
+ if (i == 1 || !strcmp(*filenames, iwant)) {
+ if (psend_file(p, help_dir[UseLang], *filenames)) {
+ /*
+ * We should never reach this unless the file
+ * was just deleted.
+ */
+ pprintf(p, "Helpfile %s could not be found! ",
+ *filenames);
+ pprintf(p, "Please inform an admin of this. "
+ "Thank you.\n");
+ }
+ } else {
+ pprintf(p, "Matches:");
+ display_directory(p, filenames, i);
+ pprintf(p, "[Type \"info\" for a list of FICS general "
+ "information files.]\n");
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_info(int p, param_list param)
+{
+ char *filenames[1000];
+ int n;
+
+ // XXX: unused
+ (void) param;
+
+ if ((n = search_directory(info_dir, NULL, filenames,
+ ARRAY_SIZE(filenames))) > 0)
+ display_directory(p, filenames, n);
+ return COM_OK;
+}
+
+PRIVATE int
+FindAndShowFile(int p, param_list param, char *dir)
+{
+ char *iwant, *filenames[1000];
+ int i;
+
+ if (param[0].type == TYPE_NULL) {
+ iwant = NULL;
+ } else {
+ iwant = param[0].val.word;
+
+ if (!safestring(iwant)) {
+ pprintf(p, "Illegal character in filename %s.\n",
+ iwant);
+ return COM_OK;
+ }
+ }
+
+ i = search_directory(dir, iwant, filenames, ARRAY_SIZE(filenames));
+
+ if (i == 0) {
+ pprintf(p, "No information available on \"%s\".\n",
+ (iwant ? iwant : ""));
+ } else if (i == 1 || !strcmp(*filenames, iwant ? iwant : "")) {
+ if (psend_file(p, dir, *filenames)) {
+ /*
+ * We should never reach this unless the file
+ * was just deleted.
+ */
+ pprintf(p, "File %s could not be found! ", *filenames);
+ pprintf(p, "Please inform an admin of this. "
+ "Thank you.\n");
+ }
+ } else {
+ if (iwant && *iwant)
+ pprintf(p, "Matches:\n");
+ display_directory(p, filenames, i);
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_uscf(int p, param_list param)
+{
+ return FindAndShowFile(p, param, uscf_dir);
+}
+
+PUBLIC int
+com_adhelp(int p, param_list param)
+{
+ return FindAndShowFile(p, param, adhelp_dir);
+}
+
+PUBLIC int
+com_mailsource(int p, param_list param)
+{
+ char *buffer[1000];
+ char *iwant;
+ char fname[MAX_FILENAME_SIZE];
+ char subj[120];
+ int count;
+
+ if (!parray[p].registered) {
+ pprintf(p, "Only registered people can use the mailsource "
+ "command.\n");
+ return COM_OK;
+ }
+
+ if (param[0].type == TYPE_NULL)
+ iwant = NULL;
+ else
+ iwant = param[0].val.word;
+
+ if ((count = search_directory(source_dir, iwant, buffer,
+ ARRAY_SIZE(buffer))) == 0) {
+ pprintf(p, "Found no source file matching \"%s\".\n",
+ (iwant ? iwant : ""));
+ } else if ((count == 1) || !strcmp(iwant ? iwant : "", *buffer)) {
+ snprintf(subj, sizeof subj, "FICS source file from server "
+ "%s: %s",
+ fics_hostname,
+ *buffer);
+ snprintf(fname, sizeof fname, "%s/%s",
+ source_dir,
+ *buffer);
+
+ mail_file_to_user(p, subj, fname);
+
+ pprintf(p, "Source file %s sent to %s\n", *buffer,
+ parray[p].emailAddress);
+ } else {
+ pprintf(p, "Found %d source files matching that:\n", count);
+
+ if (iwant && *iwant) {
+ display_directory(p, buffer, count);
+ } else { // this junk is to get *.c *.h
+ char *s;
+ multicol *m = multicol_start(count);
+
+ for (int i = 0; i < count; i++) {
+ s = (buffer[i] + strlen(buffer[i]) - 2);
+
+ if (s >= buffer[i] &&
+ (!strcmp(s, ".c") || !strcmp(s, ".h")))
+ multicol_store(m, buffer[i]);
+ }
+
+ multicol_pprint(m, p, 78, 1);
+ multicol_end(m);
+ }
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_mailhelp(int p, param_list param)
+{
+ char *buffer[1000];
+ char *iwant;
+ char fname[MAX_FILENAME_SIZE];
+ char subj[120];
+ int count;
+ int lang = parray[p].language;
+
+ if (!parray[p].registered) {
+ pprintf(p, "Only registered people can use the mailhelp "
+ "command.\n");
+ return COM_OK;
+ }
+
+ if (param[0].type == TYPE_NULL)
+ iwant = NULL;
+ else
+ iwant = param[0].val.word;
+
+ count = search_directory(help_dir[lang], iwant, buffer,
+ ARRAY_SIZE(buffer));
+
+ if (count == 0 && lang != LANG_DEFAULT) {
+ count += search_directory(help_dir[LANG_DEFAULT], iwant, buffer,
+ ARRAY_SIZE(buffer));
+
+ if (count > 0) {
+ pprintf(p, "No help available in %s; "
+ "using %s instead.\n",
+ Language(lang),
+ Language(LANG_DEFAULT));
+ lang = LANG_DEFAULT;
+ }
+ }
+
+ if (count == 0) {
+ pprintf(p, "Found no help file matching \"%s\".\n",
+ (iwant ? iwant : ""));
+ } else if (count == 1 || !strcmp(*buffer, iwant ? iwant : "")) {
+ snprintf(subj, sizeof subj, "FICS help file from server %s: %s",
+ fics_hostname,
+ *buffer);
+ snprintf(fname, sizeof fname, "%s/%s",
+ help_dir[lang],
+ *buffer);
+
+ mail_file_to_user(p, subj, fname);
+
+ pprintf(p, "Help file %s sent to %s\n", *buffer,
+ parray[p].emailAddress);
+ } else {
+ pprintf(p, "Found %d helpfiles matching that:\n", count);
+ display_directory(p, buffer, count);
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_handles(int p, param_list param)
+{
+ char *buffer[1000];
+ char pdir[MAX_FILENAME_SIZE];
+ int count;
+
+ snprintf(pdir, sizeof pdir, "%s/%c", player_dir, param[0].val.word[0]);
+
+ count = search_directory(pdir, param[0].val.word, buffer,
+ ARRAY_SIZE(buffer));
+ pprintf(p, "Found %d names.\n", count);
+
+ if (count > 0)
+ display_directory(p, buffer, count);
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_getpi(int p, param_list param)
+{
+ int p1;
+
+ if (!in_list(p, L_TD, parray[p].name)) {
+ pprintf(p, "Only TD programs are allowed to use this command."
+ "\n");
+ return COM_OK;
+ }
+
+ if ((p1 = player_find_bylogin(param[0].val.word)) < 0 ||
+ parray[p1].registered == 0)
+ return COM_OK;
+
+ if (!parray[p1].registered) {
+ pprintf(p, "*getpi %s -1 -1 -1*\n", parray[p1].name);
+ } else {
+ pprintf(p, "*getpi %s %d %d %d*\n", parray[p1].name,
+ parray[p1].w_stats.rating,
+ parray[p1].b_stats.rating,
+ parray[p1].s_stats.rating);
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_getps(int p, param_list param)
+{
+ int p1;
+
+ if (((p1 = player_find_bylogin(param[0].val.word)) < 0 ||
+ parray[p1].registered == 0) ||
+ parray[p1].game < 0) {
+ pprintf(p, "*status %s 1*\n", param[0].val.word);
+ return COM_OK;
+ }
+
+ pprintf(p, "*status %s 0 %s*\n", parray[p1].name,
+ parray[(parray[p1].opponent)].name);
+ return COM_OK;
+}
+
+PUBLIC int
+com_limits(int p, param_list param)
+{
+ // XXX: unused
+ (void) param;
+
+ pprintf(p, "\nCurrent hardcoded limits:\n");
+ pprintf(p, " Max number of players: %d\n", MAX_PLAYER);
+ pprintf(p, " Max number of channels and max capacity: %d\n",
+ MAX_CHANNELS);
+ pprintf(p, " Max number of channels one can be in: %d\n",
+ MAX_INCHANNELS);
+ pprintf(p, " Max number of people on the notify list: %d\n",
+ MAX_NOTIFY);
+ pprintf(p, " Max number of aliases: %d\n", (MAX_ALIASES - 1));
+ pprintf(p, " Max number of games you can observe at a time: %d\n",
+ MAX_OBSERVE);
+ pprintf(p, " Max number of requests pending: %d\n", MAX_PENDING);
+ pprintf(p, " Max number of people on the censor list: %d\n",
+ MAX_CENSOR);
+ pprintf(p, " Max number of people in a simul game: %d\n", MAX_SIMUL);
+ pprintf(p, " Max number of messages one can receive: %d\n",
+ MAX_MESSAGES);
+ pprintf(p, " Min number of games to be active: %d\n", PROVISIONAL);
+
+ if (parray[p].adminLevel < ADMIN_ADMIN &&
+ !titled_player(p, parray[p].login)) {
+ pprintf(p, " Size of journal (entries): %d\n", MAX_JOURNAL);
+ } else {
+ pprintf(p, " Size of journal (entries): 26\n");
+ }
+
+ pprintf(p, "\nAdmin settable limits:\n");
+ pprintf(p, " Shout quota gives two shouts per %d seconds.\n",
+ quota_time);
+
+ return COM_OK;
+}
diff --git a/FICS/comproc.h b/FICS/comproc.h
new file mode 100644
index 0000000..30ef3ff
--- /dev/null
+++ b/FICS/comproc.h
@@ -0,0 +1,85 @@
+/* comproc.h
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 23/12/09 Fixed implicit ints
+ Markus Uhlin 23/12/19 Sorted declarations
+ Markus Uhlin 24/01/03 Added argument lists
+*/
+
+#ifndef _COMPROC_H
+#define _COMPROC_H
+
+#include "command.h"
+
+extern const int none;
+extern const int blitz_rat;
+extern const int std_rat;
+extern const int wild_rat;
+extern const int light_rat;
+
+/*
+ * The number of news items in the index file.
+ */
+extern int num_news;
+
+extern int FindPlayer(int, char *, int *, int *);
+
+extern int com_adhelp(int, param_list);
+extern int com_alias(int, param_list);
+extern int com_bell(int, param_list);
+extern int com_date(int, param_list);
+extern int com_flip(int, param_list);
+extern int com_getpi(int, param_list);
+extern int com_getps(int, param_list);
+extern int com_handles(int, param_list);
+extern int com_help(int, param_list);
+extern int com_highlight(int, param_list);
+extern int com_index(int, param_list);
+extern int com_info(int, param_list);
+extern int com_limits(int, param_list);
+extern int com_llogons(int, param_list);
+extern int com_logons(int, param_list);
+extern int com_mailhelp(int, param_list);
+extern int com_mailsource(int, param_list);
+extern int com_more(int, param_list);
+extern int com_news(int, param_list);
+extern int com_open(int, param_list);
+extern int com_password(int, param_list);
+extern int com_prefresh(int, param_list);
+extern int com_promote(int, param_list);
+extern int com_quit(int, param_list);
+extern int com_rating_recalc(int, param_list);
+extern int com_refresh(int, param_list);
+extern int com_servers(int, param_list);
+extern int com_set(int, param_list);
+extern int com_simopen(int, param_list);
+extern int com_stats(int, param_list);
+extern int com_style(int, param_list);
+extern int com_unalias(int, param_list);
+extern int com_uptime(int, param_list);
+extern int com_uscf(int, param_list);
+extern int com_who(int, param_list);
+
+extern void rscan_news(FILE *, int, time_t);
+extern void rscan_news2(FILE *, int, int);
+
+#endif /* _COMPROC_H */
diff --git a/FICS/config.h b/FICS/config.h
new file mode 100644
index 0000000..6edcb66
--- /dev/null
+++ b/FICS/config.h
@@ -0,0 +1,81 @@
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 94/03/08 Created
+ Sparky 95/12/30 Beautified
+ Markus Uhlin 23/12/17 Revised
+ Markus Uhlin 23/12/18 Include 'ficspaths.h'
+*/
+
+#ifndef _CONFIG_H
+#define _CONFIG_H
+
+/*
+ * The port on which the server binds
+ */
+#define DEFAULT_PORT 5000
+
+/*
+ * Must be a dns recognisable host name
+ */
+#define SERVER_HOSTNAME "rpblc.net"
+
+/*
+ * At AFICS we just use 'fics'. But for your server you might want to
+ * change this e.g. to BICS, EICS, DICS etc.
+ */
+#define SERVER_NAME "Xfics" /* for pgn output */
+
+#define SERVER_LOCATION "Las Vegas, USA" /* for pgn output */
+
+/*
+ * Which is the default language for help files? See 'variable.h' for
+ * the currently available settings.
+ */
+#define LANG_DEFAULT LANG_ENGLISH
+
+/*
+ * Locations of the data, players and games directories.
+ */
+#include "ficspaths.h"
+
+/*
+ * Where the standard ucb mail program is
+ */
+//#define MAILPROGRAM "/usr/bin/mail"
+
+/*
+ * 'SENDMAILPROG' is a faster and more reliable means of sending mail
+ * if defined. Make sure your system mailer agent is defined here
+ * properly for your system with respect to name, location and
+ * options. These may differ significantly depending on the type of
+ * system and what mailer is installed.
+ */
+#define SENDMAILPROG "/usr/sbin/sendmail -t"
+
+/*
+ * Details of the head admin
+ */
+#define HADMINHANDLE "maxxe"
+#define HADMINEMAIL "maxxe@rpblc.net"
+
+/*
+ * Registration mail address
+ */
+#define REGMAIL "maxxe@rpblc.net"
+
+#endif /* _CONFIG_H */
diff --git a/FICS/eco.c b/FICS/eco.c
new file mode 100644
index 0000000..3598943
--- /dev/null
+++ b/FICS/eco.c
@@ -0,0 +1,535 @@
+/* eco.c
+ *
+ */
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include <err.h>
+
+#include "board.h"
+#include "command.h"
+#include "config.h"
+#include "eco.h"
+#include "gamedb.h"
+#include "gameproc.h"
+#include "obsproc.h"
+#include "playerdb.h"
+#include "utils.h"
+
+#if __linux__
+#include <bsd/string.h>
+#endif
+
+#define FENPOS_SIZE 73
+#define ONMOVE_SIZE 2
+
+#define ECO_MAXFILENAME 1024
+#define ECO_MAXTMP 1024
+
+#define SCAN_FP_AND_ONMOVE "%72[\x21-z] %1s"
+
+#define SCAN_ECO "%3[0-z]"
+#define SCAN_NIC "%5[.-z]"
+#define SCAN_LONG "%255[^*\n]"
+
+PRIVATE char *book_dir = DEFAULT_BOOK;
+
+PRIVATE ECO_entry *ECO_book[1096];
+PRIVATE NIC_entry *NIC_book[1096];
+PRIVATE LONG_entry *LONG_book[4096];
+
+PRIVATE int ECO_entries, NIC_entries, LONG_entries;
+
+PRIVATE inline int
+fencmp(const unsigned char *pos1, const char *pos2)
+{
+ return strcmp((const char *)pos1, pos2);
+}
+
+PUBLIC char *
+boardToFEN(int g)
+{
+ int FENcount = 0;
+ int space = 0;
+ static char FENstring[80];
+
+ for (int i = 7; i >= 0; i--) {
+ for (int j = 0; j < 8; j++) {
+ switch (garray[g].game_state.board[j][i]) {
+ case W_PAWN:
+ SPACE_CHK();
+ FENstring[FENcount++] = 'P';
+ break;
+ case W_ROOK:
+ SPACE_CHK();
+ FENstring[FENcount++] = 'R';
+ break;
+ case W_KNIGHT:
+ SPACE_CHK();
+ FENstring[FENcount++] = 'N';
+ break;
+ case W_BISHOP:
+ SPACE_CHK();
+ FENstring[FENcount++] = 'B';
+ break;
+ case W_QUEEN:
+ SPACE_CHK();
+ FENstring[FENcount++] = 'Q';
+ break;
+ case W_KING:
+ SPACE_CHK();
+ FENstring[FENcount++] = 'K';
+ break;
+ case B_PAWN:
+ SPACE_CHK();
+ FENstring[FENcount++] = 'p';
+ break;
+ case B_ROOK:
+ SPACE_CHK();
+ FENstring[FENcount++] = 'r';
+ break;
+ case B_KNIGHT:
+ SPACE_CHK();
+ FENstring[FENcount++] = 'n';
+ break;
+ case B_BISHOP:
+ SPACE_CHK();
+ FENstring[FENcount++] = 'b';
+ break;
+ case B_QUEEN:
+ SPACE_CHK();
+ FENstring[FENcount++] = 'q';
+ break;
+ case B_KING:
+ SPACE_CHK();
+ FENstring[FENcount++] = 'k';
+ break;
+ default:
+ space++;
+ break;
+ } /* switch */
+ } /* for */
+
+ if (space > 0) {
+ FENstring[FENcount++] = (space + '0');
+ space = 0;
+ }
+
+ FENstring[FENcount++] = '/';
+ } /* for */
+
+ FENstring[--FENcount] = ' ';
+ FENstring[++FENcount] = ((garray[g].game_state.onMove == WHITE)
+ ? 'w'
+ : 'b');
+ FENstring[++FENcount] = '\0';
+
+ return FENstring;
+}
+
+PUBLIC void
+ECO_init(void)
+{
+ FILE *fp;
+ char ECO[4] = {0,0,0,0};
+ char FENpos[FENPOS_SIZE] = { '\0' };
+ char filename[ECO_MAXFILENAME] = { '\0' };
+ char onMove[ONMOVE_SIZE] = {0,0};
+ char tmp[ECO_MAXTMP] = { '\0' };
+ char *ptmp = tmp;
+ int i = 0;
+
+ snprintf(filename, sizeof filename, "%s/eco999.idx", book_dir);
+
+ if ((fp = fopen(filename, "r")) == NULL)
+ err(1, "Could not open ECO file (%s)", filename);
+
+ while (!feof(fp)) {
+ (void) strlcpy(ptmp, "", sizeof tmp);
+
+ if (fgets(ptmp, sizeof tmp, fp) == NULL ||
+ feof(fp))
+ break;
+
+ if (sscanf(ptmp, SCAN_FP_AND_ONMOVE, FENpos, onMove) != 2) {
+ warnx("%s: sscanf() error (%s:%d)", __func__,
+ filename, i);
+ break;
+ } else if (strlcat(FENpos, " ", sizeof FENpos) >= sizeof FENpos ||
+ strlcat(FENpos, onMove, sizeof FENpos) >= sizeof FENpos) {
+ warnx("%s: strlcat() error (%s:%d)", __func__,
+ filename, i);
+ break;
+ }
+
+ (void) strlcpy(ptmp, "", sizeof tmp);
+
+ if (fgets(ptmp, sizeof tmp, fp) == NULL ||
+ feof(fp))
+ break;
+ else if (sscanf(ptmp, SCAN_ECO, ECO) != 1) {
+ warnx("%s: scan eco error (%s:%d)", __func__,
+ filename, i);
+ break;
+ }
+
+ if ((ECO_book[i] = malloc(sizeof(ECO_entry))) == NULL)
+ err(1, "Cound not alloc mem for ECO entry %d", i);
+
+ (void) strlcpy(ECO_book[i]->ECO, ECO,
+ sizeof(ECO_book[i]->ECO));
+ (void) strlcpy(ECO_book[i]->FENpos, FENpos,
+ sizeof(ECO_book[i]->FENpos));
+
+ ++i;
+ }
+
+ fclose(fp);
+ ECO_book[i] = NULL;
+
+ fprintf(stderr, "%d entries in ECO book\n", i);
+ ECO_entries = i;
+
+ while (--i >= 0) {
+ if (ECO_book[i] == NULL) {
+ fprintf(stderr, "ERROR! ECO book position number %d "
+ "is NULL.", i);
+ }
+ }
+}
+
+PUBLIC void
+NIC_init(void)
+{
+ FILE *fp;
+ char FENpos[FENPOS_SIZE] = { '\0' };
+ char NIC[6] = {0,0,0,0,0,0};
+ char filename[ECO_MAXFILENAME] = { '\0' };
+ char onMove[ONMOVE_SIZE] = {0,0};
+ char tmp[ECO_MAXTMP] = { '\0' };
+ char *ptmp = tmp;
+ int i = 0;
+
+ snprintf(filename, sizeof filename, "%s/nic999.idx", book_dir);
+
+ if ((fp = fopen(filename, "r")) == NULL)
+ err(1, "Could not open NIC file (%s)", filename);
+
+ while (!feof(fp)) {
+ (void) strlcpy(ptmp, "", sizeof tmp);
+
+ if (fgets(ptmp, sizeof tmp, fp) == NULL ||
+ feof(fp))
+ break;
+
+ if (sscanf(ptmp, SCAN_FP_AND_ONMOVE, FENpos, onMove) != 2) {
+ warnx("%s: sscanf() error (%s:%d)", __func__,
+ filename, i);
+ break;
+ } else if (strlcat(FENpos, " ", sizeof FENpos) >= sizeof FENpos ||
+ strlcat(FENpos, onMove, sizeof FENpos) >= sizeof FENpos) {
+ warnx("%s: strlcat() error (%s:%d)", __func__,
+ filename, i);
+ break;
+ }
+
+ (void) strlcpy(ptmp, "", sizeof tmp);
+
+ if (fgets(ptmp, sizeof tmp, fp) == NULL ||
+ feof(fp))
+ break;
+ else if (sscanf(ptmp, SCAN_NIC, NIC) != 1) {
+ warnx("%s: scan nic error (%s:%d)", __func__,
+ filename, i);
+ break;
+ }
+
+ if ((NIC_book[i] = malloc(sizeof(NIC_entry))) == NULL)
+ err(1, "Cound not alloc mem for NIC entry %d", i);
+
+ (void) strlcpy(NIC_book[i]->NIC, NIC,
+ sizeof(NIC_book[i]->NIC));
+ (void) strlcpy(NIC_book[i]->FENpos, FENpos,
+ sizeof(NIC_book[i]->FENpos));
+
+ ++i;
+ }
+
+ fclose(fp);
+ NIC_book[i] = NULL;
+
+ fprintf(stderr, "%d entries in NIC book\n", i);
+ NIC_entries = i;
+}
+
+PUBLIC void
+LONG_init(void)
+{
+ FILE *fp;
+ char FENpos[FENPOS_SIZE] = { '\0' };
+ char LONG[256] = { '\0' };
+ char filename[ECO_MAXFILENAME] = { '\0' };
+ char onMove[ONMOVE_SIZE] = {0,0};
+ char tmp[ECO_MAXTMP] = { '\0' };
+ char *ptmp = tmp;
+ int i = 0;
+
+ snprintf(filename, sizeof filename, "%s/long999.idx", book_dir);
+
+ if ((fp = fopen(filename, "r")) == NULL)
+ err(1, "Could not open LONG file (%s)", filename);
+
+ while (!feof(fp)) {
+ (void) strlcpy(ptmp, "", sizeof tmp);
+
+ if (fgets(ptmp, sizeof tmp, fp) == NULL ||
+ feof(fp))
+ break;
+
+ if (sscanf(ptmp, SCAN_FP_AND_ONMOVE, FENpos, onMove) != 2) {
+ warnx("%s: sscanf() error (%s:%d)", __func__,
+ filename, i);
+ break;
+ } else if (strlcat(FENpos, " ", sizeof FENpos) >= sizeof FENpos ||
+ strlcat(FENpos, onMove, sizeof FENpos) >= sizeof FENpos) {
+ warnx("%s: strlcat() error (%s:%d)", __func__,
+ filename, i);
+ break;
+ }
+
+ (void) strlcpy(ptmp, "", sizeof tmp);
+
+ if (fgets(ptmp, sizeof tmp, fp) == NULL ||
+ feof(fp))
+ break;
+ else if (sscanf(ptmp, SCAN_LONG, LONG) != 1) {
+ warnx("%s: scan long error (%s:%d)", __func__,
+ filename, i);
+ break;
+ }
+
+ if ((LONG_book[i] = malloc(sizeof(LONG_entry))) == NULL)
+ err(1, "Cound not alloc mem for LONG entry %d", i);
+
+ (void) strlcpy(LONG_book[i]->LONG, LONG,
+ sizeof(LONG_book[i]->LONG));
+ (void) strlcpy(LONG_book[i]->FENpos, FENpos,
+ sizeof(LONG_book[i]->FENpos));
+
+ ++i;
+ }
+
+ fclose(fp);
+ LONG_book[i] = NULL;
+
+ fprintf(stderr, "%d entries in LONG book\n", i);
+ LONG_entries = i;
+}
+
+PUBLIC void
+BookInit(void)
+{
+ ECO_init();
+ NIC_init();
+ LONG_init();
+}
+
+PUBLIC char *
+getECO(int g)
+{
+ static char ECO[4];
+
+#ifndef IGNORE_ECO
+ int i, flag, l, r, x;
+
+ if (parray[garray[g].white].private ||
+ parray[garray[g].black].private) {
+ (void) strlcpy(ECO, "---", sizeof ECO);
+ return ECO;
+ } else {
+ if (garray[g].type == TYPE_WILD) {
+ (void) strlcpy(ECO, "---", sizeof ECO);
+ return ECO;
+ } else if (garray[g].moveList == NULL) {
+ (void) strlcpy(ECO, "***", sizeof ECO);
+ return ECO;
+ } else {
+ (void) strlcpy(ECO, "A00", sizeof ECO);
+ }
+ }
+
+ flag = 0;
+ i = garray[g].numHalfMoves;
+
+ while (i > 0 && !flag) {
+ l = 0;
+ r = (ECO_entries - 1);
+
+ while ((r >= l) && !flag) {
+ x = ((l + r) / 2);
+
+ if (fencmp(garray[g].moveList[i].FENpos,
+ ECO_book[x]->FENpos) < 0)
+ r = (x - 1);
+ else
+ l = (x + 1);
+
+ if (!fencmp(garray[g].moveList[i].FENpos,
+ ECO_book[x]->FENpos)) {
+ (void)strlcpy(ECO, ECO_book[x]->ECO,
+ sizeof ECO);
+ flag = 1;
+ }
+ }
+
+ i--;
+ } /* while */
+#else
+ (void) strlcpy(ECO, "---", sizeof ECO);
+#endif
+ return ECO;
+}
+
+PUBLIC int
+com_eco(int p, param_list param)
+{
+#ifndef IGNORE_ECO
+ int g1, p1;
+ int i, flag = 0, x, l, r;
+
+ if (param[0].type == TYPE_NULL) { // own game
+ if (parray[p].game < 0) {
+ pprintf(p, "You are not playing or examining a game."
+ "\n");
+ return COM_OK;
+ }
+
+ g1 = parray[p].game;
+
+ if (garray[g1].status != GAME_EXAMINE && !pIsPlaying(p))
+ return COM_OK;
+ } else {
+ if ((g1 = GameNumFromParam(p, &p1, &param[0])) < 0)
+ return COM_OK;
+ if (g1 >= g_num ||
+ (garray[g1].status != GAME_ACTIVE &&
+ garray[g1].status != GAME_EXAMINE)) {
+ pprintf(p, "There is no such game.\n");
+ return COM_OK;
+ }
+ }
+
+ if ((parray[garray[g1].white].private ||
+ parray[garray[g1].black].private) &&
+ parray[p].adminLevel == 0) {
+ pprintf(p, "Sorry - that game is private.\n");
+ return COM_OK;
+ } else {
+ if (garray[g1].type == TYPE_WILD) {
+ pprintf(p, "That game is a wild game.\n");
+ return COM_OK;
+ }
+ }
+
+ pprintf(p, "Info about game %d: \"%s vs. %s\"\n\n", (g1 + 1),
+ garray[g1].white_name,
+ garray[g1].black_name);
+
+ if (garray[g1].moveList == NULL)
+ return COM_OK;
+
+ /*
+ * ECO
+ */
+ flag = 0;
+ i = garray[g1].numHalfMoves;
+
+ while (i > 0 && !flag) {
+ l = 0;
+ r = (ECO_entries - 1);
+
+ while (r >= l && !flag) {
+ x = ((l + r) / 2);
+
+ if (fencmp(garray[g1].moveList[i].FENpos,
+ ECO_book[x]->FENpos) < 0)
+ r = (x - 1);
+ else
+ l = (x + 1);
+
+ if (!fencmp(garray[g1].moveList[i].FENpos,
+ ECO_book[x]->FENpos)) {
+ pprintf(p, " ECO[%3d]: %s\n", i,
+ ECO_book[x]->ECO);
+ flag = 1;
+ }
+ }
+
+ i--;
+ } /* while */
+
+ /*
+ * NIC
+ */
+ flag = 0;
+ i = garray[g1].numHalfMoves;
+
+ while (i > 0 && !flag) {
+ l = 0;
+ r = (NIC_entries - 1);
+
+ while (r >= l && !flag) {
+ x = ((l + r) / 2);
+
+ if (fencmp(garray[g1].moveList[i].FENpos,
+ NIC_book[x]->FENpos) < 0)
+ r = (x - 1);
+ else
+ l = (x + 1);
+
+ if (!fencmp(garray[g1].moveList[i].FENpos,
+ NIC_book[x]->FENpos)) {
+ pprintf(p, " NIC[%3d]: %s\n", i,
+ NIC_book[x]->NIC);
+ flag = 1;
+ }
+ }
+
+ i--;
+ } /* while */
+
+ /*
+ * LONG
+ */
+ flag = 0;
+ i = garray[g1].numHalfMoves;
+
+ while (i > 0 && !flag) {
+ l = 0;
+ r = (LONG_entries - 1);
+
+ while (r >= l && !flag) {
+ x = ((l + r) / 2);
+
+ if (fencmp(garray[g1].moveList[i].FENpos,
+ LONG_book[x]->FENpos) < 0)
+ r = (x - 1);
+ else
+ l = (x + 1);
+
+ if (!fencmp(garray[g1].moveList[i].FENpos,
+ LONG_book[x]->FENpos)) {
+ pprintf(p, " LONG[%3d]: %s\n", i,
+ LONG_book[x]->LONG);
+ flag = 1;
+ }
+ }
+
+ i--;
+ } /* while */
+#else
+ pprintf(p, "ECO not available... out of service!.\n");
+#endif
+ return COM_OK;
+}
diff --git a/FICS/eco.h b/FICS/eco.h
new file mode 100644
index 0000000..c1ab418
--- /dev/null
+++ b/FICS/eco.h
@@ -0,0 +1,37 @@
+#ifndef _ECO_H
+#define _ECO_H
+
+#include "command.h" /* param_list */
+
+#define SPACE_CHK()\
+ do {\
+ if (space > 0) {\
+ FENstring[FENcount++] = (space + '0');\
+ space = 0;\
+ }\
+ } while (0)
+
+typedef struct {
+ char ECO[4];
+ char FENpos[80];
+} ECO_entry;
+
+typedef struct {
+ char NIC[6];
+ char FENpos[80];
+} NIC_entry;
+
+typedef struct {
+ char LONG[80];
+ char FENpos[80];
+} LONG_entry;
+
+extern char *boardToFEN(int);
+extern char *getECO(int);
+extern int com_eco(int, param_list);
+extern void BookInit(void);
+extern void ECO_init(void);
+extern void LONG_init(void);
+extern void NIC_init(void);
+
+#endif
diff --git a/FICS/fics_addplayer.c b/FICS/fics_addplayer.c
new file mode 100644
index 0000000..160b28d
--- /dev/null
+++ b/FICS/fics_addplayer.c
@@ -0,0 +1,202 @@
+/* fics_addplayer.c
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1994 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 94/03/07 Created
+ Markus Uhlin 23/12/09 Revamped the file
+ Markus Uhlin 23/12/23 Fixed crypt()
+ Markus Uhlin 23/12/28 Switched to arc4random_uniform()
+ Markus Uhlin 24/01/01 Size-bounded copying
+ Markus Uhlin 24/04/01 Fixed empty hostname
+ Markus Uhlin 24/04/04 Added usage of explicit_bzero()
+ Markus Uhlin 24/05/25 Added command-line option 'a'
+ Markus Uhlin 25/03/23 Output restart notice if the
+ player is admin.
+*/
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include <err.h>
+#include <string.h>
+
+#include "command.h"
+#include "config.h"
+#include "fics_getsalt.h"
+#include "ficsmain.h"
+#include "playerdb.h"
+#include "utils.h"
+
+#if __linux__
+#include <bsd/string.h>
+#endif
+
+#define PASSLEN 8
+
+PRIVATE char *funame = NULL;
+PRIVATE char *fname = NULL;
+PRIVATE char *email = NULL;
+
+PRIVATE int admin = 0;
+
+PRIVATE void
+add_handle_to_list(const char *handle)
+{
+ FILE *fp;
+ char path[1024];
+ int fd;
+
+ snprintf(path, sizeof path, "%s/admin", DEFAULT_LISTS);
+
+ if ((fd = open(path, g_open_flags[0], g_open_modes)) < 0) {
+ warn("%s: unable to open %s", __func__, path);
+ return;
+ } else if ((fp = fdopen(fd, "a")) == NULL) {
+ warn("%s: unable to open %s", __func__, path);
+ close(fd);
+ return;
+ }
+
+ fprintf(fp, "%s\n", handle);
+ fclose(fp);
+}
+
+PRIVATE __dead void
+usage(char *progname)
+{
+ fprintf(stderr, "Usage: %s [-a] <UserName> <FullName> <EmailAddress>\n",
+ progname);
+ exit(1);
+}
+
+PUBLIC int
+main(int argc, char *argv[])
+{
+ char password[PASSLEN + 1];
+ char salt[FICS_SALT_SIZE];
+ char text[2048];
+ int i;
+ int p;
+
+ for (i = 1; i < argc; i++) {
+ if (argv[i][0] == '-') {
+ switch (argv[i][1]) {
+ case 'a':
+ admin = 1;
+ break;
+ case '?':
+ case 'h':
+ default:
+ usage(argv[0]);
+ break;
+ }
+ } else {
+ if (funame == NULL)
+ funame = argv[i];
+ else if (fname == NULL)
+ fname = argv[i];
+ else if (email == NULL)
+ email = argv[i];
+ else
+ usage(argv[0]);
+ }
+ }
+
+ if (funame == NULL || fname == NULL || email == NULL)
+ usage(argv[0]);
+
+ /* Add the player here */
+ if (strlen(funame) >= MAX_LOGIN_NAME)
+ errx(1, "Player name is too long");
+ else if (strlen(funame) < 3)
+ errx(1, "Player name is too short");
+ else if (!alphastring(funame)) {
+ errx(1, "Illegal characters in player name. "
+ "Only A-Za-z allowed.");
+ }
+
+ player_init(0);
+ p = player_new();
+
+ if (!player_read(p, funame))
+ errx(1, "%s already exists.", funame);
+
+ parray[p].name = xstrdup(funame);
+ parray[p].login = stolower(xstrdup(funame));
+ parray[p].fullName = xstrdup(fname);
+ parray[p].emailAddress = xstrdup(email);
+
+ for (i = 0; i < PASSLEN; i++)
+ password[i] = ('a' + arc4random_uniform(26));
+ password[i] = '\0';
+
+ if (strlcpy(salt, fics_getsalt(), sizeof salt) >= sizeof salt)
+ errx(1, "salt truncated");
+
+ parray[p].passwd = xstrdup(crypt(password, salt));
+ parray[p].registered = 1;
+ parray[p].rated = 1;
+
+ if (strcasecmp(parray[p].login, HADMINHANDLE) == 0 || admin) {
+ parray[p].adminLevel = ADMIN_ADMIN;
+ add_handle_to_list(parray[p].login);
+ }
+
+ player_save(p);
+
+ printf("Added player account: >%s< >%s< >%s< >%s<\n",
+ funame, fname, email, password);
+ printf("Admin: %s\n", (parray[p].adminLevel > 0 ? "Yes" : "No"));
+
+ if (parray[p].adminLevel > 0) {
+ printf("Player is admin. Please restart FICS in order for "
+ "the changes to take effect.\n");
+ }
+
+ snprintf(text, sizeof text,
+ "\nYour player account has been created.\n\n"
+ "Login Name: %s\n"
+ "Full Name: %s\n"
+ "Email Address: %s\n"
+ "Initial Password: %s\n\n"
+
+ "If any of this information is incorrect, please\n"
+ "contact the administrator to get it corrected.\n\n"
+
+ "You may change your password with the password\n"
+ "command on the server.\n\n"
+
+ "Please be advised that if this is an unauthorized\n"
+ "duplicate account for you, by using it you take\n"
+ "the risk of being banned from accessing this chess\n"
+ "server.\n\n"
+
+ "To connect to the server and use this account:\n\n"
+ "\ttelnet %s 5000\n\n"
+ "and enter your handle name and password.\n\n"
+
+ "Regards,\n\nThe FICS admins\n", funame, fname, email, password,
+ SERVER_HOSTNAME);
+
+ mail_string_to_address(email, "FICS Account Created", text);
+ explicit_bzero(password, sizeof(password));
+ explicit_bzero(text, sizeof(text));
+ return 0;
+}
diff --git a/FICS/fics_getsalt.cpp b/FICS/fics_getsalt.cpp
new file mode 100644
index 0000000..95705b0
--- /dev/null
+++ b/FICS/fics_getsalt.cpp
@@ -0,0 +1,46 @@
+/*
+ * Written by Markus Uhlin <maxxe@rpblc.net>,
+ * 25 Dec 2023.
+ */
+
+#include <cstdio>
+#include <cstring>
+#include <random>
+
+#include "fics_getsalt.h"
+
+char *
+fics_getsalt(void)
+{
+ static bool init_done = false;
+ static const char legal_index[] =
+ "./0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz";
+ static char salt[FICS_SALT_SIZE];
+
+ if (!init_done) {
+ strcpy(salt, FICS_SALT_BEG);
+ init_done = true;
+ }
+
+ std::random_device rd;
+ std::mt19937 gen(rd());
+ std::uniform_int_distribution<size_t> dist(0, strlen(legal_index) - 1);
+
+ for (size_t i = 7; i < sizeof salt; i++)
+ salt[i] = legal_index[dist(gen)];
+ salt[sizeof salt - 1] = '\0';
+ return (&salt[0]);
+}
+
+#if SELF_TEST
+int
+main(void)
+{
+ puts(fics_getsalt());
+ puts(fics_getsalt());
+ puts(fics_getsalt());
+ return 0;
+}
+#endif
diff --git a/FICS/fics_getsalt.h b/FICS/fics_getsalt.h
new file mode 100644
index 0000000..317ae27
--- /dev/null
+++ b/FICS/fics_getsalt.h
@@ -0,0 +1,13 @@
+#ifndef FICS_GETSALT_H
+#define FICS_GETSALT_H
+
+#include "common.h"
+
+#define FICS_SALT_BEG "$2b$10$"
+#define FICS_SALT_SIZE 30
+
+__FICS_BEGIN_DECLS
+char *fics_getsalt(void);
+__FICS_END_DECLS
+
+#endif
diff --git a/FICS/ficslim.cpp b/FICS/ficslim.cpp
new file mode 100644
index 0000000..6ea7ffc
--- /dev/null
+++ b/FICS/ficslim.cpp
@@ -0,0 +1,9 @@
+// SPDX-FileCopyrightText: 2024 Markus Uhlin <maxxe@rpblc.net>
+// SPDX-License-Identifier: ISC
+
+#include <limits>
+
+#include "ficslim.h"
+
+const time_t g_time_min = std::numeric_limits<time_t>::min();
+const time_t g_time_max = std::numeric_limits<time_t>::max();
diff --git a/FICS/ficslim.h b/FICS/ficslim.h
new file mode 100644
index 0000000..40f9b9a
--- /dev/null
+++ b/FICS/ficslim.h
@@ -0,0 +1,13 @@
+#ifndef FICS_LIMITS_H
+#define FICS_LIMITS_H
+
+#include <time.h>
+
+#include "common.h"
+
+__FICS_BEGIN_DECLS
+extern const time_t g_time_min;
+extern const time_t g_time_max;
+__FICS_END_DECLS
+
+#endif
diff --git a/FICS/ficsmain.c b/FICS/ficsmain.c
new file mode 100644
index 0000000..664874f
--- /dev/null
+++ b/FICS/ficsmain.c
@@ -0,0 +1,262 @@
+/* ficsmain.c
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 23/12/10 Fixed the includes
+ Markus Uhlin 23/12/12 Revamped the file
+ Markus Uhlin 24/03/16 Fixed unbounded string copying
+ and marked usage() '__dead'.
+ Markus Uhlin 24/06/01 Added command-line option 'l'
+ Markus Uhlin 24/08/03 Added command-line option 'd'
+ Markus Uhlin 24/12/04 Added command-line option 'v'
+*/
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include <sys/param.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include "board.h"
+#include "command.h"
+#include "comproc.h"
+#include "config.h"
+#ifndef IGNORE_ECO
+#include "eco.h"
+#endif
+#include "ficsmain.h"
+#include "legal.h"
+#include "legal2.h"
+#include "network.h"
+#include "playerdb.h"
+#include "ratings.h"
+#include "shutdown.h"
+#include "talkproc.h"
+#include "utils.h"
+
+#if __linux__
+#include <bsd/string.h>
+#endif
+
+PUBLIC const int g_open_flags[2] = {
+ (O_WRONLY|O_CREAT|O_APPEND),
+ (O_WRONLY|O_CREAT|O_TRUNC),
+};
+PUBLIC const mode_t g_open_modes = (S_IWUSR | S_IRUSR);
+
+/* Arguments */
+PUBLIC int port;
+PUBLIC int withConsole;
+
+PRIVATE __dead void usage(char *);
+
+PRIVATE void
+BrokenPipe(int sig)
+{
+ fprintf(stderr, "FICS: Got Broken Pipe (%d)\n", sig);
+}
+
+PRIVATE void
+daemonize(void)
+{
+ int fd[2];
+ mode_t mode[2];
+
+ mode[0] = (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ mode[1] = (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+ if (file_exists(DAEMON_LOCKFILE)) {
+ errx(1, "%s: %s: already exists\ndelete the file manually and "
+ "try again after making sure that\nno copy of the program "
+ "is already running in the background",
+ __func__,
+ DAEMON_LOCKFILE);
+ } else if ((fd[0] = open(DAEMON_LOCKFILE, (O_CREAT | O_RDWR),
+ mode[0])) == -1) {
+ err(1, "%s: open(%s, ...)", __func__, DAEMON_LOCKFILE);
+ } else if ((fd[1] = open(DAEMON_LOGFILE, (O_APPEND | O_CREAT | O_RDWR),
+ mode[1])) == -1) {
+ err(1, "%s: open(%s, ...)", __func__, DAEMON_LOGFILE);
+ } else if (daemon(1, 1) == -1) {
+ int i;
+
+ i = errno;
+ close(fd[0]);
+ close(fd[1]);
+ errno = i;
+
+ err(1, "%s: failed to run in the background", __func__);
+ }
+
+ dprintf(fd[0], "%jd\n", (intmax_t)getpid());
+ close(fd[0]);
+
+ (void) dup2(fd[1], STDIN_FILENO);
+ (void) dup2(fd[1], STDOUT_FILENO);
+ (void) dup2(fd[1], STDERR_FILENO);
+
+ if (fd[1] > 2)
+ close(fd[1]);
+}
+
+PRIVATE void
+GetArgs(int argc, char *argv[])
+{
+ port = DEFAULT_PORT;
+ withConsole = 0;
+
+ for (int i = 1; i < argc; i++) {
+ if (argv[i][0] == '-') {
+ switch (argv[i][1]) {
+ case 'p':
+ if (i == argc - 1)
+ usage(argv[0]);
+ i++;
+ if (sscanf(argv[i], "%d", &port) != 1)
+ usage(argv[0]);
+ break;
+ case 'C':
+ fprintf(stderr, "-C Not implemented!\n");
+ exit(1);
+ withConsole = 1;
+ break;
+ case 'd':
+ daemonize();
+ break;
+ case 'h':
+ usage(argv[0]);
+ break;
+ case 'l':
+ puts(legalNotice);
+ puts(legalNotice2);
+ exit(0);
+ case 'v':
+ printf("%s %s\n", VERS_NUM, COMP_DATE);
+ exit(0);
+ }
+ } else {
+ usage(argv[0]);
+ }
+ }
+}
+
+PRIVATE __dead void
+TerminateServer(int sig)
+{
+ fprintf(stderr, "FICS: Got signal %d\n", sig);
+ output_shut_mess();
+ TerminateCleanup();
+ net_close();
+ exit(EXIT_FAILURE);
+}
+
+PRIVATE void
+main_event_loop(void)
+{
+ comstr_t str;
+ int sockfd = -1;
+
+ while (1) {
+ ngc2(&str, HEARTBEATTIME);
+
+ if (process_heartbeat(&sockfd) == COM_LOGOUT && sockfd != -1) {
+ process_disconnection(sockfd);
+ net_close_connection(sockfd);
+ }
+
+ sockfd = -1;
+ }
+}
+
+PRIVATE __dead void
+usage(char *progname)
+{
+ fprintf(stderr, "Usage: %s [-p port] [-C] [-dhlv]\n", progname);
+ fprintf(stderr, "\t\t-p port\t\tSpecify port. (Default=%d)\n",
+ DEFAULT_PORT);
+ fprintf(stderr, "\t\t-C\t\tStart with console player connected "
+ "to stdin.\n");
+ fprintf(stderr, "\t\t-d\t\tRun in the background.\n");
+ fprintf(stderr, "\t\t-h\t\tDisplay this information.\n");
+ fprintf(stderr, "\t\t-l\t\tDisplay the legal notice and exit.\n");
+ fprintf(stderr, "\t\t-v\t\tDisplay version.\n");
+ exit(EXIT_FAILURE);
+}
+
+PUBLIC int
+main(int argc, char *argv[])
+{
+#ifdef DEBUG
+#ifdef HAVE_MALLOC_DEBUG
+ malloc_debug(16);
+#endif
+#endif
+ GetArgs(argc, argv);
+
+ signal(SIGINT, TerminateServer);
+ signal(SIGPIPE, BrokenPipe);
+ signal(SIGTERM, TerminateServer);
+
+ if (net_init(port)) {
+ fprintf(stderr, "FICS: Network initialize failed on port %d.\n",
+ port);
+ return EXIT_FAILURE;
+ }
+
+ startuptime = time(NULL);
+ strlcpy(fics_hostname, SERVER_HOSTNAME, sizeof fics_hostname);
+ game_high = 0;
+ player_high = 0;
+ quota_time = 60;
+ srand(startuptime);
+
+ fprintf(stderr, "FICS: Initialized on port %d at %s.\n", port,
+ strltime(&startuptime));
+ fprintf(stderr, "FICS: commands_init()\n");
+ commands_init();
+
+ fprintf(stderr, "FICS: rating_init()\n");
+ rating_init();
+
+ fprintf(stderr, "FICS: wild_init()\n");
+ wild_init();
+
+#ifndef IGNORE_ECO
+ fprintf(stderr, "FICS: book init()\n");
+ BookInit();
+#endif
+
+ fprintf(stderr, "FICS: player_array_init()\n");
+ player_array_init();
+ fprintf(stderr, "FICS: player_init(withConsole=%d)\n", withConsole);
+ player_init(withConsole);
+
+ main_event_loop();
+
+ fprintf(stderr, "FICS: Closing down.\n");
+ output_shut_mess();
+ net_close();
+
+ return EXIT_SUCCESS;
+}
diff --git a/FICS/ficsmain.h b/FICS/ficsmain.h
new file mode 100644
index 0000000..08ea466
--- /dev/null
+++ b/FICS/ficsmain.h
@@ -0,0 +1,77 @@
+/* ficsmain.h
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 23/12/13 Cleaned up the file
+*/
+
+#ifndef _FICSMAIN_H
+#define _FICSMAIN_H
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "common.h"
+
+/*
+ * Heartbead functions occur approx in this time, including checking
+ * for new connections and decrementing timeleft counters.
+ */
+#define HEARTBEATTIME 2
+
+/*
+ * Number of seconds that an idle connection can stand at login or
+ * password prompt.
+ */
+#define MAX_LOGIN_IDLE 120
+
+/*
+ * Players who have been idle for more than 1 hour is logged out.
+ */
+#define MAX_IDLE_TIME 3600
+
+#define DEFAULT_PROMPT "fics% "
+
+#define MESS_WELCOME "welcome"
+#define MESS_LOGIN "login"
+#define MESS_LOGOUT "logout"
+#define MESS_MOTD "motd"
+#define MESS_ADMOTD "admotd"
+#define MESS_UNREGISTERED "unregistered"
+
+#define STATS_MESSAGES "messages"
+#define STATS_LOGONS "logons"
+#define STATS_GAMES "games"
+#define STATS_JOURNAL "journal"
+
+__FICS_BEGIN_DECLS
+extern const int g_open_flags[2];
+extern const mode_t g_open_modes;
+
+/* Arguments */
+extern int port;
+extern int withConsole;
+__FICS_END_DECLS
+
+#endif /* _FICSMAIN_H */
diff --git a/FICS/formula.c b/FICS/formula.c
new file mode 100644
index 0000000..dd5ff23
--- /dev/null
+++ b/FICS/formula.c
@@ -0,0 +1,705 @@
+/*
+ * Formula program for FICS. Written by Dave Herscovici
+ * <dhersco@math.nps.navy.mil>
+ * Edited by DAV to include wild, non-std and untimed lightning flags.
+ *
+ * Operators allowed, in order of precedence:
+ * ! (not), - (unary),
+ * *, /,
+ * +, -,
+ * <, <=, =<, >, >=, =>, (<= and =< are equivalent, as are >= and =>)
+ * =, ==, !=, <>, (two ways of saying both equals and not equals)
+ * &, &&, (both "and")
+ * |, ||, (both "or").
+ *
+ * Parentheses () are also allowed, as is a pound sign '#' for comments.
+ * The program will divide by a fudge factor of .001 instead of 0.
+ *
+ * Variables allowed:
+ * assessdraw, assessloss, assesswin, blitz,
+ * f1, f2, f3, f4, f5, f6, f7, f8, f9,
+ * inc, lightning,
+ * maxtime(n) = maximum time n moves will take (in seconds),
+ * mymaxtime(n) = same, but just count my time,
+ * myrating, nonstandard, private, rated, rating,
+ * ratingdiff = rating - myrating,
+ * registered, standard, time, timeseal, untimed, wild.
+ *
+ * The code for translating blitz and standard variables may have to
+ * be redone. f1 through f9 are user-defined formula variables. They
+ * can be used to avoid having to retype your entire formula when you
+ * want to change one part of it. Or to compensate for the lack of a
+ * 'mood' variable.
+ *
+ * For example:
+ * set f1 rated & time=5 & inc=0 # Rated 5 minute games
+ * set f2 rating - myrating
+ * set f3 # This line is a random comment
+ * set f4 f2>400 # I want a REAL fight
+ *
+ * Then you can type:
+ * set formula f1 # Rated 5 min games only
+ * set formula etime >= 10 & f2 > -100 # Long games, decent competition.
+ * set formula f1 & !f4
+ * Or 'set formula f2 >= 0 | blitz' depending on your mood.
+ *
+ * Further modifications could account for starting positions, time
+ * odds games, provisional or established opponents, etc. Maybe f0
+ * should be reserved for your formula upon logging in, i.e. if f0 is
+ * set, your formula is automatically set to f0 when you log in.
+ */
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include <ctype.h>
+
+#include "command.h"
+#include "config.h"
+#include "formula.h"
+#include "gamedb.h"
+#include "lists.h"
+#include "network.h"
+#include "playerdb.h"
+#include "ratings.h"
+#include "rmalloc.h"
+#include "utils.h"
+
+PRIVATE char *
+GetPlayersFormula(player *pWho, int clause)
+{
+ if (clause == MAX_FORMULA)
+ return pWho->formula;
+ return pWho->formulaLines[clause];
+}
+
+PRIVATE int
+MoveIndexPastString(char *string, int *i, char *text)
+{
+ int n = strlen(text);
+
+ if (strncasecmp(text, string + *i, n))
+ return 0;
+ *i += n;
+ return n;
+}
+
+PUBLIC int
+GetRating(player *p, int gametype)
+{
+ if (gametype == TYPE_BLITZ)
+ return (p->b_stats.rating);
+ else if (gametype == TYPE_STAND)
+ return (p->s_stats.rating);
+ else if (gametype == TYPE_WILD)
+ return (p->w_stats.rating);
+ else if (gametype == TYPE_LIGHT)
+ return (p->l_stats.rating);
+ else if (gametype == TYPE_BUGHOUSE)
+ return (p->bug_stats.rating);
+ else
+ return 0;
+}
+
+PRIVATE int
+GetNumberInsideParens(game *g, int clause, int *i, int *token, int eval)
+{
+ char *string = GetPlayersFormula(&parray[g->black], clause);
+ int ret;
+
+ while (string[*i] != '\0' && isspace(string[*i]))
+ (*i)++;
+
+ if (!MoveIndexPastString(string, i, "("))
+ return ERR_BADCHAR;
+
+ ret = CheckFormula(g, clause, i, OPTYPE_PAREN, token, eval);
+
+ if (ret != ERR_NONE)
+ return ret;
+
+ if (MoveIndexPastString(string, i, ")"))
+ return ERR_NONE;
+ else
+ return ERR_PAREN;
+}
+
+PRIVATE int
+Maxtime(game *g, int numMoves, int numPlayers)
+{
+ int max;
+
+ if (g->bInitTime == g->wInitTime && g->bIncrement == g->wIncrement) {
+ max = numPlayers * (60 * g->wInitTime + numMoves *
+ g->wIncrement);
+
+ if (g->type != TYPE_UNTIMED && g->wInitTime == 0)
+ max += (10 * numPlayers);
+ } else if (numPlayers == 2) {
+ max = 60 * (g->wInitTime + g->bInitTime) + numMoves *
+ (g->wIncrement + g->bIncrement);
+
+ if (g->type != TYPE_UNTIMED && g->wInitTime == 0)
+ max += 10;
+ if (g->type != TYPE_UNTIMED && g->bInitTime == 0)
+ max += 10;
+ } else {
+ max = (60 * g->bInitTime + numMoves * g->bIncrement);
+
+ if (g->type != TYPE_UNTIMED && g->bInitTime == 0)
+ max += 10;
+ }
+ return max;
+}
+
+/*
+ * The black player in game 'g' has been challenged. S/he has a list
+ * of formulas and we're checking the one given by the index
+ * clause. 'MAX_FORMULA' denotes the actual formula. If clause is
+ * smaller, we're looking at one of the user-defined formulas. We're
+ * at position 'i' in the formula. If we find a legitimate variable
+ * there and 'eval' is 1 VarToToken() puts its value in 'tok'. This
+ * function returns 1 or 0 depending on whether a legitimate variable
+ * was found.
+ */
+PRIVATE int
+VarToToken(game *g, int clause, char *string, int *i, int *tok, int eval)
+{
+ double dummy_sterr;
+ int index = 0, c;
+ player *me = &parray[g->black];
+ player *you = &parray[g->white];
+
+ /*
+ * We list possible variables with the longest names first.
+ */
+ if (MoveIndexPastString(string, i, "registered")) {
+ *tok = you->registered;
+ } else if (MoveIndexPastString(string, i, "ratingdiff")) {
+ *tok = (GetRating(you, g->type) - GetRating(me, g->type));
+ } else if (MoveIndexPastString(string, i, "assessloss")) {
+ if (g->rated) {
+ rating_sterr_delta(g->black, g->white, g->type,
+ time(NULL), RESULT_LOSS, tok, &dummy_sterr);
+ } else {
+ *tok = 0;
+ }
+ } else if (MoveIndexPastString(string, i, "assessdraw")) {
+ if (g->rated) {
+ rating_sterr_delta(g->black, g->white, g->type,
+ time(NULL), RESULT_DRAW, tok, &dummy_sterr);
+ } else {
+ *tok = 0;
+ }
+ } else if (MoveIndexPastString(string, i, "assesswin")) {
+ if (g->rated) {
+ rating_sterr_delta(g->black, g->white, g->type,
+ time(NULL), RESULT_WIN, tok, &dummy_sterr);
+ } else {
+ *tok = 0;
+ }
+ } else if (MoveIndexPastString(string, i, "mymaxtime")) {
+ if (GetNumberInsideParens(g, clause, i, tok, eval) != ERR_NONE)
+ return 0;
+ *tok = Maxtime(g, *tok, 1);
+ }
+#ifdef TIMESEAL
+ else if (MoveIndexPastString(string, i, "timeseal"))
+ *tok = con[you->socket].timeseal;
+#endif
+ else if (MoveIndexPastString(string, i, "myrating"))
+ *tok = GetRating(me, g->type);
+ else if (MoveIndexPastString(string, i, "computer"))
+ *tok = in_list(-1, L_COMPUTER, you->name);
+ else if (MoveIndexPastString(string, i, "standard"))
+ *tok = (g->type == TYPE_STAND);
+ else if (MoveIndexPastString(string, i, "private"))
+ *tok = you->private;
+ else if (MoveIndexPastString(string, i, "maxtime")) {
+ if (GetNumberInsideParens(g, clause, i, tok, eval) != ERR_NONE)
+ return 0;
+ *tok = Maxtime(g, *tok, 2);
+ } else if (MoveIndexPastString(string, i, "abuser"))
+ *tok = in_list(-1, L_ABUSER, you->name);
+ else if (MoveIndexPastString(string, i, "rating"))
+ *tok = GetRating(you, g->type);
+ else if (MoveIndexPastString(string, i, "rated"))
+ *tok = g->rated;
+ else if (MoveIndexPastString(string, i, "blitz"))
+ *tok = (g->type == TYPE_BLITZ);
+ else if (MoveIndexPastString(string, i, "wild"))
+ *tok = (g->type == TYPE_WILD);
+ else if (MoveIndexPastString(string, i, "lightning"))
+ *tok = (g->type == TYPE_LIGHT);
+ else if (MoveIndexPastString(string, i, "nonstandard"))
+ *tok = (g->type == TYPE_NONSTANDARD);
+ else if (MoveIndexPastString(string, i, "untimed"))
+ *tok = (g->type == TYPE_UNTIMED);
+ else if (MoveIndexPastString(string, i, "time"))
+ *tok = g->wInitTime;
+ else if (MoveIndexPastString(string, i, "inc"))
+ *tok = g->wIncrement;
+ else if (tolower(string[*i]) == 'f' &&
+ isdigit(string[*i + 1]) &&
+ (c = (string[*i + 1] - '1')) >= 0 &&
+ clause > c) {
+ *i += 2;
+ return (!CheckFormula(g, c, &index, OPTYPE_NONE, tok, eval));
+ } else
+ return 0;
+ return 1;
+}
+
+/*
+ * ScanForOp() checks for an operator at position 'i' in 'string'.
+ */
+PRIVATE int
+ScanForOp(char *string, int *i)
+{
+ while (string[*i] != '\0' && isspace(string[*i]))
+ (*i)++;
+
+ if (string[*i] == '\0')
+ return OP_NONE;
+ if (string[*i] == '#')
+ return OP_NONE;
+ if (string[*i] == ')')
+ return OP_RTPAREN;
+
+ /*
+ * Two char operators and longer first.
+ */
+ if (MoveIndexPastString(string, i, "and"))
+ return OP_AND;
+ if (MoveIndexPastString(string, i, "or"))
+ return OP_OR;
+ if (MoveIndexPastString(string, i, "||"))
+ return OP_OR;
+ if (MoveIndexPastString(string, i, "&&"))
+ return OP_AND;
+ if (MoveIndexPastString(string, i, "=="))
+ return OP_EQ;
+ if (MoveIndexPastString(string, i, "!="))
+ return OP_NEQ;
+ if (MoveIndexPastString(string, i, "<>"))
+ return OP_NEQ;
+ if (MoveIndexPastString(string, i, ">="))
+ return OP_GE;
+ if (MoveIndexPastString(string, i, "=>"))
+ return OP_GE;
+ if (MoveIndexPastString(string, i, "<="))
+ return OP_LE;
+ if (MoveIndexPastString(string, i, "=<"))
+ return OP_LE;
+
+ /*
+ * One char operators now.
+ */
+ if (MoveIndexPastString(string, i, "|"))
+ return OP_OR;
+ if (MoveIndexPastString(string, i, "&"))
+ return OP_AND;
+ if (MoveIndexPastString(string, i, ">"))
+ return OP_GT;
+ if (MoveIndexPastString(string, i, "<"))
+ return OP_LT;
+ if (MoveIndexPastString(string, i, "="))
+ return OP_EQ;
+ if (MoveIndexPastString(string, i, "+"))
+ return OP_PLUS;
+ if (MoveIndexPastString(string, i, "-"))
+ return OP_MINUS;
+ if (MoveIndexPastString(string, i, "*"))
+ return OP_MULT;
+ if (MoveIndexPastString(string, i, "/"))
+ return OP_DIV;
+
+ return OP_BAD;
+}
+
+/*
+ * OpType() returns the precedence of the operator 'op'.
+ */
+PRIVATE int
+OpType(int op)
+{
+ switch (op) {
+ case OP_BAD:
+ return OPTYPE_BAD;
+ case OP_NONE:
+ return OPTYPE_NONE;
+ case OP_RTPAREN:
+ return OPTYPE_RPAREN;
+ case OP_OR:
+ return OPTYPE_OR;
+ case OP_AND:
+ return OPTYPE_AND;
+ case OP_EQ:
+ case OP_NEQ:
+ return OPTYPE_COMPEQ;
+ case OP_GT:
+ case OP_GE:
+ case OP_LT:
+ case OP_LE:
+ return OPTYPE_COMPGL;
+ case OP_PLUS:
+ case OP_MINUS:
+ return OPTYPE_ADD;
+ case OP_MULT:
+ case OP_DIV:
+ return OPTYPE_MULT;
+ case OP_NEGATE:
+ case OP_NOT:
+ return OPTYPE_UNARY;
+ default:
+ return OPTYPE_BAD;
+ }
+}
+
+/*
+ * In EvalBinaryOp() 'left' is the left operand, and 'op' is the
+ * current operator. 'g' and 'clause' specify which formula string to
+ * look at, and 'i' tells us where we are in the string. We look for a
+ * right operand from position 'i' in the string. And place the
+ * expression in 'left'. This function returns 0 if no error.
+ * Otherwise an error code is returned.
+ */
+PRIVATE int
+EvalBinaryOp(int *left, int op, game *g, int clause, int *i)
+{
+ int right, ret;
+
+ if (op == OP_OR && *left != 0) { /* Nothing to decide. */
+ *left = 1;
+ return CheckFormula(g, clause, i, OpType(op), &right, 0);
+ } else if (op == OP_AND && *left == 0) { /* Nothing to decide. */
+ return CheckFormula(g, clause, i, OpType(op), &right, 0);
+ } else {
+ ret = CheckFormula(g, clause, i, OpType(op), &right, 1);
+
+ if (ret != ERR_NONE)
+ return ret;
+ }
+
+ switch (op) {
+ default:
+ case OP_BAD:
+ return ERR_BADOP;
+ case OP_NONE:
+ case OP_RTPAREN:
+ return ERR_NONE;
+ case OP_OR:
+ *left = (*left || right);
+ return ERR_NONE;
+ case OP_AND:
+ *left = (*left && right);
+ return ERR_NONE;
+ case OP_EQ:
+ *left = (*left == right);
+ return ERR_NONE;
+ case OP_NEQ:
+ *left = (*left != right);
+ return ERR_NONE;
+ case OP_GT:
+ *left = (*left > right);
+ return ERR_NONE;
+ case OP_GE:
+ *left = (*left >= right);
+ return ERR_NONE;
+ case OP_LT:
+ *left = (*left < right);
+ return ERR_NONE;
+ case OP_LE:
+ *left = (*left <= right);
+ return ERR_NONE;
+ case OP_PLUS:
+ *left += right;
+ return ERR_NONE;
+ case OP_MINUS:
+ *left -= right;
+ return ERR_NONE;
+ case OP_MULT:
+ *left *= right;
+ return ERR_NONE;
+ case OP_DIV:
+ if (right != 0) {
+ *left /= right;
+ return ERR_NONE;
+ } else {
+ pprintf(g->black, "Dividing by %lf instead or zero in "
+ "formula.\n", FUDGE_FACTOR);
+ *left /= FUDGE_FACTOR;
+ return ERR_NONE;
+ }
+ } /* switch */
+}
+
+/*
+ * If 'eval' is 1, ScanForNumber() evaluates the number at position
+ * 'i' in the formula given by 'g' and 'clause', and places this value
+ * in 'token'. 'op_type' is the precedence of the operator preceding
+ * the i'th position. If we come to an operator of higher precedence
+ * we must keep going before we can leave this function. If 'eval' is
+ * 0, just move past the number we would evaluate. This function
+ * returns 0 if no error. Otherwise an error code is returned.
+ */
+PRIVATE int
+ScanForNumber(game *g, int clause, int *i, int op_type, int *token, int eval)
+{
+ char *string = GetPlayersFormula(&parray[g->black], clause);
+ char c;
+ int ret;
+
+ while (string[*i] != '\0' && isspace(string[*i]))
+ (*i)++;
+
+ switch (c = string[*i]) {
+ case '\0':
+ case '#':
+ if (op_type != OPTYPE_NONE)
+ return ERR_EOF;
+ else
+ *token = 1;
+ return ERR_NONE;
+ case ')':
+ if (op_type != OPTYPE_PAREN)
+ return ERR_PAREN;
+ else
+ *token = 1;
+ return ERR_NONE;
+ case '(':
+ return GetNumberInsideParens(g, clause, i, token, eval);
+ case '-':
+ case '!':
+ ++(*i);
+
+ if (c == string[*i])
+ return ERR_UNARY;
+
+ ret = ScanForNumber(g, clause, i, OPTYPE_UNARY, token, eval);
+
+ if (ret != ERR_NONE)
+ return ret;
+ if (c == '-')
+ *token = -(*token);
+ else if (c == '!')
+ *token = !(*token);
+ return ERR_NONE;
+ default:
+ if (isdigit(string[*i])) {
+ *token = 0;
+
+ do {
+ *token = (10 * (*token) + string[(*i)++] - '0');
+ } while (isdigit(string[*i]));
+
+ while (string[*i] != '\0' && isspace(string[*i]))
+ (*i)++;
+
+ if (MoveIndexPastString(string, i, "minutes"))
+ *token *= 60;
+ return ERR_NONE;
+ } else if (isalpha(string[*i])) {
+ if (!VarToToken(g, clause, string, i, token, eval))
+ return ERR_BADVAR;
+ return ERR_NONE;
+ } else
+ return ERR_NONESENSE;
+ } /* switch */
+}
+
+/*
+ * If 'eval' is 1, CheckFormula() looks for the next token in the
+ * formula given by 'g', 'clause' and 'i'. Usually this is the right
+ * side of an expression whose operator has precedence OpType(). If
+ * 'eval' is 0, just go to the end of an expression.
+ */
+PUBLIC int
+CheckFormula(game *g, int clause, int *i, int op_type, int *result, int eval)
+{
+ char *string = GetPlayersFormula(&parray[g->black], clause);
+ int token, ret, nextOp, lastPos;
+
+ if (string == NULL) {
+ *result = 1;
+ return ERR_NONE;
+ }
+
+ ret = ScanForNumber(g, clause, i, op_type, &token, eval);
+
+ if (ret != ERR_NONE)
+ return ret;
+
+ lastPos = *i;
+ nextOp = ScanForOp(string, i);
+
+ while (OpType(nextOp) > op_type) { /* Higher precedence. */
+ if (nextOp == OP_RTPAREN)
+ return ERR_PAREN;
+ if (!eval) {
+ ret = CheckFormula(g, clause, i, OpType(nextOp),
+ &token, 0);
+ } else {
+ ret = EvalBinaryOp(&token, nextOp, g, clause,
+ i);
+ }
+
+ if (ret != ERR_NONE)
+ return ret;
+ lastPos = *i;
+ nextOp = ScanForOp(string, i);
+ }
+
+ if (nextOp == OP_BAD)
+ return ERR_BADOP;
+ *result = token;
+ /*
+ * Move back unless we're at the end or at a right paren, in
+ * which case we never went forward.
+ */
+ if (nextOp != OP_NONE && nextOp != OP_RTPAREN)
+ *i = lastPos;
+ return ERR_NONE;
+}
+
+/*
+ * Which clauses are relevant for a player's formula.
+ */
+PRIVATE int
+ChooseClauses(player *who, char *formula)
+{
+ int i, which, ret = 0;
+
+ if (formula == NULL)
+ return ret;
+
+ for (i = 0; formula[i] != '\0' && formula[i] != '#'; i++) {
+ if ((i > 0 && isalnum(formula[i - 1])) || formula[i] != 'f' ||
+ !isdigit(formula[i + 1]) ||
+ sscanf(&formula[i], "f%d", &which) != 1)
+ continue;
+
+ ret |= (1 << (which - 1));
+ }
+
+ /* Now scan clauses found as part of the formula. */
+ for (i = MAX_FORMULA - 1; i >= 0; i--) {
+ if (ret & (1 << i))
+ ret |= ChooseClauses(who, who->formulaLines[i]);
+ }
+
+ return ret;
+}
+
+PRIVATE void
+ExplainFormula(game *g, textlist **clauses)
+{
+ char txt[20] = { '\0' };
+ int i, which, dummy_index, value;
+ player *challenged = &parray[g->black];
+ textlist** Cur = clauses;
+
+ which = ChooseClauses(challenged, challenged->formula);
+
+ for (i = 0; i < MAX_FORMULA; i++) {
+ if ((which & (1 << i)) == 0)
+ continue;
+ dummy_index = 0;
+ CheckFormula(g, i, &dummy_index, OPTYPE_NONE, &value, 1);
+ snprintf(txt, sizeof txt, "%d", value);
+ SaveTextListEntry(Cur, txt, i);
+ Cur = &(*Cur)->next;
+ }
+}
+
+/*
+ * GameMatchesFormula() converts parameters to a game structure and
+ * passes a pointer to this game to CheckFormula() for evaluation. It
+ * returns the evaluated formula.
+ */
+PUBLIC int
+GameMatchesFormula(int w, int b, int wTime, int wInc, int bTime, int bInc,
+ int rated, int gametype, textlist **clauseList)
+{
+ game g;
+ int index = 0, ret;
+
+ g.white = w;
+ g.black = b;
+ g.wInitTime = wTime;
+ g.bInitTime = bTime;
+ g.wIncrement = wInc;
+ g.bIncrement = bInc;
+ g.rated = rated;
+ g.type = gametype;
+
+ if (CheckFormula(&g, MAX_FORMULA, &index, OPTYPE_NONE, &ret, 1) !=
+ ERR_NONE)
+ return 0;
+ if (ret == 0)
+ ExplainFormula(&g, clauseList);
+ return ret;
+}
+
+/*
+ * SetValidFormula() sets a clause of player 'p' and creates a game
+ * structure to check whether that new formula is legitimate. If so,
+ * return 1. Otherwise reset the formula and return 0.
+ */
+PUBLIC int
+SetValidFormula(int p, int clause, char *string)
+{
+ char *Old = NULL, **Cur;
+ game g;
+ int index = 0, ret, err = ERR_NONE;
+ player *me = &parray[p];
+
+ if (clause == MAX_FORMULA)
+ Cur = &me->formula;
+ else
+ Cur = &me->formulaLines[clause];
+
+ Old = *Cur;
+
+ if (string != NULL) {
+ string = eatwhite(string);
+ *Cur = (*string != '\0' ? xstrdup(string) : NULL);
+ } else
+ *Cur = NULL;
+
+ if (*Cur == NULL) {
+ if (Old != NULL)
+ rfree(Old);
+ return 1;
+ }
+
+ g.white = g.black = p;
+ g.wInitTime = g.bInitTime = me->d_time;
+ g.wIncrement = g.bIncrement = me->d_inc;
+ g.rated = me->rated;
+ g.type = TYPE_BLITZ;
+
+ err = CheckFormula(&g, clause, &index, OPTYPE_NONE, &ret, 0);
+
+ if (err != ERR_NONE) {
+ /* Bad formula -- reset it. */
+ rfree(*Cur);
+ *Cur = Old;
+ } else {
+ if (Old != NULL)
+ rfree(Old);
+ }
+
+ return (err == ERR_NONE);
+}
+
+PUBLIC void
+ShowClauses(int p, int p1, textlist *clauses)
+{
+ if (clauses != NULL)
+ pprintf(p, "\n");
+ for (textlist *Cur = clauses; Cur != NULL; Cur = Cur->next) {
+ pprintf(p, "f%d=%s: %s\n", (Cur->index + 1), Cur->text,
+ parray[p1].formulaLines[Cur->index]);
+ }
+}
diff --git a/FICS/formula.h b/FICS/formula.h
new file mode 100644
index 0000000..b6ebc6c
--- /dev/null
+++ b/FICS/formula.h
@@ -0,0 +1,57 @@
+#ifndef FORMULA_H
+#define FORMULA_H
+
+#define OP_BAD -1
+
+#include "gamedb.h"
+#include "playerdb.h"
+
+#define OP_NONE 0
+#define OP_RTPAREN 1
+#define OP_OR 2
+#define OP_AND 3
+#define OP_EQ 4
+#define OP_NEQ 5
+#define OP_GT 6
+#define OP_GE 7
+#define OP_LT 8
+#define OP_LE 9
+#define OP_PLUS 10
+#define OP_MINUS 11
+#define OP_MULT 12
+#define OP_DIV 13
+#define OP_NEGATE 14
+#define OP_NOT 15
+
+#define OPTYPE_BAD 0
+#define OPTYPE_NONE 1
+#define OPTYPE_RPAREN 2
+#define OPTYPE_PAREN 3
+#define OPTYPE_OR 4
+#define OPTYPE_AND 5
+#define OPTYPE_COMPEQ 6
+#define OPTYPE_COMPGL 7
+#define OPTYPE_ADD 8
+#define OPTYPE_MULT 9
+#define OPTYPE_UNARY 10
+
+#define ERR_NONE 0
+#define ERR_EOF 1
+#define ERR_UNARY 2
+#define ERR_BADCHAR 3
+#define ERR_BADVAR 4
+#define ERR_BADOP 5
+#define ERR_NONESENSE 6
+#define ERR_DIVBYZERO 7
+#define ERR_PAREN 8
+
+#define FUDGE_FACTOR 1e-3
+
+extern int CheckFormula(game *, int, int *, int, int *, int);
+extern int GameMatchesFormula(int, int, int, int, int, int, int, int,
+ textlist **);
+extern int GetRating(player *, int);
+extern int SetValidFormula(int, int, char *);
+extern void ShowClauses(int, int, textlist *);
+
+#endif
diff --git a/FICS/gamedb.c b/FICS/gamedb.c
new file mode 100644
index 0000000..f3a351b
--- /dev/null
+++ b/FICS/gamedb.c
@@ -0,0 +1,2256 @@
+/* gamedb.c
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 23/12/16 Fixed compiler warnings
+ Markus Uhlin 23/12/17 Reformatted functions
+ Markus Uhlin 23/12/23 Fixed clang warnings
+ Markus Uhlin 24/05/04 Refactored and reformatted all
+ functions plus more...
+ Markus Uhlin 24/07/17 Fixed out of bounds array access
+ in game_str() which resulted in
+ a crash.
+ Markus Uhlin 24/07/18 Return value checking
+ Markus Uhlin 24/08/03 See previous change
+ Markus Uhlin 24/11/23 Added width specifications to
+ multiple fscanf() calls.
+ Markus Uhlin 24/11/23 Fixed bugs in movesToString()
+ Markus Uhlin 24/11/25 Null checks
+ Markus Uhlin 24/12/02 Fixed bugs and ignored function
+ return values.
+ Markus Uhlin 25/03/18 Fixed unchecked return values
+ Markus Uhlin 25/03/25 ReadGameState: fixed truncated
+ stdio return value.
+ Markus Uhlin 25/04/01 Fixed call of risky function
+ Markus Uhlin 25/04/01 ReadV1GameFmt: guard num half
+ moves.
+ Markus Uhlin 25/04/06 Fixed Clang Tidy warnings.
+ Markus Uhlin 25/07/28 Fixed use of potentially
+ dangerous functions.
+ Markus Uhlin 25/07/29 Usage of 'int64_t'.
+ Markus Uhlin 25/08/16 Fixed uncontrolled data used in
+ path expressions.
+*/
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include <err.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdint.h>
+
+#include "command.h"
+#include "config.h"
+#include "eco.h"
+#include "ficsmain.h"
+#include "gamedb.h"
+#include "gameproc.h"
+#include "maxxes-utils.h"
+#include "network.h"
+#include "playerdb.h"
+#include "rmalloc.h"
+#include "utils.h"
+
+#if __linux__
+#include <bsd/string.h>
+#endif
+
+/*
+ * This should be enough to hold any game up to at least 250 moves. If
+ * we overwrite this, the server will crash.
+ */
+#define GAME_STRING_LEN 16000
+
+PUBLIC game *garray = NULL;
+PUBLIC int g_num = 0;
+
+PUBLIC const char *bstr[7] = {
+ [TYPE_UNTIMED] = "untimed",
+ [TYPE_BLITZ] = "blitz",
+ [TYPE_STAND] = "standard",
+ [TYPE_NONSTANDARD] = "non-standard",
+ [TYPE_WILD] = "wild",
+ [TYPE_LIGHT] = "lightning",
+ [TYPE_BUGHOUSE] = "bughouse"
+};
+PUBLIC const char *rstr[2] = {
+ [TYPE_UNRATED] = "unrated",
+ [TYPE_RATED] = "rated"
+};
+
+PRIVATE char gameString[GAME_STRING_LEN];
+
+/*
+ * This method is awful! How about allocation as we need it and
+ * freeing afterwards!
+ */
+PRIVATE int
+get_empty_slot(void)
+{
+ if (garray != NULL) {
+ for (int i = 0; i < g_num; i++) {
+ if (garray[i].status == GAME_EMPTY)
+ return i;
+ }
+ }
+
+ g_num++;
+
+ if (!garray) {
+ garray = reallocarray(NULL, sizeof(game), g_num);
+
+ if (garray == NULL)
+ err(1, "%s: reallocarray", __func__);
+ else
+ malloc_count++;
+ } else {
+ garray = reallocarray(garray, sizeof(game), g_num);
+
+ if (garray == NULL)
+ err(1, "%s: reallocarray", __func__);
+ } // Yeah great, bet this causes lag! - DAV
+
+ garray[g_num - 1].status = GAME_EMPTY;
+ return g_num - 1;
+}
+
+PUBLIC int
+game_new(void)
+{
+ int new = get_empty_slot();
+
+ game_zero(new);
+
+ return new;
+}
+
+PUBLIC int
+game_zero(int g)
+{
+ garray[g].white = -1;
+ garray[g].black = -1;
+
+ garray[g].link = -1;
+ garray[g].passes = 0;
+ garray[g].private = 0;
+ garray[g].rated = 0;
+ garray[g].result = END_NOTENDED;
+ garray[g].status = GAME_NEW;
+ garray[g].type = TYPE_UNTIMED;
+
+ board_init(&garray[g].game_state, NULL, NULL);
+
+ garray[g].bIncrement = 0;
+ garray[g].bInitTime = 300; // 5 minutes
+ garray[g].black_name[0] = '\0';
+ garray[g].black_rating = 0;
+
+ garray[g].wIncrement = 0;
+ garray[g].wInitTime = 300; // 5 minutes
+ garray[g].white_name[0] = '\0';
+ garray[g].white_rating = 0;
+
+ garray[g].examMoveList = NULL;
+ garray[g].examMoveListSize = 0;
+
+#ifdef TIMESEAL
+ garray[g].flag_check_time = 0L;
+ garray[g].flag_pending = FLAG_NONE;
+#endif
+
+ garray[g].game_state.gameNum = g;
+ garray[g].moveList = NULL;
+ garray[g].moveListSize = 0;
+ garray[g].numHalfMoves = 0;
+ garray[g].revertHalfMove = 0;
+
+ return 0;
+}
+
+PUBLIC int
+game_free(int g)
+{
+ if (garray[g].moveListSize)
+ rfree(garray[g].moveList);
+ if (garray[g].examMoveListSize)
+ rfree(garray[g].examMoveList);
+
+ garray[g].moveListSize = 0;
+ garray[g].examMoveListSize = 0;
+
+ return 0;
+}
+
+PUBLIC int
+game_clear(int g)
+{
+ game_free(g);
+ game_zero(g);
+ return 0;
+}
+
+PUBLIC int
+game_remove(int g)
+{
+ // Should remove game from players observation list
+ game_clear(g);
+ garray[g].status = GAME_EMPTY;
+ return 0;
+}
+
+// old moves not stored now - uses smoves
+PUBLIC int
+game_finish(int g)
+{
+ player_game_ended(g); // Alert playerdb that game ended
+ game_remove(g);
+ return 0;
+}
+
+PUBLIC void
+MakeFENpos(int g, char *FEN, size_t size)
+{
+ mstrlcpy(FEN, boardToFEN(g), size);
+}
+
+PUBLIC char *
+game_time_str(int wt, int winc, int bt, int binc)
+{
+ static char tstr[50] = { '\0' };
+
+ if ((!wt) && (!winc)) { // Untimed
+ (void) strlcpy(tstr, "", sizeof tstr);
+ return tstr;
+ }
+
+ if ((wt == bt) && (winc == binc)) {
+ msnprintf(tstr, sizeof tstr, " %d %d", wt, winc);
+ } else {
+ msnprintf(tstr, sizeof tstr, " %d %d : %d %d",
+ wt, winc,
+ bt, binc);
+ }
+
+ return tstr;
+}
+
+PUBLIC char *
+game_str(int rated, int wt, int winc, int bt, int binc, char *cat, char *board)
+{
+ static char tstr[200] = { '\0' };
+
+ if (rated != TYPE_UNRATED &&
+ rated != TYPE_RATED)
+ rated = TYPE_UNRATED;
+
+ if (cat && cat[0] && board && board[0] && (strcmp(cat, "standard") ||
+ strcmp(board, "standard"))) {
+ msnprintf(tstr, sizeof(tstr), "%s %s%s Loaded from %s/%s",
+ rstr[rated],
+ bstr[game_isblitz(wt / 60, winc, bt / 60, binc, cat, board)],
+ game_time_str(wt / 60, winc, bt / 60, binc),
+ cat,
+ board);
+ } else {
+ msnprintf(tstr, sizeof(tstr), "%s %s%s",
+ rstr[rated],
+ bstr[game_isblitz(wt / 60, winc, bt / 60, binc, cat, board)],
+ game_time_str(wt / 60, winc, bt / 60, binc));
+ }
+
+ return tstr;
+}
+
+PUBLIC int
+game_isblitz(int wt, int winc, int bt, int binc, char *cat, char *board)
+{
+ int total;
+
+ if (cat && cat[0] && board && board[0] && (!strcmp(cat, "wild")))
+ return TYPE_WILD;
+ if (cat && cat[0] && board && board[0] && (strcmp(cat, "standard") ||
+ strcmp(board, "standard")))
+ return TYPE_NONSTANDARD;
+
+ if (((wt == 0) && (winc == 0)) || ((bt == 0) && (binc == 0))) {
+ /*
+ * nonsense if one is timed and one is not
+ */
+ return TYPE_UNTIMED;
+ }
+
+ if ((wt != bt) || (winc != binc))
+ return TYPE_NONSTANDARD;
+
+ total = wt * 60 + winc * 40;
+
+ if (total < 180) // 3 minutes
+ return TYPE_LIGHT;
+ if (total >= 900) // 15 minutes
+ return TYPE_STAND;
+ else
+ return TYPE_BLITZ;
+}
+
+PUBLIC void
+send_board_to(int g, int p)
+{
+ char *b;
+ int relation;
+ int side;
+
+ /*
+ * Since we know 'g' and 'p', figure out our relationship to
+ * this game...
+ */
+
+ side = WHITE;
+
+ if (garray[g].status == GAME_EXAMINE) {
+ if (parray[p].game == g) {
+ relation = 2;
+ } else {
+ relation = -2;
+ }
+ } else {
+ if (parray[p].game == g) {
+ side = parray[p].side;
+ relation = (side == garray[g].game_state.onMove ? 1 :
+ -1);
+ } else {
+ relation = 0;
+ }
+ }
+
+ if (parray[p].flip) { // flip board?
+ if (side == WHITE)
+ side = BLACK;
+ else
+ side = WHITE;
+ }
+
+ game_update_time(g);
+ b = board_to_string(garray[g].white_name, garray[g].black_name,
+ garray[g].wTime,
+ garray[g].bTime,
+ &garray[g].game_state,
+ (garray[g].status == GAME_EXAMINE)
+ ? garray[g].examMoveList
+ : garray[g].moveList,
+ parray[p].style,
+ side,
+ relation,
+ p);
+
+#ifdef TIMESEAL
+ if (con[parray[p].socket].timeseal) {
+ if (parray[p].bell) {
+ pprintf_noformat(p, "\007\n[G]\n%s", b);
+ } else {
+ pprintf_noformat(p, "\n[G]\n%s", b);
+ }
+ } else {
+ if (parray[p].bell) {
+ pprintf_noformat(p, "\007\n%s", b);
+ } else {
+ pprintf_noformat(p, "\n%s", b);
+ }
+ }
+#else
+ if (parray[p].bell) {
+ pprintf_noformat(p, "\007\n%s", b);
+ } else {
+ pprintf_noformat(p, "\n%s", b);
+ }
+#endif
+
+ if (p != commanding_player)
+ pprintf(p, "%s", parray[p].prompt);
+}
+
+PUBLIC void
+send_boards(int g)
+{
+ simul_info_t *simInfo = &parray[garray[g].white].simul_info;
+
+ if (simInfo->numBoards == 0 || simInfo->boards[simInfo->onBoard] == g) {
+ for (int p = 0; p < p_num; p++) {
+ if (parray[p].status == PLAYER_EMPTY)
+ continue;
+ if (player_is_observe(p, g) || (parray[p].game == g))
+ send_board_to(g, p);
+ }
+ }
+}
+
+PUBLIC void
+game_update_time(int g)
+{
+ unsigned int now, timesince;
+
+ if (garray[g].clockStopped)
+ return;
+ if (garray[g].type == TYPE_UNTIMED)
+ return;
+
+ now = tenth_secs();
+ timesince = now - garray[g].lastDecTime;
+
+ if (garray[g].game_state.onMove == WHITE) {
+ garray[g].wTime -= timesince;
+ } else {
+ garray[g].bTime -= timesince;
+ }
+
+ garray[g].lastDecTime = now;
+}
+
+PUBLIC void
+game_update_times(void)
+{
+ for (int g = 0; g < g_num; g++) {
+ if (garray[g].status != GAME_ACTIVE)
+ continue;
+ if (garray[g].clockStopped)
+ continue;
+ game_update_time(g);
+ }
+}
+
+PUBLIC char *
+EndString(int g, int personal)
+{
+ /*
+ * personal 0 == White checkmated
+ * personal 1 == loon checkmated
+ */
+ char *blackguy, *whiteguy;
+ static char blackstr[] = "Black";
+ static char whitestr[] = "White";
+ static char endstr[200] = { '\0' };
+
+ blackguy = (personal ? garray[g].black_name : blackstr);
+ whiteguy = (personal ? garray[g].white_name : whitestr);
+
+ switch (garray[g].result) {
+ case END_CHECKMATE:
+ msnprintf(endstr, sizeof endstr, "%s checkmated",
+ garray[g].winner == WHITE ? blackguy : whiteguy);
+ break;
+ case END_RESIGN:
+ msnprintf(endstr, sizeof endstr, "%s resigned",
+ garray[g].winner == WHITE ? blackguy : whiteguy);
+ break;
+ case END_FLAG:
+ msnprintf(endstr, sizeof endstr, "%s ran out of time",
+ garray[g].winner == WHITE ? blackguy : whiteguy);
+ break;
+ case END_AGREEDDRAW:
+ msnprintf(endstr, sizeof endstr, "Game drawn by mutual "
+ "agreement");
+ break;
+ case END_BOTHFLAG:
+ msnprintf(endstr, sizeof endstr, "Game drawn because both "
+ "players ran out of time");
+ break;
+ case END_REPETITION:
+ msnprintf(endstr, sizeof endstr, "Game drawn by repetition");
+ break;
+ case END_50MOVERULE:
+ msnprintf(endstr, sizeof endstr, "Draw by the 50 move rule");
+ break;
+ case END_ADJOURN:
+ msnprintf(endstr, sizeof endstr, "Game adjourned by mutual "
+ "agreement");
+ break;
+ case END_LOSTCONNECTION:
+ msnprintf(endstr, sizeof endstr, "%s lost connection, "
+ "game adjourned",
+ garray[g].winner == WHITE ? whiteguy : blackguy);
+ break;
+ case END_ABORT:
+ msnprintf(endstr, sizeof endstr, "Game aborted by mutual "
+ "agreement");
+ break;
+ case END_STALEMATE:
+ msnprintf(endstr, sizeof endstr, "Stalemate.");
+ break;
+ case END_NOTENDED:
+ msnprintf(endstr, sizeof endstr, "Still in progress");
+ break;
+ case END_COURTESY:
+ msnprintf(endstr, sizeof endstr, "Game courtesyaborted by %s",
+ garray[g].winner == WHITE ? whiteguy : blackguy);
+ break;
+ case END_COURTESYADJOURN:
+ msnprintf(endstr, sizeof endstr, "Game courtesyadjourned by %s",
+ garray[g].winner == WHITE ? whiteguy : blackguy);
+ break;
+ case END_NOMATERIAL:
+ msnprintf(endstr, sizeof endstr, "Game drawn because neither "
+ "player has mating material");
+ break;
+ case END_FLAGNOMATERIAL:
+ msnprintf(endstr, sizeof endstr, "%s ran out of time and %s "
+ "has no material to mate",
+ garray[g].winner == WHITE ? blackguy : whiteguy,
+ garray[g].winner == WHITE ? whiteguy : blackguy);
+ break;
+ case END_ADJDRAW:
+ msnprintf(endstr, sizeof endstr, "Game drawn by adjudication");
+ break;
+ case END_ADJWIN:
+ msnprintf(endstr, sizeof endstr, "%s wins by adjudication",
+ garray[g].winner == WHITE ? whiteguy : blackguy);
+ break;
+ case END_ADJABORT:
+ msnprintf(endstr, sizeof endstr, "Game aborted by "
+ "adjudication");
+ break;
+ default:
+ msnprintf(endstr, sizeof endstr, "???????");
+ break;
+ }
+
+ return endstr;
+}
+
+PUBLIC char *
+EndSym(int g)
+{
+ static char *symbols[] = {
+ "1-0",
+ "0-1",
+ "1/2-1/2",
+ "*"
+ };
+
+ switch (garray[g].result) {
+ case END_CHECKMATE:
+ case END_RESIGN:
+ case END_FLAG:
+ case END_ADJWIN:
+ return ((garray[g].winner == WHITE) ? symbols[0] : symbols[1]);
+ break;
+ case END_AGREEDDRAW:
+ case END_BOTHFLAG:
+ case END_REPETITION:
+ case END_50MOVERULE:
+ case END_STALEMATE:
+ case END_NOMATERIAL:
+ case END_FLAGNOMATERIAL:
+ case END_ADJDRAW:
+ return (symbols[2]);
+ break;
+ }
+
+ return (symbols[3]);
+}
+
+PUBLIC char *
+movesToString(int g, int pgn)
+{
+ char tmp[160] = { '\0' };
+ char *serv_loc = SERVER_LOCATION;
+ char *serv_name = SERVER_NAME;
+ int i, col;
+ int wr, br;
+ struct tm v_tm = {0};
+ time_t curTime;
+
+ wr = garray[g].white_rating;
+ br = garray[g].black_rating;
+
+ curTime = untenths(garray[g].timeOfStart);
+
+ if (pgn) {
+ msnprintf(gameString, sizeof gameString,
+ "\n[Event \"%s %s %s game\"]\n"
+ "[Site \"%s, %s\"]\n",
+ serv_name,
+ rstr[garray[g].rated],
+ bstr[garray[g].type],
+ serv_name,
+ serv_loc);
+
+ errno = 0;
+
+ if (localtime_r(&curTime, &v_tm) != NULL) {
+ strftime(tmp, sizeof(tmp),
+ "[Date \"%Y.%m.%d\"]\n"
+ "[Time \"%H:%M:%S\"]\n",
+ &v_tm);
+ mstrlcat(gameString, tmp, sizeof gameString);
+ } else
+ warn("%s: localtime_r()", __func__);
+
+ msnprintf(tmp, sizeof tmp,
+ "[Round \"-\"]\n"
+ "[White \"%s\"]\n"
+ "[Black \"%s\"]\n"
+ "[WhiteElo \"%d\"]\n"
+ "[BlackElo \"%d\"]\n",
+ garray[g].white_name,
+ garray[g].black_name,
+ wr, br);
+ mstrlcat(gameString, tmp, sizeof gameString);
+
+ msnprintf(tmp, sizeof tmp,
+ "[TimeControl \"%d+%d\"]\n"
+ "[Mode \"ICS\"]\n"
+ "[Result \"%s\"]\n\n",
+ garray[g].wInitTime / 10,
+ garray[g].wIncrement / 10,
+ EndSym(g));
+ mstrlcat(gameString, tmp, sizeof gameString);
+
+ col = 0;
+
+ for (i = 0; i < garray[g].numHalfMoves; i++) {
+ if (!(i % 2)) {
+ if ((col += snprintf(tmp, sizeof tmp, "%d. ",
+ i / 2 + 1)) > 70) {
+ mstrlcat(gameString, "\n",
+ sizeof gameString);
+ col = 0;
+ }
+
+ mstrlcat(gameString, tmp, sizeof gameString);
+ }
+
+ if ((col += snprintf(tmp, sizeof tmp, "%s ",
+ (garray[g].status == GAME_EXAMINE)
+ ? garray[g].examMoveList[i].algString
+ : garray[g].moveList[i].algString)) > 70) {
+ mstrlcat(gameString, "\n", sizeof gameString);
+ col = 0;
+ }
+
+ mstrlcat(gameString, tmp, sizeof gameString);
+ }
+
+ mstrlcat(gameString, "\n", sizeof gameString);
+ } else {
+ /*
+ * !pgn
+ */
+
+ msnprintf(gameString, sizeof gameString, "\n%s ",
+ garray[g].white_name);
+
+ if (wr > 0) {
+ msnprintf(tmp, sizeof tmp, "(%d) ", wr);
+ } else {
+ msnprintf(tmp, sizeof tmp, "(UNR) ");
+ }
+
+ mstrlcat(gameString, tmp, sizeof gameString);
+ msnprintf(tmp, sizeof tmp, "vs. %s ", garray[g].black_name);
+ mstrlcat(gameString, tmp, sizeof gameString);
+
+ if (br > 0) {
+ msnprintf(tmp, sizeof tmp, "(%d) ", br);
+ } else {
+ msnprintf(tmp, sizeof tmp, "(UNR) ");
+ }
+
+ mstrlcat(gameString, tmp, sizeof gameString);
+ mstrlcat(gameString, "--- ", sizeof gameString);
+
+ errno = 0;
+
+ if (localtime_r(&curTime, &v_tm) != NULL) {
+ strftime(tmp, sizeof tmp, "%Y.%m.%d %H:%M:%S", &v_tm);
+ mstrlcat(gameString, tmp, sizeof gameString);
+ } else
+ warn("%s: localtime_r()", __func__);
+
+ if (garray[g].rated) {
+ mstrlcat(gameString, "\nRated ", sizeof gameString);
+ } else {
+ mstrlcat(gameString, "\nUnrated ", sizeof gameString);
+ }
+
+ if (garray[g].type == TYPE_BLITZ) {
+ mstrlcat(gameString, "Blitz ", sizeof(gameString));
+ } else if (garray[g].type == TYPE_LIGHT) {
+ mstrlcat(gameString, "Lighting ", sizeof(gameString));
+ } else if (garray[g].type == TYPE_BUGHOUSE) {
+ mstrlcat(gameString, "Bughouse ", sizeof(gameString));
+ } else if (garray[g].type == TYPE_STAND) {
+ mstrlcat(gameString, "Standard ", sizeof(gameString));
+ } else if (garray[g].type == TYPE_WILD) {
+ mstrlcat(gameString, "Wild ", sizeof(gameString));
+ } else if (garray[g].type == TYPE_NONSTANDARD) {
+ mstrlcat(gameString, "Non-standard ",
+ sizeof(gameString));
+ } else {
+ mstrlcat(gameString, "Untimed ", sizeof(gameString));
+ }
+
+ mstrlcat(gameString, "match, initial time: ",
+ sizeof gameString);
+
+ if ((garray[g].bInitTime != garray[g].wInitTime) ||
+ (garray[g].wIncrement != garray[g].bIncrement)) {
+ /*
+ * different starting times
+ */
+
+ msnprintf(tmp, sizeof tmp, "%d minutes, increment: %d "
+ "seconds AND %d minutes, increment: %d seconds."
+ "\n\n",
+ garray[g].wInitTime / 600,
+ garray[g].wIncrement / 10,
+ garray[g].bInitTime / 600,
+ garray[g].bIncrement / 10);
+ } else {
+ msnprintf(tmp, sizeof tmp, "%d minutes, increment: "
+ "%d seconds.\n\n",
+ garray[g].wInitTime / 600,
+ garray[g].wIncrement / 10);
+ }
+
+ mstrlcat(gameString, tmp, sizeof gameString);
+ msnprintf(tmp, sizeof tmp, "Move %-19s%-19s\n",
+ garray[g].white_name,
+ garray[g].black_name);
+ mstrlcat(gameString, tmp, sizeof gameString);
+ mstrlcat(gameString, "---- ---------------- ----------------"
+ "\n", sizeof gameString);
+
+ for (i = 0; i < garray[g].numHalfMoves; i += 2) {
+ if (i + 1 < garray[g].numHalfMoves) {
+ msnprintf(tmp, sizeof tmp, "%3d. %-16s ",
+ i / 2 + 1,
+ (garray[g].status == GAME_EXAMINE)
+ ? move_and_time(&garray[g].examMoveList[i])
+ : move_and_time(&garray[g].moveList[i]));
+
+ mstrlcat(gameString, tmp, sizeof gameString);
+
+ msnprintf(tmp, sizeof tmp, "%-16s\n",
+ (garray[g].status == GAME_EXAMINE)
+ ? move_and_time(&garray[g].examMoveList[i + 1])
+ : move_and_time(&garray[g].moveList[i + 1]));
+ } else {
+ msnprintf(tmp, sizeof tmp, "%3d. %-16s\n",
+ i / 2 + 1,
+ (garray[g].status == GAME_EXAMINE)
+ ? move_and_time(&garray[g].examMoveList[i])
+ : move_and_time(&garray[g].moveList[i]));
+ }
+
+ mstrlcat(gameString, tmp, sizeof gameString);
+
+ if (strlen(gameString) > GAME_STRING_LEN - 100)
+ return gameString; // Bug out if getting
+ // close to filling this
+ // string
+ }
+
+ mstrlcat(gameString, " ", sizeof gameString);
+ }
+
+ msnprintf(tmp, sizeof tmp, "{%s} %s\n", EndString(g, 0), EndSym(g));
+ mstrlcat(gameString, tmp, sizeof gameString);
+
+ return gameString;
+}
+
+PUBLIC void
+game_disconnect(int g, int p)
+{
+ game_ended(g, (garray[g].white == p) ? WHITE : BLACK,
+ END_LOSTCONNECTION);
+}
+
+PUBLIC int
+CharToPiece(char c)
+{
+ switch (c) {
+ case 'P':
+ return W_PAWN;
+ case 'p':
+ return B_PAWN;
+ case 'N':
+ return W_KNIGHT;
+ case 'n':
+ return B_KNIGHT;
+ case 'B':
+ return W_BISHOP;
+ case 'b':
+ return B_BISHOP;
+ case 'R':
+ return W_ROOK;
+ case 'r':
+ return B_ROOK;
+ case 'Q':
+ return W_QUEEN;
+ case 'q':
+ return B_QUEEN;
+ case 'K':
+ return W_KING;
+ case 'k':
+ return B_KING;
+ default:
+ return NOPIECE;
+ }
+}
+
+PUBLIC int
+PieceToChar(int piece)
+{
+ switch (piece) {
+ case W_PAWN:
+ return 'P';
+ case B_PAWN:
+ return 'p';
+ case W_KNIGHT:
+ return 'N';
+ case B_KNIGHT:
+ return 'n';
+ case W_BISHOP:
+ return 'B';
+ case B_BISHOP:
+ return 'b';
+ case W_ROOK:
+ return 'R';
+ case B_ROOK:
+ return 'r';
+ case W_QUEEN:
+ return 'Q';
+ case B_QUEEN:
+ return 'q';
+ case W_KING:
+ return 'K';
+ case B_KING:
+ return 'k';
+ default:
+ return ' ';
+ }
+}
+
+/* One line has everything on it */
+PRIVATE int
+WriteMoves(FILE *fp, move_t *m)
+{
+ int i;
+ int piece, castle;
+ int useFile = 0, useRank = 0, check = 0;
+ unsigned long MoveInfo = (m->color == BLACK);
+
+ castle = (m->moveString[0] == 'o');
+
+ if (castle)
+ piece = KING;
+ else
+ piece = piecetype(CharToPiece(m->moveString[0]));
+
+#define ORIGINAL_CODE 0
+#if ORIGINAL_CODE
+ MoveInfo = (MoveInfo <<= 3) | piece;
+ MoveInfo = (MoveInfo <<= 3) | m->fromFile;
+ MoveInfo = (MoveInfo <<= 3) | m->fromRank;
+ MoveInfo = (MoveInfo <<= 3) | m->toFile;
+ MoveInfo = (MoveInfo <<= 3) | m->toRank;
+ MoveInfo = (MoveInfo <<= 3) | (m->pieceCaptured & 7);
+ MoveInfo = (MoveInfo <<= 3) | (m->piecePromotionTo & 7);
+ MoveInfo = (MoveInfo <<= 1) | (m->enPassant != 0);
+#else
+ MoveInfo <<= 3;
+ MoveInfo |= piece;
+
+ MoveInfo <<= 3;
+ MoveInfo |= m->fromFile;
+
+ MoveInfo <<= 3;
+ MoveInfo |= m->fromRank;
+
+ MoveInfo <<= 3;
+ MoveInfo |= m->toFile;
+
+ MoveInfo <<= 3;
+ MoveInfo |= m->toRank;
+
+ MoveInfo <<= 3;
+ MoveInfo |= (m->pieceCaptured & 7);
+
+ MoveInfo <<= 3;
+ MoveInfo |= (m->piecePromotionTo & 7);
+
+ MoveInfo <<= 1;
+ MoveInfo |= (m->enPassant != 0);
+#endif
+
+ /* Are we using from-file or from-rank in 'algString'? */
+
+ if ((i = strlen(m->algString)) > 0)
+ i -= 1;
+
+ if (m->algString[i] == '+') {
+ check = 1;
+ i--;
+ }
+
+ if (piece != PAWN && !castle) {
+ i -= 2;
+
+ if (i < 0)
+ return -1;
+ if (m->algString[i] == 'x')
+ i--;
+ if (i < 0)
+ return -1;
+ if (isdigit(m->algString[i])) {
+ useRank = 2;
+ i--;
+ }
+ if (i < 0)
+ return -1;
+
+ useFile = (islower(m->algString[i]) ? 4 : 0);
+ }
+
+ MoveInfo = ((MoveInfo << 3) | useFile | useRank | check);
+
+ fprintf(fp, "%lx %x %x\n", MoveInfo, m->tookTime, m->atTime);
+ return 0;
+}
+
+PRIVATE int
+ReadMove(FILE *fp, move_t *m)
+{
+ char line[MAX_GLINE_SIZE] = { '\0' };
+
+ if (fgets(line, sizeof line, fp) == NULL)
+ return -1;
+
+ _Static_assert(ARRAY_SIZE(m->moveString) > 7, "'moveString' too small");
+ _Static_assert(ARRAY_SIZE(m->algString) > 7, "'algString' too small");
+
+ if (sscanf(line, "%d %d %d %d %d %d %d %d %d \"%7[^\"]\" \"%7[^\"]\" "
+ "%u %u\n",
+ &m->color,
+ &m->fromFile, &m->fromRank,
+ &m->toFile, &m->toRank,
+ &m->pieceCaptured,
+ &m->piecePromotionTo,
+ &m->enPassant,
+ &m->doublePawn,
+ m->moveString,
+ m->algString,
+ &m->atTime,
+ &m->tookTime) != 13)
+ return -1;
+
+ return 0;
+}
+
+PRIVATE void
+WriteGameState(FILE *fp, game_state_t *gs)
+{
+ int i, j;
+
+ for (i = 0; i < 8; i++) {
+ for (j = 0; j < 8; j++)
+ fprintf(fp, "%c", PieceToChar(gs->board[i][j]));
+ }
+
+ fprintf(fp, "%d %d %d %d %d %d",
+ gs->wkmoved, gs->wqrmoved, gs->wkrmoved,
+ gs->bkmoved, gs->bqrmoved, gs->bkrmoved);
+
+ for (i = 0; i < 8; i++) {
+ fprintf(fp, " %d %d", gs->ep_possible[0][i],
+ gs->ep_possible[1][i]);
+ }
+
+ fprintf(fp, " %d %d %d\n", gs->lastIrreversable, gs->onMove,
+ gs->moveNum);
+}
+
+PRIVATE int
+ReadGameState(FILE *fp, game_state_t *gs, int version)
+{
+ int i, j;
+ int pieceChar;
+ int wkmoved, wqrmoved, wkrmoved, bkmoved, bqrmoved, bkrmoved;
+
+ if (version == 0) {
+ for (i = 0; i < 8; i++) {
+ for (j = 0; j < 8; j++) {
+ if (fscanf(fp, "%d ", &gs->board[i][j]) != 1)
+ return -1;
+ }
+ }
+ } else {
+ (void) getc(fp); /* Skip past a newline. */
+
+ for (i = 0; i < 8; i++) {
+ for (j = 0; j < 8; j++) {
+ if ((pieceChar = getc(fp)) == EOF)
+ return -1;
+ gs->board[i][j] = CharToPiece(pieceChar);
+ }
+ }
+ }
+
+ if (fscanf(fp, "%d %d %d %d %d %d",
+ &wkmoved, &wqrmoved, &wkrmoved,
+ &bkmoved, &bqrmoved, &bkrmoved) != 6)
+ return -1;
+
+ gs->wkmoved = wkmoved;
+ gs->wqrmoved = wqrmoved;
+ gs->wkrmoved = wkrmoved;
+
+ gs->bkmoved = bkmoved;
+ gs->bqrmoved = bqrmoved;
+ gs->bkrmoved = bkrmoved;
+
+ for (i = 0; i < 8; i++) {
+ if (fscanf(fp, " %d %d", &gs->ep_possible[0][i],
+ &gs->ep_possible[1][i]) != 2)
+ return -1;
+ }
+
+ if (fscanf(fp, " %d %d %d\n", &gs->lastIrreversable, &gs->onMove,
+ &gs->moveNum) != 3)
+ return -1;
+ return 0;
+}
+
+PUBLIC int
+got_attr_value(int g, char *attr, char *value, FILE *fp, char *file)
+{
+ if (!strcmp(attr, "w_init:")) {
+ garray[g].wInitTime = atoi(value);
+ } else if (!strcmp(attr, "w_inc:")) {
+ garray[g].wIncrement = atoi(value);
+ } else if (!strcmp(attr, "b_init:")) {
+ garray[g].bInitTime = atoi(value);
+ } else if (!strcmp(attr, "b_inc:")) {
+ garray[g].bIncrement = atoi(value);
+ } else if (!strcmp(attr, "white_name:")) {
+ mstrlcpy(garray[g].white_name, value,
+ sizeof(garray[g].white_name));
+ } else if (!strcmp(attr, "black_name:")) {
+ mstrlcpy(garray[g].black_name, value,
+ sizeof(garray[g].black_name));
+ } else if (!strcmp(attr, "white_rating:")) {
+ garray[g].white_rating = atoi(value);
+ } else if (!strcmp(attr, "black_rating:")) {
+ garray[g].black_rating = atoi(value);
+ } else if (!strcmp(attr, "result:")) {
+ garray[g].result = atoi(value);
+ } else if (!strcmp(attr, "timestart:")) {
+ garray[g].timeOfStart = atoi(value);
+ } else if (!strcmp(attr, "w_time:")) {
+ garray[g].wTime = atoi(value);
+ } else if (!strcmp(attr, "b_time:")) {
+ garray[g].bTime = atoi(value);
+ } else if (!strcmp(attr, "clockstopped:")) {
+ garray[g].clockStopped = atoi(value);
+ } else if (!strcmp(attr, "rated:")) {
+ garray[g].rated = atoi(value);
+ } else if (!strcmp(attr, "private:")) {
+ garray[g].private = atoi(value);
+ } else if (!strcmp(attr, "type:")) {
+ garray[g].type = atoi(value);
+ } else if (!strcmp(attr, "halfmoves:")) {
+ if ((garray[g].numHalfMoves = atoi(value)) == 0)
+ return 0;
+ else if (garray[g].numHalfMoves < 0 ||
+ (size_t)garray[g].numHalfMoves > INT_MAX / sizeof(move_t)) {
+ warnx("%s: num half moves out-of-bounds (%d)", __func__,
+ garray[g].numHalfMoves);
+ return -1;
+ } else {
+ /* null */;
+ }
+
+ garray[g].moveListSize = garray[g].numHalfMoves;
+ garray[g].moveList = reallocarray(NULL, sizeof(move_t),
+ garray[g].moveListSize);
+
+ if (garray[g].moveList == NULL)
+ err(1, "%s: reallocarray", __func__);
+ else
+ malloc_count++;
+
+ for (int i = 0; i < garray[g].numHalfMoves; i++) {
+ if (ReadMove(fp, &garray[g].moveList[i])) {
+ fprintf(stderr, "FICS: Trouble reading moves "
+ "from %s.\n", file);
+ return -1;
+ }
+ }
+ } else if (!strcmp(attr, "gamestate:")) { // Value meaningless
+ if (garray[g].status != GAME_EXAMINE &&
+ ReadGameState(fp, &garray[g].game_state, 0)) {
+ fprintf(stderr, "FICS: Trouble reading game state "
+ "from %s.\n", file);
+ return -1;
+ }
+ } else {
+ fprintf(stderr, "FICS: Error bad attribute >%s< from file %s\n",
+ attr, file);
+ }
+
+ return 0;
+}
+
+PRIVATE int
+ReadOneV1Move(FILE *fp, move_t *m)
+{
+ char PieceChar;
+ int i;
+ int useFile, useRank, check, piece;
+ unsigned long MoveInfo;
+
+ if (fscanf(fp, "%lx %x %x", &MoveInfo, &m->tookTime, &m->atTime) != 3)
+ return -1;
+
+ check = MoveInfo & 1;
+ useRank = MoveInfo & 2;
+ useFile = MoveInfo & 4;
+
+ MoveInfo >>= 3;
+ m->enPassant = (MoveInfo & 1); // May have to negate later.
+
+ MoveInfo >>= 1;
+ m->piecePromotionTo = (MoveInfo & 7); // May have to change color.
+
+ MoveInfo >>= 3;
+ m->pieceCaptured = (MoveInfo & 7); // May have to change color.
+
+ MoveInfo >>= 3;
+ m->toRank = (MoveInfo & 7);
+
+ MoveInfo >>= 3;
+ m->toFile = (MoveInfo & 7);
+
+ MoveInfo >>= 3;
+ m->fromRank = (MoveInfo & 7);
+
+ MoveInfo >>= 3;
+ m->fromFile = (MoveInfo & 7);
+
+ MoveInfo >>= 3;
+ piece = (MoveInfo & 7);
+
+ m->color = ((MoveInfo & 8) ? BLACK : WHITE);
+
+ if (m->pieceCaptured != NOPIECE) {
+ if (m->color == BLACK)
+ m->pieceCaptured |= WHITE;
+ else
+ m->pieceCaptured |= BLACK;
+ }
+
+ if (piece == PAWN) {
+ PieceChar = 'P';
+
+ if ((m->toRank == 3 && m->fromRank == 1) ||
+ (m->toRank == 4 && m->fromRank == 6))
+ m->doublePawn = m->toFile;
+ else
+ m->doublePawn = -1;
+
+ if (m->pieceCaptured) {
+ msnprintf(m->algString, sizeof m->algString, "%cx%c%d",
+ ('a' + m->fromFile),
+ ('a' + m->toFile),
+ (m->toRank + 1));
+ } else {
+ msnprintf(m->algString, sizeof m->algString, "%c%d",
+ ('a' + m->toFile),
+ (m->toRank + 1));
+ }
+
+ if (m->piecePromotionTo != 0) {
+ if (m->piecePromotionTo == KNIGHT) {
+ mstrlcat(m->algString, "=N",
+ sizeof(m->algString));
+ } else if (m->piecePromotionTo == BISHOP) {
+ mstrlcat(m->algString, "=B",
+ sizeof(m->algString));
+ } else if (m->piecePromotionTo == ROOK) {
+ mstrlcat(m->algString, "=R",
+ sizeof(m->algString));
+ } else if (m->piecePromotionTo == QUEEN) {
+ mstrlcat(m->algString, "=Q",
+ sizeof(m->algString));
+ }
+
+ m->piecePromotionTo |= m->color;
+ }
+
+ if (m->enPassant)
+ m->enPassant = (m->toFile - m->fromFile);
+ } else {
+ m->doublePawn = -1;
+ PieceChar = PieceToChar(piecetype(piece) | WHITE);
+
+ if (PieceChar == 'K' && m->fromFile == 4 && m->toFile == 6) {
+ mstrlcpy(m->algString, "O-O", sizeof(m->algString));
+ mstrlcpy(m->moveString, "o-o", sizeof(m->moveString));
+ } else if (PieceChar == 'K' &&
+ m->fromFile == 4 &&
+ m->toFile == 2) {
+ mstrlcpy(m->algString, "O-O-O", sizeof(m->algString));
+ mstrlcpy(m->moveString, "o-o-o", sizeof(m->moveString));
+ } else {
+ i = 0;
+ m->algString[i++] = PieceChar;
+
+ if (useFile)
+ m->algString[i++] = 'a' + m->fromFile;
+ if (useRank)
+ m->algString[i++] = '1' + m->fromRank;
+ if (m->pieceCaptured != 0)
+ m->algString[i++] = 'x';
+
+ m->algString[i++] = ('a' + m->toFile);
+ m->algString[i++] = ('1' + m->toRank);
+ m->algString[i] = '\0';
+ }
+ }
+
+ if (m->algString[0] != 'O') {
+ int ret, too_long;
+
+ ret = snprintf(m->moveString, sizeof m->moveString,
+ "%c/%c%d-%c%d",
+ PieceChar,
+ ('a' + m->fromFile),
+ (m->fromRank + 1),
+ ('a' + m->toFile),
+ (m->toRank + 1));
+
+ too_long = (ret < 0 || (size_t)ret >= sizeof m->moveString);
+
+ if (too_long) {
+ fprintf(stderr, "FICS: %s: warning: "
+ "snprintf truncated\n", __func__);
+ }
+ }
+ if (check)
+ mstrlcat(m->algString, "+", sizeof m->algString);
+ return 0;
+}
+
+PRIVATE int
+ReadV1Moves(game *g, FILE *fp)
+{
+ g->moveListSize = g->numHalfMoves;
+ g->moveList = reallocarray(NULL, sizeof(move_t), g->moveListSize);
+
+ if (g->moveList == NULL)
+ err(1, "%s: reallocarray", __func__);
+ else
+ malloc_count++;
+
+ for (int i = 0; i < g->numHalfMoves; i++) {
+ if (ReadOneV1Move(fp, &g->moveList[i]) == -1) {
+ warnx("%s: failed to read move %d/%d", __func__, i,
+ g->numHalfMoves);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+PRIVATE int
+ReadV1GameFmt(game *g, FILE *fp, const char *file, int version)
+{
+ int ret[3];
+ int64_t lval;
+
+ _Static_assert(17 < ARRAY_SIZE(g->white_name), "Unexpected array size");
+ _Static_assert(17 < ARRAY_SIZE(g->black_name), "Unexpected array size");
+
+ ret[0] = fscanf(fp, "%17s %17s", g->white_name, g->black_name);
+ ret[1] = fscanf(fp, "%d %d", // NOLINT
+ &g->white_rating,
+ &g->black_rating);
+ ret[2] = fscanf(fp, "%d %d %d %d", // NOLINT
+ &g->wInitTime,
+ &g->wIncrement,
+ &g->bInitTime,
+ &g->bIncrement);
+ if (ret[0] != 2 ||
+ ret[1] != 2 ||
+ ret[2] != 4) {
+ warnx("%s: fscanf error: %s", __func__, file);
+ return -1;
+ }
+
+ if (version < 3 && !g->bInitTime)
+ g->bInitTime = g->wInitTime;
+
+ if (fscanf(fp, "%" SCNd64, &lval) != 1) {
+ warnx("%s: %s: failed to get time of start", __func__, file);
+ return -1;
+ } else
+ g->timeOfStart = lval;
+
+ if (fscanf(fp, "%d %d", &g->wTime, &g->bTime) != 2) {
+ warnx("%s: %s: failed to get 'wTime' and 'bTime'", __func__,
+ file);
+ return -1;
+ }
+
+ if (version > 1) {
+ if (fscanf(fp, "%d %d", &g->result, &g->winner) != 2) {
+ warnx("%s: %s: failed to get 'result' nor 'winner'",
+ __func__, file);
+ return -1;
+ }
+ } else {
+ if (fscanf(fp, "%d", &g->result) != 1) {
+ warnx("%s: %s: failed to get 'result'",
+ __func__, file);
+ return -1;
+ }
+ }
+
+ ret[0] = fscanf(fp, "%d %d %d %d", &g->private, &g->type, &g->rated,
+ &g->clockStopped);
+ ret[1] = fscanf(fp, "%d", &g->numHalfMoves); // NOLINT
+ if (ret[0] != 4 || ret[1] != 1) {
+ warnx("%s: fscanf error: %s", __func__, file);
+ return -1;
+ } else if (g->numHalfMoves < 0 || (size_t)g->numHalfMoves >
+ INT_MAX / sizeof(move_t)) {
+ warnx("%s: warning: num half moves out-of-bounds (%d)",
+ __func__,
+ g->numHalfMoves);
+ return -1;
+ }
+
+ if (ReadV1Moves(g, fp) != 0) {
+ warnx("%s: failed to read moves: %s", __func__, file);
+ return -1;
+ }
+
+ if (g->status != GAME_EXAMINE &&
+ ReadGameState(fp, &g->game_state, version)) {
+ fprintf(stderr, "FICS: Trouble reading game state from %s.\n",
+ file);
+ return -1;
+ }
+
+ return 0;
+}
+
+PUBLIC int
+ReadGameAttrs(FILE *fp, char *fname, int g)
+{
+ char *attr, *value;
+ char line[MAX_GLINE_SIZE] = { '\0' };
+ int len = 0;
+ int version = 0;
+
+ if (fgets(line, sizeof line, fp) == NULL) {
+ warnx("%s: fgets error", __func__);
+ return -1;
+ }
+
+ if (line[0] == 'v') {
+ if (sscanf(line, "%*c %d", &version) != 1)
+ warn("%s: failed to get version", __func__);
+ }
+
+ if (version > 0) {
+ if (ReadV1GameFmt(&garray[g], fp, fname, version) == -1)
+ return -1;
+ } else {
+ do {
+ if ((len = strlen(line)) <= 1) {
+ if (fgets(line, sizeof line, fp) == NULL)
+ break;
+ continue;
+ }
+
+ line[len - 1] = '\0';
+ attr = eatwhite(line);
+
+ if (attr[0] == '#')
+ continue; // Comment
+
+ value = eatword(attr);
+
+ if (!*value) {
+ fprintf(stderr, "FICS: Error reading file %s\n",
+ fname);
+ if (fgets(line, sizeof line, fp) == NULL)
+ break;
+ continue;
+ }
+
+ *value = '\0';
+ value++;
+ value = eatwhite(value);
+
+ if (!*value) {
+ fprintf(stderr, "FICS: Error reading file %s\n",
+ fname);
+ if (fgets(line, sizeof line, fp) == NULL)
+ break;
+ continue;
+ }
+
+ stolower(attr);
+
+ if (got_attr_value(g, attr, value, fp, fname))
+ return -1;
+
+ if (fgets(line, sizeof line, fp) == NULL)
+ break;
+ } while (!feof(fp));
+ }
+
+ if (!(garray[g].bInitTime))
+ garray[g].bInitTime = garray[g].wInitTime;
+ return 0;
+}
+
+PUBLIC int
+game_read(int g, int wp, int bp)
+{
+ FILE *fp;
+ char fname[MAX_FILENAME_SIZE] = { '\0' };
+
+ garray[g].white = wp;
+ garray[g].black = bp;
+
+#if 0
+ garray[g].old_white = -1;
+ garray[g].old_black = -1;
+#endif
+
+ garray[g].moveListSize = 0;
+ garray[g].game_state.gameNum = g;
+
+ mstrlcpy(garray[g].white_name, parray[wp].name,
+ sizeof(garray[g].white_name));
+ mstrlcpy(garray[g].black_name, parray[bp].name,
+ sizeof(garray[g].black_name));
+
+ if (garray[g].type == TYPE_BLITZ) {
+ garray[g].white_rating = parray[wp].b_stats.rating;
+ garray[g].black_rating = parray[bp].b_stats.rating;
+ } else if (garray[g].type == TYPE_WILD) {
+ garray[g].white_rating = parray[wp].w_stats.rating;
+ garray[g].black_rating = parray[bp].w_stats.rating;
+ } else if (garray[g].type == TYPE_LIGHT) {
+ garray[g].white_rating = parray[wp].l_stats.rating;
+ garray[g].black_rating = parray[bp].l_stats.rating;
+ } else if (garray[g].type == TYPE_BUGHOUSE) {
+ garray[g].white_rating = parray[wp].bug_stats.rating;
+ garray[g].black_rating = parray[bp].bug_stats.rating;
+ } else {
+ garray[g].white_rating = parray[wp].s_stats.rating;
+ garray[g].black_rating = parray[bp].s_stats.rating;
+ }
+
+ msnprintf(fname, sizeof fname, "%s/%c/%s-%s", adj_dir,
+ parray[wp].login[0], parray[wp].login, parray[bp].login);
+ fp = fopen(fname, "r");
+
+ if (!fp) {
+ return -1;
+ }
+
+ if (ReadGameAttrs(fp, fname, g) < 0) {
+ fclose(fp);
+ return -1;
+ }
+
+ fclose(fp);
+
+ if (garray[g].result == END_ADJOURN || garray[g].result ==
+ END_COURTESYADJOURN)
+ garray[g].result = END_NOTENDED;
+
+ garray[g].status = GAME_ACTIVE;
+ garray[g].startTime = tenth_secs();
+ garray[g].lastMoveTime = garray[g].startTime;
+ garray[g].lastDecTime = garray[g].startTime;
+
+ // Need to do notification and pending cleanup
+ return 0;
+}
+
+PUBLIC int
+game_delete(int wp, int bp)
+{
+ char fname[MAX_FILENAME_SIZE];
+ char lname[MAX_FILENAME_SIZE];
+
+ msnprintf(fname, sizeof fname, "%s/%c/%s-%s", adj_dir,
+ parray[wp].login[0], parray[wp].login, parray[bp].login);
+ msnprintf(lname, sizeof lname, "%s/%c/%s-%s", adj_dir,
+ parray[bp].login[0], parray[wp].login, parray[bp].login);
+
+ unlink(fname);
+ unlink(lname);
+ return 0;
+}
+
+PRIVATE void
+WriteGameFile(FILE *fp, int g)
+{
+ game *gg = &garray[g];
+ int64_t lval;
+ player *bp = &parray[gg->black];
+ player *wp = &parray[gg->white];
+
+ fprintf(fp, "v %d\n", GAMEFILE_VERSION);
+ fprintf(fp, "%s %s\n", wp->name, bp->name);
+ fprintf(fp, "%d %d\n", gg->white_rating, gg->black_rating);
+ fprintf(fp, "%d %d %d %d\n", gg->wInitTime, gg->wIncrement,
+ gg->bInitTime, gg->bIncrement);
+
+ lval = gg->timeOfStart;
+ fprintf(fp, "%" PRId64 "\n", lval);
+
+#ifdef TIMESEAL
+ fprintf(fp, "%d %d\n",
+ (con[wp->socket].timeseal ? (gg->wRealTime / 100) : gg->wTime),
+ (con[bp->socket].timeseal ? (gg->bRealTime / 100) : gg->bTime));
+#endif
+
+ fprintf(fp, "%d %d\n", gg->result, gg->winner);
+ fprintf(fp, "%d %d %d %d\n", gg->private, gg->type, gg->rated,
+ gg->clockStopped);
+ fprintf(fp, "%d\n", gg->numHalfMoves);
+
+ for (int i = 0; i < garray[g].numHalfMoves; i++)
+ WriteMoves(fp, &garray[g].moveList[i]);
+
+ WriteGameState(fp, &garray[g].game_state);
+}
+
+PUBLIC int
+game_save(int g)
+{
+ FILE *fp;
+ char fname[MAX_FILENAME_SIZE];
+ char lname[MAX_FILENAME_SIZE];
+ game *gg = &garray[g];
+ int fd;
+ player *wp, *bp;
+
+ wp = &parray[gg->white];
+ bp = &parray[gg->black];
+
+ msnprintf(fname, sizeof fname, "%s/%c/%s-%s", adj_dir, wp->login[0],
+ wp->login, bp->login);
+ msnprintf(lname, sizeof lname, "%s/%c/%s-%s", adj_dir, bp->login[0],
+ wp->login, bp->login);
+
+ if ((fd = open(fname, g_open_flags[1], g_open_modes)) < 0) {
+ warn("%s: open: %s", __func__, fname);
+ return -1;
+ } else if ((fp = fdopen(fd, "w")) == NULL) {
+ fprintf(stderr, "FICS: Problem opening file %s for write\n",
+ fname);
+ close(fd);
+ return -1;
+ }
+
+ WriteGameFile(fp, g);
+
+#if 0
+ fprintf(fp, "W_Init: %d\n", garray[g].wInitTime);
+ fprintf(fp, "W_Inc: %d\n", garray[g].wIncrement);
+ fprintf(fp, "B_Init: %d\n", garray[g].bInitTime);
+ fprintf(fp, "B_Inc: %d\n", garray[g].bIncrement);
+ fprintf(fp, "white_name: %s\n", wp->name);
+ fprintf(fp, "black_name: %s\n", bp->name);
+ fprintf(fp, "white_rating: %d\n", garray[g].white_rating);
+ fprintf(fp, "black_rating: %d\n", garray[g].black_rating);
+ fprintf(fp, "result: %d\n", garray[g].result);
+ fprintf(fp, "TimeStart: %d\n", (int) garray[g].timeOfStart);
+ fprintf(fp, "W_Time: %d\n", garray[g].wTime);
+ fprintf(fp, "B_Time: %d\n", garray[g].bTime);
+ fprintf(fp, "ClockStopped: %d\n", garray[g].clockStopped);
+ fprintf(fp, "Rated: %d\n", garray[g].rated);
+ fprintf(fp, "Private: %d\n", garray[g].private);
+ fprintf(fp, "Type: %d\n", garray[g].type);
+ fprintf(fp, "HalfMoves: %d\n", garray[g].numHalfMoves);
+
+ for (int i = 0; i < garray[g].numHalfMoves; i++)
+ WriteMoves(fp, &garray[g].moveList[i]);
+
+ fprintf(fp, "GameState: IsNext\n");
+ WriteGameState(fp, &garray[g].game_state);
+#endif
+
+ fclose(fp);
+
+ /*
+ * Create link for easier stored game finding
+ */
+ if (bp->login[0] != wp->login[0] &&
+ link(fname, lname) != 0)
+ warn("%s: link() error", __func__);
+ return 0;
+}
+
+PRIVATE long int
+OldestHistGame(char *login)
+{
+ FILE *fp;
+ char pFile[MAX_FILENAME_SIZE] = { '\0' };
+ long int when;
+
+ /* Centralized validation of login */
+ if (!is_valid_login_name(login)) {
+ warnx("%s: invalid login value: '%s'", __func__, login);
+ return 0L;
+ }
+
+ msnprintf(pFile, sizeof pFile, "%s/player_data/%c/%s.%s", stats_dir,
+ login[0], login, STATS_GAMES);
+
+ if ((fp = fopen(pFile, "r")) == NULL) {
+ msnprintf(pFile, sizeof pFile, "%s/player_data/%c/.rem.%s.%s",
+ stats_dir, login[0], login, STATS_GAMES);
+ fp = fopen(pFile, "r");
+ }
+
+ if (fp != NULL) {
+ if (fscanf(fp, "%*d %*c %*d %*c %*d %*s %*s %*d %*d %*d %*d "
+ "%*s %*s %ld", &when) != 1) {
+ warnx("%s: %s: failed to read 'when'", __func__,
+ &pFile[0]);
+ fclose(fp);
+ return 0L;
+ }
+ fclose(fp);
+ return when;
+ } else
+ return 0L;
+}
+
+PRIVATE void
+RemoveHistGame(char *file, int maxlines)
+{
+ FILE *fp;
+ char GameFile[MAX_FILENAME_SIZE] = { '\0' };
+ char Opponent[MAX_LOGIN_NAME + 1] = { '\0' };
+ char line[MAX_LINE_SIZE] = { '\0' };
+ int count = 0;
+ long int When = 0, oppWhen = 0;
+
+ _Static_assert(20 < ARRAY_SIZE(Opponent), "Not within bounds");
+
+ if ((fp = fopen(file, "r")) == NULL) {
+ return;
+ } else if (fgets(line, ARRAY_SIZE(line), fp) == NULL) {
+ warnx("%s: fgets error (file: %s)", __func__, file);
+ fclose(fp);
+ return;
+ } else if (sscanf(line, "%*d %*c %*d %*c %*d %20s %*s %*d %*d %*d "
+ "%*d %*s %*s %ld", Opponent, &When) != 2) {
+ warnx("%s: unexpected initial line (file: %s)", __func__, file);
+ fclose(fp);
+ return;
+ }
+
+ count++;
+
+ while (fgets(line, ARRAY_SIZE(line), fp) != NULL)
+ count++;
+
+ fclose(fp);
+ stolower(Opponent);
+
+ if (count > maxlines) {
+ truncate_file(file, maxlines);
+ oppWhen = OldestHistGame(Opponent);
+
+ if (oppWhen > When || oppWhen <= 0L) {
+ msnprintf(GameFile, sizeof GameFile, "%s/%ld/%ld",
+ hist_dir, (When % 100), When);
+ unlink(GameFile);
+ }
+ }
+}
+
+PUBLIC void
+RemHist(char *who)
+{
+ FILE *fp;
+ char Opp[MAX_LOGIN_NAME] = { '\0' };
+ char fName[MAX_FILENAME_SIZE] = { '\0' };
+ long int When, oppWhen;
+
+ msnprintf(fName, sizeof fName, "%s/player_data/%c/%s.%s", stats_dir,
+ who[0], who, STATS_GAMES);
+
+ if ((fp = fopen(fName, "r")) != NULL) {
+ long int iter_no = 0;
+
+ while (!feof(fp) && !ferror(fp)) {
+ const int ret = fscanf(fp, "%*d %*c %*d %*c %*d %19s "
+ "%*s %*d %*d %*d %*d %*s %*s %ld\n", Opp, &When);
+ if (ret != 2) {
+ warnx("%s: fscanf() error (%s:%ld)", __func__,
+ fName, iter_no);
+ break;
+ }
+
+ stolower(Opp);
+
+ // Centralized validation: only allow safe login names
+ if (!is_valid_login_name(Opp)) {
+ warnx("%s: invalid value: "
+ "Opp = '%s' (skipping)", __func__, Opp);
+ iter_no++;
+ continue;
+ }
+
+ oppWhen = OldestHistGame(Opp);
+
+ if (oppWhen > When || oppWhen <= 0L) {
+ char histfile[MAX_FILENAME_SIZE] = { '\0' };
+
+ msnprintf(histfile, sizeof histfile,
+ "%s/%ld/%ld", hist_dir, (When % 100), When);
+ if (unlink(histfile) != 0) {
+ warn("%s: unlink(%s)", __func__,
+ histfile);
+ }
+ }
+
+ iter_no++;
+ }
+
+ fclose(fp);
+ }
+}
+
+PRIVATE void
+write_g_out(int g, char *file, int maxlines, int isDraw, char *EndSymbol,
+ char *name, time_t *now)
+{
+ FILE *fp;
+ char cResult;
+ char tmp[2048] = { '\0' };
+ char type[4];
+ char *goteco;
+ char *ptmp = tmp;
+ int count = -1;
+ int fd;
+ int wp, bp;
+ int wr, br;
+
+ wp = garray[g].white;
+ bp = garray[g].black;
+
+ if (garray[g].private) {
+ type[0] = 'p';
+ } else {
+ type[0] = ' ';
+ }
+
+ if (garray[g].type == TYPE_BLITZ) {
+ wr = parray[wp].b_stats.rating;
+ br = parray[bp].b_stats.rating;
+
+ type[1] = 'b';
+ } else if (garray[g].type == TYPE_WILD) {
+ wr = parray[wp].w_stats.rating;
+ br = parray[bp].w_stats.rating;
+
+ type[1] = 'w';
+ } else if (garray[g].type == TYPE_STAND) {
+ wr = parray[wp].s_stats.rating;
+ br = parray[bp].s_stats.rating;
+
+ type[1] = 's';
+ } else if (garray[g].type == TYPE_LIGHT) {
+ wr = parray[wp].l_stats.rating;
+ br = parray[bp].l_stats.rating;
+
+ type[1] = 'l';
+ } else if (garray[g].type == TYPE_BUGHOUSE) {
+ wr = parray[wp].bug_stats.rating;
+ br = parray[bp].bug_stats.rating;
+
+ type[1] = 'd';
+ } else {
+ wr = 0;
+ br = 0;
+
+ if (garray[g].type == TYPE_NONSTANDARD)
+ type[1] = 'n';
+ else
+ type[1] = 'u';
+ }
+
+ if (garray[g].rated) {
+ type[2] = 'r';
+ } else {
+ type[2] = 'u';
+ }
+
+ type[3] = '\0';
+
+ if ((fp = fopen(file, "r")) != NULL) {
+ while (fgets(tmp, sizeof tmp, fp) != NULL) {
+ /* null */;
+ }
+ if (sscanf(ptmp, "%d", &count) != 1)
+ warnx("%s: failed to read 'count'", __func__);
+ fclose(fp);
+ }
+
+ count = (count + 1) % 100;
+
+ if ((fd = open(file, g_open_flags[0], g_open_modes)) < 0) {
+ warn("%s: open: %s", __func__, file);
+ return;
+ } else if ((fp = fdopen(fd, "a")) == NULL) {
+ close(fd);
+ return;
+ }
+
+ goteco = getECO(g);
+
+ /*
+ * Counter
+ * Result
+ * MyRating
+ * MyColor
+ * OppRating
+ * OppName [pbr 2 12 2 12]
+ * ECO
+ * End
+ * Date
+ */
+ if (name == parray[wp].name) {
+ if (isDraw)
+ cResult = '=';
+ else if (garray[g].winner == WHITE)
+ cResult = '+';
+ else
+ cResult = '-';
+
+ fprintf(fp, "%d %c %d W %d %s %s %d %d %d %d %s %s %ld\n",
+ count, cResult, wr, br, parray[bp].name, type,
+ garray[g].wInitTime, garray[g].wIncrement,
+ garray[g].bInitTime, garray[g].bIncrement,
+ goteco,
+ EndSymbol,
+ (long int) *now);
+ } else {
+ if (isDraw)
+ cResult = '=';
+ else if (garray[g].winner == BLACK)
+ cResult = '+';
+ else
+ cResult = '-';
+
+ fprintf(fp, "%d %c %d B %d %s %s %d %d %d %d %s %s %ld\n",
+ count, cResult, br, wr, parray[wp].name, type,
+ garray[g].wInitTime, garray[g].wIncrement,
+ garray[g].bInitTime, garray[g].bIncrement,
+ goteco,
+ EndSymbol,
+ (long int) *now);
+ }
+
+ fclose(fp);
+ RemoveHistGame(file, maxlines);
+}
+
+/*
+ * Find from_spot in journal list - return 0 if corrupted
+ */
+PUBLIC int
+journal_get_info(struct JGI_context *ctx, const char *fname)
+{
+ FILE *fp;
+ char count;
+
+ if ((fp = fopen(fname, "r")) == NULL) {
+ fprintf(stderr, "Corrupt journal file! %s\n", fname);
+ pprintf(ctx->p, "The journal file is corrupt! See an admin.\n");
+ return 0;
+ }
+
+ while (!feof(fp)) {
+ _Static_assert(ARRAY_SIZE(ctx->WhiteName) > 20,
+ "'WhiteName' too small");
+ _Static_assert(ARRAY_SIZE(ctx->BlackName) > 20,
+ "'BlackName' too small");
+
+ _Static_assert(ARRAY_SIZE(ctx->type) > 99, "'type' too small");
+ _Static_assert(ARRAY_SIZE(ctx->eco) > 99, "'eco' too small");
+ _Static_assert(ARRAY_SIZE(ctx->ending) > 99, "'ending' too small");
+ _Static_assert(ARRAY_SIZE(ctx->result) > 99, "'result' too small");
+
+ if (fscanf(fp, "%c %20s %d %20s %d %99s %d %d %99s %99s %99s\n",
+ &count,
+ ctx->WhiteName, &ctx->WhiteRating,
+ ctx->BlackName, &ctx->BlackRating,
+ ctx->type,
+ &ctx->t, &ctx->i,
+ ctx->eco,
+ ctx->ending,
+ ctx->result) != 11) {
+ fprintf(stderr, "FICS: Error in journal info format. "
+ "%s\n", fname);
+ pprintf(ctx->p, "The journal file is corrupt! Error in "
+ "internal format.\n");
+ fclose(fp);
+ return 0;
+ }
+
+ if (tolower(count) == ctx->from_spot) {
+ fclose(fp);
+ return 1;
+ }
+ }
+
+ fclose(fp);
+ return 0;
+}
+
+PUBLIC void
+addjournalitem(int p, char count2, char *WhiteName2, int WhiteRating2,
+ char *BlackName2, int BlackRating2, char *type2, int t2, int i2,
+ char *eco2, char *ending2, char *result2, char *fname)
+{
+ FILE *fp;
+ FILE *fp2;
+ char BlackName[MAX_LOGIN_NAME + 1] = { '\0' };
+ char WhiteName[MAX_LOGIN_NAME + 1] = { '\0' };
+ char count;
+ char eco[100] = { '\0' };
+ char ending[100] = { '\0' };
+ char fname2[MAX_FILENAME_SIZE] = { '\0' };
+ char result[100] = { '\0' };
+ char type[100] = { '\0' };
+ int WhiteRating, BlackRating;
+ int fd;
+ int have_output = 0;
+ int t, i;
+
+ mstrlcpy(fname2, fname, sizeof fname2);
+ mstrlcat(fname2, ".w", sizeof fname2);
+
+ if ((fd = open(fname2, g_open_flags[1], g_open_modes)) < 0) {
+ warn("%s: open", __func__);
+ return;
+ } else if ((fp2 = fdopen(fd, "w")) == NULL) {
+ fprintf(stderr, "FICS: Problem opening file %s for write\n",
+ fname);
+ pprintf(p, "Couldn't update journal! Report this to an admin."
+ "\n");
+ close(fd);
+ return;
+ }
+
+ if ((fp = fopen(fname, "r")) == NULL) { // Empty?
+ fprintf(fp2, "%c %s %d %s %d %s %d %d %s %s %s\n",
+ count2,
+ WhiteName2, WhiteRating2,
+ BlackName2, BlackRating2,
+ type2,
+ t2, i2,
+ eco2,
+ ending2,
+ result2);
+ fclose(fp2);
+ xrename(__func__, fname2, fname);
+ return;
+ } else {
+ _Static_assert(ARRAY_SIZE(WhiteName) > 19,
+ "'WhiteName' too small");
+ _Static_assert(ARRAY_SIZE(BlackName) > 19,
+ "'BlackName' too small");
+
+ _Static_assert(ARRAY_SIZE(type) > 99, "'type' too small");
+ _Static_assert(ARRAY_SIZE(eco) > 99, "'eco' too small");
+ _Static_assert(ARRAY_SIZE(ending) > 99, "'ending' too small");
+ _Static_assert(ARRAY_SIZE(result) > 99, "'result' too small");
+
+ while (!feof(fp)) {
+ if (fscanf(fp, "%c %19s %d %19s %d %99s %d %d %99s "
+ "%99s %99s\n",
+ &count,
+ WhiteName, &WhiteRating,
+ BlackName, &BlackRating,
+ type,
+ &t, &i,
+ eco,
+ ending,
+ result) != 11) {
+ fprintf(stderr, "FICS: Error in journal info "
+ "format - aborting. %s\n", fname);
+ fclose(fp);
+ fclose(fp2);
+ return;
+ }
+
+ if ((count >= count2) && (!have_output)) {
+ fprintf(fp2, "%c %s %d %s %d %s %d %d %s %s %s\n",
+ count2,
+ WhiteName2, WhiteRating2,
+ BlackName2, BlackRating2,
+ type2,
+ t2, i2,
+ eco2,
+ ending2,
+ result2);
+ have_output = 1;
+ }
+
+ if (count != count2) {
+ fprintf(fp2, "%c %s %d %s %d %s %d %d %s %s %s"
+ "\n",
+ count,
+ WhiteName, WhiteRating,
+ BlackName, BlackRating,
+ type,
+ t, i,
+ eco,
+ ending,
+ result);
+ }
+ }
+
+ if (!have_output) { // Haven't written yet
+ fprintf(fp2, "%c %s %d %s %d %s %d %d %s %s %s\n",
+ count2,
+ WhiteName2, WhiteRating2,
+ BlackName2, BlackRating2,
+ type2,
+ t2, i2,
+ eco2,
+ ending2,
+ result2);
+ }
+ }
+
+ fclose(fp);
+ fclose(fp2);
+
+ xrename(__func__, fname2, fname);
+}
+
+PUBLIC int
+pjournal(int p, int p1, char *fname)
+{
+ FILE *fp;
+ char BlackName[MAX_LOGIN_NAME + 1] = { '\0' };
+ char WhiteName[MAX_LOGIN_NAME + 1] = { '\0' };
+ char count;
+ char eco[100] = { '\0' };
+ char ending[100] = { '\0' };
+ char result[100] = { '\0' };
+ char type[100] = { '\0' };
+ int WhiteRating, BlackRating;
+ int t, i;
+
+ if ((fp = fopen(fname, "r")) == NULL) {
+ pprintf(p, "Sorry, no journal information available.\n");
+ return COM_OK;
+ }
+
+ pprintf(p, "Journal for %s:\n", parray[p1].name);
+ pprintf(p, " White Rating Black Rating "
+ "Type ECO End Result\n");
+
+ _Static_assert(ARRAY_SIZE(WhiteName) > 19, "'WhiteName' too small");
+ _Static_assert(ARRAY_SIZE(BlackName) > 19, "'BlackName' too small");
+
+ _Static_assert(ARRAY_SIZE(type) > 99, "'type' too small");
+ _Static_assert(ARRAY_SIZE(eco) > 99, "'eco' too small");
+ _Static_assert(ARRAY_SIZE(ending) > 99, "'ending' too small");
+ _Static_assert(ARRAY_SIZE(result) > 99, "'result' too small");
+
+ while (!feof(fp)) {
+ if (fscanf(fp, "%c %19s %d %19s %d %99s %d %d %99s %99s %99s\n",
+ &count,
+ WhiteName, &WhiteRating,
+ BlackName, &BlackRating,
+ type,
+ &t, &i,
+ eco,
+ ending,
+ result) != 11) {
+ fprintf(stderr, "FICS: Error in journal info format. "
+ "%s\n", fname);
+ fclose(fp);
+ return COM_OK;
+ }
+
+ WhiteName[13] = '\0'; // only first 13 chars in name
+ BlackName[13] = '\0';
+
+ pprintf(p, "%c: %-13s %4d %-13s %4d [%3s%3d%4d] %s %3s "
+ "%-7s\n",
+ count, WhiteName, WhiteRating,
+ BlackName, BlackRating,
+ type, (t / 600), (i / 10), eco, ending,
+ result);
+ }
+
+ fclose(fp);
+ return COM_OK;
+}
+
+PUBLIC int
+pgames(int p, int p1, char *fname)
+{
+ FILE *fp;
+ char MyColor[2] = { 0,0 };
+ char OppName[MAX_LOGIN_NAME + 1] = { '\0' };
+ char eco[100] = { '\0' };
+ char ending[100] = { '\0' };
+ char result[2] = { 0,0 }; // XXX: right size?
+ char type[100] = { '\0' };
+ int MyRating, OppRating;
+ int count;
+ int wt, wi, bt, bi;
+ time_t t;
+
+ if ((fp = fopen(fname, "r")) == NULL) {
+ pprintf(p, "Sorry, no game information available.\n");
+ return COM_OK;
+ }
+
+ pprintf(p, "History for %s:\n", parray[p1].name);
+ pprintf(p, " Opponent Type "
+ "ECO End Date\n");
+
+ _Static_assert(ARRAY_SIZE(result) > 1, "'result' too small");
+ _Static_assert(ARRAY_SIZE(MyColor) > 1, "'MyColor' too small");
+ _Static_assert(ARRAY_SIZE(OppName) > 19, "'OppName' too small");
+ _Static_assert(ARRAY_SIZE(type) > 99, "'type' too small");
+ _Static_assert(ARRAY_SIZE(eco) > 99, "'eco' too small");
+ _Static_assert(ARRAY_SIZE(ending) > 99, "'ending' too small");
+
+ while (!feof(fp)) {
+ char tbuf[30] = { '\0' };
+
+ if (fscanf(fp, "%d %1s %d %1s %d %19s %99s %d %d %d %d %99s "
+ "%99s %ld\n",
+ &count, result, &MyRating, MyColor,
+ &OppRating, OppName,
+ type,
+ &wt, &wi,
+ &bt, &bi,
+ eco,
+ ending,
+ (long int *)&t) != 14) {
+ fprintf(stderr, "FICS: Error in games info format. "
+ "%s\n", fname);
+ fclose(fp);
+ return COM_OK;
+ }
+
+ OppName[13] = '\0'; // only first 13 chars in name
+
+ pprintf(p, "%2d: %s %4d %s %4d %-13s [%3s%3d%4d] %s %3s %s",
+ count, result, MyRating, MyColor,
+ OppRating, OppName,
+ type, (wt / 600), (wi / 10), eco, ending,
+ ctime_r(&t, tbuf) != NULL ? &tbuf[0] : "");
+ }
+
+ fclose(fp);
+ return COM_OK;
+}
+
+PUBLIC void
+game_write_complete(int g, int isDraw, char *EndSymbol)
+{
+ FILE *fp = NULL;
+ char fname[MAX_FILENAME_SIZE] = { '\0' };
+ int fd = -1;
+ int wp = garray[g].white, bp = garray[g].black;
+ time_t now = time(NULL);
+
+ do {
+ msnprintf(fname, sizeof fname, "%s/%ld/%ld",
+ hist_dir,
+ (long int)(now % 100),
+ (long int)now);
+ errno = 0;
+ fd = open(fname, (O_WRONLY | O_CREAT | O_EXCL), 0644);
+ if (fd == -1 && errno == EEXIST)
+ now++;
+ } while (fd == -1 && errno == EEXIST);
+
+ if (fd >= 0) {
+ if ((fp = fdopen(fd, "w")) != NULL) {
+ WriteGameFile(fp, g);
+ fclose(fp);
+ } else {
+ fprintf(stderr, "Trouble writing history file %s",
+ fname);
+ }
+
+ close(fd);
+ }
+
+ msnprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s",
+ stats_dir,
+ parray[wp].login[0],
+ parray[wp].login,
+ STATS_GAMES);
+ write_g_out(g, fname, 10, isDraw, EndSymbol, parray[wp].name, &now);
+
+ msnprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s",
+ stats_dir,
+ parray[bp].login[0],
+ parray[bp].login,
+ STATS_GAMES);
+ write_g_out(g, fname, 10, isDraw, EndSymbol, parray[bp].name, &now);
+}
+
+PUBLIC int
+game_count(void)
+{
+ int g, count = 0;
+
+ for (g = 0; g < g_num; g++) {
+ if (garray[g].status == GAME_ACTIVE ||
+ garray[g].status == GAME_EXAMINE)
+ count++;
+ }
+
+ if (count > game_high)
+ game_high = count;
+ return count;
+}
diff --git a/FICS/gamedb.h b/FICS/gamedb.h
new file mode 100644
index 0000000..8dc84d3
--- /dev/null
+++ b/FICS/gamedb.h
@@ -0,0 +1,205 @@
+/* gamedb.h
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 23/12/20 Cleaned up the file
+ Markus Uhlin 24/04/29 Added missing parameter lists
+*/
+
+#ifndef _GAMEDB_H
+#define _GAMEDB_H
+
+#include <stdint.h>
+#include <time.h>
+
+#include "board.h"
+#include "command.h"
+
+extern const char *bstr[7];
+extern const char *rstr[2];
+
+#define GAMEFILE_VERSION 3
+#define MAX_GLINE_SIZE 1024
+
+#define REL_GAME 0
+#define REL_SPOS 1
+#define REL_REFRESH 2
+#define REL_EXAMINE 3
+
+#define GAME_EMPTY 0
+#define GAME_NEW 1
+#define GAME_ACTIVE 2
+#define GAME_EXAMINE 3
+
+enum {
+ TYPE_UNRATED = 0,
+ TYPE_RATED
+};
+
+#define TYPE_UNTIMED 0
+#define TYPE_BLITZ 1
+#define TYPE_STAND 2
+#define TYPE_NONSTANDARD 3
+#define TYPE_WILD 4
+#define TYPE_LIGHT 5
+#define TYPE_BUGHOUSE 6
+
+#ifdef TIMESEAL
+#define FLAG_CHECKING -1
+#define FLAG_NONE 0
+#define FLAG_CALLED 1
+#define FLAG_ABORT 2
+#endif
+
+#define END_CHECKMATE 0
+#define END_RESIGN 1
+#define END_FLAG 2
+#define END_AGREEDDRAW 3
+#define END_REPETITION 4
+#define END_50MOVERULE 5
+#define END_ADJOURN 6
+#define END_LOSTCONNECTION 7
+#define END_ABORT 8
+#define END_STALEMATE 9
+#define END_NOTENDED 10
+#define END_COURTESY 11
+#define END_BOTHFLAG 12
+#define END_NOMATERIAL 13
+#define END_FLAGNOMATERIAL 14
+#define END_ADJDRAW 15
+#define END_ADJWIN 16
+#define END_ADJABORT 17
+#define END_COURTESYADJOURN 18
+
+typedef struct _game {
+ /*
+ * Saved in the game file.
+ */
+ int wInitTime, wIncrement;
+ int bInitTime, bIncrement;
+ time_t timeOfStart;
+
+#ifdef TIMESEAL
+ int bLastRealTime;
+ int bRealTime;
+ int bTimeWhenMoved;
+ int bTimeWhenReceivedMove;
+ int flag_pending;
+ int wLastRealTime;
+ int wRealTime;
+ int wTimeWhenMoved;
+ int wTimeWhenReceivedMove;
+ unsigned long flag_check_time;
+#endif
+
+ int wTime;
+ int bTime;
+ int clockStopped;
+ int rated;
+ int private;
+ int type;
+ int passes; // For simul's
+ int numHalfMoves;
+ move_t *moveList; // Primary movelist
+ unsigned char FENstartPos[74]; // Save the starting position
+ game_state_t game_state;
+ char white_name[18]; // To hold the playername even
+ // after he disconnects
+ char black_name[18];
+ int white_rating;
+ int black_rating;
+
+ /*
+ * Not saved in game file
+ */
+ int revertHalfMove;
+ int totalHalfMoves;
+ int white;
+ int black;
+ int link;
+ int status;
+ int moveListSize; // Total allocated in '*moveList'
+ int examHalfMoves;
+ move_t *examMoveList; // Extra movelist for examine
+ int examMoveListSize;
+
+ uint64_t startTime; // The relative time the game started
+ uint64_t lastMoveTime; // Last time a move was made
+ uint64_t lastDecTime; // Last time a players clock was
+ // decremented
+
+ int result;
+ int winner;
+} game;
+
+struct JGI_context {
+ int p;
+ char from_spot;
+ char WhiteName[MAX_LOGIN_NAME + 1];
+ int WhiteRating;
+ char BlackName[MAX_LOGIN_NAME + 1];
+ int BlackRating;
+ char type[100];
+ int t;
+ int i;
+ char eco[100];
+ char ending[100];
+ char result[100];
+};
+
+extern game *garray;
+extern int g_num;
+
+extern char *EndString(int, int);
+extern char *EndSym(int);
+extern char *game_str(int, int, int, int, int, char *, char *);
+extern char *game_time_str(int, int, int, int);
+extern char *movesToString(int, int);
+extern int CharToPiece(char);
+extern int PieceToChar(int);
+extern int ReadGameAttrs(FILE *, char *, int);
+extern int game_clear(int);
+extern int game_count(void);
+extern int game_delete(int, int);
+extern int game_finish(int);
+extern int game_free(int);
+extern int game_isblitz(int, int, int, int, char *, char *);
+extern int game_new(void);
+extern int game_read(int, int, int);
+extern int game_remove(int);
+extern int game_save(int);
+extern int game_zero(int);
+extern int got_attr_value(int, char *, char *, FILE *, char *);
+extern int journal_get_info(struct JGI_context *, const char *);
+extern int pgames(int, int, char *);
+extern int pjournal(int, int, char *);
+extern void MakeFENpos(int, char *, size_t);
+extern void RemHist(char *);
+extern void addjournalitem(int, char, char *, int, char *, int, char *,
+ int, int, char *, char *, char *, char *);
+extern void game_disconnect(int, int);
+extern void game_update_time(int);
+extern void game_update_times(void);
+extern void game_write_complete(int, int, char *);
+extern void send_board_to(int, int);
+extern void send_boards(int);
+
+#endif
diff --git a/FICS/gameproc.c b/FICS/gameproc.c
new file mode 100644
index 0000000..cd6398e
--- /dev/null
+++ b/FICS/gameproc.c
@@ -0,0 +1,2164 @@
+/* gameproc.c
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Dave Herscovici 95/11/26 Split into two files;
+ Second is 'obsproc.c'.
+ Markus Uhlin 23/12/16 Fixed compiler warnings
+ Markus Uhlin 24/01/04 Fixed pprintf_prompt() calls
+ Markus Uhlin 24/03/30 Refactored and reformatted all
+ functions.
+ Markus Uhlin 24/03/30 Size-bounded string handling.
+ Markus Uhlin 24/04/13 Added usage of msnprintf(),
+ mstrlcpy() and mstrlcat().
+ Markus Uhlin 24/05/05 Added usage of reallocarray().
+ Markus Uhlin 25/03/09 Calc string length once
+*/
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include <err.h>
+
+#include "command.h"
+#include "comproc.h"
+#include "config.h"
+#include "eco.h"
+#include "ficsmain.h"
+#include "gamedb.h"
+#include "gameproc.h"
+#include "lists.h"
+#include "matchproc.h"
+#include "maxxes-utils.h"
+#include "movecheck.h"
+#include "network.h"
+#include "obsproc.h"
+#include "playerdb.h"
+#include "ratings.h"
+#include "rmalloc.h"
+#include "utils.h"
+
+#if __linux__
+#include <bsd/string.h>
+#endif
+
+PUBLIC void
+game_ended(int g, int winner, int why)
+{
+ char *NameOfWinner, *NameOfLoser;
+ char EndSymbol[10] = { '\0' };
+ char outstr[200] = { '\0' };
+ char tmp[200] = { '\0' };
+ char winSymbol[10] = { '\0' };
+ int beingplayed = 0; // i.e. it wasn't loaded for adjudication
+ int gl = garray[g].link;
+ int isDraw = 0;
+ int p;
+ int rate_change = 0;
+ int whiteResult;
+
+ beingplayed = (parray[garray[g].black].game == g);
+
+ msnprintf(outstr, sizeof outstr, "\n{Game %d (%s vs. %s) ",
+ (g + 1),
+ parray[garray[g].white].name,
+ parray[garray[g].black].name);
+
+ garray[g].result = why;
+ garray[g].winner = winner;
+
+ if (winner == WHITE) {
+ whiteResult = RESULT_WIN;
+ mstrlcpy(winSymbol, "1-0", sizeof winSymbol);
+ NameOfWinner = parray[garray[g].white].name;
+ NameOfLoser = parray[garray[g].black].name;
+ } else {
+ whiteResult = RESULT_LOSS;
+ mstrlcpy(winSymbol, "0-1", sizeof winSymbol);
+ NameOfWinner = parray[garray[g].black].name;
+ NameOfLoser = parray[garray[g].white].name;
+ }
+
+ switch (why) {
+ case END_CHECKMATE:
+ msnprintf(tmp, sizeof tmp, "%s checkmated} %s\n",
+ NameOfLoser,
+ winSymbol);
+ mstrlcpy(EndSymbol, "Mat", sizeof EndSymbol);
+ rate_change = 1;
+ break;
+ case END_RESIGN:
+ msnprintf(tmp, sizeof tmp, "%s resigns} %s\n",
+ NameOfLoser,
+ winSymbol);
+ mstrlcpy(EndSymbol, "Res", sizeof EndSymbol);
+ rate_change = 1;
+ break;
+ case END_FLAG:
+ msnprintf(tmp, sizeof tmp, "%s forfeits on time} %s\n",
+ NameOfLoser,
+ winSymbol);
+ mstrlcpy(EndSymbol, "Fla", sizeof EndSymbol);
+ rate_change = 1;
+ break;
+ case END_STALEMATE:
+ mstrlcpy(tmp, "Game drawn by stalemate} 1/2-1/2\n", sizeof tmp);
+ isDraw = 1;
+ mstrlcpy(EndSymbol, "Sta", sizeof EndSymbol);
+ rate_change = 1;
+ whiteResult = RESULT_DRAW;
+ break;
+ case END_AGREEDDRAW:
+ mstrlcpy(tmp, "Game drawn by mutual agreement} 1/2-1/2\n",
+ sizeof tmp);
+ isDraw = 1;
+ mstrlcpy(EndSymbol, "Agr", sizeof EndSymbol);
+ rate_change = 1;
+ whiteResult = RESULT_DRAW;
+ break;
+ case END_BOTHFLAG:
+ mstrlcpy(tmp, "Game drawn because both players ran out of "
+ "time} 1/2-1/2\n", sizeof tmp);
+ isDraw = 1;
+ mstrlcpy(EndSymbol, "Fla", sizeof EndSymbol);
+ rate_change = 1;
+ whiteResult = RESULT_DRAW;
+ break;
+ case END_REPETITION:
+ mstrlcpy(tmp, "Game drawn by repetition} 1/2-1/2\n", sizeof tmp);
+ isDraw = 1;
+ mstrlcpy(EndSymbol, "Rep", sizeof EndSymbol);
+ rate_change = 1;
+ whiteResult = RESULT_DRAW;
+ break;
+ case END_50MOVERULE:
+ mstrlcpy(tmp, "Game drawn by the 50 move rule} 1/2-1/2\n",
+ sizeof tmp);
+ isDraw = 1;
+ mstrlcpy(EndSymbol, "50", sizeof EndSymbol);
+ rate_change = 1;
+ whiteResult = RESULT_DRAW;
+ break;
+ case END_ADJOURN:
+ if (gl >= 0) {
+ mstrlcpy(tmp, "Bughouse game aborted.} *\n",
+ sizeof tmp);
+ whiteResult = RESULT_ABORT;
+ } else {
+ mstrlcpy(tmp, "Game adjourned by mutual agreement} *\n",
+ sizeof tmp);
+ game_save(g);
+ }
+ break;
+ case END_LOSTCONNECTION:
+ msnprintf(tmp, sizeof tmp, "%s lost connection; game ",
+ NameOfWinner);
+
+ if (parray[garray[g].white].registered &&
+ parray[garray[g].black].registered &&
+ gl < 0) {
+ mstrlcpy(tmp, "adjourned} *\n", sizeof tmp);
+ game_save(g);
+ } else
+ mstrlcpy(tmp, "aborted} *\n", sizeof tmp);
+ whiteResult = RESULT_ABORT;
+ break;
+ case END_ABORT:
+ mstrlcpy(tmp, "Game aborted by mutual agreement} *\n",
+ sizeof tmp);
+ whiteResult = RESULT_ABORT;
+ break;
+ case END_COURTESY:
+ msnprintf(tmp, sizeof tmp, "Game courtesyaborted by %s} *\n",
+ NameOfWinner);
+ whiteResult = RESULT_ABORT;
+ break;
+ case END_COURTESYADJOURN:
+ if (gl >= 0) {
+ msnprintf(tmp, sizeof tmp, "Bughouse game "
+ "courtesyaborted by %s.} *\n",
+ NameOfWinner);
+ whiteResult = RESULT_ABORT;
+ } else {
+ msnprintf(tmp, sizeof tmp, "Game courtesyadjourned by "
+ "%s} *\n",
+ NameOfWinner);
+ game_save(g);
+ }
+ break;
+ case END_NOMATERIAL:
+ // Draw by insufficient material (e.g., lone K vs. lone K)
+ mstrlcpy(tmp, "Neither player has mating material} 1/2-1/2\n",
+ sizeof tmp);
+ isDraw = 1;
+ mstrlcpy(EndSymbol, "NM ", sizeof EndSymbol);
+ rate_change = 1;
+ whiteResult = RESULT_DRAW;
+ break;
+ case END_FLAGNOMATERIAL:
+ msnprintf(tmp, sizeof tmp, "%s ran out of time and %s has no "
+ "material to mate} 1/2-1/2\n",
+ NameOfLoser,
+ NameOfWinner);
+ isDraw = 1;
+ mstrlcpy(EndSymbol, "TM ", sizeof EndSymbol);
+ rate_change = 1;
+ whiteResult = RESULT_DRAW;
+ break;
+ case END_ADJWIN:
+ msnprintf(tmp, sizeof tmp, "%s wins by adjudication} %s\n",
+ NameOfWinner, winSymbol);
+ mstrlcpy(EndSymbol, "Adj", sizeof EndSymbol);
+ rate_change = 1;
+ break;
+ case END_ADJDRAW:
+ mstrlcpy(tmp, "Game drawn by adjudication} 1/2-1/2\n",
+ sizeof tmp);
+ isDraw = 1;
+ mstrlcpy(EndSymbol, "Adj", sizeof EndSymbol);
+ rate_change = 1;
+ whiteResult = RESULT_DRAW;
+ break;
+ case END_ADJABORT:
+ mstrlcpy(tmp, "Game aborted by adjudication} *\n", sizeof tmp);
+ whiteResult = RESULT_ABORT;
+ break;
+ default:
+ mstrlcpy(tmp, "Hmm, the game ended and I don't know why} *\n",
+ sizeof tmp);
+ break;
+ }
+
+ mstrlcat(outstr, tmp, sizeof outstr);
+
+ if (beingplayed) {
+ pprintf_noformat(garray[g].white, "%s", outstr);
+ pprintf_noformat(garray[g].black, "%s", outstr);
+
+ if (parray[garray[g].white].bell)
+ pprintf(garray[g].white, "\007");
+ if (parray[garray[g].black].bell)
+ pprintf(garray[g].black, "\007");
+
+ garray[g].link = -1; // IanO: avoids recursion
+
+ if (gl >= 0 && garray[gl].link >= 0) {
+ pprintf_noformat(garray[gl].white, "%s", outstr);
+ pprintf_noformat(garray[gl].black, "%s", outstr);
+
+ game_ended(gl, CToggle(winner), why);
+ }
+
+ for (p = 0; p < p_num; p++) {
+ if (p == garray[g].white || p == garray[g].black)
+ continue;
+ if (parray[p].status != PLAYER_PROMPT)
+ continue;
+ if (!parray[p].i_game && !player_is_observe(p, g))
+ continue;
+
+ pprintf_noformat(p, "%s", outstr);
+ pprintf_prompt(p, "%s", "");
+ }
+ }
+
+ if (garray[g].rated && rate_change) {
+ /* Adjust ratings */
+ rating_update(g);
+ } else {
+ if (beingplayed) {
+ pprintf(garray[g].white, "No ratings adjustment done."
+ "\n");
+ pprintf(garray[g].black, "No ratings adjustment done."
+ "\n");
+ }
+ }
+
+ if (rate_change && gl < 0)
+ game_write_complete(g, isDraw, EndSymbol);
+
+ /*
+ * Mail off the moves
+ */
+ if (parray[garray[g].white].automail)
+ pcommand(garray[g].white, "mailmoves");
+ if (parray[garray[g].black].automail)
+ pcommand(garray[g].black, "mailmoves");
+
+ parray[garray[g].white].num_white++;
+ parray[garray[g].white].lastColor = WHITE;
+ parray[garray[g].black].num_black++;
+ parray[garray[g].black].lastColor = BLACK;
+ parray[garray[g].white].last_opponent = garray[g].black;
+ parray[garray[g].black].last_opponent = garray[g].white;
+
+ if (beingplayed) {
+ parray[garray[g].white].game = -1;
+ parray[garray[g].black].game = -1;
+ parray[garray[g].white].opponent = -1;
+ parray[garray[g].black].opponent = -1;
+
+ if (garray[g].white != commanding_player)
+ pprintf_prompt(garray[g].white, "%s", "");
+ if (garray[g].black != commanding_player)
+ pprintf_prompt(garray[g].black, "%s", "");
+
+ if (parray[garray[g].white].simul_info.numBoards)
+ player_simul_over(garray[g].white, g, whiteResult);
+ }
+
+ game_finish(g);
+}
+
+PRIVATE int
+was_promoted(game *g, int f, int r)
+{
+#define BUGHOUSE_PAWN_REVERT 1
+#if BUGHOUSE_PAWN_REVERT
+ for (int i = g->numHalfMoves-2; i > 0; i -= 2) {
+ if (g->moveList[i].toFile == f &&
+ g->moveList[i].toRank == r) {
+ if (g->moveList[i].piecePromotionTo)
+ return 1;
+ if (g->moveList[i].fromFile == ALG_DROP)
+ return 0;
+ f = g->moveList[i].fromFile;
+ r = g->moveList[i].fromRank;
+ }
+ }
+#endif
+ return 0;
+}
+
+PUBLIC int
+pIsPlaying(int p)
+{
+ int g = parray[p].game;
+ int p1 = parray[p].opponent;
+
+ if (g < 0 || garray[g].status == GAME_EXAMINE) {
+ pprintf(p, "You are not playing a game.\n");
+ return 0;
+ } else if (garray[g].white != p && garray[g].black != p) {
+ /*
+ * oh oh; big bad game bug.
+ */
+
+ fprintf(stderr, "BUG: Player %s playing game %d according to "
+ "parray, but not according to garray.\n",
+ parray[p].name, (g + 1));
+ pprintf(p, "Disconnecting you from game number %d.\n", (g + 1));
+ parray[p].game = -1;
+
+ if (p1 >= 0 &&
+ parray[p1].game == g &&
+ garray[g].white != p1 &&
+ garray[g].black != p1) {
+ pprintf(p1, "Disconnecting you from game number %d.\n",
+ (g + 1));
+ parray[p1].game = -1;
+ }
+
+ return 0;
+ } else
+ return 1;
+}
+
+PUBLIC void
+process_move(int p, char *command)
+{
+ int g;
+ int i;
+ int len;
+ int result;
+ move_t move;
+ unsigned int now;
+
+ if (parray[p].game < 0) {
+ pprintf(p, "You are not playing or examining a game.\n");
+ return;
+ }
+
+ player_decline_offers(p, -1, -PEND_SIMUL);
+ g = parray[p].game;
+
+ if (garray[g].status != GAME_EXAMINE) {
+ if (!pIsPlaying(p)) // XXX
+ return;
+ if (parray[p].side != garray[g].game_state.onMove) {
+ pprintf(p, "It is not your move.\n");
+ return;
+ }
+ if (garray[g].clockStopped) {
+ pprintf(p, "Game clock is paused, use \"unpause\" "
+ "to resume.\n");
+ return;
+ }
+ }
+
+ if ((len = strlen(command)) > 1) {
+ if (command[len - 2] == '=') {
+ switch (tolower(command[strlen(command) - 1])) {
+ case 'n':
+ parray[p].promote = KNIGHT;
+ break;
+ case 'b':
+ parray[p].promote = BISHOP;
+ break;
+ case 'r':
+ parray[p].promote = ROOK;
+ break;
+ case 'q':
+ parray[p].promote = QUEEN;
+ break;
+ default:
+ pprintf(p, "Don't understand that move.\n");
+ return;
+ break;
+ }
+ }
+ }
+
+ switch (parse_move(command, &garray[g].game_state, &move,
+ parray[p].promote)) {
+ case MOVE_ILLEGAL:
+ pprintf(p, "Illegal move.\n");
+ return;
+ break;
+ case MOVE_AMBIGUOUS:
+ pprintf(p, "Ambiguous move.\n");
+ return;
+ break;
+ default:
+ break;
+ }
+
+ if (garray[g].status == GAME_EXAMINE) {
+ garray[g].numHalfMoves++;
+
+ if (garray[g].numHalfMoves > garray[g].examMoveListSize) {
+ garray[g].examMoveListSize += 20; // Allocate 20
+ // moves at a
+ // time
+ if (!garray[g].examMoveList) {
+ garray[g].examMoveList =
+ reallocarray(NULL,
+ sizeof(move_t),
+ garray[g].examMoveListSize);
+ if (garray[g].examMoveList == NULL)
+ err(1, "%s: reallocarray", __func__);
+ else
+ malloc_count++;
+ } else {
+ garray[g].examMoveList =
+ reallocarray(garray[g].examMoveList,
+ sizeof(move_t),
+ garray[g].examMoveListSize);
+ if (garray[g].examMoveList == NULL)
+ err(1, "%s: reallocarray", __func__);
+ }
+ }
+
+ now = tenth_secs();
+
+ result = execute_move(&garray[g].game_state, &move, 1);
+ move.atTime = now; // XXX
+ move.tookTime = 0;
+ MakeFENpos(g, (char *)move.FENpos, ARRAY_SIZE(move.FENpos));
+ garray[g].examMoveList[garray[g].numHalfMoves - 1] = move;
+
+ /*
+ * roll back time
+ */
+
+ if (garray[g].game_state.onMove == WHITE) {
+ garray[g].wTime +=
+ (garray[g].lastDecTime - garray[g].lastMoveTime);
+ } else {
+ garray[g].bTime +=
+ (garray[g].lastDecTime - garray[g].lastMoveTime);
+ }
+
+ // XXX: 'now' was assigned here
+ // <--
+
+ if (garray[g].numHalfMoves == 0)
+ garray[g].timeOfStart = now;
+
+ garray[g].lastMoveTime = now;
+ garray[g].lastDecTime = now;
+ } else { // real game
+ i = parray[p].opponent;
+
+ if (parray[i].simul_info.numBoards &&
+ parray[i].simul_info.boards[parray[i].simul_info.onBoard] !=
+ g) {
+ pprintf(p, "It isn't your turn: wait until the simul "
+ "giver is at your board.\n");
+ return;
+ }
+
+#ifdef TIMESEAL
+ if (con[parray[p].socket].timeseal) { // Does he use timeseal?
+ if (parray[p].side == WHITE) {
+ int diff;
+
+ garray[g].wLastRealTime = garray[g].wRealTime;
+ garray[g].wTimeWhenMoved =
+ con[parray[p].socket].time;
+
+ diff = (garray[g].wTimeWhenMoved -
+ garray[g].wTimeWhenReceivedMove);
+
+ if (diff < 0 ||
+ garray[g].wTimeWhenReceivedMove == 0) {
+ /*
+ * Might seem weird - but
+ * could be caused by a person
+ * moving BEFORE he receives
+ * the board pos (this is
+ * possible due to lag) but
+ * it's safe to say he moved
+ * in 0 secs.
+ */
+ garray[g].wTimeWhenReceivedMove =
+ garray[g].wTimeWhenMoved;
+ } else {
+ garray[g].wRealTime -=
+ (garray[g].wTimeWhenMoved -
+ garray[g].wTimeWhenReceivedMove);
+ }
+ } else if (parray[p].side == BLACK) {
+ int diff;
+
+ garray[g].bLastRealTime = garray[g].bRealTime;
+ garray[g].bTimeWhenMoved =
+ con[parray[p].socket].time;
+
+ diff = (garray[g].bTimeWhenMoved -
+ garray[g].bTimeWhenReceivedMove);
+
+ if (diff < 0 ||
+ garray[g].bTimeWhenReceivedMove == 0) {
+ /*
+ * Might seem weird - but
+ * could be caused by a person
+ * moving BEFORE he receives
+ * the board pos (this is
+ * possible due to lag) but
+ * it's safe to say he moved
+ * in 0 secs.
+ */
+ garray[g].bTimeWhenReceivedMove =
+ garray[g].bTimeWhenMoved;
+ } else {
+ garray[g].bRealTime -=
+ (garray[g].bTimeWhenMoved -
+ garray[g].bTimeWhenReceivedMove);
+ }
+ }
+ }
+
+ /*
+ * We need to reset the opp's time for receiving the
+ * board since the timeseal decoder only alters the
+ * time if it's 0. Otherwise the time would be changed
+ * if the player did a refresh which would screw up
+ * the timings.
+ */
+ if (parray[p].side == WHITE)
+ garray[g].bTimeWhenReceivedMove = 0;
+ else
+ garray[g].wTimeWhenReceivedMove = 0;
+#endif
+
+ game_update_time(g);
+
+ /*
+ * XXX: Maybe add autoflag here in the future?
+ */
+
+#ifdef TIMESEAL
+ if (con[parray[p].socket].timeseal) { // Does he use timeseal?
+ if (parray[p].side == WHITE) {
+ garray[g].wRealTime +=
+ (garray[g].wIncrement * 100);
+ garray[g].wTime = (garray[g].wRealTime / 100);
+ } else if (parray[p].side == BLACK) {
+ garray[g].bRealTime +=
+ (garray[g].bIncrement * 100);
+ garray[g].bTime = (garray[g].bRealTime / 100);
+ }
+ } else {
+ if (garray[g].game_state.onMove == BLACK)
+ garray[g].bTime += garray[g].bIncrement;
+ if (garray[g].game_state.onMove == WHITE)
+ garray[g].wTime += garray[g].wIncrement;
+ }
+#else
+ if (garray[g].game_state.onMove == BLACK)
+ garray[g].bTime += garray[g].bIncrement;
+ if (garray[g].game_state.onMove == WHITE)
+ garray[g].wTime += garray[g].wIncrement;
+#endif
+
+ /*
+ * Do the move.
+ */
+
+ garray[g].numHalfMoves++;
+
+ if (garray[g].numHalfMoves > garray[g].moveListSize) {
+ garray[g].moveListSize += 20; // Allocate 20 moves at
+ // a time
+
+ if (!garray[g].moveList) {
+ garray[g].moveList =
+ reallocarray(NULL,
+ sizeof(move_t),
+ garray[g].moveListSize);
+ if (garray[g].moveList == NULL)
+ err(1, "%s: reallocarray", __func__);
+ else
+ malloc_count++;
+ } else {
+ garray[g].moveList =
+ reallocarray(garray[g].moveList,
+ sizeof(move_t),
+ garray[g].moveListSize);
+ if (garray[g].moveList == NULL)
+ err(1, "%s: reallocarray", __func__);
+ }
+ }
+
+ result = execute_move(&garray[g].game_state, &move, 1);
+
+ if (result == MOVE_OK &&
+ garray[g].link >= 0 &&
+ move.pieceCaptured != NOPIECE) {
+ /*
+ * Transfer captured piece to partner.
+ * Check if piece reverts to a pawn.
+ */
+ if (was_promoted(&garray[g], move.toFile, move.toRank)) {
+ update_holding(garray[g].link,
+ colorval(move.pieceCaptured) | PAWN);
+ } else {
+ update_holding(garray[g].link,
+ move.pieceCaptured);
+ }
+ }
+
+ now = tenth_secs();
+ move.atTime = now;
+
+ if (garray[g].numHalfMoves > 1)
+ move.tookTime = (move.atTime - garray[g].lastMoveTime);
+ else
+ move.tookTime = (move.atTime - garray[g].startTime);
+
+ garray[g].lastMoveTime = now;
+ garray[g].lastDecTime = now;
+
+#ifdef TIMESEAL
+ if (con[parray[p].socket].timeseal) { // Does he use timeseal?
+ if (parray[p].side == WHITE) {
+ move.tookTime =
+ ((garray[parray[p].game].wTimeWhenMoved -
+ garray[parray[p].game].wTimeWhenReceivedMove) /
+ 100);
+ } else {
+ move.tookTime =
+ ((garray[parray[p].game].bTimeWhenMoved -
+ garray[parray[p].game].bTimeWhenReceivedMove) /
+ 100);
+ }
+ }
+#endif
+
+ MakeFENpos(g, (char *)move.FENpos, ARRAY_SIZE(move.FENpos));
+ garray[g].moveList[garray[g].numHalfMoves - 1] = move;
+ }
+
+ send_boards(g);
+
+ if (result == MOVE_ILLEGAL)
+ pprintf(p, "Internal error, illegal move accepted!\n");
+ if (result == MOVE_OK && garray[g].status == GAME_EXAMINE) {
+ for (int p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ if (player_is_observe(p1, g) || parray[p1].game == g) {
+ pprintf_prompt(p1, "%s moves: %s\n",
+ parray[p].name, move.algString);
+ }
+ }
+ }
+
+ if (result == MOVE_CHECKMATE) {
+ if (garray[g].status == GAME_EXAMINE) {
+ for (int p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ if (player_is_observe(p1, g) ||
+ parray[p1].game == g) {
+ pprintf(p1, "%s has been checkmated.\n",
+ (CToggle(garray[g].game_state.onMove)
+ == BLACK ? "White" : "Black"));
+ }
+ }
+ } else {
+ game_ended(g, CToggle(garray[g].game_state.onMove),
+ END_CHECKMATE);
+ }
+ }
+
+ if (result == MOVE_STALEMATE) {
+ if (garray[g].status == GAME_EXAMINE) {
+ for (int p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ if (player_is_observe(p1, g) ||
+ parray[p1].game == g)
+ pprintf(p1, "Stalemate.\n");
+ }
+ } else {
+ game_ended(g, CToggle(garray[g].game_state.onMove),
+ END_STALEMATE);
+ }
+ }
+
+ if (result == MOVE_NOMATERIAL) {
+ if (garray[g].status == GAME_EXAMINE) {
+ for (int p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ if (player_is_observe(p1, g) ||
+ parray[p1].game == g)
+ pprintf(p1, "No mating material.\n");
+ }
+ } else {
+ game_ended(g, CToggle(garray[g].game_state.onMove),
+ END_NOMATERIAL);
+ }
+ }
+}
+
+PUBLIC int
+com_resign(int p, param_list param)
+{
+ int g, o, oconnected;
+
+ if (param[0].type == TYPE_NULL) {
+ g = parray[p].game;
+
+ if (!pIsPlaying(p))
+ return COM_OK;
+ else {
+ player_decline_offers(p, -1, -1);
+ game_ended(g, (garray[g].white == p ? BLACK : WHITE),
+ END_RESIGN);
+ }
+ } else if (FindPlayer(p, param[0].val.word, &o, &oconnected)) {
+ g = game_new();
+
+ if (game_read(g, p, o) < 0) {
+ if (game_read(g, o, p) < 0) {
+ pprintf(p, "You have no stored game with %s\n",
+ parray[o].name);
+ if (!oconnected)
+ player_remove(o);
+ return COM_OK;
+ } else {
+ garray[g].white = o;
+ garray[g].black = p;
+ }
+ } else {
+ garray[g].white = p;
+ garray[g].black = o;
+ }
+
+ pprintf(p, "You resign your stored game with %s\n",
+ parray[o].name);
+
+ game_delete(garray[g].white, garray[g].black);
+ game_ended(g, (garray[g].white == p ? BLACK : WHITE),
+ END_RESIGN);
+
+ pcommand(p, "message %s I have resigned our stored game "
+ "\"%s vs. %s.\"",
+ parray[o].name,
+ parray[garray[g].white].name,
+ parray[garray[g].black].name);
+
+ if (!oconnected)
+ player_remove(o);
+ }
+
+ return COM_OK;
+}
+
+PRIVATE int
+Check50MoveRule(int p, int g)
+{
+ int num_reversible = garray[g].numHalfMoves;
+
+ if (garray[g].game_state.lastIrreversable >= 0)
+ num_reversible -= garray[g].game_state.lastIrreversable;
+
+ if (num_reversible > 99) {
+ game_ended(g, (garray[g].white == p ? BLACK : WHITE),
+ END_50MOVERULE);
+ return 1;
+ }
+
+ return 0;
+}
+
+PRIVATE char *
+GetFENpos(int g, int half_move)
+{
+ if (half_move < 0)
+ return ((char *)garray[g].FENstartPos);
+ return ((char *)garray[g].moveList[half_move].FENpos);
+}
+
+PRIVATE int
+CheckRepetition(int p, int g)
+{
+ char *pos1 = GetFENpos(g, garray[g].numHalfMoves - 1);
+ char *pos2 = GetFENpos(g, garray[g].numHalfMoves);
+ char *pos;
+ int flag1 = 1, flag2 = 1;
+ int move_num;
+ size_t len[3];
+
+ if (garray[g].numHalfMoves < 8) // Can't have three repeats any quicker.
+ return 0;
+
+ len[0] = strlen(pos1);
+ len[1] = strlen(pos2);
+
+ for (move_num = garray[g].game_state.lastIrreversable;
+ move_num < garray[g].numHalfMoves - 1;
+ move_num++) {
+ pos = GetFENpos(g, move_num);
+ len[2] = strlen(pos);
+
+ if (len[0] == len[2] && !strcmp(pos1, pos))
+ flag1++;
+ if (len[1] == len[2] && !strcmp(pos2, pos))
+ flag2++;
+ }
+
+ if (flag1 >= 3 || flag2 >= 3) {
+ if (player_find_pendfrom(p, parray[p].opponent, PEND_DRAW)
+ >= 0) {
+ player_remove_request(parray[p].opponent, p, PEND_DRAW);
+ player_decline_offers(p, -1, -1);
+ }
+
+ game_ended(g, (garray[g].white == p ? BLACK : WHITE),
+ END_REPETITION);
+ return 1;
+ } else
+ return 0;
+}
+
+PUBLIC int
+com_draw(int p, param_list param)
+{
+ int p1, g = parray[p].game;
+
+ ASSERT(param[0].type == TYPE_NULL);
+
+ if (!pIsPlaying(p))
+ return COM_OK;
+ if (Check50MoveRule(p, g) || CheckRepetition(p, g))
+ return COM_OK;
+
+ p1 = parray[p].opponent;
+
+ if (parray[p1].simul_info.numBoards &&
+ parray[p1].simul_info.boards[parray[p1].simul_info.onBoard] != g) {
+ pprintf(p, "You can only make requests when the simul player "
+ "is at your board.\n");
+ return COM_OK;
+ }
+
+ if (player_find_pendfrom(p, parray[p].opponent, PEND_DRAW) >= 0) {
+ player_remove_request(parray[p].opponent, p, PEND_DRAW);
+ player_decline_offers(p, -1, -1);
+ game_ended(g, (garray[g].white == p ? BLACK : WHITE),
+ END_AGREEDDRAW);
+ } else {
+ pprintf(parray[p].opponent, "\n");
+ pprintf_highlight(parray[p].opponent, "%s", parray[p].name);
+ pprintf_prompt(parray[p].opponent, " offers you a draw.\n");
+ pprintf(p, "Draw request sent.\n");
+ player_add_request(p, parray[p].opponent, PEND_DRAW, 0);
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_pause(int p, param_list param)
+{
+ int g;
+ int now;
+
+ ASSERT(param[0].type == TYPE_NULL);
+
+ if (!pIsPlaying(p))
+ return COM_OK;
+
+ g = parray[p].game;
+
+ if (garray[g].wTime == 0) {
+ pprintf(p, "You can't pause untimed games.\n");
+ return COM_OK;
+ }
+
+ if (garray[g].clockStopped) {
+ pprintf(p, "Game is already paused, "
+ "use \"unpause\" to resume.\n");
+ return COM_OK;
+ }
+
+ if (player_find_pendfrom(p, parray[p].opponent, PEND_PAUSE) >= 0) {
+ player_remove_request(parray[p].opponent, p, PEND_PAUSE);
+ garray[g].clockStopped = 1;
+
+ // Roll back the time
+ if (garray[g].game_state.onMove == WHITE) {
+ garray[g].wTime += (garray[g].lastDecTime -
+ garray[g].lastMoveTime);
+ } else {
+ garray[g].bTime += (garray[g].lastDecTime -
+ garray[g].lastMoveTime);
+ }
+
+ now = tenth_secs();
+
+ if (garray[g].numHalfMoves == 0)
+ garray[g].timeOfStart = now;
+
+ garray[g].lastMoveTime = now;
+ garray[g].lastDecTime = now;
+
+ send_boards(g);
+
+ pprintf_prompt(parray[p].opponent, "\n%s accepted pause. "
+ "Game clock paused.\n", parray[p].name);
+ pprintf(p, "Game clock paused.\n");
+ } else {
+ pprintf(parray[p].opponent, "\n");
+ pprintf_highlight(parray[p].opponent, "%s", parray[p].name);
+ pprintf_prompt(parray[p].opponent, " requests to pause the "
+ "game.\n");
+ pprintf(p, "Pause request sent.\n");
+ player_add_request(p, parray[p].opponent, PEND_PAUSE, 0);
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_unpause(int p, param_list param)
+{
+ int g;
+ int now;
+
+ ASSERT(param[0].type == TYPE_NULL);
+
+ if (!pIsPlaying(p))
+ return COM_OK;
+
+ g = parray[p].game;
+
+ if (!garray[g].clockStopped) {
+ pprintf(p, "Game is not paused.\n");
+ return COM_OK;
+ }
+
+ garray[g].clockStopped = 0;
+ now = tenth_secs();
+
+ if (garray[g].numHalfMoves == 0)
+ garray[g].timeOfStart = now;
+
+ garray[g].lastMoveTime = now;
+ garray[g].lastDecTime = now;
+
+ send_boards(g);
+
+ pprintf(p, "Game clock resumed.\n");
+ pprintf_prompt(parray[p].opponent, "\nGame clock resumed.\n");
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_abort(int p, param_list param)
+{
+ int courtesyOK = 1;
+ int p1, g, myColor, yourColor, myGTime, yourGTime;
+
+ ASSERT(param[0].type == TYPE_NULL);
+
+ g = parray[p].game;
+
+ if (!pIsPlaying(p))
+ return COM_OK;
+
+ p1 = parray[p].opponent;
+
+ if (p == garray[g].white) {
+ myColor = WHITE;
+ yourColor = BLACK;
+ myGTime = garray[g].wTime;
+ yourGTime = garray[g].bTime;
+ } else {
+ myColor = BLACK;
+ yourColor = WHITE;
+ myGTime = garray[g].bTime;
+ yourGTime = garray[g].wTime;
+ }
+
+ if (parray[p1].simul_info.numBoards &&
+ parray[p1].simul_info.boards[parray[p1].simul_info.onBoard] != g) {
+ pprintf(p, "You can only make requests when the simul player "
+ "is at your board.\n");
+ return COM_OK;
+ }
+
+ if (player_find_pendfrom(p, p1, PEND_ABORT) >= 0) {
+ player_remove_request(p1, p, PEND_ABORT);
+ player_decline_offers(p, -1, -1);
+ game_ended(g, yourColor, END_ABORT);
+ } else {
+ game_update_time(g);
+
+#ifdef TIMESEAL
+ if (con[parray[p].socket].timeseal &&
+ garray[g].game_state.onMove == myColor &&
+ garray[g].flag_pending == FLAG_ABORT) {
+ /*
+ * It's my move, opponent has asked for abort;
+ * I lagged out, my timeseal prevented
+ * courtesyabort, and I sent an abort request
+ * before acknowledging (and processing) my
+ * opponent's courtesyabort. OK, let's abort
+ * already :-).
+ */
+
+ player_decline_offers(p, -1, -1);
+ game_ended(g, yourColor, END_ABORT);
+ }
+
+ if (con[parray[p1].socket].timeseal) { // Opp uses timeseal?
+ int yourRealTime = (myColor == WHITE ?
+ garray[g].bRealTime : garray[g].wRealTime);
+
+ if (myGTime > 0 && yourGTime <= 0 && yourRealTime > 0) {
+ /*
+ * Override courtesyabort; opponent
+ * still has time. Check for lag.
+ */
+ courtesyOK = 0;
+
+ if (garray[g].game_state.onMove != myColor &&
+ garray[g].flag_pending != FLAG_CHECKING) {
+ // Opponent may be lagging; let's ask.
+ garray[g].flag_pending = FLAG_ABORT;
+ garray[g].flag_check_time = time(0);
+
+ pprintf(p, "Opponent has timeseal; "
+ "trying to courtesyabort.\n");
+ pprintf(p1, "\n[G]\n");
+
+ return COM_OK;
+ }
+ }
+ }
+#endif
+
+ if (myGTime > 0 && yourGTime <= 0 && courtesyOK) {
+ /*
+ * Player wants to abort + opponent is out of
+ * time = courtesyabort.
+ */
+
+ pprintf(p, "Since you have time, and your opponent "
+ "has none, the game has been aborted.");
+ pprintf(p1, "Your opponent has aborted the game "
+ "rather than calling your flag.");
+ player_decline_offers(p, -1, -1);
+ game_ended(g, myColor, END_COURTESY);
+ } else {
+ pprintf(p1, "\n");
+ pprintf_highlight(p1, "%s", parray[p].name);
+ pprintf(p1, " would like to abort the game; ");
+ pprintf_prompt(p1, "type \"abort\" to accept.\n");
+ pprintf(p, "Abort request sent.\n");
+ player_add_request(p, p1, PEND_ABORT, 0);
+ }
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_courtesyabort(int p, param_list param)
+{
+ pprintf(p, "Courtesyabort is obsolete; use \"abort\" instead.\n");
+ return COM_OK;
+}
+
+PUBLIC int
+com_courtesyadjourn(int p, param_list param)
+{
+ pprintf(p, "Use \"adjourn\" to courtesyadjourn a game.\n");
+ return COM_OK;
+}
+
+PRIVATE int
+player_has_mating_material(game_state_t *gs, int color)
+{
+ int i, j;
+ int minor_pieces = 0;
+ int piece;
+
+ for (i = 0; i < 8; i++) {
+ for (j = 0; j < 8; j++) {
+ piece = gs->board[i][j];
+
+ switch (piecetype(piece)) {
+ case BISHOP:
+ case KNIGHT:
+ if (iscolor(piece, color))
+ minor_pieces++;
+ break;
+ case KING:
+ case NOPIECE:
+ break;
+ default:
+ if (iscolor(piece, color))
+ return 1;
+ }
+ }
+ }
+
+ return (minor_pieces > 1 ? 1 : 0);
+}
+
+PUBLIC int
+com_flag(int p, param_list param)
+{
+ int g;
+ int myColor;
+
+ ASSERT(param[0].type == TYPE_NULL);
+
+ if (!pIsPlaying(p))
+ return COM_OK;
+
+ g = parray[p].game;
+ myColor = (p == garray[g].white ? WHITE : BLACK);
+
+ if (garray[g].type == TYPE_UNTIMED) {
+ pprintf(p, "You can't flag an untimed game.\n");
+ return COM_OK;
+ }
+
+ if (garray[g].numHalfMoves < 2) {
+ pprintf(p, "You cannot flag before both players have moved.\n"
+ "Use abort instead.\n");
+ return COM_OK;
+ }
+
+ game_update_time(g);
+
+#ifdef TIMESEAL
+ {
+ int myTime, yourTime, serverTime;
+ int opp = parray[p].opponent;
+
+ if (con[parray[p].socket].timeseal) { // Does the caller use
+ // timeseal?
+ myTime = (myColor == WHITE ? garray[g].wRealTime :
+ garray[g].bRealTime);
+ } else {
+ myTime = (myColor == WHITE ? garray[g].wTime :
+ garray[g].bTime);
+ }
+
+ serverTime = (myColor == WHITE ? garray[g].bTime :
+ garray[g].wTime);
+
+ if (con[parray[opp].socket].timeseal) { // Opp uses timeseal?
+ yourTime = (myColor == WHITE ? garray[g].bRealTime :
+ garray[g].wRealTime);
+ } else {
+ yourTime = serverTime;
+ }
+
+ // The clocks to compare are now in 'myTime' and 'yourTime'.
+ if (myTime <= 0 && yourTime <= 0) {
+ player_decline_offers(p, -1, -1);
+ game_ended(g, myColor, END_BOTHFLAG);
+ return COM_OK;
+ }
+
+ if (yourTime > 0) {
+ /*
+ * Opponent still has time, but if that's only
+ * because s/he may be lagging, we should ask
+ * for an acknowledgement and then try to call
+ * the flag.
+ */
+
+ if (serverTime <= 0 &&
+ garray[g].game_state.onMove != myColor &&
+ garray[g].flag_pending != FLAG_CHECKING) {
+ garray[g].flag_pending = FLAG_CALLED;
+ garray[g].flag_check_time = time(0);
+
+ pprintf(p, "Opponent has timeseal; "
+ "checking if (s)he's lagging.\n");
+ pprintf(opp, "\n[G]\n");
+
+ return COM_OK;
+ }
+
+ /*
+ * If we're here, it means:
+ * 1) The server agrees opponent has time, whether
+ * lagging or not.
+ * 2) Opponent has timeseal (if yourTime != serverTime),
+ * had time left after the last move (yourTime > 0),
+ * and it's still your move.
+ * 3) We're currently checking a flag call after having
+ * receiving acknowledgement from the other timeseal
+ * (and would have reset 'yourTime' if the flag were
+ * down).
+ */
+ pprintf(p, "Your opponent is not out of time!\n");
+ return COM_OK;
+ }
+ }
+#else // !defined(TIMESEAL)
+ if (garray[g].wTime <= 0 && garray[g].bTime <= 0) {
+ player_decline_offers(p, -1, -1);
+ game_ended(g, myColor, END_BOTHFLAG);
+ return COM_OK;
+ }
+
+ if (myColor == WHITE) {
+ if (garray[g].bTime > 0) {
+ pprintf(p, "Your opponent is not out of time!\n");
+ return COM_OK;
+ }
+ } else {
+ if (garray[g].wTime > 0) {
+ pprintf(p, "Your opponent is not out of time!\n");
+ return COM_OK;
+ }
+ }
+#endif
+
+ player_decline_offers(p, -1, -1);
+
+ if (player_has_mating_material(&garray[g].game_state, myColor))
+ game_ended(g, myColor, END_FLAG);
+ else
+ game_ended(g, myColor, END_FLAGNOMATERIAL);
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_adjourn(int p, param_list param)
+{
+ int p1, g, myColor, yourColor;
+
+ ASSERT(param[0].type == TYPE_NULL);
+
+ if (!pIsPlaying(p))
+ return COM_OK;
+
+ p1 = parray[p].opponent;
+ g = parray[p].game;
+
+ if (!(parray[p].registered && parray[p1].registered)) {
+ pprintf(p, "Both players must be registered to adjorn a game. "
+ "Use \"abort\".\n");
+ return COM_OK;
+ }
+
+ if (garray[g].link >= 0) {
+ pprintf(p, "Bughouse games cannot be adjourned.\n");
+ return COM_OK;
+ }
+
+ myColor = (p == garray[g].white ? WHITE : BLACK);
+ yourColor = (myColor == WHITE ? BLACK : WHITE);
+
+ if (player_find_pendfrom(p, p1, PEND_ADJOURN) >= 0) {
+ player_remove_request(p1, p, PEND_ADJOURN);
+ player_decline_offers(p, -1, -1);
+ game_ended(parray[p].game, yourColor, END_ADJOURN);
+ } else {
+ game_update_time(g);
+
+ if ((myColor == WHITE &&
+ garray[g].wTime > 0 &&
+ garray[g].bTime <= 0) ||
+ (myColor == BLACK &&
+ garray[g].bTime > 0 &&
+ garray[g].wTime <= 0)) {
+ /*
+ * Player wants to adjourn + opponent is out
+ * of time = courtesyadjourn.
+ */
+
+ pprintf(p, "Since you have time, and your opponent "
+ "has none, the game has been adjourned.");
+ pprintf(p1, "Your opponent has adjourned the game "
+ "rather than calling your flag.");
+ player_decline_offers(p, -1, -1);
+ game_ended(g, myColor, END_COURTESYADJOURN);
+ } else {
+ pprintf(p1, "\n");
+ pprintf_highlight(p1, "%s", parray[p].name);
+ pprintf(p1, " would like to adjourn the game; ");
+ pprintf_prompt(p1, "type \"adjourn\" to accept.\n");
+ pprintf(p, "Adjourn request sent.\n");
+ player_add_request(p, p1, PEND_ADJOURN, 0);
+ }
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_takeback(int p, param_list param)
+{
+ int from;
+ int g, i;
+ int nHalfMoves = 1;
+ int p1;
+
+ if (!pIsPlaying(p))
+ return COM_OK;
+
+ p1 = parray[p].opponent;
+
+ if (parray[p1].simul_info.numBoards &&
+ parray[p1].simul_info.boards[parray[p1].simul_info.onBoard] !=
+ parray[p].game) {
+ pprintf(p, "You can only make requests when the simul player "
+ "is at your board.\n");
+ return COM_OK;
+ }
+
+ g = parray[p].game;
+
+ if (garray[g].link >= 0) {
+ pprintf(p, "Takeback not implemented for bughouse games yet."
+ "\n");
+ return COM_OK;
+ }
+
+ if (param[0].type == TYPE_INT)
+ nHalfMoves = param[0].val.integer;
+
+ if ((from = player_find_pendfrom(p, parray[p].opponent, PEND_TAKEBACK))
+ >= 0) {
+ player_remove_request(parray[p].opponent, p, PEND_TAKEBACK);
+
+ if (parray[p].p_from_list[from].param1 == nHalfMoves) {
+ // Doing the takeback
+ player_decline_offers(p, -1, -PEND_SIMUL);
+
+ for (i = 0; i < nHalfMoves; i++) {
+ if (backup_move(g, REL_GAME) != MOVE_OK) {
+ pprintf(garray[g].white, "Can only "
+ "backup %d moves\n", i);
+ pprintf(garray[g].black, "Can only "
+ "backup %d moves\n", i);
+ break;
+ }
+ }
+
+#ifdef TIMESEAL
+ garray[g].wTimeWhenReceivedMove = 0;
+ garray[g].bTimeWhenReceivedMove = 0;
+#endif
+
+ send_boards(g);
+ } else {
+ if (garray[g].numHalfMoves < nHalfMoves) {
+ pprintf(p, "There are only %d half moves in "
+ "your game.\n", garray[g].numHalfMoves);
+ pprintf_prompt(parray[p].opponent, "\n%s has "
+ "declined the takeback request.\n",
+ parray[p].name);
+ return COM_OK;
+ }
+
+ pprintf(p, "You disagree on the number of half-moves "
+ "to takeback.\n");
+ pprintf(p, "Alternate takeback request sent.\n");
+ pprintf_prompt(parray[p].opponent, "\n%s proposes a "
+ "different number (%d) of half-move(s).\n",
+ parray[p].name,
+ nHalfMoves);
+ player_add_request(p, parray[p].opponent, PEND_TAKEBACK,
+ nHalfMoves);
+ }
+ } else {
+ if (garray[g].numHalfMoves < nHalfMoves) {
+ pprintf(p, "There are only %d half moves in your game."
+ "\n", garray[g].numHalfMoves);
+ return COM_OK;
+ }
+
+ pprintf(parray[p].opponent, "\n");
+ pprintf_highlight(parray[p].opponent, "%s", parray[p].name);
+ pprintf_prompt(parray[p].opponent, " would like to take back "
+ "%d half move(s).\n", nHalfMoves);
+ pprintf(p, "Takeback request sent.\n");
+ player_add_request(p, parray[p].opponent, PEND_TAKEBACK,
+ nHalfMoves);
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_switch(int p, param_list param)
+{
+ char *strTmp;
+ int g = parray[p].game;
+ int p1;
+ int tmp, now;
+
+ if (!pIsPlaying(p))
+ return COM_OK;
+
+ p1 = parray[p].opponent;
+
+ if (parray[p1].simul_info.numBoards &&
+ parray[p1].simul_info.boards[parray[p1].simul_info.onBoard] != g) {
+ pprintf(p, "You can only make requests when the simul player "
+ "is at your board.\n");
+ return COM_OK;
+ }
+
+ if (garray[g].link >= 0) {
+ pprintf(p, "Switch not implemented for bughouse games.\n");
+ return COM_OK;
+ }
+
+ if (player_find_pendfrom(p, parray[p].opponent, PEND_SWITCH) >= 0) {
+ player_remove_request(parray[p].opponent, p, PEND_SWITCH);
+ player_decline_offers(p, -1, -PEND_SIMUL);
+
+ tmp = garray[g].white;
+ garray[g].white = garray[g].black;
+ garray[g].black = tmp;
+ parray[p].side = (parray[p].side == WHITE ? BLACK : WHITE);
+
+ strTmp = xstrdup(garray[g].white_name);
+ mstrlcpy(garray[g].white_name, garray[g].black_name,
+ sizeof(garray[g].white_name));
+ mstrlcpy(garray[g].black_name, strTmp,
+ sizeof(garray[g].black_name));
+ strfree(strTmp);
+
+ parray[parray[p].opponent].side =
+ (parray[parray[p].opponent].side == WHITE ? BLACK : WHITE);
+
+ // Roll back the time
+ if (garray[g].game_state.onMove == WHITE) {
+ garray[g].wTime += (garray[g].lastDecTime -
+ garray[g].lastMoveTime);
+ } else {
+ garray[g].bTime += (garray[g].lastDecTime -
+ garray[g].lastMoveTime);
+ }
+
+ now = tenth_secs();
+
+ if (garray[g].numHalfMoves == 0)
+ garray[g].timeOfStart = now;
+
+ garray[g].lastMoveTime = now;
+ garray[g].lastDecTime = now;
+
+ send_boards(g);
+ return COM_OK;
+ }
+
+ if (garray[g].rated && garray[g].numHalfMoves > 0) {
+ pprintf(p, "You cannot switch sides once a rated game is "
+ "underway.\n");
+ return COM_OK;
+ }
+
+ pprintf(parray[p].opponent, "\n");
+ pprintf_highlight(parray[p].opponent, "%s", parray[p].name);
+ pprintf_prompt(parray[p].opponent, " would like to switch sides.\n"
+ "Type \"accept\" to switch sides, or \"decline\" to refuse.\n");
+ pprintf(p, "Switch request sent.\n");
+ player_add_request(p, parray[p].opponent, PEND_SWITCH, 0);
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_time(int p, param_list param)
+{
+ int p1, g;
+
+ if (param[0].type == TYPE_NULL) {
+ g = parray[p].game;
+
+ if (!pIsPlaying(p))
+ return COM_OK;
+ } else {
+ if ((g = GameNumFromParam(p, &p1, &param[0])) < 0)
+ return COM_OK;
+ }
+
+ if (g < 0 || g >= g_num || garray[g].status != GAME_ACTIVE) {
+ pprintf(p, "There is no such game.\n");
+ return COM_OK;
+ }
+
+ game_update_time(g);
+
+ pprintf(p, "White (%s) : %d mins, %d secs\n",
+ parray[garray[g].white].name,
+ garray[g].wTime / 600,
+ (garray[g].wTime - ((garray[g].wTime / 600) * 600)) / 10);
+ pprintf(p, "Black (%s) : %d mins, %d secs\n",
+ parray[garray[g].black].name,
+ garray[g].bTime / 600,
+ (garray[g].bTime - ((garray[g].bTime / 600) * 600)) / 10);
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_boards(int p, param_list param)
+{
+ DIR *dirp;
+ char *category = NULL;
+ char dname[MAX_FILENAME_SIZE] = { '\0' };
+#ifdef USE_DIRENT
+ struct dirent *dp;
+#else
+ struct direct *dp;
+#endif
+
+ if (param[0].type == TYPE_WORD)
+ category = param[0].val.word;
+
+ if (category) {
+ pprintf(p, "Boards Available For Category %s:\n", category);
+ msnprintf(dname, sizeof dname, "%s/%s", board_dir, category);
+ } else {
+ pprintf(p, "Categories Available:\n");
+ msnprintf(dname, sizeof dname, "%s", board_dir);
+ }
+
+ if ((dirp = opendir(dname)) == NULL) {
+ pprintf(p, "No such category %s, try \"boards\".\n", category);
+ return COM_OK;
+ }
+
+ // YUK! What a mess, how about printing an ordered directory? - DAV
+ for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
+ if (!strcmp(dp->d_name, "."))
+ continue;
+ if (!strcmp(dp->d_name, ".."))
+ continue;
+ pprintf(p, "%s\n", dp->d_name);
+ }
+
+ closedir(dirp);
+ return COM_OK;
+}
+
+PUBLIC int
+com_simmatch(int p, param_list param)
+{
+ char tmp[100] = { '\0' };
+ int num;
+ int p1, g, adjourned;
+
+ if (parray[p].game >= 0 && garray[parray[p].game].status ==
+ GAME_EXAMINE) {
+ pprintf(p, "You are still examining a game.\n");
+ return COM_OK;
+ }
+
+ p1 = player_find_part_login(param[0].val.word);
+
+ if (p1 < 0) {
+ pprintf(p, "No user named \"%s\" is logged in.\n",
+ param[0].val.word);
+ return COM_OK;
+ }
+
+ if (p == p1) {
+ pprintf(p, "You can't simmatch yourself!\n");
+ return COM_OK;
+ }
+
+ if (player_find_pendfrom(p, p1, PEND_SIMUL) >= 0) {
+ player_remove_request(p, p1, PEND_MATCH);
+ player_remove_request(p1, p, PEND_MATCH);
+ player_remove_request(p, p1, PEND_SIMUL);
+ player_remove_request(p1, p, PEND_SIMUL);
+ player_withdraw_offers(p, -1, PEND_SIMUL);
+ player_decline_offers(p1, -1, PEND_SIMUL);
+ player_withdraw_offers(p1, -1, PEND_SIMUL);
+ player_decline_offers(p, -1, PEND_MATCH);
+ player_withdraw_offers(p, -1, PEND_MATCH);
+ player_decline_offers(p1, -1, PEND_MATCH);
+ player_withdraw_offers(p1, -1, PEND_MATCH);
+ player_decline_offers(p, -1, PEND_PARTNER);
+ player_withdraw_offers(p, -1, PEND_PARTNER);
+ player_decline_offers(p1, -1, PEND_PARTNER);
+ player_withdraw_offers(p1, -1, PEND_PARTNER);
+
+ if (parray[p].simul_info.numBoards >= MAX_SIMUL) {
+ pprintf(p, "You are already playing the maximum of %d "
+ "boards.\n", MAX_SIMUL);
+ pprintf(p1, "Simul request removed, boards filled.\n");
+ return COM_OK;
+ }
+
+ // stop observing when match starts
+ unobserveAll(p);
+ unobserveAll(p1);
+
+ g = game_new();
+ adjourned = 0;
+
+ if (game_read(g, p, p1) >= 0)
+ adjourned = 1;
+
+ if (!adjourned) { // no adjourned game - so begin a new game.
+ game_remove(g);
+
+ if (create_new_match(p, p1, 0, 0, 0, 0, 0, "standard",
+ "standard", 1)) {
+ pprintf(p, "There was a problem creating the "
+ "new match.\n");
+ pprintf_prompt(p1, "There was a problem "
+ "creating the new match.\n");
+ return COM_OK;
+ }
+ } else { // resume adjourned game
+ game_delete(p, p1);
+
+ msnprintf(tmp, sizeof tmp, "{Game %d (%s vs. %s) "
+ "Continuing %s %s simul.}\n",
+ (g + 1),
+ parray[p].name,
+ parray[p1].name,
+ rstr[garray[g].rated],
+ bstr[garray[g].type]);
+
+ pprintf(p, "%s", tmp);
+ pprintf(p1, "%s", tmp);
+
+ garray[g].white = p;
+ garray[g].black = p1;
+ garray[g].status = GAME_ACTIVE;
+ garray[g].startTime = tenth_secs();
+ garray[g].lastMoveTime = garray[g].startTime;
+ garray[g].lastDecTime = garray[g].startTime;
+ parray[p].game = g;
+ parray[p].opponent = p1;
+ parray[p].side = WHITE;
+ parray[p1].game = g;
+ parray[p1].opponent = p;
+ parray[p1].side = BLACK;
+
+ send_boards(g);
+ }
+
+ num = parray[p].simul_info.numBoards;
+
+ parray[p].simul_info.results[num] = -1;
+ parray[p].simul_info.boards[num] = parray[p].game;
+ parray[p].simul_info.numBoards++;
+
+ if (parray[p].simul_info.numBoards > 1 &&
+ parray[p].simul_info.onBoard >= 0)
+ player_goto_board(p, parray[p].simul_info.onBoard);
+ else
+ parray[p].simul_info.onBoard = 0;
+ return COM_OK;
+ }
+
+ if (player_find_pendfrom(p, -1, PEND_SIMUL) >= 0) {
+ pprintf(p, "You cannot be the simul giver and request to join "
+ "another simul.\nThat would just be too confusing for me "
+ "and you.\n");
+ return COM_OK;
+ }
+
+ if (parray[p].simul_info.numBoards) {
+ pprintf(p, "You cannot be the simul giver and request to join "
+ "another simul.\nThat would just be too confusing for me "
+ "and you.\n");
+ return COM_OK;
+ }
+
+ if (parray[p].game >= 0) {
+ pprintf(p, "You are already playing a game.\n");
+ return COM_OK;
+ }
+
+ if (!parray[p1].sopen) {
+ pprintf_highlight(p, "%s", parray[p1].name);
+ pprintf(p, " is not open to receiving simul requests.\n");
+ return COM_OK;
+ }
+
+ if (parray[p1].simul_info.numBoards >= MAX_SIMUL) {
+ pprintf_highlight(p, "%s", parray[p1].name);
+ pprintf(p, " is already playing the maximum of %d boards.\n",
+ MAX_SIMUL);
+ return COM_OK;
+ }
+
+ // loon: checking for some crazy situations we can't allow :)
+ if (parray[p1].game >= 0 && parray[p1].simul_info.numBoards == 0) {
+ pprintf_highlight(p, "%s", parray[p1].name);
+
+ if (parray[garray[parray[p1].game].white].simul_info.numBoards) {
+ pprintf(p, " is playing in ");
+ pprintf_highlight(p, "%s",
+ parray[parray[p1].opponent].name);
+ pprintf(p, "'s simul, and can't accept.\n");
+ } else {
+ pprintf(p, " can't begin a simul while playing a "
+ "non-simul game.\n");
+ }
+
+ return COM_OK;
+ }
+
+ g = game_new();
+ adjourned = ((game_read(g, p, p1) < 0 && game_read(g, p1, p) < 0)
+ ? 0 : 1);
+
+ if (adjourned) {
+ if (!(garray[g].type == TYPE_UNTIMED))
+ adjourned = 0;
+ }
+
+ game_remove(g);
+
+ if (player_add_request(p, p1, PEND_SIMUL, 0)) {
+ pprintf(p, "Maximum number of pending actions reached. "
+ "Your request was not sent.\nTry again later.\n");
+ return COM_OK;
+ } else {
+
+ pprintf(p1, "\n");
+ pprintf_highlight(p1, "%s", parray[p].name);
+
+ if (adjourned) {
+ pprintf_prompt(p1, " requests to continue an "
+ "adjourned simul game.\n");
+ pprintf(p, "Request to resume simul sent. "
+ "Adjourned game found.\n");
+ } else {
+ pprintf_prompt(p1, " requests to join a simul match "
+ "with you.\n");
+ pprintf(p, "Simul match request sent.\n");
+ }
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_goboard(int p, param_list param)
+{
+ int on, g, p1;
+
+ if (!parray[p].simul_info.numBoards) {
+ pprintf(p, "You are not giving a simul.\n");
+ return COM_OK;
+ }
+
+ p1 = player_find_part_login(param[0].val.word);
+
+ if (p1 < 0) {
+ pprintf(p, "No user named \"%s\" is logged in.\n",
+ param[0].val.word);
+ return COM_OK;
+ }
+
+ if (p == p1) {
+ pprintf(p, "You can't goboard yourself!\n");
+ return COM_OK;
+ }
+
+ on = parray[p].simul_info.onBoard;
+
+ if ((g = parray[p].simul_info.boards[on]) < 0) {
+ pprintf(p, "Internal error! Unexpected negative value!\n");
+ return COM_OK;
+ }
+
+ if (p1 == garray[g].black) {
+ pprintf(p, "You are already at that board!\n");
+ return COM_OK;
+ }
+
+ if (parray[p].simul_info.numBoards > 1) {
+ player_decline_offers(p, -1, -PEND_SIMUL);
+
+ if (player_goto_simulgame_bynum(p, parray[p1].game) != -1) {
+ if (g >= 0) {
+ pprintf(garray[g].black, "\n");
+ pprintf_highlight(garray[g].black, "%s",
+ parray[p].name);
+ pprintf_prompt(garray[g].black, " has moved "
+ "away from your board.\n");
+ }
+ }
+ } else
+ pprintf(p, "You are only playing one board!\n");
+ return COM_OK;
+}
+
+PUBLIC int
+com_gonum(int p, param_list param)
+{
+ int on, g, gamenum;
+
+ if (!parray[p].simul_info.numBoards) {
+ pprintf(p, "You are not giving a simul.\n");
+ return COM_OK;
+ }
+
+ on = parray[p].simul_info.onBoard;
+ g = parray[p].simul_info.boards[on];
+ gamenum = param[0].val.integer - 1;
+
+ if (gamenum < 0)
+ gamenum = 0;
+
+ if (on == gamenum) {
+ pprintf(p, "You are already at that board!\n");
+ return COM_OK;
+ }
+
+ if (parray[p].simul_info.numBoards > 1) {
+ player_decline_offers(p, -1, -PEND_SIMUL);
+
+ if (player_goto_simulgame_bynum(p, gamenum) != -1) {
+ if (g >= 0) {
+ pprintf(garray[g].black, "\n");
+ pprintf_highlight(garray[g].black, "%s",
+ parray[p].name);
+ pprintf_prompt(garray[g].black, " has moved "
+ "away from your board.\n");
+ }
+ }
+ } else
+ pprintf(p, "You are only playing one board!\n");
+ return COM_OK;
+}
+
+PUBLIC int
+com_simnext(int p, param_list param)
+{
+ int on, g;
+
+ if (!parray[p].simul_info.numBoards) {
+ pprintf(p, "You are not giving a simul.\n");
+ return COM_OK;
+ }
+
+ if (parray[p].simul_info.numBoards > 1) {
+ player_decline_offers(p, -1, -PEND_SIMUL);
+ on = parray[p].simul_info.onBoard;
+ g = parray[p].simul_info.boards[on];
+
+ if (g >= 0) {
+ pprintf(garray[g].black, "\n");
+ pprintf_highlight(garray[g].black, "%s",
+ parray[p].name);
+ pprintf_prompt(garray[g].black, " is moving away from "
+ "your board.\n");
+ player_goto_next_board(p);
+ }
+ } else
+ pprintf(p, "You are only playing one board!\n");
+ return COM_OK;
+}
+
+PUBLIC int
+com_simprev(int p, param_list param)
+{
+ int on, g;
+
+ if (!parray[p].simul_info.numBoards) {
+ pprintf(p, "You are not giving a simul.\n");
+ return COM_OK;
+ }
+
+ if (parray[p].simul_info.numBoards > 1) {
+ player_decline_offers(p, -1, -PEND_SIMUL);
+ on = parray[p].simul_info.onBoard;
+ g = parray[p].simul_info.boards[on];
+
+ if (g >= 0) {
+ pprintf(garray[g].black, "\n");
+ pprintf_highlight(garray[g].black, "%s",
+ parray[p].name);
+ pprintf_prompt(garray[g].black, " is moving back to "
+ "the previous board.\n");
+ }
+
+ player_goto_prev_board(p);
+ } else
+ pprintf(p, "You are only playing one board!\n");
+ return COM_OK;
+}
+
+PUBLIC int
+com_simgames(int p, param_list param)
+{
+ int p1 = p;
+
+ if (param[0].type == TYPE_WORD) {
+ if ((p1 = player_find_part_login(param[0].val.word)) < 0) {
+ pprintf(p, "No player named %s is logged in.\n",
+ param[0].val.word);
+ return COM_OK;
+ }
+ }
+
+ if (p1 == p) {
+ pprintf(p, "You are playing %d simultaneous games.\n",
+ player_num_active_boards(p1));
+ } else {
+ pprintf(p, "%s is playing %d simultaneous games.\n",
+ parray[p1].name, player_num_active_boards(p1));
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_simpass(int p, param_list param)
+{
+ int g, p1, on;
+
+ if (!pIsPlaying(p))
+ return COM_OK;
+
+ g = parray[p].game;
+ p1 = garray[g].white;
+
+ if (!parray[p1].simul_info.numBoards) {
+ pprintf(p, "You are not participating in a simul.\n");
+ return COM_OK;
+ }
+
+ if (p == p1) {
+ pprintf(p, "You are the simul holder and cannot pass!\n");
+ return COM_OK;
+ }
+
+ if (player_num_active_boards(p1) == 1) {
+ pprintf(p, "This is the only game, so passing is futile.\n");
+ return COM_OK;
+ }
+
+ on = parray[p1].simul_info.onBoard;
+
+ if (parray[p1].simul_info.boards[on] != g) {
+ pprintf(p, "You cannot pass until the simul holder arrives!\n");
+ return COM_OK;
+ }
+
+ if (garray[g].passes >= MAX_SIMPASS) {
+ if (parray[p].bell)
+ pprintf(p, "\a");
+ pprintf(p, "You have reached your maximum of %d pass(es).\n",
+ MAX_SIMPASS);
+ pprintf(p, "Please move IMMEDIATELY!\n");
+ pprintf_highlight(p1, "%s", parray[p].name);
+ pprintf_prompt(p1, " tried to pass, but is out of passes.\n");
+ return COM_OK;
+ }
+
+ player_decline_offers(p, -1, -PEND_SIMUL);
+ garray[g].passes++;
+
+ pprintf(p, "You have passed and have %d pass(es) left.\n",
+ (MAX_SIMPASS - garray[g].passes));
+ pprintf_highlight(p1, "%s", parray[p].name);
+ pprintf_prompt(p1, " has decided to pass and has %d pass(es) left.\n",
+ (MAX_SIMPASS - garray[g].passes));
+ player_goto_next_board(p1);
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_simabort(int p, param_list param)
+{
+ if (!parray[p].simul_info.numBoards) {
+ pprintf(p, "You are not giving a simul.\n");
+ return COM_OK;
+ }
+
+ player_decline_offers(p, -1, -PEND_SIMUL);
+ game_ended(parray[p].simul_info.boards[parray[p].simul_info.onBoard],
+ WHITE, END_ABORT);
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_simallabort(int p, param_list param)
+{
+ if (!parray[p].simul_info.numBoards) {
+ pprintf(p, "You are not giving a simul.\n");
+ return COM_OK;
+ }
+
+ player_decline_offers(p, -1, -PEND_SIMUL);
+
+ for (int i = 0; i < parray[p].simul_info.numBoards; i++) {
+ if (parray[p].simul_info.boards[i] >= 0) {
+ game_ended(parray[p].simul_info.boards[i], WHITE,
+ END_ABORT);
+ }
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_simadjourn(int p, param_list param)
+{
+ if (!parray[p].simul_info.numBoards) {
+ pprintf(p, "You are not giving a simul.\n");
+ return COM_OK;
+ }
+
+ player_decline_offers(p, -1, -PEND_SIMUL);
+ game_ended(parray[p].simul_info.boards[parray[p].simul_info.onBoard],
+ WHITE, END_ADJOURN);
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_simalladjourn(int p, param_list param)
+{
+ if (!parray[p].simul_info.numBoards) {
+ pprintf(p, "You are not giving a simul.\n");
+ return COM_OK;
+ }
+
+ player_decline_offers(p, -1, -PEND_SIMUL);
+
+ for (int i = 0; i < parray[p].simul_info.numBoards; i++) {
+ if (parray[p].simul_info.boards[i] >= 0) {
+ game_ended(parray[p].simul_info.boards[i], WHITE,
+ END_ADJOURN);
+ }
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_moretime(int p, param_list param)
+{
+ int g, increment;
+
+ ASSERT(param[0].type == TYPE_INT);
+
+ if (parray[p].game >= 0 && garray[parray[p].game].status ==
+ GAME_EXAMINE) {
+ pprintf(p, "You cannot use moretime in an examined game.\n");
+ return COM_OK;
+ }
+
+ if ((increment = param[0].val.integer) <= 0) {
+ pprintf(p, "Moretime requires an integer value greater than "
+ "zero.\n");
+ return COM_OK;
+ }
+
+ if (!pIsPlaying(p))
+ return COM_OK;
+
+ if (increment > 600) {
+ pprintf(p, "Moretime has a maximum limit of 600 seconds.\n");
+ increment = 600;
+ }
+
+ g = parray[p].game;
+
+ if (garray[g].white == p) {
+ garray[g].bTime += increment * 10;
+#ifdef TIMESEAL
+ garray[g].bRealTime += increment * 10 * 100;
+#endif
+ pprintf(p, "%d seconds were added to your opponents clock\n",
+ increment);
+ pprintf_prompt(parray[p].opponent, "\nYour opponent has "
+ "added %d seconds to your clock.\n", increment);
+ }
+
+ if (garray[g].black == p) {
+ garray[g].wTime += increment * 10;;
+#ifdef TIMESEAL
+ garray[g].wRealTime += increment * 10 * 100;
+#endif
+ pprintf(p, "%d seconds were added to your opponents clock\n",
+ increment);
+ pprintf_prompt(parray[p].opponent, "\nYour opponent has "
+ "added %d seconds to your clock.\n", increment);
+ }
+
+ return COM_OK;
+}
diff --git a/FICS/gameproc.h b/FICS/gameproc.h
new file mode 100644
index 0000000..8f7aea9
--- /dev/null
+++ b/FICS/gameproc.h
@@ -0,0 +1,64 @@
+/* gameproc.h
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 24/03/30 Revised
+*/
+
+#ifndef _GAMEPROC_H
+#define _GAMEPROC_H
+
+#define MAX_SIMPASS 3
+#define MAX_JOURNAL 10
+
+#include "command.h" /* param_list */
+
+extern int com_abort(int, param_list);
+extern int com_adjourn(int, param_list);
+extern int com_boards(int, param_list);
+extern int com_courtesyabort(int, param_list);
+extern int com_courtesyadjourn(int, param_list);
+extern int com_draw(int, param_list);
+extern int com_flag(int, param_list);
+extern int com_goboard(int, param_list);
+extern int com_gonum(int, param_list);
+extern int com_moretime(int, param_list);
+extern int com_pause(int, param_list);
+extern int com_resign(int, param_list);
+extern int com_simabort(int, param_list);
+extern int com_simadjourn(int, param_list);
+extern int com_simallabort(int, param_list);
+extern int com_simalladjourn(int, param_list);
+extern int com_simgames(int, param_list);
+extern int com_simmatch(int, param_list);
+extern int com_simnext(int, param_list);
+extern int com_simpass(int, param_list);
+extern int com_simprev(int, param_list);
+extern int com_switch(int, param_list);
+extern int com_takeback(int, param_list);
+extern int com_time(int, param_list);
+extern int com_unpause(int, param_list);
+
+extern int pIsPlaying(int);
+extern void game_ended(int, int, int);
+extern void process_move(int, char *);
+
+#endif /* _GAMEPROC_H */
diff --git a/FICS/iset.cpp b/FICS/iset.cpp
new file mode 100644
index 0000000..afe084c
--- /dev/null
+++ b/FICS/iset.cpp
@@ -0,0 +1,23 @@
+// SPDX-FileCopyrightText: 2024 Markus Uhlin <maxxe@rpblc.net>
+// SPDX-License-Identifier: ISC
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include "iset.h"
+#include "utils.h"
+
+PUBLIC int
+com_iset(int p, param_list param)
+{
+ if (param[0].type != TYPE_WORD ||
+ param[1].type != TYPE_WORD) {
+ pprintf(p, "%s: bad parameters\n", __func__);
+ return COM_BADPARAMETERS;
+ }
+
+ pprintf(p, "%s: %s %s\n", __func__,
+ param[0].val.word,
+ param[1].val.word);
+ return COM_OK;
+}
diff --git a/FICS/iset.h b/FICS/iset.h
new file mode 100644
index 0000000..3fa3f6f
--- /dev/null
+++ b/FICS/iset.h
@@ -0,0 +1,11 @@
+#ifndef GUARD_ISET_H
+#define GUARD_ISET_H
+
+#include "command.h" /* param_list */
+#include "common.h"
+
+__FICS_BEGIN_DECLS
+int com_iset(int, param_list);
+__FICS_END_DECLS
+
+#endif
diff --git a/FICS/legal.c b/FICS/legal.c
new file mode 100644
index 0000000..87b5685
--- /dev/null
+++ b/FICS/legal.c
@@ -0,0 +1,39 @@
+/* legal.c
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+*/
+
+#include "legal.h"
+
+const char legalNotice[] =
+ "fics - An internet chess server.\n"
+ "Copyright (C) 1993 Richard V. Nash\n"
+ "\n"
+ "This program is free software; you can redistribute it and/or modify\n"
+ "it under the terms of the GNU General Public License as published by\n"
+ "the Free Software Foundation; either version 2 of the License, or\n"
+ "(at your option) any later version.\n"
+ "\n"
+ "This program is distributed in the hope that it will be useful,\n"
+ "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
+ "GNU General Public License for more details.\n";
diff --git a/FICS/legal.h b/FICS/legal.h
new file mode 100644
index 0000000..a87b8a8
--- /dev/null
+++ b/FICS/legal.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 1993 by Richard V. Nash
+ * All rights reserved world wide.
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+*/
+
+#ifndef _LEGAL_H
+#define _LEGAL_H
+
+extern const char legalNotice[];
+
+#endif /* _LEGAL_H */
diff --git a/FICS/legal2.c b/FICS/legal2.c
new file mode 100644
index 0000000..15c0c16
--- /dev/null
+++ b/FICS/legal2.c
@@ -0,0 +1,16 @@
+#include "legal2.h"
+
+const char legalNotice2[] =
+ "Copyright (c) 2023 - 2025 Markus Uhlin <maxxe@rpblc.net>\n"
+ "\n"
+ "Permission to use, copy, modify, and distribute this software for any\n"
+ "purpose with or without fee is hereby granted, provided that the above\n"
+ "copyright notice and this permission notice appear in all copies.\n"
+ "\n"
+ "THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n"
+ "WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n"
+ "MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n"
+ "ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n"
+ "WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n"
+ "ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n"
+ "OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n";
diff --git a/FICS/legal2.h b/FICS/legal2.h
new file mode 100644
index 0000000..0ee2173
--- /dev/null
+++ b/FICS/legal2.h
@@ -0,0 +1,10 @@
+#ifndef FICS_LEGAL2_H_
+#define FICS_LEGAL2_H_
+
+#include "common.h"
+
+__FICS_BEGIN_DECLS
+extern const char legalNotice2[];
+__FICS_END_DECLS
+
+#endif
diff --git a/FICS/lists.c b/FICS/lists.c
new file mode 100644
index 0000000..c11d75b
--- /dev/null
+++ b/FICS/lists.c
@@ -0,0 +1,567 @@
+/* lists.c -- new global lists code
+ *
+ * Added by Shaney, 29 May 1995.
+ * Revised by maxxe, 15 Dec 2023.
+ * This file is part of FICS.
+ */
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include <err.h>
+#include <string.h>
+
+#include "command.h"
+#include "comproc.h"
+#include "ficsmain.h"
+#include "gamedb.h"
+#include "lists.h"
+#include "maxxes-utils.h"
+#include "multicol.h"
+#include "playerdb.h"
+#include "ratings.h"
+#include "rmalloc.h"
+#include "talkproc.h"
+#include "utils.h"
+
+PRIVATE List *firstGlobalList = NULL;
+
+PRIVATE ListTable ListArray[] = {
+ {P_HEAD, "admin"},
+ {P_GOD, "removedcom"},
+ {P_ADMIN, "filter"},
+ {P_ADMIN, "ban"},
+ {P_ADMIN, "abuser"},
+ {P_ADMIN, "muzzle"},
+ {P_ADMIN, "cmuzzle"},
+ {P_PUBLIC, "fm"},
+ {P_PUBLIC, "im"},
+ {P_PUBLIC, "gm"},
+ {P_PUBLIC, "blind"},
+ {P_PUBLIC, "teams"},
+ {P_PUBLIC, "computer"},
+ {P_PUBLIC, "td"},
+ {P_PERSONAL, "censor"},
+ {P_PERSONAL, "gnotify"},
+ {P_PERSONAL, "noplay"},
+ {P_PERSONAL, "notify"},
+ {P_PERSONAL, "channel"},
+ {0, NULL},
+};
+
+/*
+ * find a list.
+ * loads from disk if not in memory.
+ */
+PRIVATE List *
+list_find(int p, enum ListWhich l)
+{
+ List *prev = NULL, *tempList, **starter;
+ int count = 0;
+ int personal;
+
+ personal = ListArray[l].rights == P_PERSONAL;
+
+ if (personal) {
+ if (p < 0)
+ return NULL;
+ starter = &parray[p].lists;
+ } else {
+ starter = &firstGlobalList;
+ }
+
+ for (tempList = *starter; tempList != NULL; tempList = tempList->next) {
+ if (l == tempList->which) {
+ if (prev != NULL) {
+ prev->next = tempList->next;
+ tempList->next = *starter;
+ *starter = tempList;
+ }
+
+ return tempList;
+ }
+
+ prev = tempList;
+ }
+
+ if ((tempList = rmalloc(sizeof(List))) == NULL)
+ return NULL;
+ if (!personal) { // now we have to load the list
+ FILE *fp;
+ char filename[MAX_FILENAME_SIZE] = { '\0' };
+ char listmember[100] = { '\0' };
+
+ msnprintf(filename, sizeof filename, "%s/%s", lists_dir,
+ ListArray[l].name);
+
+ if ((fp = fopen(filename, "r")) == NULL) {
+ rfree(tempList);
+ return NULL;
+ }
+ while (fgets(listmember, ARRAY_SIZE(listmember), fp) != NULL) {
+ listmember[strcspn(listmember, "\n")] = '\0';
+ tempList->member[count++] = xstrdup(listmember);
+ }
+
+ fclose(fp);
+ }
+
+ tempList->which = l;
+ tempList->numMembers = count;
+ tempList->next = *starter;
+ *starter = tempList;
+ return tempList;
+}
+
+/*
+ * Add item to list
+ */
+PUBLIC int
+list_add(int p, enum ListWhich l, char *s)
+{
+ List *gl;
+
+ if ((gl = list_find(p, l)) != NULL) {
+ if (gl->numMembers < MAX_GLOBAL_LIST_SIZE) {
+ gl->member[gl->numMembers] = xstrdup(s);
+ gl->numMembers++;
+ return 0;
+ } else {
+ return 1;
+ }
+ } else {
+ return 1;
+ }
+}
+
+/*
+ * Remove item from list.
+ */
+PUBLIC int
+list_sub(int p, enum ListWhich l, char *s)
+{
+ List *gl;
+
+ if ((gl = list_find(p, l)) != NULL) {
+ int i, found = -1;
+
+ for (i = 0; i < gl->numMembers; i++) {
+ if (!strcasecmp(s, gl->member[i])) {
+ found = i;
+ break;
+ }
+ }
+
+ if (found == -1)
+ return 1;
+
+ rfree(gl->member[found]);
+
+ for (i = found; i < (gl->numMembers - 1); i++)
+ gl->member[i] = gl->member[i + 1];
+
+ gl->numMembers--;
+
+ return 0;
+ }
+
+ return 1;
+}
+
+/* pretty cheesy: print each member of a list, 1 per line */
+PUBLIC void
+list_print(FILE *fp, int p, enum ListWhich l)
+{
+ List *gl;
+
+ if ((gl = list_find(p, l)) != NULL) {
+ for (int i = 0; i < gl->numMembers; i++)
+ fprintf(fp, "%s\n", gl->member[i]);
+ }
+}
+
+/*
+ * Return size of a list.
+ */
+PUBLIC int
+list_size(int p, enum ListWhich l)
+{
+ List *gl;
+
+ if ((gl = list_find(p, l)) != NULL)
+ return (gl->numMembers);
+ return 0;
+}
+
+/*
+ * Find list by name
+ * (doesn't have to be the whole name)
+ */
+PRIVATE List *
+list_findpartial(int p, char *which, int gonnado)
+{
+ List *gl;
+ int foundit, slen;
+
+ foundit = -1;
+ slen = strlen(which);
+
+ for (int i = 0; ListArray[i].name != NULL; i++) {
+ if (!strncasecmp(ListArray[i].name, which, slen)) {
+ if (foundit == -1)
+ foundit = i;
+ else
+ return NULL; // ambiguous
+ }
+ }
+
+ if (foundit != -1) {
+ int rights = ListArray[foundit].rights;
+ int youlose = 0;
+
+ switch (rights) { // check rights
+ case P_HEAD:
+ if (gonnado && !player_ishead(p))
+ youlose = 1;
+ break;
+ case P_GOD:
+ if ((gonnado && parray[p].adminLevel < ADMIN_GOD) ||
+ (!gonnado && parray[p].adminLevel < ADMIN_ADMIN))
+ youlose = 1;
+ break;
+ case P_ADMIN:
+ if (parray[p].adminLevel < ADMIN_ADMIN)
+ youlose = 1;
+ break;
+ case P_PUBLIC:
+ if (gonnado && (parray[p].adminLevel < ADMIN_ADMIN))
+ youlose = 1;
+ break;
+ }
+
+ if (youlose) {
+ pprintf(p, "\"%s\" is not an appropriate list name or "
+ "you have insufficient rights.\n", which);
+ return NULL;
+ }
+
+ gl = list_find(p, foundit);
+ } else {
+ pprintf(p, "\"%s\" does not match any list name.\n", which);
+ return NULL;
+ }
+
+ return gl;
+}
+
+/*
+ * See if something is in a list
+ */
+PUBLIC int
+in_list(int p, enum ListWhich which, char *member)
+{
+ List *gl;
+ int filterList = (which == L_FILTER);
+
+ if ((gl = list_find(p, which)) == NULL || member == NULL)
+ return 0;
+ for (int i = 0; i < gl->numMembers; i++) {
+ if (filterList) {
+ if (!strncasecmp(member, gl->member[i],
+ strlen(gl->member[i])))
+ return 1;
+ } else {
+ if (!strcasecmp(member, gl->member[i]))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Add or subtract something to/from a list.
+ */
+PUBLIC int
+list_addsub(int p, char *list, char *who, int addsub)
+{
+ List *gl;
+ char *listname, *member;
+ char *yourthe, *addrem;
+ int p1 = -1, connected, loadme, personal, ch;
+
+ if ((gl = list_findpartial(p, list, addsub)) == NULL)
+ return COM_OK;
+
+ personal = (ListArray[gl->which].rights == P_PERSONAL);
+ loadme = (gl->which != L_FILTER &&
+ gl->which != L_REMOVEDCOM &&
+ gl->which != L_CHANNEL);
+ listname = ListArray[gl->which].name;
+ yourthe = (personal ? "your" : "the");
+ addrem = (addsub == 1 ? "added to" : "removed from");
+
+ if (loadme) {
+ if (!FindPlayer(p, who, &p1, &connected)) {
+ if (addsub == 1)
+ return COM_OK;
+ member = who; // allow sub removed/renamed player
+ loadme = 0;
+ } else
+ member = parray[p1].name;
+ } else {
+ member = who;
+ }
+
+ if (addsub == 1) { // add to list
+ if (gl->which == L_CHANNEL) {
+ if (sscanf(who, "%d", &ch) == 1) {
+ if (!in_list(p, L_ADMIN, parray[p].name) &&
+ ch == 0) {
+ pprintf(p, "Only admins may join "
+ "channel 0.\n");
+ return COM_OK;
+ }
+
+ if (ch > (MAX_CHANNELS - 1)) {
+ pprintf(p, "The maximum channel "
+ "number is %d.\n",
+ (MAX_CHANNELS - 1));
+ return COM_OK;
+ }
+ } else {
+ pprintf(p, "Your channel to add must be a "
+ "number between 0 and %d.\n",
+ (MAX_CHANNELS - 1));
+ return COM_OK;
+ }
+ }
+
+ if (in_list(p, gl->which, member)) {
+ pprintf(p, "[%s] is already on %s %s list.\n", member,
+ yourthe, listname);
+
+ if (loadme && !connected)
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ if (list_add(p, gl->which, member)) {
+ pprintf(p, "Sorry, %s %s list is full.\n", yourthe,
+ listname);
+
+ if (loadme && !connected)
+ player_remove(p1);
+ return COM_OK;
+ }
+ } else if (addsub == 2) { // subtract from list
+ if (!in_list(p, gl->which, member)) {
+ pprintf(p, "[%s] is not in %s %s list.\n", member,
+ yourthe, listname);
+
+ if (loadme && !connected)
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ list_sub(p, gl->which, member);
+ }
+
+ pprintf(p, "[%s] %s %s %s list.\n", member, addrem, yourthe, listname);
+
+ if (!personal) {
+ FILE *fp;
+ char filename[MAX_FILENAME_SIZE] = { '\0' };
+ int fd;
+
+ switch (gl->which) {
+ case L_MUZZLE:
+ case L_CMUZZLE:
+ case L_ABUSER:
+ case L_BAN:
+ pprintf(p, "Please leave a comment to explain why %s "
+ "was %s the %s list.\n", member, addrem, listname);
+ pcommand(p, "addcomment %s %s %s list.\n", member,
+ addrem, listname);
+ break;
+ case L_COMPUTER:
+ if (p1 < 0) {
+ warnx("%s: negative player number", __func__);
+ break;
+ }
+ if (parray[p1].b_stats.rating > 0) {
+ UpdateRank(TYPE_BLITZ, member,
+ &parray[p1].b_stats, member);
+ }
+ if (parray[p1].s_stats.rating > 0) {
+ UpdateRank(TYPE_STAND, member,
+ &parray[p1].s_stats, member);
+ }
+ if (parray[p1].w_stats.rating > 0) {
+ UpdateRank(TYPE_WILD, member,
+ &parray[p1].w_stats, member);
+ }
+ break;
+ case L_ADMIN:
+ if (p1 < 0) {
+ warnx("%s: negative player number", __func__);
+ break;
+ }
+ if (addsub == 1) { // adding to list
+ parray[p1].adminLevel = 10;
+ pprintf(p, "%s has been given an admin level "
+ "of 10 - change with asetadmin.\n", member);
+ } else {
+ parray[p1].adminLevel = 0;
+ }
+ break;
+ case L_FILTER:
+ pprintf(p, "Please leave a message for filter to "
+ "explain why site %s was %s filter list.\n", member,
+ addrem);
+ break;
+ case L_REMOVEDCOM:
+ pprintf(p, "Please leave a message on anews to explain "
+ "why %s was %s removedcom list.\n", member, addrem);
+ break;
+ default:
+ break;
+ }
+
+ if (loadme && connected) {
+ pprintf_prompt(p1, "You have been %s the %s list "
+ "by %s.\n", addrem, listname, parray[p].name);
+ }
+
+ msnprintf(filename, sizeof filename, "%s/%s", lists_dir,
+ listname);
+
+ if ((fd = open(filename, g_open_flags[1], g_open_modes)) < 0) {
+ fprintf(stderr, "Couldn't save %s list.\n", listname);
+ } else if ((fp = fdopen(fd, "w")) == NULL) {
+ fprintf(stderr, "Couldn't save %s list.\n", listname);
+ close(fd);
+ } else {
+ for (int i = 0; i < gl->numMembers; i++)
+ fprintf(fp, "%s\n", gl->member[i]);
+ fclose(fp);
+ }
+ }
+
+ if (loadme || gl->which == L_ADMIN)
+ player_save(p1);
+ if (loadme && !connected)
+ player_remove(p1);
+ return COM_OK;
+}
+
+PUBLIC int
+com_addlist(int p, param_list param)
+{
+ return list_addsub(p, param[0].val.word, param[1].val.word, 1);
+}
+
+PUBLIC int
+com_sublist(int p, param_list param)
+{
+ return list_addsub(p, param[0].val.word, param[1].val.word, 2);
+}
+
+PUBLIC int
+com_showlist(int p, param_list param)
+{
+ List *gl;
+ char *rightnames[] = {
+ "EDIT HEAD, READ ADMINS",
+ "EDIT GODS, READ ADMINS",
+ "READ/WRITE ADMINS",
+ "PUBLIC",
+ "PERSONAL"
+ };
+ int i, rights;
+
+ if (param[0].type == 0) { // Show all lists
+ pprintf(p, "Lists:\n\n");
+
+ for (i = 0; ListArray[i].name != NULL; i++) {
+ if ((rights = ListArray[i].rights) > P_ADMIN ||
+ parray[p].adminLevel >= ADMIN_ADMIN) {
+ pprintf(p, "%-20s is %s\n", ListArray[i].name,
+ rightnames[rights]);
+ }
+ }
+ } else { // find match in index
+ if ((gl = list_findpartial(p, param[0].val.word, 0)) == NULL)
+ return COM_OK;
+
+ { // display the list
+ multicol *m = multicol_start(gl->numMembers);
+
+ pprintf(p, "-- %s list: %d %s --",
+ ListArray[gl->which].name,
+ gl->numMembers,
+ ((!strcmp(ListArray[gl->which].name, "filter"))
+ ? "ips"
+ : (!strcmp(ListArray[gl->which].name, "removedcom"))
+ ? "commands"
+ : (!strcmp(ListArray[gl->which].name, "channel"))
+ ? "channels"
+ : "names"));
+ for (i = 0; i < gl->numMembers; i++)
+ multicol_store_sorted(m, gl->member[i]);
+ multicol_pprint(m, p, 78, 2);
+ multicol_end(m);
+ }
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+list_channels(int p, int p1)
+{
+ List *gl;
+
+ if ((gl = list_findpartial(p1, "channel", 0)) == NULL)
+ return 1;
+
+ if (gl->numMembers == 0)
+ return 1;
+
+ {
+ multicol *m = multicol_start(gl->numMembers);
+
+ for (int i = 0; i < gl->numMembers; i++)
+ multicol_store_sorted(m, gl->member[i]);
+ multicol_pprint(m, p, 78, 1);
+ multicol_end(m);
+ }
+
+ return 0;
+}
+
+/*
+ * Free the memory used by a list.
+ */
+PUBLIC void
+list_free(List *gl)
+{
+ List *temp;
+
+ while (gl) {
+ for (int i = 0; i < gl->numMembers; i++)
+ strfree(gl->member[i]);
+ temp = gl->next;
+ rfree(gl);
+ gl = temp;
+ }
+}
+
+PUBLIC int
+titled_player(int p, char *name)
+{
+ if (in_list(p, L_FM, name) ||
+ in_list(p, L_IM, name) ||
+ in_list(p, L_GM, name))
+ return 1;
+ return 0;
+}
diff --git a/FICS/lists.h b/FICS/lists.h
new file mode 100644
index 0000000..f26fcb9
--- /dev/null
+++ b/FICS/lists.h
@@ -0,0 +1,76 @@
+/* lists.h
+ *
+ * Revised by maxxe 23/12/15
+ */
+
+#include <stdio.h>
+
+#ifndef _LISTS_H
+#define _LISTS_H
+
+#include "command.h" /* param_list */
+
+enum ListWhich {
+ L_ADMIN = 0,
+ L_REMOVEDCOM,
+ L_FILTER,
+ L_BAN,
+ L_ABUSER,
+ L_MUZZLE,
+ L_CMUZZLE,
+ L_FM,
+ L_IM,
+ L_GM,
+ L_BLIND,
+ L_TEAMS,
+ L_COMPUTER,
+ L_TD,
+ L_CENSOR,
+ L_GNOTIFY,
+ L_NOPLAY,
+ L_NOTIFY,
+ L_CHANNEL
+};
+
+enum ListPerm {
+ P_HEAD = 0,
+ P_GOD,
+ P_ADMIN,
+ P_PUBLIC,
+ P_PERSONAL
+};
+
+typedef struct {
+ enum ListPerm rights;
+ char *name;
+} ListTable;
+
+/*
+ * Max names in one list.
+ */
+#define MAX_GLOBAL_LIST_SIZE 200
+
+typedef struct _List List;
+
+struct _List {
+ enum ListWhich which;
+ int numMembers;
+ char *member[MAX_GLOBAL_LIST_SIZE];
+ struct _List *next;
+};
+
+extern int com_addlist(int, param_list);
+extern int com_showlist(int, param_list);
+extern int com_sublist(int, param_list);
+
+extern int in_list(int, enum ListWhich, char *);
+extern int list_add(int, enum ListWhich, char *);
+extern int list_addsub(int, char *, char *, int);
+extern int list_channels(int, int);
+extern int list_size(int, enum ListWhich);
+extern int list_sub(int, enum ListWhich, char *);
+extern int titled_player(int, char *);
+extern void list_free(List *);
+extern void list_print(FILE *, int, enum ListWhich);
+
+#endif /* _LISTS_H */
diff --git a/FICS/makerank.c b/FICS/makerank.c
new file mode 100644
index 0000000..076e16e
--- /dev/null
+++ b/FICS/makerank.c
@@ -0,0 +1,344 @@
+/*
+ * Revised by maxxe 23/12/14
+ */
+
+#include <ctype.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if __linux__
+#include <bsd/string.h>
+#endif
+
+#include "common.h"
+#include "ficsmain.h"
+#include "makerank.h"
+#include "utils.h"
+
+static ENTRY **list;
+static ENTRY **sortme;
+
+static char *rnames[] = { "std", "blitz", "wild", "lightning" };
+static int rtype;
+
+static int
+GetPlayerInfo(char *fileName, ENTRY *e)
+{
+ FILE *fp = NULL;
+ char NameWithCase[30] = { '\0' };
+ char field[20] = { '\0' };
+ char line[100] = { '\0' };
+ int done = 0;
+
+ e->computer = 0;
+
+ for (size_t i = 0; i < ARRAY_SIZE(e->r); i++) {
+ e->r[i].num = 0;
+ e->r[i].rating = 0;
+ }
+
+ if ((fp = fopen(fileName, "r")) == NULL ||
+ fgets(line, sizeof line, fp) == NULL ||
+ feof(fp)) {
+ if (fp)
+ fclose(fp);
+ return 0;
+ }
+
+ if (!strcmp(line, "v 1\n")) {
+ _Static_assert(ARRAY_SIZE(e->name) > 19, "Array too little");
+
+ if (fgets(line, sizeof line, fp) == NULL ||
+ sscanf(line, "%19s", e->name) != 1) {
+ warnx("%s: fgets() or sscanf() error", __func__);
+ fclose(fp);
+ return 0;
+ } else if (fgets(line, sizeof line, fp) == NULL ||
+ fgets(line, sizeof line, fp) == NULL ||
+ fgets(line, sizeof line, fp) == NULL) {
+ warnx("%s: fgets() error", __func__);
+// fclose(fp);
+// return 0;
+ }
+
+ if (fscanf(fp, "%d %*u %*u %*u %d %*u %*u %*u %*u %d %*u %*u "
+ "%*u %d %*u %*u %*u %*u %d %*u %*u %*u %d %*u %*u %*u %*u "
+ "%d %*u %*u %*u %d %*u %*u %*u %*u",
+ &(e->r[0].num),
+ &(e->r[0].rating),
+ &(e->r[1].num),
+ &(e->r[1].rating),
+ &(e->r[2].num),
+ &(e->r[2].rating),
+ &(e->r[3].num),
+ &(e->r[3].rating)) != 8) {
+ fprintf(stderr, "OOPS: couldn't parse player file %s."
+ "\n", fileName);
+ fclose(fp);
+ return 0;
+ }
+
+ done = 1;
+ } else {
+ do {
+ _Static_assert(ARRAY_SIZE(field) > 19,
+ "Unexpected array length");
+ _Static_assert(ARRAY_SIZE(NameWithCase) > 29,
+ "Unexpected array length");
+
+ if (sscanf(line, "%19s", field) != 1) {
+ warnx("%s: sscanf() error", __func__);
+ fclose(fp);
+ return 0;
+ }
+
+ if (!strcmp(field, "Name:")) {
+ if (sscanf(line, "%*s %29s", NameWithCase) != 1) {
+ warnx("%s: expected name with case",
+ __func__);
+ fclose(fp);
+ return 0;
+ }
+
+ if (strcasecmp(e->name, NameWithCase)) {
+ printf("TROUBLE: %s's handle is "
+ "listed as %s.\n", e->name,
+ NameWithCase);
+ } else if (strlcpy(e->name, NameWithCase,
+ sizeof e->name) >= sizeof e->name) {
+ fprintf(stderr, "%s: warning: "
+ "strlcpy() truncated\n", __func__);
+ }
+ } else if (!strcmp(field, "S_NUM:")) {
+ if (sscanf(line, "%*s %d", &(e->r[0].num)) != 1)
+ warnx("%s: S_NUM error", __func__);
+ } else if (!strcmp(field, "B_NUM:")) {
+ if (sscanf(line, "%*s %d", &(e->r[1].num)) != 1)
+ warnx("%s: B_NUM error", __func__);
+ } else if (!strcmp(field, "W_NUM:")) {
+ if (sscanf(line, "%*s %d", &(e->r[2].num)) != 1)
+ warnx("%s: W_NUM error", __func__);
+ } else if (!strcmp(field, "L_NUM:")) {
+ if (sscanf(line, "%*s %d", &(e->r[3].num)) != 1)
+ warnx("%s: L_NUM error", __func__);
+ } else if (!strcmp(field, "S_RATING:")) {
+ if (sscanf(line, "%*s %d",
+ &(e->r[0].rating)) != 1)
+ warnx("%s: S_RATING error", __func__);
+ } else if (!strcmp(field, "B_RATING:")) {
+ if (sscanf(line, "%*s %d",
+ &(e->r[1].rating)) != 1)
+ warnx("%s: B_RATING error", __func__);
+ } else if (!strcmp(field, "W_RATING:")) {
+ if (sscanf(line, "%*s %d",
+ &(e->r[2].rating)) != 1)
+ warnx("%s: W_RATING error", __func__);
+ } else if (!strcmp(field, "L_RATING:")) {
+ if (sscanf(line, "%*s %d",
+ &(e->r[3].rating)) != 1)
+ warnx("%s: L_RATING error", __func__);
+ } else if (!strcmp(field, "Network:")) {
+ done = 1;
+ }
+
+ if (fgets(line, sizeof line, fp) == NULL)
+ break;
+ } while (!done && !feof(fp));
+ }
+
+ fclose(fp);
+ return (done ? 1 : 0);
+}
+
+static int
+LoadEntries(void)
+{
+ ENTRY e;
+ FILE *fpPlayerList;
+ char command[90];
+ char letter1;
+ char pathInput[80];
+ int n = 0;
+ int listsize;
+
+ listsize = 100;
+ list = reallocarray(NULL, sizeof(ENTRY *), listsize);
+
+ if (list == NULL)
+ err(1, "%s: reallocarray", __func__);
+
+ for (letter1 = 'a'; letter1 <= 'z'; letter1++) {
+ printf("Loading %c's.\n", letter1);
+
+ snprintf(pathInput, sizeof pathInput, "%s/%c", DEFAULT_PLAYERS,
+ letter1);
+ snprintf(command, sizeof command, "ls -1 %s", pathInput);
+
+ if ((fpPlayerList = popen(command, "r")) == NULL)
+ continue;
+
+ while (1) {
+ if (fgets(e.name, sizeof(e.name), fpPlayerList) == NULL ||
+ feof(fpPlayerList))
+ break;
+
+ e.name[strcspn(e.name, "\n")] = '\0';
+
+ /*
+ * Validate that e.name does not contain path
+ * traversal or separators
+ */
+ if (!is_valid_filename(e.name, false)) {
+ printf("Skipping invalid filename: %s\n",
+ e.name);
+ continue;
+ }
+
+ if (e.name[0] != letter1) {
+ printf("File %c/%s: wrong directory.\n",
+ letter1, e.name);
+ } else {
+ snprintf(pathInput, sizeof pathInput,
+ "%s/%c/%s", DEFAULT_PLAYERS, letter1,
+ e.name);
+
+ if (GetPlayerInfo(pathInput, &e)) {
+ if ((list[n] = malloc(sizeof(ENTRY))) ==
+ NULL)
+ err(1, "%s: malloc", __func__);
+
+ memcpy(list[n], &e, sizeof(ENTRY));
+
+ if (++n == listsize) {
+ listsize += 100;
+ list = reallocarray(list,
+ listsize,
+ sizeof(ENTRY *));
+ if (list == NULL)
+ err(1, NULL);
+ }
+ }
+ }
+ } /* while (1) */
+
+ pclose(fpPlayerList);
+ } /* for */
+
+ return n;
+}
+
+static int
+SetComputers(int n)
+{
+ FILE *fpComp = NULL;
+ char comp[30] = { '\0' };
+ char line[100] = { '\0' };
+ int i = 0;
+
+ if (snprintf(line, sizeof line, "sort -f %s", COMPUTER_FILE) >=
+ (int)sizeof line) {
+ warnx("%s: snprintf truncated", __func__);
+ return 0;
+ } else if ((fpComp = popen(line, "r")) == NULL)
+ return 0;
+
+ while (i < n) {
+ if (fgets(comp, sizeof comp, fpComp) == NULL ||
+ feof(fpComp))
+ break;
+
+ comp[strcspn(comp, "\n")] = '\0';
+
+ while (i < n && strcasecmp(list[i]->name, comp) < 0)
+ i++;
+ if (i < n && strcasecmp(list[i]->name, comp) == 0)
+ list[i++]->computer = 1;
+ }
+
+ pclose(fpComp);
+ return 1;
+}
+
+static int
+sortfunc(const void *i, const void *j)
+{
+ int n = (*(ENTRY **)j)->r[rtype].rating -
+ (*(ENTRY **)i)->r[rtype].rating;
+
+ return n ? n : strcasecmp((*(ENTRY **)i)->name, (*(ENTRY **)j)->name);
+}
+
+static void
+makerank(void)
+{
+ FILE *fp;
+ char fName[200];
+ int fd;
+ int sortnum, sortmesize, i, n;
+
+ printf("Loading players\n");
+ n = LoadEntries();
+ printf("Found %d players.\n", n);
+ printf("Setting computers.\n");
+ SetComputers(n);
+
+ for (rtype = 0; rtype < 4; rtype++) {
+ sortnum = 0;
+ sortmesize = 100;
+ sortme = reallocarray(NULL, sizeof(ENTRY *),
+ sortmesize);
+
+ if (sortme == NULL)
+ err(1, "%s: reallocarray", __func__);
+
+ for (i = 0; i < n; i++) {
+ if (list[i]->r[rtype].rating) {
+ sortme[sortnum++] = list[i];
+
+ if (sortnum == sortmesize) {
+ sortmesize += 100;
+ sortme = reallocarray(sortme,
+ sortmesize,
+ sizeof(ENTRY *));
+ if (sortme == NULL)
+ err(1, NULL);
+ }
+ }
+ }
+
+ printf("Sorting %d %s.\n", sortnum, rnames[rtype]);
+ qsort(sortme, sortnum, sizeof(ENTRY *), sortfunc);
+
+ printf("Saving to file.\n");
+ snprintf(fName, sizeof fName, "%s/rank.%s", DEFAULT_STATS,
+ rnames[rtype]);
+
+ if ((fd = open(fName, g_open_flags[1], g_open_modes)) < 0 ||
+ (fp = fdopen(fd, "w")) == NULL)
+ err(1, "%s: rank file open error", __func__);
+
+ for (i = 0; i < sortnum; i++) {
+ fprintf(fp, "%s %d %d %d\n",
+ sortme[i]->name,
+ sortme[i]->r[rtype].rating,
+ sortme[i]->r[rtype].num,
+ sortme[i]->computer);
+ }
+ fclose(fp);
+ free(sortme);
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ if (argc > 1) {
+ fprintf(stderr, "usage: %s.\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ makerank();
+ return EXIT_SUCCESS;
+}
diff --git a/FICS/makerank.h b/FICS/makerank.h
new file mode 100644
index 0000000..6d70da2
--- /dev/null
+++ b/FICS/makerank.h
@@ -0,0 +1,20 @@
+#ifndef MAKERANK_H
+#define MAKERANK_H
+
+#include "config.h"
+
+#define COMPUTER_FILE DEFAULT_LISTS "/computer"
+#define MAX_LOGIN_NAME 21
+
+typedef struct _ratings {
+ int num;
+ int rating;
+} ratings;
+
+typedef struct _Entry {
+ char name[MAX_LOGIN_NAME];
+ int computer;
+ ratings r[4];
+} ENTRY;
+
+#endif
diff --git a/FICS/matchproc.c b/FICS/matchproc.c
new file mode 100644
index 0000000..1b1eab8
--- /dev/null
+++ b/FICS/matchproc.c
@@ -0,0 +1,1430 @@
+/* matchproc.c
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ hersco dhersco@stmarys-ca.edu 95/07/24 Created
+ Markus Uhlin 23/12/14 Fixed compiler warnings
+ Markus Uhlin 24/03/29 Refactored and
+ reformatted all
+ functions.
+ Markus Uhlin 24/03/29 Size-bounded string
+ handling.
+ Markus Uhlin 24/03/29 Fixed potentially
+ insecure format strings.
+ Markus Uhlin 24/12/02 com_accept: check that
+ the accept number is
+ within bounds.
+ Markus Uhlin 25/03/12 Fixed negative array
+ index read in
+ accept_match().
+*/
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include <sys/resource.h>
+
+#include <err.h>
+#include <string.h>
+
+#include "board.h"
+#include "command.h"
+#include "comproc.h"
+#include "eco.h"
+#include "formula.h"
+#include "gamedb.h"
+#include "lists.h"
+#include "matchproc.h"
+#include "network.h"
+#include "obsproc.h"
+#include "playerdb.h"
+#include "ratings.h"
+#include "rmalloc.h"
+#include "talkproc.h"
+#include "utils.h"
+
+#if __linux__
+#include <bsd/string.h>
+#endif
+
+struct print_bh_context {
+ int pp;
+ int pp1;
+
+ int rated;
+ int type;
+ int white;
+
+ char *board;
+ char *category;
+
+ int binc;
+ int bt;
+ int winc;
+ int wt;
+};
+
+PUBLIC int
+create_new_match(int white_player, int black_player, int wt, int winc, int bt,
+ int binc, int rated, char *category, char *board, int white)
+{
+ char outStr[1024] = { '\0' };
+ int g, p;
+ int reverse = 0;
+
+ if ((g = game_new()) < 0)
+ return COM_FAILED;
+
+ if (white == 0) {
+ reverse = 1;
+ } else if (white == -1) {
+ if (wt == bt && winc == binc) {
+ if (parray[white_player].lastColor ==
+ parray[black_player].lastColor) {
+ int diff1, diff2;
+
+ diff1 = (parray[white_player].num_white -
+ parray[white_player].num_black);
+ diff2 = (parray[black_player].num_white -
+ parray[black_player].num_black);
+
+ if (diff1 > diff2)
+ reverse = 1;
+ } else if (parray[white_player].lastColor == WHITE)
+ reverse = 1;
+ } else
+ reverse = 1; // Challenger is always white in
+ // unbalanced match
+ }
+
+ if (reverse) {
+ int tmp = white_player;
+
+ white_player = black_player;
+ black_player = tmp;
+ }
+
+ player_remove_request(white_player, black_player, PEND_MATCH);
+ player_remove_request(black_player, white_player, PEND_MATCH);
+ player_remove_request(white_player, black_player, PEND_SIMUL);
+ player_remove_request(black_player, white_player, PEND_SIMUL);
+ player_decline_offers(white_player, -1, PEND_MATCH);
+ player_withdraw_offers(white_player, -1, PEND_MATCH);
+ player_decline_offers(black_player, -1, PEND_MATCH);
+ player_withdraw_offers(black_player, -1, PEND_MATCH);
+ player_withdraw_offers(white_player, -1, PEND_SIMUL);
+ player_withdraw_offers(black_player, -1, PEND_SIMUL);
+
+ wt = (wt * 60); // To Seconds
+ bt = (bt * 60);
+
+ garray[g].white = white_player;
+ garray[g].black = black_player;
+
+ strlcpy(garray[g].white_name, parray[white_player].name,
+ sizeof(garray[g].white_name));
+ strlcpy(garray[g].black_name, parray[black_player].name,
+ sizeof(garray[g].black_name));
+
+ garray[g].status = GAME_ACTIVE;
+ garray[g].type = game_isblitz(wt / 60, winc, bt / 60, binc,
+ category, board);
+
+ if (garray[g].type == TYPE_UNTIMED ||
+ garray[g].type == TYPE_NONSTANDARD)
+ garray[g].rated = 0;
+ else
+ garray[g].rated = rated;
+
+ garray[g].private = (parray[white_player].private ||
+ parray[black_player].private);
+ garray[g].white = white_player;
+
+ if (garray[g].type == TYPE_BLITZ) {
+ garray[g].white_rating = parray[white_player].b_stats.rating;
+ garray[g].black_rating = parray[black_player].b_stats.rating;
+ } else if (garray[g].type == TYPE_WILD) {
+ garray[g].white_rating = parray[white_player].w_stats.rating;
+ garray[g].black_rating = parray[black_player].w_stats.rating;
+ } else if (garray[g].type == TYPE_LIGHT) {
+ garray[g].white_rating = parray[white_player].l_stats.rating;
+ garray[g].black_rating = parray[black_player].l_stats.rating;
+ } else if (garray[g].type == TYPE_BUGHOUSE) {
+ garray[g].white_rating = parray[white_player].bug_stats.rating;
+ garray[g].black_rating = parray[black_player].bug_stats.rating;
+ } else {
+ garray[g].white_rating = parray[white_player].s_stats.rating;
+ garray[g].black_rating = parray[black_player].s_stats.rating;
+ }
+
+ if (board_init(&garray[g].game_state, category, board)) {
+ pprintf(white_player, "PROBLEM LOADING BOARD. Game Aborted.\n");
+ pprintf(black_player, "PROBLEM LOADING BOARD. Game Aborted.\n");
+
+ fprintf(stderr, "FICS: PROBLEM LOADING BOARD %s %s. "
+ "Game Aborted.\n", category, board);
+ }
+
+ garray[g].game_state.gameNum = g;
+ garray[g].wTime = (wt * 10);
+ garray[g].wInitTime = (wt * 10);
+ garray[g].wIncrement = (winc * 10);
+ garray[g].bTime = (bt * 10);
+
+ if (garray[g].type != TYPE_UNTIMED) {
+ if (wt == 0)
+ garray[g].wTime = 100;
+ if (bt == 0)
+ garray[g].bTime = 100;
+ } // 0 x games start with 10 seconds
+
+#ifdef TIMESEAL
+ garray[g].wRealTime = garray[g].wTime * 100;
+ garray[g].bRealTime = garray[g].bTime * 100;
+ garray[g].wTimeWhenReceivedMove = 0;
+ garray[g].bTimeWhenReceivedMove = 0;
+#endif
+
+ garray[g].bInitTime = bt * 10;
+ garray[g].bIncrement = binc * 10;
+
+ if (garray[g].game_state.onMove == BLACK) { // Start with black
+ garray[g].numHalfMoves = 1;
+ garray[g].moveListSize = 1;
+ garray[g].moveList = rmalloc(sizeof(move_t));
+ garray[g].moveList[0].fromFile = -1;
+ garray[g].moveList[0].fromRank = -1;
+ garray[g].moveList[0].toFile = -1;
+ garray[g].moveList[0].toRank = -1;
+ garray[g].moveList[0].color = WHITE;
+
+ strlcpy(garray[g].moveList[0].moveString, "NONE",
+ sizeof(garray[g].moveList[0].moveString));
+ strlcpy(garray[g].moveList[0].algString, "NONE",
+ sizeof(garray[g].moveList[0].algString));
+ } else {
+ garray[g].numHalfMoves = 0;
+ garray[g].moveListSize = 0;
+ garray[g].moveList = NULL;
+ }
+
+ garray[g].timeOfStart = tenth_secs();
+ garray[g].startTime = tenth_secs();
+ garray[g].lastMoveTime = garray[g].startTime;
+ garray[g].lastDecTime = garray[g].startTime;
+ garray[g].clockStopped = 0;
+
+ snprintf(outStr, sizeof outStr, "\n{Game %d (%s vs. %s) "
+ "Creating %s %s match.}\n",
+ (g + 1),
+ parray[white_player].name,
+ parray[black_player].name,
+ rstr[garray[g].rated],
+ bstr[garray[g].type]);
+
+ pprintf(white_player, "%s", outStr);
+ pprintf(black_player, "%s", outStr);
+
+ for (p = 0; p < p_num; p++) {
+ int gnw, gnb;
+
+ if ((p == white_player) || (p == black_player))
+ continue;
+ if (parray[p].status != PLAYER_PROMPT)
+ continue;
+ if (parray[p].i_game)
+ pprintf_prompt(p, "%s", outStr);
+
+ gnw = in_list(p, L_GNOTIFY, parray[white_player].login);
+ gnb = in_list(p, L_GNOTIFY, parray[black_player].login);
+
+ if (gnw || gnb) {
+ pprintf(p, "Game notification: ");
+
+ if (gnw) {
+ pprintf_highlight(p, "%s",
+ parray[white_player].name);
+ } else {
+ pprintf(p, "%s", parray[white_player].name);
+ }
+
+ pprintf(p, " (%s) vs. ",
+ ratstr(GetRating(&parray[white_player],
+ garray[g].type)));
+
+ if (gnb) {
+ pprintf_highlight(p, "%s",
+ parray[black_player].name);
+ } else {
+ pprintf(p, "%s", parray[black_player].name);
+ }
+
+ pprintf_prompt(p, " (%s) %s %s %d %d\n",
+ ratstr(GetRating(&parray[black_player],
+ garray[g].type)),
+ rstr[garray[g].rated],
+ bstr[garray[g].type],
+ (garray[g].wInitTime / 600),
+ (garray[g].wIncrement / 10));
+ }
+ }
+
+ parray[white_player].game = g;
+ parray[white_player].opponent = black_player;
+ parray[white_player].side = WHITE;
+ parray[white_player].promote = QUEEN;
+ parray[black_player].game = g;
+ parray[black_player].opponent = white_player;
+ parray[black_player].side = BLACK;
+ parray[black_player].promote = QUEEN;
+ send_boards(g);
+ MakeFENpos(g, (char *)garray[g].FENstartPos,
+ ARRAY_SIZE(garray[g].FENstartPos));
+ return COM_OK;
+}
+
+PRIVATE int
+accept_match(int p, int p1)
+{
+ char board[50] = { '\0' };
+ char category[50] = { '\0' };
+ char tmp[100] = { '\0' };
+ int bh = 0, pp, pp1;
+ int g, adjourned, foo, which;
+ int wt, winc, bt, binc, rated, white;
+ pending *pend;
+
+ // stop observing when match starts
+ unobserveAll(p);
+ unobserveAll(p1);
+
+ if ((which = player_find_pendfrom(p, p1, PEND_MATCH)) < 0) {
+ pprintf(p, "%s: player_find_pendfrom: error\n", __func__);
+ pprintf(p1, "%s accepted your challenge but a fatal error "
+ "occurred\n", parray[p].name);
+ return COM_FAILED;
+ }
+
+ pend = &parray[p].p_from_list[which];
+ wt = pend->param1;
+ winc = pend->param2;
+ bt = pend->param3;
+ binc = pend->param4;
+ rated = pend->param5;
+
+ strlcpy(category, pend->char1, sizeof category);
+ strlcpy(board, pend->char2, sizeof board);
+
+ white = (pend->param6 == -1 ? -1 : (1 - pend->param6));
+
+ pprintf(p, "You accept the challenge of %s.\n", parray[p1].name);
+ pprintf(p1, "\n%s accepts your challenge.\n", parray[p].name);
+
+ player_remove_request(p, p1, -1);
+ player_remove_request(p1, p, -1);
+
+ while ((which = player_find_pendto(p, -1, -1)) != -1) {
+ foo = parray[p].p_to_list[which].whoto;
+ pprintf_prompt(foo, "\n%s, who was challenging you, "
+ "has joined a match with %s.\n",
+ parray[p].name,
+ parray[p1].name);
+ pprintf(p, "Challenge to %s withdrawn.\n", parray[foo].name);
+ player_remove_request(p, foo, -1);
+ }
+
+ while ((which = player_find_pendto(p1, -1, -1)) != -1) {
+ foo = parray[p1].p_to_list[which].whoto;
+ pprintf_prompt(foo, "\n%s, who was challenging you, "
+ "has joined a match with %s.\n",
+ parray[p1].name,
+ parray[p].name);
+ pprintf(p1, "Challenge to %s withdrawn.\n", parray[foo].name);
+ player_remove_request(p1, foo, -1);
+ }
+
+ while ((which = player_find_pendfrom(p, -1, -1)) != -1) {
+ foo = parray[p].p_from_list[which].whofrom;
+ pprintf_prompt(foo, "\n%s, whom you were challenging, "
+ "has joined a match with %s.\n",
+ parray[p].name,
+ parray[p1].name);
+ pprintf(p, "Challenge from %s removed.\n", parray[foo].name);
+ player_remove_request(foo, p, -1);
+ }
+
+ while ((which = player_find_pendfrom(p1, -1, -1)) != -1) {
+ foo = parray[p1].p_from_list[which].whofrom;
+ pprintf_prompt(foo, "\n%s, whom you were challenging, "
+ "has joined a match with %s.\n",
+ parray[p1].name,
+ parray[p].name);
+ pprintf(p1, "Challenge from %s removed.\n", parray[foo].name);
+ player_remove_request(foo, p1, -1);
+ }
+
+ if (game_isblitz(wt, winc, bt, binc, category, board) == TYPE_WILD &&
+ strcmp(board, "bughouse") == 0) {
+ bh = 1;
+
+ if ((pp = parray[p].partner) >= 0 &&
+ (pp1 = parray[p1].partner) >= 0) {
+ // stop observing when match starts
+ unobserveAll(pp);
+ unobserveAll(pp1);
+
+ pprintf(pp, "\nYour partner accepts the challenge of "
+ "%s.\n", parray[p1].name);
+ pprintf(pp1, "\nYour partner %s's challenge was "
+ "accepted.\n", parray[p].name);
+
+ while ((which = player_find_pendto(pp, -1, -1)) != -1) {
+ foo = parray[pp].p_to_list[which].whoto;
+ pprintf_prompt(foo, "\n%s, who was challenging "
+ "you, has joined a match with %s.\n",
+ parray[pp].name,
+ parray[pp1].name);
+ pprintf(pp, "Challenge to %s withdrawn.\n",
+ parray[foo].name);
+ player_remove_request(pp, foo, -1);
+ }
+
+ while ((which = player_find_pendto(pp1, -1, -1)) !=
+ -1) {
+ foo = parray[pp1].p_to_list[which].whoto;
+ pprintf_prompt(foo, "\n%s, who was challenging "
+ "you, has joined a match with %s.\n",
+ parray[pp1].name,
+ parray[pp].name);
+ pprintf(pp1, "Challenge to %s withdrawn.\n",
+ parray[foo].name);
+ player_remove_request(pp1, foo, -1);
+ }
+
+ while ((which = player_find_pendfrom(pp, -1, -1)) !=
+ -1) {
+ foo = parray[pp].p_from_list[which].whofrom;
+ pprintf_prompt(foo, "\n%s, whom you were "
+ "challenging, has joined a match with %s."
+ "\n",
+ parray[pp].name,
+ parray[pp1].name);
+ pprintf(pp, "Challenge from %s removed.\n",
+ parray[foo].name);
+ player_remove_request(foo, pp, -1);
+ }
+
+ while ((which = player_find_pendfrom(pp1, -1, -1)) !=
+ -1) {
+ foo = parray[pp1].p_from_list[which].whofrom;
+ pprintf_prompt(foo, "\n%s, whom you were "
+ "challenging, has joined a match with %s."
+ "\n",
+ parray[pp1].name,
+ parray[pp].name);
+ pprintf(pp1, "Challenge from %s removed.\n",
+ parray[foo].name);
+ player_remove_request(foo, pp1, -1);
+ }
+ } else {
+ return COM_OK;
+ }
+ }
+
+ g = game_new();
+ adjourned = 0;
+
+ if (game_read(g, p, p1) >= 0) {
+ adjourned = 1;
+ } else if (game_read(g, p1, p) >= 0) {
+ int swap;
+
+ adjourned = 1;
+ swap = p;
+ p = p1;
+ p1 = swap;
+ }
+
+ if (!adjourned) { // No adjourned game, so begin a new game.
+ game_remove(g);
+
+ if (create_new_match(p, p1, wt, winc, bt, binc, rated, category,
+ board, white) != COM_OK) {
+ snprintf(tmp, sizeof tmp, "There was a problem "
+ "creating the new match.\n");
+ pprintf(p, "%s", tmp);
+ pprintf_prompt(p1, "%s", tmp);
+ } else if (bh) {
+ white = (parray[p].side == WHITE ? 0 : 1);
+
+ if (create_new_match(pp, pp1, wt, winc, bt, binc, rated,
+ category, board, white) != COM_OK) {
+ snprintf(tmp, sizeof tmp, "There was a problem "
+ "creating the new match.\n"); // XXX
+ pprintf_prompt(pp, "%s", tmp);
+ pprintf_prompt(pp1, "%s", tmp);
+
+ snprintf(tmp, sizeof tmp, "There was a problem "
+ "creating your partner's match.\n");
+ pprintf(p, "%s", tmp);
+ pprintf_prompt(p1, "%s", tmp);
+ // IanO: abort_game()?
+ } else {
+ int g1 = parray[p].game;
+ int g2 = parray[pp].game;
+
+ garray[g1].link = g2;
+ garray[g2].link = g1;
+
+ snprintf(tmp, sizeof tmp, "\nYour partner is "
+ "playing game %d (%s vs. %s).\n",
+ (g2 + 1),
+ garray[g2].white_name,
+ garray[g2].black_name);
+ pprintf(p, "%s", tmp);
+ pprintf_prompt(p1, "%s", tmp);
+
+ snprintf(tmp, sizeof tmp, "\nYour partner is "
+ "playing game %d (%s vs. %s).\n",
+ (g1 + 1),
+ garray[g1].white_name,
+ garray[g1].black_name);
+ pprintf_prompt(pp, "%s", tmp);
+ pprintf_prompt(pp1, "%s", tmp);
+ }
+ }
+ } else { // resume adjourned game
+ game_delete(p, p1);
+
+ // XXX: must be either
+ if (garray[g].rated != TYPE_UNRATED &&
+ garray[g].rated != TYPE_RATED) {
+ warnx("%s: adjourned game neither rated/unrated",
+ __func__);
+ warnx("%s: %s vs %s", __func__,
+ parray[p].name,
+ parray[p1].name);
+ garray[g].rated = TYPE_UNRATED;
+ }
+
+ snprintf(tmp, sizeof tmp, "{Game %d (%s vs. %s) Continuing "
+ "%s %s match.}\n",
+ (g + 1),
+ parray[p].name,
+ parray[p1].name,
+ rstr[garray[g].rated],
+ bstr[garray[g].type]);
+
+ pprintf(p, "%s", tmp);
+ pprintf(p1, "%s", tmp);
+
+ garray[g].white = p;
+ garray[g].black = p1;
+ garray[g].status = GAME_ACTIVE;
+ garray[g].result = END_NOTENDED;
+ garray[g].startTime = tenth_secs();
+ garray[g].lastMoveTime = garray[g].startTime;
+ garray[g].lastDecTime = garray[g].startTime;
+ parray[p].game = g;
+ parray[p].opponent = p1;
+ parray[p].side = WHITE;
+
+ parray[p1].game = g;
+ parray[p1].opponent = p;
+ parray[p1].side = BLACK;
+
+#ifdef TIMESEAL
+ garray[g].wRealTime = garray[g].wTime * 100;
+ garray[g].bRealTime = garray[g].bTime * 100;
+ garray[g].wTimeWhenReceivedMove = 0;
+ garray[g].bTimeWhenReceivedMove = 0;
+#endif
+
+ send_boards(g);
+ }
+
+ return COM_OK;
+}
+
+PRIVATE void
+print_bughouse(int p, int p1, struct print_bh_context *ctx, char *colorstr[])
+{
+ const int pp = ctx->pp;
+ const int pp1 = ctx->pp1;
+
+ // pp
+ pprintf(pp, "\nYour bughouse partner issuing %s (%s) %s",
+ parray[p].name,
+ ratstrii(GetRating(&parray[p], ctx->type), parray[p].registered),
+ colorstr[ctx->white + 1]);
+ pprintf_highlight(pp, "%s", parray[p1].name);
+ pprintf(pp, " (%s) %s.\n",
+ ratstrii(GetRating(&parray[p1], ctx->type), parray[p1].registered),
+ game_str(ctx->rated, ctx->wt * 60, ctx->winc,
+ ctx->bt * 60, ctx->binc,
+ ctx->category, ctx->board));
+
+ pprintf(pp, "Your game would be ");
+ pprintf_highlight(pp, "%s", parray[pp1].name);
+ pprintf_prompt(pp, " (%s) %s%s (%s) %s.\n",
+ ratstrii(GetRating(&parray[pp1], ctx->type), parray[pp1].registered),
+ colorstr[ctx->white + 1],
+ parray[pp].name,
+ ratstrii(GetRating(&parray[pp], ctx->type), parray[pp].registered),
+ game_str(ctx->rated, ctx->wt * 60, ctx->winc,
+ ctx->bt * 60, ctx->binc,
+ ctx->category, ctx->board));
+
+ if (parray[pp].bell == 1)
+ pprintf_noformat(pp, "\007");
+
+ // pp1
+ pprintf(pp1, "\nYour bughouse partner was challenged ");
+ pprintf_highlight(pp1, "%s", parray[p].name);
+ pprintf(pp1, " (%s) %s",
+ ratstrii(GetRating(&parray[p], ctx->type), parray[p].registered),
+ colorstr[ctx->white + 1]);
+ pprintf(pp1, "%s (%s) %s.\n",
+ parray[p1].name,
+ ratstrii(GetRating(&parray[p1], ctx->type), parray[p1].registered),
+ game_str(ctx->rated, ctx->wt * 60, ctx->winc,
+ ctx->bt * 60, ctx->binc,
+ ctx->category, ctx->board));
+
+ pprintf(pp1, "Your game would be %s (%s) %s",
+ parray[pp1].name,
+ ratstrii(GetRating(&parray[pp1], ctx->type), parray[pp1].registered),
+ colorstr[ctx->white + 1]);
+ pprintf_highlight(pp1, "%s", parray[pp].name);
+ pprintf_prompt(pp1, " (%s) %s.\n",
+ ratstrii(GetRating(&parray[pp], ctx->type), parray[pp].registered),
+ game_str(ctx->rated, ctx->wt * 60, ctx->winc,
+ ctx->bt * 60, ctx->binc,
+ ctx->category, ctx->board));
+
+ if (parray[pp1].bell == 1)
+ pprintf_noformat(pp1, "\007");
+}
+
+PUBLIC int
+com_match(int p, param_list param)
+{
+ char board[100] = { '\0' };
+ char category[100] = { '\0' };
+ char parsebuf[100] = { '\0' };
+ char *adjustr[] = { "", " (adjourned)" };
+ char *colorstr[] = { "", "[black] ", "[white] " };
+ char *val;
+ int adjourned; // adjourned game?
+ int bh = 0, pp, pp1;
+ int binc = -1; // black increment
+ int bt = -1; // black start time
+ int confused = 0;
+ int g; // more adjourned game junk
+ int p1;
+ int pendfrom, pendto;
+ int ppend, p1pend;
+ int rated = -1; // 1 = rated, 0 = unrated
+ int type;
+ int white = -1; // 1 = want white, 0 = want black
+ int winc = -1; // white increment
+ int wt = -1; // white start time
+ textlist *clauses = NULL;
+
+ if (parray[p].game >= 0 && garray[parray[p].game].status ==
+ GAME_EXAMINE) {
+ pprintf(p, "You can't challenge while you are examining a game."
+ "\n");
+ return COM_OK;
+ }
+ if (parray[p].game >= 0) {
+ pprintf(p, "You can't challenge while you are playing a game."
+ "\n");
+ return COM_OK;
+ }
+
+ stolower(param[0].val.word);
+ p1 = player_find_part_login(param[0].val.word);
+
+ if (p1 < 0) {
+ pprintf(p, "No user named \"%s\" is logged in.\n",
+ param[0].val.word);
+ return COM_OK;
+ }
+ if (p1 == p) { // Allowing to match yourself to enter analysis mode
+ ExamineScratch(p, param);
+ return COM_OK;
+ }
+
+ if (parray[p].open == 0) {
+ parray[p].open = 1;
+ pprintf(p, "Setting you open for matches.\n");
+ }
+ if (!parray[p1].open) {
+ pprintf(p, "Player \"%s\" is not open to match requests.\n",
+ parray[p1].name);
+ return COM_OK;
+ }
+ if (parray[p1].game >= 0) {
+ pprintf(p, "Player \"%s\" is involved in another game.\n",
+ parray[p1].name);
+ return COM_OK;
+ }
+
+ // Look for an adjourned game between p and p1
+ g = game_new();
+ adjourned = (game_read(g, p, p1) >= 0 || game_read(g, p1, p) >= 0);
+
+ if (adjourned) {
+ type = garray[g].type;
+ wt = garray[g].wInitTime / 600;
+ bt = garray[g].bInitTime / 600;
+ winc = garray[g].wIncrement / 10;
+ binc = garray[g].bIncrement / 10;
+ rated = garray[g].rated;
+ }
+
+ game_remove(g);
+ pendto = player_find_pendto(p, p1, PEND_MATCH);
+ pendfrom = player_find_pendfrom(p, p1, PEND_MATCH);
+ category[0] = '\0';
+ board[0] = '\0';
+
+ if (!adjourned) {
+ if (in_list(p1, L_NOPLAY, parray[p].login)) {
+ pprintf(p, "You are on %s's noplay list.\n",
+ parray[p1].name);
+ return COM_OK;
+ }
+ if (player_censored(p1, p)) {
+ pprintf(p, "Player \"%s\" is censoring you.\n",
+ parray[p1].name);
+ return COM_OK;
+ }
+ if (player_censored(p, p1)) {
+ pprintf(p, "You are censoring \"%s\".\n",
+ parray[p1].name);
+ return COM_OK;
+ }
+
+ if (param[1].type != TYPE_NULL) {
+ int numba; // temp for atoi()
+
+ val = param[1].val.string;
+
+ while (!confused &&
+ sscanf(val, " %99s", parsebuf) == 1) {
+
+ val = eatword(eatwhite(val));
+
+ if (category[0] != '\0' && board[0] == '\0') {
+ strlcpy(board, parsebuf, sizeof board);
+ } else if (isdigit(*parsebuf)) {
+ if ((numba = atoi(parsebuf)) < 0) {
+ pprintf(p, "You can't specify "
+ "negative time controls."
+ "\n");
+ return COM_OK;
+ } else if (numba > 1000) {
+ pprintf(p, "You can't specify "
+ "time or inc above 1000."
+ "\n");
+ return COM_OK;
+ } else if (wt == -1) {
+ wt = numba;
+ } else if (winc == -1) {
+ winc = numba;
+ } else if (bt == -1) {
+ bt = numba;
+ } else if (binc == -1) {
+ binc = numba;
+ } else {
+ confused = 1;
+ }
+ } else if (strstr("rated", parsebuf) != NULL) {
+ if (rated == -1)
+ rated = 1;
+ else
+ confused = 1;
+ } else if (strstr("unrated", parsebuf) != NULL) {
+ if (rated == -1)
+ rated = 0;
+ else
+ confused = 1;
+ } else if (strstr("white", parsebuf) != NULL) {
+ if (white == -1)
+ white = 1;
+ else
+ confused = 1;
+ } else if (strstr("black", parsebuf) != NULL) {
+ if (white == -1)
+ white = 0;
+ else
+ confused = 1;
+ } else if (category[0] == '\0') {
+ strlcpy(category, parsebuf,
+ sizeof category);
+ } else {
+ confused = 1;
+ }
+ } /* while */
+
+ if (confused) {
+ pprintf(p, "Can't interpret %s in match "
+ "command.\n", parsebuf);
+ return COM_OK;
+ }
+ }
+
+ rated = (rated == -1 ? parray[p].rated : rated) &&
+ parray[p1].registered && parray[p].registered;
+
+ if (winc == -1)
+ winc = (wt == -1 ? parray[p].d_inc : 0);
+ if (wt == -1)
+ wt = parray[p].d_time;
+ if (bt == -1)
+ bt = wt;
+ if (binc == -1)
+ binc = winc;
+
+ if (!strcmp(category, "bughouse")) { // save mentioning wild
+ strlcpy(board, "bughouse", sizeof board);
+ strlcpy(category, "wild", sizeof category);
+ }
+
+ if (category[0] && !board[0]) {
+ pprintf(p, "You must specify a board and a category."
+ "\n");
+ return COM_OK;
+ }
+
+ if (category[0]) {
+ char fname[MAX_FILENAME_SIZE] = { '\0' };
+
+ (void) snprintf(fname, sizeof fname, "%s/%s/%s",
+ board_dir, category, board);
+
+ if (!file_exists(fname)) {
+ pprintf(p, "No such category/board: %s/%s\n",
+ category, board);
+ return COM_OK;
+ }
+ }
+
+ if (pendfrom < 0 && parray[p1].ropen == 0 && rated !=
+ parray[p1].rated) {
+ pprintf(p, "%s only wants to play %s games.\n",
+ parray[p1].name,
+ rstr[parray[p1].rated]);
+ pprintf_highlight(p1, "Ignoring");
+ pprintf(p1, " %srated match request from %s.\n",
+ (parray[p1].rated ? "un" : ""),
+ parray[p].name);
+
+ return COM_OK;
+ }
+
+ type = game_isblitz(wt, winc, bt, binc, category, board);
+
+ if (rated && type == TYPE_WILD && !strcmp(board, "bughouse")) {
+ pprintf(p, "Game is bughouse - "
+ "reverting to unrated\n");
+ rated = 0; // will need to kill wild and make
+ // TYPE_BUGHOUSE
+ }
+ if (rated && type == TYPE_NONSTANDARD) {
+ pprintf(p, "Game is non-standard - "
+ "reverting to unrated\n");
+ rated = 0;
+ }
+ if (rated && type == TYPE_UNTIMED) {
+ pprintf(p, "Game is untimed - "
+ "reverting to unrated\n");
+ rated = 0;
+ }
+
+ // Now check formula.
+ if ((pendfrom < 0 || param[1].type != TYPE_NULL) &&
+ !GameMatchesFormula(p, p1, wt, winc, bt, binc, rated, type,
+ &clauses)) {
+ pprintf(p, "Match request does not fit formula for "
+ "%s:\n", parray[p1].name);
+ pprintf(p, "%s's formula: %s\n",
+ parray[p1].name,
+ parray[p1].formula);
+
+ ShowClauses(p, p1, clauses);
+ ClearTextList(clauses);
+
+ pprintf_highlight(p1, "Ignoring");
+ pprintf_prompt(p1, " (formula): %s (%d) %s (%d) %s.\n",
+ parray[p].name,
+ GetRating(&parray[p], type),
+ parray[p1].name,
+ GetRating(&parray[p1], type),
+ game_str(rated, wt * 60, winc, bt * 60, binc,
+ category, board));
+
+ return COM_OK;
+ }
+
+ if (type == TYPE_WILD && strcmp(board, "bughouse") == 0) {
+ bh = 1;
+ pp = parray[p].partner;
+ pp1 = parray[p1].partner;
+
+ if (pp < 0) {
+ pprintf(p, "You have no partner for bughouse."
+ "\n");
+ return COM_OK;
+ }
+ if (pp1 < 0) {
+ pprintf(p, "Your opponent has no partner for "
+ "bughouse.\n");
+ return COM_OK;
+ }
+ if (pp == pp1) {
+ pprintf(p, "You and your opponent both chose "
+ "the same partner!\n");
+ return COM_OK;
+ }
+ if (pp == p1 || pp1 == p) {
+ pprintf(p, "You and your opponent can't choose "
+ "each other as partners!\n");
+ return COM_OK;
+ }
+ if (parray[pp].partner != p) {
+ pprintf(p, "Your partner hasn't chosen you as "
+ "his partner!\n");
+ return COM_OK;
+ }
+ if (parray[pp1].partner != p1) {
+ pprintf(p, "Your opponent's partner hasn't "
+ "chosen your opponent as his partner!\n");
+ return COM_OK;
+ }
+ if (!parray[pp].open || parray[pp].game >= 0) {
+ pprintf(p, "Your partner isn't open to play "
+ "right now.\n");
+ return COM_OK;
+ }
+ if (!parray[pp1].open || parray[pp1].game >= 0) {
+ pprintf(p, "Your opponent's partner isn't "
+ "open to play right now.\n");
+ return COM_OK;
+ }
+
+ /*
+ * Bypass NOPLAY lists, censored lists,
+ * ratedness, privacy, and formula for
+ * now. Active challenger/ee will determine
+ * these.
+ */
+ }
+
+ // Ok match offer will be made
+ } // !adjourned
+
+ if (pendto >= 0) {
+ pprintf(p, "Updating offer already made to \"%s\".\n",
+ parray[p1].name);
+ }
+
+ if (pendfrom >= 0) {
+ if (pendto >= 0) {
+ pprintf(p, "Internal error\n");
+ fprintf(stderr, "FICS: This shouldn't happen. "
+ "You can't have a match pending from and to the "
+ "same person.\n");
+ return COM_OK;
+ }
+
+ if (adjourned ||
+ (wt == parray[p].p_from_list[pendfrom].param1 &&
+ winc == parray[p].p_from_list[pendfrom].param2 &&
+ bt == parray[p].p_from_list[pendfrom].param3 &&
+ binc == parray[p].p_from_list[pendfrom].param4 &&
+ rated == parray[p].p_from_list[pendfrom].param5 &&
+ (white == -1 ||
+ white + parray[p].p_from_list[pendfrom].param6 == 1) &&
+ !strcmp(category, parray[p].p_from_list[pendfrom].char1) &&
+ !strcmp(board, parray[p].p_from_list[pendfrom].char2))) {
+ // Identical match - should accept!
+ accept_match(p, p1);
+ return COM_OK;
+ } else {
+ player_remove_pendfrom(p, p1, PEND_MATCH);
+ player_remove_pendto(p1, p, PEND_MATCH);
+ }
+ }
+
+ if (pendto < 0) {
+ if ((ppend = player_new_pendto(p)) < 0) {
+ pprintf(p, "Sorry you can't have any more pending "
+ "matches.\n");
+ return COM_OK;
+ }
+
+ if ((p1pend = player_new_pendfrom(p1)) < 0) {
+ pprintf(p, "Sorry %s can't have any more pending "
+ "matches.\n", parray[p1].name);
+ parray[p].num_to = parray[p].num_to - 1;
+ return COM_OK;
+ }
+ } else {
+ ppend = pendto;
+ p1pend = player_find_pendfrom(p1, p, PEND_MATCH);
+ }
+
+ parray[p].p_to_list[ppend].param1 = wt;
+ parray[p].p_to_list[ppend].param2 = winc;
+ parray[p].p_to_list[ppend].param3 = bt;
+ parray[p].p_to_list[ppend].param4 = binc;
+ parray[p].p_to_list[ppend].param5 = rated;
+ parray[p].p_to_list[ppend].param6 = white;
+
+ strlcpy(parray[p].p_to_list[ppend].char1, category,
+ sizeof(parray[p].p_to_list[ppend].char1));
+ strlcpy(parray[p].p_to_list[ppend].char2, board,
+ sizeof(parray[p].p_to_list[ppend].char2));
+
+ parray[p].p_to_list[ppend].type = PEND_MATCH;
+ parray[p].p_to_list[ppend].whoto = p1;
+ parray[p].p_to_list[ppend].whofrom = p;
+
+ parray[p1].p_from_list[p1pend].param1 = wt;
+ parray[p1].p_from_list[p1pend].param2 = winc;
+ parray[p1].p_from_list[p1pend].param3 = bt;
+ parray[p1].p_from_list[p1pend].param4 = binc;
+ parray[p1].p_from_list[p1pend].param5 = rated;
+ parray[p1].p_from_list[p1pend].param6 = white;
+
+ strlcpy(parray[p1].p_from_list[p1pend].char1, category,
+ sizeof(parray[p1].p_from_list[p1pend].char1));
+ strlcpy(parray[p1].p_from_list[p1pend].char2, board,
+ sizeof(parray[p1].p_from_list[p1pend].char2));
+
+ parray[p1].p_from_list[p1pend].type = PEND_MATCH;
+ parray[p1].p_from_list[p1pend].whoto = p1;
+ parray[p1].p_from_list[p1pend].whofrom = p;
+
+ if (pendfrom >= 0) {
+ pprintf(p, "Declining offer from %s and offering new match "
+ "parameters.\n", parray[p1].name);
+ pprintf(p1, "\n%s declines your match offer a match with these "
+ "parameters:", parray[p].name);
+ }
+
+ if (pendto >= 0) {
+ pprintf(p, "Updating match request to: ");
+ pprintf(p1, "\n%s updates the match request.\n", parray[p].name);
+ } else {
+ pprintf(p, "Issuing: ");
+ pprintf(p1, "\n"); // XXX: 'parray[p].name'
+ }
+
+ pprintf(p, "%s (%s) %s",
+ parray[p].name,
+ ratstrii(GetRating(&parray[p], type), parray[p].registered),
+ colorstr[white + 1]);
+ pprintf_highlight(p, "%s", parray[p1].name);
+ pprintf(p, " (%s) %s%s.\n",
+ ratstrii(GetRating(&parray[p1], type), parray[p1].registered),
+ game_str(rated, wt * 60, winc, bt * 60, binc, category, board),
+ adjustr[adjourned]);
+ pprintf(p1, "Challenge: ");
+ pprintf_highlight(p1, "%s", parray[p].name);
+ pprintf(p1, " (%s) %s",
+ ratstrii(GetRating(&parray[p], type), parray[p].registered),
+ colorstr[white + 1]);
+ pprintf(p1, "%s (%s) %s%s.\n",
+ parray[p1].name,
+ ratstrii(GetRating(&parray[p1], type), parray[p1].registered),
+ game_str(rated, wt * 60, winc, bt * 60, binc, category, board),
+ adjustr[adjourned]);
+
+ if (parray[p1].bell == 1)
+ pprintf_noformat(p1, "\007");
+
+ if (bh) {
+ struct print_bh_context ctx = {
+ .pp = pp,
+ .pp1 = pp1,
+ .rated = rated,
+ .type = type,
+ .white = white,
+ .board = &board[0],
+ .category = &category[0],
+ .binc = binc,
+ .bt = bt,
+ .winc = winc,
+ .wt = wt,
+ };
+
+ if (ctx.white >= 0)
+ print_bughouse(p, p1, &ctx, colorstr);
+ else
+ warnx("%s: cannot print bughouse", __func__);
+ }
+
+ if (in_list(p, L_COMPUTER, parray[p].name)) {
+ pprintf(p1, "--** %s is a ", parray[p].name);
+ pprintf_highlight(p1, "computer");
+ pprintf(p1, " **--\n");
+ }
+ if (in_list(p, L_COMPUTER, parray[p1].name)) {
+ pprintf(p, "--** %s is a ", parray[p1].name);
+ pprintf_highlight(p, "computer");
+ pprintf(p, " **--\n");
+ }
+ if (in_list(p, L_ABUSER, parray[p].name)) {
+ pprintf(p1, "--** %s is in the ", parray[p].name);
+ pprintf_highlight(p1, "abuser");
+ pprintf(p1, " list **--\n");
+ }
+ if (in_list(p, L_ABUSER, parray[p1].name)) {
+ pprintf(p, "--** %s is in the ", parray[p1].name);
+ pprintf_highlight(p, "abuser");
+ pprintf(p, " list **--\n");
+ }
+
+ if (rated) {
+ double newsterr;
+ int win, draw, loss;
+
+ rating_sterr_delta(p1, p, type, time(NULL), RESULT_WIN, &win,
+ &newsterr);
+ rating_sterr_delta(p1, p, type, time(NULL), RESULT_DRAW, &draw,
+ &newsterr);
+ rating_sterr_delta(p1, p, type, time(NULL), RESULT_LOSS, &loss,
+ &newsterr);
+
+ pprintf(p1, "Your %s rating will change: "
+ "Win: %s%d, Draw: %s%d, Loss: %s%d\n",
+ bstr[type],
+ (win >= 0 ? "+" : ""), win,
+ (draw >= 0 ? "+" : ""), draw,
+ (loss >= 0 ? "+" : ""), loss);
+ pprintf(p1, "Your new RD will be %5.1f\n", newsterr);
+
+ rating_sterr_delta(p, p1, type, time(NULL), RESULT_WIN, &win,
+ &newsterr);
+ rating_sterr_delta(p, p1, type, time(NULL), RESULT_DRAW, &draw,
+ &newsterr);
+ rating_sterr_delta(p, p1, type, time(NULL), RESULT_LOSS, &loss,
+ &newsterr);
+
+ pprintf(p, "Your %s rating will change: "
+ "Win: %s%d, Draw: %s%d, Loss: %s%d\n",
+ bstr[type],
+ (win >= 0 ? "+" : ""), win,
+ (draw >= 0 ? "+" : ""), draw,
+ (loss >= 0 ? "+" : ""), loss);
+ pprintf(p, "Your new RD will be %5.1f\n", newsterr);
+ }
+
+ pprintf_prompt(p1, "You can \"accept\" or \"decline\", or propose "
+ "different parameters.\n");
+ return COM_OK;
+}
+
+PUBLIC int
+com_accept(int p, param_list param)
+{
+ int acceptNum = -1;
+ int from;
+ int p1;
+ int type = -1;
+
+ if (parray[p].num_from == 0) {
+ pprintf(p, "You have no offers to accept.\n");
+ return COM_OK;
+ }
+
+ if (param[0].type == TYPE_NULL) {
+ if (parray[p].num_from != 1) {
+ pprintf(p, "You have more than one offer to accept.\n"
+ "Use \"pending\" to see them and \"accept n\" to "
+ "choose which one.\n");
+ return COM_OK;
+ }
+
+ acceptNum = 0;
+ } else if (param[0].type == TYPE_INT) {
+ if (param[0].val.integer < 1 ||
+ param[0].val.integer > parray[p].num_from) {
+ pprintf(p, "Out of range. Use \"pending\" to see "
+ "the list of offers.\n");
+ return COM_OK;
+ }
+
+ acceptNum = (param[0].val.integer - 1);
+ } else if (param[0].type == TYPE_WORD) {
+ if (!strcmp(param[0].val.word, "draw")) {
+ type = PEND_DRAW;
+ } else if (!strcmp(param[0].val.word, "pause")) {
+ type = PEND_PAUSE;
+ } else if (!strcmp(param[0].val.word, "adjourn")) {
+ type = PEND_ADJOURN;
+ } else if (!strcmp(param[0].val.word, "abort")) {
+ type = PEND_ABORT;
+ } else if (!strcmp(param[0].val.word, "takeback")) {
+ type = PEND_TAKEBACK;
+ } else if (!strcmp(param[0].val.word, "simmatch")) {
+ type = PEND_SIMUL;
+ } else if (!strcmp(param[0].val.word, "switch")) {
+ type = PEND_SWITCH;
+ } else if (!strcmp(param[0].val.word, "partner")) {
+ type = PEND_PARTNER;
+ }
+
+ if (type >= 0) {
+ acceptNum = player_find_pendfrom(p, -1, type);
+
+ if (acceptNum < 0) {
+ pprintf(p, "There are no pending %s offers.\n",
+ param[0].val.word);
+ return COM_OK;
+ }
+ } else { // Word must be a name
+ p1 = player_find_part_login(param[0].val.word);
+
+ if (p1 < 0) {
+ pprintf(p, "No user named \"%s\" is logged "
+ "in.\n", param[0].val.word);
+ return COM_OK;
+ }
+
+ if ((acceptNum = player_find_pendfrom(p, p1, -1)) < 0) {
+ pprintf(p, "There are no pending offers from "
+ "%s.\n", parray[p1].name);
+ return COM_OK;
+ }
+ }
+ }
+
+ if (acceptNum < 0 ||
+ acceptNum >= (int)ARRAY_SIZE(parray[0].p_from_list)) {
+ pprintf(p, "Accept number out-of-bounds!\n");
+ return COM_FAILED;
+ }
+
+ from = parray[p].p_from_list[acceptNum].whofrom;
+
+ switch (parray[p].p_from_list[acceptNum].type) {
+ case PEND_MATCH:
+ accept_match(p, from);
+ return COM_OK;
+ case PEND_DRAW:
+ pcommand(p, "draw");
+ break;
+ case PEND_PAUSE:
+ pcommand(p, "pause");
+ break;
+ case PEND_ABORT:
+ pcommand(p, "abort");
+ break;
+ case PEND_TAKEBACK:
+ pcommand(p, "takeback %d",
+ parray[p].p_from_list[acceptNum].param1);
+ break;
+ case PEND_SIMUL:
+ pcommand(p, "simmatch %s", parray[from].name);
+ break;
+ case PEND_SWITCH:
+ pcommand(p, "switch");
+ break;
+ case PEND_ADJOURN:
+ pcommand(p, "adjourn");
+ break;
+ case PEND_PARTNER:
+ pcommand(p, "partner %s", parray[from].name);
+ break;
+ }
+
+ return COM_OK_NOPROMPT;
+}
+
+PRIVATE int
+WordToOffer(int p, char *Word, int *type, int *p1)
+{
+ /*
+ * Convert draw adjourn match takeback abort pause simmatch
+ * switch partner or <name> to offer type.
+ */
+
+ if (!strcmp(Word, "match")) {
+ *type = PEND_MATCH;
+ } else if (!strcmp(Word, "draw")) {
+ *type = PEND_DRAW;
+ } else if (!strcmp(Word, "pause")) {
+ *type = PEND_PAUSE;
+ } else if (!strcmp(Word, "abort")) {
+ *type = PEND_ABORT;
+ } else if (!strcmp(Word, "takeback")) {
+ *type = PEND_TAKEBACK;
+ } else if (!strcmp(Word, "adjourn")) {
+ *type = PEND_ADJOURN;
+ } else if (!strcmp(Word, "switch")) {
+ *type = PEND_SWITCH;
+ } else if (!strcmp(Word, "simul")) {
+ *type = PEND_SIMUL;
+ } else if (!strcmp(Word, "partner")) {
+ *type = PEND_PARTNER;
+ } else if (!strcmp(Word, "all")) {
+ ;
+ } else {
+ if ((*p1 = player_find_part_login(Word)) < 0) {
+ pprintf(p, "No user named \"%s\" is logged in.\n",
+ Word);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+PUBLIC int
+com_decline(int p, param_list param)
+{
+ int count;
+ int declineNum;
+ int p1 = -1;
+ int type = -1;
+
+ if (parray[p].num_from == 0) {
+ pprintf(p, "You have no pending offers from other players.\n");
+ return COM_OK;
+ }
+
+ if (param[0].type == TYPE_NULL) {
+ if (parray[p].num_from == 1) {
+ p1 = parray[p].p_from_list[0].whofrom;
+ type = parray[p].p_from_list[0].type;
+ } else {
+ pprintf(p, "You have more than one pending offer. "
+ "Please specify which one\nyou wish to decline.\n"
+ "'Pending' will give you the list.\n");
+ return COM_OK;
+ }
+ } else {
+ if (param[0].type == TYPE_WORD) {
+ if (!WordToOffer(p, param[0].val.word, &type, &p1))
+ return COM_OK;
+ } else { // Must be an integer
+ declineNum = param[0].val.integer - 1;
+
+ if (declineNum >= parray[p].num_from || declineNum < 0) {
+ pprintf(p, "Invalid offer number. Must be "
+ "between 1 and %d.\n", parray[p].num_from);
+ return COM_OK;
+ }
+
+ p1 = parray[p].p_from_list[declineNum].whofrom;
+ type = parray[p].p_from_list[declineNum].type;
+ }
+ }
+
+ if ((count = player_decline_offers(p, p1, type)) != 1)
+ pprintf(p, "%d offers declined\n", count);
+ return COM_OK;
+}
+
+PUBLIC int
+com_withdraw(int p, param_list param)
+{
+ int count;
+ int p1 = -1;
+ int type = -1;
+ int withdrawNum;
+
+ if (parray[p].num_to == 0) {
+ pprintf(p, "You have no pending offers to other players.\n");
+ return COM_OK;
+ }
+
+ if (param[0].type == TYPE_NULL) {
+ if (parray[p].num_to == 1) {
+ p1 = parray[p].p_to_list[0].whoto;
+ type = parray[p].p_to_list[0].type;
+ } else {
+ pprintf(p, "You have more than one pending offer. "
+ "Please specify which one\nyou wish to withdraw.\n"
+ "'Pending' will give you the list.\n");
+ return COM_OK;
+ }
+ } else {
+ if (param[0].type == TYPE_WORD) {
+ if (!WordToOffer(p, param[0].val.word, &type, &p1))
+ return COM_OK;
+ } else { // Must be an integer
+ withdrawNum = param[0].val.integer - 1;
+
+ if (withdrawNum >= parray[p].num_to || withdrawNum < 0) {
+ pprintf(p, "Invalid offer number. Must be "
+ "between 1 and %d.\n", parray[p].num_to);
+ return COM_OK;
+ }
+
+ p1 = parray[p].p_to_list[withdrawNum].whoto;
+ type = parray[p].p_to_list[withdrawNum].type;
+ }
+ }
+
+ if ((count = player_withdraw_offers(p, p1, type)) != 1)
+ pprintf(p, "%d offers withdrawn\n", count);
+ return COM_OK;
+}
+
+PUBLIC int
+com_pending(int p, param_list param)
+{
+ int i;
+
+ if (!parray[p].num_to) {
+ pprintf(p, "There are no offers pending TO other players.\n");
+ } else {
+ pprintf(p, "Offers TO other players:\n");
+
+ for (i = 0; i < parray[p].num_to; i++) {
+ pprintf(p, " ");
+ player_pend_print(p, &parray[p].p_to_list[i]);
+ }
+ }
+
+ if (!parray[p].num_from) {
+ pprintf(p, "\nThere are no offers pending FROM other players."
+ "\n");
+ } else {
+ pprintf(p, "\nOffers FROM other players:\n");
+
+ for (i = 0; i < parray[p].num_from; i++) {
+ pprintf(p, " %d: ", i + 1);
+ player_pend_print(p, &parray[p].p_from_list[i]);
+ }
+
+ pprintf(p, "\nIf you wish to accept any of these offers type "
+ "'accept n'\nor just 'accept' if there is only one offer."
+ "\n");
+ }
+
+ return COM_OK;
+}
diff --git a/FICS/matchproc.h b/FICS/matchproc.h
new file mode 100644
index 0000000..ac9d0b4
--- /dev/null
+++ b/FICS/matchproc.h
@@ -0,0 +1,39 @@
+/* matchproc.h
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name yy/mm/dd Change
+ hersco 95/07/24 Created
+ Markus Uhlin 24/03/29 Revised
+*/
+
+#ifndef _MATCHPROC_H
+#define _MATCHPROC_H
+
+#include "command.h" /* param_list */
+
+extern int com_accept(int, param_list);
+extern int com_decline(int, param_list);
+extern int com_match(int, param_list);
+extern int com_pending(int, param_list);
+extern int com_withdraw(int, param_list);
+extern int create_new_match(int, int, int, int, int, int, int, char *,
+ char *, int);
+
+#endif /* _MATCHPROC_H */
diff --git a/FICS/maxxes-utils.c b/FICS/maxxes-utils.c
new file mode 100644
index 0000000..7db8369
--- /dev/null
+++ b/FICS/maxxes-utils.c
@@ -0,0 +1,46 @@
+// SPDX-FileCopyrightText: 2024 Markus Uhlin <maxxe@rpblc.net>
+// SPDX-License-Identifier: ISC
+
+#include <err.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#if __linux__
+#include <bsd/string.h>
+#endif
+
+#include "maxxes-utils.h"
+
+void
+snprintf_trunc_chk(const char *file, const long int line,
+ char *str, size_t size, const char *format, ...)
+{
+ int ret;
+ va_list ap;
+
+ va_start(ap, format);
+ ret = vsnprintf(str, size, format, ap);
+ va_end(ap);
+
+ if (ret < 0 || (size_t)ret >= size)
+ warnx("%s:%ld: warning: vsnprintf() truncated", file, line);
+}
+
+void
+strlcpy_trunc_chk(char *dst, const char *src, size_t dstsize,
+ const char *file,
+ const long int line)
+{
+ if (strlcpy(dst, src, dstsize) >= dstsize)
+ warnx("%s:%ld: warning: strlcpy() truncated", file, line);
+}
+
+void
+strlcat_trunc_chk(char *dst, const char *src, size_t dstsize,
+ const char *file,
+ const long int line)
+{
+ if (strlcat(dst, src, dstsize) >= dstsize)
+ warnx("%s:%ld: warning: strlcat() truncated", file, line);
+}
diff --git a/FICS/maxxes-utils.h b/FICS/maxxes-utils.h
new file mode 100644
index 0000000..667151f
--- /dev/null
+++ b/FICS/maxxes-utils.h
@@ -0,0 +1,22 @@
+#ifndef MAXXES_UTILITIES_H
+#define MAXXES_UTILITIES_H
+
+#include <stddef.h>
+
+#include "common.h"
+
+#define msnprintf(p_str, p_size, ...) \
+ snprintf_trunc_chk(__FILE__, __LINE__, (p_str), (p_size), __VA_ARGS__)
+#define mstrlcpy(p_dst, p_src, p_dstsize) \
+ strlcpy_trunc_chk((p_dst), (p_src), (p_dstsize), __FILE__, __LINE__)
+#define mstrlcat(p_dst, p_src, p_dstsize) \
+ strlcat_trunc_chk((p_dst), (p_src), (p_dstsize), __FILE__, __LINE__)
+
+void snprintf_trunc_chk(const char *file, const long int line,
+ char *str, size_t size, const char *format, ...) PRINTFLIKE(5);
+void strlcpy_trunc_chk(char *dst, const char *src, size_t dstsize,
+ const char *, const long int);
+void strlcat_trunc_chk(char *dst, const char *src, size_t dstsize,
+ const char *, const long int);
+
+#endif
diff --git a/FICS/movecheck.c b/FICS/movecheck.c
new file mode 100644
index 0000000..5f607de
--- /dev/null
+++ b/FICS/movecheck.c
@@ -0,0 +1,1608 @@
+/* movecheck.c
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 23/12/14 Fixed compiler warnings
+ Markus Uhlin 23/12/17 Fixed compiler warnings
+ Markus Uhlin 23/12/24 Fixed dead assignment
+ Markus Uhlin 24/05/05 Refactored and reformatted all
+ functions.
+ Markus Uhlin 25/03/21 Fixed out-of-bounds array access
+ in has_legal_move().
+*/
+
+#include "stdinclude.h"
+
+#include "algcheck.h"
+#include "board.h"
+#include "common.h"
+#include "gamedb.h"
+#include "movecheck.h"
+#include "network.h"
+#include "playerdb.h"
+#include "utils.h"
+
+#if __linux__
+#include <bsd/string.h>
+#endif
+
+/*
+ * Simply tests if the input string is a move or not. If it matches
+ * patterns below.
+ *
+ * Add to this list as you improve the move parser:
+ * MS_COMP e2e4
+ * MS_COMPDASH e2-e4
+ * MS_CASTLE o-o, o-o-o
+ * Not done yet:
+ * MS_ALG e4, Nd5 Ncd5
+ */
+PUBLIC int
+is_move(char *mstr)
+{
+ int len = strlen(mstr);
+
+ if ((len > 3) && (mstr[len - 2] == '='))
+ len -= 2;
+
+ if (len == 4) { // Test for e2e4
+ if (isfile(mstr[0]) && isrank(mstr[1]) &&
+ isfile(mstr[2]) && isrank(mstr[3])) {
+ return MS_COMP;
+ }
+ }
+
+ if (len == 5) { // Test for e2-e4
+ if (isfile(mstr[0]) &&
+ isrank(mstr[1]) &&
+ (mstr[2] == '-') &&
+ isfile(mstr[3]) &&
+ isrank(mstr[4]))
+ return MS_COMPDASH;
+ }
+
+ if (len == 3) { // Test for o-o
+ if ((mstr[0] == 'o') && (mstr[1] == '-') && (mstr[2] == 'o'))
+ return MS_KCASTLE;
+ if ((mstr[0] == 'O') && (mstr[1] == '-') && (mstr[2] == 'O'))
+ return MS_KCASTLE;
+ if ((mstr[0] == '0') && (mstr[1] == '-') && (mstr[2] == '0'))
+ return MS_KCASTLE;
+ }
+
+ if (len == 2) { // Test for oo
+ if ((mstr[0] == 'o') && (mstr[1] == 'o'))
+ return MS_KCASTLE;
+ if ((mstr[0] == 'O') && (mstr[1] == 'O'))
+ return MS_KCASTLE;
+ if ((mstr[0] == '0') && (mstr[1] == '0'))
+ return MS_KCASTLE;
+ }
+
+ if (len == 5) { // Test for o-o-o
+ if ((mstr[0] == 'o') && (mstr[1] == '-') && (mstr[2] == 'o') &&
+ (mstr[3] == '-') && (mstr[4] == 'o'))
+ return MS_QCASTLE;
+ if ((mstr[0] == 'O') && (mstr[1] == '-') && (mstr[2] == 'O') &&
+ (mstr[3] == '-') && (mstr[4] == 'O'))
+ return MS_QCASTLE;
+ if ((mstr[0] == '0') && (mstr[1] == '-') && (mstr[2] == '0') &&
+ (mstr[3] == '-') && (mstr[4] == '0'))
+ return MS_QCASTLE;
+ }
+
+ if (len == 3) { // Test for ooo
+ if ((mstr[0] == 'o') && (mstr[1] == 'o') && (mstr[2] == 'o'))
+ return MS_QCASTLE;
+ if ((mstr[0] == 'O') && (mstr[1] == 'O') && (mstr[2] == 'O'))
+ return MS_QCASTLE;
+ if ((mstr[0] == '0') && (mstr[1] == '0') && (mstr[2] == '0'))
+ return MS_QCASTLE;
+ }
+
+ return alg_is_move(mstr);
+}
+
+PUBLIC int
+NextPieceLoop(board_t b, int *f, int *r, int color)
+{
+ while (1) {
+ (*r) = (*r) + 1;
+
+ if (*r > 7) {
+ *r = 0;
+ *f = *f + 1;
+
+ if (*f > 7)
+ break;
+ }
+
+ if ((b[*f][*r] != NOPIECE) && iscolor(b[*f][*r], color))
+ return 1;
+ }
+
+ return 0;
+}
+
+PUBLIC int
+InitPieceLoop(board_t b, int *f, int *r, int color)
+{
+ *f = 0;
+ *r = -1;
+ return 1;
+}
+
+PRIVATE int
+legal_pawn_move(game_state_t *gs, int ff, int fr, int tf, int tr)
+{
+ if (ff == tf) {
+ if (gs->board[tf][tr] != NOPIECE)
+ return 0;
+
+ if (gs->onMove == WHITE) {
+ if (tr - fr == 1)
+ return 1;
+ if ((fr == 1) && (tr - fr == 2) &&
+ gs->board[ff][2] == NOPIECE)
+ return 1;
+ } else {
+ if (fr - tr == 1)
+ return 1;
+ if ((fr == 6) && (fr - tr == 2) &&
+ gs->board[ff][5] == NOPIECE)
+ return 1;
+ }
+
+ return 0;
+ }
+
+ if (ff != tf) { /* Capture ? */
+ if ((ff - tf != 1) && (tf - ff != 1))
+ return 0;
+ if ((fr - tr != 1) && (tr - fr != 1))
+ return 0;
+
+ if (gs->onMove == WHITE) {
+ if (fr > tr)
+ return 0;
+ if ((gs->board[tf][tr] != NOPIECE) &&
+ iscolor(gs->board[tf][tr], BLACK))
+ return 1;
+ if (gs->ep_possible[0][ff] == 1) {
+ if ((tf == ff + 1) &&
+ (gs->board[ff + 1][fr] == B_PAWN))
+ return 1;
+ } else if (gs->ep_possible[0][ff] == -1) {
+ if ((tf == ff - 1) &&
+ (gs->board[ff - 1][fr] == B_PAWN))
+ return 1;
+ }
+ } else {
+ if (tr > fr)
+ return 0;
+ if ((gs->board[tf][tr] != NOPIECE) &&
+ iscolor(gs->board[tf][tr], WHITE))
+ return 1;
+ if (gs->ep_possible[1][ff] == 1) {
+ if ((tf == ff + 1) &&
+ (gs->board[ff + 1][fr] == W_PAWN))
+ return 1;
+ } else if (gs->ep_possible[1][ff] == -1) {
+ if ((tf == ff - 1) &&
+ (gs->board[ff - 1][fr] == W_PAWN))
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+PRIVATE int
+legal_knight_move(game_state_t *gs, int ff, int fr, int tf, int tr)
+{
+ int dx, dy;
+
+ dx = ff - tf;
+ dy = fr - tr;
+
+ if ((dx == 2) || (dx == -2)) {
+ if ((dy == -1) || (dy == 1))
+ return 1;
+ }
+ if ((dy == 2) || (dy == -2)) {
+ if ((dx == -1) || (dx == 1))
+ return 1;
+ }
+ return 0;
+}
+
+PRIVATE int
+legal_bishop_move(game_state_t *gs, int ff, int fr, int tf, int tr)
+{
+ int count;
+ int dx, dy, x, y;
+ int incx, incy;
+ int startx, starty;
+
+ if (ff > tf) {
+ dx = ff - tf;
+ incx = -1;
+ } else {
+ dx = tf - ff;
+ incx = 1;
+ }
+ startx = ff + incx;
+ if (fr > tr) {
+ dy = fr - tr;
+ incy = -1;
+ } else {
+ dy = tr - fr;
+ incy = 1;
+ }
+ starty = fr + incy;
+ if (dx != dy)
+ return 0; // Not diagonal
+ if (dx == 1)
+ return 1; // One square, ok
+ count = dx - 1;
+ for (x = startx, y = starty;
+ count;
+ x += incx, y += incy, count--) {
+ if (gs->board[x][y] != NOPIECE)
+ return 0;
+ }
+ return 1;
+}
+
+PRIVATE int
+legal_rook_move(game_state_t *gs, int ff, int fr, int tf, int tr)
+{
+ int i;
+ int start, stop;
+
+ if (ff == tf) {
+ if (((fr - tr) == 1) || ((tr - fr) == 1))
+ return 1;
+ if (fr < tr) {
+ start = fr + 1;
+ stop = tr - 1;
+ } else {
+ start = tr + 1;
+ stop = fr - 1;
+ }
+ for (i = start; i <= stop; i++) {
+ if (gs->board[ff][i] != NOPIECE)
+ return 0;
+ }
+ return 1;
+ } else if (fr == tr) {
+ if (((ff - tf) == 1) || ((tf - ff) == 1))
+ return 1;
+ if (ff < tf) {
+ start = ff + 1;
+ stop = tf - 1;
+ } else {
+ start = tf + 1;
+ stop = ff - 1;
+ }
+ for (i = start; i <= stop; i++) {
+ if (gs->board[i][fr] != NOPIECE)
+ return 0;
+ }
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+PRIVATE int
+legal_queen_move(game_state_t *gs, int ff, int fr, int tf, int tr)
+{
+ return (legal_rook_move(gs, ff, fr, tf, tr) ||
+ legal_bishop_move(gs, ff, fr, tf, tr));
+}
+
+/*
+ * New one from soso
+ */
+PRIVATE int
+is_square_attacked(game_state_t *gs, int kf, int kr)
+{
+ game_state_t fakeMove;
+
+ fakeMove = *gs;
+ fakeMove.board[4][kr] = NOPIECE;
+ fakeMove.board[kf][kr] = KING | fakeMove.onMove;
+ fakeMove.onMove = CToggle(fakeMove.onMove);
+
+ if (in_check(&fakeMove))
+ return 1;
+ else
+ return 0;
+}
+
+#if 0
+PRIVATE int
+is_square_attacked(game_state_t *gs, int kf, int kr)
+{
+ int f, r;
+
+ gs->onMove = CToggle(gs->onMove);
+
+ for (InitPieceLoop(gs->board, &f, &r, gs->onMove);
+ NextPieceLoop(gs->board, &f, &r, gs->onMove);) {
+ if (legal_move(gs, f, r, kf, kr)) {
+ gs->onMove = CToggle(gs->onMove);
+ return 1;
+ }
+ }
+
+ gs->onMove = CToggle(gs->onMove);
+ return 0;
+}
+#endif
+
+PRIVATE int
+legal_king_move(game_state_t *gs, int ff, int fr, int tf, int tr)
+{
+ if (gs->onMove == WHITE) {
+ /* King side castling */
+ if ((fr == 0) && (tr == 0) && (ff == 4) && (tf == 6) &&
+ (!gs->wkmoved) &&
+ (!gs->wkrmoved) &&
+ (gs->board[5][0] == NOPIECE) &&
+ (gs->board[6][0] == NOPIECE) &&
+ (gs->board[7][0] == W_ROOK) &&
+ (!is_square_attacked(gs, 4, 0)) &&
+ (!is_square_attacked(gs, 5, 0))) {
+ return 1;
+ }
+
+ /* Queen side castling */
+ if ((fr == 0) && (tr == 0) && (ff == 4) && (tf == 2) &&
+ (!gs->wkmoved) &&
+ (!gs->wqrmoved) &&
+ (gs->board[3][0] == NOPIECE) &&
+ (gs->board[2][0] == NOPIECE) &&
+ (gs->board[1][0] == NOPIECE) &&
+ (gs->board[0][0] == W_ROOK) &&
+ (!is_square_attacked(gs, 4, 0)) &&
+ (!is_square_attacked(gs, 3, 0))) {
+ return 1;
+ }
+ } else { /* Black */
+ /* King side castling */
+ if ((fr == 7) && (tr == 7) && (ff == 4) && (tf == 6) &&
+ (!gs->bkmoved) &&
+ (!gs->bkrmoved) &&
+ (gs->board[5][7] == NOPIECE) &&
+ (gs->board[6][7] == NOPIECE) &&
+ (gs->board[7][7] == B_ROOK) &&
+ (!is_square_attacked(gs, 4, 7)) &&
+ (!is_square_attacked(gs, 5, 7))) {
+ return 1;
+ }
+
+ /* Queen side castling */
+ if ((fr == 7) && (tr == 7) && (ff == 4) && (tf == 2) &&
+ (!gs->bkmoved) &&
+ (!gs->bqrmoved) &&
+ (gs->board[3][7] == NOPIECE) &&
+ (gs->board[2][7] == NOPIECE) &&
+ (gs->board[1][7] == NOPIECE) &&
+ (gs->board[0][7] == B_ROOK) &&
+ (!is_square_attacked(gs, 4, 7)) &&
+ (!is_square_attacked(gs, 3, 7))) {
+ return 1;
+ }
+ }
+
+ if (((ff - tf) > 1) || ((tf - ff) > 1))
+ return 0;
+ if (((fr - tr) > 1) || ((tr - fr) > 1))
+ return 0;
+ return 1;
+}
+
+PRIVATE void
+add_pos(int tof, int tor, int *posf, int *posr, int *numpos)
+{
+ posf[*numpos] = tof;
+ posr[*numpos] = tor;
+ (*numpos)++;
+}
+
+PRIVATE void
+possible_pawn_moves(game_state_t *gs,
+ int onf, int onr,
+ int *posf, int *posr,
+ int *numpos)
+{
+ if (gs->onMove == WHITE) {
+ if (gs->board[onf][onr + 1] == NOPIECE) {
+ add_pos(onf, onr + 1, posf, posr, numpos);
+ if ((onr == 1) && (gs->board[onf][onr + 2] == NOPIECE))
+ add_pos(onf, onr + 2, posf, posr, numpos);
+ }
+ if ((onf > 0) &&
+ (gs->board[onf - 1][onr + 1] != NOPIECE) &&
+ (iscolor(gs->board[onf - 1][onr + 1], BLACK)))
+ add_pos(onf - 1, onr + 1, posf, posr, numpos);
+ if ((onf < 7) &&
+ (gs->board[onf + 1][onr + 1] != NOPIECE) &&
+ (iscolor(gs->board[onf + 1][onr + 1], BLACK)))
+ add_pos(onf + 1, onr + 1, posf, posr, numpos);
+ if (gs->ep_possible[0][onf] == -1)
+ add_pos(onf - 1, onr + 1, posf, posr, numpos);
+ if (gs->ep_possible[0][onf] == 1)
+ add_pos(onf + 1, onr + 1, posf, posr, numpos);
+ } else {
+ if (gs->board[onf][onr - 1] == NOPIECE) {
+ add_pos(onf, onr - 1, posf, posr, numpos);
+ if ((onr == 6) && (gs->board[onf][onr - 2] == NOPIECE))
+ add_pos(onf, onr - 2, posf, posr, numpos);
+ }
+ if ((onf > 0) &&
+ (gs->board[onf - 1][onr - 1] != NOPIECE) &&
+ (iscolor(gs->board[onf - 1][onr - 1], WHITE)))
+ add_pos(onf - 1, onr - 1, posf, posr, numpos);
+ if ((onf < 7) &&
+ (gs->board[onf + 1][onr - 1] != NOPIECE) &&
+ (iscolor(gs->board[onf + 1][onr - 1], WHITE)))
+ add_pos(onf + 1, onr - 1, posf, posr, numpos);
+ if (gs->ep_possible[1][onf] == -1)
+ add_pos(onf - 1, onr - 1, posf, posr, numpos);
+ if (gs->ep_possible[1][onf] == 1)
+ add_pos(onf + 1, onr - 1, posf, posr, numpos);
+ }
+}
+
+PRIVATE void
+possible_knight_moves(game_state_t *gs,
+ int onf, int onr,
+ int *posf, int *posr,
+ int *numpos)
+{
+ int f, r;
+ int j;
+ static int knightJumps[8][2] = {
+ {-1, 2},
+ {1, 2},
+ {2, -1},
+ {2, 1},
+ {-1, -2},
+ {1, -2},
+ {-2, 1},
+ {-2, -1}
+ };
+
+ for (j = 0; j < 8; j++) {
+ f = knightJumps[j][0] + onf;
+ r = knightJumps[j][1] + onr;
+
+ if ((f < 0) || (f > 7))
+ continue;
+ if ((r < 0) || (r > 7))
+ continue;
+ if ((gs->board[f][r] == NOPIECE) ||
+ (iscolor(gs->board[f][r], CToggle(gs->onMove))))
+ add_pos(f, r, posf, posr, numpos);
+ }
+}
+
+PRIVATE void
+possible_bishop_moves(game_state_t *gs,
+ int onf, int onr,
+ int *posf, int *posr,
+ int *numpos)
+{
+ int f, r;
+
+ /* Up Left */
+ f = onf;
+ r = onr;
+ while (1) {
+ f--;
+ r++;
+ if ((f < 0) || (f > 7))
+ break;
+ if ((r < 0) || (r > 7))
+ break;
+ if ((gs->board[f][r] != NOPIECE) &&
+ (iscolor(gs->board[f][r], gs->onMove)))
+ break;
+ add_pos(f, r, posf, posr, numpos);
+ if (gs->board[f][r] != NOPIECE)
+ break;
+ }
+
+ /* Up Right */
+ f = onf;
+ r = onr;
+ while (1) {
+ f++;
+ r++;
+ if ((f < 0) || (f > 7))
+ break;
+ if ((r < 0) || (r > 7))
+ break;
+ if ((gs->board[f][r] != NOPIECE) &&
+ (iscolor(gs->board[f][r], gs->onMove)))
+ break;
+ add_pos(f, r, posf, posr, numpos);
+ if (gs->board[f][r] != NOPIECE)
+ break;
+ }
+
+ /* Down Left */
+ f = onf;
+ r = onr;
+ while (1) {
+ f--;
+ r--;
+ if ((f < 0) || (f > 7))
+ break;
+ if ((r < 0) || (r > 7))
+ break;
+ if ((gs->board[f][r] != NOPIECE) &&
+ (iscolor(gs->board[f][r], gs->onMove)))
+ break;
+ add_pos(f, r, posf, posr, numpos);
+ if (gs->board[f][r] != NOPIECE)
+ break;
+ }
+
+ /* Down Right */
+ f = onf;
+ r = onr;
+ while (1) {
+ f++;
+ r--;
+ if ((f < 0) || (f > 7))
+ break;
+ if ((r < 0) || (r > 7))
+ break;
+ if ((gs->board[f][r] != NOPIECE) &&
+ (iscolor(gs->board[f][r], gs->onMove)))
+ break;
+ add_pos(f, r, posf, posr, numpos);
+ if (gs->board[f][r] != NOPIECE)
+ break;
+ }
+}
+
+PRIVATE void
+possible_rook_moves(game_state_t *gs,
+ int onf, int onr,
+ int *posf, int *posr,
+ int *numpos)
+{
+ int f, r;
+
+ /* Left */
+ f = onf;
+ r = onr;
+ while (1) {
+ f--;
+ if ((f < 0) || (f > 7))
+ break;
+ if ((r < 0) || (r > 7))
+ break;
+ if ((gs->board[f][r] != NOPIECE) &&
+ (iscolor(gs->board[f][r], gs->onMove)))
+ break;
+ add_pos(f, r, posf, posr, numpos);
+ if (gs->board[f][r] != NOPIECE)
+ break;
+ }
+
+ /* Right */
+ f = onf;
+ r = onr;
+ while (1) {
+ f++;
+ if ((f < 0) || (f > 7))
+ break;
+ if ((r < 0) || (r > 7))
+ break;
+ if ((gs->board[f][r] != NOPIECE) &&
+ (iscolor(gs->board[f][r], gs->onMove)))
+ break;
+ add_pos(f, r, posf, posr, numpos);
+ if (gs->board[f][r] != NOPIECE)
+ break;
+ }
+
+ /* Up */
+ f = onf;
+ r = onr;
+ while (1) {
+ r++;
+ if ((f < 0) || (f > 7))
+ break;
+ if ((r < 0) || (r > 7))
+ break;
+ if ((gs->board[f][r] != NOPIECE) &&
+ (iscolor(gs->board[f][r], gs->onMove)))
+ break;
+ add_pos(f, r, posf, posr, numpos);
+ if (gs->board[f][r] != NOPIECE)
+ break;
+ }
+
+ /* Down */
+ f = onf;
+ r = onr;
+ while (1) {
+ r--;
+ if ((f < 0) || (f > 7))
+ break;
+ if ((r < 0) || (r > 7))
+ break;
+ if ((gs->board[f][r] != NOPIECE) &&
+ (iscolor(gs->board[f][r], gs->onMove)))
+ break;
+ add_pos(f, r, posf, posr, numpos);
+ if (gs->board[f][r] != NOPIECE)
+ break;
+ }
+}
+
+PRIVATE void
+possible_queen_moves(game_state_t *gs,
+ int onf, int onr,
+ int *posf, int *posr,
+ int *numpos)
+{
+ possible_rook_moves(gs, onf, onr, posf, posr, numpos);
+ possible_bishop_moves(gs, onf, onr, posf, posr, numpos);
+}
+
+PRIVATE void
+possible_king_moves(game_state_t *gs,
+ int onf, int onr,
+ int *posf, int *posr,
+ int *numpos)
+{
+ int f, r;
+ int j;
+ static int kingJumps[8][2] = {
+ {-1, -1},
+ {0, -1},
+ {1, -1},
+ {-1, 1},
+ {0, 1},
+ {1, 1},
+ {-1, 0},
+ {1, 0}
+ };
+
+ for (j = 0; j < 8; j++) {
+ f = kingJumps[j][0] + onf;
+ r = kingJumps[j][1] + onr;
+
+ if ((f < 0) || (f > 7))
+ continue;
+ if ((r < 0) || (r > 7))
+ continue;
+ if ((gs->board[f][r] == NOPIECE) ||
+ (iscolor(gs->board[f][r], CToggle(gs->onMove))))
+ add_pos(f, r, posf, posr, numpos);
+ }
+}
+
+/* Doesn't check for check */
+PUBLIC int
+legal_move(game_state_t *gs,
+ int fFile, int fRank,
+ int tFile, int tRank)
+{
+ int legal;
+ int move_piece;
+
+ if (fFile == ALG_DROP) {
+ move_piece = fRank;
+
+ if (move_piece == KING)
+ return 0;
+ if (gs->holding[gs->onMove == WHITE ? 0 : 1][move_piece - 1] == 0)
+ return 0;
+ if (gs->board[tFile][tRank] != NOPIECE)
+ return 0;
+ if (move_piece == PAWN && (tRank == 0 || tRank == 7))
+ return 0;
+
+ return 1;
+ } else {
+ move_piece = piecetype(gs->board[fFile][fRank]);
+ }
+
+ if (gs->board[fFile][fRank] == NOPIECE)
+ return 0;
+ if (!iscolor(gs->board[fFile][fRank], gs->onMove)) // Wrong color
+ return 0;
+ if ((gs->board[tFile][tRank] != NOPIECE) &&
+ iscolor(gs->board[tFile][tRank], gs->onMove)) // Can't capture own
+ return 0;
+ if ((fFile == tFile) && (fRank == tRank)) // Same square
+ return 0;
+
+ switch (move_piece) {
+ case PAWN:
+ legal = legal_pawn_move(gs, fFile, fRank, tFile, tRank);
+ break;
+ case KNIGHT:
+ legal = legal_knight_move(gs, fFile, fRank, tFile, tRank);
+ break;
+ case BISHOP:
+ legal = legal_bishop_move(gs, fFile, fRank, tFile, tRank);
+ break;
+ case ROOK:
+ legal = legal_rook_move(gs, fFile, fRank, tFile, tRank);
+ break;
+ case QUEEN:
+ legal = legal_queen_move(gs, fFile, fRank, tFile, tRank);
+ break;
+ case KING:
+ legal = legal_king_move(gs, fFile, fRank, tFile, tRank);
+ break;
+ default:
+ return 0;
+ break;
+ }
+
+ return legal;
+}
+
+/*
+ * This fills in the rest of the mt structure once it is determined
+ * that. (Returns 'MOVE_ILLEGAL' if move leaves you in check.)
+ */
+PRIVATE int
+move_calculate(game_state_t *gs, move_t *mt, int promote)
+{
+ game_state_t fakeMove;
+ int ret, too_long;
+
+ mt->pieceCaptured = gs->board[mt->toFile][mt->toRank];
+ mt->enPassant = 0; // Don't know yet,
+ // let execute move take care of it
+
+ if (mt->fromFile == ALG_DROP) {
+ mt->piecePromotionTo = NOPIECE;
+ ret = snprintf(mt->moveString, sizeof mt->moveString,
+ "%s/%c%c-%c%d",
+ wpstring[mt->fromRank],
+ DROP_CHAR,
+ DROP_CHAR,
+ (mt->toFile + 'a'),
+ (mt->toRank + 1));
+
+ too_long = (ret < 0 || (size_t)ret >= sizeof mt->moveString);
+
+ if (too_long) { /* XXX */
+ fprintf(stderr, "FICS: %s: warning: "
+ "snprintf truncated\n", __func__);
+ }
+ } else {
+ if (piecetype(gs->board[mt->fromFile][mt->fromRank]) == PAWN &&
+ (mt->toRank == 0 || mt->toRank == 7)) {
+ mt->piecePromotionTo = (promote |
+ colorval(gs->board[mt->fromFile][mt->fromRank]));
+ } else {
+ mt->piecePromotionTo = NOPIECE;
+ }
+
+ if (piecetype(gs->board[mt->fromFile][mt->fromRank]) == PAWN &&
+ (mt->fromRank - mt->toRank == 2 ||
+ mt->toRank - mt->fromRank == 2)) {
+ mt->doublePawn = mt->fromFile;
+ } else {
+ mt->doublePawn = -1;
+ }
+
+ if (piecetype(gs->board[mt->fromFile][mt->fromRank]) == KING &&
+ mt->fromFile == 4 &&
+ mt->toFile == 2) {
+ strlcpy(mt->moveString, "o-o-o", sizeof mt->moveString);
+ } else if (piecetype(gs->board[mt->fromFile][mt->fromRank]) == KING &&
+ mt->fromFile == 4 &&
+ mt->toFile == 6) {
+ strlcpy(mt->moveString, "o-o", sizeof mt->moveString);
+ } else {
+ ret = snprintf(mt->moveString, sizeof mt->moveString,
+ "%s/%c%d-%c%d",
+ wpstring[piecetype(gs->board[mt->fromFile][mt->fromRank])],
+ (mt->fromFile + 'a'),
+ (mt->fromRank + 1),
+ (mt->toFile + 'a'),
+ (mt->toRank + 1));
+
+ too_long = (ret < 0 || (size_t)ret >= sizeof mt->moveString);
+
+ if (too_long) { /* XXX */
+ fprintf(stderr, "FICS: %s: warning: "
+ "snprintf truncated\n", __func__);
+ }
+ }
+ }
+
+ // Replace this with an algabraic de-parser
+ snprintf(mt->algString, sizeof mt->algString, "%s",
+ alg_unparse(gs, mt));
+ fakeMove = *gs;
+ execute_move(&fakeMove, mt, 0); // Calculates enPassant also
+
+ // Does making this move leave ME in check?
+ if (in_check(&fakeMove))
+ return MOVE_ILLEGAL;
+ // IanO: bughouse variants: drop cannot be check/checkmate
+
+ return MOVE_OK;
+}
+
+PUBLIC int
+legal_andcheck_move(game_state_t *gs,
+ int fFile, int fRank,
+ int tFile, int tRank)
+{
+ move_t mt;
+
+ if (!legal_move(gs, fFile, fRank, tFile, tRank))
+ return 0;
+
+ mt.color = gs->onMove;
+ mt.fromFile = fFile;
+ mt.fromRank = fRank;
+ mt.toFile = tFile;
+ mt.toRank = tRank;
+
+ /*
+ * This should take into account a pawn promoting to another
+ * piece.
+ */
+ if (move_calculate(gs, &mt, QUEEN) == MOVE_OK)
+ return 1;
+ else
+ return 0;
+}
+
+PUBLIC int
+in_check(game_state_t *gs)
+{
+ int f, r;
+ int kf = -1, kr = -1;
+
+ /* Find the king */
+ if (gs->onMove == WHITE) {
+ for (f = 0; f < 8 && kf < 0; f++) {
+ for (r = 0; r < 8 && kf < 0; r++) {
+ if (gs->board[f][r] == B_KING) {
+ kf = f;
+ kr = r;
+ }
+ }
+ }
+ } else {
+ for (f = 0; f < 8 && kf < 0; f++) {
+ for (r = 0; r < 8 && kf < 0; r++) {
+ if (gs->board[f][r] == W_KING) {
+ kf = f;
+ kr = r;
+ }
+ }
+ }
+ }
+
+ if (kf < 0) {
+ fprintf(stderr, "FICS: Error game with no king!\n");
+ return 0;
+ }
+
+ for (InitPieceLoop(gs->board, &f, &r, gs->onMove);
+ NextPieceLoop(gs->board, &f, &r, gs->onMove);) {
+ if (legal_move(gs, f, r, kf, kr)) // In Check?
+ return 1;
+ }
+
+ return 0;
+}
+
+PRIVATE int
+has_legal_move(game_state_t *gs)
+{
+ int f, r;
+ int i;
+ int kf = 0, kr = -1;
+ int kf_and_kr_set = 0;
+ int numpossible = 0;
+ int possiblef[500];
+ int possibler[500];
+
+ for (InitPieceLoop(gs->board, &f, &r, gs->onMove);
+ NextPieceLoop(gs->board, &f, &r, gs->onMove);) {
+ switch (piecetype(gs->board[f][r])) {
+ case PAWN:
+ possible_pawn_moves(gs, f, r, possiblef, possibler,
+ &numpossible);
+ break;
+ case KNIGHT:
+ possible_knight_moves(gs, f, r, possiblef, possibler,
+ &numpossible);
+ break;
+ case BISHOP:
+ possible_bishop_moves(gs, f, r, possiblef, possibler,
+ &numpossible);
+ break;
+ case ROOK:
+ possible_rook_moves(gs, f, r, possiblef, possibler,
+ &numpossible);
+ break;
+ case QUEEN:
+ possible_queen_moves(gs, f, r, possiblef, possibler,
+ &numpossible);
+ break;
+ case KING:
+ kf = f;
+ kr = r;
+ kf_and_kr_set = 1;
+ possible_king_moves(gs, f, r, possiblef, possibler,
+ &numpossible);
+ break;
+ }
+ if (numpossible >= 500) {
+ fprintf(stderr, "FICS: Possible move overrun\n");
+ return 0;
+ }
+ for (i = 0; i < numpossible; i++) {
+ if (legal_andcheck_move(gs, f, r, possiblef[i],
+ possibler[i]))
+ return 1;
+ }
+ }
+
+ if (!kf_and_kr_set && kf == 0 && kr == -1) {
+ fprintf(stderr, "FICS: %s: 'kf_and_kr_set' is 0\n", __func__);
+ return 0;
+ }
+
+ // IanO: if we got here, then kf and kr must be set
+ if (gs->gameNum >= 0 && garray[gs->gameNum].link >= 0) {
+ // bughouse: potential drops as check interpositions
+ gs->holding[gs->onMove == WHITE ? 0 : 1][QUEEN - 1]++;
+ for (f = kf - 1; f <= kf + 1; f++) {
+ for (r = kr - 1; r <= kr + 1; r++) {
+ if (f >= 0 &&
+ f < 8 &&
+ r >= 0 &&
+ r < 8 &&
+ gs->board[f][r] == NOPIECE) {
+ // try a drop next to the king
+ if (legal_andcheck_move(gs, ALG_DROP,
+ QUEEN, f, r)) {
+ gs->holding[gs->onMove == WHITE
+ ? 0 : 1][QUEEN - 1]--;
+ return 1;
+ }
+ }
+ }
+ }
+
+ gs->holding[gs->onMove == WHITE ? 0 : 1][QUEEN - 1]--;
+ }
+
+ fprintf(stderr, "FICS: NO LEGAL MOVE!\n");
+ return 0;
+}
+
+/* This will end up being a very complicated function */
+PUBLIC int
+parse_move(char *mstr, game_state_t *gs, move_t *mt, int promote)
+{
+ int result;
+ int type = is_move(mstr);
+
+ mt->color = gs->onMove;
+
+ switch (type) {
+ case MS_NOTMOVE:
+ return MOVE_ILLEGAL;
+ break;
+ case MS_COMP:
+ mt->fromFile = mstr[0] - 'a';
+ mt->fromRank = mstr[1] - '1';
+ mt->toFile = mstr[2] - 'a';
+ mt->toRank = mstr[3] - '1';
+ break;
+ case MS_COMPDASH:
+ mt->fromFile = mstr[0] - 'a';
+ mt->fromRank = mstr[1] - '1';
+ mt->toFile = mstr[3] - 'a';
+ mt->toRank = mstr[4] - '1';
+ break;
+ case MS_KCASTLE:
+ mt->fromFile = 4;
+ mt->toFile = 6;
+ if (gs->onMove == WHITE) {
+ mt->fromRank = 0;
+ mt->toRank = 0;
+ } else {
+ mt->fromRank = 7;
+ mt->toRank = 7;
+ }
+ break;
+ case MS_QCASTLE:
+ mt->fromFile = 4;
+ mt->toFile = 2;
+ if (gs->onMove == WHITE) {
+ mt->fromRank = 0;
+ mt->toRank = 0;
+ } else {
+ mt->fromRank = 7;
+ mt->toRank = 7;
+ }
+ break;
+ case MS_ALG:
+ // Fills in the mt structure
+ if ((result = alg_parse_move(mstr, gs, mt)) != MOVE_OK)
+ return result;
+ break;
+ default:
+ return MOVE_ILLEGAL;
+ break;
+ }
+
+ if (!legal_move(gs, mt->fromFile, mt->fromRank, mt->toFile, mt->toRank))
+ return MOVE_ILLEGAL;
+ return move_calculate(gs, mt, promote);
+}
+
+/*
+ * Returns 'MOVE_OK', 'MOVE_NOMATERIAL', 'MOVE_CHECKMATE', or
+ * 'MOVE_STALEMATE'.
+ *
+ * ('check_game_status' prevents recursion.)
+ */
+PUBLIC int
+execute_move(game_state_t *gs, move_t *mt, int check_game_status)
+{
+ int i, j, foobar;
+ int movedPiece;
+ int tookPiece;
+
+ if (mt->fromFile == ALG_DROP) {
+ movedPiece = mt->fromRank;
+// tookPiece = NOPIECE;
+
+ gs->holding[gs->onMove == WHITE ? 0 : 1][movedPiece-1]--;
+ gs->board[mt->toFile][mt->toRank] = (movedPiece | gs->onMove);
+
+ if (gs->gameNum >= 0)
+ gs->lastIrreversable = garray[gs->gameNum].numHalfMoves;
+ } else {
+ movedPiece = gs->board[mt->fromFile][mt->fromRank];
+ tookPiece = gs->board[mt->toFile][mt->toRank];
+
+ if (mt->piecePromotionTo == NOPIECE) {
+ gs->board[mt->toFile][mt->toRank] =
+ gs->board[mt->fromFile][mt->fromRank];
+ } else {
+ gs->board[mt->toFile][mt->toRank] =
+ (mt->piecePromotionTo | gs->onMove);
+ }
+
+ gs->board[mt->fromFile][mt->fromRank] = NOPIECE;
+
+ /*
+ * Check if irreversable
+ */
+ if (piecetype(movedPiece) == PAWN || tookPiece != NOPIECE) {
+ if (gs->gameNum >= 0) {
+ gs->lastIrreversable =
+ garray[gs->gameNum].numHalfMoves;
+ }
+ }
+
+ /*
+ * Check if this move is en-passant
+ */
+ if (piecetype(movedPiece) == PAWN &&
+ mt->fromFile != mt->toFile &&
+ tookPiece == NOPIECE) {
+ if (gs->onMove == WHITE)
+ mt->pieceCaptured = B_PAWN;
+ else
+ mt->pieceCaptured = W_PAWN;
+
+ if (mt->fromFile > mt->toFile)
+ mt->enPassant = -1;
+ else
+ mt->enPassant = 1;
+ gs->board[mt->toFile][mt->fromRank] = NOPIECE;
+ }
+
+ /*
+ * Check en-passant flags for next moves
+ */
+ for (i = 0; i < 8; i++) {
+ gs->ep_possible[0][i] = 0;
+ gs->ep_possible[1][i] = 0;
+ }
+
+ if (piecetype(movedPiece) == PAWN &&
+ (mt->fromRank == (mt->toRank + 2) ||
+ (mt->fromRank + 2) == mt->toRank)) {
+ /*
+ * Should turn on enpassent flag if possible.
+ */
+
+ if (gs->onMove == WHITE) {
+ if (mt->toFile < 7 &&
+ gs->board[mt->toFile + 1][3] == B_PAWN)
+ gs->ep_possible[1][mt->toFile + 1] = -1;
+ if ((mt->toFile - 1) >= 0 &&
+ gs->board[mt->toFile - 1][3] == B_PAWN)
+ gs->ep_possible[1][mt->toFile - 1] = 1;
+ } else {
+ if (mt->toFile < 7 &&
+ gs->board[mt->toFile + 1][4] == W_PAWN)
+ gs->ep_possible[0][mt->toFile + 1] = -1;
+ if ((mt->toFile - 1) >= 0 &&
+ gs->board[mt->toFile - 1][4] == W_PAWN)
+ gs->ep_possible[0][mt->toFile - 1] = 1;
+ }
+ }
+
+ if (piecetype(movedPiece) == ROOK && mt->fromFile == 0) {
+ if (mt->fromRank == 0 && gs->onMove == WHITE)
+ gs->wqrmoved = 1;
+ if (mt->fromRank == 7 && gs->onMove == BLACK)
+ gs->bqrmoved = 1;
+ }
+
+ if (piecetype(movedPiece) == ROOK && mt->fromFile == 7) {
+ if (mt->fromRank == 0 && gs->onMove == WHITE)
+ gs->wkrmoved = 1;
+ if (mt->fromRank == 7 && gs->onMove == BLACK)
+ gs->bkrmoved = 1;
+ }
+
+ if (piecetype(movedPiece) == KING) {
+ if (gs->onMove == WHITE)
+ gs->wkmoved = 1;
+ else
+ gs->bkmoved = 1;
+ }
+
+ if (piecetype(movedPiece) == KING &&
+ (mt->fromFile == 4 && mt->toFile == 6)) { // Check for KS
+ // castling
+ gs->board[5][mt->toRank] = gs->board[7][mt->toRank];
+ gs->board[7][mt->toRank] = NOPIECE;
+ }
+
+ if (piecetype(movedPiece) == KING &&
+ (mt->fromFile == 4 && mt->toFile == 2)) { // Check for QS
+ // castling
+ gs->board[3][mt->toRank] = gs->board[0][mt->toRank];
+ gs->board[0][mt->toRank] = NOPIECE;
+ }
+ }
+
+ if (gs->onMove == BLACK)
+ gs->moveNum++;
+
+ if (check_game_status) {
+ /*
+ * Does this move result in check?
+ */
+
+ if (in_check(gs)) {
+ /*
+ * Check for checkmate
+ */
+
+ gs->onMove = CToggle(gs->onMove);
+
+ if (!has_legal_move(gs))
+ return MOVE_CHECKMATE;
+ } else {
+ /*
+ * Check for stalemate
+ */
+
+ gs->onMove = CToggle(gs->onMove);
+
+ if (!has_legal_move(gs))
+ return MOVE_STALEMATE;
+
+ /*
+ * loon: check for insufficient mating material
+ */
+
+ foobar = 0;
+
+ for (i = 0; i < 8; i++) {
+ for (j = 0; j < 8; j++) {
+ switch (piecetype(gs->board[i][j])) {
+ case KNIGHT:
+ case BISHOP:
+ foobar++;
+ break;
+ case KING:
+ case NOPIECE:
+ break;
+ default:
+ foobar = 2;
+ break;
+ }
+ }
+ }
+
+ if (foobar < 2)
+ return MOVE_NOMATERIAL;
+ }
+ } else {
+ gs->onMove = CToggle(gs->onMove);
+ }
+
+ return MOVE_OK;
+}
+
+#ifdef TIMESEAL
+static void
+backup_move_timeseal(int g, move_t *m)
+{
+ if (m->color == WHITE) {
+ if (con[parray[garray[g].white].socket].timeseal) {
+ garray[g].wRealTime += (m->tookTime * 100);
+ garray[g].wRealTime -= (garray[g].wIncrement * 100);
+ garray[g].wTime = garray[g].wRealTime / 100;
+
+ if (con[parray[garray[g].black].socket].timeseal) {
+ garray[g].bTime = garray[g].bRealTime / 100;
+ } else {
+ garray[g].bTime += (garray[g].lastDecTime -
+ garray[g].lastMoveTime);
+ }
+ } else { // white has no timeseal
+ garray[g].wTime += m->tookTime;
+ garray[g].wTime -= garray[g].wIncrement;
+
+ if (con[parray[garray[g].black].socket].timeseal) {
+ garray[g].bTime = garray[g].bRealTime / 100;
+ } else {
+ garray[g].bTime += (garray[g].lastDecTime -
+ garray[g].lastMoveTime);
+ }
+ }
+ } else {
+ if (con[parray[garray[g].black].socket].timeseal) {
+ garray[g].bRealTime += (m->tookTime * 100);
+ garray[g].bRealTime -= (garray[g].wIncrement * 100);
+ garray[g].bTime = garray[g].bRealTime / 100;
+
+ if (con[parray[garray[g].white].socket].timeseal) {
+ garray[g].wTime = garray[g].wRealTime / 100;
+ } else {
+ garray[g].wTime += (garray[g].lastDecTime -
+ garray[g].lastMoveTime);
+ }
+ } else { // black has no timeseal
+ garray[g].bTime += m->tookTime;
+
+ if (!garray[g].bIncrement)
+ garray[g].bTime -= garray[g].wIncrement;
+ else
+ garray[g].bTime -= garray[g].bIncrement;
+ if (con[parray[garray[g].white].socket].timeseal) {
+ garray[g].wTime = garray[g].wRealTime / 100;
+ } else {
+ garray[g].wTime += (garray[g].lastDecTime -
+ garray[g].lastMoveTime);
+ }
+ }
+ }
+}
+#endif // TIMESEAL
+
+PUBLIC int
+backup_move(int g, int mode)
+{
+ game_state_t *gs;
+ int now, i;
+ move_t *m, *m1;
+
+ if (garray[g].link >= 0) // IanO: not implemented for bughouse yet
+ return MOVE_ILLEGAL;
+ if (garray[g].numHalfMoves < 1)
+ return MOVE_ILLEGAL;
+ gs = &garray[g].game_state;
+ m = (mode == REL_GAME)
+ ? &garray[g].moveList[garray[g].numHalfMoves - 1]
+ : &garray[g].examMoveList[garray[g].numHalfMoves - 1];
+ if (m->toFile < 0)
+ return MOVE_ILLEGAL;
+
+ gs->board[m->fromFile][m->fromRank] = gs->board[m->toFile][m->toRank];
+
+ if (m->piecePromotionTo != NOPIECE) {
+ gs->board[m->fromFile][m->fromRank] = PAWN |
+ colorval(gs->board[m->fromFile][m->fromRank]);
+ }
+
+ /*
+ * When takeback a _first_ move of rook, the ??rmoved variable
+ * must be cleared.
+ * To check, if the move is first, we should scan moveList.
+ */
+ if (piecetype(gs->board[m->fromFile][m->fromRank]) == ROOK) {
+ if (m->color == WHITE) {
+ if ((m->fromFile == 0) && (m->fromRank == 0)) {
+ for (i = 2; i < garray[g].numHalfMoves - 1;
+ i += 2) {
+ m1 = (mode == REL_GAME)
+ ? &garray[g].moveList[i]
+ : &garray[g].examMoveList[i];
+
+ if ((m1->fromFile == 0) &&
+ (m1->fromRank == 0))
+ break;
+ }
+
+ if (i == garray[g].numHalfMoves - 1)
+ gs->wqrmoved = 0;
+ }
+
+ if ((m->fromFile == 7) && (m->fromRank == 0)) {
+ for (i = 2; i < garray[g].numHalfMoves - 1;
+ i += 2) {
+ m1 = (mode == REL_GAME)
+ ? &garray[g].moveList[i]
+ : &garray[g].examMoveList[i];
+
+ if ((m1->fromFile == 7) &&
+ (m1->fromRank == 0))
+ break;
+ }
+
+ if (i == garray[g].numHalfMoves - 1)
+ gs->wkrmoved = 0;
+ }
+ } else {
+ if ((m->fromFile == 0) && (m->fromRank == 7)) {
+ for (i = 3; i < garray[g].numHalfMoves - 1;
+ i += 2) {
+ m1 = (mode == REL_GAME)
+ ? &garray[g].moveList[i]
+ : &garray[g].examMoveList[i];
+
+ if ((m1->fromFile == 0) &&
+ (m1->fromRank == 0))
+ break;
+ }
+
+ if (i == garray[g].numHalfMoves - 1)
+ gs->bqrmoved = 0;
+ }
+
+ if ((m->fromFile == 7) && (m->fromRank == 7)) {
+ for (i = 3; i < garray[g].numHalfMoves - 1;
+ i += 2) {
+ m1 = (mode == REL_GAME)
+ ? &garray[g].moveList[i]
+ : &garray[g].examMoveList[i];
+
+ if ((m1->fromFile == 7) &&
+ (m1->fromRank == 0))
+ break;
+ }
+
+ if (i == garray[g].numHalfMoves - 1)
+ gs->bkrmoved = 0;
+ }
+ }
+ }
+
+ if (piecetype(gs->board[m->fromFile][m->fromRank]) == KING) {
+ gs->board[m->toFile][m->toRank] = m->pieceCaptured;
+
+ if (m->toFile - m->fromFile == 2) {
+ gs->board[7][m->fromRank] = ROOK |
+ colorval(gs->board[m->fromFile][m->fromRank]);
+ gs->board[5][m->fromRank] = NOPIECE;
+
+ /*
+ * If takeback a castling, the appropriates
+ * ??moved variables must be cleared.
+ */
+ if (m->color == WHITE) {
+ gs->wkmoved = 0;
+ gs->wkrmoved = 0;
+ } else {
+ gs->bkmoved = 0;
+ gs->bkrmoved = 0;
+ }
+ goto cleanupMove;
+ }
+
+ if (m->fromFile - m->toFile == 2) {
+ gs->board[0][m->fromRank] = ROOK |
+ colorval(gs->board[m->fromFile][m->fromRank]);
+ gs->board[3][m->fromRank] = NOPIECE;
+
+ /*
+ * If takeback a castling, the appropriate
+ * ??moved variables must be cleared.
+ */
+ if (m->color == WHITE) {
+ gs->wkmoved = 0;
+ gs->wqrmoved = 0;
+ } else {
+ gs->bkmoved = 0;
+ gs->bqrmoved = 0;
+ }
+ goto cleanupMove;
+ }
+
+ /*
+ * When takeback a _first_ move of king (not the castling),
+ * the ?kmoved variable must be cleared.
+ * To check, if the move is first, we should scan moveList.
+ */
+ if (m->color == WHITE) {
+ if ((m->fromFile == 4) && (m->fromRank == 0)) {
+ for (i = 2; i < garray[g].numHalfMoves - 1;
+ i += 2) {
+ m1 = (mode == REL_GAME)
+ ? &garray[g].moveList[i]
+ : &garray[g].examMoveList[i];
+
+ if ((m1->fromFile == 4) &&
+ (m1->fromRank == 0))
+ break;
+ }
+
+ if (i == garray[g].numHalfMoves - 1)
+ gs->wkmoved = 0;
+ }
+ } else {
+ if ((m->fromFile == 4) && (m->fromRank == 7)) {
+ for (i = 3; i < garray[g].numHalfMoves - 1;
+ i += 2) {
+ m1 = (mode == REL_GAME)
+ ? &garray[g].moveList[i]
+ : &garray[g].examMoveList[i];
+
+ if ((m1->fromFile == 4) &&
+ (m1->fromRank == 7))
+ break;
+ }
+
+ if (i == garray[g].numHalfMoves - 1)
+ gs->bkmoved = 0;
+ }
+ }
+ }
+
+ if (m->enPassant) { // Do enPassant
+ gs->board[m->toFile][m->fromRank] = PAWN |
+ (colorval(gs->board[m->fromFile][m->fromRank]) == WHITE
+ ? BLACK
+ : WHITE);
+ gs->board[m->toFile][m->toRank] = NOPIECE;
+ /*
+ * Should set the enpassant array, but I don't care
+ * right now.
+ */
+ goto cleanupMove;
+ }
+
+ gs->board[m->toFile][m->toRank] = m->pieceCaptured;
+
+ cleanupMove:
+
+ if (garray[g].status != GAME_EXAMINE)
+ game_update_time(g);
+
+ garray[g].numHalfMoves--;
+
+ if (garray[g].status != GAME_EXAMINE) {
+ if (garray[g].wInitTime) { // Don't update times in untimed
+ // games
+ now = tenth_secs();
+
+#ifdef TIMESEAL
+ backup_move_timeseal(g, m);
+#else
+ if (m->color == WHITE) {
+ garray[g].wTime += m->tookTime;
+ garray[g].wTime = (garray[g].wTime -
+ garray[g].wIncrement);
+ garray[g].bTime += (garray[g].lastDecTime -
+ garray[g].lastMoveTime);
+ } else {
+ garray[g].bTime += m->tookTime;
+
+ if (!garray[g].bIncrement) {
+ garray[g].bTime = (garray[g].bTime -
+ garray[g].wIncrement);
+ } else {
+ garray[g].bTime = (garray[g].bTime -
+ garray[g].bIncrement);
+ }
+
+ garray[g].wTime += (garray[g].lastDecTime -
+ garray[g].lastMoveTime);
+ }
+#endif
+
+ if (garray[g].numHalfMoves == 0)
+ garray[g].timeOfStart = now;
+ garray[g].lastMoveTime = now;
+ garray[g].lastDecTime = now;
+ }
+ }
+
+ if (gs->onMove == BLACK) {
+ gs->onMove = WHITE;
+ } else {
+ gs->onMove = BLACK;
+ gs->moveNum--;
+ }
+
+ /*
+ * Takeback of last move is done already, it's time to update
+ * enpassant array.
+ * (Patch from Soso, added by Sparky 3/17/95)
+ */
+ if (garray[g].numHalfMoves > 0) {
+ m1 = (mode == REL_GAME)
+ ? &garray[g].moveList[garray[g].numHalfMoves - 1]
+ : &garray[g].examMoveList[garray[g].numHalfMoves - 1];
+
+ if (piecetype(gs->board[m1->toFile][m1->toRank]) == PAWN) {
+ if ((m1->toRank - m1->fromRank) == 2) {
+ if ((m1->toFile < 7) &&
+ gs->board[m1->toFile + 1][3] == B_PAWN) {
+ gs->ep_possible[1][m1->toFile + 1] = -1;
+ }
+ if ((m1->toFile - 1 >= 0) &&
+ gs->board[m1->toFile - 1][3] == B_PAWN) {
+ gs->ep_possible[1][m1->toFile - 1] = 1;
+ }
+ }
+ if ((m1->toRank - m1->fromRank) == -2) {
+ if ((m1->toFile < 7) &&
+ gs->board[m1->toFile + 1][4] == W_PAWN) {
+ gs->ep_possible[0][m1->toFile + 1] = -1;
+ }
+ if ((m1->toFile - 1 >= 0) &&
+ gs->board[m1->toFile - 1][4] == W_PAWN) {
+ gs->ep_possible[0][m1->toFile - 1] = 1;
+ }
+ }
+ }
+ }
+
+ return MOVE_OK;
+}
diff --git a/FICS/movecheck.h b/FICS/movecheck.h
new file mode 100644
index 0000000..60b37e4
--- /dev/null
+++ b/FICS/movecheck.h
@@ -0,0 +1,61 @@
+/* movecheck.h
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 24/05/05 Revised
+*/
+
+#ifndef _MOVECHECK_H
+#define _MOVECHECK_H
+
+#define MOVE_OK 0
+#define MOVE_ILLEGAL 1
+#define MOVE_STALEMATE 2
+#define MOVE_CHECKMATE 3
+#define MOVE_AMBIGUOUS 4
+#define MOVE_NOMATERIAL 5
+
+#define MS_NOTMOVE 0
+#define MS_COMP 1
+#define MS_COMPDASH 2
+#define MS_ALG 3
+#define MS_KCASTLE 4
+#define MS_QCASTLE 5
+
+#define isrank(c) (((c) <= '8') && ((c) >= '1'))
+#define isfile(c) (((c) >= 'a') && ((c) <= 'h'))
+
+#if !defined(_BOARD_H)
+#include "board.h"
+#endif
+
+extern int InitPieceLoop(board_t, int *, int *, int);
+extern int NextPieceLoop(board_t, int *, int *, int);
+
+extern int backup_move(int, int);
+extern int execute_move(game_state_t *, move_t *, int);
+extern int in_check(game_state_t *);
+extern int is_move(char *);
+extern int legal_andcheck_move(game_state_t *, int, int, int, int);
+extern int legal_move(game_state_t *, int, int, int, int);
+extern int parse_move(char *, game_state_t *, move_t *, int);
+
+#endif /* _MOVECHECK_H */
diff --git a/FICS/multicol.c b/FICS/multicol.c
new file mode 100644
index 0000000..046ac9e
--- /dev/null
+++ b/FICS/multicol.c
@@ -0,0 +1,172 @@
+/* multicol.c
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 23/12/20 Revised
+ Markus Uhlin 24/05/05 Usage of reallocarray()
+*/
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include <err.h>
+
+#include "multicol.h"
+#include "rmalloc.h"
+#include "utils.h"
+
+PUBLIC multicol *
+multicol_start(int maxArray)
+{
+ multicol *m;
+
+ m = rmalloc(sizeof(multicol));
+ m->arraySize = maxArray;
+ m->num = 0;
+ m->strArray = reallocarray(NULL, sizeof(char *), m->arraySize);
+
+ if (m->strArray == NULL)
+ err(1, "%s: reallocarray", __func__);
+ else
+ malloc_count++;
+
+ for (int i = 0; i < m->arraySize; i++)
+ m->strArray[i] = NULL;
+ return m;
+}
+
+PUBLIC int
+multicol_store(multicol *m, char *str)
+{
+ if (m == NULL || str == NULL || m->num >= m->arraySize)
+ return -1;
+ m->strArray[m->num] = xstrdup(str);
+ m->num++;
+ return 0;
+}
+
+PUBLIC int
+multicol_store_sorted(multicol *m, char *str)
+{ // Use this instead of multicol_store() to print a list sorted.
+ int found = 0;
+
+ if (m == NULL || str == NULL || m->num >= m->arraySize)
+ return -1;
+
+ for (int i = m->num; i > 0 && !found; i--) {
+ if (strcasecmp(str, m->strArray[i - 1]) >= 0) {
+ found = 1;
+ m->strArray[i] = xstrdup(str);
+ } else {
+ m->strArray[i] = m->strArray[i - 1];
+ }
+ }
+
+ if (!found)
+ m->strArray[0] = xstrdup(str);
+
+ m->num++;
+ return 0;
+}
+
+PUBLIC int
+multicol_pprint(multicol *m, int player, int cols, int space)
+{
+ char *tempptr;
+ int done;
+ int i;
+ int maxWidth = 0;
+ int numLines;
+ int numPerLine;
+ int on, theone, len;
+ int temp;
+
+ pprintf(player, "\n");
+
+ for (i = 0; i < m->num; i++) {
+ tempptr = m->strArray[i];
+ temp = strlen(tempptr); // loon: yes, this is pathetic
+
+ for (; *tempptr; tempptr++) {
+ if (*tempptr == '\033')
+ temp -= 4;
+ }
+
+ if (temp > maxWidth)
+ maxWidth = temp;
+ }
+
+ maxWidth += space;
+
+ numPerLine = (cols / maxWidth);
+ numLines = (m->num / numPerLine);
+
+ if ((numLines * numPerLine) < m->num)
+ numLines++;
+
+ on = 0;
+ done = 0;
+
+ while (!done) {
+ for (i = 0; i < numPerLine; i++) {
+ if ((theone = on + numLines * i) >= m->num)
+ break;
+
+ tempptr = m->strArray[theone];
+ temp = strlen(tempptr); // loon: yes, still pathetic
+
+ for (; *tempptr; tempptr++) {
+ if (*tempptr == '\033')
+ temp -= 4;
+ }
+
+ len = maxWidth - temp;
+
+ if (i == (numPerLine - 1))
+ len -= space;
+
+ pprintf(player, "%s", m->strArray[theone]);
+
+ while (len) {
+ pprintf(player, " ");
+ len--;
+ }
+ }
+
+ pprintf(player, "\n");
+ on += 1;
+
+ if (on >= numLines)
+ break;
+ }
+
+ return 0;
+}
+
+PUBLIC int
+multicol_end(multicol *m)
+{
+ for (int i = 0; i < m->num; i++)
+ rfree(m->strArray[i]);
+ rfree(m->strArray);
+ rfree(m);
+ return 0;
+}
diff --git a/FICS/multicol.h b/FICS/multicol.h
new file mode 100644
index 0000000..0d0cee6
--- /dev/null
+++ b/FICS/multicol.h
@@ -0,0 +1,41 @@
+/* multicol.h
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 23/12/20 Revised
+*/
+
+#ifndef _MULTICOL_H
+#define _MULTICOL_H
+
+typedef struct _multicol {
+ int arraySize;
+ int num;
+ char** strArray;
+} multicol;
+
+extern int multicol_end(multicol *);
+extern int multicol_pprint(multicol *, int, int, int);
+extern int multicol_store(multicol *, char *);
+extern int multicol_store_sorted(multicol *, char *);
+extern multicol *multicol_start(int);
+
+#endif /* _MULTICOL_H */
diff --git a/FICS/network.c b/FICS/network.c
new file mode 100644
index 0000000..19c6f22
--- /dev/null
+++ b/FICS/network.c
@@ -0,0 +1,688 @@
+/* network.c
+ *
+ */
+
+#include "stdinclude.h"
+
+#include <sys/socket.h>
+
+#include <arpa/inet.h>
+#include <arpa/telnet.h>
+#include <netinet/in.h>
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+
+#include "common.h"
+#include "config.h"
+#include "ficsmain.h"
+#include "maxxes-utils.h"
+#include "network.h"
+#include "playerdb.h"
+#include "rmalloc.h"
+#ifdef TIMESEAL
+#include "timeseal.h"
+#endif
+#include "utils.h"
+
+/* Sparse array */
+PUBLIC connection con[512];
+
+PUBLIC int no_file;
+PUBLIC int max_connections;
+
+PRIVATE int sockfd = 0;
+PRIVATE int numConnections = 0;
+
+PUBLIC int
+findConnection(int fd)
+{
+ if (con[fd].status == NETSTAT_EMPTY)
+ return -1;
+ return fd;
+}
+
+PUBLIC int
+net_addConnection(int fd, unsigned int fromHost)
+{
+ int noblock = 1;
+
+ if (findConnection(fd) >= 0) {
+ fprintf(stderr, "FICS: FD already in connection table!\n");
+ return -1;
+ }
+ if (numConnections >= max_connections)
+ return -1;
+ if (ioctl(fd, FIONBIO, &noblock) == -1) {
+ fprintf(stderr, "Error setting nonblocking mode errno=%d\n",
+ errno);
+ }
+
+ con[fd].fd = fd;
+
+ if (fd != 0)
+ con[fd].outFd = fd;
+ else
+ con[fd].outFd = 1;
+
+ con[fd].fromHost = fromHost;
+ con[fd].status = NETSTAT_CONNECTED;
+#ifdef TIMESEAL
+ con[fd].sys[0] = '\0';
+ con[fd].time = 0;
+ con[fd].timeseal = 0;
+ con[fd].user[0] = '\0';
+#endif
+
+ con[fd].numPending = 0;
+ con[fd].outPos = 0;
+ con[fd].processed = 0;
+
+ if (con[fd].sndbuf == NULL) {
+#ifdef DEBUG
+ fprintf(stderr, "FICS: nac(%d) allocating sndbuf.\n", fd);
+#endif
+ con[fd].sndbufpos = 0;
+ con[fd].sndbufsize = MAX_STRING_LENGTH;
+ con[fd].sndbuf = rmalloc(MAX_STRING_LENGTH);
+ } else {
+#ifdef DEBUG
+ fprintf(stderr, "FICS: nac(%d) reusing old sndbuf "
+ "size %d pos %d.\n", fd,
+ con[fd].sndbufsize, con[fd].sndbufpos);
+#else
+ /* empty */;
+#endif
+ }
+ con[fd].state = 0;
+
+ numConnections++;
+
+#ifdef DEBUG
+ fprintf(stderr, "FICS: fd: %d connections: %d descriptors: %d \n", fd,
+ numConnections, getdtablesize()); /* sparky 3/13/95 */
+#endif
+
+ return 0;
+}
+
+PRIVATE int
+remConnection(int fd)
+{
+ if (findConnection(fd) < 0)
+ return -1;
+ numConnections--;
+
+ con[fd].status = NETSTAT_EMPTY;
+
+ if (con[fd].sndbuf == NULL) {
+ fprintf(stderr, "FICS: remcon(%d) SNAFU, "
+ "this shouldn't happen.\n", fd);
+ } else {
+ if (con[fd].sndbufsize > MAX_STRING_LENGTH) {
+ con[fd].sndbufsize = MAX_STRING_LENGTH;
+ con[fd].sndbuf = rrealloc(con[fd].sndbuf,
+ MAX_STRING_LENGTH);
+ }
+
+ if (con[fd].sndbufpos) // didn't send everything, bummer
+ con[fd].sndbufpos = 0;
+ }
+
+ return 0;
+}
+
+PRIVATE void
+net_flushme(int which)
+{
+ int sent;
+
+ if ((sent = send(con[which].outFd, con[which].sndbuf,
+ con[which].sndbufpos, 0)) == -1) {
+ if (errno != EPIPE) { // EPIPE = they've disconnected
+ fprintf(stderr, "FICS: net_flushme(%d) couldn't send, "
+ "errno=%d.\n",
+ which, errno);
+ }
+
+ con[which].sndbufpos = 0;
+ } else {
+ con[which].sndbufpos -= sent;
+
+ if (con[which].sndbufpos) {
+ memmove(con[which].sndbuf, con[which].sndbuf + sent,
+ con[which].sndbufpos);
+ }
+ }
+
+ if (con[which].sndbufsize > MAX_STRING_LENGTH &&
+ con[which].sndbufpos < MAX_STRING_LENGTH) {
+ // time to shrink the buffer...
+ con[which].sndbuf = rrealloc(con[which].sndbuf,
+ MAX_STRING_LENGTH);
+ con[which].sndbufsize = MAX_STRING_LENGTH;
+ }
+}
+
+PRIVATE void
+net_flush_all_connections(void)
+{
+ fd_set writefds;
+ int which;
+ struct timeval to;
+
+ FD_ZERO(&writefds);
+
+ for (which = 0; which < MAX_PLAYER; which++) {
+ if (con[which].status == NETSTAT_CONNECTED &&
+ con[which].sndbufpos)
+ FD_SET(con[which].outFd, &writefds);
+ }
+
+ to.tv_usec = 0;
+ to.tv_sec = 0;
+
+ select(no_file, NULL, &writefds, NULL, &to);
+
+ for (which = 0; which < MAX_PLAYER; which++) {
+ if (FD_ISSET(con[which].outFd, &writefds))
+ net_flushme(which);
+ }
+}
+
+PRIVATE void
+net_flush_connection(int fd)
+{
+ fd_set writefds;
+ int which;
+ struct timeval to;
+
+ if ((which = findConnection(fd)) >= 0 && con[which].sndbufpos) {
+ FD_ZERO(&writefds);
+ FD_SET(con[which].outFd, &writefds);
+
+ to.tv_usec = 0;
+ to.tv_sec = 0;
+
+ select(no_file, NULL, &writefds, NULL, &to);
+
+ if (FD_ISSET(con[which].outFd, &writefds))
+ net_flushme(which);
+ }
+}
+
+PRIVATE int
+sendme(int which, char *str, int len)
+{
+ fd_set writefds;
+ int i, count;
+ struct timeval to;
+
+ count = len;
+
+ while ((i = ((con[which].sndbufsize - con[which].sndbufpos) < len) ?
+ (con[which].sndbufsize - con[which].sndbufpos) : len) > 0) {
+ memmove(con[which].sndbuf + con[which].sndbufpos, str, i);
+ con[which].sndbufpos += i;
+
+ if (con[which].sndbufpos == con[which].sndbufsize) {
+ FD_ZERO(&writefds);
+ FD_SET(con[which].outFd, &writefds);
+
+ to.tv_usec = 0;
+ to.tv_sec = 0;
+
+ select(no_file, NULL, &writefds, NULL, &to);
+
+ if (FD_ISSET(con[which].outFd, &writefds)) {
+ net_flushme(which);
+ } else {
+ // time to grow the buffer
+ con[which].sndbufsize += MAX_STRING_LENGTH;
+ con[which].sndbuf = rrealloc(con[which].sndbuf,
+ con[which].sndbufsize);
+ }
+ }
+
+ str += i;
+ len -= i;
+ }
+
+ return count;
+}
+
+/*
+ * Put LF after every CR and put '\' at the end of overlength lines.
+ *
+ * Doesn't send anything unless the buffer fills and output waits
+ * until flushed.
+ *
+ * '-1' for an error other than 'EWOULDBLOCK'.
+ */
+PUBLIC int
+net_send_string(int fd, char *str, int format)
+{
+ int which, i, j;
+
+ if ((which = findConnection(fd)) < 0)
+ return -1;
+ while (*str) {
+ const int upbound = (int)strlen(str);
+
+ for (i = 0; i < upbound && str[i] >= ' '; i++) {
+ /* null */;
+ }
+
+ if (i) {
+ if (format &&
+ (i >= (j = LINE_WIDTH - con[which].outPos))) {
+ // word wrap
+
+ i = j;
+
+ while (i > 0 && str[i - 1] != ' ')
+ i--;
+ while (i > 0 && str[i - 1] == ' ')
+ i--;
+ if (i == 0)
+ i = j - 1;
+ sendme(which, str, i);
+ sendme(which, "\n\r\\ ", 6);
+ con[which].outPos = 4;
+
+ while (str[i] == ' ') { // eat the leading
+ // spaces after we wrap
+ i++;
+ }
+ } else {
+ sendme(which, str, i);
+ con[which].outPos += i;
+ }
+ str += i;
+ } else { // non-printable stuff handled here
+ switch (*str) {
+ case '\t':
+ sendme(which, " ",
+ 8 - (con[which].outPos & 7));
+ con[which].outPos &= ~7;
+ if ((con[which].outPos += 8) >= LINE_WIDTH)
+ con[which].outPos = 0;
+ break;
+ case '\n':
+ sendme(which, "\n\r", 2);
+ con[which].outPos = 0;
+ break;
+ case '\033':
+ con[which].outPos -= 3;
+ // XXX: fallthrough here?
+ default:
+ sendme(which, str, 1);
+ }
+ str++;
+ }
+ }
+ return 0;
+}
+
+/*
+ * A) if we get a complete line (something terminated by '\n'), copy it
+ * to com and return 1.
+ *
+ * B) if we don't get a complete line, but there is no error, return 0.
+ *
+ * C) if some error, return -1.
+ */
+PUBLIC int
+readline2(comstr_t *cs, int who)
+{
+ int bytes_received, state, fd, v_pending;
+ ssize_t ret;
+ static const uint8_t ayt[] = "[Responding to AYT: Yes, I'm here.]\n";
+ static const uint8_t will_sga[] = { IAC, WILL, TELOPT_SGA, '\0' };
+ static const uint8_t will_tm[] = { IAC, WILL, TELOPT_TM, '\0' };
+ unsigned char *start, *s, *d;
+
+ state = con[who].state;
+
+ if (state == 2 || state > 4) {
+ fprintf(stderr, "FICS: state screwed for con[%d], "
+ "this is a bug.\n", who);
+ state = 0;
+ }
+
+ s = start = con[who].inBuf;
+ v_pending = con[who].numPending;
+ fd = con[who].fd;
+
+ ret = recv(fd, start + v_pending, MAX_STRING_LENGTH - 1 - v_pending, 0);
+ if (ret < INT_MIN || ret > INT_MAX)
+ errx(1, "%s: return out of bounds", __func__);
+ bytes_received = (int)ret;
+
+ if (bytes_received == 0) { // error: they've disconnected
+ return -1;
+ } else if (bytes_received == -1) {
+ if (errno != EWOULDBLOCK) { // some other error
+ return -1;
+ } else if (con[who].processed) { // nothing new and nothing old
+ return 0;
+ } else { // nothing new
+ // but some unprocessed old
+ bytes_received = 0;
+ }
+ }
+
+ if (con[who].processed)
+ s += v_pending;
+ else {
+ if (bytes_received > INT_MAX - v_pending)
+ errx(1, "%s: integer overflow", __func__);
+ bytes_received += v_pending;
+ }
+ d = s;
+
+ while (bytes_received-- > 0) {
+ switch (state) {
+ case 0: // haven't skipped over any control chars or
+ // telnet commands
+ if (*s == IAC) {
+ d = s;
+ state = 1;
+ } else if (*s == '\n') {
+ *s = '\0';
+ msnprintf(cs->com, ARRAY_SIZE(cs->com), "%s",
+ start);
+ if (bytes_received > 0)
+ memmove(start, s + 1, bytes_received);
+ con[who].state = 0;
+ con[who].numPending = bytes_received;
+ con[who].processed = 0;
+ con[who].outPos = 0;
+ return 1;
+ } else if (*s > (0xff - 0x20) || *s < 0x20) {
+ d = s;
+ state = 2;
+ }
+ break;
+ case 1: // got telnet IAC
+ if (*s == IP) {
+ return -1; // ^C = logout
+ } else if (*s == DO) {
+ state = 4;
+ } else if (*s == WILL || *s == DONT || *s == WONT) {
+ state = 3; // this is cheesy
+ // but we aren't using em
+ } else if (*s == AYT) {
+ send(fd, (char *)ayt, sizeof ayt - 1, 0);
+ state = 2;
+ } else if (*s == EL) { // erase line
+ d = start;
+ state = 2;
+ } else { // dunno what it is
+ // so ignore it
+ state = 2;
+ }
+ break;
+ case 2: // we've skipped over something
+ // need to shuffle processed chars down
+ if (*s == IAC)
+ state = 1;
+ else if (*s == '\n') {
+ *d = '\0';
+ msnprintf(cs->com, ARRAY_SIZE(cs->com), "%s",
+ start);
+ if (bytes_received > 0)
+ memmove(start, s + 1, bytes_received);
+ con[who].state = 0;
+ con[who].numPending = bytes_received;
+ con[who].processed = 0;
+ con[who].outPos = 0;
+ return 1;
+ } else if (*s >= ' ')
+ *(d++) = *s;
+ break;
+ case 3: // some telnet junk we're ignoring
+ state = 2;
+ break;
+ case 4: // got IAC DO
+ if (*s == TELOPT_TM) {
+ if (send(fd, (char *)will_tm,
+ sizeof will_tm - 1, 0) == -1)
+ warn("%s: cannot send", __func__);
+ } else if (*s == TELOPT_SGA) {
+ if (send(fd, (char *)will_sga,
+ sizeof will_sga - 1, 0) == -1)
+ warn("%s: cannot send", __func__);
+ }
+ state = 2;
+ break;
+ }
+
+ s++;
+ } // while
+
+ if (state == 0)
+ d = s;
+ else if (state == 2)
+ state = 0;
+
+ con[who].state = state;
+ con[who].numPending = d - start;
+ con[who].processed = 1;
+
+ if (con[who].numPending == MAX_STRING_LENGTH - 1) { // buffer full
+ *d = '\0';
+ msnprintf(cs->com, ARRAY_SIZE(cs->com), "%s", start);
+ con[who].state = 0;
+ con[who].numPending = 0;
+ con[who].processed = 0;
+ return 1;
+ }
+ return 0;
+}
+
+PUBLIC int
+net_init(int p_port)
+{
+ int opt, ret;
+ struct linger lingeropt;
+ struct sockaddr_in serv_addr;
+
+ /*
+ * Although we have 256 descriptors to work with for opening
+ * files, we can only use 126 for sockets under SunOS 4.x.x
+ * socket libs. Using glibc can get you up to 256 again. Many
+ * OS's can do more than that!
+ * Sparky 9/20/95
+ */
+
+ if ((no_file = getdtablesize()) > MAX_PLAYER + 6)
+ no_file = MAX_PLAYER + 6;
+ max_connections = no_file - 6;
+
+ for (int i = 0; i < no_file; i++) {
+ con[i].status = NETSTAT_EMPTY;
+ con[i].sndbuf = NULL;
+ con[i].sndbufsize = con[i].sndbufpos = 0;
+ }
+
+ /* Open a TCP socket (an Internet stream socket) */
+ if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+ fprintf(stderr, "FICS: can't open stream socket\n");
+ return -1;
+ }
+
+ /* Bind our local address so that the client can send to us */
+ memset(&serv_addr, 0, sizeof serv_addr);
+ serv_addr.sin_family = AF_INET;
+ serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ serv_addr.sin_port = htons(p_port);
+
+ /*
+ * Attempt to allow rebinding to the port...
+ */
+
+ opt = 1;
+ ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *) &opt,
+ sizeof opt);
+ if (ret == -1)
+ warn("%s: SO_REUSEADDR", __func__);
+
+ opt = 1;
+ ret = setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt,
+ sizeof opt);
+ if (ret == -1)
+ warn("%s: SO_KEEPALIVE", __func__);
+
+ lingeropt.l_onoff = 0;
+ lingeropt.l_linger = 0;
+ ret = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, (char *) &lingeropt,
+ sizeof(lingeropt));
+ if (ret == -1)
+ warn("%s: SO_LINGER", __func__);
+
+ if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof serv_addr) < 0) {
+ fprintf(stderr, "FICS: can't bind local address. errno=%d\n",
+ errno);
+ return -1;
+ }
+
+ opt = 1;
+ ioctl(sockfd, FIONBIO, &opt);
+ listen(sockfd, 5);
+ return 0;
+}
+
+PUBLIC void
+net_close(void)
+{
+ for (int i = 0; i < no_file; i++) {
+ if (con[i].status != NETSTAT_EMPTY)
+ net_close_connection(con[i].fd);
+ }
+}
+
+PUBLIC void
+net_close_connection(int fd)
+{
+ if (con[fd].status == NETSTAT_CONNECTED)
+ net_flush_connection(fd);
+ if (!remConnection(fd)) {
+ if (fd > 2)
+ close(fd);
+ }
+}
+
+PUBLIC void
+turn_echo_on(int fd)
+{
+ int ret;
+ static unsigned char wont_echo[] = {IAC, WONT, TELOPT_ECHO, '\0'};
+
+ ret = send(fd, (char *)wont_echo, sizeof wont_echo - 1, 0);
+ if (ret == -1)
+ warn("%s: cannot send", __func__);
+}
+
+PUBLIC void
+turn_echo_off(int fd)
+{
+ int ret;
+ static unsigned char will_echo[] = {IAC, WILL, TELOPT_ECHO, '\0'};
+
+ ret = send(fd, (char *)will_echo, sizeof will_echo - 1, 0);
+ if (ret == -1)
+ warn("%s: cannot send", __func__);
+}
+
+PUBLIC unsigned int
+net_connected_host(int fd)
+{
+ int which;
+
+ if ((which = findConnection(fd)) < 0) {
+ fprintf(stderr, "FICS: FD not in connection table!\n");
+ return -1;
+ }
+ return con[which].fromHost;
+}
+
+PUBLIC void
+ngc2(comstr_t *cs, int timeout)
+{
+ fd_set readfds;
+ int fd, loop, nfound, lineComplete;
+ socklen_t cli_len = sizeof(struct sockaddr_in);
+ struct sockaddr_in cli_addr;
+ struct timeval to;
+
+ while ((fd = accept(sockfd, (struct sockaddr *) &cli_addr, &cli_len)) !=
+ -1) {
+ if (net_addConnection(fd, cli_addr.sin_addr.s_addr)) {
+ fprintf(stderr, "FICS is full. fd = %d.\n", fd);
+ psend_raw_file(fd, mess_dir, MESS_FULL);
+ close(fd);
+ } else {
+ process_new_connection(fd, net_connected_host(fd));
+ }
+ }
+
+ if (errno != EWOULDBLOCK) {
+ fprintf(stderr, "FICS: Problem with accept(). errno=%d\n",
+ errno);
+ }
+ net_flush_all_connections();
+
+ FD_ZERO(&readfds);
+ for (loop = 0; loop < no_file; loop++) {
+ if (con[loop].status != NETSTAT_EMPTY)
+ FD_SET(con[loop].fd, &readfds);
+ }
+
+ to.tv_usec = 0;
+ to.tv_sec = timeout;
+
+ nfound = select(no_file, &readfds, NULL, NULL, &to);
+
+ /* XXX: unused */
+ (void) nfound;
+
+ for (loop = 0; loop < no_file; loop++) {
+ if (con[loop].status != NETSTAT_EMPTY) {
+ fd = con[loop].fd;
+
+ if ((lineComplete = readline2(cs, fd)) == 0) {
+ // partial line: do nothing
+ continue;
+ }
+
+ if (lineComplete > 0) { // complete line: process it
+#ifdef TIMESEAL
+ if (!parseInput(cs->com, &con[loop]))
+ continue;
+#endif
+ if (process_input(fd, cs->com) != COM_LOGOUT) {
+ net_flush_connection(fd);
+ continue;
+ }
+ }
+
+ /*
+ * Disconnect anyone who gets here
+ */
+ process_disconnection(fd);
+ net_close_connection(fd);
+ }
+ }
+}
+
+PUBLIC int
+net_consize(void)
+{
+ int total = sizeof con;
+
+ for (int i = 0; i < no_file; i++)
+ total += con[i].sndbufsize;
+ return total;
+}
diff --git a/FICS/network.h b/FICS/network.h
new file mode 100644
index 0000000..2225734
--- /dev/null
+++ b/FICS/network.h
@@ -0,0 +1,96 @@
+/* network.h
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 23/12/13 Cleaned up the file
+*/
+
+#ifndef _NETWORK_H
+#define _NETWORK_H
+
+#include "command.h" /* For MAX_STRING_LENGTH */
+
+#define NET_NETERROR 0
+#define NET_NEW 1
+#define NET_DISCONNECT 2
+#define NET_READLINE 3
+#define NET_TIMEOUT 4
+#define NET_NOTCOMPLETE 5
+
+#define LINE_WIDTH 80
+
+#ifndef O_NONBLOCK
+#define O_NONBLOCK 00004
+#endif
+
+#define NETSTAT_EMPTY 0
+#define NETSTAT_CONNECTED 1
+#define NETSTAT_IDENT 2
+
+typedef struct _connection {
+ int fd;
+ int outFd;
+ int status;
+ unsigned int fromHost;
+
+#ifdef TIMESEAL
+ char sys[512];
+ char user[512];
+ int time;
+ int timeseal;
+#endif
+
+ /* Input buffering */
+ int numPending;
+ int processed;
+ unsigned char inBuf[MAX_STRING_LENGTH];
+
+ /* Output buffering */
+ char *sndbuf; /* our send buffer, or NULL if none yet */
+ int outPos; /* column count */
+ int sndbufpos; /* position in send buffer */
+ int sndbufsize; /* size of send buffer (this changes) */
+ int state; /* 'telnet state' */
+
+ /* identd stuff */
+ char ident[20];
+ int mypal;
+} connection;
+
+extern connection con[512];
+
+extern int no_file;
+extern int max_connections;
+
+extern int findConnection(int);
+extern int net_addConnection(int, unsigned int);
+extern int net_consize(void);
+extern int net_init(int);
+extern int net_send_string(int, char *, int);
+extern int readline2(comstr_t *, int);
+extern unsigned int
+ net_connected_host(int);
+extern void net_close(void);
+extern void net_close_connection(int);
+extern void ngc2(comstr_t *, int);
+extern void turn_echo_off(int);
+extern void turn_echo_on(int);
+#endif /* _NETWORK_H */
diff --git a/FICS/obsproc.c b/FICS/obsproc.c
new file mode 100644
index 0000000..8c7194c
--- /dev/null
+++ b/FICS/obsproc.c
@@ -0,0 +1,2052 @@
+/* obsproc.c
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Dave Herscovici 95/11/26 Created
+ Markus Uhlin 23/12/13 Fixed bugs
+ Markus Uhlin 23/12/13 Reformatted functions
+ Markus Uhlin 24/04/28 Completed reformatting
+ Markus Uhlin 24/04/28 Replaced unbounded string
+ handling functions.
+ Markus Uhlin 24/04/29 Fixed compiler warnings
+ Markus Uhlin 24/07/07 Fixed unhandled return values of
+ fscanf().
+ Markus Uhlin 24/12/02 Improved old_mail_moves()
+ Markus Uhlin 25/01/18 Fixed -Wshadow
+ Markus Uhlin 25/03/15 Fixed possible buffer overflow
+ in FindHistory2().
+ Markus Uhlin 25/04/06 Fixed Clang Tidy warnings.
+*/
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include <err.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#include "command.h"
+#include "comproc.h"
+#include "config.h"
+#include "eco.h"
+#include "ficsmain.h"
+#include "formula.h"
+#include "gamedb.h"
+#include "gameproc.h"
+#include "matchproc.h"
+#include "maxxes-utils.h"
+#include "movecheck.h"
+#include "network.h"
+#include "obsproc.h"
+#include "playerdb.h"
+#include "ratings.h"
+#include "rmalloc.h"
+#include "utils.h"
+
+PUBLIC int
+GameNumFromParam(int p, int *p1, parameter *param)
+{
+ if (param->type == TYPE_WORD) {
+ *p1 = player_find_part_login(param->val.word);
+
+ if (*p1 < 0) {
+ pprintf(p, "No user named \"%s\" is logged in.\n",
+ param->val.word);
+ return -1;
+ }
+
+ if (parray[*p1].game < 0) {
+ pprintf(p, "%s is not playing a game.\n",
+ parray[*p1].name);
+ }
+
+ return parray[*p1].game;
+ } else { // Must be an integer
+ *p1 = -1;
+
+ if (param->val.integer <= 0) {
+ pprintf(p, "%d is not a valid game number.\n",
+ param->val.integer);
+ }
+
+ return (param->val.integer - 1);
+ }
+}
+
+PRIVATE int
+gamesortfunc(const void *i, const void *j)
+{
+ const int x = *(int *)i;
+ const int y = *(int *)j;
+
+ /*
+ * examine mode games moved to top of "games" output
+ */
+ return (GetRating(&parray[garray[x].white],
+ garray[x].type) +
+ GetRating(&parray[garray[x].black],
+ garray[x].type) -
+ (garray[x].status == GAME_EXAMINE ? 10000 : 0) -
+ GetRating(&parray[garray[y].white],
+ garray[y].type) -
+ GetRating(&parray[garray[y].black],
+ garray[y].type) +
+ (garray[y].status == GAME_EXAMINE ? 10000 : 0));
+}
+
+PUBLIC int
+com_games(int p, param_list param)
+{
+ char *s = NULL;
+ int *sortedgames; // for qsort
+ int count = 0;
+ int i, j;
+ int selected = 0;
+ int slen = 0;
+ int totalcount;
+ int wp, bp;
+ int ws, bs;
+
+ totalcount = game_count();
+
+ if (totalcount == 0) {
+ pprintf(p, "There are no games in progress.\n");
+ } else {
+ // for qsort
+ sortedgames = reallocarray(NULL, totalcount, sizeof(int));
+
+ if (sortedgames == NULL)
+ err(1, "%s", __func__);
+ else
+ malloc_count++;
+
+ if (param[0].type == TYPE_WORD) {
+ s = param[0].val.word;
+ slen = strlen(s);
+
+ if ((selected = atoi(s)) < 0)
+ selected = 0;
+ }
+
+ for (i = 0; i < g_num; i++) {
+ if (garray[i].status != GAME_ACTIVE &&
+ garray[i].status != GAME_EXAMINE)
+ continue;
+ if ((selected) && (selected != i + 1))
+ continue; // not selected game number
+
+ wp = garray[i].white;
+ bp = garray[i].black;
+
+ UNUSED_VAR(wp);
+ UNUSED_VAR(bp);
+
+ if ((!selected) &&
+ s &&
+ strncasecmp(s, garray[i].white_name, slen) &&
+ strncasecmp(s, garray[i].black_name, slen))
+ continue; // player names did not match
+ sortedgames[count++] = i;
+ } /* for */
+
+ if (!count) {
+ pprintf(p, "No matching games were found "
+ "(of %d in progress).\n", totalcount);
+ } else {
+ qsort(sortedgames, count, sizeof(int), gamesortfunc);
+ pprintf(p, "\n");
+
+ for (j = 0; j < count; j++) {
+ i = sortedgames[j];
+
+ wp = garray[i].white;
+ bp = garray[i].black;
+
+ board_calc_strength(&garray[i].game_state,
+ &ws, &bs);
+
+ if (garray[i].status != GAME_EXAMINE) {
+ pprintf_noformat(p, "%2d %4s %-11.11s "
+ "%4s %-10.10s [%c%c%c%3d %3d] ",
+ (i + 1),
+ ratstrii(GetRating(&parray[wp],
+ garray[i].type),
+ parray[wp].registered),
+ parray[wp].name,
+ ratstrii(GetRating(&parray[bp],
+ garray[i].type),
+ parray[bp].registered),
+ parray[bp].name,
+ (garray[i].private ? 'p' : ' '),
+ *bstr[garray[i].type],
+ *rstr[garray[i].rated],
+ (garray[i].wInitTime / 600),
+ (garray[i].wIncrement / 10));
+
+ game_update_time(i);
+
+ pprintf_noformat(p, "%6s -",
+ tenth_str((garray[i].wTime > 0 ?
+ garray[i].wTime : 0), 0));
+
+ pprintf_noformat(p, "%6s (%2d-%2d) "
+ "%c: %2d\n",
+ tenth_str((garray[i].bTime > 0 ?
+ garray[i].bTime : 0), 0),
+ ws,
+ bs,
+ (garray[i].game_state.onMove ==
+ WHITE ? 'W' : 'B'),
+ garray[i].game_state.moveNum);
+ } else {
+ pprintf_noformat(p, "%2d "
+ "(Exam. %4d %-11.11s %4d %-10.10s) "
+ "[%c%c%c%3d %3d] ",
+ (i + 1),
+ garray[i].white_rating,
+ garray[i].white_name,
+ garray[i].black_rating,
+ garray[i].black_name,
+ (garray[i].private ? 'p' : ' '),
+ *bstr[garray[i].type],
+ *rstr[garray[i].rated],
+ (garray[i].wInitTime / 600),
+ (garray[i].wIncrement / 10));
+
+ pprintf_noformat(p, "%c: %2d\n",
+ (garray[i].game_state.onMove ==
+ WHITE ? 'W' : 'B'),
+ garray[i].game_state.moveNum);
+ }
+ } /* for */
+
+ if (count < totalcount) {
+ pprintf(p, "\n %d game%s displayed "
+ "(of %d in progress).\n",
+ count,
+ (count == 1 ? "" : "s"),
+ totalcount);
+ } else {
+ pprintf(p, "\n %d game%s displayed.\n",
+ totalcount, (totalcount == 1 ? "" : "s"));
+ }
+ } /* else */
+
+ rfree(sortedgames);
+ }
+
+ return COM_OK;
+}
+
+PRIVATE int
+do_observe(int p, int obgame)
+{
+ if (garray[obgame].private &&
+ parray[p].adminLevel < ADMIN_ADMIN) {
+ pprintf(p, "Sorry, game %d is a private game.\n", (obgame + 1));
+ return COM_OK;
+ }
+
+ if (garray[obgame].white == p ||
+ garray[obgame].black == p) {
+ if (garray[obgame].status != GAME_EXAMINE) {
+ pprintf(p, "You cannot observe a game that you are "
+ "playing.\n");
+ return COM_OK;
+ }
+ }
+
+ if (player_is_observe(p, obgame)) {
+ pprintf(p, "Removing game %d from observation list.\n",
+ (obgame + 1));
+ player_remove_observe(p, obgame);
+ } else {
+ if (!player_add_observe(p, obgame)) {
+ pprintf(p, "You are now observing game %d.\n",
+ (obgame + 1));
+ send_board_to(obgame, p);
+ } else {
+ pprintf(p, "You are already observing the maximum "
+ "number of games.\n");
+ }
+ }
+
+ return COM_OK;
+}
+
+PUBLIC void
+unobserveAll(int p)
+{
+ for (int i = 0; i < parray[p].num_observe; i++) {
+ pprintf(p, "Removing game %d from observation list.\n",
+ (parray[p].observe_list[i] + 1));
+ }
+
+ parray[p].num_observe = 0;
+}
+
+PUBLIC int
+com_unobserve(int p, param_list param)
+{
+ int gNum, p1;
+
+ if (param[0].type == TYPE_NULL) {
+ unobserveAll(p);
+ return COM_OK;
+ }
+
+ if ((gNum = GameNumFromParam(p, &p1, &param[0])) < 0)
+ return COM_OK;
+
+ if (!player_is_observe(p, gNum)) {
+ pprintf(p, "You are not observing game %d.\n", gNum);
+ } else {
+ player_remove_observe(p, gNum);
+ pprintf(p, "Removing game %d from observation list.\n",
+ (gNum + 1));
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_observe(int p, param_list param)
+{
+ int i;
+ int p1, obgame;
+
+ if (parray[p].game >= 0 && garray[parray[p].game].status ==
+ GAME_EXAMINE) {
+ pprintf(p, "You are still examining a game.\n");
+ return COM_OK;
+ }
+
+ if (param[0].type == TYPE_NULL) {
+ unobserveAll(p);
+ return COM_OK;
+ }
+
+ if ((obgame = GameNumFromParam(p, &p1, &param[0])) < 0)
+ return COM_OK;
+
+ if ((obgame >= g_num) ||
+ ((garray[obgame].status != GAME_ACTIVE) &&
+ (garray[obgame].status != GAME_EXAMINE))) {
+ pprintf(p, "There is no such game.\n");
+ return COM_OK;
+ }
+
+ if (p1 >= 0 && parray[p1].simul_info.numBoards) {
+ for (i = 0; i < parray[p1].simul_info.numBoards; i++)
+ if (parray[p1].simul_info.boards[i] >= 0)
+ do_observe(p, parray[p1].simul_info.boards[i]);
+ } else {
+ do_observe(p, obgame);
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_allobservers(int p, param_list param)
+{
+ int first;
+ int g;
+ int obgame;
+ int p1;
+ int start, end;
+
+ if (param[0].type == TYPE_NULL) {
+ obgame = -1;
+ } else {
+ obgame = GameNumFromParam(p, &p1, &param[0]);
+
+ if (obgame < 0)
+ return COM_OK;
+ }
+
+ if (obgame == -1) {
+ start = 0;
+ end = g_num;
+ } else if ((obgame >= g_num) ||
+ (garray[obgame].status != GAME_ACTIVE &&
+ garray[obgame].status != GAME_EXAMINE)) {
+ pprintf(p, "There is no such game.\n");
+ return COM_OK;
+ } else {
+ start = obgame;
+ end = obgame + 1;
+ }
+
+ /*
+ * list games being played
+ */
+ for (g = start; g < end; g++) {
+ if ((garray[g].status == GAME_ACTIVE) &&
+ ((parray[p].adminLevel > 0) ||
+ (garray[g].private == 0))) {
+ for (first = 1, p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].status != PLAYER_EMPTY &&
+ player_is_observe(p1, g)) {
+ if (first) {
+ pprintf(p, "Observing %2d "
+ "[%s vs. %s]:",
+ (g + 1),
+ parray[garray[g].white].name,
+ parray[garray[g].black].name);
+ first = 0;
+ }
+
+ pprintf(p, " %s%s",
+ (parray[p1].game >= 0 ? "#" : ""),
+ parray[p1].name);
+ }
+ }
+
+ if (!first)
+ pprintf(p, "\n");
+ }
+ } /* for */
+
+ /*
+ * list games being examined last
+ */
+ for (g = start; g < end; g++) {
+ if ((garray[g].status == GAME_EXAMINE) &&
+ ((parray[p].adminLevel > 0) ||
+ (garray[g].private == 0))) {
+ for (first = 1, p1 = 0; p1 < p_num; p1++) {
+ if ((parray[p1].status != PLAYER_EMPTY) &&
+ (player_is_observe(p1, g) ||
+ (parray[p1].game == g))) {
+ if (first) {
+ if (strcmp(garray[g].white_name,
+ garray[g].black_name)) {
+ pprintf(p, "Examining "
+ "%2d [%s vs %s]:",
+ (g + 1),
+ garray[g].white_name,
+ garray[g].black_name);
+ } else {
+ pprintf(p, "Examining "
+ "%2d (scratch):",
+ (g + 1));
+ }
+
+ first = 0;
+ }
+
+ pprintf(p, " %s%s",
+ (parray[p1].game == g ? "#" : ""),
+ parray[p1].name);
+ }
+ }
+
+ if (!first)
+ pprintf(p, "\n");
+ }
+ } /* for */
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_unexamine(int p, param_list param)
+{
+ int g, p1, flag = 0;
+
+ if (parray[p].game < 0 || garray[parray[p].game].status !=
+ GAME_EXAMINE) {
+ pprintf(p, "You are not examining any games.\n");
+ return COM_OK;
+ }
+
+ g = parray[p].game;
+ parray[p].game = -1;
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+
+ if (parray[p1].game == g && p != p1) {
+ /*
+ * ok - there are other examiners to take over
+ * the game.
+ */
+ flag = 1;
+ }
+
+ if (player_is_observe(p1, g) || parray[p1].game == g) {
+ pprintf(p1, "%s stopped examining game %d.\n",
+ parray[p].name, (g + 1));
+ }
+ }
+
+ if (!flag) {
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ if (player_is_observe(p1, g)) {
+ pprintf(p1, "There are no examiners.\n");
+ pcommand(p1, "unobserve %d", (g + 1));
+ }
+ }
+
+ game_remove(g);
+ }
+
+ pprintf(p, "You are no longer examining game %d.\n", (g + 1));
+ return COM_OK;
+}
+
+PUBLIC int
+com_mexamine(int p, param_list param)
+{
+ int g, p1, p2;
+
+ if (parray[p].game < 0 || garray[parray[p].game].status !=
+ GAME_EXAMINE) {
+ pprintf(p, "You are not examining any games.\n");
+ return COM_OK;
+ }
+
+ if ((p1 = player_find_part_login(param[0].val.word)) < 0) {
+ pprintf(p, "No user named \"%s\" is logged in.\n",
+ param[0].val.word);
+ return COM_OK;
+ }
+
+ g = parray[p].game;
+
+ if (!player_is_observe(p1, g)) {
+ pprintf(p, "%s must observe the game you are analysing.\n",
+ parray[p1].name);
+ return COM_OK;
+ } else {
+ if (parray[p1].game >= 0) {
+ pprintf(p, "%s is already analysing the game.\n",
+ parray[p1].name);
+ return COM_OK;
+ }
+
+ /*
+ * If we get here - let's make him examiner of the
+ * game.
+ */
+ unobserveAll(p1); // Fix for Xboard
+
+ player_decline_offers(p1, -1, PEND_MATCH);
+ player_withdraw_offers(p1, -1, PEND_MATCH);
+ player_withdraw_offers(p1, -1, PEND_SIMUL);
+
+ parray[p1].game = g;
+ pprintf(p1, "You are now examiner of game %d.\n", (g + 1));
+ send_board_to(g, p1); // Pos not changed - but fixes Xboard
+
+ for (p2 = 0; p2 < p_num; p2++) {
+ if (parray[p2].status != PLAYER_PROMPT)
+ continue;
+ if (p2 == p1)
+ continue;
+ if (player_is_observe(p2, g) || parray[p2].game == g) {
+ pprintf_prompt(p2, "%s is now examiner of "
+ "game %d.\n", parray[p1].name, (g + 1));
+ }
+ }
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_moves(int p, param_list param)
+{
+ int g;
+ int p1;
+
+ if (param[0].type == TYPE_NULL) {
+ if (parray[p].game >= 0) {
+ g = parray[p].game;
+ } else if (parray[p].num_observe) {
+ for (g = 0; g < parray[p].num_observe; g++) {
+ pprintf(p, "%s\n",
+ movesToString(parray[p].observe_list[g],
+ 0));
+ }
+
+ return COM_OK;
+ } else {
+ pprintf(p, "You are neither playing, observing nor "
+ "examining a game.\n");
+ return COM_OK;
+ }
+ } else {
+ g = GameNumFromParam(p, &p1, &param[0]);
+
+ if (g < 0)
+ return COM_OK;
+ }
+
+ if ((g < 0) ||
+ (g >= g_num) ||
+ ((garray[g].status != GAME_ACTIVE) &&
+ (garray[g].status != GAME_EXAMINE))) {
+ pprintf(p, "There is no such game.\n");
+ return COM_OK;
+ }
+
+ if ((garray[g].white != p) &&
+ (garray[g].black != p) &&
+ (garray[g].private) &&
+ (parray[p].adminLevel < ADMIN_ADMIN)) {
+ pprintf(p, "Sorry, that is a private game.\n");
+ return COM_OK;
+ }
+
+ pprintf(p, "%s\n", movesToString(g, 0)); // pgn may break interfaces?
+ return COM_OK;
+}
+
+PUBLIC int
+com_mailmoves(int p, param_list param)
+{
+ char subj[81] = { '\0' };
+ int g;
+ int p1;
+
+ if (!parray[p].registered) {
+ pprintf(p, "Unregistered players cannot use mailmoves.\n");
+ return COM_OK;
+ }
+
+ if (param[0].type == TYPE_NULL) {
+ if (parray[p].game >= 0) {
+ g = parray[p].game;
+ } else {
+ pprintf(p, "You are neither playing, observing nor "
+ "examining a game.\n");
+ return COM_OK;
+ }
+ } else {
+ g = GameNumFromParam(p, &p1, &param[0]);
+
+ if (g < 0)
+ return COM_OK;
+ }
+
+ if ((g < 0) ||
+ (g >= g_num) ||
+ ((garray[g].status != GAME_ACTIVE) &&
+ (garray[g].status != GAME_EXAMINE))) {
+ pprintf(p, "There is no such game.\n");
+ return COM_OK;
+ }
+
+ if ((garray[g].white != p) &&
+ (garray[g].black != p) &&
+ (garray[g].private) &&
+ (parray[p].adminLevel < ADMIN_ADMIN)) {
+ pprintf(p, "Sorry, that is a private game.\n");
+ return COM_OK;
+ }
+
+ msnprintf(subj, sizeof subj, "FICS game report %s vs %s",
+ garray[g].white_name,
+ garray[g].black_name);
+
+ if (mail_string_to_user(p, subj, movesToString(g, parray[p].pgn))) {
+ pprintf(p, "Moves NOT mailed, perhaps your address is "
+ "incorrect.\n");
+ } else {
+ pprintf(p, "Moves mailed.\n");
+ }
+
+ return COM_OK;
+}
+
+PRIVATE int
+old_mail_moves(int p, int mail, param_list param)
+{
+ FILE *fp = NULL;
+ char fname[MAX_FILENAME_SIZE] = { '\0' };
+ char tmp[2048] = { '\0' };
+ char *ptmp = tmp;
+ int count = 0;
+ int p1, connected;
+
+ if (mail && (!parray[p].registered)) {
+ pprintf(p, "Unregistered players cannot use mailoldmoves.\n");
+ return COM_OK;
+ }
+
+ if (param[0].type == TYPE_WORD) {
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+ } else {
+ p1 = p;
+ connected = 1;
+ }
+
+ (void) snprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s",
+ stats_dir, parray[p1].login[0], parray[p1].login, STATS_GAMES);
+ fp = fopen(fname, "r"); // old moves now looks in history to save mem
+ // - DAV
+
+ if (!fp) {
+ pprintf(p, "There is no old game for %s.\n", parray[p1].name);
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ while (fgets(tmp, sizeof tmp, fp) != NULL) {
+ /* null */;
+ }
+
+ if (sscanf(ptmp, "%d", &count) != 1) {
+ warnx("%s: sscanf() error", __func__);
+ fclose(fp);
+ return COM_FAILED;
+ }
+
+ fclose(fp);
+ pprintf(p, "Last game for %s was history game %d.\n", parray[p1].name,
+ count);
+
+ if (mail)
+ pcommand(p, "mailstored %s %d", parray[p1].name, count);
+ else
+ pcommand(p, "smoves %s %d", parray[p1].name, count);
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+}
+
+PUBLIC int
+com_oldmoves(int p, param_list param)
+{
+ return old_mail_moves(p, 0, param);
+}
+
+PUBLIC int
+com_mailoldmoves(int p, param_list param)
+{
+ return old_mail_moves(p, 1, param);
+}
+
+PUBLIC void
+ExamineScratch(int p, param_list param)
+{
+ char *val;
+ char board[100];
+ char category[100];
+ char parsebuf[100];
+ int confused = 0;
+ int g = game_new();
+
+ unobserveAll(p);
+
+ player_decline_offers(p, -1, PEND_MATCH);
+ player_withdraw_offers(p, -1, PEND_MATCH);
+ player_withdraw_offers(p, -1, PEND_SIMUL);
+
+ garray[g].bInitTime = garray[g].bIncrement = 0;
+ garray[g].wInitTime = garray[g].wIncrement = 0;
+
+ garray[g].startTime = tenth_secs();
+ garray[g].timeOfStart = tenth_secs();
+
+ garray[g].clockStopped = 0;
+ garray[g].lastDecTime = garray[g].startTime;
+ garray[g].lastMoveTime = garray[g].startTime;
+ garray[g].rated = 0;
+ garray[g].status = GAME_EXAMINE;
+ garray[g].totalHalfMoves = 0;
+ garray[g].type = TYPE_UNTIMED;
+ garray[g].wTime = garray[g].bTime = 0;
+ garray[g].white = garray[g].black = p;
+ parray[p].game = g;
+ parray[p].side = WHITE;
+
+ board[0] = '\0';
+ category[0] = '\0';
+
+ if (param[0].val.string != parray[p].name &&
+ param[1].type == TYPE_WORD) {
+ mstrlcpy(category, param[0].val.string, sizeof category);
+ mstrlcpy(board, param[1].val.string, sizeof board);
+ } else if (param[1].type != TYPE_NULL) {
+ val = param[1].val.string;
+
+ while (!confused && sscanf(val, " %99s", parsebuf) == 1) {
+ val = eatword(eatwhite(val));
+
+ if (category[0] != '\0' && board[0] == '\0') {
+ mstrlcpy(board, parsebuf, sizeof board);
+ } else if (isdigit(*parsebuf)) {
+ pprintf(p, "You can't specify time controls."
+ "\n");
+ return;
+ } else if (category[0] == '\0') {
+ mstrlcpy(category, parsebuf, sizeof category);
+ } else {
+ confused = 1;
+ }
+ }
+
+ if (confused) {
+ pprintf(p, "Can't interpret %s in match command.\n",
+ parsebuf);
+ return;
+ }
+ }
+
+ if (category[0] && !board[0]) {
+ pprintf(p, "You must specify a board and a category.\n");
+ return;
+ }
+
+ pprintf(p, "Starting a game in examine (scratch) mode.\n");
+
+ if (category[0]) {
+ pprintf(p, "Loading from catagory: %s, board: %s.\n",
+ category,
+ board);
+ }
+ if (board_init(&garray[g].game_state, category, board)) {
+ pprintf(p, "PROBLEM LOADING BOARD. Game Aborted.\n");
+ fprintf(stderr, "FICS: PROBLEM LOADING BOARD. Game Aborted.\n");
+ return;
+ }
+
+ garray[g].game_state.gameNum = g;
+ mstrlcpy(garray[g].white_name, parray[p].name,
+ sizeof(garray[g].white_name));
+ mstrlcpy(garray[g].black_name, parray[p].name,
+ sizeof(garray[g].black_name));
+ garray[g].white_rating = garray[g].black_rating =
+ parray[p].s_stats.rating;
+ send_boards(g);
+ MakeFENpos(g, (char *)garray[g].FENstartPos,
+ ARRAY_SIZE(garray[g].FENstartPos));
+}
+
+PRIVATE int
+ExamineStored(FILE *fp, int p, char *filename)
+{
+ char board[100];
+ char category[100];
+ game *gg;
+ int g;
+
+ unobserveAll(p);
+
+ player_decline_offers(p, -1, PEND_MATCH);
+ player_withdraw_offers(p, -1, PEND_MATCH);
+ player_withdraw_offers(p, -1, PEND_SIMUL);
+
+ g = game_new();
+ gg = &garray[g];
+
+ board[0] = '\0';
+ category[0] = '\0';
+
+ if (board_init(&gg->game_state, category, board)) {
+ pprintf(p, "PROBLEM LOADING BOARD. Game Aborted.\n");
+ fprintf(stderr, "FICS: PROBLEM LOADING BOARD %s %s. "
+ "Game Aborted.\n", category, board);
+ return -1;
+ }
+
+ gg->status = GAME_EXAMINE;
+
+ if (ReadGameAttrs(fp, filename, g) < 0) {
+ pprintf(p, "Gamefile is corrupt; please notify an admin.\n");
+ return -1;
+ }
+
+ gg->startTime = tenth_secs();
+ gg->lastDecTime = gg->startTime;
+ gg->lastMoveTime = gg->startTime;
+
+ gg->totalHalfMoves = gg->numHalfMoves;
+ gg->numHalfMoves = 0;
+ gg->revertHalfMove = 0;
+
+ gg->black = p;
+ gg->white = p;
+ gg->game_state.gameNum = g;
+
+ parray[p].game = g;
+ parray[p].side = WHITE;
+
+ send_boards(g);
+ MakeFENpos(g, (char *)garray[g].FENstartPos,
+ ARRAY_SIZE(garray[g].FENstartPos));
+
+ return g;
+}
+
+PRIVATE void
+ExamineAdjourned(int p, int p1, int p2)
+{
+ FILE *fp;
+ char *p1Login, *p2Login;
+ char filename[1024] = { '\0' };
+ int g;
+
+ p1Login = parray[p1].login;
+ p2Login = parray[p2].login;
+
+ (void)snprintf(filename, sizeof filename, "%s/%c/%s-%s",
+ adj_dir, *p1Login, p1Login, p2Login);
+ fp = fopen(filename, "r");
+
+ if (!fp) {
+ (void)snprintf(filename, sizeof filename, "%s/%c/%s-%s",
+ adj_dir, *p2Login, p1Login, p2Login);
+ fp = fopen(filename, "r");
+
+ if (!fp) {
+ (void)snprintf(filename, sizeof filename,
+ "%s/%c/%s-%s",
+ adj_dir, *p2Login, p2Login, p1Login);
+ fp = fopen(filename, "r");
+
+ if (!fp) {
+ (void)snprintf(filename, sizeof filename,
+ "%s/%c/%s-%s",
+ adj_dir, *p1Login, p2Login, p1Login);
+ fp = fopen(filename, "r");
+
+ if (!fp) {
+ pprintf(p, "No stored game between "
+ "\"%s\" and \"%s\".\n",
+ parray[p1].name,
+ parray[p2].name);
+ return;
+ }
+ }
+ }
+ }
+
+ g = ExamineStored(fp, p, filename);
+ fclose(fp);
+
+ if (g >= 0) {
+ if (garray[g].white_name[0] == '\0') {
+ mstrlcpy(garray[g].white_name, p1Login,
+ sizeof(garray[g].white_name));
+ }
+ if (garray[g].black_name[0] == '\0') {
+ mstrlcpy(garray[g].black_name, p2Login,
+ sizeof(garray[g].black_name));
+ }
+ }
+}
+
+PRIVATE char *
+FindHistory(int p, int p1, int p_game)
+{
+ FILE *fpHist;
+ int index = 0;
+ long int when = 0;
+ static char fileName[MAX_FILENAME_SIZE];
+
+ msnprintf(fileName, sizeof fileName, "%s/player_data/%c/%s.%s",
+ stats_dir, parray[p1].login[0], parray[p1].login, STATS_GAMES);
+
+ if ((fpHist = fopen(fileName, "r")) == NULL) {
+ pprintf(p, "No games in history for %s.\n", parray[p1].name);
+ return NULL;
+ }
+
+ do {
+ int ret;
+
+ ret = fscanf(fpHist, "%d %*c %*d %*c %*d %*s %*s %*d %*d %*d "
+ "%*d %*s %*s %ld", &index, &when);
+ if (ret != 2) {
+ warnx("%s: %s: corrupt", __func__, fileName);
+ fclose(fpHist);
+ return NULL;
+ }
+ } while (!feof(fpHist) &&
+ !ferror(fpHist) &&
+ index != p_game);
+
+ if (feof(fpHist) || ferror(fpHist)) {
+ pprintf(p, "There is no history game %d for %s.\n", p_game,
+ parray[p1].name);
+ fclose(fpHist);
+ return NULL;
+ }
+
+ fclose(fpHist);
+
+ if (when < 0 || when >= LONG_MAX) {
+ pprintf(p, "Corrupt history data for %s (invalid timestamp).\n",
+ parray[p1].name);
+ return NULL;
+ }
+
+ msnprintf(fileName, sizeof fileName, "%s/%ld/%ld", hist_dir,
+ (when % 100), when);
+ return (&fileName[0]);
+}
+
+PRIVATE char *
+FindHistory2(int p, int p1, int p_game, char *End, const size_t End_size)
+{
+ FILE *fpHist;
+ char fmt[80] = { '\0' };
+ char *resolvedPath;
+ int index = 0;
+ long int when = 0;
+ static char fileName[MAX_FILENAME_SIZE];
+
+ msnprintf(fileName, sizeof fileName, "%s/player_data/%c/%s.%s",
+ stats_dir, parray[p1].login[0], parray[p1].login, STATS_GAMES);
+
+ if ((fpHist = fopen(fileName, "r")) == NULL) {
+ pprintf(p, "No games in history for %s.\n", parray[p1].name);
+ return NULL;
+ }
+
+ msnprintf(fmt, sizeof fmt, "%%d %%*c %%*d %%*c %%*d %%*s %%*s %%*d "
+ "%%*d %%*d %%*d %%*s %%%zus %%ld\n", (End_size - 1));
+
+ do {
+ if (fscanf(fpHist, fmt, &index, End, &when) != 3) {
+ warnx("%s: %s: corrupt", __func__, fileName);
+ fclose(fpHist);
+ return NULL;
+ }
+ } while (!feof(fpHist) &&
+ !ferror(fpHist) &&
+ index != p_game);
+
+ if (feof(fpHist) || ferror(fpHist)) {
+ pprintf(p, "There is no history game %d for %s.\n", p_game,
+ parray[p1].name);
+ fclose(fpHist);
+ return NULL;
+ }
+
+ fclose(fpHist);
+
+ if (when < 0 || when >= LONG_MAX) {
+ pprintf(p, "Invalid history timestamp for %s.\n",
+ parray[p1].name);
+ return NULL;
+ }
+
+ msnprintf(fileName, sizeof fileName, "%s/%ld/%ld", hist_dir,
+ (when % 100), when);
+
+ // Validate that the resolved path is within hist_dir
+ if ((resolvedPath = realpath(fileName, NULL)) == NULL) {
+ warn("%s: realpath", __func__);
+ return NULL;
+ }
+
+ if (strncmp(resolvedPath, hist_dir, strlen(hist_dir)) != 0) {
+ warnx("%s: path traversal detected", __func__);
+ free(resolvedPath);
+ return NULL;
+ }
+
+ // Copy 'resolvedPath' back to 'fileName' for return
+ mstrlcpy(fileName, resolvedPath, sizeof fileName);
+ free(resolvedPath);
+
+ return (&fileName[0]);
+}
+
+PRIVATE void
+ExamineHistory(int p, int p1, int p_game)
+{
+ FILE *fpGame;
+ char *fileName;
+
+ if ((fileName = FindHistory(p, p1, p_game)) != NULL) {
+ if ((fpGame = fopen(fileName, "r")) == NULL) {
+ pprintf(p, "History game %d not available for %s.\n",
+ p_game, parray[p1].name);
+ } else {
+ ExamineStored(fpGame, p, fileName);
+ fclose(fpGame);
+ }
+ }
+}
+
+PRIVATE void
+ExamineJournal(int p, int p1, char slot)
+{
+ FILE *fpGame;
+ char *name_from = parray[p1].login;
+ char fname[MAX_FILENAME_SIZE] = { '\0' };
+
+ if ((parray[p1].jprivate) &&
+ (p != p1) &&
+ (parray[p].adminLevel < ADMIN_ADMIN)) {
+ pprintf(p, "Sorry, this journal is private.\n");
+ return;
+ }
+
+ if (((slot - 'a' - 1) > MAX_JOURNAL) &&
+ (parray[p1].adminLevel < ADMIN_ADMIN) &&
+ (!titled_player(p, parray[p1].login))) {
+ pprintf(p, "%s's maximum journal entry is %c\n",
+ parray[p1].name,
+ toupper((char)(MAX_JOURNAL + 'A' - 1)));
+ return;
+ }
+
+ msnprintf(fname, sizeof fname, "%s/%c/%s.%c", journal_dir, name_from[0],
+ name_from, slot);
+
+ if ((fpGame = fopen(fname, "r")) == NULL) {
+ pprintf(p, "Journal entry %c is not available for %s.\n",
+ toupper(slot),
+ parray[p1].name);
+ } else {
+ ExamineStored(fpGame, p, fname);
+ fclose(fpGame);
+ }
+}
+
+PUBLIC int
+com_examine(int p, param_list param)
+{
+ char *param2string;
+ char fname[MAX_FILENAME_SIZE] = { '\0' };
+ int p1, p2 = p, p1conn, p2conn = 1;
+
+ if (parray[p].game >= 0 && garray[parray[p].game].status ==
+ GAME_EXAMINE) {
+ pprintf(p, "You are already examining a game.\n");
+ } else if (parray[p].game >= 0) {
+ pprintf(p, "You are playing a game.\n");
+ } else if (param[0].type == TYPE_NULL) {
+ ExamineScratch(p, param);
+ } else if (param[0].type == TYPE_WORD) {
+ if (param[1].type == TYPE_WORD) {
+ msnprintf(fname, sizeof fname, "%s/%s/%s", board_dir,
+ param[0].val.word, param[1].val.word);
+
+ if (file_exists(fname)) {
+ ExamineScratch(p, param);
+ return COM_OK;
+ }
+ }
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &p1conn))
+ return COM_OK;
+
+ if (param[1].type == TYPE_INT)
+ ExamineHistory(p, p1, param[1].val.integer);
+ else {
+ if (param[1].type == TYPE_WORD) {
+ /*
+ * Lets check the journal
+ */
+ param2string = param[1].val.word;
+
+ if (strlen(param2string) == 1 &&
+ isalpha(param2string[0])) {
+ ExamineJournal(p, p1, param2string[0]);
+
+ if (!p1conn)
+ player_remove(p1);
+ return COM_OK;
+ } else {
+ if (!FindPlayer(p, param[1].val.word,
+ &p2, &p2conn)) {
+ if (!p1conn)
+ player_remove(p1);
+ return COM_OK;
+ }
+ }
+ }
+
+ ExamineAdjourned(p, p1, p2);
+
+ if (!p2conn)
+ player_remove(p2);
+ }
+
+ if (!p1conn)
+ player_remove(p1);
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_stored(int p, param_list param)
+{
+ DIR *dirp;
+ char dname[MAX_FILENAME_SIZE];
+ int p1, connected;
+#ifdef USE_DIRENT
+ struct dirent *dp;
+#else
+ struct direct *dp;
+#endif
+
+ if (param[0].type == TYPE_WORD) {
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+ } else {
+ p1 = p;
+ connected = 1;
+ }
+
+ msnprintf(dname, sizeof dname, "%s/%c", adj_dir, parray[p1].login[0]);
+ dirp = opendir(dname);
+
+ if (!dirp) {
+ pprintf(p, "Player %s has no games stored.\n", parray[p1].name);
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ pprintf(p, "Stored games for %s:\n", parray[p1].name);
+
+ for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
+ if (file_has_pname(dp->d_name, parray[p1].login)) {
+ pprintf(p, " %s vs. %s\n",
+ file_wplayer(dp->d_name),
+ file_bplayer(dp->d_name));
+ }
+ }
+
+ closedir(dirp);
+ pprintf(p, "\n");
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+}
+
+PRIVATE void
+stored_mail_moves(int p, int mail, param_list param)
+{
+ FILE *fpGame;
+ char *fileName;
+ char *name_from;
+ char *param2string = NULL;
+ char fileName2[MAX_FILENAME_SIZE];
+ int g = -1;
+ int wp, wconnected, bp, bconnected, gotit = 0;
+
+ if (mail && !parray[p].registered) {
+ pprintf(p, "Unregistered players cannot use mailstored.\n");
+ return;
+ }
+
+ if (!FindPlayer(p, param[0].val.word, &wp, &wconnected))
+ return;
+
+ if (param[1].type == TYPE_INT) { /* look for a game from history */
+ if ((fileName = FindHistory(p, wp, param[1].val.integer)) !=
+ NULL) {
+ if ((fpGame = fopen(fileName, "r")) == NULL) {
+ pprintf(p, "History game %d not available "
+ "for %s.\n",
+ param[1].val.integer,
+ parray[wp].name);
+ } else {
+ g = game_new();
+
+ if (ReadGameAttrs(fpGame, fileName, g) < 0)
+ pprintf(p, "Gamefile is corrupt; "
+ "please notify an admin.\n");
+ else
+ gotit = 1;
+
+ fclose(fpGame);
+ }
+ }
+ } else {
+ /*
+ * Let's test for journal
+ */
+ name_from = param[0].val.word;
+ param2string = param[1].val.word;
+
+ if (strlen(param2string) == 1 && isalpha(param2string[0])) {
+ if (parray[wp].jprivate &&
+ parray[p].adminLevel < ADMIN_ADMIN &&
+ p != wp) {
+ pprintf(p, "Sorry, the journal from which you "
+ "are trying to fetch is private.\n");
+ } else {
+ if ((param2string[0] - 'a' - 1) > MAX_JOURNAL &&
+ parray[wp].adminLevel < ADMIN_ADMIN &&
+ !titled_player(p, parray[wp].login)) {
+ pprintf(p, "%s's maximum journal entry "
+ "is %c\n",
+ parray[wp].name,
+ toupper((char)(MAX_JOURNAL +
+ 'A' - 1)));
+ } else {
+ msnprintf(fileName2, sizeof fileName2,
+ "%s/%c/%s.%c",
+ journal_dir,
+ name_from[0],
+ name_from,
+ param2string[0]);
+
+ if ((fpGame = fopen(fileName2, "r")) ==
+ NULL) {
+ pprintf(p, "Journal entry %c "
+ "is not available for %s.\n",
+ toupper(param2string[0]),
+ parray[wp].name);
+ } else {
+ g = game_new();
+
+ /* XXX: was 'fileName' */
+ if (ReadGameAttrs(fpGame,
+ fileName2, g) < 0)
+ pprintf(p, "Journal "
+ "entry is corrupt; "
+ "please notify an "
+ "admin.\n");
+ else
+ gotit = 1;
+
+ fclose(fpGame);
+ }
+ }
+ }
+ } else {
+ /*
+ * look for a stored game between the players
+ */
+
+ if (FindPlayer(p, param[1].val.word, &bp,
+ &bconnected)) {
+ g = game_new();
+
+ if (game_read(g, wp, bp) >= 0) {
+ gotit = 1;
+ } else if (game_read(g, bp, wp) >= 0) {
+ gotit = 1;
+ } else {
+ pprintf(p, "There is no stored game "
+ "%s vs. %s\n",
+ parray[wp].name,
+ parray[bp].name);
+ }
+
+ if (!bconnected)
+ player_remove(bp);
+ }
+ }
+ }
+
+ if (gotit) {
+ if (strcasecmp(parray[p].name, garray[g].white_name) &&
+ strcasecmp(parray[p].name, garray[g].black_name) &&
+ garray[g].private &&
+ parray[p].adminLevel < ADMIN_ADMIN) {
+ pprintf(p, "Sorry, that is a private game.\n");
+ } else {
+ if (mail == 1) { /* Do mailstored */
+ char subj[81];
+
+ if (param[1].type == TYPE_INT) {
+ msnprintf(subj, sizeof subj, "FICS "
+ "history game: %s %d",
+ parray[wp].name,
+ param[1].val.integer);
+ } else {
+ if (param2string == NULL) /* XXX */
+ errx(1, "%s: param2string == "
+ "NULL", __func__);
+ if (strlen(param2string) == 1 &&
+ isalpha(param2string[0])) {
+ msnprintf(subj, sizeof subj,
+ "FICS journal "
+ "game %s vs %s",
+ garray[g].white_name,
+ garray[g].black_name);
+ } else {
+ msnprintf(subj, sizeof subj,
+ "FICS adjourned "
+ "game %s vs %s",
+ garray[g].white_name,
+ garray[g].black_name);
+ }
+ }
+ if (mail_string_to_user(p, subj, movesToString
+ (g, parray[p].pgn)))
+ pprintf(p, "Moves NOT mailed, perhaps "
+ "your address is incorrect.\n");
+ else
+ pprintf(p, "Moves mailed.\n");
+ } else {
+ pprintf(p, "%s\n", movesToString(g, 0));
+ } /* Do smoves */
+ }
+ }
+
+ if (!wconnected)
+ player_remove(wp);
+ if (g != -1)
+ game_remove(g);
+}
+
+PUBLIC int
+com_mailstored(int p, param_list param)
+{
+ stored_mail_moves(p, 1, param);
+ return COM_OK;
+}
+
+PUBLIC int
+com_smoves(int p, param_list param)
+{
+ stored_mail_moves(p, 0, param);
+ return COM_OK;
+}
+
+PUBLIC int
+com_sposition(int p, param_list param)
+{
+ int g;
+ int wp, wconnected, bp, bconnected, confused = 0;
+
+ if (!FindPlayer(p, param[0].val.word, &wp, &wconnected))
+ return (COM_OK);
+ if (!FindPlayer(p, param[1].val.word, &bp, &bconnected)) {
+ if (!wconnected)
+ player_remove(wp);
+ return (COM_OK);
+ }
+
+ g = game_new();
+
+ if (game_read(g, wp, bp) < 0) { // if no game white-black,
+ if (game_read(g, bp, wp) < 0) { // look for black-white
+ confused = 1;
+
+ pprintf(p, "There is no stored game %s vs. %s\n",
+ parray[wp].name,
+ parray[bp].name);
+ } else {
+ int tmp;
+
+ tmp = wp;
+ wp = bp;
+ bp = tmp;
+ tmp = wconnected;
+ wconnected = bconnected;
+ bconnected = tmp;
+ }
+ }
+
+ if (!confused) {
+ if ((wp != p) &&
+ (bp != p) &&
+ (garray[g].private) &&
+ (parray[p].adminLevel < ADMIN_ADMIN)) {
+ pprintf(p, "Sorry, that is a private game.\n");
+ } else {
+ garray[g].white = wp;
+ garray[g].black = bp;
+ garray[g].startTime = tenth_secs();
+ garray[g].lastMoveTime = garray[g].startTime;
+ garray[g].lastDecTime = garray[g].startTime;
+
+ pprintf(p, "Position of stored game %s vs. %s\n",
+ parray[wp].name,
+ parray[bp].name);
+
+ send_board_to(g, p);
+ }
+ }
+
+ game_remove(g);
+
+ if (!wconnected)
+ player_remove(wp);
+ if (!bconnected)
+ player_remove(bp);
+ return COM_OK;
+}
+
+PUBLIC int
+com_forward(int p, param_list param)
+{
+ int g, i;
+ int nHalfMoves = 1;
+ int p1;
+ unsigned int now;
+
+ if (!(parray[p].game >= 0 && garray[parray[p].game].status ==
+ GAME_EXAMINE)) {
+ pprintf(p, "You are not examining any games.\n");
+ return COM_OK;
+ }
+
+ g = parray[p].game;
+
+ if (!strcmp(garray[g].white_name, garray[g].black_name)) {
+ pprintf(p, "You cannot go forward; no moves are stored.\n");
+ return COM_OK;
+ }
+
+ if (param[0].type == TYPE_INT)
+ nHalfMoves = param[0].val.integer;
+
+ if (garray[g].numHalfMoves > garray[g].revertHalfMove) {
+ pprintf(p, "No more moves.\n");
+ return COM_OK;
+ }
+
+ if (garray[g].numHalfMoves < garray[g].totalHalfMoves) {
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ if (player_is_observe(p1, g) || parray[p1].game == g) {
+ pprintf(p1, "%s goes forward %d move%s.\n",
+ parray[p].name,
+ nHalfMoves,
+ (nHalfMoves == 1 ? "" : "s"));
+ }
+ }
+ }
+
+ for (i = 0; i < nHalfMoves; i++) {
+ if (garray[g].numHalfMoves < garray[g].totalHalfMoves) {
+ execute_move(&garray[g].game_state,
+ &garray[g].moveList[garray[g].numHalfMoves], 1);
+
+ if (garray[g].numHalfMoves + 1 >
+ garray[g].examMoveListSize) {
+ // Allocate 20 moves at a time.
+ garray[g].examMoveListSize += 20;
+
+ if (!garray[g].examMoveList) {
+ garray[g].examMoveList =
+ reallocarray(NULL,
+ sizeof(move_t),
+ garray[g].examMoveListSize);
+ if (garray[g].examMoveList == NULL)
+ err(1, "%s", __func__);
+ malloc_count++;
+ } else {
+ garray[g].examMoveList =
+ reallocarray(garray[g].examMoveList,
+ sizeof(move_t),
+ garray[g].examMoveListSize);
+ if (garray[g].examMoveList == NULL)
+ err(1, "%s", __func__);
+ }
+ }
+
+ garray[g].examMoveList[garray[g].numHalfMoves] =
+ garray[g].moveList[garray[g].numHalfMoves];
+ garray[g].revertHalfMove++;
+ garray[g].numHalfMoves++;
+ } else {
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ if (player_is_observe(p1, g) ||
+ parray[p1].game == g)
+ pprintf(p1, "End of game.\n");
+ }
+
+ break;
+ }
+ }
+
+ // roll back time
+ if (garray[g].game_state.onMove == WHITE) {
+ garray[g].wTime += (garray[g].lastDecTime -
+ garray[g].lastMoveTime);
+ } else {
+ garray[g].bTime += (garray[g].lastDecTime -
+ garray[g].lastMoveTime);
+ }
+
+ now = tenth_secs();
+
+ if (garray[g].numHalfMoves == 0)
+ garray[g].timeOfStart = now;
+ garray[g].lastMoveTime = now;
+ garray[g].lastDecTime = now;
+
+ send_boards(g);
+ return COM_OK;
+}
+
+PUBLIC int
+com_backward(int p, param_list param)
+{
+ int g, i;
+ int nHalfMoves = 1;
+ int p1;
+ unsigned int now;
+
+ if (!(parray[p].game >= 0 && garray[parray[p].game].status ==
+ GAME_EXAMINE)) {
+ pprintf(p, "You are not examining any games.\n");
+ return COM_OK;
+ }
+
+ g = parray[p].game;
+
+ if (param[0].type == TYPE_INT)
+ nHalfMoves = param[0].val.integer;
+
+ if (garray[g].numHalfMoves != 0) {
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ if (player_is_observe(p1, g) || parray[p1].game == g) {
+ pprintf(p1, "%s backs up %d move%s.\n",
+ parray[p].name,
+ nHalfMoves,
+ (nHalfMoves == 1 ? "" : "s"));
+ }
+ }
+ }
+
+ for (i = 0; i < nHalfMoves; i++) {
+ if (backup_move(g, REL_EXAMINE) != MOVE_OK) {
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ if (player_is_observe(p1, g) ||
+ parray[p1].game == g)
+ pprintf(p1, "Beginning of game.\n");
+ }
+
+ break;
+ }
+ }
+
+ if (garray[g].numHalfMoves < garray[g].revertHalfMove)
+ garray[g].revertHalfMove = garray[g].numHalfMoves;
+
+ // roll back time
+ if (garray[g].game_state.onMove == WHITE) {
+ garray[g].wTime += (garray[g].lastDecTime -
+ garray[g].lastMoveTime);
+ } else {
+ garray[g].bTime += (garray[g].lastDecTime -
+ garray[g].lastMoveTime);
+ }
+
+ now = tenth_secs();
+
+ if (garray[g].numHalfMoves == 0)
+ garray[g].timeOfStart = now;
+ garray[g].lastMoveTime = now;
+ garray[g].lastDecTime = now;
+
+ send_boards(g);
+ return COM_OK;
+}
+
+PUBLIC int
+com_revert(int p, param_list param)
+{
+ int g, i;
+ int nHalfMoves = 1;
+ int p1;
+ unsigned int now;
+
+ if (!(parray[p].game >= 0 && garray[parray[p].game].status ==
+ GAME_EXAMINE)) {
+ pprintf(p, "You are not examining any games.\n");
+ return COM_OK;
+ }
+
+ g = parray[p].game;
+ nHalfMoves = garray[g].numHalfMoves - garray[g].revertHalfMove;
+
+ if (nHalfMoves == 0) {
+ pprintf(p, "Already at mainline.\n");
+ return COM_OK;
+ }
+
+ if (nHalfMoves < 0) { // eek - should NEVER happen!
+ fprintf(stderr, "OUCH! in %s: nHalfMoves < 0\n", __func__);
+ return COM_OK;
+ }
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ if (player_is_observe(p1, g) || parray[p1].game == g) {
+ pprintf(p1, "%s reverts to mainline.\n",
+ parray[p].name);
+ }
+ }
+
+ for (i = 0; i < nHalfMoves; i++)
+ backup_move(g, REL_EXAMINE);
+
+ // roll back time
+ if (garray[g].game_state.onMove == WHITE) {
+ garray[g].wTime += (garray[g].lastDecTime -
+ garray[g].lastMoveTime);
+ } else {
+ garray[g].bTime += (garray[g].lastDecTime -
+ garray[g].lastMoveTime);
+ }
+
+ now = tenth_secs();
+
+ if (garray[g].numHalfMoves == 0)
+ garray[g].timeOfStart = now;
+ garray[g].lastMoveTime = now;
+ garray[g].lastDecTime = now;
+
+ send_boards(g);
+ return COM_OK;
+}
+
+PUBLIC int
+com_history(int p, param_list param)
+{
+ char fname[MAX_FILENAME_SIZE] = { '\0' };
+ int p1, connected;
+
+ if (param[0].type == TYPE_WORD) {
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+ } else {
+ p1 = p;
+ connected = 1;
+ }
+
+ msnprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s", stats_dir,
+ parray[p1].login[0], parray[p1].login, STATS_GAMES);
+ pgames(p, p1, fname);
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+}
+
+PUBLIC int
+com_journal(int p, param_list param)
+{
+ char fname[MAX_FILENAME_SIZE];
+ int p1, connected;
+
+ if (param[0].type == TYPE_WORD) {
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+ } else {
+ p1 = p;
+ connected = 1;
+ }
+
+ if (!parray[p1].registered) {
+ pprintf(p, "Only registered players may keep a journal.\n");
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ if (parray[p1].jprivate &&
+ p != p1 &&
+ parray[p].adminLevel < ADMIN_ADMIN) {
+ pprintf(p, "Sorry, this journal is private.\n");
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ msnprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s", stats_dir,
+ parray[p1].login[0], parray[p1].login, STATS_JOURNAL);
+ pjournal(p, p1, fname);
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+}
+
+PRIVATE void
+jsave_journalentry(int p, char save_spot, int p1, char from_spot, char *to_file)
+{
+ FILE *Game;
+ char *name_from = parray[p1].login;
+ char *name_to = parray[p].login;
+ char command[MAX_FILENAME_SIZE * 2 + 3];
+ char fname[MAX_FILENAME_SIZE];
+ char fname2[MAX_FILENAME_SIZE];
+ struct JGI_context ctx;
+
+ msnprintf(fname, sizeof fname, "%s/%c/%s.%c", journal_dir, name_from[0],
+ name_from, from_spot);
+
+ if ((Game = fopen(fname, "r")) == NULL) {
+ pprintf(p, "Journal entry %c not available for %s.\n",
+ toupper(from_spot),
+ parray[p1].name);
+ return;
+ }
+
+ fclose(Game);
+
+ msnprintf(fname2, sizeof fname2, "%s/%c/%s.%c", journal_dir, name_to[0],
+ name_to, save_spot);
+ unlink(fname2);
+
+ msnprintf(command, sizeof command, "cp %s %s", fname, fname2);
+ if (system(command)) { // XXX
+ pprintf(p, "System command in jsave_journalentry failed!\n");
+ pprintf(p, "Please report this to an admin.\n");
+ fprintf(stderr, "FICS: System command failed in "
+ "jsave_journalentry\n");
+ return;
+ }
+
+ msnprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s", stats_dir,
+ name_to[0], name_to, STATS_JOURNAL);
+
+ /*
+ * Init context
+ */
+ ctx.p = p;
+ ctx.from_spot = from_spot;
+ ctx.WhiteRating = 0;
+ ctx.BlackRating = 0;
+ ctx.t = 0;
+ ctx.i = 0;
+ memset(ctx.WhiteName, 0, sizeof(ctx.WhiteName));
+ memset(ctx.BlackName, 0, sizeof(ctx.BlackName));
+ memset(ctx.type, 0, sizeof(ctx.type));
+ memset(ctx.eco, 0, sizeof(ctx.eco));
+ memset(ctx.ending, 0, sizeof(ctx.ending));
+ memset(ctx.result, 0, sizeof(ctx.result));
+
+ if (!journal_get_info(&ctx, fname))
+ return;
+
+ addjournalitem(p, toupper(save_spot),
+ ctx.WhiteName, ctx.WhiteRating,
+ ctx.BlackName, ctx.BlackRating,
+ ctx.type,
+ ctx.t, ctx.i,
+ ctx.eco,
+ ctx.ending,
+ ctx.result,
+ to_file);
+ pprintf(p, "Journal entry %s %c saved in slot %c in journal.\n",
+ parray[p1].name, toupper(from_spot), toupper(save_spot));
+}
+
+PUBLIC void
+jsave_history(int p, char save_spot, int p1, int from, char *to_file)
+{
+ FILE *Game;
+ char *EndSymbol;
+ char *HistoryFname;
+ char *name_to = parray[p].login;
+ char End[100] = { '\0' };
+ char command[MAX_FILENAME_SIZE * 2 + 3] = { '\0' };
+ char filename[MAX_FILENAME_SIZE + 1] = { '\0' }; // XXX
+ char jfname[MAX_FILENAME_SIZE] = { '\0' };
+ char type[4];
+ int g;
+
+ if ((HistoryFname = FindHistory2(p, p1, from, End, sizeof End)) !=
+ NULL) {
+ if ((Game = fopen(HistoryFname, "r")) == NULL) {
+ pprintf(p, "History game %d not available for %s.\n",
+ from,
+ parray[p1].name);
+ } else {
+ msnprintf(jfname, sizeof jfname, "%s/%c/%s.%c",
+ journal_dir,
+ name_to[0],
+ name_to,
+ save_spot);
+ unlink(jfname);
+
+ msnprintf(command, sizeof command, "cp %s %s",
+ HistoryFname, jfname);
+ if (system(command)) { // XXX: A little messy, but works
+ pprintf(p, "System command in jsave_history "
+ "failed!\n");
+ pprintf(p, "Please report this to an admin.\n");
+ fprintf(stderr, "FICS: System command failed "
+ "in jsave_journalentry\n");
+ fclose(Game);
+ return;
+ }
+
+ g = game_new(); // Open a dummy game
+
+ // XXX: is 'filename' right here?
+ if (ReadGameAttrs(Game, filename, g) < 0) {
+ pprintf(p, "Gamefile is corrupt. Please tell "
+ "an admin.\n");
+ game_free(g);
+ fclose(Game);
+ return;
+ }
+
+ fclose(Game);
+
+ if (garray[g].private) {
+ type[0] = 'p';
+ } else {
+ type[0] = ' ';
+ }
+
+ if (garray[g].type == TYPE_BLITZ) {
+ type[1] = 'b';
+ } else if (garray[g].type == TYPE_WILD) {
+ type[1] = 'w';
+ } else if (garray[g].type == TYPE_STAND) {
+ type[1] = 's';
+ } else {
+ if (garray[g].type == TYPE_NONSTANDARD)
+ type[1] = 'n';
+ else
+ type[1] = 'u';
+ }
+
+ if (garray[g].rated) {
+ type[2] = 'r';
+ } else {
+ type[2] = 'u';
+ }
+
+ type[3] = '\0';
+ EndSymbol = EndSym(g);
+ addjournalitem(p, toupper(save_spot),
+ garray[g].white_name, garray[g].white_rating,
+ garray[g].black_name, garray[g].black_rating,
+ type,
+ garray[g].wInitTime,
+ garray[g].wIncrement,
+ getECO(g), End, EndSymbol, to_file);
+ game_free(g);
+ pprintf(p, "Game %s %d saved in slot %c in journal.\n",
+ parray[p1].name, from, toupper(save_spot));
+ }
+ }
+}
+
+PUBLIC int
+com_jsave(int p, param_list param)
+{
+ char *from;
+ char *to = param[0].val.word;
+ char fname[MAX_FILENAME_SIZE] = { '\0' };
+ int p1, p1conn;
+
+ if (!parray[p].registered) {
+ pprintf(p, "Only registered players may keep a journal.\n");
+ return COM_OK;
+ }
+
+ if (strlen(to) != 1 || !(isalpha(to[0]))) {
+ pprintf(p, "Journal entries are referenced by single "
+ "letters.\n");
+ return COM_OK;
+ }
+
+ if ((to[0] - 'a' - 1) > MAX_JOURNAL &&
+ parray[p].adminLevel < ADMIN_ADMIN &&
+ !titled_player(p, parray[p].login)) {
+ pprintf(p, "Your maximum journal entry is %c\n",
+ toupper((char)(MAX_JOURNAL + 'A' - 1)));
+ return COM_OK;
+ }
+
+ if (!FindPlayer(p, param[1].val.word, &p1, &p1conn))
+ return COM_OK;
+
+ if (param[2].type == TYPE_INT) {
+ // grab from a history
+ msnprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s",
+ stats_dir, parray[p].login[0], parray[p].login,
+ STATS_JOURNAL);
+ jsave_history(p, to[0], p1, param[2].val.integer, fname);
+ } else {
+ from = param[2].val.word;
+
+ if (strlen(from) != 1 || !(isalpha(from[0]))) {
+ pprintf(p, "Journal entries are referenced by single "
+ "letters.\n");
+ if (!p1conn)
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ if (parray[p1].jprivate &&
+ parray[p].adminLevel < ADMIN_ADMIN &&
+ p != p1) {
+ pprintf(p, "Sorry, the journal from which you are "
+ "trying to fetch is private.\n");
+ if (!p1conn)
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ if ((to[0] - 'a' - 1) > MAX_JOURNAL &&
+ parray[p1].adminLevel < ADMIN_ADMIN &&
+ !titled_player(p, parray[p1].login)) {
+ pprintf(p, "%s's maximum journal entry is %c\n",
+ parray[p1].name,
+ toupper((char)(MAX_JOURNAL + 'A' - 1)));
+
+ if (!p1conn)
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ if ((p == p1) && (to[0] == from[0])) {
+ pprintf(p, "Source and destination entries are the "
+ "same.\n");
+ return COM_OK;
+ }
+
+ // grab from a journal
+ msnprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s",
+ stats_dir, parray[p].login[0], parray[p].login,
+ STATS_JOURNAL);
+ jsave_journalentry(p, to[0], p1, from[0], fname);
+ }
+
+ if (!p1conn)
+ player_remove(p1);
+ return COM_OK;
+}
diff --git a/FICS/obsproc.h b/FICS/obsproc.h
new file mode 100644
index 0000000..e9fa1a0
--- /dev/null
+++ b/FICS/obsproc.h
@@ -0,0 +1,59 @@
+/* obsproc.h
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Dave Herscovici 95/11/26 Created
+ Markus Uhlin 24/04/28 Revised
+*/
+
+#ifndef _OBSPROC_H
+#define _OBSPROC_H
+
+#include "command.h" /* param_list */
+
+#define MAX_JOURNAL 10
+
+extern int GameNumFromParam(int, int *, parameter *);
+extern int com_allobservers(int, param_list);
+extern int com_backward(int, param_list);
+extern int com_examine(int, param_list);
+extern int com_forward(int, param_list);
+extern int com_games(int, param_list);
+extern int com_history(int, param_list);
+extern int com_journal(int, param_list);
+extern int com_jsave(int, param_list);
+extern int com_mailmoves(int, param_list);
+extern int com_mailoldmoves(int, param_list);
+extern int com_mailstored(int, param_list);
+extern int com_mexamine(int, param_list);
+extern int com_moves(int, param_list);
+extern int com_observe(int, param_list);
+extern int com_oldmoves(int, param_list);
+extern int com_revert(int, param_list);
+extern int com_smoves(int, param_list);
+extern int com_sposition(int, param_list);
+extern int com_stored(int, param_list);
+extern int com_unexamine(int, param_list);
+extern int com_unobserve(int, param_list);
+extern void ExamineScratch(int, param_list);
+extern void jsave_history(int, char, int, int, char *);
+extern void unobserveAll(int);
+
+#endif /* _OBSPROC_H */
diff --git a/FICS/playerdb.c b/FICS/playerdb.c
new file mode 100644
index 0000000..9919bfd
--- /dev/null
+++ b/FICS/playerdb.c
@@ -0,0 +1,3278 @@
+/* playerdb.c
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 23/12/13 Fixed compiler warnings
+ Markus Uhlin 23/12/17 Usage of 'time_t'
+ Markus Uhlin 23/12/19 Usage of 'time_t'
+ Markus Uhlin 23/12/25 Reformatted functions
+ Markus Uhlin 23/12/31 Completed reformation of all
+ functions and much more...
+ Markus Uhlin 24/03/16 Replaced unbounded string
+ handling functions and added
+ truncation checks.
+ Markus Uhlin 24/08/04 Fixed multiple possible buffer
+ overflows.
+ Markus Uhlin 24/08/13 Handled function return values
+ Markus Uhlin 24/11/24 Fixed incorrect format strings
+ Markus Uhlin 24/12/02 Made many improvements
+ Markus Uhlin 24/12/04 Added player number checks
+ Markus Uhlin 25/02/11 Calc string length once
+ Markus Uhlin 25/03/22 Fixed overflowed return value in
+ player_search().
+ Markus Uhlin 25/03/23 Fixed overflowed array index
+ read/write.
+ Markus Uhlin 25/03/29 player_remove_request:
+ fixed overflowed array index
+ read/write.
+ Markus Uhlin 25/04/02 add_to_list: added an upper
+ limit for the list size.
+ Markus Uhlin 25/04/06 Fixed Clang Tidy warnings.
+ Markus Uhlin 25/07/28 Restricted file permissions upon
+ creation.
+ Markus Uhlin 25/07/30 Usage of 'int64_t'.
+*/
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+
+#include "command.h"
+#include "comproc.h"
+#include "config.h"
+#include "ficslim.h"
+#include "ficsmain.h"
+#include "gamedb.h"
+#include "lists.h"
+#include "maxxes-utils.h"
+#include "network.h"
+#include "playerdb.h"
+#include "ratings.h"
+#include "rmalloc.h"
+#include "talkproc.h"
+#include "utils.h"
+
+#if __linux__
+#include <bsd/string.h>
+#endif
+
+PUBLIC player parray[PARRAY_SIZE];
+PUBLIC int p_num = 0;
+
+/*
+ * Checks if a player number is within bounds.
+ */
+PUBLIC bool
+player_num_ok_chk(const int num)
+{
+ return (num >= 0 && num <= p_num &&
+ num < (int)ARRAY_SIZE(parray));
+}
+
+PUBLIC void
+xrename(const char *fn, const char *name1, const char *name2)
+{
+ if (fn == NULL || name1 == NULL || name2 == NULL) {
+ errno = EINVAL;
+ warn("%s", __func__);
+ return;
+ }
+
+ errno = 0;
+
+ if (rename(name1, name2) != 0)
+ warn("%s: '%s' -> '%s'", fn, name1, name2);
+}
+
+PRIVATE int
+get_empty_slot(void)
+{
+ for (int i = 0; i < p_num; i++) {
+ if (parray[i].status == PLAYER_EMPTY)
+ return i;
+ }
+
+ p_num++;
+
+ if ((p_num + 1) >= PARRAY_SIZE) {
+ fprintf(stderr, "*** Bogus attempt to %s() past end of parray "
+ "***\n", __func__);
+ }
+
+ parray[p_num - 1].status = PLAYER_EMPTY;
+
+ return (p_num - 1);
+}
+
+PUBLIC void
+player_array_init(void)
+{
+ for (int i = 0; i < PARRAY_SIZE; i++)
+ parray[i].status = PLAYER_EMPTY;
+}
+
+PUBLIC void
+player_init(int startConsole)
+{
+ int p;
+
+ if (startConsole) {
+ net_addConnection(0, 0);
+ p = player_new();
+
+ parray[p].login = xstrdup("console");
+ parray[p].name = xstrdup("console");
+ parray[p].passwd = xstrdup("*");
+ parray[p].fullName = xstrdup("The Operator");
+ parray[p].emailAddress = NULL;
+ parray[p].prompt = xstrdup("fics%");
+ parray[p].adminLevel = ADMIN_GOD;
+ parray[p].socket = 0;
+ parray[p].busy[0] = '\0';
+
+ pprintf_prompt(p, "\nLogged in on console.\n");
+ }
+}
+
+PUBLIC int
+player_new(void)
+{
+ int new;
+
+ new = get_empty_slot();
+ player_zero(new);
+ return new;
+}
+
+PUBLIC int
+player_zero(int p)
+{
+#define INVALID ((char *)-42)
+ int i;
+
+ parray[p].name = NULL;
+ parray[p].emailAddress = NULL;
+ parray[p].fullName = NULL;
+ parray[p].passwd = NULL;
+ parray[p].prompt = def_prompt;
+
+ parray[p].adminLevel = 0;
+ parray[p].automail = 0;
+
+ for (i = 0; i < MAX_ALIASES; i++) {
+ parray[p].alias_list[i].comm_name = INVALID;
+ parray[p].alias_list[i].alias = INVALID;
+ }
+
+ parray[p].bell = 0;
+ parray[p].d_height = 24;
+ parray[p].d_inc = 12;
+ parray[p].d_time = 2;
+ parray[p].d_width = 79;
+
+ parray[p].flip = 0;
+ parray[p].formula = NULL;
+
+ for (i = 0; i < MAX_FORMULA; i++)
+ parray[p].formulaLines[i] = NULL;
+
+ parray[p].game = -1;
+ parray[p].highlight = 0;
+ parray[p].i_admin = 1;
+ parray[p].i_cshout = 1;
+ parray[p].i_game = 0;
+ parray[p].i_kibitz = 1;
+ parray[p].i_login = 0;
+ parray[p].i_mailmess = 0;
+ parray[p].i_shout = 1;
+ parray[p].i_tell = 1;
+ parray[p].jprivate = 0;
+ parray[p].kiblevel = 0;
+ parray[p].language = LANG_DEFAULT;
+
+ parray[p].lastColor = WHITE;
+ parray[p].lastHost = 0;
+ parray[p].last_channel = -1;
+ parray[p].last_command_time = 0;
+ parray[p].last_file = NULL;
+ parray[p].last_file_byte = 0L;
+ parray[p].last_opponent = -1;
+ parray[p].last_tell = -1;
+ parray[p].lastshout_a = 0;
+ parray[p].lastshout_b = 0;
+
+ parray[p].lists = NULL;
+ parray[p].login = NULL;
+ parray[p].logon_time = 0;
+ parray[p].notifiedby = 0;
+ parray[p].numAlias = 0;
+ parray[p].num_black = 0;
+ parray[p].num_comments = 0;
+ parray[p].num_formula = 0;
+ parray[p].num_from = 0;
+ parray[p].num_observe = 0;
+ parray[p].num_plan = 0;
+ parray[p].num_to = 0;
+ parray[p].num_white = 0;
+ parray[p].open = 1;
+ parray[p].opponent = -1;
+
+ parray[p].partner = -1;
+ parray[p].pgn = 0;
+ for (i = 0; i < MAX_PLAN; i++)
+ parray[p].planLines[i] = INVALID;
+ parray[p].private = 0;
+ parray[p].promote = QUEEN;
+
+ parray[p].rated = 0;
+ parray[p].registered = 0;
+ parray[p].ropen = 1;
+ parray[p].seek = 0;
+ parray[p].simul_info.numBoards = 0;
+ parray[p].socket = -1;
+ parray[p].sopen = 0;
+ parray[p].status = PLAYER_NEW;
+ parray[p].style = 0;
+ parray[p].thisHost = 0;
+ parray[p].timeOfReg = 0;
+ parray[p].totalTime = 0;
+
+ parray[p].b_stats.best = 0;
+ parray[p].b_stats.dra = 0;
+ parray[p].b_stats.los = 0;
+ parray[p].b_stats.ltime = 0;
+ parray[p].b_stats.num = 0;
+ parray[p].b_stats.rating = 0;
+ parray[p].b_stats.sterr = 350.0;
+ parray[p].b_stats.whenbest = 0;
+ parray[p].b_stats.win = 0;
+
+ parray[p].bug_stats.best = 0;
+ parray[p].bug_stats.dra = 0;
+ parray[p].bug_stats.los = 0;
+ parray[p].bug_stats.ltime = 0;
+ parray[p].bug_stats.num = 0;
+ parray[p].bug_stats.rating = 0;
+ parray[p].bug_stats.sterr = 350.0;
+ parray[p].bug_stats.whenbest = 0;
+ parray[p].bug_stats.win = 0;
+
+ parray[p].l_stats.best = 0;
+ parray[p].l_stats.dra = 0;
+ parray[p].l_stats.los = 0;
+ parray[p].l_stats.ltime = 0;
+ parray[p].l_stats.num = 0;
+ parray[p].l_stats.rating = 0;
+ parray[p].l_stats.sterr = 350.0;
+ parray[p].l_stats.whenbest = 0;
+ parray[p].l_stats.win = 0;
+
+ parray[p].s_stats.best = 0;
+ parray[p].s_stats.dra = 0;
+ parray[p].s_stats.los = 0;
+ parray[p].s_stats.ltime = 0;
+ parray[p].s_stats.num = 0;
+ parray[p].s_stats.rating = 0;
+ parray[p].s_stats.sterr = 350.0;
+ parray[p].s_stats.whenbest = 0;
+ parray[p].s_stats.win = 0;
+
+ parray[p].w_stats.best = 0;
+ parray[p].w_stats.dra = 0;
+ parray[p].w_stats.los = 0;
+ parray[p].w_stats.ltime = 0;
+ parray[p].w_stats.num = 0;
+ parray[p].w_stats.rating = 0;
+ parray[p].w_stats.sterr = 350.0;
+ parray[p].w_stats.whenbest = 0;
+ parray[p].w_stats.win = 0;
+
+ // Used to store the player's interface.
+ // For example: xboard 4.9.1
+ (void) memset(&(parray[p].interface[0]), 0,
+ ARRAY_SIZE(parray[p].interface));
+
+ return 0;
+}
+
+PUBLIC int
+player_free(int p)
+{
+ int i;
+
+ strfree(parray[p].login);
+ strfree(parray[p].name);
+ strfree(parray[p].passwd);
+ strfree(parray[p].fullName);
+ strfree(parray[p].emailAddress);
+
+ if (parray[p].prompt != def_prompt)
+ strfree(parray[p].prompt);
+
+ for (i = 0; i < parray[p].num_plan; i++)
+ strfree(parray[p].planLines[i]);
+ for (i = 0; i < parray[p].num_formula; i++)
+ strfree(parray[p].formulaLines[i]);
+
+ strfree(parray[p].formula);
+ list_free(parray[p].lists);
+
+ for (i = 0; i < parray[p].numAlias; i++) {
+ strfree(parray[p].alias_list[i].comm_name);
+ strfree(parray[p].alias_list[i].alias);
+ }
+
+ return 0;
+}
+
+PUBLIC int
+player_clear(int p)
+{
+ player_free(p);
+ player_zero(p);
+ return 0;
+}
+
+PUBLIC int
+player_remove(int p)
+{
+ int i;
+
+ if (!player_num_ok_chk(p)) {
+ warnx("%s: invalid player number %d", __func__, p);
+ return -1;
+ }
+
+ player_decline_offers(p, -1, -1);
+ player_withdraw_offers(p, -1, -1);
+
+ if (parray[p].simul_info.numBoards) { // Player disconnected in
+ // middle of simul
+ for (i = 0; i < parray[p].simul_info.numBoards; i++) {
+ if (parray[p].simul_info.boards[i] >= 0) {
+ game_disconnect(parray[p].simul_info.boards[i],
+ p);
+ }
+ }
+ }
+
+ if (parray[p].game >= 0) { // Player disconnected in the middle of
+ // a game!
+ pprintf(parray[p].opponent, "Your opponent has lost contact "
+ "or quit.\n");
+ game_disconnect(parray[p].game, p);
+ }
+
+ for (i = 0; i < p_num; i++) {
+ if (parray[i].status == PLAYER_EMPTY)
+ continue;
+
+ if (parray[i].last_tell == p)
+ parray[i].last_tell = -1;
+
+ if (parray[i].last_opponent == p)
+ parray[i].last_opponent = -1;
+
+ if (parray[i].partner == p) {
+ pprintf_prompt(i, "Your partner has disconnected.\n");
+ player_withdraw_offers(i, -1, PEND_BUGHOUSE);
+ player_decline_offers(i, -1, PEND_BUGHOUSE);
+ parray[i].partner = -1;
+ }
+ }
+
+ player_clear(p);
+ parray[p].status = PLAYER_EMPTY;
+ return 0;
+}
+
+PRIVATE int
+add_to_list(FILE *fp, enum ListWhich lw, int *size, int p)
+{
+ char buf[MAX_STRING_LENGTH] = { '\0' };
+
+ _Static_assert(1023 < ARRAY_SIZE(buf), "Buffer too small");
+
+#define SCAN_STR "%1023s"
+
+ if (*size <= 0 || *size > MAX_GLOBAL_LIST_SIZE)
+ return -2;
+
+ while ((*size)-- > 0 && fscanf(fp, SCAN_STR, buf) == 1)
+ list_add(p, lw, buf);
+
+ return (*size <= 0 ? 0 : -1);
+}
+
+PRIVATE void
+ReadV1PlayerFmt(int p, player *pp, FILE *fp, char *file, int version)
+{
+ char *tmp;
+ char tmp2[MAX_STRING_LENGTH] = { '\0' };
+ int bs, ss, ws, ls, bugs;
+ int i, size_cens, size_noplay, size_not, size_gnot,
+ size_chan, len;
+ intmax_t array[2] = { 0 };
+ intmax_t ltime_tmp[5] = { 0 };
+ intmax_t wb_tmp[5] = { 0 };
+ size_t n;
+
+ /* XXX: not referenced */
+ (void) version;
+
+ /*
+ * Name
+ */
+ if (fgets(tmp2, sizeof tmp2, fp) != NULL && // NOLINT
+ strcmp(tmp2, "NONE\n") != 0) {
+ tmp2[strcspn(tmp2, "\n")] = '\0';
+ pp->name = xstrdup(tmp2);
+ } else {
+ pp->name = NULL;
+ }
+
+ /*
+ * Full name
+ */
+ if (fgets(tmp2, sizeof tmp2, fp) != NULL && // NOLINT
+ strcmp(tmp2, "NONE\n") != 0) {
+ tmp2[strcspn(tmp2, "\n")] = '\0';
+ pp->fullName = xstrdup(tmp2);
+ } else {
+ pp->fullName = NULL;
+ }
+
+ /*
+ * Password
+ */
+ if (fgets(tmp2, sizeof tmp2, fp) != NULL && // NOLINT
+ strcmp(tmp2, "NONE\n") != 0) {
+ tmp2[strcspn(tmp2, "\n")] = '\0';
+ pp->passwd = xstrdup(tmp2);
+ } else {
+ pp->passwd = NULL;
+ }
+
+ /*
+ * Email
+ */
+ if (fgets(tmp2, sizeof tmp2, fp) != NULL && // NOLINT
+ strcmp(tmp2, "NONE\n") != 0) {
+ tmp2[strcspn(tmp2, "\n")] = '\0';
+ pp->emailAddress = xstrdup(tmp2);
+ } else {
+ pp->emailAddress = NULL;
+ }
+
+ if (feof(fp) ||
+ ferror(fp) ||
+ fscanf(fp, "%d %d %d %d %d %d %jd %d %jd %d %d %d %d %d %d %jd %d %jd "
+ "%d %d %d %d %d %d %jd %d %jd %d %d %d %d %d %d %jd %d %jd %d %d %d %d "
+ "%d %d %jd %d %jd %u\n",
+ &pp->s_stats.num, &pp->s_stats.win, &pp->s_stats.los,
+ &pp->s_stats.dra, &pp->s_stats.rating, &ss,
+ &ltime_tmp[0], &pp->s_stats.best, &wb_tmp[0],
+
+ &pp->b_stats.num, &pp->b_stats.win, &pp->b_stats.los,
+ &pp->b_stats.dra, &pp->b_stats.rating, &bs,
+ &ltime_tmp[1], &pp->b_stats.best, &wb_tmp[1],
+
+ &pp->w_stats.num, &pp->w_stats.win, &pp->w_stats.los,
+ &pp->w_stats.dra, &pp->w_stats.rating, &ws,
+ &ltime_tmp[2], &pp->w_stats.best, &wb_tmp[2],
+
+ &pp->l_stats.num, &pp->l_stats.win, &pp->l_stats.los,
+ &pp->l_stats.dra, &pp->l_stats.rating, &ls,
+ &ltime_tmp[3], &pp->l_stats.best, &wb_tmp[3],
+
+ &pp->bug_stats.num, &pp->bug_stats.win, &pp->bug_stats.los,
+ &pp->bug_stats.dra, &pp->bug_stats.rating, &bugs,
+ &ltime_tmp[4], &pp->bug_stats.best, &wb_tmp[4],
+
+ &pp->lastHost) != 46) {
+ fprintf(stderr, "Player %s is corrupt\n", parray[p].name);
+ return;
+ }
+
+ for (n = 0; n < ARRAY_SIZE(ltime_tmp); n++) {
+ if (ltime_tmp[n] < g_time_min ||
+ ltime_tmp[n] > g_time_max) {
+ warnx("%s: player %s is corrupt "
+ "('ltime' out of bounds!)",
+ __func__, parray[p].name);
+ return;
+ }
+ }
+
+ pp->s_stats.ltime = ltime_tmp[0];
+ pp->b_stats.ltime = ltime_tmp[1];
+ pp->w_stats.ltime = ltime_tmp[2];
+ pp->l_stats.ltime = ltime_tmp[3];
+ pp->bug_stats.ltime = ltime_tmp[4];
+
+ for (n = 0; n < ARRAY_SIZE(wb_tmp); n++) {
+ if (wb_tmp[n] < g_time_min ||
+ wb_tmp[n] > g_time_max) {
+ warnx("%s: player %s is corrupt "
+ "('whenbest' out of bounds!)",
+ __func__, parray[p].name);
+ return;
+ }
+ }
+
+ pp->s_stats.whenbest = wb_tmp[0];
+ pp->b_stats.whenbest = wb_tmp[1];
+ pp->w_stats.whenbest = wb_tmp[2];
+ pp->l_stats.whenbest = wb_tmp[3];
+ pp->bug_stats.whenbest = wb_tmp[4];
+
+ pp->b_stats.sterr = (bs / 10.0);
+ pp->s_stats.sterr = (ss / 10.0);
+ pp->w_stats.sterr = (ws / 10.0);
+ pp->l_stats.sterr = (ls / 10.0);
+ pp->bug_stats.sterr = (bugs / 10.0);
+
+ if (fgets(tmp2, sizeof tmp2, fp) == NULL) {
+ fprintf(stderr, "Player %s is corrupt\n", parray[p].name);
+ return;
+ } else {
+ tmp2[strcspn(tmp2, "\n")] = '\0';
+ pp->prompt = xstrdup(tmp2);
+ }
+
+ if (fscanf(fp, "%d %d %d %jd %jd %d %d %d %d %d %d %d %d %d %d %d %d %d "
+ "%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
+ &pp->open, &pp->rated, &pp->ropen, &array[0], &array[1],
+ &pp->bell, &pp->pgn, &pp->notifiedby, &pp->i_login, &pp->i_game,
+ &pp->i_shout, &pp->i_cshout, &pp->i_tell, &pp->i_kibitz,
+ &pp->private, &pp->jprivate, &pp->automail, &pp->i_mailmess,
+ &pp->style, &pp->d_time, &pp->d_inc, &pp->d_height, &pp->d_width,
+ &pp->language, &pp->adminLevel, &pp->num_white, &pp->num_black,
+ &pp->highlight, &pp->num_comments, &pp->num_plan, &pp->num_formula,
+ &size_cens, &size_not, &size_noplay, &size_gnot, &pp->numAlias,
+ &size_chan) != 37) {
+ fprintf(stderr, "Player %s is corrupt\n", parray[p].name);
+ return;
+ }
+
+ for (n = 0; n < ARRAY_SIZE(array); n++) {
+ if (array[n] < g_time_min ||
+ array[n] > g_time_max) {
+ warnx("%s: player %s is corrupt "
+ "(time not within range)",
+ __func__, parray[p].name);
+ return;
+ }
+ }
+
+ pp->timeOfReg = array[0];
+ pp->totalTime = array[1];
+
+ if (pp->num_plan >= MAX_PLAN) {
+ warnx("Player %s is corrupt\nToo many plans (%d)",
+ parray[p].name,
+ pp->num_plan);
+ return;
+ } else if (pp->num_formula >= MAX_FORMULA) {
+ warnx("Player %s is corrupt\nToo many formulas (%d)",
+ parray[p].name,
+ pp->num_formula);
+ return;
+ } else if (pp->numAlias >= MAX_ALIASES) {
+ warnx("Player %s is corrupt\nToo many aliases (%d)",
+ parray[p].name,
+ pp->numAlias);
+ return;
+ }
+
+ if (pp->num_plan > 0) {
+ for (i = 0; i < pp->num_plan; i++) {
+ if (fgets(tmp2, sizeof tmp2, fp) == NULL) {
+ warnx("%s: bad plan: feof %s", __func__, file);
+ return;
+ }
+
+ if (!(len = strlen(tmp2))) {
+ fprintf(stderr, "FICS: Error bad plan in "
+ "file %s\n", file);
+ i--;
+ pp->num_plan--;
+ } else {
+ // Get rid of '\n'.
+ tmp2[strcspn(tmp2, "\n")] = '\0';
+
+ pp->planLines[i] = (len > 1 ? xstrdup(tmp2) :
+ NULL);
+ }
+ }
+ }
+
+ if (pp->num_formula > 0) {
+ for (i = 0; i < pp->num_formula; i++) {
+ if (fgets(tmp2, sizeof tmp2, fp) == NULL) {
+ warnx("%s: bad formula: feof %s", __func__,
+ file);
+ return;
+ }
+
+ if (!(len = strlen(tmp2))) {
+ fprintf(stderr, "FICS: Error bad formula in "
+ "file %s\n", file);
+ i--;
+ pp->num_formula--;
+ } else {
+ // Get rid of '\n'.
+ tmp2[strcspn(tmp2, "\n")] = '\0';
+
+ pp->formulaLines[i] = (len > 1 ? xstrdup(tmp2) :
+ NULL);
+ }
+ }
+ }
+
+ if (fgets(tmp2, sizeof tmp2, fp) == NULL) {
+ warnx("%s: fgets() error", __func__);
+ return;
+ }
+
+ tmp2[strcspn(tmp2, "\n")] = '\0';
+
+ if (!strcmp(tmp2, "NONE"))
+ pp->formula = NULL;
+ else
+ pp->formula = xstrdup(tmp2);
+
+ if (pp->numAlias > 0) {
+ for (i = 0; i < pp->numAlias; i++) {
+ if (fgets(tmp2, sizeof tmp2, fp) == NULL) {
+ warnx("%s: bad alias: feof %s", __func__, file);
+ return;
+ }
+
+ if (!strlen(tmp2)) { // XXX
+ fprintf(stderr, "FICS: Error bad alias in "
+ "file %s\n", file);
+ i--;
+ pp->numAlias--;
+ } else {
+ tmp2[strcspn(tmp2, "\n")] = '\0';
+ tmp = eatword(tmp2);
+ *tmp = '\0';
+ tmp++;
+ tmp = eatwhite(tmp);
+
+ pp->alias_list[i].comm_name = xstrdup(tmp2);
+ pp->alias_list[i].alias = xstrdup(tmp);
+ }
+ }
+ }
+
+ if (add_to_list(fp, L_CENSOR, &size_cens, p) == -1) {
+ warnx("%s: add to list error (L_CENSOR): player: %s",
+ __func__, parray[p].name);
+ } else if (add_to_list(fp, L_NOTIFY, &size_not, p) == -1) {
+ warnx("%s: add to list error (L_NOTIFY): player: %s",
+ __func__, parray[p].name);
+ } else if (add_to_list(fp, L_NOPLAY, &size_noplay, p) == -1) {
+ warnx("%s: add to list error (L_NOPLAY): player: %s",
+ __func__, parray[p].name);
+ } else if (add_to_list(fp, L_GNOTIFY, &size_gnot, p) == -1) {
+ warnx("%s: add to list error (L_GNOTIFY): player: %s",
+ __func__, parray[p].name);
+ } else if (add_to_list(fp, L_CHANNEL, &size_chan, p) == -1) {
+ warnx("%s: add to list error (L_CHANNEL): player: %s",
+ __func__, parray[p].name);
+ }
+}
+
+PRIVATE int
+got_attr_value_player(int p, char *attr, char *value, FILE *fp, char *file)
+{
+ char *tmp1;
+ char tmp[MAX_LINE_SIZE] = { '\0' };
+ int i, len;
+
+ if (!strcmp(attr, "name:")) {
+ parray[p].name = xstrdup(value);
+ } else if (!strcmp(attr, "password:")) {
+ parray[p].passwd = xstrdup(value);
+ } else if (!strcmp(attr, "fullname:")) {
+ parray[p].fullName = xstrdup(value);
+ } else if (!strcmp(attr, "email:")) {
+ parray[p].emailAddress = xstrdup(value);
+ } else if (!strcmp(attr, "prompt:")) {
+ parray[p].prompt = xstrdup(value);
+ } else if (!strcmp(attr, "s_num:")) {
+ parray[p].s_stats.num = atoi(value);
+ } else if (!strcmp(attr, "s_win:")) {
+ parray[p].s_stats.win = atoi(value);
+ } else if (!strcmp(attr, "s_loss:")) {
+ parray[p].s_stats.los = atoi(value);
+ } else if (!strcmp(attr, "s_draw:")) {
+ parray[p].s_stats.dra = atoi(value);
+ } else if (!strcmp(attr, "s_rating:")) {
+ parray[p].s_stats.rating = atoi(value);
+ } else if (!strcmp(attr, "s_sterr:")) {
+ parray[p].s_stats.sterr = (atoi(value) / 10.0);
+ } else if (!strcmp(attr, "s_ltime:")) {
+ parray[p].s_stats.ltime = atoi(value);
+ } else if (!strcmp(attr, "s_best:")) {
+ parray[p].s_stats.best = atoi(value);
+ } else if (!strcmp(attr, "s_wbest:")) {
+ parray[p].s_stats.whenbest = atoi(value);
+ } else if (!strcmp(attr, "b_num:")) {
+ parray[p].b_stats.num = atoi(value);
+ } else if (!strcmp(attr, "b_win:")) {
+ parray[p].b_stats.win = atoi(value);
+ } else if (!strcmp(attr, "b_loss:")) {
+ parray[p].b_stats.los = atoi(value);
+ } else if (!strcmp(attr, "b_draw:")) {
+ parray[p].b_stats.dra = atoi(value);
+ } else if (!strcmp(attr, "b_rating:")) {
+ parray[p].b_stats.rating = atoi(value);
+ } else if (!strcmp(attr, "b_sterr:")) {
+ parray[p].b_stats.sterr = (atoi(value) / 10.0);
+ } else if (!strcmp(attr, "b_ltime:")) {
+ parray[p].b_stats.ltime = atoi(value);
+ } else if (!strcmp(attr, "b_best:")) {
+ parray[p].b_stats.best = atoi(value);
+ } else if (!strcmp(attr, "b_wbest:")) {
+ parray[p].b_stats.whenbest = atoi(value);
+ } else if (!strcmp(attr, "w_num:")) {
+ parray[p].w_stats.num = atoi(value);
+ } else if (!strcmp(attr, "w_win:")) {
+ parray[p].w_stats.win = atoi(value);
+ } else if (!strcmp(attr, "w_loss:")) {
+ parray[p].w_stats.los = atoi(value);
+ } else if (!strcmp(attr, "w_draw:")) {
+ parray[p].w_stats.dra = atoi(value);
+ } else if (!strcmp(attr, "w_rating:")) {
+ parray[p].w_stats.rating = atoi(value);
+ } else if (!strcmp(attr, "w_sterr:")) {
+ parray[p].w_stats.sterr = (atoi(value) / 10.0);
+ } else if (!strcmp(attr, "w_ltime:")) {
+ parray[p].w_stats.ltime = atoi(value);
+ } else if (!strcmp(attr, "w_best:")) {
+ parray[p].w_stats.best = atoi(value);
+ } else if (!strcmp(attr, "w_wbest:")) {
+ parray[p].w_stats.whenbest = atoi(value);
+ } else if (!strcmp(attr, "open:")) {
+ parray[p].open = atoi(value);
+ } else if (!strcmp(attr, "rated:")) {
+ parray[p].rated = atoi(value);
+ } else if (!strcmp(attr, "ropen:")) {
+ parray[p].ropen = atoi(value);
+ } else if (!strcmp(attr, "bell:")) {
+ parray[p].bell = atoi(value);
+ } else if (!strcmp(attr, "pgn:")) {
+ parray[p].pgn = atoi(value);
+ } else if (!strcmp(attr, "timeofreg:")) {
+ parray[p].timeOfReg = atoi(value);
+ } else if (!strcmp(attr, "totaltime:")) {
+ parray[p].totalTime = atoi(value);
+ } else if (!strcmp(attr, "notifiedby:")) {
+ parray[p].notifiedby = atoi(value);
+ } else if (!strcmp(attr, "i_login:")) {
+ parray[p].i_login = atoi(value);
+ } else if (!strcmp(attr, "i_game:")) {
+ parray[p].i_game = atoi(value);
+ } else if (!strcmp(attr, "i_shout:")) {
+ parray[p].i_shout = atoi(value);
+ } else if (!strcmp(attr, "i_cshout:")) {
+ parray[p].i_cshout = atoi(value);
+ } else if (!strcmp(attr, "i_tell:")) {
+ parray[p].i_tell = atoi(value);
+ } else if (!strcmp(attr, "i_kibitz:")) {
+ parray[p].i_kibitz = atoi(value);
+ } else if (!strcmp(attr, "kiblevel:")) {
+ parray[p].kiblevel = atoi(value);
+ } else if (!strcmp(attr, "private:")) {
+ parray[p].private = atoi(value);
+ } else if (!strcmp(attr, "jprivate:")) {
+ parray[p].jprivate = atoi(value);
+ } else if (!strcmp(attr, "automail:")) {
+ parray[p].automail = atoi(value);
+ } else if (!strcmp(attr, "i_mailmess:")) {
+ parray[p].i_mailmess = atoi(value);
+ } else if (!strcmp(attr, "style:")) {
+ parray[p].style = atoi(value);
+ } else if (!strcmp(attr, "d_time:")) {
+ parray[p].d_time = atoi(value);
+ } else if (!strcmp(attr, "d_inc:")) {
+ parray[p].d_inc = atoi(value);
+ } else if (!strcmp(attr, "d_height:")) {
+ parray[p].d_height = atoi(value);
+ } else if (!strcmp(attr, "d_width:")) {
+ parray[p].d_width = atoi(value);
+ } else if (!strcmp(attr, "language:")) {
+ parray[p].language = atoi(value);
+ } else if (!strcmp(attr, "admin_level:")) {
+ parray[p].adminLevel = atoi(value);
+
+ if (parray[p].adminLevel >= ADMIN_ADMIN)
+ parray[p].i_admin = 1;
+ } else if (!strcmp(attr, "i_admin:")) {
+ /* parray[p].i_admin = atoi(value) */;
+ } else if (!strcmp(attr, "computer:")) {
+ /* parray[p].computer = atoi(value) */;
+ } else if (!strcmp(attr, "black_games:")) {
+ parray[p].num_black = atoi(value);
+ } else if (!strcmp(attr, "white_games:")) {
+ parray[p].num_white = atoi(value);
+ } else if (!strcmp(attr, "uscf:")) {
+ /* parray[p].uscfRating = atoi(value) */;
+ } else if (!strcmp(attr, "muzzled:")) {
+ /* obsolete */;
+ } else if (!strcmp(attr, "cmuzzled:")) {
+ /* obsolete */;
+ } else if (!strcmp(attr, "highlight:")) {
+ parray[p].highlight = atoi(value);
+ } else if (!strcmp(attr, "network:")) {
+ /* parray[p].network_player = atoi(value) */;
+ } else if (!strcmp(attr, "lasthost:")) {
+ parray[p].lastHost = atoi(value);
+ } else if (!strcmp(attr, "channel:")) {
+ list_addsub(p, "channel", value, 1);
+ } else if (!strcmp(attr, "num_comments:")) {
+ parray[p].num_comments = atoi(value);
+ } else if (!strcmp(attr, "num_plan:")) {
+ /*
+ * num_plan
+ */
+
+ if ((parray[p].num_plan = atoi(value)) >= MAX_PLAN) {
+ warnx("%s: %s: too many plans (%d)", __func__, file,
+ parray[p].num_plan);
+ return -1;
+ } else if (parray[p].num_plan > 0) {
+ for (i = 0; i < parray[p].num_plan; i++) {
+
+ if (fgets(tmp, sizeof tmp, fp) == NULL) {
+ warnx("%s: bad plan: feof %s",
+ __func__, file);
+ return -1;
+ }
+
+ if (!(len = strlen(tmp))) {
+ fprintf(stderr, "FICS: Error bad plan "
+ "in file %s\n", file);
+ i--;
+ parray[p].num_plan--;
+ } else {
+ // Get rid of '\n'.
+ tmp[strcspn(tmp, "\n")] = '\0';
+
+ parray[p].planLines[i] = (len > 1 ?
+ xstrdup(tmp) : NULL);
+ }
+ }
+ } else {
+ /* null */;
+ }
+ } else if (!strcmp(attr, "num_formula:")) {
+ /*
+ * num_formula
+ */
+
+ if ((parray[p].num_formula = atoi(value)) >= MAX_FORMULA) {
+ warnx("%s: %s: too many formulas (%d)", __func__, file,
+ parray[p].num_formula);
+ return -1;
+ } else if (parray[p].num_formula > 0) {
+ for (i = 0; i < parray[p].num_formula; i++) {
+ if (fgets(tmp, sizeof tmp, fp) == NULL) {
+ warnx("%s: bad formula: feof %s",
+ __func__, file);
+ return -1;
+ }
+
+ if (!(len = strlen(tmp))) {
+ fprintf(stderr, "FICS: Error bad "
+ "formula in file %s\n", file);
+ i--;
+ parray[p].num_formula--;
+ } else {
+ // Get rid of '\n'.
+ tmp[strcspn(tmp, "\n")] = '\0';
+
+ parray[p].formulaLines[i] = (len > 1 ?
+ xstrdup(tmp) : NULL);
+ }
+ }
+ } else {
+ /* null */;
+ }
+ } else if (!strcmp(attr, "formula:")) {
+ /*
+ * formula
+ */
+
+ parray[p].formula = xstrdup(value);
+ } else if (!strcmp(attr, "num_alias:")) {
+ /*
+ * num_alias
+ */
+
+ if ((parray[p].numAlias = atoi(value)) >= MAX_ALIASES) {
+ warnx("%s: %s: too many aliases (%d)", __func__, file,
+ parray[p].numAlias);
+ return -1;
+ } else if (parray[p].numAlias > 0) {
+ for (i = 0; i < parray[p].numAlias; i++) {
+ if (fgets(tmp, sizeof tmp, fp) == NULL) {
+ warnx("%s: bad alias: feof %s",
+ __func__, file);
+ return -1;
+ }
+
+ if (!strlen(tmp)) { // XXX
+ fprintf(stderr, "FICS: Error bad alias "
+ "in file %s\n", file);
+ i--;
+ parray[p].numAlias--;
+ } else {
+ tmp[strcspn(tmp, "\n")] = '\0';
+ tmp1 = tmp;
+ tmp1 = eatword(tmp1);
+ *tmp1 = '\0';
+ tmp1++;
+ tmp1 = eatwhite(tmp1);
+
+ parray[p].alias_list[i].comm_name =
+ xstrdup(tmp);
+ parray[p].alias_list[i].alias =
+ xstrdup(tmp1);
+ }
+ }
+ } else {
+ /* null */;
+ }
+ } else if (!strcmp(attr, "num_censor:")) {
+ /*
+ * num_censor
+ */
+
+ if ((i = atoi(value)) < 0) {
+ warnx("%s: num censor negative", __func__);
+ return -1;
+ } else if (i > MAX_CENSOR) {
+ warnx("%s: num censor too large", __func__);
+ return -1;
+ }
+
+ while (i--) {
+ if (fgets(tmp, sizeof tmp, fp) == NULL) {
+ warnx("%s: bad censor: feof %s",
+ __func__, file);
+ return -1;
+ }
+
+ if (!(len = strlen(tmp)) || len == 1) { // blank lines
+ // do occur!
+ fprintf(stderr, "FICS: Error bad censor in "
+ "file %s\n", file);
+ } else {
+ tmp[strcspn(tmp, "\n")] = '\0';
+ list_add(p, L_CENSOR, tmp);
+ }
+ }
+ } else if (!strcmp(attr, "num_notify:")) {
+ if ((i = atoi(value)) < 0) {
+ warnx("%s: num notify negative", __func__);
+ return -1;
+ } else if (i > MAX_NOTIFY) {
+ warnx("%s: num notify too large", __func__);
+ return -1;
+ }
+
+ while (i--) {
+ if (fgets(tmp, sizeof tmp, fp) == NULL) {
+ warnx("%s: bad notify: feof %s",
+ __func__, file);
+ return -1;
+ }
+
+ if (!(len = strlen(tmp)) || len == 1) { // blank lines
+ // do occur!
+ fprintf(stderr, "FICS: Error bad notify in "
+ "file %s\n", file);
+ } else {
+ tmp[strcspn(tmp, "\n")] = '\0';
+ list_add(p, L_NOTIFY, tmp);
+ }
+ }
+ } else if (!strcmp(attr, "num_noplay:")) {
+ if ((i = atoi(value)) < 0) {
+ warnx("%s: num noplay negative", __func__);
+ return -1;
+ }
+
+ while (i--) {
+ if (fgets(tmp, sizeof tmp, fp) == NULL) {
+ warnx("%s: bad noplay: feof %s",
+ __func__, file);
+ return -1;
+ }
+
+ if (!(len = strlen(tmp)) || len == 1) { // blank lines
+ // do occur!
+ fprintf(stderr, "FICS: Error bad noplay in "
+ "file %s\n", file);
+ } else {
+ tmp[strcspn(tmp, "\n")] = '\0';
+ list_add(p, L_NOPLAY, tmp);
+ }
+ }
+ } else if (!strcmp(attr, "num_gnotify:")) {
+ if ((i = atoi(value)) < 0) {
+ warnx("%s: num gnotify negative", __func__);
+ return -1;
+ }
+
+ while (i--) {
+ if (fgets(tmp, sizeof tmp, fp) == NULL) {
+ warnx("%s: bad gnotify: feof %s",
+ __func__, file);
+ return -1;
+ }
+
+ if (!(len = strlen(tmp)) || len == 1) { // blank lines
+ // do occur!
+ fprintf(stderr, "FICS: Error bad gnotify in "
+ "file %s\n", file);
+ } else {
+ tmp[strcspn(tmp, "\n")] = '\0';
+ list_add(p, L_GNOTIFY, tmp);
+ }
+ }
+ } else {
+ fprintf(stderr, "FICS: Error bad attribute >%s< from file %s\n",
+ attr, file);
+ }
+
+ return 0;
+}
+
+PUBLIC int
+player_read(int p, char *name)
+{
+ FILE *fp = NULL;
+ char fname[MAX_FILENAME_SIZE] = { '\0' };
+ char line[MAX_LINE_SIZE] = { '\0' };
+ char *attr, *value;
+ char *resolvedPath = NULL;
+ int len = 0;
+ int version = 0;
+
+ parray[p].login = stolower(xstrdup(name)); // free on error?
+
+ if (!is_valid_login_name(parray[p].login)) {
+ warnx("%s: invalid login name: %s", __func__, parray[p].login);
+ return -1;
+ }
+
+ snprintf(fname, sizeof fname, "%s/%c/%s", player_dir,
+ parray[p].login[0], parray[p].login);
+
+ if ((resolvedPath = realpath(fname, NULL)) != NULL) {
+ if (strncmp(resolvedPath, player_dir,
+ strlen(player_dir)) != 0) {
+ warnx("%s: path traversal detected", __func__);
+ free(resolvedPath);
+ return -1;
+ }
+ mstrlcpy(fname, resolvedPath, sizeof fname);
+ free(resolvedPath);
+ resolvedPath = NULL;
+ }
+
+ if ((fp = fopen(fname, "r")) == NULL) { // Unregistered player
+ parray[p].name = xstrdup(name);
+ parray[p].registered = 0;
+ return -1;
+ }
+
+ parray[p].registered = 1; // Lets load the file
+
+ if (fgets(line, sizeof line, fp) == NULL) { // Ok, so which version
+ warnx("%s: fgets() error", __func__); // file?
+ fclose(fp);
+ return -1;
+ }
+
+ if (line[0] == 'v')
+ (void)sscanf(line, "%*c %d", &version);
+ if (version > 0) // Quick method:
+ ReadV1PlayerFmt(p, &parray[p], fp, fname, version);
+ else { // Do it the old SLOW way
+ do {
+ if (feof(fp))
+ break;
+ if ((len = strlen(line)) <= 1)
+ continue;
+
+ line[len - 1] = '\0';
+ attr = eatwhite(line);
+
+ if (attr[0] == '#')
+ continue; // Comment
+
+ value = eatword(attr);
+
+ if (!*value) {
+ fprintf(stderr, "FICS: Error reading file %s\n",
+ fname);
+ continue;
+ }
+
+ *value = '\0';
+ value++;
+ value = eatwhite(value);
+ stolower(attr);
+ got_attr_value_player(p, attr, value, fp, fname);
+
+ if (fgets(line, sizeof line, fp) == NULL)
+ break;
+ } while (!feof(fp));
+ }
+
+ fclose(fp);
+
+ if (version == 0) {
+ player_save(p); // Ensure old files are quickly converted e.g.
+ // when someone fingers.
+ }
+
+ if (!parray[p].name) {
+ parray[p].name = xstrdup(name);
+ pprintf(p, "\n*** WARNING: Your Data file is corrupt. "
+ "Please tell an admin ***\n");
+ }
+
+ return 0;
+}
+
+PUBLIC int
+player_delete(int p)
+{
+ char fname[MAX_FILENAME_SIZE];
+
+ if (!parray[p].registered) // Player must not be registered
+ return -1;
+
+ snprintf(fname, sizeof fname, "%s/%c/%s", player_dir,
+ parray[p].login[0], parray[p].login);
+ unlink(fname);
+
+ return 0;
+}
+
+PUBLIC int
+player_markdeleted(int p)
+{
+ FILE *fp;
+ char fname[MAX_FILENAME_SIZE];
+ char fname2[MAX_FILENAME_SIZE];
+ int fd;
+
+ if (!parray[p].registered) // Player must not be registered
+ return -1;
+
+ snprintf(fname, sizeof fname, "%s/%c/%s", player_dir,
+ parray[p].login[0], parray[p].login);
+ snprintf(fname2, sizeof fname2, "%s/%c/%s.delete", player_dir,
+ parray[p].login[0], parray[p].login);
+ xrename(__func__, fname, fname2);
+
+ errno = 0;
+ fd = open(fname2, g_open_flags[0], g_open_modes);
+
+ if (fd < 0) {
+ warn("%s: open", __func__);
+ return -1;
+ } else if ((fp = fdopen(fd, "a")) != NULL) { // Touch the file
+ fprintf(fp, "\n");
+ fclose(fp);
+ } else {
+ close(fd);
+ }
+
+ return 0;
+}
+
+PRIVATE void
+WritePlayerFile(FILE *fp, int p)
+{
+ int i;
+ player *pp = &parray[p];
+
+ fprintf(fp, "v %d\n", PLAYER_VERSION);
+
+ fprintf(fp, "%s\n", (pp->name ? pp->name : "NONE"));
+ fprintf(fp, "%s\n", (pp->fullName ? pp->fullName : "NONE"));
+ fprintf(fp, "%s\n", (pp->passwd ? pp->passwd : "NONE"));
+ fprintf(fp, "%s\n", (pp->emailAddress ? pp->emailAddress : "NONE"));
+
+ fprintf(fp, "%d %d %d %d %d %d %jd %d %jd %d %d %d %d %d %d %jd %d %jd %d "
+ "%d %d %d %d %d %jd %d %jd %d %d %d %d %d %d %jd %d %jd %d %d %d %d %d "
+ "%d %jd %d %jd %u\n",
+ pp->s_stats.num, pp->s_stats.win, pp->s_stats.los,
+ pp->s_stats.dra, pp->s_stats.rating,
+ (int)(pp->s_stats.sterr * 10.0),
+ (intmax_t)pp->s_stats.ltime, pp->s_stats.best,
+ (intmax_t)pp->s_stats.whenbest,
+
+ pp->b_stats.num, pp->b_stats.win, pp->b_stats.los,
+ pp->b_stats.dra, pp->b_stats.rating,
+ (int)(pp->b_stats.sterr * 10.0),
+ (intmax_t)pp->b_stats.ltime, pp->b_stats.best,
+ (intmax_t)pp->b_stats.whenbest,
+
+ pp->w_stats.num, pp->w_stats.win, pp->w_stats.los,
+ pp->w_stats.dra, pp->w_stats.rating,
+ (int)(pp->w_stats.sterr * 10.0),
+ (intmax_t)pp->w_stats.ltime, pp->w_stats.best,
+ (intmax_t)pp->w_stats.whenbest,
+
+ pp->l_stats.num, pp->l_stats.win, pp->l_stats.los,
+ pp->l_stats.dra, pp->l_stats.rating,
+ (int)(pp->l_stats.sterr * 10.0),
+ (intmax_t)pp->l_stats.ltime, pp->l_stats.best,
+ (intmax_t)pp->l_stats.whenbest,
+
+ pp->bug_stats.num, pp->bug_stats.win, pp->bug_stats.los,
+ pp->bug_stats.dra, pp->bug_stats.rating,
+ (int)(pp->bug_stats.sterr * 10.0),
+ (intmax_t)pp->bug_stats.ltime, pp->bug_stats.best,
+ (intmax_t)pp->bug_stats.whenbest,
+
+ pp->lastHost); /* fprintf() */
+
+ fprintf(fp, "%s\n", pp->prompt);
+
+ fprintf(fp, "%d %d %d %jd %jd %d %d %d %d %d %d %d %d %d %d %d %d %d %d "
+ "%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
+ pp->open, pp->rated, pp->ropen,
+ (intmax_t)pp->timeOfReg,
+ (intmax_t)pp->totalTime,
+ pp->bell, pp->pgn, pp->notifiedby, pp->i_login, pp->i_game,
+ pp->i_shout, pp->i_cshout, pp->i_tell, pp->i_kibitz, pp->private,
+ pp->jprivate, pp->automail, pp->i_mailmess, pp->style, pp->d_time,
+ pp->d_inc, pp->d_height, pp->d_width, pp->language, pp->adminLevel,
+ pp->num_white, pp->num_black, pp->highlight, pp->num_comments,
+ pp->num_plan, pp->num_formula,
+
+ list_size(p, L_CENSOR),
+ list_size(p, L_NOTIFY),
+ list_size(p, L_NOPLAY),
+ list_size(p, L_GNOTIFY),
+ pp->numAlias,
+ list_size(p, L_CHANNEL));
+
+ for (i = 0; i < pp->num_plan; i++)
+ fprintf(fp, "%s\n", (pp->planLines[i] ? pp->planLines[i] : ""));
+ for (i = 0; i < pp->num_formula; i++) {
+ fprintf(fp, "%s\n", (pp->formulaLines[i] ? pp->formulaLines[i] :
+ ""));
+ }
+
+ if (parray[p].formula != NULL)
+ fprintf(fp, "%s\n", pp->formula);
+ else
+ fprintf(fp, "NONE\n");
+
+ for (i = 0; i < pp->numAlias; i++) {
+ fprintf(fp, "%s %s\n", pp->alias_list[i].comm_name,
+ pp->alias_list[i].alias);
+ }
+
+ list_print(fp, p, L_CENSOR);
+ list_print(fp, p, L_NOTIFY);
+ list_print(fp, p, L_NOPLAY);
+ list_print(fp, p, L_GNOTIFY);
+ list_print(fp, p, L_CHANNEL);
+}
+
+PUBLIC int
+player_save(int p)
+{
+ FILE *fp;
+ char fname[MAX_FILENAME_SIZE];
+ int fd;
+
+ if (!player_num_ok_chk(p)) {
+ warnx("%s: invalid player number %d", __func__, p);
+ return -1;
+ }
+
+ if (!parray[p].registered) // Player must not be registered
+ return -1;
+
+ if (parray[p].name == NULL) { // Fixes a bug if name is null
+ pprintf(p, "WARNING: Your player file could not be updated, "
+ "due to corrupt data.\n");
+ return -1;
+ }
+
+ if (strcasecmp(parray[p].login, parray[p].name)) {
+ pprintf(p, "WARNING: Your player file could not be updated, "
+ "due to corrupt data.\n");
+ return -1;
+ }
+
+ snprintf(fname, sizeof fname, "%s/%c/%s", player_dir,
+ parray[p].login[0], parray[p].login);
+
+ errno = 0;
+ fd = open(fname, g_open_flags[1], g_open_modes);
+
+ if (fd < 0) {
+ warn("%s: Problem opening file %s for write", __func__, fname);
+ return -1;
+ } else if ((fp = fdopen(fd, "w")) == NULL) {
+ warn("%s: Problem opening file %s for write", __func__, fname);
+ close(fd);
+ return -1;
+ }
+
+ WritePlayerFile(fp, p);
+ fclose(fp);
+ return 0;
+}
+
+PUBLIC int
+player_find(int fd)
+{
+ for (int i = 0; i < p_num; i++) {
+ if (parray[i].status == PLAYER_EMPTY)
+ continue;
+ if (parray[i].socket == fd)
+ return i;
+ }
+
+ return -1;
+}
+
+PUBLIC int
+player_find_bylogin(char *name)
+{
+ for (int i = 0; i < p_num; i++) {
+ if (parray[i].status == PLAYER_EMPTY ||
+ parray[i].status == PLAYER_LOGIN ||
+ parray[i].status == PLAYER_PASSWORD)
+ continue;
+
+ if (!parray[i].login)
+ continue;
+
+ if (!strcmp(parray[i].login, name))
+ return i;
+ }
+
+ return -1;
+}
+
+PUBLIC int
+player_find_part_login(char *name)
+{
+ int found = -1;
+ int i;
+ size_t namelen;
+
+ if ((i = player_find_bylogin(name)) >= 0)
+ return i;
+
+ namelen = strlen(name);
+
+ for (i = 0; i < p_num; i++) {
+ if (parray[i].status == PLAYER_EMPTY ||
+ parray[i].status == PLAYER_LOGIN ||
+ parray[i].status == PLAYER_PASSWORD)
+ continue;
+
+ if (!parray[i].login)
+ continue;
+
+ if (!strncmp(parray[i].login, name, namelen)) {
+ if (found >= 0) /* Ambiguous */
+ return -2;
+ found = i;
+ }
+ }
+
+ return found;
+}
+
+PUBLIC int
+player_censored(int p, int p1)
+{
+ if (in_list(p, L_CENSOR, parray[p1].login))
+ return 1;
+ return 0;
+}
+
+/*
+ * Is p1 on p's notify list?
+ */
+PUBLIC int
+player_notified(int p, int p1)
+{
+ if (!parray[p1].registered)
+ return 0;
+
+ /* Possible bug: 'p' has just arrived! */
+ if (!parray[p].name)
+ return 0;
+
+ return in_list(p, L_NOTIFY, parray[p1].login);
+}
+
+PUBLIC void
+player_notify_departure(int p)
+{
+ if (!parray[p].registered)
+ return;
+
+ for (int p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].notifiedby &&
+ !player_notified(p1, p) &&
+ player_notified(p, p1) &&
+ parray[p1].status == PLAYER_PROMPT) {
+ if (parray[p1].bell)
+ pprintf_noformat(p1, "\007");
+ pprintf(p1, "\nNotification: ");
+ pprintf_highlight(p1, "%s", parray[p].name);
+ pprintf_prompt(p1, " has departed and isn't on your "
+ "notify list.\n");
+ }
+ }
+}
+
+PUBLIC int
+player_notify_present(int p)
+{
+ int count = 0;
+ int p1;
+
+ if (!parray[p].registered)
+ return count;
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (player_notified(p, p1) && parray[p1].status ==
+ PLAYER_PROMPT) {
+ if (!count)
+ pprintf(p, "Present company includes:");
+ count++;
+
+ pprintf(p, " %s", parray[p1].name);
+
+ if (parray[p1].notifiedby &&
+ !player_notified(p1, p) &&
+ parray[p1].status == PLAYER_PROMPT) {
+ if (parray[p1].bell)
+ pprintf_noformat(p1, "\007");
+ pprintf(p1, "\nNotification: ");
+ pprintf_highlight(p1, "%s", parray[p].name);
+ pprintf_prompt(p1, " has arrived and isn't on "
+ "your notify list.\n");
+ }
+ }
+ }
+
+ if (count)
+ pprintf(p, ".\n");
+ return count;
+}
+
+PUBLIC int
+player_notify(int p, char *note1, char *note2)
+{ // Notify those interested that 'p' has arrived/departed.
+ int count = 0;
+ int p1;
+
+ if (!parray[p].registered)
+ return count;
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (player_notified(p1, p) && parray[p1].status ==
+ PLAYER_PROMPT) {
+ if (parray[p1].bell)
+ pprintf_noformat(p1, "\007");
+
+ pprintf(p1, "\nNotification: ");
+ pprintf_highlight(p1, "%s", parray[p].name);
+ pprintf_prompt(p1, " has %s.\n", note1);
+
+ if (!count)
+ pprintf(p, "Your %s was noted by:", note2);
+ count++;
+
+ pprintf(p, " %s", parray[p1].name);
+ }
+ }
+
+ if (count)
+ pprintf(p, ".\n");
+ return count;
+}
+
+/*
+ * Show adjourned games upon logon
+ */
+PUBLIC int
+showstored(int p)
+{
+ DIR *dirp;
+ char dname[MAX_FILENAME_SIZE];
+ int c = 0, p1;
+ multicol *m = multicol_start(50); // Limit to 50
+ // (should be enough)
+#ifdef USE_DIRENT
+ struct dirent *dp;
+#else
+ struct direct *dp;
+#endif
+
+ snprintf(dname, sizeof dname, "%s/%c", adj_dir, parray[p].login[0]);
+
+ if ((dirp = opendir(dname)) == NULL) {
+ multicol_end(m);
+ return COM_OK;
+ }
+
+ for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
+ if (file_has_pname(dp->d_name, parray[p].login)) {
+ if (strcmp(file_wplayer(dp->d_name), parray[p].login)) {
+ p1 = player_find_bylogin
+ (file_wplayer(dp->d_name));
+ } else {
+ p1 = player_find_bylogin
+ (file_bplayer(dp->d_name));
+ }
+
+ if (p1 >= 0) {
+ if (c < 50)
+ multicol_store(m, parray[p1].name);
+ pprintf(p1, "\nNotification: ");
+ pprintf_highlight(p1, "%s", parray[p].name);
+ pprintf_prompt(p1, ", who has an adjourned "
+ "game with you, has arrived.\n");
+ c++;
+ }
+ }
+ }
+
+ closedir(dirp);
+
+ if (c == 1) {
+ pprintf(p, "1 player, who has an adjourned game with you, is "
+ "online:\007");
+ } else if (c > 1) {
+ pprintf(p, "\n%d players, who have an adjourned game with you, "
+ "are online:\007", c);
+ }
+
+ if (c != 0)
+ multicol_pprint(m, p, parray[p].d_width, 2);
+ multicol_end(m);
+ return COM_OK;
+}
+
+PUBLIC int
+player_count(int CountAdmins)
+{
+ int count;
+ int i;
+
+ count = 0;
+ i = 0;
+
+ while (i < p_num) {
+ if (parray[i].status == PLAYER_PROMPT &&
+ (CountAdmins || !in_list(i, L_ADMIN, parray[i].name)))
+ count++;
+ i++;
+ }
+
+ if (count > player_high)
+ player_high = count;
+ return count;
+}
+
+PUBLIC int
+player_idle(int p)
+{ // XXX
+ if (parray[p].status != PLAYER_PROMPT)
+ return (time(NULL) - parray[p].logon_time);
+
+ return (time(NULL) - parray[p].last_command_time);
+}
+
+PUBLIC int
+player_ontime(int p)
+{ // XXX
+ return (time(NULL) - parray[p].logon_time);
+}
+
+PRIVATE void
+write_p_inout(int inout, int p, char *file, int maxlines)
+{
+ FILE *fp;
+ int fd;
+
+ errno = 0;
+ fd = open(file, g_open_flags[0], g_open_modes);
+
+ if (fd < 0) {
+ warn("%s: open", __func__);
+ return;
+ } else if ((fp = fdopen(fd, "a")) == NULL) {
+ warn("%s: fdopen", __func__);
+ close(fd);
+ return;
+ }
+
+ fprintf(fp, "%d %s %ld %d %s\n", inout, parray[p].name,
+ (long int)time(NULL), parray[p].registered,
+ dotQuad(parray[p].thisHost));
+
+ fclose(fp);
+
+ if (maxlines)
+ truncate_file(file, maxlines);
+}
+
+PUBLIC void
+player_write_login(int p)
+{
+ char fname[MAX_FILENAME_SIZE];
+
+ if (parray[p].registered) {
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s",
+ stats_dir, parray[p].login[0], parray[p].login,
+ STATS_LOGONS);
+ write_p_inout(P_LOGIN, p, fname, 8);
+ }
+
+ snprintf(fname, sizeof fname, "%s/%s", stats_dir, STATS_LOGONS);
+ write_p_inout(P_LOGIN, p, fname, 30);
+
+ snprintf(fname, sizeof fname, "%s/%s", stats_dir, "logons.log");
+ write_p_inout(P_LOGIN, p, fname, 0);
+}
+
+PUBLIC void
+player_write_logout(int p)
+{
+ char fname[MAX_FILENAME_SIZE];
+
+ if (parray[p].registered) {
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s",
+ stats_dir, parray[p].login[0], parray[p].login,
+ STATS_LOGONS);
+ write_p_inout(P_LOGOUT, p, fname, 8);
+ }
+
+ snprintf(fname, sizeof fname, "%s/%s", stats_dir, STATS_LOGONS);
+ write_p_inout(P_LOGOUT, p, fname, 30);
+
+ snprintf(fname, sizeof fname, "%s/%s", stats_dir, "logons.log");
+ write_p_inout(P_LOGOUT, p, fname, 0);
+}
+
+PUBLIC time_t
+player_lastconnect(int p)
+{
+ FILE *fp;
+ char fname[MAX_FILENAME_SIZE];
+ char ipstr[20];
+ char loginName[MAX_LOGIN_NAME];
+ int inout, registered;
+ int ret, too_long;
+ int64_t lval = 0;
+ time_t last = 0;
+
+ ret = snprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s",
+ stats_dir, parray[p].login[0], parray[p].login, STATS_LOGONS);
+ too_long = (ret < 0 || (size_t)ret >= sizeof fname);
+
+ if (too_long) {
+ fprintf(stderr, "FICS: %s: warning: snprintf truncated\n",
+ __func__);
+ }
+
+ if ((fp = fopen(fname, "r")) == NULL)
+ return 0;
+
+ inout = 1;
+
+ while (!feof(fp)) {
+ if (inout == P_LOGIN)
+ last = lval;
+
+ _Static_assert(19 < ARRAY_SIZE(loginName),
+ "'loginName' too small");
+ _Static_assert(19 < ARRAY_SIZE(ipstr),
+ "'ipstr' too small");
+
+ if (fscanf(fp, ("%d %19s " "%" SCNd64 " %d %19s\n"), &inout,
+ loginName, &lval, &registered, ipstr) != 5) {
+ fprintf(stderr, "FICS: Error in login info format. %s"
+ "\n", fname);
+ fclose(fp);
+ return 0;
+ }
+ }
+
+ fclose(fp);
+ return last;
+}
+
+PUBLIC time_t
+player_lastdisconnect(int p)
+{
+ FILE *fp;
+ char fname[MAX_FILENAME_SIZE];
+ char ipstr[20];
+ char loginName[MAX_LOGIN_NAME];
+ int inout, registered;
+ int ret, too_long;
+ int64_t lval;
+ time_t last = 0;
+
+ ret = snprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s",
+ stats_dir, parray[p].login[0], parray[p].login, STATS_LOGONS);
+ too_long = (ret < 0 || (size_t)ret >= sizeof fname);
+
+ if (too_long) {
+ fprintf(stderr, "FICS: %s: warning: snprintf truncated\n",
+ __func__);
+ }
+
+ if ((fp = fopen(fname, "r")) == NULL)
+ return 0;
+
+ while (!feof(fp)) {
+ _Static_assert(19 < ARRAY_SIZE(loginName),
+ "'loginName' too small");
+ _Static_assert(19 < ARRAY_SIZE(ipstr),
+ "'ipstr' too small");
+
+ if (fscanf(fp, ("%d %19s " "%" SCNd64 " %d %19s\n"), &inout,
+ loginName, &lval, &registered, ipstr) != 5) {
+ fprintf(stderr, "FICS: Error in login info format. %s"
+ "\n", fname);
+ fclose(fp);
+ return 0;
+ }
+
+ if (inout == P_LOGOUT)
+ last = lval;
+ }
+
+ fclose(fp);
+ return last;
+}
+
+PUBLIC void
+player_pend_print(int p, pending *pend)
+{
+ char outstr[200] = { '\0' };
+ char tmp[200] = { '\0' };
+
+ if (p == pend->whofrom) {
+ (void)strlcpy(outstr, "You are offering ", sizeof outstr);
+ } else {
+ snprintf(outstr, sizeof outstr, "%s is offering ",
+ parray[pend->whofrom].name);
+ }
+
+ if (p == pend->whoto) {
+ /* null */;
+ } else {
+ snprintf(tmp, sizeof tmp, "%s ", parray[pend->whoto].name);
+ }
+
+ if (strlcat(outstr, tmp, sizeof outstr) >= sizeof outstr) {
+ fprintf(stderr, "FICS: %s: warning: strlcat() truncated\n",
+ __func__);
+ }
+
+ switch (pend->type) {
+ case PEND_MATCH:
+ snprintf(tmp, sizeof tmp, "%s.", game_str(pend->param5,
+ (pend->param1 * 60),
+ pend->param2,
+ (pend->param3 * 60),
+ pend->param4,
+ pend->char1,
+ pend->char2));
+ break;
+ case PEND_DRAW:
+ strlcpy(tmp, "a draw.\n", sizeof tmp);
+ break;
+ case PEND_PAUSE:
+ strlcpy(tmp, "to pause the clock.\n", sizeof tmp);
+ break;
+ case PEND_ABORT:
+ strlcpy(tmp, "to abort the game.\n", sizeof tmp);
+ break;
+ case PEND_TAKEBACK:
+ snprintf(tmp, sizeof tmp, "to takeback the last %d "
+ "half moves.\n", pend->param1);
+ break;
+ case PEND_SIMUL:
+ strlcpy(tmp, "to play a simul match.\n", sizeof tmp);
+ break;
+ case PEND_SWITCH:
+ strlcpy(tmp, "to switch sides.\n", sizeof tmp);
+ break;
+ case PEND_ADJOURN:
+ strlcpy(tmp, "an adjournment.\n", sizeof tmp);
+ break;
+ case PEND_PARTNER:
+ strlcpy(tmp, "to be bughouse partners.\n", sizeof tmp);
+ break;
+ }
+
+ if (strlcat(outstr, tmp, sizeof outstr) >= sizeof outstr) {
+ fprintf(stderr, "FICS: %s: warning: strlcat() truncated\n",
+ __func__);
+ }
+
+ pprintf(p, "%s\n", outstr);
+}
+
+PUBLIC int
+player_find_pendto(int p, int p1, int type)
+{
+ for (int i = 0; i < parray[p].num_to; i++) {
+ if (parray[p].p_to_list[i].whoto != p1 && p1 != -1)
+ continue;
+ if (type < 0 || parray[p].p_to_list[i].type == type)
+ return i;
+ if (type == PEND_BUGHOUSE &&
+ parray[p].p_to_list[i].type == PEND_MATCH &&
+ !strcmp(parray[p].p_to_list[i].char2, "bughouse"))
+ return i;
+ }
+
+ return -1;
+}
+
+PUBLIC int
+player_new_pendto(int p)
+{
+ if (parray[p].num_to >= MAX_PENDING)
+ return -1;
+
+ parray[p].num_to++;
+
+ return (parray[p].num_to - 1);
+}
+
+PUBLIC int
+player_remove_pendto(int p, int p1, int type)
+{
+ bool removed = false;
+ int w;
+
+ if ((w = player_find_pendto(p, p1, type)) < 0)
+ return -1;
+
+ for (; w < (parray[p].num_to - 1); w++) {
+ if (w + 1 >= (int)ARRAY_SIZE(parray[0].p_to_list)) {
+ warnx("%s: overflowed array index write", __func__);
+ break;
+ }
+
+ parray[p].p_to_list[w] = parray[p].p_to_list[w + 1];
+ removed = true;
+ }
+
+ UNUSED_VAR(removed);
+ parray[p].num_to -= 1;
+
+ return (0);
+}
+
+PUBLIC int
+player_find_pendfrom(int p, int p1, int type)
+{
+ for (int i = 0; i < parray[p].num_from; i++) {
+ if (parray[p].p_from_list[i].whofrom != p1 && p1 != -1)
+ continue;
+ if (type == PEND_ALL || parray[p].p_from_list[i].type == type)
+ return i;
+ if (type < 0 && parray[p].p_from_list[i].type != -type)
+ return i;
+ /*
+ * The above "if" allows a type of -PEND_SIMUL to
+ * match every request EXCEPT simuls, for example. I'm
+ * doing this because Heringer does not want to
+ * decline simul requests when he makes a move in a
+ * sumul. -- hersco.
+ */
+
+ if (type == PEND_BUGHOUSE &&
+ parray[p].p_from_list[i].type == PEND_MATCH &&
+ !strcmp(parray[p].p_from_list[i].char2, "bughouse"))
+ return i;
+ }
+
+ return -1;
+}
+
+PUBLIC int
+player_new_pendfrom(int p)
+{
+ if (parray[p].num_from >= MAX_PENDING)
+ return -1;
+
+ parray[p].num_from++;
+
+ return (parray[p].num_from - 1);
+}
+
+PUBLIC int
+player_remove_pendfrom(int p, int p1, int type)
+{
+ bool removed = false;
+ int w;
+
+ if ((w = player_find_pendfrom(p, p1, type)) < 0)
+ return -1;
+
+ for (; w < (parray[p].num_from - 1); w++) {
+ if (w + 1 >= (int)ARRAY_SIZE(parray[0].p_from_list)) {
+ warnx("%s: overflowed array index write", __func__);
+ break;
+ }
+
+ parray[p].p_from_list[w] = parray[p].p_from_list[w + 1];
+ removed = true;
+ }
+
+ UNUSED_VAR(removed);
+ parray[p].num_from -= 1;
+
+ return (0);
+}
+
+PUBLIC int
+player_add_request(int p, int p1, int type, int param)
+{
+ int pendf;
+ int pendt;
+
+ if (player_find_pendto(p, p1, type) >= 0)
+ return -1; // Already exists
+
+ if ((pendt = player_new_pendto(p)) == -1)
+ return -1;
+ if ((pendf = player_new_pendfrom(p1)) == -1) {
+ parray[p].num_to--;
+ return -1;
+ }
+
+ parray[p].p_to_list[pendt].type = type;
+ parray[p].p_to_list[pendt].whoto = p1;
+ parray[p].p_to_list[pendt].whofrom = p;
+ parray[p].p_to_list[pendt].param1 = param;
+
+ parray[p1].p_from_list[pendf].type = type;
+ parray[p1].p_from_list[pendf].whoto = p1;
+ parray[p1].p_from_list[pendf].whofrom = p;
+ parray[p1].p_from_list[pendf].param1 = param;
+
+ return 0;
+}
+
+PUBLIC int
+player_remove_request(int p, int p1, int type)
+{
+ bool removed;
+ int to = 0, from = 0;
+
+ while (to != -1 && (to = player_find_pendto(p, p1, type)) != -1) {
+ removed = false;
+
+ for (; to < parray[p].num_to - 1; to++) {
+ if (to + 1 >= (int)ARRAY_SIZE(parray[0].p_to_list)) {
+ warnx("%s: overflowed array index read/write",
+ __func__);
+ break;
+ }
+
+ parray[p].p_to_list[to] = parray[p].p_to_list[to + 1];
+ removed = true;
+ }
+
+ UNUSED_VAR(removed);
+ parray[p].num_to -= 1;
+ }
+
+ while (from != -1 && (from = player_find_pendfrom(p1, p, type)) != -1) {
+ removed = false;
+
+ for (; from < parray[p1].num_from - 1; from++) {
+ if (from + 1 >= (int)ARRAY_SIZE(parray[0].p_from_list)) {
+ warnx("%s: overflowed array index read/write",
+ __func__);
+ break;
+ }
+
+ parray[p1].p_from_list[from] =
+ parray[p1].p_from_list[from + 1];
+ removed = true;
+ }
+
+ UNUSED_VAR(removed);
+ parray[p1].num_from -= 1;
+ }
+
+ if ((type == PEND_ALL || type == PEND_MATCH) && parray[p].partner >= 0)
+ player_remove_request(parray[p].partner, p1, PEND_BUGHOUSE);
+
+ return 0;
+}
+
+PUBLIC int
+player_decline_offers(int p, int p1, int offerType)
+{
+ char *pName = parray[p].name;
+ char *p2Name;
+ int count = 0;
+ int offer;
+ int part, p2part;
+ int type, p2;
+
+ // First get rid of bughouse offers from partner.
+
+ if ((offerType == PEND_MATCH || offerType == PEND_ALL) &&
+ parray[p].partner >= 0 &&
+ parray[parray[p].partner].partner == p) {
+ count += player_decline_offers(parray[p].partner, p1,
+ PEND_BUGHOUSE);
+ }
+
+ while ((offer = player_find_pendfrom(p, p1, offerType)) >= 0) {
+ type = parray[p].p_from_list[offer].type;
+ p2 = parray[p].p_from_list[offer].whofrom;
+ p2Name = parray[p2].name;
+ part = parray[p].partner;
+
+ if (part >= 0 && parray[part].partner != p)
+ part = -1;
+
+ p2part = parray[p2].partner;
+
+ if (p2part >= 0 && parray[p2part].partner != p2)
+ p2part = -1;
+
+ switch (type) {
+ case PEND_MATCH:
+ pprintf_prompt(p2, "\n%s declines the match offer.\n",
+ pName);
+ pprintf(p, "You decline the match offer from %s.\n",
+ p2Name);
+
+ if (!strcmp(parray[p].p_from_list[offer].char2,
+ "bughouse")) {
+ if (part >= 0) {
+ pprintf_prompt(part, "Your partner "
+ "declines the bughouse offer from "
+ "%s.\n", parray[p2].name);
+ }
+
+ if (p2part >= 0) {
+ pprintf_prompt(p2part, "%s declines "
+ "the bughouse offer from your "
+ "partner.\n", parray[p].name);
+ }
+ }
+
+ break;
+ case PEND_DRAW:
+ pprintf_prompt(p2, "\n%s declines draw request.\n",
+ pName);
+ pprintf(p, "You decline the draw request from %s.\n",
+ p2Name);
+ break;
+ case PEND_PAUSE:
+ pprintf_prompt(p2, "\n%s declines pause request.\n",
+ pName);
+ pprintf(p, "You decline the pause request from %s.\n",
+ p2Name);
+ break;
+ case PEND_ABORT:
+ pprintf_prompt(p2, "\n%s declines abort request.\n",
+ pName);
+ pprintf(p, "You decline the abort request from %s.\n",
+ p2Name);
+ break;
+ case PEND_TAKEBACK:
+ pprintf_prompt(p2, "\n%s declines the takeback request."
+ "\n", pName);
+ pprintf(p, "You decline the takeback request from %s."
+ "\n", p2Name);
+ break;
+ case PEND_ADJOURN:
+ pprintf_prompt(p2, "\n%s declines the adjourn request."
+ "\n", pName);
+ pprintf(p, "You decline the adjourn request from %s.\n",
+ p2Name);
+ break;
+ case PEND_SWITCH:
+ pprintf_prompt(p2, "\n%s declines the switch sides "
+ "request.\n", pName);
+ pprintf(p, "You decline the switch sides request from "
+ "%s.\n", p2Name);
+ break;
+ case PEND_SIMUL:
+ pprintf_prompt(p2, "\n%s declines the simul offer.\n",
+ pName);
+ pprintf(p, "You decline the simul offer from %s.\n",
+ p2Name);
+ break;
+ case PEND_PARTNER:
+ pprintf_prompt(p2, "\n%s declines your partnership "
+ "request.\n", pName);
+ pprintf(p, "You decline the partnership request from "
+ "%s.\n", p2Name);
+ break;
+ } /* switch */
+
+ player_remove_request(p2, p, type);
+ count++;
+ } /* while */
+
+ return count;
+}
+
+PUBLIC int
+player_withdraw_offers(int p, int p1, int offerType)
+{
+ char *pName = parray[p].name;
+ char *p2Name;
+ int count = 0;
+ int offer;
+ int part, p2part;
+ int type, p2;
+
+ // First get rid of bughouse offers from partner.
+
+ if ((offerType == PEND_MATCH || offerType == PEND_ALL) &&
+ parray[p].partner >= 0 &&
+ parray[parray[p].partner].partner == p) {
+ count += player_withdraw_offers(parray[p].partner, p1,
+ PEND_BUGHOUSE);
+ }
+
+ while ((offer = player_find_pendto(p, p1, offerType)) >= 0) {
+ type = parray[p].p_to_list[offer].type;
+ p2 = parray[p].p_to_list[offer].whoto;
+ p2Name = parray[p2].name;
+ part = parray[p].partner;
+
+ if (part >= 0 && parray[part].partner != p)
+ part = -1;
+
+ p2part = parray[p2].partner;
+
+ if (p2part >= 0 && parray[p2part].partner != p2)
+ p2part = -1;
+
+ switch (type) {
+ case PEND_MATCH:
+ pprintf_prompt(p2, "\n%s withdraws the match offer.\n",
+ pName);
+ pprintf(p, "You withdraw the match offer to %s.\n",
+ p2Name);
+
+ if (!strcmp(parray[p].p_to_list[offer].char2,
+ "bughouse")) {
+ if (part >= 0) {
+ pprintf_prompt(part, "Your partner "
+ "withdraws the bughouse offer to "
+ "%s.\n", parray[p2].name);
+ }
+ if (p2part >= 0) {
+ pprintf_prompt(p2part, "%s withdraws "
+ "the bughouse offer to your "
+ "partner.\n", parray[p].name);
+ }
+ }
+
+ break;
+ case PEND_DRAW:
+ pprintf_prompt(p2, "\n%s withdraws draw request.\n",
+ pName);
+ pprintf(p, "You withdraw the draw request to %s.\n",
+ p2Name);
+ break;
+ case PEND_PAUSE:
+ pprintf_prompt(p2, "\n%s withdraws pause request.\n",
+ pName);
+ pprintf(p, "You withdraw the pause request to %s.\n",
+ p2Name);
+ break;
+ case PEND_ABORT:
+ pprintf_prompt(p2, "\n%s withdraws abort request.\n",
+ pName);
+ pprintf(p, "You withdraw the abort request to %s.\n",
+ p2Name);
+ break;
+ case PEND_TAKEBACK:
+ pprintf_prompt(p2, "\n%s withdraws the takeback "
+ "request.\n", pName);
+ pprintf(p, "You withdraw the takeback request to %s.\n",
+ p2Name);
+ break;
+ case PEND_ADJOURN:
+ pprintf_prompt(p2, "\n%s withdraws the adjourn request."
+ "\n", pName);
+ pprintf(p, "You withdraw the adjourn request to %s.\n",
+ p2Name);
+ break;
+ case PEND_SWITCH:
+ pprintf_prompt(p2, "\n%s withdraws the switch sides "
+ "request.\n", pName);
+ pprintf(p, "You withdraw the switch sides request to "
+ "%s.\n", p2Name);
+ break;
+ case PEND_SIMUL:
+ pprintf_prompt(p2, "\n%s withdraws the simul offer.\n",
+ pName);
+ pprintf(p, "You withdraw the simul offer to %s.\n",
+ p2Name);
+ break;
+ case PEND_PARTNER:
+ pprintf_prompt(p2, "\n%s withdraws partnership request."
+ "\n", pName);
+ pprintf(p, "You withdraw the partnership request to %s."
+ "\n", p2Name);
+ break;
+ } /* switch */
+
+ player_remove_request(p, p2, type);
+ count++;
+ } /* while */
+
+ return count;
+}
+
+PUBLIC int
+player_is_observe(int p, int g)
+{
+ int i;
+
+ for (i = 0; i < parray[p].num_observe; i++) {
+ if (parray[p].observe_list[i] == g)
+ break;
+ }
+ if (i == parray[p].num_observe)
+ return 0;
+ else
+ return 1;
+}
+
+PUBLIC int
+player_add_observe(int p, int g)
+{
+ if (parray[p].num_observe == MAX_OBSERVE)
+ return -1;
+ parray[p].observe_list[parray[p].num_observe] = g;
+ parray[p].num_observe++;
+ return 0;
+}
+
+PUBLIC int
+player_remove_observe(int p, int g)
+{
+ int i;
+
+ for (i = 0; i < parray[p].num_observe; i++) {
+ if (parray[p].observe_list[i] == g)
+ break;
+ }
+
+ if (i == parray[p].num_observe)
+ return -1; // Not found!
+
+ for (; i < (parray[p].num_observe - 1); i++)
+ parray[p].observe_list[i] = parray[p].observe_list[i + 1];
+
+ parray[p].num_observe--;
+
+ return 0;
+}
+
+PUBLIC int
+player_game_ended(int g)
+{
+ for (int p = 0; p < p_num; p++) {
+ if (parray[p].status == PLAYER_EMPTY)
+ continue;
+ player_remove_observe(p, g);
+ }
+
+ player_remove_request(garray[g].white, garray[g].black, -1);
+ player_remove_request(garray[g].black, garray[g].white, -1);
+
+ player_save(garray[g].white);
+ player_save(garray[g].black);
+
+ return 0;
+}
+
+PUBLIC int
+player_goto_board(int p, int board_num)
+{
+ int start, count = 0, on, g;
+
+ if (board_num < 0 || board_num >= parray[p].simul_info.numBoards)
+ return -1;
+
+ if (parray[p].simul_info.boards[board_num] < 0)
+ return -1;
+
+ parray[p].simul_info.onBoard = board_num;
+ parray[p].game = parray[p].simul_info.boards[board_num];
+ parray[p].opponent = garray[parray[p].game].black;
+
+ if (parray[p].simul_info.numBoards == 1)
+ return 0;
+
+ send_board_to(parray[p].game, p);
+
+ start = parray[p].game;
+ on = parray[p].simul_info.onBoard;
+
+ do {
+ if ((g = parray[p].simul_info.boards[on]) >= 0) {
+ if (count == 0) {
+ if (parray[garray[g].black].bell)
+ pprintf(garray[g].black, "\007");
+
+ pprintf(garray[g].black, "\n");
+ pprintf_highlight(garray[g].black, "%s",
+ parray[p].name);
+ pprintf_prompt(garray[g].black, " is at your "
+ "board!\n");
+ } else if (count == 1) {
+ if (parray[garray[g].black].bell)
+ pprintf(garray[g].black, "\007");
+
+ pprintf(garray[g].black, "\n");
+ pprintf_highlight(garray[g].black, "%s",
+ parray[p].name);
+ pprintf_prompt(garray[g].black, " will be at "
+ "your board NEXT!\n");
+ } else {
+ pprintf(garray[g].black, "\n");
+ pprintf_highlight(garray[g].black, "%s",
+ parray[p].name);
+ pprintf_prompt(garray[g].black, " is %d boards "
+ "away.\n", count);
+ }
+
+ count++;
+ }
+
+ if (++on >= parray[p].simul_info.numBoards)
+ on = 0;
+ } while (start != parray[p].simul_info.boards[on]);
+
+ return 0;
+}
+
+PUBLIC int
+player_goto_next_board(int p)
+{
+ int g;
+ int on;
+ int start;
+
+ on = parray[p].simul_info.onBoard;
+ start = on;
+
+ do {
+ on++;
+
+ if (on >= parray[p].simul_info.numBoards)
+ on = 0;
+
+ if ((g = parray[p].simul_info.boards[on]) >= 0)
+ break;
+ } while (start != on);
+
+ if (g == -1) {
+ pprintf(p, "\nMajor Problem! Can't find your next board.\n");
+ return -1;
+ }
+
+ return player_goto_board(p, on);
+}
+
+PUBLIC int
+player_goto_prev_board(int p)
+{
+ int g;
+ int on;
+ int start;
+
+ on = parray[p].simul_info.onBoard;
+ start = on;
+
+ do {
+ --on;
+
+ if (on < 0)
+ on = (parray[p].simul_info.numBoards) - 1;
+
+ if ((g = parray[p].simul_info.boards[on]) >= 0)
+ break;
+ } while (start != on);
+
+ if (g == -1) {
+ pprintf(p, "\nMajor Problem! Can't find your previous board."
+ "\n");
+ return -1;
+ }
+
+ return player_goto_board(p, on);
+}
+
+PUBLIC int
+player_goto_simulgame_bynum(int p, int num)
+{
+ int g;
+ int on;
+ int start;
+
+ on = parray[p].simul_info.onBoard;
+ start = on;
+
+ do {
+ on++;
+
+ if (on >= parray[p].simul_info.numBoards)
+ on = 0;
+
+ if ((g = parray[p].simul_info.boards[on]) == num)
+ break;
+ } while (start != on);
+
+ if (g != num) {
+ pprintf(p, "\nYou aren't playing that game!!\n");
+ return -1;
+ }
+
+ return player_goto_board(p, on);
+}
+
+PUBLIC int
+player_num_active_boards(int p)
+{
+ int count = 0;
+
+ if (!parray[p].simul_info.numBoards)
+ return 0;
+
+ for (int i = 0; i < parray[p].simul_info.numBoards; i++) {
+ if (parray[p].simul_info.boards[i] >= 0)
+ count++;
+ }
+
+ return count;
+}
+
+PUBLIC int
+player_num_results(int p, int result)
+{
+ int count = 0;
+
+ if (!parray[p].simul_info.numBoards)
+ return 0;
+
+ for (int i = 0; i < parray[p].simul_info.numBoards; i++) {
+ if (parray[p].simul_info.results[i] == result)
+ count++;
+ }
+
+ return count;
+}
+
+PUBLIC int
+player_simul_over(int p, int g, int result)
+{
+ char tmp[1024];
+ int on, ong, p1, which = -1, won;
+
+ for (won = 0; won < parray[p].simul_info.numBoards; won++) {
+ if (parray[p].simul_info.boards[won] == g) {
+ which = won;
+ break;
+ }
+ }
+
+ if (which == -1) {
+ pprintf(p, "I can't find that game!\n");
+ return -1;
+ }
+
+ pprintf(p, "\nBoard %d has completed.\n", (won + 1));
+
+ on = parray[p].simul_info.onBoard;
+ ong = parray[p].simul_info.boards[on];
+
+ parray[p].simul_info.boards[won] = -1;
+ parray[p].simul_info.results[won] = result;
+
+ if (player_num_active_boards(p) == 0) {
+ snprintf(tmp, sizeof tmp, "\n{Simul (%s vs. %d) is over.}\n"
+ "Results: %d Wins, %d Losses, %d Draws, %d Aborts\n",
+ parray[p].name,
+ parray[p].simul_info.numBoards,
+ player_num_results(p, RESULT_WIN),
+ player_num_results(p, RESULT_LOSS),
+ player_num_results(p, RESULT_DRAW),
+ player_num_results(p, RESULT_ABORT));
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (parray[p].status != PLAYER_PROMPT)
+ continue;
+ if (!parray[p1].i_game && !player_is_observe(p1, g) &&
+ p1 != p)
+ continue;
+ pprintf_prompt(p1, "%s", tmp);
+ }
+
+ parray[p].simul_info.numBoards = 0;
+
+ pprintf_prompt(p, "\nThat was the last board, thanks for "
+ "playing.\n");
+
+ return 0;
+ }
+
+ if (ong == g) /* This game is over */
+ player_goto_next_board(p);
+ else
+ player_goto_board(p, parray[p].simul_info.onBoard);
+
+ pprintf_prompt(p, "\nThere are %d boards left.\n",
+ player_num_active_boards(p));
+ return 0;
+}
+
+PRIVATE void
+GetMsgFile(int p, char *fName, const size_t size, const char *func)
+{
+ int ret, too_long;
+
+ ret = snprintf(fName, size, "%s/player_data/%c/%s.%s", stats_dir,
+ parray[p].login[0], parray[p].login, STATS_MESSAGES);
+ too_long = (ret < 0 || (size_t)ret >= size);
+
+ if (too_long) {
+ fprintf(stderr, "FICS: %s: warning: snprintf truncated\n",
+ func);
+ }
+}
+
+PUBLIC int
+player_num_messages(int p)
+{
+ char fname[MAX_FILENAME_SIZE];
+
+ if (!parray[p].registered)
+ return 0;
+
+ GetMsgFile(p, fname, sizeof fname, __func__);
+
+ return lines_file(fname);
+}
+
+PUBLIC int
+player_add_message(int top, int fromp, char *message)
+{
+ FILE *fp;
+ char fname[MAX_FILENAME_SIZE] = { '\0' };
+ char messbody[1024] = { '\0' };
+ char subj[256] = { '\0' };
+ int fd;
+ time_t t = time(NULL);
+
+ if (!parray[top].registered)
+ return -1;
+ if (!parray[fromp].registered)
+ return -1;
+
+ GetMsgFile(top, fname, sizeof fname, __func__);
+
+ if (lines_file(fname) >= MAX_MESSAGES && parray[top].adminLevel == 0)
+ return -1;
+
+ errno = 0;
+ fd = open(fname, g_open_flags[0], g_open_modes);
+
+ if (fd < 0)
+ return -1;
+ else if ((fp = fdopen(fd, "a")) == NULL) {
+ close(fd);
+ return -1;
+ }
+
+ fprintf(fp, "%s at %s: %s\n", parray[fromp].name, strltime(&t),
+ message);
+ fclose(fp);
+
+ pprintf(fromp, "\nThe following message was sent ");
+
+ if (parray[top].i_mailmess) {
+ snprintf(subj, sizeof subj, "FICS message from %s at FICS %s "
+ "(Do not reply by mail)", parray[fromp].name,
+ fics_hostname);
+ snprintf(messbody, sizeof messbody, "%s at %s: %s\n",
+ parray[fromp].name, strltime(&t), message);
+ mail_string_to_user(top, subj, messbody);
+ pprintf(fromp, "(and emailed) ");
+ }
+
+ pprintf(fromp, "to %s:\n %s\n", parray[top].name, message);
+ return 0;
+}
+
+PUBLIC void
+SaveTextListEntry(textlist **Entry, char *string, int n)
+{
+ *Entry = rmalloc(sizeof(textlist));
+
+ (*Entry)->text = xstrdup(string);
+ (*Entry)->index = n;
+ (*Entry)->next = NULL;
+}
+
+PUBLIC textlist *
+ClearTextListEntry(textlist *entry)
+{
+ textlist *ret = entry->next;
+
+ strfree(entry->text);
+ rfree(entry);
+ return ret;
+}
+
+PUBLIC void
+ClearTextList(textlist *head)
+{
+ for (textlist *cur = head; cur != NULL; cur = ClearTextListEntry(cur)) {
+ /* null */;
+ }
+}
+
+/*
+ * if which = 0 load all messages;
+ * if it's (p1 + 1) load messages only from p1;
+ * if it's -(p1 + 1) load all messages EXCEPT those from p1.
+ */
+PRIVATE int
+SaveThisMsg(int which, char *line)
+{
+ char Sender[MAX_LOGIN_NAME] = { '\0' };
+ int p1;
+
+ _Static_assert(19 < ARRAY_SIZE(Sender), "Array too small");
+
+ if (which == 0)
+ return 1;
+
+ if (sscanf(line, "%19s", Sender) != 1) {
+ warnx("%s: failed to read sender", __func__);
+ return 0;
+ }
+
+ if (which < 0) {
+ p1 = (-which) - 1;
+ return strcmp(Sender, parray[p1].name);
+ }
+
+ p1 = (which - 1);
+ return !strcmp(Sender, parray[p1].name);
+}
+
+/*
+ * which = 0 to load all messages;
+ * it's (p1 + 1) to load messages only from p1;
+ * and it's -(p1 + 1) to load all messages EXCEPT those from p1.
+ */
+PRIVATE int
+LoadMsgs(int p, int which, textlist **Head)
+{
+ FILE *fp;
+ char fName[MAX_FILENAME_SIZE];
+ char line[MAX_LINE_SIZE];
+ int n = 0, nSave = 0;
+ textlist** Cur = Head;
+
+ *Head = NULL;
+ GetMsgFile(p, fName, sizeof fName, __func__);
+
+ if ((fp = fopen(fName, "r")) == NULL)
+ return -1;
+
+ while (fgets(line, sizeof line, fp) != NULL) {
+ if (SaveThisMsg(which, line)) {
+ SaveTextListEntry(Cur, line, ++n);
+ Cur = &(*Cur)->next;
+ nSave++;
+ } else
+ n++;
+ }
+
+ fclose(fp);
+ return nSave;
+}
+
+/*
+ * start > 0 and end > start (or end = 0) to save messages in range;
+ * start < 0 and end < start (or end = 0) to clear messages in range;
+ * if end = 0, range goes to end of file (not tested yet).
+ */
+PRIVATE int
+LoadMsgRange(int p, int start, int end, textlist **Head)
+{
+ FILE *fp;
+ char fName[MAX_FILENAME_SIZE];
+ char line[MAX_LINE_SIZE];
+ int n = 1, nSave = 0, nKill = 0;
+ textlist** Cur = Head;
+
+ *Head = NULL;
+ GetMsgFile(p, fName, sizeof fName, __func__);
+
+ if ((fp = fopen(fName, "r")) == NULL) {
+ pprintf(p, "You have no messages.\n");
+ return -1;
+ }
+
+ for (n = 1; n <= end || end <= 0; n++) {
+ if (fgets(line, sizeof line, fp) == NULL)
+ break;
+ if ((start < 0 && (n < -start || n > -end)) ||
+ (start >= 0 && n >= start)) {
+ SaveTextListEntry(Cur, line, n);
+ Cur = &(*Cur)->next;
+ nSave++;
+ } else
+ nKill++;
+ }
+
+ fclose(fp);
+
+ if (start < 0) {
+ if (n <= -start)
+ pprintf(p, "You do not have a message %d.\n", -start);
+ return nKill;
+ } else {
+ if (n <= start)
+ pprintf(p, "You do not have a message %d.\n", start);
+ return nSave;
+ }
+}
+
+PRIVATE int
+WriteMsgFile(int p, textlist *Head)
+{
+ FILE *fp;
+ char fName[MAX_FILENAME_SIZE] = { '\0' };
+ int fd;
+ textlist *Cur;
+
+ GetMsgFile(p, fName, sizeof fName, __func__);
+
+ errno = 0;
+ fd = open(fName, g_open_flags[1], g_open_modes);
+
+ if (fd < 0)
+ return 0;
+ else if ((fp = fdopen(fd, "w")) == NULL) {
+ close(fd);
+ return 0;
+ }
+ for (Cur = Head; Cur != NULL; Cur = Cur->next)
+ fprintf(fp, "%s", Cur->text);
+ fclose(fp);
+ return 1;
+}
+
+PUBLIC int
+ClearMsgsBySender(int p, param_list param)
+{
+ int nFound;
+ int p1, connected;
+ textlist *Head;
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return -1;
+
+ nFound = LoadMsgs(p, -(p1 + 1), &Head);
+
+ if (nFound < 0) {
+ pprintf(p, "You have no messages.\n");
+ } else if (nFound == 0) {
+ pprintf(p, "You have no messages from %s.\n", parray[p1].name);
+ } else {
+ if (WriteMsgFile(p, Head)) {
+ pprintf(p, "Messages from %s cleared.\n",
+ parray[p1].name);
+ } else {
+ pprintf(p, "Problem writing message file; "
+ "please contact an admin.\n");
+ fprintf(stderr, "Problem writing message file for "
+ "%s.\n", parray[p].name);
+ }
+
+ ClearTextList(Head);
+ }
+
+ if (!connected)
+ player_remove(p1);
+ return nFound;
+}
+
+PRIVATE void
+ShowTextList(int p, textlist *Head, int ShowIndex)
+{
+ textlist *CurMsg;
+
+ if (ShowIndex) {
+ for (CurMsg = Head; CurMsg != NULL; CurMsg = CurMsg->next)
+ pprintf(p, "%2d. %s", CurMsg->index, CurMsg->text);
+ } else {
+ for (CurMsg = Head; CurMsg != NULL; CurMsg = CurMsg->next)
+ pprintf(p, "%s", CurMsg->text);
+ }
+}
+
+PUBLIC int
+player_show_messages(int p)
+{
+ int n;
+ textlist *Head;
+
+ n = LoadMsgs(p, 0, &Head);
+
+ if (n <= 0) {
+ pprintf(p, "You have no messages.\n");
+ return -1;
+ } else {
+ pprintf(p, "Messages:\n");
+ ShowTextList(p, Head, 1);
+ ClearTextList(Head);
+ return 0;
+ }
+}
+
+PUBLIC int
+ShowMsgsBySender(int p, param_list param)
+{
+ int nFrom = -1, nTo = -1;
+ int p1, connected;
+ textlist *Head;
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return -1;
+
+ if (!parray[p1].registered) {
+ pprintf(p, "Player \"%s\" is unregistered and cannot send or "
+ "receive messages.\n", parray[p1].name);
+ return -1; /* no need to disconnect */
+ }
+
+ if (p != p1) {
+ if ((nTo = LoadMsgs(p1, p + 1, &Head)) <= 0) {
+ pprintf(p, "%s has no messages from you.\n",
+ parray[p1].name);
+ } else {
+ pprintf(p, "Messages to %s:\n", parray[p1].name);
+ ShowTextList(p, Head, 0);
+ ClearTextList(Head);
+ }
+ }
+
+ if ((nFrom = LoadMsgs(p, p1 + 1, &Head)) <= 0) {
+ pprintf(p, "\nYou have no messages from %s.\n",
+ parray[p1].name);
+ } else {
+ pprintf(p, "Messages from %s:\n", parray[p1].name);
+ ShowTextList(p, Head, 1);
+ ClearTextList(Head);
+ }
+
+ if (!connected)
+ player_remove(p1);
+ return (nFrom > 0 || nTo > 0);
+}
+
+PUBLIC int
+ShowMsgRange(int p, int start, int end)
+{
+ int n;
+ textlist *Head;
+
+ if ((n = LoadMsgRange(p, start, end, &Head)) > 0) {
+ ShowTextList(p, Head, 1);
+ ClearTextList(Head);
+ }
+
+ return n;
+}
+
+PUBLIC int
+ClrMsgRange(int p, int start, int end)
+{
+ int n;
+ textlist *Head;
+
+ n = LoadMsgRange(p, -start, -end, &Head);
+
+ if (n > 0) {
+ if (WriteMsgFile(p, Head))
+ pprintf(p, "Message %d cleared.\n", start);
+ }
+
+ if (n >= 0)
+ ClearTextList(Head);
+ return n;
+}
+
+PUBLIC int
+player_clear_messages(int p)
+{
+ char fname[MAX_FILENAME_SIZE];
+
+ if (!parray[p].registered)
+ return -1;
+ GetMsgFile(p, fname, sizeof fname, __func__);
+ unlink(fname);
+ return 0;
+}
+
+/*
+ * Find player matching the given string.
+ *
+ * First looks for exact match with a logged in player, then an exact
+ * match with a registered player, then a partial unique match with a
+ * logged in player, then a partial match with a registered player.
+ *
+ * Returns player number if the player is connected. Negative (player
+ * number) if the player had to be connected and 0 if no player was
+ * found.
+ */
+PUBLIC int
+player_search(int p, char *name)
+{
+ char *buffer[1000];
+ char pdir[MAX_FILENAME_SIZE];
+ int p1, count;
+
+ // Exact match with connected player?
+ if ((p1 = player_find_bylogin(name)) >= 0) {
+ if (p1 + 1 >= (int)ARRAY_SIZE(parray))
+ return 0;
+ return (p1 + 1);
+ }
+
+ // Exact match with registered player?
+ snprintf(pdir, sizeof pdir, "%s/%c", player_dir, name[0]);
+ count = search_directory(pdir, name, buffer, ARRAY_SIZE(buffer));
+
+ if (count > 0 && !strcmp(name, *buffer))
+ goto ReadPlayerFromFile; // Found an unconnected
+ // registered player
+
+ // Partial match with connected player?
+ if ((p1 = player_find_part_login(name)) >= 0) {
+ return (p1 + 1);
+ } else if (p1 == -2) {
+ // Ambiguous. Matches too many connected players.
+ pprintf(p, "Ambiguous name '%s'; matches more than one player."
+ "\n", name);
+ return 0;
+ }
+
+ // Partial match with registered player?
+ if (count < 1) {
+ pprintf(p, "There is no player matching that name.\n");
+ return 0;
+ } else if (count > 1) {
+ pprintf(p, "-- Matches: %d names --", count);
+ display_directory(p, buffer, count);
+ return 0;
+ }
+
+ ReadPlayerFromFile:
+
+ p1 = player_new();
+
+ if (player_read(p1, *buffer)) {
+ player_remove(p1);
+ pprintf(p, "ERROR: a player named %s was expected but not "
+ "found!\n", *buffer);
+ pprintf(p, "Please tell an admin about this incident. "
+ "Thank you.\n");
+ return 0;
+ }
+
+ return (-p1) - 1; // Negative to indicate player was not connected
+}
+
+PUBLIC int
+player_kill(char *name)
+{
+ char fname[MAX_FILENAME_SIZE];
+ char fname2[MAX_FILENAME_SIZE];
+
+ snprintf(fname, sizeof fname, "%s/%c/%s", player_dir, name[0],
+ name);
+ snprintf(fname2, sizeof fname2, "%s/%c/.rem.%s", player_dir, name[0],
+ name);
+ xrename(__func__, fname, fname2);
+
+ RemHist(name);
+
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.games",
+ stats_dir, name[0], name);
+ snprintf(fname2, sizeof fname2, "%s/player_data/%c/.rem.%s.games",
+ stats_dir, name[0], name);
+ xrename(__func__, fname, fname2);
+
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.comments",
+ stats_dir, name[0], name);
+ snprintf(fname2, sizeof fname2, "%s/player_data/%c/.rem.%s.comments",
+ stats_dir, name[0], name);
+ xrename(__func__, fname, fname2);
+
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.logons",
+ stats_dir, name[0], name);
+ snprintf(fname2, sizeof fname2, "%s/player_data/%c/.rem.%s.logons",
+ stats_dir, name[0], name);
+ xrename(__func__, fname, fname2);
+
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.messages",
+ stats_dir, name[0], name);
+ snprintf(fname2, sizeof fname2, "%s/player_data/%c/.rem.%s.messages",
+ stats_dir, name[0], name);
+ xrename(__func__, fname, fname2);
+
+ return 0;
+}
+
+PUBLIC int
+player_rename(char *name, char *newname)
+{
+ char fname[MAX_FILENAME_SIZE];
+ char fname2[MAX_FILENAME_SIZE];
+
+ snprintf(fname, sizeof fname, "%s/%c/%s", player_dir, name[0],
+ name);
+ snprintf(fname2, sizeof fname2, "%s/%c/%s", player_dir, newname[0],
+ newname);
+ xrename(__func__, fname, fname2);
+
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.games",
+ stats_dir, name[0], name);
+ snprintf(fname2, sizeof fname2, "%s/player_data/%c/%s.games",
+ stats_dir, newname[0], newname);
+ xrename(__func__, fname, fname2);
+
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.comments",
+ stats_dir, name[0], name);
+ snprintf(fname2, sizeof fname2, "%s/player_data/%c/%s.comments",
+ stats_dir, newname[0], newname);
+ xrename(__func__, fname, fname2);
+
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.logons",
+ stats_dir, name[0], name);
+ snprintf(fname2, sizeof fname2, "%s/player_data/%c/%s.logons",
+ stats_dir, newname[0], newname);
+ xrename(__func__, fname, fname2);
+
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.messages",
+ stats_dir, name[0], name);
+ snprintf(fname2, sizeof fname2, "%s/player_data/%c/%s.messages",
+ stats_dir, newname[0], newname);
+ xrename(__func__, fname, fname2);
+
+ return 0;
+}
+
+PUBLIC int
+player_raise(char *name)
+{
+ char fname[MAX_FILENAME_SIZE];
+ char fname2[MAX_FILENAME_SIZE];
+
+ snprintf(fname, sizeof fname, "%s/%c/%s", player_dir,
+ name[0], name);
+ snprintf(fname2, sizeof fname2, "%s/%c/.rem.%s", player_dir,
+ name[0], name);
+ xrename(__func__, fname2, fname);
+
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.games",
+ stats_dir, name[0], name);
+ snprintf(fname2, sizeof fname2, "%s/player_data/%c/.rem.%s.games",
+ stats_dir, name[0], name);
+ xrename(__func__, fname2, fname);
+
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.comments",
+ stats_dir, name[0], name);
+ snprintf(fname2, sizeof fname2, "%s/player_data/%c/.rem.%s.comments",
+ stats_dir, name[0], name);
+ xrename(__func__, fname2, fname);
+
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.logons",
+ stats_dir, name[0], name);
+ snprintf(fname2, sizeof fname2, "%s/player_data/%c/.rem.%s.logons",
+ stats_dir, name[0], name);
+ xrename(__func__, fname2, fname);
+
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.messages",
+ stats_dir, name[0], name);
+ snprintf(fname2, sizeof fname2, "%s/player_data/%c/.rem.%s.messages",
+ stats_dir, name[0], name);
+ xrename(__func__, fname2, fname);
+
+ return 0;
+}
+
+PUBLIC int
+player_reincarn(char *name, char *newname)
+{
+ char fname[MAX_FILENAME_SIZE];
+ char fname2[MAX_FILENAME_SIZE];
+
+ snprintf(fname, sizeof fname, "%s/%c/%s", player_dir,
+ newname[0], newname);
+ snprintf(fname2, sizeof fname2, "%s/%c/.rem.%s", player_dir,
+ name[0], name);
+ xrename(__func__, fname2, fname);
+
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.games",
+ stats_dir, newname[0], newname);
+ snprintf(fname2, sizeof fname2, "%s/player_data/%c/.rem.%s.games",
+ stats_dir, name[0], name);
+ xrename(__func__, fname2, fname);
+
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.comments",
+ stats_dir, newname[0], newname);
+ snprintf(fname2, sizeof fname2, "%s/player_data/%c/.rem.%s.comments",
+ stats_dir, name[0], name);
+ xrename(__func__, fname2, fname);
+
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.logons",
+ stats_dir, newname[0], newname);
+ snprintf(fname2, sizeof fname2, "%s/player_data/%c/.rem.%s.logons",
+ stats_dir, name[0], name);
+ xrename(__func__, fname2, fname);
+
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.messages",
+ stats_dir, newname[0], newname);
+ snprintf(fname2, sizeof fname2, "%s/player_data/%c/.rem.%s.messages",
+ stats_dir, name[0], name);
+ xrename(__func__, fname2, fname);
+
+ return 0;
+}
+
+PUBLIC int
+player_num_comments(int p)
+{
+ char fname[MAX_FILENAME_SIZE];
+
+ if (!parray[p].registered)
+ return 0;
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s", stats_dir,
+ parray[p].login[0], parray[p].login, "comments");
+ return lines_file(fname);
+}
+
+PUBLIC int
+player_add_comment(int p_by, int p_to, char *comment)
+{
+ FILE *fp;
+ char fname[MAX_FILENAME_SIZE] = { '\0' };
+ int fd;
+ time_t t = time(NULL);
+
+ if (!parray[p_to].registered)
+ return -1;
+
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s", stats_dir,
+ parray[p_to].login[0], parray[p_to].login, "comments");
+
+ errno = 0;
+ fd = open(fname, g_open_flags[0], g_open_modes);
+
+ if (fd < 0) {
+ warn("%s: open", __func__);
+ return -1;
+ } else if ((fp = fdopen(fd, "a")) == NULL) {
+ warn("%s: fdopen", __func__);
+ close(fd);
+ return -1;
+ }
+
+ fprintf(fp, "%s at %s: %s\n", parray[p_by].name, strltime(&t), comment);
+ fclose(fp);
+ parray[p_to].num_comments = player_num_comments(p_to);
+ return 0;
+}
+
+PUBLIC int
+player_show_comments(int p, int p1)
+{
+ char fname[MAX_FILENAME_SIZE] = { '\0' };
+
+ snprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s", stats_dir,
+ parray[p1].login[0], parray[p1].login, "comments");
+
+ if (psend_file(p, NULL, fname) == -1)
+ warnx("%s: psend_file() error", __func__);
+ return 0;
+}
+
+/*
+ * Returns 1 if player is head admin and 0 otherwise.
+ */
+PUBLIC int
+player_ishead(int p)
+{
+ return (!strcasecmp(parray[p].name, hadmin_handle));
+}
diff --git a/FICS/playerdb.h b/FICS/playerdb.h
new file mode 100644
index 0000000..1ccae61
--- /dev/null
+++ b/FICS/playerdb.h
@@ -0,0 +1,281 @@
+/* playerdb.h
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 23/12/13 Cleaned up the file
+*/
+
+#include "command.h"
+#include "lists.h"
+
+#ifndef _PLAYERDB_H
+#define _PLAYERDB_H
+
+#include <stdbool.h>
+
+#define PLAYER_VERSION 1
+
+#define MAX_ALIASES 30
+#define MAX_CENSOR 50
+#define MAX_FORMULA 9
+#define MAX_INCHANNELS 16
+#define MAX_MESSAGES 40
+#define MAX_NOTIFY 80
+#define MAX_OBSERVE 30
+#define MAX_PENDING 10
+#define MAX_PLAN 10
+#define MAX_PLAYER 500
+#define MAX_SIMUL 30
+
+#define PLAYER_EMPTY 0
+#define PLAYER_NEW 1
+#define PLAYER_INQUEUE 2
+#define PLAYER_LOGIN 3
+#define PLAYER_PASSWORD 4
+#define PLAYER_PROMPT 5
+
+#define P_LOGIN 0
+#define P_LOGOUT 1
+
+#define SORT_BLITZ 0
+#define SORT_STAND 1
+#define SORT_ALPHA 2
+#define SORT_WILD 3
+
+typedef struct _statistics {
+ double sterr;
+ int num, win, los, dra, rating, best;
+ time_t ltime;
+ time_t whenbest;
+} statistics;
+
+#define PEND_MATCH 0
+#define PEND_DRAW 1
+#define PEND_ABORT 2
+#define PEND_TAKEBACK 3
+#define PEND_ADJOURN 4
+#define PEND_SWITCH 5
+#define PEND_SIMUL 6
+#define PEND_PAUSE 7
+#define PEND_PARTNER 8
+#define PEND_BUGHOUSE 9
+#define PEND_ALL -1
+
+#define PEND_TO 0
+#define PEND_FROM 1
+
+typedef struct _pending {
+ int type;
+ int whoto;
+ int whofrom;
+ int param1, param2, param3, param4, param5, param6;
+ char char1[50];
+ char char2[50];
+} pending;
+
+typedef struct _simul_info_t {
+ int numBoards;
+ int onBoard;
+ int results[MAX_SIMUL];
+ int boards[MAX_SIMUL];
+} simul_info_t;
+
+typedef struct _player {
+ /*
+ * This first block is not saved between logins
+ */
+ List *lists;
+ char busy[100];
+ char interface[45]; // For example: 'xboard 4.9.1'
+ char *identptr;
+ char *last_file;
+ char *login;
+ int flip;
+ int game;
+ int i_admin;
+ int kiblevel;
+ int lastColor;
+ int last_channel;
+ time_t last_command_time;
+ int last_opponent;
+ int last_tell;
+ time_t lastshout_a;
+ time_t lastshout_b;
+ time_t logon_time;
+ int num_comments; // number of lines in comments file
+ int num_from;
+ int num_observe;
+ int num_to;
+ int observe_list[MAX_OBSERVE];
+ int opponent; // Only valid if game is >= 0
+ int side; // Only valid if game is >= 0
+ int partner;
+ int registered;
+ int seek; // new
+ int socket;
+ int sopen;
+ int status;
+ time_t timeOfReg;
+ time_t totalTime;
+ long last_file_byte;
+ pending p_from_list[MAX_PENDING];
+ pending p_to_list[MAX_PENDING];
+ simul_info_t simul_info;
+ unsigned int thisHost;
+
+ /*
+ * All of this is saved between logins
+ */
+ char *name;
+ char *emailAddress;
+ char *fullName;
+ char *passwd;
+ char *prompt;
+ statistics b_stats;
+ statistics l_stats;
+ statistics s_stats;
+ statistics w_stats;
+ statistics bug_stats;
+ alias_type alias_list[MAX_ALIASES];
+ char *formula;
+ char *formulaLines[MAX_FORMULA];
+ char *planLines[MAX_PLAN];
+ int adminLevel;
+ int automail;
+ int bell;
+ int d_height;
+ int d_inc;
+ int d_time;
+ int d_width;
+ int highlight;
+ int i_cshout;
+ int i_game;
+ int i_kibitz;
+ int i_login;
+ int i_mailmess;
+ int i_shout;
+ int i_tell;
+ int jprivate;
+ int language;
+ int nochannels;
+ int notifiedby;
+ int numAlias;
+ int num_black;
+ int num_formula;
+ int num_plan;
+ int num_white;
+ int open;
+ int pgn;
+ int private;
+ int promote;
+ int rated;
+ int ropen;
+ int style;
+ unsigned int lastHost;
+} player;
+
+typedef struct _textlist {
+ char *text;
+ int index;
+ struct _textlist *next;
+} textlist;
+
+#define PARRAY_SIZE (MAX_PLAYER + 50)
+
+extern player parray[PARRAY_SIZE];
+extern int p_num;
+
+extern bool player_num_ok_chk(const int);
+extern void xrename(const char *, const char *, const char *);
+
+extern int ClearMsgsBySender(int, param_list);
+extern int ClrMsgRange(int, int, int);
+extern int ShowMsgRange(int, int, int);
+extern int ShowMsgsBySender(int, param_list);
+extern int showstored(int);
+extern textlist *ClearTextListEntry(textlist *);
+extern void ClearTextList(textlist *);
+extern void SaveTextListEntry(textlist **, char *, int);
+
+extern int player_add_comment(int, int, char *);
+extern int player_add_message(int, int, char *);
+extern int player_add_observe(int, int);
+extern int player_add_request(int, int, int, int);
+extern int player_censored(int, int);
+extern int player_clear(int);
+extern int player_clear_messages(int);
+extern int player_count(int);
+extern int player_decline_offers(int, int, int);
+extern int player_delete(int);
+extern int player_find(int);
+extern int player_find_bylogin(char *);
+extern int player_find_part_login(char *);
+extern int player_find_pendfrom(int, int, int);
+extern int player_find_pendto(int, int, int);
+extern int player_free(int);
+extern int player_game_ended(int);
+extern int player_goto_board(int, int);
+extern int player_goto_next_board(int);
+extern int player_goto_prev_board(int);
+extern int player_goto_simulgame_bynum(int, int);
+extern int player_idle(int);
+extern int player_is_observe(int, int);
+extern int player_ishead(int);
+extern int player_kill(char *);
+extern int player_markdeleted(int);
+extern int player_new(void);
+extern int player_new_pendfrom(int);
+extern int player_new_pendto(int);
+extern int player_notified(int, int);
+extern int player_notified_departure(int);
+extern int player_notify(int, char *, char *);
+extern int player_notify_present (int);
+extern int player_num_active_boards(int);
+extern int player_num_comments(int);
+extern int player_num_messages(int);
+extern int player_num_results(int, int);
+extern int player_ontime(int);
+extern int player_raise(char *);
+extern int player_read(int, char *);
+extern int player_reincarn(char *, char *);
+extern int player_remove(int);
+extern int player_remove_observe(int, int);
+extern int player_remove_pendfrom(int, int, int);
+extern int player_remove_pendto(int, int, int);
+extern int player_remove_request(int, int, int);
+extern int player_rename(char *, char *);
+extern int player_save(int);
+extern int player_search(int, char *);
+extern int player_show_comments(int, int);
+extern int player_show_messages(int);
+extern int player_simul_over(int, int, int);
+extern int player_withdraw_offers(int, int, int);
+extern int player_zero(int);
+extern time_t player_lastconnect(int);
+extern time_t player_lastdisconnect(int);
+extern void player_array_init(void);
+extern void player_init(int);
+extern void player_notify_departure(int);
+extern void player_pend_print(int, pending *);
+extern void player_write_login(int);
+extern void player_write_logout(int);
+
+#endif /* _PLAYERDB_H */
diff --git a/FICS/rating_conv.c b/FICS/rating_conv.c
new file mode 100644
index 0000000..3f5a98e
--- /dev/null
+++ b/FICS/rating_conv.c
@@ -0,0 +1,86 @@
+/* Ratings conversions by DAV */
+/* GNU licensing applies */
+
+#include "stdinclude.h"
+
+#include "command.h"
+#include "common.h"
+#include "rating_conv.h"
+#include "utils.h"
+
+PRIVATE int
+elo_to_uscf(int elo)
+{
+ return (elo + 100);
+}
+
+PRIVATE int
+uscf_to_elo(int uscf)
+{
+ return (uscf - 100);
+}
+
+PRIVATE int
+bcf_to_elo(int bcf)
+{
+ return (bcf * 8 + 600);
+}
+
+PRIVATE int
+elo_to_bcf(int elo)
+{
+ return (elo - 600) / 8;
+}
+
+#if 0
+PRIVATE int
+uscf_to_bcf(int uscf)
+{
+ return (uscf - 700) / 8;
+}
+#endif
+
+#if 0
+PRIVATE int
+bcf_to_uscf(int bcf)
+{
+ return (bcf * 8 + 700);
+}
+#endif
+
+PRIVATE void
+printgrades(int p, int elo, int uscf, int bcf)
+{
+ pprintf(p, "Grading conversion:\n");
+ pprintf(p, " ELO = %d, USCF = %d, BCF = %d\n", elo, uscf, bcf);
+}
+
+PUBLIC int
+com_CONVERT_BCF(int p, param_list param)
+{
+ int elo = bcf_to_elo(param[0].val.integer);
+ int uscf = elo_to_uscf(elo);
+
+ printgrades(p, elo, uscf, param[0].val.integer);
+ return COM_OK;
+}
+
+PUBLIC int
+com_CONVERT_ELO(int p, param_list param)
+{
+ int bcf = elo_to_bcf(param[0].val.integer);
+ int uscf = elo_to_uscf(param[0].val.integer);
+
+ printgrades(p, param[0].val.integer, uscf, bcf);
+ return COM_OK;
+}
+
+PUBLIC int
+com_CONVERT_USCF(int p, param_list param)
+{
+ int elo = uscf_to_elo(param[0].val.integer);
+ int bcf = elo_to_bcf(elo);
+
+ printgrades(p, elo, param[0].val.integer, bcf);
+ return COM_OK;
+}
diff --git a/FICS/rating_conv.h b/FICS/rating_conv.h
new file mode 100644
index 0000000..682d326
--- /dev/null
+++ b/FICS/rating_conv.h
@@ -0,0 +1,10 @@
+#ifndef _RATINGCONV_H
+#define _RATINGCONV_H
+
+#include "command.h" /* param_list */
+
+extern int com_CONVERT_BCF(int, param_list);
+extern int com_CONVERT_ELO(int, param_list);
+extern int com_CONVERT_USCF(int, param_list);
+
+#endif /* _RATINGCONV_H */
diff --git a/FICS/ratings.c b/FICS/ratings.c
new file mode 100644
index 0000000..a85776d
--- /dev/null
+++ b/FICS/ratings.c
@@ -0,0 +1,1964 @@
+/*
+ ratings.c
+
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ vek leeds@math.gatech.edu 95/04/05 Glicko system, with sterr
+ Markus Uhlin 23/12/17 Fixed the includes
+ Markus Uhlin 23/12/17 Usage of 'time_t'
+ Markus Uhlin 24/04/05 Reformatted ALL functions and
+ reenabled disabled code.
+ Markus Uhlin 24/04/05 Replaced unbounded string
+ handling functions.
+ Markus Uhlin 24/05/20 Fixed clang warnings
+ Markus Uhlin 24/07/07 Return value checking of the
+ fscanf() calls.
+ Markus Uhlin 24/11/27 Added sscanf() width spec and
+ fixed ignored retvals.
+ Markus Uhlin 24/11/28 Added null checks
+ Markus Uhlin 25/03/16 Fixed use of 32-bit 'time_t'.
+ Markus Uhlin 25/04/06 Fixed Clang Tidy warnings.
+ Markus Uhlin 25/07/28 Fixed missing return-value check
+ for a 'scanf'-like function.
+ Markus Uhlin 25/07/28 Restricted file permissions upon
+ creation.
+*/
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdint.h>
+
+#include "command.h"
+#include "comproc.h"
+#include "config.h"
+#include "ficsmain.h"
+#include "gamedb.h"
+#include "lists.h"
+#include "playerdb.h"
+#include "ratings.h"
+#include "utils.h"
+
+#if __linux__
+#include <bsd/string.h>
+#endif
+
+// Constants for Glicko system
+#define Gd 3.25
+#define Gr0 1720
+#define Gs0 350
+#define Gq 0.00575646273249
+#define Gp 0.000010072398601964
+// End of Glicko system variables
+
+#define LOWESTHIST 800
+#define MAXHIST 30
+
+PRIVATE double Ratings_B_Average;
+PRIVATE double Ratings_B_StdDev;
+PRIVATE double Ratings_S_Average;
+PRIVATE double Ratings_S_StdDev;
+PRIVATE double Ratings_L_Average;
+PRIVATE double Ratings_L_StdDev;
+PRIVATE double Ratings_W_Average;
+PRIVATE double Ratings_W_StdDev;
+
+PRIVATE double Rb_M = 0.0,
+ Rb_S = 0.0,
+ Rb_total = 0.0;
+PRIVATE int Rb_count = 0;
+
+PRIVATE double Rs_M = 0.0,
+ Rs_S = 0.0,
+ Rs_total = 0.0;
+PRIVATE int Rs_count = 0;
+
+PRIVATE double Rl_M = 0.0,
+ Rl_S = 0.0,
+ Rl_total = 0.0;
+PRIVATE int Rl_count = 0;
+
+PRIVATE double Rw_M = 0.0,
+ Rw_S = 0.0,
+ Rw_total = 0.0;
+PRIVATE int Rw_count = 0;
+
+PRIVATE rateStruct bestS[MAX_BEST];
+PRIVATE int numS = 0;
+PRIVATE rateStruct bestB[MAX_BEST];
+PRIVATE int numB = 0;
+PRIVATE rateStruct bestW[MAX_BEST];
+PRIVATE int numW = 0;
+
+PRIVATE int sHist[MAXHIST] = { 0 };
+PRIVATE int bHist[MAXHIST] = { 0 };
+PRIVATE int wHist[MAXHIST] = { 0 };
+PRIVATE int lHist[MAXHIST] = { 0 };
+
+PRIVATE char sdir[] = DEFAULT_STATS;
+
+PUBLIC int
+is_active(int Games)
+{
+ return (Games >= PROVISIONAL);
+}
+
+PUBLIC void
+rating_add(int rating, int type)
+{
+ int which;
+
+ if ((which = (rating - LOWESTHIST) / 100) < 0)
+ which = 0;
+
+ if (which >= MAXHIST)
+ which = MAXHIST - 1;
+
+ if (type == TYPE_BLITZ) {
+ bHist[which] += 1;
+
+ Rb_count++;
+ Rb_total += rating;
+
+ if (Rb_count == 1) {
+ Rb_M = rating;
+ } else {
+ Rb_S = Rb_S + (rating - Rb_M) * (rating - Rb_M);
+ Rb_M = Rb_M + (rating - Rb_M) / (Rb_count);
+ }
+
+ Ratings_B_StdDev = sqrt(Rb_S / Rb_count);
+ Ratings_B_Average = (Rb_total / (double)Rb_count);
+ } else if (type == TYPE_WILD) { // TYPE_WILD
+ wHist[which] += 1;
+
+ Rw_count++;
+ Rw_total += rating;
+
+ if (Rw_count == 1) {
+ Rw_M = rating;
+ } else {
+ Rw_S = Rw_S + (rating - Rw_M) * (rating - Rw_M);
+ Rw_M = Rw_M + (rating - Rw_M) / (Rw_count);
+ }
+
+ Ratings_W_StdDev = sqrt(Rw_S / Rw_count);
+ Ratings_W_Average = (Rw_total / (double)Rw_count);
+ } else if (type == TYPE_LIGHT) { // TYPE_LIGHT
+ lHist[which] += 1;
+
+ Rl_count++;
+ Rl_total += rating;
+
+ if (Rl_count == 1) {
+ Rl_M = rating;
+ } else {
+ Rl_S = Rl_S + (rating - Rl_M) * (rating - Rl_M);
+ Rl_M = Rl_M + (rating - Rl_M) / (Rl_count);
+ }
+
+ Ratings_L_StdDev = sqrt(Rl_S / Rl_count);
+ Ratings_L_Average = (Rl_total / (double)Rl_count);
+
+ // Insert bughouse stuff
+ } else { // TYPE_STAND
+ sHist[which] += 1;
+
+ Rs_count++;
+ Rs_total += rating;
+
+ if (Rs_count == 1) {
+ Rs_M = rating;
+ } else {
+ Rs_S = Rs_S + (rating - Rs_M) * (rating - Rs_M);
+ Rs_M = Rs_M + (rating - Rs_M) / (Rs_count);
+ }
+
+ Ratings_S_StdDev = sqrt(Rs_S / Rs_count);
+ Ratings_S_Average = (Rs_total / (double)Rs_count);
+ }
+}
+
+PUBLIC void
+rating_remove(int rating, int type)
+{
+ int which;
+
+ if ((which = (rating - LOWESTHIST) / 100) < 0)
+ which = 0;
+
+ if (which >= MAXHIST)
+ which = MAXHIST - 1;
+
+ if (type == TYPE_BLITZ) {
+ bHist[which] = bHist[which] - 1;
+
+ if (bHist[which] < 0)
+ bHist[which] = 0;
+ if (Rb_count == 0)
+ return;
+
+ Rb_count--;
+ Rb_total -= rating;
+
+ if (Rb_count == 0) {
+ Rb_M = 0;
+ Rb_S = 0;
+ } else {
+ Rb_M = Rb_M - (rating - Rb_M) / (Rb_count);
+ Rb_S = Rb_S - (rating - Rb_M) * (rating - Rb_M);
+
+ if (Rb_S < 0)
+ Rb_S = 0;
+ }
+
+ if (Rb_count) {
+ Ratings_B_StdDev = sqrt(Rb_S / Rb_count);
+ Ratings_B_Average = (Rb_total / (double)Rb_count);
+ } else {
+ Ratings_B_StdDev = 0;
+ Ratings_B_Average = 0;
+ }
+ } else if (type == TYPE_WILD) { // TYPE_WILD
+ wHist[which] = wHist[which] - 1;
+
+ if (wHist[which] < 0)
+ wHist[which] = 0;
+ if (Rw_count == 0)
+ return;
+
+ Rw_count--;
+ Rw_total -= rating;
+
+ if (Rw_count == 0) {
+ Rw_M = 0;
+ Rw_S = 0;
+ } else {
+ Rw_M = Rw_M - (rating - Rw_M) / (Rw_count);
+ Rw_S = Rw_S - (rating - Rw_M) * (rating - Rw_M);
+
+ if (Rw_S < 0)
+ Rw_S = 0;
+ }
+
+ if (Rw_count) {
+ Ratings_W_StdDev = sqrt(Rw_S / Rw_count);
+ Ratings_W_Average = (Rw_total / (double)Rw_count);
+ } else {
+ Ratings_W_StdDev = 0;
+ Ratings_W_Average = 0;
+ }
+ } else if (type == TYPE_LIGHT) { // TYPE_LIGHT
+ lHist[which] = lHist[which] - 1;
+
+ if (lHist[which] < 0)
+ lHist[which] = 0;
+ if (Rl_count == 0)
+ return;
+
+ Rl_count--;
+ Rl_total -= rating;
+
+ if (Rl_count == 0) {
+ Rl_M = 0;
+ Rl_S = 0;
+ } else {
+ Rl_M = Rl_M - (rating - Rl_M) / (Rl_count);
+ Rl_S = Rl_S - (rating - Rl_M) * (rating - Rl_M);
+
+ if (Rl_S < 0)
+ Rl_S = 0;
+ }
+
+ if (Rl_count) {
+ Ratings_L_StdDev = sqrt(Rl_S / Rl_count);
+ Ratings_L_Average = (Rl_total / (double)Rl_count);
+ } else {
+ Ratings_L_StdDev = 0;
+ Ratings_L_Average = 0;
+ }
+
+ // insert bughouse stuff here
+ } else { // TYPE_STAND
+ sHist[which] = sHist[which] - 1;
+
+ if (sHist[which] < 0)
+ sHist[which] = 0;
+ if (Rs_count == 0)
+ return;
+
+ Rs_count--;
+ Rs_total -= rating;
+
+ if (Rs_count == 0) {
+ Rs_M = 0;
+ Rs_S = 0;
+ } else {
+ Rs_M = Rs_M - (rating - Rs_M) / (Rs_count);
+ Rs_S = Rs_S - (rating - Rs_M) * (rating - Rs_M);
+
+ if (Rs_S < 0)
+ Rs_S = 0;
+ }
+
+ if (Rs_count) {
+ Ratings_S_StdDev = sqrt(Rs_S / Rs_count);
+ Ratings_S_Average = (Rs_total / (double)Rs_count);
+ } else {
+ Ratings_S_StdDev = 0;
+ Ratings_S_Average = 0;
+ }
+ }
+}
+
+PRIVATE void
+load_ratings(void)
+{
+ FILE *fp;
+ char fname[MAX_FILENAME_SIZE] = { '\0' };
+
+ snprintf(fname, sizeof fname, "%s/newratingsV%d_data", stats_dir,
+ STATS_VERSION);
+
+ if ((fp = fopen(fname, "r")) == NULL) {
+ warn("%s: can't read ratings data", __func__);
+ return;
+ } else if (fscanf(fp, "%lf %lf %lf %d", &Rb_M, &Rb_S, &Rb_total,
+ &Rb_count) != 4 ||
+ fscanf(fp, "%lf %lf %lf %d", &Rs_M, &Rs_S, &Rs_total,
+ &Rs_count) != 4 ||
+ fscanf(fp, "%lf %lf %lf %d", &Rw_M, &Rw_S, &Rw_total,
+ &Rw_count) != 4 ||
+ fscanf(fp, "%lf %lf %lf %d", &Rl_M, &Rl_S, &Rl_total,
+ &Rl_count) != 4) {
+ warn("%s: fscanf", __func__);
+ fclose(fp);
+ return;
+ }
+
+ for (int i = 0; i < MAXHIST && !feof(fp) && !ferror(fp); i++) {
+ int ret;
+
+ sHist[i] = bHist[i] = wHist[i] = lHist[i] = 0;
+
+ ret = fscanf(fp, "%d %d %d %d", &sHist[i], &bHist[i], &wHist[i],
+ &lHist[i]);
+ if (ret != 4) {
+ warnx("%s: %s: too few items assigned (iteration: %d)",
+ __func__, fname, i);
+ fclose(fp);
+ return;
+ }
+ }
+
+ if (ferror(fp)) {
+ warnx("%s: %s: the error indicator is set", __func__, fname);
+ fclose(fp);
+ return;
+ }
+
+ fclose(fp);
+
+ if (Rs_count != 0) {
+ Ratings_S_StdDev = sqrt(Rs_S / Rs_count); // NOLINT
+ Ratings_S_Average = (Rs_total / (double)Rs_count);
+ } else {
+ Ratings_S_StdDev = 0;
+ Ratings_S_Average = 0;
+ }
+ if (Rb_count != 0) {
+ Ratings_B_StdDev = sqrt(Rb_S / Rb_count); // NOLINT
+ Ratings_B_Average = (Rb_total / (double)Rb_count);
+ } else {
+ Ratings_B_StdDev = 0;
+ Ratings_B_Average = 0;
+ }
+ if (Rw_count != 0) {
+ Ratings_W_StdDev = sqrt(Rw_S / Rw_count); // NOLINT
+ Ratings_W_Average = (Rw_total / (double)Rw_count);
+ } else {
+ Ratings_W_StdDev = 0;
+ Ratings_W_Average = 0;
+ }
+ if (Rl_count != 0) {
+ Ratings_L_StdDev = sqrt(Rl_S / Rl_count); // NOLINT
+ Ratings_L_Average = (Rl_total / (double)Rl_count);
+ } else {
+ Ratings_L_StdDev = 0;
+ Ratings_L_Average = 0;
+ }
+}
+
+PUBLIC void
+save_ratings(void)
+{
+ FILE *fp;
+ char fname[MAX_FILENAME_SIZE] = { '\0' };
+ int fd;
+
+ snprintf(fname, sizeof fname, "%s/newratingsV%d_data", stats_dir,
+ STATS_VERSION);
+
+ errno = 0;
+ fd = open(fname, g_open_flags[1], g_open_modes);
+
+ if (fd < 0) {
+ warn("%s: can't write ratings data", __func__);
+ return;
+ } else if ((fp = fdopen(fd, "w")) == NULL) {
+ warn("%s: can't write ratings data", __func__);
+ close(fd);
+ return;
+ }
+
+ fprintf(fp, "%10f %10f %10f %d\n", Rb_M, Rb_S, Rb_total, Rb_count);
+ fprintf(fp, "%10f %10f %10f %d\n", Rs_M, Rs_S, Rs_total, Rs_count);
+ fprintf(fp, "%10f %10f %10f %d\n", Rw_M, Rw_S, Rw_total, Rw_count);
+ fprintf(fp, "%10f %10f %10f %d\n", Rl_M, Rl_S, Rl_total, Rl_count);
+
+ for (int i = 0; i < MAXHIST; i++) {
+ fprintf(fp, "%d %d %d %d\n", sHist[i], bHist[i], wHist[i],
+ lHist[i]);
+ }
+
+ fclose(fp);
+}
+
+PRIVATE void
+BestRemove(int p)
+{
+ int i;
+
+ for (i = 0; i < numB; i++) {
+ if (!strcmp(bestB[i].name, parray[p].name))
+ break;
+ }
+ if (i < numB) {
+ for (; i < numB - 1; i++) {
+ strlcpy(bestB[i].name, bestB[i + 1].name,
+ sizeof bestB[i].name);
+ bestB[i].rating = bestB[i + 1].rating;
+ }
+ numB--;
+ }
+
+ for (i = 0; i < numS; i++) {
+ if (!strcmp(bestS[i].name, parray[p].name))
+ break;
+ }
+ if (i < numS) {
+ for (; i < numS - 1; i++) {
+ strlcpy(bestS[i].name, bestS[i + 1].name,
+ sizeof bestS[i].name);
+ bestS[i].rating = bestS[i + 1].rating;
+ }
+ numS--;
+ }
+
+ for (i = 0; i < numW; i++) {
+ if (!strcmp(bestW[i].name, parray[p].name))
+ break;
+ }
+ if (i < numW) {
+ for (; i < numW - 1; i++) {
+ strlcpy(bestW[i].name, bestW[i + 1].name,
+ sizeof bestW[i].name);
+ bestW[i].rating = bestW[i + 1].rating;
+ }
+ numW--;
+ }
+}
+
+PRIVATE void
+BestAdd(int p)
+{
+ int where, j;
+
+ if (parray[p].b_stats.rating > 0 &&
+ parray[p].b_stats.num > 19) {
+ for (where = 0; where < numB; where++) {
+ if (parray[p].b_stats.rating > bestB[where].rating)
+ break;
+ }
+
+ if (where < MAX_BEST) {
+ for (j = numB; j > where; j--) {
+ if (j == MAX_BEST)
+ continue;
+ strlcpy(bestB[j].name, bestB[j - 1].name,
+ sizeof(bestB[j].name));
+ bestB[j].rating = bestB[j - 1].rating;
+ }
+
+ strlcpy(bestB[where].name, parray[p].name,
+ sizeof(bestB[where].name));
+ bestB[where].rating = parray[p].b_stats.rating;
+
+ if (numB < MAX_BEST)
+ numB++;
+ }
+ }
+
+ if (parray[p].s_stats.rating > 0 &&
+ parray[p].s_stats.num > 19) {
+ for (where = 0; where < numS; where++) {
+ if (parray[p].s_stats.rating > bestS[where].rating)
+ break;
+ }
+
+ if (where < MAX_BEST) {
+ for (j = numS; j > where; j--) {
+ if (j == MAX_BEST)
+ continue;
+ strlcpy(bestS[j].name, bestS[j - 1].name,
+ sizeof(bestS[j].name));
+ bestS[j].rating = bestS[j - 1].rating;
+ }
+
+ strlcpy(bestS[where].name, parray[p].name,
+ sizeof(bestS[where].name));
+ bestS[where].rating = parray[p].s_stats.rating;
+
+ if (numS < MAX_BEST)
+ numS++;
+ }
+ }
+
+ if (parray[p].w_stats.rating > 0 &&
+ parray[p].w_stats.num > 19) {
+ for (where = 0; where < numW; where++) {
+ if (parray[p].w_stats.rating > bestW[where].rating)
+ break;
+ }
+
+ if (where < MAX_BEST) {
+ for (j = numW; j > where; j--) {
+ if (j == MAX_BEST)
+ continue;
+ strlcpy(bestW[j].name, bestW[j - 1].name,
+ sizeof(bestW[j].name));
+ bestW[j].rating = bestW[j - 1].rating;
+ }
+
+ strlcpy(bestW[where].name, parray[p].name,
+ sizeof(bestW[where].name));
+ bestW[where].rating = parray[p].w_stats.rating;
+
+ if (numW < MAX_BEST)
+ numW++;
+ }
+ }
+}
+
+PUBLIC void
+BestUpdate(int p)
+{
+ BestRemove(p);
+ BestAdd(p);
+}
+
+PRIVATE void
+zero_stats(void)
+{
+ for (int i = 0; i < MAXHIST; i++) {
+ sHist[i] = 0;
+ bHist[i] = 0;
+ wHist[i] = 0;
+ lHist[i] = 0;
+ }
+
+ Rb_M = 0.0, Rb_S = 0.0, Rb_total = 0.0;
+ Rb_count = 0;
+
+ Rs_M = 0.0, Rs_S = 0.0, Rs_total = 0.0;
+ Rs_count = 0;
+
+ Rw_M = 0.0, Rw_S = 0.0, Rw_total = 0.0;
+ Rw_count = 0;
+
+ Rl_M = 0.0, Rl_S = 0.0, Rl_total = 0.0;
+ Rl_count = 0;
+
+ numS = 0;
+ numB = 0;
+ numW = 0;
+}
+
+#if 0
+PUBLIC int
+com_best(int p, param_list param)
+{
+ pprintf(p, "Standard Blitz Wild\n");
+
+ for (int i = 0; i < MAX_BEST; i++) {
+ if (i >= numS && i >= numB)
+ break;
+
+ if (i < numS) {
+ pprintf(p, "%4d %-17s ",
+ bestS[i].rating,
+ bestS[i].name);
+ } else {
+ pprintf(p, " ");
+ }
+
+ if (i < numB) {
+ pprintf(p, "%4d %-17s ",
+ bestB[i].rating,
+ bestB[i].name);
+ } else {
+ pprintf(p, " ");
+ }
+
+ if (i < numW) {
+ pprintf(p, "%4d %-17s\n",
+ bestW[i].rating,
+ bestW[i].name);
+ } else {
+ pprintf(p, "\n");
+ }
+ }
+
+ return COM_OK;
+}
+#endif
+
+PUBLIC void
+rating_init(void)
+{
+ zero_stats();
+ load_ratings();
+}
+
+/*
+ * This recalculates the rating info from the player data. (Which can
+ * take a long time!)
+ */
+PUBLIC void
+rating_recalc(void)
+{
+ DIR *dirp;
+ char dname[MAX_FILENAME_SIZE];
+ int c;
+ int p1;
+#if USE_DIRENT
+ struct dirent *dp;
+#else
+ struct direct *dp;
+#endif
+ time_t t = time(NULL);
+
+ fprintf(stderr, "FICS: Recalculating ratings at %s\n", strltime(&t));
+ zero_stats();
+
+ for (c = 'a'; c <= 'z'; c++) {
+ snprintf(dname, sizeof dname, "%s/%c", player_dir, c);
+
+ if ((dirp = opendir(dname)) == NULL)
+ continue;
+
+ for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
+ if (dp->d_name[0] != '.') {
+ p1 = player_new();
+
+ if (player_read(p1, dp->d_name)) {
+ player_remove(p1);
+
+ fprintf(stderr, "FICS: Problem reading "
+ "player %s.\n", dp->d_name);
+
+ continue;
+ }
+
+ if (parray[p1].b_stats.rating > 0) {
+ rating_add(parray[p1].b_stats.rating,
+ TYPE_BLITZ);
+ }
+ if (parray[p1].s_stats.rating > 0) {
+ rating_add(parray[p1].s_stats.rating,
+ TYPE_STAND);
+ }
+ if (parray[p1].l_stats.rating > 0) {
+ rating_add(parray[p1].l_stats.rating,
+ TYPE_LIGHT);
+ }
+
+ // insert bughouse stuff here
+
+ if (parray[p1].w_stats.rating > 0) {
+ rating_add(parray[p1].w_stats.rating,
+ TYPE_WILD);
+ }
+
+ player_remove(p1);
+ }
+ } /* for */
+
+ closedir(dirp);
+ } /* for */
+
+ if (Rs_count) {
+ Ratings_S_StdDev = sqrt(Rs_S / Rs_count);
+ Ratings_S_Average = (Rs_total / (double)Rs_count);
+ } else {
+ Ratings_S_StdDev = 0;
+ Ratings_S_Average = 0;
+ }
+ if (Rb_count) {
+ Ratings_B_StdDev = sqrt(Rb_S / Rb_count);
+ Ratings_B_Average = (Rb_total / (double)Rb_count);
+ } else {
+ Ratings_B_StdDev = 0;
+ Ratings_B_Average = 0;
+ }
+ if (Rl_count) {
+ Ratings_L_StdDev = sqrt(Rl_S / Rl_count);
+ Ratings_L_Average = (Rl_total / (double)Rl_count);
+ } else {
+ Ratings_L_StdDev = 0;
+ Ratings_L_Average = 0;
+ }
+ if (Rw_count) {
+ Ratings_W_StdDev = sqrt(Rw_S / Rw_count);
+ Ratings_W_Average = (Rw_total / (double)Rw_count);
+ } else {
+ Ratings_W_StdDev = 0;
+ Ratings_W_Average = 0;
+ }
+
+ save_ratings();
+
+ t = time(NULL);
+ fprintf(stderr, "FICS: Finished at %s\n", strltime(&t));
+}
+
+PRIVATE int
+Round(double x)
+{
+ return (x < 0 ? (int)(x - 0.5) : (int)(x + 0.5));
+}
+
+PRIVATE double
+Gf(double ss)
+{
+ return (1.0 / sqrt(1.0 + Gp * ss * ss));
+}
+
+/*
+ * Confusing but economical: calculate error and attenuation function
+ * together.
+ */
+PRIVATE double
+GE(int r, int rr, double ss, double *fss)
+{
+ *fss = Gf(ss);
+ return (1.0 / (1.0 + pow(10.0, (rr - r) * (*fss) / 400.0)));
+}
+
+PRIVATE double
+current_sterr(double s, int64_t t)
+{
+ if (t < 0)
+ t = 0; // this shouldn't happen
+ return sqrt(s * s + Gd * Gd * log(1.0 + t / 60.0));
+}
+
+/*
+ * Calculates new rating and standard error. By vek. The person who
+ * invented the ratings system is Mark E. Glickman, Ph.D. His e-mail
+ * address is glickman@hustat.harvard.edu as of April '95. Postscript
+ * copy of the note I coded this from should be available for ftp from
+ * ics.onenet.net, if not elsewhere.
+ */
+PUBLIC void
+rating_sterr_delta(int p1, int p2, int type, time_t gtime, int result,
+ int *deltarating, double *newsterr)
+{
+ double E, fs2, denominator, GK, w; // Parts of fancy formulas
+ double delta, sigma; // Results to return
+ double s1, s2;
+ int r1, r2; // Initial sterrs and ratings
+ statistics *p1_stats;
+ statistics *p2_stats;
+ time_t t1, t2;
+
+ if (type == TYPE_BLITZ) {
+ p1_stats = &parray[p1].b_stats;
+ p2_stats = &parray[p2].b_stats;
+ } else if (type == TYPE_WILD) {
+ p1_stats = &parray[p1].w_stats;
+ p2_stats = &parray[p2].w_stats;
+ } else if (type == TYPE_LIGHT) {
+ p1_stats = &parray[p1].l_stats;
+ p2_stats = &parray[p2].l_stats;
+
+ // insert bughouse stuff here
+ } else {
+ p1_stats = &parray[p1].s_stats;
+ p2_stats = &parray[p2].s_stats;
+ }
+
+ /*
+ * Calculate effective pre-game sterr's. ltime == 0 implies
+ * never had sterr.
+ */
+ if (p1_stats->ltime == 0)
+ s1 = Gs0;
+ else {
+ t1 = gtime - p1_stats->ltime;
+ s1 = current_sterr(p1_stats->sterr, t1);
+
+ if (s1 > Gs0)
+ s1 = Gs0;
+ }
+
+ if (p2_stats->ltime == 0)
+ s2 = Gs0;
+ else {
+ t2 = gtime - p2_stats->ltime;
+ s2 = current_sterr(p2_stats->sterr, t2);
+
+ if (s2 > Gs0)
+ s2 = Gs0;
+ }
+
+ if (p1_stats->rating == 0 && p1_stats->num == 0)
+ r1 = Gr0;
+ else
+ r1 = p1_stats->rating;
+
+ if (p2_stats->rating == 0 && p2_stats->num == 0)
+ r2 = Gr0;
+ else
+ r2 = p2_stats->rating;
+
+ if (result == RESULT_WIN) {
+ w = 1.0;
+ } else if (result == RESULT_DRAW) {
+ w = 0.5;
+ } else {
+ w = 0.0;
+ }
+
+ E = GE(r1, r2, s2, &fs2);
+ denominator = 1.0 / (s1 * s1) + Gq * Gq * fs2 * fs2 * E * (1.0 - E);
+ GK = Gq * fs2 / denominator;
+ delta = GK * (w - E);
+
+ if (p1_stats->rating == 0 && p1_stats->num == 0)
+ *deltarating = Round(Gr0 + delta);
+ else
+ *deltarating = Round(delta); // Returned values: deltarating,
+ // newsterr.
+
+ sigma = 1.0 / sqrt(denominator);
+ *newsterr = (double) sigma;
+}
+
+PUBLIC int
+rating_delta(int p1, int p2, int type, int result, int gtime)
+{
+ int delta;
+ double sigma;
+
+ rating_sterr_delta(p1, p2, type, gtime, result, &delta, &sigma);
+ return delta;
+}
+
+PUBLIC int
+rating_update(int g)
+{
+ double wSigma, bSigma;
+ int inprogress = (g == parray[garray[g].black].game);
+ int wDelta, bDelta;
+ int wRes, bRes;
+ statistics *b_stats;
+ statistics *w_stats;
+ time_t gtime;
+
+ /*
+ * If this is adjudication of stored game - be quiet about
+ * ratings change.
+ */
+ if (garray[g].type == TYPE_BLITZ) {
+ w_stats = &parray[garray[g].white].b_stats;
+ b_stats = &parray[garray[g].black].b_stats;
+ } else if (garray[g].type == TYPE_STAND) {
+ w_stats = &parray[garray[g].white].s_stats;
+ b_stats = &parray[garray[g].black].s_stats;
+ } else if (garray[g].type == TYPE_WILD) {
+ w_stats = &parray[garray[g].white].w_stats;
+ b_stats = &parray[garray[g].black].w_stats;
+ } else if (garray[g].type == TYPE_LIGHT) {
+ w_stats = &parray[garray[g].white].l_stats;
+ b_stats = &parray[garray[g].black].l_stats;
+ } else {
+ fprintf(stderr, "FICS: Can't update untimed ratings!\n");
+ return -1;
+ }
+
+ switch (garray[g].result) {
+ case END_CHECKMATE:
+ case END_RESIGN:
+ case END_FLAG:
+ case END_ADJWIN:
+ if (garray[g].winner == WHITE) {
+ wRes = RESULT_WIN;
+ bRes = RESULT_LOSS;
+ } else {
+ bRes = RESULT_WIN;
+ wRes = RESULT_LOSS;
+ }
+ break;
+ case END_AGREEDDRAW:
+ case END_REPETITION:
+ case END_50MOVERULE:
+ case END_STALEMATE:
+ case END_NOMATERIAL:
+ case END_BOTHFLAG:
+ case END_ADJDRAW:
+ case END_FLAGNOMATERIAL:
+ wRes = bRes = RESULT_DRAW;
+ break;
+ default:
+ fprintf(stderr, "FICS: Update undecided game %d?\n",
+ garray[g].result);
+ return -1;
+ }
+
+ gtime = untenths(garray[g].timeOfStart);
+
+ rating_sterr_delta(garray[g].white, garray[g].black, garray[g].type,
+ gtime, wRes, &wDelta, &wSigma);
+ rating_sterr_delta(garray[g].black, garray[g].white, garray[g].type,
+ gtime, bRes, &bDelta, &bSigma);
+
+ w_stats->ltime = gtime;
+ b_stats->ltime = gtime;
+
+ if (wRes == RESULT_WIN) {
+ w_stats->win++;
+ } else if (wRes == RESULT_LOSS) {
+ w_stats->los++;
+ } else {
+ w_stats->dra++;
+ }
+
+ w_stats->num++;
+
+ if (bRes == RESULT_WIN) {
+ b_stats->win++;
+ } else if (bRes == RESULT_LOSS) {
+ b_stats->los++;
+ } else {
+ b_stats->dra++;
+ }
+
+ b_stats->num++;
+
+ rating_remove(w_stats->rating, garray[g].type);
+ rating_remove(b_stats->rating, garray[g].type);
+
+ if (inprogress) {
+ pprintf(garray[g].white, "%s rating adjustment: %d ",
+ ((garray[g].type == TYPE_BLITZ) ? "Blitz" :
+ ((garray[g].type == TYPE_WILD) ? "Wild" :
+ ((garray[g].type == TYPE_LIGHT) ? "Lightning" :
+ "Standard"))),
+ w_stats->rating);
+ pprintf(garray[g].black, "%s rating adjustment: %d ",
+ ((garray[g].type == TYPE_BLITZ) ? "Blitz" :
+ ((garray[g].type == TYPE_WILD) ? "Wild" :
+ ((garray[g].type == TYPE_LIGHT) ? "Lightning" :
+ "Standard"))),
+ b_stats->rating);
+ }
+
+ if (wDelta < -1000) {
+ pprintf(garray[g].white, "not changed due to bug "
+ "(way too small)! sorry!\n");
+ fprintf(stderr, "FICS: Got too small ratings bug for %s "
+ "(w) vs. %s\n",
+ parray[garray[g].white].login,
+ parray[garray[g].black].login);
+ } else if (wDelta > 3000) {
+ pprintf(garray[g].white, "not changed due to bug "
+ "(way too big)! sorry!\n");
+ fprintf(stderr, "FICS: Got too big ratings bug for %s "
+ "(w) vs. %s\n",
+ parray[garray[g].white].login,
+ parray[garray[g].black].login);
+ } else {
+ w_stats->rating += wDelta;
+ w_stats->sterr = wSigma;
+ }
+
+ if (bDelta < -1000) {
+ pprintf(garray[g].black, "not changed due to bug "
+ "(way too small)! sorry!\n");
+ fprintf(stderr, "FICS: Got too small ratings bug for %s "
+ "(b) vs. %s\n",
+ parray[garray[g].black].login,
+ parray[garray[g].white].login);
+ } else if (bDelta > 3000) {
+ pprintf(garray[g].black, "not changed due to bug "
+ "(way too big)! sorry!\n");
+ fprintf(stderr, "FICS: Got too big ratings bug for %s "
+ "(b) vs. %s\n",
+ parray[garray[g].black].login,
+ parray[garray[g].white].login);
+ } else {
+ b_stats->rating += bDelta;
+ b_stats->sterr = bSigma;
+ }
+
+ rating_add(w_stats->rating, garray[g].type);
+ rating_add(b_stats->rating, garray[g].type);
+
+ if (w_stats->rating > w_stats->best &&
+ is_active(w_stats->num)) {
+ w_stats->best = w_stats->rating;
+ w_stats->whenbest = time(NULL);
+ }
+ if (b_stats->rating > b_stats->best &&
+ is_active(b_stats->num)) {
+ b_stats->best = b_stats->rating;
+ b_stats->whenbest = time(NULL);
+ }
+
+ // Ratings are now saved to disk after each game
+ player_save(garray[g].white);
+ player_save(garray[g].black);
+
+ // foxbat 3.11.95
+ if (garray[g].type == TYPE_BLITZ) {
+ Rb_count++;
+ Rb_total += (w_stats->rating + b_stats->rating) / 2.0;
+ } else if (garray[g].type == TYPE_STAND) {
+ Rs_count++;
+ Rs_total += (w_stats->rating + b_stats->rating) / 2.0;
+ } else if (garray[g].type == TYPE_LIGHT) {
+ Rl_count++;
+ Rl_total += (w_stats->rating + b_stats->rating) / 2.0;
+ } else if (garray[g].type == TYPE_WILD) {
+ Rw_count++;
+ Rw_total += (w_stats->rating + b_stats->rating) / 2.0;
+ }
+
+ // end add
+ if (inprogress) {
+ pprintf(garray[g].white, "--> %d\n", w_stats->rating);
+ pprintf(garray[g].black, "--> %d\n", b_stats->rating);
+ }
+
+ save_ratings();
+
+ UpdateRank(garray[g].type, parray[garray[g].white].name, w_stats,
+ parray[garray[g].white].name);
+ UpdateRank(garray[g].type, parray[garray[g].black].name, b_stats,
+ parray[garray[g].black].name);
+ return 0;
+}
+
+PUBLIC int
+com_assess(int p, param_list param)
+{
+ double newsterr1;
+ double newsterr2;
+ int p1 = p, p2;
+ int p1_connected = 1, p2_connected = 1;
+ int win1, draw1, loss1;
+ int win2, draw2, loss2;
+ time_t nowtime;
+
+ nowtime = time(NULL);
+
+ if (param[0].type == TYPE_NULL) {
+ if (parray[p].game < 0) {
+ pprintf(p, "You are not playing a game.\n");
+ return COM_OK;
+ } else if (garray[parray[p].game].status == GAME_EXAMINE) {
+ if (!strcmp(garray[parray[p].game].black_name,
+ parray[p].name)) {
+ pcommand(p, "assess %s\n",
+ garray[parray[p].game].white_name);
+ } else {
+ pcommand(p, "assess %s %s\n",
+ garray[parray[p].game].white_name,
+ garray[parray[p].game].black_name);
+ }
+
+ return COM_OK;
+ } else {
+ p2 = parray[p].opponent;
+ }
+ } else {
+ if (!FindPlayer(p, param[0].val.word, &p2, &p2_connected)) {
+ pprintf(p, "No user named \"%s\" was found.\n",
+ param[0].val.word);
+ return COM_OK;
+ }
+
+ if (param[1].type != TYPE_NULL) {
+ p1 = p2;
+ p1_connected = p2_connected;
+
+ if (!FindPlayer(p, param[1].val.word, &p2,
+ &p2_connected)) {
+ pprintf(p, "No user named \"%s\" was found.\n",
+ param[1].val.word);
+
+ if (!p1_connected)
+ player_remove(p1);
+ return COM_OK;
+ }
+ }
+ }
+
+ if (p1 == p2) {
+ pprintf(p, "You can't assess the same players.\n");
+
+ if (!p1_connected)
+ player_remove(p1);
+ if (!p2_connected)
+ player_remove(p2);
+ return COM_OK;
+ }
+
+ rating_sterr_delta(p1, p2, TYPE_BLITZ, nowtime, RESULT_WIN, &win1,
+ &newsterr1);
+ rating_sterr_delta(p1, p2, TYPE_BLITZ, nowtime, RESULT_DRAW, &draw1,
+ &newsterr1);
+ rating_sterr_delta(p1, p2, TYPE_BLITZ, nowtime, RESULT_LOSS, &loss1,
+ &newsterr1);
+ rating_sterr_delta(p2, p1, TYPE_BLITZ, nowtime, RESULT_WIN, &win2,
+ &newsterr2);
+ rating_sterr_delta(p2, p1, TYPE_BLITZ, nowtime, RESULT_DRAW, &draw2,
+ &newsterr2);
+ rating_sterr_delta(p2, p1, TYPE_BLITZ, nowtime, RESULT_LOSS, &loss2,
+ &newsterr2);
+
+ pprintf(p, "\nBlitz\n %10s (%4s, RD: %5.1f) %10s (%4s, RD: %5.1f)\n",
+ parray[p1].name,
+ ratstrii(parray[p1].b_stats.rating, parray[p1].registered),
+ parray[p1].b_stats.sterr,
+ parray[p2].name,
+ ratstrii(parray[p2].b_stats.rating, parray[p2].registered),
+ parray[p2].b_stats.sterr);
+
+ pprintf(p, "Win : %4d %4d\n",
+ win1,
+ loss2);
+ pprintf(p, "Draw: %4d %4d\n",
+ draw1,
+ draw2);
+ pprintf(p, "Loss: %4d %4d\n",
+ loss1,
+ win2);
+ pprintf(p, "New RD: %5.1f %5.1f\n",
+ newsterr1,
+ newsterr2);
+
+ rating_sterr_delta(p1, p2, TYPE_STAND, nowtime, RESULT_WIN, &win1,
+ &newsterr1);
+ rating_sterr_delta(p1, p2, TYPE_STAND, nowtime, RESULT_DRAW, &draw1,
+ &newsterr1);
+ rating_sterr_delta(p1, p2, TYPE_STAND, nowtime, RESULT_LOSS, &loss1,
+ &newsterr1);
+ rating_sterr_delta(p2, p1, TYPE_STAND, nowtime, RESULT_WIN, &win2,
+ &newsterr2);
+ rating_sterr_delta(p2, p1, TYPE_STAND, nowtime, RESULT_DRAW, &draw2,
+ &newsterr2);
+ rating_sterr_delta(p2, p1, TYPE_STAND, nowtime, RESULT_LOSS, &loss2,
+ &newsterr2);
+
+ pprintf(p, "\nStandard\n %10s (%4s, RD: %5.1f) %10s "
+ "(%4s, RD: %5.1f)\n",
+ parray[p1].name,
+ ratstrii(parray[p1].s_stats.rating, parray[p1].registered),
+ parray[p1].s_stats.sterr,
+ parray[p2].name,
+ ratstrii(parray[p2].s_stats.rating, parray[p2].registered),
+ parray[p2].s_stats.sterr);
+
+ pprintf(p, "Win : %4d %4d\n",
+ win1,
+ loss2);
+ pprintf(p, "Draw: %4d %4d\n",
+ draw1,
+ draw2);
+ pprintf(p, "Loss: %4d %4d\n",
+ loss1,
+ win2);
+ pprintf(p, "New RD: %5.1f %5.1f\n",
+ newsterr1,
+ newsterr2);
+
+ rating_sterr_delta(p1, p2, TYPE_LIGHT, nowtime, RESULT_WIN, &win1,
+ &newsterr1);
+ rating_sterr_delta(p1, p2, TYPE_LIGHT, nowtime, RESULT_DRAW, &draw1,
+ &newsterr1);
+ rating_sterr_delta(p1, p2, TYPE_LIGHT, nowtime, RESULT_LOSS, &loss1,
+ &newsterr1);
+ rating_sterr_delta(p2, p1, TYPE_LIGHT, nowtime, RESULT_WIN, &win2,
+ &newsterr2);
+ rating_sterr_delta(p2, p1, TYPE_LIGHT, nowtime, RESULT_DRAW, &draw2,
+ &newsterr2);
+ rating_sterr_delta(p2, p1, TYPE_LIGHT, nowtime, RESULT_LOSS, &loss2,
+ &newsterr2);
+
+ pprintf(p, "\nLightning\n %10s (%4s, RD: %5.1f) %10s "
+ "(%4s, RD: %5.1f)\n",
+ parray[p1].name,
+ ratstrii(parray[p1].l_stats.rating, parray[p1].registered),
+ parray[p1].l_stats.sterr,
+ parray[p2].name,
+ ratstrii(parray[p2].l_stats.rating, parray[p2].registered),
+ parray[p2].l_stats.sterr);
+
+ pprintf(p, "Win : %4d %4d\n",
+ win1,
+ loss2);
+ pprintf(p, "Draw: %4d %4d\n",
+ draw1,
+ draw2);
+ pprintf(p, "Loss: %4d %4d\n",
+ loss1,
+ win2);
+ pprintf(p, "New RD: %5.1f %5.1f\n",
+ newsterr1,
+ newsterr2);
+
+ rating_sterr_delta(p1, p2, TYPE_WILD, nowtime, RESULT_WIN, &win1,
+ &newsterr1);
+ rating_sterr_delta(p1, p2, TYPE_WILD, nowtime, RESULT_DRAW, &draw1,
+ &newsterr1);
+ rating_sterr_delta(p1, p2, TYPE_WILD, nowtime, RESULT_LOSS, &loss1,
+ &newsterr1);
+
+ rating_sterr_delta(p2, p1, TYPE_WILD, nowtime, RESULT_WIN, &win2,
+ &newsterr2);
+ rating_sterr_delta(p2, p1, TYPE_WILD, nowtime, RESULT_DRAW, &draw2,
+ &newsterr2);
+ rating_sterr_delta(p2, p1, TYPE_WILD, nowtime, RESULT_LOSS, &loss2,
+ &newsterr2);
+
+ pprintf(p, "\nWild\n %10s (%4s, RD: %5.1f) %10s (%4s, RD: %5.1f)\n",
+ parray[p1].name,
+ ratstrii(parray[p1].w_stats.rating, parray[p1].registered),
+ parray[p1].w_stats.sterr,
+ parray[p2].name,
+ ratstrii(parray[p2].w_stats.rating, parray[p2].registered),
+ parray[p2].w_stats.sterr);
+
+ pprintf(p, "Win : %4d %4d\n",
+ win1,
+ loss2);
+ pprintf(p, "Draw: %4d %4d\n",
+ draw1,
+ draw2);
+ pprintf(p, "Loss: %4d %4d\n",
+ loss1,
+ win2);
+ pprintf(p, "New RD: %5.1f %5.1f\n",
+ newsterr1,
+ newsterr2);
+
+ if (!p1_connected)
+ player_remove(p1);
+ if (!p2_connected)
+ player_remove(p2);
+ return COM_OK;
+}
+
+PUBLIC int
+com_best(int p, param_list param)
+{
+ return Best(p, param, 1);
+}
+
+PUBLIC int
+com_hbest(int p, param_list param)
+{
+ return Best(p, param, 0);
+}
+
+PUBLIC int
+com_statistics(int p, param_list param)
+{
+ pprintf(p, " Standard Blitz Lightning "
+ "Wild\n");
+ pprintf(p, "average: %7.2f %7.2f %7.2f %7.2f\n",
+ Ratings_S_Average,
+ Ratings_B_Average,
+ Ratings_L_Average,
+ Ratings_W_Average);
+ pprintf(p, "std dev: %7.2f %7.2f %7.2f %7.2f\n",
+ Ratings_S_StdDev,
+ Ratings_B_StdDev,
+ Ratings_L_StdDev,
+ Ratings_W_StdDev);
+ pprintf(p, "number : %7d %7d %7d %7d\n",
+ Rs_count, Rb_count, Rl_count, Rw_count);
+ return COM_OK;
+}
+
+/*
+ * Return the difference of 'a - b'
+ */
+PUBLIC int
+int_diff(const char *fn, const int a, const int b)
+{
+ if ((b > 0 && a < INT_MIN + b) ||
+ (b < 0 && a > INT_MAX + b))
+ errx(1, "%s: integer overflow (%d - %d)", fn, a, b);
+ return (a - b);
+}
+
+PUBLIC int
+com_fixrank(int p, param_list param)
+{
+ int p1, connected;
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+
+ UpdateRank(TYPE_BLITZ, parray[p1].name, &parray[p1].b_stats,
+ parray[p1].name);
+ UpdateRank(TYPE_STAND, parray[p1].name, &parray[p1].s_stats,
+ parray[p1].name);
+ UpdateRank(TYPE_WILD, parray[p1].name, &parray[p1].w_stats,
+ parray[p1].name);
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+}
+
+PUBLIC int
+com_rank(int p, param_list param)
+{
+ return DisplayRank(p, param, 1);
+}
+
+PUBLIC int
+com_hrank(int p, param_list param)
+{
+ return DisplayRank(p, param, 0);
+}
+
+PUBLIC int
+DisplayRank(int p, param_list param, int showComputers)
+{
+ int show = (SHOW_BLITZ|SHOW_STANDARD|SHOW_WILD);
+ int start, end, target, connected;
+
+ if (param[0].type == TYPE_NULL) {
+ DisplayTargetRank(p, parray[p].name, show, showComputers);
+ return COM_OK;
+ } else if (isdigit(param[0].val.word[0])) {
+ int ret;
+
+ start = 0;
+ end = -1;
+ ret = sscanf(param[0].val.word, "%d-%d", &start, &end);
+
+ if (ret != 2)
+ return COM_FAILED;
+
+ if (end > 0 && (param[1].type != TYPE_NULL))
+ show = ShowFromString(param[1].val.word);
+
+ DisplayRankedPlayers(p, start, end, show, showComputers);
+ return COM_OK;
+ } else {
+ target = player_search(p, param[0].val.word);
+
+ if (target == 0) {
+ pprintf(p, "Target %s not found.\n", param[0].val.word);
+ return COM_OK;
+ }
+
+ connected = (target > 0);
+
+ if (!connected)
+ target = -target - 1;
+ else
+ target--;
+
+ if (param[1].type != TYPE_NULL)
+ show = ShowFromString(param[1].val.word);
+
+ DisplayTargetRank(p, parray[target].name, show, showComputers);
+
+ if (!connected)
+ player_remove(target);
+ return COM_OK;
+ }
+}
+
+/*
+ * CompareStats() returns:
+ * - 1 if s1 comes first,
+ * - -1 if s2 comes first,
+ * - and 0 if neither takes precedence.
+ */
+PRIVATE int
+CompareStats(char *name1, statistics *s1,
+ char *name2, statistics *s2)
+{
+ int i, l1;
+
+ if (s1 == NULL) {
+ if (s2 == NULL)
+ return 0;
+ else
+ return -1;
+ } else if (s2 == NULL)
+ return 1;
+
+ if (s1->rating > s2->rating)
+ return 1;
+ if (s1->rating < s2->rating)
+ return -1;
+
+ l1 = strlen(name1);
+
+ for (i = 0; i < l1; i++) {
+ if (name2[i] == '\0')
+ return -1;
+ if (tolower(name1[i]) < tolower(name2[i]))
+ return 1;
+ if (tolower(name1[i]) > tolower(name2[i]))
+ return -1;
+ }
+
+ if (name2[i] != '\0')
+ return 1;
+
+ fprintf(stderr, "Duplicate entries found: %s.\n", name1);
+ return 0;
+}
+
+PRIVATE int
+GetRankFileName(char *out, const size_t size, int type)
+{
+ switch (type) {
+ case TYPE_BLITZ:
+ snprintf(out, size, "%s/rank.blitz", sdir);
+ return type;
+ case TYPE_STAND:
+ snprintf(out, size, "%s/rank.std", sdir);
+ return type;
+ case TYPE_WILD:
+ snprintf(out, size, "%s/rank.wild", sdir);
+ return type;
+ default:
+ return -1;
+ }
+}
+
+PUBLIC void
+UpdateRank(int type, char *addName, statistics *sNew, char *delName)
+{
+ FILE *fp;
+ FILE *fptemp;
+ char RankFile[MAX_FILENAME_SIZE];
+ char TmpRankFile[MAX_FILENAME_SIZE];
+ char command[MAX_STRING_LENGTH];
+ char line[MAX_RANK_LINE] = { '\0' };
+ char login[MAX_LOGIN_NAME] = { '\0' };
+ int comp = 0;
+ int fd = -1;
+ statistics sCur;
+
+ if (GetRankFileName(RankFile, sizeof RankFile, type) < 0)
+ return;
+
+ if ((fp = fopen(RankFile, "r")) == NULL) {
+ warn("%s: can't open rank file to update", __func__);
+ return;
+ }
+
+ snprintf(TmpRankFile, sizeof TmpRankFile, "%s/tmpRank", sdir);
+
+ errno = 0;
+ fd = open(TmpRankFile, g_open_flags[1], g_open_modes);
+
+ if (fd < 0) {
+ warn("%s: open", __func__);
+ fclose(fp);
+ return;
+ } else if ((fptemp = fdopen(fd, "w")) == NULL) {
+ warn("%s: unable to open rank file for updating", __func__);
+ fclose(fp);
+ close(fd);
+ return;
+ }
+
+ while (fgets(line, sizeof line, fp) != NULL) {
+ _Static_assert(ARRAY_SIZE(login) > 19, "'login' too small");
+
+ if (sscanf(line, "%19s %d %d %d", login,
+ &sCur.rating, &sCur.num, &comp) != 4) {
+ warnx("%s: %s: sscanf() error -- too few items",
+ __func__, RankFile);
+ continue;
+ }
+
+ if (delName != NULL &&
+ !strcasecmp(delName, login)) { // Kill name.
+ delName = NULL;
+ continue;
+ }
+
+ if (addName != NULL &&
+ CompareStats(addName, sNew, login, &sCur) > 0) {
+ int computer = in_list(-1, L_COMPUTER, addName);
+
+ fprintf(fptemp, "%s %d %d %d\n", addName, sNew->rating,
+ sNew->num, computer);
+ addName = NULL;
+ }
+
+ fprintf(fptemp, "%s %d %d %d\n", login, sCur.rating, sCur.num,
+ comp);
+ }
+
+ fclose(fptemp);
+ fclose(fp);
+
+ // XXX
+#define NASH_CODE 0
+#if NASH_CODE
+ snprintf(command, sizeof command, "mv %s %s", TmpRankFile, RankFile);
+ system(command);
+#else
+ if (rename(TmpRankFile, RankFile) == -1)
+ warn("%s: rename()", __func__);
+ UNUSED_VAR(command);
+#endif
+}
+
+PRIVATE void
+DisplayRankHead(int p, int show)
+{
+ char Line[MAX_STRING_LENGTH] = { '\0' };
+
+ if (CheckFlag(show, SHOW_BLITZ))
+ strlcat(Line, " Blitz ", sizeof(Line));
+ if (CheckFlag(show, SHOW_STANDARD))
+ strlcat(Line, " Standard ", sizeof(Line));
+ if (CheckFlag(show, SHOW_WILD))
+ strlcat(Line, " Wild", sizeof(Line));
+
+ pprintf(p, "%s\n\n", Line);
+}
+
+PRIVATE int
+CountRankLine(int countComp, char *loginName, int num, int is_computer)
+{
+ if (loginName == NULL || loginName[0] == '\0')
+ return 0;
+ return (countComp || !is_computer) && (is_active(num));
+}
+
+PRIVATE int
+GetRank(FILE *fp, char *target, int countComp)
+{
+ char line[MAX_RANK_LINE] = { '\0' };
+ char login[MAX_LOGIN_NAME] = { '\0' };
+ int count = 0;
+ int is_computer = 0;
+ int nGames = 0;
+ int playerFound = 0;
+
+ while (fgets(line, sizeof line, fp) != NULL &&
+ !playerFound) {
+ _Static_assert(ARRAY_SIZE(login) > 19, "'login' too small");
+
+ if (sscanf(line, "%19s %*d %d %d", login, &nGames, &is_computer)
+ < 3) {
+ continue;
+ }
+
+ if ((playerFound = !strcasecmp(login, target)) ||
+ CountRankLine(countComp, login, nGames, is_computer))
+ count++;
+ }
+
+ return (playerFound ? count : -1);
+}
+
+PRIVATE void
+PositionFilePtr(FILE *fp, int count, int *last, int *nTied, int showComp)
+{
+ char line[MAX_RANK_LINE] = { '\0' };
+ char login[MAX_LOGIN_NAME] = { '\0' };
+ int rating, nGames, is_computer;
+
+ if (fp == NULL)
+ return;
+
+ rating = nGames = is_computer = 0;
+ errno = 0;
+ rewind(fp);
+ if (errno) {
+ warn("%s: rewind", __func__);
+ return;
+ }
+
+ for (int i = 1; i < count; i++) {
+ do {
+ _Static_assert(ARRAY_SIZE(login) > 19,
+ "'login' too small");
+
+ if (feof(fp) || ferror(fp) ||
+ fgets(line, sizeof line, fp) == NULL)
+ break;
+ else if (sscanf(line, "%19s %d %d %d", login, &rating,
+ &nGames, &is_computer) != 4) {
+ warnx("%s: sscanf() error", __func__);
+ break;
+ }
+ } while (!CountRankLine(showComp, login, nGames, is_computer));
+
+ if (ferror(fp)) {
+ warnx("%s: the error indicator is set", __func__);
+ return;
+ }
+
+ if (rating != *last) {
+ *nTied = 1;
+ *last = rating;
+ } else
+ (*nTied)++;
+ }
+}
+
+PRIVATE int
+ShowRankEntry(int p, FILE *fp, int count, int comp, char *target,
+ int *lastRating, int *nTied)
+{
+ char login[MAX_LOGIN_NAME] = { '\0' };
+ char newLine[MAX_RANK_LINE] = { '\0' };
+ int rating, findable, nGames, is_comp;
+
+ // XXX
+ rating = 0;
+ findable = (count > 0 && !feof(fp) && !ferror(fp));
+ nGames = 0;
+ is_comp = 0;
+
+ if (findable) {
+ do {
+ if (fgets(newLine, sizeof newLine, fp) == NULL ||
+ feof(fp) ||
+ ferror(fp)) {
+ findable = 0;
+ } else if (newLine[0] != '\0') {
+ _Static_assert(ARRAY_SIZE(login) > 19,
+ "Assertion has failed");
+
+ if (sscanf(newLine, "%19s %d %d %d", login,
+ &rating, &nGames, &is_comp) != 4) {
+ warnx("%s: sscanf() error", __func__);
+ findable = 0;
+ break;
+ }
+ } else {
+ login[0] = '\0';
+ }
+ } while (!CountRankLine(comp, login, nGames, is_comp) &&
+ findable &&
+ strcasecmp(login, target));
+ }
+
+ if (findable) {
+ if (!strcasecmp(login, target) && !CountRankLine(comp, login,
+ nGames, is_comp)) {
+ pprintf_highlight(p, "---- %-12.12s %4s", login,
+ ratstr(rating));
+ pprintf(p, " ");
+ return 0;
+ } else if (*lastRating == rating && *nTied < 1) {
+ pprintf(p, " ");
+
+ if (!strcasecmp(login, target)) {
+ pprintf_highlight(p, "%-12.12s %4s", login,
+ ratstr(rating));
+ } else {
+ pprintf(p, "%-12.12s %4s", login,
+ ratstr(rating));
+ }
+
+ pprintf(p, " ");
+ return 1;
+ } else {
+ if (*nTied >= 1) {
+ if (*lastRating == rating)
+ count -= *nTied;
+ *nTied = -1;
+ }
+
+ if (!strcasecmp(login, target)) {
+ pprintf_highlight(p, "%4d. %-12.12s %4s", count,
+ login, ratstr(rating));
+ } else {
+ pprintf(p, "%4d. %-12.12s %4s", count, login,
+ ratstr(rating));
+ }
+
+ pprintf(p, " ");
+ *lastRating = rating;
+ return 1;
+ }
+ } else {
+ pprintf(p, "%25s", "");
+ return 1;
+ }
+}
+
+PRIVATE int
+CountAbove(int num, int blitz, int std, int wild, int which)
+{
+ int max = blitz;
+
+ if (max < std)
+ max = std;
+ if (max < wild)
+ max = wild;
+ return (max <= (num + 1) / 2 ? max - 1 : (num + 1) / 2);
+}
+
+PRIVATE int
+ShowRankLines(int p, FILE *fb, FILE *fs, FILE *fw, int bCount, int sCount,
+ int wCount, int n, int showComp, int show, char *target)
+{
+ int lastBlitz = 9999, nTiedBlitz = 0;
+ int lastStd = 9999, nTiedStd = 0;
+ int lastWild = 9999, nTiedWild = 0;
+
+ if (n <= 0)
+ return 0;
+
+ if (fb != NULL && CheckFlag(show, SHOW_BLITZ)) {
+ PositionFilePtr(fb, bCount, &lastBlitz, &nTiedBlitz, showComp);
+
+ if (feof(fb))
+ ClearFlag(show, SHOW_BLITZ);
+ }
+
+ if (fs != NULL && CheckFlag(show, SHOW_STANDARD)) {
+ PositionFilePtr(fs, sCount, &lastStd, &nTiedStd, showComp);
+
+ if (feof(fs))
+ ClearFlag(show, SHOW_STANDARD);
+ }
+
+ if (fw != NULL && CheckFlag(show, SHOW_WILD)) {
+ PositionFilePtr(fw, wCount, &lastWild, &nTiedWild, showComp);
+
+ if (feof(fw))
+ ClearFlag(show, SHOW_WILD);
+ }
+
+ if (!CheckFlag(show, (SHOW_BLITZ | SHOW_STANDARD | SHOW_WILD)))
+ return 0;
+
+ DisplayRankHead(p, show);
+
+ for (int i = 0; i < n && show; i++) {
+ if (fb != NULL && CheckFlag(show, SHOW_BLITZ)) {
+ bCount += ShowRankEntry(p, fb, bCount, showComp, target,
+ &lastBlitz, &nTiedBlitz);
+ }
+ if (fs != NULL && CheckFlag(show, SHOW_STANDARD)) {
+ sCount += ShowRankEntry(p, fs, sCount, showComp, target,
+ &lastStd, &nTiedStd);
+ }
+ if (fw != NULL && CheckFlag(show, SHOW_WILD)) {
+ wCount += ShowRankEntry(p, fw, wCount, showComp, target,
+ &lastWild, &nTiedWild);
+ }
+ pprintf(p, "\n");
+ }
+
+ return 1;
+}
+
+PUBLIC int
+DisplayTargetRank(int p, char *target, int show, int showComp)
+{
+ FILE *fb = NULL;
+ FILE *fs = NULL;
+ FILE *fw = NULL;
+ char Path[MAX_FILENAME_SIZE] = { '\0' };
+ int numAbove;
+ int numToShow = 20;
+ int blitzRank = -1, blitzCount;
+ int stdRank = -1, stdCount;
+ int wildRank = -1, wildCount;
+
+ if (CheckFlag(show, SHOW_BLITZ)) {
+ GetRankFileName(Path, sizeof Path, TYPE_BLITZ);
+
+ if ((fb = fopen(Path, "r")) != NULL)
+ blitzRank = GetRank(fb, target, showComp);
+ if (blitzRank < 0)
+ ClearFlag(show, SHOW_BLITZ);
+ }
+
+ if (CheckFlag(show, SHOW_STANDARD)) {
+ GetRankFileName(Path, sizeof Path, TYPE_STAND);
+
+ if ((fs = fopen(Path, "r")) != NULL)
+ stdRank = GetRank(fs, target, showComp);
+ if (stdRank < 0)
+ ClearFlag(show, SHOW_STANDARD);
+ }
+
+ if (CheckFlag(show, SHOW_WILD)) {
+ GetRankFileName(Path, sizeof Path, TYPE_WILD);
+
+ if (CheckFlag(show, SHOW_WILD))
+ fw = fopen(Path, "r");
+ if (fw != NULL)
+ wildRank = GetRank(fw, target, showComp);
+ if (wildRank < 0)
+ ClearFlag(show, SHOW_WILD);
+ }
+
+ if (!CheckFlag(show, (SHOW_BLITZ | SHOW_STANDARD | SHOW_WILD))) {
+ pprintf(p, "No ratings to show.\n");
+
+ if (fb != NULL)
+ fclose(fb);
+ if (fs != NULL)
+ fclose(fs);
+ if (fw != NULL)
+ fclose(fw);
+ return 0;
+ }
+
+ numAbove = CountAbove(numToShow, blitzRank, stdRank, wildRank, show);
+
+ blitzCount = int_diff(__func__, blitzRank, numAbove);
+ stdCount = int_diff(__func__, stdRank, numAbove);
+ wildCount = int_diff(__func__, wildRank, numAbove);
+
+ ShowRankLines(p, fb, fs, fw, blitzCount, stdCount, wildCount, numToShow,
+ showComp, show, target);
+
+ if (fb != NULL)
+ fclose(fb);
+ if (fs != NULL)
+ fclose(fs);
+ if (fw != NULL)
+ fclose(fw);
+ return 1;
+}
+
+PUBLIC int
+DisplayRankedPlayers(int p, int start, int end, int show, int showComp)
+{
+ FILE *fb = NULL;
+ FILE *fs = NULL;
+ FILE *fw = NULL;
+ char Path[MAX_FILENAME_SIZE] = { '\0' };
+ int num = (end - start + 1);
+
+ if (start <= 0)
+ start = 1;
+ if (num <= 0)
+ return 0;
+ if (num > 100)
+ num = 100;
+
+ if (CheckFlag(show, SHOW_BLITZ)) {
+ GetRankFileName(Path, sizeof Path, TYPE_BLITZ);
+
+ if ((fb = fopen(Path, "r")) == NULL)
+ ClearFlag(show, SHOW_BLITZ);
+ }
+ if (CheckFlag(show, SHOW_STANDARD)) {
+ GetRankFileName(Path, sizeof Path, TYPE_STAND);
+
+ if ((fs = fopen(Path, "r")) == NULL)
+ ClearFlag(show, SHOW_STANDARD);
+ }
+ if (CheckFlag(show, SHOW_WILD)) {
+ GetRankFileName(Path, sizeof Path, TYPE_WILD);
+
+ if ((fw = fopen(Path, "r")) == NULL)
+ ClearFlag(show, SHOW_WILD);
+ }
+
+ ShowRankLines(p, fb, fs, fw, start, start, start, num, showComp, show,
+ "");
+
+ if (fb)
+ fclose(fb);
+ if (fs)
+ fclose(fs);
+ if (fw)
+ fclose(fw);
+ return 1;
+}
+
+PUBLIC int
+ShowFromString(char *s)
+{
+ int len = strlen(s ? s : "");
+ int show = 0;
+
+ if (s == NULL || s[0] == '\0')
+ return (SHOW_BLITZ | SHOW_STANDARD | SHOW_WILD);
+
+ for (int i = 0; i < len; i++) {
+ switch (s[i]) {
+ case 'b':
+ SetFlag(show, SHOW_BLITZ);
+ break;
+ case 's':
+ SetFlag(show, SHOW_STANDARD);
+ break;
+ case 'w':
+ SetFlag(show, SHOW_WILD);
+ break;
+ }
+ }
+
+ return show;
+}
+
+PUBLIC int
+Best(int p, param_list param, int ShowComp)
+{
+ int show = (SHOW_BLITZ | SHOW_STANDARD | SHOW_WILD);
+
+ if (param[0].type != TYPE_NULL)
+ show = ShowFromString(param[0].val.word);
+
+ DisplayRankedPlayers(p, 1, 20, show, ShowComp);
+ return COM_OK;
+}
diff --git a/FICS/ratings.h b/FICS/ratings.h
new file mode 100644
index 0000000..7f2a521
--- /dev/null
+++ b/FICS/ratings.h
@@ -0,0 +1,81 @@
+/* ratings.h
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 24/04/02 Revised
+*/
+
+#ifndef _RATINGS_H
+#define _RATINGS_H
+
+#include "common.h"
+
+#define STATS_VERSION 2
+
+#define RESULT_WIN 0
+#define RESULT_DRAW 1
+#define RESULT_LOSS 2
+#define RESULT_ABORT 3
+
+#define PROVISIONAL 20
+
+#define MAX_RANK_LINE 50
+#define MAX_BEST 20
+
+#define SHOW_BLITZ 0x1
+#define SHOW_STANDARD 0x2
+#define SHOW_WILD 0x4
+
+#include "command.h"
+
+typedef struct _rateStruct {
+ char name[MAX_LOGIN_NAME];
+ int rating;
+} rateStruct;
+
+__FICS_BEGIN_DECLS
+extern int Best(int, param_list, int);
+extern int DisplayRank(int, param_list, int);
+extern int DisplayRankedPlayers(int, int, int, int, int);
+extern int DisplayTargetRank(int, char *, int, int);
+extern int ShowFromString(char *);
+extern int com_assess(int, param_list);
+extern int com_best(int, param_list);
+extern int com_fixrank(int, param_list);
+extern int com_hbest(int, param_list);
+extern int com_hrank(int, param_list);
+extern int com_rank(int, param_list);
+extern int com_statistics(int, param_list);
+extern int int_diff(const char *, const int, const int);
+extern int is_active(int);
+extern int rating_delta(int, int, int, int, int);
+extern int rating_update(int);
+extern void BestUpdate(int);
+extern void UpdateRank(int, char *, statistics *, char *);
+extern void rating_add(int, int);
+extern void rating_init(void);
+extern void rating_recalc(void);
+extern void rating_remove(int, int);
+extern void rating_sterr_delta(int, int, int, time_t, int, int *, double *);
+extern void save_ratings(void);
+__FICS_END_DECLS
+
+#endif /* _RATINGS_H */
diff --git a/FICS/rmalloc.c b/FICS/rmalloc.c
new file mode 100644
index 0000000..0a8f46d
--- /dev/null
+++ b/FICS/rmalloc.c
@@ -0,0 +1,97 @@
+/* rmalloc.c
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 23/12/20 Revised
+*/
+
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include "rmalloc.h"
+
+PUBLIC unsigned int allocated_size = 0;
+PUBLIC unsigned int malloc_count = 0;
+PUBLIC unsigned int free_count = 0;
+
+PUBLIC void *
+rmalloc(int byteSize)
+{
+ void *newptr;
+
+#ifdef HASMALLOCSIZE
+ allocated_size += byteSize;
+#endif
+
+ malloc_count++;
+
+ if ((newptr = malloc(byteSize)) == NULL) {
+ fprintf(stderr, "Out of memory in %s()!\n", __func__);
+ abort();
+ }
+
+ return newptr;
+}
+
+PUBLIC void *
+rrealloc(void *ptr, int byteSize)
+{
+#ifdef HASMALLOCSIZE
+ allocated_size += (byteSize - malloc_size(ptr));
+#endif
+
+ if (ptr == NULL) {
+ fprintf(stderr, "Hoser! Null ptr passed to %s()!\n", __func__);
+ return NULL;
+ } else {
+ void *newptr;
+
+ if ((newptr = realloc(ptr, byteSize)) == NULL) {
+ fprintf(stderr, "Out of memory in %s()!\n", __func__);
+ abort();
+ }
+
+ return newptr;
+ }
+}
+
+PUBLIC void
+rfree(void *ptr)
+{
+#ifdef HASMALLOCSIZE
+ allocated_size = (allocated_size - malloc_size(ptr));
+#endif
+
+ if (ptr == NULL) {
+ fprintf(stderr, "Hoser! Null ptr passed to %s()!\n", __func__);
+ } else {
+ free_count++;
+ free(ptr);
+ }
+}
+
+PUBLIC void
+strfree(char *string)
+{
+ if (string != NULL)
+ rfree(string);
+}
diff --git a/FICS/rmalloc.h b/FICS/rmalloc.h
new file mode 100644
index 0000000..eae02c2
--- /dev/null
+++ b/FICS/rmalloc.h
@@ -0,0 +1,38 @@
+/* rmalloc.h
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 23/12/20 Revised
+*/
+
+#ifndef _RMALLOC_H
+#define _RMALLOC_H
+
+extern unsigned int allocated_size;
+extern unsigned int malloc_count;
+extern unsigned int free_count;
+
+extern void *rmalloc(int);
+extern void *rrealloc(void *, int);
+extern void rfree(void *);
+extern void strfree(char *);
+
+#endif /* _RMALLOC_H */
diff --git a/FICS/shutdown.c b/FICS/shutdown.c
new file mode 100644
index 0000000..6e765ed
--- /dev/null
+++ b/FICS/shutdown.c
@@ -0,0 +1,311 @@
+/*
+ * Revised by maxxe <maxxe@rpblc.net>,
+ * 18 Dec 2023.
+ */
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include "command.h"
+#include "network.h"
+#include "playerdb.h"
+#include "shutdown.h"
+#include "utils.h"
+
+#if __linux__
+#include <bsd/string.h>
+#endif
+
+PRIVATE char downer[1024];
+PRIVATE char reason[1024];
+
+PRIVATE time_t lastTimeLeft;
+PRIVATE time_t shutdownStartTime;
+PRIVATE time_t shutdownTime = 0;
+
+PUBLIC void
+output_shut_mess(void)
+{
+ time_t shuttime = time(NULL);
+
+ fprintf(stderr, "FICS: Shutting down at %s\n", strltime(&shuttime));
+}
+
+PUBLIC __dead void
+ShutDown(void)
+{
+ time_t shuttime = time(NULL);
+
+ for (int p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+
+ pprintf(p1, "\n\n **** Server shutdown ordered by %s. "
+ "****\n", downer);
+
+ if (reason[0] != '\0') {
+ pprintf(p1, "\n **** We are going down because: "
+ "%s. ****\n", reason);
+ }
+ }
+
+ TerminateCleanup();
+ fprintf(stderr, "FICS: Shut down ordered at %s by %s.\n",
+ strltime(&shuttime), downer);
+ output_shut_mess();
+ net_close();
+ exit(0);
+}
+
+PUBLIC void
+ShutHeartBeat(void)
+{
+ int crossing = 0;
+ int p1;
+ int timeLeft;
+ time_t t = time(NULL);
+
+ if (!shutdownTime)
+ return;
+ if (!lastTimeLeft)
+ lastTimeLeft = shutdownTime;
+
+ timeLeft = shutdownTime - (t - shutdownStartTime);
+
+ if (lastTimeLeft > 3600 && timeLeft <= 3600)
+ crossing = 1;
+ if (lastTimeLeft > 2400 && timeLeft <= 2400)
+ crossing = 1;
+ if (lastTimeLeft > 1200 && timeLeft <= 1200)
+ crossing = 1;
+ if (lastTimeLeft > 600 && timeLeft <= 600)
+ crossing = 1;
+ if (lastTimeLeft > 300 && timeLeft <= 300)
+ crossing = 1;
+ if (lastTimeLeft > 120 && timeLeft <= 120)
+ crossing = 1;
+ if (lastTimeLeft > 60 && timeLeft <= 60)
+ crossing = 1;
+ if (lastTimeLeft > 10 && timeLeft <= 10)
+ crossing = 1;
+
+ if (crossing) {
+ fprintf(stderr, "FICS: **** Server going down in %d minutes "
+ "and %d seconds. ****\n\n",
+ (timeLeft / 60),
+ timeLeft - ((timeLeft / 60) * 60));
+
+ if (reason[0] != '\0') {
+ fprintf(stderr,"FICS: We are going down because: %s.\n",
+ reason);
+ }
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+
+ pprintf(p1, "\n\n **** Server going down in %d "
+ "minutes and %d seconds. ****\n",
+ (timeLeft / 60),
+ timeLeft - ((timeLeft / 60) * 60));
+
+ if (reason[0] != '\0') {
+ pprintf_prompt(p1, "\n **** We are going "
+ "down because: %s. ****\n", reason);
+ } else
+ pprintf_prompt(p1, "\n");
+ }
+ }
+
+ lastTimeLeft = timeLeft;
+
+ if (timeLeft <= 0)
+ ShutDown();
+}
+
+/*
+ * Tells a user about a shutdown. Returns 1 if there is to be one, and
+ * 0 otherwise. (For 'whenshut' command.)
+ */
+PUBLIC int
+check_and_print_shutdown(int p)
+{
+ int timeLeft;
+ time_t t = time(NULL);
+
+ if (!shutdownTime)
+ return 0; // no shutdown
+
+ timeLeft = shutdownTime - (t - shutdownStartTime);
+
+ pprintf(p, "\n **** Server going down in %d minutes and %d seconds. "
+ "****\n",
+ (timeLeft / 60),
+ timeLeft - ((timeLeft / 60) * 60));
+
+ if (reason[0] != '\0') {
+ pprintf(p, "\n **** We are going down because: %s. ****\n",
+ reason);
+ }
+ return 1;
+}
+
+
+/*
+ * Usage: shutdown [now,cancel,time]
+ *
+ * This command shuts down the server. If the parameter is omitted or
+ * is 'now' then the server is immediately halted cleanly. If a time
+ * is given then a countdown commences and the server is halted when
+ * time is up. If 'cancel' is given then the countdown is stopped.
+ */
+PUBLIC int
+com_shutdown(int p, param_list param)
+{
+ char *ptr;
+ int p1, secs;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+ strlcpy(downer, parray[p].name, sizeof downer);
+ shutdownStartTime = time(NULL);
+
+ if (shutdownTime) { // Cancel any pending shutdowns
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ pprintf(p1, "\n\n **** Server shutdown canceled by "
+ "%s. ****\n", downer);
+ }
+
+ shutdownTime = 0;
+
+ if (param[0].type == TYPE_NULL)
+ return COM_OK;
+ }
+
+ /*
+ * Work out how soon to shut down
+ */
+ if (param[0].type == TYPE_NULL) {
+ shutdownTime = 300;
+ } else {
+ if (!strcmp(param[0].val.word, "now")) {
+ shutdownTime = 0;
+ } else if (!strcmp(param[0].val.word, "die")) {
+ fprintf(stderr,"%s salutes FICS and presses the "
+ "self-destruct button.\n", parray[p].name);
+ output_shut_mess();
+ abort();
+ } else if (!strcmp(param[0].val.word, "cancel")) {
+ return COM_OK;
+ } else {
+ ptr = param[0].val.word;
+ shutdownTime = secs = 0;
+ p1 = 2;
+
+ while (*ptr) {
+ switch (*ptr) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ secs = (secs * 10 + *ptr - '0');
+ break;
+ case ':':
+ if (p1--) {
+ shutdownTime = (shutdownTime *
+ 60 + secs);
+ secs = 0;
+ break;
+ }
+ default:
+ shutdownTime = 0;
+ pprintf(p, "I don't know what you mean "
+ "by %s\n", param[0].val.word);
+ return COM_OK;
+ }
+
+ ptr++;
+ }
+
+ shutdownTime = (shutdownTime * 60 + secs);
+ }
+ }
+
+ if (shutdownTime <= 0)
+ ShutDown();
+ if (param[1].type == TYPE_STRING)
+ strlcpy(reason, param[1].val.string, sizeof reason);
+ else
+ reason[0] = '\0'; // No reason - perhaps admin is in a
+ // bad mood? :)
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+
+ pprintf(p1, "\n\n **** Server shutdown ordered by %s. "
+ "****\n", downer);
+
+ if (reason[0] != '\0') {
+ pprintf(p1, " **** We are going down because: "
+ "%s. ****\n", reason);
+ }
+
+ pprintf(p1, " **** Server going down in %ld minutes and %ld "
+ "seconds. ****\n",
+ (long int)(shutdownTime / 60),
+ (long int)(shutdownTime % 60));
+ if (p != p1) // fix double prompt - DAV
+ pprintf_prompt(p1, "\n");
+ else
+ pprintf(p1, "\n");
+ }
+
+ lastTimeLeft = 0;
+ return COM_OK;
+}
+
+PUBLIC int
+server_shutdown(int secs, char *why)
+{
+ if (shutdownTime && shutdownTime <= secs) {
+ /*
+ * Server is already shutting down
+ */
+ return 0;
+ }
+
+ strlcpy(downer, "Automatic", sizeof downer);
+ shutdownTime = secs;
+ shutdownStartTime = time(NULL);
+
+ for (int p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ pprintf(p1, "\n\n **** Automatic Server shutdown. ****\n");
+ pprintf(p1, "%s\n", why);
+ pprintf_prompt(p1, " **** Server going down in %ld minutes "
+ "and %ld seconds. ****\n\n",
+ (long int)(shutdownTime / 60),
+ (long int)(shutdownTime - ((shutdownTime / 60) * 60)));
+ }
+
+ fprintf(stderr, "FICS: **** Automatic Server shutdown. ****\n");
+ fprintf(stderr, "FICS: %s\n", why);
+ return 0;
+}
+
+PUBLIC int
+com_whenshut(int p, param_list param)
+{
+ if (check_and_print_shutdown(p) == 0)
+ pprintf(p, "No shutdown currently in progress\n");
+ return COM_OK;
+}
diff --git a/FICS/shutdown.h b/FICS/shutdown.h
new file mode 100644
index 0000000..8728ecb
--- /dev/null
+++ b/FICS/shutdown.h
@@ -0,0 +1,19 @@
+/* shutdown.h */
+
+#ifndef _SHUTDOWN_H
+#define _SHUTDOWN_H
+
+#include "command.h" /* param_list */
+#include "common.h" /* __dead */
+
+extern int check_and_print_shutdown(int);
+extern int com_shutdown(int, param_list);
+extern int com_whenshut(int, param_list);
+extern int server_shutdown(int, char *);
+
+extern __dead void ShutDown(void);
+
+extern void ShutHeartBeat(void);
+extern void output_shut_mess(void);
+
+#endif /* _SHUTDOWN_H */
diff --git a/FICS/sought.cpp b/FICS/sought.cpp
new file mode 100644
index 0000000..b4dd282
--- /dev/null
+++ b/FICS/sought.cpp
@@ -0,0 +1,41 @@
+// SPDX-FileCopyrightText: 2024 Markus Uhlin <maxxe@rpblc.net>
+// SPDX-License-Identifier: ISC
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include "sought.h"
+
+/*
+ * Usage: sought [all]
+ *
+ * The "sought" command can be used in two ways: (a) typing "sought
+ * all" will display all current ads including your own; (b) typing
+ * "sought" alone will display only those current ads for which you
+ * are eligible based on any formula you might have (default). An
+ * example output is as follows:
+ *
+ * 0 1900 Hawk blitz 5 0 rated 1800-2000 f
+ * 1 1700 Friar wild7 2 12 unrated [white] 0-9999
+ * 4 1500 loon standard 5 0 unrated 0-9999 m
+ *
+ * The various columns have this information:
+ *
+ * Ad index number
+ * Player's rating
+ * Player's handle
+ * Type of chess match
+ * Time at start
+ * Increment per move
+ * Rated/unrated
+ * Color (if specified)
+ * Rating range
+ * Auto start/manual start and whether formula will be checked
+ */
+PUBLIC int
+com_sought(int p, param_list param)
+{
+ UNUSED_PARAM(p);
+ UNUSED_PARAM(param);
+ return COM_OK;
+}
diff --git a/FICS/sought.h b/FICS/sought.h
new file mode 100644
index 0000000..873435c
--- /dev/null
+++ b/FICS/sought.h
@@ -0,0 +1,11 @@
+#ifndef COM_SOUGHT_H
+#define COM_SOUGHT_H
+
+#include "command.h" /* param_list */
+#include "common.h"
+
+__FICS_BEGIN_DECLS
+int com_sought(int, param_list);
+__FICS_END_DECLS
+
+#endif
diff --git a/FICS/talkproc.c b/FICS/talkproc.c
new file mode 100644
index 0000000..601f43b
--- /dev/null
+++ b/FICS/talkproc.c
@@ -0,0 +1,1086 @@
+/* talkproc.c
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name yy/mm/dd Change
+ hersco and Marsalis 95/07/24 Created
+ Sparky 95/10/05
+ Modifed tell function for the following
+ changes: no longer informs you that someone
+ is censoring in tells to channels, whisper or
+ kibitz. Titles now shown instead of ratings
+ in kibitz and whisper, admins on duty now are
+ shown as (*), and computers marked as (C) as
+ well as with rating.
+ Markus Uhlin 23/12/10 Cleaned up the file
+ Markus Uhlin 23/12/10 Fixed compiler warnings
+ Markus Uhlin 24/01/04 Fixed pprintf() calls
+ Markus Uhlin 24/04/14 Refactored and reformatted ALL
+ functions.
+ Markus Uhlin 25/01/18 Fixed -Wshadow
+ Markus Uhlin 25/04/04 tell: fixed constant expression
+ result
+*/
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include "command.h"
+#include "comproc.h"
+#include "formula.h"
+#include "gamedb.h"
+#include "gameproc.h"
+#include "lists.h"
+#include "maxxes-utils.h"
+#include "network.h"
+#include "playerdb.h"
+#include "talkproc.h"
+#include "utils.h"
+
+#include <sys/resource.h>
+
+#define TELL_TELL 0
+#define TELL_SAY 1
+#define TELL_WHISPER 2
+#define TELL_KIBITZ 3
+#define TELL_CHANNEL 4
+
+int quota_time;
+
+// hawk: hacked it to fit ALL persons - quota list is not needed anymore
+PRIVATE int
+CheckShoutQuota(int p)
+{
+ time_t timeleft = 0;
+ time_t timenow = time(NULL);
+
+ timeleft = timenow - parray[p].lastshout_a;
+
+ if (timeleft < quota_time && parray[p].adminLevel == 0)
+ return (quota_time - timeleft);
+ return 0;
+}
+
+PUBLIC int
+com_shout(int p, param_list param)
+{
+ int count = 0;
+ int p1;
+ int timeleft; // time left for quota if applicable
+
+ if (!parray[p].registered) {
+ pprintf(p, "Only registered players can use the shout "
+ "command.\n");
+ return COM_OK;
+ }
+
+ if (in_list(p, L_MUZZLE, parray[p].login)) {
+ pprintf(p, "You are muzzled.\n");
+ return COM_OK;
+ }
+
+ if (param[0].type == TYPE_NULL) {
+ if ((timeleft = CheckShoutQuota(p))) {
+ pprintf(p, "Next shout available in %d seconds.\n",
+ timeleft);
+ } else {
+ pprintf(p, "Your next shout is ready for use.\n");
+ }
+
+ return COM_OK;
+ }
+
+ if ((timeleft = CheckShoutQuota(p))) {
+ pprintf(p, "Shout not sent. Next shout in %d seconds.\n",
+ timeleft);
+ return COM_OK;
+ }
+
+ parray[p].lastshout_a = parray[p].lastshout_b;
+ parray[p].lastshout_b = time(NULL);
+
+ if (!printablestring(param[0].val.string)) {
+ pprintf(p, "Your message contains some unprintable "
+ "character(s).\n");
+ return COM_OK;
+ }
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (p1 == p)
+ continue;
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ if (!parray[p1].i_shout)
+ continue;
+ if (player_censored(p1, p))
+ continue;
+
+ count++;
+
+ pprintf_prompt(p1, "\n%s shouts: %s\n",
+ parray[p].name,
+ param[0].val.string);
+ }
+
+ pprintf(p, "(%d) %s shouts: %s\n", count, parray[p].name,
+ param[0].val.string);
+
+ if ((timeleft = CheckShoutQuota(p))) {
+ pprintf(p, "Next shout in %d seconds.\n", timeleft);
+ return COM_OK;
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_cshout(int p, param_list param)
+{
+ int count = 0;
+ int p1;
+
+ if (!parray[p].registered) {
+ pprintf(p, "Only registered players can use the cshout "
+ "command.\n");
+ return COM_OK;
+ }
+
+ if (in_list(p, L_CMUZZLE, parray[p].login)) {
+ pprintf(p, "You are c-muzzled.\n");
+ return COM_OK;
+ }
+
+ if (!printablestring(param[0].val.string)) {
+ pprintf(p, "Your message contains some unprintable "
+ "character(s).\n");
+ return COM_OK;
+ }
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (p1 == p)
+ continue;
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ if (!parray[p1].i_cshout)
+ continue;
+ if (player_censored(p1, p))
+ continue;
+
+ count++;
+
+ pprintf_prompt(p1, "\n%s c-shouts: %s\n",
+ parray[p].name,
+ param[0].val.string);
+ }
+
+ pprintf(p, "(%d) %s c-shouts: %s\n", count, parray[p].name,
+ param[0].val.string);
+ return COM_OK;
+}
+
+PUBLIC int
+com_it(int p, param_list param)
+{
+ int count = 0;
+ int p1;
+ int timeleft;
+
+ if (!parray[p].registered) {
+ pprintf(p, "Only registered players can use the it command.\n");
+ return COM_OK;
+ }
+
+ if (in_list(p, L_MUZZLE, parray[p].login)) {
+ pprintf(p, "You are muzzled.\n");
+ return COM_OK;
+ }
+
+ if (param[0].type == TYPE_NULL) {
+ if ((timeleft = CheckShoutQuota(p))) {
+ pprintf(p, "Next shout available in %d seconds.\n",
+ timeleft);
+ } else {
+ pprintf(p, "Your next shout is ready for use.\n");
+ }
+
+ return COM_OK;
+ }
+
+ if ((timeleft = CheckShoutQuota(p))) {
+ pprintf(p, "Shout not sent. Next shout in %d seconds.\n",
+ timeleft);
+ return COM_OK;
+ }
+
+ parray[p].lastshout_a = parray[p].lastshout_b;
+ parray[p].lastshout_b = time(NULL);
+
+ if (!printablestring(param[0].val.string)) {
+ pprintf(p, "Your message contains some unprintable "
+ "character(s).\n");
+ return COM_OK;
+ }
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (p1 == p)
+ continue;
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ if (!parray[p1].i_shout)
+ continue;
+ if (player_censored(p1, p))
+ continue;
+
+ count++;
+
+ if (!strncmp(param[0].val.string, "\'", 1) ||
+ !strncmp(param[0].val.string, ",", 1) ||
+ !strncmp(param[0].val.string, ".", 1)) {
+ pprintf_prompt(p1, "\n--> %s%s\n", parray[p].name,
+ param[0].val.string);
+ } else {
+ pprintf_prompt(p1, "\n--> %s %s\n", parray[p].name,
+ param[0].val.string);
+ }
+ }
+
+ if (!strncmp(param[0].val.string, "\'", 1) ||
+ !strncmp(param[0].val.string, ",", 1) ||
+ !strncmp(param[0].val.string, ".", 1)) {
+ pprintf(p, "(%d) --> %s%s\n", count, parray[p].name,
+ param[0].val.string);
+ } else {
+ pprintf(p, "(%d) --> %s %s\n", count, parray[p].name,
+ param[0].val.string);
+ }
+
+ if ((timeleft = CheckShoutQuota(p))) {
+ pprintf(p, "Next shout in %d seconds.\n", timeleft);
+ return COM_OK;
+ }
+
+ return COM_OK;
+}
+
+PRIVATE int
+tell(int p, int p1, char *msg, int why, int ch)
+{
+ char tmp[MAX_LINE_SIZE] = { '\0' };
+ int rating1;
+ int rating;
+
+ if (!printablestring(msg)) {
+ pprintf(p, "Your message contains some unprintable "
+ "character(s).\n");
+ return COM_OK;
+ }
+
+ if ((!parray[p1].i_tell) && (!parray[p].registered)) {
+ pprintf(p, "Player \"%s\" isn't listening to unregistered "
+ "tells.\n", parray[p1].name);
+ return COM_OK;
+ }
+
+ if (player_censored(p1, p) && parray[p].adminLevel == 0) {
+ if (why != TELL_KIBITZ &&
+ why != TELL_WHISPER &&
+ why != TELL_CHANNEL) {
+ pprintf(p, "Player \"%s\" is censoring you.\n",
+ parray[p1].name);
+ }
+
+ return COM_OK;
+ }
+
+ switch (why) {
+ case TELL_SAY:
+ pprintf_highlight(p1, "\n%s", parray[p].name);
+ pprintf_prompt(p1, " says: %s\n", msg);
+ break;
+ case TELL_WHISPER:
+ case TELL_KIBITZ:
+ rating = GetRating(&parray[p], TYPE_BLITZ);
+
+ if (rating < (rating1 = (GetRating(&parray[p], TYPE_STAND))))
+ rating = rating1;
+
+ if (in_list(p, L_FM, parray[p].name)) {
+ pprintf(p1, "\n%s(FM)", parray[p].name);
+ } else if (in_list(p, L_IM, parray[p].name)) {
+ pprintf(p1, "\n%s(IM)", parray[p].name);
+ } else if (in_list(p, L_GM, parray[p].name)) {
+ pprintf(p1, "\n%s(GM)", parray[p].name);
+ } else if (parray[p].adminLevel >= 10 && parray[p].i_admin) {
+ pprintf(p1, "\n%s(*)", parray[p].name);
+ } else if ((rating >= parray[p1].kiblevel) ||
+ ((parray[p].adminLevel >= 10) && (parray[p].i_admin))) {
+ if (!parray[p].registered) {
+ pprintf(p1, "\n%s(++++)", parray[p].name);
+ } else if (rating != 0) {
+ if (in_list(p, L_COMPUTER, parray[p].name)) {
+ pprintf(p1, "\n%s(%d)(C)",
+ parray[p].name,
+ rating);
+ } else {
+ pprintf(p1, "\n%s(%d)",
+ parray[p].name,
+ rating);
+ }
+ } else {
+ pprintf(p1, "\n%s(----)", parray[p].name);
+ }
+ } else {
+ break;
+ }
+
+ if (why == TELL_WHISPER)
+ pprintf_prompt(p1, " whispers: %s\n", msg);
+ else
+ pprintf_prompt(p1, " kibitzes: %s\n", msg);
+ break;
+ case TELL_CHANNEL:
+ pprintf(p1, "\n%s", parray[p].name);
+ pprintf_prompt(p1, "(%d): %s\n", ch, msg);
+ break;
+ case TELL_TELL:
+ default:
+ if (parray[p1].highlight) {
+ pprintf_highlight(p1, "\n%s", parray[p].name);
+ } else {
+ pprintf(p1, "\n%s", parray[p].name);
+ }
+
+ pprintf_prompt(p1, " tells you: %s\n", msg);
+ break;
+ }
+
+ tmp[0] = '\0';
+
+ if (!(parray[p1].busy[0] == '\0')) {
+ msnprintf(tmp, sizeof tmp, ", who %s (idle: %s)",
+ parray[p1].busy,
+ hms_desc(player_idle(p1)));
+ } else {
+ if (((player_idle(p1) % 3600) / 60) > 2) {
+ msnprintf(tmp, sizeof tmp, ", who has been idle %s",
+ hms_desc(player_idle(p1)));
+ }
+ }
+
+ if (why == TELL_SAY || why == TELL_TELL) {
+ pprintf(p, "(told %s%s)\n", parray[p1].name,
+ (((parray[p1].game >= 0) &&
+ (garray[parray[p1].game].status == GAME_EXAMINE)) ?
+ ", who is examining a game" :
+ (parray[p1].game >= 0 &&
+ (parray[p1].game != parray[p].game)) ?
+ ", who is playing" : tmp));
+ parray[p].last_tell = p1;
+ }
+
+ return COM_OK;
+}
+
+/*
+ * Tells partner - doesn't change last tell.
+ */
+PUBLIC int
+com_ptell(int p, param_list param)
+{
+ char tmp[MAX_LINE_SIZE];
+ int p1;
+
+ if (parray[p].partner < 0) {
+ pprintf(p, "You do not have a partner at present.\n");
+ return COM_OK;
+ }
+
+ p1 = parray[p].partner;
+
+ if (p1 < 0 ||
+ parray[p1].status == PLAYER_PASSWORD ||
+ parray[p1].status == PLAYER_LOGIN) {
+ pprintf(p, "Your partner is not logged in.\n");
+ return COM_OK;
+ }
+
+ if (parray[p1].highlight) {
+ pprintf_highlight(p1, "\n%s", parray[p].name);
+ } else {
+ pprintf(p1, "\n%s", parray[p].name);
+ }
+
+ pprintf_prompt(p1, " (your partner) tells you: %s\n",
+ param[0].val.string);
+ tmp[0] = '\0';
+
+ if (!(parray[p1].busy[0] == '\0')) {
+ msnprintf(tmp, sizeof tmp, ", who %s (idle: %s)",
+ parray[p1].busy,
+ hms_desc(player_idle(p1)));
+ } else {
+ if (((player_idle(p1) % 3600) / 60) > 2) {
+ msnprintf(tmp, sizeof tmp, ", who has been idle %s",
+ hms_desc(player_idle(p1)));
+ }
+ }
+
+ pprintf(p, "(told %s%s)\n", parray[p1].name,
+ (((parray[p1].game >= 0) &&
+ (garray[parray[p1].game].status == GAME_EXAMINE)) ?
+ ", who is examining a game" :
+ (parray[p1].game >= 0 &&
+ (parray[p1].game != parray[p].game)) ?
+ ", who is playing" : tmp));
+ return COM_OK;
+}
+
+PRIVATE int
+chtell(int p, int ch, char *msg)
+{
+ int p1, count = 0;
+
+ if (ch == 0 && parray[p].adminLevel == 0) {
+ pprintf(p, "Only admins may send tells to channel 0.\n");
+ return COM_OK;
+ }
+
+ if (ch < 0) {
+ pprintf(p, "The lowest channel number is 0.\n");
+ return COM_OK;
+ }
+
+ if (ch >= MAX_CHANNELS) {
+ pprintf(p, "The maximum channel number is %d.\n",
+ (MAX_CHANNELS - 1));
+ return COM_OK;
+ }
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (p1 == p || parray[p1].status != PLAYER_PROMPT)
+ continue;
+ if (on_channel(ch, p1) && !player_censored(p1, p)) {
+ tell(p, p1, msg, TELL_CHANNEL, ch);
+ count++;
+ }
+ }
+
+ if (count)
+ parray[p].last_channel = ch;
+
+ pprintf(p, "(%d->(%d))\n", ch, count);
+
+ if (!on_channel(ch, p))
+ pprintf(p, " (You're not listening to channel %d.)\n", ch);
+ return COM_OK;
+}
+
+PUBLIC int
+com_whisper(int p, param_list param)
+{
+ int count = 0;
+ int g;
+ int p1;
+
+ if (!parray[p].num_observe && parray[p].game < 0) {
+ pprintf(p, "You are not playing or observing a game.\n");
+ return COM_OK;
+ }
+
+ if (!parray[p].registered && (parray[p].game == -1)) {
+ pprintf(p, "You must be registered to whisper other people's "
+ "games.\n");
+ return COM_OK;
+ }
+
+ if (parray[p].game >= 0)
+ g = parray[p].game;
+ else
+ g = parray[p].observe_list[0];
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (p1 == p)
+ continue;
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ if (player_is_observe(p1, g) ||
+ (garray[g].link >= 0 &&
+ player_is_observe(p1, garray[g].link))) {
+ tell(p, p1, param[0].val.string, TELL_WHISPER, 0);
+
+ if (parray[p].adminLevel >= ADMIN_ADMIN ||
+ !garray[g].private)
+ count++;
+ }
+ }
+
+ pprintf(p, "whispered to %d.\n", count);
+ return COM_OK;
+}
+
+PUBLIC int
+com_kibitz(int p, param_list param)
+{
+ int count = 0;
+ int g;
+ int p1;
+
+ if (!parray[p].num_observe && parray[p].game < 0) {
+ pprintf(p, "You are not playing or observing a game.\n");
+ return COM_OK;
+ }
+
+ if (!parray[p].registered && (parray[p].game == -1)) {
+ pprintf(p, "You must be registered to kibitz other people's "
+ "games.\n");
+ return COM_OK;
+ }
+
+ if (parray[p].game >= 0)
+ g = parray[p].game;
+ else
+ g = parray[p].observe_list[0];
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (p1 == p)
+ continue;
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ if ((player_is_observe(p1, g) ||
+ parray[p1].game == g ||
+ (garray[g].link >= 0 &&
+ (player_is_observe(p1, garray[g].link) ||
+ parray[p1].game == garray[g].link))) &&
+ parray[p1].i_kibitz) {
+ tell(p, p1, param[0].val.string, TELL_KIBITZ, 0);
+
+ if (parray[p].adminLevel >= ADMIN_ADMIN ||
+ !garray[g].private ||
+ parray[p1].game == g)
+ count++;
+ }
+ }
+
+ pprintf(p, "kibitzed to %d.\n", count);
+ return COM_OK;
+}
+
+PUBLIC int
+com_tell(int p, param_list param)
+{
+ int p1;
+
+ if (param[0].type == TYPE_NULL)
+ return COM_BADPARAMETERS;
+
+ if (param[0].type == TYPE_WORD) {
+ stolower(param[0].val.word);
+
+ if (!strcmp(param[0].val.word, ".")) {
+ if (parray[p].last_tell < 0) {
+ pprintf(p, "No one to tell anything to.\n");
+ return COM_OK;
+ } else {
+ return tell(p, parray[p].last_tell,
+ param[1].val.string, TELL_TELL, 0);
+ }
+ }
+
+ if (!strcmp(param[0].val.word, ",")) {
+ if (parray[p].last_channel < 0) {
+ pprintf(p, "No previous channel.\n");
+ return COM_OK;
+ } else {
+ return chtell(p, parray[p].last_channel,
+ param[1].val.string);
+ }
+ }
+
+ p1 = player_find_part_login(param[0].val.word);
+
+ if (p1 < 0 ||
+ parray[p1].status == PLAYER_PASSWORD ||
+ parray[p1].status == PLAYER_LOGIN) {
+ pprintf(p, "No user named \"%s\" is logged in.\n",
+ param[0].val.word);
+ return COM_OK;
+ }
+
+ return tell(p, p1, param[1].val.string, TELL_TELL, 0);
+ } else { // Channel
+ return chtell(p, param[0].val.integer, param[1].val.string);
+ }
+}
+
+PUBLIC int
+com_xtell(int p, param_list param)
+{
+ char *msg;
+ char tmp[2048];
+ int p1;
+
+ msg = param[1].val.string;
+ p1 = player_find_part_login(param[0].val.word);
+
+ if (p1 < 0 ||
+ parray[p1].status == PLAYER_PASSWORD ||
+ parray[p1].status == PLAYER_LOGIN) {
+ pprintf(p, "No user named \"%s\" is logged in.\n",
+ param[0].val.word);
+ return COM_OK;
+ }
+
+ if (!printablestring(msg)) {
+ pprintf(p, "Your message contains some unprintable "
+ "character(s).\n");
+ return COM_OK;
+ }
+
+ if ((!parray[p1].i_tell) && (!parray[p].registered)) {
+ pprintf(p, "Player \"%s\" isn't listening to unregistered "
+ "tells.\n", parray[p1].name);
+ return COM_OK;
+ }
+
+ if (player_censored(p1, p) && parray[p].adminLevel == 0) {
+ pprintf(p, "Player \"%s\" is censoring you.\n",
+ parray[p1].name);
+ return COM_OK;
+ }
+
+ if (parray[p1].highlight) {
+ pprintf_highlight(p1, "\n%s", parray[p].name);
+ } else {
+ pprintf(p1, "\n%s", parray[p].name);
+ }
+
+ pprintf_prompt(p1, " tells you: %s\n", msg);
+ tmp[0] = '\0';
+
+ if (!(parray[p1].busy[0] == '\0')) {
+ msnprintf(tmp, sizeof tmp, ", who %s (idle: %s)",
+ parray[p1].busy,
+ hms_desc(player_idle(p1)));
+ } else {
+ if (((player_idle(p1) % 3600) / 60) > 2) {
+ msnprintf(tmp, sizeof tmp, ", who has been idle %s",
+ hms_desc(player_idle(p1)));
+ }
+ }
+
+ pprintf(p, "(told %s%s)\n", parray[p1].name,
+ ((parray[p1].game >= 0 &&
+ garray[parray[p1].game].status == GAME_EXAMINE) ?
+ ", who is examining a game" :
+ (parray[p1].game >= 0 && parray[p1].game != parray[p].game) ?
+ ", who is playing" : tmp));
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_say(int p, param_list param)
+{
+ if (parray[p].opponent < 0) {
+ if (parray[p].last_opponent < 0) {
+ pprintf(p, "No one to say anything to, try tell.\n");
+ return COM_OK;
+ } else {
+ return tell(p, parray[p].last_opponent,
+ param[0].val.string, TELL_SAY, 0);
+ }
+ }
+
+ return tell(p, parray[p].opponent, param[0].val.string, TELL_SAY, 0);
+}
+
+PUBLIC int
+com_inchannel(int p, param_list param)
+{
+ char tmp[18];
+ int p1, count = 0;
+
+ if (param[0].type == TYPE_NULL) {
+ pprintf(p, "inchannel [no params] has been removed\n");
+ pprintf(p, "Please use inchannel [name] or inchannel"
+ "[number]\n");
+ return COM_OK;
+ }
+
+ if (param[0].type == TYPE_WORD) {
+ p1 = player_find_part_login(param[0].val.word);
+
+ if ((p1 < 0) || (parray[p1].status != PLAYER_PROMPT)) {
+ pprintf(p, "No user named \"%s\" is logged in.\n",
+ param[0].val.word);
+ return COM_OK;
+ }
+
+ pprintf(p, "%s is in the following channels:", parray[p1].name);
+
+ if (list_channels(p, p1))
+ pprintf(p, " No channels found.\n");
+ return COM_OK;
+ } else {
+ msnprintf(tmp, sizeof tmp, "%d", param[0].val.integer);
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ if (in_list(p1, L_CHANNEL, tmp)) {
+ if (!count) {
+ pprintf(p, "Channel %d:",
+ param[0].val.integer);
+ }
+
+ pprintf(p, " %s%s", parray[p1].name,
+ (((parray[p1].adminLevel >= 10) &&
+ (parray[p1].i_admin) &&
+ (param[0].val.integer < 2)) ? "(*)" : ""));
+
+ count++;
+ }
+ }
+
+ if (!count) {
+ pprintf(p, "Channel %d is empty.\n",
+ param[0].val.integer);
+ } else {
+ pprintf(p, "\n%d %s in channel %d.\n", count,
+ (count == 1 ? "person is" : "people are"),
+ param[0].val.integer);
+ }
+
+ return COM_OK;
+ }
+}
+
+PUBLIC int
+com_sendmessage(int p, param_list param)
+{
+ int p1, connected = 1;
+
+ if (!parray[p].registered) {
+ pprintf(p, "You are not registered and cannot send messages."
+ "\n");
+ return COM_OK;
+ }
+
+ if (param[0].type == TYPE_NULL || param[1].type == TYPE_NULL) {
+ pprintf(p, "No message sent.\n");
+ return COM_OK;
+ }
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+
+ if (!parray[p1].registered) {
+ pprintf(p, "Player \"%s\" is unregistered and cannot receive "
+ "messages.\n", parray[p1].name);
+ return COM_OK;
+ }
+
+ if (player_censored(p1, p) && parray[p].adminLevel == 0) {
+ pprintf(p, "Player \"%s\" is censoring you.\n",
+ parray[p1].name);
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ if (player_add_message(p1, p, param[1].val.string)) {
+ pprintf(p, "Couldn't send message to %s. "
+ "Message buffer full.\n", parray[p1].name);
+ } else {
+ if (connected) {
+ pprintf(p1, "\n%s just sent you a message:\n",
+ parray[p].name);
+ pprintf_prompt(p1, " %s\n", param[1].val.string);
+ }
+ }
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+}
+
+PUBLIC int
+com_messages(int p, param_list param)
+{
+ int start;
+
+ if (!parray[p].registered) {
+ pprintf(p, "Unregistered players may not send or receive "
+ "messages.\n");
+ return COM_OK;
+ }
+
+ if (param[0].type == TYPE_NULL) {
+ player_show_messages(p);
+ } else if (param[0].type == TYPE_WORD) {
+ if (param[1].type != TYPE_NULL)
+ return com_sendmessage(p, param);
+ else
+ ShowMsgsBySender(p, param);
+ } else {
+ start = param[0].val.integer;
+ ShowMsgRange(p, start, start);
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_clearmessages(int p, param_list param)
+{
+ int start;
+
+ if (player_num_messages(p) <= 0) {
+ pprintf(p, "You have no messages.\n");
+ return COM_OK;
+ }
+
+ if (param[0].type == TYPE_NULL) {
+ pprintf(p, "Messages cleared.\n");
+ player_clear_messages(p);
+ } else if (param[0].type == TYPE_WORD) {
+ ClearMsgsBySender(p, param);
+ } else if (param[0].type == TYPE_INT) {
+ start = param[0].val.integer;
+ ClrMsgRange(p, start, start);
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_mailmess(int p, param_list param)
+{
+ char *buffer[1000];
+ char filename[MAX_FILENAME_SIZE];
+ char fname[MAX_FILENAME_SIZE];
+ char mdir[MAX_FILENAME_SIZE];
+ char subj[120];
+ int ret, too_long;
+
+ if (!parray[p].registered) {
+ pprintf(p, "Only registered people can use the mailmess "
+ "command.\n");
+ return COM_OK;
+ }
+
+ snprintf(filename, sizeof filename, "%s.messages", parray[p].login);
+ snprintf(mdir, sizeof mdir, "%s/player_data/%c/", stats_dir,
+ parray[p].login[0]);
+
+ if (search_directory(mdir, filename, buffer, ARRAY_SIZE(buffer))) {
+ snprintf(subj, sizeof subj, "Your FICS messages from server %s",
+ fics_hostname);
+
+ ret = snprintf(fname, sizeof fname, "%s/%s", mdir, filename);
+ too_long = (ret < 0 || (size_t)ret >= sizeof fname);
+
+ if (!too_long) {
+ mail_file_to_user(p, subj, fname);
+ pprintf(p, "Messages sent to %s\n",
+ parray[p].emailAddress);
+ }
+ } else {
+ pprintf(p, "You have no messages.\n");
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_znotify(int p, param_list param)
+{
+ int p1, count = 0;
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (player_notified(p, p1)) {
+ if (!count) {
+ pprintf(p, "Present company on your notify "
+ "list:\n ");
+ }
+
+ pprintf(p, " %s", parray[p1].name);
+ count++;
+ }
+ }
+
+ if (count)
+ pprintf(p, ".\n");
+ else
+ pprintf(p, "No one from your notify list is logged on.\n");
+
+ count = 0;
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (player_notified(p1, p) &&
+ parray[p1].status == PLAYER_PROMPT) {
+ if (!count) {
+ pprintf(p, "The following players have you "
+ "on their notify list:\n ");
+ }
+
+ pprintf(p, " %s", parray[p1].name);
+ count++;
+ }
+ }
+
+ if (count)
+ pprintf(p, ".\n");
+ else
+ pprintf(p, "No one logged in has you on their notify list.\n");
+ return COM_OK;
+}
+
+PUBLIC int
+com_qtell(int p, param_list param)
+{
+ char buffer1[MAX_STRING_LENGTH]; // no highlight and no bell
+ char buffer2[MAX_STRING_LENGTH]; // no highlight and bell
+ char buffer3[MAX_STRING_LENGTH]; // highlight and no bell
+ char buffer4[MAX_STRING_LENGTH]; // highlight and and bell
+ char dummy[2];
+ char tmp[MAX_STRING_LENGTH];
+ int i, count;
+ int p1;
+
+ if (!in_list(p, L_TD, parray[p].name)) {
+ pprintf(p, "Only TD programs are allowed to use this command."
+ "\n");
+ return COM_OK;
+ }
+
+ mstrlcpy(buffer1, ":", sizeof buffer1);
+ mstrlcpy(buffer2, ":", sizeof buffer2);
+ mstrlcpy(buffer3, ":", sizeof buffer3);
+ mstrlcpy(buffer4, ":", sizeof buffer4);
+
+ msnprintf(tmp, sizeof tmp, "%s", param[1].val.string);
+
+ count = 0;
+ i = 0;
+ while (tmp[i] != '\0' && count < 1029) {
+ if (tmp[i] == '\\' && tmp[i + 1] == 'n') {
+ mstrlcat(buffer1, "\n:", sizeof buffer1);
+ mstrlcat(buffer2, "\n:", sizeof buffer2);
+ mstrlcat(buffer3, "\n:", sizeof buffer3);
+ mstrlcat(buffer4, "\n:", sizeof buffer4);
+
+ count += 2;
+ i += 2;
+ } else if (tmp[i] == '\\' && tmp[i + 1] == 'b') {
+ mstrlcat(buffer2, "\007", sizeof buffer2);
+ mstrlcat(buffer4, "\007", sizeof buffer4);
+
+ count++;
+ i += 2;
+ } else if (tmp[i] == '\\' && tmp[i + 1] == 'H') {
+ mstrlcat(buffer3, "\033[7m", sizeof buffer3);
+ mstrlcat(buffer4, "\033[7m", sizeof buffer4);
+
+ count += 4;
+ i += 2;
+ } else if (tmp[i] == '\\' && tmp[i + 1] == 'h') {
+ mstrlcat(buffer3, "\033[0m", sizeof buffer3);
+ mstrlcat(buffer4, "\033[0m", sizeof buffer4);
+
+ count += 4;
+ i += 2;
+ } else {
+ dummy[0] = tmp[i];
+ dummy[1] = '\0';
+
+ mstrlcat(buffer1, dummy, sizeof buffer1);
+ mstrlcat(buffer2, dummy, sizeof buffer2);
+ mstrlcat(buffer3, dummy, sizeof buffer3);
+ mstrlcat(buffer4, dummy, sizeof buffer4);
+
+ count++;
+ i++;
+ }
+ } // while
+
+ if (param[0].type == TYPE_WORD) {
+ if ((p1 = player_find_bylogin(param[0].val.word)) < 0) {
+ pprintf(p, "*qtell %s 1*\n", param[0].val.word);
+ return COM_OK;
+ }
+
+ pprintf_prompt(p1, "\n%s\n",
+ (parray[p1].highlight && parray[p1].bell) ?
+ buffer4 :
+ (parray[p1].highlight && !parray[p1].bell) ?
+ buffer3 :
+ (!parray[p1].highlight && parray[p1].bell) ?
+ buffer2 : buffer1);
+ pprintf(p, "*qtell %s 0*\n", parray[p1].name);
+ } else {
+ int ch = param[0].val.integer;
+
+ if (ch == 0) {
+ pprintf(p, "*qtell %d 1*\n", param[0].val.integer);
+ return COM_OK;
+ }
+ if (ch < 0) {
+ pprintf(p, "*qtell %d 1*\n", param[0].val.integer);
+ return COM_OK;
+ }
+ if (ch >= MAX_CHANNELS) {
+ pprintf(p, "*qtell %d 1*\n", param[0].val.integer);
+ return COM_OK;
+ }
+
+ msnprintf(tmp, sizeof tmp, "%d", param[0].val.integer);
+
+ for (p1 = 0; p1 < p_num ; p1++) {
+ if (p1 == p || player_censored(p1, p) ||
+ parray[p1].status != PLAYER_PROMPT)
+ continue;
+ if (in_list(p1, L_CHANNEL, tmp)) {
+ pprintf_prompt(p1, "\n%s\n",
+ (parray[p1].highlight && parray[p1].bell) ?
+ buffer4 :
+ (parray[p1].highlight && !parray[p1].bell) ?
+ buffer3 :
+ (!parray[p1].highlight && parray[p1].bell) ?
+ buffer2 : buffer1);
+ }
+ }
+
+ pprintf(p, "*qtell %d 0*\n", param[0].val.integer);
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+on_channel(int ch, int p)
+{
+ char tmp[20];
+
+ snprintf(tmp, sizeof tmp, "%d", ch);
+ return in_list(p, L_CHANNEL, tmp); // since needs 'ch' converted
+ // to a string keep hidden from
+ // view
+}
diff --git a/FICS/talkproc.h b/FICS/talkproc.h
new file mode 100644
index 0000000..cb53485
--- /dev/null
+++ b/FICS/talkproc.h
@@ -0,0 +1,56 @@
+/* talkproc.h
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name yy/mm/dd Change
+ hersco and Marsalis 95/07/24 Created
+ Markus Uhlin 23/12/10 Deleted declarations
+ Markus Uhlin 23/12/10 Sorted the declarations
+ Markus Uhlin 24/04/14 Added parameter lists
+*/
+
+#ifndef _TALKPROC_H
+#define _TALKPROC_H
+
+#include "command.h" /* param_list */
+
+#define MAX_CHANNELS 256
+
+extern int quota_time;
+
+extern int com_clearmessages(int, param_list);
+extern int com_cshout(int, param_list);
+extern int com_inchannel(int, param_list);
+extern int com_it(int, param_list);
+extern int com_kibitz(int, param_list);
+extern int com_mailmess(int, param_list);
+extern int com_messages(int, param_list);
+extern int com_ptell(int, param_list);
+extern int com_qtell(int, param_list);
+extern int com_say(int, param_list);
+extern int com_sendmessage(int, param_list);
+extern int com_shout(int, param_list);
+extern int com_tell(int, param_list);
+extern int com_whisper(int, param_list);
+extern int com_xtell(int, param_list);
+extern int com_znotify(int, param_list);
+
+extern int on_channel(int, int);
+
+#endif /* _TALKPROC_H */
diff --git a/FICS/utils.c b/FICS/utils.c
new file mode 100644
index 0000000..4da2411
--- /dev/null
+++ b/FICS/utils.c
@@ -0,0 +1,1214 @@
+/* utils.c
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 23/12/10 Fixed compiler warnings (plus more)
+ Markus Uhlin 23/12/10 Deleted check_emailaddr()
+ Markus Uhlin 23/12/17 Reformatted functions
+ Markus Uhlin 23/12/25 Revised
+ Markus Uhlin 24/03/16 Replaced unbounded string
+ handling functions.
+ Markus Uhlin 24/07/05 Fixed unusual struct allocations
+ and replaced strcpy() with strlcpy().
+ Markus Uhlin 24/07/07 Made certain functions private
+ Markus Uhlin 24/07/07 Added parameter 'size' to
+ psprintf_highlight().
+ Markus Uhlin 24/08/11 Improved fix_time().
+ Markus Uhlin 24/12/01 Usage of sizeof and fixed
+ ignored fgets() retvals.
+ Markus Uhlin 24/12/02 Fixed a possible array overrun
+ in truncate_file().
+ Markus Uhlin 25/03/09 truncate_file:
+ fixed null ptr dereference.
+ Markus Uhlin 25/04/06 Fixed Clang Tidy warnings.
+ Markus Uhlin 25/07/21 Replaced non-reentrant functions
+ with their corresponding thread
+ safe version.
+ Markus Uhlin 25/07/28 truncate_file: restricted file
+ permissions upon creation.
+*/
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include "config.h"
+#include "ficsmain.h"
+#include "network.h"
+#include "playerdb.h"
+#include "rmalloc.h"
+#include "utils.h"
+
+#if __linux__
+#include <bsd/string.h>
+#endif
+
+struct t_tree {
+ struct t_tree *left, *right;
+ char *name;
+};
+
+struct t_dirs {
+ struct t_dirs *left, *right;
+ time_t mtime;
+ struct t_tree *files;
+ char *name;
+};
+
+PRIVATE char** t_buffer = NULL;
+PRIVATE int t_buffersize = 0;
+
+PUBLIC bool
+is_valid_filename(const char *name, const bool allow_hidden)
+{
+ if (name == NULL || strcmp(name, "") == 0)
+ return false;
+ if (!allow_hidden && name[0] == '.')
+ return false;
+ if (strstr(name, "..") || strchr(name, '/') || strchr(name, '\\') ||
+ name[0] == '/')
+ return false;
+ for (const char *p = name; *p; ++p) {
+ if (isspace((unsigned char)*p) || iscntrl((unsigned char)*p))
+ return false;
+ }
+ for (const char *p = name; *p; ++p) {
+ if (!isalnum((unsigned char)*p) && *p != '-' && *p != '_' &&
+ *p != '.')
+ return false;
+ }
+ return true;
+}
+
+/*
+ * Function to validate login names for file path usage
+ */
+PUBLIC bool
+is_valid_login_name(const char *login)
+{
+ if (login == NULL || login[0] == '\0' ||
+ strstr(login, "..") || strchr(login, '/') || strchr(login, '\\'))
+ return false;
+
+ for (const char *p = login; *p; ++p) {
+ if (!((*p >= 'a' && *p <= 'z') ||
+ (*p >= 'A' && *p <= 'Z') ||
+ (*p >= '0' && *p <= '9') ||
+ *p == '_'))
+ return false;
+ }
+
+ return true;
+}
+
+PUBLIC int
+count_lines(FILE *fp)
+{
+ int c, nl = 0;
+
+ while ((c = fgetc(fp)) != EOF)
+ if (c == '\n')
+ ++nl;
+ return nl;
+}
+
+PUBLIC int
+iswhitespace(int c)
+{
+ if (c < ' ' || c == '\b' || c == '\n' || c == '\t' || c == ' ')
+ return 1;
+ return 0;
+}
+
+PUBLIC char *
+getword(char *str)
+{
+ int i;
+ static char word[MAX_WORD_SIZE];
+
+ i = 0;
+
+ while (*str && !iswhitespace(*str)) {
+ word[i] = *str;
+ str++;
+ i++;
+
+ if (i == MAX_WORD_SIZE) {
+ i = (i - 1);
+ break;
+ }
+ }
+
+ word[i] = '\0';
+ return word;
+}
+
+/*
+ * Returns a pointer to the first whitespace in the argument.
+ */
+PUBLIC char *
+eatword(char *str)
+{
+ while (*str && !iswhitespace(*str))
+ str++;
+ return str;
+}
+
+/*
+ * Returns a pointer to the first non-whitespace char in the argument.
+ */
+PUBLIC char *
+eatwhite(char *str)
+{
+ while (*str && iswhitespace(*str))
+ str++;
+ return str;
+}
+
+/*
+ * Returns a pointer to the same string with trailing spaces removed.
+ */
+PUBLIC char *
+eattailwhite(char *str)
+{
+ int len;
+
+ if (str == NULL)
+ return NULL;
+
+ len = strlen(str);
+
+ while (len > 0 && iswhitespace(str[len - 1]))
+ len--;
+
+ str[len] = '\0';
+ return str;
+}
+
+/*
+ * Returns the next word in a given string.
+ */
+PUBLIC char *
+nextword(char *str)
+{
+ return eatwhite(eatword(str));
+}
+
+PUBLIC int
+mail_string_to_address(char *addr, char *subj, char *str)
+{
+ FILE *fp;
+ char com[1000];
+
+#ifdef SENDMAILPROG
+ snprintf(com, sizeof com, "%s\n", SENDMAILPROG);
+#else
+ snprintf(com, sizeof com, "%s -s \"%s\" %s", MAILPROGRAM, subj, addr);
+#endif
+
+ fp = popen(com, "w");
+ if (!fp)
+ return -1;
+
+#ifdef SENDMAILPROG
+ fprintf(fp, "To: %s\nSubject: %s\n%s", addr, subj, str);
+#else
+ fprintf(fp, "%s", str);
+#endif
+
+ pclose(fp);
+ return 0;
+}
+
+PUBLIC int
+mail_string_to_user(int p, char *subj, char *str)
+{
+ if (parray[p].emailAddress &&
+ parray[p].emailAddress[0] &&
+ safestring(parray[p].emailAddress)) {
+ return mail_string_to_address(parray[p].emailAddress, subj,
+ str);
+ } else {
+ return -1;
+ }
+}
+
+PUBLIC int
+mail_file_to_address(char *addr, char *subj, char *fname)
+{
+ FILE *fp1, *fp2;
+ char com[1000] = { '\0' };
+ char tmp[MAX_LINE_SIZE] = { '\0' };
+
+ /* maybe unused */
+ (void) fp2;
+ (void) tmp;
+
+#ifdef SENDMAILPROG
+ snprintf(com, sizeof com, "%s\n", SENDMAILPROG);
+#else
+ snprintf(com, sizeof com, "%s -s \"%s\" %s < %s&", MAILPROGRAM, subj,
+ addr, fname);
+#endif
+ if ((fp1 = popen(com, "w")) == NULL)
+ return -1;
+#ifdef SENDMAILPROG
+ if ((fp2 = fopen(fname, "r")) == NULL) {
+ pclose(fp1);
+ return -1;
+ }
+
+ fprintf(fp1, "To: %s\nSubject: %s\n", addr, subj);
+
+ while (fgets(tmp, sizeof tmp, fp2) != NULL && !feof(fp2))
+ fputs(tmp, fp1);
+ fclose(fp2);
+#endif
+ pclose(fp1);
+ return 0;
+}
+
+PUBLIC int
+mail_file_to_user(int p, char *subj, char *fname)
+{
+ if (parray[p].emailAddress &&
+ parray[p].emailAddress[0] &&
+ safestring(parray[p].emailAddress)) {
+ return mail_file_to_address(parray[p].emailAddress, subj,
+ fname);
+ } else {
+ return -1;
+ }
+}
+
+/*
+ * Process a command for a user
+ */
+PUBLIC int
+pcommand(int p, char *comstr, ...)
+{
+ char tmp[MAX_LINE_SIZE] = { '\0' };
+ int current_socket = parray[p].socket;
+ int retval;
+ va_list ap;
+
+ va_start(ap, comstr);
+ vsnprintf(tmp, sizeof tmp, comstr, ap);
+ va_end(ap);
+
+ retval = process_input(current_socket, tmp);
+
+ if (retval == COM_LOGOUT) {
+ process_disconnection(current_socket);
+ net_close_connection(current_socket);
+ }
+
+ return retval;
+}
+
+PUBLIC void
+pprintf(int p, const char *format, ...)
+{
+ char tmp[10 * MAX_LINE_SIZE] = { '\0' };
+ int retval;
+ va_list ap;
+
+ va_start(ap, format);
+ retval = vsnprintf(tmp, sizeof tmp, format, ap);
+ va_end(ap);
+
+ UNUSED_VAR(retval);
+ net_send_string(parray[p].socket, tmp, 1);
+}
+
+PRIVATE void
+pprintf_dohightlight(int p)
+{
+ if (parray[p].highlight & 0x01)
+ pprintf(p, "\033[7m");
+ if (parray[p].highlight & 0x02)
+ pprintf(p, "\033[1m");
+ if (parray[p].highlight & 0x04)
+ pprintf(p, "\033[4m");
+ if (parray[p].highlight & 0x08)
+ pprintf(p, "\033[2m");
+}
+
+PUBLIC int
+pprintf_highlight(int p, char *format, ...)
+{
+ char tmp[10 * MAX_LINE_SIZE];
+ int retval;
+ va_list ap;
+
+ pprintf_dohightlight(p);
+
+ va_start(ap, format);
+ retval = vsnprintf(tmp, sizeof tmp, format, ap);
+ va_end(ap);
+
+ net_send_string(parray[p].socket, tmp, 1);
+
+ if (parray[p].highlight)
+ pprintf(p, "\033[0m");
+ return retval;
+}
+
+PRIVATE void
+sprintf_dohightlight(int p, char *s, size_t size)
+{
+ if (parray[p].highlight & 0x01)
+ strlcat(s, "\033[7m", size);
+ if (parray[p].highlight & 0x02)
+ strlcat(s, "\033[1m", size);
+ if (parray[p].highlight & 0x04)
+ strlcat(s, "\033[4m", size);
+ if (parray[p].highlight & 0x08)
+ strlcat(s, "\033[2m", size);
+}
+
+PUBLIC int
+psprintf_highlight(int p, char *s, size_t size, char *format, ...)
+{
+ int retval;
+ va_list ap;
+
+ if (parray[p].highlight) {
+ char tmp[1000] = { '\0' };
+
+ va_start(ap, format);
+ retval = vsnprintf(tmp, sizeof tmp, format, ap);
+ va_end(ap);
+
+ sprintf_dohightlight(p, s, size);
+ strlcat(s, tmp, size);
+ strlcat(s, "\033[0m", size);
+ } else {
+ va_start(ap, format);
+ retval = vsnprintf(s, size, format, ap);
+ va_end(ap);
+ }
+
+ return retval;
+}
+
+PUBLIC int
+pprintf_prompt(int p, char *format, ...)
+{
+ char tmp[10 * MAX_LINE_SIZE];
+ int retval;
+ va_list ap;
+
+ va_start(ap, format);
+ retval = vsnprintf(tmp, sizeof tmp, format, ap);
+ va_end(ap);
+
+ net_send_string(parray[p].socket, tmp, 1);
+ net_send_string(parray[p].socket, parray[p].prompt, 1);
+ return retval;
+}
+
+PUBLIC int
+pprintf_noformat(int p, char *format, ...)
+{
+ char tmp[10 * MAX_LINE_SIZE];
+ int retval;
+ va_list ap;
+
+ va_start(ap, format);
+ retval = vsnprintf(tmp, sizeof tmp, format, ap);
+ va_end(ap);
+
+ net_send_string(parray[p].socket, tmp, 0);
+ return retval;
+}
+
+PUBLIC int
+psend_raw_file(int p, const char *dir, const char *file)
+{
+ FILE *fp;
+ char fname[MAX_FILENAME_SIZE] = { '\0' };
+ char tmp[MAX_LINE_SIZE] = { '\0' };
+ int num;
+
+ if (dir)
+ snprintf(fname, sizeof fname, "%s/%s", dir, file);
+ else
+ strlcpy(fname, file, sizeof fname);
+
+ if ((fp = fopen(fname, "r")) == NULL)
+ return -1;
+
+ while (!feof(fp) && !ferror(fp)) {
+ if ((num = fread(tmp, sizeof(char), MAX_LINE_SIZE - 1,
+ fp)) > 0) {
+ tmp[num] = '\0';
+ net_send_string(parray[p].socket, tmp, 1);
+ }
+ }
+
+ if (ferror(fp)) {
+ warnx("%s: %s: the error indicator is set", __func__, fname);
+ fclose(fp);
+ return -1;
+ }
+
+ fclose(fp);
+ return 0;
+}
+
+PUBLIC int
+psend_file(int p, const char *dir, const char *file)
+{
+ FILE *fp;
+ char fname[MAX_FILENAME_SIZE] = { '\0' };
+ char tmp[MAX_LINE_SIZE] = { '\0' };
+ int lcount = (parray[p].d_height - 1);
+
+ if (parray[p].last_file)
+ rfree(parray[p].last_file);
+ parray[p].last_file = NULL;
+ parray[p].last_file_byte = 0L;
+
+ if (dir)
+ snprintf(fname, sizeof fname, "%s/%s", dir, file);
+ else
+ strlcpy(fname, file, sizeof fname);
+
+ if ((fp = fopen(fname, "r")) == NULL)
+ return -1;
+
+ while (--lcount > 0) {
+ if (fgets(tmp, sizeof tmp, fp) == NULL)
+ break;
+ net_send_string(parray[p].socket, tmp, 1);
+ }
+
+ if (!feof(fp)) {
+ if (ferror(fp)) {
+ warnx("%s: %s: the error indicator is set", __func__,
+ fname);
+ fclose(fp);
+ return -1;
+ }
+ parray[p].last_file = xstrdup(fname);
+ parray[p].last_file_byte = ftell(fp);
+ pprintf(p, "Type [next] to see next page.\n");
+ }
+
+ fclose(fp);
+ return 0;
+}
+
+/*
+ * Marsalis added on 8/27/95 so that [next] does not appear in the
+ * logout process for those that have a short screen height.
+ */
+PUBLIC int
+psend_logoutfile(int p, char *dir, char *file)
+{
+ FILE *fp;
+ char fname[MAX_FILENAME_SIZE] = { '\0' };
+ char tmp[MAX_LINE_SIZE] = { '\0' };
+
+ if (parray[p].last_file)
+ rfree(parray[p].last_file);
+ parray[p].last_file = NULL;
+ parray[p].last_file_byte = 0L;
+
+ if (dir)
+ snprintf(fname, sizeof fname, "%s/%s", dir, file);
+ else
+ strlcpy(fname, file, sizeof fname);
+
+ if ((fp = fopen(fname, "r")) == NULL)
+ return -1;
+
+ while (fgets(tmp, sizeof tmp, fp) != NULL && !feof(fp))
+ net_send_string(parray[p].socket, tmp, 1);
+
+ fclose(fp);
+ return 0;
+}
+
+PUBLIC int
+pmore_file(int p)
+{
+ FILE *fp;
+ char tmp[MAX_LINE_SIZE] = { '\0' };
+ int lcount = (parray[p].d_height - 1);
+
+ if (!parray[p].last_file) {
+ pprintf(p, "There is no more.\n");
+ return -1;
+ }
+
+ if ((fp = fopen(parray[p].last_file, "r")) == NULL) {
+ pprintf(p, "File not found!\n");
+ return -1;
+ } else if (fseek(fp, parray[p].last_file_byte, SEEK_SET) == -1) {
+ pprintf(p, "Unable to set the file position indicator.\n");
+ fclose(fp);
+ return -1;
+ }
+
+ while (--lcount > 0) {
+ if (fgets(tmp, sizeof tmp, fp) == NULL)
+ break;
+ net_send_string(parray[p].socket, tmp, 1);
+ }
+
+ if (!feof(fp)) {
+ if (ferror(fp)) {
+ warnx("%s: %s: the error indicator is set", __func__,
+ parray[p].last_file);
+ fclose(fp);
+ return -1;
+ } else if ((parray[p].last_file_byte = ftell(fp)) == -1) {
+ warn("%s: %s: ftell", __func__, parray[p].last_file);
+ fclose(fp);
+ return -1;
+ } else
+ pprintf(p, "Type [next] to see next page.\n");
+ } else {
+ rfree(parray[p].last_file);
+ parray[p].last_file = NULL;
+ parray[p].last_file_byte = 0L;
+ }
+
+ fclose(fp);
+ return 0;
+}
+
+PUBLIC int
+psend_command(int p, char *command, char *input)
+{
+ FILE *fp;
+ char tmp[MAX_LINE_SIZE];
+ int num;
+
+ if (input)
+ fp = popen(command, "w");
+ else
+ fp = popen(command, "r");
+ if (!fp)
+ return -1;
+
+ if (input) {
+ fwrite(input, sizeof(char), strlen(input), fp);
+ } else {
+ while (!feof(fp)) {
+ num = fread(tmp, sizeof(char), MAX_LINE_SIZE - 1, fp);
+ tmp[num] = '\0';
+ net_send_string(parray[p].socket, tmp, 1);
+ }
+ }
+
+ pclose(fp);
+ return 0;
+}
+
+PUBLIC char *
+stolower(char *str)
+{
+ if (!str)
+ return NULL;
+ for (int i = 0; str[i]; i++) {
+ if (isupper(str[i]))
+ str[i] = tolower(str[i]);
+ }
+ return str;
+}
+
+PUBLIC int
+safechar(int c)
+{
+ if (c == '>' || c == '!' || c == '&' || c == '*' || c == '?' ||
+ c == '/' || c == '<' || c == '|' || c == '`' || c == '$')
+ return 0;
+ return 1;
+}
+
+PUBLIC int
+safestring(char *str)
+{
+ if (!str)
+ return 1;
+ for (int i = 0; str[i]; i++) {
+ if (!safechar(str[i]))
+ return 0;
+ }
+ return 1;
+}
+
+PUBLIC int
+alphastring(char *str)
+{
+ if (!str)
+ return 1;
+ for (int i = 0; str[i]; i++) {
+ if (!isalpha(str[i]))
+ return 0;
+ }
+ return 1;
+}
+
+PUBLIC int
+printablestring(char *str)
+{
+ if (!str)
+ return 1;
+ for (int i = 0; str[i]; i++) {
+ if (!isprint(str[i]) && str[i] != '\t' && str[i] != '\n')
+ return 0;
+ }
+ return 1;
+}
+
+PUBLIC char *
+xstrdup(const char *str)
+{
+ char *out;
+
+ if (str == NULL)
+ return NULL;
+
+ const size_t size = strlen(str) + 1;
+
+ out = rmalloc(size);
+ return memcpy(out, str, size);
+}
+
+PUBLIC char *
+hms_desc(int t)
+{
+ int days, hours, mins, secs;
+ static char tstr[80];
+
+ days = t / (60 * 60 * 24);
+ hours = (t % (60 * 60 * 24)) / (60 * 60);
+ mins = ((t % (60 * 60 * 24)) % (60 * 60)) / 60;
+ secs = ((t % (60 * 60 * 24)) % (60 * 60)) % 60;
+
+ if (days == 0 && hours == 0 && mins == 0) {
+ snprintf(tstr, sizeof tstr, "%d sec%s", secs, (secs == 1 ? "" :
+ "s"));
+ } else if (days == 0 && hours == 0) {
+ snprintf(tstr, sizeof tstr, "%d min%s", mins, (mins == 1 ? "" :
+ "s"));
+ } else if (days == 0) {
+ snprintf(tstr, sizeof tstr, "%d hr%s, %d min%s, %d sec%s",
+ hours, (hours == 1 ? "" : "s"),
+ mins, (mins == 1 ? "" : "s"),
+ secs, (secs == 1 ? "" : "s"));
+ } else {
+ snprintf(tstr, sizeof tstr, "%d day%s, %d hour%s, %d minute%s "
+ "and %d second%s",
+ days, (days == 1 ? "" : "s"),
+ hours, (hours == 1 ? "" : "s"),
+ mins, (mins == 1 ? "" : "s"),
+ secs, (secs == 1 ? "" : "s"));
+ }
+
+ return tstr;
+}
+
+PUBLIC char *
+hms(int t, int showhour, int showseconds, int spaces)
+{
+ char tmp[10];
+ int h, m, s;
+ static char tstr[20];
+
+ h = (t / 3600);
+ t = (t % 3600);
+ m = (t / 60);
+ s = (t % 60);
+
+ if (h || showhour) {
+ if (spaces)
+ snprintf(tstr, sizeof tstr, "%d : %02d", h, m);
+ else
+ snprintf(tstr, sizeof tstr, "%d:%02d", h, m);
+ } else {
+ snprintf(tstr, sizeof tstr, "%d", m);
+ }
+ if (showseconds) {
+ if (spaces)
+ snprintf(tmp, sizeof tmp, " : %02d", s);
+ else
+ snprintf(tmp, sizeof tmp, ":%02d", s);
+ strlcat(tstr, tmp, sizeof tstr);
+ }
+ return tstr;
+}
+
+PRIVATE char *
+strtime(struct tm * stm)
+{
+ static char tstr[100];
+#if defined (SGI)
+ strftime(tstr, sizeof(tstr), "%a %b %e, %H:%M %Z", stm);
+#else
+ strftime(tstr, sizeof(tstr), "%a %b %e, %k:%M %Z", stm);
+#endif
+ return (tstr);
+}
+
+PUBLIC char *
+fix_time(char *old_time)
+{
+ char date[5] = { '\0' };
+ char day[5] = { '\0' };
+ char i;
+ char month[5] = { '\0' };
+ static char new_time[20];
+
+ _Static_assert(4 < ARRAY_SIZE(day), "'day' too small");
+ _Static_assert(4 < ARRAY_SIZE(month), "'month' too small");
+ _Static_assert(4 < ARRAY_SIZE(date), "'date' too small");
+
+ if (sscanf(old_time, "%4s %4s %4s", day, month, date) != 3)
+ warnx("%s: sscanf: too few items (%s)", __func__, old_time);
+
+ if (date[2] != ',') {
+ i = date[0];
+ date[0] = '0';
+ date[1] = i;
+ }
+ date[2] = '\0';
+
+ snprintf(new_time, sizeof new_time, "%s, %s %s", day, month, date);
+
+ return &new_time[0];
+}
+
+PUBLIC char *
+strltime(time_t *p_clock)
+{
+ struct tm stm = {0};
+
+ errno = 0;
+
+ if (localtime_r(p_clock, &stm) == NULL) {
+ warn("%s: localtime_r", __func__);
+ memset(&stm, 0, sizeof stm);
+ }
+ return strtime(&stm);
+}
+
+PUBLIC char *
+strgtime(time_t *p_clock)
+{
+ struct tm stm = {0};
+
+ errno = 0;
+
+ if (gmtime_r(p_clock, &stm) == NULL) {
+ warn("%s: gmtime_r", __func__);
+ memset(&stm, 0, sizeof stm);
+ }
+ return strtime(&stm);
+}
+
+/*
+ * This is used only for relative timing since it reports seconds
+ * since about 5:00 pm on Feb 16, 1994.
+ */
+PUBLIC unsigned int
+tenth_secs(void)
+{
+ struct timeval tp;
+ struct timezone tzp;
+
+ gettimeofday(&tp, &tzp);
+
+ return ((tp.tv_sec - 331939277) * 10L) + (tp.tv_usec / 100000);
+}
+
+/*
+ * This is to translate tenths-secs time back into 1/1/70 time in full
+ * seconds, because vek didn't read utils.c when he programmed new
+ * ratings. 1 sec since 1970 fits into a 32 bit int OK.
+ */
+/*
+ * 2024-11-23 maxxe: changed the return type to 'time_t'
+ */
+PUBLIC time_t
+untenths(uint64_t tenths)
+{
+ return (tenths / 10 + 331939277 + 0xffffffff / 10 + 1);
+}
+
+PUBLIC char *
+tenth_str(unsigned int t, int spaces)
+{
+ return hms((t + 5) / 10, 0, 1, spaces);
+}
+
+/*
+ * XXX: if lines in the file are greater than 1024 bytes in length,
+ * this won't work!
+ */
+PUBLIC int
+truncate_file(char *file, int lines)
+{
+#define MAX_TRUNC_SIZE 100
+ FILE *fp;
+ char tBuf[MAX_TRUNC_SIZE][MAX_LINE_SIZE];
+ int bptr = 0, trunc = 0, i;
+
+ if (lines > MAX_TRUNC_SIZE)
+ lines = MAX_TRUNC_SIZE;
+
+ if ((fp = fopen(file, "r")) == NULL)
+ return 1;
+
+ while (fgets(tBuf[bptr], MAX_LINE_SIZE, fp) != NULL) {
+ if (strchr(tBuf[bptr], '\n') == NULL) {
+ // Line too long
+ fclose(fp);
+ return -1;
+ }
+
+ if (++bptr == lines) {
+ trunc = 1;
+ bptr = 0;
+ }
+ }
+
+ fclose(fp);
+
+ if (trunc) {
+ int fd;
+
+ errno = 0;
+ fd = open(file, g_open_flags[1], g_open_modes);
+
+ if (fd < 0) {
+ warn("%s: open", __func__);
+ return 1;
+ } else if ((fp = fdopen(fd, "w")) == NULL) {
+ warn("%s: fdopen", __func__);
+ close(fd);
+ return 1;
+ }
+
+ for (i = 0; i < lines; i++) {
+ fputs(tBuf[bptr], fp);
+
+ if (++bptr == lines)
+ bptr = 0;
+ }
+
+ fclose(fp);
+ }
+
+ return 0;
+}
+
+/*
+ * XXX: If lines in the file are greater than 1024 bytes in length,
+ * this won't work!
+ */
+PUBLIC int
+lines_file(char *file)
+{
+ FILE *fp;
+ char tmp[MAX_LINE_SIZE];
+ int lcount = 0;
+
+ if ((fp = fopen(file, "r")) == NULL)
+ return 0;
+
+ while (fgets(tmp, sizeof tmp, fp) != NULL && !feof(fp))
+ lcount++;
+
+ fclose(fp);
+ return lcount;
+}
+
+PUBLIC int
+file_has_pname(char *fname, char *plogin)
+{
+ if (!strcmp(file_wplayer(fname), plogin))
+ return 1;
+ if (!strcmp(file_bplayer(fname), plogin))
+ return 1;
+ return 0;
+}
+
+PUBLIC char *
+file_wplayer(char *fname)
+{
+ char *ptr;
+ static char tmp[MAX_FILENAME_SIZE];
+
+ strlcpy(tmp, fname, sizeof tmp);
+
+ if ((ptr = strrchr(tmp, '-')) == NULL)
+ return "";
+ *ptr = '\0';
+ return tmp;
+}
+
+PUBLIC char *
+file_bplayer(char *fname)
+{
+ char *ptr;
+
+ if ((ptr = strrchr(fname, '-')) == NULL)
+ return "";
+ return ptr + 1;
+}
+
+/*
+ * Hey, leave this code alone. 'a' is always in network byte order,
+ * which is big-endian, even on a little-endian machine. I fixed this
+ * code to handle that correctly a while ago and someone gratuitously
+ * changed it so that it would work correctly only on a big-endian
+ * machine (like a Sun). I have now changed it back. --mann
+ */
+PUBLIC char *
+dotQuad(unsigned int a)
+{
+ static char tmp[20];
+ unsigned char *aa = (unsigned char *) &a;
+
+ snprintf(tmp, sizeof tmp, "%d.%d.%d.%d", aa[0], aa[1], aa[2], aa[3]);
+ return tmp;
+}
+
+PUBLIC int
+available_space(void)
+{
+ return 100000000; /* Infinite space */
+}
+
+PUBLIC int
+file_exists(char *fname)
+{
+ FILE *fp;
+
+ if ((fp = fopen(fname, "r")) == NULL)
+ return 0;
+ fclose(fp);
+ return 1;
+}
+
+PUBLIC char *
+ratstr(int rat)
+{
+ static char tmp[20][10];
+ static int on = 0;
+
+ if (on == 20)
+ on = 0;
+ if (rat) {
+ snprintf(tmp[on], sizeof tmp[on], "%4d", rat);
+ } else {
+ snprintf(tmp[on], sizeof tmp[on], "----");
+ }
+
+ on++;
+ return tmp[on - 1];
+}
+
+PUBLIC char *
+ratstrii(int rat, int reg)
+{
+ static char tmp[20][10];
+ static int on = 0;
+
+ if (on == 20)
+ on = 0;
+ if (rat) {
+ snprintf(tmp[on], sizeof tmp[on], "%4d", rat);
+ } else {
+ if (reg) {
+ snprintf(tmp[on], sizeof tmp[on], "----");
+ } else {
+ snprintf(tmp[on], sizeof tmp[on], "++++");
+ }
+ }
+
+ on++;
+ return tmp[on - 1];
+}
+
+/*
+ * Fill 't_buffer' with anything matching "want*" in file tree
+ */
+PRIVATE void
+t_sft(const char *want, struct t_tree *t)
+{
+ if (t) {
+ const char *v_want = (want ? want : "");
+ int cmp = strncmp(v_want, t->name, strlen(v_want));
+
+ if (cmp <= 0) // If 'want' <= this one, look left
+ t_sft(want, t->left);
+
+ if (t_buffersize && cmp == 0) { // If a match, add it to buffer
+ t_buffersize--;
+ *t_buffer++ = t->name;
+ }
+
+ if (cmp >= 0) // If 'want' >= this one, look right
+ t_sft(want, t->right);
+ }
+}
+
+/*
+ * Delete file tree
+ */
+PRIVATE void
+t_cft(struct t_tree **t)
+{
+ if (t != NULL && *t != NULL) {
+ t_cft(&(*t)->left);
+ t_cft(&(*t)->right);
+ rfree((*t)->name);
+ rfree(*t);
+ *t = NULL;
+ }
+}
+
+/*
+ * Make file tree for dir 'd'
+ */
+PRIVATE void
+t_mft(struct t_dirs *d)
+{
+ DIR *dirp;
+#ifdef USE_DIRENT
+ struct dirent *dp;
+#else
+ struct direct *dp;
+#endif
+ struct t_tree **t;
+
+ if ((dirp = opendir(d->name)) == NULL) {
+ fprintf(stderr, "FICS: %s: couldn't opendir\n", __func__);
+ return;
+ }
+ while ((dp = readdir(dirp))) {
+ t = &d->files;
+
+ if (dp->d_name[0] != '.') { // skip anything starting with '.'
+ size_t size;
+
+ while (*t) {
+ if (strcmp(dp->d_name, (*t)->name) < 0)
+ t = &(*t)->left;
+ else
+ t = &(*t)->right;
+ }
+
+ size = strlen(dp->d_name) + 1;
+ *t = rmalloc(sizeof(struct t_tree));
+ (*t)->right = (*t)->left = NULL;
+ (*t)->name = rmalloc(size);
+
+ if (strlcpy((*t)->name, dp->d_name, size) >= size)
+ errx(1, "%s: name too long", __func__);
+ }
+ }
+ closedir(dirp);
+}
+
+/*
+ * dir = directory to search
+ * filter = what to search for
+ * buffer = where to store pointers to matches
+ * buffersize = how many pointers will fit inside buffer
+ */
+PUBLIC int
+search_directory(char *dir, char *filter, char **buffer, int buffersize)
+{
+ int cmp;
+ static struct t_dirs *ramdirs = NULL;
+ struct stat statbuf;
+ struct t_dirs** i;
+
+ t_buffer = buffer;
+ t_buffersize = buffersize;
+
+ if (!stat(dir, &statbuf)) {
+ i = &ramdirs;
+
+ while (*i) { // Find dir in dir tree
+ if ((cmp = strcmp(dir, (*i)->name)) == 0)
+ break;
+ else if (cmp < 0)
+ i = &(*i)->left;
+ else
+ i = &(*i)->right;
+ }
+
+ if (!*i) { // If dir isn't in dir tree,
+ size_t size; // add him.
+
+ size = strlen(dir) + 1;
+ *i = rmalloc(sizeof(struct t_dirs));
+ (*i)->left = (*i)->right = NULL;
+ (*i)->files = NULL;
+ (*i)->name = rmalloc(size);
+
+ if (strlcpy((*i)->name, dir, size) >= size)
+ errx(1, "%s: dir too long", __func__);
+ }
+
+ if ((*i)->files) { // Delete any obsolete file
+ // tree.
+ if ((*i)->mtime != statbuf.st_mtime)
+ t_cft(&(*i)->files);
+ }
+
+ if ((*i)->files == NULL) { // If no file tree for him,
+ // make one.
+ (*i)->mtime = statbuf.st_mtime;
+ t_mft(*i);
+ }
+
+ t_sft(filter, (*i)->files); // Finally, search for matches.
+ }
+
+ return (buffersize - t_buffersize);
+}
+
+PUBLIC int
+display_directory(int p, char **buffer, int count)
+{ // 'buffer' contains 'count' string pointers.
+ int i;
+ multicol *m = multicol_start(count);
+
+ for (i = 0; (i < count); i++)
+ multicol_store(m, *buffer++);
+ multicol_pprint(m, p, 78, 1);
+ multicol_end(m);
+ return (i);
+}
diff --git a/FICS/utils.h b/FICS/utils.h
new file mode 100644
index 0000000..e376a03
--- /dev/null
+++ b/FICS/utils.h
@@ -0,0 +1,115 @@
+/* utils.h
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 23/12/10 Renamed strdup() to xstrdup()
+ Markus Uhlin 23/12/10 Deleted check_emailaddr()
+ Markus Uhlin 23/12/17 Added argument lists
+ Markus Uhlin 23/12/28 Completed adding argument lists
+ Markus Uhlin 24/01/04 Added usage of PRINTFLIKE()
+ Markus Uhlin 24/11/20 Usage of begin/end decls
+*/
+
+#ifndef _UTILS_H
+#define _UTILS_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "common.h" /* PRINTFLIKE() */
+#include "multicol.h"
+
+#define MAX_WORD_SIZE 1024
+
+/*
+ * Maximum length of an output line
+ */
+#define MAX_LINE_SIZE 1024
+
+/*
+ * Maximum size of a filename
+ */
+#ifdef FILENAME_MAX
+# define MAX_FILENAME_SIZE FILENAME_MAX
+#else
+# define MAX_FILENAME_SIZE 1024
+#endif
+
+#define SetFlag(VAR, FLAG) (VAR |= (FLAG))
+#define ClearFlag(VAR, FLAG) (VAR &= ~(FLAG))
+#define CheckFlag(VAR, FLAG) (VAR & (FLAG))
+
+__FICS_BEGIN_DECLS
+extern bool is_valid_filename(const char *, const bool);
+extern bool is_valid_login_name(const char *);
+extern char *dotQuad(unsigned int);
+extern char *eattailwhite(char *);
+extern char *eatwhite(char *);
+extern char *eatword(char *);
+extern char *file_bplayer(char *);
+extern char *file_wplayer(char *);
+extern char *fix_time(char *);
+extern char *getword(char *);
+extern char *hms(int, int, int, int);
+extern char *hms_desc(int);
+extern char *nextword(char *);
+extern char *ratstr(int);
+extern char *ratstrii(int, int);
+extern char *stolower(char *);
+extern char *strgtime(time_t *);
+extern char *strltime(time_t *);
+extern char *tenth_str(unsigned int, int);
+extern char *xstrdup(const char *);
+extern int alphastring(char *);
+extern int available_space(void);
+extern int count_lines(FILE *);
+extern int display_directory(int, char **, int);
+extern int file_exists(char *);
+extern int file_has_pname(char *, char *);
+extern int iswhitespace(int);
+extern int lines_file(char *);
+extern int mail_file_to_address(char *, char *, char *);
+extern int mail_file_to_user(int, char *, char *);
+extern int mail_string_to_address(char *, char *, char *);
+extern int mail_string_to_user(int, char *, char *);
+extern int pcommand(int, char *, ...) PRINTFLIKE(2);
+extern int pmore_file(int);
+extern void pprintf(int, const char *, ...) PRINTFLIKE(2);
+extern int pprintf_highlight(int, char *, ...) PRINTFLIKE(2);
+extern int pprintf_noformat(int, char *, ...) PRINTFLIKE(2);
+extern int pprintf_prompt(int, char *, ...) PRINTFLIKE(2);
+extern int printablestring(char *);
+extern int psend_command(int, char *, char *);
+extern int psend_file(int, const char *, const char *);
+extern int psend_logoutfile(int, char *, char *);
+extern int psend_raw_file(int, const char *, const char *);
+extern int psprintf_highlight(int, char *, size_t, char *, ...)
+ PRINTFLIKE(4);
+extern int safechar(int);
+extern int safestring(char *);
+extern int search_directory(char *, char *, char **, int);
+extern int truncate_file(char *, int);
+extern time_t untenths(uint64_t);
+extern unsigned int tenth_secs(void);
+__FICS_END_DECLS
+
+#endif /* _UTILS_H */
diff --git a/FICS/variable.c b/FICS/variable.c
new file mode 100644
index 0000000..e004eca
--- /dev/null
+++ b/FICS/variable.c
@@ -0,0 +1,1066 @@
+/* variable.c
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ loon 95/03/17 Added figure_boolean()
+ hersco 95/04/10 Replaced figure_boolean() with
+ set_boolean_var()
+ DAV 95/19/11 Moved variable command to here
+ Added jprivate
+ Markus Uhlin 23/12/27 Fixed the includes
+ Markus Uhlin 24/04/01 Reformatted all functions
+ Markus Uhlin 24/07/08 Added var 'interface'
+ Markus Uhlin 24/11/19 Improved Language()
+ Markus Uhlin 24/11/24 Added var 'seek'.
+*/
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include <err.h>
+#include <stdbool.h>
+
+#include "board.h"
+#include "command.h"
+#include "comproc.h"
+#include "formula.h" /* SetValidFormula() */
+#include "playerdb.h"
+#include "rmalloc.h"
+#include "talkproc.h"
+#include "utils.h"
+#include "variable.h"
+
+#if __linux__
+#include <bsd/string.h>
+#endif
+
+PRIVATE int
+set_boolean_var(int *var, char *val)
+{
+ int v = -1;
+
+ if (val == NULL)
+ return (*var = !*var);
+
+ if (sscanf(val, "%d", &v) != 1) {
+ stolower(val);
+
+ if (!strcmp(val, "off"))
+ v = 0;
+ if (!strcmp(val, "false"))
+ v = 0;
+ if (!strcmp(val, "on"))
+ v = 1;
+ if (!strcmp(val, "true"))
+ v = 1;
+ }
+
+ if (v == 0 || v == 1)
+ return (*var = v);
+ else
+ return (-1);
+}
+
+PRIVATE int
+set_open(int p, char *var, char *val)
+{
+ int v = set_boolean_var(&parray[p].open, val);
+
+ if (v < 0) {
+ return VAR_BADVAL;
+ } else if (v > 0) {
+ pprintf(p, "You are now open to receive match requests.\n");
+ } else {
+ player_decline_offers(p, -1, PEND_MATCH);
+ player_withdraw_offers(p, -1, PEND_MATCH);
+ pprintf(p, "You are no longer receiving match requests.\n");
+ }
+
+ return VAR_OK;
+}
+
+PRIVATE int
+set_sopen(int p, char *var, char *val)
+{
+ int v = set_boolean_var(&parray[p].sopen, val);
+
+ if (v < 0)
+ return VAR_BADVAL;
+
+ pprintf(p, "sopen set to %d.\n", parray[p].sopen);
+
+ if (v > 0)
+ pprintf(p, "You are now open to receive simul requests.\n");
+ else
+ pprintf(p, "You are no longer receiving simul requests.\n");
+
+ player_decline_offers(p, -1, PEND_SIMUL);
+ return VAR_OK;
+}
+
+PRIVATE int
+set_ropen(int p, char *var, char *val)
+{
+ if (set_boolean_var(&parray[p].ropen, val) < 0)
+ return VAR_BADVAL;
+ pprintf(p, "ropen set to %d.\n", parray[p].ropen);
+ return VAR_OK;
+}
+
+/*
+ * If your 'seek' variable is non-zero (whether a number, 'true' or
+ * 'on'), you will be informed of "seek" ads when they are made. zero
+ * ('false' or 'off'), you will not receive these announcements. For
+ * example, to receive ads, type "set seek 1". To stop receiving ads,
+ * type "set seek 0".
+ */
+PRIVATE int
+set_seek(int p, char *var, char *val)
+{
+ int v = set_boolean_var(&parray[p].seek, val);
+
+ if (v < 0)
+ return VAR_BADVAL;
+
+ pprintf(p, "seek set to %d.\n", parray[p].seek);
+
+ if (v > 0) {
+ pprintf(p, "You will be informed of seek ads when they are "
+ "made.\n");
+ } else {
+ pprintf(p, "You are no longer receiving seek ads.\n");
+ }
+
+ return VAR_OK;
+}
+
+PRIVATE int
+set_rated(int p, char *var, char *val)
+{
+ if (!parray[p].registered) {
+ pprintf(p, "You cannot change your rated status.\n");
+ return VAR_OK;
+ }
+ if (set_boolean_var(&parray[p].rated, val) < 0)
+ return VAR_BADVAL;
+ pprintf(p, "rated set to %d.\n", parray[p].rated);
+ return VAR_OK;
+}
+
+PRIVATE int
+set_shout(int p, char *var, char *val)
+{
+ if (set_boolean_var(&parray[p].i_shout, val) < 0)
+ return VAR_BADVAL;
+ if (parray[p].i_shout)
+ pprintf(p, "You will now hear shouts.\n");
+ else
+ pprintf(p, "You will not hear shouts.\n");
+ return VAR_OK;
+}
+
+PRIVATE int
+set_cshout(int p, char *var, char *val)
+{
+ if (set_boolean_var(&parray[p].i_cshout, val) < 0)
+ return VAR_BADVAL;
+ if (parray[p].i_cshout)
+ pprintf(p, "You will now hear cshouts.\n");
+ else
+ pprintf(p, "You will not hear cshouts.\n");
+ return VAR_OK;
+}
+
+PRIVATE int
+set_kibitz(int p, char *var, char *val)
+{
+ if (set_boolean_var(&parray[p].i_kibitz, val) < 0)
+ return VAR_BADVAL;
+ if (parray[p].i_kibitz)
+ pprintf(p, "You will now hear kibitzes.\n");
+ else
+ pprintf(p, "You will not hear kibitzes.\n");
+ return VAR_OK;
+}
+
+PRIVATE int
+set_kiblevel(int p, char *var, char *val)
+{
+ int v = -1;
+
+ if (!val)
+ return VAR_BADVAL;
+ if (sscanf(val, "%d", &v) != 1)
+ return VAR_BADVAL;
+ if (v < 0 || v > 9999)
+ return VAR_BADVAL;
+
+ parray[p].kiblevel = v;
+ pprintf(p, "Kibitz level now set to: %d.\n", v);
+ return VAR_OK;
+}
+
+PRIVATE int
+set_tell(int p, char *var, char *val)
+{
+ if (set_boolean_var(&parray[p].i_tell, val) < 0)
+ return VAR_BADVAL;
+ if (parray[p].i_tell)
+ pprintf(p, "You will now hear tells.\n");
+ else
+ pprintf(p, "You will not hear tells.\n");
+ return VAR_OK;
+}
+
+PRIVATE int
+set_notifiedby(int p, char *var, char *val)
+{
+ if (set_boolean_var(&parray[p].notifiedby, val) < 0)
+ return VAR_BADVAL;
+ if (parray[p].notifiedby) {
+ pprintf(p, "You will now hear if people notify you, "
+ "but you don't notify them.\n");
+ } else {
+ pprintf(p, "You will not hear if people notify you, "
+ "but you don't notify them.\n");
+ }
+ return VAR_OK;
+}
+
+PRIVATE int
+set_pinform(int p, char *var, char *val)
+{
+ if (set_boolean_var(&parray[p].i_login, val) < 0)
+ return VAR_BADVAL;
+ if (parray[p].i_login)
+ pprintf(p, "You will now hear logins/logouts.\n");
+ else
+ pprintf(p, "You will not hear logins/logouts.\n");
+ return VAR_OK;
+}
+
+PRIVATE int
+set_ginform(int p, char *var, char *val)
+{
+ if (set_boolean_var(&parray[p].i_game, val) < 0)
+ return VAR_BADVAL;
+ if (parray[p].i_game)
+ pprintf(p, "You will now hear game results.\n");
+ else
+ pprintf(p, "You will not hear game results.\n");
+ return VAR_OK;
+}
+
+PRIVATE int
+set_private(int p, char *var, char *val)
+{
+ if (set_boolean_var(&parray[p].private, val) < 0)
+ return VAR_BADVAL;
+ if (parray[p].private)
+ pprintf(p, "Your games will be private.\n");
+ else
+ pprintf(p, "Your games may not be private.\n");
+ return VAR_OK;
+}
+
+PRIVATE int
+set_jprivate(int p, char *var, char *val)
+{
+ if (!parray[p].registered) {
+ pprintf(p, "Unregistered players may not keep a journal.\n");
+ return VAR_OK;
+ }
+ if (set_boolean_var(&parray[p].jprivate, val) < 0)
+ return VAR_BADVAL;
+ if (parray[p].jprivate)
+ pprintf(p, "Your journal will be private.\n");
+ else
+ pprintf(p, "Your journal will not be private.\n");
+ return VAR_OK;
+}
+
+PRIVATE int
+set_automail(int p, char *var, char *val)
+{
+ if (set_boolean_var(&parray[p].automail, val) < 0)
+ return VAR_BADVAL;
+ if (parray[p].automail)
+ pprintf(p, "Your games will be mailed to you.\n");
+ else
+ pprintf(p, "Your games will not be mailed to you.\n");
+ return VAR_OK;
+}
+
+PRIVATE int
+set_mailmess(int p, char *var, char *val)
+{
+ if (!parray[p].registered) {
+ pprintf(p, "Unregistered players may not receive messages.\n");
+ return VAR_OK;
+ }
+ if (set_boolean_var(&parray[p].i_mailmess, val) < 0)
+ return VAR_BADVAL;
+ if (parray[p].i_mailmess)
+ pprintf(p, "Your messages will be mailed to you.\n");
+ else
+ pprintf(p, "Your messages will not be mailed to you.\n");
+ return VAR_OK;
+}
+
+PRIVATE int
+set_pgn(int p, char *var, char *val)
+{
+ if (set_boolean_var(&parray[p].pgn, val) < 0)
+ return VAR_BADVAL;
+ if (parray[p].pgn)
+ pprintf(p, "Games will now be mailed to you in PGN.\n");
+ else
+ pprintf(p, "Games will now be mailed to you in FICS format.\n");
+ return VAR_OK;
+}
+
+PRIVATE int
+set_bell(int p, char *var, char *val)
+{
+ if (set_boolean_var(&parray[p].bell, val) < 0)
+ return VAR_BADVAL;
+ if (parray[p].bell)
+ pprintf(p, "Bell on.\n");
+ else
+ pprintf(p, "Bell off.\n");
+ return VAR_OK;
+}
+
+PRIVATE int
+set_highlight(int p, char *var, char *val)
+{
+ int v = -1;
+
+ if (!val)
+ return VAR_BADVAL;
+ if (sscanf(val, "%d", &v) != 1)
+ return VAR_BADVAL;
+ if (v < 0 || v > 15)
+ return VAR_BADVAL;
+
+ if ((parray[p].highlight = v)) {
+ pprintf(p, "Highlight is now style ");
+ pprintf_highlight(p, "%d", v);
+ pprintf(p, ".\n");
+ } else
+ pprintf(p, "Highlight is off.\n");
+ return VAR_OK;
+}
+
+PRIVATE int
+set_style(int p, char *var, char *val)
+{
+ int v = -1;
+
+ if (!val)
+ return VAR_BADVAL;
+ if (sscanf(val, "%d", &v) != 1)
+ return VAR_BADVAL;
+ if (v < 1 || v > MAX_STYLES)
+ return VAR_BADVAL;
+
+ parray[p].style = (v - 1);
+ pprintf(p, "Style %d set.\n", v);
+ return VAR_OK;
+}
+
+PRIVATE int
+set_flip(int p, char *var, char *val)
+{
+ if (set_boolean_var(&parray[p].flip, val) < 0)
+ return VAR_BADVAL;
+ if (parray[p].flip)
+ pprintf(p, "Flip on.\n");
+ else
+ pprintf(p, "Flip off.\n");
+ return VAR_OK;
+}
+
+PRIVATE int
+set_time(int p, char *var, char *val)
+{
+ int v = -1;
+
+ if (!val)
+ return VAR_BADVAL;
+ if (sscanf(val, "%d", &v) != 1)
+ return VAR_BADVAL;
+ if (v < 0 || v > 240)
+ return VAR_BADVAL;
+
+ parray[p].d_time = v;
+ pprintf(p, "Default time set to %d.\n", v);
+ return VAR_OK;
+}
+
+PRIVATE int
+set_inc(int p, char *var, char *val)
+{
+ int v = -1;
+
+ if (!val)
+ return VAR_BADVAL;
+ if (sscanf(val, "%d", &v) != 1)
+ return VAR_BADVAL;
+ if (v < 0 || v > 300)
+ return VAR_BADVAL;
+
+ parray[p].d_inc = v;
+ pprintf(p, "Default increment set to %d.\n", v);
+ return VAR_OK;
+}
+
+PRIVATE int
+set_interface(int p, char *var, char *val)
+{
+ bool truncated = false;
+ char *cp;
+ size_t size;
+
+ if (val == NULL || strcmp(val, "") == 0)
+ return VAR_BADVAL;
+ else if (!printablestring(val)) {
+ pprintf(p, "%s: val not printable\n", __func__);
+ return VAR_BADVAL;
+ }
+
+ cp = &(parray[p].interface[0]);
+ size = ARRAY_SIZE(parray[p].interface);
+
+ if (strlcpy(cp, val, size) >= size)
+ truncated = true;
+
+ pprintf(p, "Interface set to %s.\n", cp);
+
+ if (truncated)
+ pprintf(p, "Interface was truncated!\n");
+ return VAR_OK;
+}
+
+PRIVATE int
+set_height(int p, char *var, char *val)
+{
+ int v = -1;
+
+ if (!val)
+ return VAR_BADVAL;
+ if (sscanf(val, "%d", &v) != 1)
+ return VAR_BADVAL;
+ if (v < 5 || v > 240)
+ return VAR_BADVAL;
+
+ parray[p].d_height = v;
+ pprintf(p, "Height set to %d.\n", v);
+ return VAR_OK;
+}
+
+PRIVATE int
+set_width(int p, char *var, char *val)
+{
+ int v = -1;
+
+ if (!val)
+ return VAR_BADVAL;
+ if (sscanf(val, "%d", &v) != 1)
+ return VAR_BADVAL;
+ if (v < 32 || v > 240)
+ return VAR_BADVAL;
+
+ parray[p].d_width = v;
+ pprintf(p, "Width set to %d.\n", v);
+ return VAR_OK;
+}
+
+PUBLIC const char *
+Language(unsigned int i)
+{
+ static char *Lang[NUM_LANGS] = {
+ "English",
+ "Spanish",
+ "French",
+ "Danish"
+ };
+
+ _Static_assert(ARRAY_SIZE(Lang) == 4, "Unexpected array size");
+
+ if (i >= ARRAY_SIZE(Lang)) {
+ warnx("%s: invalid arg %u (too large)", __func__, i);
+ return Lang[0];
+ }
+ return Lang[i];
+}
+
+PRIVATE int
+set_language(int p, char *var, char *val)
+{
+ int len, gotIt = -1;
+
+ if (!val)
+ return VAR_BADVAL;
+
+ len = strlen(val);
+
+ for (int i = 0; i < NUM_LANGS; i++) {
+ if (strncasecmp(val, Language(i), len))
+ continue;
+ if (gotIt >= 0)
+ return VAR_BADVAL;
+ else
+ gotIt = i;
+ }
+
+ if (gotIt < 0)
+ return VAR_BADVAL;
+
+ parray[p].language = gotIt;
+
+ pprintf(p, "Language set to %s.\n", Language(gotIt));
+ return VAR_OK;
+}
+
+PRIVATE int
+set_promote(int p, char *var, char *val)
+{
+ if (!val)
+ return VAR_BADVAL;
+
+ stolower(val);
+
+ switch (val[0]) {
+ case 'q':
+ parray[p].promote = QUEEN;
+ pprintf(p, "Promotion piece set to QUEEN.\n");
+ break;
+ case 'r':
+ parray[p].promote = ROOK;
+ pprintf(p, "Promotion piece set to ROOK.\n");
+ break;
+ case 'b':
+ parray[p].promote = BISHOP;
+ pprintf(p, "Promotion piece set to BISHOP.\n");
+ break;
+ case 'n':
+ case 'k':
+ parray[p].promote = KNIGHT;
+ pprintf(p, "Promotion piece set to KNIGHT.\n");
+ break;
+ default:
+ return VAR_BADVAL;
+ }
+
+ return VAR_OK;
+}
+
+PRIVATE int
+set_prompt(int p, char *var, char *val)
+{
+ if (!val) {
+ if (parray[p].prompt && parray[p].prompt != def_prompt)
+ rfree(parray[p].prompt);
+ parray[p].prompt = def_prompt;
+ return VAR_OK;
+ }
+
+ if (!printablestring(val))
+ return VAR_BADVAL;
+
+ if (parray[p].prompt != def_prompt)
+ rfree(parray[p].prompt);
+
+ const size_t size = strlen(val) + 2;
+ parray[p].prompt = rmalloc(size);
+
+ strlcpy(parray[p].prompt, val, size);
+ strlcat(parray[p].prompt, " ", size);
+
+ return VAR_OK;
+}
+
+PRIVATE int
+RePartner(int p, int new)
+{
+ int pOld;
+
+ if (p < 0)
+ return -1;
+
+ pOld = parray[p].partner;
+
+ if (parray[pOld].partner == p) {
+ if (new >= 0) {
+ pprintf_prompt(pOld, "Your partner has just chosen "
+ "a new partner.\n");
+ } else {
+ pprintf_prompt(pOld, "Your partner has just unset "
+ "his/her partner.\n");
+ }
+
+ player_withdraw_offers(pOld, -1, PEND_BUGHOUSE);
+ player_decline_offers(pOld, -1, PEND_BUGHOUSE);
+
+ player_withdraw_offers(p, -1, PEND_BUGHOUSE);
+ player_decline_offers(p, -1, PEND_BUGHOUSE);
+ }
+
+ player_withdraw_offers(p, -1, PEND_PARTNER);
+ player_decline_offers(p, -1, PEND_PARTNER);
+
+ parray[pOld].partner = -1;
+ parray[p].partner = new;
+
+ return new;
+}
+
+PUBLIC int
+com_partner(int p, param_list param)
+{
+ int pNew;
+
+ if (param[0].type == TYPE_NULL) {
+ RePartner(p, -1);
+ return COM_OK;
+ }
+
+ // OK, we're trying to set a new partner.
+ pNew = player_find_part_login(param[0].val.word);
+
+ if (pNew < 0 ||
+ parray[pNew].status == PLAYER_PASSWORD ||
+ parray[pNew].status == PLAYER_LOGIN) {
+ pprintf(p, "No user named \"%s\" is logged in.\n",
+ param[0].val.word);
+ return COM_OK;
+ }
+
+ if (pNew == p) {
+ pprintf(p, "You can't be your own bughouse partner.\n");
+ return COM_OK;
+ }
+
+ /*
+ * Now we know a legit partner has been chosen. Is an offer
+ * pending?
+ */
+ if (player_find_pendfrom(p, pNew, PEND_PARTNER) >= 0) {
+ pprintf(p, "You agree to be %s's partner.\n",
+ parray[pNew].name);
+ pprintf_prompt(pNew, "%s agrees to be your partner.\n",
+ parray[p].name);
+ player_remove_request(pNew, p, PEND_PARTNER);
+
+ // Make the switch.
+ RePartner(p, pNew);
+ RePartner(pNew, p);
+
+ return COM_OK;
+ }
+
+ // This is just an offer. Make sure a new partner is needed.
+ if (parray[pNew].partner >= 0) {
+ pprintf(p, "%s already has a partner.\n", parray[pNew].name);
+ return COM_OK;
+ }
+
+ pprintf(pNew, "\n");
+ pprintf_highlight(pNew, "%s", parray[p].name);
+ pprintf(pNew, " offers to be your bughouse partner; ");
+ pprintf_prompt(pNew, "type \"partner %s\" to accept.\n",
+ parray[p].name);
+ pprintf(p, "Making a partnership offer to %s.\n", parray[pNew].name);
+
+ player_add_request(p, pNew, PEND_PARTNER, 0);
+ return COM_OK;
+}
+
+PRIVATE int
+set_partner(int p, char *var, char *val)
+{
+ if (!val)
+ pprintf(p, "Command is obsolete; type \"partner\" to clear "
+ "your partner\n");
+ else
+ pprintf(p, "Command is obsolete; type \"partner %s\" to "
+ "change your partner\n", val);
+ return VAR_OK;
+}
+
+PRIVATE int
+set_busy(int p, char *var, char *val)
+{
+ if (!val) {
+ parray[p].busy[0] = '\0';
+ pprintf(p, "Your \"busy\" string was cleared.\n");
+ return VAR_OK;
+ }
+
+ if (val && !printablestring(val))
+ return VAR_BADVAL;
+
+ if (strlen(val) > 50) {
+ pprintf(p, "That string is too long.\n");
+ return VAR_BADVAL;
+ }
+
+ strlcpy(parray[p].busy, val, sizeof(parray[p].busy));
+
+ pprintf(p, "Your \"busy\" string was set to \" %s\"\n", parray[p].busy);
+ return VAR_OK;
+}
+
+PRIVATE int
+set_plan(int p, char *var, char *val)
+{
+ int i;
+ int which;
+
+ if (val && !printablestring(val))
+ return VAR_BADVAL;
+ if ((which = atoi(var)) > MAX_PLAN)
+ return VAR_BADVAL;
+ if (which > parray[p].num_plan)
+ which = parray[p].num_plan + 1;
+
+ if (which == 0) { // shove from top
+ if (parray[p].num_plan >= MAX_PLAN) // free the bottom string
+ strfree(parray[p].planLines[parray[p].num_plan - 1]);
+
+ if (parray[p].num_plan) {
+ i = (parray[p].num_plan >= MAX_PLAN ? MAX_PLAN - 1 :
+ parray[p].num_plan);
+ for (; i > 0; i--) {
+ parray[p].planLines[i] =
+ parray[p].planLines[i - 1];
+ }
+ }
+
+ if (parray[p].num_plan < MAX_PLAN)
+ parray[p].num_plan++;
+
+ parray[p].planLines[0] = (val == NULL ? NULL : xstrdup(val));
+
+ pprintf(p, "\nPlan variable %d changed to '%s'.\n", (which + 1),
+ parray[p].planLines[which]);
+ pprintf(p, "All other variables moved down.\n");
+ return VAR_OK;
+ }
+
+ if (which > parray[p].num_plan) { // new line at bottom
+ if (parray[p].num_plan >= MAX_PLAN) { // shove the old lines up
+ if (parray[p].planLines[0] != NULL)
+ rfree(parray[p].planLines[0]);
+ for (i = 0; i < parray[p].num_plan; i++) {
+ parray[p].planLines[i] =
+ parray[p].planLines[i + 1];
+ }
+ } else {
+ parray[p].num_plan++;
+ }
+
+ parray[p].planLines[which - 1] = (val == NULL ? NULL :
+ xstrdup(val));
+ pprintf(p, "\nPlan variable %d changed to '%s'.\n", which,
+ parray[p].planLines[which - 1]);
+ return VAR_OK;
+ }
+
+ which--;
+
+ if (parray[p].planLines[which] != NULL)
+ rfree(parray[p].planLines[which]);
+
+ if (val != NULL) {
+ parray[p].planLines[which] = xstrdup(val);
+ pprintf(p, "\nPlan variable %d changed to '%s'.\n",
+ (which + 1),
+ parray[p].planLines[which]);
+ } else {
+ parray[p].planLines[which] = NULL;
+
+ if (which == (parray[p].num_plan - 1)) { // clear nulls
+ // from bottom
+ while (parray[p].num_plan > 0 &&
+ parray[p].planLines[parray[p].num_plan - 1] ==
+ NULL) {
+ parray[p].num_plan--;
+ pprintf(p, "\nPlan variable %d cleared.\n",
+ (which + 1));
+ }
+ } else if (which == 0) { // clear nulls from top
+ while (which < parray[p].num_plan &&
+ parray[p].planLines[which] == NULL)
+ which++;
+
+ if (which != parray[p].num_plan) {
+ for (i = which; i < parray[p].num_plan; i++) {
+ parray[p].planLines[i - which] =
+ parray[p].planLines[i];
+ }
+ }
+
+ parray[p].num_plan -= which;
+ }
+ }
+
+ return VAR_OK;
+}
+
+PRIVATE int
+set_formula(int p, char *var, char *val)
+{
+ int which;
+ player *me = &parray[p];
+
+#ifdef NO_FORMULAS
+ pprintf(p, "Sorry -- not available because of a bug\n");
+ return COM_OK;
+#else
+ if (isdigit(var[1]))
+ which = var[1] - '1';
+ else
+ which = MAX_FORMULA;
+
+ if (val != NULL) {
+ val = eatwhite(val);
+
+ if (val[0] == '\0')
+ val = NULL;
+ }
+
+ if (!SetValidFormula(p, which, val))
+ return VAR_BADVAL;
+
+ if (which < MAX_FORMULA) {
+ if (val != NULL) {
+ while (me->num_formula < which) {
+ me->formulaLines[me->num_formula] = NULL;
+ (me->num_formula)++;
+ }
+
+ if (me->num_formula <= which)
+ me->num_formula = (which + 1);
+
+ pprintf(p, "Formula variable f%d set to %s.\n",
+ (which + 1),
+ me->formulaLines[which]);
+ return VAR_OK;
+ }
+
+ pprintf(p, "Formula variable f%d unset.\n", (which + 1));
+
+ if (which + 1 >= me->num_formula) {
+ while (which >= 0 && me->formulaLines[which] == NULL)
+ which--;
+ me->num_formula = (which + 1);
+ }
+ } else {
+ if (me->formula != NULL)
+ pprintf(p, "Formula set to %s.\n", me->formula);
+ else
+ pprintf(p, "Formula unset.\n");
+ }
+
+ return VAR_OK;
+#endif
+}
+
+PUBLIC var_list variables[] = {
+ {"automail", set_automail},
+ {"bell", set_bell},
+ {"busy", set_busy},
+ {"cshout", set_cshout},
+ {"flip", set_flip},
+ {"ginform", set_ginform},
+ {"height", set_height},
+ {"highlight", set_highlight},
+ {"i_game", set_ginform},
+ {"i_login", set_pinform},
+ {"inc", set_inc},
+ {"interface", set_interface},
+ {"jprivate", set_jprivate},
+ {"kibitz", set_kibitz},
+ {"kiblevel", set_kiblevel},
+ {"language", set_language},
+ {"mailmess", set_mailmess},
+ {"notifiedby", set_notifiedby},
+ {"open", set_open},
+ {"partner", set_partner},
+ {"pgn", set_pgn},
+ {"pinform", set_pinform},
+ {"private", set_private},
+ {"promote", set_promote},
+ {"prompt", set_prompt},
+ {"rated", set_rated},
+ {"ropen", set_ropen},
+ {"seek", set_seek},
+ {"shout", set_shout},
+ {"simopen", set_sopen},
+ {"style", set_style},
+ {"tell", set_tell},
+ {"time", set_time},
+ {"width", set_width},
+ {"0", set_plan},
+ {"1", set_plan},
+ {"2", set_plan},
+ {"3", set_plan},
+ {"4", set_plan},
+ {"5", set_plan},
+ {"6", set_plan},
+ {"7", set_plan},
+ {"8", set_plan},
+ {"9", set_plan},
+ {"10", set_plan},
+ {"f1", set_formula},
+ {"f2", set_formula},
+ {"f3", set_formula},
+ {"f4", set_formula},
+ {"f5", set_formula},
+ {"f6", set_formula},
+ {"f7", set_formula},
+ {"f8", set_formula},
+ {"f9", set_formula},
+ {"formula", set_formula},
+ {NULL, NULL}
+};
+
+PRIVATE int
+set_find(char *var)
+{
+ int gotIt = -1;
+ int i = 0;
+ size_t len = strlen(var);
+
+ while (variables[i].name) {
+ if (!strncmp(variables[i].name, var, len)) {
+ if (len == strlen(variables[i].name))
+ return i;
+ else if (gotIt >= 0)
+ return -VAR_AMBIGUOUS;
+
+ gotIt = i;
+ }
+
+ i++;
+ }
+
+ if (gotIt >= 0)
+ return gotIt;
+ return -VAR_NOSUCH;
+}
+
+PUBLIC int
+var_set(int p, char *var, char *val, int *wh)
+{
+ int which;
+
+ if (!var)
+ return VAR_NOSUCH;
+ if ((which = set_find(var)) < 0)
+ return -which;
+
+ *wh = which;
+
+ return variables[which].var_func(p, (isdigit(*variables[which].name) ?
+ var : variables[which].name), val);
+}
+
+PUBLIC int
+com_variables(int p, param_list param)
+{
+ int i;
+ int p1, connected;
+
+ if (param[0].type == TYPE_WORD) {
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+ } else {
+ p1 = p;
+ connected = 1;
+ }
+
+ pprintf(p, "Variable settings of %s:\n", parray[p1].name);
+
+ pprintf(p, " time=%-3d inc=%-3d private=%d jprivate=%d "
+ "Lang=%s\n",
+ parray[p1].d_time,
+ parray[p1].d_inc,
+ parray[p1].private,
+ parray[p1].jprivate,
+ Language(parray[p1].language));
+ pprintf(p, " rated=%d ropen=%d open=%d simopen=%d\n",
+ parray[p1].rated,
+ parray[p1].ropen,
+ parray[p1].open,
+ parray[p1].sopen);
+ pprintf(p, " shout=%d cshout=%d kib=%d tell=%d "
+ "notifiedby=%d\n",
+ parray[p1].i_shout,
+ parray[p1].i_cshout,
+ parray[p1].i_kibitz,
+ parray[p1].i_tell,
+ parray[p1].notifiedby);
+ pprintf(p, " pin=%d gin=%d style=%-3d flip=%d "
+ "kiblevel=%d\n",
+ parray[p1].i_login,
+ parray[p1].i_game,
+ (parray[p1].style + 1),
+ parray[p1].flip,
+ parray[p1].kiblevel);
+ pprintf(p, " highlight=%d bell=%d auto=%d mailmess=%d "
+ "pgn=%d\n",
+ parray[p1].highlight,
+ parray[p1].bell,
+ parray[p1].automail,
+ parray[p1].i_mailmess,
+ parray[p1].pgn);
+ pprintf(p, " width=%-3d height=%-3d\n",
+ parray[p1].d_width,
+ parray[p1].d_height);
+
+ if (parray[p1].prompt && parray[p1].prompt != def_prompt)
+ pprintf(p, " Prompt: %s\n", parray[p1].prompt);
+ if (parray[p1].partner >= 0) {
+ pprintf(p, " Bughouse partner: %s\n",
+ parray[parray[p1].partner].name);
+ }
+
+ if (parray[p1].num_formula) {
+ pprintf(p, "\n");
+
+ for (i = 0; i < parray[p1].num_formula; i++) {
+ if (parray[p1].formulaLines[i] != NULL)
+ pprintf(p, " f%d: %s\n", (i + 1),
+ parray[p1].formulaLines[i]);
+ else
+ pprintf(p, " f%d:\n", (i + 1));
+ }
+ }
+
+ if (parray[p1].formula != NULL)
+ pprintf(p, "\nFormula: %s\n", parray[p1].formula);
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+}
diff --git a/FICS/variable.h b/FICS/variable.h
new file mode 100644
index 0000000..ced7878
--- /dev/null
+++ b/FICS/variable.h
@@ -0,0 +1,55 @@
+/* variable.h
+ *
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 24/04/01 Revised
+ Markus Uhlin 24/11/24 Added begin/end decls
+*/
+
+#ifndef _VARIABLE_H
+#define _VARIABLE_H
+
+#include "common.h"
+
+#define VAR_OK 0
+#define VAR_NOSUCH 1
+#define VAR_BADVAL 2
+#define VAR_AMBIGUOUS 3
+
+#define LANG_ENGLISH 0
+#define LANG_SPANISH 1
+#define LANG_FRENCH 2
+#define LANG_DANISH 3
+#define NUM_LANGS 4
+
+typedef struct _var_list {
+ char *name;
+ int (*var_func)(int, char *, char *);
+} var_list;
+
+__FICS_BEGIN_DECLS
+extern var_list variables[];
+
+extern const char *Language(unsigned int);
+extern int var_set(int, char *, char *, int *);
+__FICS_END_DECLS
+
+#endif /* _VARIABLE_H */
diff --git a/FICS/vers.c b/FICS/vers.c
new file mode 100644
index 0000000..e2f56b4
--- /dev/null
+++ b/FICS/vers.c
@@ -0,0 +1,5 @@
+#include "vers.h"
+
+const char SGS_VERS[] = "";
+const char VERS_NUM[] = "rpblc-1.4.6";
+const char COMP_DATE[] = __DATE__;
diff --git a/FICS/vers.h b/FICS/vers.h
new file mode 100644
index 0000000..874c835
--- /dev/null
+++ b/FICS/vers.h
@@ -0,0 +1,32 @@
+/* vers.h
+ */
+
+/*
+ fics - An internet chess server.
+ Copyright (C) 1993 Richard V. Nash
+
+ 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.
+*/
+
+/* Revision history:
+ name email yy/mm/dd Change
+ Richard Nash 93/10/22 Created
+ Markus Uhlin 24/11/23 Made vars const
+*/
+
+#ifndef _VERS_H
+#define _VERS_H
+
+extern const char SGS_VERS[];
+extern const char VERS_NUM[];
+extern const char COMP_DATE[];
+
+#endif /* _VERS_H */