From c3eee8e333866d92e5fd94ae83cef618758c11bb Mon Sep 17 00:00:00 2001 From: Markus Uhlin Date: Mon, 15 Sep 2025 18:50:32 +0200 Subject: FICS RPBLC v1.4.6 --- FICS/GNU_LICENSE | 339 +++++ FICS/HISTORY | 171 +++ FICS/ISC_LICENSE | 13 + FICS/README | 103 ++ FICS/README.timeseal | 16 + FICS/README_LEGAL | 23 + FICS/README_Nash | 37 + FICS/adminproc.c | 2090 +++++++++++++++++++++++++++++++ FICS/adminproc.h | 72 ++ FICS/algcheck.c | 567 +++++++++ FICS/algcheck.h | 40 + FICS/assert_error.c | 12 + FICS/assert_error.h | 10 + FICS/board.c | 1385 +++++++++++++++++++++ FICS/board.h | 158 +++ FICS/build.mk | 95 ++ FICS/command.c | 1382 +++++++++++++++++++++ FICS/command.h | 148 +++ FICS/command_list.h | 285 +++++ FICS/common.h | 73 ++ FICS/comproc.c | 2104 +++++++++++++++++++++++++++++++ FICS/comproc.h | 85 ++ FICS/config.h | 81 ++ FICS/eco.c | 535 ++++++++ FICS/eco.h | 37 + FICS/fics_addplayer.c | 202 +++ FICS/fics_getsalt.cpp | 46 + FICS/fics_getsalt.h | 13 + FICS/ficslim.cpp | 9 + FICS/ficslim.h | 13 + FICS/ficsmain.c | 262 ++++ FICS/ficsmain.h | 77 ++ FICS/formula.c | 705 +++++++++++ FICS/formula.h | 57 + FICS/gamedb.c | 2256 ++++++++++++++++++++++++++++++++++ FICS/gamedb.h | 205 ++++ FICS/gameproc.c | 2164 ++++++++++++++++++++++++++++++++ FICS/gameproc.h | 64 + FICS/iset.cpp | 23 + FICS/iset.h | 11 + FICS/legal.c | 39 + FICS/legal.h | 31 + FICS/legal2.c | 16 + FICS/legal2.h | 10 + FICS/lists.c | 567 +++++++++ FICS/lists.h | 76 ++ FICS/makerank.c | 344 ++++++ FICS/makerank.h | 20 + FICS/matchproc.c | 1430 +++++++++++++++++++++ FICS/matchproc.h | 39 + FICS/maxxes-utils.c | 46 + FICS/maxxes-utils.h | 22 + FICS/movecheck.c | 1608 ++++++++++++++++++++++++ FICS/movecheck.h | 61 + FICS/multicol.c | 172 +++ FICS/multicol.h | 41 + FICS/network.c | 688 +++++++++++ FICS/network.h | 96 ++ FICS/obsproc.c | 2052 +++++++++++++++++++++++++++++++ FICS/obsproc.h | 59 + FICS/playerdb.c | 3278 +++++++++++++++++++++++++++++++++++++++++++++++++ FICS/playerdb.h | 281 +++++ FICS/rating_conv.c | 86 ++ FICS/rating_conv.h | 10 + FICS/ratings.c | 1964 +++++++++++++++++++++++++++++ FICS/ratings.h | 81 ++ FICS/rmalloc.c | 97 ++ FICS/rmalloc.h | 38 + FICS/shutdown.c | 311 +++++ FICS/shutdown.h | 19 + FICS/sought.cpp | 41 + FICS/sought.h | 11 + FICS/talkproc.c | 1086 ++++++++++++++++ FICS/talkproc.h | 56 + FICS/utils.c | 1214 ++++++++++++++++++ FICS/utils.h | 115 ++ FICS/variable.c | 1066 ++++++++++++++++ FICS/variable.h | 55 + FICS/vers.c | 5 + FICS/vers.h | 32 + 80 files changed, 33231 insertions(+) create mode 100644 FICS/GNU_LICENSE create mode 100644 FICS/HISTORY create mode 100644 FICS/ISC_LICENSE create mode 100644 FICS/README create mode 100644 FICS/README.timeseal create mode 100644 FICS/README_LEGAL create mode 100644 FICS/README_Nash create mode 100644 FICS/adminproc.c create mode 100644 FICS/adminproc.h create mode 100644 FICS/algcheck.c create mode 100644 FICS/algcheck.h create mode 100644 FICS/assert_error.c create mode 100644 FICS/assert_error.h create mode 100644 FICS/board.c create mode 100644 FICS/board.h create mode 100644 FICS/build.mk create mode 100644 FICS/command.c create mode 100644 FICS/command.h create mode 100644 FICS/command_list.h create mode 100644 FICS/common.h create mode 100644 FICS/comproc.c create mode 100644 FICS/comproc.h create mode 100644 FICS/config.h create mode 100644 FICS/eco.c create mode 100644 FICS/eco.h create mode 100644 FICS/fics_addplayer.c create mode 100644 FICS/fics_getsalt.cpp create mode 100644 FICS/fics_getsalt.h create mode 100644 FICS/ficslim.cpp create mode 100644 FICS/ficslim.h create mode 100644 FICS/ficsmain.c create mode 100644 FICS/ficsmain.h create mode 100644 FICS/formula.c create mode 100644 FICS/formula.h create mode 100644 FICS/gamedb.c create mode 100644 FICS/gamedb.h create mode 100644 FICS/gameproc.c create mode 100644 FICS/gameproc.h create mode 100644 FICS/iset.cpp create mode 100644 FICS/iset.h create mode 100644 FICS/legal.c create mode 100644 FICS/legal.h create mode 100644 FICS/legal2.c create mode 100644 FICS/legal2.h create mode 100644 FICS/lists.c create mode 100644 FICS/lists.h create mode 100644 FICS/makerank.c create mode 100644 FICS/makerank.h create mode 100644 FICS/matchproc.c create mode 100644 FICS/matchproc.h create mode 100644 FICS/maxxes-utils.c create mode 100644 FICS/maxxes-utils.h create mode 100644 FICS/movecheck.c create mode 100644 FICS/movecheck.h create mode 100644 FICS/multicol.c create mode 100644 FICS/multicol.h create mode 100644 FICS/network.c create mode 100644 FICS/network.h create mode 100644 FICS/obsproc.c create mode 100644 FICS/obsproc.h create mode 100644 FICS/playerdb.c create mode 100644 FICS/playerdb.h create mode 100644 FICS/rating_conv.c create mode 100644 FICS/rating_conv.h create mode 100644 FICS/ratings.c create mode 100644 FICS/ratings.h create mode 100644 FICS/rmalloc.c create mode 100644 FICS/rmalloc.h create mode 100644 FICS/shutdown.c create mode 100644 FICS/shutdown.h create mode 100644 FICS/sought.cpp create mode 100644 FICS/sought.h create mode 100644 FICS/talkproc.c create mode 100644 FICS/talkproc.h create mode 100644 FICS/utils.c create mode 100644 FICS/utils.h create mode 100644 FICS/variable.c create mode 100644 FICS/variable.h create mode 100644 FICS/vers.c create mode 100644 FICS/vers.h (limited to 'FICS') 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. + + + Copyright (C) 19yy + + 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. + + , 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 + +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 + + 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" +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 + +#include +#include +#include +#include +#include + +#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 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 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 " + " 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 + +#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 +#include + +#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 +#include + +#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, " 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:", + " ", + " |R|", + " {Q}", + " =K=" + }; + static char *bp[] = { + " ", + " p ", + " :n:", + " ", + " |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 +# 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 + +#include +#include +#include + +#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, ¶meters))) + 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 + +#include +#include +#include +#include + +#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 +#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, + ®istered, 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, ¶m[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 + +#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 +#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, ¶m[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 +#include + +#include "command.h" +#include "config.h" +#include "fics_getsalt.h" +#include "ficsmain.h" +#include "playerdb.h" +#include "utils.h" + +#if __linux__ +#include +#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] \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 , + * 25 Dec 2023. + */ + +#include +#include +#include + +#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 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 +// SPDX-License-Identifier: ISC + +#include + +#include "ficslim.h" + +const time_t g_time_min = std::numeric_limits::min(); +const time_t g_time_max = std::numeric_limits::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 + +#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 + +#include +#include +#include + +#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 +#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 +#include + +#include +#include + +#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 + * + * 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 + +#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 +#include +#include +#include +#include + +#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 +#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 +#include + +#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 + +#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 +#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, ¶m[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 +// 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 \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 +#include + +#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 + +#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 +#include +#include +#include +#include + +#if __linux__ +#include +#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 + +#include +#include + +#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 +#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 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 +// SPDX-License-Identifier: ISC + +#include +#include +#include +#include + +#if __linux__ +#include +#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 + +#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 +#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 + +#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 + +#include +#include +#include + +#include +#include +#include + +#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 +#include +#include + +#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, ¶m[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, ¶m[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, ¶m[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, ¶m[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, ¶m[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 +#include +#include +#include +#include + +#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 +#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, + <ime_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, + <ime_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, + <ime_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, + <ime_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, + <ime_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, ®istered, 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, ®istered, 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 + +#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 +#include +#include +#include +#include + +#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 +#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 , + * 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 +#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 +// 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 + +#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 +#include +#include +#include +#include + +#include "config.h" +#include "ficsmain.h" +#include "network.h" +#include "playerdb.h" +#include "rmalloc.h" +#include "utils.h" + +#if __linux__ +#include +#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 +#include +#include + +#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 +#include + +#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 +#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 */ -- cgit v1.2.3