Browse Source

Convert tabs to spaces

pull/1/head
Hidden Z 9 years ago
parent
commit
6e3862d7ab
  1. 32
      bin/py-i2phosts-builder
  2. 98
      bin/py-i2phosts-checker
  3. 166
      bin/py-i2phosts-fetcher
  4. 108
      bin/py-i2phosts-injector
  5. 152
      bin/py-i2phosts-maint
  6. 200
      bin/py-i2phosts-master
  7. 2
      pyi2phosts/api/urls.py
  8. 16
      pyi2phosts/api/views.py
  9. 4
      pyi2phosts/extsources/admin.py
  10. 18
      pyi2phosts/extsources/models.py
  11. 4
      pyi2phosts/jump/urls.py
  12. 80
      pyi2phosts/jump/views.py
  13. 4
      pyi2phosts/latest/urls.py
  14. 36
      pyi2phosts/latest/views.py
  15. 80
      pyi2phosts/lib/generic.py
  16. 40
      pyi2phosts/lib/rss.py
  17. 92
      pyi2phosts/lib/utils.py
  18. 212
      pyi2phosts/lib/validation.py
  19. 58
      pyi2phosts/postkey/admin.py
  20. 42
      pyi2phosts/postkey/models.py
  21. 6
      pyi2phosts/postkey/urls.py
  22. 262
      pyi2phosts/postkey/views.py
  23. 2
      pyi2phosts/search/urls.py
  24. 18
      pyi2phosts/search/views.py
  25. 10
      pyi2phosts/settings.py
  26. 144
      pyi2phosts/static-common/base.css
  27. 10
      pyi2phosts/static-common/inproxy.html
  28. BIN
      pyi2phosts/static-common/rss-grey-18.png
  29. 2
      pyi2phosts/templates/404.html
  30. 2
      pyi2phosts/templates/500.html
  31. 108
      pyi2phosts/templates/base.html
  32. 8
      pyi2phosts/templates/browse.html
  33. 10
      pyi2phosts/templates/contacts.html
  34. 24
      pyi2phosts/templates/faq.html
  35. 88
      pyi2phosts/templates/index.html
  36. 20
      pyi2phosts/templates/policy.html
  37. 12
      pyi2phosts/templates/postkey.html
  38. 10
      pyi2phosts/templates/subdomain_http_verify.html
  39. 28
      pyi2phosts/urls.py
  40. 28
      setup.py

32
bin/py-i2phosts-builder

@ -7,32 +7,32 @@ import configobj
# parse command line options # parse command line options
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Hosts builder for py-i2phosts.', description='Hosts builder for py-i2phosts.',
epilog='Report bugs to http://zzz.i2p/topics/733') epilog='Report bugs to http://zzz.i2p/topics/733')
parser.add_argument('-c', '--config', default='/etc/py-i2phosts/builder.conf', dest='config_file', parser.add_argument('-c', '--config', default='/etc/py-i2phosts/builder.conf', dest='config_file',
help='config file to use') help='config file to use')
parser.add_argument('-f', '--file', parser.add_argument('-f', '--file',
help='write hosts into specified file') help='write hosts into specified file')
parser.add_argument('-d', '--debug', action='store_true', parser.add_argument('-d', '--debug', action='store_true',
help='write debug messages to stdout') help='write debug messages to stdout')
args = parser.parse_args() args = parser.parse_args()
# read config # read config
spec = ''' spec = '''
hostsfile = string(default=None) hostsfile = string(default=None)
''' '''
spec = spec.split('\n') spec = spec.split('\n')
config = configobj.ConfigObj(args.config_file, configspec=spec, file_error=True) config = configobj.ConfigObj(args.config_file, configspec=spec, file_error=True)
if 'include' in config: if 'include' in config:
config_included = configobj.ConfigObj(config['include']) config_included = configobj.ConfigObj(config['include'])
config.merge(config_included) config.merge(config_included)
# django setup # django setup
DJANGO_SETTINGS_MODULE = 'pyi2phosts.settings' DJANGO_SETTINGS_MODULE = 'pyi2phosts.settings'
if 'DJANGO_PROJECT_PATH' in config: if 'DJANGO_PROJECT_PATH' in config:
DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH'] DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH']
else: else:
DJANGO_PROJECT_PATH = os.path.dirname(sys.argv[0]) + '/..' DJANGO_PROJECT_PATH = os.path.dirname(sys.argv[0]) + '/..'
sys.path.insert(1, DJANGO_PROJECT_PATH) sys.path.insert(1, DJANGO_PROJECT_PATH)
os.environ['DJANGO_SETTINGS_MODULE'] = DJANGO_SETTINGS_MODULE os.environ['DJANGO_SETTINGS_MODULE'] = DJANGO_SETTINGS_MODULE
from pyi2phosts.postkey.models import i2phost from pyi2phosts.postkey.models import i2phost
@ -43,12 +43,12 @@ validate_config(config)
# result hosts.txt # result hosts.txt
if args.file: if args.file:
hostsfile = args.file hostsfile = args.file
elif config['hostsfile'] != None: elif config['hostsfile'] != None:
hostsfile = config['hostsfile'] hostsfile = config['hostsfile']
else: else:
sys.stderr.write('Please specify "-f" or define "hostsfile" in config\n') sys.stderr.write('Please specify "-f" or define "hostsfile" in config\n')
sys.exit(1) sys.exit(1)
f = open(hostsfile, 'w') f = open(hostsfile, 'w')
# get activated hosts # get activated hosts
@ -57,5 +57,5 @@ qs = i2phost.objects.filter(activated=True)
l = qs.values('name', 'b64hash') l = qs.values('name', 'b64hash')
# write final hosts.txt-format file # write final hosts.txt-format file
for entry in l: for entry in l:
f.write(entry['name'] + '=' + entry['b64hash'] + '\n') f.write(entry['name'] + '=' + entry['b64hash'] + '\n')
f.close() f.close()

98
bin/py-i2phosts-checker

@ -10,34 +10,34 @@ import time
# parse command line options # parse command line options
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Hosts checker for py-i2phosts.', description='Hosts checker for py-i2phosts.',
epilog='Report bugs to http://zzz.i2p/topics/733') epilog='Report bugs to http://zzz.i2p/topics/733')
parser.add_argument('-d', '--debug', action='store_true', parser.add_argument('-d', '--debug', action='store_true',
help='set loglevel to debug and write messages to stdout'), help='set loglevel to debug and write messages to stdout'),
parser.add_argument('-v', '--verbose', action='store_true', parser.add_argument('-v', '--verbose', action='store_true',
help='set loglevel to info and write messages to stdout'), help='set loglevel to info and write messages to stdout'),
parser.add_argument('-c', '--config', default='/etc/py-i2phosts/checker.conf', dest='config_file', parser.add_argument('-c', '--config', default='/etc/py-i2phosts/checker.conf', dest='config_file',
help='config file to use') help='config file to use')
args = parser.parse_args() args = parser.parse_args()
# read config # read config
spec = ''' spec = '''
log_file = string(default='/var/log/py-i2phosts/master.log') log_file = string(default='/var/log/py-i2phosts/master.log')
log_level = option('debug', 'info', 'warning', 'error', 'critical', default='info') log_level = option('debug', 'info', 'warning', 'error', 'critical', default='info')
lookup_retries = integer(1, 20, default=2) lookup_retries = integer(1, 20, default=2)
''' '''
spec = spec.split('\n') spec = spec.split('\n')
config = configobj.ConfigObj(args.config_file, configspec=spec) config = configobj.ConfigObj(args.config_file, configspec=spec)
if 'include' in config: if 'include' in config:
config_included = configobj.ConfigObj(config['include']) config_included = configobj.ConfigObj(config['include'])
config.merge(config_included) config.merge(config_included)
# django setup # django setup
DJANGO_SETTINGS_MODULE = 'pyi2phosts.settings' DJANGO_SETTINGS_MODULE = 'pyi2phosts.settings'
if 'DJANGO_PROJECT_PATH' in config: if 'DJANGO_PROJECT_PATH' in config:
DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH'] DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH']
else: else:
DJANGO_PROJECT_PATH = os.path.dirname(sys.argv[0]) + '/..' DJANGO_PROJECT_PATH = os.path.dirname(sys.argv[0]) + '/..'
sys.path.insert(1, DJANGO_PROJECT_PATH) sys.path.insert(1, DJANGO_PROJECT_PATH)
os.environ['DJANGO_SETTINGS_MODULE'] = DJANGO_SETTINGS_MODULE os.environ['DJANGO_SETTINGS_MODULE'] = DJANGO_SETTINGS_MODULE
from pyi2phosts.postkey.models import i2phost from pyi2phosts.postkey.models import i2phost
@ -50,22 +50,22 @@ validate_config(config)
# configure logger # configure logger
if args.debug == True: if args.debug == True:
log_level = 'debug' log_level = 'debug'
log_file = None log_file = None
elif args.verbose == True: elif args.verbose == True:
log_level = 'info' log_level = 'info'
log_file = None log_file = None
else: else:
log_level = config['log_level'] log_level = config['log_level']
log_file = config['log_file'] log_file = config['log_file']
log = get_logger(filename=log_file, log_level=log_level) log = get_logger(filename=log_file, log_level=log_level)
# determine BOB interface address # determine BOB interface address
if 'bob_addr' in config: if 'bob_addr' in config:
bob_addr = config['bob_addr'] bob_addr = config['bob_addr']
else: else:
log.warning('BOB address isn\'t specified in config, falling back to localhost') log.warning('BOB address isn\'t specified in config, falling back to localhost')
bob_addr = '127.0.0.1:2827' bob_addr = '127.0.0.1:2827'
# split bob_addr to ip and port # split bob_addr to ip and port
bob_ip, bob_port = bob_addr.split(':') bob_ip, bob_port = bob_addr.split(':')
@ -73,37 +73,37 @@ bob_ip, bob_port = bob_addr.split(':')
# connect to BOB # connect to BOB
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try: try:
s.connect((bob_ip, int(bob_port))) s.connect((bob_ip, int(bob_port)))
# just receive BOB's greeting # just receive BOB's greeting
time.sleep(1) time.sleep(1)
data = s.recv(512) data = s.recv(512)
# make file object # make file object
f = s.makefile('r') f = s.makefile('r')
except socket.error, e: except socket.error, e:
log.error('failed to connect to BOB: %s', e) log.error('failed to connect to BOB: %s', e)
sys.exit(1) sys.exit(1)
all_hosts = i2phost.objects.all().order_by('-activated', '-last_seen') all_hosts = i2phost.objects.all().order_by('-activated', '-last_seen')
log.info('starting check') log.info('starting check')
for host in all_hosts: for host in all_hosts:
log.debug('%s: testing...', host.name) log.debug('%s: testing...', host.name)
# get b32 address from full dest key # get b32 address from full dest key
dest = host.b64hash dest = host.b64hash
b32dest = get_b32(dest) b32dest = get_b32(dest)
# do name lookup query with b32 address # do name lookup query with b32 address
# it success only if host is alive # it success only if host is alive
for i in range(config['lookup_retries']): for i in range(config['lookup_retries']):
s.send('lookup %s\n' % b32dest) s.send('lookup %s\n' % b32dest)
data = f.readline().rstrip('\n') data = f.readline().rstrip('\n')
if data == 'ERROR Address Not found.': if data == 'ERROR Address Not found.':
log.debug('%s: unable to resolve, try: %s', host.name, i) log.debug('%s: unable to resolve, try: %s', host.name, i)
elif data == 'OK ' + host.b64hash: elif data == 'OK ' + host.b64hash:
log.info('alive host: %s', host.name) log.info('alive host: %s', host.name)
# update lastseen timestamp # update lastseen timestamp
host.last_seen = datetime.datetime.utcnow() host.last_seen = datetime.datetime.utcnow()
host.save() host.save()
break break
else: else:
log.warning('unexpected reply: %s', data) log.warning('unexpected reply: %s', data)
s.close() s.close()
log.info('check finished') log.info('check finished')

166
bin/py-i2phosts-fetcher

@ -15,32 +15,32 @@ import socket
# parse command line options # parse command line options
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Hosts fetcher for py-i2phosts.', description='Hosts fetcher for py-i2phosts.',
epilog='Report bugs to http://zzz.i2p/topics/733') epilog='Report bugs to http://zzz.i2p/topics/733')
parser.add_argument('-d', '--debug', action='store_true', parser.add_argument('-d', '--debug', action='store_true',
help='write debug messages to stdout instead of log file'), help='write debug messages to stdout instead of log file'),
parser.add_argument('-c', '--config', default='/etc/py-i2phosts/fetcher.conf', dest='config_file', parser.add_argument('-c', '--config', default='/etc/py-i2phosts/fetcher.conf', dest='config_file',
help='config file to use') help='config file to use')
args = parser.parse_args() args = parser.parse_args()
# read config # read config
spec = ''' spec = '''
proxyurl = string(default='http://localhost:4444/') proxyurl = string(default='http://localhost:4444/')
log_file = string(default='/var/log/py-i2phosts/fetcher.log') log_file = string(default='/var/log/py-i2phosts/fetcher.log')
log_level = option('debug', 'info', 'warning', 'error', 'critical', default='info') log_level = option('debug', 'info', 'warning', 'error', 'critical', default='info')
''' '''
spec = spec.split('\n') spec = spec.split('\n')
config = configobj.ConfigObj(args.config_file, configspec=spec, file_error=True) config = configobj.ConfigObj(args.config_file, configspec=spec, file_error=True)
if 'include' in config: if 'include' in config:
config_included = configobj.ConfigObj(config['include']) config_included = configobj.ConfigObj(config['include'])
config.merge(config_included) config.merge(config_included)
# django setup # django setup
DJANGO_SETTINGS_MODULE = 'pyi2phosts.settings' DJANGO_SETTINGS_MODULE = 'pyi2phosts.settings'
if 'DJANGO_PROJECT_PATH' in config: if 'DJANGO_PROJECT_PATH' in config:
DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH'] DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH']
else: else:
DJANGO_PROJECT_PATH = os.path.dirname(sys.argv[0]) + '/..' DJANGO_PROJECT_PATH = os.path.dirname(sys.argv[0]) + '/..'
sys.path.insert(1, DJANGO_PROJECT_PATH) sys.path.insert(1, DJANGO_PROJECT_PATH)
os.environ['DJANGO_SETTINGS_MODULE'] = DJANGO_SETTINGS_MODULE os.environ['DJANGO_SETTINGS_MODULE'] = DJANGO_SETTINGS_MODULE
from pyi2phosts.lib.utils import get_logger from pyi2phosts.lib.utils import get_logger
@ -52,11 +52,11 @@ validate_config(config)
# configure logger # configure logger
if args.debug == True: if args.debug == True:
log_level = 'debug' log_level = 'debug'
log_file = None log_file = None
else: else:
log_level = config['log_level'] log_level = config['log_level']
log_file = config['log_file'] log_file = config['log_file']
log = get_logger(filename=log_file, log_level=log_level) log = get_logger(filename=log_file, log_level=log_level)
# we want open urls through proxy # we want open urls through proxy
@ -66,70 +66,70 @@ opener = urllib2.build_opener(proxy_handler)
all_sources = ExternalSource.objects.filter(active=True) all_sources = ExternalSource.objects.filter(active=True)
for source in all_sources: for source in all_sources:
log.debug('%s: starting work', source.name) log.debug('%s: starting work', source.name)
if source.last_modified: if source.last_modified:
last_modified = source.last_modified.strftime('%a, %d %b %Y %H:%M:%S GMT') last_modified = source.last_modified.strftime('%a, %d %b %Y %H:%M:%S GMT')
# prevent redownloading of hosts-file by passing If-Modified-Since http header # prevent redownloading of hosts-file by passing If-Modified-Since http header
opener.addheaders = [('If-Modified-Since', last_modified)] opener.addheaders = [('If-Modified-Since', last_modified)]
log.debug('%s: appending If-Modified-Since: %s', source.name, last_modified) log.debug('%s: appending If-Modified-Since: %s', source.name, last_modified)
if source.etag: if source.etag:
opener.addheaders = [('If-None-Match', source.etag)] opener.addheaders = [('If-None-Match', source.etag)]
log.debug('%s: appending If-None-Match: %s', source.name, source.etag) log.debug('%s: appending If-None-Match: %s', source.name, source.etag)
try: try:
log.debug('%s: sending GET...', source.name) log.debug('%s: sending GET...', source.name)
resp = opener.open(source.url, timeout=60) resp = opener.open(source.url, timeout=60)
except socket.timeout: except socket.timeout:
log.warning('%s: socket timeout', source.name) log.warning('%s: socket timeout', source.name)
continue continue
except urllib2.HTTPError, e: except urllib2.HTTPError, e:
if e.code == 304: if e.code == 304:
log.info('%s: not modified', source.name) log.info('%s: not modified', source.name)
source.last_success = datetime.datetime.utcnow() source.last_success = datetime.datetime.utcnow()
source.save() source.save()
else: else:
log.warning('%s: can\'t finish the request, error code: %s, reason: %s', source.name, e.code, e.reason) log.warning('%s: can\'t finish the request, error code: %s, reason: %s', source.name, e.code, e.reason)
continue continue
except urllib2.URLError, e: except urllib2.URLError, e:
log.warning('%s: failed to reach server, reason: %s', source.name, e.reason) log.warning('%s: failed to reach server, reason: %s', source.name, e.reason)
continue continue
# read data from remote and write it to local file # read data from remote and write it to local file
try: try:
log.debug('%s: reading response data', source.name) log.debug('%s: reading response data', source.name)
content = resp.read() content = resp.read()
except: except:
log.warning('%s: failed to read data', source.name) log.warning('%s: failed to read data', source.name)
continue continue
# save fetched content into temporary file # save fetched content into temporary file
fd, tmpfile = tempfile.mkstemp(text=True) fd, tmpfile = tempfile.mkstemp(text=True)
f = os.fdopen(fd, 'w') f = os.fdopen(fd, 'w')
f.write(content) f.write(content)
f.close() f.close()
# get last-modified info from header # get last-modified info from header
lm = resp.headers.get('Last-Modified') lm = resp.headers.get('Last-Modified')
if lm: if lm:
log.debug('%s: Last-Modified: %s', source.name, lm) log.debug('%s: Last-Modified: %s', source.name, lm)
source.last_modified = datetime.datetime.strptime(lm, '%a, %d %b %Y %H:%M:%S GMT') source.last_modified = datetime.datetime.strptime(lm, '%a, %d %b %Y %H:%M:%S GMT')
# get ETag # get ETag
etag = resp.headers.get('ETag') etag = resp.headers.get('ETag')
if etag: if etag:
log.debug('%s: ETag: %s', source.name, etag) log.debug('%s: ETag: %s', source.name, etag)
source.etag = etag source.etag = etag
# form command-line for invoke injector # form command-line for invoke injector
log.info('%s: adding hosts...', source.name) log.info('%s: adding hosts...', source.name)
sp_args = ['py-i2phosts-injector', '-s', '-f', tmpfile, '-d', sp_args = ['py-i2phosts-injector', '-s', '-f', tmpfile, '-d',
'Auto-added from ' + source.name] 'Auto-added from ' + source.name]
try: try:
p = subprocess.Popen(sp_args, shell=False, stdin=None, p = subprocess.Popen(sp_args, shell=False, stdin=None,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT) stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
except OSError, e: except OSError, e:
log.error('failed to exec py-i2phosts-injector: %s', e) log.error('failed to exec py-i2phosts-injector: %s', e)
if e.errno == errno.ENOENT: if e.errno == errno.ENOENT:
log.error('check your PATH environment variable') log.error('check your PATH environment variable')
sys.exit(1) sys.exit(1)
out = p.communicate()[0] out = p.communicate()[0]
os.remove(tmpfile) os.remove(tmpfile)
log.info('%s: injector output: \n%s', source.name, out) log.info('%s: injector output: \n%s', source.name, out)
# update last_success # update last_success
source.last_success = datetime.datetime.utcnow() source.last_success = datetime.datetime.utcnow()
log.debug('%s: updating last_success timestamp: %s', source.name, source.last_success) log.debug('%s: updating last_success timestamp: %s', source.name, source.last_success)
source.save() source.save()

108
bin/py-i2phosts-injector

@ -10,34 +10,34 @@ from django.core.exceptions import ValidationError
# parse command line options # parse command line options
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Hosts injector for py-i2phosts.', description='Hosts injector for py-i2phosts.',
epilog='Report bugs to http://zzz.i2p/topics/733') epilog='Report bugs to http://zzz.i2p/topics/733')
parser.add_argument('-c', '--config', default='/etc/py-i2phosts/injector.conf', dest='config_file', parser.add_argument('-c', '--config', default='/etc/py-i2phosts/injector.conf', dest='config_file',
help='config file to use') help='config file to use')
parser.add_argument('-f', '--file', dest='hostsfile', parser.add_argument('-f', '--file', dest='hostsfile',
help='hosts.txt for parsing') help='hosts.txt for parsing')
parser.add_argument('-d', '--description', default='Auto-added from external hosts.txt', parser.add_argument('-d', '--description', default='Auto-added from external hosts.txt',
help='provide custom description message') help='provide custom description message')
parser.add_argument('-a', '--approve', action='store_true', parser.add_argument('-a', '--approve', action='store_true',
help='add hosts as approved') help='add hosts as approved')
parser.add_argument('-s', '--supress', action='store_true', parser.add_argument('-s', '--supress', action='store_true',
help='supress warnings about already existed hostnames'), help='supress warnings about already existed hostnames'),
parser.add_argument('-q', '--quiet', action='store_true', parser.add_argument('-q', '--quiet', action='store_true',
help='be completely quiet, print only errors') help='be completely quiet, print only errors')
args = parser.parse_args() args = parser.parse_args()
# read config # read config
config = configobj.ConfigObj(args.config_file) config = configobj.ConfigObj(args.config_file)
if 'include' in config: if 'include' in config:
config_included = configobj.ConfigObj(config['include']) config_included = configobj.ConfigObj(config['include'])
config.merge(config_included) config.merge(config_included)
# django setup # django setup
DJANGO_SETTINGS_MODULE = 'pyi2phosts.settings' DJANGO_SETTINGS_MODULE = 'pyi2phosts.settings'
if 'DJANGO_PROJECT_PATH' in config: if 'DJANGO_PROJECT_PATH' in config:
DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH'] DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH']
else: else:
DJANGO_PROJECT_PATH = os.path.dirname(sys.argv[0]) + '/..' DJANGO_PROJECT_PATH = os.path.dirname(sys.argv[0]) + '/..'
sys.path.insert(1, DJANGO_PROJECT_PATH) sys.path.insert(1, DJANGO_PROJECT_PATH)
os.environ['DJANGO_SETTINGS_MODULE'] = DJANGO_SETTINGS_MODULE os.environ['DJANGO_SETTINGS_MODULE'] = DJANGO_SETTINGS_MODULE
from pyi2phosts.postkey.models import i2phost from pyi2phosts.postkey.models import i2phost
@ -46,57 +46,57 @@ from pyi2phosts.lib.validation import validate_b64hash
# determine approve hosts or not # determine approve hosts or not
if args.approve or config.as_bool('approve'): if args.approve or config.as_bool('approve'):
approved = True approved = True
else: else:
approved = False approved = False
# turn on output supressing if quiet # turn on output supressing if quiet
if args.quiet: if args.quiet:
args.supress = True args.supress = True
# determine what hosts.txt file we should parse # determine what hosts.txt file we should parse
if args.hostsfile: if args.hostsfile:
hostsfile = args.hostsfile hostsfile = args.hostsfile
else: else:
env = os.environ env = os.environ
if 'HOME' in env: if 'HOME' in env:
hostsfile = os.environ['HOME'] + '/.i2p/hosts.txt' hostsfile = os.environ['HOME'] + '/.i2p/hosts.txt'
else: else:
sys.stderr.write('unable to determine hosts file for parsing\n') sys.stderr.write('unable to determine hosts file for parsing\n')
sys.exit(1) sys.exit(1)
f = open(args.hostsfile, 'r') f = open(args.hostsfile, 'r')
for line in f: for line in f:
# ignore comments and empty lines # ignore comments and empty lines
if line.startswith('#') or line.isspace(): if line.startswith('#') or line.isspace():
continue continue
if line.find('=') == -1: if line.find('=') == -1:
sys.stdout.write('Invalid line: %s\n' % line) sys.stdout.write('Invalid line: %s\n' % line)
continue continue
# strip trailing '\n' # strip trailing '\n'
line = line.rstrip('\n') line = line.rstrip('\n')
entry = line.split('=', 1) entry = line.split('=', 1)
try: try:
hostname = validate_hostname(entry[0]) hostname = validate_hostname(entry[0])
base64 = validate_b64hash(entry[1], check_uniq=False) # don't require uniqueness base64 = validate_b64hash(entry[1], check_uniq=False) # don't require uniqueness
except ValidationError, e: except ValidationError, e:
sys.stdout.write('validation error: %s: %s\n\n' % (e, line)) sys.stdout.write('validation error: %s: %s\n\n' % (e, line))
else: else:
# Check for already existed hosts in database to avoid unneeded INSERTs # Check for already existed hosts in database to avoid unneeded INSERTs
# beacuse they will fail anyway. # beacuse they will fail anyway.
try: try:
h = i2phost.objects.get(name=hostname) h = i2phost.objects.get(name=hostname)
except i2phost.DoesNotExist: except i2phost.DoesNotExist:
if not args.quiet: if not args.quiet:
sys.stdout.write('Adding %s\n' % hostname) sys.stdout.write('Adding %s\n' % hostname)
host = i2phost(name=hostname, b64hash=base64, host = i2phost(name=hostname, b64hash=base64,
description=args.description, description=args.description,
date_added=datetime.datetime.utcnow(), date_added=datetime.datetime.utcnow(),
activated=False, external=True, approved=approved) activated=False, external=True, approved=approved)
host.save() host.save()
else: else:
if not args.supress: if not args.supress:
sys.stdout.write('Host %s already exists\n' % hostname) sys.stdout.write('Host %s already exists\n' % hostname)
if h.b64hash != base64: if h.b64hash != base64:
sys.stdout.write('Key conflict for host: %s\n' % hostname) sys.stdout.write('Key conflict for host: %s\n' % hostname)
f.close() f.close()

152
bin/py-i2phosts-maint

@ -10,37 +10,37 @@ import configobj
# parse command line options # parse command line options
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Hosts maintainer for py-i2phosts.', description='Hosts maintainer for py-i2phosts.',
epilog='Report bugs to http://zzz.i2p/topics/733') epilog='Report bugs to http://zzz.i2p/topics/733')
parser.add_argument('-d', '--debug', action='store_true', parser.add_argument('-d', '--debug', action='store_true',
help='write debug messages to stdout') help='write debug messages to stdout')
parser.add_argument('-c', '--config', default='/etc/py-i2phosts/maintainer.conf', dest='config_file', parser.add_argument('-c', '--config', default='/etc/py-i2phosts/maintainer.conf', dest='config_file',
help='config file to use') help='config file to use')
args = parser.parse_args() args = parser.parse_args()
# read config # read config
spec = ''' spec = '''
log_file = string(default='/var/log/py-i2phosts/maintainer.log') log_file = string(default='/var/log/py-i2phosts/maintainer.log')
log_level = option('debug', 'info', 'warning', 'error', 'critical', default='info') log_level = option('debug', 'info', 'warning', 'error', 'critical', default='info')
external_inactive_max = integer(default=365) external_inactive_max = integer(default=365)
internal_inactive_max = integer(default=14) internal_inactive_max = integer(default=14)
external_expires = integer(default=30) external_expires = integer(default=30)
internal_expires = integer(default=30) internal_expires = integer(default=30)
activate_min_delay = integer(default=3) activate_min_delay = integer(default=3)
keep_expired = integer(default=730) keep_expired = integer(default=730)
''' '''
spec = spec.split('\n') spec = spec.split('\n')
config = configobj.ConfigObj(args.config_file, configspec=spec) config = configobj.ConfigObj(args.config_file, configspec=spec)
if 'include' in config: if 'include' in config:
config_included = configobj.ConfigObj(config['include']) config_included = configobj.ConfigObj(config['include'])
config.merge(config_included) config.merge(config_included)
# django setup # django setup
DJANGO_SETTINGS_MODULE = 'pyi2phosts.settings' DJANGO_SETTINGS_MODULE = 'pyi2phosts.settings'
if 'DJANGO_PROJECT_PATH' in config: if 'DJANGO_PROJECT_PATH' in config:
DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH'] DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH']
else: else:
DJANGO_PROJECT_PATH = os.path.dirname(sys.argv[0]) + '/..' DJANGO_PROJECT_PATH = os.path.dirname(sys.argv[0]) + '/..'
sys.path.insert(1, DJANGO_PROJECT_PATH) sys.path.insert(1, DJANGO_PROJECT_PATH)
os.environ['DJANGO_SETTINGS_MODULE'] = DJANGO_SETTINGS_MODULE os.environ['DJANGO_SETTINGS_MODULE'] = DJANGO_SETTINGS_MODULE
from pyi2phosts.postkey.models import i2phost from pyi2phosts.postkey.models import i2phost
@ -52,68 +52,68 @@ validate_config(config)
# configure logger # configure logger
if args.debug == True: if args.debug == True:
log_level = 'debug' log_level = 'debug'
log_file = None log_file = None
else: else:
log_level = config['log_level'] log_level = config['log_level']
log_file = config['log_file'] log_file = config['log_file']
log = get_logger(filename=log_file, log_level=log_level) log = get_logger(filename=log_file, log_level=log_level)
all_hosts = i2phost.objects.all() all_hosts = i2phost.objects.all()
log.info('starting maintenance') log.info('starting maintenance')
for host in all_hosts: for host in all_hosts:
# how long host was added # how long host was added
dl = datetime.datetime.utcnow() - host.date_added dl = datetime.datetime.utcnow() - host.date_added
if host.last_seen == None: if host.last_seen == None:
# delete external hosts which we never seen after X days of inactivity # delete external hosts which we never seen after X days of inactivity
if host.external == True: if host.external == True:
if dl > datetime.timedelta(days=config['external_inactive_max']): if dl > datetime.timedelta(days=config['external_inactive_max']):
log.info('deleting %s, reason: external host, never seen for %s days', log.info('deleting %s, reason: external host, never seen for %s days',
host.name, config['external_inactive_max']) host.name, config['external_inactive_max'])
host.delete() host.delete()
continue continue
# delete hosts added by us and never seen after X days of inactivity # delete hosts added by us and never seen after X days of inactivity
else: else:
if dl > datetime.timedelta(days=config['internal_inactive_max']): if dl > datetime.timedelta(days=config['internal_inactive_max']):
log.info('deleting %s, reason: internal host, never seen for %s days', log.info('deleting %s, reason: internal host, never seen for %s days',
host.name, config['internal_inactive_max']) host.name, config['internal_inactive_max'])
host.delete() host.delete()
continue continue
else: else:
# configure registration period for hosts # configure registration period for hosts
if host.external == True: if host.external == True:
timedelta = datetime.timedelta(days=config['external_expires']) timedelta = datetime.timedelta(days=config['external_expires'])
else: else:
timedelta = datetime.timedelta(days=config['internal_expires']) timedelta = datetime.timedelta(days=config['internal_expires'])
# get current host expiration date from database # get current host expiration date from database
if host.expires == None: if host.expires == None:
# workaround for situation when we updating expires first time # workaround for situation when we updating expires first time
expires_current = datetime.datetime.utcnow().date() expires_current = datetime.datetime.utcnow().date()
else: else:
expires_current = host.expires expires_current = host.expires
# calculate new expiration date # calculate new expiration date
expires_new = host.last_seen + timedelta expires_new = host.last_seen + timedelta
expires_new = expires_new.date() expires_new = expires_new.date()
# update expiration date only if changed # update expiration date only if changed
if expires_new > expires_current: if expires_new > expires_current:
log.debug('updating expires for %s', host.name) log.debug('updating expires for %s', host.name)
host.expires = expires_new host.expires = expires_new
# deactivate if expired # deactivate if expired
min_dl = datetime.timedelta(days=config['activate_min_delay']) min_dl = datetime.timedelta(days=config['activate_min_delay'])
if host.expires < datetime.datetime.utcnow().date(): if host.expires < datetime.datetime.utcnow().date():
if host.activated == True: if host.activated == True:
log.info('deactivating %s, reason: expired', host.name) log.info('deactivating %s, reason: expired', host.name)
host.activated = False host.activated = False
# if not expired and added more than X days ago and approved then activate # if not expired and added more than X days ago and approved then activate
elif dl > min_dl and host.activated == False and host.approved == True: elif dl > min_dl and host.activated == False and host.approved == True:
log.info('activating %s, reason: host up and added more than %s days ago', log.info('activating %s, reason: host up and added more than %s days ago',
host.name, config['activate_min_delay']) host.name, config['activate_min_delay'])
host.activated = True host.activated = True
# if expired X days ago then delete # if expired X days ago then delete
dl_e = datetime.datetime.utcnow().date() - host.expires dl_e = datetime.datetime.utcnow().date() - host.expires
if dl_e > datetime.timedelta(days=config['keep_expired']): if dl_e > datetime.timedelta(days=config['keep_expired']):
log.info('deleting %s, reason: expired %s days ago', log.info('deleting %s, reason: expired %s days ago',
host.name, config['keep_expired']) host.name, config['keep_expired'])
host.delete() host.delete()
continue continue
host.save() host.save()

200
bin/py-i2phosts-master

@ -13,119 +13,125 @@ import threading
import daemon import daemon
# workaround for python-daemon >= 1.6 # workaround for python-daemon >= 1.6
try: try:
<<<<<<< HEAD
import daemon.pidlockfile as pidfile import daemon.pidlockfile as pidfile
except ImportError: except ImportError:
import daemon.pidfile as pidfile import daemon.pidfile as pidfile
=======
import daemon.pidlockfile as pidfile
except ImportError:
import daemon.pidfile as pidfile
>>>>>>> 923b94f... Convert tabs to spaces
class Thread(threading.Thread): class Thread(threading.Thread):
def __init__(self): def __init__(self):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.setDaemon(True) self.setDaemon(True)
class FetcherThread(Thread): class FetcherThread(Thread):
""" Run py-i2phosts-fetcher periodically """ """ Run py-i2phosts-fetcher periodically """
def run(self): def run(self):
while True: while True:
run_prog('py-i2phosts-fetcher') run_prog('py-i2phosts-fetcher')
time.sleep(float(config['fetch_interval'])) time.sleep(float(config['fetch_interval']))
class CheckerThread(Thread): class CheckerThread(Thread):
""" Run py-i2phosts-checker, py-i2phosts-maint, py-i2phosts-builder periodically """ """ Run py-i2phosts-checker, py-i2phosts-maint, py-i2phosts-builder periodically """
def run(self): def run(self):
while True: while True:
run_prog('py-i2phosts-checker') run_prog('py-i2phosts-checker')
run_prog('py-i2phosts-maint') run_prog('py-i2phosts-maint')
run_prog('py-i2phosts-builder') run_prog('py-i2phosts-builder')
time.sleep(float(config['check_interval'])) time.sleep(float(config['check_interval']))
def run_prog(prog): def run_prog(prog):
try: try:
log.info('starting: %s', prog) log.info('starting: %s', prog)
sp_args = [prog] sp_args = [prog]
if args.debug or args.verbose: if args.debug or args.verbose:
sp_args.append('-d') sp_args.append('-d')
p = subprocess.Popen(sp_args, shell=False) p = subprocess.Popen(sp_args, shell=False)
except OSError, e: except OSError, e:
log.error('failed to exec %s: %s', prog, e) log.error('failed to exec %s: %s', prog, e)
if e.errno == errno.ENOENT: if e.errno == errno.ENOENT:
log.error(' maybe it isn\'t in PATH?') log.error(' maybe it isn\'t in PATH?')
else: else:
p.wait() p.wait()
log.info('finished: %s', prog) log.info('finished: %s', prog)
def main(): def main():
def run_fetcher(): def run_fetcher():
fetcher = FetcherThread() fetcher = FetcherThread()
fetcher.start() fetcher.start()
return fetcher return fetcher
def run_checker(): def run_checker():
checker = CheckerThread() checker = CheckerThread()
checker.start() checker.start()
return checker return checker
# if we're just started, wait while fetcher get some new hosts # if we're just started, wait while fetcher get some new hosts
fetcher = run_fetcher() fetcher = run_fetcher()
log.debug('just started, delaying checker run for 300 secs') log.debug('just started, delaying checker run for 300 secs')
fetcher.join(300) # wait for 5 mins fetcher.join(300) # wait for 5 mins
# start checker and other # start checker and other
checker = run_checker() checker = run_checker()
while True: while True:
log.debug('checking fetcher and checker threads status') log.debug('checking fetcher and checker threads status')
if fetcher.isAlive() == False: if fetcher.isAlive() == False:
log.warning('fetcher thread is dead, respawning...') log.warning('fetcher thread is dead, respawning...')
fetcher = run_fetcher() fetcher = run_fetcher()
else: else:
log.debug('fetcher thread: alive') log.debug('fetcher thread: alive')
if checker.isAlive() == False: if checker.isAlive() == False:
log.warning('checker thread is dead, respawning...') log.warning('checker thread is dead, respawning...')
checker = run_checker() checker = run_checker()
else: else:
log.debug('checker thread: alive') log.debug('checker thread: alive')
# do check every 30 mins # do check every 30 mins
time.sleep(1800) time.sleep(1800)
# parse command line options # parse command line options
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Master daemon for py-i2phosts.', description='Master daemon for py-i2phosts.',
epilog='Report bugs to http://zzz.i2p/topics/733') epilog='Report bugs to http://zzz.i2p/topics/733')
parser.add_argument('-d', '--debug', action='store_true', parser.add_argument('-d', '--debug', action='store_true',
help='run in debug mode without detaching from terminal'), help='run in debug mode without detaching from terminal'),
parser.add_argument('-v', '--verbose', action='store_true', parser.add_argument('-v', '--verbose', action='store_true',
help='run in verbose mode without detaching from terminal'), help='run in verbose mode without detaching from terminal'),
parser.add_argument('-c', '--config', default='/etc/py-i2phosts/master.conf', dest='config_file', parser.add_argument('-c', '--config', default='/etc/py-i2phosts/master.conf', dest='config_file',
help='config file to use') help='config file to use')
args = parser.parse_args() args = parser.parse_args()
# read and validate config # read and validate config
spec = ''' spec = '''
log_file = string(default='/var/log/py-i2phosts/master.log') log_file = string(default='/var/log/py-i2phosts/master.log')
log_level = option('debug', 'info', 'warning', 'error', 'critical', default='info') log_level = option('debug', 'info', 'warning', 'error', 'critical', default='info')
pid_file = string(default='/var/run/py-i2phosts/master.pid') pid_file = string(default='/var/run/py-i2phosts/master.pid')
runas = string(default='_pyi2phosts') runas = string(default='_pyi2phosts')
check_interval = integer(default=43200) check_interval = integer(default=43200)
fetch_interval = integer(default=1800) fetch_interval = integer(default=1800)
''' '''
spec = spec.split('\n') spec = spec.split('\n')
config = configobj.ConfigObj(args.config_file, configspec=spec) config = configobj.ConfigObj(args.config_file, configspec=spec)
if 'include' in config: if 'include' in config:
config_included = configobj.ConfigObj(config['include']) config_included = configobj.ConfigObj(config['include'])
config.merge(config_included) config.merge(config_included)
# django setup # django setup
DJANGO_SETTINGS_MODULE = 'pyi2phosts.settings' DJANGO_SETTINGS_MODULE = 'pyi2phosts.settings'
if 'DJANGO_PROJECT_PATH' in config: if 'DJANGO_PROJECT_PATH' in config:
DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH'] DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH']
else: else:
DJANGO_PROJECT_PATH = os.path.dirname(sys.argv[0]) + '/..' DJANGO_PROJECT_PATH = os.path.dirname(sys.argv[0]) + '/..'
sys.path.insert(1, DJANGO_PROJECT_PATH) sys.path.insert(1, DJANGO_PROJECT_PATH)
os.environ['DJANGO_SETTINGS_MODULE'] = DJANGO_SETTINGS_MODULE os.environ['DJANGO_SETTINGS_MODULE'] = DJANGO_SETTINGS_MODULE
from pyi2phosts.lib.utils import get_logger from pyi2phosts.lib.utils import get_logger
@ -136,32 +142,32 @@ validate_config(config)
# configure logger # configure logger
if args.debug == True: if args.debug == True:
log_level = 'debug' log_level = 'debug'
log_file = None log_file = None
elif args.verbose == True: elif args.verbose == True:
log_level = 'info' log_level = 'info'
log_file = None log_file = None
else: else:
log_level = config['log_level'] log_level = config['log_level']
log_file = config['log_file'] log_file = config['log_file']
if not args.debug and not args.verbose: if not args.debug and not args.verbose:
# get pid object for daemon # get pid object for daemon
pid = pidfile.TimeoutPIDLockFile(config['pid_file'], 10) pid = pidfile.TimeoutPIDLockFile(config['pid_file'], 10)
# create daemon context # create daemon context
d = daemon.DaemonContext(pidfile=pid, umask=077) d = daemon.DaemonContext(pidfile=pid, umask=077)
# write stderr to logfile # FIXME: and how we will deal with log rotation? # write stderr to logfile # FIXME: and how we will deal with log rotation?
logfile = open(config['log_file'], 'a') logfile = open(config['log_file'], 'a')
d.stderr = logfile d.stderr = logfile
d.stdout = logfile d.stdout = logfile
# drop privileges when started as root # drop privileges when started as root
if os.getuid() == 0: if os.getuid() == 0:
runas = config['runas'] runas = config['runas']
pw_entry = pwd.getpwnam(runas) pw_entry = pwd.getpwnam(runas)
d.uid = pw_entry[2] d.uid = pw_entry[2]
d.gid = pw_entry[3] d.gid = pw_entry[3]
os.chown(config['log_file'], d.uid, d.gid) os.chown(config['log_file'], d.uid, d.gid)
d.open() # become daemon d.open() # become daemon
log = get_logger(filename=log_file, log_level=log_level) log = get_logger(filename=log_file, log_level=log_level)
log.info('started') log.info('started')
main() main()

2
pyi2phosts/api/urls.py

@ -1,5 +1,5 @@
from django.conf.urls import * from django.conf.urls import *
urlpatterns = patterns('pyi2phosts.api.views', urlpatterns = patterns('pyi2phosts.api.views',
url(r'^all/$', 'all'), url(r'^all/$', 'all'),
) )

16
pyi2phosts/api/views.py

@ -6,11 +6,11 @@ from pyi2phosts.postkey.models import i2phost
from pyi2phosts.lib.utils import get_b32 from pyi2phosts.lib.utils import get_b32
def all(request): def all(request):
"""Return all hosts in { "b32": "last seen timestamp" } form. Implemented by zzz request. """ """Return all hosts in { "b32": "last seen timestamp" } form. Implemented by zzz request. """
# all hosts seen at least once # all hosts seen at least once
queryset = i2phost.objects.exclude(last_seen=None) queryset = i2phost.objects.exclude(last_seen=None)
json_dict = {} json_dict = {}
for host in queryset: for host in queryset:
# pass last_seen to json in unixtime # pass last_seen to json in unixtime
json_dict[get_b32(host.b64hash)] = host.last_seen.strftime("%s") json_dict[get_b32(host.b64hash)] = host.last_seen.strftime("%s")
return HttpResponse(json.dumps(json_dict), mimetype="application/json") return HttpResponse(json.dumps(json_dict), mimetype="application/json")

4
pyi2phosts/extsources/admin.py

@ -4,7 +4,7 @@ from pyi2phosts.extsources.models import ExternalSource
class ExternalSourceAdmin(admin.ModelAdmin): class ExternalSourceAdmin(admin.ModelAdmin):
list_display = ('name', 'url', 'description', 'last_success', 'last_modified', 'etag', 'active') list_display = ('name', 'url', 'description', 'last_success', 'last_modified', 'etag', 'active')
list_editable = ['active'] list_editable = ['active']
admin.site.register(ExternalSource, ExternalSourceAdmin) admin.site.register(ExternalSource, ExternalSourceAdmin)

18
pyi2phosts/extsources/models.py

@ -3,14 +3,14 @@ from django.db import models
from pyi2phosts.lib.validation import validate_i2purl from pyi2phosts.lib.validation import validate_i2purl
class ExternalSource(models.Model): class ExternalSource(models.Model):
name = models.CharField(max_length=128, unique=True) name = models.CharField(max_length=128, unique=True)
url = models.CharField(max_length=256, validators=[validate_i2purl]) url = models.CharField(max_length=256, validators=[validate_i2purl])
description = models.CharField(max_length=512, blank=True) description = models.CharField(max_length=512, blank=True)
last_modified = models.DateTimeField(null=True, blank=True) last_modified = models.DateTimeField(null=True, blank=True)
last_success = models.DateTimeField(null=True, blank=True) last_success = models.DateTimeField(null=True, blank=True)
etag = models.CharField(max_length=256, blank=True) etag = models.CharField(max_length=256, blank=True)
active = models.BooleanField(default=True) active = models.BooleanField(default=True)
def __unicode__(self): def __unicode__(self):
return self.name return self.name

4
pyi2phosts/jump/urls.py

@ -1,7 +1,7 @@
from django.conf.urls import * from django.conf.urls import *
urlpatterns = patterns('pyi2phosts.jump.views', urlpatterns = patterns('pyi2phosts.jump.views',
(r'^([^$/]+)', 'jumper'), (r'^([^$/]+)', 'jumper'),
(r'', 'index'), (r'', 'index'),
) )

80
pyi2phosts/jump/views.py

@ -10,45 +10,45 @@ from pyi2phosts.postkey.models import i2phost
from pyi2phosts.lib.validation import validate_hostname from pyi2phosts.lib.validation import validate_hostname
def jumper(request, host): def jumper(request, host):
"""Actually do jumps.""" """Actually do jumps."""
try: try:
hostname = validate_hostname(host) hostname = validate_hostname(host)
except ValidationError, e: except ValidationError, e:
return render_to_response('jump-error.html', { return render_to_response('jump-error.html', {
'title': settings.SITE_NAME, 'title': settings.SITE_NAME,
'error': e, 'error': e,
}, context_instance=RequestContext(request)) }, context_instance=RequestContext(request))
try: try:
h = i2phost.objects.get(name=hostname) h = i2phost.objects.get(name=hostname)
except i2phost.DoesNotExist: except i2phost.DoesNotExist:
return render_to_response('jump-unknown.html', { return render_to_response('jump-unknown.html', {
'title': settings.SITE_NAME, 'title': settings.SITE_NAME,
}, context_instance=RequestContext(request)) }, context_instance=RequestContext(request))
if h.activated == True: if h.activated == True:
key = h.b64hash key = h.b64hash
else: else:
return redirect('/search/?q=' + hostname) return redirect('/search/?q=' + hostname)
# begin forming url # begin forming url
url = 'http://' + hostname url = 'http://' + hostname
# get params from requst string, e.g. from 'example.i2p/smth/1?a=b&c=d' get 'smth/1?a=b&c=d' # get params from requst string, e.g. from 'example.i2p/smth/1?a=b&c=d' get 'smth/1?a=b&c=d'
pattern = host + r'/(.+)' pattern = host + r'/(.+)'
m = re.search(pattern, request.get_full_path()) m = re.search(pattern, request.get_full_path())
if m: if m:
params = m.group(1) params = m.group(1)
url += '/' + params url += '/' + params
# determine how we should pass i2paddresshelper # determine how we should pass i2paddresshelper
# http://zzz.i2p/oldnews.html#jump # http://zzz.i2p/oldnews.html#jump
if params.find('?') == -1: if params.find('?') == -1:
suffix = '?' suffix = '?'
else: else:
suffix = '&' suffix = '&'
url += suffix + 'i2paddresshelper=' + key url += suffix + 'i2paddresshelper=' + key
else: else:
url += '/?i2paddresshelper=' + key url += '/?i2paddresshelper=' + key
return render_to_response('jump.html', { return render_to_response('jump.html', {
'title': settings.SITE_NAME, 'title': settings.SITE_NAME,
'url': url, 'url': url,
}, context_instance=RequestContext(request)) }, context_instance=RequestContext(request))
def index(request): def index(request):
return redirect('/') return redirect('/')

4
pyi2phosts/latest/urls.py

@ -3,7 +3,7 @@ from pyi2phosts.lib.rss import LatestHostsFeed
from pyi2phosts.latest.views import LatestHostsListsView from pyi2phosts.latest.views import LatestHostsListsView
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^$', LatestHostsListsView.as_view(), name='latest'), url(r'^$', LatestHostsListsView.as_view(), name='latest'),
url(r'^rss/$', LatestHostsFeed(), name='latest-rss'), url(r'^rss/$', LatestHostsFeed(), name='latest-rss'),
) )

36
pyi2phosts/latest/views.py

@ -6,25 +6,25 @@ from pyi2phosts.postkey.models import i2phost
from pyi2phosts.lib.generic import LocalObjectList from pyi2phosts.lib.generic import LocalObjectList
def get_latest(): def get_latest():
now_date = datetime.datetime.utcnow() now_date = datetime.datetime.utcnow()
start_date = now_date - datetime.timedelta(days=settings.LATEST_DAY_COUNT) start_date = now_date - datetime.timedelta(days=settings.LATEST_DAY_COUNT)
qs = i2phost.objects.filter(activated=True, qs = i2phost.objects.filter(activated=True,
date_added__range=(start_date, now_date)).order_by("-date_added")[:settings.LATEST_HOSTS_COUNT] date_added__range=(start_date, now_date)).order_by("-date_added")[:settings.LATEST_HOSTS_COUNT]
return qs return qs
class LatestHostsListsView(LocalObjectList): class LatestHostsListsView(LocalObjectList):
""" Renders list of latest active hosts added """ """ Renders list of latest active hosts added """
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(LatestHostsListsView, self).get_context_data(**kwargs) context = super(LatestHostsListsView, self).get_context_data(**kwargs)
context.update({ context.update({
'title': settings.SITE_NAME, 'title': settings.SITE_NAME,
'day_count': settings.LATEST_DAY_COUNT, 'day_count': settings.LATEST_DAY_COUNT,
'hosts_count': settings.LATEST_HOSTS_COUNT 'hosts_count': settings.LATEST_HOSTS_COUNT
}) })
return context return context
queryset = get_latest() queryset = get_latest()
template_name = 'latest.html' template_name = 'latest.html'
context_object_name = 'host_list' context_object_name = 'host_list'
paginate_by = 40 paginate_by = 40

80
pyi2phosts/lib/generic.py

@ -9,58 +9,58 @@ from pyi2phosts.postkey.models import i2phost
from pyi2phosts.postkey.templatetags import paginator from pyi2phosts.postkey.templatetags import paginator
class LocalTemplateView(TemplateView): class LocalTemplateView(TemplateView):
""" Renders some template with passing some local config variables """ """ Renders some template with passing some local config variables """
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(LocalTemplateView, self).get_context_data(**kwargs) context = super(LocalTemplateView, self).get_context_data(**kwargs)
context.update({ context.update({
'title': settings.SITE_NAME, 'title': settings.SITE_NAME,
'domain': settings.DOMAIN, 'domain': settings.DOMAIN,
'b64': settings.MY_B64, 'b64': settings.MY_B64,
'b32': get_b32(settings.MY_B64) 'b32': get_b32(settings.MY_B64)
}) })
return context return context
class LocalObjectList(ListView): class LocalObjectList(ListView):
""" Renders some list of objects """ """ Renders some list of objects """
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(LocalObjectList, self).get_context_data(**kwargs) context = super(LocalObjectList, self).get_context_data(**kwargs)
context.update({ context.update({
'title': settings.SITE_NAME, 'title': settings.SITE_NAME,
}) })
return context return context
class FaqView(LocalObjectList): class FaqView(LocalObjectList):
""" Renders list of external sources for hosts.txt """ """ Renders list of external sources for hosts.txt """
queryset = ExternalSource.objects.filter(active=True) queryset = ExternalSource.objects.filter(active=True)
template_name = 'faq.html' template_name = 'faq.html'
context_object_name = 'sources_list' context_object_name = 'sources_list'
class HostsListsView(LocalObjectList): class HostsListsView(LocalObjectList):
""" Renders list of active hosts """ """ Renders list of active hosts """
def get_queryset(self): def get_queryset(self):
allowed_orders = ['name', 'last_seen', 'date_added'] allowed_orders = ['name', 'last_seen', 'date_added']
self.order_by = self.request.GET.get('order', 'name') self.order_by = self.request.GET.get('order', 'name')
if self.order_by not in allowed_orders: if self.order_by not in allowed_orders:
self.order_by = 'name' self.order_by = 'name'
qs = super(HostsListsView, self).get_queryset() qs = super(HostsListsView, self).get_queryset()
return qs.order_by(self.order_by) return qs.order_by(self.order_by)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
""" we should pass order_by to template to not lose it while paginating """ """ we should pass order_by to template to not lose it while paginating """
context = super(LocalObjectList, self).get_context_data(**kwargs) context = super(LocalObjectList, self).get_context_data(**kwargs)
context.update({ context.update({
'order': self.order_by, 'order': self.order_by,
}) })
return context return context
queryset = i2phost.objects.filter(activated=True) queryset = i2phost.objects.filter(activated=True)
template_name = 'browse.html' template_name = 'browse.html'
context_object_name = 'host_list' context_object_name = 'host_list'
paginate_by = 40 paginate_by = 40

40
pyi2phosts/lib/rss.py

@ -5,33 +5,33 @@ from pyi2phosts.postkey.models import i2phost
from pyi2phosts.latest.views import get_latest from pyi2phosts.latest.views import get_latest
class AliveHostsFeed(Feed): class AliveHostsFeed(Feed):
""" Generate RSS feed with all alive hosts """ """ Generate RSS feed with all alive hosts """
title = settings.DOMAIN + ' alive hosts' title = settings.DOMAIN + ' alive hosts'
# FIXME: make this URL more dynamic # FIXME: make this URL more dynamic
link = 'http://' + settings.DOMAIN + '/browse/' link = 'http://' + settings.DOMAIN + '/browse/'
description = 'All known active hosts inside I2P' description = 'All known active hosts inside I2P'
def items(self): def items(self):
return i2phost.objects.filter(activated=True).order_by('name') return i2phost.objects.filter(activated=True).order_by('name')
def item_title(self, item): def item_title(self, item):
return item.name return item.name
def item_link(self, item): def item_link(self, item):
return 'http://' + item.name + '/?i2paddresshelper=' + item.b64hash return 'http://' + item.name + '/?i2paddresshelper=' + item.b64hash
def item_description(self, item): def item_description(self, item):
return item.description return item.description
class LatestHostsFeed(AliveHostsFeed): class LatestHostsFeed(AliveHostsFeed):
""" Generate RSS feed with freshly added hosts """ """ Generate RSS feed with freshly added hosts """
title = settings.DOMAIN + ' latest hosts' title = settings.DOMAIN + ' latest hosts'
# FIXME: make this URL more dynamic # FIXME: make this URL more dynamic
link = 'http://' + settings.DOMAIN + '/latest/' link = 'http://' + settings.DOMAIN + '/latest/'
description = 'Freshly added hosts' description = 'Freshly added hosts'
def items(self): def items(self):
return get_latest() return get_latest()

92
pyi2phosts/lib/utils.py

@ -8,59 +8,59 @@ import base64
from logging import handlers from logging import handlers
def get_logger(filename=None, log_level='debug'): def get_logger(filename=None, log_level='debug'):
""" Prepare logger instance for our scripts """ """ Prepare logger instance for our scripts """
# workaround for django # workaround for django
if hasattr(logging, "web_logger"): if hasattr(logging, "web_logger"):
return logging.web_logger return logging.web_logger
LEVELS = { LEVELS = {
'debug': logging.DEBUG, 'debug': logging.DEBUG,
'info': logging.INFO, 'info': logging.INFO,
'warning': logging.WARNING, 'warning': logging.WARNING,
'error': logging.ERROR, 'error': logging.ERROR,
'critical': logging.CRITICAL 'critical': logging.CRITICAL
} }
level = LEVELS.get(log_level, logging.NOTSET) level = LEVELS.get(log_level, logging.NOTSET)
format = '%(asctime)s %(module)s:%(lineno)d[%(process)d] %(levelname)s: %(message)s' format = '%(asctime)s %(module)s:%(lineno)d[%(process)d] %(levelname)s: %(message)s'
formatter = logging.Formatter(format) formatter = logging.Formatter(format)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logger.setLevel(level) logger.setLevel(level)
if filename: if filename:
handler = logging.handlers.WatchedFileHandler(filename) handler = logging.handlers.WatchedFileHandler(filename)
else: else:
handler = logging.StreamHandler() handler = logging.StreamHandler()
handler.setFormatter(formatter) handler.setFormatter(formatter)
logger.addHandler(handler) logger.addHandler(handler)
# workaround for django # workaround for django
logging.web_logger = logger logging.web_logger = logger
return logger return logger
def validate_config(config): def validate_config(config):
""" Validate configobj config """ """ Validate configobj config """
validator = validate.Validator() validator = validate.Validator()
results = config.validate(validator) results = config.validate(validator)
if results != True: if results != True:
for (section_list, key, _) in configobj.flatten_errors(config, results): for (section_list, key, _) in configobj.flatten_errors(config, results):
if key is not None: if key is not None:
sys.stderr.write('The "%s" key in the section "%s" failed validation' % sys.stderr.write('The "%s" key in the section "%s" failed validation' %
(key, ', '.join(section_list))) (key, ', '.join(section_list)))
else: else:
sys.stderr.write('The following section was missing:%s ' % sys.stderr.write('The following section was missing:%s ' %
', '.join(section_list)) ', '.join(section_list))
sys.exit(1) sys.exit(1)
def get_b32(dest): def get_b32(dest):
""" Calculate base32 hash from base64 """ """ Calculate base32 hash from base64 """
try: try:
raw_key = base64.b64decode(dest.encode('utf-8'), '-~') raw_key = base64.b64decode(dest.encode('utf-8'), '-~')
except TypeError: except TypeError:
return 'corrupted_base64_hash' return 'corrupted_base64_hash'
else: else:
hash = hashlib.sha256(raw_key) hash = hashlib.sha256(raw_key)
b32 = base64.b32encode(hash.digest()).lower().replace('=', '')+'.b32.i2p' b32 = base64.b32encode(hash.digest()).lower().replace('=', '')+'.b32.i2p'
return b32 return b32

212
pyi2phosts/lib/validation.py

@ -9,116 +9,116 @@ from pyi2phosts.postkey.models import i2phost
def validate_hostname(data): def validate_hostname(data):
""" """
Here we do hostname validation as described in Here we do hostname validation as described in
http://www.i2p2.i2p/naming.html and some additional checks http://www.i2p2.i2p/naming.html and some additional checks
described in http://zzz.i2p/topics/739 described in http://zzz.i2p/topics/739
""" """
# convert hostname to lowercase and strip leading and trailing whitespaces # convert hostname to lowercase and strip leading and trailing whitespaces
data = data.lower().strip() data = data.lower().strip()
# do lenght check here for avoiding django.db.utils.DatabaseError exceptions # do lenght check here for avoiding django.db.utils.DatabaseError exceptions
# when trying to add too long hostname with py-i2phosts-injector # when trying to add too long hostname with py-i2phosts-injector
if len(data) > 67: if len(data) > 67:
raise ValidationError(_('Too long hostname (should be 67 chars max)')) raise ValidationError(_('Too long hostname (should be 67 chars max)'))
# Must end with '.i2p'. # Must end with '.i2p'.
if re.match(r'.*\.i2p$', data) == None: if re.match(r'.*\.i2p$', data) == None:
raise ValidationError(_('Hostname doesn\'t ends with .i2p')) raise ValidationError(_('Hostname doesn\'t ends with .i2p'))
# Base 32 hostnames (*.b32.i2p) are not allowed # Base 32 hostnames (*.b32.i2p) are not allowed
if re.match(r'.*\.b32\.i2p$', data): if re.match(r'.*\.b32\.i2p$', data):
raise ValidationError(_('Base32 hostnames are not allowed')) raise ValidationError(_('Base32 hostnames are not allowed'))
# prevent common errors # prevent common errors
if re.match(r'\.i2p$', data): if re.match(r'\.i2p$', data):
raise ValidationError(_('Incomplete hostname')) raise ValidationError(_('Incomplete hostname'))
if re.match(r'^http:/', data): if re.match(r'^http:/', data):
raise ValidationError(_('Do not paste full URL, just domain')) raise ValidationError(_('Do not paste full URL, just domain'))
# Must not contain '..' # Must not contain '..'
if re.search(r'\.\.', data): if re.search(r'\.\.', data):
raise ValidationError(_('".." in hostname')) raise ValidationError(_('".." in hostname'))
# Allow only 4ld domains and below # Allow only 4ld domains and below
if data.count('.') > 3: if data.count('.') > 3:
raise ValidationError(_('Subdomains deeper than 4LD are not allowed')) raise ValidationError(_('Subdomains deeper than 4LD are not allowed'))
# Must contain only [a-z] [0-9] '.' and '-' # Must contain only [a-z] [0-9] '.' and '-'
h = re.match(r'([a-z0-9.-]+)\.i2p$', data) h = re.match(r'([a-z0-9.-]+)\.i2p$', data)
if h == None: if h == None:
raise ValidationError(_('Illegal characters in hostname')) raise ValidationError(_('Illegal characters in hostname'))
else: else:
namepart = h.groups()[0] namepart = h.groups()[0]
# Must not start with '.' or '-' # Must not start with '.' or '-'
if re.match(r'^\.|-', namepart): if re.match(r'^\.|-', namepart):
raise ValidationError(_('Hostname must not starts with "." or "-"')) raise ValidationError(_('Hostname must not starts with "." or "-"'))
# Must not contain '.-' or '-.' (as of 0.6.1.33) # Must not contain '.-' or '-.' (as of 0.6.1.33)
if re.search(r'(\.-)|(-\.)', namepart): if re.search(r'(\.-)|(-\.)', namepart):
raise ValidationError(_('Hostname contain ".-" or "-."')) raise ValidationError(_('Hostname contain ".-" or "-."'))
# Must not contain '--' except in 'xn--' for IDN # Must not contain '--' except in 'xn--' for IDN
if re.search(r'(?<!^xn)--', namepart) and re.search(r'(?<!\.xn)--', namepart): if re.search(r'(?<!^xn)--', namepart) and re.search(r'(?<!\.xn)--', namepart):
raise ValidationError(_('Hostname contain "--" and it\'s not an IDN')) raise ValidationError(_('Hostname contain "--" and it\'s not an IDN'))
# Certain hostnames reserved for project use are not allowed # Certain hostnames reserved for project use are not allowed
if re.search(r'(^|\.)(proxy|router|console|b32|b64)$', namepart): if re.search(r'(^|\.)(proxy|router|console|b32|b64)$', namepart):
raise ValidationError(_('Trying to use reserved hostname')) raise ValidationError(_('Trying to use reserved hostname'))
# Block various localhost.* in addition # Block various localhost.* in addition
if re.match(r'^localhost($|\..*$)', namepart): if re.match(r'^localhost($|\..*$)', namepart):
raise ValidationError(_('localhost.* not allowed')) raise ValidationError(_('localhost.* not allowed'))
return data return data
def validate_b64hash(data, check_uniq=True): def validate_b64hash(data, check_uniq=True):
""" """
Base64 hash validation Base64 hash validation
""" """
# strip leading and trailing whitespaces # strip leading and trailing whitespaces
data = data.strip() data = data.strip()
length = len(data) length = len(data)
# check for b32 address misuse # check for b32 address misuse
if re.match(r'.*\.b32\.i2p$', data): if re.match(r'.*\.b32\.i2p$', data):
raise ValidationError(_('You should paste base64 hash, not a base32!')) raise ValidationError(_('You should paste base64 hash, not a base32!'))
# fail if contains .i2p= (full foo.i2p=key) # fail if contains .i2p= (full foo.i2p=key)
if re.search(r'\.i2p=', data): if re.search(r'\.i2p=', data):
raise ValidationError(_('Do not paste full hosts.txt entry! Only base64 hash is needed')) raise ValidationError(_('Do not paste full hosts.txt entry! Only base64 hash is needed'))
# check for pasting router hash # check for pasting router hash
if length == 44: if length == 44:
raise ValidationError(_('Do not paste router hash! Go to i2ptunnel page and \ raise ValidationError(_('Do not paste router hash! Go to i2ptunnel page and \
find a destination hash')) find a destination hash'))
# Minimum key length 516 bytes # Minimum key length 516 bytes
if length < 516: if length < 516:
raise ValidationError(_('Specified base64 hash is less than 516 bytes')) raise ValidationError(_('Specified base64 hash is less than 516 bytes'))
# Maximum key length 616 bytes # Maximum key length 616 bytes
if length > 616: if length > 616:
raise ValidationError(_('Specified base64 hash is bigger than 616 bytes')) raise ValidationError(_('Specified base64 hash is bigger than 616 bytes'))
# keys with cert may ends with anything, so check is relaxed # keys with cert may ends with anything, so check is relaxed
if length > 516 and re.match(r'[a-zA-Z0-9\-~=]+$', data) == None: if length > 516 and re.match(r'[a-zA-Z0-9\-~=]+$', data) == None:
raise ValidationError(_('Invalid characters in base64 hash')) raise ValidationError(_('Invalid characters in base64 hash'))
# base64-validity test # base64-validity test
if length > 516: if length > 516:
# we need temporary variable here to avoid modifying main "data" # we need temporary variable here to avoid modifying main "data"
test_data = data test_data = data
# add pad-characters needed for proper decoding cos i2p does not # add pad-characters needed for proper decoding cos i2p does not
for i in range(4): for i in range(4):
quanta, leftover = divmod(len(test_data), 4) quanta, leftover = divmod(len(test_data), 4)
if leftover: if leftover:
test_data += '=' test_data += '='
else: else:
break break
# if more than 2 pad chars were added, raise an error # if more than 2 pad chars were added, raise an error
if i > 2: if i > 2:
raise ValidationError(_('Corrupted base64 hash')) raise ValidationError(_('Corrupted base64 hash'))
# base64-i2p # base64-i2p
if length == 516 and re.match(r'[a-zA-Z0-9\-~]+AA$', data) == None: if length == 516 and re.match(r'[a-zA-Z0-9\-~]+AA$', data) == None:
raise ValidationError(_('Invalid base64 hash')) raise ValidationError(_('Invalid base64 hash'))
# check ECDSA validity # check ECDSA validity
if length == 524 and re.match(r'[a-zA-Z0-9\-~]+AEAAEAAA==$', data) == None: if length == 524 and re.match(r'[a-zA-Z0-9\-~]+AEAAEAAA==$', data) == None:
raise ValidationError(_('Invalid base64 ECDSA hash')) raise ValidationError(_('Invalid base64 ECDSA hash'))
if check_uniq == True: if check_uniq == True:
# Avoid adding non-unique hashes # Avoid adding non-unique hashes
qs = i2phost.objects.filter(b64hash=data) qs = i2phost.objects.filter(b64hash=data)
if qs.exists(): if qs.exists():
raise ValidationError(_('Some host already have the same Base64 hash')) raise ValidationError(_('Some host already have the same Base64 hash'))
return data return data
def validate_i2purl(data): def validate_i2purl(data):
""" Basic I2P URL validator """ """ Basic I2P URL validator """
# convert to lowercase and strip leading and trailing whitespaces # convert to lowercase and strip leading and trailing whitespaces
data = data.lower().strip() data = data.lower().strip()
# check for http://, .i2p in domain and GET validity # check for http://, .i2p in domain and GET validity
if re.match(r'^http://(?:.+?\.i2p)(?:/?|[/?]\S+)$', data) == None: if re.match(r'^http://(?:.+?\.i2p)(?:/?|[/?]\S+)$', data) == None:
raise ValidationError(_('Bad I2P url')) raise ValidationError(_('Bad I2P url'))

58
pyi2phosts/postkey/admin.py

@ -9,46 +9,46 @@ from pyi2phosts.lib.validation import validate_b64hash
class i2phostAdminForm(forms.ModelForm): class i2phostAdminForm(forms.ModelForm):
""" Custom form for editing hosts via admin interface """ """ Custom form for editing hosts via admin interface """
def clean_name(self): def clean_name(self):
"""Validate hostname""" """Validate hostname"""
data = self.cleaned_data['name'] data = self.cleaned_data['name']
data = validate_hostname(data) data = validate_hostname(data)
return data return data
def clean_b64hash(self): def clean_b64hash(self):
"""Validate base64 hash""" """Validate base64 hash"""
data = self.cleaned_data['b64hash'] data = self.cleaned_data['b64hash']
data = validate_b64hash(data, check_uniq=False) data = validate_b64hash(data, check_uniq=False)
return data return data
class i2phostAdmin(admin.ModelAdmin): class i2phostAdmin(admin.ModelAdmin):
def url(self, hostname): def url(self, hostname):
return '<a href="http://' + get_b32(hostname.b64hash) + '">b32</a>' return '<a href="http://' + get_b32(hostname.b64hash) + '">b32</a>'
form = i2phostAdminForm form = i2phostAdminForm
url.allow_tags = True url.allow_tags = True
list_display = ('url', 'name', 'description', 'date_added', 'last_seen', 'expires', list_display = ('url', 'name', 'description', 'date_added', 'last_seen', 'expires',
'activated', 'external') 'activated', 'external')
list_display_links = ['name'] list_display_links = ['name']
list_filter = ('activated', 'external', 'approved') list_filter = ('activated', 'external', 'approved')
search_fields = ('name', 'b64hash') search_fields = ('name', 'b64hash')
ordering = ['-date_added'] ordering = ['-date_added']
class PendingAdmin(i2phostAdmin): class PendingAdmin(i2phostAdmin):
def queryset(self, request): def queryset(self, request):
qs = super(PendingAdmin, self).queryset(request) qs = super(PendingAdmin, self).queryset(request)
return qs.filter(approved=False) return qs.filter(approved=False)
def approve_selected(modeladmin, request, queryset): def approve_selected(modeladmin, request, queryset):
queryset.update(approved=True) queryset.update(approved=True)
list_filter = [] list_filter = []
list_display = ('url', 'name', 'description', 'date_added', 'last_seen', 'expires', 'approved') list_display = ('url', 'name', 'description', 'date_added', 'last_seen', 'expires', 'approved')
actions = ['approve_selected'] actions = ['approve_selected']
admin.site.register(i2phost, i2phostAdmin) admin.site.register(i2phost, i2phostAdmin)

42
pyi2phosts/postkey/models.py

@ -2,27 +2,27 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
class i2phost(models.Model): class i2phost(models.Model):
# Hostname limit is 67 characters maximum, including the '.i2p'. # Hostname limit is 67 characters maximum, including the '.i2p'.
name = models.CharField(_('I2P hostname'), max_length=67, unique=True) name = models.CharField(_('I2P hostname'), max_length=67, unique=True)
# Maximum key length 616 bytes (to account for certs up to 100 bytes). # Maximum key length 616 bytes (to account for certs up to 100 bytes).
b64hash = models.CharField(_('Base 64 hash'), max_length=616) b64hash = models.CharField(_('Base 64 hash'), max_length=616)
description = models.CharField(_('Description'), max_length=4096, blank=True) description = models.CharField(_('Description'), max_length=4096, blank=True)
date_added = models.DateTimeField(null=True, blank=True) date_added = models.DateTimeField(null=True, blank=True)
# Last time this host was up # Last time this host was up
last_seen = models.DateTimeField(null=True, blank=True) last_seen = models.DateTimeField(null=True, blank=True)
# Scheduled expiration date # Scheduled expiration date
expires = models.DateField(null=True, blank=True) expires = models.DateField(null=True, blank=True)
# Not-activated hosts will not appear in exported hosts.txt # Not-activated hosts will not appear in exported hosts.txt
activated = models.BooleanField(default=False) activated = models.BooleanField(default=False)
# Indicator for hosts added from external source # Indicator for hosts added from external source
external = models.BooleanField(default=False) external = models.BooleanField(default=False)
# Not approved hosts will not appear in exported hosts.txt # Not approved hosts will not appear in exported hosts.txt
approved = models.BooleanField(default=False) approved = models.BooleanField(default=False)
def __unicode__(self): def __unicode__(self):
return self.name return self.name
class PendingHost(i2phost): class PendingHost(i2phost):
""" Proxy model needed for displaying not approved hosts in django admin separatelly """ """ Proxy model needed for displaying not approved hosts in django admin separatelly """
class Meta: class Meta:
proxy = True proxy = True

6
pyi2phosts/postkey/urls.py

@ -1,7 +1,7 @@
from django.conf.urls import * from django.conf.urls import *
urlpatterns = patterns('pyi2phosts.postkey.views', urlpatterns = patterns('pyi2phosts.postkey.views',
(r'^$', 'addkey'), (r'^$', 'addkey'),
(r'^success/', 'success'), (r'^success/', 'success'),
(r'^subdomain/', 'subdomain'), (r'^subdomain/', 'subdomain'),
) )

262
pyi2phosts/postkey/views.py

@ -17,147 +17,147 @@ from pyi2phosts.lib.validation import validate_hostname
from pyi2phosts.lib.validation import validate_b64hash from pyi2phosts.lib.validation import validate_b64hash
class AddForm(forms.ModelForm): class AddForm(forms.ModelForm):
""" """
This is our class for host-add form. It's based on django's ModelForm This is our class for host-add form. It's based on django's ModelForm
and uses our model "i2phost" (see postkey/models.py) and uses our model "i2phost" (see postkey/models.py)
""" """
class Meta: class Meta:
model = i2phost model = i2phost
fields = ('name', 'b64hash', 'description') fields = ('name', 'b64hash', 'description')
widgets = { widgets = {
'name': forms.TextInput(attrs={'size': '67'}), 'name': forms.TextInput(attrs={'size': '67'}),
'b64hash': forms.Textarea(attrs={'rows': '1', 'cols': '100'}), 'b64hash': forms.Textarea(attrs={'rows': '1', 'cols': '100'}),
'description': forms.Textarea(attrs={'rows': '2', 'cols': '72'}) 'description': forms.Textarea(attrs={'rows': '2', 'cols': '72'})
} }
def clean_name(self): def clean_name(self):
"""Validate hostname""" """Validate hostname"""
data = self.cleaned_data['name'] data = self.cleaned_data['name']
log.debug(u'hostname: %s', self.data['name']) log.debug(u'hostname: %s', self.data['name'])
data = validate_hostname(data) data = validate_hostname(data)
# Another set of reserved hostnames (suggested by zzz) # Another set of reserved hostnames (suggested by zzz)
if re.search(r'(^|\.)(i2p|i2p2|geti2p|mail|project|i2project|i2pproject|i2p-project).i2p$', data): if re.search(r'(^|\.)(i2p|i2p2|geti2p|mail|project|i2project|i2pproject|i2p-project).i2p$', data):
raise forms.ValidationError(_('Trying to use hostname from additional reserved set')) raise forms.ValidationError(_('Trying to use hostname from additional reserved set'))
return data return data
def clean_b64hash(self): def clean_b64hash(self):
"""Validate base64 hash""" """Validate base64 hash"""
data = self.cleaned_data['b64hash'] data = self.cleaned_data['b64hash']
log.debug(u'hash: %s', self.data['b64hash']) log.debug(u'hash: %s', self.data['b64hash'])
data = validate_b64hash(data) data = validate_b64hash(data)
return data return data
def is_valid(self): def is_valid(self):
"""Log validation errors""" """Log validation errors"""
is_valid = super(AddForm, self).is_valid() is_valid = super(AddForm, self).is_valid()
if not is_valid: if not is_valid:
for field in self.errors.keys(): for field in self.errors.keys():
log.info('ValidationError: [%s]: \"%s\" %s', log.info('ValidationError: [%s]: \"%s\" %s',
field, self.data[field], self.errors[field].as_text()) field, self.data[field], self.errors[field].as_text())
return is_valid return is_valid
class SubdomainVerifyForm(forms.Form): class SubdomainVerifyForm(forms.Form):
"""Form for displaying verification filename and code when verifying a subdomain""" """Form for displaying verification filename and code when verifying a subdomain"""
filename = forms.CharField(label=_('Filename'), widget=forms.TextInput(attrs={ filename = forms.CharField(label=_('Filename'), widget=forms.TextInput(attrs={
'size': '20', 'size': '20',
'readonly': 'readonly', 'readonly': 'readonly',
'onclick': 'this.select();', 'onclick': 'this.select();',
})) }))
def save_host(request): def save_host(request):
"""Function for saving hosts after validation or subdomain verification""" """Function for saving hosts after validation or subdomain verification"""
# avoid race conditions # avoid race conditions
try: try:
h = i2phost.objects.get(name=request.session['hostname']) h = i2phost.objects.get(name=request.session['hostname'])
except i2phost.DoesNotExist: except i2phost.DoesNotExist:
host = i2phost(name=request.session['hostname'], host = i2phost(name=request.session['hostname'],
b64hash=request.session['b64hash'], b64hash=request.session['b64hash'],
description=request.session['description'], description=request.session['description'],
date_added=datetime.datetime.utcnow()) date_added=datetime.datetime.utcnow())
host.save() host.save()
return redirect('pyi2phosts.postkey.views.success') return redirect('pyi2phosts.postkey.views.success')
else: else:
log.warning('refusing to save already existed host: %s', request.session['hostname']) log.warning('refusing to save already existed host: %s', request.session['hostname'])
request.session.flush() request.session.flush()
return redirect('/') return redirect('/')
def addkey(request): def addkey(request):
if request.method == 'POST': if request.method == 'POST':
form = AddForm(request.POST) form = AddForm(request.POST)
if form.is_valid(): if form.is_valid():
request.session['hostname'] = form.cleaned_data['name'] request.session['hostname'] = form.cleaned_data['name']
request.session['b64hash'] = form.cleaned_data['b64hash'] request.session['b64hash'] = form.cleaned_data['b64hash']
request.session['description'] = form.cleaned_data['description'] request.session['description'] = form.cleaned_data['description']
if form.cleaned_data['name'].count('.') > 1: if form.cleaned_data['name'].count('.') > 1:
return redirect('pyi2phosts.postkey.views.subdomain') return redirect('pyi2phosts.postkey.views.subdomain')
else: else:
log.debug('submit is valid, saving') log.debug('submit is valid, saving')
s = save_host(request) s = save_host(request)
return s return s
else: else:
form = AddForm() form = AddForm()
return render_to_response('postkey.html', { return render_to_response('postkey.html', {
'title': settings.SITE_NAME, 'title': settings.SITE_NAME,
'form': form, 'form': form,
}, context_instance=RequestContext(request)) }, context_instance=RequestContext(request))
def success(request): def success(request):
if 'hostname' in request.session: if 'hostname' in request.session:
hn = request.session['hostname'] hn = request.session['hostname']
request.session.flush() request.session.flush()
return render_to_response('success_submission.html', { return render_to_response('success_submission.html', {
'title': settings.SITE_NAME, 'title': settings.SITE_NAME,
'hostname': hn, 'hostname': hn,
}, context_instance=RequestContext(request)) }, context_instance=RequestContext(request))
else: else:
return redirect('/') return redirect('/')
def subdomain(request): def subdomain(request):
"""Subdomain verification""" """Subdomain verification"""
if request.method == 'POST': if request.method == 'POST':
form = SubdomainVerifyForm(request.POST) form = SubdomainVerifyForm(request.POST)
if form.is_valid(): if form.is_valid():
# do verification here, then redirect to success # do verification here, then redirect to success
proxy_handler = urllib2.ProxyHandler({'http': settings.EEPROXY_URL}) proxy_handler = urllib2.ProxyHandler({'http': settings.EEPROXY_URL})
opener = urllib2.build_opener(proxy_handler) opener = urllib2.build_opener(proxy_handler)
if 'topdomain' in request.session and 'v_filename' in request.session: if 'topdomain' in request.session and 'v_filename' in request.session:
url = 'http://' + request.session['topdomain'] + '/' + request.session['v_filename'] url = 'http://' + request.session['topdomain'] + '/' + request.session['v_filename']
else: else:
log.warning('trying to call subdomain validation without a session') log.warning('trying to call subdomain validation without a session')
return redirect('/') return redirect('/')
log.info('starting http-verification of subdomain: %s', request.session['hostname']) log.info('starting http-verification of subdomain: %s', request.session['hostname'])
try: try:
log.debug('trying to open %s', url) log.debug('trying to open %s', url)
resp = opener.open(url, timeout=60) resp = opener.open(url, timeout=60)
except urllib2.URLError, e: except urllib2.URLError, e:
if hasattr(e, 'reason'): if hasattr(e, 'reason'):
log.warning('%s: failed to reach server, reason: %s', request.session['topdomain'], e.reason) log.warning('%s: failed to reach server, reason: %s', request.session['topdomain'], e.reason)
elif hasattr(e, 'code'): elif hasattr(e, 'code'):
log.warning('%s can\'t finish the request, error code: %s', log.warning('%s can\'t finish the request, error code: %s',
request.session['topdomain'], e.code) request.session['topdomain'], e.code)
return render_to_response('subdomain_http_verify_failure.html', { return render_to_response('subdomain_http_verify_failure.html', {
'title': settings.SITE_NAME, 'title': settings.SITE_NAME,
'code': e.code, 'code': e.code,
}, context_instance=RequestContext(request)) }, context_instance=RequestContext(request))
else: else:
log.debug('subdomain verification success, saving host') log.debug('subdomain verification success, saving host')
s = save_host(request) s = save_host(request)
return s return s
else: else:
# generate verification code and display info page to user # generate verification code and display info page to user
v_filename = ''.join([random.choice(string.letters + string.digits) for x in xrange(16)]) v_filename = ''.join([random.choice(string.letters + string.digits) for x in xrange(16)])
if 'hostname' in request.session: if 'hostname' in request.session:
m = re.match('.+\.(.+\.i2p$)', request.session['hostname']) m = re.match('.+\.(.+\.i2p$)', request.session['hostname'])
topdomain = m.group(1) topdomain = m.group(1)
else: else:
return redirect('/') return redirect('/')
# save needed variables in session data because otherwise it will be lost # save needed variables in session data because otherwise it will be lost
request.session['v_filename'] = v_filename request.session['v_filename'] = v_filename
request.session['topdomain'] = topdomain request.session['topdomain'] = topdomain
form = SubdomainVerifyForm({'filename': v_filename}) form = SubdomainVerifyForm({'filename': v_filename})
return render_to_response('subdomain_http_verify.html', { return render_to_response('subdomain_http_verify.html', {
'title': settings.SITE_NAME, 'title': settings.SITE_NAME,
'hostname': request.session['hostname'], 'hostname': request.session['hostname'],
'topdomain': topdomain, 'topdomain': topdomain,
'form': form, 'form': form,
}, context_instance=RequestContext(request)) }, context_instance=RequestContext(request))
log = get_logger(filename=settings.LOG_FILE, log_level=settings.LOG_LEVEL) log = get_logger(filename=settings.LOG_FILE, log_level=settings.LOG_LEVEL)

2
pyi2phosts/search/urls.py

@ -2,5 +2,5 @@ from django.conf.urls import *
from pyi2phosts.search.views import SearchedHostsListsView from pyi2phosts.search.views import SearchedHostsListsView
urlpatterns = patterns('', urlpatterns = patterns('',
(r'^$', SearchedHostsListsView.as_view()), (r'^$', SearchedHostsListsView.as_view()),
) )

18
pyi2phosts/search/views.py

@ -5,14 +5,14 @@ from pyi2phosts.lib.generic import HostsListsView
class SearchedHostsListsView(HostsListsView): class SearchedHostsListsView(HostsListsView):
""" Renders list of hosts matching search request """ """ Renders list of hosts matching search request """
def get_queryset(self): def get_queryset(self):
q = self.request.GET.get('q', '') q = self.request.GET.get('q', '')
fil = Q(name__icontains=q) | Q(b64hash__contains=q) fil = Q(name__icontains=q) | Q(b64hash__contains=q)
queryset = i2phost.objects.filter(fil) queryset = i2phost.objects.filter(fil)
return queryset return queryset
template_name = 'search_results.html' template_name = 'search_results.html'
template_object_name = 'host_list' template_object_name = 'host_list'
paginate_by = 40 paginate_by = 40

10
pyi2phosts/settings.py

@ -37,9 +37,9 @@ TIME_ZONE = 'America/Chicago'
LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = 'en-us'
LANGUAGES = ( LANGUAGES = (
('en', 'English'), ('en', 'English'),
('ru', 'Russian'), ('ru', 'Russian'),
) )
SITE_ID = 1 SITE_ID = 1
@ -139,6 +139,6 @@ EEPROXY_URL = 'http://127.0.0.1:4444'
# include local settings # include local settings
try: try:
from local_settings import * from local_settings import *
except ImportError: except ImportError:
pass pass

144
pyi2phosts/static-common/base.css

@ -1,133 +1,133 @@
html, body { html, body {
font-size: 12pt; font-size: 12pt;
background: #E6E6D1; background: #E6E6D1;
color: #000000; color: #000000;
} }
input, textarea { input, textarea {
background-color: #AEB08D; background-color: #AEB08D;
color: #000; color: #000;
border: 1px solid #000; border: 1px solid #000;
} }
label { label {
width: 8em; width: 8em;
float: top; float: top;
text-align: left; text-align: left;
margin-right: 1em; margin-right: 1em;
display: block; display: block;
} }
a { a {
background: inherit; background: inherit;
color: #6E735E; color: #6E735E;
text-decoration: none; text-decoration: none;
} }
a:visited { a:visited {
background: inherit; background: inherit;
color: #6E735E; color: #6E735E;
text-decoration: none; text-decoration: none;
} }
a:hover { a:hover {
color: #000; color: #000;
background: inherit; background: inherit;
} }
table { table {
border-collapse: collapse; border-collapse: collapse;
width: 90%; width: 90%;
margin: 10px 0px 10px 0px; margin: 10px 0px 10px 0px;
} }
tr:first-child { tr:first-child {
border: 1px solid #CCCCCC; border: 1px solid #CCCCCC;
font-weight: bold; font-weight: bold;
} }
tr { tr {
border: 1px dashed #CCCCCC; border: 1px dashed #CCCCCC;
} }
td { td {
max-width: 220px; max-width: 220px;
} }
div.menu { div.menu {
float: left; float: left;
margin: 0px 20px 20px 0px; margin: 0px 20px 20px 0px;
padding: 10px 20px 20px 0px; padding: 10px 20px 20px 0px;
border-left: solid 1px #CCCCCC; border-left: solid 1px #CCCCCC;
text-align: left; text-align: left;
color: black; color: black;
font-size: 8pt; font-size: 8pt;
clear: left; /* fixes a bug in Opera */ clear: left; /* fixes a bug in Opera */
width: 160px; width: 160px;
} }
.menu li { .menu li {
margin-left: .5em; margin-left: .5em;
margin-top: .4em; margin-top: .4em;
padding-left: .5em; padding-left: .5em;
line-height: 1.2; line-height: 1.2;
list-style-type: none; list-style-type: none;
list-style-position: outside; list-style-position: outside;
} }
.menu ol, ul { .menu ol, ul {
padding-left: 0em; padding-left: 0em;
margin-top: 0em; margin-top: 0em;
padding-top: 0em; padding-top: 0em;
} }
.errorlist { .errorlist {
color: red; color: red;
padding-left: 5em; padding-left: 5em;
} }
div.main { div.main {
margin: 0px 0px 0px 0px; margin: 0px 0px 0px 0px;
padding: 22px 60px 20px 220px; padding: 22px 60px 20px 220px;
text-align: justify; text-align: justify;
color: #000011; color: #000011;
} }
div.main li { div.main li {
margin-left: 15px; margin-left: 15px;
} }
div.footer { div.footer {
font-size: 8pt; font-size: 8pt;
text-align: center; text-align: center;
padding: 8px 0 0 0; padding: 8px 0 0 0;
} }
div.search_host { div.search_host {
position:absolute; position:absolute;
right: 15px; right: 15px;
} }
div.search_host .input input { div.search_host .input input {
font-size: 10px; font-size: 10px;
color: #6E735E; color: #6E735E;
width: 200px; width: 200px;
} }
.pager { .pager {
padding-top: 20px; padding-top: 20px;
padding-left: 100px; padding-left: 100px;
font-size: 8pt; font-size: 8pt;
} }
.pager .page a { .pager .page a {
border: 1px solid #bbbbbb; border: 1px solid #bbbbbb;
margin-left: 1px; margin-left: 1px;
margin-right: 1px; margin-right: 1px;
padding: 0px 5px 0px 5px; padding: 0px 5px 0px 5px;
text-decoration: none; text-decoration: none;
color: #000000; color: #000000;
} }
.pager .current { .pager .current {
border: 2px solid #444444; border: 2px solid #444444;
margin-left: 2px; margin-left: 2px;
margin-right: 2px; margin-right: 2px;
padding: 0px 5px 0px 5px; padding: 0px 5px 0px 5px;
} }

10
pyi2phosts/static-common/inproxy.html

@ -1,13 +1,13 @@
<html> <html>
<head><title>FAIL</title></head> <head><title>FAIL</title></head>
<body bgcolor="white"> <body bgcolor="white">
<center><h1>Non-I2P access denied</h1></center> <center><h1>Non-I2P access denied</h1></center>
<hr><center>py-i2phosts instance</center> <hr><center>py-i2phosts instance</center>
</body> </body>
</html> </html>

BIN
pyi2phosts/static-common/rss-grey-18.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 670 B

After

Width:  |  Height:  |  Size: 676 B

2
pyi2phosts/templates/404.html

@ -1,5 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<center><h1>404 Not Found</h1></center> <center><h1>404 Not Found</h1></center>
{% endblock %} {% endblock %}

2
pyi2phosts/templates/500.html

@ -1,5 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<center><h1>500 Internal server error</h1></center> <center><h1>500 Internal server error</h1></center>
{% endblock %} {% endblock %}

108
pyi2phosts/templates/base.html

@ -1,65 +1,65 @@
{% load i18n %} {% load i18n %}
<html> <html>
<head> <head>
<title> <title>
{% block title %} {% block title %}
{{ title }} {{ title }}
{% endblock %} {% endblock %}
</title> </title>
{% block head %} {% block head %}
{% endblock %} {% endblock %}
<link rel="stylesheet" type="text/css" href="/static/base.css" /> <link rel="stylesheet" type="text/css" href="/static/base.css" />
</head> </head>
<body> <body>
<div class="search_host"> <div class="search_host">
<form action="/search/"> <form action="/search/">
<input class="input" name="q" maxlength="67" type="text" value="{% trans "Search host" %}" <input class="input" name="q" maxlength="67" type="text" value="{% trans "Search host" %}"
onblur="if (value == '') {value = '{% trans "Search host" %}'}" onfocus="if (value == '{% trans "Search host" %}') onblur="if (value == '') {value = '{% trans "Search host" %}'}" onfocus="if (value == '{% trans "Search host" %}')
{value =''}" /> {value =''}" />
<input type="submit" value="{% trans "Search" %}" /> <input type="submit" value="{% trans "Search" %}" />
</form> </form>
</div> </div>
{% block navigation %} {% block navigation %}
<div class="menu"> <div class="menu">
<ul> <ul>
<li><a href=/>{% trans "Home" %}</a></li> <li><a href=/>{% trans "Home" %}</a></li>
<li><a href={% url 'faq' %}>FAQ</a></li> <li><a href={% url 'faq' %}>FAQ</a></li>
<li><a href={% url 'latest' %}>{% trans "Browse latest hosts" %}</a></li> <li><a href={% url 'latest' %}>{% trans "Browse latest hosts" %}</a></li>
<li><a href={% url 'browse' %}>{% trans "Browse alive hosts" %}</a></li> <li><a href={% url 'browse' %}>{% trans "Browse alive hosts" %}</a></li>
<li><a href={% url 'pyi2phosts.postkey.views.addkey' %}>{% trans "Register a domain" %}</a></li> <li><a href={% url 'pyi2phosts.postkey.views.addkey' %}>{% trans "Register a domain" %}</a></li>
<li><a href={% url 'contacts' %}>{% trans "Contacts" %}</a></li> <li><a href={% url 'contacts' %}>{% trans "Contacts" %}</a></li>
</ul> </ul>
</div> </div>
<form action="/i18n/setlang/" method="post"> <form action="/i18n/setlang/" method="post">
{% csrf_token %} {% csrf_token %}
{% for lang in LANGUAGES %} {% for lang in LANGUAGES %}
<input type="radio" value="{{ lang.0 }}" name="language" /> <input type="radio" value="{{ lang.0 }}" name="language" />
<img src="/static/{{ lang.0 }}.png" width="16" height="11" alt="{{ lang.0 }}"/> <img src="/static/{{ lang.0 }}.png" width="16" height="11" alt="{{ lang.0 }}"/>
{% endfor %} {% endfor %}
<input type="submit" value="Set" /> <input type="submit" value="Set" />
</form> </form>
{% endblock %} {% endblock %}
<div class="main"> <div class="main">
{% block header %} {% block header %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{% endblock %} {% endblock %}
</div> </div>
{% block footer %} {% block footer %}
<hr> <hr>
<div class="footer"> <div class="footer">
{% block footer-addon %} {% block footer-addon %}
{% endblock %} {% endblock %}
{% trans "powered-by" %}: <a href=http://py-i2phosts.i2p/>py-i2phosts</a> {% trans "powered-by" %}: <a href=http://py-i2phosts.i2p/>py-i2phosts</a>
</div> </div>
{% endblock %} {% endblock %}
</body> </body>
</html> </html>

8
pyi2phosts/templates/browse.html

@ -11,10 +11,10 @@
<table> <table>
<tr><td><a href="?order=name">{% trans "Host" %}</a></td><td><a href="?order=last_seen">{% trans "Last seen" %}</a></td><td><a href="?order=date_added">{% trans "Date added" %}</a></td><td>{% trans "Description" %}</td></tr> <tr><td><a href="?order=name">{% trans "Host" %}</a></td><td><a href="?order=last_seen">{% trans "Last seen" %}</a></td><td><a href="?order=date_added">{% trans "Date added" %}</a></td><td>{% trans "Description" %}</td></tr>
{% endblock table_header %} {% endblock table_header %}
{% for host in host_list %} {% for host in host_list %}
<tr><td><a href="http://{{ host.name }}/?i2paddresshelper={{host.b64hash}}">{{ host.name }}</a></td><td>{{ host.last_seen }}</td> <tr><td><a href="http://{{ host.name }}/?i2paddresshelper={{host.b64hash}}">{{ host.name }}</a></td><td>{{ host.last_seen }}</td>
<td>{{ host.date_added }}</td><td>{{ host.description }}</td></tr> <td>{{ host.date_added }}</td><td>{{ host.description }}</td></tr>
{% endfor %} {% endfor %}
</table> </table>
{% if is_paginated %} {% if is_paginated %}

10
pyi2phosts/templates/contacts.html

@ -5,17 +5,17 @@
{% blocktrans %} {% blocktrans %}
<h3>Direct contact methods:</h3> <h3>Direct contact methods:</h3>
<ul> <ul>
<li>IRC: #i2p-dev, nick "slow". This is the fastest contact method and should be used first.</li> <li>IRC: #i2p-dev, nick "slow". This is the fastest contact method and should be used first.</li>
<li>Email: hiddenz@mail.i2p. You can send your message here, but don't expect a fast reply.</li> <li>Email: hiddenz@mail.i2p. You can send your message here, but don't expect a fast reply.</li>
</ul> </ul>
{% endblocktrans %} {% endblocktrans %}
{% blocktrans %} {% blocktrans %}
<h3>Public discussions about service, feedback, proposals and feature requests</h3> <h3>Public discussions about service, feedback, proposals and feature requests</h3>
<ul> <ul>
<li><a href="http://hiddenchan.i2p/d/res/4222.html">Thread at hiddenchan.i2p</a>: the best place for discussion (russian and english are allowed)</li> <li><a href="http://hiddenchan.i2p/d/res/4222.html">Thread at hiddenchan.i2p</a>: the best place for discussion (russian and english are allowed)</li>
<li><a href="http://forum.i2p/viewtopic.php?t=5083">Thread at forum.i2p</a>: very rarely readable (english)</li> <li><a href="http://forum.i2p/viewtopic.php?t=5083">Thread at forum.i2p</a>: very rarely readable (english)</li>
<li><a href="http://zzz.i2p/topics/733">py-i2phosts thread at zzz.i2p</a>: thread about py-i2phosts development, rarely readable (english)</li> <li><a href="http://zzz.i2p/topics/733">py-i2phosts thread at zzz.i2p</a>: thread about py-i2phosts development, rarely readable (english)</li>
</ul> </ul>
{% endblocktrans %} {% endblocktrans %}
{% endblock %} {% endblock %}

24
pyi2phosts/templates/faq.html

@ -4,24 +4,24 @@
{% block content %} {% block content %}
<h3>{% trans "How we are learning about new hosts" %}</h3> <h3>{% trans "How we are learning about new hosts" %}</h3>
<ol> <ol>
<li>{% trans "Pulling from external sources:" %} <li>{% trans "Pulling from external sources:" %}
<ul> <ul>
{% for source in sources_list %} {% for source in sources_list %}
<li>{{ source.url }}</li> <li>{{ source.url }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
</li> </li>
<li>{% trans "Adding through our service." %} <li>{% trans "Adding through our service." %}
</li> </li>
</ol> </ol>
{% blocktrans %} {% blocktrans %}
<h3>Publishing requirements</h3> <h3>Publishing requirements</h3>
To get published a host must meet the following criteria: To get published a host must meet the following criteria:
<ul> <ul>
<li>Must have been added at least 3 days ago</li> <li>Must have been added at least 3 days ago</li>
<li>Must be up</li> <li>Must be up</li>
<li>Must be approved by admin</li> <li>Must be approved by admin</li>
</ul> </ul>
<p style="notice">Admin's approval isn't really necessary, it is only needed in <p style="notice">Admin's approval isn't really necessary, it is only needed in
order to eliminate possible hijacking and mass registration attempts. order to eliminate possible hijacking and mass registration attempts.

88
pyi2phosts/templates/index.html

@ -2,55 +2,55 @@
{% load i18n %} {% load i18n %}
{% block content %} {% block content %}
{% url 'faq' as faq_url %} {% url 'faq' as faq_url %}
{% blocktrans %} {% blocktrans %}
<h2>About</h2> <h2>About</h2>
<p>{{ title }} is a domain name registration service for I2P. Hostnames in I2P aren't <p>{{ title }} is a domain name registration service for I2P. Hostnames in I2P aren't
globally unique. {{ title }} doesn't act as "central authority", it only provides a globally unique. {{ title }} doesn't act as "central authority", it only provides a
way to publish hosts as an easy means of access to them. You can read more about how way to publish hosts as an easy means of access to them. You can read more about how
I2P naming works in the <a href=http://www.i2p2.i2p/naming.html>official I2P naming works in the <a href=http://www.i2p2.i2p/naming.html>official
docs</a>. docs</a>.
</p> </p>
<p>To find out how we're registering and publishing hosts, look at <p>To find out how we're registering and publishing hosts, look at
<a href={{ faq_url }}>FAQ</a> page. <a href={{ faq_url }}>FAQ</a> page.
</p> </p>
{% endblocktrans %} {% endblocktrans %}
{% blocktrans %} {% blocktrans %}
<h2>Addressbook service</h2> <h2>Addressbook service</h2>
<p> <p>
To start getting new hostnames from {{ title }}, add this To start getting new hostnames from {{ title }}, add this
<a href=/export/alive-hosts.txt>subscription link</a> into your <a <a href=/export/alive-hosts.txt>subscription link</a> into your <a
href=http://localhost:7657/susidns/subscriptions.jsp>router's href=http://localhost:7657/susidns/subscriptions.jsp>router's
addressbook</a>. Of course, you should <a addressbook</a>. Of course, you should <a
href=http://localhost:7657/susidns/addressbook.jsp?book=private&hostname={{ domain }}&destination={{ b64 }}>add INR</a>'s destination before. href=http://localhost:7657/susidns/addressbook.jsp?book=private&hostname={{ domain }}&destination={{ b64 }}>add INR</a>'s destination before.
</p> </p>
{% endblocktrans %} {% endblocktrans %}
{% url 'pyi2phosts.jump.views.jumper' 'example.i2p' as jump_url %} {% url 'pyi2phosts.jump.views.jumper' 'example.i2p' as jump_url %}
{% url 'pyi2phosts.jump.views.index' as jump_index %} {% url 'pyi2phosts.jump.views.index' as jump_index %}
{% blocktrans %} {% blocktrans %}
<h2>Jump service</h2><p> <h2>Jump service</h2><p>
{{ title }} also provides a jump service. For accessing hosts through it, {{ title }} also provides a jump service. For accessing hosts through it,
use urls like use urls like
<a href="{{ jump_url }}"> <a href="{{ jump_url }}">
http://{{ domain }}{{ jump_url }}</a>. http://{{ domain }}{{ jump_url }}</a>.
I2P since 0.8.3 gives possibility to add a custom jump-servers. Go to the i2ptunnel I2P since 0.8.3 gives possibility to add a custom jump-servers. Go to the i2ptunnel
<a href="http://localhost:7657/i2ptunnel/edit?tunnel=0">eeproxy configuration page <a href="http://localhost:7657/i2ptunnel/edit?tunnel=0">eeproxy configuration page
</a> and add <em>http://{{ domain }}{{ jump_index }}</em> to "Jump URL List" section. </a> and add <em>http://{{ domain }}{{ jump_index }}</em> to "Jump URL List" section.
</p> </p>
{% endblocktrans %} {% endblocktrans %}
{% url 'pyi2phosts.postkey.views.addkey' as addkey_url %} {% url 'pyi2phosts.postkey.views.addkey' as addkey_url %}
{% blocktrans %} {% blocktrans %}
<h2>Registration service</h2> <h2>Registration service</h2>
<p>If you are running an eepsite or another service and want a human-readable domain name <p>If you are running an eepsite or another service and want a human-readable domain name
for them, consider <a href={{ addkey_url }}>registering it</a>. for them, consider <a href={{ addkey_url }}>registering it</a>.
</p> </p>
{% endblocktrans %} {% endblocktrans %}
{% endblock %} {% endblock %}
{% block footer-addon %} {% block footer-addon %}
<a href=http://{{ b32 }}>b32</a> | <a href=http://localhost:7657/susidns/addressbook.jsp?book=private&hostname={{ domain }}&destination={{ b64 }}>{% trans "add" %}</a> | <a href=http://{{ b32 }}>b32</a> | <a href=http://localhost:7657/susidns/addressbook.jsp?book=private&hostname={{ domain }}&destination={{ b64 }}>{% trans "add" %}</a> |
{% endblock %} {% endblock %}

20
pyi2phosts/templates/policy.html

@ -2,14 +2,14 @@
<h2>{% trans "Domain name registration policy" %}</h2> <h2>{% trans "Domain name registration policy" %}</h2>
<ul> <ul>
<li>{% trans "Domain name registration is free." %}</li> <li>{% trans "Domain name registration is free." %}</li>
<li>{% trans "Anyone can register a domain name." %}</li> <li>{% trans "Anyone can register a domain name." %}</li>
<li>{% trans "Domain names are available on a 'first come, first serve' basis." %}</li> <li>{% trans "Domain names are available on a 'first come, first serve' basis." %}</li>
<li>{% trans "A domain name's destination must be active." %} <li>{% trans "A domain name's destination must be active." %}
{% trans "Inactive destinations cannot be published." %}</li> {% trans "Inactive destinations cannot be published." %}</li>
<li>{% trans "Domain name hoarding through mass registration (cybersquatting) is not allowed." %}</li> <li>{% trans "Domain name hoarding through mass registration (cybersquatting) is not allowed." %}</li>
<li>{% trans "Domain name registrations will not be rejected based on content." %}</li> <li>{% trans "Domain name registrations will not be rejected based on content." %}</li>
<li>{% trans "Domain name registrations will stop propagating after some period of inactivity." %}</li> <li>{% trans "Domain name registrations will stop propagating after some period of inactivity." %}</li>
<li>{% trans "Temporary or test sites should not be registered. Use b32 address instead." %}</li> <li>{% trans "Temporary or test sites should not be registered. Use b32 address instead." %}</li>
<li>{% trans "Changing key for existing domains is prohibited." %}</li> <li>{% trans "Changing key for existing domains is prohibited." %}</li>
</ul> </ul>

12
pyi2phosts/templates/postkey.html

@ -3,10 +3,10 @@
{% block content %} {% block content %}
{% include "policy.html" %} {% include "policy.html" %}
<form action="/postkey/" method="post"> <form action="/postkey/" method="post">
{% csrf_token %} {% csrf_token %}
<h3>{% trans "Enter your domain details" %}</h3> <h3>{% trans "Enter your domain details" %}</h3>
{{ form.as_p }} {{ form.as_p }}
<input type="submit" value="{% trans "Submit" %}" /> <input type="submit" value="{% trans "Submit" %}" />
</form> </form>
{% endblock %} {% endblock %}

10
pyi2phosts/templates/subdomain_http_verify.html

@ -15,10 +15,10 @@ This file should be accessible via http://{{ topdomain }}/&laquo;filename&raquo;
</p> </p>
{% endblocktrans %} {% endblocktrans %}
<form action="/postkey/subdomain/" method="post"> <form action="/postkey/subdomain/" method="post">
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} {{ form.as_p }}
<input type="submit" value="OK" /> <input type="submit" value="OK" />
</form> </form>
{% endblock %} {% endblock %}

28
pyi2phosts/urls.py

@ -13,18 +13,18 @@ from pyi2phosts.lib.generic import HostsListsView
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^$', LocalTemplateView.as_view(template_name='index.html'), name='index'), url(r'^$', LocalTemplateView.as_view(template_name='index.html'), name='index'),
url(r'^contacts/$', LocalTemplateView.as_view(template_name='contacts.html'), name='contacts'), url(r'^contacts/$', LocalTemplateView.as_view(template_name='contacts.html'), name='contacts'),
url(r'^faq/$', FaqView.as_view(), name='faq'), url(r'^faq/$', FaqView.as_view(), name='faq'),
url(r'^browse/$', HostsListsView.as_view(), name='browse'), url(r'^browse/$', HostsListsView.as_view(), name='browse'),
url(r'^browse/rss/$', AliveHostsFeed(), name='browse-rss'), url(r'^browse/rss/$', AliveHostsFeed(), name='browse-rss'),
(r'^latest/', include('pyi2phosts.latest.urls')), (r'^latest/', include('pyi2phosts.latest.urls')),
(r'^search/$', include('pyi2phosts.search.urls')), (r'^search/$', include('pyi2phosts.search.urls')),
(r'^postkey/', include('pyi2phosts.postkey.urls')), (r'^postkey/', include('pyi2phosts.postkey.urls')),
(r'^jump/', include('pyi2phosts.jump.urls')), (r'^jump/', include('pyi2phosts.jump.urls')),
(r'^api/', include('pyi2phosts.api.urls')), (r'^api/', include('pyi2phosts.api.urls')),
(r'^i18n/', include('django.conf.urls.i18n')), (r'^i18n/', include('django.conf.urls.i18n')),
# Example: # Example:
# (r'^pyi2phosts.', include('pyi2phosts.foo.urls')), # (r'^pyi2phosts.', include('pyi2phosts.foo.urls')),
@ -37,5 +37,5 @@ urlpatterns = patterns('',
) )
if settings.DEBUG: if settings.DEBUG:
urlpatterns += patterns('', (r'static/(?P<path>.*)$', 'django.views.static.serve', urlpatterns += patterns('', (r'static/(?P<path>.*)$', 'django.views.static.serve',
{'document_root': settings.MEDIA_ROOT, 'show_indexes':True})) {'document_root': settings.MEDIA_ROOT, 'show_indexes':True}))

28
setup.py

@ -8,28 +8,28 @@ setup(
author_email='hiddenz@mail.i2p', author_email='hiddenz@mail.i2p',
url='http://py-i2phosts.i2p/', url='http://py-i2phosts.i2p/',
packages=['pyi2phosts', packages=['pyi2phosts',
'pyi2phosts.postkey', 'pyi2phosts.postkey',
'pyi2phosts.postkey.templatetags', 'pyi2phosts.postkey.templatetags',
'pyi2phosts.jump', 'pyi2phosts.jump',
'pyi2phosts.extsources', 'pyi2phosts.extsources',
'pyi2phosts.lib', 'pyi2phosts.lib',
'pyi2phosts.search', 'pyi2phosts.search',
'pyi2phosts.latest'], 'pyi2phosts.latest'],
package_dir = {'': ''}, package_dir = {'': ''},
package_data = { package_data = {
'pyi2phosts': ['templates/*.html', 'static/*', 'locale/*/*/*']}, 'pyi2phosts': ['templates/*.html', 'static/*', 'locale/*/*/*']},
scripts=['bin/py-i2phosts-master', 'bin/py-i2phosts-builder', 'bin/py-i2phosts-checker', scripts=['bin/py-i2phosts-master', 'bin/py-i2phosts-builder', 'bin/py-i2phosts-checker',
'bin/py-i2phosts-fetcher', 'bin/py-i2phosts-injector', 'bin/py-i2phosts-maint'], 'bin/py-i2phosts-fetcher', 'bin/py-i2phosts-injector', 'bin/py-i2phosts-maint'],
data_files=[('/etc/py-i2phosts', ['conf/master.conf', 'conf/checker.conf', 'conf/fetcher.conf', data_files=[('/etc/py-i2phosts', ['conf/master.conf', 'conf/checker.conf', 'conf/fetcher.conf',
'conf/maintainer.conf', 'conf/builder.conf', 'conf/common.conf', 'conf/injector.conf'],), 'conf/maintainer.conf', 'conf/builder.conf', 'conf/common.conf', 'conf/injector.conf'],),
('/var/log/py-i2phosts', ['.placeholder'],), ('/var/log/py-i2phosts', ['.placeholder'],),
('/var/run/py-i2phosts', ['.placeholder'],),], ('/var/run/py-i2phosts', ['.placeholder'],),],
classifiers=[ classifiers=[
'Development Status :: 4 - Beta', 'Development Status :: 4 - Beta',
'Environment :: Console', 'Environment :: Console',
'Environment :: Web Environment', 'Environment :: Web Environment',
'Intended Audience :: Developers', 'Intended Audience :: Developers',
'Intended Audience :: System Administrators', 'Intended Audience :: System Administrators',
'License :: OSI Approved :: GPL License', 'License :: OSI Approved :: GPL License',
'Operating System :: Linux', 'Operating System :: Linux',
'Programming Language :: Python', 'Programming Language :: Python',

Loading…
Cancel
Save