1
2
3
4 """
5 The server classes.
6 """
7
8 import sys
9
10 from twisted.internet import reactor
11 from twisted.internet import protocol
12 from twisted.internet.task import LoopingCall
13
14 from lacewing.protocol import BaseProtocol
15 from lacewing.idpool import IDPool
16 from lacewing.multidict import MultikeyDict
17 from lacewing.packet import ServerPacket, ClientPacket
18 from lacewing.packetloaders import client, server
19 from lacewing.packetloaders.common import (detectType, CONNECT, SET_NAME,
20 JOIN_CHANNEL, LEAVE_CHANNEL, CHANNEL_LIST)
21 import lacewing
22
24 """
25 Represents a channel.
26
27 @ivar id: ID of the channel.
28 @ivar name: The name of the channel.
29 @ivar connections: List of the clients in the channel.
30 @ivar autoClose: True if this channel will close after the master leaves
31 @ivar master: The master connection of this channel
32 @type master: L{ServerProtocol} object
33 """
34
35 id = None
36 name = None
37 hidden = False
38 autoClose = False
39 master = None
40
41 - def __init__(self, name, id, hidden, autoClose, master):
48
63
90
91 - def sendMessage(self, message, subchannel = 0, fromConnection = None,
92 asObject = False, typeName = None, **settings):
93 """
94 Send a channel message from the given protocol instance.
95
96 @param fromConnection: The client that the message is sent
97 from (or None if the message is from the server).
98 @param message: The message to send.
99 @type message: str/number/ByteReader
100 @param subchannel: The subchannel to send the message on.
101 @type subchannel: number
102 @param typeName: If not specified, the type will be
103 automatically detected (see L{lacewing.packetloaders.common.DATA_TYPES}
104 for possible values)
105 """
106 if typeName is None:
107 typeName = detectType(message)
108 if asObject:
109 if fromConnection is None:
110 newMessage = server.ObjectServerChannelMessage()
111 else:
112 newMessage = server.ObjectChannelMessage()
113 else:
114 if fromConnection is None:
115 newMessage = server.BinaryServerChannelMessage()
116 else:
117 newMessage = server.BinaryChannelMessage()
118 newMessage.channel = self.id
119 newMessage.value = message
120 newMessage.subchannel = subchannel
121 if fromConnection is not None:
122 newMessage.peer = fromConnection.id
123 newMessage.setDataType(typeName)
124 self.sendLoader(newMessage, [fromConnection], **settings)
125
126 - def sendPrivateMessage(self, message, subchannel, sender, recipient,
127 asObject = False, typeName = None, **settings):
128 """
129 Send a message from this client to a recipient
130
131 @type message: str/number/ByteReader
132 @param subchannel: The subchannel of the message in the range 0-256
133 @param sender: Sending connection
134 @type sender: L{ServerProtocol} object
135 @param recipient: Connection to send message to
136 @type recipient: L{ServerProtocol} object
137 @param typeName: If not specified, the type will be
138 automatically detected (see L{lacewing.packetloaders.common.DATA_TYPES}
139 for possible values)
140 """
141 if typeName is None:
142 typeName = detectType(message)
143 if asObject:
144 newMessage = server.ObjectPeerMessage()
145 else:
146 newMessage = server.BinaryPeerMessage()
147 newMessage.channel = self.id
148 newMessage.value = message
149 newMessage.subchannel = subchannel
150 newMessage.peer = sender.id
151 newMessage.setDataType(typeName)
152 recipient.sendLoader(newMessage, **settings)
153
154 - def sendLoader(self, type, notClients = [], **settings):
165
167 """
168 The server protocol.
169 @ivar latency: The latency between the server and client.
170 @ivar datagramPort: The UDP port that the connection
171 sends/receives data on.
172 """
173 _timeoutCall = None
174
175 _currentPing = None
176 _pingTime = None
177 latency = None
178
179 datagramPort = None
180 _receivePacket = ClientPacket
181
182 _firstByte = True
183
185 """
186 When a connection is made, a timeout timer is started,
187 and if the user does not request connection acceptance
188 within reasonable time, disconnect (to prevent flood attacks).
189 """
190 BaseProtocol.connectionMade(self)
191 timeOut = self.factory.timeOut
192 if timeOut is not None:
193 self._timeoutCall = reactor.callLater(timeOut, self._timedOut)
194
198
199 - def ping(self, timeOut = None):
213
217
237
247
249 packetId = loader.id
250 if packetId == client.Request.id:
251 if loader.request == CONNECT:
252 if self.isAccepted:
253 self.disconnect(CONNECT, 'Already accepted')
254 return
255
256 if self._timeoutCall is not None:
257 self._timeoutCall.cancel()
258 self._timeoutCall = None
259
260 factory = self.factory
261
262 if loader.version != self.revision:
263 self.disconnect(CONNECT, 'Invalid revision')
264 return
265
266
267
268 acceptedConnections = [connection for connection in
269 factory.connections.values() if connection.isAccepted]
270
271 if len(acceptedConnections)+1 > self.factory.maxUsers:
272 self.disconnect(CONNECT, 'Server full')
273 return
274
275
276 if self.acceptConnection(loader) == False:
277 return
278 self.id = factory.userPool.pop()
279
280 self.factory.connections[(self.id,)] = self
281
282 newWelcome = server.Response()
283 newWelcome.response = CONNECT
284 newWelcome.playerId = self.id
285 newWelcome.success = True
286 newWelcome.welcome = self.factory.getWelcomeMessage(self)
287 self.sendLoader(newWelcome)
288 self.isAccepted = True
289 self.connectionAccepted(loader)
290
291 elif loader.request == SET_NAME:
292 name = loader.name
293
294 if self.acceptLogin(name) == False:
295 return
296
297 if not self.validName(name):
298 self.warn(SET_NAME, 'Invalid name', name = name)
299 return
300
301 for channel in self.channels.values():
302 if name in channel.connections:
303 self.warn(SET_NAME, 'Name already taken', name = name)
304 return
305
306 if not self.loggedIn:
307 self.setName(name)
308 self.loginAccepted(name)
309 else:
310 self.setName(name)
311 self.nameChanged(name)
312
313 elif loader.request == JOIN_CHANNEL:
314 channelName = loader.name
315 if not self.loggedIn:
316 self.disconnect(JOIN_CHANNEL, 'Name not set',
317 name = channelName)
318 return
319 if channelName in self.channels:
320 self.warn(JOIN_CHANNEL, 'Already part of channel',
321 name = channelName)
322 return
323 if self.acceptChannelJoin(channelName) == False:
324 return
325 if not self.validName(channelName):
326 self.warn(JOIN_CHANNEL, 'Invalid channel name',
327 name = channelName)
328 return
329 try:
330 channel, = self.factory.channels[channelName]
331 if self.name in channel.connections:
332 self.warn(JOIN_CHANNEL, 'Name already taken',
333 name = channelName)
334 return
335 except KeyError:
336 pass
337 flags = loader.flags
338 channel = self.joinChannel(channelName, flags['HideChannel'],
339 flags['AutoClose'])
340 self.channelJoined(channel)
341
342 elif loader.request == LEAVE_CHANNEL:
343 channelId = loader.channel
344 if not channelId in self.channels:
345 self.warn(LEAVE_CHANNEL, 'No such channel',
346 channel = channelId)
347 return
348 channel, = self.channels[channelId]
349 if self.acceptChannelLeave(channel) == False:
350 return
351 self.leaveChannel(channel)
352 self.channelLeft(channel)
353
354 elif loader.request == CHANNEL_LIST:
355 if not self.factory.channelListing:
356 self.disconnect(CHANNEL_LIST, 'Channel listing not enabled')
357 return
358 if self.acceptChannelListRequest() == False:
359 return
360 channels = []
361 for channel in self.factory.channels.values():
362 if not channel.hidden:
363 channels.append(
364 (channel.name, len(channel.connections)))
365 self.sendChannelList(channels)
366 self.channelListSent()
367
368 elif packetId in (client.BinaryServerMessage.id,
369 client.ObjectServerMessage.id):
370 self.messageReceived(loader)
371
372 elif packetId in (client.BinaryChannelMessage.id,
373 client.ObjectChannelMessage.id):
374 channelId = loader.channel
375
376 if not channelId in self.channels:
377 return
378
379 channel, = self.channels[channelId]
380
381 if self.acceptChannelMessage(channel, loader) == False:
382 return
383 channel.sendMessage(loader.value, loader.subchannel, self,
384 typeName = loader.getDataType(), asObject = loader.isObject,
385 asDatagram = loader.settings.get('datagram', False))
386 self.channelMessageReceived(channel, loader)
387
388 elif packetId in (client.BinaryPeerMessage.id,
389 client.ObjectPeerMessage.id):
390 channelId = loader.channel
391
392 if channelId not in self.channels:
393 return
394
395 channel, = self.channels[channelId]
396 try:
397 player, = channel.connections[loader.peer]
398 except KeyError:
399 return
400 if self.acceptPrivateMessage(channel, player, loader) == False:
401 return
402 channel.sendPrivateMessage(loader.value, loader.subchannel,
403 self, player, typeName = loader.getDataType(),
404 asObject = loader.isObject,
405 asDatagram = loader.settings.get('datagram', False))
406 self.privateMessageReceived(channel, player, loader)
407
408 elif packetId == client.ChannelMaster.id:
409 if not self.factory.masterRights:
410 return
411 channelId = loader.channel
412 if channelId not in self.channels:
413 return
414 channel, = self.channels[channelId]
415 if channel.master is not self:
416 return
417 try:
418 peer, = channel.connections[loader.peer]
419 except KeyError:
420 return
421 action = loader.action
422 if self.acceptMasterAction(channel, peer, action) == False:
423 return
424 if action == client.MASTER_KICK:
425 peer.leaveChannel(channel)
426 self.masterActionExecuted(channel, peer, action)
427
428 elif packetId == client.UDPHello.id and isDatagram:
429 self.udpEnabled = True
430 self.sendLoader(server.UDPWelcome(), True)
431
432 elif packetId == client.Pong.id:
433 if self._pingTime is None:
434 self.disconnect(CONNECT, 'No pong requested')
435 return
436 if self._currentPing:
437 self._currentPing.cancel()
438 self._currentPing = None
439 self.latency = reactor.seconds() - self._pingTime
440 self._pingTime = None
441 self.pongReceived(self.latency)
442 else:
443 print '(unexpected packet: %r)' % loader
444 self.disconnect()
445
446 - def sendLoader(self, loader, asDatagram = False):
447 """
448 Sends a packetloader to the client
449 @param loader: The packetloader to send.
450 @param asDatagram: True if the packet should be sent as a datagram
451 packet
452 """
453
454
455 if asDatagram:
456 if (self.factory.datagram is not None
457 and self.datagramPort is not None):
458 self.factory.datagram.sendLoader(self, loader)
459 else:
460 newPacket = ServerPacket()
461 newPacket.loader = loader
462 data = newPacket.generate()
463 self.transport.write(data)
464
465 - def disconnect(self, response = None, *arg, **kw):
466 """
467 Disconnect the the user for a specific reason.
468 """
469 if response is not None:
470 self.warn(response, **kw)
471 self.transport.loseConnection()
472
473 - def warn(self, response, warning = '', name = None, channel = None):
485
486 - def sendMessage(self, message, subchannel, channel = None, typeName = None,
487 asObject = False, asDatagram = False):
515
536
537 - def joinChannel(self, channelName, hidden = False, autoClose = False):
562
590
592 """
593 Sends a list of channels and their peer count to the other end of
594 the connection.
595
596 @param channels: List of 2-item tuples (channelName, peerCount)
597 """
598 channelList = server.Response()
599 channelList.response = CHANNEL_LIST
600 channelList.success = True
601 channelList.channels = channels
602 self.sendLoader(channelList)
603
605 """
606 Called when the server has accepted the requested connection.
607 @type welcome: L{client.Request} object
608 """
609
611 """
612 Called when the server has accepted the requested name.
613 @arg name: The name of the client.
614 @type name: str object
615 """
616
618 """
619 Called after a requested channel list is sent.
620 """
621
623 """
624 Called when the client changed name (after logging in).
625 @type name: str
626 @arg name: The name the client has changed to.
627 """
628
630 """
631 Called when a client message arrives.
632 @type message: L{client.BinaryServerMessage} or
633 L{client.ObjectServerMessage}
634 """
635
637 """
638 Called when the client sends a channel message.
639 @arg channel: the channel the user is sending to.
640 @type message: L{client.BinaryChannelMessage} or
641 L{client.ObjectChannelMessage}
642 """
643
645 """
646 Called when the client has sent a private message
647 @arg channel: the channel the user is sending from.
648 @type message: L{client.BinaryPeerMessage} or
649 L{client.ObjectPeerMessage}
650 @type recipient: L{ServerProtocol} object
651 """
652
654 """
655 Called when the client has joined a new channel.
656 @arg channel: The channel the client has joined.
657 """
658
660 """
661 Called when the client has left a channel.
662 @arg channel: The channel the client has left.
663 """
664
666 """
667 Called when a ping response has been received.
668 @arg latency: Round-trip time in seconds between the client and
669 the server.
670 """
671
673 """
674 Called when after a master action has been executed.
675 @arg channel: The channel the action took place in
676 @arg peer: The peer the action was executed on
677 @arg action: The action the master requested
678 @type action: Currently, only {pylacewing.constants.MASTER_KICK}
679 """
680
681
682
684 """
685 Cancels the client login if False is returned.
686 Useful if the server specifies client names.
687 @arg name: The requested client name.
688 @return: Return False to cancel client login.
689 @rtype: bool
690 """
691
693 """
694 Cancels the client name change if False is returned.
695 Useful if the server specifies client names.
696 @arg name: The requested client name.
697 @return: Return False to cancel name change.
698 @rtype: bool
699 """
700
702 """
703 Cancels the connection accept response if False is returned.
704 Useful for banning.
705 @type welcome: L{client.Request} object
706 @return: Return False to cancel connection acceptance.
707 @rtype: bool
708 """
709
711 """
712 Cancels the channel message if False is returned.
713 Useful for blocking invalid messages.
714 @type message: L{client.BinaryChannelMessage} or
715 L{client.ObjectChannelMessage}
716 @return: Return False to stop the channel message
717 before it is sent.
718 @rtype: bool
719 """
720
722 """
723 Cancels the private message if False is returned.
724 Useful for blocking invalid messages.
725 @arg channel: the channel the user sending from.
726 @type message: L{client.BinaryPeerMessage} or
727 L{client.ObjectPeerMessage}
728 @type recipient: L{ServerProtocol} object
729 @return: Return False to stop the channel message
730 before it is sent.
731 @rtype: bool
732 """
733
735 """
736 Cancels the sending of the channel list
737 if False is returned.
738 Useful for servers with a strict policy.
739 @return: Return False to send no channel list.
740 @rtype: bool
741 """
742
744 """
745 Cancels the channel join accept response if False is returned.
746 Useful for channel-less servers.
747 @arg channelName: The name of the channel.
748 @return: Return False to cancel channel join acceptance.
749 @rtype: bool
750 """
751
753 """
754 Cancels the channel leave accept response if False is returned.
755 Useful if the client may not leave the current channel.
756 @arg channel: The channel the client is leaving.
757 @return: Return False to cancel channel join acceptance.
758 @rtype: bool
759 """
760
762 """
763 Cancels a master action if False is returned.
764 Useful for blocking some specific master actions.
765 @arg channel: The channel the action is taking place in.
766 @arg peer: The peer this action is executed on.
767 @arg action: The action the master has requested.
768 @type action: Currently, only {pylacewing.constants.MASTER_KICK}
769 @rtype: bool
770 """
771
798
800 """
801 The server factory.
802
803 @ivar channelClass: This is the channel class that will be used when
804 creating a new channel. Subclass L{ServerChannel} and replace this
805 attribute if you want to change the behaviour of channels.
806 @ivar maxPing: This is the time the client has to respond
807 to pings (in seconds). Can be a number or None for no max ping (default)
808 @ivar pingTime: The interval between pings in seconds
809 @ivar maxUsers: The max number of users allowed on the server.
810 @ivar timeOut: The number of seconds the client has to send a Hello packet
811 before being disconnected. Can be a number or None for no timeout
812 @ivar welcomeMessage: The message sent to accepted clients.
813 @ivar ping: If True, pinging will be enabled on the server
814 @ivar channelListing: If True, channelListing is enabled on the server
815 @ivar masterRights: If True, this enables the autoclose feature for
816 clients when creating channels
817 """
818 channelClass = ServerChannel
819 timeOut = 8
820 pingTime = 8
821 maxPing = None
822 maxUsers = 1000
823 welcomeMessage = 'Welcome! Server is running pylacewing %s (%s)' % (
824 lacewing.__version__, sys.platform)
825
826 datagram = None
827 ping = True
828 channelListing = True
829 masterRights = False
830
831 _pinger = None
832
841
848
850 """
851 This method is called when a connection has been accepted, and
852 a welcome message has to be sent. The default implementation just
853 returns L{welcomeMessage}, but override this method to change that
854 behaviour.
855 @param connection: Connection that has been accepted
856 @type connection: L{ServerProtocol} object
857 @rtype: str
858 """
859 return self.welcomeMessage
860
872
876
878 """
879 Called when a channel has no users in it, and
880 is therefore removed.
881 @arg channel: The channel that is being removed.
882 """
883
885 """
886 Called when a new channel is created.
887 @arg channel: The channel that has been created.
888 """
889