From: <z-...@us...> - 2008-01-25 23:10:08
|
Revision: 7625 http://armagetronad.svn.sourceforge.net/armagetronad/?rev=7625&view=rev Author: z-man Date: 2008-01-25 15:10:10 -0800 (Fri, 25 Jan 2008) Log Message: ----------- Documented settings. text input fields now let the text wrap correctly and make use of the space allocated for them in a not-completely-dumb way. And *sigh* depending on the text field, color codes are either ignored (for the usernames) or both displayed in text and rendered. "KICK 2pack" no longer kicks user 2 instead of player 2pack. all admin commands now also accept partial name matches, like /msg, and check for case sensitive matches in the screen name first, then the user name, then case insensitive matches in the two. Added authority black/whitelists. Debug recordings now work with authentication and are safe to share, no secret information is recorded. Modified Paths: -------------- armagetronad/branches/0.2.8-auth/armagetronad/NEWS armagetronad/branches/0.2.8-auth/armagetronad/config/settings_dedicated.cfg armagetronad/branches/0.2.8-auth/armagetronad/language/english_base.txt armagetronad/branches/0.2.8-auth/armagetronad/language/english_base_notranslate.txt armagetronad/branches/0.2.8-auth/armagetronad/src/engine/ePlayer.cpp armagetronad/branches/0.2.8-auth/armagetronad/src/engine/eVoter.cpp armagetronad/branches/0.2.8-auth/armagetronad/src/engine/eVoter.h armagetronad/branches/0.2.8-auth/armagetronad/src/network/nAuthentification.cpp armagetronad/branches/0.2.8-auth/armagetronad/src/network/nKrawall.cpp armagetronad/branches/0.2.8-auth/armagetronad/src/network/nKrawall.h armagetronad/branches/0.2.8-auth/armagetronad/src/network/nKrawallPrivate.cpp armagetronad/branches/0.2.8-auth/armagetronad/src/network/nNetwork.cpp armagetronad/branches/0.2.8-auth/armagetronad/src/network/nNetwork.h armagetronad/branches/0.2.8-auth/armagetronad/src/render/rFont.cpp armagetronad/branches/0.2.8-auth/armagetronad/src/render/rFont.h armagetronad/branches/0.2.8-auth/armagetronad/src/tools/tConfiguration.cpp armagetronad/branches/0.2.8-auth/armagetronad/src/tools/tConfiguration.h armagetronad/branches/0.2.8-auth/armagetronad/src/tools/tString.cpp armagetronad/branches/0.2.8-auth/armagetronad/src/tools/tString.h armagetronad/branches/0.2.8-auth/armagetronad/src/tron/gMenus.cpp armagetronad/branches/0.2.8-auth/armagetronad/src/ui/uInputQueue.cpp armagetronad/branches/0.2.8-auth/armagetronad/src/ui/uInputQueue.h armagetronad/branches/0.2.8-auth/armagetronad/src/ui/uMenu.cpp armagetronad/branches/0.2.8-auth/armagetronad/src/ui/uMenu.h Added Paths: ----------- armagetronad/branches/0.2.8-auth/armagetronad/config/settings_authentication.cfg Modified: armagetronad/branches/0.2.8-auth/armagetronad/NEWS =================================================================== --- armagetronad/branches/0.2.8-auth/armagetronad/NEWS 2008-01-24 18:32:12 UTC (rev 7624) +++ armagetronad/branches/0.2.8-auth/armagetronad/NEWS 2008-01-25 23:10:10 UTC (rev 7625) @@ -51,18 +51,22 @@ console If --enable-armathentication was activated: +- AUTHORITY_WHITELIST and AUTHORITY_BLACKLIST to filter authorities you want on your server. - GLOBAL_ID_ENABLED to toggle remote accounts - LOCAL_USER, LOCAL_TEAM for local login accounts - MD5_PREFIX/MD5_SUFFIX for additional password scrambling - USER_LEVEL to grant users various access levels - RESERVE_SCREEN_NAME to reserve a screen name to a certain player -- ALIAS to bend authentication names around -- (UN)BAN_USER to ban really stupid users based on their global user ID +- USER_ALIAS to bend authentication names around +- (UN)BAN_USER to ban really stupid users based on their global user ID, + BAN_USER_LIST to show a list. - ACCESS_LEVEL_OP/ADMIN/CHAT/PLAY/PLAY_SLIDING to control who can do which things - /login chat command then uses the more secure hashed base logins - new chat commands /op, /deop, /promote and /demote to change other players' access rights Featurelets: +- all admin commands now also accept partial name matches, like /msg, and check for case sensitive + matches in the screen name first, then the user name, then case insensitive matches in the two. - --enable-krawallserver has been actually implemented now, and it enables secure logins to accounts local to the server and not-so-secure logins managed by authentication servers. - A subculture list for server groups that are not managed by our main master servers @@ -109,6 +113,10 @@ - ROUND_SCORE has the player's team's score appended. Bugfixes: +- Text input fields now let the text wrap correctly and make use of the space allocated + for them in a not-completely-dumb way. And *sigh* depending on the text field, color + codes are either ignored (for the usernames) or both displayed in text and rendered. +- "KICK 2pack" no longer kicks user 2 instead of player 2pack. - Fixed various trail end related extrapolation/simulation inaccuracies that looked like lag. - When extrapolating, the game's sensors never detected own or teammates' walls, Added: armagetronad/branches/0.2.8-auth/armagetronad/config/settings_authentication.cfg =================================================================== --- armagetronad/branches/0.2.8-auth/armagetronad/config/settings_authentication.cfg (rev 0) +++ armagetronad/branches/0.2.8-auth/armagetronad/config/settings_authentication.cfg 2008-01-25 23:10:10 UTC (rev 7625) @@ -0,0 +1,299 @@ +######################################################################### +# IMPORTANT: Users should NOT edit this file. Instead, copy the +# lines you want to change into a new file named autoexec.cfg +# ( either here or in your var directory ). +# This file will be overwritten when you upgrade, autoexec.cfg won't. +# Be sure to save the file as plain text, not rich text, especially +# if you're using TextEdit on a Mac. +######################################################################### + +######################################################################### +# +# Policies +# +######################################################################### + +# As all Armagetron subsystems, Authentication is complex and has many +# options for you to tweak. The policies for the default settings were +# chosen with the following priorities: +# 1. Compatibility with old clients +# 2. Security +# 3. General usability +# If you are a very security aware person, you will probably disagree +# with the priority choilce of 1. vs 2, and want to get maximal security, +# even if that means old clients will not be able to authenticate on +# your server; in this case, uncomment the following line: + +# HASH_METHOD_BLACKLIST bmd5 + +# this will disable the hash protocol clients up to 0.2.8.2.1 knew as +# the only one, which is vulnerable to relatively easy man in the +# middle attacks. New clients will, by default, refuse to use it, so +# if the the admins on your server are well educated and protect their +# login data with all means available to them, which includes using an +# up-to-date client, you should be safe even without this setting. +# You may get the occasional faked login from a regular player, though. + +######################################################################### +# +# Local Accounts +# +######################################################################### + +# To help your local users store their passwords, you should change the +# following settings to something server-specific: + +MD5_PREFIX %u: +MD5_SUFFIX :arma + +# those are strings that are appended/prepended to the password before +# the hash function is applied to them. So far, only the md5 protocol +# supports them, bmd5 ignores them. If you put a %u into the strings, +# it will be replaced by the username. This helps combat precomputation +# attacks; for team accounts, it will force the password to be kept in +# memory in plain text, though (not much of a problem). +# You need to set these up before you define the accounts. + +# The following commands are available for you to create local accounts: + +# LOCAL_USER <user name> <user password> + +# Creates an account for a single player. You should restrict the username +# to ASCII characters, best lowercase letters and numbers, and avoid spaces +# and the symbols @, #, \, :, and %. They will still work, but look ugly +# in the logs and on the screen because they all need to get escaped. +# Spaces in the username, if you absolutely must have them, need to +# be quoted or escaped, the user "Foo Bar" can get an account with either +# LOCAL_USER "Foo Bar" <password> +# or +# LOCAL_USER Foo\ Bar <password> +# You can get a literal \ into a usename by putting it there twice. + +# When logged in, local user accounts will appear as <user name>@ in the logs +# and on the screen, and they will have "Local" access rights by default. + +# You can also define accounts for whole teams with + +# LOCAL_TEAM <team name> <team password> + +# Those accouts will allow login from all usernames with a name starting with +# the team name. Users logged in via such an account will appear as +# <user name>@L_TEAM and get the access rights of "Team Member", more +# about that later. Accounts of this type are intended to be used for organized +# tournaments. + +######################################################################### +# +# Remote Accounts +# +######################################################################### + +# We support a distributed authentication system where a user has to only get +# an account at the authority of his choice and use that to authenticate with +# on all participating servers. By default, this system is disabled. Enable +# it by changing the following setting to 1. + +GLOBAL_ID 0 + +# Your server will then make connection to the remote authentication servers +# every time a user will try to authenticate; those connections will happen +# in the background and should not cause too much extra lag. + +# Accounts from remote authentication servers will appear as +# <user name>@<authority> in your logs. + +# Maybe you don't want to accept logins from all authorities. If you want to +# exclude some, put them into this comma separated list: + +AUTHORITY_BLACKLIST + +# If you only want to accept logins from a selected group of authorities, put +# them into this comma separated list: + +AUTHORITY_WHITELIST + +######################################################################### +# +# Access Rights +# +######################################################################### + +# The old, single password inteface to the /admin command is disabled +# when you compile a server with this authentication. Instead, you +# can assign access rights to individual players with + +# USER_LEVEL <user name> <level> + +# The user name is the user's full authentication name as it appears in +# your logs, with all the escapes and character encodings; the "Foo Bar@" +# user from the example above would usually appear as Foo\_Bar@, and that +# is how you need to put him there. The level is the numberic access level; +# lower values are better. The predefined meanings (of course, you can +# disagree and define your own) of these are: + +# Level Meaning Details +# 0 Owner The owner of the server. Commands entered on the +# server console are executed with these rights. +# 1 Admin A server administrator. By default, almost as +# powerful as the owner himself. +# 2 Moderator A server moderator. Is still allowed to use /admin, +# but is restricted to player management commands. +# 7 Team Leader Leader of a team. By default, no admin rights at all. +# 8 Team Member Member of a team. Local team accounts get this level. +# 12 Local User Players with local accounts get this level. +# 15 Authenticated Players with remote accounts get this level. +# 20 Program Unauthenticated players. + +# As you see, lower numeric values mean more access rights. When we say +# "a user needs at least level X to do Y", that means his numeric level +# needs to be smaller or equal than X. + +# Remote (Global ID) accounts run a slightly higher risk of getting +# compromised than local accounts (simpy due to the fact that they +# will get used more often in more locations), so you should restrict +# Admin and Moderator rights to local accounts. To still have your +# Admins and Moderators appear with their Global ID accounts in your +# logs, use this little trick: define them a local account, give +# the rights to that, and define an alias for the local account: + +# LOCAL_USER z-man <password> +# USER_LEVEL z-man@ 1 +# USER_ALIAS z-man@ Z-Man@forums + +# When your admin then logs in to your server under his global account, +# nothing special happens; only when he uses your local account, he +# will get the access rights, and apart from that, there will be no +# differences; he still wil appear under his global account. + +# You can modifiy the minimal access rights required to do certain things. +# First, there are the administrative chat commands. To use /admin, you +# need to be at least + +ACCESS_LEVEL_ADMIN 2 + +# To use the /op, /dop, /promote and /demote ad-hoc access level modifying +# commands, you need ot have at least + +ACCESS_LEVEL_OP 7 + +# and these commands cannot raise the level of a user above + +ACCESS_LEVEL_OP_MAX 2 + +# (and of course, not above the player issuing these commands.) + +# To play on the server, you need to be at least at + +ACCESS_LEVEL_PLAY 20 + +# However, if users of a higher access level than you are present, and +# your level is below + +ACCESS_LEVEL_PLAY_SLIDING 20 + +# you still will not be able to play. This is for servers with +# flexible tournament schedules, there you'll want to raise +# it to the level of 8 (Team Member), so that once the members +# of teams authorized to play on your server log in, all other +# players get removed. + +# And, lastly, to be able to chat, you need at least this level: + +ACCESS_LEVEL_CHAT 20 + +# If you don't have that, everyone on the server will be reminded +# that you want to chat at most every + +ACCESS_LEVEL_CHAT_TIMEOUT 60 + +# seconds. + +# Having sufficient rights to use /admin does not entitle you to +# use all of the commands; they need to be unlocked for you. +# By default, most Admins can use all commands. To change the +# access level required to use a command, use + +# ACCESS_LEVEL <command> <level> + +# That command itself is by default restricted to be used by +# the owner, because it is the master key to all other commands. +# Sensible commands to unlock for moderator use are: + +ACCESS_LEVEL PLAYER_MESSAGE 2 +ACCESS_LEVEL KICK 2 +ACCESS_LEVEL BAN 2 +ACCESS_LEVEL KICK_TO 2 +ACCESS_LEVEL MOVE_TO 2 +ACCESS_LEVEL KILL 2 +ACCESS_LEVEL SILENCE 2 +ACCESS_LEVEL VOICE 2 +ACCESS_LEVEL CONSOLE_MESSAGE 2 +ACCESS_LEVEL CENTER_MESSAGE 2 + +# If you want to give team members access to the basic /admin command, +# you can also allow them to manage players: + +ACCESS_LEVEL ALLOW_TEAM_CHANGE_PLAYER 7 +ACCESS_LEVEL DISALLOW_TEAM_CHANGE_PLAYER 7 + +######################################################################### +# +# Various +# +######################################################################### + +# The log format in ladderlog.txt is picked so that no unauthenticated +# user can ever appear under the same name as an authenticated user. To +# achieve that, @ signs are escaped for unauthenticated users. That +# changes their names relative to the way they appeared in older versions +# of the server. If you rather want to keep the names of unauthenticated +# players as they were before, change this to 1: + +LEGACY_LOG_NAMES 0 + +# Then, instead of mangling unauthenticated names, the authenticated names +# get a 0: prepended before them. + +# Really, really stupid users can be banned via their user ID with + +# BAN_USER <user> + +# Players of average intellect will not be stopped from playing by this, +# but they won't appear in your logs as authenticated and won't collect +# rating points for their account, so maybe this is not so useless as +# it seems. You can revert a ban with + +# UNBAN_USER <user> + +# and get a list with + +# BAN_USER_LIST + +# You can reserve a screen name to a certain user: + +# RESERVE_SCREEN_NAME <user> <screen name> + +# All other players, authenticated or not, will not be able to change their +# screen name to the picked name, then.The command is of course only +# of use if you have set ALLOW_IMPOSTORS to 0. + +# You can bend authenticated user names around with + +# ALIAS <user> <alias> + +# after doing that, a player who authenticates as <user> will appear +# as <alias>. He will get granted the access rights you gave both +# IDs. + +# By default, the authentication names of all players are visible to +# everyone. You can grant your players a certain degree of anonymity +# by hiding the user names of all players of a certain maximal access +# level with + +ACCESS_LEVEL_HIDE_OF 15 + +# However, to users of the minimal access level + +ACCESS_LEVEL_HIDE_TO 2 + +# , all user names are shown at all times. Modified: armagetronad/branches/0.2.8-auth/armagetronad/config/settings_dedicated.cfg =================================================================== --- armagetronad/branches/0.2.8-auth/armagetronad/config/settings_dedicated.cfg 2008-01-24 18:32:12 UTC (rev 7624) +++ armagetronad/branches/0.2.8-auth/armagetronad/config/settings_dedicated.cfg 2008-01-25 23:10:10 UTC (rev 7625) @@ -101,6 +101,15 @@ ############################################################################################ # +# Authentication +# +############################################################################################ + +# if you compiled your server with authentication enabled, you can uncomment the following line: +# SINCLUDE settings_authentication.cfg + +############################################################################################ +# # Public information # ############################################################################################ Modified: armagetronad/branches/0.2.8-auth/armagetronad/language/english_base.txt =================================================================== --- armagetronad/branches/0.2.8-auth/armagetronad/language/english_base.txt 2008-01-24 18:32:12 UTC (rev 7624) +++ armagetronad/branches/0.2.8-auth/armagetronad/language/english_base.txt 2008-01-25 23:10:10 UTC (rev 7625) @@ -2277,8 +2277,6 @@ voted_kill_kick You have been kicked by an angry mob of players; please stay away. network_kill_spectator You have been sitting in spectator mode for too long. -network_kick_notfound Player \1 not found. Remember that the matching is case sensitive.\n - network_ban_kph Kicks per hour of IP \1 are now \2.\n network_ban Players from IP \1 are banned for \2 minutes. Reason: \3\n network_noban Players from IP \1 are no longer banned.\n @@ -2450,8 +2448,8 @@ config_accesslevel_2 Moderator config_accesslevel_7 Team Leader config_accesslevel_8 Team Member -config_accesslevel_9 Local User -config_accesslevel_10 Authenticated +config_accesslevel_12 Local User +config_accesslevel_15 Authenticated config_accesslevel_20 Program access_level_help Changes the access level of a configuration item to make it available to lower ranked users @@ -2497,7 +2495,7 @@ login_request_redundant Already logged in. login_request_failed Login failed. Try again! -login_request_failed_dup Two logins with the same account are not permitted. +login_request_failed_dup Two logins with the same account are not permitted.\n login_not_supported This server does not support authentication of the type you requested, sorry.\n login_request Login with Authority \1 login_request_local Login with Local Account @@ -2505,6 +2503,8 @@ login_request_master Master server requires Login. # access level messages +access_level_hide_of_help Hide user account information of players with at most this access level. +access_level_hide_to_help Hide user account information to players with less than this access level. access_level_op_help Minimal access level for /op, /deop, /promote and /demote commands. access_level_op_max_help Maximal access level attainable by /op and /promote commands. access_level_admin_help Minimal access level for /admin command. @@ -2525,6 +2525,13 @@ access_level_admin_denied 0xff7f7f\1 denied,0xffffff insufficient access level.\n +authority_blacklist_help Comma separated list of authorities your server should refuse to query. +authority_whitelist_help If non-empty, only authorities on this comma separated list will be queried by your server. +trust_lan_help If set to 1, the server assumes that your LAN is safe and that nobody can run a pharming server on it. +hash_method_blacklist_help List of hash authentication methods to disable support for. + +login_message_requested User \1 requests authentication as "\2@\3".\n +login_message_responded Password request sent to user \1, username "\2", method \3, message "\4".\n login_message \3\1 has been logged in as \2.\n login_message_byorder Order of \1: login_message_special \4\1 has been logged in as \2 at access level "\3".\n @@ -2541,6 +2548,9 @@ login_error_invalidurl_slash Authentication URL \1 invalid, double slash or ending with slash. login_error_invalidurl_notfound Authentication URL \1 invalid, it was not found. login_error_noremote Authentication via Global ID not available on this server. +login_error_blacklist Authority \1 is on this server's blacklist. +login_error_whitelist Authority \1 is not on this server's whitelist. +login_error_pharm Server adress mismatch, \1 (sent by client) != \2 (our address). Pharming suspected. If you are connecting from the LAN and get this error, either set "TRUST_LAN" on the server (only if your LAN can be fully trusted, of course) or use "SERVER_IP" to make the local IP known to the server. login_error_methodmismatch The local method used for auhtentication has been modified since your password was set. In the server's config files, put all commands that define authentication methods before all local password definitions. login_error_nomethod No authentication method could be found. Your client supports \1, this server supports \2, and the authentication server supports \3. login_error_nomethodlist Authentication URL \1 does not return a list of supported methods. @@ -2573,7 +2583,7 @@ reserve_screen_namne_usage Usage: RESERVE_SCREEN_NAME <screen name (in quotes if it contains spaces)> <user> reserve_screen_name_change Screen name "\1" reserved for user \2.\n -alias_help Allows bending authenticated names around: a player authenticated as X originally can appear as y. +user_alias_help Allows bending authenticated names around: a player authenticated as X originally can appear as y. alias_usage Usage: ALIAS <user> <alias of user> alias_change User \1 will be known as \2.\n @@ -2581,11 +2591,12 @@ ban_user_message User \1 has been banned.\n unban_user_help Undoes BAN_USER. unban_user_message User \1 has been unbanned.\n +ban_user_list_help Gives a list of banned users. md5_prefix_help Extra hash prefix for local accounts used to scramble the password md5_suffix_help Extra hash suffix for local accounts used to scramble the password -global_id_enabled_help If set to 0, Global IDs (Armathentication) will be disabled on this server. +global_id_help If set to 1, Global IDs (Armathentication) will be enabled on this server. # items that should not be translated include english_base_notranslate.txt Modified: armagetronad/branches/0.2.8-auth/armagetronad/language/english_base_notranslate.txt =================================================================== --- armagetronad/branches/0.2.8-auth/armagetronad/language/english_base_notranslate.txt 2008-01-24 18:32:12 UTC (rev 7624) +++ armagetronad/branches/0.2.8-auth/armagetronad/language/english_base_notranslate.txt 2008-01-25 23:10:10 UTC (rev 7625) @@ -184,12 +184,12 @@ config_accesslevel_4 Moderator-3 config_accesslevel_5 Moderator-4 config_accesslevel_6 Moderator-5 -config_accesslevel_11 OP-ed -config_accesslevel_12 OP-ed-2 -config_accesslevel_13 OP-ed-3 -config_accesslevel_14 OP-ed-4 -config_accesslevel_15 OP-ed-5 -config_accesslevel_16 OP-ed-6 -config_accesslevel_17 OP-ed-7 -config_accesslevel_18 OP-ed-8 -config_accesslevel_19 OP-ed-9 +config_accesslevel_9 Recruit +config_accesslevel_10 Recruit-1 +config_accesslevel_11 Recruit-2 +config_accesslevel_13 OP-ed +config_accesslevel_14 OP-ed-2 +config_accesslevel_16 OP-ed-3 +config_accesslevel_17 OP-ed-4 +config_accesslevel_18 OP-ed-5 +config_accesslevel_19 OP-ed-6 Modified: armagetronad/branches/0.2.8-auth/armagetronad/src/engine/ePlayer.cpp =================================================================== --- armagetronad/branches/0.2.8-auth/armagetronad/src/engine/ePlayer.cpp 2008-01-24 18:32:12 UTC (rev 7624) +++ armagetronad/branches/0.2.8-auth/armagetronad/src/engine/ePlayer.cpp 2008-01-25 23:10:10 UTC (rev 7625) @@ -49,6 +49,7 @@ #include "eVoter.h" #include "tReferenceHolder.h" #include "tRandom.h" +#include "uInputQueue.h" #include "nServerInfo.h" #include "tRecorder.h" #include "nConfig.h" @@ -88,7 +89,7 @@ // if unsed, usernames of non-authenticated players get all special characters escaped (especially all @) // and usernames of authenticated players get left as they are (with all special characters except the last // escaped.) -bool se_legacyLogNames = true; +bool se_legacyLogNames = false; static tSettingItem<bool> se_llnConf("LEGACY_LOG_NAMES", se_legacyLogNames ); // transform special characters in name to escape sequences @@ -455,11 +456,18 @@ failure = true; } + static char const * section = "PASSWORD_MENU"; + tRecorder::Playback( section, failure ); + tRecorder::Record( section, failure ); + // immediately return the stored password if it was not marked as wrong: if (!failure) { - answer.username = storage->username; - answer.scrambled = storage->password; + if ( storage ) + { + answer.username = storage->username; + answer.scrambled = storage->password; + } answer.automatic = true; return; @@ -467,8 +475,10 @@ else storage->username.Clear(); + // scramble input for recording + uInputScrambler scrambler; + // password was not stored. Request it from user: - uMenu login(message, false); // password storage; @@ -476,7 +486,8 @@ eMenuItemPassword pw(&login, password); eMenuItemUserName us(&login, username); - + us.SetColorMode( rTextField::COLOR_IGNORE ); + uMenuItemSelection<int> storepw(&login, "$login_storepw_text", "$login_storepw_help", @@ -503,7 +514,7 @@ // return username/scrambled password { // scramble password - request.ScramblePassword( nKrawall::nScrambleInfo( username), password, scrambled ); + request.ScramblePassword( nKrawall::nScrambleInfo( username ), password, scrambled ); // if S_login still exists, we were not canceled answer.aborted = !eMenuItemPassword::entered; @@ -567,22 +578,71 @@ return min; } +// maximal user level whose accounts are hidden from other users +static tAccessLevel se_hideAccessLevelOf = tAccessLevel_Program; +static tSettingItem< tAccessLevel > se_hideAccessLevelOfConf( "ACCESS_LEVEL_HIDE_OF", se_hideAccessLevelOf ); + +// but they are only hidden to players with a lower access level than this +static tAccessLevel se_hideAccessLevelTo = tAccessLevel_Moderator; +static tSettingItem< tAccessLevel > se_hideAccessLevelToConf( "ACCESS_LEVEL_HIDE_TO", se_hideAccessLevelTo ); + +// determines whether hider can hide from seeker +static bool se_Hide( ePlayerNetID const * hider, ePlayerNetID const * seeker ) +{ + tASSERT( hider ); + if ( !seeker || seeker == hider ) + { + return false; + } + + return + hider->GetAccessLevel() >= se_hideAccessLevelOf && + seeker->GetAccessLevel() > se_hideAccessLevelTo; +} + +// console messages for players who can see users of access level hider +void se_SecretConsoleOut( tOutput const & message, tAccessLevel hider ) +{ + // high enough access levels are never secret + if ( hider < se_hideAccessLevelOf ) + { + sn_ConsoleOut( message ); + } + else + { + bool canSee[ MAXCLIENTS+1 ]; + for( int i = MAXCLIENTS; i>=0; --i ) + { + canSee[i] = false; + } + + // look which clients have someone who can see the message + for ( int i = se_PlayerNetIDs.Len()-1; i>=0; --i ) + { + ePlayerNetID* player = se_PlayerNetIDs(i); + if ( player->GetAccessLevel() <= se_hideAccessLevelTo ) + { + canSee[ player->Owner() ] = true; + } + } + + // and send it + for( int i = MAXCLIENTS; i>=0; --i ) + { + if ( canSee[i] ) + { + sn_ConsoleOut( message, i ); + } + } + } +} + static void ResultCallback( nKrawall::nCheckResult const & result ) { tString username = result.username; tString authority = result.authority; bool success = result.success; - // record and playback result (required because on playback, a new - // salt is generated and this way, a recoding does not contain ANY - // exploitable information for password theft: the scrambled password - // stored in the incoming network stream has an unknown salt value. ) - static char const * section = "AUTH"; - tRecorder::Playback( section, success ); - tRecorder::Playback( section, authority ); - tRecorder::Record( section, success ); - tRecorder::Record( section, authority ); - ePlayerNetID * player = dynamic_cast< ePlayerNetID * >( static_cast< nNetObject * >( result.user ) ); if ( !player || player->Owner() <= 0 ) { @@ -598,7 +658,7 @@ ePlayerNetID* player = se_PlayerNetIDs(i); if ( player->IsAuthenticated() && player->GetRawAuthenticatedName() == authName ) { - sn_ConsoleOut( tOutput("$login_failed_message_dup"), player->Owner() ); + sn_ConsoleOut( tOutput("$login_request_failed_dup"), player->Owner() ); return; } } @@ -620,11 +680,12 @@ { tOutput out( tOutput("$login_failed_message", result.error ) ); sn_ConsoleOut( out, player->Owner() ); + con << out; // redo automatic logins immedeately if ( result.automatic ) { - nAuthentification::RequestLogin( authority ,player->GetUserName(), *player, "$login_request_failed" ); + nAuthentification::RequestLogin( authority ,username , *player, "$login_request_failed" ); } } } @@ -632,6 +693,11 @@ // continue with scheduled logon messages ePlayerNetID::RequestScheduledLogins(); } +#else +static bool se_Hide( ePlayerNetID const * hider, ePlayerNetID const * seeker ) +{ + return false; +} #endif @@ -1162,7 +1228,32 @@ return false; } -ePlayerNetID * CompareBufferToPlayerNames( const tString & current_buffer, int & num_matches ) { +// function that returns one of the player names +typedef tString const & (ePlayerNetID::*SE_NameGetter)() const; + +// function that filters strings +typedef tString (*SE_NameFilter)( tString const & ); + +// identity filter +static tString se_NameFilterID( tString const & name ) +{ + return name; +} + +// function that filters player pairs +typedef bool (*SE_NameHider)( ePlayerNetID const * hider, ePlayerNetID const * seeker ); + +// non-hider +static bool se_NonHide( ePlayerNetID const * hider, ePlayerNetID const * seeker ) +{ + return false; +} + +// the other filter is ePlayerNetID::FilterName + +// search for exact or partial matches in player names +ePlayerNetID * CompareBufferToPlayerNames( const tString & name, int & num_matches, ePlayerNetID * requester, SE_NameGetter GetName = &ePlayerNetID::GetName, SE_NameFilter Filter = &se_NameFilterID, SE_NameHider Hider = &se_NonHide ) +{ num_matches = 0; ePlayerNetID * match = 0; @@ -1170,14 +1261,21 @@ for ( int a = se_PlayerNetIDs.Len()-1; a>=0; --a ) { ePlayerNetID* toMessage = se_PlayerNetIDs(a); + if ( (*Hider)( toMessage, requester ) ) + { + continue; + } + + tString playerName = (*Filter)( (toMessage->*GetName)() ); + // exact match? - if ( current_buffer == toMessage->GetUserName() ) + if ( playerName == name ) { num_matches = 1; return toMessage; } - if ( Contains(current_buffer, toMessage->GetUserName())) { + if ( Contains(name, playerName)) { match= toMessage; // Doesn't matter that this is wrote over everytime, when we only have one match it will be there. num_matches+=1; } @@ -1187,20 +1285,55 @@ return match; } -ePlayerNetID * se_FindPlayerByName( tString const & username, ePlayerNetID * requester = 0 ) +ePlayerNetID * se_FindPlayerByName( tString const & name, ePlayerNetID * requester = 0 ) { int num_matches = 0; - ePlayerNetID * ret = CompareBufferToPlayerNames( username, num_matches ); - + // look for matches in the exact player names first + ePlayerNetID * ret = CompareBufferToPlayerNames( name, num_matches, requester ); if ( ret && num_matches == 1 ) { return ret; } + // ok, next round: try filtering the names before comparing them, this makes the matching case-insensitive + SE_NameFilter Filter = &ePlayerNetID::FilterName; + + // look for matches in the screen names again + if ( !ret ) + { + ret = CompareBufferToPlayerNames( name, num_matches, requester, &ePlayerNetID::GetName, Filter ); + } + if ( ret && num_matches == 1 ) + { + return ret; + } + +#ifdef KRAWALL_SERVER + // nothing found? try the user names. + if ( !ret ) + { + ret = CompareBufferToPlayerNames( name, num_matches, requester, &ePlayerNetID::GetUserName, &se_NameFilterID, &se_Hide ); + } + if ( ret && num_matches == 1 ) + { + return ret; + } + + // still nothing found? user names again + if ( !ret ) + { + ret = CompareBufferToPlayerNames( name, num_matches, requester, &ePlayerNetID::GetUserName, Filter, &se_Hide ); + } + if ( ret && num_matches == 1 ) + { + return ret; + } +#endif + // More than than one match for the current buffer. Complain about that. else if (num_matches > 1) { - tOutput toSender( "$msg_toomanymatches", username ); + tOutput toSender( "$msg_toomanymatches", name ); if ( requester ) { sn_ConsoleOut(toSender,requester->Owner() ); @@ -1213,7 +1346,7 @@ } // 0 matches. The user can't spell. Complain about that, too. else { - tOutput toSender( "$msg_nomatch", username ); + tOutput toSender( "$msg_nomatch", name ); if ( requester ) { sn_ConsoleOut(toSender,requester->Owner() ); @@ -1226,24 +1359,18 @@ } } -#ifdef DEDICATED -#ifdef KRAWALL_SERVER -static ePlayerNetID * se_FindPlayerInChatCommand( ePlayerNetID * sender, char const * command, tString const & say ) +static ePlayerNetID * se_FindPlayerInChatCommand( ePlayerNetID * sender, char const * command, std::istream & s ) { - tString params(""); - if (say.StrPos(" ") == -1) + tString player; + s >> player; + + if (player == "" ) { sn_ConsoleOut( tOutput( "$chatcommand_requires_player", command ), sender->Owner() ); } - else - { - params = say.SubStr(say.StrPos(" ") + 1); - } - return se_FindPlayerByName( params, sender ); + return se_FindPlayerByName( player, sender ); } -#endif -#endif // chat message from server to client void handle_chat_client( nMessage & ); @@ -1607,7 +1734,7 @@ static tAccessLevel se_adminAccessLevel = tAccessLevel_Moderator; static tSettingItem< tAccessLevel > se_adminAccessLevelConf( "ACCESS_LEVEL_ADMIN", se_adminAccessLevel ); -void handle_command_intercept(tJUST_CONTROLLED_PTR< ePlayerNetID > &p, tString say) { +void handle_command_intercept(ePlayerNetID *p, tString say) { con << "[cmd] " << *p << ": " << say << '\n'; } @@ -1623,11 +1750,11 @@ // an operation that changes the access level of another player typedef void (*OPFUNC)( ePlayerNetID * admin, ePlayerNetID * victim ); -static void se_ChangeAccess( ePlayerNetID * admin, tString say, char const * command, OPFUNC F ) +static void se_ChangeAccess( ePlayerNetID * admin, std::istream & s, char const * command, OPFUNC F ) { if ( admin->GetAccessLevel() <= se_opAccessLevel ) { - ePlayerNetID * victim = se_FindPlayerInChatCommand( admin, command, say ); + ePlayerNetID * victim = se_FindPlayerInChatCommand( admin, command, s ); if ( victim ) { if ( victim == admin ) @@ -1702,10 +1829,10 @@ { victim->SetAccessLevel( accessLevel ); - sn_ConsoleOut( tOutput( "$access_level_promote", - victim->GetLogName(), - tCurrentAccessLevel::GetName( accessLevel ), - admin->GetLogName() ) ); + se_SecretConsoleOut( tOutput( "$access_level_promote", + victim->GetLogName(), + tCurrentAccessLevel::GetName( accessLevel ), + admin->GetLogName() ), victim->GetAccessLevel() ); } else { @@ -1721,12 +1848,12 @@ if ( accessLevel <= tAccessLevel_Authenticated ) { + se_SecretConsoleOut( tOutput( "$access_level_demote", + victim->GetLogName(), + tCurrentAccessLevel::GetName( accessLevel ), + admin->GetLogName() ), victim->GetAccessLevel() ); + victim->SetAccessLevel( accessLevel ); - - sn_ConsoleOut( tOutput( "$access_level_demote", - victim->GetLogName(), - tCurrentAccessLevel::GetName( accessLevel ), - admin->GetLogName() ) ); } else if ( victim->IsAuthenticated() ) { @@ -1736,136 +1863,151 @@ #endif // KRAWALL -void handle_chat_admin_commands(tJUST_CONTROLLED_PTR< ePlayerNetID > &p, tString say){ - // for the duration of this function, set the access level to the level of the user. - tCurrentAccessLevel levelOverride( p->GetAccessLevel() ); - - if (say.StartsWith("/login")) { - tString params(""); - if (say.StrPos(" ") == -1) - { +// log in (via admin password or hash based login) +static void se_AdminLogin( ePlayerNetID * p, tString const & say ) +{ + tString params(""); + if (say.StrPos(" ") == -1) + { #ifndef KRAWALL_SERVER - return; + return; #endif - } - else - { - params = say.SubStr(say.StrPos(" ") + 1); - } - + } + else + { + params = say.SubStr(say.StrPos(" ") + 1); + } + #ifndef KRAWALL_SERVER - // the password is not stored in the recording, hence we have to store the - // result of the password test - bool accept = true; - static const char * section = "REMOTE_LOGIN"; - if ( !tRecorder::Playback( section, accept ) ) - accept = ( params == sg_adminPass && sg_adminPass != "NONE" ); - tRecorder::Record( section, accept ); - - //change this later to read from a password file or something... - //or integrate it with auth if we ever get that done... - if ( accept ) { - se_AdminLogin( p ); - } - else + // the password is not stored in the recording, hence we have to store the + // result of the password test + bool accept = true; + static const char * section = "REMOTE_LOGIN"; + if ( !tRecorder::Playback( section, accept ) ) + accept = ( params == sg_adminPass && sg_adminPass != "NONE" ); + tRecorder::Record( section, accept ); + + //change this later to read from a password file or something... + //or integrate it with auth if we ever get that done... + if ( accept ) { + se_AdminLogin( p ); + } + else + { + tString failedLogin; + sn_ConsoleOut("Login denied!\n",p->Owner()); + failedLogin << "Remote admin login for user \"" << p->GetUserName(); + failedLogin << "\" using password \"" << params << "\" rejected.\n"; + sn_ConsoleOut(failedLogin, 0); + } +#else + if ( sn_GetNetState() == nSERVER && p->Owner() != sn_myNetID ) + { + if ( p->IsAuthenticated() ) { - tString failedLogin; - sn_ConsoleOut("Login denied!\n",p->Owner()); - failedLogin << "Remote admin login for user \"" << p->GetUserName(); - failedLogin << "\" using password \"" << params << "\" rejected.\n"; - sn_ConsoleOut(failedLogin, 0); + sn_ConsoleOut( "$login_request_redundant", p->Owner() ); + return; } -#else - if ( sn_GetNetState() == nSERVER && p->Owner() != sn_myNetID ) + + if ( p->GetRawAuthenticatedName().Len() <= 1 || params.StrPos("@") >= 0 ) { - if ( p->IsAuthenticated() ) + if ( params.StrPos( "@" ) >= 0 ) { - sn_ConsoleOut( "$login_request_redundant", p->Owner() ); - return; + p->SetRawAuthenticatedName( params ); } - - if ( p->GetRawAuthenticatedName().Len() <= 1 || params.StrPos("@") >= 0 ) + else { - if ( params.StrPos( "@" ) >= 0 ) - { - p->SetRawAuthenticatedName( params ); - } - else - { - p->SetRawAuthenticatedName( p->GetUserName() + "@" + params ); - } + p->SetRawAuthenticatedName( p->GetUserName() + "@" + params ); } - - // check for stupid bans - if ( se_IsUserBanned( p, p->GetRawAuthenticatedName() ) ) - { - return; - } - - p->loginWanted = true; - - se_RequestLogin( p ); } + + // check for stupid bans + if ( se_IsUserBanned( p, p->GetRawAuthenticatedName() ) ) + { + return; + } + + p->loginWanted = true; + + se_RequestLogin( p ); + } #endif +} + +// log out +static void se_AdminLogout( ePlayerNetID * p ) +{ +#ifdef KRAWALL_SERVER + // revoke the other kind of authentication as well + if ( p->IsAuthenticated() ) + { + p->DeAuthenticate(); } - else if (say.StartsWith("/logout")) { -#ifdef KRAWALL_SERVER - // revoke the other kind of authentication as well - if ( p->IsAuthenticated() ) - { - p->DeAuthenticate(); - } #else - if ( p->IsLoggedIn() ) - { - sn_ConsoleOut("You have been logged out!\n",p->Owner()); - } - p->BeNotLoggedIn(); + if ( p->IsLoggedIn() ) + { + sn_ConsoleOut("You have been logged out!\n",p->Owner()); + } + p->BeNotLoggedIn(); #endif +} + +// /admin chat command +static void se_AdminAdmin( ePlayerNetID * p, std::istream & s ) +{ + if ( p->GetAccessLevel() > se_adminAccessLevel ) + { + sn_ConsoleOut( tOutput( "$access_level_admin_denied" ), p->Owner() ); + return; } + + // install filter + eAdminConsoleFilter consoleFilter( p->Owner() ); + + if ( tRecorder::IsPlayingBack() ) + { + tConfItemBase::LoadPlayback(); + } + else + { + tConfItemBase::LoadAll(s); + } +} + +static void handle_chat_admin_commands( ePlayerNetID * p, tString const & say, std::istream & s ) +{ + // for the duration of this function, set the access level to the level of the user. + tCurrentAccessLevel levelOverride( p->GetAccessLevel() ); + + if (say.StartsWith("/login")) + { + se_AdminLogin( p, say ); + } + else if (say.StartsWith("/logout")) + { + se_AdminLogout( p ); + } #ifdef KRAWALL_SERVER else if (say.StartsWith("/op")) { - se_ChangeAccess( p, say, "/op", &se_Op ); + se_ChangeAccess( p, s, "/op", &se_Op ); } else if (say.StartsWith("/deop")) { - se_ChangeAccess( p, say, "/deop", &se_DeOp ); + se_ChangeAccess( p, s, "/deop", &se_DeOp ); } else if (say.StartsWith("/promote")) { - se_ChangeAccess( p, say, "/promote", &se_Promote ); + se_ChangeAccess( p, s, "/promote", &se_Promote ); } else if (say.StartsWith("/demote")) { - se_ChangeAccess( p, say, "/demote", &se_Demote ); + se_ChangeAccess( p, s, "/demote", &se_Demote ); } #endif - else if (say.StartsWith("/admin")) { - if ( p->GetAccessLevel() > se_adminAccessLevel ) - { - sn_ConsoleOut( tOutput( "$access_level_admin_denied" ), p->Owner() ); - return; - } - - tString params(""); - if (say.StrPos(" ") == -1) - return; - else - params = say.SubStr(say.StrPos(" ") + 1); - - // install filter - eAdminConsoleFilter consoleFilter( p->Owner() ); - - if ( tRecorder::IsPlayingBack() ) - { - tConfItemBase::LoadPlayback(); - } - else - { - std::stringstream s(static_cast< char const * >( params ) ); - tConfItemBase::LoadAll(s); - } + else if (say.StartsWith("/admin")) + { + se_AdminAdmin( p, s ); } else if (se_interceptUnknownCommands) @@ -1902,37 +2044,12 @@ static tSettingItem<bool> se_silAll("SILENCE_ALL", se_silenceAll); - -// checks whether a player is silenced, giving him appropriate warnings if he is -bool IsSilencedWithWarning( ePlayerNetID const * p ) -{ - if ( !se_enableChat && ! p->IsLoggedIn() ) - { - // everyone except the admins is silenced - sn_ConsoleOut( tOutput( "$spam_protection_silenceall" ), p->Owner() ); - return true; - } - else if ( p->IsSilenced() ) - { - if(se_silenceAll) { - // player is silenced, but all players are silenced by default - sn_ConsoleOut( tOutput( "$spam_protection_silenced_default" ), p->Owner() ); - } else { - // player is specially silenced - sn_ConsoleOut( tOutput( "$spam_protection_silenced" ), p->Owner() ); - } - return true; - } - - return false; -} - // handles spam checking at the right time class eChatSpamTester { public: - eChatSpamTester( REAL severity, ePlayerNetID * p, tString const & say ) - : tested_( false ), shouldBlock_( false ), severity_( severity ), player_( p ), say_( say ) + eChatSpamTester( ePlayerNetID * p, tString const & say ) + : tested_( false ), shouldBlock_( false ), player_( p ), say_( say ) { } @@ -1961,8 +2078,14 @@ } } - if ( nSpamProtection::Level_Mild <= player_->chatSpam_.CheckSpam( severity_, player_->Owner(), tOutput("$spam_chat") ) ) + REAL lengthMalus = say_.Len() / 20.0; + if ( lengthMalus > 4.0 ) { + lengthMalus = 4.0; + } + + if ( nSpamProtection::Level_Mild <= player_->chatSpam_.CheckSpam( 1+lengthMalus, player_->Owner(), tOutput("$spam_chat") ) ) + { return true; } @@ -2003,12 +2126,272 @@ bool tested_; bool shouldBlock_; - REAL severity_; ePlayerNetID * player_; tString say_; }; -void handle_chat(nMessage &m){ +// checks whether a player is silenced, giving him appropriate warnings if he is +bool IsSilencedWithWarning( ePlayerNetID const * p ) +{ + if ( !se_enableChat && ! p->IsLoggedIn() ) + { + // everyone except the admins is silenced + sn_ConsoleOut( tOutput( "$spam_protection_silenceall" ), p->Owner() ); + return true; + } + else if ( p->IsSilenced() ) + { + if(se_silenceAll) { + // player is silenced, but all players are silenced by default + sn_ConsoleOut( tOutput( "$spam_protection_silenced_default" ), p->Owner() ); + } else { + // player is specially silenced + sn_ConsoleOut( tOutput( "$spam_protection_silenced" ), p->Owner() ); + } + return true; + } + + return false; +} + +// /me chat commant +static void se_ChatMe( ePlayerNetID * p, std::istream & s, eChatSpamTester & spam ) +{ + if ( IsSilencedWithWarning(p) || spam.Block() ) + { + return; + } + + tString msg; + msg.ReadLine( s ); + + tColoredString console; + console << tColoredString::ColorString(1,1,1) << "* "; + console << *p; + console << tColoredString::ColorString(1,1,.5) << " " << msg; + console << tColoredString::ColorString(1,1,1) << " *"; + + tColoredString forOldClients; + forOldClients << tColoredString::ColorString(1,1,1) << "*" + << tColoredString::ColorString(1,1,.5) << msg + << tColoredString::ColorString(1,1,1) << "*"; + + se_BroadcastChatLine( p, console, forOldClients ); + console << "\n"; + sn_ConsoleOut(console,0); + return; +} + +// /teamleave chat command: leaves the current team +static void se_ChatTeamLeave( ePlayerNetID * p ) +{ + if ( se_assignTeamAutomatically ) + { + sn_ConsoleOut(tOutput("$player_teamleave_disallowed"), p->Owner() ); + return; + } + if(!p->TeamChangeAllowed()) { + sn_ConsoleOut(tOutput("$player_disallowed_teamchange"), p->Owner() ); + return; + } + + eTeam * leftTeam = p->NextTeam(); + if ( leftTeam ) + { + if ( !leftTeam ) + leftTeam = p->CurrentTeam(); + + if ( leftTeam->NumPlayers() > 1 ) + { + sn_ConsoleOut( tOutput( "$player_leave_team_wish", + tColoredString::RemoveColors(p->GetName()), + tColoredString::RemoveColors(leftTeam->Name()) ) ); + } + else + { + sn_ConsoleOut( tOutput( "$player_leave_game_wish", + tColoredString::RemoveColors(p->GetName()) ) ); + } + } + + p->SetTeamWish(0); +} + +// team shuffling: reorders team formation +static void se_ChatShuffle( ePlayerNetID * p, std::istream & s ) +{ + tString msg; + msg.ReadLine( s ); + + // team position shuffling. Allow players to change their team setup. + // syntax: + // /teamshuffle: shuffles you all the way to the outside. + // /teamshuffle <pos>: shuffles you to position pos + // /teamshuffle +/-<dist>: shuffles you dist to the outside/inside + // con << msgRest << "\n"; + int IDNow = p->TeamListID(); + if (!p->CurrentTeam()) + { + sn_ConsoleOut(tOutput("$player_not_on_team"), p->Owner()); + return; + } + int len = p->CurrentTeam()->NumPlayers(); + int IDWish = len-1; // default to shuffle to the outside + + // but read the target position as additional parameter + if (msg.Len() > 1) + { + IDWish = IDNow; + if ( msg[0] == '+' ) + IDWish += msg.toInt(1); + else if ( msg[0] == '-' ) + IDWish -= msg.toInt(1); + else + IDWish = msg.toInt()-1; + } + + if (IDWish < 0) + IDWish = 0; + if (IDWish >= len) + IDWish = len-1; + + if ( !se_allowShuffleUp && IDWish < IDNow ) + { + sn_ConsoleOut(tOutput("$player_noshuffleup"), p->Owner()); + return; + } + + if( IDNow == IDWish ) + { + sn_ConsoleOut(tOutput("$player_noshuffle"), p->Owner()); + } + + p->CurrentTeam()->Shuffle( IDNow, IDWish ); +} + +// /team chat commant: talk to your team +static void se_ChatTeam( ePlayerNetID * p, std::istream & s, eChatSpamTester & spam ) +{ + // odd, the refactored original did not check for silence. Probably by design. + if ( /* IsSilencedWithWarning(player) || */ spam.Block() ) + { + return; + } + + tString msg; + msg.ReadLine( s ); + + eTeam *currentTeam = p->CurrentTeam(); + + if (currentTeam != NULL) // If a player has just joined the game, he is not yet on a team. Sending a /team message will crash the server + { + // Log message to server and sender + tColoredString messageForServerAndSender = se_BuildChatString(currentTeam, p, msg); + messageForServerAndSender << "\n"; + sn_ConsoleOut(messageForServerAndSender, 0); + sn_ConsoleOut(messageForServerAndSender, p->Owner()); + + // Send message to team-mates + int numTeamPlayers = currentTeam->NumPlayers(); + for (int teamPlayerIndex = 0; teamPlayerIndex < numTeamPlayers; teamPlayerIndex++) { + if (currentTeam->Player(teamPlayerIndex)->Owner() != p->Owner()) // Do not resend the message to yourself + se_SendTeamMessage(currentTeam, p, currentTeam->Player(teamPlayerIndex), msg); + } + } + else + { + sn_ConsoleOut(tOutput("$player_not_on_team"), p->Owner()); + } +} + +// /team chat commant: talk to your team +static void se_ChatMsg( ePlayerNetID * p, std::istream & s, eChatSpamTester & spam ) +{ + // odd, the refactored original did not check for silence. Probably by design. + if ( /* IsSilencedWithWarning(player) || */ spam.Block() ) + { + return; + } + + // Check for player + ePlayerNetID * receiver = se_FindPlayerInChatCommand( p, "/msg", s ); + + // One match, send it. + if ( receiver ) { + // extract rest of message: it is the true message to send + std::ws(s); + + // read the rest of the message + tString msg_core; + msg_core.ReadLine(s); + + // build chat string + tColoredString toServer = se_BuildChatString( p, receiver, msg_core ); + toServer << '\n'; + + // log locally + sn_ConsoleOut(toServer,0); + + if ( p->CurrentTeam() == receiver->CurrentTeam() || !IsSilencedWithWarning(p) ) + { + // log to sender's console + sn_ConsoleOut(toServer, p->Owner()); + + // send to receiver + if ( p->Owner() != receiver->Owner() ) + se_SendPrivateMessage( p, receiver, msg_core ); + } + } +} + +static void ListPlayers( ePlayerNetID * receiver ) +{ + for ( int i2 = se_PlayerNetIDs.Len()-1; i2>=0; --i2 ) + { + ePlayerNetID* p2 = se_PlayerNetIDs(i2); + std::ostringstream tos; + tos << p2->Owner(); + tos << ": "; + if ( p2->GetLastAccessLevel() < tAccessLevel_Default && !se_Hide( p2, receiver ) ) + { + // player username comes from authentication name and may be much different from + // the screen name + tos << p2->GetUserName() << " ( " << p2->GetName() << ", " + << tCurrentAccessLevel::GetName( p2->GetAccessLevel() ) + << " )"; + } + else + { + tos << p2->GetName(); + } + tos << "\n"; + + if ( receiver ) + { + sn_ConsoleOut(tos.str().c_str(), receiver->Owner()); + } + else + { + con << tos.str(); + } + } +} + +static void players_conf(std::istream &s) +{ + ListPlayers( 0 ); +} + +static tConfItemFunc players("PLAYERS",&players_conf); + +// /players gives a player list +static void se_ChatPlayers( ePlayerNetID * p ) +{ + ListPlayers( p ); +} + +void handle_chat( nMessage &m ) +{ nTimeRolling currentTime = tSysTimeFloat(); unsigned short id; m.Read(id); @@ -2032,24 +2415,17 @@ return; } - // spam protection: - REAL lengthMalus = say.Len() / 20.0; - if ( lengthMalus > 4.0 ) - { - lengthMalus = 4.0; - } + eChatSpamTester spam( p, say ); - eChatSpamTester spamChatTester( lengthMalus, p, say ); - - if (say.StartsWith("/")) { std::string sayStr(say); std::istringstream s(sayStr); + tString command; s >> command; - tString msg; tConfItemBase::EatWhitespace(s); - msg.ReadLine(s); + + // now, s is ready for reading the rest of the message. #ifdef DEDICATED if (se_InterceptCommands.StrPos(command) != -1) { @@ -2059,228 +2435,45 @@ else #endif if (command == "/me") { - if ( IsSilencedWithWarning(p) ) - return; - if ( spamChatTester.Block() ) - { - return; - } - - tColoredString console; - console << tColoredString::ColorString(1,1,1) << "* "; - console << *p; - console << tColoredString::ColorString(1,1,.5) << " " << msg; - console << tColoredString::ColorString(1,1,1) << " *"; - - tColoredString forOldClients; - forOldClients << tColoredString::ColorString(1,1,1) << "*" - << tColoredString::ColorString(1,1,.5) << msg - << tColoredString::ColorString(1,1,1) << "*"; - - se_BroadcastChatLine( p, console, forOldClients ); - console << "\n"; - sn_ConsoleOut(console,0); + se_ChatMe( p, s, spam ); return; } else if (command == "/teamleave") { - if ( se_assignTeamAutomatically ) - { - sn_ConsoleOut(tOutput("$player_teamleave_disallowed"), p->Owner() ); - return; - } - if(!p->TeamChangeAllowed()) { - sn_ConsoleOut(tOutput("$player_disallowed_teamchange"), p->Owner() ); - return; - } - - eTeam * leftTeam = p->NextTeam(); - if ( leftTeam ) - { - if ( !leftTeam ) - leftTeam = p->CurrentTeam(); - - if ( leftTeam->NumPlayers() > 1 ) - { - sn_ConsoleOut( tOutput( "$player_leave_team_wish", - tColoredString::RemoveColors(p->GetName()), - tColoredString::RemoveColors(leftTeam->Name()) ) );... [truncated message content] |