comparison orpg/networking/mplay_server.py @ 0:4385a7d0efd1 grumpy-goblin

Deleted and repushed it with the 'grumpy-goblin' branch. I forgot a y
author sirebral
date Tue, 14 Jul 2009 16:41:58 -0500
parents
children c7f04d3c76f5
comparison
equal deleted inserted replaced
-1:000000000000 0:4385a7d0efd1
1 #!/usr/bin/python2.1
2 # Copyright (C) 2000-2001 The OpenRPG Project
3 #
4 # openrpg-dev@lists.sourceforge.net
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 # --
20 #
21 # File: mplay_server.py
22 # Author: Chris Davis
23 # Maintainer:
24 # Version:
25 # $Id: mplay_server.py,v 1.155 2008/01/24 03:52:03 digitalxero Exp $
26 #
27 # Description: This file contains the code for the server of the multiplayer
28 # features in the orpg project.
29 #
30
31
32 # 04-15-2005 [Snowdog]: Added patch from Brandan Yares (xeriar). Reference: patch tracker id #1182076
33
34 __version__ = "$Id: mplay_server.py,v 1.155 2008/01/24 03:52:03 digitalxero Exp $"
35
36 #!/usr/bin/env python
37 """
38 <msg to='' from='' group_id='' />
39 <player id='' ip='' group_id='' name='' action='new,del,group,update' status="" version=""/>
40 <group id='' name='' pwd='' players='' action='new,del,update' />
41 <create_group from='' pwd='' name='' />
42 <join_group from='' pwd='' group_id='' />
43 <role action='set,get,display' player='' group_id='' boot_pwd='' role=''/>
44 """
45
46 from mplay_client import *
47 from mplay_client import MPLAY_LENSIZE
48 import orpg.dirpath
49 import orpg.tools.validate
50 import gc
51 import cgi
52 import sys
53 import string
54 import time
55 import urllib
56 from orpg.mapper.map_msg import *
57 from threading import Lock, RLock
58 from struct import pack, unpack, calcsize
59 from meta_server_lib import *
60 import traceback
61 import re
62
63 # Import the minidom XML module
64 from xml.dom import minidom
65
66 # Snag the version number
67 from orpg.orpg_version import *
68
69 #Plugins
70 from server_plugins import ServerPlugins
71
72 def id_compare(a,b):
73 "converts strings to intergers for list sort comparisons for group and player ids so they end up in numeric order"
74 return cmp(int(a),int(b))
75
76
77 class game_group(object):
78 def __init__( self, id, name, pwd, desc="", boot_pwd="", minVersion="", mapFile=None, messageFile=None, persist =0 ):
79 self.id = id
80 self.name = name
81 self.desc = desc
82 self.minVersion = minVersion
83 self.messageFile = messageFile
84 self.players = []
85 self.pwd = pwd
86 self.boot_pwd = boot_pwd
87 self.game_map = map_msg()
88 self.lock = Lock()
89 self.moderated = 0
90 self.voice = {}
91 self.persistant = persist
92 self.mapFile = None
93
94 if mapFile != None:
95 self.mapFile = mapFile
96 f = open( mapFile )
97 tree = f.read()
98 f.close()
99
100 else:
101 f = open(orpg.dirpath.dir_struct["template"] + "default_map.xml")
102 tree = f.read()
103 f.close()
104
105 self.game_map.init_from_xml(tree)
106
107 def save_map(self):
108 if self.mapFile is not None and self.persistant == 1 and self.mapFile.find("default_map.xml") == -1:
109 f = open(self.mapFile, "w")
110 f.write(self.game_map.get_all_xml())
111 f.close()
112
113
114 def add_player(self,id):
115 self.players.append(id)
116
117 def remove_player(self,id):
118 if self.voice.has_key(id):
119 del self.voice[id]
120 self.players.remove(id)
121
122 def get_num_players(self):
123 num = len(self.players)
124 return num
125
126 def get_player_ids(self):
127 tmp = self.players
128 return tmp
129
130
131 def check_pwd(self,pwd):
132 return (pwd==self.pwd)
133
134 def check_boot_pwd(self,pwd):
135 return (pwd==self.boot_pwd)
136
137 def check_version(self,ver):
138 if (self.minVersion == ""):
139 return 1
140 minVersion=self.minVersion.split('.')
141 version=ver.split('.')
142 for i in xrange(min(len(minVersion),len(version))):
143 w=max(len(minVersion[i]),len(version[i]))
144 v1=minVersion[i].rjust(w);
145 v2=version[i].rjust(w);
146 if v1<v2:
147 return 1
148 if v1>v2:
149 return 0
150
151 if len(minVersion)>len(version):
152 return 0
153 return 1
154
155 #depreciated - see send_group_list()
156 def toxml(self,act="new"):
157 # Please don't add the boot_pwd to the xml, as this will give it away to players watching their console
158 xml_data = "<group id=\"" + self.id
159 xml_data += "\" name=\"" + self.name
160 xml_data += "\" pwd=\"" + str(self.pwd!="")
161 xml_data += "\" players=\"" + str(self.get_num_players())
162 xml_data += "\" action=\"" + act + "\" />"
163 return xml_data
164
165
166
167 class client_stub(client_base):
168 def __init__(self,inbox,sock,props,log):
169 client_base.__init__(self)
170 self.ip = props['ip']
171 self.role = props['role']
172 self.id = props['id']
173 self.group_id = props['group_id']
174 self.name = props['name']
175 self.version = props['version']
176 self.protocol_version = props['protocol_version']
177 self.client_string = props['client_string']
178 self.inbox = inbox
179 self.sock = sock
180 self.timeout_time = None
181 self.log_console = log
182 self.ignorelist = {}
183
184 # implement from our base class
185 def isServer( self ):
186 return 1
187
188 def clear_timeout(self):
189 self.timeout_time = None
190
191 def check_time_out(self):
192 if self.timeout_time==None:
193 self.timeout_time = time.time()
194 curtime = time.time()
195 diff = curtime - self.timeout_time
196 if diff > 1800:
197 return 1
198 else:
199 return 0
200
201 def send(self,msg,player,group):
202 if self.get_status() == MPLAY_CONNECTED:
203 self.outbox.put("<msg to='" + player + "' from='0' group_id='" + group + "' />" + msg)
204
205 def change_group(self,group_id,groups):
206 old_group_id = str(self.group_id)
207 groups[group_id].add_player(self.id)
208 groups[old_group_id].remove_player(self.id)
209 self.group_id = group_id
210 self.outbox.put(self.toxml('group'))
211 msg = groups[group_id].game_map.get_all_xml()
212 self.send(msg,self.id,group_id)
213 return old_group_id
214
215 def self_message(self,act):
216 self.send(act,self.id,self.group_id)
217
218 def take_dom(self,xml_dom):
219 self.name = xml_dom.getAttribute("name")
220 self.text_status = xml_dom.getAttribute("status")
221
222
223 ######################################################################
224 ######################################################################
225 ##
226 ##
227 ## MPLAY SERVER
228 ##
229 ##
230 ######################################################################
231 ######################################################################
232
233 class mplay_server:
234 def __init__(self, log_console=None, name=None):
235 self.log_to_console = 1
236 self.log_console = log_console
237 self.alive = 1
238 self.players = {}
239 self.listen_event = Event()
240 self.incoming_event = Event()
241 self.incoming = Queue.Queue(0)
242 self.p_lock = RLock()
243 self.next_player_id = 1
244 self.plugin_player_id = -1
245 self.next_group_id = 100
246 self.metas = {} # This holds the registerThread objects for each meta
247 self.be_registered = 0 # Status flag for whether we want to be registered.
248 self.serverName = name # Name of this server in the metas
249 self.boot_pwd = ""
250 self.server_address = None # IP or Name of server to post to the meta. None means the meta will auto-detect it.
251 self.defaultMessageFile = None
252 self.userPath = orpg.dirpath.dir_struct["user"]
253 self.lobbyMapFile = "Lobby_map.xml"
254 self.lobbyMessageFile = "LobbyMessage.html"
255 self.banFile = "ban_list.xml"
256 self.show_meta_messages = 0
257 self.log_network_messages = 0
258 self.allow_room_passwords = 1
259 self.silent_auto_kick = 0
260 self.zombie_time = 480 #time in minutes before a client is considered a ZOMBIE
261 self.minClientVersion = SERVER_MIN_CLIENT_VERSION
262 self.maxSendSize = 1024
263 self.server_port = OPENRPG_PORT
264 self.allowRemoteKill = False
265 self.allowRemoteAdmin = True
266 self.sendLobbySound = False
267 self.lobbySound = 'http://www.digitalxero.net/music/mus_tavern1.bmu'
268
269 def initServer(self, **kwargs):
270 for atter, value in kwargs.iteritems():
271 setattr(self, atter, value)
272 self.validate = orpg.tools.validate.Validate(self.userPath)
273 self.validate.config_file( self.lobbyMapFile, "default_Lobby_map.xml" )
274 self.validate.config_file( self.lobbyMessageFile, "default_LobbyMessage.html" )
275 self.server_start_time = time.time()
276
277 # Since the server is just starting here, we read in the XML configuration
278 # file. Notice the lobby is still created here by default.
279 self.groups = { '0': game_group('0','Lobby','','The game lobby', '', '', self.userPath + self.lobbyMapFile, self.userPath + self.lobbyMessageFile, 1)}
280 # Make sure the server's name gets set, in case we are being started from
281 # elsewhere. Basically, if it's passed in, we'll over ride what we were
282 # prompted for. This should never really happen at any rate.
283
284 self.initServerConfig()
285 self.listen_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
286 self.listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
287 self.listen_thread = thread.start_new_thread(self.listenAcceptThread, (0,))
288 self.in_thread = thread.start_new_thread(self.message_handler,(0,))
289
290 # Starts the player reaper thread. See self.player_reaper_thread_func() for more explanation
291 self.player_reaper_thread = thread.start_new_thread(self.player_reaper_thread_func,(0,))
292 thread.start_new_thread(self.PluginThread,())
293 self.svrcmds = {}
294 self.initsvrcmds()
295 self.ban_list = {}
296 self.initBanList()
297
298 def addsvrcmd(self, cmd, function):
299 if not self.svrcmds.has_key(cmd):
300 self.svrcmds[cmd] = {}
301 self.svrcmds[cmd]['function'] = function
302
303 def initsvrcmds(self):
304 self.addsvrcmd('msg', self.incoming_msg_handler)
305 self.addsvrcmd('player', self.incoming_player_handler)
306 self.addsvrcmd('admin', self.remote_admin_handler)
307 self.addsvrcmd('alter', self.do_alter)
308 self.addsvrcmd('role', self.do_role)
309 self.addsvrcmd('ping', self.do_ping)
310 self.addsvrcmd('system', self.do_system)
311 self.addsvrcmd('join_group', self.join_group)
312 self.addsvrcmd('create_group', self.create_group)
313 self.addsvrcmd('moderate', self.moderate_group)
314 self.addsvrcmd('plugin', self.plugin_msg_handler)
315 self.addsvrcmd('sound', self.sound_msg_handler)
316
317
318 # This method reads in the server's ban list added by Darren
319 def initBanList( self ):
320 self.log_msg("Processing Ban List File...")
321
322 # make sure the server_ini.xml exists!
323 self.validate.config_file(self.banFile, "default_ban_list.xml" )
324
325 # try to use it.
326 try:
327 self.banDom = minidom.parse(self.userPath + 'ban_list.xml')
328 self.banDom.normalize()
329 self.banDoc = self.banDom.documentElement
330
331 for element in self.banDom.getElementsByTagName('banned'):
332 playerName = element.getAttribute( 'name' ).replace("&", "&amp;").replace("<", "&lt;").replace('"', "&quot;").replace(">", "&gt;")
333 playerIP = element.getAttribute('ip')
334 self.ban_list[playerIP] = {}
335 self.ban_list[playerIP]['ip'] = playerIP
336 self.ban_list[playerIP]['name'] = playerName
337 self.log_msg(str(playerName) + " " + str(playerIP) + " is banned.")
338 self.saveBanList()
339 except Exception, e:
340 self.log_msg("Exception in initBanList() " + str(e))
341
342 # This method writes out the server's ban list added by Darren
343 def saveBanList( self ):
344 self.log_msg("Saving Ban List File...")
345
346 # try to use it.
347 try:
348 data = []
349 data.append("<server>\n")
350 for ip in self.ban_list:
351 data.append(' <banned name="' + str(self.ban_list[ip]['name'].replace("&amp;", "&").replace("&lt;", "<").replace("&quot;", '"').replace("&gt;", ">")) + '" ip="' + str(self.ban_list[ip]['ip']) + '" />' + "\n")
352 data.append("</server>")
353 file = open(self.userPath + self.banFile ,"w")
354 file.write("".join(data))
355 file.close()
356 except Exception, e:
357 self.log_msg("Exception in saveBanList() " + str(e))
358
359 # This method reads in the server's configuration file and reconfigs the server
360 # as needed, over-riding any default values as requested.
361 def initServerConfig(self):
362 self.log_msg("Processing Server Configuration File... " + self.userPath)
363 # make sure the server_ini.xml exists!
364 self.validate.config_file( "server_ini.xml", "default_server_ini.xml" )
365 # try to use it.
366 try:
367 self.configDom = minidom.parse(self.userPath + 'server_ini.xml')
368 self.configDom.normalize()
369 self.configDoc = self.configDom.documentElement
370 # Obtain the lobby/server password if it's been specified
371 if self.configDoc.hasAttribute("admin"):
372 self.boot_pwd = self.configDoc.getAttribute("admin")
373 elif self.configDoc.hasAttribute("boot"):
374 self.boot_pwd = self.configDoc.getAttribute("boot")
375 if hasattr(self, 'bootPassword'):
376 self.boot_pwd = self.bootPassword
377 elif len(self.boot_pwd) < 1:
378 self.boot_pwd = raw_input("Enter boot password for the Lobby: ")
379 if not hasattr(self, 'reg') and self.configDoc.hasAttribute("register"):
380 self.reg = self.configDoc.getAttribute("register")
381 if not len(self.reg) > 0 or self.reg[0].upper() not in ("Y", "N"):
382 opt = raw_input("Do you want to post your server to the OpenRPG Meta Server list? (y,n) ")
383 if len(opt) and (opt[0].upper() == 'Y'):
384 self.reg = 'Y'
385 else:
386 self.reg = 'N'
387 LobbyName = 'Lobby'
388 if self.configDoc.hasAttribute("lobbyname"):
389 LobbyName = self.configDoc.getAttribute("lobbyname")
390 map_node = service_node = self.configDoc.getElementsByTagName("map")[0]
391 msg_node = service_node = self.configDoc.getElementsByTagName("message")[0]
392 mapFile = map_node.getAttribute('file')
393 msgFile = msg_node.getAttribute('file')
394 if mapFile == '':
395 mapFile = 'Lobby_map.xml'
396 if msgFile == '':
397 msgFile = 'LobbyMessage.html'
398 # Update the lobby with the passwords if they've been specified
399 if len(self.boot_pwd):
400 self.groups = {'0': game_group( '0', LobbyName, "", 'The game lobby', self.boot_pwd, "",
401 self.userPath + mapFile.replace("myfiles/", ""),
402 self.userPath + msgFile.replace("myfiles/", ""), 1 )
403 }
404
405 # set ip or dns name to send to meta server
406 service_node = self.configDoc.getElementsByTagName("service")[0]
407 address = service_node.getAttribute("address")
408 address = address.lower()
409 if address == "" or address == "hostname/address" or address == "localhost":
410 self.server_address = None
411 else:
412 self.server_address = address
413 self.server_port = OPENRPG_PORT
414 if service_node.hasAttribute("port"):
415 self.server_port = int(service_node.getAttribute("port"))
416 if self.configDoc.hasAttribute("name") and len(self.configDoc.getAttribute("name")) > 0 :
417 self.name = self.configDoc.getAttribute("name")
418 else:
419 if self.reg[0].upper() == "Y":
420 if self.name == None:
421 self.name = raw_input("Server Name? ")
422 self.register()
423
424 # Get the minimum openrpg version from config if available
425 # if it isn't set min version to internal default.
426 #
427 # server_ini.xml entry for version tag...
428 # <version min="x.x.x">
429 try:
430 mver = self.configDoc.getElementsByTagName("version")[0]
431 self.minClientVersion = mver.getAttribute("min")
432 except:
433 self.minClientVersion = SERVER_MIN_CLIENT_VERSION #from orpg/orpg_version.py
434 self.defaultMessageFile = ""
435 # This try/except bit is to allow older versions of python to continue without a list error.
436
437
438
439 #------------------------[ START <AUTOKICK> TAG PROCESSING ]--------------
440 # Auto-kick option defaults for silent booting and
441 # setting the default zombie-client delay time --Snowdog 9/05
442 #
443 # server_ini.xml entry for autikick tag...
444 # <autokick silent=["no","yes"] delay="(# of seconds)">
445
446 try:
447 ak = self.configDoc.getElementsByTagName("autokick")[0]
448 if ak.hasAttribute("silent"):
449 if ((ak.getAttribute("silent")).lower() == "yes"):
450 self.silent_auto_kick = 1
451 else:
452 self.silent_auto_kick = 0
453 if ak.hasAttribute("delay"):
454 try:
455 delay = int(ak.getAttribute("delay"))
456 self.zombie_time = delay
457 except:
458 #delay value cannot be converted into an int use defaut
459 self.zombie_time = 480 #(default 8 mins)
460 self.log_msg("**WARNING** Error with autokick delay string using default (480 sec)")
461
462 except:
463 self.silent_auto_kick = 0 #(default to off)
464 self.zombie_time = 480 #(default 8 mins)
465 self.log_msg("**WARNING** Error loading autokick settings... using defaults")
466
467 alk = ""
468 if (self.silent_auto_kick == 1): alk = "(Silent Mode)"
469 self.log_msg("Auto Kick: Delay="+str(self.zombie_time) + " " + alk)
470 #------------------------[ END <AUTOKICK> TAG PROCESSING ]--------------
471
472
473
474 #-------------------------------[ START <ROOM_DEFAULT> TAG PROCESSING ]--------------------
475 #
476 # New room_defaults configuration option used to set various defaults
477 # for all user created rooms on the server. Incorporates akomans older
478 # default room message code (from above) --Snowdog 11/03
479 #
480 # option syntax
481 # <room_defaults passwords="yes" map="myfiles/LobbyMap.xml" message="myfiles/LobbyMessage.html" />
482
483 #default settings for tag options...
484 roomdefault_msg = str(self.defaultMessageFile) #no message is the default
485 roomdefault_map = "" #use lobby map as default
486 roomdefault_pass = 1 #allow passwords
487
488
489 #pull information from config file DOM
490 try:
491 roomdefaults = self.configDom.getElementsByTagName("room_defaults")[0]
492 #rd.normalize()
493 #roomdefaults = self.rd.documentElement
494 try:
495 setting = roomdefaults.getElementsByTagName('passwords')[0]
496 rpw = setting.getAttribute('allow')
497 if rpw == "no" or rpw == "0":
498 roomdefault_pass = 0
499 self.log_msg("Room Defaults: Disallowing Passworded Rooms")
500 else:
501 self.log_msg("Room Defaults: Allowing Passworded Rooms")
502 except:
503 self.log_msg("Room Defaults: [Warning] Allowing Passworded Rooms")
504 try:
505 setting = roomdefaults.getElementsByTagName('map')[0]
506 map = setting.getAttribute('file')
507 if map != "":
508 roomdefault_map = self.userPath + map.replace("myfiles/", "")
509 self.log_msg("Room Defaults: Using " + str(map) + " for room map")
510 except:
511 self.log_msg("Room Defaults: [Warning] Using Default Map")
512
513 try:
514 setting = roomdefaults.getElementsByTagName('message')[0]
515 msg = setting.getAttribute('file')
516 if msg != "":
517 if msg[:4].lower() == 'http':
518 roomdefault_msg = msg
519 else:
520 roomdefault_msg = self.userPath + msg.replace("myfiles/", "")
521 self.log_msg("Room Defaults: Using " + str(msg) + " for room messages")
522 except:
523 print ("Room Defaults: [Warning] Using Default Message")
524 except:
525 traceback.print_exc()
526 self.log_msg("**WARNING** Error loading default room settings from configuration file. Using internal defaults.")
527
528
529 #set the defaults
530 if roomdefault_msg != "" or roomdefault_msg != None:
531 self.defaultMessageFile = roomdefault_msg #<room_defaults> tag superceeds older <newrooms> tag
532 else:
533 self.defaultMessageFile = None
534
535 if roomdefault_map != "" or roomdefault_map != None:
536 self.defaultMapFile = roomdefault_map #<room_defaults> tag superceeds older <newrooms> tag
537 else:
538 self.defaultMapFile = None
539
540 ##### room default map not handled yet. SETTING IGNORED
541 if roomdefault_pass == 0: self.allow_room_passwords = 0
542 else: self.allow_room_passwords = 1
543
544 #-------------------------------[ END <ROOM_DEFAULT> TAG PROCESSING ]--------------------
545
546
547 ###Server Cheat message
548 try:
549 cheat_node = self.configDoc.getElementsByTagName("cheat")[0]
550 self.cheat_msg = cheat_node.getAttribute("text")
551 except:
552 self.cheat_msg = "**FAKE ROLL**"
553 self.log_msg("**WARNING** <cheat txt=\"\"> tag missing from server configuration file. Using empty string.")
554
555
556
557 # should validate protocal
558 validate_protocol_node = self.configDom.getElementsByTagName("validate_protocol ")
559
560 self.validate_protocol = 1
561
562 if(validate_protocol_node):
563 self.validate_protocol = (validate_protocol_node[0].getAttribute("value") == "True")
564 if(self.validate_protocol != 1):
565 self.log_msg("Protocol Validation: OFF")
566 self.makePersistentRooms()
567
568 self.log_msg("Server Configuration File: Processing Completed.")
569
570 except Exception, e:
571 traceback.print_exc()
572 self.log_msg("Exception in initServerConfig() " + str(e))
573
574
575 def makePersistentRooms(self):
576 'Creates rooms on the server as defined in the server config file.'
577
578 for element in self.configDom.getElementsByTagName('room'):
579 roomName = element.getAttribute('name')
580 roomPassword = element.getAttribute('password')
581 bootPassword = element.getAttribute('boot')
582
583 # Conditionally check for minVersion attribute
584 if element.hasAttribute('minVersion'):
585 minVersion = element.getAttribute('minVersion')
586 else:
587 minVersion = ""
588
589 # Extract the map filename attribute from the map node
590 # we only care about the first map element found -- others are ignored
591 mapElement = element.getElementsByTagName('map')[0]
592 mapFile = self.userPath + mapElement.getAttribute('file').replace("myfiles/", "")
593
594 messageElement = element.getElementsByTagName('message')[0]
595 messageFile = messageElement.getAttribute('file')
596
597 if messageFile[:4] != 'http':
598 messageFile = self.userPath + messageFile.replace("myfiles/", "")
599
600 # Make sure we have a message to even mess with
601 if(len(messageFile) == 0):
602 messageFile = self.defaultMessageFile
603
604 if(len(mapFile) == 0):
605 mapFile = self.defaultMapFile
606
607 moderated = 0
608 if element.hasAttribute('moderated') and element.getAttribute('moderated').lower() == "true":
609 moderated = 1
610
611 #create the new persistant group
612 self.new_group(roomName, roomPassword, bootPassword, minVersion, mapFile, messageFile, persist = 1, moderated=moderated)
613
614
615
616 def isPersistentRoom(self, id):
617 'Returns True if the id is a persistent room (other than the lobby), otherwise, False.'
618
619 # altered persistance tracking from simple room id based to per-group setting
620 # allows arbitrary rooms to be marked as persistant without needing the self.persistRoomThreshold
621 # -- Snowdog 4/04
622 try:
623 id = str(id) #just in case someone sends an int instead of a str into the function
624 if id not in self.groups: return 0 #invalid room, can't be persistant
625 pr = (self.groups[id]).persistant
626 return pr
627 except:
628 self.log_msg("Exception occured in isPersistentRoom(self,id)")
629 return 0
630
631
632
633 #-----------------------------------------------------
634 # Toggle Meta Logging -- Added by Snowdog 4/03
635 #-----------------------------------------------------
636 def toggleMetaLogging(self):
637 if self.show_meta_messages != 0:
638 self.log_msg("Meta Server Logging: OFF")
639 self.show_meta_messages = 0
640 else:
641 self.log_msg("Meta Server Logging: ON")
642 self.show_meta_messages = 1
643
644
645 #-----------------------------------------------------
646 # Start/Stop Network Logging to File -- Added by Snowdog 4/03
647 #-----------------------------------------------------
648 def NetworkLogging(self, mode = 0):
649 if mode == 0:
650 self.log_msg("Network Logging: OFF")
651 self.log_network_messages = 0
652 elif mode == 1:
653 self.log_msg("Network Logging: ON (composite logfile)")
654 self.log_network_messages = 1
655 elif mode == 2:
656 self.log_msg("Network Logging: ON (split logfiles)")
657 self.log_network_messages = 2
658 else: return
659 #when log mode changes update all connection stubs
660 for n in self.players:
661 try:
662 self.players[n].EnableMessageLogging = mode
663 except:
664 self.log_msg("Error changing Message Logging Mode for client #" + str(self.players[n].id))
665 def NetworkLoggingStatus(self):
666 if self.log_network_messages == 0: return "Network Traffic Log: Off"
667 elif self.log_network_messages == 1: return "Network Traffic Log: Logging (composite file)"
668 elif self.log_network_messages == 2: return "Network Traffic Log: Logging (inbound/outbound files)"
669 else: self.log_msg("Network Traffic Log: [Unknown]")
670
671
672
673
674 def register_callback(instance, xml_dom = None,source=None):
675 if xml_dom: # if we get something
676 if source == getMetaServerBaseURL(): # if the source of this DOM is the authoritative meta
677 try:
678 metacache_lock.acquire()
679 curlist = getRawMetaList() # read the raw meta cache lines into a list
680 updateMetaCache(xml_dom) # update the cache from the xml
681 newlist = getRawMetaList() # read it into a second list
682 finally:
683 metacache_lock.release()
684
685 if newlist != curlist: # If the two lists aren't identical
686 # then something has changed.
687 instance.register() # Call self.register()
688 # which will force a re-read of the meta cache and
689 # redo the registerThreads
690 else:
691 instance.register()
692
693 # Eventually, reset the MetaServerBaseURL here
694
695 ## Added to help clean up parser errors in the XML on clients
696 ## due to characters that break welformedness of the XML from
697 ## the meta server.
698 ## NOTE: this is a stopgap measure -SD
699 def clean_published_servername(self, name):
700 #clean name of all apostrophes and quotes
701 badchars = "\"\\`><"
702 for c in badchars:
703 name = name.replace(c,"")
704 return name
705
706 def registerRooms(self, args=None):
707 rooms = ''
708 id = '0'
709 time.sleep(500)
710 for rnum in self.groups.keys():
711 rooms += urllib.urlencode( {"room_data[rooms][" + str(rnum) + "][name]":self.groups[rnum].name,
712 "room_data[rooms][" + str(rnum) + "][pwd]":str(self.groups[rnum].pwd != "")})+'&'
713
714 for pid in self.groups[rnum].players:
715 rooms += urllib.urlencode( {"room_data[rooms][" + str(rnum) + "][players]["+str(pid)+"]":self.players[pid].name,})+'&'
716
717
718 for meta in self.metas.keys():
719 while id == '0':
720 id, cookie = self.metas[meta].getIdAndCookie()
721 data = urllib.urlencode( {"room_data[server_id]":id,
722 "act":'registerrooms'})
723 get_server_dom(data+'&'+rooms, self.metas[meta].path)
724
725
726 def register(self,name_given=None):
727 if name_given == None:
728 name = self.name
729 else:
730 self.name = name = name_given
731
732 name = self.clean_published_servername(name)
733
734 # Set up the value for num_users
735 if self.players:
736 num_players = len(self.players)
737 else:
738 num_players = 0
739
740 # request only Meta servers compatible with version 2
741 metalist = getMetaServers(versions=["2"])
742 if self.show_meta_messages != 0:
743 self.log_msg("Found these valid metas:")
744 for meta in metalist:
745 self.log_msg("Meta:" + meta)
746
747 # Go through the list and see if there is already a running register
748 # thread for the meta.
749 # If so, call it's register() method
750 # If not, start one, implicitly calling the new thread's register() method
751
752
753 # iterate through the currently running metas and prune any
754 # not currently listed in the Meta Server list.
755 if self.show_meta_messages != 0: self.log_msg( "Checking running register threads for outdated metas.")
756 for meta in self.metas.keys():
757 if self.show_meta_messages != 0: self.log_msg("meta:" + meta + ": ")
758 if not meta in metalist: # if the meta entry running is not in the list
759 if self.show_meta_messages != 0: self.log_msg( "Outdated. Unregistering and removing")
760 self.metas[meta].unregister()
761 del self.metas[meta]
762 else:
763 if self.show_meta_messages != 0: self.log_msg( "Found in current meta list. Leaving intact.")
764
765 # Now call register() for alive metas or start one if we need one
766 for meta in metalist:
767 if self.metas.has_key(meta) and self.metas[meta] and self.metas[meta].isAlive():
768 self.metas[meta].register(name=name, realHostName=self.server_address, num_users=num_players)
769 else:
770 self.metas[meta] = registerThread(name=name, realHostName=self.server_address, num_users=num_players, MetaPath=meta, port=self.server_port,register_callback=self.register_callback)
771 self.metas[meta].start()
772
773 #The register Rooms thread
774
775 self.be_registered = 1
776 thread.start_new_thread(self.registerRooms,(0,))
777
778
779
780 def unregister(self):
781 # loop through all existing meta entries
782 # Don't rely on getMetaServers(), as a server may have been
783 # removed since it was started. In that case, then the meta
784 # would never get unregistered.
785 #
786 # Instead, loop through all existing meta threads and unregister them
787
788 for meta in self.metas.values():
789 if meta and meta.isAlive():
790 meta.unregister()
791
792 self.be_registered = 0
793
794
795
796
797 # This method runs as it's own thread and does the group_member_check every
798 # sixty seconds. This should eliminate zombies that linger when no one is
799 # around to spook them. GC: Frequency has been reduced as I question how valid
800 # the implementation is as it will only catch a very small segment of lingering
801 # connections.
802 def player_reaper_thread_func(self,arg):
803 while self.alive:
804 time.sleep(60)
805
806 self.p_lock.acquire()
807 for group in self.groups.keys():
808 self.check_group_members(group)
809 self.p_lock.release()
810
811 #This thread runs ever 250 miliseconds, and checks various plugin stuff
812 def PluginThread(self):
813 while self.alive:
814 self.p_lock.acquire()
815 players = ServerPlugins.getPlayer()
816
817 for player in players:
818 if player is not None:
819 #Do something here so they can show up in the chat room for non web users'
820 pass
821
822 data = ServerPlugins.preParseOutgoing()
823
824 for msg in data:
825 try:
826 xml_dom = parseXml(msg)
827 xml_dom = xml_dom._get_documentElement()
828
829 if xml_dom.hasAttribute('from') and int(xml_dom.getAttribute('from')) > -1:
830 xml_dom.setAttribute('from', '-1')
831
832 xml_dom.setAttribute('to', 'all')
833 self.incoming_msg_handler(xml_dom, msg)
834 xml_dom.unlink()
835 except:
836 pass
837
838 self.p_lock.release()
839 time.sleep(0.250)
840
841
842 def sendMsg( self, sock, msg, useCompression=False, cmpType=None):
843 """Very simple function that will properly encode and send a message to te
844 remote on the specified socket."""
845
846 if useCompression and cmpType != None:
847 mpacket = cmpType.compress(msg)
848 lpacket = pack('!i', len(mpacket))
849 sock.send(lpacket)
850
851 offset = 0
852 while offset < len(mpacket):
853 slice = buffer(mpacket, offset, len(mpacket)-offset)
854 sent = sock.send(slice)
855 offset += sent
856 sentm = offset
857 else:
858 # Calculate our message length
859 length = len( msg )
860
861 # Encode the message length into network byte order
862 lp = pack('!i', length)
863
864 try:
865 # Send the encoded length
866 sentl = sock.send( lp )
867
868 # Now, send the message the the length was describing
869 sentm = sock.send( msg )
870
871 except socket.error, e:
872 self.log_msg( e )
873
874 except Exception, e:
875 self.log_msg( e )
876
877
878 def recvData( self, sock, readSize ):
879 """Simple socket receive method. This method will only return when the exact
880 byte count has been read from the connection, if remote terminates our
881 connection or we get some other socket exception."""
882
883 data = ""
884 offset = 0
885 try:
886 while offset != readSize:
887 frag = sock.recv( readSize - offset )
888
889 # See if we've been disconnected
890 rs = len( frag )
891 if rs <= 0:
892 # Loudly raise an exception because we've been disconnected!
893 raise IOError, "Remote closed the connection!"
894
895 else:
896 # Continue to build complete message
897 offset += rs
898 data += frag
899
900 except socket.error, e:
901 self.log_msg("Socket Error: recvData(): " + e )
902 data = ""
903
904 return data
905
906
907
908 def recvMsg(self, sock, useCompression=False, cmpType=None):
909 """This method now expects to receive a message having a 4-byte prefix length. It will ONLY read
910 completed messages. In the event that the remote's connection is terminated, it will throw an
911 exception which should allow for the caller to more gracefully handles this exception event.
912
913 Because we use strictly reading ONLY based on the length that is told to use, we no longer have to
914 worry about partially adjusting for fragmented buffers starting somewhere within a buffer that we've
915 read. Rather, it will get ONLY a whole message and nothing more. Everything else will remain buffered
916 with the OS until we attempt to read the next complete message."""
917
918 msgData = ""
919 try:
920 lenData = self.recvData( sock, MPLAY_LENSIZE )
921
922 # Now, convert to a usable form
923 (length,) = unpack('!i', lenData)
924
925 # Read exactly the remaining amount of data
926 msgData = self.recvData( sock, length )
927
928 try:
929 if useCompression and cmpType != None:
930 msgData = cmpType.decompress(msgData)
931 except:
932 traceback.print_exc()
933
934 except Exception, e:
935 self.log_msg( "Exception: recvMsg(): " + str(e) )
936
937 return msgData
938
939
940
941 def kill_server(self):
942 self.alive = 0
943 self.log_msg("Server stopping...")
944 self.unregister() # unregister from the Meta
945 for p in self.players.itervalues():
946 p.disconnect()
947 self.incoming.put("<system/>")
948
949 for g in self.groups.itervalues():
950 g.save_map()
951
952 try:
953 ip = socket.gethostbyname(socket.gethostname())
954 kill = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
955 kill.connect((ip, self.server_port))
956
957 # Now, send the "system" command using the correct protocol format
958 self.sendMsg( kill, "<system/>" )
959 kill.close()
960 except:
961 pass
962
963 self.listen_sock.close()
964 self.listen_event.wait(10)
965 self.incoming_event.wait(10)
966 self.log_msg("Server stopped!")
967
968
969
970 def log_msg(self,msg):
971 if self.log_to_console:
972 if self.log_console:
973 self.log_console(msg)
974 else:
975 print str(msg)
976
977
978 def print_help(self):
979 print
980 print "Commands: "
981 print "'kill' or 'quit' - to stop the server"
982 print "'broadcast' - broadcast a message to all players"
983 print "'list' - list players and groups"
984 print "'dump' - to dump player data"
985 print "'dump groups' - to list the group names and ids only"
986 print "'group n' - to list details about one group only"
987 print "'register' - To register the server as name. Also used to change the server's name if registered."
988 print "'unregister' - To remove this server from the list of servers"
989 print "'get lobby boot password' - to show the Lobby's boot password"
990 print "'set lobby boot password' - to set the Lobby's boot password"
991 print "'log' - toggles logging to the console off or on"
992 print "'log meta' - toggles logging of meta server messages on or off"
993 print "'logfile [off|on|split]' - timestamped network traffic log"
994 print "'remove room' - to remove a room from the server"
995 print "'kick' - kick a player from the server"
996 print "'ban' - ban a player from the server"
997 print "'remotekill' - This will toggle the ability to kill the server via the /admin command"
998 print "'monitor (#)' - monitors raw network I/O stream to specific client"
999 print "'purge clients' - boots all connected clients off the server immediately"
1000 print "'zombie [set [min]]' - view/set the auto-kick time for zombie clients"
1001 #drop any clients that are idle for more than 8 hours
1002 #as these are likely dead clientskick' - kick a player from the server"
1003 print "'uptime' - reports how long server has been running"
1004 print "'roompasswords' - allow/disallow room passwords (toggle)"
1005 print "'search' - will prompt for pattern and display results"
1006 print "'sendsize' - will ajust the send size limit"
1007 print "'remoteadmin' - will toggle remote admin commands"
1008 print "'togglelobbysound' - Will turn on or off the Auto sending of a sound to all players who join the loby"
1009 print "'lobbysound' - Lets you specify which sound file to send to players joining the lobby"
1010 print "'help' or '?' or 'h' - for this help message"
1011 print
1012
1013
1014 def broadcast(self,msg):
1015 self.send_to_all("0","<msg to='all' from='0' group_id='1'><font color='#FF0000'>" + msg + "</font>")
1016
1017
1018 def console_log(self):
1019 if self.log_to_console == 1:
1020 print "console logging now off"
1021 self.log_to_console = 0
1022 else:
1023 print "console logging now on"
1024 self.log_to_console = 1
1025
1026
1027 def groups_list(self):
1028 self.p_lock.acquire()
1029 try:
1030 keys = self.groups.keys()
1031 for k in keys:
1032 pw = "-"
1033 pr = " -"
1034 if self.groups[k].pwd != "":
1035 pw = "P"
1036 if self.isPersistentRoom( k ):
1037 pr = " S" #using S for static (P for persistant conflicts with password)
1038 print "Group: " + k + pr + pw + ' Name: ' + self.groups[k].name
1039 print
1040
1041 except Exception, e:
1042 self.log_msg(str(e))
1043
1044 self.p_lock.release()
1045
1046 #----------------------------------------------------------------
1047 # Monitor Function -- Added by snowdog 2/05
1048 #----------------------------------------------------------------
1049 def monitor(self, pid, mode=1 ):
1050 "allows monitoring of a specific user(s) network i/o"
1051 #if mode is not set to 1 then monitor adds toggles the state
1052 #of monitoring on the given user
1053
1054 if (mode == 1):
1055 for p in self.players:
1056 try: p.monitor("off")
1057 except: pass
1058 try:
1059 r = (self.players[pid]).set_traffic_monitor("toggle")
1060 self.log_msg("Monitor: Mode=" + str(r) + " on Player #" + str(pid))
1061 except:
1062 self.log_msg("Monitor: Invalid Player ID")
1063 traceback.print_exc()
1064
1065
1066 def search(self,patern):
1067 keys = self.groups.keys()
1068 print "Search results:"
1069 for k in keys:
1070 ids = self.groups[k].get_player_ids()
1071 for id in ids:
1072 if self.players[id].id.find(patern)>-1:
1073 self.print_player_info(self.players[id])
1074
1075 elif self.players[id].name.find(patern)>-1:
1076 self.print_player_info(self.players[id])
1077
1078 elif self.players[id].ip.find(patern)>-1:
1079 self.print_player_info(self.players[id])
1080
1081 elif self.players[id].group_id.find(patern)>-1:
1082 self.print_player_info(self.players[id])
1083
1084 elif self.players[id].role.find(patern)>-1:
1085 self.print_player_info(self.players[id])
1086
1087 elif self.players[id].version.find(patern)>-1:
1088 self.print_player_info(self.players[id])
1089
1090 elif self.players[id].protocol_version.find(patern)>-1:
1091 self.print_player_info(self.players[id])
1092
1093 elif self.players[id].client_string.find(patern)>-1:
1094 self.print_player_info(self.players[id])
1095
1096
1097 def print_player_info(self,player):
1098 print player.id,player.name,player.ip,player.group_id, player.role,player.version,player.protocol_version,player.client_string
1099
1100 #----------------------------------------------------------------
1101 # Uptime Function -- Added by snowdog 4/03
1102 #----------------------------------------------------------------
1103 def uptime(self , mode = 0):
1104 "returns string containing how long server has been in operation"
1105 ut = time.time() - self.server_start_time
1106 d = int(ut/86400)
1107 h = int( (ut-(86400*d))/3600 )
1108 m = int( (ut-(86400*d)-(3600*h))/60)
1109 s = int( (ut-(86400*d)-(3600*h)-(60*m)) )
1110 uts = str( "This server has been running for:\n " + str(d) + " days " + str(h) + " hours " + str(m) + " min. " + str(s) + " sec. [" + str(int(ut)) + " seconds]")
1111 if mode == 0: print uts
1112 else: return uts
1113
1114 #-----------------------------------------------------
1115 # Toggle Room Password Allow -- Added by Snowdog 11/03
1116 #-----------------------------------------------------
1117 def RoomPasswords(self):
1118 if self.allow_room_passwords != 0:
1119 self.allow_room_passwords = 0
1120 return "Client Created Room Passwords: Disallowed"
1121 else:
1122 self.allow_room_passwords = 1
1123 return "Client Created Room Passwords: Allowed"
1124
1125
1126 def group_dump(self,k):
1127 self.p_lock.acquire()
1128 try:
1129 print "Group: " + k
1130 print " Name: %s" % self.groups[k].name
1131 print " Desc: %s" % self.groups[k].desc
1132 print " Pass: %s" % self.groups[k].pwd
1133 print " Boot: %s" % self.groups[k].boot_pwd
1134 print " Moderated: %s" % self.groups[k].moderated
1135 print " Map: %s" % self.groups[k].game_map.get_all_xml()
1136 print
1137 except Exception, e:
1138 self.log_msg(str(e))
1139 self.p_lock.release()
1140
1141 #----------------------------------------------------------------
1142 # Player List -- Added by snowdog 4/03
1143 #----------------------------------------------------------------
1144 def player_list(self):
1145 "display a condensed list of players on the server"
1146 self.p_lock.acquire()
1147 try:
1148 print "------------[ PLAYER LIST ]------------"
1149 keys = self.groups.keys()
1150 keys.sort(id_compare)
1151 for k in keys:
1152 groupstring = "Group " + str(k) + ": " + self.groups[k].name
1153 if self.groups[k].pwd != "":
1154 groupstring += " (Pass: \"" + self.groups[k].pwd + "\" )"
1155 print groupstring
1156 ids = self.groups[k].get_player_ids()
1157 ids.sort(id_compare)
1158 for id in ids:
1159 if self.players.has_key(id):
1160 print " (%s)%s [IP: %s] %s (%s)" % ((self.players[id]).id, (self.players[id]).name, (self.players[id]).ip, (self.players[id]).idle_status(), (self.players[id]).connected_time_string())
1161 else:
1162 self.groups[k].remove_player(id)
1163 print "Bad Player Ref (#" + id + ") in group"
1164 if len(ids) > 0: print ""
1165 print "--------------------------------------"
1166 print "\nStatistics: groups: " + str(len(self.groups)) + " players: " + str(len(self.players))
1167 except Exception, e:
1168 self.log_msg(str(e))
1169 self.p_lock.release()
1170
1171
1172 def player_dump(self):
1173 self.p_lock.acquire()
1174 try:
1175 keys = self.groups.keys()
1176 for k in keys:
1177 print "Group: %s %s (pass: \"%s\")" % (str(k),self.groups[k].name, self.groups[k].pwd)
1178
1179 ids = self.groups[k].get_player_ids()
1180 for id in ids:
1181 if self.players.has_key(id):
1182 print str(self.players[id])
1183 else:
1184 self.groups[k].remove_player(id)
1185 print "Bad Player Ref (#" + id + ") in group"
1186 except Exception, e:
1187 self.log_msg(str(e))
1188
1189 self.p_lock.release()
1190
1191
1192 def update_request(self,newsock,xml_dom):
1193 # handle reconnects
1194
1195 self.log_msg( "update_request() has been called." )
1196
1197 # get player id
1198 id = xml_dom.getAttribute("id")
1199 group_id = xml_dom.getAttribute("group_id")
1200
1201 self.p_lock.acquire()
1202 if self.players.has_key(id):
1203 self.sendMsg( newsock, self.players[id].toxml("update"), self.players[id].useCompression, self.players[id].compressionType )
1204 self.players[id].reset(newsock)
1205 self.players[id].clear_timeout()
1206 need_new = 0
1207 else:
1208 need_new = 1
1209 self.p_lock.release()
1210
1211 if need_new:
1212 self.new_request(newsock,xml_dom)
1213 else:
1214 msg = self.groups[group_id].game_map.get_all_xml()
1215 self.send(msg,id,group_id)
1216
1217
1218 def new_request(self,newsock,xml_dom,LOBBY_ID='0'):
1219 #build client stub
1220 props = {}
1221 # Don't trust what the client tells us...trust what they connected as!
1222 props['ip'] = socket.gethostbyname( newsock.getpeername()[0] )
1223
1224 try:
1225 props['role'] = xml_dom.getAttribute("role")
1226 except:
1227 props['role'] = "GM"
1228
1229 props['name'] = xml_dom.getAttribute("name")
1230 props['group_id'] = LOBBY_ID
1231 props['id'] = str(self.next_player_id)
1232 props['version'] = xml_dom.getAttribute("version")
1233 props['protocol_version'] = xml_dom.getAttribute("protocol_version")
1234 props['client_string'] = xml_dom.getAttribute("client_string")
1235 self.next_player_id += 1
1236 new_stub = client_stub(self.incoming,newsock,props,self.log_console)
1237 if xml_dom.hasAttribute('useCompression'):
1238 new_stub.useCompression = True
1239
1240 if xml_dom.hasAttribute('cmpType'):
1241 cmpType = xml_dom.getAttribute('cmpType')
1242 if cmpBZ2 and cmpType == 'bz2':
1243 new_stub.compressionType = bz2
1244 elif cmpZLIB and cmpType == 'zlib':
1245 new_stub.compressionType = zlib
1246 else:
1247 new_stub.compressionType = None
1248 else:
1249 new_stub.compressionType = bz2
1250
1251 else:
1252 new_stub.useCompression = False
1253
1254 #update newly create client stub with network logging state
1255 new_stub.EnableMessageLogging = self.log_network_messages
1256
1257 self.sendMsg(newsock, new_stub.toxml("new"), False, None)
1258
1259 # try to remove circular refs
1260 if xml_dom:
1261 xml_dom.unlink()
1262
1263 # send confirmation
1264 data = self.recvMsg(newsock, new_stub.useCompression, new_stub.compressionType)
1265 try:
1266 xml_dom = parseXml(data)
1267 xml_dom = xml_dom._get_documentElement()
1268 except Exception, e:
1269 print e
1270 (remote_host,remote_port) = newsock.getpeername()
1271 bad_xml_string = "Your client sent an illegal message to the server and will be disconnected. "
1272 bad_xml_string += "Please report this bug to the development team at:<br /> "
1273 bad_xml_string += "<a href=\"http://sourceforge.net/tracker/?group_id=2237&atid=102237\">OpenRPG bugs "
1274 bad_xml_string += "(http://sourceforge.net/tracker/?group_id=2237&atid=102237)</a><br />"
1275 self.sendMsg( newsock, "<msg to='" + props['id'] + "' from='" + props['id'] + "' group_id='0' />" + bad_xml_string, new_stub.useCompression, new_stub.compressionType)
1276
1277 time.sleep(2)
1278 newsock.close()
1279 print "Error in parse found from " + str(remote_host) + ". Disconnected."
1280 print " Offending data(" + str(len(data)) + "bytes)=" + data
1281 print "Exception=" + str(e)
1282
1283 if xml_dom:
1284 xml_dom.unlink()
1285 return
1286
1287 #start threads and store player
1288
1289 allowed = 1
1290 version_string = ""
1291
1292 if ((props['protocol_version'] != PROTOCOL_VERSION) and self.validate_protocol):
1293 version_string = "Sorry, this server can't handle your client version. (Protocol mismatch)<br />"
1294 allowed = 0
1295
1296 if not self.checkClientVersion(props['version']):
1297 version_string = "Sorry, your client is out of date. <br />"
1298 version_string += "This server requires your client be version " + self.minClientVersion + " or higher to connect.<br />"
1299 allowed = 0
1300
1301 if not allowed:
1302 version_string += ' Please go to <a href="http://openrpg.digitalxero.net">http://openrpg.digitalxero.net</a> to find a compatible client.<br />'
1303 version_string += "If you can't find a compatible client on the website, chances are that the server is running an unreleased development version for testing purposes.<br />"
1304
1305 self.sendMsg( newsock, "<msg to='" + props['id'] + "' from='0' group_id='0' />" + version_string, new_stub.useCompression, new_stub.compressionType)
1306 # Give messages time to flow
1307 time.sleep(1)
1308 self.log_msg("Connection terminating due to version incompatibility with client (ver: " + props['version'] + " protocol: " + props['protocol_version'] + ")" )
1309 newsock.close()
1310 if xml_dom:
1311 xml_dom.unlink()
1312 return None
1313
1314 ip = props['ip']
1315 if self.ban_list.has_key(ip):
1316 banmsg = "You have been banned from this server.<br />"
1317 cmsg = "Banned Client: (" + str(props['id']) + ") " + str(props['name']) + " [" + str(props['ip']) + "]"
1318 self.log_msg(cmsg)
1319 allowed = 0
1320
1321 self.sendMsg( newsock, "<msg to='" + props['id'] + "' from='0' group_id='0' />" + banmsg, new_stub.useCompression, new_stub.compressionType)
1322 # Give messages time to flow
1323 time.sleep(1)
1324 newsock.close()
1325 if xml_dom:
1326 xml_dom.unlink()
1327 return None
1328
1329 #---- Connection order changed by Snowdog 1/05
1330 #---- Attempt to register player and send group data
1331 #---- before displaying lobby message
1332 #---- Does not solve the Blackhole bug but under some conditions may
1333 #---- allow for a graceful server response. -SD
1334
1335 #---- changed method of sending group names to user 8/05
1336 #---- black hole bug causes the group information to not be sent
1337 #---- to clients. Not sure why the group messages were being sent to the
1338 #---- incomming message queue, when they should be sent directly to user
1339 #---- Does not solve the black hole bug totally -SD
1340
1341 try:
1342 if xml_dom.getAttribute("id") == props['id']:
1343 new_stub.initialize_threads()
1344 self.p_lock.acquire()
1345 self.players[props['id']] = new_stub
1346 self.groups[LOBBY_ID].add_player(props['id']) #always add to lobby on connection.
1347 self.send_group_list(props['id'])
1348 self.send_player_list(props['id'],LOBBY_ID)
1349 self.p_lock.release()
1350
1351 msg = self.groups[LOBBY_ID].game_map.get_all_xml()
1352 self.send(msg,props['id'],LOBBY_ID)
1353 self.send_to_group(props['id'],LOBBY_ID,self.players[props['id']].toxml('new'))
1354 self.return_room_roles(props['id'],LOBBY_ID)
1355
1356 # Re-initialize the role for this player incase they came from a different server
1357 self.handle_role("set",props['id'], "GM",self.groups[LOBBY_ID].boot_pwd, LOBBY_ID)
1358
1359 cmsg = "Client Connect: (" + str(props['id']) + ") " + str(props['name']) + " [" + str(props['ip']) + "]"
1360 self.log_msg(cmsg)
1361
1362 # If already registered then re-register, thereby updating the Meta
1363 # on the number of players
1364 if self.be_registered:
1365 self.register()
1366 except:
1367 traceback.print_exc()
1368
1369 #something didn't go right. Notify client and drop the connection
1370 err_string = "<center>"
1371 err_string += "<hr><b>The server has encountered an error while processing your connection request.</b><hr>"
1372 err_string += "<br /><i>You are being disconnected from the server.</i><br />"
1373 err_string += "This error may represent a problem with the server. If you continue to get this message "
1374 err_string += "please contact the servers administrator to correct the issue.</center> "
1375 self.sendMsg( newsock, "<msg to='" + props['id'] + "' from='" + props['id'] + "' group_id='0' />" + err_string, new_stub.useCompression, new_stub.compressionType )
1376 time.sleep(2)
1377 newsock.close()
1378
1379
1380 # Display the lobby message
1381 self.SendLobbyMessage(newsock,props['id'])
1382
1383 if xml_dom:
1384 xml_dom.unlink()
1385
1386
1387 def checkClientVersion(self, clientversion):
1388 minv = self.minClientVersion.split('.')
1389 cver = clientversion.split('.')
1390 for i in xrange(min(len(minv),len(cver))):
1391 w=max(len(minv[i]),len(cver[i]))
1392 v1=minv[i].rjust(w);
1393 v2=cver[i].rjust(w);
1394 if v1<v2:
1395 return 1
1396 if v1>v2:
1397 return 0
1398
1399 if len(minv)>len(cver):
1400 return 0
1401 return 1
1402
1403
1404
1405 def SendLobbyMessage(self, socket, player_id):
1406 #######################################################################
1407 # Display the lobby message
1408 # prepend this server's version string to the the lobby message
1409 try:
1410 lobbyMsg = "You have connected to an <a href=\"http://www.openrpg.com\">OpenRPG</a> server, version '" + VERSION + "'"
1411
1412 # See if we have a server name to report!
1413
1414 if len(self.serverName):
1415 lobbyMsg += ", named '" + self.serverName + "'."
1416
1417 else:
1418 lobbyMsg += "."
1419
1420 # Add extra line spacing
1421 lobbyMsg += "\n\n"
1422
1423 try:
1424 self.validate.config_file("LobbyMessage.html","default_LobbyMessage.html")
1425 except:
1426 pass
1427 else:
1428 open_msg = open( self.userPath + "LobbyMessage.html", "r" )
1429 lobbyMsg += open_msg.read()
1430 open_msg.close()
1431
1432 # Send the server's lobby message to the client no matter what
1433 self.sendMsg(socket, "<msg to='" + player_id + "' from='0' group_id='0' />" + lobbyMsg, self.players[player_id].useCompression, self.players[player_id].compressionType)
1434 if self.sendLobbySound:
1435 self.sendMsg(socket, '<sound url="' + self.lobbySound + '" group_id="0" from="0" loop="True" />', self.players[player_id].useCompression, self.players[player_id].compressionType)
1436 return
1437 except:
1438 traceback.print_exc()
1439 # End of lobby message code
1440 #######################################################################
1441
1442
1443 def listenAcceptThread(self,arg):
1444 # Set up the socket to listen on.
1445 try:
1446 self.log_msg("\nlisten thread running...")
1447 adder = ""
1448 if self.server_address is not None:
1449 adder = self.server_address
1450 self.listen_sock.bind(('', self.server_port))
1451 self.listen_sock.listen(5)
1452
1453 except Exception, e:
1454 self.log_msg(("Error binding request socket!", e))
1455 self.alive = 0
1456
1457
1458 while self.alive:
1459
1460 # Block on the socket waiting for a new connection
1461 try:
1462 (newsock, addr) = self.listen_sock.accept()
1463 ## self.log_msg("New connection from " + str(addr)+ ". Interfacing with server...")
1464
1465 # Now that we've accepted a new connection, we must immediately spawn a new
1466 # thread to handle it...otherwise we run the risk of having a DoS shoved into
1467 # our face! :O After words, this thread is dead ready for another connection
1468 # accept to come in.
1469 thread.start_new_thread(self.acceptedNewConnectionThread, ( newsock, addr ))
1470
1471 except:
1472 print "The following exception caught accepting new connection:"
1473 traceback.print_exc()
1474
1475 # At this point, we're done and cleaning up.
1476 self.log_msg("server socket listening thread exiting...")
1477 self.listen_event.set()
1478
1479
1480
1481 def acceptedNewConnectionThread( self, newsock, addr ):
1482 """Once a new connection comes in and is accepted, this thread starts up to handle it."""
1483
1484 # Initialize xml_dom
1485 xml_dom = None
1486 data = None
1487
1488 # get client info and send othe client info
1489 # If this receive fails, this thread should exit without even attempting to process it
1490 self.log_msg("Connection from " + str(addr) + " has been accepted. Waiting for data...")
1491
1492 data = self.recvMsg( newsock )
1493
1494 if data=="" or data == None:
1495 self.log_msg("Connection from " + str(addr) + " failed. Closing connection.")
1496 try:
1497 newsock.close()
1498 except Exception, e:
1499 self.log_msg( str(e) )
1500 print str(e)
1501 return #returning causes connection thread instance to terminate
1502
1503
1504 if data == "<system/>":
1505 try:
1506 newsock.close()
1507 except:
1508 pass
1509 return #returning causes connection thread instance to terminate
1510
1511 # Clear out the xml_dom in preparation for new stuff, if necessary
1512 try:
1513 if xml_dom:
1514 xml_dom.unlink()
1515
1516 except:
1517 self.log_msg( "The following exception caught unlinking xml_dom:")
1518 self.log_msg("Continuing")
1519
1520 try:
1521 newsock.close()
1522 except:
1523 pass
1524 return #returning causes connection thread instance to terminate
1525
1526 # Parse the XML received from the connecting client
1527 try:
1528 xml_dom = parseXml(data)
1529 xml_dom = xml_dom._get_documentElement()
1530
1531 except:
1532 try:
1533 newsock.close()
1534 except:
1535 pass
1536 self.log_msg( "Error in parse found from " + str(addr) + ". Disconnected.")
1537 self.log_msg(" Offending data(" + str(len(data)) + "bytes)=" + data)
1538 self.log_msg( "Exception:")
1539 traceback.print_exc()
1540 return #returning causes connection thread instance to terminate
1541
1542 # Determine the correct action and execute it
1543 try:
1544 # get action
1545 action = xml_dom.getAttribute("action")
1546
1547 # Figure out what type of connection we have going on now
1548 if action == "new":
1549 self.new_request(newsock,xml_dom)
1550
1551 elif action == "update":
1552 self.update_request(newsock,xml_dom)
1553
1554 else:
1555 self.log_msg("Unknown Join Request!")
1556
1557 except Exception, e:
1558 print "The following message: " + str(data)
1559 print "from " + str(addr) + " created the following exception: "
1560 traceback.print_exc()
1561 return #returning causes connection thread instance to terminate
1562
1563 # Again attempt to clean out DOM stuff
1564 try:
1565 if xml_dom:
1566 xml_dom.unlink()
1567 except:
1568 print "The following exception caught unlinking xml_dom:"
1569 traceback.print_exc()
1570 return #returning causes connection thread instance to terminate
1571
1572
1573
1574 #========================================================
1575 #
1576 # Message_handler
1577 #
1578 #========================================================
1579 #
1580 # Changed thread organization from one continuous parsing/handling thread
1581 # to multiple expiring parsing/handling threads to improve server performance
1582 # and player load capacity -- Snowdog 3/04
1583
1584 def message_handler(self,arg):
1585 xml_dom = None
1586 self.log_msg( "message handler thread running..." )
1587 while self.alive:
1588 data = None
1589 try:
1590 data=self.incoming.get(0)
1591 except Queue.Empty:
1592 time.sleep(0.5) #sleep 1/2 second
1593 continue
1594
1595 bytes = len(data)
1596 if bytes <= 0:
1597 continue
1598 try:
1599 thread.start_new_thread(self.parse_incoming_dom,(str(data),))
1600 #data has been passed... unlink from the variable references
1601 #so data in passed objects doesn't change (python passes by reference)
1602 del data
1603 data = None
1604 except Exception, e:
1605 self.log_msg(str(e))
1606 if xml_dom: xml_dom.unlink()
1607 if xml_dom: xml_dom.unlink()
1608 self.log_msg("message handler thread exiting...")
1609 self.incoming_event.set()
1610
1611 def parse_incoming_dom(self,data):
1612 end = data.find(">") #locate end of first element of message
1613 head = data[:end+1]
1614 #self.log_msg(head)
1615 xml_dom = None
1616 try:
1617 xml_dom = parseXml(head)
1618 xml_dom = xml_dom._get_documentElement()
1619 self.message_action(xml_dom,data)
1620
1621 except Exception, e:
1622 print "Error in parse of inbound message. Ignoring message."
1623 print " Offending data(" + str(len(data)) + "bytes)=" + data
1624 print "Exception=" + str(e)
1625
1626 if xml_dom: xml_dom.unlink()
1627
1628
1629 def message_action(self, xml_dom, data):
1630 tag_name = xml_dom._get_tagName()
1631 if self.svrcmds.has_key(tag_name):
1632 self.svrcmds[tag_name]['function'](xml_dom,data)
1633 else:
1634 raise Exception, "Not a valid header!"
1635 #Message Action thread expires and closes here.
1636 return
1637
1638
1639 def do_alter(self, xml_dom, data):
1640 target = xml_dom.getAttribute("key")
1641 value = xml_dom.getAttribute("val")
1642 player = xml_dom.getAttribute("plr")
1643 group_id = xml_dom.getAttribute("gid")
1644 boot_pwd = xml_dom.getAttribute("bpw")
1645 actual_boot_pwd = self.groups[group_id].boot_pwd
1646
1647 if self.allow_room_passwords == 0:
1648 msg ="<msg to='" + player + "' from='0' group_id='0' /> Room passwords have been disabled by the server administrator."
1649 self.players[player].outbox.put(msg)
1650 return
1651 elif boot_pwd == actual_boot_pwd:
1652 if target == "pwd":
1653 lmessage = "Room password changed to from \"" + self.groups[group_id].pwd + "\" to \"" + value + "\" by " + player
1654 self.groups[group_id].pwd = value
1655 msg ="<msg to='" + player + "' from='0' group_id='0' /> Room password changed to \"" + value + "\"."
1656 self.players[player].outbox.put(msg)
1657 self.log_msg(lmessage)
1658 self.send_to_all('0',self.groups[group_id].toxml('update'))
1659 elif target == "name":
1660 # Check for & in name. We want to allow this because of its common
1661 # use in d&d games
1662 result = self.change_group_name(group_id,value,player)
1663 msg ="<msg to='" + player + "' from='0' group_id='0' />" + result
1664 self.players[player].outbox.put(msg)
1665 else:
1666 msg ="<msg to='" + player + "' from='0' group_id='0'>Invalid Administrator Password."
1667 self.players[player].outbox.put(msg)
1668
1669
1670 def do_role(self, xml_dom, data):
1671 role = ""
1672 boot_pwd = ""
1673 act = xml_dom.getAttribute("action")
1674 player = xml_dom.getAttribute("player")
1675 group_id = xml_dom.getAttribute("group_id")
1676 if act == "set":
1677 role = xml_dom.getAttribute("role")
1678 boot_pwd = xml_dom.getAttribute("boot_pwd")
1679 xml_dom.unlink()
1680 if group_id != "0":
1681 self.handle_role(act, player, role, boot_pwd, group_id)
1682 self.log_msg(("role", (player, role)))
1683
1684 def do_ping(self, xml_dom, data):
1685 player = xml_dom.getAttribute("player")
1686 group_id = xml_dom.getAttribute("group_id")
1687 sent_time = ""
1688 msg = ""
1689 try:
1690 sent_time = xml_dom.getAttribute("time")
1691 except:
1692 pass
1693
1694 if sent_time != "":
1695 #because a time was sent return a ping response
1696 msg ="<ping time='" + str(sent_time) + "' />"
1697 else:
1698 msg ="<msg to='" + player + "' from='" + player + "' group_id='" + group_id + "'><font color='#FF0000'>PONG!?!</font>"
1699
1700 self.players[player].outbox.put(msg)
1701 xml_dom.unlink()
1702
1703 def do_system(self, xml_dom, data):
1704 pass
1705
1706 def moderate_group(self,xml_dom,data):
1707 try:
1708 action = xml_dom.getAttribute("action")
1709 from_id = xml_dom.getAttribute("from")
1710 if xml_dom.hasAttribute("pwd"):
1711 pwd=xml_dom.getAttribute("pwd")
1712 else:
1713 pwd=""
1714 group_id=self.players[from_id].group_id
1715
1716 if action == "list":
1717 if (self.groups[group_id].moderated):
1718 msg = ""
1719 for i in self.groups[group_id].voice.keys():
1720 if msg != "":
1721 msg +=", "
1722 if self.players.has_key(i):
1723 msg += '('+i+') '+self.players[i].name
1724 else:
1725 del self.groups[group_id].voice[i]
1726 if (msg != ""):
1727 msg = "The following users may speak in this room: " + msg
1728 else:
1729 msg = "No people are currently in this room with the ability to chat"
1730 self.players[from_id].self_message(msg)
1731 else:
1732 self.players[from_id].self_message("This room is currently unmoderated")
1733 elif action == "enable":
1734 if not self.groups[group_id].check_boot_pwd(pwd):
1735 self.players[from_id].self_message("Failed - incorrect admin password")
1736 return
1737 self.groups[group_id].moderated = 1
1738 self.players[from_id].self_message("This channel is now moderated")
1739 elif action == "disable":
1740 if not self.groups[group_id].check_boot_pwd(pwd):
1741 self.players[from_id].self_message("Failed - incorrect admin password")
1742 return
1743 self.groups[group_id].moderated = 0
1744 self.players[from_id].self_message("This channel is now unmoderated")
1745 elif action == "addvoice":
1746 if not self.groups[group_id].check_boot_pwd(pwd):
1747 self.players[from_id].self_message("Failed - incorrect admin password")
1748 return
1749 users = xml_dom.getAttribute("users").split(',')
1750 for i in users:
1751 self.groups[group_id].voice[i.strip()]=1
1752 elif action == "delvoice":
1753 if not self.groups[group_id].check_boot_pwd(pwd):
1754 self.players[from_id].self_message("Failed - incorrect admin password")
1755 return
1756 users = xml_dom.getAttribute("users").split(',')
1757 for i in users:
1758 if self.groups[group_id].voice.has_key(i.strip()):
1759 del self.groups[group_id].voice[i.strip()]
1760 else:
1761 print "Bad input: " + data
1762
1763 except Exception,e:
1764 self.log_msg(str(e))
1765
1766
1767
1768
1769 def join_group(self,xml_dom,data):
1770 try:
1771 from_id = xml_dom.getAttribute("from")
1772 pwd = xml_dom.getAttribute("pwd")
1773 group_id = xml_dom.getAttribute("group_id")
1774 ver = self.players[from_id].version
1775 allowed = 1
1776
1777 if not self.groups[group_id].check_version(ver):
1778 allowed = 0
1779 msg = 'failed - invalid client version ('+self.groups[group_id].minVersion+' or later required)'
1780
1781 if not self.groups[group_id].check_pwd(pwd):
1782 allowed = 0
1783
1784 #tell the clients password manager the password failed -- SD 8/03
1785 pm = "<password signal=\"fail\" type=\"room\" id=\"" + group_id + "\" data=\"\"/>"
1786 self.players[from_id].outbox.put(pm)
1787
1788 msg = 'failed - incorrect room password'
1789
1790 if not allowed:
1791 self.players[from_id].self_message(msg)
1792 #the following line makes sure that their role is reset to normal,
1793 #since it is briefly set to lurker when they even TRY to change
1794 #rooms
1795 msg = "<role action=\"update\" id=\"" + from_id + "\" role=\"" + self.players[from_id].role + "\" />"
1796 self.players[from_id].outbox.put(msg)
1797 return
1798
1799 #move the player into their new group.
1800 self.move_player(from_id, group_id)
1801
1802 except Exception, e:
1803 self.log_msg(str(e))
1804
1805
1806
1807
1808 #----------------------------------------------------------------------------
1809 # move_player function -- added by Snowdog 4/03
1810 #
1811 # Split join_group function in half. separating the player validation checks
1812 # from the actual group changing code. Done primarily to impliment
1813 # boot-from-room-to-lobby behavior in the server.
1814
1815 def move_player(self, from_id, group_id ):
1816 "move a player from one group to another"
1817 try:
1818 try:
1819 if group_id == "0":
1820 self.players[from_id].role = "GM"
1821 else:
1822 self.players[from_id].role = "Lurker"
1823 except Exception, e:
1824 print "exception in move_player() "
1825 traceback.print_exc()
1826
1827 old_group_id = self.players[from_id].change_group(group_id,self.groups)
1828 self.send_to_group(from_id,old_group_id,self.players[from_id].toxml('del'))
1829 self.send_to_group(from_id,group_id,self.players[from_id].toxml('new'))
1830 self.check_group(from_id, old_group_id)
1831
1832 # Here, if we have a group specific lobby message to send, push it on
1833 # out the door! Make it put the message then announce the player...just
1834 # like in the lobby during a new connection.
1835 # -- only do this check if the room id is within range of known persistent id thresholds
1836 #also goes ahead if there is a defaultRoomMessage --akoman
1837
1838 if self.isPersistentRoom(group_id) or self.defaultMessageFile != None:
1839 try:
1840 if self.groups[group_id].messageFile[:4] == 'http':
1841 data = urllib.urlretrieve(self.groups[group_id].messageFile)
1842 roomMsgFile = open(data[0])
1843 else:
1844 roomMsgFile = open(self.groups[group_id].messageFile, "r")
1845 roomMsg = roomMsgFile.read()
1846 roomMsgFile.close()
1847 urllib.urlcleanup()
1848
1849 except Exception, e:
1850 roomMsg = ""
1851 self.log_msg(str(e))
1852
1853 # Spit that darn message out now!
1854 self.players[from_id].outbox.put("<msg to='" + from_id + "' from='0' group_id='" + group_id + "' />" + roomMsg)
1855
1856 if self.sendLobbySound and group_id == '0':
1857 self.players[from_id].outbox.put('<sound url="' + self.lobbySound + '" group_id="0" from="0" loop="True" />')
1858
1859 # Now, tell everyone that we've arrived
1860 self.send_to_all('0', self.groups[group_id].toxml('update'))
1861
1862 # this line sends a handle role message to change the players role
1863 self.send_player_list(from_id,group_id)
1864
1865 #notify user about others in the room
1866 self.return_room_roles(from_id,group_id)
1867 self.log_msg(("join_group", (from_id, group_id)))
1868 self.handle_role("set", from_id, self.players[from_id].role, self.groups[group_id].boot_pwd, group_id)
1869
1870 except Exception, e:
1871 self.log_msg(str(e))
1872
1873 thread.start_new_thread(self.registerRooms,(0,))
1874
1875 def return_room_roles(self,from_id,group_id):
1876 for m in self.players.keys():
1877 if self.players[m].group_id == group_id:
1878 msg = "<role action=\"update\" id=\"" + self.players[m].id + "\" role=\"" + self.players[m].role + "\" />"
1879 self.players[from_id].outbox.put(msg)
1880
1881
1882 # This is pretty much the same thing as the create_group method, however,
1883 # it's much more generic whereas the create_group method is tied to a specific
1884 # xml message. Ack! This version simply creates the groups, it does not
1885 # send them to players. Also note, both these methods have race
1886 # conditions written all over them. Ack! Ack!
1887 def new_group( self, name, pwd, boot, minVersion, mapFile, messageFile, persist = 0, moderated=0 ):
1888 group_id = str( self.next_group_id )
1889 self.next_group_id += 1
1890
1891 self.groups[group_id] = game_group( group_id, name, pwd, "", boot, minVersion, mapFile, messageFile, persist )
1892 self.groups[group_id].moderated = moderated
1893 ins = ""
1894 if persist !=0: ins="Persistant "
1895 lmsg = "Creating " + ins + "Group... (" + str(group_id) + ") " + str(name)
1896 self.log_msg( lmsg )
1897
1898
1899 def change_group_name(self,gid,name,pid):
1900 "Change the name of a group"
1901 # Check for & in name. We want to allow this because of its common
1902 # use in d&d games.
1903 try:
1904 loc = name.find("&")
1905 oldloc = 0
1906 while loc > -1:
1907 loc = name.find("&",oldloc)
1908 if loc > -1:
1909 b = name[:loc]
1910 e = name[loc+1:]
1911 value = b + "&amp;" + e
1912 oldloc = loc+1
1913
1914 loc = name.find("'")
1915 oldloc = 0
1916 while loc > -1:
1917 loc = name.find("'",oldloc)
1918 if loc > -1:
1919 b = name[:loc]
1920 e = name[loc+1:]
1921 name = b + "&#39;" + e
1922 oldloc = loc+1
1923
1924 loc = name.find('"')
1925 oldloc = 0
1926 while loc > -1:
1927 loc = name.find('"',oldloc)
1928 if loc > -1:
1929 b = name[:loc]
1930 e = name[loc+1:]
1931 name = b + "&quot;" + e
1932 oldloc = loc+1
1933
1934 oldroomname = self.groups[gid].name
1935 self.groups[gid].name = str(name)
1936 lmessage = "Room name changed to from \"" + oldroomname + "\" to \"" + name + "\""
1937 self.log_msg(lmessage + " by " + str(pid) )
1938 self.send_to_all('0',self.groups[gid].toxml('update'))
1939 return lmessage
1940 except:
1941 return "An error occured during rename of room!"
1942
1943 thread.start_new_thread(self.registerRooms,(0,))
1944
1945
1946
1947 def create_group(self,xml_dom,data):
1948 try:
1949 from_id = xml_dom.getAttribute("from")
1950 pwd = xml_dom.getAttribute("pwd")
1951 name = xml_dom.getAttribute("name")
1952 boot_pwd = xml_dom.getAttribute("boot_pwd")
1953 minVersion = xml_dom.getAttribute("min_version")
1954 #added var reassign -- akoman
1955 messageFile = self.defaultMessageFile
1956
1957 # see if passwords are allowed on this server and null password if not
1958 if self.allow_room_passwords != 1: pwd = ""
1959
1960
1961 #
1962 # Check for & in name. We want to allow this because of its common
1963 # use in d&d games.
1964
1965 loc = name.find("&")
1966 oldloc = 0
1967 while loc > -1:
1968 loc = name.find("&",oldloc)
1969 if loc > -1:
1970 b = name[:loc]
1971 e = name[loc+1:]
1972 name = b + "&amp;" + e
1973 oldloc = loc+1
1974
1975 loc = name.find("'")
1976 oldloc = 0
1977 while loc > -1:
1978 loc = name.find("'",oldloc)
1979 if loc > -1:
1980 b = name[:loc]
1981 e = name[loc+1:]
1982 name = b + "&#39;" + e
1983 oldloc = loc+1
1984
1985 loc = name.find('"')
1986 oldloc = 0
1987 while loc > -1:
1988 loc = name.find('"',oldloc)
1989 if loc > -1:
1990 b = name[:loc]
1991 e = name[loc+1:]
1992 name = b + "&quot;" + e
1993 oldloc = loc+1
1994
1995
1996 group_id = str(self.next_group_id)
1997 self.next_group_id += 1
1998 self.groups[group_id] = game_group(group_id,name,pwd,"",boot_pwd, minVersion, None, messageFile )
1999 self.groups[group_id].voice[from_id]=1
2000 self.players[from_id].outbox.put(self.groups[group_id].toxml('new'))
2001 old_group_id = self.players[from_id].change_group(group_id,self.groups)
2002 self.send_to_group(from_id,old_group_id,self.players[from_id].toxml('del'))
2003 self.check_group(from_id, old_group_id)
2004 self.send_to_all(from_id,self.groups[group_id].toxml('new'))
2005 self.send_to_all('0',self.groups[group_id].toxml('update'))
2006 self.handle_role("set",from_id,"GM",boot_pwd, group_id)
2007 lmsg = "Creating Group... (" + str(group_id) + ") " + str(name)
2008 self.log_msg( lmsg )
2009 jmsg = "moving to room " + str(group_id) + "."
2010 self.log_msg( jmsg )
2011 #even creators of the room should see the HTML --akoman
2012 #edit: jan10/03 - was placed in the except statement. Silly me.
2013 if self.defaultMessageFile != None:
2014 if self.defaultMessageFile[:4] == 'http':
2015 data = urllib.urlretrieve(self.defaultMessageFile)
2016 open_msg = open(data[0])
2017 urllib.urlcleanup()
2018 else:
2019 open_msg = open( self.defaultMessageFile, "r" )
2020
2021 roomMsg = open_msg.read()
2022 open_msg.close()
2023 # Send the rooms message to the client no matter what
2024 self.players[from_id].outbox.put( "<msg to='" + from_id + "' from='0' group_id='" + group_id + "' />" + roomMsg )
2025
2026 except Exception, e:
2027 self.log_msg( "Exception: create_group(): " + str(e))
2028
2029 thread.start_new_thread(self.registerRooms,(0,))
2030
2031
2032 def check_group(self, from_id, group_id):
2033 try:
2034 if group_id not in self.groups: return
2035 if group_id == '0':
2036 self.send_to_all("0",self.groups[group_id].toxml('update'))
2037 return #never remove lobby *sanity check*
2038 if not self.isPersistentRoom(group_id) and self.groups[group_id].get_num_players() == 0:
2039 self.send_to_all("0",self.groups[group_id].toxml('del'))
2040 del self.groups[group_id]
2041 self.log_msg(("delete_group", (from_id, group_id)))
2042
2043 else:
2044 self.send_to_all("0",self.groups[group_id].toxml('update'))
2045
2046 #The register Rooms thread
2047 thread.start_new_thread(self.registerRooms,(0,))
2048
2049 except Exception, e:
2050 self.log_msg(str(e))
2051
2052 def del_player(self,id,group_id):
2053 try:
2054 dmsg = "Client Disconnect: (" + str(id) + ") " + str(self.players[id].name)
2055 self.players[id].disconnect()
2056 self.groups[group_id].remove_player(id)
2057 del self.players[id]
2058 self.log_msg(dmsg)
2059
2060
2061 # If already registered then re-register, thereby updating the Meta
2062 # on the number of players
2063 # Note: Upon server shutdown, the server is first unregistered, so
2064 # this code won't be repeated for each player being deleted.
2065 if self.be_registered:
2066 self.register()
2067
2068
2069 except Exception, e:
2070 self.log_msg(str(e))
2071
2072 self.log_msg("Explicit garbage collection shows %s undeletable items." % str(gc.collect()))
2073
2074
2075
2076 def incoming_player_handler(self,xml_dom,data):
2077 id = xml_dom.getAttribute("id")
2078 act = xml_dom.getAttribute("action")
2079 #group_id = xml_dom.getAttribute("group_id")
2080 group_id = self.players[id].group_id
2081 ip = self.players[id].ip
2082 self.log_msg("Player with IP: " + str(ip) + " joined.")
2083
2084 ServerPlugins.setPlayer(self.players[id])
2085
2086 self.send_to_group(id,group_id,data)
2087 if act=="new":
2088 try:
2089 self.send_player_list(id,group_id)
2090 self.send_group_list(id)
2091 except Exception, e:
2092 traceback.print_exc()
2093 elif act=="del":
2094 #print "del player"
2095 self.del_player(id,group_id)
2096 self.check_group(id, group_id)
2097 elif act=="update":
2098 self.players[id].take_dom(xml_dom)
2099 self.log_msg(("update", {"id": id,
2100 "name": xml_dom.getAttribute("name"),
2101 "status": xml_dom.getAttribute("status"),
2102 "role": xml_dom.getAttribute("role"),
2103 "ip": str(ip),
2104 "group": xml_dom.getAttribute("group_id"),
2105 "room": xml_dom.getAttribute("name"),
2106 "boot": xml_dom.getAttribute("rm_boot"),
2107 "version": xml_dom.getAttribute("version"),
2108 "ping": xml_dom.getAttribute("time") \
2109 }))
2110
2111
2112 def strip_cheat_roll(self, string):
2113 try:
2114 cheat_regex = re.compile('&amp;#91;(.*?)&amp;#93;')
2115 string = cheat_regex.sub( r'[ ' + self.cheat_msg + " \\1 " + self.cheat_msg + ' ]', string)
2116 except:
2117 pass
2118 return string
2119
2120 def strip_body_tags(self, string):
2121 try:
2122 bodytag_regex = re.compile('&lt;\/?body(.*?)&gt;')
2123 string = bodytag_regex.sub('', string)
2124 except:
2125 pass
2126 return string
2127
2128 def msgTooLong(self, length):
2129 if length > self.maxSendSize and not self.maxSendSize == 0:
2130 return True
2131 return False
2132
2133 def incoming_msg_handler(self,xml_dom,data):
2134 xml_dom, data = ServerPlugins.preParseIncoming(xml_dom, data)
2135
2136 to_id = xml_dom.getAttribute("to")
2137 from_id = xml_dom.getAttribute("from")
2138 group_id = xml_dom.getAttribute("group_id")
2139 end = data.find(">")
2140 msg = data[end+1:]
2141
2142 if from_id == "0" or len(from_id) == 0:
2143 print "WARNING!! Message received with an invalid from_id. Message dropped."
2144 return None
2145
2146 #
2147 # check for < body to prevent someone from changing the background
2148 #
2149
2150 data = self.strip_body_tags(data)
2151
2152 #
2153 # check for &#91 and &#93 codes which are often used to cheat with dice.
2154 #
2155 if self.players[from_id].role != "GM":
2156 data = self.strip_cheat_roll(data)
2157
2158 if group_id == '0' and self.msgTooLong(len(msg) and msg[:5] == '<chat'):
2159 self.send("Your message was too long, break it up into smaller parts please", from_id, group_id)
2160 self.log_msg('Message Blocked from Player: ' + self.players[from_id].name + ' attempting to send a message longer then ' + str(self.maxSendSize))
2161 return
2162
2163 if msg[:4] == '<map':
2164 if group_id == '0':
2165 #attempt to change lobby map. Illegal operation.
2166 self.players[from_id].self_message('The lobby map may not be altered.')
2167 elif to_id.lower() == 'all':
2168 #valid map for all players that is not the lobby.
2169 self.send_to_group(from_id,group_id,data)
2170 self.groups[group_id].game_map.init_from_xml(msg)
2171 else:
2172 #attempting to send map to specific individuals which is not supported.
2173 self.players[from_id].self_message('Invalid map message. Message not sent to others.')
2174
2175 elif msg[:6] == '<boot ':
2176 self.handle_boot(from_id,to_id,group_id,msg)
2177
2178 else:
2179 if to_id == 'all':
2180 if self.groups[group_id].moderated and not self.groups[group_id].voice.has_key(from_id):
2181 self.players[from_id].self_message('This room is moderated - message not sent to others')
2182 else:
2183 self.send_to_group(from_id,group_id,data)
2184 else:
2185 self.players[to_id].outbox.put(data)
2186
2187 self.check_group_members(group_id)
2188 return
2189
2190 def sound_msg_handler(self, xml_dom, data):
2191 from_id = xml_dom.getAttribute("from")
2192 group_id = xml_dom.getAttribute("group_id")
2193 if group_id != 0:
2194 self.send_to_group(from_id, group_id, data)
2195
2196 def plugin_msg_handler(self,xml_dom,data):
2197 to_id = xml_dom.getAttribute("to")
2198 from_id = xml_dom.getAttribute("from")
2199 group_id = xml_dom.getAttribute("group_id")
2200 end = data.find(">")
2201 msg = data[end+1:]
2202
2203 if from_id == "0" or len(from_id) == 0:
2204 print "WARNING!! Message received with an invalid from_id. Message dropped."
2205 return None
2206
2207
2208 if to_id == 'all':
2209 if self.groups[group_id].moderated and not self.groups[group_id].voice.has_key(from_id):
2210 self.players[from_id].self_message('This room is moderated - message not sent to others')
2211 else:
2212 self.send_to_group(from_id, group_id, msg)
2213 else:
2214 self.players[to_id].outbox.put(msg)
2215
2216 self.check_group_members(group_id)
2217 return
2218
2219 def handle_role(self, act, player, role, given_boot_pwd, group_id):
2220 if act == "display":
2221 msg = "<msg to=\"" + player + "\" from=\"0\" group_id=\"" + group_id + "\" />"
2222 msg += "Displaying Roles<br /><br /><u>Role</u>&nbsp&nbsp&nbsp<u>Player</u><br />"
2223 keys = self.players.keys()
2224 for m in keys:
2225 if self.players[m].group_id == group_id:
2226 msg += self.players[m].role + " " + self.players[m].name + "<br />"
2227 self.send(msg,player,group_id)
2228 elif act == "set":
2229 try:
2230 actual_boot_pwd = self.groups[group_id].boot_pwd
2231 if self.players[player].group_id == group_id:
2232 if actual_boot_pwd == given_boot_pwd:
2233 self.log_msg( "Administrator passwords match -- changing role")
2234
2235 # Send update role event to all
2236 msg = "<role action=\"update\" id=\"" + player + "\" role=\"" + role + "\" />"
2237 self.send_to_group("0", group_id, msg)
2238 self.players[player].role = role
2239 if (role.lower() == "gm" or role.lower() == "player"):
2240 self.groups[group_id].voice[player]=1
2241 else:
2242 #tell the clients password manager the password failed -- SD 8/03
2243 pm = "<password signal=\"fail\" type=\"admin\" id=\"" + group_id + "\" data=\"\"/>"
2244 self.players[player].outbox.put(pm)
2245 self.log_msg( "Administrator passwords did not match")
2246 except Exception, e:
2247 print e
2248 print "Error executing the role change"
2249 print "due to the following exception:"
2250 traceback.print_exc()
2251 print "Ignoring boot message"
2252
2253 def handle_boot(self,from_id,to_id,group_id,msg):
2254 xml_dom = None
2255 try:
2256 given_boot_pwd = None
2257 try:
2258 xml_dom = parseXml(msg)
2259 xml_dom = xml_dom._get_documentElement()
2260 given_boot_pwd = xml_dom.getAttribute("boot_pwd")
2261
2262 except:
2263 print "Error in parse of boot message, Ignoring."
2264 print "Exception: "
2265 traceback.print_exc()
2266
2267 try:
2268 actual_boot_pwd = self.groups[group_id].boot_pwd
2269 server_admin_pwd = self.groups["0"].boot_pwd
2270
2271 self.log_msg("Actual boot pwd = " + actual_boot_pwd)
2272 self.log_msg("Given boot pwd = " + given_boot_pwd)
2273
2274 if self.players[to_id].group_id == group_id:
2275
2276 ### ---CHANGES BY SNOWDOG 4/03 ---
2277 ### added boot to lobby code.
2278 ### if boot comes from lobby dump player from the server
2279 ### any user in-room boot will dump to lobby instead
2280 if given_boot_pwd == server_admin_pwd:
2281 # Send a message to everyone in the room, letting them know someone has been booted
2282 boot_msg = "<msg to='all' from='%s' group_id='%s'/><font color='#FF0000'>Booting '(%s) %s' from server...</font>" % (from_id, group_id, to_id, self.players[to_id].name)
2283
2284 self.log_msg("boot_msg:" + boot_msg)
2285
2286 self.send_to_group( "0", group_id, boot_msg )
2287 time.sleep( 1 )
2288
2289 self.log_msg("Booting player " + str(to_id) + " from server.")
2290
2291 # Send delete player event to all
2292 self.send_to_group("0",group_id,self.players[to_id].toxml("del"))
2293
2294 # Remove the player from local data structures
2295 self.del_player(to_id,group_id)
2296
2297 # Refresh the group data
2298 self.check_group(to_id, group_id)
2299
2300 elif actual_boot_pwd == given_boot_pwd:
2301 # Send a message to everyone in the room, letting them know someone has been booted
2302 boot_msg = "<msg to='all' from='%s' group_id='%s'/><font color='#FF0000'>Booting '(%s) %s' from room...</font>" % (from_id, group_id, to_id, self.players[to_id].name)
2303
2304 self.log_msg("boot_msg:" + boot_msg)
2305
2306 self.send_to_group( "0", group_id, boot_msg )
2307 time.sleep( 1 )
2308
2309 #dump player into the lobby
2310 self.move_player(to_id,"0")
2311
2312 # Refresh the group data
2313 self.check_group(to_id, group_id)
2314 else:
2315 #tell the clients password manager the password failed -- SD 8/03
2316 pm = "<password signal=\"fail\" type=\"admin\" id=\"" + group_id + "\" data=\"\"/>"
2317 self.players[from_id].outbox.put(pm)
2318 print "boot passwords did not match"
2319
2320 except Exception, e:
2321 traceback.print_exc()
2322 self.log_msg('Exception in handle_boot() ' + str(e))
2323
2324 finally:
2325 try:
2326 if xml_dom:
2327 xml_dom.unlink()
2328 except Exception, e:
2329 traceback.print_exc()
2330 self.log_msg('Exception in xml_dom.unlink() ' + str(e))
2331
2332
2333 #---------------------------------------------------------------
2334 # admin_kick function -- by Snowdog 4/03
2335 # 9/17/05 updated to allow stealth boots (no client chat announce) -SD
2336 #---------------------------------------------------------------
2337 def admin_kick(self, id, message="", silent = 0 ):
2338 "Kick a player from a server from the console"
2339
2340 try:
2341 group_id = self.players[id].group_id
2342 # Send a message to everyone in the victim's room, letting them know someone has been booted
2343 boot_msg = "<msg to='all' from='0' group_id='%s'/><font color='#FF0000'>Kicking '(%s) %s' from server... %s</font>" % ( group_id, id, self.players[id].name, str(message))
2344 self.log_msg("boot_msg:" + boot_msg)
2345 if (silent == 0):
2346 self.send_to_group( "0", group_id, boot_msg )
2347 time.sleep( 1 )
2348
2349 self.log_msg("kicking player " + str(id) + " from server.")
2350 # Send delete player event to all
2351 self.send_to_group("0",group_id,self.players[id].toxml("del"))
2352
2353 # Remove the player from local data structures
2354 self.del_player(id,group_id)
2355
2356 # Refresh the group data
2357 self.check_group(id, group_id)
2358
2359 except Exception, e:
2360 traceback.print_exc()
2361 self.log_msg('Exception in admin_kick() ' + str(e))
2362
2363
2364 def admin_banip(self, ip, name="", silent = 0):
2365 "Ban a player from a server from the console"
2366 try:
2367 self.ban_list[ip] = {}
2368 self.ban_list[ip]['ip'] = ip
2369 self.ban_list[ip]['name'] = name
2370 self.saveBanList()
2371
2372 except Exception, e:
2373 traceback.print_exc()
2374 self.log_msg('Exception in admin_banip() ' + str(e))
2375
2376 def admin_ban(self, id, message="", silent = 0):
2377 "Ban a player from a server from the console"
2378 try:
2379 id = str(id)
2380 group_id = self.players[id].group_id
2381 ip = self.players[id].ip
2382 self.ban_list[ip] = {}
2383 self.ban_list[ip]['ip'] = ip
2384 self.ban_list[ip]['name'] = self.players[id].name
2385 self.saveBanList()
2386
2387 # Send a message to everyone in the victim's room, letting them know someone has been booted
2388 ban_msg = "<msg to='all' from='0' group_id='%s'/><font color='#FF0000'>Banning '(%s) %s' from server... %s</font>" % ( group_id, id, self.players[id].name, str(message))
2389 self.log_msg("ban_msg:" + ban_msg)
2390 if (silent == 0):
2391 self.send_to_group("0", group_id, ban_msg)
2392 time.sleep( .1 )
2393
2394 self.log_msg("baning player " + str(id) + " from server.")
2395 # Send delete player event to all
2396 self.send_to_group("0", group_id, self.players[id].toxml("del"))
2397
2398 # Remove the player from local data structures
2399 self.del_player(id, group_id)
2400
2401 # Refresh the group data
2402 self.check_group(id, group_id)
2403
2404 except Exception, e:
2405 traceback.print_exc()
2406 self.log_msg('Exception in admin_ban() ' + str(e))
2407
2408 def admin_unban(self, ip):
2409 try:
2410 if self.ban_list.has_key(ip):
2411 del self.ban_list[ip]
2412
2413 self.saveBanList()
2414
2415 except Exception, e:
2416 traceback.print_exc()
2417 self.log_msg('Exception in admin_unban() ' + str(e))
2418
2419 def admin_banlist(self):
2420 msg = []
2421 msg.append('<table border="1"><tr><td><b>Name</b></td><td><b>IP</b></td></tr>')
2422 for ip in self.ban_list.keys():
2423 msg.append("<tr><td>")
2424 msg.append(self.ban_list[ip]['name'])
2425 msg.append("</td><td>")
2426 msg.append(self.ban_list[ip]['ip'])
2427 msg.append("</td></tr>")
2428 msg.append("</table>")
2429
2430 return "".join(msg)
2431
2432 def admin_toggleSound(self):
2433 if self.sendLobbySound:
2434 self.sendLobbySound = False
2435 else:
2436 self.sendLobbySound = True
2437
2438 return self.sendLobbySound
2439
2440 def admin_soundFile(self, file):
2441 self.lobbySound = file
2442
2443 def admin_setSendSize(self, sendlen):
2444 self.maxSendSize = sendlen
2445 self.log_msg('Max Send Size was set to ' + str(sendlen))
2446
2447 def remove_room(self, group):
2448 "removes a group and boots all occupants"
2449 #check that group id exists
2450 if group not in self.groups:
2451 return "Invalid Room Id. Ignoring remove request."
2452
2453 self.groups[group].persistant = 0
2454 try:
2455 keys = self.groups[group].get_player_ids()
2456 for k in keys:
2457 self.del_player(k, str(group))
2458 self.check_group("0", str(group))
2459 except:
2460 pass
2461
2462 def send(self,msg,player,group):
2463 self.players[player].send(msg,player,group)
2464
2465
2466 def send_to_all(self,from_id,data):
2467 try:
2468 self.p_lock.acquire()
2469 keys = self.players.keys()
2470 self.p_lock.release()
2471 for k in keys:
2472 if k != from_id:
2473 self.players[k].outbox.put(data)
2474 except Exception, e:
2475 traceback.print_exc()
2476 self.log_msg("Exception: send_to_all(): " + str(e))
2477
2478
2479
2480 def send_to_group(self, from_id, group_id, data):
2481 data = ServerPlugins.postParseIncoming(data)
2482 try:
2483 keys = self.groups[group_id].get_player_ids()
2484 for k in keys:
2485 if k != from_id:
2486 self.players[k].outbox.put(data)
2487 except Exception, e:
2488 traceback.print_exc()
2489 self.log_msg("Exception: send_to_group(): " + str(e))
2490
2491 def send_player_list(self,to_id,group_id):
2492 try:
2493 keys = self.groups[group_id].get_player_ids()
2494 for k in keys:
2495 if k != to_id:
2496 data = self.players[k].toxml('new')
2497 self.players[to_id].outbox.put(data)
2498 except Exception, e:
2499 traceback.print_exc()
2500 self.log_msg("Exception: send_player_list(): " + str(e))
2501
2502 def send_group_list(self, to_id, action="new"):
2503 try:
2504 for key in self.groups:
2505 xml = self.groups[key].toxml(action)
2506 self.players[to_id].outbox.put(xml)
2507 except Exception, e:
2508 self.log_msg("Exception: send_group_list(): (client #"+to_id+") : " + str(e))
2509 traceback.print_exc()
2510
2511 #--------------------------------------------------------------------------
2512 # KICK_ALL_CLIENTS()
2513 #
2514 # Convience method for booting all clients off the server at once.
2515 # used while troubleshooting mysterious "black hole" server bug
2516 # Added by Snowdog 11-19-04
2517 def kick_all_clients(self):
2518 try:
2519 keys = self.groups.keys()
2520 for k in keys:
2521 pl = self.groups[k].get_player_ids()
2522 for p in pl:
2523 self.admin_kick(p,"Purged from server")
2524 except Exception, e:
2525 traceback.print_exc()
2526 self.log_msg("Exception: kick_all_clients(): " + str(e))
2527
2528
2529 # This really has little value as it will only catch people that are hung
2530 # on a disconnect which didn't complete. Other idle connections which are
2531 # really dead go undeterred.
2532 #
2533 # UPDATED 11-29-04: Changed remove XML send to forced admin_kick for 'dead clients'
2534 # Dead clients now removed more effeciently as soon as they are detected
2535 # --Snowdog
2536 def check_group_members(self, group_id):
2537 try:
2538 keys = self.groups[group_id].get_player_ids()
2539 for k in keys:
2540 #drop any clients that are idle for more than 8 hours
2541 #as these are likely dead clients
2542 idlemins = self.players[k].idle_time()
2543 idlemins = idlemins/60
2544 if (idlemins > self.zombie_time):
2545 self.admin_kick(k,"Removing zombie client", self.silent_auto_kick)
2546 elif self.players[k].get_status() != MPLAY_CONNECTED:
2547 if self.players[k].check_time_out():
2548 self.log_msg("Player #" + k + " Lost connection!")
2549 self.admin_kick(k,"Removing dead client", self.silent_auto_kick)
2550 except Exception, e:
2551 self.log_msg("Exception: check_group_members(): " + str(e))
2552
2553
2554 def remote_admin_handler(self,xml_dom,data):
2555 # handle incoming remove server admin messages
2556 # (allows basic administration of server from a remote client)
2557 # base message format: <admin id="" pwd="" cmd="" [data for command]>
2558
2559 if not self.allowRemoteAdmin:
2560 return
2561
2562 try:
2563 pid = xml_dom.getAttribute("id")
2564 gid = ""
2565 given_pwd = xml_dom.getAttribute("pwd")
2566 cmd = xml_dom.getAttribute("cmd")
2567 server_admin_pwd = self.groups["0"].boot_pwd
2568 p_id = ""
2569 p_name= ""
2570 p_ip = ""
2571
2572
2573 #verify that the message came from the proper ID/Socket and get IP address for logging
2574 if self.players.has_key(pid):
2575 p_name=(self.players[pid]).name
2576 p_ip=(self.players[pid]).ip
2577 gid=(self.players[pid]).group_id
2578 else:
2579 #invalid ID.. report fraud and log
2580 m = "Invalid Remote Server Control Message (invalid id) #" + str(pid) + " does not exist."
2581 self.log_msg( m )
2582 return
2583
2584 #log receipt of admin command added by Darren
2585 m = "Remote Server Control Message ( "+ str(cmd) +" ) from #" + str(pid) + " (" + str(p_name) + ") " + str(p_ip)
2586 self.log_msg ( m )
2587
2588 #check the admin password(boot password) against the supplied one in message
2589 #dump and log any attempts to control server remotely with invalid password
2590 if server_admin_pwd != given_pwd:
2591 #tell the clients password manager the password failed -- SD 8/03
2592 pm = "<password signal=\"fail\" type=\"server\" id=\"" + str(self.players[pid].group_id) + "\" data=\"\"/>"
2593 self.players[pid].outbox.put(pm)
2594 m = "Invalid Remote Server Control Message (bad password) from #" + str(pid) + " (" + str(p_name) + ") " + str(p_ip)
2595 self.log_msg( m )
2596 return
2597
2598 #message now deemed 'authentic'
2599 #determine action to take based on command (cmd)
2600
2601 if cmd == "list":
2602 #return player list to this user.
2603 msg ="<msg to='" + pid + "' from='0' group_id='" + gid + "'>" + self.player_list_remote()
2604 self.players[pid].outbox.put(msg)
2605
2606 elif cmd == "banip":
2607 ip = xml_dom.getAttribute("bip")
2608 name = xml_dom.getAttribute("bname")
2609 msg = "<msg to='" + pid + "' from='0' group_id='" + gid + "'> Banned: " + str(ip)
2610 self.admin_banip(ip, name)
2611
2612 elif cmd == "ban":
2613 id = xml_dom.getAttribute("bid")
2614 msg = "<msg to='" + id + "' from='0' group_id='" + gid + "'> Banned!"
2615 self.players[pid].outbox.put(msg)
2616 self.admin_ban(id, "")
2617
2618 elif cmd == "unban":
2619 ip = xml_dom.getAttribute("ip")
2620 self.admin_unban(ip)
2621 msg = "<msg to='" + pid + "' from='0' group_id='" + gid + "'> Unbaned: " + str(ip)
2622 self.players[pid].outbox.put(msg)
2623
2624 elif cmd == "banlist":
2625 msg = "<msg to='" + pid + "' from='0' group_id='" + gid + "'>" + self.admin_banlist()
2626 self.players[pid].outbox.put(msg)
2627
2628 elif cmd == "killgroup":
2629 ugid = xml_dom.getAttribute("gid")
2630 if ugid == "0":
2631 m = "<msg to='" + pid + "' from='0' group_id='" + gid + "'>Cannot Remove Lobby! Remote administrator request denied!"
2632 self.players[pid].outbox.put(m)
2633 else:
2634 result = self.prune_room(ugid)
2635 msg = "<msg to='" + pid + "' from='0' group_id='" + gid + "'>" + str(result)
2636 self.players[pid].outbox.put(msg)
2637
2638 elif cmd == "message":
2639 tuid = xml_dom.getAttribute("to_id")
2640 msg = xml_dom.getAttribute("msg")
2641 pmsg = "<msg to='" + tuid + "' from='0' group_id='" + self.players[tuid].group_id + "' >" + msg
2642 try: self.players[tuid].outbox.put(pmsg)
2643 except:
2644 msg = "<msg to='" + pid + "' from='0' group_id='" + gid + ">Unknown Player ID: No message sent."
2645 self.players[pid].outbox.put(msg)
2646
2647 elif cmd == "broadcast":
2648 bmsg = xml_dom.getAttribute("msg")
2649 self.broadcast(bmsg)
2650
2651 elif cmd == "killserver" and self.allowRemoteKill:
2652 #dangerous command..once server stopped it must be restarted manually
2653 self.kill_server()
2654
2655 elif cmd == "uptime":
2656 msg ="<msg to='" + pid + "' from='0' group_id='" + gid + "'>" + self.uptime(1)
2657 self.players[pid].outbox.put(msg)
2658
2659 elif cmd == "help":
2660 msg = "<msg to='" + pid + "' from='0' group_id='" + gid + "'>"
2661 msg += self.AdminHelpMessage()
2662 self.players[pid].outbox.put( msg)
2663
2664 elif cmd == "roompasswords":
2665 # Toggle if room passwords are allowed on this server
2666 msg = "<msg to='" + pid + "' from='0' group_id='" + gid + "'>"
2667 msg += self.RoomPasswords()
2668 self.players[pid].outbox.put( msg)
2669
2670 elif cmd == "createroom":
2671 rm_name = xml_dom.getAttribute("name")
2672 rm_pass = xml_dom.getAttribute("pass")
2673 rm_boot = xml_dom.getAttribute("boot")
2674 result = self.create_temporary_persistant_room(rm_name, rm_boot, rm_pass)
2675 msg = "<msg to='" + pid + "' from='0' group_id='" + gid + "'>" + result
2676 self.players[pid].outbox.put(msg)
2677
2678 elif cmd == "nameroom":
2679 rm_id = xml_dom.getAttribute("rmid")
2680 rm_name = xml_dom.getAttribute("name")
2681 result = self.change_group_name(rm_id,rm_name,pid)
2682 msg ="<msg to='" + pid + "' from='0' group_id='" + gid + "'/>" + result
2683 self.players[pid].outbox.put(msg)
2684
2685 elif cmd == "passwd":
2686 tgid = xml_dom.getAttribute("gid")
2687 npwd = xml_dom.getAttribute("pass")
2688 if tgid == "0":
2689 msg ="<msg to='" + pid + "' from='0' group_id='" + gid + "'>Server password may not be changed remotely!"
2690 self.players[pid].outbox.put(msg)
2691 else:
2692 try:
2693 self.groups[tgid].boot_pwd = npwd
2694 msg ="<msg to='" + pid + "' from='0' group_id='" + gid + "'>Password changed for room " + tgid
2695 self.players[pid].outbox.put(msg)
2696 except: pass
2697
2698 elif cmd == "savemaps":
2699 for g in self.groups.itervalues():
2700 g.save_map()
2701
2702 msg ="<msg to='" + pid + "' from='0' group_id='" + gid + "'>Persistent room maps saved"
2703 self.players[pid].outbox.put(msg)
2704
2705
2706 else:
2707 msg ="<msg to='" + pid + "' from='0' group_id='" + gid + "'><i>[Unknown Remote Administration Command]</i>"
2708 self.players[pid].outbox.put(msg)
2709
2710
2711 except Exception, e:
2712 self.log_msg("Exception: Remote Admin Handler Error: " + str(e))
2713 traceback.print_exc()
2714
2715
2716 def toggleRemoteKill(self):
2717 if self.allowRemoteKill:
2718 self.allowRemoteKill = False
2719 else:
2720 self.allowRemoteKill = True
2721
2722 return self.allowRemoteKill
2723
2724 def toggleRemoteAdmin(self):
2725 if self.allowRemoteAdmin:
2726 self.allowRemoteAdmin = False
2727 else:
2728 self.allowRemoteAdmin = True
2729
2730 return self.allowRemoteAdmin
2731
2732 #-----------------------------------------------------------------
2733 # Remote Administrator Help (returns from server not client)
2734 #-----------------------------------------------------------------
2735 def AdminHelpMessage(self):
2736 "returns a string to be sent as a message to a remote admin"
2737
2738 #define the help command list information
2739 info = []
2740 info.append( ['list', '/admin list', 'Displays information about rooms and players on the server'] )
2741 info.append( ['uptime', '/admin uptime', 'Information on how long server has been running'] )
2742 info.append( ['help', '/admin help', 'This help message'])
2743 info.append( ['passwd', '/admin passwd &lt;group id&gt; &lt;new password&gt;', 'Changes a rooms bootpassword. Server(lobby) password may not be changed'])
2744 info.append( ['roompasswords', '/admin roompasswords', 'Allow/Disallow Room Passwords on the server (toggles)'])
2745 info.append( ['message', '/admin message &lt;user id&gt; &lt;message&gt;', 'Send a message to a specific user on the server'])
2746 info.append( ['broadcast', '/admin broadcast &lt;message&gt;', 'Broadcast message to all players on server'])
2747 info.append( ['createroom', '/admin createroom &lt;room name&gt; &lt;boot password&gt; [password]', 'Creates a temporary persistant room if possible.<i>Rooms created this way are lost on server restarts'])
2748 info.append( ['nameroom', '/admin nameroom &lt;group id&gt; &lt;new name&gt;', 'Rename a room'])
2749 info.append( ['killgroup', '/admin killgroup &lt;room id&gt;', 'Remove a room from the server and kick everyone in it.'])
2750 if self.allowRemoteKill:
2751 info.append( ['killserver', '/admin killserver', 'Shuts down the server. <b>WARNING: Server cannot be restarted remotely via OpenRPG</b>'])
2752 info.append( ['ban', '/admin ban {playerId}', 'Ban a player from the server.'])
2753 info.append( ['unban', '/admin unban {bannedIP}', 'UnBan a player from the server.'])
2754 info.append( ['banlist', '/admin banlist', 'List Banned IPs and the Names associated with them'])
2755 info.append( ['savemaps', '/admin savemaps', 'Save all persistent room maps that are not using the default map file.'])
2756
2757
2758 #define the HTML for the help display
2759 FS = "<font size='-1'>"
2760 FE = "<font>"
2761
2762 help = "<hr><B>REMOTE ADMINISTRATOR COMMANDS SUPPORTED</b><br /><br />"
2763 help += "<table border='1' cellpadding='2'>"
2764 help += "<tr><td width='15%'><b>Command</b></td><td width='25%'><b>Format</b></td><td width='60%'><b>Description</b></td></tr>"
2765 for n in info:
2766 help += "<tr><td>" + FS + n[0] + FE + "</td><td><nobr>" + FS + n[1] + FE + "</nobr></td><td>" + FS + n[2] + FE + "</td></tr>"
2767 help += "</table>"
2768 return help
2769
2770
2771 #----------------------------------------------------------------
2772 # Create Persistant Group -- Added by Snowdog 6/03
2773 #
2774 # Allows persistant groups to be created on the fly.
2775 # These persistant groups are not added to the server.ini file
2776 # however and are lost on server restarts
2777 #
2778 # Updated function code to use per-group based persistance and
2779 # removed references to outdated persistRoomIdThreshold
2780 #----------------------------------------------------------------
2781
2782 def create_temporary_persistant_room(self, roomname, bootpass, password=""):
2783 # if the room id just above the persistant room limit is available (not in use)
2784 # then it will be assigned as a persistant room on the server
2785 "create a temporary persistant room"
2786
2787 group_id = str(self.next_group_id)
2788 self.next_group_id += 1
2789 self.groups[group_id] = game_group( group_id, roomname, password, "", bootpass, persist = 1 )
2790 cgmsg = "Create Temporary Persistant Group: (" + str(group_id) + ") " + str(roomname)
2791 self.log_msg( cgmsg )
2792 self.send_to_all('0',self.groups[group_id].toxml('new'))
2793 self.send_to_all('0',self.groups[group_id].toxml('update'))
2794 return str("Persistant room created (group " + group_id + ").")
2795
2796 #----------------------------------------------------------------
2797 # Prune Room -- Added by Snowdog 6/03
2798 #
2799 # similar to remove_room() except rooms are removed regardless
2800 # of them being persistant or not
2801 #
2802 # Added some error checking and updated room removal for per-room
2803 # based persistance -- Snowdog 4/04
2804 #----------------------------------------------------------------
2805
2806 def prune_room(self,group):
2807 #don't allow lobby to be removed
2808 if group == '0': return "Lobby is required to exist and cannot be removed."
2809
2810 #check that group id exists
2811 if group not in self.groups:
2812 return "Invalid Room Id. Ignoring remove request."
2813
2814 try:
2815 keys = self.groups[group].get_player_ids()
2816 for k in keys:
2817 self.move_player(k,'0')
2818
2819 ins = "Room"
2820 if self.isPersistentRoom(group) : ins="Persistant room"
2821 self.send_to_all("0",self.groups[group].toxml('del'))
2822 del self.groups[group]
2823 self.log_msg(("delete_group", ('0',group)))
2824 return ins + " removed."
2825
2826 except:
2827 traceback.print_exc()
2828 return "An Error occured on the server during room removal!"
2829
2830
2831 #----------------------------------------------------------------
2832 # Remote Player List -- Added by snowdog 6/03
2833 #
2834 # Similar to console listing except formated for web display
2835 # in chat window on remote client
2836 #----------------------------------------------------------------
2837 def player_list_remote(self):
2838 COLOR1 = "\"#004080\"" #header/footer background color
2839 COLOR2 = "\"#DDDDDD\"" #group line background color
2840 COLOR3 = "\"#FFFFFF\"" #player line background color
2841 COLOR4 = "\"#FFFFFF\"" #header/footer text color
2842 PCOLOR = "\"#000000\"" #Player text color
2843 LCOLOR = "\"#404040\"" #Lurker text color
2844 GCOLOR = "\"#FF0000\"" #GM text color
2845 SIZE = "size=\"-1\"" #player info text size
2846 FG = PCOLOR
2847
2848
2849 "display a condensed list of players on the server"
2850 self.p_lock.acquire()
2851 pl = "<br /><table border=\"0\" cellpadding=\"1\" cellspacing=\"2\">"
2852 pl += "<tr><td colspan='4' bgcolor=" + COLOR1 + "><font color=" + COLOR4 + "><b>GROUP &amp; PLAYER LIST</b></font></td></tr>"
2853 try:
2854
2855 keys = self.groups.keys()
2856 keys.sort(id_compare)
2857 for k in keys:
2858 groupstring = "<tr><td bgcolor=" + COLOR2 + " colspan='2'><b>Group " + str(k) + ": " + self.groups[k].name + "</b>"
2859 groupstring += "</td><td bgcolor=" + COLOR2 + " > <i>Password: \"" + self.groups[k].pwd + "\"</td><td bgcolor=" + COLOR2 + " > Boot: \"" + self.groups[k].boot_pwd + "\"</i></td></tr>"
2860 pl += groupstring
2861 ids = self.groups[k].get_player_ids()
2862 ids.sort(id_compare)
2863 for id in ids:
2864 if self.players.has_key(id):
2865 if k != "0":
2866 if (self.players[id]).role == "GM": FG = GCOLOR
2867 elif (self.players[id]).role == "Player": FG = PCOLOR
2868 else: FG = LCOLOR
2869 else: FG = PCOLOR
2870 pl += "<tr><td bgcolor=" + COLOR3 + ">"
2871 pl += "<font color=" + FG + " " + SIZE + ">&nbsp;&nbsp;(" + (self.players[id]).id + ") "
2872 pl += (self.players[id]).name
2873 pl += "</font></td><td bgcolor=" + COLOR3 + " ><font color=" + FG + " " + SIZE + ">[IP: " + (self.players[id]).ip + "]</font></td><td bgcolor=" + COLOR3 + " ><font color=" + FG + " " + SIZE + "> "
2874 pl += (self.players[id]).idle_status()
2875 pl += "</font></td><td><font color=" + FG + " " + SIZE + ">"
2876 pl += (self.players[id]).connected_time_string()
2877 pl += "</font>"
2878
2879 else:
2880 self.groups[k].remove_player(id)
2881 pl +="<tr><td colspan='4' bgcolor=" + COLOR3 + " >Bad Player Ref (#" + id + ") in group"
2882 pl+="</td></tr>"
2883 pl += "<tr><td colspan='4' bgcolor=" + COLOR1 + "><font color=" + COLOR4 + "><b><i>Statistics: groups: " + str(len(self.groups)) + " players: " + str(len(self.players)) + "</i></b></font></td></tr></table>"
2884 except Exception, e:
2885 self.log_msg(str(e))
2886 self.p_lock.release()
2887 return pl