import struct import binascii import logging import zipfile from time import time from collections import defaultdict try: from cStringIO import StringIO except ImportError: from StringIO import StringIO import gevent from gevent import event, queue from random import shuffle from steam.steamid import SteamID from steam.enums import EResult, EUniverse from steam.enums.emsg import EMsg from steam.core import crypto from steam.core.connection import TCPConnection from steam.core.msg import Msg, MsgProto from eventemitter import EventEmitter from steam.util import ip_from_int, is_proto, clear_proto_bit logger = logging.getLogger("CMClient") class CMClient(EventEmitter): """ CMClient provides a secure message channel to Steam CM servers Incoming messages are parsed and emitted as events using their :class:`steam.enums.emsg.EMsg` as event identifier """ TCP = 0 #: TCP protocol enum UDP = 1 #: UDP protocol enum verbose_debug = False #: print message connects in debug servers = None #: a instance of :class:`steam.core.cm.CMServerList` current_server_addr = None #: (ip, port) tuple connected = False #: :class:`True` if connected to CM channel_secured = False #: :class:`True` once secure channel handshake is complete key = None #: channel encryption key hmac_secret = None #: HMAC secret steam_id = SteamID() #: :class:`steam.steamid.SteamID` of the current user session_id = None #: session id when logged in webapi_authenticate_user_nonce = None #: nonce for the getting a web session _recv_loop = None _heartbeat_loop = None def __init__(self, protocol=0): self.servers = CMServerList() if protocol == CMClient.TCP: self.connection = TCPConnection() else: raise ValueError("Only TCP is supported") self.connection.event_connected.rawlink(self._handle_disconnect) self.on(EMsg.ChannelEncryptRequest, self._handle_encrypt_request), self.on(EMsg.Multi, self._handle_multi), self.on(EMsg.ClientLogOnResponse, self._handle_logon), self.on(EMsg.ClientCMList, self._handle_cm_list), def emit(self, event, *args): if event is not None: logger.debug("Emit event: %s" % repr(event)) super(CMClient, self).emit(event, *args) def connect(self): """Initiate connection to CM. Blocks until we connect.""" logger.debug("Connect initiated.") for server_addr in self.servers: if self.connection.connect(server_addr): break logger.debug("Failed to connect. Retrying...") self.current_server_addr = server_addr self.connected = True self.emit("connected") self._recv_loop = gevent.spawn(self._recv_messages) def _handle_disconnect(self, event): if not event.is_set(): gevent.spawn(self.disconnect) def disconnect(self, reconnect=False): """Close connection""" if not self.connected: return self.connected = False self.connection.disconnect() if self._heartbeat_loop: self._heartbeat_loop.kill() self._recv_loop.kill() self._reset_attributes() if reconnect: self.emit('reconnect') gevent.spawn(self.connect) else: self.emit('disconnected') def _reset_attributes(self): del self.current_server_addr del self.connected del self.channel_secured del self.key del self.hmac_secret del self.steam_id del self.session_id del self.webapi_authenticate_user_nonce del self._recv_loop del self._heartbeat_loop def send_message(self, message): """ Sends a message :param message: a message instance :type message: :class:`steam.core.msg.Msg`, :class:`steam.core.msg.MsgProto` """ if not isinstance(message, (Msg, MsgProto)): raise ValueError("Expected Msg or MsgProto, got %s" % message) if self.steam_id: message.steamID = self.steam_id if self.session_id: message.sessionID = self.session_id if self.verbose_debug: logger.debug("Outgoing: %s\n%s" % (repr(message), str(message))) else: logger.debug("Outgoing: %s", repr(message)) data = message.serialize() if self.key: if self.hmac_secret: data = crypto.symmetric_encrypt_HMAC(data, self.key, self.hmac_secret) else: data = crypto.symmetric_encrypt(data, self.key) self.connection.put_message(data) def _recv_messages(self): for message in self.connection: if not self.connected: break if self.key: if self.hmac_secret: try: message = crypto.symmetric_decrypt_HMAC(message, self.key, self.hmac_secret) except RuntimeError as e: logger.exception(e) gevent.spawn(self.disconnect) return else: message = crypto.symmetric_decrypt(message, self.key) gevent.spawn(self._parse_message, message) gevent.idle() def _parse_message(self, message): emsg_id, = struct.unpack_from(" 0: size, = struct.unpack_from("