def _check_signature(self, torrentfile, signature): """Check the torrent file's signature using the public key.""" public_key_file = open(os.path.join(doc_root, 'public.key'), 'rb') public_key = pickle.load(public_key_file) public_key_file.close() h = sha(torrentfile).digest() return public_key.verify(h, signature)
def _compute_allowed_fast_list(infohash, ip, num_fast, num_pieces): # if ipv4 then (for now assume IPv4) iplist = [int(x) for x in ip.split(".")] # classful heuristic. iplist = [chr(iplist[0]),chr(iplist[1]),chr(iplist[2]),chr(0)] h = "".join(iplist) h = "".join([h,infohash]) fastlist = [] assert num_pieces < 2**32 if num_pieces <= num_fast: return range(num_pieces) # <---- this would be bizarre while True: h = sha(h).digest() # rehash hash to generate new random string. for i in xrange(5): j = i*4 #y = [ord(x) for x in h[j:j+4]] #z = (y[0] << 24) + (y[1]<<16) + (y[2]<<8) + y[3] z = struct.unpack("!L", h[j:j+4])[0] index = int(z % num_pieces) if index not in fastlist: fastlist.append(index) if len(fastlist) >= num_fast: return fastlist
def make_meta_file_dht(path, nodes, piece_len_exp, flag=Event(), progress=dummy, title=None, comment=None, target=None, data_dir=None): # if nodes is empty, then get them out of the routing table in data_dir # else, expect nodes to be a string of comma seperated <ip>:<port> pairs # this has a lot of duplicated code from make_meta_file piece_length = 2 ** piece_len_exp a, b = os.path.split(path) if not target: if b == '': f = a + '.torrent' else: f = os.path.join(a, b + '.torrent') else: f = target info = makeinfo(path, piece_length, flag, progress) if flag.isSet(): return check_info(info) info_hash = sha(bencode(info)).digest() if not nodes: x = open(os.path.join(data_dir, 'routing_table'), 'rb') d = bdecode(x.read()) x.close() t = KTable(Node().initWithDict({'id':d['id'], 'host':'127.0.0.1','port': 0})) for n in d['rt']: t.insertNode(Node().initWithDict(n)) nodes = [(node.host, node.port) for node in t.findNodes(info_hash) if node.host != '127.0.0.1'] else: nodes = [(a[0], int(a[1])) for a in [node.strip().split(":") for node in nodes.split(",")]] data = {'nodes': nodes, 'creation date': int(time())} h = file(f, 'wb') data['info'] = info if title: data['title'] = title if comment: data['comment'] = comment h.write(bencode(data)) h.close()
raise BTFailure(_('Could not convert file/directory name "%s" to ' 'Unicode (%s). Either the assumed filesystem ' 'encoding "%s" is wrong or the filename contains ' 'illegal bytes.') % (name, unicode(e.args[0]), get_filesystem_encoding())) if u.translate(noncharacter_translate) != u: raise BTFailure(_('File/directory name "%s" contains reserved ' 'unicode values that do not correspond to ' 'characters.') % name) return u.encode('utf-8') path = os.path.abspath(path) if os.path.isdir(path): subs = subfiles(path) subs.sort() pieces = [] sh = sha() done = 0 fs = [] totalsize = 0.0 totalhashed = 0 for p, f in subs: totalsize += os.path.getsize(f) for p, f in subs: pos = 0 size = os.path.getsize(f) p2 = [to_utf8(name) for name in p] fs.append({'length': size, 'path': p2}) h = file(f, 'rb') while pos < size: a = min(size - pos, piece_length - done)
def __init__(self, metainfo): """metainfo is a dict. When read from a metainfo (i.e., .torrent file), the file must first be bdecoded before being passed to ConvertedMetainfo.""" self.bad_torrent_wrongfield = False self.bad_torrent_unsolvable = False self.bad_torrent_noncharacter = False self.bad_conversion = False self.bad_windows = False self.bad_path = False self.reported_errors = False self.is_batch = False self.orig_files = None self.files_fs = None self.total_bytes = 0 self.sizes = [] self.comment = None self.title = None # descriptive title text for whole torrent self.creation_date = None self.metainfo = metainfo self.encoding = None self.caches = None btformats.check_message(metainfo, check_paths=False) info = metainfo['info'] self.is_private = info.has_key("private") and info['private'] if 'encoding' in metainfo: self.encoding = metainfo['encoding'] elif 'codepage' in metainfo: self.encoding = 'cp%s' % metainfo['codepage'] if self.encoding is not None: try: for s in u'this is a test', u'these should also work in any encoding: 0123456789\0': assert s.encode(self.encoding).decode(self.encoding) == s except: self.encoding = 'iso-8859-1' self.bad_torrent_unsolvable = True if info.has_key('length'): self.total_bytes = info['length'] self.sizes.append(self.total_bytes) else: self.is_batch = True r = [] self.orig_files = [] self.sizes = [] i = 0 for f in info['files']: l = f['length'] self.total_bytes += l self.sizes.append(l) path = self._get_attr(f, 'path') if len(path[-1]) == 0: if l > 0: raise BTFailure(_("Bad file path component: ")+x) # BitComet makes .torrent files with directories # listed along with the files, which we don't support # yet, in part because some idiot interpreted this as # a bug in BitComet rather than a feature. path.pop(-1) for x in path: if not btformats.allowed_path_re.match(x): raise BTFailure(_("Bad file path component: ")+x) self.orig_files.append('/'.join(path)) k = [] for u in path: tf2 = self._to_fs_2(u) k.append((tf2, u)) r.append((k,i)) i += 1 # If two or more file/subdirectory names in the same directory # would map to the same name after encoding conversions + Windows # workarounds, change them. Files are changed as # 'a.b.c'->'a.b.0.c', 'a.b.1.c' etc, directories or files without # '.' as 'a'->'a.0', 'a.1' etc. If one of the multiple original # names was a "clean" conversion, that one is always unchanged # and the rest are adjusted. r.sort() self.files_fs = [None] * len(r) prev = [None] res = [] stack = [{}] for x in r: j = 0 x, i = x while x[j] == prev[j]: j += 1 del res[j:] del stack[j+1:] name = x[j][0][1] if name in stack[-1]: for name in generate_names(x[j][1], j != len(x) - 1): name = self._to_fs(name) if name not in stack[-1]: break stack[-1][name] = None res.append(name) for j in xrange(j + 1, len(x)): name = x[j][0][1] stack.append({name: None}) res.append(name) self.files_fs[i] = os.path.join(*res) prev = x self.name = self._get_attr(info, 'name') self.name_fs = self._to_fs(self.name) self.piece_length = info['piece length'] self.announce = metainfo.get('announce') self.announce_list = metainfo.get('announce-list') if 'announce-list' not in metainfo and 'announce' not in metainfo: self.is_trackerless = True else: self.is_trackerless = False self.nodes = metainfo.get('nodes') self.title = metainfo.get('title') self.comment = metainfo.get('comment') self.creation_date = metainfo.get('creation date') self.locale = metainfo.get('locale') self.url_list = metainfo.get('url-list', []) if not isinstance(self.url_list, list): self.url_list = [self.url_list, ] self.caches = metainfo.get('caches') self.hashes = [info['pieces'][x:x+20] for x in xrange(0, len(info['pieces']), 20)] self.infohash = InfoHashType(sha(bencode(info)).digest())
def _read_messages(self): # be compatible with encrypted clients. Thanks Uoti yield 1 + len(protocol_name) if self._privkey is not None or \ self._message != chr(len(protocol_name)) + protocol_name: if self.locally_initiated: if self._privkey is None: return dhstr = self._message yield DH_BYTES - len(dhstr) dhstr += self._message pub = bytetonum(dhstr) S = numtobyte(pow(pub, self._privkey, dh_prime)) pub = self._privkey = dhstr = None SKEY = self.parent.download_id x = sha('req3' + S).digest() streamid = sha('req2'+SKEY).digest() streamid = ''.join([chr(ord(streamid[i]) ^ ord(x[i])) for i in range(20)]) encrypt = ARC4.new(sha('keyA' + S + SKEY).digest()).encrypt encrypt('x'*1024) padlen = randrange(PAD_MAX) x = sha('req1' + S).digest() + streamid + encrypt( '\x00'*8 + '\x00'*3+'\x02'+'\x00'+chr(padlen)+ urandom(padlen)+'\x00\x00') self.connection.write(x) self.connection.encrypt = encrypt decrypt = ARC4.new(sha('keyB' + S + SKEY).digest()).decrypt decrypt('x'*1024) VC = decrypt('\x00'*8) # actually encrypt x = '' while 1: yield 1 x += self._message i = (x + self._rest).find(VC) if i >= 0: break yield len(self._rest) x += self._message if len(x) >= 520: self.protocol_violation('VC not found', self.connection) return yield i + 8 + 4 + 2 - len(x) x = decrypt((x + self._message)[-6:]) self._decrypt = decrypt if x[0:4] != '\x00\x00\x00\x02': self.protocol_violation('bad crypto method selected, not 2', self.connection) return padlen = (ord(x[4]) << 8) + ord(x[5]) if padlen > 512: self.protocol_violation('padlen too long', self.connection) return self.connection.write(chr(len(protocol_name)) + protocol_name + FLAGS + self.parent.download_id) yield padlen else: dhstr = self._message yield DH_BYTES - len(dhstr) dhstr += self._message privkey = bytetonum(urandom(20)) pub = numtobyte(pow(2, privkey, dh_prime)) self.connection.write(pub + urandom(randrange(PAD_MAX))) pub = bytetonum(dhstr) S = numtobyte(pow(pub, privkey, dh_prime)) dhstr = pub = privkey = None streamid = sha('req1' + S).digest() x = '' while 1: yield 1 x += self._message i = (x + self._rest).find(streamid) if i >= 0: break yield len(self._rest) x += self._message if len(x) >= 532: self.protocol_violation('incoming VC not found', self.connection) return yield i + 20 + 20 + 8 + 4 + 2 - len(x) self._message = (x + self._message)[-34:] streamid = self._message[0:20] x = sha('req3' + S).digest() streamid = ''.join([chr(ord(streamid[i]) ^ ord(x[i])) for i in range(20)]) self.parent.select_torrent_obfuscated(self, streamid) if self.parent.download_id is None: self.protocol_violation('download id unknown/rejected', self.connection) return self.logger = logging.getLogger( self.log_prefix + '.' + repr(self.parent.download_id) + '.peer_id_not_yet' ) SKEY = self.parent.download_id decrypt = ARC4.new(sha('keyA' + S + SKEY).digest()).decrypt decrypt('x'*1024) s = decrypt(self._message[20:34]) if s[0:8] != '\x00' * 8: self.protocol_violation('BAD VC', self.connection) return crypto_provide = toint(s[8:12]) padlen = (ord(s[12]) << 8) + ord(s[13]) if padlen > 512: self.protocol_violation('BAD padlen, too long', self.connection) return self._decrypt = decrypt yield padlen + 2 s = self._message encrypt = ARC4.new(sha('keyB' + S + SKEY).digest()).encrypt encrypt('x'*1024) self.connection.encrypt = encrypt if not crypto_provide & 2: self.protocol_violation("peer doesn't support crypto mode 2", self.connection) return padlen = randrange(PAD_MAX) s = '\x00' * 11 + '\x02\x00' + chr(padlen) + urandom(padlen) self.connection.write(s) S = SKEY = s = x = streamid = VC = padlen = None yield 1 + len(protocol_name) if self._message != chr(len(protocol_name)) + protocol_name: self.protocol_violation('classic handshake fails', self.connection) return yield 8 # reserved # dht is on last reserved byte if ord(self._message[7]) & DHT: self.uses_dht = True if ord(self._message[7]) & FAST_EXTENSION: if disable_fast_extension: self.uses_fast_extension = False else: if noisy: log( "Implements FAST_EXTENSION") self.uses_fast_extension = True yield 20 # download id (i.e., infohash) if self.parent.download_id is None: # incoming connection # modifies self.parent if successful self.parent.select_torrent(self, self._message) if self.parent.download_id is None: self.protocol_violation("no download_id from parent (peer from a torrent you're not running)", self.connection) return elif self._message != self.parent.download_id: self.protocol_violation("incorrect download_id from parent", self.connection) return if not self.locally_initiated: self.connection.write(chr(len(protocol_name)) + protocol_name + FLAGS + self.parent.download_id + self.parent.my_id) yield 20 # peer id if not self.id: self.id = self._message ns = (self.log_prefix + '.' + repr(self.parent.download_id) + '.' + self._message.encode('hex')) self.logger = logging.getLogger(ns) if self.id == self.parent.my_id: self.protocol_violation("talking to self", self.connection) return for v in self.parent.connections.itervalues(): if v is not self: if v.id == self.id: self.protocol_violation( "duplicate connection (id collision)", self.connection) return if (self.parent.config['one_connection_per_ip'] and v.ip == self.ip): self.protocol_violation( "duplicate connection (ip collision)", self.connection) return if self.locally_initiated: self.connection.write(self.parent.my_id) else: self.parent.everinc = True else: if self._message != self.id: self.protocol_violation("incorrect id") return self.complete = True self.parent.connection_completed(self) while True: yield 4 # message length l = toint(self._message) if l > self.parent.config['max_message_length']: self.protocol_violation("message length exceeds max (%s %s)" % (l, self.parent.config['max_message_length']), self.connection) return if l > 0: yield l self._got_message(self._message)
signfile = zurllib.urlopen(self.installer_url + '.sign') except: self.debug('Updater.get() failed to get signfile %s.sign' % self.installer_url) else: try: signature = pickle.load(signfile) except: self.debug('Updater.get() failed to load signfile %s' % signfile) if terrors: self.threadwrap(self.errorfunc, logging.WARNING, '\n'.join(terrors)) if torrentfile and signature: public_key_file = open(os.path.join(doc_root, 'public.key'), 'rb') public_key = pickle.load(public_key_file) h = sha(torrentfile).digest() if public_key.verify(h, signature): self.torrentfile = torrentfile b = bdecode(torrentfile) self.infohash = sha(bencode(b['info'])).digest() self.total_size = b['info']['length'] self.debug('Updater.get() got torrent file and signature') else: self.debug('Updater.get() torrent file signature failed to verify.') pass else: self.debug('Updater.get() doesn\'t have torrentfile %s and signature %s' % (str(type(torrentfile)), str(type(signature)))) def installer_path(self): if self.installer_dir is not None: