This repository has been archived on 2024-05-09. You can view files and clone it, but cannot push or open issues/pull-requests.
ipodderx-core/BitTorrent/track.py

875 lines
36 KiB
Python

# The contents of this file are subject to the BitTorrent Open Source License
# Version 1.1 (the License). You may not copy or use this file, in either
# source code or executable form, except in compliance with the License. You
# may obtain a copy of the License at http://www.bittorrent.com/license/.
#
# Software distributed under the License is distributed on an AS IS basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
# Written by Bram Cohen and John Hoffman
import sys
import os
import signal
import re
from threading import Event
from urlparse import urlparse
from traceback import print_exc
from time import time, gmtime, strftime, localtime
from random import shuffle
from types import StringType, IntType, LongType, ListType, DictType
from binascii import b2a_hex
from cStringIO import StringIO
from BitTorrent.obsoletepythonsupport import *
from BitTorrent.parseargs import parseargs, formatDefinitions
from BitTorrent.RawServer_magic import RawServer
from BitTorrent.HTTPHandler import HTTPHandler, months, weekdays
from BitTorrent.parsedir import parsedir
from BitTorrent.NatCheck import NatCheck
from BitTorrent.bencode import bencode, bdecode, Bencached
from BitTorrent.zurllib import quote, unquote
from BitTorrent import version
defaults = [
('port', 80,
_("Port to listen on.")),
('dfile', None,
_("file to store recent downloader info in")),
('bind', '',
_("ip to bind to locally")),
('socket_timeout', 15,
_("timeout for closing connections")),
('close_with_rst', 0,
_("close connections with RST and avoid the TCP TIME_WAIT state")),
('save_dfile_interval', 5 * 60,
_("seconds between saving dfile")),
('timeout_downloaders_interval', 45 * 60,
_("seconds between expiring downloaders")),
('reannounce_interval', 30 * 60,
_("seconds downloaders should wait between reannouncements")),
('response_size', 50,
_("default number of peers to send an info message to if the "
"client does not specify a number")),
('timeout_check_interval', 5,
_("time to wait between checking if any connections have timed out")),
('nat_check', 3,
_("how many times to check if a downloader is behind a NAT "
"(0 = don't check)")),
('log_nat_checks', 0,
_("whether to add entries to the log for nat-check results")),
('min_time_between_log_flushes', 3.0,
_("minimum time it must have been since the last flush to do "
"another one")),
('min_time_between_cache_refreshes', 600.0,
_("minimum time in seconds before a cache is considered stale "
"and is flushed")),
('allowed_dir', '',
_("only allow downloads for .torrents in this dir (and recursively in "
"subdirectories of directories that have no .torrent files "
"themselves). If set, torrents in this directory show up on "
"infopage/scrape whether they have peers or not")),
('parse_dir_interval', 60,
_("how often to rescan the torrent directory, in seconds")),
('allowed_controls', 0,
_("allow special keys in torrents in the allowed_dir to affect "
"tracker access")),
('hupmonitor', 0,
_("whether to reopen the log file upon receipt of HUP signal")),
('show_infopage', 1,
_("whether to display an info page when the tracker's root dir "
"is loaded")),
('infopage_redirect', '',
_("a URL to redirect the info page to")),
('show_names', 1,
_("whether to display names from allowed dir")),
('favicon', '',
_("file containing x-icon data to return when browser requests "
"favicon.ico")),
('only_local_override_ip', 2,
_("ignore the ip GET parameter from machines which aren't on "
"local network IPs (0 = never, 1 = always, 2 = ignore if NAT "
"checking is not enabled). HTTP proxy headers giving address "
"of original client are treated the same as --ip.")),
('logfile', '',
_("file to write the tracker logs, use - for stdout (default)")),
('allow_get', 0,
_("use with allowed_dir; adds a /file?hash={hash} url that "
"allows users to download the torrent file")),
('keep_dead', 0,
_("keep dead torrents after they expire (so they still show up on your "
"/scrape and web page). Only matters if allowed_dir is not set")),
('scrape_allowed', 'full',
_("scrape access allowed (can be none, specific or full)")),
('max_give', 200,
_("maximum number of peers to give with any one request")),
('twisted', -1,
_("Use Twisted network libraries for network connections. 1 means use twisted, 0 means do not use twisted, -1 means autodetect, and prefer twisted")),
]
def statefiletemplate(x):
if type(x) != DictType:
raise ValueError
for cname, cinfo in x.items():
if cname == 'peers':
for y in cinfo.values(): # The 'peers' key is a dictionary of SHA hashes (torrent ids)
if type(y) != DictType: # ... for the active torrents, and each is a dictionary
raise ValueError
for peerid, info in y.items(): # ... of client ids interested in that torrent
if (len(peerid) != 20):
raise ValueError
if type(info) != DictType: # ... each of which is also a dictionary
raise ValueError # ... which has an IP, a Port, and a Bytes Left count for that client for that torrent
if type(info.get('ip', '')) != StringType:
raise ValueError
port = info.get('port')
if type(port) not in (IntType, LongType) or port < 0:
raise ValueError
left = info.get('left')
if type(left) not in (IntType, LongType) or left < 0:
raise ValueError
elif cname == 'completed':
if (type(cinfo) != DictType): # The 'completed' key is a dictionary of SHA hashes (torrent ids)
raise ValueError # ... for keeping track of the total completions per torrent
for y in cinfo.values(): # ... each torrent has an integer value
if type(y) not in (IntType,LongType):
raise ValueError # ... for the number of reported completions for that torrent
elif cname == 'allowed':
if (type(cinfo) != DictType): # a list of info_hashes and included data
raise ValueError
if x.has_key('allowed_dir_files'):
adlist = [z[1] for z in x['allowed_dir_files'].values()]
for y in cinfo.keys(): # and each should have a corresponding key here
if not y in adlist:
raise ValueError
elif cname == 'allowed_dir_files':
if (type(cinfo) != DictType): # a list of files, their attributes and info hashes
raise ValueError
dirkeys = {}
for y in cinfo.values(): # each entry should have a corresponding info_hash
if not y[1]:
continue
if not x['allowed'].has_key(y[1]):
raise ValueError
if dirkeys.has_key(y[1]): # and each should have a unique info_hash
raise ValueError
dirkeys[y[1]] = 1
alas = _("your file may exist elsewhere in the universe\nbut alas, not here\n")
def isotime(secs = None):
if secs == None:
secs = time()
return strftime('%Y-%m-%d %H:%M UTC', gmtime(secs))
http_via_filter = re.compile(' for ([0-9.]+)\Z')
def _get_forwarded_ip(headers):
if headers.has_key('http_x_forwarded_for'):
header = headers['http_x_forwarded_for']
try:
x,y = header.split(',')
except:
return header
if not is_local_ip(x):
return x
return y
if headers.has_key('http_client_ip'):
return headers['http_client_ip']
if headers.has_key('http_via'):
x = http_via_filter.search(headers['http_via'])
try:
return x.group(1)
except:
pass
if headers.has_key('http_from'):
return headers['http_from']
return None
def get_forwarded_ip(headers):
x = _get_forwarded_ip(headers)
if x is None or not is_valid_ipv4(x) or is_local_ip(x):
return None
return x
def compact_peer_info(ip, port):
try:
s = ( ''.join([chr(int(i)) for i in ip.split('.')])
+ chr((port & 0xFF00) >> 8) + chr(port & 0xFF) )
if len(s) != 6:
s = ''
except:
s = '' # not a valid IP, must be a domain name
return s
def is_valid_ipv4(ip):
a = ip.split('.')
if len(a) != 4:
return False
try:
for x in a:
chr(int(x))
return True
except:
return False
def is_local_ip(ip):
try:
v = [int(x) for x in ip.split('.')]
if v[0] == 10 or v[0] == 127 or v[:2] in ([192, 168], [169, 254]):
return 1
if v[0] == 172 and v[1] >= 16 and v[1] <= 31:
return 1
except ValueError:
return 0
class Tracker(object):
def __init__(self, config, rawserver):
self.config = config
self.response_size = config['response_size']
self.max_give = config['max_give']
self.dfile = config['dfile']
self.natcheck = config['nat_check']
favicon = config['favicon']
self.favicon = None
if favicon:
try:
h = open(favicon,'r')
self.favicon = h.read()
h.close()
except:
print _("**warning** specified favicon file -- %s -- does not exist.") % favicon
self.rawserver = rawserver
self.cached = {} # format: infohash: [[time1, l1, s1], [time2, l2, s2], [time3, l3, s3]]
self.cached_t = {} # format: infohash: [time, cache]
self.times = {}
self.state = {}
self.seedcount = {}
self.only_local_override_ip = config['only_local_override_ip']
if self.only_local_override_ip == 2:
self.only_local_override_ip = not config['nat_check']
if os.path.exists(self.dfile):
try:
h = open(self.dfile, 'rb')
ds = h.read()
h.close()
tempstate = bdecode(ds)
if not tempstate.has_key('peers'):
tempstate = {'peers': tempstate}
statefiletemplate(tempstate)
self.state = tempstate
except:
print _("**warning** statefile %s corrupt; resetting") % \
self.dfile
self.downloads = self.state.setdefault('peers', {})
self.completed = self.state.setdefault('completed', {})
self.becache = {} # format: infohash: [[l1, s1], [l2, s2], [l3, s3]]
for infohash, ds in self.downloads.items():
self.seedcount[infohash] = 0
for x,y in ds.items():
if not y.get('nat',-1):
ip = y.get('given_ip')
if not (ip and self.allow_local_override(y['ip'], ip)):
ip = y['ip']
self.natcheckOK(infohash,x,ip,y['port'],y['left'])
if not y['left']:
self.seedcount[infohash] += 1
for infohash in self.downloads:
self.times[infohash] = {}
for peerid in self.downloads[infohash]:
self.times[infohash][peerid] = 0
self.reannounce_interval = config['reannounce_interval']
self.save_dfile_interval = config['save_dfile_interval']
self.show_names = config['show_names']
rawserver.add_task(self.save_dfile, self.save_dfile_interval)
self.prevtime = time()
self.timeout_downloaders_interval = config['timeout_downloaders_interval']
rawserver.add_task(self.expire_downloaders, self.timeout_downloaders_interval)
self.logfile = None
self.log = None
if (config['logfile'] != '') and (config['logfile'] != '-'):
try:
self.logfile = config['logfile']
self.log = open(self.logfile,'a')
sys.stdout = self.log
print _("# Log Started: "), isotime()
except:
print _("**warning** could not redirect stdout to log file: "), sys.exc_info()[0]
if config['hupmonitor']:
def huphandler(signum, frame, self = self):
try:
self.log.close ()
self.log = open(self.logfile,'a')
sys.stdout = self.log
print _("# Log reopened: "), isotime()
except:
print _("**warning** could not reopen logfile")
signal.signal(signal.SIGHUP, huphandler)
self.allow_get = config['allow_get']
if config['allowed_dir'] != '':
self.allowed_dir = config['allowed_dir']
self.parse_dir_interval = config['parse_dir_interval']
self.allowed = self.state.setdefault('allowed',{})
self.allowed_dir_files = self.state.setdefault('allowed_dir_files',{})
self.allowed_dir_blocked = {}
self.parse_allowed()
else:
try:
del self.state['allowed']
except:
pass
try:
del self.state['allowed_dir_files']
except:
pass
self.allowed = None
self.uq_broken = unquote('+') != ' '
self.keep_dead = config['keep_dead']
def allow_local_override(self, ip, given_ip):
return is_valid_ipv4(given_ip) and (
not self.only_local_override_ip or is_local_ip(ip) )
def get_infopage(self):
try:
if not self.config['show_infopage']:
return (404, 'Not Found', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, alas)
red = self.config['infopage_redirect']
if red != '':
return (302, 'Found', {'Content-Type': 'text/html', 'Location': red},
'<A HREF="'+red+'">Click Here</A>')
s = StringIO()
s.write('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n' \
'<html><head><title>BitTorrent download info</title>\n')
if self.favicon is not None:
s.write('<link rel="shortcut icon" href="/favicon.ico">\n')
s.write('</head>\n<body>\n' \
'<h3>BitTorrent download info</h3>\n'\
'<ul>\n'
'<li><strong>tracker version:</strong> %s</li>\n' \
'<li><strong>server time:</strong> %s</li>\n' \
'</ul>\n' % (version, isotime()))
if self.allowed is not None:
if self.show_names:
names = [ (value['name'], infohash)
for infohash, value in self.allowed.iteritems()]
else:
names = [(None, infohash) for infohash in self.allowed]
else:
names = [ (None, infohash) for infohash in self.downloads]
if not names:
s.write('<p>not tracking any files yet...</p>\n')
else:
names.sort()
tn = 0
tc = 0
td = 0
tt = 0 # Total transferred
ts = 0 # Total size
nf = 0 # Number of files displayed
if self.allowed is not None and self.show_names:
s.write('<table summary="files" border="1">\n' \
'<tr><th>info hash</th><th>torrent name</th><th align="right">size</th><th align="right">complete</th><th align="right">downloading</th><th align="right">downloaded</th><th align="right">transferred</th></tr>\n')
else:
s.write('<table summary="files">\n' \
'<tr><th>info hash</th><th align="right">complete</th><th align="right">downloading</th><th align="right">downloaded</th></tr>\n')
for name, infohash in names:
l = self.downloads[infohash]
n = self.completed.get(infohash, 0)
tn = tn + n
c = self.seedcount[infohash]
tc = tc + c
d = len(l) - c
td = td + d
nf = nf + 1
if self.allowed is not None and self.show_names:
if self.allowed.has_key(infohash):
sz = self.allowed[infohash]['length'] # size
ts = ts + sz
szt = sz * n # Transferred for this torrent
tt = tt + szt
if self.allow_get == 1:
linkname = '<a href="/file?info_hash=' + quote(infohash) + '">' + name + '</a>'
else:
linkname = name
s.write('<tr><td><code>%s</code></td><td>%s</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' \
% (b2a_hex(infohash), linkname, size_format(sz), c, d, n, size_format(szt)))
else:
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' \
% (b2a_hex(infohash), c, d, n))
ttn = 0
for i in self.completed.values():
ttn = ttn + i
if self.allowed is not None and self.show_names:
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'
% (nf, size_format(ts), tc, td, tn, ttn, size_format(tt)))
else:
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'
% (nf, tc, td, tn, ttn))
s.write('</table>\n' \
'<ul>\n' \
'<li><em>info hash:</em> SHA1 hash of the "info" section of the metainfo (*.torrent)</li>\n' \
'<li><em>complete:</em> number of connected clients with the complete file</li>\n' \
'<li><em>downloading:</em> number of connected clients still downloading</li>\n' \
'<li><em>downloaded:</em> reported complete downloads (total: current/all)</li>\n' \
'<li><em>transferred:</em> torrent size * total downloaded (does not include partial transfers)</li>\n' \
'</ul>\n')
s.write('</body>\n' \
'</html>\n')
return (200, 'OK', {'Content-Type': 'text/html; charset=iso-8859-1'}, s.getvalue())
except:
print_exc()
return (500, 'Internal Server Error', {'Content-Type': 'text/html; charset=iso-8859-1'}, 'Server Error')
def scrapedata(self, infohash, return_name = True):
l = self.downloads[infohash]
n = self.completed.get(infohash, 0)
c = self.seedcount[infohash]
d = len(l) - c
f = {'complete': c, 'incomplete': d, 'downloaded': n}
if return_name and self.show_names and self.allowed is not None:
f['name'] = self.allowed[infohash]['name']
return (f)
def get_scrape(self, paramslist):
fs = {}
if paramslist.has_key('info_hash'):
if self.config['scrape_allowed'] not in ['specific', 'full']:
return (400, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'},
bencode({'failure reason':
_("specific scrape function is not available with this tracker.")}))
for infohash in paramslist['info_hash']:
if self.allowed is not None and infohash not in self.allowed:
continue
if infohash in self.downloads:
fs[infohash] = self.scrapedata(infohash)
else:
if self.config['scrape_allowed'] != 'full':
return (400, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'},
bencode({'failure reason':
_("full scrape function is not available with this tracker.")}))
if self.allowed is not None:
hashes = self.allowed
else:
hashes = self.downloads
for infohash in hashes:
fs[infohash] = self.scrapedata(infohash)
return (200, 'OK', {'Content-Type': 'text/plain'}, bencode({'files': fs}))
def get_file(self, infohash):
if not self.allow_get:
return (400, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'},
_("get function is not available with this tracker."))
if not self.allowed.has_key(infohash):
return (404, 'Not Found', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, alas)
fname = self.allowed[infohash]['file']
fpath = self.allowed[infohash]['path']
return (200, 'OK', {'Content-Type': 'application/x-bittorrent',
'Content-Disposition': 'attachment; filename=' + fname},
open(fpath, 'rb').read())
def check_allowed(self, infohash, paramslist):
if self.allowed is not None:
if not self.allowed.has_key(infohash):
return (200, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'},
bencode({'failure reason':
_("Requested download is not authorized for use with this tracker.")}))
if self.config['allowed_controls']:
if self.allowed[infohash].has_key('failure reason'):
return (200, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'},
bencode({'failure reason': self.allowed[infohash]['failure reason']}))
return None
def add_data(self, infohash, event, ip, paramslist):
peers = self.downloads.setdefault(infohash, {})
ts = self.times.setdefault(infohash, {})
self.completed.setdefault(infohash, 0)
self.seedcount.setdefault(infohash, 0)
def params(key, default = None, l = paramslist):
if l.has_key(key):
return l[key][0]
return default
myid = params('peer_id','')
if len(myid) != 20:
raise ValueError, 'id not of length 20'
if event not in ['started', 'completed', 'stopped', 'snooped', None]:
raise ValueError, 'invalid event'
port = int(params('port',''))
if port < 0 or port > 65535:
raise ValueError, 'invalid port'
left = int(params('left',''))
if left < 0:
raise ValueError, 'invalid amount left'
peer = peers.get(myid)
mykey = params('key')
auth = not peer or peer.get('key', -1) == mykey or peer.get('ip') == ip
gip = params('ip')
local_override = gip and self.allow_local_override(ip, gip)
if local_override:
ip1 = gip
else:
ip1 = ip
if not auth and local_override and self.only_local_override_ip:
auth = True
if params('numwant') is not None:
rsize = min(int(params('numwant')), self.max_give)
else:
rsize = self.response_size
if event == 'stopped':
if peer and auth:
self.delete_peer(infohash,myid)
elif not peer:
ts[myid] = time()
peer = {'ip': ip, 'port': port, 'left': left}
if mykey:
peer['key'] = mykey
if gip:
peer['given ip'] = gip
if port:
if not self.natcheck or (local_override and self.only_local_override_ip):
peer['nat'] = 0
self.natcheckOK(infohash,myid,ip1,port,left)
else:
NatCheck(self.connectback_result,infohash,myid,ip1,port,self.rawserver)
else:
peer['nat'] = 2**30
if event == 'completed':
self.completed[infohash] += 1
if not left:
self.seedcount[infohash] += 1
peers[myid] = peer
else:
if not auth:
return rsize # return w/o changing stats
ts[myid] = time()
if not left and peer['left']:
self.completed[infohash] += 1
self.seedcount[infohash] += 1
if not peer.get('nat', -1):
for bc in self.becache[infohash]:
bc[1][myid] = bc[0][myid]
del bc[0][myid]
if peer['left']:
peer['left'] = left
recheck = False
if ip != peer['ip']:
peer['ip'] = ip
recheck = True
if gip != peer.get('given ip'):
if gip:
peer['given ip'] = gip
elif peer.has_key('given ip'):
del peer['given ip']
if local_override:
if self.only_local_override_ip:
self.natcheckOK(infohash,myid,ip1,port,left)
else:
recheck = True
if port and self.natcheck:
if recheck:
if peer.has_key('nat'):
if not peer['nat']:
l = self.becache[infohash]
y = not peer['left']
for x in l:
del x[y][myid]
del peer['nat'] # restart NAT testing
else:
natted = peer.get('nat', -1)
if natted and natted < self.natcheck:
recheck = True
if recheck:
NatCheck(self.connectback_result,infohash,myid,ip1,port,self.rawserver)
return rsize
def peerlist(self, infohash, stopped, is_seed, return_type, rsize):
data = {} # return data
seeds = self.seedcount[infohash]
data['complete'] = seeds
data['incomplete'] = len(self.downloads[infohash]) - seeds
if ( self.allowed is not None and self.config['allowed_controls'] and
self.allowed[infohash].has_key('warning message') ):
data['warning message'] = self.allowed[infohash]['warning message']
data['interval'] = self.reannounce_interval
if stopped or not rsize: # save some bandwidth
data['peers'] = []
return data
bc = self.becache.setdefault(infohash,[[{}, {}], [{}, {}], [{}, {}]])
len_l = len(bc[0][0])
len_s = len(bc[0][1])
if not (len_l+len_s): # caches are empty!
data['peers'] = []
return data
l_get_size = int(float(rsize)*(len_l)/(len_l+len_s))
cache = self.cached.setdefault(infohash,[None,None,None])[return_type]
if cache:
if cache[0] + self.config['min_time_between_cache_refreshes'] < time():
cache = None
else:
if ( (is_seed and len(cache[1]) < rsize)
or len(cache[1]) < l_get_size or not cache[1] ):
cache = None
if not cache:
vv = [[],[],[]]
cache = [ time(),
bc[return_type][0].values()+vv[return_type],
bc[return_type][1].values() ]
shuffle(cache[1])
shuffle(cache[2])
self.cached[infohash][return_type] = cache
for rr in xrange(len(self.cached[infohash])):
if rr != return_type:
try:
self.cached[infohash][rr][1].extend(vv[rr])
except:
pass
if len(cache[1]) < l_get_size:
peerdata = cache[1]
if not is_seed:
peerdata.extend(cache[2])
cache[1] = []
cache[2] = []
else:
if not is_seed:
peerdata = cache[2][l_get_size-rsize:]
del cache[2][l_get_size-rsize:]
rsize -= len(peerdata)
else:
peerdata = []
if rsize:
peerdata.extend(cache[1][-rsize:])
del cache[1][-rsize:]
if return_type == 2:
peerdata = ''.join(peerdata)
data['peers'] = peerdata
return data
def get(self, connection, path, headers):
ip = connection.get_ip()
nip = get_forwarded_ip(headers)
if nip and not self.only_local_override_ip:
ip = nip
paramslist = {}
def params(key, default = None, l = paramslist):
if l.has_key(key):
return l[key][0]
return default
try:
(scheme, netloc, path, pars, query, fragment) = urlparse(path)
if self.uq_broken == 1:
path = path.replace('+',' ')
query = query.replace('+',' ')
path = unquote(path)[1:]
for s in query.split('&'):
if s != '':
i = s.index('=')
kw = unquote(s[:i])
paramslist.setdefault(kw, [])
paramslist[kw] += [unquote(s[i+1:])]
if path == '' or path == 'index.html':
return self.get_infopage()
if path == 'scrape':
return self.get_scrape(paramslist)
if (path == 'file'):
return self.get_file(params('info_hash'))
if path == 'favicon.ico' and self.favicon is not None:
return (200, 'OK', {'Content-Type' : 'image/x-icon'}, self.favicon)
if path != 'announce':
return (404, 'Not Found', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, alas)
# main tracker function
infohash = params('info_hash')
if not infohash:
raise ValueError, 'no info hash'
notallowed = self.check_allowed(infohash, paramslist)
if notallowed:
return notallowed
event = params('event')
rsize = self.add_data(infohash, event, ip, paramslist)
except ValueError, e:
return (400, 'Bad Request', {'Content-Type': 'text/plain'},
'you sent me garbage - ' + str(e))
if params('compact'):
return_type = 2
elif params('no_peer_id'):
return_type = 1
else:
return_type = 0
data = self.peerlist(infohash, event=='stopped', not params('left'),
return_type, rsize)
if paramslist.has_key('scrape'):
data['scrape'] = self.scrapedata(infohash, False)
return (200, 'OK', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, bencode(data))
def natcheckOK(self, infohash, peerid, ip, port, not_seed):
bc = self.becache.setdefault(infohash,[[{}, {}], [{}, {}], [{}, {}]])
bc[0][not not_seed][peerid] = Bencached(bencode({'ip': ip, 'port': port,
'peer id': peerid}))
bc[1][not not_seed][peerid] = Bencached(bencode({'ip': ip, 'port': port}))
bc[2][not not_seed][peerid] = compact_peer_info(ip, port)
def natchecklog(self, peerid, ip, port, result):
year, month, day, hour, minute, second, a, b, c = localtime(time())
print '%s - %s [%02d/%3s/%04d:%02d:%02d:%02d] "!natcheck-%s:%i" %i 0 - -' % (
ip, quote(peerid), day, months[month], year, hour, minute, second,
ip, port, result)
def connectback_result(self, result, downloadid, peerid, ip, port):
record = self.downloads.get(downloadid, {}).get(peerid)
if ( record is None
or (record['ip'] != ip and record.get('given ip') != ip)
or record['port'] != port ):
if self.config['log_nat_checks']:
self.natchecklog(peerid, ip, port, 404)
return
if self.config['log_nat_checks']:
if result:
x = 200
else:
x = 503
self.natchecklog(peerid, ip, port, x)
if not record.has_key('nat'):
record['nat'] = int(not result)
if result:
self.natcheckOK(downloadid,peerid,ip,port,record['left'])
elif result and record['nat']:
record['nat'] = 0
self.natcheckOK(downloadid,peerid,ip,port,record['left'])
elif not result:
record['nat'] += 1
def save_dfile(self):
self.rawserver.add_task(self.save_dfile, self.save_dfile_interval)
h = open(self.dfile, 'wb')
h.write(bencode(self.state))
h.close()
def parse_allowed(self):
self.rawserver.add_task(self.parse_allowed, self.parse_dir_interval)
# logging broken .torrent files would be useful but could confuse
# programs parsing log files, so errors are just ignored for now
def ignore(message):
pass
r = parsedir(self.allowed_dir, self.allowed, self.allowed_dir_files,
self.allowed_dir_blocked, ignore,include_metainfo = False)
( self.allowed, self.allowed_dir_files, self.allowed_dir_blocked,
added, garbage2 ) = r
for infohash in added:
self.downloads.setdefault(infohash, {})
self.completed.setdefault(infohash, 0)
self.seedcount.setdefault(infohash, 0)
self.state['allowed'] = self.allowed
self.state['allowed_dir_files'] = self.allowed_dir_files
def delete_peer(self, infohash, peerid):
dls = self.downloads[infohash]
peer = dls[peerid]
if not peer['left']:
self.seedcount[infohash] -= 1
if not peer.get('nat',-1):
l = self.becache[infohash]
y = not peer['left']
for x in l:
del x[y][peerid]
del self.times[infohash][peerid]
del dls[peerid]
def expire_downloaders(self):
for infohash, peertimes in self.times.items():
for myid, t in peertimes.items():
if t < self.prevtime:
self.delete_peer(infohash, myid)
self.prevtime = time()
if (self.keep_dead != 1):
for key, peers in self.downloads.items():
if len(peers) == 0 and (self.allowed is None or
key not in self.allowed):
del self.times[key]
del self.downloads[key]
del self.seedcount[key]
self.rawserver.add_task(self.expire_downloaders, self.timeout_downloaders_interval)
def track(args):
if len(args) == 0:
print formatDefinitions(defaults, 80)
return
try:
config, files = parseargs(args, defaults, 0, 0)
except ValueError, e:
print _("error: ") + str(e)
print _("run with no arguments for parameter explanations")
return
r = RawServer(Event(), config)
t = Tracker(config, r)
s = r.create_serversocket(config['port'], config['bind'], True)
r.start_listening(s, HTTPHandler(t.get, config['min_time_between_log_flushes']))
r.listen_forever()
t.save_dfile()
print _("# Shutting down: ") + isotime()
def size_format(s):
if (s < 1024):
r = str(s) + 'B'
elif (s < 1048576):
r = str(int(s/1024)) + 'KiB'
elif (s < 1073741824):
r = str(int(s/1048576)) + 'MiB'
elif (s < 1099511627776):
r = str(int((s/1073741824.0)*100.0)/100.0) + 'GiB'
else:
r = str(int((s/1099511627776.0)*100.0)/100.0) + 'TiB'
return(r)