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)
Example #2
0
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: