def get_ctx(self, allow_unknown_ca=False, req_peer_cert=True, session=None): ctx = SSL.Context("sslv23") # Set certificate and private key m2.ssl_ctx_use_x509(ctx.ctx, self.cert.x509) m2.ssl_ctx_use_rsa_privkey(ctx.ctx, self.rsakey.rsa) if not m2.ssl_ctx_check_privkey(ctx.ctx): raise CryptoError('public/private key mismatch') # Ciphers/Options ctx.set_cipher_list(CIPHER_SET) ctx.set_options(CTX_OPTIONS) # CA settings cloc = os.path.join(global_certpath, 'cacert.root.pem') if ctx.load_verify_locations(cafile=cloc) != 1: log.error("Problem loading CA certificates") raise CryptoError('CA certificates not loaded') # Verification cb = mk_verify_cb(allow_unknown_ca=allow_unknown_ca) CTX_V_FLAGS = SSL.verify_peer if req_peer_cert: CTX_V_FLAGS |= SSL.verify_fail_if_no_peer_cert ctx.set_verify(CTX_V_FLAGS, 3, cb) # Session if session: ctx.set_session_id_ctx(session) return ctx
def got_have(self, message): i = toint(message[1:]) if i >= self.torrent.numpieces: log.error("Piece index out of range") self.fatal_error() return self.download.got_have(i)
def get_ctx(self, allow_unknown_ca=False, req_peer_cert=True, session=None): ctx = SSL.Context("sslv23") # Set certificate and private key m2.ssl_ctx_use_x509(ctx.ctx, self.cert.x509) m2.ssl_ctx_use_rsa_privkey(ctx.ctx, self.rsakey.rsa) if not m2.ssl_ctx_check_privkey(ctx.ctx): raise CryptoError('public/private key mismatch') # Ciphers/Options ctx.set_cipher_list(CIPHER_SET) ctx.set_options(CTX_OPTIONS) # CA settings cloc = os.path.join(global_certpath, 'cacert.root.pem') if ctx.load_verify_locations(cafile=cloc) != 1: log.error("Problem loading CA certificates") raise CryptoError('CA certificates not loaded') # Verification cb = mk_verify_cb(allow_unknown_ca=allow_unknown_ca) CTX_V_FLAGS = SSL.verify_peer if req_peer_cert: CTX_V_FLAGS |= SSL.verify_fail_if_no_peer_cert ctx.set_verify(CTX_V_FLAGS,3,cb) # Session if session: ctx.set_session_id_ctx(session) return ctx
def got_tcode(self, message): tcreader = TCReader(self.manager.certificate) try: tcdata = tcreader.parseTC(message[1:]) except Anomos.Crypto.CryptoError, e: log.error("Decryption Error: %s" % str(e)) self.socket.close() return
def got_partial(self, message): p_remain = toint(message[1:5]) self.partial_recv += message[5:] if len(self.partial_recv) > self.neighbor.config['max_message_length']: log.error("Received message longer than max length") return if len(message[5:]) == p_remain: self.got_message(self.partial_recv) self.partial_recv = ''
class AnomosNeighborProtocol(AnomosProtocol): ## NeighborProtocol is intended to be implemented by NeighborLink ## def __init__(self): AnomosProtocol.__init__(self) self.msgmap.update({PARTIAL:self.got_partial, TCODE: self.got_tcode}) def format_message(self, stream_id, message): return tobinary(stream_id)[2:] + \ tobinary(len(message)) + message def invalid_message(self, t): log.warning("Invalid message of type %02x on %s. Closing neighbor."% \ (ord(t), self.uniq_id())) self.socket.close() def got_partial(self, message): p_remain = toint(message[1:5]) payload = message[5:] self.partial_recv += payload if len(self.partial_recv) > self.config['max_message_length']: log.error("Received message longer than max length") return if len(payload) == p_remain: self.got_message(self.partial_recv) self.partial_recv = '' def got_tcode(self, message): tcreader = TCReader(self.manager.certificate) try: tcdata = tcreader.parseTC(message[1:]) except Anomos.Crypto.CryptoError, e: log.error("Decryption Error: %s" % str(e)) self.socket.close() return sid = tcdata.sessionID if not self.manager.check_session_id(sid): #TODO: Key mismatch is pretty serious, probably want to ban the # user who sent this TCode log.error("Session id mismatch") self.socket.close() return if tcdata.type == chr(0): # Relayer type nextTC = tcdata.nextLayer nid = tcdata.neighborID self.start_relay_stream(nid, nextTC) elif tcdata.type == chr(1): # Terminal type infohash = tcdata.infohash keydata = tcdata.keydata e2e_key = Anomos.Crypto.AESKey(keydata[:32],keydata[32:]) torrent = self.manager.get_torrent(infohash) if not torrent: log.error("Requested torrent not found") self.socket.close() return self.start_endpoint_stream(torrent, e2e_key) else: log.error("Unsupported TCode Format") self.socket.close()
def start_circuit(self, tc, infohash, aeskey): """Called from Rerequester to initialize new circuits we've just gotten TCs for from the Tracker""" if self.count_streams() >= self.config['max_initiate']: log.warning("Not starting circuit -- Stream count exceeds maximum") return tcreader = TCReader(self.certificate) try: tcdata = tcreader.parseTC(tc) except Anomos.Crypto.CryptoError, e: log.error("Decryption Error: %s" % str(e)) return
def __init__(self, url, config, schedule, neighbors, amount_left, up, down, local_port, infohash, doneflag, diefunc, sfunc, certificate, sessionid): ########################## self.config = config self.schedule = schedule self.neighbors = neighbors self.amount_left = amount_left self.up = up self.down = down self.local_port = local_port self.infohash = infohash self.doneflag = doneflag self.diefunc = diefunc self.successfunc = sfunc self.certificate = certificate self.ssl_ctx = self.certificate.get_ctx(allow_unknown_ca=False) self.sessionid = sessionid ### Tracker URL ### self.https = True parsed = urlparse(url) # (<scheme>,<netloc>,<path>,<params>,<query>,<fragment>) self.url = parsed[1] self.remote_port = 5555 # Assume port 5555 by default if ":" in self.url: # <netloc> = <url>:<port> i = self.url.index(":") self.remote_port = int(self.url[i+1:]) self.url = self.url[:i] self.path = parsed[2] self.basequery = None self.failed_peers = [] self.changed_port = False self.announce_interval = 30 * 60 self.finish = False self.current_started = None self.fail_wait = None self.last_time = None self.warned = False self.proxy_url = self.config.get('tracker_proxy', None) self.proxy_username = None self.proxy_password = None if self.proxy_url: self.parse_proxy_url() if parsed[0] != 'https': log.error("You are trying to make an unencrypted connection to a tracker, and this has been disabled for security reasons. Halting.") self.https = False
def _postrequest(self, data=None, errormsg=None): self.current_started = None self.last_time = bttime() if errormsg is not None: log.warning(errormsg) self._fail() return try: # Here's where we receive/decrypt data from the tracker r = bdecode(data) check_peers(r) except BTFailure, e: if data != '': log.error('bad data from tracker - ' + str(e)) self._fail() return
def save_ui_config(defaults, section, save_options): p = SafeConfigParser() filename = os.path.join(defaults['data_dir'], 'ui_config') p.read(filename) p.remove_section(section) p.add_section(section) for name in save_options: p.set(section, name, str(defaults[name])) try: f = file(filename, 'w') p.write(f) f.close() except Exception, e: try: f.close() except: pass log.error('Could not permanently save options: '+ str(e))
def set_filesystem_encoding(encoding): global filesystem_encoding filesystem_encoding = 'ascii' if encoding == '': try: sys.getfilesystemencoding except AttributeError: log.warning("This seems to be an old Python version which does not support detecting the filesystem encoding. Assuming 'ascii'.") return encoding = sys.getfilesystemencoding() if encoding is None: log.warning("Python failed to autodetect filesystem encoding. Using 'ascii' instead.") return try: 'a1'.decode(encoding) except: log.error("Filesystem encoding '"+encoding+"' is not supported. Using 'ascii' instead.") return filesystem_encoding = encoding
def got_exception(self, e): is_external = False if isinstance(e, BTShutdown): log.error(str(e)) is_external = True elif isinstance(e, BTFailure): log.critical(str(e)) self._activity = ("download failed: " + str(e), 0) elif isinstance(e, IOError): log.critical("IO Error " + str(e)) self._activity = ("killed by IO error: " + str(e), 0) elif isinstance(e, OSError): log.critical("OS Error " + str(e)) self._activity = ("killed by OS error: " + str(e), 0) else: data = StringIO() print_exc(file=data) log.critical(data.getvalue()) self._activity = ("killed by internal exception: " + str(e), 0) try: self._close() except Exception, e: log.error("Additional error when closing down due to " "error: " + str(e))
def got_exception(self, e): is_external = False if isinstance(e, BTShutdown): log.error(str(e)) is_external = True elif isinstance(e, BTFailure): log.critical(str(e)) self._activity = ('download failed: ' + str(e), 0) elif isinstance(e, IOError): log.critical('IO Error ' + str(e)) self._activity = ('killed by IO error: ' + str(e), 0) elif isinstance(e, OSError): log.critical('OS Error ' + str(e)) self._activity = ('killed by OS error: ' + str(e), 0) else: data = StringIO() print_exc(file=data) log.critical(data.getvalue()) self._activity = ('killed by internal exception: ' + str(e), 0) try: self._close() except Exception, e: log.error('Additional error when closing down due to ' 'error: ' + str(e))
def fatal_error(self, msg=""): log.error(msg) self.close()
class NeighborManager(object): """NeighborManager keeps track of the neighbors a peer is connected to and which tracker those neighbors are on. """ def __init__(self, config, certificate, ssl_ctx, sessionid, schedule, ratelimiter): self.config = config self.certificate = certificate self.ssl_ctx = ssl_ctx self.sessionid = sessionid self.schedule = schedule self.ratelimiter = ratelimiter self.neighbors = {} self.relay_measure = Measure(self.config['max_rate_period']) self.relay_count = 0 self.incomplete = {} self.torrents = {} self.waiting_tcs = {} self.failedPeers = [] ## Got new neighbor list from the tracker ## def update_neighbor_list(self, list): freshids = dict([(i[2], (i[0], i[1])) for i in list]) #{nid : (ip, port)} # Remove neighbors not found in freshids for id in self.neighbors.keys(): if not freshids.has_key(id): self.rm_neighbor(id) # Start connections with the new neighbors for id, loc in freshids.iteritems(): if self.nid_collision(id, loc): # Already had neighbor by the given id at a different location log.warning('NID collision - x%02x' % ord(id)) # To be safe, kill connection with the neighbor we already # had with the requested ID and add ID to the failed list self.rm_neighbor(id) elif (not self.has_neighbor(id)) and (id not in self.failedPeers): self.start_connection(id, loc) ## Start a new neighbor connection ## def start_connection(self, id, loc): """ Start a new SSL connection to the peer at loc and assign them the NeighborID id @param loc: (IP, Port) @param id: The neighbor ID to assign to this connection @type loc: tuple @type id: int """ if self.config['one_connection_per_ip'] and self.has_ip(loc[0]): log.warning('Got duplicate IP address in neighbor list. ' \ 'Multiple connections to the same IP are disabled ' \ 'in your config.') return self.incomplete[id] = loc conn = P2PConnection(addr=loc, ssl_ctx=self.ssl_ctx, connect_cb=self.socket_cb, schedule=self.schedule) def socket_cb(self, sock): """ Called by P2PConnection after connect() has completed """ if sock.connected: log.info('Connected to %s' % str(sock.addr)) for id, v in self.incomplete.iteritems(): if v == sock.addr: break else: return #loc wasn't found AnomosNeighborInitializer(self, sock, id) else: #Remove nid,loc pair from incomplete torm = [] for k, v in self.incomplete.items(): if v == sock.addr: log.info('Failed to connect, discarding \\x%02x' % ord(k)) torm.append(k) for j in torm: self.rm_neighbor(j) if sock.addr == None: if self.incomplete.items() != []: log.info("Remaining incomplete peers: %d" % len(self.incomplete.items())) else: log.info("No remaining incomplete peers") else: log.info("Failed to open connection to %s\n" % str(sock.addr)) def failed_connections(self): return self.failedPeers def remove_reported_failids(self, failids): for i in failids: if i in self.failedPeers: self.failedPeers.remove(i) ## AnomosNeighborInitializer got a full handshake ## def add_neighbor(self, socket, id): log.info("Adding Neighbor: \\x%02x" % ord(id)) self.neighbors[id] = NeighborLink(self, socket, id, \ self.config, self.ratelimiter) def rm_neighbor(self, nid): if self.incomplete.has_key(nid): self.incomplete.pop(nid) if self.neighbors.has_key(nid): self.neighbors.pop(nid) if nid is not None: self.failedPeers.append(nid) #TODO: implement banning def ban(self, ip): pass def has_neighbor(self, nid): return self.neighbors.has_key(nid) or self.incomplete.has_key(nid) def nid_collision(self, nid, loc): # If the locations are the same, there's no collision if self.neighbors.has_key(nid): return self.neighbors[nid].get_loc()[0] != loc[0] elif self.incomplete.has_key(nid): return self.incomplete[nid][0] != loc[0] return False def check_session_id(self, sid): return sid == self.sessionid def has_ip(self, ip): return ip in [n.socket.addr[0] for n in self.neighbors.values()] \ or ip in [x for x,y in self.incomplete.values()] def get_ips(self): ips = [] for n in self.neighbors.values(): ips.append([n.socket.addr[0], n.socket.addr[1], n.id]) return ips def is_incomplete(self, nid): return self.incomplete.has_key(nid) def count(self, tracker=None): return len(self.neighbors) def connection_completed(self, socket, id): """Called by AnomosNeighborInitializer""" if self.incomplete.has_key(id): del self.incomplete[id] if id == NAT_CHECK_ID: log.info("NAT check ok.") return self.add_neighbor(socket, id) tasks = self.waiting_tcs.get(id) if tasks is None: return for task in tasks: #TODO: Would a minimum wait between these tasks aid anonymity? self.schedule(0, task) del self.waiting_tcs[id] def lost_neighbor(self, id): self.rm_neighbor(id) def initializer_failed(self, id): """Connection closed before finishing initialization""" self.rm_neighbor(id) def start_circuit(self, tc, infohash, aeskey): """Called from Rerequester to initialize new circuits we've just gotten TCs for from the Tracker""" if self.count_streams() >= self.config['max_initiate']: log.warning("Not starting circuit -- Stream count exceeds maximum") return tcreader = TCReader(self.certificate) try: tcdata = tcreader.parseTC(tc) except Anomos.Crypto.CryptoError, e: log.error("Decryption Error: %s" % str(e)) return nid = tcdata.neighborID sid = tcdata.sessionID torrent = self.get_torrent(infohash) nextTC = tcdata.nextLayer if sid != self.sessionid: log.error("Not starting circuit -- SessionID mismatch!") elif torrent is None: log.error("Not starting circuit -- Unknown torrent") elif nid in self.incomplete: log.info("Postponing circuit until neighbor \\x%02x completes " % ord(nid)) self.schedule_tc(nid, infohash, aeskey, nextTC) elif nid not in self.neighbors: log.error("Not starting circuit -- NID \\x%02x is not assigned" % ord(nid)) else: self.neighbors[nid].start_endpoint_stream(torrent, aeskey, data=nextTC)
if errormsg is not None: log.warning(errormsg) self._fail() return try: # Here's where we receive/decrypt data from the tracker r = bdecode(data) check_peers(r) except BTFailure, e: if data != '': log.error('bad data from tracker - ' + str(e)) self._fail() return if r.has_key('failure reason'): if self.neighbors.count() > 0: log.error('rejected by tracker - ' + r['failure reason']) else: log.critical("Aborting the torrent as it was " \ "rejected by the tracker while not connected to any peers. " \ "Message from the tracker:\n" + r['failure reason']) self._fail() return elif self.neighbors is None: # Torrent may have been closed before receiving a response # from the tracker. self._fail() return else: self.fail_wait = None if r.has_key('warning message'): log.error('warning from tracker - ' + r['warning message'])
def reread_config(self): try: newvalues = configfile.get_config(self.config, 'anondownloadcurses') except Exception, e: log.error('Error reading config: ' + str(e)) return
def got_exception(self, e): log.error(e)