def handle_rpc_result(self, msg_id, sequence, reader, request): Log.d('Handling RPC result, request is%s None', ' not' if request else '') reader.read_int(signed=False) # code request_id = reader.read_long(signed=False) inner_code = reader.read_int(signed=False) if request and request_id == request.msg_id: request.confirm_received = True if inner_code == 0x2144ca19: # RPC Error error = RPCError(code=reader.read_int(), message=reader.tgread_string()) Log.w('Read RPC error: %s', str(error)) if error.must_resend: if not request: raise ValueError( 'The previously sent request must be resent. ' 'However, no request was previously sent (called from updates thread).' ) request.confirm_received = False if error.message.startswith('FLOOD_WAIT_'): self.updates_thread_sleep = error.additional_data print('Should wait {}s. Sleeping until then.'.format( error.additional_data)) sleep(error.additional_data) elif '_MIGRATE_' in error.message: raise InvalidDCError(error.additional_data) else: raise error else: if not request: raise ValueError( 'Cannot receive a request from inside an RPC result from the updates thread.' ) Log.d('Reading request response') if inner_code == 0x3072cfa1: # GZip packed unpacked_data = gzip.decompress(reader.tgread_bytes()) with BinaryReader(unpacked_data) as compressed_reader: request.on_response(compressed_reader) else: reader.seek(-4) request.on_response(reader)
def enumerate_backups_entities(): """Enumerates the entities of all the available backups""" if isdir(Backuper.backups_dir): # Look for subdirectories for directory in listdir(Backuper.backups_dir): entity_file = path.join(Backuper.backups_dir, directory, 'entity.tlo') # Ensure the entity.pickle file exists if isfile(entity_file): # Load and yield it with open(entity_file, 'rb') as file: with BinaryReader(stream=file) as reader: yield reader.tgread_object()
def receive(self, request, timeout=timedelta(seconds=5)): """Receives the specified MTProtoRequest ("fills in it" the received data). This also restores the updates thread. An optional timeout can be specified to cancel the operation if no data has been read after its time delta""" with self.lock: # Don't stop trying to receive until we get the request we wanted while not request.confirm_received: seq, body = self.transport.receive(timeout) message, remote_msg_id, remote_sequence = self.decode_msg(body) with BinaryReader(message) as reader: self.process_msg(remote_msg_id, remote_sequence, reader, request) # We can now set the flag to False thus resuming the updates thread self.waiting_receive = False
def updates_thread_method(self): """This method will run until specified and listen for incoming updates""" # Set a reasonable timeout when checking for updates timeout = timedelta(minutes=1) while not self.updates_thread_stopping: # Only try to receive updates if we're not waiting to receive a request if not self.waiting_receive: with self.lock: try: now = time() # if ping_interval seconds passed since last ping # (or startup) - send new one if now >= self.ping_time_last + self.ping_interval: self.ping_time_last = now self.send_ping() # if sending ping - we doesn't processing any other updates continue self.updates_thread_receiving = True seq, body = self.transport.receive(timeout) message, remote_msg_id, remote_sequence = self.decode_msg( body) with BinaryReader(message) as reader: self.process_msg(remote_msg_id, remote_sequence, reader) except (ReadCancelledError, TimeoutError): pass self.updates_thread_receiving = False # If we are here, it is because the read was cancelled # Sleep a bit just to give enough time for the other thread # to acquire the lock. No need to sleep if we're not running anymore if not self.updates_thread_stopping: sleep(0.1)
def updates_thread_method(self): """This method will run until specified and listen for incoming updates""" # Set a reasonable timeout when checking for updates timeout = timedelta(minutes=1) while self.updates_thread_running: # Always sleep a bit before each iteration to relax the CPU, # since it's possible to early 'continue' the loop to reach # the next iteration, but we still should to sleep. if self.updates_thread_sleep: sleep(self.updates_thread_sleep) self.updates_thread_sleep = None else: # Longer sleep if we're not expecting updates (only pings) sleep(0.1 if self.on_update_handlers else 1) # Only try to receive updates if we're not waiting to receive a request if not self.waiting_receive: with self.lock: Log.d('Updates thread acquired the lock') try: now = time() # If ping_interval seconds passed since last ping, send a new one if now >= self.ping_time_last + self.ping_interval: self.ping_time_last = now self.send_ping() Log.d('Ping sent from the updates thread') # Exit the loop if we're not expecting to receive any updates if not self.on_update_handlers: Log.d('No updates handlers found, continuing') continue self.updates_thread_receiving = True Log.d( 'Trying to receive updates from the updates thread' ) seq, body = self.transport.receive(timeout) message, remote_msg_id, remote_sequence = self.decode_msg( body) Log.i('Received update from the updates thread') with BinaryReader(message) as reader: self.process_msg(remote_msg_id, remote_sequence, reader) except TimeoutError: Log.d('Receiving updates timed out') # TODO Workaround for issue #50 r = GetStateRequest() try: Log.d( 'Sending GetStateRequest (workaround for issue #50)' ) self.send(r) self.receive(r) except TimeoutError: Log.w( 'Timed out inside a timeout, trying to reconnect...' ) self.reconnect() self.send(r) self.receive(r) except ReadCancelledError: Log.i('Receiving updates cancelled') except OSError: Log.w('OSError on updates thread, %s logging out', 'was' if self.logging_out else 'was not') if self.logging_out: # This error is okay when logging out, means we got disconnected # TODO Not sure why this happens because we call disconnect()… self.set_updates_thread(running=False) else: raise Log.d('Updates thread released the lock') self.updates_thread_receiving = False
def do_authentication(transport): """Executes the authentication process with the Telegram servers. If no error is rose, returns both the authorization key and the time offset""" sender = MtProtoPlainSender(transport) # Step 1 sending: PQ Request nonce = os.urandom(16) with BinaryWriter() as writer: writer.write_int(0x60469778, signed=False) # Constructor number writer.write(nonce) sender.send(writer.get_bytes()) # Step 1 response: PQ Request pq, pq_bytes, server_nonce, fingerprints = None, None, None, [] with BinaryReader(sender.receive()) as reader: response_code = reader.read_int(signed=False) if response_code != 0x05162463: raise AssertionError('Invalid response code: {}'.format( hex(response_code))) nonce_from_server = reader.read(16) if nonce_from_server != nonce: raise AssertionError('Invalid nonce from server') server_nonce = reader.read(16) pq_bytes = reader.tgread_bytes() pq = get_int(pq_bytes) vector_id = reader.read_int() if vector_id != 0x1cb5c415: raise AssertionError('Invalid vector constructor ID: {}'.format( hex(response_code))) fingerprints = [] fingerprint_count = reader.read_int() for _ in range(fingerprint_count): fingerprints.append(reader.read(8)) # Step 2 sending: DH Exchange new_nonce = os.urandom(32) p, q = Factorizator.factorize(pq) with BinaryWriter() as pq_inner_data_writer: pq_inner_data_writer.write_int( 0x83c95aec, signed=False) # PQ Inner Data pq_inner_data_writer.tgwrite_bytes(get_byte_array(pq, signed=False)) pq_inner_data_writer.tgwrite_bytes( get_byte_array( min(p, q), signed=False)) pq_inner_data_writer.tgwrite_bytes( get_byte_array( max(p, q), signed=False)) pq_inner_data_writer.write(nonce) pq_inner_data_writer.write(server_nonce) pq_inner_data_writer.write(new_nonce) cipher_text, target_fingerprint = None, None for fingerprint in fingerprints: cipher_text = RSA.encrypt( get_fingerprint_text(fingerprint), pq_inner_data_writer.get_bytes()) if cipher_text is not None: target_fingerprint = fingerprint break if cipher_text is None: raise AssertionError( 'Could not find a valid key for fingerprints: {}' .format(', '.join([get_fingerprint_text(f) for f in fingerprints]))) with BinaryWriter() as req_dh_params_writer: req_dh_params_writer.write_int( 0xd712e4be, signed=False) # Req DH Params req_dh_params_writer.write(nonce) req_dh_params_writer.write(server_nonce) req_dh_params_writer.tgwrite_bytes( get_byte_array( min(p, q), signed=False)) req_dh_params_writer.tgwrite_bytes( get_byte_array( max(p, q), signed=False)) req_dh_params_writer.write(target_fingerprint) req_dh_params_writer.tgwrite_bytes(cipher_text) req_dh_params_bytes = req_dh_params_writer.get_bytes() sender.send(req_dh_params_bytes) # Step 2 response: DH Exchange encrypted_answer = None with BinaryReader(sender.receive()) as reader: response_code = reader.read_int(signed=False) if response_code == 0x79cb045d: raise AssertionError('Server DH params fail: TODO') if response_code != 0xd0e8075c: raise AssertionError('Invalid response code: {}'.format( hex(response_code))) nonce_from_server = reader.read(16) if nonce_from_server != nonce: raise NotImplementedError('Invalid nonce from server') server_nonce_from_server = reader.read(16) if server_nonce_from_server != server_nonce: raise NotImplementedError('Invalid server nonce from server') encrypted_answer = reader.tgread_bytes() # Step 3 sending: Complete DH Exchange key, iv = utils.generate_key_data_from_nonces(server_nonce, new_nonce) plain_text_answer = AES.decrypt_ige(encrypted_answer, key, iv) g, dh_prime, ga, time_offset = None, None, None, None with BinaryReader(plain_text_answer) as dh_inner_data_reader: dh_inner_data_reader.read(20) # hashsum code = dh_inner_data_reader.read_int(signed=False) if code != 0xb5890dba: raise AssertionError('Invalid DH Inner Data code: {}'.format(code)) nonce_from_server1 = dh_inner_data_reader.read(16) if nonce_from_server1 != nonce: raise AssertionError('Invalid nonce in encrypted answer') server_nonce_from_server1 = dh_inner_data_reader.read(16) if server_nonce_from_server1 != server_nonce: raise AssertionError('Invalid server nonce in encrypted answer') g = dh_inner_data_reader.read_int() dh_prime = get_int(dh_inner_data_reader.tgread_bytes(), signed=False) ga = get_int(dh_inner_data_reader.tgread_bytes(), signed=False) server_time = dh_inner_data_reader.read_int() time_offset = server_time - int(time.time()) b = get_int(os.urandom(2048), signed=False) gb = pow(g, b, dh_prime) gab = pow(ga, b, dh_prime) # Prepare client DH Inner Data with BinaryWriter() as client_dh_inner_data_writer: client_dh_inner_data_writer.write_int( 0x6643b654, signed=False) # Client DH Inner Data client_dh_inner_data_writer.write(nonce) client_dh_inner_data_writer.write(server_nonce) client_dh_inner_data_writer.write_long(0) # TODO retry_id client_dh_inner_data_writer.tgwrite_bytes( get_byte_array( gb, signed=False)) with BinaryWriter() as client_dh_inner_data_with_hash_writer: client_dh_inner_data_with_hash_writer.write( utils.sha1(client_dh_inner_data_writer.get_bytes())) client_dh_inner_data_with_hash_writer.write( client_dh_inner_data_writer.get_bytes()) client_dh_inner_data_bytes = client_dh_inner_data_with_hash_writer.get_bytes( ) # Encryption client_dh_inner_data_encrypted_bytes = AES.encrypt_ige( client_dh_inner_data_bytes, key, iv) # Prepare Set client DH params with BinaryWriter() as set_client_dh_params_writer: set_client_dh_params_writer.write_int(0xf5045f1f, signed=False) set_client_dh_params_writer.write(nonce) set_client_dh_params_writer.write(server_nonce) set_client_dh_params_writer.tgwrite_bytes( client_dh_inner_data_encrypted_bytes) set_client_dh_params_bytes = set_client_dh_params_writer.get_bytes() sender.send(set_client_dh_params_bytes) # Step 3 response: Complete DH Exchange with BinaryReader(sender.receive()) as reader: code = reader.read_int(signed=False) if code == 0x3bcbf734: # DH Gen OK nonce_from_server = reader.read(16) if nonce_from_server != nonce: raise NotImplementedError('Invalid nonce from server') server_nonce_from_server = reader.read(16) if server_nonce_from_server != server_nonce: raise NotImplementedError('Invalid server nonce from server') new_nonce_hash1 = reader.read(16) auth_key = AuthKey(get_byte_array(gab, signed=False)) new_nonce_hash_calculated = auth_key.calc_new_nonce_hash(new_nonce, 1) if new_nonce_hash1 != new_nonce_hash_calculated: raise AssertionError('Invalid new nonce hash') return auth_key, time_offset elif code == 0x46dc1fb9: # DH Gen Retry raise NotImplementedError('dh_gen_retry') elif code == 0xa69dae02: # DH Gen Fail raise NotImplementedError('dh_gen_fail') else: raise AssertionError('DH Gen unknown: {}'.format(hex(code)))