def parseMessageHeader(cls, headerData): """Parses a message header. Returns a tuple of messagetype, messageflags, sequencenumber, datalength, datahmac.""" if not headerData or len(headerData)!=cls.HEADERSIZE: raise errors.ProtocolError("header data size mismatch") tag, ver, msgType, flags, seq, dataLen, headerchecksum, datahmac = struct.unpack(cls.headerFmt, headerData) if tag!=cls.pyro_tag or ver!=constants.PROTOCOL_VERSION: raise errors.ProtocolError("invalid data or unsupported protocol version") if headerchecksum!=(msgType+ver+dataLen+flags+seq+MessageFactory.MAGIC)&0xffff: raise errors.ProtocolError("header checksum mismatch") return msgType, flags, seq, dataLen, datahmac
def from_header(cls, headerData): """Parses a message header. Does not yet process the annotations chunks and message data.""" if not headerData or len(headerData)!=cls.header_size: raise errors.ProtocolError("header data size mismatch") tag, ver, msg_type, flags, seq, data_size, serializer_id, annotations_size, _, checksum = struct.unpack(cls.header_format, headerData) if tag!=b"PYRO" or ver!=constants.PROTOCOL_VERSION: raise errors.ProtocolError("invalid data or unsupported protocol version") if checksum!=(msg_type+ver+data_size+annotations_size+flags+serializer_id+seq+cls.checksum_magic)&0xffff: raise errors.ProtocolError("header checksum mismatch") msg = Message(msg_type, b"", serializer_id, flags, seq) msg.data_size = data_size msg.annotations_size = annotations_size return msg
def __init__(self, msgType, databytes, serializer_id, flags, seq, annotations=None, hmac_key=None): self.type = msgType self.flags = flags self.seq = seq self.data = databytes self.data_size = len(self.data) self.serializer_id = serializer_id self.annotations = annotations or {} self.hmac_key = hmac_key if self.hmac_key: self.annotations["HMAC"] = self.hmac() self.annotations_size = sum( [6 + len(v) for v in self.annotations.values()]) if 0 < Pyro4.config.MAX_MESSAGE_SIZE < (self.data_size + self.annotations_size): raise errors.ProtocolError( "max message size exceeded (%d where max=%d)" % (self.data_size + self.annotations_size, Pyro4.config.MAX_MESSAGE_SIZE))
def recv(cls, connection, requiredMsgTypes=None, hmac_key=None): """ Receives a pyro message from a given connection. Accepts the given message types (None=any, or pass a sequence). Also reads annotation chunks and the actual payload data. Validates a HMAC chunk if present. """ msg = cls.from_header(connection.recv(cls.header_size)) msg.hmac_key = hmac_key if 0 < Pyro4.config.MAX_MESSAGE_SIZE < (msg.data_size + msg.annotations_size): errorMsg = "max message size exceeded (%d where max=%d)" % ( msg.data_size + msg.annotations_size, Pyro4.config.MAX_MESSAGE_SIZE) log.error("connection " + str(connection) + ": " + errorMsg) connection.close( ) # close the socket because at this point we can't return the correct sequence number for returning an error message raise errors.ProtocolError(errorMsg) if requiredMsgTypes and msg.type not in requiredMsgTypes: err = "invalid msg type %d received" % msg.type log.error(err) raise errors.ProtocolError(err) if msg.annotations_size: # read annotation chunks annotations_data = connection.recv(msg.annotations_size) msg.annotations = {} i = 0 while i < msg.annotations_size: anno, length = struct.unpack("!4sH", annotations_data[i:i + 6]) if sys.version_info >= (3, 0): anno = anno.decode("ASCII") msg.annotations[anno] = annotations_data[i + 6:i + 6 + length] i += 6 + length # read data msg.data = connection.recv(msg.data_size) if "HMAC" in msg.annotations and hmac_key: if msg.annotations["HMAC"] != msg.hmac(): raise errors.SecurityError("message hmac mismatch") elif ("HMAC" in msg.annotations) != bool(hmac_key): # Not allowed: message contains hmac but hmac_key is not set, or vice versa. err = "hmac key config not symmetric" log.warning(err) raise errors.SecurityError(err) return msg
def __pyroCreateConnection(self, replaceUri=False): """ Connects this proxy to the remote Pyro daemon. Does connection handshake. Returns true if a new connection was made, false if an existing one was already present. """ with self.__pyroConnLock: if self._pyroConnection is not None: return False # already connected from Pyro4.naming import resolve # don't import this globally because of cyclic dependancy uri=resolve(self._pyroUri) # socket connection (normal or Unix domain socket) conn=None log.debug("connecting to %s", uri) connect_location=uri.sockname if uri.sockname else (uri.host, uri.port) with self.__pyroLock: try: if self._pyroConnection is not None: return False # already connected sock=socketutil.createSocket(connect=connect_location, reuseaddr=Pyro4.config.SOCK_REUSE, timeout=self.__pyroTimeout) conn=socketutil.SocketConnection(sock, uri.object) # Do handshake. For now, no need to send anything. (message type CONNECT is not yet used) msg = Message.recv(conn, None) # any trailing data (dataLen>0) is an error message, if any except Exception: x=sys.exc_info()[1] if conn: conn.close() err="cannot connect: %s" % x log.error(err) if isinstance(x, errors.CommunicationError): raise else: ce = errors.CommunicationError(err) ce.__cause__ = x raise ce else: if msg.type==Pyro4.message.MSG_CONNECTFAIL: error="connection rejected" if msg.data: serializer = util.get_serializer_by_id(msg.serializer_id) data = serializer.deserializeData(msg.data, compressed=msg.flags & Pyro4.message.FLAGS_COMPRESSED) error += ", reason: " + data conn.close() log.error(error) raise errors.CommunicationError(error) elif msg.type==Pyro4.message.MSG_CONNECTOK: self._pyroConnection=conn if replaceUri: self._pyroUri=uri log.debug("connected to %s", self._pyroUri) return True else: conn.close() err="connect: invalid msg type %d received" % msg.type log.error(err) raise errors.ProtocolError(err)
def __pyroCreateConnection(self, replaceUri=False): """ Connects this proxy to the remote Pyro daemon. Does connection handshake. Returns true if a new connection was made, false if an existing one was already present. """ with self.__pyroConnLock: if self._pyroConnection is not None: return False # already connected from Pyro4.naming import resolve # don't import this globally because of cyclic dependancy uri=resolve(self._pyroUri) # socket connection (normal or Unix domain socket) conn=None log.debug("connecting to %s", uri) connect_location=uri.sockname if uri.sockname else (uri.host, uri.port) with self.__pyroLock: try: if self._pyroConnection is not None: return False # already connected sock=socketutil.createSocket(connect=connect_location, reuseaddr=Pyro4.config.SOCK_REUSE, timeout=self.__pyroTimeout) conn=socketutil.SocketConnection(sock, uri.object) # Do handshake. For now, no need to send anything. msgType, flags, seq, data = MessageFactory.getMessage(conn, None) # any trailing data (dataLen>0) is an error message, if any except Exception: x=sys.exc_info()[1] if conn: conn.close() err="cannot connect: %s" % x log.error(err) if isinstance(x, errors.CommunicationError): raise else: raise errors.CommunicationError(err) else: if msgType==MessageFactory.MSG_CONNECTFAIL: error="connection rejected" if data: if sys.version_info>=(3,0): data=str(data,"utf-8") error+=", reason: "+data conn.close() log.error(error) raise errors.CommunicationError(error) elif msgType==MessageFactory.MSG_CONNECTOK: self._pyroConnection=conn if replaceUri: log.debug("replacing uri with bound one") self._pyroUri=uri log.debug("connected to %s", self._pyroUri) return True else: conn.close() err="connect: invalid msg type %d received" % msgType log.error(err) raise errors.ProtocolError(err)
def __annotations_bytes(self): if self.annotations: a = [] for k, v in self.annotations.items(): if len(k) != 4: raise errors.ProtocolError("annotation key must be of length 4") if sys.version_info >= (3, 0): k = k.encode("ASCII") a.append(struct.pack("!4sH", k, len(v))) a.append(v) return b"".join(a) return b""
def createMessage(cls, msgType, databytes, flags, seq): """creates a message containing a header followed by the given databytes""" databytes=databytes or cls.empty_bytes if 0 < Pyro4.config.MAX_MESSAGE_SIZE < len(databytes): raise errors.ProtocolError("max message size exceeded (%d where max=%d)" % (len(databytes), Pyro4.config.MAX_MESSAGE_SIZE)) if Pyro4.config.HMAC_KEY: flags|=MessageFactory.FLAGS_HMAC bodyhmac=hmac.new(Pyro4.config.HMAC_KEY, databytes, digestmod=hashlib.sha1).digest() else: bodyhmac=MessageFactory.empty_hmac headerchecksum=(msgType+constants.PROTOCOL_VERSION+len(databytes)+flags+seq+MessageFactory.MAGIC)&0xffff msg=struct.pack(cls.headerFmt, cls.pyro_tag, constants.PROTOCOL_VERSION, msgType, flags, seq, len(databytes), headerchecksum, bodyhmac) return msg+databytes
def getMessage(cls, connection, requiredMsgType): headerdata = connection.recv(cls.HEADERSIZE) msgType, flags, seq, datalen, datahmac = cls.parseMessageHeader(headerdata) if 0 < Pyro4.config.MAX_MESSAGE_SIZE < datalen: errorMsg = "max message size exceeded (%d where max=%d)" % (datalen, Pyro4.config.MAX_MESSAGE_SIZE) log.error("connection "+str(connection)+": "+errorMsg) connection.close() # close the socket because at this point we can't return the correct sequence number for returning an error message raise errors.ProtocolError(errorMsg) if requiredMsgType is not None and msgType != requiredMsgType: err="invalid msg type %d received" % msgType log.error(err) raise errors.ProtocolError(err) databytes=connection.recv(datalen) local_hmac_set=Pyro4.config.HMAC_KEY is not None and len(Pyro4.config.HMAC_KEY) > 0 if flags&MessageFactory.FLAGS_HMAC and local_hmac_set: if datahmac != hmac.new(Pyro4.config.HMAC_KEY, databytes, digestmod=hashlib.sha1).digest(): raise errors.SecurityError("message hmac mismatch") elif flags&MessageFactory.FLAGS_HMAC != local_hmac_set: # Message contains hmac and local HMAC_KEY not set, or vice versa. This is not allowed. err="hmac key config not symmetric" log.warn(err) raise errors.SecurityError(err) return msgType, flags, seq, databytes
def _pyroInvoke(self, methodname, vargs, kwargs, flags=0): """perform the remote method call communication""" if self._pyroConnection is None: # rebind here, don't do it from inside the invoke because deadlock will occur self.__pyroCreateConnection() serializer = util.get_serializer(Pyro4.config.SERIALIZER) data, compressed = serializer.serializeCall( self._pyroConnection.objectId, methodname, vargs, kwargs, compress=Pyro4.config.COMPRESSION) if compressed: flags |= Pyro4.message.FLAGS_COMPRESSED if methodname in self._pyroOneway: flags |= Pyro4.message.FLAGS_ONEWAY with self.__pyroLock: self._pyroSeq=(self._pyroSeq+1)&0xffff if Pyro4.config.LOGWIRE: log.debug("proxy wiredata sending: msgtype=%d flags=0x%x ser=%d seq=%d data=%r" % (Pyro4.message.MSG_INVOKE, flags, serializer.serializer_id, self._pyroSeq, data)) msg = Message(Pyro4.message.MSG_INVOKE, data, serializer.serializer_id, flags, self._pyroSeq) try: self._pyroConnection.send(msg.to_bytes()) del msg # invite GC to collect the object, don't wait for out-of-scope if flags & Pyro4.message.FLAGS_ONEWAY: return None # oneway call, no response data else: msg = Message.recv(self._pyroConnection, [Pyro4.message.MSG_RESULT]) if Pyro4.config.LOGWIRE: log.debug("proxy wiredata received: msgtype=%d flags=0x%x ser=%d seq=%d data=%r" % (msg.type, msg.flags, msg.serializer_id, msg.seq, msg.data)) self.__pyroCheckSequence(msg.seq) if msg.serializer_id != serializer.serializer_id: error = "invalid serializer in response: %d" % msg.serializer_id log.error(error) raise errors.ProtocolError(error) data = serializer.deserializeData(msg.data, compressed=msg.flags & Pyro4.message.FLAGS_COMPRESSED) if msg.flags & Pyro4.message.FLAGS_EXCEPTION: if sys.platform=="cli": util.fixIronPythonExceptionForPickle(data, False) raise data else: return data except (errors.CommunicationError, KeyboardInterrupt): # Communication error during read. To avoid corrupt transfers, we close the connection. # Otherwise we might receive the previous reply as a result of a new methodcall! # Special case for keyboardinterrupt: people pressing ^C to abort the client # may be catching the keyboardinterrupt in their code. We should probably be on the # safe side and release the proxy connection in this case too, because they might # be reusing the proxy object after catching the exception... self._pyroRelease() raise
def handleRequest(self, conn): """ Handle incoming Pyro request. Catches any exception that may occur and wraps it in a reply to the calling side, as to not make this server side loop terminate due to exceptions caused by remote invocations. """ request_flags = 0 request_seq = 0 request_serializer_id = util.MarshalSerializer.serializer_id wasBatched = False isCallback = False try: msg = Message.recv( conn, [Pyro4.message.MSG_INVOKE, Pyro4.message.MSG_PING]) request_flags = msg.flags request_seq = msg.seq request_serializer_id = msg.serializer_id if Pyro4.config.LOGWIRE: log.debug( "daemon wiredata received: msgtype=%d flags=0x%x ser=%d seq=%d data=%r" % (msg.type, msg.flags, msg.serializer_id, msg.seq, msg.data)) if msg.type == Pyro4.message.MSG_PING: # return same seq, but ignore any data (it's a ping, not an echo). Nothing is deserialized. msg = Message(Pyro4.message.MSG_PING, b"pong", msg.serializer_id, 0, msg.seq) if Pyro4.config.LOGWIRE: log.debug( "daemon wiredata sending: msgtype=%d flags=0x%x ser=%d seq=%d data=%r" % (msg.type, msg.flags, msg.serializer_id, msg.seq, msg.data)) conn.send(msg.to_bytes()) return if msg.serializer_id not in self.__serializer_ids: raise errors.ProtocolError( "message used serializer that is not accepted: %d" % msg.serializer_id) serializer = util.get_serializer_by_id(msg.serializer_id) objId, method, vargs, kwargs = serializer.deserializeCall( msg.data, compressed=msg.flags & Pyro4.message.FLAGS_COMPRESSED) del msg # invite GC to collect the object, don't wait for out-of-scope obj = self.objectsById.get(objId) if obj is not None: if kwargs and sys.version_info < (2, 6, 5) and os.name != "java": # Python before 2.6.5 doesn't accept unicode keyword arguments kwargs = dict((str(k), kwargs[k]) for k in kwargs) if request_flags & Pyro4.message.FLAGS_BATCH: # batched method calls, loop over them all and collect all results data = [] for method, vargs, kwargs in vargs: method = util.resolveDottedAttribute( obj, method, Pyro4.config.DOTTEDNAMES) try: result = method( *vargs, **kwargs ) # this is the actual method call to the Pyro object except Exception: xt, xv = sys.exc_info()[0:2] log.debug( "Exception occurred while handling batched request: %s", xv) xv._pyroTraceback = util.formatTraceback( detailed=Pyro4.config.DETAILED_TRACEBACK) if sys.platform == "cli": util.fixIronPythonExceptionForPickle( xv, True) # piggyback attributes data.append(futures._ExceptionWrapper(xv)) break # stop processing the rest of the batch else: data.append(result) wasBatched = True else: # normal single method call method = util.resolveDottedAttribute( obj, method, Pyro4.config.DOTTEDNAMES) if request_flags & Pyro4.message.FLAGS_ONEWAY and Pyro4.config.ONEWAY_THREADED: # oneway call to be run inside its own thread thread = threadutil.Thread(target=method, args=vargs, kwargs=kwargs) thread.setDaemon(True) thread.start() else: isCallback = getattr(method, "_pyroCallback", False) data = method( *vargs, **kwargs ) # this is the actual method call to the Pyro object else: log.debug("unknown object requested: %s", objId) raise errors.DaemonError("unknown object") if request_flags & Pyro4.message.FLAGS_ONEWAY: return # oneway call, don't send a response else: data, compressed = serializer.serializeData( data, compress=Pyro4.config.COMPRESSION) response_flags = 0 if compressed: response_flags |= Pyro4.message.FLAGS_COMPRESSED if wasBatched: response_flags |= Pyro4.message.FLAGS_BATCH if Pyro4.config.LOGWIRE: log.debug( "daemon wiredata sending: msgtype=%d flags=0x%x ser=%d seq=%d data=%r" % (Pyro4.message.MSG_RESULT, response_flags, serializer.serializer_id, request_seq, data)) msg = Message(Pyro4.message.MSG_RESULT, data, serializer.serializer_id, response_flags, request_seq) conn.send(msg.to_bytes()) except Exception: xt, xv = sys.exc_info()[0:2] if xt is not errors.ConnectionClosedError: log.debug("Exception occurred while handling request: %r", xv) if not request_flags & Pyro4.message.FLAGS_ONEWAY: # only return the error to the client if it wasn't a oneway call tblines = util.formatTraceback( detailed=Pyro4.config.DETAILED_TRACEBACK) self._sendExceptionResponse(conn, request_seq, request_serializer_id, xv, tblines) if isCallback or isinstance( xv, (errors.CommunicationError, errors.SecurityError)): raise # re-raise if flagged as callback, communication or security error.
def __pyroCheckSequence(self, seq): if seq != self._pyroSeq: err = "invoke: reply sequence out of sync, got %d expected %d" % ( seq, self._pyroSeq) log.error(err) raise errors.ProtocolError(err)