Scripts:ReserveSlots

From BF2 Technical Information Wiki
Jump to navigation Jump to search

This will open a file "knownplayers.txt" and use it to maintain a list of players who should get priority.

To use it, log in with rcon and type "rcon addPlayer playerName" or "rcon removePlayer playerName". You can change the server mode with "rcon changeMode mode" where mode is public, private or semiprivate. Public is anything goes, private is list only and semiprivate let's anyone join but will kick players off the list to make room for players on the list if the server gets full. Semiprivate will only kick commanders as a last resort.

There's also "rcon numFromList" to get the number of people on the server who are on the list. "rcon isOnList playername" is obvious, and "rcon currMode" will tell you what mode the server is in.

Enjoy!

Call this file kickoutsiders.py and put it in your standard_admin folder along with a knownplayers.txt file.

kickoutsiders.py:

import host
import bf2

scriptName = 'Doc & sirSolarius Kicker v1.7'
fileName = 'admin/standard_admin/knownplayers.txt'
knownPlayers = []
mode = 1


def init():
    print "Starting " + scriptName
    global knownPlayers, fileName
    playersFile = file(fileName, 'r')
    for i in playersFile:
        knownPlayers.append(i.rstrip())
    playersFile.close()
    print knownPlayers
    host.registerHandler('PlayerConnect', onPlayerConnect, 1)


def onPlayerConnect(aPlayer):
    global knownPlayers, mode
   
    # private
    if mode == 0:
        if not aPlayer.getName() in knownPlayers:
            host.rcon_invoke('game.sayAll "' + scriptName + ': Player ' + aPlayer.getName() + ' was not on the list!' + '"')
            host.rcon_invoke('admin.kickPlayer ' + str(aPlayer.index))
   
    # semiprivate
    elif mode == 1:
        #if we're at capacity (new person fills our extra spot)
        if bf2.playerManager.getNumberOfPlayers() ==  bf2.serverSettings.getMaxPlayers():
            # if he's not on the list, put him back where he came from
            if not aPlayer.getName() in knownPlayers:
                host.rcon_invoke('game.sayAll "' + scriptName + ': Player ' + aPlayer.getName() + ' kicked: no room for people off the list!' + '"')
                host.rcon_invoke('admin.kickPlayer ' + str(aPlayer.index))
                return
            # he's on the list: kick someone who isn't
            else:
                currPlayers=bf2.playerManager.getPlayers()
                foundCommander=0
                commander=aPlayer
                for i in currPlayers:
                    # if we found someone to toss out
                    if not i.getName() in knownPlayers:
                        # if he's the commander, try to find someone else
                        if i.isCommander():
                            commander=i
                            foundCommander=1
                        else:
                            host.rcon_invoke('game.sayAll "' + scriptName + ': Player ' + i.getName() + ' kicked for ' + aPlayer.getName() + '"')
                            host.rcon_invoke('admin.kickPlayer ' + str(i.index))
                            return
                # if we checked everyone and only the commander remains... bye bye
                if foundCommander == 1:
                    host.rcon_invoke('game.sayAll "' + scriptName + ': Player ' + commander.getName() + ' kicked for ' + aPlayer.getName() + '"')
                    host.rcon_invoke('admin.kickPlayer ' + str(commander.index))
                else:
                    # no one to kick =(
                    #host.rcon_invoke('game.sayAll "' + scriptName + ': Player ' + aPlayer.getName() + ' kicked: no room even for people on the list!' + '"')
                    #host.rcon_invoke('admin.kickPlayer ' + str(aPlayer.index))
                    return

def changeMode(m):
    global mode, knownPlayers, scriptName
    if m == 'private':
        host.rcon_invoke('game.sayAll "' + scriptName + ': Changing to private mode.  Flushing the undesirables!"')
        currPlayers=bf2.playerManager.getPlayers()
        for i in currPlayers:
            if not i.getName() in knownPlayers:
                host.rcon_invoke('admin.kickPlayer ' + str(i.index))
               
        mode=0
    elif m == 'semiprivate':
        host.rcon_invoke('game.sayAll "' + scriptName + ': Changing to semiprivate mode.  Bring on some asshats!"')
        mode=1
    else:
        host.rcon_invoke('game.sayAll "' + scriptName + ': Changing to public mode.  Bring on the asshats!"')
        mode=2
   
def addPlayerToList(aPlayerName):
    global fileName, knownPlayers
    if aPlayerName in knownPlayers:
        print 'User already in list!'
        return
    playersFile = file(fileName, 'a')
    playersFile.write(aPlayerName+'\n')
    knownPlayers.append(aPlayerName)
    print 'User added to list.'
   
def writeMode(ctx):
    global mode
    if (mode == 0):
        ctx.write('The current mode is private.')
    elif(mode == 1):
        ctx.write('The current mode is semiprivate.')
    else:
        ctx.write('The current mode is public.')

def writeNumList(ctx):
    global knownPlayers
    playerCount=0
    currPlayers=bf2.playerManager.getPlayers()
    for i in currPlayers:
        if i.getName() in knownPlayers:
            playerCount=playerCount+1       
    ctx.write('There are %i players from the list online.'  % (playerCount) )

def isOnList(player, ctx):
    global knownPlayers
    if player in knownPlayers:
        ctx.write('Player ' + player + ' is registered on the list.')
    else:
        ctx.write('Player ' + player + ' is *not* on the list.  Do what you must with him.')

def removePlayerFromList(aPlayerName):
    global fileName, knownPlayers
    if not aPlayerName in knownPlayers:
        return
    knownPlayers.remove(aPlayerName)
    playersFile = file(fileName, 'w')
    playersFile.truncate(0)
    for aPlayer in knownPlayers:
        playersFile.write(aPlayer+'\n')
    playersFile.close()

Now overwrite the default.py file in your admin folder with this.

default.py:

# vim: ts=4 noexpandtab
#
# Battlefield II -- default remote console module.
#
# This is a very simple, TCP based remote console which does simple MD5 digest
# password authentication. It can be configured via the admin/default.cfg file.
# Recognized options are 'port' and 'password'.
#
# Implementation guidelines for this file (and for your own rcon modules):
#
#  - All socket operations MUST be non-blocking. If your module blocks on a
#    socket the game server will hang.
#
#  - Do as little work as possible in update() as it runs in the server main
#    loop.
#
# Other notes:
#
#  - To get end-of-message markers (0x04 hex) after each reply, begin your
#    commands with an ascii 0x02 code. This module will then append the
#    end-of-message marker to all results. This is useful if you need to wait
#    for a complete response.
#
# Copyright (c)2004 Digital Illusions CE AB
# Author: Andreas `dep' Fredriksson

import socket
import errno
import host
import bf2
import types
import md5
import string
import random
import standard_admin.kickoutsiders

options = {
    'port': '4711',
    'password': None,

    # True if multiple commands should be processed in one update and
    # if as many responses as possible should be sent each update.
    'allowBatching': False
}

# Returns a seed string of random characters to be used as a salt to protect
# password sniffing.
def make_seed(seed_length):
    return ''.join([string.ascii_letters[random.randint(0, len(string.ascii_letters)-1)] for x in xrange(0, seed_length)])

# Concats a seed string with the password and returns an ASCII-hex MD5 digest.
def digest(seed, pw):
    if not pw: return None
    m = md5.new()
    m.update(seed)
    m.update(pw)
    return m.hexdigest()

# Parses the config file, if it's there
def parseConfig():
    def boolFromString (str):
        if str in ['True', 'true', '1']:
            return True
        elif value in ['False', 'false', '0']:
            return False
        else:
            raise ValueError

    fn = 'admin/default.cfg'
    try:
        config = open(fn, 'r')
        lineNo = 0
        for line in config:
            lineNo += 1
            if line.strip() != '' and line.strip() != '\n':
                try:
                    (key, value) = line.split('=')
                    key = key.strip()
                    value = value.strip()
                   
                    if key == 'allowBatching': value = boolFromString (value)
                    options[key] = value
                   
                except ValueError:
                    print 'warning: syntax error in "%s" on line %d' % (fn, lineNo)
    except IOError, detail:
        print 'warning: couldn\'t read "%s": %s' % (fn, detail)

# A stateful output buffer that knows how to enqueue data and ship it out
# without blocking.
class OutputBuffer(object):

    def __init__(self, sock):
        self.sock = sock
        self.data = []
        self.index = 0

    def enqueue(self, str):
        self.data.append(str)

    def update(self):
        allowBatching = options['allowBatching']
        while len(self.data) > 0:
            try:
                item = self.data[0]
                scount = self.sock.send(item[self.index:])
                self.index += scount
                if self.index == len(item):
                    del self.data[0]
                    self.index = 0
            except socket.error, detail:
                if detail[0] != errno.EWOULDBLOCK:
                    return detail[1]
            if not allowBatching:
                break
        return None

# Each TCP connection is represented by an object of this class.
class AdminConnection(object):

    def __init__(self, srv, sock, addr):
        print 'new rcon/admin connection from %s:%d' % (addr[0], addr[1])
        self.server = srv
        self.sock = sock
        self.addr = addr
        self.sock.setblocking(0)
        self.buffer = ''
        self.seed = make_seed(16)
        self.correct_digest = digest(self.seed, options['password'])
        self.outbuf = OutputBuffer(self.sock)
       
        # Welcome message *must* end with \n\n
        self.outbuf.enqueue('### Battlefield 2 default RCON/admin ready.\n')
        self.outbuf.enqueue('### Digest seed: %s\n' % (self.seed))
        self.outbuf.enqueue('\n') # terminate welcome message with extra LF

    def update(self):
        err = None
        try:
            allowBatching = options['allowBatching']
            while not err:
                data = self.sock.recv(1024)
                if data:
                    self.buffer += data
                    while not err:
                        nlpos = self.buffer.find('\n')
                        if nlpos != -1:
                            self.server.onRemoteCommand(self, self.buffer[0:nlpos])
                            self.buffer = self.buffer[nlpos+1:] # keep rest of buffer
                        else:
                            if len(self.buffer) > 128:
                                err = 'data format error: no newline in message'
                            break
                        if not allowBatching: break
                else:
                    err = 'peer disconnected'
               
                if not allowBatching: break

        except socket.error, detail:
            if detail[0] != errno.EWOULDBLOCK:
                err = detail[1]

        if not err:
            err = self.outbuf.update()

        if err:
            print 'rcon: closing %s:%d: %s' % (self.addr[0], self.addr[1], err)
            try:
                self.sock.shutdown(2)
                self.sock.close()
            except:
                print 'rcon: warning: failed to close %s' % (self.addr)
                pass
            return 0
        else:
            return 1

# Context passed to remote command implementations for them to write output to
# either a remote tcp socket or an in-game client executing 'rcon <command>'.
class CommandContext(object):
    def __init__(self):
        self.player = None
        self.socket = None
        self.output = []

    def isInGame(self): return self.player is not None
    def isSocket(self): return self.socket is not None
    def write(self, text): self.output.append(text)

# The server itself.
class AdminServer(object):

    def __init__(self, port):
        # state for tcp rcon connections
        self.port = port
        self.backlog = 1
        self.peers = []
        self.openSocket()

        # state for in-game rcon connections
        host.registerHandler('RemoteCommand', self.onRemoteCommand, 1)
        host.registerHandler('PlayerDisconnect', self.onPlayerDisconnect, 1)
        host.registerHandler('ChatMessage', self.onChatMessage, 1)

        # contains player ids for players which have successfully authenticated
        # themselves with 'rcon login <passwd>'
        self.authed_players = {}
        # contains sockets for connections which have successfully authenticated
        # themselves with 'login <passwd>'
        self.authed_sockets = {}

        # rcon commands supported in this vanilla version
        self.rcon_cmds = {
            'login': self.rcmd_login,
            'users': self.rcmd_users,
            'exec': self.rcmd_exec,
            'addPlayer': self.rcmd_addPlayer,
            'removePlayer': self.rcmd_removePlayer,
            'changeMode': self.rcmd_changeMode,
            'currentMode': self.rcmd_currMode,
            'numFromList': self.rcmd_numFromList,
            'isOnList': self.rcmd_isOnList
        }

    # Called when a user types 'rcon ' followed by any string in a client
    # console window or when a TCP client sends a complete line to be
    # evaluated.
    def onRemoteCommand(self, playerid_or_socket, cmd):
        cmd = cmd.strip()
        interactive = True

        # Is this a non-interactive client?
        if len(cmd) > 0 and cmd[0] == '\x02':
            cmd = cmd[1:]
            interactive = False

        spacepos = cmd.find(' ')
        if spacepos == -1: spacepos=len(cmd)
        subcmd = cmd[0:spacepos]

        ctx = CommandContext()
        authed = 0
        if type(playerid_or_socket) == types.IntType:
            ctx.player = playerid_or_socket
            authed = self.authed_players.has_key(ctx.player)
        else:
            ctx.socket = playerid_or_socket
            authed = self.authed_sockets.has_key(ctx.socket)
           
        # you can only login unless you are authenticated
        if subcmd != 'login' and not authed:
            ctx.write('error: not authenticated: you can only invoke \'login\'\n')
        else:
            if self.rcon_cmds.has_key(subcmd):
                self.rcon_cmds[subcmd](ctx, cmd[spacepos+1:])
            else:
                ctx.write('unknown command: \'%s\'\n' % (subcmd))

        feedback = ''.join(ctx.output)
        if ctx.socket:
            if interactive:
                ctx.socket.outbuf.enqueue(feedback)
            else:
                ctx.socket.outbuf.enqueue(feedback + '\x04')
        else:
            host.rcon_feedback(ctx.player, feedback)

    # When players disconnect, remove them from the auth map if they were
    # authenticated so that the next user with the same id doesn't get rcon
    # access.
    def onPlayerDisconnect(self, player_id):
        if self.authed_players.has_key(player_id):
            del self.authed_players[player_id]

    # Called whenever a player issues a chat string.
    def onChatMessage(self, player_id, text, channel, flags):
        print 'chat: pid=%d text=\'%s\' channel=%s' % (player_id, text, channel)

    # Sets up the listening TCP RCON socket. This binds to 0.0.0.0, which may
    # not be what you want but it's a sane default for most installations.
    def openSocket(self):
        try:
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            #self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, 0)
            #self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.sock.bind(('0.0.0.0', self.port))
            self.sock.listen(self.backlog)
            self.sock.setblocking(0)
        except socket.error, detail:
            print 'failed to bind rcon socket--only in-game rcon will be enabled'

    # WARNING: update is called very frequently -- don't go crazy with logic
    # here.
    def update(self):
        # if we don't have a socket, just return
        if not self.sock: return

        # without blocking, check for new connections
        try:
            conn, peeraddr = self.sock.accept()
            self.peers.append(AdminConnection(self, conn, peeraddr))
        except socket.error, detail:
            if detail[0] != errno.EWOULDBLOCK:
                raise socket.error, detail

        # update clients and mark connections that fail their update
        disc = []
        for client in self.peers:
            if not client.update(): disc.append(client)

        # delete any auth status for closed tcp connections
        for d in disc:
            if self.authed_sockets.has_key(d): del self.authed_sockets[d]

        # now keep the remaining clients
        self.peers = filter(lambda x: x not in disc, self.peers)

    def shutdown(self):
        if self.sock:
            self.sock.close()

    # Command implementations go here (member functions of the AdminServer)

    # Allows a in-game rcon client to authenticate and get access.
    def rcmd_login(self, ctx, cmd):
        success = 0
        if ctx.isInGame():
            # We're called by an in-game rcon client, use plain-text password
            # (encoded into bf2 network stream).
            if cmd.strip() == options['password']:
                    self.authed_players[ctx.player] = 1
                    success = 1
            elif self.authed_players.has_key(ctx.player):
                    del self.authed_players[ctx.player]
        else:
            # tcp client, require seeded digest to match instead of pw
            if cmd.strip() == ctx.socket.correct_digest:
                self.authed_sockets[ctx.socket] = 1
                print 'rcon: tcp client from %s:%d logged on' % ctx.socket.addr
                success = 1
            else:
                if self.authed_sockets.has_key(ctx.socket):
                    del self.authed_sockets[ctx.socket]
                print 'rcon: tcp client from %s:%d failed pw challenge' % ctx.socket.addr

        if success:
            ctx.write('Authentication successful, rcon ready.\n')
        else:
            ctx.write('Authentication failed.\n')

    # Lists rcon-authenticated players.
    def rcmd_users(self, ctx, cmd):
        ctx.write('active rcon users:\n')
        for id in self.authed_players:
            if id == -1:
                ctx.write('-1 (local server console)\n')
            else:
                try:
                    player = bf2.playerManager.getPlayerByIndex(id)
                    ctx.write('%d from %s name=\'%s\'\n' % (id, player.getAddress(), player.getName()))
                except:
                    ctx.write('%d (no info)\n' % (id))

        for peer in self.authed_sockets:
            ctx.write('tcp: %s:%d\n' % (peer.addr[0], peer.addr[1]))

    # Executes a console command on the server.
    def rcmd_exec(self, ctx, cmd):
        ctx.write(host.rcon_invoke(cmd))
       
    def rcmd_addPlayer(self, ctx, cmd):
        ctx.write('Adding player ' + cmd)
        standard_admin.kickoutsiders.addPlayerToList(cmd)
       
    def rcmd_removePlayer(self, ctx, cmd):
        ctx.write('Removing player ' + cmd)
        standard_admin.kickoutsiders.removePlayerFromList(cmd)
       
    def rcmd_changeMode(self, ctx, cmd):
        standard_admin.kickoutsiders.changeMode(cmd)
       
    def rcmd_currMode(self, ctx, cmd):
        standard_admin.kickoutsiders.writeMode(ctx)
       
    def rcmd_numFromList(self, ctx, cmd):
        standard_admin.kickoutsiders.writeNumList(ctx)
       
    def rcmd_isOnList(self, ctx, cmd):
        standard_admin.kickoutsiders.isOnList(cmd, ctx)

# parse the configuration file
parseConfig()

# our single server instance
server = AdminServer(int(options['port']))

# These functions are called from the engine -- we implement them in terms of a
# class instance:


def init():
    print 'initializing default admin/rcon module'
   
    # load (optional) admin scripts like teamkill punish and autobalance
    import standard_admin


def shutdown():
    if server:
        print 'shutting down default admin/rcon module'
        server.shutdown()

def update():
    if server: server.update()

note from Milton: Wow this script seems to be really good, unfortunately I cannot test it because I am using BF2CC, do you think that you could modify the default.py that BF2CC has in order to make it so that we can use both BF2CC and your script?

note from Brian:I've adapted the script for the BF2CC app. Enjoy!

default.py

# vim: ts=4 noexpandtab
#
# Battlefield II -- default remote console module.
#
# This is a very simple, TCP based remote console which does simple MD5 digest
# password authentication. It can be configured via the admin/default.cfg file.
# Recognized options are 'port' and 'password'.
#
# Implementation guidelines for this file (and for your own rcon modules):
#
#  - All socket operations MUST be non-blocking. If your module blocks on a
#    socket the game server will hang.
#
#  - Do as little work as possible in update() as it runs in the server main
#    loop.
#
# Other notes:
#
#  - To get end-of-message markers (0x04 hex) after each reply, begin your
#    commands with an ascii 0x02 code. This module will then append the
#    end-of-message marker to all results. This is useful if you need to wait
#    for a complete response.
#
# Copyright (c)2004 Digital Illusions CE AB
# Author: Andreas `dep' Fredriksson
#
# This version of this script is managed by the BF2CC crew.
#
# CHANGE LOG
# June 13 - Hooked in BF2CC.py file.  Seperating BF2CC custom commands from EA's customs commands.
#
# June 25 - Added exception handlers for all functions.
# June 25 - Added python log file support for errors and 'print' statements in the python code. (redir of stdout) options - pythonlogfile
# June 25 - Added support for alternative IP binding.  options - ip
#
# Version 3.5
# July 5 - Added some security related fixes
# July 5 - Added player rank into player list function

import socket
import errno
import host
import bf2
import types
import md5
import string
import random
import bf2cc # Added BF2CC June 13, 2005
import sys
import standard_admin.kickoutsiders

# Set these options in the default.cfg
options = {
    'port': '4711',
    'password': None,
        'ip': '0.0.0.0', # Added BF2CC rbarone
        'pythonlogfile': 'pythonlog.txt' # Added BF2CC rbarone
}

# Function added BF2CC rbarone
def printExceptionTrace(e, location):
        # generate line number
        lineNum = ''
        execInfo = sys.exc_info()
        tb = execInfo[2]
        while tb is not None:
                if lineNum == '':
                        lineNum = str(tb.tb_lineno)
                else:
                        lineNum += ', ' + str(tb.tb_lineno)
                tb = tb.tb_next
       
        print('**** ERROR ERROR ERROR ****\n  Exception generated in %s\n     %s\n     %s\n     (Line(s): %s)\n' % (location, str(execInfo[0]), str(e), lineNum))
        if options['pythonlogfile'] != '':
                sys.stdout.flush()

# Returns a seed string of random characters to be used as a salt to protect
# password sniffing.
def make_seed(seed_length):
    return ''.join([string.ascii_letters[random.randint(0, len(string.ascii_letters)-1)] for x in xrange(0, seed_length)])

# Concats a seed string with the password and returns an ASCII-hex MD5 digest.
def digest(seed, pw):
    if not pw: return None
    m = md5.new()
    m.update(seed)
    m.update(pw)
    return m.hexdigest()

# Parses the config file, if it's there
def parseConfig():
    def boolFromString (str):
        if str in ['True', 'true', '1']:
            return True
        elif value in ['False', 'false', '0']:
            return False
        else:
            raise ValueError

    fn = 'admin/default.cfg'
    try:
        config = open(fn, 'r')
        lineNo = 0
        for line in config:
            lineNo += 1
            if line.strip() != '' and line.strip() != '\n':
                try:
                    (key, value) = line.split('=')
                    key = key.strip()
                    value = value.strip()
                   
                    #if key == 'allowBatching': value = boolFromString (value) # Removed BF2CC rbarone
                    options[key] = value
                   
                except ValueError:
                    print 'warning: syntax error in "%s" on line %d' % (fn, lineNo)
    except IOError, detail:
        print 'warning: couldn\'t read "%s": %s' % (fn, detail)
        except Exception, e:
                printExceptionTrace(e, 'parseConfig')
               
        if options['pythonlogfile'] != '':
                global stdoutFlushTimer
               
                stdoutFlushTimer = bf2.Timer(onFlushStdout, 10, 1)
                stdoutFlushTimer.setRecurring(10)

                sys.stdout = file(options['pythonlogfile'], 'a')

def onFlushStdout(data):
        try:
                sys.stdout.flush()
        except:
                print 'could not flush'
                pass
               
# A stateful output buffer that knows how to enqueue data and ship it out
# without blocking.
class OutputBuffer(object):
    def __init__(self, sock):
        self.sock = sock
        self.data = []
        self.index = 0

    def enqueue(self, str):
        try:                    # Added BF2CC rbaron
                        self.data.append(str)
                except Exception, e:
                        printExceptionTrace(e, 'enqueue')

    def update(self):
        allowBatching = True # Changed BF2CC rbarone - Always set to true
        while len(self.data) > 0:
            try:
                item = self.data[0]
                scount = self.sock.send(item[self.index:])
                self.index += scount
                if self.index == len(item):
                    del self.data[0]
                    self.index = 0
            except socket.error, detail:
                if detail[0] != errno.EWOULDBLOCK:
                    return detail[1]
                        except Exception, e:
                                printExceptionTrace(e, 'OutputBuffer.update')
                       
                        # if not allowBatching: # Removed BF2CC rbarone
            #    break
        return None

# Each TCP connection is represented by an object of this class.
class AdminConnection(object):

    def __init__(self, srv, sock, addr):
        try:               
                        print 'new rcon/admin connection from %s:%d' % (addr[0], addr[1])
                        self.server = srv
                        self.sock = sock
                        self.addr = addr
                        self.sock.setblocking(0)
                        self.buffer = ''
                        self.seed = make_seed(16)
                        self.correct_digest = digest(self.seed, options['password'])
                        self.outbuf = OutputBuffer(self.sock)
                                       
                        self.bf2ccClient = bf2cc.Client(srv.bf2ccServer) # Added BF2CC June 13, 2005 rbarone
       
                        # Welcome message *must* end with \n\n
                        self.outbuf.enqueue('### Battlefield 2 version ' + bf2cc.scriptVersion + ' ' + bf2cc.serverType + ' default RCON/admin ready.\n') # Changed BF2CC - Added script version rbarone
                        self.outbuf.enqueue('### Digest seed: %s\n' % (self.seed))
                        self.outbuf.enqueue('\n') # terminate welcome message with extra LF
                except Exception, e:
                        printExceptionTrace(e, 'AdminConnection.__init__')
                       
    def update(self):
        err = None
        try:
            allowBatching = True # Changed BF2CC - Always set to true
            while not err:
                                data = self.sock.recv(1024)
                                if data:
                                        self.buffer += data
                                        while not err:
                                                nlpos = self.buffer.find('\n')
                                                if nlpos != -1:
                                                        self.server.onRemoteCommand(self, self.buffer[0:nlpos])
                                                        self.buffer = self.buffer[nlpos+1:] # keep rest of buffer
                                                else:
                                                        if len(self.buffer) > 128:
                                                                err = 'data format error: no newline in message'
                                                        break
                                                #if not allowBatching: break # Removed BF2CC rbarone
                                else:
                                        err = 'peer disconnected'
                                       
                #if not allowBatching: break #Removed BF2CC rbarone

        except socket.error, detail:
            if detail[0] != errno.EWOULDBLOCK:
                err = detail[1]
                except Exception, e:
                        printExceptionTrace(e, 'AdminConnection.update')
                        err = 'Exception in AdminConnection.update'

                try:

                        if not err:
                                err = self.outbuf.update()
       
                        if err:
                                print 'rcon: closing %s:%d: %s' % (self.addr[0], self.addr[1], err)
                                self.bf2ccClient.UnregisterClient() # Added BF2CC June 13, 2005 rbarone
                                try:
                                        self.bf2ccClient = None
                                        self.sock.shutdown(2)
                                        self.sock.close()
                                except:
                                        print 'rcon: warning: failed to close %s' % (self.addr)
                                        pass
                                return 0
                        else:
                                return 1
                except Exception, e:
                        printExceptionTrace(e, 'AdminConnection.update')
                        err = 'Exception in AdminConnection.update'

# Context passed to remote command implementations for them to write output to
# either a remote tcp socket or an in-game client executing 'rcon <command>'.
class CommandContext(object):
    def __init__(self):
        self.player = None
        self.socket = None
        self.output = []

    def isInGame(self): return self.player is not None
    def isSocket(self): return self.socket is not None
    def write(self, text): self.output.append(text)

# The server itself.
class AdminServer(object):

    def __init__(self, port):
        try:               
                        # state for tcp rcon connections
                        self.port = port
                        self.backlog = 1
                        self.peers = []
                        self.openSocket()
       
                        # state for in-game rcon connections
                        host.registerHandler('RemoteCommand', self.onRemoteCommand, 1)
                        host.registerHandler('PlayerDisconnect', self.onPlayerDisconnect, 1)
                        host.registerHandler('ChatMessage', self.onChatMessage, 1)
       
                        # contains player ids for players which have successfully authenticated
                        # themselves with 'rcon login <passwd>'
                        self.authed_players = {}
                        # contains sockets for connections which have successfully authenticated
                        # themselves with 'login <passwd>'
                        self.authed_sockets = {}
       
                        # rcon commands supported in this vanilla version
                        self.rcon_cmds = {
                                'login': self.rcmd_login,
                                'users': self.rcmd_users,
                                'exec': self.rcmd_exec,
                                'bf2cc': self.rcmd_bf2cc,
                                'addPlayer': self.rcmd_addPlayer,
                                'removePlayer': self.rcmd_removePlayer,
                                'changeMode': self.rcmd_changeMode,
                                'currMode': self.rcmd_currMode,
                                'numFromList': self.rcmd_numFromList,
                                'isOnList': self.rcmd_isOnList,
                        }
                       
                        self.bf2ccServer = bf2cc.Server(self) # Added BF2CC June 13, 2005
                        #self.bf2ccServer.RegisterServer(self) # Added BF2CC June 13, 2005
                except Exception, e:
                        printExceptionTrace(e, 'AdminServer.__init__')
                        raise e

    # Called when a user types 'rcon ' followed by any string in a client
    # console window or when a TCP client sends a complete line to be
    # evaluated.
    def onRemoteCommand(self, playerid_or_socket, cmd):
        try:
                        cmd = cmd.strip()
                        interactive = True
       
                        # Is this a non-interactive client?
                        if len(cmd) > 0 and cmd[0] == '\x02':
                                cmd = cmd[1:]
                                interactive = False
       
                        spacepos = cmd.find(' ')
                        if spacepos == -1: spacepos=len(cmd)
                        subcmd = cmd[0:spacepos]
       
                        ctx = CommandContext()
                        authed = 0
                        if type(playerid_or_socket) == types.IntType:
                                ctx.player = playerid_or_socket
                                authed = self.authed_players.has_key(ctx.player)
                        else:
                                ctx.socket = playerid_or_socket
                                authed = self.authed_sockets.has_key(ctx.socket)
                               
                        # you can only login unless you are authenticated
                        if subcmd != 'login' and not authed:
                                ctx.write('error: not authenticated: you can only invoke \'login\'\n')
                        else:
                                if self.rcon_cmds.has_key(subcmd):
                                        self.rcon_cmds[subcmd](ctx, cmd[spacepos+1:])
                                else:
                                        ctx.write('unknown command: \'%s\'\n' % (subcmd))
       
                        feedback = ''.join(ctx.output)
                        if ctx.socket:
                                if interactive:
                                        ctx.socket.outbuf.enqueue(feedback)
                                else:
                                        ctx.socket.outbuf.enqueue(feedback + '\x04')
                        else:
                                host.rcon_feedback(ctx.player, feedback)
                except Exception, e:
                        printExceptionTrace(e, 'AdminServer.onRemoteCommand')

    # When players disconnect, remove them from the auth map if they were
    # authenticated so that the next user with the same id doesn't get rcon
    # access.
    def onPlayerDisconnect(self, player):
        try:
                        if self.authed_players.has_key(player.index):
                                print('Removing PlayerID: %s' % str(player.index))
                                del self.authed_players[player.index]
                except Exception, e:
                        printExceptionTrace(e, 'AdminServer.onPlayerDisconnect')


    # Called whenever a player issues a chat string.
    def onChatMessage(self, player_id, text, channel, flags):
        try:
                        print 'chat: pid=%d text=\'%s\' channel=%s' % (player_id, text, channel)
                except Exception, e:
                        printExceptionTrace(e, 'AdminServer.onChatMessage')


    # Sets up the listening TCP RCON socket. This binds to 0.0.0.0, which may
    # not be what you want but it's a sane default for most installations.
    def openSocket(self):
        try:
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            #self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, 0)
            #self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.sock.bind((options['ip'], self.port))
            self.sock.listen(self.backlog)
            self.sock.setblocking(0)
        except socket.error, detail:
            print 'failed to bind rcon socket.  Check the IP and port configured, it may be in use by another program.'
                        self.sock = None
                except Exception, e:
                        printExceptionTrace(e, 'AdminServer.openSocket')
                       

    # WARNING: update is called very frequently -- don't go crazy with logic
    # here.
    def update(self):
        try:
                        # if we don't have a socket, just return
                        if not self.sock: return
       
                        # without blocking, check for new connections
                        try:
                                conn, peeraddr = self.sock.accept()
                                self.peers.append(AdminConnection(self, conn, peeraddr))
                        except socket.error, detail:
                                if detail[0] != errno.EWOULDBLOCK:
                                        raise socket.error, detail
       
                        # update clients and mark connections that fail their update
                        disc = []
                        for client in self.peers:
                                if not client.update(): disc.append(client)
       
                        # delete any auth status for closed tcp connections
                        for d in disc:
                                if self.authed_sockets.has_key(d): del self.authed_sockets[d]
       
                        # now keep the remaining clients
                        self.peers = filter(lambda x: x not in disc, self.peers)
                except Exception, e:
                        printExceptionTrace(e, 'AdminServer.update')

    def shutdown(self):
        try:
                        if self.sock:
                                self.sock.close()
                except Exception, e:
                        printExceptionTrace(e, 'AdminServer.shutdown')
                               

    # Command implementations go here (member functions of the AdminServer)

    # Allows a in-game rcon client to authenticate and get access.
    def rcmd_login(self, ctx, cmd):
        try:
                        success = 0
                        if ctx.isInGame():
                                # We're called by an in-game rcon client, use plain-text password
                                # (encoded into bf2 network stream).
                                if cmd.strip() == options['password']:
                                                self.authed_players[ctx.player] = 1
                                                success = 1
                                                print 'rcon: in-game client, player id: %s authenticated' % str(ctx.player)
                                elif self.authed_players.has_key(ctx.player):
                                                del self.authed_players[ctx.player]
                        else:
                                # tcp client, require seeded digest to match instead of pw
                                if cmd.strip() == ctx.socket.correct_digest:
                                        self.authed_sockets[ctx.socket] = 1
                                        print 'rcon: tcp client from %s:%d logged on' % ctx.socket.addr
                                        success = 1
                                else:
                                    if self.authed_sockets.has_key(ctx.socket):
                                        del self.authed_sockets[ctx.socket]
                                    print 'rcon: tcp client from %s:%d failed pw challenge' % ctx.socket.addr
       
                        if success:
                                if ctx.isSocket():  # Added BF2CC June 13, 2005
                                        ctx.socket.bf2ccClient.RegisterClient(ctx.socket) # Added BF2CC June 13, 2005
                                       
                                ctx.write('Authentication successful, rcon ready.\n')
                        else:
                                ctx.write('Authentication failed.\n')
                except Exception, e:
                        printExceptionTrace(e, 'AdminServer.rcmd_login')
               

    # Lists rcon-authenticated players.
    def rcmd_users(self, ctx, cmd):
        try:
                        ctx.write('active rcon users:\n')
                        for id in self.authed_players:
                                if id == -1:
                                        ctx.write('-1 (local server console)\n')
                                else:
                                        try:
                                                player = bf2.playerManager.getPlayerByIndex(id)
                                                ctx.write('%d from %s name=\'%s\'\n' % (id, player.getAddress(), player.getName()))
                                        except:
                                                ctx.write('%d (no info)\n' % (id))
       
                        for peer in self.authed_sockets:
                                ctx.write('tcp: %s:%d\n' % (peer.addr[0], peer.addr[1]))
                except Exception, e:
                        printExceptionTrace(e, 'AdminServer.rcmd_users')
               
    # Executes a console command on the server.
    def rcmd_exec(self, ctx, cmd):
        try:
                        ctx.write(host.rcon_invoke(cmd))
                except Exception, e:
                        printExceptionTrace(e, 'AdminServer.rcmd_exec')
               

    # Executes a bf2cc command on the server. Added BF2CC June 13, 2005
    def rcmd_bf2cc(self, ctx, cmd): # Added BF2CC June 13, 2005
                try:
                        ctx.socket.bf2ccClient.Exec(ctx, cmd) # Added BF2CC June 13, 2005
                except Exception, e:
                        printExceptionTrace(e, 'AdminServer.rcmd_bf2cc')

    def rcmd_addPlayer(self, ctx, cmd):
        ctx.write('Adding player ' + cmd)
        standard_admin.kickoutsiders.addPlayerToList(cmd)
       
    def rcmd_removePlayer(self, ctx, cmd):
        ctx.write('Removing player ' + cmd)
        standard_admin.kickoutsiders.removePlayerFromList(cmd)
       
    def rcmd_changeMode(self, ctx, cmd):
        standard_admin.kickoutsiders.changeMode(cmd)
       
    def rcmd_currMode(self, ctx, cmd):
        standard_admin.kickoutsiders.writeMode(ctx)
       
    def rcmd_numFromList(self, ctx, cmd):
        standard_admin.kickoutsiders.writeNumList(ctx)
       
    def rcmd_isOnList(self, ctx, cmd):
        standard_admin.kickoutsiders.isOnList(cmd, ctx)


# parse the configuration file
parseConfig()


# our single server instance
server = AdminServer(int(options['port']))

# These functions are called from the engine -- we implement them in terms of a
# class instance:


def init():
    print 'initializing default admin/rcon module'
   
        try:
                # load (optional) admin scripts like teamkill punish and autobalance
                import standard_admin
        except Exception, e:
                printExceptionTrace(e, 'default.py.init')

def shutdown():
    if server:
        print 'shutting down default admin/rcon module'
        server.shutdown()

def update():
    if server: server.update()