5
5
from BitTornado.RawServer import RawServer, autodetect_ipv6, autodetect_socket_style
6
6
from BitTornado.HTTPHandler import HTTPHandler, months, weekdays
7
7
from BitTornado.parsedir import parsedir
8
from NatCheck import NatCheck
8
from NatCheck import NatCheck, CHECK_PEER_ID_ENCRYPTED
9
from BitTornado.BTcrypto import CRYPTO_OK
9
10
from T2T import T2TList
10
11
from BitTornado.subnetparse import IP_List, ipv6_to_ipv4, to_ipv4, is_valid_ip, is_ipv4
11
12
from BitTornado.iprangeparse import IP_List as IP_Range_List
91
93
('keep_dead', 0, 'keep dead torrents after they expire (so they still show up on your /scrape and web page)'),
92
94
('scrape_allowed', 'full', 'scrape access allowed (can be none, specific or full)'),
93
95
('dedicated_seed_id', '', 'allows tracker to monitor dedicated seed(s) and flag torrents as seeded'),
96
('compact_reqd', 1, "only allow peers that accept a compact response"),
96
99
def statefiletemplate(x):
99
102
for cname, cinfo in x.items():
100
103
if cname == 'peers':
101
104
for y in cinfo.values(): # The 'peers' key is a dictionary of SHA hashes (torrent ids)
102
if type(y) != DictType: # ... for the active torrents, and each is a dictionary
104
for id, info in y.items(): # ... of client ids interested in that torrent
107
if type(info) != DictType: # ... each of which is also a dictionary
108
raise ValueError # ... which has an IP, a Port, and a Bytes Left count for that client for that torrent
109
if type(info.get('ip', '')) != StringType:
111
port = info.get('port')
112
if type(port) not in (IntType,LongType) or port < 0:
114
left = info.get('left')
115
if type(left) not in (IntType,LongType) or left < 0:
105
if type(y) != DictType: # ... for the active torrents, and each is a dictionary
107
for id, info in y.items(): # ... of client ids interested in that torrent
110
if type(info) != DictType: # ... each of which is also a dictionary
111
raise ValueError # ... which has an IP, a Port, and a Bytes Left count for that client for that torrent
112
if type(info.get('ip', '')) != StringType:
114
port = info.get('port')
115
if type(port) not in (IntType,LongType) or port < 0:
117
left = info.get('left')
118
if type(left) not in (IntType,LongType) or left < 0:
120
if type(info.get('supportcrypto')) not in (IntType,LongType):
122
if type(info.get('requirecrypto')) not in (IntType,LongType):
117
124
elif cname == 'completed':
118
125
if (type(cinfo) != DictType): # The 'completed' key is a dictionary of SHA hashes (torrent ids)
119
126
raise ValueError # ... for keeping track of the total completions per torrent
215
222
print "**warning** specified favicon file -- %s -- does not exist." % favicon
216
223
self.rawserver = rawserver
217
self.cached = {} # format: infohash: [[time1, l1, s1], [time2, l2, s2], [time3, l3, s3]]
224
self.cached = {} # format: infohash: [[time1, l1, s1], [time2, l2, s2], ...]
218
225
self.cached_t = {} # format: infohash: [time, cache]
231
238
if self.only_local_override_ip == 2:
232
239
self.only_local_override_ip = not config['nat_check']
241
if CHECK_PEER_ID_ENCRYPTED and not CRYPTO_OK:
242
print ('**warning** crypto library not installed,' +
243
' cannot completely verify encrypted peers')
234
245
if exists(self.dfile):
236
247
h = open(self.dfile, 'rb')
246
257
self.downloads = self.state.setdefault('peers', {})
247
258
self.completed = self.state.setdefault('completed', {})
249
self.becache = {} # format: infohash: [[l1, s1], [l2, s2], [l3, s3]]
261
''' format: infohash: [[l0, s0], [l1, s1], ...]
262
l0,s0 = compact, not requirecrypto=1
263
l1,s1 = compact, only supportcrypto=1
264
l2,s2 = [compact, crypto_flag], all peers
267
l4,l4 = [ip,port] nopeerid
269
if config['compact_reqd']:
273
self.cache_default = [({},{}) for i in xrange(x)]
250
274
for infohash, ds in self.downloads.items():
251
275
self.seedcount[infohash] = 0
252
276
for x,y in ds.items():
263
287
if is_valid_ip(gip) and (
264
288
not self.only_local_override_ip or local_IPs.includes(ip) ):
266
self.natcheckOK(infohash,x,ip,y['port'],y['left'])
290
self.natcheckOK(infohash,x,ip,y['port'],y)
268
292
for x in self.downloads.keys():
269
293
self.times[x] = {}
460
484
s.write('<tr><td><code>%s</code></td><td align="right"><code>%i</code></td><td align="right"><code>%i</code></td><td align="right"><code>%i</code></td></tr>\n' \
461
485
% (b2a_hex(hash), c, d, n))
463
for i in self.completed.values():
465
486
if self.config['allowed_dir'] and self.show_names:
466
s.write('<tr><td align="right" colspan="2">%i files</td><td align="right">%s</td><td align="right">%i</td><td align="right">%i</td><td align="right">%i/%i</td><td align="right">%s</td></tr>\n'
467
% (nf, size_format(ts), tc, td, tn, ttn, size_format(tt)))
487
s.write('<tr><td align="right" colspan="2">%i files</td><td align="right">%s</td><td align="right">%i</td><td align="right">%i</td><td align="right">%i</td><td align="right">%s</td></tr>\n'
488
% (nf, size_format(ts), tc, td, tn, size_format(tt)))
469
s.write('<tr><td align="right">%i files</td><td align="right">%i</td><td align="right">%i</td><td align="right">%i/%i</td></tr>\n'
470
% (nf, tc, td, tn, ttn))
490
s.write('<tr><td align="right">%i files</td><td align="right">%i</td><td align="right">%i</td><td align="right">%i</td></tr>\n'
471
492
s.write('</table>\n' \
473
494
'<li><em>info hash:</em> SHA1 hash of the "info" section of the metainfo (*.torrent)</li>\n' \
474
495
'<li><em>complete:</em> number of connected clients with the complete file</li>\n' \
475
496
'<li><em>downloading:</em> number of connected clients still downloading</li>\n' \
476
'<li><em>downloaded:</em> reported complete downloads (total: current/all)</li>\n' \
497
'<li><em>downloaded:</em> reported complete downloads</li>\n' \
477
498
'<li><em>transferred:</em> torrent size * total downloaded (does not include partial transfers)</li>\n' \
586
607
raise ValueError, 'id not of length 20'
587
608
if event not in ['started', 'completed', 'stopped', 'snooped', None]:
588
609
raise ValueError, 'invalid event'
589
port = long(params('port',''))
610
port = params('cryptoport')
612
port = params('port','')
590
614
if port < 0 or port > 65535:
591
615
raise ValueError, 'invalid port'
592
616
left = long(params('left',''))
594
618
raise ValueError, 'invalid amount left'
595
619
uploaded = long(params('uploaded',''))
596
620
downloaded = long(params('downloaded',''))
621
if params('supportcrypto'):
624
s = int(params['requirecrypto'])
598
633
peer = peers.get(myid)
599
634
islocal = local_IPs.includes(ip)
621
656
ts[myid] = clock()
622
peer = {'ip': ip, 'port': port, 'left': left}
657
peer = { 'ip': ip, 'port': port, 'left': left,
658
'supportcrypto': supportcrypto,
659
'requirecrypto': requirecrypto }
624
661
peer['key'] = mykey
628
665
if not self.natcheck or islocal:
630
self.natcheckOK(infohash,myid,ip1,port,left)
667
self.natcheckOK(infohash,myid,ip1,port,peer)
632
NatCheck(self.connectback_result,infohash,myid,ip1,port,self.rawserver)
669
NatCheck(self.connectback_result,infohash,myid,ip1,port,
670
self.rawserver,encrypted=requirecrypto)
634
672
peer['nat'] = 2**30
635
673
if event == 'completed':
636
674
self.completed[infohash] += 1
638
676
self.seedcount[infohash] += 1
640
678
peers[myid] = peer
688
726
if not self.natcheck or islocal:
690
self.natcheckOK(infohash,myid,ip1,port,left)
728
self.natcheckOK(infohash,myid,ip1,port,peer)
692
NatCheck(self.connectback_result,infohash,myid,ip1,port,self.rawserver)
730
NatCheck(self.connectback_result,infohash,myid,ip1,port,
731
self.rawserver,encrypted=requirecrypto)
697
def peerlist(self, infohash, stopped, tracker, is_seed, return_type, rsize):
736
def peerlist(self, infohash, stopped, tracker, is_seed,
737
return_type, rsize, supportcrypto):
698
738
data = {} # return data
699
739
seeds = self.seedcount[infohash]
700
740
data['complete'] = seeds
711
751
cache = self.cached_t.setdefault(infohash, None)
712
752
if ( not cache or len(cache[1]) < rsize
713
753
or cache[0] + self.config['min_time_between_cache_refreshes'] < clock() ):
714
bc = self.becache.setdefault(infohash,[[{}, {}], [{}, {}], [{}, {}]])
754
bc = self.becache.setdefault(infohash,self.cache_default)
715
755
cache = [ clock(), bc[0][0].values() + bc[0][1].values() ]
716
756
self.cached_t[infohash] = cache
717
757
shuffle(cache[1])
726
766
data['peers'] = []
729
bc = self.becache.setdefault(infohash,[[{}, {}], [{}, {}], [{}, {}]])
730
len_l = len(bc[0][0])
731
len_s = len(bc[0][1])
769
bc = self.becache.setdefault(infohash,self.cache_default)
770
len_l = len(bc[2][0])
771
len_s = len(bc[2][1])
732
772
if not (len_l+len_s): # caches are empty!
733
773
data['peers'] = []
743
783
peers = self.downloads[infohash]
784
if self.config['compact_reqd']:
787
vv = ([],[],[],[],[])
745
788
for key, ip, port in self.t2tlist.harvest(infohash): # empty if disabled
746
789
if not peers.has_key(key):
747
vv[0].append({'ip': ip, 'port': port, 'peer id': key})
748
vv[1].append({'ip': ip, 'port': port})
749
vv[2].append(compact_peer_info(ip, port))
790
cp = compact_peer_info(ip, port)
792
vv[2].append((cp,'\x00'))
793
if not self.config['compact_reqd']:
794
vv[3].append({'ip': ip, 'port': port, 'peer id': key})
795
vv[4].append({'ip': ip, 'port': port})
750
796
cache = [ self.cachetime,
751
797
bc[return_type][0].values()+vv[return_type],
752
798
bc[return_type][1].values() ]
776
822
peerdata.extend(cache[1][-rsize:])
777
823
del cache[1][-rsize:]
779
peerdata = ''.join(peerdata)
780
data['peers'] = peerdata
825
data['peers'] = ''.join(peerdata)
826
elif return_type == 1:
827
data['crypto_flags'] = "0x01"*len(peerdata)
828
data['peers'] = ''.join(peerdata)
829
elif return_type == 2:
830
data['crypto_flags'] = ''.join([p[1] for p in peerdata])
831
data['peers'] = ''.join([p[0] for p in peerdata])
833
data['peers'] = peerdata
873
926
bencode({'response': 'OK'}))
875
928
if params('compact') and ipv4:
929
if params('requirecrypto'):
931
elif params('supportcrypto'):
935
elif self.config['compact_reqd'] and ipv4:
936
return (400, 'Bad Request', {'Content-Type': 'text/plain'},
937
'your client is outdated, please upgrade')
877
938
elif params('no_peer_id'):
882
943
data = self.peerlist(infohash, event=='stopped',
883
944
params('tracker'), not params('left'),
945
return_type, rsize, params('supportcrypto'))
886
947
if paramslist.has_key('scrape'): # deprecated
887
948
data['scrape'] = self.scrapedata(infohash, False)
895
956
return (200, 'OK', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, bencode(data))
898
def natcheckOK(self, infohash, peerid, ip, port, not_seed):
899
bc = self.becache.setdefault(infohash,[[{}, {}], [{}, {}], [{}, {}]])
900
bc[0][not not_seed][peerid] = Bencached(bencode({'ip': ip, 'port': port,
902
bc[1][not not_seed][peerid] = Bencached(bencode({'ip': ip, 'port': port}))
903
bc[2][not not_seed][peerid] = compact_peer_info(ip, port)
959
def natcheckOK(self, infohash, peerid, ip, port, peer):
960
seed = not peer['left']
961
bc = self.becache.setdefault(infohash,self.cache_default)
962
cp = compact_peer_info(ip, port)
963
reqc = peer['requirecrypto']
964
bc[2][seed][peerid] = (cp,chr(reqc))
965
if peer['supportcrypto']:
966
bc[1][seed][peerid] = cp
968
bc[0][seed][peerid] = cp
969
if not self.config['compact_reqd']:
970
bc[3][seed][peerid] = Bencached(bencode({'ip': ip, 'port': port,
972
bc[4][seed][peerid] = Bencached(bencode({'ip': ip, 'port': port}))
906
975
def natchecklog(self, peerid, ip, port, result):
910
979
ip, port, result)
912
981
def connectback_result(self, result, downloadid, peerid, ip, port):
913
record = self.downloads.get(downloadid, {}).get(peerid)
982
record = self.downloads.get(downloadid,{}).get(peerid)
914
983
if ( record is None
915
984
or (record['ip'] != ip and record.get('given ip') != ip)
916
985
or record['port'] != port ):
926
995
if not record.has_key('nat'):
927
996
record['nat'] = int(not result)
929
self.natcheckOK(downloadid,peerid,ip,port,record['left'])
998
self.natcheckOK(downloadid,peerid,ip,port,record)
930
999
elif result and record['nat']:
931
1000
record['nat'] = 0
932
self.natcheckOK(downloadid,peerid,ip,port,record['left'])
1001
self.natcheckOK(downloadid,peerid,ip,port,record)
933
1002
elif not result:
934
1003
record['nat'] += 1