From 6e3862d7ab3d0658218d1e4ece95956f155b872a Mon Sep 17 00:00:00 2001 From: Hidden Z Date: Mon, 26 Oct 2015 18:55:35 +0000 Subject: [PATCH] Convert tabs to spaces --- bin/py-i2phosts-builder | 32 +-- bin/py-i2phosts-checker | 98 +++---- bin/py-i2phosts-fetcher | 166 +++++------ bin/py-i2phosts-injector | 108 ++++---- bin/py-i2phosts-maint | 152 +++++----- bin/py-i2phosts-master | 200 ++++++------- pyi2phosts/api/urls.py | 2 +- pyi2phosts/api/views.py | 16 +- pyi2phosts/extsources/admin.py | 4 +- pyi2phosts/extsources/models.py | 18 +- pyi2phosts/jump/urls.py | 4 +- pyi2phosts/jump/views.py | 80 +++--- pyi2phosts/latest/urls.py | 4 +- pyi2phosts/latest/views.py | 36 +-- pyi2phosts/lib/generic.py | 80 +++--- pyi2phosts/lib/rss.py | 40 +-- pyi2phosts/lib/utils.py | 92 +++--- pyi2phosts/lib/validation.py | 212 +++++++------- pyi2phosts/postkey/admin.py | 58 ++-- pyi2phosts/postkey/models.py | 42 +-- pyi2phosts/postkey/urls.py | 6 +- pyi2phosts/postkey/views.py | 262 +++++++++--------- pyi2phosts/search/urls.py | 2 +- pyi2phosts/search/views.py | 18 +- pyi2phosts/settings.py | 10 +- pyi2phosts/static-common/base.css | 144 +++++----- pyi2phosts/static-common/inproxy.html | 10 +- pyi2phosts/static-common/rss-grey-18.png | Bin 670 -> 676 bytes pyi2phosts/templates/404.html | 2 +- pyi2phosts/templates/500.html | 2 +- pyi2phosts/templates/base.html | 108 ++++---- pyi2phosts/templates/browse.html | 8 +- pyi2phosts/templates/contacts.html | 10 +- pyi2phosts/templates/faq.html | 24 +- pyi2phosts/templates/index.html | 88 +++--- pyi2phosts/templates/policy.html | 20 +- pyi2phosts/templates/postkey.html | 12 +- .../templates/subdomain_http_verify.html | 10 +- pyi2phosts/urls.py | 28 +- setup.py | 28 +- 40 files changed, 1121 insertions(+), 1115 deletions(-) diff --git a/bin/py-i2phosts-builder b/bin/py-i2phosts-builder index b7d27b1..d4810b2 100755 --- a/bin/py-i2phosts-builder +++ b/bin/py-i2phosts-builder @@ -7,32 +7,32 @@ import configobj # parse command line options parser = argparse.ArgumentParser( - description='Hosts builder for py-i2phosts.', - epilog='Report bugs to http://zzz.i2p/topics/733') + description='Hosts builder for py-i2phosts.', + epilog='Report bugs to http://zzz.i2p/topics/733') 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', - help='write hosts into specified file') + help='write hosts into specified file') parser.add_argument('-d', '--debug', action='store_true', - help='write debug messages to stdout') + help='write debug messages to stdout') args = parser.parse_args() # read config spec = ''' - hostsfile = string(default=None) - ''' + hostsfile = string(default=None) + ''' spec = spec.split('\n') config = configobj.ConfigObj(args.config_file, configspec=spec, file_error=True) if 'include' in config: - config_included = configobj.ConfigObj(config['include']) - config.merge(config_included) + config_included = configobj.ConfigObj(config['include']) + config.merge(config_included) # django setup DJANGO_SETTINGS_MODULE = 'pyi2phosts.settings' if 'DJANGO_PROJECT_PATH' in config: - DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH'] + DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH'] 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) os.environ['DJANGO_SETTINGS_MODULE'] = DJANGO_SETTINGS_MODULE from pyi2phosts.postkey.models import i2phost @@ -43,12 +43,12 @@ validate_config(config) # result hosts.txt if args.file: - hostsfile = args.file + hostsfile = args.file elif config['hostsfile'] != None: - hostsfile = config['hostsfile'] + hostsfile = config['hostsfile'] else: - sys.stderr.write('Please specify "-f" or define "hostsfile" in config\n') - sys.exit(1) + sys.stderr.write('Please specify "-f" or define "hostsfile" in config\n') + sys.exit(1) f = open(hostsfile, 'w') # get activated hosts @@ -57,5 +57,5 @@ qs = i2phost.objects.filter(activated=True) l = qs.values('name', 'b64hash') # write final hosts.txt-format file for entry in l: - f.write(entry['name'] + '=' + entry['b64hash'] + '\n') + f.write(entry['name'] + '=' + entry['b64hash'] + '\n') f.close() diff --git a/bin/py-i2phosts-checker b/bin/py-i2phosts-checker index 17d2954..f4c494f 100755 --- a/bin/py-i2phosts-checker +++ b/bin/py-i2phosts-checker @@ -10,34 +10,34 @@ import time # parse command line options parser = argparse.ArgumentParser( - description='Hosts checker for py-i2phosts.', - epilog='Report bugs to http://zzz.i2p/topics/733') + description='Hosts checker for py-i2phosts.', + epilog='Report bugs to http://zzz.i2p/topics/733') 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', - 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', - help='config file to use') + help='config file to use') args = parser.parse_args() # read config spec = ''' - log_file = string(default='/var/log/py-i2phosts/master.log') - log_level = option('debug', 'info', 'warning', 'error', 'critical', default='info') - lookup_retries = integer(1, 20, default=2) - ''' + log_file = string(default='/var/log/py-i2phosts/master.log') + log_level = option('debug', 'info', 'warning', 'error', 'critical', default='info') + lookup_retries = integer(1, 20, default=2) + ''' spec = spec.split('\n') config = configobj.ConfigObj(args.config_file, configspec=spec) if 'include' in config: - config_included = configobj.ConfigObj(config['include']) - config.merge(config_included) + config_included = configobj.ConfigObj(config['include']) + config.merge(config_included) # django setup DJANGO_SETTINGS_MODULE = 'pyi2phosts.settings' if 'DJANGO_PROJECT_PATH' in config: - DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH'] + DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH'] 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) os.environ['DJANGO_SETTINGS_MODULE'] = DJANGO_SETTINGS_MODULE from pyi2phosts.postkey.models import i2phost @@ -50,22 +50,22 @@ validate_config(config) # configure logger if args.debug == True: - log_level = 'debug' - log_file = None + log_level = 'debug' + log_file = None elif args.verbose == True: - log_level = 'info' - log_file = None + log_level = 'info' + log_file = None else: - log_level = config['log_level'] - log_file = config['log_file'] + log_level = config['log_level'] + log_file = config['log_file'] log = get_logger(filename=log_file, log_level=log_level) # determine BOB interface address if 'bob_addr' in config: - bob_addr = config['bob_addr'] + bob_addr = config['bob_addr'] else: - log.warning('BOB address isn\'t specified in config, falling back to localhost') - bob_addr = '127.0.0.1:2827' + log.warning('BOB address isn\'t specified in config, falling back to localhost') + bob_addr = '127.0.0.1:2827' # split bob_addr to ip and port bob_ip, bob_port = bob_addr.split(':') @@ -73,37 +73,37 @@ bob_ip, bob_port = bob_addr.split(':') # connect to BOB s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: - s.connect((bob_ip, int(bob_port))) - # just receive BOB's greeting - time.sleep(1) - data = s.recv(512) - # make file object - f = s.makefile('r') + s.connect((bob_ip, int(bob_port))) + # just receive BOB's greeting + time.sleep(1) + data = s.recv(512) + # make file object + f = s.makefile('r') except socket.error, e: - log.error('failed to connect to BOB: %s', e) - sys.exit(1) + log.error('failed to connect to BOB: %s', e) + sys.exit(1) all_hosts = i2phost.objects.all().order_by('-activated', '-last_seen') log.info('starting check') for host in all_hosts: - log.debug('%s: testing...', host.name) - # get b32 address from full dest key - dest = host.b64hash - b32dest = get_b32(dest) - # do name lookup query with b32 address - # it success only if host is alive - for i in range(config['lookup_retries']): - s.send('lookup %s\n' % b32dest) - data = f.readline().rstrip('\n') - if data == 'ERROR Address Not found.': - log.debug('%s: unable to resolve, try: %s', host.name, i) - elif data == 'OK ' + host.b64hash: - log.info('alive host: %s', host.name) - # update lastseen timestamp - host.last_seen = datetime.datetime.utcnow() - host.save() - break - else: - log.warning('unexpected reply: %s', data) + log.debug('%s: testing...', host.name) + # get b32 address from full dest key + dest = host.b64hash + b32dest = get_b32(dest) + # do name lookup query with b32 address + # it success only if host is alive + for i in range(config['lookup_retries']): + s.send('lookup %s\n' % b32dest) + data = f.readline().rstrip('\n') + if data == 'ERROR Address Not found.': + log.debug('%s: unable to resolve, try: %s', host.name, i) + elif data == 'OK ' + host.b64hash: + log.info('alive host: %s', host.name) + # update lastseen timestamp + host.last_seen = datetime.datetime.utcnow() + host.save() + break + else: + log.warning('unexpected reply: %s', data) s.close() log.info('check finished') diff --git a/bin/py-i2phosts-fetcher b/bin/py-i2phosts-fetcher index 649dd55..ab05816 100755 --- a/bin/py-i2phosts-fetcher +++ b/bin/py-i2phosts-fetcher @@ -15,32 +15,32 @@ import socket # parse command line options parser = argparse.ArgumentParser( - description='Hosts fetcher for py-i2phosts.', - epilog='Report bugs to http://zzz.i2p/topics/733') + description='Hosts fetcher for py-i2phosts.', + epilog='Report bugs to http://zzz.i2p/topics/733') 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', - help='config file to use') + help='config file to use') args = parser.parse_args() # read config spec = ''' - proxyurl = string(default='http://localhost:4444/') - log_file = string(default='/var/log/py-i2phosts/fetcher.log') - log_level = option('debug', 'info', 'warning', 'error', 'critical', default='info') - ''' + proxyurl = string(default='http://localhost:4444/') + log_file = string(default='/var/log/py-i2phosts/fetcher.log') + log_level = option('debug', 'info', 'warning', 'error', 'critical', default='info') + ''' spec = spec.split('\n') config = configobj.ConfigObj(args.config_file, configspec=spec, file_error=True) if 'include' in config: - config_included = configobj.ConfigObj(config['include']) - config.merge(config_included) + config_included = configobj.ConfigObj(config['include']) + config.merge(config_included) # django setup DJANGO_SETTINGS_MODULE = 'pyi2phosts.settings' if 'DJANGO_PROJECT_PATH' in config: - DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH'] + DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH'] 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) os.environ['DJANGO_SETTINGS_MODULE'] = DJANGO_SETTINGS_MODULE from pyi2phosts.lib.utils import get_logger @@ -52,11 +52,11 @@ validate_config(config) # configure logger if args.debug == True: - log_level = 'debug' - log_file = None + log_level = 'debug' + log_file = None else: - log_level = config['log_level'] - log_file = config['log_file'] + log_level = config['log_level'] + log_file = config['log_file'] log = get_logger(filename=log_file, log_level=log_level) # we want open urls through proxy @@ -66,70 +66,70 @@ opener = urllib2.build_opener(proxy_handler) all_sources = ExternalSource.objects.filter(active=True) for source in all_sources: - log.debug('%s: starting work', source.name) - if source.last_modified: - 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 - opener.addheaders = [('If-Modified-Since', last_modified)] - log.debug('%s: appending If-Modified-Since: %s', source.name, last_modified) - if source.etag: - opener.addheaders = [('If-None-Match', source.etag)] - log.debug('%s: appending If-None-Match: %s', source.name, source.etag) - try: - log.debug('%s: sending GET...', source.name) - resp = opener.open(source.url, timeout=60) - except socket.timeout: - log.warning('%s: socket timeout', source.name) - continue - except urllib2.HTTPError, e: - if e.code == 304: - log.info('%s: not modified', source.name) - source.last_success = datetime.datetime.utcnow() - source.save() - else: - log.warning('%s: can\'t finish the request, error code: %s, reason: %s', source.name, e.code, e.reason) - continue - except urllib2.URLError, e: - log.warning('%s: failed to reach server, reason: %s', source.name, e.reason) - continue - # read data from remote and write it to local file - try: - log.debug('%s: reading response data', source.name) - content = resp.read() - except: - log.warning('%s: failed to read data', source.name) - continue - # save fetched content into temporary file - fd, tmpfile = tempfile.mkstemp(text=True) - f = os.fdopen(fd, 'w') - f.write(content) - f.close() - # get last-modified info from header - lm = resp.headers.get('Last-Modified') - if 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') - # get ETag - etag = resp.headers.get('ETag') - if etag: - log.debug('%s: ETag: %s', source.name, etag) - source.etag = etag - # form command-line for invoke injector - log.info('%s: adding hosts...', source.name) - sp_args = ['py-i2phosts-injector', '-s', '-f', tmpfile, '-d', - 'Auto-added from ' + source.name] - try: - p = subprocess.Popen(sp_args, shell=False, stdin=None, - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - except OSError, e: - log.error('failed to exec py-i2phosts-injector: %s', e) - if e.errno == errno.ENOENT: - log.error('check your PATH environment variable') - sys.exit(1) - out = p.communicate()[0] - os.remove(tmpfile) - log.info('%s: injector output: \n%s', source.name, out) - # update last_success - source.last_success = datetime.datetime.utcnow() - log.debug('%s: updating last_success timestamp: %s', source.name, source.last_success) - source.save() + log.debug('%s: starting work', source.name) + if source.last_modified: + 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 + opener.addheaders = [('If-Modified-Since', last_modified)] + log.debug('%s: appending If-Modified-Since: %s', source.name, last_modified) + if source.etag: + opener.addheaders = [('If-None-Match', source.etag)] + log.debug('%s: appending If-None-Match: %s', source.name, source.etag) + try: + log.debug('%s: sending GET...', source.name) + resp = opener.open(source.url, timeout=60) + except socket.timeout: + log.warning('%s: socket timeout', source.name) + continue + except urllib2.HTTPError, e: + if e.code == 304: + log.info('%s: not modified', source.name) + source.last_success = datetime.datetime.utcnow() + source.save() + else: + log.warning('%s: can\'t finish the request, error code: %s, reason: %s', source.name, e.code, e.reason) + continue + except urllib2.URLError, e: + log.warning('%s: failed to reach server, reason: %s', source.name, e.reason) + continue + # read data from remote and write it to local file + try: + log.debug('%s: reading response data', source.name) + content = resp.read() + except: + log.warning('%s: failed to read data', source.name) + continue + # save fetched content into temporary file + fd, tmpfile = tempfile.mkstemp(text=True) + f = os.fdopen(fd, 'w') + f.write(content) + f.close() + # get last-modified info from header + lm = resp.headers.get('Last-Modified') + if 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') + # get ETag + etag = resp.headers.get('ETag') + if etag: + log.debug('%s: ETag: %s', source.name, etag) + source.etag = etag + # form command-line for invoke injector + log.info('%s: adding hosts...', source.name) + sp_args = ['py-i2phosts-injector', '-s', '-f', tmpfile, '-d', + 'Auto-added from ' + source.name] + try: + p = subprocess.Popen(sp_args, shell=False, stdin=None, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + except OSError, e: + log.error('failed to exec py-i2phosts-injector: %s', e) + if e.errno == errno.ENOENT: + log.error('check your PATH environment variable') + sys.exit(1) + out = p.communicate()[0] + os.remove(tmpfile) + log.info('%s: injector output: \n%s', source.name, out) + # update last_success + source.last_success = datetime.datetime.utcnow() + log.debug('%s: updating last_success timestamp: %s', source.name, source.last_success) + source.save() diff --git a/bin/py-i2phosts-injector b/bin/py-i2phosts-injector index 6fb8467..a167f30 100755 --- a/bin/py-i2phosts-injector +++ b/bin/py-i2phosts-injector @@ -10,34 +10,34 @@ from django.core.exceptions import ValidationError # parse command line options parser = argparse.ArgumentParser( - description='Hosts injector for py-i2phosts.', - epilog='Report bugs to http://zzz.i2p/topics/733') + description='Hosts injector for py-i2phosts.', + epilog='Report bugs to http://zzz.i2p/topics/733') 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', - help='hosts.txt for parsing') + help='hosts.txt for parsing') 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', - help='add hosts as approved') + help='add hosts as approved') 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', - help='be completely quiet, print only errors') + help='be completely quiet, print only errors') args = parser.parse_args() # read config config = configobj.ConfigObj(args.config_file) if 'include' in config: - config_included = configobj.ConfigObj(config['include']) - config.merge(config_included) + config_included = configobj.ConfigObj(config['include']) + config.merge(config_included) # django setup DJANGO_SETTINGS_MODULE = 'pyi2phosts.settings' if 'DJANGO_PROJECT_PATH' in config: - DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH'] + DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH'] 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) os.environ['DJANGO_SETTINGS_MODULE'] = DJANGO_SETTINGS_MODULE from pyi2phosts.postkey.models import i2phost @@ -46,57 +46,57 @@ from pyi2phosts.lib.validation import validate_b64hash # determine approve hosts or not if args.approve or config.as_bool('approve'): - approved = True + approved = True else: - approved = False + approved = False # turn on output supressing if quiet if args.quiet: - args.supress = True + args.supress = True # determine what hosts.txt file we should parse if args.hostsfile: - hostsfile = args.hostsfile + hostsfile = args.hostsfile else: - env = os.environ - if 'HOME' in env: - hostsfile = os.environ['HOME'] + '/.i2p/hosts.txt' - else: - sys.stderr.write('unable to determine hosts file for parsing\n') - sys.exit(1) + env = os.environ + if 'HOME' in env: + hostsfile = os.environ['HOME'] + '/.i2p/hosts.txt' + else: + sys.stderr.write('unable to determine hosts file for parsing\n') + sys.exit(1) f = open(args.hostsfile, 'r') for line in f: - # ignore comments and empty lines - if line.startswith('#') or line.isspace(): - continue - if line.find('=') == -1: - sys.stdout.write('Invalid line: %s\n' % line) - continue - # strip trailing '\n' - line = line.rstrip('\n') - entry = line.split('=', 1) - try: - hostname = validate_hostname(entry[0]) - base64 = validate_b64hash(entry[1], check_uniq=False) # don't require uniqueness - except ValidationError, e: - sys.stdout.write('validation error: %s: %s\n\n' % (e, line)) - else: - # Check for already existed hosts in database to avoid unneeded INSERTs - # beacuse they will fail anyway. - try: - h = i2phost.objects.get(name=hostname) - except i2phost.DoesNotExist: - if not args.quiet: - sys.stdout.write('Adding %s\n' % hostname) - host = i2phost(name=hostname, b64hash=base64, - description=args.description, - date_added=datetime.datetime.utcnow(), - activated=False, external=True, approved=approved) - host.save() - else: - if not args.supress: - sys.stdout.write('Host %s already exists\n' % hostname) - if h.b64hash != base64: - sys.stdout.write('Key conflict for host: %s\n' % hostname) + # ignore comments and empty lines + if line.startswith('#') or line.isspace(): + continue + if line.find('=') == -1: + sys.stdout.write('Invalid line: %s\n' % line) + continue + # strip trailing '\n' + line = line.rstrip('\n') + entry = line.split('=', 1) + try: + hostname = validate_hostname(entry[0]) + base64 = validate_b64hash(entry[1], check_uniq=False) # don't require uniqueness + except ValidationError, e: + sys.stdout.write('validation error: %s: %s\n\n' % (e, line)) + else: + # Check for already existed hosts in database to avoid unneeded INSERTs + # beacuse they will fail anyway. + try: + h = i2phost.objects.get(name=hostname) + except i2phost.DoesNotExist: + if not args.quiet: + sys.stdout.write('Adding %s\n' % hostname) + host = i2phost(name=hostname, b64hash=base64, + description=args.description, + date_added=datetime.datetime.utcnow(), + activated=False, external=True, approved=approved) + host.save() + else: + if not args.supress: + sys.stdout.write('Host %s already exists\n' % hostname) + if h.b64hash != base64: + sys.stdout.write('Key conflict for host: %s\n' % hostname) f.close() diff --git a/bin/py-i2phosts-maint b/bin/py-i2phosts-maint index d445a01..95693f2 100755 --- a/bin/py-i2phosts-maint +++ b/bin/py-i2phosts-maint @@ -10,37 +10,37 @@ import configobj # parse command line options parser = argparse.ArgumentParser( - description='Hosts maintainer for py-i2phosts.', - epilog='Report bugs to http://zzz.i2p/topics/733') + description='Hosts maintainer for py-i2phosts.', + epilog='Report bugs to http://zzz.i2p/topics/733') 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', - help='config file to use') + help='config file to use') args = parser.parse_args() # read config spec = ''' - log_file = string(default='/var/log/py-i2phosts/maintainer.log') - log_level = option('debug', 'info', 'warning', 'error', 'critical', default='info') - external_inactive_max = integer(default=365) - internal_inactive_max = integer(default=14) - external_expires = integer(default=30) - internal_expires = integer(default=30) - activate_min_delay = integer(default=3) - keep_expired = integer(default=730) - ''' + log_file = string(default='/var/log/py-i2phosts/maintainer.log') + log_level = option('debug', 'info', 'warning', 'error', 'critical', default='info') + external_inactive_max = integer(default=365) + internal_inactive_max = integer(default=14) + external_expires = integer(default=30) + internal_expires = integer(default=30) + activate_min_delay = integer(default=3) + keep_expired = integer(default=730) + ''' spec = spec.split('\n') config = configobj.ConfigObj(args.config_file, configspec=spec) if 'include' in config: - config_included = configobj.ConfigObj(config['include']) - config.merge(config_included) + config_included = configobj.ConfigObj(config['include']) + config.merge(config_included) # django setup DJANGO_SETTINGS_MODULE = 'pyi2phosts.settings' if 'DJANGO_PROJECT_PATH' in config: - DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH'] + DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH'] 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) os.environ['DJANGO_SETTINGS_MODULE'] = DJANGO_SETTINGS_MODULE from pyi2phosts.postkey.models import i2phost @@ -52,68 +52,68 @@ validate_config(config) # configure logger if args.debug == True: - log_level = 'debug' - log_file = None + log_level = 'debug' + log_file = None else: - log_level = config['log_level'] - log_file = config['log_file'] + log_level = config['log_level'] + log_file = config['log_file'] log = get_logger(filename=log_file, log_level=log_level) all_hosts = i2phost.objects.all() log.info('starting maintenance') for host in all_hosts: - # how long host was added - dl = datetime.datetime.utcnow() - host.date_added - if host.last_seen == None: - # delete external hosts which we never seen after X days of inactivity - if host.external == True: - if dl > datetime.timedelta(days=config['external_inactive_max']): - log.info('deleting %s, reason: external host, never seen for %s days', - host.name, config['external_inactive_max']) - host.delete() - continue - # delete hosts added by us and never seen after X days of inactivity - else: - if dl > datetime.timedelta(days=config['internal_inactive_max']): - log.info('deleting %s, reason: internal host, never seen for %s days', - host.name, config['internal_inactive_max']) - host.delete() - continue - else: - # configure registration period for hosts - if host.external == True: - timedelta = datetime.timedelta(days=config['external_expires']) - else: - timedelta = datetime.timedelta(days=config['internal_expires']) - # get current host expiration date from database - if host.expires == None: - # workaround for situation when we updating expires first time - expires_current = datetime.datetime.utcnow().date() - else: - expires_current = host.expires - # calculate new expiration date - expires_new = host.last_seen + timedelta - expires_new = expires_new.date() - # update expiration date only if changed - if expires_new > expires_current: - log.debug('updating expires for %s', host.name) - host.expires = expires_new - # deactivate if expired - min_dl = datetime.timedelta(days=config['activate_min_delay']) - if host.expires < datetime.datetime.utcnow().date(): - if host.activated == True: - log.info('deactivating %s, reason: expired', host.name) - host.activated = False - # 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: - log.info('activating %s, reason: host up and added more than %s days ago', - host.name, config['activate_min_delay']) - host.activated = True - # if expired X days ago then delete - dl_e = datetime.datetime.utcnow().date() - host.expires - if dl_e > datetime.timedelta(days=config['keep_expired']): - log.info('deleting %s, reason: expired %s days ago', - host.name, config['keep_expired']) - host.delete() - continue - host.save() + # how long host was added + dl = datetime.datetime.utcnow() - host.date_added + if host.last_seen == None: + # delete external hosts which we never seen after X days of inactivity + if host.external == True: + if dl > datetime.timedelta(days=config['external_inactive_max']): + log.info('deleting %s, reason: external host, never seen for %s days', + host.name, config['external_inactive_max']) + host.delete() + continue + # delete hosts added by us and never seen after X days of inactivity + else: + if dl > datetime.timedelta(days=config['internal_inactive_max']): + log.info('deleting %s, reason: internal host, never seen for %s days', + host.name, config['internal_inactive_max']) + host.delete() + continue + else: + # configure registration period for hosts + if host.external == True: + timedelta = datetime.timedelta(days=config['external_expires']) + else: + timedelta = datetime.timedelta(days=config['internal_expires']) + # get current host expiration date from database + if host.expires == None: + # workaround for situation when we updating expires first time + expires_current = datetime.datetime.utcnow().date() + else: + expires_current = host.expires + # calculate new expiration date + expires_new = host.last_seen + timedelta + expires_new = expires_new.date() + # update expiration date only if changed + if expires_new > expires_current: + log.debug('updating expires for %s', host.name) + host.expires = expires_new + # deactivate if expired + min_dl = datetime.timedelta(days=config['activate_min_delay']) + if host.expires < datetime.datetime.utcnow().date(): + if host.activated == True: + log.info('deactivating %s, reason: expired', host.name) + host.activated = False + # 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: + log.info('activating %s, reason: host up and added more than %s days ago', + host.name, config['activate_min_delay']) + host.activated = True + # if expired X days ago then delete + dl_e = datetime.datetime.utcnow().date() - host.expires + if dl_e > datetime.timedelta(days=config['keep_expired']): + log.info('deleting %s, reason: expired %s days ago', + host.name, config['keep_expired']) + host.delete() + continue + host.save() diff --git a/bin/py-i2phosts-master b/bin/py-i2phosts-master index 0dc8d26..8dcc8ab 100755 --- a/bin/py-i2phosts-master +++ b/bin/py-i2phosts-master @@ -13,119 +13,125 @@ import threading import daemon # workaround for python-daemon >= 1.6 try: +<<<<<<< HEAD import daemon.pidlockfile as pidfile except ImportError: 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): - def __init__(self): - threading.Thread.__init__(self) - self.setDaemon(True) + def __init__(self): + threading.Thread.__init__(self) + self.setDaemon(True) class FetcherThread(Thread): - """ Run py-i2phosts-fetcher periodically """ + """ Run py-i2phosts-fetcher periodically """ - def run(self): - while True: - run_prog('py-i2phosts-fetcher') - time.sleep(float(config['fetch_interval'])) + def run(self): + while True: + run_prog('py-i2phosts-fetcher') + time.sleep(float(config['fetch_interval'])) 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): - while True: - run_prog('py-i2phosts-checker') - run_prog('py-i2phosts-maint') - run_prog('py-i2phosts-builder') - time.sleep(float(config['check_interval'])) + def run(self): + while True: + run_prog('py-i2phosts-checker') + run_prog('py-i2phosts-maint') + run_prog('py-i2phosts-builder') + time.sleep(float(config['check_interval'])) def run_prog(prog): - try: - log.info('starting: %s', prog) - sp_args = [prog] - if args.debug or args.verbose: - sp_args.append('-d') - p = subprocess.Popen(sp_args, shell=False) - except OSError, e: - log.error('failed to exec %s: %s', prog, e) - if e.errno == errno.ENOENT: - log.error(' maybe it isn\'t in PATH?') - else: - p.wait() - log.info('finished: %s', prog) + try: + log.info('starting: %s', prog) + sp_args = [prog] + if args.debug or args.verbose: + sp_args.append('-d') + p = subprocess.Popen(sp_args, shell=False) + except OSError, e: + log.error('failed to exec %s: %s', prog, e) + if e.errno == errno.ENOENT: + log.error(' maybe it isn\'t in PATH?') + else: + p.wait() + log.info('finished: %s', prog) def main(): - def run_fetcher(): - fetcher = FetcherThread() - fetcher.start() - return fetcher - - def run_checker(): - checker = CheckerThread() - checker.start() - return checker - - # if we're just started, wait while fetcher get some new hosts - fetcher = run_fetcher() - log.debug('just started, delaying checker run for 300 secs') - fetcher.join(300) # wait for 5 mins - # start checker and other - checker = run_checker() - - while True: - log.debug('checking fetcher and checker threads status') - if fetcher.isAlive() == False: - log.warning('fetcher thread is dead, respawning...') - fetcher = run_fetcher() - else: - log.debug('fetcher thread: alive') - if checker.isAlive() == False: - log.warning('checker thread is dead, respawning...') - checker = run_checker() - else: - log.debug('checker thread: alive') - # do check every 30 mins - time.sleep(1800) + def run_fetcher(): + fetcher = FetcherThread() + fetcher.start() + return fetcher + + def run_checker(): + checker = CheckerThread() + checker.start() + return checker + + # if we're just started, wait while fetcher get some new hosts + fetcher = run_fetcher() + log.debug('just started, delaying checker run for 300 secs') + fetcher.join(300) # wait for 5 mins + # start checker and other + checker = run_checker() + + while True: + log.debug('checking fetcher and checker threads status') + if fetcher.isAlive() == False: + log.warning('fetcher thread is dead, respawning...') + fetcher = run_fetcher() + else: + log.debug('fetcher thread: alive') + if checker.isAlive() == False: + log.warning('checker thread is dead, respawning...') + checker = run_checker() + else: + log.debug('checker thread: alive') + # do check every 30 mins + time.sleep(1800) # parse command line options parser = argparse.ArgumentParser( - description='Master daemon for py-i2phosts.', - epilog='Report bugs to http://zzz.i2p/topics/733') + description='Master daemon for py-i2phosts.', + epilog='Report bugs to http://zzz.i2p/topics/733') 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', - 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', - help='config file to use') + help='config file to use') args = parser.parse_args() # read and validate config spec = ''' - log_file = string(default='/var/log/py-i2phosts/master.log') - log_level = option('debug', 'info', 'warning', 'error', 'critical', default='info') - pid_file = string(default='/var/run/py-i2phosts/master.pid') - runas = string(default='_pyi2phosts') - check_interval = integer(default=43200) - fetch_interval = integer(default=1800) - ''' + log_file = string(default='/var/log/py-i2phosts/master.log') + log_level = option('debug', 'info', 'warning', 'error', 'critical', default='info') + pid_file = string(default='/var/run/py-i2phosts/master.pid') + runas = string(default='_pyi2phosts') + check_interval = integer(default=43200) + fetch_interval = integer(default=1800) + ''' spec = spec.split('\n') config = configobj.ConfigObj(args.config_file, configspec=spec) if 'include' in config: - config_included = configobj.ConfigObj(config['include']) - config.merge(config_included) + config_included = configobj.ConfigObj(config['include']) + config.merge(config_included) # django setup DJANGO_SETTINGS_MODULE = 'pyi2phosts.settings' if 'DJANGO_PROJECT_PATH' in config: - DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH'] + DJANGO_PROJECT_PATH = config['DJANGO_PROJECT_PATH'] 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) os.environ['DJANGO_SETTINGS_MODULE'] = DJANGO_SETTINGS_MODULE from pyi2phosts.lib.utils import get_logger @@ -136,32 +142,32 @@ validate_config(config) # configure logger if args.debug == True: - log_level = 'debug' - log_file = None + log_level = 'debug' + log_file = None elif args.verbose == True: - log_level = 'info' - log_file = None + log_level = 'info' + log_file = None else: - log_level = config['log_level'] - log_file = config['log_file'] + log_level = config['log_level'] + log_file = config['log_file'] if not args.debug and not args.verbose: - # get pid object for daemon - pid = pidfile.TimeoutPIDLockFile(config['pid_file'], 10) - # create daemon context - d = daemon.DaemonContext(pidfile=pid, umask=077) - # write stderr to logfile # FIXME: and how we will deal with log rotation? - logfile = open(config['log_file'], 'a') - d.stderr = logfile - d.stdout = logfile - # drop privileges when started as root - if os.getuid() == 0: - runas = config['runas'] - pw_entry = pwd.getpwnam(runas) - d.uid = pw_entry[2] - d.gid = pw_entry[3] - os.chown(config['log_file'], d.uid, d.gid) - d.open() # become daemon + # get pid object for daemon + pid = pidfile.TimeoutPIDLockFile(config['pid_file'], 10) + # create daemon context + d = daemon.DaemonContext(pidfile=pid, umask=077) + # write stderr to logfile # FIXME: and how we will deal with log rotation? + logfile = open(config['log_file'], 'a') + d.stderr = logfile + d.stdout = logfile + # drop privileges when started as root + if os.getuid() == 0: + runas = config['runas'] + pw_entry = pwd.getpwnam(runas) + d.uid = pw_entry[2] + d.gid = pw_entry[3] + os.chown(config['log_file'], d.uid, d.gid) + d.open() # become daemon log = get_logger(filename=log_file, log_level=log_level) log.info('started') main() diff --git a/pyi2phosts/api/urls.py b/pyi2phosts/api/urls.py index 4d80e1e..fb0217c 100644 --- a/pyi2phosts/api/urls.py +++ b/pyi2phosts/api/urls.py @@ -1,5 +1,5 @@ from django.conf.urls import * urlpatterns = patterns('pyi2phosts.api.views', - url(r'^all/$', 'all'), + url(r'^all/$', 'all'), ) diff --git a/pyi2phosts/api/views.py b/pyi2phosts/api/views.py index 8ef3608..bfaf86c 100644 --- a/pyi2phosts/api/views.py +++ b/pyi2phosts/api/views.py @@ -6,11 +6,11 @@ from pyi2phosts.postkey.models import i2phost from pyi2phosts.lib.utils import get_b32 def all(request): - """Return all hosts in { "b32": "last seen timestamp" } form. Implemented by zzz request. """ - # all hosts seen at least once - queryset = i2phost.objects.exclude(last_seen=None) - json_dict = {} - for host in queryset: - # pass last_seen to json in unixtime - json_dict[get_b32(host.b64hash)] = host.last_seen.strftime("%s") - return HttpResponse(json.dumps(json_dict), mimetype="application/json") + """Return all hosts in { "b32": "last seen timestamp" } form. Implemented by zzz request. """ + # all hosts seen at least once + queryset = i2phost.objects.exclude(last_seen=None) + json_dict = {} + for host in queryset: + # pass last_seen to json in unixtime + json_dict[get_b32(host.b64hash)] = host.last_seen.strftime("%s") + return HttpResponse(json.dumps(json_dict), mimetype="application/json") diff --git a/pyi2phosts/extsources/admin.py b/pyi2phosts/extsources/admin.py index ca35f7c..aaa83ab 100644 --- a/pyi2phosts/extsources/admin.py +++ b/pyi2phosts/extsources/admin.py @@ -4,7 +4,7 @@ from pyi2phosts.extsources.models import ExternalSource class ExternalSourceAdmin(admin.ModelAdmin): - list_display = ('name', 'url', 'description', 'last_success', 'last_modified', 'etag', 'active') - list_editable = ['active'] + list_display = ('name', 'url', 'description', 'last_success', 'last_modified', 'etag', 'active') + list_editable = ['active'] admin.site.register(ExternalSource, ExternalSourceAdmin) diff --git a/pyi2phosts/extsources/models.py b/pyi2phosts/extsources/models.py index 5c54351..de4824b 100644 --- a/pyi2phosts/extsources/models.py +++ b/pyi2phosts/extsources/models.py @@ -3,14 +3,14 @@ from django.db import models from pyi2phosts.lib.validation import validate_i2purl class ExternalSource(models.Model): - name = models.CharField(max_length=128, unique=True) - url = models.CharField(max_length=256, validators=[validate_i2purl]) - description = models.CharField(max_length=512, blank=True) - last_modified = models.DateTimeField(null=True, blank=True) - last_success = models.DateTimeField(null=True, blank=True) - etag = models.CharField(max_length=256, blank=True) - active = models.BooleanField(default=True) + name = models.CharField(max_length=128, unique=True) + url = models.CharField(max_length=256, validators=[validate_i2purl]) + description = models.CharField(max_length=512, blank=True) + last_modified = models.DateTimeField(null=True, blank=True) + last_success = models.DateTimeField(null=True, blank=True) + etag = models.CharField(max_length=256, blank=True) + active = models.BooleanField(default=True) - def __unicode__(self): - return self.name + def __unicode__(self): + return self.name diff --git a/pyi2phosts/jump/urls.py b/pyi2phosts/jump/urls.py index 7fb61b2..fbd5d74 100644 --- a/pyi2phosts/jump/urls.py +++ b/pyi2phosts/jump/urls.py @@ -1,7 +1,7 @@ from django.conf.urls import * urlpatterns = patterns('pyi2phosts.jump.views', - (r'^([^$/]+)', 'jumper'), - (r'', 'index'), + (r'^([^$/]+)', 'jumper'), + (r'', 'index'), ) diff --git a/pyi2phosts/jump/views.py b/pyi2phosts/jump/views.py index 757c5d1..8e2b547 100644 --- a/pyi2phosts/jump/views.py +++ b/pyi2phosts/jump/views.py @@ -10,45 +10,45 @@ from pyi2phosts.postkey.models import i2phost from pyi2phosts.lib.validation import validate_hostname def jumper(request, host): - """Actually do jumps.""" - try: - hostname = validate_hostname(host) - except ValidationError, e: - return render_to_response('jump-error.html', { - 'title': settings.SITE_NAME, - 'error': e, - }, context_instance=RequestContext(request)) - try: - h = i2phost.objects.get(name=hostname) - except i2phost.DoesNotExist: - return render_to_response('jump-unknown.html', { - 'title': settings.SITE_NAME, - }, context_instance=RequestContext(request)) - if h.activated == True: - key = h.b64hash - else: - return redirect('/search/?q=' + hostname) - # begin forming url - 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' - pattern = host + r'/(.+)' - m = re.search(pattern, request.get_full_path()) - if m: - params = m.group(1) - url += '/' + params - # determine how we should pass i2paddresshelper - # http://zzz.i2p/oldnews.html#jump - if params.find('?') == -1: - suffix = '?' - else: - suffix = '&' - url += suffix + 'i2paddresshelper=' + key - else: - url += '/?i2paddresshelper=' + key - return render_to_response('jump.html', { - 'title': settings.SITE_NAME, - 'url': url, - }, context_instance=RequestContext(request)) + """Actually do jumps.""" + try: + hostname = validate_hostname(host) + except ValidationError, e: + return render_to_response('jump-error.html', { + 'title': settings.SITE_NAME, + 'error': e, + }, context_instance=RequestContext(request)) + try: + h = i2phost.objects.get(name=hostname) + except i2phost.DoesNotExist: + return render_to_response('jump-unknown.html', { + 'title': settings.SITE_NAME, + }, context_instance=RequestContext(request)) + if h.activated == True: + key = h.b64hash + else: + return redirect('/search/?q=' + hostname) + # begin forming url + 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' + pattern = host + r'/(.+)' + m = re.search(pattern, request.get_full_path()) + if m: + params = m.group(1) + url += '/' + params + # determine how we should pass i2paddresshelper + # http://zzz.i2p/oldnews.html#jump + if params.find('?') == -1: + suffix = '?' + else: + suffix = '&' + url += suffix + 'i2paddresshelper=' + key + else: + url += '/?i2paddresshelper=' + key + return render_to_response('jump.html', { + 'title': settings.SITE_NAME, + 'url': url, + }, context_instance=RequestContext(request)) def index(request): - return redirect('/') + return redirect('/') diff --git a/pyi2phosts/latest/urls.py b/pyi2phosts/latest/urls.py index 61e55ab..e378268 100644 --- a/pyi2phosts/latest/urls.py +++ b/pyi2phosts/latest/urls.py @@ -3,7 +3,7 @@ from pyi2phosts.lib.rss import LatestHostsFeed from pyi2phosts.latest.views import LatestHostsListsView urlpatterns = patterns('', - url(r'^$', LatestHostsListsView.as_view(), name='latest'), - url(r'^rss/$', LatestHostsFeed(), name='latest-rss'), + url(r'^$', LatestHostsListsView.as_view(), name='latest'), + url(r'^rss/$', LatestHostsFeed(), name='latest-rss'), ) diff --git a/pyi2phosts/latest/views.py b/pyi2phosts/latest/views.py index 54bcc0a..50a89d8 100644 --- a/pyi2phosts/latest/views.py +++ b/pyi2phosts/latest/views.py @@ -6,25 +6,25 @@ from pyi2phosts.postkey.models import i2phost from pyi2phosts.lib.generic import LocalObjectList def get_latest(): - now_date = datetime.datetime.utcnow() - start_date = now_date - datetime.timedelta(days=settings.LATEST_DAY_COUNT) - qs = i2phost.objects.filter(activated=True, - date_added__range=(start_date, now_date)).order_by("-date_added")[:settings.LATEST_HOSTS_COUNT] - return qs + now_date = datetime.datetime.utcnow() + start_date = now_date - datetime.timedelta(days=settings.LATEST_DAY_COUNT) + qs = i2phost.objects.filter(activated=True, + date_added__range=(start_date, now_date)).order_by("-date_added")[:settings.LATEST_HOSTS_COUNT] + return qs class LatestHostsListsView(LocalObjectList): - """ Renders list of latest active hosts added """ + """ Renders list of latest active hosts added """ - def get_context_data(self, **kwargs): - context = super(LatestHostsListsView, self).get_context_data(**kwargs) - context.update({ - 'title': settings.SITE_NAME, - 'day_count': settings.LATEST_DAY_COUNT, - 'hosts_count': settings.LATEST_HOSTS_COUNT - }) - return context + def get_context_data(self, **kwargs): + context = super(LatestHostsListsView, self).get_context_data(**kwargs) + context.update({ + 'title': settings.SITE_NAME, + 'day_count': settings.LATEST_DAY_COUNT, + 'hosts_count': settings.LATEST_HOSTS_COUNT + }) + return context - queryset = get_latest() - template_name = 'latest.html' - context_object_name = 'host_list' - paginate_by = 40 + queryset = get_latest() + template_name = 'latest.html' + context_object_name = 'host_list' + paginate_by = 40 diff --git a/pyi2phosts/lib/generic.py b/pyi2phosts/lib/generic.py index 53ed292..5085c60 100755 --- a/pyi2phosts/lib/generic.py +++ b/pyi2phosts/lib/generic.py @@ -9,58 +9,58 @@ from pyi2phosts.postkey.models import i2phost from pyi2phosts.postkey.templatetags import paginator 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): - context = super(LocalTemplateView, self).get_context_data(**kwargs) - context.update({ - 'title': settings.SITE_NAME, - 'domain': settings.DOMAIN, - 'b64': settings.MY_B64, - 'b32': get_b32(settings.MY_B64) - }) - return context + def get_context_data(self, **kwargs): + context = super(LocalTemplateView, self).get_context_data(**kwargs) + context.update({ + 'title': settings.SITE_NAME, + 'domain': settings.DOMAIN, + 'b64': settings.MY_B64, + 'b32': get_b32(settings.MY_B64) + }) + return context class LocalObjectList(ListView): - """ Renders some list of objects """ + """ Renders some list of objects """ - def get_context_data(self, **kwargs): - context = super(LocalObjectList, self).get_context_data(**kwargs) - context.update({ - 'title': settings.SITE_NAME, - }) - return context + def get_context_data(self, **kwargs): + context = super(LocalObjectList, self).get_context_data(**kwargs) + context.update({ + 'title': settings.SITE_NAME, + }) + return context 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) - template_name = 'faq.html' - context_object_name = 'sources_list' + queryset = ExternalSource.objects.filter(active=True) + template_name = 'faq.html' + context_object_name = 'sources_list' class HostsListsView(LocalObjectList): - """ Renders list of active hosts """ + """ Renders list of active hosts """ - def get_queryset(self): - allowed_orders = ['name', 'last_seen', 'date_added'] - self.order_by = self.request.GET.get('order', 'name') - if self.order_by not in allowed_orders: - self.order_by = 'name' - qs = super(HostsListsView, self).get_queryset() - return qs.order_by(self.order_by) + def get_queryset(self): + allowed_orders = ['name', 'last_seen', 'date_added'] + self.order_by = self.request.GET.get('order', 'name') + if self.order_by not in allowed_orders: + self.order_by = 'name' + qs = super(HostsListsView, self).get_queryset() + return qs.order_by(self.order_by) - def get_context_data(self, **kwargs): - """ we should pass order_by to template to not lose it while paginating """ - context = super(LocalObjectList, self).get_context_data(**kwargs) - context.update({ - 'order': self.order_by, - }) - return context + def get_context_data(self, **kwargs): + """ we should pass order_by to template to not lose it while paginating """ + context = super(LocalObjectList, self).get_context_data(**kwargs) + context.update({ + 'order': self.order_by, + }) + return context - queryset = i2phost.objects.filter(activated=True) - template_name = 'browse.html' - context_object_name = 'host_list' - paginate_by = 40 + queryset = i2phost.objects.filter(activated=True) + template_name = 'browse.html' + context_object_name = 'host_list' + paginate_by = 40 diff --git a/pyi2phosts/lib/rss.py b/pyi2phosts/lib/rss.py index 5f7c6bf..5c680e9 100755 --- a/pyi2phosts/lib/rss.py +++ b/pyi2phosts/lib/rss.py @@ -5,33 +5,33 @@ from pyi2phosts.postkey.models import i2phost from pyi2phosts.latest.views import get_latest class AliveHostsFeed(Feed): - """ Generate RSS feed with all alive hosts """ + """ Generate RSS feed with all alive hosts """ - title = settings.DOMAIN + ' alive hosts' - # FIXME: make this URL more dynamic - link = 'http://' + settings.DOMAIN + '/browse/' - description = 'All known active hosts inside I2P' + title = settings.DOMAIN + ' alive hosts' + # FIXME: make this URL more dynamic + link = 'http://' + settings.DOMAIN + '/browse/' + description = 'All known active hosts inside I2P' - def items(self): - return i2phost.objects.filter(activated=True).order_by('name') + def items(self): + return i2phost.objects.filter(activated=True).order_by('name') - def item_title(self, item): - return item.name + def item_title(self, item): + return item.name - def item_link(self, item): - return 'http://' + item.name + '/?i2paddresshelper=' + item.b64hash + def item_link(self, item): + return 'http://' + item.name + '/?i2paddresshelper=' + item.b64hash - def item_description(self, item): - return item.description + def item_description(self, item): + return item.description class LatestHostsFeed(AliveHostsFeed): - """ Generate RSS feed with freshly added hosts """ + """ Generate RSS feed with freshly added hosts """ - title = settings.DOMAIN + ' latest hosts' - # FIXME: make this URL more dynamic - link = 'http://' + settings.DOMAIN + '/latest/' - description = 'Freshly added hosts' + title = settings.DOMAIN + ' latest hosts' + # FIXME: make this URL more dynamic + link = 'http://' + settings.DOMAIN + '/latest/' + description = 'Freshly added hosts' - def items(self): - return get_latest() + def items(self): + return get_latest() diff --git a/pyi2phosts/lib/utils.py b/pyi2phosts/lib/utils.py index df8c362..0008902 100644 --- a/pyi2phosts/lib/utils.py +++ b/pyi2phosts/lib/utils.py @@ -8,59 +8,59 @@ import base64 from logging import handlers def get_logger(filename=None, log_level='debug'): - """ Prepare logger instance for our scripts """ + """ Prepare logger instance for our scripts """ - # workaround for django - if hasattr(logging, "web_logger"): - return logging.web_logger + # workaround for django + if hasattr(logging, "web_logger"): + return logging.web_logger - LEVELS = { - 'debug': logging.DEBUG, - 'info': logging.INFO, - 'warning': logging.WARNING, - 'error': logging.ERROR, - 'critical': logging.CRITICAL - } - level = LEVELS.get(log_level, logging.NOTSET) - format = '%(asctime)s %(module)s:%(lineno)d[%(process)d] %(levelname)s: %(message)s' - formatter = logging.Formatter(format) - logger = logging.getLogger(__name__) - logger.setLevel(level) - if filename: - handler = logging.handlers.WatchedFileHandler(filename) - else: - handler = logging.StreamHandler() - handler.setFormatter(formatter) - logger.addHandler(handler) + LEVELS = { + 'debug': logging.DEBUG, + 'info': logging.INFO, + 'warning': logging.WARNING, + 'error': logging.ERROR, + 'critical': logging.CRITICAL + } + level = LEVELS.get(log_level, logging.NOTSET) + format = '%(asctime)s %(module)s:%(lineno)d[%(process)d] %(levelname)s: %(message)s' + formatter = logging.Formatter(format) + logger = logging.getLogger(__name__) + logger.setLevel(level) + if filename: + handler = logging.handlers.WatchedFileHandler(filename) + else: + handler = logging.StreamHandler() + handler.setFormatter(formatter) + logger.addHandler(handler) - # workaround for django - logging.web_logger = logger + # workaround for django + logging.web_logger = logger - return logger + return logger def validate_config(config): - """ Validate configobj config """ - validator = validate.Validator() - results = config.validate(validator) - if results != True: - for (section_list, key, _) in configobj.flatten_errors(config, results): - if key is not None: - sys.stderr.write('The "%s" key in the section "%s" failed validation' % - (key, ', '.join(section_list))) - else: - sys.stderr.write('The following section was missing:%s ' % - ', '.join(section_list)) - sys.exit(1) + """ Validate configobj config """ + validator = validate.Validator() + results = config.validate(validator) + if results != True: + for (section_list, key, _) in configobj.flatten_errors(config, results): + if key is not None: + sys.stderr.write('The "%s" key in the section "%s" failed validation' % + (key, ', '.join(section_list))) + else: + sys.stderr.write('The following section was missing:%s ' % + ', '.join(section_list)) + sys.exit(1) def get_b32(dest): - """ Calculate base32 hash from base64 """ - try: - raw_key = base64.b64decode(dest.encode('utf-8'), '-~') - except TypeError: - return 'corrupted_base64_hash' - else: - hash = hashlib.sha256(raw_key) - b32 = base64.b32encode(hash.digest()).lower().replace('=', '')+'.b32.i2p' - return b32 + """ Calculate base32 hash from base64 """ + try: + raw_key = base64.b64decode(dest.encode('utf-8'), '-~') + except TypeError: + return 'corrupted_base64_hash' + else: + hash = hashlib.sha256(raw_key) + b32 = base64.b32encode(hash.digest()).lower().replace('=', '')+'.b32.i2p' + return b32 diff --git a/pyi2phosts/lib/validation.py b/pyi2phosts/lib/validation.py index 3e00412..ca0de35 100755 --- a/pyi2phosts/lib/validation.py +++ b/pyi2phosts/lib/validation.py @@ -9,116 +9,116 @@ from pyi2phosts.postkey.models import i2phost def validate_hostname(data): - """ - Here we do hostname validation as described in - http://www.i2p2.i2p/naming.html and some additional checks - described in http://zzz.i2p/topics/739 - """ - # convert hostname to lowercase and strip leading and trailing whitespaces - data = data.lower().strip() - # do lenght check here for avoiding django.db.utils.DatabaseError exceptions - # when trying to add too long hostname with py-i2phosts-injector - if len(data) > 67: - raise ValidationError(_('Too long hostname (should be 67 chars max)')) - # Must end with '.i2p'. - if re.match(r'.*\.i2p$', data) == None: - raise ValidationError(_('Hostname doesn\'t ends with .i2p')) - # Base 32 hostnames (*.b32.i2p) are not allowed - if re.match(r'.*\.b32\.i2p$', data): - raise ValidationError(_('Base32 hostnames are not allowed')) - # prevent common errors - if re.match(r'\.i2p$', data): - raise ValidationError(_('Incomplete hostname')) - if re.match(r'^http:/', data): - raise ValidationError(_('Do not paste full URL, just domain')) - # Must not contain '..' - if re.search(r'\.\.', data): - raise ValidationError(_('".." in hostname')) - # Allow only 4ld domains and below - if data.count('.') > 3: - raise ValidationError(_('Subdomains deeper than 4LD are not allowed')) - # Must contain only [a-z] [0-9] '.' and '-' - h = re.match(r'([a-z0-9.-]+)\.i2p$', data) - if h == None: - raise ValidationError(_('Illegal characters in hostname')) - else: - namepart = h.groups()[0] - # Must not start with '.' or '-' - if re.match(r'^\.|-', namepart): - raise ValidationError(_('Hostname must not starts with "." or "-"')) - # Must not contain '.-' or '-.' (as of 0.6.1.33) - if re.search(r'(\.-)|(-\.)', namepart): - raise ValidationError(_('Hostname contain ".-" or "-."')) - # Must not contain '--' except in 'xn--' for IDN - if re.search(r'(? 67: + raise ValidationError(_('Too long hostname (should be 67 chars max)')) + # Must end with '.i2p'. + if re.match(r'.*\.i2p$', data) == None: + raise ValidationError(_('Hostname doesn\'t ends with .i2p')) + # Base 32 hostnames (*.b32.i2p) are not allowed + if re.match(r'.*\.b32\.i2p$', data): + raise ValidationError(_('Base32 hostnames are not allowed')) + # prevent common errors + if re.match(r'\.i2p$', data): + raise ValidationError(_('Incomplete hostname')) + if re.match(r'^http:/', data): + raise ValidationError(_('Do not paste full URL, just domain')) + # Must not contain '..' + if re.search(r'\.\.', data): + raise ValidationError(_('".." in hostname')) + # Allow only 4ld domains and below + if data.count('.') > 3: + raise ValidationError(_('Subdomains deeper than 4LD are not allowed')) + # Must contain only [a-z] [0-9] '.' and '-' + h = re.match(r'([a-z0-9.-]+)\.i2p$', data) + if h == None: + raise ValidationError(_('Illegal characters in hostname')) + else: + namepart = h.groups()[0] + # Must not start with '.' or '-' + if re.match(r'^\.|-', namepart): + raise ValidationError(_('Hostname must not starts with "." or "-"')) + # Must not contain '.-' or '-.' (as of 0.6.1.33) + if re.search(r'(\.-)|(-\.)', namepart): + raise ValidationError(_('Hostname contain ".-" or "-."')) + # Must not contain '--' except in 'xn--' for IDN + if re.search(r'(? 616: - raise ValidationError(_('Specified base64 hash is bigger than 616 bytes')) - # keys with cert may ends with anything, so check is relaxed - if length > 516 and re.match(r'[a-zA-Z0-9\-~=]+$', data) == None: - raise ValidationError(_('Invalid characters in base64 hash')) - # base64-validity test - if length > 516: - # we need temporary variable here to avoid modifying main "data" - test_data = data - # add pad-characters needed for proper decoding cos i2p does not - for i in range(4): - quanta, leftover = divmod(len(test_data), 4) - if leftover: - test_data += '=' - else: - break - # if more than 2 pad chars were added, raise an error - if i > 2: - raise ValidationError(_('Corrupted base64 hash')) - # base64-i2p - if length == 516 and re.match(r'[a-zA-Z0-9\-~]+AA$', data) == None: - raise ValidationError(_('Invalid base64 hash')) - # check ECDSA validity - if length == 524 and re.match(r'[a-zA-Z0-9\-~]+AEAAEAAA==$', data) == None: + """ + Base64 hash validation + """ + # strip leading and trailing whitespaces + data = data.strip() + length = len(data) + # check for b32 address misuse + if re.match(r'.*\.b32\.i2p$', data): + raise ValidationError(_('You should paste base64 hash, not a base32!')) + # fail if contains .i2p= (full foo.i2p=key) + if re.search(r'\.i2p=', data): + raise ValidationError(_('Do not paste full hosts.txt entry! Only base64 hash is needed')) + # check for pasting router hash + if length == 44: + raise ValidationError(_('Do not paste router hash! Go to i2ptunnel page and \ + find a destination hash')) + # Minimum key length 516 bytes + if length < 516: + raise ValidationError(_('Specified base64 hash is less than 516 bytes')) + # Maximum key length 616 bytes + if length > 616: + raise ValidationError(_('Specified base64 hash is bigger than 616 bytes')) + # keys with cert may ends with anything, so check is relaxed + if length > 516 and re.match(r'[a-zA-Z0-9\-~=]+$', data) == None: + raise ValidationError(_('Invalid characters in base64 hash')) + # base64-validity test + if length > 516: + # we need temporary variable here to avoid modifying main "data" + test_data = data + # add pad-characters needed for proper decoding cos i2p does not + for i in range(4): + quanta, leftover = divmod(len(test_data), 4) + if leftover: + test_data += '=' + else: + break + # if more than 2 pad chars were added, raise an error + if i > 2: + raise ValidationError(_('Corrupted base64 hash')) + # base64-i2p + if length == 516 and re.match(r'[a-zA-Z0-9\-~]+AA$', data) == None: + raise ValidationError(_('Invalid base64 hash')) + # check ECDSA validity + if length == 524 and re.match(r'[a-zA-Z0-9\-~]+AEAAEAAA==$', data) == None: raise ValidationError(_('Invalid base64 ECDSA hash')) - if check_uniq == True: - # Avoid adding non-unique hashes - qs = i2phost.objects.filter(b64hash=data) - if qs.exists(): - raise ValidationError(_('Some host already have the same Base64 hash')) - return data + if check_uniq == True: + # Avoid adding non-unique hashes + qs = i2phost.objects.filter(b64hash=data) + if qs.exists(): + raise ValidationError(_('Some host already have the same Base64 hash')) + return data def validate_i2purl(data): - """ Basic I2P URL validator """ - # convert to lowercase and strip leading and trailing whitespaces - data = data.lower().strip() - # check for http://, .i2p in domain and GET validity - if re.match(r'^http://(?:.+?\.i2p)(?:/?|[/?]\S+)$', data) == None: - raise ValidationError(_('Bad I2P url')) + """ Basic I2P URL validator """ + # convert to lowercase and strip leading and trailing whitespaces + data = data.lower().strip() + # check for http://, .i2p in domain and GET validity + if re.match(r'^http://(?:.+?\.i2p)(?:/?|[/?]\S+)$', data) == None: + raise ValidationError(_('Bad I2P url')) diff --git a/pyi2phosts/postkey/admin.py b/pyi2phosts/postkey/admin.py index 547d187..75a54ea 100644 --- a/pyi2phosts/postkey/admin.py +++ b/pyi2phosts/postkey/admin.py @@ -9,46 +9,46 @@ from pyi2phosts.lib.validation import validate_b64hash class i2phostAdminForm(forms.ModelForm): - """ Custom form for editing hosts via admin interface """ + """ Custom form for editing hosts via admin interface """ - def clean_name(self): - """Validate hostname""" - data = self.cleaned_data['name'] - data = validate_hostname(data) - return data + def clean_name(self): + """Validate hostname""" + data = self.cleaned_data['name'] + data = validate_hostname(data) + return data - def clean_b64hash(self): - """Validate base64 hash""" - data = self.cleaned_data['b64hash'] - data = validate_b64hash(data, check_uniq=False) - return data + def clean_b64hash(self): + """Validate base64 hash""" + data = self.cleaned_data['b64hash'] + data = validate_b64hash(data, check_uniq=False) + return data class i2phostAdmin(admin.ModelAdmin): - def url(self, hostname): - return 'b32' + def url(self, hostname): + return 'b32' - form = i2phostAdminForm - url.allow_tags = True - list_display = ('url', 'name', 'description', 'date_added', 'last_seen', 'expires', - 'activated', 'external') - list_display_links = ['name'] - list_filter = ('activated', 'external', 'approved') - search_fields = ('name', 'b64hash') - ordering = ['-date_added'] + form = i2phostAdminForm + url.allow_tags = True + list_display = ('url', 'name', 'description', 'date_added', 'last_seen', 'expires', + 'activated', 'external') + list_display_links = ['name'] + list_filter = ('activated', 'external', 'approved') + search_fields = ('name', 'b64hash') + ordering = ['-date_added'] class PendingAdmin(i2phostAdmin): - def queryset(self, request): - qs = super(PendingAdmin, self).queryset(request) - return qs.filter(approved=False) + def queryset(self, request): + qs = super(PendingAdmin, self).queryset(request) + return qs.filter(approved=False) - def approve_selected(modeladmin, request, queryset): - queryset.update(approved=True) + def approve_selected(modeladmin, request, queryset): + queryset.update(approved=True) - list_filter = [] - list_display = ('url', 'name', 'description', 'date_added', 'last_seen', 'expires', 'approved') - actions = ['approve_selected'] + list_filter = [] + list_display = ('url', 'name', 'description', 'date_added', 'last_seen', 'expires', 'approved') + actions = ['approve_selected'] admin.site.register(i2phost, i2phostAdmin) diff --git a/pyi2phosts/postkey/models.py b/pyi2phosts/postkey/models.py index 7b57ae0..a07ddca 100644 --- a/pyi2phosts/postkey/models.py +++ b/pyi2phosts/postkey/models.py @@ -2,27 +2,27 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ class i2phost(models.Model): - # Hostname limit is 67 characters maximum, including the '.i2p'. - name = models.CharField(_('I2P hostname'), max_length=67, unique=True) - # Maximum key length 616 bytes (to account for certs up to 100 bytes). - b64hash = models.CharField(_('Base 64 hash'), max_length=616) - description = models.CharField(_('Description'), max_length=4096, blank=True) - date_added = models.DateTimeField(null=True, blank=True) - # Last time this host was up - last_seen = models.DateTimeField(null=True, blank=True) - # Scheduled expiration date - expires = models.DateField(null=True, blank=True) - # Not-activated hosts will not appear in exported hosts.txt - activated = models.BooleanField(default=False) - # Indicator for hosts added from external source - external = models.BooleanField(default=False) - # Not approved hosts will not appear in exported hosts.txt - approved = models.BooleanField(default=False) + # Hostname limit is 67 characters maximum, including the '.i2p'. + name = models.CharField(_('I2P hostname'), max_length=67, unique=True) + # Maximum key length 616 bytes (to account for certs up to 100 bytes). + b64hash = models.CharField(_('Base 64 hash'), max_length=616) + description = models.CharField(_('Description'), max_length=4096, blank=True) + date_added = models.DateTimeField(null=True, blank=True) + # Last time this host was up + last_seen = models.DateTimeField(null=True, blank=True) + # Scheduled expiration date + expires = models.DateField(null=True, blank=True) + # Not-activated hosts will not appear in exported hosts.txt + activated = models.BooleanField(default=False) + # Indicator for hosts added from external source + external = models.BooleanField(default=False) + # Not approved hosts will not appear in exported hosts.txt + approved = models.BooleanField(default=False) - def __unicode__(self): - return self.name + def __unicode__(self): + return self.name class PendingHost(i2phost): - """ Proxy model needed for displaying not approved hosts in django admin separatelly """ - class Meta: - proxy = True + """ Proxy model needed for displaying not approved hosts in django admin separatelly """ + class Meta: + proxy = True diff --git a/pyi2phosts/postkey/urls.py b/pyi2phosts/postkey/urls.py index d55cc9f..dbaf391 100644 --- a/pyi2phosts/postkey/urls.py +++ b/pyi2phosts/postkey/urls.py @@ -1,7 +1,7 @@ from django.conf.urls import * urlpatterns = patterns('pyi2phosts.postkey.views', - (r'^$', 'addkey'), - (r'^success/', 'success'), - (r'^subdomain/', 'subdomain'), + (r'^$', 'addkey'), + (r'^success/', 'success'), + (r'^subdomain/', 'subdomain'), ) diff --git a/pyi2phosts/postkey/views.py b/pyi2phosts/postkey/views.py index d303622..cbae2bc 100644 --- a/pyi2phosts/postkey/views.py +++ b/pyi2phosts/postkey/views.py @@ -17,147 +17,147 @@ from pyi2phosts.lib.validation import validate_hostname from pyi2phosts.lib.validation import validate_b64hash class AddForm(forms.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) - """ - class Meta: - model = i2phost - fields = ('name', 'b64hash', 'description') - widgets = { - 'name': forms.TextInput(attrs={'size': '67'}), - 'b64hash': forms.Textarea(attrs={'rows': '1', 'cols': '100'}), - 'description': forms.Textarea(attrs={'rows': '2', 'cols': '72'}) - } - def clean_name(self): - """Validate hostname""" - data = self.cleaned_data['name'] - log.debug(u'hostname: %s', self.data['name']) - data = validate_hostname(data) - # Another set of reserved hostnames (suggested by zzz) - 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')) - return data - def clean_b64hash(self): - """Validate base64 hash""" - data = self.cleaned_data['b64hash'] - log.debug(u'hash: %s', self.data['b64hash']) - data = validate_b64hash(data) - return data - def is_valid(self): - """Log validation errors""" - is_valid = super(AddForm, self).is_valid() - if not is_valid: - for field in self.errors.keys(): - log.info('ValidationError: [%s]: \"%s\" %s', - field, self.data[field], self.errors[field].as_text()) - return is_valid + """ + This is our class for host-add form. It's based on django's ModelForm + and uses our model "i2phost" (see postkey/models.py) + """ + class Meta: + model = i2phost + fields = ('name', 'b64hash', 'description') + widgets = { + 'name': forms.TextInput(attrs={'size': '67'}), + 'b64hash': forms.Textarea(attrs={'rows': '1', 'cols': '100'}), + 'description': forms.Textarea(attrs={'rows': '2', 'cols': '72'}) + } + def clean_name(self): + """Validate hostname""" + data = self.cleaned_data['name'] + log.debug(u'hostname: %s', self.data['name']) + data = validate_hostname(data) + # Another set of reserved hostnames (suggested by zzz) + 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')) + return data + def clean_b64hash(self): + """Validate base64 hash""" + data = self.cleaned_data['b64hash'] + log.debug(u'hash: %s', self.data['b64hash']) + data = validate_b64hash(data) + return data + def is_valid(self): + """Log validation errors""" + is_valid = super(AddForm, self).is_valid() + if not is_valid: + for field in self.errors.keys(): + log.info('ValidationError: [%s]: \"%s\" %s', + field, self.data[field], self.errors[field].as_text()) + return is_valid class SubdomainVerifyForm(forms.Form): - """Form for displaying verification filename and code when verifying a subdomain""" - filename = forms.CharField(label=_('Filename'), widget=forms.TextInput(attrs={ - 'size': '20', - 'readonly': 'readonly', - 'onclick': 'this.select();', - })) + """Form for displaying verification filename and code when verifying a subdomain""" + filename = forms.CharField(label=_('Filename'), widget=forms.TextInput(attrs={ + 'size': '20', + 'readonly': 'readonly', + 'onclick': 'this.select();', + })) def save_host(request): - """Function for saving hosts after validation or subdomain verification""" - # avoid race conditions - try: - h = i2phost.objects.get(name=request.session['hostname']) - except i2phost.DoesNotExist: - host = i2phost(name=request.session['hostname'], - b64hash=request.session['b64hash'], - description=request.session['description'], - date_added=datetime.datetime.utcnow()) - host.save() - return redirect('pyi2phosts.postkey.views.success') - else: - log.warning('refusing to save already existed host: %s', request.session['hostname']) - request.session.flush() - return redirect('/') + """Function for saving hosts after validation or subdomain verification""" + # avoid race conditions + try: + h = i2phost.objects.get(name=request.session['hostname']) + except i2phost.DoesNotExist: + host = i2phost(name=request.session['hostname'], + b64hash=request.session['b64hash'], + description=request.session['description'], + date_added=datetime.datetime.utcnow()) + host.save() + return redirect('pyi2phosts.postkey.views.success') + else: + log.warning('refusing to save already existed host: %s', request.session['hostname']) + request.session.flush() + return redirect('/') def addkey(request): - if request.method == 'POST': - form = AddForm(request.POST) - if form.is_valid(): - request.session['hostname'] = form.cleaned_data['name'] - request.session['b64hash'] = form.cleaned_data['b64hash'] - request.session['description'] = form.cleaned_data['description'] - if form.cleaned_data['name'].count('.') > 1: - return redirect('pyi2phosts.postkey.views.subdomain') - else: - log.debug('submit is valid, saving') - s = save_host(request) - return s - else: - form = AddForm() - return render_to_response('postkey.html', { - 'title': settings.SITE_NAME, - 'form': form, - }, context_instance=RequestContext(request)) + if request.method == 'POST': + form = AddForm(request.POST) + if form.is_valid(): + request.session['hostname'] = form.cleaned_data['name'] + request.session['b64hash'] = form.cleaned_data['b64hash'] + request.session['description'] = form.cleaned_data['description'] + if form.cleaned_data['name'].count('.') > 1: + return redirect('pyi2phosts.postkey.views.subdomain') + else: + log.debug('submit is valid, saving') + s = save_host(request) + return s + else: + form = AddForm() + return render_to_response('postkey.html', { + 'title': settings.SITE_NAME, + 'form': form, + }, context_instance=RequestContext(request)) def success(request): - if 'hostname' in request.session: - hn = request.session['hostname'] - request.session.flush() - return render_to_response('success_submission.html', { - 'title': settings.SITE_NAME, - 'hostname': hn, - }, context_instance=RequestContext(request)) - else: - return redirect('/') + if 'hostname' in request.session: + hn = request.session['hostname'] + request.session.flush() + return render_to_response('success_submission.html', { + 'title': settings.SITE_NAME, + 'hostname': hn, + }, context_instance=RequestContext(request)) + else: + return redirect('/') def subdomain(request): - """Subdomain verification""" - if request.method == 'POST': - form = SubdomainVerifyForm(request.POST) - if form.is_valid(): - # do verification here, then redirect to success - proxy_handler = urllib2.ProxyHandler({'http': settings.EEPROXY_URL}) - opener = urllib2.build_opener(proxy_handler) - if 'topdomain' in request.session and 'v_filename' in request.session: - url = 'http://' + request.session['topdomain'] + '/' + request.session['v_filename'] - else: - log.warning('trying to call subdomain validation without a session') - return redirect('/') - log.info('starting http-verification of subdomain: %s', request.session['hostname']) - try: - log.debug('trying to open %s', url) - resp = opener.open(url, timeout=60) - except urllib2.URLError, e: - if hasattr(e, 'reason'): - log.warning('%s: failed to reach server, reason: %s', request.session['topdomain'], e.reason) - elif hasattr(e, 'code'): - log.warning('%s can\'t finish the request, error code: %s', - request.session['topdomain'], e.code) - return render_to_response('subdomain_http_verify_failure.html', { - 'title': settings.SITE_NAME, - 'code': e.code, - }, context_instance=RequestContext(request)) - else: - log.debug('subdomain verification success, saving host') - s = save_host(request) - return s - else: - # generate verification code and display info page to user - v_filename = ''.join([random.choice(string.letters + string.digits) for x in xrange(16)]) - if 'hostname' in request.session: - m = re.match('.+\.(.+\.i2p$)', request.session['hostname']) - topdomain = m.group(1) - else: - return redirect('/') - # save needed variables in session data because otherwise it will be lost - request.session['v_filename'] = v_filename - request.session['topdomain'] = topdomain - form = SubdomainVerifyForm({'filename': v_filename}) - return render_to_response('subdomain_http_verify.html', { - 'title': settings.SITE_NAME, - 'hostname': request.session['hostname'], - 'topdomain': topdomain, - 'form': form, - }, context_instance=RequestContext(request)) + """Subdomain verification""" + if request.method == 'POST': + form = SubdomainVerifyForm(request.POST) + if form.is_valid(): + # do verification here, then redirect to success + proxy_handler = urllib2.ProxyHandler({'http': settings.EEPROXY_URL}) + opener = urllib2.build_opener(proxy_handler) + if 'topdomain' in request.session and 'v_filename' in request.session: + url = 'http://' + request.session['topdomain'] + '/' + request.session['v_filename'] + else: + log.warning('trying to call subdomain validation without a session') + return redirect('/') + log.info('starting http-verification of subdomain: %s', request.session['hostname']) + try: + log.debug('trying to open %s', url) + resp = opener.open(url, timeout=60) + except urllib2.URLError, e: + if hasattr(e, 'reason'): + log.warning('%s: failed to reach server, reason: %s', request.session['topdomain'], e.reason) + elif hasattr(e, 'code'): + log.warning('%s can\'t finish the request, error code: %s', + request.session['topdomain'], e.code) + return render_to_response('subdomain_http_verify_failure.html', { + 'title': settings.SITE_NAME, + 'code': e.code, + }, context_instance=RequestContext(request)) + else: + log.debug('subdomain verification success, saving host') + s = save_host(request) + return s + else: + # generate verification code and display info page to user + v_filename = ''.join([random.choice(string.letters + string.digits) for x in xrange(16)]) + if 'hostname' in request.session: + m = re.match('.+\.(.+\.i2p$)', request.session['hostname']) + topdomain = m.group(1) + else: + return redirect('/') + # save needed variables in session data because otherwise it will be lost + request.session['v_filename'] = v_filename + request.session['topdomain'] = topdomain + form = SubdomainVerifyForm({'filename': v_filename}) + return render_to_response('subdomain_http_verify.html', { + 'title': settings.SITE_NAME, + 'hostname': request.session['hostname'], + 'topdomain': topdomain, + 'form': form, + }, context_instance=RequestContext(request)) log = get_logger(filename=settings.LOG_FILE, log_level=settings.LOG_LEVEL) diff --git a/pyi2phosts/search/urls.py b/pyi2phosts/search/urls.py index 8196277..7a0b51c 100644 --- a/pyi2phosts/search/urls.py +++ b/pyi2phosts/search/urls.py @@ -2,5 +2,5 @@ from django.conf.urls import * from pyi2phosts.search.views import SearchedHostsListsView urlpatterns = patterns('', - (r'^$', SearchedHostsListsView.as_view()), + (r'^$', SearchedHostsListsView.as_view()), ) diff --git a/pyi2phosts/search/views.py b/pyi2phosts/search/views.py index 343362c..71883c5 100644 --- a/pyi2phosts/search/views.py +++ b/pyi2phosts/search/views.py @@ -5,14 +5,14 @@ from pyi2phosts.lib.generic import HostsListsView class SearchedHostsListsView(HostsListsView): - """ Renders list of hosts matching search request """ + """ Renders list of hosts matching search request """ - def get_queryset(self): - q = self.request.GET.get('q', '') - fil = Q(name__icontains=q) | Q(b64hash__contains=q) - queryset = i2phost.objects.filter(fil) - return queryset + def get_queryset(self): + q = self.request.GET.get('q', '') + fil = Q(name__icontains=q) | Q(b64hash__contains=q) + queryset = i2phost.objects.filter(fil) + return queryset - template_name = 'search_results.html' - template_object_name = 'host_list' - paginate_by = 40 + template_name = 'search_results.html' + template_object_name = 'host_list' + paginate_by = 40 diff --git a/pyi2phosts/settings.py b/pyi2phosts/settings.py index 4f1d4fd..1e9b0dd 100644 --- a/pyi2phosts/settings.py +++ b/pyi2phosts/settings.py @@ -37,9 +37,9 @@ TIME_ZONE = 'America/Chicago' LANGUAGE_CODE = 'en-us' LANGUAGES = ( - ('en', 'English'), - ('ru', 'Russian'), - ) + ('en', 'English'), + ('ru', 'Russian'), + ) SITE_ID = 1 @@ -139,6 +139,6 @@ EEPROXY_URL = 'http://127.0.0.1:4444' # include local settings try: - from local_settings import * + from local_settings import * except ImportError: - pass + pass diff --git a/pyi2phosts/static-common/base.css b/pyi2phosts/static-common/base.css index ee70805..b37ba98 100644 --- a/pyi2phosts/static-common/base.css +++ b/pyi2phosts/static-common/base.css @@ -1,133 +1,133 @@ html, body { - font-size: 12pt; - background: #E6E6D1; - color: #000000; + font-size: 12pt; + background: #E6E6D1; + color: #000000; } input, textarea { - background-color: #AEB08D; - color: #000; - border: 1px solid #000; + background-color: #AEB08D; + color: #000; + border: 1px solid #000; } label { - width: 8em; - float: top; - text-align: left; - margin-right: 1em; - display: block; + width: 8em; + float: top; + text-align: left; + margin-right: 1em; + display: block; } a { - background: inherit; - color: #6E735E; - text-decoration: none; + background: inherit; + color: #6E735E; + text-decoration: none; } a:visited { - background: inherit; - color: #6E735E; - text-decoration: none; + background: inherit; + color: #6E735E; + text-decoration: none; } a:hover { - color: #000; - background: inherit; + color: #000; + background: inherit; } table { - border-collapse: collapse; - width: 90%; - margin: 10px 0px 10px 0px; + border-collapse: collapse; + width: 90%; + margin: 10px 0px 10px 0px; } tr:first-child { - border: 1px solid #CCCCCC; - font-weight: bold; + border: 1px solid #CCCCCC; + font-weight: bold; } tr { - border: 1px dashed #CCCCCC; + border: 1px dashed #CCCCCC; } td { - max-width: 220px; + max-width: 220px; } div.menu { - float: left; - margin: 0px 20px 20px 0px; - padding: 10px 20px 20px 0px; - border-left: solid 1px #CCCCCC; - text-align: left; - color: black; - font-size: 8pt; - clear: left; /* fixes a bug in Opera */ - width: 160px; + float: left; + margin: 0px 20px 20px 0px; + padding: 10px 20px 20px 0px; + border-left: solid 1px #CCCCCC; + text-align: left; + color: black; + font-size: 8pt; + clear: left; /* fixes a bug in Opera */ + width: 160px; } .menu li { - margin-left: .5em; - margin-top: .4em; - padding-left: .5em; - line-height: 1.2; - list-style-type: none; - list-style-position: outside; + margin-left: .5em; + margin-top: .4em; + padding-left: .5em; + line-height: 1.2; + list-style-type: none; + list-style-position: outside; } .menu ol, ul { - padding-left: 0em; - margin-top: 0em; - padding-top: 0em; + padding-left: 0em; + margin-top: 0em; + padding-top: 0em; } .errorlist { - color: red; - padding-left: 5em; + color: red; + padding-left: 5em; } div.main { - margin: 0px 0px 0px 0px; - padding: 22px 60px 20px 220px; - text-align: justify; - color: #000011; + margin: 0px 0px 0px 0px; + padding: 22px 60px 20px 220px; + text-align: justify; + color: #000011; } div.main li { - margin-left: 15px; + margin-left: 15px; } div.footer { - font-size: 8pt; - text-align: center; - padding: 8px 0 0 0; + font-size: 8pt; + text-align: center; + padding: 8px 0 0 0; } div.search_host { - position:absolute; - right: 15px; + position:absolute; + right: 15px; } div.search_host .input input { - font-size: 10px; - color: #6E735E; - width: 200px; + font-size: 10px; + color: #6E735E; + width: 200px; } .pager { - padding-top: 20px; - padding-left: 100px; - font-size: 8pt; + padding-top: 20px; + padding-left: 100px; + font-size: 8pt; } .pager .page a { - border: 1px solid #bbbbbb; - margin-left: 1px; - margin-right: 1px; - padding: 0px 5px 0px 5px; - text-decoration: none; - color: #000000; + border: 1px solid #bbbbbb; + margin-left: 1px; + margin-right: 1px; + padding: 0px 5px 0px 5px; + text-decoration: none; + color: #000000; } .pager .current { - border: 2px solid #444444; - margin-left: 2px; - margin-right: 2px; - padding: 0px 5px 0px 5px; + border: 2px solid #444444; + margin-left: 2px; + margin-right: 2px; + padding: 0px 5px 0px 5px; } diff --git a/pyi2phosts/static-common/inproxy.html b/pyi2phosts/static-common/inproxy.html index d340f42..010af3a 100644 --- a/pyi2phosts/static-common/inproxy.html +++ b/pyi2phosts/static-common/inproxy.html @@ -1,13 +1,13 @@ - FAIL + FAIL - + -

Non-I2P access denied

+

Non-I2P access denied

-
py-i2phosts instance
+
py-i2phosts instance
- + diff --git a/pyi2phosts/static-common/rss-grey-18.png b/pyi2phosts/static-common/rss-grey-18.png index 19b8c923943fb03a21b0388724a22910a91e7cc7..306eabf6a920fb85c9a38d473e6f200e8a5c3ca1 100644 GIT binary patch delta 39 ocmbQox`cH?oQwhx6nI1yGca%qgD@k*tT_@43=AOgjU`%40IEy}w*UYD delta 33 mcmZ3&I*)ZioET?;M`SSr1Gg{;GcwGYBf-GHz`3zXiwOXMC

404 Not Found

+

404 Not Found

{% endblock %} diff --git a/pyi2phosts/templates/500.html b/pyi2phosts/templates/500.html index d430a40..c50af92 100644 --- a/pyi2phosts/templates/500.html +++ b/pyi2phosts/templates/500.html @@ -1,5 +1,5 @@ {% extends "base.html" %} {% block content %} -

500 Internal server error

+

500 Internal server error

{% endblock %} diff --git a/pyi2phosts/templates/base.html b/pyi2phosts/templates/base.html index 9556961..a55ac1e 100644 --- a/pyi2phosts/templates/base.html +++ b/pyi2phosts/templates/base.html @@ -1,65 +1,65 @@ {% load i18n %} - - - {% block title %} - {{ title }} - {% endblock %} - - {% block head %} - {% endblock %} - - - -
-
- - -
-
+ + + {% block title %} + {{ title }} + {% endblock %} + + {% block head %} + {% endblock %} + + + +
+
+ + +
+
- {% block navigation %} - + {% block navigation %} + -
- {% csrf_token %} - {% for lang in LANGUAGES %} - - {{ lang.0 }} - {% endfor %} - -
+
+ {% csrf_token %} + {% for lang in LANGUAGES %} + + {{ lang.0 }} + {% endfor %} + +
- {% endblock %} + {% endblock %} -
- {% block header %} - {% endblock %} +
+ {% block header %} + {% endblock %} - {% block content %} - {% endblock %} -
+ {% block content %} + {% endblock %} +
- {% block footer %} -
- - {% endblock %} - + {% block footer %} +
+ + {% endblock %} + diff --git a/pyi2phosts/templates/browse.html b/pyi2phosts/templates/browse.html index b061cd6..b15bc76 100644 --- a/pyi2phosts/templates/browse.html +++ b/pyi2phosts/templates/browse.html @@ -11,10 +11,10 @@ {% endblock table_header %} - {% for host in host_list %} - - - {% endfor %} + {% for host in host_list %} + + + {% endfor %}
{% trans "Host" %}{% trans "Last seen" %}{% trans "Date added" %}{% trans "Description" %}
{{ host.name }}{{ host.last_seen }}{{ host.date_added }}{{ host.description }}
{{ host.name }}{{ host.last_seen }}{{ host.date_added }}{{ host.description }}
{% if is_paginated %} diff --git a/pyi2phosts/templates/contacts.html b/pyi2phosts/templates/contacts.html index f5ee3f6..7be1b89 100644 --- a/pyi2phosts/templates/contacts.html +++ b/pyi2phosts/templates/contacts.html @@ -5,17 +5,17 @@ {% blocktrans %}

Direct contact methods:

{% endblocktrans %} {% blocktrans %}

Public discussions about service, feedback, proposals and feature requests

{% endblocktrans %} {% endblock %} diff --git a/pyi2phosts/templates/faq.html b/pyi2phosts/templates/faq.html index 4af70da..986116f 100644 --- a/pyi2phosts/templates/faq.html +++ b/pyi2phosts/templates/faq.html @@ -4,24 +4,24 @@ {% block content %}

{% trans "How we are learning about new hosts" %}

    -
  1. {% trans "Pulling from external sources:" %} -
      - {% for source in sources_list %} -
    • {{ source.url }}
    • - {% endfor %} -
    -
  2. -
  3. {% trans "Adding through our service." %} -
  4. +
  5. {% trans "Pulling from external sources:" %} +
      + {% for source in sources_list %} +
    • {{ source.url }}
    • + {% endfor %} +
    +
  6. +
  7. {% trans "Adding through our service." %} +
{% blocktrans %}

Publishing requirements

To get published a host must meet the following criteria:

Admin's approval isn't really necessary, it is only needed in order to eliminate possible hijacking and mass registration attempts. diff --git a/pyi2phosts/templates/index.html b/pyi2phosts/templates/index.html index 17edb91..d604a7c 100644 --- a/pyi2phosts/templates/index.html +++ b/pyi2phosts/templates/index.html @@ -2,55 +2,55 @@ {% load i18n %} {% block content %} - {% url 'faq' as faq_url %} - {% blocktrans %} -

About

-

{{ 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 - way to publish hosts as an easy means of access to them. You can read more about how - I2P naming works in the official - docs. -

+ {% url 'faq' as faq_url %} + {% blocktrans %} +

About

+

{{ 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 + way to publish hosts as an easy means of access to them. You can read more about how + I2P naming works in the official + docs. +

-

To find out how we're registering and publishing hosts, look at - FAQ page. -

- {% endblocktrans %} +

To find out how we're registering and publishing hosts, look at + FAQ page. +

+ {% endblocktrans %} - {% blocktrans %} -

Addressbook service

-

- To start getting new hostnames from {{ title }}, add this - subscription link into your router's - addressbook. Of course, you should add INR's destination before. -

- {% endblocktrans %} + {% blocktrans %} +

Addressbook service

+

+ To start getting new hostnames from {{ title }}, add this + subscription link into your router's + addressbook. Of course, you should add INR's destination before. +

+ {% endblocktrans %} - {% url 'pyi2phosts.jump.views.jumper' 'example.i2p' as jump_url %} - {% url 'pyi2phosts.jump.views.index' as jump_index %} - {% blocktrans %} -

Jump service

- {{ title }} also provides a jump service. For accessing hosts through it, - use urls like - - http://{{ domain }}{{ jump_url }}. - I2P since 0.8.3 gives possibility to add a custom jump-servers. Go to the i2ptunnel - eeproxy configuration page - and add http://{{ domain }}{{ jump_index }} to "Jump URL List" section. -

- {% endblocktrans %} + {% url 'pyi2phosts.jump.views.jumper' 'example.i2p' as jump_url %} + {% url 'pyi2phosts.jump.views.index' as jump_index %} + {% blocktrans %} +

Jump service

+ {{ title }} also provides a jump service. For accessing hosts through it, + use urls like + + http://{{ domain }}{{ jump_url }}. + I2P since 0.8.3 gives possibility to add a custom jump-servers. Go to the i2ptunnel + eeproxy configuration page + and add http://{{ domain }}{{ jump_index }} to "Jump URL List" section. +

+ {% endblocktrans %} - {% url 'pyi2phosts.postkey.views.addkey' as addkey_url %} - {% blocktrans %} -

Registration service

-

If you are running an eepsite or another service and want a human-readable domain name - for them, consider registering it. -

- {% endblocktrans %} + {% url 'pyi2phosts.postkey.views.addkey' as addkey_url %} + {% blocktrans %} +

Registration service

+

If you are running an eepsite or another service and want a human-readable domain name + for them, consider registering it. +

+ {% endblocktrans %} {% endblock %} {% block footer-addon %} - b32 | {% trans "add" %} | + b32 | {% trans "add" %} | {% endblock %} diff --git a/pyi2phosts/templates/policy.html b/pyi2phosts/templates/policy.html index d484aff..4e32f7b 100644 --- a/pyi2phosts/templates/policy.html +++ b/pyi2phosts/templates/policy.html @@ -2,14 +2,14 @@

{% trans "Domain name registration policy" %}

diff --git a/pyi2phosts/templates/postkey.html b/pyi2phosts/templates/postkey.html index 9946e4f..e1cd91e 100644 --- a/pyi2phosts/templates/postkey.html +++ b/pyi2phosts/templates/postkey.html @@ -3,10 +3,10 @@ {% block content %} {% include "policy.html" %} -
- {% csrf_token %} -

{% trans "Enter your domain details" %}

- {{ form.as_p }} - -
+
+ {% csrf_token %} +

{% trans "Enter your domain details" %}

+ {{ form.as_p }} + +
{% endblock %} diff --git a/pyi2phosts/templates/subdomain_http_verify.html b/pyi2phosts/templates/subdomain_http_verify.html index 0e1ba43..6d4dcdc 100644 --- a/pyi2phosts/templates/subdomain_http_verify.html +++ b/pyi2phosts/templates/subdomain_http_verify.html @@ -15,10 +15,10 @@ This file should be accessible via http://{{ topdomain }}/«filename»

{% endblocktrans %} -
- {% csrf_token %} - {{ form.as_p }} - -
+
+ {% csrf_token %} + {{ form.as_p }} + +
{% endblock %} diff --git a/pyi2phosts/urls.py b/pyi2phosts/urls.py index b3dde76..3b9e578 100644 --- a/pyi2phosts/urls.py +++ b/pyi2phosts/urls.py @@ -13,18 +13,18 @@ from pyi2phosts.lib.generic import HostsListsView urlpatterns = patterns('', - 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'^faq/$', FaqView.as_view(), name='faq'), - url(r'^browse/$', HostsListsView.as_view(), name='browse'), - url(r'^browse/rss/$', AliveHostsFeed(), name='browse-rss'), - - (r'^latest/', include('pyi2phosts.latest.urls')), - (r'^search/$', include('pyi2phosts.search.urls')), - (r'^postkey/', include('pyi2phosts.postkey.urls')), - (r'^jump/', include('pyi2phosts.jump.urls')), - (r'^api/', include('pyi2phosts.api.urls')), - (r'^i18n/', include('django.conf.urls.i18n')), + 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'^faq/$', FaqView.as_view(), name='faq'), + url(r'^browse/$', HostsListsView.as_view(), name='browse'), + url(r'^browse/rss/$', AliveHostsFeed(), name='browse-rss'), + + (r'^latest/', include('pyi2phosts.latest.urls')), + (r'^search/$', include('pyi2phosts.search.urls')), + (r'^postkey/', include('pyi2phosts.postkey.urls')), + (r'^jump/', include('pyi2phosts.jump.urls')), + (r'^api/', include('pyi2phosts.api.urls')), + (r'^i18n/', include('django.conf.urls.i18n')), # Example: # (r'^pyi2phosts.', include('pyi2phosts.foo.urls')), @@ -37,5 +37,5 @@ urlpatterns = patterns('', ) if settings.DEBUG: - urlpatterns += patterns('', (r'static/(?P.*)$', 'django.views.static.serve', - {'document_root': settings.MEDIA_ROOT, 'show_indexes':True})) + urlpatterns += patterns('', (r'static/(?P.*)$', 'django.views.static.serve', + {'document_root': settings.MEDIA_ROOT, 'show_indexes':True})) diff --git a/setup.py b/setup.py index 1ec93c3..abd83dc 100644 --- a/setup.py +++ b/setup.py @@ -8,28 +8,28 @@ setup( author_email='hiddenz@mail.i2p', url='http://py-i2phosts.i2p/', packages=['pyi2phosts', - 'pyi2phosts.postkey', - 'pyi2phosts.postkey.templatetags', - 'pyi2phosts.jump', - 'pyi2phosts.extsources', - 'pyi2phosts.lib', - 'pyi2phosts.search', - 'pyi2phosts.latest'], + 'pyi2phosts.postkey', + 'pyi2phosts.postkey.templatetags', + 'pyi2phosts.jump', + 'pyi2phosts.extsources', + 'pyi2phosts.lib', + 'pyi2phosts.search', + 'pyi2phosts.latest'], package_dir = {'': ''}, 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', - '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', - 'conf/maintainer.conf', 'conf/builder.conf', 'conf/common.conf', 'conf/injector.conf'],), - ('/var/log/py-i2phosts', ['.placeholder'],), - ('/var/run/py-i2phosts', ['.placeholder'],),], + 'conf/maintainer.conf', 'conf/builder.conf', 'conf/common.conf', 'conf/injector.conf'],), + ('/var/log/py-i2phosts', ['.placeholder'],), + ('/var/run/py-i2phosts', ['.placeholder'],),], classifiers=[ 'Development Status :: 4 - Beta', - 'Environment :: Console', + 'Environment :: Console', 'Environment :: Web Environment', 'Intended Audience :: Developers', - 'Intended Audience :: System Administrators', + 'Intended Audience :: System Administrators', 'License :: OSI Approved :: GPL License', 'Operating System :: Linux', 'Programming Language :: Python',